【STM32】第四周学习问题汇总

问:C语言我懂,但是此段代码的含义不懂,在哪用

答:以 "发送 ACK(ack=0)" 为例,时序流程是:

发送方发完 8 位数据后,调用i2c_send_ack(0);

代码将 SDA 拉低(对应 ACK);

发送方控制 SCL 拉高 → 接收方在 SCL 高电平时,读取到 SDA 低电平,确认 "已接收";

发送方控制 SCL 拉低 → 应答周期结束,SDA 控制权交回发送方,准备下一次传输。

这段代码是讲主机接收ACK的流程

主机在发送完一个字节后,需将SDA线置为高电平(释放控制权),以便从机操作。

主机生成第9个时钟脉冲

主机拉高SCL线,进入第9个时钟周期的高电平阶段。

从机操作SDA线

若从机应答,会在SCL高电平期间将SDA拉低。

若从机不应答,SDA保持高电平。

主机读取SDA状态

主机在SCL高电平期间检测SDA线的电平:

低电平(0):表示收到ACK,数据传输继续。

高电平(1):表示收到NACK,需根据场景处理(如终止传输或重试)。

请复习I2C通信协议https://blog.csdn.net/2301_76153977/article/details/154956302?spm=1001.2014.3001.5501


问:for循环在这是什么意思

答:循环8次(对应8位),每次处理1位,顺序是最高位→最低位(符合I2C规则)。以下是每一步的细节:

(1)主机拉高SCL:生成时钟高电平

I2C_SCL_SET();

• I2C的时钟由主机完全控制,I2C_SCL_SET()将SCL线拉高,进入时钟脉冲的高电平阶段。

• 此时从机必须将当前要发送的位稳定在SDA线上(I2C要求:SCL高电平期间,数据不能变化)。

(2)读取SDA电平:获取从机发送的位

if(I2C_SDA_VALUE() == GPIO_PIN_SET) { ... }

• I2C_SDA_VALUE():读取SDA线的当前电平(从机发送的数据位)。

• GPIO_PIN_SET:表示SDA为高电平(从机发送的是1);若为GPIO_PIN_RESET,则是低电平(从机发送的是0)。

(3)将数据位写入data:拼接字节

data |= (0x80 >> i);

这是最关键的一行,需结合二进制位操作理解:

• 0x80:十六进制80对应二进制10000000,是8位中的最高位(第7位,从0开始计数)。

• 0x80 >> i:将最高位逐步右移,依次对应第7-i位(从最高位到最低位):

• i=0:0x80 >> 0 = 0x80→ 第7位(最高位);

• i=1:0x80 >> 1 = 0x40→ 第6位;

• i=2:0x80 >> 2 = 0x20→ 第5位;

• ...

• i=7:0x80 >> 7 = 0x01→ 第0位(最低位)。

• data |= (0x80 >> i):若SDA为高电平(从机发送1),则将当前位置为1;若为低电平(发送0),则该位保持0(因为data初始为0,不操作即保留0)。

(4)主机拉低SCL:完成一个时钟周期

举个例子,看data如何逐步拼接:

从机发送0x55(二进制01010101)

• i=0:SDA=0(第7位是0)→data保持0;

• i=1:SDA=1(第6位是1)→data |= 0x40→data=0x40(01000000);

• i=2:SDA=0(第5位是0)→data保持0x40;

• i=3:SDA=1(第4位是1)→data |= 0x10→data=0x50(01010000);

• i=4:SDA=0(第3位是0)→data保持0x50;

• i=5:SDA=1(第2位是1)→data |= 0x04→data=0x54(01010100);

• i=6:SDA=0(第1位是0)→data保持0x54;

• i=7:SDA=1(第0位是1)→data |= 0x01→data=0x55(01010101)。

最终data=0x55,与从机发送的数据完全一致!

问:为什么八位要依次接收,不能直接接收一个字节么?

答:要理解I2C必须"依次接收8位"的原因,需要从物理层限制、协议时序规则和实现逻辑三个核心角度分析:

一、最根本的原因:I2C是"串行通信"

串行通信(Serial Communication)的定义是:数据通过一根数据线逐位(Bit)传输(对比并行通信:通过多根数据线同时传输多位)。

I2C的物理层只有1根数据线(SDA),从机无法"一次性发送8位"------因为没有足够的硬件线路同时承载8位信号。因此,无论软件如何设计,物理层都强制要求数据必须"一位一位传",接收方也必须"一位一位收"。

二、I2C协议的"位级时序"要求

即使物理层允许并行传输,I2C的协议规则也强制要求"逐位处理",因为每一位的传输都需要时钟同步和电平采样:

  1. 时钟同步(SCL的作用)

I2C是同步通信,由主机生成时钟(SCL线),从机必须根据SCL的节奏发送数据:

• 从机在SCL低电平期间准备下一位数据(SDA电平可变化);

• 从机在SCL高电平期间保持SDA电平稳定(确保主机能准确采样);

• 主机在SCL高电平期间读取SDA的电平(即当前位的值)。

因此,每一位都需要一个完整的SCL周期(低→高→低),8位数据就需要8个SCL周期------这是协议的硬规则,无法跳过。

  1. 位级采样的必要性

主机必须逐位验证SDA的电平,因为:

• I2C的数据是最高位(MSB)优先,必须按顺序接收(第1位是bit7,第8位是bit0);

• 每一位的采样时机必须严格对应SCL的高电平(早了或晚了都会读错);

