# YTM32B1 Flash Driver Binary (Position-Independent)

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

A compact, position-independent flash driver for YTM32B1 MCUs, compiled into a small binary image (`FlashDriver.bin`) that an application can either embed as a C array or download into SRAM and call through a fixed function table. It is designed for bootloader/OTA scenarios where flash must be erased and programmed from code that runs outside the flash region being modified.

## Highlights

- **Position-Independent Code (PIC)**: the image is built with `-fpic -fpie` and contains no absolute address assumptions. The host application resolves every function entry from the table stored at the image base (`FLS_DRV_BASE_ADDR`), so the binary can be loaded at different SRAM/Flash addresses without rebuilding.
- **Runs from RAM**: because the driver executes from SRAM, it can erase and program *any* flash region, including the region the application itself runs from — the typical requirement for bootloader/OTA updates.
- **Tiny footprint**: the whole image (function table + code + constants) must fit in a 4 KB link area (`link/relative.ld`); a current YTM32B1ME1 build is about 856 bytes.
- **Single-header integration**: the host side only needs `fls_drv_bin.h` (plus the generated data header in array mode). API signatures follow the SDK flash driver conventions and return `status_t`, covering the core operations used by bootloaders (init/deinit, sector erase, program, boot swap). It is a compact subset and is not a full replacement for the SDK flash driver.
- **Multi-chip, one build**: all supported chip variants are produced in a single CMake invocation.

## Repository Structure

```
flash_driver_bin/
├── bin_project/                  # Flash driver source project (builds the binary)
│   ├── CMakeLists.txt            # CMake >= 3.21; builds one image per chip
│   ├── flash_driver.c            # Flash driver implementation (PIC)
│   ├── flash_driver.h            # Driver header (API documentation)
│   ├── fls_drv_table.c/.h        # Function table placed at the image start
│   ├── fls_drv_bin.h             # Host-side header: TEST_BY_ARRAY, FLS_DRV_BASE_ADDR, API macros
│   ├── link/relative.ld          # Linker script (4 KB m_text region)
│   ├── CMSIS/                    # CMSIS core headers
│   ├── platform/devices/         # Device headers/startup for the 8 supported chips
│   ├── tools/bin2h.exe           # Bin → C array converter (Windows executable)
│   └── build/                    # Local build output (git-ignored, generated)
├── YTM32B1HA0_Flash_Demo/        # Demo app for YTM32B1HA0 (Flash_Demo)
├── YTM32B1LE1_Flash_Demo/        # Demo app for YTM32B1LE1 (Flash_Demo)
├── YTM32B1MC0_Flash_Demo/        # Demo app for YTM32B1MC0 (Flash_Demo)
├── YTM32B1MD2_FlashDemo/         # Demo app for YTM32B1MD2 (Flash_Demo)
├── YTM32B1ME1_Flash_Demo/        # Demo app for YTM32B1ME1 (Flash_Demo)
└── LICENSE.md                    # BSD-3-Clause
```

## Supported Chips and Demos

The driver source tree covers 8 chips; each build produces one image per chip:

| Chip | Chip | Chip |
|---|---|---|
| YTM32B1HA0 | YTM32B1MC0 | YTM32B1ME0 |
| YTM32B1LE0 | YTM32B1MD1 | YTM32B1ME1 |
| YTM32B1LE1 | YTM32B1MD2 | |

Five ready-to-run demo projects demonstrate erase/program/read-back verification through the binary driver (`app/fls_drv_bin.h` + `app/fls_drv_bin_data.h` + `app/main.c`). Each demo is a complete standalone SDK project (its own CMakeLists and toolchain files) and can be built with the same CMake/Ninja workflow described in its own ReadMe:

| Demo directory | Chip | Project name |
|---|---|---|
| `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 |

## Prerequisites

- **CMake** >= 3.21 (required by `bin_project/CMakeLists.txt`)
- **Ninja** build system
- **Arm GNU Toolchain** (`arm-none-eabi-gcc`, `arm-none-eabi-objcopy`, `arm-none-eabi-objdump`, `arm-none-eabi-ar`, `arm-none-eabi-size`)

YCT (Yuntu's toolchain/IDE environment) can provide both Ninja and the Arm GNU toolchain. No specific installation path is assumed — make sure the tools are on your `PATH` (or configured in your toolchain manager) before invoking CMake.

`bin_project/tools/bin2h.exe` (included in the repository) is used at build time to convert the binary into the C array header.

## Build

The CMake project lives in `bin_project/` (there is no top-level CMakeLists). From the repository root:

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

or from inside `bin_project/`:

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

All 8 chip images are built in a single pass (`bin_project/CMakeLists.txt` enumerates `platform/devices/YTM*`). The current project applies its own optimization and debug flags; review `bin_project/CMakeLists.txt` before changing these flags for production builds.

> `bin_project/build/` is generated locally and git-ignored. Always rebuild; do not rely on any prebuilt directory being present or up to date.

Compile-time options are passed through `CMAKE_C_FLAGS`. For example, to disable read verify (see [Read Verify](#read-verify-flash_read_verify_enable)):

```sh
cmake -S bin_project -B bin_project/build -G Ninja -DCMAKE_C_FLAGS=-DFLASH_READ_VERIFY_ENABLE=0U
cmake --build bin_project/build
```

Re-run CMake when changing `CMAKE_C_FLAGS` — the definition must reach the compiler for the target build.

## Build Artifacts

Each chip's outputs are written to `bin_project/build/<CHIP>/`:

| Artifact (relative to repo root) | Description |
|---|---|
| `bin_project/build/<CHIP>/FlashDriver.bin` | Raw binary to download to SRAM (integration method B) |
| `bin_project/build/<CHIP>/FlashDriver.hex` | Intel HEX format |
| `bin_project/build/<CHIP>/FlashDriver.s19` | Motorola S-record format |
| `bin_project/build/<CHIP>/FlashDriver.lst` | `objdump -h -S` listing |
| `bin_project/build/<CHIP>/fls_drv_bin_data.h` | C array of the binary (`bin_data[]`), for array mode (method A) |
| `bin_project/build/<CHIP>/<CHIP>_FlashDriver.elf` | ELF with symbols, e.g. `YTM32B1ME1_FlashDriver.elf` |

Example: `bin_project/build/YTM32B1ME1/FlashDriver.bin`.

## Integration

The host-side entry point is **`bin_project/fls_drv_bin.h`**. It defines two switches that select the integration mode:

```c
#define TEST_BY_ARRAY  /* Comment this line if download bin file to 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)  /* SRAM address of the downloaded bin */
#endif /* TEST_BY_ARRAY */
```

Both `TEST_BY_ARRAY` and `FLS_DRV_BASE_ADDR` are defined in `bin_project/fls_drv_bin.h` — edit them there (the header also depends on `status.h` for `status_t`, which the host SDK provides).

### Method A — Array test mode (default)

1. Keep `TEST_BY_ARRAY` defined (default).
2. Copy `bin_project/fls_drv_bin.h` and the generated `bin_project/build/<CHIP>/fls_drv_bin_data.h` into your application project. The demos already contain these copies under `app/`.
3. The driver code is linked into the application image and runs from flash — convenient for quick demos where the region being tested is not the region the driver itself executes from.

### Method B — Download to SRAM

1. In `bin_project/fls_drv_bin.h`, comment out `#define TEST_BY_ARRAY`.
2. Set `FLS_DRV_BASE_ADDR` to the SRAM address where `FlashDriver.bin` will be placed (default `0x20004000`).
3. Download `bin_project/build/<CHIP>/FlashDriver.bin` to that SRAM address.
4. The PIC driver then runs from SRAM and can safely erase/program any flash region, including the region the application runs from — the setup used by bootloaders/OTA.

### PRESCALER: configure before Init

Before calling `FLASH_DRV_Init()` (or any erase/program API), the **PRESCALER field in the EFM CTRL register must match the current flash operation clock**; `FLASH_DRV_Init()` itself does not configure it. Re-configure it after every clock change.

The SDK clock driver computes it as (see `platform/drivers/src/clock/YTM32B1Mx/clock_YTM32B1Mx.c` and `.../YTM32B1Lx/clock_YTM32B1Lx.c`):

```c
tempReg &= ~(EFM_CTRL_PRESCALER_MASK);
EFM->CTRL = tempReg | EFM_CTRL_PRESCALER(tempFreq / 2000000U);   /* tempFreq = flash operation clock (Hz) */
```

i.e. **`PRESCALER = flashClockHz / 2,000,000`**, where `flashClockHz` is the frequency of the flash operation clock source (`FEATURE_EFM_OPERATION_CLOCK_SOURCE`, e.g. `SLOW_BUS_CLK`). Always follow the target chip's SDK clock driver and reference manual — the exact clock source and rounding rule (the SVD describes "slow bus clock [MHz] / 2, round up", and some chips use the core clock) can differ per chip.

## Read Verify (`FLASH_READ_VERIFY_ENABLE`)

