Bullit Accelerometer

The accelerometer can measure acceleration in two axis using Analog Devices ADXL202. +-2g can be measured in each axis. Look here for datasheets and application notes.

pcb.jpg

Accelerometer with microcontroller and display

Introduction

Features

The accelerometer can measure acceleration and time. Using the time and the acceleration it can calculate and display: The mass of the object can also be input, the accelerometer can then also display: See the User Manual.

Hardware

The sensor is the Analog Devices ADXL202.

CPU is an ATMEL AT90S8535 running at 4Mhz. A 32768 Hz crystal is connected to the real time clock timer inputs (TIMER2) and is the time base for the accelerometer. This allows a ceramic resonator to be used instead of a crystal if desired. A ceramic resonator should provide even lower power consumption. Power to all subcomponents like display and accelerometer senser are controlled by the CPU so that they can be turned off to save power.

The ADXL is connected to the Input Capture Pin (ICP) and the analog comparator. Rset of the ADXL that controls the period time is set so that there is approximatly 20000 ticks to one period.

The LCD display have 3 rows and is connected to the CPU using two data pins. The LCD uses I2C protocol and uses a variant of this driver circuit. A negative voltage charge pump is driven by the CPU to generate the bias voltage needed by the display. By measuring the bias voltage voltage with the ADC the CPU can regulate the negative voltage. A voltage divider between the negative lcd bias and the battery makes the voltage in a range to be measured.

Vref to the ADC is connected directly to the battery. One of the ADC inputs is connected to a reference voltage from a zener diode. The zener is driven by one of the pins from the CPU. This minimises the current consumption by just turning on the reference voltage when Vbatt need to be calculated from measurements of the zener voltage.

RS232 line driver. A simple transistor inverts the output from the Tx line so that it can be connected to an PC RS232 port. Two buttons are connected. Through two diodes they each can pull down INT1 to wake the CPU if necessary. Three LED provide debug status, TIMER2 ticks, Idle/PowerSave and On/Off. Audio feedback is provided through a small buzzer. Each hardware is wrapped by a macro in the hardware.h file. No port is accessed directly without using a macro.

 PD7 [out] BLUE LED (1s alive blink)
 PD6 [in]  ICP; ADXL x-axis (8535:pin20)
 PD5 
 PD4 [out] BUZZER_OUT
 PD3 [in]  INT1. _WAKEUP  (8535:pin17)
 PD2       INT0.  (8535:pin16)
 PD1 [out] UART TXD (8535:pin15)
 PD0 [in]  UART RXD (8535:pin14)

 PC7 [in]  32678 Hz Crystal
 PC6 [out] 32678 Hz Crystal
 PC5 [i/o] I2C Data (8535:pin27) 
 PC4 [i/o] I2C Clk (8535:pin26)
 PC3 [out] LCD_PWR  (8535:pin25)
 PC2 [out] LCD_CHARGE_PUMP2  (8535:pin24)
 PC1 [out] LCD_CHARGE_PUMP1  (8535:pin23)
 PC0 [out] LCD_CHARGE_PUMP0  (8535:pin22)

 PB7 [in]  SCK   (8535:pin8)
 PB6 [out]  MISO   (8535:pin7)
 PB5 [in] MOSI   (8535:pin6)
 PB4       _SS
 PB3 [in]  AIN1;(ADXL Power / 2) (8535:pin4)
 PB2 [in]  AIN0; ADXL y-axis (8535:pin3)
 PB1 [out] ADXL Power (8535:pin2)
 PB0 [out] RED LED (on if CPU clock is on, off is CPU is in power save)

 PA7
 PA6   (8535:pin34)
 PA5
 PA4
 PA3
 PA2 [out] Vref supply  (8535:pin38)
 PA1 [ain] VLCD Meas    (8535:pin39)
 PA0 [ain] Vcc measure  (8535:pin40)

Theory of Operation

ADXL - Theory of Operation

...magic. Got it.

Building the code

Install the CYGWIN AVR-GCC development environment for Windows. This is my makefile with the auto configuration of the RTC clock to start counting the time from when the code was built. The excellent Doxygen is used to generate the documentation.

