[经验分享] RSL10 adc多通道数据采集

dql2016   2021-6-20 14:18 楼主

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

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

 

4个ADC输入原理图.PNG ADC的4个内部信号分别是AOUT、VDDC、VBAT/2、GND,通过软件配置。

输入源配置.PNG

外部adc输入管脚.PNG

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

低速和高速采样配置.PNG ADC输出数据是14bit的,输入电压最大基本为2V。

输出数据格式和精度.PNG ADC支持中断功能,采样分为常规模式和连续模式,连续模式无法同时采集多通道的数据。在常规模式下,ADC多通道依次采样,每采样8次触发一次中断。

采样通道.PNG

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

/* ----------------------------------------------------------------------------
 * app.c
 * - Simple application using a DIO5 input to control a DIO6 output
 * ------------------------------------------------------------------------- */

#include "app.h"
#include <printf.h>
#include "oled.h"

volatile uint8_t data_ready_flag;
volatile uint8_t bat_error_flag;
volatile float adc_value[8];

/* ----------------------------------------------------------------------------
 * 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[8]    = {0.0f};

    /* Get status of ADC */
    uint32_t adc_status = Sys_ADC_Get_BATMONStatus();
    if ((adc_status & (1 << 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->BATMON_COUNT_VAL;
    }
    else
    {
        adc_filter_sum[0] = adc_filter_sum[0] + ADC->DATA_TRIM_CH[0];
        adc_filter_sum[1] = adc_filter_sum[1] + ADC->DATA_TRIM_CH[1];
        adc_filter_sum[2] = adc_filter_sum[2] + ADC->DATA_TRIM_CH[2];
        adc_filter_sum[3] = adc_filter_sum[3] + ADC->DATA_TRIM_CH[3];
        adc_filter_sum[4] = adc_filter_sum[4] + ADC->DATA_TRIM_CH[4];
        adc_filter_sum[5] = adc_filter_sum[5] + ADC->DATA_TRIM_CH[5];
        adc_filter_sum[6] = adc_filter_sum[6] + ADC->DATA_TRIM_CH[6];
        adc_filter_sum[7] = adc_filter_sum[7] + ADC->DATA_TRIM_CH[7];
        adc_samples_count++;
        if (adc_samples_count == ADC_FILTER_COUNTS)
        {
            adc_samples_count = 0;
            adc_value[0] = (adc_filter_sum[0] + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value[1] = (adc_filter_sum[1] + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value[2] = (adc_filter_sum[2] + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value[3] = (adc_filter_sum[3] + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value[4] = (adc_filter_sum[4] + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value[5] = (adc_filter_sum[5] + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value[6] = (adc_filter_sum[6] + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_value[7] = (adc_filter_sum[7] + (float)ADC_FILTER_COUNTS / 2.0f) / (float)ADC_FILTER_COUNTS;
            adc_filter_sum[0]    = 0;
            adc_filter_sum[1]    = 0;
            adc_filter_sum[2]    = 0;
            adc_filter_sum[3]    = 0;
            adc_filter_sum[4]    = 0;
            adc_filter_sum[5]    = 0;
            adc_filter_sum[6]    = 0;
            adc_filter_sum[7]    = 0;
            data_ready_flag   = 1;
        }
    }
}

void DIO0_IRQHandler(void)
{
    if (DIO_DATA->ALIAS[BUTTON_DIO] == 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->ENABLE_ALIAS = VDDRF_ENABLE_BITBAND;
	ACS_VDDRF_CTRL->CLAMP_ALIAS  = VDDRF_DISABLE_HIZ_BITBAND;

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

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

	/* Remove RF isolation */
	SYSCTRL_RF_ACCESS_CFG->RF_ACCESS_ALIAS = RF_ACCESS_ENABLE_BITBAND;

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

	/* Enable 48 MHz oscillator divider to generate an 48 MHz clock. */
	RF_REG2F->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->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->CFG[RECOVERY_DIO] = DIO_MODE_INPUT | DIO_WEAK_PULL_UP | DIO_LPF_DISABLE | DIO_6X_DRIVE;
	while (DIO_DATA->ALIAS[RECOVERY_DIO] == 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[8];
    int32_t v[8];

    for(int i=0;i<8;i++)
    {
    	adc_in_volts[i] = (adc_value[i] * 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[i] = 2*(adc_value[i] * 2.0f) / (float)16384;
    	}
    	PRINTF("ADC[%d] input value = %d mV\n",i,(int32_t)(adc_in_volts[i] * 1000));
    	v[i]=(int32_t)(adc_in_volts[i] * 1000);
    	if(i<4)
    	{
    		OLED_ShowNum(40,2*i,v[i],get_num_len(v[i]),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 < 8; i++ )
    {
    	JQ8900_SDA_SET();
        if ( addr & 0x01 ) //高电平:低电平=3:1表示数据位1,每个位用两个脉冲表示
        {
            Delay1us ( 1200 );
            JQ8900_SDA_CLR();
            Delay1us ( 400 );
        }
        else              //高电平:低电平=1:3表示数据位0 ,每个位用两个脉冲表示
        {
            Delay1us ( 1200 );
            JQ8900_SDA_CLR();
            Delay1us ( 400 );
        }
        addr >>= 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] = 0.0f;
	adc_value[1] = 0.0f;
	adc_value[2] = 0.0f;
	adc_value[3] = 0.0f;
	adc_value[4] = 0.0f;
	adc_value[5] = 0.0f;
	adc_value[6] = 0.0f;
	adc_value[7] = 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");
        }
    }
}

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

VDDC介绍.PNG 根据原理图VDDC在电路板上的位置就是电容C5那里。

原理图VDDC.PNG 实测为1.0972V

VDDC电压实际测试.jpg adc测量为通道5值为1105mV,可见内部ADC精度不差,测量下电源电压等足够了。

adc.gif

新版设计.jpg

 

 

 

回复评论 (2)

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

点赞  2021-6-20 17:20

很详细,期待后续。

加油!在电子行业默默贡献自己的力量!:)
点赞  2021-6-21 09:26
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复