【GD32F303红枫派使用手册】第十一节 ADC-电源电压单通道ADC检测实验

11.1 实验内容

通过本实验主要学习以下内容:

  • ADC的简介
  • GD32F303 ADC工作原理
  • 查询方式实现ADC单通道采样

11.2 实验原理

11.2.1 ADC原理

我们知道,自然界中有非常多的模拟信号,比如上一节提到的光照强度,还有其他的例如温度、声音等等,那么人们是怎么来衡量一个模拟信号的呢?

我们通常会说今天光照度达到了3万Lux(照度单位),现在测量到的体温是36.5℃,我们所处的环境是40分贝,没错,人们就是通过将这些模拟信号数字化,从而达到衡量这些模拟信号的目的。那对于MCU来说,如果要测量一个模拟量,可以通过自带的ADC(Analog-to-Digital converters)模块,即模-数转换器将模拟量转化为可以被MCU读取到的数字量。

11.2.2 GD32F303 ADC工作原理

GD32F303有3个12位逐次逼近型ADC(SAR ADC),这三个ADC可以独立工作,也可以让ADC0和ADC1工作在同步模式下。有最多21个外部ADC引脚可用于将连接到这些引脚的电压值转换为数字量,这些引脚号可以通过Datasheet获得。

表中ADC012_INx的意思是:该IO口可以作为通道x用于ADC0、ADC1和ADC2。如ADC012_IN0,表示PA0可以用于ADC0的通道0使用,也可以作为ADC1和ADC2的通道0使用。但要注意:不能在同一个时刻让不同的ADC去转换同一个通道,否则会有无法预料的结果

 

以下总结了GD32F303 ADC的特性:

  • 高性能:
     – 可配置12位、 10位、 8位、或者6位分辨率;
     – 自校准;
     – 可编程采样时间;
     – 数据寄存器可配置数据对齐方式;
     – 支持规则数据转换的DMA请求。
  • 模拟输入通道:
     – 16个外部模拟输入通道;
     – 1个内部温度传感器输入通道(VSENSE);
     – 1个内部参考电压输入通道(VREFINT)。
  •  转换开始的发起:
     – 软件;
     – 硬件触发。
  • 转换模式:
     – 转换单个通道,或者扫描一组通道;
     – 单次模式,每次触发转换一次选择的输入通道;
     – 连续模式,连续转换所选择的输入通道;
     – 间断模式;
     – 同步模式(适用于具有两个或多个ADC的设备)。
  •  模拟看门狗。
  •  中断的产生:
     – 规则组或注入组转换结束;
     – 模拟看门狗事件。
  • 过采样:
     – 16位的数据寄存器;
     – 可调整的过采样率,从2x到256x;
     – 高达8位的可编程数据移位。
  •  ADC供电要求:
     – 2.4V到3.6V,一般供电电压为3.3V。
  •  ADC输入范围: VREFN ≤VIN ≤VREFP  。

下面介绍下GD32F303的ADC框图:

标注1:输入电压和参考电压

输入电压引脚定义如下表:

大于等于100pin的GD32F303,ADC参考电压等于VREFP,100pin以下的GD32F303,ADC参考电压等于VDDA

 GD32F303的ADC是12bit有效位的,满量程对应的转换值是4095,即当采样引脚上的电压等于ADC参考电压时,得到的转换值即为4095。故理论采样是指可通过以下公式得到:

采样数值=实际电压/参考电压*4095

标注2:输入通道

前面提到,ADC有最多16个外部模拟通道和2个内部通道,外部通道号从IN0~IN15,由IO口号来决定,两个内部通道是IN16(温度传感器)和IN17(内部Vrefint,典型值1.2V),下表给出了IO口号对应的ADC通道:

标注3:规则组和注入组

每个ADC有两个组——规则组和注入组。

规则组有两个重要的参数,其一为转换的个数,其二为转换的序列,规定好这两个参数后,一旦开始规则组的转换,则ADC就按照转换序列一个一个的进行模-数转换,直到达到要求的转换个数。

规则组的转换个数由ADC_RSQ0寄存器的RL[3:0]位规定,转换的总数目为RL[3:0]+1,转换总数目最大为16个;转换序列由ADC_RSQ0~ADC_RSQ2共同决定,我们来看下这几个寄存器。

ADC_RSQ0寄存器:

ADC_RSQ1寄存器: ADC_RSQ2寄存器:

举个例子,现需要按照CH3->CH2->CH1的顺序进行规则组转换,则设定RL[3:0] = 2,然后设定RSQ0为CH3,RSQ1为CH2,RSQ2为CH1,则当开始规则组转换时,ADC首先进行RSQ0规定的通道即CH3的转换,再进行RSQ1规定的通道即CH2的转换,最后进行RSQ2规定的通道即CH1转换,当这三个通道转换完后,规则组转换结束。

