|
第14章 电压表\电流表\显示器\计数器 通过本模块的学习制作,可以进一步熟悉高精度ADC的运用,单片机 内部DataFlash的读写、动态显示程序的编写(包括小数的显示)以及自 定义的SPI主从机数据通信原理,测试中,本模块可以直接插接到第一章 介绍的单片机实验板“通用计数器/显示器接口”上使用。 同一个模块,通过硬件上的简单设置可实现4种不同的功能
K3(P4.2) | K4(P4.3) | 模块功能 | | K1(P3.6) | K2(P3.7) | 电压表档位 | 输入阻抗 | 0 | 0 | 电流表 | | 0 | 0 | 2.0480V | 2MΩ | 0 | 1 | 计数器 | | 0 | 1 | 20.480V | | 1 | 0 | 显示器 | | 1 | 0 | 204.80V | | 1 | 1 | 电压表(默认) | | 1 | 1 | 20.480V(默 认) | |
① 电压表说明: 使用18位MCP3421A0T-E/CH芯片作ADC,通过软件校准的方式可使整个模块电压测量误差 最大值不大于量程的±0.05%,在电路上,2V档由外部信号直接输入ADC,其它档位使用 1MΩ电阻降压,然后使用MCP6V01T-E/SN 轨到轨自动调零运放作ADC输入电压跟随器,由 于精度要求高,普通运放无法满足要求。 ② 电流表说明: 使用0.1Ω/0.5W的电流取样电阻,可测量0~2A范围内的电流,取样出来的电压信号不经过电 压跟随器,直接送入ADC芯片。 ③ 通用显示器说明: 外部单片机通过CLK与DAT两条信号线向模块送入数据,模块能显示0~99999范围内整数或小 数值,外部单片机需要一次向模块发送5个字节的数据,第1字节表示模块地址,默认值为0 ,在多个模块CLK与DAT并接在一起的情况下,只有与发送地址相符的模块才接收与处理总线 上的数据,第2、3、4字节是需要显示的数据,第2字节是数据高字节,第3字节是数据中间 字节,4字节是数据低字节,第5字节确定需要显示的小数位数,所有字节都是按高位在前, 低位在后的顺序发送,每一位的发送是外部单片机先把数据位放到DAT线上,然后拉低CLK线 ,模块内部是在CLK下降沿后读取DAT线上的状态,经测试,模块在22.1184MHz工作频率下外 部单片机数据发送时钟脉冲高电平1uS、低电平5uS条件下工作正常,也就是说发送一组数据 的最短时间需要大约(1 + 5)×8×5 = 240uS,为了提高稳定性,可以适当降低通信时钟频 率(主要是延长脉冲低电平时间),但也不能过低,要求一组数据必须在0.2秒内传送完成 ,超过0.2秒,模块自动清除前面已收到的不完整的数据,这样保证了数据传送的可靠性。 ④ 计数器说明: 外部脉冲信号通过DAT线送入模块,每产生一个脉冲下降沿,计数器数值加1,超过最大值 99999后从0开始循环,要求外部脉冲信号低电平不能大于0.6V,高电平不能小于2V,高电平 最大值也不能超过40V,模块具有断电自动存储数据的功能,可通过开关将CLK线接GND,上 电瞬间即可将显示的计数值清零。
- /*显示程序,用STC15F2K60S2做显示芯片
- // 硬件说明:P42 P43 P36 P37
- 0 0 电流表 0 0 1.9999V
- 0 1 计数器 0 1 19.999V
- 1 0 显示器 1 0 199.99V
- 1 1 电压表 1 1 999.99V
-
- // 待显示数据由主机传来,定义5个字节的显示代码
- // 第一字节是本机的地址码,对本机而言,必须是“0”
- // 接下来三字节是要显示的数据(高字节在前,低字节在后)
- // 第五个字节是要显示的小数点位置,可能值是0-4,表示显示几位小数
- //分别代表:0——不显示 1——十位,2——百位,3——千位 ,4——万位 ,个位后小数点固定不显示
- 99999 9999.9 999.99 99.999 9.9999
- //接线:P07 P06 P05 P04 P03 P02 P01 P00
- // e d h c g a f b
- //0 0 0 1 0 1 0 0 0 0x28
- //1 1 1 1 0 1 1 1 0 0xee
- //2 0 0 1 1 0 0 1 0 0x32
- //3 1 0 1 0 0 0 1 0 0xa2
- //4 1 1 1 0 0 1 0 0 0xe4
- //5 1 0 1 0 0 0 0 1 0xa1
- //6 0 0 1 0 0 0 0 1 0x21
- //7 1 1 1 0 1 0 1 0 0xea
- //8 0 0 1 0 0 0 0 0 0x20
- //9 1 0 1 0 0 0 0 0 0xa0
- //A 0 1 1 0 0 0 0 0 0x60
- //b 0 0 1 0 0 1 0 1 0x25
- //C 0 0 1 1 1 0 0 1 0x39
- //d 0 0 1 0 0 1 1 0 0x26
- //E 0 0 1 1 0 0 0 1 0x31
- //F 0 1 1 1 0 0 0 1 0x71
- //H 1 1 1 1 1 1 1 1 0xff
- //显示位接线 P15 P14 P13 P16 P12
- // (高位)1 2 3 4 5(低位)
- //P32(INT0)接时钟线,P33(INT1)接数据线*/
- #include "STC15F2K60S2.H " // 包含STC15F2K60S2单片机寄存器定义文件
- #include "UART.H"
- #include <intrins.h>
- #include "mcp3421.H"
- #include "myfun.h"
- #include "PowerDown_save.H"
- #define Hidden 16
- #define Address 0 // 本机地址,不同显示模块修改此数值即可
- #define TimeOver 500 // 定义一个超时值(5字节通信),500*2ms=1S,允许最慢2秒传送完一帧数据
- sbit CLK=P3^2; // 外部输入的时钟端
- sbit DAT=P3^3; // 外部输入的数据端
- unsigned char code DispCode[]={0x28,0xee,0x32,0xa2,0xe4,0xa1,0x21,0xea,0x20,0xa0,0x60,0x25,0x39,0x26,0x31,0x71,0xff};/*/显示代码*/
- unsigned char code DispBit[]={0xdf,0xef,0xf7,0xbf,0xfb}; //位码表 (左边最高位)11011111(P1) 11101111(P1) 11110111(P1) 10111111(P1) 11111011(P1)(右边最低位)
- unsigned long DispData=0; // 用来显示的值
- unsigned char DispBuf[5]={0,0,0,0,0}; // 显示缓冲区 DispBuf[0]是左边最高位 DispBuf[4]是右边最低位
- unsigned char temp0,temp1,temp2,temp3,temp4; // 最高位 temp0 ,最低位 temp4,5位最大显示99 999,只接收3字节,显示缓冲5字节。
- unsigned char RecDatCount; // 接收的数据位数计数器
- unsigned char bdata RecDat; // 接收到的数据
- sbit RecDat0=RecDat^0; // 定义接收数据的末位为RecDat0,这要求RecDat位于bdata空间中
- bit ReciveOK ; // 接收完成标志,为1,说明已接收到完整的5个字节,可进行数据处理了
- unsigned char DotCnt=1; // 显示小数位数。
- unsigned char InAddress=0; // 外部输入地址,确定是否与本机地址相同
- bit StartOverCount; // 一旦有接收中断,则置位该位,如果收到40位,则清该位
- unsigned int OverCount; // 超时计数器,当有接收中断发生,就让这个计数器开始计数(每个T0中
- // 断计数一次,如果这个计数器计到了500(即1秒)仍没有被清除,说明接收有误,由主程序清 RecDatCount
- extern struct POWER_UP Power_up; // 计数器变量
- // 中断程序用于完成5字节数据接收(1字节地址、3字节长整数、1字节小数位数说明)
- void ReciveDate() interrupt 0 // 外部中断0(int0)中断处理程序
- {
- if(StartOverCount==0) StartOverCount=1; // 开启溢出计时器
- RecDatCount++; // 中断次数(0-40)
- RecDat=RecDat<<1; // 主机是先发送高位,后发送低位
- if(DAT)
- RecDat0=1;
- else
- RecDat0=0;
- if(RecDatCount==8) // 接收完第一个字节
- {
- InAddress=RecDat; // 保存外部输入的地址
- }
- else if(RecDatCount==16) // 第2个数据,长整数最高字节
- {
- if(InAddress==Address) // 地址相符才处理数据,方便多模块并联使用
- DispData=RecDat*65536;
- }
- else if(RecDatCount==24) // 第3个数据,长整数中间字节
- {
- if(InAddress==Address) // 地址相符才处理数据,方便多模块并联使用
- DispData=DispData+RecDat*256;
- }
- else if(RecDatCount==32) // 第4个数据,长整数最低字节
- {
- if(InAddress==Address) // 地址相符才处理数据,方便多模块并联使用
- DispData+=RecDat;
- }
- else if(RecDatCount==40) // 否则就是第5个数据,即小数点显示位数
- {
- if(InAddress==Address) // 地址相符才处理数据,方便多模块并联使用
- DotCnt=RecDat;
- ReciveOK=1; // 要求刷新显示器
- RecDatCount=0; // 中断次数(0-40)
- StartOverCount=0; // 接收到40个字符,清标志
- OverCount=0; // 清超时计数器
- RecDat=0;
- }
- }
- void EX0_Init() // 外中断0用于数据接收 CLK 输入端口
- {
- IT0=1; // 外部引脚下降沿触发(CLK时钟信号)
- EX0=1; // 外中断int0中断允许
- PX0=1; // 将外中断置为高级中断,定时器T0低级中断
- EA=1; // 比较器断0电存储只能使用低级中断,无法更改。
- }
- void EX1_Init() // 外中断1用于数据接收 DAT 输入端口与计数器脉冲输入口
- {
- IT1=1; // 设置为下降沿触发,用于计数器脉冲输入
- EX1=1; // 开外部中断1
- PX1=1; // 将外中断1置为高级中断,定时器T0低级中断
- EA=1; // 开总中断
- }
- void Timer0_Init() // 初始化定时器0用于动态显示程序
- {
- TMOD &= 0xF0; // 设置定时器模式
- TMOD |= 0x01; // 设置定时器模式
- TH0=(65536-4000)/256 ; // 计数脉冲周期 T=1/F= 1/(22.1184/12)= 0.5425uS
- TL0=(65536-4000)%256 ; // 4000*0.5425=2.17mS
- TR0=1; // T0开始运行
- ET0=1; // 定时器T0中断允许
- PT0=0; // 将定时器置为低级中断
- EA=1;
- }
- // 定时器 T0 用于完成5位数码管论流点亮
- void Timer0() interrupt 1 // 定时器T0的中断处理代码
- {
- unsigned char temp; // 动态显示中间变量
- static unsigned char Count; // 用于统计当前正显示哪一位(先显示左边最高位)
- //********* 数据接收过程的时间限制 ************
- if(StartOverCount) // 如果要求计数的标志是1
- OverCount++; // 计数器加1
- //********* 正式动态显示程序 ******************
- P1|=0x7c; // 关断前次显示0111 1100
- temp=DispBit[Count];
- P1&=temp; // 开启P1位控制
-
- temp=DispBuf[Count]; // 5位显示缓冲器BCD码
- P0=DispCode[temp]; // 查字形码表格(0-16)
-
- if(Count<4) // 显示小数点(最右端小数点不显示)
- {
- if(DotCnt==(4-Count))
- {
- P0&=0xDF; // 点亮小数点h位置 1101 1111
- }
- }
- Count++;
- if(Count==5) Count=0;
- TH0=(65536-4000)/256 ; // 计数脉冲周期 T=1/F= 1/(22.1184/12)= 0.5425uS
- TL0=(65536-4000)%256 ; // 4000*0.5425=2.17mS
- }
- void long_to_bcd(unsigned long temp)
- {
- unsigned char temp0,temp1,temp2,temp3,temp4; // 最高位 temp0 ,最低位 temp4,5位最大显示99 999,只接收3字节,显示缓冲5字节。
-
- temp%=100000; // 如果收到的数超过10 0000则仅取小于10 0000的值
- temp4 = temp % 10; // 获得个位
- temp3 = temp / 10 % 10; // 获得十位
- temp2 = temp / 100 % 10; // 获得百位
- temp1 = temp / 1000 % 10; // 获得千位
- temp0 = temp / 10000 % 10; // 获得万位
- if((temp0==0)&&(DotCnt<4)) // 如果最高位等于0,而显示的小数位数小于4位
- DispBuf[0]=Hidden; // 那么最高位应该消隐
- else
- DispBuf[0]=temp0; // 否则将这个数送入最高位
- if((temp0==0)&&(temp1==0)&&(DotCnt<3)) // 最高位、次高位同时为0,且小数位数小于3位
- DispBuf[1]=Hidden;
- else
- DispBuf[1]=temp1;
- if((temp0==0)&&(temp1==0)&&(temp2==0)&&(DotCnt<2))
- // 最高位、次高位、第三位均为0,且小数位数小于2位时消隐
- DispBuf[2]=Hidden;
- else
- DispBuf[2]=temp2;
-
- if((temp0==0)&&(temp1==0)&&(temp2==0)&&(temp3==0)&&(DotCnt<1))
- // 最高位、次高位、第三位、第四位均为0,且小数位数小于1位(无)时消隐
- DispBuf[3]=Hidden;
- else
- DispBuf[3]=temp3;
- DispBuf[4]=temp4; // 最低位直接显示
- }
- void main(void)
- {
- //////////////////////// 电压电流表变量 ///////////////////
- unsigned char test_data[3]={0x00,0x00,0x00}; // 存放 MCP3421 AD转换结果
- long aa; // 计算 MCP3421电压值的中间变量
- float VIN3421; // 计算出来的MCP3421原始电压
- long V3421; // 显示时用于将数据扩大1000或10000倍
- ///////////////////////////////////////////////////////////
- UART_init(); // 串口初始化(占用定时器1)9600/22.1184MHz
- printf("串口初始化完毕");
- Timer0_Init(); // 初始化定时器0用于动态显示程序
-
- ReciveOK=1; // 要求刷新显示器
- CLK=1; // 数据接收端口初始化
- DAT=1;
- DispData=0X00; // 上电显示0.0000
- DotCnt=4; // 上电显示0.0000
- /*************************************************************************
- // 2A电流表,电流取样电阻:0.1Ω/0.5W,
- *************************************************************************/
- if ((P42==0)&&(P43==0)) // 电流表
- {
- DotCnt=4; // 2V档,保留4位小数,2.0480A
- WrToMCP3421(SlaveADDR, 0x9C); // 1001 1100 18位分辨率
- delay300ms();
- while(1)
- {
- aa=test_data[0]<<8;
- aa=aa+test_data[1];
- aa=aa<<8;
- aa=aa+ test_data[2];
- VIN3421=2.048*aa/131071; // 得到取样电阻上的电压值
-
- VIN3421=VIN3421/0.1; // I=U/R
- VIN3421=VIN3421-0.000; // 要求零输入零输出
- if (VIN3421<0 ) VIN3421=0; // 数码管没编写显示负数的功能,电脑能直接显示负数
-
- printf("I= :%.5f ",VIN3421);
- // 浮点数转长整数
- V3421=VIN3421*10000; // 2V档,保留4位小数 ,2.0480
- long_to_bcd(V3421);
-
- delay300ms(); // 延时避免硬接频繁操作
- delay300ms();
- }
- }
- /*************************************************************************
- // 计数器, DAT为计数脉冲输入端,上电瞬间如果CLK=0则清除计数值
- *************************************************************************/
- if ((P42==0)&&(P43==1))
- {
- DotCnt=0; // 计数器不显示小数
- ReadFLASH(); // 读取单片机内部FLASH中保存的重要数据,只需2个时钟。
- comparator_init(); // 比较器掉电中断初始化
- EraseFLASH(); // 扇区擦除需要21mS
- EX1_Init(); // 外部计数端口初始化
- while(1);
- }
- /*************************************************************************
- // 5位通用显示器
- *************************************************************************/
- if ((P42==1)&&(P43==0))
- {
- DotCnt =4; // 上电时显示0.0000
- EX0_Init(); // 外中断0用于数据接收时钟输入端口
- while(1)
- {
- if(ReciveOK) // 如果收到了40位数据,将数值转BCD码放入显示缓冲器。
- {
- long_to_bcd(DispData);
- ReciveOK=0;
- }
- if(OverCount>=TimeOver) // 出现了超时错误
- {
- RecDatCount=0; // 将接收计数器清零
- StartOverCount=0; // 接收到40个字符,清除计数标志
- OverCount=0; // 清超时计数器
- }
- }
- }
- /*************************************************************************
- // 18位分辨率电压表
- *************************************************************************/
- if ((P42==1)&&(P43==1))
- {
- if ((P36==0)&&(P37==0)) DotCnt=4; // 2V档,保留4位小数 ,2.0480
- if ((P36==0)&&(P37==1)) DotCnt=3; // 20V档,保留3位小数, 20.048
- if ((P36==1)&&(P37==0)) DotCnt=2; // 200V档,保留2位小数, 200.48
- if ((P36==1)&&(P37==1)) DotCnt=2; // 1000V档,保留2位小数, 999.99
-
- WrToMCP3421(SlaveADDR, 0x9C); // 1001 1100 18位分辨率
- delay300ms();
-
- while(1)
- {
- RdFromMCP3421(SlaveADDR, test_data,3); //连续读取3个字节数据
- aa=test_data[0]<<8;
- aa=aa+test_data[1];
- aa=aa<<8;
- aa=aa+ test_data[2];
- VIN3421=2.048*aa/131071;
- if ((P36==0)&&(P37==0)) // 2V档
- {
- VIN3421=VIN3421*1.00; // 2V档,无衰减,精密校准
- printf("2V :%.5f ",VIN3421);
- V3421=VIN3421*10000; // 2V档,保留4位小数 ,2.0480
- }
- if ((P36==0)&&(P37==1)) // 20V档
- {
- VIN3421=VIN3421*10.00; // 20V档,10倍衰减+衰减电阻误差补偿
- printf("20V :%.4f ",VIN3421);
- V3421=VIN3421*1000; // 20V档,保留3位小数 ,20.480
- }
- if ((P36==1)&&(P37==0)) // 200V档
- {
- VIN3421=VIN3421*100.00; // 200V档,100倍衰减+衰减电阻误差补偿
- printf("200V :%.3f ",VIN3421);
- V3421=VIN3421*100; // 200V档,保留2位小数,204.80
- }
- if ((P36==1)&&(P37==1)) // 1000V档
- {
- VIN3421=VIN3421*1.00; // 2V档,无衰减,精密校准
- printf("2V :%.5f ",VIN3421);
- V3421=VIN3421*10000; // 2V档,保留4位小数 ,2.0480
- }
-
- VIN3421=VIN3421*10.00; // 20V档,10倍衰减+衰减电阻误差补偿
- //
- VIN3421=VIN3421-0.000; // 要求零输入零输出
- if (VIN3421<0 ) VIN3421=0; // 数码管没编写显示负数的功能,电脑能直接显示负数
-
- printf("20V :%.5f ",VIN3421);
- // 浮点数转长整数
- if ((P36==0)&&(P37==0)) V3421=VIN3421*10000; // 2V档,保留4位小数 ,2.0480
- if ((P36==0)&&(P37==1)) V3421=VIN3421*1000; // 20V档,保留3位小数, 20.048
- if ((P36==1)&&(P37==0)) V3421=VIN3421*100; // 200V档,保留2位小数, 200.48
- if ((P36==1)&&(P37==1)) V3421=VIN3421*100; // 1000V档,保留2位小数, 999.99
- long_to_bcd(V3421);
-
- delay300ms(); // 延时避免硬接频繁操作
- delay300ms();
- }
- }
- }
- void X1_ISR(void) interrupt 2 // 外部中断1中断函数实现计数功能
- {
- Power_up.times++;
- long_to_bcd(Power_up.times);
- }
复制代码
|
|