stm32(SPI读写W25Q18)

SPI 是什么?

SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总 线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提 供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如 AT91RM9200 。

SPI 包含 4 条总线,SPI 总线包含 4 条总线,分别为SS、SCK、MOSI、MISO。它们的作用介绍如 下 :

  • (1) MISO – Master Input Slave Output,主设备数据输入,从设备数据输出
  • (2) MOSI – Master Output Slave Input,主设备数据输出,从设备数据输入
  • (3) SCK – Serial Clock,时钟信号,由主 设备产生
  • (4) CS – Chip Select,片选信号,由主设备控制 

SPI 工作原理 

 SPI 工作模式

时钟极性(CPOL): 没有数据传输时时钟线的空闲状态电平

0:SCK在空闲状态保持低电平 1:SCK在空闲状态保持高 电平

时钟相位(CPHA): 时钟线在第几个时钟边沿采样数据

0:SCK的第一(奇数)边沿进行数据位采样,数据在第一个时 钟边沿被锁存

1:SCK的第二(偶数)边沿进行数据位采样,数据在第二个时钟边沿被锁存

模式 0 和模式 3 最常用。 

模式 0 时序图:

模式 3 时序图:

W25Q128 介绍 

什么是 W25Q128 ?

W25Q128 是华邦公司推出的一款 SPI 接口的 NOR Flash 芯片,其存储空间为 128 Mbit,相当于 16M 字节。 Flash 是常用的用于储存数据的半导体器件,它具有容量大,可重复擦写、按“扇区/块”擦除、掉 电后数据可继续保存的特性。

Flash 是有一个物理特性:只能写 0 ,不能写 1 ,写 1 靠擦除。

W25Q128 存储架构 

一般按扇区(4k)进行擦除。

可以按 章 -- 节 -- 页 -- 字 进行理解。 

 写使能 (06H)

执行页写,扇区擦除,块擦除,片擦除,写状态寄存器等指令前,需要写使能。

拉低CS片选 → 发送06H → 拉高CS片选

读状态寄存器(05H)

拉低CS片选 → 发送05H→ 返回SR1的值 → 拉高CS片选

读时序(03H)

拉低CS片选 → 发送03H→ 发送24位地址 → 读取数据(1~n) → 拉高CS片选

页写时序 (02H)

页写命令最多可以向FLASH传输256个字节的数据。

拉低CS片选 → 发送02H→ 发送24位地址 → 发送数据(1~n) → 拉高CS片选

扇区擦除时序(20H)

写入数据前,检查内存空间是否全部都是 0XFF ,不满足需擦除。 拉

低CS片选 → 发送20H→ 发送24位地址 → 拉高CS片选

W25Q128 状态寄存器 W25Q128 一共有 3 个状态寄存器,它们的作用是跟踪芯片的状态。 其中,状态寄存器 1 较为常用。

 W25Q128 常见操作流程

以下流程省略了拉低/拉高片选信号CS

 

写操作 

 

实验:使用 SPI 通讯读写 W25Q128 模块

硬件接线 :VCC -- 3.3V CS -- PA4 CLK -- PA5 DO -- PA6 DI -- PA7

cubeMX配置

w25q128_write_nocheck流程图 

代码实现 

main.c

int main(void)
{/* USER CODE BEGIN 1 */uint8_t datatemp[TEXT_SIZE];/* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_SPI1_Init();MX_USART1_UART_Init();/* USER CODE BEGIN 2 */w25q128_init();/* 写入测试数据 */sprintf((char *)datatemp, "liangxu shuai");w25q128_write(datatemp, FLASH_WriteAddress, TEXT_SIZE);printf("数据写入完成!\r\n");/* 读出测试数据 */memset(datatemp, 0, TEXT_SIZE);w25q128_read(datatemp, FLASH_ReadAddress, TEXT_SIZE);printf("读出数据:%s\r\n", datatemp);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}

W25Q128.c

