找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 144780|回复: 55
打印 上一主题 下一主题
收起左侧

学51单片机-基于PCF8591的AD采样和DA输出

  [复制链接]
跳转到指定楼层
楼主
ID:74835 发表于 2015-4-8 22:48 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
      
      首先思考一个问题,我们的世界是数字的还是模拟的?
      当然是模拟的了,所有的量都是在一定范围内连续变化的。我们为了能够更加方便的描述这些量,对它们进行了数字化。而数字量就不一样了,它是分立的的几个值。
      举个例子,我们形容一个人的身高,模拟的说法是一米七到一米七五之间,数字的说法就是一米七三。
接下来说AD转换器,它的出现也是为了让我们能更方便、更直接的描述电压的高低。AD转换器,英文全称为Analog-to-Digital Converter,是模拟量到数字量的一个转换过程,主要用于电压的采集。它的出现就如同有了一把尺子,很容易就能量出电压的高低。
在电子设备中,经常要检测各种模拟量:温度、压力、速度、流量、重力加速度等等,这些模拟量都被相应的传感器转换为电压信号,我们只需要测量电压的高低,就能得到相应参数。
AD的主要参数有哪些?
1、AD的位数:表明这个AD共有2^n个刻度,8位AD,输出的刻度是0~255.
2、分辨率:就是AD能够分辨的最小的模拟量变化,假设5.10V的系统用8位的AD采样,那么它能分辨的最小电压就是5.10/255=0.02V。
3、INL:Interger NONliner 积分非线性度,表示了ADC器件在所有的数值点上对应的模拟值,和真实值之间误差最大的那一点的误差值。也就是,输出数值偏离线性最大的距离。单位是LSB(即最低位所表示的量)。比如12位ADC:TLC2543,INL值为1LSB。那么,如果基准4.095V,测某电压得的转换结果是1000,那么,真实电压值可能分布在0.999~1.001V之间。
4、DNL:Differencial NonLiner-差分非线性度,理论上说,模数器件相邻量个数据之间,模拟量的差值都是一样的。就相一把疏密均匀的尺子。但实际并不如此。一把分辨率1毫米的尺子,相邻两刻度之间也不可能都是1毫米整。那么,ADC相邻两刻度之间最大的差异就叫差分非线性值(Differencial NonLiner)。DNL值如果大于1,那么这个ADC甚至不能保证是单调的,输入电压增大,在某个点数值反而会减小。这种现象在SAR(逐位比较)型ADC中很常见。
5、基准源:有内部基准源、外部基准源等等。
6、转换速率:也就是转换周期的倒数,转换周期就是完成一次AD转换所需的时间。
今天要用到的器件是PCF8591,为什么选它?太多的开发板上用它做演示了,而且还是IIC总线通信的。既学习了AD采样,又学习了IIC总线。
先上应用电路:
      如上图所示,PCF8591的9脚和10脚,一个是数据线SDA,一个是时钟线SCL。分别接到单片机的P2.0 , P2.1上面。
