Initial commit

This commit is contained in:
Puck Meerburg
2025-12-30 13:48:35 +00:00
commit 0f7b02c8a6
67 changed files with 20228 additions and 0 deletions

126
src/anim.c Normal file
View File

@@ -0,0 +1,126 @@
#include <stdint.h>
#include <stdlib.h>
#include "wang.h"
// The left- and rightwards animations work, effectively, the same
// each has a one-screen (44 pixel) dead zone at the start/end
// up and down are one-page only
// fixed just steps through each page
// snowflake idk
// picture i'm not sure yet but we'll find out
// laser same
void rotate_vertical(uint16_t *fb, int width, int count) {
for (int i = 0; i < width; i++) {
fb[i] = (fb[i] << count) | (fb[i] >> (11 - count));
}
}
void render_moved(uint16_t *fb, int index, int x, int rotate) {
for (int i = 0; i < 44; i++) fb[i] = 0;
// Read in the initial buffer.
// The image starts at 0, and continues until {width}.
// We trust the wang code not to overread.
if (x < 0) {
if (x < -44) return;
wang_read(index, 0, 44 + x, fb - x);
} else {
wang_read(index, x, 44, fb);
}
if (rotate < 0) rotate = 11 - rotate;
if (rotate > 11) rotate = rotate % 11;
rotate_vertical(fb, 44, rotate);
}
int apply_modifiers(uint16_t *fb, int frame, int blink, int marquee) {
int can_restart_blink = !blink;
int can_restart_marquee = !marquee;
if (frame % 6 > 3 && blink) {
for (int i = 0; i < 44; i++) fb[i] = 0;
can_restart_blink = frame % 6 == 5;
}
if (marquee) {
// marquee pattern is 00010001
uint16_t marquee = (0x11 << ((frame / 2) % 4));
// Apply top row
for (int i = 0; i < 44; i++) {
if (marquee & (1 << (i % 8))) fb[i] ^= 1;
}
for (int i = 1; i < 10; i++) {
if (marquee & (1 << ((i - 1) % 8))) fb[43] ^= (1 << i);
}
// The marquee pattern is now shifted by 5 bits.
for (int i = 0; i < 44; i++) {
if (marquee & (1 << ((i + 5) % 8))) fb[43 - i] ^= (1 << 10);
}
for (int i = 1; i < 10; i++) {
if (marquee & (1 << ((i + 4) % 8))) fb[0] ^= (1 << (10 - i));
}
can_restart_marquee = frame % 8 == 7;
}
return can_restart_blink && can_restart_marquee;
}
int anim_render(uint16_t *fb, int index, int frame) {
int im_width = flash_header.widths[index] * 8;
if (im_width % 44 < 5) {
im_width = im_width - (im_width % 44);
}
int speed = flash_header.speed_and_mode[index] >> 4;
frame /= 8 - speed;
int animation = flash_header.speed_and_mode[index] & 0xf;
int x = 0;
int rotate = 0;
int can_restart_lr = 1;
int can_restart_rotate = 1;
switch (animation) {
case 0: // left
x = -44 + frame;
can_restart_lr = 0;
if (x >= im_width - 1) can_restart_lr = 1;
break;
case 1: // right
x = im_width + 44 - frame;
can_restart_lr = 0;
if (x < -43) can_restart_lr = 1;
break;
case 2:
rotate = frame % 11;
can_restart_rotate = rotate == 11;
break;
case 3:
rotate = 10 -(frame % 11);
can_restart_rotate = rotate == 0;
break;
case 4:
if (im_width > 44) {
can_restart_lr = 0;
if (frame > (im_width / 44) * 16) {
can_restart_lr = 1;
}
x = (frame / 16) * 44;
}
if (im_width - x < 44) {
x -= 22 - (im_width - x) / 2;
}
}
render_moved(fb, index, x, rotate);
int can_restart_mods = apply_modifiers(fb, frame, flash_header.flash_map & (1 << index), flash_header.marquee_map & (1 << index));
return can_restart_lr && can_restart_rotate && can_restart_mods;
}

200
src/ble.c Normal file
View File

@@ -0,0 +1,200 @@
#include <stdint.h>
#include <stdlib.h>
#include "CH58x_common.h"
#include "CH58x_sys.h"
#include "CH58xBLE_LIB.h"
#include "led.h"
#include "wang.h"
int ble_on = 0;
int ble_connected = 0;
void ble_toggle(void) {
uint8_t ble_mode = ble_on ? FALSE : TRUE;
GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(ble_mode), &ble_mode);
ble_on = !ble_on;
}
// We use TMOS only for BLE things and wrap the rest.
static __attribute__((aligned(4), section(".noinit")))
uint8_t BLE_BUF[1024 * 6];
static void ble_calib_cb(void) {
Calibration_LSI(Level_128);
}
static uint8_t periph_task = INVALID_TASK_ID;
static uint16_t peripheral_task(uint8_t task_id, uint16_t events) {
if (events & SYS_EVENT_MSG) {
uint8_t *pMsg = tmos_msg_receive(periph_task);
if (pMsg) {
// process the message. noop.
tmos_msg_deallocate(pMsg);
}
return events ^ SYS_EVENT_MSG;
}
return 0;
}
static void gap_onParamUpdate(uint16_t a, uint16_t b, uint16_t c, uint16_t d) {
}
static void gap_onStateChange(gapRole_States_t a, gapRoleEvent_t *b) {
}
uint8_t advertData[] = {
0x02,
GAP_ADTYPE_FLAGS,
GAP_ADTYPE_FLAGS_GENERAL | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED,
0x03,
GAP_ADTYPE_16BIT_MORE,
LO_UINT16(0xFEE0),
HI_UINT16(0xFEE0),
};
uint8_t scanRspData[31] = {
0x05,
GAP_ADTYPE_SLAVE_CONN_INTERVAL_RANGE,
LO_UINT16(100),
HI_UINT16(100),
LO_UINT16(200),
HI_UINT16(200),
0x02,
GAP_ADTYPE_POWER_LEVEL,
6,
19,
GAP_ADTYPE_LOCAL_NAME_COMPLETE,
'L', 'S', 'L', 'E', 'D', ' ', 'B', 'a', 'd', 'g', 'e', ' ', 'W', 'i', 't', 'c', 'h', 0,
0, 0, 0, 0,
};
static gapRolesBroadcasterCBs_t broadcast_handlers = {
0,
0,
};
static gapRolesCBs_t gap_handlers = {
gap_onStateChange,
0,
gap_onParamUpdate,
};
static gapBondCBs_t bond_managers = {
0,
0,
};
static const uint16_t service_uuid = 0xFEE0;
static const gattAttrType_t service = {2, (uint8_t *) &service_uuid};
static const uint16_t rx_char_uuid = 0xFEE1;
static uint8_t rx_char_props = GATT_PROP_WRITE;
static uint8_t rx_char_val[16];
static gattAttribute_t attr_table[] = {
{{2, &primaryServiceUUID}, GATT_PERMIT_READ, 0, &service},
{{2, &characterUUID}, GATT_PERMIT_READ, 0, &rx_char_props},
{{2, &rx_char_uuid}, GATT_PERMIT_WRITE, 0, &rx_char_val},
};
uint16_t blefb[44] = {0};
static bStatus_t write_handler(uint16_t conn_handle, gattAttribute_t *pAttr, uint8_t *pValue, uint16_t len, uint16_t offset, uint8_t method) {
if (gattPermitAuthorWrite(pAttr->permissions)) return ATT_ERR_INSUFFICIENT_AUTHOR;
uint16_t uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]);
if (uuid != rx_char_uuid) return ATT_ERR_ATTR_NOT_FOUND;
wang_rx(pValue, len);
return SUCCESS;
}
static gattServiceCBs_t service_handlers = {
0,
write_handler,
0,
};
void ble_init(void) {
bleConfig_t cfg = {0};
// BLE heap
cfg.MEMAddr = (uint32_t) BLE_BUF;
cfg.MEMLen = (uint32_t) sizeof(BLE_BUF);
// not using the bonding information, as we do not support _bonding_.
// This should be changed. but. you know.
// TODO: magic numbers
// amount of buffered packets
cfg.BufNumber = (512 / 23);
// maximum data length of each packet
cfg.BufMaxLen = (64 + 4);
cfg.TxNumEvent = 1;
cfg.TxPower = LL_TX_POWEER_6_DBM;
cfg.ConnectNumber = (1 & 3) | (1 << 2);
cfg.SelRTCClock = 1 << 7;
cfg.rcCB = ble_calib_cb;
uint8_t m[6];
GetMACAddress(m);
memcpy(cfg.MacAddr, m, 6);
BLE_LibInit(&cfg);
sys_safe_access_enable();
R8_CK32K_CONFIG &= ~(RB_CLK_OSC32K_XT | RB_CLK_XT32K_PON);
sys_safe_access_enable();
R8_CK32K_CONFIG |= RB_CLK_INT32K_PON;
sys_safe_access_disable();
Calibration_LSI(Level_128);
RTC_InitTime(2020, 1, 1, 0, 0, 0);
TMOS_TimerInit(0);
periph_task = TMOS_ProcessEventRegister(peripheral_task);
GAPRole_PeripheralInit();
// the GAP parameters all kinda suck to set. sooo
#define SET_PARAM(a, b, c, d) do { static c val = d; a(b, sizeof(val), &val); } while(0)
SET_PARAM(GAPRole_SetParameter, GAPROLE_ADVERT_ENABLED, uint8_t, FALSE);
GAPRole_SetParameter(GAPROLE_SCAN_RSP_DATA, 31, scanRspData);
GAPRole_SetParameter(GAPROLE_ADVERT_DATA, sizeof(advertData), advertData);
SET_PARAM(GAPRole_SetParameter, GAPROLE_MIN_CONN_INTERVAL, uint16_t, 6);
SET_PARAM(GAPRole_SetParameter, GAPROLE_MAX_CONN_INTERVAL, uint16_t, 500);
GGS_SetParameter(GGS_DEVICE_NAME_ATT, 20, "LED Badge Witch\0\0\0\0\0\0\0");
GAP_SetParamValue(TGAP_DISC_ADV_INT_MIN, 100);
GAP_SetParamValue(TGAP_DISC_ADV_INT_MAX, 200);
SET_PARAM(GAPBondMgr_SetParameter, GAPBOND_PERI_DEFAULT_PASSCODE, uint32_t, 0);
SET_PARAM(GAPBondMgr_SetParameter, GAPBOND_PERI_PAIRING_MODE, uint8_t, GAPBOND_PAIRING_MODE_NO_PAIRING);
SET_PARAM(GAPBondMgr_SetParameter, GAPBOND_PERI_MITM_PROTECTION, uint8_t, FALSE);
SET_PARAM(GAPBondMgr_SetParameter, GAPBOND_PERI_IO_CAPABILITIES, uint8_t, GAPBOND_IO_CAP_DISPLAY_ONLY);
SET_PARAM(GAPBondMgr_SetParameter, GAPBOND_PERI_BONDING_ENABLED, uint8_t, FALSE);
GAPRole_BroadcasterSetCB(&broadcast_handlers);
GGS_AddService(GATT_ALL_SERVICES);
GATTServApp_AddService(GATT_ALL_SERVICES);
GAPRole_PeripheralStartDevice(periph_task, &bond_managers, &gap_handlers);
GATTServApp_RegisterService(attr_table, GATT_NUM_ATTRS(attr_table), GATT_MAX_ENCRYPT_KEY_SIZE, &service_handlers);
}
struct connection_data {
uint16_t handle;
uint16_t interval;
uint16_t slave_latency;
uint16_t timeout;
};

7
src/ble.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
void ble_init(void);
extern int ble_on;
extern int ble_connected;
void ble_toggle(void);

79
src/button.c Normal file
View File

@@ -0,0 +1,79 @@
#include <stdint.h>
#include <stdlib.h>
#include "CH58x_common.h"
#include "CH58x_sys.h"
#include "CH58xBLE_LIB.h"
volatile int button_count[2] = {15, 0};
volatile int button_pressed[2] = {1, 0};
static void handle_button(int i, int state) {
if (state && button_count[i] < 16) button_count[i]++;
else if (!state && button_count[i] > 0) button_count[i]--;
if (button_count[i] == 16) button_pressed[i] = 1;
else if (button_count[i] == 0) button_pressed[i] = 0;
}
__INTERRUPT
__HIGH_CODE
void TMR3_IRQHandler(void) {
handle_button(0, !!GPIOA_ReadPortPin(GPIO_Pin_1));
handle_button(1, !GPIOB_ReadPortPin(GPIO_Pin_22));
TMR3_ClearITFlag(TMR0_3_IT_CYC_END);
}
void button_init(void)
{
GPIOA_ModeCfg(GPIO_Pin_1, GPIO_ModeIN_PD);
GPIOB_ModeCfg(GPIO_Pin_22, GPIO_ModeIN_PU);
DelayMs(100);
if (!GPIOB_ReadPortPin(GPIO_Pin_22)) {
asm volatile("j 0x00");
}
TMR3_TimerInit(FREQ_SYS / 1000); // 1ms timer
TMR3_ITCfg(ENABLE, TMR0_3_IT_CYC_END);
PFIC_EnableIRQ(TMR3_IRQn);
GPIOA_ModeCfg(GPIO_Pin_5, GPIO_ModeIN_Floating);
ADC_ExtSingleChSampInit(SampleFreq_3_2, ADC_PGA_0);
ADC_DataCalib_Rough();
ADC_ChannelCfg(1);
GPIOA_ModeCfg(GPIO_Pin_0, GPIO_ModeIN_PU);
GPIOA_ModeCfg(GPIO_Pin_2, GPIO_ModeIN_PD);
}
int get_battery_percentage(void) {
uint32_t adc = 0;
for (int i = 0; i < 24; i++) {
adc += ADC_ExcutSingleConver();
}
adc /= 24;
// We have a rough voltage divider setup
float v_adc = (adc / 4096.0 * 2.1);
float v_bat = (v_adc / 100.0 * (182.0 + 100.0));
if (v_bat < 3.3)
return 0;
if (v_bat > 4.2) return 100;
return ((v_bat - 3.3) / (4.2 - 3.3)) * 100;
}
int is_charging(void) {
return !GPIOA_ReadPortPin(GPIO_Pin_0);
}
int is_plugged(void) {
return !!GPIOA_ReadPortPin(GPIO_Pin_2);
}

8
src/button.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
void button_init(void);
extern volatile int button_pressed[2];
int get_battery_percentage(void);
int is_charging(void);
int is_plugged(void);

26
src/cdc.c Normal file
View File

