头图

前言

  • 1)芯片简介:SC300 + Cortex-M4F内核,最高频率 204Mhz,片上内建 640KB SRAM 和 4MB Flash

    • ARM SecurCore SC300 核心

      • 32-bit RISC Core(ARMv7-M)
      • MPU 内存保护单元
      • 最高 204MHz 主频(1、2 分频可调)
      • FPU 单元
      • 1 个受控 JTAG-DP/SW-DP 调试端口
    • 640 KB RAM
    • 1 个 QSPI 控制器, 支持 XIP
    • 4 UART、3 SPI、1 I2C、8 Timer、1 LCDI、1 DMA、1 USB、1 ADC/DAC、1 DCMI
  • 2)烧录原理:

    • (1)OpenOCD 常规烧录驱动方式为:将一小段程序传送到芯片 RAM 内,由该程序将目标文件从 RAM 中通过异步或同步的方式搬运到芯片 FLASH 中。
    • (2)AIR105 的数据手册没有列出 FLASH 的相关详情,只提供了一个 FLASH 相关的 ROM API,所以本文章通过调用 ROM API 来实现程序烧录。
  • 4)ROM API 如下:

    // air105_rom_falsh_api @ 0x00008010UL :
    #define ROM_QSPI_Init                   (*((void     (*)(QSPI_InitTypeDef    *))             (*(uint32_t *)0x8010)))
    #define ROM_QSPI_ReadID                 (*((uint32_t (*)(QSPI_CommandTypeDef *))             (*(uint32_t *)0x8014)))
    #define ROM_QSPI_WriteParam             (*((uint8_t  (*)(QSPI_CommandTypeDef *, uint16_t))   (*(uint32_t *)0x8018)))
    #define ROM_QSPI_EraseSector            (*((uint8_t  (*)(QSPI_CommandTypeDef *, uint32_t))   (*(uint32_t *)0x801C)))
    #define ROM_QSPI_EraseChip                (*((uint8_t  (*)(QSPI_CommandTypeDef *))             (*(uint32_t *)0x8020)))    // This's a reasonable guess.
    #define ROM_QSPI_ProgramPage            (*((uint8_t  (*)(QSPI_CommandTypeDef *, DMA_TypeDef *,   \
                                                            uint32_t, uint32_t, uint8_t *))      (*(uint32_t *)0x8024)))
    #define ROM_QSPI_ReleaseDeepPowerDown   (*((uint8_t  (*)(QSPI_CommandTypeDef *))             (*(uint32_t *)0x802C)))
    
  • 5)链接:

1 Air105 驱动编写

  • 1)编写驱动详细过程可以去看看我的上一篇《OpenOCD 添加 Air001》,里面介绍了为 OpenOCD 增加驱动需要修改的文件。这里我们只介绍 air105.c 文件的内容。
  • 2)同样的,air105.c 驱动程序围绕 flash\_driver 的对象 air105\_flash 建立:

    const struct flash_driver air105_flash = {
        .name = "air105",
        .commands = air105_command_handlers,
        .flash_bank_command = air105_flash_bank_command,
        .erase = air105_erase,
        .write = air105_write,
        .read = default_flash_read,
        .probe = air105_probe,
        .auto_probe = air105_auto_probe,
        .erase_check = default_flash_blank_check,
        .info = air105_get_info,
        .free_driver_priv = default_flash_free_driver_priv,
    };

1.1 air105_probe()

  • 1)函数中主要对 flash\_bank 对象进行补全,包括 FLASH 的基地址、大小、扇区相关信息等。同时我们也会这里通过 ROM\_QSPI\_Init() 对 QSPI 进行初始化(实际好像可以省略)、ROM\_QSPI\_ReadID() 读取芯片的 ID。
  • 2)调用 ROM_QSPI_ReadID() 的代码如下:

    static const uint8_t air105_read_id_code[] = {
                                /* the address of ROM_QSPI_ReadID() API @ 0x8014                        */
    
        0x4f, 0xf4, 0x00, 0x40, /* mov  r0, #0x8000     ; move 0x8000 to r0                             */
        0x41, 0x69,             /* ldr  r1, [r0, #0x14] ; Load the value of r0 offset 0x14 to r1        */
        0x00, 0x20,             /* movs r0, #0x00       ; the request param that write in r0 is NULL    */
        0x88, 0x47,             /* blx  r1              ; call the function                             */
        0x00, 0xbe,             /* bkpt #0              ; breakpoint                                    */
    };
    
    ......
     retval = target_run_algorithm(target, 0, NULL, ARRAY_SIZE(reg_params), reg_params,
                              algorithm->address, 0,
                              TIMEROUT_DEFAULT, &armv7m_info);
  • 3)由于该 ROM API 的参数可为 NULL,所以我们只需要配置好供代码运行 working\_area,然后执行 target\_run\_algorithm() 即可。

