当前位置:首页 > 技术学院 > 技术前线
[导读]使用独立的Driver线程并不是逻辑解耦的唯一实现方法。现代处理器一般都会提供一类特殊的SVC中断,作为System Service的入口。通过指定的SVC请求号,即可在SVC中断上下文中调用指定的驱动程序。

我个人理解,嵌入式应用设计中,应用(App)和驱动程序(Driver)的解耦可以分为三个层次:

1. 构建解耦:提高代码可读性

合理设计头文件和函数接口,将驱动和应用层的代码(*.c)分离编译。代码中的调用关系保持不变。

将App和Driver在文件的层面上分离,有助于提高代码可读性。但是经过编译-链接环节后,显式的调用关系会让App和Driver在二进制层面是混合在一起的。

2. 二进制解耦:接口抽象(隐藏不必要信息,提高可移植性)

通过函数指针和注册机制,将驱动程序和应用层在二进制层面上分离。应用层通过函数指针,以查找并跳转的方式执行驱动程序。

注册机制实质上就是查找表(Look Up Table)。App和Driver可以单独被链接到不同的地址,但是通过指定的函数指针查找并跳转,二者依然可以建立调用关系。

但是由于App和Driver间的调用关系从未真正解除,二者的代码会运行在同一个程序上下文中。我们很难高效地在同一个程序上下文中,分离App和Driver的代码,有针对性地部署安全策略。在App/Driver的API中插桩虽然可以实现权限控制的需求,但无论是开发效率,还是执行效率,都非常低下。当系统规模逐渐膨胀、逻辑逐渐复杂的时候,这种实现方式对效率的影响是灾难性的。

3. 逻辑解耦:程序安全(分离执行上下文,提高安全性)

应用层和驱动层仅通过结构化信息,传递驱动请求和响应。由于二者间不存在显式/隐式的调用关系,所以驱动层和应用层可以运行在不同的地址空间,使得可以适应相对复杂安全设计。

举个例子,App线程和Driver线程,以某种IPC方法传递一类自描述结构体。在这类自描述结构体中,应当能够描述App线程需要的一系列驱动动作请求。当Driver线程拿到自描述结构体时,就会根据其中所描述的动作,调用指定的驱动代码完成指定的动作,并将结果以类似的方式传回App线程。这种设计使得App和Driver间不存在任何调用关系,并且由于App和Driver运行在不同的程序上下文,易于实现更有效的安全机制。我们可以使用MMU或者MPU这类硬件,非常轻松地划定App和Driver代码的运行权限。通过在Driver线程中实现更完备的安全检查,程序可以更有效地拦截并处理空指针、非法数据格式这类异常,提高系统健壮性。

当然,使用独立的Driver线程并不是逻辑解耦的唯一实现方法。现代处理器一般都会提供一类特殊的SVC中断,作为System Service的入口。通过指定的SVC请求号,即可在SVC中断上下文中调用指定的驱动程序。

但这种设计方法也不是没有代价的。由于消除了调用关系,这种方法中的驱动请求和驱动执行是异步关系。对于高频执行,或者高实时性要求的驱动需求来说,异步机制引入的不确定性是需要谨慎斟酌的。

需要说明的是,上述三类解耦的设计方法是逐渐递进,而不是并列或者互斥的关系。

在构建解耦的基础上,实现二进制解耦有助于提高程序的抽象程度,从而提高程序的可移植性。

而在构建解耦、二进制解耦的基础上,实现**逻辑解耦**则有利于进一步地提高程序的健壮性。

我们通常认为,在中断中,不能执行耗时的操作,否则会影响系统的稳定性,尤其对于嵌入式编程。对于带操作系统的程序而言,可以通过操作系统的调度,将中断处理分成两个部分,耗时的操作可以放到线程中去执行,但是对于没有操作系统的情况,又应该如何处理呢

比较常见的,我们可能会定义一些全局变量,作为flag,然后在mainloop中不停的判断这些flag,再在中断中修改这些flag,最后在mainloop中执行具体的逻辑,但是这样,无疑会增加耦合,增加程序维护成本。

cpost

cpost正是应用在这种情况下的一个简单但又十分方便的工具,它可以特别方便的进行上下文的切换,减少模块耦合。

cpost借鉴的Android的handler机制,通过在mainloop中跑一个任务,然后在其他地方,可以是中断,也可以是模块逻辑中,直接抛出需要执行的函数,使其脱离调用处的上下文,运行在mainloop中。cpost还支持延迟处理,可以指定函数在抛出后多久执行

使用

cpost的使用十分简单,这里以使用在嵌入式无操作系统中为例,主要用作中断延迟处理的情况

1、配置系统tick

配置cpost.h中的宏CPOST_GET_TICK,配置成获取系统tick,以stm32 hal为例

# defineCPOST_GET_TICK HAL_GetTick

2、配置处理进程 在mainloop调用cpostProcess函数

intmain( void)

{

...

while( 1)

{

cpostProcess;

}

return0;

}

3、抛出任务

在中断等需要进行上下文切换的地方调用cpsot接口,使其在mainloop中运行

cpost(intHandler);

原理解析

cpost的原理其实很简单,其代码量也十分少,总共加起来就只有几十行代码,cpost维护了一个而全局的数组

