Silly little project - bicycle rear light

Thread Starter

Robartes

Joined Oct 1, 2014
57
This 'project' (it's not really big enough to qualify as a full project IMO, hence the quotes) came about when my son (8 yo) came home from school with yet another light missing off of his bicycle -- I swear he or his classmates eat the things.

Anyway, this time instead of being him another set of lights (although in the end that's probably cheaper, but money is never the reason we do these things), I decided to build my own. The design parameters for this project were:

  • the light is battery powered, so low power consumption is required
  • the circuit would need to be powered from 3V (either a stepped down/regulated LiIon or 2 plain 1.5V AA batteries)
  • the circuit would need to fit inside the casing of a bicycle rear light I had lying around from one of his previous escapades
  • the circuit would need to reproduce the behaviour of my own bicycle's rear light -- it works with a button (momentary pushbutton), which cycles the light between off, blinking (at about 2Hz) and on

With these parameters in mind, I came up with the following design, based on individual IC's & components:

ic_schematic.jpg

The basic idea behind this circuit is that the 'cycling' behaviour of the pushbutton is implemented by a 2-bit counter built from 2 D-type flipflops (the 74HC74 chip), with the fourth state of this counter (11) resetting the entire thing back to the initial state using a simple NAND gate built out of two transistors ('manual TTL' so to speak :) ). The rest of the circuit has some transistors to switch two LEDs and a 7414 inverting Schmitt trigger IC that does the debouncing of the pushbutton and provides an astable multivibrator for the 'blinking' state.

The circuit when built resulted in this rat's nest of cabling (I really should work on my point to point protoboard wiring skills):

IMG_1737.jpg IMG_1736.jpg


And of course, this first attempt did not work at all (as in no light whatsoever). While I had built it successfully on a breadboard, transferring it to protoboard presented some challenges. One was the wiring rat's nest shown above, which undoubtedly would have introduced lots of fun issues. However, it never came to that because of another issue the keen eyed among you might have already spotted: I soldered the IC's directly to the board, without sockets. This, combined with the rat's nest above, resulted in my soldering iron hanging around some of these pins for far too long and destroying the 74HC74 chip. When I had finished the circuit and tested it, I noticed it did not do anything and started debugging with multimeter & scope. I found that the fist Schmitt trigger did present the correct pulse on the first D-flop's clock pin, but then the 74HC74 never did anything with it (Q0 remained low). After rereading the 74HC74's datasheet, I am now fairly sure I exceeded the 'maximum soldering temperature' (around 250 degrees C for 10 seconds) on a number of the 74HC74 pins. I probably destroyed the connection between the IC's die and the pin by brute thermal force :).

In addition to the circuit not working when built on protoboard (which is a pretty big show stopper by itself :) ), I was not really happy with the power consumption of it in idle state when I measured it on the breadboard version -- it still drew a significant fraction of a milliamp, which with batteries of a few hundred mAh would mean lots of battery replacements.

So, back to the drawing board. This time, instead of going the hardcore TTL logic (well, high speed CMOS, to be pedantic :) ) route, I decided to use this as an opportunity to brush up on my C skills and implement the entire thing with an Atmel ATtiny microprocessor. The circuit for this is simplicity itself, of course:

avr_schematic.jpg IMG_1734.jpg

(please take note of the professional way of fixing the board in the case)

All of the good stuff in this version is in the code, which is included below.

This is the version that is going into my son's bicycle (well, actually my own, as I gave him my rear light to tide him over and he's not keen on giving it back :) ). In idle state, it consumes less than my multimeter can measure on its lowest setting (microamps) so it should keep going for over a year before the battery needs replacing, depending on how much bicycle riding at night I do of course: in 'on' mode it draws just over 20mA, which, incidentally, is the reason I did not include any current limiting resistors for the LEDs. Next step is to power this from a LiIon battery charged by the dynamo on my bicycle, but that's another project.

Thanks for reading this far! Code for the ATTiny included below:

Code:
/* Name: fietslicht.c
* Author: Bart Vetters <robartes@nirya.be>
*/

#define F_CPU 1000000

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include <stdint.h>

#define LED                  PB0
#define BUTTON               PB1
#define BUTTON_INTERRUPT_PIN PCINT1
#define MIN_DEBOUNCE_COUNT   15

typedef enum {
    OFF,
    ON,
    BLINKING,
} led_status_t;

#define NUM_LED_STATUS 3

/* button_press_counter
   Keeps track of button presses for debouncing purposes
*/
volatile unsigned char button_press_counter = 0;


/* Interrupt handler for button press. This does not actually
   do anything, as its only function is to wake up the processor
*/
EMPTY_INTERRUPT(PCINT0_vect);


