A005-软件结构-前后台结构

前后台结构

-------------------------------------------------------------------------------------------------------------------------------------

开发环境:AVR Studio 4.19 + avr-toolchain-installer-3.4.1.1195-win32.win32.x86

芯片型号:ATmega16

芯片主频:8MHz

-------------------------------------------------------------------------------------------------------------------------------------

一、  概述

1、前后台

前台:用来处理输入输出,一般在中断中进行

后台:用来处理逻辑判断、任务计算等,一般交由CPU处理

事件:前后台之间使用事件互相通信,告知事件的发生和结束,这也称消息管理

事件/消息管理是前后台的核心。

关于事件/消息管理,参考这篇文章(消息机制在软件设计中的应用):http://www.xuebuyuan.com/1777265.html

-------------------------------------------------------------------------------------------------------------------------------------

2、前后台的运行过程

下面这个示例包含三部分:[按键输入,红外输入,数码管输出] + [事件管理] + [数值计算与存储]

在这个示例中、将使用定时器0的OCF0中断定时检测按键和刷新数码管、使用定时器1的ICP1中断接收红外信号。

如果中断中监测到按键1被按下、就将“事件_按键1按下”设置为1,表示这个事件发生,否则为0。

如果中断中收到红外码2、就将“事件_接收红外2”设置为1,表示这个事件发生,否则为0。

其余事件也是以同样的方式产生、所有事件都保存在事件队列中,供CPU实时查询。

这些事件并不直接调用对应的任务API,而只是设置消息标志。

到此、前台中断的任务结束,不再参与后续的操作。

在后台、CPU会循环监测消息队列,如果发现“事件_按键1按下”标志为1,就调度API"任务_显示001"去执行。

同时、如果后台CPU处理数值计算完毕,也不直接将数值送给前台的数码管显示,而是设置“事件_计算结束”标志为1,然后就此结束。

CPU监测到“事件_计算结束”标志为1时 ,才调度数码管的API"任务_更新显示"去执行,修改数码管显示的数据。

在中断中、数码管实时刷新后,就可以看到数值计算的结果了。

也就是说、前台的消息通过消息队列,将信息传递给CPU

CPU通过与前台任务关联的API来控制和传送数据给前台任务。

前后台之间互相隔离,当然也可以使用消息队列来联系前后台。

-------------------------------------------------------------------------------------------------------------------------------------

3、事件/消息机制的好处

这样的设计、将各个任务模块之间互相隔离,形成松散的联系,软件构成很清晰。

降低了系统的总体复杂度,也就是将系统拆分成了很多小模块,他们互相独立。

而互相独立的模块很容易被单独修改和替换、而又不会影响到其他模块。

-------------------------------------------------------------------------------------------------------------------------------------

二、  代码实现

说明:

1、下面将分几个步骤实现上面的示例:

第一步:前台定时中断调度模块,使用定时器0分时调度每个定时任务,包括数码管和按键扫描等,并给出操作任务的API

第二步:事件/消息管理模块,作为前后台之间的通信模块。

第三步:前台实时任务,包括红外接收等,并给出操作任务的API

第四步:后台数值计算任务。

第一步:  前台定时任务调度模块

说明:

1、这里将建立一个分时调度模块,用来分时调度3个定时任务

代码:

config.h中用到的一些定义:

#ifndef _countof
#define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0]))
#endif

#ifndef NULL
#define NULL    0
#endif

sys.timer.c中的调度模块:

#include <avr/interrupt.h>
#include "Drv_Timer.h"
#include "sys_timer.h"

typedef void(*p_task_funtion)(void);
typedef struct
{
    uint8_t delay;          // 任务延时计数
    uint8_t period;         // 任务运行间隔
    p_task_funtion task;    // 任务函数
}T_sys_task, *pT_sys_task;

// 这个数组用来注册和管理任务队列
static T_sys_task sys_task_ctrl[10];
static pT_sys_task p_sys_task_ctrl = sys_task_ctrl;

// ==========================================================================================================
// 初始化系统任务队列
//
// ==========================================================================================================
void sys_task_init(void)
{
    uint8_t index = 0;

    for(index = 0; index < _countof(sys_task_ctrl); index++)
    {
        (p_sys_task_ctrl + index)->delay  = 0;
        (p_sys_task_ctrl + index)->period = 0;
        (p_sys_task_ctrl + index)->task   = NULL;
    }
}

