ADC

ADC代表模数转换器(Analog-to-Digital Converter),是一种电子设备或电路,用于将模拟信号转换为数字信号。模拟信号是连续变化的信号,如电压、电流或温度,而数字信号是离散的信号,由一系列数字值表示。ADC的主要作用是将模拟信号转化为数字形式,以便数字电子设备能够处理、存储和分析这些信号。

AT32 的 ADC 是一个将模拟输入信号转换为设定分辨率数位数字信号的外设。采样率最高可达 5.33MSPS。多达 26 个通道源可进行采样及转换。具备多种功能强大的模式,本文主要以 ADC 的特 色功能进行讲解和案列解析。

ADC简介

ADC控制器的功能极其强大。其包含但不限于以下内容
  • 时钟及状态,由数字和模拟时钟两个部分组成
  • 分辨率及采样转换,可配置分辨率为12/10/8/6位的转换,采样周期支持广范围的配置
  • 自校准,自带校准功能以纠正数据偏移
  • 基本模式,支持多种模式,不同模式可组合使用满足多种应用
  • 不同优先权的通道,普通通道与抢占通道具备不同的优先权
  • 多种独立的触发源,包括TMR、EXINT、软触发等多种触发选择
  • 数据后级处理,包括数据的对齐,抢占通道偏移量等多种处理
  • 转换中止,可软件控制在ADC不掉电状态下实现转换中止
  • 过采样器,普通及抢占通道均支持过采样
  • 电压监测,通过对转换结果的判定来实现电压监测
  • 中断及状态事件,具备多种标志指示ADC状态,且某些标志还具备中断功能
  • 多种转换数据的获取方式,包括DMA获取、CPU获取两种方式实现转换数据的读取
  • 多达26个通道:IN0~IN17,IN20~IN27

《雅特力AT32F423 ADC使用指南》全文。

来源:AT32 MCU 雅特力科技

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 21

01、引言

很多 STM32 系列中的 ADC 都带有自校准的功能。它提供了一个自动校准的过程,用于驱动包括 ADC 上电/掉电序列在内的所有校准动作。在这个过程中,ADC 计算出一个校准因子,并在内部应用到此 ADC 模块,直到下一次 ADC 掉电。在执行任何 ADC 操作之前必须校准,以消除芯片之间 ADC 结果的偏差。

02、问题

2.1. 问题详情

客户使用 STM32U575ZIT6Q 验证 ADC4 时,使用 STM32CubeMX 配置后生成工程项目。因为使用 ADC 进行采样转换前,必须要做 ADC 的自校准。于是在 main 函数中加入自校准代码,如图 1 所示。

1.png

图1.代码:ADC 自校准

然而,在运行代码的情况下,发现 PC 指针最后跑到这个 Calibration Error 的Error_Handler()里。也就是说,执行自校准失败了! 

再检查进入 Error_Handler()发生的问题,发现在执行HAL_ADCEx_Calibration_Start()时陷在了下面这个 Loop 当中,如图 2 所示。

2.png

图2.代码:等待 ADC 自校准完成

也就是说,ADCAL 位被置 1 后,始终没有被硬件清 0,代表自校准始终不成功。

2.2. 问题分析

查看 STM32U5 的参考手册 RM0456 中关于自校准的描述。考虑到校准没有成功,那么应该看看是不是自校准需要哪些条件,而这些条件并没有成立。 

于是,在 ADC 章节中的 Calibration 小节找到了这么一段话,如图 3 所示。

3.png

图3.ADC 自校准要求条件

也就是说,在初始化自校准之前,需要保证 3 个条件: 

1) ADC 的电压调整器已经使能并正常工作(ADVREGEN = 1 且 LDORDY =1) 

2) ADC 没有打开(ADEN=0) 

3) 自动掉电模式没有使能(AUTOFF = 0) 

回到刚才等待 ADC 自校准完成的代码,当指针停留到这边时,在线调试检查各个标志位情况,发现 ADVREGEN=1,ADEN=0,AUTOFF=0,LDORDY=0。所以,可以肯定的是就是 LDORDY 不为 1,也就是说 ADC 的电压调整器还没有准备好,导致了自校准无法成功并退出。 

在 STM32U5 中,引入了一个新的 ADC 特性,叫 ADC 电压调整器(ADC voltageregulator)。在使用 ADC 之前,这个电压调整器必须被使能并且能够稳定工作。可以通过将 ADC_CR 寄存器中的 ADVREGEN 位置 1 来使能它,然后必须要等这个电压调整器的启动时间之后,才可以正常启动自校准或者使用 ADC。这个 LDO 有没有准备好,可以通过 ADC_ISR 寄存器中的 LDORDY 这个位来判断。LDORDY=1 才代表了 LDO 已经准备好了。 

回到客户的问题,可以知道即使 ADVREGEN 置 1 了,LDORDY 始终没有置起来,电压调整器没有启动工作,难道是 ADC 的这个电压调整器坏掉了? 

别急,想到电源的问题,还不能忘了参考手册的另一个章节 :电源控制 PWR。翻到PWR 这一章,先要考虑到,与 ADC 相关的电源为 VDDA,所以要重点查看 VDDA 的内容。找到 Independent analog peripherals supply 这一小节,可以看以下关键的句子,如图 4。

4.png

图4.独立的模拟外设供电

从图中黄色高亮的文字,我们可以了解到,STM32U5 的 VDDA 在控制上还跟其他系列不一样,它有一个开关来选择是否隔离。芯片复位后,VDDA 提供的 ADC 和模拟开关控制在逻辑和电气上是隔离的,因此不可用。一旦 VDDA 电源存在,就必须在使用模拟外设之前,通过在 PWR_SVMCR 寄存器中将 ASV 置位来消除隔离。也就是说,要想使用ADC,必须先将 ASV 置位来消除隔离。 

在 PWR_SVMCR 寄存器的解释中,也可以看到 ASV 位的描述中要求将此位强制置 1才可以使用模拟外设,如图 5。

5.png

图5.ASV 位描述

ASV 的配置是没有在 STM32CubeMX 中进行配置的,所以需要在后期添加。所以,很可能就是因为没有将 ASV 置位以使能 VDDA,导致 ADC 外设被隔离,并没有得到供电,所以 ADC 的电压调整器不可能正常工作,LDORDY 也不可能为 1。

03、问题解决

在工程项目中搜索 ASV,可以找到两个 API,如图 6。

6.png

图6.VDDA 使能或禁用 API

也就是说,我们需要先将 HAL_PWREx_EnableVddA()添加到代码中。因为其属于MSP 硬件配置,所以将其加到 HAL_MspInit()函数,如图 7 所示。

7.png

图7.修改过的 HAL_MspInit()函数

再重新编译,然后执行指令代码,就可以看到 ADC 的自校准可以正常完成,PC 指针已经可以正常跑到自校准后面的代码了。

04、小结

在 STM32U5 中,为了更好地控制功耗,ADC 中加入电压调整器和 VDDA 隔离功能。这与以往的 STM32 有所不同,需要注意一下。使用 ADC 等模拟外设前,需要注意将PWR_SVMCR 寄存器中将 ASV 置位来消除隔离。 

其实不仅仅是 VDDA 可以隔离,VDDIO2 也是可以的,通过 PWR_SVMCR 寄存器的IO2SV 位进行控制。

如果仔细看过 STM32U5 的 ADC 例程,可以看到 HAL_MspInit()的内容是这么写的,如图 8 所示。