/* She's got no more power, cap'n */
void init_power_save(void) {

  /* Shut down stuff we do not need */
  power_adc_disable();
  power_timer1_disable();
  power_usi_disable();

}

/* Interrupt initialisation */
void init_interrupt(void) {

    // Enable pin change interrupt
    GIMSK |= (1 << PCIE);

    // Set BUTTON pin mask for pin change interrupt
    PCMSK |= ( 1 << BUTTON_INTERRUPT_PIN);

    // Enable interrupts
    sei();

}

/* Timer utility functions */
void clear_timer_output(void) {

    // Set COM0Ax to 0 to disconnect timer from LED pin
    TCCR0A &= ~( 1 << COM0A1 | 1 << COM0A0 );

}

void hold_timer(void) {

    // Assert TSM & PSR0 bits to stop timer
    GTCCR |= (( 1 << TSM ) | ( 1 << PSR0 ));

}

/* Set up timer for PWM. Aim is about 2Hz with 70% duty cycle */
void start_timer(void) {

    // Hold timer
    hold_timer();

    // Set OCR0A for 70% duty cycle (0.7 * 255 =~ 178);
    OCR0A = 178;

    // Set TCCR0A for non-inverting PWM on OC0A ( OC0A = PB0 = LED)
    TCCR0A |= ( 1 << COM0A1 );
    TCCR0A &= ~( 1 << COM0A0);

    // Set TCCR0A & TCCR0B for Phase correct PWM, count to 0xFF
    TCCR0B &= ~( 1 << WGM02 | 1 << WGM01 );
    TCCR0A |= ( 1 << WGM00 );

    // Set timer prescaler to glacial (CLKio / 1024)
    // 512 timer clock ticks is a full period, timer clock is at 1E6/1024, so
    // we end up just shy of 2Hz
    TCCR0B |= (1 << CS00 | 1 << CS02);
    TCCR0B &= ~( 1 << CS01 );

    // Set TSM to 0 to start timer. Hardware resets PSR0 automatically
    GTCCR &= ~( 1 << TSM );

}



/* Initiate sleep mode */
void go_to_sleep(void) {

   // Set Power-Down sleep mode
   MCUCR |= ( 1 << SM1 );
   MCUCR &= ~( 1 <<SM0 );

   // Go to sleep
   sleep_enable();
   sleep_cpu();


}

/* Check button press (includes debouncing) */
uint8_t check_button_pressed(void) {

    if ( bit_is_clear(PINB, BUTTON) ) {
      // Increase button loop counter
      button_press_counter++;
    } else {
      // No more button pressed
      button_press_counter=0;
    }

    if ( button_press_counter >= MIN_DEBOUNCE_COUNT ) {
      // We have a live one. Wait a while to make sure user
      // releases button
      _delay_ms(500);
      button_press_counter=0;
      return 1;
    } else {
      return 0;
    }

}

void set_led_status (led_status_t expected_led_status) {

    switch ( expected_led_status ) {
   
        case OFF:
            hold_timer();
            clear_timer_output();
            PORTB &= ~(1 << LED);
            go_to_sleep();
            break;

        case BLINKING:
            start_timer();
            break;

        case ON:
            hold_timer();
            clear_timer_output();
            PORTB |= ( 1 << LED );
            break;
    }
   
}



int main(void)
{

    led_status_t current_led_status = OFF;

    /* Set LED as output */
    DDRB |= ( 1 << LED);

    /* Enable pull up on all unused pins (datasheet best practice) */
    PORTB = (0x3f & ~( 1 << LED ));

    /* The above two commands also take care of BUTTON correctly, so no
       need to repeat ourselves here */

    /* Put chip in minimum power consumption mode */
    init_power_save();

    /* Enable pin change interrupt on BUTTON */
    init_interrupt();

    /* Turn off LED & go to sleep */
    PORTB &= ~( 1 << LED );
    go_to_sleep();

    /* Event loop. Wait for button presses to toggle leds around */
    for(;;) {
        if ( check_button_pressed() ) {
          if ( ++current_led_status == NUM_LED_STATUS ) {
            // Reset back to 0. c enums aren't smart enough to have ++ operator wrap around
            current_led_status = OFF;
          }
          set_led_status(current_led_status);
        }
    }
    return 0;   /* never reached */
}
 

#12

Joined Nov 30, 2010
18,224
I already told the moderators to consider this for the Completed Projects forum. Right now, you are where most of the, "need help" posts arrive.
 

Thread Starter

Robartes

Joined Oct 1, 2014
57
I already told the moderators to consider this for the Completed Projects forum. Right now, you are where most of the, "need help" posts arrive.
Yes, it was moved - thanks! I initially chose the Projects forum instead of this one as the 'How to post a project here' topic at the top there suggested to do so. I'll remember for next time :)
 
Top