feat(sys): 添加AES加密和电机OTA中继功能模块

添加了AES128加密算法实现和电机OTA中继相关文件,包括:
- ./sys/aes128.h 和 ./sys/aes128.cpp - AES128加密算法头文件和实现
- ./sys/motor_ota_relay.h 和 ./sys/motor_ota_relay.cpp - 电机OTA中继功能
- 更新了项目配置文件包含这些新模块
This commit is contained in:
cbb
2026-07-26 20:39:59 +08:00
parent 934836618a
commit 079c6442d4
25 changed files with 2503 additions and 103 deletions
+183
View File
@@ -0,0 +1,183 @@
/**
* @file aes128.cpp
* @brief AES-128 ECB 加解密实现(移植自电机 BOOT 工程 sys/aes128.cpp)。
*/
#include "sys/aes128.h"
/* S盒 */
static const uint8_t sbox[256] = {
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
};
/* 逆S盒 */
static const uint8_t inv_sbox[256] = {
0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d
};
/* 轮常量 */
static const uint8_t rcon[10] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36};
static uint8_t xtime(uint8_t x) { return static_cast<uint8_t>((x << 1) ^ (((x >> 7) & 1) * 0x1b)); }
static uint8_t gf_mul(uint8_t a, uint8_t b) {
uint8_t p = 0;
while (b) {
if (b & 1) p ^= a;
const uint8_t hi = a & 0x80;
a <<= 1;
if (hi) a ^= 0x1b;
b >>= 1;
}
return p;
}
static void add_round_key(uint8_t *state, const uint8_t *rk) {
for (uint8_t i = 0; i < 16; i++) state[i] ^= rk[i];
}
static void sub_bytes(uint8_t *state) {
for (uint8_t i = 0; i < 16; i++) state[i] = sbox[state[i]];
}
static void inv_sub_bytes(uint8_t *state) {
for (uint8_t i = 0; i < 16; i++) state[i] = inv_sbox[state[i]];
}
static void shift_rows(uint8_t *s) {
uint8_t t;
t=s[1]; s[1]=s[5]; s[5]=s[9]; s[9]=s[13]; s[13]=t;
t=s[2]; s[2]=s[10]; s[10]=t; t=s[6]; s[6]=s[14]; s[14]=t;
t=s[3]; s[3]=s[15]; s[15]=s[11]; s[11]=s[7]; s[7]=t;
}
static void inv_shift_rows(uint8_t *s) {
uint8_t t;
t=s[13]; s[13]=s[9]; s[9]=s[5]; s[5]=s[1]; s[1]=t;
t=s[2]; s[2]=s[10]; s[10]=t; t=s[6]; s[6]=s[14]; s[14]=t;
t=s[7]; s[7]=s[11]; s[11]=s[15]; s[15]=s[3]; s[3]=t;
}
static void mix_columns(uint8_t *s) {
for (uint8_t c = 0; c < 4; c++) {
const uint8_t i = c * 4;
const uint8_t a0=s[i], a1=s[i+1], a2=s[i+2], a3=s[i+3];
s[i] = xtime(a0) ^ xtime(a1) ^ a1 ^ a2 ^ a3;
s[i+1] = a0 ^ xtime(a1) ^ xtime(a2) ^ a2 ^ a3;
s[i+2] = a0 ^ a1 ^ xtime(a2) ^ xtime(a3) ^ a3;
s[i+3] = xtime(a0) ^ a0 ^ a1 ^ a2 ^ xtime(a3);
}
}
static void inv_mix_columns(uint8_t *s) {
for (uint8_t c = 0; c < 4; c++) {
const uint8_t i = c * 4;
const uint8_t a0=s[i], a1=s[i+1], a2=s[i+2], a3=s[i+3];
s[i] = gf_mul(0x0e,a0)^gf_mul(0x0b,a1)^gf_mul(0x0d,a2)^gf_mul(0x09,a3);
s[i+1] = gf_mul(0x09,a0)^gf_mul(0x0e,a1)^gf_mul(0x0b,a2)^gf_mul(0x0d,a3);
s[i+2] = gf_mul(0x0d,a0)^gf_mul(0x09,a1)^gf_mul(0x0e,a2)^gf_mul(0x0b,a3);
s[i+3] = gf_mul(0x0b,a0)^gf_mul(0x0d,a1)^gf_mul(0x09,a2)^gf_mul(0x0e,a3);
}
}
static void key_expansion(aes128_context *ctx) {
memcpy(ctx->round_keys[0], ctx->key, 16);
for (uint8_t i = 1; i <= 10; i++) {
uint8_t temp[4];
temp[0] = ctx->round_keys[i-1][12];
temp[1] = ctx->round_keys[i-1][13];
temp[2] = ctx->round_keys[i-1][14];
temp[3] = ctx->round_keys[i-1][15];
const uint8_t t = temp[0];
temp[0]=temp[1]; temp[1]=temp[2]; temp[2]=temp[3]; temp[3]=t;
temp[0]=sbox[temp[0]]; temp[1]=sbox[temp[1]];
temp[2]=sbox[temp[2]]; temp[3]=sbox[temp[3]];
temp[0] ^= rcon[i-1];
ctx->round_keys[i][0] = ctx->round_keys[i-1][0] ^ temp[0];
ctx->round_keys[i][1] = ctx->round_keys[i-1][1] ^ temp[1];
ctx->round_keys[i][2] = ctx->round_keys[i-1][2] ^ temp[2];
ctx->round_keys[i][3] = ctx->round_keys[i-1][3] ^ temp[3];
for (uint8_t j = 1; j < 4; j++) {
ctx->round_keys[i][j*4+0] = ctx->round_keys[i][(j-1)*4+0] ^ ctx->round_keys[i-1][j*4+0];
ctx->round_keys[i][j*4+1] = ctx->round_keys[i][(j-1)*4+1] ^ ctx->round_keys[i-1][j*4+1];
ctx->round_keys[i][j*4+2] = ctx->round_keys[i][(j-1)*4+2] ^ ctx->round_keys[i-1][j*4+2];
ctx->round_keys[i][j*4+3] = ctx->round_keys[i][(j-1)*4+3] ^ ctx->round_keys[i-1][j*4+3];
}
}
}
void aes128_set_key(aes128_context *ctx, const uint8_t *key) {
memcpy(ctx->key, key, AES128_KEY_LEN);
key_expansion(ctx);
}
void aes128_encrypt_block(aes128_context *ctx, const uint8_t *input, uint8_t *output) {
memcpy(ctx->state, input, AES_BLOCK_SIZE);
add_round_key(ctx->state, ctx->round_keys[0]);
for (uint8_t r = 1; r <= 9; r++) {
sub_bytes(ctx->state);
shift_rows(ctx->state);
mix_columns(ctx->state);
add_round_key(ctx->state, ctx->round_keys[r]);
}
sub_bytes(ctx->state);
shift_rows(ctx->state);
add_round_key(ctx->state, ctx->round_keys[10]);
memcpy(output, ctx->state, AES_BLOCK_SIZE);
}
void aes128_decrypt_block(aes128_context *ctx, const uint8_t *input, uint8_t *output) {
memcpy(ctx->state, input, AES_BLOCK_SIZE);
add_round_key(ctx->state, ctx->round_keys[10]);
for (uint8_t r = 9; r >= 1; r--) {
inv_shift_rows(ctx->state);
inv_sub_bytes(ctx->state);
add_round_key(ctx->state, ctx->round_keys[r]);
inv_mix_columns(ctx->state);
}
inv_shift_rows(ctx->state);
inv_sub_bytes(ctx->state);
add_round_key(ctx->state, ctx->round_keys[0]);
memcpy(output, ctx->state, AES_BLOCK_SIZE);
}
void aes128_ecb_encrypt_48bytes(aes128_context *ctx, const uint8_t *input, uint8_t *output) {
aes128_encrypt_block(ctx, input, output);
aes128_encrypt_block(ctx, input + 16, output + 16);
aes128_encrypt_block(ctx, input + 32, output + 32);
}
void aes128_ecb_decrypt_48bytes(aes128_context *ctx, const uint8_t *input, uint8_t *output) {
aes128_decrypt_block(ctx, input, output);
aes128_decrypt_block(ctx, input + 16, output + 16);
aes128_decrypt_block(ctx, input + 32, output + 32);
}
+28
View File
@@ -0,0 +1,28 @@
/**
* @file aes128.h
* @brief AES-128 ECB 加解密(纯软件实现,移植自电机 BOOT 工程)。
* 用于电机 OTA relay 中对固件数据加密及密钥协商。
*/
#ifndef SYS_AES128_H
#define SYS_AES128_H
#include <cstdint>
#include <cstddef>
#include <cstring>
static constexpr uint8_t AES128_KEY_LEN = 16U;
static constexpr uint8_t AES_BLOCK_SIZE = 16U;
struct aes128_context {
uint8_t key[AES128_KEY_LEN];
uint8_t round_keys[11][16];
uint8_t state[AES_BLOCK_SIZE];
};
void aes128_set_key(aes128_context *ctx, const uint8_t *key);
void aes128_encrypt_block(aes128_context *ctx, const uint8_t *input, uint8_t *output);
void aes128_decrypt_block(aes128_context *ctx, const uint8_t *input, uint8_t *output);
void aes128_ecb_encrypt_48bytes(aes128_context *ctx, const uint8_t *input, uint8_t *output);
void aes128_ecb_decrypt_48bytes(aes128_context *ctx, const uint8_t *input, uint8_t *output);
#endif /* SYS_AES128_H */
+60 -18
View File
@@ -14,31 +14,62 @@
* 功能开关(调试/特性总闸 — 改这里即可开关,改完重编)
* 0/false=关,1/true=开。需要 #if 的保留 #define。
* ============================================================ */
/** @brief 周期温度/电压 UART 打印(5s,task_manager #if)。 */
#define CONFIG_UART_TEMP_PERIODIC_PRINT 1
/** @brief UART 诊断详细级别(task_runner #if;0=精简 1=含 CAN 统计)。 */
#define CONFIG_UART_DIAG_LEVEL 1
/**
* @brief 5s 诊断任务是否输出全量 UART 摘要(task_runner)。
* @note 任务本身始终 5s 调度:0=默认仅状态变化/错误增量;1=周期打印 ADC/CAN/ECAT 全量行。
* 现场抓吞吐/温度时改回 1 后重编即可。
*/
#define CONFIG_UART_TEMP_PERIODIC_PRINT 0
/**
* @brief 全量摘要详细级别(仅 PERIODIC_PRINT=1 时生效;0=ADC 精简 1=含 CAN 统计)。
* @note 默认 0;需要 can_tx/rx/drop 等列时与 PERIODIC_PRINT 一并打开。
*/
#define CONFIG_UART_DIAG_LEVEL 0
/**
* @brief SPI 调试反馈 0xC4:GD32 ADC 温度/VDDA/VIN 1Hz 主动推送。
* @note 调试用;0=不注册 handler、不推送。与 ESP32 GD32_ADC_FEEDBACK_ENABLE 配对。
*/
#define CONFIG_SPI_ADC_FEEDBACK_ENABLE 1
/**
* @brief 多电机状态表 0xC5:CAN STATUS 帧缓存 + 250ms 主动推送。
* @note 0=不注册 handler、不推送、不解析 STATUS 帧。与 ESP32 端 motor_state_task 配对。
*/
#define CONFIG_MOTOR_STATE_ENABLE 1
/**
* @brief 多电机版本 0xC6:CAN 版本应答帧(0x00FF)缓存 + 事件驱动推送。
* @note 0=不识别版本帧、不推送。与 ESP32 端 motor_version 缓存配对。
* 版本为按需查询的低频数据,独立于 0xC5 周期推送。
*/
#define CONFIG_MOTOR_VERSION_ENABLE 1
/** @brief 最大电机节点数 (node ID 1-9)。 */
constexpr uint8_t CONFIG_MOTOR_MAX_NODES = 9U;
/** @brief 电机离线判定超时 (ms)。超过此时间未收到 STATUS 帧则标记 offline。 */
constexpr uint16_t CONFIG_MOTOR_OFFLINE_MS = 3000U;
/** @brief SPI 命令逐帧日志(#if)。 */
#define CONFIG_SPI_APP_LOG_ENABLE 0
/** @brief CAN_SEND 成功帧详细日志(#if)。 */
#define CONFIG_SPI_CAN_SEND_VERBOSE_LOG 0
/** @brief CAN-FD BRS 使能(canfd.cpp #if)。 */
#define CONFIG_CAN_FD_BRS_ENABLE 1U
/** @brief CAN-FD BRS 使能(canfd.cpp #if)。
* 关闭 BRS: 数据段用仲裁段同速率,牢牢性更高。
* 电机 BOOT 无法正确接收 BRS 高速段→nonce(16B FD+BRS)被拒绝→握手失败。 */
#define CONFIG_CAN_FD_BRS_ENABLE 0U
/** @brief 启动时总线探测帧;正常固件关。 */
#define CONFIG_CAN_STARTUP_BUS_TEST_ENABLE 0
/** @brief 启动时内部回环自检;正常固件关。 */
#define CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE 0
/** @brief CAN 启动详细诊断 dump;默认关。 */
#define CONFIG_CAN_STARTUP_DIAG_VERBOSE 0
/** @brief CAN0/1/2 通道使能。 */
/**
* @brief 周期测试发送任务(taskCanTxSend,发 0x18FF5000 64B FD 帧)。
* @note 调试用:该 PGN 无业务接收节点 ACK。auto_retrans=ENABLE 时无 ACK 被
* 无限重发吞进 TEC(TEC 一直涨);DISABLE 时单发失败即 MTEn→error_count++。
* 电机拓扑下无对端应答,默认关以消除持续错误帧。需要回放测试波形时打开。
*/
#define CONFIG_CAN_PERIODIC_TEST_TX_ENABLE 0
/** @brief CAN0/1/2 通道使能。CH1/CH2 未接设备,关闭避免持续 CAN 错误 + 占 SPI 带宽。 */
constexpr bool CONFIG_CAN_CH0_ENABLE = true;
constexpr bool CONFIG_CAN_CH1_ENABLE = true;
constexpr bool CONFIG_CAN_CH2_ENABLE = true;
constexpr bool CONFIG_CAN_CH1_ENABLE = false;
constexpr bool CONFIG_CAN_CH2_ENABLE = false;
/** @brief 主循环 WFI(main.cpp #if)。 */
#define CONFIG_MAIN_LOOP_WFI_ENABLE 0
/** @brief EtherCAT SYNC0 同步模式(#if)。 */
@@ -48,13 +79,13 @@ constexpr bool CONFIG_CAN_CH2_ENABLE = true;
* APP 固件版本号
* ============================================================ */
constexpr uint32_t APP_FW_VERSION_MAJOR = 1U;
constexpr uint32_t APP_FW_VERSION_MINOR = 3U;
constexpr uint32_t APP_FW_VERSION_PATCH = 0U;
constexpr uint32_t APP_FW_VERSION_MINOR = 4U;
constexpr uint32_t APP_FW_VERSION_PATCH = 75U; /* 1.4.75:尾段fed>=fw_size旁路水位门控,修复末6包死锁 */
constexpr uint32_t APP_FW_VERSION_BUILD = 0U;
/** @brief USART0 默认波特率。 */
constexpr uint32_t CONFIG_USART0_BAUDRATE = 115200U;
constexpr uint32_t CONFIG_USART0_BAUDRATE = 3000000U; /* 3M:诊断打印高速输出,printf 阻塞极小、不拖慢 relay */
/** @brief ADC 外设实例。 */
constexpr uint32_t CONFIG_ADC_INSTANCE = ADC0;
@@ -100,8 +131,13 @@ constexpr uint16_t CONFIG_SPI_RX_BUF_SIZE = 256U;
constexpr uint16_t CONFIG_SPI_LOG_BUF_SIZE = 256U;
/** @brief 主动按铃超时:ESP32 未在该时间内来读则 cancelRing(ms)。 */
constexpr uint32_t CONFIG_SPI_RING_TIMEOUT_MS = 50U;
/** @brief SPI 命令 handler 注册表容量。 */
constexpr uint8_t CONFIG_SPI_MAX_COMMANDS = 8U;
/** @brief SPI 命令 handler 注册表容量。
*
* 当前 init 注册 9 个:boot_request / ota_wait / ping / can_bridge /
* can_feedback / id_snapshot / adc_feedback / motor_state / motor_ota。
* registerHandler 表满时静默丢弃,motor_ota 曾因容量=8 被丢弃(0xD0 回 0xEE)。
* 取 12 留余量;新增 handler 时须确认本容量足够。 */
constexpr uint8_t CONFIG_SPI_MAX_COMMANDS = 12U;
/** @brief 每通道不重复 ID 快照表最大条目数(与 ESP32 CANFD_ID_SNAPSHOT_MAX 对齐)。 */
constexpr uint8_t CONFIG_CANFD_ID_SNAPSHOT_MAX = 16U;
/** @brief 快照条目最大数据字节数(CAN FD DLC 上限)。 */
@@ -176,10 +212,15 @@ constexpr uint32_t CONFIG_TASK_PERIOD_VREF_SAMPLE_TICKS = 5U;
constexpr uint32_t CONFIG_TASK_PERIOD_TEMP_SAMPLE_TICKS = 10U;
/** @brief 调度基准节拍:10ms 计数周期,单位 100us tick。 */
constexpr uint32_t CONFIG_TASK_PERIOD_CAN_RX_TICKS = 100U;
/** @brief 调度基准节拍:CAN 周期发送任务周期,10 tick = 1ms。 */
constexpr uint32_t CONFIG_TASK_PERIOD_CAN_TX_TICKS = 10U;
/** @brief 调度基准节拍:CAN 周期发送任务周期,100 tick = 10ms。
* @note 原 1ms/帧过密,改为 10ms/帧以降低 CAN 总线负载与 GD32 邮箱压力。 */
constexpr uint32_t CONFIG_TASK_PERIOD_CAN_TX_TICKS = 100U;
/** @brief 调度基准节拍:心跳周期,单位 100us tick。 */
constexpr uint32_t CONFIG_TASK_PERIOD_HEARTBEAT_TICKS = 10000U;
/** @brief 调度基准节拍:多电机状态 0xC5 主动推送启动周期,2500 tick = 250ms。
* @note 独立于 1Hz heartbeat,避免连带加速 OTA Boot 轮询 / CAN 反馈快照刷新 /
* ADC 推送 / checkOffline 离线判定(3s 阈值)。前端 age 应稳定在 ~120ms。 */
constexpr uint32_t CONFIG_TASK_PERIOD_MOTOR_STATE_TICKS = 2500U;
/** @brief 调度基准节拍:APP 状态/诊断打印周期,50000 tick = 5s。 */
constexpr uint32_t CONFIG_TASK_PERIOD_DIAG_PRINT_TICKS = 50000U;
@@ -225,8 +266,9 @@ constexpr uint32_t CONFIG_ESC_PDI_OUTPUT_OFFSET = 0x1200U;
constexpr size_t CONFIG_ETHERCAT_PDO_SIZE = 32U;
/* ---------- EXMC 引脚配置(GD32E508 EXMC 标准映射) ---------- */
constexpr uint32_t CONFIG_ESC_EXMC_BASE_ADDR = 0x60000000U;
/* Region1 = NE1 @ 0x60000000;数值与 gd32e50x_exmc.h 中 EXMC_BANK0_NORSRAM_REGION1 一致 */
constexpr uint32_t CONFIG_ESC_EXMC_BASE_ADDR = 0x64000000U;
/* Region1 = NE1 @ 0x64000000(GD32E50x 用户手册 Rev2.2 图25-3: Bank0 四 Region 各 64MB,
* HADDR[27:26]=01 选中 NE1)。LAN9252 CS 接 PD7=EXMC_NE1,故基址为 0x64000000。 */
#ifndef EXMC_BANK0_NORSRAM_REGION1
#define EXMC_BANK0_NORSRAM_REGION1 ((uint32_t)0x00000001U)
#endif
File diff suppressed because it is too large Load Diff
+84
View File
@@ -0,0 +1,84 @@
/**
* @file motor_ota_relay.h
* @brief 电机 OTA relay 状态机 — GD32 本地完成与电机 Bootloader 的全部交互。
*
* 职责:
* - AES-128 密钥协商(K_master 存本地 flash)
* - 固件数据 AES-ECB 加密 + keyed-CRC32
* - CAN 上逐包 stop-and-wait(发 0x101 → 等 0x102 ACK → 按包号继续)
* - 进度/完成/错误通过 SPI ring push 报 ESP32
*
* ESP32 通过 SPI 0xD0(start) / 0xD1(data) / 0xD3(cancel) 喂数据,
* GD32 通过 0xD2 ring push 报进度。
*/
#ifndef SYS_MOTOR_OTA_RELAY_H
#define SYS_MOTOR_OTA_RELAY_H
#include <cstdint>
namespace MotorOtaRelay {
/* 电机 Bootloader CAN ID(固定,非节点相关) */
static constexpr uint32_t kOtaReqId = 0x00000100U; /* Host→Motor 握手 */
static constexpr uint32_t kOtaDataId = 0x00000101U; /* Host→Motor 数据 */
static constexpr uint32_t kOtaAckId = 0x00000102U; /* Motor→Host ACK */
static constexpr uint32_t kOtaWaitId = 0x00000103U; /* Motor→Host IDLE心跳 */
static constexpr uint32_t kOtaCancelId = 0x00000000U; /* Host→Motor 取消 */
static constexpr uint32_t kPacketPayload = 48U; /* 每包有效载荷 */
static constexpr uint32_t kFrameDlc = 64U; /* CAN FD DLC */
static constexpr uint32_t kMaxFwSize = 0xC000U; /* 48 KB */
static constexpr uint32_t kAckTimeoutMs = 5000U; /* 单包 ACK 超时 */
static constexpr uint32_t kHandshakeTimeoutMs = 3000U;
enum class State : uint8_t {
kIdle = 0,
kHandshakeReq, /* 已发 [00 AC],等 ACK */
kHandshakeNonce, /* 已发 nonce,等 36B ACK */
kHandshakeConfirm, /* 已发 confirm,等 [BA C0] */
kErase, /* 已发 size,等擦除完成 ACK */
kTransfer, /* 逐包传输中 */
kFinishOta, /* 收到 0xF0F0F0F0,已发 [0A AC],等 FINISH ACK */
kFinished, /* 完成 */
kError, /* 错误 */
kPreCancel = 9 /* 等待电机 BOOT 心跳(0x103)确认已进入 Bootloader,然后进 kHandshakeReq */
};
struct Status {
State state;
uint8_t error_code;
uint16_t packets_acked;
uint16_t total_packets;
uint32_t fw_size;
};
/* 初始化(注册 CAN RX 回调等) */
void init();
/* 主循环推进(从 task_runner 周期调用,建议 1~5ms 一次) */
void process();
/* 数据发送推进:由主循环 TaskRunner 每圈调用,按 SysTick 钟表控制每包间隔
* (不再走 ISR 计时 + TASK 标志,消除跨上下文计数器竞争)。 */
void onTxTick();
/* CAN RX 分发入口:匹配 0x102 ACK 帧时消费,返回 true 表示已处理 */
bool onCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t len);
/* SPI 命令接口 */
bool start(uint8_t can_channel, uint32_t fw_size);
/**
* @brief 喂入固件明文。payload 布局:[offset:u32 LE][data...]
* offset 为固件绝对字节偏移;重复/重叠区间幂等 ACK,缺口拒绝(→SPI WAIT)。
* 无 offset 的旧纯流会在 SPI 超时重传时把同一 chunk 追加进环缓(BLF 见前 236B 循环)。
*/
bool feedData(const uint8_t *data, uint16_t len);
void cancel();
/* 状态查询 */
Status getStatus();
bool isActive();
} /* namespace MotorOtaRelay */
#endif /* SYS_MOTOR_OTA_RELAY_H */
+9 -3
View File
@@ -32,6 +32,7 @@ void TaskManager::init()
g_scheduler.div_can_rx = 0U;
g_scheduler.div_can_tx = 0U;
g_scheduler.div_heartbeat = 0U;
g_scheduler.div_motor_state = 0U;
g_scheduler.div_diag_print = 0U;
}
@@ -95,7 +96,6 @@ void TaskManager::tick100us()
if(++g_scheduler.div_can_tx >= CONFIG_TASK_PERIOD_CAN_TX_TICKS) {
g_scheduler.div_can_tx = 0U;
g_scheduler.tick_1ms++;
set(1UL << TASK_CAN_TX_SEND);
}
@@ -106,12 +106,18 @@ void TaskManager::tick100us()
set(1UL << TASK_HEARTBEAT);
}
#if (CONFIG_UART_TEMP_PERIODIC_PRINT != 0)
/* 多电机状态 0xC5 推送:250ms 独立节拍。
* 不挂在 1Hz heartbeat 上,避免连带加速 OTA Boot 轮询/CAN快照/ADC推送/checkOffline。 */
if(++g_scheduler.div_motor_state >= CONFIG_TASK_PERIOD_MOTOR_STATE_TICKS) {
g_scheduler.div_motor_state = 0U;
set(1UL << TASK_MOTOR_STATE_PUSH);
}
/* 始终 5s 调度诊断任务:默认静默检查错误增量,PERIODIC_PRINT=1 时打全量摘要 */
if(++g_scheduler.div_diag_print >= CONFIG_TASK_PERIOD_DIAG_PRINT_TICKS) {
g_scheduler.div_diag_print = 0U;
set(1UL << TASK_DIAGNOSTIC_PRINT);
}
#endif
}
/**
+182 -42
View File
@@ -8,8 +8,11 @@
#include "device/watchdog.h"
#include "device/spi/app_spi_command.h"
#include "device/canfd.h"
#include "device/motor_state.h"
#include "device/motor_version.h"
#include "sys/config.h"
#include "sys/ota_boot_request.h"
#include "sys/motor_ota_relay.h"
#include "sys/task_manager.h"
#include "sys/task_runner.h"
#include "comm/ethercat_slave.h"
@@ -134,7 +137,7 @@ void TaskRunner::taskAdcProcess()
}
/**
* @brief 执行 CAN 周期发送任务:每 1ms 在每个使能通道上发送一帧 CANFD 测试帧。
* @brief 执行 CAN 周期发送任务:每 10ms 在每个使能通道上发送一帧 CANFD 测试帧。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
@@ -150,6 +153,7 @@ void TaskRunner::taskAdcProcess()
*/
void TaskRunner::taskCanTxSend()
{
#if (CONFIG_CAN_PERIODIC_TEST_TX_ENABLE != 0)
static uint32_t seq = 0U;
for (uint8_t ch = 0U; ch < 3U; ++ch) {
@@ -176,6 +180,11 @@ void TaskRunner::taskCanTxSend()
seq++;
}
}
#else
/* CONFIG_CAN_PERIODIC_TEST_TX_ENABLE=0:测试帧无业务接收节点 ACK,
* 单发模式下会持续产生 MTEn 错误帧,整任务空跑。 */
(void)0;
#endif
}
/**
@@ -199,6 +208,25 @@ void TaskRunner::taskCanRxParse()
OtaBootRequest::process();
return;
}
if(MotorOtaRelay::onCanFrame(s_runtime.last_rx_channel,
s_runtime.last_rx_id,
s_runtime.last_rx_data,
s_runtime.last_rx_len)) {
consumed++;
continue;
}
#if (CONFIG_MOTOR_STATE_ENABLE != 0)
MotorState::onCanFrame(s_runtime.last_rx_channel,
s_runtime.last_rx_id,
s_runtime.last_rx_data,
s_runtime.last_rx_len);
#endif
#if (CONFIG_MOTOR_VERSION_ENABLE != 0)
MotorVersion::onCanFrame(s_runtime.last_rx_channel,
s_runtime.last_rx_id,
s_runtime.last_rx_data,
s_runtime.last_rx_len);
#endif
consumed++;
}
@@ -221,6 +249,22 @@ void TaskRunner::taskHeartbeat()
canFeedbackStartRound();
/* ADC 调试反馈 1Hz:CONFIG_SPI_ADC_FEEDBACK_ENABLE=0 时为空操作 */
adcFeedbackStartRound();
#if (CONFIG_MOTOR_STATE_ENABLE != 0)
MotorState::checkOffline();
#endif
}
/**
* @brief 启动多电机状态 0xC5 推送轮次(250ms 独立节拍)。
* @note 与 taskHeartbeat 解耦:不连带加速 OTA Boot 轮询 / CAN 快照 / ADC 推送 /
* checkOffline 离线判定(3s 阈值)。仅触发本轮 P0/P1 分页推送,
* 实际 SPI 传输由 motorStatePushStep() 在主循环按铃协议推进。
*/
void TaskRunner::taskMotorStatePush()
{
#if (CONFIG_MOTOR_STATE_ENABLE != 0)
motorStateStartRound();
#endif
}
/**
@@ -238,60 +282,62 @@ bool TaskRunner::copyAdcSample(Adc::Sample *out)
}
/**
* @brief 输出当前温度、电压与 CAN 诊断信息。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 由 5 秒节拍触发,通过 UART 打印当前温度、电压;采用定点整数格式,避免依赖浮点 `printf` 支持。
* @brief 5s 诊断任务:默认仅状态变化/错误增量;可选全量 UART 摘要。
* @note CONFIG_UART_TEMP_PERIODIC_PRINT=1 时打印 ADC/CAN/ECAT 全量行(调试用)。
* 默认=0:相对上次快照有 CAN 错误计数增量、ADC valid 跳变、ECAT ready 变化或 ISR 超阈时才打印。
* 采用定点整数格式,避免依赖浮点 printf。
*/
void TaskRunner::taskDiagnosticPrint()
{
const auto *diag = CanFd::diag();
const auto &s = s_runtime.sample;
const int32_t temp_x100 = static_cast<int32_t>(s.temperature * 100.0f);
const uint32_t vdda_mV = static_cast<uint32_t>(s.vdda * 1000.0f);
const uint32_t vin_adc_mV = static_cast<uint32_t>(s.vin_adc_v * 1000.0f);
#if (CONFIG_UART_TEMP_PERIODIC_PRINT != 0)
/* ---- Debug 全量摘要:周期刷 ADC / CAN 吞吐 / ECAT ISR 统计 ---- */
{
const int32_t temp_x100 = static_cast<int32_t>(s.temperature * 100.0f);
const uint32_t vdda_mV = static_cast<uint32_t>(s.vdda * 1000.0f);
const uint32_t vin_adc_mV = static_cast<uint32_t>(s.vin_adc_v * 1000.0f);
#if (CONFIG_UART_DIAG_LEVEL == 0)
printf("[APP] uptime=%lus valid=%u, temp_raw=%u, vref_raw=%u, temp=%ld.%02ld C, vdda=%lu.%03lu V, vin_adc=%lu.%03lu V\r\n",
(unsigned long)g_app_uptime_sec,
static_cast<unsigned int>(s.valid),
static_cast<unsigned int>(s.temp_raw),
static_cast<unsigned int>(s.vref_raw),
static_cast<long>(temp_x100 / 100),
static_cast<long>((temp_x100 >= 0 ? temp_x100 : -temp_x100) % 100),
static_cast<unsigned long>(vdda_mV / 1000U),
static_cast<unsigned long>(vdda_mV % 1000U),
static_cast<unsigned long>(vin_adc_mV / 1000U),
static_cast<unsigned long>(vin_adc_mV % 1000U));
printf("[APP] uptime=%lus valid=%u, temp_raw=%u, vref_raw=%u, temp=%ld.%02ld C, vdda=%lu.%03lu V, vin_adc=%lu.%03lu V\r\n",
(unsigned long)g_app_uptime_sec,
static_cast<unsigned int>(s.valid),
static_cast<unsigned int>(s.temp_raw),
static_cast<unsigned int>(s.vref_raw),
static_cast<long>(temp_x100 / 100),
static_cast<long>((temp_x100 >= 0 ? temp_x100 : -temp_x100) % 100),
static_cast<unsigned long>(vdda_mV / 1000U),
static_cast<unsigned long>(vdda_mV % 1000U),
static_cast<unsigned long>(vin_adc_mV / 1000U),
static_cast<unsigned long>(vin_adc_mV % 1000U));
#else
printf("[APP] uptime=%lus valid=%u, temp=%ld.%02ld C, vdda=%lu.%03lu V, vin_adc=%lu.%03lu V, rx_ch=%u, rx_id=0x%08lX, rx_len=%u, tx_ch=%u, can_tx=%lu, can_rx=%lu, sw_drop=%lu, hw_full=%lu, hw_over=%lu, tx_abort=%lu, can_err=%lu\r\n",
(unsigned long)g_app_uptime_sec,
static_cast<unsigned int>(s.valid),
static_cast<long>(temp_x100 / 100),
static_cast<long>((temp_x100 >= 0 ? temp_x100 : -temp_x100) % 100),
static_cast<unsigned long>(vdda_mV / 1000U),
static_cast<unsigned long>(vdda_mV % 1000U),
static_cast<unsigned long>(vin_adc_mV / 1000U),
static_cast<unsigned long>(vin_adc_mV % 1000U),
static_cast<unsigned int>(s_runtime.last_rx_channel),
static_cast<unsigned long>(s_runtime.last_rx_id),
static_cast<unsigned int>(s_runtime.last_rx_len),
static_cast<unsigned int>(diag->last_tx_channel),
static_cast<unsigned long>(diag->tx_count),
static_cast<unsigned long>(diag->rx_count),
static_cast<unsigned long>(diag->rx_drop_count),
static_cast<unsigned long>(diag->rx_fifo_full_count),
static_cast<unsigned long>(diag->rx_fifo_overfull_count),
static_cast<unsigned long>(diag->tx_abort_count),
static_cast<unsigned long>(diag->error_count));
printf("[APP] uptime=%lus valid=%u, temp=%ld.%02ld C, vdda=%lu.%03lu V, vin_adc=%lu.%03lu V, rx_ch=%u, rx_id=0x%08lX, rx_len=%u, tx_ch=%u, can_tx=%lu, can_rx=%lu, sw_drop=%lu, hw_full=%lu, hw_over=%lu, tx_abort=%lu, can_err=%lu\r\n",
(unsigned long)g_app_uptime_sec,
static_cast<unsigned int>(s.valid),
static_cast<long>(temp_x100 / 100),
static_cast<long>((temp_x100 >= 0 ? temp_x100 : -temp_x100) % 100),
static_cast<unsigned long>(vdda_mV / 1000U),
static_cast<unsigned long>(vdda_mV % 1000U),
static_cast<unsigned long>(vin_adc_mV / 1000U),
static_cast<unsigned long>(vin_adc_mV % 1000U),
static_cast<unsigned int>(s_runtime.last_rx_channel),
static_cast<unsigned long>(s_runtime.last_rx_id),
static_cast<unsigned int>(s_runtime.last_rx_len),
static_cast<unsigned int>(diag->last_tx_channel),
static_cast<unsigned long>(diag->tx_count),
static_cast<unsigned long>(diag->rx_count),
static_cast<unsigned long>(diag->rx_drop_count),
static_cast<unsigned long>(diag->rx_fifo_full_count),
static_cast<unsigned long>(diag->rx_fifo_overfull_count),
static_cast<unsigned long>(diag->tx_abort_count),
static_cast<unsigned long>(diag->error_count));
#endif
{
if (EthercatSlave::isEscReady()) {
uint32_t maxCyc = 0U, avgCyc = 0U, cnt = 0U;
EthercatSlave::getIsrStats(&maxCyc, &avgCyc, &cnt);
/* 180MHz: cycles / 180 = µs;用 *1000/180 获得毫微秒精度避免浮点 */
/* 180MHz: cycles / 180 = µs;用 *1000/180 获得亚微秒精度避免浮点 */
const uint32_t maxUs_x1000 = maxCyc * 1000U / 180U;
const uint32_t avgUs_x1000 = avgCyc * 1000U / 180U;
printf("[ECAT] periodicIsr cnt=%lu max=%lu.%03luus avg=%lu.%03luus\r\n",
@@ -303,6 +349,88 @@ void TaskRunner::taskDiagnosticPrint()
(unsigned long)EthercatSlave::getChipId());
}
}
#else
/* ---- 默认静默路径:仅错误增量 / 状态变化,无 uptime 心跳 ---- */
{
static uint32_t s_prev_rx_drop = 0U;
static uint32_t s_prev_rx_fifo_full = 0U;
static uint32_t s_prev_rx_fifo_overfull = 0U;
static uint32_t s_prev_tx_abort = 0U;
static uint32_t s_prev_error = 0U;
static uint8_t s_prev_adc_valid = 0xFFU; /* 0xFF=尚未采样 */
static bool s_prev_esc_ready = false;
static bool s_esc_ready_inited = false;
static uint32_t s_prev_ecat_max_cyc = 0U;
const uint32_t rx_drop = diag->rx_drop_count;
const uint32_t rx_full = diag->rx_fifo_full_count;
const uint32_t rx_over = diag->rx_fifo_overfull_count;
const uint32_t tx_abort = diag->tx_abort_count;
const uint32_t can_err = diag->error_count;
if (rx_drop > s_prev_rx_drop || rx_full > s_prev_rx_fifo_full
|| rx_over > s_prev_rx_fifo_overfull || tx_abort > s_prev_tx_abort
|| can_err > s_prev_error) {
printf("[CAN-DIAG] uptime=%lus err=%lu(+%lu) drop=%lu(+%lu) full=%lu(+%lu) over=%lu(+%lu) abort=%lu(+%lu)\r\n",
(unsigned long)g_app_uptime_sec,
(unsigned long)can_err,
(unsigned long)(can_err - s_prev_error),
(unsigned long)rx_drop,
(unsigned long)(rx_drop - s_prev_rx_drop),
(unsigned long)rx_full,
(unsigned long)(rx_full - s_prev_rx_fifo_full),
(unsigned long)rx_over,
(unsigned long)(rx_over - s_prev_rx_fifo_overfull),
(unsigned long)tx_abort,
(unsigned long)(tx_abort - s_prev_tx_abort));
s_prev_rx_drop = rx_drop;
s_prev_rx_fifo_full = rx_full;
s_prev_rx_fifo_overfull = rx_over;
s_prev_tx_abort = tx_abort;
s_prev_error = can_err;
}
if (s_prev_adc_valid == 0xFFU) {
s_prev_adc_valid = s.valid;
} else if (s.valid != s_prev_adc_valid) {
printf("[APP] adc valid %u -> %u (uptime=%lus)\r\n",
static_cast<unsigned int>(s_prev_adc_valid),
static_cast<unsigned int>(s.valid),
(unsigned long)g_app_uptime_sec);
s_prev_adc_valid = s.valid;
}
const bool esc_ready = EthercatSlave::isEscReady();
if (!s_esc_ready_inited) {
s_prev_esc_ready = esc_ready;
s_esc_ready_inited = true;
} else if (esc_ready != s_prev_esc_ready) {
printf("[ECAT] ESC ready %u -> %u chip_id=0x%08lX (uptime=%lus)\r\n",
static_cast<unsigned int>(s_prev_esc_ready ? 1U : 0U),
static_cast<unsigned int>(esc_ready ? 1U : 0U),
(unsigned long)EthercatSlave::getChipId(),
(unsigned long)g_app_uptime_sec);
s_prev_esc_ready = esc_ready;
}
if (esc_ready) {
uint32_t maxCyc = 0U, avgCyc = 0U, cnt = 0U;
EthercatSlave::getIsrStats(&maxCyc, &avgCyc, &cnt);
/* 阈值单位 µs → CPU 周期(180 MHz) */
const uint32_t threshCyc = CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD * 180U;
if (maxCyc > threshCyc && maxCyc > s_prev_ecat_max_cyc) {
const uint32_t maxUs_x1000 = maxCyc * 1000U / 180U;
printf("[ECAT] ISR max over threshold: max=%lu.%03luus thr=%luus cnt=%lu (uptime=%lus)\r\n",
(unsigned long)(maxUs_x1000 / 1000U),
(unsigned long)(maxUs_x1000 % 1000U),
(unsigned long)CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD,
(unsigned long)cnt,
(unsigned long)g_app_uptime_sec);
s_prev_ecat_max_cyc = maxCyc;
}
}
}
#endif
}
/**
@@ -359,12 +487,23 @@ void TaskRunner::runPending()
OtaBootRequest::process();
}
/* 电机 OTA relay:活跃时每圈推进状态机(等 ACK / 超时 / 发下一包)。
* 非活跃时立即返回,开销可忽略。 */
MotorOtaRelay::process();
/* 数据发送推进:主循环每圈调用,按 SysTick 钟表控制每包间隔
* (替代原靠 tick100us 计数 + TASK_MOTOR_OTA_TX 的方式,消除计数器竞争)。 */
MotorOtaRelay::onTxTick();
/* CAN 反馈主动推送:GD32 APP 循环推送 3 通道 × 2 页反馈帧到 ESP32,
* 替代 ESP32 主动轮询。非阻塞:上一帧未读走时立即返回。
* 每次主循环迭代调用一次,ESP32 读走一帧后 CS 释放中断唤醒主循环继续推送。 */
canFeedbackPushStep();
/* 多电机状态(0xC5):优先级介于 CAN 反馈与 ADC 之间;ring 忙则跳过。 */
motorStatePushStep();
/* ADC 调试反馈(0xC4):优先级低于 CAN;ring 忙则跳过。可 config 关闭。 */
adcFeedbackPushStep();
/* 多电机版本(0xC6):事件驱动、低频,优先级最低;无 dirty 版本时立即返回。 */
motorVersionPushStep();
/* 周期路径( SYNC0 ISR preempt=0 )用 s_periodicSeq 序列号通知主循环,
* 不再走 task_flags,避免 BASEPRI 临界区与 preempt=0 的 RMW 竞态。
@@ -385,6 +524,7 @@ void TaskRunner::runPending()
if(pending & (1UL << TASK_CAN_RX_PARSE)) taskCanRxParse();
if(pending & (1UL << TASK_CAN_TX_SEND)) taskCanTxSend();
if(pending & (1UL << TASK_HEARTBEAT)) taskHeartbeat();
if(pending & (1UL << TASK_MOTOR_STATE_PUSH)) taskMotorStatePush();
if(pending & (1UL << TASK_DIAGNOSTIC_PRINT)) taskDiagnosticPrint();
if(pending & (1UL << TASK_ETHERCAT_PROCESS)) taskEthercatProcess();
}
+3 -1
View File
@@ -46,10 +46,12 @@ private:
static void taskAdcProcess();
/** @brief 解析并消费接收帧。 */
static void taskCanRxParse();
/** @brief CAN 周期发送任务;每 1ms 每通道发送一帧测试报文。 */
/** @brief CAN 周期发送任务;每 10ms 每通道发送一帧测试报文。 */
static void taskCanTxSend();
/** @brief 心跳维护任务。 */
static void taskHeartbeat();
/** @brief 多电机状态 0xC5 主动推送启动(250ms 独立节拍)。 */
static void taskMotorStatePush();
/** @brief 诊断打印任务。 */
static void taskDiagnosticPrint();
/** @brief EtherCAT 周期/事件处理(非实时应用层 PDO 映射)。 */