本篇总结
对 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.uvprojx,F7 编译,确认 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 一次性编程区域读写测试结果
可看到 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 不可逆,每个地址仅可写一次,写入前必须确认数据正确性