Below is my accelerometer controlled bouncing ball code. I'm using an adxl330. This is for an arduino, but using avr studio and without the boot loader. I did borrow a couple of pieces of code from the arduino sources.
I made a breakout board with .1" a header and soldered it by hand. It's pretty easy with an accurate PCB. Start by fluxing and tinning both the flexible connector and the PCB. A tiny amount of solder on each is plenty. Then apply more flux to both. Align the connector on the PCB so that a small amount of the tinned PCB traces are exposed and hold the display in place with tape. Then I used a straight screwdriver to push down a small amount of the flexible connector to contact the board, and applied just enough heat to the exposed PCB traces for the solder to flow. Work your way across with the heat and screwdriver.
Code: Select all#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#define INDEX 0x00
#define DATA 0x02
#define IDBYTE 0x74
// Coordinates of update rectangle
// Bouncing ball will be confined to this space
#define TOP 0
#define LEFT 0
#define BOTTOM 96
#define RIGHT 128
#define PITCH 256 // Width of the full display in unsigned chars
#define WIDTH (RIGHT-LEFT)
#define HEIGHT (BOTTOM-TOP)
#define ROLL_DAMP 0.995f
#define BOUNCE_DAMP 0.75f
#define ACCEL_SCALE 20.0f
#define MINACCEL 30.0f
#define MINSPEED 0.001f
#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
// Ball size
#define RADIUS 16
#define RADIUS_SQUARED (RADIUS * RADIUS)
// Distance from center of ball to it's bounce
#define PADDING (RADIUS)
void lcd_setup();
void lcd_transmit( uint8_t data );
void lcd_write_reg( unsigned short index, unsigned short data );
void lcd_clear_black(void);
void update_ball(void);
void timer_setup(void);
unsigned long millis(void);
float mirror(float coord, float axis, float dampen);
void adc_init(void); //Initializes converter and interrupt
void increment_adc_port(void);
#define FIRST_ADC_PORT 0
#define LAST_ADC_PORT 2
#define ADC_COUNT (LAST_ADC_PORT - FIRST_ADC_PORT + 1)
volatile uint8_t g_adc_port = FIRST_ADC_PORT;
volatile uint16_t g_adc[ADC_COUNT];
uint16_t g_x_avg;
uint16_t g_y_avg;
float x;
float y;
float x_last;
float y_last;
float dt;
float dt_last;
float dt_scalar;
float a_x;
float a_y;
float t_last;
volatile unsigned long timer0_clock_cycles = 0;
volatile unsigned long timer0_millis = 0;
unsigned short initcode[] PROGMEM = {
// Oscillator start instruction
0x00, 0x0001,
// Pause
0xFFFF, 20,
// Front & back porch of 3 pixels
0x08, 0x0303,
// EOR = 1
// 1 field
0x02, 0x0500,
// Set scan mode
// Set 96 raster rows
0x01, 0x010B,
// Set frame cycle control
// 4 clocks gate output time
// 17 clocks raster row
0x0B, 0x4001,
// Gamma gradient adjustments.
0x30, 0x0000,
0x31, 0x0000,
0x32, 0x0000,
0x33, 0x0401,
0x34, 0x0707,
0x35, 0x0707,
0x36, 0x0707,
0x37, 0x0104,
0x38, 0x0004,
0x39, 0x0004,
// Vertical scroll control
0x41, 0x0280,
// 1st-Screen Drive Position (R42h)
0x42, 0x8300,
// 2nd-Screen Drive Position (R43h)
0x43, 0x9F9F,
// Power control 2
0x11, 0x0001,
// Power control 3
0x12, 0x0008,
// Power control 4
0x13, 0x100E,
// Power control 1
0x10, 0x0044,
// Power control 3
0x12, 0x0018,
// Gate scan position
0x40, 0x0000,
// Vertical scroll control
0x41, 0x0000,
// 1st-Screen Drive Position (R42h)
0x42, 0x5F00,
// Pause
0xFFFF, 40,
// Power control 4
0x13, 0x300C,
// Pause
0xFFFF, 60,
// Power control 1
0x10, 0x4340,
// Pause
0xFFFF, 100,
// Display control
// Reverse display???
0x07, 0x0205,
0xFFFF, 40,
0x07, 0x0227,
0xFFFF, 1,
// Entry mode
// Set address auto-increment starting at top left
// Physical display is 666 18 bit BGR?
0x03, 0x1030,
0x0D, 0x3336,
0x07, 0x1237,
// End of init
0xFFFF, 0xFFFF,
};
void lcd_setup() {
int i;
// Configure arduino pins for SPI
DDRB = (1<<PB3)|(1<<PB5)|(1<<PB2);
SPCR = (1<<SPE)|(1<<MSTR)|(0<<SPR0)|(0<<SPR1);
SPSR = (1<<SPI2X);
// Send init array defined above
for( i = 0;;i += 2 ) {
if( pgm_read_word(&initcode[i]) == 0xFFFF && pgm_read_word(&initcode[i+1]) == 0xFFFF ) break;
if( pgm_read_word(&initcode[i]) == 0xFFFF ) _delay_ms(pgm_read_word(&initcode[i+1]));
else lcd_write_reg( pgm_read_word(&initcode[i]), pgm_read_word(&initcode[i+1]) );
}
// Pause 10ms per the datasheet
_delay_ms( 10 );
}
int main() {
int xp, yp;
long frames = 0;
dt_last = 0.0;
t_last = 0.0;
dt = 0;
a_x = 0;
a_y = 0;
x = (RIGHT-LEFT)/2;
y = (BOTTOM-TOP)/2;
x_last = x;
y_last = y;
timer_setup();
adc_init();
lcd_setup();
lcd_clear_black();
// Allow the ADC to get some samples
_delay_ms(100);
g_x_avg = g_adc[0];
g_y_avg = g_adc[1];
for(int i = 0; i < 10; i++) {
g_x_avg = (float)g_x_avg * 0.9f + (float)g_adc[0] * 0.1f;
g_y_avg = (float)g_y_avg * 0.9f + (float)g_adc[1] * 0.1f;
_delay_ms(10);
}
t_last = (float)millis() / 1000;
for(;;) {
float t;
t = (float)millis() / 1000;
dt = t - t_last;
if (dt_last == 0)
{
dt_scalar = 1;
} else {
dt_scalar = dt / dt_last;
}
a_x = ((float)g_x_avg - (float)g_adc[0]) * ACCEL_SCALE;
a_y = ((float)g_y_avg - (float)g_adc[1]) * ACCEL_SCALE;
if (a_x < MINACCEL && a_x > -MINACCEL) a_x = 0;
if (a_y < MINACCEL && a_y > -MINACCEL) a_y = 0;
update_ball();
int ix = x;
int iy = y;
// Reverse direction at the edges with a buffer of PADDING pixels
if( ix < LEFT + PADDING ) {
x = mirror(x, LEFT + PADDING, BOUNCE_DAMP);
x_last = mirror(x_last, LEFT + PADDING, BOUNCE_DAMP);
ix = x;
} else if (ix > RIGHT - PADDING ) {
x = mirror(x, RIGHT - PADDING, BOUNCE_DAMP);
x_last = mirror(x_last, RIGHT - PADDING, BOUNCE_DAMP);
ix = x;
}
if( iy < TOP + PADDING ) {
y = mirror(y, TOP + PADDING, BOUNCE_DAMP);
y_last = mirror(y_last, TOP + PADDING, BOUNCE_DAMP);
iy = y;
} else if (iy > BOTTOM - PADDING ) {
y = mirror(y, BOTTOM - PADDING, BOUNCE_DAMP);
y_last = mirror(y_last, BOTTOM - PADDING, BOUNCE_DAMP);
iy = y;
}
// Dampen any movement less than MINSPEED
if( (x-x_last)*(x-x_last) + (y-y_last)*(y-y_last) < (MINSPEED * MINSPEED)) {
x_last = x;
y_last = y;
}
int ix_last = x_last;
int iy_last = y_last;
// Calculate the update rectangle
int b_top, b_left, b_bottom, b_right;
if (x > x_last) {
// Ball moving right
b_left = ix_last - PADDING;
b_right = ix + PADDING;
} else {
// Ball moving left
b_left = (ix - PADDING);
b_right = ix_last + PADDING;
}
if (y > y_last) {
// Ball moving down
b_top = iy_last - PADDING;
b_bottom = iy + PADDING;
} else {
// Ball moving up
b_top = iy - PADDING;
b_bottom = iy_last + PADDING;
}
// Set the horizontal display range that will be sent below
lcd_write_reg(0x44, (b_right << 8) | b_left);
// Set the vertical display range that will be sent below
lcd_write_reg(0x45, (b_bottom << 8) | b_top);
// Clear the RAM write data mask - all bits will be set
// Determines what bits are committed to display RAM
// The format is as follows...
// xxGGGGGGxxBBBBBB
// xxxxxxxxxxRRRRRR
lcd_write_reg(0x23, 0b0000000000000000);
lcd_write_reg(0x24, 0b0000000000000000);
// Ram address set
// These are 16 bit pixels, so I'm
// not sure why adding LEFT * 2 doesn't work
lcd_write_reg(0x21, (b_top * PITCH + b_left));
// Initiate a full-frame transfer by sending the first part of the write data command
PORTB = PORTB & ~(1<<PB2);
lcd_transmit( IDBYTE|INDEX );
lcd_transmit( 0x00 );
lcd_transmit( 0x22 );
PORTB = PORTB | (1<<PB2);
PORTB = PORTB & ~(1<<PB2);
lcd_transmit( IDBYTE|DATA );
for( yp = b_top; yp <= b_bottom; yp++ ) {
for( xp = b_left; xp <= b_right; xp++ ) {
// Is this pixel closer than RADIUS to the ball?
if( (xp-ix)*(xp-ix) + (yp-iy)*(yp-iy) < (RADIUS_SQUARED)) {
// Color bits seem to be as follows:
// 0baRRRRaGG
// 0bGGGaBBBB
//
// I believe the a bits are alpha, but I don't think this
// works in fast-write mode - so the a bits should be zero here
// Send ball color pixel
lcd_transmit(0b01111000);
lcd_transmit(0b00001111);
} else {
// Spiffy checkered background
if (((xp >> 3) + (yp >> 3)) & 1) {
// Send background color pixel
lcd_transmit(0b00000000);
lcd_transmit(0b00000000);
} else {
lcd_transmit(0b00000000);
lcd_transmit(0b01100000);
}
}
} // for xp
} // for yp
// Signal the end of this transmission
PORTB = PORTB | (1<<PB2);
t_last = t;
dt_last = dt;
frames++;
} // forever
}
// Send a byte
void lcd_transmit( uint8_t data ) {
SPDR = data;
while(!(SPSR & (1<<SPIF))) ;
}
// Parameters:
// index - register index from datasheet
// data - data byte to put in the register
void lcd_write_reg( unsigned short index, unsigned short data ) {
PORTB = PORTB & ~(1<<PB2);
lcd_transmit(IDBYTE|INDEX);
lcd_transmit(0x00);
lcd_transmit(index);
PORTB = PORTB | (1<<PB2);
PORTB = PORTB & ~(1<<PB2);
lcd_transmit(IDBYTE|DATA);
lcd_transmit(data>>8);
lcd_transmit(data);
PORTB = PORTB | (1<<PB2);
}
// Use fast write mode to clear the screen with black pixels
// There might be a better way in hardware
void lcd_clear_black(void) {
int x;
int y;
// Set the horizontal display range that will be sent below
lcd_write_reg(0x44, (127 << 8) | 0);
// Set the vertical display range that will be sent below
lcd_write_reg(0x45, (95 << 8) | 0);
// Clear the RAM write data mask - all bits will be set
lcd_write_reg(0x23, 0x0000);
lcd_write_reg(0x24, 0x0000);
// Ram address set
lcd_write_reg(0x21, 0x0000);
// Initiate a full-frame transfer by sending the first part of the write data command
PORTB = PORTB & ~(1<<PB2);
lcd_transmit( IDBYTE|INDEX );
lcd_transmit( 0x00 );
lcd_transmit( 0x22 );
PORTB = PORTB | (1<<PB2);
PORTB = PORTB & ~(1<<PB2);
lcd_transmit( IDBYTE|DATA );
for( y = 0; y < 96; y++ ) {
for( x = 0; x < 128; x++ ) {
if (((x >> 3) + (y >> 3)) & 1) {
// Send background color pixel
lcd_transmit(0b00000000);
lcd_transmit(0b00000000);
} else {
lcd_transmit(0b00000000);
lcd_transmit(0b01100000);
}
} // for x
} // for y
// Signal the end of this transmission
PORTB = PORTB | (1<<PB2);
}
void update_ball(void) {
float x_next;
float y_next;
// time-corrected verlet
x_next = x + ((x - x_last) * dt_scalar + a_x * dt * dt) * ROLL_DAMP;
y_next = y + ((y - y_last) * dt_scalar + a_y * dt * dt) * ROLL_DAMP;
y_last = y;
x_last = x;
x = x_next;
y = y_next;
}
void timer_setup(void) {
sei();
// on the ATmega168, timer 0 is also used for fast hardware pwm
// (using phase-correct PWM would mean that timer 0 overflowed half as often
// resulting in different millis() behavior on the ATmega8 and ATmega168)
#if defined(__AVR_ATmega168__)
sbi(TCCR0A, WGM01);
sbi(TCCR0A, WGM00);
#endif
// set timer 0 prescale factor to 64
#if defined(__AVR_ATmega168__)
sbi(TCCR0B, CS01);
sbi(TCCR0B, CS00);
#else
sbi(TCCR0, CS01);
sbi(TCCR0, CS00);
#endif
// enable timer 0 overflow interrupt
#if defined(__AVR_ATmega168__)
sbi(TIMSK0, TOIE0);
#else
sbi(TIMSK, TOIE0);
#endif
}
SIGNAL(SIG_OVERFLOW0)
{
// timer 0 prescale factor is 64 and the timer overflows at 256
timer0_clock_cycles += 64UL * 256UL;
while (timer0_clock_cycles > clockCyclesPerMicrosecond() * 1000UL) {
timer0_clock_cycles -= clockCyclesPerMicrosecond() * 1000UL;
timer0_millis++;
}
}
unsigned long millis(void)
{
unsigned long m;
uint8_t oldSREG = SREG;
// disable interrupts while we read timer0_millis or we might get an
// inconsistent value (e.g. in the middle of the timer0_millis++)
cli();
m = timer0_millis;
SREG = oldSREG;
return m;
}
float mirror(float coord, float axis, float dampen) {
return axis + (axis - coord) * dampen;
}
void adc_init(void) {
// Sampling frequency prescaler
ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
// Set reference voltage
ADMUX |= (1 << REFS0);
// Disable auto trigger
ADCSRA &=~ (1 << ADATE);
// Select our initial input channel
ADMUX |= FIRST_ADC_PORT;
// Enable ADC
PRR &=~ (1 << PRADC);
ADCSRA |= (1 << ADEN);
// Enable interrupts
ADCSRA |= (1 << ADIE);
// Start sampling
ADCSRA |= (1 << ADSC);
}
void increment_adc_port(void)
{
if (g_adc_port >= LAST_ADC_PORT) {
g_adc_port = FIRST_ADC_PORT;
}
else {
g_adc_port++;
}
}
ISR(ADC_vect)
{
// Code to be executed when ISR fires
// Grab the current value
g_adc[g_adc_port] = ADCL;
g_adc[g_adc_port] |= ADCH << 8;
// switch to next channel
increment_adc_port();
ADMUX &=~ 0b00001111;
ADMUX |= (1 << REFS0);
ADMUX |= (uint8_t)g_adc_port;
// Disable ADC
ADCSRA &=~ (1 << ADEN);
// Enable ADC
PRR &=~ (1 << PRADC);
ADCSRA |= (1 << ADEN);
// Resume sampling
ADCSRA |= (1 << ADSC);
}