基于STM32F103软件模拟I2C的纯粹C语言面向对象尝试

最近也是秋招收到offer了,嵌软方向,本地企业,于是闲的没事干在学校开摆,顺便提升下代码水平。

于是首当其冲的就是指针和面向对象思维,emmmm怎么说呢,这方面蛮薄弱的,于是刚学了一点结构体指针的皮毛,结合之前比赛因为用了许多I2C设备而多次反复的初始化GPIO口的麻烦,尝试了下把I2C面向对象化,顺便复习下I2C。

面向对象核心思想和C语言的实现

众所周知,向对象编程(Object-Oriented Programming,OOP)是一种通过抽象和封装来提高代码复用性和可维护性的编程范式,但是C语言他原生是面向过程语言,并未向Python和Java那样原生支持面向对象的特性。

C语言的精髓和核心是指针,通过指针直接操作内存,可以实现很多骚操作;加上C语言的struct结构体,以及typedef自定义类型,从而实现模拟类和对象;又因结构体支持结构体的嵌套,因此可以实现基于嵌套意义的类和方法继承;进而实现面向对象的思想和全部功能。

面向对象的核心思想

面向对象编程的核心包括以下几方面:

  • 封装:将数据和操作封装到单一的模块中,对外仅暴露接口,隐藏实现细节。

  • 继承:通过复用已有模块的属性和方法来实现扩展,避免重复代码。

  • 多态:基于对象类型的不同,动态调用合适的方法。

在C语言中模拟面向对象的方法

虽然 C 语言本身不支持类和对象,但可以通过结构体和函数指针模拟这些特性:

但由于C是为面向过程设计的,本质上仍然是面向过程的语言,因此类方法必须严格按照面向过程的顺序。

  • 结构体实现封装
    结构体(struct)是 C 语言中组织数据的核心工具。可以在结构体中包含数据和函数指针,从而将操作封装为模块化的对象。

  • 函数指针实现多态
    通过在结构体中嵌入函数指针,可以动态绑定不同对象的行为。例如,不同设备可以通过函数指针调用各自的初始化和操作方法。

  • 嵌套结构体模拟继承
    使用嵌套的方式,使一个结构体拥有另一个结构体的成员,达到继承属性和方法的效果。

I2C——Inter-Integrated Circuit集成电路总线

I2C总线特性

I2C是一种广泛应用于嵌入式系统的串行通信协议,采用主从结构,通过两线完成数据传输。

  • 两线结构:SCL时钟、SDA数据。

  • 通讯可靠:通过应答信号(ACK/NACK)确认每次数据传输的成功

  • 开漏模式:需要手动 / 外部上拉,实现多设备同步半双攻。

I2C通讯基本过程

  1. 启动条件(Start):主机拉低 SDA 并拉高 SCL。
  2. 地址传输:主机发送从设备地址,等待应答。
  3. 数据读写:按字节传输数据,每字节后需要应答信号。
  4. 停止条件(Stop):主机拉低 SCL 后拉高 SDA。

代码实现

#ifndef __I2C_H__
#define __I2C_H__

#include "stm32f10x.h"

typedef struct SoftI2C_t{
GPIO_TypeDef *SCL_Port; /* SCL引脚的GPIO端口 */
uint16_t SCL_Pin; /* SCL引脚的GPIO引脚编号 */
GPIO_TypeDef *SDA_Port; /* SDA引脚的GPIO端口 */
uint16_t SDA_Pin; /* SDA引脚的GPIO引脚编号 */
uint32_t Delay; /* 延时参数,用于控制时序 */

/* 各种I2C操作函数的指针 */
void(*Start)(struct SoftI2C_t *self);
void(*Stop)(struct SoftI2C_t *self);
uint8_t(*WriteByte)(struct SoftI2C_t *self, uint8_t byte);
uint8_t(*ReadByte)(struct SoftI2C_t *self, uint8_t ack);

} SoftI2C_t;

void SoftI2C_Init(SoftI2C_t *self); /* 初始化I2C */

