对于从事嵌入式开发的朋友来说,CAN总线通信的需求十分常见。本文以STM32F105系列微控制器为例,基于标准外设库,使用CAN1外设,提供一套完整的双机通信代码实现方案。整个流程涵盖初始化、数据发送、接收以及双机配对的关键配置,下面我们逐步展开。

1. CAN总线初始化
初始化是CAN通信的基础环节,涉及GPIO配置、中断设置、工作模式与波特率设定以及过滤器配置,每一步都不可或缺。请看具体代码实现:
#include "stm32f10x.h"
#include "stm32f10x_can.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "misc.h"
// CAN初始化结构体
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
CanTxMsg TxMessage;
CanRxMsg RxMessage;
void CAN_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIO和CAN时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
// 配置CAN引脚: PA11->RX, PA12->TX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // TX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 推挽复用输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // RX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void CAN_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 配置CAN接收中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void CAN_Mode_Config(void)
{
CAN_DeInit(CAN1);
CAN_StructInit(&CAN_InitStructure);
// CAN工作模式配置
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; // 禁止FIFO锁定
CAN_InitStructure.CAN_TXFP = DISABLE; // 禁止发送FIFO优先级
// 配置波特率
// CAN波特率 = APB1时钟 / ((CAN_BS1 + CAN_BS2 + 1) * CAN_Prescaler)
// 假设APB1时钟为36MHz,波特率设为500kbps
CAN_InitStructure.CAN_BS1 = CAN_BS1_9tq; // 时间段1
CAN_InitStructure.CAN_BS2 = CAN_BS2_4tq; // 时间段2
CAN_InitStructure.CAN_Prescaler = 6; // 分频系数
// 总时间片 = 1 + 9 + 4 = 14
// 波特率 = 36MHz / (14 * 6) = 428.57kHz (接近500k)
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; // 正常工作模式
CAN_Init(CAN1, &CAN_InitStructure);
// 配置CAN过滤器
CAN_FilterInitStructure.CAN_FilterNumber = 0; // 使用过滤器0
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; // 掩码模式
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; // 32位模式
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000; // 要过滤的ID高位
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000; // 要过滤的ID低位
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000; // 掩码高位
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000; // 掩码低位
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0; // 关联到FIFO0
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; // 启用过滤器
CAN_FilterInit(&CAN_FilterInitStructure);
// 使能CAN接收中断
CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
}
2. CAN数据发送函数
发送部分的逻辑非常清晰:先构建发送帧结构体,填充ID、数据长度及具体内容,然后调用发送接口并等待传输完成。注意增加超时处理,避免程序陷入死等状态。
uint8_t CAN_Send_Msg(uint8_t* msg, uint8_t len, uint32_t id)
{
uint8_t i = 0;
uint8_t transmit_mailbox = 0;
TxMessage.StdId = id; // 标准ID
TxMessage.ExtId = 0x00; // 扩展ID
TxMessage.IDE = CAN_Id_Standard; // 标准帧
TxMessage.RTR = CAN_RTR_Data; // 数据帧
TxMessage.DLC = len; // 数据长度(最大8字节)
// 填充数据
for(i = 0; i < len; i++)
{
TxMessage.Data[i] = msg[i];
}
// 发送数据
transmit_mailbox = CAN_Transmit(CAN1, &TxMessage);
// 等待发送完成
i = 0;
while((CAN_TransmitStatus(CAN1, transmit_mailbox) != CANTXOK) && (i < 0xFF))
{
i++;
}
if(i >= 0xFF)
{
return 1; // 发送失败
}
return 0; // 发送成功
}
3. CAN数据接收中断服务函数
接收采用中断方式,效率更高。进入中断后先检查标志位,再读取FIFO中的数据存入缓冲区,记得清除中断标志。缓冲区内容可后续处理,例如转发至串口或触发其他控制逻辑。
void USB_LP_CAN1_RX0_IRQHandler(void)
{
static uint8_t receive_buffer[8];
if(CAN_GetITStatus(CAN1, CAN_IT_FMP0) != RESET)
{
// 读取接收到的数据
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
// 清除中断标志
CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);
// 处理接收到的数据
if(RxMessage.IDE == CAN_Id_Standard)
{
uint8_t i;
// 复制接收到的数据
for(i = 0; i < RxMessage.DLC; i++)
{
receive_buffer[i] = RxMessage.Data[i];
}
// 在这里处理接收到的数据
// 例如:将数据发送到串口或进行其他处理
// Process_Received_Data(receive_buffer, RxMessage.DLC, RxMessage.StdId);
}
}
}
4. 主函数示例
主函数中演示了周期性发送的典型用法:先初始化系统时钟和CAN外设,然后在主循环中每隔1秒发送一帧数据。实际项目可根据需求调整发送频率或添加条件触发机制。
int main(void)
{
uint8_t send_data[8] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
uint32_t message_id = 0x123; // CAN消息ID
// 初始化系统时钟
SystemInit();
// 配置GPIO
CAN_GPIO_Config();
// 配置NVIC中断
CAN_NVIC_Config();
// 配置CAN模式
CAN_Mode_Config();
// 主循环
while(1)
{
// 每隔1秒发送一次数据
Delay(1000); // 自定义延时函数
// 发送CAN消息
CAN_Send_Msg(send_data, 8, message_id);
// 可以在这里添加其他处理逻辑
}
}
5. 双机通信配置说明
要使两个设备正常通信,ID分配和过滤器设置是关键。以设备A作为发送方、设备B作为接收方为例:
设备A配置(发送设备):
// 设置设备A的ID为0x100
uint32_t device_a_id = 0x100;
设备B配置(接收设备):
// 配置过滤器只接收来自设备A的消息
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0100 << 5; // ID高16位
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000; // ID低16位
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0xFFFF; // 掩码高位
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000; // 掩码低位
6. 完整的工程配置步骤
硬件连接:
- CAN_H 连接 CAN_H
- CAN_L 连接 CAN_L
- 两个设备的GND相连
- 在总线的两端接120Ω终端电阻
软件配置:
// 波特率计算
// 假设系统时钟为72MHz,APB1时钟为36MHz
// 500kbps配置:BS1=9, BS2=4, Prescaler=6
// 1Mbps配置:BS1=9, BS2=4, Prescaler=3
编译和下载:
- 使用Keil MDK或STM32CubeIDE
- 确保包含正确的头文件
- 配置正确的系统时钟
注意事项
- 终端电阻:必须在CAN总线的两端各接一个120Ω的终端电阻
- 波特率:两个设备的波特率必须相同
- 中断优先级:根据实际应用需求合理配置中断优先级
- 错误处理:在实际应用中应添加错误检测和重发机制
- ID冲突:确保每个设备的CAN ID唯一,避免冲突
