完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第71章 STM32H7的內部Flash應用之模擬EEPROM
本章節爲大家講解STM32H7的內部Flash模擬EEPROM,主要應用到板子沒有外置EERPOM的場合,而且H7的內部Flash比較大,可以開闢一個扇區用於模擬EEPROM。
目錄
第71章 STM32H7的內部Flash應用之模擬EEPROM
71.3 模擬EEPROM板級支持包(bsp_cpu_flash.c)
71.1 初學者重要提示
- 學習本章節前,務必優先學習第70章。
- 使用內部Flash模擬EEPROM,務必告訴編譯要使用的存儲空間,防止這個空間存入了程序。
- STM32H7的Flash編程時,務必保證要編程的地址是32字節對齊的,即此地址對32求餘爲0。並且編程的數據必須32字節整數倍。
- STM32H743XI有兩個獨立的BANK,一個BANK的編程和擦除操作對另一個BANK沒有任何影響。但是用戶應用程序和要擦寫的Flash扇區在同一個BANK,在執行擦寫操作時,應用應用程序將停止運行,包括中斷服務程序。
- 使用內部Flash模擬EEPROM要做到先擦除後使用。
71.2 模擬EEPROM驅動設計
這裏重點把內部Flash的讀取,編程和擦除做個說明。
71.2.1 內部Flash擦除的實現
內部Flash的擦除思路如下:
- 第1步,獲取擦除地址所處的扇區。
- 第2步,調用函數HAL_FLASH_Unlock解鎖。
- 第3步,調用函數HAL_FLASHEx_Erase擦除一個扇區。
- 第4步,調用函數HAL_FLASH_Lock上鎖。
按照這個思路,程序實現如下:
1. /* 2. ****************************************************************************************************** 3. * 函 數 名: bsp_EraseCpuFlash 4. * 功能說明: 擦除CPU FLASH一個扇區 (128KB) 5. * 形 參: _ulFlashAddr : Flash地址 6. * 返 回 值: 0 成功, 1 失敗 7. * HAL_OK = 0x00, 8. * HAL_ERROR = 0x01, 9. * HAL_BUSY = 0x02, 10. * HAL_TIMEOUT = 0x03 11. * 12. ****************************************************************************************************** 13. */ 14. uint8_t bsp_EraseCpuFlash(uint32_t _ulFlashAddr) 15. { 16. uint32_t FirstSector = 0, NbOfSectors = 0; 17. FLASH_EraseInitTypeDef EraseInitStruct; 18. uint32_t SECTORError = 0; 19. uint8_t re; 20. 21. /* 解鎖 */ 22. HAL_FLASH_Unlock(); 23. 24. /* 獲取此地址所在的扇區 */ 25. FirstSector = bsp_GetSector(_ulFlashAddr); 26. 27. /* 固定1個扇區 */ 28. NbOfSectors = 1; 29. 30. /* 擦除扇區配置 */ 31. EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS; 32. EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3; 33. 34. if (_ulFlashAddr >= ADDR_FLASH_SECTOR_0_BANK2) 35. { 36. EraseInitStruct.Banks = FLASH_BANK_2; 37. } 38. else 39. { 40. EraseInitStruct.Banks = FLASH_BANK_1; 41. } 42. 43. EraseInitStruct.Sector = FirstSector; 44. EraseInitStruct.NbSectors = NbOfSectors; 45. 46. /* 扇區擦除 */ 47. re = HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError); 48. 49. /* 擦除完畢後,上鎖 */ 50. HAL_FLASH_Lock(); 51. 52. return re; 53. }
這裏將此程序設計的關鍵點爲大家做個說明:
- 第25行函數是通過函數bsp_GetSector獲取要擦除地址所處的扇區。這個函數的實現比較簡單,代碼如下:
/* ********************************************************************************************************* * 函 數 名: bsp_GetSector * 功能說明: 根據地址計算扇區首地址 * 形 參: 無 * 返 回 值: 扇區號(0-7) ********************************************************************************************************* */ uint32_t bsp_GetSector(uint32_t Address) { uint32_t sector = 0; if (((Address < ADDR_FLASH_SECTOR_1_BANK1) && (Address >= ADDR_FLASH_SECTOR_0_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_1_BANK2) && (Address >= ADDR_FLASH_SECTOR_0_BANK2))) { sector = FLASH_SECTOR_0; } else if (((Address < ADDR_FLASH_SECTOR_2_BANK1) && (Address >= ADDR_FLASH_SECTOR_1_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_2_BANK2) && (Address >= ADDR_FLASH_SECTOR_1_BANK2))) { sector = FLASH_SECTOR_1; } else if (((Address < ADDR_FLASH_SECTOR_3_BANK1) && (Address >= ADDR_FLASH_SECTOR_2_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_3_BANK2) && (Address >= ADDR_FLASH_SECTOR_2_BANK2))) { sector = FLASH_SECTOR_2; } else if (((Address < ADDR_FLASH_SECTOR_4_BANK1) && (Address >= ADDR_FLASH_SECTOR_3_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_4_BANK2) && (Address >= ADDR_FLASH_SECTOR_3_BANK2))) { sector = FLASH_SECTOR_3; } else if (((Address < ADDR_FLASH_SECTOR_5_BANK1) && (Address >= ADDR_FLASH_SECTOR_4_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_5_BANK2) && (Address >= ADDR_FLASH_SECTOR_4_BANK2))) { sector = FLASH_SECTOR_4; } else if (((Address < ADDR_FLASH_SECTOR_6_BANK1) && (Address >= ADDR_FLASH_SECTOR_5_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_6_BANK2) && (Address >= ADDR_FLASH_SECTOR_5_BANK2))) { sector = FLASH_SECTOR_5; } else if (((Address < ADDR_FLASH_SECTOR_7_BANK1) && (Address >= ADDR_FLASH_SECTOR_6_BANK1)) || \ ((Address < ADDR_FLASH_SECTOR_7_BANK2) && (Address >= ADDR_FLASH_SECTOR_6_BANK2))) { sector = FLASH_SECTOR_6; } else if (((Address < ADDR_FLASH_SECTOR_0_BANK2) && (Address >= ADDR_FLASH_SECTOR_7_BANK1)) || \ ((Address < CPU_FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_7_BANK2))) { sector = FLASH_SECTOR_7; } else { sector = FLASH_SECTOR_7; } return sector; }
由於STM32H7的BANK1和BANK2是獨立的,都有8個扇區,所以程序裏面只需返回要擦除地址所處的扇區號即可。
- 第47行的擦除函數HAL_FLASHEx_Erase在第70章的4.4小節有說明。
71.2.2 內部Flash編程的實現
內部Flash的編程思路如下:
- 第1步,判斷是否要編寫數據進去,如果數據已經在內部Flash裏面。
- 第2步,調用函數HAL_FLASH_Unlock解鎖。
- 第3步,調用函數HAL_FLASH_Program對內部Flash編程數據。
- 第4步,調用函數HAL_FLASH_Lock上鎖。
按照這個思路,程序實現如下:
1. /* 2. ****************************************************************************************************** 3. * 函 數 名: bsp_WriteCpuFlash 4. * 功能說明: 寫數據到CPU 內部Flash。 必須按32字節整數倍寫。不支持跨扇區。扇區大小128KB. \ 5. * 寫之前需要擦除扇區. 長度不是32字節整數倍時,最後幾個字節末尾補0寫入. 6. * 形 參: _ulFlashAddr : Flash地址 7. * _ucpSrc : 數據緩衝區 8. * _ulSize : 數據大小(單位是字節, 必須是32字節整數倍) 9. * 返 回 值: 0-成功,1-數據長度或地址溢出,2-寫Flash出錯(估計Flash壽命到) 10. ****************************************************************************************************** 11. */ 12. uint8_t bsp_WriteCpuFlash(uint32_t _ulFlashAddr, uint8_t *_ucpSrc, uint32_t _ulSize) 13. { 14. uint32_t i; 15. uint8_t ucRet; 16. 17. /* 如果偏移地址超過芯片容量,則不改寫輸出緩衝區 */ 18. if (_ulFlashAddr + _ulSize > CPU_FLASH_BASE_ADDR + CPU_FLASH_SIZE) 19. { 20. return 1; 21. } 22. 23. /* 長度爲0時不繼續操作 */ 24. if (_ulSize == 0) 25. { 26. return 0; 27. } 28. 29. ucRet = bsp_CmpCpuFlash(_ulFlashAddr, _ucpSrc, _ulSize); 30. 31. if (ucRet == FLASH_IS_EQU) 32. { 33. return 0; 34. } 35. 36. __set_PRIMASK(1); /* 關中斷 */ 37. 38. /* FLASH 解鎖 */ 39. HAL_FLASH_Unlock(); 40. 41. for (i = 0; i < _ulSize / 32; i++) 42. { 43. uint64_t FlashWord[4]; 44. 45. memcpy((char *)FlashWord, _ucpSrc, 32); 46. _ucpSrc += 32; 47. 48. if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, _ulFlashAddr, 49. (uint64_t)((uint32_t)FlashWord)) == HAL_OK) 50. { 51. _ulFlashAddr = _ulFlashAddr + 32; /* 遞增,操作下一個256bit */ 52. } 53. else 54. { 55. goto err; 56. } 57. } 58. 59. /* 長度不是32字節整數倍 */ 60. if (_ulSize % 32) 61. { 62. uint64_t FlashWord[4]; 63. 64. FlashWord[0] = 0; 65. FlashWord[1] = 0; 66. FlashWord[2] = 0; 67. FlashWord[3] = 0; 68. memcpy((char *)FlashWord, _ucpSrc, _ulSize % 32); 69. if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, _ulFlashAddr, 70. (uint64_t)((uint32_t)FlashWord)) == HAL_OK) 71. { 72. ; // _ulFlashAddr = _ulFlashAddr + 32; 73. 74. } 75. else 76. { 77. goto err; 78. } 79. } 80. 81. /* Flash 加鎖,禁止寫Flash控制寄存器 */ 82. HAL_FLASH_Lock(); 83. 84. __set_PRIMASK(0); /* 開中斷 */ 85. 86. return 0; 87. 88. err: 89. /* Flash 加鎖,禁止寫Flash控制寄存器 */ 90. HAL_FLASH_Lock(); 91. 92. __set_PRIMASK(0); /* 開中斷 */ 93. 94. return 1; 95. }
關於此函數有幾個要點:
- 第1個參數必須32字節對齊,即要編程的Flash地址對32求餘爲0。
- 第3個參數必須是32字節的整數倍,長度不是32字節整數倍時,最後幾個字節補0寫入。
- 第29行,函數bsp_CmpCpuFlash放在這裏只有一個作用,判斷將要寫入的數據是否已經在內部Flash存在,如果已經存在,無需重複寫入,直接返回。
- 第36行,做了一個關中斷操作,這裏有個知識點要給大家普及下,由於H7的BANK1和BANK2是獨立的,當前正在擦除的扇區所處的BANK會停止所有在此BANK執行的程序,包含中斷也會停止執行。比如當前的應用程序都在BANK1,如果要擦除或者編程BANK2,是不會不影響BANK1裏面執行的程序。這裏安全起見加上了開關中斷。
- 第41到57行,先將32字節整數倍的數據通過函數HAL_FLASH_Program編程,此函數每次可以固定編程32字節數據。
- 第60到79行,將剩餘不足32字節的數據補0,湊齊32字節編程。
71.2.3 內部Flash讀取的實現
內部Flash數據讀取比較簡單,採用總線方式讀取,跟訪問內部RAM是一樣的。比如要讀取地址
0x08100000裏面的一個32bit變量,我們就可以:
變量 = *(uint32_t *)(0x08100000)
爲了方便起見,也專門準備了一個函數:
/* ********************************************************************************************************* * 函 數 名: bsp_ReadCpuFlash * 功能說明: 讀取CPU Flash的內容 * 形 參: _ucpDst : 目標緩衝區 * _ulFlashAddr : 起始地址 * _ulSize : 數據大小(單位是字節) * 返 回 值: 0=成功,1=失敗 ********************************************************************************************************* */ uint8_t bsp_ReadCpuFlash(uint32_t _ulFlashAddr, uint8_t *_ucpDst, uint32_t _ulSize) { uint32_t i; if (_ulFlashAddr + _ulSize > CPU_FLASH_BASE_ADDR + CPU_FLASH_SIZE) { return 1; } /* 長度爲0時不繼續操作,否則起始地址爲奇地址會出錯 */ if (_ulSize == 0) { return 1; } for (i = 0; i < _ulSize; i++) { *_ucpDst++ = *(uint8_t *)_ulFlashAddr++; } return 0; }
71.2.4 告訴編譯器使用的扇區(重要)
使用內部Flash模擬EEPROM切不可隨意定義一個扇區使用。因爲編譯器並不知道用戶使用了這個扇區,導致應用程序也會編程到此扇區裏面,所以就需要告訴編譯器。
告訴MDK的方法如下(0x0810 0000是H7的BANK2首地址):
const uint8_t para_flash_area[128*1024] __attribute__((at(0x08100000)));
告訴IAR的方法如下:
#pragma location=0x08100000 const uint8_t para_flash_area[128*1024];
這裏有兩點特別注意:
- 模擬EEPROM的扇區可以定義到從第2個扇區開始的任何扇區,但不可以定義到首扇區,因爲這個扇區是默認的boot啓動地址。
- 如果應用程序不大的話,不推薦定義到末尾扇區,以MDK爲例,定義到末尾扇區後,會導致整個Flash空間都被使用,從而讓程序下載下載時間變長。
71.3 模擬EEPROM板級支持包(bsp_cpu_flash.c)
模擬EEPROM的驅動文件bsp_cpu_flash.c主要實現瞭如下幾個API供用戶調用:
- bsp_GetSector
- bsp_ReadCpuFlash
- bsp_CmpCpuFlash
- bsp_EraseCpuFlash
- bsp_WriteCpuFlash
71.3.1 函數bsp_GetSector
函數原型:
uint32_t bsp_GetSector(uint32_t Address)
函數描述:
此函數主要用於獲取給定地址所處的扇區。
函數參數:
- 第1個參數是用戶給定的地址。
- 返回值,範圍FLASH_SECTOR_0到FLASH_SECTOR_7。
71.3.2 函數bsp_ReadCpuFlash
函數原型:
uint8_t bsp_ReadCpuFlash(uint32_t _ulFlashAddr, uint8_t *_ucpDst, uint32_t _ulSize)
函數描述:
此函數用於從內部Flash讀取數據
函數參數:
- 第1個參數讀取的起始地址。
- 第2個參數是讀取數據的存儲地址
- 第3個參數是讀取數據的大小,單位字節。
- 返回值,0表示成功,1表示失敗。
71.3.3 函數bsp_CmpCpuFlash
函數原型:
uint8_t bsp_CmpCpuFlash(uint32_t _ulFlashAddr, uint8_t *_ucpBuf, uint32_t _ulSize)
函數描述:
要編程的數據是否在內部Flash已經存在。
函數參數:
- 第1個參數是內部Flash地址。
- 第2個參數是緩衝區地址。
- 第3個參數是數據大小,單位字節。
- 返回值:
FLASH_IS_EQU 0 Flash內容和待寫入的數據相等,不需要擦除和寫操作。
FLASH_REQ_WRITE 1 Flash不需要擦除,直接寫。
FLASH_REQ_ERASE 2 Flash需要先擦除,再寫。
FLASH_PARAM_ERR 3 函數參數錯誤。
71.3.4 函數bsp_EraseCpuFlash
函數原型:
uint8_t bsp_EraseCpuFlash(uint32_t _ulFlashAddr)
函數描述:
此函數用於擦除一個扇區,大小128KB
函數參數:
- 第1個參數要擦除的扇區地址,可以是此扇區範圍內的任意值,一般填扇區首地址即可。
- 返回值:
HAL_OK = 0x00
HAL_ERROR = 0x01
HAL_BUSY = 0x02
HAL_TIMEOUT = 0x03
71.3.5 函數bsp_WriteCpuFlash
函數原型:
uint8_t bsp_WriteCpuFlash(uint32_t _ulFlashAddr, uint8_t *_ucpSrc, uint32_t _ulSize)
函數描述:
此函數用於編程數據到內部Flash。
函數參數:
- 第1個參數是要編程的內部Flash地址。
- 第2個參數是數據緩衝區地址。
- 第3個參數是數據大小,單位字節。
- 返回值,0-成功,1-數據長度或地址溢出,2-寫Flash出錯(估計Flash壽命到)。
注意事項:
- 第1個參數必須32字節對齊,即要編程的Flash地址對32求餘爲0。
- 第3個參數必須是32字節的整數倍,長度不是32字節整數倍時,此函數會將幾個字節補0寫入
71.4 模擬EEPROM驅動移植和使用
模擬EEPROM移植步驟如下:
- 第1步:複製bsp_cpu_flash.c和bsp_cpu_flash.h到自己的工程目錄,並添加到工程裏面。
- 第2步:Flash驅動文件主要用到HAL庫的Flash驅動文件,簡單省事些可以添加所有HAL庫C源文件進來。
- 第3步,應用方法看本章節配套例子即可。
71.5 實驗例程設計框架
通過程序設計框架,讓大家先對配套例程有一個全面的認識,然後再理解細節,本次實驗例程的設計框架如下:
第1階段,上電啓動階段:
- 這部分在第14章進行了詳細說明。
第2階段,進入main函數:
- 第1部分,硬件初始化,主要是MPU,Cache,HAL庫,系統時鐘,滴答定時器和LED。
- 第2部分,應用程序設計部分,實現內部Flash模擬EEPROM。
71.6 實驗例程說明(MDK)
配套例子:
V7-049_內部Flash模擬EEPROM
實驗目的:
- 學習內部Flash模擬EEPROM。
實驗內容:
- 使用內部Flash模擬EEPROM,務必告訴編譯要使用的存儲空間,防止這個空間存入了程序。
- 對於同一個地址空間,僅支持一次編程(不推薦二次編程,即使是將相應bit由數值1編程0)。
- 只能對已經擦除的空間做編程,擦除1個扇區是128KB。
- H7的Flash編程時,務必保證要編程的地址是32字節對齊的,即此地址對32求餘爲0。
並且編程的數據必須32字節整數倍,函數bsp_WriteCpuFlash對字節數不夠32字節整數倍的情況自動補0。
實驗操作:
- K1鍵按下,將8bit,16bit和32bit數據寫入到內部Flash。
- K2鍵按下,將結構體數據寫入到內部Flash。
上電後串口打印的信息:
波特率 115200,數據位 8,奇偶校驗位無,停止位 1。
程序設計:
系統棧大小分配:
RAM空間用的DTCM:
硬件外設初始化
硬件外設的初始化是在 bsp.c 文件實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鐘: - 調用函數HAL_InitTick,初始化滴答時鐘中斷1ms。 - 設置NVIV優先級分組爲4。 */ HAL_Init(); /* 配置系統時鐘到400MHz - 切換使用HSE。 - 此函數會更新全局變量SystemCoreClock,並重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用於代碼執行時間測量,MDK5.25及其以上版本才支持,IAR不支持。 - 默認不開啓,如果要使能此選項,務必看V7開發板用戶手冊第xx章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder並開啓 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitDWT(); /* 初始化DWT時鐘週期計數器 */ bsp_InitKey(); /* 按鍵初始化,要放在滴答定時器之前,因爲按鈕檢測是通過滴答定時器掃描 */ bsp_InitTimer(); /* 初始化滴答定時器 */ bsp_InitLPUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC總線74HC574擴展IO. 必須在 bsp_InitLed()前執行 */ bsp_InitLed(); /* 初始化LED */ bsp_InitExtSDRAM(); /* 初始化SDRAM */ }
MPU配置和Cache配置:
數據Cache和指令Cache都開啓。配置了AXI SRAM區(本例子未用到AXI SRAM)和FMC的擴展IO區。
/* ********************************************************************************************************* * 函 數 名: MPU_Config * 功能說明: 配置MPU * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); /* 配置AXI SRAM的MPU屬性爲Write back, Read allocate,Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置FMC擴展IO的MPU屬性爲Device或者Strongly Ordered */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x60000000; MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER1; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } /* ********************************************************************************************************* * 函 數 名: CPU_CACHE_Enable * 功能說明: 使能L1 Cache * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void CPU_CACHE_Enable(void) { /* 使能 I-Cache */ SCB_EnableICache(); /* 使能 D-Cache */ SCB_EnableDCache(); }
每10ms調用一次蜂鳴器處理:
蜂鳴器處理是在滴答定時器中斷裏面實現,每10ms執行一次檢測。
/* ********************************************************************************************************* * 函 數 名: bsp_RunPer10ms * 功能說明: 該函數每隔10ms被Systick中斷調用1次。詳見 bsp_timer.c的定時中斷服務程序。一些處理時間要求 * 不嚴格的任務可以放在此函數。比如:按鍵掃描、蜂鳴器鳴叫控制等。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_RunPer10ms(void) { bsp_KeyScan10ms(); }
主功能:
主程序實現如下操作:
- 啓動一個自動重裝軟件定時器,每100ms翻轉一次LED2。
- K1鍵按下,將8bit,16bit和32bit數據寫入到內部Flash。
- K2鍵按下,將結構體數據寫入到內部Flash。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程序入口 * 形 參: 無 * 返 回 值: 錯誤代碼(無需處理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按鍵代碼 */ uint8_t ucTest, *ptr8; uint16_t uiTest, *ptr16; uint32_t ulTest, *ptr32; PARAM_T tPara, *paraptr; /* 初始化數據 */ tPara.Baud485 = 0x5555AAAA; tPara.ParamVer = 0x99; tPara.ucBackLight = 0x7788; tPara.ucRadioMode = 99.99f; bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名稱和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(0, 100); /* 啓動1個100ms的自動重裝的定時器 */ while (1) { bsp_Idle(); /* 這個函數在bsp.c文件。用戶可以修改這個函數實現CPU休眠和喂狗 */ /* 判斷定時器超時時間 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 進來一次 */ bsp_LedToggle(2); } /* 按鍵濾波和檢測由後臺systick中斷服務程序實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */ ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下,將8bit,16bit和32bit數據寫入到內部Flash */ /* 1、對於同一個地址空間,僅支持一次編程(不推薦二次編程,即使是將相應bit由數值1編程0)。 2、只能對已經擦除的空間做編程,擦除1個扇區是128KB。 3、H7的Flash編程時,務必保證要編程的地址是32字節對齊的,即此地址對32求餘爲0。並且編 程的數據必須32字節整數倍。函數bsp_WriteCpuFlash對字節數不夠32字節整數倍的情況自動補 0。 */ /* 擦除扇區 */ bsp_EraseCpuFlash((uint32_t)para_flash_area); ucTest = 0xAA; uiTest = 0x55AA; ulTest = 0x11223344; /* 扇區寫入數據 */ bsp_WriteCpuFlash((uint32_t)para_flash_area + 32*0, (uint8_t *)&ucTest, sizeof(ucTest)); bsp_WriteCpuFlash((uint32_t)para_flash_area + 32*1, (uint8_t *)&uiTest, sizeof(uiTest)); bsp_WriteCpuFlash((uint32_t)para_flash_area + 32*2, (uint8_t *)&ulTest, sizeof(ulTest)); /* 讀出數據並打印 */ ptr8 = (uint8_t *)(para_flash_area + 32*0); ptr16 = (uint16_t *)(para_flash_area + 32*1); ptr32 = (uint32_t *)(para_flash_area + 32*2); printf("寫入數據:ucTest = %x, uiTest = %x, ulTest = %x\r\n", ucTest, uiTest, ulTest); printf("讀取數據:ptr8 = %x, ptr16 = %x, ptr32 = %x\r\n", *ptr8, *ptr16, *ptr32); break; case KEY_DOWN_K2: /* K2鍵按下, 將結構體數據寫入到內部Flash */ /* 擦除扇區 */ bsp_EraseCpuFlash((uint32_t)para_flash_area); /* 扇區寫入數據 */ bsp_WriteCpuFlash((uint32_t)para_flash_area, (uint8_t *)&tPara, sizeof(tPara)); /* 讀出數據並打印 */ paraptr = (PARAM_T *)((uint32_t)para_flash_area); printf("寫入數據:Baud485=%x, ParamVer=%x, ucBackLight=%x, ucRadioMode=%f\r\n", tPara.Baud485, tPara.ParamVer, tPara.ucBackLight, paraptr->ucRadioMode); printf("讀取數據:Baud485=%x, ParamVer=%x, ucBackLight=%x, ucRadioMode=%f\r\n", paraptr->Baud485, paraptr->ParamVer, paraptr->ucBackLight, paraptr->ucRadioMode); break; default: /* 其它的鍵值不處理 */ break; } } } }
71.7 實驗例程說明(IAR)
配套例子:
V7-049_內部Flash模擬EEPROM
實驗目的:
- 學習內部Flash模擬EEPROM。
實驗內容:
- 使用內部Flash模擬EEPROM,務必告訴編譯要使用的存儲空間,防止這個空間存入了程序。
- 對於同一個地址空間,僅支持一次編程(不推薦二次編程,即使是將相應bit由數值1編程0)。
- 只能對已經擦除的空間做編程,擦除1個扇區是128KB。
- H7的Flash編程時,務必保證要編程的地址是32字節對齊的,即此地址對32求餘爲0。
並且編程的數據必須32字節整數倍,函數bsp_WriteCpuFlash對字節數不夠32字節整數倍的情況自動補0。
實驗操作:
- K1鍵按下,將8bit,16bit和32bit數據寫入到內部Flash。
- K2鍵按下,將結構體數據寫入到內部Flash。
上電後串口打印的信息:
波特率 115200,數據位 8,奇偶校驗位無,停止位 1。
程序設計:
系統棧大小分配:
RAM空間用的DTCM:
硬件外設初始化
硬件外設的初始化是在 bsp.c 文件實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鐘: - 調用函數HAL_InitTick,初始化滴答時鐘中斷1ms。 - 設置NVIV優先級分組爲4。 */ HAL_Init(); /* 配置系統時鐘到400MHz - 切換使用HSE。 - 此函數會更新全局變量SystemCoreClock,並重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用於代碼執行時間測量,MDK5.25及其以上版本才支持,IAR不支持。 - 默認不開啓,如果要使能此選項,務必看V7開發板用戶手冊第xx章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder並開啓 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitDWT(); /* 初始化DWT時鐘週期計數器 */ bsp_InitKey(); /* 按鍵初始化,要放在滴答定時器之前,因爲按鈕檢測是通過滴答定時器掃描 */ bsp_InitTimer(); /* 初始化滴答定時器 */ bsp_InitLPUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC總線74HC574擴展IO. 必須在 bsp_InitLed()前執行 */ bsp_InitLed(); /* 初始化LED */ bsp_InitExtSDRAM(); /* 初始化SDRAM */ }
MPU配置和Cache配置:
數據Cache和指令Cache都開啓。配置了AXI SRAM區(本例子未用到AXI SRAM)和FMC的擴展IO區。
/* ********************************************************************************************************* * 函 數 名: MPU_Config * 功能說明: 配置MPU * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); /* 配置AXI SRAM的MPU屬性爲Write back, Read allocate,Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置FMC擴展IO的MPU屬性爲Device或者Strongly Ordered */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x60000000; MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER1; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } /* ********************************************************************************************************* * 函 數 名: CPU_CACHE_Enable * 功能說明: 使能L1 Cache * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void CPU_CACHE_Enable(void) { /* 使能 I-Cache */ SCB_EnableICache(); /* 使能 D-Cache */ SCB_EnableDCache(); }
每10ms調用一次蜂鳴器處理:
蜂鳴器處理是在滴答定時器中斷裏面實現,每10ms執行一次檢測。
/* ********************************************************************************************************* * 函 數 名: bsp_RunPer10ms * 功能說明: 該函數每隔10ms被Systick中斷調用1次。詳見 bsp_timer.c的定時中斷服務程序。一些處理時間要求 * 不嚴格的任務可以放在此函數。比如:按鍵掃描、蜂鳴器鳴叫控制等。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_RunPer10ms(void) { bsp_KeyScan10ms(); }
主功能:
主程序實現如下操作:
- 啓動一個自動重裝軟件定時器,每100ms翻轉一次LED2。
- K1鍵按下,將8bit,16bit和32bit數據寫入到內部Flash。
- K2鍵按下,將結構體數據寫入到內部Flash。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程序入口 * 形 參: 無 * 返 回 值: 錯誤代碼(無需處理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按鍵代碼 */ uint8_t ucTest, *ptr8; uint16_t uiTest, *ptr16; uint32_t ulTest, *ptr32; PARAM_T tPara, *paraptr; /* 初始化數據 */ tPara.Baud485 = 0x5555AAAA; tPara.ParamVer = 0x99; tPara.ucBackLight = 0x7788; tPara.ucRadioMode = 99.99f; bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名稱和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(0, 100); /* 啓動1個100ms的自動重裝的定時器 */ while (1) { bsp_Idle(); /* 這個函數在bsp.c文件。用戶可以修改這個函數實現CPU休眠和喂狗 */ /* 判斷定時器超時時間 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 進來一次 */ bsp_LedToggle(2); } /* 按鍵濾波和檢測由後臺systick中斷服務程序實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */ ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { case KEY_DOWN_K1: /* K1鍵按下,將8bit,16bit和32bit數據寫入到內部Flash */ /* 1、對於同一個地址空間,僅支持一次編程(不推薦二次編程,即使是將相應bit由數值1編程0)。 2、只能對已經擦除的空間做編程,擦除1個扇區是128KB。 3、H7的Flash編程時,務必保證要編程的地址是32字節對齊的,即此地址對32求餘爲0。並且編 程的數據必須32字節整數倍。函數bsp_WriteCpuFlash對字節數不夠32字節整數倍的情況自動補 0。 */ /* 擦除扇區 */ bsp_EraseCpuFlash((uint32_t)para_flash_area); ucTest = 0xAA; uiTest = 0x55AA; ulTest = 0x11223344; /* 扇區寫入數據 */ bsp_WriteCpuFlash((uint32_t)para_flash_area + 32*0, (uint8_t *)&ucTest, sizeof(ucTest)); bsp_WriteCpuFlash((uint32_t)para_flash_area + 32*1, (uint8_t *)&uiTest, sizeof(uiTest)); bsp_WriteCpuFlash((uint32_t)para_flash_area + 32*2, (uint8_t *)&ulTest, sizeof(ulTest)); /* 讀出數據並打印 */ ptr8 = (uint8_t *)(para_flash_area + 32*0); ptr16 = (uint16_t *)(para_flash_area + 32*1); ptr32 = (uint32_t *)(para_flash_area + 32*2); printf("寫入數據:ucTest = %x, uiTest = %x, ulTest = %x\r\n", ucTest, uiTest, ulTest); printf("讀取數據:ptr8 = %x, ptr16 = %x, ptr32 = %x\r\n", *ptr8, *ptr16, *ptr32); break; case KEY_DOWN_K2: /* K2鍵按下, 將結構體數據寫入到內部Flash */ /* 擦除扇區 */ bsp_EraseCpuFlash((uint32_t)para_flash_area); /* 扇區寫入數據 */ bsp_WriteCpuFlash((uint32_t)para_flash_area, (uint8_t *)&tPara, sizeof(tPara)); /* 讀出數據並打印 */ paraptr = (PARAM_T *)((uint32_t)para_flash_area); printf("寫入數據:Baud485=%x, ParamVer=%x, ucBackLight=%x, ucRadioMode=%f\r\n", tPara.Baud485, tPara.ParamVer, tPara.ucBackLight, paraptr->ucRadioMode); printf("讀取數據:Baud485=%x, ParamVer=%x, ucBackLight=%x, ucRadioMode=%f\r\n", paraptr->Baud485, paraptr->ParamVer, paraptr->ucBackLight, paraptr->ucRadioMode); break; default: /* 其它的鍵值不處理 */ break; } } } }
71.8 總結
本章節就爲大家講解這麼多, 實際應用中的注意事項比較多,應用到項目之前務必實際測試熟悉下。