找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3906|回复: 18
收起左侧

STC15单片机ADC程序问题,请教

  [复制链接]
回帖奖励 100 黑币 回复本帖可获得 20 黑币奖励! 每人限 1 次
ID:609989 发表于 2019-12-13 15:03 | 显示全部楼层 |阅读模式
我想使用STC15的内部模拟量,测试电压,只使用P1.0口,想通过串口发到PC。15支持10位,我就需要高八位就好了。
接线:1.直接使用信号源(1~4.3)V,接到P1.0口;

下面是程序,我几乎照搬了,为啥还不行 ,第一次使用单片机的模拟量,真的头疼的不行。。。。。是不是接线有问题?哎

单片机源程序如下:
  1. #include <stc15f2k60s2.H>                        
  2. #include <intrins.H>                    
  3. #define uint unsigned int               
  4. #define uchar unsigned char            
  5.    
  6. #define FOSC 11059200L          //系统频率
  7. #define BAUD 9600            //串口波特率

  8. #define ADC_POWER   0x80                //ADC power control dit (宏定义ADC电源控制位)1000 0000
  9. #define ADC_FLAG    0x10                //ADC complete flag (宏定义ADC完成标志位)    0001 0000
  10. #define ADC_START   0x08                //ADC start control dit (宏定义ADC启动控制位)0000 1000
  11. #define ADC_SPEEDLL 0x00                //420 clocks (宏定义420时钟)速度控制         0000 0000
  12. #define ADC_SPEEDL  0x20                //280 clocks (宏定义280时钟)速度控制         0001 0000
  13. #define ADC_SPEEDH  0x40                //140 clocks (宏定义140时钟)速度控制         0010 0000
  14. #define ADC_SPEEDHH 0x60                //070 clocks (宏定义70时钟)速度控制          0011 0000

  15. sbit Q1=P1^1;                                //驱动继电器端口定义

  16. bit busy;
  17. uchar i,a=0xaa;
  18. uint  ch=0,num;
  19. unsigned char String[2];   //串口1接收数据缓存区

  20. /*----------------------------
  21. 1ms延时子程序(
  22. -----------------------------*/
  23. void delayms(uint k)                    

  24. {
  25.     uint i,j;            
  26.     for(i=k;i>0;i--)   
  27.         for(j=120;j>0;j--);   
  28. }

  29. /*----------------------------
  30. 串口初始化
  31. -----------------------------*/
  32. void UartInit()                //9600bps@11.0592MHz
  33. {
  34.    
  35.     SCON = 0x50;        //8位数据,可变波特率
  36.     T2L = (65536 - (FOSC/4/BAUD));  //设置波特率重装值
  37.     T2H = (65536 - (FOSC/4/BAUD))>>8;
  38.     AUXR = 0x14;
  39.     AUXR |= 0x01;        //串口1选择定时器2为波特率发生器
  40.     ES = 1;                          //使能串口1中断
  41.     EA = 1;
  42. }
  43. /*************发送字符****************/
  44. void SendData(unsigned char dat)
  45. {
  46.     while (busy);              //等待前面的数据发送完成
  47.     busy = 1;
  48.     SBUF = dat;                //写数据到UART数据寄存器
  49. }
  50. ///*************发送字符串****************/
  51. //void SendString(char *s)
  52. //{
  53. //    while (*s)                  //检测字符串结束标志
  54. //    {
  55. //        SendData(*s++);        //发送当前字符
  56. //    }
  57. //}

  58. /*************UART_1 中断服务程序****************/
  59. void Uart1() interrupt 4
  60. {
  61.     if (RI)
  62.     {
  63.         RI = 0;           //清除RI位        
  64.         String[i]= SBUF;              
  65.         i++;
  66.         if(i==2){i=0;REN=0;}
  67.     }
  68.     if (TI)
  69.     {
  70.         TI = 0;                //清除TI位
  71.         busy = 0;              //清忙标志
  72.     }
  73. }
  74. /*----------------------------
  75.    初始化ADC
  76. -----------------------------*/
  77. void InitADC()
  78. {
  79.     P1ASF=0x01;                            //设置P1.0端口作为模拟输入
  80.     ADC_RES=0;                            //Clear previous result (清除以前的结果)
  81.     ADC_CONTR=ADC_POWER|ADC_SPEEDLL|ADC_START|ch;    //开启ACD电源、设置转换速度
  82.     delayms(100);                            //上电延时                                                
  83. }
  84. //ADC中断
  85. void adc_isr() interrupt 5
  86. {
  87.     ADC_CONTR &= !ADC_FLAG;
  88. //    SendData(ch);
  89.     SendData(ADC_RES);
  90.     ADC_CONTR=ADC_POWER|ADC_SPEEDLL|ADC_START|ch;
  91. }
  92. /*--------------------------------------------------
  93. 主函数
  94. ---------------------------------------------------*/
  95. void main()
  96. {
  97.     delayms(1000);
  98.     P1M1 = 0x01;                         //P1.0高阻 P1.1强推挽:0000 0001 ADC输入高阻、继电器强上拉
  99.     P1M0 = 0x02;                         //P1.0高阻 P1.1强推挽:0000 0010 ADC输入高阻、继电器强上拉
  100.     P_SW1=0x40;                            //P3.6/P3.7串口1 RXD/TXD
  101. //    Q1=0;                                    //驱动继电器端口初始化0
  102.     InitADC();                            //Init ADC sfr (初始化ADC SFR)
  103.     UartInit();   
  104.     IE=0xa0;
  105.     while(1);
  106. }
