heartblink README¶
Project: heartblink
Created: Mon 1 Mar 2021 14:42:11 AEDT
Doc Author 2021 by t.j.porter <terry@tjporter.com.au> license: MIT, please see COPYING
Purpose: Blinks/fades the blue led on a F0 Discovery board like a heartbeat.
Three variations of Sine, Cosine and Triangle modulation are included.
Intended Audience: anyone
MCU: STM32F051 (any STM32F0xx should work)
Board: STM F0 Discovery
Required: arm-none-eabi, st-flash, linux, freebsd etc
Literature:
Code Author(s): Matthias Koch <matthias.koch@hot.uni-hannover.de>
Code License: all *.s are GPL3
Binary Size: 58-60 Bytes
Full project Tarball with ready to flash ‘sine’ binary:
Description¶
Heartblink fades a STM F0 Discovery Board LED in and out like a heartbeat. This is done in software only, no timer peripheral is used making it easily portable to other brands of Cortex-M0. Total binary size is only 58-60 Bytes.
How Does It Work¶
A differential equation is numerically approximated (providing the sine/cosine/triangle function), the result interpreted as a floating point number and converted to integer (to get an exp(x) approximation) and fed into a sigma/delta modulator, the output of which drives the LED.
The exp(x) approximation is to correct for human eyesight
Three Versions¶
There are three different versions for you to try:
Triangle
Sine
Cosine
Makefile Selection and Build¶
Selection¶
In the Makefile edit by un-commenting, and commenting as required. Select one of the three choices below. In this example “sine” is selected.
SRC = sine
# SRC = cosine
# SRC = triangle
Build¶
Build with “make clean and make”
flash with “make flash”
Note
Cosine and sine are the same except cosine starts with the led fully on.
Sine¶
58 Byte binary.
@ Heartblink for STM32F0 Discovery, by Matthias Koch <matthias.koch@hot.uni-hannover.de>
@ Sine Variation
@ Blue Led is connected to PC8.
.syntax unified
.cpu cortex-m0
.thumb
.text
blinky: @ Execute the vector table :-)
.word 0x40020C5C @ Stack pointer: Plus a suitable offset, this gives RCC_AHBENR (0x40021014).
@ But it is also this sequence of opcodes:
@ lsrs r4, r3, #17 @ Divide a bit to get blink frequency into visible range.
@ ands r2, r0 @ Doesn't harm here.
.word reset + 1 @ Reset Handler.
@ Opcode sequence:
@ movs r1, r1
@ movs r0, r0
@ -----------------------------------------------------------------------------
reset:
@ -----------------------------------------------------------------------------
movs r2, #0x48 @ Start to generate address of PIOC_MODER (0x48000800)
lsls r2, r2, #16 @ 0x48 --> 0x480000
str r2, [sp, 0x40021014-0x40020C5C] @ Set 0x80000 in RCC_AHBENR (0x40021014). Bit 0x400000 enables PIOF also
adds r2, #8 @ --> 0x480008
lsls r2, #8 @ --> 0x48000800
lsls r1, r2, #5 @ 0x48000800 << 5 = 0x10000 for PC8, blue LED
@ lsls r1, r2, #7 @ 0x48000800 << 7 = 0x40000 for PC9, green LED
str r1, [r2] @ Switch pin to output
movs r5, 1 @ Set up initial x, y for Minsky circle algorithm
lsls r6, r5, 19
@ -----------------------------------------------------------------------------
breathe_led: @ Generate smooth breathing LED effect
@ -----------------------------------------------------------------------------
@ Register usage:
@ r0 : Unused
@ r1 : Scratch
@ r2 : Initialised with IO address for GPIO
@ r3 : Scratch
@ r4 : Unused
@ r5 : Minsky circle alg x = sin(t)
@ r6 : Minsky circle alg y = cos(t)
@ r7 : Phase accumulator for sigma-delta modulator
@ Minsky circle algorithm x, y = sin(t), cos(t)
asrs r1, r5, 17 @ -dx = y >> 17
subs r6, r1 @ x += dx
asrs r1, r6, 17 @ dy = x >> 17
adds r5, r1 @ y += dy
asrs r1, r5, 13 @ -49 <= r4 <= 64 --> scaled sin(t)
adds r1, 183 @ 134 <= r4 <= 247 --> scaled sin(t) with offset
movs r3, 7 @ Simplified bitexp function.
ands r3, r1
adds r3, 8 @ Valid for inputs from 0x10 = 16 to 0xF7 = 247.
lsrs r1, r1, 3 @ Gives 0 if below 16, and too small values above 247.
subs r1, 2 @ Input in r1
lsls r3, r1 @ Output in r3
subs r7, r3 @ Sigma-Delta phase accumulator
sbcs r3, r3 @ Sigma-Delta output through carry, which is inverted for subs
str r3, [r2, 0x14] @ Set output accordingly
b.n breathe_led
Cosine¶
58 Byte binary.
@ Heartblink for STM32F0 Discovery, by Matthias Koch <matthias.koch@hot.uni-hannover.de>
@ Cosine variation, starts with LED on
@ Blue Led is connected to PC8.
.syntax unified
.cpu cortex-m0
.thumb
.text
blinky: @ Execute the vector table :-)
.word 0x40020C5C @ Stack pointer: Plus a suitable offset, this gives RCC_AHBENR (0x40021014).
@ But it is also this sequence of opcodes:
@ lsrs r4, r3, #17 @ Divide a bit to get blink frequency into visible range.
@ ands r2, r0 @ Doesn't harm here.
.word reset + 1 @ Reset Handler.
@ Opcode sequence:
@ movs r1, r1
@ movs r0, r0
@ -----------------------------------------------------------------------------
reset:
@ -----------------------------------------------------------------------------
movs r2, #0x48 @ Start to generate address of PIOC_MODER (0x48000800)
lsls r2, r2, #16 @ 0x48 --> 0x480000
str r2, [sp, 0x40021014-0x40020C5C] @ Set 0x80000 in RCC_AHBENR (0x40021014). Bit 0x400000 enables PIOF also
adds r2, #8 @ --> 0x480008
lsls r2, #8 @ --> 0x48000800
lsls r1, r2, #5 @ 0x48000800 << 5 = 0x10000 for PC8, blue LED
@ lsls r1, r2, #7 @ 0x48000800 << 7 = 0x40000 for PC9, green LED
str r1, [r2] @ Switch pin to output
movs r5, 1 @ Set up initial x, y for Minsky circle algorithm
lsls r6, r5, 19
@ -----------------------------------------------------------------------------
breathe_led: @ Generate smooth breathing LED effect
@ -----------------------------------------------------------------------------
@ Register usage:
@ r0 : Unused
@ r1 : Scratch
@ r2 : Initialised with IO address for GPIO
@ r3 : Scratch
@ r4 : Unused
@ r5 : Minsky circle alg x = sin(t)
@ r6 : Minsky circle alg y = cos(t)
@ r7 : Phase accumulator for sigma-delta modulator
@ Minsky circle algorithm x, y = sin(t), cos(t)
asrs r1, r5, 17 @ -dx = y >> 17
subs r6, r1 @ x += dx
asrs r1, r6, 17 @ dy = x >> 17
adds r5, r1 @ y += dy
asrs r1, r6, 13 @ -49 <= r4 <= 64 --> scaled cos(t)
adds r1, 183 @ 134 <= r4 <= 247 --> scaled cos(t) with offset
movs r3, 7 @ Simplified bitexp function.
ands r3, r1
adds r3, 8 @ Valid for inputs from 0x10 = 16 to 0xF7 = 247.
lsrs r1, r1, 3 @ Gives 0 if below 16, and too small values above 247.
subs r1, 2 @ Input in r1
lsls r3, r1 @ Output in r3
subs r7, r3 @ Sigma-Delta phase accumulator
sbcs r3, r3 @ Sigma-Delta output through carry, which is inverted for subs
str r3, [r2, 0x14] @ Set output accordingly
b.n breathe_led
Triangle¶
60 Byte binary.
@ Heartblink for STM32F0 Discovery, by Matthias Koch <matthias.koch@hot.uni-hannover.de>
@ Triangle variation
@ Blue Led is connected to PC8.
.syntax unified
.cpu cortex-m0
.thumb
.text
blinky: @ Execute the vector table :-)
.word 0x40020C5C @ Stack pointer: Plus a suitable offset, this gives RCC_AHBENR (0x40021014).
@ But it is also this sequence of opcodes:
@ lsrs r4, r3, #17 @ Divide a bit to get blink frequency into visible range.
@ ands r2, r0 @ Doesn't harm here.
.word reset + 1 @ Reset Handler.
@ Opcode sequence:
@ movs r1, r1
@ movs r0, r0
@ -----------------------------------------------------------------------------
reset:
@ -----------------------------------------------------------------------------
movs r2, #0x48 @ Start to generate address of PIOC_MODER (0x48000800)
lsls r2, r2, #16 @ 0x48 --> 0x480000
str r2, [sp, 0x40021014-0x40020C5C] @ Set 0x80000 in RCC_AHBENR (0x40021014). Bit 0x400000 enables PIOF also
adds r2, #8 @ --> 0x480008
lsls r2, #8 @ --> 0x48000800
lsls r1, r2, #5 @ 0x48000800 << 5 = 0x10000 for PC8, blue LED
@ lsls r1, r2, #7 @ 0x48000800 << 7 = 0x40000 for PC9, green LED
str r1, [r2] @ Switch pin to output
@ -----------------------------------------------------------------------------
breathe_led: @ Generate smooth breathing LED effect
@ -----------------------------------------------------------------------------
@ Register usage:
@ r0 : Unused
@ r1 : Scratch
@ r2 : Initialised with IO address for GPIO
@ r3 : Scratch
@ r4 : Unused
@ r5 : Delay counter
@ r6 : Slow triangle counter, clipped
@ r7 : Phase accumulator for sigma-delta modulator
adds r5, 1 @ Delay counter
lsls r6, r5, 12 @ Select blinking speed here.
asrs r6, r6, 15 @ Triangle generator between 0 and 0x10000.
bpl 1f
negs r6, r6 @ abs((upcounter << 11) >>> 15)
1:
movs r1, 247-133 @ Scale range:
muls r6, r1 @ (high-low) * x / 65536 + low
lsrs r6, 16 @
adds r6, 133 @ Baseline minimum brightness
movs r3, 7 @ Simplified bitexp function.
ands r3, r6
adds r3, 8 @ Valid for inputs from 0x10 = 16 to 0xF7 = 247.
lsrs r1, r6, 3 @ Gives 0 if below 16, and too small values above 247.
subs r1, 2 @ Input in r6 is kept
lsls r3, r1 @ Output in r3
subs r7, r3 @ Sigma-Delta phase accumulator
sbcs r3, r3 @ Sigma-Delta output through carry, which is inverted for subs
str r3, [r2, 0x14] @ Set output accordingly
b.n breathe_led
Comments by Crest¶
Matthias uses the minsky circle to approximate a sinus function which is faster than the proper cordic algorithm and good enough for a dimming a led.
Given a pair (x, y) on the almost circle the algorithm updates this pair by adding or substracting the upper bits of the other axis shifted right a few places to each axis and he uses one of the axis as the brigthness of the led to get the nice slow fade in and out with almost linear perceived brightness