# YTM32B1 Flash 驱动二进制（位置无关代码）

[English](README.md) | 简体中文

一个面向 YTM32B1 MCU 的紧凑型、位置无关（Position-Independent）Flash 驱动，编译为一个小型二进制镜像（`FlashDriver.bin`）。应用既可将它作为 C 数组内嵌，也可下载到 SRAM，再通过固定的函数表调用。该设计面向引导加载程序（Bootloader）/OTA 场景——当需要擦除、编程正在被修改的 Flash 区域时，驱动代码本身必须运行在别处。

## 项目定位与 PIC 优势

- **位置无关代码（PIC）**：镜像以 `-fpic -fpie` 编译，不含任何绝对地址假设。主机应用从镜像基址（`FLS_DRV_BASE_ADDR`）处的函数表中解析每个函数入口，因此二进制可被加载到不同的 SRAM/Flash 地址而无需重新编译。
- **从 SRAM 运行**：驱动在 SRAM 中执行，因此可以擦除、编程*任意* Flash 区域，包括应用自身所在的区域——这正是 Bootloader/OTA 更新的典型需求。
- **占用极小**：整个镜像（函数表 + 代码 + 常量）必须放入 4 KB 链接区域（`link/relative.ld`）；当前 YTM32B1ME1 构建产物仅约 856 字节。
- **单头文件集成**：主机侧只需 `fls_drv_bin.h`（数组模式下另需生成的 data 头文件）。API 签名遵循 SDK Flash 驱动惯例并返回 `status_t`，覆盖引导加载所需的核心操作（Init/Deinit、扇区擦除、编程、BootSwap）。它是 SDK 驱动的紧凑子集，并非完整替代品。
- **多芯片一次构建**：一次 CMake 调用即可产出全部芯片变体。

## 目录结构

```
flash_driver_bin/
├── bin_project/                  # Flash 驱动源码工程（生成二进制镜像）
│   ├── CMakeLists.txt            # 要求 CMake >= 3.21；为每个芯片各生成一个镜像
│   ├── flash_driver.c            # Flash 驱动实现（PIC）
│   ├── flash_driver.h            # 驱动头文件（API 文档）
│   ├── fls_drv_table.c/.h        # 位于镜像起始处的函数表
│   ├── fls_drv_bin.h             # 主机侧头文件：TEST_BY_ARRAY、FLS_DRV_BASE_ADDR、API 宏
│   ├── link/relative.ld          # 链接脚本（4 KB m_text 区域）
│   ├── CMSIS/                    # CMSIS 内核头文件
│   ├── platform/devices/         # 8 款支持芯片的设备头文件/启动文件
│   ├── tools/bin2h.exe           # Bin → C 数组转换工具（Windows 可执行文件）
│   └── build/                    # 本地构建输出（已被 git 忽略，由构建生成）
├── YTM32B1HA0_Flash_Demo/        # YTM32B1HA0 演示工程（Flash_Demo）
├── YTM32B1LE1_Flash_Demo/        # YTM32B1LE1 演示工程（Flash_Demo）
├── YTM32B1MC0_Flash_Demo/        # YTM32B1MC0 演示工程（Flash_Demo）
├── YTM32B1MD2_FlashDemo/         # YTM32B1MD2 演示工程（Flash_Demo）
├── YTM32B1ME1_Flash_Demo/        # YTM32B1ME1 演示工程（Flash_Demo）
└── LICENSE.md                    # BSD-3-Clause 许可证
```

## 支持的芯片与 Demo

驱动源码覆盖 8 款芯片，每次构建为每款芯片各产出一个镜像：

| 芯片 | 芯片 | 芯片 |
|---|---|---|
| YTM32B1HA0 | YTM32B1MC0 | YTM32B1ME0 |
| YTM32B1LE0 | YTM32B1MD1 | YTM32B1ME1 |
| YTM32B1LE1 | YTM32B1MD2 | |

仓库附带 5 个可直接运行的演示工程，通过二进制驱动演示擦除/编程/回读校验（`app/fls_drv_bin.h` + `app/fls_drv_bin_data.h` + `app/main.c`）。每个演示都是完整的独立 SDK 工程（自带 CMakeLists 与工具链文件），可按其各自 ReadMe 中描述的 CMake/Ninja 流程构建：

