STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯

❤ 2019.12.18 

    今天有个很大的收获,就是知道了什么是【回调函数】哈哈哈。。。

    好吧言归正传,这个文章是我在调试我的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。

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第1张图片

    另外就是外部晶振,Cube里默认是使用内部高速振荡器,如果用外部晶振,需要在RCC里面的HSE里选择Crystal/Ceramic Resonator,然后在时钟配置里选择外部晶振。

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第2张图片

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第3张图片

 

    下面开始具体的学习(爬坑)过程。

〇 在Cube里面配置相关功能

    因为我用的是野火的开发板,所以首先在芯片选择里面选STM32F103ZE

    然后就是按照上面说的方法配置仿真器引脚和晶振引脚。

    然后对时钟树进行配置。

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第4张图片

    时钟树的讲解在野火的资料和视频里讲的很详细了,第一次用图形化方式配置,还有点小激动。。。这里需要注意的是CAN总线是挂载在APB1高速总线上的,PCLK1的频率直接决定了CAN模块的参数配置。

    接下来配置端口,根据野火的原理图,我们知道can的端口用的是PB8和PB9.

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第5张图片

    于是在Cube里选择相应端口。

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第6张图片

    然后在configuration里面配置相应参数。

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第7张图片

    这里需要注意的是,CAN通讯里面有波特率的概念(最大1MHz),但是并不是直接设置波特率的速率,而是分别设置预分频系数,每一段指令周期的时间,最后通过计算才得出can总线的波特率。

    CAN总线的物理层和协议层在野火的资料和视频里有很详细的讲解,而且可以看一看瑞萨科技的圣经《CAN入门书》,在这里我稍微搬一搬。其实我也是昨天才弄懂了can总线的波特率。。。

    ♣ CAN总线的基础知识

        ○ 物理层

    STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第8张图片

    CAN总线的硬件结构,can总线用差分线传输数据,除了can控制器外必须配有can收发器,总线两端需要串联120欧电阻。

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第9张图片

    CAN总线用CANH与CANL两线的电压差表示0和1,差为0V表示1(隐性电平),差为2V表示0(显性电平)。

 

        ○ 协议层

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第10张图片

    CAN信号的每一位分为如图几个部分,最小的时间单位是Tq(Time Quantum),其作用和讲解就不搬了,这几部分的时长一起决定了CAN总线的波特率。

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第11张图片

    CAN总线的报文结构,如图所以,详细的就不搬了。

        ○ STM32的CAN模块介绍

    STM32的CAN架构

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第12张图片

    CAN控制器的基本参数

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第13张图片

    CAN的四种工作模式

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第14张图片

    位时序和波特率

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第15张图片

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第16张图片

    此外,STM32的CAN模块还包括发送邮箱,接收FIFO,过滤器等等,我就不搬了,可以参见野火的《零死角玩转STM32》一书或相关资料。

   

    搬完了CAN的基础知识以及STM32的can模块相关信息,然后再来看Cube的设置界面

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第17张图片

    可以看出就是对上面提到的一些参数的配置。

    这里配置成1MHz的工作模式,普通收发模式。理论上来说,一般调试设备的时候都先选成回环模式或者静默回环模式,但是因为我不会STM32的在线仿真,如果设置成回环模式的话,需要同时保证发送程序和接收程序都正确才能调试成功,不过我打算先调试发送程序,再调试接收程序,用CAN信号分析仪作为辅助工具,所以这里选择普通模式。

    然后在project manager里面输入工程的名字、位置、编译器的类型及版本,然后点

    没有问题的话就可以点Open Project

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第18张图片

    然后就打开了熟悉的keil(然而HAL库函数看的一脸懵逼。。。),打开工程之后记得先编译一下,一般来说会0 Errors,0 Warnings。

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第19张图片

   

〇 用HAL库实现CAN总线的发送

    连标准库的使用都是一知半解得我在HAL库里完全不知所措,这该如何下手?

    还好我们有百度,我们有CSDN。。。

    在这里首先感谢下面这篇文章的作者,用非常简洁的语句和代码实现了发送的功能,其他一些文章很多都是调用自己写的函数,而作者只简单的调用了库函数,虽然可能在功能上有不足,但贵在直观。

→→→ stm32CubeMx CAN 发送数据

    按照文章的示例,我在main函数的Private Variables部分添加代码

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第20张图片

    在Initialize all configured peripherals部分添加代码

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第21张图片

    在主函数的user code 2部分添加发送邮箱信息

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第22张图片

    最后在主程序while(1)部分添加代码

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第23张图片

    有一点需要注意,使用Cube+HAL库时,用户只能在生成的代码的

        /* USER CODE BEGIN */

        和

        /* USER CODE END */

    之间插入代码,不然在使用Cube修改程序配置时会被覆盖掉!切记!

    然后编译代码,选择仿真器,连接CAN信号分析仪,烧录程序,观察分析仪软件有没有收到数据。

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第24张图片

    OK!No problem!

    下面就是解决接收的问题。

    

