前言

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

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

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

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

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

1-1. 面向对象的核心思想

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

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

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

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

1-2. 在 C 语言中模拟面向对象的方法

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

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

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

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

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

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

2.1. I2C 总线特性

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

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

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

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

2.2. I2C 通讯基本过程

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

3. 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#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;
}