跳转至

模数转换器(ADC)


1、ADC概述

  • ADC是CI13XX系列芯片中实现模数转换功能的模块,主要用于电压值的模拟量转换成数字量,以供CPU进行电压监控或其他处理。

2、ADC特性

  • 4通道,12-bit分辨率,只支持单端输入通道,SAR ADC;
  • 采样速率可达1MSPS(一次转换需15cycles,所以若要达到1MSPS的采样速率,必须保证此时ADC的时钟频率为15MHz);
  • 1MSPS时,工作电流450uA;关闭时电流小于1uA;
  • 模拟输入范围为VREFL至VREF。
  • 中断有两种情况,一种情况是ADC采样值异常触发中断。另外一种情况是ADC每完成一次转换都会触发中断,这种情况下,ADC触发中断的情况太过频繁,会对整个系统造成一定的影响,所以ADC的中断模式只使用采样值异常的情况。
  • CI13XX系列芯片的ADC含有4个外部通道ADC_CHANNEL_2(AIN2)、ADC_CHANNEL_3(AIN3)、ADC_CHANNEL_4(AIN4)、ADC_CHANNEL_5(AIN5)

3、使用示例

3.1、连续转换模式

  • 连续采样模式的情况下,ADC启动之后,会一直工作,用户某个时候想读ADC转换之后的结果,就直接调用adc_get_result函数。
#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;

    scu_set_device_gate(HAL_ADC_BASE,1);     //打开ADC时钟
    dpmu_set_adio_reuse(PC1,ANALOG_MODE);     //配置ADC通道5的引脚为模拟功能,更多请查看ci130x_sch.h的IO复用注释
    dpmu_set_io_pull(PC1,DPMU_IO_PULL_DISABLE);    //关闭引脚内部上下拉
    eclic_irq_enable(ADC_IRQn);       //使能中断
    __eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle);   //指定中断服务函数
    adc_poweron();
    adc_series_mode(ADC_CHANNEL_5);    //初始化成连续模式
    for(;;)
    {
        for(int i = 0;i < 200;i++)
        {
            temp = adc_get_result(ADC_CHANNEL_5);
            temp = temp*33000/4096;
            temp /= 10;
            adc_current[i] = temp;
            vTaskDelay(pdMS_TO_TICKS(1));
        }
        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、普通软件触发模式

  • 普通软件触发模式测试,需要软件写一次触发才会采集一次数据,再调用adc_get_result函数获取电压。
#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;
    scu_set_device_gate(HAL_ADC_BASE,1);     //打开ADC时钟
    dpmu_set_adio_reuse(PC1,ANALOG_MODE);       //配置ADC通道5的引脚为模拟功能,更多请查看ci130x_sch.h的IO复用注释
    dpmu_set_io_pull(PC1,DPMU_IO_PULL_DISABLE);    //关闭引脚内部上下拉
    eclic_irq_enable(ADC_IRQn);       //使能中断
    __eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle);   //指定中断服务函数
    adc_poweron();             
    adc_signal_mode(ADC_CHANNEL_5);   //初始化成普通软件触发模式

    for(;;)
    {
        for(int i = 0;i < 200;i++)
        {
            adc_soc_soft_ctrl(ENABLE);     //ADC软件触发(软件强制开始转换)
            adc_wait_int(ADC_CHANNEL_5);       //等待中断
            temp = adc_get_result(ADC_CHANNEL_5);    //获取瞬时电压值
            temp = temp*33000/4096;
            temp /= 10;
            adc_current[i] = temp;
            vTaskDelay(pdMS_TO_TICKS(2));
        }
        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、周期转换模式

  • 在周期转换模式下,ADC会根据设置的周期,一个周期转换一次,如果打开ADC中断,转换完成会触发中断。
#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_cha5_int_flag;      //用于查询异常中断的标志

    scu_set_device_gate(HAL_ADC_BASE,1);     //打开ADC时钟
    dpmu_set_adio_reuse(PC1,ANALOG_MODE);    //配置ADC通道5的引脚为模拟功能,更多请查看ci130x_sch.h的IO复用注释
    dpmu_set_io_pull(PC1,DPMU_IO_PULL_DISABLE);   //关闭引脚内部上下拉

    eclic_irq_enable(ADC_IRQn);     //使能中断
    __eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle);   //指定中断服务函数
    adc_poweron();
    adc_cycle_mode(ADC_CHANNEL_5,1000);     //配置周期 (1000 + 1) * 512

    adc_channel_min_value(ADC_CHANNEL_5,1241);       //最小阈值设置,宽度范围是0 ~ 4095
    adc_channel_min_value_int(ADC_CHANNEL_5,ENABLE);
    adc_channel_max_value(cha,4095);                 //最大阈值设置,宽度范围是0 ~ 4095
    adc_channel_max_value_int(cha,ENABLE);
    for(;;)
    {
        for(int i = 0;i < 200;i++)
        {
            temp = adc_get_result(ADC_CHANNEL_5);
            temp = temp*33000/4096;
            temp /= 10;
            adc_current[i] = temp;
            vTaskDelay(pdMS_TO_TICKS(1));
        }
        if(adc_cha5_int_flag)
        {
            mprintf("电压异常!!\n");     //如果在采集到阈值之外的值,报告异常
            adc_cha5_int_flag = 0;
        }
        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、校准模式

  • 校准模式下,ADC不会采集管脚上接入的电压,而是测试ADC内部的一个电压基准,校准模式下,ADC的采样值应恒定在(0xfff/2)附近。
#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;

    scu_set_device_gate(HAL_ADC_BASE,1);   //打开ADC时钟
    dpmu_set_adio_reuse(PC1,ANALOG_MODE);    //配置ADC通道5的引脚为模拟功能,更多请查看ci130x_sch.h的IO复用注释
    eclic_irq_enable(ADC_IRQn);
    __eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle);   //指定中断服务函数
    adc_poweron();
    adc_caculate_mode();    //打开ADC校验模式
    adc_signal_mode(ADC_CHANNEL_5);     配置单次转换模式
    for(;;)
    {
        for(recv_count=0;recv_count<adc_test_count;recv_count++)
        {
            for(int i = 0;i < 200;i++)
            {
                adc_soc_soft_ctrl(ENABLE);
                adc_wait_int(ADC_CHANNEL_5);
                temp = adc_get_result(ADC_CHANNEL_5);
                temp = temp*33000/4096;
                temp /= 10;
                adc_current[i] = temp;
                vTaskDelay(pdMS_TO_TICKS(1));
            }
            for(int i = 0;i < 200;i++)
            {
                val += adc_current[i];
            }
            val /= 200;
            mprintf("第%d次val:%dmv\n",recv_count,val);
        }
    }
}

3.5、内部ADC电压检测

  • 内部ADC电压检测,采用一个周期检测一次电压,再调用adc_get_result函数获取电压。
#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;

    scu_set_device_gate(HAL_ADC_BASE,0);
    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,1);

    dpmu_unlock_cfg_config();//解锁
    dpmu_pmu_div_resistance_en(ENABLE);//分压电阻使能
    dpmu_config_update_en(DPMU_UPDATE_EN_NUM_LDO1);//LDO配置更新使能
    dpmu_config_update_en(DPMU_UPDATE_EN_NUM_LDO2);
    dpmu_config_update_en(DPMU_UPDATE_EN_NUM_LDO3);
    dpmu_config_update_en(DPMU_UPDATE_EN_NUM_VDT);
    dpmu_config_update_en(DPMU_UPDATE_EN_NUM_TRIM);
    dpmu_lock_cfg_config();
    eclic_irq_enable(ADC_IRQn);       //使能中断
    __eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle);   //指定中断服务函数
    adc_poweron();    

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