@@ -0,0 +1,26 @@
#include <stdint.h>
#include <stdlib.h>
#include "CH58x_common.h"
#include "CH58x_sys.h"
#include "CH58xBLE_LIB.h"
#include "usb/core.h"
#include "wang.h"
void cdc_tick(void) {
static uint8_t buf[64];
static int count = -1;
count = usb_recv(1, buf, 64);
if (count <= 0) return;
wang_rx(buf, count);
}
void hid_tick(void) {
static uint8_t buf[64];
static int count = -1;
count = usb_recv(2, buf, 64);
if (count <= 0) return;
wang_rx(buf, count);
}

4
src/cdc.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
void cdc_tick(void);
void hid_tick(void);

9
src/img/menu.xbm Normal file
View File

@@ -0,0 +1,9 @@
#define menu_width 44
#define menu_height 11
static unsigned char menu_bits[] = {
0x01, 0x3c, 0x80, 0x07, 0x70, 0x08, 0xfe, 0xdb, 0x7f, 0xfb, 0xaf, 0x07,
0xfe, 0xdb, 0x7b, 0x7b, 0xaf, 0x07, 0xee, 0xdb, 0x72, 0x5b, 0xad, 0x07,
0xe6, 0xdb, 0x69, 0x6b, 0xab, 0x07, 0x02, 0xda, 0x73, 0xeb, 0xab, 0x07,
0xe6, 0xdb, 0x69, 0xeb, 0xab, 0x07, 0xee, 0xdb, 0x72, 0xdb, 0xad, 0x07,
0xfe, 0xdb, 0x7b, 0x3b, 0xae, 0x07, 0xfe, 0xdb, 0x7f, 0xfb, 0xaf, 0x07,
0x01, 0x3c, 0x80, 0x07, 0x30, 0x00 };

190
src/led.c Normal file
View File

@@ -0,0 +1,190 @@
#include <stdint.h>
#include <stdlib.h>
#include "CH58x_common.h"
#include "CH58x_sys.h"
#include "CH58xBLE_LIB.h"
#include "led.h"
uint32_t pa_mask;
uint32_t pb_mask;
#define A(v) v
#define B(v) (v | 0x80)
uint8_t pins[23] = {
A(15),
B(18),
B(0),
B(7),
A(12),
A(10),
A(11),
B(9),
B(8),
B(15),
B(14),
B(13),
B(12),
B(5),
A(4),
B(3),
B(4),
B(2),
B(1),
B(6),
B(21),
B(20),
B(19),
};
// Set up the pa_mask and pb_mask values.
// These are used to mask out the GPIOs we control from those we do not.
void display_init(void) {
for (int i = 0; i < 23; i++) {
if (pins[i] & 0x80) {
pb_mask |= (1 << (pins[i] & 0x7F));
} else {
pa_mask |= (1 << (pins[i] & 0x7F));
}
}
}
// Commit a struct row_buf to the display.
__HIGH_CODE
void display_commit(struct row_buf *b) {
// To avoid as much artifacting, we first tri-state all our pins.
// This is done in order of PD_DRV (which controls either the pull-down
// or the current drive capability).
R32_PA_PD_DRV = (R32_PA_PD_DRV & ~pa_mask);
R32_PB_PD_DRV = (R32_PB_PD_DRV & ~pb_mask);
// We then disable drive any of the previously-output GPIOs low.
R32_PA_OUT = (R32_PA_OUT & ~pa_mask);
R32_PB_OUT = (R32_PB_OUT & ~pb_mask);
// Now we set all GPIOs as inputs, tristating them.
R32_PA_DIR = (R32_PA_DIR & ~pa_mask);
R32_PB_DIR = (R32_PB_DIR & ~pb_mask);
// We set the nicessary pins to output (driving them low),
R32_PA_DIR |= b->pa_dir;
// Configure the current drive (which is a noop, as none of the pins should be driven high)..
R32_PA_PD_DRV |= b->pa_drv;
// (Do the same on GPIO bank B)
R32_PB_DIR |= b->pb_dir;
R32_PB_PD_DRV |= b->pb_drv;
// And drive the one pin high, setting up the entire matrix.
R32_PA_OUT |= b->pa_out;
R32_PB_OUT |= b->pb_out;
// This is most plausibly overkill, and needs some testing.
}
void display_make_buf(uint16_t *fb, struct row_buf *b, int i) {
#define SET_OUT(pin) if (pin & 0x80) { b->pb_dir |= (1 << (pin & 0x7F)); } else { b->pa_dir |= (1 << pin); }
#define SET_HI(pin) if (pin & 0x80) { b->pb_out |= (1 << (pin & 0x7F)); } else { b->pa_out |= (1 << pin); }
b->pa_dir = 0;
b->pa_out = 0;
b->pa_drv = 0;
b->pb_dir = 0;
b->pb_out = 0;
b->pb_drv = 0;
uint16_t col1 = fb[i * 2];
uint16_t col2 = fb[i * 2 + 1];
// On the first column, the lower two pins are swapped. Fix this in software.
if (i == 0) {
uint16_t bit = col1 & 1;
col1 = (col1 & 0xFFFE) | (col2 & 1);
col2 = (col2 & 0xFFFE) | bit;
}
// The LEDs are written in a zig-zag pattern.
uint32_t merged = 0;
merged |= ((col1 & 1) << 22);
merged |= ((col2 & 1) << 23);
for (int j = 0; j < 11; j++) {
col1 >>= 1;
col2 >>= 1;
merged >>= 2;
merged |= (col1 & 1) << 22;
merged |= (col2 & 1) << 23;
}
// In this LED matrix, we set row N as a high output,
// then set all other lines to tristate or low, depending
// on whether they should be on or off.
int count = 0;
for (int j = 0; j < 23; j++) {
if (j == i) continue;
if (merged & 1) {
count++;
SET_OUT(pins[j]);
}
merged >>= 1;
}
// Set the charlieplexed row.
// This avoids setting it if the row is empty,
// to decrease artifacting.
if (count) {
SET_OUT(pins[i]); SET_HI(pins[i]);
}
// If we have >5 LEDs on, enable the 20mA drive mode.
if (count > 5) {
b->pa_drv = b->pa_dir;
b->pb_drv = b->pb_dir;
}
}
void display_make_buf_all(uint16_t *fb, struct row_buf *b) {
for (int i = 0; i < 22; i++) {
display_make_buf(fb, b + i, i);
}
}
int display_brightness = 0;
static struct row_buf display_screens[22 * 2];
static struct row_buf blank = {0};
struct row_buf *display_screen = display_screens;
static struct row_buf *display_screen_render = display_screens + 22;
void display_flip(void) {
struct row_buf *new_render = display_screen == display_screens ? (display_screens + 22) : display_screens;
struct row_buf *old_render = display_screen;
__atomic_exchange(&display_screen, &display_screen_render, &display_screen_render, __ATOMIC_RELAXED);
}
__INTERRUPT
__HIGH_CODE
void TMR0_IRQHandler(void)
{
static int cur_index;
static int cur_bness;
// If this is a spurious interrupt, skip it.
if (!TMR0_GetITFlag(TMR0_3_IT_CYC_END)) return;
TMR0_ClearITFlag(TMR0_3_IT_CYC_END);
cur_index += 1;
if (cur_index > 21) {
cur_index = 0;
cur_bness++;
if (cur_bness == 16) {
cur_bness = 0;
}
}
if (cur_bness > display_brightness) {
display_commit(&blank);
} else {
display_commit(display_screen_render + cur_index);
}
}

24
src/led.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
extern uint32_t pa_mask;
extern uint32_t pb_mask;
struct row_buf {
uint32_t pa_dir;
uint32_t pa_out;
uint32_t pa_drv;
uint32_t pb_dir;
uint32_t pb_out;
uint32_t pb_drv;
};
void display_commit(struct row_buf *b);
void display_make_buf(uint16_t *fb, struct row_buf *b, int i);
void display_make_buf_all(uint16_t *fb, struct row_buf *b);
extern int display_brightness;
extern struct row_buf *display_screen;
void display_flip(void);
void display_init(void);

136
src/main.c Normal file
View File

@@ -0,0 +1,136 @@
#include <stdint.h>
#include <stdlib.h>
#include "CH58x_common.h"
#include "CH58x_sys.h"
#include "CH58xBLE_LIB.h"
#include "usb/core.h"
#include "led.h"
#include "button.h"
#include "menu.h"
#include "ble.h"
#include "cdc.h"
#include "wang.h"
int anim_render(uint16_t *fb, int index, int frame);
enum usb_control_resp bl_handler(enum usb_control_state state) {
if (usb_control_request.bmRequestType != 0x60) return USB_CONTROL_RESP_PASS;
if (usb_control_request.bRequest != 0x69)
return USB_CONTROL_RESP_STALL;
asm volatile("j 0x00");
return USB_CONTROL_RESP_ACK;
}
int btn1_was_pressed = 1;
int btn2_was_pressed = 0;
int btn1_hold = 0;
int btn2_hold = 0;
int image_index = 0;
int frame = 0;
int subframe = 0;
int main_handler(void) {
#define TIMER(btn0, btn1, v, count) if (button_pressed[0] != btn0 || button_pressed[1] != btn1) { v = 0; } else if (v < count) { v += 1; } else
static int menu_timer = 0;
TIMER(1, 0, menu_timer, 500) {
display_brightness = (display_brightness + 15) % 16;
menu_timer = 0;
menu_switch();
return 1;
}
if (button_pressed[0] && !btn1_was_pressed) {
display_brightness = (display_brightness + 1) % 16;
}
if (button_pressed[1] && !btn2_was_pressed) {
if (flash_header_valid) {
image_index++;
if (!flash_header.widths[image_index]) image_index = 0;
frame = 0;
subframe = 99999;
}
display_flip();
}
subframe++;
if (subframe > 32 && flash_header_valid) {
// approximately 60fps
subframe = 0;
uint16_t fb[44];
if (anim_render(fb, image_index, frame)) {
frame = 0;
} else {
frame++;
}
display_make_buf_all(fb, display_screen);
display_flip();
}
btn2_was_pressed = button_pressed[1];
btn1_was_pressed = button_pressed[0];
return 0;
}
int main()
{
SetSysClock(CLK_SOURCE_PLL_60MHz);
button_init();
display_init();
WWDG_SetCounter(0x7F);
WWDG_ResetCfg(ENABLE);
TMR0_TimerInit((FREQ_SYS / 16000) / 2);
TMR0_ITCfg(ENABLE, TMR0_3_IT_CYC_END);
PFIC_EnableIRQ(TMR0_IRQn);
TMR3_TimerInit(FREQ_SYS / 200);
TMR3_ITCfg(ENABLE, TMR0_3_IT_CYC_END);
PFIC_EnableIRQ(TMR3_IRQn);
usb_register_handler(bl_handler);
usb_init();
ble_init();
wang_init();
while (1) {
WWDG_SetCounter(0x7F);
static int btldr_timer = 0;
if (button_pressed[1] && button_pressed[0]) {
btldr_timer++;
if (btldr_timer > 1000) asm volatile("j 0x00");
} else {
btldr_timer = 0;
}
static int cur_handler = 0;
if (cur_handler) {
cur_handler = menu_handler();
if (!cur_handler) {
btn2_was_pressed = button_pressed[1];
btn1_was_pressed = button_pressed[0];
subframe = 99999;
}
} else
cur_handler = main_handler();
TMOS_SystemProcess();
cdc_tick();
hid_tick();
DelayMs(1);
}
}

145
src/menu.c Normal file
View File

@@ -0,0 +1,145 @@
#include <stdint.h>
#include <stdlib.h>
#include "CH58x_common.h"
#include "CH58x_sys.h"
#include "CH58xBLE_LIB.h"
#include "usb/core.h"
#include "led.h"
#include "ble.h"
#include "button.h"
#include "img/menu.xbm"
static void boop()
{
WWDG_ResetCfg(DISABLE);
PFIC_DisableIRQ(TMR0_IRQn);
PFIC_DisableIRQ(TMR3_IRQn);
// Stop wasting energy
GPIOA_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_Floating);
GPIOB_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_Floating);
GPIOA_ModeCfg(GPIO_Pin_1, GPIO_ModeIN_PD);
while (GPIOA_ReadPortPin(GPIO_Pin_1)) DelayMs(1);
DelayMs(100);
// 0 to 1 (pulldown, so implies being _pressed_)
// however, this doesn't work?
GPIOA_ITModeCfg(GPIO_Pin_1, GPIO_ITMode_HighLevel);
PFIC_EnableIRQ(GPIO_A_IRQn);
// Wait for shit to settle
while (GPIOA_ReadPortPin(GPIO_Pin_1)) DelayMs(1);
PWR_PeriphWakeUpCfg(ENABLE, RB_SLP_GPIO_WAKE, Long_Delay);
/* Good bye */
LowPower_Shutdown(0);
}
static void render_xbm(unsigned char *bits, struct row_buf *b) {
uint16_t fb[45] = {0};
int index = 0;
for (int j = 0; j < 11; j++) {
index = j * 48;
for (int i = 0; i < 44; i++) {
int bit = bits[index / 8] & (1 << (index % 8));
if (!bit) fb[i] |= (1 << j);
index++;
}
}
display_make_buf_all(fb, b);
}
int menu_index = 0;
static int btn0_pressed = 0;
static int btn1_pressed = 0;
void menu_render(void) {
char buffer[(48 * 11) / 8];
memcpy(buffer, menu_bits, sizeof(buffer));
int invert_index = 1 + menu_index * 13;
for (int i = invert_index; i < (invert_index + 9); i++) {
for (int j = 1; j < 10; j++)
buffer[(i / 8) + (j * 48 / 8)] ^= (1 << (i % 8));
}
int batt_percent = ((get_battery_percentage() + 7) * 10) / 100;
int charge = is_charging() || is_plugged();
for (int i = 39; i < 43; i++) {
for (int j = 0; j < 10; j++) {
int index = 10 - j;
if (j <= batt_percent) {
buffer[(i / 8) + (index * 48 / 8)] &= ~(1 << (i % 8));
} else if (charge && (j - 2) <= batt_percent) {
if (i % 2 == j % 2)
buffer[(i / 8) + (index * 48 / 8)] &= ~(1 << (i % 8));
}
}
}
if (!ble_on) {
#define PIX(x, y) buffer[((x) / 8) + ((y) * 48 / 8)] |= (1 << ((x) % 8))
for (int i = 0; i < 11; i += 2) {
PIX(13 + i, 0);
PIX(13 + i, 10);
PIX(13, i);
PIX(23, i);
}
}
render_xbm(buffer, display_screen);
display_flip();
}
void menu_switch(void) {
btn0_pressed = button_pressed[0];
btn1_pressed = button_pressed[1];
menu_index = 0;
menu_render();
}
int menu_handler(void) {
static int btldr_timer = 0;
static int render_ticks = 0;
render_ticks++;
if (button_pressed[1] && button_pressed[0]) {
btldr_timer++;
if (btldr_timer > 2000) asm volatile("j 0x00");
} else {
btldr_timer = 0;
}
if (button_pressed[0] && !btn0_pressed && !button_pressed[1]) {
menu_index = (menu_index + 1) % 3;
menu_render();
render_ticks = 0;
}
if (button_pressed[1] && !btn1_pressed && !button_pressed[0]) {
if (menu_index == 0) {
return 0;
} else if (menu_index == 1) {
ble_toggle();
menu_render();
render_ticks = 0;
} else if (menu_index == 2) {
boop();
}
}
if (render_ticks > 125) {
render_ticks = 0;
menu_render();
}
btn0_pressed = button_pressed[0];
btn1_pressed = button_pressed[1];
return 1;
}

