1. 硬件原理
1.1 SPI通信协议
-
SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
-
四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)
-
同步,全双工
-
支持总线挂载多设备(一主多从)
1.2 硬件连接
- 多NSS独立片选方式
- 菊花链方式
引脚 | 含义 |
---|---|
DO(MOSI) | Master Output, Slave Input, SPI主控用来发出数据,SPI从设备用来接收数据,输出引脚设置为推挽输出 |
DI(MISO) | Master Input, Slave Output, SPI主控用来发出数据,SPI从设备用来接收数据,输入引脚设置为浮空或上拉输入,从机不输出时为高阻态。 |
SCK | Serial Clock,时钟 |
CS | Chip Select,芯片选择引脚,NSS 信号线由高变低,是 SPI 通讯的起始信号。NSS 信号由低变高,是 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。 |
移位寄存器示意图
1.3 时序
- 起始:CS从高到低
- 终止:CS从低到高
SPI的时钟极性CPOL和时钟相位CPHA可以分别为0或1,由此构成了四种组合:
CPOL | CPHA | 模式 | 含义 |
---|---|---|---|
0 | 0 | 0 | CLK初始电平为低电平,在第一个时钟沿采样数据 |
0 | 1 | 1 | CLK初始电平为低电平,在第二个时钟沿采样数据 |
1 | 0 | 2 | CLK初始电平为高电平,在第一个时钟沿采样数据 |
1 | 1 | 3 | CLK初始电平为高电平,在第二个时钟沿采样数据 |
常用的是模式0和模式3,因为它们都是在上升沿采样数据,不用去在乎时钟的初始电平是什么,只要在上升沿采集数据就行。
IIC有效字节流数据第一个字节是寄存器地址,之后是读写的数据,使用的是读写寄存器模型
SPI用指令码加读写数据模型,发送指令字节的方式来读取,从机中有指令集对应,
发送指令。
指定地址读。
指定地址写。
1.4 代码
IO模拟
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
uint8_t MySPI_SwapByte(uint8_t ByteSend) //交换字节,发送的话就不需要读取返回值,接收的话就发送0XFF接收返回的数据。
{
uint8_t i, ByteReceive = 0x00;
for (i = 0; i < 8; i ++) //模式0,其他模式对着时序图换一下MySPI_W_SCK就行
{
MySPI_W_MOSI(ByteSend & (0x80 >> i));
MySPI_W_SCK(1);
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
MySPI_W_SCK(0);
}
return ByteReceive;
}
1.5 SPI FLASH W25Qxx
1.5.1 硬件原理
-
W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景
-
存储介质:Nor Flash(闪存)
-
时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)
-
存储容量(24位地址):
W25Q40: 4Mbit / 512KByte
W25Q80: 8Mbit / 1MByte
W25Q16: 16Mbit / 2MByte
W25Q32: 32Mbit / 4MByte
W25Q64: 64Mbit / 8MByte
W25Q128: 128Mbit / 16MByte
W25Q256: 256Mbit / 32MByte
spi flash hold
spi flash hold
该引脚接收到低电平时,且 /CS=0,数据引脚为高阻态,芯片可以屏蔽总线的数据和时钟信号,当引脚为高电平时,可以继续恢复对芯片的操作,适用于多设备SPI控制,分时使用。这个引脚的意义是引进了3种设备情况:设备不被选中,被选中但不工作,被选中且工作;没有这个引脚功能时,芯片只有两种情况:不被选中,选中且工作。
该引脚通过控制寄存器可以有复用功能,作为数据引脚。
SPI Flash有三种工作模式
SPI Flash有三种工作模式:Standard SPI,Dual SPI,Quad SPI。这三种模式的区别在于数据引脚的数量和功能不一样。
Standard SPI
标准SPI,也就是我们常说的四线:片选 (/CS),时钟 (CLK),输入数据 (DI),输出数据 (DO)。另外配有写保护 (/WP) 和维持 (/HOLD) 功能。
Dual SPI
这种工作模式就是对标准SPI进行了改进,将DO,DI改成IO1和IO2,变成了双向IO口,这样一个时钟周期可以读写2位数据。写保护(/WP)和维持(/HOLD)功能仍然保留。
Quad SPI
这种工作模式是对Dual SPI模式进行改进,就是上面讲的,将写保护 (/WP) 和维持 (/HOLD) 引脚复用为IO口,标记为IO3,IO4,这样总共就是四个IO口,数据传送速度更快。
- 空间划分:块64KB,扇区4KB,页256B
- SPI控制逻辑
- 状态寄存器
- 页缓存,会对一次性写入的数据缓存
1.5.2 Flash操作注意事项
写入操作时:
-
写入操作前,必须先进行写使能
-
每个数据位只能由1改写为0,不能由0改写为1(不能覆盖改写,要先擦除,发送擦除指令)
-
写入数据前必须先擦除,擦除后,所有数据位变为1
-
擦除必须按最小擦除单元进行
-
连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
-
写入操作结束后,芯片进入忙状态,不响应新的读写操作
读取操作时:
- 直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取
BUSY
- 擦除和写入会变为忙状态,每次操作都要先读是不是忙状态再操作。
WEL
- 任何写入都要先写使能
1.5.3 代码
void W25Q64_Init(void)
{
MySPI_Init();
}
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_JEDEC_ID); //发送读ID指令
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换读取8位厂商ID
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换读取高8位设备ID
*DID <<= 8;
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);//交换读取低8位设备ID
MySPI_Stop();
}
void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE);
MySPI_Stop();
}
void W25Q64_WaitBusy(void)
{
uint32_t Timeout;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); //读状态寄存器
Timeout = 100000;
while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01) //查看是否忙状态
{
Timeout --;
if (Timeout == 0) //超时判断
{
break;
}
}
MySPI_Stop();
}
//页编程:页的某个地址写一堆数据
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
uint16_t i;
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
MySPI_SwapByte(Address >> 16); //先发地址最高位
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for (i = 0; i < Count; i ++) //循环发送数据
{
MySPI_SwapByte(DataArray[i]);
}
MySPI_Stop();
W25Q64_WaitBusy(); //事后等待,保证出了这个函数是不忙的,会浪费资源。
//也可以放在最前面,事前等待
}
//扇区擦除
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
MySPI_Stop();
W25Q64_WaitBusy();
}
//读数据
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint32_t i;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_DATA);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for (i = 0; i < Count; i ++)
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
}
MySPI_Stop();
}
1.6 常见面试问题
SPI总线的工作频率
SPI是一种事实标准,由Motorola开发,并没有一个官方标准。已知的有的器件SPI已达到50Mbps。具体到产品中SPI的速率主要看主从器件SPI控制器的性能限制。
SPI最大传输速率受以下几个条件影响:
1)SPI的最大时钟频率;
2)CPU处理SPI数据的能力;
3)输出端驱动能力(PCB所允许的最大信号传输速率);
W25Qxx系列时钟频率:80MHz/160MHz(Dual SPI)/320MHz(Quad SPI)
SPI优缺点
SPI的优点
- 全双工串行通信;
- 高速数据传输速率。
- 简单的软件配置;
- 极其灵活的数据传输,不限于8位,它可以是任意大小的字;
- 非常简单的硬件结构。从站不需要唯一地址(与I2C不同)。从机使用主机时钟,不需要精密时钟振荡器/晶振(与UART不同)。不需要收发器(与CAN不同)。
SPI的缺点
- 没有硬件从机应答信号(主机可能在不知情的情况下无处发送);
- 通常仅支持一个主设备;
- 需要更多的引脚(与I2C不同);
- 没有定义硬件级别的错误检查协议;
- 与RS-232和CAN总线相比,只能支持非常短的距离;
2. 应用编程–spidev应用程序分析
内核提供的测试程序:tools\spi\spidev_fdx.c
2.1 使用方法
spidev_fdx [-h] [-m N] [-r N] /dev/spidevB.D
- -h: 打印用法
- -m N:先写1个字节0xaa,再读N个字节,**注意:**不是同时写同时读
- -r N:读N个字节
2.2 代码分析
2.2.1 显示设备属性
2.2.2 读数据
2.2.3 先写再读
2.2.4 同时读写
2.3 spidev的缺点
使用read、write函数时,只能读、写,这是半双工方式。
使用ioctl可以达到全双工的读写。
但是spidev有2个缺点:
- 不支持中断
- 只支持同步操作,不支持异步操作:就是read/write/ioctl这些函数只能执行完毕才可返回
3.内核驱动
总线-设备-驱动模型
SPI子系统中:SPI控制器、SPI设备-驱动。
在设备树里,会有一个节点用来表示SPI控制器。
在这个SPI控制器下面,连接有哪些SPI设备?会在设备树里使用子节点来描述SPI设备。
官方的通用dev驱动spidev驱动程序
自己编写设备spi驱动程序,如DAC4822,TLC5615,OLED SD1306
SPI_Master驱动程序框架
SPI_Slave_Mode驱动程序框架