程序框架设计示例2_RTOSday3

本文介绍了程序设计的层次,从软件系统到子系统、类和子程序的拆分,强调了子系统独立性。通过案例展示了如何将系统拆分为输入/输出/业务子系统,并以LCD显示为例,详细说明了驱动程序、库函数和应用程序的职责分离。此外,文章还讨论了结构体在系统设计中的重要性,以及程序设计的原则,如头文件和源文件的功能区分,模块化和代码组织。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

程序框架设计示例2_RTOS

报名了百问网的七天物联网课程通过两天的学习确实收获很多。下面几天我会陆续把每节课程的重点、难点以及自己的理解记录下来。

1. 系统拆分:理论介绍

在《代码大全》第5章中,把程序设计分为这几个层次:

  • 第1层:软件系统,就是整个系统、整个程序

  • 第2层:分解为子系统或包。比如我们可以拆分为:输入子系统、显示子系统、业务系统

    子系统如何实现,要使用面向对象的思想抽象成结构体。

  • 第3层:分解为类。在C语言里没有类,可以使用结构体来描述子系统。

  • 第4层:分解成子程序:实现那些结构体(结构体中有函数指针)

在这里插入图片描述

2. 怎么拆分为子系统

2.1 拆分原则

上一次的裸机是把系统分为按键系统和LED灯系统。各个子系统要尽可能独立。

2.2 按照数据流向分:输入/输出/业务

在这里插入图片描述

输入来源很多种:

2.3 根据输入源来细分输入部分

又可以细分为:用户输入、传感器、远程控制等等。

在这里插入图片描述

2.4根据输出 细分输出部分

又可以细分为:显示屏、控制各类设备、数据保存等等。

在这里插入图片描述

2.5 输入、输出的关系:业务

在这里插入图片描述

2.6 驱动和应用分开

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

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

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

  • 驱动程序:
    • 提供像素操作的功能
    • 但是怎么显示字符、显示多大、在哪显示,这不关我的事
  • 库函数/功能函数:
    • 提供显示字符、显示图片的功能
    • 但是显示什么字符、在哪显示,这不关我的事
  • APP:
    • 使用库函数来显示字符、显示图片
    • 我甚至不需要看驱动程序
      在这里插入图片描述

2.7 举例

在这里插入图片描述
在这里插入图片描述

用户小程序来控制开发板上的灯、风扇。

1.在LCD上显示字符A:

在这里插入图片描述

三种硬件结构来实现LCD显示。现在我要设计一种显示系统,要能够支持这三种结构。

//构造一个结构体能够支持所有的LCD设备。
Display_dev{
	char *name;
    int ixres; //reselution x方向上的分辨率
    int iyres; //y方向上的分辨率
    int iBpp; // 第一种与第三种可以确定一个基地址(Base Adder)
    //但是对于第二种,地址是通过IIC访问的,无法直接访问,那如何处理:
    //在Cpu中划分出一个内存地址。分配出一块临时内存,可以获得基地址。在刷到上面。
    char *FramBuffer;
    //对于第二种我们要刷新,因此要定义这个刷新函数。
    void (*FlashFB)() ;//第二种情况要临时缓存,刷新到显存中去。
    
}

在这里插入图片描述

首先由四个文件:framebuffer_manager.c/framebuffer_manager.h/mylcd.c/oled.c

//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;
//framebuffer_manager.c
//管理层

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;
}
// 返回值: 0表示被按下, 1表示被松开
static int read_key1(void)
{
	static GPIO_PinState pre_key;
    GPIO_PinState key;
    key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);

	/* 按键无变化 */
	if (key == pre_key)
		return -1;

	/* 按键发生了变化 */
	pre_key = key;
    if (key == GPIO_PIN_RESET)
        return 0xA | (1<<7);
    else
        return 0xA;
}


static key k1 = {"k1", 0xA, NULL, read_key1};

void k1_init(void)
{
	register_key(&k1);
}

void k1_isr(void)
{
    GPIO_PinState key;
    key = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_6);

	/* 按键发生了变化 */
    if (key == GPIO_PIN_RESET)
        put_buf(0xA | (1<<7));
    else
        put_buf(0xA) ;
}
//oled.c文件
//

//初始化函数
static void oled_init(DisplayDev *dev)
{
}
//刷新函数
static void oled_flush(DisplayDev *dev)
{
}

static char oled_fb[1024];//128*64/8(1位=8bit) = 1024

static DisplayDev oled = {
	"oled",  //名字
	128,	//x方向
	64,		//y方向
	1,		//每个像素用1位来表示
	oled_fb,
	oled_init,//函数
	oled_flush,//函数
};

void oled_init(void)
{
    //注册在管理层写。
	register_display_dev(&oled);
}