需要注意的是,每转换一个规则组通道,转换结果都会放在寄存器ADC_RDATA中,所以CPU一定要在下一个通道转换完成前将上一个通道转换结果读走,否则会导致上一个通道数据被新的数据覆盖。所以在多通道规则组转换时,为了保证能读到所有通道的数据,一定要使用DMA(直接存储器访问控制器),每个通道转换结束后,都会给DMA发送请求,DMA就会将最新的ADC_RDATA中的数据搬走。关于ADC配合DMA的使用,后面章节会详细介绍。

说完规则组,我们再说下注入组。注入组,可以按照特定的序列组织成最多 4 个转换的序列。 ADC_ISQ 寄存器规定了注入组的通道选择。 ADC_ISQ 寄存器的 IL[1:0]位规定了整个注入组转换序列的长度。  

ADC_ISQ寄存器:

 

和规则组转换序列不同的是,如果 IL[1:0]长度不足 4,注入通道转换从(4-IL[1:0]-1) 开始:

当IL = 3,注入组转换顺序为ISQ0 >> ISQ1 >> ISQ2 >> ISQ3,转换结果分别放在ADC_IDATA0~ADC_IDATA3;

当IL = 2,注入组转换顺序为ISQ1 >> ISQ2 >> ISQ3,转换结果分别放在ADC_IDATA0~ADC_IDATA2;

当IL = 1,注入组转换顺序为ISQ2 >> ISQ3,转换结果分别放在ADC_IDATA0~ADC_IDATA1;

当IL = 0,注入组转换ISQ3,转换结果放在ADC_IDATA0

举个例子,现需要按照CH3->CH2->CH1的顺序进行注入组转换,则设定IL[3:0] = 2,然后设定ISQ1为CH3,ISQ2为CH2,ISQ3为CH1,则当开始注入组转换时,ADC首先进行ISQ1规定的通道即CH3的转换,再进行ISQ2规定的通道即CH2的转换,最后进行ISQ3规定的通道即CH1转换,当这三个通道转换完后,注入组转换结束。

因为4个通道转换的结果分别放在4个不同的注入组数据寄存器ADC_IDATAx中,所以注入组不需要用到DMA,只需要在注入组转换完成后分别去不同注入组数据寄存器中取数即可。

标注4:触发源

ADC的规则组和注入组需要选特定的触发源用于触发ADC转换,注意,ADC的Enable(即ADC_CTL1寄存器的ADC_ON位置“1”)不会触发ADC转换,而是当选定的触发源来临后ADC才开始转换。

触发源分为内部触发和外部触发,内部触发是指当ADC_ON已经为“1”的情况下,不改变其他ADC寄存器,再往ADC_ON位写“1”,将触发一次ADC转换;外部触发源是除了内部触发源以外的触发源,外部触发源可以通过ADC_CTL1寄存器查看:

ADC_CTL1寄存器:

标注5:规则组和注入组的数据寄存器

如标注3规则组和注入组中的表述,每个ADC的规则组只有一个数据寄存器ADC_RDATA,每转换一个通道,转换结果放在这个寄存器中,在下一通道转换结束前必须要将上一个通道的转换结果取走;每个ADC的注入组有4个数据寄存器ADC_IDATAx(x = 0,1,2,3),分别保存4个通道的ADC注入组的转换数据。

标注6:ADC中断及标志位

ADC的中断总共有三种:规则组转换结束中断、注入组转换结束中断以及模拟看门狗,可以通过将ADC_CTL0中的EOCIE、EOICIE和WDEIE置“1”来开启相应中断。

ADC_STAT寄存器中的EOC、EOIC和WDE表示相应事件发生,EOC置“1”表示规则组的转换已经结束;EOIC置“1”表示注入组的转换已经结束,注意:注入组转换结束时,EOC标志位也会置起。

GD32F303的ADC原理部分就介绍到这里,下面我们通过电源电压单通道采样实验来详细介绍下ADC的用法。

11.3 硬件设计

电源电压检测的原理图如下:

ADC_IN4连接到MCU的PF6管脚通过ADC转换可以得到PF6脚上具体的电压值,再通过该电压值可反推电源电压值。

11.4 代码解析

本实验只用到一个ADC通道:PF6——ADC2_CH4,故可以选择使用ADC2的规则组进行转换,并通过查询EOC标志位来判断通道转换完成。

11.4.1 ADC初始化

在driver_adc.c中定义了ADC初始化函数driver_adc_config:

C
void driver_adc_config(typdef_adc_ch_general *ADC,typdef_adc_ch_parameter *ADC_CH)
{         
    uint8_t i;
    /*配置ADC时钟频率*/
    rcu_adc_clock_config(ADC->adc_psc);
    /*使能ADC时钟*/
    rcu_periph_clock_enable(ADC->rcu_adc);
    /*配置ADC相关IO口,先配置时钟,再将IO口设置为模拟输入*/
    for(i=0 ;i<ADC->ch_count; i++)
    {
        if(ADC_CH[i].adc_channel < ADC_CHANNEL_16)
        {
            rcu_periph_clock_enable(ADC_CH[i].rcu_port);
            gpio_init(ADC_CH[i].port, GPIO_MODE_AIN, ADC_CH[i].gpio_speed, ADC_CH[i].pin);                                        
        }
        else
        {
            adc_tempsensor_vrefint_enable();
        }
    }
    /*配置ADC工作模式,如独立模式,规则并行模式等*/
    adc_mode_config(ADC->adc_mode);
    /*配置规则组的扫描模式和连续转换模式*/
    adc_special_function_config(ADC->adc_port, ADC_SCAN_MODE, ADC->adc_scan_function);
    if(ADC->adc_channel_group == ADC_REGULAR_CHANNEL)
    {
        adc_special_function_config(ADC->adc_port, ADC_CONTINUOUS_MODE, ADC->adc_continuous_function);        
    }
    /*选择数据右对齐*/
    adc_data_alignment_config(ADC->adc_port, ADC_DATAALIGN_RIGHT);
    /*配置转换通道数*/
    adc_channel_length_config(ADC->adc_port, ADC->adc_channel_group, ADC->ch_count);        
    /*配置转换顺序*/
    if(ADC->adc_channel_group == ADC_REGULAR_CHANNEL)
    {
        for(i = 0;i< ADC->ch_count;i++)
        {
            adc_regular_channel_config(ADC->adc_port, i, ADC_CH[i].adc_channel,ADC_CH[i].sample_time);
        }
    }
    else if(ADC->adc_channel_group == ADC_INSERTED_CHANNEL)
    {
        for(i = 0;i< ADC->ch_count;i++)
        {
            adc_inserted_channel_config(ADC->adc_port, i, ADC_CH[i].adc_channel,ADC_CH[i].sample_time);
        }        
    }
    /*选择触发源及使能外部触发模式*/
    adc_external_trigger_source_config(ADC->adc_port, ADC->adc_channel_group, ADC->trigger_source);        
    adc_external_trigger_config(ADC->adc_port, ADC->adc_channel_group, ENABLE);
    /*选择是否需要使用DMA*/
    if(ADC->DMA_mode == ENABLE)
    {
        adc_dma_mode_enable(ADC->adc_port);
    }
    /*ADC的使能和自校准,ADC使能后需要经过一定的ADC_CLK后才能校准,本示例中直接使用1ms延时*/
    adc_enable(ADC->adc_port);
    delay_ms(1);
    adc_calibration_enable(ADC->adc_port);
}

 

C
void driver_adc_config(typdef_adc_ch_general *ADC,typdef_adc_ch_parameter *ADC_CH)
{         
    uint8_t i;
    /*配置ADC时钟频率*/
    rcu_adc_clock_config(ADC->adc_psc);
    /*使能ADC时钟*/
    rcu_periph_clock_enable(ADC->rcu_adc);
    /*配置ADC相关IO口,先配置时钟,再将IO口设置为模拟输入*/
    for(i=0 ;i<ADC->ch_count; i++)
    {
        if(ADC_CH[i].adc_channel < ADC_CHANNEL_16)
        {
            rcu_periph_clock_enable(ADC_CH[i].rcu_port);
            gpio_init(ADC_CH[i].port, GPIO_MODE_AIN, ADC_CH[i].gpio_speed, ADC_CH[i].pin);                                        
        }
        else
        {
            adc_tempsensor_vrefint_enable();
        }
    }
    /*配置ADC工作模式,如独立模式,规则并行模式等*/
    adc_mode_config(ADC->adc_mode);
    /*配置规则组的扫描模式和连续转换模式*/
    adc_special_function_config(ADC->adc_port, ADC_SCAN_MODE, ADC->adc_scan_function);
    if(ADC->adc_channel_group == ADC_REGULAR_CHANNEL)
    {
        adc_special_function_config(ADC->adc_port, ADC_CONTINUOUS_MODE, ADC->adc_continuous_function);        
    }
    /*选择数据右对齐*/
    adc_data_alignment_config(ADC->adc_port, ADC_DATAALIGN_RIGHT);
    /*配置转换通道数*/
    adc_channel_length_config(ADC->adc_port, ADC->adc_channel_group, ADC->ch_count);        
    /*配置转换顺序*/
    if(ADC->adc_channel_group == ADC_REGULAR_CHANNEL)
    {
        for(i = 0;i< ADC->ch_count;i++)
        {
            adc_regular_channel_config(ADC->adc_port, i, ADC_CH[i].adc_channel,ADC_CH[i].sample_time);
        }
    }
    else if(ADC->adc_channel_group == ADC_INSERTED_CHANNEL)
    {
        for(i = 0;i< ADC->ch_count;i++)
        {
            adc_inserted_channel_config(ADC->adc_port, i, ADC_CH[i].adc_channel,ADC_CH[i].sample_time);
        }        
    }
    /*选择触发源及使能外部触发模式*/
    adc_external_trigger_source_config(ADC->adc_port, ADC->adc_channel_group, ADC->trigger_source);        
    adc_external_trigger_config(ADC->adc_port, ADC->adc_channel_group, ENABLE);
    /*选择是否需要使用DMA*/
    if(ADC->DMA_mode == ENABLE)
    {
        adc_dma_mode_enable(ADC->adc_port);
    }
    /*ADC的使能和自校准,ADC使能后需要经过一定的ADC_CLK后才能校准,本示例中直接使用1ms延时*/
    adc_enable(ADC->adc_port);
    delay_ms(1);
    adc_calibration_enable(ADC->adc_port);
}

 在解析上述代码前,我们先看driver_adc.h两个结构体声明。

