【STM32实践篇】:I2C驱动编写

文章目录

  • I2C 物理层
  • I2C 协议层
    • 1. 数据有效性
    • 2. 起始和停止信号
    • 3. 应答响应
    • 4. 总线的寻址方式
    • 5. 数据传输
      • 5.1 主机向从机发送数据
      • 5.2 主机由从机中读数据
      • 5.3 I2C通信复合格式
  • I2C 驱动编写
    • 1. 配置 SCL 和 SDA
    • 2. I2C起始信号和停止信号
    • 3. 等待从设备应答
    • 4. 主机发送ACK和NACK信号
    • 5. 发送一个Byte
    • 6. 接收一个Byte
    • 7. 函数声明


在这里插入图片描述


I2C 物理层

在这里插入图片描述

两条信号线

  1. SCL (Serial Clock Line): 时钟信号线,连接主设备MCU 和所有从设备Device A, B, C;主设备控制SCL时钟的生成和频率。

  2. SDA (Serial Data Line): 数据信号线,与SCL平行,连接主设备和所有从设备;所有的数据(地址、命令、实际数据)都通过这条线在设备间双向传输。

共享总线架构

  1. 多个设备共享物理连接:所有通信通过这两条线进行,无论MCU与哪个设备通信,都使用SDA和SCL这两条线;设备通过唯一地址被寻址。

开漏输出 (Open-Drain)

每一个连接到SDA或SCL线的点,其输出驱动器都是一个开漏结构,这意味着:

  1. 器件只能主动拉低信号线(将信号导通到GND)。

  2. 器件无法主动驱动信号线为高电平,它只能释放总线(关闭MOS管),让信号线自然上浮。

上拉电阻 (Pull-up Resistors)

由于总线上所有设备都使用开漏输出,它们无法主动输出高电平,上拉电阻为总线提供高电平。

  1. 当所有设备都不主动拉低总线时,上拉电阻会将SDA和SCL线拉至高电平 ≈Vcc,这是总线的空闲状态(Idle State)。

  2. 当某个设备需要发送低电平时,它只需激活其开漏输出,将总线拉低到GND 0V,上拉电阻限制了此时流过该MOS管的电流。

I2C 协议层

1. 数据有效性

在这里插入图片描述

  1. SCL 高电平期间:要求数据稳定,接收端设备是在SCL时钟的上升沿或其前后很短时间窗内对SDA线上的数据进行采样读取的,此时数据必须稳定,才能确保接收方采样到正确无误的值。

  2. SCL 低电平期间:允许数据变化,此时SCL为低,所有设备都知道当前不是采样时刻,发送端可以切换电平来设置下一个比特(0或1)。

2. 起始和停止信号

在这里插入图片描述

  1. ​起始信号 (Start Condition - S):当 SCL 线处于稳定的 高电平 状态时,SDA 线发生一个从 高电平 到 低电平 的下降沿跳变。

  2. 终止信号 (Stop Condition - P):当 SCL 线处于稳定的 高电平 状态时,SDA 线发生一个从 低电平 到 高电平 的上升沿跳变。​

3. 应答响应

在这里插入图片描述
每当发送器件传输完一个字节的数据后,后面必须紧跟一个校验位,这个校验位是接收端通过控制SDA(数据线)来实现的,以提醒发送端数据我这边已经接收完成,数据传送可以继续进行。这个校验位其实就是数据或地址传输过程中的响应。

响应包括 应答(ACK) 和 非应答(NACK) 两种信号。

作为数据接收端时,当设备(无论主从机)接收到I2C传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送 应答(ACK) 信号即低电平脉冲,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送 非应答(NACK) 信号即高电平脉冲,发送方接收到该信号后会产生一个停止信号,结束信号传输。

每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。

4. 总线的寻址方式

在这里插入图片描述
D7~D1位组成从机的地址。D0位是数据传送方向位,为 0 时表示主机向从机写数据,为 1 时表示主机由从机读数据。

当主机发送了一个地址后,总线上的每个器件都将头7位与它自己的地址比较,如果一样,器件会判定它被主机寻址,其他地址不同的器件将被忽略后面的数据信号。至于是从机接收器还是从机发送器,都由R/W位决定。

5. 数据传输

I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/W),用“0”表示主机发送(写)数据(W),“1”表示主机接收数据(R)。每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。

5.1 主机向从机发送数据

在这里插入图片描述

IIC主机向从机写数据的过程为:主机先发起始信号S,接着发送含写标志位(0)的从机地址,若从机回应答A ,主机持续发送数据帧(每帧后从机应答A );最后一帧数据发送后,从机回A / A‾\overline{A}A ,主机再发终止信号P ,完成写操作。

5.2 主机由从机中读数据

在这里插入图片描述

IIC主机从从机读数据的过程为:主机先发起始信号S,接着发送含读标志位(1)的从机地址,从机回应答A后,从机开始发送数据帧;主机对中间数据帧回应答A ,最后一帧数据传输时主机回非应答A‾\boldsymbol{\overline{A}}A ,随后主机发送终止信号P ,完成读数据操作。

5.3 I2C通信复合格式

在这里插入图片描述
IIC 传送过程中需改变方向时,主机先按初始方向(如写,方向位为 0 )发起始信号 S 、发送从机地址 + 方向位,完成该方向数据传输;随后重发起始信号 S ,再次发送同一从机地址,且读 / 写方向位与初始方向反相(如改为读,方向位为 1 ),从而实现传送方向切换,继续后续数据交互。

I2C 驱动编写

我们使用的是软件模拟I2C,所以在配置管脚输出类型时,选择推挽输出即可,如果配置为开漏输出也是可以的,但是需要引脚外接上拉电阻。

1. 配置 SCL 和 SDA

使用PB8作为SCL,PB9作为SDA

iic.h

#ifndef _IIC_H
#define _IIC_H#include "stm32f4xx_hal.h"
#include <stdint.h>// =====================================================
// SCL时钟线配置
#define IIC_SCL_PORT        GPIOB       // SCL端口
#define IIC_SCL_PIN         GPIO_PIN_8  // SCL引脚// SDA数据线配置
#define IIC_SDA_PORT        GPIOB       // SDA端口
#define IIC_SDA_PIN         GPIO_PIN_9  // SDA引脚
// =====================================================// IO操作宏
#define IIC_SCL_HIGH()      HAL_GPIO_WritePin(IIC_SCL_PORT, IIC_SCL_PIN, GPIO_PIN_SET)      // SCL置高
#define IIC_SCL_LOW()       HAL_GPIO_WritePin(IIC_SCL_PORT, IIC_SCL_PIN, GPIO_PIN_RESET)    // SCL置低
#define IIC_SDA_HIGH()      HAL_GPIO_WritePin(IIC_SDA_PORT, IIC_SDA_PIN, GPIO_PIN_SET)      // SDA置高
#define IIC_SDA_LOW()       HAL_GPIO_WritePin(IIC_SDA_PORT, IIC_SDA_PIN, GPIO_PIN_RESET)    // SDA置低
#define READ_SDA()          HAL_GPIO_ReadPin(IIC_SDA_PORT, IIC_SDA_PIN)                     // 读取SDA状态// 时序参数宏:定义IIC通信时序延迟(单位:微秒)
#define IIC_DELAY()         delay_us(2)  // 标准时序延迟(适用于数据位变化)
#define IIC_START_DELAY()   delay_us(5)  // 起始/停止信号额外延迟(确保信号稳定)#endif

iic.c

