最近也是秋招收到 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;
}