您的位置 首页 嵌入式

浅谈单片机应用程序架构

对于单片机程序来说,大家都不陌生,但是真正使用架构,考虑架构的恐怕并不多,随着程序开发的不断增多,本人觉得架构是非常必要的。前不

关于单片机程序来说,咱们都不生疏,可是真实运用架构,考虑架构的恐怕并不多,跟着程序开发的不断增多,自己觉得架构是十分必要的。前不就发帖与咱们一同评论了一下《谈谈怎样架构你的单片机程序》,发现真实运用架构的并不都,并且这类书本根本没有。

自己经过探索试验,并总结,大致运用程序的架构有三种:

1. 简略的前后台次序碑文程序,这类写法是大多数人运用的办法,不需用考虑程序的详细架构,直接经过碑文次序编写运用程序即可。

2. 时刻片轮询法,此办法是介于次序碑文与操作体系之间的一种办法。

3. 操作体系,此法应该是运用程序编写的最高境地。

下面就别离谈谈这三种办法的利害和习惯规模等。。。。。。。。。。。。。

1. 次序碑文法:

这种办法,这运用程序比较简略,实时性,并行性要求不太高的情况下是不错的办法,程序规划简略,思路比较明晰。可是当运用程序比较杂乱的时分,假如没有一个完好的流程图,恐怕他人很难看懂程序的运转状况,并且跟着程序功用的增加,编写运用程序的工程师的大脑也开端紊乱。即不利于晋级保护,也不利于代码优化。自己写个几个比较杂乱一点的运用程序,刚开端便是运用此法,终究尽管能够完结功用,可是自己的思想一向处于紊乱状况。导致程序一向不能让自己满足。

这种办法大多数人都会选用,并且咱们承受的教育也根本都是运用此法。关于咱们这些根本没有学习过数据结构,程序架构的单片机工程师来说,无疑很难在运用程序的规划上有一个很大的进步,也导致了不同工程师编写的运用程序很难彼此利于和学习。

自己主张,假如喜爱运用此法的网友,假如编写比较杂乱的运用程序,必定要先理清脑筋,规划好完好的流程图再编写程序,不然结果很严重。当然应该程序自身很简略,此法仍是一个十分有必要的挑选。

下面就写一个次序碑文的程序模型,方面和下面两种办法比照:

代码:

int main(void)
{
uint8 keyValue;

InitSys(); // 初始化

while (1)
{
TaskDisplayClock();
keyValue = TaskKeySan();
switch (keyValue)
{
case x: TaskDispStatus(); break;

default: break;
}
}
}

2. 时刻片轮询法

时刻片轮询法,在许多书本中有说到,并且有许多时分都是与操作体系一同呈现,也便是说许多时分是操作体系中运用了这一办法。不过咱们这儿要说的这个时刻片轮询法并不是挂在操作体系下,而是在前后台程序中运用此法。也是本贴要详细阐明和介绍的办法。

关于时刻片轮询法,尽管有不少书本都有介绍,但大多说得并不体系,仅仅提提概念罢了。下面自己将详细介绍自己形式,并参阅他人的代码树立的一个时刻片轮询架构程序的办法,我想将给初学者有必定的学习性。

记住在前不久自己发帖《1个守时器多处复用的问题》,咱们时刻的问题,并没有详细阐明怎样完结1个守时器多处复用。在这儿咱们先介绍一下守时器的复用功用。。。

运用1个守时器,可所以恣意的守时器,这儿不做特别阐明,下面假定有3个使命,那么咱们应该做如下作业:

1. 初始化守时器,这儿假定守时器的守时中止为1ms(当然你能够改成10ms,这个和操作体系相同,中止过于频频功率就低,中止太长,实时性差)。

2. 界说一个数值:

仿制内容到剪贴板

代码:

#define TASK_NUM (3)// 这儿界说的使命数为3,一共有三个使命会运用此守时器守时。

uint16 TaskCount[TASK_NUM]; // 这儿为三个使命界说三个变量来寄存守时值

uint8 TaskMark[TASK_NUM];// 相同对应三个标志位,为0一共时刻没到,为1一共守时时刻到。

3. 在守时器中止服务函数中增加:

仿制内容到剪贴板

代码:

