打印
[单片机芯片]

CH32L103 ADC应用

[复制链接]
209|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
L-MCU|  楼主 | 2024-7-5 11:13 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 L-MCU 于 2024-7-5 13:44 编辑

1、ADC介绍
CH32L103具有一个ADC单元,为12位的逐次逼近型的模拟数字转换器,最高支持48MHz的时钟输入。下面框图为CH32L103 ADC模块框图:
由框图可知:
(1)CH32L103 ADC支持10个外部通道和3个内部通道,这3个内部通道分别为:内部温度传感器通道(ADC_IN16)、内部参考电压通道(ADC_IN17)、内部VDDA/2通道(ADC_IN18) 。如下图:
(2)CH32L103 ADC支持PGA增益放大,可实现小信号放大采样,如下图:
(3)CH32L103 ADC模拟输入范围为:VSSA—VDDA,VDDA的大小在使用时一般为VDD的大小,即3.3V,因此模拟输入范围使用时一般为0—3.3V。ADC模拟输入范围如下图:
(4)CH32L103 ADC的10个外部通道和3个内部通道可配置为规则通道组和注入通道组。规则通道组具有1个16位的规则数据寄存器,注入通道组具有4个16位的注入数据寄存器,对应注入通道组的4个通道。规则通道组则共用1个16位的规则数据寄存器。如下图:
(5)CH32L103 ADC具有模拟看门狗功能,当经过ADC转换的ADC值低于低阈值或高于高阈值,会进行模拟看门狗复位,如下图:
(6)CH32L103 ADC规则通道组和注入通道组可以通过软件触发或外部触发事件触发转换,规则通道组和注入通道组对应的触发事件如下图:
(7)CH32L103 ADC支持DMA、输入时钟以及采样时间可配置,如下图:
CH32L103 ADC的时钟来自PB2,输入时钟最高可配置48MHz,如下图:
CH32L103 ADC输入时钟可选择HCLK时钟输入或选择PCLK2经过ADC分频后(ADCPRE[1:0])的时钟输入。一般默认选择PCLK2经过ADC分频后(ADCPRE[1:0])的时钟输入。ADC分频可以是PCLK2 2/4/6/8分频后作为ADC时钟,注意ADC时钟最高不要超过48MHz。ADC输入时钟配置主要通过时钟配置寄存器0(RCC_CFGR0)进行配置,具体如下:
CH32L103 ADC采样时间可编程配置,具体采样时间以及ADC总转换时间如下:
以ADC输入时钟48MHz,采样时间1.5个周期计算,则ADC总转换时间:
T=1.5T+12.5T=14T=14*(1/48MHz)≈0.29us

CH32L103 ADC支持DMA功能,注意仅规则组支持DMA功能,注入组转换不支持DMA功能。规则通道转换的值储存在一个仅有的数据寄存器ADC_RDATAR中,为防止连续转换多个规则通道时,没有及时取走ADC_RDATAR寄存器中的数据,可以开启ADC的DMA功能。

2、ADC应用
CH32L103 EVT提供了ADC各类应用例程,如下图,可直接参考EVT例程进行应用。

关于ADC DMA功能
EVT中ADC DMA为单通道的使用例程,在此基础上介绍ADC DMA多通道的修改使用,主要需要修改以下几个点:
1、定义一个数组,数组的大小根据ADC通道的数量决定,此处使用3个ADC通道,定义数组如下:
2、对ADC通道引脚进行GPIO初始化,此处选择PA1、PA2、PA3对应的3个通道,ADC GPIO初始化如下,配置为模拟输入模式:
3、使用多通道时,需要开启扫描模式,且ADC通道的数量需要设置为3,如下图:
4、修改DMA初始化,将DMA存储器的地址修改为定义的数组的首地址,DMA数据缓冲区的大小修改为3,DMA的模式改为循环模式,
5、配置ADC通道转换顺序以及采样周期,开启软件触发ADC转换,在while循环中打印对应的ADC值,如下图:
完整程序如下:
<font face="新宋体" size="3">/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2023/07/08
* Description        : Main program body.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/

/*
*@Note
*ADC DMA sampling routines:
*ADC channel 1 (PA1), the rule group channel obtains ADC conversion data
*for 1024 consecutive times through DMA.
*
*/

#include "debug.h"

/* Global Variable */
u16 TxBuf[1024];
u16 ADCValue[3]={0};
s16 Calibrattion_Val = 0;

