一、CAN总线概述:
CAN(Controller Area Network)总线是一种广泛应用于汽车、工业控制等领域的串行通信协议,由德国博世公司于1986年推出。其设计初衷是解决汽车电子系统中复杂线束的问题,现已成为国际标准(ISO 11898)。
二、核心特点
1… 高可靠性:采用差分信号传输(CAN_H和CAN_L),如NXP的JTA1050(5V供电)通信,CAN_H与CAN_L电压均为2.5V表示隐性逻辑1,CAN_H=3.5V同时CAN_L=1.5V,相差2V即为显性逻辑0,抗干扰能力强。
2. 多主通信:节点无主从之分,任何节点均可主动发送数据。
3.冲突仲裁:基于优先级的非破坏性仲裁机制(标识符决定优先级)。
4. 实时性:短帧结构(最大8字节数据)和高速率(可达1 Mbps)适合实时控制。
三、帧类型
1数据帧:传输实际数据,包含标识符、控制段、数据段和CRC校验。
2. 远程帧:请求其他节点发送特定数据,无数据段。
3.错误帧:节点检测错误时主动发送,强制其他节点重发。
4.过载帧:用于增加帧间隔延迟,处理繁忙状态。
四、应用场景
1.汽车电子:ECU(发动机控制单元)、传感器、仪表盘通信。
2.工业自动化:PLC、电机控制器、机器人通信。
3. 医疗设备:医疗仪器间的数据同步。
五、物理层标准
4. 高速CAN(ISO 11898-2):速率1 Mbps,终端电阻120Ω,用于实时性要求高的场景。
2.低速容错CAN(ISO 11898-3):速率125 kbps,抗干扰更强,适用于恶劣环境。
六、协议栈组成
5. 物理层:定义电气特性(如RS-485电平)。
6. 数据链路层:处理帧结构、错误检测、仲裁等。
7. 应用层协议:如CANopen、J1939(汽车专用)。
七、 CAN硬件配置
1.查看STM32F103ZET6手册:确定要用的引脚。
八、基于STM32CUBE MX进行代码生成:
配置debug为SW,否则无法仿真调试。
2.配置时钟:外部时钟
由图可知APB1外设时钟为36MHZ,即CAN总线时钟。
3.配置CAN总线参数
本例为1MHZ通信,所以分频为3 、BS1=8、BS2=3。波特率 = APB1 时钟 / (分频系数 * (1 + BS1 + BS2))=36MHZ/(3*(1+8+3))=1MHZ 通信频率。
点击generate code生成代码。
九、代码编写
1.配置CAN的GPIO管脚:
gpio.c
#include "gpio.h"
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11|GPIO_PIN_12, GPIO_PIN_RESET);
/*Configure GPIO pins : PA11 PA12 */
GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
2.代码can.c
#include "can.h"
CAN_HandleTypeDef hcan;
/* 使能CAN接收中断 */
void CAN_Enable_Interrupt(void)//配置CAN_RX1接收通道
{
// 使能FIFO0消息 pending 中断(接收到数据时触发)
if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
{
Error_Handler();
}
// 配置NVIC(中断优先级)
HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 0, 0); // 优先级可根据需求调整
HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn);
}
void MX_CAN_Init(void)
{
// ***************************************************************
// BaudRate = 1 / NominalBitTime
// NominalBitTime = 1tq + tBS1 + tBS2
// tq = (BRP[9:0] + 1) x tPCLK
// tPCLK = CAN's clock = APB1's clock
// 1Mbps 速率下,采用点的位置在6tq位置处,BS1=5, BS2=2
// 500kbps 速率下,采用点的位置在8tq位置处,BS1=7, BS2=3
// 250kbps 速率下,采用点的位置在14tq位置处,BS1=13, BS2=2
// 125k, 100k, 50k, 20k, 10k 的采用点位置与 250K 相同
// ****************************************************************
//波特率 = APB1 时钟 / (分频系数 * (1 + BS1 + BS2))=36MHZ/(3*(1+8+3))=1MHZ 与电机通信
hcan.Instance = CAN1;
hcan.Init.Prescaler = 3; // 预分频器
hcan.Init.Mode = CAN_MODE_NORMAL; // 正常模式(收发使能)
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ; // 同步跳转宽度:1个时间量子
hcan.Init.TimeSeg1 = CAN_BS1_8TQ; // 时间段1:8个时间量子
hcan.Init.TimeSeg2 = CAN_BS2_3TQ; // 时间段2:3个时间量子
hcan.Init.TimeTriggeredMode = DISABLE; // 禁用时间触发模式
hcan.Init.AutoBusOff = DISABLE; // 自动退出总线关闭模式(可选)
hcan.Init.AutoWakeUp = DISABLE;
hcan.Init.AutoRetransmission = DISABLE; // 自动重发(建议使能)
hcan.Init.ReceiveFifoLocked = DISABLE; // 接收FIFO不锁定
hcan.Init.TransmitFifoPriority = DISABLE;// 发送FIFO优先级不使能
if (HAL_CAN_Init(&hcan) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN CAN_Init 2 过滤器配置:若需接收特定 ID,
需修改FilterId和FilterMask(掩码为 1 的位表示必须匹配)*/
// 配置CAN过滤器
CAN_FilterTypeDef can_filter_init;
can_filter_init.FilterBank = 0; // 过滤器组0(共14组,CAN1用0-13)
can_filter_init.FilterMode = CAN_FILTERMODE_IDMASK; // 掩码模式
can_filter_init.FilterScale = CAN_FILTERSCALE_32BIT; // 32位过滤器
/* 接收所有标准ID消息(掩码全0表示不过滤) */
can_filter_init.FilterIdHigh = 0x0000; // 过滤器ID高位 32位ID高16位
can_filter_init.FilterIdLow = 0x0000; // 过滤器ID低位 32位ID低16位
can_filter_init.FilterMaskIdHigh = 0x0000;// 过滤器掩码高位 32位掩码高16位
can_filter_init.FilterMaskIdLow = 0x0000; // 过滤器掩码低位 32位掩码低16位
//在初始化过程中,需要配置过滤器以决定使用哪个 FIFO 缓冲区。 此处为CAN_RX0 关联CAN1_RX0_IRQ中断向量
can_filter_init.FilterFIFOAssignment = CAN_RX_FIFO0; // 关联到接收FIFO0 匹配消息存入FIFO0
can_filter_init.FilterActivation = ENABLE; // 启用过滤器
can_filter_init.SlaveStartFilterBank = 14; // 从机过滤器起始组 (单CAN时无用)
if (HAL_CAN_ConfigFilter(&hcan, &can_filter_init) != HAL_OK)
{
Error_Handler();
}
// 启动CAN
if (HAL_CAN_Start(&hcan) != HAL_OK)
{
Error_Handler();
}
}
void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(canHandle->Instance==CAN1)
{
__HAL_RCC_CAN1_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
/**CAN GPIO Configuration
PD0 ------> CAN_RX
PD1 ------> CAN_TX
*/
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
__HAL_AFIO_REMAP_CAN1_3();
HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn);
}
}
void HAL_CAN_MspDeInit(CAN_HandleTypeDef* canHandle)
{
if(canHandle->Instance==CAN1)
{
__HAL_RCC_CAN1_CLK_DISABLE();
/**CAN GPIO Configuration
PD0 ------> CAN_RX
PD1 ------> CAN_TX
*/
HAL_GPIO_DeInit(GPIOD, GPIO_PIN_0|GPIO_PIN_1);
HAL_NVIC_DisableIRQ(CAN1_RX1_IRQn);
// 禁用中断
HAL_NVIC_DisableIRQ(CAN1_RX0_IRQn);
}
}
/* USER CODE BEGIN 1 */
//MX_CAN_GPIO_Init(); // 初始化CAN GPIO
//MX_CAN_Init(); // 初始化CAN外设
//CAN_Filter_Config(); // 配置过滤器
//HAL_CAN_Start(&hcan); // 启动CAN外设
/* CAN接收消息(轮询方式)
参数:id - 存储接收的标准ID
data - 存储接收的数据(最大8字节)
len - 存储数据长度(1-8)
返回:HAL_OK - 接收成功;其他 - 接收失败
*/
HAL_StatusTypeDef CAN_Receive_Message(uint32_t *id, uint8_t *data, uint8_t *len)
{
CAN_RxHeaderTypeDef RxHeader;
uint32_t fifo_level;
// 检查FIFO0是否有数据(也可检查FIFO1,根据过滤器配置)
fifo_level = HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0);
if (fifo_level == 0)
{
return HAL_ERROR; // FIFO为空,无数据
}
// 读取FIFO0中的数据
if (HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, data) != HAL_OK)
{
return HAL_ERROR; // 读取失败
}
// 仅处理标准数据帧(过滤远程帧和扩展帧)
if (RxHeader.IDE != CAN_ID_STD || RxHeader.RTR != CAN_RTR_DATA)
{
return HAL_ERROR;
}
// 提取有效信息
*id = RxHeader.StdId; // 标准ID(11位)
*len = RxHeader.DLC; // 数据长度(1-8)
return HAL_OK;
}
3.主程序main.c
#include "main.h"
#include "can.h"
#include "gpio.h"
/* USER CODE END PV */
CAN_RxHeaderTypeDef RxHeader;
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_CAN_Init();
/* USER CODE BEGIN 2 */
uint8_t MOTO_DISABLE[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC};
uint32_t tx_mailbox;
// 准备发送数据
CAN_TxHeaderTypeDef tx_header;
tx_header.StdId = 0x101; // 标准ID 电机控制 ID 号
tx_header.ExtId = 0x00; // 扩展ID未使用
tx_header.RTR = CAN_RTR_DATA; // 数据帧
tx_header.IDE = CAN_ID_STD; // 标准帧
tx_header.DLC = 8; // 数据长度
tx_header.TransmitGlobalTime = DISABLE;
while (1)
{
//发送数据
if(HAL_CAN_AddTxMessage(&hcan, &tx_header, MOTO_DISABLE, &tx_mailbox) != HAL_OK)
{
Error_Handler();
}
// 等待发送完成
while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) != 3) {}
HAL_Delay(1000);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
通过CAN转USB工具进行测试:
5.编写主程序CAN接收代码:(采用轮循的方式接收,不设置中断):
#include "main.h"
#include "can.h"
#include "gpio.h"
uint8_t data[8]; //接收缓存数据
uint8_t FIFO_test[2];
uint32_t global_rx_id;//接收ID号
uint8_t global_rx_len;//接收数据长度
uint8_t global_rx_flag = 0;//接收标志位
uint32_t fifo_level;//有数据标志位
CAN_RxHeaderTypeDef RxHeader;
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_CAN_Init();
while (1)
{
// 轮询接收数据
/******************CAN通信接收数据处理程序开始*******************************/
// 检查FIFO0是否有数据(也可检查FIFO1,根据过滤器配置)
// 本工程初始CAN用的是FIFO的0缓冲区 收到数据其
fifo_level = HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0); //FIFO有数据不为0 执行完其返回标志自动清零
if (fifo_level == 0) // FIFO为空,无数据
__nop();
else
{
FIFO_test[0]++;
// 读取FIFO0中的数据(如同循环检测按键 看数据变化)
if (HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, data) == HAL_OK)//成功接收为 HAL_OK
{ }
//仅处理标准数据帧(过滤远程帧和扩展帧)
if (RxHeader.IDE != CAN_ID_STD || RxHeader.RTR != CAN_RTR_DATA)
{
return HAL_ERROR;
}
// 提取有效信息
global_rx_id = RxHeader.StdId; // 标准ID(11位)
global_rx_len = RxHeader.DLC; // 数据长度(1-8)
}
// 检查FIFO1是否有数据
fifo_level = HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO1);
if (fifo_level == 0) // FIFO为空,无数据
__nop();
else
FIFO_test[1]++;
/******************CAN通信接收数据处理程序结束*******************************/
HAL_Delay(50);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
6.启用CAN通信工具与仿真测试:OK
————————————————
版权声明:本文为CSDN博主「兴园电子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36598085/article/details/151152958
|
|