STM32之bxCAN

CAN基础知识介绍

CAN介绍

什么是CAN

CAN(Controller Area Network),是ISO国际标准化的串行通信协议。为了满足汽车产业的“减少线束的数量”、“通过多个LAN,进行大量数据的高速通信”的需求。

  • 低速CAN(ISO11519)通信速率10~125Kbps,总线长度可达1000米。

  • 高速CAN(经典CAN)(ISO11898)通信速率125Kbps~1Mbps,总线长度≤40米。

  • CAN FD通信速率可达5Mbps,并且兼容经典CAN,遵循ISO11898-1 做数据收发。

CAN总线通讯网络

  • 一般CAN总线包含CAN控制器(开发板内CAN外设)、CAN收发器(CAH收发器芯片:TAJ1050、TAJ1042、SIT1050T)、CAN_High和CAN_Low

  • CAN总线由两根线(CAN_High、CAN_Low)组成的,允许挂载多个设备节点(一般低速CAN20个,高速CAN30个,一般取决于CAN收发器的芯片)。

闭环总线网络(高速CAN)

开环总线网络(低速CAN)

CAN总线特点

  • 多主控制:每个设备都可以主动发送数据。

  • 系统的柔软性:没有类似地址的信息,添加设备不改变原来总线的状态。

  • 通信速度:速度快,距离远。

  • 具有错误检测和错误通知和错误恢复功能。

  • 故障封闭:判断故障类型,并且进行隔离。

  • 连接节点多:CAN协议中,我们可以挂载多个节点(每个节点由CAN控制器和CAN收发器组成) ,通过总线来实现节点通讯,与其他协议不同的是,CAN协议不对节点的地址进行编码,而是对节点的数据内容进行编码。

CAN应用场景

CAN总线协议已经广泛应用在汽车电子、工业自动化、船舶、医疗设备、工业设备等方面。

CAN物理层

CAN物理层特性

  • CAN使用差分信号(CAN_High - CAN_Low)进行数据传输,根据CAN_High和CAN_Low上的电位差来判断总线电平。

  • 总线电平分为显性电平(逻辑0)和隐性电平(逻辑1)。

  • 显性电平具有优先权(即当某个发送方发送一个显性电平,那么总线都是逻辑0)。发送方通过使总线电平发生变化,将消息发给接收方。

  • CAN总线电平与CAN_High和CAN_Low的关系:

CAN数据传输

发送数据:控制器发送一个逻辑信号,收发器将这个逻辑信号变成差分信号传送到总线中。

接收数据:收发器将差分信号转化为逻辑信号,即0或1的二进制编码;

CAN协议层

CAN帧种类介绍

CAN总线以“帧”形式进行通信。CAN协议定义了5种类型的帧,分别是数据帧、遥控帧、错误帧、过载帧和间隔帧,其中数据帧最常用。

帧类型

帧作用

数据帧

用于发送单元向接收单元传输数据的帧

遥控帧

用于接收单元向具有相同ID的发送单元请求数据的帧

错误帧

用于当检测出错误时向其他单元通知错误的帧

过载帧

用于接收单元通知其他尚未做好接收准备的帧

间隔帧

用于将数据帧以及遥控帧与前面的帧分离开来的帧

CAN的其他帧的分析方法与数据帧基本一样,我们只要搞懂对数据帧就基本可以搞懂CAN的其他帧了。

CAN数据帧介绍

数据帧由7段组成,分为标准帧(CAN2.0A)和扩展帧(CAN2.0B),这两者的区别主要体现在仲裁段和控制段。

  • 帧起始:表示数据帧开始的段,一位显性信号(逻辑0)。

  • 仲裁段:表示该帧优先级的段,优先级。

  • 控制段:表示数据的字节数即保留位的段。

  • 数据段:数据的内容,一帧可发送0~8字节数。

  • CRC段:检查帧的传输错误的段。

  • ACK段:表示确认正常接收的段。

  • 帧结束:表示数据帧结束的段,7位隐性信号(逻辑1)。