❤ 2019.12.20

〇 HAL库实现CAN总线的接收

    CAN总线的接收需要配置中断,总不能用查询的方式来接收can指令吧,(而且也没有看到有人用查询的方式来接收的例程。。。),虽然can的发送是用延迟的方式发送的,但是如果发送的数据量比较大,肯定也是需要用中断方式发送的,但是发送先不用着急,先把接收中断搞好。

    can的发送很简单,就几行代码,我也是在云里雾里的情况下偶然发现别人的文章才实现的,但是我并没有找到简单朴素的讲解can接收的文章。。。很多文章可能是HAL库的版本较老,函数名都有很大的差别,还有一些给出了一大堆代码,但是讲解并不详细,对于我这样的初学者很不友好。

    不过有一个还比较新的工程包,作者写的很认真,注释很详细,对于初学者理解代码有一定的帮助。在这里表示感谢~

基于STM32F103的V1.7.0HAL库的CAN协议接收与发送例程

    不过这里例程里面包含了FreeRTOS系统,显得比较复杂,而且作者对函数库有一定的改动(后面说),不能直接用于cube生成的代码,而且代码有一些作者自身的编程习惯,有时候我觉得不太规范,这时候我想到了st的官方例程。

    官方例程在hal库的库文件包里

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第25张图片

    埋得还是比较深的,还好里面有个索引文件,要不还真找不到。

    官方例程使用中最大的障碍就是全英语。。。。

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第26张图片

    不过呢不得不说格式很规范,很有参考(搬运)价值~

    说实话直接看官方例程的话,如果不是很熟悉stm32的can模块工作方式,不熟悉hal库的编程习惯,还是很不好理解的。我首先学习了野火的视频,对stm32的can模块在标准库下的工作流程有了个初步的了解,然后参考了刚刚提到的那个有详细中文注释的工程文件,对其配置和使用有了大概的了解,随后我把官方例程和cube生成的未配置的代码进行对比,(大家来找茬?),找到他们之间不同的地方,就是我需要在我的工程里添加的部分。不过官方例程并没有像cube生成的文件里那样标出来哪些是user code 哪些是自动生成的code,因为hal库的特性,我只能更改代码里面的user code部分,所以我得把官方例程里的那些已经被cube自动生成的部分去掉。

    说实话,虽然我前两天弄明白了什么是回调函数,但是具体到回调函数的使用我还不知道。比如说can的中断服务函数cube已经生成完毕,但是在库函数了,所以正常情况下我是不可以修改的。但是我却不知道应该在哪里修改。。。我甚至不知道哪个函数是负责读取接收邮箱的。。。

    根据我多年的经验,我猜这个函数是用来读取接收邮箱的数据的,但是这个函数在例程里并没有被直接调用,所以我经过检索发现了蛛丝马迹。

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第27张图片

    这个是在主函数里定义的一个回调函数,看来他就是罪魁祸首。让我来看看他是在那里调用的。

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第28张图片

    哼,果然是他!这个函数就是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函数里添加

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第29张图片

    主循环里添加

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第30张图片

    最后在Cube的设置里打开can的接收中断。(其实我最开始忘记了,配置完过滤器才想起来。。。不过后来我又把过滤器的配置代码注释掉之后又来测试过)

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第31张图片

    因为我用的是FIFO0,所以打开CAN RX0的中断,generate code。

    打开,编译,下载,打开can信号分析仪,能接收到初始化的那条数据,向stm32发送数据,果然没反应。。。。

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第32张图片

    现在最大的可能就是因为没有设置过滤器。

 

    这里先搬一下关于stm32can的过滤器的信息。

    ○ STM32can模块过滤器(验收筛选器)

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第33张图片

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第34张图片

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第35张图片

 

    接下来对照官方例程,配置can的过滤器。

    首先在初始化函数里加入结构体的声明

STM32学习笔记(2)——使用Cube+HAL库实现CAN通讯_第36张图片

    然后在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学习笔记(2)——使用Cube+HAL库实现CAN通讯_第37张图片

   

    至此STM32的Cube+HAL库的CAN通讯学习与测试暂告一段落,后面可能还有中断方式发送can数据等等的内容,等需要的时候我再继续写。

    总的来说,STM32的can模块比F28335的人性化好多,我一直对F28335的需要给每个邮箱配置ID(虽然有32个邮箱好像挺多的),然后只能发送和接收相同ID的数据这个设定很不爽。。。简直反人类。。。

你可能感兴趣的:(STM32)