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