IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32之SPI详细解析 -> 正文阅读

[嵌入式]STM32之SPI详细解析

SPI介绍

SPI协议,用来传输数据的一种标准化协议。

SPI包括这些独特的特点:

  • 主模式和从模式

  • 双向模式

  • 从模式选择输出

  • 模式故障错误标志与CPU中断能力

  • 双缓冲数据寄存器

  • 具有可编程极性和相位的串行时钟

  • 在等待模式下对SPI操作的控制

引脚描述:

? MOSI:此引脚用于在配置为主主模块时从SPI模块中传输数据,并在配置为从主模块时接收数据。(主出从入)

? MISO:在配置为SPI模块时从SPI模块中传输数据,在配置为主模块时接收数据。(主入从出)

? SS:(低有效)用于将选择信号从SPI模块输出到另一个外设,当其配置为主控时进行数据传输,当SPI配置为从控时作为输入来接收从选择信号。
? 该引脚相当于片选。

? SCK:此引脚用于输出SPI传输数据或接收从属时钟的时钟。

时序分析

首先要了解两个概念:CPHA(Clock Phase,时钟相位)和CPOL(Clock Polarity,时钟极性)

  1. 时钟极性:是指 SPI 通讯设备处于空闲状态时,SCK 信号线的电平信号(即 SPI 通讯开始前、 NSS 线为高电平时 SCK 的状态)。CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。

  2. 时钟相位:是指数据的采样的时刻,当 CPHA=0 时,MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的”奇数边沿”被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿”采样。

因此SPI关于时钟的配置,就有了以下4种情况:

模式CLK
CPOL = 0;CPHA = 0SCK 在空闲状态时为低电平,数据线上的信号在 SCK 时钟线的奇数边沿被采样
CPOL = 1;CPHA = 0SCK 在空闲状态时为高电平,数据线上的信号在 SCK 时钟线的奇数边沿被采样
CPOL = 0;CPHA = 1SCK 在空闲状态时为低电平,数据线上的信号在 SCK 时钟线的偶数边沿被采样
CPOL = 1;CPHA = 1SCK 在空闲状态时为高电平,数据线上的信号在 SCK 时钟线的偶数边沿被采样

时序1:CPHA = 0

在这里插入图片描述

从时序图可以看出,当 CPHA = 0 的时候,无论CPOL等于多少,在SAMPLE那一栏即采样项(橙色方框处),都是在奇数边沿进行采样,即采样边沿仅受CPHA的影响。并且采样开始前要先拉低SS,即进行片选。

时序2:CPHA = 1

在这里插入图片描述

从时序图可以看出,当 CPHA = 1 的时候,无论CPOL等于多少,在SAMPLE那一栏即采样项(橙色方框处),都是在偶数边沿进行采样,即采样边沿仅受CPHA的影响。并且采样开始前要先拉低SS,即进行片选。

综上所述,可以发现SPI的协议自由度是比IIC要高一些的,给了开发者更多的自由搭配的空间。

比如在写OLED的spi的时候,可能就不用加上MISO引脚,但是要额外搭配DC(Data/Command)引脚,区别发送的是数据还是命令。

接下来我们分别看看硬件SPI和软件模拟SPI

STM32的硬件SPI

接下来我们看看STM32中的硬件SPI,这里以STM32F103RCT6为例。

功能框图

在这里插入图片描述

  1. MOSI、MISO、SCK、NSS与前文说过的一样,四根引脚。
  2. 波特率发生器:由框图可以看出,波特率发生器链接的是SCK,那么可想而知,这是用来产生时钟信号的,既然用来产生时钟信号,那么肯定和STM32的时钟有关,并且也能够进行分频之类的操作。并且框图也指出,寄存器SPI_CR1的BR[2,0] 位指向波特率发生器,由数据手册得知,该位是对 fpclk时钟的分频因子,对 fpclk的分频结果就是 SCK 引脚的输出时钟频率。其中的 fpclk频率是指 SPI 所在的 APB 总线频率。
    计算结果如下:
BR[0:2]分频结果(SCK 频率)
000fpclk/2
001fpclk/4
010fpclk/8
011fpclk/16
100fpclk/32
101fpclk/64
110fpclk/128
111fpclk/256
  1. 数据控制单元:该部分包含接收缓冲区、发送缓冲区、数据移位寄存器。
    发送数据的时候,数据移位寄存器将发送缓冲区内的数据一位一位发出去;接收数据的时候,数据移位寄存器则把把接收缓冲区内的数据一位一位读进来。并且每个数据帧长度可以通过“控制寄存器 CR1”的“DFF 位”配置成 8 位及 16 位模式。配置“LSBFIRST 位”可选择高位先行(MSB)还是低位先行(LSB)。

    对于数据寄存器(DR),通过写 SPI的数据寄存器可以把数据填入发送缓冲区,通过读SPI的数据寄存器可以获取接收缓冲区中的内容。

  2. 剩下的部分则是整体配置SPI相关的控制部分。SPI的运行模式,则随着我们这部分的配置的不同而不同。除了基本的SPI相关参数的配置,还包括SPI的中断信号、DMA请求、NSS信号线配置等。

从选择(NSS)脚管理,即SS引脚,进行片选

有2种NSS模式:

● 软件NSS模式:可以通过设置SPI_CR1寄存器的SSM位来使能这种模式。内部NSS信号电平可以通过写SPI_CR1的SSI位来驱动,就可以将NSS引脚用作别的功能。

● 硬件NSS模式,分两种情况:

─ NSS输出被使能:当STM32作为主机,并且NSS输出已经通过SPI_CR2寄存器的SSOE位使能,这时NSS引脚被拉低,所有NSS引脚与这个主SPI的NSS引脚相连并配置为硬件NSS的SPI设备,将自动变成从机。当一个SPI设备需要发送广播数据,它必须拉低NSS信号,以通知所有其它设备它是主机;如果它不能拉低NSS,这意味着总线上有另外一个主设备在通信,这时将产生一个硬件失败错误(Hard Fault)。

─ NSS输出被关闭:允许操作于多主机环境。

在这里插入图片描述

通讯过程

(来自野火的《零死角玩转STM32》)

在这里插入图片描述

主模式收发流程及事件说明如下:

(1) 控制 NSS 信号线,产生起始信号(图中没有画出),即先将NSS拉低;

(2) 把要发送的数据写入到“数据寄存器 DR”中,该数据会被存储到发送缓冲区;

(3) 通讯开始,SCK 时钟开始运行。MOSI 把发送缓冲区中的数据一位一位地传输出去;MISO 则把数据一位一位地存储进接收缓冲区中;

(4) 当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“RXNE标志位”会被置 1,表示传输完一帧,接收缓冲区非空;这里的两个标志位要由软件清零。

(5) 等待到 “TXE 标志位” 为 1 时,若还要继续发送数据,则再次往 “数据寄存器DR” 写入数据即可;等待到 “RXNE标志位” 为 1 时,通过读取“数据寄存器DR”可以获取接收缓冲区中的内容。假如我们使能了 TXE 或 RXNE 中断,TXE 或 RXNE 置 1 时会产生 SPI 中断信号,进入同一个中断服务函数,到 SPI 中断服务程序后,可通过检查寄存器位来了解是哪一个事件,再分别进行处理。也可以使用 DMA 方式来收发“数据寄存器 DR”中的数据。

初始化结构体

typedef struct
{
    uint16_t SPI_Direction; 			/*设置 SPI 的单双向模式 */
    uint16_t SPI_Mode; 					/*设置 SPI 的主/从机端模式 */
    uint16_t SPI_DataSize; 				/*设置 SPI 的数据帧长度,可选 8/16 位 */
    uint16_t SPI_CPOL; 					/*设置时钟极性 CPOL,可选高/低电平*/
    uint16_t SPI_CPHA; 					/*设置时钟相位,可选奇/偶数边沿采样 */
    uint16_t SPI_NSS; 					/*设置 NSS 引脚由 SPI 硬件控制还是软件控制*/
    uint16_t SPI_BaudRatePrescaler; 	/*设置时钟分频因子,fpclk/分频数=fSCK */
    uint16_t SPI_FirstBit; 				/*设置 MSB/LSB 先行 */
    uint16_t SPI_CRCPolynomial; 		/*设置 CRC 校验的表达式 */
} SPI_InitTypeDef;

