单片机开发需要学什么语言(单片机开发需要学什么软件)

单片机软件开发,实时系统分时多任务,看完你就懂了

///插播一条:我自己在今年年初录制了一套还比较系统的入门单片机教程,想要的同学找我拿就行了免費的,私信我就可以~点我头像黑色字体加我地球呺也能领取哦。最近比较闲,带做毕设,带学生参加省级或以上比///单片机开发需要学什么语言(单片机开发需要学什么软件)

说明:

作者本人虽然岁数不小,但却是初入此行的新人,此篇文章只适合正在学习的准程序员,或初入行业的新人。当然如果有大牛路过,欢迎指点。

单片机软件开发,如果不用实时系统的话,那基于时间片的分时多任一定是会用到的时间片的分时多任看似简单,其实还是有几个方面需要注意的,以下我就根据自己的了解和实际应用经验来讲解一下相关代码。虽然我没有把完整的代码贴出来,但以下的代码块都是从实际的应用中摘出来的。意思就是贴出的代码块都是经过实际验证的。

一。首先简单介绍一基于时间片的分时多任。顾名思时间就是把单片机执行的函数或任务,按划定的时间片来执行。比如一个工程有按键,有触摸屏通讯,LED运行指示。可以把他们三个分别当3个任务,并且每个任务的运行间隔时间也可以自定义。比如按键扫描一 10ms执行一次就行了。通 20ms执行一次。运行指示可 250ms执行一次。这样就在一个主循环里有3个任务,而且每个任务执行的时间间隔都不一样,可以充分利CPU硬件资源,CPU的资源尽量少浪费在无用的循环里。

那么这个时间是依据什么来的呢?我们一般是用一个定时器,1ms中断一次,也就是产生一个任务节拍。(下文提到的所有任务节拍都1ms)每次中断都有一个或多个全局变量1,根据这个全局变量的值我们就可以知道任务延时情况。

/*==================================更改分割线=======================================之前这篇笔记写得有些杂乱,我自己都看不下去。在这五一小长假的最后一天我就收拾起自己的懒散,重新认真编辑一次,其中的代码部分也是优化过了的。任务除了基本的分时运行功能外,还可以在运行中暂停或恢复某个任务,以及统计某个任务的运行时间,ms为单位*/

进入正题!!!

介绍任务。

//代码文 task.h#define TASK_MAX 10u //最大任务数量#define TASK_BEAT_MS 1u //任务节拍时间ms#define TASK_BATE_MAX (u32)40050 //最大任务节//任务函数类型typedefvoid(*TASK)(void*p_arg);

//任务结构体typedefstruct{

s8id;//IDu8RunTime;//任务运行时间统计10不统计)TASKTaskAddr;//任务函数地址void*p_arg;//任务参数vu32TaskBeat;//任务节拍u32TaskDelay;//延迟节拍}TaskStruct;

externvu16TaskBeat_ms;//系统定时,毫秒(用作任务片延时)externTaskStructtask[TASK_MAX];//任务结构体数组

以上代码块就是把任务相关的一些变量以及函数指针放在一个结构体里,之后对任务的运行以及管理只需要传递结构体指针就行了。

任务管理

再来看看任务管理函数,以及统计任务运行时间函数。

//代码文 task.c/*任务函数任务管理函数 *p_task 任务结构体指针返回值*/voidTask(TaskStruct*p_task){

if(p_task==NULL)//指针错误return;

if(p_task->TaskAddr==NULL)//函数指针为空return;

if(p_task->id<0)//此任务暂停return;

if(p_task->TaskBeat>p_task->TaskDelay)//任务节拍大于上次节拍{

p_task->TaskBeat=1u;

if(p_task->RunTime==0)//不统计任务执行时间p_task->TaskAddr(p_task->p_arg);//任务函数else//统计任务执行时间{

TaskRunTime(p_task->id,1);//执行任务前节拍p_task->TaskAddr(p_task->p_arg);//任务函数TaskRunTime(p_task->id,2);//执行任务后节拍}

}}

/*统计任务运行时间把统计的任务时间通过串口输出 task_id ID(不可重复 mark 用于标记任务运行前后时 1任务运行 2任务运行后返回值*/staticvoidTaskRunTime(s8task_id,u32mark){

staticu32RunBeat[3];//记录任务节拍

if(mark==1)

{

RunBeat[0]=TaskBeat_ms;//任务运行前任务节拍return;

}

elseif(mark==2)

{

RunBeat[1]=TaskBeat_ms;//任务运行后任务节拍if(RunBeat[1]>=RunBeat[0])

RunBeat[2]=RunBeat[1]RunBeat[0];

else

RunBeat[2]=(TASK_BATE_MAXRunBeat[1])+RunBeat[0];

//发送任务运行时间printf("id:%02d tim[%04u] bea:%u\r\n",task_id,RunBeat[2],TaskBeat_ms);

}}