#include "iic.h"
#include "SysTick.h"  // 确保包含延时函数头文件static void SDA_OUT(void);  // 配置SDA为输出模式
static void SDA_IN(void);   // 配置SDA为输入模式/*** @brief  IIC总线初始化* @note   配置SCL和SDA引脚为推挽输出模式,并释放总线*/
void IIC_Init(void) {GPIO_InitTypeDef GPIO_InitStruct = {0};// 使能GPIOB时钟__HAL_RCC_GPIOB_CLK_ENABLE();// 配置SCL和SDA引脚GPIO_InitStruct.Pin = IIC_SCL_PIN | IIC_SDA_PIN;  // 同时配置两个引脚GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;       // 推挽输出模式GPIO_InitStruct.Pull = GPIO_PULLUP;               // 内部上拉(确保总线空闲时为高电平)GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;     // 高速模式HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);// 初始状态:总线释放(SCL和SDA均为高电平)IIC_SDA_HIGH();IIC_SCL_HIGH();
}/*** @brief  配置SDA为输出模式* @note   在主机需要控制SDA线时调用(发送数据/地址时)*/
static void SDA_OUT(void) {GPIO_InitTypeDef GPIO_InitStruct = {.Pin = IIC_SDA_PIN,.Mode = GPIO_MODE_OUTPUT_PP,   // 推挽输出.Pull = GPIO_PULLUP,           // 上拉电阻使能.Speed = GPIO_SPEED_FREQ_HIGH  // 高速模式};HAL_GPIO_Init(IIC_SDA_PORT, &GPIO_InitStruct);
}/*** @brief  配置SDA为输入模式* @note   在主机需要读取SDA线时调用(接收数据/ACK时)*/
static void SDA_IN(void) {GPIO_InitTypeDef GPIO_InitStruct = {.Pin = IIC_SDA_PIN,.Mode = GPIO_MODE_INPUT,       // 输入模式.Pull = GPIO_PULLUP,           // 上拉电阻使能.Speed = GPIO_SPEED_FREQ_HIGH  // 高速模式};HAL_GPIO_Init(IIC_SDA_PORT, &GPIO_InitStruct);
}

2. I2C起始信号和停止信号

/*** @brief  产生IIC起始信号* @note   时序:SCL高电平时,SDA从高变低*/
void IIC_Start(void) {SDA_OUT();          // 确保SDA为输出模式IIC_SDA_HIGH();     // SDA置高IIC_SCL_HIGH();     // SCL置高IIC_START_DELAY();  // 保持起始条件建立时间IIC_SDA_LOW();      // START信号:SCL高时SDA从高变低IIC_DELAY();        // 保持SDA低电平时间IIC_SCL_LOW();      // 钳住总线,准备发送数据
}/*** @brief  产生IIC停止信号* @note   时序:SCL高电平时,SDA从低变高*/
void IIC_Stop(void) {SDA_OUT();          // 确保SDA为输出模式IIC_SCL_LOW();      // 确保SCL为低(数据稳定期)IIC_SDA_LOW();      // SDA置低(准备停止信号)IIC_DELAY();        // 延迟IIC_SCL_HIGH();     // SCL置高IIC_START_DELAY();  // 保持停止条件建立时间IIC_SDA_HIGH();     // STOP信号:SCL高时SDA从低变高IIC_DELAY();        // 确保总线释放
}

3. 等待从设备应答

/*** @brief  等待从设备应答* @retval 0: 收到ACK应答*         1: 未收到ACK(超时)*/
uint8_t IIC_Wait_Ack(void) {uint8_t wait_time = 0;SDA_IN();           // 配置SDA为输入模式IIC_SDA_HIGH();     // 释放SDA线(由上拉电阻拉高)IIC_DELAY();        // 短暂延时IIC_SCL_HIGH();     // 主机拉高SCL,从机应在此时拉低SDAIIC_DELAY();        // 等待从机响应// 检测SDA是否为低电平(ACK信号)while(READ_SDA() == GPIO_PIN_SET) {if(++wait_time > 250) {   // 超时检测(约500us)IIC_Stop();           // 终止通信return 1;             // 超时失败}delay_us(2);              // 微小延时避免忙等}IIC_SCL_LOW();      // SCL置低,结束ACK检测return 0;           // 成功收到ACK
}

4. 主机发送ACK和NACK信号

/*** @brief  产生ACK应答信号* @note   主机在接收数据后发送ACK(继续接收)*/
void IIC_Ack(void) {IIC_SCL_LOW();      // SCL置低(数据变化期)SDA_OUT();          // 控制SDA线IIC_SDA_LOW();      // SDA置低(ACK信号)IIC_DELAY();        // 数据建立时间IIC_SCL_HIGH();     // SCL置高(ACK有效)IIC_DELAY();        // 保持ACK时间IIC_SCL_LOW();      // SCL置低,结束ACK
}/*** @brief  产生NACK非应答信号* @note   主机在接收数据后发送NACK(结束接收)*/
void IIC_NAck(void) {IIC_SCL_LOW();      // SCL置低(数据变化期)SDA_OUT();          // 控制SDA线IIC_SDA_HIGH();     // SDA置高(NACK信号)IIC_DELAY();        // 数据建立时间IIC_SCL_HIGH();     // SCL置高(NACK有效)IIC_DELAY();        // 保持NACK时间IIC_SCL_LOW();      // SCL置低,结束NACK
}

5. 发送一个Byte

/*** @brief  IIC发送一个字节* @param  txd: 要发送的字节数据* @note   高位先发,每个时钟周期发送1位*/
void IIC_Send_Byte(uint8_t txd) {SDA_OUT();          // 确保SDA为输出模式IIC_SCL_LOW();      // SCL置低,开始数据传输// 循环发送8位数据(MSB first)for(uint8_t i = 0; i < 8; i++) {// 根据数据最高位设置SDA(txd & 0x80) ? IIC_SDA_HIGH() : IIC_SDA_LOW();txd <<= 1;      // 左移准备下一位IIC_DELAY();    // 数据建立时间IIC_SCL_HIGH(); // 产生上升沿,从机采样数据IIC_DELAY();    // 保持高电平时间IIC_SCL_LOW();  // 产生下降沿,准备下一位IIC_DELAY();    // 数据保持时间}
}

6. 接收一个Byte

/*** @brief  IIC读取一个字节* @param  ack: 是否发送应答(1=ACK, 0=NACK)* @retval 读取到的字节数据* @note   高位先收,每个时钟周期读取1位*/
uint8_t IIC_Read_Byte(uint8_t ack) {uint8_t receive = 0;SDA_IN();           // 配置SDA为输入模式// 循环读取8位数据(MSB first)for(uint8_t i = 0; i < 8; i++) {IIC_SCL_LOW();  // 确保SCL为低(数据变化期)IIC_DELAY();    // 从机准备数据时间IIC_SCL_HIGH(); // 产生上升沿,主机采样数据receive <<= 1;  // 左移接收寄存器if(READ_SDA()) receive |= 0x01; // 读取SDA状态并存储IIC_DELAY();    // 保持高电平时间}// 根据参数发送ACK/NACKack ? IIC_Ack() : IIC_NAck();return receive;
}

7. 函数声明

// IIC操作函数声明
// =====================================================
void IIC_Init(void);                // IIC总线初始化
void IIC_Start(void);               // 发送IIC起始信号
void IIC_Stop(void);                // 发送IIC停止信号
void IIC_Send_Byte(uint8_t txd);    // IIC发送一个字节
uint8_t IIC_Read_Byte(uint8_t ack); // IIC读取一个字节(ack=1发送ACK,ack=0发送NACK)
uint8_t IIC_Wait_Ack(void);         // 等待从设备应答(0=成功,1=失败)
void IIC_Ack(void);                 // 发送ACK应答信号
void IIC_NAck(void);                // 发送NACK非应答信号
// =====================================================

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/87872.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/87872.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ragflow本地部署教程linux Ubuntu系统

以下是一份在 Ubuntu 系统上本地部署 RAGFlow 的详细教程。 一、基础环境准备 1.硬件要求 –CPU ≥ 4核 –RAM ≥ 16 GB –磁盘空间 ≥ 50 GB&#xff08;建议 SSD&#xff09; 2.系统配置 更新系统 sudo apt update && sudo apt upgrade -y 设置内核参数&#xff…

[netty5: WebSocketClientHandshaker WebSocketClientHandshakerFactory]-源码分析

在阅读这篇文章前&#xff0c;推荐先阅读以下内容&#xff1a; [netty5: WebSocketFrame]-源码分析[netty5: WebSocketFrameEncoder & WebSocketFrameDecoder]-源码解析 WebSocketClientHandshakerFactory WebSocketClientHandshakerFactory 是用于根据 URI 和协议版本创…

4.2 如何训练⼀个 LLM

⼀般⽽⾔&#xff0c;训练⼀个完整的 LLM 需要经过图1中的三个阶段——Pretrain、SFT 和 RLHF。 4.2.1 Pretrain 预训练任务与架构 任务类型&#xff1a;采用因果语言模型&#xff08;CLM&#xff09;&#xff0c;通过预测下一个 token 进行训练&#xff0c;与传统预训练模型…

Qt中的QObject::moveToThread方法详解

一、QObject::moveToThread方法QObject::moveToThread()是Qt框架中一个非常重要的功能&#xff0c;它允许改变QObject及其子对象的线程关联性。这个功能在多线程编程中特别有用&#xff0c;可以将耗时操作移到工作线程执行&#xff0c;避免阻塞主线程/GUI线程。基本用法void QO…

【9】用户接入与认证配置

本文旨在帮助网络管理员在 SD-WAN 环境中实现安全、稳定的用户接入与认证策略,涵盖本地/远程认证、权限管理、密码策略、SSH、会话控制等关键配置要素。 1.密码策略与账户安全 从 IOS XE SD-WAN 17.3.1 起,Cisco 引入密码强化功能,用于统一用户密码的复杂度与有效性要求。密…

第十六节:第三部分:多线程:线程安全问题、取钱问题的模拟

线程安全问题介绍&#xff1a;取钱的线程安全问题 取钱的线程安全问题 取钱案例需求分析 线程安全问题出现的原因 代码&#xff1a;模拟线程安全问题&#xff08;上述取钱案例&#xff09; Account类&#xff08;账户类&#xff09; package com.itheima.day3_thread_safe;pu…

APE:大语言模型具有人类水平的提示工程能力

摘要 通过以自然语言指令作为条件输入&#xff0c;大型语言模型&#xff08;LLMs&#xff09;展现出令人印象深刻的通用计算能力。然而&#xff0c;任务表现严重依赖于用于引导模型的提示&#xff08;prompt&#xff09;质量&#xff0c;而最有效的提示通常是由人类手工设计的…

X86 CPU 工作模式

1.概述 1.实模式 实模式又称实地址模式&#xff0c;实&#xff0c;即真实&#xff0c;这个真实分为两个方面&#xff0c;一个方面是运行真实的指令&#xff0c;对指令的动作不作区分&#xff0c;直接执行指令的真实功能&#xff0c;另一方面是发往内存的地址是真实的&#xff…

Java设计模式之行为型模式(策略模式)介绍与说明

一、策略模式简介 策略模式&#xff08;Strategy Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使它们可以相互替换&#xff0c;且算法的变化不会影响使用算法的客户。策略模式让算法独立于使用它的客…

【BIOS+MBR 微内核手写实现】

本文基于BIOS+MBR的架构,从四部分讲解微内核是如何实现的: 1)搭建微内核编译调试环境 2)梳理微内核的代码结构:伪指令讲解 3)手写实现微内核框架,输出简单的字符串 4)讲解微内核启动阶段的具体运行过程 先完成内核工程创建,如下图 我们这里使用nasm风格的汇编编写,…

