第1章 基于单片机开发板的控制应用
目前,在许多单片机应用系统中,上、下位机分工明确,作为下位机核心器件的单片机往往只负责数据的采集和通信,而上位机(一般为个人计算机,简称PC)通常以基于图形界面的Windows系统为操作平台;为便于查询和保存数据,还需要数据库的支持,这种应用的核心是数据通信,它包括单片机和上位机之间、客户端和服务器之间及客户端和客户端之间的通信,而单片机和上位机之间数据通信则是整个系统的基础。
单片机和PC的通信是通过单片机的串口和PC串口之间的硬件连接实现的。
1.1 单片机概述
目前单片机以其独特的优点,在智能仪表、家用电器、工业控制、数据采集和网络通信等领域得到广泛的应用。各行各业的工程技术人员根据需要对单片机应用系统进行设计和开发,从而改变了传统控制系统的设计思想和设计方法。以前,必须由模拟电路或数字电路才能实现的大部分控制功能,现在由单片机通过软件方法便可实现,这使得控制系统的性能大大提高,应用领域更加广泛。
单片机以嵌入式应用为主,故又被称为嵌入式微控制器。国际上常把单片机称为微控制器(MCU),而国内则比较习惯称为“单片机”。
1.1.1 单片机的组成
单片机又称单片微控制器,它将一个计算机系统集成到一块芯片上,主要包括微处理器(CPU)、存储器(随机访问存储器RAM、只读存储器ROM)及各种输入/输出接口(包括定时器/计数器、并行I/O接口、串行口、A/D转换器及脉冲宽度调制PWM)等,如图1-1所示。
图1-1 单片机组成框图
1. 程序存储器(ROM)
ROM用来存放用户程序,可分类为EPROM、Mask ROM、OTP ROM和Flash ROM等。
用EPROM型存储器编程(把程序代码通过一种算法写入程序存储器的操作)后,其内容可用紫外线擦除,用户可反复使用,因此特别适用于开发过程,但EPROM型单片机价格很高。
Mask ROM型存储器的单片机价格最低,适用于大批量生产。由于Mask ROM型单片机的代码只能由生产厂商在制造芯片时写入,用户更改程序代码十分不便,因此在产品未成熟时选用此型单片机风险较高。
OTP ROM型(一次可编程)单片机价格介于EPROM和MaskROM型单片机之间,它允许用户自己对其编程,但只能写入一次。
Flash ROM型单片机可采用电擦除的方法修改其内容,允许用户使用编程工具或在系统中快速修改程序代码,并且可以反复使用。
2. 中央处理器(CPU)
CPU是单片机的核心单元,通常由算术逻辑运算部件ALU和控制部件构成。CPU就像人的大脑一样,决定了单片机的运算能力和处理速度。
3. 随机存储器(RAM)
RAM用来存放程序运行时的工作变量和数据,由于RAM的制作工艺复杂,价格比ROM高得多,所以单片机内部的RAM非常宝贵,通常仅有几十到几百字节。RAM的内容是易失性(也有称为易挥发性)的,掉电后会丢失。最近出现的EEPROM或Flash ROM型的数据存储器,方便用户存放不经常改变的数据及其他重要信息。单片机通常还有特殊寄存器和通用寄存器,也属于RAM空间,但它们在单片机中存取数据速度很快;特殊寄存器还有充分发挥单片机各种资源的功效,但这部分存储器占用存储空间更小。
4. 并行输入/输出(I/O)接口
并行输入、输出(I/O)接口通常为独立的双向I/O接口,任意接口既可以用作输入方式,又可以用作输出方式,通过软件编程设定。现代的单片机的I/O接口还有不同的功能,有的内部具有上拉或下拉电阻,有的是漏极开路输出,有的能提供足够的电流直接驱动外部设备。I/O接口是单片机的重要资源,也是衡量单片机功能的重要指标之一。
5. 串行输入/输出接口
串行输入/输出口用于单片机和串行设备或与其他单片机的通信。串行通信有同步和异步之分,这可以用硬件或通用串行收发器件实现。不同的单片机可能提供不同标准的串行通信接口,如UART、SPI、I2C和MicroWire等。
6. 定时器/计数器(T/C)
定时器/计数器(T/C)用于单片机内部精确定时或对外部事件(输入信号如脉冲)进行计数,通常单片机内部有两个或两个以上的定时器/计数器。
7. 系统时钟
系统时钟通常需要外接石英晶体或其他振荡源提供时钟信号输入,也有的使用内部RC振荡器。系统时钟相当于PC微机中的主频。
以上只是单片机的基本构成,现代的单片机又加入了许多新的功能部件,如模拟/数字转换器(A/D)、数字/模拟转换器(D/A)、温度传感器、液晶(LCD)驱动电路、电压监控、看门狗(WDT)电路和低压检测(LVD)电路等。
1.1.2 常用的单片机系列
自20世纪80年代以来,单片机产品如雨后春笋般大量涌现。GI、Rochwell、Intel、Zilog、Motorola和NEC等世界上几大计算机公司都纷纷推出自己的单片机系列。
虽然单片机的品种繁多,但在我国使用最多的还是Intel公司研发的MCS-51系列单片机。MCS-51系列单片机是于20世纪80年代初,在MCS-48系列的基础上发展起来的,虽然仍然是8位的单片机,但它品种齐全、兼容性强,而且性能价格比高;同时,其软、硬件应用设计资料丰富,已为广大工程技术人员所熟悉,因此在我国得到了广泛的应用。
MCS是Intel公司的注册商标。凡Intel公司生产的以8051为核心单元的其他派生单片机都可以称为MCS-51系列,有时简称为51系列。MCS-51系列单片机包括3个基本型8031、8051、8751和对应的低功耗型801231、80C51、87C51。
MCS-51系列及80C51单片机有多个品种,它们的引脚及指令系统相互兼容,主要在内部结构上有些区别,最常用的51系列单片机是8051和AT89C51(如图1-2所示)等。
图1-2 AT89C51系列单片机产品示意图
AT89C51具有片内EEPROM,是真正的单片机,由于不需要外接EPROM,所以AT89C51的应用非常普遍。8031、8051片内没有EPROM,但它的软硬件系统开发成熟,在市场上价格很低,所以应用也非常广泛。目前开发的51系列的产品大多是8031、8051和AT89C51等。
宏晶公司生产的STC89C5lRC单片机为低电压、高性能的CMOS 8位单片机,片内含2KB的可反复擦写的只读程序存储器(PEROM)和128B的随机存取数据存储器(RAM),工作电压为2.7~6V,且含有两个16位的定时器,6个内部中断源,可编程的串口UART和兼容标准MCS-51指令系统。该单片机片内置有通用8位中央处理器和Flash存储单元,封装只有40针,体积比较小,工作温度为-40~85℃。
STC89C5lRC单片机可以利用STC-ISP软件方便的实现在线烧写程序。
1.1.3 单片机的开发工具
1. 仿真器
单片机的仿真器本身就是一个单片机系统,具有与所要开发的单片机应用系统相同的单片机芯片。
当一个单片机应用系统电路连接完毕,由于其自身无调试能力,无法检验好坏时,可以将系统中的单片机拔掉,插上在线仿真器提供的仿真头。仿真头是一个40脚插头,它是仿真器的单片机信号的延伸,即单片机应用系统与仿真器共用一块单片机芯片。当在开发工具上通过在线仿真器调试单片机应用系统时,就像使用应用系统中真实的单片机一样,这种替代被称为仿真。
在线仿真器是由一系列硬件构成的设备。开发工具中的在线仿真器能够仿真应用系统中的单片机,并能模拟应用系统中的ROM、RAM和I/O接口的功能,这使得在线仿真的应用系统的运行环境和脱机运行的环境完全一致,以实现单片机应用系统的一次性开发。
2. 编程语言
开发单片机的编程语言主要是汇编语言和C语言。
采用汇编语言编程必须对单片机的内部资源和外围电路非常熟悉,尤其是对指令系统的使用必须非常熟练,对程序开发者的要求比较高。用汇编语言开发软件,程序量通常比较大,方方面面需要考虑周全,一切问题都要由程序设计者安排,其实时性和可靠性完全取决于程序设计人员的水平。采用汇编语言编程主要适用于功能比较简单的中小型应用系统。
采用C语言编程时,只需对单片机的内部结构基本了解,对外围电路比较熟悉,但对指令系统则不必非常熟悉。用C语言开发软件相对轻松,很多细节问题无须考虑,编译软件会替设计者安排好。因此,C语言在单片机软件开发中的应用越来越广,使用者越来越多。当开发环境为基于操作系统编程时,编程语言通常采用C语言。
单纯采用C语言编程也有不足之处。在一些对时序要求非常苛刻或对运行效率要求非常高的场合,只有汇编语言能够很好地胜任。因此,在很多情况下,采用C语言和汇编语言混合编程是最佳选择。
从编程难度来看,汇编语言比C语言要难得多,但作为一个立志从事单片机系统开发的科技人员,必须熟练掌握汇编语言程序设计方法。在熟练掌握汇编语言编程之后,学习C语言编程将是一件比较轻松的事情,并且能够将C语言和汇编语言非常恰当地融合在一起,同时以最短的时间和最低的代价,开发出高质量的软件。
当系统调试结束,确认软件无故障时,应把用户应用程序固化到EPROM中。EPROM写入器就是完成这种任务的专用设备,也是单片机开发工具中的重要组成部分。
1.1.4 单片机的特点及应用
1. 单片机的特点
单片机具有如下特点:
(1)集成度高。单片机把CPU、RAM、ROM、I/O接口及定时器/计数器都集成在一个芯片上,和常规的计算机系统相比,它具有体积小,集成度高的特点。如MCS-51系列单片机,具有16位的定时器/计数器和4个并行I/O接口,此外还提供有串行接口。
(2)存储量大。采用16位地址总线的8位单片机可寻址外部64KB数据存储器和64KB程序存储器。此外,大部分单片机还有片上RAM和内部ROM,在大多数情况下,使用内部存储器就已足够,从而减少了器件的使用数量、降低了成本。
(3)性能高、速度快。为了提高速度和执行效率,单片机使用RISC体系结构、并行流水线操作和DSP等设计技术,使指令运行速度大幅提高。一般单片机的时钟频率可以达到12MHz。
(4)抗干扰性高。单片机的各种功能部件都集成在一块芯片上,特别是存储器也集成在芯片内部;单片机布线短,大都在芯片内部传送数据,因此不易受到外部的干扰,增强了抗干扰能力,使系统运行更加可靠。
(5)指令丰富。单片机一般都有传送指令,逻辑运算指令,转移指令,加、减运算指令,以及位操作指令。
(6)实时控制能力强。实时控制又称过程控制,是指及时地检测设备、采集数据信息,并按最佳方案对设备进行自动调节和控制。单片机具有很强的逻辑操作、位处理和判断转移功能,运行速度快,特别适合于工业系统实时控制。
(7)应用开发周期短。单片机结构简单,硬件组合、软件编程都很方便,又容易进行模拟试验,因此付诸实际应用快。
2. 单片机的应用
单片机由于具有可靠性高、集成度高、价格低廉和容易产品化等特点,在智能仪器仪表、工业实时控制、智能终端、通信设备、医疗器械、汽车电器和家用电器等领域都得到了广泛的应用。以下简单介绍一些典型的应用。
1)家用电器领域
如洗衣机、空调器、汽车电子与保安系统、电视机、录像机、DVD机、音响设备、电子秤、IC卡、手机和BP机等,在这些设备中使用单片机机芯之后,其控制功能和性能大大提高,并实现了智能化、最优化控制。
2)终端及外部设备控制
计算机网络终端设备,如银行终端、商业POS(自动收款机)、复印机等,以及计算机外部设备,如打印机、绘图机、传真机、键盘和通信终端等。在这些设备中使用单片机,使其具有计算、存储、显示和输入等功能,因其有和计算机连接的接口,故而能使计算机的能力及应用范围大大提高、更好地发挥计算机的性能。
3)工业自动化领域
在工业自动化领域,单片机系统主要用来实现信号的检测、数据的采集及应用对象的控制。这些系统中除一些小型工控机外,许多都是以单片机为核心的单机或多机网络系统。
单片机广泛地应用于各种实时控制系统中。例如,在工业测控、航空航天和尖端武器等实时控制系统中,都可以用单片机作为控制器。单片机的实时数据处理能力和控制功能,能使系统保持在最佳工作状态,提高系统的工作效率和产品质量。
4)智能仪表
单片机具有体积小、功耗低、控制功能强、扩展灵活、微型化和使用方便等优点,广泛应用于仪器仪表中,结合不同类型的传感器,可实现诸如电压、功率、频率、湿度、温度、流量和压力等物理量的测量。例如,精密的测量设备(功率计、示波器和各种分析仪),采用单片机控制使得仪器仪表能够数字化、智能化和微型化,并且功能比起采用电子或数字电路更加强大,大大提高了其性能价格比。
通过采用单片机软件编程技术,使长期以来测量仪表中的误差修正、线性化处理、存储和数据处理等难题迎刃而解,提高了仪器的精度和可靠性,扩大了仪器的功能。
5)机电一体化产品
机电一体化是机械工业发展的方向。机电一体化产品是指集机械技术、微电子技术、计算机技术和传感器技术于一体,具有智能化特征的机电产品,例如,微机控制的车床和钻床等。单片机作为产品中的控制器,能充分发挥它体积小、可靠性高超功能强等优点,大大提高机器的自动化和智能化程度。可编程顺序控制器也是一个典型的机电控制器,核心常常就是由一个单片机构成的。
1.2 串行通信组件MSComm
MSComm组件全称为Microsoft Communications Control,是Microsoft公司提供的串行通信编程的ActiveX组件,它既可以用来提供简单的串行端口通信功能,也可以用来创建全双工的、事件驱动的和高效实用的通信程序。
MSComm组件在串口编程时非常方便,程序员不必花时间去了解较为复杂的API函数,而且在Visual Basic、Visual C++和Delphi等语言中均可使用;使用它可以建立与串行端口的连接、通过串行端口连接到其他通信设备(如调制解调器)、发出命令、交换数据,以及监视和响应串行连接中发生的事件和错误;使用它可以进行诸如拨打电话、监视串行端口的输入数据乃至创建功能完备的终端程序等。
1.2.1 MSComm组件处理通信的方式
MSComm组件通过串行端口传输和接收数据,为应用程序提供串行通信功能。
MSComm组件提供下列两种处理通信的方式。
1. 事件驱动方式
该方式相当于一般程序设计中的中断方式。当串口发生事件或错误时,MSComm组件会产生OnComm事件,用户程序可以捕获该事件进行相应处理,它是处理串行端口交互作用的一种非常有效的方法。在许多情况下,在事件发生时程序会希望得到通知。例如,在串口接收缓冲区中有一个字符到达或一个变化发生时,程序都可以利用MSComm组件的OnComm事件捕获并处理这些通信事件;OnComm事件还可以检查和处理通信错误。在程序的每个关键功能之后,可以通过检查CommEvent属性的值来查询事件和错误。
在程序设计中,可以在OnComm事件处理函数中加入自己的处理代码,一旦事件发生即可自动执行该段程序。这种方法的优点是程序响应及时、可靠性高。
2. 查询方式
在程序的每个关键功能之后,对用户程序中设计定时或不定时的查询,通过检查CommEvent属性的值来查询事件和错误,从而作出相应的处理。在进行简单应用程序设计时可采用这种方法。例如,如果写一个简单的电话拨号程序,则没有必要对每接收一个字符都产生事件,因为唯一等待接收的字符是调制解调器的“OK”响应。
1.2.2 MSComm组件的引用
1. 组件安装
当开始一个项目的设计时,Delphi的ActiveX组件面板中会有许多默认的组件让设计者选用,这些原本就有的组件是内置组件,提供了一些基本的系统设计组件给设计者;不过,功能比较特别的组件就不会出现在其中,而是用来设计通信功能的组件MSComm也不在其中。
由于Delphi的串行通信组件并不会主动出现在组件面板里,当需要MSComm组件时,便首先要把它添加到组件面板中。
在Delphi中安装MSComm组件,先打开Delphi集成开发环境,选择菜单“Component”中的“Import ActiveX Control”命令,在“Import AcitiveX”选项卡内选择“Microsoft Comm Control 6.0(Version 1.1)”项,如图1-3所示。
图1-3 Import ActiveX对话框
指定相关参数后,单击“Install”按钮,在弹出的“Install”对话框中,单击“OK”按钮,此时MSComm组件就被添加到ActiveX组件面板中,其图标为一个电话的形状,如图1-4所示。
图1-4 ActiveX组件面板中的MSComm组件
如果图1-3列表中没有该组件,则单击“Add”按钮,在出现的对话框中指定Mscomm32.ocx文件的路径。如果打开的是以前的项目,项目中含有MSComm组件的引用记录,则项目会自动去搜寻MSComm组件,并将它载入,不需要以上的步骤。
ActiveX组件面板中有了MSComm组件,就可以选择MSComm组件的图标后将其加到程序窗体上。
每个使用的MSComm组件对应着一个串行端口,如果应用程序需要访问多个串行端口,必须使用多个MSComm组件。
2. 组件操4F5
在使用Delphi所提供的串行通信功能之前,必须对Delphi的MSComm组件作一个了解,以便可以将串行通信的概念套用上去。
Windows采用了全新的对象化思想设计:把所有的程序都对象化。对象化之后,在采用Delphi设计串行通信的相关项目时,一样是遵循了如下4个主要步骤。
(1)对象:首先了解所要操作的对象是什么?
(2)属性:该对象所具备的特性有哪些?
(3)事件:该对象在系统执行的过程中会因其他对象而发生什么样的事情?
(4)方法:当该对象被引发了某个事件之后,程序应该采用的步骤是什么?
在通信前还需添加对象。首先我们要使用MSComm组件对外作串行通信,因此,在组件面板中选择了MSComm组件的图标后将其添加到程序窗体上,便可在窗体上安置了一个MSComm,形同安装一个和串行端口沟通的管道在画面上。利用该组件PC就可以通过Delphi实现与串口设备的串口通信了。
接下来就是属性的设置。每一个组件的属性都相当多,通过属性值的设置,可以指定硬件以一定的方式工作。
1.2.3 MSComm组件的常用属性
MSComm组件的属性很多,这里介绍串口编程中经常用到的14个重要属性。
1. CommPort属性
语法:MSComm1.CommPort[:=Value];
作用:设置或返回通信端口号。
设计时,CommPort属性值value可以设置为1~16之间的任何整数值(默认值为1)来表示串口COM1,COM2…。如果用PortOpen属性打开一个并不存在的端口,MSComm组件会产生错误68(设备无效)。
注意:必须在打开端口之前设置CommPort属性。
例如,MSComm1.CommPort := 2; //COM2上连接有一个调制解调器
2.Input属性
语法:MSComm1.Input
作用:返回并删除接收缓冲区中的数据流。
InputLen属性确定被Input属性读取的字符数。InputMode属性确定用Input属性读取的数据类型。
例如,如果希望从接收缓冲区获取数据,并将其显示在一个文本框中,可以使用代码如下:
TxtDisplay.Text := MSComm1.Input;
该属性在设计时无效,在运行时为只读。
3. InputLen属性
语法:MSComm1.InputLen [:= value];
作用:设置并返回Input属性从接收缓冲区读取的字符数。
Value是整型表达式,说明Input属性从接收缓冲区中读取的字符数。
说明:InputLen属性的默认值是0。设置InputLen为0时,使用Input将使MSComm组件读取接收缓冲区中全部的内容。若接收缓冲区中InputLen字符无效,Input属性返回一个零长度字符串("")。
在使用Input前,用户可以选择检查InBufferCount属性来确定缓冲区中是否已有需要数目的字符。该属性在从输出格式为定长数据的机器读取数据时非常有用。如果读取以定长数据块的形式格式化了的数据时,则需要将该属性设置为合适的值。
例如,MSComm1.InputLen:=10; //当程序执行该指令时,只会读取10个字符
4. InputMode属性
语法:MSComm1.InputMode[:=Value];
作用:设置或返回接收数据的数据类型。
InputMode属性的Value值可以设置为如下常数:
(1)0——通过Input属性以文本方式取回传入的数据。
(2)1——通过Input属性以二进制方式取回传入的数据。
例如,MSComm1.InputMode:=1; //表示以二进制方式读取数据
5. OutPut属性
语法:MSComm1.OutPut[:=Value];
作用:向传输缓冲区写数据流。
Output属性可以传输文本数据或二进制数据。用Output属性传输文本数据,必须定义一个包含一个字符串的Variant;发送二进制数据,必须传递一个包含字节数组的Variant到Output属性。
正常情况下,如果发送一个ANSI字符串到应用程序,可以以文本数据的形式发送;如果发送包含嵌入控制字符或Null字符等数据,要以二进制形式发送。
可用Output属性发送命令、文字字符串或Byte数组数据。
例如:MSComm1.Output: = "ATDT 551-5555"; //发送AT命令串
MSComm1.Output := " This is a text string "; //发送文本字符串
该属性在设计时无效,在运行时为只读。
6. PortOpen属性
语法:MSComm1.PortOpen[:=Value];
作用:设置或返回通信端口的状态。
设置PortOpen属性为True时打开端口;设置为False时则关闭端口,并清除接收和传输缓冲区。当应用程序终止时,MSComm组件自动关闭串行端口。
在打开端口之前,确定CommPort属性设置为一个合法的端口。如果CommPort属性设置为一个非法的端口,当打开该端口时,MSComm组件产生错误68(设备无效)。
串行端口设备必须支持Settings属性当前的设置值。如果Settings属性包含硬件不支持的通信设置值,硬件可能不会正常工作。
该属性在设计时无效,在运行时为只读。
7. Settings属性
语法:MSComm1.Settings[:=Value];
作用:设置并返回通信参数。
值Value为String型,说明通信端口的设置值。
Settings属性可以用来指定波特率、奇偶校验、数据位数和停止位数;奇偶校验设置是为了进行数据校验,但通常是不用的,设置为“N”;数据位数指定了代表一个数据块的比特数;停止位指出了何时接收到一个完整数据块。
例如:MSComm1.Settings := "9600,N,8,1"; //表示传输速率为9600bps,没有奇偶校验位,8位数据位,1位停止位
8.RThreshold属性
语法:MSComm1.Rthreshold [:= value ];
作用:OnComm事件发生之前,设置并返回接收缓冲区可接收的字符数。
Value是整型表达式,说明在产生OnComm事件之前要接收的字符数。
当接收字符后,若Rthreshold属性设置为0(默认值)则不产生OnComm事件;设置Rthreshold为1,接收缓冲区每收到一个字符都会使MSComm组件触发OnComm事件。
9.SThreshold属性
语法:MSComm1.SThreshold [:= value ];
作用:OnComm事件发生之前,设置并返回发送缓冲区中允许的最小字符数。
Value是整型表达式,代表在OnComm事件产生之前在传输缓冲区中的最小字符数。
若设置SThreshold属性为0(默认值),数据传输事件不会产生OnComm事件;若设置Sthreshold属性为1,当传输缓冲区完全空时,MSComm组件产生OnComm事件。如果在传输缓冲区中的字符数小于value,CommEvent属性设置为comEvSend,并产生OnComm事件。
OnComm事件被用来监视和响应通信状态的变化。如果将Rthreshold和SThreshold属性的值都设置为零,就可以避免发生OnComm事件;如果将该值设置为非零的值(如1),每当缓冲区中接收到一个字符时,就会产生OnComm事件。
10. DTREnable属性
语法:MSComm1. DTREnable [:= value ];
作用:确定在通信时是否使DTR线有效。
DTR是计算机发送到调制解调器的信号,指示计算机在等待接收传输。当DTREnable设置为True,打开端口时,DTR线设置为高电平(开);当端口被关闭时,DTR线设置为低电平(关)。当DTREnable设置为False时,DTR线始终保持为低电平。
11. RTSEnable属性
语法:MSComm1. RTSEnable [:= value ];
作用:确定是否使RTS线有效。
通常由计算机发送RTS信号到调制解调器,请求允许发送数据。当RTSEnable设置为True,且端口打开时,RTS线设置为高电平;端口关闭时,RTS线设置为低电平。当RTSEnable设置为False时,RTS线始终保持为低电平。
12. InBufferSize属性
语法:MSComm1.InBufferSize[:=Value];
作用:设置或返回接收缓冲区大小。
值Value为Integer型,表示接收缓冲区的字节数。例如,可选1024。
InBufferSize和OutBufferSize属性指定了为接收和发送缓冲区分配的内存数量。这两个值设置得越大,应用程序中可用的内存就越少。如果缓冲区太小,就要冒缓冲区溢出的风险,除非采用握手信号。
由于现在大多数微机有更多的可用内存资源,缓冲区内存分配已不那么至关紧要了。因此,可以把缓冲区的值设得高一些而不影响应用程序的性能。
13. InBufferCount属性
语法:MSComm1.InBufferCount[:=Value];
作用:返回接收缓冲区中等待的字符数。
InBufferCount是指调制解调器已接收,并在接收缓冲区等待被取走的字符数。可以设置InBufferCount属性为0来清除接收缓冲区。
14. Handshaking属性
语法:MSComm1.Handshaking[:=Value];
作用:设置或返回硬件握手协议。指PC与Modem之间为了控制流速而约定的内部协议。Value值的设置及意义如下:
(1)0——ComNone:没有握手协议。不考虑流量控制。
(2)1——ComXOn/Xoff:即在数据流中嵌入控制符来进行流量控制。
(3)2——ComRTS:即由信号线RTS自动进行流量控制。
(4)3——ComRTSXOnXOff:两者皆可。
注:实践中发现选用2(即ComRTS)是很方便的。
说明:要保证数据传输成功,必须对接收和发送缓冲区进行管理。例如,要保证接收数据的速度不超出缓冲区的限制。握手是指一种内部的通信协议,通过它将数据从硬件端口传输到接收缓冲区。当串行端口收到一个字符时,通信设备必须将其移入接收缓冲区中,使程序能够读到。如果数据到达端口的速度太快,通信设备可能会来不及将数据移入接收缓冲区。握手协议保证了不会由于缓冲区溢出而导致丢失数据。
需要使用的协议与连接的设备有关。如果将该值设置为ComRTSXOnXOff,便可以同时支持两种协议。
1.2.4 MSComm组件的OnComm事件
根据应用程序的用途和功能,在连接到其他设备的过程中,以及接收或发送数据的过程中,可能需要监视并响应一些事件和错误。
可以使用OnComm事件和CommEvent属性捕捉并检查通信事件和错误的值。
CommEvent属性返回最近的通信事件或错误,该属性在设计时无效,在运行时为只读。
只要发生通信事件或错误时,都会触发OnComm事件,CommEvent属性的值将被改变。因此,在发生OnComm事件时,如果有必要,可以检查CommEvent属性的值。由于通信(特别是通过电话线的通信)是不可预料的,捕捉这些事件和错误将有助于使应用程序对这些情况做出相应的反应。
MSComm组件把17个事件归并为一个事件OnComm,用属性CommEvent的17个值来区分不同的触发时机。
如表1-1所示,列出了几个可能触发OnComm事件的通信事件,对应的值将在发生事件时被写入CommEvent属性。
表1-1 通信事件常数定义值
另外10种情况是可能发生的各种通信错误时触发,可参看有关资料。
如表1-2所示,列出的错误同样会触发OnComm事件,并在CommEvent属性中写入相应的值。
表1-2 通信错误常数定义值
MSComm组件可捕获的错误消息如表1-3所示。
表1-3 MSComm组件可捕获的错误消息
通过事件的引发,借由CommEvent属性值的数值便可明确了解所发生的错误或事件,程序中通常就以常数定义作为判断,一旦OnComm事件发生,会连带地引入CommEvent参数,用户可以在每一个相关的Case语句之后编写程序代码来处理特定的错误或事件。
1.2.5 MSComm组件通信步骤
通常以下面的步骤来使用Delphi的MSComm组件作通信控制:
(1)加入通信部件,也就是MSComm对象。
(2)设置通信端口号码,即CommPort属性。
(3)设置通信协议,即HandShaking属性。
(4)设置传输速度等参数,即Settings属性。
(5)设置其他参数,若必要时再加上其他的属性设置。
(6)打开通信端口,即PortOpen属性设成True。
(7)送出字符串或读入字符串,使用Input及Output属性。
(8)使用完MSComm通信对象后,将通信端口关闭,即PortOpen属性设成False。遵循以上步骤,便可以自行建构串行通信传输系统。
1.3 系统设计说明
1.3.1 设计任务
分别利用Keil C51、汇编语言编写程序实现单片机数据采集与控制;利用Delphi编写程序实现PC与单片机自动化控制。任务要求如下。
1. 模拟电压输入
单片机开发板接收变化的模拟电压(范围:0~5V)并在数码管上显示(保留1位小数);PC接收单片机发送的电压值(十六进制,1字节),转换成十进制形式,并以数字、曲线的方式显示。
2. 模拟电压输出
在PC程序界面中输入一个数值(范围:0~10),发送到单片机开发板,在数码管上显示(保留1位小数),并通过模拟电压输出端口输出同样大小的电压值。
3. 数字量输入
将单片机开发板数字量输入端口与地短接或断开,产生数字信号0或1送到单片机数码管上显示;单片机再将数字信号发送到PC显示。
4. 数字量输出
PC发出开关指令(0或1)传送给单片机开发板,驱动相应的继电器动作。
单片机与PC通信,在程序设计上涉及两个部分的内容:一是单片机的C51数据采集和控制程序;二是PC的串口通信程序和各种功能程序。
1.3.2 硬件系统
1. 线路连接
基于单片机开发板的数据采集与控制系统结构如图1-5所示。
图1-5 PC与单片机组成的数据采集与控制系统框图
工作过程:作为下位机的单片机实时采集测量到的电压值,并将采集的电压数据显示在数码管上,同时采集的电压值通过串口传送到上位机PC。上位机收到下位机传送来的电压数据,在显示屏上显示。上位机PC设置电压值,通过串口发送到单片机系统,单片机数码管显示该电压,并通过模拟电压输出端口输出。上位机PC发出开关指令传送给单片机系统,驱动继电器动作。电气开关产生开关信号,发送到PC显示。
如图1-6所示,单片机开发板与PC数据通信采用3线制,将单片机开发板B的串口与PC串口的3个引脚(RXD、TXD、GND)分别连在一起,即将PC和单片机的发送数据线TXD与接收数据RXD交叉连接,两者的地线GND直接相连。
图1-6 PC与单片机开发板B组成数据采集与控制系统
由于单片机的TTL逻辑电平和RS-232C的电气特性完全不同,RS-232C的逻辑0电平规定为3~15V之间,逻辑1电平为-3~-15V之间,因此,在将PC和单片机的RXD和TXD交叉连接时必须进行电平转换。单片机开发板B使用的是MAX232电平转换芯片。
模拟电压输入:直接采用单片机的5V电压输出(40和20引脚)。将电位器两端与STC89C51RC单片机的40和20引脚相连,电位器的中间端点(输出电压0~5V)与单片机开发板B的模拟量输入口AI0相连。
模拟电压输出:不需连线,使用万用表直接测量单片机开发板B的AO0、AO1、AO2和AO3端口与GND端口之间的输出电压。
数字量输入:使用杜邦线将单片机开发板B的DI0、DI1、DI2、DI3端口与DGND端口连接或断开即可。
数字量输出:不需连线,直接使用单片机开发板B的继电器和指示灯。
2. 单片机开发板B简介
单片机开发板B是电子开发网专为单片机初学者设计并开发的一种实验兼开发板。开发这个产品的目的是为了帮助单片机初学者快速学会单片机技术。在自学单片机的过程中,通过做一系列的实验,就能够比较容易地领会了单片机那些枯燥、难懂的专业术语,而且这款实验开发板弥补了市场上常见的单片机实验板的一些不足,有针对性地面向最终的实用控制功能,包括模拟量输入与输出接口、数字量输入与输出接口,并且增加了实用的继电器接口,可以使实验板能够直接用于控制各种负载,成为一个实用化的嵌入式控制系统。单片机开发板B的实物图如图1-7所示。
单片机开发板B主要元件如下。
电源部分元件:9V左右直流插头式小电源,电源插座1个,7805稳压芯片1个,470μF/16V电源滤波电容2个,0.1μF独石电容2个,电源指示绿色LED1个,LED限流电阻(560Ω)1个。
单片机最小系统部分的元件:STC89C51单片机芯片1片,40脚零拔插力ZIF插座1个,复位用22μF/16V电容1个,复位用1kΩ电阻1个,30pF小电容2个,12MHz晶振1个。
图1-7 单片机开发板B实物图
实验部分元件:小红色长方形LED 8个,1kΩ数码管限流排阻8个,共阴两位一体化的数码管1个,3V电磁型蜂鸣器1个,8550驱动三极管1个,1kΩ三极管基极驱动电阻3个,微型轻触开关4个,4位的红色拨码开关1个,12V JQC-3F继电器(一组常开转常闭)2个,1N4007防反峰二极管2个,8050驱动三极管(e/b/c)2个,继电器状态指示红色发光二极管2个,MAX232芯片1片,5.1kΩ上拉电阻2个,10μF电容4个,塑封一体化红外线接收头1个,AT24C02存储器芯片1片,220μF滤波电容1个,0.1μF电容1个,32个按键的红外遥控手柄1个,串口通信电缆1根,4.7kΩ上拉电阻1个。
单片机开发板B可以做很多实验,如模拟电压输入与输出、开关量输入与输出、红外线遥控器编码分析仪、通用频率计和温度测控等。
有关单片机开发板B的详细信息请查询电子开发网(http://www.dzkfw.com/)。
1.4 数据采集与控制程序设计
1.4.1 模拟量输入
1.4.1.1 利用Keil C51实现单片机模拟电压输入
Keil C51软件是众多单片机应用开发的优秀软件之一,它集编辑、编译和仿真于一体,支持汇编语言、PLM语言和C语言的程序设计,界面友好,易学易用。
启动Keil C51,几秒后出现编辑界面,按照如下步骤可实现单片机模拟电压输入。
1. 建立一个新工程
单击“Project”菜单,在弹出的下拉菜单中选中“New Project”选项,出现“Create New Project”对话框。然后选择需要保存的路径、文件夹,输入工程文件的名字,如pc_com(后缀名默认),单击“保存”按钮。
这时会弹出一个“Select Device for Target ‘Target 1’”对话框,要求用户选择单片机的型号,用户可以根据使用的单片机来选择。Keil C51几乎支持所有的51核的单片机。这里选择Atmel的89C51。选择89C51之后,右边一栏出现对这个单片机的基本的说明,单击“确定”按钮。
2. 编写程序
单击“File”菜单,在下拉菜单中单击“New”选项,光标在编辑窗口中闪烁时,便可以输入用户的应用程序,建议首先保存该空白的文件。
单击“File”菜单,在下拉菜单中选中“Save As”选项单击,在“文件名”栏右侧的编辑框中输入欲使用的文件名,同时,必须输入正确的扩展名,如pc_com.c,单击“保存”按钮。
注意:若用C语言编写程序,则扩展名为(.c);若用汇编语言编写程序,则扩展名必须为(.asm)。
回到编辑界面后,单击“Target 1”前面的“+”号,在“Source Group 1”上单击右键,弹出菜单,单击“Add File to Group‘Source Group 1’”。
选中pc_com.c,单击“Add ”按钮,再单击“Close ”按钮。此时,注意到“Source Group 1”文件夹中多了一个子项“pc_com.c”。子项的数目与所增加的源程序的数目相同。
完成以上步骤后,请输入C语言源程序。
在输入程序时,Keil C51会自动识别关键字,并以不同的颜色提示用户加以注意,这样会减少用户失误的机会,有利于提高编程效率。
3. 编译程序
单击“Project”菜单,在下拉菜单中单击“Options for Target‘Target 1’”选项,出现对话框,选择“Output”选项卡,选中“Create HEX Files”项,单击“确定”按钮。
再单击“Project”菜单,在下拉菜单中选择“Built Target”选项(或者使用快捷键F7),进行编译。若有错误将会在output窗口提示,用户可根据此提示,找出错误并修改,直至编译通过,如图1-8所示。
图1-8 Keil C51编译界面
至此,用户在Keil C51上完成了一个完整的工程,并生成了一个编程器烧写文件,即pc_com.hex。
以下是完成单片机模拟电压输入的C51参考程序:
/****************************************************************** **程序功能:模拟电压输入,显示屏显示(保留1位小数),并以十六进制形式发送给PC ** 晶振频率:11.0592MHz ** 线路→单片机开发板B ******************************************************************************/ #include <REG51.H> #include <intrins.h> /*******************TLC0832端口定义******************************************/ sbit ADC_CLK=P1^2; sbit ADC_DO=P1^3; sbit ADC_DI=P1^4; sbit ADC_CS=P1^7; /***************数码显示 键盘接口定义****************************************/ sbit PS0=P2^4; //数码管小数点后第一位 sbit PS1=P2^5; //数码管个位 sbit PS2=P2^6; //数码管十位 sbit PS3=P2^7; //数码管百位 sfr P_data=0x80; //P0口为显示数据输出口 sbit P_K_L=P2^2; //键盘列 sbit JDQ1=P2^0; //继电器1控制 sbit JDQ2=P2^1; //继电器2控制 //字段转换表 unsigned char tab[]={0xfc,0x60,0xda,0xf2,0x66,0xb6,0xbe,0xe0,0xfe,0xf6,0xee,0x3e,0x9c,0x7a,0x9e,0x8e}; unsigned char adc_change(unsigned char a); //操作TLC0832 unsigned int htd(unsigned int a); //进制转换函数 void display(unsigned int a); //显示函数 void delay(unsigned int); //延时函数 void main(void) { unsigned int a,temp; TMOD=0x20; //定时器1——方式2 TL1=0xfd; TH1=0xfd; //11.0592MHz晶振,波特率为9600 SCON=0x50; //方式1 TR1=1; //启动定时 while(1) { temp=adc_change('0')*10*5/255; for(a=0;a<200;a++) //显示,兼有延时的作用 display(htd(temp)); //SBUF=(unsigned char)(temp>>8); //将测量结果发送给PC //while(TI!=1); //TI=0; SBUF=(unsigned char)temp; while(TI!=1); TI=0; if(temp>45) JDQ1=0; //继电器1动作 else JDQ1=1; //继电器1复位 if(temp<5) JDQ2=0; //继电器2动作 else JDQ2=1; //继电器1复位 } } /**************************数码管显示函数**************************/ /*函数原型:void display(void) /*函数功能:数码管显示 /*调用模块:delay() /******************************************************************/ void display(unsigned int a) { bit b=P_K_L; P_K_L=1; //防止按键干扰显示 P_data=tab[a&0x0f]; //显示小数点后第1位 PS0=0; PS1=1; PS2=1; PS3=1; delay(200); P_data=tab[(a>>4)&0x0f]|0x01; //显示个位 PS0=1; PS1=0; delay(200); //P_data=tab[(a>>8)&0x0f]; //显示十位 PS1=1; //PS2=0; //delay(200); //P_data=tab[(a>>12)&0x0f]; //显示百位 //PS2=1; //PS3=0; //delay(200); //PS3=1; P_K_L=b; //恢复按键 P_data=0xff; //恢复数据口 } /***************************************************************************** ; 函数名称:adc_change ; 功能描述:TI公司8位2通ADC芯片TLC0832的控制时序 ; 形式参数:config(无符号整型变量) ; 返回参数:a_data ; 局部变量:m、n *******************************************************************************/ unsigned char adc_change(unsigned char config) //操作TLC0832 { unsigned char i,a_data=0; ADC_CLK=0; _nop_(); ADC_DI=0; _nop_(); ADC_CS=0; _nop_(); ADC_DI=1; _nop_(); ADC_CLK=1; _nop_(); ADC_CLK=0; if(config=='0') { ADC_DI=1; _nop_(); ADC_CLK=1; _nop_(); ADC_DI=0; _nop_(); ADC_CLK=0; } else if(config=='1') { ADC_DI=1; _nop_(); ADC_CLK=1; _nop_(); ADC_DI=1; _nop_(); ADC_CLK=0; } ADC_CLK=1; _nop_(); ADC_CLK=0; _nop_(); ADC_CLK=1; _nop_(); ADC_CLK=0; for(i=0;i<8;i++) { a_data<<=1; ADC_CLK=0; a_data+=(unsigned char)ADC_DO; ADC_CLK=1; } ADC_CS=1; ADC_DI=1; return a_data; } /**************************十六进制转十进制函数**************************/ /*函数原型:uint htd(uint a) /*函数功能:十六进制转十进制 /*输入参数:要转换的数据 /*输出参数:转换后的数据 /******************************************************************/ unsigned int htd(unsigned int a) { unsigned int b,c; b=a%10; c=b; a=a/10; b=a%10; c=c+(b<<4); a=a/10; b=a%10; c=c+(b<<8); a=a/10; b=a%10; c=c+(b<<12); return c; } /*******************************延时函数*********************************/ /*函数原型:delay(unsigned int delay_time) /*函数功能:延时函数 /*输入参数:delay_time (输入要延时的时间) /**********************************************************************/ void delay(unsigned int delay_time) //延时子程序 {for(;delay_time>0;delay_time--) {} }
4.烧写程序
程序经过调试运行之后就可以将其烧写进单片了。STC系列单片机在线下载程序只需要用串口连接到单片机上就可以。用串口线连接PC与单片机实验板,将编写好的汇编程序用Keil μVision3编译生成HEX文件就可以实现程序的简便烧写。
在网站http://www.mcu-memory.com/上下载STC单片机ISP编程软件。按照提示在计算机上运行该程序,其界面如图1-9所示。
图1-9 单片机烧写程序界面
烧写程序步骤如下。
连接单片机开发板与PC,先不接通实验板电源。按下面步骤完成单片机程序的在线下载:
第一步:选择要下载程序的单片机型号。
第二步:打开编译完成要下载到单片机中扩展名为.HEX的文件。
第三步:选择与实验板连接的串口。
第四步:选择合适的通信波特率(可以省略该步骤)。
第五步:单击“Download/下载”按钮。
第六步:接通实验板电源。
完成以上6步,几秒后就可以将程序下载到单片机中,并能够运行。
程序烧写进单片机之后,就可以给单片机开发板通电了,这时数码管上将会显示检测的电压值。
5.串口通信调试
在进行串口开发之前,一般要进行串口调试,经常使用的工具是“串口调试助手”程序。这是一个适用于Windows平台的串口监视、串口调试的程序,可以在线设置各种通信速率、通信端口等参数,既可以发送字符串命令,也可以发送文件;既可以设置自动发送/手动发送方式,也可以十六进制显示接收到的数据等,提高串了口开发效率。
打开“串口调试助手”程序(ScomAssistant.exe),首先设置串口号COM1、波特率9600、校验位NONE、数据位8、停止位1等参数(注意:设置的参数必须与单片机设置的一致),选择“十六进制显示”和“十六进制发送”打开串口,如图1-10所示。
图1-10 串口调试助手
如果PC与单片机开发板串口连接正确,则单片机连续向PC发送检测的电压值,用1字节的十六进制数据表示,如0F,该数据串在返回信息框内显示。
将单片机返回数据转换为十进制,并除以10,即可知当前电压测量值为1.5V。
1.4.1.2 利用汇编语言实现单片机模拟电压输入
以下是完成单片机模拟电压输入的汇编参考程序:
/****************************************************************** ** 程序功能:模拟电压输入,显示屏显示(保留1位小数),并以十六进制形式发送给PC ** 晶振频率:11.0592MHz ** 线路→单片机开发板B ******************************************************************/ ;单片机内存分配申明! A_BYTE EQU 20H ;数码管小数点后第1位数存放内存位置 B_BYTE EQU 21H ;数码管个位数存放内存位置 C_BYTE EQU 22H ;数码管十位数存放内存位置 TEMP_ADC EQU 23H ;用于保存AD结果(十进制) ADC_CLK BIT P1.2; ADC_DO BIT P1.3; ADC_DI BIT P1.4; ADC_CS BIT P1.7; JDQ1 BIT P2.0 ;继电器1控制 JDQ2 BIT P2.1 ;继电器2控制 PS0 BIT P2.4 ;数码管小数点后第1位 PS1 BIT P2.5 ;数码管个位 PS2 BIT P2.6 ;数码管十位 PS3 BIT P2.7 ;数码管百位 ;进行温度显示,这里我们考虑用两位数码管来显示温度 ;显示范围00到99度,显示精度为1度 ;因为12位转化时每一位的精度为0.0625度,我们不要求显示小数,所以可以抛弃TEMPER_L的 ;低4位,将TEMPER_H中的低4位移入TEMPER_L中的高4位,这样获得一个新字节,这个 ;字节就是实际测量获得的温度,这个转化温度的方法非常简洁,无须乘以系数0.0625 ORG 0000H SJMP MAIN ORG 0030H MAIN: MOV SP,#60H MOV SCON,#50H ;设置成串口1方式 MOV TMOD,#20H ;波特率发生器T1工作在模式2上 MOV TH1,#0FDH ;预置初值(按照波特率9600bps预置初值) MOV TL1,#0FDH ;预置初值(按照波特率9600bps预置初值) ORL PCON,#80H ;波特率加倍 SETB TR1 ;启动定时器T1 LOOP: ACALL ADC ;调用AD转换子程序 MOV R3,#0 MOV R4,A MOV R7,#50 ;乘以50再除以255得到实际电压值的10倍,这样就可以保留小数点后1位 ACALL NMUL21 MOV R7,#255 ACALL NDIV31 MOV TEMP_ADC,R4 MOV A,R3 MOV R2,A MOV A,R4 MOV R3,A ACALL HTD ;转换成十进制 MOV A,R6 ANL A,#0FH MOV A_BYTE,A MOV A,R6 SWAP A ANL A,#0FH MOV B_BYTE,A MOV A,R5 ANL A,#0FH MOV C_BYTE,A ACALL SEND ;调用串口显示子程序 ACALL DISPLAY ;调用数码管显示子程序 MOV A,R6 ;判断电压>45(4.5V) CJNE A,#45H,LOOP2 SETB JDQ1 SETB JDQ2 SJMP LOOP LOOP2: JC LOOP3 CLR JDQ1 SETB JDQ2 SJMP LOOP LOOP3: SETB JDQ1 MOV A,R6 ;判断电压<5(0.5V) CJNE A,#5H,LOOP4 SETB JDQ2 SJMP LOOP LOOP4: JNC LOOP5 CLR JDQ2 SJMP LOOP LOOP5: SETB JDQ2 SJMP LOOP ;8位串行A/D芯片兼容ADC0832 ;AD转换子程序 ADC: SETB ADC_DI SETB ADC_DO SETB ADC_CLK CLR ADC_CS NOP SETB ADC_CS CLR ADC_CLK CLR ADC_CS ;开始采集 CALL D1MS ;延时子程序 SETB ADC_DI ;首个位为1(起始位) SETB ADC_CLK ;时钟上升沿 NOP CLR ADC_CLK SETB ADC_DI ;又一个时钟上升沿用于极性选择 SETB ADC_CLK ;SGL=1(单极性对地)而不是对VREF NOP CLR ADC_CLK CLR C ;0地址选择位单元 MOV ADC_DI,C SETB ADC_CLK ;又一个时钟上升沿用于选地址 NOP CLR ADC_CLK NOP SETB ADC_CLK ;第四个时钟上升沿 NOP CLR ADC_CLK NOP SETB ADC_CLK MOV C,ADC_DO CLR ADC_CLK ;时钟下降沿读入数据 RLC A ;7 SETB ADC_CLK MOV C,ADC_DO CLR ADC_CLK RLC A SETB ADC_CLK MOV C,ADC_DO CLR ADC_CLK RLC A SETB ADC_CLK MOV C,ADC_DO CLR ADC_CLK RLC A SETB ADC_CLK MOV C,ADC_DO CLR ADC_CLK RLC A SETB ADC_CLK MOV C,ADC_DO CLR ADC_CLK RLC A SETB ADC_CLK MOV C,ADC_DO CLR ADC_CLK RLC A SETB ADC_CLK MOV C,ADC_DO CLR ADC_CLK RLC A SETB ADC_CLK NOP CLR ADC_CLK ;1 NOP SETB ADC_CLK NOP CLR ADC_CLK ;2 NOP SETB ADC_CLK NOP CLR ADC_CLK ;3 NOP SETB ADC_CLK NOP CLR ADC_CLK ;4 NOP SETB ADC_CLK NOP CLR ADC_CLK ;5 NOP SETB ADC_CLK NOP CLR ADC_CLK ;6 NOP SETB ADC_CLK NOP CLR ADC_CLK ;7 NOP SETB ADC_CLK NOP CLR ADC_CLK ;wait for高阻态 NOP SETB ADC_CLK NOP CLR ADC_CLK ;wait for 高阻态 NOP CALL D1MS SETB ADC_CS RET ;串口发送数据子程序 SEND: CLR TI MOV SBUF,TEMP_ADC JNB TI,$ RET DISPLAY: MOV DPTR,#NUMTAB ;指定查表起始地址 MOV R0,#4 DPL1: MOV R1,#250 ;显示1000次 DPLOP: MOV A,A_BYTE ;取小数点后第1位数 MOVC A,@A+DPTR ;查个位数的7段代码 MOV P0,A ;送出个位的7段代码 CLR PS0 SETB PS1 SETB PS2 SETB PS3 ACALL D1MS ;显示1ms MOV A,B_BYTE ;取个位数 MOVC A,@A+DPTR ;查十位数的7段代码 ORL A,#01H MOV P0,A ;送出十位的7段代码 SETB PS0 CLR PS1 ACALL D1MS ;显示1ms ;MOV A,C_BYTE ;取十位数 ;MOVC A,@A+DPTR ;查十位数的7段代码 ;MOV P0,A ;送出十位的7段代码 SETB PS1 ;CLR PS2 ;ACALL D1MS ;显示1ms ;SETB PS2 DJNZ R1,DPLOP DJNZ R0,DPL1 RET ;单字节无符号数乘法程序 (R3R4*R7)=(R2R3R4) ;入口:R3,R4,R7 占用资源:ACC,B 堆栈需求:2字节 出口:R2,R3,R4 NMUL21: MOV A,R4 MOV B,R7 MUL AB MOV R4,A MOV A,B XCH A,R3 MOV B,R7 MUL AB ADD A,R3 MOV R3,A CLR A ADDC A,B MOV R2,A CLR OV RET ;单字节无符号除法程序 (R2R3R4/R7)=(R2)R3R4,余数R7 ;入口:R2,R3,R4,R7 占用资源:ACC,B,F0 堆栈需求:3字节 出口:(R2),R3,R4,R7,OV NDIV31: MOV A,R2 MOV B,R7 DIV AB PUSH ACC MOV R2,B MOV B,#10H NDV311: CLR C MOV A,R4 RLC A MOV R4,A MOV A,R3 RLC A MOV R3,A MOV A,R2 RLC A MOV R2,A MOV F0,C CLR C SUBB A,R7 JB F0,NDV312 JC NDV313 NDV312: MOV R2,A INC R4 NDV313: DJNZ B,NDV311 POP ACC CLR OV JZ NDV314 SETB OV NDV314: XCH A,R2 MOV R7,A RET HTD: CLR A ;R2和R3是程序数据入口 MOV R4,A ;转换后的数放在R4,R5,R6 MOV R5,A ;转换完后分别屏蔽高位和低位取出数据 MOV R6,A MOV R7,#10H ZH: CLR C MOV A,R3 RLC A MOV R3,A MOV A,R2 RLC A MOV R2,A MOV A,R6 ADDC A,R6 DA A MOV R6,A MOV A,R5 ADDC A,R5 DA A MOV R5,A MOV A,R4 ADDC A,R4 DA A MOV R4,A DJNZ R7,ZH RET D1MS: MOV R7,#80 ;1ms延时 DJNZ R7,$ RET numtab: DB 0FCH,60H,0DAH,0F2H,66H,0B6H,0BEH,0E0H,0FEH,0F6H ;实验板上的7段数码管0~9数字的共阴显示代码 END
1.4.1.3 利用Delphi实现PC与单片机模拟电压输入
1. 建立工程
启动Delphi 7,选择“File”菜单中的“New”子菜单下的“Application”选项,创建一个新的工程;选择“File”菜单中的“Save All”选项,在弹出的对话框内设置保存位置,分别保存单元文件DAI.pas和工程文件project1.dpr。
2. 窗体设计
(1)设置窗体Form1属性。在对象检视器中,选择Caption属性,设置标题为“PC与单片机通信(电压检测)”。
(2)为了实现计算机之间的串口通信,需要添加MScomm控件。选择“Component”菜单中的“Import AxtiveX Control控件”,在弹出的对话框中选择“Microsoft Comm Control 6.0(Version1.1)”项,单击“Install”按钮,在弹出的对话框中单击“OK”按钮,所选择的控件就会出现在“ActiveX”组件面板中。
(3)用组件面板以一定的次序为窗体添加组件,放置方式如图1-11所示。
图1-11 程序设计界面
(4)属性设置。组件的主要属性设置如表1-4所示。
表1-4 各组件的主要属性设置
3. 代码实现
具体实现步骤如下。
(1)在DAI.pas文件中,添加全局变量,具体代码如下:
var Form1: TForm1; datatemp:single; //用于存储电压采样值
(2)在DAI.pas文件中,添加Fom1窗体OnCreate事件的消息响应函数FormCreate(),实现程序初始化,具体代码如下:
procedure TForm1.FormCreate(Sender: TObject); begin MSComm1.CommPort := 1; MSComm1.InputMode := 1; MSComm1.Settings:='9600,n,8,1'; //波特率9600 MSComm1.PortOpen := True; end;
(3)在DAI.pas文件中,添加Timer1组件OnTimer事件的消息响应函数Timer1Timer(),实现周期性读串口输入缓冲区数据,具体代码如下:
procedure TForm1.Timer1Timer(Sender: TObject); var inbyte:array of byte; buffer:string; datastr:array [0..20] of string; i:integer; begin inbyte:=MSComm1.input; for i:=low(inbyte) to high(inbyte) do buffer:=buffer+ inttohex(inbyte[i],0)+chr(32); //获得单片机传送来的十六进制数(1字节) if buffer<>'' then begin //获得十六进制每一位 if length(trim(buffer))=1 then begin datastr[1]:='0'; datastr[2]:=trim(buffer); edit1.text:='0'+buffer; //显示十六进制 end; if length(trim(buffer))=2 then begin datastr[1]:=copy(trim(buffer),1,1); datastr[2]:=copy(trim(buffer),2,1); edit1.Text:=buffer; //显示十六进制 end; //将十六进制转换成十进制 datatemp:=(strtoint('$'+datastr[1])*power(16,1)+strtoint('$'+datastr[2])*power(16,0))*0.1; edit2.Text:=format('%4.1f',[datatemp]); //十进制显示,保留一位小数 end; end;
(4)在DAI.pas文件中,添加“关闭”按钮相应OnClick事件的消息响应函数Button1Click(),具体代码如下:
procedure TForm1.Button1Click(Sender: TObject); begin MSComm1.PortOpen:=False; //关闭串口 close; end;
4. 运行程序
程序设计、调试完毕,运行程序。
单片机开发板接收变化的模拟电压(0~5V)并在数码管上显示(保留1位小数);PC接收单片机发送的电压值(十六进制,1字节),转换为十进制形式,并以数字、曲线的方式显示。程序运行界面如图1-12所示。
图1-12 程序运行界面
1.4.2 模拟量输出
1.4.2.1 利用Keil C51实现单片机模拟电压输出
以下是完成单片机模拟电压输出的C51参考程序:
/****************************************************************** ** TLC5620 DAC转换实验程序 程序功能:PC向单片机发送数值(0~5),如发送2.5V,则发送25的十六进制值19,单片机接收并 显示2.5,并从模拟量输出通道输出 ** 晶振频率:11.0592MHz ** 线路→单片机开发板B ******************************************************************* ; 输出电压计算公式:VOUT(DACA|B|C|D)=REF*CODE/256*(1+RNG bit value) ********************************************************************************/ #include <REG51.H> sbit SCLA=P1^2; sbit SDAA=P1^4; sbit LOAD=P1^6; sbit LDAC=P1^5; sbit PS0=P2^4; //数码管个位 sbit PS1=P2^5; //数码管十位 sbit PS2=P2^6; //数码管百位 sbit PS3=P2^7; //数码管千位 sfr P_data=0x80; //P0口为显示数据输出口 sbit P_K_L=P2^2; //键盘列 unsigned char tab[10]={0xfc,0x60,0xda,0xf2,0x66,0xb6,0xbe,0xe0,0xfe,0xf6};//字段转换表 void ini_cpuio(void); void dachang(unsigned char a,b); void dac5620(unsigned int config); /*******************************延时函数*********************************/ /*函数原型:delay(unsigned int delay_time) /*函数功能:延时函数 /*输入参数:delay_time (输入要延时的时间) /**********************************************************************/ void delay(unsigned int delay_time) //延时子程序 {for(;delay_time>0;delay_time--) {} } /**************************十六进制转十进制函数**************************/ /*函数原型:uchar htd(unsigned int a) /*函数功能:十六进制转十进制 /*输入参数:要转换的数据 /*输出参数:转换后的数据 /******************************************************************/ unsigned int htd(unsigned int a) { unsigned int b,c; b=a%10; c=b; a=a/10; b=a%10; c=c+(b<<4); a=a/10; b=a%10; c=c+(b<<8); a=a/10; b=a%10; c=c+(b<<12); return c; } /**************************数码管显示函数**************************/ /*函数原型:void display(void) /*函数功能:数码管显示 /*调用模块:delay() /******************************************************************/ void display(unsigned int a) { bit b=P_K_L; P_K_L=1; //防止按键干扰显示 a=htd(a); //转换成十进制输出 P_data=tab[a&0x0f]; //转换成十进制输出 PS0=0; PS1=1; PS2=1; PS3=1; delay(200); P_data=tab[(a>>4)&0x0f]|0x01; PS0=1; PS1=0; delay(200); //P_data=tab[(a>>8)&0x0f]; PS1=1; //PS2=0; //delay(200); //P_data=tab[(a>>12)&0x0f]; //PS2=1; //PS3=0; //delay(200); //PS3=1; P_K_L=b; //恢复按键 P_data=0xff; //恢复数据口 } /******************************************************************************/ void main(void) { unsigned int a; float b; ini_cpuio(); //初始化TLC5620 TMOD=0x20; //定时器1——方式2 TL1=0xfd; TH1=0xfd; //11.0592MHZ晶振,波特率为9600 SCON=0x50; //方式1 TR1=1; //启动定时 while(1) { if(RI) { a=SBUF; RI=0; } display(a); b=(float)a/10/2*256/2.7; //CODE=VOUT(DACA|B|C|D)/10/(1+RNG bit value)*256/Vref dachang('a',b); //控制A通道输出电压 dachang('b',b); //控制B通道输出电压 dachang('c',b); //控制C通道输出电压 dachang('d',b); //控制D通道输出电压 } } /*******************************************************************************/ void ini_cpuio(void) //CPU的IO口初始化函数 { SCLA=0; SDAA=0; LOAD=1; LDAC=1; } void dachang(unsigned char a,vout) { unsigned int config=(unsigned int)vout; //DA转换器的配置参数 config<<=5; config=config&0x1fff; switch (a) { case 'a': config=config|0x2000; break; case 'b': config=config|0x6000; break; case 'c': config=config|0xa000; break; case 'd': config=config|0xe000; break; default : break; } dac5620(config); } /******************************************************************************** ; 函数名称:dac5620 ; 功能描述:TI公司8位4通DAC芯片TLC5620的控制时序 ; 形式参数:config(无符号整型变量) ; 局部变量:m、n ; 调用模块:SENDBYTE ; 备 注:使用11位连续传输控制模式,使用LDAC下降沿锁存数据输入 *******************************************************************************/ void dac5620(unsigned int config) { unsigned char m=0; unsigned int n; for(;m<0x0b;m++) { SCLA=1; n=config; n=n&0x8000; SDAA=(bit)n; SCLA=0; config<<=1; } LOAD=0; LOAD=1; LDAC=0; LDAC=1; }
1.4.2.2 利用汇编语言实现单片机模拟电压输出
以下是完成单片机模拟电压输出的汇编参考程序。
/****************************************************************** ** TLC5620 DAC转换实验程序 程序功能:PC向单片机发送数值(0~5V),如发送2.5V,则发送25的十六进制值19,单片机接收并 显示2.5,并从模拟量输出通道输出 ** 线路→单片机开发板B ******************************************************************* ; 输出电压计算公式: VOUT(DACA|B|C|D)=REF*CODE/256*(1+RNG bit value) *******************************************************************************/ ;单片机内存分配申明! A_BYTE EQU 20H ;数码管小数点后第1位数存放内存位置 B_BYTE EQU 21H ;数码管个位数存放内存位置 C_BYTE EQU 22H ;数码管十位数存放内存位置 TEMP1 EQU 23H TEMP2 EQU 24H SCLA BIT P1.2 SDAA BIT P1.4 LOAD BIT P1.6 LDAC BIT P1.5 PS0 BIT P2.4 ;数码管小数点后第1位 PS1 BIT P2.5 ;数码管个位 PS2 BIT P2.6 ;数码管十位 PS3 BIT P2.7 ;数码管百位 ORG 0000H SJMP MAIN ORG 0030H MAIN: MOV SP,#60H CLR SCLA ;初始化TLC5620 CLR SDAA SETB LOAD SETB LDAC MOV SCON,#50H ;设置成串口1方式 MOV TMOD,#20H ;波特率发生器T1工作在模式2上 MOV TH1,#0FDH ;预置初值(按照波特率9600bps预置初值) MOV TL1,#0FDH ;预置初值(按照波特率9600bps预置初值) ;ORL PCON,#80H ;波特率加倍 SETB TR1 ;启动定时器T1 LOOP: JNB RI,LOOP1 MOV R2,#0 MOV R3,SBUF MOV TEMP2,R3 ACALL HTD MOV TEMP1,R6 MOV A,TEMP1 ANL A,#0FH MOV A_BYTE,A MOV A,TEMP1 SWAP A ANL A,#0FH MOV B_BYTE,A CLR RI LOOP1: ACALL DISPLAY;调用数码管显示子程序 MOV R3,#01H ;CODE=VOUT(DACA|B|C|D)/10/(1+RNG bit value)*256/Vref MOV R4,#0H MOV R7,TEMP2 ACALL NMUL21 MOV R7,#2 ACALL NDIV31 MOV R7,#27 ACALL NDIV31 MOV A,R4 MOV B,#0H ACALL dachang ;控制A通道输出电压 MOV A,R4 MOV B,#1H ACALL dachang ;控制B通道输出电压 MOV A,R4 MOV B,#2H ACALL dachang ;控制C通道输出电压 MOV A,R4 MOV B,#3H ACALL dachang ;控制D通道输出电压 SJMP LOOP ;TLC5620控制程序 ;B——选择通道 ;A——控制电压 dachang: XCH A,B SETB LOAD SETB LDAC SETB SCLA CJNE A,#0H,DAC1 CLR SDAA NOP CLR SCLA NOP SETB SCLA NOP CLR SCLA SJMP DAC9 DAC1: CJNE A,#1H,DAC2 CLR SDAA NOP CLR SCLA SETB SDAA SETB SCLA NOP CLR SCLA SJMP DAC9 DAC2: CJNE A,#2H,DAC3 SETB SDAA NOP CLR SCLA CLR SDAA SETB SCLA NOP CLR SCLA SJMP DAC9 DAC3: CJNE A,#3H,DAC4 SETB SDAA NOP CLR SCLA NOP SETB SCLA NOP CLR SCLA DAC9: SETB SDAA SETB SCLA NOP CLR SCLA MOV A,B MOV B,#09H DAC7: DJNZ B,DAC5 CLR LOAD NOP SETB LOAD NOP CLR LDAC NOP SETB LDAC DAC4: RET DAC5: RLC A JC DAC6 CLR SDAA DAC8: SETB SCLA NOP CLR SCLA SJMP DAC7 DAC6: SETB SDAA SJMP DAC8 DISPLAY: MOV DPTR,#NUMTAB ;指定查表起始地址 MOV R0,#4 DPL1: MOV R1,#250 ;显示1000次 DPLOP: MOV A,A_BYTE ;取小数点后第1位数 MOVC A,@A+DPTR ;查个位数的7段代码 MOV P0,A ;送出个位的7段代码 CLR PS0 SETB PS1 SETB PS2 SETB PS3 ACALL D1MS ;显示1ms MOV A,B_BYTE ;取个位数 MOVC A,@A+DPTR ;查十位数的7段代码 ORL A,#01H MOV P0,A ;送出十位的7段代码 SETB PS0 CLR PS1 ACALL D1MS ;显示1ms ;MOV A,C_BYTE ;取十位数 ;MOVC A,@A+DPTR ;查十位数的7段代码 ;MOV P0,A ;送出十位的7段代码 SETB PS1 ;CLR PS2 ;ACALL D1MS ;显示1ms ;SETB PS2 DJNZ R1,DPLOP ;100次没完循环 DJNZ R0,DPL1 ;4个100次没完循环 RET ;单字节无符号数乘法程序 (R3R4*R7)=(R2R3R4) ;入口: R3,R4,R7 ;占用资源: ACC,B ;堆栈需求: 2字节 ;出口: R2,R3,R4 NMUL21: MOV A,R4 MOV B,R7 MUL AB MOV R4,A MOV A,B XCH A,R3 MOV B,R7 MUL AB ADD A,R3 MOV R3,A CLR A ADDC A,B MOV R2,A CLR OV RET ;单字节无符号除法程序 (R2R3R4/R7)=(R2)R3R4 余数R7 ;入口: R2,R3,R4,R7 ;占用资源: ACC,B,F0 ;堆栈需求: 3字节 ;出口: (R2),R3,R4,R7,OV NDIV31: MOV A,R2 MOV B,R7 DIV AB PUSH ACC MOV R2,B MOV B,#10H NDV311: CLR C MOV A,R4 RLC A MOV R4,A MOV A,R3 RLC A MOV R3,A MOV A,R2 RLC A MOV R2,A MOV F0,C CLR C SUBB A,R7 JB F0,NDV312 JC NDV313 NDV312: MOV R2,A INC R4 NDV313: DJNZ B,NDV311 POP ACC CLR OV JZ NDV314 SETB OV NDV314: XCH A,R2 MOV R7,A RET ;十六进制转换为十进制 HTD: CLR A ;R2和R3是程序数据入口 MOV R4,A ;转换后的数放在R4,R5,R6 MOV R5,A ;转换完后在分别屏蔽高位和低位取出数据 MOV R6,A MOV R7,#10H ZH: CLR C MOV A,R3 RLC A MOV R3,A MOV A,R2 RLC A MOV R2,A MOV A,R6 ADDC A,R6 DA A MOV R6,A MOV A,R5 ADDC A,R5 DA A MOV R5,A MOV A,R4 ADDC A,R4 DA A MOV R4,A DJNZ R7,ZH RET D1MS: MOV R7,#80 ;1ms延时 DJNZ R7,$ RET numtab: DB 0FCH,60H,0DAH,0F2H,66H,0B6H,0BEH,0E0H,0FEH,0F6H ;实验板上的7段数码管0~9数字的共阴显示代码 END
1.4.2.3 利用Delphi实现PC与单片机模拟电压输出
1. 建立工程
启动Delphi 7,选择“File”菜单中的“New”子菜单下的“Application”选项,创建一个新的工程;选择“File”菜单中的“Save All”选项,在弹出的对话框内设置保存位置,分别保存单元文件DAO.pas和工程文件project1.dpr。
2. 窗体设计
(1)设置窗体Form1属性。在对象检视器中,选择Caption属性,设置标题为“单片机模拟量输出”。
(2)为了实现计算机之间的串口通信,添加MScomm控件。选择“Component”菜单中的“Import AxtiveX Control控件”,在弹出的对话框中选择“Microsoft Comm Control 6.0(Version1.1)”项,单击“Install”按钮,在弹出的对话框中单击“OK”按钮,所选择的控件就会出现在“ActiveX”组件面板中。
(3)用组件面板以一定的次序为窗体添加组件,设置方式如图1-13所示。
图1-13 程序设计界面
(4)属性设置。组件的主要属性设置如表1-5所示。
表1-5 各组件的主要属性设置
3. 代码实现
主要解决一个问题,即如何将设定的数值发送给单片机。
具体实现步骤如下。
(1)在DAO.pas文件中,添加Form1窗体OnCreate事件的消息响应函数FormCreate(),实现程序初始化,具体代码如下:
procedure TForm1.FormCreate(Sender: TObject); begin MSComm1.CommPort := 1; MSComm1.Settings := '9600,n,8,1'; MSComm1.PortOpen := True; end;
(2)在DAO.pas文件中,添加“输出”按钮相应OnClick事件的消息响应函数Button1Click(),具体代码如下:
procedure TForm1.Button1Click(Sender: TObject); var uout:variant; i:variant; begin i:= strtofloat(edit1.text)*10; uout:='$'+inttohex(i,0); //将输入的电压值乘10后转换成十六进制 MSComm1.Output:=Chr(strtoint(uout)); //将十六进制电压值发送给单片机 end;
(3)在DAO.pas文件中,添加“关闭”按钮相应OnClick事件的消息响应函数Button2Click(),具体代码如下:
procedure TForm1.Button2Click(Sender: TObject); begin MSComm1.PortOpen:=false; close; end;
4. 运行程序
程序设计、调试完毕,运行程序。
在PC程序中输入变化的数值(0~10),单击“输出”命令,发送到单片机开发板,在数码管上显示(保留1位小数),并通过模拟电压输出端口输出同样大小的电压值。可使用万用表直接测量单片机开发板B的AO0、AO1、AO2、AO3端口与GND端口之间的输出电压。程序运行界面如图1-14所示。
图1-14 程序运行界面
1.4.3 数字量输入
1.4.3.1 利用Keil C51实现单片机数字量输入
以下是完成单片机数字量输入的C51参考程序。
/****************************************************************** 程序功能:检测数字量输入端口状态(1或0,如1111表示4个通道均为高电平,0000表示4个通 道均为低电平),在数码管显示,并以二进制形式发送给PC ******************************************************************/ #include <REG51.H> /*****************开关端口定义**************************************/ sbit sw_0=P3^3; sbit sw_1=P3^4; sbit sw_2=P3^5; sbit sw_3=P3^6; /*********************数码显示 键盘接口定义******************************/ sbit PS0=P2^4; //数码管个位 sbit PS1=P2^5; //数码管十位 sbit PS2=P2^6; //数码管百位 sbit PS3=P2^7; //数码管千位 sfr P_data=0x80; //P0口为显示数据输出口 sbit P_K_L=P2^2; //键盘列 ;//字段转换表 unsigned char tab[]={0xfc,0x60,0xda,0xf2,0x66,0xb6,0xbe,0xe0,0xfe,0xf6,0xee,0x3e,0x9c,0x7a,0x9e,0x8e} unsigned int sw_in(void); //数字量输入采集 void display(unsigned int a); //显示函数 void delay(unsigned int); //延时函数 void main(void) { unsigned int a,temp; TMOD=0x20; //定时器1——方式2 TL1=0xfd; TH1=0xfd; //11.0592MHz晶振,波特率为9600 SCON=0x50; //方式1 TR1=1; //启动定时 while(1) { temp=sw_in(); for(a=0;a<200;a++) //显示,兼有延时的作用 display(temp); SBUF=(unsigned char)(temp>>8); //将测量结果发送给PC while(TI!=1); TI=0; SBUF=(unsigned char)temp; while(TI!=1); TI=0; } } /**************************数码管显示函数**************************/ /*函数原型:void display(void) /*函数功能:数码管显示 /*调用模块:delay() /******************************************************************/ unsigned int sw_in(void) { unsigned int a=0; if(sw_0) a=a+1; if(sw_1) a=a+0x10; if(sw_2) a=a+0x100; if(sw_3) a=a+0x1000; return a; } /**************************数码管显示函数**************************/ /*函数原型:void display(void) /*函数功能:数码管显示 /*调用模块:delay() /******************************************************************/ void display(unsigned int a) { bit b=P_K_L; P_K_L=1; //防止按键干扰显示 P_data=tab[a&0x0f]; //显示个1位 PS0=0; PS1=1; PS2=1; PS3=1; delay(200); P_data=tab[(a>>4)&0x0f]; //显示十位 PS0=1; PS1=0; delay(200); P_data=tab[(a>>8)&0x0f]; //显示百位 PS1=1; PS2=0; delay(200); P_data=tab[(a>>12)&0x0f]; //显示千位 PS2=1; PS3=0; delay(200); PS3=1; P_K_L=b;//恢复按键 P_data=0xff;//恢复数据口 } /*******************************延时函数*********************************/ /*函数原型:delay(unsigned int delay_time) /*函数功能:延时函数 /*输入参数:delay_time (输入要延时的时间) /**********************************************************************/ void delay(unsigned int delay_time) //延时子程序 {for(;delay_time>0;delay_time--) {} }
1.4.3.2 利用汇编语言实现单片机数字量输入
以下是完成单片机数字量输入的汇编参考程序。
/****************************************************************** 程序功能:检测数字量输入端口状态(1或0,如1111表示4个通道均为高电平,0000表示4个通 道均为低电平),在数码管显示,并以二进制形式发送给PC ******************************************************************/ ;单片机内存分配申明! A_BYTE EQU 20H ;数码管个位数存放内存位置 B_BYTE EQU 21H ;数码管十位数存放内存位置 C_BYTE EQU 22H ;数码管百位数存放内存位置 D_BYTE EQU 23H ;数码管千位数存放内存位置 TEMP1 EQU 24H TEMP2 EQU 25H /**************************开关端口定义*****************************************/ SW_0 BIT P3.3 SW_1 BIT P3.4 SW_2 BIT P3.5 SW_3 BIT P3.6 PS0 BIT P2.4 ;数码管个位 PS1 BIT P2.5 ;数码管十位 PS2 BIT P2.6 ;数码管百位 PS3 BIT P2.7 ;数码管千位 ORG 0000H SJMP MAIN ORG 0030H MAIN: MOV SP,#60H MOV SCON,#50H ;设置成串口1方式 MOV TMOD,#20H ;波特率发生器T1工作在模式2上 MOV TH1,#0FDH ;预置初值(按照波特率9600bps预置初值) MOV TL1,#0FDH ;预置初值(按照波特率9600bps预置初值) ;ORL PCON,#80H ;波特率加倍 SETB TR1 ;启动定时器T1 LOOP: ACALL SW_IN MOV A,TEMP1 ANL A,#0FH MOV A_BYTE,A MOV A,TEMP1 SWAP A ANL A,#0FH MOV B_BYTE,A MOV A,TEMP2 ANL A,#0FH MOV C_BYTE,A MOV A,TEMP2 SWAP A ANL A,#0FH MOV D_BYTE,A ACALL DISPLAY ;调用数码管显示子程序 ACALL SEND ;调用串口显示子程序 SJMP LOOP ;数字量采集 SW_IN: CLR A JNB SW_0,SW1 ORL A,#01H SW1: JNB SW_1,SW2 ORL A,#10H SW2: MOV TEMP1,A CLR A JNB SW_2,SW3 ORL A,#01H SW3: JNB SW_3,SW4 ORL A,#10H SW4: MOV TEMP2,A RET ;串口发送数据子程序 SEND: CLR TI MOV SBUF,TEMP2 JNB TI,$ CLR TI MOV SBUF,TEMP1 JNB TI,$ RET DISPLAY: MOV DPTR,#NUMTAB ;指定查表起始地址 MOV R0,#4 DPL1: MOV R1,#250 ;显示100次 DPLOP: MOV A,A_BYTE ;取个位数 MOVC A,@A+DPTR ;查个位数的7段代码 MOV P0,A ;送出个位的7段代码 SETB PS1 SETB PS2 SETB PS3 CLR PS0 ACALL D1MS ;显示1ms MOV A,B_BYTE ;取十位数 MOVC A,@A+DPTR ;查十位数的7段代码 MOV P0,A ;送出十位的7段代码 SETB PS0 CLR PS1 ACALL D1MS ;显示1ms MOV A,C_BYTE ;取百位数 MOVC A,@A+DPTR ;查百位数的7段代码 MOV P0,A ;送出百位的7段代码 SETB PS1 CLR PS2 ACALL D1MS ;显示1ms MOV A,D_BYTE ;取千位数 MOVC A,@A+DPTR ;查千位数的7段代码 MOV P0,A ;送出千位的7段代码 SETB PS2 CLR PS3 ACALL D1MS ;显示1ms SETB PS3 DJNZ R1,DPLOP DJNZ R0,DPL1 RET D1MS: MOV R7,#80 ;1ms延时 DJNZ R7,$ RET numtab: DB 0FCH,60H,0DAH,0F2H,66H,0B6H,0BEH,0E0H,0FEH,0F6H ;实验板上的7段数码管0~9数字的共阴显示代码 END
1.4.3.3 利用Delphi实现PC与单片机数字量输入
1. 建立工程
启动Delphi 7,选择“File”菜单中的“New”子菜单下的“Application”选项,创建一个新的工程;选择“File”菜单中的“Save All”选项,在弹出的对话框内设置保存位置,分别保存单元文件DDI.pas和工程文件project1.dpr。
2. 窗体设计
(1)设置窗体Form1属性。在对象检视器中,选择Caption属性,设置标题为“PC与单片机通信(电压检测)”。
(2)为了实现计算机之间的串口通信,添加MScomm控件。选择“Component”菜单中的“Import AxtiveX Control控件”,在弹出的对话框中选择“Microsoft Comm Control 6.0(Version1.1)”项,单击“Install”按钮,在弹出的对话框中单击“OK”按钮,所选择的控件就会出现在“ActiveX”组件面板中。
(3)用组件面板以一定的次序为窗体添加组件,放置方式如图1-15所示。
图1-15 程序设计界面
(4)属性设置。组件的主要属性设置如表1-6所示。
表1-6 各组件的主要属性设置
3. 代码实现
程序设计思路:单片机向PC串口发送数字量输入通道状态值,PC读取各通道状态值。
具体实现步骤如下。
(1)在DDI.pas文件中,添加全局变量,具体代码如下:
var Form1: TForm1; datatemp:single; //用于存储电压采样值
(2)在DDI.pas文件中,添加Fom1窗体OnCreate事件的消息响应函数FormCreate(),实现程序初始化,具体代码如下:
procedure TForm1.FormCreate(Sender: TObject); begin MSComm1.CommPort := 1; MSComm1.InputMode := 1; MSComm1.Settings:='9600,n,8,1'; //波特率9600 MSComm1.PortOpen := True; end;
(3)在DDI.pas文件中,添加Timer1组件OnTimer事件的消息响应函数Timer1Timer(),实现周期性读串口输入缓冲区数据,具体代码如下:
procedure TForm1.Timer1Timer(Sender: TObject); var inbyte:array of byte; buffer:string; i:integer; strdata:array [0..10] of string; n,jj:integer; nn:array [0..10] of integer; data:array [0..2] of string; datanum:string; begin inbyte:=MSComm1.input; for i:=0 to 1 do buffer:=buffer+inttohex(inbyte[i],0)+chr(32); //获得单片机传送来的十六进制数(1字节) if buffer<>'' then begin n:=1; for jj:=1 to 8 do begin strdata[jj]:=midstr(trim(buffer),jj,1); //从数据串中逐个取位 if strdata[jj]=''then //判断空格位置 begin nn[n]:=jj; n:=n+1; end; end; data[1]:=trim(midstr(trim(buffer),1,2)); //获得0和1通道的状态值 if length(data[1])=1 then data[1]:='0'+data[1]; data[2]:=trim(midstr(trim(buffer),nn[1]+1,2)); //获得2和3通道的状态值 if length(data[2])=1 then data[2]:='0'+data[2]; datanum:=data[1]+data[2]; //获得0~3通道的状态值 edit1.Text:=Midstr(datanum,1,1); //显示0通道状态值 edit2.Text:=Midstr(datanum,2,1); //显示1通道状态值 edit3.Text:=Midstr(datanum,3,1); //显示2通道状态值 edit4.Text:=Midstr(datanum,4,1); //显示3通道状态值 if edit1.text='1' then //用指示灯显示0通道状态 shape1.Brush.Color:=clRED else shape1.Brush.Color:=clLime; if edit2.text='1' then //用指示灯显示1通道状态 shape2.Brush.Color:=clRED else shape2.Brush.Color:=clLime; if edit3.text='1' then //用指示灯显示2通道状态 shape3.Brush.Color:=clRED else shape3.Brush.Color:=clLime; if edit4.text='1' then //用指示灯显示3通道状态 shape4.Brush.Color:=clRED else shape4.Brush.Color:=clLime; end; end;
(4)在DDI.pas文件中,添加 “退出”按钮相应OnClick事件的消息响应函数Button1Click(),具体代码如下:
procedure TForm1.Button1Click(Sender: TObject); begin MSComm1.PortOpen:=False; //关闭串口 close; end;
4. 运行程序
程序设计、调试完毕,运行程序。
使用杜邦线将单片机开发板B的DI0、DI1、DI2、DI3端口与DGND端口连接或断开产生数字信号0或1,并送到单片机开发板数字量输入端口,在数码管上显示;数字信号同时发送到PC程序画面显示。程序运行界面如图1-16所示。
图1-16 程序运行界面
1.4.4 数字量输出
1.4.4.1 利用Keil C51实现单片机数字量输出
以下是完成单片机数字量输出的C51参考程序。
/****************************************************************** ** 程序功能:接收PC发送的开关指令,驱动继电器动作 ** 晶振频率:11.0592MHz ** 线路→单片机开发板B ****************************************************************************/ #include <REG51.H> /************************开关端口定义****************************************/ sbit sw_0=P3^3; sbit sw_1=P3^4; sbit sw_2=P3^5; sbit sw_3=P3^6; sbit jdq1=P2^0; //继电器1 sbit jdq2=P2^1; //继电器2 void sw_out(unsigned char a); //数字量输出 /**********************************************************************/ void main(void) { unsigned char a=0; TMOD=0x20; //定时器1——方式2 TL1=0xfd; TH1=0xfd; //11.0592MHz晶振,波特率为9600 SCON=0x50; //方式1 TR1=1; //启动定时 while(1) { if(RI) { a=SBUF; RI=0; } sw_out(a); //输出数字量 } } void sw_out(unsigned char a) { if(a==0x00) { jdq1=1; //接收到PC发来的数据00,关闭继电器1和2 jdq2=1; } else if(a==0x01) { jdq1=1; //接收到PC发来的数据01,继电器1关闭,继电器2打开 jdq2=0; } else if(a==0x10) { jdq1=0; //接收到PC发来的数据10,继电器1打开,继电器2关闭 jdq2=1; } else if(a==0x11) { jdq1=0; //接收到PC发来的数据11,打开继电器1和2 jdq2=0; } }
1.4.4.2 利用汇编语言实现单片机数字量输出
以下是完成单片机数字量输出的汇编参考程序。
/****************************************************************** ** 程序功能:接收PC发送的开关指令,驱动继电器动作 ** 晶振频率:11.0592MHz ** 线路→单片机开发板B *******************************************************************/ /****************开关端口定义**************************/ SW_0 BIT P3.3; SW_1 BIT P3.4; SW_2 BIT P3.5; SW_3 BIT P3.6 jdq1 BIT P2.0 ;继电器1 jdq2 BIT P2.1 ;继电器2 TEMP EQU 20H ORG 0000H SJMP MAIN ORG 0030H MAIN: MOV SP,#60H MOV SCON,#50H ;设置成串口1方式 MOV TMOD,#20H ;波特率发生器T1工作在模式2上 MOV TH1,#0FDH ;预置初值(按照波特率9600bps预置初值) MOV TL1,#0FDH ;预置初值(按照波特率9600bps预置初值) ;ORL PCON,#80H ;波特率加倍 SETB TR1 ;启动定时器T1 MOV TEMP,#0 LOOP: JNB RI,LOOP1 MOV TEMP,SBUF CLR RI LOOP1: ACALL SW_OUT ;控制D通道输出电压 SJMP LOOP ;开关两输出程序 SW_OUT: MOV A,TEMP JNB ACC.0,SW1 CLR JDQ2 SJMP SW2 SW1: SETB JDQ2 SJMP SW2 SW2: JNB ACC.4,SW3 CLR JDQ1 SW4: RET SW3: SETB JDQ1 SJMP SW4 END
1.4.4.3 利用Delphi实现PC与单片机数字量输出
1. 建立工程
启动Delphi 7,选择“File”菜单中的“New”子菜单下的“Application”选项,创建一个新的工程;选择“File”菜单中的“Save All”选项,在弹出的对话框内设置保存位置,分别保存单元文件DDO.pas和工程文件project1.dpr。
2. 窗体设计
(1)设置窗体Form1属性。在对象检视器中,选择Caption属性,设置标题为“单片机开关量输出”。
(2)为了实现计算机之间的串口通信,添加MScomm控件。选择“Component”菜单中的“Import AxtiveX Control控件”,在弹出的对话框中选择“Microsoft Comm Control 6.0(Version1.1)”项,单击“Install”按钮,在弹出的对话框中单击“OK”按钮,所选择的控件就会出现在“ActiveX”组件面板中。
(3)用组件面板以一定的次序为窗体添加组件,放置方式如图1-17所示。
图1-17 程序设计界面
(4)属性设置。组件的主要属性设置如表1-7所示。
表1-7 各组件的主要属性设置
3. 代码实现
编写代码主要解决一个问题:如何将设定的开关量状态值(00、01、10、11四种状态,0表示关闭,1表示打开)发送给单片机。
具体实现步骤如下。
(1)在DDO.pas文件中,添加Fom1窗体OnCreate事件的消息响应函数FormCreate(),实现程序初始化,具体代码如下:
procedure TForm1.FormCreate(Sender: TObject); begin MSComm1.CommPort := 1; MSComm1.Settings := '9600,n,8,1'; MSComm1.PortOpen := True; end;
(2)在DDO.pas文件中,添加CheckBox组件OnClick事件的消息响应函数CheckBox1Click (),实现产生通道1的状态值,具体代码如下:
procedure TForm1.CheckBox1Click(Sender: TObject); begin if checkbox1.Checked=false then checkbox1.Caption:='关闭' else checkbox1.Caption:='打开'; edit1.text:=inttostr(ord(checkbox1.checked))+ inttostr(ord(checkbox2.checked)); end;;
(3)在DDO.pas文件中,添加CheckBox组件OnClick事件的消息响应函数CheckBox2Click (),实现产生通道2的状态值,具体代码如下:
procedure TForm1.CheckBox2Click(Sender: TObject); begin if checkbox2.Checked=false then checkbox2.Caption :='关闭' else checkbox2.Caption :='打开'; edit1.Text:=inttostr(ord(checkbox1.checked))+ inttostr(ord(checkbox2.checked)); end;
(4)在DDO.pas文件中,添加“输出”按钮相应OnClick事件的消息响应函数Button3Click(),具体代码如下:
procedure TForm1.Button3Click(Sender: TObject); var uout:variant; begin uout:='$'+ edit1.text; MSComm1.Output:=Chr(strtoint(uout)); //将十六进制状态值发送给单片机 end;
(5)在DDO.pas文件中,添加 “关闭”按钮相应OnClick事件的消息响应函数Button4Click(),具体代码如下:
procedure TForm1.Button4Click(Sender: TObject); begin MSComm1.PortOpen:=false; close; end;
4. 运行程序
程序设计、调试完毕,运行程序。
PC发出开关指令(0或1)传送给单片机开发板,驱动相应的继电器动作。
PC发送数据00,单片机继电器1和2关闭;PC发送数据01,单片机继电器1关闭,继电器2打开;PC发送数据10,单片机继电器1打开,继电器2关闭;PC发送数据11,单片机继电器1和2打开。程序运行界面如图1-18所示。
图1-18 程序运行界面