目录
穿插一段我所做的项目中的代码,方便理解:
首先提一下这一段代码中DMA的部分:
adc_dma_mode_enable(ADC0);
这段代码是用来使能ADC的DMA(Direct Memory Access)模式。DMA模式允许ADC在转换完成后,将转换结果直接传输到内存中,而不需要CPU的干预。这对于高速数据传输和数据处理来说是很有用的,因为可以减轻CPU的负担,提高系统效率。
在这个特定的代码中,因为希望通过DMA方式将ADC的转换结果传输到内存,以便后续的数据处理或存储。DMA模式的使用通常涉及到DMA配置的详细设置,包括目标地址、数据长度等,这些设置在其他部分的代码中进行。
-
- static void ADC0_Config(void)
- {
- rcu_periph_clock_enable(RCU_ADC0);
-
-
- //syscfg_adc_ch_remap_config(ADC0_IN8_REMAP,DISABLE); //复用通道ADC0 8 使用PB2
-
- //syscfg_adc_ch_remap_config(ADC0_IN9_REMAP,ENABLE);
-
- rcu_adc_clock_config(RCU_CKADC_CKAHB_DIV10);
- /* config the GPIO as analog mode */
-
- gpio_mode_set(GPIOC, GPIO_MODE_ANALOG,GPIO_PUPD_NONE,GPIO_PIN_10 | GPIO_PIN_11);
- gpio_mode_set(GPIOE, GPIO_MODE_ANALOG,GPIO_PUPD_NONE,GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11| GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14);
-
- //独立模式
- adc_mode_config(ADC_MODE_FREE);
-
- //右对齐
- adc_data_alignment_config(ADC0,ADC_DATAALIGN_RIGHT);
-
- //开启连续模式和扫描模式
- adc_special_function_config(ADC0,ADC_CONTINUOUS_MODE,ENABLE);
- adc_special_function_config(ADC0,ADC_SCAN_MODE,ENABLE);
-
- //规则通道个数
- adc_channel_length_config(ADC0,ADC_REGULAR_CHANNEL,ADC_CHNL_NUM);
-
- //转换顺序
- adc_regular_channel_config(ADC0,0,ADC_CHANNEL_12,ADC_SAMPLETIME_479POINT5); //OUT1 -- AD3
- adc_regular_channel_config(ADC0,1,ADC_CHANNEL_7,ADC_SAMPLETIME_479POINT5); //OUT2 -- AD4
- adc_regular_channel_config(ADC0,2,ADC_CHANNEL_6,ADC_SAMPLETIME_479POINT5); //OUT3 -- AD5
- adc_regular_channel_config(ADC0,3,ADC_CHANNEL_13,ADC_SAMPLETIME_479POINT5); //OUT4 -- AD6
- adc_regular_channel_config(ADC0,4,ADC_CHANNEL_14,ADC_SAMPLETIME_479POINT5); //OUT5 -- AD7
- adc_regular_channel_config(ADC0,5,ADC_CHANNEL_15,ADC_SAMPLETIME_479POINT5); //OUT6 -- AD8
-
- adc_regular_channel_config(ADC0,6,ADC_CHANNEL_9,ADC_SAMPLETIME_479POINT5); //OUT7 -- AD9
-
- adc_regular_channel_config(ADC0,7,ADC_CHANNEL_1,ADC_SAMPLETIME_479POINT5); //风压 -- AD1
- adc_regular_channel_config(ADC0,8,ADC_CHANNEL_0,ADC_SAMPLETIME_479POINT5); //电源电压 -- AD2
-
-
- //软件触发方式
- adc_external_trigger_source_config(ADC0,ADC_REGULAR_CHANNEL,ADC0_1_EXTTRIG_REGULAR_NONE);
- adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);
-
- //使能DMA模式
- adc_dma_mode_enable(ADC0);
-
- adc_enable(ADC0);
- delay_xms(1);
-
- //ADC 复位校准
- adc_calibration_enable(ADC0);
-
- //使能软件触发ADC 转换
- adc_software_trigger_enable(ADC0,ADC_REGULAR_CHANNEL);
-
-
- }
1. 12位ADC是一种逐次逼近型模拟数字转换器。它有多达18个通道,可测量16个外部和2个内部信号源。
rcu_periph_clock_enable(RCU_ADC0);开启时钟
2. ADC的输入时钟不得超过14MHz,它是由PCLK2经分频产生的。
所以在初始化函数中往往先进行 rcu_adc_clock_config(RCU_CKADC_CKAHB_DIV10);
这句话的意思就是进行10分频(这个是在GD32A503x中,不同的芯片有所不同)。
#define RCU_CKADC_CKAHB_DIV10 CFG2_ADCPSC(8)
/*!< ADC prescaler select CK_AHB/10 */在GD32中时钟由AHB分配。
所以底层函数中:
rcu_adc_clock_config(RCU_CKADC_CKAHB_DIV10);为10MHz
- void RCC_ADCCLKConfig(uint32_t RCC_PCLK2)
- /*RCC_PCLK2: defines the ADC clock divider. This clock is derived from the APB2 clock (PCLK2).*/
- //eg:
- RCC_ADCCLKConfig(RCC_PCLK2_Div6);//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
但是需要注意的是,如下图,ADCCLK最大14MHz。
初始化IO口:(可封装在函数中)
- gpio_mode_set(GPIOC, GPIO_MODE_ANALOG,GPIO_PUPD_NONE,GPIO_PIN_10 | GPIO_PIN_11);
- gpio_mode_set(GPIOE, GPIO_MODE_ANALOG,GPIO_PUPD_NONE,GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11| GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14);
- gpio_mode_set(GPIOB, GPIO_MODE_ANALOG,GPIO_PUPD_NONE,GPIO_PIN_1);
//模拟输入模式GPIO_MODE_ANALOG
这部分代码配置了三组GPIO口(分别是GPIOC和GPIOE和GPIOB),将它们设置为模拟输入模式,同时禁用了上拉和下拉电阻。这是因为ADC通道需要连接到模拟信号,而不需要上拉或下拉电阻对这些信号产生影响。
1. ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
2. 12位逐次逼近型ADC,1us转换时间
3. 输入电压范围:0~3.3V,转换结果范围:0~4095
4. 18个输入通道,可测量16个外部和2个内部信号源
5. 规则组和注入组两个转换单元
6. 模拟看门狗自动监测输入电压范围
GD32的图和STM32:
单次转换模式下,ADC只执行一次转换。该模式既可通过设置ADC_CR2寄存器的ADON位(只适用于规则通道)启动也可通过外部触发启动(适用于规则通道或注入通道),这时CONT位为0。 一旦选择通道的转换完成。
在连续转换模式中,当前面ADC转换一结束马上就启动另一次转换。此模式可通过外部触发启动或通过设置ADC_CR2寄存器上的ADON位启动,此时CONT位是1。
总的来说就是:单次模式、连续模式:ADC单通道要求进行一次ADC转换时配置为单次模式使能,这样ADC的这个通道转换一次后就停止转换。要求进行连续ADC转换时配置为连续模式使能,这样ADC的这个通道转换一次后接着进行下一次。 扫描模式:对应多个ADC通道的情况,每个通道依次进行转换。
通道 | ADC1 | ADC2 | ADC3 |
通道0 | PA0 | PA0 | PA0 |
通道1 | PA1 | PA1 | PA1 |
通道2 | PA2 | PA2 | PA2 |
通道3 | PA3 | PA3 | PA3 |
通道4 | PA4 | PA4 | PF6 |
通道5 | PA5 | PA5 | PF7 |
通道6 | PA6 | PA6 | PF8 |
通道7 | PA7 | PA7 | PF9 |
通道8 | PB0 | PB0 | PF10 |
通道9 | PB1 | PB1 | |
通道10 | PC0 | PC0 | PC0 |
通道11 | PC1 | PC1 | PC1 |
通道12 | PC2 | PC2 | PC2 |
通道13 | PC3 | PC3 | PC3 |
通道14 | PC4 | PC4 | |
通道15 | PC5 | PC5 | |
通道16 | 温度传感器 | ||
通道17 | 内部参考电压 |
规则组最多转换16通道,注入组最多转换4通道,但是规则通道为“一次可以点16个菜,但是一次只能上一个菜”,因为规则通道的数据寄存器仅仅为16位,而注入通道为“一次可以点4个菜,但一次可以最多上4个菜”。
ADC初始化配置:
1. 配置模式://独立模式adc_mode_config(ADC_MODE_FREE);
2. 对齐方式
//右对齐
adc_data_alignment_config(ADC0,ADC_DATAALIGN_RIGHT);
数据对齐的方式:左对齐、右对齐等方式。对于右对齐方式,ADC转换结果的低位存储在寄存器的低地址处,而高位则存储在寄存器的高地址处。
- ADC数据对齐方式指定了ADC转换结果在寄存器中的存储方式。对于右对齐方式,ADC转换结果的低位存储在寄存器的低地址处,而高位则存储在寄存器的高地址处。这种对齐方式是一种常见的选择,便于处理和理解。
-
- 对齐方式的选择可能与使用的数据类型和后续的数据处理有关。一般而言,如果你希望ADC转换结果的低位直接对应于采样的最低位,方便直接使用整数数据类型处理,那么右对齐方式是一个合适的选择。
-
- 对于上述代码片段中的 adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);,它是为 ADC0 配置右对齐方式。这样设置可以确保 ADC 转换结果按照右对齐方式存储,使得数据处理更为方便。
3. 开启连续模式和扫描模式
adc_special_function_config(ADC0,ADC_CONTINUOUS_MODE,ENABLE);
adc_special_function_config(ADC0,ADC_SCAN_MODE,ENABLE);
- 连续模式(Continuous Mode):
-
- 在连续模式下,ADC会不停地进行模拟信号的转换,而不需要外部触发。这种模式适用于需要实时、连续采样的应用,比如实时监测、数据采集等。
- 连续模式允许ADC持续地对设置的正常通道序列进行转换,无需每次都手动触发。这对于周期性的数据采集或需要持续监测的应用非常有用。
-
- 扫描模式(Scan Mode):
-
- 在扫描模式下,ADC会按照预先配置的正常通道顺序依次进行转换。每次转换完成后,ADC会自动切换到下一个正常通道,直到所有通道都被转换完成。
- 扫描模式适用于多通道的采样需求。通过在代码中配置正常通道序列,可以按照特定的顺序对多个信号进行采样,而不需要手动切换通道。
4. 规则通道个数
adc_channel_length_config(ADC0,ADC_REGULAR_CHANNEL,ADC_CHNL_NUM);
- adc_channel_length_config 函数用于设置 ADC 的正常通道组中的通道个数,其中:
-
- ADC0 是指定的 ADC 外设。
- ADC_REGULAR_CHANNEL 表示正常通道组。
- ADC_CHNL_NUM 是要配置的通道个数。
- 在嵌入式系统中,ADC 通道组是按照一定的规律或顺序进行采样的。通常,正常通道组用于按照预定顺序对一组通道进行采样。通过设置通道个数,你可以指定在一次转换中,ADC 将采样多少个通道。在这里,ADC_CHNL_NUM 的值决定了正常通道组中的通道个数。
但是规则组最多转换16通道,注入组最多转换4通道,但是规则通道为“一次可以点16个菜,但是一次只能上一个菜”,因为规则通道的数据寄存器仅仅为16位,而注入通道为“一次可以点4个菜,但一次可以最多上4个菜”。
所以紧接着需要进行转换顺序的配置:
- adc_regular_channel_config(ADC0,0,ADC_CHANNEL_12,ADC_SAMPLETIME_479POINT5); //OUT1 -- AD3
- adc_regular_channel_config(ADC0,1,ADC_CHANNEL_7,ADC_SAMPLETIME_479POINT5); //OUT2 -- AD4
- adc_regular_channel_config(ADC0,2,ADC_CHANNEL_6,ADC_SAMPLETIME_479POINT5); //OUT3 -- AD5
- adc_regular_channel_config(ADC0,3,ADC_CHANNEL_13,ADC_SAMPLETIME_479POINT5); //OUT4 -- AD6
- adc_regular_channel_config(ADC0,4,ADC_CHANNEL_14,ADC_SAMPLETIME_479POINT5); //OUT5 -- AD7
- adc_regular_channel_config(ADC0,5,ADC_CHANNEL_15,ADC_SAMPLETIME_479POINT5); //OUT6 -- AD8
- adc_regular_channel_config(ADC0,6,ADC_CHANNEL_9,ADC_SAMPLETIME_479POINT5); //OUT7 -- AD9
- adc_regular_channel_config(ADC0,7,ADC_CHANNEL_1,ADC_SAMPLETIME_479POINT5); //风压 -- AD1
- adc_regular_channel_config(ADC0,8,ADC_CHANNEL_0,ADC_SAMPLETIME_479POINT5); //电源电压 -- AD2
当你启动 ADC 转换时,它会按照这个顺序对这些通道进行采样,得到相应的模拟信号的数字化结果。
为什么需要进行这一系列操作了,是因为:
总的来说,模拟量是连续的、实数值形式的信号,而数字量是对模拟信号进行采样和量化后的离散数值信号。在监测落肥的应用中,传感器可能输出模拟信号,而ADC负责将这些模拟信号转换为微控制器或处理器能够理解的数字信号,以便进行后续的处理和分析。
-
- 在监测落肥的场景中,涉及到的是模拟量和数字量的概念。下面对这两者进行简单的解释:
-
- 模拟量(Analog Signal):
-
- 模拟量是指在一定时间内,信号的数值可以连续取任意值的信号。在农业领域,例如监测落肥量,模拟量可以是液体流量、气体压力等。这些信号的数值可以是任何在一定范围内的实数值,而不是离散的数值。
- 在这个场景中,模拟量可能对应于精确的落肥液流量或其他液体特性,这些物理量通过传感器测量并以模拟形式呈现。
- 数字量(Digital Signal):
-
- 数字量是指信号的数值以离散的形式表示。通常,模拟信号需要通过模数转换(ADC)转换为数字信号,以便在数字系统中进行处理和存储。
- 在监测落肥量的场景中,通过ADC转换后,得到的数字量可能是一个表示液体流量、压力等的数字值。数字化后的信号可以在数字系统中进行处理、存储、传输和显示。
ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。在校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。特别需要注意:建议在每次上电后执行一次校准。简单说,这就是个固定流程,stm32要求这么处理:
// ADC 复位校准
adc_calibration_enable(ADC0);
这行代码通过调用 adc_calibration_enable
函数启用了 ADC 的复位校准。ADC 校准是在初始化过程中的一项重要步骤,它有助于提高 ADC 转换的准确性。校准通常在 ADC 启动之后进行,确保 ADC 能够对模拟输入进行准确的数字化转换。
需要注意的是,这个校准步骤只执行一次,通常在初始化时进行。在代码中,你可以看到在启用 ADC 之后等待了一段时间:这是为了等待 ADC 稳定,确保校准在 ADC 开启后充分生效。
- adc_enable(ADC0);
- delay_xms(1);
- adc_calibration_enable(ADC0);
示例代码如下:
- ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
- while (ADC_GetResetCalibrationStatus(ADC1) == SET);
- ADC_StartCalibration(ADC1);
- while (ADC_GetCalibrationStatus(ADC1) == SET);
左对齐与右对齐
采样、保持、量化、编码
ADC总转换时间:TCONV = 采样时间+ 12.5个周期
例如: 当ADCCLK=14MHz,采样时间为1.5周期 TCONV = 1.5 + 12.5 = 14周期 = 1μs
为什么需要保持?
因为AD转换中量化、编码需要一定的转换时间,在这个时间内输入电压如果还在不断变化,那么就无法定位电压,所以往往在量化编码前引入采样开关,比如通过加入一个小的电容来存储电压。
最后就是使能ADC,这样就初始化完成了:
//使能软件触发ADC 转换 adc_software_trigger_enable(ADC0,ADC_REGULAR_CHANNEL);
这样,ADC 就可以按照预定的通道顺序执行正常通道的连续扫描转换
1.使能外设时钟:开启ADC1的时钟、开启GPIO的时钟
2.设置ADC的时钟,选择时钟分频
- /*开启时钟*/
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
-
- /*设置ADC时钟*/
- RCC_ADCCLKConfig(RCC_PCLK2_Div6);
3.规则组通道配置
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
4.GPIO初始化、ADC初始化配置:
- GPIO_InitTypeDef GPIO_InitStructure;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- /*ADC初始化*/
- ADC_InitTypeDef ADC_InitStructure; //定义结构体变量
- ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1
- ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐,选择右对齐
- ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发
- ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //连续转换,失能,每转换一次规则组序列后停止
- ADC_InitStructure.ADC_ScanConvMode = DISABLE; //扫描模式,失能,只转换规则组的序列1这一个位置
- ADC_InitStructure.ADC_NbrOfChannel = 1; //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
- ADC_Init(ADC1, &ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1
5.ADC使能
6.ADC校准
- /*ADC使能*/
- ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行
-
- /*ADC校准*/
- //先将ADC1校准功能恢复到默认值,然后再初始化ADC1。
- ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
- while (ADC_GetResetCalibrationStatus(ADC1) == SET);
- ADC_StartCalibration(ADC1);
- while (ADC_GetCalibrationStatus(ADC1) == SET);
AD多通道转换只需修改通道配置即可:
EOC:转换结束位,该位由硬件在规则或者注入通道组转换结束时置1,由软件清除或者读取数据寄存器后自动清除,那么等待EOC标志位置1即等待AD转换完成。
- /**
- * 函 数:获取AD转换的值
- * 参 数:ADC_Channel 指定AD转换的通道,范围:ADC_Channel_x,其中x可以是0/1/2/3
- * 返 回 值:AD转换的值,范围:0~4095
- */
- uint16_t AD_GetValue(uint8_t ADC_Channel)
- {
- ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5); //在每次转换前,根据函数形参灵活更改规则组的通道1
- ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发AD转换一次
- while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位,即等待AD转换结束
- return ADC_GetConversionValue(ADC1); //读数据寄存器,得到AD转换的结果
- }