背景
前段时间开发一个按键板驱动,该板用的STM32F103系列单片机,前任工程师用STM32CubeMX生成的工程,里面全是HAL库调用,我接手后,学习了下HAL库的用法,踩坑不少,特别是带IT
后缀的函数,初学者对其的理解很容易出错,特此记录一下。
项目中的按键板通过SPI总线与主板连接,按键板是Slave设备,因此无法确定什么时候收到主板的读写请求,要么轮询,要么依赖控制器提供的中断机制。
两种Receive流程
说明一下,SPI的BPW(bits per word)=8,不是16,因此一个word就是一个字节。
轮询:HAL_SPI_Receive流程
先看函数签名
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
hspi
是SPI控制器句柄,pData
是接收buf地址,Size
是接收buf长度,Timeout
是接收超时时间,如果期间一直没收到数据,则返回。
根据HAL源码,梳理流程概要:
注意,RxISR表示接收中断的回调函数,因为我们是轮询模式,所以该字段填0。
中断:HAL_SPI_Receive_IT流程
先看函数签名
HAL_StatusTypeDef HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size);
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi);
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi);
参数含义跟HAL_SPI_Receive一样,少了个超时参数,因为中断方式并不关心rx fifo深度
根据HAL源码,梳理流程概要:
注意:
- 每个字节的接收都会触发一次中断,因为所谓的rx fifo并不存在,其实就是直接读取控制器的DR寄存器(暂存当前收到的word),如果想提高效率,可以使用DMA版本。
- HAL_SPI_Receive_IT运行在后台(主循环),HAL_SPI_IRQHandler以及它调用的其他函数都运行在前台(中断),因此后者代码里一定不能有
printf
之类的打印语句,否则会影响SPI接收时序!
对比
可以看出,带IT后缀的receive函数,只填充控制器的上下文结构体并开启中断,剩下的都交给中断回调。这种策略将接收分成前后台两部分,后台开启中断,前台响应中断并读取数据,检测数据收够了就关闭中断,因此带IT后缀并不是传言的只能在中断态下运行。
后记
- HAL库其他总线,像UART、I2C等,它们的带IT后缀的receive函数,应该也是这种设计模式,大家可以验证一下。
- rx_fifo非空中断到底是片选信号触发的,还是SCK信号触发的,不太确定,看过芯片TRM手册,好像是SCK触发的,知道的帮忙确认下。