基于STM32实现W25Q16读写操作(spi)
文章目录
- 前言
- 一、W25Q16
- 1.介绍
- 2.SPI
- 2.1. 简介
- 2.2. 特性
- 2.3. 功能说明
- 2.4. 工作模式
- 2.5. 引脚说明
- 二、代码开发
- 1.SPI初始化
- 2.读取厂商ID
- 2.1.读写字节
- 2.2.读取ID
- 3.其他的一些操作
- 4.完整代码
- 三、效果演示
前言
在之前我们学习了flash闪存,这个更多的是内部数据存储,容量也是会比较小。这次我们来学习一下更多的存储单元w25q16,顺便了解spi———串行外围设备接口。
一、W25Q16
1.介绍
在我们的核心板子上基本都会有这么一块芯片,只是有的容量会计较大,大家可以查看板子的原理图,如图所示:
有的板子可能是w25q32或者其他,后面的数字代表的是容量,用法都是一样的,从图中或者产品手册我们可以看到芯片是spi协议通信的。
2.SPI
2.1. 简介
串行外设接口 (SPI) 可与外部器件进行半双工/全双工的同步串行通信。该接口可配置为主模式,在这种情况下,它可为外部从器件提供通信时钟 (SCK)。该接口还能够在多主模式配置 下工作。
它可用于多种用途,包括基于双线的单工同步传输,其中一条可作为双向数据线,或使用CRC 校验实现可靠通信。
2.2. 特性
● 基于三条线的全双工同步传输
● 基于双线的单工同步传输,其中一条可作为双向数据线
● 8 位或 16 位传输帧格式选择
● 主模式或从模式操作
● 多主模式功能
● 8 个主模式波特率预分频器(最大值为 fPCLK/2)
● 从模式频率(最大值为 fPCLK/2)
● 对于主模式和从模式都可实现更快的通信
● 对于主模式和从模式都可通过硬件或软件进行 NSS 管理:动态切换主/从操作
● 可编程的时钟极性和相位
● 可编程的数据顺序,最先移位 MSB 或 LSB
● 可触发中断的专用发送和接收标志
● SPI 总线忙状态标志
● SPI TI 模式
● 用于确保可靠通信的硬件 CRC 功能:
— 在发送模式下可将 CRC 值作为最后一个字节发送
— 根据收到的最后一个字节自动进行 CRC 错误校验
● 可触发中断的主模式故障、上溢和 CRC 错误标志
● 具有 DMA 功能的 1 字节发送和接收缓冲器:发送和接收请求
2.3. 功能说明
通常,SPI 通过 4 个引脚与外部器件连接:
● MISO:主输入/从输出数据。此引脚可用于在从模式下发送数据和在主模式下接收数据。
● MOSI:主输出/从输入数据。此引脚可用于在主模式下发送数据和在从模式下接收数据。
● SCK:用于 SPI 主器件的串行时钟输出以及 SPI 从器件的串行时钟输入。
● NSS:从器件选择。这是用于选择从器件的可选引脚。
2.4. 工作模式
SPI总线有四种工作方式,其中使用的最为广泛的是模式0和模式3方式。
CPOL(Clock Polarity):时钟极性选择,为0时SPI总线空闲时,时钟线为低电平 ;为1时SPI总线空闲时,时钟线为高电平。
CPHA(Clock Phase):时钟相位选择,为0时在SCLK第一个跳变沿,主机对MISO引脚电平采样;为1时在SCLK第二个跳变沿,主机对MISO引脚电平采样。
2.5. 引脚说明
二、代码开发
1.SPI初始化
在原理图中我们可以看到板子上直接是使用了PB3、4、5、14引脚,接下来我们进行初始化。
void w25qxx_init(void)
{//PB硬件时钟使能 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//配置PB3 PB5 PB14为输出模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_5|GPIO_Pin_14; //第3 4 5根引脚GPIO_InitStructure.GPIO_Mode= GPIO_Mode_OUT; //输出模式GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,增加输出电流能力。GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//高速响应GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //没有使能上下拉电阻GPIO_Init(GPIOB,&GPIO_InitStructure); //看时序图,工作在模式3,时钟线引脚PB3,初始电平为高电平PBout(3)=1;//看时序图,片选引脚PB14,初始电平为高电平 PBout(14)=1; //PB4配置输入模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; //第4根引脚GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IN; //输入模式GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,增加输出电流能力。GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//高速响应GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //没有使能上下拉电阻GPIO_Init(GPIOB,&GPIO_InitStructure); SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //全双工SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主机模式SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8位数据位SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //SPI FLASH可以设置为高电平SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //MISO在第二边沿采样数据SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //片选引脚由代码控制SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //clk=84MHZ/8=10.5MHzSPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //以最高有效位发送SPI_Init(SPI1, &SPI_InitStructure);SPI_Cmd(SPI1, ENABLE);}
2.读取厂商ID
我们需要根据产品手册来进行这以操作,我们可以看到这样一张图(如下图),这可能看起来有点复杂,不知道从哪里下手,我们需要一点一点慢慢来。
我们将上下两张图拼接起来,其实是一张图,如下图:
这里可以把它分为7各部分。
2.1.读写字节
这里我们使用的是模式3,从图中可以看出需要从高位先写入,每次8位,读取也是一样的。
uint8_t spi1_send_byte(uint8_t byte)
{int32_t i=0;uint8_t d=0;for(i=7; i>=0; i--){//对byte每个bit进行判断if(byte & (1<<i)){//MOSI引脚输出高电平PBout(5)=1;}else{//MOSI引脚输出低电平PBout(5)=0; }//时钟线输出低电平PBout(3)=0;//延时一会,MOSI引脚已经发送到对方delay_us(1);//时钟线输出高电平PBout(3)=1;//延时一会delay_us(1); //读取MISO引脚电平if(PBin(4))d|=1<<i;}return d;
}
2.2.读取ID
按照时序图的说明我们编写出一下代码
void w25qxx_read_id(uint8_t *m_id,uint8_t *d_id)
{//片选引脚输出低电平PBout(14)=0;//发送90h命令spi1_send_byte(0x90);//发送24bit地址,该数值全为0spi1_send_byte(0x00);spi1_send_byte(0x00);spi1_send_byte(0x00); //传递任意参数,读取厂商id*m_id=spi1_send_byte(0xFF);//传递任意参数,读取设备id*d_id=spi1_send_byte(0xFF); //片选引脚输出高电平PBout(14)=1;
}
3.其他的一些操作
在产品手册里有很多不同指令操作,这里不一一介绍,但是都和上面读取ID的操做差不多,如果有兴趣可以根据产品手册编写代码,下面会给出完整的代码,包括一些常见的操作。
4.完整代码
#include "stm32f4xx.h"
#include "sys.h"
#include <stdio.h>static GPIO_InitTypeDef GPIO_InitStructure;
static NVIC_InitTypeDef NVIC_InitStructure;
static USART_InitTypeDef USART_InitStructure;
static SPI_InitTypeDef SPI_InitStructure;#pragma import(__use_no_semihosting_swi)
struct __FILE { int handle; /* Add whatever you need here */ };
FILE __stdout;
FILE __stdin;int fputc(int ch, FILE *f) {USART_SendData(USART1,ch);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);USART_ClearFlag(USART1,USART_FLAG_TXE);return ch;
}
void _sys_exit(int return_code) {}void delay_us(uint32_t n)
{SysTick->CTRL = 0; // Disable SysTick,关闭系统定时器SysTick->LOAD = (168*n)-1; // 配置计数值(168*n)-1 ~ 0SysTick->VAL = 0; // Clear current value as well as count flagSysTick->CTRL = 5; // Enable SysTick timer with processor clockwhile ((SysTick->CTRL & 0x10000)==0);// Wait until count flag is setSysTick->CTRL = 0; // Disable SysTick
}void delay_ms(uint32_t n)
{while(n--){SysTick->CTRL = 0; // Disable SysTick,关闭系统定时器SysTick->LOAD = (168000)-1; // 配置计数值(168000)-1 ~ 0SysTick->VAL = 0; // Clear current value as well as count flagSysTick->CTRL = 5; // Enable SysTick timer with processor clockwhile ((SysTick->CTRL & 0x10000)==0);// Wait until count flag is set}SysTick->CTRL = 0; // Disable SysTick }void usart1_init(uint32_t baud)
{//打开PA硬件时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//打开串口1硬件时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//配置PA9和PA10为复用功能模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10; //第9 10根引脚GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF; //多功能模式GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,增加输出电流能力。GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//高速响应GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //没有使能上下拉电阻GPIO_Init(GPIOA,&GPIO_InitStructure);//将PA9和PA10引脚连接到串口1的硬件GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //配置串口1相关参数:波特率、无校验位、8位数据位、1个停止位......USART_InitStructure.USART_BaudRate = baud; //波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8位数据位USART_InitStructure.USART_StopBits = USART_StopBits_1; //1个停止位USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //允许收发数据USART_Init(USART1, &USART_InitStructure);//配置串口1的中断触发方法:接收一个字节触发中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//配置串口1的中断优先级NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);//使能串口1工作USART_Cmd(USART1, ENABLE);
}void w25qxx_init(void)
{//PB硬件时钟使能 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//配置PB3 PB5 PB14为输出模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_5|GPIO_Pin_14; //第3 4 5根引脚GPIO_InitStructure.GPIO_Mode= GPIO_Mode_OUT; //输出模式GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,增加输出电流能力。GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//高速响应GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //没有使能上下拉电阻GPIO_Init(GPIOB,&GPIO_InitStructure); //看时序图,工作在模式3,时钟线引脚PB3,初始电平为高电平PBout(3)=1;//看时序图,片选引脚PB14,初始电平为高电平 PBout(14)=1; //PB4配置输入模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; //第4根引脚GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IN; //输入模式GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,增加输出电流能力。GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//高速响应GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //没有使能上下拉电阻GPIO_Init(GPIOB,&GPIO_InitStructure); SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //全双工SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主机模式SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8位数据位SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //SPI FLASH可以设置为高电平SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //MISO在第二边沿采样数据SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //片选引脚由代码控制SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //clk=84MHZ/8=10.5MHzSPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //以最高有效位发送SPI_Init(SPI1, &SPI_InitStructure);SPI_Cmd(SPI1, ENABLE);}uint8_t spi1_send_byte(uint8_t byte)
{int32_t i=0;uint8_t d=0;for(i=7; i>=0; i--){//对byte每个bit进行判断if(byte & (1<<i)){//MOSI引脚输出高电平PBout(5)=1;}else{//MOSI引脚输出低电平PBout(5)=0; }//时钟线输出低电平PBout(3)=0;//延时一会,MOSI引脚已经发送到对方delay_us(1);//时钟线输出高电平PBout(3)=1;//延时一会delay_us(1); //读取MISO引脚电平if(PBin(4))d|=1<<i;}return d;
}//读取w25qxxState Regist(状态寄存器)值
uint8_t w25qxx_read_SR()
{uint8_t RS1 = 0;//片选引脚输出低电平PBout(14)=0;//发送90h命令spi1_send_byte(0x05);//接收指定字节数据RS1=spi1_send_byte(0xFF);//片选引脚输出高电平PBout(14)=1;return RS1;
}//w25qxx读取数据
void w25qxx_read_data(uint8_t *data,uint32_t dataaddr,uint32_t size)
{int8_t i=0;//片选引脚输出低电平PBout(14)=0;//发送90h命令spi1_send_byte(0x03);//发送24bit地址,该数值全为0//例如发送地址:0x123456spi1_send_byte((uint8_t)(dataaddr>>16));spi1_send_byte((uint8_t)(dataaddr>>8));spi1_send_byte((uint8_t)dataaddr); //接收指定字节数据for(i=0;i<size;i++)//传递任意参数,读取数据data[i]=spi1_send_byte(0xFF);//片选引脚输出高电平PBout(14)=1;
}//w25qxx写使能
void w25qxx_write_enable()
{//片选引脚输出低电平PBout(14)=0;//发送90h命令spi1_send_byte(0x06);//片选引脚输出高电平PBout(14)=1;}//w25qxx写使不能
void w25qxx_write_disable()
{//片选引脚输出低电平PBout(14)=0;//发送90h命令spi1_send_byte(0x04);//片选引脚输出高电平PBout(14)=1;}//扇区擦除
void w25qxx_erase_sector(uint32_t sectoraddr)
{w25qxx_write_enable();PBout(14) = 0;//等待BUSY位清空//while(w25qxx_read_SR()&0x01 == 0x01);//发送扇区擦除命令spi1_send_byte(0x20);//发送24bit地址,该数值全为0spi1_send_byte((uint8_t)(sectoraddr>>16));spi1_send_byte((uint8_t)(sectoraddr>>8));spi1_send_byte((uint8_t)sectoraddr);PBout(14) = 1; //等待BUSY位清空while(w25qxx_read_SR()&0x01 == 0x01);}void w25qxx_page_write(uint8_t *data,uint32_t dataaddr,uint16_t size)
{uint16_t i=0;//写使能w25qxx_write_enable();PBout(14) = 0;//发送页存储指令spi1_send_byte(0x02);//发送存储地址spi1_send_byte((uint8_t)(dataaddr>>16));spi1_send_byte((uint8_t)(dataaddr>>8));spi1_send_byte(dataaddr);//循环发送数据for(i=0;i<size;i++)spi1_send_byte(data[i]);PBout(14) = 1;//等待BUSY位清空while(w25qxx_read_SR()&0x01 == 0x01); //添加写保护w25qxx_write_disable();}void w25qxx_read_id(uint8_t *m_id,uint8_t *d_id)
{//片选引脚输出低电平PBout(14)=0;//发送90h命令spi1_send_byte(0x90);//发送24bit地址,该数值全为0spi1_send_byte(0x00);spi1_send_byte(0x00);spi1_send_byte(0x00); //传递任意参数,读取厂商id*m_id=spi1_send_byte(0xFF);//传递任意参数,读取设备id*d_id=spi1_send_byte(0xFF); //片选引脚输出高电平PBout(14)=1;
}int main(void)
{int32_t i=0;uint8_t m_id,d_id;uint8_t data[256] = {0};//使能(打开)端口F的硬件时钟,就是对端口F供电RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//串口1波特率:115200bpsusart1_init(115200); //初始化GPIO引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //第9根引脚GPIO_InitStructure.GPIO_Mode= GPIO_Mode_OUT; //输出模式GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,增加输出电流能力。GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//高速响应GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //没有使能上下拉电阻GPIO_Init(GPIOF,&GPIO_InitStructure);PFout(9)=1;//初始化SPI1连接的w25qxxw25qxx_init();w25qxx_read_id(&m_id,&d_id);printf("m_id=%x,d_id=%x\r\n",m_id,d_id);printf("read data at addr 0:\r\n");w25qxx_read_data(data,0,64);for(i=0;i<64;i++)printf("%02X ",data[i]);//扇区擦除printf("\r\nerase sector 0:\r\n"); w25qxx_erase_sector(0);printf("read data at addr 0:\r\n"); w25qxx_read_data(data,0,64);for(i=0;i<64;i++)printf("%02X ",data[i]);//页写入printf("write data at addr 0:\r\n"); for(i=0;i<256;i++)data[i]=0x88;w25qxx_page_write(data,0,256);printf("read data at addr 0:\r\n"); w25qxx_read_data(data,0,256);for(i=0;i<256;i++)printf("%02X ",data[i]);printf("\r\n");while(1){}
}void USART1_IRQHandler(void)
{uint8_t d;//检测标志位if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET){//接收数据d=USART_ReceiveData(USART1);//清空标志位USART_ClearITPendingBit(USART1,USART_IT_RXNE);}}
三、效果演示
这里还是使用串口来调试看我们做出来的效果,如下图所示:
基于STM32实现W25Q16读写操作(spi)相关推荐
- STM32CubeMX | 基于STM32使用HAL库硬件SPI驱动WK2124一拖四SPI转四路串口芯片
STM32CubeMX | 基于STM32使用HAL库硬件SPI驱动WK2124一拖四SPI转四路串口芯片 STM32基础工程生成 首先使用STM32CUBEMX生成STM32的基础工程,配置时钟到7 ...
- 基于STM32的Flash读写详解
基于STM32的Flash读取 前言 介绍 STM32 FLASH 闪存的编程和擦除 Flash读写的标准库函数 软件设计 FLASH的读取 直接读取某一地址的内容 读取选定位置的选定大小的内容 FL ...
- 基于Python的Excel读写操作--内容超详细,值得排排坐
今日份超详细的解读,Excel文件基础操作,让你以学就会. 写这个的原因是,一位群友问了很久,然后网上的教程写的又不是很详细,看的有些犯迷糊,所以我今日份写一份超详细的基本操作,让你打下良好的基础. ...
- 基于STM32的flash读写和DAC音频播放
一.STM32的flash读写 1.利用STM32CUBEMX创建工程 之前有过很多次创建工程的例子,这里大致过程如下: 2.keil中添加代码及修改配置 在flash.c中添加如下代码, 在main ...
- 基于STM32与NOR FLASH的SPI通信
SPI的通信很容易实现,相比之下,驱动FLASH反而耗费了我学习SPI整个过程的大部分时间.下面是我学习过程的一些记录. 硬件平台:秉火ISO_V2开发板 实现功能:STM32使用SPI协议读写板 ...
- STM32 FLASH 简单读写操作【有代码】
[举报再看养成习惯,噢 不对,点赞再看 养成习惯.感谢支持] STM32的Flash操作分为读写:读写. 一.读操作 读取比较简单,直接指针解引用地址就可以读取.注意一下读取的单位即可 /****** ...
- (超详细)STM32芯片Flash读写操作讲解和代码(寄存器版本)
关于Flash,官方的解释为:Flash为32位宽的存储单元,可用于存储代码和数据常量.Flash模块位于微控制器内存映射中的特定基址--.而对于我们来说,只要知道Flash闪存区是一个掉电后也不会清 ...
- STM32之flash读写操作篇
1.flash概述 flash跟ROM相似,都有断电不会丢失数据的这一特性,可用于存储一些重要的数据.数据是直接存储到内存地址上,所以要写数据时要先了解清楚自己所以芯片的flash地址是从0X0800 ...
- 基于NRF52810的FLASH读写操作
非易失性存储器控制器NVMC原理: 芯片选用的是NRF52810--QFAA,其片内的FLASH的分布模式如下: FLASH的地址范围为0x0000 0000~0x0003 0000,可擦除的地址范围 ...
最新文章
- linux大爱版本Vinux 盲人也能用的OS
- 【LOJ】#2084. 「NOI2016」网格
- 成都python数据分析师职业技能_数据分析师需要什么技能,数据分析行业都有什么职业?...
- Spring Bean初始化过程
- weblogic各个版本对JDK和Spring的支持度
- C++ 关联容器set | map | multiset | multimap
- 【Servlet笔记】Servlet入门
- Spring 通过XML配置装配Bean
- 2100 没有反弹shell_反弹shell | ncbash
- 黑客帝国_屏幕保护程序
- 市场需求文档MRD书写范例
- html图片逆时针转换,css3怎样做出逆时针旋转倒计时
- Activity切换闪屏问题
- sketch中制作蒙版及通道蒙版
- byfen网java_用java编写程序根据考试成绩的等级打印出百分制分数段
- 对冲基金:神秘Dalio和桥水崛起的秘密
- 2018年中国互联网企业百强榜单
- 【IPAM】Netbox docker模式版本升级
- vxWorks6.6下基于VxBus架构的Can控制器(sja1000t)驱动编写
- 如何修改服务器ttl值,TTL值怎么改(TTL值最大值和默认值是多少)