openocd适配APM32F402
本帖最后由 cx1524 于 2025-7-2 15:07 编辑本帖最后由 cx1524 于 2025-7-1 09:39 编辑
> 如有错误,还望斧正ヾ(≧▽≦*)o
本文章记录的是我在研究openocd的新设备适配流程时所发现的一些规律。
## 适配流程
向openocd中添加新设备,就相当与向openocd中添加一个新的 `flash_driver`。
附上 `flash_driver`结构体内容:
```cpp
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: 我们首先要实现其基础功能——烧录与擦写,即实现 `erase`和 `write`。
> [!IMPORTANT]
>
> openocd的开发环境是在Linux下的,这里我们可以用WSL去模拟Linux环境。
> [!TIP]
>
> 如果想要监控openocd的运行流程,则可以在API中添加 `LOG_USER`输出信息,如果想要不同级别的提示,可以找找其他 `LOG_*`API。
首先先查清楚APM32F402的Flash信息以及烧写过程:
根据Flash信息给出一些寄存器宏和位域宏:
```c
#define FLASH_KEY0x40022004
#define FLASH_STS0x4002200C
#define FLASH_CTRL 0x40022010
#define KEY1 0x45670123
#define KEY2 0xCDEF89AB
#define FLASH_STS_BSY 0x01
#define FLASH_CTRL_PG 0x01
#define FLASH_CTRL_PAGEERA0x02
#define FLASH_CTRL_MASSERA0x03
```
其烧写流程如下:
```mermaid
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[结束]
```
则在 `write`API中添加如下代码:
```c
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, );
}
```
其擦写流程如下:
```mermaid
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[结束]
```
则在 `erase`API中添加如下代码:
```c
```
## 附录
在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章节见到。
这做完适配,还需要自己来验证可靠性,稳定性吧!
还是使用官方支持的方式吧!
极海的设计研发人员也算是踩过坑,有解决方案。 要使用OpenOCD的话要实现上面全部的调用函数吗? 本帖最后由 cx1524 于 2025-7-2 15:06 编辑
涡流远见者 发表于 2025-7-1 19:11
要使用OpenOCD的话要实现上面全部的调用函数吗?难蚌,回复重了 明白了。
咱们平时基本也就使用program和erase。所以楼主也就实现这两个API。
{:victory:}
页:
[1]