Sunday, November 4, 2012

Morse Code Bicycle Safety Blinker

$3 in parts
Runs 4 days on 2 AA's
Tired of the boring steady blink? Sport a blinker that will get the attention of any old school ham radio operator! Check the Youtube video of the action.

This is my first project using an AVR microcontroller and really the first embedded systems project I've done since college. I had two options for programming languages C and assembly. I chose assembly because I was familiar (at least with the 8051) and because it is well documented in the datasheets. Another advantage of assembly is the ability to know exactly what is going on especially when using a device simulator.

Tools Needed: Parts Needed:
Soldering Iron
USBtinyISP
Breadboard
Computer
Free Software
#22 Solid Hookup Wire
Wire Strippers
Needle Nose Pliers
Tactile Switch
ATTiny85v Microcontroller
Bright RED LED
9V Battery Clip
2xAA Battery Holder
#24 Stranded Wire (similar to that on the battery clip) 
  • Step One: Install the USBtinyISP Drivers. I've found the LadyADA writeup to be quite adequate.
  • Step Two: Install WinAVR. This Ladyada writeup is also worth a look.
  • Step Three: Install AVR Studio 4 (requires registration).
    • Don't bother to install the Jungo USB Driver.
  • Step Four: Write your program in AVRStudio.
    • Open Start -> Programs -> Atmel AVR Tools -> AVR Studio 4
    • Select New Project
    • Select ATMEL AVR Assembler
    • Give your project a descriptive name and choose a folder. Remember the folder name, you will need it later.
    • Choose a debug platform, if you aren't sure use AVR Simulator.
    • Choose the target device. ATtiny85.
    • Copy and paste the code below. If you're brave feel free to tweak it a little.
    • Press F7 to compile the code.
  • Step Five: Program Fuses (this needs to be done once per IC)
    • Check out the The Engbedded AVR Fuse Calculator.
    • Choose the ATTiny85 AVR Part. (There is no ATtiny85v option).
      • Feature Configuration
      • Select "WD. Osc. 128 kHz; Start-up time PWRDWN/RESET: 6CK/14CK + 64ms: [CKSEL=100 SUT=10]
      • A slow clock requires less battery power.
      • Uncheck "Divide clock by 8 internally; [CKDIV8=0]
      • Check "Serial program downloading (SPI) enabled; [SPIEN=0]"
      • All other's should be unchecked.
      • Brown-out detection should be disabled.
    • No changes will be made to the Manual Fuse Bits section
    • Under Current settings copy down the generated AVRDUDE arguments
      • These should look suspeciously like -U lfuse:w:0xE4:m -u hfuse:w:0xdf:m -U efuse:w:0xff:m
    • Stuff to watch out for:
      • Any Clock selection beginning with EXT (you will need a crystal to program your chip)
      • Forgetting to check serial program (SPI) enabled. (Chip is bricked).
      • Disabling reset to gain an extra pin. (Chip is bricked, reset needed for programming.)
    • Open a DOS Prompt and execute the following command: avrdude -p attiny85 -P USB -c USBTINY -U lfuse:w:0xe4:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m
  • Step Six: Breadboard the programmer and microprocessor.
    • Set the processor on the breadboard across the channel.
    • Press the pins into the breadboard by gently pressing on the processor.
    • Use #22 solid wire to connect the pins on the processor to the ones on the programmer's 6 pin cable.
    • Keep in mind that your processor may have a dot above pin 1 rather than the notch shown.

  • Step Seven: Program the microprocessor
    • Open Dos and change to the folder your project resides in.
    • type: avrdude -p attiny85 -P USB -c USBTINY -U flash:w:my_project.hex -B 10000 (the -B 10000) slows down the programmer for the slow clock speed.
  • Step: Eight: Connect up the circuit
    • This is much easier to do on the breadboard to get started.


Advanced Concepts
Integrating USBTinyISP w/AVR Studio
The Simulator (See AVR Studio Help Screens)

In this case I mounted the whole deal to a Fi'zik - Saddle Attachment system.


The Code


;Bikey Talkie
;by Tim Kyle
.include "tn45def.inc" ;Includes the ATTiny45/65/85 definitions file
.org 0x0000 ;Places the following code from address 0x0000

;The Following instructions and "nop"s are for handling interrupts.
rjmp RESET ;Take a Relative Jump to the RESET Label
rjmp Interrupt0 ;Handle an interrupt input on INT0 or PortB Pin 2 (case pin 7)
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
reti ;return from any other interrupts that might be thrown. They are not important.