// ==========================================================================================================
// 添加任务到任务队列
//
// ==========================================================================================================
void sys_task_add(uint8_t delay, uint8_t period, p_task_funtion task)
{
    uint8_t index = 0;

    for(index = 0; index < _countof(sys_task_ctrl); index++)
    {
        if(NULL == (p_sys_task_ctrl + index)->task) { break; }
    }
    if(index >= _countof(sys_task_ctrl)) { return; }

    (p_sys_task_ctrl + index)->delay  = delay;
    (p_sys_task_ctrl + index)->period = period - 1;
    (p_sys_task_ctrl + index)->task   = task;
}

// ==========================================================================================================
//      系统任务定时器
//
// (1). 使用Timer0的OCF0中断产生1ms的时标
//
// ==========================================================================================================
void sys_timer_init(void)
{
    // 定时器0初始化:CTC模式、OC0引脚不连接、64预分频
    Drv_Timer0_init(T0_WGM_CTC, COM_MODE_NONE, T0_CLK_SOURCE_CLK_64);
    // 设置初值:TCNT0=0、OCR0=122
    Drv_Timer0_set_TCNT0_OCR0(0, 122);
    // 使能OCF0中断
    Drv_Timer0_INT_Enable(INT_MODE_OCF, ENABLE);
}

// ==========================================================================================================
//      系统定时器中断(中断周期=1ms)
//
// (1). 使用Timer0的CTC中断调度各个任务
// (2). sys_task_add中将delay初始化为0,意味着第1次进入这个中断、就会执行这个任务
//
// ==========================================================================================================
volatile uint8_t temp2016; // 调试用
ISR(TIMER0_COMP_vect)
{
    uint8_t index = 0;

    for(index = 0; index < _countof(sys_task_ctrl); index++)
    {
        temp2016 = index;
        if(NULL != (p_sys_task_ctrl + index)->task)
        {
            if(0 == (p_sys_task_ctrl + index)->delay)
            {
                (p_sys_task_ctrl + index)->delay = (p_sys_task_ctrl + index)->period;
                (p_sys_task_ctrl + index)->task();
            }
            else
            {
                (p_sys_task_ctrl + index)->delay--;
            }
        }
    }
}

Mod_LED_display.c中的数码管任务:

// ==========================================================================================================
//      LED数码管显示数据的刷新
//
// (1). 在系统定时器中定时刷新
//
// ==========================================================================================================
void Mod_LED_display_update(void)
{
    PORTD ^= (1 << PD0);  // 运行时刻标记

    // 熄灭当前数码管、并保持3个时钟周期的熄灭,用来避免余晖
    *p_LED_display_ctrl->seg_code = segment_code[_countof(segment_code) - 1];

    // 切换到下1个数码管
    p_LED_display_ctrl->index++;
    if(p_LED_display_ctrl->index > (_countof(segment_index) - 1))
    {
        p_LED_display_ctrl->index = 0;
    }

    // 修改位选、修改显示
    *p_LED_display_ctrl->seg_index |= segment_index[_countof(segment_index) - 1];
    *p_LED_display_ctrl->seg_index &= segment_index[p_LED_display_ctrl->index];
    *p_LED_display_ctrl->seg_code   = segment_code[p_LED_display_ctrl->data[segment_index[p_LED_display_ctrl->index]]];
}

main.c中初始化3个任务:

// ==========================================================================================================
// 主函数
// ==========================================================================================================
#include <avr/io.h>
#include "Mod_LED_Displayer.h"
#include "sys_timer.h"
#include "system.h"
#include "config.h"

void Mod_test01(void)
{
    PORTD ^= (1 << PD1);  // 运行时刻标记
}

void Mod_test02(void)
{
    PORTD ^= (1 << PD2);  // 运行时刻标记
}

// ==========================================================================================================
// main函数
// ==========================================================================================================
int main(void)
{
    // ------------------------------------------------------------------------------------------------------
    // 关全局中断
    cli();

    // 系统初始化
    sys_init();

    // 开全局中断
    sei();

    // PD[2:0]初始化为输出0
    DDRD  |=   (1 << DDD0) | (1 << DDD1) | (1 << DDD2);
    PORTD &= ~((1 << PD0 ) | (1 << PD1 ) | (1 << PD2 ));

    // 注册3个任务
    sys_task_add(0, 3, Mod_LED_display_update);  // 从时刻0开始运行,以后每隔3个时刻运行一次(1个时刻是1ms)
    sys_task_add(1, 3, Mod_test01);              // 从时刻1开始运行,...
    sys_task_add(2, 3, Mod_test02);              // 从时刻2开始运行,...

    // CPU数值计算
    Mod_LED_display(123456789);

    // ------------------------------------------------------------------------------------------------------
    while(1)
    {
    }
    return 0;
}

运行结果:

1、任务1:数码管显示23456789、同时PD0输出脉冲,周期为3ms

