第一章(1.3系统时钟和延时函数)

原理图、源代码、元件清单等资料下载:戳我
(本教程第一、二节内容请在主页学习)
通过本节学习系统时钟,有助于理解第一、二节的延时函数,也有助于理解在实际的项目中如何选用不同芯片及选择芯片时钟频率。
第二节的跑马灯程序中,都调用了一个300ms的延时函数:

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();     屏蔽延时函数
        }
    }
}

通过实物观察可知,当屏蔽延时函数后,只能看到8颗LED全亮效果。这是因为,没有延时函数,单片机以极快的速度反复点亮、熄灭LED,由于速度过快,根据视觉暂停(视觉暂留)现象,就只能看到全亮效果。

程序中的延时函数,实际上就是多次循环执行没有实际功能的代码(不影响主要功能)。现在通过逻辑分析,更加形象的认识延时函数在单片机I/O口输出过程中的作用。采集到的跑马灯波形如图1-3-1所示,单片机程序为C1-2-4(@11.0952MHz)

第一章(1.3系统时钟和延时函数)_第1张图片
图1-3-1

不难看出,P0.0到P0.7端口依次输出低电平后被拉高,对应实物电路中的LED逐个点亮、熄灭。根据程序可知,低电平的持续时间是300ms。那么逻辑分析实际测得的低电平持续时间是多少呢?利用逻辑分析仪的标记功能,可以很容易的知道单片机I/O口输出低电平时的持续时间,如图1-3-2所示。

第一章(1.3系统时钟和延时函数)_第2张图片
图1-3-2

根据逻辑分析仪可知,低电平的持续时间为:|T1-T2|=299.85ms,接近300ms,可以说是符合预期的。现在,从新给单片机烧录一次程序,仍然下载C1-2-4.hex,但下载之前,设定时钟频率为22.1184MHz,如图1-3-3所示。

第一章(1.3系统时钟和延时函数)_第3张图片
图1-3-3

下载程序后,观察实物电路,可以发现跑马灯的变化速率与在11.0952MHz时钟频率时相比,明显变快。再次用逻辑分析得到波形,并测出一个低电平的持续时间,如图1-3-4所示。

第一章(1.3系统时钟和延时函数)_第4张图片
图1-3-4

|T1-T2|=149.85ms, 149.85 ÷ 299.85 = 0.4997 ≈ 0.5,后者的系统时钟频率是前者的2倍,延时函数代码没有改变,延时时间后者变为了前者的1/2。为什么系统时钟频率不同,会导致延时函数的实际延时时间不同?这就需要引入时钟周期、机器周期、指令周期概念。

时钟周期

计算机工作时,是在统一的时钟脉冲控制下一拍一拍地进行的。就像军训中的齐步走口号,所有人都跟着同一个口号走,步调才能一致,方队才能整齐。而单片机的节拍是非常精准的,一个个的节拍是一个比喻,实际上是脉冲信号,脉冲信号由单片机控制器中的时序电路发出。传统8051单片机,如经常用于教学的STC89系列单片机,需要借助单片机内部的振荡器和外部晶振产生稳定的内部脉冲,本书所学习的STC15系列单片机内部集成时钟电路,因此不需要外部晶振。

脉冲是周期性的,通常在8051单片机领域,所说的时钟周期就是振荡周期。一个时钟周期,是一个具体的时间,如单片机外接(或使用内部)12MHz的晶振,此时的时钟周期为1/12us(频率的倒数),若使用11.0592Mhz晶振,时钟周期为1/11.0592us。为了便于计算说明,涉及到计算部分,本节都以12MHz为例。

机器周期

在计算机中,常把一条指令的执行过程划分为若干个阶段,每一阶段完成一项工作。例如,取指令、存储器读、存储器写等,这每一项工作称为一个基本操作。完成一个基本操作所需要的时间称为机器周期。8051系列单片机的一个机器周期同6个S周期(状态周期)组成,一个S周期由2个时钟周期组成。也就是说,一个机器周期=6个状态周期=12个时钟周期。当单片机工作在12MHz晶振频率下时,一个机器周期=12 x 1/12us = 1us。

指令周期

指令周期是执行一条指令所需要的时间,由若干个机器周期组成。还是以军训为例,军训口号有“一二”和“一二一”。喊一次“一二”向前迈出一步,喊一次“一二一”,迈出两步。“一二”和“一二一”可看作两条指令,执行他们的时间和动作过程都不相同。8051单片机的各种指令,需要不同的指令周期来完成。(详见STC官方手册《STC15全系列中文资料-第五章第2节》2015/6/29)

为了便于理解,这里总结为:一个指令周期由若干个机器周期组成,8051系列单片机的一个机器周期同6个S周期(状态周期)组成。即一个机器周期 = 6个状态周期=12个时钟周期

由此可见,8051单片机的系统时钟频率最终会影响到指令周期,现在以NOP指令为例进一步说明。

