STM32标准库——(21)Flash闪存

目录

1.简介

2.闪存模块组织

3.基本结构

4.使用指针访问存储器

5.程序存储器全擦除

6. 程序存储器页擦除

7.程序存储器编程

8.选项字节

9.选项字节擦除

10.选项字节写入

11.器件电子签名

12.读写内部Flash

12.1 接线图

12.2 相关代码

12.2.1 MyFLASH.c

12.2.2 MyFLASH.h

12.2.3 Store.c

12.2.4 Strore.h

12.2.5 main.c

13.读取芯片ID

13.1 接线图

13.2 相关代码


1.简介

  • 第一个用途,对于我们这个C8T6芯片来说,它的程序存储器容量是64K,一般我们写个简单的程序,可能就只占前面的很小一部分空间,剩下的大片空余空间我们就可以加以利用,比如存储一些我们自定义的数据,这样就非常方便,而且可以充分利用资源,不过这里要注意我们在选取存储区域时,一定不要覆盖了原有的程序,要不然程序自己把自己给破坏了,一般存储少量的参数,我们就选最后几页存储就行了,关于如何查看程序所占用空间的大小,这个我们下小节也会介绍,然后第二个用途就是通过在程序中编程IAP,实现程序的自我更新,我们在存储用户数据时要避开程序本身,以免破坏程序,但如果我们就非要修改程序本身,这会发生什么呢,那这就是第二点提到的功能,在程序中编程,利用程序来修改程序本身,实现程序的自我更新,这个在程序中编程就是IAP。
  • 在线编程(In-Circuit Programming – ICP)用于更新程序存储器的全部内容,它通过JTAG、SWD协议或系统加载程序(Bootloader)下载程序, ICP英文直译过来也可以叫在电路中编程,意思就是下载程序,你只需要留几个引脚就行,不用拆芯片了,就叫在电路中进行编程,ICP的作用是用于更新程序存储器的全部内容,它通过JTAG SWD协议或系统加载程序BOOTLOADER下载程序,这个JTAG SWD就是仿真器下载程序,就是我们目前用的stlink,使用SWD下载程序,每次下载都是把整个程序完全更新掉,那系统加载程序就是系统存储器的BOOTLOADER,也就是串口下载,串口下载也是更新整个程序,这就是我们一直在用的ICP下载方式,之后更高级的下载方式就是在程序中编程(In-Application Programming – IAP),简称IAP,它可以使用微控制器支持的任意一种通信接口下载程序,怎么实现呢,那比如这是整个程序存储器,我们首先需要自己写一个BOOTLOADER程序,并且存放在程序更新时不会覆盖的地方,比如我们放在最后面,然后需要更新程序时,我们控制程序跳转到这个自己写的BOOTLOADER这里来,在这里面我们就可以接收任意一种通讯接口传过来的数据,比如串口、USB、蓝牙转串口、WIFI转串口等等,这个传过来的数据就是待更新的程序,然后我们控制flash读写,把收到的程序写入到前面程序正常运行的地方,写完之后再控制程序跳转回正常运行的地方,或者直接复位,这样程序就完成了自我升级

2.闪存模块组织

  1.  对于主存储器(程序存储器 用来存放程序代码),这里对它进行了分页,分页是为了更好的管理闪存擦除和写保护都是以页为单位的,这点和之前W25Q64芯片的闪存一样,同为闪存它们的特性基本一样,写入前必须擦除,擦除必须以最小单位进行,擦除后数据位全变为1,数据只能1写0,不能0写1,擦除和写入之后都需要等待忙,这些都是一样的,学习这节之前,大家可以再复习一下W25Q64,再学这一节就会非常轻松了,那W25Q64的分配方式是先分为块block,再分为扇区sector比较复杂,这里就比较简单了,它只有一个基本单位就是页,每一页的大小都是1K,0到127总共128页,总量就是128K,对于C8T6来说,它只有64K,所以C8T6的页只有一半0~63总共64页共64K
  2. 第一个页的起始地址就程序存储器的起始地址0x08000000,之后就是一个字节一个地址依次线性分配的,看一下每页起始地址的规律,首先是0000然后0400、0800、0400,再之后1000、1400、1800,最后一直到1FC00,所以地址只要以000、400、800、400,结尾的都一定是页的起始地址,这个稍微记一下。
  3. 启动程序代码就是简介中的系统存储器 用户选择字节就是选项字节

