基于HART总线的噪声报警系统 - MCU/Protues/Keil

140 阅读25分钟

最近,做了一个课程设计:基于HART总线的噪声报警系统。在Protues平台进行电路设计和仿真,用Keil编写C程序代码进行模拟HART总线协议,从而完成一个关于MCU的系统小应用。

这篇文章,主要是记录在做课程设计的过程中所学到的一些东西,进行总结。

首先,介绍两个开发平台(Protues 和 Keil),以及自己使用这两个开发平台的习惯,或者说是经验技巧吧;然后在阐述这个MCU系统小应用的内容。

开发平台

Protues

Proteus是一款由英国Labcenter公司开发的电路分析与实物仿真软件。它运行于Windows操作系统上,可以仿真、分析各种模拟器件和集成电路,是目前最好的仿真单片机及外围器件的工具。此外,Proteus是世界上唯一将电路仿真软件、PCB设计软件和虚拟模型仿真软件三合一的设计平台。

image.png

Proteus的主要功能从原理图布图、代码调试到单片机与外围电路协同仿真,一键切换到PCB设计,真正实现了从概念到产品的完整设计。例如,在教学实验中,我们可以利用Proteus来模拟单片亮一个发光二极管的操作。

尽管Proteus在电力电子的模拟角度来说,电路计算并不是十分精确,只能进行粗略的计算,元器件库的更新有点落后了,很多器件都没有更新。但是用来仿真和验算单片机的程序还是非常好用的。总的来说,Proteus是一款功能强大、应用广泛的电路设计和仿真工具。

怎么安装和破解?网上资源都挺多的,我帮你们Search一下吧,不必多谢!

在这里,我列举一些使用Protues的常见操作。

Keil

Keil公司,由德国慕尼黑的Keil Elektronik GmbH和美国德克萨斯的Keil Software Inc两家公司联合运营,是全球领先的微控制器(MCU)软件开发工具供应商。其开发的集成开发环境(IDE),Keil uVision5,是一款用于对嵌入式系统中的微控制器进行编程的软件套件,包括源代码编辑器、编译器、宏汇编、连接器、库管理、项目经理、调试器以及微控制器开发、调试和编程所需的其他工具。

image.png

特别地,针对基于Cortex-M、Cortex-R4、ARM7、ARM9处理器设备的微控制器应用,Keil MDK提供了一个完整的开发环境。这个环境不仅易学易用,而且功能强大,能够满足大多数苛刻的嵌入式应用。同时,MDK-ARM有四个可用版本,分别是 MDK-Lite、MDK-Basic、MDK-Standard、MDK-Professional ,所有版本均提供一个完善的C / C++开发环境,其中MDK-Professional还包含大量的中间库。然而需要注意的是,不同的单片机可能需要不同版本的Keil来编译。

在此设计中,我使用的是Keil5,有C51和ARM两个芯片包。

image.png

安装教程我推荐以下两个链接:

Keil常见操作:

  1. 创建新项目,选择项目存放路径
  2. 选择芯片

image.png 3. 点击 Options for Target,修改项目属性

image.png image.png 4. 添加添加源代码文件

image.png image.png image.png 5. 编译运行

image.png image.png

Keil项目管理 - 项目文件夹分类命名规范:

Keil模块化编程

模块化编程是一种编程方法,它将程序分成一个个模块,每个模块完成特定的功能。模块化编程体现的是一种"解耦"的思想,使代码结构更清晰,易于理解和维护,同时也提高了代码的复用性和可移植性。

在单片机开发中,体现在将不同硬件模块的底层驱动代码进行封装模块化。一个模块分别对应两个文件,一个文件是.c源文件:包含了程序的实现逻辑。在编译时,编译器会将.c文件中的代码转换成机器码,然后链接生成可执行文件。另一个文件是.h头文件:用于声明函数、变量和常量等。当一个程序需要使用另一个程序中的函数或变量时,可以通过包含相应的头文件来实现。头文件通常包含函数原型声明、宏定义和结构体定义等。

在使用模块化编程时,通常会将每个模块的代码分别放在对应的.c文件中,然后在主程序中通过包含相应的.h头文件来调用这些模块的函数。这样可以提高代码的可读性和可维护性。

Keil面向对象编程

以结构体和指针来实现C程序代码的面向对象编程

#ifndef __LED_H__
#define __LED_H__

//RUN LED
#define MCU_RUN_LED P34
#define MCU_RUN_LED_ON (bit)1
#define MCU_RUN_LED_OFF (bit)0

//定义结构体类型
typedef struct{
    void (*LED_ON)(void); //打开
    void (*LED_OFF)(void); //关闭
    void (*LED_Flip)(void); //翻转
} LED_t

extern LED_t LED_RUN;

#endif

static void RUN_LED_ON(void);
static void RUN_LED_OFF(void);
static void RUN_LED_Flip(void);

LED_t LED_RUN = {
    RUN_LED_ON;
    RUN_LED_OFF;
    RUN_LED_Flip;
}

static void RUN_LED_ON(void){
    MCU_RUN_LED = MCU_RUN_LED_ON;
}

static void RUN_LED_OFF(void){
    MCU_RUN_LED = MCU_RUN_LED_OFF;
}

static void RUN_LED_Flip(void){
    MCU_RUN_LED = ~MCU_RUN_LED;
}

这段代码定义了一个名为LED_t的结构体类型,用于表示一个LED设备。结构体中包含了三个成员函数指针,分别用于打开LED、关闭LED和翻转LED的状态。

在代码的全局部分,定义了一个名为LED_RUN的extern变量,它是一个LED_t类型的实例。这个实例的成员函数指针被初始化为指向三个静态函数:RUN_LED_ON、RUN_LED_OFF和RUN_LED_Flip。这三个函数分别实现了打开LED、关闭LED和翻转LED状态的功能。

通过使用结构体和指针,这段代码实现了面向对象编程的思想。结构体是一种封装数据的方式,将LED的状态(开/关)和操作(打开、关闭、翻转)封装在一起。指针则用于访问和操作这些封装好的数据。这样,我们可以像使用对象一样使用LED设备,通过调用相应的成员函数来实现对LED的操作。

噪声报警系统

噪声报警系统由单片机作为主控制器,分为主机和从机。主机的外围电路包括显示模块和报警模块;从机的外围电路主要包括检测模块。在Proteus进行完成电路设计,编写C程序代码模拟HART总线协议实现主机与从机的数据通信。传感器模块负责检测分贝大小,并通过从机把数据传输给主机,完成噪声的显示和报警。

image.png

其实,这个课程设计的主要研究内容是——用UART来模拟HART协议的点对点操作模式和半双工通信方式,即双方都可以发送数据,但在同一时刻只能有一方发送,另一方接收。这种通信方式不是全双工也不是单工,因为全双工允许双方同时发送和接收数据,而单工则只能在一个方向上进行通信。在此基础上,设计并实现一个噪声报警系统。这个系统通过监测环境中的噪声水平,当噪声超过设定阈值时,触发报警装置(如LED灯或蜂鸣器)发出警报。

半双工通信可以形象地理解为在吵闹的环境中打电话。在这种场景下,你和对方都可以说话,但是你们不能同时说话。当一个人在说话时,另一个人需要等待,直到对方说完才能回应。这就像在一个嘈杂的环境中,你需要大声说话以便对方能听到你的声音,但同时也要留意对方的回应,以便及时回应。

在半双工通信中,发送方和接收方可以同时发送和接收数据,但不能同时进行。这意味着在任何时刻,只有一个设备在发送数据,而另一个设备在接收数据。在本设计中,主机和从机的通信方式就是半双工。即"一应一答",只有主机给予命令,从机才能应答。

系统组成和工作流程

系统由四个模块组成

  1. 检测:负责检测噪声分贝的大小

image.png

  1. 通信:负责接收检测模块传输过来的分贝数据,并将数据传输给显示模块和报警模块

image.png

  1. 显示:用于实时显示噪声分贝的大小

image.png

  1. 报警:用于在噪声分贝大小到达或超过设定值时触发报警

image.png

工作流程:一个操作 & 两个状态

image.png

  • 一个操作指的是 发送主机命令给从机;
  • 两个状态指的是 报警模块响应或未响应的状态;

从机从传感器模块读取噪声数据后,等待主机命令。若主机不发送命令,那么从机不会把最新的噪声数据发送给主机。也就是说,只有主机发送命令给从机,从机才能够把最新的噪声数据发送给主机。主机接收到最新的噪声数据后,将其传输至报警模块和显示模块。显示模块会实时显示主机接收到的噪声数据。报警模块会根据主机接收到的噪声数据与设定值进行对比。若最新噪声数据大于或等于设定值,则报警模块响应。反之,不响应。

硬件设计

最小电路系统

image.png

单片机最小系统,也被称为单片机最小应用系统,是指用最少的原件组成单片机可以工作的系统。它主要由电源部分、晶振部分、复位电路等构成。

在电源部分,单片机需要接入适当的电压以保证其正常工作,通常使用的是5V和3.3V这两个标准电压。例如,对于51单片机,我们可以从USB接口获取5V电源来供电。此外,需要注意的是,单片机无法承受过高的电压,如果接上过高的电压将会损坏单片机或者引发爆炸。

晶振部分,也被称为晶体振荡器,其主要作用是为单片机系统提供基准时钟信号。这个时钟信号是单片机内部所有工作步调的基础,可以说晶振就是单片机的“心脏”。

复位电路则是保证单片机能够正常运行的重要环节。当电路接收到复位信号后,单片机会执行复位操作,清空所有暂存器和寄存器的值,让单片机处于一个初始状态,等待程序的再次启动。

这三个部分是单片机最小系统的三要素,只有它们正确地连接在一起,单片机才能正常运行。在实际电路连接过程中,最小系统电路会包括电源电路、复位电路、时钟电路等多个部分。其中,电源电路用于给单片机提供工作电压;复位电路则负责在需要的时候对单片机进行复位操作;时钟电路则为单片机提供一个稳定的时钟信号。

总的来说,单片机最小系统虽然只包含电源、晶振、复位电路这三个最基本的部分,但它们是单片机能够运行的必要条件。

检测模块

image.png

检测模块由滑动变阻器和ADC0832组成,用来模拟噪声传感器。

ADC0832是一个8位的串行I/O模数转换器,具有多路转换开关。它具有较快的转换速度(转换时间32uS),并且采用单电源供电,功耗较低(仅15mW)。在Protues软件中,ADC0832主要的作用是进行A/D转换。工作时,其转换过程主要分为两个阶段:起始和通道配置阶段,由CPU发送,从ADC0832的DI端输入;以及A-D转换数据输出阶段,由ADC0832从DO端输出,供CPU接收。

ADC0832具有8个引脚,各个接口的功能如下:

  • VCC和GND:分别作为电源正极和负极
  • CLK:这是一个时钟输入引脚,用于控制转换速率
  • CS:这是一个片选输入引脚,主要用于选择芯片
  • DO和DI:这两个是数据输入/输出引脚,用于传输数据
  • CH0和CH1:这两个是模拟输入通道

一般情况下,ADC0832与单片机的接口应为4条数据线,分别是CS、CLK、DO、DI。但由于DO端与DI端在通信时并未同时有效并与单片机的接口是双向的,所以,电路设计时可以将DO和DI并联在一根数据线上使用。而ADC0832的DI、DO控制端,则是利用单片机的两个IO口来控制,这样就可以获取得到采样值。

在此设计中,ADC0832使用了六个引脚,DO和DI并联在一个数据线上连接到MCU_A从机的P1.1引脚,CLK连接到MCU_A从机的P1.0引脚,CS连接到MCU_A从机的P1.3引脚,CH0连接到一个滑动变阻器,VCC连接到一个电源。

其实,在这个系统设计中,ADC0832扮演了一个类似于传感器的角色,将滑动变阻器的阻值(模拟量)转换为数字量,用阻值来模拟"噪声的大小"。

通信模块

image.png

通信模块由两个单片机和一个机械按钮构成,以UART通信协议来模拟HART总线的特点,使得单片机和其他设备之间实现高效的数据交换。

UART是一种异步全双工通信方式,它主要使用RXD(接收引脚)和TXD(发送引脚)两个引脚。RXD用于接收数据,从其他设备发送过来的数据会通过这个引脚输入到单片机内部。TXD用于发送数据,单片机要向其他设备发送的数据会通过这个引脚输出。

当两个单片机需要进行UART通信时,可以将它们的RXD与TXD交叉连接。具体来说,就是将一个单片机的RXD接到另一个单片机的TXD,以及将第一个单片机的TXD接到第二个单片机的RXD。这种交叉连接的方式可以实现两个单片机之间的双向数据传输。在此设计中,MCU_A从机的RXD(P3.0)引脚连接到MCU_B主机TXD(P3.1)引脚,MCU_A从机的TXD(P3.1)引脚连接到MCU_B主机TXD(P3.0)引脚。两个单片机的接收引脚和发送引脚交叉连接可实现相互通信。

MCU_B主机的P3.3引脚连接一个机械按钮,它的作用是用来发送主机给从机传输音量数据的命令。按下按钮,则代表主机向从机发送命令,从机接收到命令后,即可发送最新的音量数据。否则,即使音量数据发生变化,若未按下按钮,所显示的音量数据也不会发生改变。

显示模块

image.png

显示模块由LCD1602构成,用来实时显示噪声分贝的大小。

LCD1602是一种字符型液晶显示模块,它的主要作用是显示数字、字母、图形以及少量自定义字符,可以显示2行16个字符。LCD1602拥有16个引脚,这些引脚包括8位数据总线D0-D7,以及RS、R/W和EN三个控制端口。

这些接口的功能如下:

  • RS:寄存器选择信号,用于选择指令寄存器或数据寄存器
  • R/W:读/写选择信号,用于选择读取或写入操作
  • EN:使能信号,用于启用或禁用数据总线
  • D0-D7:8位数据总线,用于传输数据

LCD1602一般通过单片机与外设进行通信和控制。在这个系统设计中,LCD1602的RS、R/W和EN三个控制端口分别连接到MCU_B主机的P2.6引脚、P2.5引脚和P2.7引脚,D0-D7的数据总线连接到MCU_B主机的P1口,从而实现对LCD1602的驱动和数据显示。

报警模块

image.png

报警模块由PNP三极管、蜂鸣器和LED组成。当噪声分贝的大小到达或超过设定值时,报警模块响应,LED点亮并蜂鸣器响起。

蜂鸣器的使用通常需要搭配外部电源和控制设备。蜂鸣器的驱动电流较大,IO口无法直接驱动,所以需要经过三极管放大电流驱动。此外,还需要为三极管设置限流电阻和下拉电阻,以保证在基极浮空或处于高阻态时,三极管能有效地关断,防止误触发。图中标注的Beep端口连接到MCU_B主机的P2.0引脚。当P2.0引脚为低电平时,报警模块响应。反之,则不响应。

软件设计

主机程序

image.png

主机程序由三个线程组成,分别是主函数、UART中断服务函数和定时器中断服务函数。

主函数进行一系列初始化操作后,获取主机命令并发送给从机,从机接收命令进行响应发送噪声数据给主机。主机获取噪声数据进行显示,并对噪声数据的情况进行检查。若超过设定噪声阈值,则报警模块响应。

UART中断服务函数是用来接收从机发送的噪声数据。当接收中断标志位为真时,进入此函数接收噪声数据。

定时器中断服务函数是用来控制报警模块的响应。当报警标志位为真时,报警模块进行响应。

从机程序

image.png

主机程序由两个线程组成,分别是主函数和UART中断服务函数。

主函数进行UART初始化后,从ADC0832处获取噪声数据并进行处理,将其以数组的形式存储到缓冲区。随后,等待主机命令准备发送数据。

UART中断服务函数是用来接收主机发送的命令。当接收中断标志位为真时,进入此函数接收主机命令。

运行结果与分析

设置噪声分贝阈值为60db,当噪声分贝值小于60db时,在LCD上仅显示分贝值,而报警模块无反应;当噪声分贝值大于或等于60db,不仅在LCD上会显示异常分贝值,而且报警模块响应,LED灯亮且蜂鸣器响起刺耳的声音。

image.png

image.png

在本设计上,使用一个机械按钮模拟HART协议的主从命令和半双工通信。只有当主机给从机发生允许传输音量数据的命令时,从机才能够把最新的音量数据发送给主机。当音量数据发生变化时,若未按下按钮,也就是主机未给从机发送命令,那么从机也不会把最新的音量数据传输给主机。显示模块上的音量数据没有发生改变,此结果就证实了这一点。

image.png

源码

主机程序

#include "reg51.h"
#include "LCD1602.h"
#include "Key.h"
#include "UART.h"
#include <intrins.H>

sbit beepSwitch = P2^0;
int warningFlag = 0;
int i = 0;
int Time50ms = 0;
unsigned char hostCMD = 0;
unsigned char receDataBuffer[4];

//发送主机命令
void SendHostCMD(unsigned char* hostCMD){
	UART_SendByte(*hostCMD);
}

void TIM0init(void){//定时器初始化 使定时器中断服务启动
	TMOD=0x21;
	TH0=0x4c;
	TL0=0x00;
	ET0=1;
	TR0=1;
	EA=1;
}

void DisPlayForm(void){
	LCD_ShowString(1,1,"Noise Monitor");
	LCD_ShowString(2,1,"                ");

	LCD_ShowChar(2,5,'d');
	LCD_ShowChar(2,6,'b');
}

void DisPlayData(){
	LCD_ShowChar(2,2,receDataBuffer[0]+'0');
	LCD_ShowChar(2,3,receDataBuffer[1]+'0');
	LCD_ShowChar(2,4,receDataBuffer[2]+'0');
}

void CheckWarningState(unsigned char* receDataBuffer, int* warningFlag){
	if(receDataBuffer[3] == 1){
		*warningFlag = 1;
	}else{
		*warningFlag = 0;
	}
}


void main(){
	InitUART();
	LCD_Init();
	TIM0init();
	DisPlayForm();

	while(1){
		hostCMD = Key();
		SendHostCMD(&hostCMD);
		DisPlayData();
		CheckWarningState(receDataBuffer, &warningFlag);
	}
}

//中断函数接收数据 - 音量值
void UART_Interrupt(void) interrupt 4{
   if(RI){
		RI = 0;
		receDataBuffer[i] = SBUF;
		i++;
		if(i>=4) i=0;
	}else{
		TI = 0;
	}
}

//定时器中断函数 - 报警模块响应
void tim0_isr(void) interrupt 1 using 1{
	TH0=0xDC;
	TL0=0x00;
	if(warningFlag==1){
		beepSwitch=0;
		Time50ms++;
		if(Time50ms>=6000){
			//Time50ms=0;
			beepSwitch=1;
		}
	}else{
		beepSwitch=1;
		Time50ms=0;
	}
}

从机程序

#include "reg51.h"
#include "ADC0832.h"
#include "UART.h"
#include <intrins.H>

#define warningThreshold 60

long volume;
int warningFlag = 0;
unsigned char sendDataBuffer[4];
unsigned char hostCMD;

