1.2 数据采集与控制程序设计
1.2.1 模拟量输入
1.2.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-4所示。
图1-4 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;aaaaaaaaaaaaaaaaaaaaaaaaaaa 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; //继电器2复位 } } /**************************数码管显示函数**************************/ /*函数原型: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-5所示。
图1-5 单片机烧写程序
烧写程序步骤如下。
连接单片机开发板与PC后,实验板电源先不要接通。
接着按下面步骤完成单片机程序的在线下载:
第一步:选择要下载程序的单片机型号。
第二步:打开编译完成要下载到单片机中扩展名为HEX的文件。
第三步:选择与实验板连接的串口。
第四步:选择合适的通信波特率(这步可以省略)。
第五步:将该选项选中(选中的目的是便于程序的调试,若下载的是调试通过的程序,此步可省略)。
第六步:单击该按钮。
第七步:接通实验板电源。
几秒后就可以将程序下载到单片机并运行了。
程序烧写进单片机之后,就可以给单片机开发板通电了,这时数码管上将会显示检测的电压值。
5.串口通信调试
在进行串口开发之前,一般要进行串口调试,经常使用的工具是“串口调试助手”程序。它是一个适用于Windows平台的串口监视、串口调试程序。它可以在线设置各种通信速率、通信端口等参数;既可以发送字符串命令,也可以发送文件;可以设置自动发送/手动发送方式;可以十六进制显示接收到的数据等。从而提高串口开发效率。
打开“串口调试助手”程序(ScomAssistant.exe),首先设置串口号为COM1、波特率为9600、校验位为NONE、数据位为8、停止位为1(注意:设置的参数必须与单片机设置的一致),选择“十六进制显示”和“十六进制发送”,打开串口,如图1-6所示。
图1-6 串口调试助手
如果PC与单片机开发板串口连接正确,则单片机连续向PC发送检测的电压值,用1个字节的十六进制数据表示,如0F,该数据串在返回信息框内显示。
将单片机返回数据转换为十进制,并除以10,即可知当前电压测量值为1.5V。
1.2.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 ;数码管百位 ;进行温度显示,这里我们考虑用两位数码管来显示温度 ;显示范围为0~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 ;预置初值(按照波特率9600b/s预置初值) MOV TL1,#0FDH ;预置初值(按照波特率9600b/s预置初值) ;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 ;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 ;单字节无符号数乘法程序 (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.2.1.3 利用LabVIEW实现PC与单片机模拟电压输入
启动LabVIEW8.2中文版,建立一个新VI程序。
1.程序前面板设计
(1)添加1个数字显示控件:控件(Controls)→新式(Modern)→数值(Numeric)→数值显示控件(Numeric Indicator),将标签改为“测量值”。
(2)添加1个仪表显示控件:控件(Controls)→新式(Modern)→数值(Numeric)→仪表(Meter),将标签改为“仪表”。
(3)添加1个实时图形显示控件:控件(Controls)→新式(Modern)→图形(Graph)→波形图(Waveform Chart),将标签改为“实时曲线”。
(4)添加1个串口资源检测控件:控件(Controls)→新式(Modern)→I/O →VISA资源名称(VISA Resource Name);单击控件箭头,选择串口号,如COM1或ASRL1:。
设计的程序前面板如图1-7所示。
图1-7 程序前面板
2.框图程序设计——添加函数与连线
程序设计思路:读单片机发送给PC的十六进制数据,并转换成十进制。
1)添加1个顺序结构
选择编程(Programming)→结构(Structure)→层叠式顺序结构(Stacked Sequence)。
将顺序结构的帧(Frame)设置为3个(序号0~2)。设置方法:选中顺序结构(Sequence),单击右键,执行“在后面添加帧”(Add Frame After)选项2次。
2)在顺序结构Frame 0中添加函数与结构
(1)在顺序结构Frame 0中添加1个串口配置函数:编程(Programming)→仪器I/O(Instrument I/O)→串口(Serial)→VISA配置串口(VISA Configure Serial Port)。
(2)在顺序结构Frame 0中添加4个数值常量:编程(Programming)→数值(Numeric)→数值常量(Numeric Constant),值分别为9600(波特率)、8(数据位)、0(校验位,无)、1(停止位)。
(3)将函数VISA资源名称(VISA Resource Name)的输出端口与串口配置(VISA Configure Serial Port)函数的输入端口VISA资源名称(VISA Resource Name)相连。
(4)将数值常量9600、8、0、1分别与VISA配置串口(VISA Configure Serial Port)函数的输入端口波特率(baud rate)、数据位(data bits)、奇偶(parity)、停止位(stop bits)相连。
连接好的框图程序如图1-8所示。
图1-8 串口初始化程序
3)在顺序结构Frame 1中添加4个函数
(1)添加1个串口字节数函数:编程(Programming)→仪器I/O(Instrument I/O)→串口(Serial)→VISA串口字节数(VISA Bytes at Serial Port),标签为“Property Node”。
(2)添加1个串口读取函数:编程(Programming)→仪器I/O(Instrument I/O)→串口(Serial)→VISA读取(VISA Read)。
(3)添加字符串转字节数组函数:编程(Programming)→字符串(String)→转换(Conversion)→字符串至字节数组转换(String To Byte Array)。
(4)添加1个索引数组(Index Array)函数:编程(Programming)→数组(Array)→索引数组(Index Array)。
(5)添加1个乘号函数:编程(Programming)→数值(Numeric)→乘(Multiply)。
(6)添加2个数值常量:编程(Programming)→数值(Numeric)→数值常量(Numeric Constant),值分别为1和0.1。
(7)将VISA资源名称(VISA Resource Name)函数的输出端口与串口字节数(VISA Bytes at Serial Port)函数(顺序结构Frame1中)的输入端口引用(reference)相连。
(8)将VISA资源名称(VISA Resource Name)函数的输出端口与串口读取(VISA Read)函数的输入端口VISA资源名称(VISA Resource Name)相连。
(9)将串口字节数(VISA Bytes at Serial Port)函数的输出端口Number of bytes at Serial Port与串口读取(VISA Read)函数的输入端口字节总数(byte count)相连。
(10)将VISA读取(VISA Read)函数的输出端口读取缓冲区(read buffer)与字符串至字节数组转换(String To Byte Array)函数的输入端口字符串(string)相连。
(11)将字符串至字节数组转换(String To Byte Array)函数的输出端口无符号字节数组(unsigned byte array)与索引数组(Index Array)函数的输入端口数组(Array)相连。
(12)将数值常量(值为1)与索引数组(Index Array)函数的输入端口索引(Index)相连。
(13)将索引数组(Index Array)函数的输出端口元素(element)与乘(Multiply)函数的输入端口x相连。
(14)将数值常量(值为0.1)与乘(Multiply)函数的输入端口y相连。
(15)将乘(Multiply)函数的输出端口x*y分别与测量数据显示图标(标签为“测量值”)、仪表控件图标(标签为“仪表”)、实时曲线控件图标(标签为“实时曲线”)相连。
连接好的框图程序如图1-9所示。
图1-9 读电压值程序
4)在顺序结构Frame 2中添加1个时间延迟函数
选择编程(Programming)→定时(Timing) →时间延迟(Time Delay),时间设置为2s,如图1-10所示。
图1-10 延时程序
3.运行程序
单击快捷工具栏中的“连续运行”按钮,运行程序。
单片机开发板接收变化的模拟电压(0~5V)并在数码管上显示(保留1位小数);PC接收单片机发送的电压值(十六进制,1个字节),转换成十进制形式,以数字、曲线的方式显示。
程序运行界面如图1-11所示。
图1-11 程序运行界面
1.2.1.4 利用LabWindows/CVI实现PC与单片机模拟电压输入
1.建立新工程
运行NI LabWindows CVI 9.0程序,LabWindows/CVI打开后,出现一个空的项目(Project)窗口。
2.程序界面设计
1)创建用户界面
从Project窗口中选择“File→New→User Interface(*.uir)”,创建一个用户界面文件(*.uir)。
2)创建控件
根据程序界面要求,在Untitled Panel窗口中创建所需控件。
3.属性设置
设置面板、控件的属性,完成要求的用户界面,如图1-12所示。
图1-12 用户界面
设置属性时,双击面板空白处、相应控件,弹出相应属性设置对话框,在相应的属性栏中选择或修改属性值。
本程序面板、控件的主要属性设置见表1-1。
表1-1 面板、控件的主要属性设置
4.程序代码设计
生成源代码框架,添加、修改所需代码。完整的源程序代码如下:
//程序功能:接收单片机发送的电压值(十六进制,1个字节),转换成十进制形式,以数字、曲线的方式显示 #include <rs232.h> #include <cvirte.h> #include <userint.h> #include "AI.h" static int panelHandle; unsigned char datatemp[1000]={00}; int num=0; //主程序 int main (int argc, char *argv[]) { if (InitCVIRTE (0, argv, 0) == 0) return -1; /out of memory */ if ((panelHandle = LoadPanel (0, "AI.uir", PANEL)) < 0) return -1; //打开并初始化串口COM1 OpenComConfig (1, "", 9600, 0, 8, 1, 512, 512); FlushInQ (1); FlushOutQ (1); DisplayPanel (panelHandle); RunUserInterface (); DiscardPanel (panelHandle); CloseCom (1); //退出程序时关闭串口 return 0; } //周期性读串口输入缓冲区数据 int CVICALLBACK TIMER (int panel, int control, int event,void *callbackData, int eventData1, int eventData2) { int i=0; int len=0; unsigned char buf[100]={00}; switch (event) { case EVENT_TIMER_TICK: len=GetInQLen (1); if (len) { //读模拟量 ComRd(1,buf,len); //显示当前值 SetCtrlVal (panelHandle,PANEL_NUMERIC_1 ,buf[len-1]); SetCtrlVal (panelHandle,PANEL_NUMERIC_2 ,(float)buf[len- 1]/10); //绘曲线 for(0;i<len;i++) { PlotStripChartPoint (panelHandle,PANEL_STRIPCHART,(float)buf[len-1] /10); i=i+1; //下一个数值 } for(0;i<len;i++) { buf[i]=0; i++; } } FlushInQ (1); FlushOutQ (1); break; } return 0; } //当退出程序时,关闭串行口 int CVICALLBACK CMD_CLOSE (int panel, int control, int event,void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: QuitUserInterface (0); CloseCom (1); //退出程序时关闭串口 break; } return 0; }
5.运行程序
程序设计、调试完毕,运行程序。
单片机开发板接收变化的模拟电压(0~5V)并在数码管上显示(保留1位小数);PC接收单片机发送的电压值(十六进制,1个字节),转换成十进制形式,以数字、曲线的方式显示。
程序运行界面如图1-13所示。
图1-13 程序运行界面
1.2.2 模拟量输出
1.2.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晶振,波特率为9600b/s 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.2.2.2 利用汇编语言实现单片机模拟电压输出
以下是完成单片机模拟电压输出的汇编参考程序:
/****************************************************************** TLC5620 DAC转换实验程序 程序功能:PC向单片机发送数值(0~5),如发送2.5V,则发送25的十六进制值19,单片机接收并显示2.5,并从模拟量输出通道输出 *晶振频率:11.0592MHz ******************************************************************* ; 输出电压计算公式: 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 ;预置初值(按照波特率9600b/s预置初值) MOV TL1,#0FDH ;预置初值(按照波特率9600b/s预置初值) ;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 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.2.2.3 利用LabVIEW实现PC与单片机模拟电压输出
启动LabVIEW 8.2中文版,建立一个新VI程序。
1.程序前面板设计
(1)添加1个数值输入控件:控件(Controls)→数值(Numeric)→数值输入控件(Numeric),将标签改为“输出电压值”。
(2)添加1个“输出”按钮控件:控件(Controls)→新式(Modern)→布尔(Boolean)→输出按钮(输出Button)。
(3)添加1个串口资源检测控件:控件(Controls)→新式(Modern)→I/O →VISA资源名称(VISA Resource Name);单击控件箭头,选择串口号,如ASRL1:或COM1。
设计的程序前面板如图1-14所示。
图1-14 程序前面板
2.框图程序设计——添加函数与连线
主要解决1个问题:如何将设定的数值发送给单片机?
1)添加1个顺序结构
选择编程(Programming) →结构(Structure) →层叠式顺序结构(Stacked Sequence)。
将顺序结构的帧(Frame)设置为2个(序号0~1)。设置方法:选中顺序结构(Sequence),单击右键,执行“在后面添加帧”(Add Frame After)选项1次。
2)在顺序结构Frame 0中添加函数与结构
(1)在顺序结构Frame 0中添加1个串口配置函数:编程(Programming)→仪器I/O(Instrument I/O)→串口(Serial)→VISA配置串口(VISA Configure Serial Port)。
(2)在顺序结构Frame 0中添加4个数值常量:编程(Programming)→数值(Numeric)→数值常量(Numeric Constant),值分别为9600(波特率)、8(数据位)、0(校验位,无)、1(停止位)。
(3)将函数VISA资源名称(VISA Resource Name)的输出端口与串口配置(VISA Configure Serial Port)函数的输入端口VISA资源名称(VISA Resource Name)相连。
(4)将数值常量9600、8、0、1分别与VISA配置串口(VISA Configure Serial Port)函数的输入端口波特率(baud rate)、数据位(data bits)、奇偶(parity)、停止位(stop bits)相连。
连接好的框图程序如图1-15所示。
图1-15 初始化串口框图程序
3)在顺序结构Frame 1中添加1个条件结构
选择编程(Programming)→结构(Structures)→条件结构(Case Structure)。
4)在条件结构的“真”选项中添加函数与结构
(1)添加1个数值常量:编程(Programming)→数值(Numeric)→数值常量(Numeric Constant),值分别为10。
(2)添加1个乘号函数:编程(Programming)→数值(Numeric)→乘(Multiply)。
(3)添加1个“创建数组”函数:编程(Programming)→数组(Array)→创建数组(Create Array)。
(4)添加字节数组转字符串函数:编程(Programming) →字符串(String)→字符串/数组转换(Conversion)→字节数组至字符串转换(Byte Array To String)。
(5)添加1个串口写入函数:编程(Programming)→仪器I/O(Instrument I/O)→串口(Serial)→VISA写入(VISA Write)。
(6)将“确定按钮”控件与选择结构的条件端口相连。
(7)将数值输入控件(标签为“输出电压值”)与乘号(Multiply)函数的输入端口x相连。
(8)将数值常量(值为10)与乘号(Multiply)函数的输入端口y相连。
(9)将乘号(Multiply)函数的输出端口x*y与创建数组(Create Array)函数的输入端口“元素”相连。
(10)将创建数组(Create Array)函数的输出端口“添加的数组”与字节数组至字符串转换函数(Byte Array To String)的输入端口“无符号字节数组”相连。
(11)将字节数组至字符串转换(Byte Array To String)函数的输出端口字符串(string)与VISA写入(VISA Write)函数的输入端口写入缓冲区(write buffer)相连。
(12)将函数VISA资源名称(VISA Resource Name)的输出端口与串口写入函数(VISA Write)的输入端口VISA资源名称(VISA Resource Name)相连。
连接好的框图程序如图1-16所示。
图1-16 输出电压框图程序
3.运行程序
单击快捷工具栏上的“连续运行”按钮,运行程序。
在PC程序中输入变化的数值(0~5),单击“输出”按钮,发送到单片机开发板,在数码管上显示(保留1位小数),并通过模拟电压输出端口输出同样大小的电压值。可使用万用表直接测量单片机开发板B的AO0,AO1,AO2,AO3端口与GND端口之间的输出电压。
程序运行界面如图1-17所示。
图1-17 程序运行界面
1.2.2.4 利用LabWindows/CVI实现PC与单片机模拟电压输出
1.建立新工程
运行NI LabWindows CVI 9.0程序,LabWindows/CVI打开后,出现一个空的项目(Project)窗口。
2.程序界面设计
1)创建用户界面
从Project窗口中选择“File→New→User Interface(*.uir)”,创建一个用户界面文件(*.uir)。
2)创建控件
根据程序界面要求,在Untitled Panel窗口中创建所需控件。
3.属性设置
设置面板、控件的属性,完成要求的用户界面,如图1-18所示。
图1-18 用户界面
设置属性时,双击面板空白处、相应控件,弹出相应属性设置对话框,在相应的属性栏中选择或修改属性值。
程序面板、控件的主要属性设置见表1-2。
表1-2 面板、控件的主要属性设置
4.程序代码设计
主要解决1个问题:如何将设定的数值发送给单片机?
#include <rs232.h> #include <cvirte.h> #include <userint.h> #include "AO.h" static int panelHandle; int main (int argc, char *argv[]) { if (InitCVIRTE (0, argv, 0) == 0) return -1; /out of memory */ if ((panelHandle = LoadPanel (0, "AO.uir", PANEL)) < 0) return -1; //打开并初始化串口COM1 OpenComConfig (1, "", 9600, 0, 8, 1, 512, 512); FlushInQ (1); FlushOutQ (1); DisplayPanel (panelHandle); RunUserInterface (); DiscardPanel (panelHandle); CloseCom (1); //退出程序时关闭串口 return 0; } //电压输出 int CVICALLBACK CMD_OUT (int panel, int control, int event,void *callbackData, int eventData1, int eventData2) { unsigned char temp=0x00; double val_AO=0; switch (event) { case EVENT_COMMIT: GetCtrlVal (panelHandle,PANEL_NUMERIC,&val_AO); temp=val_AO*10; //将输入的电压值乘10后转换成十六进制 ComWrtByte (1,temp); //将十六进制电压值发送给单片机 break; } return 0; } int CVICALLBACK CMD_CLOSE (int panel, int control, int event,void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: QuitUserInterface (0); CloseCom (1); //退出程序时关闭串口 break; } return 0; }
5.运行程序
程序设计、调试完毕,运行程序。
在PC程序中输入变化的数值(0~10),单击“输出”按钮,发送到单片机开发板,在数码管上显示(保留1位小数),并通过模拟电压输出端口输出同样大小的电压值。可使用万用表直接测量单片机开发板B的AO0,AO1,AO2,AO3端口与GND端口之间的输出电压。程序运行界面如图1-19所示。
图1-19 程序运行界面
1.2.3 数字量输入
1.2.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;aaaaaaaaaaaaaaaaaaaaaaaaaaa TMOD=0x20; //定时器1—方式2 TL1=0xfd; TH1=0xfd; //11.0592MHz晶振,波特率为9600b/s 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]; //显示个位 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.2.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 ;预置初值(按照波特率9600b/s预置初值) MOV TL1,#0FDH ;预置初值(按照波特率9600b/s预置初值) ;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.2.3.3 利用LabVIEW实现PC与单片机数字量输入
启动LabVIEW 8.2中文版,建立一个新VI程序。
1.程序前面板设计
(1)添加4个指示灯控件:控件(Controls)→新式(Modern)→布尔(Boolean)→圆形指示灯(Round LED),将标签分别改为DI0~DI3。
(2)添加1个字符串显示控件:控件(Controls)→新式(Modern)→字符串与路径(String&Path)→字符串显示控件(String Indicator),标签改为“数字量输入状态:”。右键单击该控件,选择“十六进制显示”。
(3)添加1个串口资源检测控件:控件(Controls)→新式(Modern)→I/O →VISA资源名称(VISA Resource Name);单击控件箭头,选择串口号,如“ASRL1:”或“COM1”。
设计的程序前面板如图1-20所示。
图1-20 程序前面板
2.框图程序设计——添加函数与连线
程序设计思路:单片机向PC串口发送数字量输入通道状态值,PC读各通道状态值。
1)添加1个顺序结构
选择编程(Programming) →结构(Structure) →层叠式顺序结构(Stacked Sequence)。
将顺序结构的帧(Frame)设置为3个(序号0~2)。设置方法:选中顺序结构(Sequence),单击右键,执行“在后面添加帧”(Add Frame After)选项2次。
2)在顺序结构Frame 0中添加函数与结构
(1)在顺序结构Frame 0中添加1个串口配置函数:编程(Programming)→仪器I/O(Instrument I/O)→串口(Serial)→VISA配置串口(VISA Configure Serial Port)。
(2)在顺序结构Frame 0中添加4个数值常量:编程(Programming)→数值(Numeric)→数值常量(Numeric Constant),值分别为9600(波特率)、8(数据位)、0(校验位,无)、1(停止位)。
(3)将函数VISA资源名称(VISA Resource Name)的输出端口与串口配置(VISA Configure Serial Port)函数的输入端口VISA资源名称(VISA Resource Name)相连。
(4)将数值常量9600、8、0、1分别与VISA配置串口(VISA Configure Serial Port)函数的输入端口波特率(baud rate)、数据位(data bits)、奇偶(parity)、停止位(stop bits)相连。
连接好的框图程序如图1-21所示。
图1-21 初始化串口
3)在顺序结构Frame 1中添加函数与结构
(1)添加1个串口字节数函数:编程(Programming)→仪器I/O(Instrument I/O)→串口(Serial)→VISA串口字节数(VISA Bytes at Serial Port),标签为“Property Node”。
(2)添加1个串口读取函数:编程(Programming)→仪器I/O(Instrument I/O)→串口(Serial)→VISA读取(VISA Read)。
(3)添加1个扫描值函数:编程(Programming)→字符串(String)→字符串/数值转换→扫描值。
(4)添加1个数值常量:编程(Programming)→数值(Numeric)→数值常量(Numeric Constant),值为0。
(5)添加1个字符串常量:编程(Programming)→字符串(String)→字符串常量(String Constant),值为“%b”,表示输入的是二进制数据。
(6)添加1个强制类型转换函数:编程(Programming)→数值→数据操作→强制类型转换。
(7)添加2个比较函数:编程(Programming)→比较→“等于?”。
(8)添加2个字符串常量:编程(Programming)→字符串(String)→字符串常量(String Constant),右键单击这2个字符串常量,选择“十六进制显示”,将值改为“1111”和“1100”。
(9)添加2个条件结构:编程(Programming)→结构(Structures)→条件结构(Case Structure)。
(10)在左边条件结构“真”选项中,添加4个真常量;在右边条件结构“真”选项中,添加2个真常量和2个假常量:编程(Programming)→布尔→真常量或假常量。
(11)将4个指示灯控件移入左边的条件结构“真”选项中,在右边条件结构“真”选项中,添加4个局部变量,右键单击局部变量,“选择项”分别选DI0,DI1,DI2和DI3。
(12)将VISA资源名称(VISA Resource Name)函数的输出端口与串口字节数(VISA Bytes at Serial Port)函数(顺序结构Frame1中)的输入端口引用(Reference)相连。
(13)将串口字节数(VISA Bytes at Serial Port)函数的输出端口VISA资源名称(VISA Resource Name)与串口读取(VISA Read)函数的输入端口VISA资源名称(VISA Resource Name)相连。
(14)将串口字节数(VISA Bytes at Serial Port)函数的输出端口Number of Bytes at Serial Port与串口读取(VISA Read)函数的输入端口字节总数(Byte Count)相连。
(15)将串口读取(VISA Read)函数的输出端口读取缓冲区(Read Buffer)与扫描值函数的输入端口字符串(String)相连。
(16)将字符串常量(值为“%b”)与扫描值函数的输入端口“格式字符串”相连。
(17)将扫描值函数的输出端口“输出字符串”与强制类型转换函数的输入端口x相连。
(18)将强制类型转换函数的输出端口分别与2个比较函数“=”的输入端口x相连;并与“数字量输入状态:”显示控件相连。
(19)将2个字符串常量“1111”和“1100”分别与2个比较函数“=”的输入端口y相连。
(20)将2个比较函数“=”的输出端口“x=y?”分别与条件结构的选择端口?相连。
(21)在条件结构中,将真常量与假常量分别与指示灯控件及其局部变量相连。
连接好的框图程序如图1-22所示。
图1-22 接收返回信息
4)在顺序结构Frame2中添加1个时间延迟函数
选择编程(Programming)→定时(Timing)→时间延迟(Time Delay),时间采用默认值,如图1-23所示。
图1-23 延时
3.运行程序
单击快捷工具栏上的“连续运行”按钮,运行程序。
使用杜邦线将单片机开发板B的DI0、DI1、DI2、DI3端口与DGND端口连接或断开产生数字信号0或1,并送到单片机开发板数字量输入端口,在数码管上显示;数字信号同时发送到PC程序画面显示。
程序运行界面如图1-24所示。
图1-24 程序运行界面
1.2.3.4 利用LabWindows/CVI实现PC与单片机数字量输入
1.建立新工程
运行NI LabWindows CVI 9.0程序,出现一个空的项目(Project)窗口。
2.程序界面设计
1)创建用户界面
从Project窗口中选择“File→New→User Interface(*.uir)”,创建一个用户界面文件(*.uir)。
2)创建控件
根据程序界面要求,在Untitled Panel窗口中创建所需控件。
3.属性设置
设置面板、控件的属性,完成要求的用户界面,如图1-25所示。
图1-25 用户界面
设置属性时,双击面板空白处、相应控件,弹出相应属性设置对话框,在相应的属性栏中选择或修改属性值。
程序面板、控件的主要属性设置见表1-3。
表1-3 面板、控件的主要属性设置
4.程序代码设计
程序设计思路:单片机向PC串口发送数字量输入通道状态值,PC读各通道状态值。
#include <rs232.h> #include <cvirte.h> #include <userint.h> #include "DI.h" static int panelHandle; int main (int argc, char *argv[]) { if (InitCVIRTE (0, argv, 0) == 0) return -1; /out of memory */ if ((panelHandle = LoadPanel (0, "DI.uir", PANEL)) < 0) return -1; //打开并初始化串口COM1 OpenComConfig (1, "", 9600, 0, 8, 1, 512, 512); FlushInQ (1); FlushOutQ (1); DisplayPanel (panelHandle); RunUserInterface (); DiscardPanel (panelHandle); CloseCom (1); //退出程序时关闭串口 return 0; } //退出程序 int CVICALLBACK CLOSE (int panel, int control, int event,void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: QuitUserInterface (0); CloseCom (1); //退出程序时关闭串口 break; } return 0; } //周期查询函数 int CVICALLBACK TIMER (int panel, int control, int event,void *callbackData, int eventData1, int eventData2) { int len=0; int n1=0; int n2=0; int n3=0; int n4=0; unsigned char temp[2]={00}; switch (event) { case EVENT_TIMER_TICK: len=GetInQLen (1); if (len) { ComRd(1,temp,len); n4=temp[0]/16; n3=temp[0]%16; n2=temp[1]/16; n1=temp[1]%16; if(n1<=1&n2<=1&n3<=1&n4<=1) { SetCtrlVal (panelHandle,PANEL_NUMERIC_1,n1); SetCtrlVal (panelHandle,PANEL_NUMERIC_2,n2); SetCtrlVal (panelHandle,PANEL_NUMERIC_3,n3); SetCtrlVal (panelHandle,PANEL_NUMERIC_4,n4); SetCtrlVal (panelHandle,PANEL_LED_1,n1); SetCtrlVal (panelHandle,PANEL_LED_2,n2); SetCtrlVal (panelHandle,PANEL_LED_3,n3); SetCtrlVal (panelHandle,PANEL_LED_4,n4); } } FlushInQ (1); FlushOutQ (1); break; } return 0; }
5.运行程序
程序设计、调试完毕,运行程序。
使用杜邦线将单片机开发板B的DI0、DI1、DI2、DI3端口与DGND端口连接或断开产生数字信号0或1,并送到单片机开发板数字量输入端口,在数码管上显示。数字信号同时发送到PC程序画面显示。
程序运行界面如图1-26所示。
图1-26 程序运行界面
1.2.4 数字量输出
1.2.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;aaaaaaa TMOD=0x20; //定时器1—方式2 TL1=0xfd; TH1=0xfd; //11.0592MHz晶振,波特率为9600b/s 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.2.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 ;预置初值(按照波特率9600b/s预置初值) MOV TL1,#0FDH ;预置初值(按照波特率9600b/s预置初值) ;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.2.4.3 利用LabVIEW实现PC与单片机数字量输出
启动LabVIEW 8.2中文版,建立一个新VI程序。
1.程序前面板设计
(1)添加2个字符串显示控件:控件(Controls)→新式(Modern)→字符串(String)→字符串显示控件(String Indicator),将标签改为“开关1状态”和“开关2状态”。
(2)添加2个“垂直摇杆开关”控件:控件(Controls)→新式(Modern)→布尔(Boolean)→垂直摇杆开关,将标签改为“开关1”和“开关2”。
(3)添加1个“输出”按钮控件:控件(Controls)→新式(Modern)→布尔(Boolean)→确定按钮(OK Button)。
(4)添加1个串口资源检测控件:控件(Controls)→新式(Modern)→I/O →VISA资源名称(VISA Resource Name);单击控件箭头,选择串口号,如“ASRL1:”或“COM1”。
设计的程序前面板如图1-27所示。
图1-27 程序前面板
2.框图程序设计——添加函数与连线
主要解决1个问题:如何将设定的开关量状态值(00,01,10,11四种状态,0表示关闭,1表示打开)发送给单片机?
(1)添加1个顺序结构。选择编程(Programming)→结构(Structure)→层叠式顺序结构(Stacked Sequence)。将顺序结构的帧(Frame)设置为2个(序号0-1)。设置方法:选中顺序结构(Sequence),单击右键,执行“在后面添加帧”(Add Frame After)选项1次。
(2)在顺序结构Frame 0中添加函数与结构:
① 在顺序结构Frame 0中添加1个串口配置函数:编程(Programming)→仪器I/O(Instrument I/O)→串口(Serial)→VISA配置串口(VISA Configure Serial Port)。
② 在顺序结构Frame 0中添加4个数值常量:编程(Programming)→数值(Numeric)→数值常量(Numeric Constant),值分别为9600(波特率)、8(数据位)、0(校验位,无)、1(停止位)。
③ 将函数VISA资源名称(VISA Resource Name)的输出端口与串口配置(VISA Configure Serial Port)函数的输入端口VISA资源名称(VISA Resource Name)相连。
④ 将数值常量9600、8、0、1分别与VISA配置串口(VISA Configure Serial Port)函数的输入端口波特率(baud rate)、数据位(data bits)、奇偶(parity)、停止位(stop bits)相连。连接好的框图程序如图1-28所示。
图1-28 初始化串口框图程序
(3)在顺序结构Frame 1中添加3个条件结构:编程(Programming)→结构(Structures)→条件结构(Case Structure)。
(4)在顺序结构Frame 1中添加1个连接字符串函数:编程(Programming)→字符串(String)→连接字符串(Concatenate String)。
(5)在顺序结构Frame 1中添加1个字符串转换函数:编程(Programming)→字符串/数值转换→十六进制数字符串至数值转换。
(6)在顺序结构Frame 1 中添加1个创建数组函数:编程(Programming)→数组(Array)→创建数组(Create Array)。
(7)在顺序结构Frame 1中添加字节数组转字符串函数:编程(Programming) →字符串(String)→字符串/数组转换(Conversion)→字节数组至字符串转换(Byte Array To String)。
(8)在右边条件结构的“真”选项中添加1个串口写入函数:编程(Programming)→仪器I/O(Instrument I/O)→串口(Serial)→VISA写入(VISA Write)。
(9)在左边2个条件结构的“真”选项中各添加1个字符串常量:编程(Programming)→字符串(String)→字符串常量(String Constant),值分别为1。
(10)在左边2个条件结构的“假”选项中各添加1个字符串常量:编程(Programming)→字符串(String)→字符串常量(String Constant),值分别为0。
(11)在左边2个条件结构的“假”选项中各添加1个局部变量:编程(Programming)→结构(Structures)→局部变量(Local Variable)。
分别选择局部变量,单击鼠标右键,在弹出菜单的选项(Select Item)下,为局部变量选择控件:“开关1状态”和“开关2状态”,选择写属性。
(12)将“开关1”控件、“开关2”控件、“确定按钮”控件分别与条件结构的条件端口相连。
(13)在左边2个条件结构的“真”选项中,分别将字符串常量“1”和“开关1状态”控件、“开关2状态”控件相连,再与连接字符串函数的输入端口“字符串”相连。
(14)在左边2个条件结构的“假”选项中,分别将字符串常量“0”和“开关1状态”局部变量、“开关2状态”局部变量相连,再与连接字符串函数的输入端口“字符串”相连。
(15)将连接字符串函数的输出端口“连接的字符串”与十六进制数字符串至数值转换函数的输入端口“字符串”相连。
(16)将十六进制数字符串至数值转换函数的输出端口“数字”与创建数组函数的输入端口“元素”相连。
(17)将创建数组(Create Array)函数的输出端口“添加的数组”与字节数组至字符串转换函数(Byte Array To String)的输入端口“无符号字节数组”相连。
(18)将字节数组至字符串转换(Byte Array To String)函数的输出端口字符串(String)与VISA写入(VISA Write)函数的输入端口写入缓冲区(Write Buffer)相连。
(19)将函数VISA资源名称(VISA Resource Name)的输出端口与串口写入函数(VISA Write)的输入端口VISA资源名称(VISA Resource Name)相连。
连接好的框图程序如图1-29和图1-30所示。
图1-29 输出开关信号框图程序1
图1-30 输出开关信号框图程序2
3.运行程序
单击快捷工具栏上的“连续运行”按钮,运行程序。
PC发出开关指令(00、01、10、11四种状态,0表示关闭,1表示打开)传送给单片机开发板,驱动相应的继电器动作打开或关闭。
程序运行界面如图1-31所示。
图1-31 程序运行界面
1.2.4.4 利用LabWindows/CVI实现PC与单片机数字量输出
1.建立新工程
运行NI LabWindows CVI 9.0程序,LabWindows/CVI打开后,出现一个空的项目(Project)窗口。
2.程序界面设计
1)创建用户界面
从Project窗口中选择“File→New→User Interface(*.uir)”,创建一个用户界面文件(*.uir)。
2)创建控件
根据程序界面要求,在Untitled Panel窗口中创建所需控件。
3.属性设置
设置面板、控件的属性,完成要求的用户界面,如图1-32所示。
图1-32 用户界面
设置属性时,双击面板空白处、相应控件,弹出相应属性设置对话框,在相应的属性栏中选择或修改属性值。
程序面板、控件的主要属性设置见表1-4。
表1-4 面板、控件的主要属性设置
4.程序代码设计
主要解决1个问题:如何将设定的开关量状态值(00、01、10、11四种状态,0表示关闭,1表示打开)发送给单片机?
#include <cvirte.h> #include <userint.h> #include "DO.h" #include <rs232.h> /* 'PC发送数据00,单片机继电器1和2关闭 'PC发送数据01,单片机继电器1关闭,继电器2打开 'PC发送数据10,单片机继电器1打开,继电器2关闭 'PC发送数据11,单片机继电器1和2打开 */ int k1=0; //开关1 int k2=0; //开关2 unsigned char temp=0x00; //输出缓冲 char kk[10]={0}; //状态显示 static int panelHandle; //主程序 int main (int argc, char *argv[]) { if (InitCVIRTE (0, argv, 0) == 0) return -1; /out of memory */ if ((panelHandle = LoadPanel (0, "DO.uir", PANEL)) < 0) return -1; //打开并初始化串口COM1 OpenComConfig (1, "", 9600, 0, 8, 1, 512, 512); FlushInQ (1); FlushOutQ (1); DisplayPanel (panelHandle); RunUserInterface (); DiscardPanel (panelHandle); CloseCom (1); //退出程序时关闭串口 return 0; } //退出程序 int CVICALLBACK CMD_CLOSE (int panel, int control, int event,void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: QuitUserInterface (0); CloseCom (1); //退出程序时关闭串口 break; } return 0; } //发送十六进制状态值到单片机 int CVICALLBACK CMD_OUT (int panel, int control, int event,void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: ComWrtByte (1,temp); break; } return 0; } //取开关状态 int CVICALLBACK CMD_SWITCH (int panel, int control, int event,void *callbackData, int eventData1, int eventData2) { switch (event) { case EVENT_COMMIT: GetCtrlAttribute (panelHandle,PANEL_SWITCH_1, ATTR_CTRL_VAL,&k1); GetCtrlAttribute (panelHandle,PANEL_SWITCH_2, ATTR_CTRL_VAL,&k2); temp=k1*16+k2; kk[0]=k1+48; kk[1]=k2+48; kk[2]=0; SetCtrlVal (panelHandle,PANEL_STRING,kk); break; } return 0; }
5.运行程序
程序设计、调试完毕,运行程序。
PC发出开关指令(0或1)传送给单片机开发板,驱动相应的继电器动作。
PC发送数据00,单片机继电器1和2关闭;PC发送数据01,单片机继电器1关闭,继电器2打开;PC发送数据10,单片机继电器1打开,继电器2关闭;PC发送数据11,单片机继电器1和2打开。
程序运行界面如图1-33所示。
图1-33 程序运行界面