3.基本结构

整个闪存分为程序存储器、系统存储器和选项字节三部分,这里程序存储器为以C8T6为例,它是64K的,所以总共只有64页,最后一页的起始地址是0800FC00,左边这里是闪存存储器接口,手册里还有个名称,闪存编程和擦除控制器LPEC,大家也知道这两个名称其实是一个东西就行,然后这个控制器就是闪存的管理员,他可以对程序存储器进行擦除和编程,也可以对选项字节进行擦除和编程,系统容器是不能擦除和编程的,这个选项字节里面有很大一部分配置位,其实是配置主程序存储器的读写保护的,所以右边画的写入选项字节,可以配置程序存储器的读写保护,当然选项字节还有几个别的配置参数,这个待会再讲,那这就是整个闪存的基本结构。

4.Flash解锁

首先第一步是flash解锁,这和之前W25Q64一样,W25Q64操作之前需要写使能,这个flash操作之前需要解锁,目的都是为了防止误操作,那这里解锁的方式和之前独立看门狗一样,都是通过在键寄存器写入指定的键值来实现,使用键寄存容器的好处就是更能防止误操作,每一个指令必须输密码才能完成,通过英文名称也能看出来,键的英文是KEY,直译是不是钥匙的意思,所以这个更形象的翻译我们可以把它叫做钥匙寄存器,密钥寄存器,首先LPEC共有三个键值,也就是三把开锁的钥匙,RDPRT键是解除读保护的密钥,值是0XA5,KEY1键值是0X45670123,KEY2键值是0XCDEF89AB,为什么是这些值呢,实际上是随便定义的,只要你定义的不是很简单就行,继续看怎么解锁呢,第一个是复位后FPEC被保护,不能写入FLASH_CR,也就是复位后flash默认是锁着的,然后在FLASH_KEYR键寄存器中,先写入KEY1,再写入KEY2解锁,我们找到了锁,这个锁是KEYR寄存器,怎么解呢,要先用K1钥匙解,再用K2钥匙解,最终才能解锁成功,所以这个锁的安全性非常高,有两道锁,即使程序跑飞了,歪打正着正好写入了KEY1,那也难以保证下一次又歪打正着写入了KEY2,所以非人为情况下基本不可能解锁,然后第三条还有进一步的保护措施,就是错误的操作序列会在下次复位前锁死FPEC和FLASH_CR,于是他发现有程序在尝试撬锁时,一旦没有先写入KEY1,再写入KEY2,整个模块就会完全锁死,除非复位,这是整个解锁操作,可以看到安全性非常高,接着继续看,解锁之后如何加锁呢,我们操作完成之后,要尽快把flash重新加锁,以防止意外情况,加锁的操作是设置FLASH_CR中的LOCK位锁住FPEC和FLASH_CR,这个比较简单,就是控制寄存器里面有个LOCK位,我们在这一位写1就能重新锁住闪存

4.使用指针访问存储器

  • 将0x08000000强转成__IO uint16_t *类型 即将该地址强转成uint16_t的指针类型 代表指针变量指向0x08000000的地址 此时再在外面取* 则可以得到数据
  • __IO是stm32中的宏定义 该宏定义对应C语言中的volatile 直译就是易变数据

5.程序存储器全擦除

