Projects‎ > ‎Programming‎ > ‎ATMEL AVR‎ > ‎

CycleLux


This code was used in a bicycle light controller for the CycleLux Project. In reads in the status of various switches connected to an external I/O expander and pulses the gate of various MOSFETs to control numerous LED banks. This project was completed while I was still fairly new to programming, and as such, could be greatly improved upon. I have plans for a new version of this entire project. When that is complete, I will update the source code here.

Included Header Files

I used my standard custom AVR.h file in addition to a custom header file for controlling the Microchip MCP23xnn I/O expanders  Also, a header file containing all of the project specific constants is used.

#include <custom/avr.h>
#include <custom/MCP23xnn.h>
#include "cycleLux.h"

Definitions

There were a decent number of constants in use, primarily to describe the input switches and output LED banks. A few macros for controlling pins were also used. All of these are easily identifiable in the "cycleLux.h" file.

Global Variables

There were a few global variables in use. They are mostly counters and status flags for the ISRs . The "switches" variable is used to record the state of each input switch.

volatile uint16_t ms_delay_cnt = 0; // 1ms delay counter
volatile uint16_t flash_delay = 0; // Flashers delay counter
volatile uint8_t brake_delay = 0; // Brake delay counter

volatile uint8_t stat_flag = 0x00; // Status Flags (see cycleLux.h)
volatile uint8_t stat_flag2 = 0x00; // Status Flags (see cycleLux.h)

volatile uint8_t switches = 0x00; // Input Switch Values

Main

The main part of the program is responsible for initially checking if a switch has been pressed by polling the MCP23S17 I/O expander. It will then determine what LED outputs should be on based on the switch states. The correct output states are then written to the MCP23017 which turns on the appropriate LEDs using MOSFET drivers. For example, to check the "bright" switch, the following is done:

if(check_switch(BRIGHT)){ //   If Bright Switch...
    TOGGLE_BIT(stat_flag, BRIGHT_S); //     Toggle Bright Switch State
    switch_timer = SWITCH_DELAY; //     Set delay timer for switches
}

The LED status flags are then compared to the current LED states as well as what banks should be on or off, given the current mode. This data is then sent out by doing the following:

// Check to see if the circuit has been disabled
if(!(switches & ENABLE)){ // Check to see if still enabled
  outputs = 0x00; // If disabled, turn off all lights
  stat_flag &= ~(DIM_S | BRIGHT_S | LEFT_S | RIGHT_S | BRAKE_S);
}
// Write the Lights output state to the MCP23S17
write_MCP23S17(MCP_0_WR, GPIOB, outputs); // Write to the outputs


Interrupt Handlers

The only interrupt in use is the timer0 compare match A which is the core timing mechanism of the program. It is used to create a 1ms delay as well as time the flashing of certain LED outputs, such as blinkers and the initial flashing of the brakes. The methods used to control the flashing could definitely be improved, as previously mentioned. In a future version, this functionality will be handled very differently.

/**************************************************************************
INTERRUPT HANDLERS
***************************************************************************/
// Timer 0 Compare Match A (every 1ms)
ISR(TIMER0_COMPA_vect)
{
  // Millisecond Delay Counter
  if(ms_delay_cnt) ms_delay_cnt--; // decrement ms timer
  else CLEAR_BIT(stat_flag, DELAY_MS); // when done, reset flag
  
  // Brake Flash Delay Counter
  
  #ifdef BLAH
  if((stat_flag2 & BRAKE_E) && brake_cnt){ // If Brake Flash is enabled...
if(++brake_delay == BRAKE_ON){ //   If Reached ON Time...
 CLEAR_BIT(stat_flag2, BRAKE_S); //     Turn Off Brakes
 if(!(--brake_cnt)) stat_flag &= ~BRAKE_F; //     Disable Flash when decrimented count = 0;
}
else if(brake_delay == BRAKE_TOT){ //   If Reached OFF Time...
 SET_BIT(stat_flag2, BRAKE_S); //     Turn On Brakes
}
  }
  #else
  if(brake_delay){ // If Brake Flash is enabled...
    if(--brake_delay == BRAKE_ON){ // If Reached ON Time...
 SET_BIT(stat_flag2, BRAKE_S); //     Turn On Brakes
}
else if(!brake_delay){ // If Reached end of ON Time...
 CLEAR_BIT(stat_flag2, BRAKE_S); //   Turn off Brakes
}
  }
  #endif // BLAH
  
  // Blinker Flash Delay Counter
  if(flash_delay){ // If Flashers have been enabled...
    if(--flash_delay == FLASH_ON){ // If Reached ON Time...
 if(stat_flag & FLASH_L){ //   If Left Blinker...
SET_BIT(stat_flag, LEFT_S); //     Turn On Left Blinker
SET_BIT(stat_flag2, L_SIDE_S); //     Turn On Left Side
 }
 if(stat_flag & FLASH_R){ //   If Right Blinker...
SET_BIT(stat_flag, RIGHT_S); //     Turn On Right Blinker
SET_BIT(stat_flag2, R_SIDE_S); //     Turn On Right Side
 }
}
else if(!flash_delay){ // If Reached end of ON Time...
 stat_flag &= ~ (LEFT_S | RIGHT_S); //   Turn off L / R Blinker
 stat_flag2 &= ~ (L_SIDE_S | R_SIDE_S); //   Turn off L / R Side
}
  }
  else (stat_flag &= ~(LEFT_S | RIGHT_S)); // Else Turn Off Both
}

