767 lines
24 KiB
C
767 lines
24 KiB
C
/* USER CODE BEGIN Header */
|
|
/**
|
|
******************************************************************************
|
|
* @file : main.c
|
|
* @brief : Arrive LED sign firmware
|
|
******************************************************************************
|
|
*/
|
|
/* USER CODE END Header */
|
|
#include "main.h"
|
|
|
|
/* USER CODE BEGIN Includes */
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
/* USER CODE END Includes */
|
|
|
|
/* USER CODE BEGIN PD */
|
|
#define ROWS 7
|
|
#define COLS 96
|
|
#define NUM_CHIPS 12
|
|
#define ROW_DWELL 500
|
|
#define UART_BUF 128
|
|
#define WIDE_COLS 512
|
|
|
|
/* Commands */
|
|
#define CMD_CLEAR 0x01
|
|
#define CMD_VALUES 0x02
|
|
#define CMD_HW_DEPT 0x03
|
|
#define CMD_SCROLL 0x04
|
|
/* USER CODE END PD */
|
|
|
|
/* USER CODE BEGIN PM */
|
|
/* USER CODE END PM */
|
|
|
|
SPI_HandleTypeDef hspi1;
|
|
UART_HandleTypeDef huart2;
|
|
|
|
/* USER CODE BEGIN PV */
|
|
|
|
typedef struct { GPIO_TypeDef *port; uint16_t pin; } RowPin;
|
|
static const RowPin ROW_PINS[ROWS] = {
|
|
{ROW_0_GPIO_Port, ROW_0_Pin},
|
|
{ROW_1_GPIO_Port, ROW_1_Pin},
|
|
{ROW_2_GPIO_Port, ROW_2_Pin},
|
|
{ROW_3_GPIO_Port, ROW_3_Pin},
|
|
{ROW_4_GPIO_Port, ROW_4_Pin},
|
|
{ROW_5_GPIO_Port, ROW_5_Pin},
|
|
{ROW_6_GPIO_Port, ROW_6_Pin},
|
|
};
|
|
|
|
static uint8_t fb[ROWS][NUM_CHIPS];
|
|
static uint8_t wide[ROWS][WIDE_COLS];
|
|
static uint16_t wide_cols = 0;
|
|
static int32_t scroll_x = 0;
|
|
|
|
/* UART protocol state */
|
|
static uint8_t uart_rx_byte;
|
|
static char uart_buf[UART_BUF];
|
|
static uint8_t uart_idx = 0;
|
|
static uint8_t uart_cs_acc = 0;
|
|
static uint8_t uart_cs1 = 0;
|
|
static uint8_t uart_cs2 = 0;
|
|
static uint8_t uart_len = 0;
|
|
static uint8_t uart_cmd = 0;
|
|
|
|
/* Command queue — set by ISR, read by main loop */
|
|
static volatile uint8_t pending_cmd = 0;
|
|
static volatile uint8_t pending_cmd_rdy = 0;
|
|
static char pending_msg[UART_BUF];
|
|
|
|
/* Current mode */
|
|
static uint8_t current_cmd = 0;
|
|
static char message[UART_BUF] = "";
|
|
|
|
/* USER CODE END PV */
|
|
|
|
void SystemClock_Config(void);
|
|
static void MX_GPIO_Init(void);
|
|
static void MX_SPI1_Init(void);
|
|
static void MX_USART2_UART_Init(void);
|
|
|
|
/* USER CODE BEGIN PFP */
|
|
/* USER CODE END PFP */
|
|
|
|
/* USER CODE BEGIN 0 */
|
|
|
|
/* ── 5x7 font ASCII 32-90 ── */
|
|
static const uint8_t FONT[][5] = {
|
|
{0x00,0x00,0x00,0x00,0x00}, /* ' ' */
|
|
{0x00,0x00,0x5F,0x00,0x00}, /* ! */
|
|
{0x00,0x07,0x00,0x07,0x00}, /* " */
|
|
{0x14,0x7F,0x14,0x7F,0x14}, /* # */
|
|
{0x24,0x2A,0x7F,0x2A,0x12}, /* $ */
|
|
{0x23,0x13,0x08,0x64,0x62}, /* % */
|
|
{0x36,0x49,0x55,0x22,0x50}, /* & */
|
|
{0x00,0x05,0x03,0x00,0x00}, /* ' */
|
|
{0x00,0x1C,0x22,0x41,0x00}, /* ( */
|
|
{0x00,0x41,0x22,0x1C,0x00}, /* ) */
|
|
{0x08,0x2A,0x1C,0x2A,0x08}, /* * */
|
|
{0x08,0x08,0x3E,0x08,0x08}, /* + */
|
|
{0x00,0x50,0x30,0x00,0x00}, /* , */
|
|
{0x08,0x08,0x08,0x08,0x08}, /* - */
|
|
{0x00,0x60,0x60,0x00,0x00}, /* . */
|
|
{0x20,0x10,0x08,0x04,0x02}, /* / */
|
|
{0x3E,0x51,0x49,0x45,0x3E}, /* 0 */
|
|
{0x00,0x42,0x7F,0x40,0x00}, /* 1 */
|
|
{0x42,0x61,0x51,0x49,0x46}, /* 2 */
|
|
{0x21,0x41,0x45,0x4B,0x31}, /* 3 */
|
|
{0x18,0x14,0x12,0x7F,0x10}, /* 4 */
|
|
{0x27,0x45,0x45,0x45,0x39}, /* 5 */
|
|
{0x3C,0x4A,0x49,0x49,0x30}, /* 6 */
|
|
{0x01,0x71,0x09,0x05,0x03}, /* 7 */
|
|
{0x36,0x49,0x49,0x49,0x36}, /* 8 */
|
|
{0x06,0x49,0x49,0x29,0x1E}, /* 9 */
|
|
{0x00,0x36,0x36,0x00,0x00}, /* : */
|
|
{0x00,0x56,0x36,0x00,0x00}, /* ; */
|
|
{0x00,0x08,0x14,0x22,0x41}, /* < */
|
|
{0x14,0x14,0x14,0x14,0x14}, /* = */
|
|
{0x41,0x22,0x14,0x08,0x00}, /* > */
|
|
{0x02,0x01,0x51,0x09,0x06}, /* ? */
|
|
{0x32,0x49,0x79,0x41,0x3E}, /* @ */
|
|
{0x7E,0x09,0x09,0x09,0x7E}, /* A */
|
|
{0x7F,0x49,0x49,0x49,0x36}, /* B */
|
|
{0x3E,0x41,0x41,0x41,0x22}, /* C */
|
|
{0x7F,0x41,0x41,0x41,0x3E}, /* D */
|
|
{0x7F,0x49,0x49,0x49,0x41}, /* E */
|
|
{0x7F,0x09,0x09,0x09,0x01}, /* F */
|
|
{0x3E,0x41,0x49,0x49,0x7A}, /* G */
|
|
{0x7F,0x08,0x08,0x08,0x7F}, /* H */
|
|
{0x41,0x7F,0x41,0x00,0x00}, /* I */
|
|
{0x20,0x40,0x41,0x3F,0x01}, /* J */
|
|
{0x7F,0x08,0x14,0x22,0x41}, /* K */
|
|
{0x7F,0x40,0x40,0x40,0x40}, /* L */
|
|
{0x7F,0x02,0x04,0x02,0x7F}, /* M */
|
|
{0x7F,0x04,0x08,0x10,0x7F}, /* N */
|
|
{0x3E,0x41,0x41,0x41,0x3E}, /* O */
|
|
{0x7F,0x09,0x09,0x09,0x06}, /* P */
|
|
{0x3E,0x41,0x51,0x21,0x5E}, /* Q */
|
|
{0x7F,0x09,0x19,0x29,0x46}, /* R */
|
|
{0x46,0x49,0x49,0x49,0x31}, /* S */
|
|
{0x01,0x01,0x7F,0x01,0x01}, /* T */
|
|
{0x3F,0x40,0x40,0x40,0x3F}, /* U */
|
|
{0x1F,0x20,0x40,0x20,0x1F}, /* V */
|
|
{0x7F,0x20,0x10,0x20,0x7F}, /* W */
|
|
{0x63,0x14,0x08,0x14,0x63}, /* X */
|
|
{0x03,0x04,0x78,0x04,0x03}, /* Y */
|
|
{0x61,0x51,0x49,0x45,0x43}, /* Z */
|
|
};
|
|
|
|
/* ── North star ── */
|
|
static const uint8_t STAR[7][11] = {
|
|
{0,0,0,0,1,0,0,0,0,0,0},
|
|
{0,0,0,1,1,1,0,0,0,0,0},
|
|
{1,1,0,0,1,0,0,1,1,1,1},
|
|
{1,1,1,1,1,1,1,1,1,1,1},
|
|
{1,1,0,0,1,0,0,1,1,1,1},
|
|
{0,0,0,1,1,1,0,0,0,0,0},
|
|
{0,0,0,0,1,0,0,0,0,0,0},
|
|
};
|
|
|
|
/* ── ARRIVE glyph 7x36 ── */
|
|
static const uint8_t ARRIVE_GLYPH[7][36] = {
|
|
{0,1,1,1,0,0, 0,1,1,1,0,0, 0,1,1,1,0,0, 0,1,1,1,0,0, 1,0,0,0,1,0, 1,1,1,1,1,0},
|
|
{1,0,0,0,1,0, 1,0,0,0,1,0, 1,0,0,0,1,0, 0,0,1,0,0,0, 1,0,0,0,1,0, 1,0,0,0,0,0},
|
|
{1,0,0,0,1,0, 1,0,0,0,1,0, 1,0,0,0,1,0, 0,0,1,0,0,0, 1,0,0,0,1,0, 1,0,0,0,0,0},
|
|
{1,1,1,1,1,0, 1,1,1,1,0,0, 1,1,1,1,0,0, 0,0,1,0,0,0, 0,1,0,1,0,0, 1,1,1,1,0,0},
|
|
{1,0,0,0,1,0, 1,0,1,0,0,0, 1,0,1,0,0,0, 0,0,1,0,0,0, 0,1,0,1,0,0, 1,0,0,0,0,0},
|
|
{1,0,0,0,1,0, 1,0,0,1,0,0, 1,0,0,1,0,0, 0,0,1,0,0,0, 0,0,1,0,0,0, 1,0,0,0,0,0},
|
|
{1,0,0,0,1,0, 1,0,0,0,1,0, 1,0,0,0,1,0, 0,1,1,1,0,0, 0,0,1,0,0,0, 1,1,1,1,1,0},
|
|
};
|
|
|
|
/* ════════════════════════════════════════════════
|
|
Low level display helpers
|
|
════════════════════════════════════════════════ */
|
|
|
|
static void delay_us(uint32_t us)
|
|
{
|
|
uint32_t start = DWT->CYCCNT;
|
|
uint32_t ticks = (SystemCoreClock / 1000000U) * us;
|
|
while ((DWT->CYCCNT - start) < ticks);
|
|
}
|
|
|
|
static void all_rows_off(void)
|
|
{
|
|
for (int i = 0; i < ROWS; i++)
|
|
HAL_GPIO_WritePin(ROW_PINS[i].port, ROW_PINS[i].pin, GPIO_PIN_SET);
|
|
}
|
|
|
|
static void display_refresh(void)
|
|
{
|
|
for (int row = 0; row < ROWS; row++) {
|
|
all_rows_off();
|
|
HAL_SPI_Transmit(&hspi1, fb[row], NUM_CHIPS, HAL_MAX_DELAY);
|
|
HAL_GPIO_WritePin(ROW_PINS[row].port, ROW_PINS[row].pin, GPIO_PIN_RESET);
|
|
delay_us(ROW_DWELL);
|
|
}
|
|
all_rows_off();
|
|
}
|
|
|
|
static void refresh_for(uint32_t ms)
|
|
{
|
|
uint32_t t = HAL_GetTick();
|
|
while ((HAL_GetTick() - t) < ms) { display_refresh(); }
|
|
}
|
|
|
|
/* ── Check if a new command arrived — call from long-running loops ── */
|
|
static uint8_t check_new_cmd(void)
|
|
{
|
|
return pending_cmd_rdy;
|
|
}
|
|
|
|
/* ════════════════════════════════════════════════
|
|
Framebuffer helpers
|
|
════════════════════════════════════════════════ */
|
|
|
|
static void fb_set_pixel(int col, int row, uint8_t on)
|
|
{
|
|
if (col < 0 || col >= COLS || row < 0 || row >= ROWS) return;
|
|
int chip = col / 8;
|
|
int bit = 7 - (col % 8);
|
|
if (on) fb[row][chip] |= (uint8_t)(1 << bit);
|
|
else fb[row][chip] &= ~(uint8_t)(1 << bit);
|
|
}
|
|
|
|
static void render_logo(int star_col, int text_col, int mask_col)
|
|
{
|
|
memset(fb, 0, sizeof(fb));
|
|
for (int row = 0; row < 7; row++) {
|
|
for (int sc = 0; sc < 11; sc++) {
|
|
int col = star_col + sc;
|
|
if (STAR[row][sc] && (mask_col < 0 || col >= mask_col))
|
|
fb_set_pixel(col, row, 1);
|
|
}
|
|
for (int tc = 0; tc < 36; tc++) {
|
|
int col = text_col + tc;
|
|
if (ARRIVE_GLYPH[row][tc] && (mask_col < 0 || col >= mask_col))
|
|
fb_set_pixel(col, row, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void render_logo_rows(int star_col, int text_col, uint8_t row_mask)
|
|
{
|
|
memset(fb, 0, sizeof(fb));
|
|
for (int row = 0; row < 7; row++) {
|
|
if (!(row_mask & (1 << row))) continue;
|
|
for (int sc = 0; sc < 11; sc++) {
|
|
int col = star_col + sc;
|
|
if (STAR[row][sc]) fb_set_pixel(col, row, 1);
|
|
}
|
|
for (int tc = 0; tc < 36; tc++) {
|
|
int col = text_col + tc;
|
|
if (ARRIVE_GLYPH[row][tc]) fb_set_pixel(col, row, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ── Draw text string into fb at given x offset, return end col ── */
|
|
static int render_text_to_fb(const char *txt, int x_offset)
|
|
{
|
|
int col = x_offset;
|
|
while (*txt) {
|
|
uint8_t c = (uint8_t)*txt++;
|
|
if (c >= 'a' && c <= 'z') c -= 32;
|
|
if (c >= 32 && c <= 90) {
|
|
const uint8_t *g = FONT[c - 32];
|
|
for (int i = 0; i < 5; i++, col++) {
|
|
for (int row = 0; row < ROWS; row++) {
|
|
if (g[i] & (1 << row)) fb_set_pixel(col, row, 1);
|
|
}
|
|
}
|
|
}
|
|
col++;
|
|
}
|
|
return col;
|
|
}
|
|
|
|
/* ── Render text centred on display ── */
|
|
static void render_centred_text(const char *txt)
|
|
{
|
|
int len = 0;
|
|
const char *p = txt;
|
|
while (*p++) len++;
|
|
int text_width = len * 6 - 1;
|
|
int x = (COLS - text_width) / 2;
|
|
memset(fb, 0, sizeof(fb));
|
|
render_text_to_fb(txt, x);
|
|
}
|
|
|
|
/* ── Render word into wide[] for scrolling, returns total width ── */
|
|
static uint16_t render_scroll(const char *txt, int x_start)
|
|
{
|
|
memset(wide, 0, sizeof(wide));
|
|
int col = x_start;
|
|
while (*txt && col < WIDE_COLS - 6) {
|
|
uint8_t c = (uint8_t)*txt++;
|
|
if (c >= 'a' && c <= 'z') c -= 32;
|
|
if (c >= 32 && c <= 90) {
|
|
const uint8_t *g = FONT[c - 32];
|
|
for (int i = 0; i < 5 && col < WIDE_COLS; i++, col++) {
|
|
for (int row = 0; row < ROWS; row++) {
|
|
if (g[i] & (1 << row)) wide[row][col] = 1;
|
|
}
|
|
}
|
|
}
|
|
col++;
|
|
}
|
|
return (uint16_t)(col + COLS);
|
|
}
|
|
|
|
static void update_fb_from_scroll(void)
|
|
{
|
|
for (int row = 0; row < ROWS; row++) {
|
|
memset(fb[row], 0, NUM_CHIPS);
|
|
for (int col = 0; col < COLS; col++) {
|
|
uint16_t src = (uint16_t)(scroll_x + col);
|
|
if (src < WIDE_COLS && wide[row][src])
|
|
fb_set_pixel(col, row, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ── Update fb from scroll but only write to cols >= clip_left ── */
|
|
static void update_fb_from_scroll_clipped(int clip_left)
|
|
{
|
|
for (int row = 0; row < ROWS; row++) {
|
|
for (int col = clip_left; col < COLS; col++) {
|
|
uint16_t src = (uint16_t)(scroll_x + (col - clip_left));
|
|
if (src < WIDE_COLS)
|
|
fb_set_pixel(col, row, wide[row][src]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ── Fade rows out (centre outward to top+bottom) ── */
|
|
static void fade_out_rows(int star_col, int text_col, uint8_t is_logo)
|
|
{
|
|
uint8_t masks[] = {0x7E, 0x7C, 0x3C, 0x1C, 0x0C, 0x08, 0x00};
|
|
uint8_t snap[ROWS][NUM_CHIPS];
|
|
memcpy(snap, fb, sizeof(fb));
|
|
for (int i = 0; i < 7; i++) {
|
|
if (is_logo) {
|
|
render_logo_rows(star_col, text_col, masks[i]);
|
|
} else {
|
|
memset(fb, 0, sizeof(fb));
|
|
for (int row = 0; row < ROWS; row++) {
|
|
if (masks[i] & (1 << row))
|
|
memcpy(fb[row], snap[row], NUM_CHIPS);
|
|
}
|
|
}
|
|
refresh_for(60);
|
|
}
|
|
memset(fb, 0, sizeof(fb));
|
|
}
|
|
|
|
/* ════════════════════════════════════════════════
|
|
Arrive boot / splash animation
|
|
════════════════════════════════════════════════ */
|
|
|
|
static void arrive_animation(void)
|
|
{
|
|
const int text_col = 23;
|
|
const int star_col = 61;
|
|
|
|
/* Phase 1 — star builds */
|
|
memset(fb, 0, sizeof(fb));
|
|
for (int row = 0; row < 7; row++) fb_set_pixel(star_col + 4, row, 1);
|
|
for (int sc = 3; sc <= 5; sc++) fb_set_pixel(star_col + sc, 3, 1);
|
|
refresh_for(120);
|
|
|
|
memset(fb, 0, sizeof(fb));
|
|
for (int row = 0; row < 7; row++)
|
|
for (int sc = 3; sc <= 7; sc++)
|
|
if (STAR[row][sc]) fb_set_pixel(star_col + sc, row, 1);
|
|
refresh_for(120);
|
|
|
|
render_logo(star_col, text_col, star_col);
|
|
refresh_for(150);
|
|
|
|
/* Phase 2 — glint pulse */
|
|
memset(fb, 0, sizeof(fb)); refresh_for(80);
|
|
render_logo(star_col, text_col, star_col); refresh_for(80);
|
|
memset(fb, 0, sizeof(fb)); refresh_for(60);
|
|
render_logo(star_col, text_col, star_col); refresh_for(100);
|
|
|
|
/* Phase 3 — wipe reveal */
|
|
for (int mask = star_col; mask >= text_col - 1; mask -= 2) {
|
|
render_logo(star_col, text_col, mask);
|
|
refresh_for(18);
|
|
}
|
|
render_logo(star_col, text_col, -1);
|
|
|
|
/* Phase 4 — hold */
|
|
refresh_for(2000);
|
|
|
|
/* Phase 5 — fade out */
|
|
fade_out_rows(star_col, text_col, 1);
|
|
refresh_for(200);
|
|
}
|
|
|
|
/* ════════════════════════════════════════════════
|
|
CMD_VALUES — Curious / Focused / Together loop
|
|
════════════════════════════════════════════════ */
|
|
|
|
static const char *VALUES_WORDS[] = { "CURIOUS", "FOCUSED", "TOGETHER" };
|
|
#define VALUES_COUNT 3
|
|
|
|
static void run_values(void)
|
|
{
|
|
uint8_t idx = 0;
|
|
|
|
while (!check_new_cmd()) {
|
|
/* Arrive splash with fade out */
|
|
arrive_animation();
|
|
if (check_new_cmd()) break;
|
|
|
|
/* Display word centred — no fade in, just show it */
|
|
render_centred_text(VALUES_WORDS[idx]);
|
|
refresh_for(2500);
|
|
if (check_new_cmd()) break;
|
|
|
|
/* Fade out same as arrive splash — rows collapse inward */
|
|
fade_out_rows(0, 0, 0);
|
|
if (check_new_cmd()) break;
|
|
|
|
/* Wait before next cycle */
|
|
memset(fb, 0, sizeof(fb));
|
|
refresh_for(600);
|
|
if (check_new_cmd()) break;
|
|
|
|
idx = (idx + 1) % VALUES_COUNT;
|
|
}
|
|
}
|
|
|
|
/* ════════════════════════════════════════════════
|
|
CMD_HW_DEPT — logo left, scrolling text right
|
|
════════════════════════════════════════════════ */
|
|
|
|
/* Small logo: text_col=2, star_col=40, total=53 cols
|
|
Leaves cols 54-95 (42 cols) for scroll text */
|
|
#define HW_TEXT_COL 2
|
|
#define HW_STAR_COL 40
|
|
#define HW_SCROLL_START 54
|
|
|
|
static void run_hw_dept(void)
|
|
{
|
|
const char *hw_txt = "HARDWARE DEPT ";
|
|
/* Start text one scroll-zone width to the right so it scrolls in from edge */
|
|
const int scroll_zone = COLS - HW_SCROLL_START; /* ~42 cols */
|
|
uint16_t hw_wide_cols = render_scroll(hw_txt, scroll_zone);
|
|
int32_t hw_scroll_x = 0;
|
|
uint32_t last_scroll = HAL_GetTick();
|
|
const uint32_t scroll_ms = 40;
|
|
|
|
while (!check_new_cmd()) {
|
|
/* Draw logo into fb */
|
|
render_logo(HW_STAR_COL, HW_TEXT_COL, -1);
|
|
|
|
/* Overlay scrolling text on right side */
|
|
update_fb_from_scroll_clipped(HW_SCROLL_START);
|
|
|
|
/* Advance scroll */
|
|
if ((HAL_GetTick() - last_scroll) >= scroll_ms) {
|
|
last_scroll = HAL_GetTick();
|
|
hw_scroll_x++;
|
|
if (hw_scroll_x >= hw_wide_cols) hw_scroll_x = 0;
|
|
scroll_x = hw_scroll_x;
|
|
}
|
|
|
|
display_refresh();
|
|
}
|
|
}
|
|
|
|
/* ════════════════════════════════════════════════
|
|
CMD_SCROLL — scroll arbitrary text
|
|
════════════════════════════════════════════════ */
|
|
|
|
static void run_scroll(void)
|
|
{
|
|
wide_cols = render_scroll(message, COLS);
|
|
scroll_x = 0;
|
|
update_fb_from_scroll();
|
|
|
|
uint32_t last_scroll = HAL_GetTick();
|
|
const uint32_t scroll_ms = 40;
|
|
|
|
while (!check_new_cmd()) {
|
|
if ((HAL_GetTick() - last_scroll) >= scroll_ms) {
|
|
last_scroll = HAL_GetTick();
|
|
scroll_x++;
|
|
if (scroll_x >= wide_cols) scroll_x = 0;
|
|
update_fb_from_scroll();
|
|
}
|
|
display_refresh();
|
|
}
|
|
}
|
|
|
|
/* ════════════════════════════════════════════════
|
|
UART protocol state machine
|
|
Packet: 'A' 'R' CMD LEN CS1 CS2 [MSG...]
|
|
CS1 = sum(msg) & 0xFF
|
|
CS2 = ~CS1 & 0xFF
|
|
════════════════════════════════════════════════ */
|
|
|
|
typedef enum {
|
|
UART_WAIT_A, UART_WAIT_R, UART_WAIT_CMD,
|
|
UART_WAIT_LEN, UART_WAIT_CS1, UART_WAIT_CS2, UART_WAIT_DATA,
|
|
} UartState;
|
|
|
|
static UartState uart_state = UART_WAIT_A;
|
|
|
|
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
|
|
{
|
|
if (huart->Instance != USART2) return;
|
|
uint8_t b = uart_rx_byte;
|
|
|
|
switch (uart_state) {
|
|
case UART_WAIT_A:
|
|
if (b == 'A') uart_state = UART_WAIT_R;
|
|
break;
|
|
case UART_WAIT_R:
|
|
uart_state = (b == 'R') ? UART_WAIT_CMD : UART_WAIT_A;
|
|
break;
|
|
case UART_WAIT_CMD:
|
|
uart_cmd = b;
|
|
uart_state = UART_WAIT_LEN;
|
|
break;
|
|
case UART_WAIT_LEN:
|
|
uart_len = b;
|
|
uart_idx = 0;
|
|
uart_cs_acc = 0;
|
|
uart_state = UART_WAIT_CS1;
|
|
break;
|
|
case UART_WAIT_CS1:
|
|
uart_cs1 = b;
|
|
uart_state = UART_WAIT_CS2;
|
|
break;
|
|
case UART_WAIT_CS2:
|
|
uart_cs2 = b;
|
|
uart_state = (uart_len > 0) ? UART_WAIT_DATA : UART_WAIT_A;
|
|
if (uart_len == 0) {
|
|
/* No payload — validate empty checksum (cs1=cs2=0 for empty) */
|
|
pending_cmd = uart_cmd;
|
|
pending_msg[0] = '\0';
|
|
pending_cmd_rdy = 1;
|
|
}
|
|
break;
|
|
case UART_WAIT_DATA:
|
|
uart_buf[uart_idx++] = (char)b;
|
|
uart_cs_acc += b;
|
|
if (uart_idx >= uart_len) {
|
|
uart_buf[uart_idx] = '\0';
|
|
uint8_t cs1c = uart_cs_acc & 0xFF;
|
|
uint8_t cs2c = (~uart_cs_acc) & 0xFF;
|
|
if (cs1c == uart_cs1 && cs2c == uart_cs2) {
|
|
memcpy(pending_msg, uart_buf, uart_idx + 1);
|
|
pending_cmd = uart_cmd;
|
|
pending_cmd_rdy = 1;
|
|
}
|
|
uart_state = UART_WAIT_A;
|
|
uart_idx = 0;
|
|
}
|
|
break;
|
|
}
|
|
HAL_UART_Receive_IT(&huart2, &uart_rx_byte, 1);
|
|
}
|
|
|
|
/* USER CODE END 0 */
|
|
|
|
int main(void)
|
|
{
|
|
/* USER CODE BEGIN 1 */
|
|
/* USER CODE END 1 */
|
|
|
|
HAL_Init();
|
|
|
|
/* USER CODE BEGIN Init */
|
|
/* USER CODE END Init */
|
|
|
|
SystemClock_Config();
|
|
|
|
/* USER CODE BEGIN SysInit */
|
|
/* USER CODE END SysInit */
|
|
|
|
MX_GPIO_Init();
|
|
MX_SPI1_Init();
|
|
MX_USART2_UART_Init();
|
|
|
|
/* USER CODE BEGIN 2 */
|
|
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
|
DWT->CYCCNT = 0;
|
|
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
|
|
|
all_rows_off();
|
|
memset(fb, 0, sizeof(fb));
|
|
|
|
HAL_UART_Receive_IT(&huart2, &uart_rx_byte, 1);
|
|
|
|
arrive_animation();
|
|
memset(fb, 0, sizeof(fb));
|
|
current_cmd = 0;
|
|
/* USER CODE END 2 */
|
|
|
|
/* USER CODE BEGIN WHILE */
|
|
while (1)
|
|
{
|
|
if (pending_cmd_rdy) {
|
|
pending_cmd_rdy = 0;
|
|
current_cmd = pending_cmd;
|
|
memcpy(message, pending_msg, sizeof(pending_msg));
|
|
}
|
|
|
|
switch (current_cmd) {
|
|
case CMD_CLEAR:
|
|
memset(fb, 0, sizeof(fb));
|
|
display_refresh();
|
|
current_cmd = 0;
|
|
break;
|
|
|
|
case CMD_VALUES:
|
|
run_values();
|
|
break;
|
|
|
|
case CMD_HW_DEPT:
|
|
run_hw_dept();
|
|
break;
|
|
|
|
case CMD_SCROLL:
|
|
run_scroll();
|
|
break;
|
|
|
|
default:
|
|
display_refresh();
|
|
break;
|
|
}
|
|
/* USER CODE END WHILE */
|
|
|
|
/* USER CODE BEGIN 3 */
|
|
}
|
|
/* USER CODE END 3 */
|
|
}
|
|
|
|
void SystemClock_Config(void)
|
|
{
|
|
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
|
|
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
|
|
|
|
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST);
|
|
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
|
|
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
|
|
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
|
|
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
|
|
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
|
|
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV4;
|
|
RCC_OscInitStruct.PLL.PLLN = 85;
|
|
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
|
|
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
|
|
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
|
|
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) Error_Handler();
|
|
|
|
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|
|
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
|
|
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
|
|
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
|
|
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
|
|
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
|
|
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) Error_Handler();
|
|
}
|
|
|
|
static void MX_SPI1_Init(void)
|
|
{
|
|
/* USER CODE BEGIN SPI1_Init 0 */
|
|
/* USER CODE END SPI1_Init 0 */
|
|
/* USER CODE BEGIN SPI1_Init 1 */
|
|
/* USER CODE END SPI1_Init 1 */
|
|
hspi1.Instance = SPI1;
|
|
hspi1.Init.Mode = SPI_MODE_MASTER;
|
|
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
|
|
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
|
|
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
|
|
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
|
|
hspi1.Init.NSS = SPI_NSS_HARD_OUTPUT;
|
|
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
|
|
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
|
|
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
|
|
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
|
|
hspi1.Init.CRCPolynomial = 7;
|
|
hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
|
|
hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
|
|
if (HAL_SPI_Init(&hspi1) != HAL_OK) Error_Handler();
|
|
/* USER CODE BEGIN SPI1_Init 2 */
|
|
/* USER CODE END SPI1_Init 2 */
|
|
}
|
|
|
|
static void MX_USART2_UART_Init(void)
|
|
{
|
|
/* USER CODE BEGIN USART2_Init 0 */
|
|
/* USER CODE END USART2_Init 0 */
|
|
/* USER CODE BEGIN USART2_Init 1 */
|
|
/* USER CODE END USART2_Init 1 */
|
|
huart2.Instance = USART2;
|
|
huart2.Init.BaudRate = 115200;
|
|
huart2.Init.WordLength = UART_WORDLENGTH_8B;
|
|
huart2.Init.StopBits = UART_STOPBITS_1;
|
|
huart2.Init.Parity = UART_PARITY_NONE;
|
|
huart2.Init.Mode = UART_MODE_TX_RX;
|
|
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
|
|
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
|
|
huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
|
|
huart2.Init.ClockPrescaler = UART_PRESCALER_DIV1;
|
|
huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
|
|
if (HAL_UART_Init(&huart2) != HAL_OK) Error_Handler();
|
|
if (HAL_UARTEx_SetTxFifoThreshold(&huart2, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK) Error_Handler();
|
|
if (HAL_UARTEx_SetRxFifoThreshold(&huart2, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK) Error_Handler();
|
|
if (HAL_UARTEx_DisableFifoMode(&huart2) != HAL_OK) Error_Handler();
|
|
/* USER CODE BEGIN USART2_Init 2 */
|
|
/* USER CODE END USART2_Init 2 */
|
|
}
|
|
|
|
static void MX_GPIO_Init(void)
|
|
{
|
|
GPIO_InitTypeDef GPIO_InitStruct = {0};
|
|
/* USER CODE BEGIN MX_GPIO_Init_1 */
|
|
/* USER CODE END MX_GPIO_Init_1 */
|
|
|
|
__HAL_RCC_GPIOA_CLK_ENABLE();
|
|
__HAL_RCC_GPIOB_CLK_ENABLE();
|
|
|
|
HAL_GPIO_WritePin(GPIOA, ROW_1_Pin|ROW_2_Pin|ROW_6_Pin|ROW_5_Pin, GPIO_PIN_SET);
|
|
HAL_GPIO_WritePin(GPIOB, ROW_0_Pin|ROW_3_Pin|ROW_4_Pin|LED_Pin, GPIO_PIN_SET);
|
|
|
|
GPIO_InitStruct.Pin = ROW_1_Pin|ROW_2_Pin|ROW_6_Pin|ROW_5_Pin;
|
|
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
|
|
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
|
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
|
|
|
|
GPIO_InitStruct.Pin = ROW_0_Pin|ROW_3_Pin|ROW_4_Pin;
|
|
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
|
|
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
|
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
|
|
|
|
GPIO_InitStruct.Pin = LED_Pin;
|
|
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
|
|
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
|
|
HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);
|
|
|
|
/* USER CODE BEGIN MX_GPIO_Init_2 */
|
|
/* USER CODE END MX_GPIO_Init_2 */
|
|
}
|
|
|
|
/* USER CODE BEGIN 4 */
|
|
/* USER CODE END 4 */
|
|
|
|
void Error_Handler(void)
|
|
{
|
|
/* USER CODE BEGIN Error_Handler_Debug */
|
|
__disable_irq();
|
|
while (1) {}
|
|
/* USER CODE END Error_Handler_Debug */
|
|
}
|
|
|
|
#ifdef USE_FULL_ASSERT
|
|
void assert_failed(uint8_t *file, uint32_t line) {}
|
|
#endif
|