minor cleanup, add i2c single register read/write functions

This commit is contained in:
true
2024-07-29 03:44:14 -07:00
parent 20870dc3e3
commit 8b3c45cebd
76 changed files with 58 additions and 46 deletions

View File

@@ -0,0 +1,41 @@
/********************************** (C) COPYRIGHT *******************************
* File Name : ch32v00x_conf.h
* Author : WCH
* Version : V1.0.0
* Date : 2020/08/08
* Description : Library configuration file.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/
#ifndef __CH32V00x_CONF_H
#define __CH32V00x_CONF_H
#include <ch32v00x_adc.h>
#include <ch32v00x_dbgmcu.h>
#include <ch32v00x_dma.h>
#include <ch32v00x_exti.h>
#include <ch32v00x_flash.h>
#include <ch32v00x_gpio.h>
#include <ch32v00x_i2c.h>
#include <ch32v00x_it.h>
#include <ch32v00x_iwdg.h>
#include <ch32v00x_misc.h>
#include <ch32v00x_pwr.h>
#include <ch32v00x_rcc.h>
#include <ch32v00x_spi.h>
#include <ch32v00x_tim.h>
#include <ch32v00x_usart.h>
#include <ch32v00x_wwdg.h>
#include <ch32v00x_opa.h>
#endif /* __CH32V00x_CONF_H */

View File

@@ -0,0 +1,83 @@
/********************************** (C) COPYRIGHT *******************************
* File Name : ch32v00x_it.c
* Author : WCH
* Version : V1.0.0
* Date : 2022/08/08
* Description : Main Interrupt Service Routines.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/
#include <ch32v00x_it.h>
#include <ch32v00x.h>
#include "src/adc.h"
#include "src/btn.h"
#include "src/led.h"
void NMI_Handler(void);
void HardFault_Handler(void);
void SysTick_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
/*********************************************************************
* @fn NMI_Handler
*
* @brief This function handles NMI exception.
*
* @return none
*/
void NMI_Handler(void)
{
}
/*********************************************************************
* @fn HardFault_Handler
*
* @brief This function handles Hard Fault exception.
*
* @return none
*/
void HardFault_Handler(void)
{
while (1);
}
volatile uint16_t ticnt;
volatile uint32_t uptime;
void SysTick_Handler(void)
{
if (++ticnt > 0x7ff) {
ticnt = 0;
uptime++;
}
// send new LEDs (1/128 duty)
if ((ticnt & 0xf) == 3) {
led_send();
}
// process buttons (1/512 duty)
if ((ticnt & 0x3) == 0) {
btn_poll();
}
// process ADC (1/256 duty)
if ((ticnt & 0x07) == 1) {
adc_convert();
}
if ((ticnt & 0x07) == 2) {
adc_read();
}
// clear comparison flag
SysTick->SR = 0;
return;
}

View File

@@ -0,0 +1,26 @@
/********************************** (C) COPYRIGHT *******************************
* File Name : ch32v00x_it.h
* Author : WCH
* Version : V1.0.0
* Date : 2022/08/08
* Description : This file contains the headers of the interrupt handlers.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/
#ifndef __CH32V00x_IT_H
#define __CH32V00x_IT_H
#include <stdint.h>
extern volatile uint16_t ticnt;
extern volatile uint32_t uptime;
#endif /* __CH32V00x_IT_H */

View File

