你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32H7的ADC应用之DMA方式多通道采样

[复制链接]
STMCU小助手 发布时间:2021-10-26 15:51
46.1 初学者重要提示
  学习本章节前,务必优先学习第44章,需要对ADC的基础知识和HAL库的几个常用API有个认识。
  开发板右上角有个跳线帽,可以让ADC的稳压基准接3.3V或者2.5V,本章例子是接到3.3V。
  STM32H7的ADC支持偏移校准和线性度校准。
  STM32H7的ADC多通道并不是同步采样的,本质上是通过内部的多路选择器不断切换实现的,一个采集完毕了才会采集另一个。
46.2 ADC稳压基准硬件设计
注:学习前务必优先看第14章的2.1小节,对电源供电框架有个了解。


ADC要采集的准确,就需要有一个稳定的稳压基准源,V7开发板使用的LM285D-2.5,即2.5V的基准源。硬件设计如下:
aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png



关于这个原理图要注意以下问题:


LM285D-2.5输出的是2.5V的稳压基准,原理图这里做了一个特别的处理,同时接了一个上拉电阻到VDDA(3.3V),然后用户可以使用开发板右上角的跳线帽设置Vref选择3.3V稳压还是2.5V稳压。


aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png




下面再来了解下LM285的电气特性:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png




通过这个表,我们要了解以下几点知识:


  LM285的典型值是2.5V,支持的最小值2.462V,最大值2.538V。工作电流是20uA到20mA,温飘是±20ppm/℃
  Iz是Reference current参考电流的意思:
  参考电流是20uA到1mA,温度25℃,参考电压最大变化1mV。
  参考电流是20uA到1mA,全范围温度(−40°C to 85°C),参考电压最大变化1.5mV。
  参考电流是1mA到20mA,温度25℃,参考电压最大变化10mV。
  参考电流是1mA到20mA,全范围温度(−40°C to 85°C),参考电压最大变化30mV。



那么问题来了,V7开发板上LM285的参考电流是多少? 简单计算就是:


(VDDA – 2.5V) /  1K  =(3.3 – 2.5V) / 1K = 0.8mA。


46.3 ADC驱动设计
ADC做DMA数据传输的实现思路框图如下:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png




下面将程序设计中的相关问题逐一为大家做个说明。


46.3.1 ADC软件触发  
ADC转换既可以选择外部触发也可以选择软件触发。我们这里选择的是软件触发方式的多通道转换,即连续转换序列,软件触发。对应的时序如下(在第44章的2.7小节有详细讲解软件触发和硬件触发的时序):。


ADSTART表示软件启动转换。


ADSTP表示停止转换。


EOC表示一个通道转换结束。


EOS表示所有通道转换结束。

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png




关于这个时序图的解读:

配置为连续转换的话,软件启动ADSTART会开启所有通道转换,全部转换完毕后,继续进行下一轮转换。调用了停止转换ADSTP后,会停止转换。

每个通过转换完毕有个EOC标志,所有通道转换完毕有个EOS标志。


46.3.2 ADC时钟源选择
根据第44章2.2小节的讲解,我们知道ADC有两种时钟源可供选择,可以使用来自AHB总线的系统时钟,也可以使用PLL2,PLL3,HSE,HSI或者CSI时钟。


如果采用AHB时钟,不需要做专门的配置,而采用PLL2,PLL3时钟需要特别的配置,下面是使用AHB或者PLL2时钟的配置。


  通过宏定义设置选择的时钟源
使用哪个时钟源,将另一个注释掉即可:


  1. <blockquote>/* 选择ADC的时钟源 */
复制代码




  PLL2或者AHB时钟源配置
  1. <blockquote>#if defined (ADC_CLOCK_SOURCE_PLL)
复制代码




对于PLL2的时钟输出,直接使用STM32CubeMX里面的时钟树配置即可,效果如下:


aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png




选择PLL2P输出作为ADC时钟源:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png




  ADC分频设置
无论是使用AHB时钟还是PLL2时钟都支持分频设置:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png





AHB支持下面三种分频设置:
  1. <blockquote>#define ADC_CLOCK_SYNC_PCLK_DIV1   ((uint32_t)ADC_CCR_CKMODE_0)  
复制代码





PLL2支持下面几种分频设置:


  1. <blockquote>#define ADC_CLOCK_ASYNC_DIV1       ((uint32_t)0x00000000)                                       
复制代码




有了这些认识后再看实际的分频配置就好理解了:


  1. <blockquote>#if defined (ADC_CLOCK_SOURCE_PLL)
复制代码