复制代码


回复

使用道具 举报

ID:606366 发表于 2019-12-13 16:11 | 显示全部楼层
最高不是3.3V吗
回复

使用道具 举报

ID:213173 发表于 2019-12-13 16:54 | 显示全部楼层
只发不收不必用开串口中断。1T单片机的延时函数不能直接套用12T,要改参数。
  1. #include <stc15f2k60s2.H>                        
  2. #include <intrins.H>                    
  3. #define uint unsigned int               
  4. #define uchar unsigned char            
  5. //宏定义ADC的操作命令
  6. #define ADC_POWER   0x80                        //ADC电源控制位
  7. #define ADC_START   0x08                        //ADC启动控制位
  8. #define ADC_FLAG    0x10                        //ADC完成标志位
  9. #define ADC_SPEEDLL 0x00                        //420时钟速度控制位

  10. sbit Q1=P1^1;                    //驱动继电器端口定义

  11. /*--------------------------------
  12.    ms延时子程序 11.0592MHz时钟
  13. ---------------------------------*/
  14. void delayms(uint k)
  15. {
  16.     uint i,j;            
  17.     for(i=k;i>0;i--)   
  18.         for(j=1150;j>0;j--);
  19. }
  20. /*----------------------------
  21. 串口初始化
  22. -----------------------------*/
  23. void UartInit(void)                //9600bps@11.0592MHz
  24. {
  25.         SCON = 0x50;                //8位数据,可变波特率
  26.         AUXR |= 0x01;                //串口1选择定时器2为波特率发生器
  27.         AUXR |= 0x04;                //定时器2时钟为Fosc,即1T
  28.         T2L = 0xE0;                        //设定定时初值
  29.         T2H = 0xFE;                        //设定定时初值
  30.         AUXR |= 0x10;                //启动定时器2
  31. }
  32. /*----------------------------
  33.    初始化ADC
  34. -----------------------------*/
  35. void InitADC()
  36. {
  37.         P1ASF=0x01;                            //设置P1.0端口作为模拟输入
  38.         ADC_RES=0;                            //Clear previous result (清除以前的结果)
  39.         ADC_CONTR=ADC_POWER|ADC_SPEEDLL;    //开启ACD电源、设置转换速度
  40.         delayms(1);                                                //上电延时1ms                                       
  41. }

  42. uchar GetADCResult(uchar CHA)                                //获取ADC结果
  43. {
  44.         ADC_CONTR=ADC_POWER|ADC_SPEEDLL|CHA|ADC_START;//启动转换1000 1000
  45.     _nop_();                        //延时确保正确读到ADC_CONTR寄存器的值
  46.     _nop_();
  47.     _nop_();
  48.     _nop_();
  49.         while(!(ADC_CONTR & ADC_FLAG));        //等待ADC转换完成标志置位 1001 1000
  50.         ADC_CONTR &=~ADC_FLAG;                        //关闭ADC
  51.         return ADC_RES ;                                //返回八位ADC结果
  52. }

  53. /*--------------------------------------------------
  54. 主函数
  55. ---------------------------------------------------*/
  56. void main()
  57. {
  58.         P1M1 = 0x01;                         //P1.0高阻 P1.1强推挽:0000 0001 ADC输入高阻、继电器强上拉
  59.         P1M0 = 0x02;                         //P1.0高阻 P1.1强推挽:0000 0010 ADC输入高阻、继电器强上拉
  60.         P_SW1= 0x40;                         //P3.6/P3.7串口1 RXD/TXD
  61.         Q1=0;                                //驱动继电器端口初始化0
  62.         InitADC();                            //Init ADC sfr (初始化ADC SFR)
  63.         UartInit();   
  64.         while(1)
  65.         {       
  66.                 SBUF=GetADCResult(0);//发送数据
  67.                 while(!TI);                                //等待发送完毕
  68.                 TI=0;                                                //发送中断请求标志位清0
  69.                 delayms(1000);                        //延时1s发送一次
  70.         }
  71. }
复制代码

评分

参与人数 1黑币 +10 收起 理由
baiyun1234 + 10

查看全部评分

回复

使用道具 举报

ID:609989 发表于 2019-12-14 10:22 | 显示全部楼层
wulin 发表于 2019-12-13 16:54
只发不收不必用开串口中断。1T单片机的延时函数不能直接套用12T,要改参数。

谢谢师傅,能发送了。我还有几个不明白的地方,再给我讲讲呗。
1.那个1ms延时参数问题,我用你的这个1150,为啥使用keil_5调试出来,显示的是9.2s?
2.ADC_CONTR=ADC_POWER|ADC_SPEEDLL|CHA|ADC_START;//启动转换1000 1000
  这个里面的CHA是什么东西(见前面就定义了个uchar CHA,别的没有了)。
3.关于ADC_CONTR寄存器,低三位(CHS2,CHS1,CHS0)为啥没有,我知道“ADC_CONTR=ADC_POWER|ADC_SPEEDLL|CHA|ADC_START;//启动转换1000 1000”这个低三位是0,0,0,说明选择1.0作为AD使用,那如果我想用P1.1口呢?先设置1.1口是高阻,再设置P1ASF=0x01;1.1口作为AD口使用,然后怎么设置CHS2,CHS1,CHS0呢(选择1.1口作为AD输入来用)?
4.模拟量计算:高八位结果=256*(vin/vcc),vin实际电压,vcc单片机工作电压。我实际计算与实际测量误差在(0.01~0.02)V,这个误差正常吗?
回复

使用道具 举报

ID:609989 发表于 2019-12-14 10:33 | 显示全部楼层

3.3V的低压单片机是不超3.3V,5V的是不超过5V。
回复

使用道具 举报

ID:627214 发表于 2019-12-14 20:00 | 显示全部楼层
你这个是程序问题还是硬件问题啊?
回复

使用道具 举报

ID:213173 发表于 2019-12-14 20:18 | 显示全部楼层
baiyun1234 发表于 2019-12-14 10:22
谢谢师傅,能发送了。我还有几个不明白的地方,再给我讲讲呗。
1.那个1ms延时参数问题,我用你的 ...

