嵌入式入门教学——C51

一、前期准备

1、硬件设备

  • 嵌入式入门教学——C51_第1张图片
  • 后续所有实验都是使用STC89C52RC。

2、软件设备

  •  嵌入式入门教学——C51_第2张图片

二、预备知识

1、什么是单片机?

  • 在一片集成电路芯片上集成微处理器、存储器、IO接口电路,从而构成了单芯片微型计算机,及单片机。
  • STC89C52单片机:
    • STC:公司
    • 89:所属系列
    • C:工作电压(5.5V~3.8V)
    • 52:程序空间及RAM空间大小(8KB程序空间及512B的RAM空间)

2、电平特性

  • 定义单片机为TTL电平:高+5v、低0v
  • RS232电平(计算机的串口):高-12v、低+12v 
  • 故计算机与单片机之间通讯时需要加电平转换芯片max232

3、二进制数的逻辑关系

  • 与:&
  • 或:+
  • 非:!
  • 异或:⊕(相同为0,不同为1)

4、80C51系列介绍

  • 以8051为基核开发出的CMOS工艺单片机产品统称为80C51系列。
  • STC(公司名)89(系列)C(CMOS)52(2为内部存储空间大小2*4=8k)RC
  • 40(晶振频率)C(商业级-温度)-PDIP(封装格式)
  • 0721(07年21周)CV4336

5、80C51引脚封装

  • 总线型:四组,每组8位。
  • 非总线型

6、C-51数据类型扩充定义

  • sfr:特殊功能寄存器声明。
  • sfr16:sfr16位数据声明。
  • sbit:特殊功能位声明。
  • bit:位变量声明。

7、C-51包含的头文件

  • reg51.h:定义特殊功能寄存器和位寄存器。
  • math.h:定义常用数学运算。

8、C-51的运算符

  • >>:位右移
  • <<:位左移
  • &:按位与
  • |:按位或
  • ^:按位异或
  • ~:按位取反

9、单片机主要掌握一下几点

  • 最小系统能够运行起来的必要条件:电源、晶振、复位电路(程序从头执行)。
  • 对单片机任意IO口的随意操作:输出控制电平高低、输出检测电平高低。
  • 定时器:重点掌握最常用的方式2。
  • 中断:外部中断、定时器中断、串口中断。
  • 串口通信:单片机之间、单片机与计算机间。

10、电路图符号表示

  • 电阻:R
  • 电容:C
  • 电感:L
  • 集成块/芯片:U
  • 地:GND
  • 电源:VCC、AVDD、DVDD

三、LED模块

1、点亮一个LED

1.1、LED的位置
1.2、新建一个工程
  • 新建一个工程。
  • 嵌入式入门教学——C51_第3张图片
  • 选择存放位置,新建一个文件夹用于存放该工程。
  • 嵌入式入门教学——C51_第4张图片
  • 进入文件夹,输入文件名,即工程名。
  • 嵌入式入门教学——C51_第5张图片
  • 选择单片机的型号。选择Atmel下的AT89C52。(本单片机使用的是STC89C52)
  • 嵌入式入门教学——C51_第6张图片
  • 新建一个c语言程序文件。
  • 嵌入式入门教学——C51_第7张图片
  • 嵌入式入门教学——C51_第8张图片
1.3、LED原理图
  • 下图红框区域相连接。
  • 嵌入式入门教学——C51_第9张图片
  • LED右端接的是电源,为高电平,故左端为低电平即可导通。(单片机默认输出高电平)
  •  嵌入式入门教学——C51_第10张图片
1.4、单片机如何控制LED
  • 嵌入式入门教学——C51_第11张图片
  • P2寄存器的8位决定了LED的高低电平。1为高电平,0为低电平。
  • 当为低电平时,低于外部电压,有电流通过,点亮led。
  • 【注】51单片机所有IO口上电后默认全是高电平。
  • 例如:控制第一个灯亮,p2寄存器应为:1111 1110。
1.5、进制转换
  • c语言中,不能识别二进制,应该转换为十六进制。
  • 嵌入式入门教学——C51_第12张图片
  • 例如:
    • p2=1111 1110; // 错误
    • p2=0xFE; // 正确,前面0x表示十六进制。
1.6、编写代码
#include  // 头文件,定义特殊功能寄存器和位寄存器
void main(){
	P2=0xFE; // 1111 1110
	while(1); // 让程序停止在这,不让main重复执行
}
1.7、设置生成.hex下载文件
  • 嵌入式入门教学——C51_第13张图片
  • 设置完后,编译程序,生成.hex文件。
1.8、将.hex文件下载到单片机中
  • 将单片机连接电脑串口。 
  • 打开STC-ISP程序,选择单片机型号(查看芯片上的字符,89C52和89C52RC不同),选择hex文件,点击下载。
  • 重启单片机,下载成功,成功点亮第一个led。。
  • 嵌入式入门教学——C51_第14张图片

2、LED闪烁

  • 新建工程和c语言程序文件,与前相同。
  • 尝试点亮一个led再熄灭它。
  • #include
    void main(){
        while(1){
            P2=0xFE; // 亮
            P2=0xFF; // 灭
        }
    }
  • 结果led常亮,并未闪烁。原因是led闪烁的频率过快,人的肉眼无法捕捉。 
  • 使用STC-ISP生成延时函数。设置要与使用的单片机一致。
  • 嵌入式入门教学——C51_第15张图片

  • 添加延时函数。
  • #include
    #include // 引入nop函数
    void Delay500ms() // 500ms延时函数
    {
    	unsigned char i, j, k;
    	_nop_();
    	i = 4;
    	j = 205;
    	k = 187;
    	do
    	{
    		do
    		{
    			while (--k);
    		} while (--j);
    	} while (--i);
    }
    void main(){
        while(1){
            P2=0xFE; // 亮
            Delay500ms(); // 延时500ms
            P2=0xFF; // 灭
            Delay500ms();
        }
    }
  • 编译后下载程序到单片机,LED灯以500ms的时间间隔闪烁。

3、LED流水灯

  • 新建工程和c语言程序文件,设置生成hex文件。
  • 编辑代码:
  • #include
    #include // 引入nop函数
    void Delay500ms() //500ms延时函数
    {
    	unsigned char i, j, k;
    	_nop_();
    	i = 4;
    	j = 205;
    	k = 187;
    	do
    	{
    		do
    		{
    			while (--k);
    		} while (--j);
    	} while (--i);
    }
    void main(){
        while(1){
            P2=0xFE; // 1111 1110
            Delay500ms(); // 延时500ms
            P2=0xFD; // 1111 1101
            Delay500ms();
            P2=0xFB; // 1111 1011
            Delay500ms();
            P2=0xF7; // 1111 0111
            Delay500ms();
            P2=0xEF; // 1110 1111
            Delay500ms();
            P2=0xDF; // 1101 1111
            Delay500ms();
            P2=0xBF; // 1011 1111
            Delay500ms();
            P2=0x7F; // 0111 1111
            Delay500ms();
        }
    }
  • 编译后下载程序到单片机,LED灯以500ms的时间间隔流动。
  • 原延时函数不方便,不能随时更改延时时间,重新编辑延时函数。先使用STC-ISP生成一个延时1ms的函数。
  • 嵌入式入门教学——C51_第16张图片
  • C51数据类型
  • 嵌入式入门教学——C51_第17张图片
  • 为延时函数添加一个参数,控制延时时间。
  • #include
    void Delay(unsigned int xms) //带参延时函数
    {
    	unsigned char i, j;
        while(xms--){
    	    i = 2;
    	    j = 239;
    	    do
    	    {
    		    while (--j);
    	    } while (--i);
        }
    }
    void main(){
        while(1){
            P2=0xFE; // 1111 1110
            Delay(500); // 延时500ms
            P2=0xFD; // 1111 1101
            Delay(500);
            P2=0xFB; // 1111 1011
            Delay(500);
            P2=0xF7; // 1111 0111
            Delay(500);
            P2=0xEF; // 1110 1111
            Delay(500);
            P2=0xDF; // 1101 1111
            Delay(500);
            P2=0xBF; // 1011 1111
            Delay(500);
            P2=0x7F; // 0111 1111
            Delay(500);
        }
    }
  •  带参数的延时函数更加灵活。

四、独立按键

  • 嵌入式入门教学——C51_第18张图片

1、独立按键控制LED亮灭

1.1、独立按键
  • 相当于是一种电子开关,按下时开关接通,松开时开关断开,实现原理是通过轻触按键内部的金属弹片受力弹动来实现接通和断开。
1.2、独立按键原理图
  • 下图红框区域相连接。
  • 嵌入式入门教学——C51_第19张图片
  • 嵌入式入门教学——C51_第20张图片
 1.3、编写代码
  • 由原理图知,K1由P3.1控制。
  • #include
    void main(){
        //P2=0xFE;
        while(1){
            if(P3_1==0){ // K1按下
                P2_0=0; // 按位操作,在regx52.h头文件中有声明
            }else{ // K1松开
                P2_0=1;
            }
        }
    }
  • K1按下,LED亮;K1松开,LED灭。

2、独立按键控制LED状态

2.1、按键的抖动
  • 对于机械开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动。
  • 嵌入式入门教学——C51_第21张图片
  • 抖动会使对按键的判断产生一些误操作,可以通过代码解决。
2.2、解决抖动并控制LED状态
  • #include
    void Delay(unsigned int xms) //带参延时函数
    {
    	unsigned char i, j;
        while(xms--){
    	    i = 2;
    	    j = 239;
    	    do
    	    {
    		    while (--j);
    	    } while (--i);
        }
    }
    void main(){
        while(1){
            if(P3_1==0){ // K1按下
                // 防抖
                Delay(20);
                while(P3_1==0);
                Delay(20);
    
                P2_0=~P2_0; // 取反
            }
        }
    }
  • 按下K1,没反应,松开K1,灯亮;按下K1,无反应,松开K1,灯灭。

3、独立按键控制LED显示二进制

  • #include
    void Delay(unsigned int xms)
    {
    	unsigned char i, j;
        while(xms--){
    	    i = 2;
    	    j = 239;
    	    do
    	    {
    		    while (--j);
    	    } while (--i);
        }
    }
    void main(){
        unsigned char Led_Num=0; // 8位,相当于寄存器的8位
        while(1){
            if(P3_1==0){ // K1按下
                // 防抖
                Delay(20);
                while(P3_1==0);
                Delay(20);
                
                Led_Num++;
                P2=~Led_Num; // 取反
            }
        }
    }
  • 每按一次按键,8个LED显示对应的二进制。 
  • 如:,表示0000 0111。

