RTOS嵌入式系统框架进阶
第一章 嵌入式常用裸机编程框架
第二章 面向对象编程基础
文章目录
前言
学习韦东山老师的七天物联网实战及直播课相关内容,以其课程笔记为骨,记录一下学习的过程,可能会加入一些自己的感想。
最后欢迎点赞、收藏与评论交流!
面向对象的总体思想来源《代码大全》的第5章,他把程序设计分为这以下几个层次:
- 第1层:软件系统,就是整个系统、整个程序
- 第2层:分解为子系统或包。比如我们可以拆分为:输入子系统、显示子系统、业务系统
- 第3层:分解为类。在C语言里没有类,可以使用结构体来描述子系统。
- 第4层:分解成子程序:实现那些结构体(结构体中有函数指针)。
为了方便阅读,本文从底层开始介绍代码,由底向上进行介绍,这样更方便阅读。本文主要介绍第4层与第3层的内容。
1、未使用面向对象编程的程序
1.1、库函数编程
以按键为例进行说明:
以下代码实现以下功能,key值代表是否按下按键PF6。如果按下按键,就点亮LED灯;若没有按下按键,则关闭LED。
void main(void)
{
GPIO_PinState key;
while (1)
{
key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
if (key == GPIO_PIN_RESET)
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_RESET);
else
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_SET);
}
}
但这样写代码有明显的缺陷:
- 需要结合原理图、需要有硬件知识
- 很难进行今后的代码维护和扩展
1.2、使用子函数
为了使自己的代码尽量远离硬件知识,我们在程序中加入大量的子函数。这样预留的API可以大大提高程序的可阅读性。方便以后自己代码的扩展,也方便与上层的算法人员进行代码的交流。
// main.c
void main(void)
{
int key;
while (1)
{
key = read_key();
if (key == UP)
led_on();
else
led_off();
}
}
// key.c
int read_key(void)
{
GPIO_PinState key;
key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
if (key == GPIO_PIN_RESET)
return 0;
else
return 1;
}
// led.c
void led_on(void)
{
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_RESET);
}
void led_off(void)
{
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_SET);
}
但此时我们仅完成了
第4层:分解成子程序:实现那些结构体(结构体中有函数指针)。
当程序中包含特别多的子函数时,有串口的,LED灯的,按键的,CAN口、SPI等等,这时候大量的子函数放到一起就会很头痛,因此我们想要将一些功能相似的函数耦合起来,这样更上一层楼到进到第三层:
- 第3层:分解为类。在C语言里没有类,可以使用结构体来描述子系统。
2、使用面向对象编程的程序
面向对象编程的基础是结构体与函数指针,因此在本节中我们先对函数指针和结构体进行介绍。
2.1、 函数指针
指针是C语言的灵魂,是必须掌握的内容,在此我先进行简单的介绍,如果后期看的人多并且点赞多的话,我会写专门的文章进行说明。
指针的基本概念是指向地址的变量,而指针也有相应的类型:例如int *P表示的是指针,并且大小为4个字节的内存的内容。其他数据类型有类似的效果。
而函数指针代表的是指向函数入口的指针变量,通过调用指针就可以找到函数入口,从而实现函数的调用。
假如有两个版本的按键读取函数,通过函数指针来确定哪一个使用哪一个版本的代码,代码如下(示例):
int (*read_key)(void);
// 返回值: 0表示被按下, 1表示被松开
int read_key_ver1(void)
{
GPIO_PinState key;
key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_5);
if (key == GPIO_PIN_RESET)
return 0;
else
return 1;
}
// 返回值: 0表示被按下, 1表示被松开
int read_key_ver2(void)
{
GPIO_PinState key;
key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
if (key == GPIO_PIN_RESET)
return 0;
else
return 1;
}
void key_init()
{
int ver = read_hardware_ver();// 读取硬件版本,版本1读PF5,版本2读PF6
if (ver == 1)
read_key = read_key_ver1;
else
read_key = read_key_ver2;
}
// main.c
void main(void)
{
int key;
key_init();
while (1)
{
key = read_key();
if (key == UP)
led_on();
else
led_off();
}
}
2.2、 结构体
面向对象的初衷是为了解决一类问题,因此要解决的是一类问题,即:
- 第3层:分解为类。在C语言里没有类,可以使用结构体来描述子系统。
在本文中就是要表达所有的按键处理函数。
本例中代表一个系统共有两个按键,通过结构体的表述代码如下所述(示例):
// key.c
// 返回值: 0表示被按下, 1表示被松开
int read_key(int which)
{
GPIO_PinState key;
switch (which)
{
case 0:
key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);
if (key == GPIO_PIN_RESET)
return 0;
else
return 1;
break;
case 1:
key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_7);
if (key == GPIO_PIN_RESET)
return 1;
else
return 0;
break;
}
}
该方法在小的系统代码中使用较多,但是不太容易进行扩展,如果想要加K3的话仍然需要再写一个case分支。
改进方法是通过结构体进行函数的编写,在结构体中包含指针函数的成员,这样关于按键的操作基本都被一个结构体所包含了。具体过程如下所述:
定义一个结构体变量代码示例如下:
typedef struct key {
char *name;
void (*init)(struct key *k);
int (*read)(struct key *k);
}key, *p_key;
每个按键都实现一个key结构体:
// key1.c
key k1 = {"k1", NULL, read_key1};
// key2.c
key k2 = {"k2", NULL, read_key2};
// key_net.c
key k_net = {"net", NULL, read_key_net};
总结
本文主要讲述了子函数、指针函数和结构体体的内容,主要完成的是代码第3和第4层的内容;
- 第3层:分解为类。在C语言里没有类,可以使用结构体来描述子系统。
- 第4层:分解成子程序:实现那些结构体(结构体中有函数指针)。
为接下来的代码分层打下基础,在下一篇文章中我们将主要介绍第2和1层的内容。如有不清楚的,欢迎提问或指出错误。