当前位置: 首页 > news >正文

告别时序图恐惧症:手把手教你用C语言实现IIC通信(附完整代码)

告别时序图恐惧症:手把手教你用C语言实现IIC通信(附完整代码)

第一次看到IIC时序图时,那些高低电平的波形就像天书一样让人头疼。作为嵌入式开发者,我们常常需要把抽象的时序图转化为实实在在的代码。本文将带你一步步拆解IIC通信的每个环节,用最直观的方式教你如何"看图写代码"。

1. IIC通信基础回顾

IIC(Inter-Integrated Circuit)是一种简单、双向二线制的同步串行总线,只需要两根线(SDA和SCL)就能实现设备间的通信。在开始编码前,我们需要明确几个关键概念:

  • 起始条件(START):SCL为高电平时,SDA从高电平跳变到低电平
  • 停止条件(STOP):SCL为高电平时,SDA从低电平跳变到高电平
  • 数据有效性:在SCL高电平期间,SDA必须保持稳定
  • 应答信号(ACK):每传输完一个字节后,接收方需要发送一个低电平应答
// 示例:GPIO初始化代码 void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; // 配置SCL和SDA引脚为开漏输出 GPIO_InitStruct.Pin = IIC_SCL_PIN | IIC_SDA_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(IIC_PORT, &GPIO_InitStruct); // 初始状态:SCL和SDA都置高 HAL_GPIO_WritePin(IIC_PORT, IIC_SCL_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(IIC_PORT, IIC_SDA_PIN, GPIO_PIN_SET); }

2. 从时序图到代码:基础信号实现

2.1 起始信号生成

起始信号是IIC通信的第一个动作,它的时序要求非常严格:

  1. SDA线先置高
  2. SCL线置高
  3. 等待至少4.7μs(标准模式)
  4. SDA线拉低
  5. 等待至少4μs
  6. SCL线拉低