第一步是读取lock位,看一下芯片锁没锁,下面如果lock位等于1锁住了,就执行解锁过程解锁过程就是在KEYR寄存器先写入KEY1,再写入KEY2,这里如果它当前没锁住,就不用解锁了,这是流程图里给的解锁步骤,如果锁住了就解锁,如果没锁住就不用解锁,但是在库函数中并没有这个判断,库函数是直接执行解锁过程,管你锁没锁都执行解锁,这个比较简单直接,不过效果都一样,然后继续解锁之后,首先置控制寄存器里的MER(Mass Erase)位为1,然后再置STRT(Start)位为1,其中STRT为1是触发条件,STRT为1之后芯片开始干活,然后现在看到MER位是1,它就知道接下来要干的活就是全删除,这样内部电路就会自动执行全擦除的过程,然后继续擦除也是需要花一段时间的,所以擦除过程开始后,程序要执行等待判断状态寄存器的BSY(Busy)位是否为1,BSY位表示芯片是否处于忙状态,BSY位为1表示芯片忙,所以这里如果判断BSY位等于1,就跳转回来继续循环判断,直到BSY位等于0跳出循环,最后一步这里写的是读出并验证所有页的数据,这个是测试程序才要做的,正常情况下全删除完成了,我们默认就成功了,如果还要再全读出来验证一下,这个工作量太大了,所以这里的最后一步我们就不管了,这是全擦除的流程。

6. 程序存储器页擦除

 页擦除,这个也是类似的过程,第一步一样的是解锁的流程,第二步,这个方框里的置控寄存器的PER(Page Erase)位为1,然后在AR(Address Register)地址寄存器中选择要擦除的页,最后置控制寄存器的STRT位为1,也是触发条件,芯片开始干活,然后芯片看到PER等于1,它就知道接下来要执行页擦除,然后闪存不止一页,页擦除芯片就要知道要具体擦哪一页,所以它会继续看AR寄存器的数据,AR寄存器我们要提前写入一个页的起始地址,这样芯片就会把我们指定的一页给擦除掉,然后擦除开始之后,我们也要等待BSY位,最后读出并验证数据,这个就不用看了。

7.程序存储器编程

擦除之后我们就可以执行写入的流程了,另外说明一下,STM32的闪存,在写入之前会检查指定地址有没有擦除,如果没有擦除就写入STM32则不执行写入操作,除非写入的全是0,这个数据是例外,因为不擦除就写入,可能会写入错误,但全写入0的话,写入肯定是没问题的,来看一下流程图,写入的第一步也是解锁,然后第二步我们需要置控制寄存器的PG(Programming)位为1,表示我们即将写入数据第三步就在指定的地址写入半字,这一步我们需要用到刚才说的这句代码,使用指针在指定地址写入数据,想写入什么数据,在这里指定即可,另外这里注意一下,写入操作只能以半字的形式写入,在STM32中有几个术语,字、半字和字节,其中字word就是32位数据,半字half word就是16位数据,字节byte就是8位数据,那这里只能以半字写入,意思就是只能以16位的形式写入,一次性写入两个字节,如果你要写入32位,就分两次完成,如果你只要写入八位,这个就比较麻烦了,如果你想单独写入一个字节,还要保留另一个字节的原始数据的话那只能把整页数据都读到SRAM,再随意修改SRAM数据修改全部完成之后,再把整页都擦除,最后再把整页都写回去,所以如果你想像SRAM一样随心所欲的读写,那最好的办法就先把闪存的一页读到SRAM中,读写完成后再擦除一页,整体写回去,那回到流程图这里,写入数据这个代码就触发开始的条件,不需要像擦除一样置STRT位了,写了半字之后,芯片会处于忙状态,我们等待一下BUSY清0,这样写入数据的过程就完成了,那每执行这样一个流程,只能写入一个半字,如果要写出很多数据,要不断循环调用这个流程就可以了

8.选项字节

