嵌入式硬件——基于IMX6ULL的I2C实现

作者:眰恦ゞLYF日期:2025/10/16

一、I2C 基础概念与硬件特性

1.1 I2C 总线核心定义

I2C(Inter-Integrated Circuit)是飞利浦提出的串行半双工通信总线,核心特点是两根信号线实现多设备互联:

  • SDA(Serial Data):双向数据线,用于传输数据;
  • SCL(Serial Clock):双向时钟线,由主设备产生,同步数据传输;
  • 上拉电阻:SDA 和 SCL 需外接(或引脚内部配置)上拉电阻(通常 4.7KΩ),空闲时保持高电平;
  • 主从架构:同一总线中仅 1 个主设备(如 I.MX6ULL),可挂载多个从设备(如 AT24C02、LM75),通过设备地址区分从设备。

1.2 I.MX6ULL I2C 硬件特性

  • 控制器数量:共 4 路 I2C 控制器(I2C1~I2C4),支持主 / 从模式;
  • 传输速率:标准模式(100Kbps)、快速模式(400Kbps);
  • 时钟源:默认使用IPG_CLK_ROOT(66MHz),通过分频器得到 I2C 工作时钟;
  • FIFO 支持:部分控制器含 TX/RXFIFO(如 ECSPI 关联的 I2C 无 FIFO,需软件模拟时序);
  • 中断支持:可配置 FIFO 空、传输完成、仲裁丢失等中断;
  • 器件兼容性:支持 I2C 标准从设备(EEPROM、传感器、时钟芯片等),本次重点适配AT24C02(EEPROM) 和LM75(温度传感器)。

二、I2C 核心通信时序

2.1 基础时序单元

  • 起始信号(S):SCL 为高电平时,SDA 从高电平拉低(下降沿),标志通信开始;
  • 停止信号(P):SCL 为高电平时,SDA 从低电平拉高(上升沿),标志通信结束;
  • 数据传输:SCL 高电平时,SDA 电平需稳定(数据有效);SCL 低电平时,SDA 可切换电平(准备下一位数据);
  • 应答(ACK):主设备发送 1 字节后,释放 SDA;从设备在 SCL 高电平时拉低 SDA,表示数据接收成功;
  • 非应答(NACK):主设备接收最后 1 字节后,SCL 高电平时保持 SDA 高电平,表示无需继续接收。

2.2 核心操作时序

从设备写操作(主→从,如向 AT24C02 写数据)
  • 主设备发送起始信号(S);
  • 主设备发送从设备地址 + 写标志(最低位为 0),等待从设备应答(ACK);
  • 主设备发送从设备内部寄存器地址(如 AT24C02 的存储地址),等待应答;
  • 主设备发送数据(1~N 字节),每字节后等待应答;
  • 主设备发送停止信号(P),结束写操作。
从设备读操作(从→主,如从 LM75 读温度)
  • 主设备发送起始信号(S);
  • 主设备发送从设备地址 + 写标志(0),等待应答(此时目的是 “告知读哪个寄存器”);
  • 主设备发送目标寄存器地址(如 LM75 的温度寄存器 0x00),等待应答;
  • 主设备发送重复起始信号(S)(不发停止信号,避免总线释放);
  • 主设备发送从设备地址 + 读标志(1),等待应答;
  • 主设备接收数据(1~N 字节):
    • 接收前 N-1 字节后,主设备发送 ACK;
    • 接收最后 1 字节后,主设备发送 NACK(告知从设备停止发送);
  • 主设备发送停止信号(P),结束读操作。

三、I.MX6ULL I2C 寄存器详解

核心寄存器

  • I2Cx_IADR:从设备地址寄存器;
  • I2Cx_IFDR:分频寄存器(决定 I2C 波特率);
  • I2Cx_I2CR:控制寄存器;
  • I2Cx_I2SR:状态寄存器;
  • I2Cx_I2DR:数据寄存器。

四、I2C 完整实现流程

4.1 I2C 引脚初始化

  • 配置复用功能和电气特性,以 I2C1 为例(SDA=UART4_RX,SCL=UART4_TX);
  • 初始化 I2C 控制器(先关闭,再配置分频)。

4.2 I2C 通用读写函数

  • i2c_write:向指定从设备的指定寄存器写入 N 字节数据;
  • i2c_read:从指定从设备的指定寄存器读取 N 字节数据。
I2C 写函数(i2c_write)

