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