initial import and cleanup of WP DC29 nametag code

many things are different from WP DC29, including but not limited to:
- different vendor and core
- different peripherals
- no external serial number MCU, now have different i2c-communicated MCU
- different accelerometer
- different RGBLED count
- RGBLED controller instead of PWM controller
- soft I2C instead of hard I2C
- I2C EEPROM instead of SPI Flash

code has been "cleaned up" and some work done for porting. the code "builds" but most of the render code isn't actually used yet. there's still a lot to fix between render, UI, and incomplete implementations.
This commit is contained in:
true 2024-10-13 20:12:28 -07:00
parent 2b31b1b6b3
commit 6a4284176b
48 changed files with 7337 additions and 34 deletions

View File

@ -17,15 +17,57 @@
#define i2c_init() i2cm_init()
#define i2c_start() i2cm_start()
#define i2c_start() SetSysClock(CLK_SOURCE_PLL_32MHz); i2cm_start()
#define i2c_restart() i2cm_restart()
#define i2c_stop() i2cm_stop()
#define i2c_stop() SetSysClock(CLK_SOURCE_HSE_16MHz); i2cm_stop()
#define i2c_rd(ack) i2cm_rd(ack)
#define i2c_wr(dat) i2cm_wr(dat)
#define i2c_addr(a, w) i2c_start(); i2cm_addr(a, w)
#define i2c_rdbuf(d, x) i2cm_rdbuf(d, x)
#define i2c_wrbuf(d, x) i2cm_wrbuf(d, x)
#define i2c_rdbuf(d, s) i2cm_rdbuf(d, s); i2c_stop()
#define i2c_wrbuf(d, s) i2cm_wrbuf(d, s); i2c_stop()
#define i2c_read(a, d, s) i2c_start(); \
i2cm_addr(a, 1); \
i2cm_rdbuf(d, s); \
i2c_stop()
#define i2c_read_reg8(a, r, d, s) i2c_start(); \
i2cm_addr(a, 0); \
i2cm_wr(r); \
i2cm_restart(); \
i2cm_addr(a, 1); \
i2cm_rdbuf(d, s); \
i2c_stop()
#define i2c_read_reg16(a, r, d, s) i2c_start(); \
i2cm_addr(a, 0); \
i2cm_wr(r >> 8); \
i2cm_wr(r & 0xff); \
i2cm_restart(); \
i2cm_addr(a, 1); \
i2cm_rdbuf(d, s); \
i2c_stop()
#define i2c_write(a, d, s) i2c_start(); \
i2cm_addr(a, 0); \
i2cm_wrbuf(d, s); \
i2c_stop()
#define i2c_write_reg8(a, r, d, s) i2c_start(); \
i2cm_addr(a, 0); \
i2cm_wr(r); \
i2cm_wrbuf(d, s); \
i2c_stop()
#define i2c_write_reg16(a, r, d, s) i2c_start(); \
i2cm_addr(a, 0); \
i2cm_wr(r >> 8); \
i2cm_wr(r & 0xff); \
i2cm_wrbuf(d, s); \
i2c_stop()
#else

View File

@ -59,8 +59,8 @@ void i2cm_init()
sysclk = GetSysClock();
cycles = sysclk / 500000;
delay_hi = (cycles - CYCLES_TO_HI) / 4;
delay_lo = (cycles - CYCLES_TO_LO) / 4;
delay_hi = (cycles - CYCLES_TO_HI - 2) / 4;
delay_lo = (cycles - CYCLES_TO_LO - 2) / 4;
}
void i2cm_start()
@ -139,10 +139,10 @@ uint8_t i2cm_wr(uint8_t dat)
}
// use a left-aligned address with this implementation.
uint8_t i2cm_addr(uint8_t addr, uint8_t write)
uint8_t i2cm_addr(uint8_t addr, uint8_t reading_bit)
{
addr &= ~0x1;
addr |= write ? 0 : 1;
addr |= reading_bit ? 1 : 0;
return i2cm_wr(addr);
}
@ -150,10 +150,10 @@ uint8_t i2cm_addr(uint8_t addr, uint8_t write)
void i2cm_rdbuf(uint8_t *dat, uint8_t len)
{
while(len--) *dat++ = i2cm_rd(len > 0);
i2cm_stop();
// i2cm_stop();
}
void i2cm_wrbuf(uint8_t *dat, uint8_t len)
void i2cm_wrbuf(const uint8_t *dat, uint8_t len)
{
uint8_t nack;
@ -161,5 +161,5 @@ void i2cm_wrbuf(uint8_t *dat, uint8_t len)
nack = i2cm_wr(*dat++);
if (nack) break;
}
i2cm_stop();
// i2cm_stop();
}

View File

@ -45,10 +45,10 @@ void i2cm_stop();
uint8_t i2cm_rd(uint8_t ack);
uint8_t i2cm_wr(uint8_t dat);
uint8_t i2cm_addr(uint8_t addr, uint8_t write);
uint8_t i2cm_addr(uint8_t addr, uint8_t reading_bit);
void i2cm_rdbuf(uint8_t *dat, uint8_t len);
void i2cm_wrbuf(uint8_t *dat, uint8_t len);
void i2cm_wrbuf(const uint8_t *dat, uint8_t len);

View File

@ -0,0 +1,27 @@
/*
* global.h
*
* Created on: Oct 13, 2024
* Author: true
*/
#ifndef USER_GLOBAL_H_
#define USER_GLOBAL_H_
#include <stdint.h>
extern const uint8_t vers[];
extern uint8_t cpu_use;
extern uint8_t cpu_max;
extern uint32_t uptime;
extern uint16_t uptime_hour;
extern uint8_t uptime_min;
extern uint8_t uptime_sec;
#endif /* USER_GLOBAL_H_ */

View File

@ -158,7 +158,7 @@ enum aw20x_size {
#ifdef SOFT_I2C_MASTER
#define AW20X_I2C_busy() (0)
#define AW20X_I2C_writereg(adr, reg, buf, siz) i2c_addr(adr, 1); i2c_wr(reg); i2c_wrbuf(buf, siz);
#define AW20X_I2C_writereg(adr, reg, buf, siz) i2c_write_reg8(adr, reg, buf, siz)
#else
#define AW20X_I2C_busy() (0)
#define AW20X_I2C_writereg(adr, reg, buf, siz) i2c_write_addr1b(adr, reg, buf, siz);

View File

@ -14,12 +14,24 @@
#include "ch32sub.h"
#include "port_intr.h"
#include "../comm/i2c.h"
// interrupt callbacks
#include "ui/btn.h"
volatile uint8_t intr_flag = 0;
static void (*flag_cb[8])() = {
btn_intr,
0,
0,
0,
0,
0,
0,
0
};
void ch32sub_isr()
@ -30,10 +42,30 @@ void ch32sub_isr()
void ch32sub_process()
{
uint8_t flags;
uint8_t i;
if (intr_flag) {
intr_flag = 0;
flags = 1;
// get interrupt flags
i2c_read_reg16(SUB_I2C_ADDR, REG_INTR_FLAGS, &flags, 1);
while (flags) {
for (i = 0; i < 8; i++) {
if (intr_flag & (1 << i)) {
if (flag_cb[i]) flag_cb[i]();
else {
// unhandled interrupt; clear it
// ch32sub_write_1b(REG_INTR_FLAGS, i << 1);
}
}
}
// get interrupt flags (again)
i2c_read_reg16(SUB_I2C_ADDR, REG_INTR_FLAGS, &flags, 1);
}
}
}
@ -42,15 +74,37 @@ void ch32sub_init()
// configure interrupt pin as pull-up
// interrupt driven by CH32V003 is open drain active-lo
GPIOB_ModeCfg(SUB_INTR_PIN, GPIO_ModeIN_PU);
// register interrupt handler
port_intr_cb_register(PORT_INTR_GPIOB, SUB_INTR_PIN_NR, ch32sub_isr);
// configure interrupt to be rising edge
GPIOB_ITModeCfg(SUB_INTR_PIN, GPIO_ITMode_RiseEdge);
// register interrupt handler
// note: this is handled as "high priority" within the interrupt handler now.
// port_intr_cb_register(PORT_INTR_GPIOB, SUB_INTR_PIN_NR, ch32sub_isr);
}
// things to do
void ch32sub_read(uint16_t reg, uint8_t *dat, uint8_t len)
{
i2c_read_reg16(SUB_I2C_ADDR, reg, dat, len);
}
void ch32sub_write(uint16_t reg, uint8_t *dat, uint8_t len)
{
i2c_write_reg16(SUB_I2C_ADDR, reg, dat, len);
}
void ch32sub_write_1b(uint16_t reg, uint8_t dat)
{
i2c_start();
i2c_addr(SUB_I2C_ADDR, 0);
i2c_wr(reg >> 8);
i2c_wr(reg & 0xff);
i2c_wr(dat);
i2c_stop();
}
void ch32sub_rgb_hwen(uint8_t en)
{
i2c_addr(SUB_I2C_ADDR, 1);

View File

@ -12,6 +12,8 @@
#include "CH59x_common.h"
#include <stdint.h>
#include "../comm/i2c.h"
#define SUB_I2C_ADDR 0x5e
@ -25,11 +27,85 @@
#define SUB_INT_UTX_DONE (1 << 4)
#define SUB_INT_URX_RCVD (1 << 5)
#define INT_BTN (1 << 0)
#define INT_ACCEL (1 << 1)
#define INT_UTX_DONE (1 << 4)
#define INT_UTX_TIMEOUT (1 << 5)
#define INT_UTX_ERROR (1 << 6)
#define INT_URX_RCVD (1 << 7)
enum ch32sub_regmap {
// read-only
REG_INTR_FLAGS = 0x00,
REG_RSVD_01,
REG_BTN_PUSHED_LATCHED, // buttons currently pushed.
REG_BTN_PUSHED, // buttons pushed. reading will clear this value.
REG_BTN_HELD, // buttons currently being held. reading will clear this value.
REG_BTN_RELEASED, // buttons released. reading will clear this value.
REG_BTN1_MASK, // raw bitmask of button 1 state
REG_BTN2_MASK, // button interrupt is cleared on read of ANY REG_BTNx_MASK register
REG_BTN3_MASK,
REG_BTN4_MASK,
REG_BTN5_MASK,
REG_WERR_HI = 0x0c, // write error count, high byte
REG_WERR_LO, // write error count, low byte
REG_IRDA_READ_HI, // IrDA read packet length, hi byte (currently always 0)
REG_IRDA_READ_LO, // IrDA read packet length, lo byte
// read-write
REG_INTR_FLAGS_CLEAR = 0x10,
REG_INTR_ENABLE, // interrupt enable flags
REG_BTN_DEBOUNCE_TIME,
REG_BTN1_INT_ENABLE, // button 1 interrupt mask
REG_BTN2_INT_ENABLE, // button 2 interrupt mask
REG_BTN3_INT_ENABLE, // button 3 interrupt mask
REG_BTN4_INT_ENABLE, // button 4 interrupt mask
REG_BTN5_INT_ENABLE, // button 5 interrupt mask
REG_RGB_HWEN, // high = RGB_HWEN hi, low = RGB_HWEN low
REG_IRDA_TIMER = 0x1a, // IrDA read/write packet attempt time in 1/10 second increments (max ~25.5s sending time)
REG_IRDA_WRITE_RETRY, // IrDA write packet retry timeout in 1/100 second increments (max ~2.5s per retry)
REG_IRDA_MODE, // IrDA mode. 0 = idle, 1 = transmit, 2 = receive
REG_IRDA_WRITE_HI, // IrDA write packet length, hi byte (currently ignored)
REG_IRDA_WRITE_LO, // IrDA write packet length, lo byte
REG_BTN1_HOLD_HI = 0x20, // btn initial hold time in 2ms increments, hi byte
REG_BTN1_HOLD_LO, // btn initial hold time in 2ms increments, lo byte
REG_BTN2_HOLD_HI,
REG_BTN2_HOLD_LO,
REG_BTN3_HOLD_HI,
REG_BTN3_HOLD_LO,
REG_BTN4_HOLD_HI,
REG_BTN4_HOLD_LO,
REG_BTN5_HOLD_HI,
REG_BTN5_HOLD_LO,
REG_BTN1_REPEAT_HI = 0x30, // btn repeat time in 2ms increments, hi byte
REG_BTN1_REPEAT_LO, // btn repeat time in 2ms increments, lo byte
REG_BTN2_REPEAT_HI,
REG_BTN2_REPEAT_LO,
REG_BTN3_REPEAT_HI,
REG_BTN3_REPEAT_LO,
REG_BTN4_REPEAT_HI,
REG_BTN4_REPEAT_LO,
REG_BTN5_REPEAT_HI,
REG_BTN5_REPEAT_LO,
};
void ch32sub_init();
void ch32sub_isr();
void ch32sub_process();
void ch32sub_read(uint16_t reg, uint8_t *dat, uint8_t len);
void ch32sub_write(uint16_t reg, uint8_t *dat, uint8_t len);
void ch32sub_write_1b(uint16_t reg, uint8_t dat);
void ch32sub_rgb_hwen(uint8_t en);

View File

@ -0,0 +1,8 @@
/*
* lightsense.c
*
* Created on: Oct 13, 2024
* Author: true
*/

View File

@ -0,0 +1,13 @@
/*
* lightsense.h
*
* Created on: Oct 13, 2024
* Author: true
*/
#ifndef USER_HW_LIGHTSENSE_H_
#define USER_HW_LIGHTSENSE_H_
#endif /* USER_HW_LIGHTSENSE_H_ */

View File

@ -1,8 +1,260 @@
/*
* ssd1306.c
*
* Created on: Oct 11, 2024
* Author: true
* begin 20190505 true
* modified 20241013
*/
#include "ssd1306.h"
SSD1306 oled;
uint8_t oled_fb[((SSD1306_HEIGHT >> 3) * SSD1306_WIDTH) + 1];
uint8_t ssd1306_buf[8];
volatile uint8_t ssd1306_cb_pending;
volatile uint8_t ssd1306_cb_state;
const uint8_t ssd1306_cmd_init[] = {
0x00, // command mode, all following bytes are command data
// 0xae, // display off
0xd5, // set display clock div
0x80, // suggested ratio
0xa8, // set multiplex
0x1f, // 128x32 (0x3f = 128x64)
0xd3, // set display offset
0x00, // none
0x40, // set start line
0x8d, // set charge pump
0x14, // internal VCC
0x20, // set memory addressing mode
0x00, // horizontal mode
0xa1, // segremap
0xc8, // comscandec
0xda, // set compins
0x02, //
0x81, // set contrast
0xa0, // good default value
0xd9, // set precharge
0xf1, // internal vcc
0xdb, // set vcomdeselect
0x30, //
0x2e, // disable scroll
0xa6, // non-inverted display
0xa4 // resume display operations
};
const uint8_t ssd1306_cmd_disp_window_def[] = {
SSD1306_MODE_CMD,
SSD1306_CMD_COLUMNADDR, // columns: 0 to (width - 1)
0,
SSD1306_WIDTH - 1,
SSD1306_CMD_PAGEADDR, // pages (rows*8): 0 to ((height / 8) - 1)
0,
(SSD1306_HEIGHT >> 3) - 1
};
void ssd1306_cb(uint8_t p)
{
ssd1306_cb_pending = 1;
ssd1306_cb_state = p;
}
uint8_t ssd1306_cb_get()
{
uint8_t ret;
ret = ssd1306_cb_pending;
ssd1306_cb_pending = 0;
return ret;
}
void ssd1306_idle_wait()
{
/*
uint16_t timeout = 2000;
while (!ssd1306_idle()) {
timeout--;
if (!timeout) {
#ifdef SSD1306_RESET_ON_COMM_FAIL
// todo: update reset source
// RSTSRC |= RSTSRC_SWRSF__SET;
#else
// well fuck.
// reboot the oled
shiftreg0_auxpwr(0);
shiftreg_update();
timeout = 8000;
while (timeout--) { asm("nop"); }
shiftreg0_auxpwr(1);
shiftreg_update();
// and reset i2c bus (and eventually oled)
i2c_err_isr(0);
#endif
}
}
*/
}
/*
* sends a single command byte.
*/
void ssd1306_cmd(uint8_t cmd)
{
uint8_t *buf = ssd1306_buf;
*buf++ = SSD1306_MODE_CMD;
*buf++ = cmd;
ssd1306_write(ssd1306_buf, 2, 0);
ssd1306_idle_wait();
}
/*
* sets up the SSD1306.
* does not clear the screen, turn on the screen, or send data.
* you may want to use the callback for that.
*/
void ssd1306_init_step3()
{
ssd1306_set_display(1);
oled.callback = 0;
oled.state = SSD1306_STATE_INITIALIZED | SSD1306_STATE_SET_PIXEL;
}
void ssd1306_init_step2()
{
ssd1306_cls(&oled);
ssd1306_update();
oled.callback = ssd1306_init_step3;
}
void ssd1306_init(uint8_t display_off_during_init)
{
oled.width = SSD1306_WIDTH;
oled.height = SSD1306_HEIGHT;
oled.state = 0;
oled.mode = oled_fb;
oled.fb = oled_fb + 1;
if (display_off_during_init) {
ssd1306_set_display(1);
}
ssd1306_write(ssd1306_cmd_init, (sizeof(ssd1306_cmd_init)), ssd1306_cb);
if (display_off_during_init) {
oled.callback = ssd1306_init_step2;
} else {
oled.callback = ssd1306_init_step3;
}
}
void ssd1306_update()
{
// reset our drawing window
ssd1306_write(ssd1306_cmd_disp_window_def, (sizeof(ssd1306_cmd_disp_window_def)), 0);
ssd1306_idle_wait();
// write our framebuffer
// since our i2c routine is state driven, and we let the state machine handle it,
// we can't just send some static data (in this case, first byte selecting mode)
// and then send our framebuffer. our display requires sending the comm mode
// byte first before receiving framebuffer data. so we waste a byte to do this...
// alternative would be to rewrite i2c routines to handle this...
*oled.mode = SSD1306_MODE_DATA;
ssd1306_write(oled.mode, ((oled.height >> 3) * oled.width) + 1, ssd1306_cb);
}
void ssd1306_cls(SSD1306 *o)
{
uint16_t len;
uint8_t *buf;
len = ((o->height >> 3) * o->width);
buf = o->fb;
while (len--) {
*buf++ = 0;
}
}
void ssd1306_set_display(uint8_t display)
{
uint8_t *buf = ssd1306_buf;
*buf++ = SSD1306_MODE_CMD;
*buf++ = display ? SSD1306_CMD_DISPLAY_ON : SSD1306_CMD_DISPLAY_OFF;
ssd1306_write(ssd1306_buf, 2, 0);
ssd1306_idle_wait();
}
void ssd1306_set_invert(uint8_t invert)
{
uint8_t *buf = ssd1306_buf;
*buf++ = SSD1306_MODE_CMD;
*buf++ = invert ? SSD1306_CMD_INVERT_ON : SSD1306_CMD_INVERT_OFF;
ssd1306_write(ssd1306_buf, 2, 0);
ssd1306_idle_wait();
}
void ssd1306_set_contrast(uint8_t contrast)
{
uint8_t *buf = ssd1306_buf;
*buf++ = SSD1306_MODE_CMD;
*buf++ = SSD1306_CMD_CONTRAST;
*buf++ = contrast;
ssd1306_write(ssd1306_buf, 3, 0);
ssd1306_idle_wait();
}
void ssd1306_set_flip(uint8_t flip)
{
uint8_t *buf = ssd1306_buf;
*buf++ = SSD1306_MODE_CMD;
*buf++ = flip ? SSD1306_CMD_COMSCANINC : SSD1306_CMD_COMSCANDEC;
ssd1306_write(ssd1306_buf, 2, 0);
ssd1306_idle_wait();
if (flip) {
oled.state |= SSD1306_STATE_FLIP;
} else {
oled.state &= ~SSD1306_STATE_FLIP;
}
}
void ssd1306_set_mirror(uint8_t mirror)
{
uint8_t *buf = ssd1306_buf;
*buf++ = SSD1306_MODE_CMD;
*buf++ = mirror ? SSD1306_CMD_SEGREMAP : SSD1306_CMD_SEGREMAP_MIRROR;
ssd1306_write(ssd1306_buf, 2, 0);
ssd1306_idle_wait();
if (mirror) {
oled.state |= SSD1306_STATE_MIRROR;
} else {
oled.state &= ~SSD1306_STATE_MIRROR;
}
}
void ssd1306_set_flipmirror(uint8_t flipm)
{
ssd1306_set_flip(flipm);
ssd1306_set_mirror(flipm);
}

View File

@ -1,8 +1,6 @@
/*
* ssd1306.h
*
* Created on: Oct 11, 2024
* Author: true
* ssd1306.h 494
* begin 20190505 true
*/
#ifndef USER_HW_SSD1306_H_
@ -10,4 +8,102 @@
#include <stdint.h>
#include "../comm/i2c.h"
#define SSD1306_I2C_ADDR 0x3c
#define SSD1306_WIDTH 128
#define SSD1306_HEIGHT 32
#define SSD1306_EXTERNALVCC 0x01
#define SSD1306_SWITCHCAPVCC 0x02
#define SSD1306_MODE_CMD 0x00
#define SSD1306_MODE_DATA 0x40
#define SSD1306_MODE_SINGLE 0x80
#define SSD1306_CMD_CONTRAST 0x81
#define SSD1306_CMD_DISPLAY_ALLON_OFF 0xA4
#define SSD1306_CMD_DISPLAY_ALLON 0xA5
#define SSD1306_CMD_INVERT_OFF 0xA6
#define SSD1306_CMD_INVERT_ON 0xA7
#define SSD1306_CMD_DISPLAY_OFF 0xAE
#define SSD1306_CMD_DISPLAY_ON 0xAF
#define SSD1306_CMD_DISPLAYOFFSET 0xD3
#define SSD1306_CMD_SETCOMPINS 0xDA
#define SSD1306_CMD_SETVCOMDETECT 0xDB
#define SSD1306_CMD_DISPLAYCLOCKDIV 0xD5
#define SSD1306_CMD_PRECHARGE 0xD9
#define SSD1306_CMD_MULTIPLEX 0xA8
#define SSD1306_CMD_LOWCOLUMN 0x00
#define SSD1306_CMD_HIGHCOLUMN 0x10
#define SSD1306_CMD_STARTLINE 0x40
#define SSD1306_CMD_MEMORYMODE 0x20
#define SSD1306_CMD_COLUMNADDR 0x21
#define SSD1306_CMD_PAGEADDR 0x22
#define SSD1306_CMD_COMSCANINC 0xC0
#define SSD1306_CMD_COMSCANDEC 0xC8
#define SSD1306_CMD_SEGREMAP 0xA0
#define SSD1306_CMD_SEGREMAP_MIRROR 0xA1
#define SSD1306_CMD_CHARGEPUMP 0x8D
#define SSD1306_STATE_INITIALIZED (1 << 0)
#define SSD1306_STATE_BUSY (1 << 1)
#define SSD1306_STATE_FLIP (1 << 2)
#define SSD1306_STATE_MIRROR (1 << 3)
#define SSD1306_STATE_STR_HALFWIDTH (1 << 4)
#define SSD1306_STATE_PIXEL_MASK (0xC0)
#define SSD1306_STATE_SET_PIXEL (1 << 6)
#define SSD1306_STATE_CLR_PIXEL (0)
#define SSD1306_STATE_INVERT_PIXEL (1 << 7)
typedef struct SSD1306 {
char state;
uint8_t width;
uint8_t height;
int8_t cursor_x;
int8_t cursor_y;
uint8_t *mode;
uint8_t *fb;
void (*callback)();
} SSD1306;
extern SSD1306 oled;
extern uint8_t oled_fb[((SSD1306_HEIGHT >> 3) * SSD1306_WIDTH) + 1];
#define ssd1306_write(dat, len, cb) i2c_addr(SSD1306_I2C_ADDR, 1); i2c_wrbuf(dat, len); if (cb) ssd1306_cb(0)
#define ssd1306_idle() 0
void ssd1306_idle_wait();
void ssd1306_cb(uint8_t p); // can be used to fake a callback event
uint8_t ssd1306_cb_get();
void ssd1306_init(uint8_t display_off_during_init);
void ssd1306_update();
void ssd1306_cls(SSD1306 *o);
void ssd1306_set_display(uint8_t display);
void ssd1306_set_invert(uint8_t invert);
void ssd1306_set_contrast(uint8_t contrast);
void ssd1306_set_flip(uint8_t flip);
void ssd1306_set_mirror(uint8_t mirror);
void ssd1306_set_flipmirror(uint8_t flipm);
#endif /* USER_DEVICE_SSD1306_H_ */

View File

@ -0,0 +1,134 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016 B. Stultiens
* modified by true for 12-bit values
*
* 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(uint16_t h, uint16_t s, uint16_t v, uint16_t *r, uint16_t *g , uint16_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.
*/
}
}
void hsv2rgb_32b(uint16_t h, uint16_t s, uint16_t v, uint16_t *r, uint16_t *g , uint16_t *b)
{
HSV_MONOCHROMATIC_TEST(s, v, r, g, b); // Exit with grayscale if s == 0
uint8_t 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
/*
* Bottom level: v * (1.0 - s)
* --> (v * (255 - s) + error_corr + 1) / 256
*/
uint16_t ww; // Intermediate result
ww = v * (255 - s); // We don't use ~s to prevent size-promotion side effects
ww += 1; // Error correction
ww += ww >> 8; // Error correction
*b = ww >> 8;
uint8_t h_fraction = h & 0xff; // 0...255
uint32_t d; // Intermediate result
if(!(sextant & 1)) {
// *r = ...slope_up...;
d = v * (uint32_t)((255 << 8) - (uint16_t)(s * (256 - h_fraction)));
d += d >> 8; // Error correction
d += v; // Error correction
*r = d >> 16;
} else {
// *r = ...slope_down...;
d = v * (uint32_t)((255 << 8) - (uint16_t)(s * h_fraction));
d += d >> 8; // Error correction
d += v; // Error correction
*r = d >> 16;
}
}