功能:向指定从设备的指定寄存器写入 N 字节数据

1// base:I2C控制器基地址(如I2C1)
2// device_address:从设备地址(如LM75=0x48)
3// reg_address:从设备寄存器地址(如LM75温度寄存器=0x00)
4// reg_len:寄存器地址长度(1或2字节)
5// data:待写入数据指针
6// len:数据长度
7void i2c_write(I2C_Type *base, unsigned char device_address, unsigned short reg_address, int reg_len, const unsigned char *data, int len) 
8{
9    // 1. 清除仲裁丢失和中断标志,等待总线空闲
10    base->I2SR &= ~((1 << 4) | (1 << 1));  // 清除IAL(bit4)和IIF(bit1)
11    while((base->I2SR & (1 << 7)) == 0);  // 等待ICF(bit7)=1(总线空闲)
12    
13    // 2. 配置为主设备发送模式,发送ACK
14    base->I2CR |= (1 << 5) | (1 << 4);  // MSTA=1(主模式)、MTX=1(发送)
15    base->I2CR &= ~(1 << 3);           // TXAK=0(发送ACK)
16    
17    // 3. 发送从设备地址(写模式:最低位=0)
18    base->I2SR &= ~(1 << 1);           // 清除IIF(中断标志)
19    base->I2DR = device_address << 1;   // 设备地址+写标志
20    while((base->I2SR & (1 << 1)) == 0);  // 等待传输完成(IIF=1)
21    
22    // 4. 发送寄存器地址(支持1/2字节)
23    for(int i = 0; i < reg_len; ++i) 
24    {
25        base->I2SR &= ~(1 << 1);       // 清除IIF
26        // 高位在前:若reg_len=2,先发高8位,再发低8位
27        base->I2DR = reg_address >> (reg_len - i - 1) * 8;
28        while((base->I2SR & (1 << 1)) == 0);  // 等待传输完成
29    }
30    
31    // 5. 发送数据(N字节)
32    while (len--) 
33    {
34        base->I2SR &= ~(1 << 1);       // 清除IIF
35        base->I2DR = *data++;          // 写入1字节数据
36        while((base->I2SR & (1 << 1)) == 0);  // 等待传输完成
37    }
38    
39    // 6. 发送停止信号(清除主模式)
40    base->I2CR &= ~(1 << 5);           // MSTA=0(释放主模式,产生停止信号)
41    while((base->I2SR & (1 << 5)) != 0);  // 等待IBB=0(总线空闲)
42    delayus(100);  // 短暂延时,确保停止信号稳定
43}
44
I2C 读函数(i2c_read)

功能:从指定从设备的指定寄存器读取 N 字节数据

1void i2c_read(I2C_Type *base, unsigned char device_address, unsigned short reg_address, int reg_len, unsigned char *data, int len) 
2{
3    // 1. 清除标志,等待总线空闲(同写函数)
4    base->I2SR &= ~((1 << 4) | (1 << 1));
5    while((base->I2SR & (1 << 7)) == 0);
6    
7    // 2. 配置为主设备发送模式,先写寄存器地址
8    base->I2CR |= (1 << 5) | (1 << 4);  // MSTA=1、MTX=1
9    base->I2CR &= ~(1 << 3);           // TXAK=0(发送ACK)
10    
11    // 3. 发送从设备地址(写模式)
12    base->I2SR &= ~(1 << 1);
13    base->I2DR = device_address << 1;
14    while((base->I2SR & (1 << 1)) == 0);
15    
16    // 4. 发送寄存器地址(同写函数)
17    for(int i = 0; i < reg_len; ++i) 
18    {
19        base->I2SR &= ~(1 << 1);
20        base->I2DR = reg_address >> (reg_len - i - 1) * 8;
21        while((base->I2SR & (1 << 1)) == 0);
22    }
23    
24    // 5. 发送重复起始信号,切换为读模式
25    base->I2CR |= (1 << 2);            // RSTA=1(产生重复起始)
26    base->I2SR &= ~(1 << 1);
27    base->I2DR = device_address << 1 | 1;  // 设备地址+读标志(最低位=1)
28    while((base->I2SR & (1 << 1)) == 0);
29    
30    // 6. 切换为接收模式
31    base->I2CR &= ~(1 << 4);           // MTX=0(接收)
32    base->I2SR &= ~(1 << 1);
33    
34    // 7. 若仅读1字节,提前发送NACK
35    if(len == 1) 
36    {
37        base->I2CR |= (1 << 3);        // TXAK=1(发送NACK)
38    }
39    *data = base->I2DR;  // 虚假读:触发接收(I2C全双工,发送时已接收无效数据)
40    
41    // 8. 接收N字节数据
42    while(len-- != 0) 
43    {
44        while ((base->I2SR & (1 << 1)) == 0);  // 等待接收完成
45        base->I2SR &= ~(1 << 1);
46        
47        // 处理最后1字节:发送停止信号
48        if(len == 0) 
49        {
50            base->I2CR &= ~((1 << 5) | (1 << 3));  // MSTA=0(停止)、TXAK=0
51            while((base->I2SR & (1 << 5)) != 0);    // 等待总线空闲
52        } 
53        // 处理倒数第2字节:提前发送NACK
54        else if (len == 1) 
55        {
56            base->I2CR |= (1 << 3);  // TXAK=1(下一字节发NACK)
57        }
58        
59        *data++ = base->I2DR;  // 读取接收数据
60    }
61}
62

