FPGA实战:从零构建IIC控制器驱动AT24C128与LM75
在嵌入式系统开发中,IIC总线因其简洁的两线制设计和多设备支持特性,成为连接各类传感器的首选方案。本文将带您深入理解IIC协议底层机制,并完整实现一个可复用的Verilog IIC控制器模块,重点解决AT24C128 EEPROM存储芯片和LM75温度传感器的实际驱动问题。
1. IIC协议核心机制解析
IIC总线由串行时钟线(SCL)和串行数据线(SDA)组成,采用主从架构。理解以下关键时序是自主实现控制器的前提:
基本时序单元对比表
| 时序类型 | SCL状态 | SDA变化时机 | 典型持续时间 |
|---|---|---|---|
| START | 高电平 | 下降沿 | >600ns |
| STOP | 高电平 | 上升沿 | >1300ns |
| 数据位 | 低电平 | 允许变化 | 根据时钟速率 |
| ACK周期 | 第9时钟 | 从机响应 | 完整时钟周期 |
实际调试中发现,从设备对时序的要求往往比协议规范更严格。例如在驱动AT24C128时,两次写操作之间必须保证至少5ms的间隔,否则会导致写入失败。
提示:使用示波器抓取总线波形时,建议将触发条件设置为SDA下降沿,这样能清晰捕捉START条件和数据起始位
2. 状态机设计与实现策略
2.1 分层状态机架构
我们将控制器分为主状态机和底层时序机两个层级:
// 主状态机状态定义 parameter MAIN_IDLE = 0; // 空闲状态 parameter MAIN_STAR = 1; // 启动传输 parameter MAIN_DEV0 = 2; // 发送设备地址(写) parameter MAIN_ADDR = 3; // 发送内存地址 parameter MAIN_REST = 4; // 重复启动 parameter MAIN_DEV1 = 5; // 发送设备地址(读) parameter MAIN_R8BI = 6; // 读取数据字节 parameter MAIN_W8BI = 7; // 写入数据字节 parameter MAIN_STOP = 8; // 停止传输2.2 关键状态转换逻辑
写操作典型流程:
- 发送START条件
- 发送设备地址 + 写标志位
- 发送内存地址(1-2字节)
- 发送数据字节(可连续多个)
- 发送STOP条件
读操作需要特别注意"重复启动"机制:
- 先按写操作流程发送设备地址和内存地址
- 发送重复START条件
- 发送设备地址 + 读标志位
- 读取数据字节
- 发送STOP条件
always @(posedge clk) begin case(cur_state) MAIN_IDLE: if(req) next_state <= MAIN_STAR; MAIN_STAR: if(start_done) next_state <= MAIN_DEV0; MAIN_DEV0: if(addr_ack) next_state <= wr ? MAIN_ADDR : MAIN_REST; // ...其他状态转换 endcase end3. 设备特定驱动实现
3.1 AT24C128 EEPROM驱动要点
AT24C128采用7位设备地址0b1010xxx,其中xxx由硬件引脚决定。其特殊要求包括:
- 地址字节:2字节(最大支持128Kbit)
- 页写入限制:每页64字节,跨页需要分多次写入
- 写入周期:典型5ms,必须等待完成
典型写入序列:
- START
- 0xA0 (设备地址 + 写)
- 地址高字节
- 地址低字节
- 数据字节1
- ...
- 数据字节N
- STOP
3.2 LM75温度传感器驱动
LM75作为温度传感器具有以下特点:
- 固定设备地址0b1001xxx
- 温度寄存器地址0x00
- 16位温度值,实际有效位为11位(bit[15:5])
- 温度分辨率:0.125°C
读取温度值的典型流程:
// 伪代码示例 iic_start(); iic_write_byte(0x90); // 设备地址 + 写 iic_write_byte(0x00); // 温度寄存器地址 iic_start(); // 重复启动 iic_write_byte(0x91); // 设备地址 + 读 temp_high = iic_read_byte(ACK); temp_low = iic_read_byte(NOACK); iic_stop();4. 调试技巧与实战经验
4.1 仿真波形分析
在ModelSim中设置合理的时序约束后,重点关注以下信号:
- SCL/SDA的相位关系
- START/STOP条件的建立时间
- ACK位的响应时机
- 数据位的建立保持时间
常见问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无ACK响应 | 设备地址错误 | 核对设备手册地址 |
| 数据错误 | 时序不满足 | 调整时钟速率 |
| 写入失败 | 未满足保持时间 | 增加两次操作间隔 |
4.2 硬件调试要点
- 上拉电阻选择:通常4.7kΩ,长线缆需减小阻值
- 信号完整性:避免过长的飞线,必要时使用屏蔽线
- 电源干扰:确保传感器供电稳定,可增加去耦电容
注意:实际测试中发现,某些国产替代芯片的时序要求比原厂更严格,建议在设计阶段预留20%的时序余量
5. 完整模块设计与优化
5.1 接口定义
module iic_controller ( input clk, // 系统时钟(建议>=10MHz) input rst, // 异步复位 // 用户接口 input [6:0] dev_addr, // 设备地址 input [15:0] mem_addr, // 内存地址 input [7:0] wdata, // 写入数据 output [15:0] rdata, // 读取数据 input wr, // 写使能 input req, // 请求启动 output reg ack, // 操作完成确认 // 物理接口 output scl_out, output scl_oen, // 输出使能(0:输出) input scl_in, output sda_out, output sda_oen, input sda_in );5.2 时钟生成策略
为支持不同速率的设备,我们实现可配置的时钟分频:
// 时钟分频参数计算 parameter CLK_FREQ = 50_000_000; // 50MHz系统时钟 parameter IIC_FREQ = 100_000; // 100kHz IIC时钟 localparam DIVIDER = CLK_FREQ / (IIC_FREQ * 2); reg [7:0] clk_cnt; always @(posedge clk) begin if(clk_cnt == DIVIDER-1) begin clk_cnt <= 0; scl_tick <= 1; end else begin clk_cnt <= clk_cnt + 1; scl_tick <= 0; end end5.3 仲裁与错误处理
完善的控制器应包含以下保护机制:
- 总线忙检测
- 超时处理
- 多主机仲裁支持
- ACK错误重试
// 超时计数器示例 reg [15:0] timeout; always @(posedge clk) begin if(state != next_state) timeout <= 0; else if(timeout < 16'hFFFF) timeout <= timeout + 1; end wire bus_timeout = (timeout > TIMEOUT_LIMIT);在多次项目实践中,这个自主实现的IIC控制器表现出比商用IP核更好的灵活性,特别是在需要非标准时钟速率或特殊时序要求的场景下。调试时建议先使用低速模式(如10kHz)确保基本功能正常,再逐步提高时钟频率。