你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32H7的SPI 总线应用之SPI Flash的MDK下载算法制作

[复制链接]
STMCU小助手 发布时间:2021-11-6 23:05
84.1 初学者重要提示
  SPI Flash的相关知识点可以看第78章和79章。
  SPI Flash下载算法文件直接采用HAL库制作,方便大家自己修改。
84.2 MDK下载算法基础知识
Flash编程算法是一种用于擦除应用程序或将应用程序下载到Flash的程序代码。MDK本身支持的各种器件都自带下载算法,存放在MDK各种器件的软件包里面,以STM32H7为例,算法存放在\Keil\STM32H7xx_DFP\2.6.0\CMSIS\Flash(软件包版本不同,数值2.6.0不同),但不支持的需要我们自己制作,本章教程为此而生。

84.2.1 程序能够通过下载算法下载到芯片的核心思想
认识到这点很重要:通过MDK创建一批与地址信息无关的函数,实现的功能主要有初始化,擦除,编程,读取,校验等,然后MDK调试下载阶段,会将算法文件加载到芯片的内部RAM里面(加载地址可以通过MDK设置),然后MDK通过与这个算法文件的交互,实现程序下载,调试阶段数据读取等操作。

84.2.2 算法程序中擦除操作执行流程
擦除操作大致流程:

0d071a0e889826f72cb93768e721eace.png


  加载算法到芯片RAM。
  执行初始化函数Init。
  执行擦除操作,根据用户的MDK配置,这里可以选择整个芯片擦除或者扇区擦除。
  执行Uinit函数。
  操作完毕。
84.2.3 算法程序中编程操作执行流程
编程操作大致流程:

201339135816bc603e60da45668f991d.png


  针对MDK生成的axf可执行文件做Init初始化,这个axf文件是指的大家自己创建应用程序生成的。
  查看Flash算法是否在FLM文件。如果没有在,操作失败。如果在:
  加载算法到RAM。
  执行Init函数。
  加载用户到RAM缓冲。
  执行Program Page页编程函数。
  执行Uninit函数。
  操作完毕。
84.2.4 算法程序中校验操作执行流程
校验操作大致流程:

a4a5c9e717fac7516ca272976cec4a7b.png


  校验要用到MDK生成的axf可执行文件。校验就是axf文件中下载到芯片的程序和实际下载的程序读出来做比较。
  查看Flash算法是否在FLM文件。如果没有在,操作失败。如果在:
  加载算法到RAM。
  执行Init函数。
  查看校验算法是否存在
  如果有,加载应用程序到RAM并执行校验。
  如果没有,计算CRC,将芯片中读取出来的数据和RAM中加载应用计算输出的CRC值做比较。
  执行Uninit函数。
  替换BKPT(BreakPoint断点指令)为 B. 死循环指令。
执行RecoverySupportStop,恢复支持停止。
  执行DebugCoreStop,调试内核停止。
  运行应用:
  执行失败。
执行成功,再执行硬件复位。
  操作完毕,停止调试端口。
84.3 创建MDK下载算法通用流程
下面是MDK给的一种大致操作流程,不限制必须采用这种方法,自己创建也可以的。

84.3.1 第1步,使用MDK提供好的程序模板
位于路径:\Keil\ARM\Pack\ARM\CMSIS\version\Device\_Template_Flash。

效果如下:

92bb82bfce2261d3ecab5865e30cd939.png


84.3.2 第2步,修改工程名
MDK提供的工程模板原始名字是NewDevice.uvprojx,大家可以根据自己的需要做修改。比如修改为MyDevice.uvprojx。

84.3.3 第3步,修改使用的器件
在MDK的Option选项里面设置使用的器件。

fbb792f4aa614c8d634f6846a97a399a.png


84.3.4 第4步,修改输出算法文件的名字
这个名字是方便用户查看的,比如设置为stm32h7,那么输出的算法文件就是stm32h7.flm。

73ede509936b0b438a5ba540fbc21a7c.png


注:MDK这里设置的名字与下面位置识别出来的算法名无关:

eb32aec305e29b4627294d2d4228d4ed.png


这个名字是在FlashDev.c里面定义的。

84.3.5 第5步,修改编程算法文件FlashPrg.c
模板工程里面仅提供了接口函数,内容需要用户自己填。

  1. /*
  2.    Mandatory Flash Programming Functions (Called by FlashOS):
  3.                 int Init        (unsigned long adr,   // Initialize Flash
  4.                                  unsigned long clk,
  5.                                  unsigned long fnc);
  6.                 int UnInit      (unsigned long fnc);  // De-initialize Flash
  7.                 int EraseSector (unsigned long adr);  // Erase Sector Function
  8.                 int ProgramPage (unsigned long adr,   // Program Page Function
  9.                                  unsigned long sz,
  10.                                  unsigned char *buf);

  11.    Optional  Flash Programming Functions (Called by FlashOS):
  12.                 int BlankCheck  (unsigned long adr,   // Blank Check
  13.                                  unsigned long sz,
  14.                                  unsigned char pat);
  15.                 int EraseChip   (void);               // Erase complete Device
  16.       unsigned long Verify      (unsigned long adr,   // Verify Function
  17.                                  unsigned long sz,
  18.                                  unsigned char *buf);

  19.        - BlanckCheck  is necessary if Flash space is not mapped into CPU memory space
  20.        - Verify       is necessary if Flash space is not mapped into CPU memory space
  21.        - if EraseChip is not provided than EraseSector for all sectors is called
  22. */

  23. /*
  24. *  Initialize Flash Programming Functions
  25. *    Parameter:      adr:  Device Base Address
  26. *                    clk:  Clock Frequency (Hz)
  27. *                    fnc:  Function Code (1 - Erase, 2 - Program, 3 - Verify)
  28. *    Return Value:   0 - OK,  1 - Failed
  29. */

  30. int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {

  31.   /* Add your Code */
  32.   return (0);                                  // Finished without Errors
  33. }


  34. /*
  35. *  De-Initialize Flash Programming Functions
  36. *    Parameter:      fnc:  Function Code (1 - Erase, 2 - Program, 3 - Verify)
  37. *    Return Value:   0 - OK,  1 - Failed
  38. */

  39. int UnInit (unsigned long fnc) {

  40.   /* Add your Code */
  41.   return (0);                                  // Finished without Errors
  42. }


  43. /*
  44. *  Erase complete Flash Memory
  45. *    Return Value:   0 - OK,  1 - Failed
  46. */

  47. int EraseChip (void) {

  48.   /* Add your Code */
  49.   return (0);                                  // Finished without Errors
  50. }


  51. /*
  52. *  Erase Sector in Flash Memory
  53. *    Parameter:      adr:  Sector Address
  54. *    Return Value:   0 - OK,  1 - Failed
  55. */

  56. int EraseSector (unsigned long adr) {

  57.   /* Add your Code */
  58.   return (0);                                  // Finished without Errors
  59. }


  60. /*
  61. *  Program Page in Flash Memory
  62. *    Parameter:      adr:  Page Start Address
  63. *                    sz:   Page Size
  64. *                    buf:  Page Data
  65. *    Return Value:   0 - OK,  1 - Failed
  66. */

  67. int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) {

  68.   /* Add your Code */
  69.   return (0);                                  // Finished without Errors
  70. }
复制代码

84.3.6 第6步,修改配置文件FlashDev.c
模板工程里面提供简单的配置说明:

  1. struct FlashDevice const FlashDevice  =  {
  2.    FLASH_DRV_VERS,             // Driver Version, do not modify!
  3.    "New Device 256kB Flash",   // Device Name
  4.    ONCHIP,                     // Device Type
  5.    0x00000000,                 // Device Start Address
  6.    0x00040000,                 // Device Size in Bytes (256kB)
  7.    1024,                       // Programming Page Size
  8.    0,                          // Reserved, must be 0
  9.    0xFF,                       // Initial Content of Erased Memory
  10.    100,                        // Program Page Timeout 100 mSec
  11.    3000,                       // Erase Sector Timeout 3000 mSec

  12. // Specify Size and Address of Sectors
  13.    0x002000, 0x000000,         // Sector Size  8kB (8 Sectors)
  14.    0x010000, 0x010000,         // Sector Size 64kB (2 Sectors)
  15.    0x002000, 0x030000,         // Sector Size  8kB (8 Sectors)
  16.    SECTOR_END
  17. };