这里是对应的16个字节,其中有一半的名称前面都带了个N,比如RDP和nRDP ,USER和nUSER等,这个意思就是你在写入RDP数据时,要同时在NRDP写入数据的反码,其他的这些都是一样,写这个存储器时,要在带N的对应的存储器写入反码,这样写入操作才是有效的,如果芯片检测到这两个存储器不是反码的关系,那就代表数据无效有错误,对应的功能就不执行,这是一个安全保障措施,但这个写入反码的过程,硬件会自动计算并写入,不需要我们操心,使用库函数的话,那就更简单了,函数都给我们分装好了,直接调用函数就行

第一个RDP(Read Protect)是读保护配置位,下面有解释,在RDP存储器写入RDPRT键,就刚才说的A5,然后解除读保护,如果RDP不是A5,那闪存就是读保护状态,无法通过调试器读取程序,避免程序被别人窃取,接着看第二个字节USER,这个是一些零碎的配置位,可以配置硬件看门狗和进入停机待机模式是否产生复位,这个了解即可,然后第三个和第四个字节data0和data1,这个在芯片中没有定义功能,用户可自定义使用,最后四个字节,WRP(Write Protect)0、1、2、3这四个字节配置的是写保护在中容量产品里是每一个位对应保护四个存储页,四个字节(1个字节有8位)总共32位,一位对应保护四页,总共保护32×4等于128页,正好对应中容量量的最大128页,那对于小容量和大容量产品呢,可以看一下手册,2.5选项字节说明这里,对于小容量产品,也是每一位对应保护四个存储页,但小容量产品最大只有32K,所以只需要一个字节WRP0就行,4×8=32,其他三个字节没用到,然而对于大容量产品,每一个位只能保护两个存储页,这样的话四个字节就不够用了,所以这里规定WRP3的最高位,这一位直接把剩下的所有页一起都保护了,这是写保护的定义。

9.选项字节擦除

第一步其实也是解锁闪存,这里文字并没有写,然后第二步这里文字版的流程多了一步,检查SR的BSY位,以确认没有其他正在进行的闪存操作,这个实际上就是事前等待,如果当前已经在忙了,我先等一下,这一步在刚才的流程图里并没有体现,然后下一步解锁CR的OPTWRE(Option Write Enable)位,这一步是选项字节的解锁选项字节里面还有一个单独的锁,在解锁闪存后,还需要再解锁选项字节的锁,之后才能操作选项字节,解锁选项字节的话看一下前面的寄存器(前面闪存模块组织图),整个闪存的锁是KEYR,里面选项字节的小锁是下面的OPTKEYR(Option Key Register),解锁这个小锁也是类似的流程,我们需要在OPTKEYR里先写入KEY1,再写入KEY2,这样就能解锁选项字节的小锁了,然后继续解除小锁之后和之前的擦除类似,先设置CR的OPTER(Option Erase)位为1,表示即将擦除选项字节,之后设置CR的STRT位为1,触发芯片开始干活,这样芯片就会启动擦除选项字节的工作之后等待BUSY位变为0,擦除选项字节就完成了,擦除之后就可以看写入了。

10.选项字节写入

11.器件电子签名

使用指针读指定地址下的存储器,可获取电子签名,电子签名其实就是STM32的id号它的存放区域是系统存储器,它不仅有BOOTLOADER程序,还有几个字节的id号,系统存储器起始地址是1FFFF000,看下这里,这里有两段数据,第一个是闪存容量存储器,基地址是1FFF F7E0,通过地址也可以确定它的位置,就是系统存储器,这个存储器的大小是16位,它的值就是闪存的容量单位是KB,然后第二个是产品唯一身份标识寄存器,就是每个芯片的身份证号,这个数据存放的基地址是1FFFF7E8,大小是96位,每一个芯片的这96位数据都是不一样的,使用这个唯一id号可以做一些加密的操作,比如你想写入一段程序,只能在指定设备运行,那也可以在程序的多处加入id号判断,如果不是指定设备的id号,就不执行程序功能,这样即使你的程序被盗,在别的设备上也难以运行,这是STM32的电子签名。

