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

完整教程下載地址: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.1 初學者重要提示

71.2 模擬EEPROM驅動設計

71.2.1 內部Flash擦除的實現

71.2.2 內部Flash編程的實現

71.2.3 內部Flash讀取的實現

71.2.4 告訴編譯器使用的扇區(重要)

71.3 模擬EEPROM板級支持包(bsp_cpu_flash.c)

71.3.1 函數bsp_GetSector

71.3.2 函數bsp_ReadCpuFlash

71.3.3 函數bsp_CmpCpuFlash

71.3.4 函數bsp_EraseCpuFlash

71.3.5 函數bsp_WriteCpuFlash

71.4 模擬EEPROM驅動移植和使用

71.5 實驗例程設計框架

71.6 實驗例程說明(MDK)

71.7 實驗例程說明(IAR)

71.8 總結


 

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 = %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

實驗目的:

  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 = %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 總結

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章