*

Atmel Ultrasonic Ranger Source Code

/* Filename : test8535.c
 *
 * The first test of the ATmega8535 microcontroller.
 *
 * Use an ultrasonic transmitter and receiver to calculate the distance
 * to a target.
 *
 * Author       : Craig Dunn (craig@craigsarea.com)
 * Website      : www.craigsarea.com
 * Date Started : 2 August 2004
 * Crystal      : 16MHz
 *
 **********************************************************************/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <avr/iom8535.h>
#include <string.h>


/* Defines
 **********************************************************************/
#define FOSC              16000000L
#define BAUDRATE          38400L
#define UBRR              ((FOSC / (16 * BAUDRATE)) - 1)

#define NUM_40KHZ_PULSES  5  /* Always does one less pulse. */

#define TRUE              1
#define FALSE             0

#define F_CPU             4000000
#define K_DELAY_100us     F_CPU/61349
#define K_DELAY_1ms       F_CPU/6013
#define K_DELAY_10ms      F_CPU/600


/* Custom Types
 **********************************************************************/
typedef unsigned char  U8;
typedef unsigned int   U16;
typedef char           S8;
typedef int            S16;
typedef unsigned char  BOOL;


/* Function Prototypes
 **********************************************************************/
void usart_init      (void);
void usart_tx_byte   (U8 tx_reg);
void usart_tx_str    (char *str);


/* Global Variables
 **********************************************************************/
U8 pulse_count;
U16 distance;
volatile BOOL pulses_sent;
volatile BOOL echo_received;
volatile BOOL echo_missed;



/* FUNCTION : usart_init
 *
 * Initialise the USART.
 **********************************************************************/
void usart_init(void)
{
    UCSRB = _BV(RXEN) | _BV(TXEN) | _BV(RXCIE);
    UCSRC = _BV(URSEL) | _BV(UCSZ1) | _BV(UCSZ0);
    UBRRH = (U8)UBRR >> 8;
    UBRRL = (U8)UBRR;
}



/* FUNCTION : usart_tx_byte
 *
 * Wait for the USART to become available and then send out tx_reg.
 **********************************************************************/
void usart_tx_byte(U8 tx_reg)
{    
    while(!(UCSRA & (1 << UDRE)))
    {
        /* Make sure GCC doesn't optimize this while loop out. */
        asm volatile("nop");
    }
    
    UDR = tx_reg;
}



/* FUNCTION : usart_tx_str
 *
 * Send the null terminated string pointed to by *str out of the serial
 * port.
 **********************************************************************/
void usart_tx_str(char *str)
{
    char *ptr;
    
    for(ptr = str; *ptr != '\0'; *ptr++)
        usart_tx_byte(*ptr);
}



/* FUNCTION : 
 *
 * (15 + t*( ((K_DELAY_100us-1)*6)+5 ))
 **********************************************************************/
void delay_100us(U16 t)
{
    volatile U16 i;

    while(t > 0)
    {
        for(i=0; i < K_DELAY_100us; i++)
        {
        }
        
        --t;
    }
}



/* FUNCTION : 
 *
 * (15 + t*( ((K_DELAY_1ms-1)*6)+5 ))
 **********************************************************************/
void delay_1ms(U16 t)
{
    volatile U16 i;

    while(t > 0)
    {
        for(i=0; i < K_DELAY_1ms; i++)
        {
        }
        
        --t;
    }
}



/* FUNCTION : SIGNAL(SIG_UART_RECV)
 *
 * USART RX interrupt.  Just send back whatever is received.
 **********************************************************************/
SIGNAL(SIG_UART_RECV)
{
   usart_tx_byte(UDR);
}



/* FUNCTION : SIGNAL(SIG_OVERFLOW2)
 *
 * At the end of each PWM pulse this interrupt occurs.  Either get 
 * ready for the next pulse or stop the PWM (the correct number of
 * pulses has been sent).
 **********************************************************************/
