initial commit of butchered code, minimum viable for DC32

This commit is contained in:
true
2024-08-07 02:43:20 -07:00
commit 9ecef1ab38
87 changed files with 27586 additions and 0 deletions

212
firmware/user/src/adc.c Normal file
View File

@@ -0,0 +1,212 @@
/*
* Created on: Jul 29, 2024
*
* not sure how well the ambient light sensor will work, being surrounded by other LEDs.
* I guess I could make the programs black out and read then. maybe I'll do that.
*/
#include <ch32v20x.h>
#include "adc.h"
static const uint8_t led_brightness_map[] = {
63, 60, 53, 48,
47, 46, 45, 43,
42, 41, 40, 39,
38, 36, 35, 34,
33, 31, 30, 28, // indoors normal brightness
26, 24, 22, 21,
20, 20, 19, 19,
18, 18, 17, 17
};
static GPIO_InitTypeDef lsens_a = {
.GPIO_Mode = GPIO_Mode_Out_PP,
.GPIO_Pin = LSENS_A_PIN,
.GPIO_Speed = GPIO_Speed_2MHz
};
static GPIO_InitTypeDef lsens_k = {
.GPIO_Mode = GPIO_Mode_Out_PP,
.GPIO_Pin = LSENS_K_PIN,
.GPIO_Speed = GPIO_Speed_2MHz
};
volatile uint16_t lsens_limits[2] = {LSENS_COARSE_UP, LSENS_COARSE_DOWN};
static uint8_t lsens_mode = LSENS_READING_IDLE;
uint16_t lsens_val;
uint8_t lsens_wait;
uint8_t lsens_coarse = 1;
void adc_init()
{
ADC_InitTypeDef adc = {0};
RCC_ADCCLKConfig(RCC_PCLK2_Div4);
ADC_DeInit(ADC1);
adc.ADC_Mode = ADC_Mode_Independent;
adc.ADC_ScanConvMode = DISABLE;
adc.ADC_ContinuousConvMode = DISABLE;
adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
adc.ADC_DataAlign = ADC_DataAlign_Right;
adc.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &adc);
ADC_RegularChannelConfig(ADC1, LSENS_ADC_CH, 1, ADC_SampleTime_239Cycles5);
ADC_Cmd(ADC1, ENABLE);
}
void adc_convert()
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
void adc_read()
{
uint16_t timeout = 0xfff;
while((!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)) && timeout) timeout--;
if (timeout) {
lsens_val = ADC_GetConversionValue(ADC1);
}
}
void adc_set_mode_lsens(uint8_t mode)
{
lsens_mode = mode;
if (mode == LSENS_OUTPUT) {
lsens_a.GPIO_Mode = GPIO_Mode_Out_PP;
lsens_k.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(LSENS_A_PORT, &lsens_a);
GPIO_Init(LSENS_K_PORT, &lsens_k);
}
}
uint8_t adc_get_mode_lsens()
{
return lsens_mode;
}
static void lsens_start()
{
// set anode and cathode low
LSENS_A_PORT->BCR = LSENS_A_PIN;
LSENS_K_PORT->BCR = LSENS_K_PIN;
adc_set_mode_lsens(LSENS_READING_START);
// set cathode high, let it charge
LSENS_K_PORT->BSHR = LSENS_K_PIN;
__asm("nop"); __asm("nop");
__asm("nop"); __asm("nop");
// set cathode as analog input
lsens_k.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(LSENS_K_PORT, &lsens_k);
}
static void lsens_stop()
{
lsens_a.GPIO_Mode = GPIO_Mode_Out_PP;
lsens_k.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(LSENS_A_PORT, &lsens_a);
GPIO_Init(LSENS_K_PORT, &lsens_k);
lsens_mode = LSENS_READING_IDLE;
}
void adc_process_lsens()
{
if (lsens_mode != LSENS_OUTPUT) {
// do what needs to be done by me to defeat the light enemys
switch (lsens_mode) {
case LSENS_READING_IDLE: {
// prepare LED, wait a little bit
lsens_start();
adc_convert();
lsens_wait = lsens_coarse;
lsens_mode = LSENS_READING_START;
break;
}
case LSENS_READING_START: {
if (!lsens_wait) {
// convert the LED
adc_convert();
lsens_mode = LSENS_READING_WAIT;
}
lsens_wait--;
break;
}
case LSENS_READING_WAIT: {
// read the light sensor value
adc_read();
lsens_stop();
// calculate adjustments
if (lsens_val > lsens_limits[0]) {
lsens_coarse++;
if (lsens_coarse > 0x3f) lsens_coarse = 0x3f;
} else if (lsens_val < lsens_limits[1]) {
if (lsens_coarse) lsens_coarse--;
}
lsens_wait = 255 - lsens_coarse;
// wait a bit before doing it again
lsens_mode = LSENS_READING_TIMEOUT;
break;
}
case LSENS_READING_TIMEOUT: {
if (!lsens_wait) {
// do it all again
lsens_mode = LSENS_READING_IDLE;
}
lsens_wait--;
}
}
}
}
uint16_t adc_get_lsens()
{
return lsens_val;
}
uint8_t adc_get_lsens_coarse()
{
return lsens_coarse;
}
uint8_t adc_get_brightness(uint8_t level)
{
if (!level) {
// are you outside? why? it's too fucking hot
// we'll shut down when in the presence of big nuclear fire
if (adc_get_lsens() < 1800) {
// yup, outside or in a spotlight at 3ft away or something
return 0;
}
}
if (level >= sizeof(led_brightness_map)) {
return led_brightness_map[sizeof(led_brightness_map) - 1];
}
return led_brightness_map[level];
}

49
firmware/user/src/adc.h Normal file
View File

@@ -0,0 +1,49 @@
/*
* Created on: Jul 27, 2024
*/
#ifndef USER_SRC_ADC_H_
#define USER_SRC_ADC_H_
#define LSENS_DARK_THRESHOLD 0x7ff // baseline minimum value reading achieved in darkness
#define LSENS_A_PORT GPIOB
#define LSENS_A_PIN GPIO_Pin_5
#define LSENS_K_PORT GPIOA
#define LSENS_K_PIN GPIO_Pin_4
#define LSENS_ADC_CH ADC_Channel_4
#define LSENS_COARSE_UP 2940 // counts higher than this increase lsens_coarse, maximum 64
#define LSENS_COARSE_DOWN 2820 // counts lower than this decrease lsens_coarse, minimum 1
enum lsens_mode {
LSENS_READING_IDLE = 0,
LSENS_READING_START,
LSENS_READING_WAIT,
LSENS_READING_TIMEOUT,
LSENS_OUTPUT = 0xff
};
void adc_init();
void adc_convert();
void adc_read();
void adc_set_mode_lsens(uint8_t mode);
uint8_t adc_get_mode_lsens();
void adc_process_lsens();
uint16_t adc_get_lsens();
uint8_t adc_get_lsens_coarse();
uint8_t adc_get_brightness(uint8_t level);
#endif /* USER_SRC_ADC_H_ */

217
firmware/user/src/aw20xxx.c Normal file
View File