1、ADC设置参数结构体,该结构体中规定了ADC设置所需要的参数:

C
/*ADC设置参数*/
typedef struct __typdef_adc_general
{
    rcu_periph_enum rcu_adc;//ADC时钟口
    uint32_t adc_psc;//ADC时钟源分频系数
    uint32_t adc_port;//ADC号
    uint32_t adc_mode;//ADC工作模式:ADC_MODE_FREE,ADC_DAUL_REGULAL_PARALLEL
    uint8_t adc_channel_group;//ADC工作组:规则组或注入组
    EventStatus adc_scan_function;//设置扫描模式
    EventStatus adc_continuous_function;//设置循环模式
    uint8_t ch_count;//设置转换通道个数
    typdef_adc_dma_parameter dma_parameter;//若使用DMA,则需要设置dma
    uint32_t trigger_source;//ADC触发源
    EventStatus DMA_mode;//是否使用DMA
}typdef_adc_ch_general;

 2、ADC IO口及通道参数结构体,该结构体中规定了用于ADC转换的IO口及通道参数:

C
/*ADC IO口及通道参数*/
typedef struct __typdef_adc_ch_parameter
{
    rcu_periph_enum rcu_port;//IO口时钟
    uint32_t port;//IO port
    uint32_t pin;//IO pin
    uint32_t gpio_speed;//IO 速率
    uint8_t adc_channel;//IO对应的ADC通道
    uint32_t sample_time;//IO的采样周期
}typdef_adc_ch_parameter;

好,现在我们来对ADC配置进行分段解析。

1、ADC的时钟频率配置和ADC时钟使能:

C
/*配置ADC时钟频率*/
rcu_adc_clock_config(ADC->adc_psc);
/*使能ADC时钟*/
rcu_periph_clock_enable(ADC->rcu_adc);

 GD32F303的ADC的工作时钟频率不能超过40MHz,而ADC是挂载在APB2总线上的,APB2总线最高频率可以达到120M,所以需要进行分频处理。

2、配置IO口

C
/*配置ADC相关IO口,先使能时钟,再将IO口设置为模拟输入*/
for(i=0 ;i<ADC->ch_count; i++)
{
    if(ADC_CH[i].adc_channel < ADC_CHANNEL_16)
    {
        rcu_periph_clock_enable(ADC_CH[i].rcu_port);
        gpio_init(ADC_CH[i].port, GPIO_MODE_AIN, ADC_CH[i].gpio_speed, ADC_CH[i].pin);                                        
    }
    else
    {
        adc_tempsensor_vrefint_enable();
    }
}

被用作ADC采样的IO口需要被设置为Analog模式,因为ADC_CH0~ADC_CH15是和外部IO关联的,而ADC_CH16和ADC_CH17是内部通道,所以只有在通道号小于ADC_CHANNEL_16时才需要配置IO口,而大于等于ADC_CHANNEL_16时需要使能ADC内部通道。

3、配置ADC规则组工作模式

C
/*配置ADC工作模式,如独立模式,规则并行模式等*/
adc_mode_config(ADC->adc_mode);

ADC的模式有独立模式、规则并行、注入并行、快速交叉等9种,其中用到比较多的是独立模式、规则并行和注入并行,现对这三种做简单介绍。

独立模式:三个ADC相互之间无影响,每个ADC单独工作;

规则并行:ADC0和ADC1可工作规则并行模式下,当ADC0规则组被触发开始转换时,ADC1注入组也会自动开始转换(此时ADC1的触发源一定要选择软件触发),转换结果会分别放在ADC0_RDATA和ADC1_RDATA中,其中ADC0_RDATA的上半字也会保存ADC1的转换结果,这样设计是为了方便DMA去进行两个同步ADC结果的同时搬运。

基于16个通道的规则并行模式如下图所示:

注意:
1.不要在两路 ADC 上转换相同的通道(两路 ADC 在同一通道转换时采样时间不可重叠)。
2.在并行模式下, ADC0 和 ADC1 并行采样的两个通道的需要设置为准确的相同采样时间。  

 注入并行:ADC0和ADC1可工作注入并行模式下,当ADC0注入组被触发开始转换时,ADC1注入组也会自动开始转换(此时ADC1的触发源一定要选择软件触发),和规则组不同的是,通道转换结果会放在各自的 ADC_IDATAx 寄存器中 。

基于4个通道的注入并行模式如下图所示:

4、配置扫描和连续模式

C
/*配置规则组的扫描模式和连续转换模式*/
adc_special_function_config(ADC->adc_port, ADC_SCAN_MODE, ADC->adc_scan_function);
if(ADC->adc_channel_group == ADC_REGULAR_CHANNEL)
{
    adc_special_function_config(ADC->adc_port, ADC_CONTINUOUS_MODE, ADC->adc_continuous_function);        
}                

扫描模式:当一个规则组或注入组规定了1个以上的通道转换时,需要开启扫描模式,此时规则组或注入组会根据设定好的转换顺序对通道一个一个进行转换;如果关闭扫描模式,那么规则组和注入组只会转换RSQ0和ISQ0规定的通道。

连续模式:该模式只适用于规则组,开启该模式时,当规则组转换完成后,会自动开始新一轮的转换。

下图为扫描转换模式,且连续转换模式失能的转换情况:

下图为扫描转换模式,连续转换模式使能的转换情况:

 5、设置数据对齐

C
/*选择数据右对齐*/
adc_data_alignment_config(ADC->adc_port, ADC_DATAALIGN_RIGHT);

ADC的转换结果可以选择左对齐或右对齐,以12位结果为例,左右对齐如下:

6、配置转换个数、转换顺序及通道采样周期

C
/*配置转换通道数*/
    adc_channel_length_config(ADC->adc_port, ADC->adc_channel_group, ADC->ch_count);        
    /*配置转换顺序*/
    if(ADC->adc_channel_group == ADC_REGULAR_CHANNEL)
    {
        for(i = 0;i< ADC->ch_count;i++)
        {
            adc_regular_channel_config(ADC->adc_port, i, ADC_CH[i].adc_channel,ADC_CH[i].sample_time);
        }
    }
    else if(ADC->adc_channel_group == ADC_INSERTED_CHANNEL)
    {
        for(i = 0;i< ADC->ch_count;i++)
        {
            adc_inserted_channel_config(ADC->adc_port, i, ADC_CH[i].adc_channel,ADC_CH[i].sample_time);
        }        
    }

 这段程序是配置规则组和注入组的转换长度、转换顺序及通道采样周期。转换长度和顺序前面讲过,不再赘述,这里简单介绍下采样周期。

采样周期通过adc_regular_channel_config和adc_inserted_channel_config函数的最后一个形参设置,相应的寄存器是ADC_SAMPT0和ADC_SAMPT1。

GD32F303是SAR ADC(逐次逼近型ADC),内部有采样电容,采样过程为先将内部采样电容和外部采样引脚相连,通过对内部采样电容的充放电让采样电容和外部采样引脚电压相等,然后关闭内外部电路通道,内部再通过逐次逼近比较得到电压的数字值。逐次比较的过程需要固定的1.5个采样周期,而采样电容充放电过程是可以设置的,即这里介绍的采样周期。

采样周期分8个档位可选,如一个通道选择12.5周期,则转换过程需要的总周期数为12.5+1.5 = 14,如果设置ADC的时钟(前面提到,ADC时钟是通过APB2总线经过分频所得)为30M,那么该通道转换所需要的时间为14/30M = 0.467us。

那么如何选择合适的采样周期,这个和内部采样电容的充放电时间有关,如果充放电时间不够,采样电容电压不能和外部采样引脚电压一致,则得不到准确的采样值,如充放电时间过长,则浪费CPU资源。我们可以根据采样引脚对内输入阻抗值大小来选择合适的采样周期,阻抗值和采样周期对应表可以通过GD32F303 Datasheet ADC电气参数章节获得:

举个例子,实际采样引脚对内阻抗为20kΩ,根据上表需要选择的采样周期为55.5。

7、选择ADC规则组和注入组的触发源以及使能外部触发

C
/*选择触发源及使能外部触发模式*/
adc_external_trigger_source_config(ADC->adc_port, ADC->adc_channel_group, ADC->trigger_source);        
adc_external_trigger_config(ADC->adc_port, ADC->adc_channel_group, ENABLE);

8、选择是否需要DMA

C
/*选择是否需要使用DMA*/
if(ADC->DMA_mode == ENABLE)
{
    adc_dma_mode_enable(ADC->adc_port);
}

 只有规则组才能使用DMA,在规则组设置了多通道采样即扫描模式打开时,是一定要用DMA的,否则没办法及时将转换完成的通道数据取走,其他的情况下DMA是否使用根据实际应用决定。

