51单片机定时器/计数器

目录

1、定时器/计数器0/1介绍

1.1 定时器介绍 

1.2 单片机定时/计数器原理

2、定时器/计数器0和1的相关寄存器

2.1  定时器/计数器控制寄存器TCON

2.2 定时器/计数器工作模式寄存器TMOD

2.3 定时器/计数器工作模式

2.3.1 模式0(13位定时器/计数器)

2.3.2 模式1(16位定时器/计数器)

2.3.3 模式2(8位自动重装模式)

2.3.4 模式3(两个8位计数器)

2.4  时间常数的存储——特殊功能寄存器TL0、TH0、TL1、TH1

3、定时/计数器实战篇 


1、定时器/计数器0/1介绍

1.1 定时器介绍 

在介绍定时器之前我们先科普下几个知识: 

(1)CPU 时序的有关知识

①振荡周期:为单片机提供定时信号的振荡源的周期(晶振周期或外加振荡周期)。
②状态周期:2个振荡周期为1个状态周期,用S表示。振荡周期又称S周期或时钟周期。
③机器周期:1个机器周期含6个状态周期,12个振荡周期。
④指令周期:完成 1条指令所占用的全部时间,它以机器周期为单位。例如:外接晶振为 12MHz 时,51 单片机相关周期的具体值为:振荡周期=1/12us;状态周期=1/6us;机器周期=1us;指令周期=1~4us;

(2)学习定时器前需要明白的几点

①51单片机有两组定时器/计数器,因为既可以定时,又可以计数,故称之为定时器/计数器。
②定时器/计数器和单片机的CPU是相互独立的。定时器/计数器工作的过程是自动完成的,不需要 CPU 的参与。
③51单片机中的定时器/计数器是根据机器内部的时钟或者是外部的脉冲信号对寄存器中的数据加 1。
有了定时器/计数器之后,可以增加单片机的效率,一些简单的重复加1的工作可以交给定时器/计数器处理。CPU转而处理一些复杂的事情。同时可以实现精确定时作用。

1.2 单片机定时/计数器原理

STC89C52系列单片机的定时器0和定时器1,与传统8051的定时器完全兼容,当在定时器1做波特率发生器时,定时器0可以当两个8位定时器用。

STC89C52系列单片机内部设置的两个16位定时器/计数器T0和T1都具有计数方式和定时方式两种工作方式。对每个定时器/计数器(T0和T1),在特殊功能寄存器TMOD中都有一控制位C/T来选择T0或T1为定时器还是计数器。定时器/计数器的核心部件是一个加法(也有减法)的计数器,其本质是对脉冲进行计数。只是计数脉冲来源不同:如果计数脉冲来自系统时钟,则为定时方式,此时定时器/计数器每12个时钟或者每6个时钟得到一个计数脉冲,计数值加1;如果计数脉冲来自单片机外部引脚(T0为P3.4,T1为P3.5),则为计数方式,每来一个脉冲加1。 

当定时器/计数器工作在定时模式时,可在烧录用户程序时在STC-ISP编程器中设置使能6T(双倍速)模式(系统频率是否加倍选择:采用6T (双倍速) 模式;不选:不加倍,采用12T (单倍速) 模式)是系统时钟➗12还是系统时钟➗6后让T0和T1进行计数。当定时器/计数器工作在计数模式时,对外部脉冲计数不分频。

定时器/计数器0有4种工作模式:模式0(13位定时器/计数器),模式1(16位定时器/计数器模式),模式2(8位自动重装模式),模式3(两个8位定时器/计数器)。定时器/计数器1除模式3外,其他工作模式与定时器/计数器0相同,T1在模式3时无效,停止计数。学习了相关寄存器之后,对于定时器计数器的掌握就得心应手了。

2、定时器/计数器0和1的相关寄存器

定时器/计数器关键的寄存器如下表格:

 接下来,我们就对定时器/计数器0和1的相关寄存器进行学习,而后进行实操。

2.1  定时器/计数器控制寄存器TCON

定时器/计数器的控制寄存器TCON代表的是(Timer Control),即定时器/计数器控制寄存器。TCON为定时器/计数器T0、T1的控制寄存器,同时也锁存T0、T1溢出中断源和外部请求中断源等,TCON格式如下: 

TCON:定时器/计数器中断控制寄存器(可位寻址)

该寄存器与定时/计数器有关的位就只是高四位,低四位是与外部中断有关的。也就是TCON 的低 4 位用于控制外部中断。TCON 的高 4 位用于控制定 时/计数器的启动和中断申请。在外部中断的那篇博客也讲过相关含义的,既然这里学到了该寄存,再讲一次也无妨。 为了更加方便理解,上图:

以下位是与定时/计数器有关,TCON定时/计数器控制寄存器高四位含义:

TF1(Timer1 Overflow Flag):定时器/计数器T1溢出标志。

T1被允许计数以后,从初值开始加1计数。当最高位产生溢出时由硬件置“1”TF1,向CPU请求中断,一直保持到CPU响应中断时,才由硬件清“0”TF1 (TF1也可由程序查询清“0”)。

TR1(Timer1 Run Control):定时器T1的运行控制位。

该位由软件置位和清零。当GATE (TMOD.7) =0,TR1=1时就允许T1开始计数,TR1=0时禁止T1计数。当GATE(TMOD.7) =1,TR1=1且INT1输入高电平时,才允许T1计数。

TF0(Timer0 Overflow Flag):定时器/计数器T0溢出中断标志。

T0被允许计数以后,从初值开始加1计数,当最高位产生溢出时,由硬件置“1”TF0,向CPU请求中断,一直保持CPU响应该中断时,才由硬件清“0”TF0( TF0也可由程序查询清“0”)。

TR0(Timer0 Run Control):定时器T0的运行控制位。

该位由软件置位和清零。当GATE (TMOD.3) =0,TR0=1时就允许T0开始计数,TR0=0时禁止T0计数。当GATE(TMOD.3) =1,TR0=0且INT0输入高电平时,才允许T0计数。

以下四位是与外部中断有关,TCON定时/计数器控制寄存器低四位含义:

IE1(External Interrupt1 Enable):外部中断1请求源(INT1/P3.3) 标志。

IE1=1,外部中断向CPU请求中断,当CPU响应该中断时由硬件清“0”IE1。

IT1(Interrupt1 Type Control):外部中断1触发方式控制位。

IT1=0时,外部中断1为低电平触发方式,当INT1 (P3.3)输入低电平时,置位IE1 。采用低电平触发方式时,外部中断源(输入到INT1) 必须保持低电平有效,直到该中断被CPI 响应,同时在该中断服务程序执行完之前,外部中断源必须被清除(P3.3要变高),否则将产生另一次中断。当IT1=1时,则外部中断1(INT1)端口由“1”一>“0”下降沿跳变,激活中断请求标志位IE1 ,向主机请求中断处理。

IE0(External Interrupt0 Enable):外部中断0请求源 (INTO/P3.2) 标志。

IE0=1外部中断0向CPU请求中断,当CPU响应外部中断时,由硬件清“0”IE0(边沿触发方式)。

IT0(Interrupt0 Type Control):外部中断0触发方式控制位。

IT0=0时,外部中断0为低电平触发方式,当INT0 (P3.2)输入低电平时,置位IE0。采用低电平触发方式时,外部中断源(输入到INTO) 必须保持低电平有效,直到该中断被CPU响应,同时在该中断服冬程序执行完之前,外部中断源必须被清除(P3.2要变高) ,否则将产生另一次中断。当IT0=1时,则外部中断0(INT0)端口由“1”一>“0”下降沿跳变,激活中断请求标志位IE0,向主机请求中断处理。 

2.2 定时器/计数器工作模式寄存器TMOD

在51单片机中,TMOD是定时器/计数器工作模式寄存器(Timer/Counter Mode Register)。它是一个8位的寄存器,用于设置定时器0(T0)和定时器1(T1)的工作模式。定时和计数功能由特殊功能寄存器TMOD的控制位C/T进行选择,TMOD寄存器的各位信息如下表所列。可以看出,2个定时/计数器有4种操作模式,通过TMOD的M1和M0选择。2个定时/计数器的模式0、1和2都相同,模式3不同,各模式下的功能如下所述。

TMOD:定时器/计数器工作模式寄存器(不可位寻址)

8位分为两组,高4位为定时/计数器1的方式控制字段,低4位为定时/计数器0的方式控制字段。 

GATE:门控位

(1)GATE=0时,定时/计数器只由软件控制位TRx(x为0或者1)来控制启动/停止。TRx位为1时,定时/计数器启动工作;为0时,定时/计数器停止工作。

(2)GATE=1,定时/计数器的启动要受到外部中断引脚(INT0/INT1脚)和TRx共同控制。只有当外部中断引脚INT0/INT1为高电平时,同时TR0/TR1置1时,才能启动定时/计数器0/1;

