GD32实战11__SPI FLASH
知识点
1. 理解SPI总线原理
2. 强化按时序图编程
3. 掌握FLASH
SPI原理
SPI(Serial Peripheral Interface)串行外设接口,是Motorola公司推出的一种同步串行接口技术。具体高速、全双工、同步的特点。总线本身并没有提供流控、应答确认和校验机制,需要特别注意。
如图,SPI是主从型总线有且只有一个主设备,可以有1个或多个从设备
SPI至少需要4根线,分别是
- SCK 时钟信号,由主设备产生
- MOSI (Master Out Slave In) 主设备输出,从设备输入线
- MISO (Master In Slave Out) 主设备输入,从设备输出线
- CS(可以有多根) 片选信号线,用于控制跟哪个从设备通信
如图,因SPI同步双工串行的特性,其内部原理非常简洁
从上面,可以理解SPI的收发其实就是在时钟信号下采样过程,那么我们是在时钟的上升沿、下降沿、高电平、低电平采样呢?SPI按时钟极性(CPOL)和时钟相位(CPHA)分成4种模式用于控制时钟采样,通信双方模式必须一致,具体如下
模式 CPOL CPHA Mode0 0 0 Mode1 0 1 Mode2 1 0 Mode3 1 1 时钟极性CPOL是用来配置SCLK的电平出于哪种状态时是空闲态或者有效态,时钟相位CPHA
是用来配置在第几个边沿采样数据。
CPOL=0,表示当SCLK=0时处于空闲态,所以有效状态就是SCLK处于高电平时
CPOL=1,表示当SCLK=1时处于空闲态,所以有效状态就是SCLK处于低电平时
CPHA=0,表示数据采样是在第1个边沿,数据发送在第2个边沿
CPHA=1,表示数据采样是在第2个边沿,数据发送在第1个边沿主机和从机的发送数据是同时完成的,两者的接收数据也是同时完成的。所以为了保证主从机正确通信,应使得它们的SPI具有相同的时钟极性和时钟相位。
SPI协议并没有规定数据一定是8位的,也可以是16位的,要看通信双方支持哪种。
传输时是高位先传还是低位先传,协议也没强制要求,也要看双方支持。
至此,SPI协议部分已经全部介绍完了,可见SPI非常的简洁高效,且弹性很大,需要结合具体的应用配置。
FLASH芯片手册导读
GD25Q40芯片是一款Nor FLash,Nor Flash的特点是以‘块’为操作的最小擦写单元,字节为最小读取单元。例如,该款芯片有512KByte=4094Kbit=4MBit,每页大小256Byte,每页就是Nand Flash的最下操作‘块’,具体如图
而且需要遵循先擦后写的操作。flash进行写操作时,只能将相应的位由1变0,而擦除才能把块内所有位由0变1。所有写入数据时,如果该页已经存在数据,必须先擦除再写。
既然Flash有如此特性,芯片是如何管理该芯片的呢?如下图,芯片通过一个命令和逻辑控制器与真正的存储空间进行交互,也可以这么理解,外部MCU要通过SPI总线,向FLASH芯片发送各式各样的命令来控制芯片的读写擦操作。
具体命令如下,命令很多,初看有点眼花,可以先看我标红的几个命令,可以看出,命令无非是写使能/去使能,读芯片状态,读数据,写数据,擦除命令。再仔细看其他命令也无法这几类,只是在不同模式的下操作罢了。注:此处提到的模式时芯片提供的,例如快速读写模式等等,有兴趣可阅读芯片手册,我选的是标准模式。
最后,只要安装命令的时序要求写成代码就可以了,具体时序在下面说。
通过SPI读写FLASH
用SPI总线读写FLASH芯片是非常常见的应用
硬件设计
软件设计
GD32是支持SPI控制器的,为了加深对SPI协议的理解,我会软件模拟一个SPI,也会用SPI控制器来实现一个,毕竟在实际应用中多少用硬件SPI控制器的。
软件GPIO模拟SPI
从手册里可以查到,支持mode 0和3,此处我选mode 0,即CPOL和CPHA都为0。
注意,时序参数要求必须按照手册描述写,如下
参考上表,我把延时时间固定5us,这样代码会简单很多,且满足了上述ns级最小延时的要求,虽然有几项有最大时间要求,却不影响我们编码。代码如下:
U8 DRV_SPI_SwapByte(IN U8 byte)
{U8 i = 0;U8 inDate = byte;U8 outBit = 0;U8 outDate = 0;/* SCKPL = 0; SCKPH = 0 */for (i = 0; i < 8; i++){if (inDate & 0x80){SPI_MOSI_HIGH;}else{SPI_MOSI_LOW;}SPI_Delay(5);SPI_SCK_HIGH;outBit = SPI_MISO_READ;if (outBit){outDate |= 0x1;}SPI_Delay(5);SPI_SCK_LOW;SPI_Delay(5);inDate <<= 1;if (i <7){outDate <<= 1;}}return outDate;
}
硬件SPI
详细阅读SPI配置部分代码,与前面原理部分说的完全一致,且参数GD32手册里也有详细说明。
U8 DRV_SPI_SwapByte(IN U8 byte)
{while (SPI_I2S_GetBitState(SPI1, SPI_FLAG_TBE) == RESET);SPI_I2S_SendData(SPI1, byte);while (SPI_I2S_GetBitState(SPI1, SPI_FLAG_RBNE) == RESET);return SPI_I2S_ReceiveData(SPI1);
}static VOID SPI_Configuration(VOID)
{SPI_InitPara SPI_InitStructure;RCC_APB2PeriphClock_Enable(RCC_APB2PERIPH_SPI1, ENABLE); SPI_InitStructure.SPI_TransType = SPI_TRANSTYPE_FULLDUPLEX;SPI_InitStructure.SPI_Mode = SPI_MODE_MASTER;SPI_InitStructure.SPI_FrameFormat = SPI_FRAMEFORMAT_8BIT;SPI_InitStructure.SPI_SCKPL = SPI_SCKPL_LOW;SPI_InitStructure.SPI_SCKPH = SPI_SCKPH_1EDGE;SPI_InitStructure.SPI_SWNSSEN = SPI_SWNSS_SOFT;SPI_InitStructure.SPI_PSC = SPI_PSC_32; SPI_InitStructure.SPI_FirstBit = SPI_FIRSTBIT_MSB;SPI_InitStructure.SPI_CRCPOL = 7;SPI_Init(SPI1, &SPI_InitStructure);SPI_Enable(SPI1, ENABLE);
}
FLASH芯片操作
写使能,图中1,2,3步所示,代码也是按这个时序写的,下面的命令就不再画图说明了
static VOID GD25Q40_WriteEnable(VOID) {GD25Q40_CS_LOW();DRV_SPI_SwapByte(WREN);GD25Q40_CS_HIGH(); }
等待写操作结束,代码使用的是05H,所以只回一个字节
static VOID GD25Q40_WaitForWriteEnd(VOID) {U8 FLASH_Status = 0;GD25Q40_CS_LOW();DRV_SPI_SwapByte(RDSR);do{FLASH_Status = DRV_SPI_SwapByte(Dummy_Byte);}while ((FLASH_Status & WIP_Flag) == SET); /* Write in progress */GD25Q40_CS_HIGH(); }
Sector擦除,擦除可以理解为一个特殊的写过程,即写FF的过程,所以在下一个操作之前需要等待写完成
VOID DRV_GD25Q40_SectorErase(U32 SectorAddr) {GD25Q40_WriteEnable();GD25Q40_CS_LOW();DRV_SPI_SwapByte(SE);DRV_SPI_SwapByte((SectorAddr & 0xFF0000) >> 16);DRV_SPI_SwapByte((SectorAddr & 0xFF00) >> 8);DRV_SPI_SwapByte(SectorAddr & 0xFF);GD25Q40_CS_HIGH();GD25Q40_WaitForWriteEnd(); }
Block擦除
VOID DRV_GD25Q40_BulkErase(VOID) {GD25Q40_WriteEnable();GD25Q40_CS_LOW();DRV_SPI_SwapByte(BE);GD25Q40_CS_HIGH();GD25Q40_WaitForWriteEnd(); }
读数据,读的时候可以指定任意地址读,且可以按字节读
VOID DRV_GD25Q40_BufferRead(U8* pBuffer, U32 ReadAddr, U16 NumByteToRead) {GD25Q40_CS_LOW();DRV_SPI_SwapByte(READ);DRV_SPI_SwapByte((ReadAddr & 0xFF0000) >> 16);DRV_SPI_SwapByte((ReadAddr& 0xFF00) >> 8);DRV_SPI_SwapByte(ReadAddr & 0xFF);while (NumByteToRead--) {*pBuffer = DRV_SPI_SwapByte(Dummy_Byte);pBuffer++;}GD25Q40_CS_HIGH(); }
按页写
VOID DRV_GD25Q40_PageWrite(U8* pBuffer, U32 WriteAddr, U16 NumByteToWrite) {GD25Q40_WriteEnable();GD25Q40_CS_LOW();DRV_SPI_SwapByte(WRITE);DRV_SPI_SwapByte((WriteAddr & 0xFF0000) >> 16);DRV_SPI_SwapByte((WriteAddr & 0xFF00) >> 8);DRV_SPI_SwapByte(WriteAddr & 0xFF);while (NumByteToWrite--){DRV_SPI_SwapByte(*pBuffer);pBuffer++;}GD25Q40_CS_HIGH();GD25Q40_WaitForWriteEnd(); }
写入一段buf,该接口是在按页的基础上,增加是否换页写的封装接口
VOID DRV_GD25Q40_BufferWrite(U8* pBuffer, U32 WriteAddr, U16 NumByteToWrite) {U8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;Addr = WriteAddr % GD25Q40_PageSize;count = GD25Q40_PageSize - Addr;NumOfPage = NumByteToWrite / GD25Q40_PageSize;NumOfSingle = NumByteToWrite % GD25Q40_PageSize;/* WriteAddr is GD25Q40_PageSize aligned */if (Addr == 0){ /* NumByteToWrite < GD25Q40_PageSize */if (NumOfPage == 0) {DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}else /* NumByteToWrite > GD25Q40_PageSize */{while (NumOfPage--){DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, GD25Q40_PageSize);WriteAddr += GD25Q40_PageSize;pBuffer += GD25Q40_PageSize;}DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}else /* WriteAddr is not GD25Q40_PageSize aligned */{if (NumOfPage == 0){/* (NumByteToWrite + WriteAddr) > GD25Q40_PageSize */if (NumOfSingle > count) {temp = NumOfSingle - count;DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, count);WriteAddr += count;pBuffer += count;DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, temp);}else{DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}}else /* NumByteToWrite > GD25Q40_PageSize */{NumByteToWrite -= count;NumOfPage = NumByteToWrite / GD25Q40_PageSize;NumOfSingle = NumByteToWrite % GD25Q40_PageSize;DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, count);WriteAddr += count;pBuffer += count;while (NumOfPage--){DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, GD25Q40_PageSize);WriteAddr += GD25Q40_PageSize;pBuffer += GD25Q40_PageSize;}if (NumOfSingle != 0){DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}} }
功能测试举例
VOID APP_SPI_Test(VOID)
{U32 Manufact_ID = 0;U8 Tx_Buffer[256];U8 Rx_Buffer[256];U16 i = 0;DRV_GD25Q40_Init();printf("\n\rGD32103C-EVAL-V1.1 SPI Flash: configured...\n\r");Manufact_ID = DRV_GD25Q40_ReadID();printf("\n\rThe Flash_ID:0x%X\n\r", Manufact_ID);if (Manufact_ID == sFLASH_ID) {printf("\n\rWrite to Tx_Buffer:\n\r");for(i=0; i<=255; i++) {Tx_Buffer[i] = i;printf("0x%02X ",Tx_Buffer[i]);if(i%16 == 15){printf("\n\r");}}printf("\n\rRead from Rx_Buffer:\n\r");DRV_GD25Q40_SectorErase(FLASH_WriteAddress);DRV_GD25Q40_BufferWrite(Tx_Buffer,FLASH_WriteAddress, 256);APP_Delay(10);DRV_GD25Q40_BufferRead(Rx_Buffer,FLASH_ReadAddress, 256); for(i=0; i<=255; i++) { printf("0x%02X ", Rx_Buffer[i]);if(i%16 == 15){printf("\n\r");}}printf("\n\rSPI Flash: Initialize Successfully!\n\r");}else{printf("\n\rSPI Flash: Initialize Fail!\n\r");}}
补充,Nor Flash和Nand Flash
- 结构方面
- Nor Flash采用内存的随机读取技术。各单元之间是并联的,对存储单元进行统一编址,所以可以随机访问任意一个字。既然是统一编址,Nor Flash就可以芯片内执行,即应用程序可直接在flash内运行,而无需先拷贝到RAM。
- Nand Flash数据线和地址线共用I/O线,需额外联接一些控制的输入输出。
- 读写速度方面
- Nor Flash有更快的读取速度
- Nand Flash有更快的写、擦除速度。
- 寿命(耐用性)
- Flash写入和擦除数据时会导致介质的氧化降解。这方面Nor Flash尤甚,所以Nor Flash不适合频繁擦写。
- Nor的擦写次数是10万次,Nand的擦写次数是100万次。
- 坏块处理
- Nand器件的坏块是随机分布的,在使用过程中,难免会产生坏块。所以在使用时要进行坏块管理以保障数据可靠。
- 成本和容量
- 在面积和工艺相同的情况下,Nand的容量比Nor大的多,成本更低。
- Nor Flash可直接通过程序编程,根据地址直接读取,容量一般是M级别的
- Nand Flash是根据数据块来设计的,所有Nand Flash容量更大,一般是G级别的。
- 易用性
- Nor Flash有专用的地址引脚来寻址,较容易和其他芯片联接,还支持本地执行。
- Nand Flash的IO端口采用复用的数据线和地址线,必须先通过寄存器串行地进行数据存取。各厂商对信号的定义不同,增加了应用的难度。
- 编程角度
- Nor Flash采用统一编址(有独立地址线),可随机读取每个“字”,但NOR flash不能像RAM以字节改写数据,只能按“页”写,故Nor Flash不能代替RAM。擦除既可整页擦除,也可整块擦除。
- Nand Flash共用地址线和数据线,页是读写数据的最小单元,块是擦除数据的最小单元。
- 另外,Flash进行写操作时,只能将相应的位由1变0,而擦除才能把块内所有位由0变1。所有写入数据时,如果该页已经存在数据,必须先擦除再写。
代码路径
https://github.com/YaFood/GD32F103/tree/master/TestSPI
https://gitee.com/YaFOOD/GD32F103/tree/master/TestSPI
GD32实战11__SPI FLASH相关推荐
- GD32上FAL Flash分区驱动移植及Easyflash与FlashDB移植说明
GD32上FAL Flash分区驱动移植及Easyflash与FlashDB移植说明 效果 移植前提 下载源码 移植过程 加入以下文件及文件夹到工程目录和工程 将demo目录下的接口文件做下修改 修改 ...
- GD32片内flash读写数据
GD32现在越来越火,应用也越来越广泛.我们在开发项目的时候,总会有需要掉电存储一些配置信息的时候,但是使用外挂flash.或者EEPROM,或多或少都会占用一些外围接口或增加一定的成本.于是,直接将 ...
- GD32实战20__Boot综合实验
知识点 设计并实现一个boot,需要用到如下知识点, 1. 体会boot的作用2. 新增闪存控制器学习3. 串口知识复习4. FLASH&SPI知识复习5. 状态机知识点复习6. 初识上位机软 ...
- GD32实战7__中断
引子 什么是中断 举个生活中的小栗子吧,我正在编写这个文档,突然门铃响了,我去开下门,原来是快递,签收完快递后,又回来接着写. 上面的例子中, 1. 我就是CPU 2. 编写文档,是主运行程序 ...
- 【IAP】STM32和GD32的IAP原理分析、教程、资料整理
文章目录 前言 一.什么是IAP? 二.IAP执行原理(以STM32F10X为例) 2.1 STM32F10X的储存器映像 2.2 正常上电的运行流程 2.3 加入IAP后的Bootloader运行流 ...
- GD32 使用stm32 固件库
1. 系统 1) 晶振起振区别 描述:启动时间,GD32 与STM32 启动时间都是2ms,实际上GD 的执行效率快,所以ST 的HSE_STARTUP_TIMEOUT ((uint16_t)0x05 ...
- GD32f303 flash加密
1.简介 GD32加密即将flash中程序固件保护起来,防止别人通过外部调试接口或者其他方法读取烧写的flash中的程序.防止抄袭,防止破坏. 2.mcu加密方式 <1> 写特定配置字 & ...
- STM32到GD32移植攻略
1. 系统 1) 晶振起振区别 描述:启动时间,GD32 与STM32 启动时间都是2ms,实际上GD 的执行效率快,所以ST 的HSE_STARTUP_TIMEOUT ((uint16_t)0x05 ...
- GD32F303 使用 STM32Cubmex 开发应用 使用GD32官方例程 开发Bootloader 修正错误 见评论
GD32F303 使用 STM32Cubmex 开发应用程序 使用GD32官方例程 开发Bootloader程序 最近使用GD32F303开发项目,为了偷懒使用 STM32Cubmex CPU选STM ...
最新文章
- 使用Python和OpenCV检测图像中的条形码
- Nginx负载均衡的详细配置及使用案例
- php 插入表,php 向数据库表中插入数据
- 阿里京东带头打劫,下一个被干掉的就是你
- Kotlin与Java之争
- GIT项目管理工具(part6)--放弃工作区文件修改及从仓库区恢复文件
- c 定义结构体时提示应输入声明_C++|了解结构体的内存对齐(成员声明的顺序影响占用空间大小)...
- Mac设置多屏幕的时候程序坞的位置
- Tosca new project Repository as MS SQL Server
- jQuery常用语法总结
- cross-env跨平台设置环境变量
- 西安工程大学计算机是几本专业,2016年西安工程大学计算机科学与技术(卓越班)专业在陕西录取分数线...
- 英文视频字幕自动生成
- 中国城市轨道交通与设备产业十四五建设规划与运营模式咨询报告2022-2028年
- 分享【百度搜狗360】SEO优化交流讨论Q群【禁广告/精品群】
- 2005年10月--至今,开发过的项目
- 公开数据集分享(一)-MMWHS
- IDEA中出现Connection refused: connect问题的解决方法
- 知识付费分销直播营销系统源码
- flowci php,我和flow.ci的第一次亲密接触