#include "w25q128.h"
#include "spi.h"
#include "stdio.h"/*** @brief       初始化W25Q128* @param       无* @retval      无*/
void w25q128_init(void)
{uint16_t flash_type;spi1_read_write_byte(0xFF); /* 清除DR的作用 */W25Q128_CS(1);flash_type = w25q128_read_id();   /* 读取FLASH ID. */if (flash_type == W25Q128)printf("检测到W25Q128芯片\r\n");
}/*** @brief       等待空闲* @param       无* @retval      无*/
static void w25q128_wait_busy(void)
{while ((w25q128_rd_sr1() & 0x01) == 0x01);   /* 等待BUSY位清空 */
}/*** @brief       读取W25Q128的状态寄存器1的值* @param       无* @retval      状态寄存器值*/
uint8_t w25q128_rd_sr1(void)
{uint8_t rec_data = 0;W25Q128_CS(0);spi1_read_write_byte(FLASH_ReadStatusReg1);     /* 读状态寄存器1 */rec_data = spi1_read_write_byte(0xFF);W25Q128_CS(1);return rec_data;
}/*** @brief       W25Q128写使能*   @note      将S1寄存器的WEL置位* @param       无* @retval      无*/
void w25q128_write_enable(void)
{W25Q128_CS(0);spi1_read_write_byte(FLASH_WriteEnable);   /* 发送写使能 */W25Q128_CS(1);
}/*** @brief       W25Q128发送地址* @param       address : 要发送的地址* @retval      无*/
static void w25q128_send_address(uint32_t address)
{spi1_read_write_byte((uint8_t)((address)>>16));     /* 发送 bit23 ~ bit16 地址 */spi1_read_write_byte((uint8_t)((address)>>8));      /* 发送 bit15 ~ bit8  地址 */spi1_read_write_byte((uint8_t)address);             /* 发送 bit7  ~ bit0  地址 */
}/*** @brief       擦除整个芯片*   @note      等待时间超长...* @param       无* @retval      无*/
void w25q128_erase_chip(void)
{w25q128_write_enable();    /* 写使能 */w25q128_wait_busy();       /* 等待空闲 */W25Q128_CS(0);spi1_read_write_byte(FLASH_ChipErase);  /* 发送读寄存器命令 */ W25Q128_CS(1);w25q128_wait_busy();       /* 等待芯片擦除结束 */
}/*** @brief       擦除一个扇区*   @note      注意,这里是扇区地址,不是字节地址!!*              擦除一个扇区的最少时间:150ms** @param       saddr : 扇区地址 根据实际容量设置* @retval      无*/
void w25q128_erase_sector(uint32_t saddr)
{//printf("fe:%x\r\n", saddr);   /* 监视falsh擦除情况,测试用 */saddr *= 4096;w25q128_write_enable();        /* 写使能 */w25q128_wait_busy();           /* 等待空闲 */W25Q128_CS(0);spi1_read_write_byte(FLASH_SectorErase);    /* 发送写页命令 */w25q128_send_address(saddr);   /* 发送地址 */W25Q128_CS(1);w25q128_wait_busy();           /* 等待扇区擦除完成 */
}/*** @brief       读取芯片ID* @param       无* @retval      FLASH芯片ID*   @note      芯片ID列表见: w25q128.h, 芯片列表部分*/
uint16_t w25q128_read_id(void)
{uint16_t deviceid;W25Q128_CS(0);spi1_read_write_byte(FLASH_ManufactDeviceID);   /* 发送读 ID 命令 */spi1_read_write_byte(0);    /* 写入一个字节 */spi1_read_write_byte(0);spi1_read_write_byte(0);deviceid = spi1_read_write_byte(0xFF) << 8;     /* 读取高8位字节 */deviceid |= spi1_read_write_byte(0xFF);         /* 读取低8位字节 */W25Q128_CS(1);return deviceid;
}/*** @brief       读取SPI FLASH*   @note      在指定地址开始读取指定长度的数据* @param       pbuf    : 数据存储区* @param       addr    : 开始读取的地址(最大32bit)* @param       datalen : 要读取的字节数(最大65535)* @retval      无*/
void w25q128_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint16_t i;W25Q128_CS(0);spi1_read_write_byte(FLASH_ReadData);       /* 发送读取命令 */w25q128_send_address(addr);                /* 发送地址 */for(i=0;i<datalen;i++){pbuf[i] = spi1_read_write_byte(0XFF);   /* 循环读取 */}W25Q128_CS(1);
}/*** @brief       SPI在一页(0~65535)内写入少于256个字节的数据*   @note      在指定地址开始写入最大256字节的数据* @param       pbuf    : 数据存储区* @param       addr    : 开始写入的地址(最大32bit)* @param       datalen : 要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!* @retval      无*/
static void w25q128_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint16_t i;w25q128_write_enable();    /* 写使能 */W25Q128_CS(0);spi1_read_write_byte(FLASH_PageProgram);    /* 发送写页命令 */w25q128_send_address(addr);                /* 发送地址 */for(i=0;i<datalen;i++){spi1_read_write_byte(pbuf[i]);          /* 循环写入 */}W25Q128_CS(1);w25q128_wait_busy();       /* 等待写入结束 */
}/*** @brief       无检验写SPI FLASH*   @note      必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!*              具有自动换页功能*              在指定地址开始写入指定长度的数据,但是要确保地址不越界!** @param       pbuf    : 数据存储区* @param       addr    : 开始写入的地址(最大32bit)* @param       datalen : 要写入的字节数(最大65535)* @retval      无*/
static void w25q128_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint16_t pageremain;pageremain = 256 - addr % 256;  /* 单页剩余的字节数 */if (datalen <= pageremain)      /* 不大于256个字节 */{pageremain = datalen;}while (1){/* 当写入字节比页内剩余地址还少的时候, 一次性写完* 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理*/w25q128_write_page(pbuf, addr, pageremain);if (datalen == pageremain)   /* 写入结束了 */{break;}else     /* datalen > pageremain */{pbuf += pageremain;         /* pbuf指针地址偏移,前面已经写了pageremain字节 */addr += pageremain;         /* 写地址偏移,前面已经写了pageremain字节 */datalen -= pageremain;      /* 写入总长度减去已经写入了的字节数 */if (datalen > 256)          /* 剩余数据还大于一页,可以一次写一页 */{pageremain = 256;       /* 一次可以写入256个字节 */}else     /* 剩余数据小于一页,可以一次写完 */{pageremain = datalen;   /* 不够256个字节了 */}}}
}/*** @brief       写SPI FLASH*   @note      在指定地址开始写入指定长度的数据 , 该函数带擦除操作!*              SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block*              擦除的最小单位为Sector.** @param       pbuf    : 数据存储区* @param       addr    : 开始写入的地址(最大32bit)* @param       datalen : 要写入的字节数(最大65535)* @retval      无*/
uint8_t g_w25q128_buf[4096];   /* 扇区缓存 */void w25q128_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{uint32_t secpos;uint16_t secoff;uint16_t secremain;uint16_t i;uint8_t *w25q128_buf;w25q128_buf = g_w25q128_buf;secpos = addr / 4096;       /* 扇区地址 */secoff = addr % 4096;       /* 在扇区内的偏移 */secremain = 4096 - secoff;  /* 扇区剩余空间大小 *///printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */if (datalen <= secremain){secremain = datalen;    /* 不大于4096个字节 */}while (1){w25q128_read(w25q128_buf, secpos * 4096, 4096);   /* 读出整个扇区的内容 */for (i = 0; i < secremain; i++)   /* 校验数据 */{if (w25q128_buf[secoff + i] != 0XFF){break;      /* 需要擦除, 直接退出for循环 */}}if (i < secremain)   /* 需要擦除 */{w25q128_erase_sector(secpos);  /* 擦除这个扇区 */for (i = 0; i < secremain; i++)   /* 复制 */{w25q128_buf[i + secoff] = pbuf[i];}w25q128_write_nocheck(w25q128_buf, secpos * 4096, 4096);  /* 写入整个扇区 */}else        /* 写已经擦除了的,直接写入扇区剩余区间. */{w25q128_write_nocheck(pbuf, addr, secremain);  /* 直接写扇区 */}if (datalen == secremain){break;  /* 写入结束了 */}else        /* 写入未结束 */{secpos++;               /* 扇区地址增1 */secoff = 0;             /* 偏移位置为0 */pbuf += secremain;      /* 指针偏移 */addr += secremain;      /* 写地址偏移 */datalen -= secremain;   /* 字节数递减 */if (datalen > 4096){secremain = 4096;   /* 下一个扇区还是写不完 */}else{secremain = datalen;/* 下一个扇区可以写完了 */}}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/20849.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

移动互联网应用程序(app)个人信息安全测试能力验证-流程介绍

ILONGYU 产品简介 为规范检验检测市场&#xff0c;提升检验检测机构技术能力&#xff0c;根据《检验检测机构资质认定管理办法》《实验室能力验证实施办法》等有关规定&#xff0c;市场监管总局决定在社会重点关注的部分检验检测领域&#xff0c;组织开展2020年国家级检验检测…

Bootstrap编写一个兼容主流浏览器的受众巨幕式风格页面

Bootstrap编写一个兼容主流浏览器的受众巨幕式风格页面 虽然说IE6除了部分要求苛刻的需求以外已经被可以不考虑了&#xff0c;但是WIN7自带的浏览器IE8还是需要支持的。 本文这个方法主要的优点&#xff0c;个人觉得就是准备少&#xff0c;不需要上网寻找大量的图片做素材&…

MySQL的MVCC

目录 1.MVCC基本概念 1.当前读&#xff1a; 2.快照读&#xff1a; 3.MVCC&#xff1a;Multi-Version Concurrency Control&#xff0c;多版本并发控制&#xff1a; 2.MVCC实现原理-三个隐藏字段 3.MVCC实现原理-undo log日志 1.undo log&#xff1a;回滚日志 2.undo log…

samba服务

samba服务 1. samba诞生背景2. samba简介3. samba配置文件详解3.1 配置文件中参数的作用3.1.1 security参数的四种验证方式3.1.2 passdb backend定义用户后台的类型 4. samba服务部署与配置windos共享linux挂载共享 1. samba诞生背景 1987 年&#xff0c;微软公司和英特尔公司…

vue中elementUI表单循环验证

进行验证的步骤 1、表单el-form 添加 :model"form" ref"form" :rules"rules"&#xff0c;注意是 :model"form"不是v-model&#xff0c;而后每个el-form-item绑定prop 2、不循环的示例在官网可看 3、循环表单的验证&#xff1a;3-1、e…

顺序表 --- C语言实现

目录 1.线性表 2.顺序表 2.1 概念和结构 2.2 接口实现 2.3 数组相关面试题 2.4 顺序表的问题及思考 1.线性表 什么是线性表 &#xff1a; 线性表&#xff08;linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构&#xff0c;常…

阿里云服务器架构X86_GPU_裸金属_超级计算集群详解

阿里云服务器架构有什么区别&#xff1f;X86计算、ARM计算、GPU/FPGA/ASIC、弹性裸金属服务器、超级计算集群有什么区别&#xff1f;阿里云服务器网分享云服务器ECS架构详细说明&#xff1a; 目录 阿里云服务器ECS架构说明 X86计算 ARM计算 GPU/FPGA/ASIC 弹性裸金属服务…

A_003.adb常用命令使用介绍

1.adb工作原理说明 adb命令是调试控制手机过程中用的比较多工具。adb是一个C/S架构的应用程序,主要由三部分组成: (1)、运行在电脑端的adb client程序 在电脑终端中执行adb --help可以查看当前支持的adb命令。当在命令行执行程序adb相关命令的时候,adb程序尝试连接到主机上…

layui选项卡演示

layui选项卡演示 .1 引入layui2. 选项卡演示实列3.js分离的代码4运行结果 在前端开发中&#xff0c;选项卡常用于展示多个内容模块&#xff0c;提供用户友好的界面交互方式。layui作为一款简洁易用的前端框架&#xff0c;提供了丰富的组件库&#xff0c;其中包括了强大且易用的…

Drag Your GAN论文解读,基于点的交互式操作拖动到生成图像[DragGAN]

只需要鼠标的点击就可以自动修图的产品&#xff0c;火爆问世&#xff0c;可以说是超越PS&#xff0c;神一般的存在了&#xff0c;而且没有门槛&#xff0c;对于普通大众来说直接可以上手使用&#xff0c;这个是PS完全不具备的。更关键的是&#xff0c;这款产品跟PS明显区别在于…

【yarn】 ‘husky install‘ fails if ‘.git‘ directory does not exists解决方法

问题描述 环境&#xff1a;win10 yarn 1.22.19 问题&#xff1a;在使用yarn安装前端依赖时&#xff0c;yarn install 出现错误: .git can’t be found (see https://git.io/Jc3F9) error Command failed with exit code 1. 截图 原因分析 根据设计&#xff0c;husky安装必…

开启visual studio,git for windows 疯狂占用内存的解决

1、开启visual studio&#xff0c;git for windows 疯狂占用内存的解决 最近开启visual studio写代码的时候&#xff0c;IDE总是会莫名奇妙的卡住然后闪退&#xff0c;今天打开任务管理器看了下原因&#xff0c;发现是visual studio所占磁盘内存不断疯涨&#xff0c;每秒几十M…