View File

@ -0,0 +1,132 @@
/*
* 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.
*/
#ifndef __INC_HSV2RGB_H__
#define __INC_HSV2RGB_H__
#include <stdint.h>
typedef struct color_rgb {
uint16_t r;
uint16_t g;
uint16_t b;
} color_rgb;
typedef struct color_hsv {
uint16_t h;
uint16_t s;
uint16_t v;
} color_hsv;
#define HSV2RGB_BITS 8 // 10
#define HSV2RGB_COUNT ((1 < HSV2RGB_BITS)-1)
#define HSV_HUE_SEXTANT (1 << HSV2RGB_BITS)
#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 */
#define hsv2rgb(h,s,v,r,g,b) hsv2rgb_32b(h,s,v,r,g,b)
void hsv2rgb_8b(uint16_t h, uint16_t s, uint16_t v, uint16_t *r, uint16_t *g , uint16_t *b);
void hsv2rgb_32b(uint16_t h, uint16_t s, uint16_t v, uint16_t *r, uint16_t *g , uint16_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 { uint16_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)
#define HSV_MONOCHROMATIC_TEST(s,v,r,g,b) \
do { \
if(!(s)) { \
*(r) = *(g) = *(b) = (v); \
return; \
} \
} while(0)
#endif

View File

@ -12,14 +12,37 @@
#include <stdint.h>
#include "hsv2rgb.h"
#include "../comm/i2c.h"
#include "../hw/ch32sub.h"
#define RGB_EDGE_COUNT 10
typedef struct LedProgram {
char *name;
void (*prog)(uint8_t *, uint16_t);
} LedProgram;
extern const uint8_t edge_map[10];
extern const LedProgram edge_pgm[6];
extern color_hsv hsv_edge[RGB_EDGE_COUNT];
void rgbled_init();
void rgb_edge_update(uint8_t idx);
#endif /* USER_LED_RGBLED_H_ */

View File

@ -0,0 +1,700 @@
/*
* $Id: rgbled_edge.c 500 2021-08-08 19:43:38Z true $
* begin 20210720 true
*
* programs to run to show neat shit on the LEDs
* programs may be duplicated with minimal changes between eyes and edge
* this is fine right now - we have the space
* note also that some programs may change once the LEDs run at higher bit depth
*
* programs I'd like to get done:
* - anything involving gravity, and using accelerometer
* - things that will do color fades between colors; quickest hack would be implementing rgb2hsv
*/
#include "rgbled.h"
#include "hw/lis2dw.h"
#include "../misc/intscale.h"
#include "../misc/sin7.h"
#include "../misc/tinymt.h"
#include "user_config.h"
const uint8_t edge_map[10] = {3, 5, 7, 9, 0, 2, 4, 6, 8, 1};
static uint8_t timeout;
void edge_solid(uint8_t *a, uint16_t tick)
{
// 1=bitfield, 2=timeout-set, 4=state, 5=hue, 6=sat, 7=val
// bitfield:
// 7: blank / off
// 3: override altcolor (if bit 1 is not set)
// 1: override hsv
// 0: flash
int i;
color_hsv hsv = {0, 0, 0};
// timeout
if (!timeout || (timeout > a[2])) {
timeout = a[2];
} else {
timeout--;
return;
}
if (a[1] & 0x80) {
// override off
hsv.v = 0;
} else {
// color select
if (a[1] & 0x02) {
hsv.h = a[5] * 6;
hsv.s = a[6];
hsv.v = a[7];
} else if (a[1] & 0x08) {
hsv.h = uconf.altcolor_hue * 6;
hsv.s = uconf.altcolor_sat;
hsv.v = uconf.altcolor_val;
} else {
hsv.h = uconf.favcolor_hue * 6;
hsv.s = uconf.favcolor_sat;
hsv.v = uconf.favcolor_val;
}
// flash
if (a[1] & 0x01) {
a[4] ^= 0x01;
if (a[4] & 0x01) hsv.v = 0;
}
}
// update
hsv_edge[0].h = hsv.h;
hsv_edge[0].s = hsv.s;
hsv_edge[0].v = hsv.v;
for (i = 1; i < RGB_EDGE_COUNT; i++) {
hsv_edge[i].h = hsv_edge[0].h;
hsv_edge[i].s = hsv_edge[0].s;
hsv_edge[i].v = hsv_edge[0].v;
}
}
// todo: improve fading smoothness by doing fadeout every callback instead of on timeout
// this can be done once LED bit depth is increased
void edge_flicker(uint8_t *a, uint16_t tick)
{
// 0=speed, 1=bitfield, 2=timeout-set, 4=state, 6=max-val, 7=min-val
// bitfield:
// 7: min-val is not half range (0-255); if unset min-val is half range (128-255)
// 6: apply a random hue instead of favcolor
// 5: do not apply LED remapping (results in a cross update pattern)
// 4: fades out all LEDs at speed setting (only if bit 1 SET)
// 3: same flicker throughout (only if bit 1 NOT SET)
// 2: make the LED random instead of sequential (only if bit 1 SET)
// 1: only process one LED per update
// 0: in single LED mode, reverse direction
// speed: fadeout speed
int i;
uint16_t new_val;
uint8_t min, max;
// timeout
if (!timeout || (timeout > a[2])) {
timeout = a[2];
} else {
timeout--;
return;
}
// set constants
hsv_edge[0].h = uconf.favcolor_hue * 6;
hsv_edge[0].s = uconf.favcolor_sat;
if (a[6]) max = a[6]; else max = 255;
// set value limit
if (a[1] & 0x80) {
min = a[7];
} else {
min = (a[7] >> 1) | 0x80;
}
// process and update LEDs
for (i = 0; i < RGB_EDGE_COUNT; i++) {
// compute new val on first round or if all-same flicker is disabled
if (!(a[1] & 0x08) || i == 0) {
new_val = u16_scale((prng_get16() & 0xff), 0, 255, min, max);
new_val = (uconf.favcolor_val * new_val) / 256;
}
// only doing one LED?
if (a[1] & 0x02) {
// is it random?
if (a[1] & 0x04) {
i = prng_get16() % RGB_EDGE_COUNT;
} else {
// nope, sequential
a[4]++;
a[4] %= RGB_EDGE_COUNT;
i = a[4] % RGB_EDGE_COUNT;
// is it in reverse?
if (a[1] & 0x01) {
i = RGB_EDGE_COUNT - 1 - i;
}
// correct position
if (!(a[1] & 0x20)) {
i = edge_map[i];
}
}
}
// apply
hsv_edge[i].h = (a[1] & 0x40) ? (prng_get16() & 0x600) : hsv_edge[0].h;
hsv_edge[i].s = hsv_edge[0].s;
hsv_edge[i].v = new_val & 0xff;
// bail if only doing one LED
if (a[1] & 0x02) {
// but make sure to fade LEDs if needed
if (a[1] & 0x10) {
for (i = 0; i < RGB_EDGE_COUNT; i++) {
if (hsv_edge[i].v <= a[0]) {
hsv_edge[i].v = 0;
} else {
hsv_edge[i].v -= a[0];
}
}
}
return;
}
}
}
uint8_t edge_circles_divider = 0;
uint8_t edge_circles_sec_ticks = 0;
void edge_circles(uint8_t *a, uint16_t tick)
{
// 0=speed, 1=bitfield, 2=timeout-set, 4=state, 5=hue, 6=sat, 7=val
// bitfield:
// 7: apply specified hue instead of favcolor
// 6: apply a random hue
// 5: trails desaturate
// 4: secondary value is half of primary
// 3: opposing is same hue as primary (unset is alt color hue/sat; is always primary val)
// 2: enable opposing ball
// 1: enable fading trails
// 0: direction
// others:
// a[0][7:4]: fade rate of trails
// a[0][0:3]: how many ticks until extra increment on secondary; 0 disables, 0xf sets opposing
int i;
uint16_t h, h2;
uint8_t s, v;
uint8_t x, y;
uint8_t s2;
uint8_t trailfade = a[0] >> 4;
uint8_t desatfade = a[1] & 0x20;
// fading
edge_circles_divider++;
edge_circles_divider %= 10;
if (!edge_circles_divider && (a[1] & 0x02)) {
for (i = 0; i < RGB_EDGE_COUNT; i++) {
if (hsv_edge[i].v <= trailfade) {
hsv_edge[i].v = 0;
} else {
hsv_edge[i].v -= trailfade;
}
if (desatfade) {
// fade to white too
if (hsv_edge[i].s <= (trailfade >> 1)) {
hsv_edge[i].s = 0;
} else {
hsv_edge[i].s -= (trailfade >> 1);
}
}
}
}
// timeout
if (!timeout || (timeout > a[2])) {
timeout = a[2];
} else {
timeout--;
return;
}
uint8_t srate = a[0] & 0x0f;
uint8_t dir = a[1] & 0x01;
uint8_t trail = a[1] & 0x02;
uint8_t second = a[1] & 0x04;
uint8_t scolor = a[1] & 0x08;
uint8_t secval = a[1] & 0x10;
uint8_t rndhue = a[1] & 0x40;
uint8_t sethue = a[1] & 0x80;
// state
x = (a[4] & 0xf) + 1;
x %= 10;
y = (a[4] >> 4) + 9;
if (second) {
if (srate == 0xf) {
// opposing mode
y = x + 5;
y %= 10;
} else if (srate) {
// gradual offset mode
srate <<= 2;
edge_circles_sec_ticks++;
if (edge_circles_sec_ticks > srate) {
edge_circles_sec_ticks = 0;
y += 9;
}
}
}
y %= 10;
// save state
a[4] = (y << 4) | x;
// apply direction and secondary
if (dir) {
x = 9 - x;
y = 9 - y;
}
// colors
s = s2 = a[6];
v = a[7];
h2 = uconf.altcolor_hue * 6;
if (sethue) {
h = a[5] * 6;
} else if (rndhue) {
h = prng_get16() & 0x5ff;
} else {
h = uconf.favcolor_hue * 6;
s = uconf.favcolor_sat;
v = uconf.favcolor_val;
s2 = uconf.altcolor_sat;
}
// secondary
if (scolor) {
h2 = h;
s2 = s;
}
// set the next item
hsv_edge[edge_map[x]].h = h;
hsv_edge[edge_map[x]].s = s;
hsv_edge[edge_map[x]].v = v;
if (second && x != y) {
if (secval) v >>= 1;
hsv_edge[edge_map[y]].h = h2;
hsv_edge[edge_map[y]].s = s2;
hsv_edge[edge_map[y]].v = v;
}
// clear those that are on if trails are not enabled
if (!trail) {
for (i = 0; i < RGB_EDGE_COUNT; i++) {
if (i != x || (second && (i != y))) {
hsv_edge[edge_map[i]].v = 0;
}
}
}
}
uint8_t edge_waving_divider = 0;
void edge_waving(uint8_t *a, uint16_t tick)
{
// 0=wait-delay, 1=bitfield, 2=timeout-set, 456=work
// bitfield:
// 5 trails desaturate
// 1 enable fading trails
// others:
// a[0][7:4]: fade rate of trails
// a[0][0:3]: movement rate of up-down (stall)
int i;
uint8_t trailfade = a[0] >> 4;
uint8_t desatfade = a[1] & 0x20;
edge_waving_divider++;
edge_waving_divider %= 10;
if (!edge_waving_divider && (a[1] & 0x02)) {
for (i = 0; i < RGB_EDGE_COUNT; i++) {
if (hsv_edge[i].v <= trailfade) {
hsv_edge[i].v = 0;
} else {
hsv_edge[i].v -= trailfade;
}
if (desatfade) {
// fade to white too
if (hsv_edge[i].s <= (trailfade >> 1)) {
hsv_edge[i].s = 0;
} else {
hsv_edge[i].s -= (trailfade >> 1);
}
}
}
}
uint8_t stall = a[0] & 0x0f;
// timeout
if (!timeout || (timeout > a[2])) {
timeout = a[2];
} else {
timeout--;
return;
}
// fix
if (a[4] > 3) a[4] = 0;
if (a[5]) a[5]--;
// clear values if trails not enabled
if (!(a[1] & 0x02)) {
for (i = 0; i < RGB_EDGE_COUNT; i++) {
hsv_edge[i].v = 0;
}
}
switch (a[4]) {
case 0:
case 2: { // waiting state
if (!a[5]) {
// next step
a[6] = 0;
a[4]++;
}
break;
}
case 1: { // moving down
hsv_edge[edge_map[ a[6]]].h = uconf.favcolor_hue * 6;
hsv_edge[edge_map[ a[6]]].s = uconf.favcolor_sat;
hsv_edge[edge_map[ a[6]]].v = uconf.favcolor_val;
hsv_edge[edge_map[9 - a[6]]].h = uconf.favcolor_hue * 6;
hsv_edge[edge_map[9 - a[6]]].s = uconf.favcolor_sat;
hsv_edge[edge_map[9 - a[6]]].v = uconf.favcolor_val;
a[6]++;
if (a[6] >= 5) {
a[5] = stall;
a[4]++;
break;
}
break;
}
case 3: { // moving up
hsv_edge[edge_map[4 - a[6]]].h = uconf.favcolor_hue * 6;
hsv_edge[edge_map[4 - a[6]]].s = uconf.favcolor_sat;
hsv_edge[edge_map[4 - a[6]]].v = uconf.favcolor_val;
hsv_edge[edge_map[5 + a[6]]].h = uconf.favcolor_hue * 6;
hsv_edge[edge_map[5 + a[6]]].s = uconf.favcolor_sat;
hsv_edge[edge_map[5 + a[6]]].v = uconf.favcolor_val;
a[6]++;
if (a[6] >= 5) {
a[5] = stall;
a[4]++;
break;
}
break;
}
}
}
void edge_rainbow(uint8_t *a, uint16_t tick)
{
// 0=angle-rate, 1=bitfield, 2=timeout-set, 45=angle-work, 6=sat, 7=val
// bitfield:
// 7:4 hue angle division (360 divided by 1-6; all other values mean 1, except 0 which means no offset)
// 3
// 2
// 1
// 0 direction
uint16_t *rb_angle = (uint16_t *)&a[4];
uint16_t angle;
uint16_t hoffset;
int i;
int r;
// timeout
if (!timeout || (timeout > a[2])) {
timeout = a[2];
} else {
timeout--;
return;
}
// rainbow hue angle increment
// todo: in single mode, decrement instead if direction bit set
*rb_angle += (a[0]);
if (*rb_angle >= 0x600) *rb_angle -= 0x600;
angle = *rb_angle;
// hue offset
switch (a[1] >> 4) {
case 0: { hoffset = 0; break; } // time stands still
case 2: { hoffset = 0x4d; break; } // 0xa * 0x4d * 2 = 0x604
case 3: { hoffset = 0x33; break; } // 0xa * 0x33 * 3 = 0x5fa
case 4: { hoffset = 0x26; break; } // 0xa * 0x26 * 4 = 0x5f0
case 5: { hoffset = 0x1f; break; } // 0xa * 0x1f * 5 = 0x60e
case 6: { hoffset = 0x1a; break; } // 0xa * 0x1a * 6 = 0x618
default: { hoffset = 0x9a; break; } // 0xa * 0x9a * 1 = 0x604
}
// apply to LEDs
for (i = 0; i < RGB_EDGE_COUNT; i++) {
r = (a[1] & 0x01) ? i : RGB_EDGE_COUNT - 1 - i;
hsv_edge[edge_map[r]].h = angle;
hsv_edge[edge_map[r]].s = a[6];
hsv_edge[edge_map[r]].v = a[7];
angle += hoffset;
if (angle >= 0x600) angle -= 0x600;
}
}
void edge_copmode(uint8_t *a, uint16_t tick)
{
// 0=work, 1=bitfield, 2=timeout-set, 3=timeout-work, 4=work, 56=steps, 7=val
// bitfield:
// 7 sync to tick 0
// 6 skip pattern 2
// 5 skip pattern 1
// 4 don't override sat/val to maximums (not implemented)
// 3 use different colors on either half during pattern 2
// 2 use different colors on either half during pattern 1
// 1 repeat subpattern 1
// 0 use cop colors instead of pri/alt
// others:
// a[5][7:4] amount of time to delay in pattern 2
// a[5][0:3] amount of times to repeat pattern 2 (solid color alternate)
// a[6][7:4] amount of strobe pulses in pattern 1
// a[6][0:3] amount of times to repeat pattern 1 (strobes)
int i;
uint8_t pattern, iter;
uint8_t *seq;
uint8_t w, x;
uint8_t b;
uint8_t cnt[4];
color_hsv hsv;
color_hsv hsv2;
color_hsv pri, sec;
// synchronize
if (a[1] & 0x80) {
if (!tick) {
a[3] = 0;
a[1] &= ~0x80;
} else {
return;
}
}
// timeout
if (!timeout || (timeout > a[2])) {
timeout = a[2];
} else {
timeout--;
return;
}
// colors to use
if (a[1] & 0x01) {
pri.h = 0x000;
pri.s = 0xff;
sec.h = 0x400;
sec.s = 0xff;
} else {
pri.h = uconf.favcolor_hue * 6;
pri.s = uconf.favcolor_sat;
sec.h = uconf.altcolor_hue * 6;
sec.s = uconf.altcolor_sat;
}
// pattern mode
pattern = a[0] >> 6;
iter = a[0] & 0x3f;
seq = &a[4];
cnt[0] = a[6] & 0xf;
cnt[1] = a[6] >> 4;
cnt[2] = a[5] & 0xf;
cnt[3] = a[5] >> 4;
if ((pattern == 0) && (a[1] & 0x20)) pattern++;
if ((pattern == 1) && (a[1] & 0x40)) pattern++;
if (pattern > 1) pattern = 0;
switch (pattern) {
// strobe
case 0: {
w = cnt[1] << 1;
if (!w) w = 32;
b = (a[1] & 0x02) ? 0x02 : 0x01;
x = cnt[0];
if (!x) x = 16;
x <<= b;
// set main color
hsv.h = (iter & b) ? sec.h : pri.h;
hsv.s = (iter & b) ? sec.s : pri.s;
hsv.v = 0;
// p1 alternate color mode
if (a[1] & 0x04) {
hsv2.h = (iter & b) ? pri.h : sec.h;
hsv2.s = (iter & b) ? pri.s : sec.s;
} else {
hsv2.h = hsv.h;
hsv2.s = hsv.s;
}
// set value if on
if ((*seq < w) && !(*seq & 1)) {
hsv.v = a[7];
}
if (*seq > w) {
// iteration done
iter++;
*seq = 0;
if (iter >= x) {
// pattern done
pattern++;
iter = 0;
}
break;
}
(*seq)++;
break;
}
// solid alternate
case 1: {
w = cnt[3] << 1;
if (!w) w = 32;
x = cnt[2] << 1;
if (!x) x = 32;
// set main color
hsv.h = (iter & 1) ? sec.h : pri.h;
hsv.s = (iter & 1) ? sec.s : pri.s;
hsv.v = 0;
// p2 alternate color mode
if (a[1] & 0x08) {
hsv2.h = (iter & 1) ? pri.h : sec.h;
hsv2.s = (iter & 1) ? pri.s : sec.s;
} else {
hsv2.h = hsv.h;
hsv2.s = hsv.s;
}
// set value
hsv.v = a[7];
if (*seq > w) {
// iteration done
iter++;
*seq = 0;
if (iter >= x) {
// pattern done
pattern++;
iter = 0;
}
break;
}
(*seq)++;
break;
}
}
// we only have two patterns
pattern %= 2;
// save state
a[0] = (pattern << 6) | (iter & 0x3f);
// apply to LEDs
w = RGB_EDGE_COUNT/2;
for (i = 0; i < w; i++) {
hsv_edge[edge_map[i]].h = hsv.h;
hsv_edge[edge_map[i]].s = hsv.s;
hsv_edge[edge_map[i]].v = hsv.v;
hsv_edge[edge_map[i+w]].h = hsv2.h;
hsv_edge[edge_map[i+w]].s = hsv2.s;
hsv_edge[edge_map[i+w]].v = hsv.v;
}
}
void edge_fade_from_center(uint8_t *a, uint16_t tick)
{
}
void edge_staticbar(uint8_t *a, uint16_t tick)
{
}
void edge_gravitycheck(uint8_t *a, uint16_t tick)
{
}
// implemented program table
const LedProgram edge_pgm[6] = {
{"Solid Color", edge_solid},
{"Flicker", edge_flicker},
{"Circles", edge_circles},
{"Waving", edge_waving},
{"Rainbow", edge_rainbow},
{"Cop Mode", edge_copmode},
};

View File

@ -4,6 +4,13 @@
* main MCU firmware
* 2024 true
*
* much of this code is copied from true's DC29 Whiskey Pirates badge,
* which was in turn copied from the original GAT Nametag.
*
* The GAT Nametag used an 8-bit 8051 core MCU.
* DC29 minibadge used a RISC-V core, and code was hastily ported.
* this is why things like the menu system are so convoluted;
*
* the main MCU is responsible for the following:
* - rendering OLED UI
* - rendering LED programs
@ -40,7 +47,7 @@ int main()
{
// configure clock
ch59x_xtal_conf();
SetSysClock(CLK_SOURCE_HSE_16MHz);
SetSysClock(CLK_SOURCE_PLL_32MHz);
// enable DC-DC converter; brings significant power saving
PWR_DCDCCfg(ENABLE);
@ -48,6 +55,9 @@ int main()
// get i2c up and running
i2c_init();
// decrease clock speed when not using i2c
SetSysClock(CLK_SOURCE_HSE_16MHz);
// configure port-based interrupts (used for chsub interrupt, mainly)
port_intr_init();

View File

@ -5,4 +5,24 @@
* Author: true
*/
#include "accel.h"
AccelData accel;
AccelData accel_last[4];
AccelData accel_smoothing;
int16_t movement;
int8_t accel_get_rotation()
{
return 0;
}
int16_t accel_get_movement()
{
return movement;
}

View File

@ -9,5 +9,26 @@
#define USER_MISC_ACCEL_H_
#include <stdint.h>
typedef struct AccelData {
int16_t x;
int16_t y;
int16_t z;
} AccelData;
extern AccelData accel;
extern uint16_t movement_worst;
int8_t accel_get_rotation();
int16_t accel_get_movement();
#endif /* USER_MISC_ACCEL_H_ */

View File

@ -0,0 +1,27 @@
/*
* checksum.c
* begin 20190612 true
*
* nothing special, good enough, untested, no warranty, gfy, etc
*/
#include <stdint.h>
uint16_t checksum_gen(uint8_t *dat, uint16_t len)
{
uint16_t r = 0;
while (len) {
r += *dat++;
len--;
}
return r;
}
uint16_t checksum_verify(uint8_t *dat, uint16_t len, uint16_t checksum)
{
return (checksum_gen(dat, len) == checksum) ? 1 : 0;
}

View File

@ -0,0 +1,22 @@
/*
* checksum.h
* begin 20190612 true
*
* nothing special, good enough
*/
#ifndef INC_MISC_CHECKSUM_H_
#define INC_MISC_CHECKSUM_H_
#include <stdint.h>
uint16_t checksum_gen(uint8_t *dat, uint16_t len);
uint16_t checksum_verify(uint8_t *dat, uint16_t len, uint16_t checksum);
#endif /* INC_MATH_CHECKSUM_H_ */

View File

@ -0,0 +1,50 @@
/*
* i8atan2.c
* begin 20190611 true
*
* copied and fixed up from teh internets
*/
#include <stdint.h>
static int8_t iat2(int8_t y, int8_t x)
{
return ((y * 32 + (x / 2)) / x) * 2;
}
int8_t i8atan2(int8_t y, int8_t x)
{
// determine octant
if (y >= 0) { // oct 0,1,2,3
if (x >= 0) { // oct 0,1
if (x > y) {
return iat2(-y, -x) / 2 + (0 * 32);
} else {
if (y == 0) return 0; // (x=0,y=0)
return -iat2(-x, -y) / 2 + (2 * 32);
}
} else { // oct 2,3
if (x >= -y) {
return iat2(x, -y) / 2 + (2 * 32);
} else {
return -iat2(-y, x) / 2 + (4 * 32);
}
}
} else { // oct 4,5,6,7
if (x < 0) { // oct 4,5
if (x < y) {
return iat2(y, x) / 2 + (-4 * 32);
} else {
return -iat2(x, y) / 2 + (-2 * 32);
}
} else { // oct 6,7
if (-x >= y) {
return iat2(-x, y) / 2 + (-2 * 32);
} else {
return -iat2(y, -x) / 2 + (-0 * 32);
}
}
}
}

View File

@ -0,0 +1,21 @@
/*
* i8atan2.h
* begin 20190611 true
*
* copied and fixed up from teh internets
*/
#ifndef INC_I8ATAN2_H_
#define INC_I8ATAN2_H_
#include <stdint.h>
int8_t i8atan2(int8_t y, int8_t x);
#endif /* INC_I8ATAN2_H_ */

View File

@ -0,0 +1,6 @@
#include <stdint.h>
uint16_t u16_scale(uint16_t in, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
return (((in - x1) * (y2 - x2)) / (y1 - x1)) + x2;
}

View File

@ -0,0 +1,10 @@
#ifndef INC_MISC_INTSCALE_H_
#define INC_MISC_INTSCALE_H_
uint16_t u16_scale(uint16_t in, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
#endif

View File

@ -0,0 +1,108 @@
/**
* Example for a sine/cosine table lookup
*
* butchered by true to work in 7 bits (int8_t, two turns), and to fix cos8 function
* copied / inspired more or less from
* https://www.atwillys.de/content/cc/sine-lookup-for-embedded-in-c/
**/
#include "sin7.h"
/*
* The number of bits of our data type: here 8 (sizeof operator returns bytes).
*/
#define INT8_BITS (8 * sizeof(int8_t))
#ifndef INT8_MAX
#define INT8_MAX ((1<<(INT8_BITS-1))-1)
#endif
/*
* "5 bit" large table = 32 values. The mask: all bit belonging to the table
* are 1, the all above 0.
*/
#define TABLE_BITS (5)
#define TABLE_SIZE (1<<TABLE_BITS)
#define TABLE_MASK (TABLE_SIZE-1)
/*
* The lookup table is to 90DEG, the input can be -360 to 360 DEG, where negative
* values are transformed to positive before further processing. We need two
* additional bits (*4) to represent 360 DEG:
*/
#define LOOKUP_BITS (TABLE_BITS+2)
#define LOOKUP_MASK ((1<<LOOKUP_BITS)-1)
#define FLIP_BIT (1<<TABLE_BITS)
#define NEGATE_BIT (1<<(TABLE_BITS+1))
#define INTERP_BITS (INT8_BITS-1-LOOKUP_BITS)
#define INTERP_MASK ((1<<INTERP_BITS)-1)
/**
* "5 bit" lookup table for the offsets. These are the sines for exactly
* at 0deg, 11.25deg, 22.5deg etc. The values are from -1 to 1 in Q8?.
*/
static const int8_t sin90[TABLE_SIZE + 1] = {
0x00, 0x06, 0x0c, 0x12, 0x18, 0x1e, 0x24, 0x2a,
0x30, 0x36, 0x3b, 0x41, 0x46, 0x4b, 0x50, 0x55,
0x59, 0x5e, 0x62, 0x66, 0x69, 0x6c, 0x70, 0x72,
0x75, 0x77, 0x79, 0x7b, 0x7c, 0x7d, 0x7e, 0x7e,
0x7f
};
/**
* Sine calculation using interpolated table lookup.
* Instead of radians or degrees we use "turns" here. Means this
* sine does NOT return one phase for 0 to 2*PI, but for 0 to 1.
* Input: -1 to 1 as int8 == -128 to 127
* Output: -1 to 1 as int8 == -128 to 127
*
* @param int8_t angle
* @return int8_t
*/
int8_t sin7(int8_t angle)
{
int8_t v0, v1;
if(angle < 0) {
angle += INT8_MAX;
angle += 1;
}
v0 = (angle >> INTERP_BITS);
if (v0 & FLIP_BIT) {
v0 = ~v0;
v1 = ~angle;
} else {
v1 = angle;
}
v0 &= TABLE_MASK;
v1 = sin90[v0] + (int8_t)(((int16_t)(sin90[v0+1]-sin90[v0]) * (v1 & INTERP_MASK)) >> INTERP_BITS);
if((angle >> INTERP_BITS) & NEGATE_BIT) {
v1 = -v1;
}
return v1;
}
/**
* Cosine calculation using interpolated table lookup.
* Instead of radians or degrees we use "turns" here. Means this
* cosine does NOT return one phase for 0 to 2*PI, but for 0 to 1.
* Input: -1 to 1 as int8 == -128 to 127
* Output: -1 to 1 as int8 == -128 to 127
*
* @param int8_t angle
* @return int8_t
*/
int8_t cos7(int8_t angle)
{
if (angle < 0) {
angle += INT8_MAX;
angle += 1;
}
return sin7(angle - ((INT8_MAX * 3) / 4));
}

View File

@ -0,0 +1,43 @@
/**
* Example for a interpolated sine/cosine table lookup
*
* modified by true to work in 8 bits, and to fix cos7 function
*
*/
#ifndef INC_SIN7_H_
#define INC_SIN7_H_
#include <stdint.h>
/**
* Sine calculation using interpolated table lookup.
* Instead of radians or degrees we use "turns" here. Means this
* sine does NOT return one phase for 0 to 2*PI, but for 0 to 1.
* Input: -1 to 1 as int8 "Q7" == -128 to 127.
* Output: -1 to 1 as int8 "Q7" == -128 to 127.
*
* @param int8_t angle Q7
* @return int8_t Q7
*/
int8_t sin7(int8_t angle);
/**
* Cosine calculation using interpolated table lookup.
* Instead of radians or degrees we use "turns" here. Means this
* cosine does NOT return one phase for 0 to 2*PI, but for 0 to 1.
* Input: -1 to 1 as int8 "Q7" == -128 to 127.
* Output: -1 to 1 as int8 "Q7" == -128 to 127.
*
* @param int8_t angle Q7
* @return int8_t Q7
*/
int8_t cos7(int8_t angle);
#endif /* INC_MATH_SIN7_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 "tinymt.h"
static void tinymt32_next_state(tinymt32_t *s);
static uint32_t tinymt32_temper(tinymt32_t *s);
tinymt32_t tinymt32_s;
/**
* Parameter set to use for this IETF specification. Don't change.
* This parameter set is the first entry of the precalculated
* parameter sets in tinymt32dc/tinymt32dc.0.1048576.txt by
* Kenji Rikitake, available at:
* https://github.com/jj1bdx/tinymtdc-longbatch/.
* It is also the parameter set used in:
* Rikitake, K., "TinyMT pseudo random number generator for
* Erlang", Proceedings of the 11th ACM SIGPLAN Erlang Workshop,
* September 2012.
*/
const uint32_t TINYMT32_MAT1_PARAM = UINT32_C(0x8f7011ee);
const uint32_t TINYMT32_MAT2_PARAM = UINT32_C(0xfc78ff1f);
const uint32_t TINYMT32_TMAT_PARAM = UINT32_C(0x3793fdff);
/**
* This function initializes the internal state array with a
* 32-bit unsigned integer seed.
* @param s pointer to tinymt internal state.
* @param seed a 32-bit unsigned integer used as a seed.
*/
void tinymt32_init (tinymt32_t* s, uint32_t seed)
{
const uint32_t MIN_LOOP = 8;
const uint32_t PRE_LOOP = 8;
s->status[0] = seed;
s->status[1] = s->mat1 = TINYMT32_MAT1_PARAM;
s->status[2] = s->mat2 = TINYMT32_MAT2_PARAM;
s->status[3] = s->tmat = TINYMT32_TMAT_PARAM;
for (int i = 1; i < MIN_LOOP; i++) {
s->status[i & 3] ^= i + UINT32_C(1812433253)
* (s->status[(i - 1) & 3]
^ (s->status[(i - 1) & 3] >> 30));
}
/*
* NB: The parameter set of this specification warrants
* that none of the possible 2^^32 seeds leads to an
* all-zero 127-bit internal state. Therefore, the
* period_certification() function of the original
* TinyMT32 source code has been safely removed. If
* another parameter set is used, this function will
* have to be reintroduced here.
*/
for (int i = 0; i < PRE_LOOP; i++) {
tinymt32_next_state(s);
}
}
/**
* This function outputs a 32-bit unsigned integer from
* the internal state.
* @param s pointer to tinymt internal state.
* @return 32-bit unsigned integer r (0 <= r < 2^32).
*/
uint32_t tinymt32_get_uint32(tinymt32_t* s)
{
tinymt32_next_state(s);
return tinymt32_temper(s);
}
/**
* Internal tinymt32 constants and functions.
* Users should not call these functions directly.
*/
const uint32_t TINYMT32_SH0 = 1;
const uint32_t TINYMT32_SH1 = 10;
const uint32_t TINYMT32_SH8 = 8;
const uint32_t TINYMT32_MASK = UINT32_C(0x7fffffff);
/**
* This function changes the internal state of tinymt32.
* @param s pointer to tinymt internal state.
*/
static void tinymt32_next_state (tinymt32_t* s)
{
uint32_t x;
uint32_t y;
y = s->status[3];
x = (s->status[0] & TINYMT32_MASK)
^ s->status[1]
^ s->status[2];
x ^= (x << TINYMT32_SH0);
y ^= (y >> TINYMT32_SH0) ^ x;
s->status[0] = s->status[1];
s->status[1] = s->status[2];
s->status[2] = x ^ (y << TINYMT32_SH1);
s->status[3] = y;
/*
* The if (y & 1) {...} block below replaces:
* s->status[1] ^= -((int32_t)(y & 1)) & s->mat1;
* s->status[2] ^= -((int32_t)(y & 1)) & s->mat2;
* The adopted code is equivalent to the original code
* but does not depend on the representation of negative
* integers by 2's complements. It is therefore more
* portable but includes an if branch, which may slow
* down the generation speed.
*/
if (y & 1) {
s->status[1] ^= s->mat1;
s->status[2] ^= s->mat2;
}
}
/**
* This function outputs a 32-bit unsigned integer from
* the internal state.
* @param s pointer to tinymt internal state.
* @return 32-bit unsigned pseudorandom number.
*/
static uint32_t tinymt32_temper (tinymt32_t* s)
{
uint32_t t0, t1;
t0 = s->status[3];
t1 = s->status[0] + (s->status[2] >> TINYMT32_SH8);
t0 ^= t1;
/*
* The if (t1 & 1) {...} block below replaces:
* t0 ^= -((int32_t)(t1 & 1)) & s->tmat;
* The adopted code is equivalent to the original code
* but does not depend on the representation of negative
* integers by 2's complements. It is therefore more
* portable but includes an if branch, which may slow
* down the generation speed.
*/
if (t1 & 1) {
t0 ^= s->tmat;
}
return t0;
}
uint16_t prng_scale16(uint16_t min, uint16_t max)
{
uint32_t rnd;
rnd = prng_get16();
rnd *= (max - min);
rnd >>= 16;
rnd += min;
return rnd;
}

View File

@ -0,0 +1,39 @@
/**
* Tiny Mersenne Twister
*/
#ifndef TINYMT_RAND_H_
#define TINYMT_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 /* TINYMT_RAND_H */

View File

@ -14,6 +14,8 @@
#include "CH59x_common.h"
#include "port_intr.h"
#include "hw/ch32sub.h"
#include <stdint.h>
@ -41,7 +43,7 @@ void port_intr_cb_register(uint8_t port, uint8_t idx, void (*fn)(void))
void port_intr_init()
{
// enable port interrupt
}
@ -51,7 +53,10 @@ __INTERRUPT
__HIGH_CODE
void GPIOA_IRQHandler(void)
{
uint16_t flag = R16_PA_INT_IF;
// clear flags
R16_PA_INT_IF = flag;
}
__INTERRUPT
@ -59,22 +64,25 @@ __HIGH_CODE
void GPIOB_IRQHandler(void)
{
uint8_t i;
uint8_t offset;
uint16_t flag = R16_PB_INT_IF;
// clear flags
R16_PB_INT_IF = flag;
// high priority actions
// none.
// ch32sub interrupt
if (flag & SUB_INTR_PIN) {
ch32sub_isr();
}
// general purpose fallback
for (i = 0; i < 24; i++) {
for (i = 4; i < MAX_PIN; i++) {
offset = i - 4;
if (flag & (1 << i)) {
if (cb[PORT_INTR_GPIOB][i]) {
cb[PORT_INTR_GPIOB][i]();
if (cb[PORT_INTR_GPIOB][offset]) {
cb[PORT_INTR_GPIOB][offset]();
}
}
}
}

View File

@ -0,0 +1,609 @@
/*
* draw_ssd1306.c
* begin 20190525 true
*
* mostly implemented by true
* some functions shamelessly copied and modified from interwebs
*/
#include "draw_ssd1306.h"
#include "../misc/sin7.h"
#include <string.h>
#include <stdlib.h>
#define _swap_(a, b) { t = a; a = b; b = t; }
#define _min_(a, b) (((a)<(b))?(a):(b))
#define _max_(a, b) (((a)>(b))?(a):(b))
SSD1306 *o = &oled; // we only ever support one...
void ssd1306fb_set_target(SSD1306 *target)
{
o = target;
}
void ssd1306fb_set_cursor(int8_t x, int8_t y)
{
o->cursor_x = x;
o->cursor_y = y;
}
void ssd1306fb_set_color(uint8_t color)
{
o->state &= ~SSD1306_STATE_PIXEL_MASK;
o->state |= color & SSD1306_STATE_PIXEL_MASK;
}
/*
* this function rotatecopies one framebuffer into another,
* around an axis at the center of the framebuffer.
*
* there are assumptions about pages as well; Y pages will always
* start on a boundary.
*/
void ssd1306fb_rotate(SSD1306 *src, SSD1306 *dst, int8_t rot)
{
int8_t x, y, nx, ny;
int8_t startx, starty, endx, endy;
int8_t yoff; // page, using same terminology as the rest of the code
int8_t xoff; // x offset within the page
int8_t dxoff; // destination's x offset
int8_t dyoff; // destination's x offset
uint8_t *s;
uint8_t *d;
uint16_t idx;
s = src->fb;
d = dst->fb;
endx = dst->width >> 1;
startx = 0 - endx;
if ((dst->width & 1) == 0) startx++;
endy = dst->height >> 1;
starty = 0 - endy;
if ((dst->height & 1) == 0) starty++;
for (y = starty; y <= endy; y++) {
yoff = (y - starty) & 7;
xoff = ((y - starty) >> 3) * dst->width;
for (x = startx; x <= endx; x++) {
if (s[x - startx + xoff] & (1 << yoff)) {
nx = (int16_t)(x * cos7(rot) - y * sin7(rot)) >> 7;
ny = (int16_t)(x * sin7(rot) + y * cos7(rot)) >> 7;
if ((nx >= startx) && (nx <= endx)) {
if ((ny >= starty) && (ny <= endy)) {
dyoff = (ny - starty) & 7;
dxoff = ((ny - starty) >> 3) * dst->width;
idx = nx - startx + dxoff;
if (idx >= 512) return;
d[idx] |= (1 << dyoff);
}
}
}
}
}
}
/*
* draws from data stored in horizontal priority page byte order
* (useful for copying between buffers)
*/
void ssd1306fb_copy(int8_t x, int8_t y, uint8_t width, uint8_t height, const uint8_t *dat)
{
uint16_t i;
uint8_t d;
uint8_t w;
uint8_t xp; // actual pixel x
uint16_t yp; // actual page y
int16_t pos; // output buffer offset
uint8_t rh; // pages to draw
int8_t yoff; // y pixel drawing offset
uint16_t bytes; // bytes to draw (calculated)
uint8_t *fb;
int16_t fb_siz;
if (x >= o->width) return;
if (y >= o->height) return;
fb = o->fb;
fb_siz = (o->height >> 3) * o->width;
rh = 1 + ((height - 1) >> 3);
yoff = y & 7;
bytes = (width * rh);
w = width;
height = x;
width = 0;
for (i = 0; i < bytes; i++) {
d = dat[i];
xp = x + (i % w);
yp = ((y >> 3) + (i / w)) * o->width;
pos = xp + yp;
// half-width
if (o->state & SSD1306_STATE_STR_HALFWIDTH) {
if (xp != height) {
height = xp;
width++;
if (width & 0x01) {
i += rh - 1;
continue;
}
}
}
pos -= ((width + 1) >> 1);
if ((pos < fb_siz) && (xp < o->width)) {
if (yoff >= 0) {
if (pos >= 0) {
if (o->state & SSD1306_STATE_SET_PIXEL) {
fb[pos] |= (d << yoff);
} else if (o->state & SSD1306_STATE_INVERT_PIXEL) {
fb[pos] ^= (d << yoff);
} else {
fb[pos] &= ~(d << yoff);
}
}
if (yoff && ((pos + o->width) < fb_siz)) {
if (o->state & SSD1306_STATE_SET_PIXEL) {
fb[pos + o->width] |= (d >> (8 - yoff));
} else if (o->state & SSD1306_STATE_INVERT_PIXEL) {
fb[pos + o->width] ^= (d >> (8 - yoff));
} else {
fb[pos + o->width] &= ~(d >> (8 - yoff));
}
}
}
}
}
}
/*
* draws from data stored in vertical priority page byte order (why? why not just horizontal? ...)
*/
void ssd1306fb_draw_raw(int8_t x, int8_t y, uint8_t width, uint8_t height, const uint8_t *dat, uint16_t dat_offset, uint16_t bytes)
{
uint16_t i;
uint8_t d;
uint8_t xp;
uint16_t yp;
int16_t pos;
uint8_t rh;
int8_t yoff;
int8_t y_init, yoff_init;
uint8_t *fb;
int16_t fb_siz;
if (x >= o->width) return;
if (y >= o->height) return;
fb = o->fb;
fb_siz = (o->height >> 3) * o->width;
rh = 1 + ((height - 1) >> 3);
yoff = y & 7;
bytes = (bytes == 0) ? width * rh : bytes;
y_init = y;
yoff_init = yoff;
height = x;
width = 0;
for (i = 0; i < bytes; i++) {
// reset y if next horizontal drawing phase is started
if ((i & rh) == 0) {
y = y_init;
yoff = yoff_init;
}
d = dat[dat_offset + i];
xp = x + (i / rh);
yp = ((y >> 3) + (i % rh)) * o->width;
pos = xp + yp;
// half-width
if (o->state & SSD1306_STATE_STR_HALFWIDTH) {
if (xp != height) {
height = xp;
width++;
if (width & 0x01) {
i += rh - 1;
continue;
}
}
}
pos -= ((width + 1) >> 1);
if ((pos < fb_siz) && (xp < o->width)) {
if (yoff >= 0) {
if (pos >= 0) {
if (o->state & SSD1306_STATE_SET_PIXEL) {
fb[pos] |= (d << yoff);
} else if (o->state & SSD1306_STATE_INVERT_PIXEL) {
fb[pos] ^= (d << yoff);
} else {
fb[pos] &= ~(d << yoff);
}
}
if (yoff && ((pos + o->width) < fb_siz)) {
if (o->state & SSD1306_STATE_SET_PIXEL) {
fb[pos + o->width] |= (d >> (8 - yoff));
} else if (o->state & SSD1306_STATE_INVERT_PIXEL) {
fb[pos + o->width] ^= (d >> (8 - yoff));
} else {
fb[pos + o->width] &= ~(d >> (8 - yoff));
}
}
}
}
}
}
void ssd1306fb_draw_pix(uint8_t x, uint8_t y)
{
uint8_t *fb;
if (x >= o->width) return;
if (y >= o->height) return;
fb = o->fb;
fb += ((y >> 3) * o->width);
fb += x;
y = 1 << (y & 7);
if (o->state & SSD1306_STATE_SET_PIXEL) {
*fb |= y;
} else if (o->state & SSD1306_STATE_INVERT_PIXEL) {
*fb ^= y;
} else {
*fb &= ~y;
}
}
void ssd1306fb_draw_hline(uint8_t x1, uint8_t x2, uint8_t y)
{
uint8_t t;
uint8_t *fb;
if (x1 >= o->width) return;
if (x2 >= o->width) return;
if (y >= o->height) return;
if (x1 > x2) {
_swap_(x1, x2);
}
fb = o->fb;
fb += ((y >> 3) * o->width);
fb += x1;
y = 1 << (y & 7);
t = (x2 - x1) + 1;
if (o->state & SSD1306_STATE_SET_PIXEL) {
while (t--) *fb++ |= y;
} else if (o->state & SSD1306_STATE_INVERT_PIXEL) {
while (t--) *fb++ ^= y;
} else {
y = ~y;
while (t--) *fb++ &= y;
}
}
void ssd1306fb_draw_vline(uint8_t x, uint8_t y1, uint8_t y2)
{
uint8_t t;
uint8_t draw;
uint8_t *fb;
int8_t yoff, len;
if (x >= o->width) return;
if (y1 >= o->height) return;
if (y2 >= o->height) return;
if (y1 > y2) {
_swap_(y1, y2);
}
yoff = y1 & 7;
fb = o->fb;
fb += ((y1 >> 3) * o->width);
fb += x;
len = (y2 - y1) + 1;
// work the first page if it has an offset
if (yoff) {
yoff = 8 - yoff;
draw = ~(0xff >> yoff);
if (len < yoff) {
draw &= (0xff >> (yoff - len));
}
if (o->state & SSD1306_STATE_SET_PIXEL) {
*fb |= draw;
} else if (o->state & SSD1306_STATE_INVERT_PIXEL) {
*fb ^= draw;
} else {
*fb &= ~draw;
}
if (len < yoff) return;
len -= yoff;
fb += o->width;
}
// work any full pages
while (len >= 8) {
if (o->state & SSD1306_STATE_SET_PIXEL) {
*fb = 0xff;
} else if (o->state & SSD1306_STATE_INVERT_PIXEL) {
*fb ^= ~(*fb);
} else {
*fb = 0x00;
}
len -= 8;
fb += o->width;
}
// work last page
if (len > 0) {
draw = (1 << (len & 7)) - 1;
if (o->state & SSD1306_STATE_SET_PIXEL) {
*fb |= draw;
} else if (o->state & SSD1306_STATE_INVERT_PIXEL) {
*fb ^= draw;
} else {
*fb &= ~draw;
}
}
}
void ssd1306fb_draw_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2)
{
int8_t t;
int8_t dx, dy;
int8_t err;
int8_t ystep;
int8_t steep = abs(y2 - y1) > abs(x2 - x1);
if (steep) {
_swap_(x1, y1);
_swap_(x2, y2);
}
if (x1 > x2) {
_swap_(x1, x2);
_swap_(y1, y2);
}
dx = x2 - x1;
dy = abs(y2 - y1);
err = dx / 2;
if (y1 < y2) {
ystep = 1;
} else {
ystep = -1;
}
for (; x1 <= x2; x1++) {
if (steep) {
ssd1306fb_draw_pix(y1, x1);
} else {
ssd1306fb_draw_pix(x1, y1);
}
err -= dy;
if (err < 0) {
y1 += ystep;
err += dx;
}
}
}
void ssd1306fb_draw_rect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2)
{
ssd1306fb_draw_vline(x1, y1 + 1, y2 - 1);
ssd1306fb_draw_hline(x1, x2, y1);
ssd1306fb_draw_hline(x1, x2, y2);
ssd1306fb_draw_vline(x2, y1 + 1, y2 - 1);
}
// note: rect fill not working right now, lol
void ssd1306fb_draw_rect_fill(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2)
{
uint8_t t;
if (y1 > y2) _swap_(y1, y2);
do {
// todo: replace with vline implementation, it might be faster,
// as multiple bits are set per assign vs one bit max with hline
ssd1306fb_draw_hline(x1, x2, y1);
} while (y1++ < y2);
}
void ssd1306fb_draw_circle(int8_t x, int8_t y, uint8_t radius)
{
int8_t xs = 0;
int8_t ys = radius;
int8_t dp = 1 - radius;
do {
if (dp < 0) {
dp = dp + 2 * (xs++) + 3;
} else {
dp = dp + 2 * (xs++) - 2 * (ys--) + 5;
}
ssd1306fb_draw_pix(x + xs, y + ys);
ssd1306fb_draw_pix(x - xs, y + ys);
ssd1306fb_draw_pix(x + xs, y - ys);
ssd1306fb_draw_pix(x - xs, y - ys);
ssd1306fb_draw_pix(x + ys, y + xs);
ssd1306fb_draw_pix(x - ys, y + xs);
ssd1306fb_draw_pix(x + ys, y - xs);
ssd1306fb_draw_pix(x - ys, y - xs);
} while (xs < ys);
ssd1306fb_draw_pix(x + radius, y);
ssd1306fb_draw_pix(x, y + radius);
ssd1306fb_draw_pix(x - radius, y);
ssd1306fb_draw_pix(x, y - radius);
}
/*
* character string drawing functions draw at the cursor position.
*/
uint8_t ssd1306fb_get_str_width(const uint8_t *font, const char *str, uint8_t len, int8_t extra_spacing)
{
uint8_t first = font[FONT_FIRST_CHAR_POS];
uint8_t chr_width;
uint8_t str_width = 0;
uint8_t w;
w = len;
while (w--) {
chr_width = font[FONT_JUMPTABLE_START + (str[w] - first) * FONT_JUMPTABLE_BYTES + FONT_JUMPTABLE_WIDTH];
if (o->state & SSD1306_STATE_STR_HALFWIDTH) {
chr_width >>= 1;
}
str_width += chr_width;
}
str_width += extra_spacing * (len - 1);
return str_width;
}
uint8_t ssd1306fb_get_font_height(const uint8_t *font)
{
return font[FONT_HEIGHT_POS];
}
void ssd1306fb_internal_str(const uint8_t *font, const char *str, uint8_t len, int8_t spacing)
{
const uint8_t height = font[FONT_HEIGHT_POS];
const uint8_t first = font[FONT_FIRST_CHAR_POS];
const uint16_t jt_siz = font[FONT_CHAR_NUM_POS] * FONT_JUMPTABLE_BYTES;
const uint16_t last = font[FONT_CHAR_NUM_POS] + first;
uint8_t i;
uint8_t c; // current character
uint8_t offset; // offset from first character in font table (usually space 0x20)
uint8_t *jtdata; // font jump table data for character
uint16_t pos; // position of actual character data
if (!len) return;
for (i = 0; i < len; i++) {
if (o->cursor_x > o->width) return;
c = str[i];
// character valid?
if (c >= first && c < last) {
offset = c - first;
// get
jtdata = (uint8_t *)(font + FONT_JUMPTABLE_START + (offset * FONT_JUMPTABLE_BYTES));
if (!(jtdata[FONT_JUMPTABLE_MSB] == 255 && jtdata[FONT_JUMPTABLE_LSB] == 255)) {
pos = FONT_JUMPTABLE_START +
jt_siz +
((jtdata[FONT_JUMPTABLE_MSB] << 8) +
jtdata[FONT_JUMPTABLE_LSB]);
ssd1306fb_draw_raw(
o->cursor_x,
o->cursor_y,
jtdata[FONT_JUMPTABLE_WIDTH],
height,
font,
pos,
jtdata[FONT_JUMPTABLE_SIZE]);
}
offset = jtdata[FONT_JUMPTABLE_WIDTH];
if (o->state & SSD1306_STATE_STR_HALFWIDTH) {
offset >>= 1;
}
o->cursor_x += offset;
if (i + 1 != len) {
o->cursor_x += spacing;
}
}
}
}
void ssd1306fb_draw_str(const uint8_t *font, const char *str, int8_t extra_spacing)
{
//uint8_t lh;
uint8_t len;
//lh = *(font + FONT_HEIGHT_POS);
len = strlen(str);
ssd1306fb_internal_str(font, str, len, extra_spacing); //, ssd1306fb_get_str_width(font, str, len));
}

View File

@ -0,0 +1,43 @@
/*
* $Id: draw_ssd1306.h 494 2021-07-21 11:46:11Z true $
* begin 20190525 true
*/
#ifndef USER_RENDER_DRAW_SSD1306_H_
#define USER_RENDER_DRAW_SSD1306_H_
#include "hw/ssd1306.h"
#include "render/font.h"
void ssd1306fb_set_target(SSD1306 *target);
void ssd1306fb_set_cursor(int8_t x, int8_t y);
void ssd1306fb_set_color(uint8_t color);
void ssd1306fb_rotate(SSD1306 *src, SSD1306 *dst, int8_t rot);
void ssd1306fb_copy(int8_t x, int8_t y, uint8_t width, uint8_t height, const uint8_t *dat);
void ssd1306fb_draw_raw(int8_t x, int8_t y, uint8_t width, uint8_t height, const uint8_t *dat, uint16_t dat_offset, uint16_t bytes);
void ssd1306fb_draw_pix(uint8_t x, uint8_t y);
void ssd1306fb_draw_hline(uint8_t x1, uint8_t x2, uint8_t y);
void ssd1306fb_draw_vline(uint8_t x, uint8_t y1, uint8_t y2);
void ssd1306fb_draw_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2);
void ssd1306fb_draw_rect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2);
//void ssd1306fb_draw_rect_fill(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2);
void ssd1306fb_draw_circle(int8_t x, int8_t y, uint8_t radius);
uint8_t ssd1306fb_get_str_width(const uint8_t *font, const char *str, uint8_t len, int8_t extra_spacing);
uint8_t ssd1306fb_get_font_height(const uint8_t *font);
void ssd1306fb_draw_str(const uint8_t *font, const char *str, int8_t extra_spacing);
#endif /* USER_RENDER_DRAW_SSD1306_H_ */

View File

@ -0,0 +1,51 @@
/*
* $Id: font.h 494 2021-07-21 11:46:11Z true $
* begin 20190525 true
*/
#ifndef USER_RENDER_FONT_STATIC_H_
#define USER_RENDER_FONT_STATIC_H_
#include <stdint.h>
#define FONT_JUMPTABLE_BYTES 4
#define FONT_JUMPTABLE_MSB 0
#define FONT_JUMPTABLE_LSB 1
#define FONT_JUMPTABLE_SIZE 2
#define FONT_JUMPTABLE_WIDTH 3
#define FONT_JUMPTABLE_START 4
#define FONT_WIDTH_POS 0
#define FONT_HEIGHT_POS 1
#define FONT_FIRST_CHAR_POS 2
#define FONT_CHAR_NUM_POS 3
typedef struct FontTable {
uint8_t tag_allowed;
const char *name;
const uint8_t *font;
} FontTable;
extern const uint8_t font_Dialog_plain_8[];
extern const uint8_t font_DejaVu_Sans_Mono_Bold_11[];
extern const uint8_t font_Nimbus_Mono_L_Bold_20[];
extern const uint8_t font_DialogInput_Bold_24[];
extern const uint8_t font_Chewy_24[];
extern const uint8_t font_Crushed_25[];
extern const uint8_t font_Nimbus_Sans_L_25[];
extern const uint8_t font_Orbitron_28[];
extern const FontTable font_table[8];
#endif /* USER_RENDER_FONT_STATIC_H_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
/*
* font_table.c
* begin 20190613 true
*/
#include "font.h"
const FontTable font_table[8] = {
{0, "Dialog 8", font_Dialog_plain_8},
{0, "DJVmonoB11", font_DejaVu_Sans_Mono_Bold_11},
{1, "Nimbmono20", font_Nimbus_Mono_L_Bold_20},
{1, "DJVmonoB24", font_DialogInput_Bold_24},
{1, "Chewy 24", font_Chewy_24},
{1, "Crushed 25", font_Crushed_25},
{1, "NimbSL 25", font_Nimbus_Sans_L_25},
{1, "Orbitron28", font_Orbitron_28},
};

View File

@ -0,0 +1,93 @@
/*
* btn.c
*
* created Oct 13, 2024
*
* read and process buttons from sub MCU.
* provide button handling support to application.
*
*/
#include "btn.h"
#include "hw/ch32sub.h"
BtnSub btn[BTN_COUNT];
uint8_t btn_pushed;
uint8_t btn_held;
void btn_push_cb(uint8_t idx)
{
btn_pushed |= (1 << idx);
if (btn[idx].cb_push) btn[idx].cb_push(idx);
}
void btn_hold_cb(uint8_t idx)
{
btn_held |= (1 << idx);
if (btn[idx].cb_hold) btn[idx].cb_hold(idx);
}
void btn_release_cb(uint8_t idx)
{
btn_pushed &= ~(1 << idx);
btn_held &= ~(1 << idx);
if (btn[idx].cb_release) btn[idx].cb_release(idx);
}
void btn_commit_hold()
{
uint8_t i, x;
uint8_t val[BTN_COUNT*2];
x = 0;
for (i = 0; i < BTN_COUNT; i++) {
val[x + 0x00] = btn[i].hold >> 8;
val[x + 0x01] = btn[i].hold & 0xff;
x += 2;
}
ch32sub_write(REG_BTN1_HOLD_HI, val, sizeof(val));
x = 0;
for (i = 0; i < BTN_COUNT; i++) {
val[x + 0x00] = btn[i].repeat >> 8;
val[x + 0x01] = btn[i].repeat & 0xff;
x += 2;
}
ch32sub_write(REG_BTN1_REPEAT_HI, val, sizeof(val));
}
void btn_intr()
{
uint8_t i;
uint8_t btn_state[4]; // [9];
// uint8_t *btn_mask = &btn_state[4];
// get button data.
ch32sub_read(REG_BTN_PUSHED_LATCHED, btn_state, sizeof(btn_state));
// clear button interrupt flag
ch32sub_write_1b(REG_INTR_FLAGS_CLEAR, INT_BTN);
// process callbacks for new events
for (i = 0; i < BTN_COUNT; i++) {
if (btn_state[1] & (1 << i)) {
btn_push_cb(i);
}
if (btn_state[2] & (1 << i)) {
btn_hold_cb(i);
}
if (btn_state[3] & (1 << i)) {
btn_release_cb(i);
}
}
}

View File

@ -0,0 +1,43 @@
/*
* btn.h
*
* Created on: Oct 13, 2024
* Author: true
*/
#ifndef USER_UI_BTN_H_
#define USER_UI_BTN_H_
#include <stdint.h>
#define BTN_COUNT 5
#define HOLD_PERIOD 5 // period in milliseconds for each "hold" tick internally
typedef struct BtnSub {
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);
} BtnSub;
extern BtnSub btn[BTN_COUNT];
extern uint8_t btn_pushed;
extern uint8_t btn_held;
void btn_intr();
void btn_commit_hold();
#endif /* USER_UI_BTN_H_ */