@@ -0,0 +1,136 @@
/*
* RETRO TECH COMMUNITY
* GAT Addon Firmware
* by true
*
* code was made for different random addons I designed for dc32,
* then adapted to each one. so things might be a mess.
*
* I had only a few hours on and off over a week to get the code done.
* lots of shit was copied from demo code and adapted to work. certainly
* isn't the best way to get this done.
*
* I know I could save some power, but seeing as all the badges burn it in
* LEDs and almost nobody else power optimizes their badge code, who cares?
*
* sorry. wasn't procrastination I swear. all the ideas were last minute.
*/
#include <ch32v00x.h>
#include <stdint.h>
#include "src/adc.h"
#include "src/btn.h"
#include "src/config.h"
#include "src/i2c.h"
#include "src/led.h"
#include "src/rand.h"
#include "src/ui.h"
void systick_init(void)
{
SysTick->CMP = (SystemCoreClock / 2048) - 1; // we want a 2048Hz interrupt
SysTick->CNT = 0; // clear counter
SysTick->CTLR = 0xF; // start counter in /1 mode, enable interrupts, auto-reset counter
SysTick->SR = 0; // clear count comparison flag
NVIC_EnableIRQ(SysTicK_IRQn); // enable interrupt
}
void gpio_init()
{
GPIO_InitTypeDef gpio = {0};
gpio.GPIO_Speed = GPIO_Speed_2MHz;
// Soft I2C, USART TX/RX; currently unused
gpio.GPIO_Mode = GPIO_Mode_IPD;
gpio.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6;
GPIO_Init(GPIOD, &gpio);
gpio.GPIO_Pin = GPIO_Pin_1;
GPIO_Init(GPIOA, &gpio);
// lightsense LED anode
gpio.GPIO_Mode = GPIO_Mode_Out_OD;
gpio.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOD, &gpio);
// unused pins
gpio.GPIO_Mode = GPIO_Mode_IPD;
gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_7;
GPIO_Init(GPIOC, &gpio);
gpio.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOD, &gpio);
// I2C will be handled by the driver
// IS_SDB IS31FL3729 shutdown pin (active low)
GPIOC->BCR = GPIO_Pin_3;
gpio.GPIO_Mode = GPIO_Mode_Out_OD;
gpio.GPIO_Pin = GPIO_Pin_3;
GPIO_Init(GPIOC, &gpio);
// BTN1, BTN2 will be handled by button handler
// WP for EEPROM
GPIOC->BSHR = GPIO_Pin_6;
gpio.GPIO_Mode = GPIO_Mode_Out_OD;
gpio.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOC, &gpio);
}
/*********************************************************************
* @fn main
*
* @brief Main program.
*
* @return none
*/
int main(void)
{
// configure core
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SystemCoreClockUpdate();
// enable peripheral clocks
RCC_APB1PeriphClockCmd( RCC_APB1Periph_PWR | RCC_APB1Periph_I2C1, ENABLE);
RCC_APB2PeriphClockCmd( RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA |
RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD |
RCC_APB2Periph_ADC1 | RCC_APB2Periph_TIM1, ENABLE);
// configure gpio pins
gpio_init();
// get saved settings
i2c_init();
userconf_load();
// configure hardware
adc_init();
btn_init();
led_init();
// configure random
tinymt32_init(&tinymt32_s, DBGMCU_GetCHIPID() | userconf.checksum);
// configure UI
ui_init();
// configure systick interrupt
systick_init();
// do system shit
while(1) {
__WFI();
// after sending LEDs, run our program
// to update the LED buffer for next time
// we have approx 7ms to render... plenty of time
if ((ticnt & 0xf) == 3) {
ui_render();
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Created on: Jul 27, 2024
*
* uses directly polled i2c code. ensure i2c is configured before using.
*
* todo: use dma or interrupt based i2c.
*/
#include "31fl3729.h"
#include "i2c.h"
/* sends the IS31FL3729 general config.
* does not configure per-channel current sink limits; do that separately.
* does not configure other sane defaults; do that separately.
*/
void is31fl3729_init(uint8_t i2c_addr, uint8_t config, uint8_t global_current)
{
uint8_t buf;
// enable device
#ifdef FL3729_SDB_PORT
FL3729_SDB_PORT->BSHR = FL3729_SDB_PIN;
#endif
// reset config registers
buf = FL3729_RESET_VALUE;
i2c_write_addr1b(i2c_addr, FL3729_REG_RESET, &buf, 1);
// write initial config
i2c_write_addr1b(i2c_addr, FL3729_REG_CONFIG, &config, 1);
i2c_write_addr1b(i2c_addr, FL3729_REG_G_CURRENT, &global_current, 1);
}
uint8_t is31fl3729_get_addr(uint8_t adpin)
{
return FL3729_BASE_ADDR | (adpin & 0x3);
}
void is31fl3729_set_global_current(uint8_t i2c_addr, uint8_t current)
{
i2c_write_addr1b(i2c_addr, FL3729_REG_G_CURRENT, &current, 1);
}
void is31fl3729_set_scaling_current(uint8_t i2c_addr, uint8_t cs, uint8_t current)
{
if (cs > 0xf) return;
i2c_write_addr1b(i2c_addr, FL3729_REG_SCALING + cs, &current, 1);
}
/*
* sets current limit of multiple outputs simultaneously, starting from CS1.
* useful for initial register configuration.
*/
void is31fl3729_set_scaling_current_multi(uint8_t i2c_addr, uint8_t *current, uint8_t count)
{
if (!count) return;
count &= 0xf;
i2c_write_addr1b(i2c_addr, FL3729_REG_SCALING, current, count);
}
/* updates LEDs. data is arranged as 16 bytes per switch output.
* i2c_addr: address of endpoint. get with `is31fl3729_get_addr(FL3729_ADPIN_xxx)`
* sw: first switch segment to update. usually set this to 1 for the first switch.
* if set to 0, will default to 1.
* *out: output data buffer
* len: length of buffer
*/
void is31fl3729_set_outputs(uint8_t i2c_addr, uint8_t sw, uint8_t *out, uint8_t len)
{
if (!sw) sw = 1;
// calculate start address
sw--;
sw <<= 4;
sw++;
i2c_write_addr1b(i2c_addr, sw, out, len);
}

View File

@@ -0,0 +1,93 @@
/*
* Created on: Jul 27, 2024
*/
#ifndef USER_SRC_31FL3729_H_
#define USER_SRC_31FL3729_H_
#include <ch32v00x.h>
#include <stdint.h>
#define FL3729_SDB_PORT GPIOC
#define FL3729_SDB_PIN GPIO_Pin_3
#define FL3729_BASE_ADDR 0x68
#define FL3729_ADPIN_GND 0
#define FL3729_ADPIN_SCL 1
#define FL3729_ADPIN_SDA 2
#define FL3729_ADPIN_VCC 3
#define FL3729_REG_OUTPUT 0x01
#define FL3729_REG_SCALING 0x90
#define FL3729_REG_CONFIG 0xA0
#define FL3729_REG_G_CURRENT 0xA1 // global current
#define FL3729_REG_PUPD 0xB0 // pull up / pull down
#define FL3729_REG_SS 0xB1 // spread spectrum
#define FL3729_REG_PWM 0xB2
#define FL3729_REG_RESET 0xCF
#define FL3729_GCC_MAX 64 // maximum global current level
#define FL3729_CONF_SHDN_ENA 0
#define FL3729_CONF_SHDN_DIS ((1 << 0) << 0)
#define FL3729_CONF_OSDE_OFF 0
#define FL3729_CONF_OSDE_OPEN ((1 << 0) << 1)
#define FL3729_CONF_OSDE_SHORT ((1 << 1) << 1)
#define FL3729_CONF_LOGIC_LOLEV 0
#define FL3729_CONF_LOGIC_HILEV ((1 << 0) << 3)
#define FL3729_CONF_MATRIX_9x15 0x00
#define FL3729_CONF_MATRIX_8x16 0x10
#define FL3729_CONF_MATRIX_7x16 0x20
#define FL3729_CONF_MATRIX_6x16 0x30
#define FL3729_CONF_MATRIX_5x16 0x40
#define FL3729_CONF_MATRIX_4x16 0x50
#define FL3729_CONF_MATRIX_3x16 0x60
#define FL3729_CONF_MATRIX_2x16 0x70
#define FL3729_CONF_MATRIX_1x16 0x80 // note: all of SW1-SW9 are ALWAYS ON in this mode
#define FL3729_SS_CLT_1980us 0
#define FL3729_SS_CLT_1200us (1 << 0)
#define FL3729_SS_CLT_820us (2 << 0)
#define FL3729_SS_CLT_660us (3 << 0)
#define FL3729_SS_RNG_5pct 0
#define FL3729_SS_RNG_15pct (1 << 2)
#define FL3729_SS_RNG_24pct (2 << 2)
#define FL3729_SS_RNG_34pct (3 << 2)
#define FL3729_RESET_VALUE 0xAE
/*
typedef struct IS31FL3729_Conf {
uint8_t ad_pin;
uint8_t config;
uint8_t global_brightness;
} IS31FL3729_Conf;
*/
void is31fl3729_init(uint8_t i2c_addr, uint8_t config, uint8_t global_current);
uint8_t is31fl3729_get_addr(uint8_t adpin);
void is31fl3729_set_global_current(uint8_t i2c_addr, uint8_t current);
void is31fl3729_set_scaling_current(uint8_t i2c_addr, uint8_t cs, uint8_t current);
void is31fl3729_set_scaling_current_multi(uint8_t i2c_addr, uint8_t *current, uint8_t count);
void is31fl3729_set_outputs(uint8_t i2c_addr, uint8_t sw, uint8_t *out, uint8_t len);
#endif /* USER_SRC_31FL3729_H_ */

View File

@@ -0,0 +1,142 @@
/*
* Created on: Jul 27, 2024
*
* because the ambient light sensor is facing toward the bottom, and
* this badge has an "ADJUST" knob that is intended for brightness,
* the light sensor code is incomplete.
*
* if you want to use it, copy code from another badge, where it's also
* likely incomplete lol
*/
#include <ch32v00x.h>
// deadzone fudge factors for potentionmeter. test on actual badge
// and see if necessary, or if pot hardware + adc periph is good enough
#define POT_LO 40
#define POT_HI (1024-POT_LO)
uint16_t adc_val[16];
uint16_t adc_val_idx;
uint16_t adc_avg;
uint16_t lsens_val;
void adc_init()
{
ADC_InitTypeDef adc = {0};
GPIO_InitTypeDef gpio = {0};
RCC_ADCCLKConfig(RCC_PCLK2_Div4);
gpio.GPIO_Pin = GPIO_Pin_3;
gpio.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOD, &gpio);
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, ADC_Channel_4, 1, ADC_SampleTime_241Cycles);
ADC_InjectedChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_73Cycles);
ADC_Calibration_Vol(ADC1, ADC_CALVOL_50PERCENT);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
}
void adc_calc_avg()
{
uint8_t i;
uint16_t sum = 0;
for (i = 0; i < 16; i++) {
sum += adc_val[i];
}
sum >>= 4;
adc_avg = sum;
}
void adc_convert()
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
void adc_read()
{
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
adc_val[adc_val_idx++] = ADC_GetConversionValue(ADC1);
adc_val_idx &= 0x0f;
if (!adc_val_idx) adc_calc_avg();
if (ADC1->CTLR1 & ADC_JAUTO) {
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_JEOC));
lsens_val = ADC_GetInjectedConversionValue(ADC1, ADC_InjectedChannel_1);
// reset LSENS
ADC_AutoInjectedConvCmd(ADC1, DISABLE);
}
}
void adc_use_lsens()
{
GPIO_InitTypeDef gpio = {0};
// configure GPIO
gpio.GPIO_Pin = GPIO_Pin_2;
gpio.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &gpio);
// enable injected channel
ADC_AutoInjectedConvCmd(ADC1, ENABLE);
}
void adc_stop_lsens()
{
GPIO_InitTypeDef gpio = {0};
// configure GPIO
gpio.GPIO_Pin = GPIO_Pin_2;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &gpio);
}
uint16_t adc_get_pot()
{
uint32_t pot;
// set lower bound
pot = adc_avg;
if (pot < POT_LO) pot = 0; else pot -= POT_LO;
// scale to upper bound
pot <<= 10;
pot /= POT_HI;
// 8 bits is good enough for us
pot >>= 2;
return pot & 0xff;
}
uint16_t adc_get_lsens()
{
return lsens_val;
}

View File

@@ -0,0 +1,28 @@
/*
* adc.h
*
* Created on: Jul 27, 2024
* Author: true
*/
#ifndef USER_SRC_ADC_H_
#define USER_SRC_ADC_H_
void adc_init();
void adc_config_lsens();
void adc_convert();
void adc_read();
void adc_use_lsens();
void adc_reset_lsens();
uint16_t adc_get_pot();
uint16_t adc_get_lsens();
#endif /* USER_SRC_ADC_H_ */

View File

