STM32
直播中

靓仔峰

9年用户 1144经验值
擅长:可编程逻辑
私信 关注
[问答]

怎样使用Cube和HAL库去实现CAN通讯呢

Cube软件的注意事项有哪些?

怎样使用Cube和HAL库去实现CAN通讯呢?

回帖(1)

王淑兰

2021-10-9 16:59:52
  今天有个很大的收获,就是知道了什么是【回调函数】哈哈哈。。。
  好吧言归正传,这个文章是我在调试我的ABS控制器的时候写的,本来打算写在项目笔记里,但是觉得这应该算是通用型的知识,而且我一开始是用野火的开发板调试的,所以打算把它写在STM32学习笔记下面。
  其实这不应该是第二篇学习笔记,因为至少之前写过一篇SPI通讯的,虽然是和5048磁编码器一起写的,但是也是用的stm32,不过无所谓。
  我之前学STM32的时候用的是标准库函数编程的方法,后来又接触到了寄存器编程(托欣师兄的福。。。),可是后来我从罗明睿那里知道了Cube+HAL库编程的方法,对我的启发蛮大的。
  HAL是(hardware abstract layer)硬件抽象层的意思,对函数名和函数体进行了重新定义、重新编写,标准化更高,更加通用,方便移植。配合Cube软件可以实现STM32系列MUC的可视化配置,自动生成工程代码,不需要再自己建立工程模板,而且可以随时添加新的外设和功能,不影响用户自己编写的代码,不过缺点是比较臃肿,占用空间较大。
  HAL库是ST近年大力推广的编程方式,在一些高端芯片中只有HAL库没有标准库(大概是这样的)。
  好吧,再言归正传,其实如果用标准库的话,野火开发板提供了详细的教学视频和应用例程,我可以直接用,但是我还是决定试一试Cube+HAL库的方式,虽然后来我觉得好像走进了一个新坑。。。
  〇 Cube软件的注意事项
  首先要注意的问题是,工程目录的路径里不能有中文,如果有中文的话,他生成代码的时候会报错,但不会告诉你是因为路径名字的问题,但是当你用keil打开编译的时候就会不成功。。。。切记!
  Cube有个让我很不习惯的地方就是,对于单片机的引脚,特别是一些重要的功能引脚来说,如果不在Cube里进行配置,他就会把它关闭,而不是默认开启。
  比较重要的就是SWD的下载端口,如果在一开始没有配置的话,当你第一次把程序烧录进去之后,下载端口就被关闭了,用正常的方法就无法再给单片机下载程序了。。。虽然可以通过拉高boot0的方法重置,但是还是要主要第一次配置的时候,记得在SYS里的debug模式里面选择Serial Wire。
  
  另外就是外部晶振,Cube里默认是使用内部高速振荡器,如果用外部晶振,需要在RCC里面的HSE里选择Crystal/Ceramic Resonator,然后在时钟配置里选择外部晶振。
  
  
  下面开始具体的学习(爬坑)过程。
  〇 在Cube里面配置相关功能
  因为我用的是野火的开发板,所以首先在芯片选择里面选STM32F103ZE
  
  然后就是按照上面说的方法配置仿真器引脚和晶振引脚。
  然后对时钟树进行配置。
  
  时钟树的讲解在野火的资料和视频里讲的很详细了,第一次用图形化方式配置,还有点小激动。。。这里需要注意的是CAN总线是挂载在APB1高速总线上的,PCLK1的频率直接决定了CAN模块的参数配置。
  接下来配置端口,根据野火的原理图,我们知道can的端口用的是PB8和PB9.
  
  于是在Cube里选择相应端口。
  
  然后在configuration里面配置相应参数。
  
  这里需要注意的是,CAN通讯里面有波特率的概念(最大1MHz),但是并不是直接设置波特率的速率,而是分别设置预分频系数,每一段指令周期的时间,最后通过计算才得出can总线的波特率。
  CAN总线的物理层和协议层在野火的资料和视频里有很详细的讲解,而且可以看一看瑞萨科技的圣经《CAN入门书》,在这里我稍微搬一搬。其实我也是昨天才弄懂了can总线的波特率。。。
  ♣ CAN总线的基础知识
  ○ 物理层
  
  CAN总线的硬件结构,can总线用差分线传输数据,除了can控制器外必须配有can收发器,总线两端需要串联120欧电阻。
  
  CAN总线用CANH与CANL两线的电压差表示0和1,差为0V表示1(隐性电平),差为2V表示0(显性电平)。
  ○ 协议层
  
  CAN信号的每一位分为如图几个部分,最小的时间单位是Tq(Time Quantum),其作用和讲解就不搬了,这几部分的时长一起决定了CAN总线的波特率。
  
  CAN总线的报文结构,如图所以,详细的就不搬了。
  ○ STM32的CAN模块介绍
  STM32的CAN架构
  
  CAN控制器的基本参数
  
  CAN的四种工作模式
  
  位时序和波特率
  
  
  此外,STM32的CAN模块还包括发送邮箱,接收FIFO,过滤器等等,我就不搬了,可以参见野火的《零死角玩转STM32》一书或相关资料。
  搬完了CAN的基础知识以及STM32的can模块相关信息,然后再来看Cube的设置界面
  
  可以看出就是对上面提到的一些参数的配置。
  这里配置成1MHz的工作模式,普通收发模式。理论上来说,一般调试设备的时候都先选成回环模式或者静默回环模式,但是因为我不会STM32的在线仿真,如果设置成回环模式的话,需要同时保证发送程序和接收程序都正确才能调试成功,不过我打算先调试发送程序,再调试接收程序,用CAN信号分析仪作为辅助工具,所以这里选择普通模式。
  然后在project manager里面输入工程的名字、位置、编译器的类型及版本,然后点
  
  没有问题的话就可以点Open Project
  
  然后就打开了熟悉的keil(然而HAL库函数看的一脸懵逼。。。),打开工程之后记得先编译一下,一般来说会0 Errors,0 Warnings。
  
  〇 用HAL库实现CAN总线的发送
  连标准库的使用都是一知半解得我在HAL库里完全不知所措,这该如何下手?
  还好我们有百度,我们有CSDN。。。
  在这里首先感谢下面这篇文章的作者,用非常简洁的语句和代码实现了发送的功能,其他一些文章很多都是调用自己写的函数,而作者只简单的调用了库函数,虽然可能在功能上有不足,但贵在直观。
  →→→ stm32CubeMx CAN 发送数据
  按照文章的示例,我在main函数的Private Variables部分添加代码
  
  在Initialize all configured peripherals部分添加代码
  
  在主函数的user code 2部分添加发送邮箱信息
  
  最后在主程序while(1)部分添加代码
  
  有一点需要注意,使用Cube+HAL库时,用户只能在生成的代码的
  /* USER CODE BEGIN */
  和
  /* USER CODE END */
  之间插入代码,不然在使用Cube修改程序配置时会被覆盖掉!切记!
  然后编译代码,选择仿真器,连接CAN信号分析仪,烧录程序,观察分析仪软件有没有收到数据。
  
  OK!No problem!
  下面就是解决接收的问题。
  ❤ 2019.12.20
  〇 HAL库实现CAN总线的接收
  CAN总线的接收需要配置中断,总不能用查询的方式来接收can指令吧,(而且也没有看到有人用查询的方式来接收的例程。。。),虽然can的发送是用延迟的方式发送的,但是如果发送的数据量比较大,肯定也是需要用中断方式发送的,但是发送先不用着急,先把接收中断搞好。
  can的发送很简单,就几行代码,我也是在云里雾里的情况下偶然发现别人的文章才实现的,但是我并没有找到简单朴素的讲解can接收的文章。。。很多文章可能是HAL库的版本较老,函数名都有很大的差别,还有一些给出了一大堆代码,但是讲解并不详细,对于我这样的初学者很不友好。
  不过有一个还比较新的工程包,作者写的很认真,注释很详细,对于初学者理解代码有一定的帮助。在这里表示感谢~
  不过这里例程里面包含了FreeRTOS系统,显得比较复杂,而且作者对函数库有一定的改动(后面说),不能直接用于cube生成的代码,而且代码有一些作者自身的编程习惯,有时候我觉得不太规范,这时候我想到了st的官方例程。
  官方例程在hal库的库文件包里
  
  埋得还是比较深的,还好里面有个索引文件,要不还真找不到。
  官方例程使用中最大的障碍就是全英语。。。。
  
  不过呢不得不说格式很规范,很有参考(搬运)价值~
  说实话直接看官方例程的话,如果不是很熟悉stm32的can模块工作方式,不熟悉hal库的编程习惯,还是很不好理解的。我首先学习了野火的视频,对stm32的can模块在标准库下的工作流程有了个初步的了解,然后参考了刚刚提到的那个有详细中文注释的工程文件,对其配置和使用有了大概的了解,随后我把官方例程和cube生成的未配置的代码进行对比,(大家来找茬?),找到他们之间不同的地方,就是我需要在我的工程里添加的部分。不过官方例程并没有像cube生成的文件里那样标出来哪些是user code 哪些是自动生成的code,因为hal库的特性,我只能更改代码里面的user code部分,所以我得把官方例程里的那些已经被cube自动生成的部分去掉。
  说实话,虽然我前两天弄明白了什么是回调函数,但是具体到回调函数的使用我还不知道。比如说can的中断服务函数cube已经生成完毕,但是在库函数了,所以正常情况下我是不可以修改的。但是我却不知道应该在哪里修改。。。我甚至不知道哪个函数是负责读取接收邮箱的。。。
  根据我多年的经验,我猜这个函数是用来读取接收邮箱的数据的,但是这个函数在例程里并没有被直接调用,所以我经过检索发现了蛛丝马迹。
  
  这个是在主函数里定义的一个回调函数,看来他就是罪魁祸首。让我来看看他是在那里调用的。
  
  哼,果然是他!这个函数就是can的中断服务函数。
  现在事实已经很清楚了,下面我按照例程的格式在我的工程里添加相应的语句。
  这里有个问题,我看有的文章说必须要配置过滤器,不然接收FIFO无法接收数据,这里我来验证一下,先不配置过滤器。
  首先把之前测试发送的代码删除或注释掉。
  然后在Cube生成的can初始化函数里添加语句(就不另外建立新的初始化函数了)
  /* USER CODE BEGIN CAN_Init 2 */
  //开启can模块
  if (HAL_CAN_Start(&hcan) != HAL_OK)
  {
  /* Start Error */
  Error_Handler();
  }
  //开启can中断
  if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
  {
  /* Notification Error */
  Error_Handler();
  }
  //配置发送邮箱初始信息
  TxHeader.RTR = CAN_RTR_DATA;
  TxHeader.IDE = CAN_ID_STD;
  TxHeader.StdId = std_id;
  TxHeader.TransmitGlobalTime = DISABLE;
  TxHeader.DLC = 8;
  /* USER CODE END CAN_Init 2 */
  然后新建bsp_can.c和.h文件,文件里建立HAL_CAN_RxFifo0MsgPendingCallback()的回调函数,用来接收数据,CanTestSendBack()用来将接收的数据发送出去,Error_Handler()用来处理错误。
  /**
  ******************************************************************************
  * @file bsp_can.c
  * @author zgc
  * @version V1.0
  * @date 2019.12.20
  * @brief Cube+HAL库下的can驱动(学习版)
  ******************************************************************************
  * @attention
  *
  * 平台: 秉火 STM32 F103-霸道 开发板
  * 编译器: keil 5.25
  *
  ******************************************************************************
  */
  #include “bsp_can.h”
  #include “stm32f1xx_hal.h”
  //这个是例程里带的,can发送接收的基本变量与结构体
  //CAN_HandleTypeDef CanHandle;
  CAN_TxHeaderTypeDef TxHeader;
  CAN_RxHeaderTypeDef RxHeader;
  uint8_t TxData[8] = {0x1,0x0,0x1,0x0,0x1,0x0,0x1,0x0};
  uint8_t RxData[8] = {0};
  uint32_t TxMailbox;
  uint32_t std_id = 0x123;
  //**************************************************
  //这个是什么变量我还没想好
  uint8_t MsgAvailable = 0; //接收到数据标记位
  /**
  * @name HAL_CAN_RxFifo0MsgPendingCallback
  * @brief Rx Fifo 0 message pending callback in non blocking mode,大概意思就是can接收中断函数里的一个回调函数,用来接收数据
  * @param CanHandle: pointer to a CAN_HandleTypeDef structure that contains the configuration information for the specified CAN.
  * @retval 无
  */
  void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *CanHandle)
  {
  /* Get RX message */
  if (HAL_CAN_GetRxMessage(CanHandle, CAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK)
  {
  /* Reception Error */
  Error_Handler();
  }
  MsgAvailable = 1; //接收到数据标记位置1
  }
  /**
  * @name Error_Handler
  * @brief This function is executed in case of error occurrence.大概意思就是如果出现了错误,程序就会在这里卡住,方便调试(可是怎么调试。。。)
  * @param None
  * @retval None
  */
  static void Error_Handler(void)
  {
  while (1)
  {
  }
  }
  /**
  * @name CanTestSendBack
  * @brief Can模块测试函数,将can总线接收到的数据再通过can发送出去
  * @param 无
  * @retval 无
  */
  void CanTestSendBack(void)
  {
  uint8_t i = 0;
  for(i = 0; i 《 8; i++)
  {
  TxData[i] = RxData[i];
  RxData[i] = 0;
  }
  if (HAL_CAN_AddTxMessage(&hcan, &TxHeader, TxData, &TxMailbox) != HAL_OK)
  {
  /* Transmission request Error */
  Error_Handler();
  }
  HAL_Delay(100);
  MsgAvailable = 0; //接收到数据标记位清0
  }
  /**************************END OF FILE************************************/
  然后在main函数里添加
  
  主循环里添加
  
  最后在Cube的设置里打开can的接收中断。(其实我最开始忘记了,配置完过滤器才想起来。。。不过后来我又把过滤器的配置代码注释掉之后又来测试过)
  
  因为我用的是FIFO0,所以打开CAN RX0的中断,generate code。
  打开,编译,下载,打开can信号分析仪,能接收到初始化的那条数据,向stm32发送数据,果然没反应。。。。
  
  现在最大的可能就是因为没有设置过滤器。
  这里先搬一下关于stm32can的过滤器的信息。
  ○ STM32can模块过滤器(验收筛选器)
  
  
  
  接下来对照官方例程,配置can的过滤器。
  首先在初始化函数里加入结构体的声明
  
  然后在user code部分添加代码
  //配置can过滤器
  sFilterConfig.FilterBank = 0;
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
  sFilterConfig.FilterIdHigh = 0x0000;
  sFilterConfig.FilterIdLow = 0x0000;
  sFilterConfig.FilterMaskIdHigh = 0x0000;
  sFilterConfig.FilterMaskIdLow = 0x0000;
  sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
  sFilterConfig.FilterActivation = ENABLE;
  sFilterConfig.SlaveStartFilterBank = 14;
  if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)
  {
  /* Filter configuration Error */
  Error_Handler();
  }
  编译,下载,测试,OK~
  
  至此STM32的Cube+HAL库的CAN通讯学习与测试暂告一段落,后面可能还有中断方式发送can数据等等的内容,等需要的时候我再继续写。
  总的来说,STM32的can模块比F28335的人性化好多,我一直对F28335的需要给每个邮箱配置ID(虽然有32个邮箱好像挺多的),然后只能发送和接收相同ID的数据这个设定很不爽。。。简直反人类。。。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分