全部例程

OTP - 一次性编程区域读写

W55MH32

更新于 2026年8月13日

本篇总结

对 OTP 一次性编程区域,实现数据的写入与读取验证。

🔑 KEIL MDK、UART调试、OTP

本示例演示 W55MH32L 的 OTP(One-Time Programmable,一次性可编程)区域读写流程。例程通过 4 个寄存器操作函数完成上电、设时基、写一字节、断电的完整闭环,并通过串口打印 OTP_DATA0~DATA7 验证写入结果。

前置工具准备

开始本例程前,请确认以下工具已安装:

  • WIZ UartTool V1.0:串口日志观察工具:点击下载

硬件连接

OTP 为芯片内部存储资源,无需任何外部引脚连接。仅需一根 Micro USB 线连接开发板 DEBUG USB 口到 PC,用于下载固件与观察串口输出。

项目说明
存储类型反熔丝 OTP(初始为 0,写 1 后不可逆)
数据寄存器OTP_DATA0 ~ OTP_DATA7(8 个)
外部引脚无,芯片内部资源
调试接口DEBUG USB(Micro USB)→ PC

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\OTP\,核心文件如下:

OTP/
├── USER/
│   ├── main.c            ← OTP 上电/设时基/写读/断电 + 串口重定向
│   ├── w55mh32_conf.h    ← 外设头文件集中包含
│   ├── w55mh32_it.c/h    ← 中断服务函数
│   ├── system_w55mh32.c/h← 系统初始化(时钟配置)
│   └── startup_w55mh32.s ← 启动文件
└── SYSTEM/
    └── delay/
        └── delay.c/h      ← SysTick 延时函数

步骤1:OTP 上电初始化

文件:USER/main.c | 函数:OTP_PowerOn() — 启用 SYSCFG 时钟,解锁保护寄存器,配置 LDO 启动时间。


void OTP_PowerOn(uint32_t Time)
{
    RCC->RCC_SYSCFG_CONFIG = 0x01;            /* 启用 SYSCFG 模块时钟 */
    SYSCFG->SYSCFG_LOCK = 0xAB12DFCD;         /* 写解锁密钥,解除保护 */

    if (Time > OTP_POWERON_TIME)
        OTP->OTP_LDO = Time;                  /* 用户指定的 LDO 启动时间 */
    else
        OTP->OTP_LDO = OTP_POWERON_TIME;      /* 不足最小值则强制使用最小值 */
}

三步完成:开 SYSCFG 时钟 → 写解锁密钥 0xAB12DFCD → 配置 LDO 启动时间(取入参与最小值 OTP_POWERON_TIME 的较大者),确保熔断操作时电源稳定。

解锁密钥不可省略

未写 SYSCFG_LOCK = 0xAB12DFCD 时,OTP_LDO 等寄存器写入将被硬件忽略,后续熔断操作必然失败。

步骤2:设置写入时间基准

函数:OTP_SetTime() — 以 10ns 为单位配置写入脉冲宽度,确保反熔丝击穿所需的高压施加时间。


void OTP_SetTime(uint16_t Time)
{
    OTP->OTP_10ns |= Time;   /* 以 10ns 为单位叠加写入脉冲宽度 */
}

实际调用时入参取 clocks.PCLK2_Frequency / 1000000,即按 PCLK2 周期折算出 1μs 量级的时间基准。时间过短会击穿不充分,过长会增加功耗,需按数据手册推荐值配置。

步骤3:写入一字节数据

函数:OTP_WriteByte() — 地址装载 → 触发写入 → 等待完成。地址入参由 IS_OTP_ADDRESS 宏校验合法性。


void OTP_WriteByte(uint8_t Addr, uint8_t Data)
{
    assert_param(IS_OTP_ADDRESS(Addr));   /* 地址合法性校验 */

    OTP->OTP_WR   = (Addr << 8) | Data;   /* 地址与数据拼装入 WR 寄存器 */
    OTP->OTP_CTRL = BIT(0);               /* 触发写入 */
    while (OTP->OTP_CTRL & BIT(2));       /* 等待硬件完成(bit2 清零) */
}

关键两点:OTP_WR 高 8 位为地址、低 8 位为数据;OTP_CTRL 的 bit2 为完成标志,硬件写完后自动清零,软件轮询退出。

一次性写入不可逆

反熔丝工艺只能 0→1,已写为 1 的位无法恢复为 0。同一地址重复写入不同值会导致结果按位「或」,最终值不可预测。写入前务必确认数据正确性。

步骤4:OTP 断电

函数:OTP_PowerOff() — 操作完成后关闭 OTP 供电,降低静态功耗并防止误写入。


void OTP_PowerOff(void)
{
    SYSCFG->SYSCFG_LOCK     = 0xAB12DFCD;  /* 再次解锁 */
    RCC->RCC_SYSCFG_CONFIG  = 0x00;        /* 关闭 SYSCFG 时钟,OTP 失电 */
}

与上电对称:再次写解锁密钥后关闭 SYSCFG 时钟,OTP 模块随即失电进入休眠。建议每次写操作完成后立即调用,避免长时间带电带来的误写风险。

步骤5:主函数逻辑

函数:main() — 初始化时钟与串口 → OTP 上电 → 设时基 → 写 0x5A 到地址 0 → 读 8 个数据寄存器打印 → 断电。


