51单片机流水灯制作

51单片机流水灯制作

我不得不承认自己的毅力实在是太差了,原先准备一星期写一篇博客的目标一致没有实现,挺惭愧的。前几日忙着参加学院里面关于飞思卡尔竞赛的选拔赛,被要求制作一个用51单片机实现的流水灯和呼吸灯,耗费了我不少时间。现将其记录于此。

初试与复试

初试

原本我是不打算参加初试的,让我朋友参加,积累经验,我专心鼓捣制作机器人,但我的朋友说我应该试试,耗费时间,但有利于我实现制作机器人的计划。
初试还算简单, 主要考察了数电模电以及c语音和飞思卡尔小车的实现思路等。我两手空空就去了考场,遭到了朋友的笑话,说我要是能入选就奇了怪了。因为开卷,所以基本上数电模电翻书找即可,实现思路之类的只好跟朋友共享其事先打印的资料了。好在脸皮厚,成功抵挡了无数白眼。c语言部分也不算难,有个位移的还算有趣,就是对<<符号的考察。
初试结果自然出乎我朋友的意料,我通过了,而两位朋友成功落选。我自然很是得意。不过他们向组织方表达了自己对飞思卡尔的向往后,还是被允许参加复试了。

复试

任务:
- 设计并制作基于 51 单片机的流水灯,通过按键实现 LED 灯点亮模
式的切换。
- 用 DXP 画出附录原理图, 并保存为.SchDoc 格式。
-
要求
- 基本要求:( 80 分)
- 所搭建的板子至少 8 个 LED 灯, 4 个按键,可实现至少四种 LED
点亮模式的变换。( 50 分)
- 上交一份设计报告, 报告内容包括组元分工、硬件及软件设计,另需在附录附上完整程序。( 20 分)
- 上交附录原理图。( 10 分)
- 发挥部分:( 20 分)
- 电路布局美观, 无虚焊, 焊点光度锥度好。
- 程序运行稳定。
- 其他。

制作

分工

我为队长,其他两人分别攻硬件DXP与软件编程。
先期我们的主要任务是了解51单片机的大致流程,求广,防止方向的选择错误。

制作之硬件

后来因为大家事都挺忙的,我就扛起了大梁。
在网上买了所需的东西后,我便照着葫芦画瓢,在面包板上搭建电路。第一次是在晚上11点左右,果不其然的搭建错了。没有考虑面包板的结构。后来就改正了。
在网上买的PL2303的USB转ttl线出了问题,刚开始是引脚缺杜邦头,后来我自己想办法给焊上去个杜邦头,还是不能用,貌似其内部短路,向周围的人借,也没有借到。只好从网上买了个ch340的usb转ttl线,谢天谢地,这个很好用。usb转ttl线就这样耽误了一个星期。在此处记录一下,usb转ttl线需要驱动,通过该线向单片机里烧录程序需要另外的软件,我用的是STC-ISP(竟然有个每次下载前都重新装载目标文件选项,坑死我了,不知有多少回我忘记点这个选项而没有装载修改后的hex文件去烧录,看着不能按照预期工作的单片机,又投入到keil中,好悲惨的回忆)。
吐槽一下硬件,好多坑啊。电烙铁用到还行,因为没有买用来飞线的导线,用心疼焊锡,只好把排针上金属针拔下来一个一个的焊当导线,好悲催啊。中间好几回虚焊漏焊,其中艰难不足为外人道也。

制作之软件

