一、FRAM_wr(Uint16 xaddr,Uint16 wd)
骨架
一句话主线
拉低片选 → 发 WRITE 命令 → 发地址 → 发数据 → 拉高片选。
八行口诀
一拉低,二延时,三发命令,四发地址高,五发地址低,六发数据高,七发数据低,八拉高。
分组表
| 组 | 行 | 干什么 | 一句话 |
|---|---|---|---|
| A 拉低 | 1~2 | 拉低片选 + 延时 | 选中 FM25CL64B,等它识别片选 |
| B 发命令 | 3~4 | 赋值 WRITE + 发送 | 发写数据命令 0x02 |
| C 发地址 | 5~8 | 发地址高字节 + 发地址低字节 | MSB first,先高后低 |
| D 发数据 | 9~12 | 发数据高字节 + 发数据低字节 | MSB first,先高后低 |
| E 拉高 | 13 | 拉高片选 | 释放 FM25CL64B,上升沿触发内部写周期并清 WEL |
重要前置条件
FRAM_wr() 调用前必须先 wren_cmd(),否则 WEL=0,WRITE 被拒绝。
FRAM_wr 后的行为
-
片选上升沿触发 FM25CL64B 内部写周期。
-
片选上升沿清 WEL=0,下次写前要重发 WREN。
自测题
Q1:FRAM_wr 分哪几步?
A:拉低 → 发命令 → 发地址 → 发数据 → 拉高。
Q2:FRAM_wr 发了几次 spisend?分别发什么?
A:5 次 :WRITE 命令码、地址高字节 、地址低字节 、数据高字节 、数据低字节。
Q3:WRITE_INST 为什么是 0x0200?FM25CL64B 的 WRITE 命令码是多少?
A:命令码放高 8 位(左对齐),低 8 位补 0 ,SPI 取高 8 位发送,所以 0x0200 实际发的是 0x02。FM25CL64B 的 WRITE 命令码是 0x02。
Q4:地址和数据为什么都要分两次发?
A:FM25CL64B 的地址是 16 位(实际 13 位,高 3 位补 0),数据是 16 位。C2000 SPI 是 8 位字符 ,每次只能发 8 位,所以要发两次:高字节 + 低字节。
Q5:FRAM_wr 结尾为什么没有 delay(10)?
A:FM25CL64B 是铁电 ,写周期极短(ns 级),结尾不加 delay 是可以接受的。但片选上升沿会清 WEL=0,下次写前要重发 WREN。
Q6:为什么地址高字节用 xaddr,低字节用 xaddr<<8?
A:C2000 SPI 取高 8 位发送。
-
spisend(xaddr)发 xaddr 的高 8 位(地址高字节)。 -
spisend(xaddr << 8)发 xaddr 的低 8 位(左移后到了高 8 位)。
Q7:如果写 spisend(xaddr & 0xFF) 会怎样?
A:xaddr & 0xFF 是低 8 位,但 spisend 取高 8 位发送,实际发的是 0x00,发错了。必须用 xaddr << 8。
Q8:地址和数据的字节序是什么?
A:MSB first,高字节先发,低字节后发。FM25CL64B 要求 MSB first。
Q9:片选拉高后 WRITE 才生效吗?
A:WRITE 指令和数据在发送过程中就已经进入 FM25CL64B 的内部缓冲,片选上升沿是"锁定"和"启动内部写周期"的边沿。
Q10:FRAM_wr 和 FRAM_rd 有什么区别?
A:
-
FRAM_wr():发 WRITE(0x02) + 2 字节地址 + 2 字节数据,写操作。 -
FRAM_rd():发 READ(0x03) + 2 字节地址 + 2 字节哑元,读操作。 -
读不需要 WREN,写必须先 WREN。
Q11:FRAM_wr 为什么不带回读校验?
A:回读校验在 store_para_FRAM() 里做。FRAM_wr() 只负责写,写完就返回。校验是"写完立即读回比较",属于上层逻辑(调用 FRAM_wr() 的上一层代码(store_para_FRAM())。它负责流程控制(写使能 → 写 → 回读 → 比较 → 记录错误),FRAM_wr() 只是它调用的一个下层函数。)。
Q12:如果忘了发 WREN 直接调 FRAM_wr 会怎样?
A:WEL=0,WRITE 被 FM25CL64B 拒绝,数据写不进去。
比喻
整体比喻:FRAM_wr = 去银行保险柜存包裹
-
FRAM 芯片 = 银行的保险柜仓库
-
仓库里有无数个格子(存储单元),每个格子有一个编号(地址)
-
你要往某个格子里放东西(写入数据),必须告诉柜员:放哪个格子、放什么东西
函数参数:
-
xaddr= 你要存的格子编号(地址) -
wd= 你要存的东西(16 位数据)
逐行比喻拆解
第一步:拉低片选 → 走到银行门口,拉门把手
GpioDataRegs.GPACLEAR.bit.GPIO11 = 1; // 拉低片选,选中 FM25CL64B
比喻:你走到银行门口,拉低门把手(CS 拉低),银行开门了。
第二步:延时 → 等柜员坐定(tCSS)
delay(10); // 延时,保证片选建立时间
比喻:门开了,等柜员从后台走到窗口坐定(tCSS),你才能开始说话。
第三步:准备并发送 WRITE 命令码 → 递"存包裹申请单"
spixdat = WRITE_INST; // spixdat=0x0200:WRITE 写数据命令
spisend(spixdat); // 发送 WRITE 命令码给 FM25CL64B
比喻:你对柜员说"我要存东西"(递申请单,命令码 0x02)。
第四步~第五步:发送地址高字节 + 低字节 → 告诉柜员存哪个格子
spixdat = xaddr; // 地址高字节(xaddr 的高 8 位)
spisend(spixdat); // 发送地址高字节
spixdat = (xaddr<<8); // 地址低字节(左移后高 8 位 = 原低 8 位)
spisend(spixdat); // 发送地址低字节
比喻:柜员问"存哪个格子?"你说"第 xxx 号格子"。因为格子编号是 16 位的(比如 0x1234),你要分两次说:先说高两位"12",再说低两位"34"。
为什么先发高字节再发低字节?
FM25CL64B 规定 MSB first(高位先发),这是器件规定,照做就行。
第六步~第七步:发送数据高字节 + 低字节 → 把东西放进格子
spixdat = wd; // 数据高字节(wd 的高 8 位)
spisend(spixdat); // 发送数据高字节
spixdat = wd<<8; // 数据低字节(左移后高 8 位 = 原低 8 位)
spisend(spixdat); // 发送数据低字节
比喻:柜员问"存什么东西?"你把要存的东西递过去。因为东西是 16 位的(比如 0xABCD),也要分两次递:先递高字节"AB",再递低字节"CD"。
第八步:拉高片选 → 关上门走人
GpioDataRegs.GPASET.bit.GPIO11 = 1; // 片选拉高,释放 FM25CL64B
比喻:东西存完了,你拉高门把手(CS 拉高),关上门走人。柜员在关门瞬间把东西锁进保险柜(触发内部写周期)。
注意事项的比喻解释
注意①:必须先 wren_cmd() 写使能
比喻:存东西之前,必须先递"写权限申请单"(wren_cmd),否则柜员会拒绝你------"你没授权,我不能让你存"。
注意②:必须先拉低片选,再发命令,最后拉高片选
比喻:必须"开门→办事→关门"的顺序,不能反过来。没开门就递东西,东西掉地上了;办完事不关门,柜员不知道你到底办完了没有。
注意③:地址和数据都分两次发:先高字节,再低字节
比喻:格子编号和东西都是 16 位的,但窗口一次只能递 8 位,所以要分两次。FRAM 规定"高字节先发"(MSB first),照做就行。
注意④:片选拉低后加 delay(),满足建立时间
比喻:开门后等柜员坐定(tCSS),不然你刚开门就说话,柜员没听到,后面全乱了。
注意⑤:代码结尾没有 delay()
比喻:关上门之后,代码里没有等柜员处理完的时间。但 FM25CL64B 是铁电 ,写周期极短(ns 级),通常不会出错。注意片选上升沿会清 WEL=0,下次写前要重发 WREN。
整段注释
cpp
/*******************************************************************************
* 函数名 : FRAM_wr()
* 简述 : 向 FM25CL64B 写一个 16 位数据
*
* 功能 : 拉低片选,依次发送 WRITE 指令 (0x02)、2 字节地址、2 字节数据,
* 拉高片选触发内部写周期。具体分 6 步:
* 1. 拉低 GPIO 片选,选中 FM25CL64B;
* 2. 发送 WRITE 指令 (0x02);
* 3. 发送地址高字节;
* 4. 发送地址低字节;
* 5. 发送数据高字节;
* 6. 发送数据低字节,拉高片选,触发内部写周期。
*
* 参数 : xaddr ------ 铁电字节地址(16 位,实际只用低 13 位)
* wd ------ 要写入的 16 位数据
* 返回值 : 无
* 调用关系 : 被 store_para_FRAM() 调用;内部调用 spisend()
*
* 公式计算 : 无(本函数为纯数据写入)
*
* 片选 : GPIO11,低有效
*
* 注意事项 : 1. 调用本函数前必须先发 WREN(0x06),否则 WEL=0 被拒绝;
* 2. 片选在整个操作期间必须保持低,操作结束才拉高;
* 3. 片选上升沿触发 FM25CL64B 内部写周期,同时清 WEL;
* 4. 本函数不带回读校验,校验在 store_para_FRAM() 中做。
******************************************************************************/
void FRAM_wr(Uint16 xaddr, Uint16 wd)
{
// 第 1 步:拉低片选,选中 FM25CL64B
// GPACLEAR 写 1 表示对应位清零,GPIO11 输出低电平,片选有效
GpioDataRegs.GPACLEAR.bit.GPIO11 = 1; // 片选拉低(GPIO11=0,低有效)
// 片选建立时间:等 GPIO 电平稳定后再发指令
delay(10); // 延时 10 个单位,保证建立时间
// 第 2 步:发送 WRITE 指令 (0x02)
// spixdat 赋值为 WRITE_INST(0x0200,左对齐)
spixdat = WRITE_INST; // 准备 WRITE 指令值
spisend(spixdat); // 发送 WRITE 指令,内部等待收发完成
// 第 3 步:发送地址高字节
// xaddr 高 8 位左对齐后发送(C2000 SPI 只取高 8 位)
spixdat = xaddr; // 准备地址高字节
spisend(spixdat); // 发送地址高字节
// 第 4 步:发送地址低字节
// xaddr 左移 8 位后,低 8 位移到高 8 位,SPI 取高 8 位发送
spixdat = (xaddr << 8); // 准备地址低字节(左移后高 8 位 = 原低 8 位)
spisend(spixdat); // 发送地址低字节
// 第 5 步:发送数据高字节
// wd 高 8 位左对齐后发送
spixdat = wd; // 准备数据高字节
spisend(spixdat); // 发送数据高字节
// 第 6 步:发送数据低字节
// wd 左移 8 位后,低 8 位移到高 8 位,SPI 取高 8 位发送
spixdat = (wd << 8); // 准备数据低字节(左移后高 8 位 = 原低 8 位)
spisend(spixdat); // 发送数据低字节
// 第 6 步(续):拉高片选,触发 FM25CL64B 内部写周期
// GPASET 写 1 表示对应位置 1,GPIO11 输出高电平,操作结束
GpioDataRegs.GPASET.bit.GPIO11 = 1; // 片选拉高(GPIO11=1,写周期开始)
}
逐行逐位分析
第 1 行:GpioDataRegs.GPACLEAR.bit.GPIO11 = 1
GpioDataRegs.GPACLEAR.bit.GPIO11 = 1; // 片选拉低(GPIO11=0,低有效)
位含义
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| GPIO11 | GPACLEAR 对应位 | 1 | GPIO 清零寄存器。写 1 将该位清零 (输出低电平);写 0 不影响。GPIO11 作为 FM25CL64B 的片选,低有效。 |
为什么用 GPACLEAR
原子操作,写 1 只清零 GPIO11,不影响其他位,避免读-改-写竞态。
Q&A
-
Q:片选拉低后要 delay 吗?
A:要。delay(10) 保证 FM25CL64B 检测到片选有效(tCSS)。
-
Q:GPACLEAR 和 GPADAT 有什么区别?
A:GPACLEAR 是"写 1 清零"寄存器,原子操作;GPADAT 读写整个端口,有竞态风险。
第 2 行:delay(10)
delay(10); // 延时 10 个单位,保证建立时间
作用
保证 FM25CL64B 在第一个时钟沿之前检测到片选有效(tCSS)。
Q&A
-
Q:delay(10) 是多久?
A:纯软件空循环,
delay(10)表示循环 10 次,F28002x 100MHz 下约几百纳秒。对 FM25CL64B 的 ns 级 tCSS 足够。
第 3 行:spixdat = WRITE_INST
spixdat = WRITE_INST; // 准备 WRITE 指令值
位含义
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| WRITE_INST | 宏定义 | 0x0200 | WRITE 指令。左对齐:高 8 位 = 0x02,低 8 位 = 0x00。 |
FM25CL64B 的 WRITE 指令
| 项目 | 值 |
|---|---|
| 指令名 | WRITE |
| 编码 | 0000_0010b (0x02) |
| 作用 | 写数据到指定地址 |
| 前置条件 | 必须先 WREN |
| 后续字节 | 2 字节地址 + N 字节数据 |
Q&A
-
Q:WRITE 指令需要先 WREN 吗?
A:需要。WEL=0 时 WRITE 被拒绝。
store_para_FRAM()里先调wren_cmd()再调FRAM_wr()。 -
Q:WRITE_INST 为什么是 0x0200 而不是 0x02?
A:C2000 SPI 8 位模式下数据必须左对齐。0x0200 高 8 位是 0x02,SPI 取高 8 位发送,正确。
第 4 行:spisend(spixdat)
spisend(spixdat); // 发送 WRITE 指令,内部等待收发完成
执行过程
| 步骤 | 操作 |
|---|---|
| 1 | 写 SPITXBUF = 0x0200 |
| 2 | SPI 硬件取高 8 位 0x02,移位输出 |
| 3 | spisend 内部 while(INT_FLAG==0) 等待 |
| 4 | 读 SPIRXBUF,清 INT_FLAG |
Q&A
-
Q:WRITE 指令发送后,FM25CL64B 会返回数据吗?
A:不会。WRITE 是写指令,FM25CL64B 在 SOMI 上不返回有效数据。
spiimg里是无意义值。
第 5 行:spixdat = xaddr
spixdat = xaddr; // 准备地址高字节
spisend(spixdat); // 发送地址高字节
位含义
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| xaddr | 参数 | 16 位地址 | 铁电字节地址。高 8 位 是地址的高字节,低 8 位是地址的低字节。 |
地址发送分析
-
xaddr高 8 位 = 地址高字节。 -
spisend(xaddr)左对齐后发送的是xaddr的高 8 位,即地址高字节。
FM25CL64B 地址要求
| 项目 | 值 |
|---|---|
| 地址宽度 | 13 位(8KByte) |
| 地址字节数 | 2 字节 |
| 字节序 | MSB first(高字节先发) |
| 高位补 0 | 高 3 位补 0 |
Q&A
-
Q:为什么先发地址高字节?
A:FM25CL64B 要求 MSB first,地址高字节先发,低字节后发。
-
Q:xaddr = 0x0010 时,实际发的是什么?
A:
spisend(0x0010)左对齐后发0x00(高字节),spisend(0x1000)左对齐后发0x10(低字节)。
第 6 行:spixdat = (xaddr << 8)
spixdat = (xaddr << 8); // 准备地址低字节(左移后高 8 位 = 原低 8 位)
spisend(spixdat); // 发送地址低字节
位含义
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| xaddr << 8 | 移位操作 | 16 位 | 把 xaddr 的低 8 位移到高 8 位。SPI 取高 8 位发送,即发送原 xaddr 的低 8 位。 |
移位分析
-
xaddr = 0x0010 -
xaddr << 8 = 0x1000 -
spisend(0x1000)左对齐后发高 8 位0x10,即原 xaddr 的低 8 位。
Q&A
-
Q:为什么用
xaddr << 8而不是xaddr & 0xFF?A:因为 C2000 SPI 取高 8 位发送。要发低 8 位,必须把它移到高 8 位。
xaddr << 8正好做到。 -
Q:如果写
spisend(xaddr & 0xFF)会怎样?A:
xaddr & 0xFF = 0x10,spisend(0x0010)左对齐后发高 8 位0x00,发错了。
第 7 行:spixdat = wd
spixdat = wd; // 准备数据高字节
spisend(spixdat); // 发送数据高字节
位含义
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| wd | 参数 | 16 位数据 | 要写入的数据。高 8 位 是数据高字节,低 8 位是数据低字节。 |
数据发送分析
spisend(wd)左对齐后发送wd的高 8 位,即数据高字节。
Q&A
-
Q:为什么先发数据高字节?
A:FM25CL64B 要求 MSB first,数据高字节先发。
-
Q:wd = 0x1234 时,实际发的是什么?
A:
spisend(0x1234)左对齐后发0x12(高字节),spisend(0x3400)左对齐后发0x34(低字节)。
第 8 行:spixdat = (wd << 8)
spixdat = (wd << 8); // 准备数据低字节(左移后高 8 位 = 原低 8 位)
spisend(spixdat); // 发送数据低字节
位含义
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| wd << 8 | 移位操作 | 16 位 | 把 wd 的低 8 位移到高 8 位。SPI 取高 8 位发送,即发送原 wd 的低 8 位。 |
移位分析
-
wd = 0x1234 -
wd << 8 = 0x3400 -
spisend(0x3400)左对齐后发高 8 位0x34,即原 wd 的低 8 位。
Q&A
-
Q:数据和地址的发送方式一样吗?
A:一样。都是"高字节用原值,低字节用左移 8 位",因为 C2000 SPI 取高 8 位发送。
第 9 行:GpioDataRegs.GPASET.bit.GPIO11 = 1
GpioDataRegs.GPASET.bit.GPIO11 = 1; // 片选拉高(GPIO11=1,写周期开始)
位含义
| 位 | 名称 | 值 | 含义 |
|---|---|---|---|
| GPIO11 | GPASET 对应位 | 1 | GPIO 置位寄存器。写 1 将该位置 1 (输出高电平);写 0 不影响。FM25CL64B 片选高有效表示操作结束。 |
FM25CL64B 的 WRITE 时序
| 阶段 | 片选 | 数据 |
|---|---|---|
| 操作前 | 高 | --- |
| 发送 WRITE 指令 | 低 | 0x02 |
| 发送地址高字节 | 低 | xaddr 高 8 位 |
| 发送地址低字节 | 低 | xaddr 低 8 位 |
| 发送数据高字节 | 低 | wd 高 8 位 |
| 发送数据低字节 | 低 | wd 低 8 位 |
| 操作后 | 高 | --- |
片选上升沿的作用:
-
触发 FM25CL64B 内部写周期:数据被写入铁电存储单元。
-
清 WEL 位:WEL 自动清 0,下次写前要重发 WREN。
Q&A
-
Q:片选拉高后 WRITE 才生效吗?
A:WRITE 指令和数据在发送过程中就已经进入 FM25CL64B 的内部缓冲,片选上升沿是"锁定"和"启动内部写周期"的边沿。
-
Q:片选拉高后 WEL 会怎样?
A:WEL 自动清 0。下次写前要重发 WREN。
-
Q:片选拉高后要 delay 吗?
A:FM25CL64B 是铁电,写周期极短(ns 级),通常不需要。但严格来说可以加一个短 delay。
四、逐位分析总结表
| 序 | 代码 | 寄存器/位 | 值 | 中文含义 | 面试要点 |
|---|---|---|---|---|---|
| 1 | GPACLEAR.GPIO11 = 1 | GPACLEAR.bit.GPIO11 | 1 | 片选拉低 | 原子操作,写 1 清零;低有效 |
| 2 | delay(10) | --- | --- | 片选建立时间 | 保证 FM25CL64B 检测到片选 |
| 3 | spixdat = WRITE_INST | 全局变量 | 0x0200 | WRITE 指令值 | 左对齐,高 8 位 = 0x02 |
| 4 | spisend(spixdat) | SPITXBUF | 0x0200 | 发送 WRITE 指令 | 8 位模式只发高 8 位 |
| 5 | spixdat = xaddr | 全局变量 | xaddr | 地址高字节 | 高 8 位 = 地址高字节 |
| 6 | spisend(spixdat) | SPITXBUF | xaddr | 发送地址高字节 | MSB first |
| 7 | spixdat = xaddr << 8 | 全局变量 | xaddr<<8 | 地址低字节 | 左移后高 8 位 = 原低 8 位 |
| 8 | spisend(spixdat) | SPITXBUF | xaddr<<8 | 发送地址低字节 | MSB first |
| 9 | spixdat = wd | 全局变量 | wd | 数据高字节 | 高 8 位 = 数据高字节 |
| 10 | spisend(spixdat) | SPITXBUF | wd | 发送数据高字节 | MSB first |
| 11 | spixdat = wd << 8 | 全局变量 | wd<<8 | 数据低字节 | 左移后高 8 位 = 原低 8 位 |
| 12 | spisend(spixdat) | SPITXBUF | wd<<8 | 发送数据低字节 | MSB first |
| 13 | GPASET.GPIO11 = 1 | GPASET.bit.GPIO11 | 1 | 片选拉高 | 触发内部写周期,清 WEL |
五、提问与答案解析
Q1:FRAM_wr() 的作用是什么?
答:向 FM25CL64B 写一个 16 位数据。流程为:拉低片选 → 发 WRITE 指令 (0x02) → 发 2 字节地址 → 发 2 字节数据 → 拉高片选触发内部写周期。
Q2:FRAM_wr() 前为什么必须先 WREN?
答 :FM25CL64B 的 WEL 位必须为 1 才能写数据。WEL 只有 WREN 指令能置 1。所以 store_para_FRAM() 里先调 wren_cmd() 再调 FRAM_wr()。
Q3:WRITE_INST 为什么是 0x0200 而不是 0x02?
答:C2000 SPI 8 位模式下数据必须左对齐。0x0200 高 8 位是 0x02,SPI 取高 8 位发送,正确。
Q4:为什么地址和数据都要发两次?
答:FM25CL64B 的地址是 16 位(实际 13 位,高 3 位补 0),数据是 16 位。C2000 SPI 是 8 位字符,每次只能发 8 位,所以要发两次:高字节 + 低字节。
Q5:为什么地址高字节用 xaddr,低字节用 xaddr<<8?
答:C2000 SPI 取高 8 位发送。
-
spisend(xaddr)发 xaddr 的高 8 位(地址高字节)。 -
spisend(xaddr << 8)发 xaddr 的低 8 位(左移后到了高 8 位)。
Q6:如果写 spisend(xaddr & 0xFF) 会怎样?
答 :xaddr & 0xFF 是低 8 位,但 spisend 取高 8 位发送,实际发的是 0x00,发错了。必须用 xaddr << 8。
Q7:地址和数据的字节序是什么?
答 :MSB first,高字节先发,低字节后发。FM25CL64B 要求 MSB first。
Q8:片选拉高后 WRITE 才生效吗?
答:WRITE 指令和数据在发送过程中就已经进入 FM25CL64B 的内部缓冲,片选上升沿是"锁定"和"启动内部写周期"的边沿。
Q9:片选拉高后 WEL 会怎样?
答:WEL 自动清 0。下次写前要重发 WREN。
Q10:FRAM_wr() 为什么不带回读校验?
答 :回读校验在 store_para_FRAM() 里做。FRAM_wr() 只负责写,写完就返回。校验是"写完立即读回比较",属于上层逻辑。
Q11:如果忘了拉高片选会怎样?
答:FM25CL64B 一直选中,下一条指令被当作本次操作的后续字节,命令错位。
Q12:FRAM_wr() 和 FRAM_rd() 有什么区别?
答:
-
FRAM_wr():发 WRITE(0x02) + 2 字节地址 + 2 字节数据,写操作。 -
FRAM_rd():发 READ(0x03) + 2 字节地址 + 2 字节哑元,读操作。 -
读不需要 WREN,写必须先 WREN。
六、一句话总结
FRAM_wr()就干六件事:拉低片选 → 发 WRITE(0x0200) → 发地址高字节 → 发地址低字节 → 发数据高字节 → 发数据低字节 → 拉高片选。核心点:WRITE 前必须先 WREN ;WRITE 指令是 0x0200(左对齐) ;地址和数据都是 MSB first,高字节先发 ;低字节用
<< 8左移到高 8 位 ;片选上升沿触发内部写周期并清 WEL。面试重点:WRITE 前必须 WREN、左对齐原因、地址/数据字节序、低字节左移、片选上升沿作用。