• 若跳过逐位处理,主机无法判断"当前是第几位",也无法正确拼接字节(比如MSB在前还是LSB在前)。

三、"直接接收一个字节"的误解:软件封装 vs 底层实现

我可能混淆了"软件层面的字节操作"和"底层的位操作":

• 对于硬件I2C控制器(如STM32的I2C外设),软件可以通过寄存器直接读取"一个字节"(比如I2C_RXDR寄存器),但这只是硬件帮你做了"逐位接收+拼接字节"的工作------底层依然是逐位传输的;

• 对于模拟I2C(用GPIO模拟SCL/SDA),没有硬件控制器帮忙,软件必须手动完成"逐位读取→拼接字节"的过程,因此代码中会显式循环8次。


问:什么是开漏输出和推挽输出?

我们用 "开关" 来类比芯片引脚的输出结构,更易理解:
1. 推挽输出(Push-Pull):内部有 "两个主动开关"
推挽输出的引脚内部,同时有上拉开关(接 VCC) 和下拉开关(接 GND),且两个开关互斥(不会同时闭合):

输出高电平:上拉开关闭合,下拉开关断开 → 引脚直接连 VCC(比如 3.3V);

输出低电平:下拉开关闭合,上拉开关断开 → 引脚直接连 GND(0V);

核心特点:主动输出高 / 低电平,引脚电平由内部开关 "强制决定",有强驱动能力。
2. 开漏输出(Open Drain):内部只有 "一个被动开关"
开漏输出的引脚内部,只有下拉开关(接 GND),没有上拉开关:

输出低电平:下拉开关闭合 → 引脚被强制拉到 GND(0V);

输出高电平:下拉开关断开 → 引脚 "悬空",必须靠外部 / 内部上拉电阻拉到 VCC(比如 3.3V);

核心特点:只能主动拉低,无法主动拉高,高电平是 "被动维持" 的。

推挽输出的内部结构

推挽输出并联 → 短路场景(致命!)

两个推挽设备 A 和 B,输出引脚直接并联:

电流从 VCC → 开关 A1 → 并联线 → 开关 B2 → GND,中间没有任何限流电阻(芯片内部 MOS 管电阻极小),形成超大电流回路,瞬间烧毁开关 A1、B2(即芯片内部 MOS 管)。

开漏输出的内部结构

开漏输出并联 → 安全场景(无短路!)

两个开漏设备 A 和 B,输出引脚并联,公共线接上拉电阻:


问:为什么编码器通常是开漏输出,需上拉?

答:编码器用开漏输出的核心原因 (硬件层面)

编码器的供电电压可能和 MCU 不一致(比如编码器 5V 供电,STM32 3.3V)------ 通过上拉电阻的电源选择(接 3.3V),可将编码器的 "高电平" 从 5V 适配到 3.3V,避免 MCU 引脚过压;

例:编码器 5V 供电,开漏输出低电平 = 0V,上拉到 STM32 的 3.3V 后,高电平 = 3.3V,完美匹配 MCU 的 3.3V 电平标准。

部分场景下,多个编码器的信号可能并联到同一 MCU 引脚(或总线)。若用推挽输出,两个设备同时输出高 / 低电平(比如 A 编码器输出高,B 输出低),会导致 VCC 和 GND 直接短路,烧毁芯片;若用开漏输出,即使多个设备并联,最多只有一个设备拉低电平,其他设备通过上拉电阻保持高电平,无短路风险。
为什么需要上拉电阻?

开漏输出 "无法主动输出高电平",如果没有上拉电阻:

编码器未拉低时,MCU 引脚处于 "悬空状态"(电平随机跳变),STM32 会误判为高频杂波,导致编码器计数乱跳;

加上拉电阻后:

编码器拉低 → 引脚 = 低电平(0V);

编码器释放 → 上拉电阻将引脚拉到 VCC(3.3V/5V),稳定输出高电平。


问:为什么 GPIO 配置为 "输入模式"(编码器脉冲是输入)?

答:编码器是 "信号源",MCU 是 "信号接收方"

编码器的本质是脉冲发生器:电机转动时,编码器的 A/B 相引脚主动输出高低电平脉冲;STM32 的 GPIO 需要 "接收" 这些脉冲信号,因此必须配置为输入模式------ 输入模式下,GPIO 引脚的内部电路只做 "电平检测",不向外输出任何电流 / 电平,避免干扰编码器的信号。

相关推荐
qq_401780821 小时前
2.信号 完整性(信号上升时间与带宽)
学习
车载测试工程师1 小时前
CAPL学习-ETH功能函数-方法类3
学习·tcp/ip·以太网·capl·canoe
im_AMBER1 小时前
Leetcode 69 正整数和负整数的最大计数
数据结构·笔记·学习·算法·leetcode
richxu202510011 小时前
嵌入式学习之路>单片机核心原理篇>(5)串口通信核心原理
单片机·嵌入式硬件·学习
sponge'1 小时前
opencv学习笔记12:GAN网络
笔记·opencv·学习
会飞的小蛮猪1 小时前
Rockylinux急速安装K8s学习环境
学习·容器·kubernetes
代码游侠1 小时前
数据结构--队列
数据结构·笔记·学习·算法·链表
车载测试工程师2 小时前
CAPL学习-ETH功能函数-方法类2
网络·网络协议·学习·c#·以太网·capl·canoe
xian_wwq2 小时前
【学习笔记】数据要素市场新基石:可信数据空间技术架构详解
笔记·学习