【1】05设备子系统设计|程序设计框架

创建时间:2022/3/1 16:04
更新时间:2022/4/11 14:58
作者:gi51wa2j
标签:bingo, RTOS训练营, 基于HAL的智能家居, 正文

1.设备系统_设计思路

1.1 总体框架

构造一个设备子系统来屏蔽底层硬件的差异,方便移植

1.1.1 驱动程序和HAL库区别

  • HAL库:
硬件和软件之间的藕合太严重了,如果硬件修改,随之软件也必须进行大幅度调整
  • 驱动
软件和硬件之间插入一层驱动程序,保证软件的接口不改变,驱动程序随便变化,使得应用程序可以在不同的硬件上运行。

1.2 怎么访问设备

1.2.1 裸机里怎么访问设备

使用HAL库,或者厂家自己封装的库,甚至自己编写代码直接访问寄存器


1.2.2 FreeRTOS怎么访问设备

FreeRTOS中没有驱动程序框架,它访问设备时方法跟裸机一样。


1.2.3 RT-Thread怎么访问设备

RT-Thread可以使用2中方法访问设备:

  • 像裸机一样

  • 使用RT-Thread的驱动程序框架

所谓"驱动框架",就是事先定义好的接口函数,你要添加新设备就必须实现这些接口函数。

好处是:无论硬件怎么改,驱动程序的接口不变,上面的应用程序也就不需要改变。


"I/O设备管理"接口如下:


应用程序通过标准的接口来访问设备:rt_device_find/rt_device_open/rt_device_read/rt_device_write等等。


1.2.4 Linux下怎么访问设备

Linux系统中,APP和驱动程序严格分离开:

  • APP无法直接读写寄存器

  • APP必须通过驱动程序访问设备

  • APP使用的接口只有:open/read/write/ioctl等


1.3 有必要统一设备的访问吗?

先把设备子系统分层:

至少有2层:虚线上下

  • 虚线之上:写出统一的API接口

  • 虚线之下:根据不同的系统,调用不同的函数(可以屏蔽操作系统的不一样/硬件的不一样)


对于开发应用程序的人:

  • 他不关心LED使用哪个GPIO引脚

  • 他不关心GPIO是输出高还是低来控制LED

  • 他不关心open什么、read/write什么

  • 我们不应该要求他:

    • 阅读原理图

    • 阅读芯片手册

    • 研究HAL库、RT-Thread或者Linux的驱动函数怎么调用

  • 甚至不应该要求他去理解你抽象出来的某个结构体

    • 你可以抽象出一个LEDDevice

    • 但是LEDDevice里,他只关心怎么使用Init/Control中2个函数指针

    • 其他成员一概不关心


虽然我们freertos中提供了统一的接口函数,但是这个统一能在freertos内部实现,所以我们很有必要提供更高层次的API,以LED为例:
  • 可以提供:LEDInit/LedControl,这2个函数可以放入LEDDevice结构体里

  • 应用开发者,只需要调用这2个函数

提供这些统一的接口不仅仅为了屏蔽操作系统的差别,屏蔽硬件的不一致也为了实现专业知识的隔离。


1.4 设计原则:驱动和应用分开

在Linux驱动开发中,有一句话:驱动只提供功能,不提供策略

什么意思呢?就是各司其职,不要越界。

以LCD的使用为例,可以分为3层:

  • 驱动程序:

    • 提供像素操作的功能

    • 但是怎么显示字符、显示多大、在哪显示,这不关我的事

  • 库函数/功能函数:

    • 提供显示字符、显示图片的功能

    • 但是显示什么字符、在哪显示,这不关我的事

  • APP:

    • 使用库函数来显示字符、显示图片

    • 我甚至不需要看驱动程序


我们实现各类子系统时,要划分层次的时候,也要理清楚:

  • 有哪些功能

  • 这些功能怎么细分?得到层次

  • 每个层次各司其职,不要越界


1.5 设计思路

使用面向对象的思想,对于每一种设备,抽象出一个结构体,结构体里有设备相关的函数指针。