View File

@ -0,0 +1,109 @@
/*
* $Id: menu.h 495 2021-07-22 20:53:39Z true $
* begin 20190527 true
*/
#ifndef INC_UI_MENU_H_
#define INC_UI_MENU_H_
#include <stdint.h>
#include "render/draw_ssd1306.h" // for OLED support
//#include "driver/efm8ub2_userflash.h" // flash read/write operations
#include "ui/btn.h" // for button control
typedef struct MenuItem {
uint8_t count; // amount of entries in this menu
uint8_t flags; // misc settings for the menu
struct MenuItem *root; // menu to return to when going back
uint8_t root_idx; // menu index to return to
void (*dispfn)(uint8_t); // function which handles any drawing (idx)
void (*entryfn)(uint8_t); // function to call when entering menu (idx)
} MenuItem;
#define MENU_FLAG_NONE (0)
#define MENU_FLAG_NO_AUTOCLS (1 << 0)
#define MENU_FLAG_SCROLL (1 << 4)
#define MENU_FLAG_SAVE_ON_EXIT (1 << 7)
#define MENU_BTNSTYLE_MAIN 0x00
#define MENU_BTNSTYLE_MENU 0x02
#define MENU_BTNSTYLE_NAME_IDLE 0x11
#define MENU_BTNSTYLE_NAME_EDIT 0x12
#define MENU_BTNSTYLE_LED_EDIT 0x21
#define MENU_BTNSTYLE_OPTION_EDIT 0x51
#define MENU_BTNSTYLE_PROGSIMPLE_IDLE 0xe1
#define MENU_BTNSTYLE_PROGSIMPLE_EDIT 0xe2
#define MENU_BTNSTYLE_ABOUT 0x60
extern MenuItem *menu;
extern uint8_t menu_idx;
extern uint8_t mtick;
void menu_start(uint8_t x);
void menu_stop(uint8_t x);
void menu_tick();
void menu_prev_push(MenuItem *m);
MenuItem * menu_prev_pop();
void menu_btn_use_std();
void menu_btn_use_none();
void menu_btn_exit(uint8_t idx);
void menu_draw_buttons(uint8_t mode, uint8_t indicator_mask);
void menu_draw_tabs(uint8_t active_idx);
// menu_none_nametag
extern const MenuItem menu_none;
void menu_none_disp(uint8_t idx);
// menu_0_root
extern const MenuItem menu_0;
void menu_0_disp(uint8_t idx);
void menu_0_enter(uint8_t idx);
// menu_1_name
extern const MenuItem menu_1;
void menu_1_disp(uint8_t idx);
void menu_1_enter(uint8_t idx);
// menu_2_led
extern const MenuItem menu_2;
void menu_2_disp(uint8_t idx);
void menu_2_enter(uint8_t idx);
// menu_3_snek
extern const MenuItem menu_3_snek;
void snek_disp(uint8_t idx);
// menu_5_options
extern const MenuItem menu_5;
void menu_5_disp(uint8_t idx);
void menu_5_enter(uint8_t idx);
// menu_6_about
extern const MenuItem menu_6;
void menu_6_disp(uint8_t idx);
#endif /* INC_UI_MENU_H_ */