9、ADC的使能和自校准

C
 /*ADC的使能和自校准,ADC使能后需要经过一定的ADC_CLK后才能校准,本示例中直接使用1ms延时*/
adc_enable(ADC->adc_port);
delay_ms(1);
adc_calibration_enable(ADC->adc_port);

ADC的使能比较简单,使能后ADC才能工作,这里再强调下,使能ADC不代表就开始了ADC转换,而是ADC等到触发信号才会转换,关于触发条件,请读者看前面触发源介绍。

ADC 带有一个前置校准功能。在校准期间, ADC 计算一个校准系数,这个系数是应用于 ADC内部的,它直到 ADC 下次掉电才无效。在校准期间,应用不能使用 ADC,它必须等到校准完成。在 A/D 转换前应执行校准操作。通过软件设置 CLB=1 来对校准进行初始化,在校准期间CLB 位会一直保持 1,直到校准完成,该位由硬件清 0。

ADC使能后校准前比较要等待至少14个ADC时钟周期,本实验中就直接使用1ms延时了,是完全足够的。

关于校准:一般建议ADC enable之后进行一次校准,但需要保证校准器件,ADC参考电压一定要很稳定,否则可能校准到一个错误的情况,这样后续的ADC转换反而不准确了,所以在一些特定情况下,校准也可以不加。

 11.4.2 轮训方式获取采样值函数

ADC初始化好后,就可以进行采样了,本实验是通过轮训方式进行ADC采样,下面是轮训方式获取采样值函数。

C
uint16_t driver_adc_transform_polling(typdef_adc_ch_general *ADC,typdef_adc_ch_parameter *ADC_CH)
{
    /*规则组采样*/
    if(ADC->adc_channel_group == ADC_REGULAR_CHANNEL)
    {
        /*设置规则组需要采样的通道*/
        adc_regular_channel_config(ADC->adc_port, 0, ADC_CH->adc_channel, ADC_CH->sample_time);        
        /*软件触发规则组转换*/
        adc_software_trigger_enable(ADC->adc_port, ADC_REGULAR_CHANNEL);
        /*等待EOC置起*/
        while(RESET == adc_flag_get(ADC->adc_port,ADC_FLAG_EOC));
        /*清除EOC标志位*/
        adc_flag_clear(ADC->adc_port,ADC_FLAG_EOC);
        /*将规则组转换结果作为返回值*/
        return ADC_RDATA(ADC->adc_port);
            
    }
    /*注入组采样*/
    else if(ADC->adc_channel_group == ADC_INSERTED_CHANNEL)
    {
        /*设置注入组需要采样的通道*/
        adc_inserted_channel_config(ADC->adc_port, 0, ADC_CH->adc_channel, ADC_CH->sample_time);        
        /*软件触发注入组转换*/
        adc_software_trigger_enable(ADC->adc_port, ADC_INSERTED_CHANNEL);
        /*等待EOIC置起 */
        while(RESET == adc_flag_get(ADC->adc_port,ADC_FLAG_EOIC));
        /*清除EOIC标志位*/
        adc_flag_clear(ADC->adc_port,ADC_FLAG_EOIC);
        /*将注入组转换结果作为返回值*/
        return ADC_IDATA0(ADC->adc_port);
    }
    return 0;
}

读者可以读以上代码的注释来分析这个函数,强调下,判断规则组转换完成用EOC标志位,注入组用EOIC。另外这个函数返回值是16位,但如果使用了规则并行或注入并行的话,该函数还需要稍作调整,读者可以思考下该如何修改?

11.4.3 Power_detect ADC设置所需要的参数及IO口结构体定义

在bsp_adc.c中,对Power_detect_ADC设置所需要的参数及IO扩结构体进行了定义:

C
typdef_adc_ch_general  Power_detect_ADC= {
    .rcu_adc = RCU_ADC2,//ADC2的时钟
    .adc_psc = RCU_CKADC_CKAPB2_DIV6,//ADC2设置为APB2 6分频
    .adc_port = ADC2,//ADC口为ADC2
    .adc_mode = ADC_MODE_FREE,//ADC模式为独立模式
    .adc_channel_group = ADC_REGULAR_CHANNEL,//使用规则组
    .adc_scan_function = DISABLE,//关闭扫描模式
    .adc_continuous_function = DISABLE,//关闭循环模式
    .ch_count = 1,//转换长度为1
    .dma_parameter = {0},//不使用DMA
    .trigger_source = ADC0_1_2_EXTTRIG_REGULAR_NONE,//ADC触发源选择为软件触发
    .DMA_mode = DISABLE//不使用DMA
};
//ADC通道参数配置,包括IO口,和对应通道以及采样周期
typdef_adc_ch_parameter Power_detect_ch_parameter= 
{
    .rcu_port = RCU_GPIOF,//GPIOF时钟
    .port = GPIOF,//GPIO port
    .pin = GPIO_PIN_6,//PF6
    .gpio_speed = GPIO_OSPEED_10MHZ,//PF6速度设置为10MHz
    .adc_channel = ADC_CHANNEL_4,//PF6是ADC2的通道4
    .sample_time = ADC_SAMPLETIME_55POINT5//设置采样周期为55.5
};

