Initial commit
This commit is contained in:
126
src/anim.c
Normal file
126
src/anim.c
Normal 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
200
src/ble.c
Normal 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
7
src/ble.h
Normal 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
79
src/button.c
Normal 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
8
src/button.h
Normal 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
26
src/cdc.c
Normal 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
4
src/cdc.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
void cdc_tick(void);
|
||||
void hid_tick(void);
|
||||
9
src/img/menu.xbm
Normal file
9
src/img/menu.xbm
Normal 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
190
src/led.c
Normal 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
24
src/led.h
Normal 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
136
src/main.c
Normal 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
145
src/menu.c
Normal 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
4
src/menu.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
void menu_switch(void);
|
||||
int menu_handler(void);
|
||||
232
src/usb/core.c
Normal file
232
src/usb/core.c
Normal 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
61
src/usb/core.h
Normal 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
362
src/usb/ctrl.c
Normal 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
184
src/wang.c
Normal 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
30
src/wang.h
Normal 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);
|
||||
Reference in New Issue
Block a user