模数转换器(ADC)
1、简介
- SARADC(Analog-to-Digital Converter)是将连续变化的模拟信号(如电压、电流、温度、声音等)转换为离散数字信号(二进制代码)的模块,以下简称ADC。
2、特性
- ADC分辨率为12bit;
- 支持单次采样和连续采样模式;
- 采样速率可达1MSPS(Mega Samples Per Second),若要达到1MSPS的采样速率,必须保证此时ADC的时钟频率不小于15MHz;
- 采样速率为1MSPS时,工作电流450uA;关闭时电流小于1uA;
- 模拟电源电压输入范围为1.8V~3.63V;
- 中断产生有两种情况,一种情况是ADC采样值异常触发中断;另外一种情况是ADC每完成一次转换都会触发中断;
- 支持6通道单端输入,CI13XX的ADC含有4个外部通道ADC_CHANNEL_2(AIN2)/
ADC_CHANNEL_3(AIN3)/ADC_CHANNEL_4(AIN4)/ADC_CHANNEL_5(AIN5),通道ADC_CHANNEL_0(AIN0)/ADC_CHANNEL_1(AIN1) 供芯片内部使用;
3、使用示例
| 引脚名 |
对应的ADC通道 |
| PC1 |
ADC_CHANNEL_5(AIN5) |
| PC2 |
ADC_CHANNEL_4(AIN4) |
| PC3 |
ADC_CHANNEL_3(AIN3) |
| PC4 |
ADC_CHANNEL_2(AIN2) |
3.1、连续转换模式
- 连续采样模式的情况下,ADC启动之后,会一直工作,用户某个时候想读ADC转换之后的结果,就直接调用adc_get_result函数。下面为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_series_mode_test(void)
{
uint32_t recv_count = 0;
uint32_t temp;
uint32_t val;
/*ADC通道5的引脚初始化*/
dpmu_set_adio_reuse(PC1,ANALOG_MODE); //配置ADC通道5的引脚为模拟功能,更多请查看ci130x_sch.h的IO复用注释
dpmu_set_io_pull(PC1,DPMU_IO_PULL_DISABLE); //关闭引脚内部上下拉
/*ADC控制器时钟、中断配置*/
scu_set_device_gate(HAL_ADC_BASE,ENABLE); //打开ADC时钟
eclic_irq_enable(ADC_IRQn); //使能中断
__eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle); //指定中断服务函数
/*ADC上电、初始化成连续转换模式*/
adc_poweron(); //ADC上电
adc_series_mode(ADC_CHANNEL_5); //初始化成连续转换模式
/*连续采集电压值,这里举例按200次算一次平均值,用户可按需调整次数*/
for(;;)
{
/*每隔1 ms 调用adc_get_result读1次电压值*/
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));
}
/*计算200次电压的平均值*/
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函数获取电压。下面为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_signal_mode_test()
{
uint32_t temp;
uint32_t val;
/*ADC通道4的引脚初始化*/
dpmu_set_adio_reuse(PC2,ANALOG_MODE); //配置ADC通道4的引脚为模拟功能,更多请查看ci130x_sch.h的IO复用注释
dpmu_set_io_pull(PC2,DPMU_IO_PULL_DISABLE); //关闭引脚内部上下拉
/*ADC控制器时钟、中断配置*/
scu_set_device_gate(HAL_ADC_BASE,ENABLE); //打开ADC时钟
eclic_irq_enable(ADC_IRQn); //使能中断
__eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle); //指定中断服务函数
/*ADC上电、初始化成普通软件触发模式*/
adc_poweron(); //ADC上电
adc_signal_mode(ADC_CHANNEL_4); //初始化成普通软件触发模式
/*连续采集电压值,这里举例按200次算一次平均值,用户可按需调整次数*/
for(;;)
{
/*每隔2 ms 调用1次adc_soc_soft_ctrl软件触发,中断有效后调用adc_get_result读1次电压值*/
for(int i = 0;i < 200;i++)
{
adc_soc_soft_ctrl(ENABLE); //ADC软件触发(软件强制开始转换)
adc_wait_int(ADC_CHANNEL_4); //等待中断
temp = adc_get_result(ADC_CHANNEL_4); //获取电压值
temp = temp*33000/4096;
temp /= 10;
adc_current[i] = temp;
vTaskDelay(pdMS_TO_TICKS(2));
}
/*计算200次电压的平均值*/
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中断,转换完成会触发中断。下面为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_cha3_int_flag; //用于查询异常中断的标志
/*ADC通道3的引脚初始化*/
dpmu_set_adio_reuse(PC3,ANALOG_MODE); //配置ADC通道3的引脚为模拟功能,更多请查看ci130x_sch.h的IO复用注释
dpmu_set_io_pull(PC3,DPMU_IO_PULL_DISABLE); //关闭引脚内部上下拉
/*ADC控制器时钟、中断配置*/
scu_set_device_gate(HAL_ADC_BASE,ENABLE); //打开ADC时钟
eclic_irq_enable(ADC_IRQn); //使能中断
__eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle); //指定中断服务函数
/*ADC上电、初始化成周期转换模式*/
adc_poweron(); //ADC上电
adc_cycle_mode(ADC_CHANNEL_3,1000); //配置周期 (1000 + 1) * 512
adc_channel_min_value(ADC_CHANNEL_3,1241); //最小阈值设置,宽度范围是0 ~ 4095
adc_channel_min_value_int(ADC_CHANNEL_3,ENABLE); //ADC通道采样结果低于阀值下限中断使能
adc_channel_max_value(cha,4095); //最大阈值设置,宽度范围是0 ~ 4095
adc_channel_max_value_int(cha,ENABLE); //ADC通道采样结果超过阀值上限中断使能
/*连续采集电压值,这里举例按200次算一次平均值,用户可按需调整次数*/
for(;;)
{
/*每隔1 ms 调用adc_get_result读1次电压值*/
for(int i = 0;i < 200;i++)
{
temp = adc_get_result(ADC_CHANNEL_3); //获取电压值
temp = temp*33000/4096;
temp /= 10;
adc_current[i] = temp;
vTaskDelay(pdMS_TO_TICKS(1));
}
/*如果在采集到阈值之外的值,报告异常*/
if(adc_cha3_int_flag)
{
mprintf("电压异常!!\n");
adc_cha3_int_flag = 0;
}
/*计算200次电压的平均值*/
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)附近。下面为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_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通道2的引脚初始化*/
dpmu_set_adio_reuse(PC4,ANALOG_MODE); //配置ADC通道2的引脚为模拟功能,更多请查看ci130x_sch.h的IO复用注释
/*ADC控制器时钟、中断配置*/
scu_set_device_gate(HAL_ADC_BASE,ENABLE); //打开ADC时钟
eclic_irq_enable(ADC_IRQn); //使能中断
__eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle); //指定中断服务函数
/*ADC上电、初始化成周期转换模式*/
adc_poweron(); //ADC上电
adc_caculate_mode(); //打开ADC校准模式
adc_signal_mode(ADC_CHANNEL_2); //配置单次转换模式
/*连续采集电压值,这里举例按200次算一次平均值,用户可按需调整次数*/
for(;;)
{
for(recv_count=0;recv_count<adc_test_count;recv_count++)
{
/*每隔1 ms 调用1次adc_soc_soft_ctrl软件触发,中断有效后调用adc_get_result读1次电压值*/
for(int i = 0;i < 200;i++)
{
adc_soc_soft_ctrl(ENABLE); //ADC软件触发(软件强制开始转换)
adc_wait_int(ADC_CHANNEL_2); //等待中断
temp = adc_get_result(ADC_CHANNEL_2); //获取电压值
temp = temp*33000/4096;
temp /= 10;
adc_current[i] = temp;
vTaskDelay(pdMS_TO_TICKS(1));
}
/*计算200次电压的平均值*/
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函数获取电压。下面为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"
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(); //解锁
dpmu_pmu_div_resistance_en(ENABLE); //分压电阻使能
dpmu_config_update_en(DPMU_UPDATE_EN_NUM_LDO1); //LDO1配置更新使能
dpmu_config_update_en(DPMU_UPDATE_EN_NUM_LDO2); //LDO2配置更新使能
dpmu_config_update_en(DPMU_UPDATE_EN_NUM_LDO3); //LDO3配置更新使能
dpmu_config_update_en(DPMU_UPDATE_EN_NUM_VDT); //VDT配置更新使能
dpmu_config_update_en(DPMU_UPDATE_EN_NUM_TRIM); //TRIM配置更新使能
dpmu_lock_cfg_config();
/*ADC中断、上电配置*/
eclic_irq_enable(ADC_IRQn); //使能中断
__eclic_irq_set_vector(ADC_IRQn, (int32_t)ADC_irqhandle); //指定中断服务函数
adc_poweron(); //ADC上电
adc_cycle_mode(ADC_CHANNEL_1,10); //配置周期 (10 + 1) * 512
for(;;)
{
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);
}
}
}
4、API 参考