SPI通信
Ⅰ、SPI通信概述
SPI(
Serial Peripheral Interface
,串行外设接口)是一种高速、全双工、同步的通信总线,广泛应用于嵌入式系统与外围设备间的短距离通信。SPI由摩托罗拉公司在20世纪80年代中期开发,后逐渐发展成为行业规范。它采用主从模式,通常由一个主设备(Master)和一个或多个从设备(Slave)组成。主设备负责控制通信过程,包括时钟信号的生成、从设备的选择以及数据的发送与接收
1、SPI技术规格
SPI
四根通信线SCK(Serial Clock)
:时钟信号线,由主设备产生,用于同步数据传输MOSI(Master Output, Slave Input)
:主设备输出、从设备输入的数据线MISO(Master Input, Slave Output)
:主设备输入、从设备输出的数据线CS/SS(Chip Select/Slave Select)
:从设备选择信号线,用于主设备选择与其通信的从设备(默认1)
- 工作模式:SPI有四种工作模式,由CPOL(时钟极性)和CPHA(时钟相位)定义。这决定了数据采样和发送的时钟边沿
- 数据帧格式:通常为8位或16位,可以配置为MSB(高位在前)或LSB(低位在前)。
- 传输速率:SPI支持较高的数据传输速率,通常能达到甚至超过10M/bps
- 通信特点:全双工通信,可以同时发送和接收数据;同步通信,使用时钟信号来同步数据传输;连接简单,硬件结构简单
2、SPI应用
- 存储器芯片:如EEPROM、SRAM、SPI Flash等,用于数据的高速读写和存储
- 传感器:用于与各种传感器进行通信,获取传感器数据
- 显示器:如液晶显示器和OLED显示器,传输图像数据和控制信号
- 通信模块:如无线模块等,用于数据传输
- 其他外设:如ADC、DAC等
3、硬件电路
所有SPI设备的SCK、MOSI、MISO分别连在一起
主机另外引出多条SS控制线,分别接到各从机的SS引脚
输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
移位示意图
Ⅱ、SPI时序基本单元
①、起始条件
SS从高电平切换到低电平
②、终止条件
SS从低电平切换到高电平
③、交换一个字节(模式0)
CPOL
(时钟极性)=0:空闲状态时,SCK为低电平CPHA
(时钟相位)=0:SCK第一个边沿移入数据,第二个边沿移出数据
- 由于SCK第一个边沿就要移入数据,因此在SCK之前,就要先将数据移出
④、交换一个字节(模式1)
CPOL
(时钟极性)=0:空闲状态时,SCK为低电平
CPHA
(时钟相位)=1:SCK第一个边沿移出数据,第二个边沿移入数据
- SCK上升沿时,MOSI和MISO都将数据移至数据寄存器中
- 直到SCK下降沿时,
- 主机移入数据寄存器中的最高位数据“
B7
”移入从机的最低位;- 从机移入数据寄存器中的最高位数据“
B7
”移入主机的最低位,从而完成数据交换
⑤、交换一个字节(模式2)
CPOL
(时钟极性)=1:空闲状态时,SCK为高电平
CPHA
(时钟相位)=0:SCK第一个边沿移入数据,第二个边沿移出数据
⑥、交换一个字节(模式3)
CPOL
(时钟极性)=1:空闲状态时,SCK为高电平
CPHA
(时钟相位)=1:SCK第一个边沿移出数据,第二个边沿移入数据
Ⅲ、SPI时序
①、发送指令
- 向SS指定的设备,发送指令(0x06)
②、指定地址写
- 向SS指定的设备,发送写指令(
0x02
),随后在指定地址(Address[23:0]
)下,写入指定数据(Data
)
③、指定地址读
- 向SS指定的设备,发送读指令(
0x03
),随后在指定地址(Address[23:0]
)下,读取从机数据(Data
)
Ⅳ、W25Q64 - Nor Flash(闪存)
①、硬件电路
②、W25Q64框图
W25Q64
闪存芯片中有8Mbyte
闪存空间,这些空间被划分为128个块(Block 0~Block 127
),每个块占有64kb
每个块又被分成16个扇区(
Sector 0~Sector 15
),每个扇区占有4kb
扇区又可以划分成16个页,每页占有
256byte
③、Flash操作注意事项
写入操作时:
- 写入操作前,必须先进行写使能
- 每个数据位只能由1改写为0,不能由0改写为1
- 写入数据前必须先擦除,擦除后,所有数据位变为1
- 擦除必须按最小擦除单元进行(擦除所有、擦除块、擦除扇区)
- 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
- 写入操作结束后,芯片进入忙状态,不响应新的读写操作
读取操作时:
- 直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取
④、指令表
⑤、电气特性表
⑥、软件模拟SPI
#include "stm32f10x.h" // Device header
#include "MySPI.h"
//模拟SPI
//输出:>SS: PA4
//输出:>SCK: PA5
//输出:>MOSI: PA7
//输入:>MISO: PA6
void MySPI_W_SS(uint8_t BitValue) {
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
void MySPI_W_SCK(uint8_t BitValue) {
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue) {
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}
uint8_t MySPI_R_MISO(void) {
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}
void MySPI_Init(void)
{
//初始化GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置默认电平
MySPI_W_SS(1);
MySPI_W_SCK(0);//使用SPI模式0
}
//起始条件-----SS从高电平切换到低电平
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
//终止条件-----SS从低电平切换到高电平
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
//交换一个字节(模式0)
uint8_t MySPI_SwapByte_Mod0(uint8_t ByteSend)
{
uint8_t ByteReceive = 0;
uint8_t i = 0;
for(i = 0;i < 8;i++)
{
MySPI_W_MOSI(ByteSend & (0x80 >> i));
MySPI_W_SCK(1);
if(MySPI_R_MISO() == 1) {
ByteReceive |= 0x80 >> i;}
MySPI_W_SCK(0);
}
return ByteReceive;
}
方法2:使用移位模型
//uint8_t MySPI_SwapByte_Mod0(uint8_t ByteSend)
//{
// uint8_t ByteReceive = 0;
// uint8_t i = 0;
// for(i = 0;i < 8;i++)
// {
// MySPI_W_MOSI(ByteSend & 0x80);
// ByteSend <<= 1
// MySPI_W_SCK(1);
// if(MySPI_R_MISO() == 1) {ByteSend |= 0x01;}
// MySPI_W_SCK(0);
// }
// return ByteSend;
//}
//交换一个字节(模式1)
uint8_t MySPI_SwapByte_Mod1(uint8_t ByteSend)
{
uint8_t ByteReceive = 0;
uint8_t i = 0;
for(i = 0;i < 8;i++)
{
MySPI_W_SCK