10-bit A/D Data Sampling and Transmission
This code sets up a timer to overflow at 1KHz. When the timer overflows an ADC channel is sampled with the full 10-bits of resolution available on the ATMega328P. The data is then transmitted over the UART at 38400bps, 8N1 configuration. The code had preprocessor directives to choose between big and little endian order for data transmission.
Additionally, every 512 overflows the heartbeat LED is toggled so it flashes at roughly a 1Hz rate.
/**@file endianness.c
@brief Code to transmit 16-bit ADC samples in big or little-endian order
@author Stephen Friederichs
@date 5/12/13
ADC Channels:
0 - Accelerometer X axis (Vertical)
1 - Accelerometer Y axis (Horizontal)
2 - Accelerometer Z axis (Lateral)
3 - Accelerometer 0G detect (Freefall detect)
The heartbeat LED is on Port D, pin 7
*/
/**@def F_CPU
@brief Clock frequency = 8MHZ - this is set by fuses and registers, not by this define
@note Always define this before including delay.h!
*/
#define F_CPU 8000000
/**@include io.h
@brief Include for AVR I/O register definitions
*/
#include <avr/io.h>
/**@include stdint.h
@brief Include for standard integer definitions (ie, uint8_t, int32_t, etc)
*/
#include <stdint.h>
/**@include delay.h
@brief Include for delay functions such as _delay_ms() and _delay_us()
*/
#include <util/delay.h>
/* Basic bit manipulation macros - everyone should use these. Please, steal these! Don't not use them and
don't rewrite them yourself!
*/
#define SET(x,y) x |= (1 << y)
#define CLEAR(x,y) x &= ~(1<< y)
#define READ(x,y) ((0x00 == ((x & (1<<y))>> y))?0x00:0x01)
#define TOGGLE(x,y) (x ^= (1 << y))
int main(void)
{
//Variable to count the number of times the timer interrupt has fired
uint16_t ticks = 0;
uint16_t accel_data = 0;
uint8_t transmit_enable = 0x00;
uint8_t * uart_data_pointer = &accel_data;
/*Initialization Code*/
/* ATMega328 Datasheet Table 14-1 Pg 78
Configure PD7 for use as Heartbeat LED
Set as Output Low (initially)
*/
SET(DDRD,7); //Direction: output
CLEAR(PORTD,7); //State: Lo
/* TCCR1A - ATMega328 Datasheet Section 16.11.2 pg 134 - TCCR1A
No input capture used - bits 7:6 are 0
No waveform generation used - bits 4:3 are 0
Clock source select is bits 2:0 but are not yet set - wait until the
main loop is ready to start
*/
TCCR1A = 0x00;
/* TCCR1C - ATMega328 Datasheet Section 16.11.3 pg 135
This register is only used for output compare.
There's no output compare in this application so this can be all 0's
*/
TCCR1C = 0x00;
/* TCCR1B
Note: I've disabled the CKDIV8 fuse so that the clock source is 8MHz
As per ATMega328 Datasheet Section 16.9.1 page 123, setting the timer
to Normal mode causes the counter to count up until it reaches 0xFFFF
at which point it will overrun and start back at 0. To configure this
timer/counter to produce a period of 1ms we need to start counting
at a value that causes it to reach 65535 in 1ms.
What is that value?
With a clock prescaler of 32 each count of the timer is roughly
(1/8MHz)*32 = 1uS
1ms / 1us /tick = 1000 ticks /ms
The counter counts up to 65536, so to determine what value we have to
start at we subtract 1000 from 65536:
65536-1000 = 64536
*/
#define TIMER1_PERIOD 64536
TCNT1 = TIMER1_PERIOD;
//Configure ADC to read accelerometer data
//ATMega328 - Section 24.9.1 Pg 254 - ADMUX Register
/*ADC result - left-adjusted (Bit 5). The ADC result is 10-bits wide.
In practice, the least-significant 2 bits are often too noisy to be
of any use, so they are discarded. To support this, the ATMega328P is
capable of storing the upper eight bits of the ADC result in the
ADCH register alone. In this case, I want all 10 bits of the data
so I can show how to handle endianness in serial transmissions. As
a result, the most significant two bits are stored in ADCH and the least
significant 8 are stored in ADCL.
*/
/*ADC Channel - I only care about one - the Y axis on the accelerometer
which is channel 1.*/
ADMUX = (0x01 << 6) /*Reference - AVCC - 5V. */
|(0x00 << 5) /* Right-adjust ADC result - refer to
Section 24.9.3.2 pg 256*/
|(0x01 << 0); /*Channel set to X-Axis output on
accelerometer*/
/* ATMega328 Datasheet - Section 24.9.2 - ADCSRA - ADC Status
and Control Register
ADCEN - Bit 7 - Enable ADC - Obviously set this to 1
ADCSC - Bit 6 - Start Converstion - Not yet: 0
ADATE - Bit 5 - Auto-trigger ADC - I'll be manually triggering
the ADC, so 0
ADCIF - Bit 4 - ADC Interrupt Flag - Set when conversion
completes. Ignore.
ADCIE - Bit 3 - ADC Interrupt Enable - Everything will be polled
for this, so 0
ADPS - Bits 2:0 - ADC Prescaler
*/
/*ATMega328 Section 24.4 Pg245 discusses what the prescaler should be set to:
By default, the successive approximation circuitry requires an input clock
frequency between 50kHz and 200kHz to get maximum resolution.
The ClkIO is 8MHz and the prescaler options are 2,4,8,16,32,64 and 128.
1MHz/8 = ~125KHz, so that seems good. That value is 3
*/
ADCSRA = (0x01 << 7) //Enable ADC
|(0x03); //Set prescaler to 1/8 ClkIO - 125KHz
/* ATMega328 Datasheet Section 24.9.5 Pg 257 - DIDR0
This register allows digital input buffers on ADC pins to be
disabled. This saves power, so I'll do it
*/
DIDR0 = 0x01; //Turn off digital filtering on ADC channel 0
//Configure UART for 38400 8N1 Tx Communication
//Step 1 - Baud rate
/* ATMega328 Datasheet Section 20.10 - Table 20-6 pg 192
Baud rate settings for fosc of 8MHZ
Choosing baud rate of 38.4K for minimum error
U2Xn = 0 - Use standard (not double) data rate
UBRRn = 12
*/
UBRR0 = 12;
/* UCSR0A - UART 0 Control and Status Register A
ATMega328 Datasheet Section 20.11.2 pg 194
Bits 7:2 - Status bits
Bit 1 - Double UART transmission speed - No: 0
Bit 0 - Multi-Processor Communication Mode - No:0
*/
UCSR0A = 0x00;
/* UCSR0B - UART 0 Control and Status Register B
ATMega328 Datasheet Section 20.11.3 pg
Bit 7 - Rx Complete Interrupt Enable - 0
Bit 6 - Tx Complete Interrupt Enable - 0
Bit 5 - USART Data Register Empty interrupt enable - 0
Bit 4 - Receiver Enable - Set to 1
Bit 3 - Transmitter Enable - Set to 1
Bit 2 - Character Size Bit 2 - Set to 0 for 8 bits
Bit 1 - 9th receive bit - Ignore
Bit 0 - 9th transmit bit - Ignore
*/
UCSR0B = 0x00 | (1 << 3)
| (1 << 4);
/* UCSR0C - UART 0 Control and Status Register C
ATMega328 Datasheet Section 20.11.4 - Pg 196
Bits 7:6 - Set to asynchronous (clockless) mode: 00
Bits 5:4 - Parity setting - None : 00
Bit 3 - Stop select - 1 : 0
Bit 2:1 - Character size - 8 : 11
Bit 0 - Clock polarity: Don't care : 0
*/
UCSR0C = 0x03 << 1;
//Send a known pattern upon startup to verify the UART works
UDR0 = 0xA5;
//Wait until transmit is complete
while(0x00 == READ(UCSR0A,6));
UDR0 = 0x5A;
while(0x00 == READ(UCSR0A,6));
UDR0 = 0xA5;
//Wait until transmit is complete
while(0x00 == READ(UCSR0A,6));
/* Flash the LED for a second to show that initialization has successfully
occurred
*/
SET(PORTD,7);
_delay_ms(1000);
CLEAR(PORTD,7);
/* Start the timer/counter
ATMega328 Datasheet Section 16.11.2 Pg 135 - TCCR1B
No Waveform generation: bits 4:3 = 0
No input capture: bits 7:6 = 0
Clock select: ClkIO/8 - bits 2:0 = 010b = 0x02
*/
TCCR1B = 0x02; //This starts the counter/timer
while(1)
{
/* Timer overflow - Reading the accelerometer at a 1KHz rate
and flash the heartbeat LED at a reasonable period as well
*/
if(READ(TIFR1,0))
{
/* ATMega328 Datasheet Section 16.11.9 pg137
Setting TIFR1 bit 1 clears the overflow flag
*/
SET(TIFR1,0);
/* Reload the timer/counter count value to the
previous value so that the period remains the same
*/
TCNT1 = TIMER1_PERIOD;
//Read accelerometer data via ADC
SET(ADCSRA,6); //Start ADC conversion
/* Wait until conversion finishes - this should never
be more than 25*(8000000/8)^-1 seconds, which is
about 25us. Typical measured time is ~14.5us
*/
while(0x00 == READ(ADCSRA,4));
SET(ADCSRA,4); //Clear the interrupt flag by setting it to 1
//Clear acceleration data variable before loading new value
accel_data = 0;
/* When reading the full 10-bits from the ADC the
lower register must be read first
*/
accel_data |= (uint16_t)ADCL;
//Then the upper 2 bits
accel_data |= (uint16_t)(ADCH << 8);
/* Transmission of data is toggled by transmitting a
'0' (0x30) byte over serial
*/
if(0x01 == (READ(UCSR0A,7)))
{
if(0x30 == UDR0)
{
transmit_enable =
(0x00 == transmit_enable?0xFF:0x00);
}
}
if(0xFF == transmit_enable)
{
#ifdef BIG_ENDIAN
//Send high byte...
UDR0 = uart_data_pointer[1];
while(0x00 == READ(UCSR0A,6));
//...then low byte
UDR0 = uart_data_pointer[0];
while(0x00 == READ(UCSR0A,6));
#else
//Send low byte...
UDR0 = uart_data_pointer[0];
while(0x00 == READ(UCSR0A,6));
//...then high byte
UDR0 = uart_data_pointer[1];
while(0x00 == READ(UCSR0A,6));
#endif
}
//Blink Heartbeat LED
/*
The timer period is 1ms. To keep everything simple the LED will toggle
every 512 ticks - roughly every .5s.
*/
ticks++;
//If true, the current ticks is a multiple of 512
//So blink the heartbeat LED
if(0x8000 == (ticks << 7))
{
TOGGLE(PORTD,7);
}
}
//Main Loops
}
}