51单片机开发综合实验程序结构解析

一、主程序main.c

所采用的单片机型号为:STC89C52RC(封装: LQFP-44)
按照惯例先看主函数:

/*************************
实验:综合实验程序
*************************/

#include "main.h"

void main()
{
    
    
	EA = 1;           //开总中断
	BeepInit();  //蜂鸣器初始化为不叫的状态
	ConfigTimer0(1);  //定时器0初始化定时为1ms
 	ConfigTimer2(2);  //定时器2初始化定时为2ms
	int0_Init();  //外部中断0
	int1_Init();  //外部中断1
	ConfigUART(9600);  //配置波特率为9600
	Ds1302Init();  	//DS1302函数的初始化,写入初始时间,此代码可以注释掉,如果不注释掉则单片机每次启动就会对DS1302进行时间重置
	Ds1302ReadTime();  //读取DS1302的时间

  while(1)
	{
    
    
		KeyScan();  //按键扫描
		if(KeyFlag == 1) //如果有按键按下
		{
    
    
			if(KeyState == 1) DisplayData(); //在数码管按键移位缓存中对显示缓存数组进行移位
			DigDisplay();  //进行数码管动态扫描
		}
		else  //没有按键按下
		{
    
    
			DigDisplay();  //进行数码管动态扫描显示万年历
		}
	}
}

结合以上程序,在主函数中其结构如下:
主函数结构框图
即程序一开始就对各项功能进行初始化,之后就进入while(1)死循环,在while(1)循环中对矩阵按键进行扫描,并进行数码管动态扫描显示。
那么,在主函数中调用的各个子函数又做了什么呢,这里我们就可以跟着程序运行的步骤,挨个跳转到子函数的内容中去,查看各个子函数的功能,其具体操作步骤如下:
1)、编译整个keil工程,0错误0警告
在这里插入图片描述
2)、将鼠标移到某一子函数处,点击鼠标右键,然后点击Go To Definition Of “子函数”
在这里插入图片描述

1、BeepInit()

可以看到在BeepInit()中值是将变量beep进行了清零操作

#include "beep.h"

void BeepInit()
{
    
    
	beep = 0;
}

对于变量beep,我们采用同样的方法,查看beep的内容
在这里插入图片描述
我们看到变量beep只是对单片机引脚P1.5进行了定义
也就是说蜂鸣器初始化函数只是将控制蜂鸣器的单片机引脚进行了拉低操作,我们结合原理图
在这里插入图片描述
蜂鸣器通过三极管Q8进行驱动,而P1.5引脚则可以控制三极管的通断

2、ConfigTimer0(1)

我们同样跳转到ConfigTimer0(1)中

/* 配置并启动T0,ms-T0定时时间 */
void ConfigTimer0(uint16 ms)
{
    
    
    uint32 tmp0;
    
    tmp0 = (SYS_MCLK*ms)/1000 ; //计算所需的计数值
    tmp0 = 65536 - tmp0;        //计算定时器重载值
    tmp0 = tmp0 + 33;           //补偿中断响应延时造成的误差   
    T0RH = (uint8)(tmp0>>8);   //定时器重载值拆分为高低字节
    T0RL = (uint8)tmp0;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0为模式1
    TH0 = T0RH;     //加载T0重载值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中断
    TR0 = 1;        //启动T0
}

子函数:ConfigTimer0(1)主要是对51单片机的定时器0进行初始化,其函数中的各个寄存器的配置过程大家不需要过于深究,像这类函数网上还有很多,在书本上也有对各个寄存器功能的介绍。我们通过传递参数ms,实现调用函数时直接配置定时器定时时间,也就是说在这里我们给Timer0配置了1ms的单位定时时间,每到1ms,单片机就会进入定时器0的中断服务函数,执行中断服务函数中的内容。
对于主函数中其他子函数,我们也可以采用同样的方法对其进行逐个分析,这里就不再赘述。

3、Ds1302Init();

在DS1302初始化函数中,主要是对DS1302相关寄存器写入数据:TIME[n]

/*******************************************************************************
* 函 数 名         : Ds1302Init
* 函数功能		     : 初始化DS1302.
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/

void Ds1302Init()
{
    
    
	unsigned char n;
	Ds1302Write(0x8E,0X00);		 //禁止写保护,就是关闭写保护功能
	for (n=0; n<7; n++)//写入7个字节的时钟信号:分秒时日月周年
	{
    
    
		Ds1302Write(WRITE_RTC_ADDR[n],TIME[n]);	
	}
	Ds1302Write(0x8E,0x80);		 //打开写保护功能
}

我们采用Go To Definition Of “子函数”的方式查看数组TIME[n]

//---DS1302时钟初始化2020年12月30日星期三12点30分00秒。---//
//---存储顺序是秒分时日月周年,存储格式是用BCD码---//
unsigned char TIME[7] = {
    
    0x00,0x30, 0x12, 0x30, 0x12, 0x03, 0x20};
///秒///分时日月/周///年///

这里我们代码注释写的也很清楚了,如果在main.c中调用了该函数,则每次程序复位都会将DS1302中的时间初始化为我们TIME数组中设置的时间。
如果开发板上装有纽扣电池,则我们在第一次下载程序时调用Ds1302Init(),将DS1302中的时间设置为当前的时间,再将main函数中Ds1302Init()这一函数给注释掉,这样在之后程序复位时,就不会再将DS1302中的时间进行初始化,同时因为我们有纽扣电池给DS1302供电,所有即使开发板断电,DS1302中的时间也会继续走,也就是说,下次再开启开发板,main函数中调用Ds1302ReadTime();,读取DS1302中的时间即为当前准确时间。

二、单片机内部资源的使用

通过刚才主函数的分析,大家也可以看到,主函数中的内容十分简洁,那么我们是如何通过简介的内容时间我们开发板上复杂的功能的呢,这里就要用到我们单片机的内部资源(Timer0、Timer1、Timer2、INT0、INT1)了,下面我们来介绍一下在我们这个综合实验程序中,我们利用单片机的各项内部资源都做了些什么。

1、定时器0(Timer0)

关于Timer0所做的工作,我们需要结合定时器0的中断服务函数来进行分析,大家可以参考以下流程图
在这里插入图片描述
这里我们对各项功能都分别定义了独立的定时参数,例如led_time、AD_time、mov_time等,当T0计时到1ms,进入中断服务函数,分别对各项功能的定时参数进行累加,通过if语句判定定时参数的值,当定时参数累加到我们设定值时,就执行相应的动作。

2、定时器1(Timer1)

在主函数中看似没有Timer1相关函数的使用,实际上关于T1的配置,我们是在ConfigUART(9600)中完成的,我们将T1配置为了工作方式二,作为波特率发生器,以此来实现单片机的串口通讯功能,在ConfigUART(9600)函数中,我们将串口通讯波特率设置为了9600,这里大家也可以修改为其他波特率进行使用,常见的波特率有4800、9600、115200等,波特率越高通讯速度越快,但其通讯出错的概率也会越大,这主要是由于我们通过定时器1来计算波特率时实际采用的是我们单片机的晶振来作为计时的基本时钟,而在计算高波特率时,我们所采用的晶振频率(11.0592MHz)可能无法精确计算到这一频率,会存在些许误差,在误差允许范围内,依然是可以完成串口通讯的,而当误差较大时,就会造成通讯波特率不一致的情况,这时通讯就会出错。
这里既然说到了定时器1作为波特率发生器实现串口通讯,那么我们就顺便也把我们开发板上的串口通讯内容进行分析,当单片机串口接收到数据时,程序进入串口中断函数中,在usart.c文件中我们可以找到这一函数:

/****************************
*串口中断服务函数
****************************/
void InterruptUART() interrupt 4    //串口中断是中断4
{
    
    
	EA=0;
	if(RI)  //接收数据完成(RI)
	{
    
    
		INT_NUM++;
		dataRead[INT_NUM] = SBUF;
		UART_Explain();
		RI = 0; 
	}
	EA=1;
	TR2 = 1;       //启动T2
}


在串口中断服务函数中,主要调用的函数就是UART_Explain()了,该函数是对串口收到的数据进行解析,当数据是我们的目标数据时,就执行相应的动作。

3、定时器2(Timer2)

我们所采用的单片机STC89C52RC是有定时器2的,这里我们将定时器2用作步进电机的步进状态切换定时,除了主函数中调用的初始化函数ConfigTimer2(2)(配置T2为16为重转载模式)之外,我们还可以在timer2.c文件中找到T2中断服务函数,其函数中的内容即为与步进电机相关内容的配置。
这里也顺便说明一下关于步进电机接线的问题,我们所采用的4相5线5V步进电机排线中有一根红色的线,这根红色的线接我们的VCC,即将红色的线对应开发板上VCC所在引脚,然后将排线对应插入排针即可
在这里插入图片描述
为了使电机转动现象明显,可以在电机轴上粘贴一片小纸条或者绕上一段焊锡丝
在这里插入图片描述
关于串口通讯可以参考和利用串口进行程序下载可以参考https://blog.csdn.net/Stark_/article/details/111466231
串口通讯中,我们可以发送:正转、反转、加速、减速、停止等指令控制电机转动。

4、外部中断0(INT0)和外部中断1(INT1)

在我们的exti.c文件中,我们对外部中断服务函数的内容进行了配置,

void led_water() interrupt 0 //每一个外部中断信号都会使得LED移位方向改变
{
    
    
	led_flag=~led_flag;
}

void beep_on() interrupt 2
{
    
    
		beep = 1;
}

其中led_water()函数是由我们开发板上的外部中断独立按键触发,在我们开发板上,除了矩阵按键之外,还有两个独立按键,一个是单片机复位按键,当程序出错时通过复位使系统重启,一个是外部中断触发按键,按下即可触发流水灯流动方向的改变。
而beep_on()函数是由我们的红外对管进行触发,通过遮挡红外对管,触发蜂鸣器鸣叫。

猜你喜欢

转载自blog.csdn.net/Stark_/article/details/111794689
今日推荐