STM32CubeMX | 36 - 使用CAN总线进行双板通信(TJA1050)
发布日期:2021-07-01 02:35:14 浏览次数:2 分类:技术文章

本文共 6117 字,大约阅读时间需要 20 分钟。

本篇详细的记录了如何使用STM32CubeMX配置 STM32F407ZGT6 的硬件CAN接口与另一个开发板之间通信。

1. 准备工作

硬件准备

  • 开发板

首先需要准备一个开发板,这里我准备的是STM32F407ZGT6的开发板,称之为 1# 实验板。

  • CAN收发器

开发板板载一块CAN收发器TJA1050,如图中红框所示:

软件准备

需要准备一份 TJA1050 的数据手册。

实验说明

本实验中还需要另外准备一块具备CAN收发功能的开发板,这里我使用STM32F767开发板,其板载CAN收发器也是TJA 1050,称之为 2# 实验板:

两个开发板之间的连接方式如下:
这样就形成了一个CAN总线的闭环通信网络,最高通信速度可达 1M bps/s:

2. 使用STM32CubeMX生成工程

选择芯片型号

打开STM32CubeMX,打开MCU选择器:

搜索并选中芯片STM32F407ZGT6

配置时钟源

  • 如果选择使用外部高速时钟(HSE),则需要在System Core中配置RCC;
  • 如果使用默认内部时钟(HSI),这一步可以略过;

这里我都使用外部时钟:

调试选项配置

默认没有配置下载引脚,烧录之后下载器将无法再检测到,这里我使用ST-Link,所以配置为SW选项:

配置串口

开发板板载了一个CH340换串口,连接到USART1,但是引脚不是默认引脚,需要手动修改。

接下来开始配置USART1

配置CAN外设

CAN收发器

开发板上CAN收发器(TJA1050)的原理图如下:

其中CAN_TX 和 CAN_RX 连接到CAN1外设:

网络标号 引脚
CAN_TX PA12
CAN_RX PA11

正点原子该款开发板上的PA12和PA11被复用,需要使用跳线帽来选择连接到CAN收发器。

配置CAN1控制器

选中CAN1,点击使能“Master Mode”,在右边即可看到CAN1控制器的默认GPIO,与原理图上连接CAN收发器的引脚一致,无需修改:

配置CAN控制只需配置波特率,一般为500KHz,最高1MHz,其它保持默认即可。

CAN总线的波特率比较特别,串口协议的波特率只支持一个确定值,而CAN总线的波特率支持一个较宽的范围,这也使得CAN总线的抗噪声性能大大增强。

CAN总线的波特率计算方式如下:

确定CAN外设连接的外设总线时钟PCLK1

此处CAN1连接到APB1外设总线上,在配置HCLK=168Mhz的基础上,PCLK=42Mhz。

确定分频系数

此处将PCLK1进行7分频,为 42Mhz / 7 = 6Mhz,所以设置CAN1外设的分频系数为7:

配置位段时序

CAN协议的每一个数据位都分为许多时间段,如图:

  • 同步段(SYNC_SEG):位变化应该在此时间段内发生,只有一个时间片的固定长度(1 x tq);
  • 位段1(BS1):定义采样点的位置,其持续长度可以在 1 到 16 个Tq之间调整;
  • 位段2(BS2):定义发送点的位置,其持续长度可以在 1 到 8 个Tq之间调整;
  • 同步跳转宽度(SJW):定义位段加长或缩短的上限,它可以在 1 到 4 个Tq之间调整;

目标波特率是500khz,设:

B S 1 + B S 2 + S J W = T BS1+BS2+SJW = T BS1+BS2+SJW=T
根据:
6 M h z / T = 6000 k h z / T = 500 k h z 6Mhz/T = 6000khz / T = 500khz 6Mhz/T=6000khz/T=500khz
计算出:
T = 12 T = 12 T=12
最后在BS1、BS2、SJW的每个范围内,调整出和为12即可,本文配置如下:

使能CAN1控制器接收中断

配置时钟树

STM32F407ZGT6的最高主频到168M,使HCLK = 168Mhz即可:

在这里插入图片描述

生成工程设置

代码生成设置

最后设置生成独立的初始化文件:

生成代码

点击GENERATE CODE即可生成MDK-V5工程:

4. 编写收发测试程序

4.1. 重定向printf到串口1

/* USER CODE BEGIN 1 */#if 1#include 
int fputc(int ch, FILE *stream){
/* 堵塞判断串口是否发送完成 */ while((USART1->SR & 0X40) == 0); /* 串口发送完成,将该字符发送 */ USART1->DR = (uint8_t) ch; return ch;}#endif/* USER CODE END 1 */

具体参考这篇博客:

4.2. 编写CAN1收发测试代码

①编写CAN过滤器配置函数

在 CAN 协议中,发送节点将报文广播给所有接收器。而接收节点会根据报文标识符的值来确定节点是否需要该消息,为了简化软件的工作, STM32 的 CAN 外设接收报文前会先使用过滤器检查,只接收需要的报文到 FIFO 中。

STM32的CAN控制器一共有 28 个过滤器,CAN1 和 CAN2 共用这些过滤器。

CAN过滤器结构体定义在stm32f4xx_hal_can.h文件中,在main.c中编写CAN过滤器配置函数(不进行任何过滤):

/* Private user code ---------------------------------------------------------*//* USER CODE BEGIN 0 *//* CAN过滤器配置函数 */static void CANFilter_Config(void){
CAN_FilterTypeDef sFilterConfig; sFilterConfig.FilterBank = 0; //CAN过滤器编号,范围0-27 sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; //CAN过滤器模式,掩码模式或列表模式 sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; //CAN过滤器尺度,16位或32位 sFilterConfig.FilterIdHigh = 0x000 << 5; //32位下,存储要过滤ID的高16位 sFilterConfig.FilterIdLow = 0x0000; //32位下,存储要过滤ID的低16位 sFilterConfig.FilterMaskIdHigh = 0x0000; //掩码模式下,存储的是掩码 sFilterConfig.FilterMaskIdLow = 0x0000; sFilterConfig.FilterFIFOAssignment = 0; //报文通过过滤器的匹配后,存储到哪个FIFO sFilterConfig.FilterActivation = ENABLE; //激活过滤器 sFilterConfig.SlaveStartFilterBank = 0; if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK) {
printf("CAN Filter Config Fail!\r\n"); Error_Handler(); } printf("CAN Filter Config Success!\r\n");}/* USER CODE END 0 */

其中,不同配置模式下四个数据成员内容对应的含义:

② 定义接收和发送消息变量

在main.c文件中定义CAN接收和发送消息变量:

/* Private variables ---------------------------------------------------------*//* USER CODE BEGIN PV */static CAN_TxHeaderTypeDef        TxMessage;    //CAN发送的消息的消息头static CAN_RxHeaderTypeDef        RxMessage;    //CAN接收的消息的消息头/* USER CODE END PV */

③ 编写CAN接收中断处理函数

在main.c文件的最后编写CAN接收中断处理函数:

/* USER CODE BEGIN 4 */void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan){
uint8_t data[8]; HAL_StatusTypeDef status; if (hcan == &hcan1) {
status = HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxMessage, data); if (HAL_OK == status){
printf("--->Data Receieve!\r\n"); printf("RxMessage.StdId is %#x\r\n", RxMessage.StdId); printf("data[0] is 0x%02x\r\n", data[0]); printf("data[1] is 0x%02x\r\n", data[1]); printf("data[2] is 0x%02x\r\n", data[2]); printf("data[3] is 0x%02x\r\n", data[3]); printf("<---\r\n"); } }}/* USER CODE END 4 */

④ 编写CAN发送测试数据函数

/* CAN 发送数据测试函数 */void CAN1_Send_Test(){
uint8_t data[4] = {
0x01, 0x02, 0x03, 0x04}; TxMessage.IDE = CAN_ID_STD; //设置ID类型 TxMessage.StdId = 0x222; //设置ID号 TxMessage.RTR = CAN_RTR_DATA; //设置传送数据帧 TxMessage.DLC = 4; //设置数据长度 if (HAL_CAN_AddTxMessage(&hcan1, &TxMessage, data, (uint32_t*)CAN_TX_MAILBOX0) != HAL_OK) {
printf("CAN send test data fail!\r\n"); Error_Handler(); } printf("CAN send test data success!\r\n"); }

