目录
9. 软件SPI读写W25Qx
0. 博主调侃:
本实验使用软件模拟标准的SPI读写W25Q16JV,这个W25Qx通常用来存储程序,可在W25Qx内部直接运行程序,无需移动到单片机的内部RAM中,当然用来存储数据也行的。
这里W25Qx的芯片类型有点多,大体操作都相同,每种类型都有一丢丢区别,建议用哪一块就去找对应的芯片(本人就被坑了一次,啊哈哈哈哈~)。
1. 实验内容及步骤:
1. 配置GPIO,MOSI(推挽输出)、MISO(浮空输入)、CS(输出)、SCK(输出);
2. 软件SPI读写Byte(模式0或模式3);
3. 读取W25Q16的制造商ID和设备ID;
4. 通过标准SPI对W25Q16擦除-写入-读取;
2. 硬件说明
CS -> PB12
CLK -> PB13
MISO -> PB14
MOSI -> PB15
3. 步骤详细讲解
3.1 配置GPIO
MOSI(推挽输出)、MISO(浮空输入)、CS(输出)、SCK(输出),详细的代码见源码哈。
3.2 软件SPI读写Byte(模式0或模式3)
见基于STM32F1的软件SPI讲解。
左图为软件SPI模式0的读写Byte,右图为软件SPI模式3的读写Byte
3.3 读取W25Q16的制造商ID和设备ID
详细见W25Q16芯片讲解,源码如下所示。
3.4 通过标准SPI对W25Q16擦除-写入-读取
写使能:(在擦除和写入的时候都要进行写使能)
写使能指令宏定义如下图所示
写使能操作如下图所示
擦除:(擦除操作后会清除写使能,并且busy位置1)
擦除指令宏定义如下图所示
擦除操作如下图所示
等待忙(BUSY位):
程序如下所示,读取状态寄存器1的值,并且等待BUSY为清零。
写入数据:
注意:W25Qx的写操作只能一次性连续写256个位置,超过256个位置后,就会从1开始重新覆盖,这种写256 Byte操作成为页操作。
这种页操作不是随便的写256Byte,如从0-255称为一个页,就是你从100开始写也只能写到255,下一个就从0开始;256-511称为下一个页,一次类推。
写操作程序如下图所示。由于有点长,这里就只截取一点。
这个程序会判断是否到结尾,并且跳转到另一页。
普通读操作:
快速读操作:
4. 程序设计(寄存器)
在W25Q16芯片的第0x20000位置开始存储,存储15000个数据,并且读取出来进行对比。
/*
通过标准SPI对W25Q16擦除-写入-读取
输入:
addr 读取的地址
len 读数据长度
writebuf 发送缓冲器
readbuf 接收缓冲器
*/
void W25Q_test_V2(u32 addr,u32 len,u8* writebuf,u8* readbuf)
{
u32 i=0;
u8 j=0;
u16 ID=0;
//ID输出
ID = W25Qx_Read_ID();
printf("制造商ID:0X%0X 设备ID:0X%0X\r\n",(ID>>8)&0xFF,ID&0xFF);
printf("寄存器1:0X%0X 寄存器2:0X%0X\r\n",W25Qx_Read_Reg1(),W25Qx_Read_Reg2());
printf("存储起始地址:0X%0X\r\n",addr);
for(i=0,j=1;i<len-1;i++,j++)
{
if(j==0)j=1;
writebuf[i]=j;
}
writebuf[i]='\0';
W25QX_CHIP_ERASE(); //擦除整个芯片(函数内部有等待busy了)
W25Qx_write_DATA(addr,len,writebuf); //写入(函数内部有等待busy了)
W25Qx_Read_DATA(addr,len,readbuf); //读取数据
//数据比较
if(strcmp((char*)writebuf,(char*)readbuf)==0)
printf("普通读取:存储正确\r\n");
else
printf("普通读取:存储出错\r\n");
W25Qx_Fast_Read_DATA(addr,len,readbuf); //快速读取
//数据比较
if(strcmp((char*)writebuf,(char*)readbuf)==0)
printf("快速读取:存储正确\r\n");
else
printf("快速读取:存储出错\r\n");
}
//主函数
int main(void)
{
SET_NVIC_GROUP(NVIC_PriorityGroup_4); //中断组4
Systick_Config();
USART1_Config(115200); //串口1配置115200
W25Qx_GPIO_Config(); //W25Qx初始化
W25Q_test_V2(0x020000,15000,W25Q_write,W25Q_read);
while (1)
{
}
}
5. 程序设计(标准库)
软件SPI的标准库,除了GPIO配置、时钟配置与寄存器不同,其它部分都与寄存器一致。
源码:(GPIO配置部分)
//CS
#define SOFT_CS_PIN_NUM 12
#define SOFT_CS_PIN GPIO_Pin_12
#define SOFT_CS_Mode GPIO_Mode_Out_PP
#define SOFT_CS_RCC_CLK RCC_APB2Periph_GPIOB
#define SOFT_CS_GPIOX GPIOB
#define SOFT_CS_OUT BPB_OUT(SOFT_CS_PIN_NUM)
#define SOFT_CS_IN BPB_IN(SOFT_CS_PIN_NUM)
//SCK
#define SOFT_SCK_PIN_NUM 13
#define SOFT_SCK_PIN GPIO_Pin_13
#define SOFT_SCK_Mode GPIO_Mode_Out_PP
#define SOFT_SCK_RCC_CLK RCC_APB2Periph_GPIOB
#define SOFT_SCK_GPIOX GPIOB
#define SOFT_SCK_OUT BPB_OUT(SOFT_SCK_PIN_NUM)
#define SOFT_SCK_IN BPB_IN(SOFT_SCK_PIN_NUM)
//MISO
#define SOFT_MISO_PIN_NUM 14
#define SOFT_MISO_PIN GPIO_Pin_14
#define SOFT_MISO_Mode GPIO_Mode_IN_FLOATING
#define SOFT_MISO_RCC_CLK RCC_APB2Periph_GPIOB
#define SOFT_MISO_GPIOX GPIOB
#define SOFT_MISO_OUT BPB_OUT(SOFT_MISO_PIN_NUM)
#define SOFT_MISO_IN BPB_IN(SOFT_MISO_PIN_NUM)
#define SOFT_MISO_MODE_OUT() GPIOX_PIN_Mode_Turn(SOFT_MISO_GPIOX,SOFT_MISO_PIN_NUM,GPIOX_MODE_OUT); //配置MISO为输出模式
#define SOFT_MISO_MODE_IN() GPIOX_PIN_Mode_Turn(SOFT_MISO_GPIOX,SOFT_MISO_PIN_NUM,GPIOX_MODE_IN); //配置MISO为输入模式
//MOSI
#define SOFT_MOSI_PIN_NUM 15
#define SOFT_MOSI_PIN GPIO_Pin_15
#define SOFT_MOSI_Mode GPIO_Mode_Out_PP
#define SOFT_MOSI_RCC_CLK RCC_APB2Periph_GPIOB
#define SOFT_MOSI_GPIOX GPIOB
#define SOFT_MOSI_OUT BPB_OUT(SOFT_MOSI_PIN_NUM)
#define SOFT_MOSI_IN BPB_IN(SOFT_MOSI_PIN_NUM)
#define SOFT_MOSI_MODE_OUT() GPIOX_PIN_Mode_Turn(SOFT_MOSI_GPIOX,SOFT_MOSI_PIN_NUM,GPIOX_MODE_OUT); //配置MOSI为输出模式
#define SOFT_MOSI_MODE_IN() GPIOX_PIN_Mode_Turn(SOFT_MOSI_GPIOX,SOFT_MOSI_PIN_NUM,GPIOX_MODE_IN); //配置MOSI为输入模式
//软件SPI GPIO配置
void soft_spi_gpio_config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//时钟
RCC_APB2PeriphClockCmd(SOFT_CS_RCC_CLK,ENABLE); //CS时钟
RCC_APB2PeriphClockCmd(SOFT_SCK_RCC_CLK,ENABLE); //SCK时钟
RCC_APB2PeriphClockCmd(SOFT_MISO_RCC_CLK,ENABLE); //MISO时钟
RCC_APB2PeriphClockCmd(SOFT_MOSI_RCC_CLK,ENABLE); //MOSI时钟
//CS(推挽输出、速度10MHz)
GPIO_InitStruct.GPIO_Mode = SOFT_CS_Mode;
GPIO_InitStruct.GPIO_Pin = SOFT_CS_PIN;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(SOFT_CS_GPIOX,&GPIO_InitStruct);
//SCK(推挽输出、速度10MHz)
GPIO_InitStruct.GPIO_Mode = SOFT_SCK_Mode;
GPIO_InitStruct.GPIO_Pin = SOFT_SCK_PIN;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(SOFT_SCK_GPIOX,&GPIO_InitStruct);
//MISO(输入 浮空模式)
GPIO_InitStruct.GPIO_Mode = SOFT_MISO_Mode;
GPIO_InitStruct.GPIO_Pin = SOFT_MISO_PIN;
GPIO_Init(SOFT_MISO_GPIOX,&GPIO_InitStruct);
//MOSI(推挽输出、速度10MHz)
GPIO_InitStruct.GPIO_Mode = SOFT_MOSI_Mode;
GPIO_InitStruct.GPIO_Pin = SOFT_MOSI_PIN;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(SOFT_MOSI_GPIOX,&GPIO_InitStruct);
}
6. 程序设计(HAL库)
软件SPI的HAL库,除了GPIO配置、时钟配置与寄存器不同,其它部分都与寄存器一致。
源码:(GPIO配置部分)
/*============================================= SPI宏定义 ================================================*/
//CS
#define SOFT_CS_CLK_ENABLE __HAL_RCC_GPIOB_CLK_ENABLE
#define SOFT_CS_PIN_NUM 12
#define SOFT_CS_PIN GPIO_PIN_12
#define SOFT_CS_Mode GPIO_MODE_OUTPUT_PP
#define SOFT_CS_RCC_CLK RCC_APB2Periph_GPIOB
#define SOFT_CS_GPIOX GPIOB
#define SOFT_CS_OUT BPB_OUT(SOFT_CS_PIN_NUM)
#define SOFT_CS_IN BPB_IN(SOFT_CS_PIN_NUM)
//SCK
#define SOFT_SCK_CLK_ENABLE __HAL_RCC_GPIOB_CLK_ENABLE
#define SOFT_SCK_PIN_NUM 13
#define SOFT_SCK_PIN GPIO_PIN_13
#define SOFT_SCK_Mode GPIO_MODE_OUTPUT_PP
#define SOFT_SCK_RCC_CLK RCC_APB2Periph_GPIOB
#define SOFT_SCK_GPIOX GPIOB
#define SOFT_SCK_OUT BPB_OUT(SOFT_SCK_PIN_NUM)
#define SOFT_SCK_IN BPB_IN(SOFT_SCK_PIN_NUM)
//MISO
#define SOFT_MISO_CLK_ENABLE __HAL_RCC_GPIOB_CLK_ENABLE
#define SOFT_MISO_PIN_NUM 14
#define SOFT_MISO_PIN GPIO_PIN_14
#define SOFT_MISO_Mode GPIO_MODE_INPUT
#define SOFT_MISO_RCC_CLK RCC_APB2Periph_GPIOB
#define SOFT_MISO_GPIOX GPIOB
#define SOFT_MISO_OUT BPB_OUT(SOFT_MISO_PIN_NUM)
#define SOFT_MISO_IN BPB_IN(SOFT_MISO_PIN_NUM)
#define SOFT_MISO_MODE_OUT() GPIOX_PIN_Mode_Turn(SOFT_MISO_GPIOX,SOFT_MISO_PIN_NUM,GPIOX_MODE_OUT); //配置MISO为输出模式
#define SOFT_MISO_MODE_IN() GPIOX_PIN_Mode_Turn(SOFT_MISO_GPIOX,SOFT_MISO_PIN_NUM,GPIOX_MODE_IN); //配置MISO为输入模式
//MOSI
#define SOFT_MOSI_CLK_ENABLE __HAL_RCC_GPIOB_CLK_ENABLE
#define SOFT_MOSI_PIN_NUM 15
#define SOFT_MOSI_PIN GPIO_PIN_15
#define SOFT_MOSI_Mode GPIO_MODE_OUTPUT_PP
#define SOFT_MOSI_RCC_CLK RCC_APB2Periph_GPIOB
#define SOFT_MOSI_GPIOX GPIOB
#define SOFT_MOSI_OUT BPB_OUT(SOFT_MOSI_PIN_NUM)
#define SOFT_MOSI_IN BPB_IN(SOFT_MOSI_PIN_NUM)
#define SOFT_MOSI_MODE_OUT() GPIOX_PIN_Mode_Turn(SOFT_MOSI_GPIOX,SOFT_MOSI_PIN_NUM,GPIOX_MODE_OUT); //配置MOSI为输出模式
#define SOFT_MOSI_MODE_IN() GPIOX_PIN_Mode_Turn(SOFT_MOSI_GPIOX,SOFT_MOSI_PIN_NUM,GPIOX_MODE_IN); //配置MOSI为输入模式
//软件SPI GPIO配置
void soft_spi_gpio_config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//时钟
SOFT_CS_CLK_ENABLE(); //CS时钟
SOFT_SCK_CLK_ENABLE(); //SCK时钟
SOFT_MISO_CLK_ENABLE(); //MISO时钟
SOFT_MOSI_CLK_ENABLE(); //MOSI时钟
//CS(推挽输出、速度10MHz)
GPIO_InitStruct.Mode = SOFT_CS_Mode;
GPIO_InitStruct.Pin = SOFT_CS_PIN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(SOFT_CS_GPIOX,&GPIO_InitStruct);
//SCK(推挽输出、速度10MHz)
GPIO_InitStruct.Mode = SOFT_SCK_Mode;
GPIO_InitStruct.Pin = SOFT_SCK_PIN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(SOFT_SCK_GPIOX,&GPIO_InitStruct);
//MISO(输入 浮空模式)
GPIO_InitStruct.Mode = SOFT_MISO_Mode;
GPIO_InitStruct.Pin = SOFT_MISO_PIN;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(SOFT_MOSI_GPIOX,&GPIO_InitStruct);
//MOSI(推挽输出、速度10MHz)
GPIO_InitStruct.Mode = SOFT_MOSI_Mode;
GPIO_InitStruct.Pin = SOFT_MOSI_PIN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(SOFT_MOSI_GPIOX,&GPIO_InitStruct);
}
7. 实验结果
可以看出,写入的起始地址为0x20000,最后通过普通读取和快速读取,分别读出写入的数据,并对比。结果都为存储正确。
HAL,标准库源码运行结果: