[BLE]CC2640之ADC功能实现和供电电压的采集

来源:互联网 发布:巫师3年度版优化 编辑:程序博客网 时间:2024/06/05 17:14

一、开篇
Write programs that do one thing and do it well ~

发现很多人关于使用CC2640/CC2650的过程中比较难以应对的问题就是实现ADC,为了方便大家,所以有了本篇博客,都是一些自己的理解,不对的地方请大家指正。TI的这款新品上市不久,还有需要需要更新的地方,尤其是以往以其文档多为优势,而如今到了CC26xx这却几乎没什么可参考的文档了,大家就等等吧,肯定会越来越完善的。本篇主要介绍如何使用TI官方给的driverlib实现ADC的使用,SCS也能控制ADC的实现,但是这个过于复杂,生产的代码也比较难以理解,所以还是使用driverlib吧。

二、版权声明
博主:summer

声明:喝水不忘挖井人,转载请注明出处。

原文地址:http://blog.csdn.net/qq_21842557

联系方式:dxl0725@126.com

技术交流QQ:1073811738

三、试验平台
Software Version:BLE_STACK_CC26XX_2.1.0

Hardware Version:CC2640/CC2650

IDE:IAR 7.40

四、基础知识

1) ADC模数转换器,顾名思义也就是输入模拟量输出数字量,我们感知世界上的任何事物都是感知的模拟量,但是对应于CPU而言只能识别非0即1的数字量,所以产生了ADC,ADC的实现过程中主要的两个过程就是采用保持和量化(感觉在讲废话)。在外设接口中ADC属于较难得部分了,关于ADC的一些基本概念还是比较多的,其中INL和DNL需要特别注意一下(Ps:我旁边坐了一位专门研究ADC的beauty,是她讲的。平时实验室关于ADC不懂得都找她解决),因为本篇博文主要是讲解关于CC26xx的ADC实现的,它的ADC是内部集成的12位ADC,采样速率高达200Ks/s,所以本篇博文不再详细介绍在ADC芯片选型方面要考虑的参数了,但是本篇涉及的参数肯定是选型时需要考虑的。

2)下面结合CC26xx的Datasheet介绍一些关于ADC的基本参数,下图是ADC在整个IC内部的位置,由下图可知CC26xx的ADC在RF core内部,由M0核控制,在实现ADC时可以使用官方的driverlib也可以使用官方特有的SCS平台去控制实现ADC的基本功能,CC26xx的Sensor controller的功能还是相当强大的,可惜我还不会使用,在低功耗中使用UART就是靠的它用流控控制的。
这里写图片描述
3)如下图中所示,由于INL和DNL(具体含义去百度吧)以及offset的原因,在采集数据时就会存在一个偏差,这个是不可避免的,这个采集数据的偏差还是自行软件处理吧,内部ADC的功耗还是比较低的。

这里写图片描述
这里写图片描述
这里写图片描述

五、如何在工程中实现ADC
1、首先你要知道有一个driverlib库的存在,这个里面TI官方给出了使用时调用的API,此处就不在赘述了,路径太长不方便写。
2、然后CC26xx芯片不是所有的IO口都可以作为的ADC接口使用的,官方在TRM中也给出了介绍,如下图。
这里写图片描述
3、include库文件

 在simpleBLEPeripheral.c文件中添加如下头文件。
  #include <driverlib/aux_adc.h>    #include <driverlib/aux_wuc.h>  

4、配置ADC(最关键的一部)
代码实现部分,放在simpleBLEPeripheral.c即可。ADC不需要像使用UART那样再去配置IO口映射。