8.png

图8.例程的 HAL_MspInit()函数

可以看到,在 MSP 初始化里,不仅使能了 VDDA,还使能了 VDDIO2。所以要使用VDDIO2 相关 I/O 的,也需要注意一下。

来源:STM32单片机

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 43

众所周知,模数转换,即Analog-to-Digital Converter,常称ADC,是指将连续变量的模拟信号转换为离散的数字信号的器件。主要用于对模拟信号进行数字采集,以进行数据处理。通常情况下,在转换完成时读取转换结果。而ADC中的窗口比较功能,会检测ADC结果是否低于或高于特定阈值,当转换结果在某一个范围时可以触发中断,几乎无需软件干预。该配置非常实用,它可以监视信号是否按照要求保持在特定范围内,或者在电池电量不足/过充时发出信号。

本文以RA4M2为例介绍如何设置ADC窗口比较功能。

RA MCU中一般都会包括12位逐次逼近型A/D转换器(ADC12)单元,最多可选择13路模拟量输入通道、温度传感器输出、内部参考电压进行转换。

同时,ADC12还提供比较功能(窗口A和窗口B)。比较功能分别指定窗口A和窗口B的参考值上限和的参考值下限,当所选通道的A/D转换值满足比较条件时输出中断。窗口A和窗口B的主要区别在于它们的中断输出信号不同以及对窗口B只能选择一个通道的限制。

这里以窗口A为例进行介绍。窗口A有以下4种比较模式。

• 结果低于阈值

• 结果高于阈值

• 结果在窗口内

• 结果在窗口外

窗口A比较功能相关的寄存器有ADCMPCR、ADCMPLR0/1和ADCMPDR0/1寄存器,详情请参考RA硬件手册。

1.png

2.png

3.png

4.png

5.png

ADC数据与可设置的高低阈值进行比较,并可为AD数据在设定的门限值内、外、高或低自动生成比较中断。需要设置三个选项位置,接下来我们将逐个介绍各种情况下属性设置和用户代码。

1、A/D转换结果高于门限值时产生窗口比较A中断(ADC120_CMPAI)

6.png

若想实现上面的比较功能,请按照下图中所示,在FSP中设置ADC属性。

(1)Input → Window Compare → Window A

Enable:选择Enabled

Lower Reference:请在这里设定阈值

(2) Input → Window Compare

Window Mode:选择Disabled

7.png

当AD转换结果 > ADCMPDR0(Lower Reference 设定值时)时,产生Compare中断。

2、A/D转换结果低于门限值时产生窗口比较A中断(ADC120_CMPAI)

8.png

若想实现上图的比较功能,请按照下图中所示,在FSP中设置ADC属性。

(1)Input → Window Compare → Window A

Enable:选择Enabled

Lower Reference:请在这里设定阈值

(2) Input → Window Compare

Window Mode:选择Disabled

(3)代码中将CMPLCHA0位置为“0”

R_ADC0->ADCMPLR_b->CMPLCHA0 = 0;

9.png

当AD转换结果 < ADCMPDR0(Lower Reference 设定值时),产生Compare中断。

3、A/D转换结果在门限值内

10.png

若想实现上图的比较功能,请按照下图中所示,在FSP中设置ADC属性。

(1)Input → Window Compare → Window A

Enable:选择Enabled

Lower Reference、Upper Reference:请在这里设定阈值

(2) Input → Window Compare

Window Mode:选择Enabled

11.png

当ADCMPDR0(Lower Reference设定值时)< AD转换结果< ADCMPDR1(Upper Reference设定值时)时,产生Compare中断。

4、A/D转换结果在门限值外

12.png

若想实现上图的比较功能,请按照下图中所示,在FSP中设置ADC属性。

(1)Input → Window Compare → Window A

Enable:选择Enabled

Lower Reference、Upper Reference:请在这里设定阈值

(2) Input → Window Compare

Window Mode:选择Enabled

(3)代码中将CMPLCHA0位置为“0”

R_ADC0->ADCMPLR_b->CMPLCHA0 = 0;

13.png

当AD转换结果<ADCMPDR0(Lower Reference 设定值时)或者>ADCMPDR1(Upper Reference设定值时)时,产生Compare中断。

另外,使用窗口比较功能时请注意以下限制:

● 比较功能不能与自诊断功能或双触发模式一起使用。(比较功能不适用于ADRD、ADDBLDR、ADDBLDRA和ADDBLDRB。)

● 使用匹配/不匹配事件输出时请使用单次扫描模式。

● 当温度传感器输出时,窗口A选择内部参考电压,窗口B操作被禁用。

● 当温度传感器输出时,窗口B选择内部参考电压,窗口A操作被禁用。

● 禁止为窗口A和窗口B设置相同的通道。

● 设置参考电压值时,请将高电位参考电压值设置为大于或等于低电位参考电压值。

更多内容,您可点击下方链接进入瑞萨中文论坛查看:

https://community-ja.renesas.com/zh/forums-groups/mcu-mpu/

来源:瑞萨MCU小百科

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 55

引言

MindSDK为MM32主流的微控制器,实现了一组ADC样例工程。本文通过讲解ADC模块的样例工程,介绍ADC模块的功能和用法。关于ADC模块对应的驱动程序,以及ADC外设模块硬件的实现细节,可具体查阅MindSDK工程的源文件,以及MM32微控制器(例如MM32F5270)的用户手册。

样例工程

MindSDK中为ADC驱动设计的样例工程包括:

  • adc_basic

  • adc_interrupt

  • adc_dma

  • adc_seq

  • adc_fixed_seq

  • adc_ext_seq

  • adc_awdg

  • adc_oversample

  • adc_sw_write_calib

其中, adc_baic 、 adc_interrupt 、 adc_dma 分别演示了ADC最典型的功能,包括通过轮询、中断、DMA来获取一个ADC转换通道的转换结果。adc_awdg 来展现ADC模拟看门狗的功能, adc_seq 、 adc_fixed_seq 、 adc_ext_seq 则展现ADC按照不同的转换序列来进行转换。还有一些用于特殊场景的功能,例如通过软件写入校准值,以及硬件实现的多次采样取平均值、基本滤波等。

adc_basic

adc_basic 描述了使用ADC模块最基本的方式,通过轮询方式对一个通道进行转换,并获取通道的转换值。在样例工程中,通过 ADC_Init() 函数,配置ADC数据有效数据位 ADC_Resolution_Alt0 ,ADC的转换模式为单通道单次转换模式 ADC_ConvMode_SingleSlot 、转换数据对其方式为右对齐 ADC_Align_Right 、通道转化结果通过单端转换方式,将通道与ADC内部参考电压VREF的差数字量化后输出 ADC_SingleDiffConvMode_SingleEnd 、配置单端转换方式的参考电压为内部参考电压 ADC_SingleConvVref_Internal ,配置待转换通道的序列和每个转换通道的采样周期。之后,每次通过 ADC_DoSwTrigger() 函数,使用软件触发的方式启动ADC转换。

实际运行程序时,用户在串口调试终端中每次输入任意字符,改变转换通道的输入电压,会看到串口终端界面对应数值的变化。

adc_interrupt

adc_interrupt 相对于 adc_basic 样例工程,实现了ADC转换完成后触发中断服务,在中断服务中获取转换值的用法。