4.3 封装传输函数(xfer)

封装I2C_MSG结构体,统一管理传输参数,提高代码复用性:

1// i2c.h 中定义结构体和枚举
2enum I2C_Direction 
3{
4    I2C_Write = 0,  // 写方向
5    I2C_Read = 1    // 读方向
6};
7
8struct I2C_MSG 
9{
10    unsigned char dev_address;   // 从设备地址
11    unsigned short reg_address;  // 寄存器地址
12    int reg_len;                 // 寄存器地址长度(1/2)
13    unsigned char *data;         // 数据指针
14    int len;                     // 数据长度
15    enum I2C_Direction direction;// 传输方向
16};
17
18// i2c.c 中实现传输函数
19void xfer(I2C_Type *base, struct I2C_MSG *msg) 
20{
21    if(msg->direction == I2C_Write) 
22    {
23        i2c_write(base, msg->dev_address, msg->reg_address, msg->reg_len, msg->data, msg->len);
24    } 
25    else 
26    {
27        i2c_read(base, msg->dev_address, msg->reg_address, msg->reg_len, msg->data, msg->len);
28    }
29}
30

五、LM75 温度传感器驱动(基于 I2C)

LM75 是 I2C 接口的温度传感器,设备地址为0x48,温度寄存器(0x00)存储 16 位数据(高 9 位为温度值,单位 0.5℃)。