@@ -0,0 +1,217 @@
/*
* awinic AW20108 / AW20072 / AW20054 / AW20036 LED Matrix Driver
*
* originally written by true in 2022
* while sleep deprived for a super constrained mcu
*
* some bugs fixed for defcon 32 on aug 6-7, 2024
*
* driver assumptions:
* - rows and columns are used in order on the chip, lowest to highest
* (if any are skipped, just skip this data in your buffer)
* - duty cycle will be set according to the column count
* - all AW20xxx chips will operate on the same i2c bus
* - the only i2c write routine does not have register arguments
*
* driver notices:
* - updates only happen one column at a time, and are blocking
* (future version may implement a callback when each column is done)
* - this driver has not yet implemented the pattern controller
* - this driver has not yet implemented the GAIN register, so only operates 8-bit
* (will be implemented later to allow for beyond-8-bit operation)
* - this driver has not yet implemented FADEDIM mode
* - all transfers result in copies of data, which is wasteful
* (future version may transfer LED data directly from the buffer)
*
* if you need anything different, write it yourself
*/
#include "aw20xxx.h"
#define AW20X_THIS_PAGE (aw->state & AW20X_STATE_PAGE_MASK)
#define AW20X_SET_PAGE(x) if (AW20X_THIS_PAGE != (x)) { \
aw20x_page(aw, x); \
aw->state &= ~AW20X_STATE_PAGE_MASK; \
aw->state |= x; }
static uint8_t aw_buf[25]; // enough bytes for register and single column FADEDIM update (1 + (12*2))
void aw20x_page(struct AW20x *aw, uint8_t page)
{
// the datasheet isn't clear on this. the default is zero, and only the lower
// three bits are specified. yet the DS says to send 0xCy where y is the page bits.
// we'll just do what the DS says even though it contradicts itself.
aw_buf[0] = 0xc0 | (page & AW20X_PAGE_MASK);
AW20X_I2C_writereg(aw->addr, AW20X_REG_PAGE, aw_buf, 1);
}
void aw20x_init(struct AW20x *aw, uint8_t addr, uint8_t cols, uint8_t rows, uint8_t imax)
{
// set config register as specified
aw->addr = addr;
aw->cols = cols;
aw->rows = rows;
aw->config = imax & AW20X_CONF_IMAX_MASK;
// ensure we are on page 0 to start
aw20x_page(aw, 0);
while (AW20X_I2C_busy());
// wake up
aw20x_sleep(aw, 0);
// enabled columns
aw_buf[0] = cols - 1;
AW20X_I2C_writereg(aw->addr, AW20X_REG_SIZE, aw_buf, 1);
// general config
aw_buf[0] = imax & AW20X_CONF_IMAX_MASK;
AW20X_I2C_writereg(aw->addr, AW20X_REG_GCCR, aw_buf, 1);
while (AW20X_I2C_busy());
}
void aw20x_sleep(struct AW20x *aw, uint8_t sleep)
{
// make sure we're on the config page
AW20X_SET_PAGE(AW20X_PAGE0_CONFIG);
// don't touch the buffer until we are allowed
while (AW20X_I2C_busy());
// send sleep bit
aw_buf[0] = sleep ? AW20X_SLPCR_SLEEP : 0;
AW20X_I2C_writereg(aw->addr, AW20X_REG_SLPCR, aw_buf, 1);
// set state
if (sleep) aw->state |= AW20X_STATE_SLEEP_MASK;
else aw->state &= ~AW20X_STATE_SLEEP_MASK;
// burn some cycles if we woke up
if (!sleep) PLATFORM_INIT_DELAY();
}
void aw20x_imax(struct AW20x *aw, uint8_t imax)
{
AW20X_SET_PAGE(AW20X_PAGE0_CONFIG);
// todo: implement
}
/*
* sends LED values to the chip
*/
void aw20x_commit_fade(struct AW20x *aw)
{
uint8_t c;
uint8_t row;
// make sure we're on the fade page
AW20X_SET_PAGE(AW20X_PAGE2_FADE);
// don't touch the buffer until we are allowed
while (AW20X_I2C_busy());
row = 0;
for (c = 0; c < aw->cols; c++) {
// write to chip
AW20X_I2C_writereg(aw->addr, row, aw->fade + row, aw->rows);
while (AW20X_I2C_busy());
row += AW20X_MAX_ROWS;
}
}
void aw20x_commit_dim(struct AW20x *aw)
{
// todo: implement
}
/*
* sets all LEDs to the specified 6-bit DIM value.
* used when just using FADE and 8-bit mode
* to set initial and fine tune from IMAX the output current.
*/
void aw20x_commit_dim_global(struct AW20x *aw, uint8_t dim)
{
uint8_t i;
uint8_t row = 0;
// ceil
if (dim > 0x3f) dim = 0x3f;
// make sure we're on the dim page
AW20X_SET_PAGE(AW20X_PAGE1_DIM);
// don't touch the buffer until we are allowed
while (AW20X_I2C_busy());
// clear buffer
for (i = 0; i <= aw->rows; i++) aw_buf[i] = dim;
// send buffer for each column
for (i = 0; i < aw->cols; i++) {
AW20X_I2C_writereg(aw->addr, row, aw_buf, aw->rows);
while (AW20X_I2C_busy());
row += AW20X_MAX_ROWS;
}
}
void aw20x_commit_fadedim(struct AW20x *aw)
{
}
static inline void aw20x_led_on(struct AW20x *aw, uint8_t first, uint8_t last, uint8_t on_bit)
{
}
/*
* enables LEDs based on user LED count, zero-indexed
* AW20036 would be 0-35, AW00054 would be 0-53, and so on
* for example, LEDs 8-12 on AW20054 would enable C0R8, C1R0, C1R1, C1R2
*
* todo:
* - read current state, and apply bitfields to the currently active state
* - allow bypassing the readback for faster operation (such as setting all LEDs on at startup)
* - make this more efficient (36 LEDs takes ~0.3ms on a 48MHz PIC!)
*/
void aw20x_led_enable(struct AW20x *aw, uint8_t first, uint8_t last)
{
uint8_t c, r;
uint8_t boff;
// make sure we're on the config page
AW20X_SET_PAGE(AW20X_PAGE0_CONFIG);
// don't touch the buffer until we are allowed
while (AW20X_I2C_busy());
// bits are stored 6 bits per byte, 2 bytes per column, one bit for each row
// we only want to touch bits that exist on the chip and in the correct order
boff = 0;
for (c = 0; c < (aw->cols * 2); c++) {
aw_buf[c] = 0;
for (r = 0; r < AW20X_MAX_LEDON_BITS; r++) {
if (r+boff >= first) {
if (r+boff <= last) {
aw_buf[c] |= (1 << r);
}
}
}
boff += AW20X_MAX_LEDON_BITS;
}
AW20X_I2C_writereg(aw->addr, AW20X_REG_LEDON0, aw_buf, c);
}
/*
* disables LEDs based on user LED count, zero-indexed
* (for example, LEDs 8-12 on AW20054 would enable C0R8, C0R9, C1R0, C1R1)
*/
void aw20x_led_disable(struct AW20x *aw, uint8_t first, uint8_t last)
{
}

189
firmware/user/src/aw20xxx.h Normal file
View File