其实现原理,是在 adc_basic 的配置的基础上,通过 ADC_EnableInterrupts() 函数和 NVIC_EnableIRQ()函数 开启ADC的中断。当ADC转换通道完成转换后,就进入中断服务函数。

实际运行程序时,用户在串口调试终端每次输入任意字符,改变转换通道的输入电压,会看到串口终端界面对应数值的变化。

adc_dma

adc_dma 在 adc_basic 的基础上增加对DMA外设模块的使用。在应用层 app_adc_init() 函数中添加对DMA通道的配置,通过 DMA_InitChannel() 函数, DMA_XferMode_PeriphToMemory 配置传输方向为外设到内存、 MemAddr 和 PeriphAddr 配置内存和外设地址、 MemAddrIncMode 和 PeriphAddrIncMode 配置内存和外设的增量模式、 XferCount 传输的数据量、 DMA_ReloadMode_AutoReloadContinuous 自动重载传输量、 DMA_XferWidth_32b 以32位的数据进行传输。外设映射的DMA通道初始配置完成后,通过 DMA_EnableChannel() 函数使能DMA通道,通过 NVIC_EnableIRQ() 和 DMA_EnableChannelInterrupts() 函数开启DMA中断。之后,每次通过 ADC_DoSwTrigger() 函数触发ADC开始转换,当转换完成后DMA开始搬运,当DMA搬运完成后进入中断服务函数,以此循环往复。

实际运行程序时,用户在串口调试终端中每次输入任意字符,改变转换通道的输入电压,会看到串口终端界面对应数值的变化。

adc_fixed_seq

adc_fixed_seq 在 adc_dma 样例的基础上,将转换的ADC通道由一个通道,设置为多个通道形成一个序列来进行转换,当序列转换完成后,就停止转换。通过 ADC_EnableSeqSlotFixed() 函数来配置转换序列的顺序为有低到高的顺序进行转换。DMA将ADC每个通道数据搬运到指定内存。通过在循环中调用 ADC_DoSwTrigger() 函数来进行一个序列的多次转换。或者通过 ADC_ConvMode_SeqContinues 启用序列的连续转换模式。通过 ADC_DoSwTrigger() 函数可以停止序列转换。

实际运行程序时,用户在串口调试终端中每次输入任意字符,改变配置的转换序列中每个通道的输入电压值,会看到串口调试终端界面对应数值变化。

adc_seq

adc_seq 与 adc_fixed_seq 样例的不同之处在于, adc_fixed_seq 样例只能实现一个由高到低或者由低到的通道转换序列进行转换,例如ch0-ch1-ch3或者ch3-ch1-ch0的转换顺序。adc_seq 样例则实现任意通道序列的转换,例如ch3-ch0-ch5,任意的序列。通过ADC_EnableSeqSlot () 函数配置具体的转换序列。ADC根据配置的转换序列开始进行转换。当序列转换完成后,停止转换。可以重复的调用 ADC_DoSwTrigger() 开启下一个序列的转换,一次循环往复。或者通过 ADC_ConvMode_SeqContinues 启用序列的连续转换模式。通过 ADC_DoSwTrigger() 函数可以停止序列转换。

实际运行程序时,用户在串口调试终端中每次输入任意字符,改变转换通道的输入电压,会看到串口终端界面对应数值的变化。

adc_ext_seq

adc_ext_seq 实现在当前任意序列转换过程中插入额外的序列,当插入额外的序列时,当前任意序列中正在转换的通道完成,将切换到插入的序列中进行转换,当转换完成后在继续之前任意序列中剩余通道的转换。通过 ADC_EnableExtSeqSlot() 函数配置额外的序列。例如当前任意序列为ch3-ch0-ch5,额外的序列为ch1-ch4。ADC_EnableAutoExtSeqSlot() 函数开启自动注入模式,即任意序列转换完成后,开启注入通道的转换,转换额外的序列。这种注入模式增加了整个序列的长度。当一个完整序列转换完成后,停止转换,调用 ADC_DoSwTrigger() 函数来进行一个序列的多次转换。或者通过 ADC_ConvMode_SeqContinues 启用序列的连续转换模式。通过 ADC_DoSwTrigger() 函数可以停止序列转换。

实际运行时,用户在串口调试终端中每次输入任意字符,改变转换通道的输入电压,会看到串口终端界面对应数值的变化。

adc_awdg

adc_awdg 在 adc_basic 的基础上增加对转换结果进行监控,通过 ADC_EnableHwComp() 函数配置需要监控的转换通道 ChnNum 、设置的上限值 HighLimit 和下限值 LowLimit ,可以根据上限值和下限值的大小关系设置监控范围,当上限值大于或等于下限值时,监控通道的转换值在两个阈值之外的数值,若开启中断则会进入中断服务函数;同理,当上限值小于下限值时,监控通道转换值在两个阈值之间的数值,若开启中断则会进入中断服务函数。之后,每次通过 ADC_DoSwTrigger() 函数,使用软件触发的方式启动ADC转换。

实际运行时,用户在串口调试终端中每次输入任意字符,改变转换通道的输入电压,当电压值满足监控区间时,会看到串口终端界面当前转换数据。

adc_oversample