标准数据帧

  • ID(标识符位):优先级。

  • RTR(远程发送请求位):0是数据帧,1是遥控帧。

  • IDE(扩展标识符位):用于区别是标准帧还是扩展帧,1是扩展帧,0是标准帧。

  • DLC(数据长度编码位):告知该数据帧的长度。

  • Data Field:数据长度,0~8字节。

  • CRC:循环校验序列。

  • DEL:界定符。

  • ACK:发送单元发送数据时将该位拉高,其他接收单元接收数据时将该位拉低。

扩展数据帧

  • ID(标识符位):优先级。

  • RTR(远程发送请求位):0是数据帧,1是遥控帧。

  • SRR:用在扩展格式,替代RTR,无实质作用。

  • IDE(扩展标识符位):用于区别是标准帧还是扩展帧,1是扩展帧,0是标准帧。

  • DLC(数据长度编码位):告知该数据帧的长度。

  • Data Field:数据长度,0~8字节。

  • CRC:循环校验序列。

  • DEL:界定符。

  • ACK:发送单元发送数据时将该位拉高,其他接收单元接收数据时将该位拉低。

CAN时序介绍

CAN总线以“位同步”机制实现对电平的正确采样。位数据都由四段组成:同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)和相位缓冲段2(PBS2),每段又由多个位时序Tq组成。

  • 同步段(SS):1Tq

  • 传播时间段(PTS):1~8Tq

  • 相位缓冲段1(PBS1):1~8Tq

  • 相位缓冲段2(PBS2):2~8q

  • 采样点:读取总线电平,并将读取到的电平作为位值的点,如该位数据的采样点读取的电平是显性电平。

  • 注意:当节点监测到总线上信号的跳变在SS段范围内,表示节点与总线的时序是同步,此时采样点的电平即该位的电平。

  • 根据位时序,就可以计算CAN通信的波特率?。

数据同步过程

CAN为了实现对总线电平信号的正确采样,会进行数据同步,数据同步分为硬件同步和软件同步。

硬件同步

节点通过CAN总线发送数据,一开始发送帧起始信号。总线上其他节点会检测帧起始信号在不在位数据的SS段内,来判断内部时序是否与总线同步。

如果不在SS段内,这种情况下,采样点获得的电平状态时不正确的。所以节点会使用硬件同步方式来进行调整,把自己的SS段平移到检测到边沿(帧起始信号是从隐性电平到显性电平的,具有一个下降沿)的地方,从而获得同步,同步情况下,采样点获得的电平状态才是正确的。

数据硬件同步前

数据硬件同步后

再同步

再同步利用普通数据位的边沿信号(帧起始信号时特殊的边沿信号)进行同步。再同步的方式分为两种情况:超前和滞后,即边沿信号与SS段的相对位置。

再同步时,PSB1和PSB2增加或者减少的时间称为PJW(再同步补偿宽度),一般是1~4个Tq。限定了SJW值后,再同步时,不能增加(修改)限定长度的SJW值。SJW值较大时,吸收误差能力更强,但是通讯速度会下降。

边沿信号滞后

边沿信号超前

CAN总线仲裁(优先级决定)

CAN总线处于空闲状态时,最先开始发送消息的单元获得发送权。当多个单元同时开始发送时,从仲裁段(报文ID)的第一位开始仲裁,连续输出显性电平最多的单元可继续发送,即首先出现隐性电平的单元失去对总线的占有权而变为接收单元。(即报文ID越小的单元,它的优先级越高)

在总线仲裁中失利的单元(即由发送转变成接收的单元)会自动检测总线空闲,在第一时间再次尝试发送。

STM32的CAN控制器介绍

CAN控制器介绍

STM32的CAN控制器(bxCAN)支持CAN2.0A和CAN2.0B Active版本协议。CAN2.0A只能处理标准数据帧且扩展帧的内容会识别错误,而CAN2.0B Active可以处理标准数据帧和扩展数据帧。CAN2.0B Passive只能处理标准数据帧且扩展数据帧的内容会忽略。

CAN控制器主要特点

  • 波特率最高可达1Mbps。

  • 支持时间出发通信(CAN的硬件内部定时器可以在TX/RX的帧起始位的采样点位置产生时间戳)。

  • 具有3级发送邮箱。

  • 具有3级深度的2个接收FIFO。

  • 可变的过滤器组(最多28个),STM32F103xxxx只有14个。

CAN控制器工作模式

CAN控制器的工作模式有三种:初始化模式、正常模式和睡眠模式。

CAN控制器的测试模式

CAN控制器的测试模式有三种:静默模式、环回模式和环回静默模式。(在初始化模式下可以配置CAN控制器的测试模式)

CAN控制器的框图

STM32F103xxxx没有CAN2(从)。

CAN内核(①)

包含各种控制/状态/配置寄存器,可以配置模式、波特率灯。

发送邮箱(②)

用来缓存待发送的报文,最多可以缓存3个报文,并且包含发送调度,可以决定哪个报文(报文ID)先发送。

发送处理

发送优先级由邮箱中报文的标识符(ID)决定,标识符数值越低有最高优先级,如果标识符的值相同,邮箱小的先发送。

接收FIFO(③)

当有效报文通过接收过滤器后就进入FIFO,可以在FIFO里读取报文。

接收处理

有效报文指的是(数据帧知道EOF段的最后一位都没有错误),且通过过滤器组队标识符过滤。

接收过滤器(④)

当总线上报文数据量很大时,总线上的设备会频繁获取报文,占用CPU。过滤器的作用是选择性接收有效报文,减轻系统的负担,根据滤波器的配置将报文分别送往FIFO0或FIFO1。

过滤器的作用

每个过滤器组都有两个32位寄存器CAN_FxR1和CAN_FxR2。过滤器组的功能(与寄存器的位宽和寄存器的选择模式有关)不同,寄存器的作用不尽相同。

位宽

寄存器位宽可以设置32位或者16位,寄存器存储的内容就有所区别。(STDID:标准ID,EXTID:扩展ID)

过滤器组寄存器

32位

16位(寄存器由两部分组成)

CAN_FxR1

STDID[10:0]、EXTID[17:0]、IDE、RTR

STDID[10:0]、EXTID[17:15]、IDE、RTR

CAN_FxR2

STDID[10:0]、EXTID[17:0]、IDE、RTR

STDID[10:0]、EXTID[17:15]、IDE、RTR

工作模式

工作模式可以设置标识符屏蔽模式或标识符列表模式,寄存器内容的功能就有所区别。

  • 标识符屏蔽模式:它把可接收报文 ID 的某几位作为列表,这几位被称为屏蔽位,可以把它理解成关键字搜索,只要屏蔽位(关键字)相同,就符合要求,报文就会被保存到接收 FIFO。

  • 标识符列表模式:它把要接收报文的 ID 列成一个表,要求报文 ID 与列表中的某一个标识符完全相同才可以接收,可以理解为白名单管理。

作用

通常32位用来筛选扩展数据帧,16位用来筛选标准数据帧。

模式

说明

32位标识符屏蔽模式

CAN_FxR1 存储 ID,CAN_FxR2 存储哪个位必须要与 CAN_FxR1 中的ID 一致。(过滤出1组符合条件的报文)

32 位标识符列表模式

CAN_FxR1和 CAN_FxR2 各存储 1个ID(过滤出2个符合条件的报文)

16 位标识符屏蔽模式

CAN_FxR1低 16 位存储 ID,高 16 位存储哪个位必须要与低 16 位的ID一致;

CAN_FxR2低 16 位存储 ID,高 16 位存储哪个位必须要与低 16 位的ID一致。(过滤出2组符合条件的报文)

16 位标识符模式

CAN_FxR1和 CAN_FxR2 各存储2个ID(过滤出4个符合条件的报文)

举例:使用32位掩码模式过滤出一组符合条件的报文

设置过滤器组0为32位屏蔽位模式(过滤出一组符合条件的报文)

bit

31~24

23~16

15~8

7~0

ID

CAN_F0R1

(0xFFFF0000)

1111 1111

1111 1111

0000 0000

0000 0000

屏蔽位

CAN_F0R1

(0xFF00FF00)

1111 1111

0000 0000

1111 1111

0000 0000

映像

STID[10:3]

