Skip to content

Analog-to-Digital Converter (ADC)


1. Introduction

  • The SAR ADC (Analog-to-Digital Converter) converts continuously varying analog signals (e.g., voltage, current, temperature, audio) into discrete digital signals (binary codes). Hereafter referred to as the ADC.

2. Features

  • 12-bit ADC resolution.
  • Supports single-shot and continuous sampling modes.
  • Sampling rate up to 1 MSPS (Mega Samples Per Second). To achieve 1 MSPS, the ADC clock must be at least 15 MHz.
  • At 1 MSPS, operating current is 450 µA; when powered down, current is less than 1 µA.
  • Analog supply voltage input range: 1.8 V to 3.63 V.
  • Two interrupt sources: abnormal ADC sample value interrupt; end-of-conversion interrupt on each conversion.
  • Supports 6 single-ended input channels. CI13XX provides 4 external channels: ADC_CHANNEL_2 (AIN2) / ADC_CHANNEL_3 (AIN3) / ADC_CHANNEL_4 (AIN4) / ADC_CHANNEL_5 (AIN5). Channels ADC_CHANNEL_0 (AIN0) and ADC_CHANNEL_1 (AIN1) are reserved for internal use.

3. Examples

  • Pin-to-SAR ADC channel mapping on CI13XX:
Pin Name Corresponding ADC Channel
PC1 ADC_CHANNEL_5 (AIN5)
PC2 ADC_CHANNEL_4 (AIN4)
PC3 ADC_CHANNEL_3 (AIN3)
PC4 ADC_CHANNEL_2 (AIN2)

3.1 Continuous Conversion Mode

  • In continuous sampling mode, once the ADC is started it keeps running. Whenever you need the conversion result, call adc_get_result. Example code for continuous conversion mode:
#include "ci130x_system.h"
#include <string.h>
#include "ci130x_core_eclic.h"
#include "ci_log.h"
#include "ci130x_scu.h"
#include "ci130x_dpmu.h"
#include "ci130x_adc.h"

uint32_t adc_current[502] = {0};