/*********************************************************************
* @fn      ADC_Function_Init
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Initializes ADC collection.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
void ADC_Function_Init(void)
{
    ADC_InitTypeDef  ADC_InitStructure = {0};
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    RCC_PB2PeriphClockCmd(RCC_PB2Periph_GPIOA, ENABLE);
    RCC_PB2PeriphClockCmd(RCC_PB2Periph_ADC1, ENABLE);
    RCC_ADCCLKConfig(RCC_PCLK2_Div8);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_DeInit(ADC1);
    Calibrattion_Val=Get_CalibrationValue(ADC1);
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 3;
    ADC_Init(ADC1, &ADC_InitStructure);

    ADC_DMACmd(ADC1, ENABLE);
    ADC_Cmd(ADC1, ENABLE);

    ADC_FIFO_Cmd(ADC1, ENABLE);   
    ADC_BufferCmd(ADC1, DISABLE); //disable buffer
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));
}

/*********************************************************************
* @fn      Get_ADC_Val
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Returns ADCx conversion result data.
*
* @param   ch - ADC channel.
*            ADC_Channel_0 - ADC Channel0 selected.
*            ADC_Channel_1 - ADC Channel1 selected.
*            ADC_Channel_2 - ADC Channel2 selected.
*            ADC_Channel_3 - ADC Channel3 selected.
*            ADC_Channel_4 - ADC Channel4 selected.
*            ADC_Channel_5 - ADC Channel5 selected.
*            ADC_Channel_6 - ADC Channel6 selected.
*            ADC_Channel_7 - ADC Channel7 selected.
*            ADC_Channel_8 - ADC Channel8 selected.
*            ADC_Channel_9 - ADC Channel9 selected.
*            ADC_Channel_16 - ADC Channel16 selected.
*            ADC_Channel_17 - ADC Channel17 selected.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
u16 Get_ADC_Val(u8 ch)
{
    u16 val;

    ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_CyclesMode7);
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);

    while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
    val = ADC_GetConversionValue(ADC1);

    return val;
}

/*********************************************************************
* @fn      Get_ConversionVal
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Get Conversion Value.
*
* @param   val - Sampling value
*
* [url=home.php?mod=space&uid=266161]@return[/url]  val+Calibrattion_Val - Conversion Value.
*/
u16 Get_ConversionVal(s16 val)
{
    if((val + Calibrattion_Val) < 0 || val==0)
        return 0;
    if((Calibrattion_Val + val) > 4095||val==4095)
        return 4095;
    return (val + Calibrattion_Val);
}

/*********************************************************************
* @fn      DMA_Tx_Init
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Initializes the DMAy Channelx configuration.
*
* @param   DMA_CHx - x can be 1 to 7.
*          ppadr - Peripheral base address.
*          memadr - Memory base address.
*          bufsize - DMA channel buffer size.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
void DMA_Tx_Init(DMA_Channel_TypeDef *DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize)
{
    DMA_InitTypeDef DMA_InitStructure = {0};

    RCC_HBPeriphClockCmd(RCC_HBPeriph_DMA1, ENABLE);

    DMA_DeInit(DMA_CHx);
    DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;
    DMA_InitStructure.DMA_MemoryBaseAddr = memadr;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = bufsize;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA_CHx, &DMA_InitStructure);
}

/*********************************************************************
* @fn      main
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Main program.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
int main(void)
{
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

    ADC_Function_Init();

    printf("CalibrattionValue:%d\n", Calibrattion_Val);
    DMA_Tx_Init(DMA1_Channel1, (u32)&ADC1->RDATAR, (u32)ADCValue, 3);
    DMA_Cmd(DMA1_Channel1, ENABLE);

    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_CyclesMode7);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 2, ADC_SampleTime_CyclesMode7);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 3, ADC_SampleTime_CyclesMode7);
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);

    while(1)
    {
        printf("\r\n The current ADCH1 value = %d \r\n", Get_ConversionVal(ADCValue[0]));
        printf("\r\n The current ADCH2 value = %d \r\n", Get_ConversionVal(ADCValue[1]));
        printf("\r\n The current ADCH3 value = %d \r\n", Get_ConversionVal(ADCValue[2]));
        Delay_Ms(500);
    }
}</font>

关于ADC规则通道组和注入通道组的使用
关于ADC规则通道和注入通道,注入通道类似于中断服务函数,可以打断规则通道的转换,当注入通道转换完成后,规则通道会接着被打断出继续进行转换。
CH32L103 EVT中,Auto_Injection例程为规则通道和注入通道使用例程,可以参考一下。

关于ADC触发转换
ADC转换的启动事件可以由外部事件触发。如果设置了ADC_CTLR2寄存器的EXTTRIG或 JEXTTRIG位,则可分别通过外部事件触发规则组或注入组通道的转换。此时,EXTSEL[2:0]和 JEXTSEL[2:0]位的配置决定规则组和注入组的外部事件源。
CH32L103 EVT中包含有外部触发ADC转换的例程,例程演示的是EXTI线15对注入通道的触发。下例程为EXTI线11对规则通道的触发,例程如下:
<font face="新宋体" size="3">/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2023/12/26
* Description        : Main program body.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/

/*
*@Note
*External lines trigger ADC conversion routine:
*ADC channel 1 (PA1) - injection group channel, external trigger pin (PA15) high
*level triggers EXTI line 15 event,In this mode, an ADC conversion is triggered
*by an event on EXTI line 15, and a JEOC interrupt is generated after the conversion
*is completed.
*
*/

