Saturday, March 28, 2020

0000 0000 0010 1000

A dabble in AVR Assembly (II)

In an earlier blog I looked at putting a very tired (128kHz) Attiny13a to sleep using some assembler code. But when it's awake, maybe we want to make it act like a candle and flicker. "First learn stand, then learn fly" Mr Miyagi said, so instead of a deep dive I will see if I can get one PWM channel on the μC to ramp up and down, then maybe I can get the two of them to move randomly-ish?

First we choose a pin to output to:


PWM pins are pin5 and pin 6 from this diagram. If OC0A is on PB0 (pin5), and OC0B in on PB1 (pin6) then looking at the datasheet it seems that the TCCR0A register controls the PWM behaviour of these channels (copy 2 [binary 10] into COM0A0 and 2 [binary 10] into COM0B0).


We also want to use Fast PWM mode so in assembler (copy 3 [binary 11] into WGM00); these instructions look like this:

ldi r16, 0b10100011    ; load immediate binary 10100011 to r16 register
out TCCR0A, r16        ; move out the byte from r16 to TCCR0A

The full code is a little too boring for one blog - but you can find it linked here at github, and I go over it a little in the video linked below.

Now to randomise the PWM ramping up and down (flickering), the code will need some way of generating a "random" number. The simplest way is to use a basic linear feedback shift register (LFSR) algorithm as described on page 71-ish of my Attiny13A Assembler by Example booklet. 

For an 8-bit shift, I start at 0x29 (Decimal 41) and do the LSL operation (documented in this link). There is a branch then depending on the carry bit, and an XOR operation may also be performed (in this case with 0x8B). If all goes well, the algorithm produces numbers shown in green in the right hand column.

Of course - it is only "pseudo" random as the constant starting point and XOR number ensures a set pattern each time - but it'll do for our candley-goodness. You can set it to be more random by sampling a pin and using that as a starting point.

Op
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Hex
Dec
start
0
0
1
0
1
0
0
1
29
41
lsl
0
1
0
1
0
0
1
0
52
82
8B
1
0
0
0
1
0
1
1
8B
139
eor
1
1
0
1
1
0
0
1
D9
217
lsl
1
0
1
1
0
0
1
0
B2
178
lsl
0
1
1
0
0
1
0
0
64
100
lsl
1
1
0
0
1
0
0
0
C8
200
8B
1
0
0
0
1
0
1
1
8B
139
eor
0
1
0
0
0
0
1
1
43
67
lsl
1
0
0
0
0
1
1
0
86
134
8B
1
0
0
0
1
0
1
1
8B
139
eor
0
0
0
0
1
1
0
1
0D
13
lsl
0
0
0
1
1
0
1
0
1A
26
8B
1
0
0
0
1
0
1
1
8B
139
eor
1
0
0
1
0
0
0
1
91
145


Then having achieved "randomness" we will have to keep track of a few numbers in order to vary the ramping.


Slow Ramping (two LEDs)
Slow Low Low - the smallest low value for the slow LED (SLL = 15)
Slow Low High - the largest low value for the slow LED (SLH = 40)
Slow High Low - the smallest high value for the slow LED (SHL = 150)
Slow High High - the largest high value for the slow LED (SHH = 220)

Fast Ramping (one LED)
Fast Low Low - the smallest low value for the fast LED (FLL = 100)
Fast Low High - the largest low value for the fast LED (FLH = 140)
Fast High Low - the smallest high value for the fast LED (FHL = 180)
Fast High High - the largest high value for the fast LED (FHH = 220)


The plan is to generate a "random number" between 0 and 255 using our LFSR, and then when it falls between the SLL and SLH we nab it for the starting slow ramp low value. Likewise for the slow ramp high value, the fast ramp low value and the fast ramp high value. 

The loop just checks where each ramp is, and when it reaches one of the limits, generates a new limit and bounces towards that number. A little delay (a time worth varying using this site) means we can see the LEDs ramp up and down with the naked eye and we have a "candle"!


Postscript: I found that with quite a few starting conditions the flicker was a little too predictable so I have altered the code to "throw a spanner" into the random generator. Basically there is now a counter (which you can set) for keeping track of how many random numbers have been generated. When the counter is exceeded, the spanner is thrown by subtracting a number (also set) from the random number before proceeding. Preliminary results are encouraging! The new code has been uploaded to github - look for the keyword "spanner".


Saturday, March 21, 2020

0000 0000 0010 0111

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!






Saturday, March 14, 2020

0000 0000 0010 0110

Detecting AC Mains with a CD4017 circuit

A couple of years ago I swore that I would not open the pandora's box of logic gate chips. Again and again I would load up the AliExpress cart with assorted logic chips only to back out and go back to the familiar ground of Arduino, AVR and in particular the Attiny13 microcontroller.

After some shift registers arrived one day (and proved useful) I thought it wouldn't hurt to dip a toe in the ocean and so I followed up that purchase with a small batch of CD4077 chips, also useful, and suddenly the floodgates were open.

The YouTube algorithm kicked in and soon I was absorbing all manner of material specifically related to the use of logic gate chips. At some stage I must have pushed the button on a bunch of CD4093 chips, billed as four Schmitt-trigger circuits. Now I love Schmitt-trigger circuits, so how could this IC not be useful to me at some stage?

After singing the praises of internet circuit repositories on this very blog, I came across this potentially useful AC Mains detector.
Seems legit...
So confident was I that I grabbed one of my superbly functional 1206 SMD to DIP adapters and, without even bread-boarding a prototype, soldered the circuit together and tried to add the logic chip...and then came a MAJOR snag!

All linked and ready to go
In the circuit found online, the CD4093 is shown with 16 pins. Even a cursory glance (and there was some cursing eventually) reveals that this device has only 14 pins (e.g. in the circuit VCC is shown connected to a non-existent pin 16). Uh-oh. Diving deeper, I could see that some of the connections made no sense - why have an output diode linked to pin 10 when the datasheet shows this pin as an input?

Many hours of happy (??) searching later and I abandoned the CD4093 for a CD4017 (not again - how much Deja Vu can one blog stand?). After much testing I was even able to use the prematurely soldered circuit on the 1206 SMD adapter.

So now the circuit does indeed detect AC mains, but there was some redesigning necessary. For instance, the "Antenna" must come into the "clock" pin, which is pin 14 on the CD4017 (not pin 1 as shown in the online version). This is because the AC signal is detected as a clock signal and that is what triggers the counter IC to light the LED. Once triggered, the LED stays lit even when you move the antenna away from the AC source.

The LED can be extinguished by pulling the reset pin high which resets the counter, so I added a button on the end of the board.
Fritzing with through hole equivalents

The actual circuit after re-design

So it works! But how weird is it that the online version was so far off the mark?! Also, now I have to keep looking for a circuit to use the CD4093, which in the meantime has returned to the bucket of bits.