@@ -0,0 +1,87 @@
/*
* Created on: Jul 27, 2024
*
* generic button handler like I do on most of my projects
*/
#include <ch32v00x.h>
#include "btn.h"
struct Btn btn[BTN_COUNT] = {0};
void btn_init()
{
uint8_t i;
// configure GPIO
BTN_PORT->BSHR = (BTN1_PUPD << BTN1_PIN) | (BTN2_PUPD << BTN2_PIN);
BTN_PORT->CFGLR &= ~((0xff << (BTN1_PIN*4)) | ((0xff << (BTN2_PIN*4))));
BTN_PORT->CFGLR |= (0x80 << (BTN1_PIN*4)) | (0x80 << (BTN2_PIN*4));
// configure default setup
for (i = 0; i < BTN_COUNT; i++) {
btn[i]._mask = 0;
}
btn[0]._pintype = BTN1_PIN | 0x10;
btn[1]._pintype = BTN2_PIN;
}
void btn_poll()
{
uint8_t i;
uint8_t r;
uint8_t ignore;
for (i = 0; i < BTN_COUNT; i++) {
ignore = btn[i]._mask & BTN_IGNORE;
r = BTN_PORT->INDR & (1 << (btn[i]._pintype & 0xf));
if ((!r && (btn[i]._pintype & 0x10)) || (r && !(btn[i]._pintype & 0x10))) {
// is pushed
if (btn[i]._count < BTN_DEBOUNCE) continue;
// hold counter
if (btn[i]._count < 0xffff) btn[i]._count++;
// first push?
if (!btn[i]._mask & BTN_PUSH) {
btn[i]._mask = BTN_PUSH;
if (btn[i].cb_push) {
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
btn[i]._count -= btn[i].repeat;
}
} else {
// is not pushed
if (!btn[i]._mask & BTN_RELEASE) {
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);
}
}
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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 (12 >> 1) // debounce time in 2ms
#define BTN_PORT GPIOC
#define BTN_MODE 0x80
#define BTN_PULL_UP (1 << 0)
#define BTN_PULL_DOWN (1 << 16)
#define BTN1_PIN 5
#define BTN1_PUPD BTN_PULL_UP
#define BTN2_PIN 4
#define BTN2_PUPD BTN_PULL_DOWN
#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_ */

View File

@@ -0,0 +1,61 @@
/*
* config.c
*
* Created on: Jul 27, 2024
* Author: true
*/
#include "config.h"
#include "eeprom.h"
struct UserConf userconf;
static uint16_t checksum()
{
uint16_t i;
uint16_t sum = 0;
uint8_t *uc = (uint8_t *)&userconf;
// calculate checksum
for (i = 0; i < sizeof(userconf) - 6; i++) {
sum += *uc++;
}
return sum;
}
void userconf_load()
{
uint8_t csum;
eeprom_read_bytes(0, 0, (uint8_t *)&userconf, sizeof(userconf));
csum = checksum();
if ((userconf.checkval != CHECKVAL) || (userconf.checksum != csum)) {
// config is invalid; reset to default
userconf.cursor_color = CONF_CURSOR_WHITE;
userconf.cursor_flash = 4; // default flash rate
userconf.ledprog_ena_mask = 0; // no programs enabled
userconf.ledprog_setting[0][0] = 2; // rainbow: angle top left to bottom right
userconf.ledprog_setting[0][1] = 25; // rainbow: spacing
userconf.checksum = checksum();
userconf.checkval = CHECKVAL;
}
}
void userconf_save()
{
userconf.checksum = checksum();
userconf.checkval = CHECKVAL;
// this eeprom writes in 16-byte pages only, so split up the data
eeprom_write_bytes(0, 0x00, (uint8_t *)&userconf + 0x00, 16);
eeprom_write_bytes(0, 0x10, (uint8_t *)&userconf + 0x10, 16);
}

View File

@@ -0,0 +1,50 @@
/*
* config.h
*
* Created on: Jul 27, 2024
* Author: true
*/
#ifndef USER_SRC_CONFIG_H_
#define USER_SRC_CONFIG_H_
#include <stdint.h>
#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 {
uint8_t cursor_color;
uint8_t cursor_flash;
uint8_t padding8;
uint8_t ledprog_ena_mask;
uint8_t ledprog_setting[5][2];
uint16_t checksum;
uint32_t checkval;
} UserConf; // 20 bytes
extern struct UserConf userconf;
void userconf_load();
void userconf_save();
#endif /* USER_SRC_CONFIG_H_ */

View File

@@ -0,0 +1,78 @@
/*
* Created on: Jul 27, 2024
*
* code taken from example WCH code, which was likely stolen from somewhere else.
* directly uses i2c peripheral in shitty way. no time to make this better.
* modified to support multiple "pages" (addresses) for 02/04/08/16 EEPROMs.
*
* ensure i2c bus has been configured before using these functions.
*/
#define EEPROM_BASE_ADDR (0xa0 >> 1)
#include <ch32v00x.h>
#include <stdint.h>
#include "eeprom.h"
#include "i2c.h"
/*********************************************************************
* @fn eeprom_read_byte
*
* @brief Read one data from EEPROM.
* @param page - Which 256-byte EEPROM page.
* @param addr - First read address.
* @param data - Pointer to byte(s) to write.
* @param len - Count of data to write.
*/
void eeprom_read_bytes(uint8_t page, uint8_t addr, uint8_t *data, uint8_t len)
{
page &= 0x7; // limit page to 3 bits (8 pages)
page |= EEPROM_BASE_ADDR; // add address to page
page <<= 1; // make i2c address from page
i2c_read_addr1b(page, addr, data, len);
}
/*********************************************************************
* @fn eeprom_write_bytes
*
* @brief Write one data to EEPROM.
* Note that some EEPROMs have buffers smaller than the page size.
*
* @param page - Which 256-byte EEPROM page.
* @param addr - Starting write address.
* @param data - Pointer to byte(s) to write.
* @param len - Count of data to write.
*/
void eeprom_write_bytes(uint8_t page, uint8_t addr, uint8_t *data, uint8_t len)
{
page &= 0x7; // limit page to 3 bits (8 pages)
page |= EEPROM_BASE_ADDR; // add address to page
page <<= 1; // make i2c address from page
// disable systick interrupt
NVIC_DisableIRQ(SysTicK_IRQn);
// write the data
#ifdef EEPROM_WP_PORT
EEPROM_WP_PORT->BCR = EEPROM_WP_PIN;
#endif
while (i2c_ack_poll(page));
i2c_write_addr1b(page, addr, data, len);
while (i2c_ack_poll(page));
#ifdef EEPROM_WP_PORT
EEPROM_WP_PORT->BSHR = EEPROM_WP_PIN;
#endif
// clear systick timer
SysTick->CNT = 0;
SysTick->SR = 0;
// re-enable systick interrupt
NVIC_EnableIRQ(SysTicK_IRQn);
}

View File

@@ -0,0 +1,27 @@
/*
* eeprom.h
*
* Created on: Jul 27, 2024
* Author: true
*/
#ifndef USER_SRC_EEPROM_H_
#define USER_SRC_EEPROM_H_
#include <ch32v00x.h>
#include <stdint.h>
#define EEPROM_WP_PORT GPIOC
#define EEPROM_WP_PIN GPIO_Pin_7
void eeprom_read_bytes(uint8_t page, uint8_t addr, uint8_t *data, uint8_t len);
void eeprom_write_bytes(uint8_t page, uint8_t addr, uint8_t *data, uint8_t len);
#endif /* USER_SRC_EEPROM_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 */

View File

@@ -0,0 +1,165 @@
/*
* 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 <ch32v00x.h>
#define I2C_TIMEOUT 0xffff
#define I2C_TIMEOUT_ACK_POLL 0x180
static uint32_t timeout;
void i2c_init()
{
GPIO_InitTypeDef gpio = {0};
I2C_InitTypeDef i2c = {0};
gpio.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
gpio.GPIO_Mode = GPIO_Mode_AF_OD;
gpio.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC, &gpio);
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, 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) {
while(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;
}

View File

@@ -0,0 +1,26 @@
/*
* Created on: Jul 27, 2024
*/
#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, 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_ */

View File

@@ -0,0 +1,66 @@
/*
* Created on: Jul 27, 2024
*/
#include "led.h"
#define FL3729_CS_OUTPUTS 15
#define LED_INIT_CURRENT 0
#define LED_INIT_SCALING 0xff
const uint8_t rgb_map[9] = {0x04, 0x14, 0x01, 0x11, 0x07, 0x0a, 0x1a, 0x0d, 0x1d};
const uint8_t cursor_map[3] = {0x17, 0x18, 0x19};
uint16_t rgb[9][3] = {0};
uint8_t cursor[3] = {0};
static uint8_t led_updated = 0;
void led_init()
{
uint8_t i;
uint8_t buf[FL3729_CS_OUTPUTS] = {0xff};
is31fl3729_init(FL3729_ADDR,
FL3729_CONF_SHDN_DIS | FL3729_CONF_OSDE_OFF | FL3729_CONF_MATRIX_2x16,
LED_INIT_CURRENT);
for (i = 0; i < FL3729_CS_OUTPUTS; i++) buf[i] = LED_INIT_SCALING;
is31fl3729_set_scaling_current_multi(FL3729_ADDR, buf, FL3729_CS_OUTPUTS);
}
void led_is_updated()
{
led_updated = 1;
}
void led_send()
{
uint8_t i;
uint8_t out[16*2];
// only render when there's something to render
if (led_updated) {
// stuff RGB outputs
for (i = 0; i < 9; i++) {
out[rgb_map[i] + 0] = rgb[i][0];
out[rgb_map[i] + 1] = rgb[i][1];
out[rgb_map[i] + 2] = rgb[i][2];
}
// stuff cursor outputs
out[cursor_map[0]] = cursor[0];
out[cursor_map[1]] = cursor[1];
out[cursor_map[2]] = cursor[2];
is31fl3729_set_outputs(FL3729_ADDR, 1, out, 16+15);
}
}

View File

@@ -0,0 +1,35 @@
/*
* led.h
*
* Created on: Jul 27, 2024
* Author: true
*/
#ifndef USER_SRC_LED_H_
#define USER_SRC_LED_H_
#include <stdint.h>
#include "31fl3729.h"
#define FL3729_ADDR (FL3729_BASE_ADDR | FL3729_ADPIN_GND)
extern uint16_t rgb[9][3];
extern uint8_t cursor[3];
void led_init();
void led_is_updated();
void led_send();
#endif /* USER_SRC_LED_H_ */

View File

@@ -0,0 +1,519 @@
/*
* led_rgbprog.c
*
* Created on: Jul 27, 2024
* Author: true
*/
#include <stdint.h>
#include "config.h"
#include "hsv2rgb.h"
#include "led.h"
#include "rand.h"
// led programs
void led_rgb_0_rainbow(uint8_t preview, uint8_t tick);
void led_rgb_1_lite_then_fade(uint8_t preview, uint8_t tick);
void led_rgb_2_twinkle(uint8_t preview, uint8_t tick);
void led_rgb_3_alternate(uint8_t preview, uint8_t tick);
void led_rgb_4_typing(uint8_t preview, uint8_t tick);
void ((*led_rgbprog[5])(uint8_t, uint8_t)) = {
led_rgb_0_rainbow,
led_rgb_1_lite_then_fade,
led_rgb_2_twinkle,
led_rgb_3_alternate,
led_rgb_4_typing
};
static uint8_t prog_rgb[3];
/*
* always have to have unicorn piss
*
* button: angle (45 degree shifts for each press)
* knob: spacing
*/
static uint16_t rainbow_hue = 0;
// probably some way to compute this but I can't think of it right now
// might take more codespace than this 40-byte LUT anyway
// encoding: LED1-LED2 LED3-LED4 LED5-MAX
// LED6-LED7 LED8-LED9
static const uint8_t rainbow_angles[8][5] = {
{0x12, 0x34, 0x55,
0x01, 0x23}, // bottom left to top right
{0x01, 0x23, 0x44,
0x01, 0x23}, // left to right
{0x01, 0x23, 0x44,
0x12, 0x34}, // top left to bottom right
{0x00, 0x00, 0x02,
0x22, 0x22}, // top to bottom
{0x43, 0x21, 0x04,
0x32, 0x10}, // top right to bottom left
{0x43, 0x21, 0x04,
0x43, 0x21}, // right to left
{0x43, 0x21, 0x04,
0x32, 0x10}, // bottom right to top left
{0x22, 0x22, 0x22,
0x00, 0x00} // bottom to top
};
static void rainbow_letter_next()
{
uint16_t h;
// add to our hue
h = userconf.ledprog_setting[0][1];
h <<= 2;
rainbow_hue += h;
rainbow_hue++;
rainbow_hue %= 256*6;
// make a color out of it
hsv2rgb_8b(rainbow_hue, 255, 255, &prog_rgb[0], &prog_rgb[1], &prog_rgb[2]);
}
void led_rgb_0_rainbow(uint8_t preview, uint8_t tick)
{
uint8_t i, j; // iterator
uint8_t n; // led index, remapped
uint8_t idx; // led index being updated, 0-5
uint8_t max; // max number of iterations for this angle
uint8_t angle; // fill direction from LUT
// run at half framerate
if (tick & 1) {
// no matter what, this puke is getting updated
led_is_updated();
// setup
angle = userconf.ledprog_setting[0][0];
angle &= 0x7;
max = rainbow_angles[angle][2] & 0x7;
// process outputs
for (i = 0; i <= max; i++) {
// get the next puke
rainbow_letter_next();
// apply to next set of LEDs
n = 0;
for (j = 0; j < 9; j++) {
n++;
if (j == 5) n++; // nybble index
idx = rainbow_angles[angle][n >> 1]; // get mask byte
if ((n & 1) == 0) idx >>= 4; // shift even values over
idx &= 0x7; // mask lowest 3 bits
if (idx == i) { // is this LED part of this pattern?
if (preview) { // are we in preview mode?
if (j != 0) { // is the first letter not the one selected?
if ((preview & 0x7f) == 0) {// is this preview selected?
if (j < 5) continue; // pass over any outputs that are not "TECH" letters
}
} else return; // preview is not selected, so we're done here
}
rgb[j][0] = prog_rgb[0]; // if so, copy the values
rgb[j][1] = prog_rgb[1];
rgb[j][2] = prog_rgb[2];
}
}
}
}
}
/*
* LEDs are lit from left to right, which fade out as it passes
* then top word then bottom word light, then fade out,
* and program repeats.
* button: fade rate
* knob: hue
*/
#define LITE_INTER_DWELL 20 // time between characters
#define LITE_IDLE_DWELL (256+64) // time before changing direction
#define LITE_PERSIST 10 // how long to keep each LED at fullbrite
static uint8_t lite_direction = 0;
static uint8_t lite_idx = 0;
static uint16_t lite_timeout = 0;
static uint8_t lite_persist[9] = {0};
void led_rgb_1_lite_then_fade(uint8_t preview, uint8_t tick)
{
uint16_t i, j;
uint8_t start, end;
uint16_t hue = userconf.ledprog_setting[1][1];
uint8_t rate = userconf.ledprog_setting[1][0];
// user sets the color
i = 255;
if (hue >= 254) {
// force white
i = 0;
}
hue *= 6;
hsv2rgb_8b(hue, i, 255, &prog_rgb[0], &prog_rgb[1], &prog_rgb[2]);
// make the rate adjustment a bit more intuitive
if (rate >= 254) rate = 255;
else rate >>= 2;
// fade out anything that's already lit at user's rate
for (i = 0; i < 9; i++) {
if (lite_persist[i]) lite_persist[i]--;
else for (j = 0; j < 2; j++) {
if (rgb[i][j] > rate) rgb[i][j] -= rate; else rgb[i][j] = 0;
}
}
// light it up
if (!lite_timeout) {
if (!lite_direction) {
// left to right
// light RETRO
if (lite_idx < 5) {
rgb[lite_idx][0] = prog_rgb[0];
rgb[lite_idx][1] = prog_rgb[1];
rgb[lite_idx][2] = prog_rgb[2];
lite_persist[lite_idx] = LITE_PERSIST;
}
// light TECH
if (lite_idx < 4) {
rgb[lite_idx + 5][0] = prog_rgb[0];
rgb[lite_idx + 5][1] = prog_rgb[1];
rgb[lite_idx + 5][2] = prog_rgb[2];
lite_persist[lite_idx + 5] = LITE_PERSIST;
}
} else {
// up to down
if (lite_idx < 2) {
if (!lite_idx) {
start = 0;
end = 5;
} else {
start = 5;
end = 9;
}
for (i = start; i < end; i++) {
rgb[i][0] = prog_rgb[0];
lite_persist[i] = LITE_PERSIST;
}
}
}
// next light
lite_idx++;
end = lite_direction ? 2 : 5;
// after idle phase, start over in next direction
if (lite_idx > end) {
lite_idx = 0;
lite_direction++;
lite_direction &= 1;
}
lite_timeout = lite_direction ? LITE_INTER_DWELL*2 : LITE_INTER_DWELL;
// at idle phase?
if (lite_idx == end) {
lite_timeout = LITE_IDLE_DWELL;
}
}
lite_timeout--;
}
/*
* LEDs are on at some idle level, and random letters will
* twinkle by intensifying and shifting hue slightly by a
* random small amount.
* button: saturation
* knob: flashing intensity
*/
#define TWINKLE_PERSIST 3;
#define TWINKLE_RGB_VAL 240
uint8_t twinkle_set[9] = {0};
void led_rgb_2_twinkle(uint8_t preview, uint8_t tick)
{
uint8_t i, j;
uint32_t rnd;
uint8_t sat;
// set
rnd = prng_get8();
if (rnd >= userconf.ledprog_setting[2][1]) {
// yup, we're doing it
rnd = prng_get16();
rnd *= 1536;
rnd <<= 16;
sat = userconf.ledprog_setting[2][0];
if (sat >= 128) sat = 128 - (sat - 128);
sat <<= 1;
hsv2rgb_8b(rnd, sat, TWINKLE_RGB_VAL, &prog_rgb[0], &prog_rgb[1], &prog_rgb[2]);
rnd = prng_get16() & 0xfff;
rnd /= 455;
rgb[rnd][0] = prog_rgb[0];
rgb[rnd][1] = prog_rgb[1];
rgb[rnd][2] = prog_rgb[2];
twinkle_set[rnd] = TWINKLE_PERSIST;
}
// decay
for (i = 0; i < 9; i++) {
twinkle_set[i]--;
for (j = 0; j < 2; j++) {
if (twinkle_set[i]) rgb[i][j]--;
else rgb[i][j] >>= 1;
}
}
}
/*
* alternates flashing the words between two colors.
* button: offset degrees (360/16)
* knob: hue (alternate hue will be either 90 or 180 degrees offset)
*/
void led_rgb_3_alternate(uint8_t preview, uint8_t tick)
{
uint8_t i, j;
uint16_t hue = userconf.ledprog_setting[3][1] * 6;
uint8_t offset = (userconf.ledprog_setting[3][0] & 0xf) * 16 * 6;
uint8_t is_flashing = 0;
if (userconf.cursor_color == CONF_CURSOR_OFF) {
// flash at ~2Hz
tick >>= 5;
if (tick & 1) is_flashing = 1;
} else {
// match cursor
if (cursor[userconf.cursor_color]) {
is_flashing = 1;
}
}
// set hues
for (i = 0; i < 2; i++) {
hsv2rgb_8b(hue, 255, 255, &prog_rgb[0], &prog_rgb[1], &prog_rgb[2]);
hue += offset;
hue %= 1536;
if (is_flashing) {
// set TECH
for (j = 5; j < 9; j++) {
rgb[j][0] = prog_rgb[0];
rgb[j][1] = prog_rgb[1];
rgb[j][2] = prog_rgb[2];
}
} else {
// set RETRO
for (j = 0; j < 5; j++) {
rgb[j][0] = prog_rgb[0];
rgb[j][1] = prog_rgb[1];
rgb[j][2] = prog_rgb[2];
}
}
is_flashing++;
is_flashing &= 1;
}
}
/*
* a green, orange or white phosphor sort of "typing,"
* with quick but not instant fade-in and flashing of
* an active character.
* the only program that interferes with the cursor.
* after "typing" and filling, the characters fade then
* the program repeats.
*
* no user config for this program
*/
#define TYPING_CHAR_DELAY_MIN 12 // about 1/10 of a second
#define TYPING_CHAR_DELAY_MAX 500 // just shy of 4 seconds
static const uint8_t idle_glow[3] = {12, 8, 8};
static const uint8_t typing[3][3] = {
{255, 240, 240}, // white
{ 0, 240, 0}, // green
{255, 96, 12}, // orange
};
static uint8_t typing_idx;
static uint16_t typing_char_delay;
static uint8_t typing_flash_state;
static uint8_t typing_flash_delay;
static uint8_t typing_fadeout;
void led_rgb_4_typing(uint8_t preview, uint8_t tick)
{
uint8_t i, j;
uint8_t w;
uint16_t s;
uint8_t color;
// match cursor color (well, approximate, or go green)
color = userconf.cursor_color;
if (color == CONF_CURSOR_OFF) color = CONF_CURSOR_GREEN;
// set flash
if (!typing_flash_delay) {
typing_flash_state++;
typing_flash_state &= 1;
typing_flash_delay = userconf.cursor_flash;
}
typing_flash_delay--;
// set rgb colors as appropriate
for (i = 0; i < 9; i++) {
if (i < typing_idx || ((i == typing_idx) && (typing_flash_state))) {
// these are on
rgb[i][0] = typing[color][0];
rgb[i][1] = typing[color][1];
rgb[i][2] = typing[color][2];
}
if (i > typing_idx || ((i == typing_idx) && (!typing_flash_state))) {
// these are idle
rgb[i][0] = idle_glow[0];
rgb[i][1] = idle_glow[1];
rgb[i][2] = idle_glow[2];
}
// flashing cursor fadeout
if (i == typing_idx && typing_flash_state) {
if (typing_flash_delay <= 8) {
w = 8 - typing_flash_delay;
rgb[i][0] >>= w; if (rgb[i][0] < idle_glow[0]) rgb[i][0] = idle_glow[0];
rgb[i][1] >>= w; if (rgb[i][1] < idle_glow[1]) rgb[i][1] = idle_glow[1];
rgb[i][2] >>= w; if (rgb[i][2] < idle_glow[2]) rgb[i][2] = idle_glow[2];
}
}
}
// set cursor as appropriate
for (i = 0; i < 2; i++) {
cursor[i] = 0;
}
// at the cursor
if (typing_idx >= 9) {
cursor[color] = typing_flash_state ? 240 : 12;
if (typing_flash_delay <= 8) {
w = 8 - typing_flash_delay;
cursor[color] >>= w;
if (cursor[color] < 12) cursor[color] = 12;
}
}
// decay all the characters
if (typing_idx == 10) {
if (typing_fadeout >= 3) typing_fadeout--;
else typing_fadeout = 0;
for (i = 0; i < 9; i++) {
for (j = 0; j < 2; j++) {
s = rgb[i][j];
s *= typing_fadeout;
rgb[i][j] = s >> 8;
}
}
s = cursor[color];
s *= typing_fadeout;
cursor[color] = s >> 8;
}
// are we on the next character?
if (!(--typing_char_delay)) {
// next character
typing_idx++;
if (typing_idx == 9) {
// at the cursor, we wait two max periods
typing_char_delay = TYPING_CHAR_DELAY_MAX << 1;
} else if (typing_idx == 10) {
// at the end, we wait half of one max period
typing_char_delay = TYPING_CHAR_DELAY_MAX >> 1;
} else {
typing_char_delay = prng_scale16(TYPING_CHAR_DELAY_MIN, TYPING_CHAR_DELAY_MAX);
}
if (typing_idx > 10) typing_idx = 0;
// reset cursor fade (used in last step)
typing_fadeout = 0xff;
// reset flash state and delay to reset cursor flash
// comment this code to keep persistent cursor rate
typing_flash_state = 0;
typing_flash_delay = 0;
}
}
/*
* initialize settings for programs before they run
* this can be run when changing programs to set them
* to a known state before starting over
*/
void led_rgb_firstrun()
{
uint8_t i;
for (i = 0; i < 9; i++) {
rgb[i][0] = 0;
rgb[i][1] = 0;
rgb[i][2] = 0;
}
// program 0: rainbow
// nothing to set
// program 1: lite then fade
lite_direction = 0;
lite_idx = 0;
lite_timeout = 0;
for (i = 0; i < 9; i++) {
lite_persist[i] = 0;
}
// program 2: twinkle
for (i = 0; i < 9; i++) twinkle_set[i] = 0;
// program 3: alternating
// nothing to set
// program 4: typing
typing_idx = 0;
typing_char_delay = prng_scale16(TYPING_CHAR_DELAY_MIN, TYPING_CHAR_DELAY_MAX);
typing_flash_state = 0;
typing_flash_delay = userconf.cursor_flash;
}

View File

@@ -0,0 +1,26 @@
/*
* led_rgbprog.h
*
* Created on: Jul 27, 2024
* Author: true
*/
#ifndef USER_SRC_LED_RGBPROG_H_
#define USER_SRC_LED_RGBPROG_H_
#define LED_RGBPROG_NORMAL 0
#define LED_RGBPROG_PREVIEW 0x80
extern void ((*led_rgbprog[5])(uint8_t, uint8_t));
void led_rgb_firstrun();
#endif /* USER_SRC_LED_RGBPROG_H_ */

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)
{
uint16_t rnd;
rnd = prng_get16();
rnd *= (max - min);
rnd >>= 16;
rnd += min;
return rnd;
}

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 */