#include "debug.h"

/* Global Variable */
s16 Calibrattion_Val = 0;

/*********************************************************************
* @fn      ADC_Function_Init
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Initializes ADC collection.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
void ADC_Function_Init(void)
{
    ADC_InitTypeDef  ADC_InitStructure = {0};
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    NVIC_InitTypeDef NVIC_InitStructure = {0};

    RCC_PB2PeriphClockCmd(RCC_PB2Periph_GPIOA, ENABLE);
    RCC_PB2PeriphClockCmd(RCC_PB2Periph_ADC1, ENABLE);
    RCC_ADCCLKConfig(RCC_PCLK2_Div8);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_DeInit(ADC1);
    Calibrattion_Val=Get_CalibrationValue(ADC1);
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_Ext_IT11;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
    ADC_Cmd(ADC1, ENABLE);

//    ADC_InjectedSequencerLengthConfig(ADC1, 1);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_CyclesMode7);
    ADC_ExternalTrigConvCmd(ADC1, ENABLE);

    ADC_FIFO_Cmd(ADC1, ENABLE);
    ADC_BufferCmd(ADC1, DISABLE); //disable buffer
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));
}

/*********************************************************************
* @fn      EXTI_Event_Init
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Initializes EXTI.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
void EXTI_Event_Init(void)
{
    EXTI_InitTypeDef EXTI_InitStructure = {0};
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    RCC_PB2PeriphClockCmd(RCC_PB2Periph_GPIOA, ENABLE);
    RCC_PB2PeriphClockCmd(RCC_PB2Periph_AFIO, ENABLE);

    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource11);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    EXTI_InitStructure.EXTI_Line = EXTI_Line11;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
}

/*********************************************************************
* @fn      Get_ConversionVal
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Get Conversion Value.
*
* @param   val - Sampling value
*
* [url=home.php?mod=space&uid=266161]@return[/url]  val+Calibrattion_Val - Conversion Value.
*/
u16 Get_ConversionVal(s16 val)
{
    if((val + Calibrattion_Val) < 0 || val==0)
        return 0;
    if((Calibrattion_Val + val) > 4095||val==4095)
        return 4095;
    return (val + Calibrattion_Val);
}

/*********************************************************************
* @fn      main
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Main program.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
int main(void)
{
    SystemCoreClockUpdate();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

    ADC_Function_Init();
    printf("CalibrattionValue:%d\n", Calibrattion_Val);
    EXTI_Event_Init();

    while(1);
}

void ADC_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
/*********************************************************************
* @fn      ADC1_2_IRQHandler
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   ADC1_2 Interrupt Service Function.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
void ADC_IRQHandler()
{
    u16 ADC_val;

    if(ADC_GetITStatus(ADC1, ADC_IT_EOC))
    {
        printf("ADC Extline trigger conversion...\r\n");
        ADC_val = ADC_GetConversionValue(ADC1);
        printf("ADC%04d\r\n", Get_ConversionVal(ADC_val));
    }

    ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
}
</font>
该例程在EVT外部触发ADC转换例程的基础上修改的,使用EXTI线11触发规则通道,首先修改初始化中规则通道转换的外部触发事件选择,主要对ADC 控制寄存器 2(ADC_CTLR2)位[19:17]进行配置,如下图。此处外部触发事件选择EXTI线11。
修改使能转换结束中断,对规则通道进行配置,配置使用外部事件启动转换。
关于EXTI的配置,将EXTI线15改成11即可,ADC中断中,注入通道改成规则通道即可,中断函数内容如下:
以上例程编译下载,例程中,配置上升沿触发事件转换,PA11接高电平,则触发一次ADC事件转换,打印如下:


关于ADC通道增益配置
CH32L103的ADC支持输入增益可调,可实现小信号的放大采样。ADC通道增益配置主要是对ADC控制寄存器1(ADC_CTLR1) 位27、28进行配置,如下图,此外还要使能输入Buffer。
在CH32L103 EVT中有ADC增益配置例程,如下:
<font face="新宋体" size="3">/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2023/07/08
* Description        : Main program body.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/

/*
*@Note
*ADC PGA sampling routines:
*ADC channel 1 (PA1), the rule group channel obtains ADC conversion data for 4 times.
*
*/

