sc8-nametag/nametag8_CH592/user/main.c
2024-10-26 19:44:49 -07:00

421 lines
12 KiB
C

/*
* true's GAT Nametag
* Rushed SUPERCON 8 EDITION
* main MCU firmware
* 2024 true
*
* Hastily designed and rushed to completion for Supercon 8 on Nov 1, 2024.
*
* The original GAT Nametag used an 8-bit 8051 core MCU.
* Original code was hastily ported to the WP DC29 minibadge.
* This code is again hastily ported, and is based on the DC29 minibadge codebase.
* this is why things are so convoluted.
*
*
* the main MCU is responsible for the following:
* - rendering OLED UI, LED programs
* - storing fonts in flash
* - communicating with I2C devices. includes oled, accelerometer, eeprom, sub MCU, RGBLED
* - implementing USB interface (once I get time)
* - implementing BLE (maybe)
*
*/
#include <hw/ch32sub.h>
#include "CH59x_common.h"
#include "comm/i2c.h"
#include "hw/gat/gat_gpio.h"
#include "led/rgbled.h"
#include "misc/accel.h"
#include "misc/tinymt.h"
#include "ui/menu.h"
#include "ui/oled.h"
#include "global.h"
#include "port_intr.h"
#include "user_config.h"
// global settings
#define OLED_UPDATE_RATE 13 // framerate of OLED; { (256*(8/8)) / OLED_UPDATE_RATE }; 13 = 19FPS, 15 = 17FPS
// flags
#define FLAG_OLED_UPDATE (1 << 0)
#define FLAG_RGBLED_RUN (1 << 1)
#define FLAG_ACCEL_POLL (1 << 2)
#define FLAG_CH32V_RESEND_CONF (1 << 3)
#define FLAG_RGBLED_SEND (1 << 5)
#define PROG_TICK_RATE ((32768-8192-4096) / 256) // not sure why this value can't be 32768/256
// this was checked with a stopwatch and is close enough
// this value IS FRAMERATE DEPENDENT for some reason... figure it out later
const uint8_t vers[] = "241026b";
uint32_t cpu_use = 0;
uint32_t cpu_max = 0;
uint16_t cpu_pct = 0;
uint16_t cpu_pct_max = 0;
uint32_t uptime = 0;
uint16_t uptime_hour;
uint8_t uptime_min;
uint8_t uptime_sec;
uint32_t idle_time_menu;
uint32_t idle_time_still;
uint8_t idle_go_sleep;
static volatile uint8_t flags_lo = 0;
//static uint8_t flags_hi = FLAG_RESET_SYSTICK;
static uint8_t st_tick = 0; // program tick counter
static uint8_t oled_tick = 0; // oled framerate counter
void ch59x_xtal_conf()
{
HSECFG_Current(HSE_RCur_125);
HSECFG_Capacitance(HSECap_12p);
}
/*
* the CPU will change clockspeed depending on what it is doing.
* all hardware timers are tied to system clock and cannot be derived from
* the crystal directly. so we can't rely on timers or systick for timing.
*
* this leaves the RTC as the only periodic interrupt source which uses LSI clk.
* RTC has the capability to trigger an interrupt when count bits match a mask.
* we're not using the RTC for anything else, so RTC interrupt should suffice
* for our main tick interrupt.
*/
__HIGH_CODE
void rtc_reset_trig()
{
sys_safe_access_enable();
R8_RTC_FLAG_CTRL |= RB_RTC_TRIG_CLR;
R32_RTC_TRIG = 0;
R8_RTC_MODE_CTRL |= RB_RTC_LOAD_HI;
sys_safe_access_disable();
while((R32_RTC_TRIG & 0x3FFF) != (R32_RTC_CNT_DAY & 0x3FFF));
sys_safe_access_enable();
R32_RTC_TRIG = 0;
R8_RTC_MODE_CTRL |= RB_RTC_LOAD_LO;
sys_safe_access_disable();
RTC_TRIGFunCfg(PROG_TICK_RATE);
}
void rtcisr_init()
{
LClk32K_Cfg(Clk32K_LSI, ENABLE); // enable internal oscillator
Calibration_LSI(Level_128); // calibrate LSI fromm HSE
rtc_reset_trig();
PFIC_SetPriority(RTC_IRQn, 0x00); // rtc is highest priority interrupt
PFIC_EnableIRQ(RTC_IRQn);
}
/*
* used only for rough cpu usage calculation stuff
*/
void systick_init(void)
{
SysTick->CNT = 0; // clear counter
SysTick->CTLR = SysTick_CTLR_STCLK | SysTick_CTLR_STE; // enable counter in /1 mode
}
__HIGH_CODE
void oled_update_done()
{
int8_t rot;
// reset oled callback, clear screen, and set default pixel mode and font size
oled.callback = 0;
if (!(menu->flags & MENU_FLAG_NO_AUTOCLS)) {
ssd1306_cls(&oled);
}
ssd1306fb_set_color(SSD1306_STATE_SET_PIXEL);
oled.state &= ~SSD1306_STATE_STR_HALFWIDTH;
// orientation / flipping flags
rot = accel_get_rotation(&accel);
if ((rot > (96+4)) || (rot < (32-4))) {
sysflags &= ~SYS_OLED_ROTATE_X;
} else if ((rot > (32+4)) && (rot < (96-4))) {
sysflags |= SYS_OLED_ROTATE_X;
}
if ((rot > (64+4)) && (rot < 124)) {
sysflags &= ~SYS_OLED_ROTATE_Y;
} else if ((rot > 4) && (rot < (64-4))) {
sysflags |= SYS_OLED_ROTATE_Y;
}
if ((rot < 21) || (rot > (64 + 24))) {
sysflags &= ~SYS_OLED_REVERSE_CHARS;
} else if (rot > 24 && rot < (64 + 21)){
sysflags |= SYS_OLED_REVERSE_CHARS;
}
// power saving
// todo: don't hardcode the movement values
if (accel_get_movement() > 3) {
idle_go_sleep = 0;
idle_time_still = 0;
idle_time_menu = 0;
}
// start going to sleep if the option is enabled and if we've been still for enough time
if (uconf.sleep_timeout && (idle_time_still > uconf.sleep_timeout)) {
idle_go_sleep = 1;
}
#ifdef MENU_TIMEOUT_TO_NAMETAG
// root menu idle counting
// note: untested.
if (menu == &menu_0) {
if (!idle_time_menu) {
idle_time_menu = uptime;
} else if ((uptime - idle_time_menu) >= MENU_TIMEOUT_TO_NAMETAG) {
// been at the root menu too long.
// return to nametag
menu_stop(0);
idle_time_menu = 0;
}
} else {
idle_time_menu = 0;
}
#endif
// render menu
menu_tick();
// calculate CPU usage
cpu_pct = ((cpu_use * 100) / OLED_UPDATE_RATE) / cpu_max;
if (cpu_pct > cpu_pct_max) cpu_pct_max = cpu_pct;
}
__HIGH_CODE
void lowprio_task() {
uint32_t interrupt_flags;
while (1) {
// sleep when we're doing nothing
if (!flags_lo) {
__WFI();
}
// disable all IRQs when doing any I2C related stuff, as interrupts
// can corrupt transactions
SYS_DisableAllIrq(&interrupt_flags);
if (flags_lo & FLAG_ACCEL_POLL) {
flags_lo &= ~FLAG_ACCEL_POLL;
accel_poll();
}
// todo: implement proper sleep mode that saves power
// and wakes up on interrupt, instead of this
if (!idle_go_sleep) {
// temporary: re-send sub interrupts, sub button holds, enable rgb_hwen
if (flags_lo & FLAG_CH32V_RESEND_CONF) {
flags_lo &= ~FLAG_CH32V_RESEND_CONF;
ch32sub_intr_defaults();
btn_commit_hold();
ch32sub_rgb_hwen(1);
}
// process sub MCU interrupt, if pending
ch32sub_process();
if (flags_lo & FLAG_RGBLED_SEND) {
flags_lo &= ~FLAG_RGBLED_SEND;
rgbled_send();
}
// send the last oled frame data
if (flags_lo & FLAG_OLED_UPDATE) {
flags_lo &= ~FLAG_OLED_UPDATE;
if (oled.callback && !(oled.state & SSD1306_STATE_BUSY)) {
if (ssd1306_cb_get()) {
oled.callback();
}
}
// update
// only update this frame if we're not in the middle of starting up
if (uptime) {
// process other tasks
if (oled.state & SSD1306_STATE_INITIALIZED) {
oled.callback = oled_update_done;
}
ssd1306_update();
}
cpu_use = SysTick->CNT;
}
// re-enable IRQs
SYS_RecoverIrq(interrupt_flags);
// render new rgbled frame
// this is not included in cpu calcs :/
if (flags_lo & FLAG_RGBLED_RUN) {
flags_lo &= ~FLAG_RGBLED_RUN;
// make sure a valid program is selected
if (uconf.ledprog_rgb_idx > (sizeof(rgb_pgm) / sizeof(rgb_pgm[0]))) {
uconf.ledprog_rgb_idx = 0;
}
// now run a program
rgbled_runprog(st_tick >> 2);
}
} else {
switch (idle_go_sleep) {
case 1: {
idle_go_sleep++;
// just entered the mode? then turn things off
ssd1306_set_display(0);
uconf.flags |= UCONF_FLAGS_LEDS_DISABLE;
rgbled_send();
break;
}
default: {
// check to see if we should wake
if (accel_get_movement() > 3) {
idle_go_sleep = 0;
idle_time_still = 0;
idle_time_menu = 0;
// wake up things
ssd1306_set_display(1);
uconf.flags &= ~UCONF_FLAGS_LEDS_DISABLE;
rgbled_send();
}
break;
}
}
}
// re-enable IRQs
SYS_RecoverIrq(interrupt_flags);
// drop into lower clock rate
SetSysClock(SYSCLK_FREQ_IDLE);
}
}
int main()
{
// configure clock
ch59x_xtal_conf();
SetSysClock(SYSCLK_FREQ_USEI2C);
// note that system clock speed is decreased after every use of I2C.
PWR_DCDCCfg(ENABLE); // enable DC-DC converter; brings significant power saving
i2c_init(); // get i2c initialized since most stuff will need it
uconf_load(); // read the user config
ch32sub_init(); // initialize aux MCU
ch32sub_rgb_hwen(1); // and enable RGBLED controller hardware pin so the controller can wake up
tinymt32_init(&tinymt32_s, 1337); // let's get random
accel_init(); // initialize accelerometer
rgbled_init(); // initialize RGBLED controller
gat_gpio_init(); // configure GAT aux GPIOs, get gat ID
// todo: implement // configure GAT i2c slave
ssd1306fb_set_target(&oled); // start up the OLED and menu system
ssd1306_init(1); // we'll try to init later too, since sometimes OLED fails to init
menu_stop(0); // lol. yes, this "starts" the "menu" in nametag mode
systick_init(); // start up the system tick, used for CPU percentage calculation
cpu_max = GetSysClock() / PROG_TICK_RATE;
port_intr_init(); // configure port-based interrupts (used for ch32sub interrupt)
ch32sub_intr_defaults(); // configure interrupt sources on sub MCU - buttons, etc
rtcisr_init(); // configure main program tick interrupt
lowprio_task(); // start the low priority task loop
}
__INTERRUPT
__HIGH_CODE
void RTC_IRQHandler(void)
{
// clear interrupt flag and reset RTC
rtc_reset_trig();
// speed up
SetSysClock(SYSCLK_FREQ_NORMAL);
// manual uptime counter
st_tick++;
if (!st_tick) {
uptime++;
uptime_hour = (uint16_t)(uptime / 3600);
uptime_min = (uint8_t)((uptime / 60) % 60);
uptime_sec = (uint8_t)((uptime ) % 60);
// in case the sub MCU crashes or whatever, make sure state is consistent
if (uptime & 1) {
flags_lo |= FLAG_CH32V_RESEND_CONF;
}
idle_time_still++;
idle_time_menu++;
}
oled_tick++;
if (oled_tick >= OLED_UPDATE_RATE) {
oled_tick = 0;
SysTick->CNT = 0;
flags_lo |= FLAG_OLED_UPDATE;
}
// operations
switch (st_tick & 0x3) {
case 0: {
// send RGBLEDs, render new program
flags_lo |= FLAG_RGBLED_SEND;
flags_lo |= FLAG_RGBLED_RUN;
break;
}
case 1: {
flags_lo |= FLAG_ACCEL_POLL;
// no break
}
}
}