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定义),想一下状态

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);  // 多给一位
	}
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