| 演示目录 | 芯片 | 工程名 |
|---|---|---|
| `YTM32B1HA0_Flash_Demo/` | YTM32B1HA0 | Flash_Demo |
| `YTM32B1LE1_Flash_Demo/` | YTM32B1LE1 | Flash_Demo |
| `YTM32B1MC0_Flash_Demo/` | YTM32B1MC0 | Flash_Demo |
| `YTM32B1MD2_FlashDemo/` | YTM32B1MD2 | Flash_Sync_Demo |
| `YTM32B1ME1_Flash_Demo/` | YTM32B1ME1 | Flash_Demo |

## 前置工具

- **CMake** >= 3.21（`bin_project/CMakeLists.txt` 的要求）
- **Ninja** 构建系统
- **Arm GNU 工具链**（`arm-none-eabi-gcc`、`arm-none-eabi-objcopy`、`arm-none-eabi-objdump`、`arm-none-eabi-ar`、`arm-none-eabi-size`）

YCT（云途的工具链/IDE 环境）可提供 Ninja 与 Arm GNU 工具链。本仓库不假设任何具体安装路径——调用 CMake 前请确保上述工具已在 `PATH` 中（或在工具链管理器中完成配置）。

构建时还会使用仓库自带的 `bin_project/tools/bin2h.exe` 将二进制转换为 C 数组头文件。

## 构建

CMake 工程位于 `bin_project/`（仓库顶层没有 CMakeLists）。在仓库根目录执行：

```sh
cmake -S bin_project -B bin_project/build -G Ninja
cmake --build bin_project/build
```

或在 `bin_project/` 目录内执行：

```sh
cmake -S . -B build -G Ninja
cmake --build build
```

一次构建即可产出全部 8 款芯片的镜像（`bin_project/CMakeLists.txt` 自动枚举 `platform/devices/YTM*`）。当前工程会自行追加优化与调试选项；如需调整量产构建选项，请先核对 `bin_project/CMakeLists.txt`。

> `bin_project/build/` 是本地生成目录且已被 git 忽略。请始终重新构建，不要依赖任何预置构建目录。

## 构建产物

每款芯片的输出位于 `bin_project/build/<CHIP>/`：

| 产物（相对仓库根目录） | 说明 |
|---|---|
| `bin_project/build/<CHIP>/FlashDriver.bin` | 原始二进制，用于下载到 SRAM（集成方式 B） |
| `bin_project/build/<CHIP>/FlashDriver.hex` | Intel HEX 格式 |
| `bin_project/build/<CHIP>/FlashDriver.s19` | Motorola S-record 格式 |
| `bin_project/build/<CHIP>/FlashDriver.lst` | `objdump -h -S` 反汇编清单 |
| `bin_project/build/<CHIP>/fls_drv_bin_data.h` | 二进制的 C 数组（`bin_data[]`），用于数组模式（方式 A） |
| `bin_project/build/<CHIP>/<CHIP>_FlashDriver.elf` | 含符号的 ELF，如 `YTM32B1ME1_FlashDriver.elf` |

示例：`bin_project/build/YTM32B1ME1/FlashDriver.bin`。

## 集成方式

主机侧入口是 **`bin_project/fls_drv_bin.h`**。它通过两个宏选择集成模式：

```c
#define TEST_BY_ARRAY  /* 若改为下载 bin 到 SRAM，请注释此行 */

#ifdef TEST_BY_ARRAY
#include "fls_drv_bin_data.h"
#define FLS_DRV_BASE_ADDR ((uint32_t)bin_data)
#else
#define FLS_DRV_BASE_ADDR ((uint32_t)0x20004000)  /* bin 下载到 SRAM 的地址 */
#endif /* TEST_BY_ARRAY */
```

`TEST_BY_ARRAY` 与 `FLS_DRV_BASE_ADDR` 均定义在 **`bin_project/fls_drv_bin.h`** 中——请在该文件内修改（该头文件还依赖 `status.h` 提供 `status_t`，由主机 SDK 提供）。

### 方式 A —— 数组测试模式（默认）