adc_oversample 在 adc_seq 的基础上通过 ADC_SetOverSample()`函数启动硬件的过采样模式, ADC_OverSampleRatio_256 配置过采样率为256, ADC_OverSampleDataDiv_8 配置数据右移8位, ADC_OverSampleTriggerMode_Multi 配置为所有通道的过采样转换只需要进行一次触发。injectOverSampleMode 配置是否开启注入通道的过采样模式。之后,每次通过 ADC_DoSwTrigger() 函数,使用软件触发的方式启动ADC转换。

adc_sw_write_calib

adc_sw_write_calib 在 adc_basic 的基础上实现使用软件保存ADC的校准值,然后通过软件将校准值写入寄存器中。通过 ADC_GetCalibFactor() 函数获取校准值,通过 ADC_SetCalibFactor() 来写入校准值。

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 15

在上一讲单通道ADC电压采集的基础上,本节主要介绍CKS32F4xx系列产品基于DMA传输的ADC多通道电压采集转换实现。

DMA传输在ADC中的应用

DMA是直接存储器存取,通常在使用ADC时,需要通过MCU内核不停的读取数据,如果使用DMA,那么读取的过程会绕过MCU,减轻MCU内核的处理压力,这样有利于资源的充分利用,提高ADC数据的处理效率。由于ADC规则通道组只有一个数据寄存器中,当转换多个通道时,使用DMA还可以避免丢失已经存储在ADC_DR寄存器中的数据。在使能DMA模式的情况下,每完成规则通道组中的一个通道转换后,都会生成一个DMA请求,便可将转换的数据从ADC_DR寄存器传输到用指定的目标内存位置。这样取代单通道实验使用中断服务的读取方法,可以实现多通道ADC应用中高速高效的采集。

软件设计要点

跟单通道例程一样,编写两个ADC驱动文件,bsp_adc.h和bsp_adc.c,用来存放ADC所用IO引脚的初始化函数以及ADC和DMA相关配置函数,主要流程为:

(1)初始化配置ADC目标引脚为模拟输入模式;

(2)使能ADC时钟和DMA时钟;

(3)配置DMA从ADC数据寄存器传输数据到指定的存储区;

(4)配置通用ADC为独立模式;

(5)设置ADC为12位分辨率,启动扫描,连续转换,不需要外部触发;

(6)设置ADC转换通道顺序及采样时间;

(7)使能DMA请求,DMA在AD转换完自动传输数据到指定的存储区;

(8)启动ADC模块;

(9)软件使能触发ADC转换。

这里需要注意的是,在使用ADC+DMA功能时,如果在启动ADC转换之后使能DMA,ADC采样数据可能会出现异常。因此建议先配置ADC及DMA相关参数,最后启动ADC转换。

代码实现

受篇幅限制,这里只介绍核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参考本课程配套的例程。相关核心代码实现如下:

(1)ADC宏定义

#define TEMP_NOFCHANEL      3
/*=====================通道1 IO======================*/
// PB0 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验
#define TEMP_ADC_GPIO_PORT1    GPIOB
#define TEMP_ADC_GPIO_PIN1     GPIO_Pin_0
#define TEMP_ADC_GPIO_CLK1     RCC_AHB1Periph_GPIOB
#define TEMP_ADC_CHANNEL1      ADC_Channel_8
/*=====================通道2 IO ======================*/
// PB1 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验
#define TEMP_ADC_GPIO_PORT2    GPIOB
#define TEMP_ADC_GPIO_PIN2     GPIO_Pin_1
#define TEMP_ADC_GPIO_CLK2     RCC_AHB1Periph_GPIOB
#define TEMP_ADC_CHANNEL2      ADC_Channel_9
/*=====================通道3 IO ======================*/
// PA6 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验
#define TEMP_ADC_GPIO_PORT3    GPIOA
#define TEMP_ADC_GPIO_PIN3     GPIO_Pin_6
#define TEMP_ADC_GPIO_CLK3     RCC_AHB1Periph_GPIOA
#define TEMP_ADC_CHANNEL3     ADC_Channel_6
// ADC 序号宏定义
#define TEMP_ADC              ADC1
#define TEMP_ADC_CLK          RCC_APB2Periph_ADC1
// ADC DR寄存器宏定义,ADC转换后的数字值则存放在这里
#define TEMP_ADC_DR_ADDR    ((u32)ADC1+0x4c)
// ADC DMA 通道宏定义,使用DMA传输
#define TEMP_ADC_DMA_CLK      RCC_AHB1Periph_DMA2
#define TEMP_ADC_DMA_CHANNEL  DMA_Channel_0
#define TEMP_ADC_DMA_STREAM   DMA2_Stream0

定义多个通道进行多通道ADC实验,并且定义DMA相关配置。

(2)ADC GPIO初始化

static void Temp_ADC_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    /*=====================通道1======================*/
    RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK1,ENABLE); // 使能 GPIO 时钟
    GPIO_InitStructure.GPIO_Pin = TEMP_ADC_GPIO_PIN1; // 配置 IO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;     //不上拉不下拉
    GPIO_Init(TEMP_ADC_GPIO_PORT1, &GPIO_InitStructure);
    /*=====================通道2======================*/
    RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK2,ENABLE); // 使能 GPIO 时钟
    GPIO_InitStructure.GPIO_Pin = TEMP_ADC_GPIO_PIN2; // 配置 IO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;     //不上拉不下拉
    GPIO_Init(TEMP_ADC_GPIO_PORT2, &GPIO_InitStructure);
    /*=====================通道3=======================*/
    RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK3,ENABLE); // 使能 GPIO 时钟
    GPIO_InitStructure.GPIO_Pin = TEMP_ADC_GPIO_PIN3;  // 配置 IO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;   //不上拉不下拉
    GPIO_Init(TEMP_ADC_GPIO_PORT3, &GPIO_InitStructure);
}

使用到GPIO时候都必须开启对应的GPIO时钟,GPIO用于AD转换功能必须配置为模拟输入模式。

(3)配置ADC工作模式

static void Temp_ADC_Mode_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    ADC_CommonInitTypeDef ADC_CommonInitStructure;     
    // --------------DMA Init 结构体参数初始化-------------     
    // ADC1使用DMA2,数据流0,通道0,
    RCC_AHB1PeriphClockCmd(TEMP_ADC_DMA_CLK, ENABLE);   // 开启DMA时钟
    DMA_InitStructure.DMA_PeripheralBaseAddr = TEMP_ADC_DR_ADDR; // 外设基址为:ADC 数据寄存器地址
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)ADC_ConvertedValue;  // AD值存储地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;   // 数据传输方向为外设到存储器
    DMA_InitStructure.DMA_BufferSize = TEMP_NOFCHANEL; // 缓冲区大小,指一次传输的数据量
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;// 外设寄存器只有一个,地址不递增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;   // 存储器地址固定
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  // 外设数据大小为半字
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;   // 存储器数据大小也为半字
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环传输模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;// DMA传输通道优先级为高
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;    // 禁止DMA FIFO ,使用直连模式
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; // FIFO大小,FIFO禁止时不用配置
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;  
    DMA_InitStructure.DMA_Channel = TEMP_ADC_DMA_CHANNEL; // 选择 DMA 通道,通道存在于流中
    DMA_Init(TEMP_ADC_DMA_STREAM, &DMA_InitStructure);//初始化DMA流,
    DMA_Cmd(TEMP_ADC_DMA_STREAM, ENABLE); // 使能DMA流

    RCC_APB2PeriphClockCmd(TEMP_ADC_CLK , ENABLE);// 开启ADC时钟     
    // -------------ADC Common 结构体 参数初始化----------------
    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;// 独立ADC模式
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;// 时钟为fpclk x分频
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; // 禁止DMA直接访问模式     
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;  // 采样时间间隔
    ADC_CommonInit(&ADC_CommonInitStructure);     
    // -------------------ADC Init 结构体 参数初始化--------------------------
    ADC_StructInit(&ADC_InitStructure);
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;// ADC 分辨率
    ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描模式,多通道采集需要
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  // 连续转换
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止外部边沿触发
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;  //外部触发通道,使用软件触发时此值随便赋值即可
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐ADC_InitStructure.ADC_NbrOfConversion = TEMP_NOFCHANEL;      //转换通道3个                             
    ADC_Init(TEMP_ADC, &ADC_InitStructure);

    
  // 配置 ADC 通道转换顺序和采样时间周期
  ADC_RegularChannelConfig(TEMP_ADC, TEMP_ADC_CHANNEL1, 1, ADC_SampleTime_3Cycles);
  ADC_RegularChannelConfig(TEMP_ADC, TEMP_ADC_CHANNEL2, 2, ADC_SampleTime_3Cycles);
  ADC_RegularChannelConfig(TEMP_ADC, TEMP_ADC_CHANNEL3, 3, ADC_SampleTime_3Cycles);

    ADC_DMARequestAfterLastTransferCmd(TEMP_ADC, ENABLE); // 使能DMA请求      
    ADC_DMACmd(TEMP_ADC, ENABLE);// 使能ADC DMA
    ADC_Cmd(TEMP_ADC, ENABLE);  // 使能ADC
    ADC_SoftwareStartConv(TEMP_ADC);//开始ADC转换,软件触发
}

首先,使用DMA_InitTypeDef定义了DMA初始化类型变量,另外使用ADC_InitTypeDef和ADC_CommonInitTypeDef结构体分别定义一个ADC初始化和ADC通用类型变量。

调用RCC_APB2PeriphClockCmd()开启ADC时钟以及RCC_AHB1PeriphClockCmd()开启DMA时钟。

对DMA进行必要的配置。首先设置外设基地址就是ADC的规则数据寄存器地址;存储器的地址就是指定的数据存储区空间,ADC_ConvertedValue是我们定义的一个全局数组名,它是一个无符号16位含有3个元素的整数数组;ADC规则转换对应只有一个数据寄存器,所以地址不能递增,而定义的存储区是专门用来存放不同通道数据的,所以需要自动地址递增。ADC的规则数据寄存器只有低16位有效,实际存放的数据只有12位而已,所以设置数据大小为半字大小。ADC配置为连续转换模式,DMA也设置为循环传输模式。设置好DMA相关参数后就使能DMA的ADC通道。

接下来使用ADC_CommonInitTypeDef结构体变量ADC_CommonInitStructure来配置ADC为独立模式、分频系数4、20个周期的采样延迟,并调用ADC_CommonInit函数完成ADC通用工作环境配置。

使用ADC_InitTypeDef结构体变量ADC_InitStructure来配置ADC1为12位分辨率、使能扫描模式、启动连续转换、使用内部软件触发无需外部触发事件、使用右对齐数据格式、转换通道为3,并调用ADC_Init函数完成ADC1工作环境配置。

ADC_RegularChannelConfifig函数用来绑定ADC通道转换顺序和采样时间。分别绑定3个ADC通道引脚并设置相应的转换顺序。

ADC_DMARequestAfterLastTransferCmd函数控制是否使能ADC的DMA请求,如果使能请求,并调用ADC_DMACmd函数使能DMA,则在ADC转换完成后就请求DMA实现数据传输。ADC_Cmd函数控制ADC转换启动和停止。

最后使用软件触发调用ADC_SoftwareStartConvCmd函数进行使能配置。

(4)Main程序

/**  主函数  */
int main(void)
{
    Debug_USART_Config();
    Temp_Init();
    while (1)
    {
        ADC_ConvertedValueLocal[0] =(float) ADC_ConvertedValue[0]/4096*(float)3.3;
        ADC_ConvertedValueLocal[1] =(float) ADC_ConvertedValue[1]/4096*(float)3.3;
        ADC_ConvertedValueLocal[2] =(float) ADC_ConvertedValue[2]/4096*(float)3.3;
        printf("\r\n PB0 value = %f V \r\n",ADC_ConvertedValueLocal[0]);
        printf("\r\n PB1 value = %f V \r\n",ADC_ConvertedValueLocal[1]);
        printf("\r\n PA6 value = %f V \r\n",ADC_ConvertedValueLocal[2]);
        Delay(0xffffff);  
    }
}

主函数先调用Debug_USART_Config函数配置调试串口相关参数,接下来调用Temp_Init函数进行ADC初始化配置并启动ADC。配置了DMA数据传输,它会自动把ADC转换完成后数据保存到数组ADC_ConvertedValue内,我们只要使用数组就可以了。经过简单地计算就可以得到每个通道对应的实际电压。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 11

1、MM32F0160的ADC简介

ADC外设是12位的逐次逼近型(SAR)模拟数字转换器,可以将模拟信号转换成数字信号。

ADC有16 个通道可测量内部或外部信号源,其中ADC有14 路外部输入通道和2路内部通道,如下图1 ADC的系统框图所示分别是通道14内部温度温度传感器通道,通道15内部电压传感器通道,即内部1.2V参考电压通道。

ADC的通道可以单次、单周期和连续进行转换。根据不同的方式又可以选择普通通道转换、任意通道转换、注入通道转换。ADC 的输入时钟不得超过16M,他是由 APB2 时钟(PCLK2) 分频产生。

1.png

图1 ADC系统框图

2、MM32F0160内部1.2V参考电压简介

MM32系列MCU出厂时,在Flash特定的内存地址存储了1.2V参考电压的校准值,即VDDA为3V3时采样1.2V电压的校准值。该校准值可以通过UM手册查阅。例如,可通过UM_MM32F0160_SC手册的ADC章节查阅到MM32F0163D7P的ADC内部1.2V参考电压的校准值存储在Flash特定的内存地址0x1FFFF7E0区域。根据本文章节1图1的ADC系统框图可知ADC内部1.2V参考电压通道为通道15。MM32其它系列的MCU的ADC内部1.2V参考电压校准值在Flash内存的存储地址,内部1.2V参考电压通道的通道号如下表1所示(注:其它MCU系列请参考UM手册)。

2.png

表1 MM32系列ADC内部1.2V参考电压校准值的存储地址和1.2V参考电压通道号

3、ADC内部1.2V参考电压的应用场景举例

ADC的参考电压为VDD,当MCU的供电VDD不稳定或采用电池供电时ADC的参考电压会随着VDD的波动而波动,随着电池电量和电压的下降而变化,从而影响ADC的采集和测量精度,这种场景就可以考虑使用MCU出厂时VDD为3.3V时ADC采样1.2V电压得到的采样校准值,读取该采样校准值并转换成电压值,作为ADC的间接参考电压使用。

4、ADC内部1.2V参考电压采样值的读取

以MM32F0163D7P为例,查阅UM_MM32F0160_SC手册ADC章节的ADC系统框图可知ADC内部1.2V参考电压通道为通道15(VSENSOR),在读取ADC内部1.2V参考电压采样值前需配置ADC通道15并使能该通道,使用任意通道配置核心代码如下所示,本文后面8.3章节有详细的应用举例。

/* Sampling value of each channel of ADC */
uint16_t ADC_Channel_Samp_Value[4] = {0x00};
/* Assign ADC1 1.2V Vref channel_15 to RANK 3 */   
ADC_ANY_CH_Config(ADC1,3,ADC_Channel_VoltReference);
/* Enable ADC 1.2V channel_15 voltage reference */
ADC1->ADCFG |=  ADC_ADCFG_VSEN;
/* Get the sampling value of ADC 1.2V channel 15 */    
ADC_Channel_Samp_Value[3] = ADC1->ADDR15;

5、ADC内部1.2V参考电压校准值的读取

以MM32F0163D7P为例,读取出厂VDDA为3V3时采样1.2V电压的采样校准值,并换算成电压值,核心代码如下所示。本文8.4章节有详细的读取举例。

/* 1.2V sampling calibration value storage address */
#define ADC_1_2V_SAMPLE_CALI_VALUE_MEM_ADDR 0x1FFFF7E0
/* Read ADC 1.2V Sample value */
Vref_1_2V_Cali = *(uint16_t*)(ADC_1_2V_SAMPLE_CALI_VALUE_MEM_ADDR);
/* Convert to voltage value */
Vref_1_2V_Cali_V = (float)(Vref_1_2V_Cali * (3.3/4096));

6、ADC各通道采样电压值与1.2V通道采样电压值换算关系

定义Calcu_ADC_Channel_V为缓存ADC各通道采样电压值,gADC_Sample_Value为缓存ADC各通道的采样值。ADC_Channel_Samp_Value[4]为缓存ADC各通道的采样值包括ADC的1.2V通道实时采样值,Vref_1_2V_Cali为VDDA为3V3时采样1.2V电压的采样校准值,Vref_1_2V_Cali由以上章节5 ADC内部1.2V参考电压校准值的读取得到,换算成电压值为Vref_1_2V_Cali_V = (float)(Vref_1_2V_Cali * (3.3/4096)) ,则存在以下关系式:

Calcu_ADC_Channel_V / gADC_Sample_Value = Vref_1_2V_Cali_V /ADC_Channel_Samp_Value[i]

其中i表示ADC各通道号包括ADC的1.2V通道。

由此,推得ADC各个通道的采样电压值为

Calcu_ADC_Channel_V = (float)(gADC_Sample_Value * (Vref_1_2V_Cali_V/ADC_Channel_Samp_Value[3]))

ADC_Channel_Samp_Value[3]缓存ADC的1.2V通道的实时采样值。即:ADC各通道的采样电压值=ADC各通道的采样值*(ADC内部采样1.2V电压的采样校准值换算得到的电压值 / ADC的1.2V通道的实时采样值)。

7、根据出厂ADC内部1.2V参考电压的校准值反推VDDA的电压值

由于Vref_1_2V_Cali / ADC_V1_2V_Channel_Sample = VDDA / 4096

得到VDDA = ( Vref_1_2V_Cali * 4096 ) / ADC_V1_2_Channel_Sample

注意事项:读取ADC_V1_2_Channel_Sample 采样值前需配置和使能1.2V通道(MM32F0163D7P的ADC 1.2V通道为通道15)。注:本文章节4有提到配置方法。

8、ADC使用注意事项

1)ADC高达1Msps转换速率,每个通道可以独立的设置采样保持时间,但需注意的是采样保持时间最小为2.5个ADC时钟周期,最大为240.5个ADC时钟周期,在应用场景条件允许情况下,在此范围内适当加大采样保持时间可以保证ADC采样精度。条件允许情况下适当降低ADC时钟频率也可以保证采样精度,但ADC的时钟不得超过16MHz。

2)ADC的内部1.2V Vref通道转换周期比规则转换通道的转换周期稍大一些,如果对ADC的采样速率有应用场景的要求,建议使用ADC的注入通道功能。

3)ADC的采样值受到输入阻抗的影响,关于ADC输入阻抗的计算可参考对应MCU系列的DS手册,电气特性,工作条件的ADC特性。

4)使用ADC时应注意用作ADC功能的GPIO的IO类型,例如IO类型为“TC”类型即标准的IO类型,不容忍VDD电压即ADC的输入采样电压不得超过VDD电源电压。此外GPIO内部带有上下拉钳位二极管,用户应避免输入ADC的采样电压过高引起倒灌影响ADC的工作条件和采样精度(关于IO类型的说明请参考对应MCU系列的DS手册管脚定义章节)。

9、ADC内部1.2V参考电压的使用完整举例

1)以MM32F0163D7P为例,查阅DS手册得到GPIO与ADC通道对应关系如下表2所示。

3.png表2 GPIO与ADC通道对应关系

2)以MM32F0163D7P为例,完整举例ADC内部1.2V参考电压的使用,本示例外接采样3路ADC电压值,首先初始化3路ADC通道,ADC输入的GPIO的分别为PA0对应ADC_Channel_0,PA1对应ADC_Channel_1,PA2对应ADC_Channel_2,ADC的GPIO初始化代码如下所示。

void ADC_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    /* Enable GPIOA Clock */
    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
    GPIO_StructInit(&GPIO_InitStruct);
    /* PA0 ADC_CH0, PA1 ADC_CH1, PA2 ADC_CH2 */
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; 
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AIN;    
    GPIO_Init(GPIOA, &GPIO_InitStruct);
}

3)以MM32F0163D7P为例,初始化ADC为12bit精度,时钟为72M预分频为16分频,连续转换模式,数据格式右对齐,采样保持时间为240.5个ADC时钟周期,配置任意通道模式,配置并使能ADC内部1.2V通道Channel15,ADC的初始化代码如下所示。

void ADC_Configure(void)
{
    ADC_InitTypeDef ADC_InitStruct;
    RCC_APB2PeriphClockCmd(RCC_APB2ENR_ADC, ENABLE);    /* Enable ADC clock */
    ADC_StructInit(&ADC_InitStruct);
    ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStruct.ADC_PRESCARE   = ADC_PCLK2_PRESCARE_16;
    /* ADC continue scan convert mode */
    ADC_InitStruct.ADC_Mode = ADC_Mode_Continue;   
/* AD data right-justified */       
    ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; 
    ADC_Init(ADC1, &ADC_InitStruct);
    /* Configure ADC ADC_Channel_x Sample time */
    ADC_Channel_Sample_time_Configure(ADC1,ADC_Channel_0,ADC_Samctl_240_5); 
    ADC_Channel_Sample_time_Configure(ADC1,ADC_Channel_1,ADC_Samctl_240_5);
    ADC_Channel_Sample_time_Configure(ADC1,ADC_Channel_2,ADC_Samctl_240_5);
    ADC_ANY_Cmd(ADC1, DISABLE); /* Disable ADC ANYChannel */   
    ADC_ANY_NUM_Config(ADC1,3); /* Configure Multi-Channel num */ 
    /* Assign PA0 ADC1 channel_0 to RANK 0 */     
    ADC_ANY_CH_Config(ADC1,0,ADC_Channel_0); 
    /* Assign PA1 ADC1 channel_1 to RANK 1 */ 
    ADC_ANY_CH_Config(ADC1,1,ADC_Channel_1); 
    /* Assign PA2 ADC1 channel_2 to RANK 2 */  
    ADC_ANY_CH_Config(ADC1,2,ADC_Channel_2); 
    /* Assign ADC1 1.2V Vref channel_15 to RANK 3 */   
    ADC_ANY_CH_Config(ADC1,3,ADC_Channel_VoltReference); 
    ADC_ANY_Cmd(ADC1, ENABLE);   /* Enable ADC ANYChannel */   
    /* Enable ADC 1.2V channel_15 voltage reference */
    ADC1->ADCFG |=  ADC_ADCFG_VSEN; 
    ADC_Cmd(ADC1, ENABLE); /* Enable ADC */
}

4)以MM32F0163D7P为例,读取出厂VDDA为3V3时采样1.2V电压的采样校准值,并换算成电压值,代码如下所示。

#define VDDA        3.3F
#define ADC_12BIT   4096U
#define ADC_RESOLUTION VDDA / ADC_12BIT
/* 1.2V sampling calibration value storage address */
#define ADC_1_2V_SAMPLE_CALI_VALUE_MEM_ADDR 0x1FFFF7E0
/* 1.2V sampling calibration value */
uint16_t Vref_1_2V_Cali  = 0;
/* 1.2V sampling calibration value Convert to voltage value */
float Vref_1_2V_Cali_V = 0.0;
void Read_Factory_1_2V_Sample_CaliValue(void)
{
    /* Read ADC 1.2V Sample value */
    Vref_1_2V_Cali = *(uint16_t*)(ADC_1_2V_SAMPLE_CALI_VALUE_MEM_ADDR);
    /* Convert to voltage value */
    Vref_1_2V_Cali_V = (float)(Vref_1_2V_Cali * ADC_RESOLUTION);
}

5)以MM32F0163D7P为例,由以上9.4章节读取出厂VDDA为3V3时采样1.2V电压的采样校准值,由以上9.3章节ADC内部1.2V参考电压通道的配置和使能,ADC的1.2V采样电压的校准值根据以上章节6表述的公式,结合ADC的1.2V通道15的实时采样值可用于反推VDDA的电压值,同时该采样校准值换算成电压值后可间接作为ADC多通道连续采样的参考电压。ADC内部1.2V参考电压实时采样值,ADC各通道采样值的读取并换算成电压值以及在main函数初始化UART1打印功能,ADC外设的初始化,读取ADC采样1.2V的校准值,在while(1)主循环中调用获取ADC各通道采样电压值函数的详细实现的代码如下所示。本示例通过UART1每隔200ms毫秒打印输出ADC各通道采样并换算得到电压值到串口调试助手。

/* Buffer the VDDA voltage value of the reversed ADC */
float ADC_VDDA = 0.0;
/* Sampling value of each channel of ADC */
uint16_t ADC_Channel_Samp_Value[4] = {0x00};
/* Buffer the sampling value of each channel of the ADC */
uint16_t gADC_Sample_Value = 0;
/* Buffer the voltage value of each channel of ADC */
float Calcu_ADC_Channel_V = 0.0;
void Calcu_ADC_Each_Channel_Voltage_Value(void)
{
    uint8_t i = 0;
    /* Software starts the ADC conversion */
    ADC_SoftwareStartConvCmd(ADC1, ENABLE); 
    /* ADC conversion flag */
    while(ADC_GetFlagStatus(ADC1, ADC_IT_EOC) == 0);
    /* Clears the adc's pending flags */
    ADC_ClearFlag(ADC1, ADC_IT_EOC);
    /* Get the sampling value of ADC PA0 channel 0 */
    ADC_Channel_Samp_Value[0] = ADC1->ADDR0;
    /* Get the sampling value of ADC PA1 channel 1 */
    ADC_Channel_Samp_Value[1] = ADC1->ADDR1;
    /* Get the sampling value of ADC PA2 channel 2 */
    ADC_Channel_Samp_Value[2] = ADC1->ADDR2;   
    /* Get the sampling value of ADC 1.2V channel 15 */    
    ADC_Channel_Samp_Value[3] = ADC1->ADDR15;               
    /* The factory 1.2V sampling calibration value and the 1.2V channel sampling value reverse the VDDA voltage value */
    ADC_VDDA = (Vref_1_2V_Cali_V *4096) / ADC_Channel_Samp_Value[3];
    printf("ADC_VDDA = %0.2f\r\n",ADC_VDDA);
    /* Print out the voltage value of each channel calculated by ADC */
    for(i = 0; i < 4; i++)
    {
        /* Obtain the sampling value of each channel of the ADC */
        gADC_Sample_Value = ADC_Channel_Samp_Value[i];      
        /* Calculate the voltage value of each channel according to the sampling value of each channel of the ADC */
        Calcu_ADC_Channel_V = (float)(gADC_Sample_Value * (Vref_1_2V_Cali_V/ADC_Channel_Samp_Value[3]));
        if(i == 3)
        {         
            printf("ADC1_CH15_VREF: %0.2fV\r\n",Calcu_ADC_Channel_V); 
        }
        else
        {
            printf("ADC1_CH%d: %0.2fV\r\n",i,Calcu_ADC_Channel_V);  
        }               
    } 
}


int main(void)
{
    /* SysTick Init */
    DELAY_Init();
    /* UART1 Init */
    CONSOLE_Init(115200);
    /* ADC Pin Configure */
    ADC_GPIO_Configure();
    /* ADC function Configure */
    ADC_Configure();
    /* Read factory 1.2V sample calibration value and convert to voltage value */
    Read_Factory_1_2V_Sample_CaliValue();
    while (1)
    {
        if(Tick_200ms_Flag == true)
        {
            Tick_200ms_Flag = false;
            /* Get and Calculate the voltage value of each channel of the ADC */
            Calcu_ADC_Each_Channel_Voltage_Value();
        }
    }
}

6)编译程序把烧录程序到核心板,如下图2所示,3路ADC分别采集外接的电压值,ADC各通道采样换算得到的电压值打印输出到串口调试助手,测试结果分别为ADC_VDDA该值是通过读取出厂VDDA为3V3时采样1.2V电压的采样校准值结合1.2V通道15的实时采样值反推得到的VDDA(即VDD的电压值)的电压值。ADC_CH0采集1.0V电压,实际采到1.03V,ADC_CH1接GND采集0V电压,实际采集到0V,ADC_CH2采集0.50V电压,实际采集到0.50V。其中ADC_CH15_VREF采集ADC内部1.2V通道实时采样得到的电压值,采集到1.18V,该值可参考以上9.4章节表述的出厂VDDA为3V3时采样1.2V电压的采样校准值换算得到的电压值接近。

4.png

图2

7)如下图3所示,换一组电压采集,ADC_CH0采集2.50V电压实际采集到2.51V,ADC_CH1采集3.32V电源电压实际采集到3.31V,ADC_CH2采集1.50V电压,实际采到1.50V电压。以上测试均在ADC允许的误差范围。

5.png

图3

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 22

模数转换器(ADC)的主要功能是将模拟量转换为数字量,方便MCU进行处理。下面以CW32L083为例介绍CW系列的模数转换器的特点和功能,并提供演示实例。

一、概述

CW32L083 内部集成一个 12 位精度、最高 1M SPS 转换速度的逐次逼近型模数转换器 (SAR ADC),最多可将 16 路模拟信号转换为数字信号。现实世界中的绝大多数信号都是模拟量,如光、电、声、图像信号等,都要由 ADC 转换成数字信号,才能由 MCU 进行数字化处理。

二、主要特性

• 12 位精度 

• 可编程转换速度,最高达 1M SPS 

• 16 路输入转换通道:13 路外部引脚输入 - 内置温度传感器 - 内置 BGR 1.2V 基准 - 1/3 VDDA 电源电压 

• 4 路参考电压源(Vref):- VDDA 电源电压 - ExRef(PB00)引脚电压 - 内置 1.5V 参考电压 - 内置 2.5V 参考电压 

• 采样电压输入范围:0 ~ Vref

 多种转换模式,全部支持转换累加功能 - 单次转换 - 多次转换 - 连续转换 - 序列扫描转换 - 序列断续转换 

• 支持单通道、序列通道两种通道选择,最大同时支持 8 个序列 

• 支持输入通道电压阈值监测

• 内置信号跟随器,可转换高阻抗输入信号 

• 支持片内外设自动触发 ADC 转换 

• 支持 ADC 转换完成触发 DMA

三、转换时序

ADC 的转换时序如下图所示:

1.png


向 ADC 控制寄存器 ADC_CR0 的 EN 位域写入 1,使能 ADC 模块。 

ADC_CR0.EN 由 0 变为 1 约 40μs 后 ADC_ISR.READY 标志位置 1,表示模拟电路初始化完成,可以开始进行 ADC 转换。 

向 ADC 启动寄存器 ADC_START 的 START 位域写入 1,启动 ADC 转换,转换完成后硬件自动清零。 

ADC 工作时钟 ADCCLK,由系统时钟 PCLK 经预分频器分频得到,通过控制寄存器 ADC_CR0 的 CLK 位域可选择 1 ~ 128 分频


四、工作模式

ADC 控制寄存器 ADC_CR0 的 MODE 位域配置 ADC 工作模式


启动 ADC 转换,可通过向 ADC 启动寄存器 ADC_START 的 START 位域写 1;也可通过其他外设来触发。

2.png

五、实际案例

GTIM1定时器定时1S,定时器1S中断触发启动ADC转换,采样AIN1,并通过GTIM2以PWM方波输出ADC采样值:PWM占空比50%,周期为1Hz-5000Hz,对应ADC的0-4095采样值。

1.配置ADC测试IO口

void ADC_PortInit(void) 
{ 
    REGBITS_SET(CW_SYSCTRL->AHBEN, SYSCTRL_AHBEN_GPIOA_Msk); //打开GPIO时钟 
    REGBITS_SET(CW_SYSCTRL->APBEN2, SYSCTRL_APBEN2_ADC_Msk); //打开ADC时钟 
    PA01_ANALOG_ENABLE();//set PA01 as AIN1 INPUT 
}

2.LED初始化

void LED_Init(void) 
{ 
    GPIO_InitTypeDef GPIO_InitStructure = {0}; 
    REGBITS_SET(CW_SYSCTRL->AHBEN, SYSCTRL_AHBEN_GPIOC_Msk); //打开GPIO时钟 
    /* Configure the GPIO_LED pin */ 
    GPIO_InitStructure.Pins = GPIO_PIN_2 | GPIO_PIN_3; 
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; 
    GPIO_Init(CW_GPIOC, &GPIO_InitStructure); 
    PC02_SETLOW();//LEDs are off. PC03_SETLOW(); 
}

3.PWM IO初始化

void PWM_PortInit(void) 
{ 
    GPIO_InitTypeDef GPIO_InitStructure = {0}; 
    /* PA5 PWM 输出 */ 
    __RCC_GPIOA_CLK_ENABLE(); 
    /* Configure the PWM output pin */ 
    GPIO_InitStructure.Pins = GPIO_PIN_5; 
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; 
    GPIO_Init(CW_GPIOA, &GPIO_InitStructure); 
    PA05_AFx_GTIM2CH1(); 
}

4.GTIM初始化

void GTIM_Init(void) 
{ 
    GTIM_InitTypeDef GTIM_InitStruct = {0}; 
    
    //REGBITS_SET(CW_SYSCTRL->APBEN1, SYSCTRL_APBEN1_GTIM1_Msk); //打开GTIM1 
    __RCC_GTIM1_CLK_ENABLE(); //打开GTIM1时钟 GTIM_InitStruct.Mode = GTIM_MODE_TIME;
    GTIM_InitStruct.OneShotMode = GTIM_COUNT_CONTINUE; 
    GTIM_InitStruct.Prescaler = GTIM_PRESCALER_DIV1024; 
    GTIM_InitStruct.ReloadValue = 62499ul; //T=1s. 
    GTIM_InitStruct.ToggleOutState = DISABLE; 
    GTIM_TimeBaseInit(CW_GTIM1, &GTIM_InitStruct); 
    GTIM_ITConfig(CW_GTIM1, GTIM_IT_OV, ENABLE); 
    NVIC_ClearPendingIRQ(GTIM1_IRQn); 
    NVIC_EnableIRQ(GTIM1_IRQn);
    NVIC_SetPriority(GTIM1_IRQn, 0x03); 
    
    __RCC_GTIM2_CLK_ENABLE();//打开GTIM2时钟 
    GTIM_InitStruct.ReloadValue = 0xFFFFu; 
    GTIM_InitStruct.ToggleOutState = ENABLE;
    GTIM_TimeBaseInit(CW_GTIM2, &GTIM_InitStruct); 
    valuePeriod = GTIM_InitStruct.ReloadValue; 
    valuePosWidth = valuePeriod >> 1u; 
    GTIM_OCInit(CW_GTIM2, GTIM_CHANNEL1, GTIM_OC_OUTPUT_PWM_HIGH); 
    GTIM_SetCompare1(CW_GTIM2, valuePosWidth); 
    GTIM_Cmd(CW_GTIM2, ENABLE); 
}

5.主程序main

uint16_t valueAdc; 
uint32_t valueAdcAcc; 
volatile uint8_t gFlagIrq; 
uint16_t gCntEoc = 0; 
uint8_t cntSample; 
float fTsDegree; 
uint32_t valuePeriod; 
uint32_t valuePosWidth; 
uint32_t valueReload = 0xFFFFu; 
int main(void) 
{   
    uint8_t res; 
    ADC_InitTypeDef ADC_InitStructure = {0}; 
    ADC_WdtTypeDef ADC_WdtStructure = {0};
    ADC_SingleChTypeDef ADC_SingleChStructure = {0}; 
    RCC_HSI_Enable(RCC_HSIOSC_DIV6);     //以下从HSI切换到PLL 
    RCC_PLL_Enable(RCC_PLLSOURCE_HSI, 8000000UL, RCC_PLL_MUL_8); 
    //开启PLL,PLL源为HSI 
    __RCC_FLASH_CLK_ENABLE();//打开FLASH时钟 
    FLASH_SetLatency(FLASH_Latency_3); 
    res = RCC_SysClk_Switch(RCC_SYSCLKSRC_PLL); //切换系统时钟到PLL:64MHz。 
    ADC_PortInit();//配置ADC测试IO口 
    LED_Init();//LED初始化 
    PWM_PortInit();
    GTIM_Init(); 
    ADC_StructInit(&ADC_InitStructure); //ADC默认值初始化 
    ADC_WdtInit(&ADC_WdtStructure); //ADC模拟看门狗通道初始化 
    ADC_InitStructure.ADC_ClkDiv = ADC_Clk_Div128;    //ADCCLK:500KHz. 
    ADC_InitStructure.ADC_InBufEn = ADC_BufEnable; 
    ADC_InitStructure.ADC_SampleTime = ADC_SampTime10Clk; 
    ADC_SingleChStructure.ADC_DiscardEn = ADC_DiscardNull; //配置单通道转换模式
    ADC_SingleChStructure.ADC_Chmux = ADC_ExInputCH1; //选择ADC转换通道 
    ADC_SingleChStructure.ADC_InitStruct = ADC_InitStructure; 
    ADC_SingleChStructure.ADC_WdtStruct = ADC_WdtStructure; 
    ADC_SingleChOneModeCfg(&ADC_SingleChStructure); 
    ADC_ITConfig(ADC_IT_EOC, ENABLE); 
    ADC_EnableIrq(ADC_INT_PRIORITY);
    ADC_ClearITPendingAll(); 
    ADC_Enable();//ADC使能 
    ADC_ExtTrigCfg(ADC_TRIG_GTIM1, ENABLE); //ADC外部中断触发源配置 
    GTIM_Cmd(CW_GTIM1, ENABLE);
    while (1) 
    { 
        while (!(gFlagIrq & ADC_ISR_EOC_Msk)); 
        gFlagIrq = 0u; PC03_TOG(); 
        valueAdc = ADC_GetConversionValue(); 
        valueReload = ((4095u * 125000ul) / (4999u * valueAdc + 4095u) + 1) >> 1; 
        GTIM_SetCounterValue(CW_GTIM2, 0u);     //reset. 
        GTIM_SetReloadValue(CW_GTIM2, valueReload); 
        GTIM_SetCompare1(CW_GTIM2, valuePosWidth); //等待ADC外部中断触发源启动下一次ADC转换 
    } 
}

6.实验展示

通用定时器GTIM1定时1s自动触发ADC模块进行转换,ADC通道为AIN1:PA01。

通用定时器GTIM2将AIN1的ADC采样值转换成频率可变的PWM方波,占空比50%,使用PA05作为PWM输出。ADC采样值为0时,PWM方波频率为1Hz;ADC采样值为4095时,PWM方波频率为5KHz。

3.png

来源:武汉芯源半导体

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 31

页面

订阅 RSS - ADC