dql2016 发表于 2021-6-20 14:18

RSL10 adc多通道数据采集

<p>我的项目需要用到adc多通道数据采集,RSL10自带了ADC外设,查看了数据手册后发现这个ADC还不错,14bit分辨率,精度对于一些常见的电池电压采集、传感器数据采集足够。</p>

<p>ADC外设支持8个通道信号输入,其中4个是内部信号,另外4个可以通过外部管脚输入。根据原理图,这个4个支持ADC的管脚是DIO0~DIO3,如下原理图所示:</p>

<p>&nbsp;</p>

<p>ADC的4个内部信号分别是AOUT、VDDC、VBAT/2、GND,通过软件配置。</p>

<p></p>

<p></p>

<p>RSL10的ADC外设支持低速和高速两种采样模式,对于不同的应用来说十分灵活,尤其是低功耗的应用,有些间隔长时间采集一次的模拟量完全不需要高速数据采集。</p>

<p>ADC输出数据是14bit的,输入电压最大基本为2V。</p>

<p>ADC支持中断功能,采样分为常规模式和连续模式,连续模式无法同时采集多通道的数据。在常规模式下,ADC多通道依次采样,每采样8次触发一次中断。</p>

<p></p>

<p>ADC支持多种采样率,不同的采样率输入电压范围略微不同。下表中带H的是低速模式使用,不带H的是高速模式使用。测试代码中将全部8个通道都配置。在中断中读取ADC数据并通过RTT打印和oled显示。</p>

<pre>
<code class="language-cpp">/* ----------------------------------------------------------------------------
* app.c
* - Simple application using a DIO5 input to control a DIO6 output
* ------------------------------------------------------------------------- */

#include "app.h"
#include &lt;printf.h&gt;
#include "oled.h"

volatile uint8_t data_ready_flag;
volatile uint8_t bat_error_flag;
volatile float adc_value;