void IIC_Start(void) { // SDA和SCL初始状态都为高 SDA_HIGH(); SCL_HIGH(); delay_us(5); // 满足tSU;STA时间要求 SDA_LOW(); // 产生起始条件 delay_us(4); SCL_LOW(); // 准备数据传输 delay_us(1); }

注意:实际延时时间需要根据具体MCU时钟频率调整,这里只是示例值

2.2 停止信号生成

停止信号标志着一次通信的结束,其实现与起始信号类似但顺序相反:

void IIC_Stop(void) { SDA_LOW(); // 确保SDA为低 SCL_LOW(); // 确保SCL为低 delay_us(1); SCL_HIGH(); // 先拉高SCL delay_us(4); SDA_HIGH(); // 再拉高SDA,产生停止条件 delay_us(5); // 满足tBUF时间要求 }

3. 数据传输与应答处理

3.1 字节发送流程

发送一个字节需要严格按照时序图操作,每个bit的传输都包含以下步骤:

  1. SCL拉低
  2. 设置SDA为当前bit值
  3. 延时满足tLOW时间
  4. SCL拉高
  5. 延时满足tHIGH时间
  6. SCL再次拉低
void IIC_SendByte(uint8_t byte) { for(int i=0; i<8; i++) { SCL_LOW(); delay_us(1); // 从最高位开始发送 if(byte & 0x80) { SDA_HIGH(); } else { SDA_LOW(); } byte <<= 1; delay_us(3); SCL_HIGH(); delay_us(5); SCL_LOW(); delay_us(1); } // 释放SDA线,准备接收ACK SDA_HIGH(); }

3.2 应答检测实现

每个字节传输后都需要检测从设备的应答信号:

uint8_t IIC_Wait_Ack(void) { uint8_t ack = 0; SCL_LOW(); delay_us(1); // 释放SDA线,切换为输入模式 SDA_INPUT(); delay_us(1); SCL_HIGH(); delay_us(2); // 读取SDA状态 if(SDA_READ() == GPIO_PIN_RESET) { ack = 1; // 收到ACK } else { ack = 0; // 未收到ACK } SCL_LOW(); SDA_OUTPUT(); // 切换回输出模式 delay_us(1); return ack; }

4. 完整通信流程实现

4.1 写操作流程

一个完整的IIC写操作通常包含以下步骤:

  1. 发送起始信号
  2. 发送设备地址(7位地址 + 写标志位0)
  3. 等待应答
  4. 发送寄存器地址
  5. 等待应答
  6. 发送数据
  7. 等待应答
  8. 发送停止信号
void IIC_Write(uint8_t devAddr, uint8_t regAddr, uint8_t data) { IIC_Start(); // 发送设备地址(7位) + 写标志(0) IIC_SendByte((devAddr << 1) & 0xFE); if(!IIC_Wait_Ack()) { IIC_Stop(); return; // 无应答,退出 } // 发送寄存器地址 IIC_SendByte(regAddr); if(!IIC_Wait_Ack()) { IIC_Stop(); return; } // 发送数据 IIC_SendByte(data); if(!IIC_Wait_Ack()) { IIC_Stop(); return; } IIC_Stop(); delay_ms(10); // 等待写入完成 }

4.2 读操作流程

读操作比写操作稍复杂,通常需要先发送寄存器地址,然后再发起读请求:

uint8_t IIC_Read(uint8_t devAddr, uint8_t regAddr) { uint8_t data = 0; // 第一阶段:发送寄存器地址 IIC_Start(); IIC_SendByte((devAddr << 1) & 0xFE); // 写模式 IIC_Wait_Ack(); IIC_SendByte(regAddr); IIC_Wait_Ack(); // 第二阶段:重新启动并读取数据 IIC_Start(); IIC_SendByte((devAddr << 1) | 0x01); // 读模式 IIC_Wait_Ack(); data = IIC_ReadByte(); IIC_Send_NAck(); // 发送非应答信号 IIC_Stop(); return data; }

5. 实际应用中的优化技巧

5.1 延时优化

不同IIC设备对时序要求不同,可以通过宏定义灵活调整延时参数:

// 根据不同模式定义延时 #ifdef IIC_STANDARD_MODE #define IIC_DELAY_US 5 #elif defined IIC_FAST_MODE #define IIC_DELAY_US 1 #else #define IIC_DELAY_US 3 // 默认值 #endif

5.2 错误处理机制

完善的错误处理能提高代码健壮性:

#define IIC_TIMEOUT 1000 // 超时时间(ms) IIC_Status IIC_WriteWithRetry(uint8_t devAddr, uint8_t regAddr, uint8_t data) { uint32_t startTime = HAL_GetTick(); IIC_Status status = IIC_ERROR; while((HAL_GetTick() - startTime) < IIC_TIMEOUT) { IIC_Write(devAddr, regAddr, data); // 验证写入是否成功 uint8_t readBack = IIC_Read(devAddr, regAddr); if(readBack == data) { status = IIC_OK; break; } delay_ms(10); } return status; }

5.3 多设备管理

当总线上有多个IIC设备时,可以通过地址扫描检测可用设备:

void IIC_ScanDevices(void) { printf("Scanning IIC devices...\n"); for(uint8_t addr = 0x08; addr < 0x78; addr++) { IIC_Start(); IIC_SendByte((addr << 1) & 0xFE); // 尝试写模式 if(IIC_Wait_Ack()) { printf("Device found at 0x%02X\n", addr); } IIC_Stop(); delay_ms(1); } }
http://www.zskr.cn/news/1410817.html

相关文章:

  • 跟着 MDN 学CSS day_22:(从混乱到精美HTML表格样式化完全指南)
  • 从原理到落地,Python 实现客户细分与销量预测
  • 别只当它是个编辑器:挖掘Dreamweaver CS6里那些被遗忘的‘高级’功能(AP Div与行为篇)
  • 构建本地语音AI助手:从意图识别到工具调用的完整实现
  • 告别Win11内存焦虑:深入dwm.exe与Intel核显驱动的‘爱恨纠葛’及一劳永逸的修复法
  • 别再让内核崩溃成谜:手把手教你用kdump在CentOS 8/RHEL 8上抓取完整vmcore
  • 超越first-fit:从ucore Lab 2出发,聊聊伙伴系统(Buddy System)与SLUB分配器的设计与实现思路
  • 构建稳健预测引擎:时序特征工程防泄露核心方法论
  • 用PyTorch和VGG16预训练权重,从零搭建Unet语义分割模型(附完整代码)
  • 别再只调颜色了!Echarts地图的visualMap组件,这5个隐藏功能让你的数据可视化更专业
  • Cadence CIS库添加元件不显示?手把手教你排查SPB17.4配置的5个关键点
  • PyTorch 深度学习框架核心能力与实战评测
  • AI如何重塑2026年Web开发:从意图驱动到智能工具链
  • 2026年SaaS构建成本全解析:AI辅助、外包与无代码路径深度对比
  • Ubuntu 18.04无线网卡驱动安装避坑指南:从lspci查型号到github找r8168驱动
  • 致CSDN的最后一封“情书”:与大家告别,在新阵地重拾技术写作的纯粹
  • 2026生产级AI智能体工程化实战:可观测性、评估体系与部署循环构建指南
  • 别再乱试了!Modelsim SE 2019.2 License问题,核心是MentorKG与网卡MAC地址的匹配
  • 从数据集到芯片:决策树模型自动化ASIC设计全流程解析
  • 解决EPSON RC+ 7.0编程编译报错:从‘Integer i’到‘Jump daiji’的实战排错指南
  • 从自定义Agent到技能封装:AI工程化的高效实践路径
  • 避坑指南:VMware Horizon Agent安装与桌面池授权那些容易踩的‘坑’
  • ChatGPT播客内容策划全流程拆解(含真实ROI数据看板):头部知识IP验证——用AI降本67%,完播率提升2.8倍
  • AI智能体社交推理实战:基于对抗性对话的秘密提取挑战平台
  • 从‘边际效应图’到‘Bootstrap置信区间’:一篇讲透GLMM(广义线性混合模型)的结果呈现与稳健性检验
  • SAP FICO顾问进阶:用COPA深度拆解生产成本9大差异与销售成本(含分割结构实战)
  • 2026年深孔钻探厂家推荐榜单:矿产勘查/水利隧道/地热温泉/地质灾害钻探工程实力品牌解析 - 品牌企业推荐师(官方)
  • 直流微电网并联变换器环流抑制:自适应下垂控制原理与工程实践
  • ArcGIS水文分析实战:除了画河流流域,你还能用这些中间结果做什么?
  • 别再傻傻分不清!CAN总线标准帧与扩展帧的实战选择指南(附报文ID优先级详解)