从C/C++迁移到Go:内存管理思维转变

一、引言 在当今高速发展的软件开发世界中&#xff0c;语言迁移已成为技术进化的常态。作为一名曾经的C/C开发者&#xff0c;我经历了向Go语言转变的全过程&#xff0c;其中最大的认知挑战来自内存管理模式的根本性差异。 我记得第一次接触Go项目时的困惑&#xff1a;没有析构函…

正确设置 FreeRTOS 与 STM32 的中断优先级

在裸机开发&#xff08;非 RTOS&#xff09;时&#xff0c;大多数 STM32 外设的中断优先级通常不需要手动配置&#xff0c;原因如下&#xff1a; ✅ 裸机开发中默认中断优先级行为 特点说明默认中断优先级为 0如果你不设置&#xff0c;STM32 HAL 默认设置所有外设中断为 0&…

EasyExcel之SheetWriteHandler:解锁Excel写入的高阶玩法

引言在 EasyExcel 强大的功能体系中&#xff0c;SheetWriteHandler 接口是一个关键的组成部分。它允许开发者在写入 Excel 的 Sheet 时进行自定义处理&#xff0c;为实现各种复杂的业务需求提供了强大的支持。通过深入了解和运用 SheetWriteHandler 接口&#xff0c;我们能够更…

