4、SDRAM扩展实战:W9825G6KH芯片介绍、SDRAM初始化流程
各位好,我是老周。今天咱们聊聊SDRAM扩展实战。说实话,在工业MCU项目里,内部RAM不够用是家常便饭。跑个复杂的算法,或者做个大点的显示缓冲区,内部那几十KB根本不够看。这时候,外挂一片SDRAM就是最实在的解决方案。
我选W9825G6KH这颗芯片来讲,原因很简单——它太经典了。华邦的这颗SDRAM,容量32MB,16位数据宽度,工业级温度范围,价格也亲民。我在好几个项目里都用过它,从来没出过幺蛾子。
W9825G6KH芯片速览
先看几个关键参数,心里有个底:
| 参数 | 值 |
|---|---|
| 容量 | 256Mb(32MB) |
| 数据宽度 | 16位 |
| Bank数量 | 4个 |
| 行地址 | A0-A12(13位) |
| 列地址 | A0-A8(9位) |
| 工作电压 | 3.3V ± 0.3V |
| 封装 | TSOP-54 |
嗯,这里有个小细节。它的行地址是13位,列地址是9位,4个Bank。算一下:2^13 × 2^9 × 4 × 16位 = 256Mb。没错,对得上。
SDRAM初始化流程
SDRAM的初始化,说白了就是让它从「刚上电的混沌状态」进入「可以正常读写的就绪状态」。这个过程有严格的时序要求,一步都不能错。
我画了个流程图,帮你理清思路:
这个流程看着简单,但每个步骤都有坑。我一个一个说。
第一步:上电与稳定
上电后,VDD必须在200μs内稳定到3.3V。同时,CKE引脚必须保持高电平。为什么?因为CKE是时钟使能,如果它拉低了,SDRAM会进入掉电模式,初始化就卡住了。
我曾经在一个项目里,因为电源纹波太大,VDD稳定时间超过了200μs,结果SDRAM死活初始化不成功。后来加了颗10μF的电容才搞定。嗯,电源设计真的不能省。
第二步:预充电所有Bank
预充电,说白了就是把所有Bank的行都关掉,让SDRAM回到「待机状态」。这个命令通过控制CS、RAS、CAS、WE这几个引脚来实现。
命令编码是这样的:
// 预充电命令(PRE)
// CS=0, RAS=0, CAS=1, WE=0
// A10=1 表示预充电所有Bank
// A10=0 表示只预充电当前Bank
void SDRAM_PrechargeAll(void) {
// 设置控制引脚
SDRAM_CS = 0;
SDRAM_RAS = 0;
SDRAM_CAS = 1;
SDRAM_WE = 0;
// A10拉高,预充电所有Bank
SDRAM_A10 = 1;
// 保持至少tRP时间(20ns)
delay_ns(20);
// 恢复默认状态
SDRAM_CS = 1;
}
第三步:8个刷新周期
SDRAM是动态存储器,电容会漏电,所以必须定期刷新。初始化时,需要连续执行8个刷新周期,让内部电路稳定下来。
刷新命令和预充电很像,只是控制引脚不同:
// 刷新命令(REF)
// CS=0, RAS=0, CAS=0, WE=1
void SDRAM_Refresh(void) {
SDRAM_CS = 0;
SDRAM_RAS = 0;
SDRAM_CAS = 0;
SDRAM_WE = 1;
// 保持至少tRFC时间(70ns)
delay_ns(70);
SDRAM_CS = 1;
}
// 连续执行8次
for(int i = 0; i < 8; i++) {
SDRAM_Refresh();
}
你想想看,为什么是8次?不是4次也不是16次?其实这是芯片厂商规定的。W9825G6KH的数据手册里明确写了,初始化时需要至少8个刷新周期。我建议你严格按照手册来,别自作聪明。
第四步:模式寄存器配置
这一步最关键。模式寄存器(Mode Register)决定了SDRAM的工作方式,包括突发长度、突发类型、CAS延迟等。
W9825G6KH的模式寄存器有13位(A0-A12),各位的定义如下:
| 位 | 名称 | 说明 |
|---|---|---|
| A0-A2 | 突发长度 | 000=1, 001=2, 010=4, 011=8 |
| A3 | 突发类型 | 0=顺序, 1=交错 |
| A4-A6 | CAS延迟 | 010=2, 011=3 |
| A7-A12 | 保留/测试模式 | 通常设为0 |
我个人习惯用CAS延迟=3,突发长度=4。为什么?因为大多数MCU的SDRAM控制器都支持这个配置,兼容性最好。
// 模式寄存器配置(MRS)
// CS=0, RAS=0, CAS=0, WE=0
// 地址线A0-A12写入配置值
void SDRAM_MRS(void) {
// 配置值:CAS=3, 突发长度=4, 顺序突发
// A4-A6 = 011 (CAS=3)
// A0-A2 = 010 (突发长度=4)
// A3 = 0 (顺序突发)
uint16_t config = (3 << 4) | (2 << 0);
SDRAM_CS = 0;
SDRAM_RAS = 0;
SDRAM_CAS = 0;
SDRAM_WE = 0;
// 将配置值放到地址线上
SDRAM_ADDR = config;
// 保持至少tMRD时间(2个时钟周期)
delay_clk(2);
SDRAM_CS = 1;
}
读写操作实战
初始化完成后,就可以正常读写SDRAM了。读写操作的核心是「行激活 → 读写命令 → 预充电」这个循环。
举个例子,我想往地址0x1000写入一个16位数据:
// 写操作示例
// 地址0x1000,数据0xABCD
// 1. 行激活(ACTIVE命令)
// 行地址 = 0x1000 >> 9 = 0x8
// Bank地址 = 0(假设Bank0)
SDRAM_CS = 0;
SDRAM_RAS = 0;
SDRAM_CAS = 1;
SDRAM_WE = 1;
SDRAM_ADDR = 0x8; // 行地址
SDRAM_BA = 0; // Bank0
delay_ns(20); // 等待tRCD
// 2. 写命令(WRITE命令)
// 列地址 = 0x1000 & 0x1FF = 0x0
SDRAM_CS = 0;
SDRAM_RAS = 1;
SDRAM_CAS = 0;
SDRAM_WE = 0;
SDRAM_ADDR = 0x0; // 列地址
SDRAM_DQ = 0xABCD; // 写入数据
// A10=0,不自动预充电
delay_ns(20); // 等待tWR
// 3. 预充电(关闭当前行)
SDRAM_PrechargeAll();
读操作类似,只是WE引脚拉高:
// 读操作示例
// 读取地址0x1000的数据
// 1. 行激活(同上)
SDRAM_CS = 0;
SDRAM_RAS = 0;
SDRAM_CAS = 1;
SDRAM_WE = 1;
SDRAM_ADDR = 0x8;
SDRAM_BA = 0;
delay_ns(20);
// 2. 读命令(READ命令)
SDRAM_CS = 0;
SDRAM_RAS = 1;
SDRAM_CAS = 0;
SDRAM_WE = 1; // 读操作
SDRAM_ADDR = 0x0;
// 等待CAS延迟(3个时钟周期)
delay_clk(3);
uint16_t data = SDRAM_DQ; // 读取数据
// 3. 预充电
SDRAM_PrechargeAll();
嗯,到这里,SDRAM的初始化流程和基本读写操作就讲完了。你可能会问,为什么每次读写完都要预充电?其实不一定。如果你连续读写同一行的数据,可以跳过预充电,直接发新的读写命令。这叫「页操作」,效率更高。但如果你要访问不同行,就必须先预充电,再激活新行。
我在一个图像采集项目里,就是利用页操作来提升SDRAM吞吐量的。一帧图像数据连续存储在SDRAM的一行里,读的时候只需要激活一次行,然后连续发读命令就行。速度比逐行激活快了将近一倍。
好了,今天就聊到这儿。记住,SDRAM的时序要求很严格,初始化流程一步都不能错。如果你在实际调试中遇到问题,先检查电源,再检查时序,最后检查配置。八成的问题都出在这三个地方。
公众号:蓝海资料掘金营,微信deep3321