【B站江科大自动化协51单片机入门教程(笔记1)】

文章目录

  • 1 综述
  • 2-1 点亮一个LED
  • 2-2 LED闪烁
  • 2-3 LED流水灯
  • 2-4 LED流水灯PLus
  • 3-1 独立按键控制LED亮灭
  • 3-2 独立按键控制LED状态
  • 3-3 独立按键控制LED显示二进制
  • 3-4 独立按键控制LED移位
  • 4-1 静态数码管显示
  • 4-2 动态数码管显示
  • 5-1 模块化编程
  • Delay模块
  • 5-2 LCD1602调试工具
  • LCD1602模块
  • 1 综述

    1. 硬件设备(必须准备的)


    51单片机开发板



    windows电脑

    1. 软件设备(也是必须准备的)


    Keil5编写程序



    STC-ISP下载程序

  • Keil5软件注意事项
  • Keil5 C51和Keil5 MDK的区别

    ​ 两者都是Keil系列软件,但前者是用来开发51单片机的,后者是用来开发ARM系列,比如STM32的。

    2-1 点亮一个LED

    双击Keil5软件进入,我们需要编写代码让单片机去识别,在写代码之前我们需要新建一个项目,相当于提供一个办公桌(类似于Visual studio)

    image-20220804161203467

    最好是在桌面创建文件夹后,再创建项目子文件夹并命名位Project

    image-20220804164435813

    虽然没有STC89C52,我们可以选择AT89C52代替

    image-20220804164517372

    一般不需要更改启动文件,选择否

    image-20220804164557704

    添加c语言文件

    image-20220804165645088

    命名与建立

    image-20220804165738586

    生成hex文件,然后在stc-isp中去下载到单片机中

    image-20220804170140393

    **先选择单片机型号,**我购买的单片机型号是STC89C52RC,**点击打开程序文件,**然后找到2-1文件夹中的object文件夹找到.hex文件,选择打开,**然后点击下载/编程,**再开关单片机即可

    image-20220804170351326

    2-2 LED闪烁

    和2-1一样的方式,通过新建文件夹,然后新建工程,找到单片机型号,然后创建.c文件,写入以下代码

    #include <REGX52.H>
    
    void main()
    {
    	
    	while(1)
    	{
    		P2=0xFE;
    		P2=0xFF;
    	}
    }
    

    但此时可以发现LED不是那么的亮,这是因为单片机执行代码速度特别快,看不出来在闪,我们要让亮和灭以后延时500ms,才能看出来,可以通过stc-isp生成

    image-20220804215134086

    生成的代码如下:(还需要添加头文件)

    void Delay500ms()		//@12.000MHz
    {
    	unsigned char i, j, k;
    
    	_nop_();
    	i = 4;
    	j = 205;
    	k = 187;
    	do
    	{
    		do
    		{
    			while (--k);
    		} while (--j);
    	} while (--i);
    }
    

    每次都需要生成.hex文件,别忘了!

    image-20220804215457969

    最后的代码:

    #include <REGX52.H>
    #include <INTRINS.H>
    
    void Delay500ms()		//@12.000MHz
    {
    	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();
    		P2=0xFF;
    		Delay500ms();
    	}
    }
    

    2-3 LED流水灯

    通过顺序执行代码,逐个控制亮灭,可以实现流水灯,将LED闪烁的代码加以复制粘贴再修改即可

    #include <REGX52.H>
    #include <INTRINS.H>
    
    void Delay500ms()		//@12.000MHz
    {
    	unsigned char i, j, k;
    
    	_nop_();
    	i = 4;
    	j = 205;
    	k = 187;
    	do
    	{
    		do
    		{
    			while (--k);
    		} while (--j);
    	} while (--i);
    }
    
    
    int main()
    {
    	while(1)
    	{
    		P2=0xFE;      //1111 1110
    		Delay500ms();
    		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();
    	}
    }
    

    现在是延迟500ms,那么我们如果想要200ms延迟呢?可以通过对Delay函数入手,通过给参数实现

    2-4 LED流水灯PLus

    void Delay1ms(unsigned int xms)		//@12.000MHz
    {
    	unsigned char i, j;
    
    	i = 2;
    	j = 239;
    	do
    	{
    		while (--j);
    	} while (--i);
    }
    

    xms需要指定一个数据类型(存数据的小盒子,存起来才能算),integer(整型)–int,16位(单片机)

    #include <REGX52.H>
    #include <INTRINS.H>
    
    void Delay1ms(unsigned int xms)		//@12.000MHz
    {
    	unsigned char i, j;
      while(xms)               //非0为真,先执行一次即为延时1ms,然后自减到0
    	{
    		i = 2;
    		j = 239;
    	do
    	{
    		while (--j);
    	} while (--i);
    		xms--;
    	}
    }
    
    
    int main()
    {
    		while(1)
    	{
    		P2=0xFE;      //1111 1110
    		Delay1ms(100);
    		P2=0xFD;      //1111 1101   
    		Delay1ms(100);
    		P2=0xFB;      //1111 1011
    		Delay1ms(100);
    		P2=0xF7;      //1111 0111
    		Delay1ms(100);
    		P2=0xEF;      //1110 1111
    		Delay1ms(100);
    		P2=0xDF;      //1101 1111
    		Delay1ms(100);
    		P2=0xBF;      //1011 1111
    		Delay1ms(100);
    		P2=0x7F;      //0111 1111
    		Delay1ms(100);
    	}
    }
    

    3-1 独立按键控制LED亮灭

  • 单片机通电时所有IO口都是高电平

  • 寄存器写一个值会送到IO口上,同样会检测IO口的电平读回来

  • 按键松开,读寄存器,值为1;按下时读寄存器为0

  • P2=0xFE实际上是通过控制寄存器实现的,寄存器8个为一组;如果直接操作P2,则需要同时给8个赋值,现在只想操作最低位的LED,有什么办法能实现呢?

    打开头文件,找到对位寄存器的声明

    /*------------------------------------------------
    P2 Bit Registers
    ------------------------------------------------*/
    sbit P2_0 = 0xA0;
    sbit P2_1 = 0xA1;
    sbit P2_2 = 0xA2;
    sbit P2_3 = 0xA3;
    sbit P2_4 = 0xA4;
    sbit P2_5 = 0xA5;
    sbit P2_6 = 0xA6;
    sbit P2_7 = 0xA7;
    

    <REGX52.H>有了位声明,可以直接用

    image-20220805103628181

    现在就是要实现按下实现P2_0=0,松开实现P2_0=1

    #include <REGX52.H>
    
    int main()
    {
    	while(1)
    	{
    		if(P3_1==0)   //K1接在了P31,直接读这个寄存器
    		{
    			P2_0=0;
    		}
    		else
    		{
    			P2_0=1;
    		}
    	}
    }
    

    for循环可以用来产生固定循环次数

    3-2 独立按键控制LED状态

    首先了解一下按键的抖动

  • 对于机械开关,当机械触电断开、闭合时,由于机械触电的弹性作用,一个开关闭合时不会马上稳定地接通,在断开时也不会一下子就断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动
  • 肉眼可能无法观察,比如日常生活中的开关灯,但单片机执行速率是MHZ级别的,会有明显影响,如何消除这个影响呢?

  • 硬件消抖:通过电路过滤掉
  • 软件处理:按键按下时延时20ms,松手时也延时20ms
  • 先利用stc-isp生成一个1ms的延时函数,然后测试一下:

    #include <REGX52.H>
    
    void Delay(unsigned int xms)		//@12.000MHz
    {
    	unsigned char i, j;
    	while(xms)
    	{
    		i = 2;
    		j = 239;
    		do
    		{
    			while (--j);
    		} while (--i);
    		xms--
    	}
    	
    }
    
    int main()
    {
    	while(1)
    	{
    		P2_0=0;
    		Delay(500);
    		P2_0=1;
    		Delay(500);
    	}
    }
    

    测试没有问题,那么继续写

    #include <REGX52.H>
    
    void Delay(unsigned int xms)		//@12.000MHz
    {
    	unsigned char i, j;
    	while(xms)
    	{
    		i = 2;
    		j = 239;
    		do
    		{
    			while (--j);
    		} while (--i);
    		xms--;
    	}
    	
    }
    
    int main()
    {
    	while(1)
    	{
    		if(P3_1==0)
    		{
    			Delay(20);            //如果P31是按下状态,那么延时20ms
    			while(P3_1==0);       //按下不操作,松手才操作;while(P3_1==0)检测松手,不能不加
    			Delay(20);            //松手以后,消除松手抖动
    			P2_0=~P2_0;           //可以操作了,然后取反
    		}
    			
    	}
    }
    

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

    参照上一节写出代码,中间检查:

    #include <REGX52.H>
    
    void Delay(unsigned int xms)		//@12.000MHz
    {
    	unsigned char i, j;
    	while(xms)
    	{
    		i = 2;
    		j = 239;
    		do
    		{
    			while (--j);
    		} while (--i);
    		xms--;
    	}
    }
    
    int main()
    {
    	while(1)
    	{
    		if(P3_1==0)
    		{
    			Delay(20);
    			while(P3_1==0);   // 对1位操作
    			Delay(20);
    			// 1111 1111
    			P2++;       // 8位总线直接操作,一开始是1111 1111,然后P2++会溢出,变为0000 0000 
    		}
    	}
    		
    }
    

    此时LED状态相反,该亮的灭,该灭的亮;那么直接取反,P2=~P2试一下,结果发现灯都不亮

    P2上电默认为1111 1111,P2++ 后变为0000 0000(溢出),取反后变为1111 1111,**一直都会是这样,所以不亮,**我们可以定义一个变量,然后把变量送给P2口。

    调整一下:

    int main()
    {
    	unsigned char LEDNum=0;     // 无符号字符型为0~255的8位二进制数据,表示一个寄存器
    	while(1)
    	{
    		if(P3_1==0)
    		{
    			Delay(20);
    			while(P3_1==0);
    			Delay(20);
    			
    			LEDNum=++;
    			P2=~LEDNum;
    		}
    	}
    		
    }
    

    3-4 独立按键控制LED移位

    把准备工作都做好(比前两节多了一个LEDNum定义),想一下状态

    #mermaid-svg-gRSaBEpnTbTVzZVw {font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-gRSaBEpnTbTVzZVw .error-icon{fill:#552222;}#mermaid-svg-gRSaBEpnTbTVzZVw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gRSaBEpnTbTVzZVw .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-gRSaBEpnTbTVzZVw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gRSaBEpnTbTVzZVw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gRSaBEpnTbTVzZVw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gRSaBEpnTbTVzZVw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gRSaBEpnTbTVzZVw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gRSaBEpnTbTVzZVw .marker.cross{stroke:#333333;}#mermaid-svg-gRSaBEpnTbTVzZVw svg{font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gRSaBEpnTbTVzZVw .label{font-family:”trebuchet ms”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-gRSaBEpnTbTVzZVw .cluster-label text{fill:#333;}#mermaid-svg-gRSaBEpnTbTVzZVw .cluster-label span{color:#333;}#mermaid-svg-gRSaBEpnTbTVzZVw .label text,#mermaid-svg-gRSaBEpnTbTVzZVw span{fill:#333;color:#333;}#mermaid-svg-gRSaBEpnTbTVzZVw .node rect,#mermaid-svg-gRSaBEpnTbTVzZVw .node circle,#mermaid-svg-gRSaBEpnTbTVzZVw .node ellipse,#mermaid-svg-gRSaBEpnTbTVzZVw .node polygon,#mermaid-svg-gRSaBEpnTbTVzZVw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gRSaBEpnTbTVzZVw .node .label{text-align:center;}#mermaid-svg-gRSaBEpnTbTVzZVw .node.clickable{cursor:pointer;}#mermaid-svg-gRSaBEpnTbTVzZVw .arrowheadPath{fill:#333333;}#mermaid-svg-gRSaBEpnTbTVzZVw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gRSaBEpnTbTVzZVw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gRSaBEpnTbTVzZVw .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-gRSaBEpnTbTVzZVw .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-gRSaBEpnTbTVzZVw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gRSaBEpnTbTVzZVw .cluster text{fill:#333;}#mermaid-svg-gRSaBEpnTbTVzZVw .cluster span{color:#333;}#mermaid-svg-gRSaBEpnTbTVzZVw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gRSaBEpnTbTVzZVw :root{–mermaid-font-family:”trebuchet ms”,verdana,arial,sans-serif;}

    0000 0001

    0000 0010

    0000 0100

    0000 1000

    可以使用位运算实现:

    0000 0001 0x01<<0

    0000 0010 0x01<<1

    0000 0100 0x01<<2

    0000 1000 0x01<<3

    0001 0000 0x01<<4

    0010 0000 0x01<<5

    0100 0000 0x01<<6

    1000 0000 0x01<<7 (这些都是正逻辑,1为亮,0为灭,一会需要取反)

    可以让0-7定义为LEDNum,每按一下+1,移到最左边时再回去(if)

    #include <REGX52.H>
    void Delay(unsigned int xms);
    
    unsigned char LEDNum;
    
    int main()
    {
    	while(1)
    	{
    		if(P3_1==0)
    		{
    			Delay(20);
    			while(P3_1==0);
    			Delay(20);
    			
    			LEDNum++;
    			if(LEDNum>=8)     // if语句如果只有一句,可以不用{}
    				LEDNum=0;
    			P2=~(0x01<<LEDNum) // P2是一个反向逻辑,给1是灭,给0是亮,所以取反
    		}
    	}
    }
    	
    
    void Delay(unsigned int xms)		//@12.000MHz
    {
    	unsigned char i, j;
    	while(xms)
    	{
    		i = 2;
    		j = 239;
    		do
    		{
    			while (--j);
    		} while (--i);
    		xms--;
    	}
    }
    

    发现有一个小问题:按第一下的时候直接是D2亮而不是D1亮,给P2赋初始值P2=~0x01;,写入主函数

    int main()
    {
        P2=~0x01;
    	while(1)
    	{
    		if(P3_1==0)
    		{
    			Delay(20);
    			while(P3_1==0);
    			Delay(20);
    			
    			LEDNum++;
    			if(LEDNum>=8)     // if语句如果只有一句,可以不用{}
    				LEDNum=0;
    			P2=~(0x01<<LEDNum) // P2是一个反向逻辑,给1是灭,给0是亮,所以取反
    		}
    	}
    }
    

    如果是多个按键控制呢?如两个按键,一个左移一个右移,需要注意

  • 不是真正的往右移,而是相对于前面的少往右移一位,相当于左移
  • 比如,本来是左移五位,按下K2后变成了左移4位,相当于右移了一位
  • #include <REGX52.H>
    void Delay(unsigned int xms);    //另一种声明方式
    
    unsigned char LEDNum;
    
    int main()
    {
    	P2=~0x01;
    	while(1)
    	{
    		if(P3_1==0)
    		{
    			Delay(20);
    			while(P3_1==0);
    			Delay(20);
    			
    			LEDNum++;
    			if(LEDNum>=8)
    				LEDNum=0;
    			P2=~(0x01<<LEDNum);
    		}
    		
    		if(P3_0==0)       // 开关是31 30 32 33
    		{
    			Delay(20);
    			while(P3_0==0);
    			Delay(20);
    			
    			if(LEDNum==0)     // 也需要越界判断,之前定义的是无符号char型,减到0再往下会有越界
    				LEDNum=7;    // 先判断,才操作,如果已经为0,那么赋给最大值7
    			else
    				LEDNum--;     // 不是真正的往右移,而是相对于前面的少往右移一位,相当于左移
    			P2=~(0x01<<LEDNum);
    		}
    	}
    }
    	
    
    void Delay(unsigned int xms)		//@12.000MHz
    {
    	unsigned char i, j;
    	while(xms)
    	{
    		i = 2;
    		j = 239;
    		do
    		{
    			while (--j);
    		} while (--i);
    		xms--;
    	}
    }
    

    4-1 静态数码管显示

  • LED数码管:数码管是一种简单廉价的显示器,是由多个发光二极管封装在一起组成“8”字型器件
  • image-20220807162302252

    一位数码管的连接方式:

    image-20220807162541878

    A~G和一个字节的八位对应;虽然这么看引脚比较乱,但是如果在图上表明的话可以发现引脚是“就近引出”

    如果要显示数字6,那么就需要把AFGEDC点亮:

  • 对应共阴极接法图中,3,8接地,然后A~DP输入10111110(也叫段码),把这八个数据给单片机的IO口上,即可显示"6"

  • 对应共阳极接法图中,3,8接Vcc,然后A~DP输入01000001(与共阴极相反)

  • 四位一体数码管(四个大公共端引出,其余字母相同的管子连在一块,如所有A连一块,所有B连一块):

    image-20220807163717254

    如果想在第三位显示“1”,按共阴极接法

    1. 先把12、9、6给1,8给0;这么做只会让第三个亮,其他的不亮
    2. 下面的口给0110 0000即可

    但这么做的话,如果其他数码管亮,也是一样的数字,怎么产生不一样的数字呢?(动态数码管显示)

    C51数组

  • 数组:把相同类型的一系列数据统一编制到某一个组别中,可以通过数组名+索引号简单快捷的操作大量数据
  • int x[3];        // 定义一组变量(3个)
    int x[]={1,2,3}  // 定义一组变量并初始化
    
    
    x[0]             // 引用数组的第0个变量
    x[1]             // 引用数组的第1个变量
    x[2]             // 引用数组的第2个变量
    // 引用x=3时,数组越界,读出的数值不稳定。应该避免这种操作
    

    C51子函数

  • 子函数:将完成某一种功能的程序代码单独抽取出来形成一个模块,在其他函数中可随时调用此模块,以达到代码的复用和优化程序结构的目的
  • void Function(unsigned char x, y)
    {
    	
    }
    
    返回值 函数名(形参)
    {
    	函数体
    }
    

    数码管第三位输出“6”

    #include <REGX52.H>
    
    int main()
    {
    	P2_4=1;     // 第三位显示6,74138对应于LED6是Y5,则P24-P22输入101
    	P2_3=0;
    	P2_2=1;
    	P0=0x7D;    // 下面P07-P00对应是0111 1101对应十六进制数为7D
    	while(1)
    	{
    		
    	}
    		
    }
    

    image-20220808161719795

  • 第三位显示6,74138对应于LED6是Y5,则P24-P22输入101
  • 下面P07-P00对应是0111 1101对应十六进制数为7D
  • 现在把数码管的某一位显示某一个数字封装成一个子函数

    #include <REGX52.H>
    
    unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; // 0-9的段码表
    
    void Nixie(unsigned char Location,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];     // P0的值即为要显示的数字
    }
    
    
    int main()
    {
    	Nixie(2,3);
    	while(1)
    	{
    		
    	}
    		
    }
    

    4-2 动态数码管显示

    本节实现多个位置显示不同的数字

    1. 动态数码管就是不断的扫描,这是一个循环过程,那么先把Nixie(2,3)放到while循环里,复制几段(表示三位)
    2. 加入通用的延时函数
    int main()
    {
    	
    	while(1)
    	{
    		Nixie(1,1);
    //		Delay(20);
    		Nixie(2,2);
    //		Delay(20);
    		Nixie(3,3);
    //		Delay(20);
    	}
    }
    

    此时位置显示有点错乱,来源于数码管的常见问题,我们需要加一段消影代码

    位选 段选 位选 段选 位选 段选,在下一位进行位选,上一位的段选会窜到下一位(二者紧挨着而且下一位的段选还没有到)如何避免这个问题呢?

  • 在段选之后加入清零,形成 位选 段选 清零 位选 段选 清零,即使窜到下一位也是清零窜到下一位,优化子函数
  • void Nixie(unsigned char Location,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;               // 清零
    }
    

    这样数码管就会显示123了,总代码如下:

    #include <REGX52.H>
    
    unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; 
    
    void Delay(unsigned int xms)		//@12.000MHz
    {
    	unsigned char i, j;
    	while(xms--)
    	{
          i = 2;
    	  j = 239;
    		do
    		{
    			while (--j);
    		} while (--i);
    	}
    	
    }
    
    
    void Nixie(unsigned char Location,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;
    }
    
    
    int main()
    {
    	
    	while(1)
    	{
    		Nixie(1,1);
    //		Delay(20);
    		Nixie(2,2);
    //		Delay(20);
    		Nixie(3,3);
    //		Delay(20);
    	}
    }
    

    5-1 模块化编程

  • 传统方式编程:所有的函数均放在main.c里,若使用的模块比较多,则一个文件内会有很多的代码,不利于代码的组织和管理,而且很影响编程者的思路

  • 模块化编程:把各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数的声明,其它.c文件想使用其中的代码时,只需要#include "XXX.h"文件即可。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等

  • image-20220809152503968

    之前是吧Delay函数放在main函数前面,现在将Delay函数模块化。Delay.c给主函数并不需要把所有的东西都包含进去,只需要把声明包含进去。所以在.h文件中都是提供一个接口

    image-20220809153408055

    C预编译

  • C语言的预编译以#开头,作用是在真正的编译开始之前,对代码做一些处理
  • image-20220809153804516

  • 此外还有#ifdef,#if,#else,#elif,#undef等

  • 其实#ifndef等语句是对程序的某些部分是否编译进行选择

  • 练习数码管显示函数和Delay函数的模块化

    #include <REGX52.H>   // <>是在安装目录里找这个文件
    #include "Delay.h"    // ""是在自己程序目录里寻找文件
    
    void main()
    {
    	while(1)
    	{
    		
    	}
    }
    

    建立好一个Delay.c文件后,添加头文件(文件名一般与.c文件名相同)

    image-20220809155553100

    image-20220809155905418

    在建立数码管显示模块时,需要加头文件(函数中用到了P2等变量,需要在同一个.c文件里面加入)

    image-20220809163404756

    即可通过模块化轻松实现代码的简洁化:

    #include <REGX52.H>
    #include "Delay.h"
    #include "Nixie.h"
    
    void main()
    {
    	while(1)
    	{
    		Nixie(1,1);
    		Nixie(2,2);
    		Nixie(3,3);
    	}
    }
    

    Delay模块

    Delay.c

    #include <INTRINS.H>
    
    void Delay(unsigned int xms)		//@11.0592MHz
    {
    	unsigned char i, j;
    
    	while(xms--)
    	{
    			_nop_();
    			i = 2;
    			j = 199;
    		do
    		{
    			while (--j);
    		} while (--i);
    	}
    	
    }
    

    Delay.h

    #ifndef __DELAY_H__
    #define __DELAY_H__
    
    void Delay(unsigned int xms);
    
    
    #endif
    

    5-2 LCD1602调试工具

    image-20220809185612791

    调试还有串口,数码管等。LCD1602比较方便

  • 比如数码管扫描不及时会闪烁,并且显示内容比较少
  • 可以通过串口把数据放到电脑上观察,需要不断打开
  • 下面我们来试用一下,新建项目以后找到LCD1602.c和LCD1602.h文件添加的工程目录下

    image-20220809190735605

    LCD1602模块

  • LCD1602.h
  • #ifndef __LCD1602_H__
    #define __LCD1602_H__
    
    //用户调用函数:
    void LCD_Init();
    void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
    void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
    void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
    void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
    void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
    void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
    
    #endif
    
    
  • LCD1602.c
  • #include <REGX52.H>
    
    //引脚配置:
    sbit LCD_RS=P2^6;
    sbit LCD_RW=P2^5;
    sbit LCD_EN=P2^7;
    #define LCD_DataPort P0
    
    //函数定义:
    /**
      * @brief  LCD1602延时函数,12MHz调用可延时1ms
      * @param  无
      * @retval 无
      */
    void LCD_Delay()
    {
    	unsigned char i, j;
    
    	i = 2;
    	j = 239;
    	do
    	{
    		while (--j);
    	} while (--i);
    }
    
    /**
      * @brief  LCD1602写命令
      * @param  Command 要写入的命令
      * @retval 无
      */
    void LCD_WriteCommand(unsigned char Command)
    {
    	LCD_RS=0;
    	LCD_RW=0;
    	LCD_DataPort=Command;
    	LCD_EN=1;
    	LCD_Delay();
    	LCD_EN=0;
    	LCD_Delay();
    }
    
    /**
      * @brief  LCD1602写数据
      * @param  Data 要写入的数据
      * @retval 无
      */
    void LCD_WriteData(unsigned char Data)
    {
    	LCD_RS=1;
    	LCD_RW=0;
    	LCD_DataPort=Data;
    	LCD_EN=1;
    	LCD_Delay();
    	LCD_EN=0;
    	LCD_Delay();
    }
    
    /**
      * @brief  LCD1602设置光标位置
      * @param  Line 行位置,范围:1~2
      * @param  Column 列位置,范围:1~16
      * @retval 无
      */
    void LCD_SetCursor(unsigned char Line,unsigned char Column)
    {
    	if(Line==1)
    	{
    		LCD_WriteCommand(0x80|(Column-1));
    	}
    	else if(Line==2)
    	{
    		LCD_WriteCommand(0x80|(Column-1+0x40));
    	}
    }
    
    /**
      * @brief  LCD1602初始化函数
      * @param  无
      * @retval 无
      */
    void LCD_Init()
    {
    	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
    	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
    	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
    	LCD_WriteCommand(0x01);//光标复位,清屏
    }
    
    /**
      * @brief  在LCD1602指定位置上显示一个字符
      * @param  Line 行位置,范围:1~2
      * @param  Column 列位置,范围:1~16
      * @param  Char 要显示的字符
      * @retval 无
      */
    void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
    {
    	LCD_SetCursor(Line,Column);
    	LCD_WriteData(Char);
    }
    
    /**
      * @brief  在LCD1602指定位置开始显示所给字符串
      * @param  Line 起始行位置,范围:1~2
      * @param  Column 起始列位置,范围:1~16
      * @param  String 要显示的字符串
      * @retval 无
      */
    void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
    {
    	unsigned char i;
    	LCD_SetCursor(Line,Column);
    	for(i=0;String[i]!='\0';i++)
    	{
    		LCD_WriteData(String[i]);
    	}
    }
    
    /**
      * @brief  返回值=X的Y次方
      */
    int LCD_Pow(int X,int Y)
    {
    	unsigned char i;
    	int Result=1;
    	for(i=0;i<Y;i++)
    	{
    		Result*=X;
    	}
    	return Result;
    }
    
    /**
      * @brief  在LCD1602指定位置开始显示所给数字
      * @param  Line 起始行位置,范围:1~2
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~65535
      * @param  Length 要显示数字的长度,范围:1~5
      * @retval 无
      */
    void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
    {
    	unsigned char i;
    	LCD_SetCursor(Line,Column);
    	for(i=Length;i>0;i--)
    	{
    		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
    	}
    }
    
    /**
      * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
      * @param  Line 起始行位置,范围:1~2
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:-32768~32767
      * @param  Length 要显示数字的长度,范围:1~5
      * @retval 无
      */
    void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
    {
    	unsigned char i;
    	unsigned int Number1;
    	LCD_SetCursor(Line,Column);
    	if(Number>=0)
    	{
    		LCD_WriteData('+');
    		Number1=Number;
    	}
    	else
    	{
    		LCD_WriteData('-');
    		Number1=-Number;
    	}
    	for(i=Length;i>0;i--)
    	{
    		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
    	}
    }
    
    /**
      * @brief  在LCD1602指定位置开始以十六进制显示所给数字
      * @param  Line 起始行位置,范围:1~2
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~0xFFFF
      * @param  Length 要显示数字的长度,范围:1~4
      * @retval 无
      */
    void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
    {
    	unsigned char i,SingleNumber;
    	LCD_SetCursor(Line,Column);
    	for(i=Length;i>0;i--)
    	{
    		SingleNumber=Number/LCD_Pow(16,i-1)%16;
    		if(SingleNumber<10)
    		{
    			LCD_WriteData(SingleNumber+'0');
    		}
    		else
    		{
    			LCD_WriteData(SingleNumber-10+'A');
    		}
    	}
    }
    
    /**
      * @brief  在LCD1602指定位置开始以二进制显示所给数字
      * @param  Line 起始行位置,范围:1~2
      * @param  Column 起始列位置,范围:1~16
      * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
      * @param  Length 要显示数字的长度,范围:1~16
      * @retval 无
      */
    void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
    {
    	unsigned char i;
    	LCD_SetCursor(Line,Column);
    	for(i=Length;i>0;i--)
    	{
    		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
    	}
    }
    
    

    可以通过以下代码验证LCD1602的使用

    #include <REGX52.H>
    #include "LCD1602.h"
    int main()
    {
    	LCD_Init();  // 必须先初始化
    	LCD_ShowChar(1,1,'A');      // 字符用单引号
    	LCD_ShowString(1,3,"Hello");  // 字符串用双引号;超过一行会显示不出
    	LCD_ShowNum(1,9,123,3); // 指定长度如果小于给的数,从最高位缺;大于则高位补0,可自行验证
    	LCD_ShowSignedNum(1,13,-66,2); // 不包括符号在内有两位
    	LCD_ShowHexNum(2,1,0xA8,2); 
    	LCD_ShowBinNum(2,4,0xAA,8);  // 虽然是显示二进制数但是不能直接写,只能写16进制数
    	while(1)
    	{
    		
    	}
    }
    
    #include <REGX52.H>
    #include "LCD1602.h"
    
    int Result;
    
    int main()
    {
    	LCD_Init();  // 必须先初始化
    	Result=1+1;
    	LCD_ShowNum(1,1,Result,3);  // 多给一位
    	while(1)
    	{
    		
    	}
    }
    
    #include <REGX52.H>
    #include "LCD1602.h"
    #include "Delay.h"
    
    int Result=0;
    
    int main()
    {
    	LCD_Init();  // 必须先初始化
    	
    	while(1)
    	{
    		Result++;
    		Delay(1000); 
    		LCD_ShowNum(1,1,Result,3);  // 多给一位
    	}
    }
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » 【B站江科大自动化协51单片机入门教程(笔记1)】

    发表评论