任务2PD1引脚每隔3ms翻转一次电平

任务3PD2引脚每隔3ms翻转一次电平

2、3个任务都是每隔3ms被调度一次,但他们之间互相间隔1ms被调度:

任务1的运行时刻:0, 3, 6, 9, ...

任务2的运行时刻:1, 4, 7, 10, ...

任务3的运行时刻:2, 5, 8, 11, ...

3个任务的调度时刻如下图所示:

也就是说、同一个ms内,只有1个任务被调度运行,这可以保证1ms只有很少的时间在运行前台任务,剩余的时间交给CPU去做后台任务。

示波器输出如下:

CH1PD0的输出,CH2PD1的输出,PD1滞后PD0 1ms

这表示任务2滞后任务1 1ms后被调度,周期为3ms,说明2个任务都是每隔3ms被调度一次。

CH1PD0的输出,CH2PD2的输出,PD2滞后PD0 2ms

这表示任务3滞后任务1 2ms后被调度,也就是说、任务3滞后任务2 1ms后被调度。

任务调度的周期和预期的一致、为3ms

这里实现了定时调度任务,支持10个任务,可以设定每个任务被调度运行的时刻。

两个或多个任务可以在同一时刻被调度,也可以相隔几个时刻被调度。

对于比较耗时的任务,应该单独放在1个时刻内去调度。

测试任务耗时

下面使用PD0粗略测量了数码管刷新任务消耗的时间

代码:

Mod_LED_display.c中的数码管任务中修改PD0如下:

// ==========================================================================================================
//      LED数码管显示数据的刷新
//
// (1). 在系统定时器中定时刷新
//
// ==========================================================================================================
void Mod_LED_display_update(void)
{
    PORTD |= (1 << PD0);  // 运行时刻标记

    // 熄灭当前数码管、并保持3个时钟周期的熄灭,用来避免余晖
    *p_LED_display_ctrl->seg_code = segment_code[_countof(segment_code) - 1];

    // 切换到下1个数码管
    p_LED_display_ctrl->index++;
    if(p_LED_display_ctrl->index > (_countof(segment_index) - 1))
    {
        p_LED_display_ctrl->index = 0;
    }

    // 修改位选、修改显示
    *p_LED_display_ctrl->seg_index |= segment_index[_countof(segment_index) - 1];
    *p_LED_display_ctrl->seg_index &= segment_index[p_LED_display_ctrl->index];
    *p_LED_display_ctrl->seg_code   = segment_code[p_LED_display_ctrl->data[segment_index[p_LED_display_ctrl->index]]];

    PORTD &= ~(1 << PD0);  // 运行时刻标记
}

任务1开始时PD0为高电平,任务1结束后PD0为低电平,也就是说、PD0的高电平时间就是任务1消耗的时间。

这个时间不包含进入和退出任务函数的时间,所以真实时间会稍长一点,但不会差太多。

示波器输出如下:

高电平时间为8.12us,在8MHz下每条指令是0.125us,所以这里大概是执行了66条指令。

同时、8.12us相对于1ms来说,是极其短暂的,只占到8.2%的时间,所以还有91.8%的空闲时间可以交给CPU去处理后台任务。

-------------------------------------------------------------------------------------------------------------------------------------

第二步:  消息管理模块

-------------------------------------------------------------------------------------------------------------------------------------

第三步:  前台实时任务 (红外接收)

-------------------------------------------------------------------------------------------------------------------------------------

第四步:  后台数值计算任务

0

0

000

时间: 2024-08-09 16:51:17

A005-软件结构-前后台结构的相关文章

单片机的非OS的事件驱动

