STM32软件I2C实战:从零手撸驱动代码到读取MPU6050数据

STM32软件I2C实战:从零手撸驱动代码到读取MPU6050数据

在嵌入式开发的世界里,硬件I2C控制器有时会让人又爱又恨。爱的是它集成度高,配置好就能用;恨的是,当它因为引脚冲突、时钟配置或者库函数兼容性问题而罢工时,那种无处下手的挫败感。尤其是在资源紧张或者引脚复用复杂的项目中,硬件I2C的“娇气”可能会成为项目进度的绊脚石。这时候,软件模拟I2C就成了一个极具吸引力的备选方案。它不依赖特定的硬件外设,仅用两个普通的GPIO口,就能实现完整的I2C通信。这不仅仅是“能用”,更是一种对总线时序的彻底掌控。

今天,我们就来一次深度实战,目标非常明确:在STM32平台上,完全从零开始,用代码“手搓”出一个稳定可靠的软件I2C驱动,并最终驱动一个经典的惯性测量单元——MPU6050,读取其加速度和角速度数据。这个过程,你会清晰地看到每一个起始信号、每一个数据位、每一个应答是如何通过GPIO的电平变化“编织”出来的。无论你是想彻底理解I2C的时序本质,还是急需一个不挑硬件、可移植性强的通信方案,这篇内容都将提供一条清晰的路径。

1. 软件I2C的核心:时序的精确模拟

软件I2C的本质,就是用程序控制两个GPIO引脚(一个模拟SCL时钟线,一个模拟SDA数据线),严格按照I2C协议规定的时间序,产生高低电平的变化。听起来简单,但魔鬼藏在细节里。任何一个时序的偏差,都可能导致通信失败。

1.1 I2C总线状态的微观解读

很多人看过I2C的时序图,但真正动手写代码时,会对“何时读数据”、“何时写数据”感到模糊。我们先把几个核心状态掰开揉碎。

  • 起始条件 (Start Condition):当SCL线为高电平时,SDA线发生一个从高到低的跳变。这个信号由主机(我们的STM32)发起,告诉总线上所有从机:“注意,我要开始传输了”。
  • 停止条件 (Stop Condition):当SCL线为高电平时,SDA线发生一个从低到高的跳变。这表示一次传输的结束,释放总线。
  • 数据有效性:在SCL线为高电平期间,SDA线上的数据必须保持稳定。只有这时,数据才被认为是有效的。SDA线上的数据只能在SCL线为低电平期间才能改变。
  • 应答 (ACK) 与非应答 (NACK):每个字节(8位数据)传输后,接收方需要在第9个时钟脉冲期间拉低SDA线作为应答。如果SDA线保持高电平,则为非应答。

这里有一个关键理解点,我称之为 “稳变法则”

对于写操作:主机先改变SDA数据(在SCL低电平时),然后拉高SCL并保持稳定,从机在SCL高电平稳定期间采样数据,最后主机拉低SCL为下一个数据位做准备。 对于读操作:主机先拉高SCL,从机在SCL变高后改变SDA数据,主机在SCL高电平稳定期间读取SDA状态,最后主机拉低SCL

这个“先稳后变”或“先变后稳”的顺序,是软件I2C代码逻辑的基石。

1.2 GPIO配置与基础函数构建

我们选择STM32的两个通用输出引脚,例如PB6和PB7。配置为推挽输出模式即可,因为我们将完全通过代码控制其输出状态。开漏模式加外部上拉是标准I2C硬件接法,但在软件模拟中,推挽输出更能直接控制高低电平,简化初始调试。

首先,创建一组最底层的原子操作函数:

// soft_i2c.h
#ifndef __SOFT_I2C_H
#define __SOFT_I2C_H

#include "stm32f1xx_hal.h" // 根据你的MCU系列调整

// 引脚定义,方便移植
#define I2C_SCL_PIN    GPIO_PIN_6
#define I2C_SCL_PORT   GPIOB
#define I2C_SDA_PIN    GPIO_PIN_7
#define I2C_SDA_PORT   GPIOB

// 基础电平操作
void I2C_SCL_Set(void);
void I2C_SCL_Clr(void);
void I2C_SDA_Set(void);
void I2C_SDA_Clr(void);
uint8_t I2C_SDA_Read(void);

// 延迟函数声明(需要根据系统时钟实现)
void I2C_Delay_us(uint16_t us);

// 高层功能函数
void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(uint8_t byte);
uint8_t I2C_ReadByte(void);
void I2C_SendAck(uint8_t ack);
uint8_t I2C_WaitAck(void);

#endif
// soft_i2c.c
#include "soft_i2c.h"

// 简单的微秒级延迟,基于SysTick或循环实现。此处为示例,需根据实际主频校准。
void I2C_Delay_us(uint16_t us) {
    uint32_t ticks = us * (SystemCoreClock / 1000000) / 5; // 粗略计算,需要
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值