View File

@ -0,0 +1,310 @@
/*
* $Id: menu_base.c 500 2021-08-08 19:43:38Z true $
* begin 20190527 true
*/
#include "menu.h"
#include "user_config.h"
#include <string.h>
MenuItem *menu;
uint8_t menu_idx = 0;
uint8_t mtick;
void menu_tick()
{
if (menu) {
// display
if (menu->dispfn) {
menu->dispfn(menu_idx);
// // do we flip the display?
if ((menu == &menu_6) && (menu_idx == 4)) { // accelerometer
// accelerometer page never flips
ssd1306_set_flipmirror(0);
} else if (menu != &menu_none) { // nametag
if (sysflags & SYS_OLED_ROTATE_X) {
ssd1306_set_flipmirror(1);
} else {
ssd1306_set_flipmirror(0);
}
}
}
}
mtick++;
}
void menu_start(uint8_t x)
{
x = x;
menu = (MenuItem *)&menu_0;
menu_idx = 0;
uconf.framemod = UCONF_FRAMERATE_FULL;
menu_btn_use_std();
}
void menu_stop(uint8_t x)
{
x = x;
menu = (MenuItem *)&menu_none;
menu_btn_use_none();
}
void menu_btn_next(uint8_t idx)
{
menu_idx++;
if (menu_idx >= menu->count) {
menu_idx = 0;
}
}
void menu_btn_prev(uint8_t idx)
{
if (menu_idx == 0) {
menu_idx = menu->count - 1;
} else {
menu_idx--;
}
}
void menu_btn_enter(uint8_t idx)
{
if (menu->entryfn) {
menu->entryfn(menu_idx);
}
}
void menu_btn_exit(uint8_t idx)
{
if (menu->root) {
// save data to flash if necessary
if (menu->flags & MENU_FLAG_SAVE_ON_EXIT) {
uconf_write();
}
// change menu
menu_idx = menu->root_idx;
menu = menu->root;
}
// some menu entries override the enter button.
// make sure we're set back up to known values
menu_btn_use_std();
// if any menu disabled LEDs, re-enable them
uconf.flags &= ~UCONF_FLAGS_LEDS_DISABLE;
}
void menu_btn_use_std()
{
btn[0].cb_push = &menu_btn_next;
btn[0].cb_hold = &menu_btn_next;
btn[0].cb_release = 0;
btn[0].hold = 300 / HOLD_PERIOD;
btn[0].repeat = 150 / HOLD_PERIOD;
btn[1].cb_push = &menu_btn_exit;
btn[1].cb_hold = 0;
btn[1].cb_release = 0;
btn[1].hold = 0;
btn[1].repeat = 0;
btn[2].cb_push = &menu_btn_enter;
btn[2].cb_hold = 0;
btn[2].cb_release = 0;
btn[2].hold = 0;
btn[2].repeat = 0;
btn[3].cb_push = &menu_btn_prev;
btn[3].cb_hold = &menu_btn_prev;
btn[3].cb_release = 0;
btn[3].hold = 300 / HOLD_PERIOD;
btn[3].repeat = 150 / HOLD_PERIOD;
btn_commit_hold();
}
void menu_btn_use_none()
{
uint8_t i;
for (i = 0; i < BTN_COUNT; i++) {
btn[i].cb_push = &menu_btn_enter;
btn[i].hold = 0;
btn[i].repeat = 0;
}
btn_commit_hold();
}
void menu_draw_buttons(uint8_t mode, uint8_t mask)
{
uint8_t w;
uint8_t i, j;
char button_txt[4][12];
// text
if (menu->flags & MENU_FLAG_SCROLL) {
strcpy(button_txt[0], "Scroll");
strcpy(button_txt[2], "Scroll");
} else {
button_txt[0][0] = 0;
button_txt[2][0] = 0;
}
switch (mode) {
case MENU_BTNSTYLE_MAIN: {
switch (mask) {
case 0: strcpy(button_txt[1], "Resume"); break;
case 3:
case 4: strcpy(button_txt[1], "Play"); break;
default: strcpy(button_txt[1], "Select"); break;
}
strcpy(button_txt[3], " ");
mask = 0x07;
break;
}
case MENU_BTNSTYLE_MENU: {
strcpy(button_txt[1], "Set/Chg");
strcpy(button_txt[3], "Back");
break;
}
case MENU_BTNSTYLE_NAME_IDLE: {
strcpy(button_txt[0], "Next");
strcpy(button_txt[1], "Edit");
strcpy(button_txt[2], "Prev");
strcpy(button_txt[3], "Done");
break;
}
case MENU_BTNSTYLE_NAME_EDIT: {
strcpy(button_txt[0], "Next");
strcpy(button_txt[1], "Char Done");
strcpy(button_txt[2], "Prev");
strcpy(button_txt[3], "Backsp");
break;
}
case MENU_BTNSTYLE_LED_EDIT:
case MENU_BTNSTYLE_OPTION_EDIT: {
strcpy(button_txt[0], "+");
strcpy(button_txt[1], "OK");
strcpy(button_txt[2], "-");
strcpy(button_txt[3], "OK");
break;
}
case MENU_BTNSTYLE_PROGSIMPLE_IDLE: {
strcpy(button_txt[0], "->");
strcpy(button_txt[1], "Edit");
strcpy(button_txt[2], "<-");
strcpy(button_txt[3], "Exit Editor");
break;
}
case MENU_BTNSTYLE_PROGSIMPLE_EDIT: {
strcpy(button_txt[0], "+");
strcpy(button_txt[1], "Done");
strcpy(button_txt[2], "-");
strcpy(button_txt[3], "Next ->");
break;
}
case MENU_BTNSTYLE_ABOUT: {
for (i = 0; i < 4; i++) {
button_txt[i][0] = 0x20;
button_txt[i][1] = 0x00;
}
if (mask == 0x08) {
strcpy(button_txt[3], "Back");
}
break;
}
}
for (i = 0; i < 4; i++) {
if (button_txt[i][0] == 0) {
strcpy(button_txt[i], "----");
}
}
ssd1306fb_set_cursor(18, -2);
ssd1306fb_draw_str(font_Dialog_plain_8, button_txt[0], 0);
w = ssd1306fb_get_str_width(font_Dialog_plain_8, button_txt[1], strlen(button_txt[1]), 0);
ssd1306fb_set_cursor(116 - w, -2);
ssd1306fb_draw_str(font_Dialog_plain_8, button_txt[1], 0);
ssd1306fb_set_cursor(18, 24);
ssd1306fb_draw_str(font_Dialog_plain_8, button_txt[2], 0);
w = ssd1306fb_get_str_width(font_Dialog_plain_8, button_txt[3], strlen(button_txt[3]), 0);
ssd1306fb_set_cursor(116 - w, 24);
ssd1306fb_draw_str(font_Dialog_plain_8, button_txt[3], 0);
// indicators
i = 16; j = 0; // lower and upper left
while (j < 6) {
if (mask & 0x02) ssd1306fb_draw_hline(11, i, 31 - j);
if (mask & 0x01) ssd1306fb_draw_hline(11, i, j);
i--; j++;
}
i = 118; j = 0; // lower and upper right
while (j < 6) {
if (mask & 0x08) ssd1306fb_draw_hline(i, 123, 31 - j);
if (mask & 0x04) ssd1306fb_draw_hline(i, 123, j);
i++; j++;
}
}
void menu_draw_tabs(uint8_t active_idx)
{
uint8_t h;
uint8_t i;
uint8_t x, y, s;
uint8_t count = menu->count;
h = (SSD1306_HEIGHT - 1) / count; // individual height
y = ((SSD1306_HEIGHT - (h * count)) >> 1); // starting offset
s = y; // memorized offset
for (i = 0; i < count; i++) {
x = (i == active_idx) ? 0 : 2;
ssd1306fb_draw_hline(x, 6, y);
ssd1306fb_draw_vline(x, y, y + h);
y += h;
if (i == active_idx) {
ssd1306fb_draw_hline(0, 6, y);
}
if (y >= SSD1306_HEIGHT) {
y = SSD1306_HEIGHT - 1;
}
}
ssd1306fb_draw_hline(2, 6, y);
ssd1306fb_draw_vline(6, s, y); // right line
}

