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 控制器前,需要完成如下配置:
-
禁用控制器 :写
0到DW_IC_ENABLE。 -
配置时钟 :根据 APB 时钟频率和目标 I2C 速率,计算并设置
DW_IC_SS_SCL_HCNT和DW_IC_SS_SCL_LCNT(标准模式)或DW_IC_FS_SCL_HCNT和DW_IC_FS_SCL_LCNT(快速模式)。 -
配置中断 :设置
DW_IC_RX_TL和DW_IC_TX_TL(如 FIFO 深度为 8,通常设为7和0)。根据需求设置DW_IC_INTR_MASK中断屏蔽。 -
配置控制寄存器 :设置
DW_IC_CON,配置为主机模式、速度模式等。 -
设置目标地址 :向
DW_IC_TAR写入从设备地址。 -
使能控制器 :写
1到DW_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)应遵循以下步骤:
-
读取
DW_IC_RAW_INTR_STAT或DW_IC_INTR_STAT获取中断源。 -
根据中断源执行相应操作,例如:
-
TX_EMPTY:继续填充发送 FIFO。 -
RX_FULL:读取接收 FIFO 中的数据。 -
TX_ABRT:读取DW_IC_TX_ABRT_SOURCE获取错误原因。 -
STOP_DET:标志传输完成。
-
-
清除中断:读取对应的清除寄存器(如
DW_IC_CLR_INTR、DW_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 总线,如 i2cget、i2cset 等工具。
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, ®, 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 Source :
drivers/i2c/busses/i2c-designware-*.c是驱动源码。 -
Device Tree Bindings :内核文档
Documentation/devicetree/bindings/i2c/i2c-designware.txt定义了设备树绑定规则。