【STM32H7教程】第71章 STM32H7的內部Flash應用之模擬EEPROM

  • 2020 年 3 月 11 日
  • 筆記

完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

第71章       STM32H7的內部Flash應用之模擬EEPROM

本章節為大家講解STM32H7的內部Flash模擬EEPROM,主要應用到板子沒有外置EERPOM的場合,而且H7的內部Flash比較大,可以開闢一個扇區用於模擬EEPROM。

71.1 初學者重要提示

71.2 模擬EEPROM驅動設計

71.4 模擬EEPROM板級支援包(bsp_cpu_flash.c)

71.5 模擬EERPOM驅動移植和使用

71.6 實驗常式設計框架

71.7 實驗常式說明(MDK)

71.8 實驗常式說明(IAR)

71.9 總結

71.1 初學者重要提示

  1.   學習本章節前,務必優先學習第70章。
  2.   使用內部Flash模擬EEPROM,務必告訴編譯要使用的存儲空間,防止這個空間存入了程式。
  3.   STM32H7的Flash編程時,務必保證要編程的地址是32位元組對齊的,即此地址對32求余為0。並且編程的數據必須32位元組整數倍。
  4.   STM32H743XI有兩個獨立的BANK,一個BANK的編程和擦除操作對另一個BANK沒有任何影響。但是用戶應用程式和要擦寫的Flash扇區在同一個BANK,在執行擦寫操作時,應用應用程式將停止運行,包括中斷服務程式。
  5.   使用內部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

實驗目的:

  1. 學習內部Flash模擬EEPROM。

實驗內容:

  1. 使用內部Flash模擬EEPROM,務必告訴編譯要使用的存儲空間,防止這個空間存入了程式。
  2. 對於同一個地址空間,僅支援一次編程(不推薦二次編程,即使是將相應bit由數值1編程0)。
  3. 只能對已經擦除的空間做編程,擦除1個扇區是128KB。
  4. H7的Flash編程時,務必保證要編程的地址是32位元組對齊的,即此地址對32求余為0。

並且編程的數據必須32位元組整數倍,函數bsp_WriteCpuFlash對位元組數不夠32位元組整數倍的情況自動補0。 

實驗操作:

  1. K1鍵按下,將8bit,16bit和32bit數據寫入到內部Flash。
  2. 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 = %xrn", ucTest, uiTest, ulTest);                      printf("讀取數據:ptr8 = %x, ptr16 = %x, ptr32 = %xrn", *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=%frn",                                                                         tPara.Baud485,                                                                          tPara.ParamVer,                                                                          tPara.ucBackLight,                                                                     paraptr->ucRadioMode);                        printf("讀取數據:Baud485=%x, ParamVer=%x, ucBackLight=%x, ucRadioMode=%frn",                                                                          paraptr->Baud485,                                                                              paraptr->ParamVer,                                                                             paraptr->ucBackLight,                                                                            paraptr->ucRadioMode);                      break;                  default:                      /* 其它的鍵值不處理 */                      break;              }          }      }  }

71.7 實驗常式說明(IAR)

配套例子:

V7-049_內部Flash模擬EEPROM

實驗目的:

  1. 學習內部Flash模擬EEPROM。

實驗內容:

  1. 使用內部Flash模擬EEPROM,務必告訴編譯要使用的存儲空間,防止這個空間存入了程式。
  2. 對於同一個地址空間,僅支援一次編程(不推薦二次編程,即使是將相應bit由數值1編程0)。
  3. 只能對已經擦除的空間做編程,擦除1個扇區是128KB。
  4. H7的Flash編程時,務必保證要編程的地址是32位元組對齊的,即此地址對32求余為0。

並且編程的數據必須32位元組整數倍,函數bsp_WriteCpuFlash對位元組數不夠32位元組整數倍的情況自動補0。 

實驗操作:

  1. K1鍵按下,將8bit,16bit和32bit數據寫入到內部Flash。
  2. 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 = %xrn", ucTest, uiTest, ulTest);                      printf("讀取數據:ptr8 = %x, ptr16 = %x, ptr32 = %xrn", *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=%frn",                                                                         tPara.Baud485,                                                                          tPara.ParamVer,                                                                          tPara.ucBackLight,                                                                     paraptr->ucRadioMode);                        printf("讀取數據:Baud485=%x, ParamVer=%x, ucBackLight=%x, ucRadioMode=%frn",                                                                          paraptr->Baud485,                                                                              paraptr->ParamVer,                                                                             paraptr->ucBackLight,                                                                            paraptr->ucRadioMode);                      break;                  default:                      /* 其它的鍵值不處理 */                      break;              }          }      }  }

71.8 總結

本章節就為大家講解這麼多, 實際應用中的注意事項比較多,應用到項目之前務必實際測試熟悉下。