Compare commits
16 Commits
main
...
max/motor-
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d411e2603 | |||
|
|
a05de8d49a | ||
|
|
2622696772 | ||
|
|
affaefea3a | ||
|
|
a41c62440c | ||
|
|
bdc69c87d8 | ||
|
|
23b3b9970f | ||
|
|
8e66430c86 | ||
| 330c2ab4fe | |||
|
|
9b4a31aa66 | ||
|
|
06219afe69 | ||
| 34a937628d | |||
|
|
dd52982a03 | ||
|
|
302dfea6f4 | ||
|
|
bdbd7a7c3e | ||
|
|
b0abc7a90d |
@ -1,22 +1,5 @@
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS
|
SRCS "main.c" "orin_serial.c" "vesc_can.c" "gc9a01.c"
|
||||||
"main.c"
|
|
||||||
"orin_serial.c"
|
|
||||||
"vesc_can.c"
|
|
||||||
"gitea_ota.c"
|
|
||||||
"ota_self.c"
|
|
||||||
"uart_ota.c"
|
|
||||||
"ota_display.c"
|
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
REQUIRES
|
REQUIRES driver freertos esp_timer
|
||||||
esp_wifi
|
|
||||||
esp_http_client
|
|
||||||
esp_https_ota
|
|
||||||
nvs_flash
|
|
||||||
app_update
|
|
||||||
mbedtls
|
|
||||||
cJSON
|
|
||||||
driver
|
|
||||||
freertos
|
|
||||||
esp_timer
|
|
||||||
)
|
)
|
||||||
|
|||||||
@ -11,23 +11,35 @@
|
|||||||
* to IO2/IO1 when deploying this firmware. See docs/SAUL-TEE-SYSTEM-REFERENCE.md.
|
* to IO2/IO1 when deploying this firmware. See docs/SAUL-TEE-SYSTEM-REFERENCE.md.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* ── Orin serial (CH343 USB-to-UART, 1a86:55d3 on Orin side) ── */
|
/* ── Orin serial: USB Serial/JTAG (native USB, /dev/ttyACM0 on Orin) ── */
|
||||||
#define ORIN_UART_PORT UART_NUM_0
|
#define ORIN_UART_PORT UART_NUM_0
|
||||||
#define ORIN_UART_BAUD 460800
|
#define ORIN_UART_BAUD 460800
|
||||||
#define ORIN_UART_TX_GPIO 43 /* ESP32→CH343 RXD */
|
#define ORIN_UART_TX_GPIO 43 /* unused — Orin uses USB-CDC */
|
||||||
#define ORIN_UART_RX_GPIO 44 /* CH343 TXD→ESP32 */
|
#define ORIN_UART_RX_GPIO 44 /* unused — Orin uses USB-CDC */
|
||||||
#define ORIN_UART_RX_BUF 1024
|
#define ORIN_UART_RX_BUF 1024
|
||||||
#define ORIN_TX_QUEUE_DEPTH 16
|
#define ORIN_TX_QUEUE_DEPTH 16
|
||||||
|
|
||||||
/* ── VESC CAN TWAI (SN65HVD230 transceiver, rewired for bd-66hx) ── */
|
/* ── Inter-board UART (ESP32 Balance ↔ ESP32 IO) ── */
|
||||||
#define VESC_CAN_TX_GPIO 2 /* ESP32 TWAI TX → SN65HVD230 TXD */
|
#define IO_UART_TX_GPIO 17
|
||||||
#define VESC_CAN_RX_GPIO 1 /* SN65HVD230 RXD → ESP32 TWAI RX */
|
#define IO_UART_RX_GPIO 18
|
||||||
|
|
||||||
|
/* ── VESC CAN TWAI (SN65HVD230 on Waveshare header pins) ── */
|
||||||
|
#define VESC_CAN_TX_GPIO 15 /* GPIO15 → SN65HVD230 TXD */
|
||||||
|
#define VESC_CAN_RX_GPIO 16 /* GPIO16 ← SN65HVD230 RXD */
|
||||||
#define VESC_CAN_RX_QUEUE 32
|
#define VESC_CAN_RX_QUEUE 32
|
||||||
|
|
||||||
/* VESC node IDs — matched to bd-wim1 TELEM_VESC_LEFT/RIGHT mapping */
|
/* VESC node IDs — matched to bd-wim1 TELEM_VESC_LEFT/RIGHT mapping */
|
||||||
#define VESC_ID_A 56u /* TELEM_VESC_LEFT (0x81) */
|
#define VESC_ID_A 56u /* TELEM_VESC_LEFT (0x81) */
|
||||||
#define VESC_ID_B 68u /* TELEM_VESC_RIGHT (0x82) */
|
#define VESC_ID_B 68u /* TELEM_VESC_RIGHT (0x82) */
|
||||||
|
|
||||||
|
/* ── GC9A01 240×240 round display (Waveshare ESP32-S3-LCD-1.28, SPI2) ── */
|
||||||
|
#define DISP_DC_GPIO 8
|
||||||
|
#define DISP_CS_GPIO 9
|
||||||
|
#define DISP_SCK_GPIO 10
|
||||||
|
#define DISP_MOSI_GPIO 11
|
||||||
|
#define DISP_RST_GPIO 12
|
||||||
|
#define DISP_BL_GPIO 40
|
||||||
|
|
||||||
/* ── Safety / timing ── */
|
/* ── Safety / timing ── */
|
||||||
#define HB_TIMEOUT_MS 500u /* heartbeat watchdog: disarm if exceeded */
|
#define HB_TIMEOUT_MS 500u /* heartbeat watchdog: disarm if exceeded */
|
||||||
#define DRIVE_TIMEOUT_MS 500u /* drive command staleness timeout */
|
#define DRIVE_TIMEOUT_MS 500u /* drive command staleness timeout */
|
||||||
|
|||||||
269
esp32s3/balance/main/gc9a01.c
Normal file
269
esp32s3/balance/main/gc9a01.c
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
/* gc9a01.c — GC9A01 240×240 round LCD SPI driver (bd-1yr8 display bead) */
|
||||||
|
|
||||||
|
#include "gc9a01.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "driver/spi_master.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
static const char *TAG = "gc9a01";
|
||||||
|
static spi_device_handle_t s_spi;
|
||||||
|
|
||||||
|
/* ── 5×7 bitmap font, one byte per column (bit0 = top row), ASCII 32..126 ── */
|
||||||
|
static const uint8_t s_font[95][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}, /* ')' */
|
||||||
|
{0x14,0x08,0x3E,0x08,0x14}, /* '*' */ {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}, /* ';' */
|
||||||
|
{0x08,0x14,0x22,0x41,0x00}, /* '<' */ {0x14,0x14,0x14,0x14,0x14}, /* '=' */
|
||||||
|
{0x00,0x41,0x22,0x14,0x08}, /* '>' */ {0x02,0x01,0x51,0x09,0x06}, /* '?' */
|
||||||
|
{0x32,0x49,0x79,0x41,0x3E}, /* '@' */ {0x7E,0x11,0x11,0x11,0x7E}, /* 'A' */
|
||||||
|
{0x7F,0x49,0x49,0x49,0x36}, /* 'B' */ {0x3E,0x41,0x41,0x41,0x22}, /* 'C' */
|
||||||
|
{0x7F,0x41,0x41,0x22,0x1C}, /* 'D' */ {0x7F,0x49,0x49,0x49,0x41}, /* 'E' */
|
||||||
|
{0x7F,0x09,0x09,0x09,0x01}, /* 'F' */ {0x3E,0x41,0x49,0x49,0x3A}, /* 'G' */
|
||||||
|
{0x7F,0x08,0x08,0x08,0x7F}, /* 'H' */ {0x00,0x41,0x7F,0x41,0x00}, /* 'I' */
|
||||||
|
{0x20,0x40,0x41,0x3F,0x01}, /* 'J' */ {0x7F,0x08,0x14,0x22,0x41}, /* 'K' */
|
||||||
|
{0x7F,0x40,0x40,0x40,0x40}, /* 'L' */ {0x7F,0x02,0x0C,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' */ {0x3F,0x40,0x38,0x40,0x3F}, /* 'W' */
|
||||||
|
{0x63,0x14,0x08,0x14,0x63}, /* 'X' */ {0x07,0x08,0x70,0x08,0x07}, /* 'Y' */
|
||||||
|
{0x61,0x51,0x49,0x45,0x43}, /* 'Z' */ {0x00,0x7F,0x41,0x41,0x00}, /* '[' */
|
||||||
|
{0x02,0x04,0x08,0x10,0x20}, /* '\\' */ {0x00,0x41,0x41,0x7F,0x00}, /* ']' */
|
||||||
|
{0x04,0x02,0x01,0x02,0x04}, /* '^' */ {0x40,0x40,0x40,0x40,0x40}, /* '_' */
|
||||||
|
{0x00,0x01,0x02,0x04,0x00}, /* '`' */ {0x20,0x54,0x54,0x54,0x78}, /* 'a' */
|
||||||
|
{0x7F,0x48,0x44,0x44,0x38}, /* 'b' */ {0x38,0x44,0x44,0x44,0x20}, /* 'c' */
|
||||||
|
{0x38,0x44,0x44,0x48,0x7F}, /* 'd' */ {0x38,0x54,0x54,0x54,0x18}, /* 'e' */
|
||||||
|
{0x08,0x7E,0x09,0x01,0x02}, /* 'f' */ {0x0C,0x52,0x52,0x52,0x3E}, /* 'g' */
|
||||||
|
{0x7F,0x08,0x04,0x04,0x78}, /* 'h' */ {0x00,0x44,0x7D,0x40,0x00}, /* 'i' */
|
||||||
|
{0x20,0x40,0x44,0x3D,0x00}, /* 'j' */ {0x7F,0x10,0x28,0x44,0x00}, /* 'k' */
|
||||||
|
{0x00,0x41,0x7F,0x40,0x00}, /* 'l' */ {0x7C,0x04,0x18,0x04,0x78}, /* 'm' */
|
||||||
|
{0x7C,0x08,0x04,0x04,0x78}, /* 'n' */ {0x38,0x44,0x44,0x44,0x38}, /* 'o' */
|
||||||
|
{0x7C,0x14,0x14,0x14,0x08}, /* 'p' */ {0x08,0x14,0x14,0x18,0x7C}, /* 'q' */
|
||||||
|
{0x7C,0x08,0x04,0x04,0x08}, /* 'r' */ {0x48,0x54,0x54,0x54,0x20}, /* 's' */
|
||||||
|
{0x04,0x3F,0x44,0x40,0x20}, /* 't' */ {0x3C,0x40,0x40,0x20,0x7C}, /* 'u' */
|
||||||
|
{0x1C,0x20,0x40,0x20,0x1C}, /* 'v' */ {0x3C,0x40,0x30,0x40,0x3C}, /* 'w' */
|
||||||
|
{0x44,0x28,0x10,0x28,0x44}, /* 'x' */ {0x0C,0x50,0x50,0x50,0x3C}, /* 'y' */
|
||||||
|
{0x44,0x64,0x54,0x4C,0x44}, /* 'z' */ {0x00,0x08,0x36,0x41,0x00}, /* '{' */
|
||||||
|
{0x00,0x00,0x7F,0x00,0x00}, /* '|' */ {0x00,0x41,0x36,0x08,0x00}, /* '}' */
|
||||||
|
{0x10,0x08,0x08,0x10,0x08}, /* '~' */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ── Static buffers (internal SRAM → DMA-safe) ── */
|
||||||
|
static uint8_t s_line_buf[240 * 2];
|
||||||
|
static uint8_t s_char_buf[5 * 5 * 7 * 5 * 2]; /* max scale=5: 25×35×2 */
|
||||||
|
|
||||||
|
/* ── Low-level SPI helpers ── */
|
||||||
|
static void write_cmd(uint8_t cmd)
|
||||||
|
{
|
||||||
|
gpio_set_level(DISP_DC_GPIO, 0);
|
||||||
|
spi_transaction_t t = { .length = 8, .flags = SPI_TRANS_USE_TXDATA };
|
||||||
|
t.tx_data[0] = cmd;
|
||||||
|
spi_device_polling_transmit(s_spi, &t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_bytes(const uint8_t *data, size_t len)
|
||||||
|
{
|
||||||
|
if (!len) return;
|
||||||
|
gpio_set_level(DISP_DC_GPIO, 1);
|
||||||
|
spi_transaction_t t = { .length = len * 8, .tx_buffer = data };
|
||||||
|
spi_device_polling_transmit(s_spi, &t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void write_byte(uint8_t b) { write_bytes(&b, 1); }
|
||||||
|
|
||||||
|
/* ── Address window ── */
|
||||||
|
static void set_window(int x1, int y1, int x2, int y2)
|
||||||
|
{
|
||||||
|
uint8_t d[4];
|
||||||
|
d[0] = x1 >> 8; d[1] = x1 & 0xFF; d[2] = x2 >> 8; d[3] = x2 & 0xFF;
|
||||||
|
write_cmd(0x2A); write_bytes(d, 4);
|
||||||
|
d[0] = y1 >> 8; d[1] = y1 & 0xFF; d[2] = y2 >> 8; d[3] = y2 & 0xFF;
|
||||||
|
write_cmd(0x2B); write_bytes(d, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── GC9A01 register init ── */
|
||||||
|
static void init_regs(void)
|
||||||
|
{
|
||||||
|
write_cmd(0xEF);
|
||||||
|
write_cmd(0xEB); write_byte(0x14);
|
||||||
|
write_cmd(0xFE);
|
||||||
|
write_cmd(0xEF);
|
||||||
|
write_cmd(0xEB); write_byte(0x14);
|
||||||
|
write_cmd(0x84); write_byte(0x40);
|
||||||
|
write_cmd(0x85); write_byte(0xFF);
|
||||||
|
write_cmd(0x86); write_byte(0xFF);
|
||||||
|
write_cmd(0x87); write_byte(0xFF);
|
||||||
|
write_cmd(0x88); write_byte(0x0A);
|
||||||
|
write_cmd(0x89); write_byte(0x21);
|
||||||
|
write_cmd(0x8A); write_byte(0x00);
|
||||||
|
write_cmd(0x8B); write_byte(0x80);
|
||||||
|
write_cmd(0x8C); write_byte(0x01);
|
||||||
|
write_cmd(0x8D); write_byte(0x01);
|
||||||
|
write_cmd(0x8E); write_byte(0xFF);
|
||||||
|
write_cmd(0x8F); write_byte(0xFF);
|
||||||
|
{ uint8_t d[] = {0x00,0x20}; write_cmd(0xB6); write_bytes(d,2); }
|
||||||
|
write_cmd(0x36); write_byte(0x08); /* MADCTL: normal, BGR */
|
||||||
|
write_cmd(0x3A); write_byte(0x05); /* COLMOD: 16-bit RGB565 */
|
||||||
|
{ uint8_t d[] = {0x08,0x08,0x08,0x08}; write_cmd(0x90); write_bytes(d,4); }
|
||||||
|
write_cmd(0xBD); write_byte(0x06);
|
||||||
|
write_cmd(0xBC); write_byte(0x00);
|
||||||
|
{ uint8_t d[] = {0x60,0x01,0x04}; write_cmd(0xFF); write_bytes(d,3); }
|
||||||
|
write_cmd(0xC3); write_byte(0x13);
|
||||||
|
write_cmd(0xC4); write_byte(0x13);
|
||||||
|
write_cmd(0xC9); write_byte(0x22);
|
||||||
|
write_cmd(0xBE); write_byte(0x11);
|
||||||
|
{ uint8_t d[] = {0x10,0x0E}; write_cmd(0xE1); write_bytes(d,2); }
|
||||||
|
{ uint8_t d[] = {0x21,0x0C,0x02}; write_cmd(0xDF); write_bytes(d,3); }
|
||||||
|
{ uint8_t d[] = {0x45,0x09,0x08,0x08,0x26,0x2A}; write_cmd(0xF0); write_bytes(d,6); }
|
||||||
|
{ uint8_t d[] = {0x43,0x70,0x72,0x36,0x37,0x6F}; write_cmd(0xF1); write_bytes(d,6); }
|
||||||
|
{ uint8_t d[] = {0x45,0x09,0x08,0x08,0x26,0x2A}; write_cmd(0xF2); write_bytes(d,6); }
|
||||||
|
{ uint8_t d[] = {0x43,0x70,0x72,0x36,0x37,0x6F}; write_cmd(0xF3); write_bytes(d,6); }
|
||||||
|
{ uint8_t d[] = {0x1B,0x0B}; write_cmd(0xED); write_bytes(d,2); }
|
||||||
|
write_cmd(0xAE); write_byte(0x77);
|
||||||
|
write_cmd(0xCD); write_byte(0x63);
|
||||||
|
{ uint8_t d[] = {0x07,0x07,0x04,0x0E,0x0F,0x09,0x07,0x08,0x03};
|
||||||
|
write_cmd(0x70); write_bytes(d,9); }
|
||||||
|
write_cmd(0xE8); write_byte(0x34);
|
||||||
|
{ uint8_t d[] = {0x18,0x0D,0xB7,0x18,0x0D,0x8B,0x88,0x08};
|
||||||
|
write_cmd(0x62); write_bytes(d,8); }
|
||||||
|
{ uint8_t d[] = {0x18,0x0D,0xB7,0x58,0x1E,0x0B,0x00,0xA7,0x88,0x08};
|
||||||
|
write_cmd(0x63); write_bytes(d,10); }
|
||||||
|
{ uint8_t d[] = {0x20,0x07,0x04}; write_cmd(0x64); write_bytes(d,3); }
|
||||||
|
{ uint8_t d[] = {0x10,0x85,0x80,0x00,0x00,0x4E,0x00};
|
||||||
|
write_cmd(0x74); write_bytes(d,7); }
|
||||||
|
{ uint8_t d[] = {0x3E,0x07}; write_cmd(0x98); write_bytes(d,2); }
|
||||||
|
write_cmd(0x35); /* TEON */
|
||||||
|
write_cmd(0x21); /* INVON */
|
||||||
|
write_cmd(0x11); /* SLPOUT */
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(120));
|
||||||
|
write_cmd(0x29); /* DISPON */
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(20));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Public init ── */
|
||||||
|
void gc9a01_init(void)
|
||||||
|
{
|
||||||
|
/* SPI bus */
|
||||||
|
spi_bus_config_t bus = {
|
||||||
|
.mosi_io_num = DISP_MOSI_GPIO,
|
||||||
|
.miso_io_num = -1,
|
||||||
|
.sclk_io_num = DISP_SCK_GPIO,
|
||||||
|
.quadwp_io_num = -1,
|
||||||
|
.quadhd_io_num = -1,
|
||||||
|
.max_transfer_sz = 4096,
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &bus, SPI_DMA_CH_AUTO));
|
||||||
|
|
||||||
|
spi_device_interface_config_t dev = {
|
||||||
|
.clock_speed_hz = 40 * 1000 * 1000,
|
||||||
|
.mode = 0,
|
||||||
|
.spics_io_num = DISP_CS_GPIO,
|
||||||
|
.queue_size = 1,
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &dev, &s_spi));
|
||||||
|
|
||||||
|
/* DC, RST, BL GPIOs */
|
||||||
|
gpio_set_direction(DISP_DC_GPIO, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(DISP_RST_GPIO, GPIO_MODE_OUTPUT);
|
||||||
|
gpio_set_direction(DISP_BL_GPIO, GPIO_MODE_OUTPUT);
|
||||||
|
|
||||||
|
/* Hardware reset */
|
||||||
|
gpio_set_level(DISP_RST_GPIO, 0); vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
gpio_set_level(DISP_RST_GPIO, 1); vTaskDelay(pdMS_TO_TICKS(120));
|
||||||
|
|
||||||
|
init_regs();
|
||||||
|
|
||||||
|
/* Backlight on */
|
||||||
|
gpio_set_level(DISP_BL_GPIO, 1);
|
||||||
|
ESP_LOGI(TAG, "GC9A01 init OK: DC=%d CS=%d SCK=%d MOSI=%d RST=%d BL=%d",
|
||||||
|
DISP_DC_GPIO, DISP_CS_GPIO, DISP_SCK_GPIO, DISP_MOSI_GPIO,
|
||||||
|
DISP_RST_GPIO, DISP_BL_GPIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── display_fill_rect ── */
|
||||||
|
void display_fill_rect(int x, int y, int w, int h, uint16_t rgb565)
|
||||||
|
{
|
||||||
|
if (w <= 0 || h <= 0) return;
|
||||||
|
if (x < 0) { w += x; x = 0; }
|
||||||
|
if (y < 0) { h += y; y = 0; }
|
||||||
|
if (x + w > 240) w = 240 - x;
|
||||||
|
if (y + h > 240) h = 240 - y;
|
||||||
|
if (w <= 0 || h <= 0) return;
|
||||||
|
|
||||||
|
set_window(x, y, x + w - 1, y + h - 1);
|
||||||
|
write_cmd(0x2C);
|
||||||
|
|
||||||
|
uint8_t hi = rgb565 >> 8, lo = rgb565 & 0xFF;
|
||||||
|
for (int i = 0; i < w * 2; i += 2) { s_line_buf[i] = hi; s_line_buf[i+1] = lo; }
|
||||||
|
for (int row = 0; row < h; row++) { write_bytes(s_line_buf, (size_t)(w * 2)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Glyph rasteriser (handles scale 1..5) ── */
|
||||||
|
static void draw_char_s(int x, int y, char c, uint16_t fg, uint16_t bg, int scale)
|
||||||
|
{
|
||||||
|
if ((uint8_t)c < 32 || (uint8_t)c > 126) return;
|
||||||
|
if (scale < 1) scale = 1;
|
||||||
|
if (scale > 5) scale = 5;
|
||||||
|
const uint8_t *g = s_font[(uint8_t)c - 32];
|
||||||
|
int cw = 5 * scale, ch = 7 * scale;
|
||||||
|
|
||||||
|
uint8_t *p = s_char_buf;
|
||||||
|
for (int row = 0; row < 7; row++) {
|
||||||
|
for (int sr = 0; sr < scale; sr++) {
|
||||||
|
for (int col = 0; col < 5; col++) {
|
||||||
|
uint16_t color = ((g[col] >> row) & 1) ? fg : bg;
|
||||||
|
uint8_t hi = color >> 8, lo = color & 0xFF;
|
||||||
|
for (int sc = 0; sc < scale; sc++) { *p++ = hi; *p++ = lo; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set_window(x, y, x + cw - 1, y + ch - 1);
|
||||||
|
write_cmd(0x2C);
|
||||||
|
write_bytes(s_char_buf, (size_t)(cw * ch * 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── display_draw_string / display_draw_string_s ── */
|
||||||
|
void display_draw_string(int x, int y, const char *str, uint16_t fg, uint16_t bg)
|
||||||
|
{
|
||||||
|
display_draw_string_s(x, y, str, fg, bg, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_draw_string_s(int x, int y, const char *str,
|
||||||
|
uint16_t fg, uint16_t bg, int scale)
|
||||||
|
{
|
||||||
|
int cx = x;
|
||||||
|
while (*str) {
|
||||||
|
draw_char_s(cx, y, *str++, fg, bg, scale);
|
||||||
|
cx += 6 * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── display_draw_arc ── */
|
||||||
|
void display_draw_arc(int cx, int cy, int r, int start_deg, int end_deg,
|
||||||
|
int thickness, uint16_t color)
|
||||||
|
{
|
||||||
|
for (int deg = start_deg; deg <= end_deg; deg++) {
|
||||||
|
float rad = (float)deg * (3.14159265f / 180.0f);
|
||||||
|
int px = cx + (int)((float)r * cosf(rad));
|
||||||
|
int py = cy + (int)((float)r * sinf(rad));
|
||||||
|
int half = thickness / 2;
|
||||||
|
display_fill_rect(px - half, py - half, thickness, thickness, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
esp32s3/balance/main/gc9a01.h
Normal file
24
esp32s3/balance/main/gc9a01.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
/* gc9a01.h — GC9A01 240×240 round LCD SPI driver (bd-1yr8 display bead) */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* ── Initialise SPI bus + GC9A01. Call once from app_main. ── */
|
||||||
|
void gc9a01_init(void);
|
||||||
|
|
||||||
|
/* ── Display primitives (also satisfy ota_display.h contract) ── */
|
||||||
|
void display_fill_rect(int x, int y, int w, int h, uint16_t rgb565);
|
||||||
|
void display_draw_string(int x, int y, const char *str, uint16_t fg, uint16_t bg);
|
||||||
|
void display_draw_string_s(int x, int y, const char *str,
|
||||||
|
uint16_t fg, uint16_t bg, int scale);
|
||||||
|
void display_draw_arc(int cx, int cy, int r,
|
||||||
|
int start_deg, int end_deg, int thickness, uint16_t color);
|
||||||
|
|
||||||
|
/* ── Colour palette (RGB565) ── */
|
||||||
|
#define COL_BG 0x0000u
|
||||||
|
#define COL_WHITE 0xFFFFu
|
||||||
|
#define COL_GREEN 0x07E0u
|
||||||
|
#define COL_YELLOW 0xFFE0u
|
||||||
|
#define COL_RED 0xF800u
|
||||||
|
#define COL_BLUE 0x001Fu
|
||||||
|
#define COL_ORANGE 0xFD20u
|
||||||
@ -1,23 +1,61 @@
|
|||||||
/* main.c — ESP32-S3 BALANCE app_main (bd-66hx + OTA beads) */
|
/* main.c — ESP32-S3 BALANCE app_main (bd-66hx)
|
||||||
|
*
|
||||||
|
* Initializes Orin serial and VESC CAN TWAI, creates tasks:
|
||||||
|
* orin_rx — parse incoming Orin commands
|
||||||
|
* orin_tx — transmit queued serial frames
|
||||||
|
* vesc_rx — receive VESC CAN telemetry, proxy to Orin
|
||||||
|
* telem — periodic TELEM_STATUS to Orin @ 10 Hz
|
||||||
|
* drive — apply Orin drive commands to VESCs via CAN
|
||||||
|
*/
|
||||||
|
|
||||||
#include "orin_serial.h"
|
#include "orin_serial.h"
|
||||||
#include "vesc_can.h"
|
#include "vesc_can.h"
|
||||||
#include "gitea_ota.h"
|
#include "gc9a01.h"
|
||||||
#include "ota_self.h"
|
|
||||||
#include "uart_ota.h"
|
|
||||||
#include "ota_display.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
#include "freertos/queue.h"
|
#include "freertos/queue.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
static const char *TAG = "main";
|
static const char *TAG = "main";
|
||||||
|
|
||||||
static QueueHandle_t s_orin_tx_q;
|
static QueueHandle_t s_orin_tx_q;
|
||||||
|
|
||||||
|
/* ── HUD task: SAULT brand + battery voltage on GC9A01 display ── */
|
||||||
|
static void hud_task(void *arg)
|
||||||
|
{
|
||||||
|
/* Draw static "SAULT" header in orange — scale=3: each char 15×21 px */
|
||||||
|
const int hs = 3;
|
||||||
|
int hx = (240 - 5 * 6 * hs) / 2;
|
||||||
|
display_draw_string_s(hx, 16, "SAULT", COL_ORANGE, COL_BG, hs);
|
||||||
|
|
||||||
|
char prev[16] = "";
|
||||||
|
for (;;) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
|
||||||
|
int16_t v10 = g_vesc[0].voltage_x10;
|
||||||
|
char vstr[16];
|
||||||
|
if (v10 <= 0) {
|
||||||
|
snprintf(vstr, sizeof(vstr), " --.-V");
|
||||||
|
} else {
|
||||||
|
snprintf(vstr, sizeof(vstr), "%2d.%dV", v10 / 10, abs(v10 % 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(vstr, prev) != 0) {
|
||||||
|
const int vs = 4; /* scale=4: each char 20×28 px */
|
||||||
|
int nch = (int)strlen(vstr);
|
||||||
|
int vx = (240 - nch * 6 * vs) / 2;
|
||||||
|
display_fill_rect(0, 95, 240, 7 * vs + 2, COL_BG);
|
||||||
|
display_draw_string_s(vx, 97, vstr, COL_WHITE, COL_BG, vs);
|
||||||
|
strncpy(prev, vstr, sizeof(prev) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Telemetry task: sends TELEM_STATUS to Orin at 10 Hz ── */
|
/* ── Telemetry task: sends TELEM_STATUS to Orin at 10 Hz ── */
|
||||||
static void telem_task(void *arg)
|
static void telem_task(void *arg)
|
||||||
{
|
{
|
||||||
@ -37,9 +75,10 @@ static void telem_task(void *arg)
|
|||||||
state = BAL_ARMED;
|
state = BAL_ARMED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* flags: bit0=estop_active, bit1=heartbeat_timeout */
|
/* flags: bit0=estop_active, bit1=heartbeat_timeout, bit2=twai_bus_off */
|
||||||
uint8_t flags = (g_orin_ctrl.estop ? 0x01u : 0x00u) |
|
uint8_t flags = (g_orin_ctrl.estop ? 0x01u : 0x00u) |
|
||||||
(hb_timeout ? 0x02u : 0x00u);
|
(hb_timeout ? 0x02u : 0x00u) |
|
||||||
|
(g_twai_bus_off ? 0x04u : 0x00u);
|
||||||
|
|
||||||
/* Battery voltage from VESC_ID_A STATUS_5 (V×10 → mV) */
|
/* Battery voltage from VESC_ID_A STATUS_5 (V×10 → mV) */
|
||||||
uint16_t vbat_mv = (uint16_t)((int32_t)g_vesc[0].voltage_x10 * 100);
|
uint16_t vbat_mv = (uint16_t)((int32_t)g_vesc[0].voltage_x10 * 100);
|
||||||
@ -56,22 +95,47 @@ static void telem_task(void *arg)
|
|||||||
/* ── Drive task: applies Orin drive commands to VESCs @ 50 Hz ── */
|
/* ── Drive task: applies Orin drive commands to VESCs @ 50 Hz ── */
|
||||||
static void drive_task(void *arg)
|
static void drive_task(void *arg)
|
||||||
{
|
{
|
||||||
|
uint32_t log_tick = 0;
|
||||||
|
bool was_driving = false;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
vTaskDelay(pdMS_TO_TICKS(20)); /* 50 Hz */
|
vTaskDelay(pdMS_TO_TICKS(20)); /* 50 Hz */
|
||||||
|
|
||||||
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000LL);
|
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000LL);
|
||||||
bool hb_timeout = (now_ms - g_orin_ctrl.hb_last_ms) > HB_TIMEOUT_MS;
|
bool hb_timeout = (now_ms - g_orin_ctrl.hb_last_ms) > HB_TIMEOUT_MS;
|
||||||
bool drive_stale = (now_ms - g_orin_drive.updated_ms) > DRIVE_TIMEOUT_MS;
|
bool drive_stale = (now_ms - g_orin_drive.updated_ms) > DRIVE_TIMEOUT_MS;
|
||||||
|
bool gates_ok = g_orin_ctrl.armed && !g_orin_ctrl.estop &&
|
||||||
|
!hb_timeout && !drive_stale;
|
||||||
|
|
||||||
int32_t left_erpm = 0;
|
int32_t left_erpm = 0;
|
||||||
int32_t right_erpm = 0;
|
int32_t right_erpm = 0;
|
||||||
|
|
||||||
if (g_orin_ctrl.armed && !g_orin_ctrl.estop &&
|
if (gates_ok) {
|
||||||
!hb_timeout && !drive_stale) {
|
|
||||||
int32_t spd = (int32_t)g_orin_drive.speed * RPM_PER_SPEED_UNIT;
|
int32_t spd = (int32_t)g_orin_drive.speed * RPM_PER_SPEED_UNIT;
|
||||||
int32_t str = (int32_t)g_orin_drive.steer * RPM_PER_STEER_UNIT;
|
int32_t str = (int32_t)g_orin_drive.steer * RPM_PER_STEER_UNIT;
|
||||||
left_erpm = spd + str;
|
left_erpm = spd + str;
|
||||||
right_erpm = spd - str;
|
right_erpm = spd - str;
|
||||||
|
if (!was_driving) {
|
||||||
|
ESP_LOGI(TAG, "drive ENABLED: spd=%d str=%d L=%ld R=%ld",
|
||||||
|
g_orin_drive.speed, g_orin_drive.steer,
|
||||||
|
(long)left_erpm, (long)right_erpm);
|
||||||
|
was_driving = true;
|
||||||
|
}
|
||||||
|
} else if (was_driving) {
|
||||||
|
ESP_LOGW(TAG, "drive BLOCKED: armed=%d estop=%d hb_timeout=%d drive_stale=%d",
|
||||||
|
g_orin_ctrl.armed, g_orin_ctrl.estop, hb_timeout, drive_stale);
|
||||||
|
was_driving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1 Hz gate-state diagnostic */
|
||||||
|
if ((now_ms - log_tick) >= 1000u) {
|
||||||
|
log_tick = now_ms;
|
||||||
|
ESP_LOGI(TAG, "gate: armed=%d estop=%d hb_age=%lums drv_age=%lums twai_off=%d L=%ld R=%ld",
|
||||||
|
g_orin_ctrl.armed, g_orin_ctrl.estop,
|
||||||
|
(unsigned long)(now_ms - g_orin_ctrl.hb_last_ms),
|
||||||
|
(unsigned long)(now_ms - g_orin_drive.updated_ms),
|
||||||
|
(int)g_twai_bus_off,
|
||||||
|
(long)left_erpm, (long)right_erpm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* VESC_ID_A (56) = LEFT, VESC_ID_B (68) = RIGHT per bd-wim1 protocol */
|
/* VESC_ID_A (56) = LEFT, VESC_ID_B (68) = RIGHT per bd-wim1 protocol */
|
||||||
@ -82,32 +146,32 @@ static void drive_task(void *arg)
|
|||||||
|
|
||||||
void app_main(void)
|
void app_main(void)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "ESP32-S3 BALANCE starting");
|
ESP_LOGI(TAG, "ESP32-S3 BALANCE bd-66hx starting");
|
||||||
|
|
||||||
/* OTA rollback health check — must be called within OTA_ROLLBACK_WINDOW_S */
|
|
||||||
ota_self_health_check();
|
|
||||||
|
|
||||||
/* Init peripherals */
|
|
||||||
orin_serial_init();
|
orin_serial_init();
|
||||||
vesc_can_init();
|
vesc_can_init();
|
||||||
|
gc9a01_init();
|
||||||
|
|
||||||
/* TX queue for outbound serial frames */
|
/* TX queue for outbound serial frames */
|
||||||
s_orin_tx_q = xQueueCreate(ORIN_TX_QUEUE_DEPTH, sizeof(orin_tx_frame_t));
|
s_orin_tx_q = xQueueCreate(ORIN_TX_QUEUE_DEPTH, sizeof(orin_tx_frame_t));
|
||||||
configASSERT(s_orin_tx_q);
|
configASSERT(s_orin_tx_q);
|
||||||
|
|
||||||
/* Seed heartbeat timer so we don't immediately timeout */
|
/* Seed timers so we don't immediately trip hb_timeout or drive_stale */
|
||||||
g_orin_ctrl.hb_last_ms = (uint32_t)(esp_timer_get_time() / 1000LL);
|
g_orin_ctrl.hb_last_ms = (uint32_t)(esp_timer_get_time() / 1000LL);
|
||||||
|
g_orin_drive.updated_ms = g_orin_ctrl.hb_last_ms;
|
||||||
|
g_orin_ctrl.armed = true; /* bypass for motor testing */
|
||||||
|
|
||||||
/* Create tasks */
|
/* Create tasks */
|
||||||
xTaskCreate(orin_serial_rx_task, "orin_rx", 4096, s_orin_tx_q, 10, NULL);
|
xTaskCreate(orin_serial_rx_task, "orin_rx", 4096, s_orin_tx_q, 10, NULL);
|
||||||
xTaskCreate(orin_serial_tx_task, "orin_tx", 2048, s_orin_tx_q, 9, NULL);
|
xTaskCreate(orin_serial_tx_task, "orin_tx", 2048, s_orin_tx_q, 9, NULL);
|
||||||
|
if (!g_twai_bus_off) {
|
||||||
xTaskCreate(vesc_can_rx_task, "vesc_rx", 4096, s_orin_tx_q, 10, NULL);
|
xTaskCreate(vesc_can_rx_task, "vesc_rx", 4096, s_orin_tx_q, 10, NULL);
|
||||||
|
xTaskCreate(drive_task, "drive", 4096, NULL, 8, NULL);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "CAN disabled — vesc_rx and drive tasks not started");
|
||||||
|
}
|
||||||
xTaskCreate(telem_task, "telem", 2048, NULL, 5, NULL);
|
xTaskCreate(telem_task, "telem", 2048, NULL, 5, NULL);
|
||||||
xTaskCreate(drive_task, "drive", 2048, NULL, 8, NULL);
|
xTaskCreate(hud_task, "hud", 4096, NULL, 3, NULL);
|
||||||
|
|
||||||
/* OTA subsystem — WiFi version checker + display overlay */
|
|
||||||
gitea_ota_init();
|
|
||||||
ota_display_init();
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "all tasks started");
|
ESP_LOGI(TAG, "all tasks started");
|
||||||
/* app_main returns — FreeRTOS scheduler continues */
|
/* app_main returns — FreeRTOS scheduler continues */
|
||||||
|
|||||||
@ -1,18 +1,17 @@
|
|||||||
/* orin_serial.c — Orin↔ESP32-S3 serial protocol (bd-66hx + bd-1s1s OTA cmds) */
|
/* orin_serial.c — Orin↔ESP32-S3 serial protocol implementation (bd-66hx)
|
||||||
|
*
|
||||||
|
* Implements the binary framing protocol matching bd-wim1 (Orin side).
|
||||||
|
* CRC8-SMBUS: poly=0x07, init=0x00, covers LEN+TYPE+PAYLOAD bytes.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "orin_serial.h"
|
#include "orin_serial.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "gitea_ota.h"
|
|
||||||
#include "ota_self.h"
|
|
||||||
#include "uart_ota.h"
|
|
||||||
#include "version.h"
|
|
||||||
#include "driver/uart.h"
|
#include "driver/uart.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/queue.h"
|
#include "freertos/queue.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
static const char *TAG = "orin";
|
static const char *TAG = "orin";
|
||||||
|
|
||||||
@ -207,46 +206,6 @@ static void dispatch_cmd(uint8_t type, const uint8_t *payload, uint8_t len,
|
|||||||
orin_send_ack(tx_q, type);
|
orin_send_ack(tx_q, type);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_OTA_CHECK:
|
|
||||||
/* Trigger an immediate Gitea version check */
|
|
||||||
gitea_ota_check_now();
|
|
||||||
orin_send_version_info(tx_q, OTA_TARGET_BALANCE,
|
|
||||||
BALANCE_FW_VERSION,
|
|
||||||
g_balance_update.available
|
|
||||||
? g_balance_update.version : "");
|
|
||||||
orin_send_version_info(tx_q, OTA_TARGET_IO,
|
|
||||||
IO_FW_VERSION,
|
|
||||||
g_io_update.available
|
|
||||||
? g_io_update.version : "");
|
|
||||||
orin_send_ack(tx_q, type);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CMD_OTA_UPDATE:
|
|
||||||
if (len < 1u) { orin_send_nack(tx_q, type, ERR_BAD_LEN); break; }
|
|
||||||
{
|
|
||||||
uint8_t target = payload[0];
|
|
||||||
bool triggered = false;
|
|
||||||
if (target == OTA_TARGET_IO || target == OTA_TARGET_BOTH) {
|
|
||||||
if (!uart_ota_trigger()) {
|
|
||||||
orin_send_nack(tx_q, type,
|
|
||||||
g_io_update.available ? ERR_OTA_BUSY : ERR_OTA_NO_UPDATE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
triggered = true;
|
|
||||||
}
|
|
||||||
if (target == OTA_TARGET_BALANCE || target == OTA_TARGET_BOTH) {
|
|
||||||
if (!ota_self_trigger()) {
|
|
||||||
if (!triggered) {
|
|
||||||
orin_send_nack(tx_q, type,
|
|
||||||
g_balance_update.available ? ERR_OTA_BUSY : ERR_OTA_NO_UPDATE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
orin_send_ack(tx_q, type);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
ESP_LOGW(TAG, "unknown cmd type=0x%02x", type);
|
ESP_LOGW(TAG, "unknown cmd type=0x%02x", type);
|
||||||
break;
|
break;
|
||||||
@ -331,24 +290,3 @@ void orin_serial_tx_task(void *arg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── OTA telemetry helpers (bd-1s1s) ── */
|
|
||||||
|
|
||||||
void orin_send_ota_status(QueueHandle_t q, uint8_t target,
|
|
||||||
uint8_t state, uint8_t progress, uint8_t err)
|
|
||||||
{
|
|
||||||
/* TELEM_OTA_STATUS: uint8 target, uint8 state, uint8 progress, uint8 err */
|
|
||||||
uint8_t p[4] = {target, state, progress, err};
|
|
||||||
enqueue(q, TELEM_OTA_STATUS, p, 4u);
|
|
||||||
}
|
|
||||||
|
|
||||||
void orin_send_version_info(QueueHandle_t q, uint8_t target,
|
|
||||||
const char *current, const char *available)
|
|
||||||
{
|
|
||||||
/* TELEM_VERSION_INFO: uint8 target, char current[16], char available[16] */
|
|
||||||
uint8_t p[33];
|
|
||||||
p[0] = target;
|
|
||||||
strncpy((char *)&p[1], current, 16); p[16] = '\0';
|
|
||||||
strncpy((char *)&p[17], available ? available : "", 16); p[32] = '\0';
|
|
||||||
enqueue(q, TELEM_VERSION_INFO, p, 33u);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -29,27 +29,14 @@
|
|||||||
#define TELEM_STATUS 0x80u /* status @ 10 Hz */
|
#define TELEM_STATUS 0x80u /* status @ 10 Hz */
|
||||||
#define TELEM_VESC_LEFT 0x81u /* VESC ID 56 telemetry @ 10 Hz */
|
#define TELEM_VESC_LEFT 0x81u /* VESC ID 56 telemetry @ 10 Hz */
|
||||||
#define TELEM_VESC_RIGHT 0x82u /* VESC ID 68 telemetry @ 10 Hz */
|
#define TELEM_VESC_RIGHT 0x82u /* VESC ID 68 telemetry @ 10 Hz */
|
||||||
#define TELEM_OTA_STATUS 0x83u /* OTA state + progress (bd-1s1s) */
|
|
||||||
#define TELEM_VERSION_INFO 0x84u /* firmware version report (bd-1s1s) */
|
|
||||||
#define RESP_ACK 0xA0u
|
#define RESP_ACK 0xA0u
|
||||||
#define RESP_NACK 0xA1u
|
#define RESP_NACK 0xA1u
|
||||||
|
|
||||||
/* ── OTA commands (Orin → ESP32, bd-1s1s) ── */
|
|
||||||
#define CMD_OTA_CHECK 0x10u /* no payload: trigger Gitea version check */
|
|
||||||
#define CMD_OTA_UPDATE 0x11u /* uint8 target: 0=balance, 1=io, 2=both */
|
|
||||||
|
|
||||||
/* ── OTA target constants ── */
|
|
||||||
#define OTA_TARGET_BALANCE 0x00u
|
|
||||||
#define OTA_TARGET_IO 0x01u
|
|
||||||
#define OTA_TARGET_BOTH 0x02u
|
|
||||||
|
|
||||||
/* ── NACK error codes ── */
|
/* ── NACK error codes ── */
|
||||||
#define ERR_BAD_CRC 0x01u
|
#define ERR_BAD_CRC 0x01u
|
||||||
#define ERR_BAD_LEN 0x02u
|
#define ERR_BAD_LEN 0x02u
|
||||||
#define ERR_ESTOP_ACTIVE 0x03u
|
#define ERR_ESTOP_ACTIVE 0x03u
|
||||||
#define ERR_DISARMED 0x04u
|
#define ERR_DISARMED 0x04u
|
||||||
#define ERR_OTA_BUSY 0x05u
|
|
||||||
#define ERR_OTA_NO_UPDATE 0x06u
|
|
||||||
|
|
||||||
/* ── Balance state (mirrored from TELEM_STATUS.balance_state) ── */
|
/* ── Balance state (mirrored from TELEM_STATUS.balance_state) ── */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@ -105,9 +92,3 @@ void orin_send_vesc(QueueHandle_t q, uint8_t telem_type,
|
|||||||
int16_t current_ma, uint16_t temp_c_x10);
|
int16_t current_ma, uint16_t temp_c_x10);
|
||||||
void orin_send_ack(QueueHandle_t q, uint8_t cmd_type);
|
void orin_send_ack(QueueHandle_t q, uint8_t cmd_type);
|
||||||
void orin_send_nack(QueueHandle_t q, uint8_t cmd_type, uint8_t err);
|
void orin_send_nack(QueueHandle_t q, uint8_t cmd_type, uint8_t err);
|
||||||
|
|
||||||
/* OTA telemetry helpers (bd-1s1s) */
|
|
||||||
void orin_send_ota_status(QueueHandle_t q, uint8_t target,
|
|
||||||
uint8_t state, uint8_t progress, uint8_t err);
|
|
||||||
void orin_send_version_info(QueueHandle_t q, uint8_t target,
|
|
||||||
const char *current, const char *available);
|
|
||||||
|
|||||||
@ -12,11 +12,13 @@
|
|||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
#include <inttypes.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
static const char *TAG = "vesc_can";
|
static const char *TAG = "vesc_can";
|
||||||
|
|
||||||
vesc_state_t g_vesc[2] = {0};
|
vesc_state_t g_vesc[2] = {0};
|
||||||
|
volatile bool g_twai_bus_off = false;
|
||||||
|
|
||||||
/* Index for a given VESC node ID: 0=VESC_ID_A, 1=VESC_ID_B */
|
/* Index for a given VESC node ID: 0=VESC_ID_A, 1=VESC_ID_B */
|
||||||
static int vesc_idx(uint8_t id)
|
static int vesc_idx(uint8_t id)
|
||||||
@ -33,17 +35,32 @@ void vesc_can_init(void)
|
|||||||
(gpio_num_t)VESC_CAN_RX_GPIO,
|
(gpio_num_t)VESC_CAN_RX_GPIO,
|
||||||
TWAI_MODE_NORMAL);
|
TWAI_MODE_NORMAL);
|
||||||
gcfg.rx_queue_len = VESC_CAN_RX_QUEUE;
|
gcfg.rx_queue_len = VESC_CAN_RX_QUEUE;
|
||||||
|
gcfg.tx_queue_len = 5;
|
||||||
|
|
||||||
twai_timing_config_t tcfg = TWAI_TIMING_CONFIG_500KBITS();
|
twai_timing_config_t tcfg = TWAI_TIMING_CONFIG_500KBITS();
|
||||||
twai_filter_config_t fcfg = TWAI_FILTER_CONFIG_ACCEPT_ALL();
|
twai_filter_config_t fcfg = TWAI_FILTER_CONFIG_ACCEPT_ALL();
|
||||||
|
|
||||||
ESP_ERROR_CHECK(twai_driver_install(&gcfg, &tcfg, &fcfg));
|
ESP_LOGI(TAG, "TWAI: installing driver tx=%d rx=%d 500kbps", VESC_CAN_TX_GPIO, VESC_CAN_RX_GPIO);
|
||||||
ESP_ERROR_CHECK(twai_start());
|
esp_err_t err = twai_driver_install(&gcfg, &tcfg, &fcfg);
|
||||||
ESP_LOGI(TAG, "TWAI init OK: tx=%d rx=%d 500kbps", VESC_CAN_TX_GPIO, VESC_CAN_RX_GPIO);
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "TWAI install failed (0x%x) — CAN disabled", err);
|
||||||
|
g_twai_bus_off = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = twai_start();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "TWAI start failed (0x%x) — CAN disabled", err);
|
||||||
|
g_twai_bus_off = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(200));
|
||||||
|
ESP_LOGI(TAG, "TWAI: started OK — bus active");
|
||||||
}
|
}
|
||||||
|
|
||||||
void vesc_can_send_rpm(uint8_t vesc_id, int32_t erpm)
|
void vesc_can_send_rpm(uint8_t vesc_id, int32_t erpm)
|
||||||
{
|
{
|
||||||
|
if (g_twai_bus_off) { return; }
|
||||||
uint32_t ext_id = ((uint32_t)VESC_PKT_SET_RPM << 8u) | vesc_id;
|
uint32_t ext_id = ((uint32_t)VESC_PKT_SET_RPM << 8u) | vesc_id;
|
||||||
twai_message_t msg = {
|
twai_message_t msg = {
|
||||||
.extd = 1,
|
.extd = 1,
|
||||||
@ -55,7 +72,11 @@ void vesc_can_send_rpm(uint8_t vesc_id, int32_t erpm)
|
|||||||
msg.data[1] = (uint8_t)(u >> 16u);
|
msg.data[1] = (uint8_t)(u >> 16u);
|
||||||
msg.data[2] = (uint8_t)(u >> 8u);
|
msg.data[2] = (uint8_t)(u >> 8u);
|
||||||
msg.data[3] = (uint8_t)(u);
|
msg.data[3] = (uint8_t)(u);
|
||||||
twai_transmit(&msg, pdMS_TO_TICKS(5));
|
ESP_LOGD(TAG, "send_rpm vesc_id=%u erpm=%" PRId32 " ext_id=0x%08" PRIx32, vesc_id, erpm, ext_id);
|
||||||
|
esp_err_t err = twai_transmit(&msg, pdMS_TO_TICKS(5));
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "twai_transmit failed vesc_id=%u err=0x%x", vesc_id, err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void vesc_can_rx_task(void *arg)
|
void vesc_can_rx_task(void *arg)
|
||||||
@ -64,9 +85,37 @@ void vesc_can_rx_task(void *arg)
|
|||||||
twai_message_t msg;
|
twai_message_t msg;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (twai_receive(&msg, pdMS_TO_TICKS(50)) != ESP_OK) {
|
esp_err_t rx_err = twai_receive(&msg, pdMS_TO_TICKS(50));
|
||||||
|
if (rx_err != ESP_OK) {
|
||||||
|
if (rx_err != ESP_ERR_TIMEOUT) {
|
||||||
|
ESP_LOGW(TAG, "twai_receive err=0x%x", rx_err);
|
||||||
|
}
|
||||||
|
twai_status_info_t si;
|
||||||
|
if (twai_get_status_info(&si) == ESP_OK) {
|
||||||
|
/* Mark bus-off for ANY non-running state so vesc_can_send_rpm
|
||||||
|
* won't flood with failed transmits during recovery. */
|
||||||
|
g_twai_bus_off = (si.state != TWAI_STATE_RUNNING);
|
||||||
|
|
||||||
|
if (si.state == TWAI_STATE_BUS_OFF) {
|
||||||
|
ESP_LOGE(TAG, "TWAI BUS OFF tx_err=%lu rx_err=%lu — recovering",
|
||||||
|
(unsigned long)si.tx_error_counter, (unsigned long)si.rx_error_counter);
|
||||||
|
twai_initiate_recovery();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
} else if (si.state == TWAI_STATE_STOPPED) {
|
||||||
|
esp_err_t serr = twai_start();
|
||||||
|
if (serr == ESP_OK) {
|
||||||
|
g_twai_bus_off = false;
|
||||||
|
ESP_LOGI(TAG, "TWAI recovered — bus active");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "TWAI restart failed 0x%x — backing off", serr);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* TWAI_STATE_RECOVERING: initiation already called, just wait. */
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
ESP_LOGI(TAG, "twai_receive OK id=0x%08" PRIx32 " dlc=%u", msg.identifier, msg.data_length_code);
|
||||||
if (!msg.extd) {
|
if (!msg.extd) {
|
||||||
continue; /* ignore standard frames */
|
continue; /* ignore standard frames */
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,10 @@ typedef struct {
|
|||||||
|
|
||||||
/* ── Globals (two VESC nodes: index 0 = VESC_ID_A=56, 1 = VESC_ID_B=68) ── */
|
/* ── Globals (two VESC nodes: index 0 = VESC_ID_A=56, 1 = VESC_ID_B=68) ── */
|
||||||
extern vesc_state_t g_vesc[2];
|
extern vesc_state_t g_vesc[2];
|
||||||
|
/* TWAI bus health — set by vesc_can_rx_task, read by telem_task for flags bit2 */
|
||||||
|
extern volatile bool g_twai_bus_off;
|
||||||
|
extern volatile uint32_t g_twai_tx_err_count;
|
||||||
|
extern volatile uint32_t g_twai_rx_err_count;
|
||||||
|
|
||||||
/* ── API ── */
|
/* ── API ── */
|
||||||
void vesc_can_init(void);
|
void vesc_can_init(void);
|
||||||
|
|||||||
@ -5,15 +5,6 @@ CONFIG_ESP_TASK_WDT_EN=y
|
|||||||
CONFIG_ESP_TASK_WDT_TIMEOUT_S=5
|
CONFIG_ESP_TASK_WDT_TIMEOUT_S=5
|
||||||
CONFIG_TWAI_ISR_IN_IRAM=y
|
CONFIG_TWAI_ISR_IN_IRAM=y
|
||||||
CONFIG_UART_ISR_IN_IRAM=y
|
CONFIG_UART_ISR_IN_IRAM=y
|
||||||
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
|
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
|
||||||
CONFIG_ESP_CONSOLE_UART_NUM=0
|
|
||||||
CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200
|
|
||||||
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
|
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
|
||||||
|
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||||
# OTA — bd-3gwo: dual OTA partitions + rollback
|
|
||||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
|
||||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
|
||||||
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
|
|
||||||
CONFIG_OTA_ALLOW_HTTP=y
|
|
||||||
CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP=y
|
|
||||||
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user