4
src/menu.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
void menu_switch(void);
int menu_handler(void);

232
src/usb/core.c Normal file
View File

@@ -0,0 +1,232 @@
#include <stdlib.h>
#include "core.h"
#include "CH58x_common.h"
static volatile uint8_t *ep_t_len_regs[] = {
&R8_UEP0_T_LEN,
&R8_UEP1_T_LEN,
&R8_UEP2_T_LEN,
&R8_UEP3_T_LEN,
&R8_UEP4_T_LEN,
&R8_UEP5_T_LEN,
&R8_UEP6_T_LEN,
&R8_UEP7_T_LEN,
};
static volatile uint8_t *ep_ctrl_regs[] = {
&R8_UEP0_CTRL,
&R8_UEP1_CTRL,
&R8_UEP2_CTRL,
&R8_UEP3_CTRL,
&R8_UEP4_CTRL,
&R8_UEP5_CTRL,
&R8_UEP6_CTRL,
&R8_UEP7_CTRL,
};
static uint8_t recvlens[8];
uint8_t bConfigurationValue = 0;
// The hardware requires EP0 and EP4 to share a contiguous DMA buffer.
// Fuck if I know why.
__attribute__((aligned(4))) volatile uint8_t ep04_buf[64 + 64 + 64];
__attribute__((aligned(4))) volatile uint8_t epbuf[(8 - 2) * 2 * 64];
volatile uint8_t *buf_for_ep(uint8_t ep, int dir_in) {
if (ep == 0) return ep04_buf;
if (ep == 4) return ep04_buf + (dir_in ? 128 : 64);
// EP0 and EP4 aren't in here. so skip them.
ep -= 1;
if (ep > 3) ep -= 1;
return epbuf + (ep * 128 + (dir_in ? 64 : 0));
}
// Configure the response to set for the next token for the endpoint.
// The interrupt handler will reconfigure this to return NAK whenever
// the next token has been received.
enum usb_endpoint_state set_endpoint_state(uint8_t endpoint, enum usb_endpoint_state state) {
int dir_in = endpoint & 0x80; // 0 -> OUT endpoint, 1 -> IN endpoint
int ep_num = endpoint & 0x7F;
if (dir_in) {
uint8_t prev_state = *ep_ctrl_regs[ep_num] & MASK_UEP_T_RES;
*ep_ctrl_regs[ep_num] = (*ep_ctrl_regs[ep_num] & ~MASK_UEP_T_RES) | (state & 0x03);
return prev_state;
} else {
uint8_t prev_state = (*ep_ctrl_regs[ep_num] & MASK_UEP_R_RES) >> 2;
*ep_ctrl_regs[ep_num] = (*ep_ctrl_regs[ep_num] & ~MASK_UEP_R_RES) | ((state & 0x03) << 2);
return prev_state;
}
}
// Toggle the DATA0/DATA1 toggle bit, indicating which of
// DATA0 and DATA1 to use fo rthe next receiving or sending of data.
// These bits are only toggled once data has been successfully transferred;
// a la USB_EP_STATE_ACK.
//
// The SETUP control transfers on endoint 0 _always_ use DATA0.
static void flip_endpoint_data_toggle(uint8_t endpoint) {
int dir_in = endpoint & 0x80;
int ep_num = endpoint & 0x7F;
if (dir_in) {
*ep_ctrl_regs[ep_num] ^= RB_UEP_T_TOG;
} else {
*ep_ctrl_regs[ep_num] ^= RB_UEP_R_TOG;
}
}
static void handle_bus_reset() {
R8_USB_INT_FG = RB_UIF_BUS_RST;
R8_USB_DEV_AD = 0;
bConfigurationValue = 0;
}
static void handle_suspend() {
R8_USB_INT_FG = RB_UIF_SUSPEND;
// TODO
}
static void handle_sof() {
R8_USB_INT_FG = RB_UIF_HST_SOF;
}
__HIGH_CODE
static void handle_transfer(uint8_t ep_num, int is_in) {
uint8_t endpoint = ep_num | (is_in ? 0x80 : 0);
// We may have received a transfer.
// If we expected this transfer, toggle the DATA0/DATA1 bits.
enum usb_endpoint_state prev_state = set_endpoint_state(endpoint, USB_EP_STATE_NAK);
if (prev_state == USB_EP_STATE_ACK) {
flip_endpoint_data_toggle(endpoint);
}
if (ep_num == 0) {
handle_ctrl_transfer(is_in);
return;
}
if (!is_in) {
recvlens[endpoint] = R8_USB_RX_LEN;
}
}
void usb_init() {
R8_USB_CTRL = 0x00;
R8_UEP4_1_MOD = RB_UEP4_RX_EN | RB_UEP4_TX_EN |
RB_UEP1_RX_EN | RB_UEP1_TX_EN;
R8_UEP2_3_MOD = RB_UEP2_RX_EN | RB_UEP2_TX_EN |
RB_UEP3_RX_EN | RB_UEP3_TX_EN;
R8_UEP567_MOD = RB_UEP7_RX_EN | RB_UEP7_TX_EN |
RB_UEP6_RX_EN | RB_UEP6_TX_EN |
RB_UEP5_RX_EN | RB_UEP5_TX_EN;
R16_PIN_ANALOG_IE |= RB_PIN_USB_IE | RB_PIN_USB_DP_PU;
R8_UDEV_CTRL = RB_UD_PD_DIS | RB_UD_PORT_EN;
R8_USB_DEV_AD = 0x00;
R8_USB_INT_FG = 0xFF;
R16_UEP0_DMA = ep04_buf;
R16_UEP1_DMA = epbuf + 0 * 128;
R16_UEP2_DMA = epbuf + 1 * 128;
R16_UEP3_DMA = epbuf + 2 * 128;
R16_UEP5_DMA = epbuf + 3 * 128;
R16_UEP6_DMA = epbuf + 4 * 128;
R16_UEP7_DMA = epbuf + 5 * 128;
for (int i = 0; i < 8; i++) {
set_endpoint_state(i, USB_EP_STATE_NAK);
set_endpoint_state(i | 0x80, USB_EP_STATE_NAK);
}
R8_USB_CTRL = RB_UC_DEV_PU_EN | RB_UC_INT_BUSY | RB_UC_DMA_EN;
R8_USB_INT_EN = RB_UIE_SUSPEND | RB_UIE_BUS_RST | RB_UIE_TRANSFER;
PFIC_EnableIRQ(USB_IRQn);
}
__INTERRUPT
__HIGH_CODE
void USB_IRQHandler(void) {
uint8_t interrupt = R8_USB_INT_FG;
if (interrupt & RB_UIF_BUS_RST) {
handle_bus_reset();
}
if (interrupt & RB_UIF_SUSPEND) {
handle_suspend();
}
if (interrupt & RB_UIF_TRANSFER) {
uint8_t status = R8_USB_INT_ST;
uint8_t ep_num = status & MASK_UIS_ENDP;
uint8_t token = ((status & MASK_UIS_TOKEN) >> 4) & 0x03;
if (token == 0x00) { // OUT
if (status & RB_UIS_TOG_OK) handle_transfer(ep_num, 0);
R8_USB_INT_FG = RB_UIF_TRANSFER;
} else if (token == 0x01) { // SOF
handle_sof();
R8_USB_INT_FG = RB_UIF_TRANSFER;
} else if (token == 0x02) { // IN
handle_transfer(ep_num, 1);
R8_USB_INT_FG = RB_UIF_TRANSFER;
}
if ((status & RB_UIS_SETUP_ACT)) {
handle_setup_request();
R8_USB_INT_FG = RB_UIF_TRANSFER;
}
}
}
// Receive up to buflen bytes from selected OUT endpoint.
// If data is not yet available, returns -1.
int16_t usb_recv(uint8_t endpoint, void *buffer, size_t buflen) {
if (recvlens[endpoint] == 0xFF) {
set_endpoint_state(endpoint, USB_EP_STATE_ACK);
return -1;
}
if (recvlens[endpoint] < buflen) {
buflen = recvlens[endpoint];
}
recvlens[endpoint] = 0xFF;
memcpy(buffer, buf_for_ep(endpoint, 0), buflen);
return buflen;
}
// Sends up to buflen bytes to selected IN endpoint.
// If the buffer is still busy, returns -1.
int16_t usb_xmit(uint8_t endpoint, void *buffer, size_t buflen) {
if ((*ep_ctrl_regs[endpoint] & MASK_UEP_T_RES) != USB_EP_STATE_NAK) {
return -1;
}
if (buflen > 64) buflen = 64;
memcpy(buf_for_ep(endpoint, 1), buffer, buflen);
*ep_t_len_regs[endpoint] = buflen;
set_endpoint_state(endpoint | 0x80, USB_EP_STATE_ACK);
return buflen;
}
int usb_can_xmit(uint8_t endpoint) {
return (*ep_ctrl_regs[endpoint] & MASK_UEP_T_RES) == USB_EP_STATE_NAK;
}
int usb_connected(void) {
return !!(R8_USB_MIS_ST & RB_UMS_SUSPEND);
}

