单片机关键任务优先级的实现学习

与总体产品联调时,需要各个单机系统严格按照总体要求,进行数据输出,时间的偏差将出现系统异常,控制失败等不稳定情况产生,甚至影响到产品安全。

因此必须确保某些关键任务的优先执行。单片机任务优先级一般有两种方式实现,基于单片机中断服务的中断函数进行实现基于实时操作系统的任务调度实现

基于中断服务函数实现的任务优先级对单片机硬件资源有要求,而对于实时操作系统的任务调度方式,仅需一个定时器就可完成多任务多优先级的管理。

参与某产品联调时,总体要求每间隔5ms向总控发
送一次关键数据。

当系统联调运行时,总控会产生超时报警,报警内容是通信超时。

经过排查排除了硬件问题、电磁干扰问题、程序逻辑错误未正常发送数据等问题。

通过报警时间比对,发现该报警出现时间没有规律性。通过示波器查看发现,其发送数据周期没有严格按照5ms 间隔时间发送,发送时间落在 5ms 区间段内,任意时间点都可能会进行关键数据的传递,无法预测下次一次发送数据的准确时间,当系统在规定时间内未接收到数据时,产生系统报警。

经过对程序进行逻辑分析,出现问题原因是单片机运
行任务是顺序执行,只有轮到发送数据任务执行时,才能发送数据,如果其他任务占用执行时间过长,将会导致发送任务不能在5ms时间内再次获得运行机会,因此也无法按时发送数据,造成数据超时问题。

在这里插入图片描述

1 关键任务优先执行方法
1.1 查找问题

下位机程序任务流程如图1所示:

下位机程序按照项目功能需求,将不同功能划分为不
同任务,根据每个任务特点,制定的间隔时间不一致,

如 对RS485等通信口监听时,其响应时间在50ms满足要求,自然环境下温度变化缓慢,因此温度采集500ms一次也满足要求。通过计时器进行技术,当5ms时置位5ms任务标志位,10ms时置位5ms和10ms任务标志位,通过任务标志位定义了任务执行频率,优先级高的任务得到更多执行次数。该种任务执行方式称为任务协同方式,当一个任务执行时,必须等到该任务执行完成,才能执行下一个任务。当某一时刻,多个时间任务被置位时,其按照顺序结构运行程序,任务需要排队执行,实时性不高。

下位机程序使用任务协同方式进行运行,分别定义了
5ms,10ms,20ms,50ms,100ms,200ms,500ms 等任务。

所有的任务基于顺序执行,其中5ms程序critical_task
作为关键任务。某个时刻,如定时器在计数到500ms时,其上的5ms,10ms,20ms,50ms,100ms,200ms 时间标志位被置位,任务均得到执行,导致500ms这一时刻需要顺序执行很多任务,如在5ms内不能执行完全部任务,那么下一次的关键任务程序 critical_task 将不能按时被执行,导致输出超时情况产生。 为解决超时问题,必须提升critical_task 任务的优先级,提升任务优先级的方式较多,常用的方式有中断服务函数(前后台系统)、实时操作系统实现。

1.2 关键任务任务由前后台系统保证

单片机是单核处理器,不能同时执行多个任务。

从主程序架构上看,该种顺序执行方式不能保证关键
任务critical_task 的优先执行。因此应该使用某种方式
能够中断当前正在顺序执行的任务,转向执行优先级较高的任务。

单片机中断是指正在执行一项任务A,然后突然停止
任务A去执行任务B,执行完任务B再回来继续执行任务
A 的过程。单片机中断有很多触发源,如定时器中断、外部按键中断、通信发送、接收数据中断,每个中断源都可以打断正在执行的任务,转向执行中断任务,中断任务执行完毕后,继续回到当前的任务进行未完成的操作,利用单片机的中断特性,能够保证某些代码的及时执行。将某些关键任务放入中断服务函数中,就能打断顺序执行任务而优先执行中断任务,使用该种方式提升了关键任务的优先级。
在这里插入图片描述

单片机中断源的产生有很多方式,与产品联调问题是
不能在5ms时准确的进行数据传输,因此需要在5ms时产生一次任务中断,以执行发送任务。为了满足上述要求,选择定时器中断可满足要求。

定时器中断是指单片机内部有一个从0开始向上(向
下)计数的计数器,每一次计数时间均相同,设置一个计
数目标值,当计数器计数到目标值时,会产生一个计数中断,中断后单片机可以打断当前正在执行的程序跳转执行中断服务程序。在中断服务程序中,清除中断服务向量,使得计数器归0重新计数,以此不断循环达到每隔一定时间就产生一次服务中断的工作模式。

