完整的读写flash解读(IIC方式与SPI方式相比较,基于STM32F103ZET6)

2023-10-18 06:20

本文主要是介绍完整的读写flash解读(IIC方式与SPI方式相比较,基于STM32F103ZET6),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言
前面的博客描述了如何读写flash,可能还对读写flash思路还是不是那么的清晰,首先我们用的是外置的flash,就要模拟跟外部硬件通讯的时序,这样外部硬件才能识别主控侧发出的信号是什么!
SPI是全双工,同步的时钟总线!

目的

通过SPI的方式,实现对外部flash(W25Q128)的读与写,写入的内容读出后在TFTLCD上显示出来。SPI方式可以控制FLASH,EEPROM,虽然前面的博客时使用IIC来控制EEPROM(24c02),其实是一个结果,用不同的方式实现功能。

原理

我们来简单看一下内部的构造图:
在这里插入图片描述
从内部简明图可以看出,主机smart和从机slave都有一个串行移位寄存器,主机通过向他的SPI串行移位寄存器写入数据进行发起数据传输,此寄存器通过MOSI将数据(1BYTE)传输给从机,此时,从机通过自己的移位寄存器将其中的内容(1BYTE)移出来发送给主机,这样就实现了,两个移位寄存器中内容的交换。
所以外设的写操作和读操作是也就是这样被完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。

SPI 接口一般使用 4 条线通信:
MISO 主设备数据输入,从设备数据输出。
MOSI 主设备数据输出,从设备数据输入。
SCLK 时钟信号,由主设备产生。
CS 从设备片选信号,由主设备控制。

通信时钟极性与相位
SPI 总线四线工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果 CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果 CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI 主模块和与之通信的外设备时钟相位和极性应该一致。

对比

和IIC对比,两者都是相似的配置方式
不同点:
IIC:需要模拟真实的硬件时序,数据接收,数据发送的每一个时序需要自己从底层函数构造起来
SPI:不需要构造最基础的单个字节数据发送与接收函数,可以直接调用库函数
相同点:
初始化结束,构造出能够发送与接受(单个字节)函数,作为调用的接口,在构造能够写入指定的地址,数据,长度等等,都是通过发送和接收函数完成的。

从IIC读写24c02(eeprom)到SPI读写falsh(w25q128),都是先构造使用此方式实现器件的读写基础函数,适用于大多数使用此原理进行通信的外部硬件,再根据具体的外部硬件属性,构造出来真正可以与外部器件通信的函数,如果读者没有发现,就需要大家自己要对自己的代码进行总结,为何要分模块编写我们的器件?

硬件连接

在这里插入图片描述
在这里插入图片描述
PB12控制flash的片选,PB13控制时钟频率,MISO控制数据接收,MOSI控制数据的输出。所以13,114,15可以配置为推挽输出,且是复用的,12设置成一般的推挽输出就可以。

基础函数

1.使用SPI方式操作外部器件的初始化
1).GPIO初始化函数:

void spi_init(void)
{GPIO_InitTypeDef GPIO_Initstructure;SPI_InitTypeDef  SPI_Initstructure;RCC_APB1PeriphClockCmd(	RCC_APB1Periph_SPI2,  ENABLE );RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );GPIO_Initstructure.GPIO_Mode=GPIO_Mode_AF_PP;GPIO_Initstructure.GPIO_Pin=GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;GPIO_Initstructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_Initstructure);GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);SPI_Initstructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_256;SPI_Initstructure.SPI_CPHA=SPI_CPHA_2Edge;SPI_Initstructure.SPI_CPOL=SPI_CPOL_High;SPI_Initstructure.SPI_CRCPolynomial=SPI_Initstructure.SPI_DataSize=7;SPI_Initstructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;SPI_Initstructure.SPI_FirstBit=SPI_FirstBit_MSB;SPI_Initstructure.SPI_Mode=SPI_Mode_Master;		SPI_Initstructure.SPI_NSS=SPI_NSS_Soft;	SPI_Init(SPI2, &SPI_Initstructure); SPI_Cmd(SPI2, ENABLE);write_read(0xff);
}

观察仔细的可能已经发现,上面GPIO初始化少了PB12,为什么这里初始化上呢?因为这个IO口控制两个外设,我们的 F_CS 是连接在 PB12 上面的,W25Q128 和 NRF24L01 共 用 SPI2,所以这两个器件在使用的时候,必须分时复用(通过片选控制)才行。我们的这个IO口的初始化就放在具体操作的外设初始化内。
2).对flash的读写1BYTE函数

uint8_t write_read(uint8_t TXDATA)
{uint8_t i;while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET){i++;if(i>200)return 0;}SPI_I2S_SendData(SPI2, TXDATA);i=0;while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET){i++;if(i>200)return 0;}return SPI_I2S_ReceiveData(SPI2) ;		    
}

