STM32 CAN总线通讯实验
一、实验目的与原理
1.1 实验目的
掌握STM32 CAN控制器的工作原理与配置方法
学会使用CAN总线进行节点间通信
理解CAN协议帧结构(数据帧、远程帧、错误帧、过载帧)
掌握CAN总线的滤波与屏蔽技术
实现多节点CAN网络通信
1.2 CAN总线原理
CAN总线拓扑结构:
┌─────────┐
│ 终端电阻 │
│ 120Ω │
└────┬────┘
│
┌────────┼────────┐
│ │ │
┌───▼───┐ ┌───▼───┐ ┌───▼───┐
│ CAN节点 │ │ CAN节点 │ │ CAN节点 │
│ (STM32) │ │ (STM32) │ │ (STM32) │
└───────┘ └───────┘ └───────┘
│CANH │CANH │CANH
│CANL │CANL │CANL
└────────┴────────┘
1.3 CAN帧类型对比
帧类型
用途
特点
数据帧
发送数据
包含ID、数据、CRC等
远程帧
请求数据
无数据场,请求特定ID发送数据
错误帧
错误通知
检测到错误时发送
过载帧
延迟下一帧
接收节点未准备好时发送
二、硬件设计
2.1 硬件清单
模块
型号
数量
说明
主控
STM32F103C8T6
2
CAN节点
CAN收发器
TJA1050
2
CAN收发器
终端电阻
120Ω
2
总线匹配电阻
USB-CAN适配器
USBCAN-II
1
PC端监控(可选)
杜邦线
–
若干
连接线路
2.2 硬件连接
/* CAN硬件连接定义 */
// STM32F103C8T6 CAN引脚
#define CAN_TX_PIN GPIO_Pin_12 // PB12 - CAN1_TX
#define CAN_RX_PIN GPIO_Pin_11 // PB11 - CAN1_RX
#define CAN_GPIO_PORT GPIOB
// TJA1050连接
// TJA1050_TXD -> PB12 (CAN1_TX)
// TJA1050_RXD -> PB11 (CAN1_RX)
// TJA1050_VCC -> 5V
// TJA1050_GND -> GND
// TJA1050_CANH -> CAN总线CANH
// TJA1050_CANL -> CAN总线CANL
// 终端电阻连接
// 总线两端各接120Ω电阻(CANH与CANL之间)
2.3 波特率计算
/* CAN波特率配置 */
// APB1时钟 = 36MHz
// 目标波特率 = 500kbps
// 计算公式:波特率 = APB1_CLK / ((Prescaler) * (BS1 + BS2 + 1))
// 配置示例:500kbps
#define CAN_PRESCALER 6 // 预分频器
#define CAN_BS1 8 // 时间段1 (8个时间单元)
#define CAN_BS2 1 // 时间段2 (1个时间单元)
// 波特率 = 36MHz / (6 * (8 + 1 + 1)) = 500kbps
// 其他常用波特率配置
typedef struct {
uint32_t baudrate; // 波特率
uint16_t prescaler; // 预分频器
uint8_t bs1; // 时间段1
uint8_t bs2; // 时间段2
} CAN_BaudrateConfig;
CAN_BaudrateConfig can_baudrates[] = {
{1000, 3, 8, 1}, // 1Mbps
{500, 6, 8, 1}, // 500kbps
{250, 12, 8, 1}, // 250kbps
{125, 24, 8, 1}, // 125kbps
{100, 30, 8, 1}, // 100kbps
{50, 60, 8, 1} // 50kbps
};
三、软件设计(标准外设库)
3.1 CAN初始化
/* CAN初始化代码 */
#include "stm32f10x.h"
#include "stm32f10x_can.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "misc.h"
#include <stdio.h>
// CAN句柄
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
CanTxMsg TxMessage;
CanRxMsg RxMessage;
// 全局变量
volatile uint8_t can_rx_flag = 0;
volatile uint8_t can_tx_flag = 0;
volatile uint8_t can_error_flag = 0;
uint32_t can_error_code = 0;
/* CAN GPIO初始化 */
void CAN_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 配置CAN引脚
GPIO_InitStructure.GPIO_Pin = CAN_TX_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_Init(CAN_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = CAN_RX_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(CAN_GPIO_PORT, &GPIO_InitStructure);
}
/* CAN控制器初始化 */
void CAN_Config(uint32_t baudrate) {
// 使能CAN1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
// 复位CAN控制器
CAN_DeInit(CAN1);
// 配置CAN参数
CAN_InitStructure.CAN_TTCM = DISABLE; // 禁止时间触发通信
CAN_InitStructure.CAN_ABOM = ENABLE; // 使能自动离线管理
CAN_InitStructure.CAN_AWUM = ENABLE; // 使能自动唤醒
CAN_InitStructure.CAN_NART = DISABLE; // 使能自动重传
CAN_InitStructure.CAN_RFLM = DISABLE; // 禁止接收FIFO锁定
CAN_InitStructure.CAN_TXFP = DISABLE; // 禁止发送FIFO优先级
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; // 正常工作模式
// 配置波特率
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq; // 重新同步跳跃宽度
CAN_InitStructure.CAN_BS1 = CAN_BS1_8tq; // 时间段1
CAN_InitStructure.CAN_BS2 = CAN_BS2_1tq; // 时间段2
CAN_InitStructure.CAN_Prescaler = 6; // 预分频器,500kbps
// 初始化CAN
if (CAN_Init(CAN1, &CAN_InitStructure) == CAN_InitStatus_Failed) {
printf("CAN Initialization Failed!\n");
return;
}
printf("CAN Initialized at %d kbps\n", baudrate/1000);
}
/* CAN过滤器配置 */
void CAN_Filter_Config(void) {
// 配置过滤器0
CAN_FilterInitStructure.CAN_FilterNumber = 0; // 过滤器0
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; // 掩码模式
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; // 32位
// 接收所有标准ID(11位)
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000; // 标准ID高16位
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000; // 标准ID低16位
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000; // 掩码高16位(全0表示不过滤)
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000; // 掩码低16位
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; // 关联到FIFO0
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; // 使能过滤器
CAN_FilterInit(&CAN_FilterInitStructure);
printf("CAN Filter Configured\n");
}
/* CAN中断配置 */
void CAN_NVIC_Config(void) {
NVIC_InitTypeDef NVIC_InitStructure;
// 配置CAN接收中断
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);
// 使能CAN接收中断
CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE); // FIFO0消息挂起中断
CAN_ITConfig(CAN1, CAN_IT_TME, ENABLE); // 发送邮箱空中断
CAN_ITConfig(CAN1, CAN_IT_ERR, ENABLE); // 错误中断
printf("CAN Interrupt Configured\n");
}
3.2 CAN发送与接收
/* CAN发送函数 */
uint8_t CAN_Send_Message(uint32_t id, uint8_t *data, uint8_t length) {
uint8_t mailbox;
uint16_t timeout = 0xFFFF;
// 配置发送消息
TxMessage.StdId = id; // 标准ID(11位)
TxMessage.ExtId = 0; // 扩展ID(不使用)
TxMessage.IDE = CAN_Id_Standard; // 标准帧
TxMessage.RTR = CAN_RTR_Data; // 数据帧
TxMessage.DLC = length; // 数据长度(0-8字节)
// 填充数据
for (uint8_t i = 0; i < length; i++) {
TxMessage.Data[i] = data[i];
}
// 选择空邮箱并发送
mailbox = CAN_Transmit(CAN1, &TxMessage);
// 等待发送完成
while ((CAN_TransmitStatus(CAN1, mailbox) != CAN_TxStatus_Ok) && timeout) {
timeout--;
}
if (timeout == 0) {
printf("CAN Send Timeout!\n");
return 0;
}
printf("CAN Message Sent: ID=0x%03X, Length=%d\n", id, length);
return 1;
}
/* CAN接收中断服务函数 */
void USB_LP_CAN1_RX0_IRQHandler(void) {
if (CAN_GetITStatus(CAN1, CAN_IT_FMP0) != RESET) {
// 接收消息
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
// 设置接收标志
can_rx_flag = 1;
// 清除中断标志
CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);
}
// 发送完成中断
if (CAN_GetITStatus(CAN1, CAN_IT_TME) != RESET) {
can_tx_flag = 1;
CAN_ClearITPendingBit(CAN1, CAN_IT_TME);
}
// 错误中断
if (CAN_GetITStatus(CAN1, CAN_IT_ERR) != RESET) {
can_error_flag = 1;
can_error_code = CAN_GetLastErrorCode(CAN1);
CAN_ClearITPendingBit(CAN1, CAN_IT_ERR);
}
}
/* 处理接收到的CAN消息 */
void Process_CAN_Message(void) {
if (can_rx_flag) {
printf("CAN Message Received:\n");
printf(" ID: 0x%03X\n", RxMessage.StdId);
printf(" IDE: %s\n", (RxMessage.IDE == CAN_Id_Standard) ? "Standard" : "Extended");
printf(" RTR: %s\n", (RxMessage.RTR == CAN_RTR_Data) ? "Data" : "Remote");
printf(" DLC: %d bytes\n", RxMessage.DLC);
if (RxMessage.DLC > 0) {
printf(" Data: ");
for (uint8_t i = 0; i < RxMessage.DLC; i++) {
printf("%02X ", RxMessage.Data[i]);
}
printf("\n");
}
can_rx_flag = 0;
}
}
四、实验内容
4.1 基础实验:自发自收
/* 实验1:CAN自发自收 */
void CAN_Loopback_Test(void) {
uint8_t test_data[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
printf("=== CAN Loopback Test ===\n");
// 配置为回环模式
CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;
CAN_Init(CAN1, &CAN_InitStructure);
// 发送测试数据
if (CAN_Send_Message(0x123, test_data, 8)) {
printf("Loopback test data sent\n");
}
// 延时等待接收
for (volatile uint32_t i = 0; i < 1000000; i++);
// 检查接收数据
if (can_rx_flag) {
Process_CAN_Message();
printf("Loopback test PASSED!\n");
} else {
printf("Loopback test FAILED!\n");
}
}
4.2 双机通信实验
/* 实验2:双机CAN通信 */
typedef enum {
NODE_A = 0x100, // 节点A的ID
NODE_B = 0x200 // 节点B的ID
} NodeID;
// 节点A程序
void NodeA_Task(void) {
uint8_t tx_data[8];
static uint8_t counter = 0;
// 发送数据到节点B
tx_data[0] = counter++;
tx_data[1] = 0xAA;
tx_data[2] = 0xBB;
tx_data[3] = 0xCC;
CAN_Send_Message(NODE_B, tx_data, 4);
// 处理接收到的数据
Process_CAN_Message();
}
// 节点B程序
void NodeB_Task(void) {
uint8_t tx_data[8];
static uint8_t counter = 0;
// 发送数据到节点A
tx_data[0] = counter++;
tx_data[1] = 0xDD;
tx_data[2] = 0xEE;
tx_data[3] = 0xFF;
CAN_Send_Message(NODE_A, tx_data, 4);
// 处理接收到的数据
Process_CAN_Message();
}
4.3 多节点通信实验
/* 实验3:多节点CAN网络 */
#define MAX_NODES 5
#define BROADCAST_ID 0x7FF // 广播ID
typedef struct {
uint32_t node_id; // 节点ID
uint8_t node_type; // 节点类型
uint8_t status; // 节点状态
uint32_t last_heartbeat; // 最后心跳时间
} CAN_Node;
CAN_Node can_network[MAX_NODES];
/* 发送心跳包 */
void Send_Heartbeat(uint32_t my_node_id) {
uint8_t heartbeat_data[8];
heartbeat_data[0] = 0x01; // 心跳包类型
heartbeat_data[1] = my_node_id & 0xFF;
heartbeat_data[2] = (my_node_id >> 8) & 0xFF;
heartbeat_data[3] = 0x00; // 状态正常
CAN_Send_Message(BROADCAST_ID, heartbeat_data, 4);
}
/* 处理网络消息 */
void Process_Network_Message(void) {
if (can_rx_flag) {
if (RxMessage.StdId == BROADCAST_ID) {
// 广播消息
if (RxMessage.Data[0] == 0x01) {
// 心跳包
uint32_t node_id = RxMessage.Data[1] | (RxMessage.Data[2] << 8);
printf("Heartbeat from node 0x%04X\n", node_id);
// 更新节点状态
for (uint8_t i = 0; i < MAX_NODES; i++) {
if (can_network[i].node_id == node_id) {
can_network[i].last_heartbeat = Get_SystemTick();
can_network[i].status = RxMessage.Data[3];
break;
}
}
}
}
can_rx_flag = 0;
}
}
参考代码 STM32 CAN总线通讯实验 www.youwenfan.com/contentcst/133244.html
五、高级应用
5.1 CANopen协议实现
/* CANopen协议基础实现 */
#define COB_ID_NMT 0x000 // 网络管理
#define COB_ID_SYNC 0x080 // 同步
#define COB_ID_EMCY 0x080 // 紧急
#define COB_ID_PDO1_TX 0x180 // 过程数据对象1发送
#define COB_ID_PDO1_RX 0x200 // 过程数据对象1接收
#define COB_ID_SDO_TX 0x580 // 服务数据对象发送
#define COB_ID_SDO_RX 0x600 // 服务数据对象接收
/* CANopen NMT命令 */
typedef enum {
NMT_START_REMOTE = 0x01,
NMT_STOP_REMOTE = 0x02,
NMT_ENTER_PREOP = 0x80,
NMT_RESET_NODE = 0x81,
NMT_RESET_COMM = 0x82
} NMT_Command;
/* 发送NMT命令 */
void CANopen_Send_NMT(uint8_t node_id, NMT_Command cmd) {
uint8_t nmt_data[2];
nmt_data[0] = cmd;
nmt_data[1] = node_id;
CAN_Send_Message(COB_ID_NMT, nmt_data, 2);
}
/* 发送PDO数据 */
void CANopen_Send_PDO(uint8_t node_id, uint8_t *data, uint8_t length) {
uint32_t cob_id = COB_ID_PDO1_TX + node_id;
CAN_Send_Message(cob_id, data, length);
}
5.2 汽车OBD-II诊断协议
/* OBD-II诊断协议 */
#define PID_ENGINE_RPM 0x0C // 发动机转速
#define PID_VEHICLE_SPEED 0x0D // 车速
#define PID_COOLANT_TEMP 0x05 // 冷却液温度
#define PID_FUEL_LEVEL 0x2F // 燃油液位
/* 发送OBD请求 */
void OBD_Send_Request(uint8_t pid) {
uint8_t obd_data[8];
obd_data[0] = 0x02; // 数据长度
obd_data[1] = 0x01; // 模式1:显示当前数据
obd_data[2] = pid; // PID
obd_data[3] = 0x00; // 填充
obd_data[4] = 0x00;
obd_data[5] = 0x00;
obd_data[6] = 0x00;
obd_data[7] = 0x00;
CAN_Send_Message(0x7DF, obd_data, 8); // 广播请求ID
}
/* 解析OBD响应 */
void Parse_OBD_Response(void) {
if (can_rx_flag && RxMessage.StdId == 0x7E8) {
uint8_t mode = RxMessage.Data[1];
uint8_t pid = RxMessage.Data[2];
uint16_t value;
if (mode == 0x41) { // 模式1响应
switch (pid) {
case PID_ENGINE_RPM:
value = (RxMessage.Data[3] << 8) | RxMessage.Data[4];
printf("Engine RPM: %d RPM\n", value / 4);
break;
case PID_VEHICLE_SPEED:
printf("Vehicle Speed: %d km/h\n", RxMessage.Data[3]);
break;
case PID_COOLANT_TEMP:
printf("Coolant Temp: %d °C\n", RxMessage.Data[3] - 40);
break;
}
}
can_rx_flag = 0;
}
}
六、主程序
6.1 完整主程序
/* 主程序 */
#include "stm32f10x.h"
#include "system_stm32f10x.h"
volatile uint32_t system_tick = 0;
void SysTick_Init(void) {
SysTick_Config(SystemCoreClock / 1000); // 1ms中断
}
void SysTick_Handler(void) {
system_tick++;
}
uint32_t Get_SystemTick(void) {
return system_tick;
}
int main(void) {
// 系统初始化
SystemInit();
SysTick_Init();
// 初始化串口
USART1_Init();
printf("\r\n=== STM32 CAN Bus Experiment ===\r\n");
// 初始化CAN硬件
CAN_GPIO_Init();
CAN_Config(500000); // 500kbps
CAN_Filter_Config();
CAN_NVIC_Config();
// 测试CAN回环模式
CAN_Loopback_Test();
printf("\r\nStarting normal mode...\r\n");
// 切换到正常模式
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
CAN_Init(CAN1, &CAN_InitStructure);
uint32_t last_send_time = 0;
uint8_t node_id = NODE_A; // 设置为节点A
while (1) {
uint32_t current_time = Get_SystemTick();
// 每秒发送一次数据
if (current_time - last_send_time >= 1000) {
if (node_id == NODE_A) {
NodeA_Task();
} else {
NodeB_Task();
}
last_send_time = current_time;
}
// 处理接收消息
Process_CAN_Message();
// 检查错误
if (can_error_flag) {
printf("CAN Error: 0x%08lX\r\n", can_error_code);
can_error_flag = 0;
}
// 延时
for (volatile uint32_t i = 0; i < 10000; i++);
}
}
七、调试与测试
7.1 测试方法
/* CAN总线测试工具 */
void CAN_Test_Menu(void) {
printf("\r\n=== CAN Test Menu ===\r\n");
printf("1. Loopback Test\r\n");
printf("2. Send Test Message\r\n");
printf("3. Monitor Bus Traffic\r\n");
printf("4. Error Counter\r\n");
printf("5. Baudrate Test\r\n");
printf("6. Stress Test\r\n");
printf("Select option: ");
}
/* 压力测试 */
void CAN_Stress_Test(void) {
uint32_t start_time = Get_SystemTick();
uint32_t messages_sent = 0;
uint8_t test_data[8] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22};
printf("Starting CAN stress test for 10 seconds...\r\n");
while ((Get_SystemTick() - start_time) < 10000) {
if (CAN_Send_Message(0x123, test_data, 8)) {
messages_sent++;
}
for (volatile uint32_t i = 0; i < 1000; i++); // 小延时
}
printf("Stress test completed: %lu messages sent in 10 seconds\r\n", messages_sent);
printf("Average rate: %lu messages/second\r\n", messages_sent / 10);
}
/* 错误统计 */
void CAN_Error_Statistics(void) {
uint8_t esr = CAN1->ESR;
printf("CAN Error Statistics:\r\n");
printf(" REC (Receive Error Counter): %d\r\n", (esr >> 24) & 0xFF);
printf(" TEC (Transmit Error Counter): %d\r\n", (esr >> 16) & 0xFF);
printf(" LEC (Last Error Code): %d\r\n", (esr >> 4) & 0x07);
switch ((esr >> 4) & 0x07) {
case 0: printf(" No Error\r\n"); break;
case 1: printf(" Stuff Error\r\n"); break;
case 2: printf(" Form Error\r\n"); break;
case 3: printf(" Acknowledgment Error\r\n"); break;
case 4: printf(" Bit Recessive Error\r\n"); break;
case 5: printf(" Bit Dominant Error\r\n"); break;
case 6: printf(" CRC Error\r\n"); break;
case 7: printf(" Set by Software\r\n"); break;
}
}
八、常见问题与解决
8.1 故障排除
问题
可能原因
解决方法
无法发送数据
波特率不匹配
检查所有节点的波特率配置
接收不到数据
过滤器配置错误
检查过滤器掩码设置
总线错误频繁
终端电阻缺失
在总线两端各接120Ω电阻
通信不稳定
接线问题
检查CANH/CANL是否短路或断路
只有一个节点能发
自动离线管理
确保ABOM位使能
8.2 优化建议
/* CAN性能优化 */
void CAN_Optimization_Tips(void) {
printf("CAN Optimization Tips:\r\n");
printf("1. Use appropriate baudrate for cable length\r\n");
printf("2. Enable automatic retransmission for reliability\r\n");
printf("3. Implement proper filtering to reduce CPU load\r\n");
printf("4. Use FIFO0 and FIFO1 for different priorities\r\n");
printf("5. Monitor error counters for bus health\r\n");
printf("6. Implement watchdog for error recovery\r\n");
printf("7. Use DMA for high-speed data transfer\r\n");
printf("8. Consider time-triggered CAN for real-time systems\r\n");
}
总结
本实验实现了STM32 CAN总线的完整功能:
核心功能:
CAN控制器配置 :波特率、工作模式、过滤器
数据收发 :标准帧、扩展帧、远程帧
中断处理 :接收、发送、错误中断
多节点通信 :网络管理、心跳监测
实验内容:
自发自收测试 :验证CAN控制器基本功能
双机通信 :两个STM32节点互发数据
多节点网络 :构建CAN总线网络
协议实现 :CANopen、OBD-II诊断协议
应用场景:
汽车电子 :车身控制、动力系统、诊断系统
工业控制 :PLC通信、传感器网络、设备监控
轨道交通 :列车通信网络、信号系统
医疗设备 :医疗仪器互联、数据传输