Serial peripheral interface
- 1> 实验概述
- 2> SPI硬件框图
- 初始化程序
- 3> STM32的SPI通信时序
- 3.1> 时序图
- 3.2> 文字描述
- 3.3> 注意事项
- 3.4> 流程图表示
- 3.5> 程序表示
- 接收程序:
- 发送程序:
- 4> SPI的4种模式
- 5> W25Q128存储结构
- 块 > 扇区 > 页
- 6> W25Q128常用命令
- 6.1> 读状态寄存器
- 检测忙程序
- 6.2> 写使能
- 写使能-程序
- 6.3> 擦除1个扇区
- 擦除1个扇区-程序
- 6.4> 写入1页Page数据
- 写1页数据-程序
- 6.5> 读数据
- 7> 测试程序
- 7.1> 逻辑分析仪 抓波形
1> 实验概述
使用STM32的SPI硬件模块,读写Flash
2> SPI硬件框图
MOSI : Master Output Slave Input;
MISO: Master Input Slave Output;
初始化程序
/*** @brief SPI硬件模块配置,全双工, 高位优先* @note SPI2, CS-PB12, SCK-PB13, MISO-PB14, MOSI-PB15;*/
void NorFLASH_Init(void)
{GPIO_InitTypeDef GPIO_InitStruct;SPI_InitTypeDef SPI_InitStruct;/* 首先开启时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);/* GPIO参数配置 */// CS-PB12GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);// SCK-PB13GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);// MISO-PB14GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);// MOSI-PB15GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);/* SPI2参数配置 */SPI_InitStruct.SPI_Mode = SPI_Mode_Master;SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge; // Mode 3;SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;SPI_InitStruct.SPI_CRCPolynomial = 0x07; // 复位值,无用SPI_Init(SPI2, &SPI_InitStruct);SPI_CalculateCRC(SPI2, DISABLE); // 关闭硬件CRC校验/* 使能SPI2 */SPI_Cmd(SPI2, ENABLE);
}
3> STM32的SPI通信时序
3.1> 时序图
3.2> 文字描述
Step 1> 写【第1】字节数据到SPI_DR;
Step 2> 等待【TXE == 1】, 写【第2】字节到SPI_DR;
Step 3> 等待【RXNE == 1】, 读SPI_DR, 得到【第1】字节数据;
Step 4> 如果要读写多个字节【循环重复】第2步和第3步;
Step 5> 等待【RXNE == 1】, 读SPI_DR, 得到【最后】字节数据;
Step 6> 等待【TXE == 1】,完成读写;
3.3> 注意事项
1> 接收数据时,SPI也必须发送数据,这样才能产生SCK时钟;(这点设计的感觉不好)
2> 在MISO线上输出完8bit数据后,才会存储到SPI_DR, 所以他相等于落后了一个字节;
3> TXE的标志由硬件置1,写SPI_DR可以清除;
4> RXNE 标志是由硬件置1,读SPI_DR可以清除;
3.4> 流程图表示
3.5> 程序表示
接收程序:
接收数据,也需要发送数据,通常发送无意义的0xFF
/*** @brief 接收多字节数据* @param pRxData 接收数据缓冲区* @param size 接收size字节数据*/
static void SPI2_Receive(uint8_t *pRxData, uint16_t size)
{// Step 1> 发送第1字节数据SPI_I2S_ReceiveData(SPI2); // 清除RXNE标志, 清空接收缓冲区数据SPI_I2S_SendData(SPI2, 0xFF); // 发送任意值, 目的只是产生CLKwhile (size > 1) { // Step 2> /* 等待TXE==1,然后写入第2字节, 要发送的数据 */while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) != SET) {/*wati*/;}SPI_I2S_SendData(SPI2, 0xFF);/* Step 3> 等待RXNE==1, 读出SPI_DR寄存器, 得到第1字节数据, 读的同时会清除RXNE标志 */while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) != SET) {/* 等待RXNE标志为1, 接收数据 */;}*pRxData = SPI_I2S_ReceiveData(SPI2);*pRxData++;size--;}/* Step 4> 等待RXNE==1, 读出SPI_DR寄存器, 得到最后1字节数据 */while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) != SET) {/* 等待RXNE标志为1, 接收数据 */;}*pRxData = SPI_I2S_ReceiveData(SPI2);/ *Step 5> 等待发送完成*/ while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) != SET) {/* 等待最后1字节发送完成,方便片选信号拉高 */;}
}
发送程序:
/*** @brief 发送多字节数据, 轮询方式* @param pData, 发送数据缓冲区* @param size 发送size字节数据*/
static void SPI2_Transmit(uint8_t *pData, uint16_t Size)
{ while (Size > 0) {SPI_I2S_SendData(SPI2, *pData);while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) != SET) {/* 等待TXE标志为1,发送数据 */;} pData++;Size--; }
}
4> SPI的4种模式
4种模式表格:
4种模式-时序图:
5> W25Q128存储结构
Block(块):64KByte;
Sector(扇区): 4KByte;
Page(页):256Byte;
128Mbit = 16MByte = 256个Block = 4096个Sector;
块 > 扇区 > 页
1个块 = 16个扇区;
1个扇区 = 16个页;
6> W25Q128常用命令
Flash存储器:写之前要先擦除;
写入时只能写0, 不能写1;
写1是靠擦除命令实现的。
6.1> 读状态寄存器
主机读写过程:
Step 1> 主机发送0x05命令, 从机无数据;
Step 2> 主机发送任意值,目的是产生CLK时钟,从机才能回数据;
BUSY位:
检测忙程序
/*** @brief 检测Flash忙不忙*/
void NorFLASH_ReadBusy(void)
{uint8_t cmd = 0x05;uint8_t reg = 0x00;GPIO_ResetBits(GPIOB, GPIO_Pin_12); // CS拉低SPI2_Transmit(&cmd, 1);SPI2_Receive(®, 1);while ((reg & 0x01) == 0x01) {SPI2_Receive(®, 1); // 等待busy}GPIO_SetBits(GPIOB, GPIO_Pin_12); // CS拉高
}
6.2> 写使能
写使能-程序
/*** @brief 写使能*/
void NorFLASH_WriteEnable(void)
{uint8_t cmd = 0x06;NorFLASH_ReadBusy(); // 忙检测GPIO_ResetBits(GPIOB, GPIO_Pin_12); // CS拉低SPI2_Transmit(&cmd, 1);GPIO_SetBits(GPIOB, GPIO_Pin_12); // CS拉高
}
6.3> 擦除1个扇区
硬件设计,最少只能擦除1个扇区4KByte;
24位地址:3字节;
擦除0#扇区, Adress 为【0x00 00 00】;
擦除4080#扇区,Adress为【0xFF 00 00】;
擦除1个扇区-程序
/*** @brief 擦除1个扇区数据, 4KByte* @param num 扇区序号*/
void NorFLASH_EraseSector(uint32_t num)
{uint8_t cmd[4];uint32_t addr;addr = num * 4096;// 构建数据cmd[0] = 0x20;cmd[1] = addr >> 16;cmd[2] = addr >> 8;cmd[3] = addr >> 0;NorFLASH_WriteEnable(); // 写使能NorFLASH_ReadBusy(); // 忙检测GPIO_ResetBits(GPIOB, GPIO_Pin_12); // CS拉低SPI2_Transmit(cmd, 4);GPIO_SetBits(GPIOB, GPIO_Pin_12); // CS拉高
}
6.4> 写入1页Page数据
1页Page = 256Byte;
循环写入,超过256字节,会覆盖开始的字节;
写1页数据-程序
/*** @brief 写1页数据,256Byte* @param PData 发送数据缓冲区* @param num 页序号*/
void NorFLASH_WritePage(uint8_t *pData, uint32_t num)
{uint8_t cmd[4];uint32_t addr;addr = num * 256; // 1页256个字节// 构建数据cmd[0] = 0x02;cmd[1] = addr >> 16;cmd[2] = addr >> 8;cmd[3] = addr >> 0;NorFLASH_WriteEnable(); // 写使能NorFLASH_ReadBusy(); // 忙检测GPIO_ResetBits(GPIOB, GPIO_Pin_12); // CS拉低SPI2_Transmit(cmd, 4); // 写命令SPI2_Transmit(pData, 256); // 写数据GPIO_SetBits(GPIOB, GPIO_Pin_12); // CS拉高
}
6.5> 读数据
存储器地址范围内,任意地址都可以读数据;
/*** @brief 读Flash数据* @param PRxData 接收数据缓冲区* @param addr Flash起始地址* @param size 读size字节数据*/
void NorFLASH_Read(uint8_t *pRxData, uint32_t addr, uint32_t size)
{uint8_t cmd[4];// 构建数据cmd[0] = 0x03;cmd[1] = addr >> 16;cmd[2] = addr >> 8;cmd[3] = addr >> 0;NorFLASH_ReadBusy(); // 忙检测GPIO_ResetBits(GPIOB, GPIO_Pin_12); // CS拉低SPI2_Transmit(cmd, 4); // 写命令SPI2_Receive(pRxData, size);GPIO_SetBits(GPIOB, GPIO_Pin_12); // CS拉高
}
7> 测试程序
int main(void)
{ uint32_t i = 0;USART1_Init();NorFLASH_Init();GPIO_SetBits(GPIOB, GPIO_Pin_12);delay_ms();// 擦NorFLASH_EraseSector(0);for (i = 0; i < 4096; i++) {Wbuf[i] = 0x11;}// 写NorFLASH_WritePage(Wbuf, 0); // 写1个扇区,16页// 读NorFLASH_Read(Rbuf, 0x00, 256);// 串口打印for (i = 0; i < 256; i++) {UART_Putchar(Rbuf[i]);}while ( 1 ) {/* Nothing */;}}
7.1> 逻辑分析仪 抓波形
理解使用1个新外设时,看手册描述,例程,实验调试;
这把 逻辑分析仪 立了头功;