SPI简介
SPI是一种同步串行通信接口规范,用于短距离通信,主要应用于嵌入式系统。SPI通信由一个主设备和一个或多个从设备组成,其中主设备生成时钟信号并控制通信的开始和结束。
SPI通信的基本组成
四个基本信号
SCK (Serial Clock) - 时钟信号,由主设备产生。
MOSI (Master Out Slave In) - 主设备输出,从设备输入的数据线。
MISO (Master In Slave Out) - 主设备输入,从设备输出的数据线。
CS (Chip Select) - 芯片选择信号,用于激活特定的从设备。
SPI操作模式
SPI有四种操作模式,由主设备产生的时钟信号的相位和极性决定:
模式0:CPOL = 0,CPHA = 0
模式1:CPOL = 0,CPHA = 1
模式2:CPOL = 1,CPHA = 0
模式3:CPOL = 1,CPHA = 1
其中,CPOL代表时钟的极性,CPHA代表时钟的相位。不同的模式会影响数据采样和设置的时间点。
1.Mode 0 (CPOL = 0, CPHA = 0)
时钟极性(CPOL):时钟信号的空闲状态为低电平。
时钟相位(CPHA):数据在时钟的上升沿被采样,并在下降沿改变。
特点:这是最常见的SPI模式。数据在第一个时钟脉冲的上升沿读取,之后每个时钟周期的上升沿都会读取新数据。
2. Mode 1 (CPOL = 0, CPHA = 1)
时钟极性(CPOL):时钟信号的空闲状态为低电平。
时钟相位(CPHA):数据在时钟的下降沿被采样,并在上升沿改变。
特点:与Mode 0相比,数据采样发生在时钟的下降沿。
3. Mode 2 (CPOL = 1, CPHA = 0)
时钟极性(CPOL):时钟信号的空闲状态为高电平。
时钟相位(CPHA):数据在时钟的上升沿被采样,并在下降沿改变。
特点:与Mode 0相比,时钟的空闲状态为高电平。
4. Mode 3 (CPOL = 1, CPHA = 1)
时钟极性(CPOL):时钟信号的空闲状态为高电平。
时钟相位(CPHA):数据在时钟的下降沿被采样,并在上升沿改变。
特点:这种模式下,时钟的空闲状态为高电平,数据采样在时钟的下降沿。
通信过程
初始化:主设备通过拉低CS信号来选择特定的从设备。
数据传输:主设备通过MOSI发送数据,并在同一时钟周期内通过MISO接收从设备的数据。
时钟同步:数据在时钟的上升沿或下降沿被采样,这取决于SPI的工作模式。
W25Q64简介
W25Q64CV是一款高性能SPI闪存芯片,具备64M位(8M字节)的存储容量,用于需要高速数据存储和读取的嵌入式系统。操作电压为2.7V至3.6V,提供工业级(-40°C至+85°C)和汽车级(-40°C至+105°C)的温度规格,具备软件和硬件写保护功能,支持安全启动和数据保护。
引脚
/CS: 芯片选择输入,用于激活或禁用设备
DO (IO1): 主设备输出,从设备输入的串行数据线
/WP (IO2): 写保护输入,可配置为输入/输出2
GND: 地
VCC: 电源
/HOLD (IO3): 保持输入,可配置为输入/输出3。允许系统在Flash设备正在被选中(即/CS为低电平)时,挂起或暂停当前正在进行的操作。当/HOLD引脚被释放(即拉高回到高电平)时,设备可以从之前挂起的地方恢复操作,无需重新开始整个操作流程
DI (IO0): 主设备输出,从设备输入的串行数据线
CLK: 串行时钟输入,控制数据传输的时序
存储
提供8M字节的存储空间,适用于存储大量数据和程序代码,存储阵列被组织成32,768个可编程页,每页256字节。页可以以16(4KB扇区擦除)、128(32KB块擦除)、256(64KB块擦除)或整个芯片(芯片擦除)的组进行擦除
SPI
支持SPI模式0和模式3,支持标准SPI、双SPI和四SPI操作,时钟频率高达80MHz,实现快速数据传输
指令
主要指令分类
制造商和设备识别指令:
用于读取制造商ID和设备ID,帮助用户确认连接的设备。
读指令:
包括普通读取(Read Data)、快速读取(Fast Read)、快速双输出读取(Fast Read Dual Output)等,用于从Flash中读取数据。
写指令:
包括页编程(Page Program)和四页编程(Quad Page Program),用于向Flash写入数据。
擦除指令:
包括扇区擦除(Sector Erase)、32KB块擦除(32KB Block Erase)、64KB块擦除(64KB Block Erase)和芯片擦除(Chip Erase),用于清除Flash中的存储空间。
状态寄存器指令:
包括读取状态寄存器(Read Status Register)和写状态寄存器(Write Status Register),用于监控和配置设备的状态。
安全和保护指令:
包括擦除安全寄存器(Erase Security Registers)和编程安全寄存器(Program Security Registers),用于管理设备的安全性。
特殊功能指令:
如设置突发(Set Burst with Wrap)、连续读取模式位(Continuous Read Mode Bits)、电源下降(Power-down)等。
指令执行流程
选中设备:通过拉低CS(Chip Select)引脚,选中目标Flash设备。
发送指令:通过DI(Data Input)或IO0(Data Input 0)引脚发送指令的操作码。
提供地址(如需要):某些指令,如读取、写入或擦除操作,需要用户提供24位的地址信息。
数据传输:根据指令的功能,进行数据的读取或写入。
等待完成:某些操作如擦除或编程可能需要一定的时间来完成,此时可以通过检查状态寄存器中的BUSY位来确定操作是否完成。
取消选中:操作完成后,通过拉高CS引脚取消选中设备
擦除
在闪存设备,包括W25Q64CV这样的SPI Flash Memory中,写入操作之前需要进行擦除,因为闪存是一种非易失性存储器,它使用浮栅晶体管来存储数据。每个浮栅晶体管可以表示两种状态:擦除状态和编程状态。擦除状态:在擦除状态下,浮栅中几乎没有电荷,晶体管的阈值电压较高,因此在读取操作时,晶体管不导电,表示数据"1"。编程状态:在编程状态下,浮栅捕获了电荷,降低了晶体管的阈值电压,使得在读取操作时晶体管导电,表示数据"0"。由于浮栅晶体管的设计,它们不能从"0"状态直接改为"1"状态,而是需要先擦除回到"1"状态,然后再编程为"0"。
擦除步骤
初始化:确保SPI接口已正确配置,并且W25Q64CV已选中。
写使能:发送06h指令以使能写操作。
执行擦除指令:根据需要执行扇区擦除、块擦除或芯片擦除的指令。
等待完成:擦除操作需要一定的时间来完成。可以通过检查状态寄存器中的BUSY位来确定擦除是否完成。
// 假设已经有了SPI通信的基础函数库:spi_begin(), spi_end(), spi_write(), spi_read()
// 以及控制CS引脚的函数:cs_select() 和 cs_deselect()
// 擦除操作前的写使能函数
void spi_write_enable(void) {
cs_select(); // 选中Flash设备
spi_write(0x06); // 发送写使能指令
cs_deselect(); // 取消选中设备
}
// 扇区擦除函数
void spi_sector_erase(uint32_t address) {
spi_write_enable(); // 使能写操作
cs_select(); // 选中Flash设备
spi_write(0x20); // 发送扇区擦除指令
spi_write((address >> 16) & 0xFF); // 发送地址的高8位
spi_write((address >> 8) & 0xFF); // 发送地址的中8位
spi_write(address & 0xFF); // 发送地址的低8位
cs_deselect(); // 取消选中设备
spi_wait_until_ready(); // 等待擦除完成
}
// 32KB块擦除函数
void spi_32kb_block_erase(uint32_t address) {
spi_write_enable(); // 使能写操作
cs_select(); // 选中Flash设备
spi_write(0x52); // 发送32KB块擦除指令
spi_write((address >> 16) & 0xFF); // 发送地址的高8位
spi_write((address >> 8) & 0xFF); // 发送地址的中8位
spi_write(address & 0xFF); // 发送地址的低8位
cs_deselect(); // 取消选中设备
spi_wait_until_ready(); // 等待擦除完成
}
// 64KB块擦除函数
void spi_64kb_block_erase(uint32_t address) {
spi_write_enable(); // 使能写操作
cs_select(); // 选中Flash设备
spi_write(0xD8); // 发送64KB块擦除指令
spi_write((address >> 16) & 0xFF); // 发送地址的高8位
spi_write((address >> 8) & 0xFF); // 发送地址的中8位
spi_write(address & 0xFF); // 发送地址的低8位
cs_deselect(); // 取消选中设备
spi_wait_until_ready(); // 等待擦除完成
}
// 芯片擦除函数
void spi_chip_erase(void) {
spi_write_enable(); // 使能写操作
cs_select(); // 选中Flash设备
spi_write(0xC7); // 发送芯片擦除指令
cs_deselect(); // 取消选中设备
spi_wait_until_ready(); // 等待擦除完成
}
// 等待设备准备就绪的函数
void spi_wait_until_ready(void) {
// 这里应实现等待就绪的逻辑,可能通过检查状态寄存器的BUSY位
// 例如:
uint8_t status;
do {
cs_select();
spi_write(0x05); // 发送读取状态寄存器1的指令
status = spi_read(0xFF); // 读取状态寄存器1
cs_deselect();
} while ((status & 0x01) != 0); // 等待BUSY位清零
}
读写
写入步骤
写使能
选中设备:通过拉低CS(Chip Select)引脚,选中Flash设备
发送写使能指令:通过DI(Data Input)引脚发送06h指令码,使能写操作
页编程 (Page Program)
发送页编程指令:通过DI引脚发送02h指令码
提供地址:发送24位地址信息,指向要编程的起始位置
发送数据:顺序发送要写入的数据。W25Q64CV支持的数据长度从1字节到256字节
等待完成:等待编程操作完成。这可以通过轮询状态寄存器中的BUSY位来实现
// 检查设备是否忙碌的函数
uint8_t spi_check_busy(void) {
uint8_t status;
cs_select(); // 选中Flash设备
spi_write(0x05); // 发送读取状态寄存器1的指令
status = spi_read(0xFF); // 读取状态寄存器1
cs_deselect(); // 取消选中设备
return status;
}
// 等待编程完成的函数
void spi_wait_until_ready(void) {
while (spi_check_busy() & 0x01); // 检查BUSY位,如果为1则等待
}
// 完整的编程函数,包括等待编程完成
void spi_flash_page_program(uint32_t address, const uint8_t *data, uint16_t dataSize) {
cs_select(); // 选中Flash设备
spi_write(0x06); // 发送写使能指令
cs_deselect(); // 取消选中设备
cs_select(); // 重新选中设备
spi_write(0x02); // 发送页编程指令
spi_write((address >> 16) & 0xFF); // 发送地址的高8位
spi_write((address >> 8) & 0xFF); // 发送地址的中8位
spi_write(address & 0xFF); // 发送地址的低8位
for (int i = 0; i < dataSize; ++i) {
spi_write(data[i]); // 发送数据
}
cs_deselect(); // 取消选中设备
spi_wait_until_ready(); // 等待编程完成
}
读取步骤
选中设备:拉低CS引脚
发送读取数据指令:通过DI引脚发送03h指令码
提供地址:发送24位地址信息,指向要读取的起始位置
读取数据:数据将通过DO(Data Output)引脚顺序输出
void readData(uint32_t address, uint8_t *buffer, uint16_t dataSize) {
cs_select();
spi_write(0x03); // 发送读取数据指令
spi_write(address >> 16); // 发送高8位地址
spi_write(address >> 8); // 发送中8位地址
spi_write(address); // 发送低8位地址
for (int i = 0; i < dataSize; ++i) {
buffer[i] = spi_read(0xFF); // 读取数据
}
cs_deselect();
}