Interrupt0:
ldi r16, 0x10 ; this represents about 1/3 of a second for a dit
rcall dah ; this produces a time delay long enough to handle switch bounce.
ldi r18, 0x00 ; Zero reference (there are easier ways I know)
cpse r18, r22 ; Was the unit on or off? If equal (off) skip 1 instruction
rjmp RESET
pop r20 ;pop the old program counter from the stack. It isn't important. R20 is a convenient place to put it.
sei ;Turn interrupts back on
rjmp LOOP

RESET: ;Reset Label
;Turn off all unnecessary clocks, timers, etc
ldi r16, 0x0F
out PRR, r16

;The momentary button is connected to DIP pin 8 (INT0, Port B Pin 2). It should normally be pulled up.
;The LED is connected directly between pin 3 (Port B pin 4) and Vcc. PWM replaces the resistor.
ldi r22, 0x00 ; Tell the interrupt process that the light is currently off.
sei ; set Global Interrupt Enable
;A high in the DDRB means we will use the pin for output, zeros will configure the pin for input.
;Unused pins will be configured as outputs to avoid unnecessary interrupts being thrown.
;Pin7 Pin6 Pin5 Pin4 Pin3 Pin2 Pin1 Pin0
;  1 1  1   1    1 0  1   1  = FB
ldi r16, 0xFB 
out DDRB, r16 ;Store this value in The PORTB Data direction Register
;A high in the PortB register would enable the pull up resistor for an input pin.
;A high in the PortB register sets an output pin high. (in this case turning off the light)
;Pin7 Pin6 Pin5 Pin4 Pin3 Pin2 Pin1 Pin0
;  0 0  0   1    0 1  0   0  = 14
ldi r16, 0x14 
out PORTB, r16
 
;GIMSK is the global interrupt mask. 
ldi r16, 0x40 ;Enable INT0 Interrupt
out GIMSK, r16 ;Enable INT0 Interrupt

;MCUCR Register
;00100001=21
;
;7 BODS - R/O Brown Out Detector during Sleep (page 38)
;6 PUD - Pull up disable (page 55,57)
;5 SE - Sleep Enable (Page 39) Set right before power down and unset right after power up.
;4 SM1 -Sleep Mode (page 39)
;3 SM0 -Sleep Mode (page 39)
;2 BODSE - R/O Brown Out Detector Sleep Disable (page 38)
;1 ISC01 - Interrupt sense control
;0 ISC00
;
;ISC
;00-Low Level Causes Interrupt
;01-Toggle Causes Interrupt
;10-Falling Edge
;11-Raising Edge
;
;SM
;00-Idle
;01-ADC Noise Reduction
;10-Power-down
;11-Reserved

;BODS  PUD  SE   SM1  SM0 BODSE ISC01 ISC00
;  0 0  1   1    0 0   0     0  = 30


ldi r16, 0x30 ;This is where the edge trigger for the interrupt and the sleep stuff is hiding.
out MCUCR, r16
sleep



LOOP: ;Loop Label
ldi r22, 0xff ; Tell the interrupt process that the light is currently on.
ldi r16, 0x08 ; this represents about 1/3 of a second for a dit
rcall dit
rcall dit
rcall dah
rcall dah
rcall dit
rcall dit
rcall space
rcall dah
rcall dah
rcall dit
rcall dit
rcall dah
rcall dah
rcall space
rjmp LOOP

;Subroutine Space. Tim Kyle Rev 0.1
;Waits the amount of time necessary for an interword space
;Uses Registers R16, R17 courteously (restores their previous values before return)
;Uses Labels 
;Requires: Wait
Space:
push r16 ;Save previous contents of R16 to the stack.
push r17 ;Save previous contents of R17 to the stack.
rcall WaitOff
rcall WaitOff
rcall WaitOff
pop r17
pop r16
ret


;Subroutine Dah. Tim Kyle Rev 0.1
;Waits the amount of time necessary for one dah
;Uses Registers R16, R17 courteously (restores their previous values before return)
;Uses Labels 
;Requires: Wait
Dah:
push r16 ;Save previous contents of R16 to the stack.
push r17 ;Save previous contents of R17 to the stack.
ldi r17, 0x04 ;Load Light On (the 4 keeps the button pull up resistor held high)
out PORTB, r17 ;Turn Light On
rcall WaitOn
rcall WaitOn
rcall WaitOn
ldi r17, 0x14 ;Turn Light Off (the 4 keeps the button pull up resistor held high)
out PORTB, r17 ;Turn Light Off
rcall WaitOff
pop r17
pop r16
ret