SIGNAL(SIG_OVERFLOW2)
{    
    if(pulse_count == 0)
    {            
        pulse_count = NUM_40KHZ_PULSES;  /* Get ready for the next set of pulses. */
        TCCR2 = TCCR2 & (~0x07);  /* Stop the PWM generation. */
        
        pulses_sent = TRUE;
    }
    else
    {
        TCNT2 = 130;
        pulse_count--;
    }
}



/* FUNCTION : SIGNAL(SIG_OVERFLOW1)
 *
 * The Capture/Compare timer has overflowed, therefore no echo was
 * received in time.
 **********************************************************************/
SIGNAL(SIG_OVERFLOW1)
{
    TCNT1 = 0;
    echo_missed = TRUE;
}



/* FUNCTION : SIGNAL(SIG_INPUT_CAPTURE1)
 *
 * A rising edge has been detected by the Capture/Compare module.  This
 * is the start of the echo.
 **********************************************************************/
SIGNAL(SIG_INPUT_CAPTURE1)
{    
    distance = ICR1;
    echo_received = TRUE;
}



/* FUNCTION : main
 *
 * Main implementation.
 **********************************************************************/
int main(void)
{    
    char *string;
        
    
    cli();
    usart_init();
    

    /* D7(PWM OUT)  D6(CAPTURE IN) */
    DDRD  = 0xBF;
    PORTD = 0x00;
        

    while(1)
    {        
        echo_received = FALSE;
        echo_missed   = FALSE;
        pulses_sent   = FALSE;
        pulse_count   = NUM_40KHZ_PULSES;
        
        
        /* PWM Output.
           The overflow interrupt counts the number of PWM pulses
           sent out and disables the PWM channel when the correct
           number have gone. */
        TCCR2 = _BV(WGM20)    /* PWM Waveform. */
              | _BV(CS20)     /* No Prescaler. */
              | _BV(COM21);   /* Clear on up counting. */
        OCR2 = 50;            /* 50% Duty Cycle on the PWM signal. */
        
        
        TIMSK = _BV(TOIE2);    /* Overflow Interrupt (PWM Output). */
        sei();
        
        
        /* wait for the pulses to go. */
        while(pulses_sent == FALSE)
        {
        }
        
        
        /* Make sure we don't pick up any cross talk between
           the transmitter and receiver. */
        delay_100us(2);
        
        cli();
              
            
        /* Counter Input Capture.
           Start a timer and generate an interrupt on the first 
           rising edge of the received ultrasonic echo. */
        TCCR1B = _BV(ICES1)   /* Interrupt on the rising edge. */
               | _BV(ICNC1)   /* Enable the noise canceller. */ 
               | _BV(CS11)
               | _BV(CS10);   /* CS10 and CS11 create a prescaler of 64. */
               
        TCNT1 = 0;
        TIFR  = _BV(TOV1)     /* Enable an Input Capture Overflow Interrupt. */
              | _BV(ICF1);    /* Enable an Input Capture Interrupt. */
        
        TIMSK = _BV(TOIE1)    /* Overflow Interrupt (Capture Input). */
              | _BV(TICIE1);  /* Capture Interrupt. */
    
        sei();
        
        /* Wait for some echo activity. */
        while(echo_received == FALSE && echo_missed == FALSE)
        {
        }
            
        
        /* Convert the echo to a distance and send it out of the
           serial port. */
        if(echo_received == TRUE)
        {
            /* Speed of sound = 1cm every 30us
               Timer Prescaler = 64
               External Crystal = 16MHz
               Timer Updates Every (1/16MHz) * 64 = 4uS
               
               distance = (TIMER_VALUE * 4) / 30 / 2 = TIMER_VALUE / 15 */
            distance = distance / 15;
            
            sprintf(string, "%dcm\r", distance);
            usart_tx_str(string);
        }
        
        
        /* Create a long delay before starting again.  This is so 
           we don't detect any double echos. */
        delay_1ms(1000);
    }
    
    
    return 0;
}