从职能定位看待程序架构

嵌入式设计中,
一个完整的程序,由大量的函数构成。这些函数,可能分为不同的组,每个组的函数,可能只关注于一些相关的数据对象,对它们进行读写。

从嵌入式工程设计的角度来看,
大体上,分为数据设计和处理设计两大块。
数据设计的目的,是为了更方便的进行处理设计。
本文重点讨论处理设计。

处理设计,具体而言,就是函数的设计。

一个完整的程序,被按照结构化设计的要求,划分为大量的函数。
这些函数被划分的依据原则,就是之前所述的数据交易。
而决定这些函数被划分的考虑的出发点,是函数的职能定位。
基于函数的职能定位,划分出函数后,才能更明确的界定出数据交易。

从函数布局的角度看来,由于每个函数具有自己的职能定位,所以才有了程序的组织架构的概念。

++++++++++++++++++++++++++++++++++++++++++
程序架构设计,有多种方法。

在嵌入式设计中,最常用的有几种架构,
例如,前后台架构,分层架构,散集架构,等等。
在SOD设计或者OOD设计中,内聚已经是最基本的要求,所以,现在已经不把散集架构作为单独的架构来考虑,默认,所有模块都是使用了内聚的。
如果是SOD,则将stuct和对应的操作集内聚为一个模块。
如果是OOD,则直接内聚为一个class。

本文重点从职能定位的角度,看待程序的架构问题。

首先来看看分层架构的职能定位,
不同层的函数,职能定位的侧重点不同,同层的函数,职能定位类似。
分层架构,最常见于协议栈的处理程序中,
每一层的函数,对应于协议栈的某一个层。

再来看看前后台架构的职能定位,
大体上,分为前台函数和后台函数,前台函数,通常是由interrupt触发后,才会执行,所以,前台函数,也经常被称为前台ISR函数。后台函数,则是在main中调用的函数。

在一个较简单的嵌入式程序中,
在ISR中直接处理前台需要的业务代码。
在一个较复杂的嵌入式程序中,一般则是在init时注册callback,在ISR中,找到event对应的callback来调用处理。
通常会存在一些机制类的响应框架级ISR函数,完成一些ISR相关的通用处理,例如查表,优先级调度,ISR排序等,之后,会轮询调用注册的callback,来处理对应的event。
在这个背景下,callback就是我们实际需要关注的前台函数。
当callback被调用时,就表明对应的event被触发了。

在SOD或者OOD设计中,
前台callback函数,其职能定位就是,依据触发event,更新相关的数据对象,例如flag,buffer等。
后台函数,会轮询相关的数据对象,例如flag,buffer等,进行后续的处理。

前台函数,只完成短平快的工作,例如事件响应,接口收发。
后台函数对数据对象的处理,是最主要的处理设计。

+++++++++++++++++++++++++++++++++++++++++
本文对职能定位的讨论,重点在于后台函数。

从职能上看,大体上可以分为,流程框架级函数,业务级函数,功能级函数,操作级函数,IO级函数。

流程框架级函数,并不包含特定的处理代码,而只是包含对于业务级函数的调用。
常见的框架级函数,是一个轮询函数。使用永续循环体作为函数的主体。

业务级函数,包含一些特定的业务处理代码,更主要的是,通过调用功能级函数,来完成业务逻辑的数据处理工作。

功能级函数,包含一些特定的功能运算代码,通过调用操作级函数,来读取或者写入系统内的硬件资源,获取数据后,进行运算处理。
通常会在功能级,进行函数的实义化封装,将未实义化的操作序列,封装到实义化函数中,通过函数名,就可以了解到函数实现的功能的现实含义,提高可读性。
例如:

void SystemManager::isp_ap_reset(XGpio* instance)
{
	XGpio_DiscreteWrite(instance, 1, 3);
	usleep(1000);
	XGpio_DiscreteWrite(instance, 1, 0);
	usleep(1000);
}

通过实义化封装,使得设计者通过函数名即可很快了解到函数功能的现实含义。

操作级函数,包含一些特定的时序操作代码,通过调用IO级函数,对硬件资源的REG进行读取或者写入,从而完成从硬件资源中获取数据的任务。
通常会在操作级,进行函数的实义化封装,将未实义化的读写地址,读写数据,读写参数等,封装到实义化函数中,通过函数名,就可以了解到函数实现的操作的现实含义,提高可读性。
例如:

	void set_awb_R_MIN_I(u16 value) {
		regs_.set_value(33, value, FPGA_AWB_R_MIN_I_MASK, FPGA_AWB_R_MIN_I_SHIFT);
	}

通过实义化封装,使得设计者通过函数名即可很快了解到函数操作的现实含义。

IO级函数,是最小颗粒度的函数划分,它的职能定位很明确,代码也很简短,就是使用合适的指令代码,完成对硬件资源的REG的读取或者写入。