12.读写内部Flash

12.1 接线图

12.2 相关代码

12.2.1 MyFLASH.c
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:FLASH读取一个32位的字
  * 参    数:Address 要读取数据的字地址
  * 返 回 值:指定地址下的数据
  */
uint32_t MyFLASH_ReadWord(uint32_t Address)
{
	return *((__IO uint32_t *)(Address));	//使用指针访问指定地址下的数据并返回
}

/**
  * 函    数:FLASH读取一个16位的半字
  * 参    数:Address 要读取数据的半字地址
  * 返 回 值:指定地址下的数据
  */
uint16_t MyFLASH_ReadHalfWord(uint32_t Address)
{
	return *((__IO uint16_t *)(Address));	//使用指针访问指定地址下的数据并返回
}

/**
  * 函    数:FLASH读取一个8位的字节
  * 参    数:Address 要读取数据的字节地址
  * 返 回 值:指定地址下的数据
  */
uint8_t MyFLASH_ReadByte(uint32_t Address)
{
	return *((__IO uint8_t *)(Address));	//使用指针访问指定地址下的数据并返回
}

/**
  * 函    数:FLASH全擦除
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,FLASH的所有页都会被擦除,包括程序文件本身,擦除后,程序将不复存在
  */
void MyFLASH_EraseAllPages(void)
{
	FLASH_Unlock();					//解锁
	FLASH_EraseAllPages();			//全擦除
	FLASH_Lock();					//加锁
}

/**
  * 函    数:FLASH页擦除
  * 参    数:PageAddress 要擦除页的页地址
  * 返 回 值:无
  */
void MyFLASH_ErasePage(uint32_t PageAddress)
{
	FLASH_Unlock();					//解锁
	FLASH_ErasePage(PageAddress);	//页擦除
	FLASH_Lock();					//加锁
}

/**
  * 函    数:FLASH编程字
  * 参    数:Address 要写入数据的字地址
  * 参    数:Data 要写入的32位数据
  * 返 回 值:无
  */
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
	FLASH_Unlock();							//解锁
	FLASH_ProgramWord(Address, Data);		//编程字
	FLASH_Lock();							//加锁
}

/**
  * 函    数:FLASH编程半字
  * 参    数:Address 要写入数据的半字地址
  * 参    数:Data 要写入的16位数据
  * 返 回 值:无
  */
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{
	FLASH_Unlock();							//解锁
	FLASH_ProgramHalfWord(Address, Data);	//编程半字
	FLASH_Lock();							//加锁
}
12.2.2 MyFLASH.h
#ifndef __MYFLASH_H
#define __MYFLASH_H

uint32_t MyFLASH_ReadWord(uint32_t Address);
uint16_t MyFLASH_ReadHalfWord(uint32_t Address);
uint8_t MyFLASH_ReadByte(uint32_t Address);

void MyFLASH_EraseAllPages(void);
void MyFLASH_ErasePage(uint32_t PageAddress);

void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data);
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);

#endif
12.2.3 Store.c
#include "stm32f10x.h"                  // Device header
#include "MyFLASH.h"

#define STORE_START_ADDRESS		0x0800FC00		//存储的起始地址
#define STORE_COUNT				512				//存储数据的个数

uint16_t Store_Data[STORE_COUNT];				//定义SRAM数组

/**
  * 函    数:参数存储模块初始化
  * 参    数:无
  * 返 回 值:无
  */