⑤ 编写初始化函数

修改main函数,在其中配置CAN滤波器、启动CAN控制器、使能CAN控制器接收中断:

/* USER CODE BEGIN 2 */    printf("----- CAN Test Board #1 -----\r\n");        /* 1. CAN Filter Config */    CANFilter_Config();        /* 2. CAN Start */    if (HAL_CAN_Start(&hcan1) != HAL_OK) {
printf("CAN Start Fail\r\n"); Error_Handler(); } printf("CAN Start Success\r\n"); /* 3. Enable CAN RX Interrupt */ if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK) {
printf("CAN_IT_RX_FIFO0_MSG_PENDING Enable Fail\r\n"); Error_Handler(); } printf("CAN_IT_RX_FIFO0_MSG_PENDING Enable Success\r\n"); /* USER CODE END 2 */

⑥ 在main函数中循环发送测试数据

在while(1)中循环发送测试数据函数:

/* Infinite loop */  /* USER CODE BEGIN WHILE */  while (1)  {
/* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ /* 4. Send Data */ CAN1_Send_Test(); HAL_Delay(5000); } /* USER CODE END 3 */

至此,1#测试板的配置和代码编写完成。

5. 编写2#测试板的代码

第二块板的流程和第一块板的流程几乎相同,有几点需要改动。

① STM32F767的 PCLK1 = 54Mhz,所以要配置CAN控制器的分频系数为9,其余参数不变:

② STM32F767开发板默认CAN控制器的接收引脚不匹配,需要修改到PA11:
③ 在代码中将打印信息修改为2#实验板:
④ 在代码中修改发送数据包的ID号,为1#实验板的ID:
其余地方一样。

6. 实验结果

将两个程序分别编译、下载到开发板中,使用串口助手查看结果。

1#测试板(ID:0x111)的串口打印日志为:

2#测试板(ID:0x222)的串口打印日志为:

更多精彩文章及资源,请关注我的微信公众号:『mculover666』。

转载地址:https://mculover666.blog.csdn.net/article/details/110148532 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:STM32CubeMX | 37 - 使用RS485总线进行双板通信(SP3485)
下一篇:SIMCOM A7670 模组(4G Cat.1 通信模组)AT指令测试 TCP/UDP 通信过程

发表评论

最新留言

做的很好,不错不错
[***.243.131.199]2024年04月10日 20时31分45秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

SQL优化--大数据量模糊查询缓慢 2019-05-03
Linux安装Zookeeper(Centos7) 2019-05-03
ACM进阶计划(来自于南阳理工学院) 2019-05-03
Scala学习第八天 Scala主构造器、私有构造器、构造器重载实战详解 2019-05-03
Scala学习第九天 Scala的内部类实战详解 2019-05-03
Scala学习第十天 Scala单例对象、伴生对象实战详解 2019-05-03
Scala学习第十一天 Scala中的apply实战详解 2019-05-03
Scala学习第七天 Scala类的属性和对象私有字段实战详解 2019-05-03
Scala学习第六天 Map、Tuple、Zip实战解析 2019-05-03
Scala学习第四天 Scala的For与Function进阶实战、Lazy的使用 2019-05-03
Scala学习第三天 Tuple、Array、May与文件操作入门实战 2019-05-03
Scala学习第二天 Scala函数定义、流程控制、异常处理 2019-05-03
Scala学习第五天 Scala数组操作实战详解 2019-05-03
基于key-value的存储系统Redis 2019-05-03
Scala学习第十二天 Scala中的继承:超类的构造、重写字段、重写方法代码实战 2019-05-03
Scala学习第十三天 抽象类、抽象字段、抽象方法 2019-05-03
Scala学习第十四天 Scala中作为接口的trait、在对象中混入trait代码实战 2021-07-06
Scala学习第十五天 Scala多重继承、多重继承构造器执行顺序及AOP实现 2019-05-03
Scala学习第十六天 包的定义、包对象、包的引用、包的隐式引用代码实战 2019-05-03
Scala学习第十七天 包、类、对象、成员、伴生类、伴生对象访问权限实战彻底详解 2019-05-03