在这里插入图片描述

我们将原程序中的critical_task任务从5ms任务重
移除,添加到 Count5ms_OnInterrupt 中断服务程序中,通过前后台方式实现打断其他正在执行任务来保证
critical_task 的优先执行。程序更改后critical_task
能够5ms一次准确输出,系统报警现象被消除。采用此种方法虽然简单,但是突出问题有几个:
(1)使用中断方式来保证优先级需要占用一个中断
来完成,浪费资源。
(2)当有多个关键任务需要执行时,会出现中断嵌
套,关键任务仍然会被打断执行。
(3)违背了中断中只执行不耗时简单操作的原则,
仍然存在隐患。
(4)不能对不同任务进行不同权重的CPU使用权划
分。


C51 单片机任务协同的实现可以通过定时器中断和任务标志位来完成。在 C51 中,定时器的配置和中断处理稍有不同于现代的 ARM Cortex 系列单片机,但基本原理是相似的。下面是一个简单的示例程序,演示如何使用 C51 编写一个任务协同的程序,涉及10ms、30ms、50ms、100ms 的任务执行:

#include <REG51.H>

// 定义任务标志位
bit task_10ms_flag = 0;
bit task_30ms_flag = 0;
bit task_50ms_flag = 0;
bit task_100ms_flag = 0;

// 定义定时器0的初值
#define TIMER0_INIT_VALUE 65536 - (12000 / 12)  // 12MHz晶振,计时1ms

// 定时器0中断处理函数
void Timer0_ISR(void) interrupt 1
{
    static unsigned char count_10ms = 0;
    static unsigned char count_30ms = 0;
    static unsigned char count_50ms = 0;
    static unsigned char count_100ms = 0;

    // 10ms任务
    count_10ms++;
    if (count_10ms >= 10)
    {
        count_10ms = 0;
        task_10ms_flag = 1;
    }

    // 30ms任务
    count_30ms++;
    if (count_30ms >= 30)
    {
        count_30ms = 0;
        task_30ms_flag = 1;
    }

    // 50ms任务
    count_50ms++;
    if (count_50ms >= 50)
    {
        count_50ms = 0;
        task_50ms_flag = 1;
    }

    // 100ms任务
    count_100ms++;
    if (count_100ms >= 100)
    {
        count_100ms = 0;
        task_100ms_flag = 1;
    }
}

void main(void)
{
    // 初始化定时器0
    TMOD = 0x01;  // 定时器0工作在模式1
    TH0 = TIMER0_INIT_VALUE >> 8;  // 预设初值
    TL0 = TIMER0_INIT_VALUE & 0xFF;
    TR0 = 1;  // 启动定时器0
    ET0 = 1;  // 允许定时器0中断
    EA = 1;   // 允许总中断

    while (1)
    {
        // 执行10ms任务
        if (task_10ms_flag)
        {
            task_10ms_flag = 0;
            // 在此处执行10ms的任务
        }

        // 执行30ms任务
        if (task_30ms_flag)
        {
            task_30ms_flag = 0;
            // 在此处执行30ms的任务
        }

        // 执行50ms任务
        if (task_50ms_flag)
        {
            task_50ms_flag = 0;
            // 在此处执行50ms的任务
        }

        // 执行100ms任务
        if (task_100ms_flag)
        {
            task_100ms_flag = 0;
            // 在此处执行100ms的任务
        }
    }
}

程序说明:

  • 定时器设置:使用定时器0(TMOD = 0x01),工作在模式1下,计时器1ms。通过中断每1ms计时,并根据计数器判断是否到达设定的任务执行时间。

  • 任务标志位:使用四个标志位 task_10ms_flagtask_30ms_flagtask_50ms_flagtask_100ms_flag 来表示对应的任务是否需要执行。

  • 任务执行:在主循环中,根据任务标志位的状态执行对应的任务,每次任务完成后将对应的标志位清零。

注意事项:

  • 定时器配置:根据具体的晶振频率和需求调整 TIMER0_INIT_VALUE 的值,确保定时器产生1ms的中断。

  • 任务执行顺序:任务按照设定的时间间隔执行,且串行执行,适合简单控制任务。

  • 实时性:由于是基于定时器中断,任务的实时性取决于定时器的精确性和系统负载。

这个简单的示例展示了如何在 C51 单片机上实现基于定时器和任务标志位的任务协同方式,适用于对实时性要求不是非常高的简单控制和数据采集应用。