单片机的非OS的事件驱动 Part 1 前言 很多单片机项目恐怕都是没有操作系统的前后台结构,就是main函数里用while无限循环各种任务,中断处理紧急任务.这种结构最简单,上手很容易,可是当项目比较大时,这种结构就不那么适合了,编写代码前你必须非常小心的设计各个模块和全局变量,否则最终会使整个代码结构杂乱无序,不利于维护,而且往往会因为修改了某部分代码而莫名其妙的影响到其他功能,而使调试陷入困境. 改变其中局面的最有效措施当然是引入嵌入式操作系统,但是大多数的操作系统都是付费的(特别是商业项

155242012068-林汉民-实验一

实验报告 课程 软件体系结构与设计   实验名称 软件设计的网络环境  第   页 专业 软件工程     班级  1班      学号  155242012068     姓名  林汉民 实验日期:   2017 年  9 月  14 日   报告退发 (订正 . 重做) 一.实验目的 1.复习软件工程的重要概念,熟悉软件体系结构与软件设计技术的基础概念与内容. 2.通过Internet搜索与浏览,了解网络环境中主流的软件体系结构与设计技术网站,掌握通过专业网站不断丰富软件体系结构和软件设计技

外包十年

老猿信息ID:川川曾用ID:比尔板三出生年月:1979年11月星座:天蝎专业:工业设计职业:程序员爱好:马拉松.越野跑博客:奔跑的猿 工业设计是非常好的专业,但我没有这方面的天分.大学时认识一位同学自学编程通过了高级程序员考试,我对计算机也颇有兴趣,开始学习,先后考过三级.四级.高程.毕业后一直从事软件开发工作,08年奥运前夕来到北京,开始了近10年的外包生涯. 来北京之前,在东北工作六年,从事过ERP实施及维护,开发过自控.病案管理等软件,涉及PB.VB.JAVA等技术.七月份的北京,天气闷热

Java 12网络编程

1.网络编程入门 1.1软件结构 CS结构,全称为Client/Sever结构,是指客户端与服务端的结果,如qq  迅雷 BS结构,全称为Browser/Server结构,是指浏览器和服务端的结构,常见浏览器如谷歌或火狐. 两种架构各有优势,但无论哪种架构,都离不开网络的支持,网络编程也就是在一定的协议下,支持两个计算机的通讯程序. 1.2网络通讯协议 顾名思义,网络通信协议,是指通过计算机网络一遍多台计算机实现连接,位于一个网络中的计算机在连接和通信时需要遵守同一的原则. 重点学习TCP/IP

PHP中字符串的heredoc结构和nowdoc结构

最近在维护一个古老的PHP网站,没有前后台的分离,代码看起来很吃力,上面大段大段的HTML输出.在看的时候发现很多地方用了「<<<」这样的符号来标记字符串,好像之前学习PHP的时候,只讲了单双引号两种表示字符串的方式.今天偶然在书上看到了,这种「<<<」表示字符串的方式是heredoc和nowdoc结构. echo <<<EOT <html> <head><title>主页</title></hea

CI框架3.0版本以后,前后台分离的方法。

笔者认为,CI框架官方其实并没有考虑这个前后台分离的问题,所以没有官方的分离方法.而且,2.0版本的分离,也被官方认为这是一个bug.所以在前后台分离这个问题上,其实并不如thinkphp框架. 在CI框架2.0版本时的,大多数人认为可以这样做,前后台分离是可以直接在controller下,分admin和home目录的. 这是2.0版本时 其实,今天我用的是3.0版本的CI框架.在前后台分离这个问题,我也纠结了比较久.但是为了项目结构目录的清晰,还是要做前后台分离的. 我大概是做了这样一个分离.

结构化开发方法和面向对象开发方法的比较

1. 两者基本思想的比较 1.1结构化方法的基本思想 结构化方法是一种传统的软件开发方法,它是由结构化分析.结构化设计和结构化程序设计三部分有机组合而成的.结构化设计方法是以自顶向下,逐步求精,模块化为基点,以模块化,抽象,逐层分解求精,信息隐蔽化局部化和保持模块独立为准则的设计软件的数据架构和模块架构的方法学.它的要点是是把一个复杂问题的求解过程分阶段进行,而且这种分解是自顶向下,逐层分解,使得每个阶段处理的问题都控制在人们容易理解和处理的范围内. 结构化方法的基本思想主要体现在三个方面. (

ThinkPHP - 配置项目结构

配置项目结构: 项目如果分为前后台使用. 那么最关键的就是,使用公共部分文件的划分,其中最为核心的就是公共配置文件的使用. 下面介绍的就是怎么将前后台项目的公共部分提起出来. 首先是其他公共的文件夹: 这是最顶层的文件配置. 详细的目录说明,看下面: E:\PHP\WWW\THINKPHP │ admin.php //后台主入口文件 │ index.php //前台主入口文件 ├─Admin //后台文件夹 │ ├─Common │ ├─Conf //后台配置文件夹 │ │ config.php

Java Spring技术栈构建完整前后台团购网站

课程目录和下载地址: 第1章 课程整体介绍详细介绍了课程主要内容.背景,并对项目整体流程进行了简要说明,同时对整体需求进行了分析与规划.本章还有详细的项目演示,包括前后台全部功能,看了演示后对整体功能会有一个整体的认识,对于学习后续课程有很大帮助,也能在学习的过程中有的放矢,结合自己的实际情况确定学习的重点....第2章 项目整体设计方案本章主要从技术角度介绍了常用的技术选型的原则以及在技术选型中会遇到哪些陷阱,在此基础上阐述了什么才是恰当的架构设计.我们如果做恰当的架构设计.接下来介绍了项目的