了解STM32 HAL库函数HAL_SPI_Receive_IT和HAL_SPI_Receive的区别

背景

前段时间开发一个按键板驱动,该板用的STM32F103系列单片机,前任工程师用STM32CubeMX生成的工程,里面全是HAL库调用,我接手后,学习了下HAL库的用法,踩坑不少,特别是带IT后缀的函数,初学者对其的理解很容易出错,特此记录一下。
按键板

项目中的按键板通过SPI总线与主板连接,按键板是Slave设备,因此无法确定什么时候收到主板的读写请求,要么轮询SPI控制器的rx fifo是否非空,要么依赖SPI控制器提供的中断机制,在中断里将rx fifo内容读出来。
带IT的receive

两种Receive流程

说明一下,SPI的BPW(bits per word)设置为8,因此文中一个word的size就是一个字节。

轮询: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源码,梳理流程概要:

#mermaid-svg-2sWvAJ0f1fV05UEA {font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-2sWvAJ0f1fV05UEA .error-icon{fill:#552222;}#mermaid-svg-2sWvAJ0f1fV05UEA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2sWvAJ0f1fV05UEA .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-2sWvAJ0f1fV05UEA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2sWvAJ0f1fV05UEA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2sWvAJ0f1fV05UEA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2sWvAJ0f1fV05UEA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2sWvAJ0f1fV05UEA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2sWvAJ0f1fV05UEA .marker.cross{stroke:#333333;}#mermaid-svg-2sWvAJ0f1fV05UEA svg{font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2sWvAJ0f1fV05UEA .label{font-family:”trebuchet ms”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-2sWvAJ0f1fV05UEA .cluster-label text{fill:#333;}#mermaid-svg-2sWvAJ0f1fV05UEA .cluster-label span{color:#333;}#mermaid-svg-2sWvAJ0f1fV05UEA .label text,#mermaid-svg-2sWvAJ0f1fV05UEA span{fill:#333;color:#333;}#mermaid-svg-2sWvAJ0f1fV05UEA .node rect,#mermaid-svg-2sWvAJ0f1fV05UEA .node circle,#mermaid-svg-2sWvAJ0f1fV05UEA .node ellipse,#mermaid-svg-2sWvAJ0f1fV05UEA .node polygon,#mermaid-svg-2sWvAJ0f1fV05UEA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2sWvAJ0f1fV05UEA .node .label{text-align:center;}#mermaid-svg-2sWvAJ0f1fV05UEA .node.clickable{cursor:pointer;}#mermaid-svg-2sWvAJ0f1fV05UEA .arrowheadPath{fill:#333333;}#mermaid-svg-2sWvAJ0f1fV05UEA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2sWvAJ0f1fV05UEA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2sWvAJ0f1fV05UEA .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-2sWvAJ0f1fV05UEA .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-2sWvAJ0f1fV05UEA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2sWvAJ0f1fV05UEA .cluster text{fill:#333;}#mermaid-svg-2sWvAJ0f1fV05UEA .cluster span{color:#333;}#mermaid-svg-2sWvAJ0f1fV05UEA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2sWvAJ0f1fV05UEA :root{–mermaid-font-family:”trebuchet ms”,verdana,arial,sans-serif;}

获取控制器的锁

记录起始时间

填充控制器的各个字段:
控制器状态=BUSY_RX
接收buf地址=pData
接收buf长度=Size
RxISR = NULL

待接收字节数> 0?

rx fifo为空?

超时?

读取一个字节

待接收字节数-1

释放控制器的锁

返回错误码

注意,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是否非空,而是收到rx fifo非空中断后才去读取,不会让CPU无谓等待。

根据HAL源码,梳理流程概要:

#mermaid-svg-BuyxISh8hikO7hJp {font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-BuyxISh8hikO7hJp .error-icon{fill:#552222;}#mermaid-svg-BuyxISh8hikO7hJp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BuyxISh8hikO7hJp .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-BuyxISh8hikO7hJp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BuyxISh8hikO7hJp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BuyxISh8hikO7hJp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BuyxISh8hikO7hJp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BuyxISh8hikO7hJp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BuyxISh8hikO7hJp .marker.cross{stroke:#333333;}#mermaid-svg-BuyxISh8hikO7hJp svg{font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BuyxISh8hikO7hJp .label{font-family:”trebuchet ms”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-BuyxISh8hikO7hJp .cluster-label text{fill:#333;}#mermaid-svg-BuyxISh8hikO7hJp .cluster-label span{color:#333;}#mermaid-svg-BuyxISh8hikO7hJp .label text,#mermaid-svg-BuyxISh8hikO7hJp span{fill:#333;color:#333;}#mermaid-svg-BuyxISh8hikO7hJp .node rect,#mermaid-svg-BuyxISh8hikO7hJp .node circle,#mermaid-svg-BuyxISh8hikO7hJp .node ellipse,#mermaid-svg-BuyxISh8hikO7hJp .node polygon,#mermaid-svg-BuyxISh8hikO7hJp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BuyxISh8hikO7hJp .node .label{text-align:center;}#mermaid-svg-BuyxISh8hikO7hJp .node.clickable{cursor:pointer;}#mermaid-svg-BuyxISh8hikO7hJp .arrowheadPath{fill:#333333;}#mermaid-svg-BuyxISh8hikO7hJp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BuyxISh8hikO7hJp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BuyxISh8hikO7hJp .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-BuyxISh8hikO7hJp .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-BuyxISh8hikO7hJp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BuyxISh8hikO7hJp .cluster text{fill:#333;}#mermaid-svg-BuyxISh8hikO7hJp .cluster span{color:#333;}#mermaid-svg-BuyxISh8hikO7hJp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BuyxISh8hikO7hJp :root{–mermaid-font-family:”trebuchet ms”,verdana,arial,sans-serif;}

HAL_SPI_Receive_IT

填充控制器的各个字段:
控制器状态=BUSY_RX
接收buf地址=pData
接收buf长度=Size
RxISR = SPI_RxISR_8BIT

获取控制器的锁

开启rx_fifo非空和rx_error中断

释放控制器的锁

HAL_SPI_IRQHandler

溢出中断置位?

获取中断使能和中断状态

禁用所有中断

调用用户定义的HAL_SPI_ErrorCallback()

rx fifo非空中断置位?

调用SPI_RxISR_8BIT()

退出中断

SPI_RxISR_8BIT

待接收字节数-1

将FIFO的第一个字节拷贝到pData

待接收字节数== 0?

调用SPI_CloseRx_ISR()

退出函数

SPI_CloseRx_ISR

控制器的错误码为NONE?

禁用rx_fifo非空和rx_error中断

调用用户定义的HAL_SPI_RxCpltCallback()

调用用户定义的HAL_SPI_ErrorCallback()

退出函数

注意:

  1. 每个字节的接收都会触发一次中断,因为所谓的rx fifo并不存在,其实就是直接读取控制器的DR寄存器(暂存当前收到的word),如果想提高效率,可以使用DMA版本。
  2. HAL_SPI_Receive_IT运行在后台(主循环),HAL_SPI_IRQHandler以及它调用的其他函数都运行在前台(中断),因此后者代码里一定不能有printf之类的打印语句,否则会影响SPI接收时序!

对比

可以看出,带IT后缀的receive函数,只填充控制器的上下文结构体并开启中断,剩下的都交给中断回调。这种策略将接收分成前后台两部分,后台开启中断,前台响应中断并读取数据,检测数据收够了就关闭中断,因此带IT后缀并不是传言的只能在中断态下运行。

后记

  1. HAL库其他总线,像UART、I2C等,它们的带IT后缀的receive函数,应该也是这种设计模式,大家可以验证一下。
  2. rx_fifo非空中断到底是片选信号触发的,还是SCK信号触发的,不太确定,看过芯片TRM手册,好像是SCK触发的,知道的帮忙确认下。
物联沃分享整理
物联沃-IOTWORD物联网 » 了解STM32 HAL库函数HAL_SPI_Receive_IT和HAL_SPI_Receive的区别

发表评论