DW I2C寄存器与使用简介

DW_apb_i2c 是 Synopsys DesignWare 提供的一个功能强大的 I2C 总线接口 IP(Intellectual Property)核,支持主从模式、标准/快速/高速模式,以及灵活的 FIFO 深度配置。它基于 AMBA APB 总线,具备中断控制和 DMA 接口,在嵌入式系统中应用非常广泛。

接下来,我将分块为你详细介绍该模块的寄存器、工作原理、编程流程,并提供裸机与 Linux 环境下的代码示例。


📌 一、寄存器详解

DW_apb_i2c 模块的寄存器都映射到一段连续的内存地址上,以下是关键寄存器及其偏移地址和功能说明。

偏移地址 (Offset) 寄存器名称 (Register) 功能描述
0x00 DW_IC_CON 控制寄存器:配置 I2C 的工作模式(主/从、速度模式、地址格式、重启使能等)。
0x04 DW_IC_TAR 目标地址寄存器:设置主模式下要访问的从设备地址。
0x10 DW_IC_DATA_CMD 数据命令寄存器:发送要传输的数据,并指示传输方向。
0x14 DW_IC_SS_SCL_HCNT 标准速度模式下 SCL 高电平计数:用于生成 I2C 标准速度模式时钟。
0x18 DW_IC_SS_SCL_LCNT 标准速度模式下 SCL 低电平计数:同上,用于配置 SCL 低电平持续时间。
0x1C DW_IC_FS_SCL_HCNT 快速模式下 SCL 高电平计数:用于生成 I2C 快速模式时钟。
0x20 DW_IC_FS_SCL_LCNT 快速模式下 SCL 低电平计数:同上,用于配置 SCL 低电平持续时间。
0x2C DW_IC_INTR_STAT 中断状态寄存器:反映了当前使能的中断状态。
0x30 DW_IC_INTR_MASK 中断屏蔽寄存器:屏蔽/使能特定的中断源。
0x34 DW_IC_RAW_INTR_STAT 原始中断状态寄存器:反映所有中断源的原始状态(不受屏蔽影响)。
0x38 DW_IC_RX_TL 接收 FIFO 阈值寄存器:设置 RX FIFO 的中断触发阈值。
0x3C DW_IC_TX_TL 发送 FIFO 阈值寄存器:设置 TX FIFO 的中断触发阈值。
0x40 DW_IC_CLR_INTR 清除中断寄存器:读此寄存器会清除所有已触发的中断。
0x44 DW_IC_CLR_RX_UNDER 清除接收下溢中断:读此寄存器清除接收下溢中断。
0x48 DW_IC_CLR_RX_OVER 清除接收溢出中断:读此寄存器清除接收溢出中断。
0x4C DW_IC_CLR_TX_OVER 清除发送溢出中断:读此寄存器清除发送溢出中断。
0x50 DW_IC_CLR_RD_REQ 清除读请求中断:用于从机模式,清除读请求。
0x54 DW_IC_CLR_TX_ABRT 清除发送中止中断:读此寄存器清除发送中止中断,可用于诊断。
0x58 DW_IC_CLR_RX_DONE 清除接收完成中断:读此寄存器清除接收完成中断。
0x5C DW_IC_CLR_ACTIVITY 清除活动状态中断:读此寄存器清除活动状态中断。
0x60 DW_IC_CLR_STOP_DET 清除停止检测中断:读此寄存器清除停止检测中断。
0x64 DW_IC_CLR_START_DET 清除启动检测中断:读此寄存器清除启动检测中断。
0x68 DW_IC_CLR_GEN_CALL 清除广播调用中断:读此寄存器清除广播调用中断。
0x6C DW_IC_ENABLE 使能寄存器:使能/禁用 I2C 控制器。
0x70 DW_IC_STATUS 状态寄存器:指示 I2C 控制器的当前状态(如忙/闲)。
0x74 DW_IC_TXFLR 发送 FIFO 状态寄存器:指示 TX FIFO 中当前的字节数。
0x78 DW_IC_RXFLR 接收 FIFO 状态寄存器:指示 RX FIFO 中当前的字节数。
0x80 DW_IC_TX_ABRT_SOURCE 发送中止源寄存器:指示导致发送中止的详细原因。
0xF4 DW_IC_COMP_PARAM_1 组件参数寄存器 1:提供 IP 配置参数(如 FIFO 深度)。

每个寄存器的关键位域定义如下:

1. 控制寄存器 (DW_IC_CON)

  • MASTER_MODE:设为 1 配置为主机模式。

  • SPEED:标准 100K 或快速 400K 模式。

  • RESTART_EN:允许产生重复起始条件。

  • SLAVE_DISABLE:主机模式下需设为 1

