Free-running ADC measurement with ATMega168

This is some code I wrote to take measurements from the accelerometer on the WiTilt. Rather than polling the accelerometer, as the original firmware does, I put the accelerometer in free-running mode and catch the interrupt generated when a conversion completes.

In addition, I measure each axis in turn, and accumulate the data until I’ve got 16 values from each. This results in 16x oversampling, which helps reduce accelerometer noise.

There is one thing I have not been able to figure out. In the main loop, you’ll see I blink the status LED once every 200 samples. This seems to be necessary to keep the CPU awake, although I can’t see anything in the datasheet that says it will go to sleep. Nevertheless, if I remove the LED blinking code, the device stops returning measurements very quickly. If anyone can tell me why this is happening, I’d love to hear it.

Feel free to use the code as you like.

Michael

//
//    Reads accelerometer values from ADC in free-running mode, accumulates 
//    them to get 16x oversampling, and writes the result out to Bluetooth.
//

#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define sbi(var, mask)	((var) |= (uint8_t)(1 << (mask)))
#define cbi(var, mask)	((var) &= (uint8_t)~(1 << (mask)))

#define STATUS_LED	3
#define GS1			6
#define GS2			7

#define MYUBRR		10
#define TIMER_TICKS	10000

#define OVERSAMPLE	16

static int uart_putchar(char c, FILE *stream);
static FILE my_stdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

uint16_t accum[] = { 0, 0, 0 };
uint16_t accum_copy[3];
uint16_t timer = 0, timer_copy;

uint8_t count = 0;
uint8_t data_ready = 0;

static int uart_putchar(char c, FILE *stream)
{
	loop_until_bit_is_set(UCSR0A, UDRE0);
	UDR0 = c;
	return 0;
}

void io_init(void)
{
	// Set data direction registers
	DDRB = 0b11101111;
	DDRC = 0b00100000;
	DDRD = 0b11111110;

	// Set up SPI
	SPCR = (1<<SPE) | (1<<MSTR) | (1<<CPHA) | (1<<CPOL) | (1<<SPR0);
	SPSR = (1<<SPI2X);

	// Set UART to 115,200 baud
	UBRR0H = MYUBRR >> 8;
	UBRR0L = MYUBRR & 0xFF;

	// Enable UART receive and transmit
	UCSR0B = (1<<RXEN0) | (1<<TXEN0);

	// Set UART to 8N1
	UCSR0C = (3<<UCSZ00);

	// Set UART to double speed operation
	UCSR0A = (1<<U2X0);
    
    // Set up timer
    ASSR = 0;
    TCCR1B = (1<<WGM12) | (1<<CS10);
	TIMSK1 = (1<<OCIE1A);

	OCR1AH = TIMER_TICKS >> 8;
	OCR1AL = TIMER_TICKS & 0xFF;

	// Set accelerometer to 6G mode
	sbi(PORTD, GS1);
	sbi(PORTD, GS2);

	// Set ADC to free-running mode
	ADMUX = 0;
	ADCSRA = (1<<ADEN) | (1<<ADSC) | (1<<ADATE) | (1<<ADIE) | (1<<ADPS2) | (1<<ADPS1);
	
	sei();
}

void delay_ms(uint16_t x)
{
	for(; x > 0; --x) {
		for(uint8_t y = 0; y < 40; ++y) {
			for(uint8_t z = 0; z < 40; ++z) {
				asm volatile("nop");
			}
		}
	}
}

void init(void)
{
    stdout = &my_stdout;
	io_init();

	// We need to wait for the ADC to run through a couple of values, since 
	// the first value won't match up correctly with the indicated channel
	
	for(uint8_t i = 0; i < 5; ++i) {
		sbi(PORTD, STATUS_LED);
		delay_ms(100);
		cbi(PORTD, STATUS_LED);
		delay_ms(100);
    }

	sbi(PORTD, STATUS_LED);
}

void copy_and_clear_accum(void)
{
	for(uint8_t i = 0; i < 3; ++i) {
		accum_copy[i] = accum[i];
		accum[i] = 0;
	}
	timer_copy = timer;
}

void write_accum(void)
{
	putchar(0xFF);
	putchar(0xFF);

	putchar((uint8_t)(timer_copy >> 8));
	putchar((uint8_t)(timer_copy & 0xFF));

	for(uint8_t i = 0; i < 3; ++i) {
		putchar((uint8_t)(accum_copy[i]>>8));
		putchar((uint8_t)(accum_copy[i] & 0xFF));
	}
}

ISR(ADC_vect)
{
	static uint8_t oldchan = 0;
	uint8_t curchan = ADMUX & 0x0F;

	accum[oldchan] += ADCW;
	oldchan = curchan;
	
	// When we've accumulated 16 samples per channel, copy them to the second 
	// buffer and notify the main loop
	
	if(++count == 3 * OVERSAMPLE) {
		copy_and_clear_accum();
		data_ready = 1;
		count = 0;
	}
	
	curchan = (curchan + 1) % 3;
	ADMUX = (ADMUX & 0xF0) | curchan;
}

ISR (SIG_OUTPUT_COMPARE1A)
{
	++timer;
}

int main(void)
{
	uint16_t samples = 0;

	init();
	
	while(1) {
		if(data_ready) {
			data_ready = 0;
			write_accum();

			++samples;
		}
		
		// It seems to be necessary to do something each time through the 
		// loop to make sure the microcontroller keeps responding

		if(samples == 100) {
			cbi(PORTD, STATUS_LED);
		} else if(samples == 200) {
			sbi(PORTD, STATUS_LED);
			samples = 0;
		}
	}

	return(0);
}

http://en.wikipedia.org/wiki/Volatile_variable

In your code “data_ready” needs to be volatile since it is modified in the ISR.

/Lars

Lajon:
In your code “data_ready” needs to be volatile since it is modified in the ISR.

Thanks! It looks like “timer” should also be volatile, and “count” could be declared static inside the ISR, since it is used nowhere else.

Michael