一、实验目的与原理
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; // 时间段1uint8_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; // 时间段1CAN_InitStructure.CAN_BS2 = CAN_BS2_1tq; // 时间段2CAN_InitStructure.CAN_Prescaler = 6; // 预分频器,500kbps// 初始化CANif (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) {// 配置过滤器0CAN_FilterInitStructure.CAN_FilterNumber = 0; // 过滤器0CAN_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; // 关联到FIFO0CAN_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的IDNODE_B = 0x200 // 节点B的ID
} NodeID;// 节点A程序
void NodeA_Task(void) {uint8_t tx_data[8];static uint8_t counter = 0;// 发送数据到节点Btx_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;// 发送数据到节点Atx_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 // 广播IDtypedef struct {uint32_t node_id; // 节点IDuint8_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/contentcnu/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; // PIDobd_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); // 500kbpsCAN_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; // 设置为节点Awhile (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通信、传感器网络、设备监控
- 轨道交通:列车通信网络、信号系统
- 医疗设备:医疗仪器互联、数据传输