Skip to content

Instantly share code, notes, and snippets.

@stecman
Last active September 29, 2023 09:57
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save stecman/eed14f9a01d50b9e376429959f35493a to your computer and use it in GitHub Desktop.
Save stecman/eed14f9a01d50b9e376429959f35493a to your computer and use it in GitHub Desktop.
STM8 WS2812 hacked together demo

Addressable LED demo for STM8S003

This is a quick hacked together demo of using WS2812 addressable LEDs on an STM8 micro.

Building

  • Install sdcc and scons from your package manager.
  • Clone this gist:
git clone https://gist.github.com/eed14f9a01d50b9e376429959f35493a.git stm8s-addressable-leds
cd $_
  • Clone an SDCC compatible STM8 driver into the new folder:
git clone https://github.com/stecman/stm8s-sdcc driver
  • Build with: scons

Flashing with an STLink-V2

  • Build and install stm8flash on your path
  • Run scons flash
#include "stm8s.h"
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#define NUM_LEDS 30
#define BRIGHTNESS 150
void _delay_us(uint16_t microseconds) {
TIM4->PSCR = TIM4_PRESCALER_1; // Set prescaler
// Set count to approximately 1uS (clock/microseconds in 1 second)
// The -1 adjusts for other runtime costs of this function
TIM4->ARR = ((16000000L)/1000000) - 1;
TIM4->CR1 = TIM4_CR1_CEN; // Enable counter
for (; microseconds > 1; --microseconds) {
while ((TIM4->SR1 & TIM4_SR1_UIF) == 0);
// Clear overflow flag
TIM4->SR1 &= ~TIM4_SR1_UIF;
}
}
void ws_write_byte(uint8_t byte)
{
for (uint8_t mask = 0x80; mask != 0; mask >>= 1) {
if ((byte & mask) != 0) {
// Set pin high
__asm__("bset 20495, #4");
// Delay for 850ns minus overheads
nop(); nop(); nop(); nop();
nop(); nop(); nop(); nop();
// Set pin low
__asm__("bres 20495, #4");
// Delay 400ns minus overhead
nop(); nop(); nop(); nop();
} else {
// Set pin high
__asm__("bset 20495, #4");
// Delay for 400ns minus overheads
nop(); nop(); nop(); nop();
nop(); nop();
// Set pin low
__asm__("bres 20495, #4");
// Delay for 850ns minus overheads
nop(); nop(); nop(); nop();
}
}
}
void ws_write_grb(uint8_t* colour)
{
for (uint8_t i = 0; i < 3; ++i) {
ws_write_byte(colour[i]);
}
}
inline volatile void ws_reset()
{
GPIOD->ODR &= ~(1<<4);
// Pull the line low for >50uS to reset
_delay_us(55);
}
void applyCieBrightness(uint8_t* colour)
{
// CIE1931 luminance correction table
// Generated from code here: http://jared.geek.nz/2013/feb/linear-led-pwm
static const unsigned char CIE_CORRECTION[256] = {
0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
2, 2, 2, 3, 3, 3, 3, 3, 3, 3,
3, 4, 4, 4, 4, 4, 4, 5, 5, 5,
5, 5, 6, 6, 6, 6, 6, 7, 7, 7,
7, 8, 8, 8, 8, 9, 9, 9, 10, 10,
10, 10, 11, 11, 11, 12, 12, 12, 13, 13,
13, 14, 14, 15, 15, 15, 16, 16, 17, 17,
17, 18, 18, 19, 19, 20, 20, 21, 21, 22,
22, 23, 23, 24, 24, 25, 25, 26, 26, 27,
28, 28, 29, 29, 30, 31, 31, 32, 32, 33,
34, 34, 35, 36, 37, 37, 38, 39, 39, 40,
41, 42, 43, 43, 44, 45, 46, 47, 47, 48,
49, 50, 51, 52, 53, 54, 54, 55, 56, 57,
58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
68, 70, 71, 72, 73, 74, 75, 76, 77, 79,
80, 81, 82, 83, 85, 86, 87, 88, 90, 91,
92, 94, 95, 96, 98, 99, 100, 102, 103, 105,
106, 108, 109, 110, 112, 113, 115, 116, 118, 120,
121, 123, 124, 126, 128, 129, 131, 132, 134, 136,
138, 139, 141, 143, 145, 146, 148, 150, 152, 154,
155, 157, 159, 161, 163, 165, 167, 169, 171, 173,
175, 177, 179, 181, 183, 185, 187, 189, 191, 193,
196, 198, 200, 202, 204, 207, 209, 211, 214, 216,
218, 220, 223, 225, 228, 230, 232, 235, 237, 240,
242, 245, 247, 250, 252, 255,
};
colour[0] = CIE_CORRECTION[colour[0]];
colour[1] = CIE_CORRECTION[colour[1]];
colour[2] = CIE_CORRECTION[colour[2]];
}
uint8_t fade_to(uint8_t *current, uint8_t *target)
{
uint8_t changed = 0;
for (uint8_t i = 0; i < 3; i++) {
if (current[i] != target[i]) {
changed++;
if (current[i] < target[i]) {
current[i]++;
} else {
current[i]--;
}
}
}
return changed;
}
static uint8_t lights[NUM_LEDS][3];
static uint8_t lights_index = 0;
static uint8_t lights_direction = 1;
static uint8_t light_ticks = 0;
// Actual value of the LEDs
static uint8_t colour[] = {50, 0, 0};
// Value the LEDs need to fade to
static uint8_t target[] = {50, 0, 0};
// Bit mask of which colours should be fully lit (lower 3 bits)
// This uses binary addition to get all permutations of RGB cheaply
static uint8_t colourMask = 0;
void timer2_overflow_interrupt(void) __interrupt (ITC_IRQ_TIM2_OVF)
{
// Step towards the target colour
// If we're at the target, set the next target
if (!fade_to(colour, target)) {
// Step to next permutation of RGB
colourMask = (colourMask + 1) % 8;
// Never fade to black as that's boring visually
if (colourMask == 0) {
colourMask = 1;
}
// Set target colour based on bitmask
memset(target, 0, sizeof(target));
if (colourMask & (1<<0)) {
target[0] = BRIGHTNESS;
}
if (colourMask & (1<<1)) {
target[1] = BRIGHTNESS;
}
if (colourMask & (1<<2)) {
target[2] = BRIGHTNESS;
}
applyCieBrightness(colour);
}
// Write to LED strip
ws_reset();
for (uint8_t i = 0; i < NUM_LEDS; ++i) {
ws_write_grb(colour);
}
// Clear the timer overflow so the interrupt doesn't fire again
TIM2->SR1 &= ~TIM2_SR1_UIF;
}
int main(void)
{
// Configure the clock for maximum speed on the 16MHz HSI oscillator
// At startup the clock output is divided by 8
CLK->CKDIVR = 0x0;
// Pin D4 in fast push-pull mode
// This starts high as the light looks for low signals
GPIOD->DDR |= (1<<4);
GPIOD->CR1 |= (1<<4);
GPIOD->CR2 |= (1<<4);
GPIOD->ODR &= ~(1<<4);
// B5 (LED) in fast push-pull mode
GPIOB->DDR |= (1<<5);
GPIOB->CR1 |= (1<<5);
GPIOB->ODR |= (1<<5);
GPIOB->ODR &= ~(1<<5);
// Configure timer 2 (16-bit general purpose)
TIM2->PSCR = TIM2_PRESCALER_256; // Set prescaler
TIM2->ARRH = 0x30; // Set auto-reload value for 200ms
TIM2->ARRL = 0xD4;
// TIM2->ARRH = 0x02; // Set auto-reload value for 40ms
// TIM2->ARRL = 0xC4;
TIM2->IER = TIM2_IER_UIE; // Enable interrupt
TIM2->CR1 = TIM2_CR1_CEN; // Enable counter
// Initialise lights array
memset(lights, 0, sizeof(lights));
enableInterrupts();
for (;;) {
}
}
import os
# Device definition for the stm8s.h header
# This enables available features of the device library.
STM8_DEVICE_DEFINE = "STM8S003"
# Programmer argument for stm8flash (stlink or stlinkv2)
STM8_PROGRAMMER = "stlinkv2"
# Target device argument for stm8flash
STM8_DEVICE_PROG = "stm8s003?3"
# RAM and flash size available on the target device
FLASH_SIZE_BYTES = 8096
INTERNAL_RAM_SIZE_BYTES = 1024
EXTERNAL_RAM_SIZE_BYTES = 0
# Compiled hex file target
HEX_FILE = 'main.ihx'
env = Environment(
CC = 'sdcc',
CPPDEFINES = {
STM8_DEVICE_DEFINE: None,
},
CFLAGS = [
# Building for STM8
'-mstm8',
# Optimisations and standard
'--std-sdcc11',
'--opt-code-size',
],
# SDCC uses .rel instead of .o for linkable objects
OBJSUFFIX = ".rel",
LINKFLAGS = [
'--verbose',
'--out-fmt-ihx',
# Building for STM8
'-mstm8',
# Available resources
'--iram-size', INTERNAL_RAM_SIZE_BYTES,
'--xram-size', EXTERNAL_RAM_SIZE_BYTES,
'--code-size', FLASH_SIZE_BYTES,
],
LIBS = [
'stm8',
],
# Header search paths
# Note that these are relative to the build directory (if SConstruct src='.'')
CPPPATH = [
'',
'.',
'../driver/inc',
'lib',
],
# Custom variables for use in stm8flash commands
STM8_PROGRAMMER = STM8_PROGRAMMER,
STM8_DEVICE_PROG = STM8_DEVICE_PROG,
)
env.Alias(
'flash',
env.Command(
None,
HEX_FILE,
'stm8flash -c $STM8_PROGRAMMER -p $STM8_DEVICE_PROG -w $SOURCES'
)
)
# Only build the hex file by default (don't try to flash, etc)
env.Default(
env.Program(
HEX_FILE,
'main.c'
)
)
# Build the current directory into a subdir "build"
# This has to be a separate file due to the design of scons
SConscript('SConscript', src='src', variant_dir='build', duplicate=0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment