最近,做了一个课程设计:基于HART总线的噪声报警系统。在Protues平台进行电路设计和仿真,用Keil编写C程序代码进行模拟HART总线协议,从而完成一个关于MCU的系统小应用。
这篇文章,主要是记录在做课程设计的过程中所学到的一些东西,进行总结。
首先,介绍两个开发平台(Protues 和 Keil),以及自己使用这两个开发平台的习惯,或者说是经验技巧吧;然后在阐述这个MCU系统小应用的内容。
开发平台
Protues
Proteus是一款由英国Labcenter公司开发的电路分析与实物仿真软件。它运行于Windows操作系统上,可以仿真、分析各种模拟器件和集成电路,是目前最好的仿真单片机及外围器件的工具。此外,Proteus是世界上唯一将电路仿真软件、PCB设计软件和虚拟模型仿真软件三合一的设计平台。
Proteus的主要功能从原理图布图、代码调试到单片机与外围电路协同仿真,一键切换到PCB设计,真正实现了从概念到产品的完整设计。例如,在教学实验中,我们可以利用Proteus来模拟单片亮一个发光二极管的操作。
尽管Proteus在电力电子的模拟角度来说,电路计算并不是十分精确,只能进行粗略的计算,元器件库的更新有点落后了,很多器件都没有更新。但是用来仿真和验算单片机的程序还是非常好用的。总的来说,Proteus是一款功能强大、应用广泛的电路设计和仿真工具。
怎么安装和破解?网上资源都挺多的,我帮你们Search一下吧,不必多谢!
在这里,我列举一些使用Protues的常见操作。
- 创建一个新项目
- 在元件库选取想要的元器件
- 元器件的布局与走线
- proteus中的标签及总线的使用方法
- 如何在proteus中画总线
- Proteus软件怎么快速连线以及实现网络标号
- proteus仿真电路连线
- 我最喜欢的就是
快捷键A
,网络标号连线直接起飞
- 在单片机中放入Keil编译的
.hex
文件 - 右键单片机 左键属性 - 在protues中编写单片机C程序代码 - 右键单片机 左键源代码
Keil
Keil公司,由德国慕尼黑的Keil Elektronik GmbH和美国德克萨斯的Keil Software Inc两家公司联合运营,是全球领先的微控制器(MCU)软件开发工具供应商。其开发的集成开发环境(IDE),Keil uVision5,是一款用于对嵌入式系统中的微控制器进行编程的软件套件,包括源代码编辑器、编译器、宏汇编、连接器、库管理、项目经理、调试器以及微控制器开发、调试和编程所需的其他工具。
特别地,针对基于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两个芯片包。
安装教程我推荐以下两个链接:
Keil常见操作:
- 创建新项目,选择项目存放路径
- 选择芯片
3. 点击
Options for Target
,修改项目属性
4. 添加添加源代码文件
5. 编译运行
Keil项目管理 - 项目文件夹分类命名规范:
项目名称
Project
- 存放Keil项目工程文件Source
- 存放源代码.c
和.h
App
- 存放main
等线程文件Objects
- 存放编译后输出的.hex
和.obj
Listings
- 存放编译过程产生的中间文件
- 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总线协议实现主机与从机的数据通信。传感器模块负责检测分贝大小,并通过从机把数据传输给主机,完成噪声的显示和报警。
其实,这个课程设计的主要研究内容是——用UART来模拟HART协议的点对点操作模式和半双工通信方式,即双方都可以发送数据,但在同一时刻只能有一方发送,另一方接收。这种通信方式不是全双工也不是单工,因为全双工允许双方同时发送和接收数据,而单工则只能在一个方向上进行通信。在此基础上,设计并实现一个噪声报警系统。这个系统通过监测环境中的噪声水平,当噪声超过设定阈值时,触发报警装置(如LED灯或蜂鸣器)发出警报。
半双工通信可以形象地理解为在吵闹的环境中打电话。在这种场景下,你和对方都可以说话,但是你们不能同时说话。当一个人在说话时,另一个人需要等待,直到对方说完才能回应。这就像在一个嘈杂的环境中,你需要大声说话以便对方能听到你的声音,但同时也要留意对方的回应,以便及时回应。
在半双工通信中,发送方和接收方可以同时发送和接收数据,但不能同时进行。这意味着在任何时刻,只有一个设备在发送数据,而另一个设备在接收数据。在本设计中,主机和从机的通信方式就是半双工。即"一应一答",只有主机给予命令,从机才能应答。
系统组成和工作流程
系统由四个模块组成
- 检测:负责检测噪声分贝的大小
- 通信:负责接收检测模块传输过来的分贝数据,并将数据传输给显示模块和报警模块
- 显示:用于实时显示噪声分贝的大小
- 报警:用于在噪声分贝大小到达或超过设定值时触发报警
工作流程:一个操作 & 两个状态
- 一个操作指的是 发送主机命令给从机;
- 两个状态指的是 报警模块响应或未响应的状态;
从机从传感器模块读取噪声数据后,等待主机命令。若主机不发送命令,那么从机不会把最新的噪声数据发送给主机。也就是说,只有主机发送命令给从机,从机才能够把最新的噪声数据发送给主机。主机接收到最新的噪声数据后,将其传输至报警模块和显示模块。显示模块会实时显示主机接收到的噪声数据。报警模块会根据主机接收到的噪声数据与设定值进行对比。若最新噪声数据大于或等于设定值,则报警模块响应。反之,不响应。
硬件设计
最小电路系统
单片机最小系统,也被称为单片机最小应用系统,是指用最少的原件组成单片机可以工作的系统。它主要由电源部分、晶振部分、复位电路等构成。
在电源部分,单片机需要接入适当的电压以保证其正常工作,通常使用的是5V和3.3V这两个标准电压。例如,对于51单片机,我们可以从USB接口获取5V电源来供电。此外,需要注意的是,单片机无法承受过高的电压,如果接上过高的电压将会损坏单片机或者引发爆炸。
晶振部分,也被称为晶体振荡器,其主要作用是为单片机系统提供基准时钟信号。这个时钟信号是单片机内部所有工作步调的基础,可以说晶振就是单片机的“心脏”。
复位电路则是保证单片机能够正常运行的重要环节。当电路接收到复位信号后,单片机会执行复位操作,清空所有暂存器和寄存器的值,让单片机处于一个初始状态,等待程序的再次启动。
这三个部分是单片机最小系统的三要素,只有它们正确地连接在一起,单片机才能正常运行。在实际电路连接过程中,最小系统电路会包括电源电路、复位电路、时钟电路等多个部分。其中,电源电路用于给单片机提供工作电压;复位电路则负责在需要的时候对单片机进行复位操作;时钟电路则为单片机提供一个稳定的时钟信号。
总的来说,单片机最小系统虽然只包含电源、晶振、复位电路这三个最基本的部分,但它们是单片机能够运行的必要条件。
检测模块
检测模块由滑动变阻器和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扮演了一个类似于传感器的角色,将滑动变阻器的阻值(模拟量)转换为数字量,用阻值来模拟"噪声的大小"。
通信模块
通信模块由两个单片机和一个机械按钮构成,以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引脚连接一个机械按钮,它的作用是用来发送主机给从机传输音量数据的命令。按下按钮,则代表主机向从机发送命令,从机接收到命令后,即可发送最新的音量数据。否则,即使音量数据发生变化,若未按下按钮,所显示的音量数据也不会发生改变。
显示模块
显示模块由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的驱动和数据显示。
报警模块
报警模块由PNP三极管、蜂鸣器和LED组成。当噪声分贝的大小到达或超过设定值时,报警模块响应,LED点亮并蜂鸣器响起。
蜂鸣器的使用通常需要搭配外部电源和控制设备。蜂鸣器的驱动电流较大,IO口无法直接驱动,所以需要经过三极管放大电流驱动。此外,还需要为三极管设置限流电阻和下拉电阻,以保证在基极浮空或处于高阻态时,三极管能有效地关断,防止误触发。图中标注的Beep端口连接到MCU_B主机的P2.0引脚。当P2.0引脚为低电平时,报警模块响应。反之,则不响应。
软件设计
主机程序
主机程序由三个线程组成,分别是主函数、UART中断服务函数和定时器中断服务函数。
主函数进行一系列初始化操作后,获取主机命令并发送给从机,从机接收命令进行响应发送噪声数据给主机。主机获取噪声数据进行显示,并对噪声数据的情况进行检查。若超过设定噪声阈值,则报警模块响应。
UART中断服务函数是用来接收从机发送的噪声数据。当接收中断标志位为真时,进入此函数接收噪声数据。
定时器中断服务函数是用来控制报警模块的响应。当报警标志位为真时,报警模块进行响应。
从机程序
主机程序由两个线程组成,分别是主函数和UART中断服务函数。
主函数进行UART初始化后,从ADC0832处获取噪声数据并进行处理,将其以数组的形式存储到缓冲区。随后,等待主机命令准备发送数据。
UART中断服务函数是用来接收主机发送的命令。当接收中断标志位为真时,进入此函数接收主机命令。
运行结果与分析
设置噪声分贝阈值为60db,当噪声分贝值小于60db时,在LCD上仅显示分贝值,而报警模块无反应;当噪声分贝值大于或等于60db,不仅在LCD上会显示异常分贝值,而且报警模块响应,LED灯亮且蜂鸣器响起刺耳的声音。
在本设计上,使用一个机械按钮模拟HART协议的主从命令和半双工通信。只有当主机给从机发生允许传输音量数据的命令时,从机才能够把最新的音量数据发送给主机。当音量数据发生变化时,若未按下按钮,也就是主机未给从机发送命令,那么从机也不会把最新的音量数据传输给主机。显示模块上的音量数据没有发生改变,此结果就证实了这一点。
源码
主机程序
#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;
}
鲜有人能看到这里
也是辛苦啦
停下来 放松放松 慢慢听首歌吧