以上的任务管理函数, 2 if判断是排错, 3 if是一个任务暂停功能,如果把某个任务 id变量赋值为负数,此任务就不会执行。 4 if判断才是真的得任务运行判断。具体的这些参数下面再介绍。任务统计函数其实就是在任务运行前和运行后都统计一次系统节拍,并在第二次统计完之后算出此任务的运行时间,然后通过串口输出。

定时器中断

定时器中断函数,用作任务切换。定时器配置 1ms中断一次。

//代码文 timer.c/*普通定时 TIM16计时模式,中断服务函数用作任务时钟片计*/voidTIM1_UP_TIM16_IRQHandler(void){

vu32i;

if(TIM_GetITStatus(TIM16,TIM_IT_Update)!=RESET)//定時 TIM16中断{

TIM_ClearITPendingBit(TIM16,TIM_IT_Update);// TIM16中断标

TaskBeat_ms++;//所有任务节拍(用作统计任务运行时间)for(i=0;i<TASK_MAX;i++)

{

task[i].TaskBeat++;//单个任务节拍}

for(i=0;i<MAX_PORTS;i++)

{

USARTx_Time_ms[i]++;//串口通信计时if(USARTx_Time_ms[i]>=200)

{

USARTx_Time_ms[i]=100;

}

}

}}

以上代码块,定时器中断函数里的 2 for循环和任务函数无关,是串口通信使用的,大家可以忽略掉。

到此为止,我们就单片机基于时间片的多任主要部分介绍完毕。下面我们要开始结合上面的各个代码块,来介 main.c文件里的任务结构体数组初始化和主循环里的任务运行说明。

main.c

//代码文 main.c/*任务结构体初始化任务结构体数据说明任ID任务时间统计任务函数,任务参数任务节拍延时节*/TaskStructtask[TASK_MAX]={

{0,0,KEY_Scan,NULL,0,4},//按键扫描及执行{1,0,HMI_Receive,NULL,3,29},//HMI报文{2,0,Status,NULL,0,20},//设备状态{3,0,DataDispose,NULL,19,20},//数据处理{4,0,Angle_RxTx,NULL,39,100},//角度传感器{5,0},

{6,0},

{7,0},

{8,0,Debug,NULL,0,100},//Debug调试{9,0,Runing,NULL,0,250}//运行指示};

/*Debug调试,具体运行时间视输出数据而定。实际开发完后请注释掉此任务。用来调试程序,可以输出变量,查看其具体变化 *p_arg没用返回值*/voidDebug(void*p_arg){

printf("\r\n");}