STID[2:0]、EXID[17:13]

EXID[12:5]

EXID[4:0]、IDERTR、0

过滤出ID

1111 1111

xxxx xxxx

0000 0000

xxxx xxxx

  • 屏蔽位寄存器中位值为1,表示ID要必须匹配;位值为0则不关心。

  • 在使能过滤器的情况下,总线上广播的报文ID与过滤器的配置都不匹配,CAN控制器会丢弃该报文,不会进入到接收FIFO中。

  • 注意:标识符选择位IDE和帧类型RTR需要一致,不同过滤器组的工作模式可以设置不同。

CAN控制器的位时序

STM32的CAN控制器的位时序分为三段:同步段(SYNC_SEG)、时间段1(BS1 = PTS + PBS1)、时间段2(BS2)。(主要用来计算波特率,CAN通信双方的波特率保持一致才能通信成功)

CAN相关寄存器介绍

寄存器

名称

作用

CAN_MCR

CAN主控制寄存器

主要负责CAN工作模式的配置

CAN_MSR

CAN主状态寄存器

主要用来设置CAN的当前状态

CAN_TSR

CAN发送状态寄存器

主要用来确定报文是否发送出去

CAN_RF0R

CAN接收FIFO 0寄存器

主要用来获取FIFO 0的报文数目

CAN_RF1R

CAN接收FIFO 1寄存器

主要用来获取FIFO 1的报文数目

CAN_IER

CAN中断使能寄存器

主要用来使能CAN的某些中断位

CAN_ESR

CAN错误状态寄存器

主要用来接收一些错误信息

CAN_BTR

位时序寄存器

用来设置分频/TBS1/TBS2/TSWJ 等参数,设置测试模式

CAN_(T/R)IxR

标识符寄存器

存放(待发送/接收)的报文ID、扩展ID、IDE位及RTR位

CAN_(T/R)DTxR

数据长度和时间戳寄存器

存放(待发送/接收)报文的DLC段

CAN_(T/R)DIxR

低位数据寄存器

存放(待发送/接收)报文数据段的Data0~Data3的内容

CAN_(T/R)DHxR

高位数据寄存器

存放 (待发送/接收)报文数据段的Data4~Data7的内容

CAN_FM1R

过滤器模式寄存器

用于设置各过滤器组的选择模式

CAN_FS1R

过滤器位宽寄存器

用于设置各过滤器组的位宽

CAN_FFA1R

FIFO关联寄存器

用于设置报文通过过滤器后,被存入的FIFO

CAN_FA1R

过滤器激活寄存器

用于开启对应的过滤器组

CAN_FxR(1/2)

过滤器组x寄存器

根据位宽和模式设置不同,CAN FXR1和FXR2功能不同

CAN相关HAL库驱动介绍

STM32的hal库关于CAN的结构体

CAN_InitTypeDef

CAN初始化结构体

typedef struct
{
    uint32_t Prescaler;  /* 预分频 */

    uint32_t Mode;  /* 工作模式 */ 

    uint32_t SyncJumpWidth;  /* 再次同步跳跃宽度 */

    uint32_t TimeSeg1;  /* 时间段1(BS1)长度 */ 

    uint32_t TimeSeg2;  /* 时间段2(BS2)长度 */ 

    FunctionalState TimeTriggeredMode;  /* 时间触发通信模式 */

    FunctionalState AutoBusOff;  /* 总线自动关闭模式 */

    FunctionalState AutoWakeUp;  /* 自动唤醒 */ 

    FunctionalState AutoRetransmission;  /* 自动重传 */ 

    FunctionalState ReceiveFifoLocked;  /* 接收FIFO锁定 */

    FunctionalState TransmitFifoPriority;  /* 传输FIFO优先级 */ 

} CAN_InitTypeDef;

CAN_FilterTypeDef

CAN过滤器配置结构体。

