【复旦微FM33 MCU 外设开发指南】外设篇3——SPI
前言
本系列基于复旦微FM33系列单片机的DataSheet编写,旨在提供一些开发指南。
本文章及本系列其他文章将持续更新,本系列其它文章请跳转【复旦微FM33 MCU 外设开发指南】总集篇
本文章最后更新日期:2024/08/31
文章目录
- 前言
- GPIO配置
- SPI配置
- 1. SPI的寄存器
- 2. SPI的时钟
- 3. SPI的工作模式
- 3.1 主机和从机
- 3.2 单发送模式和单接收模式
- 3.3 工作模式
- 3.4 片选控制
- 4. SPI的时序控制
- 4.1 时钟极性和时钟相位
- 4.2 MISO的时序
- 4.3 SPI两帧之间的时间间隔
- 4.4 SPI帧格式
- 5. SPI的复位
- SPI发送&接收
- 1. SPI的状态标志位
- 2. 等待标志位发送或接收
- 3. 使用中断发送或接收
- 4. 通过DMA发送或接收
- 注意事项
- 1. SPI单接收模式通过DMA接收数据,多产生两个时钟周期
GPIO配置
要使用SPI功能,需要将使用的引脚配置为数字功能(写GPIO寄存器时要注意打开GPIO时钟)。
(有的引脚需要配置额外数字功能寄存器DFS,详见GPIO章节【复旦微FM33 MCU 外设开发指南】外设篇1——GPIO)
通常,我们会将MISO、MOSI和CLK引脚配置为数字功能,而将CS引脚配置为GPIO输出功能,以实现软件片选。
可以在SPIx->CR1
寄存器中交换MOSI和MISO引脚。
SPI配置
1. SPI的寄存器
SPIx->CR1
:控制寄存器1,用于选择SPI主机/从机、SPI时钟、信号时序
SPIx->CR2
:控制寄存器2,用于控制SPI使能、数据字长配置、片选配置
SPIx->CR3
:控制寄存器3,用于清空发送缓冲区、清空接收缓冲区、清除错误标志
SPIx->IER
:中断使能寄存器,用于发送中断、接收中断、错误中断的使能和关闭
SPIx->ISR
:中断标志寄存器,用于SPI BUSY标志、接收缓冲区满标志、发送缓冲区满标志、错误标志等的读取
SPIx->TXBUF
:发送缓冲区,用于单次发送数据时写入(通过DMA传输的话会自动写入)
SPIx->RXBUF
:接收缓冲区,用于单次读取数据时写入(通过DMA传输的话会自动读取)
2. SPI的时钟
要使用SPI,要在RCC->PCLKCR3
寄存器中开启SPI外设的时钟使能。
SPI外设挂载在APB总线上,因此要调整SPI外设的工作时钟,可以通过对APBCLK预分频,也可以调整SPI的预分频。
(有关时钟的配置详见【复旦微FM33 MCU 外设开发指南】系统篇——时钟)
FM33LC0 SPI外设的预分频在SPIx->CR1
寄存器中配置,最高为APBCLK的二分频
3. SPI的工作模式
3.1 主机和从机
SPI的时序由主机产生。FM33LC0 SPI外设的主机从机模式选择在SPIx->CR1
寄存器中配置
3.2 单发送模式和单接收模式
SPI是全双工通讯,因此在读取数据的时候,也必须发送数据,以产生时钟信号。
但我们使用的过程中一般是异步的。比如从一块SRAM中读取数据,只需发送一个命令帧告诉SRAM要从哪里读,读多长就可以了,之后SRAM返回其中保存的数据,这种时候主机每一帧发送的数据是无效的。单发送和单接收就可以应用在类似的场景中。
可以在SPIx->CR2
寄存器中使能或关闭单发送模式/单接收模式。
3.3 工作模式
可以在SPIx->CR2
寄存器中控制SPI的工作模式。
SPI有很多种工作模式,常用四线全双工模式
除此之外有四线半双工(一个MISO/MOSI引脚用于区分命令帧和数据帧)和三线半双工(删除一个MISO/MOSI引脚)等
四线半双工常见的应用就是SPI显示屏
3.4 片选控制
可以在SPIx->CR2
寄存器中控制片选。
当然我日常喜欢使用软件片选,而且不通过寄存器去改变片选状态,而是将引脚配置为GPIO输出模式,写GPIOx->DO
寄存器来改变其引脚状态。
4. SPI的时序控制
4.1 时钟极性和时钟相位
SPI时序控制最基本的即为时钟极性和时钟相位,SPI的主机和从机应配置为相同的时钟极性和时钟相位。
一般MCU作为SPI的主机使用,从机的时钟极性和时钟相位一般是固定死的,根据从机的DataSheet要求配置即可。
FM33LC0 SPI外设的时钟极性和时钟相位在SPIx->CR1
寄存器中配置
4.2 MISO的时序
除了时钟极性和时钟相位,还可以在SPIx->CR1
寄存器中调整MISO的时序
4.3 SPI两帧之间的时间间隔
SPI传输时,接上示波器,可以观察两个时钟周期之间有时间间隔。
1.如果是多次的单次传输(直接向TXBUF中写入数据然后启动SPI传输),那么这个时间间隔主要是软件等待标志位产生的
2.如果是通过DMA的连续传输,那么这个时间间隔会变得很短。
我们用SPI来和显示屏通讯,会发现使用DMA的刷屏速率会比不用DMA快很多,主要是省下了大量的两帧之间的时间间隔。
可以在SPIx->CR1
寄存器中调整SPI的两帧数据之间的等待时间。
(但我认为这个只对DMA传输影响显著,因为多次的单次传输,SPI两帧的时间间隔实在是太久了)
4.4 SPI帧格式
可以在SPIx->CR2
寄存器中调整SPI单帧的传输字长。
可以在SPIx->CR1
寄存器中调整SPI的帧格式为大端或者小端。
5. SPI的复位
SPI2的复位在RCC->AHBRSTCR1
寄存器中。
SPI1的复位在RCC->AHBRSTCR2
寄存器中。
注意,这里的复位不是说写1就复位了,复位好了之后要写0取消复位。
SPI发送&接收
1. SPI的状态标志位
SPI的状态标志位在SPIx->ISR
寄存器中查询。
虽说这个寄存器叫中断状态标志位,但没开启中断的时候,这个标志位也会置位,只是不响应中断罢了。
SPIx->ISR
寄存器中常用的三个标志位:
RXBF和TXBE标志位对应SPIx->IER
寄存器中可以开启的接收完成中断和发送完成中断,即开启中断后,如果TXBE和RXBF置位,就会进入中断服务函数,在中断服务函数中可以判断中断来源。
那RXBF和TXBE什么时候置位?根据描述来看,是接收缓存满或者发送缓冲区空的时候。
但必须注意接收缓存满不代表接收完成,发送缓冲区空也不代表发送完成。
通过文首结构框图,可以看到不仅有发送/接收缓冲区,也有发送/接收移位寄存器。
在写入SPIx->TXBUF
后,数据会从SPIx->TXBUF
传入发送移位寄存器中,这时SPIx->TXBUF
空,并置位TXBE标志,进入中断服务函数。
但这时发送移位寄存器的值还没有发送出去。
BUSY标志位才能代表当前是否有数据传输,只有在移位寄存器空,才会清零BUSY标志位,代表SPI当前没有数据传输
如果没有意识到这点,在写软件的时候会经常遇到一些错误,比如接收数组里没有最后一帧。
2. 等待标志位发送或接收
在发送时,向SPIx->TXBUF
寄存器写入,然后等待BUSY标志
在接收时,向SPIx->TXBUF
寄存器写入任意数据(因为SPI是全双工),然后等待BUSY标志,读取SPIx->RXBUF
多字节的发送/接收就是重复这个过程。
这种方式最简单,缺点是效率很低,而且软件死等的方式在软件开发中很危险,如果等不到标志程序就死在那里了,除非看门狗复位。
3. 使用中断发送或接收
在发送时,使能发送中断,向SPIx->TXBUF
寄存器写入,在中断服务函数中等待BUSY标志为0,即为发送完成
如果还要继续写入数据,在中断服务函数中可以不等待BUSY标志为0,直接向SPIx->TXBUF
写入,以实现连续的发送。
当然,进入中断之后要判断产生中断的原因,是发送完成中断、接受完成中断还是错误中断。
4. 通过DMA发送或接收
这里DataSheet上写的很好,不做过多阐述。
注意事项
1. SPI单接收模式通过DMA接收数据,多产生两个时钟周期
应用场景: 主机使用单发送模式向从机发送命令字节,随后开启单接收模式,通过DMA接收从机回发数据
问题描述: 用示波器看波形,会发现多了两个时钟周期,配置spi字长为8bit,就会多出两个8bit时钟;配置spi字长为16bit,就会多出两个16bit时钟
调试记录:
1.收到的数据无误
2.使能SPI后时钟线立刻产生了两个时钟周期,因此和DMA无关,可能是SPI单接收模式的问题
3.如SPI不使用单接收模式而使用全双工接收数据,相同情况下时钟线不会产生两个时钟周期。确认是单接收模式的问题
推测原因:
在分析原因之前,重复一下SPI外设的特性:
- SPI是全双工通信,主机即使只接收数据也要向TX缓冲区写数据,时钟产生并开启传输。即使将SPI配置为单接收模式,也是SPI外设自动向TX缓冲区填写数据,时钟产生并开启传输
- SPI外设有缓冲区和移位寄存器,从机返回的数据将先进入移位寄存器,再进入接收缓冲区。在接收缓冲区非空的时候,移位寄存器的数据是无法进入接收缓冲区的。因此如果主机接收缓冲区读的不及时,接收缓冲区非空,移位寄存器无法写入接收缓冲区,则SPI传输中止(时序由主机提供)
根据以上特性,可以分析出原因:
- 当读取完一组数据后,SPI还在单接收模式中,继续往TX缓冲区填写数据,因此继续产生时钟,有时钟就有接收到的数据(如果从机不响应,则接收到的就是全0)
- 但是SPI主机没有继续读取RX缓冲区的数据(因为已经收到了设定长度的数据),导致RX缓冲区满,进而导致移位寄存器无法写入接收缓冲区。
- 因此,多出的第一个时钟周期的数据在接收缓冲区中,第二个时钟周期的数据在移位寄存器中
- 最终因为移位寄存器满而停止传输
解决办法:
在DMA传输下,不使用单接收模式,使用全双工模式。
这是因为DMA接收时会自动写TXBUF,不需要再配置单接收模式。