复制代码

注:名字New Device 256kB Flash就是我们第4步所说的。MDK的Option选项里面会识别出这个名字。

84.3.7 第7步,保证生成的算法文件中RO和RW段的独立性,即与地址无关
C和汇编的配置都勾选上:

81745e69eaae3963023d8d62d8d854db.png


汇编:

1fce5c0df84635c2b704a595d853a5d0.png


如果程序的所有只读段都与位置无关,则该程序为只读位置无关(ROPI, Read-only position independence)。ROPI段通常是位置无关代码(PIC,position-independent code),但可以是只读数据,也可以是PIC和只读数据的组合。选择“ ROPI”选项,可以避免用户不得不将代码加载到内存中的特定位置。这对于以下例程特别有用:

(1)加载以响应运行事件。

(2)在不同情况下使用其他例程的不同组合加载到内存中。

(3)在执行期间映射到不同的地址。

使用Read-Write position independence同理,表示的可读可写数据段。

84.3.8 第8步,将程序可执行文件axf修改为flm格式
通过下面的命令就可以将生成的axf可执行文件修改为flm。

0f1f6d27190f470484633137076dbe3c.png


84.3.9 第9步,分散加载设置
我们这里的分散加载文件直接使用MDK模板工程里提供好的即可,无需任何修改。

c54ffaac8b74e54b1bac0ff78d6bb18a.png


分散加载文件中的内容如下:

  1. ; Linker Control File (scatter-loading)
  2. ;

  3. PRG 0 PI               ; Programming Functions
  4. {
  5.   PrgCode +0           ; Code
  6.   {
  7.     * (+RO)
  8.   }
  9.   PrgData +0           ; Data
  10.   {
  11.     * (+RW,+ZI)
  12.   }
  13. }

  14. DSCR +0                ; Device Description
  15. {
  16.   DevDscr +0
  17.   {
  18.     FlashDev.o
  19.   }
  20. }
复制代码

--diag_suppress L6305用于屏蔽L6503类型警告信息。

特别注意,设置了分散加载后,此处的配置就不再起作用了:

b4e157ab29667b0d404715604c9786cd.png


84.4 SPI Flash的MDK下载算法制作
下面将QSPI Flash算法制作过程中的几个关键点为大家做个说明。

84.4.1 第1步,制作前重要提示
这两点非常重要:

  程序里面不要开启任何中断,全部查询方式。
  HAL库里面各种时间基准相关的API全部处理掉。简单省事些,我们这里是直接注释,采用死等即可。无需做超时等待,因为超时后,已经意味着操作失败了,跟死等没有区别。
84.4.2 第2步,准备一个工程模板
推荐大家直接使用我们本章工程准备好的模板即可,如果大家自己制作,注意一点,请使用当前最新的HAL库。

b0cfb040027000e7e6c6dac47bc3f960.png


84.4.3 第3步,修改HAL库
这一步比较重要,主要修改了以下三个文件:

75fdbf8e42073683e67cb1828f2f66ff.png


主要是修改了HAL库时间基准相关的几个API,并注释掉了一批无关的API。具体修改内容,大家可以找个比较软件,对比修改后的这个文件和CubeH7软件包V1.8.0(软件包里面的HAL库版本是V1.9.0)的差异即可。