已知在传统8051单片机中,一个NOP指令的指令周期为1个机器周期,即12个时间周期。STC15系列增强型单片机对NOP指令提速了12倍,只需要1/12个机器周期,即1个时钟周期。当系统时钟频率为12MHz时,STC15系列单片机执行NOP指令所需时间为1/12us,记为t1。
(注:在KeilC51环境中,不能以某个C语句分析其指令周期,Keil编译器先把C语言编译为汇编语言,因此这里使用汇编指令介绍,读者理解改变指令周期的原理即可)
当改变系统时钟频率为24MHz时,执行NOP指令仍为1/12个机器周期,即1个时钟周期,但此时时钟周期为1/24M=1/24us,即为t2。
t1=1/12us
t2=1/24us
t1=2 * t2
当提成倍数的提高系统时钟频率时,实际上是成倍数的提高指令周期。第二节的延时函数,实际上是多个指令的一个合集,不管用到了何种指令,当系统时钟提高一倍后(11.0592MHz变为22.1184MHz),延时函数中用到的所有指令都提速了一倍,因此300ms的延时函数代码最终实现的是150ms延时。
在前面的介绍中,为了便于计算和延时,选取的时钟频率为12MHz和24MHz。实际的项目中,尤其是涉及到串口通信的开发,都是使用11.0592MHz(或其倍数)时钟频率。原因会在后续的章节中相信介绍。此处只需要明白不同时钟频率会影响单片机的指令执行速度即可。

如何设计延时函数

除了IO口的输入输出控制,,IIC通信、SPI通信等也都需要不同时长的延时函数,下面介绍常见的延时函数的设计方法。

nop()是KEILC51中特有的延时函数。(使用时需包含intrins.h)

单片机在读取传感器、IC芯片数据时,如DS18B20,DS1302等,往往需要加入较短的延时。当STC15系列单片机工作在12MHz时钟频率下时,nop()的延时时间为1/12us,当工作在11.0592MHz时,延时时间为1/11.0952us,可以近似的认为延时时间也是1/12us。
示例代码:

unsigned char DS1302_ReadByte()
{
    unsigned char i,dat = 0;

    for (i=0; i<8; i++)            
    {
        SCLK = 0;                   //时钟线拉低
        _nop_();                    //延时等待
        _nop_();
        dat >>= 1;                  //数据右移一位
        if (IO) dat |= 0x80;        //读取数据
        SCLK = 1;                   //时钟线拉高
        _nop_();                    
        _nop_();
    }

    return dat;
}

此代码功能是单片机读取DS1302芯片1字节数据,此处增加短暂的延时,是因为单片机的IO口被拉低(SCLK = 0)拉高(SCLK = 1)时,STC15系列单片机的执行速度非常快,如果不加延时,会导致DS1302芯片没有监测到外部电平变化,有可能导致数据溢出不正常。
(注:上述结论可能会因为实际电路、测试环境差异得出不同结论)

2 循环延时函数

部分传感器等芯片,往往需要较长延时,如延时10us、200us等,此时需要循环的方式实现较长时间延时。现以STC15系列单片机为例,设计一个简单的延时函数。
程序C1-3-2:

#include"STC15F2K60S2.h"
#include"intrins.h"

void Delay(unsigned int n)
{
    while (n--)
    {
        _nop_();
    }
}

void main()
{
    while(1)
    {
        Delay(5);
        Delay(10);
        Delay(20);
        Delay(40);
        Delay(80);
        Delay(160);
        Delay(320);
        Delay(640);
    }
}

上面的延时函数实际延时时间是多少呢?通过KEIL的Debug功能,设置时钟频率为12MHz。Debug过程如图1-3-5图1-3-6所示,

第一章(1.3系统时钟和延时函数)_第5张图片
图1-3-5
第一章(1.3系统时钟和延时函数)_第6张图片
图1-3-6

执行DelayXus(5)的延时时间=0.00007500-0.00006467=0.00001033s≈10.33us

用此方法,依次求出后面的延时时间,如表1-3-1所示.

函数名 延时时间 单位:us
Delay(5) 10.33
Delay(10) 18.25
Delay(20) 34.08
Delay(40) 65.75
Delay(80) 129.09
Delay(160) 255.75
Delay(320) 509.25
Delay(640) 1016.08

(注:debug方法推导STC15系列单片机延时时,存在误差)

Delay(5)函数当做一个10us延时的基准,需要50us延时函数时,可以直接调用DelayXus(25)实现,尽管这样会存在一定的误差,但实际项目开发中,大多数传感器或IC芯片允许这种延时上的误差,如DS18B20传感器,官方手册规定,传感器工作之前,需要一个480至960us的低电平脉冲,这个范围很宽,所以延时函数不需要特别精确。

不同芯片或传感器,也会用到较为精准的延时函数,如跑马灯的要求“每隔0.3秒点亮一颗LED”这实际上就是明确的延时要求。这就需要程序设计者有一定的程序设计能力,或通过定时器实现,或通过循环实现。定时器延时会在后面的章节中介绍,这里主要介绍循环延时(又叫软件延时,非定时器延时)。在程序C1-3-2中,延时函数并不精准,但可以通过计算的方式得到精确延时。