int main(void)
{
    RCC_ClocksTypeDef clocks;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    delay_init();
    UART_Configuration(115200);
    RCC_GetClocksFreq(&clocks);

    printf("\n");
    printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, "
           "PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
           (float)clocks.SYSCLK_Frequency / 1000000,
           (float)clocks.HCLK_Frequency / 1000000,
           (float)clocks.PCLK1_Frequency / 1000000,
           (float)clocks.PCLK2_Frequency / 1000000,
           (float)clocks.ADCCLK_Frequency / 1000000);
    printf("OTP Write Read Test.\n");

    OTP_PowerOn(clocks.PCLK2_Frequency / 10);       /* 100ms 启动时间 */
    OTP_SetTime(clocks.PCLK2_Frequency / 1000000);  /* 1us 写入脉冲 */

    OTP_WriteByte(OTP_ADDRESS_0, 0x5A);             /* 向地址 0 写入 0x5A */

    printf("OPT Data0 : 0x%x\n", OTP->OTP_DATA0);
    printf("OPT Data1 : 0x%x\n", OTP->OTP_DATA1);
    printf("OPT Data2 : 0x%x\n", OTP->OTP_DATA2);
    printf("OPT Data3 : 0x%x\n", OTP->OTP_DATA3);
    printf("OPT Data4 : 0x%x\n", OTP->OTP_DATA4);
    printf("OPT Data5 : 0x%x\n", OTP->OTP_DATA5);
    printf("OPT Data6 : 0x%x\n", OTP->OTP_DATA6);
    printf("OPT Data7 : 0x%x\n", OTP->OTP_DATA7);

    OTP_PowerOff();
    while (1);
}

时钟频率参数动态传入 OTP_PowerOn / OTP_SetTime,确保不同主频下时间基准一致。写入完成后立即读取 OTP_DATA0~7 寄存器验证,最后断电进入死循环。

如果你想自定义

  • 改写入地址 → 修改 OTP_WriteByte 第一个参数,须为 OTP_ADDRESS_0~7 之一
  • 改写入数据 → 修改第二个参数,注意已写 1 的位无法恢复为 0

步骤6:printf 重定向

重写 fputc() 将 printf 输出重定向到 USART1,并自动在 \n 前补 \r 确保串口工具正确换行。


int fputc(int c, FILE *f)
{
    if (c == '\n')
        SER_PutChar('\r');         // 补回车,确保串口工具正确换行
    return (SER_PutChar(c));
}

int SER_PutChar(int ch)
{
    while (!USART_GetFlagStatus(USART_TEST, USART_FLAG_TC));
    USART_SendData(USART_TEST, (uint8_t)ch);
    return ch;
}

编译与下载

Keil MDK 中打开 OTP.uvprojxF7 编译,确认 0 Error(s)。连接 DEBUG USB 口到 PC,F8 下载。下载成功后按 RESET 键启动新固件。

运行验证

打开 WIZ UartTool V1.0,选择开发板对应 COM 口,波特率 115200 / 8N1,打开串口。按 RESET 启动后,预期串口输出:

SYSCLK: 72.0Mhz, HCLK: 72.0Mhz, PCLK1: 36.0Mhz, PCLK2: 72.0Mhz, ADCCLK: 36.0Mhz
OTP Write Read Test.
OPT Data0 : 0x5a
OPT Data1 : 0x0
OPT Data2 : 0x0
OPT Data3 : 0x0
OPT Data4 : 0x0
OPT Data5 : 0x0
OPT Data6 : 0x0
OPT Data7 : 0x0
WIZ UartTool 串口输出 — OTP 读写测试结果
WIZ UartTool 串口输出 — OTP 一次性编程区域读写测试结果

可看到 OTP_DATA0 已成功写入 0x5A,其余地址保持默认 0x0,验证 OTP 写入与读取功能正常。注意:OTP 一次性可编程,再次下载运行同一固件时 DATA0 仍为 0x5A(已固化),不会重新写入。

常见问题

Q: 写入后 OTP_DATA0 仍为 0x00

检查 OTP_PowerOn 是否在 OTP_WriteByte 之前调用,且 SYSCFG_LOCK = 0xAB12DFCD 解锁密钥未省略。LDO 启动时间不足也会导致击穿失败。

Q: 程序卡在 while(OTP->OTP_CTRL & BIT(2)) 不动

OTP 模块未正常上电或 SYSCFG 时钟未启用。确认 RCC->RCC_SYSCFG_CONFIG = 0x01 已执行;检查供电电压是否在 2.7V~3.6V 范围内。

Q: 重复下载后 OTP_DATA0 变成 0xFF 或其他值

OTP 反熔丝工艺只能 0→1。首次写入 0x5A(01011010)后,对应位已永久击穿。若再次调用 OTP_WriteByte(OTP_ADDRESS_0, 0xXX),结果为两次数据按位或,无法恢复。OTP 区域仅可写一次。

Q: assert_param 断言失败

传入 OTP_WriteByte 的地址不在 OTP_ADDRESS_0~7 范围内。OTP 仅支持 8 个字节地址空间,超出会被 IS_OTP_ADDRESS 宏拦截。

Q: 串口无任何输出

先确认 COM 口号正确、波特率 115200;检查 UART_Configuration 中 PA9(TX)/PA10(RX) 引脚配置;下载后需按 RESET 键启动新固件。

总结

本文基于 W55MH32L 的 OTP 模块完成了一次性编程区域读写验证:

  • OTP_PowerOn 启用 SYSCFG 时钟 + 写解锁密钥 0xAB12DFCD + 配置 LDO 启动时间,确保熔断时电源稳定
  • OTP_SetTime 以 10ns 为单位设置写入脉冲宽度,按 PCLK2 频率折算为 1μs 基准
  • OTP_WriteByte 通过 OTP_WR(地址+数据)+ OTP_CTRL(触发+完成标志)完成一字节写入
  • OTP_PowerOff 再次解锁后关闭 SYSCFG 时钟,OTP 失电进入休眠
  • 关键约束:反熔丝工艺 只能 0→1 不可逆,每个地址仅可写一次,写入前必须确认数据正确性

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家