@@ -0,0 +1,189 @@
/*
* awinic AW20108 / AW20072 / AW20054 / AW20036 LED Matrix Driver
*/
#ifndef AW20X_LED_MATRIX_H
#define AW20X_LED_MATRIX_H
#include <stdint.h>
#include "i2c.h"
#define PLATFORM_INIT_DELAY() { uint16_t zz = 1000; while(zz--); }
// burn cycles for ~200us
#define AW20X_MAX_COLS 9
#define AW20X_MAX_ROWS 12
#define AW20X_MAX_LEDON_BITS 6
#define AW20X_ADDR_SCL 0x38 // AD pin tied to SCL
#define AW20X_ADDR_SDA 0x39 // AD pin tied to SDA
#define AW20X_ADDR_GND 0x3A // AD pin tied to GND
#define AW20X_ADDR_VDD 0x3B // AD pin tied to VDD
#define AW20X_CONF_IMAX_MASK 0xf0 // 7:4
#define AW20X_CONF_IMAX_OFFSET 4 // 7:4
#define AW20X_CONF_ADDR_MASK 0x03 // 1:0
#define AW20X_CONF_ADDR_OFFSET 0 // 1:0
#define AW20X_CONF_USE_FADEDIM 0x08 // aw20x.fade now becomes fadedim; updates are done with FADEDIM page
// this mode uses 2 bytes per LED; dim as byte 0, fade as byte 1
#define AW20X_CONF_USE_EXPEN 0x04 // sets GCCR.EXPEN=1; fade is now only 6 bits (not yet implemented)
#define AW20X_PAGE_MASK 0x07
#define AW20X_STATE_PAGE_MASK AW20X_PAGE_MASK
#define AW20X_STATE_SLEEP_MASK 0x80
#define AW20X_PAGE0_CONFIG 0x00 // function register
#define AW20X_PAGE1_DIM 0x01 // 5:0 dim; 8 bits per LED
#define AW20X_PAGE2_FADE 0x02 // 7:0 fade; 8 bits per LED
#define AW20X_PAGE3_PATTERN 0x03 // 1:0 pattern; 8 bits per LED
#define AW20X_PAGE4_DIMFADE 0x04 // 13:8 dim, 7:0 fade; 16 bits per LED
#define AW20X_PAGE5_DFP 0x05 // 15:14 pat, 13:8 dim, 7:0 fade; 16 bits per LED
#define AW20X_REG_IDR 0x00 // R 7:0 chip ID {0x18}
#define AW20X_REG_SLPCR 0x01 // RW 7 sleep {0x80}
#define AW20X_REG_RSTR 0x02 // W 7:0 SW_RSTN (?)
#define AW20X_REG_GCCR 0x03 // RW 7:3 IMAX(7:4), ALLON, -, -, EXPEN {0x10}
#define AW20X_REG_FCD 0x04 // W 0 FCDE (fast clear display enable)
#define AW20X_REG_CLKSYS 0x05 // RW 1:0 CLK_IO, CLK_SEL
#define AW20X_REG_FLTCFG1 0x09 // RW 5:0 UVLOPE, OTPE, UVIE, OTIE, UVLOE, OTE
#define AW20X_REG_FLTCFG2 0x0a // RW 3:2 UVTH
#define AW20X_REG_ISRFLT 0x0b // RW 5:0 PAT2IS, PAT1IS, PAT0IS, -, -, UVLOIS, OTIS
#define AW20X_REG_LEDON0 0x31 // W 5:0 ON0:ON5, same pattern through to LEDON17 (0x42)
#define AW20X_REG_PATCR 0x43 // RW 6:0 PAT2IE, PAT1IE, PAT0IE, -, PAT2EN, PAT1EN, PAT0EN
#define AW20X_REG_FADEH0 0x44 // RW 7:0 FADEH0
#define AW20X_REG_FADEH1 0x45
#define AW20X_REG_FADEH2 0x46
#define AW20X_REG_FADEL0 0x47
#define AW20X_REG_FADEL1 0x48
#define AW20X_REG_FADEL2 0x49 // RW 7:0 FADEL2
#define AW20X_REG_PAT0T0 0x4a // RW 7:0 T1[4], T2[4]
#define AW20X_REG_PAT0T1 0x4b // RW 7:0 T3[4], T4[4]
#define AW20X_REG_PAT0T2 0x4c // RW 7:0 LE[2], LB[2], LT(11:8)[4]
#define AW20X_REG_PAT0T3 0x4d // RW 7:0 LT(7:0)
#define AW20X_REG_PAT1T0 0x4e
#define AW20X_REG_PAT1T1 0x4f
#define AW20X_REG_PAT1T2 0x50
#define AW20X_REG_PAT1T3 0x51
#define AW20X_REG_PAT2T0 0x52
#define AW20X_REG_PAT2T1 0x53
#define AW20X_REG_PAT2T2 0x54
#define AW20X_REG_PAT2T3 0x55
#define AW20X_REG_PAT0CFG 0x56 // RW 2:0 SWITCH, RAMPE, PATMD
#define AW20X_REG_PAT1CFG 0x57
#define AW20X_REG_PAT2CFG 0x58
#define AW20X_REG_PATGO 0x59 // RW 6:0 PAT2ST, PAT1ST, PAT0ST, -, RUN2, RUN1, RUN0
#define AW20X_REG_SIZE 0x80 // RW 3:0 SWSEL
#define AW20X_REG_PAGE 0xf0 // RW 2:0 page select, 0-5; available from all pages
#define AW20X_IDR_ID 0x18 // value for all chips in this series
#define AW20X_SLPCR_SLEEP 0x01 // sleep mode (default is HIGH / asleep)
#define AW20X_RSTR_SW_RSTN 0x01 // write value to soft reset the chip
#define AW20X_GCCR_IMAX 0xf0 // global current setting (default 20mA)
#define AW20X_GCCR_ALLON 0x08 // 0=normal, 1=force all on
#define AW20X_GCCR_EXPEN 0x01 // 0=fade is linear 8-bit, 1=fade is exponential 6-bit
#define AW20X_FCD_FCDE 0x01 // write value to clear display (DS doesn't specify; is this a blanker?)
#define AW20X_CLKSYS_CLK_IO 0x02 // 0=no clk output, 1=clk output on (output) CLKIO pin
#define AW20X_CLKSYS_CLK_SEL 0x01 // 0=internal 4MHz, 1=use clk on (input) CLKIO pin
#define AW20X_FLTCFG1_UVLOPE 0x20 // 1=enable UVLO protection; chip sets SLPCR.SLEEP when ISRFLT.UVLOIS=1
#define AW20X_FLTCFG1_OTPE 0x10 // 1=enable overtemp protection; chip sets SLPCR.SLEEP when ISRFLT.UVLOIS=1
#define AW20X_FLTCFG1_UVIE 0x08 // 1=UVLO interrupt enable
#define AW20X_FLTCFG1_OTIE 0x04 // 1=overtemp interrupt enable
#define AW20X_FLTCFG1_UVLOE 0x02 // 1=enable UVLO detect
#define AW20X_FLTCFG1_OTE 0x01 // 1=enable overtemp detect
#define AW20X_FLTCFG1_UVTH_2V0 (0x00 << 2) // UVLO threshold voltage
#define AW20X_FLTCFG1_UVTH_2V1 (0x01 << 2) // UVLO threshold voltage
#define AW20X_FLTCFG1_UVTH_2V2 (0x02 << 2) // UVLO threshold voltage
#define AW20X_FLTCFG1_UVTH_2V3 (0x03 << 2) // UVLO threshold voltage
#define AW20X_ISRFLT_PAT2IS 0x40 // pattern controller 2 interrupt (finished breath loop)
#define AW20X_ISRFLT_PAT1IS 0x20 // pattern controller 1 interrupt (finished breath loop)
#define AW20X_ISRFLT_PAT0IS 0x10 // pattern controller 0 interrupt (finished breath loop)
#define AW20X_ISRFLT_UVLOIS 0x02 // 0=normal, 1=UVLO detected
#define AW20X_ISRFLT_OTIS 0x01 // 0=normal, 1=overtemp detected
// todo: fill in all values from PATCR onward
#define AW20X_SOFT_RESET AW20X_RSTR_SW_RSTN
enum aw20x_imax {
AW20X_IMAX_10MA = 0x00,
AW20X_IMAX_20MA = 0x10,
AW20X_IMAX_30MA = 0x20,
AW20X_IMAX_40MA = 0x30,
AW20X_IMAX_60MA = 0x40,
AW20X_IMAX_80MA = 0x50,
AW20X_IMAX_120MA = 0x60,
AW20X_IMAX_160MA = 0x70,
AW20X_IMAX_3_3MA = 0x80,
AW20X_IMAX_6_7MA = 0x90,
AW20X_IMAX_10MA_2 = 0xa0,
AW20X_IMAX_13_3MA = 0xb0,
AW20X_IMAX_20MA_2 = 0xc0,
AW20X_IMAX_26_7MA = 0xd0,
AW20X_IMAX_40MA_2 = 0xe0,
AW20X_IMAX_53_3MA = 0xf0
};
enum aw20x_size {
AW20X_SIZE_1COL = 0,
AW20X_SIZE_2COL,
AW20X_SIZE_3COL,
AW20X_SIZE_4COL,
AW20X_SIZE_5COL,
AW20X_SIZE_6COL,
AW20X_SIZE_7COL,
AW20X_SIZE_8COL,
AW20X_SIZE_9COL
};
#define AW20X_I2C_busy() (0)
#define AW20X_I2C_writereg(adr, reg, buf, siz) i2c_write_addr1b(adr, reg, buf, siz);
typedef struct AW20x {
uint8_t addr;
uint8_t config; // settings for the chip
uint8_t cols; // highest column used, 1-6
uint8_t rows; // highest row used, 1-12
uint8_t state; // keeps track of active page, and high bit is set if asleep
uint8_t pad[3];
uint8_t *fade; // led buffer location for FADE (required), of size cols+rows
uint8_t *gain; // led buffer location for GAIN (optional), of size cols+rows
} AW20x;
void aw20x_init(struct AW20x *aw, uint8_t addr, uint8_t cols, uint8_t rows, uint8_t imax);
void aw20x_sleep(struct AW20x *aw, uint8_t sleep);
void aw20x_commit_fade(struct AW20x *aw);
void aw20x_commit_dim_global(struct AW20x *aw, uint8_t dim);
void aw20x_led_enable(struct AW20x *aw, uint8_t first, uint8_t last);
#endif /* AW02X_LED_MATRIX_H */

94
firmware/user/src/btn.c Normal file
View File

@@ -0,0 +1,94 @@
/*
* Created on: Jul 27, 2024
*
* generic button handler like I do on most of my projects
*/
#include <ch32v20x.h>
#include "btn.h"
struct Btn btn[BTN_COUNT] = {0};
void btn_init()
{
uint8_t i;
// gpio for buttons has already been configured
btn[0]._pintype = BTN_UP_PIN;
btn[1]._pintype = BTN_DN_PIN;
// default setup
for (i = 0; i < BTN_COUNT; i++) {
btn[i]._mask = BTN_RELEASE;
// ignore if held
if (!(BTN_PORT->INDR & (1 << btn[i]._pintype))) {
btn[i]._mask |= BTN_IGNORE;
}
}
}
void btn_poll()
{
uint8_t i;
uint8_t ignore;
for (i = 0; i < BTN_COUNT; i++) {
ignore = btn[i]._mask & BTN_IGNORE;
// pushed?
if (!(BTN_PORT->INDR & (1 << btn[i]._pintype))) {
// hold counter
if (btn[i]._count < 0xffff) btn[i]._count++;
// pushed long enough?
if (btn[i]._count < BTN_DEBOUNCE) continue;
// first push?
if (!(btn[i]._mask & BTN_PUSH)) {
btn[i]._mask = BTN_PUSH | ignore;
if (btn[i].cb_push && !ignore) {
btn[i].cb_push(i);
btn[i]._mask |= (BTN_PUSH << 4);
}
} else if (btn[i]._count >= btn[i].hold) {
// held to count limit
// if button is not repeatable, do not retrigger
if ((btn[i]._mask & BTN_HOLD) && !btn[i].repeat) continue;
btn[i]._mask |= BTN_HOLD;
// call callback only if not in ignore state
if (btn[i].cb_hold && !ignore) {
btn[i].cb_hold(i);
btn[i]._mask |= (BTN_HOLD << 4);
}
// apply repeat rate to count
if (btn[i].repeat > btn[i]._count) {
btn[i]._count = 0;
} else btn[i]._count -= btn[i].repeat;
}
}
// is not pushed
else {
if (!(btn[i]._mask & BTN_RELEASE)) {
// note: release will remove ignore status
btn[i]._mask = BTN_RELEASE;
btn[i]._count = 0;
// call callback only if not in ignore state
if (btn[i].cb_release && !ignore) {
btn[i].cb_release(i);
btn[i]._mask |= (BTN_RELEASE << 4);
}
}
}
}
}

