❤ 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。
另外就是外部晶振,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库的版本较老,函数名都有很大的差别,还有一些给出了一大堆代码,但是讲解并不详细,对于我这样的初学者很不友好。
不过有一个还比较新的工程包,作者写的很认真,注释很详细,对于初学者理解代码有一定的帮助。在这里表示感谢~
基于STM32F103的V1.7.0HAL库的CAN协议接收与发送例程
不过这里例程里面包含了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的数据这个设定很不爽。。。简直反人类。。。