Libraries and Utilities

The accelerometer consists of the following software components:

Software Components

The code consist of a number of parts that run concurrently. A number of interrupts and the main loop.

ADXL Timer1 interrupts

//ADXL TIMER1 interrupts begin
INTERRUPT(SIG_INPUT_CAPTURE1)
{
  //Get the captured counter  
  uint16_t cnt = TIMER1_READ_INPUT_CAPTURE();

  //Normalize to ticks after TaTc, use the fact that it is 
  //16 bit timer and 16bit arithmetics is used
  cnt -= TaTc;

  //Check prescaler, if cnt < 65536 / 4 then we can use a lower
  //prescaler value
  #if (ADXL_AUTOMATIC_PRESCALER_ENABLED == 1)
  if (!Timer1_Prescaler_Decrease(cnt))
  #endif //(ADXL_AUTOMATIC_PRESCALER_ENABLED == 1)
  {
    if (validMeasurements > 0)
    {
      validMeasurements--;
    }
    else
    {
        if (AxisX > CalibrationMeasured.TxMax)
          CalibrationMeasured.TxMax = AxisX;
        if (AxisX < CalibrationMeasured.TxMin)
          CalibrationMeasured.TxMin = AxisX;
        if (AxisY > CalibrationMeasured.TyMax)
          CalibrationMeasured.TyMax = AxisY;
        if (AxisY < CalibrationMeasured.TyMin)
          CalibrationMeasured.TyMin = AxisY;
    }

    //See algorithm description in .h file.
    if (TIMER1_INPUT_CAPTURE_IS_RISING_EDGE())
    {
      //(a) (c)
      TaTc += cnt;

      CalibrationMeasured.Tp = 
        FIR_Filter_BoxCar_Input(&periodFilter.filter, (FIR_FilterSample_t)cnt);

      //Enable overflow detection
      TIMER1_SET_OUTPUT_COMPARE_A(TaTc + ADXL_TIMERTICKS_OVERRUN);
      TIMER1_INPUT_CAPTURE_FALLING_EDGE();
    }
    else {
      // (b) (d)
      if (ADXL_IS_X_AXIS())
      {
        //(b)
        AxisX = FIR_Filter_BoxCar_Input(&AxisXFilter.filter, (FIR_FilterSample_t)cnt);

        ADXL_SELECT_Y_AXIS();
      }
      else {
        // (d)
        AxisY = FIR_Filter_BoxCar_Input(&AxisYFilter.filter, (FIR_FilterSample_t)cnt);

        ADXL_SELECT_X_AXIS();
      }
      TIMER1_INPUT_CAPTURE_RISING_EDGE();
    }
  }
}




INTERRUPT(SIG_OUTPUT_COMPARE1A)
{
  //Timer 1 overflowed, increase prescaler and reset the number of valid validMeasurements
  //uS_counter = 0;

#if (ADXL_AUTOMATIC_PRESCALER_ENABLED == 1)
  Timer1_Prescaler_Increase();
#endif //(ADXL_AUTOMATIC_PRESCALER_ENABLED == 1)
  RED_LED_CHANGE();

  validMeasurements = ADXL_STABLE_MEASUREMENT_COUNT;
}


//ADXL TIMER1 interrupts end

RTC and Timers Timer2 interrupts

//TIMER2 Interrupts begin

INTERRUPT(SIG_OUTPUT_COMPARE2)
{
  Timers_Update();
}

INTERRUPT(SIG_OVERFLOW2)
{
  //TIMER2_DEBUG_OUTPUT_CHANGE();
  //DEBUG_PRINT_INFO_(".");
  RTC_Increase(TIMER2_RTC_UPDATE_TIME);
}

//TIMER2 Interrupts end

Keyboard wakeup / RS232 INT1 intterupt

Main Loop

