A src/iv.c => src/iv.c +293 -0
@@ 0,0 1,293 @@
+#include <string.h>
+#include <ctype.h>
+
+#include <libopencm3/stm32/rcc.h>
+#include <libopencm3/stm32/gpio.h>
+
+#include "iv.h"
+#include "util.h"
+
+#define IV8_PORT GPIOA
+#define IV8_PORT_RCC RCC_GPIOA
+#define IV8_CLOCK GPIO1
+#define IV8_STROBE GPIO2
+#define IV8_DATA GPIO0
+#define IV8_BLANK GPIO4
+#define IV8_PINS (IV8_STROBE | IV8_CLOCK | IV8_DATA | IV8_BLANK)
+
+#define GRID_HH (1 << 0)
+#define GRID_MM (1 << 1)
+
+#define SEG_ONES_A (1 << 8)
+#define SEG_ONES_B (1 << 7)
+#define SEG_ONES_C (1 << 5)
+#define SEG_ONES_D (1 << 4)
+#define SEG_ONES_E (1 << 11)
+#define SEG_ONES_F (1 << 10)
+#define SEG_ONES_G (1 << 9)
+#define SEG_ONES_DP (1 << 6)
+
+#define SEG_TENS_A (1 << 16)
+#define SEG_TENS_B (1 << 15)
+#define SEG_TENS_C (1 << 13)
+#define SEG_TENS_D (1 << 12)
+#define SEG_TENS_E (1 << 19)
+#define SEG_TENS_F (1 << 18)
+#define SEG_TENS_G (1 << 17)
+#define SEG_TENS_DP (1 << 14)
+
+#define SEGS_0(HL) (SEG_##HL##_A | SEG_##HL##_B | SEG_##HL##_C | SEG_##HL##_D | SEG_##HL##_E | SEG_##HL##_F)
+#define SEGS_1(HL) (SEG_##HL##_B | SEG_##HL##_C)
+#define SEGS_2(HL) (SEG_##HL##_A | SEG_##HL##_B | SEG_##HL##_G | SEG_##HL##_E | SEG_##HL##_D)
+#define SEGS_3(HL) (SEG_##HL##_A | SEG_##HL##_B | SEG_##HL##_G | SEG_##HL##_C | SEG_##HL##_D)
+#define SEGS_4(HL) (SEG_##HL##_F | SEG_##HL##_G | SEG_##HL##_B | SEG_##HL##_C)
+#define SEGS_5(HL) (SEG_##HL##_A | SEG_##HL##_F | SEG_##HL##_G | SEG_##HL##_C | SEG_##HL##_D)
+#define SEGS_6(HL) (SEG_##HL##_A | SEG_##HL##_F | SEG_##HL##_G | SEG_##HL##_C | SEG_##HL##_D | SEG_##HL##_E)
+#define SEGS_7(HL) (SEG_##HL##_A | SEG_##HL##_B | SEG_##HL##_C)
+#define SEGS_8(HL) (SEG_##HL##_A | SEG_##HL##_B | SEG_##HL##_C | SEG_##HL##_D | SEG_##HL##_E | SEG_##HL##_F | SEG_##HL##_G)
+#define SEGS_9(HL) (SEG_##HL##_F | SEG_##HL##_A | SEG_##HL##_B | SEG_##HL##_G | SEG_##HL##_C)
+#define SEGS_DP(HL) (SEG_##HL##_DP)
+#define SEGS_A(HL) (SEG_##HL##_A | SEG_##HL##_B | SEG_##HL##_C | SEG_##HL##_E | SEG_##HL##_F | SEG_##HL##_G)
+#define SEGS_B(HL) (SEG_##HL##_C | SEG_##HL##_D | SEG_##HL##_E | SEG_##HL##_F | SEG_##HL##_G)
+#define SEGS_C(HL) (SEG_##HL##_A | SEG_##HL##_D | SEG_##HL##_E | SEG_##HL##_F)
+#define SEGS_D(HL) (SEG_##HL##_B | SEG_##HL##_C | SEG_##HL##_D | SEG_##HL##_E | SEG_##HL##_G)
+#define SEGS_E(HL) (SEG_##HL##_A | SEG_##HL##_D | SEG_##HL##_E | SEG_##HL##_F | SEG_##HL##_G)
+#define SEGS_F(HL) (SEG_##HL##_A | SEG_##HL##_E | SEG_##HL##_F | SEG_##HL##_G)
+#define SEGS_G(HL) (SEG_##HL##_A | SEG_##HL##_C | SEG_##HL##_D | SEG_##HL##_E | SEG_##HL##_F)
+#define SEGS_H(HL) (SEG_##HL##_B | SEG_##HL##_C | SEG_##HL##_E | SEG_##HL##_F | SEG_##HL##_G)
+#define SEGS_I(HL) (SEG_##HL##_E | SEG_##HL##_F)
+#define SEGS_J(HL) (SEG_##HL##_B | SEG_##HL##_C | SEG_##HL##_D | SEG_##HL##_E)
+#define SEGS_K(HL) (SEG_##HL##_A | SEG_##HL##_C | SEG_##HL##_E | SEG_##HL##_F | SEG_##HL##_G)
+#define SEGS_L(HL) (SEG_##HL##_E | SEG_##HL##_F | SEG_##HL##_D)
+#define SEGS_M(HL) (SEG_##HL##_A | SEG_##HL##_C | SEG_##HL##_E)
+#define SEGS_N(HL) (SEG_##HL##_C | SEG_##HL##_E | SEG_##HL##_G)
+#define SEGS_O(HL) (SEG_##HL##_A | SEG_##HL##_B | SEG_##HL##_C | SEG_##HL##_D | SEG_##HL##_E | SEG_##HL##_F)
+#define SEGS_P(HL) (SEG_##HL##_A | SEG_##HL##_B | SEG_##HL##_E | SEG_##HL##_F | SEG_##HL##_G)
+#define SEGS_Q(HL) (SEG_##HL##_A | SEG_##HL##_B | SEG_##HL##_C | SEG_##HL##_F | SEG_##HL##_G)
+#define SEGS_R(HL) (SEG_##HL##_E | SEG_##HL##_G)
+#define SEGS_S(HL) (SEG_##HL##_A | SEG_##HL##_C | SEG_##HL##_D | SEG_##HL##_F | SEG_##HL##_G)
+#define SEGS_T(HL) (SEG_##HL##_D | SEG_##HL##_E | SEG_##HL##_F | SEG_##HL##_G)
+#define SEGS_U(HL) (SEG_##HL##_B | SEG_##HL##_C | SEG_##HL##_D | SEG_##HL##_E | SEG_##HL##_F)
+#define SEGS_V(HL) (SEG_##HL##_C | SEG_##HL##_D | SEG_##HL##_E)
+#define SEGS_W(HL) (SEG_##HL##_B | SEG_##HL##_D | SEG_##HL##_F)
+#define SEGS_X(HL) (SEG_##HL##_B | SEG_##HL##_C | SEG_##HL##_E | SEG_##HL##_F | SEG_##HL##_G)
+#define SEGS_Y(HL) (SEG_##HL##_B | SEG_##HL##_C | SEG_##HL##_D | SEG_##HL##_F | SEG_##HL##_G)
+#define SEGS_Z(HL) (SEG_##HL##_A | SEG_##HL##_C | SEG_##HL##_D | SEG_##HL##_G)
+#define SEGS_EXC(HL) (SEG_##HL##_B | SEG_##HL##_DP)
+#define SEGS_HYP(HL) (SEG_##HL##_G)
+
+// Setup the GPIO for the IV-18 shift register.
+void iv_gpio_setup() {
+ rcc_periph_clock_enable(IV8_PORT_RCC);
+ gpio_mode_setup(IV8_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, IV8_PINS);
+ gpio_set_output_options(IV8_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, IV8_PINS);
+
+ // Keep blank low to actually start displaying stuff. Could PWM this to set
+ // brigness, maybe later.
+ gpio_clear(IV8_PORT, IV8_BLANK);
+}
+
+// Converts boolean values into segment data (dots).
+static uint32_t dots(bool tens, bool ones) {
+ uint32_t n = 0;
+ if (tens) n += SEGS_DP(TENS);
+ if (ones) n += SEGS_DP(ONES);
+ return n;
+}
+
+// Converts characters to segment data.
+static uint32_t segments(uint8_t tens, uint8_t ones) {
+ uint32_t n = 0;
+ switch (toupper(tens)) {
+ case '0': n += SEGS_0(TENS); break;
+ case '1': n += SEGS_1(TENS); break;
+ case '2': n += SEGS_2(TENS); break;
+ case '3': n += SEGS_3(TENS); break;
+ case '4': n += SEGS_4(TENS); break;
+ case '5': n += SEGS_5(TENS); break;
+ case '6': n += SEGS_6(TENS); break;
+ case '7': n += SEGS_7(TENS); break;
+ case '8': n += SEGS_8(TENS); break;
+ case '9': n += SEGS_9(TENS); break;
+ case 'A': n += SEGS_A(TENS); break;
+ case 'B': n += SEGS_B(TENS); break;
+ case 'C': n += SEGS_C(TENS); break;
+ case 'D': n += SEGS_D(TENS); break;
+ case 'E': n += SEGS_E(TENS); break;
+ case 'F': n += SEGS_F(TENS); break;
+ case 'G': n += SEGS_G(TENS); break;
+ case 'H': n += SEGS_H(TENS); break;
+ case 'I': n += SEGS_I(TENS); break;
+ case 'J': n += SEGS_J(TENS); break;
+ case 'K': n += SEGS_K(TENS); break;
+ case 'L': n += SEGS_L(TENS); break;
+ case 'M': n += SEGS_M(TENS); break;
+ case 'N': n += SEGS_N(TENS); break;
+ case 'O': n += SEGS_O(TENS); break;
+ case 'P': n += SEGS_P(TENS); break;
+ case 'Q': n += SEGS_Q(TENS); break;
+ case 'R': n += SEGS_R(TENS); break;
+ case 'S': n += SEGS_S(TENS); break;
+ case 'T': n += SEGS_T(TENS); break;
+ case 'U': n += SEGS_U(TENS); break;
+ case 'V': n += SEGS_V(TENS); break;
+ case 'W': n += SEGS_W(TENS); break;
+ case 'X': n += SEGS_X(TENS); break;
+ case 'Y': n += SEGS_Y(TENS); break;
+ case 'Z': n += SEGS_Z(TENS); break;
+ case '.': n += SEGS_DP(TENS); break;
+ case '-': n += SEGS_HYP(TENS); break;
+ case '!': n += SEGS_EXC(TENS); break;
+ }
+
+ switch (toupper(ones)) {
+ case '0': n += SEGS_0(ONES); break;
+ case '1': n += SEGS_1(ONES); break;
+ case '2': n += SEGS_2(ONES); break;
+ case '3': n += SEGS_3(ONES); break;
+ case '4': n += SEGS_4(ONES); break;
+ case '5': n += SEGS_5(ONES); break;
+ case '6': n += SEGS_6(ONES); break;
+ case '7': n += SEGS_7(ONES); break;
+ case '8': n += SEGS_8(ONES); break;
+ case '9': n += SEGS_9(ONES); break;
+ case 'A': n += SEGS_A(ONES); break;
+ case 'B': n += SEGS_B(ONES); break;
+ case 'C': n += SEGS_C(ONES); break;
+ case 'D': n += SEGS_D(ONES); break;
+ case 'E': n += SEGS_E(ONES); break;
+ case 'F': n += SEGS_F(ONES); break;
+ case 'G': n += SEGS_G(ONES); break;
+ case 'H': n += SEGS_H(ONES); break;
+ case 'I': n += SEGS_I(ONES); break;
+ case 'J': n += SEGS_J(ONES); break;
+ case 'K': n += SEGS_K(ONES); break;
+ case 'L': n += SEGS_L(ONES); break;
+ case 'M': n += SEGS_M(ONES); break;
+ case 'N': n += SEGS_N(ONES); break;
+ case 'O': n += SEGS_O(ONES); break;
+ case 'P': n += SEGS_P(ONES); break;
+ case 'Q': n += SEGS_Q(ONES); break;
+ case 'R': n += SEGS_R(ONES); break;
+ case 'S': n += SEGS_S(ONES); break;
+ case 'T': n += SEGS_T(ONES); break;
+ case 'U': n += SEGS_U(ONES); break;
+ case 'V': n += SEGS_V(ONES); break;
+ case 'W': n += SEGS_W(ONES); break;
+ case 'X': n += SEGS_X(ONES); break;
+ case 'Y': n += SEGS_Y(ONES); break;
+ case 'Z': n += SEGS_Z(ONES); break;
+ case '.': n += SEGS_DP(ONES); break;
+ case '-': n += SEGS_HYP(ONES); break;
+ case '!': n += SEGS_EXC(ONES); break;
+ }
+ return n;
+}
+
+enum {
+ SIDE_HOURS = GRID_HH,
+ SIDE_MINUTES = GRID_MM,
+};
+
+// State of the driver.
+struct {
+ uint8_t side;
+ uint8_t digits[4];
+ bool dots[4];
+
+ uint32_t shift;
+ uint8_t tick;
+} state = {
+ .side = SIDE_HOURS,
+ .digits = {'0', '0', '0', '0'},
+ .dots = {false, false, false, false},
+ .shift = 0,
+ .tick = 0,
+};
+
+// Swaps the side that's being shown currently.
+static uint32_t swap_sides(void) {
+ if (state.side == SIDE_HOURS) {
+ state.side = SIDE_MINUTES;
+ } else {
+ state.side = SIDE_HOURS;
+ }
+ return state.side;
+}
+
+// Ticks the state, supposed to be called very frequently (i.e. from a high
+// frequency timer). 10kHz is probably good. This function basically shifts
+// stuff to the HV2812, one bit per tick. When the whole 20 bits have been
+// shifted, if triggers a latch and flips the side that's being shifted.
+void iv_tick() {
+ if (++state.tick == 20) {
+ gpio_set_fast(IV8_PORT, IV8_STROBE);
+ __asm__("nop");
+ __asm__("nop");
+ __asm__("nop");
+ __asm__("nop");
+ gpio_clear_fast(IV8_PORT, IV8_STROBE);
+
+ state.shift = swap_sides();
+
+ size_t ih = 0;
+ size_t il = 0;
+
+ if (state.shift == SIDE_HOURS) {
+ ih = 0;
+ il = 1;
+ } else {
+ ih = 2;
+ il = 3;
+ }
+
+ state.shift += segments(state.digits[ih], state.digits[il])
+ + dots(state.dots[ih], state.dots[il]);
+
+ state.tick = 0;
+ }
+
+ if (state.shift & 1) {
+ gpio_set_fast(IV8_PORT, IV8_DATA);
+ } else {
+ gpio_clear_fast(IV8_PORT, IV8_DATA);
+ }
+ gpio_set_fast(IV8_PORT, IV8_CLOCK);
+ __asm__("nop");
+ __asm__("nop");
+ __asm__("nop");
+ __asm__("nop");
+ gpio_clear_fast(IV8_PORT, IV8_CLOCK);
+ state.shift >>= 1;
+}
+
+// Sets the characters directly. Memory corruption will occur with arrays
+// shorter than 4.
+void iv_set_characters(const char chars[]) {
+ memcpy(state.digits, chars, 4);
+}
+
+// Sets the decimal points directly. Memory corruption will occur with arrays
+// shorter than 4.
+void iv_set_dots(const bool dots[]) {
+ memcpy(state.dots, dots, 4);
+}
+
+// Sets a string into the display. Allows decimal points to be in the string.
+void iv_set_str(const char* str) {
+ size_t position = 0;
+
+ memset(state.digits, 0, 4);
+ memset(state.dots, 0, 4);
+
+ size_t len = strlen(str);
+ for (size_t i = 0; i < len; i++) {
+ char c = str[i];
+ state.digits[position] = c;
+ if (i + 1 < len && str[i+1] == '.') {
+ state.dots[position] = true;
+ i++;
+ }
+ position++;
+ }
+}
A src/iv.h => src/iv.h +10 -0
@@ 0,0 1,10 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+void iv_gpio_setup(void);
+void iv_tick(void);
+void iv_set_str(const char*);
+void iv_set_characters(const char[]);
+void iv_set_dots(const bool[]);
A src/main.c => src/main.c +104 -0
@@ 0,0 1,104 @@
+#include "main.h"
+#include "util.h"
+#include "iv.h"
+#include "ws.h"
+#include "rotenc.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <libopencm3/cm3/nvic.h>
+#include <libopencm3/stm32/rcc.h>
+#include <libopencm3/stm32/gpio.h>
+#include <libopencm3/stm32/timer.h>
+
+static void gpio_setup(void)
+{
+ iv_gpio_setup();
+ ws_gpio_setup();
+ re_gpio_setup();
+}
+
+static void timers_setup(void) {
+ rcc_periph_clock_enable(RCC_TIM2);
+ rcc_periph_reset_pulse(RST_TIM2);
+ timer_set_mode(TIM2, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
+ timer_set_prescaler(TIM2, 64); // 1MHz
+ timer_set_period(TIM2, 100); // 10kHz
+ timer_enable_counter(TIM2);
+ nvic_enable_irq(NVIC_TIM2_IRQ);
+ timer_enable_irq(TIM2, TIM_DIER_UIE);
+
+ rcc_periph_clock_enable(RCC_TIM14);
+ rcc_periph_reset_pulse(RST_TIM14);
+ timer_set_mode(TIM14, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
+ timer_set_prescaler(TIM14, 6400); // 10kHz
+ timer_set_period(TIM14, 100); // 100Hz
+ timer_enable_counter(TIM14);
+ nvic_enable_irq(NVIC_TIM14_IRQ);
+ timer_enable_irq(TIM14, TIM_DIER_UIE);
+}
+
+void tim2_isr(void) {
+ if (timer_get_flag(TIM2, TIM_SR_UIF)) {
+ timer_clear_flag(TIM2, TIM_SR_UIF);
+
+ iv_tick();
+ re_tick();
+ }
+}
+
+static volatile uint8_t kek = 1;
+
+// This one has to be fast as possible, so skip checking the flag and write to
+// registers directly.
+void tim14_isr(void) {
+ if (timer_get_flag(TIM14, TIM_SR_UIF)) {
+ timer_clear_flag(TIM14, TIM_SR_UIF);
+
+ ws_shift((const void*)(WS_RGB[]){
+ ws_rgb(kek, 0, 0),
+ ws_rgb(0, kek, 0)}, 6);
+ kek++;
+ }
+}
+
+static int16_t c = 0;
+
+static void c_to_iv(void) {
+ char str[8];
+ sprintf(str, "%d", c);
+ iv_set_str(str);
+}
+
+static void zero(void) {
+ c = 0;
+ c_to_iv();
+}
+
+static void inc(void) {
+ c++;
+ c_to_iv();
+}
+
+static void dec(void) {
+ c--;
+ c_to_iv();
+}
+
+int main(void)
+{
+ rcc_clock_setup(&rcc_clock_config[RCC_CLOCK_CONFIG_HSI_PLL_64MHZ]);
+
+ gpio_setup();
+ timers_setup();
+
+ re_set_fns(inc, dec, zero);
+
+ iv_set_str("hey!");
+
+ for (;;) { }
+
+ return 0;
+}
A src/main.h => src/main.h +0 -0
A src/rotenc.c => src/rotenc.c +66 -0
@@ 0,0 1,66 @@
+#include "rotenc.h"
+
+#include <stddef.h>
+#include <libopencm3/stm32/rcc.h>
+#include <libopencm3/stm32/gpio.h>
+
+#define GPIO_PORT GPIOB
+#define GPIO_RCC RCC_GPIOB
+#define BUTTON GPIO0
+#define ROT_A GPIO1
+#define ROT_B GPIO2
+
+void re_gpio_setup(void) {
+ rcc_periph_clock_enable(GPIO_RCC);
+ gpio_mode_setup(GPIO_PORT, GPIO_MODE_INPUT,
+ GPIO_PUPD_NONE, BUTTON | ROT_A | ROT_B);
+}
+
+static struct {
+ void (*ccw_fn)(void);
+ void (*cw_fn)(void);
+ void (*btn_fn)(void);
+ uint32_t previus_a;
+ uint32_t previus_button;
+} state = {
+ .ccw_fn = NULL,
+ .cw_fn = NULL,
+ .btn_fn = NULL,
+ .previus_a = 0,
+ .previus_button = 0,
+};
+
+void re_set_fns(void (*ccw_fn)(void), void (*cw_fn)(void), void (*btn_fn)(void)) {
+ state.ccw_fn = ccw_fn;
+ state.cw_fn = cw_fn;
+ state.btn_fn = btn_fn;
+}
+
+// Reads the rotary encoder and the button, calls callbacks if there are
+// updates. This should be called very frequently to missing anything.
+// Debouncing is not necessary as the hardware already has LPF's for all the
+// buttons. It basically checks whether A changed from the previous state. If
+// it did, it means the rotary encoder has rotated. Comparing the state of A
+// to the state of B allows to check which direction it went since they're out
+// of phase.
+void re_tick(void) {
+ uint16_t a = !gpio_get(GPIO_PORT, ROT_A);
+
+ if (a && state.previus_a != a) {
+ if ((!gpio_get(GPIO_PORT, ROT_B)) != a) {
+ if (state.ccw_fn != NULL)
+ state.ccw_fn();
+ } else {
+ if (state.cw_fn != NULL)
+ state.cw_fn();
+ }
+ }
+
+ state.previus_a = a;
+
+ uint16_t btn = !gpio_get(GPIO_PORT, BUTTON);
+ if (state.btn_fn != NULL && btn != state.previus_button && btn) {
+ state.btn_fn();
+ }
+ state.previus_button = btn;
+}
A src/rotenc.h => src/rotenc.h +5 -0
@@ 0,0 1,5 @@
+#pragma once
+
+void re_gpio_setup(void);
+void re_tick(void);
+void re_set_fns(void (*ccw_fn)(void), void (*cw_fn)(void), void (*btn_fn)(void));
A src/util.c => src/util.c +7 -0
@@ 0,0 1,7 @@
+#include "util.h"
+
+void delay(volatile uint64_t d) {
+ while (--d) {
+ __asm__ volatile("" : "+g" (d) : :);
+ }
+}
A src/util.h => src/util.h +10 -0
@@ 0,0 1,10 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <libopencm3/stm32/gpio.h>
+
+void delay(volatile uint64_t d);
+
+#define gpio_set_fast(port, gpios) (GPIO_BSRR(port) = gpios)
+#define gpio_clear_fast(port, gpios) (GPIO_BRR(port) = gpios)
A src/ws.c => src/ws.c +104 -0
@@ 0,0 1,104 @@
+#include "ws.h"
+
+#include <math.h>
+#include <libopencm3/stm32/rcc.h>
+#include <libopencm3/stm32/gpio.h>
+
+#include "util.h"
+
+#define wait_250ns() { \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); };
+
+#define wait_550ns() { \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); \
+ __asm__("nop"); };
+
+#define GPIO_PORT GPIOB
+#define GPIO_RCC RCC_GPIOB
+#define DATA_PIN GPIO3
+
+const double GAMMA = 1.0 / 1.8;
+
+void ws_gpio_setup() {
+ rcc_periph_clock_enable(GPIO_RCC);
+ gpio_mode_setup(GPIO_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, DATA_PIN);
+ gpio_set_output_options(GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, DATA_PIN);
+ gpio_clear(GPIO_PORT, DATA_PIN);
+}
+
+static inline void shift_bit(uint8_t bit) {
+ if (bit) {
+ gpio_set_fast(GPIO_PORT, DATA_PIN);
+ wait_550ns();
+ gpio_clear_fast(GPIO_PORT, DATA_PIN);
+ } else {
+ gpio_set_fast(GPIO_PORT, DATA_PIN);
+ wait_250ns();
+ gpio_clear_fast(GPIO_PORT, DATA_PIN);
+ }
+}
+
+static inline void latch(void) {
+ delay(700);
+}
+
+void ws_shift(const void* data, size_t len) {
+ for (size_t i = 0; i < len; i++) {
+ for (uint8_t s = 8; s-- > 0;) {
+ shift_bit(((const uint8_t*)data)[i] & (uint8_t)(1 << s));
+ wait_550ns();
+ }
+ }
+}
+
+void ws_latch() {
+ latch();
+}
+
+static uint8_t correct_gamma(uint8_t i) {
+ return (uint8_t)(255.0 * (pow((double)i / 255.0, GAMMA)));
+}
+
+WS_RGB ws_rgb(uint8_t r, uint8_t g, uint8_t b) {
+ WS_RGB rgb;
+ rgb.r = correct_gamma(r);
+ rgb.g = correct_gamma(g);
+ rgb.b = correct_gamma(b);
+ return rgb;
+}
A src/ws.h => src/ws.h +15 -0
@@ 0,0 1,15 @@
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+typedef struct {
+ uint8_t g;
+ uint8_t r;
+ uint8_t b;
+} WS_RGB;
+
+void ws_gpio_setup(void);
+void ws_shift(const void* data, size_t len);
+void ws_latch(void);
+WS_RGB ws_rgb(uint8_t r, uint8_t g, uint8_t b);