这里需要注意,计算代码延时时间,并不是简单的计算C代码执行速度,而是通过编译后的汇编代码,延时函数Delay(unsigned int n)编译后的汇编代码如图所示。

第一章(1.3系统时钟和延时函数)_第7张图片
图1-3-7

根据汇编代码,设延时时间为Y,延时函数形参为X,时钟周期为T,可得到下面的公式
Y=18T*X+28T
X=(Y-28T)/18T
当系统时钟频率为12MHz,需要延时时间为200us时,形参X=(200-28/12)/(18/12)=131.78≈132。因此DelayXus(132)就是一个200us的延时函数。但实际上,这种方法不仅需要懂得编译后的汇编代码,且仍会存在误差。只能说与粗略的延时相比,这种方法相对精确。
(这里不要求读者掌握汇编代码,读者作为了解即可)

3 延时函数生成软件

以通过第三方软件延时计算机器可以得到更为精准的延时函数,免去了复杂繁琐的计算,STC官方的ISP软件就提供了软件延时计算器,如图1-3-8所示。

第一章(1.3系统时钟和延时函数)_第8张图片
图1-3-8

简单的5步,就可以计算出需要的延时代码,第二节中的300ms延时函数,就是通过这种方式得到的。

不同时钟频率对项目的影响

以STC15系列单片机为例,大多数情况下,时钟频率不需要特别考虑。尽管更高的时钟频率可以让CPU更快的执行指令,但有些功能本身并不需要很高时钟频率。把第二节的跑马灯看做是一种项目需求,现改为基于22.1184MHz时钟频率实现,程序代码如下。
程序C1-3-3

#include"STC15F2K60S2.h"
#include"intrins.h"
        
void Delay100ms()       //@22.1184MHz
{
    unsigned char i, j, k;

    _nop_();
    _nop_();
    i = 9;
    j = 104;
    k = 139;
    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();       
        }
    }
}

对比程序C1-2-4和程序C1-3-1可知,除了延时函数代码不同,主函数内代码完全相同。无论系统时钟频率如何提高,仅仅是提高了端口输出金额循环移位代码的速度。延时时间依然是300ms。换言之,输出语句的执行时间在微秒级,即便是提高代码速度后,还是在微秒级。微秒级的变化对于300ms的延时来说,影响微乎其微,人眼根本部分无法觉察区别。这里为了让更高效的CPU实现延时,实际上是做执行了更多的无实际功能的代码。即便是在较为复杂的电路中,如888光立方控制512颗LED,对比跑马灯控制8颗LED,数量上差了64倍,但在合理的程序逻辑下,也可以忽略掉时钟频率带来的影响。

在什么时候需要提高时钟频率呢?通常是在涉及到大量计算的情况下,有必要提高系统时钟频率。如图1-3-9所示的项目要求。

第一章(1.3系统时钟和延时函数)_第9张图片
图1-3-9

若数据处理部分是个比较简单的公式:i=ii+i3+i*2+100,CPU极短的时间内即可处理完成。即便是提高时(或降低)钟频率,也不会影“每0.1秒通过串口发送数据”的需求。

但随着项目的复杂,影响单片机CPU效率的因素还有中断、数据结构等因素。假设数据处理的算法非常复杂,数据处理的时间大于0.1秒,便不能满足项目要求。此时只有提高时钟频率,进而提高指令的执行速度,让CPU处理数据的时间尽可能的小于项目所要求的0.1秒。
当单片机工作在最大时钟频率时,处理一次数据的时间仍大于0.1秒,此时只能更为换其他处理速度更快的单片机。这里也必须指出,STC系列单片机与STM32、AVR等芯片比较,确实存在处理速度上的不足。

或许有读者会提出,为什么不直接让所有项目都直接使用高性能芯片开发呢?会不会省去了很多后顾之忧?笔者从11年发布开源设计至今,包括实际参与的项目开发,以及和其他单片机工程师交流得到的经验便是,8051内核的单片机在硬件成本、研发成本、维护成本及研发周期上,与其他单片机相比,仍具有诸多优势。一个经验丰富的项目研发主管首要工作就是考虑上述因素对整个项目的影响,特别是在新兴的物联网行业中,这些项目对单片机的运算速度要求并不高,单片机往往只作为数据采集和远端控制,而核心研发部分在服务器端或其他带有操作系统的主控机,因此需要尽可能快速的开发出硬件部分,以便与软件平台对接。此时8051单片机因为结构简单,开发周期短,资料丰富,往往成为首选芯片。

综上所述,为了满足不同产品开发需要,只有深入学习并熟练掌握8051芯片结构,才能在实际应用更好的物尽其用,也只有熟练掌握8051单片机,才能权衡复杂的项目中,应答使用何种芯片。

网络学习以下词条,可有助于理解本节内容。
时钟周期、机器周期、指令周期、51单片机总线时序

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

你可能感兴趣的:(第一章(1.3系统时钟和延时函数))