Python单例模式魔法方法or属性

1.单例模式概念定义:单例模式(Singleton Pattern)是一种创建型设计模式&#xff0c;它确保一个类只能有一个实例&#xff0c;并提供一个全局访问点来获取该实例。这种模式在需要控制资源访问、配置管理或协调系统操作时特别有用。核心特点:私有构造函数&#xff1a;防止外部通过…

【Kubernetes系列】Kubernetes 资源请求(Requests)

博客目录 引言一、资源请求的基本概念1.1 什么是资源请求1.2 请求与限制的区别 二、CPU 请求的深入解析2.1 CPU 请求的单位与含义2.2 CPU 请求的调度影响2.3 CPU 请求与限制的关系 三、内存请求的深入解析3.1 内存请求的单位与含义3.2 内存请求的调度影响3.3 内存请求的特殊性 …

大型语言模型中的自动化思维链提示

摘要 大型语言模型&#xff08;LLMs&#xff09;能够通过生成中间推理步骤来执行复杂的推理任务。为提示演示提供这些步骤的过程被称为思维链&#xff08;CoT&#xff09;提示。CoT提示有两种主要范式。一种使用简单的提示语&#xff0c;如“让我们一步一步思考”&#xff0c;…

Private Set Generation with Discriminative Information(2211.04446v1)

