已解决
SPI协议和W25Q128详解
来自网友在路上 170870提问 提问时间:2023-09-22 08:43:26阅读次数: 70
最佳答案 问答题库708位专家为你答疑解惑
1.SPI 是什么?
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总 线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提 供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如 AT91RM9200 。
2.SPI 物理架构
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,片选信号,由主设备控制
3.SPI 工作原理
4.SPI 工作模式
时钟极性(CPOL):
- 没有数据传输时时钟线的空闲状态电平
- SCK在空闲状态保持低电平
- SCK在空闲状态保持高 电平
时钟相位(CPHA):
时钟线在第几个时钟边沿采样数据 :
SCK的第一(奇数)边沿进行数据位采样,数据在第一个时 钟边沿被锁
SCK的第二(偶数)边沿进行数据位采样,数据在第二个时钟边沿被锁存
其中模式0和模式3最常用
模式 0 时序图:
模式 3 时序图:
5.W25Q128 介绍
1.什么是 W25Q128 ?
- W25Q128 是华邦公司推出的一款 SPI 接口的 NOR Flash 芯片,其存储空间为 128 Mbit,相当于 16M 字节。
- Flash 是常用的用于储存数据的半导体器件,它具有容量大,可重复擦写、按“扇区/块”擦除、掉 电后数据可继续保存的特性。
- Flash 是有一个物理特性:只能写 0 ,不能写 1 ,写 1 靠擦除。
2.W25Q128 存储架构
- 一般按扇区(4k)进行擦除
- 可以按 章 -- 节 -- 页 -- 字 进行理解。
3.W25Q128 常用指令
W25Q128 全部指令非常多,但常用的如下几个指令:
指令(HEX) 名称 作用 0X06 写使能 写入数据/擦除之前,必须先发送该指令 0X05 读SR1 判定FLASH是否处于空闲状态,擦除用 0X03 读数据 用于读取NOR FLASH数据 0X02 页写 用于写入NOR FLASH数据,最多写256字节 0X20 扇区擦除 扇区擦除指令,最小擦除单位(4096字节)写使能 (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片选
4.W25Q128 状态寄存器
W25Q128 一共有 3 个状态寄存器,它们的作用是跟踪芯片的状态。
其中,状态寄存器 1 较为常用。
BUSY:指示当前的状态,0 表示空闲,1 表示忙碌 WEL:写使能锁定,为 1 时,可以操作页/扇 区/块。为 0 时,写禁止。
5.W25Q128 常见操作流程
以下流程省略了拉低/拉高片选信号CS。
读操作:
擦除扇区:
写操作:
6.使用 SPI 通讯读写 W25Q128 模块
硬件接线
- VCC -- 3.3V
- CS -- PA4
- CLK -- PA5
- DO -- PA6
- DI -- PA7
cubeMX配置
w25q128_write_nocheck流程图
代码示例:
w25q128.c
#include "w25q128.h"
#include "spi.h"
#include "stdio.h"/*** @brief 初始化W25Q128* @param 无* @retval 无v*/
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;/* 下一个扇区可以写完了 */}}}
}
main.c
int main(void)
{uint8_t datatemp[TEXT_SIZE];HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_SPI1_Init();MX_USART1_UART_Init();w25q128_init();/* 写入测试数据 */sprintf((char *)datatemp, "zgl nb");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);while (1){}
}
编译烧录,打开串口助手
查看全文
99%的人还看了
相似问题
- JVM:字节码文件,类的生命周期,类加载器
- 网工内推 | 字节原厂,正式编,网络工程师,最高30K*15薪
- Go 以小端字节序修改文件
- UDP端口接收到的字节流如何转为QJsonObject、QJsonArray的方法
- 《2020年最新面经》—字节跳动Java社招面试题
- 计算Qt中的QAudioOutput缓冲区未播放的音频字节数对应时长
- 字节面试:请说一下DDD的流程,用电商系统为场景
- 深入理解JVM虚拟机第二十一篇:详解JVM当中的操作数在栈以及分析操作数栈与字节码指令和执行引擎的关系图解
- 【Linux网络编程_TCP/UDP_字节序_套接字 实现: FTP 项目_局域网聊天项目 (已开源) 】.md updata:23/11/05
- 【项目源码】反编译Java字节码生成源码
猜你感兴趣
版权申明
本文"SPI协议和W25Q128详解":http://eshow365.cn/6-11318-0.html 内容来自互联网,请自行判断内容的正确性。如有侵权请联系我们,立即删除!
- 上一篇: QT之QString的用法介绍
- 下一篇: 【Python】OJ 常用函数