61
src/usb/core.h Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
#include <stdint.h>
#include <stdlib.h>
enum usb_endpoint_state {
USB_EP_STATE_ACK = 0,
USB_EP_STATE_TIMEOUT = 1,
USB_EP_STATE_NAK = 2,
USB_EP_STATE_STALL = 3,
};
struct __attribute__((packed)) usb_control_request {
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
};
extern struct usb_control_request usb_control_request;
extern void *control_transfer_buf;
extern uint16_t control_transfer_len;
extern uint8_t bConfigurationValue;
extern volatile uint8_t ep04_buf[64 + 64 + 64];
extern volatile uint8_t epbuf[(8 - 2) * 2 * 64];
enum usb_control_resp {
USB_CONTROL_RESP_PASS,
USB_CONTROL_RESP_ACK,
USB_CONTROL_RESP_STALL,
};
enum usb_control_state {
USB_CONTROL_SETUP,
USB_CONTROL_DATA_SENT,
USB_CONTROL_FINISHED,
};
typedef enum usb_control_resp (*usb_control_request_handler)(enum usb_control_state);
void usb_register_handler(usb_control_request_handler);
enum usb_control_request_type {
USB_CONTROL_REQUEST_TYPE_OUT,
USB_CONTROL_REQUEST_TYPE_IN,
USB_CONTROL_REQUEST_TYPE_NO_DATA,
};
void ctrl_respond_out(void *buf, uint16_t len);
void handle_ctrl_transfer(int is_in);
int16_t usb_recv(uint8_t endpoint, void *buffer, size_t buflen);
int16_t usb_xmit(uint8_t endpoint, void *buffer, size_t buflen);
int usb_can_xmit(uint8_t ep);
void usb_init();
void handle_setup_request();
enum usb_endpoint_state set_endpoint_state(uint8_t endpoint, enum usb_endpoint_state state);
int usb_connected(void);

362
src/usb/ctrl.c Normal file
View File

