Diving into the STM32 HAL-----ADC笔记
将模拟外设连接到微控制器是很常见的。在数字时代,仍然有很多产生模拟信号的设备:传感器、电位计、变送器和音频外设只是产生可变电压的模拟设备的几个例子,该电压通常在固定的间隔内。通过读取此电压,我们可以将其转换为有助于我们的固件处理的数字实体。例如,TMP36 是一种非常流行的温度传感器,它产生与电路工作电压(据说提供比例输出)和环境温度成比例的可变电压。
所有 STM32 微控制器都至少提供一个模数转换器 (ADC),该外设能够通过专用 I/O 获取多个输入电压,并将它们转换为数字。将输入电压与众所周知的固定电压(也称为参考电压)进行比较。该参考电压可以来自 VDDA 域,也可以在具有高引脚数的 MCU 中由外部和固定参考电压发生器提供(这些 MCU 提供名为 VREF+ 的专用引脚)。大多数 STM32 MCU 都提供 12 位 ADC。其中一些来自 STM32F3 和 STM32H7 产品组合,甚至还有一个 16 位 ADC。
与目前看到的其他 STM32 外设不同,ADC 在各种 STM32 系列之间甚至在给定系列内部都可能有很大差异。因此,将只介绍这个有用的外围设备,让读者负责深入分析他正在考虑的特定 MCU 中的 ADC。在我们分析 STM32 微控制器中 ADC 以及相关 CubeHAL 提供的功能之前,最好先快速介绍一下该外设的工作原理。
1、SAR ADC 简介
在几乎所有的 STM32 微控制器中,ADC 都实现为 12 位逐次逼近寄存器 ADC。根据销售类型和使用的封装,它可以具有可变数量的多路复用输入通道(在大多数具有高引脚数的 STM32 MCU 中通常超过 10 个通道),允许测量来自外部源的信号。此外,还提供一些内部通道:一个用于内部温度传感器 (VSENSE) 的通道,一个用于内部参考电压 (VREF INT) 的通道,一个用于监控外部 VBAT 电源的通道,以及一个用于监控提供原生单色无源 LCD 控制器的 MCU 中的 LCD 电压的通道(例如,STM32L053 就是其中之一)。在较新的STM32系列(STM32F3/L4/L4+/L5/G0/G5/H7)中实现的ADC也能够转换全差分输入。
各种通道的 A/D 转换可以在单次、连续、扫描或不连续模式下进行。ADC 的结果存储在左对齐或右对齐的 16 位数据寄存器中。此外,ADC 还实现了模拟看门狗功能,允许应用程序检测输入电压是否超出用户定义的上限或下限阈值:如果发生这种情况,将触发专用 IRQ。
上图是 ADC的结构示意图。输入选择和扫描控制单元执行 ADC 输入源的选择。根据转换模式(单次、扫描或连续模式),该装置会自动在输入通道之间切换,以便可以定期对每个进行采样。该装置的输出馈送给 ADC。
上图还显示了 ADC 的另一个重要部分:启动和停止控制单元。它的作用是控制 A/D 转换过程,它可以由软件或可变数量的输入源触发。此外,它在内部连接到一些定时器的 TRGO 线,因此可以在 DMA 模式下自动执行时间驱动的转换。我们将分析ADC 外设这个重要的模式。
上图显示了构成 SAR ADC 单元的主要模块。输入信号通过 SHA 单元。如前面图所示,开关和电容器与 ADC 输入串联。该部分代表上图所示的采样保持 (SHA) 单元,这是所有 ADC 都提供的功能。该装置在转换周期内保持输入信号恒定方面发挥着重要作用。得益于内部计时单元,它由可配置的 clock 调节,SAR 通过关闭/打开前面图中的 “switch” 来不断连接/断开输入源。为了保持输入的电压电平恒定,SHA 是通过电容器网络实现的:这确保了在 A/D 转换期间源信号保持在一定水平,这是一个需要给定时间的过程,具体取决于所选的转换频率。
SHA 模块的输出馈送到一个比较器,该比较器将其与内部 DAC 产生的另一个信号进行比较。比较结果被发送到 logic unit,该 logic unit 根据特征明确的算法计算 input 信号的数值表示。该算法是 SAR ADC 与其他 A/D 转换器的区别。
逐次逼近算法通过将输入信号与内部 DAC 产生的电压进行比较来计算输入信号的电压,该电压是 VREF 电压的一小部分:如果输入信号高于此内部参考电压,则该值会进一步增加,直到输入信号更低。最终结果将对应于一个从 0 到最大 12 位无符号整数的数字,即 2^12 − 1 = 4095。假设 VREF = 3300mV ,我们得到3300mV 用4095表示。这意味着 1bit = 3300 / 4095 ≈ 0.8mV 。例如,等于 2.5V 的输入电压将被转换为: x = 4095 / 3300mV × 2500mV = 3102。
SAR 算法的工作方式如下:
1. 输出数据寄存器归零,MSB 位设置为 1。这将对应于内部 DAC 生成的明确定义的电压电平。
2. 将 DAC 的输出与输入信号 VIN 进行比较: 1. 如果 VIN 高于 VIN,则保留该位为 1;2. 如果 VIN 低于 VIN,则 bit 设置回 0;
3. 算法继续处理数据寄存器中的下一个 MSB 位,直到所有位都设置为 1 或 0。
上图表示 4 位 ADC 内部 SAR logic unit 进行的转换过程。让我们考虑以红色突出显示的路径,并假设 VIN = 2700mV 和 VREF = 3300mV 。该算法首先将 MSB 设置为 1,这对应于 1000 = 8。这意味着:
当 VIN 高于 1760mV 时,第 4 位等于 1,算法传递到下一个 MSB 位。数据寄存器现在等于 1100 = 12,DAC 产生等于 2640mV 的输出。如果 VIN 仍然高于此值,则第 3 位再次等于 1。寄存器设置为 1110 = 14,这导致内部电压等于 3080mV。这次 VIN 较低,第二位重置为零。该算法现在将第 1 位设置为 1,这导致内部电压等于 2860mV。该值仍然高于 VIN,算法将最后一位重置为零。因此,ADC 检测到输入电压接近 2640mV。显然,ADC 提供的分辨率越高,转换后的值就越接近 VIN。
SAR 算法本质上是在二叉树中执行搜索。这种算法的最大优点是转换以 N 个周期执行,其中 N 对应于 ADC 分辨率。因此,12 位 ADC 需要 12 个周期来执行转换。但是一个周期可以持续多久呢?每秒的周期数,即 ADC 频率,是 ADC 的性能评估参数。SAR ADC 的速度非常快,尤其是在 ADC 分辨率降低的情况下(采样位越少,每次转换的周期就越少)。但是,由于电流流入引脚,源和 MCU 引脚之间的模拟信号源或串联电阻 (RIN) 的阻抗会导致其两端的电压降。
内部电容器网络的充电(我们用 Cadc 表示)由前面图中的开关控制,其电阻等于 Radc。随着源电阻的增加(即 Rtot = Radc + Rin),保持电容器充满电所需的时间会增加。
上图显示了模拟信号源电阻效应。Cadc 的有效充电受 Rtot 控制,因此充电时间常数变为 tC = (Radc + Rin) × Cadc。如果采样时间小于通过 Rtot 为 Cadc 充满电所需的时间(tS < tC),则 ADC 转换的数字值小于实际值。一般来说,需要等待 tC 的倍数才能达到合理的精度。
对于高速 A/D 转换,在电路板设计过程中考虑 PCB 布局和适当去耦的影响非常重要。ST提供了一个写得很好的应用笔记 AN2834(https://bit.ly/1rHj9ZN),其中提供了几个重要的技巧,以充分利用 STM32 MCU 中集成的 ADC。
2、HAL_ADC 模块
为了操作 ADC 外设,HAL 定义了 C 结构ADC_HandleTypeDef,其定义方式如下:
typedef struct {
ADC_TypeDef *Instance; /* Pointer to ADC descriptor */
ADC_InitTypeDef Init; /* ADC initialization parameters */
__IO uint32_t NbrOfCurrentConversionRank; /* ADC number of current conversion rank */
DMA_HandleTypeDef *DMA_Handle; /* Pointer to the DMA Handler */
HAL_LockTypeDef Lock; /* ADC locking object */
__IO uint32_t State; /* ADC communication state */
__IO uint32_t ErrorCode; /* Error code */
} ADC_HandleTypeDef;
• Instance:是指向我们将要使用的 ADC 描述符的指针。例如,ADC1 是第一个 ADC 外设的描述符。
• Init:是 C 结构ADC_InitTypeDef的实例,用于配置 ADC。
• NbrOfCurrentConversionRank:对应于常规转换组中的当前第 i 个通道(排名)。
• DMA_Handle:这是指向配置为在 DMA 模式下执行 A/D 转换的 DMA 处理程序的指针。它由 __HAL_LINKDMA() 宏自动配置。
使用 C 结构体的实例执行 ADC 配置ADC_InitTypeDef,其定义方式如下:
typedef struct {
uint32_t ClockPrescaler; /* Selects the ADC clock frequency */
uint32_t Resolution; /* Configures the ADC resolution mode */
uint32_t ScanConvMode; /* The scan sequence direction. */
uint32_t ContinuousConvMode; /* Specifies whether the conversion is performed in Continuous or Single mode */
uint32_t DataAlign; /* Specifies whether the ADC data alignment is left or right */
uint32_t NbrOfConversion; /* Specifies the number of ranks that will be converted within the regular group sequencer */
uint32_t NbrOfDiscConversion; /* Specifies the number of discontinuous conversions in which the main sequence of regular group */
uint32_t DiscontinuousConvMode; /* Specifies whether the conversion sequence of regular group is performed in Complete-sequence/Discontinuous sequence */
uint32_t ExternalTrigConv; /* Select the external event used to trigger the start of conversion */
uint32_t ExternalTrigConvEdge; /* Select the external trigger edge and enable it */
uint32_t DMAContinuousRequests; /* Specifies whether the DMA requests are performed in one shot or in continuous mode */
uint32_t EOCSelection; /* Specifies what EOC (End Of Conversion) flag is used for conversion polling and interruption */
} ADC_InitTypeDef;
• ClockPrescaler:定义 ADC 模拟电路部分的时钟 (ADCCLK) 的速度。在上一段中,我们已经看到 ADC 有一个内部时序单元,用于控制输入开关的开关频率。ADCCLK 确定此 timing unit 的速度,它会影响每秒的样本数,因为它定义了每个 conversion cycle使用的时间量。该时钟由外设时钟由可编程预分频器分频产生,该预分频器允许 ADC 在 fPCLK/2,/4,/6 或 /8 下工作。在一些STM32 MCU中,ADCCLK也可以从HSI振荡器派生而来。该字段的值会影响 MCU 中实现的所有 ADC 的 ADCCLK 速度。
• 分辨率:除了 STM32F1 MCU 的 ADC 不允许选择样本的分辨率之外,使用此字段可以定义 A/D 转换分辨率。它可以采用下表中的值。
分辨率越高,一秒钟内可能的转换就越少。如果 speed 与您的应用程序无关,强烈建议将 bit resolution 设置为最大,将 conversion speed 设置为最小。
• ScanConvMode:此字段可以采用值 ENABLE 或 DISABLE,用于启用/禁用扫描转换模式。
• ContinuousConvMode:指定转换是在单一模式还是连续模式下执行,并且可以采用值 ENABLE 或 DISABLE。
• NbrOfConversion:指定将在扫描模式下转换的常规组的通道数。此数字应等于配置的等级数。
• DataAlign:指定转换结果的数据对齐方式。ADC 数据寄存器实现为半字寄存器。由于仅使用 12-bits 来存储转换,因此此参数确定这些位在 register 内的对齐方式。它可以采用值 ADC_DATAALIGN_LEFT 或 ADC_DATAALIGN_RIGHT。
• ExternalTrigConvEdge:选择外部触发源以使用计时器驱动转换。
• EOCSelection:根据转换模式(单次或连续转换),ADC 相应地设置转换结束 (EOC) 标志。ADC 轮询或中断 API 使用此字段来确定转换何时完成,它可以采用 ADC_EOC_SEQ_CONV 的值表示连续转换,ADC_EOC_SINGLE_CONV 的值表示单次转换。
在我们开始做一个实际示例之前,我们必须分析另外两个主题:如何配置输入通道以及如何对其输入信号进行采样。
2.1、转换模式
STM32 MCU中实现的ADC提供了多种转换模式,可用于处理不同的应用场景。现在我们将简要介绍其中最相关的:ST 的 AN3116(https://bit.ly/39EFUpk)描述了 ADC 提供的所有可能的转换模式。
2.1.1、单通道、单转换模式
这是最简单的 ADC 模式。在此模式下,ADC 执行单个通道的单次转换(单个采样),如下图所示,并在转换完成时停止。
2.1.2、扫描单转换模式
这种模式在一些 ST 文档中也称为多通道单模,用于在独立模式下连续转换某些通道。使用 ranks,可以使用此 ADC 模式以不同的采样时间和自定义顺序连续配置最多 16 个通道的任何序列。例如,可以执行下图中所示的序列。
这样,就不必在转换过程中停止 ADC,即可使用不同的采样时间重新配置下一个通道。此模式可节省额外的 CPU 负载和繁重的软件开发。扫描转换在 DMA 模式下执行。
例如,当启动系统取决于某些参数(例如知道机械臂系统中机械臂尖端的坐标)时,可以使用此模式。在这种情况下,您必须在通电时读取机械臂系统中每个关节的位置,以确定机械臂尖端的坐标。此模式还可用于对多个信号电平(电压、压力、温度等)进行单次测量,以决定是否可以启动系统,以保护人员和设备。
2.1.3、单通道、连续转换模式
此模式在常规通道转换中连续无限地转换单个通道。连续模式功能允许 ADC 在后台工作。ADC 连续转换通道,无需 CPU 的任何干预。此外,DMA 可以在循环模式下使用,从而降低 CPU 负载。
例如,这种 ADC 模式可以用于监控电池电压、使用 PID 测量和调节烘箱温度等。
2.1.4、扫描连续转换模式
这种模式也称为多通道连续模式,可用于在 ADC 处于独立模式下连续转换某些通道。使用 ranks,您可以连续配置最多 16 个通道的任意序列,具有不同的采样时间和不同的顺序。此模式与多通道单转换模式类似,不同之处在于它不会在序列的最后一个通道之后停止转换,而是从第一个通道重新开始转换序列并无限期地继续。扫描转换在 DMA 模式下执行。
例如,此模式可用于监控多个电池充电器中的多个电压和温度。在充电过程中读取每个电池的电压和温度。当电压或温度达到最高水平时,应将相应的电池与充电器断开。
2.1.5、注入式转换模式
此模式适用于由外部事件或软件触发转换时使用。注入的组优先于常规通道组。它会中断常规通道组中当前通道的转换。
例如,此模式可用于将通道的转换同步到事件。在电机控制应用中,晶体管开关会产生噪声,影响 ADC 测量并导致错误转换,这很有趣。因此,使用定时器,可以实现注入转换模式,将 ADC 测量延迟到晶体管开关之后。
2.1.6、双模式
STM32微控制器提供双模式,具有两个ADC:ADC1主器件和ADC2从器件。ADC1 和 ADC2 触发器在内部同步,用于常规和注入通道转换。ADC1 和 ADC2 协同工作。在某些器件中,最多有 3 个 ADC:ADC1、ADC2 和 ADC3。在这种情况下, ADC3始终独立工作,不与其他 ADC 同步。
双模式的工作原理是,当转换结束时,ADC1 和 ADC2 的结果同时保存在 ADC1 32 位数据寄存器中。通过分离两个结果,我们可以同时获取来自两个独立通道的数据。有关双模式的更多信息,请参阅 ST AN3116(https://bit.ly/39EFUpk)。
2.2、通道选择
根据所使用的 STM32 系列和封装,STM32 MCU 中的 ADC 可以转换来自可变数量通道的信号。在 F0 和 L0 系列中,通道的分配是固定的:第一个始终是 IN0,第二个是 IN1,依此类推。用户只能决定是否启用通道。这意味着在扫描模式下,第一个采样通道将始终为 IN0,第二个 IN1,依此类推。其他STM32 MCU 提供了组的概念。组由转换序列组成,这些序列可以在任何通道上以任何顺序完成。虽然输入通道是固定的并绑定到特定的 MCU 引脚(即 IN0 是第一个通道,IN1 是第二个通道,依此类推),但它们可以在逻辑上重新排序以形成自定义采样序列。通过为通道分配一个从 1 到 16 的索引来执行通道的重新排序。此索引在 CubeHAL 中称为 rank。
上图显示了这个概念。尽管 IN4 通道是固定的(例如,它连接到 STM32F401RE MCU 中的 PA4 引脚),但它可以在逻辑上分配给 1 级,以便它成为第一个被采样的通道。那些提供这种可能性的 MCU 还允许单独选择每个通道的采样速度,这与 F0/L0 MCU 不同,后者的配置是 ADC 范围的。channel/rank 配置是使用 C 结构ADC_ChannelConfTypeDef的实例来执行的,该实例按以下方式定义:
typedef struct {
uint32_t Channel; /* Specifies the channel to configure into ADC rank */
uint32_t Rank; /* Specifies the rank ID */
uint32_t SamplingTime; /* Sampling time value for the selected channel */
uint32_t Offset; /* Reserved for future use, can be set to 0 */
} ADC_ChannelConfTypeDef;
• Channel:指定频道 ID。它可以假设值 ADC_CHANNEL_0,ADC_CHANNEL_ 1...ADC_CHANNEL_N,具体取决于可用通道的有效数量。
• Rank:对应于与通道关联的排序。它可以采用 1 到 16 之间的值,这是用户可定义的排序的最大数量。
• SamplingTime:指定要为所选通道设置的采样时间值,该值对应于 ADC 周期数。此数字不能是任意的,但它是所选值列表的一部分。CubeMX 提供了特定 MCU 的可接受值列表。
每个 ADC 存在两组:
• 一个常规组,由多达 16 个通道组成,对应于扫描转换期间采样通道的顺序。
• 一个注入组,由最多 4 个通道组成,如果执行注入转换,则对应于注入通道的顺序。
2.3、ADC 分辨率和转换速度
通过降低 ADC 分辨率可以执行更快的转换。实际上,采样时间由固定的循环数(通常为 3)加上可变的循环数定义,具体取决于 A/D 分辨率。每种分辨率的最小转换时间如下:
• 12 位:3 + ∼12 = 15 个 ADCCLK 周期
• 10 位:3 + ∼10 = 13 个 ADCCLK 周期
• 8 位: 3 + ∼8 = 11 个 ADCCLK 周期
• 6 位: 3 + ∼6 = 9 个 ADCCLK 周期
通过降低分辨率,可以增加每秒的最大样本数,在某些 STM32 MCU 中甚至达到 15Msps 以上。请记住, ADCCLK 是从外设 clock派生的: 这意味着 SYSCLK 和 PCLK 速度会影响每秒的最大样本数。
2.4、轮询模式下的 A/D 转换
与大多数 STM32 外设一样,ADC 可以在三种模式下驱动:轮询、中断和 DMA 模式。定时器最终可以驱动最后一种模式,以便以固定的间隔进行 A/D 转换。当我们需要以给定频率对信号进行采样时,这非常有用,例如在音频应用程序中。
使用传递给 HAL_ADC_Init() 例程的 ADC_InitTypeDef 结构实例配置 ADC 控制器后,我们可以使用 HAL_ADC_Start() 函数启动外设。根据所选的转换模式,ADC 将连续或仅转换每个选定的输入------在这种情况下,要再次转换选定的输入,我们需要在再次调用 HAL_ADC_Start() 之前调用 HAL_ADC_Stop() 函数。
在轮询模式下,我们使用函数
HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, uint32_t Timeout);
来确定 A/D 转换何时完成,并且结果在 ADC 数据寄存器内可用。该函数接受指向 ADC 处理程序描述符的指针和 Timeout 值,该值表示我们愿意等待的最长时间(以毫秒为单位)。或者,我们可以传递 HAL_MAX_DELAY 以无限期等待。
要检索结果,我们可以使用函数:
uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef* hadc);
现在,我们终于准备好分析一个完整的示例了。它使用所有 STM32 MCU 中可用的内部温度传感器作为 ADC 的源。温度传感器连接到内部 ADC 输入。确切的输入编号取决于特定的 MCU 系列和封装。例如,在 STM32F401RE MCU 中,温度传感器连接到 ADC1 外设的 IN18。但是,HAL 旨在抽象化此特定方面。在我们分析实际代码之前,最好快速了解一下温度传感器的电气特性,这些特性报告在 MCU 的数据表中。
上表显示了STM32F401RE MCU 中温度传感器的特性。它的典型精度为 1°C,平均斜率为 2.5mV/°C。 此外,温度传感器结点的工作原理是,在 25°C 时,电压降为 760mV。这意味着,为了计算检测到的温度,我们可以使用以下公式:
STM32 内部温度传感器在 IC 生产期间经过工厂校准。通常在 30°C 和 110°C 之间采样两个温度。 它们分别称为 TS_CAL1 和 TS_CAL2。检测到的温度存储在非易失性系统内存中。确切的内存地址在特定的数据表中报告。利用这些数据,可以对检测到的温度进行线性化,从而将误差引导回 1°C 的典型精度值。ST 提供了专门针对该主题的应用说明:AN3964(https://bit.ly/1XfbuO6)。但是,请记住,内部温度传感器测量 IC 的温度(因此也测量 PCB 的温度)。根据具体的 STM32 系列、MCU 运行频率、执行的操作、启用的外设、电源部分等,检测到的温度可以远高于有效环境温度。例如,笔者已经验证了在 200MHz 下运行的 STM32F7 MCU 在 20°C 的室温下,工作温度约为 ∼45°C。
以下代码显示了如何在 STM32F401RE MCU 中执行内部温度传感器输出的 A/D 转换。
/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
UART_HandleTypeDef huart2;
char msg[30];
uint16_t rawValue;
float temp;
int main(void) {
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_ADC1_Init();
/* Starts the ADC */
HAL_ADC_Start(&hadc1);
while (1) {
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
rawValue = HAL_ADC_GetValue(&hadc1);
temp = ((float)rawValue) / 4095 * 3300;
temp = ((temp - 1430.0) / 4.3) + 25;
sprintf(msg, "ADC rawValue: %hu\r\n", rawValue);
HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
sprintf(msg, "Temperature: %f\r\n", temp);
HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
}
}
static void MX_ADC1_Init(void) {
ADC_ChannelConfTypeDef sConfig = {0};
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) */
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
HAL_ADC_Init(&hadc1);
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. */
sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}
首先要分析的是函数 MX_ADC1_Init() ,它初始化 ADC1 外设。ADC 被配置为 ADCCLK(即 ADC 模拟部分的时钟)是 PCLK2 频率的一半,在以最大速度运行STM32F401RE为 84MHz。接下来,将 ADC 分辨率配置为最大值:12 位。扫描转换模式被禁用,而连续转换模式被启用,这样我们就可以重复轮询转换,而无需停止然后重新启动 ADC。因此,EOC 标志设置为 ADC_EOC_SEQ_CONV。请注意,在这种情况下,参数 NbrOfConversion 完全没有意义且多余,因为单个转换模式会自动假定采样通道数等于 1。
配置温度传感器通道并为其分配等级 1:即使我们不执行扫描转换,我们也需要为使用的通道指定等级。采样时间设置为 480 个周期:这意味着,给定 84MHz 的时钟速度,并考虑到 ADCCLK 设置为 PCLK 速度的一半,我们每 10μs执行一次 A/D 转换。
为什么我们选择这种转换速度?原因来自前面电气表中,其中指出 ADC 采样时间 TS_temp 等于 10μs,精度为 1°C。 例如,如果通过将 SamplingTime 字段设置为 ADC_SAMPLETIME_3CYCLES将速度提高到 3 个周期,将看到转换后的结果通常是完全错误的。
在同一表中,可以找到另一个有趣的数据:温度传感器启动时间(即传感器启用时稳定输出电压所需的时间)在 6 到 10μs 之间。但是,我们不需要注意这方面,因为 HAL_ADC_ConfigChannel() 例程旨在正确处理启动时间。这意味着,该函数将执行忙等待 10μs 以允许温度传感器建立。
现在,我们可以专注于 main() 例程。一旦 ADC1 外设启动,我们就会启动一个无限循环,循环轮询 ADC 以进行 A/D 转换。完成后,我们可以检索转换后的值并应用方程来计算以摄氏度为单位的温度。结果最终打印在 UART2 接口上。
CubeF1 HAL 中的 HAL_ADC 模块与其他 HAL 略有不同。要启动由软件驱动的转换,需要参数 hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START 在 ADC 初始化期间指定。这与其他 HAL 的作用完全不同,目前尚不清楚为什么 ST 开发人员采用这种不同的方法。此外,甚至 CubeMX 在生成相应的初始化代码时也提供了不同的配置来考虑这一特性。
2.5、中断模式下的 A/D 转换
在中断模式下执行 A/D 转换与目前所看到的没有太大区别。像往常一样,我们必须定义连接到 ADC 全局中断的 ISR,以分配所需的中断优先级并启用相应的 IRQ。与所有其他 HAL 外设一样,我们必须从 ADC ISR 调用 HAL_ADC_IRQHandler() 并实现回调例程 HAL_ADC_ConvCpltCallback(),该例程在转换结束时由 HAL 自动调用。最后,通过使用 HAL_ADC_Start_IT() 函数启动 ADC 来启用所有与 ADC 相关的中断。
以下示例仅演示如何在 interrupt 模式下执行转换。ADC 的初始化代码与上一个示例中使用的相同。
int main(void) {
HAL_Init();
BSP_Init();
/* Initialize all configured peripherals */
MX_ADC1_Init();
HAL_NVIC_SetPriority(ADC_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ADC_IRQn);
HAL_ADC_Start_IT(&hadc1);
while (1);
}
void ADC_IRQHandler(void) {
HAL_ADC_IRQHandler(&hadc1);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
char msg[30];
uint16_t rawValue;
float temp;
rawValue = HAL_ADC_GetValue(&hadc1);
temp = ((float)rawValue) / 4095 * 3300;
temp = ((temp - 760.0) / 2.5) + 25;
sprintf(msg, "rawValue: %hu\r\n", rawValue);
HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
sprintf(msg, "Temperature: %f\r\n", temp);
HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
}
2.6、DMA 模式下的 A/D 转换驱动
ADC 外设最有趣的模式是 DMA 模式。这种模式允许在没有 CPU 干预的情况下执行转换,并且在循环模式下使用 DMA,我们可以轻松设置 ADC,使其执行连续转换。此外,此模式非常适合使用定时器驱动转换,允许以固定采样率对输入信号进行采样。当我们想要使用扫描模式执行多个通道的转换时,还必须在 DMA 模式下使用 ADC 外设。
要在 DMA 模式下执行 A/D 转换,此过程通常涉及以下步骤如下:
• 根据所需的转换模式(单次扫描、连续扫描等)设置 ADC 外设。
• 设置与所使用的 ADC 控制器相对应的 DMA 通道/流。
• 使用 __HAL_LINKDMA() 宏将 DMA 处理程序描述符链接到 ADC 处理程序。
• 启用 DMA 和与使用的 DMA 流关联的 IRQ。
• 使用 HAL_ADC_Start_DMA() 以 DMA 模式启动 ADC,将引用传递给用于存储从 ADC 获取的数据的数组。
• 通过定义 HAL_ADC_ConvCpltCallback()回调来准备捕获 EOC 事件。
以下示例设计为在 STM32F401RE MCU 上运行,展示了如何使用 DMA 模式执行单次扫描转换。我们要分析的第一部分是与 ADC 外设和 DMA 控制器的设置相关的部分。
static void MX_ADC1_Init(void) {
ADC_ChannelConfTypeDef sConfig = {0};
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) */
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV8;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 3;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
HAL_ADC_Init(&hadc1);
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. */
sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Rank = 2;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Rank = 3;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
/* ADC1 DMA Init */
hdma_adc1.Instance = DMA2_Stream0;
hdma_adc1.Init.Channel = DMA_CHANNEL_0;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_NORMAL;
hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_adc1) != HAL_OK);
__HAL_LINKDMA(&hadc1,DMA_Handle,hdma_adc1);
}
static void MX_DMA_Init(void) {
/* DMA controller clock enable */
__HAL_RCC_DMA2_CLK_ENABLE();
/* DMA interrupt init */
/* DMA2_Stream0_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}
void DMA2_Stream0_IRQHandler(void) {
HAL_DMA_IRQHandler(&hdma_adc1);
}
MX_ADC1_Init() 配置 ADC,使其对三个通道执行一次扫描。ADCCLK 设置为最低值,并启用扫描模式。配置 ADC,使其始终从内部温度传感器执行转换:这没有用,但不幸的是 DEMO 板没有嵌入模拟外设来使用。init 代码继续配置 DMA2 Stream0/Channel0,以便在 ADC 完成转换时执行外设到内存的传输。显然,转换序列由分配给通道的排序指定。由于 ADC 数据寄存器是 16 位宽的,因此我们配置 DMA,以便执行半字传输。最后,函数 MX_DMA_Init() 启用连接到 DMA2_Stream0 的 IRQ,其 ISR 将调用 HAL_DMA_IRQHandler() 例程。这将导致 HAL_ADC_ConvCpltCallback() 函数在扫描转换结束时由 HAL 自动调用。回调设置一个全局变量,用于指示转换结束。
char msg[30];
uint16_t rawValues[3];
float temp;
volatile uint8_t convCompleted = 0;
int main(void) {
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_DMA_Init();
MX_ADC1_Init();
MX_GPIO_Init();
MX_USART2_UART_Init();
/* Starts the ADC in DMA mode*/
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)rawValues, 3);
while(!convCompleted);
HAL_ADC_Stop_DMA(&hadc1);
for(uint8_t i = 0; i < hadc1.Init.NbrOfConversion; i++) {
temp = ((float)rawValues[i]) / 4095 * 3300;
temp = ((temp - 760.0) / 2.5) + 25;
sprintf(msg, "rawValue %d: %hu\r\n", i, rawValues[i]);
HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
sprintf(msg, "Temperature %d: %f\r\n",i, temp);
HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
}
while (1);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef*hadc) {
convCompleted = 1;
}
以上代码行显示了 main() 函数。ADC 以 DMA 模式启动,方法是将指针传递给 rawValues 数组和转换次数:这必须与 hadc1.Init.NbrOfConversion 字段相对应。最后,当 HAL_ADC_ConvCpltCallback() 例程将 convCompleted 变量设置为 1 时,rawValues 数组的内容将被转换,结果将打印在 UART2 接口上。请注意,HAL_ADC_Stop_DMA() 调用:执行此操作不是为了停止转换(在三个样本后自动停止),而是为了允许在 DMA 模式下连续使用 ADC 外设(否则转换将不会开始)。
基于 STM32L0 的电路板的所有者会发现略有不同的样本。在 STM32L0 MCU 中,等级rank被固定地分配给 ADC 通道,序列反映了通道编号方案。因此,序列中的等级数由启用的通道数定义,每个通道的等级由通道号定义(通道 0 固定在等级 0 上,通道 1 固定在等级 1 上,依此类推)。这意味着温度传感器通道不能分配给超过 1 个等级。因此,对于STM32L073RZ示例,此示例和下一个示例将仅在一个通道上循环。
2.6.1、DMA 模式下对同一通道进行多次转换
要在 DMA 模式下对同一通道(或同一通道序列)执行给定数量的转换,需要执行以下操作:
• 设置 hadc.Init.ContinuousConvMode 字段设置为 ENABLE。
• 分配足够大小的缓冲区。
• 将所需传输的数量传递给 HAL_ADC_Start_DMA()。
2.6.2、DMA 模式下的多次而非连续转换
要在 DMA 模式下执行多次转换,您需要执行以下步骤:
• 设置 hadc.Init.DMAContinuousRequests 字段设置为 ENABLE。
• 调用 HAL_ADC_Start_DMA() 以在 DMA 模式下开始转换。
相反,如果 hadc.Init.DMAContinuousRequests 字段设置为 DISABLE,则需要在每个转换序列结束时调用 HAL_ADC_Stop_DMA(),然后再再次调用 HAL_ADC_Start_DMA()。否则,转换将不会开始。
2.6.3、DMA 模式下的连续转换
要在 DMA 模式下执行连续转换,您需要执行以下步骤:
• 设置 hadc.Init.ContinuousConvMode 字段设置为 ENABLE。
• 设置 hadc。Init.DMAContinuousRequests 字段设置为 ENABLE,否则 ADC 在第一个扫描序列完成后不会重新触发 DMA。
• 在 DMA_CIRCULAR 模式下配置 DMA 流/通道。
2.7、错误管理
ADC 外设能够在转换丢失时通知开发人员。当正在进行连续或扫描模式转换时,并且 ADC 数据寄存器在读取之前被连续事务覆盖时,会发生此错误情况。发生这种情况时,将设置 ADC_SR register 中的特殊位并生成 ADC 中断。
我们可以通过实现以下回调来捕获溢出错误:
void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc);
发生超限错误时,将禁用 DMA 传输,并且不再接受 DMA 请求。在这种情况下,如果发出 DMA 请求,则正在进行的常规转换将中止,并忽略进一步的常规触发器。然后,必须清除所用 DMA 流的 OVR 标志和 DMAEN 位,并重新初始化 DMA 和 ADC,以将所需的转换通道数据传输到正确的内存位置(所有这些操作在调用 HAL_ADC_Start_DMA() 例程时由 HAL 自动执行)。
我们可以通过启用上一个示例中的 continuous conversion 模式,并设置Init.DMAContinuousRequests 字段为 ENABLE 来模拟溢出错误。(在某些 STM32 MCU 中,还需要通过设置 hadc 来显式启用溢出检测,Init.Overrun 设置为 ADC_OVR_DATA_OVERWRITTEN。)如果启用了 ADC 中断,并且从中调用了 HAL_ADC_IRQHandler(),那么将能够捕获超限错误。
超限错误不仅与 ADC 接口的错误配置有关。即使 ADC 在 DMA 循环模式下工作,也可以生成它。DMA 被多个外设严重利用,会发现当 DMA 执行其他并发事务时,可能会发生超限错误。即使总线仲裁应避免争用条件,尤其是在正确设置优先级时,也会在一些不可重现的情况下遇到此错误。通过正确处理超限错误,能够在发生这种情况时重新启动转换。
2.8、定时器驱动转换
ADC 外设可配置为由定时器通过 TRGO 触发线驱动。用于执行此操作的 timer 在 chip design 期间是硬连线的。例如,在 STM32F401RE MCU 中,可以使用 TIM2 定时器同步 ADC1 外设。此功能对于在给定频率下执行 ADC 转换非常有用。例如,我们可以对麦克风产生的 20kHz 频率的音频波进行采样。然后,结果数据可以存储在持久内存中。
ADC 转换可由中断和 DMA 模式下的定时器驱动。当我们只对一个 Channel 进行低频采样时,前者很有用。后者对于高频下的 scan mode 转换是必需的。要启用计时器驱动的转化,您可以按照以下步骤操作:
• 根据所需的采样频率配置通过 TRGO 线路连接到 ADC 的定时器。
• 配置定时器的 TRGO,以便在每次生成更新事件时触发 (TIM_TRGO_UPDATE)。请注意,即使定时器不在主模式下工作,使用 HAL_TIMEx_MasterConfigSynchronization() 例程配置定时器的 TRGO 输出模式也很重要。
• 配置 ADC,使所选定时器 TRGO 线触发转换,并确保禁用连续转换模式(因为是触发转换的 TRGO 线)。此外,设置 hadc.Init.DMAContinuousRequests 字段设置为 ENABLE,并且 DMA 处于循环模式(如果要无限期地执行 N 转换),或将 hadc.Init.DMAContinuousRequests 字段设置为 DISABLE(如果要在执行 N 次转换后停止)。
• 请务必设置 hadc。Init.ContinuousConvMode 字段设置为 DISABLE,否则 ADC 会自行执行转换,而无需等待定时器触发。
• 启动定时器。
• 在中断或 DMA 模式下启动 ADC。
以下示例展示了如何使用 TIM2 定时器在 STM32F401RE MCU 中每 1 秒触发一次转换。
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) */
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV8;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG2_T2_TRGO;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 3;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
HAL_ADC_Init(&hadc1);
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. */
sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Rank = 2;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Rank = 3;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
/* ADC1 DMA Init */
hdma_adc1.Instance = DMA2_Stream0;
hdma_adc1.Init.Channel = DMA_CHANNEL_0;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_adc1);
__HAL_LINKDMA(&hadc1,DMA_Handle,hdma_adc1);
}
static void MX_TIM2_Init(void) {
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 47999;
htim2.Init.Period = 1999;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
HAL_TIM_Base_Init(&htim2);
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);
}
我们从 MX_TIM2_Init() 函数的分析开始。首先,它配置 TIM2 定时器,使其在以 84MHz 运行的 MCU 中每 1 秒溢出一次。接下来,配置定时器,以便在 TRGO 线路溢出时置位。相反,ADC 配置为从同一通道(连接到温度传感器的通道)执行三次转换。ADC 还配置为从 TIM2 TRGO 线路触发,以便 DMA 请求连续触发转换。DMA 相应地配置为在循环模式下工作。
int main(void) {
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_DMA_Init();
MX_ADC1_Init();
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_TIM2_Init();
HAL_TIM_Base_Start(&htim2);
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)rawValues, 3);
while(1) {
while(!convCompleted);
for(uint8_t i = 0; i < hadc1.Init.NbrOfConversion; i++) {
temp = ((float)rawValues[i]) / 4095 * 3300;
temp = ((temp - 760.0) / 2.5) + 25;
sprintf(msg, "rawValue %d: %hu\r\n", i, rawValues[i]);
HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
sprintf(msg, "Temperature %d: %f\r\n",i, temp);
HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
}
convCompleted = 0;
}
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef*hadc) {
convCompleted = 1;
}
上面的代码显示了 main() 函数的内容,应该很容易理解。定时器启动,ADC 在 DMA 模式下启动,以从 ADC 执行三次采集。如果运行该示例,则可以看到 DMA 每三秒完成一次传输,并设置了 convCompleted 变量:这会导致每三秒在 UART2 接口上打印三次转换。
2.9、由外部事件驱动的转换
在某些STM32 MCU中,可以配置EXTI线来触发A/D转换。例如,在 STM32F401RE MCU 中,可以启用 EXTI line 11 以实现此类用途。这意味着该线路上连接的任何 MCU 引脚(PA11、PB11 等)都是触发转换的有效源。请注意,不能同时使用 EXTI 线路和定时器作为触发源。
2.10、ADC 校准
某些 STM32 系列(如 STM32F1/F3/L0/L4/G4 系列)实现的 ADC 外设提供了一个自动校准程序,可驱动所有校准序列,包括 ADC 的上电/关机序列。在此过程中,ADC 计算一个校准因数,该因数为 7 位宽,并在 ADC 内部施加,直到下一次 ADC 断电。在校准过程中,应用程序不得使用 ADC,并且必须等待校准完成。校准是任何 ADC 操作的初步阶段。它消除了由于工艺或带隙变化而可能因芯片而异的偏移误差。用于单端 input conversions的 calibration factor 与用于 differential input conversions的 factor 不同。
HAL_ADC_Ex 模块提供 3 个有用的函数,可用于 ADC 校准。
HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef* hadc, uint32_t SingleDiff);
该函数自动执行校准程序。必须在 HAL_ADC_Init() 之后和使用任何 HAL_ADC_Start_XXX() 例程之前调用它。传递参数ADC_SINGLE_ENDED执行单端校准,而传递ADC_DIFFERENTIAL_ENDED执行差分输入校准。
uint32_t HAL_ADCEx_Calibration_GetValue(ADC_HandleTypeDef* hadc, uint32_t SingleDiff);
该函数用于检索计算的校准值,而
HAL_StatusTypeDef HAL_ADCEx_Calibration_SetValue(ADC_HandleTypeDef* hadc, uint32_t SingleDiff, uint32_t CalibrationFactor);
用于设置自定义派生的校准值。
3、使用 CubeMX 配置 ADC 外设
CubeMX 允许通过几个步骤轻松配置 ADC 外设。第一个步骤是在 IP Tree 视图中启用所需的 ADC 通道,如下图所示。
ADC 的输入通道启用输入后,我们可以从配置视图配置 ADC 外设,如下图所示。
这些字段反映了到目前为止看到的 ADC 配置参数。只有一个部分容易让新手用户感到困惑:通道的配置方式。实际上,我们首先需要通过设置 Number of Conversion 字段。接下来,(这真的很重要)我们需要点击配置对话框中的其他地方,以便根据指定的通道数增加 Rank 字段的数量。在那些提供常规组和注入组概念的 MCU 中,我们可以独立选择每个通道的采样速度。CubeMX 将自动生成所有初始化代码。
如本章前面所述,CubeF1 HAL 中的 HAL_ADC 模块与其他 HAL 不同。要启动由软件驱动的转换,需要参数 hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START 在 ADC 初始化期间指定。CubeMX 反映了这种不同的配置,但要了解如何以正确的方式配置外设是很棘手的。因此,要启用软件驱动的转换,必须将 External Trigger Conversion Edge 参数设置为 Trigger detection on the rising edge。这将使字段 External Trigger Conversion Source 可用,必须选择条目 Software trigger。否则,将无法执行转换。
原文地址:https://blog.csdn.net/maxiumII/article/details/143901827
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!