void adc_series_mode_test(void)
{
    uint32_t recv_count = 0;
    uint32_t temp;
    uint32_t val;

    /*ADC channel 5 pin initialization*/
    dpmu_set_adio_reuse(PC1,ANALOG_MODE);       //Configure ADC channel 5 pin as analog function
    dpmu_set_io_pull(PC1,DPMU_IO_PULL_DISABLE); //Disable internal pull-up/pull-down

    /*ADC controller clock, interrupt configuration*/
    scu_set_device_gate(HAL_ADC_BASE,ENABLE);   //Enable ADC clock
    eclic_irq_enable(ADC_IRQn);                 //Enable interrupt
    __eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle); //Specify interrupt service function

    /*ADC power on, initialize to continuous conversion mode*/
    adc_poweron();                              //ADC power on
    adc_series_mode(ADC_CHANNEL_5);             //Initialize continuous conversion mode

    /*Continuous voltage collection, here examples calculate the average value of 200 times, users can adjust the number of times as needed*/
    for(;;)
    {
        /*Every 1 ms call adc_get_result to read 1 voltage value*/
        for(int i = 0;i < 200;i++)
        {
            temp = adc_get_result(ADC_CHANNEL_5); //Get voltage value
            temp = temp*33000/4096;
            temp /= 10;
            adc_current[i] = temp;
            vTaskDelay(pdMS_TO_TICKS(1));
        }

        /*Calculate the average value of 200 times voltage*/
        for(int i = 0;i < 200;i++)
        {
            val += adc_current[i];
        }
        val /= 200;

        mprintf("val:%dmv\n",val);
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

3.2 Software-Triggered Single Conversion Mode

  • In software-triggered mode, the software must issue a trigger to acquire one sample, then call adc_get_result to read the voltage. Example code:
#include "ci130x_system.h"
#include <string.h>
#include "ci130x_core_eclic.h"
#include "ci_log.h"
#include "ci130x_scu.h"
#include "ci130x_dpmu.h"
#include "ci130x_adc.h"

uint32_t adc_current[502] = {0};

void adc_signal_mode_test()
{
    uint32_t temp;
    uint32_t val;

    /*ADC channel 4 pin initialization*/
    dpmu_set_adio_reuse(PC2,ANALOG_MODE);        //Configure ADC channel 4 pin as analog function
    dpmu_set_io_pull(PC2,DPMU_IO_PULL_DISABLE);  //Disable internal pull-up/pull-down

    /*ADC controller clock, interrupt configuration*/
    scu_set_device_gate(HAL_ADC_BASE,ENABLE);         //Enable ADC clock
    eclic_irq_enable(ADC_IRQn);                  //Enable interrupt
    __eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle);   //Specify interrupt service function

    /*ADC power on, initialize to normal software trigger mode*/
    adc_poweron();                               //ADC power on
    adc_signal_mode(ADC_CHANNEL_4);              //Initialize software-triggered mode

    /*Continuous voltage collection, here examples calculate the average value of 200 times, users can adjust the number of times as needed*/
    for(;;)
    {
        /*Every 2 ms call adc_soc_soft_ctrl to trigger software conversion, interrupt effective after adc_get_result to read 1 voltage value*/
        for(int i = 0;i < 200;i++)
        {
            adc_soc_soft_ctrl(ENABLE);            //ADC software trigger
            adc_wait_int(ADC_CHANNEL_4);          //Wait for interrupt
            temp = adc_get_result(ADC_CHANNEL_4); //Get voltage value
            temp = temp*33000/4096;
            temp /= 10;
            adc_current[i] = temp;
            vTaskDelay(pdMS_TO_TICKS(2));
        }

        /*Calculate the average value of 200 times voltage*/
        for(int i = 0;i < 200;i++)
        {
            val += adc_current[i];
        }
        val /= 200;

        mprintf("val:%dmv\n",val);       
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

3.3 Periodic Conversion Mode

  • In periodic conversion mode, the ADC performs one conversion per configured period. If ADC interrupts are enabled, an interrupt is raised upon conversion completion. Example code:
#include "ci130x_system.h"
#include <string.h>
#include "ci130x_core_eclic.h"
#include "ci_log.h"
#include "ci130x_scu.h"
#include "ci130x_dpmu.h"
#include "ci130x_adc.h"

uint32_t adc_current[502] = {0};

void adc_cycle_mode_test(void)
{
    uint32_t temp;
    uint32_t val;
    extern uint8_t adc_cha3_int_flag;      //Flag for abnormal interrupt

    /*ADC channel 3 pin initialization*/
    dpmu_set_adio_reuse(PC3,ANALOG_MODE);         //Configure ADC channel 3 pin as analog function
    dpmu_set_io_pull(PC3,DPMU_IO_PULL_DISABLE);   //Disable internal pull-up/pull-down

    /*ADC controller clock, interrupt configuration*/
    scu_set_device_gate(HAL_ADC_BASE,ENABLE);     //Enable ADC clock
    eclic_irq_enable(ADC_IRQn);                   //Enable interrupt
    __eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle);   //Specify interrupt service function

    /*ADC power on, initialize to periodic conversion mode*/
    adc_poweron();                                   //ADC power on
    adc_cycle_mode(ADC_CHANNEL_3,1000);              //Configure period
    adc_channel_min_value(ADC_CHANNEL_3,1241);       //Set minimum threshold value
    adc_channel_min_value_int(ADC_CHANNEL_3,ENABLE); //Enable interrupt for below threshold
    adc_channel_max_value(cha,4095);                 //Set maximum threshold value
    adc_channel_max_value_int(cha,ENABLE);           //Enable interrupt for above threshold

    /*Continuous voltage collection, here examples calculate the average value of 200 times, users can adjust the number of times as needed*/
    for(;;)
    {
        /*Every 1 ms call adc_get_result to read 1 voltage value*/
        for(int i = 0;i < 200;i++)
        {
            temp = adc_get_result(ADC_CHANNEL_3); //Get voltage value
            temp = temp*33000/4096;
            temp /= 10;
            adc_current[i] = temp;
            vTaskDelay(pdMS_TO_TICKS(1));
        }

        /*If the collected value is outside the threshold, report an exception*/
        if(adc_cha3_int_flag)
        {
            mprintf("Voltage abnormal!!\n");     
            adc_cha3_int_flag = 0;
        }

        /*Calculate the average value of 200 times voltage*/
        for(int i = 0;i < 200;i++)
        {
            val += adc_current[i];
        }
        val /= 200;

        mprintf("val:%dmv\n",val);
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

3.4 Calibration Mode

  • In calibration mode, the ADC does not sample the external pin voltage but measures an internal reference. The sampled value should remain near (0xFFF / 2). Example code:
#include "ci130x_system.h"
#include <string.h>
#include "ci130x_core_eclic.h"
#include "ci_log.h"
#include "ci130x_scu.h"
#include "ci130x_dpmu.h"
#include "ci130x_adc.h"

uint32_t adc_test_count = 10000;
uint32_t adc_current[502] = {0};

void adc_correct_test(void)
{
    uint32_t recv_count = 0;
    uint32_t temp;
    uint32_t val;

    /*ADC channel 2 pin initialization*/
    dpmu_set_adio_reuse(PC4,ANALOG_MODE);      //Configure ADC channel 2 pin as analog function

    /*ADC controller clock, interrupt configuration*/
    scu_set_device_gate(HAL_ADC_BASE,ENABLE);  //Enable ADC clock
    eclic_irq_enable(ADC_IRQn);                //Enable interrupt
    __eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle);   //Specify interrupt service function

    /*ADC power on, initialize to calibration mode*/
    adc_poweron();                      //ADC power on
    adc_caculate_mode();                //Enable calibration mode
    adc_signal_mode(ADC_CHANNEL_2);     //Configure single conversion mode

    /*Continuous voltage collection, here examples calculate the average value of 200 times, users can adjust the number of times as needed*/
    for(;;)
    {
        for(recv_count=0;recv_count<adc_test_count;recv_count++)
        {
            /*Every 1 ms call adc_soc_soft_ctrl to trigger software conversion, interrupt effective after adc_get_result to read 1 voltage value*/
            for(int i = 0;i < 200;i++)
            {
                adc_soc_soft_ctrl(ENABLE);            //ADC software trigger
                adc_wait_int(ADC_CHANNEL_2);          //Wait for interrupt
                temp = adc_get_result(ADC_CHANNEL_2); //Get voltage value
                temp = temp*33000/4096;
                temp /= 10;
                adc_current[i] = temp;
                vTaskDelay(pdMS_TO_TICKS(1));
            }

            /*Calculate the average value of 200 times voltage*/
            for(int i = 0;i < 200;i++)
            {
                val += adc_current[i];
            }
            val /= 200;

            mprintf("第%d次val:%dmv\n",recv_count,val);
        }
    }
}

3.5 Internal ADC Voltage Monitoring

  • Internal ADC voltage monitoring samples once per period, then calls adc_get_result to read the voltage. Example code:
#include "ci130x_system.h"
#include <string.h>
#include "ci130x_core_eclic.h"
#include "ci_log.h"
#include "ci130x_scu.h"
#include "ci130x_dpmu.h"
#include "ci130x_adc.h"

void adc_vol_test()
{
    uint32_t temp;

    /*ADC的时钟分频、时钟开关、复位配置*/
    scu_set_device_gate(HAL_ADC_BASE,DISABLE);
    scu_set_device_reset(HAL_ADC_BASE);
    scu_para_en_disable(HAL_ADC_BASE);
    scu_set_div_parameter(HAL_ADC_BASE,12);
    scu_para_en_enable(HAL_ADC_BASE);
    scu_set_device_reset_release(HAL_ADC_BASE);
    scu_set_device_gate(HAL_ADC_BASE,ENABLE);

    /*内部ADC电压检测初始化*/
    dpmu_unlock_cfg_config();                       //Unlock
    dpmu_pmu_div_resistance_en(ENABLE);             //Enable divider resistance
    dpmu_config_update_en(DPMU_UPDATE_EN_NUM_LDO1); //Enable LDO1 configuration update
    dpmu_config_update_en(DPMU_UPDATE_EN_NUM_LDO2); //Enable LDO2 configuration update
    dpmu_config_update_en(DPMU_UPDATE_EN_NUM_LDO3); //Enable LDO3 configuration update
    dpmu_config_update_en(DPMU_UPDATE_EN_NUM_VDT);  //Enable VDT configuration update
    dpmu_config_update_en(DPMU_UPDATE_EN_NUM_TRIM); //Enable TRIM configuration update
    dpmu_lock_cfg_config();

    /*ADC中断、上电配置*/
    eclic_irq_enable(ADC_IRQn);                     //Enable interrupt
    __eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle);   //Specify interrupt service function
    adc_poweron();                                  //ADC power on
    adc_cycle_mode(ADC_CHANNEL_1,10);               //Configure period

    for(;;)
    {
        for(int i = 0;i < 2000;i++)
        {
            temp = adc_get_result(ADC_CHANNEL_1);  //Get voltage value
            temp = temp*33000/4096;
            temp /= 10;
            mprintf("%d:%d\n",ADC_CHANNEL_1,temp);
        }
    }
}

4. API Reference