1. 保持 `TEST_BY_ARRAY` 已定义（默认状态）。
2. 将 `bin_project/fls_drv_bin.h` 与生成的 `bin_project/build/<CHIP>/fls_drv_bin_data.h` 复制到应用工程中。各演示工程已在 `app/` 目录下自带这两份文件。
3. 驱动代码随应用镜像链接、从 Flash 运行——适合快速验证，前提是所测试的区域不是驱动自身所在的区域。

### 方式 B —— 下载到 SRAM 运行

1. 在 `bin_project/fls_drv_bin.h` 中注释掉 `#define TEST_BY_ARRAY`。
2. 将 `FLS_DRV_BASE_ADDR` 改为 `FlashDriver.bin` 将要下载到的 SRAM 地址（默认 `0x20004000`）。
3. 将 `bin_project/build/<CHIP>/FlashDriver.bin` 下载到该 SRAM 地址。
4. PIC 驱动随后从 SRAM 运行，可安全擦除/编程任意 Flash 区域（包括应用自身所在区域）——即 Bootloader/OTA 的典型用法。

### PRESCALER：必须在 Init 之前配置

调用 `FLASH_DRV_Init()`（以及任何擦除/编程 API）之前，**EFM CTRL 寄存器中的 PRESCALER 字段必须与当前 Flash 工作时钟匹配**；`FLASH_DRV_Init()` 本身不会配置它。每次时钟变更后都要重新配置。

SDK 时钟驱动中的计算方式如下（见 `platform/drivers/src/clock/YTM32B1Mx/clock_YTM32B1Mx.c` 与 `.../YTM32B1Lx/clock_YTM32B1Lx.c`）：

```c
tempReg &= ~(EFM_CTRL_PRESCALER_MASK);
EFM->CTRL = tempReg | EFM_CTRL_PRESCALER(tempFreq / 2000000U);   /* tempFreq = Flash 工作时钟（Hz） */
```

即 **`PRESCALER = flashClockHz / 2,000,000`**，其中 `flashClockHz` 为 Flash 工作时钟源（`FEATURE_EFM_OPERATION_CLOCK_SOURCE`，例如 `SLOW_BUS_CLK`）的频率。请始终以目标芯片的 SDK 时钟驱动和参考手册为准——不同芯片的时钟源与取整规则可能不同（SVD 描述为"慢速总线时钟 [MHz] / 2，向上取整"，部分芯片使用内核时钟）。

## 读校验（`FLASH_READ_VERIFY_ENABLE`）

读校验让 EFM 在每次擦除/编程命令后执行一次校验读，使命令错误由驱动自身直接报告，而不必等到之后的回读比对才发现。它对应 SDK Flash 驱动状态中的 `readVerify` 字段，但由 `bin_project/flash_driver.h` 中的 `FLASH_READ_VERIFY_ENABLE` 在编译期固定（宏有 `#ifndef` 保护，先前的定义优先）：

- **`1U` —— 使能（默认）。** 在定义了 `EFM_CTRL_CMD_VERIFY_EN_MASK` 的芯片上（如 YTM32B1ME1/HA0/LE1/MC0/MD2），`FLASH_DRV_Init()` 会置位该位。在定义了 `FEATURE_EFM_ERASE_BLOCK_VERIFY_CMD_CODE` / `FEATURE_EFM_ERASE_SECTOR_VERIFY_CMD_CODE` / `FEATURE_EFM_PROGRAM_VERIFY_CMD_CODE` 的芯片上（如 YTM32B1MD1、YTM32B1ME0），擦除/编程函数会使用 VERIFY 命令码。
- **`0U` —— 关闭。** `FLASH_DRV_Init()` 清除 `EFM_CTRL_CMD_VERIFY_EN_MASK`，并使用普通（非校验）命令码。操作更快；如需要请自行回读检查。

