小白能懂:嵌入式进阶:RTOS嵌入式系统框架
第一章 嵌入式常用裸机编程框架
第二章 面向对象编程基础
第三章 ESP8622物联网基础
第四章 STM32与ESP8266物联网编程
第五章 物联网编程优化
第六章 以OLED为例介绍RTOS面向对象编程
本节与本系列第二章联系较为紧密,建议一起观看
文章目录
前言
学习韦东山老师的七天物联网实战及直播课相关内容,以其课程笔记为骨,记录一下学习的过程,可能会加入一些自己的感想。
最后欢迎点赞、收藏与评论交流!
一、拆分系统
1 系统拆分原则
各系统尽可能独立,不互相干涉。
2 按照数据流向分:输入/输出/业务
2.1 输入部分
输入部分主要是系统的输入,主要包括用户输入(键盘、串口或者其他按键)、传感器(接收到外界的数据)及远城操控(PC及电脑上位机端)等等。
2.1 输出部分
输出部分主要有显示系统(显示屏或者led灯)、控制各类设备(电机或者其他的执行机构)及数据保存(flash芯片)等。
2.3 业务部分
业务系统主要是指输入输出系统之间的关系。
复杂的业务系统示例如下所示:
3 以LCD为例设计业务系统
原则:驱动只提供功能,不提供策略。 各司其职,互不干涉。
问题:如何设计一个程序在LCD屏幕上显示一个字符“A”,难点在于
- LCD的接口可能有I2C、SPI或者其他的ram读写
- A可能多种字体,或者其他的艺术字的格式?还有字体大小的不同
3.1 显示系统分层 ——设备层
-
LCD自带显存的设备,RAM接口
给定基地址baseaddr,只要写入之后,硬件会帮忙自动刷新图像。 -
LCD自带显存的设备,I2C、SPI接口
最左端为人为分配的内存,与显存大小相同,通过手动刷新的方式来显示图像。
-
计算能力强大的linux芯片,有LCD控制器,相当于芯片内部开辟了一段内存充当LCD显存。(LCD无显存,接口RAM)
给定基地址baseaddr,写入之后,显存的图像内容会被自动刷新。
3.2 显示系统分层 ——结构体
设计一款显示系统,可以支持上述1,2,3种屏幕要求,为此我们设计结构体如下所示:
DisplayDev{
char *name;
int iXres;
int iYres;
int iBpp; //(bit per prex)每个像素多少个字节显示
char *FrameBuffer; //基地址
void (*FlashFB)(); //刷显存的子函数,只在第二种屏幕中有需要;第1、3中屏幕中无需该函数
}
3.3 显示系统分层 ——显示设备框架
程序框架主要有以下方面的分层:设备层(APP),管理层和驱动层。
各层程序之间层次分明,不互相干涉。设备层主要是进行后续的算法或上层开发;管理层主要是对底层驱动的控制,控制哪些驱动可以供上层程序使用;驱动层主要是对硬件的控制与编写,只是写硬件芯片上的驱动。
伪代码程序编写如下:
1. 首先构建framebuffer_manager的结构体,
<framebuffer_manager.h>
typedef struct DisplayDev {
char *name;
int iXres;
int iYres;
int iBpp;
char *Framebuffer;
void (*Init)(struct DisplayDev *d);
void (*FlushFB)(struct DisplayDev *d);
}DisplayDev, *p_DisplayDev;
2. 驱动层相应的初始化函数
<oled.c>
static void oled_init(DisplayDev *dev)
{
}
static void oled_flush(DisplayDev *dev)
{
}
static char oled_fb[1024];
static DisplayDev oled = {
"oled",
128, //屏幕尺寸128*64
64,
1, //数组中每一个字节代表一个像素点
oled_fb, //基地址
oled_init,
oled_flush,
};
void oled_init(void)
{
register_display_dev(&oled); //注册函数的实现,将底层的驱动交给上层应用程序
}
3. 实现注册函数
int dev_cnt = 0;
DisplayDev *devs[32];
void register_display_dev(DisplayDev *dev)
{
devs[dev_cnt] = dev;
dev_cnt++;
}
void desplay_devs_init(void)
{
oled_init();
}
DisplayDev *get_display_dev(char *name)
{
int i = 0;
for (i = 0; i < dev_cnt; i++)
if (strcmp(name, devs[i]->name) == 0)
return devs[i];
return NULL;
}
4. 在main函数中编写测试程序
void main(void)
{
DisplayDev *pdev;
int size;
desplay_devs_init(); //先初始化屏幕
pdev = get_display_dev("oled"); //以名字取出相应的屏幕
pdev->Init(pdev); //初始化这个屏幕
size = pdev->iXres * pdev->iYres * pdev->iBpp / 8; //计算屏幕尺寸
memset(pdev->Framebuffer, 0, size); //初始化屏幕都为0
pdev->FlushFB(pdev); //开始刷新屏幕
}
3.4 显示系统分层 ——显示系统
在上一节3.3中主要对硬件设备的驱动框架进行了介绍,本节主要对屏幕的具体显示系统(如某个像素点的显示,文字的显示,显示字符串,或者位图)进行介绍:
/* color: 0x00RRGGBB */
int SetPixel(DisplayDev *pdev, int x, int y, int color)
{
char *base = pdev->Framebuffer; //基地址
int offset = (y*(pdev->iXres)+x)*pdev->iBpp/8; //要操作的像素点需要偏移的字节数,将一维数据转化为二维数据
char *data;
char *dataw;
int bit = x % 8;
data = &base[offset];
dataw = &base[offset];
switch (pdev->iBpp)
{
case 1:
{
if (color)
data |= (1<<bit); //对特殊的字节进行操作,到底是点亮还是熄灭
else
data &= ~(1<<bit);
break;
}
case 8:
{
*data = color; //对特殊的字节进行操作,显示特殊的颜色
break;
}
case 16:
{
*dataw = color;
break;
}
}
}
现在的程序分层如下所示:
二、freertos操作
有以下问题,一个小人在oled屏幕上一直走,单片机有一个无中断功能的按键去停止他,如何实现该功能?
2.1 裸机思维
使用定时器去实现小人的一直运动,在运动任务之前去查询一下按键,如果按键按下,就去停止小人。
2.2 freertos框架
freertos主要解决多个耗时程序之间的时间分配问题!
现在的难点主要是主要是多任务之间的通讯,任务2需要任务1的通知来停止小人的运动。
在RTOS中一般不用全局变量,一般是队列和信号量进行通讯功能。
2.3 多进程与多线程
以下属于扩展内容,他们的主要区别是:一个进程可以包含多个线程,同一个进程拥有的全局变量可以在进程中的不同线程使用。如对以下图片有疑问,请移步评论区讨论:
总结
本文主要以屏幕显示为例介绍了多任务系统的程序框架。