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

一、开篇

        Write programs that do one thing and do it well ~~~~~

        发现非常多人关于使用CC2640/CC2650的过程中比較难以应对的问题就是实现ADC。为了方便大家,所以有了本篇博客,都是一些自己的理解。不正确的地方请大家指正。

        TI的这款新品上市不久,还有须要须要更新的地方,尤其是以往以其文档多为优势。而现在到了CC26xx这却差点儿没什么可參考的文档了,大家就等等吧。肯定会越来越完好的。

        本篇主要介绍怎样使用TI官方给的driverlib实现ADC的使用,SCS也能控制ADC的实现,可是这个过于复杂。生产的代码也比較难以理解,所以还是使用driverlib吧。


三、试验平台

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的功耗还是比較低的。



五、怎样在project中实现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口映射。

//*****************************************************************************
//! rief 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:
//!     - 
ef ADC_COMPB_IN_VDD1P2V
//!     - 
ef ADC_COMPB_IN_VSSA
//!     - 
ef ADC_COMPB_IN_VDDA3P3V
//!     - 
ef ADC_COMPB_IN_AUXIO7 //DIO9
//!     - 
ef ADC_COMPB_IN_AUXIO6 //DIO8
//!     - 
ef ADC_COMPB_IN_AUXIO5 //DIO7
//!     - 
ef ADC_COMPB_IN_AUXIO4 //DIO6
//!     - 
ef ADC_COMPB_IN_AUXIO3 //DIO5
//!     - 
ef ADC_COMPB_IN_AUXIO2
//!     - 
ef ADC_COMPB_IN_AUXIO1
//!     - 
ef 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放在一起讲吧,它俩比較接近。首先,非常多project师在做类似于手环、心率计、HCG等等时可能都须要使用到芯片的ADC功能。又想实时的获取供电电压。可是内部ADC的数量又是有限的。怎样实现多路使用ADC呢,大家首先想到的应该就是切换使用内部ADC,可是在一路正在使用的时候直接切换掉是不是对正在ADC採集的数据导致错误呢。或者说这个切换的时间怎样把握呢。所以CC26xx有了这个Battery Monitor的功能。让project师測量供电电压时不再占用外部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的电压变化值。能够精确获取到。

八、结论

        实在不知道写什么了,还是打个广告吧。

        学挖掘机技术哪家强?

原文地址:https://www.cnblogs.com/yfceshi/p/7199126.html