typedef struct
{
  uint32_t FilterIdHigh;  /* ID高字节 */          

  uint32_t FilterIdLow;  /* ID低字节 */           

  uint32_t FilterMaskIdHigh;  /* 屏蔽位高字节 */      

  uint32_t FilterMaskIdLow;  /*屏蔽位低字节 */       

  uint32_t FilterFIFOAssignment;  /* 过滤器关联FIFO */  

  uint32_t FilterBank;  /* 选择过滤器组 */          

  uint32_t FilterMode;  /* 过滤器模式 */          

  uint32_t FilterScale;  /* 过滤器位宽 */           

  uint32_t FilterActivation;  /* 过滤器使能 */      

  uint32_t SlaveStartFilterBank;  /* 从CAN选择启动过滤器组(单CAN情况下,该成员没有意义) */  

} CAN_FilterTypeDef;

过滤器配置模式

CAN_FxR1[31:16]

CAN_FxR1[15:0]

CAN_FxR2[31:16]

CAN_FxR2[15:.0]

32位标识符屏蔽模式

FilterIdHigh

FilterIdLow

FilterMaskIdHigh

FilterMaskIdLow

32 位标识符列表模式

FilterIdHigh

FilterIdLow

FilterMaskIdHigh

FilterMaskIdLow

16 位标识符屏蔽模式

FilterMaskIdLow

FilterIdLow

FilterMaskIdHigh

FilterIdHigh

16 位标识符模式

FilterMaskIdLow

FilterIdLow

FilterMaskIdHigh

FilterIdHigh

需要结合印象来赋值:

  • 32位位宽:STID[10:3]、STID[2:0]、EXID[17:13]、EXID[12:5]、EXID[4:0]、IDE、ETR、0

  • 16位位宽:STID[10:3]、STID[2:0]、RTR、IDE、EXID[17:15]

32位标识符列表模式过滤器配置代码

32位标识符屏蔽模式过滤器配置代码

CAN_TxHeaderTypeDef

CAN发送消息结构体。

typedef struct
{
  uint32_t StdId;  /* 标准标识符(当CAN数据帧是扩展数据帧时,该成员无意义) */ 

  uint32_t ExtId;  /* 扩展标识符(当CAN数据帧是标准数据帧时,该成员无意义) */ 
  
  uint32_t IDE;  /* 帧格式(该成员决定标准数据帧或扩展数据帧) */ 

  uint32_t RTR;  /* 帧类型(该成员决定数据帧或遥控帧) */ 

  uint32_t DLC;  /* 数据长度 */ 

  FunctionalState TransmitGlobalTime;  /* 发送时间标记(时间戳) */

} CAN_TxHeaderTypeDef;

CAN_RxHeaderTypeDef

CAN接收消息结构体。

typedef struct
{
  uint32_t StdId;  /* 标准标识符(当CAN数据帧是扩展帧时,该成员无意义) */ 

  uint32_t ExtId;  /* 扩展标识符(当CAN数据帧是标准帧时,该成员无意义) */ 

  uint32_t IDE;  /* 帧格式(该成员决定标准帧或扩展帧) */ 

  uint32_t RTR;  /* 帧类型(该成员决定数据帧或遥控帧) */ 

  uint32_t DLC;  /* 数据长度 */ 

  uint32_t Timestamp;  /* 时间戳 */ 

  uint32_t FilterMatchIndex;  /* 过滤器号 */ 

} CAN_RxHeaderTypeDef;

STM32的hal库关于CAN的函数

__HAL_RCC_CAN1_CLK_ENABLE()

使能CAN时钟。(使用STM32CubeMX会自动配置)

HAL_CAN_Init()

初始化CAN。(使用STM32CubeMX会自动配置)

HAL_CAN_ConfigFilter()

配置CAN接收过滤器。(将配置好的接收过滤器结构体与CAN外设连接起来)

原型:
HAL_StatusTypeDef HAL_CAN_ConfigFilter(CAN_HandleTypeDef *hcan, CAN_FilterTypeDef *sFilterConfig)

参数:
CAN_HandleTypeDef *hcan:CAN句柄
CAN_FilterTypeDef *sFilterConfig:接收过滤器结构体地址

实例:
HAL_CAN_ConfigFilter(&hcan,&myCan_filter); /* 配置CAN外设的接收过滤器 */

HAL_CAN_Start()

启动CAN外设。

