EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
1. 简单的前后台顺序执行程序,这类写法是大多数人使用的方法,不需用思考程序的具体架构,直接通过执行顺序编写应用程序即可。2. 时间片轮询法,此方法是介于顺序执行与操作系统之间的一种方法。3. 操作系统,此法应该是应用程序编写的最高境界。一、顺序执行法这种方法,这应用程序比较简单,实时性,并行性要求不太高的情况下是不错的方法,程序设计简单,思路比较清晰。但是当应用程序比较复杂的时候,如果没有一个完整的流程图,恐怕别人很难看懂程序的运行状态,而且随着程序功能的增加,编写应用程序的工程师的大脑也开始混乱。即不利于升级维护,也不利于代码优化。本人写个几个比较复杂一点的应用程序,刚开始就是使用此法,最终虽然能够实现功能,但是自己的思维一直处于混乱状态。导致程序一直不能让自己满意。 这种方法大多数人都会采用,而且我们接受的教育也基本都是使用此法。对于我们这些基本没有学习过数据结构,程序架构的单片机工程师来说,无疑很难在应用程序的设计上有一个很大的提高,也导致了不同工程师编写的应用程序很难相互利于和学习。本人建议,如果喜欢使用此法的网友,如果编写比较复杂的应用程序,一定要先理清头脑,设计好完整的流程图再编写程序,否则后果很严重。当然应该程序本身很简单,此法还是一个非常必须的选择。下面就写一个顺序执行的程序模型,方便和下面两种方法对比:代 码/**************************************************************************************
# Y$ F' A$ M+ G! K* FunctionName : main()
# F) z8 k/ R+ r* Description : 主函数
5 n* f; n: i. y+ z8 p* EntryParameter : None
0 R( I, _* S7 P2 Y, k8 E* K* ReturnValue : None. ~( o: F6 w8 J5 ^) q* i" d4 }
**************************************************************************************/, p* V! E5 M, o4 Z" Z% [. h/ [
int main(void)
9 ]4 V3 P! `' e; Z" o6 r5 n: V0 z{
7 y( N5 k+ h b1 H" Muint8 keyValue; InitSys(); // 初始化 while (1)
7 I; w8 J' I& ?( t! t9 g{" p& v/ c/ d1 R l
TaskDisplayClock();
8 ]) m0 Z Z5 e% E) U) qkeyValue = TaskKeySan();
9 i5 u9 `. U. K! y3 _! Gswitch (keyValue)
) Y f% \7 h# \3 Q# @/ q{5 z. x$ t" h6 Z. {
case x: TaskDispStatus(); break;# y v, s' y( @7 w7 f
... F% e3 _( q q Y5 v( x6 v
default: break;
: u g( F& P; @1 Z- v}
: S$ y, c. N) s! X. p}
5 c% `0 k7 A1 s h% x" j: t6 Z}二、时间片轮询法时间片轮询法,在很多书籍中有提到,而且有很多时候都是与操作系统一起出现,也就是说很多时候是操作系统中使用了这一方法。不过我们这里要说的这个时间片轮询法并不是挂在操作系统下,而是在前后台程序中使用此法。也是本贴要详细说明和介绍的方法。 对于时间片轮询法,虽然有不少书籍都有介绍,但大多说得并不系统,只是提提概念而已。下面本人将详细介绍这种模式,并参考别人的代码建立的一个时间片轮询架构程序的方法,我想将给初学者有一定的借鉴性。在这里我们先介绍一下定时器的复用功能。 使用1个定时器,可以是任意的定时器,这里不做特殊说明,下面假设有3个任务,那么我们应该做如下工作: 1. 初始化定时器,这里假设定时器的定时中断为1ms(当然你可以改成10ms,这个和操作系统一样,中断过于频繁效率就低,中断太长,实时性差)。 2. 定义一个数值:代 码#define TASK_NUM (3) // 这里定义的任务数为3,表示有三个任务会使用此定时器定时。 uint16 TaskCount[TASK_NUM] ; // 这里为三个任务定义三个变量来存放定时值uint8 TaskMark[TASK_NUM]; // 同样对应三个标志位,为0表示时间没到,为1表示定时时间到。 3. 在定时器中断服务函数中添加:代 码/**************************************************************************************) S3 F" c4 o) ^1 \6 E0 z; i* w- P
* FunctionName : TimerInterrupt()
( |" r8 V2 W+ A( k4 P* Description : 定时中断服务函数0 _( f! ~( {8 l/ d* Z! O* k$ Z
* EntryParameter : None
! q9 K0 s# q; t! ]* ReturnValue : None
, L- S- F+ j6 m! K; k**************************************************************************************/: n3 h. [( T& L2 K4 ~! w
void TimerInterrupt(void)" D/ a% h _. t( X* V
{
$ T5 U- B _/ K# t1 p" xuint8 i; for (i=0; i<TASKS_NUM; i++)
- w+ P3 G- b- u& m- g{
8 X5 ]$ W6 e- R4 @# {' c* C+ U1 ]6 Kif (TaskCount)
6 R2 U4 z) r& ~. x) O{
6 I% }% T7 h' }8 V1 n$ |TaskCount--;
( E5 y; `+ _0 h* E) b, t3 |if (TaskCount == 0)
- Q8 p. k. I# h7 A4 |) h [) m{
+ r/ K( C# |6 L; E: e; O* rTaskMark = 0x01;
" `9 G1 C8 N' Q% X4 S# \4 T}
$ ^/ W1 f) H) ]}6 f5 e# A% ?- t' T8 T
}; `* V# _3 u6 _! }3 [! ]2 D
} 代码解释:定时中断服务函数,在中断中逐个判断,如果定时值为0了,表示没有使用此定时器或此定时器已经完成定时,不着处理。否则定时器减一,知道为零时,相应标志位值1,表示此任务的定时值到了。 4. 在我们的应用程序中,在需要的应用定时的地方添加如下代码,下面就以任务1为例:代 码TaskCount[0] = 20; // 延时20msTaskMark[0] = 0x00; // 启动此任务的定时器到此我们只需要在任务中判断TaskMark[0] 是否为0x01即可。其他任务添加相同,至此一个定时器的复用问题就实现了。用需要的朋友可以试试,效果不错哦。。。。。。。。。。。通过上面对1个定时器的复用我们可以看出,在等待一个定时的到来的同时我们可以循环判断标志位,同时也可以去执行其他函数。 循环判断标志位:那么我们可以想想,如果循环判断标志位,是不是就和上面介绍的顺序执行程序是一样的呢?一个大循环,只是这个延时比普通的for循环精确一些,可以实现精确延时。 执行其他函数:那么如果我们在一个函数延时的时候去执行其他函数,充分利用CPU时间,是不是和操作系统有些类似了呢?但是操作系统的任务管理和切换是非常复杂的。下面我们就将利用此方法架构一直新的应用程序。 时间片轮询法的架构: 1.设计一个结构体:代 码// 任务结构 W% p& B: m' _, G( L% u) y/ e! t
typedef struct _TASK_COMPONENTS
8 x+ F3 @3 P% U+ K+ q- L{1 I8 h7 Y* r5 X( C1 @
uint8 Run; // 程序运行标记:0-不运行,1运行# u* w. l Y, s8 e: b, p( W
uint8 Timer; // 计时器
8 G% h$ n/ H, ?7 r4 s- Z7 Xuint8 ItvTime; // 任务运行间隔时间
' D* _7 w# n+ X9 i; S1 Nvoid (*TaskHook)(void); // 要运行的任务函数( P! `# X+ s* U
} TASK_COMPONENTS; // 任务定义这个结构体的设计非常重要,一个用4个参数,注释说的非常详细,这里不在描述。 2. 任务运行标志出来,此函数就相当于中断服务函数,需要在定时器的中断服务函数中调用此函数,这里独立出来,并于移植和理解。代 码/**************************************************************************************4 z# Y. O; A, _* y% F E u
* FunctionName : TaskRemarks()
4 D G5 ?% G! z# I* G# E* Description : 任务标志处理
1 z: u; Y) \1 |' S* EntryParameter : None
- Q3 L; ]! p6 X6 l8 q( t- _# S3 a* ReturnValue : None
9 r" B$ I- B/ L$ V1 @3 A**************************************************************************************/8 z# y M1 f, ?# `3 T2 Q
void TaskRemarks(void)
- c/ e* |* N& y' @0 |* n{: o: a$ j* n+ r
uint8 i; for (i=0; i<TASKS_MAX; i++) // 逐个任务时间处理. T6 L* A& h4 [2 y- D* z& s( s9 a- M
{ T! F& I3 S+ k0 W i" q
if (TaskComps.Timer) // 时间不为0
9 K: j; M4 u# {; k* y# V* g{* H n7 [1 Q i( v+ }# ]7 E
TaskComps.Timer--; // 减去一个节拍, k2 G4 y& g2 d, Q. _+ w b& N9 x
if (TaskComps.Timer == 0) // 时间减完了( j( f' q! [; \- v l: X1 @
{
' J6 v7 L! O" p& {TaskComps.Timer = TaskComps.ItvTime; // 恢复计时器值,从新下一次6 H! Z2 |) g2 `* D% L: r
TaskComps.Run = 1; // 任务可以运行
! g( h2 X$ X# m4 _}0 n5 J- ~$ n# W ?. ?; F* I
}
0 R( C. D! S, L7 C0 }}9 ?; g0 U- v l% E% ]: Q
}大家认真对比一下次函数,和上面定时复用的函数是不是一样的呢? 3. 任务处理:代 码/**************************************************************************************
( c. N7 Z$ ^4 h: ?9 F# w, b7 K* FunctionName : TaskProcess() | D/ w& `7 `* u1 C9 G* ^5 f
* Description : 任务处理
0 R3 f1 ]5 I p& d3 ?0 n* EntryParameter : None! @; v1 t# E+ a' K
* ReturnValue : None
- c" f8 ^8 T; \4 l% T**************************************************************************************/
( c; G; O- M0 ~: C1 ]; i2 l0 Bvoid TaskProcess(void)) i4 \2 w+ P, k6 P; ^: x0 n
{
' \- n( R4 {9 h q" S- k! x3 ?uint8 i; for (i=0; i<TASKS_MAX; i++) // 逐个任务时间处理. E* f0 e" |- p2 y/ O0 p& H$ N) _' p
{5 a* e# F9 O5 U4 K
if (TaskComps.Run) // 时间不为08 T. F7 f! x- X; [" t
{
1 f& }6 B, b5 BTaskComps.TaskHook(); // 运行任务
l7 K6 g6 S( o* a) V8 P7 TTaskComps.Run = 0; // 标志清0
+ I# ?2 g) F5 R: }" B}
# ]" U6 Z2 M! b+ m. q}! B& }. M7 t* k; V, e* j
}此函数就是判断什么时候该执行那一个任务了,实现任务的管理操作,应用者只需要在main()函数中调用此函数就可以了,并不需要去分别调用和处理任务函数。 到此,一个时间片轮询应用程序的架构就建好了,大家看看是不是非常简单呢?此架构只需要两个函数,一个结构体,为了应用方面下面将再建立一个枚举型变量。 下面就说说怎样应用吧,假设我们有三个任务:时钟显示,按键扫描,和工作状态显示。 1. 定义一个上面定义的那种结构体变量: 代 码/**************************************************************************************# A" Q. u, M. t. f& k- B0 I
* Variable definition
8 y* |0 c3 L, [, z' I! u**************************************************************************************/
% x& r2 f, |+ R( @ ]static TASK_COMPONENTS TaskComps[] =1 _) n& N1 f$ ]0 {+ V( b7 c
{0 a% E* [3 G* l1 U" V1 X
{0, 60, 60, TaskDisplayClock}, // 显示时钟
; C8 W4 r/ i) y/ z{0, 20, 20, TaskKeySan}, // 按键扫描2 E7 k! ~6 w, a& C
{0, 30, 30, TaskDispStatus}, // 显示工作状态 // 这里添加你的任务。。。。};在定义变量时,我们已经初始化了值,这些值的初始化,非常重要,跟具体的执行时间优先级等都有关系,这个需要自己掌握。①大概意思是,我们有三个任务,没1s执行以下时钟显示,因为我们的时钟最小单位是1s,所以在秒变化后才显示一次就够了。②由于按键在按下时会参数抖动,而我们知道一般按键的抖动大概是20ms,那么我们在顺序执行的函数中一般是延伸20ms,而这里我们每20ms扫描一次,是非常不错的出来,即达到了消抖的目的,也不会漏掉按键输入。③为了能够显示按键后的其他提示和工作界面,我们这里设计每30ms显示一次,如果你觉得反应慢了,你可以让这些值小一点。后面的名称是对应的函数名,你必须在应用程序中编写这函数名称和这三个一样的任务。 2. 任务列表:代 码// 任务清单4 P4 X! Y! w: `
typedef enum _TASK_LIST
# ]) }( ~+ {$ B: s: v. ?* A{$ H9 W5 O6 q& D: P+ l$ V
TAST_DISP_CLOCK, // 显示时钟' z; t& x' A6 k' P. o5 A
TAST_KEY_SAN, // 按键扫描
4 P& ^: y3 u- q! ATASK_DISP_WS, // 工作状态显示
# u% y' J. ?' W/ ]$ n// 这里添加你的任务。。。。, _$ D2 ~9 L0 x% m; i
TASKS_MAX // 总的可供分配的定时任务数目
. `0 m1 b s4 G0 O! k" i1 d1 @$ j} TASK_LIST;好好看看,我们这里定义这个任务清单的目的其实就是参数TASKS_MAX的值,其他值是没有具体的意义的,只是为了清晰的表面任务的关系而已。 3. 编写任务函数:代 码/**************************************************************************************) j; F3 A2 C& }* u( K
* FunctionName : TaskDisplayClock()
8 F3 r: N# f( u) `6 k* Description : 显示任务* EntryParameter : None
' m8 R' ^6 Y+ i% r% }1 Q7 s$ b* ReturnValue : None
, O( E; i6 w1 X/ K' p) \5 T**************************************************************************************/
: l" B# w! s" I/ L( C) rvoid TaskDisplayClock(void)
8 I" b, E* I( v" }{ }/**************************************************************************************
0 z. C( H8 e! N) s# v0 [4 R* FunctionName : TaskKeySan()
! X4 ~. Y$ T+ z1 c6 U- t! C* Description : 扫描任务
7 _- L! V, X+ v8 j* EntryParameter : None
1 ?3 ]# Q+ A: |; G5 ?( r. W* ReturnValue : None2 @% D. i/ S G; ]! Q0 u, g6 t
**************************************************************************************/
?# `! i& }) Z* t$ Avoid TaskKeySan(void)9 n n- G4 D* x4 t/ h& A3 m6 R
{}/**************************************************************************************/ h+ G" P" q7 C! I
* FunctionName : TaskDispStatus()
( W5 b& k% A* S; o* Description : 工作状态显示
6 `, r! V9 P. K& G4 z$ d" b6 S* EntryParameter : None( p' [8 G r' L0 V
* ReturnValue : None
6 t1 }2 Z% O( M7 C4 b9 Z0 Z( C**************************************************************************************/ W2 h! ^0 m. E% \' V, n# v
void TaskDispStatus(void)5 ~( l; ]% \9 s. a
{}// 这里添加其他任务。。。。。。。。。现在你就可以根据自己的需要编写任务了。 4. 主函数:代 码/**************************************************************************************
8 t$ j* `+ f5 B7 o+ z$ \* FunctionName : main()
7 V6 ?5 A! \6 A5 e- i* ?. o5 o* Description : 主函数! M+ w( }* H! k4 N5 H* n9 H
* EntryParameter : None; F4 _; {6 D* a: e s. Z
* ReturnValue : None3 k) l1 Q+ a+ y- l# R: r$ x( U8 @
**************************************************************************************/9 V0 p, H K, L
int main(void)
1 e; x+ [8 {/ y2 N. ~{
8 T, _. h+ X0 {: x) M) I0 H2 }. lInitSys(); // 初始化 while (1)
* l; s' w- X E{5 y# e. d9 ]- B1 g% n9 Y# s
TaskProcess(); // 任务处理0 B5 ]8 o: L* ~
}
% e6 F" e2 o% h$ \' m}到此我们的时间片轮询这个应用程序的架构就完成了,你只需要在我们提示的地方添加你自己的任务函数就可以了。是不是很简单啊,有没有点操作系统的感觉在里面? 不防试试把,看看任务之间是不是相互并不干扰?并行运行呢?当然重要的是,还需要,注意任务之间进行数据传递时,需要采用全局变量,除此之外还需要注意划分任务以及任务的执行时间,在编写任务时,尽量让任务尽快执行完成。。。。。。。。三、操作系统操作系统的本身是一个比较复杂的东西,任务的管理,执行本事并不需要我们去了解。但是光是移植都是一件非常困难的是,虽然有人说过“你如果使用过系统,将不会在去使用前后台程序”。但是真正能使用操作系统的人并不多,不仅是因为系统的使用本身很复杂,而且还需要购买许可证(ucos也不例外,如果商用的话)。这里本人并不想过多的介绍操作系统本身,因为不是一两句话能过说明白的,下面列出UCOS下编写应该程序的模型。大家可以对比一下,这三种方式下的各自的优缺点。代 码/**************************************************************************************
9 i4 ~+ a: o7 F8 {5 Q* FunctionName : main()1 Z, Y( H* Y, w' O) f: F
* Description : 主函数+ B% s' l1 w$ w! x) R% O
* EntryParameter : None, w4 u: R0 ]' w: _ \# F1 r9 v9 a
* ReturnValue : None; O. R( |+ j8 k- J7 l
**************************************************************************************/
$ g8 d+ t, s7 b: w, V! uint main(void)
* x9 b' ?: J# O{
; G! o) m' O; Y7 Q- W' NOSInit(); // 初始化uCOS-II OSTaskCreate((void (*) (void *)) TaskStart, // 任务指针
/ r0 E( s' \4 q5 _0 p5 R: ?: R( g(void *) 0, // 参数' S& A2 n) Y. N5 z4 O# V7 ]4 b, ]
(OS_STK *) &TaskStartStk[TASK_START_STK_SIZE - 1], // 堆栈指针/ S9 s$ [# U, v- y
(INT8U ) TASK_START_PRIO); // 任务优先级 OSStart(); // 启动多任务环境 return (0);
1 f) b6 J8 r% f, ^* a/ B} 代 码/**************************************************************************************% f$ w% m, |0 ^. p! d' Z
* FunctionName : TaskStart()
( I1 P5 o1 Q7 S2 Y7 Z% H/ [* Description : 任务创建,只创建任务,不完成其他工作
& J+ p2 ?# @# U8 y6 J Z5 B1 D* EntryParameter : None
- G9 h* u$ g: d0 I0 _' R* ReturnValue : None w8 q( T# F7 ^" @+ r+ `2 Z8 J
**************************************************************************************/
% l; O6 o( B, }! m* Wvoid TaskStart(void* p_arg)) U% \) X$ f, c$ a/ u
{+ Y" T, A8 F3 O+ z8 q" I% ^2 Y5 L9 P
OS_CPU_SysTickInit(); // Initialize the SysTick.#if (OS_TASK_STAT_EN > 0)! L9 } y/ T6 `% D5 F
OSStatInit(); // 这东西可以测量CPU使用量
% S* R4 j0 c9 x9 U" f1 I/ G#endif OSTaskCreate((void (*) (void *)) TaskLed, // 任务1
y4 A, ]% m- W8 z+ T* v(void *) 0, // 不带参数, a, b( z, {/ l: Y+ v
(OS_STK *) &TaskLedStk[TASK_LED_STK_SIZE - 1], // 堆栈指针
; r% ]# q8 }* Q' W- q ^4 P' @6 b(INT8U ) TASK_LED_PRIO); // 优先级 // Here the task of creating your while (1)
* |9 q& D9 P/ ?+ }{) v" i* W. v8 ]: n T9 R, @" H
OSTimeDlyHMSM(0, 0, 0, 100);
# J7 W8 d: P: Q6 x}" Y/ F5 w8 D) H! N
}
8 }0 F$ K: z; X2 p: H* K) ? |