50
firmware/user/src/btn.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* btn.h
*
* Created on: Jul 27, 2024
* Author: true
*/
#ifndef USER_SRC_BTN_H_
#define USER_SRC_BTN_H_
#define BTN_COUNT 2
#define BTN_DEBOUNCE 15 // debounce time in ~2ms increments
#define BTN_PORT GPIOA
#define BTN_UP_PIN 2
#define BTN_DN_PIN 3
#define BTN_PUSH (1 << 0)
#define BTN_HOLD (1 << 1)
#define BTN_RELEASE (1 << 2)
#define BTN_IGNORE (1 << 3)
typedef struct Btn {
uint8_t _mask;
uint8_t _pintype;
uint16_t _count; // held counts
uint16_t hold; // initial hold
uint16_t repeat; // repeated hold
void (*cb_push)(uint8_t);
void (*cb_hold)(uint8_t);
void (*cb_release)(uint8_t);
} Btn;
extern struct Btn btn[BTN_COUNT];
void btn_init();
void btn_poll();
#endif /* USER_SRC_BTN_H_ */

150
firmware/user/src/config.c Normal file
View File

@@ -0,0 +1,150 @@
/*
* config.c
*
* Created on: Jul 27, 2024
* Author: true
*/
#include <string.h>
#include <ch32v20x.h>
#include "config.h"
#include "flash.h"
struct UserConf userconf;
static uint8_t active_page;
uint32_t chip_get_flash_size()
{
uint32_t w;
w = DBGMCU_GetCHIPID();
w >>= 16;
w &= 0xff;
if (w >= 0x80) return 131072; // can be 3 different sizes, but I don't care right now;
// no applicable MCU for us has this size anyway
switch (w) {
case 0x33:
case 0x36:
case 0x37:
case 0x38:
case 0x39: {
return 32768;
}
default: {
return 65536;
}
}
}
static uint32_t calc_address()
{
uint32_t calc = (FLASH_BASE + chip_get_flash_size()) - (CONF_FLASH_PAGE_SIZE * CONF_FLASH_PAGES);
return calc;
}
/*
static void read_page_from_flash(uint8_t page, uint32_t *data, uint16_t len)
{
uint32_t *addr = calc_address();
// no more than 256 bytes at a time necessary
if (len > 64) len = 64;
// set address of page
addr += page * CONF_FLASH_PAGE_SIZE;
// read the data
flash_read(addr, data, len);
}
*/
static void write_page_to_flash(uint8_t page, uint32_t *data, uint16_t len)
{
len = (uint16_t)len;
uint32_t addr = calc_address() + (page * CONF_FLASH_PAGE_SIZE);
// write the data
// note we don't pass any length. we'll just read whatever garbage
// is in RAM after our config and write it to flash. lol
flash_write256((uint32_t *)addr, data);
}
static uint16_t checksum(struct UserConf *conf)
{
uint16_t i;
uint16_t sum = 0;
uint8_t *uc = (uint8_t *)conf;
// calculate checksum
for (i = 0; i < sizeof(userconf) - 6; i++) {
sum += *uc++;
}
return sum;
}
void userconf_load()
{
uint8_t i;
uint32_t ver_highest = 0;
uint8_t page = CONF_FLASH_PAGES;
uint32_t addr;
struct UserConf *flash;
// read pages and see if we can find our data
while (page--) {
addr = calc_address() + (page * CONF_FLASH_PAGE_SIZE);
flash = (struct UserConf *)addr;
if (flash->checkval == CHECKVAL) {
if (flash->checksum == checksum(flash)) {
if (ver_highest < flash->version) {
ver_highest = flash->version;
active_page = page;
}
}
}
}
if (!ver_highest) {
// config is invalid; reset to default
userconf.version = 0;
// default program configs
// program 0: rainbow puke
userconf.checksum = checksum(&userconf);
userconf.checkval = CHECKVAL;
} else {
memcpy(&userconf, (uint8_t *)(calc_address() + (active_page * 256)), sizeof(userconf));
}
}
void userconf_save()
{
// we can only save ~4 billion times
userconf.version++;
userconf.checksum = checksum(&userconf);
userconf.checkval = CHECKVAL;
// determine page to write
active_page++;
active_page %= CONF_FLASH_PAGES;
// this MCU writes and erases in full page sized blocks.
// nothing is mentioned in the datasheet nor reference manual
// about repeated writes. so to be safe, each page gets
// written to once per erase.
write_page_to_flash(active_page, (uint32_t *)&userconf, sizeof(userconf)/4);
}

View File

@@ -0,0 +1,52 @@
/*
* config.h
*
* Created on: Jul 27, 2024
* Author: true
*/
#ifndef USER_SRC_CONFIG_H_
#define USER_SRC_CONFIG_H_
#include <stdint.h>
#include <ch32v20x.h>
#define CONF_FLASH_ADDR_BASE FLASH_BASE
#define CONF_FLASH_PAGE_SIZE 256 // CH32V20x is 256-byte page in fast mode
#define CONF_FLASH_PAGES 8 // wear leveling pages
#define CONF_CURSOR_WHITE 0
#define CONF_CURSOR_GREEN 1
#define CONF_CURSOR_ORANGE 2
#define CONF_CURSOR_OFF 3
#define CONF_CURSOR_SELECT_MASK 0x03
#define CONF_CURSOR_FLASH_MASK 0x70
#define CONF_CURSOR_FLASH_SHIFT 4
#define CHECKVAL 0x2024dc32
typedef struct UserConf {
uint32_t version;
uint8_t top_prog_ena_map;
uint8_t bot_prog_ena_map;
uint16_t checksum;
uint32_t checkval;
} UserConf; // 12 bytes
extern struct UserConf userconf;
void userconf_load();
void userconf_save();
#endif /* USER_SRC_CONFIG_H_ */

43
firmware/user/src/flash.c Normal file
View File

@@ -0,0 +1,43 @@
/*
* Created on: Jul 29, 2024
*
* routines for fucking around with built in flash memory.
*/
#include <stdint.h>
#include <ch32v20x.h>
// reads from flash in 32-bit mode
// note: this function is untested
uint8_t flash_read(uint32_t *flash_addr, uint32_t *data, uint32_t len)
{
uint32_t *addr = (uint32_t *)flash_addr;
while (len >= 4) {
*data++ = *addr++;
len -= 4;
}
return 0;
}
// erases flash page, then writes 256-byte data buffer to flash page
// flash page must be at 256-byte boundary
uint8_t flash_write256(uint32_t *flash_addr, uint32_t *data)
{
FLASH_Status s;
// erase flash page
s = FLASH_ROM_ERASE((uint32_t)flash_addr, 256);
if (s != FLASH_COMPLETE) {
return s;
}
s = FLASH_ROM_WRITE((uint32_t)flash_addr, data, 256);
if (s != FLASH_COMPLETE) {
return s;
}
return 0;
}

18
firmware/user/src/flash.h Normal file
View File

@@ -0,0 +1,18 @@
/*
* flash.h
*
* Created on: Jul 29, 2024
* Author: true
*/
#ifndef USER_SRC_FLASH_H_
#define USER_SRC_FLASH_H_
int8_t flash_read(uint32_t *flash_addr, uint32_t *data, uint32_t len);
int8_t flash_write256(uint32_t *flash_addr, uint32_t *data);
#endif /* USER_SRC_FLASH_H_ */

View File