/* ----------------------------------------------------------------------------
* Function      : void ADC_BATMON_IRQHandler(void)
* ----------------------------------------------------------------------------
* Description   : Handle ADC and BATMON interrupts. When the interrupt is from
*               ADC add the ADC value to the accumulator adc_value and
*               increment the counter. When counter reach 100 calculate the
*               average and set data_ready.
*               When the interrupt is from battery monitor set bat_error.
* Inputs      : None
* Outputs       : None
* Assumptions   : None
* ------------------------------------------------------------------------- */
void ADC_BATMON_IRQHandler(void)
{
    static uint32_t adc_samples_count = 0;
    static uint32_t adc_filter_sum    = {0.0f};

    /* Get status of ADC */
    uint32_t adc_status = Sys_ADC_Get_BATMONStatus();
    if ((adc_status &amp; (1 &lt;&lt; ADC_BATMON_STATUS_BATMON_ALARM_STAT_Pos)) == BATMON_ALARM_TRUE)
    {
      /* Battery monitor alarm status is set */
      bat_error_flag = 1;
      /* Clear the battery monitor status and counter */
      Sys_ADC_Clear_BATMONStatus();
      uint32_t dummy = ADC-&gt;BATMON_COUNT_VAL;
    }
    else
    {
      adc_filter_sum = adc_filter_sum + ADC-&gt;DATA_TRIM_CH;
      adc_filter_sum = adc_filter_sum + ADC-&gt;DATA_TRIM_CH;
      adc_filter_sum = adc_filter_sum + ADC-&gt;DATA_TRIM_CH;
      adc_filter_sum = adc_filter_sum + ADC-&gt;DATA_TRIM_CH;
      adc_filter_sum = adc_filter_sum + ADC-&gt;DATA_TRIM_CH;
      adc_filter_sum = adc_filter_sum + ADC-&gt;DATA_TRIM_CH;
      adc_filter_sum = adc_filter_sum + ADC-&gt;DATA_TRIM_CH;
      adc_filter_sum = adc_filter_sum + ADC-&gt;DATA_TRIM_CH;
      adc_samples_count++;
      if (adc_samples_count == ADC_FILTER_COUNTS)
      {
            adc_samples_count = 0;
            adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value = (adc_filter_sum + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_filter_sum    = 0;
            adc_filter_sum    = 0;
            adc_filter_sum    = 0;
            adc_filter_sum    = 0;
            adc_filter_sum    = 0;
            adc_filter_sum    = 0;
            adc_filter_sum    = 0;
            adc_filter_sum    = 0;
            data_ready_flag   = 1;
      }
    }
}

void DIO0_IRQHandler(void)
{
    if (DIO_DATA-&gt;ALIAS == 0)
    {
      /* Button is pressed: Ignore next interrupt.This is required to deal with the debounce circuit limitations. */
      PRINTF("=== BUTTON_DIO interrupt detect ===\n");
    }
}

/* ----------------------------------------------------------------------------
* Description   : Initialize the system, then toggle DIO6 as controlled by DIO5 (press to toggle input/output).
* ------------------------------------------------------------------------- */

void Initialize(void)
{
    /* Mask all interrupts */
    __set_PRIMASK(PRIMASK_DISABLE_INTERRUPTS);

    /* Disable all existing interrupts, clearing all pending source */
    Sys_NVIC_DisableAllInt();
    Sys_NVIC_ClearAllPendingInt();

    /* Prepare the 48 MHz crystal
       * Start and configure VDDRF */
        ACS_VDDRF_CTRL-&gt;ENABLE_ALIAS = VDDRF_ENABLE_BITBAND;
        ACS_VDDRF_CTRL-&gt;CLAMP_ALIAS= VDDRF_DISABLE_HIZ_BITBAND;

        /* Wait until VDDRF supply has powered up */
        while (ACS_VDDRF_CTRL-&gt;READY_ALIAS != VDDRF_READY_BITBAND);

        /* Enable RF power switches */
        SYSCTRL_RF_POWER_CFG-&gt;RF_POWER_ALIAS   = RF_POWER_ENABLE_BITBAND;

        /* Remove RF isolation */
        SYSCTRL_RF_ACCESS_CFG-&gt;RF_ACCESS_ALIAS = RF_ACCESS_ENABLE_BITBAND;

        /* Start the 48 MHz oscillator without changing the other register bits */
        RF-&gt;XTAL_CTRL = ((RF-&gt;XTAL_CTRL &amp; ~XTAL_CTRL_DISABLE_OSCILLATOR) | XTAL_CTRL_REG_VALUE_SEL_INTERNAL);

        /* Enable 48 MHz oscillator divider to generate an 48 MHz clock. */
        RF_REG2F-&gt;CK_DIV_1_6_CK_DIV_1_6_BYTE = CK_DIV_1_6_PRESCALE_1_BYTE;

        /* Wait until 48 MHz oscillator is started */
        while (RF_REG39-&gt;ANALOG_INFO_CLK_DIG_READY_ALIAS != ANALOG_INFO_CLK_DIG_READY_BITBAND);

        /* Switch to (divided 48 MHz) oscillator clock */
        Sys_Clocks_SystemClkConfig(JTCK_PRESCALE_1 |EXTCLK_PRESCALE_1 |SYSCLK_CLKSRC_RFCLK);

        /* Test DIO12 to pause the program to make it easy to re-flash */
        DIO-&gt;CFG = DIO_MODE_INPUT | DIO_WEAK_PULL_UP | DIO_LPF_DISABLE | DIO_6X_DRIVE;
        while (DIO_DATA-&gt;ALIAS == 0);

        Sys_DIO_Config(0, DIO_MODE_INPUT);
        Sys_DIO_Config(1, DIO_MODE_INPUT);
        Sys_DIO_Config(2, DIO_MODE_INPUT);
        Sys_DIO_Config(3, DIO_MODE_INPUT);

        /* Setup DIO6 as a GPIO output for LED*/
        Sys_DIO_Config(LED_DIO, DIO_MODE_GPIO_OUT_0);

        Sys_DIO_Config(OLED_SCL, DIO_MODE_GPIO_OUT_0);
        Sys_DIO_Config(OLED_SDA, DIO_MODE_GPIO_OUT_0);

        Sys_DIO_Config(JQ8900, DIO_MODE_GPIO_OUT_0);

        /* Setup DIO5 as a GPIO intput for BUTTON*/
        /* Setup DIO5 as a GPIO input with interrupts on transitions
       * Use the integrated debounce circuit to ensure that only a
       * single interrupt event occurs for each push of the pushbutton.
       * The debounce circuit always has to be used in combination with the
       * transition mode to deal with the debounce circuit limitations.
       * A debounce filter time of 50 ms is used. */
        Sys_DIO_Config(BUTTON_DIO, DIO_MODE_GPIO_IN_0 | DIO_WEAK_PULL_UP | DIO_LPF_DISABLE);
        Sys_DIO_IntConfig(0, DIO_EVENT_FALLING_EDGE | DIO_SRC(BUTTON_DIO) | DIO_DEBOUNCE_ENABLE,DIO_DEBOUNCE_SLOWCLK_DIV1024, 49);

        /* Set the ADC configuration */
        Sys_ADC_Set_Config(ADC_VBAT_DIV2_NORMAL | ADC_NORMAL | ADC_PRESCALE_320H);

        /* Set the battery monitor interrupt configuration */
        Sys_ADC_Set_BATMONIntConfig(INT_EBL_ADC | ADC_INT_CH0| ADC_INT_CH1| ADC_INT_CH2| ADC_INT_CH3| ADC_INT_CH4| ADC_INT_CH5| ADC_INT_CH6|ADC_INT_CH7 | INT_EBL_BATMON_ALARM);

        /* Configure both input selection for an ADC channel to GND so the OFFSET is subtracted automatically to result. */
        Sys_ADC_InputSelectConfig(ADC_0_CHANNEL, ADC_POS_INPUT_DIO0 | ADC_NEG_INPUT_GND);
        Sys_ADC_InputSelectConfig(ADC_1_CHANNEL, ADC_POS_INPUT_DIO1 | ADC_NEG_INPUT_GND);
        Sys_ADC_InputSelectConfig(ADC_2_CHANNEL, ADC_POS_INPUT_DIO2 | ADC_NEG_INPUT_GND);
        Sys_ADC_InputSelectConfig(ADC_3_CHANNEL, ADC_POS_INPUT_DIO3 | ADC_NEG_INPUT_GND);
        Sys_ADC_InputSelectConfig(4, ADC_POS_INPUT_AOUT | ADC_NEG_INPUT_GND);
        Sys_ADC_InputSelectConfig(5, ADC_POS_INPUT_VDDC | ADC_NEG_INPUT_GND);
        Sys_ADC_InputSelectConfig(6, ADC_POS_INPUT_VBAT_DIV2 | ADC_NEG_INPUT_GND);
        Sys_ADC_InputSelectConfig(7, ADC_POS_INPUT_GND | ADC_NEG_INPUT_GND);

        /* Enable interrupts */
        NVIC_EnableIRQ(DIO0_IRQn);
        NVIC_EnableIRQ(ADC_BATMON_IRQn);

    printf_init();

    /* Unmask all interrupts */
    __set_PRIMASK(PRIMASK_ENABLE_INTERRUPTS);
}

void Send_ADC_Value(void)
{
    uint8_t size;
    float adc_in_volts;
    int32_t v;

    for(int i=0;i&lt;8;i++)
    {
            adc_in_volts = (adc_value * 2.0f) / (float)16384;//2^14=16384 Vref=2.0V
            /* Multiply by 2 as we measure VBAT/2 and divide by a gain factor to convert the value from ADC. */
            if(i==6)
            {
                    adc_in_volts = 2*(adc_value * 2.0f) / (float)16384;
            }
            PRINTF("ADC[%d] input value = %d mV\n",i,(int32_t)(adc_in_volts * 1000));
            v=(int32_t)(adc_in_volts * 1000);
            if(i&lt;4)
            {
                    OLED_ShowNum(40,2*i,v,get_num_len(v),16);
            }
    }
}

void Delay1us(uint32_t us)
{
        PRINTF("delay_us=%ld\n",(uint32_t)(us * SystemCoreClock/1000000));
        Sys_Delay_ProgramROM((uint32_t)(us * SystemCoreClock/1000000));
}

void SendData (uint8_t addr)
{
    uint8_t i;

    JQ8900_SDA_SET();//开始拉搞
    Delay1us ( 1000 );
    JQ8900_SDA_CLR();//开始引导码
    Delay1us ( 4000 );//此处延时最少要大于2ms

    for ( i = 0; i &lt; 8; i++ )
    {
            JQ8900_SDA_SET();
      if ( addr &amp; 0x01 ) //高电平:低电平=3:1表示数据位1,每个位用两个脉冲表示
      {
            Delay1us ( 1200 );
            JQ8900_SDA_CLR();
            Delay1us ( 400 );
      }
      else            //高电平:低电平=1:3表示数据位0 ,每个位用两个脉冲表示
      {
            Delay1us ( 1200 );
            JQ8900_SDA_CLR();
            Delay1us ( 400 );
      }
      addr &gt;&gt;= 1;
    }
    JQ8900_SDA_SET();
}

int main(void)
{
    Initialize();
    PRINTF("DEVICE INITIALIZED\n");

        /*SendData(0x0a);
        Delay1us ( 10000 );
        SendData(0x01);
        Delay1us ( 10000 );
        SendData(0x0b);*/

    OLED_Init();
    OLED_Clear();

    OLED_ShowString(128-3*8,0,"MHz",16);
    OLED_ShowString(0,0,"adc0:",16);
    OLED_ShowString(0,2,"adc1:",16);
    OLED_ShowString(0,4,"adc2:",16);
    OLED_ShowString(0,6,"adc3:",16);

        data_ready_flag = 0;
        bat_error_flag= 0;
        adc_value = 0.0f;
        adc_value = 0.0f;
        adc_value = 0.0f;
        adc_value = 0.0f;
        adc_value = 0.0f;
        adc_value = 0.0f;
        adc_value = 0.0f;
        adc_value = 0.0f;

    while (1)
    {
      Sys_Watchdog_Refresh();
      Sys_GPIO_Toggle(LED_DIO);
      //PRINTF("SystemCoreClock = %dMHz\n", SystemCoreClock/1000000);
      OLED_ShowNum(88,0,SystemCoreClock/1000000,get_num_len(SystemCoreClock/1000000),16);
      Sys_Delay_ProgramROM((uint32_t)(0.1 * SystemCoreClock));
      if (data_ready_flag == 1)
      {
            data_ready_flag = 0;
            Send_ADC_Value();
      }
      if (bat_error_flag == 1)
      {
            bat_error_flag = 0;
            PRINTF("VBAT voltage lower than threshold!!\n");
      }
    }
}
</code></pre>

<p>下面就实际测试下通过ADC读取的VDDC和用万用表测量的VDDC的差别,以此来粗略判断ADC采集的精度。VDDC是内部很多数字模块的电源,按照手册说法,这个电压可配置,通常是1.2V。</p>

<p>根据原理图VDDC在电路板上的位置就是电容C5那里。</p>

<p>实测为1.0972V</p>

<p>adc测量为通道5值为1105mV,可见内部ADC精度不差,测量下电源电压等足够了。</p>

<p></p>

<p></p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

Jacktang 发表于 2021-6-20 17:20

<p>RSL10自带了ADC外设,确实很方便,楼主效果实测为1.0972V,这个精度很可以了</p>

soso 发表于 2021-6-21 09:26

<p>很详细,期待后续。</p>
页: [1]
查看完整版本: RSL10 adc多通道数据采集