做课题查新网站,交易网站建设,开发制作微信小程序,怎么用视频做网站首页一、SPI通信
0.IIC与SPI的优缺点
https://blog.csdn.net/weixin_44575952/article/details/124182011 1.SPI介绍 同步#xff08;有时钟线#xff09;#xff0c;高速#xff0c;全双工#xff08;数据发送和数据接收各占一条线#xff09; 1#xff09;SCK:时钟线--有时钟线高速全双工数据发送和数据接收各占一条线 1SCK:时钟线--SCLKCLKCK---等价于IIC的SCK 2MOSI主机输出从机接收MISO主机接收从机输出DOData OutputDIData Input---等价于IIC的SDA 3SS片选NSSNot Slave Select--低电平有效CSChip Select--专门进行主机和该指定从机的通信线路可能不只一条 4SPI只接受一主多从 5SPI没有应答数据 DODI的区别 先确定芯片的身份主机/从机 2.硬件电路 1SCK是主机控制SCK是主机输出SCK是从机接收 2MOSI主机输出从机接收 3MISO主机接收从机输出 4SS从机选择线低电平有效 3.移位示意图 SPI的数据收发基于字节交换 如果单纯想要接收或者发送—---则将接收或者发送的数据自动屏蔽掉即可 只发送只接收既发送既接收 4.SPI时序基本单元 SPI发起指令操作的时候传输的数据单元是指令码寄存器地址操作数 1.起始条件 SS低电平有效通信时间段内一直保持低电平 起始条件SS从高电平切换到低电平 2.终止条件 终止条件SS从低电平切换到高电平 3.交换一个字节模式1 模式1:第一个边沿放数据,也可以描述成高电平放数据,第二个边沿采集数据,也可以描述成低电平采集数据(采集数据时数据不能更改) 下降沿采样将数据读入寄存器中 4.交换一个字节模式3 与模式1的区别SCK极性取反CPOL1 5.交换一个字节模式0 相比于模式1数据输出快了半个时钟 上升沿采样将数据读入寄存器中 6.交换一个字节模式2 与模式0的区别SCK极性取反CPOL1 7.注意点 1CPOL用于设置极性1表示高电平有效0表示低电平有效 2CPHA不是用于决定上升沿读取还是下降沿读取而是决定第几个周期进行采样。 3一般如果我们想要接收数据读取数据则我们可以随便写入读出一个值即可其他不用理会。我们一般发送0xff或者0x00 5.SPI时序
1.发送指令 使用模式0在时序开始前存放数据在上升沿读取数据 发送0x06芯片公司自己定义---W25Q64是写使能 接收到0xff不需要看因为我们目的是主机发送给从机所以从机传输的数据是什么无所谓 2.指定地址写 1向SS指定的设备发送写指令0x02 2随后在指定地址Address[23:0]下写入指定数据Data 由此图可知要在地址为:0x123456下写入0x55这个数据 3.指定地址读 1向SS指定的设备发送读指令0x03 2随后在指定地址Address[23:0]下读取从机数据Data 二、单片机中用到的存储器
1.物理层存储器 1磁存储原理磁带软盘机械硬盘磁盘 2光刻存储DVD 3半导体存储EEPROMNandFlashNorFlash 2.Nand和Nor的差异 (1)Nand容量大价格低需要按块访问不能按字节访问需要专用时序接口访问不能直接接到地址总线上 (2)Nor容量小价格高按块擦和写、按字节读需要专用时序接口访问 不同点 相同点 3.单片机系统常用存储解决方案 (1)单片机自身代码存储在内部Flash中本质是NorFlash (2)存少量掉电不丢失数据用EEPROM一般都是比较小--》IIC通信速度较慢典型24C02 (2)存中容量掉电不丢失数据用SPINorFlash使用SPI是为了减少引脚--》SPI通信速度比IIC快一般64k-32MB范围 (3)存大容量掉电不丢失数据用SPINandFlash一般32MB-1GB范围 (4)要便于插拔和扩展用TF/SD卡U盘等一般容量在GB级别。 (5)现在还有新型的SDNand就是芯片封装的SD卡容量在nMB-1GB级别。 (6)更大容量板载存储用eMMC芯片一般容量4GB-256GB级别 (7)STM32内部Flash可以开放给程序用存储少量掉电不丢失数据。 4、存储器总结 (1)多种可用根据产品特点和需求选择重点考虑性价比、容量、寿命、速度、可靠性等因素大多数行业都有选型惯例。 (2)程序员不必过多关心内部存储颗粒特性更多关心编程接口即可。 三、W25Q64
1.W25Q64简介
开发板中的 FLASH 芯片型号W25Q64。W25Q 系列为台湾华邦公司推出的是一种使用 SPI 通讯协议的 NOR FLASH 存储器。芯片型号后两位表示芯片容量例如 W25Q64 的 64 就是指 64Mbit 也就是 8M 的容量。它的 CS/CLK/DIO/DO 引脚分别连接到了 STM32 对应的 SPI 引脚 NSS/SCK/MOSI/MISO 上其中 STM32 的 NSS 引脚虽然是其片上 SPI 外设的硬件引脚但实际上后面的程序只是把它当成一个普通的 GPIO使用软件的方式控制 NSS 信号所以在 SPI 的硬件设计中NSS 可以随便选择普通的 GPIO不必纠结于选择硬件 NSS 信号。
FLASH 芯片中还有 WP 和 HOLD 引脚。WP 引脚可控制写保护功能当该引脚为低电平时禁止写入数据。我们直接接电源不使用写保护功能。HOLD 引脚可用于暂停通讯该引脚为低电平时通讯暂停数据输出引脚输出高阻抗状态时钟和数据输入引脚无效。我们直接接电源不使用通讯暂停功能。 1AT24C存储容量是KB级别的W25Q64是MB级别 2存储容量24位地址 2.硬件电路 3、W25Q64框图 1W25Q64使用的存储空间是8MB128*648,192bit---8,192/10248MByte实际上可以使用16MB--所以地址从0x00 00 00到0x7f ff ff 2存储空间的划分先划分为若干块在划分为若干扇区最后划分为若干页 1.分为Block 将8MB/128Block分为64KB每一个大小为64KB0-127) 2.分为Sector
将64KB/16分为4KB 3.分为page
将4K/25bit分为16bit 4.其他部分 SPI控制器状态寄存器数据缓存区 5.Flash操作的注意事项 1如果我们没有对Flash进行擦除则原来是0xAA:1010 1010如果想要修改为0x55:0101 0101---实际上无法修改【因为数据位只能由1--0无法从0--1】 2如果不进行擦除则【读出数据原始数据写入数据】 3因为要擦除将全部数据位置为1所以我们如果读写Flash输出为0xff则表示该位置被擦除后未被重写过 4擦除的最小单位扇区4096字节为单位 5一个写入时序最多只能写一页的数据不能跨页页就是256字节【因为页缓冲区只有256字节】超出部分会覆盖前面的位置部分 6写入操作后芯片会处于忙状态因为要将缓冲区中的数据写入Flash中【所以我们在执行写操作的代码后要检测芯片是否处于忙状态】 7在要进行读操作之前也要先判断芯片是否处于忙状态 8写入不能跨页但是读取可以跨页 9SPIFlash读写的最小单位是1个字节而且地址不必对齐 四、SPIFlashW25Q64数据手册解读
https://www.aiema.cn/part/datasheet/w25q64dwzpig-fn195394276 1、主要SPIFlash厂家 (1)SPIFlash本质SPI接口芯片内部存储颗粒NandNor (2)台湾Winbond华邦W开头、MXIC旺宏M开头 (3)国内GD兆易创新GD开头 2.数据手册查看
1.标准SPI指令 2.状态寄存器 1写入数据后不需要我们手动将写失能【会自动失能】 2一个写使能只能保证后续一条写指令可以操作 BUSY 是状态寄存器 (S0) 中的只读位当器件正在执行命令时该位设置为 1 状态 页编程、四页编程、扇区擦除、块擦除、芯片擦除、写入状态寄存器或 擦除/编程安全寄存器指令。 在此期间设备将忽略进一步的指令 除了读取状态寄存器和擦除/编程暂停指令参见 tW、tPP、tSE、tBE 和 交流特性中的 tCE。 当编程、擦除或写入状态/安全寄存器指令有 完成后BUSY 位将被清除为 0 状态指示设备已准备好接受进一步的指令。 写使能锁存器 (WEL) 是状态寄存器 (S1) 中的一个只读位在执行 写使能指令。 当器件写禁止时WEL 状态位清零。 一个写 上电时或执行以下任何指令后会出现禁用状态写入禁用、页面 编程、四页编程、扇区擦除、块擦除、芯片擦除、写状态寄存器、擦除 安全寄存器和程序安全寄存器。 3.指令表 五、软件SPI读写
1.硬件接线 2.SPI代码编写
#include stm32f10x.h // Device headervoid MySPI_W_CS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}
void MySPI_W_CLK(uint8_t BitValue)
{GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue)
{GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);
}
uint8_t MySPI_R_MISO(void)
{return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}void MySPI_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_Init_Structure;//要求配置为推挽输出浮空或上拉输入GPIO_Init_Structure.GPIO_ModeGPIO_Mode_IPU;GPIO_Init_Structure.GPIO_PinGPIO_Pin_6;GPIO_Init_Structure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_Init_Structure);GPIO_Init_Structure.GPIO_ModeGPIO_Mode_Out_PP;GPIO_Init_Structure.GPIO_PinGPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7;GPIO_Init_Structure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_Init_Structure);MySPI_W_CS(1);MySPI_W_CLK(0);
}
//三个基本时序起始交换数据终止
void MySPI_Start(void)
{MySPI_W_CS(0);
}
void MySPI_Stop(void)
{MySPI_W_CS(1);
}
//通过掩码依次挑出每一位操作优点是保留了原数据的值
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{uint8_t i,ByteReceive 0x00;for(i 0;i 8;i ){MySPI_W_MOSI(ByteSend (0x80i));MySPI_W_CLK(1);if(MySPI_R_MISO() 1){ByteReceive | (0x80 i);}MySPI_W_CLK(0);}return ByteReceive;
}//还可以按照移位示意图中的方式交换数据优点是效率高但不能保存原数据值
//uint8_t MySPI_SwapByte(uint8_t ByteSend)
//{
// uint8_t i;
// for(i0;i8;i )
// {
// MySPI_W_MOSI(ByteSend 0x80);
// ByteSend 1; //最高位移出最后补0
// MySPI_W_CLK(1);
// if(MySPI_R_MISO() 1) {ByteSend | 0x01;} //输入的数据放在最低位
// MySPI_W_CLK(0);
// }
// return ByteSend;
//}
0.电平翻转函数封装 因为W25Q64的频率很快所以中间不需要添加延时函数 1.初始化 2.起始信号 3.终止信号 4.交换发送/接收一个字节模式0 主机发送数据给从机从机发送数据给主机 1SS设置为下降沿 2将数据读入到引脚 3SCK设置为上升沿 4将数据从引脚读出 5将SCK设置为下降沿 5.交换发送/接收一个字节模式1 3.W25Q64代码
#include stm32f10x.h // Device header
#include MySPI.H
#include W25Q64_INS.Hvoid W25Q64_Init(void)
{MySPI_Init();
}
//用指针的方式来获取多个函数的值
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)
{MySPI_Start();MySPI_SwapByte(W25Q64_JEDEC_ID);*MID MySPI_SwapByte(W25Q64_DUMMY_BYTE);*DID MySPI_SwapByte(W25Q64_DUMMY_BYTE);*DID 8;*DID | MySPI_SwapByte(W25Q64_DUMMY_BYTE);MySPI_Stop();
}
void W25Q64_WiteEnable(void)
{MySPI_Start();MySPI_SwapByte(W25Q64_WRITE_ENABLE);MySPI_Stop();
}
/*** brief 直到BUSY清零后结束* param * retval */
void W25Q64_WaitBusy(void)
{uint32_t Timeout100000; //为了防止卡死MySPI_Start();MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);//直到busy不为1while((MySPI_SwapByte(0xFF) 0X01) 0X01){Timeout--;if(Timeout 0){break;}}MySPI_Stop();
}//页编程写入注意页编程写入一页的范围
void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArray,uint16_t count)
{//没有24位通过数组可以传多个字节。所以用32位,写入数据的数量范围0-256所以用uint16不用uint8W25Q64_WiteEnable();uint16_t i;MySPI_Start();MySPI_SwapByte(W25Q64_PAGE_PROGRAM);MySPI_SwapByte(Address 16);MySPI_SwapByte(Address 8); //高两位会丢弃MySPI_SwapByte(Address);for(i0;i count ;i){MySPI_SwapByte(DataArray[i]);}MySPI_Stop();W25Q64_WaitBusy();
}void W25Q64_SectorErase(uint32_t Address)
{//写使能仅对之后跟随的一条时序有效结束之后会失能所以每个函数加入这个就不用再写失能W25Q64_WiteEnable();MySPI_Start();MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);MySPI_SwapByte(Address 16);MySPI_SwapByte(Address 8); MySPI_SwapByte(Address);MySPI_Stop();W25Q64_WaitBusy();
}void W25Q64_ReadData(uint32_t Address,uint8_t *DataArray,uint32_t count) //改为32位
{ uint32_t i;MySPI_Start();MySPI_SwapByte(W25Q64_READ_DATA);MySPI_SwapByte(Address 16);MySPI_SwapByte(Address 8); MySPI_SwapByte(Address);for(i0;i count ;i){//调用交换读取之后内部指针自动自增DataArray[i]MySPI_SwapByte(W25Q64_DUMMY_BYTE);}MySPI_Stop();
}
0.初始化 1.获取ID 实际上是【抛砖引玉】 2.宏定义 3.写使能 4.读状态寄存器1 判断当前芯片是否处于忙状态 5.Page Program 这里我们传入数据为uint16_t不能写uint8_t因为int8最大是255而我们page最大256所以如果使用int8空间不足够 如果发送到设备的字节超过256个寻址将封装到页的开头并覆盖以前发送的数据。 DataArray写入的数值 写入操作前必须先进行写使能 6.Sector Erase (4KB) 写入操作前必须先进行写使能 7.Read Data DataArray返回读取到的数值 4.测试代码 #include stm32f10x.h // Device header
#include Delay.h
#include LED.H
#include Key.h
#include OLED.H
#include W25Q64.Huint8_t MID;
uint16_t DID;uint8_t ArrayWrite[]{0x55,0x66,0x77,0x88};
uint8_t ArrayRead[4];
int main(void)
{OLED_Init();W25Q64_Init();OLED_ShowString(1,1,MID: DID:);OLED_ShowString(2,1,W:);OLED_ShowString(3,1,R:);W25Q64_ReadID(MID,DID);OLED_ShowHexNum(1,5,MID,2);OLED_ShowHexNum(1,12,DID,4);//只擦除不写入可以验证flash擦除之后变为ff//不擦除直接改写可以测试不能由0到1只能1到0//写之前先擦除。xxx000-xxffffW25Q64_SectorErase(0x000000); //页地址范围xxxx00-xxxxffW25Q64_PageProgram(0X000000,ArrayWrite,4); W25Q64_ReadData(0X000000,ArrayRead,4);OLED_ShowHexNum(2,3,ArrayWrite[0],2);OLED_ShowHexNum(2,6,ArrayWrite[1],2);OLED_ShowHexNum(2,9,ArrayWrite[2],2);OLED_ShowHexNum(2,12,ArrayWrite[3],2);OLED_ShowHexNum(3,3,ArrayRead[0],2);OLED_ShowHexNum(3,6,ArrayRead[1],2);OLED_ShowHexNum(3,9,ArrayRead[2],2);OLED_ShowHexNum(3,12,ArrayRead[3],2);while(1){}
}
5.事前等待 VS 事后等待
1.事前等待 表示我们在编写一个函数之前先判断此时芯片是否处于忙状态。但是需要每一个函数前都进行判断读寄存器写寄存器都要进行判断 2.事后等待 表示我们在写完一个执行写操作的函数后在程序退出之前查看芯片是否处于忙状态。此时如果处于忙状态则我们可以停下来等待如果不处于忙状态则直接退出。不用每一个函数中都调用。 3.小总结 事前等待1、写入前先等待等不忙了再写入 2、效率高。 3、在写入和读取操作之前都要等待。 事后等待1、写入后立刻等待不忙了退出。 2、这样最保险函数结束后函数之外的地方芯片肯定不忙。 3、只需在写入后等待。 六、W25Q64的HAL源代码解析
1、 CubeMX例程展示
【精选】STM32CubeMX学习笔记10——SPI接口使用(读写SPI Flash W25Q64)_stm32cubemx配置spi-CSDN博客
1.时钟设置 2.SPI设置 1在 Connectivity 中选择 SPI1 设置并选择 Full-Duplex Master 全双工主模式不开启 NSS 即不使用硬件片选信号 SPI 为默认设置不作修改。只需注意一下Prescaler 分频系数最低为 4波特率 (Baud Rate) 为 18.0 MBits/s。这里被限制了SPI1 最高通信速率可达 36Mbtis/s。 Clock Polarity(CPOL)SPI 通讯设备处于空闲状态时SCK 信号线的电平信号(即 SPI 通讯开始前、 NSS 线为高电平时 SCK 的状态)。CPOL0 时 SCK 在空闲状态时为低电平CPOL1 时则相反。Clock Phase(CPHA)指数据的采样的时刻当 CPHA0 时MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的“奇数边沿”被采样。当 CPHA1 时数据线在 SCK 的“偶数边沿”采样。 3.设置SSCS:片选 原理图中虽然将 CS 片选接到了硬件 SPI1 的 NSS 引脚因为硬件 NSS 使用比较麻烦所以后面直接把 PA4 配置为普通 GPIO手动控制片选信号。 在右边图中找到 SPI1 NSS 对应引脚选择 GPIO_Output。 修改输出高电平 High【因为SS是低电平有效所以初始化为高电平】 2.MDK例程分析 3.HAL库中SPI库函数分析 4.SPIFlash驱动分析
https://www.cnblogs.com/wenhao-Web/p/13827313.html
STM32F405CubeMX HAL库读写W25Q64 SPI Flash例程_hal库spi例程-CSDN博客
W25Q64写可跨页数据 1SPIFlash允许跨页读不允许跨页写 2SPIFlash写的时候单次写是不能跨页的 #include main.h
#include stm32f4xx_hal.hSPI_HandleTypeDef hspi1;#define CS_PIN GPIO_PIN_4
#define CS_PORT GPIOA#define PAGE_SIZE 256 // 假设一页的大小为256字节void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);void W25Q64_WriteData(uint8_t* dataBuffer, uint32_t address, uint32_t dataSize);int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_SPI1_Init();// 允许片选引脚HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET);// 写入数据的缓冲区uint8_t dataBuffer[512]; // 假设写入512字节的数据// 从地址0开始写入数据W25Q64_WriteData(dataBuffer, 0, sizeof(dataBuffer));// 关闭片选引脚HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET);while (1){// Your application code here}
}// 写入数据的函数
void W25Q64_WriteData(uint8_t* dataBuffer, uint32_t address, uint32_t dataSize)
{uint32_t currentPage, remainingBytes;// 计算当前页和剩余字节数currentPage address / PAGE_SIZE;remainingBytes dataSize;// 写入整页数据while (remainingBytes PAGE_SIZE) {// 发送写使能命令uint8_t writeEnableCommand 0x06;HAL_SPI_Transmit(hspi1, writeEnableCommand, 1, HAL_MAX_DELAY);// 发送写命令和地址uint8_t writeCommand[] {0x02, (uint8_t)((address 16) 0xFF), (uint8_t)((address 8) 0xFF), (uint8_t)(address 0xFF)};HAL_SPI_Transmit(hspi1, writeCommand, sizeof(writeCommand), HAL_MAX_DELAY);// 发送数据HAL_SPI_Transmit(hspi1, dataBuffer, PAGE_SIZE, HAL_MAX_DELAY);// 等待写入完成while (W25Q64_IsWriteInProgress()) {HAL_Delay(1);}// 更新地址和剩余字节数address PAGE_SIZE;dataBuffer PAGE_SIZE;remainingBytes - PAGE_SIZE;}// 写入剩余字节if (remainingBytes 0) {// 发送写使能命令uint8_t writeEnableCommand 0x06;HAL_SPI_Transmit(hspi1, writeEnableCommand, 1, HAL_MAX_DELAY);// 发送写命令和地址uint8_t writeCommand[] {0x02, (uint8_t)((address 16) 0xFF), (uint8_t)((address 8) 0xFF), (uint8_t)(address 0xFF)};HAL_SPI_Transmit(hspi1, writeCommand, sizeof(writeCommand), HAL_MAX_DELAY);// 发送剩余数据HAL_SPI_Transmit(hspi1, dataBuffer, remainingBytes, HAL_MAX_DELAY);// 等待写入完成while (W25Q64_IsWriteInProgress()) {HAL_Delay(1);}}
}// 检查写入是否仍在进行中
int W25Q64_IsWriteInProgress(void)
{uint8_t statusReg;// 发送读取状态寄存器命令uint8_t readStatusCommand 0x05;HAL_SPI_Transmit(hspi1, readStatusCommand, 1, HAL_MAX_DELAY);// 读取状态寄存器HAL_SPI_Receive(hspi1, statusReg, 1, HAL_MAX_DELAY);// 检查忙位 (Bit 0)return (statusReg 0x01);
}void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{GPIO_InitTypeDef GPIO_InitStruct {0};if (hspi-Instance SPI1){__HAL_RCC_SPI1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/**SPI1 GPIO Configuration PA5 ------ SPI1_SCKPA6 ------ SPI1_MISOPA7 ------ SPI1_MOSI */GPIO_InitStruct.Pin GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;GPIO_InitStruct.Mode GPIO_MODE_AF_PP;GPIO_InitStruct.Pull GPIO_NOPULL;GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH;GPIO_InitStruct.Alternate GPIO_AF5_SPI1;HAL_GPIO_Init(GPIOA, GPIO_InitStruct);}
}首先计算当前页和剩余字节数然后循环写入整页数据。在每个循环中它发送写使能命令写命令和地址然后发送数据。在每次写入后它等待写入完成然后更新地址和剩余字节数。最后如果有剩余字节它再次发送写使能命令写命令和地址并发送剩余的数据。函数 W25Q64_IsWriteInProgress 用于检查写入是否仍在进行中。 W25Q64读可跨页数据
#include main.h
#include stm32f4xx_hal.hSPI_HandleTypeDef hspi1;#define CS_PIN GPIO_PIN_4
#define CS_PORT GPIOA#define PAGE_SIZE 256 // 假设一页的大小为256字节void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);void W25Q64_ReadData(uint8_t* dataBuffer, uint32_t address, uint32_t dataSize);int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_SPI1_Init();// 允许片选引脚HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET);// 读取数据的缓冲区uint8_t dataBuffer[512]; // 假设读取512字节的数据// 从地址0开始读取数据W25Q64_ReadData(dataBuffer, 0, sizeof(dataBuffer));// 关闭片选引脚HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET);while (1){// Your application code here}
}// 读取数据的函数
void W25Q64_ReadData(uint8_t* dataBuffer, uint32_t address, uint32_t dataSize)
{uint32_t currentPage, remainingBytes;// 计算当前页和剩余字节数currentPage address / PAGE_SIZE;remainingBytes dataSize;// 读取整页数据while (remainingBytes PAGE_SIZE) {// 发送读命令和地址uint8_t readCommand[] {0x03, (uint8_t)((address 16) 0xFF), (uint8_t)((address 8) 0xFF), (uint8_t)(address 0xFF)};HAL_SPI_Transmit(hspi1, readCommand, sizeof(readCommand), HAL_MAX_DELAY);// 接收数据HAL_SPI_Receive(hspi1, dataBuffer, PAGE_SIZE, HAL_MAX_DELAY);// 更新地址和剩余字节数address PAGE_SIZE;dataBuffer PAGE_SIZE;remainingBytes - PAGE_SIZE;}// 读取剩余字节if (remainingBytes 0) {// 发送读命令和地址uint8_t readCommand[] {0x03, (uint8_t)((address 16) 0xFF), (uint8_t)((address 8) 0xFF), (uint8_t)(address 0xFF)};HAL_SPI_Transmit(hspi1, readCommand, sizeof(readCommand), HAL_MAX_DELAY);// 接收剩余数据HAL_SPI_Receive(hspi1, dataBuffer, remainingBytes, HAL_MAX_DELAY);}
}void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{GPIO_InitTypeDef GPIO_InitStruct {0};if (hspi-Instance SPI1){__HAL_RCC_SPI1_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/**SPI1 GPIO Configuration PA5 ------ SPI1_SCKPA6 ------ SPI1_MISOPA7 ------ SPI1_MOSI */GPIO_InitStruct.Pin GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;GPIO_InitStruct.Mode GPIO_MODE_AF_PP;GPIO_InitStruct.Pull GPIO_NOPULL;GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH;GPIO_InitStruct.Alternate GPIO_AF5_SPI1;HAL_GPIO_Init(GPIOA, GPIO_InitStruct);}
} W25Q64_ReadData 函数首先计算当前页和剩余字节数然后循环读取整页数据。在每个循环中它发送读命令和地址然后接收数据。在每次读取后它更新地址和剩余字节数。最后如果有剩余字节它再次发送读命令和地址并接收剩余的数据。 七、STM32内部Flash
1、内部flash信息
1.查数据手册的flash章节 STM32F10xxx闪存编程参考手册.pdf · 林何/STM32F103C8 - 码云 - 开源中国 (gitee.com) 正常原来程序的代码从前往后写。 所以正常额外添加的代码从后往前写防止把原来的程序覆盖掉。 2.查MDK工程编译后的map文件 从Flash往后数9324后开始就跨页写入数据 3.操作函数查HAL库
未完成