1.常见的for嵌套延时函数是按8051/12T单片机编写的,用在1T单片机延时时间就不对了。keil_5调试显示的是9.2s是因为这个编辑软件不含STC单片机,所以显示的时间不代表与实际相符。
2.CHA是在uchar GetADCResult(uchar CHA)函数中定义的形参,其值只能在0~7范围,就是对ADC_CONTR寄存器的低三位(CHS2,CHS1,CHS0)置数。
3.如果想用P1.1口,先设置P1.1口是高阻,再设置P1ASF=0x02; //P1.1口作为AD口使用。 0000 0010
4.这个误差是正常的。要想误差小一定要设法使参考电压准确和改用10位ADC。
再给你一个示例,串口发送转换后计算的电压值。
  1. #include <STC15F2K60S2.H>
  2. #include <intrins.h>
  3. #define uint unsigned int               
  4. #define uchar unsigned char   
  5. //宏定义ADC参考电压mV
  6. #define VCC_V  5000
  7. //宏定义ADC的操作命令
  8. #define ADC_POWER   0x80            //ADC电源控制位
  9. #define ADC_FLAG    0x10            //ADC完成标志
  10. #define ADC_START   0x08            //ADC起始控制位
  11. #define ADC_SPEEDLL 0x00            //540个时钟
  12. //声明子函数
  13. void InitUart();
  14. void SendData(uchar dat);
  15. void InitADC();
  16. uchar GetADCResult(uchar ch);
  17. void delayms(uint k);
  18. //定义变量
  19. uchar dis_buf[]="0.00V\n";                                //显示缓存数组
  20. uchar ADC_RESH;                                                        //ADC变量
  21. uint count;                                                                        //中间变量
  22. //主程序
  23. void main()
  24. {
  25.         uchar i;
  26.         P0M0 = 0x00;
  27.         P0M1 = 0x00;
  28.         P1M0 = 0x00;                                                        //P1.0高阻
  29.         P1M1 = 0x01;                                                        //P1.0高阻
  30.         P2M0 = 0x00;
  31.         P2M1 = 0x00;
  32.         P3M0 = 0x00;
  33.         P3M1 = 0x00;
  34.         P4M0 = 0x00;
  35.         P4M1 = 0x00;
  36.         P5M0 = 0x00;
  37.         P5M1 = 0x00;
  38.         P6M0 = 0x00;
  39.         P6M1 = 0x00;
  40.         P7M0 = 0x00;
  41.         P7M1 = 0x00;

  42. //        P_SW1|=0x40;                                                        //P3.6/P3.7串口1 RXD/TXD
  43.         InitUart();                                                                //初始化串口
  44.         InitADC();                                                                //初始化ADC
  45.         while (1)
  46.         {
  47.                 ADC_RESH=GetADCResult(0);                //读取第0通道转换值

  48.                 count=(VCC_V*(long)ADC_RESH/256+5)/10;//计算电压值(四舍五入),精度1%V
  49.                 dis_buf[0]=count/100%10+'0';        //数据分解到显示缓存数组
  50.                 dis_buf[2]=count/10%10+'0';
  51.                 dis_buf[3]=count%10+'0';
  52.                 for(i=0;i<6;i++)
  53.                         SendData(dis_buf[i]);                //串口发送电压值文本字符
  54.                 delayms(500);                                                //延时0.5s
  55.     }
  56. }
  57. /*----------------------------
  58. 初始化ADC
  59. ----------------------------*/
  60. void InitADC()
  61. {
  62.     P1ASF = 0x01;                   //设置P1.0口为AD口
  63.     ADC_RES = 0;                    //清除结果寄存器
  64.     ADC_CONTR = ADC_POWER | ADC_SPEEDLL;
  65.     delayms(2);                       //ADC上电并延时
  66. }
  67. /*----------------------------
  68. 读取ADC结果
  69. ----------------------------*/
  70. uchar GetADCResult(uchar ch)
  71. {
  72.     ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ch | ADC_START;
  73.     _nop_();                        //等待4个NOP
  74.     _nop_();
  75.     _nop_();
  76.     _nop_();
  77.     while (!(ADC_CONTR & ADC_FLAG));//等待ADC转换完成
  78.     ADC_CONTR &= ~ADC_FLAG;         //清除完成标志
  79.     return ADC_RES;                 //返回8位ADC结果
  80. }

  81. /*----------------------------
  82. 初始化串口
  83. ----------------------------*/
  84. void InitUart()
  85. {
  86.         SCON = 0x5A;                        //设置串口为8位可变波特率
  87.         AUXR |= 0x01;                        //串口1选择定时器2为波特率发生器
  88.         AUXR |= 0x04;                        //定时器2时钟为Fosc,即1T
  89.         T2L = 0xE0;                                //设定定时初值
  90.         T2H = 0xFE;                                //设定定时初值
  91.         AUXR |= 0x10;                        //启动定时器2
  92. }
  93. /*----------------------------
  94. 发送串口数据
  95. ----------------------------*/
  96. void SendData(uchar dat)
  97. {
  98.     while (!TI);                        //等待前一个数据发送完成
  99.     TI = 0;                                        //清除发送标志
  100.     SBUF = dat;                        //发送当前数据
  101. }

  102. /*--------------------------------
  103.    ms延时子程序 11.0592MHz时钟
  104. ---------------------------------*/
  105. void delayms(uint k)
  106. {
  107.     uint i,j;            
  108.     for(i=k;i>0;i--)   
  109.         for(j=1150;j>0;j--);
  110. }

复制代码



回复

使用道具 举报

ID:662183 发表于 2019-12-15 22:29 | 显示全部楼层
baiyun1234 发表于 2019-12-14 10:22
谢谢师傅,能发送了。我还有几个不明白的地方,再给我讲讲呗。
1.那个1ms延时参数问题,我用你的 ...

我觉得很正常,首先你测量仪表有误差。电源也不是精密电源
回复

使用道具 举报

ID:609989 发表于 2019-12-16 08:16 | 显示全部楼层
无敌小杰杰 发表于 2019-12-14 20:00
你这个是程序问题还是硬件问题啊?

硬件没问题,已经做好了
回复

使用道具 举报

ID:609989 发表于 2019-12-16 16:50 | 显示全部楼层
wulin 发表于 2019-12-14 20:18
1.常见的for嵌套延时函数是按8051/12T单片机编写的,用在1T单片机延时时间就不对了。keil_5调试显示的是9 ...

谢谢师傅,关于多通道与单通道采集,基本上是掌握了。还有个小问题。
1.关于:ADC_RESH=GetADCResult(0);  //赋值
             count=(VCC_V*(long)ADC_RESH/256+5)/10;#define VCC_V  5000
  ①.这个(long)ADC_RESH表示是:unsigned long int 表示一个双字,但是下面的子函数:uchar GetADCResult(uchar ch),定义类型是char,这里为什么会写long呢?
  ②.这里面的+5什么意思,8位采集的计算公式应:ADC_RES=256*vin(模拟电压)/vcc(标准电压)么?
  ③.如果按照5V计算,算出来是小数,多以用VCC_V=5000mv放大,而后面的/10,是因为结果是0.01V,即1%,用来缩小。可以这么理解吧。。。
    ※这个我找了个简单方法,直接算成float,用printf串口打印出去就好了。

  
回复

使用道具 举报

ID:213173 发表于 2019-12-16 21:00 | 显示全部楼层
baiyun1234 发表于 2019-12-16 16:50
谢谢师傅,关于多通道与单通道采集,基本上是掌握了。还有个小问题。
1.关于:ADC_RESH=GetADCRes ...

你理解的基本是对的。
在实际应用中使用10位ADC每2ms取样一次,ADC_RESH累加后除10得到平均值,也就是简单滤波。
取20ms做取样周期的目的是因为50HZ工频共模干扰无处不在,这样得到的ADC值相对稳定些。
下面是实际应用中的平均法滤波程序,使用浮点运算当然可以,但速度要慢许多。
#define VCC_V  5000//参考电压mV
unsigned int ADC_RESH;
unsigned int ADC_RESX;
void Smooth()
{
        static unsigned char count=0;                //计数器变量
        ADC_RESH += GetADCResult(0);                //累加10次采样值
        count++;                                                        //计数器变量自+1
        if(count>=10)                                                //如果计数到10次采样
        {
                count1=0;                                                //计数器清0
                ADC_RESX=(VCC_V*(long)ADC_RESH/1024+5)/10;//强制转换数据类型(32位)运算,否则数据超出16位溢出而出错,得到(4舍5入)mV
        }
}
回复