4、独立按键控制LED移位

  • #include
    void Delay(unsigned int xms) // 延时函数
    {
    	unsigned char i, j;
        while(xms--){
    	    i = 2;
    	    j = 239;
    	    do
    	    {
    		    while (--j);
    	    } while (--i);
        }
    }
    void main(){
        unsigned char Led_Num=0;
        P2=~0x01; // 初始化
        while(1){
            // K1按键,左移
            if(P3_1==0){
                Delay(20);
                while(P3_1==0);
                Delay(20);
    						
                if(Led_Num==0)
                    Led_Num=7;
                else Led_Num--;
                P2=~(0x01<=8) // 越界判断
                    Led_Num=0;
                P2=~(0x01<
  • 按下K1,LED左移一个;按下K2,LED右移一个。

五、数码管

  • 嵌入式入门教学——C51_第22张图片

1、静态数码管显示

1.1、数码管介绍
  • 数码管是一种简单、廉价的显示器,是由多个发光极管封装在一起组成“8”字型的器件。
1.1.1、一位数码管
  • 段的名称、引脚序号
    • 嵌入式入门教学——C51_第23张图片嵌入式入门教学——C51_第24张图片
  • 共阴极连接、共阳极连接
    • 嵌入式入门教学——C51_第25张图片嵌入式入门教学——C51_第26张图片
  • 例如,让共阴极数码管显示6。
    • 嵌入式入门教学——C51_第27张图片,应该让A、C、D、E、F、G点亮。
    • 首先需要将3,8引脚接地,即位选(让某个数码管亮)。
    • 嵌入式入门教学——C51_第28张图片,控制7、4、2、1、9、10输出高电平,即段选(让数码管输出6)。
1.1.2、四位一体数码管
  • 引脚序号
    • 嵌入式入门教学——C51_第29张图片
  • 连接方式
    • 嵌入式入门教学——C51_第30张图片
  • 例如,让共阴极数码管第3位显示1。
    • 嵌入式入门教学——C51_第31张图片,需要如下图所示。
    • 嵌入式入门教学——C51_第32张图片
  • 【注】同一时刻,只能有一个位被选中显示数字,或者4个位都显示相同的数字。(静态)
1.2、数码管原理图
  • 嵌入式入门教学——C51_第33张图片
  • 上图左边74HC573为双向数据缓冲器,来提高驱动能力。
  • 138译码器用来控制位选(3位输入,8位输出),原理如下表。
  •  嵌入式入门教学——C51_第34张图片嵌入式入门教学——C51_第35张图片
  • 嵌入式入门教学——C51_第36张图片
1.3、编写代码
  • 让第三位数码管显示6。
  • 嵌入式入门教学——C51_第37张图片
  • 嵌入式入门教学——C51_第38张图片
  • #include
    
    void main(){
        // 位选
        P2_4=1;
        P2_3=0;
        P2_2=1;
        // 段选
        P0=0x7D; // 0111 1101
        while(1);
    }
 1.4、引入函数
  • #include
    //段选
    unsigned char NixieTable[]={
        0x3F, 0x06, 0x5B, 0x4F,
        0x66, 0x6D, 0x7D, 0x07,
        0x7F, 0x6F, 0x77, 0x7C,
        0x39, 0x5E, 0x79, 0x71, 0x00
    };
    // 数码管显示函数
    void Nixie(unsigned char Location, int Number){
        switch(Location){
            case 1: P2_4=1; P2_3=1; P2_2=1; break;
            case 2: P2_4=1; P2_3=1; P2_2=0; break;
            case 3: P2_4=1; P2_3=0; P2_2=1; break;
            case 4: P2_4=1; P2_3=0; P2_2=0; break;
            case 5: P2_4=0; P2_3=1; P2_2=1; break;
            case 6: P2_4=0; P2_3=1; P2_2=0; break;
            case 7: P2_4=0; P2_3=0; P2_2=1; break;
            case 8: P2_4=0; P2_3=0; P2_2=0; break;
        }
        P0=NixieTable[Number];
    }
    void main(){
        Nixie(3,6);
        while(1);
    }

2、动态数码管显示

2.1、数码管消影
  • 现象:数码管数字显示的位置错乱。
  • 动态数码管显示过程
    • 位选 -> 段选 -> 位选 -> 段选。。。
  • 原因:速度太快,数据串位。
  • 解决方法
    • 位选 -> 段选 -> 清零 -> 位选 -> 段选。。。
2.2、编写代码
  • #include
    // 延时函数
    void Delay(unsigned int xms)
    {
    	unsigned char i, j;
        while(xms--){
    	    i = 2;
    	    j = 239;
    	    do
    	    {
    		    while (--j);
    	    } while (--i);
        }
    }
    //段选
    unsigned char NixieTable[]={
        0x3F, 0x06, 0x5B, 0x4F,
        0x66, 0x6D, 0x7D, 0x07,
        0x7F, 0x6F, 0x77, 0x7C,
        0x39, 0x5E, 0x79, 0x71, 0x00
    };
    // 数码管显示函数
    void Nixie(unsigned char Location, int Number){
        switch(Location){
            case 1: P2_4=1; P2_3=1; P2_2=1; break;
            case 2: P2_4=1; P2_3=1; P2_2=0; break;
            case 3: P2_4=1; P2_3=0; P2_2=1; break;
            case 4: P2_4=1; P2_3=0; P2_2=0; break;
            case 5: P2_4=0; P2_3=1; P2_2=1; break;
            case 6: P2_4=0; P2_3=1; P2_2=0; break;
            case 7: P2_4=0; P2_3=0; P2_2=1; break;
            case 8: P2_4=0; P2_3=0; P2_2=0; break;
        }
        P0=NixieTable[Number];
        // 消影
        Delay(1);
        P0=0x00;
    }
    void main(){
        while(1){
            Nixie(1,1);
            //Delay(20);
            Nixie(2,2);
            //Delay(20);
            Nixie(3,3);
            //Delay(20);
        }
    }
2.3、数码管驱动方式
  • 单片机直接扫描:硬件设备简单,但会耗费大量的单片机CPU时间(如上代码)。
  • 专用驱动芯片:内部自带显存、扫描电路,单片机只需告诉它显示什么即可。(TM1640芯片)

六、模块化编程及LCD1602调试工具

1、模块化编程

  • 把各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数的声明,其它.c文件想使用其中的代码时,只需要#include "xxx.h"文件即可。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等。
1.1、案例
  • 新建工程文件,在其目录下新建三个文件夹。Functions用来放模块化的函数,Objects用来放.hex文件,Listings用来存在.lst文件。
  • 嵌入式入门教学——C51_第39张图片
  • 设置生成.hex文件,并把它存放到Objects文件夹中。
  • 嵌入式入门教学——C51_第40张图片
  • 设置listing存放到Listings文件夹中。
  • 嵌入式入门教学——C51_第41张图片
  • 假设已经有下面延时函数的模块。
    • Delay.h
    • #ifndef __DELAY_H__
      #define __DELAY_H__
      
      void Delay(unsigned int xms);
      
      #endif
    • 开头两句和最后一句代码是为了防止重复定义。(在头文件中都有)
      • #ifndef __XX_H__:如果没有定义__XX_H__,执行下列语句。
      • #define __XX_H__:定义__XX_H__。
      • #endif:与#ifndef匹配,组成“括号”。
    • Delay.c
    • void Delay(unsigned int xms)
      {
      	unsigned char i, j;
          while(xms--){
      	    i = 2;
      	    j = 239;
      	    do
      	    {
      		    while (--j);
      	    } while (--i);
          }
      }
  • 将延时函数的模块复制到Functions文件夹中。
  • 嵌入式入门教学——C51_第42张图片
  • 先添加main函数,再添加Delay.c到工程中。
  • 嵌入式入门教学——C51_第43张图片
  • 设置延时函数的路径(用于寻找.h文件)。
  • 嵌入式入门教学——C51_第44张图片
  • 编写main函数。 
  • #include "Delay.h"
    void main(){
        P2=0xFE; // 亮
        Delay(20);
        P2=0xFF; // 灭
        Delay(20);
    }
  • 延时函数成功被调用。
1.2、注意事项
  • .c文件:函数、变量的定义。
  • .h文件:可被外部调用的函数、变量的声明。
  • 任何自定义的变量、函数在调用前必须有定义或声明(同一个.c)。
  • 使用到的自定义函数的.c文件必须添加到工程参与编译。
  • 使用到的.h文件必须要放在编译器可寻找到的地方(工程文件夹根目录、安装目录、自定义)。

2、LCD1602调试工具

  • 嵌入式入门教学——C51_第45张图片嵌入式入门教学——C51_第46张图片
  • 下图为插入位置和调节亮度(使用螺丝刀左右调节)的位置。
  • 嵌入式入门教学——C51_第47张图片
  • 使用LCD1602液晶屏作为调试窗口,提供类似printf函数的功能,可实时观察单片机内部数据的变换情况,便于调试和演示。
  • 这里有LCD1602模块化的代码,只需要知道怎么使用即可。下载链接:【免费】LCD1602驱动代码-单片机文档类资源-CSDN文库
函数 作用
LCD_Init(); 初始化
LCD_ShowChar(1,1,'A'); 显示一个字符
LCD_ShowString(1,3,"Hello"); 显示字符串
LCD_ShowNum(1,9,123,3); 显示十进制数字
LCD_ShowSignedNum(1,13,-66,2); 显示有符号十进制数宇
LCD_ShowHexNum(2,1,0xA8,2); 显示十六进制数字
LCD_ShowBinNum(2,4,0xAA,8); 显示二进制数宇
2.1、调用方法
  • 将LCD1602的模块化代码复制到工程目录中。
  • 嵌入式入门教学——C51_第48张图片
  • 在Keil中引入LCD1602驱动代码。
  • 嵌入式入门教学——C51_第49张图片嵌入式入门教学——C51_第50张图片
  • 如果添加的文件打不开,将文件格式改为C。
  • 嵌入式入门教学——C51_第51张图片嵌入式入门教学——C51_第52张图片
  • 【注】Flah->configure Flash Tools->C51->Include Paths,添加.h文件的路径(一定要添加,不然不显示!)
  • 嵌入式入门教学——C51_第53张图片
  • 编写main函数。
  • #include 
    #include "LCD1602.h"
    void main(){
    	LCD_Init(); // 初始化
    	LCD_ShowChar(1,1,'A'); // 第一行,第一列,显示一个字符 
    	LCD_ShowString(1,3,"Hello"); // 第一行,第三列,显示一个字符串
    	LCD_ShowNum(1,9,123,3); // 第一行,第九列,显示数字,三位
    	LCD_ShowSignedNum(1,13,-66,2); // 第一行,第十三列,显示数字,两位
    	LCD_ShowHexNum(2,1,0xA8,2); // 第二行,第一列,显示十六进制数,两位
    	LCD_ShowBinNum(2,4,0xAA,8); // 第二行,第四列,显示二进制数,八位
    	while(1);
    }
  • 下载hex文件到单片机,LCD1602显示如下。
  • 嵌入式入门教学——C51_第54张图片
 2.2、调试方法
  • 假设,验证程序中的变量是否每秒加一。
  • 添加上面延时函数模块到工程目录中。
  • 嵌入式入门教学——C51_第55张图片
  • 将Delay.c添加到工程中。
  • 嵌入式入门教学——C51_第56张图片
  • 设置引入路径。
  • 嵌入式入门教学——C51_第57张图片
  • 编写main函数。
  • #include 
    #include "LCD1602.h"
    #include "Delay.h"
    int Result=0;
    void main(){
    	LCD_Init(); // 初始化
    	while(1){
    		Result++;
    		Delay(1000);
    		LCD_ShowNum(1,1,Result,3);
    	}
    }
  • 编译下载后,LCD1602显示每秒加一。

七、矩阵键盘

  • 嵌入式入门教学——C51_第58张图片
  • 在键盘中按键数量较多时,为了减少I/0口的用,通常将按键排列成矩阵形式。
  • 采用逐行或逐列的“扫描”,就可以读出任何位置按键的状态。

 1、扫描

  • 数码管扫描(输出扫描)
    • 原理:显示第1位 -> 显示第2位 -> 显示第3位 -> ...,然后快速循环这个过程,最终实现所有数码管同时显示的效果(动态显示)。
  • 矩阵键盘扫描(输入扫描)
    • 原理:读取第1行(列) -> 读取第2行(列) -> 读取第3行(列) -> ...,然后快速循环这个过程,最终实现所有按键同时检测的效果。
  • 以上两种扫描方式的共性:节省I/O口。

 2、矩阵按键原理图

  • 嵌入式入门教学——C51_第59张图片嵌入式入门教学——C51_第60张图片
  • I/O口如何知道哪个按键被按下:
    • 按行扫描:
      • 嵌入式入门教学——C51_第61张图片,将P17、P16、P15、P14依次循环为0(看作接地),其余为1。
      • 如果此时P17为0,当S1按下,P13为0;当S2按下,P12为0。
      • 【注】按行扫描时蜂鸣器会响,是由于引脚冲突造成的。
    • 按列扫描:
      • 嵌入式入门教学——C51_第62张图片,将P13、P12、P11、P10依次循环为0(看作接地),其余为1。
      • 如果此时P13为0,当S1按下,P17为0;当S5按下,P16为0。

3、矩阵键盘键码显示(LCD1602显示)

  • 内容:按下矩阵键盘的按键,在LCD1602上显示对应的键码值。
  • 新建工程,在工程目录下新建Functions、Objects、Listings文件夹。将LCD1602的驱动代码和延时函数的代码复制到Functions中。(代码上面有)
  • 嵌入式入门教学——C51_第63张图片
  • 设置.hex文件和.lst文件存放位置。
  • 嵌入式入门教学——C51_第64张图片嵌入式入门教学——C51_第65张图片
  • 新建main.c文件,将Delay.c和LCD1602.c添加到工程中,并且设置引入路径。
  • 嵌入式入门教学——C51_第66张图片
  • 编写矩阵键盘模块,新建MatrixKey.c和MatrixKey.h用来存放矩阵键盘模块。
  • 编写MatrixKey.h文件。
  • #ifndef __MATRIXKEY_H__
    #define __MATRIXKEY_H__
    unsigned char MatrixKey();
    #endif
  • 【小技巧】快速生成头文件的模板
    • 嵌入式入门教学——C51_第67张图片嵌入式入门教学——C51_第68张图片
    • 添加后,双击即可使用。
  • 编写MatrixKey.c文件。
  • #include 
    #include "Delay.h"
    /**
    	*	@brief	矩阵键盘读取按键键码
    	* @param	无
    	*	@retval	KeyNumber 按下按键的键码值
    		如果按键按下不放,程序会停留在此函数,松手的瞬间,返回键码,没有按键按下时,返回零。
    	*/
    unsigned char MatrixKey(){
    	unsigned char KeyNumber=0;
    	// 按列扫描
    	// 第一列
    	P1=0xFF;
    	P1_3=0;
    	if(P1_7==0){ Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}
    	if(P1_6==0){ Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}
    	if(P1_5==0){ Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;}
    	if(P1_4==0){ Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;}
    	// 第二列
    	P1=0xFF;
    	P1_2=0;
    	if(P1_7==0){ Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;}
    	if(P1_6==0){ Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;}
    	if(P1_5==0){ Delay(20);while(P1_5==0);Delay(20);KeyNumber=10;}
    	if(P1_4==0){ Delay(20);while(P1_4==0);Delay(20);KeyNumber=14;}
    	// 第三列
    	P1=0xFF;
    	P1_1=0;
    	if(P1_7==0){ Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}
    	if(P1_6==0){ Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;}
    	if(P1_5==0){ Delay(20);while(P1_5==0);Delay(20);KeyNumber=11;}
    	if(P1_4==0){ Delay(20);while(P1_4==0);Delay(20);KeyNumber=15;}
    	// 第四列
    	P1=0xFF;
    	P1_0=0;
    	if(P1_7==0){ Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;}
    	if(P1_6==0){ Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;}
    	if(P1_5==0){ Delay(20);while(P1_5==0);Delay(20);KeyNumber=12;}
    	if(P1_4==0){ Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;}
    
    	return KeyNumber;
    }
  • 编写main.c文件。
  • #include 
    #include "Delay.h"
    #include "LCD1602.h"
    #include "MatrixKey.h"
    unsigned char KeyNum;
    void main(){
    	LCD_Init();
    	LCD_ShowString(1,1,"Which One?");
    	while(1){
    		KeyNum=MatrixKey();
    		if(KeyNum){ // 不加if,KeyNum会马上刷新为0
    			LCD_ShowNum(2,1,KeyNum,2);
    		}
    	}
    }
  •  编译下载程序到单片机,按键后LCD1602显示对应键码值。
  • 嵌入式入门教学——C51_第69张图片

4、矩阵键盘密码锁(LCD1602显示)

  • 内容:s1~s10为1~9和0,s11为确定,s12为取消。输入密码正确,显示TRUE,输入密码错误,显示ERR。
  • 复制上一个工程的文件夹并修改名称(快速创建),将其中的MatrixKey.c和MatrixKey.h放入Functions中。
  • 嵌入式入门教学——C51_第70张图片
  • 打开工程,将原先的MatrixKey.c和MatrixKey.h从工程中删除,重新添加MatrixKey.c文件,并设置引入路径。(将矩阵键盘模块化)
  • 嵌入式入门教学——C51_第71张图片
  • 修改main.c文件。
  • #include 
    #include "Delay.h"
    #include "LCD1602.h"
    #include "MatrixKey.h"
    unsigned char KeyNum;
    unsigned int Password,Count;
    void main(){
    	LCD_Init();
    	LCD_ShowString(1,1,"Password:");
    	while(1){
    		KeyNum=MatrixKey();
    		if(KeyNum){ // 不加if,KeyNum会马上刷新为0
    			if(KeyNum<=10){ // 如果是s1~s10按下,输入密码
    				if(Count<4){ // 限制密码位数
    					Password*=10; // 密码左移一位
    					Password+=KeyNum%10; // 将10转化为0,获得一位密码
    					Count++; // 记录密码位数
    				}
    			}
    			LCD_ShowNum(2,1,Password,4); // 更新显示
    		}
    		if(KeyNum==11){ // s11按下,确认
    			if(Password==1234){ // 判断密码
    				LCD_ShowString(1,11,"TRUE"); // 密码正确
    				Password=0; // 密码清零
    				Count=0;	// 计次清零
    				LCD_ShowNum(2,1,Password,4); // 更新显示
    			}else{
    				LCD_ShowString(1,11,"ERR"); // 密码错误
    				Password=0; // 密码清零
    				Count=0;	// 计次清零
    				LCD_ShowNum(2,1,Password,4); // 更新显示
    			}
    		}
    		if(KeyNum==12){ // s12按下,取消
    				Password=0; // 密码清零
    				Count=0;	// 计次清零
    				LCD_ShowNum(2,1,Password,4); // 更新显示
    		}
    	}
    }
  • 编译下载程序到单片机,调试显示正常。 

八、定时器和中断

1、定时器

  • 定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号每隔“一秒”,计数单元的数值就增加一,当计数单元数值增加到“设定的闹钟提醒时间”时,计数单元就会向中断系统发出中断申请产生“响铃提醒”,使程序跳转到中断服务函数中执行。
  • 嵌入式入门教学——C51_第72张图片
  • 定时器作用:
    • 用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作。
    • 替代长时间的Delay,提高CPU的运行效率和处理速度。
  • 定时器个数:3个(TO、T1、T2),TO和T1与传统的51单片机兼容T2是此型号单片机增加的资源(不同的单片机可能有不同数量的定时器)。
1.1、定时器工作模式
  • STC89C52的TO和T1均有四种工作模式:
    • 模式0:13位定时器/计数器
    • 模式1:16位定时器/计数器 (常用)
    • 模式2:8位自动重装模式
    • 模式3:两个8位计数器
  • 模式1框图:
    • 由SYSclk(系统时钟)或T0 Pin(外部脉冲)提供脉冲。
    • SYSclk:系统时钟,即晶振周期,本开发板上的晶振为12MHZ。
    • TL0和TH0(两个8位,范围是0~65535)用于计数,每隔1us加一,直到溢出时,产生中断,总共定时时间为65535us。
      • 例如,需要计时1ms,只需要给其赋值64535,还有1000到65535,刚好为1ms。
1.2、定时器相关寄存器
  • 寄存器是连接软硬件的媒介,在单片机中寄存器就是一段特殊的RAM存储器一方面,寄存器可以存储和读取数据;另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式寄存器相当于一个复杂机器的“操作按钮”。
  • 嵌入式入门教学——C51_第73张图片
  • TCON是控制寄存器,控制T0、T1的启动和停止及设置溢出标志,即控制定时器启动和中断申请。(具体每一位的作用看参考手册)
    • TF1:定时器/计数器T1溢出标志。
    • TR1:定时器T1的运行控制位。
    • TF0:定时器/计数器T0溢出中断标志。
    • TR0:定时器T0的运行控制位。
    • IE1:外部中断1请求源标志。
    • IT1:外部中断1触发方式控制位。
    • IE0:外部中断0请求源标志。
    • IT0:外部中断0触发方式控制位。
  • TMOD是定时/计数器的工作方式寄存器,确定工作方式和功能。(具体每一位的作用看参考手册)
    • 嵌入式入门教学——C51_第74张图片
    • GATE:定时器开关。
    • C/T:选择定时器。
    • M1、M0:定时器/计数器模式选择。

2、中断

  • CPU在处理某一事件A时,发生了另一事件B请求CPU迅速去处理(中断发生);CPU暂时中断当前的工作,转去处理事件B(中断响应和中断服务);待CPU将事件B处理完毕后,再回到原来事件A被中断的地方继续处理事件A(中断返回),这一过程称为中断。
  • 嵌入式入门教学——C51_第75张图片
  • 引起CPU中断的根源,称为中断源
  • 被中断的地方,称为断点
  • 中断功能的部件,称为中断系统
2.1、中断系统的结构
  • STC89C52中断资源:
    • 中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、外部中断2、外部中断3)
    • 中断优先级个数:4个
    • 中断号:嵌入式入门教学——C51_第76张图片
  • 中断结构(部分)
    • 嵌入式入门教学——C51_第77张图片
  • 各中断源响应优先级:
    • 0 外部中断0
    • 1 定时/计数器0
    • 2 外部中断1 
    • 3 定时/计数器1 
    • 4 串行口
  • 中断优先级的三个原则:
    • CPU同时接收到几个中断时,首先响应优先级别最高的中断请求。
    • 正在进行的中断过程不能被新的同级或低优先级的中断请求所中断。
    • 正在进行的低优先级中断服务,能被高优先级中断请求所中断。
  • 中断系统内部设有两个用户不能寻址的优先级状态触发器。
    • 其中一个置1,表示正在响应高优先级的中断,它将阻断后来所有的中断请求。
    • 另一个置1,表示正在响应低优先级中断,它将阻断后来所有的低优先级中断请求。
  • 中断响应的条件:
    • 中断源有中断请求。
    • 此中断源的中断允许位为1。
    • CPU开中断(即EA=1)。
2.2、中断寄存器
  • 嵌入式入门教学——C51_第78张图片
  • IE是中断允许寄存器,控制中断的开启。
    • EA:CPU的总中断允许控制位。
    • ET2:定时/计数器T2的溢出中断允许位。
    • ES:串行口1中断允许位。
    • ET1:定时/计数器T1的溢出中断允许位。
    • EX1:外部中断1中断允许位。
    • ET0:T0的溢出中断允许位。
    • EX0:外部中断0中断允许位。
  • IPH是中断优先级控制寄存器高。(具体每一位的作用看参考手册)
  • IP是中断优先级控制寄存器低。
    • PT0H,PT0:定时器0中断优先级控制位。(其他的看参考手册)
      • 当PT0H=0且PT0=0时,定时器0中断为最低优先级中断(优先级0)
      • 当PT0H=0且PT0=1时,定时器0中断为较低优先级中断(优先级1)
      • 当PT0H=1且PT0=0时,定时器0中断为较高优先级中断(优先级2)
      • 当PT0H=1且PT0=1时,定时器0中断为最高优先级中断(优先级3)
  • 【注】不可位寻址的寄存器只能整体赋值;可位寻址的寄存器可以按位赋值。

3、独立按键控制LED流水灯状态

  • 内容:按下独立键盘的按键,改变LED流水灯的流转方向。
  • 新建工程,在工程目录下新建Objects、Listings文件夹,并设置.hex文件和.lst文件存放位置。
3.1、定时器0初始化
3.1.1、手写
  • 使用定时器0需要对其进行初始化,包括选择定时器和其工作模式。
  • 对TMOD寄存器操作,选择定时器和其工作模式。(不可按位赋值)
    • 嵌入式入门教学——C51_第79张图片
    • 高4位控制定时器1,不使用,故赋全0,即TMOD=0000 0001。
  • 对TCON寄存器操作,控制寄存器开启。(可按位赋值)
    • TF=0;TR0=1;
  • 配置中断
    • 嵌入式入门教学——C51_第80张图片
    • ET0=1; EA=1; PT0=0;
  • 编写代码
    • //定时器0初始化
      void Timer0Init(){
      	//TMOD=0x01; // 0000 0001 这样赋值在同时使用定时器1和定时器0时可能会产生冲突
      	TMOD&=0xF0; // 把TMOD低四位清零,高四位不变
      	TMOD|=TMOD|0x01; // 把TMOD最低位置1,高四位不变
      	TF0=0; // 中断溢出标志位
      	TR0=1; // 开启定时器
      	// 初始化计数器
      	TH0=64535/256; // 取高位
      	TL0=64535%256+1; // 取低位
      	// 配置中断
      	ET0=1; // 允许中断
      	EA=1;  // 允许总中断
      	PT0=0; // 中断优先级
      }
3.1.2、自动生成
  • 也可以使用STC-ISP生成初始化函数。(建议使用)
  • 嵌入式入门教学——C51_第81张图片
  • 但是没有配置中断,需要另外添加。
  • void Timer0Init(void)		//1毫秒@12.000MHz
    {
    	TMOD &= 0xF0;		//设置定时器模式
    	TMOD |= 0x01;		//设置定时器模式
    	TL0 = 0x18;		//设置定时初值
    	TH0 = 0xFC;		//设置定时初值
    	TF0 = 0;		//清除TF0标志
    	TR0 = 1;		//定时器0开始计时
    	// 配置中断
    	ET0=1; // 允许中断
    	EA=1;  // 允许总中断
    	PT0=0; // 中断优先级
    }
3.1.3、定时器模块化
  • 在工程目录下新建Functions文件夹,将Timer0.c和Timer0.h放入其中。将Timer.c加入工程中,并设置其引入路径。
  • Timer0.c
  • #include 
    /**
    	*	@brief	定时器0初始化,1毫秒@12.000MHz
    	* 	@param	无
    	*	@retval	无
    	*/
    void Timer0Init(void)		//1毫秒@12.000MHz
    {
    	TMOD &= 0xF0;		//设置定时器模式
    	TMOD |= 0x01;		//设置定时器模式
    	TL0 = 0x18;		//设置定时初值
    	TH0 = 0xFC;		//设置定时初值
    	TF0 = 0;		//清除TF0标志
    	TR0 = 1;		//定时器0开始计时
    	// 配置中断
    	ET0=1;
    	EA=1; 
    	PT0=0;
    }
    /* 定时器中断函数模板
    void Timer0_Routine() interrupt 1{
    	static unsigned int T0Count;
    	TL0 = 0x18;		//设置定时初值
    	TH0 = 0xFC;		//设置定时初值
    	T0Count++;
    	if(T0Count>=1000){
    		T0Count=0;
    		P2_0=~P2_0;
    	}
    }
    */
  • Timer0.h
  • #ifndef __TIMER0_H__
    #define __TIMER0_H__
    void Timer0Init(void);
    #endif
3.2、独立按键模块化
  • 将延时函数模块和独立按键模块放入Functions文件夹。将Delay.c和Key.c加入工程中,并设置其引入路径。
  • Key.c
  • #include 
    #include "Delay.h"
    /**
    	*	@brief	获取独立按键键码
    	* 	@param	无
    	*	@retval	按下按键的键码,范围:0~4,无按键按下时,返回0
    	*/
    unsigned char Key(){
    	unsigned char KeyNumber=0;
    	if(P3_1==0){Delay(20);while(P3_1==0);Delay(20);KeyNumber=1;}
    	if(P3_0==0){Delay(20);while(P3_0==0);Delay(20);KeyNumber=2;}
    	if(P3_2==0){Delay(20);while(P3_2==0);Delay(20);KeyNumber=3;}
    	if(P3_3==0){Delay(20);while(P3_3==0);Delay(20);KeyNumber=4;}
    	return KeyNumber;
    }
  • Key.h
  • #ifndef __KEY_H__
    #define __KEY_H__
    unsigned char Key();
    #endif
    
3.3、编写main.c文件
  • 定时器0每隔1微秒会执行中断函数,而中断函数中每隔500次会执行真正的操作,也就是每隔0.5ms会移动一次LED。
  • #include 
    #include "Timer0.h"
    #include "Key.h"
    #include  // 引入_crol_()和_cror_()循环移位函数
    unsigned char KeyNum,LEDMode;
    void main(){
    	Timer0Init(); // 定时器0初始化
    	P2=0xFE; // 初始化LED
    	while(1){ // 每当定时器溢出,就跳转执行中断函数
    		KeyNum=Key();
    		if(KeyNum){
    			if(KeyNum==1){
    				LEDMode++;
    				if(LEDMode>=2) LEDMode=0; // LEDMode只在0和1变化
    			}
    		}
    	}
    }
    // 中断函数
    void Timer0_Routine() interrupt 1{
    	static unsigned int T0Count;
    	TL0 = 0x18;		//设置定时初值
    	TH0 = 0xFC;		//设置定时初值
    	T0Count++;
    	if(T0Count>=500){ // 每隔0.5ms让LED移动
    		T0Count=0;
    		if(LEDMode==0)
    			P2=_crol_(P2,1); // P2左移1位
    		else P2=_cror_(P2,1); // P2右移1位
    	}
    }

4、定时器时钟(LCD1602显示)

  • 新建工程,在工程目录下新建Functions、Objects、Listings文件夹,将延时函数、定时器和LCD1602模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中,将.lst文件存放到Listings中。新建main.c文件,添加Delay.c、LCD1602.c、Timer0.c到工程中,并设置其引入路径。
  • 编写main.c文件。
  • #include 
    #include "Delay.h"
    #include "LCD1602.h"
    #include "Timer0.h"
    unsigned char Sec=55,Min=59,Hour=23;
    void main(){
    	LCD_Init();
    	Timer0Init();
    	LCD_ShowString(1,1,"Clock:");
    	LCD_ShowString(2,1,"  :  :");
    	while(1){
    		LCD_ShowNum(2,1,Hour,2); // 小时
    		LCD_ShowNum(2,4,Min,2); // 分钟
    		LCD_ShowNum(2,7,Sec,2); // 秒
    	}
    }
    // 中断函数
    void Timer0_Routine() interrupt 1{
    	static unsigned int T0Count;
    	TL0 = 0x18;		//设置定时初值
    	TH0 = 0xFC;		//设置定时初值
    	T0Count++;
    	if(T0Count>=1000){
    		T0Count=0;
    		Sec++;
    		if(Sec>=60){
    			Sec=0;
    			Min++;
    			if(Min>=60){
    				Min=0;
    				Hour++;
    				if(Hour>=24) Hour=0;
    			}
    		}
    	}
    }

待续。。。

你可能感兴趣的:(嵌入式,51单片机)