void LoadingSendDataBuffer(long* volume, int* warningFlag, unsigned char* sendDataBuffer){
	//调用adc0832Read(1,0)函数来读取音量值,并将结果存储在变量volume中
	*volume = adc0832Read(1,0);

	//根据音量值是否大于等于警告阈值来更新警告标志位
	if(*volume >= warningThreshold){
		*warningFlag = 1;
	}else{
		*warningFlag = 0;
	}

	sendDataBuffer[0] = *volume/100; // 音量的百位
	sendDataBuffer[1] = *volume/10%10; // 十位
	sendDataBuffer[2] = *volume%10; // 个位
	sendDataBuffer[3] = *warningFlag; // 报警标志位
}

//发送音量数据
void WaitSendData(unsigned char* sendDataBuffer, unsigned char* hostCMD){
	if(1 == *hostCMD){
		UART_SendByte(sendDataBuffer[0]);
		UART_SendByte(sendDataBuffer[1]);
		UART_SendByte(sendDataBuffer[2]);
		UART_SendByte(sendDataBuffer[3]);
	}
}

void main(){
	InitUART();
	
	while(1){
		LoadingSendDataBuffer(&volume, &warningFlag, sendDataBuffer);
		WaitSendData(sendDataBuffer, &hostCMD);
	}
}

//中断函数接收数据 - 主机命令
void UART_Interrupt(void) interrupt 4{
   if(RI){
		RI = 0;
		hostCMD = SBUF;
	}else{
		TI = 0;
	}
}

LCD驱动代码

#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

#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P1

//函数定义:
/**
  * @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');
	}
}

ADC0832驱动代码

#ifndef __ADC0832_H__
#define __ADC0832_H__

unsigned char adc0832Read(bit SGL, bit ODD);

#endif

#include <REGX52.H>

sbit CS = P1^3;
sbit SCL = P1^0;
sbit DO = P1^1;

unsigned char adc0832Read(bit SGL, bit ODD){
	unsigned char index = 0, value = 0, anotherValue = 0;		
    SCL = 0;
    DO = 1;
    CS = 0;
    SCL = 1;
    SCL = 0;
    DO = SGL;
    SCL = 1;
    SCL = 0;
    DO = ODD;
    SCL = 1;
    SCL = 0;
    DO = 1;
    for(index = 0; index < 8; index++){
        SCL = 1;
        SCL = 0;
        value <<= 1;
        if(DO)
            value++;						
    }
    for(index = 0; index < 8; index++){
        anotherValue >>= 1;
        if(DO)
            anotherValue += 0x80;
        SCL = 1;
        SCL = 0;
    }
    CS = 1;
    SCL = 1;	
    if(value == anotherValue)
        return value;
	return 0;
}

按键驱动代码

#ifndef __KEY_H__
#define __KEY_H__

unsigned char Key();

#endif

#include <REGX52.H>
#include "Delay.h"

/**
  * @brief  获取独立按键键码
  * @param  无
  * @retval 按下按键的键码,获取对应返回值
  */
unsigned char Key()
{
	unsigned char KeyNumber=0;
	
	if(P3_3==0){Delay(20);while(P3_3==0);Delay(20);KeyNumber=1;}
	
	return KeyNumber;
}

#ifndef __DELAY_H__
#define __DELAY_H__

void Delay(unsigned int xms);

#endif

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

UART通信协议

#ifndef __UART_H__
#define __UART_H__

void InitUART(void);
void UART_SendByte(unsigned char c);

#endif

#include <REGX52.H>

//初始化UART 使单片机具有接收和发送数据的能力
void InitUART(void){
    // 配置波特率为9600
    TMOD = 0x20; // 设置定时器1为8位自动重装载模式
    TH1 = 0xFD; // 设置波特率为9600
    TL1 = 0xFD;
    TR1 = 1; // 启动定时器1

    // 配置串口工作方式为1(8位数据,1位停止位,无校验)
    SCON = 0x50;

    // 使能串口中断
    ES = 1;
    EA = 1;
}

//发送一个字节
void UART_SendByte(unsigned char c){
    SBUF = c;
    while(!TI);
    TI = 0;
}

鲜有人能看到这里

也是辛苦啦

停下来 放松放松 慢慢听首歌吧