@@ -0,0 +1,362 @@
#include <stdlib.h>
#include "core.h"
#include "CH58x_common.h"
struct usb_control_request usb_control_request;
// These track the currently ongoing control transfer.
void *control_transfer_buf;
uint16_t control_transfer_len;
void ctrl_respond_out(void *buf, uint16_t len) {
control_transfer_buf = buf;
if (len > usb_control_request.wLength) len = usb_control_request.wLength;
control_transfer_len = len;
}
__HIGH_CODE
static enum usb_control_request_type ctrlreq_type() {
if (!usb_control_request.wLength) {
return USB_CONTROL_REQUEST_TYPE_NO_DATA;
}
return usb_control_request.bmRequestType & 0x80 ? USB_CONTROL_REQUEST_TYPE_IN : USB_CONTROL_REQUEST_TYPE_OUT;
}
uint8_t device_descriptor[18] = {
0x12,
0x01,
0x00, 0x02,
0x00,
0x00,
0x00,
0x40,
0x16, 0x04,
0x20, 0x50,
0x20, 0x04,
0x00,
0x00,
0x00,
0x01,
};
uint8_t config_descriptor[0x6b] = {
0x09, // bLength
0x02, // bDescriptorType
0x6b, 0x00, // wTotalLength
0x03, // bNumInterfaces
0x01, // bConfigurationValue
0x00, // iConfiguration
0x80, // bmAttributes
0x64, // bMaxPower
0x09, // bLength
0x04, // bDescriptorType
0x00, // bInterfaceNumber
0x00, // bAlternateSetting
0x02, // bNumEndpoints
0x03, // bInterfaceClass (HID)
0x00, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0x00, // iInterface
// HID descriptor
0x09,
0x21,
0x11, 0x01,
0x00,
0x01,
0x22,
0x18, 0x00,
0x07,
0x05,
0x82,
0x03,
0x10, 0x00,
0x01,
0x07,
0x05,
0x02,
0x03,
0x10, 0x00,
0x01,
0x08, // bLength
0x0b, // bDescriptorType ~ INTERFACE ASSOCIATION DESCRIPTOR
0x01, // bFirstInterface
0x02, // bInterfaceCount
0x02, // bFunctionClass 0x02
0x02, // bFunctionSubclass 0x02
0x01, // bFunctionProtocol 0x01
0x00, // iFunction
0x09, // bLength
0x04, // bDescriptorType
0x01, // bInterfaceNumber
0x00, // bAlternateSetting
0x01, // bNumEndpoints
0x02, // bInterfaceClass
0x02, // bInterfaceSubClass
0x01, // bInterfaceProtocol
0x00, // iInterface
0x05, // bLength
0x24, // bDescriptorType
0x00, // bDescriptorSubtype
0x10, 0x01, // bcdCDC
0x05, // bLength
0x24, // bDescriptorType
0x01, // bDescriptorSubtype
0x00, // bmCapabilities
0x01, // bDataInterface
// ACM descripotor
0x04, // bLength
0x24, // bDescriptorType
0x02, // bDescriptorSubtype
0x00, // bmCapabilities
// ACM union descriptor
0x05, // bLength
0x24, // bDescriptorType
0x06, // bDescriptorSubtype
0x00, // bMasterInterface
0x01, // bSlaveInterface0
0x07, // bLength
0x05, // bDescriptorType
0x88, // bEndpointAddress
0x03, // bmAttributes
0x10, 0x00, // wMaxPacketSize
0x01, // bInterval
0x09, // bLength
0x04, // bDescriptorType
0x02, // bInterfaceNumber
0x00, // bAlternateSetting
0x02, // bNumEndpoints
0x0a, // bInterfaceClass
0x00, // bInterfaceSubClass
0x00, // bInterfaceProtocol
0x00, // iInterface
0x07, // bLength
0x05, // bDescriptorType
0x81, // bEndpointAddress
0x02, // bmAttributes
0x20, 0x00, // wMaxPacketSize
0x01, // bInterval
0x07, // bLength
0x05, // bDescriptorType
0x01, // bEndpointAddress
0x02, // bmAttributes
0x20, 0x00, // wMaxPacketSize
0x01, // bInterval
};
uint8_t report_descriptor[24] = {
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
0x09, 0x01, // Usage (0x01)
0xA1, 0x01, // Collection (Application)
0x09, 0x02, // Usage (0x02)
0x15, 0x00, // Logical Minimum (0)
0x25, 0xFF, // Logical Maximum (255)
0x75, 0x08, // Report Size (8)
0x95, 0x40, // Report Count (64)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0x03, // Usage (0x03)
0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0 // End Collection
};
__HIGH_CODE
static enum usb_control_resp handle_hid_request(enum usb_control_state state) {
if ((usb_control_request.bmRequestType & 0x7F) != 0x21 && (usb_control_request.bmRequestType & 0x7f) != 1) return USB_CONTROL_RESP_PASS;
if (usb_control_request.wIndex != 0) return USB_CONTROL_RESP_PASS;
if (usb_control_request.bmRequestType == 0x81 && usb_control_request.bRequest == 0x06 && usb_control_request.wValue == 0x2200) {
if (state == USB_CONTROL_SETUP) ctrl_respond_out(report_descriptor, 24);
return USB_CONTROL_RESP_ACK;
}
return USB_CONTROL_RESP_STALL;
}
__HIGH_CODE
static enum usb_control_resp handle_main_request(enum usb_control_state state) {
if (usb_control_request.bmRequestType & 0x7F != 0x00) return USB_CONTROL_RESP_PASS;
switch (usb_control_request.bRequest) {
case 0: // GET_STATUS
if (state == USB_CONTROL_SETUP) {
static uint16_t status_val = 0;
ctrl_respond_out((void *)&status_val, 2);
}
return USB_CONTROL_RESP_ACK;
case 1: // CLEAR_FEATURE
case 3: // SET_FEATURE
return USB_CONTROL_RESP_STALL;
case 5: // SET_ADDRESS
if (state == USB_CONTROL_FINISHED) {
R8_USB_DEV_AD = (uint8_t) usb_control_request.wValue;
}
return USB_CONTROL_RESP_ACK;
case 6: // GET_DESCRIPTOR
if ((usb_control_request.wValue & 0xFF00) == 0x0100) {
if (state == USB_CONTROL_SETUP) ctrl_respond_out(device_descriptor, sizeof(device_descriptor));
} else if ((usb_control_request.wValue & 0xFF00) == 0x0200) {
if (state == USB_CONTROL_SETUP) ctrl_respond_out(config_descriptor, sizeof(config_descriptor));
} else {
return USB_CONTROL_RESP_STALL;
}
return USB_CONTROL_RESP_ACK;
case 7: // SET_DESCRIPTOR
return USB_CONTROL_RESP_STALL;
case 8: // GET_CONFIGURATION
if (state == USB_CONTROL_SETUP) {
ctrl_respond_out(&bConfigurationValue, 1);
}
return USB_CONTROL_RESP_STALL;
case 9: // SET_CONFIGURATION
if (usb_control_request.wValue > 1 || usb_control_request.wLength || usb_control_request.wIndex) {
return USB_CONTROL_RESP_STALL;
}
return USB_CONTROL_RESP_ACK;
case 10: // GET_INTERFACE
case 11: // SET_INTERFACE
case 12: // SYNCH_FRAME
return USB_CONTROL_RESP_STALL;
}
return USB_CONTROL_RESP_STALL;
}
static usb_control_request_handler handlers[16] = {0};
static int handlers_index = 0;
void usb_register_handler(usb_control_request_handler handler) {
handlers[handlers_index++] = handler;
}
__HIGH_CODE
static enum usb_control_resp call_handler(enum usb_control_state state) {
enum usb_control_resp r = handle_main_request(state);
if (r != USB_CONTROL_RESP_PASS) return r;
r = handle_hid_request(state);
if (r != USB_CONTROL_RESP_PASS) return r;
for (int i = 0; i < handlers_index; i++) {
if (!handlers[i]) continue;
enum usb_control_resp resp = handlers[i](state);
if (resp != USB_CONTROL_RESP_PASS) return resp;
}
return USB_CONTROL_RESP_STALL;
}
__HIGH_CODE
void handle_ctrl_transfer(int is_in) {
if (!control_transfer_buf) {
call_handler(USB_CONTROL_FINISHED);
if (is_in) {
R8_UEP0_CTRL = RB_UEP_T_TOG | RB_UEP_R_TOG | UEP_R_RES_ACK | UEP_T_RES_STALL;
return;
}
R8_UEP0_CTRL = RB_UEP_T_TOG | RB_UEP_R_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
return;
}
if (is_in) {
// Account for the bytes having been sent.
if (control_transfer_len >= 64) {
control_transfer_len -= 64;
control_transfer_buf += 64;
} else
control_transfer_len = 0;
// If we have more data to transfer, set up the next
// DMA.
if (control_transfer_len > 0) {
memcpy(ep04_buf, control_transfer_buf, control_transfer_len > 64 ? 64 : control_transfer_len);
R8_UEP0_T_LEN = control_transfer_len > 64 ? 64 : control_transfer_len;
set_endpoint_state(0x80, USB_EP_STATE_ACK);
} else {
enum usb_control_resp result = call_handler(USB_CONTROL_DATA_SENT);
control_transfer_buf = 0;
set_endpoint_state(0x00, result == USB_CONTROL_RESP_ACK ? USB_EP_STATE_ACK : USB_EP_STATE_STALL);
}
} else {
uint8_t ack_bytes = R8_USB_RX_LEN;
if (ack_bytes > control_transfer_len) {
ack_bytes = control_transfer_len;
}
control_transfer_len -= ack_bytes;
memcpy(control_transfer_buf, ep04_buf, ack_bytes);
control_transfer_buf += ack_bytes;
if (control_transfer_len > 0) {
// We have more data to receive. so re-ready the endpoint.
set_endpoint_state(0x00, USB_EP_STATE_ACK);
} else {
enum usb_control_resp result = call_handler(USB_CONTROL_DATA_SENT);
control_transfer_buf = 0;
set_endpoint_state(0x80, result == USB_CONTROL_RESP_ACK ? USB_EP_STATE_ACK : USB_EP_STATE_STALL);
}
}
}
__HIGH_CODE
void handle_setup_request() {
// Copy the setup request from the EP0 buffer.
memcpy(&usb_control_request, ep04_buf, 8);
// We don't expect the previous request to be driven anymore.
// Reset the control transfer.
control_transfer_buf = 0;
control_transfer_len = 0;
// The next requests (and responses) will start with DATA1.
R8_UEP0_CTRL = RB_UEP_T_TOG | RB_UEP_R_TOG | UEP_R_RES_NAK | UEP_T_RES_NAK;
if (call_handler(USB_CONTROL_SETUP) != USB_CONTROL_RESP_ACK) {
control_transfer_buf = 0;
control_transfer_len = 0;
// We need to stall this request.
// This is done by stalling either the first data stage packet,
// or the status stage packet.
// In an OUT type control request, the next request will be an OUT.
// In other cases, it will always be IN.
if (ctrlreq_type() != USB_CONTROL_REQUEST_TYPE_OUT) {
set_endpoint_state(0x80, USB_EP_STATE_STALL);
} else {
set_endpoint_state(0x00, USB_EP_STATE_STALL);
}
} else {
if (ctrlreq_type() != USB_CONTROL_REQUEST_TYPE_OUT) {
int mlen = control_transfer_len;
if (mlen > 64) mlen = 64;
memcpy(ep04_buf, control_transfer_buf, mlen);
R8_UEP0_T_LEN = mlen;
set_endpoint_state(0x80, USB_EP_STATE_ACK);
} else {
set_endpoint_state(0x00, USB_EP_STATE_ACK);
}
}
}