文件夹:

./main
./Framebuffer
	/framebuffer_manager.c
	/framebuffer_manager.h
	/mylcd.c
	/oled.c

把上面的配置定义完成后,如何去使用呢,写一个main.c文件

//main.c文件
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);

    //设置完后我就刷新一下。
	pdev->FlushFB(pdev);
}

以上实现了显示设备的驱动与使用,下面设计显示系统。如图所示

在这里插入图片描述

硬件方向上:显示设备:精力放在硬件操作上。如何设计信号,存储,传输。

显示系统:精力放在像素上

字体系统:精力放在‘A’___字体。类型。

下面演示显示系统的代码实现:

首先是文件夹安排:

./main
./framebuffer
	/framebuffer_manager.c
	/framebuffer_manager.h
	/mylcd.c
	/oled.c
./display 
	/display.c
//display.c
//这个函数跟硬件没什么关系。
/* color: 0x00RRGGBB */
//设置像素。设置DisplayDev *pdev显示设备的像素,坐标是(x,y),设置成什么颜色。
int SetPixel(DisplayDev *pdev, int x, int y, int color)
{
    //首先找到了基地址,然后根据地址确定位置。基地址等于Framebuffer;
	char *base = pdev->Framebuffer;
    //偏移值是y行*每行占得字节数(像素)+x个像素
    //这个位置的前面有这么多的像素 ,每一个像素占据多少位?
    //偏移值*iBpp/8
	int offset = (y*(pdev->iXres)+x)*pdev->iBpp/8;
//当y坐标为零,x坐标对应0-7是一个字节,8-15。。。    
//假设初始坐标是(0,0),一个坐标是(x,y)找到他的offset是多少:
//  首先:y前面从0——(y-1)相当于有y行 。每一行有多少像素呢?y*(pdev->iXres)
//然后第0——(x-1)像素相当于有x列个像素。
    //那么在(x,y)坐标之前, 有多少个像素呢,就相当于一个小块的面积,如图所示
    //(y*(pdev->iXres)+x)个像素,每个像素占据多少位,要除以8.
    
	char *data;
	char *dataw;
	int bit = x % 8;

	data = &base[offset]; //data这个指针指向base这个基地址,偏移值是offset。
	dataw = &base[offset]; 
    
//怎么操作这个基地址呢。判断多少位。
	switch (pdev->iBpp)
	{
       //每个像素1位你如何操作
		case 1:
		{
			if (color)//不等于零表示要去点亮他。
				data |= (1<<bit);
			else
				data &= ~(1<<bit);//否则清掉。
			
			break;
		}
       //每个像素8位你如何操作
		case 8:
		{
			*data = color;
			break;
		}
       //每个像素16位你如何操作
		case 16:
		{
			*dataw = color;
			break;
		}
	}
}

在这里插入图片描述

3. 子系统的核心:结构体/类

在Linux开发、Linux APP开发中,类/结构体时核心。

能抽象出这些类/结构体,就体现了你的编程能力。

为什么?

  • 面向对象:用结构体来表示一个功能,比如输入、输出、网络传输、数据处理

  • 有什么优势?程序可以使用模块化涉及,容易维护、容易扩展、容易升级

  • 举例

在这里插入图片描述

3.1 输入事件

按键、触摸屏、网络数据,数据完全不一样!

怎么统一?

在这里插入图片描述

3.2 输入设备

谁产生输入事件?

输入设备!

里面有哪些函数?初始化、获得数据。

在这里插入图片描述

3.3 输入缓冲区

多个输入设备,它们产生的数据放在哪里?用环形缓冲区来保存。

在这里插入图片描述

4. 程序设计原则

  • 头文件、源文件功能时不一样的
    • 头文件:声明,给别人引用的
      • 用不到的static函数,就不要放在头文件里声明
      • 函数接口容易看懂
    • 源文件:定义,实现函数
      • 功能独立
      • 尽量不使用别的模块
  • 不要太长
  • 功能太大的话,拆分为子函数
  • 全局变量不要暴露出去

4.1 举例

在这里插入图片描述

数据。

[外链图片转存中…(img-UPzVGzQ2-1648726428959)]

3.3 输入缓冲区

多个输入设备,它们产生的数据放在哪里?用环形缓冲区来保存。

4. 程序设计原则

  • 头文件、源文件功能时不一样的
    • 头文件:声明,给别人引用的
      • 用不到的static函数,就不要放在头文件里声明
      • 函数接口容易看懂
    • 源文件:定义,实现函数
      • 功能独立
      • 尽量不使用别的模块
  • 不要太长
  • 功能太大的话,拆分为子函数
  • 全局变量不要暴露出去

4.1 举例

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值