C/T:定时/计数器工作模式选择位

(1)C/T=0时,定时/计数器为定时器方式,定时/计数器对晶振脉冲的分频信号(机器周期)进行计数,从定时/计数器的计数值便可求得计数时间,因此称为定时器方式。

(2)C/T=1时,定时/计数器为计数器方式,定时/计数器对外部引脚 T0(P3.4)或T1(P3.5)上输入的脉冲进行计数。CPU 在每个机器周期的 S5P2期间,对 T0或T1引脚进行采样,如在前一个机器周期采得的值为 1,后一个机器周期采得的值为 0,则计数器加1。由于确认一次负跳变需要两个机器周期,因此最高计数频率为晶振频率的 1/24(12T 模式)或1/12(6T模式)。

M1、M0:定时/计数器工作方式选择位

2.3 定时器/计数器工作模式

这里我主要讲的定时/计数器工作方式0、1、2,至于方式3,因为基本用不到,所以就不过多的解释,感兴趣的可以自己去了解。关于定时/计数器工作方式0、1、2原理的解读以定时/计数器0为例子进行讲解,至于定时/计数器1的工作方式0、1、2的原理和定时/计数器0是一样的。为了方便理解定时/计数器各个模式内部工作原理图的与门、或门、非门。上图:

2.3.1 模式0(13位定时器/计数器)

当CT=0时,多路开关连接到系统时钟的分频输出,T0对时钟周期计数,T0工作在定时方式。当C/T=1时,多路开关连接到外部脉冲输入P3.4/T0,即T0工作在计数方式。
STC89C52系列单片机的定时器有两种计数速率:一种是12T模式,每12个时钟加1,与传统8051单片机相同:另外一种是6T模式,每6个时钟加1,速度是传统8051单片机的2倍。T0的速率在烧录用户程序时在STC-ISP编程器中设置。 

该模式下的13位寄存器包含TH0全部8个位及TL0的低5位。TL0的高3位不定,可将其忽略。

(1)门控位 GATE 具有特殊的作用。当 GATE=0 时,经反相后使或门输出为 1,此时仅由 TR0 控制与门的开启,与门输出 1 时,控制开关接通,计数开始。如下图:

上图是属于:模式0:M0=0;M1=0;定时器模式:C/T=0;门口位:GATE=0;单纯由定时/计数器运行控制位控制:TR0=1; 

方式0为13位计数,由TL0的低5位(高3位未用)和TH0的8位组成。机器周期每来一个脉冲,特殊功能寄存器(Timer Low0)TL0计数值加1,当TL0计数值加到TL0的最大值时,再加1时,TL0溢出,TL0归0;TL0的低5位溢出时向TH0进位,TH0计数值加1,当TH0计数值加到TH0的最大值时,TH0溢出时,置位TCON中的TF0标志,向CPU发出中断请求。

 (2)当 GATE=1时,由外中断引脚信号控制或门的输出,此时控制与门的开启由外中断引脚信号和 TR0 共同控制定时/计数器的开启。当 TR0=1 时,外中断引脚信号引脚的高电平启动计数,外中断引脚信号引脚的低电平停止计数。这种方式常用来测量外中断引脚上正脉冲的宽度。如下图:

定时/计数器工作模式选择位C/T=1时,定时/计数器为计数器方式,具体的计数方式参考定时器/计数器工作模式寄存器TMOD介绍中C/T的讲解,这里就不过多的陈述了;计数模式时,计数脉冲是 T0 引脚上的外部脉冲。计数初值与计数个数的关系为:X=2(13)-N。其中2(13)表示 2 的 13 次方。(X表示计数初值,N表示计数个数)。

注意:该方式不能像方式2一样,硬件自动重装TL0和TH0初值,是需要我们自己软件重装TH0和TH0初值。也就是说使用该方法时,不仅需要在定时/计数器初始化时,给定时/计数器特殊功能寄存器TL0和TH0赋初始值,而且还需要在TH0溢出后进入定时/计数器中断函数后再赋初TL0和TH0始值一次。

2.3.2 模式1(16位定时器/计数器)

模式1除了使用了TH0及TL0全部16位外,由 TL0 作为低 8 位,TH0 作为高 8 位,组成了16 位加 1 计数器。其他与模式0完全相同。即此模式下定时器/计数器0作为16位定时器/计数器,如下图所示。 

需要注意的一点是:当C/T=1;处于计数模式时,计数脉冲是 T0 引脚上的外部脉冲。计数初值与计数个数的关系为:X=2(16)- N。其中2(16)表示 2 的 16 次方。(X表示计数初值,N表示计数个数)。

注意:该方式也不能像方式2一样,硬件自动重装TL0和TH0初值,是需要我们自己软件重装TH0和TH0初值。也就是说使用该方法时,不仅需要在定时/计数器初始化时,给定时/计数器特殊功能寄存器TL0和TH0赋初始值,而且还需要在TH0溢出后进入定时/计数器中断函数后再赋TL0和TH0初始值一次。

2.3.3 模式2(8位自动重装模式)

该模式的原理和前面两种模式相同,唯一不同点,可以通过观察下图: 

该方式只需 TL0溢出时,就可以置位TCON中的TF0标志,向CPU发出中断请求。该方式硬件自动重装TL0的初值,就不需要TL0溢出后进入定时/计数器中断函数后再赋TL0初始值一次。至于自动重装是该这么理解呢?

TL0的溢出不仅置位TF0,而且将TH0内容重新装入TL0,TH0内容由软件预置,重装时TH0内容不变。什么意思呢?意思是:当TL0溢出时,TL0归0,不仅置位TF0,向CPU发出中断请求,而且还将TH0的值赋值给TL0(TL0=TH0),因为我们一般初始化定时/计数器的时候都会给寄存器TL0和TH0赋初值的。这一操作下来,那我们就不需要在进入中断函数的时候再给寄存器TL0赋初始值了。

还需要注意的一点是:当C/T=1;处于计数模式时,计数脉冲是 T0 引脚上的外部脉冲。计数初值与计数个数的关系为:X=2(8)- N。其中2(8)表示 2 的 8 次方。(X表示计数初值,N表示计数个数)。

2.3.4 模式3(两个8位计数器)

对定时器1,在模式3时,定时器1停止计数,效果与将TR1设置为0相同。

对定时器0,此模式下定时器0的TL0及TH0作为2个独立的8位计数器。下图为模式3时的定时器0逻辑图。TL0占用定时器0的控制位:CT、GATE、TR0、INT0及TF0。TH0限定为定时器功能(计数器周期),占用定时器1的TR1及TF1。此时,TH0控制定时器1中断。 

模式3是为了增加一个附加的8位定时器/计数器而提供的,使单片机具有三个定时器/计数器。模式3只适用于定时器/计数器0,定时器T1处于模式3时相当于TR1=0,停止计数,而T0可作为两个定时器用。

该模式其实也就是特殊功能寄存器TH0控制定时/计数器1中断(区别:一般定时/计数器1中断的控制是TH1和TL1的),TL0控制定时/计数器0中断。没什么难的,但是,一般我们用不到。

2.4  时间常数的存储——特殊功能寄存器TL0、TH0、TL1、TH1

在51单片机上的特殊功能寄存器(Special Function Register,SFR)区,有两组寄存器是专门用来存放计数器的时间常数的。它们就是TL0、TH0和TL1、TH1,各自都是8位的。其中TL0和TH0分管时间常数的低8位和高8位,控制计数器0;TL1和TH1同理,控制计数器1。

为什么要把低8位和高8位分开存储呢?因为这样可以为计数器提供更多的工作方式。这4个寄存器既用来存放时间常数,同时也可看作4个定时计数器。51单片机的计数器有4种工作方式:方式0,方式1,方式2,方式3。(工作方式的设置是通过设定TMOD寄存器的M1和M0位来控制的)。

3、定时/计数器实战篇 

这里我们也是以定时/计数器0为例子,配置定时初始化程序。说明波特率的计算,也就是TH0和TL0的初始值如何给,我们这里直接使用STC-ISP软件生成的。

第一步:设置定时器/计数器控制寄存器TCON:低四位与外部中断有关,与定时/计数器无关,不需要管。①只需要打开定时/计数器0就可以——>TCON.4/TR0=1;②清除TF0标志——>TCON.5/TF0=0;综合的:TR0=1;TF0=0;(等同于TCON=0x10;)如下图:

第二步:设置定时器/计数器工作模式寄存器TMOD:我们这里使用的是定时/计数器0、定时模式、单纯TR0控制定时器开启、工作模式1(16位定时/计数器模式);

定时器0,那么工作模式寄存器TMOD高四位都为零;定时模式、单纯TR0控制定时器开启——>C/T=0;GATE=0;工作模式1(16位定时/计数器模式)——>M1=0;M0=1;综合得:TMOD=0x01;如下图:

 第三步:设置特殊功能寄存器TL0、TH0初始值:晶振为:12.000MHz;设置为1毫秒溢出一(1毫秒执行一次中断)次——>TH0=0xFC;TL0=0x18;综合得:TH0=0xFC;TL0=0x18;无图:

第四步:配置中断允许寄存器IE:①开启CPU的总中断允许控制位——>EA/IE.7=1;②开启定时/计数器0中断允许位——>ET0/IE.1=1;综合得:EA=1;ET0=1;(等同于IE=0x82);如下图:

综合上述所有步骤,就可以完成定时/计数器初始化,我们将它封装成一个函数,如下:

/**
  * @brief  定时器0初始化,1毫秒@12.000MHz
  * @param  无
  * @retval 无
  */
void Timer0Init(void)
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
    //TMOD=0x01;        //和上面的两步是一样的
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0=1;
	EA=1;
}

定时/计数器1也是同理的。

下面我们开始用我们写好的定时/计数器口初始化驱动程序进行实操了,实操内容为:电子时钟;

现象图如下:

程序如下:

main.c

#include <REGX52.H>
#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)	//定时器分频,1s
	{
		T0Count=0;
		Sec++;			//1秒到,Sec自增
		if(Sec>=60)
		{
			Sec=0;		//60秒到,Sec清0,Min自增
			Min++;
			if(Min>=60)
			{
				Min=0;	//60分钟到,Min清0,Hour自增
				Hour++;
				if(Hour>=24)
				{
					Hour=0;	//24小时到,Hour清0
				}
			}
		}
	}
}

Timer0.c

#include <REGX52.H>

/**
  * @brief  定时器0初始化,1毫秒@12.000MHz
  * @param  无
  * @retval 无
  */
void Timer0Init(void)
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
    //TMOD=0x01;        //和上面的两步是一样的
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0=1;
	EA=1;
}

/*定时器中断函数模板
void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count=0;
		
	}
}
*/

Timer0.h

#ifndef __TIMER0_H__
#define __TIMER0_H__

void Timer0Init(void);

#endif

Delay.c


void Delay(unsigned int xms)
{
	unsigned char i, j;
	while(xms--)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
	}
}

Delay.h

#ifndef __DELAY_H__
#define __DELAY_H__

void Delay(unsigned int xms);

#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.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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/52346.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot运维

能够掌握SpringBoot程序多环境开发 能够基于Linux系统发布SpringBoot工程 能够解决线上灵活配置SpringBoot工程的需求 Windows打包运行 你的电脑不可能一直开着机联网作为服务器&#xff1a; 我们将我们项目打包放到外部的服务器上&#xff0c;这样其他用户才能正常访问&#x…

设计模式之四:工厂模式

引言&#xff1a;除了使用new操作符之外&#xff0c;还有更多制造对象的方法。同时&#xff0c;实例化这个活动不应该总是公开地进行。 1.简单工厂模式 这里有一些相关的具体类&#xff0c;要在运行时有一些具体条件来决定究竟实例化哪个类。这样的代码&#xff08;if..elseif…

MFC自定义控件使用

用VS2005新建一个MFC项目,添加一个Custom Control控件在窗体 我们需要为自定义控件添加一个类。项目,添加类,MFC类 设置类名字,基类为CWnd,你也可以选择CDialog作为基类 类创建完成后,在它的构造函数中注册一个新的自定义窗体,取名为"MyWindowClass" WNDCL…

深入了解HTTP代理在网络爬虫与SEO实践中的角色

随着互联网的不断发展&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;成为各大企业和网站重要的推广手段。然而&#xff0c;传统的SEO方法已经难以应对日益复杂和智能化的搜索引擎算法。在这样的背景下&#xff0c;HTTP代理爬虫作为一种重要的工具&#xff0c;正在逐渐被…

JUC中其他常用类

1.CopyOnWriteArrayList ArrayList是线程不安全的&#xff0c;Vector是线程安全的(方法被Synchronized修饰)&#xff0c;CopyOnWriterArrayList是在Vector的基础上再做优化&#xff0c;因为当读取操作较多时&#xff0c;Vector的效率不高。CopyOnWriterArrayList中读操作并没有…

ceph-mon运行原理分析

一、流程&#xff1a;ceph-deploy部署ceph-mon组建集群 1.ceph-deploy部署ceph-mon的工作流程及首次启动 1&#xff09;通过命令创建ceph-mon&#xff0c;命令为&#xff1a;ceph-deploy create mon keyring def mon(args):if args.subcommand create:mon_create(args)elif…