@@ -0,0 +1,94 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016 B. Stultiens
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "hsv2rgb.h"
void hsv2rgb_8b(int16_t h, uint8_t s, uint8_t v, uint8_t *r, uint8_t *g , uint8_t *b)
{
uint8_t sextant;
uint8_t bb;
uint16_t ww;
uint8_t h_fraction;
if (!(s)) {
*(r) = *(g) = *(b) = (v);
return;
}
sextant = h >> 8;
HSV_SEXTANT_TEST(sextant); // Optional: Limit hue sextants to defined space
HSV_POINTER_SWAP(sextant, r, g, b); // Swap pointers depending which sextant we are in
*g = v; // Top level
// Perform actual calculations
/*
* Bottom level: v * (1.0 - s)
* --> (v * (255 - s) + error_corr) / 256
*/
bb = ~s;
ww = v * bb;
ww += 1; // Error correction
ww += ww >> 8; // Error correction
*b = ww >> 8;
h_fraction = h & 0xff; // 0...255
if(!(sextant & 1)) {
// *r = ...slope_up...;
/*
* Slope up: v * (1.0 - s * (1.0 - h))
* --> (v * (255 - (s * (256 - h) + error_corr1) / 256) + error_corr2) / 256
*/
ww = !h_fraction ? ((uint16_t)s << 8) : (s * (uint8_t)(-h_fraction));
ww += ww >> 8; // Error correction 1
bb = ww >> 8;
bb = ~bb;
ww = v * bb;
ww += v >> 1; // Error correction 2
*r = ww >> 8;
} else {
// *r = ...slope_down...;
/*
* Slope down: v * (1.0 - s * h)
* --> (v * (255 - (s * h + error_corr1) / 256) + error_corr2) / 256
*/
ww = s * h_fraction;
ww += ww >> 8; // Error correction 1
bb = ww >> 8;
bb = ~bb;
ww = v * bb;
ww += v >> 1; // Error correction 2
*r = ww >> 8;
/*
* A perfect match for h_fraction == 0 implies:
* *r = (ww >> 8) + (h_fraction ? 0 : 1)
* However, this is an extra calculation that may not be required.
*/
}
}

View File

@@ -0,0 +1,98 @@
#ifndef _INC_HSV2RGB_H
#define _INC_HSV2RGB_H
#include <stdint.h>
typedef struct color_rgb {
uint8_t r;
uint8_t g;
uint8_t b;
} color_rgb;
typedef struct color_hsv {
int16_t h;
uint8_t s;
uint8_t v;
} color_hsv;
#define HSV_HUE_SEXTANT 256
#define HSV_HUE_STEPS (6 * HSV_HUE_SEXTANT)
#define HSV_HUE_MIN 0
#define HSV_HUE_MAX (HSV_HUE_STEPS - 1)
#define HSV_SAT_MIN 0
#define HSV_SAT_MAX 255
#define HSV_VAL_MIN 0
#define HSV_VAL_MAX 255
/* Options: */
#define HSV_USE_SEXTANT_TEST /* Limit the hue to 0...360 degrees */
void hsv2rgb_8b(int16_t h, uint8_t s, uint8_t v, uint8_t *r, uint8_t *g , uint8_t *b);
/*
* Macros that are common to all implementations
*/
#ifdef HSV_USE_SEXTANT_TEST
#define HSV_SEXTANT_TEST(sextant) \
if((sextant) > 5) { \
(sextant) = 5; \
}
#else
#define HSV_SEXTANT_TEST(sextant)
#endif
/*
* Pointer swapping:
* sext. r g b r<>b g<>b r <> g result
* 0 0 0 v u c !u v c u v c
* 0 0 1 d v c d v c
* 0 1 0 c v u u v c u v c
* 0 1 1 c d v v d c d v c d v c
* 1 0 0 u c v u v c u v c
* 1 0 1 v c d v d c d v c d v c
*
* if(sextant & 2)
* r <-> b
*
* if(sextant & 4)
* g <-> b
*
* if(!(sextant & 6) {
* if(!(sextant & 1))
* r <-> g
* } else {
* if(sextant & 1)
* r <-> g
* }
*/
#define HSV_SWAPPTR(a,b) do { uint8_t *tmp = (a); (a) = (b); (b) = tmp; } while(0)
#define HSV_POINTER_SWAP(sextant,r,g,b) \
do { \
if((sextant) & 2) { \
HSV_SWAPPTR((r), (b)); \
} \
if((sextant) & 4) { \
HSV_SWAPPTR((g), (b)); \
} \
if(!((sextant) & 6)) { \
if(!((sextant) & 1)) { \
HSV_SWAPPTR((r), (g)); \
} \
} else { \
if((sextant) & 1) { \
HSV_SWAPPTR((r), (g)); \
} \
} \
} while(0)
#endif /* _INC_HSV2RGB_H */

163
firmware/user/src/i2c.c Normal file
View File

@@ -0,0 +1,163 @@
/*
* i2c.c
*
* routines more or less copied from WCH example code, then fucked around with to work.
*
* these routines have serious issues.
* - any i2c issue may lock up the machine.
* - timeout handlers are hastily added and may have problems.
* - there's no error handling of any kind
* - the library code makes some serious assumptions re: flags
* - it's a shitpile of polling
*/
#include <ch32v20x.h>
#define I2C_TIMEOUT 0xefff
#define I2C_TIMEOUT_ACK_POLL 0x180
static uint16_t timeout;
void i2c_init()
{
I2C_InitTypeDef i2c = {0};
// ensure GPIO pins are configured before initializing
i2c.I2C_ClockSpeed = 666666;
i2c.I2C_Mode = I2C_Mode_I2C;
i2c.I2C_DutyCycle = I2C_DutyCycle_16_9;
i2c.I2C_OwnAddress1 = 0x7f;
i2c.I2C_Ack = I2C_Ack_Enable;
i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(I2C1, &i2c);
I2C_Cmd(I2C1, ENABLE);
}
/*
* reads data from devices which use a single-byte address register
*/
int8_t i2c_read_addr1b(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t len)
{
timeout = I2C_TIMEOUT;
while((I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) != RESET) && timeout--);
if (!timeout) return -1;
I2C_GenerateSTART(I2C1, ENABLE);
timeout = I2C_TIMEOUT;
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) && timeout--);
if (!timeout) return -2;
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
timeout = I2C_TIMEOUT;
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) && timeout--);
if (!timeout) return -3;
I2C_SendData(I2C1, reg);
timeout = I2C_TIMEOUT;
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) && timeout--);
if (!timeout) return -4;
I2C_GenerateSTART(I2C1, ENABLE);
timeout = I2C_TIMEOUT;
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) && timeout--);
if (!timeout) return -5;
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Receiver);
timeout = I2C_TIMEOUT;
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) && timeout--);
if (!timeout) return -6;
while (len) {
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == RESET) {
I2C_AcknowledgeConfig(I2C1, len);
}
*data++ = I2C_ReceiveData(I2C1);
len--;
if (!len) {
I2C_GenerateSTOP(I2C1, ENABLE);
}
}
return 0;
}
uint8_t i2c_read_reg_8b(uint8_t addr, uint8_t reg)
{
uint8_t dat;
i2c_read_addr1b(addr, reg, &dat, 1);
return dat;
}
int8_t i2c_write_addr1b(uint8_t addr, uint8_t reg, const uint8_t *data, uint8_t len)
{
timeout = I2C_TIMEOUT;
while((I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) != RESET) && timeout--);
if (!timeout) return -1;
I2C_GenerateSTART(I2C1, ENABLE);
timeout = I2C_TIMEOUT;
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) && timeout--);
if (!timeout) return -2;
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
timeout = I2C_TIMEOUT;
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) && timeout--);
if (!timeout) return -3;
I2C_SendData(I2C1, reg);
timeout = I2C_TIMEOUT;
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) && timeout--);
if (!timeout) return -4;
while (len) {
// fixme: can get stuck here if address isn't found.
// somehow all the above passes but this will fail
if (I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE) != RESET) {
I2C_SendData(I2C1, *data++);
len--;
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
}
}
I2C_GenerateSTOP(I2C1, ENABLE);
return 0;
}
void i2c_write_reg_8b(uint8_t addr, uint8_t reg, uint8_t dat)
{
i2c_write_addr1b(addr, reg, &dat, 1);
}
int8_t i2c_ack_poll(uint8_t addr)
{
int8_t addr_match = 0;
timeout = I2C_TIMEOUT;
while((I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) != RESET) && timeout--);
if (!timeout) return -1;
I2C_GenerateSTART(I2C1, ENABLE);
timeout = I2C_TIMEOUT;
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) && timeout--);
if (!timeout) return -2;
I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Receiver);
timeout = I2C_TIMEOUT_ACK_POLL;
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) && timeout--);
if (!timeout) {
addr_match = -128;
}
I2C_GenerateSTOP(I2C1, ENABLE);
return addr_match;
}

29
firmware/user/src/i2c.h Normal file
View File