在上述的程序中,的确存在一种可能性,即30ms和50ms的任务同时在处理的情况。这是因为任务标志位 task_30ms_flagtask_50ms_flag 可能会在同一个定时器中断周期内同时被置位,从而导致它们在主循环中几乎同时被检测到并执行对应的任务代码段。

要解决这个问题,可以考虑以下几点改进:

  1. 任务优先级管理:在任务标志位设置时,考虑将更高优先级的任务标志位先置位,确保高优先级任务优先执行。例如,在定时器中断处理函数中,先处理100ms任务标志位,然后是50ms、30ms、最后是10ms任务标志位。

  2. 任务执行顺序控制:在主循环中,确保每次只处理一个任务标志位,并且每个任务完成后清除对应的标志位。这样可以避免同时处理多个任务的情况。

  3. 优化任务时间间隔:考虑调整任务的时间间隔,使得不同任务的触发时间点尽可能错开,减少同时触发的可能性。

下面是改进后的代码示例,演示如何根据任务的优先级顺序处理任务标志位:

#include <REG51.H>

// 定义任务标志位
bit task_10ms_flag = 0;
bit task_30ms_flag = 0;
bit task_50ms_flag = 0;
bit task_100ms_flag = 0;

// 定义定时器0的初值
#define TIMER0_INIT_VALUE 65536 - (12000 / 12)  // 12MHz晶振,计时1ms

// 定时器0中断处理函数
void Timer0_ISR(void) interrupt 1
{
    static unsigned char count_10ms = 0;
    static unsigned char count_30ms = 0;
    static unsigned char count_50ms = 0;
    static unsigned char count_100ms = 0;

    // 100ms任务
    count_100ms++;
    if (count_100ms >= 100)
    {
        count_100ms = 0;
        task_100ms_flag = 1;
    }

    // 50ms任务
    if (count_100ms % 50 == 0)
    {
        task_50ms_flag = 1;
    }

    // 30ms任务
    if (count_100ms % 30 == 0)
    {
        task_30ms_flag = 1;
    }

    // 10ms任务
    count_10ms++;
    if (count_10ms >= 10)
    {
        count_10ms = 0;
        task_10ms_flag = 1;
    }
}

void main(void)
{
    // 初始化定时器0
    TMOD = 0x01;  // 定时器0工作在模式1
    TH0 = TIMER0_INIT_VALUE >> 8;  // 预设初值
    TL0 = TIMER0_INIT_VALUE & 0xFF;
    TR0 = 1;  // 启动定时器0
    ET0 = 1;  // 允许定时器0中断
    EA = 1;   // 允许总中断

    while (1)
    {
        // 执行100ms任务
        if (task_100ms_flag)
        {
            task_100ms_flag = 0;
            // 在此处执行100ms的任务
        }

        // 执行50ms任务
        if (task_50ms_flag)
        {
            task_50ms_flag = 0;
            // 在此处执行50ms的任务
        }

        // 执行30ms任务
        if (task_30ms_flag)
        {
            task_30ms_flag = 0;
            // 在此处执行30ms的任务
        }

        // 执行10ms任务
        if (task_10ms_flag)
        {
            task_10ms_flag = 0;
            // 在此处执行10ms的任务
        }
    }
}

改进说明:

  • 优先级处理:在定时器中断处理函数中,先处理更高优先级的任务标志位(例如100ms任务),然后逐级处理较低优先级的任务标志位。这样可以确保在同一个定时器中断周期内只有一个任务标志位被置位。

  • 顺序执行:在主循环中,每次只检测并执行一个任务标志位的任务,确保任务的顺序执行,并在每个任务完成后清除对应的标志位。

通过这样的改进,可以有效避免30ms和50ms任务同时在处理的情况,保证任务执行的可控性和稳定性。


/*T3时间定义*/
unsigned char 	Counter_5ms = 0;
unsigned char 	Counter_10ms = 0;
unsigned char 	Counter_30ms = 0;
unsigned char 	Counter_100ms = 0;
unsigned char 	Counter_300ms = 0;

bit             Flag_1ms = 0;
bit             Flag_5ms = 0;
bit             Flag_10ms = 0;
bit             Flag_30ms = 0;
bit             Flag_100ms = 0;
bit             Flag_300ms = 0;

