Saturday, December 19, 2020

0000 0000 0100 1110

Christmas supercomputer

Note: it is the summer holidays DownUnder, so this blog (and YT channel) will be taking a little break for a month - see you in 2021!

Who doesn't love red and green flashing LEDs at Christmas? For the last couple of years we have had these little CR2032 driven ATTiny13 chips flashing a couple of 5mm LEDs strewn throughout the house (and outside).

The microcontrollers are running at 1MHz and they are mostly sleep, just pumping out alternate 1/8th second blips to the LEDs followed by a 4 second gap. In this guise the coin cells last around 6-9 months (!!), although they do get a bit dim towards the end of that run.


This year I decided to update the project, again using ATTiny13a chips, but this time ambling along at 128kHz (less power) which switch 1206 SMD LEDs (less power at 1/4W), but flashing more frequently (1/8th second on, 1/2 second off). So it will be very interesting to see how they last through the festive season and beyond. I can't remember if the original was coded in assembly or not, but this one sure is:

;
; Device: ATtiny13A, Package: 8-pin-PDIP_SOIC
;
;            ______
;         1 /      |8
; RESET o--|       |--o VCC
;         2|       |7
;   PB3 o--|       |--o PB2
;         3|       |6
;   PB4 o--|       |--o PB1
;         4|       |5
;   GND o--|       |--o PB0
;          |_______|
;

.nolist
.include "tn13adef.inc"         ; Define device ATtiny13A
.list

; **********************************
;       R E G I S T E R S
; **********************************

.def rSreg = R15                   ; Save/Restore status port
.def rmp = R16                     ; Define multipurpose register
.def colour = R1

; **********************************
;           S R A M
; **********************************

.dseg
.org SRAM_START

; **********************************
;         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                 ; watchdog timer interrupt
    reti ; ADCC

; **********************************
;  I N T - S E R V I C E   R O U T .
; **********************************

; Add all interrupt service routines

mytimer_ISR:

     ; as I understand it, cli and sei are automatic on
     ; entry and exit respectively of ISR

     in r16, SREG      ; save SREG
     push r16          ; do this if you want to use

finishisr:

     pop r16           ; do this if you pushed SREG above
     out SREG, r16     ; restore SREG

     reti

; **********************************
;  M A I N   P R O G R A M   I N I T
; **********************************

Main:
    ldi rmp,Low(RAMEND)
    out SPL,rmp                    ; Init LSB stack pointer

    ldi r16, 0b00011000            ; output mode for PB3/4
    out DDRB, r16
    ldi r16, 0b00010000
    mov colour, r16                 ; red is on

    ; turn on PB4, enable internal pullups
    ; on the rest of the pins except PB3
    ldi r16, 0b11110111
    out PORTB, r16

; **********************************
;    P R O G R A M   L O O P
; **********************************

Loop:

    rcall waitabit                 ; 1/16 second subroutine
    rcall waitalot                 ; 1/2 second subroutine

    rjmp Loop

waitabit:

    wdr                      ; reset watchdog timer
    ldi r16, 0b01000010      ; 1/8 second timer
    out WDTCR, r16
    rcall sleepnow           ; sleep subroutine
    rcall flipbit            ; flip the led, if off then on, etc.
    ret

waitalot:

    wdr                      ; reset watchdog timer
    ldi r16, 0b01000101      ; 1/2 second timer
    out WDTCR, r16
    rcall sleepnow           ; sleep subroutine
    rcall flipbit            ; flip the leds
    ret

sleepnow:

    ; 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, pinb              ; what is the state of the pins?
    andi r16, 0b00011000      ; load red or green on
    brbc 1, turnoff

testred:
    mov r16, colour
    ldi r17, 0b00001000
    cp r16, r17
    brbs 1, greenon

redon:
    ldi r16, 0b00001000       ; set colour red
    mov colour, r16           ; and make it so
    ldi r16, 0b00001000
    out portb, r16
    rjmp finishflip

greenon:
    ldi r16, 0b00010000       ; set colour green
    mov colour, r16           ; and make it so
    ldi r16, 0b00010000
    out portb, r16
    rjmp finishflip

turnoff:
    ldi r16, 0b00000000
    out portb, r16

finishflip:

    ret

; End of source code


The only downside is that with the frantic flashing the place looks like an alien landing site or a Christmas super computer - good one!

In line with some other current projects, it is my solemn promise to make next year's batch from Pesky Padauk microcontrollers, probably the PMS150C.




Saturday, December 12, 2020

0000 0000 0100 1101

Orange Pi temperature control

There is an Orange Pi Zero attached to my 3D printer running OctoPrint as per a previous post and YouTube video. On the assumption that it might run hot at times (introducing the possibility of a throttle or shutdown scenario) I connected a fan to the top of the printed enclosure which runs continuously off the 5V rail.


