赞
踩
ADC(模数转换器) 是一种电子设备,用于将连续的模拟信号转换为相应的离散数字数据。这在许多应用中非常重要,特别是在嵌入式系统和数据采集系统中。
具体来说,ADC将来自外部环境的模拟信号(例如来自传感器的温度、压力、光强等)转换为数字形式,以便微处理器或其他数字电路可以处理和分析这些数据。ADC的输出通常是二进制数字,代表了模拟信号的离散值。
在嵌入式系统中,ADC常常用于采集传感器数据,比如监控温度、湿度、光照等环境参数。这些数据随后可以被处理器用来做出决策、控制系统或者在显示器上显示。
总的来说,ADC是将模拟世界的数据转换为数字世界的重要桥梁,使得我们能够对现实世界的各种信号进行量化和处理。
常见ADC类型:https://blog.csdn.net/whereismatrix/article/details/81814431
常见的ADC类型包括并行比较型和逐次逼近型。它们各有优点和缺点:
并行比较型 ADC:
逐次逼近型 ADC:
在选择 ADC 类型时,需要根据应用的具体要求权衡各种因素,包括转换速度、成本、功耗和分辨率等。
逐次逼近型 ADC (Successive Approximation ADC)通常由以下几个主要部分组成:
控制电路:控制电路负责管理 ADC 的整个转换过程。它通常包括时钟控制、状态机、控制逻辑等,用于确保 ADC 能够按照预定的顺序和时序完成模拟信号到数字信号的转换。
数码寄存器:数码寄存器是 ADC 中用于存储转换结果的部分。它的位数决定了 ADC 的分辨率。在逐次逼近型 ADC 中,数码寄存器通常是一个 N 位的寄存器,其中 N 表示 ADC 的分辨率。
D/A 转换器:D/A 转换器(Digital-to-Analog Converter)用于产生 ADC 内部的参考电压或比较电压。在逐次逼近型 ADC 中,D/A 转换器通常用于生成逼近比较电压,以便与输入模拟信号进行比较。
电压比较器:电压比较器用于比较输入模拟信号和由 D/A 转换器产生的比较电压。比较器的输出结果将用于 ADC 控制电路中的决策逻辑,驱动 ADC 进行逐步逼近型转换过程。
逐次逼近型 ADC 通过多次逼近比较的过程来逐渐逼近输入模拟信号的真实值,并将结果存储在数码寄存器中。这种类型的 ADC 结构相对简单,适用于许多低速应用场景。
ADC 特性参数包括:
分辨率:ADC 的分辨率表示其能够辨别的最小模拟量的能力。通常以位数来表示,例如 8 位、10 位、12 位、16 位等。更高的分辨率意味着 ADC 能够更精细地将模拟信号转换为数字信号。
转换时间:转换时间是完成一次 A/D 转换所需的时间。它决定了 ADC 的采样率,即每秒钟能够完成多少次转换。转换时间越短,ADC 的采样率就越高,能够更快地对模拟信号进行采样和转换。
精度:ADC 的精度是指其输出数字量与输入模拟量之间的误差。它受到多种因素的影响,包括 ADC 自身的性能、环境条件(如温度、气压等)以及电路设计的质量等。精度通常以百分比或者 LSB(最小刻度基本单位)来表示。
量化误差:量化误差是指将模拟信号转换为数字信号时,由于量化过程的精度限制而引入的误差。这个误差是由数字量化的离散性质所导致的,通常使用四舍五入等近似方法,因此会产生一定的误差。量化误差的大小取决于 ADC 的分辨率,分辨率越高,量化误差通常越小。
根据您提供的信息,以下是 STM32 各系列 ADC 的主要特性:
F1 系列:
F4 系列:
F7 系列:
H7 系列:
以上是基于您提供的信息对各系列 STM32 的 ADC 特性的概述。请注意,具体的特性可能因芯片型号和具体配置而略有差异,建议查阅相关的数据手册以获取更详细的信息。
ADC(模拟-数字转换器)的框图结构通常包括以下部分:
参考电压/模拟部分电压:
输入通道:
转换序列:
触发源:
转换时间:
数据寄存器:
中断:
这些部分共同构成了 ADC 的框图结构,使其能够将模拟信号转换为数字信号,并与微处理器进行通信。
在STM32H7系列中,ADC的框图结构通常包括以下部分:
VREF+电压:
ADC双时钟域架构:
输入通道:
转换序列:
触发源:
转换时间:
参考电压:
ADC核心:
数据寄存器:
中断:
这些部分共同构成了STM32H7系列ADC的框图结构,使其能够有效地将模拟信号转换为数字信号,并与微处理器进行通信。
在STM32中,ADC的参考电压(VREF+)和模拟部分电压有以下特点和限制:
ADC供电电源:
ADC输入电压范围:
因此,当使用STM32的ADC时,需要确保模拟输入信号的电压在VREF-和VREF+之间,并且ADC的供电电源在正常工作范围内。
在STM32中,ADC的转换序列被组织为规则组和注入组,具体特点如下:
规则转换组:
注入转换组:
通过规则组和注入组的灵活组合,STM32的ADC可以满足各种不同的转换需求,从而更好地适应不同的应用场景。
规则组和注入组的执行优先级如下所示:
规则组执行优先级:
注入组执行优先级:
总的来说,注入组的转换具有较高的优先级,可以在规则组转换之前或之后执行,并且可以在一些特殊情况下优先执行。规则组的转换相对较为常规,优先级较低,会等待注入组转换完成后才执行。
规则序列和注入序列是STM32中ADC转换序列的两种类型,它们的设置可以通过相关的寄存器进行配置。
规则序列:
注入序列:
配置规则序列和注入序列的步骤如下:
具体的配置步骤和寄存器设置会根据具体的STM32系列和型号而有所不同,需要查阅相应系列的参考手册或技术文档以获取详细信息。
对于STM32系列中的ADC模块,确实存在这两种触发转换的方法。
ADON位触发转换(仅限于某些旧的STM32系列,如F1系列):
外部事件触发转换:
对于外部事件触发转换,需要配置相应的触发源,并在启动ADC时等待外部事件的发生以触发转换。这种方法通常更加灵活,并且适用于各种应用场景。
规则组外部触发和注入组外部触发的使用方法如下:
规则组外部触发使用方法:
配置触发源: 首先,需要选择外部事件作为触发源,例如外部GPIO引脚的信号变化、定时器的触发等。这通常需要配置相关的GPIO或定时器,并将其与ADC模块的触发输入信号相连。
配置ADC触发模式: 在初始化ADC时,需要设置触发模式为外部触发。这可以通过配置 ADC_CR2 寄存器中的 EXTEN 和 EXTSEL 位来实现。EXTEN 用于配置触发边沿,EXTSEL 用于选择触发源。
启动ADC转换: 当外部事件发生时,会触发ADC转换。在启动ADC时,ADC会等待外部触发事件,并在触发后执行规则组中的转换。
注入组外部触发使用方法:
配置触发源: 类似于规则组外部触发,首先需要选择外部事件作为触发源,并将其与注入组的触发输入信号相连。
配置注入组触发模式: 在初始化ADC时,需要设置注入组的触发模式为外部触发。这可以通过配置 ADC_CR2 寄存器中的 JEXTEN 和 JEXTSEL 位来实现。JEXTEN 用于配置注入组触发边沿,JEXTSEL 用于选择触发源。
启动ADC转换: 当外部事件触发时,注入组会执行相应的转换。在启动ADC时,注入组会等待外部触发事件,并在触发后执行注入组中的转换。
通过以上步骤,可以实现规则组和注入组的外部触发转换,从而灵活地应对各种采样需求。
设置ADC时钟和转换时间的方法如下:
设置ADC时钟:
设置ADC转换时间:
通过以上步骤,可以根据需求灵活地设置ADC的时钟和转换时间,以满足具体的应用需求。
数据寄存器用于存储ADC转换的结果,其中规则数据寄存器 ADCx_DR
存储规则组转换的结果,而注入数据寄存器 ADCx_JDRy
存储注入组转换的结果,其中 y
可以是1到4,对应着注入组中不同的转换通道。
在读取数据之前,需要根据应用的要求设置数据的对齐方式。这可以通过 ADCx_CR2
寄存器的 ALIGN
位来实现。具体地:
根据具体需求选择合适的对齐模式,然后读取数据寄存器中的转换结果即可。
在STM32的F1/F4/F7系列中,ADC模块支持多种中断事件,以及对应的事件标志和使能控制位。这些中断事件包括:
规则通道转换结束(End of Conversion, EOC):表示规则组中的一个转换通道完成了转换。
注入通道转换结束(Injected End of Conversion, JEOC):表示注入组中的一个转换通道完成了转换。
模拟看门狗状态位(Analog Watchdog, AWD):表示转换结果超出预设阈值,可以用于检测输入信号的异常状态。
溢出(Overflow, OVR):表示转换结果溢出,即转换结果超出了数据寄存器的容量。在F1系列中没有此中断。
此外,对于规则组转换结束后,还可以产生DMA请求,可以使用DMA及时传输转换好的数据到指定的内存地址,防止数据被覆盖。
单次转换模式和连续转换模式是ADC的两种工作模式,具体特点如下:
单次转换模式:
连续转换模式:
总之,单次转换模式适用于只需要进行一次转换的场景,而连续转换模式适用于需要连续监测的场景。
扫描模式是指ADC进行转换时是否扫描多个通道的模式。具体特点如下:
关闭扫描模式:
使用扫描模式:
总之,扫描模式可以让ADC在一次转换中依次转换多个通道的数据,适用于需要监测多个信号的场景。
根据配置描述,通过ADC1通道1(PA1)采集电位器的电压,并显示ADC转换的数字量及换算后的电压值。以下是实现该功能的步骤:
确定最小刻度: 对于STM32系列,如果ADC的参考电压(VREF+)为3.3V,且分辨率为12位,则最小刻度为 ( \frac{3.3V}{2^{12}} ) ,即 ( \frac{3.3V}{4096} ) 。这是ADC可以分辨的最小电压变化。
确定转换时间: 根据您提供的采样时间239.5个ADC时钟周期,转换时间为21μs。
模式组合: 您选择了单次转换模式,并且不使用扫描模式。
编程实现:
下面是一个简单的示例代码,用于实现这些步骤:
#include "stm32f4xx_hal.h"
#include <stdio.h>
ADC_HandleTypeDef hadc;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
uint32_t adc_value;
float voltage;
HAL_ADC_Start(&hadc);
HAL_ADC_PollForConversion(&hadc, HAL_MAX_DELAY);
adc_value = HAL_ADC_GetValue(&hadc);
voltage = adc_value * 3.3 / 4096;
printf("ADC Value: %lu\n", adc_value);
printf("Voltage: %.2fV\n", voltage);
while (1) {
}
}
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
}
static void MX_ADC1_Init(void) {
ADC_ChannelConfTypeDef sConfig = {0};
hadc.Instance = ADC1;
hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc.Init.Resolution = ADC_RESOLUTION_12B;
hadc.Init.ScanConvMode = DISABLE;
hadc.Init.ContinuousConvMode = DISABLE;
hadc.Init.DiscontinuousConvMode = DISABLE;
hadc.Init.NbrOfDiscConversion = 0;
hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc.Init.NbrOfConversion = 1;
hadc.Init.DMAContinuousRequests = DISABLE;
hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
HAL_ADC_Init(&hadc);
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
HAL_ADC_ConfigChannel(&hadc, &sConfig);
}
static void MX_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
请根据您的实际硬件配置和需求进行适当的修改。
单通道ADC采集实验配置步骤,以下是对每个步骤的简要说明:
配置ADC工作参数、ADC校准:
ADC MSP初始化:
配置ADC相应通道相关参数:
启动A/D转换:
等待规则通道转换完成:
获取规则通道A/D转换结果:
通过以上步骤,您可以完成单通道ADC采集的实验配置。确保根据您的具体硬件和应用需求进行适当的配置和修改。
adc.c
#include "./BSP/ADC/adc.h"
ADC_HandleTypeDef g_adc_handle;
/* ADC单通道初始化函数 */
void adc_init(void)
{
ADC_ChannelConfTypeDef adc_ch_conf;
// 配置 ADC 句柄参数
g_adc_handle.Instance = ADC1;
g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
g_adc_handle.Init.ContinuousConvMode = DISABLE;
g_adc_handle.Init.NbrOfConversion = 1;
g_adc_handle.Init.DiscontinuousConvMode = DISABLE;
g_adc_handle.Init.NbrOfDiscConversion = 0;
g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
// 初始化 ADC
HAL_ADC_Init(&g_adc_handle);
// 进行 ADC 校准
HAL_ADCEx_Calibration_Start(&g_adc_handle);
// 配置 ADC 通道
adc_ch_conf.Channel = ADC_CHANNEL_1;
adc_ch_conf.Rank = ADC_REGULAR_RANK_1;
adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
HAL_ADC_ConfigChannel(&g_adc_handle, &adc_ch_conf);
}
/* ADC MSP初始化函数 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
if(hadc->Instance == ADC1)
{
GPIO_InitTypeDef gpio_init_struct;
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
// 使能GPIOA和ADC1时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_ADC1_CLK_ENABLE();
// 配置GPIOA引脚为模拟模式
gpio_init_struct.Pin = GPIO_PIN_1;
gpio_init_struct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
// 配置 ADC 时钟
adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
}
}
/* 获取ADC转换后的结果函数 */
uint32_t adc_get_result(void)
{
// 启动 ADC 转换
HAL_ADC_Start(&g_adc_handle);
// 等待转换完成
HAL_ADC_PollForConversion(&g_adc_handle, 10);
// 获取转换结果
return (uint16_t)HAL_ADC_GetValue(&g_adc_handle);
}
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "./SYSTEM/sys/sys.h"
void adc_init(void); /* ADC单通道初始化函数 */
uint32_t adc_get_result(void); /* 获取ADC转换后的结果函数 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/ADC/adc.h"
int main(void)
{
uint16_t adcx;
float temp;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
adc_init(); /* 初始化ADC */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "ADC TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH1_VAL:", BLUE);
lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
while (1)
{
adcx = adc_get_result();
lcd_show_xnum(134, 110, adcx, 5, 16, 0, BLUE); /* 显示ADCC采样后的原始值 */
temp = (float)adcx * (3.3 / 4096); /* 获取计算后的带小数的实际电压值,比如3.1111 */
adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
lcd_show_xnum(134, 130, adcx, 1, 16, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
lcd_show_xnum(150, 130, temp, 3, 16, 0X80, BLUE);/* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
LED0_TOGGLE();
delay_ms(100);
}
}
#include "./BSP/ADC/adc.h"
ADC_HandleTypeDef g_adc_handle;
DMA_HandleTypeDef g_dma_handle;
/* ADC单通道初始化函数 */
void adc_init(void)
{
ADC_ChannelConfTypeDef adc_ch_conf;
// 配置 ADC 句柄参数
g_adc_handle.Instance = ADC1;
g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
g_adc_handle.Init.ContinuousConvMode = DISABLE;
g_adc_handle.Init.NbrOfConversion = 1;
g_adc_handle.Init.DiscontinuousConvMode = DISABLE;
g_adc_handle.Init.NbrOfDiscConversion = 0;
g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
// 初始化 ADC
HAL_ADC_Init(&g_adc_handle);
// 进行 ADC 校准
HAL_ADCEx_Calibration_Start(&g_adc_handle);
// 配置 ADC 通道
adc_ch_conf.Channel = ADC_CHANNEL_1;
adc_ch_conf.Rank = ADC_REGULAR_RANK_1;
adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
HAL_ADC_ConfigChannel(&g_adc_handle, &adc_ch_conf);
}
/* ADC MSP初始化函数 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
if(hadc->Instance == ADC1)
{
GPIO_InitTypeDef gpio_init_struct;
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
// 使能GPIOA和ADC1时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_ADC1_CLK_ENABLE();
// 配置GPIOA引脚为模拟模式
gpio_init_struct.Pin = GPIO_PIN_1;
gpio_init_struct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
// 配置 ADC 时钟
adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
}
}
/* DMA初始化函数 */
void dma_init(void)
{
// 初始化 DMA 句柄
g_dma_handle.Instance = DMA1_Channel1;
g_dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;
g_dma_handle.Init.MemInc = DMA_MINC_ENABLE;
g_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
g_dma_handle.Init.Mode = DMA_CIRCULAR;
g_dma_handle.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&g_dma_handle);
}
/* 将 DMA 和 ADC 句柄联系起来 */
void adc_dma_link(void)
{
// 将 DMA 与 ADC 关联
__HAL_LINKDMA(&g_adc_handle, DMA_Handle, g_dma_handle);
}
/* 使能 DMA 数据流传输完成中断 */
void dma_interrupt_enable(void)
{
// 配置 DMA 数据流传输完成中断
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}
/* DMA 数据流中断服务函数 */
void DMA1_Channel1_IRQHandler(void)
{
// 处理 DMA 数据流中断事件
HAL_DMA_IRQHandler(&g_dma_handle);
}
/* 启动 DMA,开启传输完成中断 */
void dma_start(void)
{
// 启动 DMA,开启传输完成中断
HAL_DMA_Start_IT(&g_dma_handle, (uint32_t)&ADC1->DR, (uint32_t)&adc_value, 1);
}
/* 触发 ADC 转换,DMA 传输数据 */
void adc_start_dma(void)
{
// 启动 ADC 转换,DMA 传输数据
HAL_ADC_Start_DMA(&g_adc_handle, (uint32_t*)&adc_value, 1);
}
adc.c
#include "./BSP/ADC/adc.h"
DMA_HandleTypeDef g_dma_adc_handle;
ADC_HandleTypeDef g_adc_dma_handle;
uint8_t g_adc_dma_sta;
/* ADC DMA读取 初始化函数 */
void adc_dma_init(uint32_t mar)
{
ADC_ChannelConfTypeDef adc_ch_conf;
// 使能 DMA1 时钟
__HAL_RCC_DMA1_CLK_ENABLE();
// 配置 DMA 句柄参数
g_dma_adc_handle.Instance = DMA1_Channel1;
g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;
g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE;
g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
g_dma_adc_handle.Init.Mode = DMA_NORMAL;
g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;
HAL_DMA_Init(&g_dma_adc_handle);
// 将 DMA 与 ADC 关联
__HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle);
// 配置 ADC 句柄参数
g_adc_dma_handle.Instance = ADC1;
g_adc_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
g_adc_dma_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
g_adc_dma_handle.Init.ContinuousConvMode = ENABLE;
g_adc_dma_handle.Init.NbrOfConversion = 1;
g_adc_dma_handle.Init.DiscontinuousConvMode = DISABLE;
g_adc_dma_handle.Init.NbrOfDiscConversion = 0;
g_adc_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
HAL_ADC_Init(&g_adc_dma_handle);
// 进行 ADC 校准
HAL_ADCEx_Calibration_Start(&g_adc_dma_handle);
// 配置 ADC 通道
adc_ch_conf.Channel = ADC_CHANNEL_1;
adc_ch_conf.Rank = ADC_REGULAR_RANK_1;
adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf);
// 配置 DMA1_Channel1 中断优先级
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);
// 使能 DMA1_Channel1 中断
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
// 启动 DMA,开启传输完成中断
HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);
// 启动 ADC 转换,DMA 传输数据
HAL_ADC_Start_DMA(&g_adc_dma_handle, &mar ,0);
}
/* ADC MSP初始化函数 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
if(hadc->Instance == ADC1)
{
GPIO_InitTypeDef gpio_init_struct;
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
// 使能 GPIOA 和 ADC1 时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_ADC1_CLK_ENABLE();
// 配置 GPIOA 引脚为模拟模式
gpio_init_struct.Pin = GPIO_PIN_1;
gpio_init_struct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
// 配置 ADC 时钟
adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
}
}
/* 使能一次ADC DMA传输函数 */
void adc_dma_enable(uint16_t cndtr)
{
// 关闭 ADC1 和 DMA1_Channel1
ADC1->CR2 &= ~(1 << 0);
DMA1_Channel1->CCR &= ~(1 << 0);
while (DMA1_Channel1->CCR & (1 << 0));
// 设置 DMA1_Channel1 传输数据长度
DMA1_Channel1->CNDTR = cndtr;
// 启动 DMA1_Channel1 和 ADC1
DMA1_Channel1->CCR |= 1 << 0;
ADC1->CR2 |= (1 << 0) | (1 << 22);
// 禁用 ADC1
// __HAL_ADC_DISABLE(&g_adc_dma_handle);
// 禁用 DMA1_Channel1,并等待传输完成
// __HAL_DMA_DISABLE(&g_dma_adc_handle);
// while (__HAL_DMA_GET_FLAG(&g_dma_adc_handle, __HAL_DMA_GET_TC_FLAG_INDEX(&g_dma_adc_handle)));
// 重新设置 DMA1_Channel1 的传输数据长度
// DMA1_Channel1->CNDTR = cndtr;
// 重新使能 DMA1_Channel1
// __HAL_DMA_ENABLE(&g_dma_adc_handle);
// 使能 ADC1
// __HAL_ADC_ENABLE(&g_adc_dma_handle);
// 启动 ADC 转换
// HAL_ADC_Start(&g_adc_dma_handle);
}
/* ADC DMA采集中断服务函数 */
void DMA1_Channel1_IRQHandler(void)
{
// 判断 DMA1 传输完成中断事件是否发生
if (DMA1->ISR & (1<<1))
{
// 标记 ADC DMA 状态
g_adc_dma_sta = 1;
// 清除 DMA1 传输完成中断标志
DMA1->IFCR |= 1 << 1;
}
}
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "./SYSTEM/sys/sys.h"
void adc_dma_init(uint32_t mar); /* ADC DMA读取 初始化函数 */
void adc_dma_enable(uint16_t cndtr); /* 使能一次ADC DMA传输函数 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/ADC/adc.h"
#define ADC_DMA_BUF_SIZE 100 /* ADC DMA采集 BUF大小 */
uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMA BUF */
extern uint8_t g_adc_dma_sta; /* DMA传输状态标志, 0,未完成; 1, 已完成 */
int main(void)
{
uint16_t i;
uint16_t adcx;
uint32_t sum;
float temp;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
adc_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "ADC DMA TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH1_VAL:", BLUE);
lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */
while (1)
{
if (g_adc_dma_sta == 1)
{
/* 计算DMA 采集到的ADC数据的平均值 */
sum = 0;
for (i = 0; i < ADC_DMA_BUF_SIZE; i++) /* 累加 */
{
sum += g_adc_dma_buf[i];
}
adcx = sum / ADC_DMA_BUF_SIZE; /* 取平均值 */
/* 显示结果 */
lcd_show_xnum(134, 110, adcx, 4, 16, 0, BLUE); /* 显示ADCC采样后的原始值 */
temp = (float)adcx * (3.3 / 4096); /* 获取计算后的带小数的实际电压值,比如3.1111 */
adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
lcd_show_xnum(134, 130, adcx, 1, 16, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
lcd_show_xnum(150, 130, temp, 3, 16, 0X80, BLUE); /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */
adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */
}
LED0_TOGGLE();
delay_ms(100);
}
}
adc.c
#include "./BSP/ADC/adc.h"
DMA_HandleTypeDef g_dma_nch_adc_handle;
ADC_HandleTypeDef g_adc_nch_dma_handle;
uint8_t g_adc_dma_sta;
/* ADC N通道(6通道) DMA读取 初始化函数 */
void adc_nch_dma_init(uint32_t mar)
{
ADC_ChannelConfTypeDef adc_ch_conf;
// 使能 DMA1 时钟
__HAL_RCC_DMA1_CLK_ENABLE();
// 配置 DMA 句柄参数
g_dma_nch_adc_handle.Instance = DMA1_Channel1;
g_dma_nch_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
g_dma_nch_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;
g_dma_nch_adc_handle.Init.MemInc = DMA_MINC_ENABLE;
g_dma_nch_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
g_dma_nch_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
g_dma_nch_adc_handle.Init.Mode = DMA_NORMAL;
g_dma_nch_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;
HAL_DMA_Init(&g_dma_nch_adc_handle);
// 将 DMA 句柄与 ADC 句柄关联
__HAL_LINKDMA(&g_adc_nch_dma_handle, DMA_Handle, g_dma_nch_adc_handle);
// 配置 ADC 句柄参数
g_adc_nch_dma_handle.Instance = ADC1;
g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
g_adc_nch_dma_handle.Init.ScanConvMode = ADC_SCAN_ENABLE;
g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE;
g_adc_nch_dma_handle.Init.NbrOfConversion = 6; // 6个通道的转换
g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE;
g_adc_nch_dma_handle.Init.NbrOfDiscConversion = 0;
g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
HAL_ADC_Init(&g_adc_nch_dma_handle);
// 启动 ADC 校准
HAL_ADCEx_Calibration_Start(&g_adc_nch_dma_handle);
// 配置 ADC 通道参数
adc_ch_conf.Channel = ADC_CHANNEL_0;
adc_ch_conf.Rank = ADC_REGULAR_RANK_1;
adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);
// 这里依次配置了6个通道,示例中只展示了第一个通道的配置
// 配置 DMA1_Channel1 的中断优先级和使能
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
// 启动 DMA 传输并开启 ADC DMA 模式
HAL_DMA_Start_IT(&g_dma_nch_adc_handle, (uint32_t)&ADC1->DR, mar, 0);
HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, &mar ,0);
}
/* ADC MSP初始化函数 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
if(hadc->Instance == ADC1)
{
GPIO_InitTypeDef gpio_init_struct;
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
// 使能 GPIOA 和 ADC1 时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_ADC1_CLK_ENABLE();
// 配置 GPIOA 引脚为模拟输入模式
gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
gpio_init_struct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
// 配置 ADC 时钟
adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
}
}
/* 使能一次ADC DMA传输函数 */
void adc_dma_enable(uint16_t cndtr)
{
// 关闭 ADC1
ADC1->CR2 &= ~(1 << 0);
// 关闭 DMA1_Channel1,并等待传输完成
DMA1_Channel1->CCR &= ~(1 << 0);
while (DMA1_Channel1->CCR & (1 << 0));
// 重新设置 DMA1_Channel1 的传输数据长度
DMA1_Channel1->CNDTR = cndtr;
// 重新使能 DMA1_Channel1
DMA1_Channel1->CCR |= 1 << 0;
// 使能 ADC1
ADC1->CR2 |= 1 << 0;
// 启动 ADC 转换
ADC1->CR2 |= 1 << 22;
}
/* ADC DMA采集中断服务函数 */
void DMA1_Channel1_IRQHandler(void)
{
// 检查 DMA 传输完成标志位
if (DMA1->ISR & (1<<1))
{
// 设置 DMA 传输完成标志
g_adc_dma_sta = 1;
// 清除 DMA 传输完成标志位
DMA1->IFCR |= 1 << 1;
}
}
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "./SYSTEM/sys/sys.h"
void adc_nch_dma_init(uint32_t mar); /* ADC N通道(6通道) DMA读取 初始化函数 */
void adc_dma_enable(uint16_t cndtr); /* 使能一次ADC DMA传输函数 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/ADC/adc.h"
#define ADC_DMA_BUF_SIZE 50 * 6 /* ADC DMA采集 BUF大小, 应等于ADC通道数的整数倍 */
uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMA BUF */
extern uint8_t g_adc_dma_sta; /* DMA传输状态标志, 0,未完成; 1, 已完成 */
int main(void)
{
uint16_t i,j;
uint16_t adcx;
uint32_t sum;
float temp;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
adc_nch_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "ADC 6CH DMA TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 12, 12, "ADC1_CH0_VAL:", BLUE);
lcd_show_string(30, 122, 200, 12, 12, "ADC1_CH0_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 140, 200, 12, 12, "ADC1_CH1_VAL:", BLUE);
lcd_show_string(30, 152, 200, 12, 12, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 170, 200, 12, 12, "ADC1_CH2_VAL:", BLUE);
lcd_show_string(30, 182, 200, 12, 12, "ADC1_CH2_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 200, 200, 12, 12, "ADC1_CH3_VAL:", BLUE);
lcd_show_string(30, 212, 200, 12, 12, "ADC1_CH3_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 230, 200, 12, 12, "ADC1_CH4_VAL:", BLUE);
lcd_show_string(30, 242, 200, 12, 12, "ADC1_CH4_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 260, 200, 12, 12, "ADC1_CH5_VAL:", BLUE);
lcd_show_string(30, 272, 200, 12, 12, "ADC1_CH5_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */
while (1)
{
if (g_adc_dma_sta == 1)
{
/* 循环显示通道0~通道5的结果 */
for(j = 0; j < 6; j++) /* 遍历6个通道 */
{
sum = 0; /* 清零 */
for (i = 0; i < ADC_DMA_BUF_SIZE / 6; i++) /* 每个通道采集了50次数据,进行50次累加 */
{
sum += g_adc_dma_buf[(6 * i) + j]; /* 相同通道的转换数据累加 */
}
adcx = sum / (ADC_DMA_BUF_SIZE / 6); /* 取平均值 */
/* 显示结果 */
lcd_show_xnum(108, 110 + (j * 30), adcx, 4, 12, 0, BLUE); /* 显示ADC采样后的原始值 */
temp = (float)adcx * (3.3 / 4096); /* 获取计算后的带小数的实际电压值,比如3.1111 */
adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
lcd_show_xnum(108, 122 + (j * 30), adcx, 1, 12, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
lcd_show_xnum(120, 122 + (j * 30), temp, 3, 12, 0X80, BLUE);/* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
}
g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */
adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */
}
LED0_TOGGLE();
delay_ms(100);
}
}
用过采样和求均值提高ADC分辨率 方程推导过程:https://max.book118.com/html/2018/0506/165038217.shtm
过采样和求均值的方式可以有效提高ADC的分辨率。下面是具体步骤:
确定过采样率: 过采样率是指在一定时间内采集的样本数。根据要增加的分辨率位数和初始采样频率,可以使用以下方程确定过采样率:
f
o
s
=
4
w
×
f
s
f_{os} = 4^w \times f_s
fos=4w×fs
其中,
f
o
s
f_{os}
fos是过采样频率,
w
w
w是希望增加的分辨率位数,
f
s
f_s
fs是初始采样频率。
求均值: 在得到过采样率之后,进行多次采样,并将采样结果进行求和。例如,对于12位分辨率的ADC要提高4位分辨率,需要进行256倍的过采样( 2 4 = 256 2^4 = 256 24=256)。然后将这256次采样结果求和,最后右移4位,即可得到提高分辨率后的结果。
注意:提高N位分辨率,需要将求和结果右移N位。
通过这种方法,可以在不增加硬件成本的情况下提高ADC的分辨率,提高系统对信号的精确度和灵敏度。
adc.c
#include "./BSP/ADC/adc.h"
DMA_HandleTypeDef g_dma_adc_handle;
ADC_HandleTypeDef g_adc_dma_handle;
uint8_t g_adc_dma_sta;
/* ADC DMA读取 初始化函数 */
void adc_dma_init(uint32_t mar)
{
ADC_ChannelConfTypeDef adc_ch_conf;
// 使能DMA时钟
__HAL_RCC_DMA1_CLK_ENABLE();
// 配置DMA句柄
g_dma_adc_handle.Instance = DMA1_Channel1;
g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;
g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE;
g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
g_dma_adc_handle.Init.Mode = DMA_NORMAL;
g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;
HAL_DMA_Init(&g_dma_adc_handle);
// 将DMA与ADC句柄联系起来
__HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle);
// 配置ADC句柄
g_adc_dma_handle.Instance = ADC1;
g_adc_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
g_adc_dma_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
g_adc_dma_handle.Init.ContinuousConvMode = ENABLE;
g_adc_dma_handle.Init.NbrOfConversion = 1;
g_adc_dma_handle.Init.DiscontinuousConvMode = DISABLE;
g_adc_dma_handle.Init.NbrOfDiscConversion = 0;
g_adc_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
HAL_ADC_Init(&g_adc_dma_handle);
// ADC校准
HAL_ADCEx_Calibration_Start(&g_adc_dma_handle);
// 配置ADC通道
adc_ch_conf.Channel = ADC_CHANNEL_1;
adc_ch_conf.Rank = ADC_REGULAR_RANK_1;
adc_ch_conf.SamplingTime = ADC_SAMPLETIME_1CYCLE_5; // 采样时间为1.5个ADC时钟周期
HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf);
// 配置DMA传输中断
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
// 启动DMA传输
HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);
// 启动ADC DMA传输
HAL_ADC_Start_DMA(&g_adc_dma_handle, &mar ,0);
}
/* ADC MSP初始化函数 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
if(hadc->Instance == ADC1)
{
GPIO_InitTypeDef gpio_init_struct;
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
// 使能GPIOA时钟和ADC1时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_ADC1_CLK_ENABLE();
// 配置ADC引脚为模拟输入模式
gpio_init_struct.Pin = GPIO_PIN_1;
gpio_init_struct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
// 配置ADC时钟
adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
}
}
/* 使能一次ADC DMA传输函数 */
void adc_dma_enable(uint16_t cndtr)
{
// 禁止ADC和DMA传输
ADC1->CR2 &= ~(1 << 0);
DMA1_Channel1->CCR &= ~(1 << 0);
while (DMA1_Channel1->CCR & (1 << 0));
// 配置DMA传输数量
DMA1_Channel1->CNDTR = cndtr;
// 使能DMA传输和ADC
DMA1_Channel1->CCR |= 1 << 0;
ADC1->CR2 |= 1 << 0;
ADC1->CR2 |= 1 << 22;
// 等待DMA传输完成
// __HAL_DMA_DISABLE(&g_dma_adc_handle);
// while (__HAL_DMA_GET_FLAG(&g_dma_adc_handle, __HAL_DMA_GET_TC_FLAG_INDEX(&g_dma_adc_handle)));
// DMA1_Channel1->CNDTR = cndtr;
// __HAL_DMA_ENABLE(&g_dma_adc_handle);
// 启动ADC
// __HAL_ADC_ENABLE(&g_adc_dma_handle);
// HAL_ADC_Start(&g_adc_dma_handle);
}
/* ADC DMA采集中断服务函数 */
void DMA1_Channel1_IRQHandler(void)
{
if (DMA1->ISR & (1<<1))
{
g_adc_dma_sta = 1;
DMA1->IFCR |= 1 << 1;
}
}
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "./SYSTEM/sys/sys.h"
void adc_dma_init(uint32_t mar); /* ADC DMA读取 初始化函数 */
void adc_dma_enable(uint16_t cndtr); /* 使能一次ADC DMA传输函数 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/ADC/adc.h"
/* ADC过采样技术, 是利用ADC多次采集的方式, 来提高ADC精度, 采样速度每提高4倍
* 采样精度提高 1bit, 同时, ADC采样速度降低4倍, 如提高4bit精度, 需要256次采集
* 才能得出1次数据, 相当于ADC速度慢了256倍. 理论上只要ADC足够快, 我们可以无限
* 提高ADC精度, 但实际上ADC并不是无限快的, 而且由于ADC性能限制, 并不是位数无限
* 提高结果就越好, 需要根据自己的实际需求和ADC的实际性能来权衡.
*/
#define ADC_OVERSAMPLE_TIMES 256 /* ADC过采样次数, 这里提高4bit分辨率, 需要256倍采样 */
#define ADC_DMA_BUF_SIZE ADC_OVERSAMPLE_TIMES * 10 /* ADC DMA采集 BUF大小, 应等于过采样次数的整数倍 */
uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMA BUF */
extern uint8_t g_adc_dma_sta; /* DMA传输状态标志, 0,未完成; 1, 已完成 */
int main(void)
{
uint16_t i;
uint32_t adcx;
uint32_t sum;
float temp;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
adc_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "ADC OverSample TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH1_VAL:", BLUE);
lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */
while (1)
{
if (g_adc_dma_sta == 1)
{
/* 计算DMA 采集到的ADC数据的平均值 */
sum = 0;
for (i = 0; i < ADC_DMA_BUF_SIZE; i++) /* 累加 */
{
sum += g_adc_dma_buf[i];
}
adcx = sum / (ADC_DMA_BUF_SIZE / ADC_OVERSAMPLE_TIMES); /* 取平均值 */
adcx >>= 4; /* 除以2^4倍, 得到12+4位 ADC精度值, 注意: 提高 N bit精度, 需要 >> N */
/* 显示结果 */
lcd_show_xnum(134, 110, adcx, 5, 16, 0, BLUE); /* 显示ADC采样后的原始值 */
temp = (float)adcx * (3.3 / 65536); /* 获取计算后的带小数的实际电压值,比如3.1111 */
adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
lcd_show_xnum(134, 130, adcx, 1, 16, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
lcd_show_xnum(150, 130, temp, 3, 16, 0X80, BLUE); /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */
adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */
}
LED0_TOGGLE();
delay_ms(100);
}
}
温度计算方法如下所示:
首先,从ADC采集到的内部温度传感器的电压值 V S E N S E V_{SENSE} VSENSE开始。
计算温度与 V S E N S E V_{SENSE} VSENSE曲线的平均斜率,通常以毫伏/摄氏度( m v / ℃ mv/℃ mv/℃)或微伏/摄氏度( u v / ℃ uv/℃ uv/℃)为单位表示。典型值约为4.3mv/℃。
确定25°C时的 V S E N S E V_{SENSE} VSENSE值,典型值为1.43。
使用以下公式计算温度(以摄氏度为单位):
T ( ℃ ) = ( V 25 − V S E N S E A v g _ S l o p e ) + 25 T(℃) = \left(\frac{V25 - V_{SENSE}}{Avg\_Slope}\right) + 25 T(℃)=(Avg_SlopeV25−VSENSE)+25
其中,
V
25
V_{25}
V25 是在25°C时的
V
S
E
N
S
E
V_{SENSE}
VSENSE值,
A
v
g
_
S
l
o
p
e
Avg\_Slope
Avg_Slope 是温度与
V
S
E
N
S
E
V_{SENSE}
VSENSE曲线的平均斜率。
adc.c
#include "./BSP/ADC/adc.h"
ADC_HandleTypeDef g_adc_handle;
/* ADC 内部温度传感器 初始化函数 */
void adc_temperature_init(void)
{
ADC_ChannelConfTypeDef adc_ch_conf;
// 初始化 ADC 句柄
g_adc_handle.Instance = ADC1;
g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
g_adc_handle.Init.ContinuousConvMode = DISABLE;
g_adc_handle.Init.NbrOfConversion = 1;
g_adc_handle.Init.DiscontinuousConvMode = DISABLE;
g_adc_handle.Init.NbrOfDiscConversion = 0;
g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
HAL_ADC_Init(&g_adc_handle);
// 进行 ADC 校准
HAL_ADCEx_Calibration_Start(&g_adc_handle);
// 配置 ADC 通道
adc_ch_conf.Channel = ADC_CHANNEL_16;
adc_ch_conf.Rank = ADC_REGULAR_RANK_1;
adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
HAL_ADC_ConfigChannel(&g_adc_handle, &adc_ch_conf);
}
/* ADC MSP初始化函数 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
if(hadc->Instance == ADC1)
{
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
// 使能 ADC1 时钟
__HAL_RCC_ADC1_CLK_ENABLE();
// 配置 ADC1 时钟
adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
}
}
/* 获得ADC转换后的结果函数 */
uint32_t adc_get_result(void)
{
// 启动 ADC 转换
HAL_ADC_Start(&g_adc_handle);
// 等待转换完成
HAL_ADC_PollForConversion(&g_adc_handle, 10);
// 返回转换结果
return (uint16_t)HAL_ADC_GetValue(&g_adc_handle);
}
/* 获取内部温度传感器温度值 */
short adc_get_temperature(void)
{
uint32_t adcx;
short result;
double temperature;
// 获取 ADC 转换结果
adcx = adc_get_result();
// 计算温度值
temperature = adcx * (3.3 / 4096);
temperature = (1.43 - temperature) / 0.0043 + 25;
// 转换成整型
result = temperature *= 100;
// 返回温度值
return result;
}
adc.h
#ifndef __ADC_H
#define __ADC_H
#include "./SYSTEM/sys/sys.h"
void adc_temperature_init(void); /* ADC 内部温度传感器 初始化函数 */
uint32_t adc_get_result(void); /* 获得ADC转换后的结果函数 */
short adc_get_temperature(void); /* 获取内部温度传感器温度值 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/ADC/adc.h"
int main(void)
{
short temp;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
adc_temperature_init(); /* 初始化ADC内部温度传感器采集 */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "Temperature TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 120, 200, 16, 16, "TEMPERATE: 00.00C", BLUE);
while (1)
{
temp = adc_get_temperature(); /* 得到温度值 */
if (temp < 0)
{
temp = -temp;
lcd_show_string(30 + 10 * 8, 120, 16, 16, 16, "-", BLUE); /* 显示负号 */
}
else
{
lcd_show_string(30 + 10 * 8, 120, 16, 16, 16, " ", BLUE); /* 无符号 */
}
lcd_show_xnum(30 + 11 * 8, 120, temp / 100, 2, 16, 0, BLUE); /* 显示整数部分 */
lcd_show_xnum(30 + 14 * 8, 120, temp % 100, 2, 16, 0X80, BLUE); /* 显示小数部分 */
LED0_TOGGLE(); /* LED0闪烁,提示程序运行 */
delay_ms(250);
}
}
光敏二极管(Photodiode)是一种特殊的二极管,其核心是一个PN结。与普通二极管不同的是,光敏二极管对光的强度非常敏感。它在工作时需要加上反向电压。
光敏二极管的暗电流非常小,通常小于0.1微安,这是因为在无光照的情况下,几乎没有光子撞击PN结,因此几乎没有电流通过。
当有光照射到光敏二极管上时,光的强度越大,反向电流也越大,形成光电流。这种光电流的变化是非线性的,即光强增大时,电流不是简单地线性增加,而是呈现出一种非线性的变化。
为了利用光敏二极管来检测光的强度变化,通常会串联一个电阻。当光照强度发生变化时,光敏二极管的电流也会随之变化,导致电阻两端的电压发生变化。通过连接至ADC(模数转换器)的方式,可以读取电压的变化,从而得知光强的变化情况。
总的来说,光敏二极管可以通过测量其反向电流的变化来检测光的强度变化,从而实现对光的检测和测量。
光敏传感器实验的原理是利用光敏二极管(或其他类型的光敏传感器)的光敏特性来检测光照强度的变化,并将其转换为电信号输出。以下是光敏传感器实验的基本原理:
光敏传感器选择:选择适合实验需求的光敏传感器,如光敏二极管、光敏电阻等。根据实验要求和预期的光强范围选择合适的传感器。
传感器连接:将光敏传感器连接到适当的电路中。通常,光敏传感器需要与电源和负载电阻相连接。光敏传感器的输出端可以连接到微控制器、模数转换器(ADC)或其他电子设备,用于读取和处理光强信号。
光照实验:将光敏传感器放置在待测的光源附近,观察传感器输出信号随着光照强度的变化而变化。可以尝试不同光照条件下的实验,比如改变光源的亮度或距离,以及改变光源的颜色。
数据采集与分析:利用连接的微控制器或ADC等设备采集传感器输出的电信号,并进行分析处理。可以将光照强度与传感器输出信号之间的关系绘制成曲线或图表,以便更直观地理解传感器的响应特性。
实验验证与应用:根据实验结果对光敏传感器的性能进行验证,并根据实验需求进行进一步的应用。例如,可以将光敏传感器用于制作光控开关、光敏电压表、光强监测系统等。
通过光敏传感器实验,可以深入了解光敏传感器的工作原理、特性和应用场景,为光电转换和光控系统的设计提供实验基础和参考。
adc3.c
#include "./BSP/ADC/adc3.h"
ADC_HandleTypeDef g_adc_handle;
/* ADC单通道初始化函数 */
void adc3_init(void)
{
ADC_ChannelConfTypeDef adc_ch_conf;
// 设置ADC句柄实例
g_adc_handle.Instance = ADC3;
// 设置数据对齐方式为右对齐
g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
// 禁用扫描模式
g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
// 禁用连续转换模式
g_adc_handle.Init.ContinuousConvMode = DISABLE;
// 设置转换通道数量为1
g_adc_handle.Init.NbrOfConversion = 1;
// 禁用不连续转换模式
g_adc_handle.Init.DiscontinuousConvMode = DISABLE;
// 不启用不连续转换
g_adc_handle.Init.NbrOfDiscConversion = 0;
// 设置外部触发转换模式为软件触发
g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
// 初始化ADC
HAL_ADC_Init(&g_adc_handle);
// 执行ADC校准
HAL_ADCEx_Calibration_Start(&g_adc_handle);
// 配置ADC通道
adc_ch_conf.Channel = ADC_CHANNEL_6; // 设置通道为ADC_CHANNEL_6
adc_ch_conf.Rank = ADC_REGULAR_RANK_1; // 设置通道在规则组中的排位
adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; // 设置采样时间
HAL_ADC_ConfigChannel(&g_adc_handle, &adc_ch_conf); // 配置ADC通道
}
/* ADC MSP初始化函数 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
if(hadc->Instance == ADC3)
{
GPIO_InitTypeDef gpio_init_struct;
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
// 使能GPIOF时钟
__HAL_RCC_GPIOF_CLK_ENABLE();
// 使能ADC3时钟
__HAL_RCC_ADC3_CLK_ENABLE();
// 配置GPIOF引脚为模拟输入模式
gpio_init_struct.Pin = GPIO_PIN_8; // 设置引脚为GPIO_PIN_8
gpio_init_struct.Mode = GPIO_MODE_ANALOG; // 设置引脚模式为模拟输入
HAL_GPIO_Init(GPIOF, &gpio_init_struct); // 初始化GPIOF引脚
// 配置ADC时钟
adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC; // 设置ADC时钟源
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6; // 设置ADC时钟分频
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init); // 配置ADC时钟
}
}
/* 获得ADC转换后的结果函数 */
uint32_t adc_get_result(void)
{
// 启动ADC转换
HAL_ADC_Start(&g_adc_handle);
// 等待转换完成
HAL_ADC_PollForConversion(&g_adc_handle, 10);
// 获取转换结果并返回
return (uint16_t)HAL_ADC_GetValue(&g_adc_handle);
}
/* 读取光敏传感器值 */
uint8_t lsens_get_val(void)
{
uint32_t temp_val;
// 获取ADC转换结果
temp_val = adc_get_result();
// 将ADC转换结果除以40(假设这是一个适当的缩放系数),并进行阈值处理
temp_val /= 40;
if (temp_val > 100) temp_val = 100; // 阈值处理,确保结果不超过100
// 返回光敏传感器值
return (uint8_t)(100 - temp_val); // 返回取反后的结果
}
adc3.h
#ifndef __ADC_H
#define __ADC_H
#include "./SYSTEM/sys/sys.h"
void adc3_init(void); /* ADC单通道初始化函数 */
uint32_t adc_get_result(void); /* 获得ADC转换后的结果函数 */
uint8_t lsens_get_val(void); /* 读取光敏传感器值 */
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/ADC/adc3.h"
int main(void)
{
short adcx;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
adc3_init(); /* 初始化ADC3 */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "LSENS TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "LSENS_VAL:", BLUE);
while (1)
{
adcx = lsens_get_val();
lcd_show_xnum(30 + 10 * 8, 110, adcx, 3, 16, 0, BLUE); /* 显示ADC的值 */
LED0_TOGGLE(); /* LED0闪烁,提示程序运行 */
delay_ms(250);
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。