C51语言及通用I/O口应用

4.1 C51的程序结构

4.2 C51的数据结构

4.3 C51与汇编的混合编程

4.4 C51仿真开发方法

4.5 通用I/O口的简单应用

4.6 通用I/O口的进阶应用

 4.1.1  C51语言概述

C51语言是51单片机的一种高级编程语言,与低级语言的汇编语言相比,一方面具有结构化语言特点和机器级控制能力,代码紧凑执行效率可与汇编语言媲美。另一方面,由于接近自然语言,程序的可读性强,易于调试维护,编程工作量小,产品开发周期短。可见 C51具有很大的单片机程序开发优势,现已成为51单片机的主流编程语言。

标准C语言自问世以来,以诸多优点而获得了广泛应用,已成功移植到了大中小型各类计算机中,其中C51语言就是标准C语言用于51单片机的子集。

除了少数扩展功能外,大多数C语言的功能,如分支选择、循环控制、运算及表达式等执行语句,数组、结构体、预处理命令等基本语句在C51中都能通行延用,这对于具有C语言编程基础的读者掌握单片机程序设计,无疑是极为有利的。

由于C51毕竟是针对8051系列CPU扩展而成的,因而与标准C语言针对的机型在不少方面是有差异的,主要表现为以下几个方面。

①数据类型方面

51单片机中的特殊功能寄存器SFR、可位寻址存储空间、工作寄存器Rn等都是51单片机的特有资源,C51具有对其进行访问和操作的特殊规则;

②存储类型方面

51单片机采用哈佛结构存储器空间,程序和数据分别存储在不同的存储空间中,因而C51有表征不同存储空间变量类型的能力,这也是C51区别于标准C语言的最大之处;

③函数属性方面

由于51系统是8位机资源有限,不允许太多的复杂运算,因而标准C的库函数中只有少量可用于C51。而且由于中断函数对机器的硬件系统相关性很高,因而C51的中断函数也是有其特殊规则的;

④输入输出方面

C51的输入输出是通过访问机器端口的映射地址实现的,而标准C语言则是通过如printfscanf等输入输出库函数实现的,两者做法相差较大。

4.1.2  C51的程序结构

C51程序的基本单位是函数。一个C51源程序至少包含一个主函数main(),也可以是一个主函数和若干个其他函数。主函数是程序的入口;主函数中的所有语句执行完毕后程序就结束。

实例:实现P1.0引脚处LED闪烁控制功能

C51的程序结构

预处理命令——在编译之前进行的处理。 预处理包含三方面的内容: 宏定义、文件包含、条件编译,以符号“#”开头;

函数声明——把函数的名字、函数类型以及形参类型、个数和顺序通知编译系统;

变量定义——遵循“先定义后使用”的原则,以分号结尾。

4.2 C51的数据结构

1.C51的变量

在C语言编程中,数值可以改变的量称为变量。

例如:

51单片机多存储空间中如何确定变量与地址的关系?

C51变量定义的四要素:

存储种类 数据类型 存储类型 变量名

存储种类用于说明变量的作用范围:

1auto(自动型)——变量的作用范围在定义它的函数体或语句块内。执行结束后,变量所占内存即被释放。

2extern(外部型) ——在一个源文件中被定义为外部型的变量,在其它源文件中需要通过extern说明方可使用。

3static(静态型) ——利用static可使变量定义所在的函数或语句块执行结束后,其分配的内存单元继续保留。

4register(寄存器型) ——目前已不推荐使用。

缺省存储种类为auto (自动)型变量

存储种类 数据类型  存储类型 变量名

数据类型用于表示数据存放格式

除上述常规格式外,51单片机还有三种新的存储格式:

C51扩充的3种数据类型:bitsfrsfr16sbit

bit

关键词bit用于定义一个位变量,语法规则:

bit bit_name  [= 01];

例如:bit door  = 0 ;  

           //定义一个叫door的位变量且初值为0

与标准C的数据类型声明的语法规则是一致的,

如:   int int_name [ = 常数];

sfrsfr16

关键词sfrsfr16用于定义SFR字节地址变量,语法规则

       sfr sfr_name = 字节地址常数;

        sfr16 sfr_name = 字节地址常数;

例如,   sfr  P0 = 0x80;            //定义P0口地址80H

   sfr  PCON = 0x87;      //定义PCON地址87H

   sfr16  DPTR=0x82;     //定义DPTR的低地址82H