Wraps everything together:
  //Main program loop begin
  FOREVER 
  {
    static bool ButtonPressed = false;

    //Update the LCD charge pump timer
    UpdateLCDChargePumpTimer();

    if (ButtonPressed)
    {
      if (!BUTTON_RESET_DOWN() && !BUTTON_MODE_DOWN())
      {
        ButtonPressed = false;
      }
    }
    else {
      if (BUTTON_MODE_DOWN())
      {
        AccelR8_Mode_t mode = currentMode;
        
        ButtonPressed = true;
        
        Buzzer_Buzz();

        mode++;
        if (mode >= AccelR8_Mode_LAST) mode = AccelR8_Mode_FIRST;

        Timer_Set(TIMER_SWITCH_OFF,TIMER_SWITCH_OFF_ON_TIMEOUT);

        Mode_NewMode(mode);
      }

      if (BUTTON_RESET_DOWN())
      {
        ButtonPressed = true;

        Buzzer_Buzz();

        switch (currentMode)
        {
          case AccelR8_Mode_Calibrate:
            {
              const ADXL_CalibrationInfo_t* info_p =
                ADXL_GetMeasuredCalibrationInfo();

              eeprom_write_block(info_p,(void*)&EEPROM_SavedCalibrationData,sizeof(ADXL_CalibrationInfo_t));
            }
            break;
          case AccelR8_Mode_LateralAcceleration:
            z_offset = current_acceleration;
            horiz_speed = 0;
            break;

          default:
            break;

        }
      }
    }

    //Update display
    if (Timer_HasExpired(TIMER_UPDATE_DISPLAY)) 
    {
      UpdateDisplay();
    }

    if (Timer_HasExpired(TIMER_SWITCH_OFF)) 
    {
      Mode_NewMode(AccelR8_Mode_Off);
    }

    if (Timer_HasExpired(TIMER_LOG))
    {
      ADXL_g_t axisX = ADXL_GetX();
      ADXL_g_t axisY = ADXL_GetY();

      axisX/=10;
      axisY/=10;

      uart_printf("%+05d %+05d\r", axisX, axisY);
    }

    //Read ADC
    if (Timer_HasExpired(TIMER_READ_ADC)) 
    {
      dbg_printf("\r\nVlcd:%6dmV tmr:%6dms", LCD_Voltage_Current, LCD_ChargePumpTimer);

      ADC_ReadValue = 0xFFFF;
      CPU_Clock_Allocate();
      ADC_ENABLE();
      ADC_SETUP_MUX(ADC_MUX_VLCD);
      ADC_ENABLE_INTERRUPT();
      ADC_START();
    } 

    //LCD Charge Pump
    if (Timer_HasExpired(TIMER_LCD_CHARGE_PUMP)) 
    {
      LCD_CHARGE_PUMP_TOGGLE();
      Timer_Set(TIMER_LCD_CHARGE_PUMP,LCD_ChargePumpTimer);     
    }

    //Wait 1s for 32KHz crystal to stabilize before going to powersave mode
    if (Timer_HasExpired(TIMER_TIMER2_STABILIZE)) 
    {
      Timer_Reset(TIMER_TIMER2_STABILIZE);
      DEBUG_PRINT_INFO_("\r\nTIMER2 stable...\r\n");
      CPU_Clock_Free();
    }

    //LED blink alive indication
    if (Timer_HasExpired(TIMER_BLUE_LED)) 
    {
      BLUE_LED_CHANGE();
    }

    //Check if any timer already expired
    if (Timers_GotoToIdle())
    {
      //Is clock needed by any resource?
      if (CPU_Clock_IsAllocated() || !Buzzer_PowerSaveOk())
      {
        //Yes. Only goto idle
        //CPU_IDLE();

                                              
        MCUCR |= _BV(SE);                          
        __asm__ __volatile__ ("sleep" "\n\t" :: ); 
        MCUCR &= ~_BV(SE);                         
     }
      else {
        //Nope, no clock needed. Powersave.
        RED_LED_OFF();
        UART_POWER_SAVE_SLEEP();
        ADC_DISABLE();
        CPU_POWER_SAVE();
        UART_POWER_SAVE_WAKEUP();
        RED_LED_ON();
      }
    }
  }
  //Main program loop end

Download

Bullit Accelerometer

Copyright 2002 Jörgen Birkler. For accelR8 Accelerometer on Mon Jan 19 21:43:19 2004 by Doxygen 1.3.5.