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



五、如何在工程中实现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的电压变化值,可以精确获取到。

八、结论

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

        学挖掘机技术哪家强?

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值