STM32CubeMX配置CAN
已更新完成,标准帧部分已补,前面can相关知识有时间再写吧,没时间不写了,不耽误配置,按照步骤走是可以直接用的
工作中经常用到,每次隔时间长就会忘,这次写个笔记留给自己,目前扩展帧已完善,标准帧后续会写,还有一些空出来的后续再写,配置写的挺清楚的,可以直接用,我用的芯片是f103rct,芯片不一样工程不能直接用
通过网盘分享的文件:can.rar
链接: https://pan.baidu.com/s/1y5U1vkkf1eW3LUpLs3vuSQ?pwd=ifjj 提取码: ifjj
--来自百度网盘超级会员v8的分享
介绍这部分是can的原理,可以不看,直接到配置部分
一、CAN总线介绍
1.物理层
只介绍高速CAN,闭环,总线最大长度为 40m,通信速度最高为 1Mbps,总线的两端各要求有一个“120 欧”的电阻

CAN 总线可以挂载多个通讯节点,且CAN 通讯协议不对节点进行地址编码,而是对数据内容进行编码,所以网络中的节点个数理论上不受限制,只要总线的负载足够即可。CAN 通讯节点由一个 CAN 控制器及 CAN 收发器组成,控制器与收发器之间通过 CAN_Tx 及CAN_Rx 信号线相连,收发器与 CAN 总线之间使用 CAN_High 及 CAN_Low 信号线相连。CAN_High 及 CAN_Low 是一对差分信号线。
CAN 协议中对它使用的 CAN_High 及 CAN_Low 表示的差分信号做了规定,以高速 CAN 协议为例,当表示逻辑 1 时 (隐性电平) , CAN_High 和 CAN_Low 线上的电压均为 2.5v,即它们的电压差 VH-V:sub:L=0V;而表示逻辑 0 时 (显性电平) , CAN_High 的电平为 3.5V, CAN_Low 线的电平为 1.5V,即它们的电压差为 VH-V:sub:L=2V。例如,当 CAN收发器从 CAN_Tx 线接收到来自 CAN 控制器的低电平信号时 (逻辑 0),它会使 CAN_High 输出3.5V,同时 CAN_Low 输出 1.5V,从而输出显性电平表示逻辑 0 。CAN只有一对差分线,同一时刻只能有一个通讯节点发送信号,半双工

当 CAN 节点需要发送数据时,控制器把要发送的二进制编码通过 CAN_Tx 线发送到收发器,然
后由收发器把这个普通的逻辑电平信号转化成差分信号,通过差分线 CAN_High 和 CAN_Low 线
输出到 CAN 总线网络。而通过收发器接收总线上的数据到控制器时,则是相反的过程,收发器
把总线上收到的 CAN_High 及 CAN_Low 信号转化成普通的逻辑电平信号,通过 CAN_Rx 输出到
控制器中。
2.协议层
2.1CAN 的报文种类及结构
CAN 一共规定了 5 种类型的帧

数据帧是在 CAN 通讯中最主要、最复杂的报文

数据帧以一个显性位 (逻辑 0) 开始,以 7 个连续的隐性位 (逻辑 1) 结束,在它们之间,分别有仲
裁段、控制段、数据段、 CRC 段和 ACK 段。
仲裁段:表示优先级,由报文 ID 、 RTR、 IDE 和 SRR 位组成
ID:数据帧具有标准格式和扩展格式两种,标准格式的 ID 为 11 位,扩展格式的 ID 为 29 位,ID 决定数据帧发送的优先级,总线上同时出现显性电平和隐性电平,总线的状态会被置为显性电平,也就是显性电平优先级高,就意味着ID越小,优先级越高

RTR:0:数据帧 1:遥控帧
IDE: 0:标准格式 1:扩展格式
SRR: 只存在于扩展格式,替代标准格式中的 RTR
控制段:主要是DLC,4 个数据位组成,表示本报文中的数据段含有多少个字节,范围0~8
数据段:数据内容,MSB先行
CRC段:校验
ACK 段:类似 I2C 总线,在 ACK 槽位中,发送节点发送的是隐性位,而接收节点则在这一位中发送显性位以示应答。
帧结束:7 个隐性位
2.2位时序
CAN 使用“位同步”的方式来抗干扰、吸收误差,实现对总线电平信号进行正确的采样,确保通讯正常。为了实现位同步, CAN 协议把每一个数据位的时序分解成如图所示的 SS 段、 PTS 段、PBS1 段、 PBS2 段,这四段的长度加起来即为一个 CAN 数据位的长度。
这里只需要知道
SS段:信号跳变沿被包含在 SS 段内,表示同步,大小固定为 1Tq。
PTS 段:大小为 1~8Tq
PBS1段:大小为 1~8Tq
PBS2段:大小为 2~8Tq
其中PBS1段和PBS2段是需要我们在cubemx里配置的,会和can总线的波特率有关
二、stm32CAN外设
1.工作模式

