FireBLE

FireBLE 是一个面向于打造智能生活的开源平台,以BLE(Bluetooth Low Energy)技术为核心,拥有超低的功耗、不俗的处理能力和广泛的应用场合,专注于更智能、高效率的工作模式,让生活在科技中更安全、方便、快捷。也许您一个不经意的想法与FireBLE擦出的火花,会在这片原野上燎出火焰,甚至燃烧整个世界。

ADC 驱动

更新时间:2017-08-08 阅读:2909

前言

adc主要实现了将模拟电压转换数字形式,使得CPU可以识别外部的电压大小。QN902x系列拥有2/5路可配置量程的8/10/12位ADC,内置1V标准基准电压,也可外接基准电压,ADC的采样率高达16MHz。

初始化

系统平台初始化

相比之前的例程中,在系统初始化中adc还需要加入一些平台上的初始化,在SystemInit函数中,之前的子模块初始化需要改为

/*
 **************************
 * Sub module clock setting
 **************************
 */
  // DC-DC
  dc_dc_enable(true);
  // QN platform initialization
  plf_init(NORMAL_MODE, __XTAL, QN_32K_RCO, NULL, 0);

GPIO配置

接下来是GPIO的配置

    adc_io_config();


P30,P31,P06,P07分别为ADC的输入通道AIN0,AIN1,AIN2,AIN3,P05作为ADC的GPIO触发引脚。

// pin mux
syscon_SetPMCR0(QN_SYSCON, P00_UART0_TXD_PIN_CTRL
                         | P05_ADCT_PIN_CTRL
#if ADC_EXT_REF_EN==TRUE
                         | P06_AIN2_PIN_CTRL
                         | P07_AIN3_PIN_CTRL
#else
                         | P06_SW_DAT_PIN_CTRL
                         | P07_SW_CLK_PIN_CTRL
#endif
                         | P17_UART0_RXD_PIN_CTRL
                         );
syscon_SetPMCR1(QN_SYSCON, P27_PWM0_PIN_CTRL
                         | P26_PWM1_PIN_CTRL
                         | P31_AIN1_PIN_CTRL
                         | P30_AIN0_PIN_CTRL
                         );

使能AIN0,AIN1,AIN2,AIN3引脚的模拟输入功能

analog_pin_enable(AIN0, MASK_ENABLE);
analog_pin_enable(AIN1, MASK_ENABLE);
analog_pin_enable(AIN2, MASK_ENABLE);
analog_pin_enable(AIN3, MASK_ENABLE);

串口初始化

初始化串口实乃意外,乃是为了更好的看到ADC的结果。

#if (__AHB_CLK == 32000UL)
    uart_init(QN_UART0, __USART_CLK, UART_1200);
#else
    uart_init(QN_UART0, __USART_CLK, UART_115200);
#endif
    uart_tx_enable(QN_UART0, MASK_ENABLE);

ADC初始化

ADC的初始化,本例中,不开启32K晶振,不适用外部晶振。

    // ADC initialization    
#if ADC_WORKING_AT_32K==TRUE
    clk32k_enable(__32K_TYPE);
    adc_init(ADC_SINGLE_WITHOUT_BUF_DRV, ADC_CLK32K_16000, ADC_INT_REF, ADC_12BIT);
#else
 
#if ADC_EXT_REF_EN==TRUE
    adc_init(ADC_SINGLE_WITHOUT_BUF_DRV, ADC_CLK_1000000, ADC_EXT_REF2, ADC_12BIT);
    //adc_init(ADC_SINGLE_WITHOUT_BUF_DRV, ADC_CLK_1000000, ADC_EXT_REF1, ADC_12BIT);
#else    
    adc_init(ADC_SINGLE_WITHOUT_BUF_DRV, ADC_CLK_1000000, ADC_INT_REF, ADC_12BIT);
#endif
#endif

现在来分析一下

adc_init(ADC_SINGLE_WITHOUT_BUF_DRV, ADC_CLK_1000000, ADC_INT_REF, ADC_12BIT);

这句代码,第一个参数比较复杂,在工程中搜索(查到定义是跳不过去的,因为这是枚举变量),发现如下语句,在ADC采集有两种方式,一种是差分采集,一种是单信号采集,并且采集的时候如果使用内部buffer的话,不能检测到0.2v一下的电压,但是如果基准电压的1.5倍小于VDD-0.2的话,可以扩大量程到0.2V~1.5*VREF。对于例程来说,配置为单信号采集,不采用系统buffer,采样频率为1MHz,采用内部基准电压,ADC的位数为12位。

/// ADC input mode
enum ADC_IN_MOD
{
    ADC_DIFF_WITH_BUF_DRV = 0,      /*!< ADC differential input with buffer, input singal
 0.2 =< VIN(V) <= VDD-0.2, ADC result [-2048, 2047] map to [-VREF, VREF). */
    ADC_DIFF_WITHOUT_BUF_DRV,       /*!< ADC differential input without buffer, input singal
 0 =< VIN(V) <= VDD, and should have enough driving capability, ADC result [-2048, 2047] map to [-VREF, VREF). */
    ADC_SINGLE_WITH_BUF_DRV,        /*!< ADC single-ended input with buffer, input singal
 0.2 =< VIN(V) <= 1.5*VREF <= VDD-0.2, ADC result [x, 2047] map to [0.2, 1.5*VREF). */
    ADC_SINGLE_WITHOUT_BUF_DRV      /*!< ADC single-ended input without buffer, input singal
 0 =< VIN(V) <= VREF <= VDD, and should have enough driving capability, ADC result [0, 2047] map to [0, VREF). */
};
 