2. 数据命令寄存器 (DW_IC_DATA_CMD)

  • DAT (位 7-0):要发送或接收的数据字节。

  • CMD (位 8):0 为写,1 为读。

  • STOP (位 9):1 表示传输完成后发送停止条件。

3. 中断屏蔽寄存器 (DW_IC_INTR_MASK)

  • 支持的中断源包括:

    • RX_UNDER:接收下溢。

    • RX_OVER:接收溢出。

    • RX_FULL:接收 FIFO 达到阈值。

    • TX_OVER:发送溢出。

    • TX_EMPTY:发送 FIFO 为空或低于阈值。

    • RD_REQ:读请求(从机模式)。

    • TX_ABRT:发送中止。

    • RX_DONE:接收完成。

    • ACTIVITY:总线活动。

    • STOP_DET:检测到停止条件。

    • START_DET:检测到启动条件。

4. 时钟高/低计数寄存器 (DW_IC_SS_SCL_HCNT / DW_IC_SS_SCL_LCNT)

  • 计算公式基于 APB 总线时钟频率和所需 I2C 时钟周期。

  • 需查阅芯片数据手册获取准确计算方式。

5. 发送中止源寄存器 (DW_IC_TX_ABRT_SOURCE)

  • 常见错误原因:

    • ABRT_7B_ADDR_NOACK:7 位地址无应答。

    • ABRT_10ADDR1_NOACK:10 位地址首字节无应答。

    • ABRT_10ADDR2_NOACK:10 位地址次字节无应答。

    • ABRT_TXDATA_NOACK:数据无应答。

    • ABRT_GCALL_NOACK:广播地址无应答。


📌 二、初始化流程

在使用 I2C 控制器前,需要完成如下配置:

  1. 禁用控制器 :写 0DW_IC_ENABLE

  2. 配置时钟 :根据 APB 时钟频率和目标 I2C 速率,计算并设置 DW_IC_SS_SCL_HCNTDW_IC_SS_SCL_LCNT(标准模式)或 DW_IC_FS_SCL_HCNTDW_IC_FS_SCL_LCNT(快速模式)。

  3. 配置中断 :设置 DW_IC_RX_TLDW_IC_TX_TL(如 FIFO 深度为 8,通常设为 70)。根据需求设置 DW_IC_INTR_MASK 中断屏蔽。

  4. 配置控制寄存器 :设置 DW_IC_CON,配置为主机模式、速度模式等。

  5. 设置目标地址 :向 DW_IC_TAR 写入从设备地址。

  6. 使能控制器 :写 1DW_IC_ENABLE


📌 三、主机发送与接收流程

1. 主机发送 (Master Transmit)
  • DW_IC_DATA_CMD 写入要发送的数据,CMD 位设为 0

  • 最后一项数据的 STOP 位设为 1

  • 等待发送完成中断(如 TX_EMPTY)或轮询 DW_IC_TXFLR

2. 主机接收 (Master Receive)
  • DW_IC_DATA_CMD 写入任意值(如 0xFF),CMD 位设为 1

  • DW_IC_DATA_CMD 读取接收到的数据。

  • 最后一项数据的 STOP 位设为 1,以发送停止条件。


📌 四、中断处理

中断服务程序(ISR)应遵循以下步骤:

  1. 读取 DW_IC_RAW_INTR_STATDW_IC_INTR_STAT 获取中断源。

  2. 根据中断源执行相应操作,例如:

    • TX_EMPTY:继续填充发送 FIFO。

    • RX_FULL:读取接收 FIFO 中的数据。

    • TX_ABRT:读取 DW_IC_TX_ABRT_SOURCE 获取错误原因。

    • STOP_DET:标志传输完成。

  3. 清除中断:读取对应的清除寄存器(如 DW_IC_CLR_INTRDW_IC_CLR_TX_ABRT)。


📌 五、代码示例

1. 裸机驱动示例
cpp 复制代码
#include <stdint.h>

// 寄存器偏移定义
#define DW_IC_CON          0x00
#define DW_IC_TAR          0x04
#define DW_IC_DATA_CMD     0x10
#define DW_IC_SS_SCL_HCNT  0x14
#define DW_IC_SS_SCL_LCNT  0x18
#define DW_IC_FS_SCL_HCNT  0x1C
#define DW_IC_FS_SCL_LCNT  0x20
#define DW_IC_INTR_MASK    0x30
#define DW_IC_RAW_INTR_STAT 0x34
#define DW_IC_RX_TL        0x38
#define DW_IC_TX_TL        0x3C
#define DW_IC_CLR_INTR     0x40
#define DW_IC_CLR_TX_ABRT  0x54
#define DW_IC_ENABLE       0x6C
#define DW_IC_STATUS       0x70
#define DW_IC_TXFLR        0x74
#define DW_IC_RXFLR        0x78
#define DW_IC_TX_ABRT_SOURCE 0x80

