Serial peripheral interface
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-PB12
GPIO_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-PB13
GPIO_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-PB14
GPIO_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-PB15
GPIO_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); // 发送任意值, 目的只是产生CLK
while (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个新外设时,看手册描述,例程,实验调试;
这把 逻辑分析仪 立了头功;