//*****************************************************************************  //! \brief Selects internal or external input for the ADC  //! Note that calling this function also selects the same input for AUX_COMPB.  //! \param input  //!     Internal/external input selection:  //!     - \ref ADC_COMPB_IN_VDD1P2V  //!     - \ref ADC_COMPB_IN_VSSA  //!     - \ref ADC_COMPB_IN_VDDA3P3V  //!     - \ref ADC_COMPB_IN_AUXIO7 //DIO9  //!     - \ref ADC_COMPB_IN_AUXIO6 //DIO8  //!     - \ref ADC_COMPB_IN_AUXIO5 //DIO7  //!     - \ref ADC_COMPB_IN_AUXIO4 //DIO6  //!     - \ref ADC_COMPB_IN_AUXIO3 //DIO5  //!     - \ref ADC_COMPB_IN_AUXIO2  //!     - \ref ADC_COMPB_IN_AUXIO1  //!     - \ref ADC_COMPB_IN_AUXIO0  //*****************************************************************************  uint32_t AdcOneShotRead(uint8_t auxIo)  {      uint32_t turnedOnClocks = 0;      //////////// Config clock/////////////////////      // Only turn on clocks that are not already enabled. Not thread-safe, obviously.      turnedOnClocks |= AUXWUCClockStatus(AUX_WUC_ADC_CLOCK) ? 0 : AUX_WUC_ADC_CLOCK;      turnedOnClocks |= AUXWUCClockStatus(AUX_WUC_ADI_CLOCK) ? 0 : AUX_WUC_ADI_CLOCK;      turnedOnClocks |= AUXWUCClockStatus(AUX_WUC_SOC_CLOCK) ? 0 : AUX_WUC_SOC_CLOCK;      // Enable clocks and wait for ready      AUXWUCClockEnable(turnedOnClocks);        while(AUX_WUC_CLOCK_OFF == AUXWUCClockStatus(turnedOnClocks));      /////// Seclect auxIO  /////////////      AUXADCSelectInput(auxIo);      ////////// Enable ///////////      AUXADCEnableSync(AUXADC_REF_FIXED, AUXADC_SAMPLE_TIME_2P7_US, AUXADC_TRIGGER_MANUAL);      delay(10);      //Scaling disable      AUXADCDisableInputScaling();         AUXADCGenManualTrigger();       // Trigger sample       uint32_t adcValue = AUXADCReadFifo();         AUXADCDisable();//Power_Saving      return adcValue;  }  

在上述实现代码中所涉及的一些问题,讲述一下自己的理解,如果仅仅是为了实现ADC功能那么下面可以不用看了,下面讲的比较细了。
1)首先是ADC使能函数

    跟踪查看函数原型如下。