@@ -0,0 +1,29 @@
/*
* i2c.h
*
* Created on: Jul 27, 2024
* Author: true
*/
#ifndef USER_SRC_I2C_H_
#define USER_SRC_I2C_H_
#include <stdint.h>
void i2c_init();
int8_t i2c_read_addr1b(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t len);
int8_t i2c_write_addr1b(uint8_t addr, uint8_t reg, const uint8_t *data, uint8_t len);
uint8_t i2c_read_reg_8b(uint8_t addr, uint8_t reg);
void i2c_write_reg_8b(uint8_t addr, uint8_t reg, uint8_t dat);
int8_t i2c_ack_poll(uint8_t devaddr);
#endif /* USER_SRC_I2C_H_ */

177
firmware/user/src/led.c Normal file
View File

@@ -0,0 +1,177 @@
/*
* Created on: Jul 28, 2024
*/
#include <stdint.h>
#include <ch32v20x.h>
#include "led.h"
#include "adc.h"
#define AW20X_DIM 31 // initial global current setting
#define AW20X_COLS 2
#define AW20X_ROWS 12
#define AW20X_FADE_COUNT (AW20X_ROWS * AW20X_COLS)
static const uint16_t pwm_cie_256in_1024out[] = {
0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7,
7, 8, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 15,
15, 16, 17, 17, 18, 19, 19, 20, 21, 22, 22, 23, 24, 25, 26, 27,
28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 43, 44,
45, 47, 48, 50, 51, 52, 54, 55, 57, 58, 60, 61, 63, 65, 66, 68,
70, 71, 73, 75, 77, 79, 81, 83, 84, 86, 88, 90, 93, 95, 97, 99,
101, 103, 106, 108, 110, 113, 115, 118, 120, 123, 125, 128, 130, 133, 136, 138,
141, 144, 147, 149, 152, 155, 158, 161, 164, 167, 171, 174, 177, 180, 183, 187,
190, 194, 197, 200, 204, 208, 211, 215, 218, 222, 226, 230, 234, 237, 241, 245,
249, 254, 258, 262, 266, 270, 275, 279, 283, 288, 292, 297, 301, 306, 311, 315,
320, 325, 330, 335, 340, 345, 350, 355, 360, 365, 370, 376, 381, 386, 392, 397,
403, 408, 414, 420, 425, 431, 437, 443, 449, 455, 461, 467, 473, 480, 486, 492,
499, 505, 512, 518, 525, 532, 538, 545, 552, 559, 566, 573, 580, 587, 594, 601,
609, 616, 624, 631, 639, 646, 654, 662, 669, 677, 685, 693, 701, 709, 717, 726,
734, 742, 751, 759, 768, 776, 785, 794, 802, 811, 820, 829, 838, 847, 857, 866,
875, 885, 894, 903, 913, 923, 932, 942, 952, 962, 972, 982, 992, 1002, 1013, 1023,
};
struct LedMatrix led;
uint8_t boeing[BOEING_COUNT];
AW20x awled;
static uint8_t awled_fade[AW20X_FADE_COUNT];
static uint8_t led_matrix_updated = 0;
// helper for LED programs
void use_brightest(uint8_t *dest, uint8_t *compare, uint8_t count)
{
while (count--) {
if (*dest < *compare) *dest = *compare;
dest++;
compare++;
}
}
static uint16_t pwm_gamma(uint8_t in)
{
return pwm_cie_256in_1024out[in];
}
void led_init()
{
TIM_TimeBaseInitTypeDef timer ={0};
TIM_OCInitTypeDef pwm = {0};
uint8_t i;
// reset LED values
for (i = 0; i < sizeof(led.all); i++) {
led.all[i] = 0;
}
boeing[0] = boeing[1] = boeing[2] = boeing[3] = 0;
// configure matrix
awled.fade = awled_fade;
for (i = 0; i < AW20X_FADE_COUNT; i++) {
awled_fade[i] = 0;
}
aw20x_init(&awled, AW20X_ADDR_GND << 1, 2, 12, AW20X_IMAX_13_3MA);
aw20x_commit_dim_global(&awled, AW20X_DIM);
aw20x_led_enable(&awled, 0, 23);
// configure rear bottom blue LEDs
timer.TIM_Period = (1 << 10) - 1; // 10-bit
timer.TIM_Prescaler = 0;
timer.TIM_ClockDivision = TIM_CKD_DIV1;
timer.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(BOEING_TIM, &timer);
pwm.TIM_OCMode = TIM_OCMode_PWM1;
pwm.TIM_OutputState = TIM_OutputState_Enable;
pwm.TIM_Pulse = 0;
pwm.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(BOEING_TIM, &pwm);
TIM_OC2Init(BOEING_TIM, &pwm);
TIM_OC3Init(BOEING_TIM, &pwm);
TIM_OC4Init(BOEING_TIM, &pwm);
TIM_CtrlPWMOutputs(BOEING_TIM, ENABLE);
TIM_OC1PreloadConfig(BOEING_TIM, TIM_OCPreload_Disable);
TIM_ARRPreloadConfig(BOEING_TIM, ENABLE);
TIM_Cmd(BOEING_TIM, ENABLE);
}
void led_matrix_is_updated()
{
led_matrix_updated = 1;
}
void led_matrix_send()
{
int8_t i, j;
// only render when there's something to render
if (led_matrix_updated) {
// convert led matrix
j = 0;
// first RGBs are on the second select, in GBR order
j = 12;
for (i = 0; i < 4; i++) {
awled.fade[j++] = led.ind.rgb[i][2];
awled.fade[j++] = led.ind.rgb[i][1];
awled.fade[j++] = led.ind.rgb[i][0];
}
// individual LEDs are kinda random, dunno why
awled.fade[ 3] = led.ind.led[0];
awled.fade[ 6] = led.ind.led[1];
awled.fade[ 8] = led.ind.led[2];
// last RGB is last on the first column
awled.fade[ 9] = led.ind.rgb[4][2];
awled.fade[10] = led.ind.rgb[4][1];
awled.fade[11] = led.ind.rgb[4][0];
// time to send
aw20x_commit_fade(&awled);
led_matrix_updated = 0;
}
}
void led_boeing_update()
{
uint8_t i;
uint16_t scale[BOEING_COUNT];
uint8_t lsens = adc_get_lsens_coarse();
uint8_t max = adc_get_brightness(lsens);
// this isn't a matrix so we can just update whenever
// but we need to scale to ambient light level
for (i = 0; i < BOEING_COUNT; i++) {
scale[i] = max * boeing[i];
scale[i] >>= 6;
}
TIM_CtrlPWMOutputs(BOEING_TIM, DISABLE);
BOEING_TIM->CH1CVR = pwm_gamma(boeing[1]);
BOEING_TIM->CH2CVR = pwm_gamma(boeing[0]);
BOEING_TIM->CH3CVR = pwm_gamma(boeing[3]);
BOEING_TIM->CH4CVR = pwm_gamma(boeing[2]);
TIM_CtrlPWMOutputs(BOEING_TIM, ENABLE);
}

52
firmware/user/src/led.h Normal file
View File

@@ -0,0 +1,52 @@
/*
* Created on: Jul 28, 2024
*/
#ifndef USER_SRC_LED_H_
#define USER_SRC_LED_H_
#include <stdint.h>
#include "aw20xxx.h"
#include "ledprog_boeing.h"
#include "ledprog_flame.h"
#define BOEING_TIM TIM3
#define BOEING_COUNT 4
typedef struct LedMatrix {
union {
uint8_t all[18];
struct ind {
uint8_t rgb[5][3];
uint8_t led[3];
} ind;
};
} LedMatrix;
extern struct LedMatrix led;
extern uint8_t boeing[4];
void use_brightest(uint8_t *dest, uint8_t *compare, uint8_t count);
void led_init();
void led_matrix_is_updated();
void led_matrix_send();
void led_boeing_update();
#endif /* USER_SRC_LED_H_ */

View File

@@ -0,0 +1,77 @@
/*
* Created on: Aug 7, 2024
*/
#include <stdint.h>
#include "hsv2rgb.h"
#include "led.h"
#include "rand.h"
static uint16_t rnd;
static uint16_t work[4];
/*
*
*/
static void prog_solid(uint8_t level)
{
uint8_t i;
for (i = 0; i < 4; i++) {
boeing[i] = level;
}
}
static void prog_0_nothing(uint8_t tick)
{
rnd = (uint16_t)rnd;
work[0] = rnd;
prog_solid(0);
}
static void prog_1_dim(uint8_t tick)
{
prog_solid(16);
}
static void prog_2_normal(uint8_t tick)
{
prog_solid(96);
}
static void prog_3_brite(uint8_t tick)
{
prog_solid(192);
}
void (*ledprog_boeing[4])(uint8_t) = {
prog_0_nothing,
prog_1_dim,
prog_2_normal,
prog_3_brite
};
void ledprog_bot_init()
{
uint8_t i;
rnd = prng_get16();
// global program initialization
for (i = 0; i < 4; i++) {
work[i] = 0;
}
}

