本人大学生一枚,参加了RM的比赛,以前在Windows下开发stm32用于比赛,由于换了Linux系统,所以最近一直捣鼓怎么在Linux下开发,最后还是决定使用Linux下通用的方法:gcc+CubeMX+vscode+makefile开发,其中gcc用来编译,CubeMx用来生成工程,vscode用来编写代码。由于CubeMX生成的工程是基于hal库的,所以系统研究了hal的构成以及最新的hal库的变化。(由于刚入门,所以有不对或者不恰当的地方还烦请大佬帮忙)
首先根据官方的说法,hal的移植性很好,所以最先就是了解为什么移植性好,首先就是hal库的整体配置思路,ST官方将与MCU底层有关的代码以及与MCU无关的代码分开实现,实现的方法就是通过MSP回调函数函数,比如在配置串口的时候,前边的基本配置与标准库基本一样,通过一个结构体配置相关的参数,最后在Init函数中通过传入句柄的地址来完成初始化配置,但是在hal库中,为了实现可移植,在Init函数最后,会自动调用一个MSPInit回调函数,这个函数在官方的库中已经有了定义(官方给的这个函数内部是空的),但是这个函数前有_wake修饰,意思是“弱”修饰。只要用户在任意文件中再一次定义这个函数(也就是定义一个同名函数),程序在执行的时候就会执行重新定义以后的函数(有点类似面对对象的语言中的重写)。我们一般将与MCU有关的底层的代码写在MSP函数中,也就是GPIO的配置,这样在进行移植程序的时候只要修改MSP函数中的东西就可以,所以就实现了ST官方所要的移植性,当然,这种方法有好处也有坏处,好处是实现了程序的移植,坏处就是使得程序及其的复杂、繁琐。
Hal库在中断中也使用了这种回调的方式,比如串口中断,配置的过程极其的繁琐(相对于标准库来说,真的很繁琐),我们比赛的车上的程序用的是串口接收完成中断,在hal库中,有一个通用的串口中断处理函数HAL_UART_IRQHandler,当你开启了中断之后,只要接收到一个字符就会进入void USART1_IRQHandler(void)这个中断函数中,在这个函数里就调用了通用中断函数HAL_UART_IRQHandler,这个函数是官方定义好的,一般情况下我们不会修改这个函数,那么我们自己的中断服务写在什么地方呐?这就要了解这个通用函数内部的情况,这个函数内部的重要源码如下:
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
uint32_t tmp1 = 0, tmp2 = 0;
...//此处省略好多代码
tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE);
tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_RXNE);
if((tmp1 != RESET) && (tmp2 != RESET))
{
UART_Receive_IT(huart);
}
...//此处省略一大堆代码
}
首先,其内部首先判断中断类型是否为接收中断,如果为接收中断,就执行UART_Receive_IT(huart)这个函数,这个函数的作用是把每次中断接收到的字符保存在串口句柄的缓存指针 pRxBuffPtr(自己在配置串口的时候设置的变量) 中,同时每次接收一个字符,其计数器 RxXferCount 减 1,直到接收完成 RxXferSize 个字符之后 RxXferCount 设置为0,同时调用接收完成回调函数 HAL_UART_RxCpltCallback 进行处理。以下是HAL_UART_RxCpltCallback的源码:
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
...//此处省略部分代码
if(--huart->RxXferCount == 0)
{
HAL_UART_RxCpltCallback(huart);
}
...//此处省略部分代码
}
可以看到,当计数器减到0时,就调用HAL_UART_RxCpltCallback(huart);这个回调函数,所以,我们自己的中断服务一般就放在以下2个位置:
1…如果不是接收完成中断,直接写在void HAL_UART_IRQHandler函数中就可以,或者直接写在void USART1_IRQHandler(void)这个函数中
2…如果是接收完成中断,就要写在HAL_UART_RxCpltCallback(huart);中
同时,hal库为我们提供了5个中断回调函数:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);//发送完成回调函数
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);//发送完成过半
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//接收完成回调函数
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//接收完成过半
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);//错误处理回调函数
由此我们可以看到,hal库对中断的处理是多么富有逻辑性,同时又多么的繁琐。
以上就是hal库的基本编程思路。
然后就是最近看到一些资料对can的配置有些老旧,以前的can的标准外设库中用于初始化can的句柄中(也就是那个结构体)会要求用户定义接收以及发送的结构体。以下是旧版本的标准外设库(现在当你用cubeMX创建工程的时候你会发现,在标准外设库中多了一个叫legacy的文件夹,这个文件夹中的can的外设库就是旧版的)中关于初始化句柄的内容:
typedef struct
{
CAN_TypeDef *Instance;
CAN_InitTypeDef Init;
CanTxMsgTypeDef* pTxMsg;
CanRxMsgTypeDef* pRxMsg;
__IO HAL_CAN_StateTypeDef State;
HAL_LockTypeDef Lock;
__IO uint32_t ErrorCode;
}CAN_HandleTypeDef;
看到其中的pTxMsg、pRxMsg就是让我们定义接收已经发送的结构体,然后在最新的库中没有了这两个,当是真的是不知所措,无奈只能自己研究一下源码,现在,can启动时要用到2个函数:
1.HAL_CAN_Start(&hcan) //开启can
2.HAL_CAN_ActivateNotification(&hcan1,CAN_IT_RX_FIFO0_MSG_PENDING); //开启中断
此外需要的函数有:
1.Can发送函数
HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef*hcan,CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox);
2.Can接收函数
HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef*hcan,uint32_tRxFifo,CAN_RxHeaderTypeDef *pHeader, uint8_t aData[]);
然而在官方那里并没有收发数据的数据帧组数,这需要自己来定义一个
typedef struct
{
uint8_t Data[8];
}RX_Massage;
以上就是自己最近学习并且整理的一些算是笔记的东西。谢谢大佬指正。