84.4.4 第4步,时钟初始化
我们已经用不到滴答定时器了,直接在bsp.c文件里面对滴答初始化函数做重定向:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: HAL_InitTick
  4. *    功能说明: 重定向,不使用
  5. *    形    参: TickPriority
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
  10. {
  11.     return HAL_OK;
  12. }
  13. 然后就是HSE外置晶振的配置,大家根据自己的板子实际外挂晶振大小,修改stm32h7xx_hal_conf.h文件中HSE_VALUE大小,实际晶振多大,这里就修改为多大:

  14. #if !defined  (HSE_VALUE)
  15. #define HSE_VALUE    ((uint32_t)25000000) /*!< Value of the External oscillator in Hz */
  16. #endif /* HSE_VALUE */
  17. 最后修改PLL:

  18. /*
  19. *********************************************************************************************************
  20. *    函 数 名: SystemClock_Config
  21. *    功能说明: 初始化系统时钟
  22. *                System Clock source            = PLL (HSE)
  23. *                SYSCLK(Hz)                     = 400000000 (CPU Clock)
  24. *               HCLK(Hz)                       = 200000000 (AXI and AHBs Clock)
  25. *                AHB Prescaler                  = 2
  26. *                D1 APB3 Prescaler              = 2 (APB3 Clock  100MHz)
  27. *                D2 APB1 Prescaler              = 2 (APB1 Clock  100MHz)
  28. *                D2 APB2 Prescaler              = 2 (APB2 Clock  100MHz)
  29. *                D3 APB4 Prescaler              = 2 (APB4 Clock  100MHz)
  30. *                HSE Frequency(Hz)              = 25000000
  31. *               PLL_M                          = 5
  32. *                PLL_N                          = 160
  33. *                PLL_P                          = 2
  34. *                PLL_Q                          = 4
  35. *                PLL_R                          = 2
  36. *                VDD(V)                         = 3.3
  37. *                Flash Latency(WS)              = 4
  38. *    形    参: 无
  39. *    返 回 值: 1 表示失败,0 表示成功
  40. *********************************************************************************************************
  41. */
  42. int SystemClock_Config(void)
  43. {
  44.     RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  45.     RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  46.     HAL_StatusTypeDef ret = HAL_OK;

  47.     /* 锁住SCU(Supply configuration update) */
  48.     MODIFY_REG(PWR->CR3, PWR_CR3_SCUEN, 0);

  49.     /*
  50.       1、芯片内部的LDO稳压器输出的电压范围,可选VOS1,VOS2和VOS3,不同范围对应不同的Flash读速度,
  51.          详情看参考手册的Table 12的表格。
  52.       2、这里选择使用VOS1,电压范围1.15V - 1.26V。
  53.     */
  54.     __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  55.     while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}

  56.     /* 使能HSE,并选择HSE作为PLL时钟源 */
  57.     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  58.     RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  59.     RCC_OscInitStruct.HSIState = RCC_HSI_OFF;
  60.     RCC_OscInitStruct.CSIState = RCC_CSI_OFF;
  61.     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  62.     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;

  63.     RCC_OscInitStruct.PLL.PLLM = 5;
  64.     RCC_OscInitStruct.PLL.PLLN = 160;
  65.     RCC_OscInitStruct.PLL.PLLP = 2;
  66.     RCC_OscInitStruct.PLL.PLLR = 2;
  67.     RCC_OscInitStruct.PLL.PLLQ = 4;        

  68.     RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
  69.     RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2;   
  70.     ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);
  71.     if(ret != HAL_OK)
  72.     {
  73.         return 1;        
  74.     }

  75.     /*
  76.        选择PLL的输出作为系统时钟
  77.        配置RCC_CLOCKTYPE_SYSCLK系统时钟
  78.        配置RCC_CLOCKTYPE_HCLK 时钟,对应AHB1,AHB2,AHB3和AHB4总线
  79.        配置RCC_CLOCKTYPE_PCLK1时钟,对应APB1总线
  80.        配置RCC_CLOCKTYPE_PCLK2时钟,对应APB2总线
  81.        配置RCC_CLOCKTYPE_D1PCLK1时钟,对应APB3总线
  82.        配置RCC_CLOCKTYPE_D3PCLK1时钟,对应APB4总线     
  83.     */
  84.     RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_D1PCLK1 | RCC_CLOCKTYPE_PCLK1 | \
  85.                                  RCC_CLOCKTYPE_PCLK2  | RCC_CLOCKTYPE_D3PCLK1);

  86.     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  87.     RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
  88.     RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
  89.     RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;  
  90.     RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
  91.     RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
  92.     RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;

  93.     /* 此函数会更新SystemCoreClock,并重新配置HAL_InitTick */
  94.     ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
  95.     if(ret != HAL_OK)
  96.     {
  97.         return 1;
  98.     }

  99.     /*
  100.       使用IO的高速模式,要使能IO补偿,即调用下面三个函数
  101.       (1)使能CSI clock
  102.       (2)使能SYSCFG clock
  103.       (3)使能I/O补偿单元, 设置SYSCFG_CCCSR寄存器的bit0
  104.     */
  105.     __HAL_RCC_CSI_ENABLE() ;

  106.     __HAL_RCC_SYSCFG_CLK_ENABLE() ;

  107.     HAL_EnableCompensationCell();

  108.     __HAL_RCC_D2SRAM1_CLK_ENABLE();
  109.     __HAL_RCC_D2SRAM2_CLK_ENABLE();
  110.     __HAL_RCC_D2SRAM3_CLK_ENABLE();

  111.     return 0;
  112. }
