Punch Acceleration Sensor – Part 3

In this part I will describe the software side of the project. You can download the code here. I would highly recommend that you download ATMega328P documentation as well for register descriptions, notes and sample code.

Beginner Arduino Notes.

Any Arduino based program has at least 2 entry points:

void setup()
{
}
void loop()
{
}

Code within setup() is executed only once at the start and code within loop() is executed multiple times. You will also notice syntax like this:

ISR (TIMER1_OVF_vect) 
{
}

This is an interrupt handling routine for the Timer1 overflow interrupt. Just writing this code however is not enough as you need to enable that interrupt. Some of the interrupts are already handled by Arduino Wire library (such as Timer0 overflow) so they are not available to user code without additional steps.

There are several ways to do i/o operations. Arduino provides friendly pinMode(), digitalWrite(), digitalRead() functions but you can use native AVR way of reading/writing to various microcontroller registers.

Reading from A/D Converter.

The AD7680 A/D converter is read using Serial Peripheral Interface (SPI). The initialization of this interface is done within setup() function:

  // initialize SPI interface
  digitalWrite(CS_PIN,HIGH); // initial value
  pinMode(CS_PIN, OUTPUT);
  pinMode(SCK_PIN, OUTPUT);
  pinMode(MISO_PIN, INPUT);
  // configure SPI to: enable, master, clock high when idle, sample on falling edge, freq divider 4
  SPCR = (1<<SPE) | (1<<MSTR) | (1<<CPOL) | (1<<CPHA); 
  SPSR |=  (1<<SPI2X); // double the clock speed

SPE bit enables SPI, MSTR bits set’s microcontroller to be the master, DORD=0 for most significant bit first order, SPR1=0 and SPR2=0 to set clock frequency to the processor clock divided by 4 but it is then doubled by setting SPI2X bit of SPSR register so in the end it’s the chip clock/2.

CPOL=1 and CPHA=1 settings could be very confusing (they were to me) so an additional explanation is required. CPHA sets clock phase and CPOL sets clock polarity. These settings determine when the data is sampled and have to match between the master and slave. Here’s the timing diagram for the AD7680:

AD7680 Timing Diagram

Note that when CS is set AD7680 expects clock to be high and that the best time to read data is on the raising edge of the clock. Now here are 4 modes of SPI operation for microcontroller set by the CPHA and CPOL bits:

ATMega SPI modes

As you can see from the diagram, the mode that matches AD7680 diagram is mode 3 where CPCHA=1 and CPOL=1. It also starts with SCK high and samples on the raising edge of the clock.

To perform an actual reading, following code is used:

  digitalWrite(CS_PIN,LOW); //   start ad conversion
  SPDR = 0; // start transmission of byte 1
  while(!(SPSR & (1<<SPIF)));
  byte b1 = SPDR;
  SPDR = 0; // start transmission of byte 2
  while(!(SPSR & (1<<SPIF)));
  byte b2 = SPDR;
  SPDR = 0; // start transmission of byte 3
  while(!(SPSR & (1<<SPIF)));
  byte b3 = SPDR;
  digitalWrite(CS_PIN,HIGH); // end transmission
  unsigned int reading = (b1 << 11) | (b2 << 3 ) | ( b3 >> 5 );

SPDR is SPI Data Register. Writing to the register initiates data transmission. SPSR is SPI Status Register. When a serial transfer is complete, the SPIF Flag (SPI Interrupt Flag – most significant bit of SPSR) is set. So once we write 0 to SPI Data Register, we need to wait until SPI Interrupt Flag is set. That’s what while(!(SPSR & (1<<SPIF))); does. After which we can read results of the transmission from SPI Data Register. If you look at the AD7680 timing diagram above you will also see that AD7680 starts transmitting data on the 4th clock and ends on the 19th. I can’t instruct microcontroller to wait 3 clocks and then start reading so I have to tell it to get all data including leading and trailing zeroes. The ( b1 << 11 ) | ( b2 << 3 ) | (b3 >> 5 ); line extracts the useful 16 bits of data from 24 bits (3 bytes) read. First useful byte of data consists of last 5 bits of b1 and first 3 bits of b2: ( b1 << 3 ) + ( b2 >> 5 ), however we have to make it a most significant byte of our result so we shift it by 8 bits left (or multiply by 256): ( b1 << ( 3 + 8 ) ) + ( ( b2 >> 5 ) << 8 ) ). The second useful byte is the lower 5 bits of b2 and higher 3 bits of b3: ( b2 << 3 ) + ( b3 >> 5 ). Combining it with the first byte yields ( b1 << 11 ) + ( b2 << 3 ) + (b3 >> 5 )

