Compare commits

..
2 Commits
Author SHA1 Message Date
cbb 771d34398f feat(core): OTA可用版本,添加看门狗功能并优化系统初始化日志
- 添加watchdog组件用于系统监控
- 在system_gd32e50x.c中增加USART0诊断输出,在IRC8M和PLL配置后
  发送"APP"和"CLK"标识
- 优化AppSpiCommand类中的日志处理机制,使用临界区保护
- 添加对全0xFF数据包的过滤处理,避免无效命令处理
- 将OTA重启延时从滴答计数改为毫秒级延时,提高响应速度
- 移除温度传感器相关文件,添加看门狗相关文件到项目配置
- 更新FWDGT API调用方式,使用新的配置函数
- 增加详细的启动日志输出,便于调试和追踪初始化过程
2026-05-30 20:03:31 +08:00
cbb f63114f5fd feat(canfd): 切换 CANFD 接收为中断驱动和环形缓冲机制
- 将 CANFD 接收机制由轮询改为 FIFO0 的中断事件驱动处理
- 实现循环取空硬件 FIFO0,提高接收效率和响应速度
- 引入环形缓冲区缓存接收帧,支持多路通道多帧队列管理
- 置位解析任务标志位,实现接收数据的异步解析
- 增加接收中断统计,包括 FIFO 满和溢出计数
- 重构临界区代码,使用自定义函数保护中断状态
- 修改接收过滤器配置,支持按掩码匹配标准帧进入 FIFO0
- 使能 CAN FIFO0 多种中断标志,包括非空、满和溢出中断
2026-05-30 00:24:31 +08:00
28 changed files with 1709 additions and 121 deletions
+4
View File
@@ -31,6 +31,10 @@ solution:
- set:
images:
- project-context: GD32E508_DCU_APP.Debug
debugger:
name: ST-Link@pyOCD
clock: 4000000
protocol: swd
# List of different build configurations.
build-types:
@@ -16,6 +16,8 @@ project:
- file: ./sys/task_manager.cpp
- file: ./sys/task_runner.h
- file: ./sys/task_runner.cpp
- file: ./sys/ota_boot_request.h
- file: ./sys/ota_boot_request.cpp
- file: ./sys/config.h
- group: device
files:
@@ -25,10 +27,14 @@ project:
- file: ./device/ADC.cpp
- file: ./device/canfd.h
- file: ./device/CANFD.cpp
- file: ./device/app_spi_command.h
- file: ./device/app_spi_command.cpp
- file: ./device/Timer.h
- file: ./device/Timer.cpp
- file: ./device/temp_sensor.h
- file: ./device/watchdog.h
- file: ./device/temp_sensor.cpp
- file: ./device/watchdog.cpp
# List components to use for your application.
# A software component is a re-usable unit that may be configurable.
@@ -50,6 +56,7 @@ project:
- component: Device:GD32E50x_StdPeripherals:TIMER
- component: Device:GD32E50x_StdPeripherals:USART
- component: Device:GD32E50x_libopt
- component: Device:GD32E50x_StdPeripherals:FWDGT
# List executable file formats to be generated.
output:
@@ -63,6 +70,7 @@ project:
C-CPP:
- -DGD32E508
- -DGD32E50X
- -DHXTAL_VALUE=8000000U
- -gdwarf-5
- -D__MICROLIB
- -fshort-enums
@@ -75,6 +83,7 @@ project:
ASM:
- -DGD32E508
- -DGD32E50X
- -DHXTAL_VALUE=8000000U
- -gdwarf-5
- -D__MICROLIB
Link:
@@ -0,0 +1,227 @@
/*!
\file gd32e50x_fwdgt.c
\brief FWDGT driver
\version 2026-02-09, V1.7.0, firmware for GD32E50x
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "gd32e50x_fwdgt.h"
/*!
\brief enable write access to FWDGT_PSC and FWDGT_RLD
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_write_enable(void)
{
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
}
/*!
\brief disable write access to FWDGT_PSC and FWDGT_RLD
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_write_disable(void)
{
FWDGT_CTL = FWDGT_WRITEACCESS_DISABLE;
}
/*!
\brief start the free watchdog timer counter
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_enable(void)
{
FWDGT_CTL = FWDGT_KEY_ENABLE;
}
/*!
\brief configure the free watchdog timer counter prescaler value
\param[in] prescaler_value: specify prescaler value
only one parameter can be selected which is shown as below:
\arg FWDGT_PSC_DIV4: FWDGT prescaler set to 4
\arg FWDGT_PSC_DIV8: FWDGT prescaler set to 8
\arg FWDGT_PSC_DIV16: FWDGT prescaler set to 16
\arg FWDGT_PSC_DIV32: FWDGT prescaler set to 32
\arg FWDGT_PSC_DIV64: FWDGT prescaler set to 64
\arg FWDGT_PSC_DIV128: FWDGT prescaler set to 128
\arg FWDGT_PSC_DIV256: FWDGT prescaler set to 256
\param[out] none
\retval ErrStatus: ERROR or SUCCESS
*/
ErrStatus fwdgt_prescaler_value_config(uint16_t prescaler_value)
{
uint32_t timeout = FWDGT_PSC_TIMEOUT;
uint32_t flag_status = RESET;
ErrStatus status = SUCCESS;
/* enable write access to FWDGT_PSC */
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
/* configure FWDGT */
FWDGT_PSC = (uint32_t)prescaler_value;
/* wait until the PUD flag to be reset */
do {
flag_status = FWDGT_STAT & FWDGT_STAT_PUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
return status;
}
/*!
\brief configure the free watchdog timer counter reload value
\param[in] reload_value: specify reload value(0x0000 - 0x0FFF)
\param[out] none
\retval ErrStatus: ERROR or SUCCESS
*/
ErrStatus fwdgt_reload_value_config(uint16_t reload_value)
{
uint32_t timeout = FWDGT_RLD_TIMEOUT;
uint32_t flag_status = RESET;
ErrStatus status = SUCCESS;
/* enable write access to FWDGT_RLD */
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
/* configure FWDGT_RLD */
FWDGT_RLD = RLD_RLD(reload_value);
/* wait until the RUD flag to be reset */
do {
flag_status = FWDGT_STAT & FWDGT_STAT_RUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
return status;
}
/*!
\brief reload the counter of FWDGT
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_counter_reload(void)
{
FWDGT_CTL = FWDGT_KEY_RELOAD;
}
/*!
\brief configure counter reload value, and prescaler divider value
\param[in] reload_value: specify reload value(0x0000 - 0x0FFF)
\param[in] prescaler_value: FWDGT prescaler value
only one parameter can be selected which is shown as below:
\arg FWDGT_PSC_DIV4: FWDGT prescaler set to 4
\arg FWDGT_PSC_DIV8: FWDGT prescaler set to 8
\arg FWDGT_PSC_DIV16: FWDGT prescaler set to 16
\arg FWDGT_PSC_DIV32: FWDGT prescaler set to 32
\arg FWDGT_PSC_DIV64: FWDGT prescaler set to 64
\arg FWDGT_PSC_DIV128: FWDGT prescaler set to 128
\arg FWDGT_PSC_DIV256: FWDGT prescaler set to 256
\param[out] none
\retval ErrStatus: ERROR or SUCCESS
*/
ErrStatus fwdgt_config(uint16_t reload_value, uint8_t prescaler_value)
{
uint32_t timeout = FWDGT_PSC_TIMEOUT;
uint32_t flag_status = RESET;
ErrStatus status = SUCCESS;
/* start the free watchdog timer counter */
FWDGT_CTL = FWDGT_KEY_ENABLE;
/* enable write access to FWDGT_PSC,and FWDGT_RLD */
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
/* configure FWDGT PSC */
FWDGT_PSC = (uint32_t)prescaler_value;
/* wait until the PUD flag to be reset */
do {
flag_status = FWDGT_STAT & FWDGT_STAT_PUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
if(SUCCESS == status) {
/* configure FWDGT_RLD */
FWDGT_RLD = RLD_RLD(reload_value);
/* wait until the RUD flag to be reset */
timeout = FWDGT_RLD_TIMEOUT;
do {
flag_status = FWDGT_STAT & FWDGT_STAT_RUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
}
if(SUCCESS == status) {
/* reload the counter */
FWDGT_CTL = FWDGT_KEY_RELOAD;
}
return status;
}
/*!
\brief get flag state of FWDGT
\param[in] flag: flag to get
only one parameter can be selected which is shown as below:
\arg FWDGT_FLAG_PUD: a write operation to FWDGT_PSC register is on going
\arg FWDGT_FLAG_RUD: a write operation to FWDGT_RLD register is on going
\param[out] none
\retval FlagStatus: SET or RESET
*/
FlagStatus fwdgt_flag_get(uint16_t flag)
{
FlagStatus flag_status = RESET;
if(FWDGT_STAT & flag) {
flag_status = SET;
}
return flag_status;
}
@@ -0,0 +1,227 @@
/*!
\file gd32e50x_fwdgt.c
\brief FWDGT driver
\version 2026-02-09, V1.7.0, firmware for GD32E50x
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "gd32e50x_fwdgt.h"
/*!
\brief enable write access to FWDGT_PSC and FWDGT_RLD
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_write_enable(void)
{
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
}
/*!
\brief disable write access to FWDGT_PSC and FWDGT_RLD
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_write_disable(void)
{
FWDGT_CTL = FWDGT_WRITEACCESS_DISABLE;
}
/*!
\brief start the free watchdog timer counter
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_enable(void)
{
FWDGT_CTL = FWDGT_KEY_ENABLE;
}
/*!
\brief configure the free watchdog timer counter prescaler value
\param[in] prescaler_value: specify prescaler value
only one parameter can be selected which is shown as below:
\arg FWDGT_PSC_DIV4: FWDGT prescaler set to 4
\arg FWDGT_PSC_DIV8: FWDGT prescaler set to 8
\arg FWDGT_PSC_DIV16: FWDGT prescaler set to 16
\arg FWDGT_PSC_DIV32: FWDGT prescaler set to 32
\arg FWDGT_PSC_DIV64: FWDGT prescaler set to 64
\arg FWDGT_PSC_DIV128: FWDGT prescaler set to 128
\arg FWDGT_PSC_DIV256: FWDGT prescaler set to 256
\param[out] none
\retval ErrStatus: ERROR or SUCCESS
*/
ErrStatus fwdgt_prescaler_value_config(uint16_t prescaler_value)
{
uint32_t timeout = FWDGT_PSC_TIMEOUT;
uint32_t flag_status = RESET;
ErrStatus status = SUCCESS;
/* enable write access to FWDGT_PSC */
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
/* configure FWDGT */
FWDGT_PSC = (uint32_t)prescaler_value;
/* wait until the PUD flag to be reset */
do {
flag_status = FWDGT_STAT & FWDGT_STAT_PUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
return status;
}
/*!
\brief configure the free watchdog timer counter reload value
\param[in] reload_value: specify reload value(0x0000 - 0x0FFF)
\param[out] none
\retval ErrStatus: ERROR or SUCCESS
*/
ErrStatus fwdgt_reload_value_config(uint16_t reload_value)
{
uint32_t timeout = FWDGT_RLD_TIMEOUT;
uint32_t flag_status = RESET;
ErrStatus status = SUCCESS;
/* enable write access to FWDGT_RLD */
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
/* configure FWDGT_RLD */
FWDGT_RLD = RLD_RLD(reload_value);
/* wait until the RUD flag to be reset */
do {
flag_status = FWDGT_STAT & FWDGT_STAT_RUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
return status;
}
/*!
\brief reload the counter of FWDGT
\param[in] none
\param[out] none
\retval none
*/
void fwdgt_counter_reload(void)
{
FWDGT_CTL = FWDGT_KEY_RELOAD;
}
/*!
\brief configure counter reload value, and prescaler divider value
\param[in] reload_value: specify reload value(0x0000 - 0x0FFF)
\param[in] prescaler_value: FWDGT prescaler value
only one parameter can be selected which is shown as below:
\arg FWDGT_PSC_DIV4: FWDGT prescaler set to 4
\arg FWDGT_PSC_DIV8: FWDGT prescaler set to 8
\arg FWDGT_PSC_DIV16: FWDGT prescaler set to 16
\arg FWDGT_PSC_DIV32: FWDGT prescaler set to 32
\arg FWDGT_PSC_DIV64: FWDGT prescaler set to 64
\arg FWDGT_PSC_DIV128: FWDGT prescaler set to 128
\arg FWDGT_PSC_DIV256: FWDGT prescaler set to 256
\param[out] none
\retval ErrStatus: ERROR or SUCCESS
*/
ErrStatus fwdgt_config(uint16_t reload_value, uint8_t prescaler_value)
{
uint32_t timeout = FWDGT_PSC_TIMEOUT;
uint32_t flag_status = RESET;
ErrStatus status = SUCCESS;
/* start the free watchdog timer counter */
FWDGT_CTL = FWDGT_KEY_ENABLE;
/* enable write access to FWDGT_PSC,and FWDGT_RLD */
FWDGT_CTL = FWDGT_WRITEACCESS_ENABLE;
/* configure FWDGT PSC */
FWDGT_PSC = (uint32_t)prescaler_value;
/* wait until the PUD flag to be reset */
do {
flag_status = FWDGT_STAT & FWDGT_STAT_PUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
if(SUCCESS == status) {
/* configure FWDGT_RLD */
FWDGT_RLD = RLD_RLD(reload_value);
/* wait until the RUD flag to be reset */
timeout = FWDGT_RLD_TIMEOUT;
do {
flag_status = FWDGT_STAT & FWDGT_STAT_RUD;
} while((--timeout > 0U) && ((uint32_t)RESET != flag_status));
if((uint32_t)RESET != flag_status) {
status = ERROR;
}
}
if(SUCCESS == status) {
/* reload the counter */
FWDGT_CTL = FWDGT_KEY_RELOAD;
}
return status;
}
/*!
\brief get flag state of FWDGT
\param[in] flag: flag to get
only one parameter can be selected which is shown as below:
\arg FWDGT_FLAG_PUD: a write operation to FWDGT_PSC register is on going
\arg FWDGT_FLAG_RUD: a write operation to FWDGT_RLD register is on going
\param[out] none
\retval FlagStatus: SET or RESET
*/
FlagStatus fwdgt_flag_get(uint16_t flag)
{
FlagStatus flag_status = RESET;
if(FWDGT_STAT & flag) {
flag_status = SET;
}
return flag_status;
}
@@ -14,10 +14,10 @@
// <o> Base address <0x0-0xFFFFFFFF:8>
// <i> Defines base address of memory region. Default: 0x08000000
// <i> Contains Startup and Vector Table
#define __ROM0_BASE 0x08000000
#define __ROM0_BASE 0x08048000
// <o> Region size [bytes] <0x0-0xFFFFFFFF:8>
// <i> Defines size of memory region. Default: 0x00080000
#define __ROM0_SIZE 0x00080000
#define __ROM0_SIZE 0x00036000
// </h>
// <h> __ROM1 (unused)
@@ -1,4 +1,4 @@
/*!
/*!
\file system_gd32e50x.c
\brief CMSIS Cortex-M33 Device Peripheral Access Layer Source File for
GD32E50x Device Series
@@ -31,7 +31,7 @@
#define __IRC8M (IRC8M_VALUE) /* internal 8 MHz RC oscillator frequency */
#define __HXTAL (HXTAL_VALUE) /* high speed crystal oscillator frequency */
#define VECT_TAB_OFFSET (uint32_t)0x00 /* vector table base offset */
#define VECT_TAB_OFFSET (uint32_t)0x00048000 /* vector table base offset */
/* select a system clock by uncommenting the following line */
/* use IRC8M */
@@ -142,8 +142,12 @@ static void system_clock_180m_hxtal(void);
/* configure the system clock */
static void system_clock_config(void);
/* software delay to prevent the impact of Vcore fluctuations.
It is strongly recommended to include it to avoid issues caused by self-removal. */
/*!
\brief software delay to prevent the impact of Vcore fluctuations
\param[in] time delay factor
\param[out] none
\retval none
*/
static void _soft_delay_(uint32_t time)
{
__IO uint32_t i;
@@ -158,6 +162,33 @@ static void _soft_delay_(uint32_t time)
*/
void SystemInit (void)
{
/* ===== DIAG: raw USART0 output at IRC8M (8MHz) ===== */
/* Enable AFIO + GPIOA + USART0 clocks via raw register */
*((volatile uint32_t *)0x40021018U) |= (1U << 14) | (1U << 2) | (1U << 0);
/* PA9 = AF_PP, 50MHz (CTL=0b11, MD=0b11) -> CTL1 bit[4..7]=0b1011 */
{
volatile uint32_t ctl = *((volatile uint32_t *)0x40010804U);
ctl &= ~(0xFU << 4);
ctl |= (0xBU << 4); /* AF_PP 50MHz */
*((volatile uint32_t *)0x40010804U) = ctl;
}
/* USART0 baud: BAUD = 8000000/115200 = 69 */
*((volatile uint32_t *)0x40013808U) = 69U;
/* USART0 CTL0: TE + EN = bit3 + bit13 */
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
/* Send "APP" via USART0 DATA register */
*((volatile uint32_t *)0x40013804U) = 'A';
while ((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {}
*((volatile uint32_t *)0x40013804U) = 'P';
while ((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {}
*((volatile uint32_t *)0x40013804U) = 'P';
while ((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {}
*((volatile uint32_t *)0x40013804U) = '\r';
while ((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {}
*((volatile uint32_t *)0x40013804U) = '\n';
while ((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {}
/* ===== END DIAG ===== */
/* FPU settings */
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
@@ -193,6 +224,22 @@ void SystemInit (void)
/* configure the system clock source, PLL Multiplier, AHB/APBx prescalers and Flash settings */
system_clock_config();
/* ===== DIAG: after PLL config, at 180MHz, send "CLK" ===== */
/* Re-enable USART0 at 180MHz: BAUD = 180000000/115200 = 1562.5 = 1563 */
*((volatile uint32_t *)0x40013808U) = 1563U;
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
*((volatile uint32_t *)0x40013804U) = 'C';
while ((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {}
*((volatile uint32_t *)0x40013804U) = 'L';
while ((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {}
*((volatile uint32_t *)0x40013804U) = 'K';
while ((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {}
*((volatile uint32_t *)0x40013804U) = '\r';
while ((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {}
*((volatile uint32_t *)0x40013804U) = '\n';
while ((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {}
/* ===== END DIAG ===== */
#ifdef VECT_TAB_SRAM
nvic_vector_table_set(NVIC_VECTTAB_RAM,VECT_TAB_OFFSET);
#else
@@ -997,7 +1044,23 @@ static void system_clock_180m_hxtal(void)
RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4 | RCU_CFG0_PLLMF_5);
RCU_CFG0 |= RCU_PLL_MUL45;
#elif (defined(GD32E50X_CL) || defined(GD32E508))
#elif defined(GD32E508)
/* GD32E508 + 8MHz HXTAL:
CK_PREDIV0 = (CK_HXTAL)/2 *8 /8 = 4 MHz
CK_PLL = CK_PREDIV0 * 45 = 180 MHz */
RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4 | RCU_CFG0_PLLMF_5);
RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | RCU_PLL_MUL45);
RCU_CFG1 &= ~(RCU_CFG1_PLLPRESEL | RCU_CFG1_PREDV0SEL | RCU_CFG1_PLL1MF | RCU_CFG1_PREDV1 | RCU_CFG1_PREDV0);
RCU_CFG1 |= (RCU_PLLPRESRC_HXTAL | RCU_PREDV0SRC_CKPLL1 | RCU_PLL1_MUL8 | RCU_PREDV1_DIV2 | RCU_PREDV0_DIV8);
/* enable PLL1 */
RCU_CTL |= RCU_CTL_PLL1EN;
/* wait till PLL1 is ready */
while((RCU_CTL & RCU_CTL_PLL1STB) == 0U){
}
#elif defined(GD32E50X_CL)
/* CK_PLL = (CK_PREDIV0) * 45 = 180 MHz */
RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4 | RCU_CFG0_PLLMF_5);
RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | RCU_PLL_MUL45);
+52 -7
View File
@@ -7,6 +7,24 @@
#include "device/adc.h"
#include "sys/systick.h"
#include "sys/config.h"
#include <math.h>
namespace {
constexpr float kMinValidVdda = 2.7f;
constexpr float kMaxValidVdda = 3.6f;
constexpr float kMinValidTemperature = -40.0f;
constexpr float kMaxValidTemperature = 125.0f;
static bool isValidAdcRaw(uint16_t raw)
{
return (raw > 0U) && (raw < static_cast<uint16_t>(CONFIG_ADC_MAX_VALUE));
}
static bool isFiniteFloat(float value)
{
return isfinite(value) != 0;
}
} // namespace
namespace {
constexpr uint32_t kDmaPeriph = DMA0;
@@ -53,6 +71,7 @@ void Adc::initDma()
* @param[out] 无。
* @retval 无返回值。
* @note 按 `VIN_ADC / TEMP / VREFINT` 三通道顺序启用连续扫描与 DMA 采样。
* 参考 GD32E508 文档要求,内部温度与 VREFINT 通道使用长采样时间,以满足 ts_temp 要求。
*/
void Adc::init()
{
@@ -70,11 +89,14 @@ void Adc::init()
adc_special_function_config(CONFIG_ADC_INSTANCE, ADC_CONTINUOUS_MODE, ENABLE);
adc_data_alignment_config(CONFIG_ADC_INSTANCE, ADC_DATAALIGN_RIGHT);
adc_channel_length_config(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL, kChannelCount);
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 0U, CONFIG_ADC_VIN_CHANNEL, CONFIG_ADC_SAMPLE_TIME);
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 1U, CONFIG_ADC_TEMP_CHANNEL, CONFIG_ADC_SAMPLE_TIME);
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 2U, CONFIG_ADC_VREFINT_CHANNEL, CONFIG_ADC_SAMPLE_TIME);
/* 内部温度传感器和 VREFINT 使用长采样时间,确保满足文档要求。 */
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 0U, CONFIG_ADC_VIN_CHANNEL, ADC_SAMPLETIME_239POINT5);
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 1U, CONFIG_ADC_TEMP_CHANNEL, ADC_SAMPLETIME_239POINT5);
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 2U, CONFIG_ADC_VREFINT_CHANNEL, ADC_SAMPLETIME_239POINT5);
adc_external_trigger_source_config(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);
adc_external_trigger_config(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL, DISABLE);
adc_external_trigger_config(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL, ENABLE);
adc_dma_mode_enable(CONFIG_ADC_INSTANCE);
adc_tempsensor_vrefint_enable();
@@ -83,6 +105,7 @@ void Adc::init()
adc_enable(CONFIG_ADC_INSTANCE);
SysTickTimer::delayMs(CONFIG_ADC_STABILIZE_DELAY_MS);
adc_calibration_enable(CONFIG_ADC_INSTANCE);
SysTickTimer::delayMs(CONFIG_ADC_STABILIZE_DELAY_MS);
adc_software_trigger_enable(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL);
SysTickTimer::delayMs(1U);
s_dmaReady = 1U;
@@ -169,8 +192,14 @@ float Adc::calculateVinAdcVoltage(uint16_t vinRaw, float vdda)
*/
float Adc::calculateVdda(uint16_t vrefRaw)
{
if(vrefRaw == 0U) return CONFIG_ADC_DEFAULT_VDDA;
return (CONFIG_ADC_VREFINT_TYPICAL_VOLTAGE * CONFIG_ADC_MAX_VALUE) / static_cast<float>(vrefRaw);
if(!isValidAdcRaw(vrefRaw)) return 0.0f;
const float vdda = (CONFIG_ADC_VREFINT_TYPICAL_VOLTAGE * CONFIG_ADC_MAX_VALUE) /
static_cast<float>(vrefRaw);
if(!isFiniteFloat(vdda) || vdda < kMinValidVdda || vdda > kMaxValidVdda) {
return 0.0f;
}
return vdda;
}
/**
@@ -179,11 +208,22 @@ float Adc::calculateVdda(uint16_t vrefRaw)
* @param[in] vdda 当前 ADC 供电电压。
* @param[out] 无。
* @retval float 返回换算得到的芯片温度,单位 ℃。
* @note 按 GD32E508 文档推荐公式处理:
* Temperature = (V25 - Vtemperature) / Avg_Slope + 25
*/
float Adc::calculateTemperature(uint16_t tempRaw, float vdda)
{
if(!isValidAdcRaw(tempRaw)) return 0.0f;
if(!isFiniteFloat(vdda) || vdda < kMinValidVdda || vdda > kMaxValidVdda) return 0.0f;
const float sensorVoltage = (static_cast<float>(tempRaw) * vdda) / CONFIG_ADC_MAX_VALUE;
return ((CONFIG_ADC_TEMP_V25 - sensorVoltage) / CONFIG_ADC_TEMP_AVG_SLOPE) + 25.0f;
const float temperature = ((CONFIG_ADC_TEMP_V25 - sensorVoltage) / CONFIG_ADC_TEMP_AVG_SLOPE) + 25.0f;
if(!isFiniteFloat(temperature) ||
temperature < kMinValidTemperature ||
temperature > kMaxValidTemperature) {
return 0.0f;
}
return temperature;
}
/**
@@ -203,4 +243,9 @@ void Adc::collectSample(Sample *sample)
sample->vdda = calculateVdda(sample->vref_raw);
sample->vin_adc_v = calculateVinAdcVoltage(sample->vin_raw, sample->vdda);
sample->temperature = calculateTemperature(sample->temp_raw, sample->vdda);
sample->valid = (isValidAdcRaw(sample->temp_raw) &&
isValidAdcRaw(sample->vref_raw) &&
(sample->vdda >= kMinValidVdda) && (sample->vdda <= kMaxValidVdda) &&
(sample->temperature >= kMinValidTemperature) &&
(sample->temperature <= kMaxValidTemperature)) ? 1U : 0U;
}
+82 -31
View File
@@ -6,6 +6,7 @@
#include "main.h"
#include "device/canfd.h"
#include "sys/config.h"
#include "sys/task_manager.h"
namespace {
struct TxFrame {
@@ -40,8 +41,32 @@ constexpr CanChannelConfig kChannels[] = {
};
constexpr size_t kChannelCount = sizeof(kChannels) / sizeof(kChannels[0]);
constexpr uint8_t kRxQueueDepth = 4U;
constexpr uint8_t kTxQueueDepth = 4U;
constexpr uint32_t kCanRxParseTaskMask = (1UL << TASK_CAN_RX_PARSE);
/**
* @brief Enter a critical section and preserve the previous interrupt mask.
* @param[in] 无。
* @param[out] 无。
* @retval uint32_t Previous PRIMASK value.
*/
static uint32_t enterCriticalSection()
{
const uint32_t primask = __get_PRIMASK();
__disable_irq();
return primask;
}
/**
* @brief Exit a critical section by restoring the saved interrupt mask.
* @param[in] primask Previous PRIMASK value returned by `enterCriticalSection()`.
* @param[out] 无。
* @retval 无返回值。
*/
static void exitCriticalSection(uint32_t primask)
{
__set_PRIMASK(primask);
}
} // namespace
CanFd::Diag CanFd::s_diag{};
@@ -72,23 +97,23 @@ void CanFd::initGpio()
}
/**
* @brief 为指定 CAN 控制器配置全接收过滤器。
* @brief 为指定 CAN 控制器配置接收过滤器。
* @param[in] canPeriph 目标 CAN 外设实例。
* @param[in] filterNumber 过滤器编号。
* @param[out] 无。
* @retval 无返回值。
* @note 当前使用 32 位 mask 模式放行全部报文,并统一进入 FIFO0。
* @note 当前默认按 `CONFIG_CAN_RX_FILTER_ID`/`CONFIG_CAN_RX_FILTER_MASK` 对标准帧做掩码匹配,并统一进入 FIFO0。
*/
void CanFd::filterAcceptAll(uint32_t canPeriph, uint8_t filterNumber)
void CanFd::configureRxFilter(uint32_t canPeriph, uint8_t filterNumber)
{
can_filter_parameter_struct can_filter{};
can_filter.filter_number = filterNumber;
can_filter.filter_mode = CAN_FILTERMODE_MASK;
can_filter.filter_bits = CAN_FILTERBITS_32BIT;
can_filter.filter_list_high = 0x0000U;
can_filter.filter_list_high = static_cast<uint16_t>((CONFIG_CAN_RX_FILTER_ID & 0x7FFU) << 5U);
can_filter.filter_list_low = 0x0000U;
can_filter.filter_mask_high = 0x0000U;
can_filter.filter_mask_low = 0x0000U;
can_filter.filter_mask_high = static_cast<uint16_t>((CONFIG_CAN_RX_FILTER_MASK & 0x7FFU) << 5U);
can_filter.filter_mask_low = static_cast<uint16_t>(1U << 2U);
can_filter.filter_fifo_number = CAN_FIFO0;
can_filter.filter_enable = ENABLE;
can_filter_init(canPeriph, &can_filter);
@@ -237,7 +262,7 @@ bool CanFd::enqueueTxFrame(uint8_t channelIndex, const StatusPayload *status)
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 初始化后默认将三路 TCAN1057A 收发器置为 Normal,并清零收发诊断状态。
* @note 初始化后默认将三路 TCAN1057A 收发器置为 Normal,并使能 FIFO0 的非空/满/overfull 中断统计。
*/
void CanFd::init()
{
@@ -253,8 +278,8 @@ void CanFd::init()
can_deinit(ch.canPeriph);
initNominalTiming(ch.canPeriph);
initFdMode(ch.canPeriph);
filterAcceptAll(ch.canPeriph, ch.filterNumber);
can_interrupt_enable(ch.canPeriph, CAN_INT_RFNE0);
configureRxFilter(ch.canPeriph, ch.filterNumber);
can_interrupt_enable(ch.canPeriph, CAN_INT_RFNE0 | CAN_INT_RFF0 | CAN_INT_RFO0);
nvic_irq_enable(ch.rx0Irq, 1U, static_cast<uint8_t>(i));
s_rxQueues[i].head = 0U;
@@ -348,12 +373,16 @@ void CanFd::sendPrepared()
*/
bool CanFd::hasRxFrame()
{
bool found = false;
const uint32_t primask = enterCriticalSection();
for(size_t i = 0; i < kChannelCount; ++i) {
if(s_rxQueues[i].count != 0U) {
return true;
found = true;
break;
}
}
return false;
exitCriticalSection(primask);
return found;
}
/**
@@ -371,7 +400,7 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t
RxFrame localFrame{};
bool found = false;
__disable_irq();
const uint32_t primask = enterCriticalSection();
for(size_t n = 0; n < kChannelCount; ++n) {
const uint8_t idx = static_cast<uint8_t>((s_nextConsumeChannel + n) % kChannelCount);
auto &queue = s_rxQueues[idx];
@@ -386,7 +415,7 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t
found = true;
break;
}
__enable_irq();
exitCriticalSection(primask);
if(!found) {
return false;
@@ -447,31 +476,44 @@ void CanFd::setSilentMode()
* @param[in] channelIndex 目标 CAN 通道索引(0/1/2)。
* @param[out] 无。
* @retval 无返回值。
* @note 从对应控制器 FIFO0 取出一帧报文,更新诊断统计,并写入该通道软件接收队列。
* @note 从对应控制器 FIFO0 循环取空所有待处理报文,更新诊断统计,写入该通道软件接收队列,并置位解析任务。
*/
void CanFd::handleRxInterrupt(uint8_t channelIndex)
{
if(channelIndex >= kChannelCount) return;
const auto &ch = kChannels[channelIndex];
if(can_interrupt_flag_get(ch.canPeriph, CAN_INT_FLAG_RFL0) == RESET) return;
const bool fifoFull = (can_interrupt_flag_get(ch.canPeriph, CAN_INT_FLAG_RFF0) != RESET);
const bool fifoOverfull = (can_interrupt_flag_get(ch.canPeriph, CAN_INT_FLAG_RFO0) != RESET);
if(fifoFull) {
s_diag.rx_fifo_full_count++;
}
if(fifoOverfull) {
s_diag.rx_fifo_overfull_count++;
}
can_receive_message_struct rx_message{};
can_struct_para_init(CAN_RX_MESSAGE_STRUCT, &rx_message);
can_message_receive(ch.canPeriph, CAN_FIFO0, &rx_message);
bool hadTraffic = false;
while(can_interrupt_flag_get(ch.canPeriph, CAN_INT_FLAG_RFL0) != RESET) {
hadTraffic = true;
s_diag.rx_count++;
s_diag.last_rx_channel = channelIndex;
s_diag.last_payload_length = rx_message.rx_dlen;
s_diag.last_rx_fd_flag = rx_message.fd_flag;
s_diag.last_rx_brs = rx_message.fd_brs;
s_diag.last_rx_esi = rx_message.fd_esi;
s_diag.last_rx_id = (rx_message.rx_ff == CAN_FF_STANDARD) ? rx_message.rx_sfid : rx_message.rx_efid;
can_receive_message_struct rx_message{};
can_struct_para_init(CAN_RX_MESSAGE_STRUCT, &rx_message);
can_message_receive(ch.canPeriph, CAN_FIFO0, &rx_message);
s_diag.rx_count++;
s_diag.last_rx_channel = channelIndex;
s_diag.last_payload_length = rx_message.rx_dlen;
s_diag.last_rx_fd_flag = rx_message.fd_flag;
s_diag.last_rx_brs = rx_message.fd_brs;
s_diag.last_rx_esi = rx_message.fd_esi;
s_diag.last_rx_id = (rx_message.rx_ff == CAN_FF_STANDARD) ? rx_message.rx_sfid : rx_message.rx_efid;
auto &queue = s_rxQueues[channelIndex];
if(queue.count >= kRxQueueDepth) {
s_diag.rx_drop_count++;
continue;
}
auto &queue = s_rxQueues[channelIndex];
if(queue.count >= kRxQueueDepth) {
s_diag.rx_drop_count++;
} else {
auto &frame = queue.frames[queue.tail];
frame.channel = channelIndex;
frame.id = s_diag.last_rx_id;
@@ -479,11 +521,20 @@ void CanFd::handleRxInterrupt(uint8_t channelIndex)
for(uint8_t i = 0; i < rx_message.rx_dlen; ++i) {
frame.data[i] = rx_message.rx_data[i];
}
queue.tail = static_cast<uint8_t>((queue.tail + 1U) % kRxQueueDepth);
queue.count++;
}
can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFL0);
if(fifoFull) {
can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFF0);
}
if(fifoOverfull) {
can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFO0);
}
if(hadTraffic) {
TaskManager::set(kCanRxParseTaskMask);
}
}
/**
+1 -1
View File
@@ -83,4 +83,4 @@ void SchedulerTimer::onInterrupt()
* @retval 无返回值。
* @note C 向量包装函数,内部转调 `SchedulerTimer::onInterrupt()`。
*/
void TIMER2_IRQHandler(void) { SchedulerTimer::onInterrupt(); }
extern "C" void TIMER2_IRQHandler(void) { SchedulerTimer::onInterrupt(); }
+1
View File
@@ -20,6 +20,7 @@ public:
float vin_adc_v; /**< VIN_ADC 引脚处电压(分压后的 ADC 端电压),单位 V。 */
float vdda; /**< 根据内部参考电压换算得到的 MCU 供电电压,单位 V。 */
float temperature; /**< 根据温度通道换算得到的芯片温度,单位 ℃。 */
uint8_t valid; /**< 采样是否有效:1=有效,0=存在原始值/换算异常。 */
};
/**
+424
View File
@@ -0,0 +1,424 @@
/**
* @file app_spi_command.cpp
* @brief APP 运行期 SPI 命令从机实现。
*
* 复用 BOOT 的 SpiOta 协议帧格式(SOF+TYPE+LEN+PAYLOAD+CRC),
* APP 只处理进入 Bootloader 命令(TYPE=0xB0),
* 收到 OTA 命令帧(TYPE=0x01/0x02/0x03)时回复 WAIT 帧告知
* ESP32 "当前不在 BOOT 模式"。
*
* 调试日志采用延迟输出:processCommand() 只写 s_logBuf + 置 s_logPending,
* 同时置位 TASK_SPI_LOG 唤醒主循环,poll() 中立刻 printf。
*/
#include "device/app_spi_command.h"
extern "C" {
#include "gd32e50x_exti.h"
}
#include "gd32e50x_gpio.h"
#include "gd32e50x_misc.h"
#include "gd32e50x_rcu.h"
#include "gd32e50x_spi.h"
#include "sys/config.h"
#include "sys/ota_boot_request.h"
#include "sys/task_manager.h"
#include "main.h"
#include <stdio.h>
#include <string.h>
/* ============================================================
* 协议常量(与 BOOT SpiOta 一致)
* ============================================================ */
static constexpr uint8_t kSof = 0xA5U;
static constexpr uint8_t kTypeBootReq = 0xB0U;
static constexpr uint8_t kTypeOtaReq = 0x01U;
static constexpr uint8_t kTypeAppPing = 0xA1U;
static constexpr uint8_t kTypeOtaData = 0x02U;
static constexpr uint8_t kTypeOtaCancel = 0x03U;
static constexpr uint8_t kRspAck = 0x80U;
static constexpr uint8_t kRspWait = 0x81U;
static constexpr uint8_t kRspError = 0x82U;
static constexpr uint16_t kFrameOverhead = 5U;
static constexpr uint16_t kRxBufSize = 128U;
static constexpr uint16_t kTxBufSize = 64U;
static constexpr uint16_t kLogBufSize = 128U;
static bool isAllBytesValue(const uint8_t *data, uint8_t len, uint8_t value)
{
if (!data || len == 0U) {
return false;
}
for (uint8_t i = 0U; i < len; ++i) {
if (data[i] != value) {
return false;
}
}
return true;
}
/* ============================================================
* 静态成员
* ============================================================ */
volatile bool AppSpiCommand::s_csActive = false;
volatile uint8_t AppSpiCommand::s_rxLen = 0U;
volatile uint8_t AppSpiCommand::s_txLen = 0U;
volatile uint8_t AppSpiCommand::s_txPos = 0U;
volatile bool AppSpiCommand::s_logPending = false;
uint8_t AppSpiCommand::s_rxBuf[kRxBufSize] = {};
uint8_t AppSpiCommand::s_txBuf[kTxBufSize] = {};
char AppSpiCommand::s_logBuf[kLogBufSize] = {};
/* ============================================================
* CRC8 / 帧构建 / 帧解析(与 BOOT SpiOta 一致)
* ============================================================ */
static uint8_t spi_crc8(const uint8_t *data, uint16_t len)
{
uint8_t crc = 0x00U;
for (uint16_t i = 0U; i < len; ++i) {
crc ^= data[i];
}
return crc;
}
static bool spi_build_frame(uint8_t type, const uint8_t *payload, uint16_t payload_len,
uint8_t *out, uint16_t *out_len)
{
if (!out || !out_len) { return false; }
uint16_t total = (uint16_t)(kFrameOverhead + payload_len);
if (total > kTxBufSize) { return false; }
out[0] = kSof;
out[1] = type;
out[2] = (uint8_t)(payload_len & 0xFFU);
out[3] = (uint8_t)((payload_len >> 8) & 0xFFU);
if (payload && payload_len > 0U) {
memcpy(&out[4], payload, payload_len);
}
out[4 + payload_len] = spi_crc8(out, (uint16_t)(4U + payload_len));
*out_len = total;
return true;
}
static bool spi_try_parse_frame(const uint8_t *frame, uint16_t frame_len,
uint8_t *type, const uint8_t **payload, uint16_t *payload_len)
{
if (!frame || frame_len < kFrameOverhead) { return false; }
if (frame[0] != kSof) { return false; }
uint16_t len = (uint16_t)(frame[2] | ((uint16_t)frame[3] << 8));
uint16_t expected = (uint16_t)(kFrameOverhead + len);
if (expected != frame_len) { return false; }
if (spi_crc8(frame, (uint16_t)(4U + len)) != frame[4U + len]) { return false; }
if (type) { *type = frame[1]; }
if (payload) { *payload = &frame[4]; }
if (payload_len) { *payload_len = len; }
return true;
}
/* ============================================================
* 延迟日志:写入缓冲(ISR 安全,不调用 printf)
* ============================================================ */
void AppSpiCommand::logDeferred(const char *msg)
{
if (s_logPending) { return; }
snprintf(s_logBuf, kLogBufSize, "[SPI-APP] %s", msg);
s_logPending = true;
}
void AppSpiCommand::logDeferredFmt(const char *fmt, uint8_t val)
{
if (s_logPending) { return; }
snprintf(s_logBuf, kLogBufSize, fmt, val);
s_logPending = true;
}
/* ============================================================
* 硬件配置(与 BOOT 完全一致)
* ============================================================ */
void AppSpiCommand::configureGpio()
{
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_AF);
gpio_pin_remap_config(GPIO_SPI0_REMAP, ENABLE);
gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);
gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_3 | GPIO_PIN_5);
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_15);
}
void AppSpiCommand::configureSpi0()
{
rcu_periph_clock_enable(RCU_SPI0);
spi_i2s_deinit(SPI0);
spi_parameter_struct spi_init_struct;
spi_struct_para_init(&spi_init_struct);
spi_init_struct.device_mode = SPI_SLAVE;
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT;
spi_init_struct.nss = SPI_NSS_SOFT;
spi_init_struct.endian = SPI_ENDIAN_MSB;
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
spi_init_struct.prescale = SPI_PSC_8;
spi_init(SPI0, &spi_init_struct);
spi_nss_internal_low(SPI0);
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_RBNE);
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_ERR);
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
nvic_irq_enable(SPI0_IRQn, 1U, 0U);
spi_enable(SPI0);
}
void AppSpiCommand::configureExti()
{
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_15);
exti_init(EXTI_15, EXTI_INTERRUPT, EXTI_TRIG_BOTH);
exti_interrupt_enable(EXTI_15);
exti_interrupt_flag_clear(EXTI_15);
nvic_irq_enable(EXTI10_15_IRQn, 1U, 1U);
}
/* ============================================================
* 初始化
* ============================================================ */
void AppSpiCommand::init()
{
s_csActive = false;
s_rxLen = 0U;
s_txLen = 0U;
s_txPos = 0U;
s_logPending = false;
memset(s_rxBuf, 0, sizeof(s_rxBuf));
memset(s_txBuf, 0xFF, sizeof(s_txBuf));
memset(s_logBuf, 0, sizeof(s_logBuf));
configureGpio();
configureSpi0();
configureExti();
}
/* ============================================================
* 帧处理:CS 释放后解析帧并准备回复(不调用 printf)
* ============================================================ */
void AppSpiCommand::processCommand()
{
if (s_rxLen == 0U) {
return;
}
/* 尝试按协议帧解析 */
uint8_t frame_type = 0U;
const uint8_t *payload = nullptr;
uint16_t payload_len = 0U;
if (spi_try_parse_frame(s_rxBuf, s_rxLen, &frame_type, &payload, &payload_len)) {
/* ★ 合法帧:按 TYPE 分发 */
uint16_t tx_len = 0U;
switch (frame_type) {
case kTypeBootReq:
/* 请求进入 Bootloader */
logDeferred("BOOT_REQ → 进BOOT");
{
const bool ok = OtaBootRequest::requestEnterBoot();
uint8_t ack = ok ? CONFIG_SPI_APP_OTA_BOOT_ACK_OK
: CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR;
spi_build_frame(kRspAck, &ack, 1U, s_txBuf, &tx_len);
if (ok) {
TaskManager::set(1UL << TASK_HEARTBEAT);
}
}
break;
case kTypeOtaReq:
case kTypeOtaData:
case kTypeOtaCancel:
/* OTA 命令帧 — APP 不处理,回复 WAIT */
logDeferredFmt("[SPI-APP] OTA type=0x%02X → WAIT", frame_type);
{
uint8_t hint = 0x01U;
spi_build_frame(kRspWait, &hint, 1U, s_txBuf, &tx_len);
}
break;
case kTypeAppPing:
/* APP 存活探测 — 回复 ACK 携带状态摘要 */
logDeferred("[SPI-APP] PING → APP_ALIVE");
{
/* payload: [0]=0xA1(魔术) [1]=运行秒数低8位 [2]=运行秒数高8位 [3]=任务位图低8位 */
extern volatile uint32_t g_app_uptime_sec;
uint8_t alive_payload[4] = {
0xA1U,
(uint8_t)(g_app_uptime_sec & 0xFFU),
(uint8_t)((g_app_uptime_sec >> 8) & 0xFFU),
(uint8_t)(TaskManager::state().task_flags & 0xFFU)
};
spi_build_frame(kRspAck, alive_payload, sizeof(alive_payload), s_txBuf, &tx_len);
}
break;
default:
/* 未知帧类型 */
logDeferredFmt("[SPI-APP] 未知 type=0x%02X → ERROR", frame_type);
{
uint8_t err = 0xEEU;
spi_build_frame(kRspError, &err, 1U, s_txBuf, &tx_len);
}
break;
}
s_txLen = (uint8_t)tx_len;
s_txPos = 0U;
} else {
/* ★ 不符合协议帧 — 兼容旧逻辑 */
if (s_rxBuf[0] == CONFIG_SPI_APP_OTA_BOOT_CMD) {
/* 旧式单字节 0xB0 */
logDeferred("旧式0xB0 → 进BOOT");
const bool ok = OtaBootRequest::requestEnterBoot();
uint8_t ack = ok ? CONFIG_SPI_APP_OTA_BOOT_ACK_OK
: CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR;
s_txBuf[0] = CONFIG_SPI_APP_OTA_BOOT_CMD;
s_txBuf[1] = ack;
s_txBuf[2] = 0x4FU;
s_txBuf[3] = 0x54U;
s_txLen = 4U;
s_txPos = 0U;
if (ok) {
TaskManager::set(1UL << TASK_HEARTBEAT);
}
} else {
/* 无法识别 */
logDeferredFmt("[SPI-APP] 非协议 0x%02X → ERROR", s_rxBuf[0]);
uint16_t tx_len = 0U;
uint8_t err = 0xEEU;
spi_build_frame(kRspError, &err, 1U, s_txBuf, &tx_len);
s_txLen = (uint8_t)tx_len;
s_txPos = 0U;
}
}
/* 置位 TASK_SPI_LOG,唤醒主循环立刻打印日志 */
TaskManager::set(1UL << TASK_SPI_LOG);
}
/* ============================================================
* 主循环轮询:打印延迟日志
* ============================================================ */
void AppSpiCommand::poll()
{
char log_copy[kLogBufSize] = {};
bool has_log = false;
__disable_irq();
if (s_logPending) {
memcpy(log_copy, s_logBuf, sizeof(log_copy));
s_logPending = false;
has_log = true;
}
__enable_irq();
if (has_log) {
printf("%s\r\n", log_copy);
}
}
/* ============================================================
* CS 边沿处理
* ============================================================ */
void AppSpiCommand::onCsAssert()
{
s_csActive = true;
s_rxLen = 0U;
s_txPos = 0U;
if (s_txLen > 0U) {
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_TBE);
}
}
void AppSpiCommand::onCsRelease()
{
if (!s_csActive) {
return;
}
s_csActive = false;
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
if (isAllBytesValue(s_rxBuf, s_rxLen, 0xFFU)) {
s_rxLen = 0U;
return;
}
processCommand();
}
/* ============================================================
* SPI0 / EXTI 中断处理
* ============================================================ */
void AppSpiCommand::onInterrupt()
{
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RBNE) != RESET) {
if (!s_csActive && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) {
onCsAssert();
}
uint8_t byte = (uint8_t)(spi_i2s_data_receive(SPI0) & 0xFFU);
if (s_csActive && s_rxLen < sizeof(s_rxBuf)) {
s_rxBuf[s_rxLen++] = byte;
}
}
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_TBE) != RESET) {
if (!s_csActive && gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) {
onCsAssert();
}
uint8_t tx_byte = 0xFFU;
if (s_csActive && s_txPos < s_txLen) {
tx_byte = s_txBuf[s_txPos++];
if (s_txPos >= s_txLen) {
s_txLen = 0U;
s_txPos = 0U;
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
}
} else {
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
}
spi_i2s_data_transmit(SPI0, tx_byte);
}
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_RXORERR) != RESET) {
(void)spi_i2s_data_receive(SPI0);
s_rxLen = 0U;
}
if (spi_i2s_interrupt_flag_get(SPI0, SPI_I2S_INT_FLAG_FERR) != RESET) {
spi_i2s_format_error_clear(SPI0, SPI_FLAG_FERR);
}
}
void AppSpiCommand::onCsExtiInterrupt()
{
if (exti_interrupt_flag_get(EXTI_15) != RESET) {
exti_interrupt_flag_clear(EXTI_15);
if (gpio_input_bit_get(GPIOA, GPIO_PIN_15) == RESET) {
onCsAssert();
} else {
onCsRelease();
}
}
}
extern "C" void SPI0_IRQHandler(void)
{
AppSpiCommand::onInterrupt();
}
extern "C" void EXTI10_15_IRQHandler(void)
{
AppSpiCommand::onCsExtiInterrupt();
}
+55
View File
@@ -0,0 +1,55 @@
#ifndef DEVICE_APP_SPI_COMMAND_H
#define DEVICE_APP_SPI_COMMAND_H
#include <stdint.h>
/**
* @brief APP 运行期 SPI 命令从机模块。
*
* 复用 BOOT 的 SpiOta 协议帧格式(SOF+TYPE+LEN+PAYLOAD+CRC):
* - TYPE=0xB0 → 请求进入 Bootloader,回复 ACK 帧
* - TYPE=0x01/0x02/0x03(OTA 命令)→ 回复 WAIT 帧(不在 BOOT 模式)
* - 旧式单字节 0xB0 命令仍兼容
*
* 调试日志采用延迟输出:processCommand() 只写缓冲+置标志,
* poll() 中再 printf,避免在帧处理路径上耗时。
*/
class AppSpiCommand {
public:
/** @brief 初始化 SPI0 从机命令通道。 */
static void init();
/** @brief 主循环轮询:打印延迟日志。 */
static void poll();
/** @brief SPI0 中断处理入口。 */
static void onInterrupt();
/** @brief CS 边沿中断处理入口。 */
static void onCsExtiInterrupt();
private:
static constexpr uint16_t kRxBufSize = 128U;
static constexpr uint16_t kTxBufSize = 64U;
static constexpr uint16_t kLogBufSize = 128U;
static volatile bool s_csActive;
static volatile uint8_t s_rxLen;
static volatile uint8_t s_txLen;
static volatile uint8_t s_txPos;
static volatile bool s_logPending;
static uint8_t s_rxBuf[kRxBufSize];
static uint8_t s_txBuf[kTxBufSize];
static char s_logBuf[kLogBufSize];
static void configureGpio();
static void configureSpi0();
static void configureExti();
static void onCsAssert();
static void onCsRelease();
static void processCommand();
static void logDeferred(const char *msg);
static void logDeferredFmt(const char *fmt, uint8_t val);
};
#endif /* DEVICE_APP_SPI_COMMAND_H */
+12 -8
View File
@@ -20,7 +20,9 @@ public:
uint32_t tx_count; /**< 成功发送的报文计数。 */
uint32_t rx_count; /**< 成功接收的报文计数。 */
uint32_t error_count; /**< 发送失败或检测到的错误计数。 */
uint32_t rx_drop_count; /**< 接收 FIFO 满导致丢弃的报文计数。 */
uint32_t rx_drop_count; /**< 软件接收队列满导致丢弃的报文计数。 */
uint32_t rx_fifo_full_count; /**< 硬件 FIFO0 进入满状态的次数。 */
uint32_t rx_fifo_overfull_count; /**< 硬件 FIFO0 发生 overfull 的次数。 */
uint32_t tx_drop_count; /**< 发送 FIFO 满导致丢弃的报文计数。 */
uint32_t last_rx_id; /**< 最近一次接收报文的标识符。 */
uint32_t last_tx_id; /**< 最近一次发送报文的标识符。 */
@@ -105,7 +107,7 @@ public:
* @param[out] length 输出报文数据长度的指针;允许为空。
* @param[out] channel 输出报文来源的 CAN 通道号指针;允许为空。
* @retval bool true 表示成功消费一帧报文,false 表示当前 3 路缓存均为空。
* @note 每路通道各自保留最近一帧;消费顺序采用轮转扫描,避免长期偏向固定通道。
* @note 每路通道使用轻量环形缓冲保留多帧;消费顺序采用轮转扫描,避免长期偏向固定通道。
*/
static bool consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel = nullptr);
@@ -153,6 +155,8 @@ public:
static void handleRxInterrupt(uint8_t channelIndex);
private:
static constexpr uint8_t kRxQueueDepth = 16U;
struct RxFrame {
uint8_t channel;
uint32_t id;
@@ -161,16 +165,16 @@ private:
};
struct RxQueue {
RxFrame frames[4];
uint8_t head;
uint8_t tail;
uint8_t count;
RxFrame frames[kRxQueueDepth];
volatile uint8_t head;
volatile uint8_t tail;
volatile uint8_t count;
};
/** @brief 初始化 CAN0/CAN1/CAN2 相关 GPIO。 */
static void initGpio();
/** @brief 为指定控制器配置全接收过滤器。 */
static void filterAcceptAll(uint32_t canPeriph, uint8_t filterNumber);
/** @brief 为指定控制器配置接收过滤器。 */
static void configureRxFilter(uint32_t canPeriph, uint8_t filterNumber);
/** @brief 配置指定控制器的 CAN 仲裁段标称位时序。 */
static void initNominalTiming(uint32_t canPeriph);
/** @brief 配置指定控制器的 CAN FD 数据段位时序与 TDC。 */
+13 -10
View File
@@ -50,12 +50,14 @@ bool Usart0::isBufferEmpty()
* @param[in] ch 待发送字符。
* @param[out] 无。
* @retval bool true 表示成功写入发送缓冲,false 表示缓冲已满而被丢弃。
* @note 缓冲满时仅增加丢弃计数,不阻塞主流程。
* @note 当前统一由 TBE 中断从队列中取字节发送;此处只负责入队并使能发送中断。
*/
bool Usart0::putCharNonBlocking(int ch)
{
uint16_t next = nextIndex(s_txHead);
__disable_irq();
const uint16_t next = nextIndex(s_txHead);
if(next == s_txTail) {
__enable_irq();
s_dropCount++;
return false;
}
@@ -63,15 +65,16 @@ bool Usart0::putCharNonBlocking(int ch)
s_txBuf[s_txHead] = static_cast<uint8_t>(ch);
s_txHead = next;
usart_interrupt_enable(USART0, USART_INT_TBE);
__enable_irq();
return true;
}
/**
* @brief 向 USART0 发送缓冲写入一个字符。
* @brief 将字符写入 USART0 发送环形缓冲。
* @param[in] ch 待发送字符。
* @param[in] f 标准流指针。
* @param[in] f 标准流指针,实际未使用。
* @param[out] 无。
* @retval int 返回写入缓冲的字符值。
* @retval int 返回待发送字符值。
* @note 当前采用非阻塞写入策略;若缓冲已满则丢弃该字符但仍返回原字符值。
*/
int Usart0::putChar(int ch, FILE *f)
@@ -94,8 +97,7 @@ void Usart0::onInterrupt()
if(!isBufferEmpty()) {
usart_data_transmit(USART0, s_txBuf[s_txTail]);
s_txTail = nextIndex(s_txTail);
}
if(isBufferEmpty()) {
} else {
usart_interrupt_disable(USART0, USART_INT_TBE);
}
}
@@ -106,11 +108,12 @@ void Usart0::onInterrupt()
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 将 PA9 配置为复用推挽发送脚,将 PA10 配置为浮空输入接收脚。
* @note 将 PA9 配置为复用推挽发送脚,将 PA10 配置为浮空输入接收脚,并使能 AF 时钟。
*/
void Usart0::configurePins()
{
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_AF);
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
}
@@ -120,7 +123,7 @@ void Usart0::configurePins()
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 使用 `CONFIG_USART0_BAUDRATE` 配置串口参数,并使能 USART0 中断。
* @note 使用 `CONFIG_USART0_BAUDRATE` 配置串口参数,并使能 USART0 TBE 中断用于队列发送。
*/
void Usart0::init()
{
@@ -142,7 +145,7 @@ void Usart0::init()
* @param[in] ch 待发送字符。
* @param[in] f 标准流指针。
* @param[out] 无。
* @retval int 返回写入缓冲的字符值。
* @retval int 返回待发送字符值。
* @note 该 C 包装函数内部转调 `Usart0::putChar()`。
*/
extern "C" int fputc(int ch, FILE *f) { return Usart0::putChar(ch, f); }
+31
View File
@@ -0,0 +1,31 @@
/**
* @file watchdog.cpp
* @brief APP 独立看门狗(FWDGT)实现。
*
* 时钟源: IRC40K (~40 KHz)
* 预分频: CONFIG_FWDGT_PSC, 重载值: CONFIG_FWDGT_RLD
* 超时 = (PSC / 40000) * (RLD + 1) 秒
*/
#include "device/watchdog.h"
#include "sys/config.h"
#include "main.h"
void Watchdog::init()
{
fwdgt_write_enable();
fwdgt_prescaler_value_config(CONFIG_FWDGT_PSC);
fwdgt_reload_value_config(CONFIG_FWDGT_RLD);
fwdgt_write_disable();
fwdgt_enable();
}
void Watchdog::feed()
{
fwdgt_counter_reload();
}
void Watchdog::abandon()
{
/* 不喂狗即可,FWDGT 超时后硬件自动复位。 */
}
+25
View File
@@ -0,0 +1,25 @@
/**
* @file watchdog.h
* @brief APP 独立看门狗模块(FWDGT)。
*
* 超时后系统硬复位,BOOT 检测 FWDGT 复位标志后停留在 OTA 等待模式,
* 而非继续跳入卡死的 APP。
*/
#ifndef DEVICE_WATCHDOG_H
#define DEVICE_WATCHDOG_H
#include <stdint.h>
class Watchdog {
public:
/** @brief 初始化并启动 FWDGT,超时约 CONFIG_FWDGT_TIMEOUT_MS 毫秒。 */
static void init();
/** @brief 喂狗(重载计数器),应在主循环中周期调用。 */
static void feed();
/** @brief 停止喂狗,让看门狗自然超时触发复位跳转 BOOT。 */
static void abandon();
};
#endif /* DEVICE_WATCHDOG_H */
+17 -4
View File
@@ -14,12 +14,17 @@
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 该函数仅负责串口输出启动提示,不修改系统控制状态。
* @note 当前恢复 UART 输出以便观察启动与周期诊断信息。
*/
void App::printBanner() const
{
printf("\r\n%s\r\n", APP_BANNER_TEXT);
printf("SystemCoreClock=%u Hz\r\n", SystemCoreClock);
printf("\r\n");
printf("========================================\r\n");
printf(" GD32E508 DCU APP started OK\r\n");
printf(" System clock: 180 MHz\r\n");
printf(" Build: " __DATE__ " " __TIME__ "\r\n");
printf("========================================\r\n");
printf("[APP] APP running, uptime_sec=0\r\n");
}
/**
@@ -27,19 +32,27 @@ void App::printBanner() const
* @param[in] 无。
* @param[out] 无。
* @retval int 固定返回 0;在嵌入式常驻循环场景下通常不会真正返回。
* @note 完成系统初始化、任务运行时初始化和启动横幅输出后,持续执行任务分发循环。
* @note 当前恢复正常初始化与任务执行路径;空闲等待前短暂关中断,避免任务刚置位后误入睡眠。
*/
int App::run()
{
printf("[APP] >>> main() entered, SystemCoreClock=%lu\r\n", (unsigned long)SystemCoreClock);
printf("[APP] >>> calling AppInitializer::init()...\r\n");
AppInitializer::init();
printf("[APP] >>> init done, calling TaskRunner::init()...\r\n");
TaskRunner::init();
printf("[APP] >>> TaskRunner ready, entering main loop\r\n");
printBanner();
while(1) {
TaskRunner::runPending();
const uint32_t primask = __get_PRIMASK();
__disable_irq();
if(TaskManager::state().task_flags == 0U) {
__DSB();
__WFI();
}
__set_PRIMASK(primask);
}
return 0;
+5 -1
View File
@@ -1,4 +1,4 @@
#ifndef MAIN_H
#ifndef MAIN_H
#define MAIN_H
#include "RTE_Components.h"
@@ -22,6 +22,7 @@ enum system_task_t {
TASK_STATUS_LED, /**< 状态灯维护任务。 */
TASK_HEARTBEAT, /**< 系统心跳维护任务。 */
TASK_DIAGNOSTIC_PRINT, /**< 诊断信息打印任务。 */
TASK_SPI_LOG, /**< SPI 延迟日志打印任务。 */
TASK_COUNT /**< 任务总数,用于边界或统计场景。 */
};
@@ -55,6 +56,9 @@ struct task_scheduler_t {
*/
extern task_scheduler_t g_scheduler;
/** @brief APP 运行秒数计数器,由 TaskManager::tick100us 在每秒节拍递增。 */
extern volatile uint32_t g_app_uptime_sec;
/**
* @brief 应用顶层对象。
* @note 负责组织初始化、横幅输出与主循环执行。
+39 -2
View File
@@ -35,12 +35,37 @@
/** @brief ADC 空输入保护时默认 VDDA,单位 V。 */
#define CONFIG_ADC_DEFAULT_VDDA 3.3f
/** @brief ADC 中值滤波窗口长度(最终样本数为该值 + 1)。 */
#define CONFIG_ADC_FILTER_SIZE 6U
/** @brief CAN 外设实例。 */
#define CONFIG_CAN_INSTANCE CAN0
/** @brief CAN 外设时钟使能枚举。 */
#define CONFIG_CAN_RCU RCU_CAN0
/** @brief CAN 状态报文标准帧 ID。 */
#define CONFIG_CAN_STATUS_ID 0x321U
/** @brief ESP32 请求 DCU_APP 进入 Bootloader OTA 窗口的扩展帧 ID(备用 CAN 入口)。 */
#define CONFIG_CAN_OTA_BOOT_REQ_ID 0x00000320U
/** @brief 进入 Bootloader OTA 请求魔术字节 0。 */
#define CONFIG_CAN_OTA_BOOT_MAGIC0 0x4FU
/** @brief 进入 Bootloader OTA 请求魔术字节 1。 */
#define CONFIG_CAN_OTA_BOOT_MAGIC1 0x54U
/** @brief 进入 Bootloader OTA 请求魔术字节 2。 */
#define CONFIG_CAN_OTA_BOOT_MAGIC2 0x41U
/** @brief 进入 Bootloader OTA 请求魔术字节 3。 */
#define CONFIG_CAN_OTA_BOOT_MAGIC3 0x21U
/** @brief ESP32 在 APP 运行期通过 SPI 发送的进入 Bootloader OTA 命令。 */
#define CONFIG_SPI_APP_OTA_BOOT_CMD 0xB0U
/** @brief SPI 进入 Bootloader OTA 命令确认状态:成功。 */
#define CONFIG_SPI_APP_OTA_BOOT_ACK_OK 0xA5U
/** @brief SPI 进入 Bootloader OTA 命令确认状态:失败。 */
#define CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR 0xE2U
/** @brief CAN 接收过滤器目标标准帧 ID。 */
#define CONFIG_CAN_RX_FILTER_ID CONFIG_CAN_STATUS_ID
/** @brief CAN 接收过滤器标准帧掩码;`0x7FFU` 表示精确匹配 11-bit ID。 */
#define CONFIG_CAN_RX_FILTER_MASK 0x7FFU
/** @brief 单次 CAN 接收解析任务最多消费的软件队列帧数,避免主循环长期被接收流量占用。 */
#define CONFIG_CAN_RX_PARSE_BUDGET 48U
/** @brief CAN 状态载荷中调试标识字符 0。 */
#define CONFIG_CAN_STATUS_TAG0 0x43U
/** @brief CAN 状态载荷中调试标识字符 1。 */
@@ -69,14 +94,26 @@
#define CONFIG_TASK_PERIOD_VREF_SAMPLE_TICKS 5U
/** @brief 调度基准节拍:ADC 温度采样/处理周期,单位 100us tick。 */
#define CONFIG_TASK_PERIOD_TEMP_SAMPLE_TICKS 10U
/** @brief 调度基准节拍:CAN 接收轮询/解析周期,单位 100us tick。 */
/** @brief 调度基准节拍:10ms 计数周期,单位 100us tick。 */
#define CONFIG_TASK_PERIOD_CAN_RX_TICKS 100U
/** @brief 调度基准节拍:CAN 发送/状态灯周期,单位 100us tick。 */
#define CONFIG_TASK_PERIOD_CAN_TX_TICKS 1000U
/** @brief 调度基准节拍:心跳/诊断打印周期,单位 100us tick。 */
#define CONFIG_TASK_PERIOD_HEARTBEAT_TICKS 10000U
/** @brief UART 诊断打印级别:0=仅温度/电压,1=温度/电压+CAN 统计。 */
/** @brief 周期温度/电压 UART 打印开关:0=关闭,1=开启。 */
#define CONFIG_UART_TEMP_PERIODIC_PRINT 1
/** @brief UART 诊断打印级别:0=仅温度/电压,1=温度/电压+CAN 统计。 */
#define CONFIG_UART_DIAG_LEVEL 1
/** @brief FWDGT 独立看门狗预分频值。 */
#define CONFIG_FWDGT_PSC FWDGT_PSC_DIV64
/** @brief FWDGT 独立看门狗重载值。
* 超时 = (64 / 40000) * (RLD + 1) = 1.6ms * (RLD+1)
* RLD=1874 → 1.6ms * 1875 ≈ 3.0s
*/
#define CONFIG_FWDGT_RLD 1874U
/** @brief FWDGT 超时近似值(毫秒),仅供文档参考。 */
#define CONFIG_FWDGT_TIMEOUT_MS 3000U
#endif /* SYS_CONFIG_H */
+16
View File
@@ -10,7 +10,10 @@
#include "device/usart0.h"
#include "device/Timer.h"
#include "device/adc.h"
#include "device/watchdog.h"
#include "device/app_spi_command.h"
#include "sys/systick.h"
#include "sys/ota_boot_request.h"
#include "sys/task_manager.h"
/**
@@ -22,11 +25,24 @@
*/
void AppInitializer::init()
{
printf("[APP] init: SysTick...\r\n");
SysTickTimer::initMs();
printf("[APP] init: USART0...\r\n");
Usart0::configurePins();
Usart0::init();
printf("[APP] init: TempSensor...\r\n");
TempSensor::init();
printf("[APP] init: CANFD...\r\n");
CanFd::init();
printf("[APP] init: OtaBootRequest...\r\n");
OtaBootRequest::init();
printf("[APP] init: AppSpiCommand...\r\n");
AppSpiCommand::init();
printf("[APP] init: TaskManager...\r\n");
TaskManager::init();
printf("[APP] init: SchedulerTimer...\r\n");
SchedulerTimer::init();
printf("[APP] init: Watchdog...\r\n");
Watchdog::init();
printf("[APP] init: all done\r\n");
}
+159
View File
@@ -0,0 +1,159 @@
/**
* @file ota_boot_request.cpp
* @brief DCU_APP 进入 Bootloader OTA 窗口控制实现。
*/
#include "sys/ota_boot_request.h"
#include "gd32e50x.h"
#include "sys/config.h"
#include "sys/systick.h"
namespace {
/** Bootloader OTA 配置页地址,与 BOOT 工程 sys/config.h 保持一致。 */
constexpr uint32_t kOtaConfigPageAddr = 0x0807E000U;
constexpr uint32_t kAppLatestAvalAddr = kOtaConfigPageAddr + 0x00U;
constexpr uint32_t kAppLatestEnAddr = kOtaConfigPageAddr + 0x08U;
constexpr uint32_t kAppLatestCheckAddr = kOtaConfigPageAddr + 0x10U;
constexpr uint32_t kOtaAval = 0x00000000U;
constexpr uint32_t kOtaNotAval = 0xFFFFFFFFU;
constexpr uint32_t kFmcPageSize = 0x00000800U;
constexpr uint32_t kRebootDelayMs = 20U;
static bool writeWord(uint32_t addr, uint32_t value)
{
fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum status = fmc_word_program(addr, value);
fmc_lock();
return status == FMC_READY;
}
/**
* @brief 按 Bootloader OTA_SET 语义更新配置页中单个 word。
* @note Flash 只能 1->0;需要写回 0xFFFFFFFF 时必须擦整页再恢复其他字段。
*/
static bool otaConfigSet(uint32_t targetAddr, uint32_t value)
{
if(targetAddr < kOtaConfigPageAddr || targetAddr >= (kOtaConfigPageAddr + kFmcPageSize)) {
return false;
}
const uint32_t current = *((volatile uint32_t *)targetAddr);
if(current == value) {
return true;
}
if(value != kOtaNotAval) {
return writeWord(targetAddr, value);
}
uint32_t words[6];
for(uint32_t i = 0U; i < 6U; ++i) {
words[i] = *((volatile uint32_t *)(kOtaConfigPageAddr + i * 4U));
}
words[(targetAddr - kOtaConfigPageAddr) / 4U] = value;
fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum status = fmc_page_erase(kOtaConfigPageAddr);
if(status != FMC_READY) {
fmc_lock();
return false;
}
bool ok = true;
for(uint32_t i = 0U; i < 6U; ++i) {
if(words[i] == kOtaNotAval) {
continue;
}
status = fmc_word_program(kOtaConfigPageAddr + i * 4U, words[i]);
if(status != FMC_READY) {
ok = false;
break;
}
}
fmc_lock();
return ok;
}
static bool requestMagicValid(const uint8_t *data, uint8_t length)
{
return data != nullptr && length >= 4U &&
data[0] == CONFIG_CAN_OTA_BOOT_MAGIC0 &&
data[1] == CONFIG_CAN_OTA_BOOT_MAGIC1 &&
data[2] == CONFIG_CAN_OTA_BOOT_MAGIC2 &&
data[3] == CONFIG_CAN_OTA_BOOT_MAGIC3;
}
} // namespace
/** @brief 已写入 OTA 标志并等待软复位的挂起标志。 */
volatile bool OtaBootRequest::s_rebootPending = false;
/**
* @brief 初始化进入 Bootloader 请求模块的运行态。
*/
void OtaBootRequest::init()
{
s_rebootPending = false;
}
/**
* @brief 写入 Bootloader OTA 等待标志,并安排稍后软复位。
* @return true 表示标志写入成功;false 表示 Flash 配置页写入失败。
*/
bool OtaBootRequest::requestEnterBoot()
{
bool ok = true;
ok = otaConfigSet(kAppLatestCheckAddr, kOtaAval) && ok;
ok = otaConfigSet(kAppLatestAvalAddr, kOtaAval) && ok;
if(ok) {
ok = otaConfigSet(kAppLatestEnAddr, kOtaNotAval);
}
if(ok) {
s_rebootPending = true;
}
return ok;
}
/**
* @brief 处理备用 CAN 入口的进入 Bootloader 请求。
* @note 当前主路径是 SPI 命令;CAN 入口只负责识别魔术字并触发同一套标志写入逻辑。
*/
bool OtaBootRequest::handleCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t length)
{
if(id != CONFIG_CAN_OTA_BOOT_REQ_ID) {
return false;
}
if(!requestMagicValid(data, length)) {
return true;
}
(void)channel;
const bool ok = requestEnterBoot();
(void)ok;
return true;
}
/**
* @brief 在主循环任务中执行短延迟软复位。
* @note 不在 SPI/CAN 中断里直接复位,避免应答还没发出就重启。
* 这里使用毫秒级短延时,而不是 1s 心跳计数;否则一次 SPI 请求
* 实际会拖到 2~3 秒后才复位,ESP32 第二次 OTA 时很容易在 APP 还没
* 真正重启进入 BOOT 之前就发出 HANDSHAKE_REQ。
*/
void OtaBootRequest::process()
{
if(!s_rebootPending) {
return;
}
s_rebootPending = false;
SysTickTimer::delayMs(kRebootDelayMs);
__disable_irq();
NVIC_SystemReset();
while(1) {
}
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef SYS_OTA_BOOT_REQUEST_H
#define SYS_OTA_BOOT_REQUEST_H
#include <stdint.h>
/**
* @brief DCU_APP 进入 Bootloader OTA 窗口控制模块。
* @note ESP32 通过 CAN-FD 发送受保护命令,APP 写 OTA 标志后软复位,
* Bootloader 下次启动会停留在 OTA 等待窗口。
*/
class OtaBootRequest {
public:
/** @brief 初始化内部状态。 */
static void init();
/**
* @brief 尝试处理一帧 CAN 接收报文。
* @param[in] channel 报文来源 CAN 通道。
* @param[in] id 报文 ID。
* @param[in] data 报文负载。
* @param[in] length 报文负载长度。
* @retval true 该报文已被 OTA/BOOT 控制模块消费。
* @retval false 该报文不是 OTA/BOOT 控制报文。
*/
static bool handleCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t length);
/**
* @brief 设置 Bootloader OTA 等待标志并安排软复位。
* @retval true 标志写入成功,稍后会复位进入 Bootloader。
* @retval false Flash 标志写入失败。
*/
static bool requestEnterBoot();
/** @brief 若已确认进入 BOOT 请求,则延迟片刻后复位。 */
static void process();
private:
static volatile bool s_rebootPending;
};
#endif /* SYS_OTA_BOOT_REQUEST_H */
+1 -1
View File
@@ -54,4 +54,4 @@ void SysTickTimer::initMs()
* @retval 无返回值。
* @note C 向量包装函数,内部转调 `SysTickTimer::onInterrupt()`。
*/
void SysTick_Handler(void) { SysTickTimer::onInterrupt(); }
extern "C" void SysTick_Handler(void) { SysTickTimer::onInterrupt(); }
+39 -5
View File
@@ -1,4 +1,4 @@
/**
/**
* @file task_manager.cpp
* @brief 任务调度管理器实现。
*/
@@ -7,8 +7,36 @@
#include "sys/task_manager.h"
#include "sys/config.h"
namespace {
/**
* @brief Enter a critical section and preserve the previous interrupt mask.
* @param[in] 无。
* @param[out] 无。
* @retval uint32_t Previous PRIMASK value.
*/
static uint32_t enterCriticalSection()
{
const uint32_t primask = __get_PRIMASK();
__disable_irq();
return primask;
}
/**
* @brief Exit a critical section by restoring the saved interrupt mask.
* @param[in] primask Previous PRIMASK value returned by `enterCriticalSection()`.
* @param[out] 无。
* @retval 无返回值。
*/
static void exitCriticalSection(uint32_t primask)
{
__set_PRIMASK(primask);
}
} // namespace
task_scheduler_t g_scheduler = {0};
volatile uint32_t g_app_uptime_sec = 0U;
/**
* @brief 初始化任务调度器内部状态。
* @param[in] 无。
@@ -40,7 +68,9 @@ void TaskManager::init()
*/
void TaskManager::set(uint32_t taskMask)
{
const uint32_t primask = enterCriticalSection();
g_scheduler.task_flags |= taskMask;
exitCriticalSection(primask);
}
/**
@@ -53,10 +83,10 @@ void TaskManager::set(uint32_t taskMask)
uint32_t TaskManager::takePending()
{
uint32_t pending;
__disable_irq();
const uint32_t primask = enterCriticalSection();
pending = g_scheduler.task_flags;
g_scheduler.task_flags &= ~pending;
__enable_irq();
exitCriticalSection(primask);
return pending;
}
@@ -65,7 +95,7 @@ uint32_t TaskManager::takePending()
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 按预设周期置位 ADC、CAN 接收、状态灯、心跳与诊断打印等任务。
* @note 按预设周期推进节拍并置位 ADC、状态灯、心跳与诊断打印等任务;CAN 接收改由 RX 中断事件驱动。
*/
void TaskManager::tick100us()
{
@@ -85,7 +115,6 @@ void TaskManager::tick100us()
if(++g_scheduler.div_can_rx >= CONFIG_TASK_PERIOD_CAN_RX_TICKS) {
g_scheduler.div_can_rx = 0U;
g_scheduler.tick_10ms++;
set((1UL << TASK_CAN_RX_POLL) | (1UL << TASK_CAN_RX_PARSE));
}
if(++g_scheduler.div_can_tx >= CONFIG_TASK_PERIOD_CAN_TX_TICKS) {
@@ -97,7 +126,12 @@ void TaskManager::tick100us()
if(++g_scheduler.div_heartbeat >= CONFIG_TASK_PERIOD_HEARTBEAT_TICKS) {
g_scheduler.div_heartbeat = 0U;
g_scheduler.tick_1s++;
g_app_uptime_sec++;
#if (CONFIG_UART_TEMP_PERIODIC_PRINT != 0)
set((1UL << TASK_HEARTBEAT) | (1UL << TASK_DIAGNOSTIC_PRINT));
#else
set(1UL << TASK_HEARTBEAT);
#endif
}
}
+1
View File
@@ -42,6 +42,7 @@ public:
* @param[in] taskMask 待置位的任务位图掩码。
* @param[out] 无。
* @retval 无返回值。
* @note 内部通过保持当前 PRIMASK 状态的临界区保证可由主循环与 ISR 共享调用。
*/
static void set(uint32_t taskMask);
+143 -30
View File
@@ -5,14 +5,66 @@
#include "main.h"
#include "device/adc.h"
#include "device/watchdog.h"
#include "device/app_spi_command.h"
#include "device/canfd.h"
#include "device/temp_sensor.h"
#include "sys/config.h"
#include "sys/ota_boot_request.h"
#include "sys/task_manager.h"
#include "sys/task_runner.h"
namespace {
constexpr uint32_t kAdcFilterWindow = CONFIG_ADC_FILTER_SIZE + 1U;
/**
* @brief Push one sample into a fixed-size sliding window buffer.
* @param[in,out] buffer Sample buffer with `kAdcFilterWindow` elements.
* @param[in] value New sample value.
*/
static void pushSample(float *buffer, float value)
{
for(uint32_t i = 0U; i < CONFIG_ADC_FILTER_SIZE; ++i) {
buffer[i] = buffer[i + 1U];
}
buffer[CONFIG_ADC_FILTER_SIZE] = value;
}
/**
* @brief Apply median filtering to a fixed-size sample window.
* @param[in] buffer Sample buffer with `kAdcFilterWindow` elements.
* @return Median value in the current window.
*/
static float medianFilter(const float *buffer)
{
float sorted[kAdcFilterWindow];
for(uint32_t i = 0U; i < kAdcFilterWindow; ++i) {
sorted[i] = buffer[i];
}
for(uint32_t i = 1U; i < kAdcFilterWindow; ++i) {
const float key = sorted[i];
uint32_t j = i;
while((j > 0U) && (sorted[j - 1U] > key)) {
sorted[j] = sorted[j - 1U];
--j;
}
sorted[j] = key;
}
return sorted[kAdcFilterWindow / 2U];
}
} // namespace
struct TaskRunner::Runtime {
temp_sensor_data_t sample{};
float temperature_samples[kAdcFilterWindow] = {0.0f};
float vdda_samples[kAdcFilterWindow] = {0.0f};
float vin_adc_samples[kAdcFilterWindow] = {0.0f};
uint16_t last_vin_raw = 0U;
uint16_t last_vref_raw = 0U;
uint16_t last_temp_raw = 0U;
uint8_t last_rx_channel = 0U;
uint32_t last_rx_id = 0U;
uint8_t last_rx_len = 0U;
@@ -30,7 +82,8 @@ TaskRunner::Runtime TaskRunner::s_runtime;
*/
void TaskRunner::taskAdcVrefSample()
{
Adc::collectSample(&s_runtime.sample);
s_runtime.last_vin_raw = Adc::readVinRaw();
s_runtime.last_vref_raw = Adc::readVrefRaw();
}
/**
@@ -42,7 +95,7 @@ void TaskRunner::taskAdcVrefSample()
*/
void TaskRunner::taskAdcTempSample()
{
Adc::collectSample(&s_runtime.sample);
s_runtime.last_temp_raw = Adc::readTempRaw();
}
/**
@@ -54,9 +107,30 @@ void TaskRunner::taskAdcTempSample()
*/
void TaskRunner::taskAdcProcess()
{
/* 当前采样任务已直接调用 Adc::collectSample() 填充工程值。
* 该处理任务保留为调度兼容入口,后续若重新拆分采样/换算阶段,可在此恢复。 */
(void)s_runtime.sample;
auto &s = s_runtime.sample;
const float vdda = Adc::calculateVdda(s_runtime.last_vref_raw);
const float vin_adc_v = Adc::calculateVinAdcVoltage(s_runtime.last_vin_raw, vdda);
const float temperature = Adc::calculateTemperature(s_runtime.last_temp_raw, vdda);
const bool sampleValid = (s_runtime.last_vref_raw > 0U) &&
(s_runtime.last_vref_raw < 4095U) &&
(s_runtime.last_temp_raw > 0U) &&
(s_runtime.last_temp_raw < 4095U) &&
(vdda >= 2.7f) && (vdda <= 3.6f) &&
(temperature >= -40.0f) && (temperature <= 125.0f);
if(sampleValid) {
pushSample(s_runtime.vdda_samples, vdda);
pushSample(s_runtime.vin_adc_samples, vin_adc_v);
pushSample(s_runtime.temperature_samples, temperature);
}
s.vin_raw = s_runtime.last_vin_raw;
s.vref_raw = s_runtime.last_vref_raw;
s.temp_raw = s_runtime.last_temp_raw;
s.vdda = medianFilter(s_runtime.vdda_samples);
s.vin_adc_v = medianFilter(s_runtime.vin_adc_samples);
s.temperature = medianFilter(s_runtime.temperature_samples);
s.valid = sampleValid ? 1U : 0U;
}
/**
@@ -77,21 +151,36 @@ void TaskRunner::taskCanRxPoll() { CanFd::poll(); }
*/
void TaskRunner::taskCanTxSend()
{
/* 当前阶段不做周期性发送调度。
* 若后续需要显式验证某一路发送能力,可由调用方先 prepare,再直接调用 CanFd::sendPrepared()。 */
/* 当前阶段不做周期性发送调度。 */
}
/**
* @brief 解析并消费一帧待处理的 CAN 接收报文。
* @brief 批量解析并消费待处理的 CAN 接收报文。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 若当前存在待消费报文,则更新最近一次接收的 ID、数据长度、数据内容与来源通道号。
* @note 一次任务按预算尽量清空软件接收队列;若仍有积压,则重新置位解析任务。
*/
void TaskRunner::taskCanRxParse()
{
if(CanFd::hasRxFrame())
CanFd::consumeRxFrame(&s_runtime.last_rx_id, s_runtime.last_rx_data, &s_runtime.last_rx_len, &s_runtime.last_rx_channel);
uint32_t consumed = 0U;
while(consumed < CONFIG_CAN_RX_PARSE_BUDGET) {
if(!CanFd::consumeRxFrame(&s_runtime.last_rx_id, s_runtime.last_rx_data, &s_runtime.last_rx_len, &s_runtime.last_rx_channel)) {
break;
}
if(OtaBootRequest::handleCanFrame(s_runtime.last_rx_channel,
s_runtime.last_rx_id,
s_runtime.last_rx_data,
s_runtime.last_rx_len)) {
OtaBootRequest::process();
return;
}
consumed++;
}
if(CanFd::hasRxFrame()) {
TaskManager::set(1UL << TASK_CAN_RX_PARSE);
}
}
/**
@@ -99,16 +188,11 @@ void TaskRunner::taskCanRxParse()
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 当前阶段三路 CANFD 仅要求底层收发能力可用,不做周期性业务状态广播;后续若需要恢复按通道发送,可在此处显式调用 `CanFd::prepareStatusForChannel()` 或 `CanFd::prepareStatus()`。
* @note 当前阶段三路 CANFD 仅要求底层收发能力可用,不做周期性业务状态广播。
*/
void TaskRunner::taskCanTxPrepare()
{
/* 当前阶段三路 CANFD 只要求底层收发能力可用,不做周期性业务状态广播。
* 保留该任务入口,后续若需要恢复按通道发送,可在此处显式调用:
* CanFd::prepareStatusForChannel(channel, &status);
* 或:
* CanFd::prepareStatus(&status);
*/
/* 当前阶段不做周期性业务状态广播。 */
}
/**
@@ -116,7 +200,7 @@ void TaskRunner::taskCanTxPrepare()
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 当前仅保留任务入口,具体状态灯策略尚未实现。
* @note 当前仅保留任务入口。
*/
void TaskRunner::taskStatusLed() { /* 预留 */ }
@@ -125,32 +209,47 @@ void TaskRunner::taskStatusLed() { /* 预留 */ }
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 当前仅保留任务入口,后续可在此加入周期健康检查或轻量保活逻辑。
* @note 当前仅保留任务入口。
*/
void TaskRunner::taskHeartbeat() { /* 预留 */ }
void TaskRunner::taskHeartbeat() { OtaBootRequest::process(); }
/**
* @brief 输出当前温度、电压与 CAN 诊断信息。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 由 1 秒节拍触发,通过 UART 打印当前温度、电压,以及最近一次 CAN 接收与统计信息。
* @note 由 1 秒节拍触发,通过 UART 打印当前温度、电压;采用定点整数格式,避免依赖浮点 `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_DIAG_LEVEL == 0)
printf("temp=%.2f C, vdda=%.3f V, vin_adc=%.3f V\r\n",
s.temperature,
s.vdda,
s.vin_adc_v);
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("temp=%.2f C, vdda=%.3f V, vin_adc=%.3f V, rx_ch=%u, rx_id=0x%08lX, rx_len=%u, tx_ch=%u, can_tx=%lu, can_rx=%lu, can_drop=%lu, tx_drop=%lu, can_err=%lu\r\n",
s.temperature,
s.vdda,
s.vin_adc_v,
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_drop=%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),
@@ -158,6 +257,8 @@ void TaskRunner::taskDiagnosticPrint()
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_drop_count),
static_cast<unsigned long>(diag->error_count));
#endif
@@ -173,6 +274,15 @@ void TaskRunner::taskDiagnosticPrint()
void TaskRunner::init()
{
Adc::collectSample(&s_runtime.sample);
s_runtime.last_vin_raw = s_runtime.sample.vin_raw;
s_runtime.last_vref_raw = s_runtime.sample.vref_raw;
s_runtime.last_temp_raw = s_runtime.sample.temp_raw;
for(uint32_t i = 0U; i < kAdcFilterWindow; ++i) {
s_runtime.temperature_samples[i] = s_runtime.sample.temperature;
s_runtime.vdda_samples[i] = s_runtime.sample.vdda;
s_runtime.vin_adc_samples[i] = s_runtime.sample.vin_adc_v;
}
}
/**
@@ -184,6 +294,8 @@ void TaskRunner::init()
*/
void TaskRunner::runPending()
{
Watchdog::feed();
const uint32_t pending = TaskManager::takePending();
if(pending == 0U) return;
@@ -195,4 +307,5 @@ void TaskRunner::runPending()
if(pending & (1UL << TASK_STATUS_LED)) taskStatusLed();
if(pending & (1UL << TASK_HEARTBEAT)) taskHeartbeat();
if(pending & (1UL << TASK_DIAGNOSTIC_PRINT)) taskDiagnosticPrint();
if(pending & (1UL << TASK_SPI_LOG)) AppSpiCommand::poll();
}
+8 -7
View File
@@ -153,7 +153,8 @@
**当前任务节拍划分:**
- 500us:采样 VREF 原始值
- 1ms:采样温度原始值 + 计算工程值
- 10ms:CAN 接收轮询 + 接收解析
- CAN RX 中断:置位接收解析任务,主循环按预算尽快消费
- 10ms:保留系统 10ms 节拍计数
- 100ms:CAN 发送准备 + CAN 发送 + 状态灯任务
- 1s:心跳任务 + 诊断打印任务
@@ -323,20 +324,20 @@
### `device/CANFD.cpp`
**作用:**
- 初始化 CANFD 工作模式
- 轮询接收 FIFO
- 缓存接收帧
- RX0 中断批量搬空硬件 FIFO0
- 按通道缓存接收帧
- 构建状态发送帧
- 发送准备好的 FD 帧
- 维护 CANFD 诊断统计信息
**内部拆分特点:**
- RX 和 TX 已拆成两段式
- 接收侧:`poll -> cache -> consume`
- 接收侧:`rx interrupt -> queue -> consume`
- 发送侧:`prepare -> send`
**当前内部缓存:**
- `g_rx_frame`:单帧接收缓存
- `g_tx_buffer`:待发送帧缓存
- `s_rxQueues`:三路接收环形缓冲
- `s_txQueues`:三路待发送环形缓冲
- `g_can_diag`:收发与错误统计
这是当前最重要的通信模块之一。
@@ -497,11 +498,11 @@ main.cpp
- `main` 已瘦身
- 任务管理与任务执行已分离
- CANFD 收发流程已拆成两段式
- CANFD 接收已切换为中断事件驱动和环形缓冲
- ADC / 温度 / 定时器 / 串口 已模块化
### 当前仍可继续演进的方向
- 将 `task_runner.cpp` 再拆成 `task_adc.cpp / task_can.cpp / task_diag.cpp`
- 将 CANFD 单帧缓存升级为轻量环形缓冲
- 将协议帧定义抽到单独文件中
- 完善状态灯任务与心跳任务