1.2 air105_write()

  • 1)其它的 ROM API 大同小异,这里我们着重提一下 air105\_write()。air105\_write() 有同步和异步两种实现方式:

    • (1)同步方式:可以开辟一块 working\_area 用来放数据,另一块用来放算法;也可以循环调用 ROM\_QSPI\_ProgramPage() 按页写入。前者由于我们已经有了异步算法所以不再实现,后者只是作为异步算法的保底操作。
    • (2)异步方式:通过 FIFO 以页为单位进行异步写入。
  • 2)异步算法:

        /* Params:
         * r0 - param of QSPI_CommandTypeDef
         * r1 - param of DMA_TypeDef
         * r2 - param of target address
         * r3 - param of count(default 256 bytes)
         * sp - param of source address
         * Clobbered(Warning: The ROM API may use low-address registers.):
         * r7  - rp
         * r8  - wp
         * r9  - workarea start
         * r10 - workarea end
         * r11 - target address
         * r12 - count(pages)
         */
    
    _start:
    wait_fifo:
        ldr     r8, [r9, #0]    /* read wp from workarea start offset 0 byte    */
        cmp     r8, #0          /* abort if wp == 0                             */
        beq     exit
        ldr     r7, [r9, #4]    /* read rp from workarea start offset 4 byte    */
        cmp     r8, r7          /* wait until rp != wp                          */
        beq     wait_fifo
    
        b       write_page      /* call ROM API to write a page(256 bytes)      */
    
    write_success:
        ldr     r7, [r9, #4]    /* read rp from workarea start offset 4 byte    */
        adds    r7, #0x100      /* rp += 256                                    */
        adds    r11, #0x100     /* target address += 256                        */
    
        cmp     r7, r10         /* wrap rp at end of buffer                     */
        bcc        no_wrap         /* jump when r7 < r10                           */
        mov        r7, r9          /* when r7 >= r10, also rp is workarea end      */
        adds    r7, #8          /* set r8 work start offset 8 byte, fifo start  */
    
    no_wrap:
        str     r7, [r9, #4]    /* store rp                                     */
        subs    r12, r12, #1    /* decrement page count                         */
        cmp     r12, #0         /* r12==0, all pages write done                 */
        beq     exit            /* loop if not done                             */
        b        wait_fifo
    
    write_page:
        mov     r0, #0x8000
        ldr     r5, [r0, #0x24] /* load ROM API at 0x8024       */
    
        movs    r0, #0x00       /* param of QSPI_CommandTypeDef */
        // movs    r1, #0x4000     /* param of DMA_TypeDef         */
        // lsls    r1, #16
        // adds    r1, #0x800
        movs    r1, #0x00
        mov     r2, r11         /* param of target address      */
        movs    r3, #0x100      /* param of count, default 256  */
        str     r7, [sp, #0x00] /* param of source address      */
        blx     r5
        cmp     r0, #0
        bne     error           /* ROM API return value not 0   */
        b       write_success
    
    error:
        movs    r7, #0          /* clear rp, wait exit          */
        str     r7, [r9, #4]
        bkpt    #0              /* call ROM API failed          */
    
    exit:
        bkpt    #0
  • 这里的异步算法就是官方示例的 FIFO 程序,我们只需要实现自已的页写入逻辑即可。

1.3 header

  • 1)Air105 将 FLASH 起始地址 0x01000000 开始的 4KB(即 0x01000000 ~ 0x01001000)作为 header,其中包含对数据的 SHA256 摘要,以及整个 header 的 CRC32。这也是为什么 Link Script 如下配置:

    MEMORY
    {
        RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 640K
        FLASH    (rx)    : ORIGIN = 0x01001000,   LENGTH = 4092K
    }
  • 参考:

2 测试

  • 1)烧录注意事项:

    • (1)禁止使用 flash fillb/fillh/fillw 命令写入超过 4KB 的内容。(因为会触发 header 更新导致程序无法运行)
    • (2)同样的,待烧录的程序不要小于 4KB。(因为不会触发 header 更新)
    • (3)以上问题的原因是:OpenOCD 在回调 .write 时,只有一个 buffer 数组以及一个 buffer 长度(区别只是是否对扇区大小取整),导致无法分辨出当前写入是 flash fillx 还是 flash write_image 命令。
  • 2)这里将对 3 种文件进行烧录测试,主要测试烧录的速度:

    • (1).elf 文件:CLion 烧录方式
    • (2).hex 文件:Keil 方式(600KB 左右烧录用时 16 秒)
    • (3).bin 文件:串口烧录方式(将 Python 项目 air105-uploader 打包成 exe 测试,烧录大文件时报错,暂时无法对比)
  • 3)烧录命令:

    flash write_image erase "/path/to/air105_mh1903s.elf"
    flash write_image erase "/path/to/air105_mh1903s.hex"
    flash write_image erase "/path/to/air105_mh1903s.bin" 0x01001000
  • 3)测试结果如下:

    • (1).elf 文件
    大小(字节)擦除扇区及时间烧录速度总计
    647168158 sectors in 8.741290s15.030012s (42.049 KiB/s)24.152393s (26.167 KiB/s)
    13926433 sectors in 2.276249s4.207788s (31.370 KiB/s)6.568403s (20.096 KiB/s)
    122883 sectors in 0.450726s1.199754s (10.002 KiB/s)1.734829s (6.917 KiB/s)
    • (2).hex 文件
    大小(字节)擦除扇区及时间烧录速度总计
    643072157 sectors in 8.936524s16.025162s (39.188 KiB/s)25.196297s (24.924 KiB/s)
    13516834 sectors in 2.342474s4.303417s (31.603 KiB/s)6.720435s (20.237 KiB/s)
    81922 sectors in 0.460953s1.549566s (5.163 KiB/s)2.077185s (3.851 KiB/s)
    • (3).bin 文件
    大小(字节)擦除扇区及时间烧录速度总计
    643072157 sectors in 8.798601s15.725622s (39.935 KiB/s)24.566441s (25.563 KiB/s)
    13516833 sectors in 2.258748s4.018007s (32.852 KiB/s)6.316407s (20.898 KiB/s)
    81922 sectors in 0.454118s1.572565s (5.087 KiB/s)2.084758s (3.837 KiB/s)
  • 4)看一下 643072 字节的 .hex 文件烧录,擦除扇区用时 9 秒,烧录 16 秒,总用时 25 秒,与 Keil 的 16 秒相比,慢了 9 秒左右。容我嘴硬一下,其实还有优化的空间:

    • (1)比如擦除时可以配置一次擦除的扇区数,如果我们配置每次擦除 50 个扇区,那么 150 个扇区只要运行 3 次擦除算法,时间上能减少 6 秒左右。问题是如果每次擦除 50 页,那么运行 ROM\_QSPI\_EraseSector() API 的算法有极大概率返回失败结果,所以目前我们按照每次擦除 15 个扇区来使用。另外,这里也无法使用片擦除(Mass Erase,虽然驱动已经提供该命令),因为擦除、烧录操作基于 QSPI,一旦片擦除 QSPI 无法运行,芯片就变砖了。
    • (2)另外,可以在调用 ROM\_QSPI\_ProgramPage() API 时使用 DMA。同样的,当我配置好 DMA 时偶尔出现死活烧录不了的情况,无奈放弃。
    • (3)还有,调用 API 时使用 target\_run\_algorithm() 函数可配置超时时间,配置 1000 毫秒时有小概率擦除或烧录失败,当前配置 1200 毫秒。
    • (4)最后,烧录时需要进行 SHA256 摘要以及 CRC-32 签名,由于对 SHA256、CRC-32 的 算法不太了解,不知是否可以进行优化,个人感觉没有优化空间。
  • 5)最后提一下,成也 QSPI,败也 QSPI。如果本驱动的 bug 或程序的 bug 导致 QSPI 无法正常运行,或者 SWD 的 IO 口 被占用,别担心,可以找一个最简单的点灯程序,使用下面的命令通过串口烧录进去即可抢救回来:

    air105-uploader.exe [comx] air105_mh1903s.bin
    • air105-uploader.exe 程序可以自已使用 pyinstaller 打包,也可以源码直接运行,源码在前言的链接中。官方也提供了一份 C# 代码 soc_download,没有仓库,我是在项目的 issues 中发现的。
  • 6)最后的最后,把 OpenOCD 驱动打包安装后,在 CLion 中测试一下,卧槽,我看到了什么?

    • 1-01.png

送南阳马生序
7 声望3 粉丝

余之业有不精、德有不成,非天质之卑,则心不若他之专耳,岂他人之过哉!