SPI是串行外围设备接口(通信协议)、高速、全双工、串行的通信总线、占用了3/4根线、有时钟线与数据的输入输出线、有主从结构的。SPI 主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。
SPI 是一个环形总线结构,由 ss(cs)(片选)、sck、sdi(主机输入)、sdo(主机输出) 构成,其时序其实很简单,主要是在 sck 的控制下,两个双向移位寄存器进行数据交换。
三线: CS MOSI/MISO SCK 半双工
四线: CS MOSI MISO SCK 全双工
CS: 片选 ,设备被选,拉低有效的,连接从机
MOSI: 主机输出 从机输入
MISO: 主机输入 从机输出
SCK: 时钟线
选择从机进行通信:
IIC→通过从机的器件地址
SPI→通过片选线选中需要通信的从机
SPI通信原理
上升沿发送、下降沿接收、高位先发送。
上升沿到来的时候,sdo 上的电平将被发送到从设备的寄存器中。
下降沿到来的时候,sdi 上的电平将被接收到主设备的寄存器中。
假设主机和从机初始化就绪:并且主机的 sbuff=0xaa (10101010),从机的sbuff=0x55 (01010101),下面将分步对 spi 的 8 个时钟周期的数据情况演示一遍(假设上升沿发送数据)。
由脉冲0到脉冲1,产生上升沿时,主机发送最高位1,从机发送最高位0,主机sbuff左移一位等待接收数据,从机左移一位接收主机发送的1,等待下降沿时,主机接收数据。持续产生脉冲,实现两个寄存器8位的交换。
SPI 总线有四种工作方式(SP0, SP1, SP2, SP3),其中使用的最为广泛的是 SPI0 和 SPI3 方式。
时钟极性(CPOL): CPOL=0 默认电平为低 CPOL=1 默认电平为高
时钟相位(CPHA): CPHA=0 第一个边沿进行采样 CPHA=1 第二个边沿进行采样
模式 | 时钟极性(CPOL) | 时钟相位(SPHA) | 工作模式 |
---|---|---|---|
SP0 | 0 | 0 | 上升沿被采样,下降沿发送数据 |
SP1 | 0 | 1 | 下降沿被采样,上升沿发送数据 |
SP2 | 1 | 0 | 下降沿被采样,上升沿发送数据 |
SP3 | 1 | 1 | 上升沿被采样,下降沿发送数据 |
● 基于三条线的全双工同步传输
● 基于双线的单工同步传输,其中一条可作为双向数据线
● 8 位或 16 位传输帧格式选择
● 主模式或从模式操作
● 多主模式功能
● 8 个主模式波特率预分频器(最大值为 fPCLK/2)
● 从模式频率(最大值为 fPCLK/2)
● 对于主模式和从模式都可实现更快的通信
● 对于主模式和从模式都可通过硬件或软件进行 NSS 管理:动态切换主/从操作
● 可编程的时钟极性和相位
● 可编程的数据顺序,最先移位 MSB 或 LSB
● 可触发中断的专用发送和接收标志
MOSI: 数据输出引脚、类似TX
数据方向: TDR----》发送缓冲区—》移位寄存器—》MOSI
MISO: 数据输入引脚、类似RX
数据方向: MISO—》移位寄存器—》接收缓冲区—》RDR
TDR与RDR为同一个寄存器(DR)
/*
函 数 名:Flash_SPI_Config
函数功能:Flash_SPI初始化
返 回 值:无
形 参:无
备 注:
FLASH_SPI1_MOSI -- PA7 -- 复用推挽输出
FLASH_SPI1_MISO -- PA6 -- 复用输入
FLASH_SPI1_CS -- PA4 -- 通用推挽输出
FLASH_SPI1_SCLK -- PA5 -- 复用推挽输出
*/
void Flash_SPI_Config(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //打开PA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE); //打开SPI1时钟
GPIO_InitTypeDef gpio_InitTypeDef = {0}; //定义GPIO结构体
gpio_InitTypeDef.GPIO_Mode = GPIO_Mode_AF; //复用功能
gpio_InitTypeDef.GPIO_OType = GPIO_OType_PP; //推挽输出
gpio_InitTypeDef.GPIO_Pin = GPIO_Pin_7; //PA7
gpio_InitTypeDef.GPIO_Speed = GPIO_Fast_Speed; //高速
GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_SPI1); //串口复用功能
GPIO_Init(GPIOA,&gpio_InitTypeDef);
gpio_InitTypeDef.GPIO_Mode = GPIO_Mode_AF; //复用输入功能
gpio_InitTypeDef.GPIO_Pin = GPIO_Pin_6; //PA6
gpio_InitTypeDef.GPIO_Speed = GPIO_Fast_Speed; //高速
GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_SPI1); //串口复用功能
GPIO_Init(GPIOA,&gpio_InitTypeDef);
gpio_InitTypeDef.GPIO_Mode = GPIO_Mode_OUT; //通用输出功能
gpio_InitTypeDef.GPIO_OType = GPIO_OType_PP; //推挽输出
gpio_InitTypeDef.GPIO_Pin = GPIO_Pin_4; //PA4
gpio_InitTypeDef.GPIO_Speed = GPIO_Fast_Speed; //高速
GPIO_Init(GPIOA,&gpio_InitTypeDef);
gpio_InitTypeDef.GPIO_Mode = GPIO_Mode_AF; //通用输出功能
gpio_InitTypeDef.GPIO_OType = GPIO_OType_PP; //推挽输出
gpio_InitTypeDef.GPIO_Pin = GPIO_Pin_5; //PA5
gpio_InitTypeDef.GPIO_Speed = GPIO_Fast_Speed; //高速
GPIO_PinAFConfig(GPIOA,GPIO_PinSource5,GPIO_AF_SPI1); //串口复用功能
GPIO_Init(GPIOA,&gpio_InitTypeDef);
SPI1->CR1 &= ~(3<<0); //时钟相位,时钟极性
SPI1->CR1 |= (1<<2); //主模式选择(注配置)
SPI1->CR1 &= ~(1<<7); //先发送MSB
SPI1->CR1 |= (3<<8); //使能软件从器件管理(片选)
SPI1->CR1 &= ~(1<<11); //8位数据帧格式
SPI1->CR1 &= ~(1<<15); //双线单项通信数据模式
SPI1->CR1 |= (1<<6); //SPI使能
}
/*
函 数 名:SPI1_ExchangeByte
函数功能:数据收发
返 回 值:待发送的数据
形 参:接收到的数据
备 注:
*/
u8 SPI1_ExchangeByte(u8 sData)
{
//等待发送缓冲区为空
while(!(SPI1->SR & (1 << 1)));
SPI1->DR = sData;
//等待接收缓冲区非空
while(!(SPI1->SR & (1 << 0)));
return SPI1->DR;
}
W25Q64 (64M-bit),W25Q16(16M-bit)和 W25Q32(32M-bit)是为系统提供一个最小的空间、引脚和功耗的存储器解决方案的串行 Flash 存储器。25Q 系列比普通的串行 Flash 存储器更灵活,性能更优越。基于双倍/四倍的 SPI,它们能够可以立即完成提供数据给 RAM,包括存储声音、文本和数据。芯片支持的工作电压 2.7V 到 3.6V,正常工作时电流小于 5mA,掉电时低于 1uA。所有芯片提供标准的封装。
W25Q64/16/32 由每页 256 字节组成。每页的 256 字节用一次页编程指令即可完成。每次可以擦除 16 页(1 个扇区)、128 页(32KB 块)、256 页(64KB 块)和全片擦除。
Flash存储:掉电不丢失、常见的flash设备(U盘 硬件 光盘……)
片选端(CS):
SPI 片选(/CS)引脚使能和禁止芯片操作。当CS为高电平时,芯片未被选择,串行数据输出(DO、IO0、IO1、IO2 和 IO3)引脚为高阻态。未被选择时,芯片处于待机状态下的低功耗,除非芯片内部在擦除、编程。当/CS 变成低电平,芯片功耗将增长到正常工作,能够从芯片读写数据。上电后,在接收新的指令前,/CS 必须由高变为低电平。上电后,/CS 必须上升到 VCC。在/CS 接上拉电阻可以完成这个。
写使能
/*
函 数 名:W25Qxx_Write_Enable
函数功能:写使能
返 回 值:无
形 参:无
备 注:
指令码--0x06
*/
void W25Qxx_Write_Enable(void)
{
SPI_CS = 0;
SPI1_ExchangeByte(0x06);
SPI_CS = 1;
}
读状态寄存器
注: 除了指令外,在芯片内部存储的数据一般默认为0xff。
读取状态寄存器的指令是 8 位的指令。发送指令之前,先将/ CS 拉低,再发送指令码“05 h”或者“35h”。设备收到读取状态寄存器的指令后,将状态信息(高位)依次移位发送出去,上图所示。读出的状态信息,最低位为 1 代表忙,最低位为 0 代表可以操作,状态信息读取完毕,将片选线拉高。
读状态寄存器指令可以使用在任何时候,即使程序在擦除的过程中或者写状态寄存器周期正在进中。这可以检测忙碌状态来确定周期是否完成,以确定设备是否可以接受另一个指令
/*
函 数 名:W25Qxx_Read_State
函数功能:状态读取寄存器1
返 回 值:状态寄存器值
形 参:无
备 注:
状态寄存器1 -- 0x05
状态寄存器2 -- 0x35
*/
u16 W25Qxx_Read_State(void)
{
u16 state;
SPI_CS = 0;
SPI1_ExchangeByte(0x05);
state = SPI1_ExchangeByte(0xff);
SPI_CS = 1;
return state;
}
读数据
读取数据指令允许按顺序读取一个字节的内存数据。当片选 CS/拉低之后,紧随其后是一个 24 位的地址(A23-A0)(需要发送 3 次,每次 8 个字节,先发高位)。芯片收到地址后,将要读的数据按字节大小转移出去,数据是先转移高位,对于单片机,时钟下降沿发送数据,上升沿接收数据。读数据时,地址会自动增加,允许连续的读取数据。这意味着读取整个内存的数据,只要用一个指令就可以读完。数据读取完成之后,片选信号/ CS 拉高。
读取数据的指令序列,如上图所示。如果一个读数据指令而发出的时候,设备正在擦除扇区,或者(忙= 1),该读指令将被忽略,也不会对当前周期有什么影响。
/*
函 数 名:W25Qxx_Read_Data
函数功能:读数据
返 回 值:无
形 参:u32 addr,u32 len,u8 *rev_buff
备 注:
*/
void W25Qxx_Read_Data(u32 addr,u16 len,u8 *rev_buff)
{
SPI_CS = 0;
SPI1_ExchangeByte(0x03);
SPI1_ExchangeByte(addr>>16);
SPI1_ExchangeByte(addr>>8);
SPI1_ExchangeByte(addr);
while(len--)
{
*rev_buff++=SPI1_ExchangeByte(0xff);
}
SPI_CS = 1;
while((W25Qxx_Read_State()&(1<<0)));
}
页编程
页编程指令允许从一个字节到 256 字节的数据编程(一页)(编程之前必须保证内存空间是 0XFF)。允许写入指令之前,必须先发送设备写使能指令。写使能开启后,设备才能接收编程指令。开启页编程先拉底/ CS,然后发送指令代码“02 h”,接着发送一个 24 位地址(A23-A0)(发送 3 次,每次 8 位) 和至少一个数据字节(数据字节不能超过 256字节)。数据字节发送完毕,需要拉高片选线 CS/,,并判断状态位,等待写入结束。
进行页编程时,如果数据字节数超过了 256 字节,地址将自动回到页的起始地址,覆盖掉之前的数据。在某些情况下,数据字节小于 256 字节(同一页内),也可以正常对其他字节存放,不会有任何影响。如果存放超过 256 字节的数据,需要分次编程存放。
/*
函 数 名:W25Qxx_Page_Program
函数功能:页编程
返 回 值:无
形 参:u32 addr,u32 len,u8 *send_buff
备 注:
写之前擦除一下,避免数据紊乱
*/
void W25Qxx_Page_Program(u32 addr,u16 len,u8 *send_buff)
{
W25Qxx_Write_Enable();
SPI_CS = 0;
SPI1_ExchangeByte(0x02);
SPI1_ExchangeByte(addr>>16);
SPI1_ExchangeByte(addr>>8);
SPI1_ExchangeByte(addr);
while(len--)
{
SPI1_ExchangeByte(*send_buff++);
}
SPI_CS = 1;
while((W25Qxx_Read_State()&(1<<0)));
}
/*
扇区擦除
扇区擦除指令可以擦除指定一个扇区(4 k 字节)内所有数据,将内存空间恢复到 0xFF 状态。写入扇区擦除指令之前必须执行设备写使能(发送设备写使能指令 0x06),并判断状态寄存器(状态寄存器位最低位必须等于 0才能操作)。发送的扇区擦除指令前,先拉低/ CS,接着发送扇区擦除指令码”20 h”,和 24 位地址(A23-A0),地址发送完毕后,拉高片选线 CS/,,并判断状态位,等待擦除结束。擦除一个扇区的最少需要 150ms 时间。
/*
函 数 名:W25Qxx_Sector_Erase
函数功能:扇擦除
返 回 值:无
形 参:u32 addr --待删除得扇区首地址
备 注:
*/
void W25Qxx_Sector_Erase(u32 addr)
{
W25Qxx_Write_Enable();
while((W25Qxx_Read_State()&(1<<0)));
SPI_CS = 0;
SPI1_ExchangeByte(0x20);
SPI1_ExchangeByte(addr>>16);
SPI1_ExchangeByte(addr>>8);
SPI1_ExchangeByte(addr);
SPI_CS = 1;
while((W25Qxx_Read_State()&(1<<0)));
}
跨页写
/*
函 数 名:W25Qxx_Stride_Pagewrite
函数功能:跨页写数据
返 回 值:无
形 参:u32 addr,u32 len,u8 *rev_buff
备 注:
使用之前擦除一下
*/
void W25Qxx_Stride_Pagewrite(u32 addr,u16 length,u8 *send_buff)
{
u16 n = 256-addr%256;
if(length<=n)
{
n = length;
}
do{
W25Qxx_Page_Program(addr,n,send_buff);
addr = addr+n;
send_buff+=n;
length-=n;
if(length>256)
{
n =256;
}
else{
n = length;
}
}while(length);
}
注: 当使用页编程或跨页写时,需注意存放数据的数组大小,在STM32当中,局部变量存储的栈空间只能存储0x4000字节的数据。