为什么选这两个引脚?因为51单片机上没有IIC总线接口,需要用普通的IO模拟,所以它随便选了两个IO接上就行。
VREF是什么?基准电压,也是它能测量的最大电压。
如何控制?今天先不说IIC总线,只说控制流程。
看器件手册可以知道:
分四步:
1、发送地址字节,选择该器件。
2、发送控制字节,选择相应通道。               //
3、重新发送地址字节,选择该器件。
4、接收目标通道的数据。
这次的程序流程是:AD采样,串口发送,循环执行。
下面是AD采样源代码:
  1. /**********************51单片机学习例程************************
  2. *  平台:Keil U4 + STC89C52
  3. *  名称:AD采样+串口发送
  4. *  编写:起航
  5. *  晶体:11.0592MHZ
  6. ******************************************************************/
  7. #include <reg52.h>
  8. #include <intrins.h>

  9. typedef unsigned char uint8;
  10. typedef unsigned int uint16;

  11. #define SLAVEADDR  0x90                 //定义器件地址
  12. #define nops()  do{_nop_();_nop_();_nop_();_nop_();_nop_();} while(0) //定义空指令


  13. sbit SCL = P2^1;       //I2C  时钟
  14. sbit SDA = P2^0;       //I2C  数据

  15. void delay(uint16 n)
  16. {
  17.         while (n--);
  18. }
  19. /**
  20. * 函数: i2c_start()
  21. * 功能: 启动i2c                  起始信号
  22. */
  23. void i2c_start()
  24. {
  25.         SCL = 1;
  26.         nops();
  27.         SDA = 1;
  28.         nops();
  29.         SDA = 0;
  30.         nops();
  31.         SCL = 0;
  32. }

  33. /**
  34. * 函数: i2c_stop()
  35. * 功能: 停止i2c
  36. */
  37. void i2c_stop()
  38. {
  39.         SCL = 0;
  40.         nops();
  41.         SDA = 0;
  42.         nops();
  43.         SCL = 1;
  44.         nops();
  45.         SDA = 1;
  46.         nops();
  47. }

  48. /**
  49. * 函数: i2c_ACK(bit ck)
  50. * 功能: ck为1时发送应答信号ACK,
  51. *       ck为0时不发送ACK
  52. */
  53. void i2c_ACK(bit ck)
  54. {
  55.     if (ck)
  56.                 SDA = 0;
  57.     else
  58.                 SDA = 1;
  59.     nops();
  60.     SCL = 1;
  61.     nops();
  62.     SCL = 0;
  63.         nops();
  64.     SDA = 1;
  65.     nops();
  66. }

  67. /**
  68. * 函数: i2c_waitACK()
  69. * 功能: 返回为0时收到ACK
  70. *       返回为1时没收到ACK
  71. */
  72. bit i2c_waitACK()
  73. {
  74.         SDA = 1;
  75.         nops();
  76.         SCL = 1;
  77.         nops();
  78.         if (SDA)
  79.         {   
  80.                 SCL = 0;
  81.                 i2c_stop();
  82.                 return 1;
  83.         }
  84.         else
  85.         {  
  86.                 SCL = 0;
  87.                 return 0;
  88.         }
  89. }

  90. /**
  91. * 函数: i2c_sendbyte(uint8 bt)
  92. * 功能: 将输入的一字节数据bt发送
  93. */
  94. void i2c_sendbyte(uint8 bt)
  95. {
  96.     uint8 i;
  97.    
  98.     for(i=0; i<8; i++)
  99.     {  
  100.         if (bt & 0x80)
  101.                         SDA = 1;
  102.         else
  103.                         SDA = 0;
  104.         nops();
  105.         SCL = 1;
  106.         bt <<= 1;
  107.         nops();      
  108.         SCL = 0;
  109.     }
  110. }

  111. /**
  112. * 函数: i2c_recbyte( )
  113. * 功能: 从总线上接收1字节数据
  114. */
  115. uint8 i2c_recbyte()
  116. {
  117.         uint8 dee, i;
  118.        
  119.         for (i=0; i<8; i++)
  120.         {
  121.                 SCL = 1;   
  122.                 nops();
  123.                 dee <<= 1;
  124.                 if (SDA)
  125.                         dee = dee | 0x01;
  126.                 SCL = 0;
  127.                 nops();
  128.         }
  129.        
  130.         return dee;
  131. }

  132. /**
  133. * 函数: i2c_readbyte
  134. * 输入: addr
  135. * 功能: 读出一字节数据
  136. * 返回值: 0->成功  1->失败
  137. */
  138. bit i2c_readbyte(uint8 com, uint8 *dat)
  139. {       
  140.         i2c_start();
  141.         i2c_sendbyte(SLAVEADDR);    //地址
  142.     if (i2c_waitACK())
  143.                 return 1;
  144.         i2c_sendbyte(com);        //控制字节
  145.     if (i2c_waitACK())
  146.                 return 1;
  147.         i2c_start();
  148.         i2c_sendbyte(SLAVEADDR+1); //地址
  149.     if (i2c_waitACK())
  150.                 return 1;
  151.         *dat = i2c_recbyte();      //读数据
  152.         i2c_ACK(0);                //因为只读一字节数据,不发送ACK信号
  153.         i2c_stop();   
  154.        
  155.         return 0;
  156. }

  157. /**
  158. * UART初始化
  159. * 波特率:9600
  160. */
  161. void uart_init(void)
  162. {
  163.     ET1=0;
  164.     TMOD = 0x21;        // 定时器1工作在方式2(自动重装)
  165.     SCON = 0x50;        // 10位uart,允许串行接受

  166.     TH1 = 0xFD;
  167.     TL1 = 0xFD;

  168.     TR1 = 1;
  169. }

  170. /**
  171. * UART 发送一字节
  172. */
  173. void UART_Send_Byte(uint8 dat)
  174. {
  175.         SBUF = dat;
  176.         while (TI == 0);
  177.         TI = 0;
  178. }


  179. main()
  180. {
  181.         uint8 ans;

  182.         uart_init();

  183.         while(1)
  184.         {

  185.                 i2c_readbyte(0x43, &ans);
  186.             
  187.                 UART_Send_Byte(ans);
  188.                 delay(50000);
  189.         }
  190. }
复制代码
本程序下载链接(附带PCF8591中文资料pdf资料): PCF8591AD采样.rar (459.16 KB, 下载次数: 946)
-------------------------------------------------------------------------------------

下面介绍PCF8591的DA输出:

      忽然发现,已经写到AD/DA这里来了。严格来说,已经不是51单片机的内容了,而是周边应用电路的一些东西。这些东西涉及的知识面比较广,什么都有可能提到。
      关于AD/DA,或者其它设备,我的学习思路是先模仿,再深究。
      因为无论是课本也好,器件手册也好,大部分讲的都是原理或者寄存器,起到的是一个工具书的作用,类似于语文课上用的字典。但是这就出现了一个问题,很多人想通过看课本或者看器件手册的方式来掌握这些设备。
      这个思路有问题吗?没有问题吗?
      还记得我刚才说的话么,它们就类似于语文课上用的字典,但是,有谁是通过看字典学会说话的!!!
      我们都是通过模仿别人学会说话的,遇到不认识的字才去查字典!但是很多人或者很多学校都把这两件事的顺序搞反了。
      记得之前我在英飞凌官网进行芯片选型,网页都翻烂了,找不到合适的。因为英飞凌不是我家开的,我不能保证每次都能顺利的找到我想要的东西。
      但是,我同事参加了一次电子展,在展会上遇到了英飞凌的展台,然后问他们,他们一听我们的需求,马上找出一堆能满足我们要求的芯片。
      这就是思路的问题!
      扯远了,说回到DA控制。
DA转换(Digital to Analog),是将数字量变成模拟量的一个过程。AD与DA刚好是相反的两个过程,AD是把模拟信号变成单片机可识别的数字信号;DA是把单片机可识别的数字信号变成连续变化的模拟量。这两种功能的应用范围都非常广泛!
      主要参数如下,具体什么意思就不讲了,大家可以百度一下。(因为我编不出来了...)
1)分辩率(Resolution)
2) 转换速率(Conversion Rate)
3)量化误差 (Quantizing Error)
4)偏移误差(Offset Error)
5)满刻度误差(Full Scale Error)
6)线性度(Linearity)
其他指标还有:绝对精度(Absolute Accuracy) ,相对精度(Relative Accuracy),微分非线性,单调性和无错码,总谐波失真(Total Harmonic Distotortion缩写THD)和积分非线性。
看到这么多参数,是不是很晕?
搞了这些年电子,感触最深的有一点是:无论做什么,先求有,再求好!
不要总想一口吃个胖子,没那么多天才。参数是很多,但是没要求你一下子全都记住,甚至你可以只记一两个。先把大致的应用流程跑一遍,跑下来,你才对这个设备有一个整体的概念,然后针对你的要求,比对相应的参数,进行修改、调试。
哪怕是在工作中,也不一定会考虑全部的参数。例如转换时间,我到现在也没认真看PIC内部的AD采样转换时间有多久,因为有些设备对实时性要求很低,速度慢一些也没事。
然后是控制流程,认真看器件手册的,或者看了昨天日志的,都知道是怎样一个流程:
第一步:写器件地址;
第二步:写控制位。
第三步:写入数据。
好了,上程序。通过DA输出渐变电压控制LED,形成呼吸灯的效果。里面有个警告:
*** WARNING L16: UNCALLED SEGMENT, IGNORED FOR OVERLAY PROCESS
      大家可以研究下,如何消除警告。