不同设备,不强求统一,不强求用一个结构体类型,支持所有设备。

编写函数时,要注意:

  • 头文件:这些函数是面向APP开发者,假设他们对硬件一无所知,尽量不传入专业类型的参数

  • C文件:函数内部,再根据不同系统、不同芯片,调用其他函数

2.设备系统_实现LED设备

本节源码:在GIT仓库中

rtos_doc_source\RTOS培训资料\ 01_项目1_基于HAL库实现智能家居\ 05_项目1_基于HAL库的智能家居\1_项目源码\10_7_device_led

2.1 总体框架


2.2 LED有什么功能

  • 开、关

  • 设置颜色

  • 设置亮度


2.3 抽象出结构体

2.3.1 如何抽象出结构体

首先要明白怎么描述一个对象
  • 它有什么属性?
    • 结构体成员
  • 它有什么功能?
    • 函数指针
本质:概括出它的属性,抽象出它的功能

#define LED_WHITE 0 #define LED_BLUE 1 #define LED_GREEN 2 typedef struct LEDDevice { int which; int (*Init)(struct LEDDevice *ptLEDDevice); int (*Control)(struct LEDDevice *ptLEDDevice, int iStatus); void (*SetColor)(struct LEDDevice *ptLEDDevice, int iColor); void (*SetBrightness)(struct LEDDevice *ptLEDDevice, int iBrightness); }LEDDevice, *PLEDDevice;


2.4 编程

我们把LED设备分为4层:

2.4.1 设备层

static int LEDDeviceInit(struct LEDDevice *ptLEDDevice) { return KAL_LEDDeviceInit(ptLEDDevice); } static int LEDDeviceControl(struct LEDDevice *ptLEDDevice, int iStatus) { return KAL_LEDDeviceControl(ptLEDDevice, iStatus); } static LEDDevice g_tLEDDevices = { {LED_WHITE, LEDDeviceInit, LEDDeviceControl}, {LED_BLUE,  LEDDeviceInit, LEDDeviceControl}, {LED_GREEN, LEDDeviceInit, LEDDeviceControl}, }; PLEDDevice GetLEDDevice(int which) { if (which >= LED_WHITE && which <= LED_GREEN) return &g_tLEDDevices[which]; else return NULL; }


2.4.2 内核抽象层

#include <led_device.h> int KAL_LEDDeviceInit(struct LEDDevice *ptLEDDevice) { /* 对于裸机/FreeRTOS */ return CAL_LEDDeviceInit(ptLEDDevice); /* 对于RT-Thread */ /* 对于Linux */ } int KAL_LEDDeviceControl(struct LEDDevice *ptLEDDevice, int iStatus) { /* 对于裸机/FreeRTOS */ return CAL_LEDDeviceControl(ptLEDDevice, iStatus); /* 对于RT-Thread */ /* 对于Linux */ }


2.4.3 芯片抽象层

#include <led_device.h> int CAL_LEDDeviceInit(struct LEDDevice *ptLEDDevice) { /* 对于hal */ /* 已经在MX_GPIO_Init初始化了引脚 */ return 0; } int CAL_LEDDeviceControl(struct LEDDevice *ptLEDDevice, int iStatus) { /* 对于hal */ return HAL_LEDDeviceControl(ptLEDDevice, iStatus); }


2.4.4 硬件操作层

/* *  函数名:int HAL_LEDDeviceControl(struct LEDDevice *ptLEDDevice, int iStatus) *  输入参数:ptLEDDevice-哪个LED *  输入参数:iStatus-LED状态, 1-亮, 0-灭 *  输出参数:无 *  返回值:0-成功, -1: 失败 */ int HAL_LEDDeviceControl(struct LEDDevice *ptLEDDevice, int iStatus) { if (!ptLEDDevice) return -1; switch (ptLEDDevice->which) { case LED_WHITE: {     HAL_GPIO_WritePin(WHITE_GPIO_Port, WHITE_Pin, !iStatus); break; } case LED_BLUE: {     HAL_GPIO_WritePin(BLUE_GPIO_Port, BLUE_Pin, !iStatus); break; } case LED_GREEN: {     HAL_GPIO_WritePin(GREEN_GPIO_Port, GREEN_Pin, !iStatus); break; } default: return -1; } return 0; }


