从APB到SDA:手把手教你用Verilog搭建一个可配置的I2C Master控制器
在嵌入式系统和FPGA设计中,I2C总线因其简单的两线制结构和多设备支持能力,成为连接低速外设的首选方案。本文将带你从零开始,用Verilog实现一个基于APB总线的可配置I2C Master控制器,重点解决实际工程中的时序同步、状态机设计和调试难题。
1. I2C Master控制器的核心架构
一个完整的I2C Master控制器需要处理协议时序、总线仲裁、时钟同步等多个复杂问题。我们的设计采用分层架构:
- APB接口层:负责与处理器通信,接收配置参数和传输数据
- 寄存器组:包括时钟分频、控制状态、数据缓冲等寄存器
- 协议引擎:实现START/STOP条件生成、ACK检测等状态机
- 时钟生成模块:根据Prescale值产生符合标准的SCL时钟
- 数据移位单元:处理并行-串行转换和信号同步
典型的寄存器映射如下:
| 寄存器名 | 地址偏移 | 宽度 | 功能描述 |
|---|---|---|---|
| CTRL | 0x00 | 8 | 控制寄存器(使能/中断) |
| PRESCALE | 0x04 | 16 | 时钟分频系数 |
| TXDATA | 0x08 | 8 | 发送数据缓冲 |
| RXDATA | 0x0C | 8 | 接收数据缓冲 |
| STATUS | 0x10 | 8 | 状态标志(BUSY/ACK等) |
提示:APB总线采用小端模式,寄存器地址偏移需按32位对齐
2. 时钟生成与同步设计
I2C的时钟同步是设计中最容易出问题的部分。我们需要实现:
- 根据系统时钟和Prescale值生成标准SCL
- 处理多Master场景下的时钟同步
- 确保建立/保持时间满足规范要求
时钟分频的核心代码如下:
always @(posedge clk or negedge rst_n) begin if (!rst_n) begin clk_cnt <= 16'd0; scl_out <= 1'b1; end else if (i2c_enable) begin if (clk_cnt >= prescale_val) begin clk_cnt <= 16'd0; scl_out <= ~scl_out; // 翻转SCL时钟 end else begin clk_cnt <= clk_cnt + 1; end end end关键时序参数:
| 模式 | 标准速率 | 建立时间 | 保持时间 |
|---|---|---|---|
| 标准模式 | 100kHz | 250ns | 300ns |
| 快速模式 | 400kHz | 100ns | 200ns |
| 高速模式 | 3.4MHz | 50ns | 100ns |
3. I2C协议状态机实现
I2C协议的核心是一个精确的状态机,需要处理以下状态转换:
- IDLE:等待传输开始
- START:生成起始条件
- ADDR:发送设备地址+R/W位
- DATA_TX/RX:数据传输阶段
- ACK/NACK:应答处理
- STOP:生成停止条件
状态机Verilog实现片段:
parameter [2:0] IDLE = 3'b000, START = 3'b001, ADDR = 3'b010, DATA = 3'b011, ACK = 3'b100, STOP = 3'b101; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= IDLE; sda_out <= 1'b1; end else begin case(state) IDLE: if (start_cond) state <= START; START: begin sda_out <= 1'b0; state <= ADDR; end // 其他状态处理... endcase end end4. 典型应用:读取温度传感器数据
以常见的LM75温度传感器为例,演示完整的读写流程:
初始化配置:
- 设置Prescale寄存器产生100kHz SCL
- 使能I2C控制器(CTRL.I2C_EN=1)
写入传感器地址:
// 写入设备地址(0x48) + 写方向 I2C->TXDATA = 0x90; // 0x48 << 1 | 0写入寄存器指针:
I2C->TXDATA = 0x00; // 选择温度寄存器重新START并读取数据:
// 发送重复START // 写入设备地址(0x48) + 读方向 I2C->TXDATA = 0x91; // 0x48 << 1 | 1 // 读取两个字节数据 temp_high = I2C->RXDATA; temp_low = I2C->RXDATA;
5. 调试技巧与常见问题
在实际调试中,以下几个工具和技术特别有用:
- 逻辑分析仪:抓取SCL/SDA波形,验证时序
- 仿真测试:构建完整的Testbench环境
- 状态机跟踪:通过LED或串口输出当前状态
常见问题排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无ACK响应 | 设备地址错误/设备未就位 | 检查地址/供电 |
| 数据采样错误 | 建立/保持时间不足 | 调整SCL相位 |
| 总线死锁 | 异常中断未发送STOP | 硬件复位/重新初始化 |
| 仲裁失败 | 多Master竞争 | 增加重试机制 |
在FPGA开发板上实测时,发现SCL信号质量对传输稳定性影响极大。建议在PCB设计时:
- 保持SCL/SDA走线等长
- 使用合适的上拉电阻(通常4.7kΩ)
- 避免与其他高速信号平行走线