1. 遇到什么问题&#xff0c;解决了什么遇到的问题现有差分隐私生成模型受限于高维数据分布建模的复杂性&#xff0c;合成样本实用性不足。深度生成模型训练依赖大量数据&#xff0c;加入隐私约束后更难优化&#xff0c;且不保证下游任务&#xff08;如分类&#xff09;的最优解…

C++编程语言入门指南

一、C语言概述 C是由丹麦计算机科学家Bjarne Stroustrup于1979年在贝尔实验室开发的一种静态类型、编译式、通用型编程语言。最初被称为"C with Classes"(带类的C)&#xff0c;1983年更名为C。它既具有高级语言的抽象特性&#xff0c;又保留了底层硬件操作能力&…

ZED相机与Foxglove集成:加速机器人视觉调试效率的实用方案

随着机器人技术的发展&#xff0c;实时视觉数据流的高效传输和可视化成为提升系统性能的重要因素。通过ZED相机&#xff08;包括ZED 2i和ZED X&#xff09;与Foxglove Studio平台的结合&#xff0c;开发者能够轻松访问高质量的2D图像、深度图和点云数据&#xff0c;从而显著提高…

目标检测新纪元:DETR到Mamba实战解析

&#x1f680;【实战分享】目标检测的“后 DEⱯ”时代&#xff1a;DETR/DINO/RT-DETR及新型骨干网络探索&#xff08;含示例代码&#xff09; 目标检测从 YOLO、Faster R-CNN 到 Transformer 结构的 DETR&#xff0c;再到 DINO、RT-DETR&#xff0c;近两年出现了许多新趋势&am…