/*主函数无返回值*/intmain(void){

Hard_Init();//硬件初始化Hard_Set();//外设设置

printf("编译日期%s.\r\n",__DATE__);

printf("编译时间%s.\r\n",__TIME__);

while(1)

{

Task(&task[0]);//按键扫描Task(&task[1]);//HMI报文Task(&task[2]);//设备状态Task(&task[3]);//数据处理Task(&task[4]);//角度传感// Task(&task[8]); //Debug调试Task(&task[9]);//运行指示}}

/*运行指示,运行时间小1ms*/voidRuning(void*p_arg){

staticu8run_num=0;

IWDG_Feed();//喂狗,独立看门狗LED_RUN=!LED_RUN;//LED运行指示run_num++;

if(run_num>1)

{

run_num=0;

CPU_GetTemp();//CPU温度}}

细说

现在我们要开始仔细讲解以上代码的变量和一些逻辑判断的具体说明以及作用。

1:先来看上面的任务结构体数组定义,这个要结合最上面代码块的任务结构体声明来看。

因为我们在前 task.h头文件,通过 TASK_MAX把最大任务定义 10,所以在这个结构体数组我们初始化10个任务结构体变量。

1.1任务结构体里的1个成员是任 id,这个任 id我们手动初始化,0开始往后累加,不可以重复。假如任2在系统刚上电时不需要运行,我们可以把他 id初始化2,等到他可以运行的时候再赋值2

1.2结构体2个成员变量是任务运行时间统计0不统计1统计。时间单位ms。我们结合第二段代码,任务管理函数来看,如果这个参数0就直接运行任务函数,0就会在任务运行前、后各统1次任务节拍。在2次统计完节拍之后算出任务运行时占用的节拍数,并且通过串口输出。1节拍,就1ms)这个功能一般是在前期调试的时候使用,清楚自己的任务运行时间,在实际发布的时候这个变量都初始化0,不然任务实际运行时间才一ms,可是通过串口输出统计结果就要十几二十毫秒,会拖慢其他任务的实时性。

任务运行时间统计代码块:

//代码文 task.c

if(p_task->RunTime==0)//不统计任务执行时间p_task->TaskAddr(p_task->p_arg);//任务函数else//统计任务执行时间{

TaskRunTime(p_task->id,1);//执行任务前节拍p_task->TaskAddr(p_task->p_arg);//任务函数TaskRunTime(p_task->id,2);//执行任务后节拍}

1.33个成员就是函数指针,最终任务管理函数通过这个函数指针,来调用相应的任务函数。所有的任务函数都是这种类型。结合前面结构体声明来看,这是一 void类型函数也就是没有返回值,还带一 void指针参数,关于这个指针其实一般是用不到的,可以忽略掉他,如果有警告的话在任务函数里可 p_arg = p_arg消除警告。

//代码文 task.h//任务函数类型typedefvoid(*TASK)(void*p_arg);

1.44个成员是任务函数的参数,可以看到我们所有的结构体变量都把这个参数初始化为一 NULL指针。

1.55个成员变量是任务节 TaskBeat。每个任务都有一个任务节拍变量,在定时器中断里以ms1的方式一直累加。如下代码段所示。

//代码文 timer.c

TaskBeat_ms++;//所有任务节拍(用作统计任务运行时间)for(i=0;i<TASK_MAX;i++)

{

task[i].TaskBeat++;//单个任务节拍}

1.6终于到最后一个成员变量了,他就是任务延时节拍。就是说这个任务间隔多少个任务节拍运行一次。也就是我们这个任务管理函数最基本的功能。因为我们的目的就让每个任务函数以他合理的频率来分时运行。 main.c文件我们把每个任务的延时节拍都初始化为不同的值。最 4ms,最 250ms。实际延时节拍都是根据实际项目需求来定的。

在任务管理函数里,我们每次都判断任务的运行节拍是否大于延时节拍。如果条件为真,说明这个任务的延时时间完了,他可以运行。进 if语句后,第一步我们把当前任务的运行节拍复位,赋值1。目的是让他运行完此次任务后开始新一轮的延时。第二步执行任务函数指针,也就是执行具体的任务。

//代码文 task.c

if(p_task->TaskBeat>p_task->TaskDelay)//任务节拍大于上次节拍{

p_task->TaskBeat=1u;

if(p_task->RunTime==0)//不统计任务执行时间p_task->TaskAddr(p_task->p_arg);//任务函数else//统计任务执行时间{

TaskRunTime(p_task->id,1);//执行任务前节拍p_task->TaskAddr(p_task->p_arg);//任务函数TaskRunTime(p_task->id,2);//执行任务后节拍}

}

2:进 main函数里的主循环,如下所示。我们看到主循环里是一直调用任务管理函 Task() Task的参数都不同。其实就是任务结构体数组里的不同结构体变量指针。根据实际参数,最终就可以调用不同的任务函数。

其实我们完全可以在主循环里只调用一 Task()然后 Task里遍历结构体数组里的结构体变量,来判断哪个任务可以运行,这样代码更加简洁。但是如果这样做的话 Task会把整个结构体数组都遍历一遍,有些不需要运行的任务就需要我们手动修改结构体数组初始化值,而且我们不能很直观的看出哪个任务可以运行,哪个任务不可以运行。有些我们只在调试时使用的任务,比 Debug(void *p_arg)任务。在调试时我们用他输出我们要观察的一些变量,但在实际发布时就不需要他了。我们只要在主循环里注释掉调用他 Task(&task[8]);就行了。而且在主循环里我们可以很直观的看出那些任务可以运行,那些任务不能运行。

//代码文 main.c

while(1)

{

Task(&task[0]);//按键扫描Task(&task[1]);//HMI报文Task(&task[2]);//设备状态Task(&task[3]);//数据处理Task(&task[4]);//角度传感// Task(&task[8]); //Debug调试Task(&task[9]);//运行指示}

基于时间片的分时任务到此我们就介绍完了,希望能对大家有所帮助。

最后附上一些免费的入门单片机教程,私信我就行

本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 sumchina520@foxmail.com 举报,一经查实,本站将立刻删除。
如若转载,请注明出处:https://www.dasum.com/174672.html