void TimerInterrupt(void)
{
uint8 i;

for (i=0; i {
if (TaskCount[i])
{
TaskCount[i]–;
if (TaskCount[i] == 0)
{
TaskMark[i] = 0x01;
}
}
}
}

代码解说:守时中止服务函数,在中止中逐一判别,假如守时值为0了,一共没有运用此守时器或此守时器现已完结守时,不着处理。不然守时器减一,知道为零时,相应标志位值1,一共此使命的守时值到了。

4. 在咱们的运用程序中,在需求的运用守时的当地增加如下代码,下面就以使命1为例:

仿制内容到剪贴板

代码:

TaskCount[0] = 20;// 延时20ms

TaskMark[0]= 0x00; // 发动此使命的守时器

到此咱们只需求在使命中判别TaskMark[0]是否为0x01即可。其他使命增加相同,至此一个守时器的复用问题就完结了。用需求的朋友能够试试,作用不错哦。。。。。。。。。。。

经过上面临1个守时器的复用咱们能够看出,在等候一个守时的到来的一起咱们能够循环判别标志位,一起也能够去碑文其他函数。

循环判别标志位:

那么咱们能够想想,假如循环判别标志位,是不是就和上面介绍的次序碑文程序是相同的呢?一个大循环,仅仅这个延时比一般的for循环准确一些,能够完结准确延时。

碑文其他函数:

那么假如咱们在一个函数延时的时分去碑文其他函数,充分运用CPU时刻,是不是和操作体系有些相似了呢?可是操作体系的使命办理和切换是十分杂乱的。下面咱们就将运用此办法架构一向新的运用程序。

时刻片轮询法的架构:

1.规划一个结构体:

仿制内容到剪贴板

代码:

// 使命结构
typedef struct _TASK_COMPONENTS
{
uint8 Run; // 程序运转符号:0-不运转,1运转
uint8 Timer; // 计时器
uint8 ItvTime;// 使命运转间隔时刻
void (*TaskHook)(void);// 要运转的使命函数
} TASK_COMPONENTS;// 使命界说

这个结构体的规划十分重要,一个用4个参数,注释说的十分详细,这儿不在描绘。

2. 使命运转标志出来,此函数就相当于中止服务函数,需求在守时器的中止服务函数中调用此函数,这儿独立出来,并于移植和了解。

仿制内容到剪贴板

代码:

void TaskRemarks(void)
{
uint8 i;

for (i=0; i // 逐一使命时刻处理
{
if (TaskComps[i].Timer) // 时刻不为0
{
TaskComps[i].Timer–; // 减去一个节拍
if (TaskComps[i].Timer == 0) // 时刻减完了
{
TaskComps[i].Timer = TaskComps[i].ItvTime; // 康复计时器值,重新下一次
TaskComps[i].Run = 1; // 使命能够运转
}
}
}
}

咱们仔细比照一下次函数,和上面守时复用的函数是不是相同的呢?

3. 使命处理

仿制内容到剪贴板

代码:

void TaskProcess(void)
{
uint8 i;

for (i=0; i // 逐一使命时刻处理
{
if (TaskComps[i].Run) // 时刻不为0
{
TaskComps[i].TaskHook(); // 运转使命
TaskComps[i].Run = 0; // 标志清0
}
}
}

此函数便是判别什么时分该碑文那一个使命了,完结使命的办理操作,运用者只需求在main()函数中调用此函数就能够了,并不需求去别离调用和处理使命函数。

到此,一个时刻片轮询运用程序的架构就建好了,咱们看看是不是十分简略呢?此架构只需求两个函数,一个结构体,为了运用方面下面将再树立一个枚举型变量。

下面我就就说说怎样运用吧,假定咱们有三个使命:时钟显现,按键扫描,和作业状况显现。

1. 界说一个上面界说的那种结构体变量

仿制内容到剪贴板

代码:

static TASK_COMPONENTS TaskComps[] =
{
{0, 60, 60, TaskDisplayClock}, // 显现时钟
{0, 20, 20, TaskKeySan}, // 按键扫描
{0, 30, 30, TaskDispStatus}, // 显现作业状况

// 这儿增加你的使命。。。。

};

在界说变量时,咱们现已初始化了值,这些值的初始化,十分重要,跟详细的碑文时刻优先级等都有联系,这个需求自己把握。

①大约意思是,咱们有三个使命,没1s碑文以下时钟显现,咱们咱们的时钟最小单位是1s,所以在秒改变后才显现一次就够了。

②咱们按键在按下时会参数颤动,而咱们知道一般按键的颤动大约是20ms,那么咱们在次序碑文的函数中一般是延伸20ms,而这儿咱们每20ms扫描一次,是十分不错的出来,即达到了消抖的意图,也不会漏掉按键输入。

③为了能够显现按键后的其他提示和作业界面,咱们这儿规划每30ms显现一次,假如你觉得反响慢了,你能够让这些值小一点。后边的称号是对应的函数名,你有必要在运用程序中编写这函数称号和这三个相同的使命。

2. 使命列表

仿制内容到剪贴板

代码:

// 使命清单
typedef enum _TASK_LIST
{
TAST_DISP_CLOCK, // 显现时钟
TAST_KEY_SAN, // 按键扫描
TASK_DISP_WS, // 作业状况显现
// 这儿增加你的使命。。。。
TASKS_MAX // 总的可供分配的守时使命数目
} TASK_LIST;

好好看看,咱们这儿界说这个使命清单的意图其实便是参数TASKS_MAX的值,其他值是没有详细的含义的,仅仅为了明晰的外表使命的联系罢了。

3. 编写使命函数

仿制内容到剪贴板

代码:

void TaskDisplayClock(void)
{

}

void TaskKeySan(void)
{

}

void TaskDispStatus(void)
{

}

// 这儿增加其他使命。。。。。。。。。

现在你就能够依据自己的需求编写使命了。

4. 主函数

仿制内容到剪贴板

代码:

int main(void)
{
InitSys(); // 初始化

while (1)
{
TaskProcess(); // 使命处理
}
}

到此咱们的时刻片轮询这个运用程序的架构就完结了,你只需求在咱们提示的当地增加你自己的使命函数就能够了。是不是很简略啊,有没有点操作体系的感觉在里面?

不防试试把,看看使命之间是不是彼此并不搅扰?并行运转呢?当然重要的是,还需求,留意使命之间进行数据传递时,需求选用全局变量,除此之外还需求留意区分使命以及使命的碑文时刻,在编写使命时,尽量让使命赶快碑文完结。。。。。。。。。

3.操作体系

操作体系的自身是一个比较杂乱的东西,使命的办理,碑文本事并不需求咱们去了解。可是光是移植都是一件十分困难的是,尽管有人说过“你假如运用过体系,将不会在去运用前后台程序”。可是真实能运用操作体系的人并不多,不只是咱们体系的运用自身很杂乱,并且还需求购买许可证(ucos也不破例,假如商用的话)。

这儿自己并不想过多的介绍操作体系自身,咱们不是一两句话能过阐了解的,下面列出UCOS下编写应该程序的模型。咱们能够比照一下,这三种方法下的各自的优缺点。

仿制内容到剪贴板

代码:

int main(void)
{
OSInit(); // 初始化uCOS-II

OSTaskCreate((void (*) (void *)) TaskStart, // 使命指针
(void *) 0, // 参数
(OS_STK *) &TaskStartStk[TASK_START_STK_SIZE – 1], // 仓库指针
(INT8U ) TASK_START_PRIO); // 使命优先级

OSStart(); // 发动多使命环境

return (0);
}

仿制内容到剪贴板

代码:

void TaskStart(void* p_arg)
{
OS_CPU_SysTickInit(); // Initialize the SysTick.

#if (OS_TASK_STAT_EN > 0)
OSStatInit(); // 这东西能够丈量CPU运用量
#endif

OSTaskCreate((void (*) (void *)) TaskLed,// 使命1
(void *) 0, // 不带参数
(OS_STK *) &TaskLedStk[TASK_LED_STK_SIZE – 1], // 仓库指针
(INT8U ) TASK_LED_PRIO); // 优先级

// Here the task of creating your

while (1)
{
OSTimeDlyHMSM(0, 0, 0, 100);
}
}

不难看出,时刻片轮询法优势仍是比较大的,即由次序碑文法的长处,也有操作体系的长处。结构明晰,简略,十分简单了解。。。。。。。。。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/qianrushi/263554.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部