Maximum Acceleration.

Detecting maximum acceleration is just a matter of calculating minimum and maximum value of the a/d reading and then converting it to the acceleration in g using following formula:

Acceleration = (ADReading / 2^16 * 5 - 2.5) / 0.008

Here, 2^15 is the maximum 16 bit value which corresponds to the analog voltage of 5V, 2.5 is the no-acceleration value and 0.008 is sensitivity volts per g.

The only tricky part in this part of the code is to know when to reset this maximum acceleration. The rule that i used was: reset when there is a transition from “rest” state to not “rest” state. The “rest” state is defined as system has not been experiencing acceleration for that past 0.5 sec. In order to implement this rule there are 3 state variables:

  • atRestJit – no acceleration at the moment
  • atRestJitTS – time stamp when atRestJit changed
  • atRest – the “rest” state

atRestJit is a “jittery” at rest and changes every time acceleration passes 10g threshold. The atRest is then calculated as follows. If atRestJit is false then atRest is false as well. When atRestJit is true and has not changed for the last 0.5 sec then aRest is true.

Displaying Results on 7 Segment LED

Displaying Single Digit

As described in the previous post, in order to display a digit on a 7 segment LED display we need to set the pin that selects the digit to high and then pins that select the segments to be lit to low. In this implementation, digit select pins are driven directly from ATMega while segment selection pins are driven by shift register.

Digit selection pins are bits 2,3,4,5 of the PortD which corresponds to pins 2,3,4,5 on the Arduino board as well. These can be set via digitalWrite(2, HIGH); but also directly via PORTD |= (1<<PORTD2);.