// 寄存器位定义
#define DW_IC_CON_MASTER        (1 << 0)
#define DW_IC_CON_SPEED_STD     (1 << 1)
#define DW_IC_CON_SPEED_FAST    (1 << 2)
#define DW_IC_CON_RESTART_EN    (1 << 5)
#define DW_IC_CON_SLAVE_DISABLE (1 << 6)

#define DW_IC_INTR_TX_EMPTY     (1 << 4)
#define DW_IC_INTR_TX_ABRT      (1 << 6)
#define DW_IC_INTR_STOP_DET     (1 << 9)

typedef struct {
    uint32_t base_addr;
} dw_i2c_t;

// 底层读写函数
static inline void dw_i2c_write32(dw_i2c_t *i2c, uint32_t reg, uint32_t val) {
    *(volatile uint32_t *)(i2c->base_addr + reg) = val;
}

static inline uint32_t dw_i2c_read32(dw_i2c_t *i2c, uint32_t reg) {
    return *(volatile uint32_t *)(i2c->base_addr + reg);
}

// 初始化 I2C 控制器
void dw_i2c_init(dw_i2c_t *i2c, uint32_t clk_rate, uint32_t i2c_speed) {
    // 1. 禁用控制器
    dw_i2c_write32(i2c, DW_IC_ENABLE, 0);
    
    // 2. 计算并设置 SCL 时钟计数(示例值,需根据实际计算)
    uint32_t hcnt = (clk_rate / (2 * i2c_speed)) - 7;
    uint32_t lcnt = (clk_rate / (2 * i2c_speed)) - 5;
    if (i2c_speed <= 100000) { // 标准模式
        dw_i2c_write32(i2c, DW_IC_SS_SCL_HCNT, hcnt);
        dw_i2c_write32(i2c, DW_IC_SS_SCL_LCNT, lcnt);
    } else { // 快速模式
        dw_i2c_write32(i2c, DW_IC_FS_SCL_HCNT, hcnt);
        dw_i2c_write32(i2c, DW_IC_FS_SCL_LCNT, lcnt);
    }
    
    // 3. 设置 FIFO 阈值(假设 FIFO 深度为 8)
    dw_i2c_write32(i2c, DW_IC_RX_TL, 7);
    dw_i2c_write32(i2c, DW_IC_TX_TL, 0);
    
    // 4. 配置控制寄存器
    uint32_t con = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE | DW_IC_CON_RESTART_EN;
    if (i2c_speed <= 100000)
        con |= DW_IC_CON_SPEED_STD;
    else
        con |= DW_IC_CON_SPEED_FAST;
    dw_i2c_write32(i2c, DW_IC_CON, con);
    
    // 5. 使能控制器
    dw_i2c_write32(i2c, DW_IC_ENABLE, 1);
}

// 主机发送数据
int dw_i2c_master_tx(dw_i2c_t *i2c, uint16_t slave_addr, uint8_t *data, int len) {
    // 1. 设置目标地址
    dw_i2c_write32(i2c, DW_IC_TAR, slave_addr);
    
    // 2. 发送数据
    for (int i = 0; i < len; i++) {
        uint32_t cmd = data[i];
        if (i == len - 1)
            cmd |= (1 << 9); // 设置 STOP 位
        dw_i2c_write32(i2c, DW_IC_DATA_CMD, cmd);
    }
    
    // 3. 等待传输完成
    while (!(dw_i2c_read32(i2c, DW_IC_RAW_INTR_STAT) & DW_IC_INTR_STOP_DET));
    dw_i2c_read32(i2c, DW_IC_CLR_INTR); // 清除中断
    
    // 4. 检查错误
    if (dw_i2c_read32(i2c, DW_IC_RAW_INTR_STAT) & DW_IC_INTR_TX_ABRT) {
        uint32_t abort_src = dw_i2c_read32(i2c, DW_IC_TX_ABRT_SOURCE);
        dw_i2c_read32(i2c, DW_IC_CLR_TX_ABRT);
        return -abort_src; // 返回错误码
    }
    return 0;
}