#include "debug.h"

/* Global Variable */
s16 Calibrattion_Val = 0;

/*********************************************************************
* @fn      ADC_Function_Init
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Initializes ADC collection.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
void ADC_Function_Init(void)
{
    ADC_InitTypeDef  ADC_InitStructure = {0};
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    RCC_PB2PeriphClockCmd(RCC_PB2Periph_GPIOA, ENABLE);
    RCC_PB2PeriphClockCmd(RCC_PB2Periph_ADC1, ENABLE);
    RCC_ADCCLKConfig(RCC_PCLK2_Div8);

    ADC_OffsetCalibrationConfig(ADC1);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_DeInit(ADC1);
    Calibrattion_Val=Get_CalibrationValue(ADC1);
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_Pga = ADC_Pga_4;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);

    ADC_Cmd(ADC1, ENABLE);

    ADC_FIFO_Cmd(ADC1, ENABLE);
    ADC_BufferCmd(ADC1, DISABLE); //disable buffer
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));

    ADC_BufferCmd(ADC1, ENABLE); //enable buffer
    ADC_OffsetCalibrationConfig(ADC1);
}

/*********************************************************************
* @fn      Get_ADC_Val
*
* [url=home.php?mod=space&uid=247401]@brief[/url]   Returns ADCx conversion result data.
*
* @param   ch - ADC channel.
*            ADC_Channel_0 - ADC Channel0 selected.
*            ADC_Channel_1 - ADC Channel1 selected.
*            ADC_Channel_2 - ADC Channel2 selected.
*            ADC_Channel_3 - ADC Channel3 selected.
*            ADC_Channel_4 - ADC Channel4 selected.
*            ADC_Channel_5 - ADC Channel5 selected.
*            ADC_Channel_6 - ADC Channel6 selected.
*            ADC_Channel_7 - ADC Channel7 selected.
*            ADC_Channel_8 - ADC Channel8 selected.
*            ADC_Channel_9 - ADC Channel9 selected.
*            ADC_Channel_16 - ADC Channel16 selected.
*            ADC_Channel_17 - ADC Channel17 selected.
*
* [url=home.php?mod=space&uid=266161]@return[/url]  none
*/
u16 Get_ADC_Val(u8 ch)
{
    u16 val;

    ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_CyclesMode7);
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);

    while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
    val = ADC_GetConversionValue(ADC1);

    return val;
}

/*********************************************************************
* @fn      Get_ConversionVal
*
* @brief   Get Conversion Value.
*
* @param   val - Sampling value
*
* @return  val+Calibrattion_Val - Conversion Value.
*/
u16 Get_ConversionVal(s16 val)
{
    if((val + Calibrattion_Val) < 0 || val==0)
        return 0;
    if((Calibrattion_Val + val) > 4095||val==4095)
        return 4095;
    return (val + Calibrattion_Val);
}

/*********************************************************************
* @fn      main
*
* @brief   Main program.
*
* @return  none
*/
int main(void)
{
    u16 ADC_val ;
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

    ADC_Function_Init();
    printf("CalibrattionValue:%d\n", Calibrattion_Val);

    while(1)
    {
        ADC_val = Get_ADC_Val(ADC_Channel_1);
        printf("ADC_val = %d\r\n",Get_ConversionVal(ADC_val));
        Delay_Ms(1000);
    }
}
</font>
初始化过程中,首先配置4倍增益,最大可配64倍,开启使能输入Buffer,开启配置偏移校准。