View File

@ -0,0 +1,45 @@
/*
* $Id: menu_def.c 499 2021-07-26 05:24:02Z true $
* begin 20190527 true
*
* main menu
*/
#include "menu.h"
#include "user_config.h"
// fake menu when exiting
const MenuItem menu_none = {1, 0, 0, 0, &menu_none_disp, &menu_start};
// root menu
// exit, snek, morble, set name, leds, options, about
const MenuItem menu_0 = {7, MENU_FLAG_SCROLL,
0, 0, &menu_0_disp, &menu_0_enter};
// name menu
// set name, display mode, flip mode, font select, char spacing, half-width, color invert
const MenuItem menu_1 = {7, MENU_FLAG_SCROLL | MENU_FLAG_SAVE_ON_EXIT,
(MenuItem *)&menu_0, 1, &menu_1_disp, &menu_1_enter};
// led menu
// edge mode, edge setup, eyes mode, eyes setup, favcolor hue, sat, val, altcolor hue, sat, val
const MenuItem menu_2 = {10, MENU_FLAG_SCROLL | MENU_FLAG_SAVE_ON_EXIT,
(MenuItem *)&menu_0, 2, &menu_2_disp, &menu_2_enter};
// snek menu
const MenuItem menu_3_snek = {1, MENU_FLAG_NONE, //MENU_FLAG_SAVE_ON_EXIT,
(MenuItem *)&menu_0, 3, &snek_disp, NULL};
// options menu
// leds on/off, sleep on/off, sleep threshold, wake threshold, recal lightsense, show cpu usage, show accel "angle",
const MenuItem menu_5 = {7, MENU_FLAG_SCROLL | MENU_FLAG_SAVE_ON_EXIT,
(MenuItem *)&menu_0, 5, &menu_5_disp, &menu_5_enter};
// about menu
// credits, leds, leds, leds, accel, cpu usage/uptime, light/temp, errors, font test
const MenuItem menu_6 = {9, MENU_FLAG_SCROLL,
(MenuItem *)&menu_0, 6, &menu_6_disp, 0};

View File

@ -0,0 +1,324 @@
/*
* $Id: menu_entry_0.c 494 2021-07-21 11:46:11Z true $
* begin 20190527 true
*
* main menu functions
*/
#include "menu.h"
#include "render/font.h"
#include "misc/accel.h"
#include "misc/sin7.h"
#include "global.h"
#include "user_config.h"
#include <string.h>
#include <stdio.h>
#define ROTATE_TARGET_WIDTH 40
#define ROTATE_TARGET_HEIGHT 32
#define ROTATE_NAME_LEN_FULLRATE 15 // set to length in characters of name to run at
#define ROTATE_NAME_LEN_HALFRATE 15 // the specified rate, max. exceeding half rate
// name length will not allow dynamic names.
// if your MCU has i2c DMA, set these to max name.
// if no i2c DMA, then set based on reported CPU
// usage per update rate, and tweak update rate.
uint8_t rotsrc_fb[(ROTATE_TARGET_WIDTH * (ROTATE_TARGET_HEIGHT >> 3)) + 1];
uint8_t rotdst_fb[(ROTATE_TARGET_WIDTH * (ROTATE_TARGET_HEIGHT >> 3)) + 1];
SSD1306 rotsrc;
SSD1306 rotdst;
uint8_t wiggle = 1;
static void menu_none_init()
{
rotsrc.width = rotdst.width = ROTATE_TARGET_WIDTH;
rotsrc.height = rotdst.height = ROTATE_TARGET_HEIGHT;
rotsrc.mode = rotsrc_fb;
rotsrc.fb = rotsrc_fb + 1;
rotdst.mode = rotdst_fb;
rotdst.fb = rotdst_fb + 1;
}
static void menu_none_set_halfwidth(SSD1306 *dst)
{
if (uconf.nameconf & UCONF_NAME_MODE_HALFWIDTH) {
dst->state |= SSD1306_STATE_STR_HALFWIDTH;
} else {
dst->state &= ~SSD1306_STATE_STR_HALFWIDTH;
}
}
static void menu_none_set_flipmirror(uint8_t flip_flag)
{
if (uconf.nameconf & UCONF_NAME_MODE_AUTOROTATE) {
if (sysflags & flip_flag) {
ssd1306_set_flipmirror(1);
} else {
ssd1306_set_flipmirror(0);
}
} else if (uconf.nameconf & UCONF_NAME_MODE_ROTATE180) {
ssd1306_set_flipmirror(1);
} else {
ssd1306_set_flipmirror(0);
}
}
const uint8_t err_namewide_1[] = "Name Too Wide";
const uint8_t err_namewide_2[] = "Shorten the name, reduce";
const uint8_t err_namewide_3[] = "spacing or try a smaller font.";
const uint8_t err_namelong_1[] = "Name Too Long";
const uint8_t err_namelong_2[] = "Names 10 to 15 chars long";
const uint8_t err_namelong_3[] = "must use Horizontal orientation.";
static void menu_none_print_error(uint8_t *err1, uint8_t *err2, uint8_t *err3)
{
ssd1306fb_set_cursor(0, 0);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, (const char *)err1, 0);
ssd1306fb_set_cursor(0, 12);
ssd1306fb_draw_str(font_Dialog_plain_8, (const char *)err2, 0);
ssd1306fb_set_cursor(0, 20);
ssd1306fb_draw_str(font_Dialog_plain_8, (const char *)err3, 0);
return;
}
void menu_none_disp(uint8_t idx)
{
uint8_t i, j;
uint8_t o;
uint16_t w;
int8_t left, top;
int8_t rot;
char txt[6];
(idx = idx);
menu_none_init();
// total width and left / starting boundary
menu_none_set_halfwidth(&rotsrc);
ssd1306fb_set_target(&rotsrc);
w = ssd1306fb_get_str_width(font_table[uconf.font_idx].font,
uconf.name,
strlen(uconf.name),
uconf.char_spacing);
if (w > oled.width + 16) {
menu_none_print_error((uint8_t *)err_namewide_1, (uint8_t *)err_namewide_2, (uint8_t *)err_namewide_3);
}
left = (oled.width >> 1) - (w >> 1);
top = oled.height - ssd1306fb_get_font_height(font_table[uconf.font_idx].font) - 1;
// get rotation
rot = accel_get_rotation();
// render modes
switch (uconf.nameconf & UCONF_NAME_DISP_MASK) {
case UCONF_NAME_DISP_STATIC_HORIZ: {
menu_none_set_flipmirror(SYS_OLED_ROTATE_X);
menu_none_set_halfwidth(&oled);
ssd1306fb_set_target(&oled);
ssd1306fb_set_cursor(left, top);
ssd1306fb_draw_str(font_table[uconf.font_idx].font, uconf.name, uconf.char_spacing);
break;
}
case UCONF_NAME_DISP_STATIC_VERT: {
menu_none_set_flipmirror(SYS_OLED_ROTATE_Y);
rot = 95;
goto MENU_0_DISP_CHAR_ROTATE;
}
case UCONF_NAME_DISP_WIGGLE: {
menu_none_set_flipmirror(SYS_OLED_ROTATE_X);
// why this value? why the fuck not. value is the "speed" of the wiggle
wiggle += MISC_WIGGLE_RATE;
wiggle += (uconf.framemod == UCONF_FRAMERATE_FULL) ? 0 : MISC_WIGGLE_RATE;
// the shift is the maximum "angle" of the wiggle
rot = (cos7(wiggle) >> MISC_WIGGLE_SHIFT);
goto MENU_0_DISP_CHAR_ROTATE;
}
case UCONF_NAME_DISP_CHAR_ROTATE: {
ssd1306_set_flipmirror(0);
MENU_0_DISP_CHAR_ROTATE:
// set pixel printing color - only set it on the output, not on rotation buffers
ssd1306fb_set_target(&oled);
ssd1306fb_set_color(uconf.nameconf & UCONF_NAME_MODE_COLOR_INVERT ?
SSD1306_STATE_INVERT_PIXEL : SSD1306_STATE_SET_PIXEL);
j = strlen(uconf.name);
// set framerate, or abort if invalid
if (j > ROTATE_NAME_LEN_FULLRATE) {
if (j > ROTATE_NAME_LEN_HALFRATE) {
menu_none_print_error((uint8_t *)err_namelong_1, (uint8_t *)err_namelong_2, (uint8_t *)err_namelong_3);
return;
} else {
uconf.framemod = UCONF_FRAMERATE_HALF;
}
} else {
uconf.framemod = UCONF_FRAMERATE_FULL;
}
// configure rotation buffer
ssd1306fb_set_target(&rotsrc);
ssd1306fb_set_color(SSD1306_STATE_SET_PIXEL);
menu_none_set_halfwidth(&rotsrc);
txt[1] = 0;
for (i = 0; i < strlen(uconf.name); i++) {
j = i;
// change character order
if (uconf.nameconf & UCONF_NAME_DISP_MASK) {
// this method is a bit jumpy, but works for now
if (sysflags & SYS_OLED_REVERSE_CHARS) {
j = strlen(uconf.name) - i - 1;
}
}
txt[0] = uconf.name[j];
// set target buffer, need to do this before width calc
// as this will have the halfwidth bit set appropriately
ssd1306fb_set_target(&rotsrc);
// get left drawing position, so the character is rendered centered
o = ssd1306fb_get_str_width(font_table[uconf.font_idx].font, txt, 1, 0);
w = ((rotsrc.width >> 1) - (o >> 1));
// clear buffers before work
ssd1306_cls(&rotdst);
ssd1306_cls(&rotsrc);
// render and rotate
ssd1306fb_set_cursor(w, top);
ssd1306fb_draw_str(font_table[uconf.font_idx].font, txt, 0);
ssd1306fb_rotate(&rotsrc, &rotdst, rot);
// copy dst buffer to oled buffer in correct position
ssd1306fb_set_target(&oled);
ssd1306fb_copy(left - w, 0, ROTATE_TARGET_WIDTH, ROTATE_TARGET_HEIGHT, rotdst.fb);
// update position of character based on spacing
left += o + uconf.char_spacing;
}
break;
}
}
ssd1306fb_set_target(&oled);
if (uconf.flags & UCONF_FLAGS_SHOW_ACCEL_ANGLE) {
sprintf(txt, "%+3d", accel_get_rotation());
ssd1306fb_set_cursor(90, 0);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, txt, 1);
}
if (uconf.flags & UCONF_FLAGS_SHOW_CPU_USAGE) {
sprintf(txt, "%3u%%", cpu_use);
ssd1306fb_set_cursor(90, 20);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, txt, 1);
}
oled.state &= ~SSD1306_STATE_STR_HALFWIDTH;
}
void menu_0_disp(uint8_t idx)
{
char txt[12];
uint8_t w;
ssd1306fb_set_color(SSD1306_STATE_SET_PIXEL);
// circle next to item
ssd1306fb_draw_circle(24, 16, (mtick & 0x04) ? 2 : 1);
// which item selected?
ssd1306fb_set_cursor(32, 9);
switch (idx) {
case 0: strcpy(txt, "Nametag!"); break;
case 1: strcpy(txt, "Name Setup"); break;
case 2: strcpy(txt, "LED Setup"); break;
case 3: strcpy(txt, "Snek"); break;
case 4: strcpy(txt, "Morble"); break;
case 5: strcpy(txt, "Options"); break;
case 6: strcpy(txt, "About"); break;
}
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, txt, 1);
// disabled / incomplete entries
switch (idx) {
case 4: {
w = ssd1306fb_get_str_width(font_DejaVu_Sans_Mono_Bold_11, txt, strlen(txt), 1);
ssd1306fb_draw_hline(32, w + 32, 16);
ssd1306fb_draw_hline(32, w + 32, 17);
}
}
// draw extras
menu_draw_tabs(idx);
menu_draw_buttons(MENU_BTNSTYLE_MAIN, idx);
}
void menu_0_enter(uint8_t idx)
{
switch (idx) {
case 0: {
menu_stop(0);
return;
}
case 1: {
menu = (MenuItem *)&menu_1;
menu_idx = 0;
return;
}
case 2: {
menu = (MenuItem *)&menu_2;
menu_idx = 0;
return;
}
case 3: {
menu = (MenuItem *)&menu_3_snek;
return;
}
case 4: {
return;
}
case 5: {
menu = (MenuItem *)&menu_5;
menu_idx = 0;
return;
}
case 6: {
menu = (MenuItem *)&menu_6;
menu_idx = 0;
return;
}
}
}