正常模式:一个正常的 CAN 节点,可以向总线发送数据和接收数据
静默模式:自己输出端的逻辑 0 数据会直接传输到它自己的输入端,逻辑 1 可以被发送到总线,不能向总线发送显性位 (逻辑 0),只能发送隐性位 (逻辑 1)。输入端可以从总线接收内容。由于只可发送的隐性位不会强制影响总线的状态,所以把它称为静默模式。这种模式一般用于监测,它可以用于分析总线上的流量,但又 不会因为发送显性位而影响总线。
回环模式:
回环静默模式:
STM32 外设定义的位时序与我们前面解释的 CAN 标准时序有一点区别

SYNC_SEG 段固定长度为 1Tq,BS1 段理解为是由前面介绍的 CAN 标准协议中 PTS 段与 PBS1 段合在一起的,而 BS2 段就相当于 PBS2 段。
CAN 发送流程
程序选择 1 个空置的邮箱(TME=1) →设置标识符(ID),数据长度和发送数据→设置 CAN_TIxR 的 TXRQ 位为 1,请求发送→邮箱挂号(等待成为最高优先级) →预定发送(等待总线空闲) →发送→邮箱空置。

CAN 接收流程
CAN 接收到的有效报文,被存储在 3 级邮箱深度的 FIFO 中。 FIFO 完全由硬件来管理,从而节省了 CPU 的处理负荷,简化了软件并保证了数据的一致性。应用程序只能通过读取 FIFO输出邮箱,来读取 FIFO 中最先收到的报文。这里的有效报文是指那些正确被接收的(直到 EOF都没有错误)且通过了标识符过滤的报文。前面我们知道 CAN 的接收有 2 个 FIFO,我们每个过滤器组都可以设置其关联的 FIFO,通过 CAN_FFA1R 的设置,可以将过滤器组关联到FIFO0/FIFO1。
CAN 接收流程为: FIFO 空→收到有效报文→挂号_1(存入 FIFO 的一个邮箱,这个由硬件控制,我们不需要理会) →收到有效报文→挂号_2→收到有效报文→挂号_3→收到有效报文溢出。
这个流程里面,我们没有考虑从 FIFO 读出报文的情况,实际情况是:我们必须在 FIFO 溢出前,读出至少 1 个报文,否则下个报文到来,将导致 FIFO 溢出,从而出现报文丢失。每读出 1 个报文,相应的挂号就减 1,直到 FIFO 空。

三、配置
3.1 stm32cubemx选配
cubemx的配置还是很简单的,sys选serial wire
时钟RCC,选择外部高速时钟
时钟树,需要的时钟根据自己需求选择,我喜欢配最大,一定要是HSE和PLLCLK
增加一路串口用来打印调试信息,选择异步模式,注意波特率,我这里用的默认
can:
psc,bs1,bs2这三个俩都是用来算波特率的,只要这三个数在范围内,根据实际需要的波特率,想怎么配怎么配
计算波特率:
bruad=PCLK1/Prescaler/(BS1+BS2+ReSynchronization)名字均与cubemx里对的上,自行对照
f1系列的can都挂载在APB1上
前面时钟树,APB1外设这条路上的时钟频率是36MHz
我实际需要400bit/s,所以braud=36000000/9/(5+4+1)=400
打开中断

文件名

勾选分文件编程,给每一个外设一个.c文件
生成工程,这样在cubemx的选配工作就完成了
3.2 代码
1.准备工作
新建班级支持包,加头文件
1.1 选库
打开工程文件,点击魔术棒选项,勾选库,不选库的话程序会卡在初始化,不能往下执行
![]()

接下来在debug界面,改一下下载器,默认是ST,我用的是CMSIS