在与其他SPI从机搭配使用时,有时还要参考从机的数据手册;这类从机一般分为两类:

  1. 仅收发数据,比如FLASH芯片中的W25Q64;
  2. 收发数据和指令,这类从机额外使用DC引脚来区别SPI上发送的数据是单纯的数据还是指令,比如OLED等。

软件SPI

软件SPI就是用普通IO口模拟SPI的时序和通讯方法。这里我用SPI通讯方式的LCD搭配STM32CubeMX来做介绍

GPIO配置

其中DC用来区分写数据还是写指令

BLK调节LCD背光

CS即片选

SCK即时钟

SDA即MOSI信号线

需要注意的是,这里的SCK配置的是上拉,即使SCK时钟在空闲时为高电平,即CPOL = 1

在这里插入图片描述

void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(BLK_GPIO_Port, BLK_Pin, GPIO_PIN_SET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, DC_Pin|CS_Pin|SPI_SCK_Pin|SPI_SDA_Pin
                          |RES_Pin, GPIO_PIN_SET);

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = BLK_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(BLK_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pins : PAPin PAPin PAPin PAPin
                           PAPin */
  GPIO_InitStruct.Pin = DC_Pin|CS_Pin|SPI_SCK_Pin|SPI_SDA_Pin
                          |RES_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

模拟时序

很典型的一个写8bit数据的函数,入口数据dat,首先选中LCD,进入循环,对dat拆出高位bit分析是1还是0,再决定MOSI输出的是1还是0,并且这个过程是在一个时钟脉冲内完成的。一次循环结束后,dat左移一位,将低一位的bit推向高位,为下一次循环做好准备。在8次循环过后,就完成了一个8bit数据的发送。其实LCD的使用中,SPI相关的只有以下函数,其他的都是在此之上进行的扩展。(没有明显区分采样时刻是奇数还是偶数边沿)

void LCD_Writ_Bus(u8 dat) 
{	
	u8 i;
	LCD_CS_Clr();
	for(i=0;i<8;i++)
	{			  
		LCD_SCLK_Clr();
		if(dat&0x80)
		{
		   LCD_MOSI_1();
		}
		else
		{
		   LCD_MOSI_0();
		}
		LCD_SCLK_Set();
		dat<<=1;
	}	
  LCD_CS_Set();	
}

当然读取一个数据也可由上面推导出来,但是,LCD上并没有MISO引脚,所以各位看看就好。用不上。

uint8_t LCD_Read_Bus(void)
{
    uint8_t i;
    uint8_t value = 0;
    
	LCD_CS_Clr();
	for(i=0;i<8;i++)
	{			  
		LCD_SCLK_Clr();
        value <<= 1;
        if(LCD_MISO_READ() == 1)
        {
            value = value + 1;
        }
		LCD_SCLK_Set();
	}	
    LCD_CS_Set();	
    return value;
}

比如写一个16位的数据

void LCD_WR_DATA(u16 dat)
{
	LCD_Writ_Bus(dat>>8);
	LCD_Writ_Bus(dat);
}

比如LCD写命令

void LCD_WR_REG(u8 dat)
{
	LCD_DC_Clr();//写命令
	LCD_Writ_Bus(dat);
	LCD_DC_Set();//写数据
}
//因为大多数情况下是写数据,所以这里配置完写命令后要及时切换回写数据

总结

单单从SPI的基本协议来看,SPI可能比IIC更简单一些,只是分出了四种模式,但是单纯的SPI根据不同的从机要对协议进行不一样的扩展,这就提升了编程的难度,但是也加大了协议本身的自由度。速率方面的话,同一个芯片的硬件SPI的速率是绝对远超软件SPI的。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-03-15 22:44:59  更:2022-03-15 22:46:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 6:42:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码