11.4.4 Power_detect ADC初始化和读Power_detect的具体实现函数

在bsp_adc.c中定义了Power_detect ADC初始化和读Power_detect的具体实现函数:

C
uint16_t Power_detect_data = 0;
void bsp_Power_detect_ADC_config()
{
    driver_adc_config(&Power_detect_ADC,&Power_detect_ch_parameter);
}
void bsp_Power_detect_data_get()
{
    Power_detect_data = driver_adc_transform_polling(&Power_detect_ADC,&Power_detect_ch_parameter);
}

11.4.5 main函数实现

C
int main(void)
{
  driver_init();//延时函数初始化
    bsp_uart_init(&BOARD_UART);//BOARD_UART串口初始化
    bsp_Power_detect_ADC_config();//Power_detect ADC配置        
    while (1)
    {
        delay_ms(1000);//延时1s
        bsp_Power_detect_data_get();//获取Power_detect数据
        printf(" the Power_detect data is %d \r\n", Power_detect_data);//打印Power_detect数据
        printf(" the Power voltage is %.2f V \r\n", (float)Power_detect_data/4096*3.3f*2);//输出供电电压值
    }
}

 

本例程main函数首先进行了延时函数初始化,为了演示实验结果,这里初始化了BOARD_UART串口,关于串口的使用,请读者参考串口章节,然后是Power_detect ADC配置。在主循环中实现每秒钟进行一次Power_detect的ADC采样,并将转换结果打印出来。

11.5 实验结果

使用USB-TypeC线,连接电脑和板上USB to UART口后,配置好串口调试助手,即可看到Power_detect打印数据了。

由聚沃科技原创,来源于【红枫派开发板】第十一讲 ADC-电源电压单通道ADC检测实验 - 苏州聚沃电子科技有限公司 (gd32bbs.com)

GD32MCU技术交流群:859440462   

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/693005.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python教程:Python操作MySQL基础使用

8、Python操作MySQL基础使用 8.1 安装pymysql pip install pymysql8.2 测试连接 测试代码 from pymysql import Connection# 获取到MySQL数据库的链接对象 conn Connection(# 主机名hostlocalhost,# 端口号,默认3306port3306,# 账户名userroot,# 密码password3535 )# 打印…

【JavaEE】Spring Boot 配置文件详解

一.配置文件的相关概念. 配置文件主要用于配置应用程序的行为和属性. Spring Boot的配置文件提供了一种灵活且强大的方式&#xff0c;用于管理应用程序的配置信息。很多项目或框架的配置信息也放在配置文件中: 项目的启动端口.数据库的连接信息(用户名/密码/驱动等的信息).第三…

Python 使用scrapy框架

1、安装scrapy 2、使用scrapy创建项目,在终端命令行 执行如下命令&#xff0c;会创建一个myproject项目 scrapy startproject myproject 3、创建完成后&#xff0c;目录结构如下 4、cd myproject进入项目 ,执行scrapy genspider weather ******&#xff0c;会在spiders下创建…

Linux驱动应用编程(四)IIC(获取BMP180温度/压力数据)

本文目录 一、基础1. 查看开发板手册&#xff0c;获取可用IIC总线2. 挂载从机&#xff0c;查看从机地址。3. 查看BMP180手册&#xff0c;使用命令读/写某寄存器值。4. 查看BMP180手册通信流程。 二、IIC常用API1. iic数据包/报2. ioctl函数 三、数据包如何被处理四、代码编写流…

《软件定义安全》之二:SDN/NFV环境中的安全问题

第2章 SDN/NFV环境中的安全问题 1.架构安全 SDN强调了控制平面的集中化&#xff0c;从架构上颠覆了原有的网络管理&#xff0c;所以SDN的架构安全就是首先要解决的问题。例如&#xff0c;SDN实现中网络控制器相关的安全问题。 1.1 SDN架构的安全综述 从网络安全的角度&…

Client does not support authentication protocol requested by server

连接mysql数据库报错 Client does not support authentication protocol requested by server; 打开命令行进入mysql mysql -uroot -p ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456;FLUSH PRIVILEGES;重新连接成功

P1072 [NOIP2009 提高组] Hankson 的趣味题