View File

@@ -0,0 +1,17 @@
/*
* Created on: Aug 6, 2024
*/
#ifndef USER_SRC_LEDPROG_BOEING_H_
#define USER_SRC_LEDPROG_BOEING_H_
extern void (*ledprog_boeing[4])(uint8_t);
void ledprog_bot_init();
#endif /* USER_SRC_LEDPROG_BOEING_H_ */

View File

@@ -0,0 +1,136 @@
/*
* Created on: Aug 7, 2024
*/
#include <stdint.h>
#include "hsv2rgb.h"
#include "led.h"
#include "rand.h"
static uint16_t rnd;
static uint16_t work[4];
/*
*
*/
static void prog_0_flames(uint8_t tick)
{
uint8_t i;
uint16_t j;
uint16_t hue = 0; // straight red
if ((tick & 0x3) == 0) {
for (i = 0; i < 5; i++) {
work[0] = 8;
j = prng_get8();
if (j > 128) {
work[0] += j >> 1;
}
if (j > 64) {
// put some orange-green hue in there sometimes
j = (j >= 0xfd) ? (5*6) : 0;
hsv2rgb_8b(hue + j, 255, work[0] & 0xff,
&led.ind.rgb[i][0], &led.ind.rgb[i][1], &led.ind.rgb[i][2]);
}
}
led_matrix_is_updated();
}
// update orange more slowly (why? dunno)
if ((tick & 0x7) == 3) {
for (i = 0; i < 3; i++) {
work[0] = 32;
j = prng_get8();
if (j > 0x7f) {
work[0] += j >> 1;
}
led.ind.led[i] = work[0];
}
led_matrix_is_updated();
}
}
/*
* flames
*/
static void prog_1_rainbow(uint8_t tick)
{
uint8_t i;
uint16_t hue;
work[0] += 1;
work[0] &= 0xff;
hue = work[0] * 6;
if (tick & 1) {
for (i = 0; i < 5; i++) {
hsv2rgb_8b(hue, 255, 255, &led.ind.rgb[i][0], &led.ind.rgb[i][1], &led.ind.rgb[i][2]);
hue += 32;
hue %= 1536;
}
for (i = 0; i < 3; i++) {
led.ind.led[i] = 0;
}
led_matrix_is_updated();
}
}
/*
* iterate over previous programs after random delays
*/
static void prog_2_iterate(uint8_t tick)
{
}
static void prog_3_off(uint8_t tick)
{
uint8_t i;
// blank it
for (i = 0; i < sizeof(led.all); i++) {
led.all[i] = 0;
}
if (!work[0]) {
led_matrix_is_updated();
work[0] = 1;
}
}
void (*ledprog_flame[4])(uint8_t) = {
prog_0_flames,
prog_1_rainbow,
prog_2_iterate,
prog_3_off
};
void ledprog_top_init()
{
uint8_t i;
rnd = prng_get16();
// global program initialization
for (i = 0; i < 4; i++) {
work[i] = 0;
}
}

View File

@@ -0,0 +1,18 @@
/*
* Created on: Aug 7, 2024
*/
#ifndef USER_SRC_LEDPROG_FLAME_H_
#define USER_SRC_LEDPROG_FLAME_H_
extern void (*ledprog_flame[4])(uint8_t);
void ledprog_top_init();
#endif /* USER_SRC_LEDPROG_FLAME_H_ */

162
firmware/user/src/rand.c Normal file
View File

@@ -0,0 +1,162 @@
/**
* Tiny Mersenne Twister: only 127-bit internal state.
* Derived from the reference implementation version 1.1 (2015/04/24)
* by Mutsuo Saito (Hiroshima University) and Makoto Matsumoto
* (Hiroshima University).
*/
#include <stdint.h>
#include "rand.h"
static void tinymt32_next_state(tinymt32_t *s);
static uint32_t tinymt32_temper(tinymt32_t *s);
tinymt32_t tinymt32_s;
/**
* Parameter set to use for this IETF specification. Don't change.
* This parameter set is the first entry of the precalculated
* parameter sets in tinymt32dc/tinymt32dc.0.1048576.txt by
* Kenji Rikitake, available at:
* https://github.com/jj1bdx/tinymtdc-longbatch/.
* It is also the parameter set used in:
* Rikitake, K., "TinyMT pseudo random number generator for
* Erlang", Proceedings of the 11th ACM SIGPLAN Erlang Workshop,
* September 2012.
*/
const uint32_t TINYMT32_MAT1_PARAM = UINT32_C(0x8f7011ee);
const uint32_t TINYMT32_MAT2_PARAM = UINT32_C(0xfc78ff1f);
const uint32_t TINYMT32_TMAT_PARAM = UINT32_C(0x3793fdff);
/**
* This function initializes the internal state array with a
* 32-bit unsigned integer seed.
* @param s pointer to tinymt internal state.
* @param seed a 32-bit unsigned integer used as a seed.
*/
void tinymt32_init (tinymt32_t* s, uint32_t seed)
{
const uint32_t MIN_LOOP = 8;
const uint32_t PRE_LOOP = 8;
s->status[0] = seed;
s->status[1] = s->mat1 = TINYMT32_MAT1_PARAM;
s->status[2] = s->mat2 = TINYMT32_MAT2_PARAM;
s->status[3] = s->tmat = TINYMT32_TMAT_PARAM;
for (int i = 1; i < MIN_LOOP; i++) {
s->status[i & 3] ^= i + UINT32_C(1812433253)
* (s->status[(i - 1) & 3]
^ (s->status[(i - 1) & 3] >> 30));
}
/*
* NB: The parameter set of this specification warrants
* that none of the possible 2^^32 seeds leads to an
* all-zero 127-bit internal state. Therefore, the
* period_certification() function of the original
* TinyMT32 source code has been safely removed. If
* another parameter set is used, this function will
* have to be reintroduced here.
*/
for (int i = 0; i < PRE_LOOP; i++) {
tinymt32_next_state(s);
}
}
/**
* This function outputs a 32-bit unsigned integer from
* the internal state.
* @param s pointer to tinymt internal state.
* @return 32-bit unsigned integer r (0 <= r < 2^32).
*/
uint32_t tinymt32_get_uint32(tinymt32_t* s)
{
tinymt32_next_state(s);
return tinymt32_temper(s);
}
/**
* Internal tinymt32 constants and functions.
* Users should not call these functions directly.
*/
const uint32_t TINYMT32_SH0 = 1;
const uint32_t TINYMT32_SH1 = 10;
const uint32_t TINYMT32_SH8 = 8;
const uint32_t TINYMT32_MASK = UINT32_C(0x7fffffff);
/**
* This function changes the internal state of tinymt32.
* @param s pointer to tinymt internal state.
*/
static void tinymt32_next_state (tinymt32_t* s)
{
uint32_t x;
uint32_t y;
y = s->status[3];
x = (s->status[0] & TINYMT32_MASK)
^ s->status[1]
^ s->status[2];
x ^= (x << TINYMT32_SH0);
y ^= (y >> TINYMT32_SH0) ^ x;
s->status[0] = s->status[1];
s->status[1] = s->status[2];
s->status[2] = x ^ (y << TINYMT32_SH1);
s->status[3] = y;
/*
* The if (y & 1) {...} block below replaces:
* s->status[1] ^= -((int32_t)(y & 1)) & s->mat1;
* s->status[2] ^= -((int32_t)(y & 1)) & s->mat2;
* The adopted code is equivalent to the original code
* but does not depend on the representation of negative
* integers by 2's complements. It is therefore more
* portable but includes an if branch, which may slow
* down the generation speed.
*/
if (y & 1) {
s->status[1] ^= s->mat1;
s->status[2] ^= s->mat2;
}
}
/**
* This function outputs a 32-bit unsigned integer from
* the internal state.
* @param s pointer to tinymt internal state.
* @return 32-bit unsigned pseudorandom number.
*/
static uint32_t tinymt32_temper (tinymt32_t* s)
{
uint32_t t0, t1;
t0 = s->status[3];
t1 = s->status[0] + (s->status[2] >> TINYMT32_SH8);
t0 ^= t1;
/*
* The if (t1 & 1) {...} block below replaces:
* t0 ^= -((int32_t)(t1 & 1)) & s->tmat;
* The adopted code is equivalent to the original code
* but does not depend on the representation of negative
* integers by 2's complements. It is therefore more
* portable but includes an if branch, which may slow
* down the generation speed.
*/
if (t1 & 1) {
t0 ^= s->tmat;
}
return t0;
}
uint16_t prng_scale16(uint16_t min, uint16_t max)
{
uint32_t rnd;
rnd = prng_get16();
rnd *= (max - min);
rnd >>= 16;
rnd += min;
return rnd;
}