注意:C语言中十六进制整数是数值前加0x0X前缀

sbit

关键词sbit用于定义SFR位地址变量

位地址表达形式:绝对位地址、相对位地址

sbit型可用三种定义形式:

    1)将SFR绝对位地址定义为位变量名

    sbit  bit_name = 位地址常数;

    例如, sbit CY = 0xD7;

2)将SFR相对位地址定义为位变量名

   sbit  bit_name = sfr字节地址 ^ 位位置;    

   例如, sbit CY = 0xD0^7;

3)将SFR相对位位置定义位变量名

           sbit bit_name =  sfr_name ^ 位位置;

   例如, sbit CY = PSW^7;

C51编译器在头文件“REG51.H”中定义了全部sfr/sfr16sbit变量。

用一条预处理命令#include <REG51.H>把这个头文件包含到C51程序中,无需重新定义即可直接使用它们的名称。

编程举例:

#include<REG51.h>   //51单片机头文件
void delay();       //延时函数
sbit p1_0 = P1^0;   //输出端口定义
main()              //主函数
{
    while (1)       //无限循环体
    {
        p1_0 = 0;   //P1.0 = 0,led亮
        delay();    //延时
        p1_0 = 1;
        delay();
    }
    
}

void delay()        //延时函数
{                   
    unsigned char i;//字符型变量i定义
    for ( i = 200; i > 0; i--);  //循环延时
}

存储种类 数据类型 存储类型 变量名

存储类型体现了变量的存放区域。51系列单片机共有6个存储类型(分布在3个逻辑存储空间中)。

不同存储类型的特点

三种编译模式分别对应于三种缺省存储类型

约定:若无特殊声明,一般均为“SMALL编译模式”

存储种类 数据类型 存储类型 变量名

变量名可以由字母、数字和下划线三种字符组成,且第一个字符必须为字母或下划线,变量名长度随编译系统而定。

变量名具有字母大小写的敏感性,如SUMsum代表不同的变量。

强调:头文件中定义的变量都是大写的,若程序采取小写变量则需要重新定义。

变量名不得使用标准C语言和C51语言的关键字。

数据结构定义举例

//定义system_status为无符号字符型自动变量,该变量位于data区中且初值为0

unsigned char bdata status_byte; 

//定义status_byte为无符号字符型自动变量,该变量位于bdata

unsigned int code unit_id[2]={0x1234, 0x89ab}; 

//定义unit_id[2]为无符号整型自动变量,该变量位于code区中,是长度为2的数组,且初值为0x12340x89ab

static char m, n; 

//定义mn2个位于data区中的有符号字符型静态变量。

2. C51的指针

C语言指针的一般定义形式为:

              数据类型  *指针变量名 [= &被指向变量名]

其中,指针变量指向一个由“数据类型”说明的变量。被指向变量和指针变量都位于C编译器默认的内存区中。

        例如:  int a =’A’;

                      int *p1= &a;

这表示p1是一个指向int型变量的指针变量,此时p1的值为int型变量a的地址,而ap1两个变量都位于C编译器默认的内存区中。

对于C51,除了数据类型外,指针定义中还应能说明:

      1)指针变量自身位于哪个存储区中?

      2)被指向变量位于哪个存储区中?

C51指针的一般定义形式:

数据类型 [存储类型1] * [存储类型2] 变量名  [=&被指向变量名]

数据类型——被指向变量的类型,如int型或char

存储类型1——被指向变量所在的存储区,缺省时由地址赋值关系决定

存储类型2——指针变量所在的存储区,缺省时为编译器默认的存储区

1 若采用SMALL编译模式,试解释下述定义的含义。

       char xdata a = ‘A’;

          char *ptr = &a;

数据类型 [存储类型1] * [存储类型2] 变量名  [=&被指向变量名]

解:ptr是一个指向char型变量的指针它本身位于SMALL编译模式默认的data存储区里此时它指向位于xdata存储区里的char型变量a的地址

解:char *ptr形式定义的指针变量,既可指向位于xdata存储区的char型变量a的地址,也可指向位于idata存储区的char型变量b的地址(由赋值操作关系决定)。

3:试解释以下指针定义的含义

  char xdata a = ‘A’;

  char xdata *ptr = &a;

ptr是位于data存储区且固定指向xdata存储区的char型变量的指针变量此时ptr的值为变量a的地址不能像例2那样再将idata存储区的char型变量b的地址赋予ptr)。