3.设备系统_单元测试

本节源码:在GIT仓库中

rtos_doc_source\RTOS培训资料\ 01_项目1_基于HAL库实现智能家居\ 05_项目1_基于HAL库的智能家居\1_项目源码\10_8_device_led_unittest

3.1 编译程序

编译程序时,最多的警告就是:

..\smartdevice\kal\kal_time.c(16): warning:  #223-D: function "CAL_GetTime" declared implicitly

在.c文件中使用了某些函数,但是没有这些函数的声明。

解决方法是:

  • 在头文件中声明函数

  • 在.c文件中包含头文件


从第1个警告、第1个错误开始解决,修改完第1个警告/错误后就重新编译。


3.2 编写测试代码

代码:led_test.c

/********************************************************************** * 函数名称: led_test * 功能描述: 设备系统LED设备单元测试函数 * 输入参数: 无 * 输出参数: 无 * 返 回 值: 无 * 修改日期       版本号     修改人       修改内容 * ----------------------------------------------- * 2021/09/27      V1.0   韦东山       创建 ***********************************************************************/ void led_test(void) { PLEDDevice p1 = GetLEDDevice(LED_WHITE); PLEDDevice p2 = GetLEDDevice(LED_BLUE); PLEDDevice p3 = GetLEDDevice(LED_GREEN); p1->Init(p1); p2->Init(p2); p3->Init(p3); while (1) { p1->Control(p1, 1); p2->Control(p2, 1); p3->Control(p3, 1); KAL_Delay(500); p1->Control(p1, 0); p2->Control(p2, 0); p3->Control(p3, 0); KAL_Delay(500); } }


3.3 上机实验

三个灯同时闪烁

3.4 1-3节问题汇总

问:我记得输入子系统中您并不推荐用宏开关,而是用结构体来支持不同类型,当初还举了lcd的例子。

答:对于这个问题,什么时候使用宏开关 ?什么时候使用结构体?
问题的核心在于:是否同时支持?
对于一个编译好的程序,我们不会同时支持裸机、支持RTOS。
所以我们可以使用宏开关,来启动一部分代码,禁止另一部分代码,不占用多余Flash。
而程序中,要支持多种输入设备,要支持多种LCD,比如程序不变,换其它规格的LCD,最好是使用链表。
因此,要同时支持,就用结构体;事先就定死只支持一个,就用宏开关。

问:比如一个标准库的gpio初始化是传一个整形的instance和一个pin,

然后hal库的gpio初始化是要传gpio寄存器的首地址和pin,
那cal对外封装的结构体是要封装三个参数吗?整形instance,首地址,pin?
答: 不用,这里要有“翻译”,举个例子:

4.设备系统_显示设备结构体抽象

本节源码:在GIT仓库中

rtos_doc_source\RTOS培训资料\ 01_项目1_基于HAL库实现智能家居\ 05_项目1_基于HAL库的智能家居\1_项目源码\10_9_device_display_struct

4.1 显示设备的硬件概括

4.1.1 LCD显示原理

什么是LCD?就是多行多列的像素:

  • 对于黑白屏幕(单色屏幕),这些像素只有2个状态:点亮、熄灭

  • 对于彩色屏幕,这些像素有颜色:可以用RGB三原色来表示

怎么控制LCD上每个像素的状态?

  • 有显存,就是一块内存,也被称为FrameBuffer

  • 每个像素在显存上都有对应的数据

    • 对于黑白屏(单色屏),每个像素在显存里有对应的1位数据

    • 对于彩色屏,每个像素在显存里有几个字(可能是1字节、2字节、4字节)

    • 每个像素用多少位数据?被称为BPP:Bits Per Pixel。