关于ADC模拟看门狗配置
如果被ADC转换的模拟电压低于低阈值或高于高阈值,AWD模拟看门狗状态位被设置。阈值设置位于ADC_WDHTR和ADC_WDLTR寄存器的最低12个有效位中。通过设置ADC_CTLR1 寄存器的AWDIE位以允许产生相应中断。通过设置ADC_CFG寄存器的AWDRST_EN位可开启模拟看门狗复位。
在CH32L103 EVT中有模拟看门狗例程,程序如下:
<font face="新宋体" size="3">/********************************** (C) COPYRIGHT *******************************
* File Name          : main.c
* Author             : WCH
* Version            : V1.0.0
* Date               : 2024/01/19
* Description        : Main program body.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/

/*
*@Note
*Analog watchdog routine:
*ADC channel 1 (PA1), detect that the ADC conversion data on the rule group channel
*is outside 2000 - 3500 and trigger the simulation Watchdog interrupt.
*
*/

#include "debug.h"

/* Global Variable */
s16 Calibrattion_Val = 0;

/* WWDG Reset Enable Definition */
#define WDT_RST_ENABLE   0
#define WDT_RST_DISABLE  1

/* WWDG Reset Enable Selection */
#define WDT_RST   WDT_RST_DISABLE
//#define WDT_RST   WDT_RST_ENABLE

/*********************************************************************
* @fn      ADC_Function_Init
*
* @brief   Initializes ADC collection.
*
* @return  none
*/
void ADC_Function_Init(void)
{
    ADC_InitTypeDef  ADC_InitStructure = {0};
    GPIO_InitTypeDef GPIO_InitStructure = {0};
    NVIC_InitTypeDef NVIC_InitStructure = {0};

    RCC_PB2PeriphClockCmd(RCC_PB2Periph_GPIOA, ENABLE);
    RCC_PB2PeriphClockCmd(RCC_PB2Periph_ADC1, ENABLE);
    RCC_ADCCLKConfig(RCC_PCLK2_Div8);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_DeInit(ADC1);
    Calibrattion_Val=Get_CalibrationValue(ADC1);
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);

    /* Higher Threshold:3500, Lower Threshold:2000 */
    ADC_AnalogWatchdogThresholdsConfig(ADC1, 3500, 2000);
    ADC_AnalogWatchdogSingleChannelConfig(ADC1, ADC_Channel_1);

#if (WDT_RST == WDT_RST_ENABLE)
    ADC_AnalogWatchdogResetCmd(ADC1, ENABLE);
#else
    ADC_AnalogWatchdogResetCmd(ADC1, DISABLE);
#endif
    ADC_AnalogWatchdogCmd(ADC1, ADC_AnalogWatchdog_SingleRegEnable);

    NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    ADC_ITConfig(ADC1, ADC_IT_AWD, ENABLE);
    ADC_Cmd(ADC1, ENABLE);

    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_CyclesMode7);

    ADC_FIFO_Cmd(ADC1, ENABLE);
    ADC_BufferCmd(ADC1, DISABLE); //disable buffer
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));
}

/*********************************************************************
* @fn      Get_ADC_Val
*
* @brief   Returns ADCx conversion result data.
*
* @param   ch - ADC channel.
*            ADC_Channel_0 - ADC Channel0 selected.
*            ADC_Channel_1 - ADC Channel1 selected.
*            ADC_Channel_2 - ADC Channel2 selected.
*            ADC_Channel_3 - ADC Channel3 selected.
*            ADC_Channel_4 - ADC Channel4 selected.
*            ADC_Channel_5 - ADC Channel5 selected.
*            ADC_Channel_6 - ADC Channel6 selected.
*            ADC_Channel_7 - ADC Channel7 selected.
*            ADC_Channel_8 - ADC Channel8 selected.
*            ADC_Channel_9 - ADC Channel9 selected.
*            ADC_Channel_16 - ADC Channel16 selected.
*            ADC_Channel_17 - ADC Channel17 selected.
*
* @return  none
*/
u16 Get_ADC_Val(u8 ch)
{
    u16 val;

    ADC_SoftwareStartConvCmd(ADC1, ENABLE);

    while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));

    val = ADC_GetConversionValue(ADC1);

    return val;
}