View File

@ -0,0 +1,398 @@
/*
* $Id: menu_entry_1.c 494 2021-07-21 11:46:11Z true $
* begin 20190612 true
*
* name setup menu functions
*/
#include "menu.h"
#include "render/font.h"
#include "user_config.h"
#include <stdio.h>
#include <string.h>
#define EDIT_MODE_OFF 0x00
#define EDIT_MODE_IDLE 0x01
#define EDIT_MODE_EDIT 0x02
static uint8_t menu_last;
static uint8_t edit_mode = 0;
static uint8_t edit_pos;
uint8_t menu_1_get_offset()
{
uint8_t a;
if (edit_pos < strlen(uconf.name)) {
a = uconf.name[edit_pos];
if (a < 0x30) {
return 0x30;
} else if (a < 0x40) {
return 0x40;
} else if (a < 0x60) {
return 0x60;
}
}
return 0x20;
}
void menu_1_btn_next(uint8_t idx)
{
uint8_t first, last, len;
if (edit_mode == EDIT_MODE_IDLE) {
edit_pos++;
if (edit_pos > strlen(uconf.name) || edit_pos > UCONF_NAME_MAXLEN) {
edit_pos = 0;
}
} else {
first = font_table[uconf.font_idx].font[FONT_FIRST_CHAR_POS];
last = font_table[uconf.font_idx].font[FONT_CHAR_NUM_POS] + first;
len = strlen(uconf.name);
if (edit_pos < UCONF_NAME_MAXLEN ) {
uconf.name[edit_pos]++;
if ((uconf.name[edit_pos] < first) || (uconf.name[edit_pos] >= last)) {
uconf.name[edit_pos] = first;
}
if (edit_pos == len) {
uconf.name[edit_pos + 1] = 0x00;
}
}
}
}
void menu_1_btn_prev(uint8_t idx)
{
uint8_t first, last, len;
if (edit_mode == EDIT_MODE_IDLE) {
if (edit_pos == 0) {
edit_pos = strlen(uconf.name) + 1;
}
edit_pos--;
} else {
first = font_table[uconf.font_idx].font[FONT_FIRST_CHAR_POS];
last = font_table[uconf.font_idx].font[FONT_CHAR_NUM_POS] + first;
len = strlen(uconf.name);
if (edit_pos < UCONF_NAME_MAXLEN) {
if (uconf.name[edit_pos] <= first) {
uconf.name[edit_pos] = last;
}
uconf.name[edit_pos]--;
if (edit_pos == len) {
uconf.name[edit_pos + 1] = 0x00;
}
}
}
}
void menu_1_btn_enter(uint8_t idx)
{
if (edit_mode == EDIT_MODE_IDLE) {
edit_mode = EDIT_MODE_EDIT;
} else {
edit_mode = EDIT_MODE_IDLE;
}
}
void menu_1_btn_exit(uint8_t idx)
{
uint8_t i;
if (edit_mode == EDIT_MODE_IDLE) {
edit_mode = EDIT_MODE_OFF;
menu_btn_use_std();
} else {
// delete
/*
if (strlen(uconf.name)) {
if (edit_pos) {
for (i = edit_pos; i < strlen(uconf.name) - 1; i++) {
uconf.name[i] = uconf.name[i+1];
}
uconf.name[i] = 0x00;
}
}*/
// backspace
if (edit_pos && (uconf.name[0] != 0)) {
for (i = edit_pos - 1; i < strlen(uconf.name); i++) {
uconf.name[i] = uconf.name[i + 1];
}
uconf.name[i] = 0x00;
edit_pos--;
}
}
}
void menu_1_btn_use()
{
btn[0].cb_push = &menu_1_btn_prev;
btn[0].cb_hold = &menu_1_btn_prev;
btn[0].cb_release = 0;
btn[0].hold = 280 / HOLD_PERIOD;
btn[0].repeat = ((edit_mode == EDIT_MODE_IDLE) ? 240 : 60) / HOLD_PERIOD;
btn[1].cb_push = &menu_1_btn_exit;
btn[1].cb_hold = 0;
btn[1].cb_release = 0;
btn[1].hold = 0;
btn[1].repeat = 0;
btn[2].cb_push = &menu_1_btn_enter;
btn[2].cb_hold = 0;
btn[2].cb_release = 0;
btn[2].hold = 0;
btn[2].repeat = 0;
btn[3].cb_push = &menu_1_btn_next;
btn[3].cb_hold = &menu_1_btn_next;
btn[3].cb_release = 0;
btn[3].hold = btn[0].hold;
btn[3].repeat = btn[0].repeat;
btn_commit_hold();
}
void menu_1_name_edit()
{
uint8_t w, x;
char txt[10];
ssd1306fb_set_cursor(10, 8);
ssd1306fb_draw_str(font_table[1].font, uconf.name, 0);
ssd1306fb_draw_circle(oled.cursor_x + 4, oled.cursor_y + 7, (mtick & 0x04) ? 2 : 1);
// draw underline
if (edit_mode == EDIT_MODE_IDLE || (edit_mode == EDIT_MODE_EDIT && (mtick & 0x08))) {
x = font_table[1].font[FONT_WIDTH_POS];
w = x - 1;
x *= edit_pos;
x += 10;
w += x;
ssd1306fb_draw_hline(x + 1, w, 21);
}
// draw character count and limits
sprintf(txt, "%2u / %02u", (uint8_t)strlen(uconf.name), (uint8_t)UCONF_NAME_MAXLEN);
ssd1306fb_set_cursor((oled.width >> 1) - (ssd1306fb_get_str_width(font_table[0].font, txt, strlen(txt), 0) >> 1), 24);
ssd1306fb_draw_str(font_table[0].font, txt, 0);
}
void menu_1_font_next()
{
do {
uconf.font_idx++;
if (uconf.font_idx >= (sizeof(font_table) / sizeof(font_table[0]))) {
uconf.font_idx = 0;
}
} while (!font_table[uconf.font_idx].tag_allowed);
}
void menu_1_disp(uint8_t idx)
{
int8_t w, x;
char txt[16];
if (edit_mode != EDIT_MODE_OFF) {
menu_1_btn_use(); // this also sets button speed as necessary
menu_1_name_edit();
goto MENU_0_DRAW_TEXT_DONE;
}
ssd1306fb_set_cursor(10, 7);
// which item selected?
switch (idx) {
case 0: {
ssd1306fb_draw_str(font_table[0].font, "Set Your Name", 1);
strcpy(txt, "Set>");
break;
}
case 1: {
MENU_0_ORIENTATION:
ssd1306fb_draw_str(font_table[0].font, "Nametag Orientation", 1);
switch (uconf.nameconf & UCONF_NAME_DISP_MASK) {
case UCONF_NAME_DISP_STATIC_HORIZ: {
strcpy(txt, "Horiz");
break;
}
case UCONF_NAME_DISP_STATIC_VERT: {
strcpy(txt, "Vert");
break;
}
case UCONF_NAME_DISP_WIGGLE: {
strcpy(txt, "Wiggle");
break;
}
case UCONF_NAME_DISP_CHAR_ROTATE: {
oled.state |= SSD1306_STATE_STR_HALFWIDTH;
strcpy(txt, "Always UP");
break;
}
}
break;
}
case 2: {
if ((uconf.nameconf & UCONF_NAME_DISP_MASK) != UCONF_NAME_DISP_CHAR_ROTATE) {
ssd1306fb_draw_str(font_table[0].font, "Nametag Flip", 1);
if (uconf.nameconf & UCONF_NAME_MODE_ROTATE180) {
strcpy(txt, "Flip");
} else if (uconf.nameconf & UCONF_NAME_MODE_AUTOROTATE) {
strcpy(txt, "Auto");
} else {
strcpy(txt, "Normal");
}
break;
} else {
if (menu_last > menu_idx) {
menu_idx--;
goto MENU_0_ORIENTATION;
} else {
menu_idx++;
goto MENU_0_FONT;
}
}
break;
}
case 3: {
MENU_0_FONT:
ssd1306fb_draw_str(font_table[0].font, "Font", 1);
ssd1306fb_set_cursor(10, 15);
ssd1306fb_draw_str(font_table[0].font, font_table[uconf.font_idx].name, 1);
ssd1306fb_set_cursor(69,
2 + oled.height - ssd1306fb_get_font_height(font_table[uconf.font_idx].font));
ssd1306fb_draw_str(font_table[uconf.font_idx].font, "ab", 1);
goto MENU_0_DRAW_TEXT_DONE;
break;
}
case 4: {
ssd1306fb_draw_str(font_table[0].font, "Character Spacing (px)", 1);
sprintf(txt, "%d", (int8_t)uconf.char_spacing);
break;
}
case 5: {
ssd1306fb_draw_str(font_table[0].font, "Render at Half Width?", 1);
strcpy(txt, (uconf.nameconf & UCONF_NAME_MODE_HALFWIDTH) ? "Yes" : "No");
break;
}
case 6: {
ssd1306fb_draw_str(font_table[0].font, "Pixel Draw Mode", 1);
strcpy(txt, (uconf.nameconf & UCONF_NAME_MODE_COLOR_INVERT) ? "Invert" : "Bright");
break;
}
}
w = ssd1306fb_get_str_width(font_table[1].font, txt, strlen(txt), 1);
x = 68 - (w >> 1);
ssd1306fb_set_cursor(x, 18);
ssd1306fb_draw_str(font_table[1].font, txt, 1);
// draw box
ssd1306fb_draw_rect(x - 2, 18, x + w + 2, 31);
MENU_0_DRAW_TEXT_DONE:
oled.state &= ~SSD1306_STATE_STR_HALFWIDTH;
// draw extras
menu_draw_tabs(idx);
if (edit_mode == EDIT_MODE_OFF) {
w = MENU_BTNSTYLE_MENU;
} else {
w = 0x10 + edit_mode;
}
menu_draw_buttons(w, 0x0f);
// remember this for skipping
menu_last = menu_idx;
}
void menu_1_enter(uint8_t idx)
{
int8_t a;
switch (idx) {
case 0: {
// start the name editor...
edit_mode = EDIT_MODE_IDLE;
edit_pos = 0;
return;
}
case 1: { // display mode
a = (uconf.nameconf & UCONF_NAME_DISP_MASK) >> UCONF_NAME_DISP_OFFSET;
if (++a >= UCONF_NAME_DISP_COUNT) {
a = 0;
}
uconf.nameconf &= ~UCONF_NAME_DISP_MASK;
uconf.nameconf |= (a << UCONF_NAME_DISP_OFFSET) & UCONF_NAME_DISP_MASK;
return;
}
case 2: { // char flip mode
if ((uconf.nameconf & UCONF_NAME_DISP_MASK) != UCONF_NAME_DISP_CHAR_ROTATE) {
if (uconf.nameconf & UCONF_NAME_MODE_AUTOROTATE) {
uconf.nameconf &= ~(UCONF_NAME_MODE_ROTATE180 | UCONF_NAME_MODE_AUTOROTATE);
} else if (uconf.nameconf & UCONF_NAME_MODE_ROTATE180) {
uconf.nameconf &= ~UCONF_NAME_MODE_ROTATE180;
uconf.nameconf |= UCONF_NAME_MODE_AUTOROTATE;
} else {
uconf.nameconf |= UCONF_NAME_MODE_ROTATE180;
}
}
return;
}
case 3: { // font
menu_1_font_next();
break;
}
case 4: {
if (++uconf.char_spacing > 9) {
uconf.char_spacing = -1;
}
break;
}
case 5: {
if (uconf.nameconf & UCONF_NAME_MODE_HALFWIDTH) {
uconf.nameconf &= ~UCONF_NAME_MODE_HALFWIDTH;
} else {
uconf.nameconf |= UCONF_NAME_MODE_HALFWIDTH;
}
break;
}
case 6: {
if (uconf.nameconf & UCONF_NAME_MODE_COLOR_INVERT) {
uconf.nameconf &= ~UCONF_NAME_MODE_COLOR_INVERT;
} else {
uconf.nameconf |= UCONF_NAME_MODE_COLOR_INVERT;
}
break;
}
}
}

View File