void Store_Init(void)
{
	/*判断是不是第一次使用*/
	if (MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5)	//读取第一个半字的标志位,if成立,则执行第一次使用的初始化
	{
		MyFLASH_ErasePage(STORE_START_ADDRESS);					//擦除指定页
		MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5);	//在第一个半字写入自己规定的标志位,用于判断是不是第一次使用
		for (uint16_t i = 1; i < STORE_COUNT; i ++)				//循环STORE_COUNT次,除了第一个标志位
		{
			MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000);		//除了标志位的有效数据全部清0
		}
	}
	
	/*上电时,将闪存数据加载回SRAM数组,实现SRAM数组的掉电不丢失*/
	for (uint16_t i = 0; i < STORE_COUNT; i ++)					//循环STORE_COUNT次,包括第一个标志位
	{
		Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2);		//将闪存的数据加载回SRAM数组
	}
}

/**
  * 函    数:参数存储模块保存数据到闪存
  * 参    数:无
  * 返 回 值:无
  */
void Store_Save(void)
{
	MyFLASH_ErasePage(STORE_START_ADDRESS);				//擦除指定页
	for (uint16_t i = 0; i < STORE_COUNT; i ++)			//循环STORE_COUNT次,包括第一个标志位
	{
		MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, Store_Data[i]);	//将SRAM数组的数据备份保存到闪存
	}
}

/**
  * 函    数:参数存储模块将所有有效数据清0
  * 参    数:无
  * 返 回 值:无
  */
void Store_Clear(void)
{
	for (uint16_t i = 1; i < STORE_COUNT; i ++)			//循环STORE_COUNT次,除了第一个标志位
	{
		Store_Data[i] = 0x0000;							//SRAM数组有效数据清0
	}
	Store_Save();										//保存数据到闪存
}
12.2.4 Strore.h
#ifndef __STORE_H
#define __STORE_H

extern uint16_t Store_Data[];

void Store_Init(void);
void Store_Save(void);
void Store_Clear(void);

#endif
12.2.5 main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Store.h"
#include "Key.h"

uint8_t KeyNum;					//定义用于接收按键键码的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();				//OLED初始化
	Key_Init();					//按键初始化
	Store_Init();				//参数存储模块初始化,在上电的时候将闪存的数据加载回Store_Data,实现掉电不丢失
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Flag:");
	OLED_ShowString(2, 1, "Data:");
	
	while (1)
	{
		KeyNum = Key_GetNum();		//获取按键键码
		
		if (KeyNum == 1)			//按键1按下
		{
			Store_Data[1] ++;		//变换测试数据
			Store_Data[2] += 2;
			Store_Data[3] += 3;
			Store_Data[4] += 4;
			Store_Save();			//将Store_Data的数据备份保存到闪存,实现掉电不丢失
		}
		
		if (KeyNum == 2)			//按键2按下
		{
			Store_Clear();			//将Store_Data的数据全部清0
		}
		
		OLED_ShowHexNum(1, 6, Store_Data[0], 4);	//显示Store_Data的第一位标志位
		OLED_ShowHexNum(3, 1, Store_Data[1], 4);	//显示Store_Data的有效存储数据
		OLED_ShowHexNum(3, 6, Store_Data[2], 4);
		OLED_ShowHexNum(4, 1, Store_Data[3], 4);
		OLED_ShowHexNum(4, 6, Store_Data[4], 4);
	}
}

现象:Flag显示闪存的第一个标志位 Data中的数据 每按下一次按键 第一个数据自增1 第二个数据自增2 依次类推 自增后调用Store_Save 将数据全部存储到闪存中 于是每次上电后 函数初始化会将闪存中的数据读取到数组中并显示在OLED屏 实现掉电不丢失

13.读取芯片ID

13.1 接线图

13.2 相关代码

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"