因为负责软件工作的同学比较忙,所以我接手了程序的开发。他只完成了基本的流水灯部分,而且冗余的代码超多,我又优化了一番。比如说对开关引脚的判断,竟然用了4个if语句,还没有考虑全,我简单几句就搞定了,还有对开关变化的判断。然后开始添加新功能。
因为我觉得单个的呼吸灯可能会比较没有竞争力,所以准备不光实现灯的强度在时间维度上呈三角波,还要实现灯的强度在空间维度上呈三角波,甚至要实现灯的强度在时间、空间上同时呈三角波(就是初相位相差一定值的平行三角波),想想就美好。不光要有三角波,还要有正弦波,再多加个强度相差一定值的正弦波(感觉这个比初相位相差一定知道正弦波计算量小些)。
需求想好了,就该实现了。我需要手动完成PWM。总结了一下实现PWM的几种思路:

  • 用一个定时器定时,如以100μs为一个小周期,然后以100个小周期为一个PWM周期(10ms),频率为100Hz(1s / 10 ms)。在定时器中断处理函数里直接输出buffer中的数据,或者在处理函数中处理判断后输出数据。
  • 用一个定时器定时,比如给通电时间(占空比 * PWM周期)定时,然后在中断处理函数中启动不通电时间(PWM周期 - 通电时间)的定时,可用原来的定时器也可用其他的定时器。
  • 用一个定时器给PWM周期定时,然后用另外一个定时器给通电时间(也可以是不通电时间)定时。
    综合考虑后我选择第一种方法,噩梦才刚刚开始。
    如何实现不同相位的多个平行正弦波的呼吸灯?使用小周期只是提供了实现的可能而已,还不够。后来在操场上散步时,才想明白了实现方式。我靠自己想明白了单极性脉宽调制的原理,相当激动啊。换句话说,我是使用单极性脉宽调制来实现想法的。设置一个数组来存储八个LED的占空比,占空比在时间维度上不断的沿正弦函数变化,在每个小周期的定时器中断时通过占空比与小周期计数的大小比较来确定引脚输出
    然而实际效果不尽人意,几乎就是随意的亮灭。后来我通过keil的仿真是才发现,51单片机的计算能力太差,中断处理函数结束时,下一次中断几乎就出来了。对八个占空比的处理不能在完整的一次完成。几天的努力还是没能实现的自己当初的想法,有些失落,虽然自己收获了不少,特别是感觉自己对c语言小技巧的使用(有些自大啦啦啦)。
    后来也就没办法,只好不用正弦波,只用三角波了。三角波也能玩出新花样。比如两排强度互补的呼吸灯。其实就是在传统意义上的不通电时间时输出对在通电时期输出数据的取反,比如在通电时间是输出0x00,在不通电时间输出0xFF。

总结

其他的好像也没什么可以说的了。好不容易能沉下心写会技术博客的感觉真好!!!

附录:

程序之现实版

#include <STC89C5xRC.H>
#include<intrins.h>
#include<math.h>
#include<stdlib.h>


/*************全局常量***********/
#define uchar unsigned char 
#define uint unsigned int
#define LED P1

/*****************全局变量**********************************/
uchar changed, status_switch = 0x00, status_switch_pre = 0x00;  //status_switch记录当前开关状态,status_switch_pre记录此前开关状态,changed记录开关状态是否变化
uchar counter_PWM = 0, length_PWM = 100;    //定时器0的两次中断时间为一小周期,length_PWM个小周期组成一个PWM大周期,counter_PWM负责计数,表示进行到了PWM大周期中的第counter_PWM个小周期
uchar duty_cycle, buffer;   //duty_cycle是占空比,buffer缓冲LED点亮信息

uchar i, j;
float f;

/*******************部分流水灯模式所需信息**************/
uchar code table1[]={0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80,0x00,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfc,0xff};
        //0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f   左移
uchar code table2[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf}; 
uchar code table3[]={0x7e,0xbd,0xdb,0xe7,0xff};
uchar code table4[]={0xaa,0x55};

/*********函数声明********************/
void delay1ms();
void delay(uint x);
void keyscan();
void close_int();
void init_pwm();
void timer0_init(void);


