找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3090|回复: 7
收起左侧

stc12c5a60s2 0-30电压测量 有注释

  [复制链接]
ID:254747 发表于 2019-11-7 19:09 | 显示全部楼层 |阅读模式
#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--);
    }
}




回复

使用道具 举报

ID:254747 发表于 2019-11-8 12:51 | 显示全部楼层
一定要记住  当I/O口设置为高阻模式的时候 千万不要有上拉电阻否则会造成高阻模式处于上拉状态 造成误差

高阻模式的电平是什么
     答  高阻模式的电平为任意电平 可以是高  也可以是低  可以是无穷大   高阻的中心定义就是和电路看上去是断开的 但实际又没有断开   只是阻抗高而已   
高阻模式主要用在哪里 为什么要高阻模式   
     答  高阻模式主要用在总线上面 例如I2C总线   因为总线每次通讯的时候只能和一个IC通讯, 这是为什么呢   因为每一个IC通讯的时候 电平不一样 如果都通讯就会造成通讯混乱
     从这个描述上看   高阻模式就是一个抗干扰能力强的开关  但是他比开关更先进  高阻抗 高抗干扰能力 低功耗  而且是一个被动的开关,
    经过上面的描述我们可以知道  只要总线上一个IC损坏就会导致整个总线损坏  为什么?因为总线没有被释放,出现了通讯混乱
  高阻模式的电平由谁控制
   答   由负载控制   负载是低  高阻模式就是低   负载是高  高阻模式就是高   

  所以在AD转换的时候  用高阻模式提高精度  是唯一途径

评分

参与人数 1黑币 +90 收起 理由
admin + 90 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

ID:355469 发表于 2019-11-8 07:31 | 显示全部楼层
好,需要,谢谢。
回复

使用道具 举报

ID:113020 发表于 2019-11-8 10:08 | 显示全部楼层
很好!收藏了
回复

使用道具 举报

ID:138282 发表于 2019-11-8 21:52 | 显示全部楼层
好帖谢谢分享!
回复

使用道具 举报

ID:600631 发表于 2020-1-28 01:33 来自手机 | 显示全部楼层
师傅,这个程序如果显示电流和功率的话,应该怎么写程序呢
回复

使用道具 举报

ID:105786 发表于 2020-3-4 20:54 | 显示全部楼层
自制STC 12C5A60S2电压电流表正在学习中
回复

使用道具 举报

ID:163501 发表于 2020-3-5 21:56 | 显示全部楼层
这个坛的好就是活跃人多,有东西学
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表