The fan is a bit noisy, although that doesn't matter as the unit is located downstairs in the garage/workshop, but also I was thinking that it may not be necessary to have the fan running all of the time. Maybe it only needs to cut in and out as the temperature of the board changes?

While the Orange Pi Zero is working well with the printer at the moment I don't want to hassle it too much, so to test a few ideas kicking around in my noggin I grabbed an Orange Pi PC as a guinea pig instead.

I loaded an SD card with the appropriate version of Armbian (choosing the "focal" version). Included with the OS is the command "armbianmonitor -m" which can check the temperature of the SOC directly. You can also find out the temperature via the command:

"cat /sys/class/thermal/thermal_zone0/temp"

...which will output something like 29200 if the temperature is 29.2°C.

The first (and most recommended) method of temperature mitigation for this board is to attach a heat sink to the SOC, in this case an ARM H3+.


Without doing extensive tests, this one simple modification did lower the temperature quite a bit - and I'm pretty sure from reading a few of the forums posts out there that this would be sufficient to keep the board cool. The only thing is that if the board does start to overheat, then performance is throttled and perhaps a a long print run might end up being interrupted and/or stopped - very sad!

Therefore, I 3D-printed a suitable enclosure that allowed for a fan to be installed directly over the SOC.

The fan works directly from either the 5V or the 3.3V rail as per the Orange Pi Zero version, but here I wanted to dive a little deeper and control the fan according to need. After reading about many options, I found this site which recommended a GPIO linked to a transistor as a switching mechanism (I've altered the circuit to suit as shown below).

Then, by controlling the GPIO status (HIGH or LOW), it should be possible to turn on and off the fan as required. Although many sites recommended a python-based solution for the control of the pin, I chose a bash script instead as per this github repository because I plan to have the script run at start-up (perhaps as a daemon) and this solution seemed the most likely to succeed.

I had to modify the script a bit (to include for instance hysteresis and address some scripting errors), but it ended up being as follows:

#!/bin/sh -e
#
#fantemp.sh
#
# see https://github.com/orangepi-xunlong/wiringOP for building wiringOP
#
# +------+-----+--------+OrangePiH3+-------+-----+------+
# | GPIO | wPi | Name	| Physical | Name  | wPi | GPIO |
# +------+-----+--------+----++----+-------+-----+------+
# |      |     |   3.3V |  1 || 2  | 5V	   | 	 |      |
# |   12 |   0 |  SDA.0 |  3 || 4  | 5V	   | 	 | 	|
# |   11 |   1 |  SCL.0 |  5 || 6  | GND   | 	 | 	|
# |    6 |   2 |    PA6 |  7 || 8  | TXD.3 | 3   | 13   |
# |      |     |    GND |  9 || 10 | RXD.3 | 4   | 14   |
# |    1 |   5 |  RXD.2 | 11 || 12 | PD14  | 6   | 110  |
# |    0 |   7 |  TXD.2 | 13 || 14 | GND   | 	 | 	|
# |    3 |   8 |  CTS.2 | 15 || 16 | PC04  | 9   | 68   |
# |      |     |   3.3V | 17 || 18 | PC07  | 10  | 71   |
# |   64 |  11 | MOSI.0 | 19 || 20 | GND   | 	 | 	|
# |   65 |  12 | MISO.0 | 21 || 22 | RTS.2 | 13  | 2    |
# |   66 |  14 | SCLK.0 | 23 || 24 | CE.0  | 15  | 67   |
# |      |     |    GND | 25 || 26 | PA21  | 16  | 21   |
# |   19 |  17 |  SDA.1 | 27 || 28 | SCL.1 | 18  | 18   |
# |    7 |  19 |   PA07 | 29 || 30 | GND   | 	 | 	|
# |    8 |  20 |   PA08 | 31 || 32 | RTS.1 | 21  | 200  |
# |    9 |  22 |   PA09 | 33 || 34 | GND   | 	 | 	|
# |   10 |  23 |   PA10 | 35 || 36 | CTS.1 | 24  | 201  |
# |   20 |  25 |   PA20 | 37 || 38 | TXD.1 | 26  | 198  |
# |      |     |    GND | 39 || 40 | RXD.1 | 27  | 199  |
# +------+-----+--------+----++----+-------+-----+------+
# | GPIO | wPi | Name	| Physical | Name  | wPi | GPIO |
# +------+-----+--------+OrangePiH3+-------+-----+------+
#

# see above table
PIN=6

# two temperatures for hysteresis
TEMP1=35000 # 35 degrees
TEMP2=45000 # 45 degrees

gpio mode $PIN out

while true; do
  if [ $(cat /sys/class/thermal/thermal_zone0/temp) -gt $TEMP1 ] \
  && [ $(cat /sys/class/thermal/thermal_zone0/temp) -gt $TEMP2 ]
  then
    gpio write $PIN 1
  elif [ $(cat /sys/class/thermal/thermal_zone0/temp) -lt $TEMP2 ] \
    && [ $(cat /sys/class/thermal/thermal_zone0/temp) -lt $TEMP1 ]
  then
    gpio write $PIN 0
  fi
  sleep 5
done

As part of testing, I dialed up a stress test as follows:

stress --cpu 8 --io 4 --vm 2 --vm-bytes 128M --timeout 45s

Monitoring of the CPU showed the temperature shooting up, and then the fan coming on and keeping everything cool - only to turn off when the "stress" is off. Very good result!

The final part was to solder up a board with the extra bits including the transistor to be placed inside the cooled housing, and make all the connections as per the breadboard test.

Now all I need to do is to retrofit this solution to the Orange Pi Zero attached to the printer and that will be mission accomplished!




Saturday, December 5, 2020

0000 0000 0100 1100

What is better than one QX5252 chip?

I was musing about two recent but separate projects, one which sings the praises of the QX5252 chip and the other which throws the ATTiny13 chip into a watchdog role. What if the watchdog could effectively have endless energy via the big yellow in the sky, and so can run "forever" and be available to do watchdog-type activities?

I decided to test the addition of a second QX5252, one chip operating to "pump" out stable energy to the microcontroller, and the other to grab ergs from the sun when available.

Normally with the QX5252 and similar ICs the chip shuts down the output of the circuit if there is enough light, and all of the solar panel energy is shunted to the rechargeable battery for the next night of LED fun. If, however, you want the battery to simultaneously charge and discharge during the day (is that even possible?) then one variation worth exploring is to maybe employ a second QX5252 just for that purpose.

So the plan is:

  1. use the "normal" stable joule thief PCB and program an ATTiny13 to do something (probably blink), but do not connect a Solar Panel to the board so that the QX5252 is fooled into being always in "discharge" mode
  2. add another QX5252 to the rail, but have no output components such that it is charging the battery if there is any light available to do so
In part (1), above, I struggled to write the assembly code which initially seemed an easy variation on previous projects. All I wanted to do was use two LEDs and flash them alternatively for 2 seconds on then 8 seconds off - e.g. red on (2 seconds), all off (8 seconds), yellow on (2 seconds), all off (8 seconds), rinse and repeat...

Of course, if the states of the LEDs are not changing then the microcontroller can sleep to save power. Also the little fella should be running anaemically at 128kHz thus drawing minimum current.

Eventually the code worked, but if you can see a better way to more efficiently and/or clearly write these routines, please leave a comment on the YouTube channel.

.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 PB3, PB4, input for the rest
    ldi r16, 0b00011000
    out DDRB, r16

    ; turn on PB4, enable internal pullups
    ; on the rest unused pins
    ldi r16, 0b11110111
    out PORTB, r16

    ; which LED is active?
    ldi r18, 0b00010000             ; start with PB4

; **********************************
;    P R O G R A M   L O O P
; **********************************

Loop:

    rcall waitabit            ; 2 second (on)
    rcall waitalot            ; 8 seconds (off)
    rjmp loop

waitabit:

    ; reset watchdog timer
    wdr

    ; 2 second timer
    ldi r16, 0b01000111       ; 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

    rcall sleepnow
    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:

    cpi r18, 0b00010000    ; is pb4 active?
    brbs 1, workonpb4

workonpb3:

    in r16, portb
    andi r16, 0b00001000   ; mask all but PB3
    cpi r16, 0b00001000    ; is pb3 on?

    brbs 1, turnoffpb3

    in r16, portb          ; flip PB3
    ldi r17, 0b00001000
    eor r16, r17
    out portb, r16

    rjmp finishflip

turnoffpb3:

    in r16, portb          ; flip PB3
    ldi r17, 0b00001000
    eor r16, r17
    out portb, r16
    ldi r18, 0b00010000    ; make PB4 active

    rjmp finishflip

workonpb4:

    in r16, portb
    andi r16, 0b00010000
    cpi r16, 0b00010000    ; is PB4 on?

    brbs 1, turnoffpb4

    in r16, portb          ; flip PB4
    ldi r17, 0b00010000
    eor r16, r17
    out portb, r16

    rjmp finishflip

turnoffpb4:

    in r16, portb          ; flip PB4
    ldi r17, 0b00010000
    eor r16, r17
    out portb, r16
    ldi r18, 0b00001000    ; make PB3 active

finishflip:

ret

; End of source code


I was very stoked to see this circuit and code not only work as intended, but also hold its own over weeks of testing, oscillating between about 1.33V after a day of sunshine to 1.25V after a couple of days and nights of overcast misery. Nice one two QX5252 Frankenstein bloke!

The only question remaining is: should I add a couple of diodes going to the rail from each of the outputs so they don't interfere with each other? Are the losses so great (although they would probably be Schottky diodes for minimum forward voltage) that it's not worth the addition? There are more tests to be made, but for now enjoy the <usual> blinking madness.