首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你有几种方法写流水灯

你有几种方法写流水灯

作者头像
单片机技术宅
发布2021-02-22 11:17:45
1.1K0
发布2021-02-22 11:17:45
举报
文章被收录于专栏:初学单片机初学单片机

流水灯,学单片机时,编程第一课的内容,多少小伙伴的单片机之路都是从流水灯开始的。那有没有想过,我们能用几种方式来写流水灯,各有什么优缺点呢?今天小代就来聊聊流水灯的写法。

硬件介绍:小代采用自己画的STC15开发板,8位LED接P0口,阳极驱动,共阴连接方式,就这么多。

(实际测试时用的数码管模拟,昨天刚接了线,懒得再次重新接线,直接用数码管来代替LED,原理都一样,能验证就好)

1.初学者入门法

直接上代码看后再说

#include "STC15.h"     
#define u8  unsigned char
#define u16 unsigned int
void delay_ms(u16 x);           //ms延时函数
void main(){
    u8  led=0x01;
    u8  i=0;
    P0M1=0x00;  //STC15单片机把IO设为推挽输出
    P0M0=0xff;
    P2M1=0x00;
    P2M0=0xff;
    P2=0xff;   //数码管位选驱动,硬件介绍上已经说明原因
    while(1){
        //①初学者常见delay延时方式流水灯
        led=0x01;
        for(i=0;i<8;i++){
            P0=led;
            delay_ms(1000);
            led=led<<1;
        }
     }      //while(1) 结束
}//main结束

//************************************
//功能:ms延时函数 
//参数:x--延时的时间长短,x=1000约为1s  24M晶振
//作者:小代
//微信关注:单片机技术宅
//************************************
void delay_ms(u16 x){
    u16 i,j,k;
    for(k=0;k<x;k++){
        for(i=0;i<20;i++){
            for(j=0;j<125;j++);
        }
    }
}

一看代码37行,太多了,仔细一看,注释和空行占据了一大半,这样一想就开心多了。这个代码没有更多需要解释的,采用最常见的,初学者最喜欢的方式,delay延时方式实现,下面聊优缺点。

优点:简单粗暴好理解,能看懂文字(字母)的都知道

缺点:代码执行效率太低,太低,太低

程序流程图如下图所示,CPU更多的时间是花在“等待1s”上,效率太低,单纯的这样演示流水灯,那这个程序没有任何问题,当在这个程序中再加入其它的程序,必然会出问题,第一流水灯会被执行慢了,第二,新加入的功能反应会很慢,特别是一些实时性有要求的代码。加到这里就会有问题了。为了解决这些问题,我们引出了第二种方式的流水灯。

2.记录主程序执行次数方式

还是先上代码再聊

#include "STC15.h"

#define u8 unsigned char

#define u16 unsigned int

//************************************

//功能:主函数入口 main

//参数:无

//作者:小代

//微信关注:单片机技术宅

//************************************

void main(){

u8 led=0x01;

u16 main_cnt=0;

P0M1=0x00; //STC15单片机把IO设为推挽输出

P0M0=0xff;

P2M1=0x00;

P2M0=0xff;

P2=0xff; //数码管位选驱动,硬件介绍上已经说明原因

while(1){

//②主程序循环次数实现延时方式流水灯

main_cnt++; //主程序每运行一次,main_cnt将会被加1

//通过if语句实现内部代码块的运行速率降低

//只有主程序循环60000此后,if语句内的代码块才被执行一次

//如若需要更低的执行速率,可以采用两层变量嵌套计数

P0=led;

if(main_cnt>60000){

main_cnt=0; //次数记录清零,为下一次记录准备

if(led==0x80){

led=0x01;

}

else{

led=led<<1;

}

}

} //while(1) 结束

}

//main结束

还是40+行代码,还是注释+空行占据了一半左右。以上代码最大的特点就是没有之前执行效率很低的delay函数,也算是升级了吧。没了delay延时,那如何实现等待的呢?小代慢慢聊。

在while(1)循环里的第21行,只要主程序每运行一次,21行就会对变量main_cnt加1,这个变量定义为16位无符号类型,最大可以到65535,在此,小代记录60000此后就去点亮下一个流水灯的LED,显示效果比方法1的略快,以上注释也说了,如果单一变量记录次数没法满足需要的等待时间,可以采用两个变量嵌套记录的方式。