//*****************************************************************************  // Enables the ADC for synchronous operation  //*****************************************************************************  void AUXADCEnableSync(uint32_t refSource, uint32_t sampleTime, uint32_t trigger)  {      // Enable the ADC reference, with the following options:      // - SRC: Set when using relative reference      // - REF_ON_IDLE: Set when using fixed reference and sample time < 21.3 us      uint8_t adcref0 = refSource | ADI_4_AUX_ADCREF0_EN_M;      if (!refSource && (sampleTime < AUXADC_SAMPLE_TIME_21P3_US)) {          adcref0 |= ADI_4_AUX_ADCREF0_REF_ON_IDLE_M;      }      ADI8BitsSet(AUX_ADI4_BASE, ADI_4_AUX_O_ADCREF0, adcref0);      // Enable the ADC clock      HWREG(AUX_WUC_BASE + AUX_WUC_O_ADCCLKCTL) = AUX_WUC_ADCCLKCTL_REQ_M;      while (!(HWREG(AUX_WUC_BASE + AUX_WUC_O_ADCCLKCTL) & AUX_WUC_ADCCLKCTL_ACK_M));      // Enable the ADC data interface      if (trigger == AUXADC_TRIGGER_MANUAL) {          // Manual trigger: No need to configure event routing from GPT          HWREG(AUX_ANAIF_BASE + AUX_ANAIF_O_ADCCTL) = AUX_ANAIF_ADCCTL_START_SRC_NO_EVENT0 | AUX_ANAIF_ADCCTL_CMD_EN;      } else {          // GPT trigger: Configure event routing via MCU_EV to the AUX domain          HWREG(EVENT_BASE + EVENT_O_AUXSEL0) = trigger;          HWREG(AUX_ANAIF_BASE + AUX_ANAIF_O_ADCCTL) = AUX_ANAIF_ADCCTL_START_SRC_MCU_EV | AUX_ANAIF_ADCCTL_CMD_EN;      }      // Release reset and enable the ADC      ADI8BitsSet(AUX_ADI4_BASE, ADI_4_AUX_O_ADC0, ADI_4_AUX_ADC0_EN_M | ADI_4_AUX_ADC0_RESET_N_M |                                                   (sampleTime << ADI_4_AUX_ADC0_SMPL_CYCLE_EXP_S));  }  
  使用方法:AUXADCEnableSync(AUXADC_REF_FIXED, AUXADC_SAMPLE_TIME_2P7_US, AUXADC_TRIGGER_MANUAL)。比较重要的就是第一和第二个参数。其中第一个参数AUXADC_REF_FIXED是4.3V,关于这个4.3的由来,TI一直没有给相关的介绍,我觉得应该是由电源端引入然后经过内部的boost电路升压到4.3V作为这个参考电压的。其中还有几个可以作为参考电压的,我没有试过,只是用了AUXADC_REF_FIXED这一个,不敢妄下结论,大家可以去实测一下;第二个参数就是所谓的采样时间,其倒数就是采样频率,因为这套代码配置是使用的同步采样(Sync),所以采样频率的配置就不能像用单独的用timer去跟随实现非同步采样(Async)产生的采样频率多了,只有官方给的几个可以用,感觉这个影响不大,接近就行了,如下。
*****************************************************************************  // Defines for ADC sampling type for synchronous operation.  *****************************************************************************  #define AUXADC_SAMPLE_TIME_2P7_US           3  #define AUXADC_SAMPLE_TIME_5P3_US           4  #define AUXADC_SAMPLE_TIME_10P6_US          5  #define AUXADC_SAMPLE_TIME_21P3_US          6  #define AUXADC_SAMPLE_TIME_42P6_US          7  #define AUXADC_SAMPLE_TIME_85P3_US          8  #define AUXADC_SAMPLE_TIME_170_US           9  #define AUXADC_SAMPLE_TIME_341_US           10  #define AUXADC_SAMPLE_TIME_682_US           11  #define AUXADC_SAMPLE_TIME_1P37_MS          12  #define AUXADC_SAMPLE_TIME_2P73_MS          13  #define AUXADC_SAMPLE_TIME_5P46_MS          14  #define AUXADC_SAMPLE_TIME_10P9_MS          15  

如果使用使用非同步采样(Async)的话,CC26xx就不能进入standby状态了,官方解释如下图(应该没有理解错吧)。
这里写图片描述
2)Scaling disable

    作用就是缩小ADC的IO口的采样电压的范围,disable以后最大输入电压1.49就达到满量程了(实测),所以这样可以提升单位电压内的分辨率(1.49/4096)。

这里写图片描述
函数原型:

//*****************************************************************************  // Disables scaling of the ADC input  //*****************************************************************************  // Register: ADI_4_AUX_O_ADC1  // Field:     [0] SCALE_DIS  // Disable capacitive input voltage scaling. Should only be 1 for test  // purposes.  // 0: ADC input is scaled from 0-4.3V to 0-1.4V internally  // 1: ADC input is not scaled. Do not exceed 1.4V on input  #define ADI_4_AUX_ADC1_SCALE_DIS                                    0x00000001  #define ADI_4_AUX_ADC1_SCALE_DIS_BITN                                        0  #define ADI_4_AUX_ADC1_SCALE_DIS_M                                  0x00000001  #define ADI_4_AUX_ADC1_SCALE_DIS_S                                           0  */  //*****************************************************************************  void  AUXADCDisableInputScaling(void)  {      ADI8BitsSet(AUX_ADI4_BASE, ADI_4_AUX_O_ADC1, ADI_4_AUX_ADC1_SCALE_DIS_M);  } 