;Subroutine Dit. Tim Kyle Rev 0.1
;Waits the amount of time necessary for one dit
;Uses Registers R16, R17 courteously (restores their previous values before return)
;Uses Labels 
;Requires: Wait
Dit:
push r16 ;Save previous contents of R16 to the stack.
push r17 ;Save previous contents of R17 to the stack.
ldi r17, 0x04 ;Load Light On (the 4 keeps the button pull up resistor held high)
out PORTB, r17 ;Turn Light On
rcall WaitOn
ldi r17, 0x14 ;Turn Light Off (the 4 keeps the button pull up resistor held high)
out PORTB, r17 ;Turn Light Off
rcall WaitOff
pop r17
pop r16
ret


;Subroutine WaitOff. Tim Kyle Rev 0.1
;Waits the number of cycles specified in R16 * 1027 + 26 with the light off
;Uses Registers R16, R17, R18 courteously (restores their previous values before return)
;Uses Labels Wait, Wait1, Wait2
;If R16 is set to 0, cycles=28
;Requires: None
WaitOff:
push r16 ;Save previous contents of R16 to the stack.
push r17 ;Save previous contents of R17 to the stack.
push r18 ;Save previous contents of R18 to the stack.
ldi r17, 0x00 ;r17 is set to 0 because it is decremented to 255 before the compare is done.
ldi r18, 0x00 ;r18 is used as the 0 register to compare with because I opted to use cpse.
cpse r16, r18 ;if a zero is passed in R16 exit (otherwise it would be equivalent to 0xFF+1)
rjmp Wait1
rjmp Wait2
Wait1:
nop
nop
nop
nop
nop
nop
nop
dec r17
cpse r17, r18 ;Compare R1 and R3 (always 0) if they are equal skip the next instruction.
rjmp WAIT1
dec r16
cpse r16, r18
rjmp WAIT1
pop R18 ;Return previous contents of R18
pop R17 ;Return previous contents of R17
pop R16 ;Return previous contents of R16
ret
Wait2:
pop R18 ;Return previous contents of R18
pop R17 ;Return previous contents of R17
pop R16 ;Return previous contents of R16
ret
;End of Wait Subroutine

;Subroutine WaitOn. Tim Kyle Rev 0.1
;Waits the number of cycles specified in R16 * 1027 + 26 with the light on
;Uses Registers R16, R17, R18 courteously (restores their previous values before return)
;Uses Labels Wait, Wait1, Wait2
;If R16 is set to 0, cycles=28
;Requires: None
WaitOn:
push r16 ;Save previous contents of R16 to the stack.
push r17 ;Save previous contents of R17 to the stack.
push r18 ;Save previous contents of R18 to the stack.
push r19
push r20
ldi r17, 0x00 ;r17 is set to 0 because it is decremented to 255 before the compare is done.
ldi r18, 0x00 ;r18 is used as the 0 register to compare with because I opted to use cpse.
ldi r19, 0x04 ;Zeros for turning light on (the 4 keeps the button pull up resistor held high)
ldi r20, 0x14 ;One for Turning Light off (the 4 keeps the button pull up resistor held high)
cpse r16, r18 ;if a zero is passed in R16 exit (otherwise it would be equivalent to 0xFF+1)
rjmp WaitOn1
rjmp WaitOn2
WaitOn1:
out PORTB, r20 ;Turn Light off
nop
;nop
;nop
;nop
;nop
;nop
;nop
out PORTB, r19 ;Turn Light On
dec r17
cpse r17, r18 ;Compare R1 and R3 (always 0) if they are equal skip the next instruction.
rjmp WaitOn1
dec r16
cpse r16, r18
rjmp WaitOn1
pop R20 ;Return previous contents of R20
pop R19 ;Return previous contents of R19
pop R18 ;Return previous contents of R18
pop R17 ;Return previous contents of R17
pop R16 ;Return previous contents of R16
ret
WaitOn2:
pop R20 ;Return previous contents of R20
pop R19 ;Return previous contents of R19
pop R18 ;Return previous contents of R18
pop R17 ;Return previous contents of R17
pop R16 ;Return previous contents of R16
ret
;End of WaitOn Subroutine

No comments:

Post a Comment