打印
[开发工具]

openocd适配APM32F402

[复制链接]
118|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
cx1524|  楼主 | 2025-6-30 17:29 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

[i=s] 本帖最后由 cx1524 于 2025-7-2 15:07 编辑 [/i]<br /> <br />

[i=s] 本帖最后由 cx1524 于 2025-7-1 09:39 编辑 [/i]

如有错误,还望斧正ヾ(≧▽≦*)o

本文章记录的是我在研究openocd的新设备适配流程时所发现的一些规律。

适配流程

向openocd中添加新设备,就相当与向openocd中添加一个新的 flash_driver

附上 flash_driver结构体内容:

struct flash_driver {
    const struct command_registration *commands;
    __FLASH_BANK_COMMAND((*flash_bank_command));
    int (*erase)(struct flash_bank *bank, unsigned int first, unsigned int last);
    int (*protect)(struct flash_bank *bank, int set, unsigned int first, unsigned int last);
    int (*write)(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count);
    int (*read)(struct flash_bank *bank, uint8_t *buffer, uint32_t offset, uint32_t count);

    int (*verify)(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count);
    int (*probe)(struct flash_bank *bank);
    int (*erase_check)(struct flash_bank *bank);
    int (*protect_check)(struct flash_bank *bank);
    int (*info)(struct flash_bank *bank, struct command_invocation *cmd);
    int (*auto_probe)(struct flash_bank *bank);
    void (*free_driver_priv)(struct flash_bank *bank);
};

一个 flash_driver至少需要提供以下几个接口 (这些接口如果没有实现,那也得保留一个return ERROR_OK,不然openocd会无法通过编译)

  • erase:擦写bank的指定Sector区域;
  • protect:开启/关闭对bank指定Sector区域的保护;
  • write:从指定地址写入多个数据;
  • probe:探测设备以确定Flash种类;
  • protect_check:检查bank是否处于保护状态;
  • info:显示Flash bank的信息;

部分接口可以自行编写或使用openocd提供的通用实现或复用已实现接口,如

  • read可以使用openocd的 default_flash_read
  • erase_check可以使用openocd的 default_flash_blank_check
  • free_driver_priv可以使用openocd的 default_flash_free_driver_priv
  • auto_probe可以复用 probe

[!TIP]

这些接口都是提供给openocd的默认指令用的, 例如我们在使用 flash write_image指令时,openocd就会调用到目标 flash_driver.auto_probe.probe.write函数。

适配中会使用到的接口

openocd提供了一套API让我们能操作设备的内存地址,常用的有:

  • target_read_u32:从指定地址处中读取1个32位数据;
  • target_read_u16:从指定地址处中读取1个16位数据;
  • target_read_u8:从指定地址处中读取1个8位数据;
  • target_write_u32:向指定地址处写入1个32位数据;
  • target_write_u16:向指定地址处写入1个16位数据;
  • target_write_u8:向指定地址处写入1个8位数据;
  • target_write_memory:从指定地址处中读取n个32位数据;
  • target_read_memory:向指定地址处写入n个32位数据;

可以使用上述API对设备寄存器进行读写以实现烧录。

以F402为例

Q: 假设现在我们要让APM32F402适配OpenOCD,要做些什么?

A: 我们首先要实现其基础功能——烧录与擦写,即实现 erasewrite

[!IMPORTANT]

openocd的开发环境是在Linux下的,这里我们可以用WSL去模拟Linux环境。

[!TIP]

如果想要监控openocd的运行流程,则可以在API中添加 LOG_USER输出信息,如果想要不同级别的提示,可以找找其他 LOG_*API。

首先先查清楚APM32F402的Flash信息以及烧写过程:

根据Flash信息给出一些寄存器宏和位域宏:

#define FLASH_KEY  0x40022004
#define FLASH_STS  0x4002200C
#define FLASH_CTRL 0x40022010
#define KEY1       0x45670123
#define KEY2       0xCDEF89AB

#define FLASH_STS_BSY       0x01
#define FLASH_CTRL_PG       0x01
#define FLASH_CTRL_PAGEERA  0x02
#define FLASH_CTRL_MASSERA  0x03

其烧写流程如下:

graph TD;
  A[解锁Flash] --> B[等待Flash busy状态结束]
  B --> C[置位CTRL寄存器中的PG位]
  C --> D[向地址写入16位数]
  D --> E[复位CTRL寄存器中的PG位]
  E --> F{写入是否结束}
  F --> |否| B
  F --> |是| G[等待Flash busy状态结束]
  G --> H[结束]

则在 writeAPI中添加如下代码:

static int write(struct flash_bank *bank, const uint8_t *buffer,
        uint32_t offset, uint32_t count)
{
    struct target *target = bank->target;
    uint32_t status = 0;
    for(;;)
    {
        if(target_read_u32(target, FLASH_STS, &status) == ERROR_OK)
        {
            if(status & FLASH_STS_BSY == 0)
            {
                break;
            }
        }

        if(timeout-- <= 0)
        {
            return ERROR_FAIL;
        }
    }
    uint32_t addr = bank->base + offset;
    count /= 2;
    target_write_memory(addr, );
}

其擦写流程如下:

graph TD;
  A[解锁Flash] --> B[等待Flash busy状态结束]
  B --> C[置位CTRL寄存器中的PG位]
  C --> D[向地址写入16位数]
  D --> E[复位CTRL寄存器中的PG位]
  E --> F{写入是否结束}
  F --> |否| B
  F --> |是| G[等待Flash busy状态结束]
  G --> H[结束]

则在 eraseAPI中添加如下代码:

附录

在openocd中可以尝试使用 target_write_memory去写FLash,不过速度会很慢。

在openocd中有一种烧录方法:用一个单独的.C或.S文件设计一个Flash烧录算法,并将其编译成elf文件,再使用脚本bin2char.sh将elf文件中的 .text段抽成十六进制数放在.inc文件中 (类似文件可以见 /contrib/loaders/flash/目录中的内容),在进行烧录流程时分别将烧录算法与 待烧录数据加载进RAM中,再执行RAM中的烧录算法进行烧录。

多bank操作

如果设备上存在多段存储空间,我们可以通过 flash bank指令分别为这些存储空间注册bank, 可以使用 flash banks查看已注册的bank信息与数量。

多目标操作

如果设备拥有多颗核心,那么openocd就需要为每一颗核心设置一个目标(target),当openocd在启动一个拥有多个目标的cfg时, 如果没有指定端口号,openocd则会从3333端口号开始,逐一递增地为目标们开辟端口。

[!IMPORTANT]

在为不同核心创建目标时,需要知道每个核心所使用的AP(Assert Port),并使用 -ap-num为目标设置AP号,这样才能实现多核场景下对核心的单独控制。

如果没有指定 -ap-num,那么所有的目标都会默认指向AP0,相当于多个目标控制 同一个核心。

核心的AP内容一般会在芯片手册的debug章节见到。

使用特权

评论回复
沙发
夜幕叙事曲| | 2025-7-1 10:21 | 只看该作者
这做完适配,还需要自己来验证可靠性,稳定性吧!
还是使用官方支持的方式吧!
极海的设计研发人员也算是踩过坑,有解决方案。

使用特权

评论回复
板凳
涡流远见者| | 2025-7-1 19:11 | 只看该作者
要使用OpenOCD的话要实现上面全部的调用函数吗?

使用特权

评论回复
评论
cx1524 2025-7-2 15:02 回复TA
是的,flash_driver中给出的接口都得实现,但如果用不到某些功能的话,只在实现函数中保留return ERROR_OK就好 
cx1524 2025-7-2 15:01 回复TA
是的,flash_driver中给出的接口都得实现,但如果用不到某些功能的话,只在实现函数中保留return ERROR_OK就好 
地板
cx1524|  楼主 | 2025-7-2 15:02 | 只看该作者
本帖最后由 cx1524 于 2025-7-2 15:06 编辑
涡流远见者 发表于 2025-7-1 19:11
要使用OpenOCD的话要实现上面全部的调用函数吗?
难蚌,回复重了

使用特权

评论回复
5
涡流远见者| | 2025-7-2 15:47 | 只看该作者
明白了。
咱们平时基本也就使用program和erase。所以楼主也就实现这两个API。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

8

主题

13

帖子

0

粉丝