CpostHandler cposhHandlers[CPOST_MAX_HANDLER_SIZE] = { 0};

其中,数组的每一个元素表示包含了需要执行的函数和参数,当调用cpost接口时,被post的函数和参数会被保存在这个数组中,然后mainloop中运行的cpostProcess函数会遍历这个数组,当满足条件时,执行对应的函数,从而达到上下文切换的目的

voidcpostProcess( void)

{

for( size_ti = 0; i < CPOST_MAX_HANDLER_SIZE; i++)

{

if(cposhHandlers[i].handler)

{

if(cposhHandlers[i].time == 0|| CPOST_GET_TICK >= cposhHandlers[i].time)

{

cposhHandlers[i].handler(cposhHandlers[i].param);

cposhHandlers[i].handler = NULL;

}

}

}

}

其实,cpost的方式,和一开始提到的使用全局的flag进行上下文切换的方法很像,只不过,cpost通过一个数组的维护和直接post函数的方式,省去了维护flag的成本,也不需要将需要执行的函数耦合到mianloop中,从而变得简单易用。

完美解耦 - cevent应用

对于模块化编程来说,如何实现各模块间的解耦一直是一个比较令人头疼的问题,特别是对于嵌入式编程,由于控制逻辑复杂,并且对程序体积有控制,经常容易写出各独立模块之间相互调用的问题。由此,cpost中的cevent组件,通过模仿Android系统中的广播机制,提供了一种非常简单的模块间解耦实现。

原理

cevent借鉴的是Android系统的广播机制,一方面,各模块在工作的时候,都会有多个具体的事件点,在高耦合的编程中,可能会在这些地方调用其他模块的功能,比如说,在通信模块接收到指令的时候,需要闪烁一下指示灯。

使用cevent,我们可以在这些地方抛出一个事件,当前模块不需要关心在这各地方需要执行哪些其他模块的逻辑,由其他模块,或者用户定义一个事件监听,当具体的事件发生时,执行相应的动作。

使用

cevent使用注册的方式监听事件,会依赖于编译环境,目前支持keil,iar,和gcc,对于gcc,需要修改链接文件(.ld),在只读数据区添加:

_cevent_start = .;

KEEP (*(cEvent))

_cevent_end = .;

1、初始化cevent

系统初始化时,调用ceventInit

ceventInit;

2、注册cevent事件监听

在c文件中,调用CEVENT_EXPORT导出事件监听

CEVENT_EXPORT( 0, handler, ( void*)param);

3、发送cevent事件

在事件发生的地方,调用ceventPost抛出事件

ceventPost( 0);

使用cevent解耦模块初始化

嵌入式编程中,我们习惯会在程序启动的时候,调用各个模块的初始化函数,其实这也是一种耦合,会造成main函数中出现很长的初始化代码,借助cevent,我们可以对初始化进行优化解耦。

1、定义初始化事件

定义初始化事件的值,对于初始化,有些模块可能会依赖于其他模块的初始化,会有一个先后顺序要求,所以这里我们可以把初始化分成两个阶段,定义两个事件,当然,如果有更复杂的要求,可以再多分几个阶段,只需要多定义几个事件就行

# defineEVENT_INIT_STAGE1 0

# defineEVENT_INIT_STAGE2 1

2、初始化cevent,抛出事件

在main函数中初始化cevent,并抛出初始化事件

intmain( void)

{

...

ceventInit;

ceventPost(EVENT_INIT_STAGE1);

ceventPost(EVENT_INIT_STAGE2);

...

return0;

}

3、注册事件监听

对所有需要初始化的函数注册事件监听,这里我以对letter-shell注册事件监听为例,分为两个部分,初始化串口和初始化shell。

在serial模块中,将串口初始化注册到初始化第一阶段,cevent支持将不大于7个的参数直接传递到注册的监听函数中,下面的注册方式,相当于在EVENT_INIT_STAGE1事件发生的地方,也就是main函数中对应的位置,调用serialInit(&debugSerial)

CEVENT_EXPORT(EVENT_INIT_STAGE1, serialInit, ( void*)(&debugSerial));

然后再shell模块中,将shell初始化函数注册到初始化第二阶段。

CEVENT_EXPORT(EVENT_INIT_STAGE1, shellInit);

使用cevent解耦mainloop

再无操作系统的嵌入式编程中,我们如果同时希望运行多个模块的逻辑,通常是在mainloop中循环调用,这种将函数写入mainloop的做法,也会增加耦合

intmain( void)

{

...

while( 1)

{

// 写在mainloop中的模块逻辑

shellTask(&shell);

LedProcess;

...

}

return0;

}

通过使用cevent,也可以很方便的消除这种耦合

1、定义mainloop事件

定义mainloop事件的值

# defineEVENT_MAIN_LOOP 3

2、在mainloop中抛出事件

去掉mainloop中对其他模块的调用,改为排除mainloop事件

intmain( void)

{

...

while( 1)

{

ceventPost(EVENT_MAIN_LOOP);

}

return0;

}

3、在各模块中注册事件监听

分别在各个模块中,注册对mainloop事件的监听

CEVENT_EXPORT(EVENT_MAIN_LOOP, shellTask, ( void*)(&shell));

CEVENT_EXPORT(EVENT_MAIN_LOOP, LedProcess);

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