OpenOCD 添加 AIR105(上)
前言
-
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
- ARM SecurCore SC300 核心
-
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 文件
大小(字节) 擦除扇区及时间 烧录速度 总计 647168 158 sectors in 8.741290s 15.030012s (42.049 KiB/s) 24.152393s (26.167 KiB/s) 139264 33 sectors in 2.276249s 4.207788s (31.370 KiB/s) 6.568403s (20.096 KiB/s) 12288 3 sectors in 0.450726s 1.199754s (10.002 KiB/s) 1.734829s (6.917 KiB/s) - (2).hex 文件
大小(字节) 擦除扇区及时间 烧录速度 总计 643072 157 sectors in 8.936524s 16.025162s (39.188 KiB/s) 25.196297s (24.924 KiB/s) 135168 34 sectors in 2.342474s 4.303417s (31.603 KiB/s) 6.720435s (20.237 KiB/s) 8192 2 sectors in 0.460953s 1.549566s (5.163 KiB/s) 2.077185s (3.851 KiB/s) - (3).bin 文件
大小(字节) 擦除扇区及时间 烧录速度 总计 643072 157 sectors in 8.798601s 15.725622s (39.935 KiB/s) 24.566441s (25.563 KiB/s) 135168 33 sectors in 2.258748s 4.018007s (32.852 KiB/s) 6.316407s (20.898 KiB/s) 8192 2 sectors in 0.454118s 1.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 中测试一下,卧槽,我看到了什么?