#endif

#include "I2C.h"

/* 定义GPIO引脚操作 */
#define SCL_HIGH(self) GPIO_SetBits((self)->SCL_Port, (self)->SCL_Pin)
#define SCL_LOW(self) GPIO_ResetBits((self)->SCL_Port, (self)->SCL_Pin)
#define SDA_HIGH(self) GPIO_SetBits((self)->SDA_Port, (self)->SDA_Pin)
#define SDA_LOW(self) GPIO_ResetBits((self)->SDA_Port, (self)->SDA_Pin)
#define SDA_READ(self) GPIO_ReadInputDataBit((self)->SDA_Port, (self)->SDA_Pin)

/* SysTick内核延迟 */
static void _SoftI2C_Delay(SoftI2C_t *self)
{
if (!self->Delay){

return ;
}
SysTick->LOAD = 72 * (self->Delay); /* 设置定时器重装值 */
SysTick->VAL = 0x00; /* 清空当前计数值 */
SysTick->CTRL = 0x00000005; /* 设置时钟源为HCLK,启动定时器 */
while(!(SysTick->CTRL & 0x00010000)); /* 等待计数到0 */
SysTick->CTRL = 0x00000004; /* 关闭定时器 */
}

static void _SoftI2C_GPIO_Init(SoftI2C_t *self)
{

if (self->SCL_Port == GPIOB || self->SDA_Port == GPIOB) {

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
}
if (self->SCL_Port == GPIOA || self->SDA_Port == GPIOA) {

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
}


GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = self->SCL_Pin;
GPIO_Init(self->SCL_Port, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = self->SDA_Pin;
GPIO_Init(self->SDA_Port, &GPIO_InitStructure);


SDA_HIGH(self);
SCL_HIGH(self);
}
static void SoftI2C_Start(SoftI2C_t *self)
{
SDA_HIGH(self);
SCL_HIGH(self);
_SoftI2C_Delay(self);
SDA_LOW(self);
_SoftI2C_Delay(self);
SCL_LOW(self);
}

static void SoftI2C_Stop(SoftI2C_t *self)
{
SCL_LOW(self);
SDA_LOW(self);
_SoftI2C_Delay(self);
SCL_HIGH(self);
SDA_HIGH(self);
_SoftI2C_Delay(self);
}

static uint8_t SoftI2C_WriteByte(SoftI2C_t *self, uint8_t byte)
{
uint8_t ack = 0;
for (uint8_t i = 0; i < 8; i++) {
if (byte & 0x80) {

SDA_HIGH(self);
} else {
SDA_LOW(self);
}

byte <<= 1;
SCL_HIGH(self);
_SoftI2C_Delay(self);
SCL_LOW(self);
_SoftI2C_Delay(self);
}

// 等待ACK响应
SDA_HIGH(self);
SCL_HIGH(self);
_SoftI2C_Delay(self);
ack = !SDA_READ(self);
_SoftI2C_Delay(self);
SCL_LOW(self);

return ack;
}


static uint8_t SoftI2C_ReadByte(SoftI2C_t *self, uint8_t ack)
{
uint8_t byte = 0;
SDA_HIGH(self);

for (uint8_t i = 0; i < 8; i++){
byte <<= 1;
SCL_HIGH(self);
_SoftI2C_Delay(self);

if (SDA_READ(self)){

byte |= 0x01;
}
SCL_LOW(self);
_SoftI2C_Delay(self);
}

if (ack){

SDA_LOW(self);
}else{
SDA_HIGH(self);
}

SCL_HIGH(self);
_SoftI2C_Delay(self);
SCL_LOW(self);
SDA_HIGH(self);

return byte;
}

/* 定义构造函数 */
void SoftI2C_Init(SoftI2C_t *self)
{
_SoftI2C_GPIO_Init(self);

self->Start = SoftI2C_Start;
self->Stop = SoftI2C_Stop;
self->WriteByte = SoftI2C_WriteByte;
self->ReadByte = SoftI2C_ReadByte;
}