美文网首页
第一章(1.2—跑马灯的实现)

第一章(1.2—跑马灯的实现)

作者: 穿山甲开源工作室 | 来源:发表于2017-12-08 21:34 被阅读269次

    实物制作视频教程:
    http://v.youku.com/v_show/id_XMzIxNDkzNTYxMg==.html?spm=a2hzp.8244740.0.0
    这次的实物电路是接着上一节的电路制作的,希望看过第一节的读者没有丢弃之前的电路板,若你是第一次看到本视频,点此可阅读前一节内容。

    如果你的计算机无法编译我提供的工程文件,你需要看下面的视频教程
    Keil软件添加STC单片机并创建工程视频教程

    原理图、源代码、元件清单等资料下载 戳我下载

    1.2.1 单片机准双向口的电器特性

    STC15系列单片机IO口默认工作在准双向口模式,与STC89系列单片机不同,STC89系列单片机的P0端口默认为开漏模式。STC15系列单片机准双向口模式的拉电流为150μA~150μA,灌电流为20mA。
    单片机I/O口作为输出使用时,可由软件控制输出0或1,通常称之为逻辑“0”和逻辑“1”,当输出逻辑“0”时,可用万用表测得单片机IO口电压为0V,当输出逻辑“1”时,万用表测得单片机IO口电压为5V或3.3v(3.3v单片机)。
    注:实际上逻辑“0”和逻辑“1”测得电压与本文的理论值有所误差,逻辑“0”通常测得的电压并不是绝对0V,但通常都小于0.1V。逻辑“1”测得的电压会随着工作电压不同而变化。
    原理图中D1至D8八只发光二极管的负极与单片机P0端口的P0.0至P0.7一一对应,所有发光二极管均串联了一只100欧姆的电阻。大多数发光二极管的工作电压在1.8 -3.2V之间。在实际制作过程中,读者根据选用的二极管工作电压选择100至500欧姆的电阻。
    所有LED的阳极串联电阻后,并联到5V电源处,利用发光二极管单向导通的特性,当单片机IO口的输出逻辑“0”时,LED正负极两端的电势差大于导通电压时,LED开始发光工作。
    通过编程,依次控制P0组端口每个I/O的逻辑输出,达到跑马灯效果。
    思考:本原理图中的LED接法称之为“共阳极”接法,共阴极接法应该如何设计电路?

    1.2.2 控制逻辑的实现

    大到机器人,小到传感器,都离不开单片机的I/O口操作,跑马灯就是单片机I/O口的入门课。STC系列单片机可通过如下指令操作某一I/O口。

    P00 = 0;        //P0.0端口输出逻辑“0”
    P01 = 1;        // P0.1端口输出逻辑“1”
    

    用同样的方法,可操作其他组端口:

    P20 = 0;        //P2.0端口输出逻辑“0”
    P31 = 1;        //P3.1端口输出逻辑“1”
    P41 = 1;        //P4.1端口输出逻辑“1”
    

    我们可以这样操作I/O口,是因为单片机的头文件中定义好了这些内容,在STC15F2K60S2.h头文件中,部分定义如下:

    sfr P0          =   0x80;  
    sbit P00        =   P0^0;
    sbit P01        =   P0^1;
    sbit P02        =   P0^2;
    sbit P03        =   P0^3;
    sbit P04        =   P0^4;
    sbit P05        =   P0^5;
    sbit P06        =   P0^6;
    sbit P07        =   P0^7;
    sfr P1          =   0x90;   
    sbit P10        =   P1^0;
    sbit P11        =   P1^1;
    sbit P12        =   P1^2;
    sbit P13        =   P1^3;
    sbit P14        =   P1^4;
    sbit P15        =   P1^5;
    sbit P16        =   P1^6;
    sbit P17        =   P1^7;
    

    也可以通过下面的方法实现I/O口的输出控制。

    P0^0 = 0;
    P1^1 = 1;   
    P2^3 = 1;
    

    在实际的项目开发中,为了提高代码的阅读性及可维护性,经常需要使用“sbit”位定义从新定义IO口,如下面的代码所示。

    #include"STC15F2K60S2.h"
    sbit led0 = P0^0;
    void main()
    {
        led0 = 1; 
        led0 = 0;  
    }
    

    led0 = 1;等价于P0^0 = 0;P00 = 0;这样设计程序的好处是,一旦硬件需要做出修改,只需要在程序开头修改led0所对应的IO口即可。
    跑马灯实际上就是控制8个IO口的逻辑输出,在控制逻辑上可理解为:

    流程图

    根据上面的流程图,可设计较为简单的程序:

    程序C1-2-1

    #include"STC15F2K60S2.h"
    #include"intrins.h"
    
    sbit led1 = P0^0; 
    sbit led2 = P0^1;   
    sbit led3 = P0^2;   
    sbit led4 = P0^3;   
    sbit led5 = P0^4;   
    sbit led6 = P0^5;   
    sbit led7 = P0^6;   
    sbit led8 = P0^7; 
            
    void Delay300ms()       //@11.0592MHz
    {
        unsigned char i, j, k;
    
        _nop_();
        _nop_();
        i = 13;
        j = 156;
        k = 83;
        do
        {
            do
            {
                while (--k);
            } while (--j);
        } while (--i);
    }
    
    
    void main(void)
    {
        
        while(1)
        {
            led1 = 0;
            Delay300ms();
            led1 = 1;
    
            led2 = 0;
            Delay300ms();
            led2 = 1;
    
            led3 = 0;
            Delay300ms();   
            led3 = 1;
    
            led4 = 0;
            Delay300ms();
            led4 = 1;
    
            led5 = 0;
            Delay300ms();
            led5 = 1;
    
            led6 = 0;
            Delay300ms();
            led6 = 1;
    
            led7 = 0;
            Delay300ms();
            led7 = 1;
    
            led8 = 0;
            Delay300ms();
            led8 = 1;
        }
    }
    

    除了前文中操作I/O口的方法,还可以直接操作一组I/O口,示例代码:

    P0 = 0x01;  //P0^0输出1,P0^1至P0^7输出0
    P0 = 0xFF;  //P0^0至P0^7全部输出1
    

    这种写法的优点在于一次性可操作一组端口的8个I/O,1字节十六进制长度是8位,与P0口的8个I/O口相对应,对应关系如表1-2-1所示。


    表1-2-1

    由表1-2-1可知,只需要改变P0端口的赋值,即可实现跑马灯效果,完整代码如下。

    程序代码C1-2-2

    #include"STC15F2K60S2.h"
    #include"intrins.h"
            
    void Delay300ms()       //@11.0592MHz
    {
        unsigned char i, j, k;
    
        _nop_();
        _nop_();
        i = 13;
        j = 156;
        k = 83;
        do
        {
            do
            {
                while (--k);
            } while (--j);
        } while (--i);
    }
    
    void main(void)
    {
        while(1)
        {           
            P0 = 0xFE;  
            Delay300ms();
            P0 = 0xFD;  
            Delay300ms();   
            P0 = 0xFB;  
            Delay300ms();
            P0 = 0xF7;  
            Delay300ms();   
            P0 = 0xEF;  
            Delay300ms();
            P0 = 0xDF;  
            Delay300ms();   
            P0 = 0xBF;  
            Delay300ms();
            P0 = 0x7F;  
            Delay300ms();       
        }
    }
    

    1.2.3代码优化

    代码C1-2-1与代码C1-2-2可以很直观的让程序阅读者读懂代码含义,但这两种写法都显得过于臃肿。下面介绍2种方式优化上述代码。

    通过观察程序C1-2-2,可发现P0端口每次赋值是有规律的,对于才接触单片机的读者,可能难以发现十六进制数的规律,转换为二进制后,能较为容易的发现赋值规律,如表1-2-2所示。


    表1-2-2

    每次赋值都是前一次的数值左移一次,根据此规律,可以设计如下代码:

    程序C1-2-3

    #include"STC15F2K60S2.h"
    #include"intrins.h"
            
    void Delay300ms()       //@11.0592MHz
    {
        unsigned char i, j, k;
    
        _nop_();
        _nop_();
        i = 13;
        j = 156;
        k = 83;
        do
        {
            do
            {
                while (--k);
            } while (--j);
        } while (--i);
    }
    
    void main(void)
    {   
        unsigned char i,m;  
    
        while(1)
        {
            for(i=0,m=0xFE;i<8;i++)
            {           
                P0 = m; 
                m = m<<1;
                Delay300ms();       
            }
    
        }
    }
    

    主函数还可以这样设计

    void main(void)
    {   
        unsigned char i;    
    
        while(1)
        {
            for(i=0;i<8;i++)
            {           
                P0 = 0xFE<<i;   
                Delay300ms();       
            }
        }
    }
    

    思考:两种主函数对比,能发现什么不同吗?

    将C-1-2-3.hex文件烧录到单片机后,会发现显示效果与视频中的显示效果二相同,并不是预期的显示效果一。这是因为,每次执行左移指令后,会自动将数据最低位补“0”,如图1-2-2所示。


    图1-2-2

    由此可见,直接使用左移指令,并不难达到预期效果,当然这种现实方式也值得学习。若想实现演示视频中的第一种显示效果,可以使用“循环移位”,如图1-2-3所示。


    图1-2-3

    使用循环移位后,最高位多出的“1”不会被丢掉,而是“移动”到数据的最低位。若使用51汇编,可使用循环移位指令(RL 循环左移)实现,虽然Keil Cx51编译器完整的实现了ANSI C标准,但ANSI C并不支持类似汇编中循环移位指令、布尔跳转指令(JBC)、空操作等指令。为了让用户在使用C语言编程时可以使用一些汇编语言的操作,Cx51编译器提供了INTRINS.H库,添加INTRINS.H库后,可以像汇编那样通过循环移位指令设计跑马灯程序,代码如下。

    程序C1-2-4

    #include"STC15F2K60S2.h"
    #include"intrins.h"
            
    void Delay300ms()       //@11.0592MHz
    {
        unsigned char i, j, k;
    
        _nop_();
        _nop_();
        i = 13;
        j = 156;
        k = 83;
        do
        {
            do
            {
                while (--k);
            } while (--j);
        } while (--i);
    }
    
    void main(void)
    {   
        unsigned char i,m=0xFE; 
    
        while(1)
        {
            for(i=0;i<8;i++)
            {           
                P0 = m;
                m = _crol_(m,1); //循环左移一次
                Delay300ms();       
            }
        }
    }
    

    本程序中使用的_crol_用于无符号字符型(unsigned char),INTRINS.H还包含支持其他类型数据类型的循环移位操作,如表1-2-3所示。

    表1-2-3

    网络学习以下词条,将有助于理解本节内容。

    逻辑电平、拉电流、灌电流、发光二极管特性

    下节预告:延时函数、系统时钟

    作者水平有限,编写过程中难免出现不当之处,还望读者诸君不吝赐教,或许您有好的建议,欢迎与我联系QQ:136678431,作者将报以实质性奖励。

    相关文章

      网友评论

          本文标题:第一章(1.2—跑马灯的实现)

          本文链接:https://www.haomeiwen.com/subject/cbsyixtx.html