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(內存控制寄存器)的各個位的作用,各位請參考一下。