4:试解释以下指针定义的含义

  char xdata a = ‘A’;

  char xdata *idata ptr = &a;

ptr是固定指向xdata存储区的char型变量的指针变量, 它自身存放在idata存储区中,此时ptr指向位于xdata存储区中的char型变量a的地址。

4.3 C51与汇编的混合编程

汇编语言特点:

优点:执行速度快、效率高、实时性强、与硬件结合紧密。

缺点:编程难度大、可读性差,不便于移植、开发时间长

C语言特点

优点:编程容易、可移植性强、支持多种数据类型,能直接对硬件进行操作,效率高。

缺点:实时处理弱于汇编语言,无法准确定时。

混合编程特点:

程序框架或主体部分用C语言编写,对那些使用频率高、要求执行效率高、延时精确的部分用汇编语言编写,这样既保证整个程序的可读性,又保证单片机应用系统的性能。

4.4 C51仿真开发方法

4.4.1  C51程序编译

与汇编语言程序相似的是,C51语言编写的源程序也不能直接被单片机识别,必须转换成固件程序(firmware),又称为目标代码程序后才能被执行。

C51程序的编辑、编译和仿真运行也需要借助Proteus中的Source Code标签页才能。

实例1 循环流水灯

3章实例13电路基础上改用C51编程,实现流水灯循环功能

【解】本实例的C51编程思路与汇编语言基本相同,只要编写循环右移和循环左移的自定义函数即可,但为简化编程工作量也可以直接使用C51系统的库函数。

可以看出,_cror_函数具有将低位移出值补到高位的功能。该函数有两个无符号字符型的形参,前者用来存放被移位的数据,后者用来存放移位次数,函数返回值是无符号字符型。由此可知,利用P2 =_cror_(P2,1)语句便可得到循环右移一位的结果。同理,利用P2 =_crol_(P2,1)语句可得到循环左移一位的结果。

还要指出的是,调用_cror_库函数需要在源程序开头处添加一条预处理命令“#include <intrins.h>”。

实例1源程序

//实例1 循环流水灯
#include<reg51.h>                //包含reg51的头文件
#include<intrins.h>              //包含移位库函数的头文件
void delay(void)                 //定义延时函数
{
    unsigned char i,j;
    for(i = 1;i <= 50;i++)
        for(j = 1;j<=150;j++);
}

void main()
{
    unsigned char i;
    P2 = 0xfe;                    //P2初值,对应于D1亮其余灭    
    delay();                      //延时
    while(1)                      //无限循环
    {
        for(i = 1;i<=7;i++)       //由上而下流动
            {
                P2 = _crol_(P2,1);//调用左循环移位库函数将P2左循环1位
                delay();
            }
        for(i = 1;i<=7;i++)
            {
                P2 = _cror_(P2,1);//调用右循环移位库函数将P2右循环1位
                delay();
            }
    }
}

C51源程序的规范写法

缩进是通过键盘的Tab键实现的,缩进可以使程序更有层次感。缩进原则是:如果地位相等,则不需要缩进;如果属于某一个代码的内部代码就需要缩进。

对齐主要是针对大括号{}而言的,{}分别都要独占一行。互为一对的{}要位于同一列,并且与引用它们的语句左对齐。另外,{}之内的代码要向内缩进一个Tab,且同一地位的要左对齐,地位不同的继续缩进。

重视注释语言的作用,根据软件工程的思想,注释要占整个文档的20%以上。所以注释要写得很详细,而且格式要写得很规范。格式虽然不会影响程序的功能,但会影响可读性,出错了查错也会很方便。

4.5 通用I/O口的简单应用

并行I/O口是51单片机基本结构中的重要组成部分,共有P0P1P2P3四个8位端口。

单片机与外设的连接有两种方式,一是采用通用I/O口方式(以下简称为I/O口方式),二是采用片外总线方式。

前者的接口原理比较简单,应用容易实现,但受I/O口线数量限制只能用于少量外设的场合。

后者的接口原理相对复杂,通常需要外围器件配合才能连接外设,但可以节省I/O口线,便于外设扩展。

节和下节介绍通用I/O口方式C51编程应用,总线方式应用将在第8章中介绍。

4.1.1 基本输入/输出设备与应用

