美文网首页
【RTOS训练营】设备子系统、晚课学员提问

【RTOS训练营】设备子系统、晚课学员提问

作者: 韦东山嵌入式Linux | 来源:发表于2022-08-24 14:39 被阅读0次

    一:设备子系统

    1.1 抽象结构体

    我们一直强调,面向对象的思想,即对某一个硬件,抽象出一个结构体。

    怎么描述一个对象?

    • 它有什么属性?
      • 结构体成员
    • 它有什么功能?
      • 函数指针

    因此,我们就要概括出它的属性,抽象出它的功能

    举个例子,LED有哪些属性?

    image.png

    我们先说简单一点,注意这个电路图,我们可以得出几个属性?

    第一,它使用哪一个引脚;

    第二,这引脚输出高电平还是低电平,可以让这个灯点亮;

    所以我们结构体里面就可以包含这些属性,如下:

    typedef struct LEDDevice {
        int group;
        int pin;
        int active_high;
    }
    

    对于这个LED,它使用哪一个引脚?

    比如103,就得确定它属于哪一组GPIO,再确定它属于这一组里面的哪一个引脚,所以我们得到了前面两项:group、pin。

    active_high什么意思呢?

    就表示这个LED,是高电平有效还是低电平有效。

    这就是根据硬件的属性,抽象出结构体里面的变量成员。

    这个结构体还没写完,只写了一半,我们再来看看这个LED有哪些功能:

    • 开、关
    • 设置颜色
    • 设置亮度

    对于最简单的灯,可以开,可以关。有些高级的灯,还可以调整颜色,设置亮度。

    所以我们有三个功能,那么这个结构体我们就可以写得比较完善了:

    typedef struct LEDDevice {
        int group;
        int pin;
        int active_high;
    
        /* 初始化LED设备, 成功则返回0 */
        int (*Init)(struct LEDDevice *ptLEDDevice);
    
        /* 控制LED设备, iStatus取值: 1-亮,0-灭 */
        int (*Control)(struct LEDDevice *ptLEDDevice, int iStatus);
    
        /* 未实现 */
        void (*SetColor)(struct LEDDevice *ptLEDDevice, int iColor);
    
        /* 未实现 */
        void (*SetBrightness)(struct LEDDevice *ptLEDDevice, int iBrightness);
    }LEDDevice, *PLEDDevice;
    

    再看看这个结构体有什么缺点,能不能够优化?

    1.亮度,颜色和当前亮灭可以在结构体里定义几个变量来表示。

    以便实现下次再开灯时,直接使用上一次的亮度值。

    2.作为用户,我只关心他是哪个灯,不关心它使用的是哪个GPIO组的哪个引脚,因此可以改进如下:

    #define LED_WHITE   0
    #define LED_BLUE    1
    #define LED_GREEN   2
    
    typedef struct LEDDevice {
        int which;
        int brightness;
        int color;
        
        /* 初始化LED设备, 成功则返回0 */
        int (*Init)(struct LEDDevice *ptLEDDevice);
    
        /* 控制LED设备, iStatus取值: 1-亮,0-灭 */
        int (*Control)(struct LEDDevice *ptLEDDevice, int iStatus);
    
        /* 未实现 */
        void (*SetColor)(struct LEDDevice *ptLEDDevice, int iColor);
    
        /* 未实现 */
        void (*SetBrightness)(struct LEDDevice *ptLEDDevice, int iBrightness);
        
        void (*SetFrequency)(struct LEDDevice *ptLEDDevice, int freq);
    }LEDDevice, *PLEDDevice;
    

    用户只需要传入LED_BLUE,结构体中使用which来指定是哪一个灯。

    3.这些属性是不是可以封装成一个结构体?

    这些属性,因为设备本身很复杂的,当然可以再封装出一些结构体。

    但是结构体本身,也增加了使用者的难度,所以这是一个要综合考虑的事情,举个例子:

    在Linux里面,怎么描述一个显示器?

    显示器里面有什么: 分辨率、每个像素用多少位来表示(bpp)、显存、上下左右的边沿、刷新频率等等。

    它有很多参数,你当然可以把这些参数一个一个列出来。但是, Linux里面,把这参数分为了两个结构体:

    • 结构体1:这个结构体描述的是LCD的、可以变化的信息。

      比如说用32位来表示一个像素,那么这32位里面,红绿蓝分别在哪里?是不是又得来描述这些属性?

      在这个结构体里面,又定义了一个新的结构体:fb_bitfield

    image.png
    • 结构体2(fb_bitfield):用这个结构里来表示红绿蓝分别在哪个位置,占据这32位里面的哪些位。

    回到我们的LED,如果这些属性比较简单,就没必要再新增一个结构体了。

    为了简化示例,这里只保留属性which

    image.png

    1.2 实现结构体

    抽象出这个结构体之后,我们就来实现这个结构体。

    前面是定义结构体的类型,下面要根据具体的LED,来定义结构体变量。

    static LEDDevice g_tLEDDevices[] = {
        {LED_WHITE, LEDDeviceInit, LEDDeviceControl},
        {LED_BLUE,  LEDDeviceInit, LEDDeviceControl}
        {LED_GREEN, LEDDeviceInit, LEDDeviceControl},
    }
    

    这里定了一个数组并赋值,这是一个结构体数组。

    image.png

    每一个数组项是一个LEDDevice结构体,用来表示一盏LED。

    里面有函数指针,怎么使用?

    image.png

    1.3 使用结构体

    我们现在,定义了三个LED设备,也都实现了里面的函数,

    作为一个使用者,我怎么去使用它呢?

    image.png

    上面的图里面,有一个函数:GetLEDDevice

    通过这个函数,去获得一个结构体,然后就可以调用结构体里面的函数来操作LED。

    举个例子,在这个文件里:

    image.png

    获得LED,初始化LED,控制LED:

    image.png

    作为应用程序开发的人,他的使用就是这么简单。

    二:晚课学员提问

    1. 问: 课程中这些函数怎么实现才能够比较容易扩展?

    image.png

    答: 要扩展什么?需要先想清楚自己要做什么事情。

    比如,我想让这个工程,能够支持裸机、 FreeRTOS、RT-Thread。

    当他支持裸机的时候,我想让他能够支持多款芯片:ST的、其他国产芯片。

    这个时候就要考虑程序的分层,以LED的硬件初始化为例:

    image.png

    如果把硬件的初始化代码放在这个函数里面的话,要换一种硬件的时候,就需要来改这个函数。

    换一种内核的时候,也要来改这个函数,所以我们应该分层。

    image.png

    这个所谓的抽象层,说的很高大上,但是里面的技术一点都不高大上。

    我们先来看看分层的实现:

    image.png

    我们想要我们的程序,支持裸机,支持各类rtos,怎么做呢?

    以初始化函数为例:

    image.png

    我想去初始化LED,我要调用一个KAL_LEDDeviceInit

    在这个函数里面,通过宏开关,来调用不同的内核的函数。

    现在对于裸机,我们抽象出了一个函数:CAL_LEDDeviceInit

    为什么不直接去调用HAL的代码?

    因为有些芯片它有HAL库,有些芯片就没有HAL库。

    你用ST的HAL写出了这个程序,今年ST的芯片买不到了,用了国产的芯片,没有HAL了,是不是要头大了?

    所以对裸机程序,我们又可以封装出这一层:

    image.png

    使用这些宏开关,来决定使用哪一套代码。

    下面这个图,就是我们分层的意义:

    image.png

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

    答: 对于这个问题,什么时候使用宏开关 ?什么时候使用结构体?

    问题的核心在于:是否同时支持?

    对于一个编译好的程序,我们不会同时支持裸机、支持RTOS。

    所以我们可以使用宏开关,来启动一部分代码,禁止另一部分代码,不占用多余Flash。

    而程序中,要支持多种输入设备,要支持多种LCD,比如程序不变,换其它规格的LCD,最好是使用链表。

    因此,要同时支持,就用结构体;事先就定死只支持一个,就用宏开关。

    3. 问: 老师,就是设备子系统为啥不像输入子系统那样用链表?

    答: 因为各类设备不容易统一,比如:

    • led: 开关
    • 风扇:正转、反转、风速。

    把LED设备、风扇设备、显示屏,放入一个链表的话,这个链表元素就不一样了。

    所以还不如:

    单独管理LED设备,你可以把多个LED设备放到LED链表里去,

    单独管理风扇设备,你可以把多个风扇设备放到风扇链表里去。

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

    然后hal库的gpio初始化是要传gpio寄存器的首地址和pin,

    那cal对外封装的结构体是要封装三个参数吗?整形instance,首地址,pin?

    答: 不用,这里要有“翻译”,举个例子:

    image.png

    5. 问: 请问,这种写法 sizeof(g_tLEDDevices) / sizeof(sizeof(g_tLEDDevices[0])) 分子和分母的数值分别是多少啊?

    image.png

    答: 整个数组的大小 / 单个数组项的大小。

    相关文章

      网友评论

          本文标题:【RTOS训练营】设备子系统、晚课学员提问

          本文链接:https://www.haomeiwen.com/subject/loqogrtx.html