我还喜欢勾选一下下载之后自动复位运行,这样就不用我按按键复位了,点击上一张图红框里的setting即可进入下面的界面,记得点击ok保存,然后编译一下

1.2 板级支持包
之前就说过了,板级支持包就是自己的头文件和源文件,如果不喜欢单独构建文件,也可以在工程文件的指定位置写,cubemx生成的代码每个源文件都留了自己编程的位置。
举例,在这两行中间写就行。在其他位置写的话,如果后续改动了cubemx里的配置,重新生成代码之后会被清空,所有要么写的时候注意位置,要么就构建板级支持包,班级支持包里的东西不会被清空

接下来是构建板级支持包的步骤
首先在工程文件任意位置,新建文件夹,一定要在工程文件里,我喜欢在工程文件的Drivers里,这个文件里原本就有两个生成的文件夹,名字可以随便起,不带中文就行

在里面新建源文件和头文件,因为是can,和生成的can区分开,我是这么命名的

在头文件加入条件编译,我习惯就在txt这写了,待会去keil里写也行,记得留出空行,不然会报错

回到keil,将刚才新建的文档加入工程,,按照一下步骤操作


添加完了如图所示,点击ok即可

在左侧project栏里可以看到添加成功的文件

接下来酱板级支持包的头文件添加到工程,不然编译的时候找不到,点击魔术棒选项,把文件夹添加进来,点击ok然后编译,不编译就识别不到

在源文件加入代码,编译无错,可以跳转到对应的头文件
最后,将串口打印重定向,用来调试,这段代码也可以放到自己的板级支持包里,但是这个工程并没有额外的串口功能要实现,所以放到usart.c里了,还需包含头文件,代码在下面,复制到对应位置
所需代码如下
/* USER CODE BEGIN 0 */
#include "stdio.h"
/* USER CODE END 0 */
/* USER CODE BEGIN 1 */
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
return (ch);
}
/* USER CODE END 1 */
至此准备工作完成
main.c里同样要加stdio.h的头文件,不然printf用不了,在mian.c里加入打印看看代码能不能执行
2.代码
2.1 发送
发送是很简单的,现在就可以写发送的代码了,在bsp_can.c里写,记得添加头文件
扩展帧版
bsp_can.h
#ifndef _BSP_CAN_H_
#define _BSP_CAN_H_
#include "can.h"
void can_send(uint32_t id,uint8_t *buf,uint8_t len);
#endif
bsp_can.c
#include "bsp_can.h"
CAN_TxHeaderTypeDef TxHeader;
void can_send(uint32_t id,uint8_t *buf,uint8_t len){
uint32_t TxMailbox;
TxHeader.StdId=id;
TxHeader.ExtId=id;
TxHeader.IDE=CAN_ID_EXT;
TxHeader.RTR=CAN_RTR_DATA;
TxHeader.DLC=len;
while(HAL_CAN_AddTxMessage(&hcan, &TxHeader, buf, &TxMailbox) != HAL_OK);
}
来到main.c,将我们自己的头文件包含进来

在对应位置,加入如下代码,别忘记打开can,打印是用来检测程序是否运行到这的
/* USER CODE BEGIN 2 */
HAL_CAN_Start(&hcan);
printf("55555\n");
uint8_t data[3]={0x1,0x2,0x3};
can_send(0x10080000,data,3);
/* USER CODE END 2 */
验证can能不能用有多种方法,可以自检,用回环模式,我这边采用了can分析仪,单独买的,很好用,我用的下面的,有客服会教怎么用

还有配套的软件,也有教程

代码写好以后,编译下载,可以看到发送的信息,发送函数完成

标准帧
bsp_can.h和扩展帧一样
#include "bsp_can.h"
CAN_TxHeaderTypeDef TxHeader;
void CAN_Send(uint32_t id, uint8_t* buf, uint8_t len)
{
uint32_t TxMailbox;
TxHeader.StdId = id;
TxHeader.IDE = CAN_ID_STD;
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.DLC = len;
while(HAL_CAN_AddTxMessage(&hcan, &TxHeader, buf, &TxMailbox) != HAL_OK);//发送
}
main.c验证,我换id为0x123,可以看到能收到

2.2 接收
过滤器
can的关键就是搞懂过滤器配置,过滤器主要用于接收的时候过滤id,下面这张图,可以看到有四种模式,很明显前两个模式是留给扩展帧,后两个模式是留给标准帧的

