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);
}