原型:
HAL_StatusTypeDef HAL_CAN_Start(CAN_HandleTypeDef *hcan)

参数:
CAN_HandleTypeDef *hcan:CAN句柄

实例:
HAL_CAN_Start(&hcan); /* 启动CAN外设 */

HAL_CAN_ActivateNotification()

使能CAN中断。(该函数会调用__HAL_CAN_ENABLE_IT())

HAL_CAN_AddTxMessage()

添加数据到某个发送邮箱。

原型:
HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox)

参数:
CAN_HandleTypeDef *hcan:CAN句柄
CAN_TxHeaderTypeDef *pHeader:CAN发送结构体地址
uint8_t aData[]:待发送数据
uint32_t *pTxMailbo:发送邮箱

实例:
CAN_TxHeaderTypeDef myCan_tx; /* CAN发送结构体 */
uint32_t tx_mail = CAN_TX_MAILBOX0; /* 数据发送邮箱为邮箱0 */
uint8_t buf[8] = "zhu_tou";

HAL_CAN_AddTxMessage(&hcan,&myCan_tx,buf,&tx_mail); /* 从buf中发送数据到邮箱0 */

HAL_CAN_GetTxMailboxesFreeLevel()

获取发送邮箱的状态。

原型:
uint32_t HAL_CAN_GetTxMailboxesFreeLevel(CAN_HandleTypeDef *hcan)

参数:
CAN_HandleTypeDef *hcan:CAN句柄

实例:
while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) != 3); /* 等待数据发送完毕:当发送邮箱的3个邮箱都为空时,返回值为3 */

HAL_CAN_GetRxMessage()

从接收FIFO接收数据。

原型:
HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[])

参数:
CAN_HandleTypeDef *hcan:CAN句柄
uint32_t RxFifo:接收FIFO
CAN_RxHeaderTypeDef *pHeader:CAN接收结构体地址
uint8_t aData[]:接收数据存储地址

实例:
CAN_RxHeaderTypeDef myCan_rx; /* CAN接收结构体 */
uint8_t rcvbuf[8] = {0};

HAL_CAN_GetRxMessage(&hcan,CAN_RX_FIFO0,&myCan_rx,buf); /* 从接收FIFO 0接收消息到buf中 */

HAL_CAN_GetRxFifoFillLevel()

获取接收FIFO的状态。

原型:
uint32_t HAL_CAN_GetRxFifoFillLevel(CAN_HandleTypeDef *hcan, uint32_t RxFifo)

参数:
CAN_HandleTypeDef *hcan:CAN句柄
uint32_t RxFifo:接收FIFO

实例:
/* 判断接收FIFO 0是否为空 */
if(HAL_CAN_GetRxFifoFillLevel(&hcan,CAN_RX_FIFO0) == 0){
return 0; /*为空返回0*/
}

CAN基本使用步骤

  1. CAN外设初始化MX_CAN_Init()

  • 配置波特率、CAN功能、接收过滤器等等。

  1. 使能CAN时钟和初始化CAN相关引脚HAL_CAN_MspInit()

  • 使用STM32CubeMX会自动配置。

  1. CAN数据的接收和发送

  • CAN数据的接收:HAL_CAN_AddTxMessage()

  • 获取发送邮箱的状态:HAL_CAN_GetTxMailboxesFreeLevel()

  • CAN数据的发送:HAL_CAN_GetRxMessage()

  • 获取接收FIFO的状态:HAL_CAN_GetRxFifoFillLevel()

  1. 使用CAN的中断(可选)

  • 使能CAN的相关中断:__HAL_CAN_ENABLE_IT()

  • 配置NVIC

  • 编写中断服务函数

CAN实验1(使用回环模式实现自发自收)

通过CAN总线将数据自发自收,每按下一次按键就发送一次数据,并将接收的数据显示在串口上。(接收过滤器接收所有报文,不进行筛选)

使用STM32CubeMX创建工程

配置SYS

配置RCC

配置GPIO

PA0(按键)配置成输入引脚。

配置串口信息(UART1)

配置CAN

配置工程名称、工程路径

选择固件库

生成工程

使用MicroLIB库