过滤器的代码是需要自己写的,代码里圈出来的部分就对应以上四种模式,只要配置这四个成员就能满足以上四个模式,接下来介绍这四个模式
先从最简单的开始
4个16位标识符列表模式
选择这个模式以后, FilterIdHigh,FilterIdLow,FilterMaskIdHigh,FilterMaskIdLow这四个变量全部变为id,每一个变量代表一个你想过滤的id,只需填入对应的id即可
标准帧id为11位,可以看到只有搞11位是id, 因为是数据帧,RTR位是0,因为是为标准帧IDE位是0,EXID也是0,没有扩展id,所以低五位全是零,所以代码里直接将id左移五位填入了。
void CAN_Filter_Config(void)
{
CAN_FilterTypeDef CAN_FilterInitStructure;
CAN_FilterInitStructure.FilterBank=0;//筛选器0
CAN_FilterInitStructure.FilterMode=CAN_FILTERMODE_IDLIST;//屏蔽模式
CAN_FilterInitStructure.FilterScale=CAN_FILTERSCALE_16BIT;//位宽 32
CAN_FilterInitStructure.FilterIdHigh=0x123<<5;
CAN_FilterInitStructure.FilterIdLow=0x346;
CAN_FilterInitStructure.FilterMaskIdHigh=0x0000;
CAN_FilterInitStructure.FilterMaskIdLow=0x136<<5;
CAN_FilterInitStructure.FilterFIFOAssignment=CAN_FILTER_FIFO0;
CAN_FilterInitStructure.FilterActivation=ENABLE;
CAN_FilterInitStructure.SlaveStartFilterBank = 14;
HAL_CAN_ConfigFilter(&hcan,&CAN_FilterInitStructure);
HAL_CAN_Start(&hcan);//开启CAN
HAL_CAN_ActivateNotification(&hcan,CAN_IT_RX_FIFO0_MSG_PENDING);//开启CAN-FIFO0中断
}
2个16位标识符屏蔽模式
选择这个模式以后, FilterIdHigh,FilterIdLow,对应需要过滤的id,FilterMaskIdHigh,FilterMaskIdLow根据需要,将需要保留的位写为1即可
void CAN_Filter_Config(void)
{
CAN_FilterTypeDef CAN_FilterInitStructure;
CAN_FilterInitStructure.FilterBank=0;//筛选器0
CAN_FilterInitStructure.FilterMode=CAN_FILTERMODE_IDLIST;//屏蔽模式
CAN_FilterInitStructure.FilterScale=CAN_FILTERSCALE_16BIT;//位宽 32
CAN_FilterInitStructure.FilterIdHigh=0x123<<5;//id1
CAN_FilterInitStructure.FilterIdLow=0x346<<5;//id2
CAN_FilterInitStructure.FilterMaskIdHigh=0x0000;//不过滤
CAN_FilterInitStructure.FilterMaskIdLow=0x0000;//不过滤
CAN_FilterInitStructure.FilterFIFOAssignment=CAN_FILTER_FIFO0;
CAN_FilterInitStructure.FilterActivation=ENABLE;
CAN_FilterInitStructure.SlaveStartFilterBank = 14;
HAL_CAN_ConfigFilter(&hcan,&CAN_FilterInitStructure);
HAL_CAN_Start(&hcan);//开启CAN
HAL_CAN_ActivateNotification(&hcan,CAN_IT_RX_FIFO0_MSG_PENDING);//开启CAN-FIFO0中断
}
1个32位标识符屏蔽模式
选择这个模式以后, FilterIdHigh,FilterIdLow,填入所需的id高位和低位,FilterMaskIdHigh,FilterMaskIdLow则成为掩码
掩码:有了掩码以后可以想保留哪位就保留哪位,将需与id一致的bit写1,不管的bit写0
举例:我选择扩展id=0x10241234,我想让id=0x102x1xxx的所有id进入,那么 FilterMaskIdHigh=0xfff0,FilterMaskIdLow=0x1000
这里有两个特例:
FilterMaskIdHigh=0x0000,FilterMaskIdLow=0x0000;这两位都为0时,代表不过滤,所有id均可进入
FilterMaskIdHigh=0xffff,FilterMaskIdLow=0xffff;代表只有一个id可以进入,就是与FilterIdHigh,FilterIdLow一模一样的id才可以进入,在例子下就是只有0x10241234这个id可以进入
void CAN_Filter_Config(void)
{
CAN_FilterTypeDef CAN_FilterInitStructure;
CAN_FilterInitStructure.FilterBank=0;//筛选器0
CAN_FilterInitStructure.FilterMode=CAN_FILTERMODE_IDMASK;//屏蔽模式
CAN_FilterInitStructure.FilterScale=CAN_FILTERSCALE_32BIT;//位宽 32
CAN_FilterInitStructure.FilterIdHigh=(0x10240000>>13) & 0xffff;//id高位
CAN_FilterInitStructure.FilterIdLow=(0x10240000|CAN_ID_EXT|CAN_RTR_DATA) & 0xffff;//ID低位
//我这个代码就是让高四位一样的所有id都进入
CAN_FilterInitStructure.FilterMaskIdHigh=0xfff0;
CAN_FilterInitStructure.FilterMaskIdLow=0x0000;
CAN_FilterInitStructure.FilterFIFOAssignment=CAN_FILTER_FIFO0;
CAN_FilterInitStructure.FilterActivation=ENABLE;
CAN_FilterInitStructure.SlaveStartFilterBank = 14;
HAL_CAN_ConfigFilter(&hcan,&CAN_FilterInitStructure);
HAL_CAN_Start(&hcan);//开启CAN
HAL_CAN_ActivateNotification(&hcan,CAN_IT_RX_FIFO0_MSG_PENDING);//开启CAN-FIFO0中断
}
2个32位标识符列表模式
选择这个模式以后, FilterIdHigh,FilterIdLow是一个32位的id,FilterMaskIdHigh,FilterMaskIdLow也是一个32位的id,只允许这里两个id进入
了解了过滤器之后,接着写接收的代码,用中断接收,前面已经选配好了,直接写代码,在刚才发送的基础上增加以下代码,我这里就是只要前四位一样就都接收进来
CAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[8] = {0};
void CAN_Filter_Config(void)
{
CAN_FilterTypeDef CAN_FilterInitStructure;
CAN_FilterInitStructure.FilterBank=0;//筛选器0
CAN_FilterInitStructure.FilterMode=CAN_FILTERMODE_IDMASK;//屏蔽模式
CAN_FilterInitStructure.FilterScale=CAN_FILTERSCALE_32BIT;//位宽 32
CAN_FilterInitStructure.FilterIdHigh=(0x10240000>>13) & 0xffff;//id高位
CAN_FilterInitStructure.FilterIdLow=(0x10240000|CAN_ID_EXT|CAN_RTR_DATA) & 0xffff;//ID低位
CAN_FilterInitStructure.FilterMaskIdHigh=0xffff;
CAN_FilterInitStructure.FilterMaskIdLow=0x0000;
CAN_FilterInitStructure.FilterFIFOAssignment=CAN_FILTER_FIFO0;
CAN_FilterInitStructure.FilterActivation=ENABLE;
CAN_FilterInitStructure.SlaveStartFilterBank = 14;
HAL_CAN_ConfigFilter(&hcan,&CAN_FilterInitStructure);
HAL_CAN_Start(&hcan);//开启CAN
HAL_CAN_ActivateNotification(&hcan,CAN_IT_RX_FIFO0_MSG_PENDING);//开启CAN-FIFO0中断
}
//可以直接将这两行放到main里,我这里为了方便,额外写了这个函数
void CAN_Config(void){
CAN_Filter_Config();
HAL_CAN_Start(&hcan);
}
用的是中断接收,还要写中断回调函数,为了方便我也在写在bsp_can.c里,写在mian里其实也行,但是还得把接收的句柄extern过去,有点麻烦,中断函数如下,里面每一步的打印都是用来检测程序是否运行到这的
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
printf("333\n");
HAL_CAN_GetRxMessage(hcan,CAN_RX_FIFO0,&RxHeader,RxData);//接收数据
if(RxHeader.ExtId==0x10240000){
printf("10240000\n");
}
__HAL_CAN_ENABLE_IT(hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
}
写好之后还是用测试软件发送,看看是否能按预期打印,可以看到,不符合过滤器的id连中断都进不了,在中断里可以根据id的不同进行不同的数据处理

更多推荐




所有评论(0)