5.1 温度读取函数(lm75.c

1#include "lm75.h"
2#include "i2c.h"
3#include "MCIMX6Y2.h"
4
5// 读取LM75温度(返回值:℃,如25.5℃返回25.5)
6float lm75_get_temperature(void) 
7{
8    unsigned char buffer[2] = {0};  // 存储16位温度数据
9    short temp_raw;                 // 原始温度值(16位)
10    
11    // 1. 构造I2C传输参数
12    struct I2C_MSG msg = 
13    {
14        .direction = I2C_Read,      // 读方向
15        .dev_address = 0x48,        // LM75设备地址
16        .reg_address = 0x00,        // 温度寄存器地址(1字节)
17        .reg_len = 1,               // 寄存器地址长度=1
18        .data = buffer,             // 数据缓冲区
19        .len = 2                    // 读取2字节
20    };
21    
22    // 2. 调用I2C传输函数
23    xfer(I2C1, &msg);
24    
25    // 3. 解析温度数据(LM75数据格式:高8位+低8位,高9位有效)
26    temp_raw = (buffer[0] << 8) | buffer[1];  // 组合16位原始数据
27    temp_raw >>= 7;                          // 右移7位,保留高9位(符号位+8位数值)
28    return temp_raw * 0.5f;                  // 0.5℃/LSB,转换为实际温度
29}
30

5.2 头文件声明(lm75.h

1#ifndef __LM75_H__
2#define __LM75_H__
3
4// 读取LM75温度,返回值单位:℃
5extern float lm75_get_temperature(void);
6
7#endif
8

5.3 主函数测试(main.c

初始化 I2C、UART 和 LM75,通过 UART 打印温度数据:

1#include "led.h"
2#include "uart.h"
3#include "i2c.h"
4#include "lm75.h"
5#include "delay.h"
6#include "stdio.h"
7
8int main(void) 
9{
10    // 1. 初始化系统时钟、UART(用于打印)、I2C1
11    init_clock();    // 初始化系统时钟(IPG_CLK=66MHz)
12    init_uart1();    // 初始化UART1(115200bps,用于打印温度)
13    init_i2c1();     // 初始化I2C1(100Kbps)
14    
15    while(1) 
16    {
17        // 2. 读取LM75温度
18        float temp = lm75_get_temperature();
19        
20        // 3. 格式化温度数据(避免浮点数打印误差)
21        int temp_int = (int)temp;          // 整数部分(如25.5→25)
22        int temp_dec = (int)((temp - temp_int) * 10);  // 小数部分(如25.5→5)
23        
24        // 4. 通过UART打印温度
25        printf("LM75 Temperature: %d.%d℃\n", temp_int, temp_dec);
26        
27        delayms(1000);  // 1秒刷新一次
28    }
29    return 0;
30}
31

六、关键注意事项

  1. 上拉电阻配置:I2C 总线必需上拉,可通过引脚电气属性配置内部上拉(如IOMUXC_SetPinConfigPUS位),或外接 4.7KΩ 电阻;
  2. 仲裁丢失处理:若多主设备竞争总线,I2SR->IAL会置 1,需清除该位后重新初始化 I2C;
  3. 应答判断:传输过程中需检查I2SR->RXAK,若为 1(接收 NACK),需重新发送或终止通信;
  4. 寄存器地址长度:不同器件的寄存器地址长度不同(如 AT24C02 为 1 字节,某些传感器为 2 字节),需在I2C_MSG中正确设置reg_len
  5. SION 位使能:部分 I2C 引脚需使能SION(软件输入路径),否则无法读取 SDA 电平(如IOMUXC_SetPinMux的第 2 个参数设为 1);
  6. 时钟分频计算:I2C 波特率 = IPG_CLK / 分频系数,标准模式(100Kbps)推荐分频系数 = 640(66MHz/640≈103Kbps),快速模式(400Kbps)推荐分频系数 = 160(66MHz/160≈412.5Kbps)。

七、总结

I.MX6ULL 的 I2C 开发的核心是严格遵循时序规范熟练操作寄存器,关键流程可概括为:引脚复用配置 → I2C控制器初始化(分频、使能) → 封装通用读写函数 → 适配具体I2C器件(LM75/AT24C02) → 测试验证。通过结构体封装传输参数(如I2C_MSG)可显著提高代码复用性,这一思想也与 Linux 内核 I2C 子系统的设计一致,为后续驱动开发打下基础。


嵌入式硬件——基于IMX6ULL的I2C实现》 是转载文章,点击查看原文


相关推荐


企业级 K8s 深度解析:从容器编排到云原生基石的十年演进
我的offer在哪里2025/10/14

引言:为什么 K8s 成为企业数字化的 "必选项" 2024 年 6 月,Kubernetes(简称 K8s)迎来了诞生十周年的里程碑。十年前,Google 工程师在 GitHub 上提交的第一行代码,如今已成长为全球最大的开源项目之一 —— 拥有来自 8000 多家公司、44 个国家的 88000 名贡献者,以及超过 700 万开发者组成的庞大社区。在企业战场,这个用 "8" 代替 "ubernete" 七个字符的技术名词,早已不是单纯的技术工具,而是支撑数字化转型的核心基础设施。 根据《


Go语言实战案例——进阶与部署篇:性能优化与 pprof 性能分析实践
程序员爱钓鱼2025/10/13

在实际开发中,当 Go 服务上线后,性能问题往往成为系统稳定性的关键因素。 有时是 CPU 占用过高,有时是内存泄漏,也可能是请求响应变慢。 要解决这些问题,不能仅依靠直觉,而应借助可靠的工具进行性能分析与定位。 Go 官方提供的 pprof 工具,正是性能分析的利器。 本文将通过一个完整的案例,带你了解如何在 Go 项目中使用 pprof 进行性能采样、分析瓶颈并进行优化。 一 为什么需要性能分析 在高并发或长时间运行的 Go 程序中,性能问题往往难以肉眼察觉。 常见问题包括: 1 CPU


用搬家公司的例子来入门webpack
颜酱2025/10/11

📚 官方文档:Webpack 官网 | Webpack 中文文档 🚀 实践项目:webpack-simple-demo - 本文档的完整示例项目 Webpack 主要是干什么的? Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(static module bundler)。简单来说,它就是把你的项目中的各种文件(TS、Less、JS、CSS、图片等)打包成一个或多个浏览器可以直接使用的文件。特别像搬家公司,把你的东西打包进各种盒子里。 英文中"bundle"意为


ASM1042芯片在汽车BCM项目的工程化应用探索
国科安芯2025/10/10

摘要 随着汽车产业的快速发展,车身控制模块(BCM)作为汽车电子系统的核心组件之一,对芯片的性能、可靠性和适应性提出了更高的要求。本文综合分析了国科安芯推出的ASM1042芯片的技术特性、可靠性测试结果以及实际应用案例,结合汽车BCM项目的需求背景,系统性地探讨了ASM1042芯片在汽车电子领域的工程化应用潜力。通过对芯片性能的深度剖析以及实际应用中的挑战与解决方案的详细阐述,本文旨在为汽车电子系统的设计与优化提供参考,同时推动高性能通信芯片在汽车领域的广泛应用。 一、引言 在现代汽车架构


dep.ts 逐行解读
excel2025/10/8

简化归纳 一、导入与上下文说明(开头几行) import { extend, isArray, isIntegerKey, isMap, isSymbol } from '@vue/shared' import type { ComputedRefImpl } from './computed' import { type TrackOpTypes, TriggerOpTypes } from './constants' import { type DebuggerEventExtraInf


[特殊字符]️ Spring Cloud Eureka 三步通:搭建注册中心 + 服务注册 + 服务发现,通俗易懂!
绝顶少年2025/10/7

📌 引言:什么是服务注册与发现? 在微服务架构中,我们通常会将一个大型系统拆分成多个小服务,比如: 用户服务(user-service) 订单服务(order-service) 支付服务(payment-service) 这些服务可能会​​相互调用​​,比如订单服务要查询用户信息,就需要调用用户服务。 但问题来了:​​订单服务怎么知道用户服务在哪里(IP + 端口)?​​ 👉 ​​这就是服务注册与发现要解决的问题!​​ 🤖 什么是 Eureka? ​​E


最新版 Python 的内置函数大全
IMPYLH2025/10/5

Python 的内建函数 Python 提供了大量开箱即用的内置函数,这些函数就像是你编程工具箱中的瑞士军刀——小巧但功能强大,随时准备帮你解决各种编程挑战。从简单的数据转换到复杂的迭代操作,内置函数都能让我们的代码更加简洁、优雅和高效。 无论你是刚刚踏入编程大门的新手,还是希望提升编码效率的资深开发者,掌握Python内置函数都将为你的编程之旅带来质的飞跃。 让我们放下繁琐的重复代码,拥抱Python内置函数带来的简洁与力量。 Python 3.13 共计 71 个内置函数,以下按字母


人工智能技术的广阔前景
南鸢1.02025/10/4

博主主页:【南鸢1.0】 本文专栏:创作灵感 | 人工智能 文章目录  简介 未来前景与可能性 应对策略与建议 1. 技能提升与再培训 2. 制定与遵循伦理准则 3. 强调人机协作 4. 重视数据安全与隐私 更好的AI工具 1.TensorFlow & PyTorch 2.Tableau 3.ChatGPT和其他对话AI模型 4.Notion AI 总结  简介 人工智能技术的应用前景确实非常广阔,且其在各个领域的潜力和效益也愈加显著。在这一背景下,以下


复盘:第一个 AI 出海站,我做错了哪些事?
孟健AI编程2025/10/3

大家好,我是孟健。 上周,我把自己的第一个出海站点部署上线。 算是把需求挖掘、开发、部署、获客到变现的闭环跑完了。 真的是:惊喜和意外齐飞,踩坑和成长并存。 这篇文章就把整个过程拆开复盘,希望能给正在筹备 AI 出海的你一些参考。 01 选词太随意,开局就挖了坑 当时我只打开 Google Trends,看着趋势线还不错就直接选词。 上线后数据一出才发现,词的全球热度在持续下滑,而且几乎只有巴西有流量。 美国、英国、加拿大等英语主流市场几乎没人搜,打榜和运营正反馈极低。 站点一上来就被迫做多


四种对象型创建模式:抽象工厂、 build模式、原型ProtoType与单例模式
CoderIsArt2025/10/2

1. 抽象工厂模式 (Abstract Factory) 组件框图 ┌─────────────────┐ ┌──────────────────┐ │ Client │ │ AbstractFactory │ ├─────────────────┤ ├──────────────────┤ │ │───────>│+createProductA() │ │

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0