~rf/iv8-clock-old

c27b7aac57d4f320abc5d92655f934df0f30290a — donotnoot 3 years ago 2f042fe master
Add basic functionality
10 files changed, 614 insertions(+), 0 deletions(-)

A src/iv.c
A src/iv.h
A src/main.c
A src/main.h
A src/rotenc.c
A src/rotenc.h
A src/util.c
A src/util.h
A src/ws.c
A src/ws.h
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);