根据寄存器标志位判断移位寄存器的状态,参数是要写入的值,return是读到的值,SPI_I2S_SendData(SPI2, TXDATA);和SPI_I2S_ReceiveData(SPI2) ;都是库函数可以调用的,不需要像IIC那样构造时许函数!
3).SPI速度设置
根据预分频值,通过你位操作实现对SPI速率的设置

void SPI2_SetSpeed(uint8_t SPI_BaudRatePrescaler)
{assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));SPI2->CR1&=0XFFC7;SPI2->CR1|=SPI_BaudRatePrescaler;	SPI_Cmd(SPI2,ENABLE); 
}

2.基于SPI基础函数,搭建属于W25Q128器件的基础通信函数
1)GPIO初始化函数

void W25Q128_init()
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_SetBits(GPIOB,GPIO_Pin_12);W25QX_CS=1;spi_init();//引用SPI初始化函数SPI2_SetSpeed(SPI_BaudRatePrescaler_2);//SPI速率函数调用version=read_IC_ID();//版本读写函数
}

2)都状态寄存器值函数
我们读出来的是一个八位的二进制数据,如下对应,

//BIT      7       6     5     4     3      2       1       0SPR     RV    TB   BP2   BP1    BP0     WEL    BUSY状态保护位 TB,BP2,BP1,BP0:FLASH的区域写保护设置,WEL写使能锁定,BUSY忙标记位
uint8_t read_rs(void)
{uint8_t status;W25QX_CS=0;write_read(0x05);status=write_read(0xff);W25QX_CS=1;return status;
}

3)写(修改)状态寄存器值函数
注意:此函数0,1,6位不可修改

void write_rs(uint8_t data)
{W25QX_CS=0;write_read(0x01);write_read(data);W25QX_CS=1;
}

4)写使能函数
操作WEL位,将其置位

void write_en(void)
{W25QX_CS=0;write_read(0x06);W25QX_CS=1;
}

5)发送禁止写数据命令
也是操作WEL位,将其清零

void write_disEN(void)
{W25QX_CS=0;write_read(0x04);W25QX_CS=1;}

6)发送读取器件ID命令

uint16_t read_IC_ID(void)
{uint16_t temp=0;W25QX_CS=0;write_read(0x90);write_read(0x00);write_read(0x00);write_read(0x00);temp|=write_read(0xff)<<8;temp|=write_read(0xff);W25QX_CS=1;return temp;
}

7)从指定地址,读取指定长度的数据

void read_len_data(uint32_t addr,uint16_t len,uint8_t *buff)
{uint16_t i;W25QX_CS=0;write_read(0x03);write_read((addr>>16)&0xff);write_read((addr>>8)&0xff);write_read(addr&0xff);for(i=0;i<len;i++){*(buff+i)=write_read(0xff);}W25QX_CS=1;
}

8)等待BUSY位清空(不忙)

void wait_write_end(void)
{while((read_rs()&0x01)==0x01);
}

9)写一页(小于256字节)数据函数

void write_len_page(uint32_t addr,uint16_t len,uint8_t *data)
{uint16_t i;write_en();W25QX_CS=0;write_read(0x02);//·¢ËÍдҳµØÖ·ÃüÁîwrite_read((addr>>16)&0xff);write_read((addr>>8)&0xff);write_read(addr&0xff);for(i=0;i<len;i++)write_read(*(data+i));W25QX_CS=1;wait_write_end();
}

10)擦除一个扇区(此flash的datasheet一扇区为4K),也是最小擦除单位

void erase_sector(uint32_t addr)
{addr*=4096;write_en();wait_write_end();W25QX_CS=0;write_read(0x20);write_read((addr>>16)&0xff);write_read((addr>>8)&0xff);write_read(addr&0xff);W25QX_CS=1;wait_write_end();
}

11)擦除整个falsh芯片

void erase_whole_sector(void)
{write_en();wait_write_end();W25QX_CS=0;write_read(0xC7);W25QX_CS=1;wait_write_end();
}

12)要写入数据的区域都是0xff条件下,写入小于65535个字节函数

void write_len_nocheck(uint32_t addr,uint16_t len,uint8_t *data)
{uint16_t surplus;surplus=256-len%256;if(surplus>=len)surplus=len;while(1){write_len_page(addr,surplus,data);if(surplus==len)break;else {addr+=surplus;len-=surplus;data+=surplus;if(len<=256){surplus=len;}else surplus=256;}}
}

13)在写入数据前,不管是否有擦除要写入数据的flash的基础函数,再写入的过程中要判断是否是可写入的

void write_len_check(uint32_t addr,uint16_t len,uint8_t *data)
{uint32_t sector;uint16_t pag;uint8_t *buf;uint16_t purplu;uint16_t number;uint16_t i;sector=addr/4096;pag=addr%4096;purplu=4096-pag;buf=buff;if(purplu>=len){purplu=len;}while(1){read_len_data(sector*4096,4096,buf);for(number=0;number<purplu;number++){if(buf[pag+number]!=0xff)break;					 }if(number<purplu){erase_sector(sector);for(i=0;i<purplu;i++){buf[pag+i]=*(data+i);}write_len_nocheck(sector*4096,4096,buf);}else{write_len_nocheck(addr,purplu,data);}if(len==purplu)break;else {sector++;pag=0;len-=purplu;addr+=purplu;data+=purplu;if(len<=4096)purplu=len;elsepurplu=4096;}}
}