Hankson 的趣味题 这题要有思维&#xff01;对。数论&#xff01;最大公约数与最小公倍数。 用LaTex写公式&#xff0c;真的麻烦&#xff01;wcnmd!,,,,,,be---- 于是我用手写了&#xff1a; 大功告成&#xff01;上马&#xff01; #include<cstdio> using namespace …

Python | Leetcode Python题解之第134题加油站

题目&#xff1a; 题解&#xff1a; class Solution:def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:start, cur_res, total_res 0, 0, 0for i in range(len(gas)):cur_res gas[i] - cost[i]total_res gas[i] - cost[i]if cur_res < 0:cur_r…

MFA 轰炸:苹果用户的攻击目标

一些 Apple (苹果) 用户报告了利用密码重置功能进行的网络钓鱼攻击。 你注意到 iPhone 上的系统提示你输入密码。你点击“不允许”。然后这种情况一次又一次地发生。 在某个时候&#xff0c;你可能会感到恼火或开始恐慌&#xff0c;然后点击“允许”。 然后&#xff0c;你接…

【LeetCode:312. 戳气球+ 动态规划】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

创建google cloud storage notification 的权限问题

问题 根据google 的文档&#xff1a; https://cloud.google.com/storage/docs/reporting-changes#command-line 明确表示&#xff0c; 要创建storage notificaiton &#xff0c; 创建者(or service account) 只需要bucket 和 pubsub admin roles 但是实际上我在公司尝试为1个…

《令狐带你阅读JDK源码之简单集合ArrayList》

文章目录 Java简单集合ArrayList继承体系源码解析 总结 大家好哈&#xff0c;欢迎来到令狐小哥本期专栏&#xff0c;这期专栏主要是带着大家阅读JDK源码&#xff0c;我会分几期篇幅来介绍这个jdk源码、会进行剖析、梳理&#xff0c;欢迎大家指正阅读。后面我会配套自己的视频进…

C 语言实现Linux终端显示IP二维码

调试信息&#xff1a;开发者可以在终端生成二维码&#xff0c;包含调试信息或日志数据&#xff0c;便于移动设备扫描和查看。设备配置&#xff1a;物联网设备配置时&#xff0c;通过终端生成配置二维码&#xff0c;扫描后进行设备配置。 Ubuntu/Debian 环境安装二维码库 sudo a…

超详解——python数字和运算——小白篇

目录 1.位运算 2. 常用内置函数/模块 math模块&#xff1a; random模块&#xff1a; decimal模块&#xff1a; 3.内置函数&#xff1a; 总结&#xff1a; 1.位运算 位运算是对整数在内存中的二进制表示进行操作。Python支持以下常见的位运算符&#xff1a; 按位与&…

python字典应用

""" 字典应用 字典中保存了股票信息&#xff0c;完成下面的操作 1.找出股票价格大于100元的股票并创建一个新的字典 2、找出价格最高和最低的股票对应的股票代码 3.按照股票价格从高到低给股票代码排序 """stocks {AAPL: 191.88,G00G: 1186.96,…

记一次postgresql拼接函数string_agg() 和row_number() 使用

PG两个函数使用需求和简单介绍 需求背景介绍第一个需求背景是这样的需求升级一下接下来讲讲STRING_AGG()基本语法排序 然后我们再说说ROW_NUMBER()基本语法使用 row_number() over (partition by) 进行分组统计使用 row_num限定每组数量 需求背景介绍 第一个需求背景是这样的 …

【传知代码】BLIP - VLP任务的新框架(论文复现)

前言&#xff1a;在当今人工智能与机器学习领域&#xff0c;视觉-语言预训练&#xff08;Vision-and-Language Pre-training, VLP&#xff09;任务正逐渐崭露头角&#xff0c;其对于推动跨模态智能系统的进步起着至关重要的作用。在这些系统中&#xff0c;图像与文本不再是孤立…

Python | Leetcode Python题解之第137题只出现一次的数字II

题目&#xff1a; 题解&#xff1a; class Solution:def singleNumber(self, nums: List[int]) -> int:a b 0for num in nums:b ~a & (b ^ num)a ~b & (a ^ num)return b

【vue实战项目】通用管理系统:图表功能

目录 前言 1.概述 2.数据概览页 2.1.柱状图 2.2.折线图 2.3.地图 前言 本文是博主前端Vue实战系列中的一篇文章&#xff0c;本系列将会带大家一起从0开始一步步完整的做完一个小项目&#xff0c;让你找到Vue实战的技巧和感觉。 专栏地址&#xff1a; https://blog.csd…

harbor1.7.1的访问报错502 bad gateway

背景&#xff1a; 在访问harbor镜像仓库时提示报错如下&#xff1a; 问题分析&#xff1a; 根据提供的报错内容来看时harbor服务的nginx组件服务异常了的&#xff0c;导致无法访问harbor服务&#xff0c;查看harbor服务结果如下&#xff1a; serviceharbor:~/harbor$ docker…