/********主函数****************************/
void main()
{
    while(1) {
        keyscan(); 

        switch (status_switch) {    //根据status_switch选择灯的点亮模式
        case 0x00:
            LED = 0xFF;
            break;
        case 0x01:
            for (i=0;i<16;i++) { 
                LED=table1[i];     
                delay(200);
                keyscan();     
                if (changed)
                    break;   
            }   
            break; 
        case 0x02:
            for(i=0;i<14;i++) { 
                LED=table2[i];     
                delay(300);
                keyscan();     
                if (changed)
                    break;   
            }
            break;          
        case 0x03:
            for(i=0;i<5;i++) {
                LED=table3[i];     
                delay(250);
                keyscan();     
                if (changed)
                    break;   
            }
            break;          
        case 0x04:
            for(i=0;i<2;i++){
                LED=table4[i];     
                delay(3000);
                keyscan();     
                if (changed)
                    break;      
            }
            break;          
        case 0x05:
            for (i=0;i<16;i++) { 
                LED=~table1[i];     
                delay(200);
                keyscan();     
                if (changed)
                    break;   
            }   
            break; 

        case 0x06:
            for(i=0;i<14;i++) { 
                LED=~table2[i];     
                delay(500);
                keyscan();     
                if (changed)
                    break;   
            }
            break;       
        case 0x07:
            for(i=0;i<5;i++) {
                LED=~table3[i];     
                delay(250);
                keyscan();     
                if (changed)
                    break;   
            }
            break; 

        case 0x08:
        case 0x09:
        case 0x0A:
        case 0x0B:
            timer0_init();
            while (1) {
                init_pwm();     
                for (i = 100; i > 0; i-=5) {
                    duty_cycle = i;             //改变占空比     
                    delay(25);
                    keyscan();                  //开关检测
                    if (changed) {
                        close_int();            //关闭定时器
                        break;                  //退出for语句
                    }
                }
                break;                          //退出switch语句
                for (i = 0; i < 100; i+=5) {
                    duty_cycle = i;
                    delay(25);
                    keyscan();                  //在该case语句中设置两个keyscan是为了更快的对开关变化做出响应
                    if (changed) {
                        close_int();
                        break;
                    }
                }
                break;      
            }
            break;

        case 0x0C:          
        case 0x0D:
            timer0_init();
            while (1) {
                init_pwm();                 
                delay(250);         //因为不需要改变有时间维度上的变化,所以没有占空比的改变,只需延时即可

                keyscan();
                if (changed) {
                    close_int();
                    break;
                }
            }
            break;

        case 0x0E:
            srand(counter_PWM);  //设置随机数种子
            while(1) {
                LED = rand() % 256;     //保证随机数在0与255之间
                delay(200);

                keyscan();
                if (changed)
                    break;  
            }

            break; 
        case 0x0F:  
            LED = 0x00;
            break;
        } 
    }
}

/*****************延时1ms*************/
void delay1ms()     //@11.0592MHz
{
    unsigned char i, j;

    _nop_();
    i = 2;
    j = 199;
    do
    {
        while (--j);
    } while (--i);
}   

/**********延时x毫秒***********/
void delay(uint x)
{   uchar k;
    for(k=x; k>0; k--)
        delay1ms();
}

/****开关检测。修改开关状态(status_switch)以及更改标志(changed)****/
void keyscan()  
{ 
    status_switch_pre = status_switch;          //记录此前的开关状态
    P2 = 0xFF;                                  //准备读取P2
    status_switch = (~P2) & 0x0F;              //读取P2并提取有用信息
    changed = (status_switch != status_switch_pre);         //判断是否改变 
}  

/*****关闭定时器0***********************/
void close_int()
{
    TR0 = 0;//关闭Timer0
    ET0 = 0;//关闭T0中断
    LED = 0xFF;//PWM输出高电平
}

/*************PWM的初始化******/
void init_pwm()
{
    buffer = 0x00;      //清空缓存区

    duty_cycle = 0;     //归零占空比
}

/*****************定时器0************************/
void timer0_init(void)      //100微秒@11.0592MHz
{
    AUXR &= 0x7F;       //定时器时钟12T模式
    TMOD &= 0xF0;       //设置定时器模式
    TMOD |= 0x02;       //设置定时器模式
    TL0 = 0xA4;     //设置定时初值
    TH0 = 0xA4;     //设置定时重载值
    TF0 = 0;        //清除TF0标志
    TR0 = 1;        //定时器0开始计时

    ET0=1;          //开启T0中断
    EA=1;           //开总中断
}

