多个LED以不同频率运行程序变形
本篇带你一步一步走进多任务,用非常接地气的方式带你了解多任务,无论是嵌入式小白还是入行多年嵌入式大牛都有一定的借鉴作用,接下来我们多种方法来实现三个LED灯以不同的频率闪烁
实验内容:黄色灯1Hz(500ms),蓝色灯2Hz(250ms),红色灯4Hz(125ms)
引脚关系:黄色灯->P0.0、蓝色灯->P0.1、红色灯->P0.2
方案一:在大循环中计数的方式
实验效果:
程序代码:main.c文件
/*************************************
** 文件名称:main.c
** 描 述:LED灯闪烁
** 实验平台:STC12C5A60S2最小系统板 + 面包板 + 分立元器件
** 日 期:2020/01/25
** 作 者:wllis
** 历史记录:
硬件连接:
YELLOW_LED->P0.0
BLUE_LED ->P0.1
RED_LED ->P0.2
**************************************/
#include "main.h"
#include "LED.h"
/*********** 变量定义 *************/
#define SYSTEM_DELAY 5 // 系统延时5ms
#define YELLOW_LED_TASK_TIME 100 // SYSTEM_DELAY*100 = 500ms任务
#define BLUE_LED_TASK_TIME 50 // SYSTEM_DELAY*50 = 250ms任务
#define RED_LED_TASK_TIME 25 // SYSTEM_DELAY*25 = 125ms任务
uint8_t YELLOW_LED_Task_Count = YELLOW_LED_TASK_TIME;
uint8_t BLUE_LED_Task_Count = BLUE_LED_TASK_TIME;
uint8_t RED_LED_Task_Count = RED_LED_TASK_TIME;
/*************************************
** 函 数 名:main
** 输入参数:none
** 返 回 值:none
** 说 明:主函数
**************************************/
void main ( void )
{
while(1)
{
// 1Hz黄色LED灯闪烁任务
if(YELLOW_LED_Task_Count<=0)
{
YELLOW_LED_Task_Count = YELLOW_LED_TASK_TIME;
LED_Togle( YELLOW_LED );
}
// 2Hz蓝色LED灯闪烁任务
if(BLUE_LED_Task_Count<=0)
{
BLUE_LED_Task_Count = BLUE_LED_TASK_TIME;
LED_Togle( BLUE_LED );
}
// 4Hz绿色LED灯闪烁任务
if(RED_LED_Task_Count<=0)
{
RED_LED_Task_Count = RED_LED_TASK_TIME;
LED_Togle( RED_LED );
}
YELLOW_LED_Task_Count--;
BLUE_LED_Task_Count--;
RED_LED_Task_Count--;
// 5ms 系统延时
delay( SYSTEM_DELAY );
}
}
/*************************************
** 函 数 名:delay
** 输入参数:需要延时ms数,16位无符号整数
** 返 回 值:none
** 说 明:延时函数
**************************************/
void delay( uint16_t time )
{
while(--time){ Delay1ms(); }
}
/*************************************
** 函 数 名:Delay1ms
** 输入参数:none
** 返 回 值:none
** 说 明:1ms延时函数
**************************************/
void Delay1ms( void ) //@11.0592MHz
{
uint8_t i, j;
_nop_();
_nop_();
_nop_();
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
}
/************* end of file **********/
main.h文件
/*************************************
** 文件名称:main.h
** 描 述:公共头文件
** 实验平台:STC12C5A60S2最小系统板 + 面包板 + 分立元器件
** 日 期:2020/01/24
** 作 者:wllis
** 历史记录:
**************************************/
#ifndef __MAIN_H
#define __MAIN_H
#include
#include
/****** 变量类型定义 defines ******/
typedef unsigned char uint8_t;
typedef unsigned int uint16_t;
void delay( uint16_t time );
void Delay1ms( void );
#endif
/************* end of file **********/
LED.c文件
/*************************************
** 文件名称:LED.c
** 描 述:LED控制源文件
** 实验平台:STC12C5A60S2最小系统板 + 面包板 + 分立元器件
** 日 期:2020/01/25
** 作 者:wllis
** 历史记录:
硬件连接:
YELLOW_LED->P0.0
BLUE_LED ->P0.1
RED_LED ->P0.2
**************************************/
#include "LED.h"
/*************************************
** 函 数 名:LED_Togle
** 输入参数:需要翻转的LED
** 返 回 值:none
** 说 明:LED翻转函数
**************************************/
void LED_Togle( uint8_t LED )
{
switch(LED)
{
case YELLOW_LED:
YELLOW_LED_PIN = ~YELLOW_LED_PIN;
break;
case BLUE_LED:
BLUE_LED_PIN = ~BLUE_LED_PIN;
break;
case RED_LED:
RED_LED_PIN = ~RED_LED_PIN;
break;
default:
break;
}
}
/************* end of file **********/
LED.h文件
/*************************************
** 文件名称:LED.h
** 描 述:LED控制头文件
** 实验平台:STC12C5A60S2最小系统板 + 面包板 + 分立元器件
** 日 期:2020/01/25
** 作 者:wllis
** 历史记录:
硬件连接:
YELLOW_LED->P0.0
BLUE_LED ->P0.1
RED_LED ->P0.2
**************************************/
#ifndef __LED_H
#define __LED_H
#include "main.h"
#define YELLOW_LED 1
#define BLUE_LED 2
#define RED_LED 3
/*********** 引脚定义 *************/
sbit YELLOW_LED_PIN = P0^0;
sbit BLUE_LED_PIN = P0^1;
sbit RED_LED_PIN = P0^2;
/*********** 函数定义 *************/
void LED_Togle( uint8_t LED );
#endif
/************* end of file **********/
实验按照我们预想的那样,实现了三个LED灯以不同频率闪烁;这个例子基本实现了多任务执行,任务执行的最小周期取决于我们的系统周期,像该例子中,系统周期就是5ms(200Hz);那么我们想要实现高于200Hz执行的任务可以修改系统延时SYSTEM_DELAY,参考LED闪烁的任务实现方式尝试添加其他任务 ,并验证实验效果。
思考:我们注意到while循环中用到了等待延时函数delay( SYSTEM_DELAY );,我们觉得这样还不够,有没有方法可以在main函数大循环中不用任何延时来实现不同频率LED灯的闪烁,大家可以短暂的思考下,带着对问题的思考我们一起来看下方案二的实现方式,或许会给你一些启发。
方案二:采用定时器
我们知道无论哪种单片机都有定时器,像高级一点的单片机还有专门的系统定时器(systick),比如说: STM32,那么同样是减法运算,我们把它放在在定时器中来进行操作。
为了区别于方案1,我们把每个任务的周期改一下:
实验内容:黄色灯4Hz(125ms),蓝色灯2Hz(250ms),红色灯1Hz(500ms)
引脚关系:黄色灯->P0.0、蓝色灯->P0.1、红色灯->P0.2
定时器:我们这里选用T0,定时器周期计算参考宏晶公司提供的软件里面的示例代码,我们这里应为没有让定时器工作在1T状态,所以跟传统单片机是一样的操作方式。
实验效果
程序代码:main.c源文件
/*************************************
** 文件名称:main.c
** 描 述:三个LED灯以不同频率闪烁
** 实验平台:STC12C5A60S2最小系统板 + 面包板 + 分立元器件
** 日 期:2020/01/25
** 作 者:wllis
** 历史记录:
硬件连接:
YELLOW_LED->P0.0
BLUE_LED ->P0.1
RED_LED ->P0.2
**************************************/
#include "main.h"
#include "LED.h"
/*********** 变量定义 *************/
#define SYSTEM_DELAY 1000 // 系统周期1ms
#define YELLOW_LED_TASK_TIME 125 // SYSTEM_DELAY*0.125 = 125ms任务
#define BLUE_LED_TASK_TIME 250 // SYSTEM_DELAY*0.25 = 250ms任务
#define RED_LED_TASK_TIME 500 // SYSTEM_DELAY*0.5 = 500ms任务
uint8_t YELLOW_LED_Task_Count = YELLOW_LED_TASK_TIME;
uint8_t BLUE_LED_Task_Count = BLUE_LED_TASK_TIME;
uint8_t RED_LED_Task_Count = RED_LED_TASK_TIME;
/*************************************
** 函 数 名:main
** 输入参数:none
** 返 回 值:none
** 说 明:主函数
**************************************/
void main ( void )
{
// 系统定时器0初始化
SYSTEM_T0_Init( );
// 使能总中断,这样定时器0才会启动
EA = 1;
while(1)
{
// 1Hz黄色LED灯闪烁任务
if(YELLOW_LED_Task_Count<=0)
{
YELLOW_LED_Task_Count = YELLOW_LED_TASK_TIME;
LED_Togle( YELLOW_LED );
}
// 2Hz蓝色LED灯闪烁任务
if(BLUE_LED_Task_Count<=0)
{
BLUE_LED_Task_Count = BLUE_LED_TASK_TIME;
LED_Togle( BLUE_LED );
}
// 4Hz绿色LED灯闪烁任务
if(RED_LED_Task_Count<=0)
{
RED_LED_Task_Count = RED_LED_TASK_TIME;
LED_Togle( RED_LED );
}
}
}
/*************************************
** 函 数 名:SYSTEM_T0_Init
** 输入参数:none
** 返 回 值:none
** 说 明:T0初始化函数
**************************************/
void SYSTEM_T0_Init( void )
{
// 定时器0配置为16位定时器,当溢出时手工重装
TMOD &= 0xF0; // 清除所有有关T0的位 (T1不变)
TMOD |= 0x01; // 设置所需的T0相关位 (T1 不变)
// 设置定时器重装值
TR0 = 0; // 停止定时器0
// STC12C5A60S2是1T的单片机,跟传统51单片机的指令周期12T有差别
// 我们这里设置1ms产生一次中断
TL0 = 65536 -(11059200/12/SYSTEM_DELAY); // 定时器低8位赋值
TH0 = (65536-(11059200/12/SYSTEM_DELAY))>>8; // 定时器高8位赋值
// 启动T0
TR0 = 1;
// 使能定时器T0中断
ET0 = 1;
}
/*************************************
** 函 数 名:SYSTEM_Tick_Update() interrupt 1
** 输入参数:none
** 返 回 值:none
** 说 明:定时器T0中断入口函数
**************************************/
void SYSTEM_Tick_Update( void ) interrupt 1
{
// 设置定时器重装值
TR0 = 0; // 停止定时器0
// STC12C5A60S2跟传统T0定时器设置一样
// 我们这里设置1ms产生一次中断
TL0 = 65536-(11059200/12/1000); // 定时器低8位赋值
TH0 = (65536-(11059200/12/1000))>>8; // 定时器高8位赋值
// 启动T0
TR0 = 1;
YELLOW_LED_Task_Count--;
BLUE_LED_Task_Count--;
RED_LED_Task_Count--;
}
/************* end of file **********/
main.h头文件
/*************************************
** 文件名称:main.h
** 描 述:公共头文件
** 实验平台:STC12C5A60S2最小系统板 + 面包板 + 分立元器件
** 日 期:2020/01/24
** 作 者:wllis
** 历史记录:
**************************************/
#ifndef __MAIN_H
#define __MAIN_H
#include
#include
/****** 变量类型定义 defines ******/
typedef unsigned char uint8_t;
typedef unsigned int uint16_t;
void SYSTEM_T0_Init( void );
#endif
/************* end of file **********/
LED灯部分的头文件和源文件没有改动,大家可以参考方案一的代码。
方案二多任务原理:采用定时器T0的1ms周期性中断操作来对每个任务要计时的量来进行减法运算,当减到零时,运行任务,然后再重新进行任务延时赋值。
总结:
1、两种多任务方案都能满足小规模嵌入式系统的要求,并且添加多个任务也非常方便
2、采用任务方式编写程序,我们更多的时候不是在写代码,而是在考虑如何让更多的代码可重复利用以及修改的方便性
3、方案二已经接近时间片轮询法系统雏形,可以移植到其他单片机上同样能使用
预告:接下来我们将对方案二中的多任务程序进行进一步改造,使之能适应不同功能需求,并且能方便的添加任务,从而达到我们对一个简单的多任务系统的要求,同时在接下来的讲解中还会牵涉到数据结构,我们也大可不必担心,因为只使用到了其中一部分数据结构,大家可以提前去预习下。
可能很多人在大学里面把数据结构学完,考试完都不清楚数据结构有些什么用途。
给大家推荐一本书,大家可以参考下:
https://pan.baidu.com/link/zhihu/7lhnzbuchxiVUtVE1GbLhK1GZXU1E1TQQZhU==
同样的,还是希望大家多多支持正版书籍,每一本好书都值得你多次去阅读!!