@ -0,0 +1,425 @@
/*
* $Id: menu_entry_2.c 495 2021-07-22 20:53:39Z true $
* begin 20190612 true
*
* led setup menu functions
*/
#include "ui/menu.h"
#include "render/font.h"
#include "led/rgbled.h"
#include "user_config.h"
#include <stdio.h>
#include <string.h>
#define EDIT_MODE_OFF 0x00
#define EDIT_MODE_IDLE 0x01
#define EDIT_MODE_EDIT 0x02
static uint8_t edit_mode = MENU_BTNSTYLE_MENU;
static uint8_t edit_idx;
static uint8_t prog_data_idx = 0;
static void menu_2_val_inc(uint8_t *val, uint8_t loop)
{
if (!loop && *val == 0xff) return;
(*val)++;
}
static void menu_2_val_dec(uint8_t *val, uint8_t loop)
{
if (!loop && *val == 0x00) return;
(*val)--;
}
void menu_2_btn_next(uint8_t idx)
{
switch (edit_idx) {
case 1:
case 3: {
switch (edit_mode) {
case MENU_BTNSTYLE_PROGSIMPLE_IDLE: {
prog_data_idx++;
if (prog_data_idx > 15) {
prog_data_idx = 0;
}
break;
}
case MENU_BTNSTYLE_PROGSIMPLE_EDIT: {
uint8_t e;
uint8_t *x;
uint8_t *s;
s = uconf.ledprog_edge_data[uconf.ledprog_edge_idx];
x = &s[prog_data_idx >> 1];
if (prog_data_idx & 0x01) {
e = *x & 0xf;
e++; if (e >= 0x10) e = 0;
*x &= 0xf0;
*x |= e & 0xf;
} else {
e = *x >> 4;
e++; if (e >= 0x10) e = 0;
*x &= 0x0f;
*x |= e << 4;
}
break;
}
}
break;
}
case 4: menu_2_val_inc(&uconf.favcolor_hue, 1); return;
case 5: menu_2_val_inc(&uconf.favcolor_sat, 0); return;
case 6: menu_2_val_inc(&uconf.favcolor_val, 0); return;
case 7: menu_2_val_inc(&uconf.altcolor_hue, 1); return;
case 8: menu_2_val_inc(&uconf.altcolor_sat, 0); return;
case 9: menu_2_val_inc(&uconf.altcolor_val, 0); return;
}
}
void menu_2_btn_prev(uint8_t idx)
{
switch (edit_idx) {
case 1:
case 3: {
switch (edit_mode) {
case MENU_BTNSTYLE_PROGSIMPLE_IDLE: {
if (prog_data_idx) {
prog_data_idx--;
} else {
prog_data_idx = 15;
}
break;
}
case MENU_BTNSTYLE_PROGSIMPLE_EDIT: {
uint8_t e;
uint8_t *x;
uint8_t *s;
s = uconf.ledprog_edge_data[uconf.ledprog_edge_idx];
x = &s[prog_data_idx >> 1];
if (prog_data_idx & 0x01) {
e = *x & 0xf;
if (!e) e = 0xf; else e--;
*x &= ~0xf;
*x |= e & 0xf;
} else {
e = *x >> 4;
if (!e) e = 0xf; else e--;
*x &= ~0xf0;
*x |= e << 4;
}
break;
}
}
break;
}
case 4: menu_2_val_dec(&uconf.favcolor_hue, 1); return;
case 5: menu_2_val_dec(&uconf.favcolor_sat, 0); return;
case 6: menu_2_val_dec(&uconf.favcolor_val, 0); return;
case 7: menu_2_val_dec(&uconf.altcolor_hue, 1); return;
case 8: menu_2_val_dec(&uconf.altcolor_sat, 0); return;
case 9: menu_2_val_dec(&uconf.altcolor_val, 0); return;
}
}
void menu_2_btn_exit(uint8_t idx)
{
edit_mode = MENU_BTNSTYLE_MENU;
menu_btn_use_std();
}
void menu_2_btn_use(uint8_t idx)
{
btn[0].cb_push = &menu_2_btn_prev;
btn[0].cb_hold = &menu_2_btn_prev;
btn[0].cb_release = 0;
btn[0].hold = 200 / HOLD_PERIOD;
btn[0].repeat = 40 / HOLD_PERIOD;
btn[1].cb_push = &menu_2_btn_exit;
btn[1].cb_hold = 0;
btn[1].cb_release = 0;
btn[1].hold = 0;
btn[1].repeat = 0;
btn[2].cb_push = &menu_2_btn_exit;
btn[2].cb_hold = 0;
btn[2].cb_release = 0;
btn[2].hold = 0;
btn[2].repeat = 0;
btn[3].cb_push = &menu_2_btn_next;
btn[3].cb_hold = &menu_2_btn_next;
btn[3].cb_release = 0;
btn[3].hold = btn[0].hold;
btn[3].repeat = btn[0].repeat;
btn_commit_hold();
}
void menu_2_edit_enter(uint8_t idx)
{
if (edit_mode == MENU_BTNSTYLE_PROGSIMPLE_IDLE) {
edit_mode = MENU_BTNSTYLE_PROGSIMPLE_EDIT;
} else {
edit_mode = MENU_BTNSTYLE_PROGSIMPLE_IDLE;
}
}
void menu_2_edit_exit(uint8_t idx)
{
if (edit_mode == MENU_BTNSTYLE_PROGSIMPLE_IDLE) {
edit_mode = MENU_BTNSTYLE_MENU;
menu_btn_use_std();
} else {
prog_data_idx++;
prog_data_idx %= 16;
}
}
void menu_2_edit_use(uint8_t idx)
{
btn[0].cb_push = &menu_2_btn_prev;
btn[0].cb_hold = &menu_2_btn_prev;
btn[0].cb_release = 0;
btn[0].hold = 280 / HOLD_PERIOD;
btn[0].repeat = 160 / HOLD_PERIOD;
btn[1].cb_push = &menu_2_edit_exit;
btn[1].cb_hold = 0;
btn[1].cb_release = 0;
btn[1].hold = 0;
btn[1].repeat = 0;
btn[2].cb_push = &menu_2_edit_enter;
btn[2].cb_hold = 0;
btn[2].cb_release = 0;
btn[2].hold = 0;
btn[2].repeat = 0;
btn[3].cb_push = &menu_2_btn_next;
btn[3].cb_hold = &menu_2_btn_next;
btn[3].cb_release = 0;
btn[3].hold = btn[0].hold;
btn[3].repeat = btn[0].repeat;
btn_commit_hold();
}
void menu_2_disp(uint8_t idx)
{
int i;
int8_t w, x;
uint8_t f;
char txt[20];
if (edit_mode == MENU_BTNSTYLE_LED_EDIT) {
menu_2_btn_use(idx);
}
ssd1306fb_set_cursor(10, 7);
if (idx >= 2 && idx <= 7) {
f = (idx - 4) / 3;
ssd1306fb_draw_str(font_table[0].font, (f == 0) ? "Favorite " : "Alternate ", 1);
}
// which item selected?
switch (idx) {
case 0: {
ssd1306fb_draw_str(font_table[0].font, "Edge Program", 1);
ssd1306fb_set_cursor(16, 15);
ssd1306fb_draw_str(font_table[0].font, edge_pgm[uconf.ledprog_edge_idx].name, 1);
goto MENU_2_DRAW_TEXT_DONE;
}
case 1: {
uint8_t *s;
sprintf(txt, "Edge");
s = uconf.ledprog_edge_data[uconf.ledprog_edge_idx];
if (edit_mode == MENU_BTNSTYLE_MENU) {
ssd1306fb_draw_str(font_table[0].font, txt, 1);
ssd1306fb_draw_str(font_table[0].font, " Settings", 1);
} else {
ssd1306fb_set_cursor(48, 0);
ssd1306fb_draw_str(font_table[1].font, txt, 1);
}
w = 16;
x = 0;
for (i = 0; i < 8; i++) {
sprintf(txt, "%01X", s[i] >> 4);
ssd1306fb_set_cursor(w, 15);
ssd1306fb_draw_str(font_table[0].font, txt, 0);
if (prog_data_idx == x++) {
if (edit_mode == MENU_BTNSTYLE_PROGSIMPLE_EDIT ||
edit_mode == MENU_BTNSTYLE_PROGSIMPLE_IDLE) {
ssd1306fb_draw_hline(w, w+4, 25);
}
if (edit_mode == MENU_BTNSTYLE_PROGSIMPLE_EDIT) {
ssd1306fb_draw_hline(w, w+4, 14);
}
}
w += 5;
sprintf(txt, "%01X", s[i] & 0xf);
ssd1306fb_set_cursor(w, 15);
ssd1306fb_draw_str(font_table[0].font, txt, 0);
if (prog_data_idx == x++) {
if (edit_mode == MENU_BTNSTYLE_PROGSIMPLE_EDIT ||
edit_mode == MENU_BTNSTYLE_PROGSIMPLE_IDLE) {
ssd1306fb_draw_hline(w, w+4, 25);
}
if (edit_mode == MENU_BTNSTYLE_PROGSIMPLE_EDIT) {
ssd1306fb_draw_hline(w, w+4, 14);
}
}
w += 8;
}
if (edit_mode == MENU_BTNSTYLE_PROGSIMPLE_EDIT) {
}
goto MENU_2_DRAW_TEXT_DONE;
}
case 2:
case 5: {
ssd1306fb_draw_str(font_table[0].font, "Color Hue", 1);
// bar position
f = (idx == 4) ? uconf.favcolor_hue : uconf.altcolor_hue;
// draw hue markers
ssd1306fb_draw_vline(36 + 21, 19, 21);
ssd1306fb_draw_vline(36 + 42, 19, 21);
goto MENU_2_EDIT_COLOR_LINE;
}
case 3:
case 6: {
ssd1306fb_draw_str(font_table[0].font, "Color Sat.", 1);
// bar position
f = (idx == 5) ? uconf.favcolor_sat : uconf.altcolor_sat;
goto MENU_2_PCT_COLOR_TEXT;
}
case 4:
case 7: {
ssd1306fb_draw_str(font_table[0].font, "Color Value", 1);
// bar position
f = (idx == 6) ? uconf.favcolor_val : uconf.altcolor_val;
MENU_2_PCT_COLOR_TEXT:
// percentage text
sprintf(txt, "%3u%%", ((uint16_t)f * 100) / 255);
ssd1306fb_set_cursor(50, 24);
ssd1306fb_draw_str(font_table[0].font, txt, 1);
MENU_2_EDIT_COLOR_LINE:
// draw value line
f >>= 2;
f += 36;
ssd1306fb_draw_hline(36, 99, 20);
// draw end markers
ssd1306fb_draw_vline(36, 19, 21);
ssd1306fb_draw_vline(36 + 63, 19, 21);
// draw position triangle
ssd1306fb_draw_hline(f - 2, f + 2, 16);
ssd1306fb_draw_line(f + 2, 16, f, 19);
ssd1306fb_draw_line(f - 2, 16, f, 19);
if (edit_mode == MENU_BTNSTYLE_LED_EDIT) {
if (idx == 4 || idx == 7) {
// RGBR indicators
ssd1306fb_set_cursor(33, 21);
ssd1306fb_draw_str(font_table[0].font, "R", 0);
oled.cursor_x = 33 + 21;
ssd1306fb_draw_str(font_table[0].font, "G", 0);
oled.cursor_x = 33 + 42;
ssd1306fb_draw_str(font_table[0].font, "B", 0);
oled.cursor_x = 33 + 63;
ssd1306fb_draw_str(font_table[0].font, "R", 0);
} else {
// 0 - 100% indicators
ssd1306fb_set_cursor(28 + 7, 21);
ssd1306fb_draw_str(font_table[0].font, "0%", 0);
oled.cursor_x = 28 + 63 - 14;
ssd1306fb_draw_str(font_table[0].font, "100%", 0);
}
}
goto MENU_2_DRAW_TEXT_DONE;
}
}
w = ssd1306fb_get_str_width(font_table[1].font, txt, strlen(txt), 1);
x = 68 - (w >> 1);
ssd1306fb_set_cursor(x, 18);
ssd1306fb_draw_str(font_table[1].font, txt, 1);
// draw box
ssd1306fb_draw_rect(x - 2, 18, x + w + 2, 31);
MENU_2_DRAW_TEXT_DONE:
// draw extras
menu_draw_tabs(idx);
menu_draw_buttons(edit_mode, 0x0f);
}
void menu_2_enter(uint8_t idx)
{
uint8_t a;
edit_idx = idx;
switch (idx) {
case 0: {
a = (sizeof(edge_pgm) / sizeof(edge_pgm[0]));
uconf.ledprog_edge_idx++;
if (uconf.ledprog_edge_idx >= a) {
uconf.ledprog_edge_idx = 0;
}
break;
}
case 1: {
edit_mode = MENU_BTNSTYLE_PROGSIMPLE_IDLE;
menu_2_edit_use(idx);
break;
}
default: {
if (edit_mode == MENU_BTNSTYLE_LED_EDIT) {
edit_mode = MENU_BTNSTYLE_MENU;
menu_btn_use_std();
} else {
edit_mode = MENU_BTNSTYLE_LED_EDIT;
}
}
}
}

View File

@ -0,0 +1,392 @@
/*
* $Id: menu_entry_3.c 494 2021-07-21 11:46:11Z true $
* begin 20200807 true
*
* snek
*/
#include "ui/menu.h"
#include "render/draw_ssd1306.h"
#include "led/rgbled.h"
#include "misc/accel.h"
#include "misc/tinymt.h"
#include "global.h"
#include "user_config.h"
#include <stdio.h>
#include <string.h>
#define SNEK_SPEED_BASE 33
#define SNEK_SPEED_MODIFIER 3
#define SNEK_SPEED_MAXSEL 11
#define SNEK_DIR_NORTH 0x01
#define SNEK_DIR_SOUTH 0x02
#define SNEK_DIR_WEST 0x04
#define SNEK_DIR_EAST 0x08
#define SNEK_FUDGE 0x80
struct Snek {
uint8_t direction;
uint8_t speed;
uint8_t timeout;
uint8_t rsvd;
int8_t head_x;
int8_t head_y;
int8_t tail_x;
int8_t tail_y;
int8_t apple_x;
int8_t apple_y;
};
static struct Snek snek;
static uint16_t playfield[64];
static uint8_t gamemode = 0xff;
static uint8_t banner;
static int8_t banner_x[4];
static int8_t banner_y[4];
void snek_renderframe()
{
uint8_t i;
uint8_t w;
int8_t a, b;
int8_t x, y;
// render apple
snek.direction += 0x40;
w = snek.direction >> 6;
x = snek.apple_x << 1;
y = snek.apple_y << 1;
switch (w) {
case 1: x++; break;
case 2: x++; y++; break;
case 3: y++; break;
}
oled.state &= ~SSD1306_STATE_PIXEL_MASK;
oled.state |= SSD1306_STATE_INVERT_PIXEL;
ssd1306fb_draw_pix(x, y);
// render
oled.state &= ~SSD1306_STATE_PIXEL_MASK;
oled.state |= SSD1306_STATE_SET_PIXEL;
for (x = 0; x < 64; x++) {
a = x << 1;
w = playfield[x] >> 8;
y = playfield[x] & 0xff;
for (i = 0; i < 8; i++) {
if (w & (1 << i)) {
b = (i + 0) << 1;
ssd1306fb_draw_vline(a+0, b, b + 1);
ssd1306fb_draw_vline(a+1, b, b + 1);
}
if (y & (1 << i)) {
b = (i + 8) << 1;
ssd1306fb_draw_vline(a+0, b, b + 1);
ssd1306fb_draw_vline(a+1, b, b + 1);
}
}
}
// do we do next step?
snek.timeout += SNEK_SPEED_MODIFIER * snek.speed;
if (snek.timeout < SNEK_SPEED_BASE) {
return;
}
snek.timeout -= SNEK_SPEED_BASE;
// move head based on direction
switch (snek.direction) {
case SNEK_DIR_NORTH: snek.head_y--; break;
case SNEK_DIR_SOUTH: snek.head_y++; break;
case SNEK_DIR_WEST: snek.head_x--; break;
case SNEK_DIR_EAST: snek.head_x++; break;
}
snek.head_x += 64;
snek.head_x %= 64;
snek.head_y += 16;
snek.head_y %= 16;
playfield[snek.head_x] |= (1 << snek.head_y);
// determine solids near tail location
w = 0;
// derender old tail
// set new tail
}
void snek_fixapple()
{
// uint8_t y_pos, y_off;
while (1) {
// is a pixel lit at this location
if (playfield[snek.apple_x] & (1 << snek.apple_y)) {
// if so, move our apple
snek.apple_x += 3;
snek.apple_x %= 64;
snek.apple_y++;
snek.apple_y %= 16;
} else {
break;
}
}
}
void snek_newgame()
{
uint16_t rnd;
uint8_t i;
// set up snek default coordinates
rnd = prng_get16();
snek.head_x = ((rnd & 0xff) % 48) + 8;
snek.head_y = ((rnd >> 8) % 8) + 4;
// what way is the head moving?
rnd = prng_get16();
snek.direction = 1 << (rnd & 0x03);
// set our initial tail based on this direction
snek.tail_x = snek.head_x;
snek.tail_y = snek.head_y;
switch (snek.direction & 0xf) {
case SNEK_DIR_NORTH: snek.tail_y++; break;
case SNEK_DIR_SOUTH: snek.tail_y--; break;
case SNEK_DIR_WEST: snek.tail_x++; break;
case SNEK_DIR_EAST: snek.tail_x--; break;
}
// position the apple
snek.apple_x = ((rnd & 0xfc) % 48) + 8;
snek.apple_y = ((rnd >> 8) % 8) + 4;
snek_fixapple();
// clear the screen
for (i = 0; i < 64; i++) {
playfield[i] = 0;
}
// render initial frame
playfield[snek.head_x] |= (1 << snek.head_y);
playfield[snek.tail_x] |= (1 << snek.tail_y);
snek_renderframe();
// start the game
gamemode = 0;
}
void snek_banner_jiggle()
{
int8_t x_off, y_off;
uint16_t rnd;
rnd = prng_get16();
x_off = (rnd ) & 0x03;
y_off = (rnd >> 8) & 0x01;
switch (banner) {
case 0: { // welcome
banner_x[0] = 31;
banner_y[0] = 0 + y_off;
break;
}
case 1: { // snek
banner_x[1] = 29;
banner_y[1] = 7 + y_off;
break;
}
case 2: { // to
banner_x[2] = 78;
banner_y[2] = 0 + y_off;
break;
}
case 3: { // speed
banner_x[3] = -1;
banner_y[3] = 11 + y_off;
break;
}
}
banner_x[banner] += x_off;
}
void snek_btn_prev(uint8_t idx)
{
if (!gamemode) {
} else {
if (snek.speed > SNEK_SPEED_MAXSEL) {
snek.speed = SNEK_SPEED_MAXSEL;
}
if (snek.speed > 1) {
snek.speed--;
}
}
}
void snek_btn_next(uint8_t idx)
{
if (!gamemode) {
} else {
if (snek.speed < SNEK_SPEED_MAXSEL) {
snek.speed++;
}
}
}
void snek_btn_enter(uint8_t idx)
{
if (gamemode != 0) {
snek_newgame();
}
}
void snek_btn_exit(uint8_t idx)
{
if (!gamemode) {
gamemode = 1;
} else {
gamemode = 0xff;
menu_btn_use_std();
menu_btn_exit(idx);
}
}
void snek_btn_use()
{
btn[0].cb_push = &snek_btn_prev;
btn[0].cb_hold = &snek_btn_prev;
btn[0].cb_release = 0;
btn[0].hold = 200 / HOLD_PERIOD;
btn[0].repeat = 40 / HOLD_PERIOD;
btn[1].cb_push = &snek_btn_exit;
btn[1].cb_hold = 0;
btn[1].cb_release = 0;
btn[1].hold = 0;
btn[1].repeat = 0;
btn[2].cb_push = &snek_btn_enter;
btn[2].cb_hold = 0;
btn[2].cb_release = 0;
btn[2].hold = 0;
btn[2].repeat = 0;
btn[3].cb_push = &snek_btn_next;
btn[3].cb_hold = &snek_btn_next;
btn[3].cb_release = 0;
btn[3].hold = btn[0].hold;
btn[3].repeat = btn[0].repeat;
btn_commit_hold();
}
void snek_init()
{
uint8_t i;
snek_btn_use();
for (i = 0; i < 5; i++) {
banner = i;
snek_banner_jiggle();
}
gamemode = 17;
}
void snek_disp(uint8_t idx)
{
// uint8_t i;
int8_t w;
char txt[12];
if (gamemode == 0xff) {
snek_init();
}
// banner jiggle
if (!banner) {
banner = 4;
}
banner--;
snek_banner_jiggle();
if (gamemode) {
ssd1306_cls(&oled);
// main menu
strcpy(txt, "play");
w = ssd1306fb_get_str_width(font_Dialog_plain_8, txt, strlen(txt), 0);
ssd1306fb_set_cursor(116 - w, -2);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 0);
strcpy(txt, "exit");
w = ssd1306fb_get_str_width(font_Dialog_plain_8, txt, strlen(txt), 0);
ssd1306fb_set_cursor(116 - w, 24);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 0);
// speed
ssd1306fb_set_cursor(-1, 6);
ssd1306fb_draw_str(font_table[0].font, "spd", 1);
ssd1306fb_set_cursor( 0, 15);
txt[0] = 0x30 + (snek.speed / 10);
txt[1] = 0x30 + (snek.speed % 10);
txt[2] = 0x00;
ssd1306fb_draw_str(font_table[1].font, txt, 1);
// welcome
ssd1306fb_set_cursor(banner_x[0], banner_y[0]);
ssd1306fb_draw_str(font_table[0].font, "welcom", 1);
// to
if (gamemode <= 9) {
ssd1306fb_set_cursor(banner_x[2], banner_y[2]);
ssd1306fb_draw_str(font_table[0].font, "to", 1);
}
// snek
if (gamemode == 1) {
ssd1306fb_set_cursor(banner_x[1], banner_y[1]);
ssd1306fb_draw_str(font_table[3].font, "snek", 1);
}
if (gamemode > 1) {
gamemode--;
}
} else {
// running the game
snek_renderframe();
}
}

View File