苍穹外卖day07——缓存菜品套餐+购物车功能实现

缓存菜品——需求设计与分析 问题说明 用户访问量过大带来的一个直接效果就是响应速度慢&#xff0c;使用体验下降。 实现思路 使用redis缓存菜品数据&#xff0c;减少数据库查询操作。 页面展示上基本就是同一个分类在同一页&#xff0c;所以key-value结构可以使用不同的分…

Vue没有node_modules怎么办

npm install 一下 然后再npm run serve 就可以运行了

记一次偶然的网站sql注入

自己学了点渗透的内容后就开始尝试挖漏洞了&#xff0c;偶然发现了这个yp网站&#xff0c;由于好奇心就浏览了一下里面的内容&#xff0c;突然注意到有个id的地方跳转页面&#xff0c;于是就想试试看有没有注入&#xff0c;就有了以下的内容。。。 界面如下 当时就是好奇点进去…

事件标志组

Q: 什么是事件标志组&#xff1f; A: 事件标志位&#xff1a;表明某个事件是否发生&#xff0c;联想&#xff1a;全局变量 flag。通常按位表示&#xff0c;每一个位表示一个事件&#xff08;高8位不算&#xff09; 事件标志组是一组事件标志位的集合&#xff0c; 可以简单的理…

ElasticSearch基础篇-Java API操作

ElasticSearch基础-Java API操作 演示代码 创建连接 POM依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:sch…

springCloud Eureka注册中心配置详解

1、创建一个springBoot项目 2、在springBoot项目中添加SpringCloud依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.3</version><type>…

从源程序到可执行文件的四个过程

从源程序到可执行文件的四个过程 预处理编译汇编链接 程序要运行起来&#xff0c;必须要经过四个步骤&#xff1a;预处理、编译、汇编和链接&#xff0c;如下图所示&#xff1a; -E选项&#xff1a;提示编译器执行完预处理就停下来&#xff0c;后边的编译、汇编、链接就先不执…

关于在VS2017中编译Qt项目遇到的问题

关于在VS2017中编译Qt项目遇到的问题 【QT】VS打开QT项目运行不成功 error MSB6006 “cmd.exe”已退出,代码为 2。如何在VS2017里部署的Qt Designer上编辑槽函数 【QT】VS打开QT项目运行不成功 error MSB6006 “cmd.exe”已退出,代码为 2。 链接 如何在VS2017里部署的Qt Design…

flask实现一个登录界面

flask实现一个登录界面 基础的Flask项目结构 forms.py&#xff1a;定义登录表单和表单字段的文件。templates/login.html&#xff1a;用于渲染登录表单的 HTML 模板文件。routes.py&#xff1a;定义应用的路由和视图函数的文件。__init__.py&#xff1a;创建并初始化 Flask 应…

解压缩软件WinRAR-bandizip-7z--洛

个人收集的解压软件&#xff01;后期还会更新 ------------------------------------------------------------------- WinRAR&#xff1a;密码1234WinRARhttps://wwzb.lanzoue.com/b0485ldcj BandiZip&#xff1a;密码1234 Bandizip-Professionalhttps://wwzb.lanzoue.com/…

SpringBoot内嵌的Tomcat:

SpringBoot内嵌Tomcat源码&#xff1a; 1、调用启动类SpringbootdemoApplication中的SpringApplication.run()方法。 SpringBootApplication public class SpringbootdemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootdemoApplicat…

windows下载安装FFmpeg

FFmpeg是一款强大的音视频处理软件&#xff0c;下面介绍如何在windows下下载安装FFmpeg 下载 进入官网: https://ffmpeg.org/download.html, 选择Windows, 然后选择"Windows builds from gyan.dev" 在弹出的界面中找到release builds, 然后选择一个版本&#xff0…

如何在MacBook上彻底删除mysql

好久以前安装过&#xff0c;但是现在配置mysql一直出错&#xff0c;索性全部删掉重新配置。 一、停止MySQL服务 首先&#xff0c;请确保 MySQL 服务器已经停止运行&#xff0c;以免影响后续的删除操作。 sudo /usr/local/mysql/support-files/mysql.server stop如果你输入之…

【RTT驱动框架分析03】- sfus flash 操作库的分析和基于STM32F103RCT6+CUBEMX的SFUS移植教程

sfus flash 操作库的分析 sfus 抽象 /*** serial flash device*/ typedef struct {char *name; /**< serial flash name */size_t index; /**< index of flash device information table see flash_…