使用道具 举报

ID:213173 发表于 2019-12-16 21:48 | 显示全部楼层
baiyun1234 发表于 2019-12-16 16:50
谢谢师傅,关于多通道与单通道采集,基本上是掌握了。还有个小问题。
1.关于:ADC_RESH=GetADCRes ...

修正:
void Smooth()
{
        static unsigned char count=0;                //计数器变量
        ADC_RESH += GetADCResult(0);                //累加10次采样值
        count++;                                                        //计数器变量自+1
        if(count>=10)                                                //如果计数到10次采样
        {
                count1=0;                                                //计数器清0
                ADC_RESX=(VCC_V*(long)ADC_RESH/1024+5)/10;//强制转换数据类型(32位)运算,否则数据超出16位溢出而出错,得到(4舍5入)mV
              ADC_RESH=0;//清0
        }
}
回复

使用道具 举报

ID:609989 发表于 2019-12-17 17:51 | 显示全部楼层
wulin 发表于 2019-12-16 21:48
修正:
void Smooth()
{

谢谢了师傅,讲的真到位。今天我把10位计算方式的也搞出来了。
还有最后有一点:ADC_RESX=(VCC_V*(long)ADC_RESH/1024+5)/10;这里面的+5。可以这么理解吗?
1.因为最后我们是想以整数方式发送(正常计算出来是小数),所以5V放大1000倍是5000,千分之一精度,进行计算VCC_V*(long)ADC_RESH/1024;
2.然后因为要求精度是0.01V,%1V精度,所以我们四舍五入(为了精确点),所以在后面+5,得出VCC_V*(long)ADC_RESH/1024+5;
3.最后/10,算成%1,即0.01V。

回复

使用道具 举报

ID:213173 发表于 2019-12-17 19:44 | 显示全部楼层
本帖最后由 wulin 于 2019-12-18 07:42 编辑
baiyun1234 发表于 2019-12-17 17:51
谢谢了师傅,讲的真到位。今天我把10位计算方式的也搞出来了。
还有最后有一点:ADC_RESX=(VCC_V* ...

你的理解基本正确
+5是为了小数部分四舍五入,除10是因为ADC_RESH是累加10次采样值,需要除10得平均值。
设想一下:VCC=5V,最大采样值1023=0x03ff,
计算公式:IN=VCC*1023/1024=4.9951171875
采用四舍五入:IN=5000*1023/1024+5=5000.1171875mV
取整数后误差更小些。

评分

参与人数 1黑币 +10 收起 理由
baiyun1234 + 10

查看全部评分

回复

使用道具 举报

ID:609989 发表于 2019-12-18 13:26 | 显示全部楼层
wulin 发表于 2019-12-17 19:44
你的理解基本正确
+5是为了小数部分四舍五入,除10是因为ADC_RESH是累加10次采样值,需要除10得平均值。 ...

恩,明白了,谢谢
回复

使用道具 举报

ID:911776 发表于 2021-5-31 10:03 | 显示全部楼层
wulin 发表于 2019-12-13 16:54
只发不收不必用开串口中断。1T单片机的延时函数不能直接套用12T,要改参数。

为什么我这里SCON是一定要设置为0x5a, 才能有数据呢
回复

使用道具 举报

ID:718536 发表于 2021-6-1 14:36 | 显示全部楼层
好贴,好棒的问答。感谢大佬
回复

使用道具 举报

ID:839438 发表于 2021-7-10 00:01 | 显示全部楼层
哇,解决问题
回复

使用道具 举报

ID:57657 发表于 2021-7-10 19:15 | 显示全部楼层
wulin 发表于 2019-12-13 16:54
只发不收不必用开串口中断。1T单片机的延时函数不能直接套用12T,要改参数。

1T单片机可以用12T的定时器延时,但不能用12T的软件延时。
回复

使用道具 举报

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

本版积分规则

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

Powered by 单片机教程网

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