六、关于精度和测试结果
TI的 R&D给出了一份他们在实验的测试结果,大家可以按照这个作为比较,实测和这份数据差不多。
这里写图片描述
七、附加题(你肯定懂得它的含义)
什么?不知道“附加题”的意义何在?那你肯定没经历过高考的洗礼~~~~

    原本想再单独写一篇博文介绍关于供电电压采集的,但是感觉比较简单就和ADC放在一起讲吧,它俩比较接近。首先,很多工程师在做类似于手环、心率计、HCG等等时可能都需要使用到芯片的ADC功能,又想实时的获取供电电压,但是内部ADC的数量又是有限的,如何实现多路使用ADC呢,大家首先想到的应该就是切换使用内部ADC,但是在一路正在使用的时候直接切换掉是不是对正在ADC采集的数据导致错误呢,或者说这个切换的时间如何把握呢。所以CC26xx有了这个Battery Monitor的功能,让工程师测量供电电压时不再占用外部adc_io接口。关于IO口重映射是如何实现的,我也不了解(who knows? tell me.),反正觉得比较牛掰,用起来非常顺手,尤其是在layout布局布线的时候,个人臆测可能是使用电子开关之类的方法切换的吧,电子开关的功耗也非常小,搞硬件的就是牛掰。为什么这些高科技都是国外的,那么有哪些是“黑科技”掌握在国人手里呢?如下就是,国人之骄傲~~~

好吧,废话不多说。 关于供电电压的测试问题,这个可以不使用ADC测试了,CC26xx内部有专门测试芯片供电电压的(还有测试芯片温度的,不再赘述测试温度)。

这里写图片描述

   1、代码实现    在simpleBLEPeripheral.c文件中添加如下头文件。
#include <driverlib/aon_batmon.h> 

在需要的地方使用如下代码获取当前的电池电压。

 //BAT Monitor  AONBatMonEnable();  // <int.frac> format size <3.8> in units of volt  //返回值32位中[10:8]代表INT 。[7:0]代表FRAC ,对于小数部分,一个单位代表0.00390625v,小数部分的分辨率只有50mV(TYP)  batval = AONBatMonBatteryVoltageGet();  

AONBatMonBatteryVoltageGet()的函数原型是:

__STATIC_INLINE uint32_t  AONBatMonBatteryVoltageGet(void)  {      uint32_t ui32CurrentBattery;      ui32CurrentBattery = HWREG(AON_BATMON_BASE + AON_BATMON_O_BAT);      // Return the current battery voltage measurement.      return (ui32CurrentBattery >> AON_BATMON_BAT_FRAC_S);  }  

返回值是根据不同的位代表芯片供电电压的整数部分和小数部分的,详细介绍如下。

//*****************************************************************************  // Register: AON_BATMON_O_BAT  //*****************************************************************************  // Field:  [10:8] INT  // Integer part:  // 0x0: 0V + fractional part  // ...  // 0x3: 3V + fractional part  // 0x4: 4V + fractional part  #define AON_BATMON_BAT_INT_M                                        0x00000700  #define AON_BATMON_BAT_INT_S                                                 8  // Field:   [7:0] FRAC  // Fractional part, standard binary fractional encoding.  // 0x00: .0V  // ...  // 0x20: 1/8 = .125V  // 0x40: 1/4 = .25V  // 0x80: 1/2 = .5V  // ...  // 0xA0: 1/2 + 1/8 = .625V  // ...  // 0xFF: Max  #define AON_BATMON_BAT_FRAC_M                                       0x000000FF  #define AON_BATMON_BAT_FRAC_S                                                0  

测试结果:只测试了0.1V的电压变化值,可以精确获取到。

0 0