Read verify makes the EFM perform a verify-read after each erase/program command, so command errors are reported by the driver itself instead of only by a later read-back. It mirrors the `readVerify` field of the SDK flash driver state, but is fixed at compile time by `FLASH_READ_VERIFY_ENABLE` in `bin_project/flash_driver.h` (the macro is `#ifndef`-guarded, so a prior definition wins):

- **`1U` — enabled (default).** On chips that define `EFM_CTRL_CMD_VERIFY_EN_MASK` (e.g. YTM32B1ME1/HA0/LE1/MC0/MD2), `FLASH_DRV_Init()` sets the bit. On chips that define `FEATURE_EFM_ERASE_BLOCK_VERIFY_CMD_CODE` / `FEATURE_EFM_ERASE_SECTOR_VERIFY_CMD_CODE` / `FEATURE_EFM_PROGRAM_VERIFY_CMD_CODE` (e.g. YTM32B1MD1, YTM32B1ME0), the erase/program functions issue the VERIFY command codes.
- **`0U` — disabled.** `FLASH_DRV_Init()` clears `EFM_CTRL_CMD_VERIFY_EN_MASK` and the normal (non-verify) command codes are used. Operations complete faster; perform your own read-back check if needed.

To disable it for a build, either edit the default value in `bin_project/flash_driver.h` (set `FLASH_READ_VERIFY_ENABLE` to `0U`) or pass the definition to the compiler as shown in [Build](#build) (`-DCMAKE_C_FLAGS=-DFLASH_READ_VERIFY_ENABLE=0U`). The macro is compile-time only — no runtime state or API is involved, and the driver image must be rebuilt for the setting to take effect.

## API Reference

The image starts with a function table (`fls_drv_tbl_t`); the macros in `fls_drv_bin.h` read the table at `FLS_DRV_BASE_ADDR` and add the base address to each entry (PIC dispatch). The entry point symbol is `flashdriver_table`.

| Macro | Description |
|---|---|
| `FLASH_DRV_GetVersion()` | Returns the table version (`FLS_DRV_VERSION = 1`). Useful as a sanity check that the table is present at `FLS_DRV_BASE_ADDR`. |
| `FLASH_DRV_Init()` | Initializes the driver: sets the maximum EFM TIMING registers where supported, and sets/clears `CMD_VERIFY_EN` according to `FLASH_READ_VERIFY_ENABLE` (default `1U` = verify enabled — see [Read Verify](#read-verify-flash_read_verify_enable)). Call it after configuring PRESCALER and before any erase/program operation. |
| `FLASH_DRV_Deinit()` | De-initializes the driver. Currently a no-op that always returns `STATUS_SUCCESS`. |
| `FLASH_DRV_EraseSector(dest, size)` | Erases one or more sectors of main/data flash synchronously. See alignment constraints below. |
| `FLASH_DRV_Program(dest, size, pData)` | Programs data synchronously. See alignment constraints below. |
| `FLASH_DRV_BootSwap()` | Swaps the boot flash blocks. **Optional by chip** — see the warning below. |

## Return Values (`status_t`)

APIs return SDK-style `status_t` values (see `platform/devices/status.h`). The most common values returned by this driver:

| Value | Meaning |
|---|---|
| `STATUS_SUCCESS` (`0x000`) | Operation completed successfully |
| `STATUS_ERROR` (`0x001`) | Operation failed (e.g. EFM command error flag set) |
| `STATUS_BUSY` (`0x002`) | EFM controller busy — operation not started |
| `STATUS_EFM_BUSY` (`0x903`) | EFM busy (returned by `Program`/`BootSwap` paths) |
| `STATUS_InvalidArgument` (`0x006`) | Invalid argument (e.g. misalignment) rejected by `DEV_ASSERT` |
| `STATUS_EFM_ADDRESS_OUT_OF_RANGE` (`0x906`) | Erase range left the flash arrays or crossed arrays with different sector sizes (`EraseSector`) |

Other SDK codes (e.g. `STATUS_TIMEOUT`, `STATUS_UNSUPPORTED`) exist in the common header but are not produced by this driver. Note that `EraseSector`/`Program` return success when `size` is zero without any input validation.

## Alignment and Size Constraints

- **`FLASH_DRV_EraseSector(dest, size)`**: `dest` must be aligned to the sector size of the target array (`FEATURE_EFM_MAIN_ARRAY_SECTOR_SIZE` or `FEATURE_EFM_DATA_ARRAY_SECTOR_SIZE`), and `size` must be a multiple of it. The driver determines the sector size from the destination address.
- **`FLASH_DRV_Program(dest, size, pData)`**: `dest` and `size` must be aligned to `FEATURE_EFM_WRITE_UNIT_SIZE` (the program write unit, e.g. 8 bytes on YTM32B1ME1), and `pData` must be **32-bit (4-byte) aligned**. Data is programmed in write-unit chunks.
- These checks are enforced by `DEV_ASSERT` inside the binary driver (always active — a failed check returns `STATUS_InvalidArgument`); ensure alignment at the call site in all builds.
- Flash can only be programmed where it has been erased — the target region must be `0xFF` before programming; erase the sector first.
- Erasing/programming is a blocking operation (the driver polls the EFM status register until the command completes).

## Interrupts and Cache

- **Interrupts**: the command launcher polls the EFM status register in a busy loop. In the default build of this project `EFM_AUTO_DISABLE_GLOBAL_INT` is **not** defined, so the driver does not mask global interrupts itself. If your ISRs may execute code from, or access, the flash region being erased/programmed, mask interrupts around the calls. Note that the situation depends on the memory layout: the YTM32B1ME1 demo comments that when the code runs from a different flash block than the one being operated on, masking may not be required. Verify against your target's reference manual.
- **Cache**: when the chip supports it (`FEATURE_SOC_SUPPORT_CACHE`), the driver invalidates the flash cache after each command, and cleans/invalidates the data cache when present and enabled — i.e. cache coherence is handled inside the driver in those configurations. The YTM32B1MD2 demo additionally recommends disabling the flash cache before P-Flash operations as the simplest, least error-prone approach. Check the behavior on your target and follow its reference manual; a post-program read-back verification is always a good idea.

## BootSwap — High-Risk Operation

`FLASH_DRV_BootSwap()` swaps the boot flash block with the current one; it persistently changes which image boots after reset.

- The `bootSwap` table entry exists only on chips that define `FEATURE_EFM_BOOT_SWAP_CMD_CODE` (the entry is marked *optional by chip* in `fls_drv_bin.h`). On a chip without support, calling `FLASH_DRV_BootSwap()` would read an absent table field — do not call it unless the target chip supports boot swap.
- An incorrect or interrupted swap sequence can leave the system unable to boot the intended image. Only use it in bootloader flows with a validated recovery path, and test thoroughly before deployment.

## 4 KB Link-Area Limit

`bin_project/link/relative.ld` maps the whole image into a single region:

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

The driver image (table + code + constants) must fit within 4 KB. If it grows beyond the limit, the link fails with a "region `m_text` overflowed" error — check `FlashDriver.map` (`--print-memory-usage` output) for the current usage.

## License

This project is licensed under the **BSD-3-Clause** license (see [LICENSE.md](LICENSE.md)), Copyright (c) 2020-2025 Yuntu Microelectronics Co., Ltd.

## Troubleshooting / Checklist

1. **Sanity-check the table**: `FLASH_DRV_GetVersion()` should return `1`. If not, the wrong image is at `FLS_DRV_BASE_ADDR` (array not linked in, or binary downloaded to the wrong SRAM address).
2. **PRESCALER**: configure the EFM CTRL PRESCALER to `flashClockHz / 2,000,000` before `FLASH_DRV_Init()` and after every clock change; follow the target chip's SDK/reference manual.
3. **Alignment**: `EraseSector` — `dest`/`size` multiples of the sector size; `Program` — `dest`/`size` multiples of `FEATURE_EFM_WRITE_UNIT_SIZE` and `pData` 4-byte aligned.
4. **Erase before program**: the target region must be `0xFF` (erased) before programming; verify by reading back after the operation.
5. **SRAM mode**: `FlashDriver.bin` must actually be loaded at `FLS_DRV_BASE_ADDR`; a wrong address causes a hard fault or undefined behavior on the first API call.
6. **Link overflow**: if the build reports "region `m_text` overflowed", the image exceeds the 4 KB link area.
7. **Missing `bin2h.exe`**: the post-build step that generates `fls_drv_bin_data.h` fails if `bin_project/tools/bin2h.exe` is not present.
8. **Interrupts/cache**: if ISRs can touch the flash region under operation, mask interrupts around erase/program; suggest disabling the cache before the flash operation.
9. **BootSwap**: call only on chips that support it, and only in flows with a validated recovery path.
10. **Do not keep the driver as persistent executable content**: the function table stores relative offsets that are resolved from `FLS_DRV_BASE_ADDR`. Prefer loading the image into a controlled SRAM address only when a flash operation is required, then validate the base address and `FLASH_DRV_GetVersion()` before calling any erase/program API. An invalid base address, corrupted image, or unintended control-flow jump may resolve to the wrong function address and cause an unintended flash erase or program operation.