/*************定时中断1处理程序****************/
void timer0_int(void) interrupt 1   
{

    switch (status_switch) {
    case 0x08:
/**********单个灯光强度在时间维度上呈三角形波*********/
        buffer = 0x00;
        buffer |= (counter_PWM < duty_cycle) << 0;
        break;   
    case 0x09:
/*****两半灯光强度在空间维度上呈互补三角形波**************************/
        if (counter_PWM < duty_cycle)
            buffer = 0xF0;
        else
            buffer = 0x0F;
        break;
    case 0x0A:
/*****交叉灯的灯光强度在空间维度上呈互补三角形波**************************/
        if (counter_PWM < duty_cycle)
            buffer = 0x55;
        else
            buffer = 0xAA;
        break;
    case 0x0B:
/*****全部灯光强度在时间维度上呈三角形波***************/
        if (counter_PWM < duty_cycle)
            buffer = 0xFF;
        else
            buffer = 0x00; 
        break;

    case 0x0C:
/***************灯光强度在空间维度上呈中凸三角形波************/
        if (counter_PWM < 5)                //原谅我不是完美的三角形波
            buffer = 0xFF;
        else if (counter_PWM < 20)
            buffer = 0x7E;
        else if (counter_PWM <45)
            buffer = 0x3C;
        else
            buffer = 0x18;
        break;
    case 0x0D:
/********灯光强度在空间维度上呈中凹三角形波******************/
        if (counter_PWM < 5)
            buffer = _crol_(0xFF, 4);
        else if (counter_PWM < 20)
            buffer = _crol_(0x7E, 4);
        else if (counter_PWM <45)
            buffer = _crol_(0x3C, 4);
        else
            buffer = _crol_(0x18, 4);
        break;          
    }

    LED = ~buffer;                 //引脚的1代表灯灭,所以反转一下

    if (counter_PWM >= length_PWM)  
        counter_PWM = 0;            //将counter_PWM限制在0到length_PWM之间
    counter_PWM++;
}

程序之理想版

#include <STC89C5xRC.H>
#include<intrins.h>
#include<math.h>

/*************全局常量***********/
#define uchar unsigned char 
#define uint unsigned int
#define LED P1
#define PI 3.1415926

uchar i, j;
float f;

/*****************全局变量**********************************/
uchar changed, status_switch = 0x00, status_switch_pre = 0x00;
uchar counter_PWM = 0, length_PWM = 100, buffer;
uchar duty_cycles[8];

/*******************部分流水灯模式所需信息**************/
uchar code table1[]={0xfe,0xfc,0xf8,0xf0,0xe0,0xc0,0x80,0x00,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfc,0xff};
        //0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f   左移
uchar code table2[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf}; 
uchar code table3[]={0x7e,0xbd,0xdb,0xe7,0xff};
uchar code table4[]={0xaa,0x55};

/*********函数声明********************/
void delay1ms();
void delay(uint x);
void keyscan();
void close_int();
void init_pwm();
void timer0_init(void);