要在构建中关闭它，可以修改 `bin_project/flash_driver.h` 中的默认值（将 `FLASH_READ_VERIFY_ENABLE` 设为 `0U`），或按[构建](#构建)一节所示向编译器传入定义（`-DCMAKE_C_FLAGS=-DFLASH_READ_VERIFY_ENABLE=0U`）。该宏仅在编译期生效——不涉及任何运行期状态或 API，且必须重新构建驱动镜像才会生效。

## API 参考

镜像起始处为函数表（`fls_drv_tbl_t`）；`fls_drv_bin.h` 中的宏在 `FLS_DRV_BASE_ADDR` 处读取函数表，并将基址加到每个表项上完成跳转（PIC 分发）。链接入口符号为 `flashdriver_table`。

| 宏 | 说明 |
|---|---|
| `FLASH_DRV_GetVersion()` | 返回函数表版本号（`FLS_DRV_VERSION = 1`）。可用于校验 `FLS_DRV_BASE_ADDR` 处的表是否正确。 |
| `FLASH_DRV_Init()` | 初始化驱动；在支持的器件上设置最大 EFM TIMING 寄存器值，并按 `FLASH_READ_VERIFY_ENABLE`（默认 `1U` = 使能）设置/清除 `CMD_VERIFY_EN`（见[读校验](#读校验flash_read_verify_enable)）。请在 PRESCALER 配置完成后、任何擦除/编程操作之前调用。 |
| `FLASH_DRV_Deinit()` | 反初始化驱动。当前为空操作，恒返回 `STATUS_SUCCESS`。 |
| `FLASH_DRV_EraseSector(dest, size)` | 同步擦除主 Flash/数据 Flash 的一个或多个扇区。对齐要求见下文。 |
| `FLASH_DRV_Program(dest, size, pData)` | 同步编程数据。对齐要求见下文。 |
| `FLASH_DRV_BootSwap()` | 交换引导 Flash 块。**按芯片可选**——见下方警告。 |

## 返回值（`status_t`）

API 返回 SDK 风格的 `status_t`（见 `platform/devices/status.h`）。本驱动最常返回的值：

| 值 | 含义 |
|---|---|
| `STATUS_SUCCESS`（`0x000`） | 操作成功完成 |
| `STATUS_ERROR`（`0x001`） | 操作失败（例如 EFM 命令错误标志置位） |
| `STATUS_BUSY`（`0x002`） | EFM 控制器忙——操作未执行 |
| `STATUS_EFM_BUSY`（`0x903`） | EFM 忙（`Program`/`BootSwap` 路径返回） |
| `STATUS_InvalidArgument`（`0x006`） | 参数无效（如对齐不满足），由 `DEV_ASSERT` 拒绝 |
| `STATUS_EFM_ADDRESS_OUT_OF_RANGE`（`0x906`） | 擦除范围超出 Flash 阵列，或跨入扇区大小不同的阵列（`EraseSector`） |

公共头文件中还有其它 SDK 错误码（如 `STATUS_TIMEOUT`、`STATUS_UNSUPPORTED`），但本驱动不会产生它们。注意：当 `size` 为 0 时，`EraseSector`/`Program` 不做任何输入校验直接返回成功。

## 对齐与长度约束

- **`FLASH_DRV_EraseSector(dest, size)`**：`dest` 必须按目标阵列的扇区大小对齐（`FEATURE_EFM_MAIN_ARRAY_SECTOR_SIZE` 或 `FEATURE_EFM_DATA_ARRAY_SECTOR_SIZE`），`size` 必须是其整数倍。驱动根据目标地址自动判断扇区大小。
- **`FLASH_DRV_Program(dest, size, pData)`**：`dest` 与 `size` 必须按 `FEATURE_EFM_WRITE_UNIT_SIZE`（编程写入单元，如 YTM32B1ME1 为 8 字节）对齐，且 `pData` 必须 **32 位（4 字节）对齐**。数据按写入单元分块编程。
- 上述约束由二进制驱动内部的 `DEV_ASSERT` 检查（始终生效——检查失败返回 `STATUS_InvalidArgument`）；请在调用处保证对齐，所有构建均需满足。
- Flash 只能写入已擦除的区域——编程前目标区域必须为 `0xFF`，请先擦除扇区。
- 擦除/编程为阻塞操作（驱动轮询 EFM 状态寄存器直至命令完成）。

## 中断与 Cache 注意事项

- **中断**：命令执行器在忙等循环中轮询 EFM 状态寄存器。本工程默认构建**未**定义 `EFM_AUTO_DISABLE_GLOBAL_INT`，因此驱动不会自行屏蔽全局中断。如果中断服务程序（ISR）可能执行位于正在擦除/编程 Flash 区域中的代码或访问该区域，请在调用前屏蔽中断。注意这取决于内存布局：YTM32B1ME1 演示工程注释指出，当代码运行在与操作目标不同的 Flash 块时，可能无需屏蔽中断。请以目标芯片参考手册为准。
- **Cache**：当芯片支持时（`FEATURE_SOC_SUPPORT_CACHE`），驱动会在每次命令完成后使 Flash cache 失效；数据 cache 存在且使能时同样会被 clean/invalidate——即在这些配置下 cache 一致性由驱动内部处理。YTM32B1MD2 演示工程另建议在操作 P-Flash 前禁用 Flash cache，作为最简单、不易出错的方式。请结合目标芯片实测并遵循其参考手册；编程后回读校验始终是推荐做法。

## BootSwap —— 高风险操作

`FLASH_DRV_BootSwap()` 交换引导 Flash 块与当前 Flash 块，会**持久性地改变复位后启动的镜像**。

- `bootSwap` 表项仅在芯片定义了 `FEATURE_EFM_BOOT_SWAP_CMD_CODE` 时存在（`fls_drv_bin.h` 中标注为"按芯片可选"）。在不支持的芯片上调用 `FLASH_DRV_BootSwap()` 会读取不存在的表项——除非目标芯片支持 BootSwap，否则不要调用。
- 错误或被打断的交换序列可能使系统无法启动预期镜像。仅在具备已验证恢复路径的 Bootloader 流程中使用，并在量产前充分测试。

## 4 KB 链接区域限制

`bin_project/link/relative.ld` 将整个镜像映射到单一区域：

```
m_text (RX) : ORIGIN = 0x00000000, LENGTH = 4096
```

驱动镜像（函数表 + 代码 + 常量）必须小于 4 KB。若超出限制，链接将报 "region `m_text` overflowed" 错误——可通过 `FlashDriver.map`（`--print-memory-usage` 输出）查看当前占用。

## 许可证

本项目采用 **BSD-3-Clause** 许可证（见 [LICENSE.md](LICENSE.md)），Copyright (c) 2020-2025 Yuntu Microelectronics Co., Ltd.（云途半导体）。

## Troubleshooting / 检查清单

1. **校验函数表**：`FLASH_DRV_GetVersion()` 应返回 `1`。否则说明 `FLS_DRV_BASE_ADDR` 处的镜像不正确（数组未链接，或二进制下载到了错误的 SRAM 地址）。
2. **PRESCALER**：在 `FLASH_DRV_Init()` 之前且每次时钟变更后将 EFM CTRL 的 PRESCALER 配置为 `flashClockHz / 2,000,000`；以目标芯片 SDK/参考手册为准。
3. **对齐**：`EraseSector` 的 `dest`/`size` 为扇区大小整数倍；`Program` 的 `dest`/`size` 为 `FEATURE_EFM_WRITE_UNIT_SIZE` 整数倍且 `pData` 4 字节对齐。
4. **先擦除再编程**：编程前目标区域必须为 `0xFF`（已擦除）；操作后回读校验。
5. **SRAM 模式**：`FlashDriver.bin` 必须实际加载到 `FLS_DRV_BASE_ADDR` 处；地址错误会在首次 API 调用时导致硬错误（Hard Fault）或未定义行为。
6. **链接溢出**：若构建报 "region `m_text` overflowed"，说明镜像超过 4 KB 链接区域。
7. **缺少 `bin2h.exe`**：若 `bin_project/tools/bin2h.exe` 不存在，生成 `fls_drv_bin_data.h` 的构建后步骤将失败。
8. **中断/cache**：若 ISR 可能访问被操作的 Flash 区域，请在擦除/编程期间屏蔽中断；建议在 Flash 操作前禁用 cache。
9. **BootSwap**：仅在芯片支持时调用，且仅在具备已验证恢复路径的流程中使用。
10. **不建议将驱动作为常驻可执行内容持久保存**：函数表中保存的是基于 `FLS_DRV_BASE_ADDR` 解析的相对偏移。建议仅在需要执行 Flash 操作时，将镜像加载到受控的 SRAM 地址，并在调用任何擦除/编程 API 前校验基址和 `FLASH_DRV_GetVersion()`。若基址错误、镜像损坏或程序异常跑飞，可能解析到错误的函数地址，进而造成 Flash 误擦除或误编程。