基本输出设备:发光二极管(Light Emitting Diode

基本输入设备按钮(Button)或开关(Switch

P0口的两种输入接口电路有差别

有上拉电阻时,可以检测到10两种状态,其中按键未按下为1,按下为0

无上拉电阻时,只能检测到是否为0状态,其中按键未按下为不确定状态,按下为0

P1P3

实例2 独立按键识别

要求采用独立按键方式实现下述功能:开机时LED全熄,然后根据按键动作使相应灯亮,并将亮灯状态保持到按压其它键时为止。

解题分析

由于P0口高4位引脚空置,电平为不确定值。为在读取P0口时能得到一个仅与按键状态有关的读入值,需要将高4位强制为0,为此可对读取的P0口值进行与操作,即key = P0 & 0x0f,使P04位始终为0

为避免将按键释放后读到的P0值写入P2口,可以利用语句if (key!= 0x0f ) P2=key仅在低4位读入值不为0x0f时才向P2输出P0状态值,这样就能保持先前的亮灯状态,直至有新的按键压下时才刷新显示。

实例2源程序

//实例2 独立按键识别
#include<REG51.H>
void main()
{
    char key = 0;
    while(1)
        {
            key = P0 & 0x0f;
            if(key!= 0x0f) P2 = key;
        }
}

实例3 键控流水灯

要求在实例2电路图的基础上,实现以下功能

  K1为“启动键”,首次按压K1可产生“自下向上” 的流水灯运动;

  K2 为“停止键”,按压K2可终止流水灯的运动(全部灭灯);

  K3K4为“方向键,分别产生 “自上向下”和 “自下向上” 运动。

思路分析

①通过读取键值引导程序进行分支控制。需要设立两个可根据键值修改的标志变量,然后再根据标志变量的组合关系控制流水灯的流向与启停

②流水灯的控制提前将4种亮灯花样数据作为数组元素存入数组led中,然后再利用下标法依次调用。

花样数据: {0xfe,0xfd,0xfb,0xf7}

获取按键状态

 修改方向和启停标志值

D1D4循环方向控制

实例3源程序

4.1.2 数码管原理与静态显示应用

LED显示元件——人机交互输出设备,其作用是指示中间运行结果与运行状态,具有显示亮度高,响应速度快的特点。

七段式LED数码管Proteus7-Segment Display

字符的显示码或字模与数码管的类型有关

实例LED数码管显示

图示为一个8位数码管显示电路,其中80C51单片机P0口的引脚与共阴极数码管的段码引脚相连。要求编程实现循环显示09字符,时间间隔为500循环步的功能。

分析:

数码管的显示字符与显示字模之间没有特别的规律可循。通常的做法是:将显示字模按显示字符代表的数值大小顺序存入一字符数组中,例如字符09的共阴极显示字模的数组

  led_mod[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}

使用时,只需用待显示值作为下标变量调用该数组,即可取得相应的字模。本例中只要提取出09的显示字模并送P0口输出,便可实现题意要求的功能。

实例5源程序

实例6 计数显示器

图为2计数显示器的电路原理图

要求数码管的显示初值为0,单击按键后,按增量1进行累加,累加值实时显示在数码管上。当累加值达到99后清零重新开始计数,如此无限循环。

解题分析:

只要设置一个按键闭合次数变量count,并将其值送到P0P2口即可实现题意要求。

两个关键问题需要关注

按键的处理问题

按键通常为机械式弹性开关。当机械触点断开、闭合时,由于触点的弹性作用,按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,抖动造成电压的波动

显然按键抖动会造成难以判断按键闭合状态的问题

按键消抖最简单的方法是软件消抖法,即当检测到有键按下时,先用软件延时10ms,然后再次检测按键的状态。若仍是闭合状态电平,则可认为是真正有键按下。反之则应作为误判处理。同理,按键释放时的检测也需做类似的处理。

虽然电路仿真时不可能有按键抖动问题,但在程序设计时还是应该按实际电路的消抖考虑。

为避免按键在压下期间被连续统计,确保一次点击仅能被统计一次,计数值应该在按键先被压下然后又被释放之后才能更新

②计数值的拆分显示原理

为使计数器变量count中的两位十进制数能分别显示在两只数码管上,需要将计数值先进行拆分再送交显示。

拆分原理

count用取模运算(count%10)拆出个位值,用整除运算(count/10)拆出十位值。

P2 = table[count%10];

P0 = table[count/10];

实例6源程序

4.6 通用I/O口的进阶应用

4.6.1  数码管动态显示原理与应用

两种显示接口:静态显示接口和动态显示接口

Ø 所有数码管的输入端( 段码线 )对应并联在一个 IO 口上,而每位数码管的公共端( 位码线 )分别由一位 IO 线控制;
Ø IO 口输出的显示码可被所有数码管收到,但只有满足 位码线电平 要求的数码管可被驱动。

动态显示编程原理

快速(如10ms)切换段码值和位码值,使每一时刻只有一只数码管被驱动。利用视力暂留特性,可获得连续显示效果。

优点:占用IO口资源较少(节省空间)

缺点:需要CPU不断进行干预(占用机时)

实例数码管动态显示

图为采用共阴极LED数码管的电路原理图,要求采用动态显示原理显示字符“L2”。

图中双联LED数码管是Proteus提供的控件模型,相当于段码位在内部做了并联,而位码位独立接出的两只数码管。

分析

Proteus中的双联LED数码管相当于两个并联的数码管。

L2的显示方法

将位码0x020x01先后送入P3口,可依次使能左、右两个数码管。此时若将0x380x5b两个段码(显示字模)依次送到P2口,便可产生“L2”的动态显示效果。

实例7 源程序

4.2.2 行列式键盘原理与应用

独立式键盘的电路简单,易于编程,但占用的I/O口线较多,当需要较多按键时可能产生IO口资源紧张问题。

行列式键盘——I/O口分为行线和列线,按键跨接在行线和列线上,列线通过上拉电阻接正电源。

行列式键盘编程原理(以P2口接4×4键盘为例)

第二步  按键闭合状态判断

键值——按键闭合时从引脚读出的数值。

按键闭合前后,所在行线端口电平反转;

P2后,若发现其低4位为f,说明无键压下;反之则相反。

          如果 (P2 & 0x0f) = 0x0f  →无键压下

          如果 (P2 & 0x0f) 0x0f  →有键压下

第三步 查找闭合键键号

实例8行列式键盘

4×4行列式键盘如下图所示

功能要求:开机黑屏→按下任意按键后,数码管上显示该键的键号(0F)→若没有新键按下,维持前次按键结果。 

实例8源程序

实例1位密码锁

功能要求:在4X4行列式键盘基础上实现1位密码锁如下功能

16 个按键分别代表字符 0 9 A F ,开锁密码为字符 7
系统上电后 LED 灭(代表上锁),数码管显示闪烁“ 8 ”,约 1 秒后改为“ - ”(即待机状态);
单击按键表示输入一位密码,若密码输入正确,则显示“ P ”, LED 亮(代表开锁),持续约 3 秒钟后自动进入待机状态(表示过期自动上锁);
否则显示“ E ”, LED 保持灭(表示开锁错误),持续约 3 秒钟后自动进入待机状态。
如此反复无限循环。试根据上述要求完成一个基于 51 单片机的软硬件系统设计。

解题分析:

根据任务要求,硬件系统中可以用一位共阴极LED数码管作为显示器件,采用静态连接方式;16个按键采用4×4矩阵键盘连接方式;一位共阴极发光二极管作为密码锁开锁开关。

源程序设计思路:

按键闭合检测可以采用实例 7 getkey () 函数;
LED 操作和数码管显示 可由 自定义有参函数 action(char stat,char num ) 完成,其中形参 stat 代表 8 P E 的显示码, num 代表开锁和上锁的操作码。
函数 action() 还要承担字符闪烁控制和待机字符显示的任务。

小结

1. C51变量的一般定义形式为:

〔存储种类〕 数据类型 〔存储类型〕 变量名;

l 存储种类包括autoexternstaticregister 4个说明符,缺省时为auto型。

l 常用数据类型为charintC51扩充类型为bitsfrsfr16sbit

l 存储类型包括databdataidatapdataxdatacode 6个具体类型,缺省类型由编译模式指定。

l 变量名可由字母、数字和下画线3种字符组成,首字符应为字母或下画线。

2C51指针的一般定义形式为:

数据类型 〔存储类型1 * 〔存储类型2〕 指针变量名;

l 数据类型是被指向变量的数据类型。

l 存储类型1是被指向变量的存储类型,缺省时需根据该变量的定义确定。

l 存储类型2是指针变量的存储类型,缺省时根据C51编译模式确定。

l 变量名可由字母、数字和下画线3种字符组成,首字符应为字母或下画线。

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值