/// ADC reference voltage
enum ADC_REF
{
    ADC_INT_REF = 0,    /*!< Internal reference, VREF = 1.0V */
    ADC_EXT_REF1,       /*!< External reference1(with buffer and gain=2, input PIN is P0.7):
 VREF = 2*EXT_REF1 (0 < EXT_REF1 < (VDD-1.0)/2). */
    ADC_EXT_REF2        /*!< External reference2(without buffer, input PIN is P0.7): 
VERF = EXT_REF2 (0 < EXT_REF2 < VDD), EXT_REF2 should have driving capability. */
};

ADC采集

adc采集配置声明,adc采集方式比较多,在不同需要的采集之前需要先配置采集方式,例程展示有GPIO触发采集、定时器流控采集、软件触发采集、抽取滤波采集、采样比较。

// Read configuration
adc_read_configuration read_cfg;

GPIO触发采集

配置采集触发源为GPIO_P05

    read_cfg.trig_src = ADC_TRIG_GPIO;
    syscon_SetPMCR2WithMask(QN_SYSCON, SYSCON_MASK_ADCT_PIN_SEL, ADC_GPIO15_TRIG);

配置PWM输出,将PWM_CH0与ADC_TRIG_GPIO相连,即可通过IO触发ADC进行电压采集了。

    // triger by gpio in burst or burst scan mode, connect PWM output to ADC trigger PIN
    pwm_init(PWM_CH0);
    pwm_config(PWM_CH0, PWM_PSCAL_DIV, PWM_COUNT_US(50, PWM_PSCAL_DIV), PWM_COUNT_US(25, PWM_PSCAL_DIV));
    pwm_enable(PWM_CH0, MASK_ENABLE);

同样先设置adc采集的标志位,再在回调函数中清零

    adc_done = 0;

adc采集模式为突发模式,采集通道为AIN0,采集512个数据存入buf数组中,等待采集完成。

    // modify here
    read_cfg.mode = BURST_MOD;
    read_cfg.start_ch = AIN0;
    read_cfg.end_ch = AIN0;
    adc_read(&read_cfg, buf, 512, adc_test_cb);
    while (adc_done == 0);

由于采用的是外部缓存,所以buf为adc_example.c中声明的全局变量。

int16_t buf[512];

打印ADC的采样结果

    for (int i = 0; i < 512; i++) {
        printf("%d\t %d\r\n", buf[i], ADC_RESULT_mV(buf[i]));
    }

计算ADC采样结果的平均值,并打印出来。程序运行到这就算结束了。

    int sum = 0;
    for (int i = 0; i < 10; i++) {
        sum += buf[511 - 2*i];
    }
    sum = sum / 10;
    printf("average: %d\t %d\r\n", sum, ADC_RESULT_mV(sum));

定时器触发采集

配置触发源为定时器触发,配置定时器为50us触发一次。

    read_cfg.trig_src = ADC_TRIG_TOVF1;
    // triger by timer1 overflow
    timer_init(QN_TIMER1, NULL);
    timer_pwm_config(QN_TIMER1, TIMER_PSCAL_DIV, TIMER_COUNT_US(100, TIMER_PSCAL_DIV), TIMER_COUNT_US(50, TIMER_PSCAL_DIV));
    timer_enable(QN_TIMER1, MASK_ENABLE);

同样先设置adc采集的标志位,再在回调函数中清零

    adc_done = 0;

adc采集模式为突发模式,采集通道为AIN0,采集512个数据存入buf数组中,等待采集完成。

    // modify here
    read_cfg.mode = BURST_MOD;
    read_cfg.start_ch = AIN0;
    read_cfg.end_ch = AIN0;
    adc_read(&read_cfg, buf, 512, adc_test_cb);
    while (adc_done == 0);

由于采用的是外部缓存,所以buf为adc_example.c中声明的全局变量。

int16_t buf[512];

打印ADC的采样结果

    for (int i = 0; i < 512; i++) {
        printf("%d\t %d\r\n", buf[i], ADC_RESULT_mV(buf[i]));
    }

计算ADC采样结果的平均值,并打印出来。程序运行到这就算结束了。

    int sum = 0;
    for (int i = 0; i < 10; i++) {
        sum += buf[511 - 2*i];
    }
    sum = sum / 10;
    printf("average: %d\t %d\r\n", sum, ADC_RESULT_mV(sum));

软件触发采集

配置触发源为软件触发。软件触发无法控制adc的采样速度和时间。

    read_cfg.trig_src = ADC_TRIG_SOFT;

同样先设置adc采集的标志位,再在回调函数中清零

    adc_done = 0;