39
firmware/user/src/rand.h Normal file
View File

@@ -0,0 +1,39 @@
/**
* Tiny Mersenne Twister
*/
#ifndef CODE_INC_RAND_H_
#define CODE_INC_RAND_H_
/**
* tinymt32 internal state vector and parameters
*/
typedef struct {
uint32_t status[4];
uint32_t mat1;
uint32_t mat2;
uint32_t tmat;
} tinymt32_t;
extern tinymt32_t tinymt32_s;
void tinymt32_init(tinymt32_t *s, uint32_t seed);
uint32_t tinymt32_get_uint32(tinymt32_t* s);
#define prng_get8() (tinymt32_get_uint32(&tinymt32_s) & 0xff)
#define prng_get16() (tinymt32_get_uint32(&tinymt32_s) & 0xffff)
#define prng_get32() tinymt32_get_uint32(&tinymt32_s)
uint16_t prng_scale16(uint16_t min, uint16_t max);
#endif /* CODE_INC_RAND_H */

94
firmware/user/src/touch.c Normal file
View File

@@ -0,0 +1,94 @@
/*
* Created on: Jul 29, 2024
*
* touch my pepper
*/
#include <stdint.h>
#include <ch32v20x.h>
#include "touch.h"
const uint16_t tbtn_adc_ch[2] = {TBTN2, TBTN1};
uint16_t tbtn_idle_cal[2];
uint16_t touch_read_adc(u8 adc_ch)
{
ADC_RegularChannelConfig(TBTN_ADC, adc_ch, 1, ADC_SampleTime_7Cycles5);
TBTN_ADC->IDATAR1 = 0x10; // charging time
TBTN_ADC->RDATAR = 0x08; // discharging time
ADC_SoftwareStartConvCmd(TBTN_ADC, ENABLE);
while(!ADC_GetFlagStatus(TBTN_ADC, ADC_FLAG_EOC));
return (uint16_t)TBTN_ADC->RDATAR;
}
uint16_t touch_read(u8 btn_ch)
{
if (btn_ch < TBTN_COUNT) return touch_read_adc(tbtn_adc_ch[btn_ch]);
else return 0xffff;
}
uint8_t touch_read_pushed(uint8_t btn_ch)
{
if (btn_ch > 1) return 0;
if (touch_read_adc(tbtn_adc_ch[btn_ch]) < tbtn_idle_cal[btn_ch] - TBTN_PUSHED_COUNTS) {
return 1;
}
return 0;
}
void touch_cal()
{
uint8_t i, j;
uint32_t val;
for (i = 0; i < TBTN_COUNT; i++) {
// do a dummy read
touch_read(tbtn_adc_ch[i]);
// hang until the button appears to be idle
val = 0;
while (val < TBTN_IDLE_LOW_VAL) {
val = touch_read(tbtn_adc_ch[i]);
}
// get average of 4 readings and store as calibration value
val = 0;
for (j = 0; j < 4; j++) {
val += touch_read(tbtn_adc_ch[i]);
}
val >>= 2;
tbtn_idle_cal[i] = val & 0xfff;
}
}
void touch_init()
{
ADC_InitTypeDef adc = {0};
// make sure ADC peripheral is clocked and
// pins are configured as analog inputs before calling this function.
// configure ADC for touchkey use
adc.ADC_Mode = ADC_Mode_Independent;
adc.ADC_ScanConvMode = DISABLE;
adc.ADC_ContinuousConvMode = DISABLE;
adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
adc.ADC_DataAlign = ADC_DataAlign_Right;
adc.ADC_NbrOfChannel = 1;
ADC_Init(TBTN_ADC, &adc);
// enable ADC, then enable touchkey
ADC_Cmd(TBTN_ADC, ENABLE);
TBTN_ADC->CTLR1 |= ADC_CTLR1_BUFEN | ADC_CTLR1_TKENABLE;
// calibrate idle touch
touch_cal();
}

37
firmware/user/src/touch.h Normal file
View File

@@ -0,0 +1,37 @@
/*
* touch.h
*
* Created on: Jul 29, 2024
* Author: true
*/
#ifndef USER_SRC_TOUCH_H_
#define USER_SRC_TOUCH_H_
#define TBTN_ADC ADC2
#define TBTN_COUNT 2
#define TBTN1 ADC_Channel_4 // right button
#define TBTN2 ADC_Channel_9 // left button
#define TBTN_IDLE_LOW_VAL 0xfd0 // values above this == not touched. used to calibrate
#define TBTN_PUSHED_COUNTS 0x37f // counts must drop below tbtn_idle_cal-TBTN_THRESHOLD to be considered a press
#define ADC_CTLR1_TKENABLE (1 << 24)
#define ADC_CTLR1_BUFEN (1 << 26)
void touch_init();
uint16_t touch_read_adc(u8 adc_ch);
uint16_t touch_read(u8 btn_ch);
uint8_t touch_read_pushed(uint8_t btn_ch);
#endif /* USER_SRC_TOUCH_H_ */

144
firmware/user/src/ui.c Normal file
View File

@@ -0,0 +1,144 @@
/*
* Created on: Jul 28, 2024
*/
#include <stdint.h>
#include "ui.h"
#include "adc.h"
#include "btn.h"
#include "config.h"
#include "led.h"
#define MODE_RUN 0
#define MODE_PROGRAM 1
#define MODE_PARAMETER 2
#define UI_CONF_SAVE_TIMEOUT 384
#define UI_PROG_RUNTIME_MIN (128*15) // 15 seconds
#define UI_PROG_RUNTIME_MAX (128*120) // 120 seconds
#define PROG_RANDOM 0x80
static uint8_t mode = MODE_RUN;
static uint8_t tick = 0;
static uint16_t save_delay = 0;
void ui_btn_push_cb(uint8_t idx)
{
}
void ui_btn_hold_cb(uint8_t idx)
{
/*
switch (idx) {
case 0: { // BTN_UP, upper programs
userconf.top_prog_ena_map ^= PROG_RANDOM;
break;
}
case 1: { // BTN_DN, lower programs
userconf.bot_prog_ena_map ^= PROG_RANDOM;
break;
}
}
*/
}
void ui_btn_release_cb(uint8_t idx)
{
uint8_t update;
switch (idx) {
case 0: { // BTN_UP, upper programs
update = userconf.top_prog_ena_map & ~(PROG_RANDOM);
update++;
if (update > 3) update = 0;
userconf.top_prog_ena_map = update | (userconf.top_prog_ena_map & PROG_RANDOM);
ledprog_top_init();
break;
}
case 1: { // BTN_DN, lower programs
update = userconf.bot_prog_ena_map & ~(PROG_RANDOM);
update++;
if (update > 3) update = 0;
userconf.bot_prog_ena_map = update | (userconf.bot_prog_ena_map & PROG_RANDOM);
ledprog_bot_init();
break;
}
}
save_delay = UI_CONF_SAVE_TIMEOUT;
}
void ui_init()
{
btn[0].hold = 1200 >> 1;
btn[0].repeat = 0; // (1000 / 20) >> 1;
btn[0].cb_push = ui_btn_push_cb;
btn[0].cb_hold = ui_btn_hold_cb;
btn[0].cb_release = ui_btn_release_cb;
btn[1].hold = 1200 >> 1;
btn[1].repeat = 0;
btn[1].cb_push = ui_btn_push_cb;
btn[1].cb_hold = ui_btn_hold_cb;
btn[1].cb_release = ui_btn_release_cb;
}
void ui_render()
{
uint8_t w;
tick++;
uint8_t prog_top_idx = userconf.top_prog_ena_map & ~(PROG_RANDOM);
uint8_t prog_bot_idx = userconf.bot_prog_ena_map & ~(PROG_RANDOM);
switch (mode) {
case MODE_RUN: {
// run programs
if (ledprog_flame[prog_top_idx]) {
ledprog_flame[prog_top_idx](tick);
}
if (ledprog_boeing[prog_bot_idx]) {
ledprog_boeing[prog_bot_idx](tick);
}
// check flash save
if (save_delay) {
save_delay--;
if (!save_delay) {
userconf_save();
}
}
break;
}
case MODE_PROGRAM: {
break;
}
case MODE_PARAMETER: {
break;
}
}
// set LED current based on ambient light level
w = adc_get_lsens_coarse();
w = adc_get_brightness(w);
// is31fl3729_set_global_current(FL3729_ADDR, w);
}

15
firmware/user/src/ui.h Normal file
View File

@@ -0,0 +1,15 @@
/*
* Created on: Jul 28, 2024
*/
#ifndef USER_SRC_UI_H_
#define USER_SRC_UI_H_
void ui_init();
void ui_render();
#endif /* USER_SRC_UI_H_ */