From f63114f5fde9f8068a5710dff3d58974644f4300 Mon Sep 17 00:00:00 2001 From: cbb <2625274797@qq.com> Date: Sat, 30 May 2026 00:24:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(canfd):=20=E5=88=87=E6=8D=A2=20CANFD=20?= =?UTF-8?q?=E6=8E=A5=E6=94=B6=E4=B8=BA=E4=B8=AD=E6=96=AD=E9=A9=B1=E5=8A=A8?= =?UTF-8?q?=E5=92=8C=E7=8E=AF=E5=BD=A2=E7=BC=93=E5=86=B2=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 CANFD 接收机制由轮询改为 FIFO0 的中断事件驱动处理 - 实现循环取空硬件 FIFO0,提高接收效率和响应速度 - 引入环形缓冲区缓存接收帧,支持多路通道多帧队列管理 - 置位解析任务标志位,实现接收数据的异步解析 - 增加接收中断统计,包括 FIFO 满和溢出计数 - 重构临界区代码,使用自定义函数保护中断状态 - 修改接收过滤器配置,支持按掩码匹配标准帧进入 FIFO0 - 使能 CAN FIFO0 多种中断标志,包括非空、满和溢出中断 --- DCU_APP.csolution.yml | 4 + .../GD32E508_DCU_APP.cproject.yml | 6 + .../Device/GD32E508VE/regions_GD32E508VE.h | 4 +- .../RTE/Device/GD32E508VE/system_gd32e50x.c | 28 +- GD32E508_DCU_APP/device/ADC.cpp | 61 ++- GD32E508_DCU_APP/device/CANFD.cpp | 113 +++-- GD32E508_DCU_APP/device/Timer.cpp | 2 +- GD32E508_DCU_APP/device/adc.h | 1 + GD32E508_DCU_APP/device/app_spi_command.cpp | 397 ++++++++++++++++++ GD32E508_DCU_APP/device/app_spi_command.h | 55 +++ GD32E508_DCU_APP/device/canfd.h | 22 +- GD32E508_DCU_APP/device/usart0.cpp | 23 +- GD32E508_DCU_APP/device/watchdog.cpp | 31 ++ GD32E508_DCU_APP/device/watchdog.h | 25 ++ GD32E508_DCU_APP/main.cpp | 19 +- GD32E508_DCU_APP/main.h | 6 +- GD32E508_DCU_APP/sys/config.h | 41 +- GD32E508_DCU_APP/sys/initial.cpp | 8 +- GD32E508_DCU_APP/sys/ota_boot_request.cpp | 163 +++++++ GD32E508_DCU_APP/sys/ota_boot_request.h | 42 ++ GD32E508_DCU_APP/sys/systick.cpp | 2 +- GD32E508_DCU_APP/sys/task_manager.cpp | 46 +- GD32E508_DCU_APP/sys/task_manager.h | 3 +- GD32E508_DCU_APP/sys/task_runner.cpp | 175 ++++++-- GD32E508_DCU_APP/sys/task_runner.h | 2 +- README.md | 15 +- 26 files changed, 1173 insertions(+), 121 deletions(-) create mode 100644 GD32E508_DCU_APP/device/app_spi_command.cpp create mode 100644 GD32E508_DCU_APP/device/app_spi_command.h create mode 100644 GD32E508_DCU_APP/device/watchdog.cpp create mode 100644 GD32E508_DCU_APP/device/watchdog.h create mode 100644 GD32E508_DCU_APP/sys/ota_boot_request.cpp create mode 100644 GD32E508_DCU_APP/sys/ota_boot_request.h diff --git a/DCU_APP.csolution.yml b/DCU_APP.csolution.yml index e136bbe..7af6f0a 100644 --- a/DCU_APP.csolution.yml +++ b/DCU_APP.csolution.yml @@ -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: diff --git a/GD32E508_DCU_APP/GD32E508_DCU_APP.cproject.yml b/GD32E508_DCU_APP/GD32E508_DCU_APP.cproject.yml index 8b07390..f655c49 100644 --- a/GD32E508_DCU_APP/GD32E508_DCU_APP.cproject.yml +++ b/GD32E508_DCU_APP/GD32E508_DCU_APP.cproject.yml @@ -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,6 +27,8 @@ 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 @@ -63,6 +67,7 @@ project: C-CPP: - -DGD32E508 - -DGD32E50X + - -DHXTAL_VALUE=8000000U - -gdwarf-5 - -D__MICROLIB - -fshort-enums @@ -75,6 +80,7 @@ project: ASM: - -DGD32E508 - -DGD32E50X + - -DHXTAL_VALUE=8000000U - -gdwarf-5 - -D__MICROLIB Link: diff --git a/GD32E508_DCU_APP/RTE/Device/GD32E508VE/regions_GD32E508VE.h b/GD32E508_DCU_APP/RTE/Device/GD32E508VE/regions_GD32E508VE.h index 68d0696..2bf1be6 100644 --- a/GD32E508_DCU_APP/RTE/Device/GD32E508VE/regions_GD32E508VE.h +++ b/GD32E508_DCU_APP/RTE/Device/GD32E508VE/regions_GD32E508VE.h @@ -14,10 +14,10 @@ // Base address <0x0-0xFFFFFFFF:8> // Defines base address of memory region. Default: 0x08000000 // Contains Startup and Vector Table -#define __ROM0_BASE 0x08000000 +#define __ROM0_BASE 0x08048000 // Region size [bytes] <0x0-0xFFFFFFFF:8> // Defines size of memory region. Default: 0x00080000 -#define __ROM0_SIZE 0x00080000 +#define __ROM0_SIZE 0x00036000 // // __ROM1 (unused) diff --git a/GD32E508_DCU_APP/RTE/Device/GD32E508VE/system_gd32e50x.c b/GD32E508_DCU_APP/RTE/Device/GD32E508VE/system_gd32e50x.c index 62360fe..07bc968 100644 --- a/GD32E508_DCU_APP/RTE/Device/GD32E508VE/system_gd32e50x.c +++ b/GD32E508_DCU_APP/RTE/Device/GD32E508VE/system_gd32e50x.c @@ -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; @@ -997,7 +1001,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); diff --git a/GD32E508_DCU_APP/device/ADC.cpp b/GD32E508_DCU_APP/device/ADC.cpp index 15f74b4..f86f39c 100644 --- a/GD32E508_DCU_APP/device/ADC.cpp +++ b/GD32E508_DCU_APP/device/ADC.cpp @@ -7,6 +7,24 @@ #include "device/adc.h" #include "sys/systick.h" #include "sys/config.h" +#include + +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(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(vrefRaw); + if(!isValidAdcRaw(vrefRaw)) return 0.0f; + + const float vdda = (CONFIG_ADC_VREFINT_TYPICAL_VOLTAGE * CONFIG_ADC_MAX_VALUE) / + static_cast(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(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); -} \ No newline at end of file + sample->valid = (isValidAdcRaw(sample->temp_raw) && + isValidAdcRaw(sample->vref_raw) && + (sample->vdda >= kMinValidVdda) && (sample->vdda <= kMaxValidVdda) && + (sample->temperature >= kMinValidTemperature) && + (sample->temperature <= kMaxValidTemperature)) ? 1U : 0U; +} diff --git a/GD32E508_DCU_APP/device/CANFD.cpp b/GD32E508_DCU_APP/device/CANFD.cpp index 0a35cf4..9f6863f 100644 --- a/GD32E508_DCU_APP/device/CANFD.cpp +++ b/GD32E508_DCU_APP/device/CANFD.cpp @@ -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((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((CONFIG_CAN_RX_FILTER_MASK & 0x7FFU) << 5U); + can_filter.filter_mask_low = static_cast(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(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((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((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); + } } /** diff --git a/GD32E508_DCU_APP/device/Timer.cpp b/GD32E508_DCU_APP/device/Timer.cpp index cc90e03..c33c43d 100644 --- a/GD32E508_DCU_APP/device/Timer.cpp +++ b/GD32E508_DCU_APP/device/Timer.cpp @@ -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(); } diff --git a/GD32E508_DCU_APP/device/adc.h b/GD32E508_DCU_APP/device/adc.h index b29800b..59a888a 100644 --- a/GD32E508_DCU_APP/device/adc.h +++ b/GD32E508_DCU_APP/device/adc.h @@ -20,6 +20,7 @@ public: float vin_adc_v; /**< VIN_ADC 引脚处电压(分压后的 ADC 端电压),单位 V。 */ float vdda; /**< 根据内部参考电压换算得到的 MCU 供电电压,单位 V。 */ float temperature; /**< 根据温度通道换算得到的芯片温度,单位 ℃。 */ + uint8_t valid; /**< 采样是否有效:1=有效,0=存在原始值/换算异常。 */ }; /** diff --git a/GD32E508_DCU_APP/device/app_spi_command.cpp b/GD32E508_DCU_APP/device/app_spi_command.cpp new file mode 100644 index 0000000..1c5c046 --- /dev/null +++ b/GD32E508_DCU_APP/device/app_spi_command.cpp @@ -0,0 +1,397 @@ +/** + * @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 +#include + +/* ============================================================ + * 协议常量(与 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; + +/* ============================================================ + * 静态成员 + * ============================================================ */ +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() +{ + if (s_logPending) { + s_logPending = false; + printf("%s\r\n", s_logBuf); + } +} + +/* ============================================================ + * 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); + 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(); +} \ No newline at end of file diff --git a/GD32E508_DCU_APP/device/app_spi_command.h b/GD32E508_DCU_APP/device/app_spi_command.h new file mode 100644 index 0000000..9185221 --- /dev/null +++ b/GD32E508_DCU_APP/device/app_spi_command.h @@ -0,0 +1,55 @@ +#ifndef DEVICE_APP_SPI_COMMAND_H +#define DEVICE_APP_SPI_COMMAND_H + +#include + +/** + * @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 */ \ No newline at end of file diff --git a/GD32E508_DCU_APP/device/canfd.h b/GD32E508_DCU_APP/device/canfd.h index 63bd583..c29d3eb 100644 --- a/GD32E508_DCU_APP/device/canfd.h +++ b/GD32E508_DCU_APP/device/canfd.h @@ -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。 */ @@ -188,4 +192,4 @@ private: using canfd_diag_t = CanFd::Diag; using canfd_status_payload_t = CanFd::StatusPayload; -#endif /* DEVICE_CANFD_H */ \ No newline at end of file +#endif /* DEVICE_CANFD_H */ diff --git a/GD32E508_DCU_APP/device/usart0.cpp b/GD32E508_DCU_APP/device/usart0.cpp index 5846b03..275b50b 100644 --- a/GD32E508_DCU_APP/device/usart0.cpp +++ b/GD32E508_DCU_APP/device/usart0.cpp @@ -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(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); } diff --git a/GD32E508_DCU_APP/device/watchdog.cpp b/GD32E508_DCU_APP/device/watchdog.cpp new file mode 100644 index 0000000..5950631 --- /dev/null +++ b/GD32E508_DCU_APP/device/watchdog.cpp @@ -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_config(CONFIG_FWDGT_PSC); + fwdgt_reload_config(CONFIG_FWDGT_RLD); + fwdgt_write_disable(); + fwdgt_enable(); +} + +void Watchdog::feed() +{ + fwdgt_counter_reload(); +} + +void Watchdog::abandon() +{ + /* 不喂狗即可,FWDGT 超时后硬件自动复位。 */ +} diff --git a/GD32E508_DCU_APP/device/watchdog.h b/GD32E508_DCU_APP/device/watchdog.h new file mode 100644 index 0000000..eede2a9 --- /dev/null +++ b/GD32E508_DCU_APP/device/watchdog.h @@ -0,0 +1,25 @@ +/** + * @file watchdog.h + * @brief APP 独立看门狗模块(FWDGT)。 + * + * 超时后系统硬复位,BOOT 检测 FWDGT 复位标志后停留在 OTA 等待模式, + * 而非继续跳入卡死的 APP。 + */ +#ifndef DEVICE_WATCHDOG_H +#define DEVICE_WATCHDOG_H + +#include + +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 */ diff --git a/GD32E508_DCU_APP/main.cpp b/GD32E508_DCU_APP/main.cpp index 6b0df32..a67aba9 100644 --- a/GD32E508_DCU_APP/main.cpp +++ b/GD32E508_DCU_APP/main.cpp @@ -1,4 +1,4 @@ -/** +/** * @file main.cpp * @brief 应用主入口与顶层流程实现。 */ @@ -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,7 +32,7 @@ void App::printBanner() const * @param[in] 无。 * @param[out] 无。 * @retval int 固定返回 0;在嵌入式常驻循环场景下通常不会真正返回。 - * @note 完成系统初始化、任务运行时初始化和启动横幅输出后,持续执行任务分发循环。 + * @note 当前恢复正常初始化与任务执行路径;空闲等待前短暂关中断,避免任务刚置位后误入睡眠。 */ int App::run() { @@ -37,9 +42,13 @@ int App::run() 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; diff --git a/GD32E508_DCU_APP/main.h b/GD32E508_DCU_APP/main.h index 60d0b6f..236c02d 100644 --- a/GD32E508_DCU_APP/main.h +++ b/GD32E508_DCU_APP/main.h @@ -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 负责组织初始化、横幅输出与主循环执行。 diff --git a/GD32E508_DCU_APP/sys/config.h b/GD32E508_DCU_APP/sys/config.h index 96478e3..e7ec3d6 100644 --- a/GD32E508_DCU_APP/sys/config.h +++ b/GD32E508_DCU_APP/sys/config.h @@ -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 */ diff --git a/GD32E508_DCU_APP/sys/initial.cpp b/GD32E508_DCU_APP/sys/initial.cpp index 3ae2bbe..0968051 100644 --- a/GD32E508_DCU_APP/sys/initial.cpp +++ b/GD32E508_DCU_APP/sys/initial.cpp @@ -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" /** @@ -27,6 +30,9 @@ void AppInitializer::init() Usart0::init(); TempSensor::init(); CanFd::init(); + OtaBootRequest::init(); + AppSpiCommand::init(); TaskManager::init(); SchedulerTimer::init(); -} \ No newline at end of file + Watchdog::init(); +} diff --git a/GD32E508_DCU_APP/sys/ota_boot_request.cpp b/GD32E508_DCU_APP/sys/ota_boot_request.cpp new file mode 100644 index 0000000..078bc92 --- /dev/null +++ b/GD32E508_DCU_APP/sys/ota_boot_request.cpp @@ -0,0 +1,163 @@ +/** + * @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 kRebootDelayTicks = 2U; + +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 复位前的调度延迟计数,给 SPI/CAN 应答留出发送时间。 */ +uint32_t OtaBootRequest::s_rebootCountdown = 0U; + +/** + * @brief 初始化进入 Bootloader 请求模块的运行态。 + */ +void OtaBootRequest::init() +{ + s_rebootPending = false; + s_rebootCountdown = 0U; +} + +/** + * @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_rebootCountdown = kRebootDelayTicks; + 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 中断里直接复位,避免应答还没发出就重启。 + */ +void OtaBootRequest::process() +{ + if(!s_rebootPending) { + return; + } + + if(s_rebootCountdown > 0U) { + --s_rebootCountdown; + return; + } + + __disable_irq(); + NVIC_SystemReset(); + while(1) { + } +} diff --git a/GD32E508_DCU_APP/sys/ota_boot_request.h b/GD32E508_DCU_APP/sys/ota_boot_request.h new file mode 100644 index 0000000..ccb0207 --- /dev/null +++ b/GD32E508_DCU_APP/sys/ota_boot_request.h @@ -0,0 +1,42 @@ +#ifndef SYS_OTA_BOOT_REQUEST_H +#define SYS_OTA_BOOT_REQUEST_H + +#include + +/** + * @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; + static uint32_t s_rebootCountdown; +}; + +#endif /* SYS_OTA_BOOT_REQUEST_H */ diff --git a/GD32E508_DCU_APP/sys/systick.cpp b/GD32E508_DCU_APP/sys/systick.cpp index 9ce059e..2594a91 100644 --- a/GD32E508_DCU_APP/sys/systick.cpp +++ b/GD32E508_DCU_APP/sys/systick.cpp @@ -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(); } diff --git a/GD32E508_DCU_APP/sys/task_manager.cpp b/GD32E508_DCU_APP/sys/task_manager.cpp index 02d281c..f86564f 100644 --- a/GD32E508_DCU_APP/sys/task_manager.cpp +++ b/GD32E508_DCU_APP/sys/task_manager.cpp @@ -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 } } @@ -111,4 +145,4 @@ void TaskManager::tick100us() task_scheduler_t &TaskManager::state() { return g_scheduler; -} \ No newline at end of file +} diff --git a/GD32E508_DCU_APP/sys/task_manager.h b/GD32E508_DCU_APP/sys/task_manager.h index 9390ae5..0c6b22f 100644 --- a/GD32E508_DCU_APP/sys/task_manager.h +++ b/GD32E508_DCU_APP/sys/task_manager.h @@ -42,6 +42,7 @@ public: * @param[in] taskMask 待置位的任务位图掩码。 * @param[out] 无。 * @retval 无返回值。 + * @note 内部通过保持当前 PRIMASK 状态的临界区保证可由主循环与 ISR 共享调用。 */ static void set(uint32_t taskMask); @@ -55,4 +56,4 @@ public: static task_scheduler_t &state(); }; -#endif /* SYS_TASK_MANAGER_H */ \ No newline at end of file +#endif /* SYS_TASK_MANAGER_H */ diff --git a/GD32E508_DCU_APP/sys/task_runner.cpp b/GD32E508_DCU_APP/sys/task_runner.cpp index c12affb..844b7fb 100644 --- a/GD32E508_DCU_APP/sys/task_runner.cpp +++ b/GD32E508_DCU_APP/sys/task_runner.cpp @@ -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(s.temperature * 100.0f); + const uint32_t vdda_mV = static_cast(s.vdda * 1000.0f); + const uint32_t vin_adc_mV = static_cast(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(s.valid), + static_cast(s.temp_raw), + static_cast(s.vref_raw), + static_cast(temp_x100 / 100), + static_cast((temp_x100 >= 0 ? temp_x100 : -temp_x100) % 100), + static_cast(vdda_mV / 1000U), + static_cast(vdda_mV % 1000U), + static_cast(vin_adc_mV / 1000U), + static_cast(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(s.valid), + static_cast(temp_x100 / 100), + static_cast((temp_x100 >= 0 ? temp_x100 : -temp_x100) % 100), + static_cast(vdda_mV / 1000U), + static_cast(vdda_mV % 1000U), + static_cast(vin_adc_mV / 1000U), + static_cast(vin_adc_mV % 1000U), static_cast(s_runtime.last_rx_channel), static_cast(s_runtime.last_rx_id), static_cast(s_runtime.last_rx_len), @@ -158,6 +257,8 @@ void TaskRunner::taskDiagnosticPrint() static_cast(diag->tx_count), static_cast(diag->rx_count), static_cast(diag->rx_drop_count), + static_cast(diag->rx_fifo_full_count), + static_cast(diag->rx_fifo_overfull_count), static_cast(diag->tx_drop_count), static_cast(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(); -} \ No newline at end of file + if(pending & (1UL << TASK_SPI_LOG)) AppSpiCommand::poll(); +} diff --git a/GD32E508_DCU_APP/sys/task_runner.h b/GD32E508_DCU_APP/sys/task_runner.h index 959f098..5575088 100644 --- a/GD32E508_DCU_APP/sys/task_runner.h +++ b/GD32E508_DCU_APP/sys/task_runner.h @@ -51,4 +51,4 @@ private: static void taskDiagnosticPrint(); }; -#endif /* SYS_TASK_RUNNER_H */ \ No newline at end of file +#endif /* SYS_TASK_RUNNER_H */ diff --git a/README.md b/README.md index 090def8..8586f42 100644 --- a/README.md +++ b/README.md @@ -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 单帧缓存升级为轻量环形缓冲 - 将协议帧定义抽到单独文件中 - 完善状态灯任务与心跳任务