本篇总结
使用 CAN,收发数据帧并通过中断打印报文。
🔑 KEIL MDK、UART调试、CAN
W55MH32L 内置 1 个 CAN 控制器,支持 CAN 2.0A/B 协议标准,最高速率 1Mbps。本示例演示 CAN 控制器的初始化、过滤器配置,每秒发送数据帧,接收中断打印报文内容。
前置工具准备
开始本例程前,请确认以下工具已安装:
- WIZ UartTool V1.0:串口收发测试工具:点击下载
硬件连接
本例程使用 CAN1 的 TX(PA12) 和 RX(PA11) 引脚。在环回模式下无需外部 CAN 收发器即可自测。如需连接外部 CAN 总线,需通过 CAN 收发器芯片(如 TJA1050)连接到 CANH/CANL 总线。USART1 用于输出调试信息。
| 引脚 | 功能 | 方向 | 模式 |
| PA12 | CAN1_TX | 开发板 → 收发器 | 复用推挽输出 (AF_PP) |
| PA11 | CAN1_RX | 收发器 → 开发板 | 上拉输入 (IPU) |
| PA9 | USART1_TX | 开发板 → PC | 复用推挽输出 (AF_PP) |
| PA10 | USART1_RX | PC → 开发板 | 浮空输入 (IN_FLOATING) |
工程结构
工程位于 SDK 内 1.SDK\ModuleDemo\CAN\,核心文件如下:
CAN/
├── USER/
│ ├── main.c ← CAN初始化 + 收发测试 + printf重定向
│ ├── w55mh32_conf.h ← 外设头文件集中包含
│ ├── w55mh32_it.c/h ← 中断服务函数(CAN接收中断)
│ ├── system_w55mh32.c/h← 系统初始化(时钟配置)
│ └── startup_w55mh32.s ← 启动文件
└── SYSTEM/
└── delay/
└── delay.c/h ← SysTick 延时函数
步骤1:CAN 初始化与过滤器配置
函数:CAN_Configuration() — 配置 PA12(TX)/PA11(RX) 引脚、CAN 波特率为 1Mbps、过滤器接收所有标准帧。
void CAN_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
/* PA12: CAN_TX → 复用推挽输出 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* PA11: CAN_RX → 上拉输入 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* CAN 基本参数:1Mbps / 正常模式 */
CAN_InitStructure.CAN_TTCM = DISABLE;
CAN_InitStructure.CAN_ABOM = DISABLE;
CAN_InitStructure.CAN_AWUM = DISABLE;
CAN_InitStructure.CAN_NART = DISABLE;
CAN_InitStructure.CAN_RFLM = DISABLE;
CAN_InitStructure.CAN_TXFP = DISABLE;
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
/* 波特率 = 36MHz / (6*(1+3+2)) = 36MHz/36 = 1Mbps */
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_3tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_2tq;
CAN_InitStructure.CAN_Prescaler = 6;
CAN_Init(CAN1, &CAN_InitStructure);
/* 过滤器:接收所有标准帧 */
CAN_FilterInitStructure.CAN_FilterNumber = 1;
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
}
波特率计算公式:CAN_BaudRate = APB1_CLK / (Prescaler × (1 + BS1 + BS2))。APB1 = 36MHz,Prescaler=6,BS1=3tq,BS2=2tq,则波特率 = 36M / (6 × (1+3+2)) = 36M / 36 = 1Mbps。过滤器设置为 IdMask 模式且掩码全 0,表示接收所有 ID 的报文。
如果你想自定义
- 改波特率 → 修改
CAN_Prescaler、CAN_BS1、CAN_BS2 参数
- 改工作模式 →
CAN_Mode_LoopBack(环回自测)、CAN_Mode_Silent(静默监听)、CAN_Mode_Normal(正常通信)
- 过滤特定ID → 设置
CAN_FilterIdHigh/Low 和掩码,只接收指定 ID 的报文
步骤2:CAN 发送与接收中断
CAN_Send() 构造标准数据帧并发送,USB_LP_CAN1_RX0_IRQHandler() 在接收 FIFO0 有报文时触发中断,读取并打印报文内容。
/* CAN 发送函数 */
void CAN_Send(uint8_t *data, uint8_t len)
{
CanTxMsg TxMessage;
TxMessage.StdId = 0x321;
TxMessage.IDE = CAN_ID_STD;
TxMessage.RTR = CAN_RTR_DATA;
TxMessage.DLC = len;
for (uint8_t i = 0; i < len; i++)
TxMessage.Data[i] = data[i];
CAN_Transmit(CAN1, &TxMessage);
}
/* CAN 接收中断服务函数 */
void USB_LP_CAN1_RX0_IRQHandler(void)
{
CanRxMsg RxMessage;
if (CAN_GetITStatus(CAN1, CAN_IT_FMP0) != RESET)
{
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
printf("CAN Recv: ID=0x%X, DLC=%d, Data=", RxMessage.StdId, RxMessage.DLC);
for (uint8_t i = 0; i < RxMessage.DLC; i++)
printf("%02X ", RxMessage.Data[i]);
printf("\n");
}
}
发送时构造 CanTxMsg 结构体设置 ID(0x321)、帧类型(标准数据帧)、数据长度(8字节)和数据内容,调用 CAN_Transmit() 发送。接收中断通过 CAN_Receive() 读取 FIFO0 中的报文。
步骤3:主函数逻辑
函数:main() — 初始化 CAN 和串口,使能接收中断,每秒发送一帧数据并递增首字节。
int main(void)
{
RCC_ClocksTypeDef clocks;
delay_init();
UART_Configuration(115200);
RCC_GetClocksFreq(&clocks);
printf("CAN LoopBack Test.\n");
CAN_Configuration();
CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
/* NVIC 配置 */
NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn);
uint8_t sendData[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
while (1)
{
CAN_Send(sendData, 8);
printf("CAN Send: ID=0x321, Data=");
for (uint8_t i = 0; i < 8; i++)
printf("%02X ", sendData[i]);
printf("\n");
delay_ms(1000);
sendData[0]++;
}
}
主循环每秒发送一帧 8 字节数据帧(ID=0x321),首字节递增。在环回模式下,发送的报文会被自身接收,触发接收中断打印收到的数据。
编译与下载
Keil MDK 中打开 CAN.uvprojx,F7 编译,确认 0 Error(s)。连接 DEBUG USB 口到 PC,F8 下载。
运行验证
打开 WIZ UartTool V1.0,波特率 115200 / 8N1。下载固件并按 RESET 后,串口每秒打印发送和接收的 CAN 报文。在环回模式下,发送的数据会被自身接收,发送和接收内容一致。如使用 CAN 分析仪软件,可看到总线上的数据帧。
CAN 通信测试结果:串口打印发送/接收报文,CAN 分析仪显示总线数据帧
常见问题
Q: CAN 通信无数据收发
确认 CAN 模式设置正确。环回模式(LoopBack)下无需外部收发器即可自测。正常模式(Normal)下必须连接 CAN 收发器芯片和终端电阻(120Ω)。检查波特率参数是否与总线一致。
Q: 接收中断不触发
确认过滤器配置正确(掩码全 0 表示接收所有帧)。检查 NVIC 中断是否使能(CAN_IT_FMP0),以及中断服务函数名是否正确(USB_LP_CAN1_RX0_IRQHandler)。
Q: 波特率不匹配导致通信失败
CAN 波特率 = APB1_CLK / (Prescaler × (1 + BS1 + BS2))。确保所有节点使用相同的波特率。W55MH32L 的 APB1 时钟默认 36MHz,可通过 RCC_GetClocksFreq 确认。
总结
本文基于 W55MH32L 的 CAN 控制器实现了数据帧的发送与中断接收:
- CAN 初始化:配置 PA12(TX)/PA11(RX) 引脚,波特率 1Mbps,正常/环回模式可切换
- 过滤器配置:IdMask 模式,掩码全 0 接收所有 ID 的标准数据帧
- 每秒发送 8 字节数据帧(ID=0x321),接收中断中读取并打印报文内容
- 支持 CAN 2.0A/B 协议标准,适用于汽车电子和工业控制领域的高可靠性通信