【GL08】STM32--ADC/DAC

一、ADC简介

ADC 即模拟信号到数字信号的转换,即用数字信号展现模拟的世界,所有的计算机或者数字处理器只能接受以 0 和 1 两种状态的数字信号,而对于模拟信号,则无法识别,而需要经过模拟数字转换器来感受模拟的世界。

ADC 的本身的性能也是非常重要的,ADC 的主要指标有分辨率(Resolution)、采样速率(Sample Rate)、量化误差(Quantuzing Error)、增益误差(Gain Error)、微分非线性(Differential Nonlinerity)。

1. ADC参数

1.分辨率:数字变化一个最小量时模拟信号的变化量,定义为满刻度与2n 的比值,其中 n为 ADC 位数,分辨率越高,数字量转换一格时对应的模拟量变化越精细,越能表示实际的模拟信号。

2.采样速率:每秒的采样次数称为采样速率,又称为吞吐率,以每秒采样数为单位,即SPS(Sample Per Secind)。

3.转换速率:与采样速率不同,采样时间与转换速率成反比,即完成一次完整的 AD 转换所需要的时间,采样时间由 ADC 的转换时间和建立时间等构成。在建立时间中受到外部信号源的输出阻抗的影响,信号源的输出阻抗越大,ADC 建立时间将越长,从而增加了采样时间,可以利用运算放大器的高输入阻抗低输出阻抗特性设计成一个电压跟随器,再接到ADC 模拟输入端,这样可以减小串入到模拟输入端的电阻,从而减少建立时间以保证 ADC能活得更大的采样率,串接电压跟随器还可以起到减小被采样对象对系统干扰的作用。另外,在多通道采样中,由于通道切换需要时间,也要充分考虑 ADC 建立时间的问题。

4.量化误差:由 ADC 的优先分辨率引起的误差,即优先分辨率 ADC 的阶梯状转换特性曲线与理想 ADC 的特性曲线之间的最大偏差。通常是 1 个或半个最小数字量的模拟变化量(1LSB 或 1/2LSB)。

5.偏移误差。增益误差、微分积分线性误差

2.ADC类型

(1)并联比较型ADC

(2)逐次逼近型ADC

二、ADC封装代码

包括:初始化、配置、读取数据、数据处理。

cpp 复制代码
ADC_HandleTypeDef g_adc_handle;   /* ADC句柄 */

/**
 *ADC初始化函数
 *我们使用16位精度, ADC采样时钟=32M, 转换时间为:采样周期 + 8.5个ADC周期
 *设置最大采样周期: 810.5, 则转换时间 = 819个ADC周期 = 25.6us
 */
void adc_init(void)
{
    g_adc_handle.Instance = ADC_ADCX;                                               /* 选择哪个ADC */
    g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2;                        /* 输入时钟2分频,即adc_ker_ck=per_ck/2=32Mhz */
    g_adc_handle.Init.Resolution = ADC_RESOLUTION_16B;                              /* 16位模式  */
    g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;                              /* 非扫描模式,仅用到一个通道 */
    g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;                           /* 关闭EOC中断 */
    g_adc_handle.Init.LowPowerAutoWait = DISABLE;                                   /* 自动低功耗关闭 */
    g_adc_handle.Init.ContinuousConvMode = DISABLE;                                 /* 关闭连续转换模式 */
    g_adc_handle.Init.NbrOfConversion = 1;                                          /* 赋值范围是1~16,本实验用到1个常规通道 */
    g_adc_handle.Init.DiscontinuousConvMode = DISABLE;                              /* 禁止常规转换组不连续采样模式 */
    g_adc_handle.Init.NbrOfDiscConversion = 0;                                      /* 配置不连续采样模式的通道数,禁止常规转换组不连续采样模式后,此参数忽略 */
    g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;                        /* 软件触发 */
    g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;         /* 采用软件触发的话,此位忽略 */
    g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;             /* 常规通道的数据仅仅保存在DR寄存器里面 */
    g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;                           /* 有新的数据后直接覆盖掉旧数据 */
    g_adc_handle.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;                         /* 设置ADC转换结果的左移位数 */
    g_adc_handle.Init.OversamplingMode = DISABLE;                                   /* 关闭过采样 */
    HAL_ADC_Init(&g_adc_handle);                                                    /* 初始化 */

    HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); /* ADC校准 */
}



/**
 * @brief       ADC底层驱动,引脚配置,时钟使能
                此函数会被HAL_ADC_Init()调用
 * @param       hadc:ADC句柄
 * @retval      无
 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
    if(hadc->Instance == ADC_ADCX)
    {
        GPIO_InitTypeDef gpio_init_struct;

        ADC_ADCX_CHY_CLK_ENABLE();                      /* 使能ADC1/2时钟 */
        ADC_ADCX_CHY_GPIO_CLK_ENABLE();                 /* 开启ADC通道IO引脚时钟 */

        __HAL_RCC_ADC_CONFIG(RCC_ADCCLKSOURCE_CLKP);    /* ADC外设时钟选择 */

        gpio_init_struct.Pin = ADC_ADCX_CHY_GPIO_PIN;   /* ADC通道IO引脚 */
        gpio_init_struct.Mode = GPIO_MODE_ANALOG;       /* 模拟 */
        HAL_GPIO_Init(ADC_ADCX_CHY_GPIO_PORT, &gpio_init_struct);
    }
}

/**
 * @brief       获得ADC转换后的结果 
 * @param       ch: 通道值 0~19,取值范围为:ADC_CHANNEL_0~ADC_CHANNEL_19
 * @retval      返回值:转换结果
 */
uint32_t adc_get_result(uint32_t ch)
{
    ADC_ChannelConfTypeDef adc_ch_conf = {0};

    adc_ch_conf.Channel = ch;                              /* 通道 */
    adc_ch_conf.Rank = ADC_REGULAR_RANK_1;                 /* 序列 */
    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_810CYCLES_5; /* 采样时间,设置最大采样周期: 810.5个ADC周期 */
    adc_ch_conf.SingleDiff = ADC_SINGLE_ENDED;             /* 单边采集 */
    adc_ch_conf.OffsetNumber = ADC_OFFSET_NONE;            /* 不使用偏移量的通道 */
    adc_ch_conf.Offset = 0;                                /* 偏移量为0 */
    HAL_ADC_ConfigChannel(&g_adc_handle, &adc_ch_conf);    /* 通道配置 */

    HAL_ADC_Start(&g_adc_handle);                          /* 开启ADC */
    HAL_ADC_PollForConversion(&g_adc_handle, 10);          /* 轮询转换 */
    return HAL_ADC_GetValue(&g_adc_handle);                /* 返回最近一次ADC1常规组的转换结果 */
}

/**
 * @brief       获取通道ch的转换值,取times次,然后平均
 * @param       ch      : 通道号, 0~19
 * @param       times   : 获取次数
 * @retval      通道ch的times次转换结果平均值
 */
uint32_t adc_get_result_average(uint32_t ch, uint8_t times)
{
    uint32_t temp_val = 0;
    uint8_t t;

    for (t = 0; t < times; t++) /* 获取times次数据 */
    {
        temp_val += adc_get_result(ch);
        delay_ms(5);
    }

    return temp_val / times;    /* 返回平均值 */
}

三、ADC运用(重点)

STM32H750 系列有 3 个 ADC, 都可以独立工作,其中 ADC1 和 ADC2 还可以组成双重模式(提高采样率)。 STM32H750 的 ADC 分辨率高达 16 位,每个 ADC 具有多达 20 个的采集通道,这些通道的 A/D 转换可以单次、连续、扫描或间断模式执行。 ADC的结果可以左对齐或右对齐方式存储在 32 位数据寄存器中。STM32H750 的 ADC 最大的转换速率为 4.5Mhz,也就是转换时间为 0.22us(12 位分辨率时),不要让 ADC 的时钟超过 36M,否则将导致结果准确度下降。(STM32F1系列:ADC的输入时钟不得超过14MHz,它是由PCLK2经分频产生)

1.ADC_DMA封装函数
cpp 复制代码
//ADC_DMA.c
uint16_t dma_i;
uint16_t adc1_val_buf[ADC1_CHANNEL_CNT*ADC1_CHANNEL_FRE]; //传递给DMA存放多通道采样值的数组
uint32_t adc1_aver_val[ADC1_CHANNEL_CNT] = {0}; //计算多通道的平均采样值的过程数组
uint16_t value[ADC1_CHANNEL_CNT] = {0};//多通道的平均采样值的数组
uint16_t value_max[ADC1_CHANNEL_CNT] = {0};

void ADC_Sample_Start(){
  if(HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&adc1_val_buf, (ADC1_CHANNEL_CNT * ADC1_CHANNEL_FRE)) != HAL_OK)
  {
    Error_Handler(); 
  }
}

void ADC_Process(){
  //处理DMA数据
  for(dma_i = 0; dma_i < ADC1_CHANNEL_CNT; dma_i++)
  {
    adc1_aver_val[dma_i] = 0;
  }
  /* 在采样值数组中分别取出每个通道的采样值并求和 */
  for(dma_i = 0; dma_i < ADC1_CHANNEL_FRE; dma_i++)
  {
    adc1_aver_val[0] +=  adc1_val_buf[dma_i*ADC1_CHANNEL_CNT+0];
    adc1_aver_val[1] +=  adc1_val_buf[dma_i*ADC1_CHANNEL_CNT+1];
    adc1_aver_val[2] +=  adc1_val_buf[dma_i*ADC1_CHANNEL_CNT+2];
  }
  /* 依次对每个通道采样值求平均值 */
  for(dma_i = 0; dma_i < ADC1_CHANNEL_CNT; dma_i++)
  {
    value[dma_i] = adc1_aver_val[dma_i] / ADC1_CHANNEL_FRE;
  }

}
cpp 复制代码
// ADC_DMA.c

#include "main.h"
#include "adc.h"

//ADC DMA传输
#define ADC1_CHANNEL_CNT 4 	//采样通道数
#define ADC1_CHANNEL_FRE 5	//单个通道采样次数,用来取平均值
extern uint16_t adc1_val_buf[ADC1_CHANNEL_CNT*ADC1_CHANNEL_FRE];
extern uint32_t adc1_aver_val[ADC1_CHANNEL_CNT]; //计算多通道的平均采样值的过程数组
extern uint16_t value[ADC1_CHANNEL_CNT];
extern uint16_t dma_i;

void ADC_Sample_Start();
void ADC_Process();
2.ADC常用函数HAL库
cpp 复制代码
//DAC常用函数
HAL_DAC_Start(DAC_HandleTypeDef* hdac, uint32_t Channel);     //开启DAC输出
HAL_DAC_Stop(DAC_HandleTypeDef* hdac, uint32_t Channel);   //关闭DAC输出
HAL_DAC_Start_DMA(DAC_HandleTypeDef* hdac, uint32_t Channel, uint32_t* pData, uint32_t Length, uint32_t Alignment); //需要函数中不断开启,开启DAC的DMA输出
HAL_DAC_Stop_DMA(DAC_HandleTypeDef* hdac, uint32_t Channel); //关闭DAC的DMA输出
HAL_DAC_SetValue(DAC_HandleTypeDef* hdac, uint32_t Channel, uint32_t Alignment, uint32_t Data);  //设置DAC输出值
HAL_DAC_GetValue(DAC_HandleTypeDef* hdac, uint32_t Channel);  //获取DAC输出值

四、DAC知识点

STM32H750 的 DAC 模块( 数字/模拟转换模块) 是 12 位数字输入,电压输出型的 DAC。DAC 可以配置为 8 位或 12 位模式,也可以与 DMA 控制器配合使用。 DAC 工作在 12 位模式时,数据可以设置成左对齐或右对齐。 DAC 模块有 2 个输出通道,每个通道都有单独的转换器。在双 DAC 模式下, 2 个通道可以独立地进行转换,也可以同时进行转换并同步地更新 2 个通道的输出。 DAC 可以通过引脚输入参考电压 Vref+(通 ADC 共用) 以获得更精确的转换结果。

PWM--DAC原理

PWM(Pulse Width Modulation,脉冲宽度调制)DAC技术是一种利用PWM信号模拟输出连续变化电压或电流的技术。在PWM中,信号是一个周期固定的方波,其特点是占空比(高电平持续时间相对于整个周期的比例)是可以调节的。对于一个PWM信号来说,理论上它可以被看作是由一个不变的直流分量和一系列不同频率的交流分量组成,其中直流分量对应于PWM信号的平均值,而其他交流分量则随着占空比的变化而包含不同的谐波成分。

在PWM DAC应用中,通过改变PWM信号的占空比来模拟不同的电压等级。由于PWM本身是数字信号,要将其转换为模拟信号,就需要通过一个低通滤波器来滤除高频的PWM方波分量,保留并平滑其直流分量。这样一来,随着PWM占空比的改变,经过滤波后的输出信号就变成了一个连续变化且与占空比成比例的模拟电压或电流信号。

因此,PWM DAC是一种经济高效的数模转换方案,尤其适用于嵌入式系统和其他对成本、功耗或集成度有较高要求的应用场合,但由于受到滤波器性能的限制,其输出模拟信号的质量(如分辨率、线性度、噪声和失真)相较于专用的高精度DAC可能较低。为了提高PWM DAC的输出精度,可以通过增加PWM位数(提高分辨率)、优化滤波器设计以及采用多个PWM通道并行合成更高级别的DAC等方式来改善性能。

cpp 复制代码
// 定义全局PWM-DAC定时器通道句柄
TIM_HandleTypeDef g_timx_pwm_chy_handle;

// PWM DAC初始化函数
// 该函数用于初始化TIM1定时器,使其产生PWM信号用于控制DAC输出电压
void pwmdac_init(uint16_t arr, uint16_t psc)
{
    // 初始化定时器通道PWM配置结构体
    TIM_OC_InitTypeDef timx_oc_pwm_chy = {0};

    // 设置定时器实例为TIM1
    g_timx_pwm_chy_handle.Instance = TIM1;

    // 配置TIM1的基本参数,包括预分频psc和自动重载值arr
    g_timx_pwm_chy_handle.Init.Prescaler = psc;
    g_timx_pwm_chy_handle.Init.Period = arr;
    g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数模式
    g_timx_pwm_chy_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; // 自动重装载使能

    // 初始化TIM1定时器PWM模式
    HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle);

    // 配置TIM1通道1为PWM模式1
    timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;
    timx_oc_pwm_chy.Pulse = 0; // 初始脉宽设置为0
    timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_HIGH; // PWM极性为高电平有效
    HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, TIM_CHANNEL_1); // 配置TIM1通道1

    // 启动TIM1通道1的PWM输出
    HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, TIM_CHANNEL_1);
}

// TIM MSP初始化函数
// 该函数主要用于初始化TIM1相关的GPIO,使其可以与TIM1通道相配合工作
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM1)
    {
        GPIO_InitTypeDef gpio_init_struct;

        // 使能GPIOA和TIM1时钟
        __HAL_RCC_GPIOA_CLK_ENABLE();
        __HAL_RCC_TIM1_CLK_ENABLE();

        // 配置TIM1通道1对应的GPIOA Pin 8
        gpio_init_struct.Pin = GPIO_PIN_8;
        gpio_init_struct.Mode = GPIO_MODE_AF_PP; // 设置为推挽复用输出模式
        gpio_init_struct.Pull = GPIO_PULLUP; // 上拉使能,防止悬空
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速输出
        HAL_GPIO_Init(GPIOA, &gpio_init_struct); // 初始化GPIOA Pin 8
    }
}

// 设置PWM DAC输出电压函数
// 该函数接收一个电压值vol(单位毫伏),并根据该值调整TIM1通道1的PWM占空比以改变DAC输出电压
void pwmdac_set_voltage(uint16_t vol)
{
    // 将输入电压值从毫伏转换为与TIM1比较寄存器匹配的比例值
    float temp = vol;
    temp /= 1000; // 单位转换为伏特
    temp = temp * 256 / 3.3; // 将电压值映射到0-256区间,假设DAC参考电压为3.3V

    // 设置TIM1通道1的比较寄存器(即PWM占空比)
    __HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, TIM_CHANNEL_1, temp);
}
相关推荐
日晨难再1 小时前
嵌入式:STM32的启动(Startup)文件解析
stm32·单片机·嵌入式硬件
yufengxinpian2 小时前
集成了高性能ARM Cortex-M0+处理器的一款SimpleLink 2.4 GHz无线模块-RF-BM-2340B1
单片机·嵌入式硬件·音视频·智能硬件
__基本操作__3 小时前
历遍单片机下的IIC设备[ESP--0]
单片机·嵌入式硬件
网易独家音乐人Mike Zhou9 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
zy张起灵9 小时前
48v72v-100v转12v 10A大功率转换电源方案CSM3100SK
经验分享·嵌入式硬件·硬件工程
PegasusYu12 小时前
STM32CUBEIDE FreeRTOS操作教程(九):eventgroup事件标志组
stm32·教程·rtos·stm32cubeide·free-rtos·eventgroup·时间标志组
lantiandianzi16 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
文弱书生65616 小时前
输出比较简介
stm32
哔哥哔特商务网16 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件
跟着杰哥学嵌入式16 小时前
单片机进阶硬件部分_day2_项目实践
单片机·嵌入式硬件