手机网站建设推广,西安关键词网站排名,建设银行信用卡在网站激活后如何设置密码,网上做国外兼职网站#x1f431;作者#xff1a;一只大喵咪1201 #x1f431;专栏#xff1a;《智能家居项目》 #x1f525;格言#xff1a;你只管努力#xff0c;剩下的交给时间#xff01; 输入子系统中目前仅实现了按键输入#xff0c;剩下的网络输入和标准输入在以后会逐步实现作者一只大喵咪1201 专栏《智能家居项目》 格言你只管努力剩下的交给时间 输入子系统中目前仅实现了按键输入剩下的网络输入和标准输入在以后会逐步实现今天先来实现设备子系统包含LED设备(GPIO控制)风扇设备OLED设备。 目录 设计思路LED设备设备层内核抽象层芯片抽象层硬件操作单元测试 显示设备管理及设备层内核抽象层芯片抽象层及硬件操作单元测试 风扇设备设备层内核抽象层芯片抽象层硬件操作单元测试 设计思路
不同内核下是访问设备的方式是不同的
裸机里怎么访问设备
对于ST芯片可以使用HAL库访问设备对于一些国产芯片可以使用厂家自己封装的库甚至自己编写代码直接访问寄存器来访问设备。
FreeRTOS怎么访问设备
FreeRTOS中并没有驱动程序框架它访问设备时方法和裸机一样。
RT-Thread怎么访问设备
RT-Thread可以使用两种方法访问设备
像裸机一样访问使用RT-Thread的驱动程序框架 如上图便是RT-Thread驱动程序框架的示意图所谓驱动框架就是事先定义好的接口函数无论是访问设备还是添加新设备直接调用这些接口函数即可。
所以我们要做的就是实现这些接口函数好让应用层去直接调用这样做的好处是无论硬件怎么修改驱动程序的接口并不会改变上层的应用程序也就不需要改变。 如上图所示是RT-Thread驱动框架下“I/O设备管理”的接口应用程序通过标准的接口来访问设备 rt_device_find查找设备rt_device_open打开设备rt_device_read从设备中读取数据rt_device_write向设备中写数据 Linux下怎么访问设备
Linux系统中和驱动程序严格分离开应用程序无法直接读写寄存器应用程序必须通过驱动程序访问设备应用程序使用的接口只有open/read/write/ioctl等。 如上图所示APP就是应用程序在Linux下一切皆文件所以设备在Linux内核中都被抽象成了一个文件所以应用层必须通过系统调用接口通过文件系统来访问设备。 再来看我们要实现的设备子系统目前暂时将其分为两层虚线之上写出统一的应程序接口虚线之下根据不同的系统调用不同的函数这样一来就统一了设备的访问方式。
对于开发应用程序的人他不关心LED使用哪个GPIO引脚GPIO是输出高还是低来控制LEDopen的是什么设备、read/write的又是什么设备也不用去看原理图芯片手册以及研究HAL库RT-Thread或者Linux的驱动函数怎么调用。
甚至不用理解我们抽象出来的设备结构体只需要关心结构体中的Init和Contral两个函数指针怎么使用即可其他的一概不用关心所以非常有必要提供更高层的API接口。
以LCD的使用为例可以分为三层 如上图其中设备驱动层中的驱动程序负责提供像素操作的功能但是显示什么字符、显示多大、在哪显示不用关心。
文字/图像显示层中包含库函数/函数功能提供显示字符、显示图片功能的接口但是显示什么字符、显示多大、在哪显示不用关心。
应用程序则通过库函数来显示字符以及图片并且决定显示什么字符显示在哪里显示多大等问题但是并不用关心驱动程序。 驱动只提供功能不提供策略应用程序只提供策略不提供功能的实现。不同的层要各司其职不要越界实现不同专业知识的隔离。 在实现设备子系统的时候使用面向对象的思想对于每一种设备抽象出一个结构体结构体里有设备相关的函数指针不同设备不强求统一不强求用一个结构体类型来支持所有设备。
编写函数时要注意
头文件这些函数是面向应用程序开发者假设他们对硬件一无所知。C文件函数内部再根据不同系统、不同芯片调用其他函数。
LED设备 如上图所示我们把LED设备分为4层接下来就是一层一层来实现。
设备层 LED设备的功能 LED的开和关设置LED的颜色设置LED的亮度 虽然本喵使用的LED灯并不支持设置颜色和直接设置亮度但是有一些高级的LED灯是支持的这里我们为了以后更好的维护和扩展所以在抽象LED设备结构体的时候都考虑上 如上图所示抽象出来的LEDDevice结构体就是用来描述LED设备的包含LED编号初始化和控制方法以及设置颜色和亮度的方法还需要在LED的设备层提供一个获取LED设备的方法。 如上图所示因为LED设备只有三个所以通过一个全局的LEDDevice数组来管理LED设备对每一个LED设备进行了初始化实现了LED设备的初始化函数以及控制函数为了达到分层的目的在函数内部调用内核抽象层的初始化和控制函数。
至于设置颜色和亮度的函数并没有实现因为本喵使用的LED灯并不支持这两种功能还实现了一个获取LED设备的函数在上层使用访问LED设备的时候必须先调用该函数获取LED设备。
内核抽象层 如上图无论是访问LED设备还是控制LED设备甚至初始化LED设备在内核抽象都有不同的方式这里定义一个配置文件用来控制是使用哪种方式: 如上图所示用到哪种内核就定义相应的标识符常量。 如上图所示头文件提供了内核抽象层对LED设备初始化和控制的函数声明。 如上图所示内核抽象层源文件提供了LED设备初始化和控制函数的实现通过宏开关来控制调用对应内核下的LED初始化和控制函数。
其中对于RT-Thread和Linux下的LED设备初始化和控制函数本喵在这里并不会实现这里仅仅是为了给大家演示本喵的代码架构写的。 对于裸机和FreeRTOSLED设备的初始化和控制函数都是一样的。 芯片抽象层 如上图在真正访问芯片的时候不同类型的芯片支持不同的库同样可以通过宏开关来控制使用哪种库来访问芯片
#define CONFIG_SUPPORT_HAL 0x10
//#define CONFIG_SUPPORT_OTHER 0x20将这部分代码加入到config.h中去。 如上图头文件代码提供了芯片抽象层LED设备初始化和控制的函数声明。 如上图源文件代码提供了芯片抽象层LED设备初始化和控制函数的实现对于HAL库调用对应的HAL库对应的初始化和控制函数对应其他库则调用其他方式。
硬件操作
本喵使用的是ST的芯片所以使用HAL库来操作硬件 如上图所示头文件提供了LED设备的GPIO信息以及初始化和控制的函数声明。 如上图所示源文件提供了初始化和控制函数的实现在控制函数中switch通过LEDDevice结构体中的LED编号操作具体的LED设备。
单元测试
LED设备从设备层到操作硬件共五层已经实现了下面就进行单元测试看是否符合预期 如上图所示LED设备的测试函数其中LED点亮和熄灭中间的延时为了应用层和HAL库的解耦本喵只是通过循环进行了一个大概的循环有兴趣的小伙伴可以按照五层的框架在最底层调用HAL_Delay()自己实现一下。 如上图白蓝绿三个灯在不停闪烁。
显示设备
为了让这里的代码更加容易维护和扩展适用于多种类型的显示设备本喵通过LCD显示的原理来抽象出一个具有普适性的显示设备结构体虽然本喵最终用的是OLED屏幕但是仍然可以用这个结构。
LCD显示原理 什么是LCD就是多行多列的像素点
对于黑白屏幕(单色屏幕)这些像素点只有2个状态点亮、熄灭。对于彩色屏幕这些像素点有颜色可以用RGB三原色来表示。
怎么控制LCD上每个像素的状态
通过显存就是一块内存也被称为FrameBuffer。每个像素在显存上都有对应的数据会在屏幕上对应位置显示。 对于黑白屏(单色屏)每个像素点在显存里有对应的1位数据只有两种状态表示点亮和熄灭。对于彩色屏每个像素点在显存里有几个字节(可能是1字节、2字节、4字节)可以表示多种颜色。BPPBits Per Pixel用来表示每个像素点有多少位数据。 LCD可能自带显存也可能不带有显存(要使用LCD的话就需要在系统内存中分配显存)有三种类型的LCD。
LCD含有显存, CPU通过I2C协议访问显示 如上图很多I支持I2C、SPI接口的屏幕本身是含有显存的要在LCD上显示文字、图片就需要网显存里写入数据程序通过I2C接口写显存LCD控制器就会让LCD屏幕显示相应内容。
LCD没有显存, LCD控制器从内存得到数据 如上图很多TFT LCD本身是没有显存的那么数据保存在系统内存里分配一块空间这块RAM中的内存就可以看作是显存。设置好LCD控制器后它就会自动从这个显存取出数据、发送给LCD我们只需要写数据到RAM中的显存即可。
LCD含有显存, CPU可以直接访问 如上图有些LCD含有显存并且CPU可以直接访问显存不需要I2C灯协议就像访问一般内存一样直接访问显存我们只需要写数据到显存即。 对于软件来说这3种LCD都有显存第1种无法直接写显存第2、3种可以直接写显存能否让第1种LCD能否也直接写显存可以
在系统内存RAM中分配另一个显存FrameBuffer。应用程序直接写这个显存。设备层等下层再通过I2C将RAM显存中的数据发送到LCD自带的显存中。 如上图同样按照五层来实现程序。
管理及设备层
在设备层要抽象出一个显示设备的结构体根据前面的分析以及LED设备的经验该结构体包含
初始化函数用来初始化显示设备。在RAM中都有显存通过显存的起始地址显示设备的分辨率以及bpp来描述显存。对于第一种LCD还需要一个Flush函数把RAM显存中的数据刷到LCD显存中去。 如上图头文件中代码抽象出了显示设备的结构体用这个结构体可以描述多种类型的显示设备包括LCD和OLED这些显示设备可以用链表管理起来。 void* FBBase显示设备的显存。 如上图所示源文件创建了一个静态的全局显示设备链表用这个链表来管理所有显示设备提供了向链表中注册新设备的函数以及从链表中获取设备的函数。 由于pDisplayDevice链表头指针被static修饰了就只能在当前源文件访问和操作这个链表。使用__修饰的函数表明该函数会在其他位置被调用而且要实现的功能相同。 此时设备层的宏观描述已经有了接下来就是创建具体的显示设备了这里创建的是OLED屏。 如上图所示创建一个全局的DisplayDevice变量用来实例化OLED设备根据OLED的特性填充该结构体变量其中RAM中的显存就是一个全局的数组用static修饰的初始化和刷新显存的函数初始化结构体中的方法。
对于点亮(X , Y)处像素的函数本喵单独讲解一下 如上图所示坐标系便是显存抽象出来的坐标其中左上角的坐标原点就是RAM显存数组的首地址本喵使用的OLED是128*64的所以可以分为8页128列。
现在要点亮坐标(x, y)处的像素点所以要计算出该像素点位于RAM显存中的哪个位置然后在程序中向该位置写入数据即可。
共有64行像素点每8行分为一页所以(x, y)所在页为page y / 8由于RAM中的显存是一个数组它并不存在页的概念所以可以计算出(xy)对应那个字节在数组中的下标是page*128 x地址就是FrameBuffer page * 128 x由于一个字节有8个bit而每一个像素点用一个bit所以(x, y)所用那个bit在该字节中是第y % 8个bit。 如上图代码所示在将(x, y)坐标所对应的显存位置计算出来后根据dwColor颜色值来决定是点亮该像素点函数熄灭或者是用哪种颜色点亮。 OLED不支持显示不同颜色所以dwColor的值不为0则表示点亮为0则表示熄灭。 如上图所示代码便是OLED设备剩下的代码在添加OLED显示设备的时候用到了static修饰的g_DisplayDevice全局变量该变量同样只能在该源文件内使用。
AddDisplayDeviceOLED函数中调用的DisplayDeviceRegister注册函数是属于display_device.h文件中的所以需要包含该头文件。
在display_device中需要有添加显示设备的函数AddDisplayDevices在该函数中势必会调用oled_device.h中的AddDisplayDeviceOLED函数所以要包含该头文件。 此时就产生了互相包含的场景display_device中包含oled_device.holed_device中包含display_device.h。 为了避免互相包含再创建一个dispaly_system.c文件来管理这两个导致互相包含的函数。 如上图所示在这里实现AddDisplayDevices更加合适而获取设备的方法也是放在这里更合适。 如上图所示便是这些文件中的内容概述和调用关系这些所有内容构成了整个设备层。
内核抽象层
内核抽象层需要实现的只有显示设备的初始化已经将数据刷新到OLED自带的显存中的Flush函数。 如上图代码同样为了方便或者和维护对于不同的内核调用不同的方式本喵使用的是裸机所以需要调用芯片抽象层对应的函数。
芯片抽象层及硬件操作
芯片抽象层同样是分为支持HAL库和支持其他库只是需要实现初始化函数和CAL_OLEDDeviceFlush函数即可。 如上图所示对于HAL库在初始化的时候直接初始化I2C协议用到的引脚以及调用OLED屏幕的初始化函数这里部分内容本喵在I2C通信协议 | OLED屏一文中有详细讲解这里本喵就直接使用了。
在CAL_OLEDDeviceFlush函数中对于HAL库调用OLED_Copy拷贝函数将数据从RAM显存中搬运到OLED自带的显存中该拷贝函数本喵没有实现过这里实现一下 如上图将RAM显存中的数据分八次写入OLED自带的显存中每次写入一个page的数据也就是128字节。
单元测试
显示设备的五层实现以后同样需要进单元测试 如上图所示在测试显示设备的时候先添加所有显示设备(本喵这里只有一个OLED显示设备)然后从设备管理列表中获取名为OLED的设备如果不存在则打印错误信息。
获取成功后先调用显示设备的初始化函数进行初始化然后清屏就是将RAM中的显存全部清0再调用显示设备中的SetPixel设置像素函数在OLED屏幕上画一个十字。 如上图所示在OLED屏幕上显示了一个十字。
风扇设备 如上图所示在本喵的开发板上有一个电机模块上面有一个风扇可以控制它顺时针转逆时针转停止。 如上图所示是电机接口模块示意图风扇的控制非常简单只需要改变PC6和PE6也就是INB和INA口的电平状态就可以真值表如下
风扇状态INAINB顺时针旋转01逆时针旋转10停止00停止11
其中0表示该接口输入低电平1表示该接口输出高电平。风扇设备分为四层本喵就不详细讲解了直接上代码。
设备层 如上图所示头文件代码定义了描述风扇设备的结构体包含风扇的速度属性初始化函数设置速度的函数当前速度属性是多余的本喵这里并不会控制速度包括设置速度函数也只是控制风扇的启停和正反转。
如果使用PWM波来控制电风扇就可以控制其速度所以为了以后方便扩展维护本喵就保留了速度这个属性。 如上图所示是源文件创建了一个全局的风扇设备变量来实例化并进行了初始化还提供了获取风扇设备的函数不再多说。
内核抽象层 如上图所示对于不同的内核调用相应层的函数即可不再讲解。
芯片抽象层 如上图代码在初始化函数中对于HAL库由于在使用CubeMX创建工程的已经配置好了引脚所以这里不用再调用HAL库的初始化函数了直接返回0即可表示初始化成功对于设置速度的函数则需要调用HAL库中的设置速度函数。
硬件操作 如上图代码所示根据iSpeed的不同情况对照风扇的真值表设置INA和INB引脚的电平状态即可。
单元测试 如上图所示测试代码让风扇顺时针转2秒停止2秒逆时针转2秒停止2秒如此循环。
为了实现应用层的独立延时函数使用的是KAL_Delay其最在芯片抽象层仍然是封装的HAL_Delay。 如上图代码所示实现了延时函数头文件自己加就行本喵不在讲解。 如上图所示是风扇转起来的状态符合我们的要求。