void time3_int(void) interrupt VECTOR_Timer3
{
		_push_(INSCON);
		SELBANK1;
		T3CON |= (0<<7); // 中断标志清零
		TL3 = (unsigned char)(T3INIT) &0xFF;		 
		TH3 = (unsigned char)(T3INIT>>8);
	
		Flag_1ms = 1;  //1毫秒
		
	if(++Counter_5ms>4)	//5ms定时
    {
			Flag_5ms=1;
			Counter_5ms=0;
			if(++Counter_10ms>1)//10ms定时
			{
				Flag_10ms=1;			
				Counter_10ms=0;
				if(++Counter_30ms>1)//10ms定时
				{
					FAN_30ms=1;	
					Flag_30ms=1;
					Counter_30ms=0;
				}
				if(++Counter_100ms>9)
				{
					Flag_100ms=1;
					Counter_100ms=0;
						if(++Counter_300ms>2)
						{
							Flag_300ms=1;
							Counter_300ms=0;
						}
				}
			}
		}
		SELBANK0;
		_pop_(INSCON);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/780457.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java 基础知识之 switch 语句和 yield 关键字

传统 switch 语句 传统的 switch 语句我们已经写了一万遍了&#xff0c;以下是一个典型的 switch 语句&#xff1a; int dayOfWeek 3; switch (dayOfWeek) {case 1:System.out.println("星期一");break;case 2:System.out.println("星期二");break;case…

STM32-I2C

本内容基于江协科技STM32视频学习之后整理而得。 文章目录 1. I2C通信1.1 I2C通信简介1.2 硬件电路1.3 I2C时序基本单元1.3.1 起始条件和终止条件1.3.2 发送一个字节1.3.3 接收一个字节1.3.4 发送应答和接收应答 1.4 I2C时序1.4.1 指定地址写1.4.2 当前地址读1.4.3 指定地址读…

Java应用系统设计与实现--学生信息管理系统(附解决方案源码)

一、实验目的及要求 1.1实验目的 掌握Java GUI编程技术&#xff0c;了解Swing框架的使用。 掌握MySQL数据库的基本操作&#xff0c;了解如何在Java中连接和操作数据库。 掌握用户权限管理的基本概念和实现方法。 提升综合运用所学知识设计和实现一个完整应用系统的能力…

QThread moveToThread的妙用

官方文档描述 总结就是移动到线程的对象不能有父对象&#xff0c;执行start即起一个线程&#xff0c;示例是将myObject移动到主线程中。QT中这种方式起一个线程是非常简单的。 示例描述以及代码 描述往Communicate线程中频繁添加任务&#xff0c;等任务结束的时候统计计算的结…

【python教程】数据分析——numpy、pandas、matplotlib

【python教程】数据分析——numpy、pandas、matplotlib 文章目录 什么是matplotlib安装matplotlib&#xff0c;画个折线 什么是matplotlib matplotlib:最流行的Python底层绘图库&#xff0c;主要做数据可视化图表,名字取材于MATLAB&#xff0c;模仿MATLAB构建 安装matplotlib&…

Node 中基于 Koa 框架的 Web 服务搭建实战

前言 在《Node之Web服务 - 掘金 (juejin.cn)》一文中,我们使用 HTTP 模块构建了后端接口,从而实现了后端服务的开发。可以对此进行进一步优化 http模块代码回顾 const http require("http");const server http.createServer((req, res) > {if (reqUrl.pathna…

【面试八股文】java基础知识

引言 本文是java面试时的一些常见知识点总结归纳和一些拓展&#xff0c;笔者在学习这些内容时&#xff0c;特地整理记录下来&#xff0c;以供大家学习共勉。 一、数据类型 1.1 为什么要设计封装类&#xff0c;Integer和int区别是什么&#xff1f; 使用封装类的目的 对象化:…

阶段三:项目开发---搭建项目前后端系统基础架构:任务13:实现基本的登录功能

任务描述 任务名称&#xff1a; 实现基本的登录功能 知识点&#xff1a; 了解前端Vue项目的基本执行过程 重 点&#xff1a; 构建项目的基本登陆功能 内 容&#xff1a; 通过实现项目的基本登录功能&#xff0c;来了解前端Vue项目的基本执行过程&#xff0c;并完成基…

前端面试题17(js快速检索方法详解)

在前端JavaScript中&#xff0c;快速检索数据通常涉及到数组或对象的搜索。这里我会介绍几种常见的快速检索方法&#xff0c;并提供相应的代码示例。 1. 数组的find和findIndex方法 find: 返回数组中满足条件的第一个元素的值。findIndex: 返回数组中满足条件的第一个元素的索…

基于LSTM的股票价格预测

摘要 本课设旨在利用LSTM&#xff08;长短期记忆&#xff09;网络实现股票价格预测&#xff0c;通过收集、预处理股票数据集&#xff0c;并构建预测模型进行训练与优化。实验结果显示&#xff0c;经过优化调整模型参数&#xff0c;模型在测试集上取得了较为理想的预测效果。尽…

《征服数据结构》SparseArray

摘要&#xff1a; 1&#xff0c;SparseArray的介绍 2&#xff0c;SparseArray的代码实现 1&#xff0c;SparseArray的介绍 前面我们讲过《ArrayMap》&#xff0c;用它来实现哈希表&#xff0c;其中存放key和value的数组长度是存放散列表数组长度的二倍。 在哈希表中如果key值是…

SwiftData 模型对象的多个实例在 SwiftUI 中不能及时同步的解决

概览 我们已经知道,用 CoreData 在背后默默支持的 SwiftUI 视图在使用 @FetchRequest 来查询托管对象集合时,若查询结果中的托管对象在别处被改变将不会在 FetchedResults 中得到及时的刷新。 那么这一“囧境”在 SwiftData 里是否也会“卷土重来”呢?空说无益,就让我们在…

【项目设计】负载均衡式——Online Judge

负载均衡式——Online Judge&#x1f60e; 前言&#x1f64c;Online Judge 项目一、项目介绍二、项目技术栈三、项目使用环境四、项目宏观框架五、项目后端服务实现过程1、comm模块设计1.1 Log.hpp实现1.2 Util.hpp实现 2、compiler_server 模块设计2.1compile.hpp文件代码编写…

vb.netcad二开自学笔记2:认识vs编辑器

认识一下宇宙第一编辑器的界面图标含义还是很重要的&#xff0c;否则都不知道面对的是什么还怎么继续&#xff1f; 一、VS编辑器中常见的图标的含义 变量 长方体&#xff1a;变量 局部变量 两个矩形块&#xff1a;枚举 预定义的枚举 紫色立方体&#xff1a;方法 橙色树状结构…

通过AIS实现船舶追踪与照射

前些天突然接到个紧急的项目&#xff1a;某处需要实现对夜航船只进行追踪并用激光灯照射以保障夜航安全。这个项目紧急到什么程度呢&#xff1f;&#xff01;现场激光灯都安装好了&#xff0c;还有三个星期就要验收了&#xff0c;但上家没搞定就甩给我们了:( 从技术上看&#…

Java -- 实现MD5加密/加盐

目录 1. 加密的引出2. MD5介绍3. 解决MD5不可解密方法4. 实现加密解密4.1 加密4.2 验证密码 1. 加密的引出 在MySQL数据库中&#xff0c;一般都需要把密码、身份证、电话号码等信息进行加密&#xff0c;以确保数据的安全性。如果使用明文来存储&#xff0c;当数据库被入侵的时…

力扣考研经典题 反转链表

核心思想 头插法&#xff1a; 不断的将cur指针所指向的节点放到头节点之前&#xff0c;然后头节点指向cur节点&#xff0c;因为最后返回的是head.next 。 解题思路 1.如果头节点是空的&#xff0c;或者是只有一个节点&#xff0c;只需要返回head节点即可。 if (head null …

Vatee万腾平台:创新科技,驱动未来

在科技日新月异的今天&#xff0c;每一个创新的火花都可能成为推动社会进步的重要力量。Vatee万腾平台&#xff0c;作为科技创新领域的佼佼者&#xff0c;正以其卓越的技术实力、前瞻性的战略眼光和不懈的探索精神&#xff0c;驱动着未来的车轮滚滚向前。 Vatee万腾平台深知&am…

公有链、私有链与联盟链:区块链技术的多元化应用与比较

引言 区块链技术自2008年比特币白皮书发布以来&#xff0c;迅速发展成为一项具有颠覆性潜力的技术。区块链通过去中心化、不可篡改和透明的方式&#xff0c;提供了一种全新的数据存储和管理方式。起初&#xff0c;区块链主要应用于加密货币&#xff0c;如比特币和以太坊。然而&…

RUST 编程语言 绘制随机颜色图片 画圆形 画矩形 画直线

什么是Rust Rust是一种系统编程语言&#xff0c;旨在提供高性能和安全性。它是由Mozilla和其开发社区创建的开源语言&#xff0c;设计目标是在C的应用场景中提供一种现代、可靠和高效的选择。Rust的目标是成为一种通用编程语言&#xff0c;能够处理各种计算任务&#xff0c;包…