制作jflash下载算法,并解决堆栈溢出导致的写数据错误问题

1、jflash下载算法介绍

jflash是segger开发的一款软件,需要配合jlink一起使用。使用过jlink的人都知道,在项目开发调试阶段非常有帮助。
jflash下载算法就是实现当jflash中没有自己使用的芯片,或者想使用jflash将程序下载到外部flash中时,通过jflash调用flash下载算法程序完成对flash的擦除、读、写、校验操作。jflash的界面如下所示:
制作jflash下载算法,并解决堆栈溢出导致的写数据错误问题_第1张图片

2、jflash下载算法的制作过程

jflash下载算法的制作,在mdk中提供了对应的模板工程,路径在Keil_v5\ARM\Flash\_Template中,可以直接使用模板工程,在其中添加自己的flash驱动即可。

本文就不过多介绍整个过程了,具体的步骤,请参考:
从零编写STM32H7的MDK SPI FLASH下载算法。
或csdn对其的转载
从零编写STM32H7的MDK SPI FLASH下载算法。

本文重点讲述遇到的问题,并介绍解决方法。

3、移植sfud过程中,编译错误 L6248E

此问题可以参考我的另一篇博客:mdk制作外部flash下载算法, 编译错误 L6248E。

主要是因为在sfud里面,多个地方定义了char *name指针用于存储flash型号的名字。但是因为flash下载算法要求编译出的代码必须是地址无关的代码,因此这里不能用指针,需要为name明确地址空间。这里需要将指针改为数组,因为数组的地址空间编译后是确定的,改动点如下:

将所以*name指针,改为数组:

typedef struct __sfud_spi {
    /* SPI device name */
    char name[32];
    /* 以下有省略 */
} sfud_spi, *sfud_spi_t;

4、解决代码执行过程中的栈溢出问题

flash下载算法没有启动文件,因此我们无法设置程序运行的堆栈的大小,但是默认的堆栈又很小,当函数嵌套过多或局部变量定义太多时就会出现栈溢出,导致覆盖正常程序使用的变量,此问题很难定位,我在测试中就遇到了这个问题。

  • 4.1问题分析
    下面我们通过map文件来分析下。
    首先看下模板工程提供的链接脚本文件:
; Linker Control File (scatter-loading)
;

PRG 0 PI               ; Programming Functions
{
  PrgCode +0           ; Code
  {
    * (+RO)
  }
  PrgData +0           ; Data
  {
    * (+RW,+ZI)
  }
}

DSCR +0                ; Device Description
{
  DevDscr +0
  {
    FlashDev.o
  }
}

上述脚本中,PRG 所指定的内存区域是flash下载算法所在的区域。DSCR所指定的区域主要用于存放FlashDevice这个结构体,jflash通过获取此区域的这个结构体可以知道flash的一些基本信息。
按这个链接脚本生成的map文件中PRG和DSCR区域部分如下:
制作jflash下载算法,并解决堆栈溢出导致的写数据错误问题_第2张图片
从上图可以看出.bss段所占的空间并没有算在PrgData中,而0x0000373c这个地址正好是代码中定义的4096byte的数组如下:
在这里插入图片描述
源码如下:

#define BUF_SIZE 4096
uint8_t aux_buf[BUF_SIZE];

从map文件还能看出,sfud.o中的变量也在.bss段中,是在0x0000473c这个位置:
在这里插入图片描述
但是DSCR的起始位置是0x0000373c,说明了.bss段在这里并没有分配空间。这也就说明了.bss段并不占用生成的程序的大小,而是在启动时由启动文件把它加载到sram的另一块地方,然后初始化为0,并且.bss段和栈空间会放在一起进行空间分配。
如果是一个正常程序,这样当然没问题。但是我们的flash下载算法程序就不能这样操作了,因为它没有启动文件,flash算法运行时.bss段处的数据也没有加载到sram这个操作,因此。bss段中的变量仍旧是在0x0000373c开始处的空间。
Init函数中,添加打印堆栈的代码,如下:

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

    /* Add your Code */
    int result = 0;

    UART0_Init(115200, USART_WordLength_8b, USART_StopBits_1b, USART_Parity_No);

    SFUD_INFO("MSP: 0x%x\n", __get_MSP());

    SFUD_INFO("Init: adr=0x%x, clk=0x%x, fnc=0x%x\n", adr, clk, fnc);

    g_size = 0;

    result = sfud_init();
    if(result != SFUD_SUCCESS) {
        return result;
    }
    return (0);                                  // Finished without Errors
}

