STM32F407 系列文章 - CAN通讯(十一)
目录
前言
一般STM32F407芯片都会自带2路CAN接口,分别为CAN1和CAN2,其通讯速度高达1Mb/s,每个CAN总线发送端具备三个发送邮箱,用来区别发送优先级,接收端具备两个具有三级深度的接收 FIFO,用来存储数据。一般从407芯片端口输出的CAN信号抗干扰性比较差,不足以保证通讯的稳定性和可靠性,这时我们就需要通过添加驱动电路,可以增强信号的驱动能力,确保信号在传输过程中不受干扰或衰减,从而提高通讯的稳定性和可靠性。一般市场上所卖的板子都带这一功能的,因此要实现CAN总线通讯功能,需准备STM32F407开发板一块和CAN-Tool分析仪工具一个。
一、CAN
控制器局域网总线(CAN,Controller Area Network)是ISO国际标准化的串行通信协议总线,使用双绞线来传输信号,具有高性能、高可靠性和易于扩展的特点,广泛应用于汽车、工业控制、机器人控制等领域,是世界上应用最广泛的现场总线之一。CAN总线协议_百度百科 (baidu.com)
CAN通讯是一种多主机串行异步通信总线,允许网络中的各个节点(设备)进行无中心控制的通信。每个节点都可以在总线上发送报文,当多个节点同时发送报文时,CAN总线会使用仲裁机制决定哪个报文优先发送,优先级高的报文会先发送,低优先级的报文则会停止发送。CAN总线的通信过程分为发送报文、仲裁机制、数据传输和错误检测与处理四个阶段。
要了解更为详细的CAN总线协议及其报文构成,可参考CAN总线通信协议-优快云博客,讲的挺全面的。
二、CAN驱动电路
STM32单片机在进行CAN通讯时加驱动电路是为了增强信号传输能力、提供总线保护以及满足CAN总线物理层规范。这些措施有助于提高通讯的稳定性和可靠性,确保单片机与CAN总线上的其他设备之间的正常通讯。下面提供一款国产驱动芯片SIT1050,详细的芯片参数及引脚特性参考其datasheet。
SIT1050是一款应用于CAN协议控制器和物理总线之间的接口芯片,可应用于卡车、公交、小汽车、工业控制等领域,速率可达到1Mbps,具有在总线与CAN协议控制器之间进行差分信号传输的能力,设计电路原理如下。
三、CAN软件设计
CAN软件包括底层代码和用户层代码,底层代码主要完成CAN状态的初始化工作,主要涉及到对底层硬件引脚、时钟、中断的定义及调用;用户层代码主要完成对CAN总线上数据消息的解析和处理。
关于底层代码的实现可以通过调用HAL官方库文件,或者在可视化工具STM32CubeMX上配置,然后一键画生成底层代码。不管那种方式其实都是在以CAN_TypeDef *结构完成CAN寄存器的配置,在stm32f407xx.h文件上可查看。
1.CAN状态初始化
通过函数can1_init()完成,主要为CAN1的设置,包括波特率、工作模式设置、底层驱动配置( 包括引脚配置、时钟配置、中断配置)、过滤器设置、CAN总线外围设备、CAN中断使能设置等等,该函数被main()函数调用。代码中有详细的介绍,代码如下(示例):
CAN_HandleTypeDef hcan1; /* CAN1句柄 */
CAN_HandleTypeDef hcan2; /* CAN2句柄 */
/**
* @brief CAN初始化
* @note Prescaler : 重新同步跳跃时间单元.范围: 1~3;
* TimeSeg2 : 时间段2的时间单元.范围: 1~8;
* TimeSeg1 : 时间段1的时间单元.范围: 1~16;
* Prescaler : 波特率分频器.范围: 1~1024;
* 以上4个参数, 在函数内部会减1, 所以, 任何一个参数都不能等于0
* CAN挂在APB1上面, 其输入时钟频率为 Fpclk1 = PCLK1 = 42Mhz
* tq = Prescaler * tpclk1;
* 波特率 = Fpclk1 / ((TimeSeg1 + TimeSeg2 + 1) * Prescaler);
* 已知42M时钟和500Kbps要求, 根据波特率公式
* 配置TimeSeg1 = 7, TimeSeg2 = 6 , 为Prescaler = 6
* 得出CAN波特率为: 42M / ((6 + 7 + 1) * 6) = 500Kbps
*
* @param mode : CAN_MODE_NORMAL, 普通模式;
CAN_MODE_LOOPBACK,回环模式;
* @retval 0, 初始化成功; 其他, 初始化失败;
*/
uint8_t can1_init(uint32_t mode)
{
/*** 1.完成CAN1的波特率和模式设置
* 这里也可以通过外部选择波特率配置can1_init(uint32_t bps, uint32_t mode)
* case 125:
* case 500:
* case 1000:
***/
hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 6; /* 分频系数(Fdiv)为Prescaler+1 */
hcan1.Init.Mode = mode; /* 模式设置 */
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; /* 重新同步跳跃宽度为SyncJumpWidth+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ */
hcan1.Init.TimeSeg1 = CAN_BS1_7TQ; /* 范围CAN_BS1_1TQ~CAN_BS1_16TQ */
hcan1.Init.TimeSeg2 = CAN_BS2_6TQ; /* 范围CAN_BS2_1TQ~CAN_BS2_8TQ */
hcan1.Init.TimeTriggeredMode = DISABLE; /* 非时间触发通信模式 */
hcan1.Init.AutoBusOff = ENABLE; /* 软件自动离线管理 */
hcan1.Init.AutoWakeUp = DISABLE; /* 睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位) */
hcan1.Init.AutoRetransmission = ENABLE; /* 禁止报文自动传送 */
hcan1.Init.ReceiveFifoLocked = DISABLE; /* 报文不锁定,新的覆盖旧的 */
hcan1.Init.TransmitFifoPriority = ENABLE; /* 优先级由报文标识符决定 */
// 2.完成CAN1的底层驱动配置 包括引脚配置、时钟配置、中断配置
if (HAL_CAN_Init(&hcan1) != HAL_OK)
{
Error_Handler();
return 1;
}
// 3.完成ID号为1#设备的过滤器设置
CAN_Filter_Config(&hcan1, 1);
// 4.启动CAN1总线外围设备
#if CAN1_iSOpen
if(HAL_CAN_Start(&hcan1)!=HAL_OK) {
Error_Handler();
return 1;
}
// 5.使能CAN1中断
else
Enable_CAN1_Interrupts();
#endif
return 0;
}
上面can1_init()函数包含了HAL_CAN_Init()、CAN_Filter_Config()、HAL_CAN_Start()、Enable_CAN1_Interrupts(),这4个函数分别完成如下功能:
- HAL_CAN_Init(),此函数为HAL库函数,主要是调用HAL_CAN_MspInit()函数,以完成CAN1的底层驱动配置 包括引脚配置、时钟配置、中断配置。函数HAL_CAN_MspInit()同样为HAL库函数,但是其被定义为若函数,可以用来被重写的,代码如下(示例)。
/**
* @brief CAN底层驱动,引脚配置,时钟配置,中断配置
此函数会被HAL_CAN_Init()调用
* @param hcan:CAN句柄
* @retval 无
*/
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
{
GPIO_InitTypeDef gpio_init_struct = {0};
if (CAN1 == hcan->Instance)
{
/* CAN1 clock enable */
__HAL_RCC_CAN1_CLK_ENABLE(); /* 使能CAN1时钟 */
/**CAN1 GPIO Configuration
PA11 ------> CAN1_RX
PA12 ------> CAN1_TX
*/
gpio_init_struct.Pin = GPIO_PIN_11|GPIO_PIN_12;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
gpio_init_struct.Alternate = GPIO_AF9_CAN1;
HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* CAN1_RX和CAN1_TX脚 模式设置 */
/* CAN1 interrupt Init */
HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 1);
HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);
HAL_NVIC_SetPriority(CAN1_RX1_IRQn, 1, 1);
HAL_NVIC_EnableIRQ(CAN1_RX1_IRQn);
}
else if(CAN2 == hcan->Instance)
{
/* CAN2 clock enable */
__HAL_RCC_CAN2_CLK_ENABLE(); /* 使能CAN2时钟 */
/**CAN2 GPIO Configuration
PB12 ------> CAN2_RX
PB13 ------> CAN2_TX
*/
gpio_init_struct.Pin = GPIO_PIN_12|GPIO_PIN_13;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_NOPULL;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
gpio_init_struct.Alternate = GPIO_AF9_CAN2;
HAL_GPIO_Init(GPIOB, &gpio_init_struct); /* CAN2_RX和CAN2_TX脚 模式设置 */
/* CAN2 interrupt Init */
HAL_NVIC_SetPriority(CAN2_RX0_IRQn, 1, 1);
HAL_NVIC_EnableIRQ(CAN2_RX0_IRQn);
HAL_NVIC_SetPriority(CAN2_RX1_IRQn, 1, 1);
HAL_NVIC_EnableIRQ(CAN2_RX1_IRQn);
}
}
- CAN_Filter_Co