feat(canfd): 切换 CANFD 接收为中断驱动和环形缓冲机制
- 将 CANFD 接收机制由轮询改为 FIFO0 的中断事件驱动处理 - 实现循环取空硬件 FIFO0,提高接收效率和响应速度 - 引入环形缓冲区缓存接收帧,支持多路通道多帧队列管理 - 置位解析任务标志位,实现接收数据的异步解析 - 增加接收中断统计,包括 FIFO 满和溢出计数 - 重构临界区代码,使用自定义函数保护中断状态 - 修改接收过滤器配置,支持按掩码匹配标准帧进入 FIFO0 - 使能 CAN FIFO0 多种中断标志,包括非空、满和溢出中断
This commit is contained in:
@@ -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,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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
float vin_adc_v; /**< VIN_ADC 引脚处电压(分压后的 ADC 端电压),单位 V。 */
|
||||
float vdda; /**< 根据内部参考电压换算得到的 MCU 供电电压,单位 V。 */
|
||||
float temperature; /**< 根据温度通道换算得到的芯片温度,单位 ℃。 */
|
||||
uint8_t valid; /**< 采样是否有效:1=有效,0=存在原始值/换算异常。 */
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 <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;
|
||||
|
||||
/* ============================================================
|
||||
* 静态成员
|
||||
* ============================================================ */
|
||||
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();
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
#endif /* DEVICE_CANFD_H */
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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 超时后硬件自动复位。 */
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
|
||||
@@ -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 负责组织初始化、横幅输出与主循环执行。
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
Watchdog::init();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#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;
|
||||
static uint32_t s_rebootCountdown;
|
||||
};
|
||||
|
||||
#endif /* SYS_OTA_BOOT_REQUEST_H */
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
#endif /* SYS_TASK_MANAGER_H */
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -51,4 +51,4 @@ private:
|
||||
static void taskDiagnosticPrint();
|
||||
};
|
||||
|
||||
#endif /* SYS_TASK_RUNNER_H */
|
||||
#endif /* SYS_TASK_RUNNER_H */
|
||||
|
||||
@@ -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 单帧缓存升级为轻量环形缓冲
|
||||
- 将协议帧定义抽到单独文件中
|
||||
- 完善状态灯任务与心跳任务
|
||||
|
||||
|
||||
Reference in New Issue
Block a user