@ -0,0 +1,154 @@
/*
* $Id: menu_entry_5.c 499 2021-07-26 05:24:02Z true $
* begin 20190613 true
*
* settings menu functions
*/
#include "ui/menu.h"
#include "render/font.h"
#include "hw/lightsense.h"
#include "user_config.h"
#include <stdio.h>
#include <string.h>
const uint16_t sleep_times[] = {
0,
300,
600,
900,
1200,
1800,
};
void menu_5_disp(uint8_t idx)
{
int8_t w, x;
char txt[12];
ssd1306fb_set_cursor(10, 7);
uconf.flags &= ~UCONF_FLAGS_LEDS_DISABLE;
// which item selected?
switch (idx) {
case 0: {
ssd1306fb_draw_str(font_table[0].font, "Enable LEDs", 1);
strcpy(txt, (uconf.flags & UCONF_FLAGS_LEDS_ENABLE) ? "On" : "Off");
break;
}
case 1: {
ssd1306fb_draw_str(font_table[0].font, "No Movement Sleep Time", 0);
if (uconf.sleep_timeout) {
sprintf(txt, "%dmin", (uconf.sleep_timeout / 60));
} else {
strcpy(txt, "Off");
}
break;
}
case 2: {
ssd1306fb_draw_str(font_table[0].font, "No Movement Threshold", 0);
strcpy(txt, "Normal");
break;
}
case 3: {
ssd1306fb_draw_str(font_table[0].font, "Wakeup Threshold", 1);
strcpy(txt, "Normal");
break;
}
case 4: {
// constantly save value at this screen
uconf.lsens_lo_thresh = lsens_get_lo_threshold();
// ensure LEDs are disabled when calibrating
uconf.flags |= UCONF_FLAGS_LEDS_DISABLE;
ssd1306fb_draw_str(font_table[0].font, "Recal Lightsense DARK RM!", 0);
sprintf(txt, "%d", uconf.lsens_lo_thresh);
break;
}
case 5: {
ssd1306fb_draw_str(font_table[0].font, "Debug: Show CPU%", 1);
strcpy(txt, (uconf.flags & UCONF_FLAGS_SHOW_CPU_USAGE) ? "Yes" : "No");
break;
}
case 6: {
ssd1306fb_draw_str(font_table[0].font, "Debug: Show Accel", 1);
strcpy(txt, (uconf.flags & UCONF_FLAGS_SHOW_ACCEL_ANGLE) ? "Yes" : "No");
break;
}
}
w = ssd1306fb_get_str_width(font_table[1].font, txt, strlen(txt), 1);
x = 68 - (w >> 1);
ssd1306fb_set_cursor(x, 18);
ssd1306fb_draw_str(font_table[1].font, txt, 1);
// draw box
ssd1306fb_draw_rect(x - 2, 18, x + w + 2, 31);
// draw extras
menu_draw_tabs(idx);
menu_draw_buttons(MENU_BTNSTYLE_MENU, 0x0f);
}
void menu_5_enter(uint8_t idx)
{
int i, j;
switch (idx) {
case 0: {
if (uconf.flags & UCONF_FLAGS_LEDS_ENABLE) {
uconf.flags &= ~UCONF_FLAGS_LEDS_ENABLE;
} else {
uconf.flags |= UCONF_FLAGS_LEDS_ENABLE;
}
break;
}
case 1: {
j = sizeof(sleep_times) / sizeof(sleep_times[0]);
for (i = 0; i < j; i++) {
if (uconf.sleep_timeout == sleep_times[i]) {
i++;
i %= j;
uconf.sleep_timeout = sleep_times[i];
j = 0xff;
break;
}
}
// couldn't find a match, so set default
if (j != 0xff) {
uconf.sleep_timeout = sleep_times[4];
}
break;
}
case 4: {
// reset sensor threshold to recal value
lsens_set_lo_threshold(0xffff);
break;
}
case 5: {
if (uconf.flags & UCONF_FLAGS_SHOW_CPU_USAGE) {
uconf.flags &= ~UCONF_FLAGS_SHOW_CPU_USAGE;
} else {
uconf.flags |= UCONF_FLAGS_SHOW_CPU_USAGE;
}
break;
}
case 6: {
if (uconf.flags & UCONF_FLAGS_SHOW_ACCEL_ANGLE) {
uconf.flags &= ~UCONF_FLAGS_SHOW_ACCEL_ANGLE;
} else {
uconf.flags |= UCONF_FLAGS_SHOW_ACCEL_ANGLE;
}
break;
}
}
}

View File

@ -0,0 +1,339 @@
/*
* $Id: menu_entry_6.c 500 2021-08-08 19:43:38Z true $
* begin 20190527 true
*
* about menu functions
*/
#include "ui/btn.h"
#include "ui/menu.h"
#include "hw/lightsense.h"
#include "led/rgbled.h"
#include "misc/accel.h"
#include "global.h"
#include "user_config.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MCU_FLASH 448
#define MCU_SRAM 24+2
uint8_t font_index = 0;
uint8_t font_glyph = 0;
uint8_t sn_byte = 0;
void menu_6_font_index(int8_t dir)
{
font_index += dir;
if (font_index >= (sizeof(font_table) / sizeof(font_table[0]))) {
if (dir > 0) {
font_index = 0;
} else {
font_index = (sizeof(font_table) / sizeof(font_table[0])) - 1;
}
}
}
void menu_6_font_glyph(int8_t dir)
{
font_glyph += dir;
if (font_glyph > font_table[font_index].font[FONT_CHAR_NUM_POS] - 1) {
if (dir > 0) {
font_glyph = 0;
} else {
font_glyph = font_table[font_index].font[FONT_CHAR_NUM_POS] - 1;
}
}
}
void menu_6_font_next(uint8_t idx)
{
/* todo: implement button state flags
if (btn[1].held) {
menu_6_font_index(1);
} else {
menu_6_font_glyph(1);
}
*/
}
void menu_6_font_prev(uint8_t idx)
{
/*
if (btn[2].held) {
menu_6_font_index(-1);
} else {
menu_6_font_glyph(-1);
}
*/
}
void menu_6_accel_reset()
{
movement_worst = 0;
}
void menu_6_btn_use()
{
btn[1].cb_push = &menu_6_font_prev;
btn[1].cb_hold = &menu_6_font_prev;
btn[1].hold = 200 / HOLD_PERIOD;
btn[1].repeat = 40 / HOLD_PERIOD;
btn[2].cb_push = &menu_6_font_next;
btn[2].cb_hold = &menu_6_font_next;
btn[2].hold = btn[1].hold;
btn[2].repeat = btn[1].repeat;
btn_commit_hold();
}
void menu_6_disp(uint8_t idx)
{
uint8_t i;
char txt[30];
const uint8_t led_pos[4][2] = {
{10, 1},
{82, 1},
{10, 17},
{82, 17},
};
// which item selected?
switch (idx) {
case 0: {
ssd1306fb_set_cursor(11, 0);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, "GAT", 1);
ssd1306fb_set_cursor(13, 11);
ssd1306fb_draw_str(font_Dialog_plain_8, "Nametag", 1);
ssd1306fb_set_cursor(80, 0);
ssd1306fb_draw_str(font_Dialog_plain_8, "by true", 1);
if (sn_byte) {
sprintf(txt, "#%d", sn_byte);
i = ssd1306fb_get_str_width(font_DejaVu_Sans_Mono_Bold_11, txt, strlen(txt), 0);
ssd1306fb_set_cursor(97 - (i >> 1), 10);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, txt, 1);
}
sprintf(txt, "%s", vers);
ssd1306fb_set_cursor(11, 24);
ssd1306fb_draw_str(font_Dialog_plain_8, "v", 0);
ssd1306fb_set_cursor(15, 24);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 0);
ssd1306fb_set_cursor(54, 24);
ssd1306fb_draw_str(font_Dialog_plain_8, "@WhiskeyHackers", 0);
break;
}
case 1: {
ssd1306fb_set_cursor(54, 4);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, "LED", 1);
ssd1306fb_set_cursor(54, 15);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, "1-4", 1);
for (i = 0; i < 4; i++) {
ssd1306fb_set_cursor(led_pos[i][0], led_pos[i][1]);
sprintf(txt, "R%d h%03X", i + 1, hsv_edge[edge_map[i + 0]].h);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 1);
sprintf(txt, "s%02X v%02X", hsv_edge[edge_map[i + 0]].s, hsv_edge[edge_map[i + 0]].v);
oled.cursor_x = led_pos[i][0];
oled.cursor_y += 7;
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 1);
}
break;
}
case 2: {
ssd1306fb_set_cursor(54, 4);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, "LED", 1);
ssd1306fb_set_cursor(54, 15);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, "5-8", 1);
for (i = 0; i < 4; i++) {
ssd1306fb_set_cursor(led_pos[i][0], led_pos[i][1]);
sprintf(txt, "R%d h%03X", i + 5, hsv_edge[edge_map[i + 4]].h);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 1);
sprintf(txt, "s%02X v%02X", hsv_edge[edge_map[i + 4]].s, hsv_edge[edge_map[i + 4]].v);
oled.cursor_x = led_pos[i][0];
oled.cursor_y += 7;
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 1);
}
break;
}
case 3: {
ssd1306fb_set_cursor(54, 4);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, "LED", 1);
ssd1306fb_set_cursor(54, 15);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, "9-12", 1);
for (i = 0; i < 4; i++) {
ssd1306fb_set_cursor(led_pos[i][0], led_pos[i][1]);
sprintf(txt, "R%d h%03X", i + 5, hsv_edge[edge_map[i + 8]].h);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 1);
sprintf(txt, "s%02X v%02X", hsv_edge[edge_map[i + 8]].s, hsv_edge[edge_map[i + 8]].v);
oled.cursor_x = led_pos[i][0];
oled.cursor_y += 7;
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 1);
}
break;
}
case 4: {
ssd1306fb_set_cursor(10, -1);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, "Accelerometer", 1);
sprintf(txt, "X: %3d", accel.x);
ssd1306fb_set_cursor(11, 10);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 1);
sprintf(txt, "Y: %3d", accel.y);
ssd1306fb_set_cursor(11, 17);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 1);
sprintf(txt, "Z: %3d", accel.z);
ssd1306fb_set_cursor(11, 24);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 1);
sprintf(txt, "m%i", abs(accel_get_movement()));
ssd1306fb_set_cursor(104, 10);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 0);
sprintf(txt, "w%d", movement_worst);
ssd1306fb_set_cursor(106, 17);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 0);
ssd1306fb_draw_line(80, 23, 80 + (accel.x >> 1), 23 + (accel.y >> 2));
ssd1306fb_set_color(SSD1306_STATE_INVERT_PIXEL);
ssd1306fb_draw_circle(80 + (accel.x >> 1), 23 + (accel.y >> 2), 2);
ssd1306fb_set_color(SSD1306_STATE_SET_PIXEL);
break;
}
case 5: {
sprintf(txt, "CPU Load: %3u%%", cpu_use);
ssd1306fb_set_cursor(10, -1);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, txt, 1);
sprintf(txt, "CPU Max: %3u%%", cpu_max);
ssd1306fb_set_cursor(10, 9);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, txt, 1);
ssd1306fb_set_cursor(10, 23);
sprintf(txt, "Up %01d:%02d:%02d", uptime_hour, uptime_min, uptime_sec);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 0);
break;
}
case 6: {
ssd1306fb_set_cursor(10, -1);
ssd1306fb_draw_str(font_Dialog_plain_8, "Light: ", 1);
oled.cursor_x = 39;
sprintf(txt, "%d lo, %02d + %d", lsens_get_lo_threshold(), lsens_get_hi(), lsens_get_lo());
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 0);
ssd1306fb_set_cursor(10, 7);
ssd1306fb_draw_str(font_Dialog_plain_8, "Temp: Batt:", 1);
oled.cursor_x = 42;
sprintf(txt, "%d.%dC", temp_degc, temp_degc_decimal);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 0);
oled.cursor_x = 98;
sprintf(txt, "%d.%02dV", batt_volt, batt_mv);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 0);
ssd1306fb_set_cursor(10, 16);
ssd1306fb_draw_str(font_Dialog_plain_8, "Flash Conf Writes: ", 0);
sprintf(txt, "%lu", uconf.iter);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 1);
ssd1306fb_set_cursor(10, 24);
ssd1306fb_draw_str(font_Dialog_plain_8, "MCU: ", 1);
sprintf(txt, "%dK / %dK", MCU_FLASH, MCU_SRAM);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 1);
break;
}
case 7: {
ssd1306fb_set_cursor(10, -1);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, "Error Counters", 1);
/*
ssd1306fb_set_cursor(10, 10);
sprintf(txt, "i2c0: %lu e# %02x", i2c_err_cnt[0], i2c_last_err_reason[0]);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 0);
ssd1306fb_set_cursor(10, 17);
sprintf(txt, "i2c1: %lu e# %02x", i2c_err_cnt[1], i2c_last_err_reason[1]);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 0);
ssd1306fb_set_cursor(10, 24);
sprintf(txt, "uart1: s%lu i%lu", btn_short_packet_cnt, btn_invalid_packet_cnt);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 0);
ssd1306fb_set_cursor(74, 10);
sprintf(txt, "brt-pk: %04x", eyes_val_lsens);
ssd1306fb_draw_str(font_Dialog_plain_8, txt, 0);
*/
break;
}
case 8: {
ssd1306fb_set_cursor(10, -1);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, "Font Test", 1);
sprintf(txt, "Index:%3u", (uint8_t)font_index);
ssd1306fb_set_cursor(10, 9);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, txt, 1);
sprintf(txt, "Glyph:%3u", (uint8_t)(font_glyph + 0x20));
ssd1306fb_set_cursor(10, 19);
ssd1306fb_draw_str(font_DejaVu_Sans_Mono_Bold_11, txt, 1);
sprintf(txt, "%c", (font_glyph + 0x20));
ssd1306fb_draw_rect(83, 3, 116, 28);
ssd1306fb_set_cursor(85, 5);
ssd1306fb_draw_str(font_table[font_index].font, txt, 1);
break;
}
}
// draw extras
menu_draw_tabs(idx);
// buttons
switch (idx) {
case 0:
case 1:
case 2:
case 3: {
menu_btn_use_std();
break;
}
case 4: {
menu_btn_use_std();
btn[2].cb_push = menu_6_accel_reset;
break;
}
case 8: {
menu_6_btn_use();
menu_draw_buttons(MENU_BTNSTYLE_ABOUT, 0x0c);
break;
}
default: {
menu_btn_use_std();
menu_draw_buttons(MENU_BTNSTYLE_ABOUT, 0x08);
break;
}
}
}

View File

View File

@ -0,0 +1,143 @@
/*
* $Id: user_config.c 500 2021-08-08 19:43:38Z true $
* begin 20190615 true
*/
#include "user_config.h"
#include "hw/eeprom16.h"
#include "misc/checksum.h"
#include "led/rgbled.h"
#include <string.h>
UserConf uconf;
uint8_t sysflags;
uint16_t uconf_flash_offset;
static const uint8_t uconf_edge_defaults[8][8] = {
{0x03, 0x10, 0x04, 0x00, 0x00, 0x00, 0xff, 0xa0},
{0x03, 0x10, 0x04, 0x00, 0x00, 0x00, 0xff, 0xa0},
{0x15, 0x42, 0x50, 0x00, 0xa0, 0x30, 0xff, 0x20},
{0x03, 0x10, 0x04, 0x00, 0x00, 0x00, 0xff, 0xa0},
{0x03, 0x11, 0x05, 0x00, 0x00, 0x00, 0xff, 0x80},
{0x00, 0x01, 0x04, 0x00, 0x00, 0x66, 0x46, 0x80},
{0x03, 0x10, 0x04, 0x00, 0x00, 0x00, 0xff, 0xa0},
{0x03, 0x10, 0x04, 0x00, 0x00, 0x00, 0xff, 0xa0}
};
static void uconf_defaults()
{
int i;
uconf.ver = UCONF_VER;
uconf.flags = UCONF_FLAGS_LEDS_ENABLE;
uconf.framemod = UCONF_FRAMERATE_FULL;
uconf.nameconf = UCONF_NAME_DISP_CHAR_ROTATE | UCONF_NAME_MODE_AUTOROTATE | UCONF_NAME_MODE_COLOR_INVERT;
if (uconf_flash_offset == 0xf0) {
uconf.iter = 0;
}
uconf.font_idx = 3;
uconf.char_spacing = 2;
// todo: add LUT
strcpy(uconf.name, "Supercon");
uconf.favcolor_hue = 170;
uconf.favcolor_sat = 240;
uconf.favcolor_val = 32;
uconf.altcolor_hue = 0;
uconf.altcolor_sat = 240;
uconf.altcolor_val = 32;
uconf.ledprog_edge_idx = 4;
for (i = 0; i < 8; i++) {
uconf.ledprog_edge[i] = 0;
}
memcpy(uconf.ledprog_edge_data, uconf_edge_defaults, sizeof(uconf_edge_defaults));
uconf.lsens_lo_thresh = 0x6a0;
uconf.sleep_timeout = 20 * 60;
uconf.checksum = checksum_gen((uint8_t *)&uconf, sizeof(uconf) - 2);
}
static int8_t uconf_validate()
{
// version check
if (uconf.ver != UCONF_VER) {
return -1;
}
// blank check
if (uconf.checksum == 0xffff) return -2;
// checksum verify
if (!checksum_verify((uint8_t *)&uconf, sizeof(uconf) - 2, uconf.checksum)) {
return -3;
}
// fix any mistakes
if (uconf.ledprog_edge_idx > (sizeof(edge_pgm) / sizeof(edge_pgm[0]))) uconf.ledprog_edge_idx = 0;
return 0;
}
void uconf_load()
{
uint8_t i;
w25q_power_up();
for (i = 0; i < FLASH_RSVD_PAGES; i++) {
// find last page of valid config from flash
uconf_flash_offset = (FLASH_RSVD_PAGES - 1) - i;
w25q_read(uconf_flash_offset * FLASH_UCONF_BYTES, (uint8_t *)&uconf, FLASH_UCONF_BYTES);
if (!uconf_validate()) {
// valid data
return;
}
}
w25q_power_down();
// flash has no valid data whatsoever
// don't worry about flash setup; that is done during writing
uconf_flash_offset = 0xf0;
uconf_defaults();
}
void uconf_write()
{
eclic_global_interrupt_disable();
w25q_power_up();
// track writes and set up next page to write
uconf.iter++;
uconf_flash_offset++;
if (uconf_flash_offset >= FLASH_RSVD_PAGES) {
// we need to erase flash and start writing at the beginning
w25q_erase_sector(0x000000, 1);
uconf_flash_offset = 0;
}
// calculate checksum
uconf.checksum = checksum_gen((uint8_t *)&uconf, sizeof(uconf) - 2);
// write config data
w25q_write(uconf_flash_offset * FLASH_UCONF_BYTES, (uint8_t *)&uconf, FLASH_UCONF_BYTES);
w25q_power_down();
eclic_global_interrupt_enable();
}

View File

@ -0,0 +1,105 @@
/*
* user_config.h
*
* Created on: Jun 12, 2019
* Author: true
*/
#ifndef USER_CONFIG_H_
#define USER_CONFIG_H_
#include "misc/checksum.h"
// some global options
#define FLASH_RSVD_PAGES 16 // how many FLASH_UCONF_BYTES to reserve/check
#define FLASH_UCONF_BYTES 256 // basically assumed everywhere
#define MISC_WIGGLE_RATE (7 - 4);
#define MISC_WIGGLE_SHIFT 4
// system state stuff
#define SYS_OLED_ROTATE_X (1 << 0)
#define SYS_OLED_ROTATE_Y (1 << 1)
#define SYS_OLED_REVERSE_CHARS (1 << 2)
// user configuration stuff
#define UCONF_VER 0x03
#define UCONF_FLAGS_AUTOROTATE_ENA (1 << 0)
#define UCONF_FLAGS_LEDS_DISABLE (1 << 3) // used by programs to temporarily disable LEDs
#define UCONF_FLAGS_FAVOR_SPEED (1 << 4) // todo: increase clock rather than reduce framerate
#define UCONF_FLAGS_LEDS_ENABLE (1 << 5)
#define UCONF_FLAGS_SHOW_CPU_USAGE (1 << 6)
#define UCONF_FLAGS_SHOW_ACCEL_ANGLE (1 << 7)
#define UCONF_FRAMERATE_FULL 40 // this is set/used at runtime, makes no difference in saved config
#define UCONF_FRAMERATE_HALF 40 // take the tickrate divided by this for the framerate.
#define UCONF_NAME_MAXLEN 15
#define UCONF_NAME_MODE_ROTATE180 (1 << 0) // force flipping the display over
#define UCONF_NAME_MODE_AUTOROTATE (1 << 1) // automatically flip the display
#define UCONF_NAME_MODE_HALFWIDTH (1 << 2) // render only every other line of fonts
#define UCONF_NAME_MODE_COLOR_INVERT (1 << 3) // use pixel invert instead of set
#define UCONF_NAME_DISP_MASK 0x30
#define UCONF_NAME_DISP_COUNT 4
#define UCONF_NAME_DISP_OFFSET 4
#define UCONF_NAME_DISP_STATIC_HORIZ 0x00 // unchanging name
#define UCONF_NAME_DISP_STATIC_VERT 0x10 // unchanging name, each char rotated 90deg
#define UCONF_NAME_DISP_WIGGLE 0x20 // sine-wiggling name
#define UCONF_NAME_DISP_CHAR_ROTATE 0x30 // auto-rotating characters
// this must be FLASH_UCONF_BYTES long for assumptions in code to work out
typedef struct UserConf {
uint8_t ver;
uint8_t flags;
uint8_t framemod;
uint8_t nameconf; // 4
uint32_t iter; // 8
uint8_t font_idx;
int8_t char_spacing; // 10
char name[UCONF_NAME_MAXLEN + 1]; // 26
char name2[UCONF_NAME_MAXLEN + 1]; // 42
uint8_t favcolor_hue;
uint8_t favcolor_sat;
uint8_t favcolor_val; // 45
uint8_t altcolor_hue;
uint8_t altcolor_sat;
uint8_t altcolor_val; // 48
uint8_t ledprog_edge_idx;
uint8_t ledprog_eyes_idx; // 50
uint8_t ledprog_edge[16]; // 66
uint8_t ledprog_edge_data[16][8]; // 194
uint8_t padding[54]; // 248
uint16_t lsens_lo_thresh; // 250
uint16_t sleep_timeout; // 252
uint16_t tempcx10_offset; // 253-254
uint16_t checksum; // 255-256
} UserConf;
extern UserConf uconf;
extern uint8_t sysflags;
extern uint8_t batt_volt;
extern uint8_t batt_mv;
extern uint8_t temp_degc;
extern uint8_t temp_degc_decimal;
void uconf_load();
void uconf_write();
#endif /* USER_CONFIG_H_ */