上述代码打印出的MSP的值,我测试如下:

MSP: 0x200038d4

因为我的代码运行在0x20000000处,所以,MSP的偏移位置也就是0x38d4,减去0x0000373c得到408byte,也就是说栈的大小必须在408byte以内,超过就会对.bss处的变量造成覆盖。

  • 4.2 改善一下程序
    上述问题的产生是由于变量在.bss段里所致,那如果变量不在.bss段里呢?
    我们改动下述部分的代码:
#define BUF_SIZE 4096
uint8_t aux_buf[BUF_SIZE] = {1};

上述代码对aux_buf[]数组给了初值,这样编译出的map文件如下:
在这里插入图片描述在这里插入图片描述
可以看出aux_buf到.data段里了,也就是变成了程序的一部分,同时打印出的堆栈MSP的值也变大了。堆栈溢出不会对aux_buf造成影响了。但是这里的.bss段仍存放的是sfud.o里的未初始化的变量,这可是读写flash会使用到的。而sfud_write中有这样的代码:
制作jflash下载算法,并解决堆栈溢出导致的写数据错误问题_第3张图片
这里cmd_data就是在.bss段里,正好是261byte=0x105,和map文件对应上。
看到这里就明白了如果栈溢出,就会对cmd_data进行覆盖,对flash的写数据造成影响。这和我实际测试测出来的现象一样,经常在0xfc这个flash地址写错,就是因为栈向下生长对cmd_data[252]进行了覆盖。

  • 4.3、解决方法
    经过上面分析可以看出,这个程序存在两个问题,①生成的程序中.bss段里的变量没有启动文件为其分配空间;②默认的栈太小,栈会溢出。
    就从这两个地方入手。
    • 1、给.bss段单独创建区域。
      既然.bss段的变量地址没有进行空间分配,而它的地址编译后又是确定的,并且.bss段在PRG区域的最后边,可以先将.bss段独立出来,修改链接脚本如下:
; Linker Control File (scatter-loading)
;

PRG 0 PI               ; Programming Functions
{
  PrgCode +0           ; Code
  {
    * (+RO)
  }
  PrgData +0           ; Data
  {
    * (+RW)
  }
  PrgZI +0             ; ZI
  {
    * (+ZI)
  }
}

DSCR +0                ; Device Description
{
  DevDscr +0
  {
    FlashDev.o
  }
}

生成的map文件:
制作jflash下载算法,并解决堆栈溢出导致的写数据错误问题_第4张图片
上图虽说在链接时给.bss单独了空间,但是程序运行时并没有启动文件为其分配空间,而栈的位置又是以0x00003694这个程序结束的的位置为开始偏移指定字节(偏移多少我们无从得知,我打印出来大概就是400多byte),依然存在溢出覆盖的问题,因此需要调整栈的起始位置。
我们可以创建个大数组,这个数组用来扩展编译出的程序所占的地址空间,来间接作为栈来使用。修改的链接脚本如下:

; Linker Control File (scatter-loading)
;

PRG 0 PI               ; Programming Functions
{
  PrgCode +0           ; Code
  {
    * (+RO)
  }
  PrgData +0           ; Data
  {
    * (+RW)
  }
  PrgZI +0             ; ZI
  {
    * (+ZI)
  }
}

EXTSPACE +0x2000	; 和PRG区域间隔8192byte
{
  EXSTACKAPACE +0
  {
    * (.exstackspace)
  }
}

DSCR +0                ; Device Description
{
  DevDscr +0
  {
    FlashDev.o
  }
}

上述间隔0x2000是相对于PrgData的结束位置来说的,因为ZI在编译出的程序中并不占空间,也就是程序在PrgData后就已经结束了。0x2000的作用就是用于在程序结尾处预留8K区域给.bss,也就是ZI。
到此处为止,.bss的空间就解决了,因为预留的足够大,即使栈溢出也关系不大。但是为了更加严谨,接下来还是需要预留栈空间的位置。
FlashPrg.c中定义数组,并将其分配在EXTSPACE中:

#define EXT_STACK_SIZE  (2048)
#define EXTSTACK_AREA_ATTRIBUTES  __attribute__ ((section(".exstackspace"))) __attribute__((aligned(4)))
volatile uint8_t ext_stack_space[EXT_STACK_SIZE] EXTSTACK_AREA_ATTRIBUTES;

