分层设计的编程方法——输入系统(未落实完成)

如何整合散乱数据

对于一个系统的输入端,可能会有多种输入形式。比如单片机要检测按键、传感器、串口接收数据等等。这些输入信号都是独立的,不好管理。因此利用结构体将它整合起来,方便后面以分层的方法进行编程。

我们所需两种结构体:

  • 输入数据结构体
  • 输入设备结构体

输入数据结构体将全部的数据进行打包,方便后续调用采集到的数据。输入设备结构体主要是拿到源数据,它与底层的硬件驱动链接在一起。

具体的结构如下:

数据结构体

结构体所需的信息如下:

  • 类型:用于确定这个结构体是谁的数据
  • 数据:用于存放硬件产生的相应信息

具体的结构体声明如下:

/* 输入数据抽象层 */
typedef struct InputEvent{
	
	INPUT_EVENT_TYPE eType;	//类型,用于分辨是哪一种输入
	
	TIME_T time;	//按键时间
	int iKEY;		//哪一个按键
	int iPressure;  //按键状态
	
	char str[INPUT_BUF_LEN];//用于存储串口数据
	
}InputEvent,*PInputEvent;

在这个声明中,数据有两组:按键数据、串口接收数据。如果想继续扩展的话,只需要在这个结构体后面继续加入数据组即可。

在这个声明中,类型eType采用的是枚举类型,time采用的是宏定义的类型,其他变量使用的是int、char而非uint8_t这种类型。下面将说明为什么要使用这样的定义方法。

枚举声明类型

对于eType,含义是指定当前结构体是谁的数据,它是一个固定值。比如可以规定按键数据就是1,串口数据就是2...等等。这样我们可以使用枚举类型来实现类型的定义。

具体枚举声明如下:

typedef enum{
	
	INPUT_EVENT_TYPE_KEY,
	INPUT_EVENT_TYPE_NET,
	INPUT_EVENT_TYPE_STDIO
	
}INPUT_EVENT_TYPE;

使用枚举类型的好处,就是更好的管理这个类型的个数。当前的类型个数是3个,那么我们如果想再加一个类型,只需在INPUT_EVENT_TYPE_STDIO之后再加一行,这样就可以实现了扩展。

宏定义声明类型

对于time,含义是采集到按键按下的时间,但这个时间的存储方式并不知道是什么样子。在stm32中可以通过HAL_GetTick()来读取,返回的是int型,但在其他芯片中,返回可能是结构体或者其他类型。

这种情况下,可以使用宏定义来保证灵活性,这样只需修改宏定义,即可修改time的类型,方便了后面代码的修改。

具体宏定义如下:

#define TIME_T int

不使用uint8_t

uint8_t是一个别名,它声明在其他的文件中。在这种分层的文件中,最好使用最基本的类型名称,而不是使用别名。因此使用的是int、char、unsigned char等类型,而不是他们的别名uint8_t

设备结构体

结构体所需的信息如下:

  • 名称:根据名称来找到相应的设备结构体
  • 链表指针:以链表方式管理这个结构体
  • 设备处理函数指针
  • 设备初始化函数指针
  • 设备反初始化函数指针

具体的结构体声明如下:

/* 输入设备抽象层 */
typedef struct InputDevice{

	char* name;				  /* 后面可以根据名称来找到相应的设备结构体 */
	struct InputDevice* pNext;/* 链表 */
	
	int (*GetInputEvent)(PInputEvent ptInputEvent);	/* 指向设备处理函数 */
	int (*DeviceInit)(void);						/* 指向设备初始化函数 */
	int (*DeviceExit)(void);						/* 指向设备反初始化函数 */
	
}InputDevice,*PInputDevice;

这个name是一个固定的值,比如按键为“KEY”,串口为“UART”

有链表指针pNext是为了支持多个设备,将每个设备变为一个结构体,扩展设备只需插入链表

函数有返回值,含义为成功或者失败。

输入子系统

现在我们已经声明了数据存放的结构体,实现了各种输入数据的打包形式。之后我们只需要对这种打包的形式进行读或者写,就可以实现数据的传输。

对于读数据的部分,我们称为业务,它与硬件是隔离开的,是一种纯逻辑性的代码。