// 主机接收数据
int dw_i2c_master_rx(dw_i2c_t *i2c, uint16_t slave_addr, uint8_t *data, int len) {
    // 1. 设置目标地址
    dw_i2c_write32(i2c, DW_IC_TAR, slave_addr);
    
    // 2. 发送读命令
    for (int i = 0; i < len; i++) {
        uint32_t cmd = (1 << 8); // 设置 CMD 位为 1(读)
        if (i == len - 1)
            cmd |= (1 << 9); // 设置 STOP 位
        dw_i2c_write32(i2c, DW_IC_DATA_CMD, cmd);
    }
    
    // 3. 等待数据接收完成
    while (dw_i2c_read32(i2c, DW_IC_RXFLR) < len);
    
    // 4. 读取数据
    for (int i = 0; i < len; i++) {
        uint32_t val = dw_i2c_read32(i2c, DW_IC_DATA_CMD);
        data[i] = val & 0xFF;
    }
    
    // 5. 等待停止条件
    while (!(dw_i2c_read32(i2c, DW_IC_RAW_INTR_STAT) & DW_IC_INTR_STOP_DET));
    dw_i2c_read32(i2c, DW_IC_CLR_INTR);
    
    return 0;
}
2. Linux 内核驱动

Linux 内核已经包含了完善的 i2c-designware 驱动,通常不需要从头编写。使用时只需在设备树中添加相应节点,内核会自动加载驱动。

一个典型的设备树节点如下:

dts

复制代码
i2c@f0000 {
    #address-cells = <1>;
    #size-cells = <0>;
    compatible = "snps,designware-i2c";
    reg = <0xf0000 0x1000>;
    interrupts = <15>;
    clock-frequency = <400000>;
};

配置完成后,可在用户空间通过 /dev/i2c-X 设备文件访问 I2C 总线,如 i2cgeti2cset 等工具。

3. Linux 用户空间示例

以下是一个使用标准 I2C 接口读取从设备寄存器的用户空间程序示例:

cpp 复制代码
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>

int main() {
    int fd = open("/dev/i2c-1", O_RDWR);
    if (fd < 0) return 1;
    
    int slave_addr = 0x50;  // 从设备地址
    if (ioctl(fd, I2C_SLAVE, slave_addr) < 0) return 1;
    
    // 写寄存器地址
    unsigned char reg = 0x10;
    if (write(fd, &reg, 1) != 1) return 1;
    
    // 读取数据
    unsigned char data;
    if (read(fd, &data, 1) != 1) return 1;
    
    printf("Read data: 0x%02x\n", data);
    close(fd);
    return 0;
}

📌 六、常见问题与调试

  • 传输中止 :读取 DW_IC_TX_ABRT_SOURCE 寄存器获取原因,常见为地址或数据无应答。

  • 时钟配置错误 :确保 DW_IC_SS_SCL_HCNT / DW_IC_SS_SCL_LCNT 计算正确,否则 SCL 频率不准确。

  • FIFO 溢出:检查 RX/TX 阈值设置,确保及时处理数据。

  • 中断未触发 :检查 DW_IC_INTR_MASK 是否使能了对应中断源。


🔗 七、参考资源

  • Synopsys DesignWare DW_apb_i2c Databook:官方数据手册,包含完整的寄存器定义和时序要求。

  • Linux Kernel Sourcedrivers/i2c/busses/i2c-designware-*.c 是驱动源码。

  • Device Tree Bindings :内核文档 Documentation/devicetree/bindings/i2c/i2c-designware.txt 定义了设备树绑定规则。

相关推荐
Mr..Jackey2 小时前
RA6809 的 HMI(人机交互) 开发:菜单逻辑架构设计与实现详解(4)
单片机·51单片机·人机交互·交互
笨笨饿2 小时前
#65_反激电源
stm32·单片机·嵌入式硬件·算法·硬件工程·个人开发
汽车芯猿2 小时前
嵌入式固件内存占用分析利器:Python实现S19/HEX地址空间可视化工具
python·单片机·嵌入式硬件
LCG元11 小时前
STM32实战:基于STM32F103的Bootloader设计与IAP在线升级
javascript·stm32·嵌入式硬件
不怕犯错,就怕不做12 小时前
Linux-Sensor驱动移植与调试(转载)
linux·驱动开发·嵌入式硬件
LCMICRO-1331084774612 小时前
长芯微LCMDC8584完全P2P替代ADS8584,是一款16位、4通道同步采样的逐次逼近型(SAR)模数转换器(ADC)
stm32·单片机·嵌入式硬件·fpga开发·硬件工程·模数转换器adc
STC_USB_CAN_805112 小时前
菜单学习,科学计算器使用【TFT240*320彩屏+实际键盘】@Ai8051U,ST7789
单片机·学习·51单片机
FreakStudio13 小时前
无硬件学LVGL—定时器篇:基于Web模拟器+MicroPython速通GUI开发
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机
异方辰电子15 小时前
8.原理图为什么看不到具体的电路(比如STM32的晶振等)
stm32·单片机·嵌入式硬件