此是stm32开发板测falsh是16MB,分为256块,每块大小为64K,每块分为16扇区,每个扇区4K,每个扇区又分为16页,每页为256字节,这也就是上面的函数为什么写入超过256字节时要经过处理

主函数mian

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "W25Q128.h"
const u8 TEXT_Buffer[]={" STM32 SPI TEST"};
#define SIZE sizeof(TEXT_Buffer)
extern 	uint8_t buff[4096];int main(void){	 uint8_t x=0;uint8_t key;uint8_t datatemp[SIZE];uint32_t FLASH_SIZE; delay_init();	    	uart_init(115200);	 	LED_Init();			     LCD_Init();KEY_Init();	W25Q128_init();while(read_IC_ID()!=0xEF17)	{LCD_ShowString(30,150,200,16,16,"W25Q128 Check Failed!");delay_ms(500);LCD_ShowString(30,150,200,16,16,"Please Check!        ");delay_ms(500);LED0=!LED0;}while(1) {		 key=KEY_Scan(0);if(key==KEY1_PRES){LCD_Fill(0,170,239,319,WHITE);LCD_ShowString(30,170,200,16,16,"Start Write W25Q128...."); 	write_len_check(FLASH_SIZE-100,SIZE,(u8*)TEXT_Buffer);LCD_ShowString(30,170,200,16,16,"W25Q128 Write Finished!");}if(key==KEY0_PRES){LCD_ShowString(30,170,200,16,16,"Start Read W25Q128.... ");				read_len_data(FLASH_SIZE-100,SIZE,datatemp);LCD_ShowString(30,170,200,16,16,"The Data Readed Is:  ");	LCD_ShowString(30,190,200,16,16,datatemp);}} 
}

实验现象

通过先按KEY1 按键写入数据,然后按 KEY0 读取数据,显示屏上显示结果,写入的数据可以自行修改!

流程总结

先获得首地址(WriteAddr)所在的扇区,并计算在扇区内
的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是否要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定长度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此循环,直到写入结束。

从代码可以看出,我们在SPI函数的基础之上,是以256字节的单位(页)大小区域上进行写操作的(读操作比较简单就不陈述),超过256的我们需要构造函数,使得地址加256,继续进行写操作,如果写的数据超过了一个扇区(4K),需要对超过扇区的地址数据进行累加,利用扇区偏移,来操作扇区级别的数据写入!这两个数据处理关键点需要我们抓住,这样就能构造出在64M(1块)内进行数据的连续写操作。

其他

通过按键控制数据的写入与读出,TFTLCD显示结果,调用前面我的博客的头文件及初始化函数即可,
基础的函数当然还有,都是一个原理,器件的参数,属性都需要参照器件的datasheet,虽然有些许器件原理和大小都是相同的,也不排除例外,还是建议去参照数据手册收再编写程序

这篇关于完整的读写flash解读(IIC方式与SPI方式相比较,基于STM32F103ZET6)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/230776

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

百度/小米/滴滴/京东,中台架构比较

小米中台建设实践 01 小米的三大中台建设:业务+数据+技术 业务中台--从业务说起 在中台建设中,需要规范化的服务接口、一致整合化的数据、容器化的技术组件以及弹性的基础设施。并结合业务情况,判定是否真的需要中台。 小米参考了业界优秀的案例包括移动中台、数据中台、业务中台、技术中台等,再结合其业务发展历程及业务现状,整理了中台架构的核心方法论,一是企业如何共享服务,二是如何为业务提供便利。

10. 文件的读写

10.1 文本文件 操作文件三大类: ofstream:写操作ifstream:读操作fstream:读写操作 打开方式解释ios::in为了读文件而打开文件ios::out为了写文件而打开文件,如果当前文件存在则清空当前文件在写入ios::app追加方式写文件ios::trunc如果文件存在先删除,在创建ios::ate打开文件之后令读写位置移至文件尾端ios::binary二进制方式

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

MCU7.keil中build产生的hex文件解读

1.hex文件大致解读 闲来无事,查看了MCU6.用keil新建项目的hex文件 用FlexHex打开 给我的第一印象是:经过软件的解释之后,发现这些数据排列地十分整齐 :02000F0080FE71:03000000020003F8:0C000300787FE4F6D8FD75810702000F3D:00000001FF 把解释后的数据当作十六进制来观察 1.每一行数据

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

什么是 Flash Attention

Flash Attention 是 由 Tri Dao 和 Dan Fu 等人在2022年的论文 FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness 中 提出的, 论文可以从 https://arxiv.org/abs/2205.14135 页面下载,点击 View PDF 就可以下载。 下面我

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念