Utilities and Initialization

To control the MCP23s17, I used a software SPI transmission since the ATtiny25 uses the USI for SPI communication, and it is just easy to control in manually for what little data I was transmitting. 

// Bit Bang SPI 8 Bits
unsigned char SPI_bitBang(uint8_t byte)
{
  uint8_t bit = 0;
  for(bit = 0; bit < 8; bit++)
  {
if(byte & 0x80) SET_DO; // If bit(7) of "byte" is high
else CLEAR_DO; // if bit(7) of "byte" is low
byte <<= 1; // Shift "byte" to the left by one bit
SET_SCK; // Serial Clock Rising Edge
byte |= (PINB & U_DI); // Set bit(0) of "byte" to data in value
CLEAR_SCK; // Serial Clock Falling Edge
  }
  return byte; // Returns shifted data in value
}

// Write 8 Bits of Data to a Specific Data Bus
void write_MCP23S17(const uint8_t opcode, const uint8_t reg_addr, const uint8_t reg_data)
{
  cli(); // Turn Off Interrupts during read/write
  CLEAR_CS; // Lower Chip Select to start transfer
  SPI_bitBang(opcode); // MCP Operation Code = MCP Address + write
  SPI_bitBang(reg_addr); // MCP Data Register Address
  SPI_bitBang(reg_data); // MCP Data Register Data
  SET_CS; // Raise Chip Select to stop transfer
  sei(); // Turn on Interrupts after read/write
}

// Read 8 Bits of Data from a Specific Data Bus
unsigned char read_MCP23S17(const uint8_t opcode, const uint8_t reg_addr)
{
  uint8_t byte = 0;
  cli(); // Turn Off Interrupts during read/write
  CLEAR_CS; // Lower Chip Select to start transfer
  SPI_bitBang(opcode); // MCP Operation Code = MCP Address + read
  SPI_bitBang(reg_addr); // MCP Data Register Address
  byte = SPI_bitBang(0x00); // Empty Data to shift out
  SET_CS; // Raise Chip Select to stop transfer
  sei(); // Turn on Interrupts after read/write
  return byte; // Return data shifted in
}

The other important utilities are the ones used to check and debounce switches. This was my first attempt at performing this function in software. I now know better ways to do it, but a the time, this was fairly difficult to figure out...

unsigned char debounce_switch(void)
{
  uint8_t state = 0x00;
  delay_ms(MS_DEBOUNCE); // Delay for so long
  state = read_MCP23S17(MCP_0_RD, GPIOA); // Check Button Status
  return state; // return switch stats
}

unsigned char check_switch(uint8_t input)
{
  uint8_t state = 0x00;
  switches = read_MCP23S17(MCP_0_RD, GPIOA); // Check Button Status
  if(switches & input){ // Check for a particular switch
switches = debounce_switch(); // Debounce Switch
if(switches & input) state = 1; // Check again after debounce
  }
  return state; // return switch state
}

The AVR intializatoin is pretty straight forward: Set up the I/O pins and turn on timer0 for a 1ms ISR.

Downloads



  Click here to download all of the necessary source code files!