程序源码如下:
  1. /**********************51单片机学习例程************************
  2. *  平台: Keil U34 + STC89C52RD
  3. *         名称:IIC协议 PCF8591ADDA转换        ,此程序通过IIC协议对DAAD芯片操作, 并输出模拟量,用LED亮度渐变指示
  4. *  编写:起航
  5. *  晶振:11.0592MHZ
  6. ******************************************************************/

  7. #include<reg52.h>    //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义
  8. #include <intrins.h> //包含NOP空指令函数_nop_();

  9. #define AddWr 0x90   //写数据地址
  10. #define AddRd 0x91   //读数据地址

  11. sbit RST=P2^4;   //关掉时钟芯片输出
  12. sbit Sda=P2^0;      //定义总线连接端口
  13. sbit Scl=P2^1;
  14. sbit Fm=P2^3;          //FM
  15. sbit dula=P2^6;
  16. sbit wela=P2^7;
  17. // bit ADFlag;          //定义AD采样标志位
  18. unsigned char code Datatab[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//7段数共阴码管段码表
  19. data unsigned char  Display[8];//定义临时存放数码管数值
  20. /*------------------------------------------------
  21.                     延时程序
  22. ------------------------------------------------*/
  23. void mDelay(unsigned char j)
  24. {
  25.   unsigned int i;
  26.   for(;j>0;j--)
  27.      {
  28.           for(i=0;i<125;i++)
  29.              {;}
  30.           }
  31.   }
  32. /*------------------------------------------------
  33.                     初始化定时器1
  34. ------------------------------------------------*/
  35. void Init_Timer1(void)
  36. {
  37. TMOD |= 0x10;                             
  38. TH1=0xff;                              /* Init value */
  39. TL1=0x00;
  40. //PT1=1;                   /* 优先级    */
  41. EA=1;                      /* interupt enable */
  42. ET1=1;                     /* enable timer1 interrupt */
  43. TR1=1;  
  44. }
  45. /*------------------------------------------------
  46.                     启动IIC总线
  47. ------------------------------------------------*/
  48.   void Start(void)
  49.   {
  50.    Sda=1;
  51.    _nop_();
  52.    Scl=1;
  53.    _nop_();
  54.    Sda=0;
  55.    _nop_();
  56.    Scl=0;
  57.   }


  58. /*------------------------------------------------
  59.                     停止IIC总线
  60. ------------------------------------------------*/
  61.   void Stop(void)
  62.   {
  63.    Sda=0;
  64.    _nop_();
  65.    Scl=1;
  66.    _nop_();
  67.    Sda=1;
  68.    _nop_();
  69.    Scl=0;
  70.    }


  71. /*------------------------------------------------
  72.                    应答IIC总线
  73. ------------------------------------------------*/
  74.    void Ack(void)
  75.    {
  76.     Sda=0;
  77.         _nop_();
  78.         Scl=1;
  79.         _nop_();
  80.         Scl=0;
  81.         _nop_();
  82.         }

  83. /*------------------------------------------------
  84.               发送一个字节
  85. ------------------------------------------------*/
  86.          void Send(unsigned char Data)
  87.          {
  88.           unsigned char BitCounter=8;
  89.           unsigned char temp;

  90.           do
  91.             {
  92.                  temp=Data;
  93.                  Scl=0;
  94.                  _nop_();
  95.                  if((temp&0x80)==0x80)
  96.                     Sda=1;
  97.                  else
  98.                     Sda=0;

  99.                         Scl=1;
  100.                         temp=Data<<1;
  101.                         Data=temp;
  102.                         BitCounter--;
  103.                   }
  104.           while(BitCounter);
  105.               Scl=0;
  106.           }

  107. /*------------------------------------------------
  108.                     写入DA数模转换值
  109. ------------------------------------------------*/
  110.           void DAC(unsigned char Data)
  111.           {
  112.                    Start();
  113.                    Send(AddWr); //写入芯片地址
  114.                    Ack();
  115.                    Send(0x40);  //写入控制位,使能DAC输出
  116.                    Ack();
  117.                    Send(Data);  //写数据
  118.                    Ack();
  119.                    Stop();
  120.                  
  121.            }


  122.         void fmg(void)//fm关
  123.         {       
  124.         Fm=1;        //                关 fm
  125.         }
  126.                  void cmg(void)//数码管锁存函数                   关时钟DS1302
  127.         {
  128.         dula=1;
  129.         P0=0x00;
  130.         dula=0;
  131.         wela=1;
  132.         P0=0x00;
  133.         wela=0;
  134.         RST=0;                //                关时钟DS1302
  135.         }
  136. /*------------------------------------------------
  137.                    主程序
  138. ------------------------------------------------*/
  139.         void main()
  140.         {
  141.          unsigned char num;                   //DA数模输出变量
  142.    Init_Timer1();
  143.          cmg();//数码管锁存
  144.          fmg();
  145.          while(1)
  146.            {
  147.        DAC(num);       //DA输出,可以用LED模拟电压变化
  148.                    num++;          //累加,到256后溢出变为0,往复循环。显示在LED上亮度逐渐变化
  149.                    mDelay(20);     //延时用于清晰看出变化
  150.            }
  151.         }

复制代码
下载链接: PCF8591DAC_LED.zip (29.03 KB, 下载次数: 405)





评分

参与人数 3黑币 +15 收起 理由
爱拉屎 + 5 绝世好帖!
勇猛的大表哥 + 5 共享资料的黑币奖励!
vancee + 5 共享资料的黑币奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏21 分享淘帖 顶6 踩
回复

使用道具 举报

沙发
ID:81389 发表于 2015-5-28 18:23 | 只看该作者
请问楼主,PCF8591适合做心电信号的AD采样吗?求指导啊
回复

使用道具 举报

板凳
ID:84098 发表于 2015-6-27 19:37 | 只看该作者
看了一下,刚学哎
回复

使用道具 举报

地板
ID:86381 发表于 2015-7-21 16:47 | 只看该作者
正在学习AD/DA!谢谢
回复

使用道具 举报

5#
ID:86664 发表于 2015-7-26 11:15 | 只看该作者
不错,正在学ad/da
回复

使用道具 举报

6#
ID:87908 发表于 2015-8-8 11:18 | 只看该作者
很好很强大
回复

使用道具 举报

7#
ID:88201 发表于 2015-8-12 17:43 | 只看该作者
//unsigned char code Datatab[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
//data unsigned char  Display[8];
警告应该是因为这两句是多余的
回复

使用道具 举报

8#
ID:91409 发表于 2015-10-6 16:04 | 只看该作者
楼主,我是小白。收获好多,谢谢
回复

使用道具 举报

9#
ID:96325 发表于 2015-11-19 11:13 | 只看该作者
很好的东西~谢谢楼主
回复

使用道具 举报

10#
ID:96325 发表于 2015-11-19 11:14 | 只看该作者
很好的东西,谢谢楼主
回复

使用道具 举报

11#
ID:96909 发表于 2015-11-27 11:35 | 只看该作者
    请问“发送一个字节”这一段程序是干啥用的,为啥要用着个程序呢,谢谢我是小白
回复

使用道具 举报

12#
ID:97468 发表于 2015-11-28 15:52 | 只看该作者
很好的东西~谢谢楼主
回复

使用道具 举报

13#
ID:97896 发表于 2015-12-1 19:31 | 只看该作者
刚开始学数模转换,谢谢楼主啦。
回复

使用道具 举报

14#
ID:65439 发表于 2015-12-3 10:47 | 只看该作者
深度好贴,谢谢楼主了。
回复

使用道具 举报

15#
ID:97051 发表于 2015-12-21 10:50 | 只看该作者
很好的文章,刚好要用……
回复

使用道具 举报

16#
ID:102731 发表于 2016-1-11 10:51 | 只看该作者
楼主东西很好,对我帮助很大
回复

使用道具 举报

17#
ID:103715 发表于 2016-1-18 23:35 | 只看该作者
学习了,学习了,感谢楼主
回复

使用道具 举报

18#
ID:97171 发表于 2016-2-23 11:13 | 只看该作者

楼主,这两个圈里的器件指的应该是同一个器件吧?那为什么第152行代码中器件地址是SLAVEADDR,而第159行代码中器件地址是SLAVEADDR+1?为什么要加1呢?
回复

使用道具 举报

19#
ID:97171 发表于 2016-2-24 08:39 | 只看该作者
我突然明白了,152行的SLAVEADDR是要写,159行的SLAVEADDR+1是要读,所以要加1

评分

参与人数 1黑币 +3 收起 理由
勇猛的大表哥 + 3 赞一个!

查看全部评分

回复

使用道具 举报

20#
ID:119828 发表于 2016-5-10 16:27 | 只看该作者
学习了,谢谢!
回复

使用道具 举报

21#
ID:120508 发表于 2016-5-13 11:56 | 只看该作者
DA输出之前没怎么了解  今天恶补  谢谢
回复

使用道具 举报

22#
ID:122310 发表于 2016-5-21 09:52 | 只看该作者
谢谢分享!!
回复

使用道具 举报

23#
ID:123329 发表于 2016-5-26 14:33 | 只看该作者
很好,十分有用。感谢分享。
回复

使用道具 举报

24#
ID:123955 发表于 2016-5-30 10:45 | 只看该作者
请问,你们有遇到DA输出的时候电压不够的情况吗?测到VCC有5.19V,但是输出最大只能到4.42V,
回复

使用道具 举报

25#
ID:126889 发表于 2016-6-15 18:09 | 只看该作者
请问转换周期怎么计算,或者说具体采样频率怎么计算
回复

使用道具 举报

26#
ID:127352 发表于 2016-6-21 15:41 | 只看该作者
PCF8591和PCF8591T有什么区别吗?是不是管脚不同?
回复

使用道具 举报

27#
ID:141194 发表于 2016-10-3 11:57 | 只看该作者
楼主威武  楼主万岁万岁万万岁
回复

使用道具 举报

28#
ID:146352 发表于 2016-11-5 23:58 | 只看该作者
终于在这找到了,正需要
回复

使用道具 举报

29#
ID:147122 发表于 2016-11-9 20:30 | 只看该作者
涨姿势
回复

使用道具 举报

30#
ID:147122 发表于 2016-11-9 20:31 | 只看该作者
加油啊
回复

使用道具 举报

31#
ID:147740 发表于 2016-11-17 17:25 | 只看该作者
貌似很不错
回复

使用道具 举报

32#
ID:129418 发表于 2016-12-17 21:40 | 只看该作者
感谢分享
回复

使用道具 举报

33#
ID:170948 发表于 2017-3-27 15:50 | 只看该作者
没有黑币啊,好苦恼。。。。。。。
回复

使用道具 举报

34#
ID:190947 发表于 2017-4-19 22:15 | 只看该作者
w2 w3是什么东西??
回复

使用道具 举报

35#
ID:192309 发表于 2017-4-22 13:26 | 只看该作者
很给力!!
回复

使用道具 举报

36#
ID:73234 发表于 2017-5-7 09:52 | 只看该作者
谢谢楼主,毕设正好需要这个器件
回复

使用道具 举报

37#
ID:73234 发表于 2017-5-12 10:59 | 只看该作者
谢谢楼主,!!!
回复

使用道具 举报

38#
ID:184237 发表于 2017-8-22 11:06 | 只看该作者
请问,我没加上拉电阻,数值一直在跳,等我加上上拉电阻,求平均值后  数据会稳定下来吗
回复

使用道具 举报

39#
ID:279557 发表于 2018-2-7 09:45 | 只看该作者
厉害啊,好东西
回复

使用道具 举报

40#
ID:314244 发表于 2018-4-23 10:00 | 只看该作者
还没看见 不知道是不是深度好文,现在正在登陆
回复

使用道具 举报

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

本版积分规则

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

Powered by 单片机教程网

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