can.c文件编写

  1. CAN外设初始化:主要配置波特率、CAN功能、接收过滤器

  1. CAN发送数据函数

  1. CAN接收数据函数

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    can.c
  * @brief   This file provides code for the configuration
  *          of the CAN instances.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/

#include "can.h"
#include <stdio.h>

/* USER CODE BEGIN 0 */

CAN_TxHeaderTypeDef myCan_tx;  /* CAN发送结构体 */
CAN_RxHeaderTypeDef myCan_rx;  /* CAN接收结构体 */

/* USER CODE END 0 */

CAN_HandleTypeDef hcan;

/* CAN外设初始化 */
void MX_CAN_Init(void)
{

  /* USER CODE BEGIN CAN_Init 0 */

  /* USER CODE END CAN_Init 0 */

  /* USER CODE BEGIN CAN_Init 1 */

  /* USER CODE END CAN_Init 1 */
  hcan.Instance = CAN1;
  hcan.Init.Mode = CAN_MODE_LOOPBACK;  /* 环回模式:自发自收 */
    
    /* 波特率相关 */
    hcan.Init.Prescaler = 4;  /* 分频系数 */
  hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;  /* 重新同步跳跃宽度 */
  hcan.Init.TimeSeg1 = CAN_BS1_9TQ;  /* 时间段1 */
  hcan.Init.TimeSeg2 = CAN_BS2_8TQ;  /* 时间段2 */
    
    /* CAN功能设置 */
  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 */

    /* 配置接收过滤器 */
    CAN_FilterTypeDef myCan_filter;  /* CAN过滤器结构体 */
    
    myCan_filter.FilterMode = CAN_FILTERMODE_IDMASK;  /* 工作模式:屏蔽位模式 */
    myCan_filter.FilterScale = CAN_FILTERSCALE_32BIT;  /* 32位位宽 */
    
    /* 过滤器接收所有报文,不进行筛选 */
    myCan_filter.FilterIdHigh = 0;
    myCan_filter.FilterIdLow = 0;
    myCan_filter.FilterMaskIdHigh = 0;
    myCan_filter.FilterMaskIdLow = 0;
    
    myCan_filter.FilterBank = 0;  /* 选择过滤器组 */
    myCan_filter.FilterFIFOAssignment = CAN_FilterFIFO0;  /* 过滤器关联FIFO 0 */
    myCan_filter.FilterActivation = CAN_FILTER_ENABLE;  /* 使能过滤器 */
    
    HAL_CAN_ConfigFilter(&hcan,&myCan_filter);  /* 配置CAN外设的接收过滤器 */
    
  /* USER CODE END CAN_Init 2 */

}

/* 使能CAN时钟和初始化相关引脚 */
void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(canHandle->Instance==CAN1)
  {
  /* USER CODE BEGIN CAN1_MspInit 0 */

  /* USER CODE END CAN1_MspInit 0 */
    /* CAN1 clock enable */
    __HAL_RCC_CAN1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**CAN GPIO Configuration
    PA11     ------> CAN_RX
    PA12     ------> CAN_TX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_11;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN CAN1_MspInit 1 */

  /* USER CODE END CAN1_MspInit 1 */
  }
}

