1. APM32F103 CAN总线配置实战指南
在嵌入式开发中,CAN总线因其高可靠性和实时性被广泛应用于汽车电子、工业控制等领域。最近我在一个工业控制项目中使用了APM32F103RCT6这款国产MCU,需要配置CAN总线与多个节点通信。经过实际调试,我发现虽然APM32F103与STM32F103在寄存器层面高度兼容,但在CAN配置上还是有些细节需要注意。
2. 硬件设计与引脚配置
2.1 CAN引脚映射方案选择
APM32F103的CAN控制器支持多种引脚映射方式,根据数据手册,主要分为三种情况:
- 默认映射:CAN_RX->PA11, CAN_TX->PA12
- Remap1映射:CAN_RX->PB8, CAN_TX->PB9
- Remap2映射:CAN_RX->PD0, CAN_TX->PD1
选择PB8/PB9作为CAN引脚的原因主要有:
- 项目PCB布局时PA11/PA12已被USB功能占用
- PB端口距离CAN收发器位置更近,布线更方便
- 避免与调试接口引脚冲突
2.2 GPIO初始化代码详解
c复制void CAN_GPIO_Init(void)
{
GPIO_Config_T configStruct;
// 使能相关时钟
RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_AFIO);
RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOB);
// 配置引脚重映射
GPIO_ConfigPinRemap(GPIO_REMAP1_CAN1);
// 配置TX引脚(PB9)
configStruct.pin = GPIO_PIN_9;
configStruct.mode = GPIO_MODE_AF_PP; // 复用推挽输出
configStruct.speed = GPIO_SPEED_50MHz; // 高速模式
GPIO_Config(GPIOB, &configStruct);
// 配置RX引脚(PB8)
configStruct.pin = GPIO_PIN_8;
configStruct.mode = GPIO_MODE_IN_PU; // 带上拉输入
GPIO_Config(GPIOB, &configStruct);
}
关键点说明:
- 必须首先使能AFIO时钟才能进行引脚重映射
- TX引脚配置为复用推挽输出,RX配置为带上拉输入
- GPIO速度设置为50MHz以适应CAN通信的高速要求
注意:如果使用默认引脚(PA11/PA12),则不需要调用GPIO_ConfigPinRemap()函数
3. CAN控制器初始化配置
3.1 波特率计算方法
CAN总线波特率由以下参数决定:
- APB1时钟频率(PCLK1,通常为36MHz)
- 分频系数(Prescaler)
- 时间段1(BS1)
- 时间段2(BS2)
- 同步跳转宽度(SJW)
计算公式:
code复制波特率 = PCLK1 / (Prescaler * (1 + BS1 + BS2))
以常见的500Kbps为例:
code复制PCLK1 = 36MHz
目标波特率 = 500Kbps
选择 Prescaler=6, BS1=5, BS2=2
实际波特率 = 36MHz / (6*(1+5+2)) = 500Kbps
3.2 CAN初始化完整代码
c复制void CAN1_Init(void)
{
CAN_Config_T canConfig;
CAN_FilterConfig_T filterConfig;
// 使能CAN时钟
RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_CAN1);
// CAN参数配置
canConfig.autoBusOffManage = ENABLE; // 自动总线关闭管理
canConfig.autoRetransmission = DISABLE; // 禁用自动重传
canConfig.autoWakeUpMode = DISABLE; // 禁用自动唤醒
canConfig.nonAutoRetransmission = ENABLE; // 启用非自动重传
canConfig.recFIFOLockMode = DISABLE; // 禁用接收FIFO锁定
canConfig.timeTriggerMode = DISABLE; // 禁用时间触发模式
canConfig.operatingMode = CAN_OPERATING_MODE_NORMAL; // 正常工作模式
// 波特率配置(500Kbps)
canConfig.baudRatePrescaler = 6;
canConfig.timeSegment1 = CAN_TIME_SEGMENT1_5TQ;
canConfig.timeSegment2 = CAN_TIME_SEGMENT2_2TQ;
canConfig.syncJumpWidth = CAN_SYNC_JUMP_WIDTH_1TQ;
CAN_Config(CAN1, &canConfig);
// 过滤器配置(接收所有消息)
filterConfig.filterNumber = 0;
filterConfig.filterMode = CAN_FILTER_MODE_ID_MASK;
filterConfig.filterScale = CAN_FILTER_SCALE_32BIT;
filterConfig.filterIDHigh = 0x0000;
filterConfig.filterIDLow = 0x0000;
filterConfig.filterMaskIDHigh = 0x0000;
filterConfig.filterMaskIDLow = 0x0000;
filterConfig.filterFIFOAssignment = CAN_FILTER_FIFO0;
filterConfig.filterActivation = ENABLE;
CAN_ConfigFilter(&filterConfig);
}
4. CAN消息收发实现
4.1 发送消息函数
c复制uint8_t CAN_SendMsg(uint32_t id, uint8_t* data, uint8_t len)
{
CAN_TxMessage_T txMsg;
uint8_t mailbox;
// 检查参数有效性
if(len > 8) return 1;
// 配置发送消息
txMsg.extID = id;
txMsg.idType = CAN_ID_EXTENDED; // 使用扩展ID
txMsg.frameType = CAN_FRAME_TYPE_DATA; // 数据帧
txMsg.dlc = len; // 数据长度
// 拷贝数据
for(uint8_t i=0; i<len; i++) {
txMsg.data[i] = data[i];
}
// 发送消息
mailbox = CAN_TxMessage(CAN1, &txMsg);
// 等待发送完成
while(CAN_ReadTxMailboxStatus(CAN1, mailbox) != CAN_TX_STATUS_OK);
return 0;
}
4.2 接收消息处理
c复制void CAN_ReceiveProcess(void)
{
CAN_RxMessage_T rxMsg;
// 检查是否有消息
if(CAN_ReadRxFIFOStatus(CAN1, CAN_RX_FIFO0) == SET)
{
// 读取消息
CAN_ReadRxMessage(CAN1, CAN_RX_FIFO0, &rxMsg);
// 处理消息
printf("Received ID:0x%08X, Len:%d, Data:", rxMsg.extID, rxMsg.dlc);
for(uint8_t i=0; i<rxMsg.dlc; i++) {
printf("%02X ", rxMsg.data[i]);
}
printf("\n");
// 释放FIFO
CAN_ReleaseRxFIFO(CAN1, CAN_RX_FIFO0);
}
}
5. 常见问题与调试技巧
5.1 CAN通信失败排查步骤
-
检查物理层连接
- 确认CAN_H和CAN_L接线正确
- 测量终端电阻(通常为120Ω)
- 检查CAN收发器供电是否正常
-
检查软件配置
- 确认GPIO时钟和CAN时钟已使能
- 验证波特率计算是否正确
- 检查过滤器配置是否过于严格
-
使用示波器观察波形
- 测量CAN_TX引脚是否有波形输出
- 观察CAN总线差分信号质量
5.2 实际调试中的经验总结
-
波特率同步问题
- 确保所有节点波特率设置一致
- 适当增加同步跳转宽度(SJW)可以提高容错性
-
总线负载控制
- 避免单帧数据量过大(建议≤8字节)
- 合理设置消息发送间隔
-
错误处理机制
- 实现CAN错误中断回调函数
- 监控错误计数器值
c复制void CAN1_IRQHandler(void)
{
if(CAN_ReadIntFlag(CAN1, CAN_INT_ERR) != RESET)
{
// 读取错误状态
uint8_t errCode = CAN_ReadErrorCode(CAN1);
uint8_t errStatus = CAN_ReadLastErrorCode(CAN1);
// 错误处理逻辑
printf("CAN Error! Code:0x%02X, Status:0x%02X\n", errCode, errStatus);
// 清除中断标志
CAN_ClearIntFlag(CAN1, CAN_INT_ERR);
}
}
6. 性能优化建议
-
使用DMA传输
- 配置DMA自动搬运CAN接收数据
- 减少CPU中断处理开销
-
双FIFO利用
- 将高优先级消息分配到FIFO0
- 普通消息分配到FIFO1
-
定时发送优化
- 对于周期性消息,使用硬件定时器触发发送
- 避免软件延时导致的时序抖动
通过以上配置和优化,APM32F103的CAN总线通信可以达到工业级稳定性和实时性要求。在实际项目中,建议先使用CAN分析仪验证通信质量,再逐步增加节点数量。