通过这种方式点亮LED灯,真正的点灯程序段只有在主程序运行60000后才会被运行到一次。仔细看主程序里面,其实就只有一条语句,那就是对main_cnt做加1处理,完了每次都去判断一下main_cnt有没有到60000了,到了后才去处理LED点灯。下面聊聊优缺点。

优点:程序简单,效率高

缺点:点灯的速度会随着主程序内循环语句的增多而降低。

也就是说如果主程序里增加了其他的运行语句,那这里控制点灯的记录次数值就不再是60000,需要减小,或者说60000得到的效果就会让流水灯变慢。程序流程图如下图

到这里,程序执行效率问题我们已经解决了,在这程序基础上加入其它代码后,各个功能还是能运行起来,但是还是有问题,流水灯的速度会改变,为解决这个问题,小代引出了第三种方法。

3.定时器实现

说到定时器,首先还得知道中断的原理,但是,中断和定时器,学单片机连这两个知识点都没学会,那怎好意思说你学过单片机。也就是说中断和定时器就像单片机驱动IO口一样的普通,一样的简单,不要想的多复杂。还是先上代码再聊。

#include "STC15.h"

#define u8 unsigned char

#define u16 unsigned int

void delay_ms(u16 x); //ms延时函数

void Timer0Init(void); //2毫秒@24.000MHz

u16 timer0_cnt=0;

bit timer0_1s_flag=0;

//************************************

//功能:主函数入口 main

//参数:无

//作者:小代

//微信关注:单片机技术宅

//************************************

void main(){

u8 led=0x01;

u16 main_cnt=0;

P0M1=0x00; //STC15单片机把IO设为推挽输出

P0M0=0xff;

P2M1=0x00;

P2M0=0xff;

P2=0xff; //数码管位选驱动

Timer0Init(); //定时器初始化

while(1){

//③通过定时器中断控制流水灯效果

//主程序里只做LED的驱动

//如果任务少,判断标志位置位都可以放到定时器中断里

//这里统一放到了主循环里

P0=led;

if(timer0_1s_flag==1){

timer0_1s_flag=0; //清除标志位

if(led==0x80){ //是否移动了8次

led=0x01;

}

else{

led=led<<1;

}

}

}//while(1) 结束

}

//main结束

//************************************

//功能:定时器初始化函数

//参数:无

//作者:小代

//微信关注:单片机技术宅

//************************************

void Timer0Init(void) //2毫秒@24.000MHz

{

AUXR |=0x80; //定时器时钟1T模式

TMOD &=0xF0; //设置定时器模式

TL0 =0x80; //设置定时初值

TH0 =0x44; //设置定时初值

TF0 =0; //清除TF0标志

TR0 =1; //定时器0开始计时

ET0=1; //开中断

EA=1;

}

//************************************

//功能:Timer0中断函数 2ms中断一次

//参数:无

//作者:小代

//微信关注:单片机技术宅

//************************************

void timer0_int (void) interrupt 1 {

TF0 =0;

timer0_cnt++;

if(timer0_cnt==500){

timer0_1s_flag=1;

timer0_cnt=0;

}

}

看到这,一看代码量暴增,一下子就是原来的两倍,其实更多的也还是注释,代码量是有所增加,因为用到了定时器和中断,需要对定时器进行初始化,完了还需要一个中断函数。在这个程序,定时T0每2ms中断一次,进入中断后,中断处理函数直接对timer0_cnt变量加1记录,当记录500次后,说明1s时间到,此时置位1s时间标志位timer0_1s_flag=1。在主程序,只做点灯和判断1s时间到来没有,当1s时间到来后,主程序就去处理LED的移位操作。

优点:效率极高,实时性好,时间稳定

缺点:非要说,那就是用到了定时器和中断,初学者会觉得难

通过这种方式实现流水灯,还可以在主程序中加入其它的代码,并不会影响到流水灯的运行。其实这只是一个最简单的模型,在复杂项目中同样可以用这模型,比如,1s点亮一个LED灯,1ms刷新一次数码管,10ms读取一次按键,500ms读取一次ADC的值等等。这样很多的功能都可以采用这样的模型来实现,并且各个功能之间是互不干扰的。

到此,今天的内容聊完了,其实第三种方式就是“状态机”或者“分时处理”等等一些比较高级的名称就是这么一回事。下次再听到这些高大上的名称时,一定要知道,这并不是什么高深的东西。更重要的是在以后的复杂项目中要会用这样的方式去处理各个功能之间的切换。

·END·

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-02-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 单片机技术宅 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档