对于写数据的部分,我们称为硬件,它与硬件是结合的,是通过控制硬件的代码来获取源数据。

简易框架如下:

环形缓冲区

环形缓冲区的存在理由

从上述可以看出,在程序运行时,硬件要不断的获取源数据并转换成结构体形式存放起来,业务要不断的读取结构体形式的数据并进行相应的业务逻辑。而源数据又不是一个硬件采集的,即:数据有很多种,比如有按键数据,串口数据等等。

这就存在了一个问题,数据存哪?如果只存放在一个结构体中,那么如果处理的慢了,就会覆盖导致数据丢失。这个问题与串口接收的问题类似,因此也采用环形缓冲区的方式解决该问题。

环形缓冲区的实现

因为环形缓冲区就是一个数组(声明中为buf),数组中存放了一个又一个的数据。在串口中存放的是char型数据,而在这里需要存放的是结构体数据,即:前面的数据结构体类型InputEvent。

因此,只需修改buf的类型,而整体的实现框架依旧不变,即可完成功能。下面是具体的代码实现。

1、缓冲区声明

#define INPUT_BUF_SIZE 10	

/* 输入子系统的缓冲区 */
/* 用途:存储输入设备的数据,防止覆盖导致数据丢失 */
typedef struct{
	
	/* 缓冲区存放的是输入设备的数据,这数据是以InputEvent结构体形式存在 */
	InputEvent buf[INPUT_BUF_SIZE];

	int R_point;/* 读指针 */
	int W_point;/* 写指针 */
	
}InputEventBuffer;

2、提供的接口

/* 向下的接口:写缓冲区 */
/* 写缓冲区,将InputEvent类型的变量的值写入buf中 */
int PutInputEvent(PInputEvent ptInputEvent);
/* 向上的接口:读缓冲区 */
/* 读缓冲区,将buf中的数据保存在InputEvent类型的变量中 */
int GetInputEvent(PInputEvent ptInputEvent);

3、接口的具体实现

#include "input_buf.h"

/* 使用static的原因是不让其他.c文件访问到该全局变量 */
static InputEventBuffer g_tInputBuffer;

/* 环形缓冲区写数据 */
/* 返回值: 1:成功写入 0:写入失败 */
int PutInputEvent(PInputEvent ptInputEvent){
	
	if(ptInputEvent == NULL){
		return -1;
	}
	
	/* 1.判断缓冲区是否已经满了 */
	if((g_tInputBuffer.W_point+1)%INPUT_BUF_SIZE == g_tInputBuffer.R_point){
		return 0;
	}
	
	/* 2.写缓冲区 */
	g_tInputBuffer.buf[g_tInputBuffer.W_point] = *ptInputEvent;
	
	/* 3.写指针+1 */
	g_tInputBuffer.W_point = (g_tInputBuffer.W_point+1)%INPUT_BUF_SIZE;
	
	return 1;
}

/* 环形缓冲区读操作 */
int GetInputEvent(PInputEvent ptInputEvent){
	
	if(ptInputEvent == NULL){
		return -1;
	}
	
	/* 1.判断缓冲区是否为空 */
	if(g_tInputBuffer.R_point == g_tInputBuffer.W_point){
		return 0;
	}
	
	/* 2.读缓冲区 */
	*ptInputEvent = g_tInputBuffer.buf[g_tInputBuffer.R_point];
	
	/* 3.读指针+1 */
	g_tInputBuffer.R_point = (g_tInputBuffer.R_point+1)%INPUT_BUF_SIZE;
	
	return 1;
}

硬件设备抽象

前面我们只知道该将读取到的数据写入环形缓冲区,但并没有方法控制这些采集数据的设备。可以使用前面声明的设备结构体来对设备进行控制。

在编写该代码之前,需要对整个的控制框架进行了解,具体的框架如下:

数据由硬件采集得到,硬件需要驱动但各种内核不同、芯片也不同,这就需要一层一层的编写。

对于最上层的数据,以前面声明的设备结构体与数据结构体进行管理,是与硬件完全分离的代码。

对于内核,向上提供一个接口,供设备结构体进行使用,这一层并不进行对硬件底层的访问

对于芯片,向上提供一个接口,供内核使用,这一层才是真正的硬件驱动代码。

  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值