我的项目需要用到adc多通道数据采集,RSL10自带了ADC外设,查看了数据手册后发现这个ADC还不错,14bit分辨率,精度对于一些常见的电池电压采集、传感器数据采集足够。
ADC外设支持8个通道信号输入,其中4个是内部信号,另外4个可以通过外部管脚输入。根据原理图,这个4个支持ADC的管脚是DIO0~DIO3,如下原理图所示:
ADC的4个内部信号分别是AOUT、VDDC、VBAT/2、GND,通过软件配置。
RSL10的ADC外设支持低速和高速两种采样模式,对于不同的应用来说十分灵活,尤其是低功耗的应用,有些间隔长时间采集一次的模拟量完全不需要高速数据采集。
ADC支持中断功能,采样分为常规模式和连续模式,连续模式无法同时采集多通道的数据。在常规模式下,ADC多通道依次采样,每采样8次触发一次中断。
ADC支持多种采样率,不同的采样率输入电压范围略微不同。下表中带H的是低速模式使用,不带H的是高速模式使用。 测试代码中将全部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。
adc测量为通道5值为1105mV,可见内部ADC精度不差,测量下电源电压等足够了。