A dabble in AVR Assembly (I)
I have a project that I have been working on for a few years now. It entails a small 2V solar panel collecting the meagre rays we get down here in Tasmania (42.9° S, 147.3° E), and then with the help of a QX5252 IC charging an NiMH battery.The battery discharges at night to an Attiny13A running anaemically at 128kHz on low voltage and minimal current providing "candle" light via some beautiful 5050 yellow LEDs.
With a few hours of winter light on the panel, the candle runs pretty well through the night - a triumph of resource allocation and what a colleague of mine likes to call "running on the energy of a warm fart".
Along the way there have been quite a few challenges. Let's start with the whole idea of an AVR running at 128kHz, which according to the MicroCore documentation should be quite fine for the Attiny13A. That said, I "bricked" a few chips and spent countless hours trying to use this frequency reliably, even purchasing an "unbricker" which gave me my chips back, but did not deal with the cause of the problem.
I actually gave up for a year or so (using instead a more reliable 1.2MHz as internal clock on the chips) before I happened upon a blog by Łukasz Podkalicki which exactly described the solution - what a treasure. The key was having a USBasp geared so slow that it is able to converse with the μC at this speed. I have configured two such USBasp devices and have labelled them "The Slow Guys" - they actually take a noticeably longer time to upload to the μC.
A standard Attiny13A chip galloping along at 9.6MHz consumes 4.3mA doing night-time candley-goodness, but then the current requirements reduce to 1.3mA at 1.2MHz, and finally a miserly 400μA at 128kHz. All of these savings have a direct impact on the length of time the candle runs during the big dark.
Setting the clock fuses using this very useful site results in a command line for avrdude along the lines of "avrdude -c usbasp -p t13 -P /dev/ttyS0 -b 250 -U lfuse:w:0x7b:m -U hfuse:w:0xff:m". That's all a bit scary but I actually don't use avrdude on the command line anyway, but rather prefer to use the GUI version AVRdudess to load the hex file, and the Arduino IDE to set the fuses.
It was a remarkable experience the day I was able to get a blinky sketch running correctly at 1Hz from an Attiny13A ambling along 128kHz.
I then became consumed with the idea of a deep sleep (and hence minimal current) for my Attiny13A babies during the day when not needed to be "candles". Scouring the interwebs revealed some wonderful advice and tutorials from Nick Gammon. So I dived in with firstly an ATmega328p powered Arduino, then a bare Atmega328p and eventually an Attiny85.
When it came to the Attiny13A I could certainly get the creature to sleep well with mods based on Nick's work, but I was by that time reading the datasheet late at night and thinking that perhaps <gulp> assembler was the next step.
Now I hadn't written any assembler since the late 1990s when I was in way too deep writing Motorola 68000 code on the Amiga. First I tried to dive straight in and modify/combine various bits of AVR code found by trawling online, but in the end I bit the bullet and wrote functional lines of code for every single assembler instruction found for the Attiny13A, The result was my poo-litzer prize winning tome "AVR Assembler by Example".
Despite not topping the best seller charts, I love this little booklet because it now means that I can more easily understand how to put the Attiny13A to sleep, plus heaps of other cool stuff that seems only possible down at the bare metal level of programming.
I have to give a huge shout-out here to Gerhard Schmidt and his simulator software which has meant that I can compile and debug avr asm stuff on the fly, before committing the code (via the generated hex file) to the chip (see video below).
My trial program for deep sleep blinks an LED for one second, then sleeps for one minute. Here is the code:
;
.nolist
.include "tn13adef.inc" ; Define device ATtiny13A
.list
; **********************************
; H A R D W A R E
; **********************************
; Device: ATtiny13A, Package: 8 - pin - PDIP_SOIC
;
; _________
; 1 / | 8
; o-- | RESET VCC | --o
; o-- | PB3 PB2 | --o
; o-- | PB4 PB1 | --o
; o-- | GND PB0 | --o
; 4 | __________ | 5
;
; **********************************
; C O D E
; **********************************
.cseg
.org 000000
; **********************************
; R E S E T & I N T - V E C T O R S
; **********************************
rjmp Main ; Reset vector
reti ; INT0
reti ; PCI0
reti ; OVF0
reti ; ERDY
reti ; ACI
reti ; OC0A
reti ; OC0B
rjmp mytimer_ISR ; my interrupt
reti ; ADCC
; **********************************
; I N T - S E R V I C E R O U T .
; **********************************
mytimer_ISR:
; sleep is interrupted, program returns
reti
; **********************************
; M A I N P R O G R A M I N I T
; **********************************
;
Main:
ldi r16, Low(RAMEND)
out SPL, r16 ; Init LSB stack pointer
; output mode for PB4, input for the rest
ldi r16, 0b00010000
out DDRB, r16
; turn on PB4, enable internal pullups
; on the rest of the pins
ldi r16, 0b11111111
out PORTB, r16
; **********************************
; P R O G R A M L O O P
; **********************************
Loop:
rcall waitabit ; one second
rcall waitalot ; one minute
rjmp loop
waitabit:
; reset watchdog timer
wdr
; 1 second timer
ldi r16, 0b01000110 ; see datasheet
out WDTCR, r16
rcall sleepnow
rcall flipbit
ret
waitalot:
; reset watchdog timer
wdr
; 8 second timer
ldi r16, 0b01100001 ; see datasheet
out WDTCR, r16
ldi r21, 6 ; 6,5,4,3,2,1,0: 7x8=56 seconds
countdown:
; count down to zero and sleep in lots of 8
rcall sleepnow
dec r21
brbc 1, countdown
; finished long sleep, so flip LED
rcall flipbit
ret
sleepnow:
; see datasheet MCUCR, ADCSRA, BODCR
; sleep enable, power down mode
ldi r16, 0b00110000
out MCUCR, r16
; disable ADC
ldi r16, 0b00010000
out ADCSRA, r16
; disable interrupts to make sure the following
; timed sequence is not interrupted
cli
; disable BOD - must be done in a timed sequence
; as follows, write 1 to BODS and BODSE then within
; four clock cycles write 1 to BODS and 0 to BODSE
; then within three clock cycles enable sleep
; page 33 on the datasheet
ldi r16, 0b00000011
ldi r17, 0b00000010
out BODCR, r16
out BODCR, r17
sei
sleep
; sleep disable
ldi r16, 0b00000000
out MCUCR, r16
ret
flipbit:
in r16, portb
ldi r17, 0b00010000
eor r16, r17
out portb, r16
ret
; End of source code
So now that we have a hex file from the AVR simulator, we burn the fuses (either Arduino IDE or avrdudess), load the hex file to avrdudess and Program!


No comments:
Post a Comment