/********主函数*************/
void main()
{
    while(1) {
        keyscan(); 

        switch (status_switch) {
        case 0x00:
            LED = 0xFF;
            break;
        case 0x01:
            for (i=0;i<16;i++) { 
                LED=table1[i];     
                delay(200);
                keyscan();     
                if (changed)
                    break;   
            }   
            break; 
        case 0x02:
            for(i=0;i<14;i++) { 
                LED=table2[i];     
                delay(300);
                keyscan();     
                if (changed)
                    break;   
            }
            break;          
        case 0x03:
            for(i=0;i<5;i++) {
                LED=table3[i];     
                delay(250);
                keyscan();     
                if (changed)
                    break;   
            }
            break;          
        case 0x04:
            for(i=0;i<2;i++){
                LED=table4[i];     
                delay(300);
                keyscan();     
                if (changed)
                    break;      
            }
            break;          
        case 0x05:
            for (i=0;i<16;i++) { 
                LED=~table1[i];     
                delay(200);
                keyscan();     
                if (changed)
                    break;   
            }   
            break; 

        case 0x06:
            for(i=0;i<14;i++) { 
                LED=~table2[i];     
                delay(300);
                keyscan();     
                if (changed)
                    break;   
            }
            break;

        case 0x07:
            for(i=0;i<5;i++) {
                LED=~table3[i];     
                delay(250);
                keyscan();     
                if (changed)
                    break;   
            }
            break; 

        case 0x08:
/**********灯光强度在时间维度上呈三角形波*********/            
            timer0_init();
            while (1) {
                init_pwm();

                for (i = 100; i > 0; i--) {
                    duty_cycles[j] = i;
                    buffer |= ((counter_PWM < duty_cycles[j]) << j);   //j代表第几盏灯,第一盏灯时j=0,因为没有指明j,所以每次都有不同的灯作为呼吸灯亮              
                    delay(50);
                    keyscan();
                    if (changed) {           //在此插入changed的判断语句是为了更好的对开关变化做出响应
                        close_int();
                        break;
                    }
                }
                for (i = 0; i < 100; i++) {
                    duty_cycles[j] = i;
                    buffer |= ((counter_PWM < duty_cycles[j]) << j);   //j代表第几盏灯,第一盏灯时j=0;              
                    delay(50);
                    keyscan();
                    if (changed) {
                        close_int();
                        break;
                    }
                }
            }
            break;       
        case 0x09:
/***************灯光强度在空间维度上呈三角形波************/
            timer0_init();
            for (j = 0; j < 4; j++)
                duty_cycles[j] = 25 * j;
            for (j = 4; j < 8; j++)
                duty_cycles[j] = 25 * (8 - j);

            while (1) {
                init_pwm();             
                for (j = 0; j < 8; j++) {
                    buffer |= ((counter_PWM < duty_cycles[j]) << j);                
                }
//                  if (counter_PWM < 25)
//                      buffer = 0xFF;
//                  else if (counter_PWM < 50)
//                      buffer = 0x7E;
//                  else if (counter_PWM < 75)
//                      buffer = 0x3C;
//                  else
//                      buffer = 0x18;
                delay(100);

                keyscan();
                if (changed) {
                    close_int();
                    break;
                }       
            }
            break;          
        case 0x0A:
/********灯光强度在时间、空间维度上呈三角形波******************/
            timer0_init();
            while (1) {
                init_pwm();
                for (i = 0; i < 100; i++) {                         
                    for (j = 0; j < 4; j++)
                        duty_cycles[j] = (25 * j + i) % 100;
                    for (j = 4; j < 8; j++)
                        duty_cycles[j] = (25 * (8 - j) + i) % 100;  
                    for (j = 0; j < 8; j++)
                        buffer |= ((counter_PWM < duty_cycles[j]) << j);                    
                    delay(50);

                    keyscan();
                    if (changed) {
                        close_int();
                        break;
                    }
                }
                for (i = 100; i > 0; i--) {                         
                    for (j = 0; j < 4; j++)
                        duty_cycles[j] = (25 * j + i) % 100;
                    for (j = 4; j < 8; j++)
                        duty_cycles[j] = (25 * (8 - j) + i) % 100;  
                    for (j = 0; j < 8; j++)
                        buffer |= ((counter_PWM < duty_cycles[j]) << j);  
                    delay(50);

                    keyscan();
                    if (changed) {
                        close_int();
                        break;
                    }
                }
            }
            break; 
        case 0x0B:
/*****灯光强度在时间维度上呈正弦***************/
            timer0_init();
            while (1) {
                init_pwm(); 

                for (i = 0; i < 180; i++) {
                    duty_cycles[j] = (uchar)(sin(PI * i / 180) * 100);  //j号灯为呼吸灯
                    buffer |= ((counter_PWM < duty_cycles[j]) << j); 
                    delay(50);

                    keyscan();
                    if (changed) {
                        close_int();
                        break;
                    }
                }
            }
            break;
        case 0x0C:
/*****灯光强度在空间维度上呈正弦波**************************/
            timer0_init();
            while (1) {             
                init_pwm();
                for (j = 0; j < 8; j++) {
                    duty_cycles[j] = (uchar)(sin(PI * ((int)(i + 22.5 * j) % 180) /180) * 100);
                    buffer |= (counter_PWM < duty_cycles[j]) << j;
                }
                delay(100); 

                keyscan();
                if (changed) {
                    close_int();
                    break;
                }
            }
            break; 
        case 0x0D:
/**灯光强度在时间维度上呈三角形波,空间上为相位相差22.5度的数个平行正弦波**/
            timer0_init();
            while (1) {
                init_pwm();             
                for (i = 0; i < 180; i++) {
                    buffer = 0x00;
                    for (j = 0; j < 8; j++) {
                        duty_cycles[j] = (uchar)(sin(PI * ((int)(i + 22.5 * j) % 180) /180) * 100);
                        buffer |= (counter_PWM < duty_cycles[j]) << j;
                    }
                    delay(10);

                    keyscan();
                    if (changed) {
                        close_int();
                        break;
                    }
                }
            }
            break;
        case 0x0E:
/*灯光强度在时间维度上呈正弦波,空间上为强度相差22级的平行正弦波*/
            timer0_init();
            while (1) {             
                for (i = 0; i < 180; i++) {
                    buffer = 0x00;
                    for (j = 0; j < 8; j++) {
//                      duty_cycles[j] = (uchar)((int)(sin(PI * i / 180) * 100 + 22) % 100);
                        duty_cycles[j] = (uchar)(sin(PI * i / 180) * 100 + 22);

                        buffer |= (counter_PWM < duty_cycles[j]) << j;
                    }
                    delay(10);

                    keyscan();
                    if (changed) {
                        close_int();
                        break;
                    }
                }
            }
            break;
        case 0x0F:  
            LED = 0x00;
            break;
        } 
    }
}