复制代码

84.4.5 第5步,配置文件FlashDev.c的实现
配置如下:

  1. struct FlashDevice const FlashDevice  =  {
  2.     FLASH_DRV_VERS,               /* 驱动版本,勿修改,这个是MDK定的 */
  3.     "ARMFLY_STM32H743_SPI_25Q64", /* 算法名,添加算法到MDK安装目录会显示此名字 */
  4.     EXTSPI,                       /* 设备类型 */
  5.     0xC0000000,                   /* Flash起始地址 */
  6.     8 * 1024 * 1024,              /* Flash大小,8MB */
  7.     4096,                         /* 编程页大小 */
  8.     0,                            /* 保留,必须为0 */
  9.     0xFF,                         /* 擦除后的数值 */
  10.     6000,                         /* 页编程等待时间 */
  11.     6000,                         /* 扇区擦除等待时间 */
  12.     4 * 1024, 0x000000,          /* 扇区大小,扇区地址 */
  13.     SECTOR_END   
  14. };
复制代码

注释已经比较详细,大家根据自己的需要做修改即可。注意一点,算法名ARMFLY_STM32H743_SPI_W25Q64会反馈到这个地方:

5e80b34f327decc1acee5eaa274ff154.png


84.4.6 第6步,编程文件FlashPrg.c的实现
下面将文件中实现的几个函数为大家做个说明:

  初始化函数Init
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: Init
  4. *    功能说明: Flash编程初始化
  5. *    形    参: adr Flash基地址,芯片首地址。
  6. *             clk 时钟频率
  7. *             fnc 函数代码,1 - Erase, 2 - Program, 3 - Verify
  8. *    返 回 值: 0 表示成功, 1表示失败
  9. *********************************************************************************************************
  10. */
  11. int Init (unsigned long adr, unsigned long clk, unsigned long fnc)
  12. {
  13.     int result = 0;

  14.     /* 系统初始化 */
  15.     SystemInit();

  16.     /* 时钟初始化 */
  17.     result = SystemClock_Config();
  18.     if (result  != 0)
  19.     {
  20.         return 1;        
  21.     }

  22.     bsp_InitSPIBus();
  23.     bsp_InitSFlash();

  24.     return 0;
  25. }
复制代码

  复位初始化函数Uinit
擦除,编程和校验函数后都会调用此函数,我们这里未使用。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: UnInit
  4. *    功能说明: 复位初始化
  5. *    形    参: fnc 函数代码,1 - Erase, 2 - Program, 3 - Verify
  6. *    返 回 值: 0 表示成功, 1表示失败
  7. *********************************************************************************************************
  8. */
  9. int UnInit (unsigned long fnc)
  10. {
  11.     return (0);
  12. }
复制代码

复位初始化这里,直接将其设置为内存映射模式。

  整个芯片擦除函数EraseChip
如果大家配置勾选了MDK Option选项中此处的配置,会调用的整个芯片擦除:

23b**ee7f8052bf8a02d0531285d14.png


实际应用中不推荐大家勾选这里,因为整个芯片擦除太耽误时间。

另外,如果大家的算法工程里面没有添加此函数,MDK会调用扇区擦除函数来实现,直到所有扇区擦除完毕。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: EraseChip
  4. *    功能说明: 整个芯片擦除
  5. *    形    参: 无
  6. *    返 回 值: 0 表示成功, 1表示失败
  7. *********************************************************************************************************
  8. */
  9. int EraseChip (void)
  10. {   
  11.     sf_EraseChip();
  12.     return 0;      
  13. }
复制代码

  扇区擦除函数EraseSector
如果大家配置勾选了MDK Option选项中此处的配置,会调用扇区擦除:



  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: EraseSector
  4. *    功能说明: 扇区擦除
  5. *    形    参: adr 擦除地址
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. int EraseSector (unsigned long adr)
  10. {   
  11.     adr -= SPI_FLASH_MEM_ADDR;

  12.     sf_EraseSector(adr);

  13.     return 0;     
  14. }
复制代码

这里要注意两点:

(1)     程序里面的操作adr -= SPI_FLASH_MEM_ADDR,实际传递进来的地址是带了首地址的,即0xC0000000。特别注意,我们这里的0xC0000000是随意设置的,因为STM32H7的标准SPI外设并不支持内存映射。

(2)     这里执行的擦除大小要前面FlashDev.c文件中配置的扇区大小一致,这里是执行的4KB为扇区进行擦除。

  页编程函数ProgramPage
页编程函数实现如下:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: ProgramPage
  4. *    功能说明: 页编程
  5. *    形    参: adr 页起始地址
  6. *             sz  页大小
  7. *             buf 要写入的数据地址
  8. *    返 回 值: 无
  9. *********************************************************************************************************
  10. */
  11. int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf)
  12. {
  13.     adr -= SPI_FLASH_MEM_ADDR;

  14.     sf_WriteBuffer(buf, adr, sz);

  15.     return (0);                     
  16. }
复制代码

这里注意两点:

(1)     W25Q64的页大小是256字节,前面FlashDev.c中将页编程大小设置为4096字节,主要是方便擦除操作。

(2)     程序里面的操作adr -= SPI_FLASH_MEM_ADDR,实际传递进来的地址是带了首地址的,即0xC0000000。

  读取和校验函数
校验函数实现如下:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: Verify
  4. *    功能说明: 校验
  5. *    形    参: adr 起始地址
  6. *             sz  数据大小
  7. *             buf 要校验的数据缓冲地址
  8. *    返 回 值: -
  9. *********************************************************************************************************
  10. */
  11. unsigned char aux_buf[4096];
  12. unsigned long Verify (unsigned long adr, unsigned long sz, unsigned char *buf)
  13. {
  14.     int i;

  15.     adr -= SPI_FLASH_MEM_ADDR;

  16.     sf_ReadBuffer(aux_buf, adr, sz);

  17.     for (i = 0; i< sz; i++)
  18.     {
  19.         if (aux_buf<i style="font-style: italic;"> != buf)
  20.         </i>return (adr+i);              /* 校验失败 */     
  21.     }

  22.     adr += SPI_FLASH_MEM_ADDR;
  23.     return (adr+sz);                 /* 校验成功 */   
  24. }
复制代码

84.4.7 第7步,修改SPI Flash驱动文件(引脚,命令等)
最后一步就是SPI Flash(W25Q64)的驱动修改,大家可以根据自己的需求做修改。使用的引脚定义在文件bsp_spi_bus.c:

  1. /*
  2. *********************************************************************************************************
  3. *                                时钟,引脚,DMA,中断等宏定义
  4. *********************************************************************************************************
  5. */
  6. #define SPIx                            SPI1
  7. #define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()
  8. #define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()

  9. #define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()
  10. #define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()

  11. #define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
  12. #define SPIx_SCK_GPIO                    GPIOB
  13. #define SPIx_SCK_PIN                    GPIO_PIN_3
  14. #define SPIx_SCK_AF                        GPIO_AF5_SPI1

  15. #define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
  16. #define SPIx_MISO_GPIO                    GPIOB
  17. #define SPIx_MISO_PIN                     GPIO_PIN_4
  18. #define SPIx_MISO_AF                    GPIO_AF5_SPI1

  19. #define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
  20. #define SPIx_MOSI_GPIO                    GPIOB
  21. #define SPIx_MOSI_PIN                     GPIO_PIN_5
  22. #define SPIx_MOSI_AF                    GPIO_AF5_SPI1
