#include <STC12C5A60S2.H>
#include "intrins.h"
#define uchar unsigned char
#define uint unsigned int
#define ulong unsigned long
/*Define ADC operation const for ADC_CONTR*/
#define ADC_POWER 0x80 //ADC 电源控制位 10000000 0X80 /
#define ADC_FLAG 0x10 //ADC 完成标志 00010000 0x10
#define ADC_START 0x08 //ADC 启动ADC开关 00001000 0x08 /
#define ADC_SPEEDLL 0x00 //420 转换速度 00000000 0x00 /
#define ADC_SPEEDL 0x20 //280 转换速度 00100000 0x20
#define ADC_SPEEDH 0x40 //140 转换速度 01000000 0x40
#define ADC_SPEEDHH 0x60 //70 转换速度 01100000 0x60
/*----------------------------------------------------------------------------------------------
meidangzuidivoltage:表示AD最高电压5V对应10bit_AD转换的最小电压是多少 5/1024= 0.00488V 也就是AD在
//10bit下测量的最小电压,
//那么在计算AD电压值的时候将公式 “GetADCResult(ch)*5/1024”分两步进行 方便后面对数据分离显示到
//1602上面 先计算ADC_jiancedaozuidivoltage=5*10000000/1024 先将这个数放大100万倍
//最后在算AD_jie_guo=(ADC_jiancedaozuidivoltage*GetADCResult(ch);
----------------------------------------------------------------------------------------------*/
ulong ADC_jiancedaozuidivoltage,temp,AD_jie_guo; //长整形数据 16bit
float OVP; //定义一个浮点数 以保留小数点 提高进度
uint num,i,vlue;//ADC_mV,ADC_RESX,VCC_V=5.2;
sbit lcden = P2^7;
sbit rs = P2^6;
sbit rw = P2^5;
sbit LED = P3^0;
void Delay(uint n); //延时函数
uint GetADCResult(uchar ch); // ad转换函数
void InitADC(); //ad初始化函数
void OCP_1(); //过压 欠压 提醒函数声明
float count(uchar ch); //AD值100次平均值函数
//ulong count(uchar ch); //ad计算函数测量0-5V时候用的
uchar code table[20]= {" Shu Kong QuDong"}; // 开机画面的布置
uchar code table2[20]={" CQDZ Alan V1.01"};
/*-----------------
延时函数
-----------------*/
void delayms(uint xms) //延时函数
{
uint i,j;
for(i=xms;i>0;i--)
for (j=960;j>0;j--);
}
/*--------------------
写命令
---------------------*/
void lcd_write_com(uchar com)
{
rw=0;
rs=0; //写命令状态
P0=com;
lcden=1;
delayms(5);
lcden=0;
}
/**-----------------
写数据
--------------- ***/
void write_date_(uchar date)
{
rw=0; //写数据
rs=1; //写数据状态
P0=date;
delayms(5);
lcden=1; //使能
delayms(5);
lcden=0;
}
/*---------------
初始化显示屏
---------------*/
void lcd_init(void)
{
lcden=0;
lcd_write_com(0x38); //设置8位格式,2行,5*7
lcd_write_com(0x0c); //整体显示,关光标,不显示
lcd_write_com(0x06); // 设定输入方式,增量不移位
lcd_write_com(0x01);//清屏幕
delayms(5); //延?
}
/*-----------------------
函数名称 格式定义
函数的介绍 在某个屏幕位置上显示一个字符,X(0-16),y(1-2)
X:表示字的格式 一共16个 Y:表示行 一共2行
-------------------------*/
//格式定义
void lcd_disp_char(uchar y,uchar x, uint dat)
{
uint address;
if(y==1) //y为1 在第一行
address=0x80+0x10+4+x; //整屏左移动以后 从新定义新的起始位置 但是要加上之前移动后的地址
else
address=0xc0+0x10+x; //y为2 在第二行 X显示字的位置 0XC0是 0x80+0x40的结果
lcd_write_com(address); //写入要写的位置
write_date_( dat); //写入你要写的数据
}
/*------------------
显示函数2
-------------------*/
void disp()
{
AD_jie_guo = count(0); //经过上面的计算求出来100次的平均值存放在AD_jie_guo里面
/*-------------------------------------------------------------------------------
//扩大电压 我的量程是0-30V 分压电阻是 10k 2k 电阻比的6 反推 当测试电压为5v
//的时候 最高电压为30V 测量后调试OK 因电阻误差 调整了数据为6.02
//同时这里也可以用(temp/0.167)/100 这个是电压比也就是30V分压为5V
//然后5/30=0.167的结果也是一样的
---------------------------------------------------------------------------------*/
temp=((ADC_jiancedaozuidivoltage*AD_jie_guo)*6.02)/100;
// temp=(temp*6.02)/100; //备用算法 这样太占用位置 我把这步合并到上面了
//0x30是显示数字 0-9 30表示第一个数0
lcd_disp_char(1,0, temp%10000000/1000000+0x30 ); //十位
lcd_disp_char(1,1, temp%1000000/100000+0x30); //个位
lcd_disp_char(1,2,'.' ); // 小数点
lcd_disp_char(1,3, temp%100000/10000+0x30 ); //个分位
lcd_disp_char(1,4, temp%10000/10000+0x30 ); //百分位
lcd_disp_char(1,5,'V' );
}
//count(0)
/*-----------------------------------
名称 开机画面
功能 开机的时候显示一下铭牌
for来完成 屏幕左移动
----------------------------------*/
void init()
{
// lcd_write_com(0x80+0x10); //定义显示的位置 起始地址
lcd_write_com(0x80); //定义显示的位置 起始地址
for(num=0;num<20;num++)
{
write_date_(table[num]); //初始化屏幕的初始数字“0000”
delayms(5);
}
// lcd_write_com(0x80+0x40+0x10); //定义第二排,显示的地址 0x80是显示屏寄存器第一排起始地址
lcd_write_com(0xc0); //定义显示的位置 起始地址
for(num=0;num<20;num++) //0x40是第二排起始地址
{
write_date_(table2[num]);
delayms(5);
}
for(num=0;num<20;num++) //整屏左移动 这里的21就是指可以移动多少格
{ //可以是100可以是1000 相当于是电子屏幕一样
lcd_write_com(0x18); //0x18是整屏左移 指令
delayms(50);
}
}
void main()
{
lcd_init();
init();
InitADC(); //Init ADC sfr
ADC_jiancedaozuidivoltage=(49600000)/1024; //5V参考电压,计算分度值的时候,数值过小会出现很大偏差
//所以放大1000万倍 这样精度会高一些
while (1)
{
disp();
OCP_1();
}
}
/*************************
* 功能 ADC转换函数
* 带结果返回 带结果返回
**************************/
uint GetADCResult(uchar ch) //参数的定义“ch”初始化就是为0 所以这里的“ch”应该是用来设置用哪个端口
{ //来设置AD的 这个“ADC_POWER|ADC_SPEEDLL|ch|ADC_START”;或出来的结果是
//10001000 后面三个0对应的是 CHS2 CHS1 CHS0 查表格 刚好是P1.0口
//作为AD模拟输入的。如果要改变端口就要给ch赋值 假如要P1.2 就需要赋值为ch=0x02;
ADC_CONTR = ADC_POWER|ADC_SPEEDLL|ch|ADC_START; //配置相关项目打开
_nop_();
_nop_();
_nop_(); //4个机器时间的延时 以保障转换完成
_nop_();
while(!(ADC_CONTR&ADC_FLAG)); //判断是否转换完成
ADC_CONTR &= ~ADC_FLAG; //关闭ADC同时清零
vlue = (ADC_RES*4+ADC_RESL); //返回结果10bit 也就是电压值 ,左移几次就乘以2的几次方
//右移几次就除以2的几次方 这里的ADC_RES*4相当于ADC_RES<<2 左移了2
Delay(2); //延时一下
return vlue;
}
/******************************
* 功能:计算1结果函数 将10bit的结果计算为对应电压值MV
* 函数类型 float 型 浮点型
* 带结果返回 带结果返回
******************************/
/*
ulong count(uchar ch)
{
ADC_RESX=GetADCResult(ch);
ADC_mV=(VCC_V*(long)ADC_RESX*10000/1024+5)/10;//强制转换长整型运算,得到结果mV(4舍5入)
return ADC_mV;
}*/
/******************************
* 功能:计算2结果函数 将10bit的结果计算为对应电压值MV
* 函数类型 float 型 浮点型 保留小数点 提高精度
* 带结果返回 带结果返回
* 同时对GetADCResult(ch)采样100次求平均值 提高进度
******************************/
float count(uchar ch)
{
float AD_val; //定义处理后的数值AD_val为浮点数 初始状态AD_val为0 经过100次变化后
uchar n; //AD_val值为100次的GetADCResult(ch)这个变化值 然后在除以100就是平均值
for(n=0;n<100;n++)
AD_val += GetADCResult(ch); //转换100次求平均值(提高精度)
AD_val /= 100; //意思是GetADCResult(ch)经过了100次转换后的值保存到了AD_val里面 然后在
//除以100 就是求平均值 这样的精度高一点
// AD_val=(AD_val*5)/1024; //AD的参考电压是单片机上的5v,所以乘5即为实际电压值,因为为分段计算这个就
//暂时不用 如果测量0-5v就可以这样计算
OVP = ( AD_val*4.96 ) / 1024; //为过压 欠压 提醒做 前期的计算 方便后面使用
return AD_val;
}
/*-------------------------------------------------------------------------------
函数功能 过压ovp 欠压 检测 cpu供电VCC测量的是4.96V 本程序用的也是4.96V
下面计算也用4.96V。
计算方案:
通过计算AD基础值变化也就是"0-5V"的变化情况,然后人为的乘以扩量程倍率“6.02”
得到的就是要过压去,欠压保护的值。
例如:现在要电压达到10v的时候启动欠压提醒
计算过程:方法一
本次计算实际是通过vlue = (ADC_RES*4+ADC_RESL);在0-1024之间变化的值来确认模拟
电压输入是多少的 如 1024/4.96V(VCC)*(VIN)模拟量,那么对应10v的模拟量电压应该为
10/6.02=1.7V,那么它vlue = (ADC_RES*4+ADC_RESL)就要变化到多少才是对应的1.7V呢,
通过公式1024/4.96V(vcc)*1.7V(vin)=350;
通过计算它的值在1.7V的时候vlue = (ADC_RES*4+ADC_RESL)=350. 那么我们通过判断
这个vlue = (ADC_RES*4+ADC_RESL)的数据也可以判断对应保护电压,但是计算很麻烦
所以我们把这个麻烦的过程直接让程序计算好,然后直接用对应系数1.7V即可。
方法二
这个就是我在float count(uchar ch)函数里面计算好了的(OVP)如下
OVP =( AD_val*4.96 ) / 1024;
OVP必须是float类型的数据保留小数点 不然误差大,只要保留了小数点误差非常小
通过上面计算好的这个过程然后我们要计算欠压就非常好算了,例如我要12V欠压
直接用12V/6.02这个系数 得到的就是你要的AD模拟量大小 12/6.02约等于2.0V
只要判断OVP到了2.0就说明12V到了 做什么处理就看使用者了 可以报警 可以切断电源
本程序我是10V欠压 计算 10/6.02约等于1.7V 我判断是OVP>1.7 也就是对应的欠压过压点
到了 我是点亮LED,低于1.7也就是10V 就关闭LED;
----------------------------------------------------------------------------------*/
void OCP_1()
{
if(OVP > 1.7) //高于10V 10/6.02=1.7
{
LED = 0;
// 调试的时候用的 通过判断temp来判断是否过压 如果过压就启动保护
}
if(OVP < 1.7) //低于10v 10/6.02=1.7
{
LED = 1;
}
}
/*----------------------------
功能 ADC初始化函数
----------------------------*/
void InitADC() //adc初始化函数
{
P1M1 = 0x01; //设置P1.0高阻模式
P1M0 = 0x00; //设置P1.0高阻模式
P1ASF = 0x01; //开放P1.0通道ADC功能 就是8个I/O口全部开放
ADC_RES = 0; //清除之前的结果
ADC_RESL = 0;
ADC_CONTR = ADC_POWER | ADC_SPEEDLL; //打开AD电压 转换速度540
Delay(2); //ADC 延时一下
}
/*----------------------------
延时函数
----------------------------*/
void Delay(uint n)
{
uint x;
while (n--)
{
x = 5000;
while (x--);
}
}
|