要在 M0A23 系列微控制器上实现 UART 和 CAN 之间的桥接传输,需通过软件逻辑将数据在两个接口间双向转发。以下是完整的实现步骤和代码示例:
1. 系统初始化
1.1 时钟配置
#include "NuMicro.h" // 包含 M0A23 库头文件
void SYS_Init(void) {
// 启用 UART0 和 CAN0 时钟
CLK_EnableModuleClock(UART0_MODULE);
CLK_EnableModuleClock(CAN0_MODULE);
// 设置 UART0 时钟源为 PCLK
CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UART0SEL_PCLK, 0);
// 设置 CAN0 时钟源为 PCLK
CLK_SetModuleClock(CAN0_MODULE, CLK_CLKSEL0_CAN0SEL_PCLK, 0);
}
2. UART 初始化
配置 UART 为中断接收模式:
void UART0_Init(void) {
// 复用 UART0 引脚 (PB.0-TX, PB.1-RX)
SYS->GPB_MFPL &= ~(SYS_GPB_MFPL_PB0MFP_Msk | SYS_GPB_MFPL_PB1MFP_Msk);
SYS->GPB_MFPL |= (SYS_GPB_MFPL_PB0MFP_UART0_TX | SYS_GPB_MFPL_PB1MFP_UART0_RX);
// UART 配置:115200波特率, 8数据位, 无校验, 1停止位
UART_Open(UART0, 115200);
// 启用接收中断
UART_EnableInt(UART0, UART_INTEN_RDAIEN_Msk);
NVIC_EnableIRQ(UART0_IRQn);
}
3. CAN 初始化
配置 CAN 控制器和接收中断:
void CAN0_Init(void) {
// 复用 CAN0 引脚 (PA.11-RX, PA.12-TX)
SYS->GPA_MFPH &= ~(SYS_GPA_MFPH_PA11MFP_Msk | SYS_GPA_MFPH_PA12MFP_Msk);
SYS->GPA_MFPH |= (SYS_GPA_MFPH_PA11MFP_CAN0_RXD | SYS_GPA_MFPH_PA12MFP_CAN0_TXD);
// CAN 配置:500kbps, 正常模式
CAN_Open(CAN0, 500000, CAN_NORMAL_MODE);
// 配置接收过滤器(接收所有标准ID)
CAN_SetRxMsg(CAN0, 0, CAN_STD_ID, 0x7FF);
// 启用接收中断
CAN_EnableInt(CAN0, CAN_CON_IE_Msk | CAN_CON_SIE_Msk);
NVIC_EnableIRQ(CAN0_IRQn);
}
4. 中断服务函数
4.1 UART 接收中断
volatile uint8_t uart_rx_buf[64];
volatile uint16_t uart_rx_count = 0;
void UART0_IRQHandler(void) {
// 检查接收数据中断标志
if (UART_GET_INT_FLAG(UART0, UART_INTSTS_RDAIF_Msk)) {
// 读取一个字节并存入缓存
uart_rx_buf[uart_rx_count++] = UART_READ(UART0);
// 检查帧结束(例如换行符或长度超限)
if (uart_rx_buf[uart_rx_count-1] == 'n' || uart_rx_count >= 64) {
ForwardUARTtoCAN(); // 触发转发到 CAN
uart_rx_count = 0; // 重置缓存
}
}
}
4.2 CAN 接收中断
volatile STR_CANMSG_T can_rx_msg; // CAN 消息结构体
void CAN0_IRQHandler(void) {
// 检查接收中断标志
if (CAN_GetIntFlag(CAN0, CAN_INTSTS_RXOK_Msk)) {
CAN_Receive(CAN0, 0, &can_rx_msg); // 读取消息
ForwardCANtoUART(); // 转发到 UART
CAN_ClrIntFlag(CAN0, CAN_INTSTS_RXOK_Msk); // 清除标志
}
}
5. 数据转发逻辑
5.1 UART → CAN 转发
void ForwardUARTtoCAN(void) {
STR_CANMSG_T txMsg;
// 配置 CAN 消息(标准帧)
txMsg.IdType = CAN_STD_ID;
txMsg.Id = 0x123; // 目标 CAN ID
txMsg.DLC = uart_rx_count; // 数据长度
memcpy(txMsg.Data, uart_rx_buf, uart_rx_count);
// 发送消息(邮箱0)
if (CAN_Transmit(CAN0, 0, &txMsg) == FALSE) {
// 错误处理:重试或丢弃数据
}
}
5.2 CAN → UART 转发
void ForwardCANtoUART(void) {
// 将 CAN 数据通过 UART 发送
for (int i = 0; i < can_rx_msg.DLC; i++) {
UART_WRITE(UART0, can_rx_msg.Data[i]);
}
// 可选:添加帧结束符(如换行)
UART_WRITE(UART0, 'n');
}
6. 主函数
int main(void) {
SYS_Init(); // 初始化系统
UART0_Init(); // 初始化 UART
CAN0_Init(); // 初始化 CAN
while (1) {
// 主循环可添加其他任务
__WFI(); // 进入低功耗模式
}
}
关键优化方向
流控机制
- UART 端:添加硬件流控(RTS/CTS)或软件流控(XON/XOFF)。
- CAN 端:检查邮箱状态,避免因总线繁忙丢包。
超时处理
在 ForwardUARTtoCAN() 中添加超时检查,避免未收到结束符导致缓存阻塞。
双缓冲策略
为 UART/CAN 分别设计双缓存(如 Ping-Pong Buffer),实现接收和转发并行。
错误处理
在中断中检测 CAN 总线错误(CAN_INTSTS_ERRI_Msk),并复位控制器。
物理层注意事项
- CAN 总线:需连接 CAN 收发器(如 TJA1050),添加 120Ω 终端电阻。
- UART 电平:若连接 PC,需 USB-TTL 转换器(如 CH340)。
- 防冲突:避免同时双向转发造成数据循环,可添加方向控制逻辑。
通过上述步骤,M0A23 即可稳定桥接 UART 和 CAN 数据流,适用于工业网关、车载设备调试等场景。