http://www.openedv.com/docs/index.html
这个是正点原子开发板的下载资料,您可以到这个网站下载STM32F103精英板资料,拿到具体例程(实验32 FLASH模拟EEPROM实验)。
此例程是基于STM32F103精英板(标准库)进行开发,对STM32内部的FLASH进行读写操作。
通过main函数,我们来解析这个函数的目的
#include "led.h" #include "delay.h" #include "key.h" #include "sys.h" #include "lcd.h" #include "usart.h" #include "stmflash.h" /************************************************ ALIENTEK精英STM32开发板实验32 FLASH模拟EEPROM 实验 技术支持:www.openedv.com 淘宝店铺:http://eboard.taobao.com 关注微信公众平台微信号:"正点原子",免费获取STM32资料。 广州市星翼电子科技有限公司 作者:正点原子 @ALIENTEK ************************************************/ //要写入到STM32 FLASH的字符串数组 const u8 TEXT_Buffer[]={"22STM32FLASH_TEXT22"}; #define SIZE sizeof(TEXT_Buffer) //数组长度 #define FLASH_SAVE_ADDR 0X08070000 //设置FLASH 保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小+0X08000000) int main(void) { u8 key; u16 i=0; u8 datatemp[SIZE]; delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级 uart_init(115200); //串口初始化为115200 LED_Init(); //初始化与LED连接的硬件接口 KEY_Init(); //初始化按键 LCD_Init(); //初始化LCD POINT_COLOR=RED; //设置字体为红色 LCD_ShowString(30,50,200,16,16,"ELITE STM32"); LCD_ShowString(30,70,200,16,16,"FLASH EEPROM TEST"); LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK"); LCD_ShowString(30,110,200,16,16,"2015/1/18"); LCD_ShowString(30,130,200,16,16,"KEY1:Write KEY0:Read"); while(1) { key=KEY_Scan(0); if(key==KEY0_PRES) { LCD_Fill(0,170,239,400,WHITE); LCD_ShowString(30,170,200,16,16,"The Data Readed Is:"); STMFLASH_Read(FLASH_SAVE_ADDR,(u16 *)datatemp,SIZE); LCD_ShowString(30,190,200,16,16,datatemp); } else if(key==KEY1_PRES) { LCD_Fill(0,170,239,400,WHITE); LCD_ShowString(30,170,200,16,16,"FLASH Write Finished!"); STMFLASH_Write(FLASH_SAVE_ADDR,(u16 *)TEXT_Buffer,SIZE); } i++; delay_ms(10); if(i==20) { LED0=!LED0;//提示系统正在运行 i=0; } } }
mian函数大体是对各种外设进行初始化:
uart_init(115200); //串口初始化为115200 =========〉通过串口1把数据传输到LCD,LCD才能正常显示 LED_Init(); //初始化与LED连接的硬件接口 =========〉对流水灯轮流亮灭,确认工程是否在正常工作 KEY_Init(); //初始化按键 ==========>按键初始化,当某一按键按下时,执行对应操作 LCD_Init(); //初始化LCD ==========>对LCD屏幕初始化,可以人机界面交流
如mian函数中红色部分,按键0(KEY0)按下对FLASH进行读取操作,并通过LCD屏幕显示出来“The Data Readed Is: 11STM32FLASH_TEXT11”
按键1(KEY1)按下时对FLASH进行写入操作,并通过LCD显示“FLASH Write Finished!”
接下来就要来到今天的重头戏了,FLASH如何实现读写?
其中最重要的是STMFLASH_Read和STMFLASH_Write这两个函数。
提到FLASH的读写函数,我把读写函数的流程通过一个图说明一下
我们通过具体的代码来分析,其中注释基本已经清晰明了地说明了
#include "stmflash.h" /* 函数名:STMFLASH_ReadHalfWord 功能: 读取半字底层函数 输入: (u32*)fladdr(地址) 返回: (u16) (数据) */ u16 STMFLASH_ReadHalfWord(u32 fladdr) { return *(u32*)fladdr; } /* 函数名:STMFLASH_WriteNoCheck 功能: 利用库函数(写半字进FLASH的底层函数)对多个数据进行写入 输入: (u32*)fladdr(写入地址) (u16*)WriteBuf(要写入的数据) (u16)NumToWrite(写入数据的个数) 返回: NULL */ //写入多个数据进入Flash //这个FLAS有512K字节,但是这里把一次最多读取限制为(u16 0xFFFFFFFF=65536字节=64K字节) void STMFLASH_WriteNoCheck(u32 fladdr,u16* Write_Buf,u16 NumToWrite) { u16 i; for(i=0;i<NumToWrite;i++) { FLASH_ProgramHalfWord(fladdr,Write_Buf[i]); fladdr+=2; } } /* 函数名:STMFLASH_Read 功能: 对多个数据进行读取 输入: (u32*)fladdr(读取地址) (u16*)WriteBuf(要读取的数据) (u16)NumToWrite(读取数据的个数) 返回: NULL */ void STMFLASH_Read(u32 fladdr,u16* Read_Buf,u16 NumToRead) { u16 i; for(i=0;i<NumToRead;i++) { Read_Buf[i]=STMFLASH_ReadHalfWord(fladdr); fladdr+=2; } } /* 函数名:STMFLASH_Write 功能: 分扇区对Flash进行写入操作 输入: (u32*)fladdr(写入地址) (u16*)WriteBuf(要写入的数据) (u16)NumToWrite(写入数据的个数) 返回: NULL 注意: 每次进行对Flash操作之间要进行解锁操作,之后再上锁,不然会造成数据写入失败 */ #define STMFLASH_Sector_Size 2048 u16 FLASH_BUF[STMFLASH_Sector_Size/2]; void STMFLASH_Write(u32 fladdr,u16* Write_Buf,u16 NumToWrite) { u16 i; u32 offaddr; u16 secpos,secoff,secremain; offaddr=fladdr-STM32_FLASH_BASE; //去掉基地址之后,得出偏移量 secpos=offaddr/STMFLASH_Sector_Size; //偏移量/扇区大小=当前所在扇区块 secoff=(fladdr%STMFLASH_Sector_Size)/2; //(当前扇区区域)已经写入的次数 secremain=STMFLASH_Sector_Size/2-secoff; //(扇区剩余区域)可写的次数 if(secremain>=NumToWrite) secremain=NumToWrite; FLASH_Unlock(); //解锁 while(1) { STMFLASH_WriteNoCheck(secpos*STMFLASH_Sector_Size+STM32_FLASH_BASE,FLASH_BUF,STMFLASH_Sector_Size/2);//读取要写入的扇区的数据 for(i=0;i<secremain;i++) //判断该扇区的空余区域是否都是空的 { if(FLASH_BUF[i+secoff]!=0xFFFF) break; } if(i<secremain) //非空情况,要先擦除,在进行写入 { FLASH_ErasePage(secpos*STMFLASH_Sector_Size+STM32_FLASH_BASE); for(i=0;i<secremain;i++) { FLASH_BUF[secoff+i]=Write_Buf[i]; } STMFLASH_WriteNoCheck(secpos*STMFLASH_Sector_Size+STM32_FLASH_BASE,FLASH_BUF,secremain); } else { STMFLASH_WriteNoCheck(fladdr,Write_Buf,NumToWrite); } if(secremain==NumToWrite) break; else //假如一个扇区还写不完,则进入下一个扇区 { secpos++; //扇区加一,可写入的次数清零 secoff=0; fladdr+=(secremain*2); //写入地址要加上已经写过的地址偏移量 Write_Buf+=secremain; //数组的起始地址要加上已经写入的次数 NumToWrite-=secremain; //写入的个数要减去已经写入的次数 if(NumToWrite>=1024) secremain=1024; //本个扇区要写入的次数 else secremain=NumToWrite; } } FLASH_Lock();//上锁 }
这上面毕竟值得注意的是在对内部FLASH进行写入操作时要记得解锁FLASH_Unlock();,写完之后要记得上锁FLASH_Lock();
接着必须说明一下库函数FLASH_ErasePage
if(status == FLASH_COMPLETE) { /* if the previous operation is completed, proceed to erase the page */ FLASH->CR|= CR_PER_Set; //PER:页擦除选择擦除页。 FLASH->AR = Page_Address; //当进行页擦除时,通过AR设置要擦除的页地址 FLASH->CR|= CR_STRT_Set; //STRT:开始当该位为’1’时将触发一次擦除操作。该位只可由软件置为’1’并在BSY变为’1’时清为’0’。 /* Wait for last operation to be completed */ status = FLASH_WaitForLastBank1Operation(EraseTimeout); //延时等待擦除操作完成 /* Disable the PER Bit */ FLASH->CR &= CR_PER_Reset; }
其中值得一提的是FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)函数
该函数也是官方给出的,我们只需要用就好了。但要注意,这个是个半字的写操作,即uint16_t 的数据算半字呢,
因为单片机是32的,对于32位单片机系统来说,一个字是4个字节的,8位的比如51单片机系统一个字就是2位的,
64位单片机系统一个字就是8个字节,脱离单片机系统说字是多少个字节是没意义的。所以这里写入/读出半字也就是一次写入2个字节,写完/读出一次地址会加2。
if(Address < FLASH_BANK1_END_ADDRESS) { if(status == FLASH_COMPLETE) { /* if the previous operation is completed, proceed to program the new data */ FLASH->CR |= CR_PG_Set; //PG:编程选择编程操作。 *(__IO uint16_t*)Address = Data;//对写入地址进行赋值 /* Wait for last operation to be completed */ status = FLASH_WaitForLastBank1Operation(ProgramTimeout); //延时等待操作完成 /* Disable the PG Bit */ FLASH->CR &= CR_PG_Reset; //把PG位清零,即停止编写操作 } }
在下面将插入一张FLASH->CR(内存控制寄存器)的各个位的作用,各位请参考一下。