上述2048代表在EXTSPACE空间中占用2048字节作为栈的扩展区域,起始位置和PrgData区域偏了0x2000。
map文件如下:
制作jflash下载算法,并解决堆栈溢出导致的写数据错误问题_第5张图片
修改后的空间分配如下:
在这里插入图片描述

至此,flash写错误的问题得到解决。

5、完整的代码

  • 1、Target.lin文件如下:
; Linker Control File (scatter-loading)
;

PRG 0 PI               ; Programming Functions
{
  PrgCode +0           ; Code
  {
    * (+RO)
  }
  PrgData +0           ; Data
  {
    * (+RW)
  }
  PrgZI +0             ; ZI
  {
    * (+ZI)
  }
}

EXTSPACE +0x2000
{
  EXSTACKAPACE +0
  {
    * (.exstackspace)
  }
}

DSCR +0                ; Device Description
{
  DevDscr +0
  {
    FlashDev.o
  }
}
  • 2、FlashPrg.c文件如下:
/**************************************************************************//**
 * @file     FlashPrg.c
 * @brief    Flash Programming Functions adapted for New Device Flash
 * @version  V1.0.0
 * @date     10. January 2018
 ******************************************************************************/
/*
 * Copyright (c) 2010-2018 Arm Limited. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the License); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an AS IS BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
#include "FlashOS.H"        // FlashOS Structures
#include "uart.h"
#include "sfud.h"

#define EXT_STACK_SIZE  (2048)
#define EXTSTACK_AREA_ATTRIBUTES  __attribute__ ((section(".exstackspace"))) __attribute__((aligned(4)))
volatile uint8_t ext_stack_space[EXT_STACK_SIZE] EXTSTACK_AREA_ATTRIBUTES;

#define BUF_SIZE 4096
uint8_t aux_buf[BUF_SIZE];

/* 
   Mandatory Flash Programming Functions (Called by FlashOS):
                int Init        (unsigned long adr,   // Initialize Flash
                                 unsigned long clk,
                                 unsigned long fnc);
                int UnInit      (unsigned long fnc);  // De-initialize Flash
                int EraseSector (unsigned long adr);  // Erase Sector Function
                int ProgramPage (unsigned long adr,   // Program Page Function
                                 unsigned long sz,
                                 unsigned char *buf);

   Optional  Flash Programming Functions (Called by FlashOS):
                int BlankCheck  (unsigned long adr,   // Blank Check
                                 unsigned long sz,
                                 unsigned char pat);
                int EraseChip   (void);               // Erase complete Device
      unsigned long Verify      (unsigned long adr,   // Verify Function
                                 unsigned long sz,
                                 unsigned char *buf);

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

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

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

    /* Add your Code */
    int result = 0;

    __disable_irq();

    UART0_Init(115200, USART_WordLength_8b, USART_StopBits_1b, USART_Parity_No);

    SFUD_INFO("MSP: 0x%x\n", __get_MSP());

    SFUD_INFO("Init: adr=0x%x, clk=0x%x, fnc=0x%x\n", adr, clk, fnc);

    g_size = 0;

    result = sfud_init();
    if(result != SFUD_SUCCESS) {
        return result;
    }
    return (0);                                  // Finished without Errors
}


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

int UnInit (unsigned long fnc) {

    /* Add your Code */
    SFUD_INFO("UnInit: fnc=0x%x\n", fnc);
    return (0);                                  // Finished without Errors
}


/*
 *  Erase complete Flash Memory
 *    Return Value:   0 - OK,  1 - Failed
 */

int EraseChip (void) {

  /* Add your Code */
    int result = 0;

    const sfud_flash *flash = sfud_get_device(SFUD_DEVICE_INDEX);
    result = sfud_erase (flash, 0, flash->chip.capacity);
    if (result != SFUD_SUCCESS) {
        return result;
    }

    SFUD_INFO("EraseChip ok.\n");
    return (0);                                  // Finished without Errors
}


/*
 *  Erase Sector in Flash Memory
 *    Parameter:      adr:  Sector Address
 *    Return Value:   0 - OK,  1 - Failed
 */