int main(void)
{
	OLED_Init();						//OLED初始化
	
	OLED_ShowString(1, 1, "F_SIZE:");	//显示静态字符串
	OLED_ShowHexNum(1, 8, *((__IO uint16_t *)(0x1FFFF7E0)), 4);		//使用指针读取指定地址下的闪存容量寄存器
	
	OLED_ShowString(2, 1, "U_ID:");		//显示静态字符串
	OLED_ShowHexNum(2, 6, *((__IO uint16_t *)(0x1FFFF7E8)), 4);		//使用指针读取指定地址下的产品唯一身份标识寄存器
	OLED_ShowHexNum(2, 11, *((__IO uint16_t *)(0x1FFFF7E8 + 0x02)), 4);
	OLED_ShowHexNum(3, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x04)), 8);
	OLED_ShowHexNum(4, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x08)), 8);
	
	while (1)
	{
		
	}
}

 

现象:显示屏第二行以16位半字显示地址 低端先行 所以从左到右是001E 002C 第三行以32位显示则为30313432 第四行也是32位 即42313130

  • 39
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
STM32标准库提供了一些函数来实现SPI和Flash的操作。 要使用SPI,需要初始化SPI外设,设置传输模式、数据位数、时钟频率等参数。然后使用SPI传输函数来发送和接收数据。 以下是一个使用STM32标准库SPI的示例代码: ```c #include "stm32f1xx.h" void SPI_Init(void) { // 初始化GPIO GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); 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; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化SPI SPI_HandleTypeDef SPI_InitStruct = {0}; __HAL_RCC_SPI1_CLK_ENABLE(); SPI_InitStruct.Instance = SPI1; SPI_InitStruct.Mode = SPI_MODE_MASTER; SPI_InitStruct.Direction = SPI_DIRECTION_2LINES; SPI_InitStruct.DataSize = SPI_DATASIZE_8BIT; SPI_InitStruct.CLKPolarity = SPI_POLARITY_LOW; SPI_InitStruct.CLKPhase = SPI_PHASE_1EDGE; SPI_InitStruct.NSS = SPI_NSS_SOFT; SPI_InitStruct.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; SPI_InitStruct.FirstBit = SPI_FIRSTBIT_MSB; SPI_InitStruct.TIMode = SPI_TIMODE_DISABLE; SPI_InitStruct.CRCCalculation = SPI_CRCCALCULATION_DISABLE; SPI_InitStruct.CRCPolynomial = 10; HAL_SPI_Init(&SPI_InitStruct); } void SPI_Transmit(uint8_t* data, uint16_t size) { HAL_SPI_Transmit(&hspi1, data, size, HAL_MAX_DELAY); } void SPI_Receive(uint8_t* data, uint16_t size) { HAL_SPI_Receive(&hspi1, data, size, HAL_MAX_DELAY); } ``` 要使用Flash,需要先初始化Flash,然后使用Flash擦除函数和编程函数来擦除和编程Flash。以下是一个使用STM32标准库Flash的示例代码: ```c #include "stm32f1xx.h" void FLASH_Init(void) { // 解锁Flash HAL_FLASH_Unlock(); // 设置Flash编程时间 __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_2); // 禁止缓存和预取指 __HAL_FLASH_PREFETCH_BUFFER_DISABLE(); __HAL_FLASH_INSTRUCTION_CACHE_DISABLE(); __HAL_FLASH_DATA_CACHE_DISABLE(); // 锁定Flash HAL_FLASH_Lock(); } void FLASH_Erase(uint32_t address) { // 解锁Flash HAL_FLASH_Unlock(); // 擦除Flash扇区 FLASH_Erase_Sector(FLASH_SECTOR_2, FLASH_VOLTAGE_RANGE_3); // 锁定Flash HAL_FLASH_Lock(); } void FLASH_Write(uint32_t address, uint8_t* data, uint16_t size) { // 解锁Flash HAL_FLASH_Unlock(); // 编程Flash for (uint16_t i = 0; i < size; i += 2) { uint16_t value = (data[i + 1] << 8) | data[i]; HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, address + i, value); } // 锁定Flash HAL_FLASH_Lock(); } ``` 这些示例代码仅供参考,实际使用时需要根据具体的硬件和应用场景进行适当的修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值