184
src/wang.c Normal file
View File

@@ -0,0 +1,184 @@
#include <stdint.h>
#include <stdlib.h>
#include "CH58x_common.h"
#include "CH58x_sys.h"
#include "CH58xBLE_LIB.h"
#include "ISP583.h"
#include "led.h"
#include "wang.h"
#include "usb/core.h"
enum state {
RECV_HEADER,
RECV_WIDTHS,
RECV_TIME,
RECV_BLANK,
RECV_DATA,
};
__attribute__((aligned(4)))
uint8_t wang_buf[16];
int wang_buf_pos = 0;
int expected_data_chunks = 0;
int data_chunk_index = 0;
struct wang_header cur_header;
int wang_rx_inner(const uint8_t *data, int bytes) {
// First try to fill the wang_buf.
int max_bytes = 16 - wang_buf_pos;
int to_copy = bytes;
if (to_copy > max_bytes) to_copy = max_bytes;
memcpy(wang_buf + wang_buf_pos, data, to_copy);
wang_buf_pos += to_copy;
if (wang_buf_pos < 16) return to_copy;
static enum state cur_state = RECV_HEADER;
if (memcmp(wang_buf, "wang\0\0", 6) == 0) {
// We assume "wang\0\0" never happens in real data.
// This is technically a bad assumption, but also,
// who gives a shit.
cur_state = RECV_HEADER;
} else if (cur_state == RECV_HEADER) {
// Assume all our data is properly 16-byte aligned always.
// This is a really sucky assumption, though.
wang_buf_pos = 0;
return to_copy;
}
switch (cur_state) {
case RECV_HEADER:
memcpy(cur_header.magic, wang_buf, 16);
cur_state = RECV_WIDTHS;
break;
case RECV_WIDTHS:
expected_data_chunks = 0;
for (int i = 0; i < 8; i++) {
cur_header.widths[i] = ((uint16_t)wang_buf[i * 2] << 8) | ((uint16_t)wang_buf[i * 2 + 1]);
expected_data_chunks += cur_header.widths[i] * 11;
}
data_chunk_index = 0;
cur_state = RECV_TIME;
break;
case RECV_TIME:
memcpy(cur_header.padding, wang_buf, 16);
cur_state = RECV_BLANK;
break;
case RECV_BLANK:
memcpy(cur_header.blank, wang_buf, 16);
cur_state = RECV_DATA;
// We are ready to receive the data. This is where things get a bit wonky.
// _erase_ all of flash while we work on this
int to_erase = sizeof(struct wang_header) + expected_data_chunks;
while (to_erase % 16) to_erase++;
if (to_erase < EEPROM_MIN_ER_SIZE)
to_erase = EEPROM_MIN_ER_SIZE;
EEPROM_ERASE(0, to_erase);
break;
case RECV_DATA:
EEPROM_WRITE(sizeof(struct wang_header) + 16 * data_chunk_index, wang_buf, 16);
data_chunk_index++;
if ((data_chunk_index * 16) > expected_data_chunks) {
EEPROM_WRITE(0, (uint8_t *)&cur_header, sizeof(struct wang_header));
wang_init();
cur_state = RECV_HEADER;
}
break;
}
wang_buf_pos = 0;
return to_copy;
}
void wang_rx(const uint8_t *data, int length) {
while (length) {
int len = wang_rx_inner(data, length);
data += len;
length -= len;
}
}
__attribute__((aligned(4)))
struct wang_header flash_header;
int flash_header_valid = 0;
void wang_init(void) {
EEPROM_READ(0, &flash_header, sizeof(struct wang_header));
if (memcmp(flash_header.magic, "wang\0\0", 6) != 0) {
flash_header_valid = 0;
return;
}
flash_header_valid = 1;
}
void wang_render(int index, struct row_buf *output) {
uint16_t width = flash_header.widths[index];
if (width == 0) return;
int start_i = sizeof(struct wang_header);
for (int i = 0; i < index; i++) {
start_i += flash_header.widths[i] * 11;
}
// We oversize this fb to deal with whatever bullshit.
if (width > 6) width = 6;
uint16_t fb[48] = {0};
for (int j = 0; j < width; j++) {
// We read a single 11-pixel tall row, and byte-swap it into this screen.
__attribute__((aligned(4)))
uint8_t chunk[11];
EEPROM_READ(start_i + j * 11, chunk, 11);
for (int y = 0; y < 11; y++) {
for (int x = 0; x < 8; x++) {
// read most-significant-bit first
if (chunk[y] & (1 << (7 - x)))
fb[j * 8 + x] |= (1 << y);
}
}
}
display_make_buf_all(fb, output);
}
void wang_read(int index, int offset, int width, uint16_t *out) {
uint16_t iwidth = flash_header.widths[index];
if (iwidth == 0) return;
int start_i = sizeof(struct wang_header);
for (int i = 0; i < index; i++) {
start_i += flash_header.widths[i] * 11;
}
int start_page = offset / 8;
int end_page = ((offset + width - 1) / 8);
if (end_page >= iwidth) end_page = iwidth - 1;
int start_x_global = offset;
int end_x_global = offset + width;
uint16_t *out_frobbed = out - start_x_global;
for (int i = start_page; i <= end_page; i++) {
__attribute__((aligned(4)))
uint8_t chunk[11];
EEPROM_READ(start_i + i * 11, chunk, 11);
// Recalculate the start and end x relative to
// our position
int start_x = start_x_global - i * 8;
int end_x = end_x_global - i * 8;
if (start_x < 0) start_x = 0;
if (end_x > 7) end_x = 7;
uint16_t *out_page = out_frobbed + i * 8;
for (int y = 0; y < 11; y++) {
for (int x = start_x; x <= end_x; x++) {
if (chunk[y] & (1 << (7 - x)))
out_page[x] |= (1 << y);
}
}
}
}

30
src/wang.h Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
struct wang_header {
// First 16-byte chunk
uint8_t magic[6]; // "wang\0\0"
uint8_t flash_map;
uint8_t marquee_map;
uint8_t speed_and_mode[8];
// Second 16-byte chunk
uint16_t widths[8];
// Third 16-byte chunk
uint8_t padding[6];
uint8_t time[6];
uint8_t padding2[4];
// Fourth 16-byte chunk, empty
uint8_t blank[16];
};
// Receive some data in wang format
void wang_rx(const uint8_t *, int);
extern struct wang_header flash_header;
extern int flash_header_valid;
void wang_init(void);
void wang_render(int index, struct row_buf *output);
void wang_read(int index, int offset, int width, uint16_t *buf);