void HAL_CAN_MspDeInit(CAN_HandleTypeDef* canHandle)
{

  if(canHandle->Instance==CAN1)
  {
  /* USER CODE BEGIN CAN1_MspDeInit 0 */

  /* USER CODE END CAN1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_CAN1_CLK_DISABLE();

    /**CAN GPIO Configuration
    PA11     ------> CAN_RX
    PA12     ------> CAN_TX
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_11|GPIO_PIN_12);

  /* USER CODE BEGIN CAN1_MspDeInit 1 */

  /* USER CODE END CAN1_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */

/* 发送消息数据函数 */
uint8_t can_send_message(uint32_t id,uint8_t *buf,uint8_t len)
{
    uint32_t tx_mail;  /* 数据发送邮箱 */
    myCan_tx.RTR = CAN_RTR_DATA;  /* 帧类型为数据帧 */
    myCan_tx.IDE = CAN_ID_EXT;  /* 帧格式为扩展数据帧 */
    myCan_tx.ExtId = id;  /* 扩展数据帧ID */
    myCan_tx.DLC = len;  /* 数据长度 */

        /*如果三个发送邮箱都阻塞了就一直等待直到其中某个邮箱空闲*/
        
        while((HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0)){ 
            printf("没有邮箱空闲\r\n");
            HAL_Delay(1000);
    }
        
        /* 哪个邮箱空闲,就选哪个邮箱作为发送邮箱 */
        if ((hcan.Instance->TSR & CAN_TSR_TME0) != 0U){
        tx_mail = CAN_TX_MAILBOX0;
    } else if ((hcan.Instance->TSR & CAN_TSR_TME1) != 0U) {
        tx_mail = CAN_TX_MAILBOX1;
    } else if ((hcan.Instance->TSR & CAN_TSR_TME2) != RESET) {
        tx_mail = CAN_TX_MAILBOX2;
    }
    
        /* 从buf中发送消息 */
    if(HAL_CAN_AddTxMessage(&hcan,&myCan_tx,buf,&tx_mail) != HAL_OK){
            return 1;
        }  
        
        /* 等待数据发送完毕:当发送邮箱的3个邮箱都为空时,返回值为3 */
    while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) != 3);
        printf("邮箱为空\r\n");
        return 0;
}

/*接收数据函数*/
uint8_t can_receive_message(uint8_t *buf)
{
    /* 判断接收FIFO 0是否为空 */
    if(HAL_CAN_GetRxFifoFillLevel(&hcan,CAN_RX_FIFO0) == 0){
        return 0;  /*为空返回0*/
    } 
    
    HAL_CAN_GetRxMessage(&hcan,CAN_RX_FIFO0,&myCan_rx,buf);  /* 接收消息到buf中 */
    return myCan_rx.DLC;  /*不为空返回数据长度*/
}

/* USER CODE END 1 */

can.h文件编写

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    can.h
  * @brief   This file contains all the function prototypes for
  *          the can.c file
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __CAN_H__
#define __CAN_H__

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

extern CAN_HandleTypeDef hcan;

/* USER CODE BEGIN Private defines */

/* USER CODE END Private defines */

void MX_CAN_Init(void);

/* USER CODE BEGIN Prototypes */

/* 发送消息数据函数 */
uint8_t can_send_message(uint32_t id,uint8_t *buf,uint8_t len);

/*接收数据函数*/
uint8_t can_receive_message(uint8_t *buf);

/* USER CODE END Prototypes */

#ifdef __cplusplus
}
#endif

#endif /* __CAN_H__ */

main.c文件编写

  1. 启动CAN外设

  1. 发送数据

  1. 接收数据

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "can.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include <stdio.h>


/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

uint8_t rcvlen = 0;
uint8_t rcvbuf[8] = {0};
uint8_t can = 0;
uint8_t sendbuf[8] = "zhu_tou";  

/* 重写stdio.h文件中的prinft()里的fputc()函数 */
int fputc(int my_data,FILE *p)
{
    unsigned char temp = my_data;
    /* 改写后,使用printf()函数会将数据通过串口一发送出去 */
    HAL_UART_Transmit(&huart1,&temp,1,0xffff);  /* 0xfffff为最大超时时间 */
    return my_data;
}

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_CAN_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
    
    printf("haozige\r\n");  /* 确认串口是否启动 */
    HAL_CAN_Start(&hcan);  /* 启动CAN外设 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
        
        if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){  /* 当按键被按下*/
            HAL_Delay(50);  //按键防抖
            if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){
                //HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
                if(can_send_message(0x12345678,sendbuf,8)){
                                    printf("发送失败\r\n");
                                }  /* CAN发送数据,一次发送8字节 */
            }
        }
        
        rcvlen = can_receive_message(rcvbuf);  /* CAN接收数据 */
        if(rcvlen){
            printf("%s\r\n",rcvbuf);  /* 把接收到的数据打印到串口上 */
        }
              HAL_Delay(100);
        
    }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  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();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  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();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
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 */

猜你喜欢

转载自blog.csdn.net/weixin_54076783/article/details/129502057