- 在DCU_APP项目中添加cpu_load.h和cpu_load.cpp模块用于CPU负载率监测 - 修改csolution配置将构建目标从Debug切换为Release - 在EtherCAT PDO协议栈中新增段5诊断区(632-639字节),包含负载率和序列号 - 在所有关键中断服务程序中添加CpuLoad::isrEnter()/isrExit()钩子用于准确统计中断占用 - 增强EtherCAT同步中断处理,改进AL事件掩码守护机制,修复OP状态下PDI中断门控问题 - 解决SSC源码中指针地址对齐警告和打包成员访问警告问题 - 在主循环中集成CPU负载率采样tick()调用,实现1秒滚动窗口统计 BREAKING CHANGE: 构建配置从Debug改为Release,EtherCAT协议栈增加新的诊断数据区
192 lines
6.4 KiB
C++
192 lines
6.4 KiB
C++
/**
|
|
* @file usart0.cpp
|
|
* @brief USART0 串口模块实现与 printf 重定向。
|
|
*/
|
|
|
|
#include "main.h"
|
|
#include "device/usart0.h"
|
|
#include "sys/config.h"
|
|
#include "sys/critical.h"
|
|
#include "sys/cpu_load.h"
|
|
|
|
uint8_t Usart0::s_txBuf[Usart0::kTxBufSize];
|
|
volatile uint16_t Usart0::s_txHead = 0U;
|
|
volatile uint16_t Usart0::s_txTail = 0U;
|
|
volatile uint32_t Usart0::s_txDropped = 0U;
|
|
|
|
/**
|
|
* @brief 计算发送环形缓冲区中的下一个索引。
|
|
* @param[in] idx 当前索引值。
|
|
* @param[out] 无。
|
|
* @retval uint16_t 返回环形缓冲区中的下一个索引。
|
|
*/
|
|
uint16_t Usart0::nextIndex(uint16_t idx)
|
|
{
|
|
return (idx + 1U) % kTxBufSize;
|
|
}
|
|
|
|
/**
|
|
* @brief 判断 USART0 发送缓冲区是否为空。
|
|
* @param[in] 无。
|
|
* @param[out] 无。
|
|
* @retval bool true 表示发送缓冲区为空,false 表示仍有待发数据。
|
|
*/
|
|
bool Usart0::isBufferEmpty()
|
|
{
|
|
return s_txHead == s_txTail;
|
|
}
|
|
|
|
/**
|
|
* @brief 在临界区内直接轮询 TBE 发出一个字节,腾出一个缓冲槽位。
|
|
* @param[in] 无。
|
|
* @param[out] 无。
|
|
* @retval bool true 表示成功发出一字节(s_txTail 已前进),false 表示等待 TBE 超时。
|
|
* @note 调用者必须已持有临界区:USART0 中断为 preempt=3,已被 BASEPRI 屏蔽,
|
|
* 故此处独占 s_txTail,不会与 onInterrupt() 取到同一字节。
|
|
* 走轮询而非等中断,因此在中断上下文里调用 printf 也不会死锁。
|
|
*/
|
|
bool Usart0::drainOneByteLocked()
|
|
{
|
|
if(isBufferEmpty()) {
|
|
return false;
|
|
}
|
|
uint32_t spin = kTbeSpinMax;
|
|
while(usart_flag_get(USART0, USART_FLAG_TBE) == RESET) {
|
|
if(--spin == 0U) {
|
|
return false; /* USART 未在发送(外设异常/时钟丢失),交由调用者计丢弃 */
|
|
}
|
|
}
|
|
usart_data_transmit(USART0, s_txBuf[s_txTail]);
|
|
s_txTail = nextIndex(s_txTail);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief 向 USART0 发送缓冲写入一个字符,缓冲满时自旋腾位而非丢弃。
|
|
* @param[in] ch 待发送字符。
|
|
* @param[out] 无。
|
|
* @retval bool true 表示成功写入发送缓冲,false 表示 USART 异常导致该字节被丢弃。
|
|
* @note 正常路径仍是"入队 + 使能 TBE 中断"的非阻塞发送。仅在缓冲写满时
|
|
* (3M 波特率下 init 阶段诊断突发写入会写满)就地轮询发出一字节腾位,
|
|
* 以字节级背压换取日志完整——旧版此处静默丢弃且 putChar 忽略返回值,
|
|
* 实测使 [ECAT] 诊断出现 "bus eaabled"/"pdi_type=0x8Dexect" 等掉字,
|
|
* 曾导致 LAN9252 故障被两次误定性,故日志完整性优先于此处的等待开销。
|
|
* 仅当 USART 外设本身不发送时才真正丢弃并累加 s_txDropped。
|
|
*/
|
|
bool Usart0::putCharNonBlocking(int ch)
|
|
{
|
|
uint32_t save = RT_CRITICAL_SAVE();
|
|
uint16_t next = nextIndex(s_txHead);
|
|
if(next == s_txTail) {
|
|
/* 缓冲已满:就地发出最老的一字节腾出槽位,失败则说明 USART 真的没在发。 */
|
|
if(!drainOneByteLocked()) {
|
|
++s_txDropped;
|
|
RT_CRITICAL_RESTORE(save);
|
|
return false;
|
|
}
|
|
next = nextIndex(s_txHead);
|
|
}
|
|
|
|
s_txBuf[s_txHead] = static_cast<uint8_t>(ch);
|
|
s_txHead = next;
|
|
usart_interrupt_enable(USART0, USART_INT_TBE);
|
|
RT_CRITICAL_RESTORE(save);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief 将字符写入 USART0 发送环形缓冲。
|
|
* @param[in] ch 待发送字符。
|
|
* @param[in] f 标准流指针,实际未使用。
|
|
* @param[out] 无。
|
|
* @retval int 返回待发送字符值。
|
|
* @note 当前采用非阻塞写入策略;若缓冲已满则丢弃该字符但仍返回原字符值。
|
|
*/
|
|
int Usart0::putChar(int ch, FILE *f)
|
|
{
|
|
(void)f;
|
|
(void)putCharNonBlocking(ch);
|
|
return ch;
|
|
}
|
|
|
|
/**
|
|
* @brief USART0 中断处理入口。
|
|
* @param[in] 无。
|
|
* @param[out] 无。
|
|
* @retval 无返回值。
|
|
* @note 处理发送缓冲区的 TBE 中断驱动输出;当缓冲为空时自动关闭发送中断。
|
|
*/
|
|
void Usart0::onInterrupt()
|
|
{
|
|
if(usart_interrupt_flag_get(USART0, USART_INT_FLAG_TBE) != RESET) {
|
|
if(!isBufferEmpty()) {
|
|
usart_data_transmit(USART0, s_txBuf[s_txTail]);
|
|
s_txTail = nextIndex(s_txTail);
|
|
} else {
|
|
usart_interrupt_disable(USART0, USART_INT_TBE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief 配置 USART0 对应 GPIO 引脚。
|
|
* @param[in] 无。
|
|
* @param[out] 无。
|
|
* @retval 无返回值。
|
|
* @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);
|
|
}
|
|
|
|
/**
|
|
* @brief 初始化 USART0 外设与中断。
|
|
* @param[in] 无。
|
|
* @param[out] 无。
|
|
* @retval uint8_t Usart0::InitFault 故障码(0=USART 已使能,CTL0.UEN 回读置位)。
|
|
* @note 使用 `CONFIG_USART0_BAUDRATE` 配置串口参数,并使能 USART0 TBE 中断用于队列发送。
|
|
*/
|
|
uint8_t Usart0::init()
|
|
{
|
|
rcu_periph_clock_enable(RCU_USART0);
|
|
usart_deinit(USART0);
|
|
usart_baudrate_set(USART0, CONFIG_USART0_BAUDRATE);
|
|
usart_word_length_set(USART0, USART_WL_8BIT);
|
|
usart_stop_bit_set(USART0, USART_STB_1BIT);
|
|
usart_parity_config(USART0, USART_PM_NONE);
|
|
usart_receive_config(USART0, USART_RECEIVE_ENABLE);
|
|
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
|
|
usart_enable(USART0);
|
|
nvic_irq_enable(USART0_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 0U);
|
|
return ((USART_CTL0(USART0) & USART_CTL0_UEN) != 0U) ? kFaultOk : kFaultNotEnabled;
|
|
}
|
|
|
|
/* ----- 中断入口 / printf 重定向的 C 包装 --------------------------- */
|
|
/**
|
|
* @brief 标准库 fputc 重定向入口。
|
|
* @param[in] ch 待发送字符。
|
|
* @param[in] f 标准流指针。
|
|
* @param[out] 无。
|
|
* @retval int 返回待发送字符值。
|
|
* @note 该 C 包装函数内部转调 `Usart0::putChar()`。
|
|
*/
|
|
extern "C" int fputc(int ch, FILE *f) { return Usart0::putChar(ch, f); }
|
|
|
|
/**
|
|
* @brief USART0 中断向量入口。
|
|
* @param[in] 无。
|
|
* @param[out] 无。
|
|
* @retval 无返回值。
|
|
* @note 该 C 包装函数内部转调 `Usart0::onInterrupt()`。
|
|
*/
|
|
extern "C" void USART0_IRQHandler(void)
|
|
{
|
|
CpuLoad::isrEnter();
|
|
Usart0::onInterrupt();
|
|
CpuLoad::isrExit();
|
|
}
|