Segment selection is driven by sn74hc594 8bit shift register. First step is to computer the value to be sent to shift register and that is done via the switch statement. Note that since the segments that we want to turn on have to have LOW value, the value to be sent to register is inverted (~):

  switch(char_to-display)
  {
    case '0':
    case 'o':
    case 'O':
      segments = ~((1<<SEG_A) | (1<<SEG_B) | (1<<SEG_C) | (1<<SEG_D) | (1<<SEG_E) | (1<<SEG_F) );  
      break;
      
    case '1':
      segments = ~( (1<<SEG_B) | (1<<SEG_C) );  
      break;
....

Sending it to shift register via SPI interface is a breeze:

  // shift out segments
  shiftOut(SEG_SER,SEG_CLK,MSBFIRST,segments);
  // need to pulse clock high one more time since register is 1 clock behind data in
  digitalWrite(SEG_SER,HIGH);
  digitalWrite(SEG_CLK,HIGH);
  digitalWrite(SEG_CLK,LOW);

Here SEG_CLK is clock and mapped to Arduino pin 6 (Port D 6) and SEG_SER is data on Arduino pin 7 (Port D 7). The only tricky part is that clock needs to be pulsed one more time because in this implementation shift register clock (SRCLK) and storage register clock (SRCLK) are tied in together and when that happens data that is output by sn74hc594 is always one clock behind data that is sent to it.

Displaying Multiple Digits

The easiest way to display multiple digits is just to display them one after another:

while(true)
{
   DisplayDigit(1);
   delay(100);
   DisplayDigit(2);
   delay(100);
   DisplayDigit(3);
   delay(100);
   DisplayDigit(4);
   delay(100);
}

However if I were to do that then the program would not do anything else but display digits. The solution is to use interrupts on the timer:

int digit=1;
void timer_interrupt() // assume timer is set to call this every 100 ms
{
   DisplayDigit(digit);
   digit++;
   if (digit>4) digit=1;
}

The implementation is very similar to that, except an additional timer interrupt is used to turn all digits off 0.6 ms after they been turned on. This is done to avoid burning LEDs out since no current limiting resistors were used. 16 bit Timer 1 of the ATMega is used for this purpose:

void setup()
{
  TCCR1A = 0; // normal operation. other modes are used for PWM
  TCCR1B = (1<<CS10); //Set Prescaler to clk/1 : freq: 16Mhz, 1 click = 0.0625 micro secs
  TCCR1C = 0; // no forcing output compare
  OCR1A = BRIGHTNESS; // output compare a value - brightness (10,000)
  TIMSK1 = (1<<OCIE1A) | (1<<TOIE1); // Output Compare A Match Interrupt Enable, Overflow Interrupt Enable. Overflow will happen every 2^16 ticks. freq: 244.14 Hz
}

byte currentDigit = 0;

// called on timer1 overflow - 244 Hz
ISR (TIMER1_OVF_vect) 
{
  LED7x4_display_digit(currentDigit);
  currentDigit++;
  if (currentDigit>3) currentDigit=0;
}


// called BRIGHTNESS ticks after overflow
ISR (TIMER1_COMPA_vect) 
{
  // turn off all digits
  DIG_PORT &= ~( (1<<DIG_1) | (1<<DIG_2) | (1<<DIG_3) | (1<<DIG_4));
}

Miscellaneous

In order to produce the chart in the first part of this post, different code was used. This code would continuously store data in internal buffer overriding old values when it ran out of room (which happened pretty quickly since the ATMega328 internal SRAM is only 2k so i could store no more than 900 16 bit samples). When an acceleration event was detected, program recorded next 800 samples and then printed entire buffer via Serial interface. This provided 100 samples before the acceleration event as well as 800 samples of the event itself. At first I have attempted to print to serial directly as A/D is read but even at highest baud rate I was missing a lot of data, so i had to switch to this store and print approach.

Overall, the programming aspects of the project was quite enjoyable with the exception of cryptic c++ errors that i was getting from time to time (mostly from forgetting basic things like how break code between .h and .cpp files). One annoying thing that i’ve encountered is the lack of clear documentation on which Arduino/Wire library to include for various purposes. I wanted to use a boolean and byte types but for some reason while they worked in the main file, they didn’t in the LED7x4.cpp file. Turns out they are not built in types but instead defined in some .h file. It took a while to figure out which .h file i need – #include <WProgram.h>. The reason i didn’t need it in main file is that Arduino puts it there automatically before compiling – but not in any of the other files.

Anyway, it was a fun project and I hope some one will find this rather too detailed description useful. I have tried to target it to the starting embedded electronics hobbyist such as myself – this was my first electronics project since college.

Cheers,

Aleksey

Previous: Part 2 – Hardware

Next: Follow up

About these ads

Tags: , , , , , ,

6 Responses to “Punch Acceleration Sensor – Part 3”

  1. Punching accelerometers - Hack a Day Says:

    [...] after finishing his Makiwara punching bag, [Abieneman] wired and programmed an Arduino to an accelerometer to find out just how much acceleration (and with some math, force) [...]

  2. Jez Weston Says:

    Hey, interesting stuff there. I’ve been playing with accelerometers to control LEDs recently and the noise is indeed a bugger. Did you do any filtering on the data, or is it just all raw?

    (I’ve been using moving averages a lot, but that won’t help for picking peak accelerations, unless your sampling rate is much higher than the rate that your acceleration changes.)

    • abieneman Says:

      I have thought about averaging and even implemented it at some point, however it took only 140 samples to reach the peak of acceleration so i decided against averaging in order not to miss the peak. Thinking about this now it would not hurt to do 4-8 point average filter. Even without any filtering it produced about 1.5g variation and from 100g peak acceleration it’s only 1.5% which was sufficient for this application.

      Speaking of averaging, i though of a way to make it faster by eliminating the floating division: if you make the averaging buffer 2^n samples long you can just use shift right n bits operation instead of division by 2^n.

  3. Jez Weston Says:

    The accel I’m using has low-pass filters on the outputs, so there’s no point reading any faster than 150 Hz. Adding, say, 8 samples per data bit and it rapidly gets you a slow reading rate. Still, as you say, it’d be worth you grabbing the raw data, and just messing about with it in a spreadsheet to see what effect filters and filter parameters have.

    (And definitely yes to the 2^n buffer size.)

  4. Kam Hanif Says:

    In your code you have defined:

    #define zeroG 0x8000
    #define atRestHigh 33816 // +10g
    #define atRestLow 31719 //-10g

    I’m having trouble actually understanding what these numbers correspond to (I understand zeroG is hex for 32768) but what do these numbers actually mean? Can’t figure it out!

    Many thanks

    • abieneman Says:

      The atRestHigh and atRestLow are the values that correspond to the +10 g and -10 g accelerations. If current acceleration value falls within that range then i assume that device is at rest. This can be configured to a more narrow range for more sensitivity (and more false positives). The value is calculated using formula ADReading = (Acceleration * 0.008 + 2.5)/5 * 2^16 = Acceleration * 104.85 + 32768

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: