STM32-16-ADC

2024-06-07 00:04
文章标签 16 stm32 adc

本文主要是介绍STM32-16-ADC,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

STM32-01-认识单片机
STM32-02-基础知识
STM32-03-HAL库
STM32-04-时钟树
STM32-05-SYSTEM文件夹
STM32-06-GPIO
STM32-07-外部中断
STM32-08-串口
STM32-09-IWDG和WWDG
STM32-10-定时器
STM32-11-电容触摸按键
STM32-12-OLED模块
STM32-13-MPU
STM32-14-FSMC_LCD
STM32-15-DMA

文章目录

  • STM32-16-ADC
    • 1. ADC简介
    • 2. ADC工作原理
    • 3. 单通道ADC采集实验
        • 1. 相关寄存器
        • 2. 配置步骤
        • 3. 代码实现
    • 4. 单通道ADC采集(DMA读取)实验
    • 5. 多通道ADC采集(DMA读取)实验
    • 6. 单通道ADC过采样实验
    • 7. 内部温度传感器实验
    • 8. 光敏传感器实验

STM32-16-ADC

1. ADC简介

  • 什么是ADC:

    ADCAnalog-to-digital converter,即模拟/数字转换器,可以将外部的模拟信号转换为数字信号。STM32F103系列芯片拥有3ADC(C8T6只有2个),这些ADC可以独立使用,其中 ADC1和ADC2还可以组成双重模式提高采样率)。STM32的ADC是12逐次逼近型的模拟数字转换器。它有18个通道,可测量16个外部和2个内部信号源,其中ADC3根据CPU引脚的不同其通道数也不同,一般有8个外部通道。ADC中的各个通道的AD转换可以单次连续扫描间断模式执行。ADC的结果可以以左对齐或者右对齐存储在16数据寄存器中。
    在这里插入图片描述

    在嵌入式系统中,ADC通常用于采集传感器的数据,例如压力、温度、图像传感器等参数,被转换成电压后,通过ADC转换为数字量,供单片机处理,单片机根据处理结果进行相关控制和显示。

  • 主要特性:

    1. 12位分辨率
    2. 转换结束、注入转换结束和发生模拟看门狗时产生中断
    3. 单次和连续转换模式
    4. 自校准
    5. 带内嵌数据一致性的数据对齐
    6. 采样间隔可以按通道分别编程
    7. 规则转换和注入转换均有外部触发选项
    8. 间断模式
    9. 双重模式
    10. ADC转换时间:时钟为72MHz为1.17us
    11. ADC供电要求:2.4V到3.6V
    12. ADC输入范围:Vref-<=Vin<=Vref+
    13. 规则通道转换期间有DMA请求产生
  • 常见的ADC类型:

    ADC电路类型优点缺点
    并联比较型转换速度最快成本高、功耗高,分辨率低
    逐次逼近型结构简单,功耗低转换速度较慢

    并联比较型(Flash型)和逐次逼近型(SAR型)是两种常见的模数转换器(ADC)架构,它们在工作原理、速度、功耗、精度等方面有明显的区别。

    并联比较型ADC(Flash ADC)
    在这里插入图片描述

    工作原理

    • 并联比较型ADC使用一组并行的比较器,将输入模拟信号同时与参考电压分压网络进行比较。
    • 每个比较器对应一个特定的电压范围,输出一个二进制码。
    • 比较器的输出直接决定ADC的数字输出,无需逐步逼近。

    特点

    • 速度快:由于所有比较器同时工作,转换时间非常短,适合高频信号采样。
    • 高功耗:大量并行比较器和电阻网络消耗较多电能。
    • 高复杂度:电路复杂度随分辨率成指数增长,如8位ADC需要255个比较器。
    • 应用场景:高速数据采集、视频处理、通信系统等。

    逐次逼近型ADC(SAR ADC)
    在这里插入图片描述

    工作原理

    • 逐次逼近型ADC通过一个逐次逼近寄存器(SAR)和一个比较器工作。
    • 它逐步逼近输入模拟信号,从最高位到最低位,逐位进行比较。
    • 每次比较后,根据比较结果决定下一个比较的方向,直到所有位都确定。

    特点

    • 速度适中:转换速度适中,比Flash ADC慢,但比积分型ADC快。
    • 低功耗:由于只使用一个比较器,功耗较低。
    • 高分辨率:适合高分辨率应用,电路复杂度与分辨率成线性关系。
    • 应用场景:低功耗应用、音频采样、精密测量仪器等。
    特点并联比较型ADC逐次逼近型ADC
    转换速度非常快(纳秒级)适中(微秒级)
    功耗
    电路复杂度高(比较器数量多)适中(逐次逼近寄存器)
    分辨率适中(通常8位或以下)高(可达16位或以上)
    典型应用高速数据采集、视频处理音频采样、精密测量

    并联比较型ADC适合需要极高转换速度的应用,而逐次逼近型ADC则在功耗和分辨率之间提供了良好的平衡,适合多种精密测量中速数据采集的应用。

  • ADC的特性参数

    • 分辨率: 表示ADC能辨别的最小模拟量,用二进制位表示
    • 转换时间: 表示完成一次A/D转换所需要的时间,转换时间越短,采样率越高
    • 精度: 最小刻度基础上叠加各种误差的参数,精度受ADC性能、温度和气压等影响
    • 量化误差: 用数字量近似表示模拟量,采用四舍五入原则,此过程产生的误差为量化误差

2. ADC工作原理

ADC框图
在这里插入图片描述

  • ①输入电压
    在这里插入图片描述

  • ②输入通道

    确定好ADC输入电压后,通过ADC输入通道把外部电压输送到ADC转换器中,下面是ADC1~3对应的通道。
    在这里插入图片描述

  • ③转换顺序

    在STM32的ADC转换过程中,规则组和注入组是两种不同的ADC转换序列,它们在配置、使用场景和优先级等方面存在显著区别。

    规则组(Regular Group)

    定义和特点

    • 规则组是常规的ADC转换序列,通常用于连续的、周期性的ADC采样。
    • 可以包含多个通道,这些通道按照配置的顺序依次进行转换。
    • 规则组的转换可以由多种触发源启动,如软件触发、定时器触发等。

    应用场景

    • 常用于采集传感器数据定期采样信号等场景。
    • 适用于实时性要求较高的应用,但相对注入组来说,优先级较低。

    触发源

    • 支持多种触发源,包括软件触发和硬件触发(如定时器事件)。
    • 可设置为连续转换模式或单次转换模式。

    注入组(Injected Group)

    定义和特点

    • 注入组是为了在规则转换之外进行临时或优先转换的一种机制。
    • 通常包含少量通道,用于处理突发性或优先级更高的ADC采样需求。
    • 注入组转换有独立的触发源,与规则组转换互不干扰。

    应用场景

    • 常用于优先级更高的采样需求,如监测关键参数的变化快速响应事件等。
    • 注入组的转换结果可以通过中断DMA方式快速处理,适用于突发事件的处理。

    触发源

    • 注入组有独立的触发源,可以是外部事件、定时器触发等。
    • 通常由特定的事件触发,不会像规则组那样进行连续转换。

    总结:

    特点规则组(Regular Group)注入组(Injected Group)
    转换触发多种触发源(软件、定时器等)独立触发源(外部事件、定时器等)
    转换顺序配置的通道顺序配置的少量优先通道
    优先级相对较低相对较高
    应用场景定期采样、传感器数据采集关键参数监测、突发事件处理
    转换模式连续或单次转换通常为单次转换
    结果处理常规方式处理可通过中断或DMA快速处理

在这里插入图片描述

规则序列

规则组最多允许16个输入通道进行转换,那么就需要设置通道转换的顺序,即规则序列。

规则序列寄存器控制关系如下:
在这里插入图片描述

注入序列

注入组最大允许4个通道输入,它的注入序列由JSQR寄存器配置。

注入序列寄存器控制关系如下:
在这里插入图片描述

  • ④触发源
    在这里插入图片描述

    规则组外部触发使用方法是将 EXTTRIG 位置 1,并且通过 EXTSET[2:0]位选择规则组启动转换的触发源。如果 EXTSET[2:0]位设置为 111,那么可以通过 SWSTART 为启动 ADC 转换, 相当于软件触发。

    注入组外部触发使用方法是将 JEXTTRIG 位置 1,并且通过 JEXTSET[2:0]位选择注入组启动转换的触发源。如果 JEXTSET[2:0]位设置为 111,那么可以通过 JSWSTART 为启动 ADC 转换,相当于软件触发。
    在这里插入图片描述

  • ⑤转换时间

    ADC的输入时钟是由PCLK2经过分频产生的,分频系数是由RCC_CFGR寄存器的ADCPRE[1:0]位设置的,可选择 2/4/8/16 分频。需要注意的是,ADC 的输入时钟频率最大值是14MHz,如果超过这个值将会导致 ADC 的转换结果准确度下降。 一般我们设置 PCLK272MHz。为了不超过 ADC 的最大输入时钟频率 14MHz,我们设置 ADC 的预分频器分频系数为 6,就可以得到 ADC 的输入时钟频率为 72MHz/6,即 12MHz
    在这里插入图片描述

在这里插入图片描述

  • ⑥数据寄存器
    在这里插入图片描述

    ADC 规则数据寄存器(ADC_DR)

    ADC规则组数据寄存器ADC_DR是一个32位的寄存器,独立模式时只使用到该寄存器低16位保存ADC1/2/3的规则转换数据。在双ADC模式下,高16位用于保存ADC2转换的数据,低16位用于保存ADC1转换的数据。

    因为ADC的精度是12位的,ADC_DR寄存器无论高16位还是低16w位,存放数据的位宽都是16位的,所以允许选择数据对齐方式。由ADC_CR2寄存器的ALIGN位设置数据对齐方式,可选择:右对齐或者左对齐

    规则组16个输入通道只对应一个数据寄存器,所以通道转换完成后要及时取出数据,比较常用的方法是使用DMA模式。当规则组的转换结束后,就会产生DMA请求,这样就可以把数据及时搬运到用户指定的目的地址存放。只有ADC1和ADC3可以产生DMA请求,而ADC2转换的数据可以通过双ADC模式,利用ADC1的DMA功能传输。

    ADC 注入数据寄存器x(ADC_JDRx)(x=1~4)

    ADC注入数据寄存器有4个,注入组最多有4个输入通道,刚好每个通道都有自己对应的数据寄存器。ADC_JDRx寄存器是32位的,低16位有效,高16位保留,数据也同样需要选择对齐方式。也是由ADC _CR2寄存器的ALIGN位设置数据对齐方式,可选择:右对齐或者左对齐。

  • ⑦中断

    中断事件事件标志使能控制位
    规则通道转换结束EOCEOCIE
    注入通道转换结束JEOCJEOCIE
    设置了模拟看门狗状态位AWDAWDIE

    规则组和注入组的转换结束后,除了可以产生中断外,还可以产生 DMA 请求,我们利用DMA 及时把转换好的数据传输到指定的内存里,防止数据被覆盖。

  • ⑧单次转换模式和连续转换模式
    在这里插入图片描述

  • ⑨扫描模式
    在这里插入图片描述

3. 单通道ADC采集实验

1. 相关寄存器
  • ADC控制寄存器1(ADC_CR1)
    在这里插入图片描述

  • ADC控制寄存器2(ADC_CR2)
    在这里插入图片描述

  • ADC采样事件寄存器1(ADC_SMPR1)
    在这里插入图片描述

  • ADC采集事件寄存器2(ADC_SMPR2)
    在这里插入图片描述

    ADC 采样时间设置需要由两个寄存器设置,ADC_SMPR1ADC_SMPR2,分别设置通道 10~17 和通道 0~9 的采样时间,每个通道用 3 个位设置。

  • ADC规则序列寄存器1(ADC_SQR1)
    在这里插入图片描述

    ADC_SQR2
    在这里插入图片描述

    ADC_SQR3
    在这里插入图片描述

    L[3:0]用于设置规则组序列的长度,取值范围:015,表示规则组的长度是116。本实验只用了1个输入通道,所以L[3:0]位设置为0000即可。

    SQ13[4:0]~SQ16[4:0]位设置规则组序列的第 13~16 个转换编号,第 1~12 个转换编号的设置请查看 ADC_SQR2 和 ADC_SQR3 寄存器。

  • ADC规则数据寄存器(ADC_DR)
    在这里插入图片描述

  • ADC状态寄存器(ADC_SR)
    在这里插入图片描述

2. 配置步骤
  1. 配置ADC工作参数、ADC校准

    HAL_ADC_Init()
    HAL_ADCEx_Calibration_Start()
    
  2. ADC MSP初始化

    HAL_ADC_MspInit()     
    
  3. 配置ADC相应通道相关参数

    HAL_ADC_ConfigChannel()
    
  4. 启动A/D转换

    HAL_ADC_Start()
    
  5. 等待规则通道转换完成

    HAL_ADC_PollForConversion()
    
  6. 获取规则通道A/D转换结果

    HAL_ADC_GetValue()
    
  • 具体函数功能

    函数主要寄存器主要功能
    HAL_ADC_Init()CR1、CR2配置ADC工作参数
    HAL_ADCEx_Calibration_Start()CR2ADC校准
    HAL_ADC_MspInit()存放NVIC、CLOCK、GPIO初始化代码
    HAL_RCCEx_PeriphCLKConfig()RCC_CFGR设置扩展外设时钟,如:ADC、RTC等
    HAL_ADC_ConfigChannel()SQRx、SMPRx配置ADC相应通道的相关参数
    HAL_ADC_Start()CR2启动A/D转换
    HAL_ADC_PollForConversion()SR等待规则通道转换完成
    HAL_ADC_GetValue()DR获取规则通道A/D转换结果
3. 代码实现
  • 功能: 采集ADC1通道1(PA1)上面的电压,并在LCD模块上面显示ADC规则数据寄存器12位的转换值以及将该值换算成电压后的电压值。使用杜邦线将ADC和RV1排针连接,使得PA1连接到电位器上,然后将ADC采集到的数据和转换后的电压值在TFTLCD屏中显示。用户可以通过调节电位器的旋钮改变电压值。LED0闪烁,提示程序运行。

  • ADC单通道初始化函数

    void adc_init(void)
    {ADC_ChannelConfTypeDef adc_ch_conf;g_adc_handle.Instance = ADC1;                             //选择ADC1g_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;                    //通道数选择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);   //进行初始化HAL_ADCEx_Calibration_Start(&g_adc_handle);   //校准ADCadc_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);    //通道配置
    }
    

    g_adc_handle.Instance = ADC1;: 选择ADC1作为转换的ADC模块。

    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;: 设置间断模式的规则通道数为0。

    g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;: 设置软件触发转换。

    adc_ch_conf.Channel = ADC_CHANNEL_1;: 选择ADC通道1。

    adc_ch_conf.Rank = ADC_REGULAR_RANK_1;: 设置通道1为规则组的第一个转换通道。

    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;: 采样时间为239.5个ADC时钟周期。

    STM32的ADC1模块进行初始化和通道配置,为后续的ADC数据采集做好准备。具体来说,初始化配置包括设置数据对齐方式、转换模式和触发方式等。通过校准确保ADC转换的精度,然后配置一个规则组通道,使得ADC可以在指定通道上进行采样并转换。

  • 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的引脚1为模拟输入模式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外设时钟adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;//分频因子6,时钟为72M/6=12MHz  HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);  //设置ADC时钟}
    }
    

    配置ADC1的时钟源和分频系数。这里选择了RCC的ADC外设时钟,并将ADC的时钟频率设置为系统时钟(72MHz)除以6,即12MHz。这有助于确保ADC1以适当的频率运行,从而保证其性能和精度。

  • 启动ADC并获取转换结果函数

    uint32_t adc_get_result(void)
    {// 启动A/D转换HAL_ADC_Start(&g_adc_handle);   // 开启ADC// 等待规则通道转换完成HAL_ADC_PollForConversion(&g_adc_handle, 10);   // 轮询转换,等待完成// 获取规则通道A/D转换结果return (uint16_t)HAL_ADC_GetValue(&g_adc_handle);  // 返回最近一次规则组的转换结果
    }
    
    1. 启动A/D转换:

      HAL_ADC_Start(&g_adc_handle);
      

      g_adc_handle 是先前定义的并初始化好的 ADC 句柄。此操作会开始对选定的ADC通道进行模数转换。

    2. 等待规则通道转换完成:

      HAL_ADC_PollForConversion(&g_adc_handle, 10);
      

      调用 HAL_ADC_PollForConversion 函数进行轮询,等待转换完成。这里设置了一个超时值为10个时钟周期。如果在超时时间内转换完成,函数会返回并继续执行;否则,它会阻塞直到超时或者转换完成。

    3. 获取规则通道A/D转换结果:

      return (uint16_t)HAL_ADC_GetValue(&g_adc_handle);
      

      调用 HAL_ADC_GetValue 函数获取ADC转换结果。返回的值是最近一次转换的结果,类型为 uint16_t,因为一般ADC的结果是16位。

  • 主函数

    int main(void)
    {uint16_t adcx; //ADC换算后的数字量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 */oled_init();                        //OLED初始化adc_init();lcd_show_string(110, 50, 240, 16, 32, "STM32", GREEN);lcd_show_string(105, 90, 240, 16, 24, "ADC TEST", GREEN);lcd_show_string(30, 150, 240, 16, 24, "CH1_VAL:", BLUE);lcd_show_string(30, 180, 240, 16, 24, "CH1_VOL:0.000V", BLUE);oled_show_string(10,0,"ADC TEST",24);oled_show_string(0, 25, "CH1_VAL:", 16);oled_show_string(0, 41, "CH1_VOL:0.000V", 16);oled_refresh_gram();while (1){adcx = adc_get_result();  //获取ADC转换后的结果lcd_show_xnum(126, 150, adcx, 5, 24, 0, BLUE);oled_show_num(65, 25, adcx, 5, 16);//在LCD和OLED上显示电压值整数部分temp = (float)adcx * (3.3 / 4096);adcx = temp;lcd_show_xnum(126, 180, adcx, 1, 24, 0, BLUE);oled_show_num(64, 41, adcx, 1, 16);//在LCD和OLED上显示电压值小数部分temp -= adcx;temp *= 1000;lcd_show_xnum(150, 180, temp, 3, 24, 0x80, BLUE);oled_show_num(80, 41, temp, 3, 16);LED0_TOGGLE(); delay_ms(100);oled_refresh_gram();}
    }
    

    实验结果:

    作用

    • 实现ADC转换,并将结果显示在LCD和OLED上,实时显示ADC通道的数值和对应的电压值。

    影响

    • 可以实时监控ADC通道的输入电压,适用于需要监测模拟信号的应用,如传感器数据读取。
    • 通过LED的切换,可以直观地知道系统是否在运行。
    • 延时和轮询会占用CPU资源,对实时性有一定影响,如果有更高的实时性要求,可以考虑使用中断或DMA。

4. 单通道ADC采集(DMA读取)实验

  • 功能: 使用 ADC 采集(DMA 读取)通道 1(PA1)上面的电压,在 LCD 和OLED模块上面显示 ADC 转换值以及换算成电压后的电压值。使用短路帽将 ADC 和 RV1 排针连接,使得 PA1 连接到电位器上,然后将 ADC 采集到的数据和转换后的电压值在 TFTLCD 屏中显示。用户可以通过调节电位器的旋钮改变电压值。LED0 闪烁,提示程序运行。

  • ADC_DMA初始化函数

    void adc_dma_init(uint32_t mar)
    {ADC_ChannelConfTypeDef adc_ch_conf = {0};__HAL_RCC_DMA1_CLK_ENABLE();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);//2.将DMA和ADC句柄连接起来__HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle);g_adc_dma_handle.Instance = ADC1;                             //选择ADC1g_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;                    //通道数选择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);   //进行初始化HAL_ADCEx_Calibration_Start(&g_adc_dma_handle);   //校准ADCadc_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);    //通道配置HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);HAL_ADC_Start_DMA(&g_adc_dma_handle, &mar, 0);
    }
    

    总结:

    1. 启用DMA1时钟
    __HAL_RCC_DMA1_CLK_ENABLE();
    
    • 使能DMA1的时钟,确保DMA1能够正常工作。
    1. 配置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; // 外设数据对齐为16位
    g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; // 内存数据对齐为16位
    g_dma_adc_handle.Init.Mode = DMA_NORMAL;                    // 普通模式
    g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;       // 优先级为中等
    HAL_DMA_Init(&g_dma_adc_handle);
    
    • 初始化DMA1的通道1,配置DMA的数据传输方向地址递增模式数据对齐方式传输模式优先级
    1. 将DMA和ADC句柄连接起来
    __HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle);
    
    • 将DMA句柄与ADC句柄连接,使ADC能够使用DMA传输数据。
    1. 初始化ADC
    g_adc_dma_handle.Instance = ADC1;                            // 选择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);
    
    • 配置并初始化ADC1,包括数据对齐方式扫描模式连续转换模式转换通道数间断模式触发方式
    1. 校准ADC
    HAL_ADCEx_Calibration_Start(&g_adc_dma_handle);
    
    • 启动ADC的校准,以提高转换精度。
    1. 配置ADC通道
    adc_ch_conf.Channel = ADC_CHANNEL_1;                       // 通道1
    adc_ch_conf.Rank = ADC_REGULAR_RANK_1;                     // 规则组第1个转换
    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;     // 采样时间
    HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf);
    
    • 配置ADC的输入通道转换序列采样时间
    1. 配置DMA中断优先级并启用中断
    HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);
    HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
    
    • 设置DMA1通道1中断的优先级,并启用中断。
    1. 启动DMA传输和ADC DMA模式
    HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);
    HAL_ADC_Start_DMA(&g_adc_dma_handle, &mar, 0);
    
    • 启动DMA传输,将ADC数据寄存器的地址作为源地址,mar作为目标地址,并指定传输数据量。
    • 启动ADC的DMA模式,使得ADC转换完成后通过DMA传输数据。

    代码的功能:

    • 使能DMA和ADC时钟:确保DMA和ADC正常工作。
    • 配置DMA:设置DMA传输的方向、地址递增模式、数据对齐方式、传输模式和优先级。
    • 初始化ADC:配置ADC的工作模式、转换方式和触发方式。
    • 校准ADC:提高ADC转换的精度。
    • 配置ADC通道:指定ADC的输入通道和采样时间。
    • 配置中断:设置DMA中断的优先级并启用中断,以便在数据传输完成后处理中断。
    • 启动DMA传输和ADC DMA模式:实现ADC转换结果通过DMA自动传输到指定内存地址,提高数据传输效率和系统性能。

    对程序的影响:

    • 提高数据传输效率:使用DMA自动传输数据,减少CPU的负担。
    • 自动化数据采集:通过DMA实现ADC数据的自动传输,无需手动处理数据搬运。
    • 实时数据处理:配置中断处理机制,可以在数据传输完成后立即处理,提高系统的实时性和响应速度。
  • ADC MSP初始化函数

    void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
    {if(hadc->Instance == ADC1){GPIO_InitTypeDef gpio_init_struct;RCC_PeriphCLKInitTypeDef adc_clk_init = {0};//使能时钟__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_ADC1_CLK_ENABLE();//配置工作模式gpio_init_struct.Pin = GPIO_PIN_1;gpio_init_struct.Mode = GPIO_MODE_ANALOG;  //模拟输入HAL_GPIO_Init(GPIOA, &gpio_init_struct);adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;  //ADC外设时钟adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;     //分频因子6,时钟为72M/6=12MHz  HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);  //设置ADC时钟}
    }
    
  • 使能一次ADC DMA传输函数

    void adc_dma_enable(uint16_t cndtr)
    {ADC1->CR2 &= ~(1 << 0);  //CONT位置1,关闭连续转换模式DMA1_Channel1->CCR &= ~(1 << 0);      //禁用DMA通道1while(DMA1_Channel1->CCR & (1 << 0)); //等待DMA通道1禁用完成DMA1_Channel1->CNDTR = cndtr;         //设置DMA传输数据量DMA1_Channel1->CCR |= 1 << 0;         //启用DMA通道1ADC1->CR2 |= 1 << 0;   //开启ADC1ADC1->CR2 |= 1 << 22;  //启用ADC1的DMA请求
    }
    

    总结:

    1. 关闭ADC连续转换模式

      ADC1->CR2 &= ~(1 << 0);
      
    • 清除ADC1控制寄存器2(CR2)的第0位,关闭连续转换模式。
    1. 禁用DMA通道1

      DMA1_Channel1->CCR &= ~(1 << 0);
      
      • 清除DMA1通道1控制寄存器(CCR)的第0位,禁用DMA通道1。
    2. 等待DMA通道1禁用完成

      while(DMA1_Channel1->CCR & (1 << 0));
      
      • 使用while循环等待,直到DMA1通道1控制寄存器的第0位被清除,确保DMA通道1已完全禁用。
    3. 设置DMA传输数据量

      DMA1_Channel1->CNDTR = cndtr;
      
      • 将传入的参数 cndtr 赋值给DMA1通道1的数据传输数量寄存器(CNDTR),指定DMA传输的数据量。
    4. 启用DMA通道1

      DMA1_Channel1->CCR |= 1 << 0;
      
      • 设置DMA1通道1控制寄存器的第0位,启用DMA通道1。
    5. 开启ADC1

      ADC1->CR2 |= 1 << 0;
      
    • 设置ADC1控制寄存器2(CR2)的第0位,开启ADC1。
    1. 启用ADC1的DMA请求
      ADC1->CR2 |= 1 << 22;
      
      • 设置ADC1控制寄存器2(CR2)的第22位,启用ADC1的DMA请求。这样,ADC转换完成后将自动通过DMA将数据传输到指定的存储位置。

    作用:

    • 启用DMA:通过DMA自动传输ADC转换的数据,减少CPU的负担,提高数据传输效率。
    • 启用ADC连续转换模式:让ADC能够连续进行多次转换,而无需每次都启动转换。-
  • ADC DMA采集中断服务函数

    void DMA1_Channel1_IRQHandler(void)
    {if(DMA1->ISR & (1<<1)){g_adc_dma_sta = 1;//清除中断标志位DMA1->IFCR |= (1 << 1);}}
    

    总结:

    1. 中断处理

      • 该函数是DMA1通道1的中断处理函数。当DMA1通道1触发中断时,系统会调用这个函数。
    2. 检查中断标志

      • if(DMA1->ISR & (1<<1)):检查DMA1的中断状态寄存器(ISR)的第1位是否被置位。这一位表示DMA1通道1的传输完成中断。如果该位被置位,说明DMA1通道1完成了数据传输。
    3. 设置状态标志

      • g_adc_dma_sta = 1;:设置一个全局标志位 g_adc_dma_sta,表示DMA传输已完成。这个标志位可以在主程序或者其他地方用来判断DMA传输状态。
    4. 清除中断标志

      • DMA1->IFCR |= (1 << 1);:清除DMA1通道1的传输完成中断标志位。这一步是必须的,否则中断处理程序会再次触发。

    作用总结:

    • 响应DMA传输完成中断:当DMA1通道1完成数据传输时,会触发中断。
    • 设置传输完成标志:通过设置 g_adc_dma_sta,主程序或其他代码可以检测到DMA传输已完成,并进行后续处理。
    • 清除中断标志位:清除中断标志位,以便系统能处理下一次DMA传输完成中断。
  • 主函数

    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 */oled_init();adc_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */oled_show_string(10,0,"ADC TEST",24);oled_show_string(0, 25, "CH1_VAL:", 16);oled_show_string(0, 41, "CH1_VOL:0.000V", 16);oled_refresh_gram();lcd_show_string(110, 50, 240, 16, 32, "STM32", GREEN);lcd_show_string(105, 90, 240, 16, 24, "ADC TEST", GREEN);lcd_show_string(30, 150, 240, 16, 24, "CH1_VAL:", BLUE);lcd_show_string(30, 180, 240, 16, 24, "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];printf("%d\r\n", sum);}adcx = sum / ADC_DMA_BUF_SIZE;           /* 取平均值 *//* 显示结果 */lcd_show_xnum(126, 150, adcx, 5, 24, 0, BLUE);     /* 显示ADCC采样后的原始值 */oled_show_num(65, 25, adcx, 5, 16);temp = (float)adcx * (3.3 / 4096);                  /* 获取计算后的带小数的实际电压值,比如3.1111 */adcx = temp;                                        /* 赋值整数部分给adcx变量,因为adcx为u16整形 */lcd_show_xnum(126, 180, adcx, 1, 24, 0, BLUE);     /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */oled_show_num(64, 41, adcx, 1, 16);temp -= adcx;                                       /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */temp *= 1000;                                       /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */lcd_show_xnum(150, 180, temp, 3, 24, 0x80, BLUE);  /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */oled_show_num(80, 41, temp, 3, 16);g_adc_dma_sta = 0;                                  /* 清除DMA采集完成状态标志 */adc_dma_enable(ADC_DMA_BUF_SIZE);                   /* 启动下一次ADC DMA采集 */}LED0_TOGGLE();delay_ms(100);oled_refresh_gram();}
    }
    

    总结:

    adc_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
    

    初始化ADC和DMA,使得ADC可以通过DMA方式进行数据采集,采集的数据存储在 g_adc_dma_buf 缓冲区中。

    adc_dma_enable(ADC_DMA_BUF_SIZE);   /* 启动ADC DMA采集 */
    

    启动ADC DMA采集,使得ADC开始连续采集数据并通过DMA传输到缓冲区 g_adc_dma_buf

    代码通过初始化和配置STM32的各种外设,实现了ADC数据的DMA采集,并在LCD和OLED上显示采集到的ADC数据和转换后的电压值。同时,通过中断和DMA机制实现了高效的数据采集和处理。

    什么时候采集,什么时候传输

    ADC采集的控制

    • ADC的连续转换模式被启用(g_adc_dma_handle.Init.ContinuousConvMode = ENABLE;)。这意味着ADC会连续地进行转换。
    • 但是,ADC的转换触发和数据读取是通过DMA控制的。当你调用adc_dma_enable函数时,ADC和DMA都被启动,并开始进行数据采集和传输。

    DMA传输的控制

    • 当你停止DMA传输(例如,通过禁用DMA通道),ADC可能会继续进行转换,但是这些转换结果不会通过DMA传输到内存。这是因为DMA传输是负责将ADC转换结果传输到指定的内存缓冲区(g_adc_dma_buf)。
    • 如果DMA被禁用,但ADC继续进行转换,结果仍会存储在ADC的数据寄存器(DR)中,但不会被传输到内存。

5. 多通道ADC采集(DMA读取)实验

  • 功能:使用ADC1采集(DMA读取)通道1/2/3/4/5/6的电压,在LCD模块上面显示对应的ADC转换值以及换算成电压后的电压值。可以使用杜邦线连接PA0/PA1/PA2/PA3/PA4/PA5到你想测量的电压源(0~3.3V),然后通过TFTLCD显示的电压值。LED0闪烁,提示程序运行。

  • 多通道ADC初始化函数

    void adc_nch_dma_init(uint32_t mar)
    {ADC_ChannelConfTypeDef adc_ch_conf = {0};__HAL_RCC_DMA1_CLK_ENABLE();   //使能DMA1时钟g_dma_adc_handle.Instance = DMA1_Channel1;   //初始化DMA1的通道1g_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);//2.将DMA和ADC句柄连接起来__HAL_LINKDMA(&g_adc_nch_dma_handle, DMA_Handle, g_dma_adc_handle);g_adc_nch_dma_handle.Instance = ADC1;                             //选择ADC1g_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;                    //通道数选择1个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);   //进行初始化HAL_ADCEx_Calibration_Start(&g_adc_nch_dma_handle);   //校准ADCadc_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);    //通道配置adc_ch_conf.Channel = ADC_CHANNEL_1;                   //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_2;                 //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);    //通道配置adc_ch_conf.Channel = ADC_CHANNEL_2;                   //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_3;                 //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);    //通道配置adc_ch_conf.Channel = ADC_CHANNEL_3;                   //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_4;                 //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);    //通道配置adc_ch_conf.Channel = ADC_CHANNEL_4;                   //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_5;                 //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);    //通道配置adc_ch_conf.Channel = ADC_CHANNEL_5;                   //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_6;                 //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);    //通道配置HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, &mar, 0);
    }
    

    多通道ADC转换与单通道ADC转换的区别:

    DMA的相关配置不需要改动;

    ADC的配置要使能扫描模式通道数设置为6个

    g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE;    //打开连续转换模式
    g_adc_nch_dma_handle.Init.NbrOfConversion = 6;            //通道数选择6个
    

    设置6个通道序列:

    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);    //通道配置adc_ch_conf.Channel = ADC_CHANNEL_1;                   //输入通道
    adc_ch_conf.Rank = ADC_REGULAR_RANK_2;                 //转换序列
    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);    //通道配置adc_ch_conf.Channel = ADC_CHANNEL_2;                   //输入通道
    adc_ch_conf.Rank = ADC_REGULAR_RANK_3;                 //转换序列
    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);    //通道配置adc_ch_conf.Channel = ADC_CHANNEL_3;                   //输入通道
    adc_ch_conf.Rank = ADC_REGULAR_RANK_4;                 //转换序列
    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);    //通道配置adc_ch_conf.Channel = ADC_CHANNEL_4;                   //输入通道
    adc_ch_conf.Rank = ADC_REGULAR_RANK_5;                 //转换序列
    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);    //通道配置adc_ch_conf.Channel = ADC_CHANNEL_5;                   //输入通道
    adc_ch_conf.Rank = ADC_REGULAR_RANK_6;                 //转换序列
    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //采样时间
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_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};//使能时钟__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_ADC1_CLK_ENABLE();//配置工作模式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_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;  //ADC外设时钟adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;     //分频因子6,时钟为72M/6=12MHz  HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);  //设置ADC时钟}
    }
    
  • 使能一次ADC DMA传输函数

    void adc_dma_enable(uint16_t cndtr)
    {ADC1->CR2 &= ~(1 << 0);  //CONT位置1,关闭连续转换模式DMA1_Channel1->CCR &= ~(1 << 0);      //禁用DMA通道1while(DMA1_Channel1->CCR & (1 << 0)); //等待DMA通道1禁用完成DMA1_Channel1->CNDTR = cndtr;         //设置DMA传输数据量DMA1_Channel1->CCR |= 1 << 0;         //启用DMA通道1ADC1->CR2 |= 1 << 0;   //开启ADC1ADC1->CR2 |= 1 << 22;  //启用ADC1的DMA请求
    }
    
  • ADC DMA采集中断服务函数

    void DMA1_Channel1_IRQHandler(void)
    {if(DMA1->ISR & (1<<1)){g_adc_dma_sta = 1;//清除中断标志位DMA1->IFCR |= (1 << 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);}
    }
    

6. 单通道ADC过采样实验

  • 功能:使用ADC1通道1(PA1),通过软件方式实现16位分辨率采集外部电压,并在LCD模块上面显示对应的ADC转换值以及换算成电压后的电压值。可以使用杜邦线连接PA1到你想测量的电压源(0~3.3V),然后通过TFTLCD显示的电压值。LED0闪烁,提示程序运行。

  • 单通道ADC初始化函数

    void adc_dma_init(uint32_t mar)
    {ADC_ChannelConfTypeDef adc_ch_conf = {0};__HAL_RCC_DMA1_CLK_ENABLE();   //使能DMA1时钟g_dma_adc_handle.Instance = DMA1_Channel1;   //初始化DMA1的通道1g_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);//2.将DMA和ADC句柄连接起来__HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle);g_adc_dma_handle.Instance = ADC1;                             //选择ADC1g_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;                    //通道数选择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);   //进行初始化HAL_ADCEx_Calibration_Start(&g_adc_dma_handle);   //校准ADCadc_ch_conf.Channel = ADC_CHANNEL_1;                   //输入通道adc_ch_conf.Rank = ADC_REGULAR_RANK_1;                 //转换序列adc_ch_conf.SamplingTime = ADC_SAMPLETIME_1CYCLE_5; //采样时间HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf);    //通道配置HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);HAL_ADC_Start_DMA(&g_adc_dma_handle, &mar, 0);
    }
    

    与前面的实验相比,此函数只需要修改采样时间即可

    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
    
  • 主函数循环代码

    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采样精度。

    提高精度的原理

    1. 增加采样次数

      通过增加采样次数来进行过采样。过采样可以降低噪声,提高信噪比,从而提高分辨率。将多个采样值相加并取平均值,可以增加有效位数。

    2. 移位操作

      通过移位操作,将结果右移,以提高分辨率。增加N位精度,需要对累加后的结果右移N位。

7. 内部温度传感器实验

  • 功能: 通过ADC的通道16读取STM32F103内部温度传感器的电压值,并将其转换为温度值,显示在TFTLCD屏上。LED0闪烁用于提示程序正在运行。

  • 温度计算方法:
    在这里插入图片描述

  • ADC内部温度传感器初始化函数

    void adc_temperature_init(void)
    {ADC_ChannelConfTypeDef adc_ch_conf;g_adc_handle.Instance = ADC1;                             //选择ADC1g_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;                    //通道数选择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);   //进行初始化HAL_ADCEx_Calibration_Start(&g_adc_handle);   //校准ADCadc_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};//使能时钟__HAL_RCC_ADC1_CLK_ENABLE();adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;  //ADC外设时钟adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;     //分频因子6,时钟为72M/6=12MHz  HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);  //设置ADC时钟}
    }
    
  • 使能一次ADC DMA传输函数

    uint32_t adc_get_result()
    {//4.启动A/D转换HAL_ADC_Start(&g_adc_handle);   //开启ADC//5.等待规则通道转换完成HAL_ADC_PollForConversion(&g_adc_handle, 10);   //轮询转换//6.获取规则通道A/D转换结果return (uint16_t)HAL_ADC_GetValue(&g_adc_handle);  //返回最近一次规则组的转换结果
    }
    
  • 获取内部温度传感器的结果函数

    short adc_get_temperature(void)
    {uint32_t adcx;adcx = adc_get_result();short result;double temperature;temperature = adcx * (3.3 / 4096);temperature = (1.43 - temperature) / 0.0043 + 25;result = temperature * 100;return result;
    }
    
  • 主函数

    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);}
    }
    

8. 光敏传感器实验

声明:资料来源(战舰STM32F103ZET6开发板资源包)

  1. Cortex-M3权威指南(中文).pdf
  2. STM32F10xxx参考手册_V10(中文版).pdf
  3. STM32F103 战舰开发指南V1.3.pdf
  4. STM32F103ZET6(中文版).pdf
  5. 战舰V4 硬件参考手册_V1.0.pdf

这篇关于STM32-16-ADC的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1037612

相关文章

零基础STM32单片机编程入门(一)初识STM32单片机

文章目录 一.概要二.单片机型号命名规则三.STM32F103系统架构四.STM32F103C8T6单片机启动流程五.STM32F103C8T6单片机主要外设资源六.编程过程中芯片数据手册的作用1.单片机外设资源情况2.STM32单片机内部框图3.STM32单片机管脚图4.STM32单片机每个管脚可配功能5.单片机功耗数据6.FALSH编程时间,擦写次数7.I/O高低电平电压表格8.外设接口

16.Spring前世今生与Spring编程思想

1.1.课程目标 1、通过对本章内容的学习,可以掌握Spring的基本架构及各子模块之间的依赖关系。 2、 了解Spring的发展历史,启发思维。 3、 对 Spring形成一个整体的认识,为之后的深入学习做铺垫。 4、 通过对本章内容的学习,可以了解Spring版本升级的规律,从而应用到自己的系统升级版本命名。 5、Spring编程思想总结。 1.2.内容定位 Spring使用经验

【Qt6.3 基础教程 16】 掌握Qt中的时间和日期:QTimer和QDateTime的高效应用

文章目录 前言QTimer:定时任务的强大工具QTimer的基本用法高级特性:单次定时器 QDateTime:处理日期和时间获取当前日期和时间日期和时间的格式化输出日期和时间计算 用例:创建一个倒计时应用结论 前言 在开发桌面应用程序时,处理时间和日期是一个常见且重要的任务。Qt框架提供了强大的工具来处理与时间相关的功能,其中QTimer和QDateTime是最核心的类。本

STM32单片机PWR电源控制详解

文章目录 1. PWR概述 2. 电源结构框图 3. 上电复位和掉电复位 4. 可编程电压监测器 5. 低功耗模式 6. 模式选择 6.1 睡眠模式 6.2 停止模式 6.3 待机模式 7. 代码示例 1. PWR概述 PWR(Power Control)电源控制,负责管理STM32内部的电源供电部分,可以实现可编程电压监测器和低功耗模式的功能。 可编程电压监测器

STM32学习之一:什么是STM32

目录 1.什么是STM32 2.STM32命名规则 3.STM32外设资源 4. STM32的系统架构 5. 从0到1搭建一个STM32工程 学习stm32已经很久了,因为种种原因,也有很久一段时间没接触过stm32了。等我捡起来的时候,发现很多都已经忘记了,重新捡起来吧。 每次谈到stm32如何如何,那么该如何解释什么是stm32呢? 1.什么是STM32 stm32

Linux float int和16进制互相转换

Linux 上float int和16进制互换操作。之前把float转16进制,也就是转成4个字节,方便使用串口传输嘛。使用的方法是: //float 转 16进制float x_pid_p = 15.0;unsigned char * bValue = (unsigned char *)& x_pid_p;printf("%x\t%x\t%x\t%x\n", bValue[0], bVa

STM32G030F6使用CubeMx配置DMA读取多通道ADC实验

1. 使用 CubeMx 创建 ADC 工程 打开 CubeMx 软件,选中我们此次使用的单片机型号 STM32G030F6P6 ,点击 StartProject. 先配置一下串口,用来打印相关信息 再来配置 ADC 配置DMA PS:DMA 需要要配置成循环模式,否则只填满一次缓存数组后就停止工作,需要重调用启动 DMA 的函数. 配置时钟 ps:本实验使用内部高速时钟

STM32学习 修改系统主频

前面时钟树的学习说明单片机的主频是可以修改的,那么怎么更改系统的主频,这里做一个简单的介绍。首先要明白,单片机的程序是如何运行,这里简单说明一下。 对应的代码在startup_stm32....文件里面,这里是复位程序的汇编代码。 复位子程序是系统上电后第一个执行的程序,调用 SystemInit 函数初始化系统时钟,然后调用 C 库函数 _mian,最终调用 main 函数去到 C

使用J-Link Commander查找STM32死机问题

接口:PA13,PA14,请勿连接复位引脚。 输入usb命令 这里我已经连接过了STM32F407VET6了。 再输入connect命令 这里我已经默认选择了SWD接口,4000K速率。 可以输入speed 4000命令选择4000K速率: 写一段崩溃代码进行测试: void CashCode(void){*((volatile uint32_t*) 0x080FFFFF)

2021-02-16物料档案条码添加和蓝牙条码标签打印,金蝶安卓盘点机PDA,金蝶仓库条码管理WMS系统

物料档案条码添加和蓝牙条码标签打印,金蝶安卓盘点机PDA https://member.bilibili.com/platform/upload-manager/article 本期视频我们来讲解一下汉点机PDA条码添加和条码标签蓝牙便携打印: 在实际使用中,我们商品有两种情况: 一种是商品本身就有条码, 比如:超市卖的可口可乐,牛奶等商品,商品本身就有69开头的国标码,那么我们就可以使用盘点