复制代码

硬件设置了之后,剩下就是SPI Flash相关的几个配置和片选引脚配置,在文件bsp_spi_flash.c:

主要是下面这几个:

  1. /* 串行Flash的片选GPIO端口, PD13  */
  2. #define SF_CS_CLK_ENABLE()             __HAL_RCC_GPIOD_CLK_ENABLE()
  3. #define SF_CS_GPIO                    GPIOD
  4. #define SF_CS_PIN                    GPIO_PIN_13

  5. #define SF_CS_0()                    SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U)
  6. #define SF_CS_1()                    SF_CS_GPIO->BSRR = SF_CS_PIN

  7. #define CMD_AAI       0xAD      /* AAI 连续编程指令(FOR SST25VF016B) */
  8. #define CMD_DISWR      0x04        /* 禁止写, 退出AAI状态 */
  9. #define CMD_EWRSR      0x50        /* 允许写状态寄存器的命令 */
  10. #define CMD_WRSR      0x01      /* 写状态寄存器命令 */
  11. #define CMD_WREN      0x06        /* 写使能命令 */
  12. #define CMD_READ      0x03      /* 读数据区命令 */
  13. #define CMD_RDSR      0x05        /* 读状态寄存器命令 */
  14. #define CMD_RDID      0x9F        /* 读器件ID命令 */
  15. #define CMD_SE        0x20        /* 擦除扇区命令 */
  16. #define CMD_BE        0xC7        /* 批量擦除命令 */
  17. #define DUMMY_BYTE    0xA5        /* 哑命令,可以为任意值,用于读操作 */

  18. #define WIP_FLAG      0x01        /* 状态寄存器中的正在编程标志(WIP) */
复制代码

84.5 SPI Flash的MDK下载算法使用方法
编译本章教程配套的例子,生成的算法文件位于此路径下:

15d26ccebf6ffb3480f4ca6cecf15906.png


84.5.1 下载算法存放位置
生成算法文件后,需要大家将其存到MDK安装目录,有两个位置可以存放,任选其一,推荐第2种:

  第1种:存放到MDK的STM32H7软包安装目录里面:\Keil\STM32H7xx_DFP\2.6.0\CMSIS\Flash(软包版本不同,数值2.6.0不同)。
  第2种:MDK的安装目录 \ARM\Flash里面。


84.5.2 下载配置
注意这里一定要够大,否则会提示算法文件无法加载:

f6e80114a68806de27b08975ee670898.png


我们这里是将其加到DTCM中,即首地址为0x20000000,大家也可以存储到任意其它RAM地址,只要空间还够加载算法文件即可。推荐使用AXI SRAM(地址0x24000000),因为这块RAM空间足够大。

如果要下载程序到QSPI Flash里面,需要做如下配置:

efe1ac1d0fcdd711fb010157b7e1d3a8.png


84.5.3 验证算法文件是否可以正常使用
为了验证算法文件是否可以正常使用,大家可以运行本教程第86章配套的例子。

84.6 实验例程说明
本章配套例子:V7-065_SPI Flash的MDK下载算法制作

编译后,算法文件会存到此路径下:

d96f01a2f53788079a9c6504bdae69e5.png


84.7 总结
本章节就为大家讲解这么多,为了熟练掌握,大家可以尝试自己实现一个Flash下载算法。


419d43f9b4e68e2849437c14e5a20fcf.png
f0f8d92a80ae444a604f30fa68d200e7.png
收藏 评论0 发布时间:2021-11-6 23:05

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版