嵌入式设计中,
一个完整的程序,由大量的函数构成。这些函数,可能分为不同的组,每个组的函数,可能只关注于一些相关的数据对象,对它们进行读写。
从嵌入式工程设计的角度来看,
大体上,分为数据设计和处理设计两大块。
数据设计的目的,是为了更方便的进行处理设计。
本文重点讨论处理设计。
处理设计,具体而言,就是函数的设计。
一个完整的程序,被按照结构化设计的要求,划分为大量的函数。
这些函数被划分的依据原则,就是之前所述的数据交易。
而决定这些函数被划分的考虑的出发点,是函数的职能定位。
基于函数的职能定位,划分出函数后,才能更明确的界定出数据交易。
从函数布局的角度看来,由于每个函数具有自己的职能定位,所以才有了程序的组织架构的概念。
++++++++++++++++++++++++++++++++++++++++++
程序架构设计,有多种方法。
在嵌入式设计中,最常用的有几种架构,
例如,前后台架构,分层架构,散集架构,等等。
在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_ 。