421 lines
12 KiB
C
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
|
|
}
|
|
}
|
|
}
|