View File

@@ -0,0 +1,552 @@
/*
* Created on: Jul 27, 2024
*
* super basic interface for the buttons
* renderer for the cursor (always 50% duty cycle)
* interface for the rgb programs
*/
#include <stdint.h>
#include "adc.h"
#include "btn.h"
#include "config.h"
#include "led.h"
#include "led_rgbprog.h"
#include "rand.h"
#define MODE_RUN 0
#define MODE_PROGRAM 1
#define MODE_PARAMETER 2
#define MIN_GC 4
#define MAX_GC 60
#define UI_CONF_SAVE_TIMEOUT 160
#define UI_PROG_RUNTIME_MIN (128*15) // 15 seconds
#define UI_PROG_RUNTIME_MAX (128*120) // 120 seconds
static uint8_t mode = MODE_RUN;
static uint8_t tick = 0;
static uint16_t editor_timeout_timer = 0;
static uint8_t target_gc = 0;
static uint8_t cursor_flash = 0;
static uint8_t cursor_state = 0xff;
static const uint16_t cursor_flash_rates[] = { // off-to-on flash rates in 1/256 second increments
255, // (0.5 on-off/second)
191, // (0.75 on-off/second)
127, // (1.0 on-off/second)
85, // C64 rate (1.5 on-off/second)
68, // Windows default (530ms on-off)
41, // PC MDA (3.114 on-off/second)
34, // PC CGA/EGA (3.745 on-off/second)
29, // PC VGA text mode (4.38 on-off/second)
};
static uint8_t rgb_prog_idx = 0; // currently running rgbled program index
static uint16_t rgb_prog_timer = 0; // timeout until this program is done and switches
static uint8_t rgb_prog_is_editing = 0; // currently editing a program's parameters
static uint8_t preview_idx = 0; // currently selected program preview index
static uint8_t config_save_timer;
void ui_btn_push_cb(uint8_t idx)
{
uint8_t i;
// are both buttons pushed?
if ((btn[0]._mask & BTN_PUSH) && (btn[1]._mask & BTN_PUSH)) {
// are none held?
if (!(btn[0]._mask & BTN_HOLD) && !(btn[1]._mask & BTN_HOLD)) {
// can only enter programming mode if the cursor is flashing
if (userconf.cursor_color != CONF_CURSOR_OFF) {
// put both buttons into ignore mode, so hold and release events
// do not fire
btn[0]._mask |= BTN_IGNORE;
btn[1]._mask |= BTN_IGNORE;
// both buttons are pushed at the same time quickly, and not held
// this will toggle modes
mode++;
if (mode > MODE_PARAMETER) {
mode = MODE_RUN;
}
// reset any LED program
led_rgb_firstrun();
// configure the preview index to be the first available
// depending on the mode
if (mode == MODE_PROGRAM) preview_idx = 0;
if (mode == MODE_PARAMETER) {
preview_idx = 0;
for (i = 0; i < 5; i++) {
if (userconf.ledprog_ena_mask & (1 << preview_idx)) {
preview_idx = i;
break;
}
}
}
return;
}
}
}
// do normal button actions
switch (mode) {
/* button logic flow:
*
*
* mode changes happen by pressing both buttons.
*
*
* NORMAL MODE: cursor is flashing.
*
* cursor changes are committed to EEPROM a couple of seconds after changing is done.
*
* if cursor is off, programming mode cannot be entered.
*
* - top button: changes cursor color (white, green, orange, off)
* - bot button: changes cursor flash rate. press and hold while not off to
* enter program change mode.
*/
/*
* PROGRAM CHANGE MODE: cursor is off.
*
*
* letters in RETRO show enabled programs. by default, none are enabled.
* enabled programs are bright. disabled ones are dim.
* selected program will flash. if on, will flash brightly. if off, it will flash dimly.
*
* any program which is enabled can run. the active program randomly changes after random delays.
*
* letters in TECH preview the currently selected program.
* letters in RETRO will each preview their own program (any flash / twinkle will always have idle light)
*
* - top button: tap enables or disables the program. push and hold does nothing.
* - bot button: tap selects the next program index. push and hold enters parameter change mode.
*
*
* PARAMETER CHANGE MODE: cursor is on.
*
* letters in RETRO flash to show currently selected program.
* selected program will flash. if a program has been disabled, nothing will light for that letter.
*
* letters in TECH preview the current program, at some fixed brightness.
*
* - top button: increment some parameter, with loop (speed, etc)
* - bot button: selects the next program
* - knob: changes some parameter (color/hue, etc)
*
* settings are saved when the cursor returns to flashing from any other mode.
*
* settings are restored to default if the top button is held while powering on.
* restored settings are not committed until entering programming mode
*/
}
}
void ui_btn_hold_cb(uint8_t idx)
{
}
void ui_btn_release_cb(uint8_t idx)
{
uint8_t i;
uint8_t w;
switch (mode) {
/* button logic flow:
*
*
* mode changes happen by pressing both buttons.
*/
/*
* NORMAL MODE: cursor is flashing (or off if disabled).
*
* cursor changes are committed to EEPROM about a second after changing is done.
*
* if cursor is off, programming mode cannot be entered.
*
* any program which is enabled will run. the active program randomly changes after random delays.
*/
case MODE_RUN: {
switch (idx) {
case 0: { // - top button: changes cursor color (white, green, orange, off)
userconf.cursor_color++;
userconf.cursor_color &= 0x3;
config_save_timer = UI_CONF_SAVE_TIMEOUT;
break;
}
case 1: { // - bot button: changes cursor flash rate. press and hold
// while cursor is not off to enter program change mode.
userconf.cursor_flash++;
userconf.cursor_flash &= 0x7;
config_save_timer = UI_CONF_SAVE_TIMEOUT;
break;
}
}
break;
}
/*
* PROGRAM CHANGE MODE: cursor is on.
*
* letters in RETRO show programs. by default, all are disabled.
* enabled programs are bright. disabled programs are dim.
* selected program will flash. if program is on, will flash brightly. if off, it will flash dimly.
*
* letters in TECH preview the currently selected program.
* letters in RETRO will each preview their own program (any flash / twinkle will always have idle light)
*/
case MODE_PROGRAM: {
switch (idx) {
case 0: { // - top button: tap enables or disables the program. push and hold does nothing.
w = userconf.ledprog_ena_mask;
if (userconf.ledprog_ena_mask & (1 << preview_idx)) {
userconf.ledprog_ena_mask &= ~(1 << preview_idx);
} else {
userconf.ledprog_ena_mask |= (1 << preview_idx);
}
break;
}
case 1: { // - bot button: tap selects the next program index. push and hold does nothing.
preview_idx++;
if (preview_idx >= 5) preview_idx = 0;
break;
}
}
break;
}
/*
* PARAMETER CHANGE MODE: cursor is off (or rather, very dimly on).
*
* letters in RETRO only show enabled programs. dark for disabled programs.
* RETRO letters will flash to indicate currently selected program.
*
* letters in TECH preview the current program, at some fixed brightness.
*
*
*
* - knob: changes some parameter (color/hue, etc)
*/
case MODE_PARAMETER: {
switch (idx) {
case 0: { // - top button: tapped or held: increment some parameter, with loop (speed, etc)
userconf.ledprog_setting[preview_idx][0]++;
break;
}
case 1: { // - bot button: when editing disabled: selects the next program
// when editing enabled: decrements some parameter, with loop (speed, etc)
// held: enables / disables editing the program (done in other callback)
if (rgb_prog_is_editing) {
if (userconf.ledprog_setting[preview_idx][0]) {
userconf.ledprog_setting[preview_idx][0]--;
} else {
userconf.ledprog_setting[preview_idx][0] = 0xff;
}
} else {
// select new program, except the last program, since that doesn't have a user editable config
w = preview_idx;
for (i = 0; i < 4; i++) {
w++;
if (w > 4) w = 0;
if (userconf.ledprog_ena_mask & (1 << w)) {
preview_idx = w;
break;
}
}
}
break;
}
}
break;
}
}
}
void ui_cursor_flash()
{
uint8_t color, flash;
uint8_t level = 0;
color = userconf.cursor_color;
flash = userconf.cursor_flash;
switch (mode) {
case MODE_RUN: {
if (rgb_prog_idx != 4) {
// initial setup
if (cursor_state == 0xff) {
cursor_state = 0;
goto ui_cursor_flash_setup;
}
// wind down counter
cursor_flash--;
// set brightness of led, if we're in an on state
if (cursor_state && (color < CONF_CURSOR_OFF)) {
// first frame of on = not quite full brightness
level = (cursor_flash == cursor_flash_rates[flash] - 1) ? 160 : 255;
// at final frames, dim
if (cursor_flash < 4) {
level >>= (cursor_flash << 1);
}
// set the level on the cursor
if (cursor[color] != level) {
cursor[color] = level;
led_is_updated();
}
}
if (!cursor_flash) {
// toggle on/off
cursor_state++;
cursor_state &= 1;
ui_cursor_flash_setup:
// set new cursor rate
cursor_flash = cursor_flash_rates[flash] >> 1;
// set all colors off
cursor[0] = cursor[1] = cursor[2] = 0;
}
}
break;
}
case MODE_PROGRAM: {
// cursor is always on bright
cursor[color] = 255;
break;
}
case MODE_PARAMETER: {
// cursor is always on dim
cursor[color] = 16;
break;
}
}
}
void ui_init()
{
btn[0].hold = 330 >> 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 = 330 >> 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()
{
uint32_t t;
uint8_t i;
uint8_t w;
uint8_t flash;
// deal with eeprom
if (config_save_timer) {
config_save_timer--;
if (config_save_timer) {
userconf_save();
}
}
// flash rate at 2 times/second
flash = tick >> 5;
flash &= 0x3;
if (editor_timeout_timer) {
editor_timeout_timer--;
if (!editor_timeout_timer) {
mode = MODE_RUN;
// save settings to eeprom
userconf_save();
}
}
switch (mode) {
case MODE_RUN: { // render an existing program
tick++;
// set brightness from knob 64 times/second
// (the value actually updates less frequently, but we need to fade nicely)
if (tick & 3 == 3) {
w = adc_get_pot() >> 2;
if (!w) {
if (target_gc) target_gc--; // full off = full off.
} else if (w < target_gc) {
if (target_gc > MIN_GC) target_gc--; // dim to min possible
} else if (w > target_gc) {
if (target_gc < MAX_GC) target_gc++; // dim to max possible
}
is31fl3729_set_global_current(FL3729_ADDR, target_gc);
}
led_rgbprog[rgb_prog_idx](LED_RGBPROG_NORMAL, tick);
// fade and change programs depending on the timer
rgb_prog_timer--;
if (rgb_prog_timer <= 17) {
if (rgb_prog_timer > 9) {
// fade out current program
w = (rgb_prog_timer & 0x1f) - 10;
for (i = 0; i < 9; i++) {
rgb[i][0] >>= w;
rgb[i][1] >>= w;
rgb[i][2] >>= w;
}
} else if (rgb_prog_timer > 7) {
// select a new random program
if (rgb_prog_timer == 8) {
w = prng_get8();
w &= 0x3;
if (w == rgb_prog_idx) w++;
rgb_prog_idx = w;
led_rgb_firstrun();
}
// clear out for now, since new program hasn't actually been run
for (i = 0; i < 9; i++) {
rgb[i][0] = 0;
rgb[i][1] = 0;
rgb[i][2] = 0;
}
} else if (rgb_prog_timer >= 1) {
// fade in new program
w = 8 - (rgb_prog_timer & 0x1f);
for (i = 0; i < 9; i++) {
rgb[i][0] >>= w;
rgb[i][1] >>= w;
rgb[i][2] >>= w;
}
} else { // 0
// randomize next program timing
t = prng_get16();
t *= (UI_PROG_RUNTIME_MAX - UI_PROG_RUNTIME_MIN);
t >>= 16;
rgb_prog_timer = t + UI_PROG_RUNTIME_MIN;
}
}
break;
}
case MODE_PROGRAM: {
// always postpone config saving
config_save_timer = UI_CONF_SAVE_TIMEOUT;
// always force rendering
led_is_updated();
for (i = 0; i < 5; i++) {
// render
led_rgbprog[i](LED_RGBPROG_PREVIEW | preview_idx, tick);
if (preview_idx == i) {
// flash the selected output
if (flash & 1) {
rgb[i][0] >>= 5;
rgb[i][1] >>= 5;
rgb[i][2] >>= 5;
}
} else {
// dim inactive outputs
if (!(userconf.ledprog_ena_mask & (1 << i))) {
rgb[i][0] >>= 3;
rgb[i][1] >>= 3;
rgb[i][2] >>= 3;
}
}
}
break;
}
case MODE_PARAMETER: {
// always postpone config saving
config_save_timer = UI_CONF_SAVE_TIMEOUT;
// always force rendering
led_is_updated();
// deal with knob if editing is active
if (rgb_prog_is_editing) {
userconf.ledprog_setting[preview_idx][1] = adc_get_pot();
}
for (i = 0; i < 5; i++) {
// render
if (userconf.ledprog_ena_mask & (1 << i)) {
led_rgbprog[i](LED_RGBPROG_PREVIEW | preview_idx, tick);
if (preview_idx == i) {
// flash the selected output
if (flash & 1) {
rgb[i][0] >>= 5;
rgb[i][1] >>= 5;
rgb[i][2] >>= 5;
}
} else {
// dim the other outputs
rgb[i][0] >>= 2;
rgb[i][1] >>= 2;
rgb[i][2] >>= 2;
}
} else {
// clear the output if it isn't enabled
rgb[i][0] = 0;
rgb[i][1] = 0;
rgb[i][2] = 0;
}
}
break;
}
}
ui_cursor_flash();
}

View File

@@ -0,0 +1,18 @@
/*
* ui.h
*
* Created on: Jul 27, 2024
* Author: true
*/
#ifndef USER_SRC_UI_H_
#define USER_SRC_UI_H_
void ui_init();
void ui_render();
#endif /* USER_SRC_UI_H_ */

View File

@@ -0,0 +1,449 @@
/********************************** (C) COPYRIGHT *******************************
* File Name : system_ch32v00x.c
* Author : WCH
* Version : V1.0.0
* Date : 2022/08/08
* Description : CH32V00x Device Peripheral Access Layer System Source File.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/
#include <ch32v00x.h>
/*
* Uncomment the line corresponding to the desired System clock (SYSCLK) frequency (after
* reset the HSI is used as SYSCLK source).
* If none of the define below is enabled, the HSI is used as System clock source.
*/
//#define SYSCLK_FREQ_8MHz_HSI 8000000
#define SYSCLK_FREQ_24MHZ_HSI HSI_VALUE
//#define SYSCLK_FREQ_48MHZ_HSI 48000000
//#define SYSCLK_FREQ_8MHz_HSE 8000000
//#define SYSCLK_FREQ_24MHz_HSE HSE_VALUE
//#define SYSCLK_FREQ_48MHz_HSE 48000000
/* Clock Definitions */
#ifdef SYSCLK_FREQ_8MHz_HSI
uint32_t SystemCoreClock = SYSCLK_FREQ_8MHz_HSI; /* System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_24MHZ_HSI
uint32_t SystemCoreClock = SYSCLK_FREQ_24MHZ_HSI; /* System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_48MHZ_HSI
uint32_t SystemCoreClock = SYSCLK_FREQ_48MHZ_HSI; /* System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_8MHz_HSE
uint32_t SystemCoreClock = SYSCLK_FREQ_8MHz_HSE; /* System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_24MHz_HSE
uint32_t SystemCoreClock = SYSCLK_FREQ_24MHz_HSE; /* System Clock Frequency (Core Clock) */
#elif defined SYSCLK_FREQ_48MHz_HSE
uint32_t SystemCoreClock = SYSCLK_FREQ_48MHz_HSE; /* System Clock Frequency (Core Clock) */
#else
uint32_t SystemCoreClock = HSI_VALUE;
#endif
__I uint8_t AHBPrescTable[16] = {1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8};
/* system_private_function_proto_types */
static void SetSysClock(void);
#ifdef SYSCLK_FREQ_8MHz_HSI
static void SetSysClockTo_8MHz_HSI(void);
#elif defined SYSCLK_FREQ_24MHZ_HSI
static void SetSysClockTo_24MHZ_HSI(void);
#elif defined SYSCLK_FREQ_48MHZ_HSI
static void SetSysClockTo_48MHZ_HSI(void);
#elif defined SYSCLK_FREQ_8MHz_HSE
static void SetSysClockTo_8MHz_HSE(void);
#elif defined SYSCLK_FREQ_24MHz_HSE
static void SetSysClockTo_24MHz_HSE(void);
#elif defined SYSCLK_FREQ_48MHz_HSE
static void SetSysClockTo_48MHz_HSE(void);
#endif
/*********************************************************************
* @fn SystemInit
*
* @brief Setup the microcontroller system Initialize the Embedded Flash Interface,
* the PLL and update the SystemCoreClock variable.
*
* @return none
*/
void SystemInit (void)
{
RCC->CTLR |= (uint32_t)0x00000001;
RCC->CFGR0 &= (uint32_t)0xFCFF0000;
RCC->CTLR &= (uint32_t)0xFEF6FFFF;
RCC->CTLR &= (uint32_t)0xFFFBFFFF;
RCC->CFGR0 &= (uint32_t)0xFFFEFFFF;
RCC->INTR = 0x009F0000;
SetSysClock();
}
/*********************************************************************
* @fn SystemCoreClockUpdate
*
* @brief Update SystemCoreClock variable according to Clock Register Values.
*
* @return none
*/
void SystemCoreClockUpdate (void)
{
uint32_t tmp = 0, pllsource = 0;
tmp = RCC->CFGR0 & RCC_SWS;
switch (tmp)
{
case 0x00:
SystemCoreClock = HSI_VALUE;
break;
case 0x04:
SystemCoreClock = HSE_VALUE;
break;
case 0x08:
pllsource = RCC->CFGR0 & RCC_PLLSRC;
if (pllsource == 0x00)
{
SystemCoreClock = HSI_VALUE * 2;
}
else
{
SystemCoreClock = HSE_VALUE * 2;
}
break;
default:
SystemCoreClock = HSI_VALUE;
break;
}
tmp = AHBPrescTable[((RCC->CFGR0 & RCC_HPRE) >> 4)];
if(((RCC->CFGR0 & RCC_HPRE) >> 4) < 8)
{
SystemCoreClock /= tmp;
}
else
{
SystemCoreClock >>= tmp;
}
}
/*********************************************************************
* @fn SetSysClock
*
* @brief Configures the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers.
*
* @return none
*/
static void SetSysClock(void)
{
RCC->APB2PCENR |= RCC_APB2Periph_GPIOD;
GPIOD->CFGLR&=(~0xF0);
GPIOD->CFGLR|=0x80;
GPIOD->BSHR =0x2;
//GPIO_IPD_Unused();
#ifdef SYSCLK_FREQ_8MHz_HSI
SetSysClockTo_8MHz_HSI();
#elif defined SYSCLK_FREQ_24MHZ_HSI
SetSysClockTo_24MHZ_HSI();
#elif defined SYSCLK_FREQ_48MHZ_HSI
SetSysClockTo_48MHZ_HSI();
#elif defined SYSCLK_FREQ_8MHz_HSE
SetSysClockTo_8MHz_HSE();
#elif defined SYSCLK_FREQ_24MHz_HSE
SetSysClockTo_24MHz_HSE();
#elif defined SYSCLK_FREQ_48MHz_HSE
SetSysClockTo_48MHz_HSE();
#endif
/* If none of the define above is enabled, the HSI is used as System clock.
* source (default after reset)
*/
}
#ifdef SYSCLK_FREQ_8MHz_HSI
/*********************************************************************
* @fn SetSysClockTo_8MHz_HSI
*
* @brief Sets HSE as System clock source and configure HCLK, PCLK2 and PCLK1 prescalers.
*
* @return none
*/
static void SetSysClockTo_8MHz_HSI(void)
{
/* Flash 0 wait state */
FLASH->ACTLR &= (uint32_t)((uint32_t)~FLASH_ACTLR_LATENCY);
FLASH->ACTLR |= (uint32_t)FLASH_ACTLR_LATENCY_0;
/* HCLK = SYSCLK = APB1 */
RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV3;
}
#elif defined SYSCLK_FREQ_24MHZ_HSI
/*********************************************************************
* @fn SetSysClockTo_24MHZ_HSI
*
* @brief Sets System clock frequency to 24MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
*
* @return none
*/
static void SetSysClockTo_24MHZ_HSI(void)
{
/* Flash 0 wait state */
FLASH->ACTLR &= (uint32_t)((uint32_t)~FLASH_ACTLR_LATENCY);
FLASH->ACTLR |= (uint32_t)FLASH_ACTLR_LATENCY_0;
/* HCLK = SYSCLK = APB1 */
RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
}
#elif defined SYSCLK_FREQ_48MHZ_HSI
/*********************************************************************
* @fn SetSysClockTo_48MHZ_HSI
*
* @brief Sets System clock frequency to 48MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
*
* @return none
*/
static void SetSysClockTo_48MHZ_HSI(void)
{
/* Flash 0 wait state */
FLASH->ACTLR &= (uint32_t)((uint32_t)~FLASH_ACTLR_LATENCY);
FLASH->ACTLR |= (uint32_t)FLASH_ACTLR_LATENCY_1;
/* HCLK = SYSCLK = APB1 */
RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
/* PLL configuration: PLLCLK = HSI * 2 = 48 MHz */
RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC));
RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSI_Mul2);
/* Enable PLL */
RCC->CTLR |= RCC_PLLON;
/* Wait till PLL is ready */
while((RCC->CTLR & RCC_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
/* Wait till PLL is used as system clock source */
while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
{
}
}
#elif defined SYSCLK_FREQ_8MHz_HSE
/*********************************************************************
* @fn SetSysClockTo_8MHz_HSE
*
* @brief Sets System clock frequency to 56MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
*
* @return none
*/
static void SetSysClockTo_8MHz_HSE(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* Close PA0-PA1 GPIO function */
RCC->APB2PCENR |= RCC_AFIOEN;
AFIO->PCFR1 |= (1<<15);
RCC->CTLR |= ((uint32_t)RCC_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CTLR & RCC_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
RCC->APB2PCENR |= RCC_AFIOEN;
AFIO->PCFR1 |= (1<<15);
if ((RCC->CTLR & RCC_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
if (HSEStatus == (uint32_t)0x01)
{
/* Flash 0 wait state */
FLASH->ACTLR &= (uint32_t)((uint32_t)~FLASH_ACTLR_LATENCY);
FLASH->ACTLR |= (uint32_t)FLASH_ACTLR_LATENCY_0;
/* HCLK = SYSCLK = APB1 */
RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV3;
/* Select HSE as system clock source */
RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
RCC->CFGR0 |= (uint32_t)RCC_SW_HSE;
/* Wait till HSE is used as system clock source */
while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x04)
{
}
}
else
{
/*
* If HSE fails to start-up, the application will have wrong clock
* configuration. User can add here some code to deal with this error
*/
}
}
#elif defined SYSCLK_FREQ_24MHz_HSE
/*********************************************************************
* @fn SetSysClockTo_24MHz_HSE
*
* @brief Sets System clock frequency to 72MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
*
* @return none
*/
static void SetSysClockTo_24MHz_HSE(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* Close PA0-PA1 GPIO function */
RCC->APB2PCENR |= RCC_AFIOEN;
AFIO->PCFR1 |= (1<<15);
RCC->CTLR |= ((uint32_t)RCC_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CTLR & RCC_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
RCC->APB2PCENR |= RCC_AFIOEN;
AFIO->PCFR1 |= (1<<15);
if ((RCC->CTLR & RCC_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
if (HSEStatus == (uint32_t)0x01)
{
/* Flash 0 wait state */
FLASH->ACTLR &= (uint32_t)((uint32_t)~FLASH_ACTLR_LATENCY);
FLASH->ACTLR |= (uint32_t)FLASH_ACTLR_LATENCY_0;
/* HCLK = SYSCLK = APB1 */
RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
/* Select HSE as system clock source */
RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
RCC->CFGR0 |= (uint32_t)RCC_SW_HSE;
/* Wait till HSE is used as system clock source */
while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x04)
{
}
}
else
{
/*
* If HSE fails to start-up, the application will have wrong clock
* configuration. User can add here some code to deal with this error
*/
}
}
#elif defined SYSCLK_FREQ_48MHz_HSE
/*********************************************************************
* @fn SetSysClockTo_48MHz_HSE
*
* @brief Sets System clock frequency to 72MHz and configure HCLK, PCLK2 and PCLK1 prescalers.
*
* @return none
*/
static void SetSysClockTo_48MHz_HSE(void)
{
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/* Close PA0-PA1 GPIO function */
RCC->APB2PCENR |= RCC_AFIOEN;
AFIO->PCFR1 |= (1<<15);
RCC->CTLR |= ((uint32_t)RCC_HSEON);
/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CTLR & RCC_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
if ((RCC->CTLR & RCC_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}
if (HSEStatus == (uint32_t)0x01)
{
/* Flash 0 wait state */
FLASH->ACTLR &= (uint32_t)((uint32_t)~FLASH_ACTLR_LATENCY);
FLASH->ACTLR |= (uint32_t)FLASH_ACTLR_LATENCY_1;
/* HCLK = SYSCLK = APB1 */
RCC->CFGR0 |= (uint32_t)RCC_HPRE_DIV1;
/* PLL configuration: PLLCLK = HSE * 2 = 48 MHz */
RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_PLLSRC));
RCC->CFGR0 |= (uint32_t)(RCC_PLLSRC_HSE_Mul2);
/* Enable PLL */
RCC->CTLR |= RCC_PLLON;
/* Wait till PLL is ready */
while((RCC->CTLR & RCC_PLLRDY) == 0)
{
}
/* Select PLL as system clock source */
RCC->CFGR0 &= (uint32_t)((uint32_t)~(RCC_SW));
RCC->CFGR0 |= (uint32_t)RCC_SW_PLL;
/* Wait till PLL is used as system clock source */
while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08)
{
}
}
else
{
/*
* If HSE fails to start-up, the application will have wrong clock
* configuration. User can add here some code to deal with this error
*/
}
}
#endif

View File

@@ -0,0 +1,32 @@
/********************************** (C) COPYRIGHT *******************************
* File Name : system_ch32v00x.h
* Author : WCH
* Version : V1.0.0
* Date : 2022/08/08
* Description : CH32V00x Device Peripheral Access Layer System Header File.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/
#ifndef __SYSTEM_CH32V00x_H
#define __SYSTEM_CH32V00x_H
#ifdef __cplusplus
extern "C" {
#endif
extern uint32_t SystemCoreClock; /* System Clock Frequency (Core Clock) */
/* System_Exported_Functions */
extern void SystemInit(void);
extern void SystemCoreClockUpdate(void);
#ifdef __cplusplus
}
#endif
#endif /*__CH32V00x_SYSTEM_H */