要注意的是,LCD可能自带显存,也可能不带有显存(要使用LCD的话,就需要在系统内存中分配显存)。



有三种类型的LCD。

4.1.2 LCD含有显存, CPU通过I2C访问

很多I2C、SPI接口的屏幕,本身是含有显存的。要在LCD上显示文字、图片,就需要网显存里写入数据。

程序通过I2C接口写显存。


4.1.3 LCD含有显存, CPU可以直接访问

有些LCD含有显存,并且CPU可以直接访问显存:就像访问一般内存一样访问显存。

我们只需要写数据到显存即可。

4.1.4 LCD没有显存, LCD控制器从内存得到数据

很多TFT LCD本身是没有显存的,那么数据保存在哪里?可以在系统内存里分配一块空间,它就是显存。

设置好LCD控制器后,它就会自动从显存取出数据、发送给LCD。

我们只需要写数据到显存即可。

4.1.5 差别在哪?

对于软件来说,这3种LCD都有显存,第1种无法直接写显存;第2、3种可以直接写显存。

能否统一?

对于第1种LCD,能否也直接写显存?可以:

  • 在系统内存分配另一个"显存FB"

  • 软件直接写"显存FB"

  • 在通过I2C把"显存FB"的内容传送到LCD自带的显存


4.2 显示设备的结构体抽象

怎么抽象出一个显示设备?

  • 有初始化函数

  • 有显存,怎么描述显存?

    • 起始地址

    • 分辨率

    • 每个像素用多少位来表示

  • 对于第1种LCD,还需要一个Flush函数,把"显存FB"的内容"刷"到LCD的显存去

结构体如下:

typedef struct DispayDevice { char *name;    //名字 void *FBBase;    //显存基地址,Frame帧 int iXres;             //X分辨率 int iYres;             //X分辨率 int iBpp;              //每个像素用几位来表示 int (*Init)(struct DispayDevice *ptDev);//初始化函数 void (*Flush)(struct DispayDevice *ptDev);//刷新函数,将显存数据刷新到屏幕 }DispayDevice, *PDispayDevice;


  • 对于可以直接写显存的设备,可以把Flush函数设置为空函数

  • 对于不能直接写显存的设备,先把数据放到FBBase这个临时显存中,然后调用Flush函数把数据刷到显示器中

5.设备系统_实现显示设备

本节源码:在GIT仓库中

rtos_doc_source\RTOS培训资料\ 01_项目1_基于HAL库实现智能家居\ 05_项目1_基于HAL库的智能家居\1_项目源码\10_10_device_display_oled // 需要用到OLED的代码 rtos_doc_source\RTOS培训资料\ 01_项目1_基于HAL库实现智能家居\ 03_项目必备的HAL库基础\1_项目源码\4_OLED

5.1 显示设备的结构体抽象

怎么抽象出一个显示设备?

  • 有初始化函数

  • 有显存,怎么描述显存?

    • 起始地址

    • 分辨率

    • 每个像素用多少位来表示

  • 对于第1种LCD,还需要一个Flush函数,把"显存FB"的内容"刷"到LCD的显存去

结构体如下:

typedef struct DispayDevice { char *name; void *FBBase; /* CPU能直接读写的显存 */ int iXres;    /* X方向分辨率 */ int iYres;    /* Y方向分辨率 */ int iBpp;     /* 每个像素使用多少个像素 */ int (*Init)(struct DispayDevice *ptDev);   /* 硬件初始化 */ void (*Flush)(struct DispayDevice *ptDev); /* 把FBBase的数据刷到LCD的显存里 */ /* 设置FBBase中的数据, 把(iX,iY)的像素设置为颜色dwColor * dwColor的格式:0x00RRGGBB */ int (*SetPixel)(struct DispayDevice *ptDev, int iX, int iY, unsigned int dwColor); }DispayDevice, *PDispayDevice;


5.2 程序层次

5.3 编程

核心是底下的oled_device.c,它要构造出一个DisplayDevice。

我们先实现它,再去考虑管理的事情。

5.3.1 构造DisplayDevice

文件:oled_device.c

static DispayDevice g_tOLEDDevice = { "OLED", g_OLEDFramebuffer, 128, 64, 1, OLEDDeviceInit, OLEDDeviceFlush, OLEDDeviceSetPixel };
5.3.1.1 Init和Flush

OLEDDeviceInit和OLEDDeviceFlush比较简单,都是调用下面KAL层的函数:

/********************************************************************** * 函数名称: OLEDDeviceInit * 功能描述: 初始化OLED硬件 * 输入参数: ptDev-哪个显示设备 * 输出参数: 无 * 返 回 值: 0-成功 * 修改日期       版本号     修改人       修改内容 * ----------------------------------------------- * 2021/09/29      V1.0   韦东山       创建 ***********************************************************************/ static int OLEDDeviceInit(struct DispayDevice *ptDev) { /* 初始化OLED硬件 */ return KAL_OLEDDeviceInit(ptDev); } /********************************************************************** * 函数名称: OLEDDeviceFlush * 功能描述: 把临时显存的数据, 刷到LCD的显存去 * 输入参数: ptDev-哪个显示设备 * 输出参数: 无 * 返 回 值: 无 * 修改日期       版本号     修改人       修改内容 * ----------------------------------------------- * 2021/09/29      V1.0   韦东山       创建 ***********************************************************************/ static void OLEDDeviceFlush(struct DispayDevice *ptDev) { /* 把Framebuffer g_OLEDFramebuffer的数据搬到OLED自带的显存里 */ KAL_OLEDDeviceFlush(ptDev); }


5.3.1.2 SetPixel函数

需要计算的就是,(iX, iY)在显存里哪个位置、哪个bit,

代码如下:

/********************************************************************** * 函数名称: OLEDDeviceSetPixel * 功能描述: 在显存中设置(iX,iY)像素的颜色 * 输入参数: ptDev-哪个显示设备 * 输入参数: iX-X坐标 * 输入参数: iY-Y坐标 * 输入参数: dwColor-颜色 * 输出参数: 无 * 返 回 值: 0-成功 * 修改日期       版本号     修改人       修改内容 * ----------------------------------------------- * 2021/09/29      V1.0   韦东山       创建 ***********************************************************************/ static int OLEDDeviceSetPixel(struct DispayDevice *ptDev, int iX, int iY, unsigned int dwColor) /* 0x00RRGGBB */ { unsigned char *buf = ptDev->FBBase; int page; unsigned char *byte; int bit; if (iX >= ptDev->iXres || iY >= ptDev->iYres) return -1;   page = iY / 8;  /*得到页信息*/ byte = buf + page * 128 + iX; /*得到像素地址*/ bit = iY % 8;   /*得到列信息*/   if (dwColor) *byte |= (1<<bit); else *byte &= ~(1<<bit); return 0; }


5.3.2 KAL层

文件:kal_oled_device.c

核心思路就是根据配置项,调用对应的函数。代码如下:

/********************************************************************** * 函数名称: KAL_OLEDDeviceInit * 功能描述: 内核抽象层的函数,初始化OLED硬件 * 输入参数: ptDev-哪个显示设备 * 输出参数: 无 * 返 回 值: 0-成功 * 修改日期       版本号     修改人       修改内容 * ----------------------------------------------- * 2021/09/29      V1.0   韦东山       创建 ***********************************************************************/ int KAL_OLEDDeviceInit(struct DispayDevice *ptDev) { /* 初始化OLED硬件 */ #if defined (CONFIG_NOOS) return CAL_OLEDDeviceInit(ptDev); #elif defined (CONFIG_FREERTOS) return FreeRTOS_OLEDDeviceInit(ptDev); #elif defined (CONFIG_RTTHREAD) return RTThread_OLEDDeviceInit(ptDev); #endif } /********************************************************************** * 函数名称: OLEDDeviceFlush * 功能描述: 内核抽象层的函数, 把临时显存的数据, 刷到LCD的显存去 * 输入参数: ptDev-哪个显示设备 * 输出参数: 无 * 返 回 值: 无 * 修改日期       版本号     修改人       修改内容 * ----------------------------------------------- * 2021/09/29      V1.0   韦东山       创建 ***********************************************************************/ void KAL_OLEDDeviceFlush(struct DispayDevice *ptDev) { /* 把Framebuffer g_OLEDFramebuffer的数据搬到OLED自带的显存里 */ #if defined (CONFIG_NOOS) CAL_OLEDDeviceFlush(ptDev); #elif defined (CONFIG_FREERTOS) FreeRTOS_OLEDDeviceFlush(ptDev); #elif defined (CONFIG_RTTHREAD) RTThread_OLEDDeviceFlush(ptDev); #endif   }


5.3.3 CAL层

文件:cal_oled_device.c

核心思路就是根据配置项,调用对应的函数。代码如下:

/********************************************************************** * 函数名称: CAL_OLEDDeviceInit * 功能描述: 芯片抽象层的函数,初始化OLED硬件 * 输入参数: ptDev-哪个显示设备 * 输出参数: 无 * 返 回 值: 0-成功 * 修改日期       版本号     修改人       修改内容 * ----------------------------------------------- * 2021/09/29      V1.0   韦东山       创建 ***********************************************************************/ int CAL_OLEDDeviceInit(struct DispayDevice *ptDev) { /* 初始化OLED硬件 */ #if defined (CONFIG_SUPPORT_HAL)     // 1. 重新初始化I2C的引脚     I2C_GPIO_ReInit();     // 2. 初始化OLED      OLED_Init(); return 0; #elif return NOHAL_OLEDDeviceInit(ptDev); #endif } /********************************************************************** * 函数名称: CAL_OLEDDeviceFlush * 功能描述: 芯片抽象层的函数, 把临时显存的数据, 刷到LCD的显存去 * 输入参数: ptDev-哪个显示设备 * 输出参数: 无 * 返 回 值: 无 * 修改日期       版本号     修改人       修改内容 * ----------------------------------------------- * 2021/09/29      V1.0   韦东山       创建 ***********************************************************************/ void CAL_OLEDDeviceFlush(struct DispayDevice *ptDev) { /* 把Framebuffer g_OLEDFramebuffer的数据搬到OLED自带的显存里 */ #if defined (CONFIG_SUPPORT_HAL) OLED_Copy(ptDev->FBBase); #elif NOHAL_OLEDDeviceFlush(ptDev); #endif   }


5.3.4 硬件操作

一起讲HAL库时写过程序,参考代码:

// 需要用到OLED的代码 rtos_doc_source\RTOS培训资料\ 01_项目1_基于HAL库实现智能家居\ 03_项目必备的HAL库基础\1_项目源码\4_OLED

5.4 显示设备晚课问题汇总

问:课程中抽象的LCD结构体,里面void *FBBase;为什么使用void *?

答:之所以写成 void *, 是因为他可以直接赋给给任何的指针: char *buf = FBBase; short *buf = FBBase;
也可以接受任何的指针: char *buf; FBBase = buf; short *buf; FBBase = buf;
对于8bbp的情况, 我们操作一个数据时就是unsigned char *
对于16bbp的情况, 我们操作一个数据时就是unsigned short *
对于32bbp的情况, 我们操作一个数据时就是unsigned int

问:课程中display的设备最终是调用到oled底层的接口的,很疑惑,为什么不是对Oled进行抽象,oled和display这个设备的关系是什么?

答:Oled属于一种显示设备,OLED属于Display。
我们的产品中可能支持多个屏幕。

问:对于屏幕不同颜色闪屏的这种是怎么设计的,循环嘛?

答: 闪屏的问题在于:程序一边写数据, LCD同时读数据来显示。
比如就会出现半边红半边蓝的问题:
眼睛就感觉到闪屏。

问:应该有像freetype库那样的开源库吧,输入汉字,他给你转换成点阵?

答:可以得到点阵,但是这个点阵怎么在屏幕上显示出来,底层的驱动需要我们实现。一般来说都是提供显存:LVGL、QT等GUI都是这样做的。

问:怎么判断cpu是否可以直接操作显存呢?

答:要大家可以打开这个原理图:
F103通过内存接口,来连接LCD, LCD上自带显存, CPU可以直接写显存。


这个就是RAM-LIKE接口。
这个里面必定有片选信号:
下面我们再来看看这种非内存接口情况:
我们以一个IMX6ULL板子为例,它的接线全是LCD的颜色数据线:


所以,
1. LCD本身没有内存
2. 主芯片上必定有一个LCD控制器
3. LCD控制器会去内存里面取出数据,发给LCD



我们再来比较一下这两种接口,
内存的接口是怎样的:有地址线、有数据线、是读还是写、还有片选



再想想,内存,要读数据写数据才叫内存。
要读写内存哪一个位置?就得要有地址。
要传输数据:就得要有数据线。

再来看看LCD的接口,明显和RAM-LIKE接口有区别:

问:如果LCD控制器是12位的,怎么转换成RGB565的16位呢?

答:比如我的LCD控制器只能够发出4位的红颜色,但是LCD它需要5位的红颜色,有两个办法来处理:
第一,把LCD的R3接地,让最低位永远都是0;
第二,把LCD的R3接到LCD控制器的红颜色最高位;
这就是所谓的补位,人的眼睛没有那么敏感,这种接法都没什么问题的。

6.设备系统_显示设备单元测试

本节源码:在GIT仓库中

rtos_doc_source\RTOS培训资料\ 01_项目1_基于HAL库实现智能家居\ 05_项目1_基于HAL库的智能家居\1_项目源码\10_11_device_display_oled_unittest

6.1 程序层次

6.2 编写管理层

6.2.1 display_device.c

使用链表来管理底下各个显示设备,底层的显示设备要把自己注册进链表:

* 函数名称: DisplayDeviceRegister * 功能描述: 注册一个输入设备 * 输入参数: ptDisplayDevice-输入设备 * 输出参数: 无 * 返 回 值: 无 * 修改日期        版本号     修改人       修改内容 * ----------------------------------------------- * 2021/09/24      V1.0   韦东山       创建 ***********************************************************************/ void DisplayDeviceRegister(PDisplayDevice ptDisplayDevice) { ptDisplayDevice->pNext = g_ptDisplayDevices; g_ptDisplayDevices = ptDisplayDevice; }


我们还可以通过名字从链表中把设备取出来,__GetDisplayDevice函数前面有__前缀,表示这是内部函数:

/********************************************************************** * 函数名称: __GetDisplayDevice * 功能描述: 获得显示设备 * 输入参数: name-显示设备的名称 * 输出参数: 无 * 返 回 值: 成功-PDisplayDevice, 失败-NULL * 修改日期        版本号     修改人       修改内容 * ----------------------------------------------- * 2021/10/08      V1.0   韦东山       创建 ***********************************************************************/ PDisplayDevice __GetDisplayDevice(char *name) { PDisplayDevice pTmp = g_ptDisplayDevices; while (pTmp) { if (strcmp(pTmp->name, name) == 0) return pTmp; else pTmp = pTmp->pNext; } return NULL; }


6.2.2 display_system.c

程序中关于显示设备链表的注册,为了避免display_device.c和oled_device.c两个文件相互调用,这里创建display_system.c,显示设备对外的统一接口。

这是显示设备对外的统一接口,提供了2个函数。

第一个函数是注册所有的显示设备:

  • 它调用OLED设备提供的AddDisplayDeviceOLED函数

  • 如果还有其他显示设备,请在此函数中添加注册代码

/********************************************************************** * 函数名称: AddDisplayDevices * 功能描述: 注册多个显示设备 * 输入参数: 无 * 输出参数: 无 * 返 回 值: 无 * 修改日期        版本号     修改人       修改内容 * ----------------------------------------------- * 2021/10/08      V1.0   韦东山       创建 ***********************************************************************/ void AddDisplayDevices(void) { AddDisplayDeviceOLED(); }


第二个函数是根据名字获得显示设备:

/********************************************************************** * 函数名称: GetDisplayDevice * 功能描述: 获得显示设备 * 输入参数: name-显示设备的名称 * 输出参数: 无 * 返 回 值: 成功-PDisplayDevice, 失败-NULL * 修改日期        版本号     修改人       修改内容 * ----------------------------------------------- * 2021/10/08      V1.0   韦东山       创建 ***********************************************************************/ PDisplayDevice GetDisplayDevice(char *name) { return __GetDisplayDevice(name); }


6.3 编写单元测试代码

代码为smartdevice\unittest\display_test.c,函数如下:

/********************************************************************** * 函数名称: display_test * 功能描述: 显示设备单元测试函数 * 输入参数: 无 * 输出参数: 无 * 返 回 值: 无 * 修改日期       版本号     修改人       修改内容 * ----------------------------------------------- * 2021/10/08      V1.0   韦东山       创建 ***********************************************************************/ void display_test(void) { PDisplayDevice ptDevice; char *name = "OLED"; int x, y; AddDisplayDevices(); ptDevice = GetDisplayDevice(name); if (!ptDevice) { printf("Can not get DisplayDevice %s\r\n", name); return; } /* 1. 初始化设备 */ ptDevice->Init(ptDevice); /* 2. 清除屏幕 */ memset(ptDevice->FBBase, 0, ptDevice->iSize); /* 3. 画线 */ y = ptDevice->iYres / 2; for (x = 0; x < ptDevice->iXres; x++) ptDevice->SetPixel(ptDevice, x, y, 1); x = ptDevice->iXres / 2; for (y = 0; y < ptDevice->iYres; y++) ptDevice->SetPixel(ptDevice, x, y, 1); /* 4. Flush */ ptDevice->Flush(ptDevice); }

6.4 上机测试

测试结果为:在OLED屏幕上画出一个十字。



7.设备系统_风扇设备源码讲解

本节源码:在GIT仓库中

rtos_doc_source\RTOS培训资料\ 01_项目1_基于HAL库实现智能家居\ 05_项目1_基于HAL库的智能家居\1_项目源码\10_12_device_fan


7.1 硬件操作原理

如下图连接风扇:


风扇电路图我们也没有,但是接口很简单:

D:\abc\rtos_doc_source\RTOS培训资料\ 00_基础资源资料\1_STM32F103\3_硬件资料\2_扩展板原理图\ F103_Extend_V2.pdf



INA
INB
顺时针旋转
0
1
逆时针旋转
1
0
停止
0
0
停止
1
1


7.2 代码讲解

从上到下涉及的文件: smartdevice\unittest\fan_test.c smartdevice\device\fan_device.c smartdevice\kal\kal_fan_device.c smartdevice\cal\cal_fan_device.c ModuleDrivers\driver_fan.c


7.3 单元测试

smartdevice\unittest\fan_test.c:

/********************************************************************** * 函数名称: fan_test * 功能描述: 设备系统风扇设备单元测试函数 * 输入参数: 无 * 输出参数: 无 * 返 回 值: 无 * 修改日期       版本号     修改人       修改内容 * ----------------------------------------------- * 2021/10/18      V1.0   韦东山       创建 ***********************************************************************/ void fan_test(void) { PFANDevice ptDev = GetFANDevice(); /* 初始化设备 */ ptDev->Init(ptDev); while (1) { /* 顺时针旋转 */ ptDev->SetSpeed(ptDev, 1); KAL_Delay(2000); /* 停止 */ ptDev->SetSpeed(ptDev, 0); KAL_Delay(2000); /* 逆时针旋转 */ ptDev->SetSpeed(ptDev, -1); KAL_Delay(2000); /* 停止 */ ptDev->SetSpeed(ptDev, 0); KAL_Delay(2000); } }