/*********************************************************************
* @fn      main
*
* @brief   Main program.
*
* @return  none
*/
int main(void)
{
    u16 ADC_val;

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

    ADC_Function_Init();
    printf("CalibrattionValue:%d\n", Calibrattion_Val);

    while(1)
    {
        ADC_val = Get_ADC_Val(ADC_Channel_1);
        printf("%04d\r\n", ADC_val);
        Delay_Ms(500);
    }
}


void ADC_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));

/*********************************************************************
* @fn      ADC1_2_IRQHandler
*
* @brief   This function handles analog wathdog exception.
*
* @return  none
*/
void ADC_IRQHandler(void)
{
    if(ADC_GetITStatus( ADC1, ADC_IT_AWD)){
        printf( "Enter AnalogWatchdog Interrupt\r\n" );
    }

    ADC_ClearITPendingBit( ADC1, ADC_IT_AWD);
}</font>
该例程相较于正常ADC例程,在此基础上增加了对模拟看门狗的配置,如下图:
ADC_AnalogWatchdogThresholdsConfig函数主要用于对ADC看门狗高低阈值寄存器进行配置,如下图:
例程中配置高阈值寄存器的值为3500,低阈值寄存器的值为2000.
ADC_AnalogWatchdogSingleChannelConfig函数主要是对ADC 控制寄存器1(ADC_CTLR1)位[4:0]进行配置,如下图,例程配置选择通道1。
ADC_AnalogWatchdogResetCmd函数用于使能或禁止模拟看门狗复位,主要是对ADC 配置寄存器(ADC_CFG)进行配置,如下图:
当禁止模拟看门狗复位,当ADC通道采样值不在设置阈值范围内时,MCU不会复位,如下图打印现象:
当使能模拟看门狗复位,当ADC通道采样值不在设置阈值范围内时,MCU会复位,如下图打印现象:
MCU在不停复位。

关于ADC内部温度传感器
CH32L103内置温度传感器,连接 ADC_INT16 通道,通过 ADC 将传感器输出的电压转换成数字值来反馈芯片内部温度,推荐设置采样时间是 17.1us。温度传感器输出的电压随温度线性变化,由于制造离散性,其线性变化的曲线斜率和偏移有所不同,所以内部温度传感器更适合于检测温度的变化,而不是测量绝对的温度。如果需要测量精确的温度,应该使用一个外置的温度传感器。

3、ADC使用注意事项
关于ADC通道串扰问题
ADC多通道使用时,有可能会遇到串扰问题,问题原因是相邻通道之间透过采样电容Cs发生了藕合。
解决办法:
(1)增大 ADC 相邻两个通道采样之间的延时,增加采样保持时间或在电压信号相差较大的信道中间,增加对基准电压的采样,使信号处于平均值,在切换通道时不会因为充放电不及时导致数据错误。
(2)精度要求不高的话,把采样周期设置的大一些,越大越好。当然,干扰还是会有,只是会低一些。
(3)若有两个ADC单元,相邻两个通道使用不同的ADC单元,如把通道5和通道6分别配置到ADC1和ADC2上面,彻底隔离。
(4)DMA方式一次只配置一个通道,例如先让通道5采集500ms,再让通道6采集500ms,同时前100ms的数据丢掉不用。

关于ADC校准
CH32L103的ADC值需要进行软件校准,需要根据得到的校准值进行校准配置,在EVT例程中提供了校准函数,使用时可直接调用该函数,如下图:

关于ADC外部输入阻抗
AD采样时,需考虑信号输入电路的阻抗,整体上,该阻值越大,为保证转换精度,所需采样时间就越长。芯片采样时间是有限的,外部输入阻抗也是有上限的,CH32L103的外部输入阻抗最大值为50KΩ。
关于ADC采样偏差问题
ADC采样时,有时会遇到采样结果偏差过大问题,可以从以下几个角度查找问题:
(1)检查一下VDDA的值是否在规定范围内,一般设置VDDA=VDD,但也可单独给VDDA供电;
(2)检查一下外部采样电路、分压电路是否有问题,注意外部电路与MCU共GND;
(3)注意模拟引脚不要接超过VDDA的上拉电阻;
(4)检查一下测试所用电源是否为隔离电源,开发板尽量不要用电脑供电。电脑、测试电源轻微漏电都会导致测量结果出现大偏差。

附件为ADC相关例程,可以下载参考一下。
         

CH32L103 ADC.zip

3.32 MB

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

12

主题

16

帖子

0

粉丝