adc采集模式为突发模式,采集通道为AIN0,采集512个数据存入buf数组中,等待采集完成。

    // modify here
    read_cfg.mode = BURST_MOD;
    read_cfg.start_ch = AIN0;
    read_cfg.end_ch = AIN0;
    adc_read(&read_cfg, buf, 512, adc_test_cb);
    while (adc_done == 0);

由于采用的是外部缓存,所以buf为adc_example.c中声明的全局变量。

int16_t buf[512];

打印ADC的采样结果

    for (int i = 0; i < 512; i++) {
        printf("%d\t %d\r\n", buf[i], ADC_RESULT_mV(buf[i]));
    }

计算ADC采样结果的平均值,并打印出来。程序运行到这就算结束了。

    int sum = 0;
    for (int i = 0; i < 10; i++) {
        sum += buf[511 - 2*i];
    }
    sum = sum / 10;
    printf("average: %d\t %d\r\n", sum, ADC_RESULT_mV(sum));

抽取滤波采集

抽取滤波采集一般用于对高速信号的采样中。在对高速信号上采样,因为数据量过大,往往很难进行实时的数据处理,需要采用数字下变频(DDC)技术,将采样得到的高速率信号变成低速率基带信号,以便进行下一步的信号处理。下图为对信号进行采样频为64的ADC数据采集。

#if ADC_DECIMATION_EN == TRUE
    adc_decimation_enable(DECI_RATE_64, MASK_ENABLE);
#endif

打印ADC的采样结果

    for (int i = 0; i < 512; i++) {
        printf("%d\t %d\r\n", buf[i], ADC_RESULT_mV(buf[i]));
    }

计算ADC采样结果的平均值,并打印出来。程序运行到这就算结束了。

    int sum = 0;
    for (int i = 0; i < 10; i++) {
        sum += buf[511 - 2*i];
    }
    sum = sum / 10;
    printf("average: %d\t %d\r\n", sum, ADC_RESULT_mV(sum));

ADC比较器

将ADC作为比较器来使用。

#if ADC_COMPARATOR_EN == TRUE
    //adc_compare_init(DECI_DATA, 2500, -2000, adc_WCMP_cb);
    adc_compare_init(ADC_DATA, 600, -600, adc_WCMP_cb);
#endif

打印adc采集电压值的电压采集比较结果

int m = 0;
int n = 0;
for (int i = 0; i < 512; i++) {
   if (buf[i] > 600) {
        m++;
   }
   else if (buf[i] < -600) {
       n++;
   }
}
printf("m = %d\t n = %d\r\n", m, n);

打印ADC的采样结果

    for (int i = 0; i < 512; i++) {
        printf("%d\t %d\r\n", buf[i], ADC_RESULT_mV(buf[i]));
    }

计算ADC采样结果的平均值,并打印出来。程序运行到这就算结束了。

    int sum = 0;
    for (int i = 0; i < 10; i++) {
        sum += buf[511 - 2*i];
    }
    sum = sum / 10;
    printf("average: %d\t %d\r\n", sum, ADC_RESULT_mV(sum));

内置温度传感器

QN902x内置有温度传感器,可以通过ADC读取温度传感器的电压值以计算当前温度值。
打开温度传感器

temp_sensor_enable(MASK_ENABLE);

初始化ADC,采用比较带缓存方式采集,采样频率为1MHz,基准电压为内部基准1V,采用12位ADC采集。

    int16_t tempv;
    adc_init(ADC_DIFF_WITH_BUF_DRV, ADC_CLK_1000000, ADC_INT_REF, ADC_12BIT);

配置为软件触发,触发模式为突发模式,采样通道为内部TEMP通道,清零标志位开始读取

    adc_done = 0;
    read_cfg.trig_src = ADC_TRIG_SOFT;
    read_cfg.mode = BURST_MOD;
    read_cfg.start_ch = TEMP;
    read_cfg.end_ch = TEMP;
    adc_read(&read_cfg, &tempv, 1, adc_test_cb);
    while (adc_done == 0);

打印出计算后的温度值

    printf("temperature: %0.1f\r\n", (float)(TEMPERATURE_X10(tempv)/10.0));

内置电池电量检测器

QN902x内置有电池电量检测器,可以通过ADC读取电池的电压值以计算当前剩余电量值。
打开电池电量检测器

    battery_monitor_enable(MASK_ENABLE);

初始化ADC,采用单信号带缓存方式采集,采样频率为1MHz,基准电压为内部基准1V,采用12位ADC采集。

    int16_t battv;
    adc_init(ADC_SINGLE_WITH_BUF_DRV, ADC_CLK_1000000, ADC_INT_REF, ADC_12BIT);

配置为软件触发,触发模式为突发模式,采样通道为内部BATT通道,清零标志位开始读取

    adc_done = 0;
    read_cfg.trig_src = ADC_TRIG_SOFT;
    read_cfg.mode = BURST_MOD;
    read_cfg.start_ch = BATT;
    read_cfg.end_ch = BATT;
    adc_read(&read_cfg, &battv, 1, adc_test_cb);
    while (adc_done == 0);

打印出计算后的电池剩余电量值