+++++++++++++++++++++++++++++++++++++++++++++
下面补充说明前后台架构。
从职能定位的角度考虑,
前台callback函数,其职能定位,就是事件响应。通用的做法是,在事件响应时,更新数据对象的相关成员变量。
后台action函数,其职能定位,就是业务处理。通用的做法是,在业务处理流程中,读取被前台函数更新的数据对象的相关成员变量的值,复制到自己的局部变量中,然后再依据局部变量进行处理,处理完后,将局部变量的值更新到数据对象的相关成员变量中。

下面看几个例子。

void time_loop_isr_handle(SystemManager* sys)
{
	XIntc* xintc = &sys->intc_;

	//关闭中断
	XIntc_Disable(xintc, TIME_LOOP_IRQ_ID);

		u32 now_timestamp = sys->get_timestamp();
		sys->state_timestamp_ = now_timestamp;

	//打开中断
	XIntc_Enable(xintc, TIME_LOOP_IRQ_ID);
}

这个callback函数,被提供的callbackref,是一个SystemManager类对象,以指针形式提供。
在整个系统的数据对象拓扑中,SystemManager对象,具有最大范围的索引能力,所以,以它为callbackref,是最合适的。

这个前台函数,其职能就是在timer的事件触发时,读取当前时间戳,然后更新数据对象中对应的成员对象。

void focus_motor_isr_handle(SystemManager* sys)
{
	XIntc* xintc = &sys->intc_;

	//关闭中断
	XIntc_Disable(xintc, FOCUS_MOTOR_IRQ_ID);

		sys->focus_motor_reach_ = 1;

	//打开中断
	XIntc_Enable(xintc, FOCUS_MOTOR_IRQ_ID);
}

这个前台函数,其职能就是在focus_motor的事件触发时,更新数据对象中对应的成员对象,这里,是一个flag变量。

void trigger_isr_handle(SystemManager* sys)
{
	XIntc* xintc = &sys->intc_;
	AxiLiteIn* xin = &sys->xin_;
	AutoExpGain* xaec = &sys->xaec_;
	AutoFocus* xaf = &sys->xaf_;
	AutoFocusAccl* xaf_accl = &sys->xaf_accl_;


	//关闭中断
	XIntc_Disable(xintc, SENSOR_TRIGGER_IRQ_ID);


		//设置数字增益
		sys->xcmos_.set_digital_gain(sys->xaec_.gain_, 0);

		//读取亮度均值
		u32 bayer_sum = xin->get_bayer_sum();
		u32 bayer_cnt = xin->get_bayer_cnt();
		u32 bayer_aver = (bayer_cnt == 0) ? 0 : (bayer_sum / bayer_cnt);
		xaec->lum_level_ = bayer_aver;

		//读取清晰度评价
		xaf->current_clarity_ = xaf_accl->get_clarity_value();

		//帧计数
		sys->sensor_frame_count_++;

	//打开中断
	XIntc_Enable(xintc, SENSOR_TRIGGER_IRQ_ID);
}

这个前台函数,其职能就是在trigger的事件触发时,
1 设置增益,
2 读取硬件REG的值,计算后,用计算的结果值更新数据对象的成员变量lum_level_ 。
3 读取硬件REG的值,用读取的值更新数据对象的成员变量current_clarity_ 。

void video_isr_handle(SystemManager* sys)
{	
	XIntc* xintc = &sys->intc_;
	AxiLiteIn* xin = &sys->xin_;
	AutoExpGain* xaec = &sys->xaec_;
	AutoFocus* xaf = &sys->xaf_;
	AutoFocusAccl* xaf_accl = &sys->xaf_accl_;

	//关闭中断
	XIntc_Disable(xintc, RGB_INFO_IRQ_ID);

		//读取清晰度评价
		xaf->current_clarity_ = xaf_accl->get_clarity_value();

		//读取RGB分块均值数据
		xin->get_rgb_win_sum(xaec->win_sum_);
		u32 lum_level = xaec->calc_lum_level();
		//防止亮度震荡
		const u32 scale = 900;
		xaec->lum_level_ = lum_level;//(scale * lum_level + (1024 - scale) * xaec->lum_level_) / 1024;

		//帧计数
		sys->video_frame_count_++;
		
	//打开中断
	XIntc_Enable(xintc, RGB_INFO_IRQ_ID);
}

这个前台函数,其职能就是在video的事件触发时,
1 读取硬件REG的值,用读取的值更新数据对象的成员变量current_clarity_ 。
2 读取硬件REG的值,计算后,用计算的结果值更新数据对象的成员变量lum_level_ 。
3 读取硬件REG的值,用读取的值更新数据对象的成员变量current_clarity_ 。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值