/*****************延时1ms*************/
void delay1ms()     //@11.0592MHz
{
    unsigned char i, j;

    _nop_();
    i = 2;
    j = 199;
    do
    {
        while (--j);
    } while (--i);
}   

/**********延时x毫秒***********/
void delay(uint x)
{   uchar k;
    for(k=x; k>0; k--)
        delay1ms();
}

/****开关检测。修改开关状态(status_switch)以及更改标志(changed)****/
void keyscan()  
{ 
    status_switch_pre = status_switch;
    P2 = 0xFF;
    status_switch = (~P2) & 0x0F;  
    changed = (status_switch != status_switch_pre); 
}  

/*****关闭定时器0***********************/
void close_int()
{
    TR0 = 0;//关闭Timer0
    ET0 = 0;//关闭T0中断
    LED = 0xFF;//PWM输出高电平
}

/*************PWM的初始化******/
void init_pwm()
{
    uchar k;

    buffer = 0x00;
    for (k = 0; k < 8; k++)
        duty_cycles[k] = 0;
}

/*****************定时器0************************/
void timer0_init(void)      //100微秒@11.0592MHz
{
    AUXR &= 0x7F;       //定时器时钟12T模式
    TMOD &= 0xF0;       //设置定时器模式
    TMOD |= 0x02;       //设置定时器模式
    TL0 = 0xA4;     //设置定时初值
    TH0 = 0xA4;     //设置定时重载值
    TF0 = 0;        //清除TF0标志
    TR0 = 1;        //定时器0开始计时

    ET0=1;          //开启T0中断
    EA=1;           //开总中断
}

/*************定时中断1处理程序****************/
void timer0_int(void) interrupt 1   
{   
    LED = ~buffer;                 //引脚的1代表灯灭,所以反转一下

    if (counter_PWM >= length_PWM)
        counter_PWM = 0;            //将counter_PWM限制在0到length_PWM之间
    counter_PWM++;
}

猜你喜欢

转载自blog.csdn.net/Tifa_Best/article/details/50358500