46.3.3 ADC的DMA配置
由于函数HAL_ADC_Start_DMA封装的DMA传输函数是HAL_DMA_Start_IT。而我们这里仅需要用到DMA传输,而用不到中断,所以不开启对应的NVIC即可,这里使用的是DMA1_Stream1,测量了PC0,Vbat/4,VrefInt和温度四个通道。


  1. <blockquote>1.    /*
复制代码



这里把几个关键的地方阐释下:


  第11 - 13行,对作为局部变量的HAL库结构体做初始化,防止不确定值配置时出问题。
  第17 - 38行,前面2.2小节已经讲解,ADC时钟源选择AHB时钟还是PLL时钟。
  第41 – 46行,选择PC0作为数据采集引脚。
  第49- 68行,配置DMA的基本参数,注释较详细。这里是采用的ADC外设到内部SRAM的传输方向,数据带宽设置16bit,循环传输模式。
  第71行,这行代码比较重要,应用中容易被遗忘,用于关联ADC句柄和DMA句柄。在用户调用ADC的DMA传输方式函数HAL_ADC_Start_DMA时,此函数内部调用的HAL_DMA_Start_IT会用到DMA句柄。
  第75 - 107行,主要是ADC的配置,注释较详细,配置ADC3为16bit模式,扫描多通道,连续转换,软件触发。
  第111 – 114行,这里的是采用的ADC偏移校准,如果要采用线性度校准,
  第119 -129行,配置ADC多通道采样的第1个序列。这里使用的通道10是PC0引脚的复用功能,不是随意设置的。另外注意转换速度的计算,在程序里面有注释。
  第139 – 151行,配置ADC多通道采样的第2个序列,采样的Vbat/4电压。
  第154 – 166行,配置ADC多通道采样的第3个序列,采样的VrefInt电压。
  第169 – 181行,配置ADC多通道采样的第4个序列,采样的温度。
  第185 – 188行,启动ADC的DMA方式数据传输。
46.3.4 DMA存储器选择注意事项
由于STM32H7 Cache的存在,凡是CPU和DMA都会操作到的存储器,我们都要注意数据一致性问题。对于本章节要实现的功能,要注意读Cache问题,防止DMA已经更新了缓冲区的数据,而我们读取的却是Cache里面缓存的。这里提供两种解决办法:


  方法一:
关闭DMA所使用SRAM存储区。
  1. /* 配置SRAM的MPU属性为Device或者Strongly Ordered,即关闭Cache */
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  3. MPU_InitStruct.BaseAddress      = 0x60000000;
  4. MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  11. MPU_InitStruct.SubRegionDisable = 0x00;
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
复制代码

  方法二:
设置SRAM的缓冲区做32字节对齐,大小最好也是32字节整数倍,然后调用函数SCB_InvalidateDCache_by_Addr做无效化操作即可,保证CPU读取到的数据是刚更新好的。

本章节配套例子是直接使用的方法二。例子中变量的定义方式如下:
  1. /* 方便Cache类的API操作,做32字节对齐 */
  2. #if defined ( __ICCARM__ )
  3. #pragma location = 0x38000000
  4. uint16_t ADCxValues[4];
  5. #elif defined ( __CC_ARM )
  6. ALIGN_32BYTES(__attribute__((section (".RAM_D3"))) uint16_t ADCxValues[4]);
  7. #endif
复制代码

对于IAR需要#pragma location指定位置,而MDK通过分散加载即可实现,详情看前面第26章,有详细讲解。


46.3.5 读取DMA缓冲数据
程序中配置的DMA缓冲区可以存储4次ADC的转换数据,正好ADCxValues[0]对应PC0引脚的采样电压,ADCxValues[1]对应Vbat/4电压,ADCxValues[2]对应VrefInt采样的电源,ADCxValues[3]对应温度采样值。


具体实现代码如下:


  1. <blockquote>/*
复制代码



46.4 ADC板级支持包(bsp_adc.c)
ADC驱动文件bsp_adc.c提供了如下函数:
  • bsp_InitADC
  •   bsp_GetAdcValues

46.4.1 函数bsp_InitADC
函数原型:

void bsp_InitADC(void)

函数描述:


此函数用于初始化ADC,采用DMA方式进行多通道采样,采集了PC0, Vbat/4, VrefInt和温度。


注意事项:


关于此函数的讲解在本章2.3小节。
使用举例:


作为初始化函数,直接在bsp.c文件的bsp_Init函数里面调用即可。


46.4.2 函数bsp_GetAdcValues
函数原型:


void bsp_GetAdcValues(void)

函数描述:


此函数用于获取ADC的转换数据。


注意事项:


关于此函数的讲解在本章2.4和2.5小节。
使用举例:


根据需要,周期性调用即可。


46.5 ADC驱动移植和使用
ADC驱动的移植比较方便:


  第1步:复制bsp_adc.c和bsp_adc.h到自己的工程目录,并添加到工程里面。
  第2步:这几个驱动文件主要用到HAL库的GPIO、TIM,DMA和ADC驱动文件,简单省事些可以添加所有HAL库.C源文件进来。
  第3步,应用方法看本章节配套例子即可,另外就是根据自己的需要做配置修改。



46.6 实验例程设计框架
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png




  第1阶段,上电启动阶段:


这部分在第14章进行了详细说明。
  第2阶段,进入main函数:   


第1步,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器,LED,串口和ADC。
第2步,周期性的打印ADC采集的多通道数据。




46.7 实验例程说明(MDK)
配套例子:


V7-024-ADC+DMA的多通道采集


实验目的:


学习ADC + DMA的多通道采集实现。
实验内容:


例子默认用的PLL时钟供ADC使用,大家可以通过bsp_adc.c文件开头宏定义切换到AHB时钟。
采用DMA方式进行多通道采样,采集了PC0, Vbat/4, VrefInt和温度。
每隔500ms,串口会打印一次。
板子正常运行时LED2闪烁。
PC0引脚位置(稳压基准要短接3.3V):


aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png




上电后串口打印的信息:


波特率 115200,数据位 8,奇偶校验位无,停止位 1

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png





程序设计:


  系统栈大小分配:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png





  RAM空间用的DTCM:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png





  硬件外设初始化


硬件外设的初始化是在 bsp.c 文件实现:


  1. <blockquote>/*
复制代码



  MPU配置和Cache配置:


数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区和D3域的SRAM4。







  主功能:


主程序实现如下操作:


每隔500ms,串口会打印一次ADC采集 的PC0, Vbat/4, VrefInt和温度。
  1. <blockquote>/*
复制代码




46.8 实验例程说明(IAR)
配套例子:


V7-024-ADC+DMA的多通道采集


实验目的:


学习ADC + DMA的多通道采集实现。
实验内容:


例子默认用的PLL时钟供ADC使用,大家可以通过bsp_adc.c文件开头宏定义切换到AHB时钟。
采用DMA方式进行多通道采样,采集了PC0, Vbat/4, VrefInt和温度。
每隔500ms,串口会打印一次。
板子正常运行时LED2闪烁。
PC0引脚位置(稳压基准要短接3.3V):

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png





上电后串口打印的信息:


波特率 115200,数据位 8,奇偶校验位无,停止位 1

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png





程序设计:


  系统栈大小分配:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png





  RAM空间用的DTCM:


aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png




  硬件外设初始化


硬件外设的初始化是在 bsp.c 文件实现:
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_Init
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_Init(void)
  10. {
  11.     /* 配置MPU */
  12.     MPU_Config();
  13.    
  14.     /* 使能L1 Cache */
  15.     CPU_CACHE_Enable();

  16.     /*
  17.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
  18.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
  19.        - 设置NVIV优先级分组为4。
  20.      */
  21.     HAL_Init();

  22.     /*
  23.        配置系统时钟到400MHz
  24.        - 切换使用HSE。
  25.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
  26.     */
  27.     SystemClock_Config();

  28.     /*
  29.        Event Recorder:
  30.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
  31.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
  32.     */   
  33. #if Enable_EventRecorder == 1  
  34.     /* 初始化EventRecorder并开启 */
  35.     EventRecorderInitialize(EventRecordAll, 1U);
  36.     EventRecorderStart();
  37. #endif
  38.    
  39.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
  40.     bsp_InitTimer();      /* 初始化滴答定时器 */
  41.     bsp_InitUart();    /* 初始化串口 */
  42.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
  43.     bsp_InitLed();        /* 初始化LED */   
  44.     bsp_InitADC();     /* 初始化ADC */
  45. }
复制代码




  MPU配置和Cache配置:


数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区和D3域的SRAM4。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: MPU_Config
  4. *    功能说明: 配置MPU
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void MPU_Config( void )
  10. {
  11.     MPU_Region_InitTypeDef MPU_InitStruct;

  12.     /* 禁止 MPU */
  13.     HAL_MPU_Disable();

  14.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
  15.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  16.     MPU_InitStruct.BaseAddress      = 0x24000000;
  17.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  18.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  19.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  20.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  21.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  22.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  23.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  24.     MPU_InitStruct.SubRegionDisable = 0x00;
  25.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  26.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
  27.    
  28.    
  29.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
  30.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  31.     MPU_InitStruct.BaseAddress      = 0x60000000;
  32.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  33.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  34.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  35.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
  36.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  37.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  38.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  39.     MPU_InitStruct.SubRegionDisable = 0x00;
  40.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
  41.    
  42.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  43.     /* 配置SRAM4的属性为Write through, read allocate,no write allocate */
  44.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  45.     MPU_InitStruct.BaseAddress      = 0x38000000;
  46.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  47.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  48.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
  49.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  50.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  51.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
  52.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  53.     MPU_InitStruct.SubRegionDisable = 0x00;
  54.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  55. HAL_MPU_ConfigRegion(&MPU_InitStruct);

  56.     /*使能 MPU */
  57.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  58. }

  59. /*
  60. *********************************************************************************************************
  61. *    函 数 名: CPU_CACHE_Enable
  62. *    功能说明: 使能L1 Cache
  63. *    形    参: 无
  64. *    返 回 值: 无
  65. *********************************************************************************************************
  66. */
  67. static void CPU_CACHE_Enable(void)
  68. {
  69.     /* 使能 I-Cache */
  70.     SCB_EnableICache();

  71.     /* 使能 D-Cache */
  72.     SCB_EnableDCache();
  73. }
复制代码


  主功能:


主程序实现如下操作:


每隔500ms,串口会打印一次ADC采集 的PC0, Vbat/4, VrefInt和温度。
  1. <blockquote>/*
复制代码

46.9 总结
本章节就为大家讲解这么多,ADC多通道采样在实际项目中也比较实用,望初学者熟练掌握。






收藏 评论0 发布时间:2021-10-26 15:51

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版