int EraseSector (unsigned long adr) {

    /* Add your Code */
    int result = 0;
    uint32_t block_start;
    const sfud_flash *flash = sfud_get_device(SFUD_DEVICE_INDEX);

    block_start = adr;
    result = sfud_erase (flash, block_start, 4096);
    if (result != SFUD_SUCCESS) {
        return result;
    }

    SFUD_INFO("EraseSector ok: adr=0x%x\n", adr);
    return (0);                                  // Finished without Errors
}


/*
 *  Program Page in Flash Memory
 *    Parameter:      adr:  Page Start Address
 *                    sz:   Page Size
 *                    buf:  Page Data
 *    Return Value:   0 - OK,  1 - Failed
 */

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

    /* Add your Code */
    const sfud_flash *flash = sfud_get_device(SFUD_DEVICE_INDEX);
    uint32_t start_addr = adr;

    SFUD_INFO("ProgramPage, addr=0x%x, size=%d\n", adr, sz);

    if(sfud_write(flash, start_addr, sz, buf) != SFUD_SUCCESS) {
        SFUD_INFO("ProgramPage err!\n");
        return -1;
    }

    if(start_addr < 0x20000) {
        g_size += sz;
    }

    return (0);                                  // Finished without Errors
}

unsigned long Verify(unsigned long adr, unsigned long sz, unsigned char *buf)
{
    const sfud_flash *flash = sfud_get_device(SFUD_DEVICE_INDEX);
    uint32_t start_addr = adr;
    uint32_t len;
    uint32_t n = 0;
    uint32_t size = sz;

    // SFUD_INFO("Verify: addr=0x%x, size=0x%x, buf=0x%p\n", adr, sz, buf);

    while (size)
    {
        if (size < BUF_SIZE) {
            len = size;
        } else {
            len = BUF_SIZE;
        }

        sfud_read(flash, start_addr, len, aux_buf);
        for (int i = 0; i < len; i++) {
            if (aux_buf[i] != buf[n + i]) {
                SFUD_INFO("Verify err, addr=0x%x, aux_buf[%d]=0x%x\n", start_addr + i, i, aux_buf[i]);
                return (start_addr + i);   // Verification Failed (return address)
            }
        }

        size -= len;
        start_addr += len;
        n += len;
    }
    SFUD_INFO("Verify ok: 0x%x\n", adr + sz);
    return (adr + sz);                    // Done successfully
}

int BlankCheck (unsigned long adr,unsigned long sz,unsigned char pat)
{
    const sfud_flash *flash = sfud_get_device(SFUD_DEVICE_INDEX);
    uint32_t start_addr = adr;
    uint32_t len;

    SFUD_INFO("BlankCheck: addr=0x%x, size=0x%x, pat=0x%x\n", adr, sz, pat);

    while (sz)
    {
        if (sz < BUF_SIZE) {
            len = sz;
        } else {
            len = BUF_SIZE;
        }

        sfud_read(flash, start_addr, len, aux_buf);
        for (int i = 0; i < len; i++) {
            if (aux_buf[i] != pat) {
                SFUD_INFO("BlankCheck err, addr=0x%x\n", start_addr + i);
                return (start_addr + i + 1);   // Verification Failed (return address)
            }
        }

        sz -= len;
        start_addr += len;
    }
    SFUD_INFO("BlankCheck ok: 0x%x\n", adr + sz);
    return (0);
}

6、在jflash中添加新算法

  • 1、创建文件夹
    C:\Users\'你电脑的用户名'\AppData\Roaming\SEGGER\下创建JLinkDevices文件夹。
  • 2、JLinkDevices文件结构需要按类似以下布局:
JLinkDevices
└── W25Q128
    └── W25Q128JM
        ├── W25Q128_SPI_FLASH.FLM
        └── W25Q128.xml

其中 W25Q128_SPI_FLASH.FLM就是mdk生成的flash下载算法。

  • 3、W25Q128.xml内容
<Database>
<Device>
       <ChipInfo Vendor="WINBIND" Name="Cortex-M7" WorkRAMAddr="0x20000000" WorkRAMSize="0x20000"Core="JLINK_CORE_CORTEX_M7" />
       <FlashBankInfo Name="QSPI Flash" BaseAddr="0x00000000" MaxSize="0x01000000" Loader="W25Q128_SPI_FLASH.FLM" LoaderType="FLASH_ALGO_TYPE_OPEN" />
Device>
Database>

你可能感兴趣的:(嵌入式硬件,mdk)