feat(canfd): 切换 CANFD 接收为中断驱动和环形缓冲机制

- 将 CANFD 接收机制由轮询改为 FIFO0 的中断事件驱动处理
- 实现循环取空硬件 FIFO0,提高接收效率和响应速度
- 引入环形缓冲区缓存接收帧,支持多路通道多帧队列管理
- 置位解析任务标志位,实现接收数据的异步解析
- 增加接收中断统计,包括 FIFO 满和溢出计数
- 重构临界区代码,使用自定义函数保护中断状态
- 修改接收过滤器配置,支持按掩码匹配标准帧进入 FIFO0
- 使能 CAN FIFO0 多种中断标志,包括非空、满和溢出中断
This commit is contained in:
cbb
2026-05-30 00:24:31 +08:00
parent 078ea06317
commit f63114f5fd
26 changed files with 1173 additions and 121 deletions
+53 -8
View File
@@ -7,6 +7,24 @@
#include "device/adc.h"
#include "sys/systick.h"
#include "sys/config.h"
#include <math.h>
namespace {
constexpr float kMinValidVdda = 2.7f;
constexpr float kMaxValidVdda = 3.6f;
constexpr float kMinValidTemperature = -40.0f;
constexpr float kMaxValidTemperature = 125.0f;
static bool isValidAdcRaw(uint16_t raw)
{
return (raw > 0U) && (raw < static_cast<uint16_t>(CONFIG_ADC_MAX_VALUE));
}
static bool isFiniteFloat(float value)
{
return isfinite(value) != 0;
}
} // namespace
namespace {
constexpr uint32_t kDmaPeriph = DMA0;
@@ -53,6 +71,7 @@ void Adc::initDma()
* @param[out] 无。
* @retval 无返回值。
* @note 按 `VIN_ADC / TEMP / VREFINT` 三通道顺序启用连续扫描与 DMA 采样。
* 参考 GD32E508 文档要求,内部温度与 VREFINT 通道使用长采样时间,以满足 ts_temp 要求。
*/
void Adc::init()
{
@@ -70,11 +89,14 @@ void Adc::init()
adc_special_function_config(CONFIG_ADC_INSTANCE, ADC_CONTINUOUS_MODE, ENABLE);
adc_data_alignment_config(CONFIG_ADC_INSTANCE, ADC_DATAALIGN_RIGHT);
adc_channel_length_config(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL, kChannelCount);
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 0U, CONFIG_ADC_VIN_CHANNEL, CONFIG_ADC_SAMPLE_TIME);
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 1U, CONFIG_ADC_TEMP_CHANNEL, CONFIG_ADC_SAMPLE_TIME);
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 2U, CONFIG_ADC_VREFINT_CHANNEL, CONFIG_ADC_SAMPLE_TIME);
/* 内部温度传感器和 VREFINT 使用长采样时间,确保满足文档要求。 */
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 0U, CONFIG_ADC_VIN_CHANNEL, ADC_SAMPLETIME_239POINT5);
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 1U, CONFIG_ADC_TEMP_CHANNEL, ADC_SAMPLETIME_239POINT5);
adc_regular_channel_config(CONFIG_ADC_INSTANCE, 2U, CONFIG_ADC_VREFINT_CHANNEL, ADC_SAMPLETIME_239POINT5);
adc_external_trigger_source_config(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);
adc_external_trigger_config(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL, DISABLE);
adc_external_trigger_config(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL, ENABLE);
adc_dma_mode_enable(CONFIG_ADC_INSTANCE);
adc_tempsensor_vrefint_enable();
@@ -83,6 +105,7 @@ void Adc::init()
adc_enable(CONFIG_ADC_INSTANCE);
SysTickTimer::delayMs(CONFIG_ADC_STABILIZE_DELAY_MS);
adc_calibration_enable(CONFIG_ADC_INSTANCE);
SysTickTimer::delayMs(CONFIG_ADC_STABILIZE_DELAY_MS);
adc_software_trigger_enable(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL);
SysTickTimer::delayMs(1U);
s_dmaReady = 1U;
@@ -169,8 +192,14 @@ float Adc::calculateVinAdcVoltage(uint16_t vinRaw, float vdda)
*/
float Adc::calculateVdda(uint16_t vrefRaw)
{
if(vrefRaw == 0U) return CONFIG_ADC_DEFAULT_VDDA;
return (CONFIG_ADC_VREFINT_TYPICAL_VOLTAGE * CONFIG_ADC_MAX_VALUE) / static_cast<float>(vrefRaw);
if(!isValidAdcRaw(vrefRaw)) return 0.0f;
const float vdda = (CONFIG_ADC_VREFINT_TYPICAL_VOLTAGE * CONFIG_ADC_MAX_VALUE) /
static_cast<float>(vrefRaw);
if(!isFiniteFloat(vdda) || vdda < kMinValidVdda || vdda > kMaxValidVdda) {
return 0.0f;
}
return vdda;
}
/**
@@ -179,11 +208,22 @@ float Adc::calculateVdda(uint16_t vrefRaw)
* @param[in] vdda 当前 ADC 供电电压。
* @param[out] 无。
* @retval float 返回换算得到的芯片温度,单位 ℃。
* @note 按 GD32E508 文档推荐公式处理:
* Temperature = (V25 - Vtemperature) / Avg_Slope + 25
*/
float Adc::calculateTemperature(uint16_t tempRaw, float vdda)
{
if(!isValidAdcRaw(tempRaw)) return 0.0f;
if(!isFiniteFloat(vdda) || vdda < kMinValidVdda || vdda > kMaxValidVdda) return 0.0f;
const float sensorVoltage = (static_cast<float>(tempRaw) * vdda) / CONFIG_ADC_MAX_VALUE;
return ((CONFIG_ADC_TEMP_V25 - sensorVoltage) / CONFIG_ADC_TEMP_AVG_SLOPE) + 25.0f;
const float temperature = ((CONFIG_ADC_TEMP_V25 - sensorVoltage) / CONFIG_ADC_TEMP_AVG_SLOPE) + 25.0f;
if(!isFiniteFloat(temperature) ||
temperature < kMinValidTemperature ||
temperature > kMaxValidTemperature) {
return 0.0f;
}
return temperature;
}
/**
@@ -203,4 +243,9 @@ void Adc::collectSample(Sample *sample)
sample->vdda = calculateVdda(sample->vref_raw);
sample->vin_adc_v = calculateVinAdcVoltage(sample->vin_raw, sample->vdda);
sample->temperature = calculateTemperature(sample->temp_raw, sample->vdda);
}
sample->valid = (isValidAdcRaw(sample->temp_raw) &&
isValidAdcRaw(sample->vref_raw) &&
(sample->vdda >= kMinValidVdda) && (sample->vdda <= kMaxValidVdda) &&
(sample->temperature >= kMinValidTemperature) &&
(sample->temperature <= kMaxValidTemperature)) ? 1U : 0U;
}
+82 -31
View File
@@ -6,6 +6,7 @@
#include "main.h"
#include "device/canfd.h"
#include "sys/config.h"
#include "sys/task_manager.h"
namespace {
struct TxFrame {
@@ -40,8 +41,32 @@ constexpr CanChannelConfig kChannels[] = {
};
constexpr size_t kChannelCount = sizeof(kChannels) / sizeof(kChannels[0]);
constexpr uint8_t kRxQueueDepth = 4U;
constexpr uint8_t kTxQueueDepth = 4U;
constexpr uint32_t kCanRxParseTaskMask = (1UL << TASK_CAN_RX_PARSE);
/**
* @brief Enter a critical section and preserve the previous interrupt mask.
* @param[in] 无。
* @param[out] 无。
* @retval uint32_t Previous PRIMASK value.
*/
static uint32_t enterCriticalSection()
{
const uint32_t primask = __get_PRIMASK();
__disable_irq();
return primask;
}
/**
* @brief Exit a critical section by restoring the saved interrupt mask.
* @param[in] primask Previous PRIMASK value returned by `enterCriticalSection()`.
* @param[out] 无。
* @retval 无返回值。
*/
static void exitCriticalSection(uint32_t primask)
{
__set_PRIMASK(primask);
}
} // namespace
CanFd::Diag CanFd::s_diag{};
@@ -72,23 +97,23 @@ void CanFd::initGpio()
}
/**
* @brief 为指定 CAN 控制器配置全接收过滤器。
* @brief 为指定 CAN 控制器配置接收过滤器。
* @param[in] canPeriph 目标 CAN 外设实例。
* @param[in] filterNumber 过滤器编号。
* @param[out] 无。
* @retval 无返回值。
* @note 当前使用 32 位 mask 模式放行全部报文,并统一进入 FIFO0。
* @note 当前默认按 `CONFIG_CAN_RX_FILTER_ID`/`CONFIG_CAN_RX_FILTER_MASK` 对标准帧做掩码匹配,并统一进入 FIFO0。
*/
void CanFd::filterAcceptAll(uint32_t canPeriph, uint8_t filterNumber)
void CanFd::configureRxFilter(uint32_t canPeriph, uint8_t filterNumber)
{
can_filter_parameter_struct can_filter{};
can_filter.filter_number = filterNumber;
can_filter.filter_mode = CAN_FILTERMODE_MASK;
can_filter.filter_bits = CAN_FILTERBITS_32BIT;
can_filter.filter_list_high = 0x0000U;
can_filter.filter_list_high = static_cast<uint16_t>((CONFIG_CAN_RX_FILTER_ID & 0x7FFU) << 5U);
can_filter.filter_list_low = 0x0000U;
can_filter.filter_mask_high = 0x0000U;
can_filter.filter_mask_low = 0x0000U;
can_filter.filter_mask_high = static_cast<uint16_t>((CONFIG_CAN_RX_FILTER_MASK & 0x7FFU) << 5U);
can_filter.filter_mask_low = static_cast<uint16_t>(1U << 2U);
can_filter.filter_fifo_number = CAN_FIFO0;
can_filter.filter_enable = ENABLE;
can_filter_init(canPeriph, &can_filter);
@@ -237,7 +262,7 @@ bool CanFd::enqueueTxFrame(uint8_t channelIndex, const StatusPayload *status)
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 初始化后默认将三路 TCAN1057A 收发器置为 Normal,并清零收发诊断状态。
* @note 初始化后默认将三路 TCAN1057A 收发器置为 Normal,并使能 FIFO0 的非空/满/overfull 中断统计。
*/
void CanFd::init()
{
@@ -253,8 +278,8 @@ void CanFd::init()
can_deinit(ch.canPeriph);
initNominalTiming(ch.canPeriph);
initFdMode(ch.canPeriph);
filterAcceptAll(ch.canPeriph, ch.filterNumber);
can_interrupt_enable(ch.canPeriph, CAN_INT_RFNE0);
configureRxFilter(ch.canPeriph, ch.filterNumber);
can_interrupt_enable(ch.canPeriph, CAN_INT_RFNE0 | CAN_INT_RFF0 | CAN_INT_RFO0);
nvic_irq_enable(ch.rx0Irq, 1U, static_cast<uint8_t>(i));
s_rxQueues[i].head = 0U;
@@ -348,12 +373,16 @@ void CanFd::sendPrepared()
*/
bool CanFd::hasRxFrame()
{
bool found = false;
const uint32_t primask = enterCriticalSection();
for(size_t i = 0; i < kChannelCount; ++i) {
if(s_rxQueues[i].count != 0U) {
return true;
found = true;
break;
}
}
return false;
exitCriticalSection(primask);
return found;
}
/**
@@ -371,7 +400,7 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t
RxFrame localFrame{};
bool found = false;
__disable_irq();
const uint32_t primask = enterCriticalSection();
for(size_t n = 0; n < kChannelCount; ++n) {
const uint8_t idx = static_cast<uint8_t>((s_nextConsumeChannel + n) % kChannelCount);
auto &queue = s_rxQueues[idx];
@@ -386,7 +415,7 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t
found = true;
break;
}
__enable_irq();
exitCriticalSection(primask);
if(!found) {
return false;
@@ -447,31 +476,44 @@ void CanFd::setSilentMode()
* @param[in] channelIndex 目标 CAN 通道索引(0/1/2)。
* @param[out] 无。
* @retval 无返回值。
* @note 从对应控制器 FIFO0 取出一帧报文,更新诊断统计,并写入该通道软件接收队列。
* @note 从对应控制器 FIFO0 循环取空所有待处理报文,更新诊断统计,写入该通道软件接收队列,并置位解析任务。
*/
void CanFd::handleRxInterrupt(uint8_t channelIndex)
{
if(channelIndex >= kChannelCount) return;
const auto &ch = kChannels[channelIndex];
if(can_interrupt_flag_get(ch.canPeriph, CAN_INT_FLAG_RFL0) == RESET) return;
const bool fifoFull = (can_interrupt_flag_get(ch.canPeriph, CAN_INT_FLAG_RFF0) != RESET);
const bool fifoOverfull = (can_interrupt_flag_get(ch.canPeriph, CAN_INT_FLAG_RFO0) != RESET);
if(fifoFull) {
s_diag.rx_fifo_full_count++;
}
if(fifoOverfull) {
s_diag.rx_fifo_overfull_count++;
}
can_receive_message_struct rx_message{};
can_struct_para_init(CAN_RX_MESSAGE_STRUCT, &rx_message);
can_message_receive(ch.canPeriph, CAN_FIFO0, &rx_message);
bool hadTraffic = false;
while(can_interrupt_flag_get(ch.canPeriph, CAN_INT_FLAG_RFL0) != RESET) {
hadTraffic = true;
s_diag.rx_count++;
s_diag.last_rx_channel = channelIndex;
s_diag.last_payload_length = rx_message.rx_dlen;
s_diag.last_rx_fd_flag = rx_message.fd_flag;
s_diag.last_rx_brs = rx_message.fd_brs;
s_diag.last_rx_esi = rx_message.fd_esi;
s_diag.last_rx_id = (rx_message.rx_ff == CAN_FF_STANDARD) ? rx_message.rx_sfid : rx_message.rx_efid;
can_receive_message_struct rx_message{};
can_struct_para_init(CAN_RX_MESSAGE_STRUCT, &rx_message);
can_message_receive(ch.canPeriph, CAN_FIFO0, &rx_message);
s_diag.rx_count++;
s_diag.last_rx_channel = channelIndex;
s_diag.last_payload_length = rx_message.rx_dlen;
s_diag.last_rx_fd_flag = rx_message.fd_flag;
s_diag.last_rx_brs = rx_message.fd_brs;
s_diag.last_rx_esi = rx_message.fd_esi;
s_diag.last_rx_id = (rx_message.rx_ff == CAN_FF_STANDARD) ? rx_message.rx_sfid : rx_message.rx_efid;
auto &queue = s_rxQueues[channelIndex];
if(queue.count >= kRxQueueDepth) {
s_diag.rx_drop_count++;
continue;
}
auto &queue = s_rxQueues[channelIndex];
if(queue.count >= kRxQueueDepth) {
s_diag.rx_drop_count++;
} else {
auto &frame = queue.frames[queue.tail];
frame.channel = channelIndex;
frame.id = s_diag.last_rx_id;
@@ -479,11 +521,20 @@ void CanFd::handleRxInterrupt(uint8_t channelIndex)
for(uint8_t i = 0; i < rx_message.rx_dlen; ++i) {
frame.data[i] = rx_message.rx_data[i];
}
queue.tail = static_cast<uint8_t>((queue.tail + 1U) % kRxQueueDepth);
queue.count++;
}
can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFL0);
if(fifoFull) {
can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFF0);
}
if(fifoOverfull) {
can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFO0);
}
if(hadTraffic) {
TaskManager::set(kCanRxParseTaskMask);
}
}
/**
+1 -1
View File
@@ -83,4 +83,4 @@ void SchedulerTimer::onInterrupt()
* @retval 无返回值。
* @note C 向量包装函数,内部转调 `SchedulerTimer::onInterrupt()`。
*/
void TIMER2_IRQHandler(void) { SchedulerTimer::onInterrupt(); }
extern "C" void TIMER2_IRQHandler(void) { SchedulerTimer::onInterrupt(); }
+1
View File
@@ -20,6 +20,7 @@ public:
float vin_adc_v; /**< VIN_ADC 引脚处电压(分压后的 ADC 端电压),单位 V。 */
float vdda; /**< 根据内部参考电压换算得到的 MCU 供电电压,单位 V。 */
float temperature; /**< 根据温度通道换算得到的芯片温度,单位 ℃。 */
uint8_t valid; /**< 采样是否有效:1=有效,0=存在原始值/换算异常。 */
};
/**
+397
View File
@@ -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();
}
+55
View File
@@ -0,0 +1,55 @@
#ifndef DEVICE_APP_SPI_COMMAND_H
#define DEVICE_APP_SPI_COMMAND_H
#include <stdint.h>
/**
* @brief APP 运行期 SPI 命令从机模块。
*
* 复用 BOOT 的 SpiOta 协议帧格式(SOF+TYPE+LEN+PAYLOAD+CRC):
* - TYPE=0xB0 → 请求进入 Bootloader,回复 ACK 帧
* - TYPE=0x01/0x02/0x03(OTA 命令)→ 回复 WAIT 帧(不在 BOOT 模式)
* - 旧式单字节 0xB0 命令仍兼容
*
* 调试日志采用延迟输出:processCommand() 只写缓冲+置标志,
* poll() 中再 printf,避免在帧处理路径上耗时。
*/
class AppSpiCommand {
public:
/** @brief 初始化 SPI0 从机命令通道。 */
static void init();
/** @brief 主循环轮询:打印延迟日志。 */
static void poll();
/** @brief SPI0 中断处理入口。 */
static void onInterrupt();
/** @brief CS 边沿中断处理入口。 */
static void onCsExtiInterrupt();
private:
static constexpr uint16_t kRxBufSize = 128U;
static constexpr uint16_t kTxBufSize = 64U;
static constexpr uint16_t kLogBufSize = 128U;
static volatile bool s_csActive;
static volatile uint8_t s_rxLen;
static volatile uint8_t s_txLen;
static volatile uint8_t s_txPos;
static volatile bool s_logPending;
static uint8_t s_rxBuf[kRxBufSize];
static uint8_t s_txBuf[kTxBufSize];
static char s_logBuf[kLogBufSize];
static void configureGpio();
static void configureSpi0();
static void configureExti();
static void onCsAssert();
static void onCsRelease();
static void processCommand();
static void logDeferred(const char *msg);
static void logDeferredFmt(const char *fmt, uint8_t val);
};
#endif /* DEVICE_APP_SPI_COMMAND_H */
+13 -9
View File
@@ -20,7 +20,9 @@ public:
uint32_t tx_count; /**< 成功发送的报文计数。 */
uint32_t rx_count; /**< 成功接收的报文计数。 */
uint32_t error_count; /**< 发送失败或检测到的错误计数。 */
uint32_t rx_drop_count; /**< 接收 FIFO 满导致丢弃的报文计数。 */
uint32_t rx_drop_count; /**< 软件接收队列满导致丢弃的报文计数。 */
uint32_t rx_fifo_full_count; /**< 硬件 FIFO0 进入满状态的次数。 */
uint32_t rx_fifo_overfull_count; /**< 硬件 FIFO0 发生 overfull 的次数。 */
uint32_t tx_drop_count; /**< 发送 FIFO 满导致丢弃的报文计数。 */
uint32_t last_rx_id; /**< 最近一次接收报文的标识符。 */
uint32_t last_tx_id; /**< 最近一次发送报文的标识符。 */
@@ -105,7 +107,7 @@ public:
* @param[out] length 输出报文数据长度的指针;允许为空。
* @param[out] channel 输出报文来源的 CAN 通道号指针;允许为空。
* @retval bool true 表示成功消费一帧报文,false 表示当前 3 路缓存均为空。
* @note 每路通道各自保留最近一帧;消费顺序采用轮转扫描,避免长期偏向固定通道。
* @note 每路通道使用轻量环形缓冲保留多帧;消费顺序采用轮转扫描,避免长期偏向固定通道。
*/
static bool consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel = nullptr);
@@ -153,6 +155,8 @@ public:
static void handleRxInterrupt(uint8_t channelIndex);
private:
static constexpr uint8_t kRxQueueDepth = 16U;
struct RxFrame {
uint8_t channel;
uint32_t id;
@@ -161,16 +165,16 @@ private:
};
struct RxQueue {
RxFrame frames[4];
uint8_t head;
uint8_t tail;
uint8_t count;
RxFrame frames[kRxQueueDepth];
volatile uint8_t head;
volatile uint8_t tail;
volatile uint8_t count;
};
/** @brief 初始化 CAN0/CAN1/CAN2 相关 GPIO。 */
static void initGpio();
/** @brief 为指定控制器配置全接收过滤器。 */
static void filterAcceptAll(uint32_t canPeriph, uint8_t filterNumber);
/** @brief 为指定控制器配置接收过滤器。 */
static void configureRxFilter(uint32_t canPeriph, uint8_t filterNumber);
/** @brief 配置指定控制器的 CAN 仲裁段标称位时序。 */
static void initNominalTiming(uint32_t canPeriph);
/** @brief 配置指定控制器的 CAN FD 数据段位时序与 TDC。 */
@@ -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 */
+13 -10
View File
@@ -50,12 +50,14 @@ bool Usart0::isBufferEmpty()
* @param[in] ch 待发送字符。
* @param[out] 无。
* @retval bool true 表示成功写入发送缓冲,false 表示缓冲已满而被丢弃。
* @note 缓冲满时仅增加丢弃计数,不阻塞主流程。
* @note 当前统一由 TBE 中断从队列中取字节发送;此处只负责入队并使能发送中断。
*/
bool Usart0::putCharNonBlocking(int ch)
{
uint16_t next = nextIndex(s_txHead);
__disable_irq();
const uint16_t next = nextIndex(s_txHead);
if(next == s_txTail) {
__enable_irq();
s_dropCount++;
return false;
}
@@ -63,15 +65,16 @@ bool Usart0::putCharNonBlocking(int ch)
s_txBuf[s_txHead] = static_cast<uint8_t>(ch);
s_txHead = next;
usart_interrupt_enable(USART0, USART_INT_TBE);
__enable_irq();
return true;
}
/**
* @brief 向 USART0 发送缓冲写入一个字符。
* @brief 将字符写入 USART0 发送环形缓冲。
* @param[in] ch 待发送字符。
* @param[in] f 标准流指针。
* @param[in] f 标准流指针,实际未使用。
* @param[out] 无。
* @retval int 返回写入缓冲的字符值。
* @retval int 返回待发送字符值。
* @note 当前采用非阻塞写入策略;若缓冲已满则丢弃该字符但仍返回原字符值。
*/
int Usart0::putChar(int ch, FILE *f)
@@ -94,8 +97,7 @@ void Usart0::onInterrupt()
if(!isBufferEmpty()) {
usart_data_transmit(USART0, s_txBuf[s_txTail]);
s_txTail = nextIndex(s_txTail);
}
if(isBufferEmpty()) {
} else {
usart_interrupt_disable(USART0, USART_INT_TBE);
}
}
@@ -106,11 +108,12 @@ void Usart0::onInterrupt()
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 将 PA9 配置为复用推挽发送脚,将 PA10 配置为浮空输入接收脚。
* @note 将 PA9 配置为复用推挽发送脚,将 PA10 配置为浮空输入接收脚,并使能 AF 时钟。
*/
void Usart0::configurePins()
{
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_AF);
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
}
@@ -120,7 +123,7 @@ void Usart0::configurePins()
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 使用 `CONFIG_USART0_BAUDRATE` 配置串口参数,并使能 USART0 中断。
* @note 使用 `CONFIG_USART0_BAUDRATE` 配置串口参数,并使能 USART0 TBE 中断用于队列发送。
*/
void Usart0::init()
{
@@ -142,7 +145,7 @@ void Usart0::init()
* @param[in] ch 待发送字符。
* @param[in] f 标准流指针。
* @param[out] 无。
* @retval int 返回写入缓冲的字符值。
* @retval int 返回待发送字符值。
* @note 该 C 包装函数内部转调 `Usart0::putChar()`。
*/
extern "C" int fputc(int ch, FILE *f) { return Usart0::putChar(ch, f); }
+31
View File
@@ -0,0 +1,31 @@
/**
* @file watchdog.cpp
* @brief APP 独立看门狗(FWDGT)实现。
*
* 时钟源: IRC40K (~40 KHz)
* 预分频: CONFIG_FWDGT_PSC, 重载值: CONFIG_FWDGT_RLD
* 超时 = (PSC / 40000) * (RLD + 1) 秒
*/
#include "device/watchdog.h"
#include "sys/config.h"
#include "main.h"
void Watchdog::init()
{
fwdgt_write_enable();
fwdgt_prescaler_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 超时后硬件自动复位。 */
}
+25
View File
@@ -0,0 +1,25 @@
/**
* @file watchdog.h
* @brief APP 独立看门狗模块(FWDGT)。
*
* 超时后系统硬复位,BOOT 检测 FWDGT 复位标志后停留在 OTA 等待模式,
* 而非继续跳入卡死的 APP。
*/
#ifndef DEVICE_WATCHDOG_H
#define DEVICE_WATCHDOG_H
#include <stdint.h>
class Watchdog {
public:
/** @brief 初始化并启动 FWDGT,超时约 CONFIG_FWDGT_TIMEOUT_MS 毫秒。 */
static void init();
/** @brief 喂狗(重载计数器),应在主循环中周期调用。 */
static void feed();
/** @brief 停止喂狗,让看门狗自然超时触发复位跳转 BOOT。 */
static void abandon();
};
#endif /* DEVICE_WATCHDOG_H */