19、STM32 ADC与时钟树系统详解

STM32 ADC与时钟树系统详解

1. STM32 ADC基础操作与特性

在使用STM32的ADC(模拟 - 数字转换器)时,我们可以通过连接电位器到ADC输入引脚(如PA0和PA1)来进行简单的测试。使用 minicom 连接后,将电位器逆时针旋转。若逆时针旋转时电压读数接近 +3.3 伏,需反转电位器外部引脚的连接。修正后,读数应接近零。当电位器旋转到中间位置时,应能读到约 +1.5 伏,顺时针完全旋转时读数应接近 +3.3 伏。

STM32的ADC外设具有很强的灵活性,本次演示只是触及了其功能的表面。除了单转换功能外,ADC外设还可以配置为使用通道组并执行扫描操作。不仅可以扫描任意通道序列,还能将ADC值注入到结果中。此外,扫描和通道组可以包含来自外设ADC1和ADC2的通道。

ADC使用的练习问题如下:
1. STM32内部温度是如何表示的?
2. GPIO_CNF_INPUT_ANALOG GPIO_CNF_INPUT_PULL_UPDOWN GPIO_CNF_INPUT_FLOAT 的值有何不同?
3. 如果PCLK频率为36 MHz,当ADC配置为预分频系数为4时,ADC时钟速率是多少?
4. 请列举影响ADC总功耗的三个配置选项。
5. 假设预分频器后的ADC时钟为12 MHz,配置为 ADC_SMPR_SMP_41DOT5CYC 的采样需要多长时间?

2. STM32时钟树系统概述

大多数微处理器都使用一个或多个时钟,STM32系列也不例外。该系列具有高度可配置性,虽然增加了软件的复杂性,但也为设计者提供了更大的灵活性,可通过关闭不需要的外设和时钟来降低功耗。

STM32F103C8T6支持多种时钟,了解这些时钟及其配置方法有助于我们计算正确的预分频系数,以产生正确的波特率、SPI时钟速率,并为定时器提供正确的时钟信号。同时,还能帮助我们利用特殊的时钟特性,避免潜在的问题。

2.1 时钟源

STM32F103C8T6共有四个独立的时钟源,如下表所示:
| 时钟源 | 频率 | 类型 | 驱动方式 | 稳定性 |
| ---- | ---- | ---- | ---- | ---- |
| LSI | 40 kHz | 低速内部 | 电阻和电容 | 差 |
| LSE | 32.768 kHz | 低速外部 | 晶体 | 好 |
| HSI | 8 MHz | 高速内部 | 电阻和电容 | 差 |
| HSE | 4 - 16 MHz(蓝板上为8 MHz) | 高速外部 | 晶体 | 好 |

2.2 RC振荡器与晶体振荡器
  • RC振荡器 :虽然RC振荡器稳定性较差,但对于一些应用来说,MCU有一个合理的时钟来执行指令就足够了。使用RC振荡器可以节省设计者提供晶体的成本,从而减少零件数量。RC振荡器通过电阻对电容进行充电和放电来工作,电容和电阻的组合决定了整体频率。STM32的RC振荡器均为内部振荡器,否则需要外部提供电阻和电容。
  • 晶体振荡器 :晶体振荡器的稳定性较好。振荡器在低电平和高电平信号之间切换的速率越高,电流消耗就越大。每次振荡器从低电平切换到高电平,需要向电路中注入电子,即需要电流流动(充电);从高电平切换到低电平时,需要将电子从电路中抽出并排放到地(放电),这都需要能量。因此,在设计STM32平台的时钟时,需要充分考虑这一点。对于电池供电且执行时间不太重要的应用,使用低速振荡器是合理的选择;而对于通过USB从桌面供电且速度是主要要求的应用,则更倾向于使用高速振荡器。此外,准确性也是选择振荡器的一个重要标准,例如在实现不同单元之间的串行链路时,需要准确的波特率。
2.3 实时时钟、看门狗时钟和系统时钟
  • 实时时钟(RTC) :当HSE为8 MHz时,实时时钟的源及相关参数如下表所示:
    | 振荡器源 | 源频率 | 分频器 | 结果频率 |
    | ---- | ---- | ---- | ---- |
    | HSE | 8.000 MHz | 128 | 62.5 kHz |
    | LSE | 32.768 kHz | 1 | 32.768 kHz |
    | LSI | 40 kHz | 1 | 40 kHz |
  • 看门狗时钟 :独立看门狗(IWDG)硬连线到LSI 40 kHz时钟。
  • 系统时钟(SYSCLK) :系统时钟是最重要的基本时钟配置类别,其他重要时钟都由此派生。SYSCLK只能由四个时钟源中的两个提供:HSI(RC,8 MHz)和HSE(晶体,4 - 16 MHz,蓝板上为8 MHz)。系统时钟可以使用PLL(锁相环)将输入时钟频率最高倍频到72 MHz。以下是系统时钟从HSI和PLL派生的情况:
    | 源 | 频率 | PLL乘数 | 结果频率 |
    | ---- | ---- | ---- | ---- |
    | HSI | 8 MHz | 无PLL | 8 MHz |
    | HSI | 8 MHz ÷ 2 | 2 | 8 MHz |
    | HSI | 8 MHz ÷ 2 | 3 | 12 MHz |
    |… |… |… |… |
    | HSI | 8 MHz ÷ 2 | 16 | 64 MHz |

系统时钟从HSE和PLL派生的情况如下:
| 源 | 频率 | PLL乘数 | HSE ÷ 2 | HSE ÷ 1 |
| ---- | ---- | ---- | ---- | ---- |
| HSE | 8.000 MHz | 无PLL | 8 MHz | 8 MHz |
| HSE | 8.000 MHz | 2 | 8 MHz | 16 MHz |
| HSE | 8.000 MHz | 3 | 12 MHz | 24 MHz |
|… |… |… |… |… |
| HSE | 8.000 MHz | 16 | 64 MHz | 超限制 |

当使用USB时,有效的时钟配置如下表所示:
| SYSCLK频率 | USB分频器 | 结果USB时钟 |
| ---- | ---- | ---- |
| 72 MHz | ÷ 1.5 | 48 MHz |
| 48 MHz | ÷ 1 | 48 MHz |

2.4 AHB总线

AHB(Advanced High-performance Bus)是ARM高级微控制器总线架构(AMBA)的一部分,是一种用于片上系统(SoC)设计中功能模块连接和管理的开放标准片上互连规范。在STM32F103C8T6中,当SYSCLK为72 MHz时,AHB频率的配置如下表所示:
| 位值 | 分频器 | 结果频率 |
| ---- | ---- | ---- |
| 0xxx | SYSCLK不分频 | 72 MHz |
| 1000 | SYSCLK除以2 | 36 MHz |
| 1001 | SYSCLK除以4 | 18 MHz |
|… |… |… |
| 1111 | SYSCLK除以512 | 140.625 kHz |

3. 系统时钟配置函数分析

在许多演示代码中,最初使用 rcc_clock_setup_in_hse_8mhz_out_72mhz() 函数来配置系统时钟,现在 libopencm3 项目要求使用 rcc_clock_setup_pll(&rcc_hse_configs[RCC_CLOCK_HSE8_72MHZ]) 。以下是原 rcc_clock_setup_in_hse_8mhz_out_72mhz() 函数的代码:

0911: void rcc_clock_setup_in_hse_8mhz_out_72mhz(void)
0912: {
0913:   /* Enable internal high-speed oscillator. */
0914:   rcc_osc_on(RCC_HSI);
0915:   rcc_wait_for_osc_ready(RCC_HSI);
0916:
0917:   /* Select HSI as SYSCLK source. */
0918:   rcc_set_sysclk_source(RCC_CFGR_SW_SYSCLKSEL_HSICLK);
0919:
0920:   /* Enable external high-speed oscillator 8MHz. */
0921:   rcc_osc_on(RCC_HSE);
0922:   rcc_wait_for_osc_ready(RCC_HSE);
0923:   rcc_set_sysclk_source(RCC_CFGR_SW_SYSCLKSEL_HSECLK);
0924:
0925:   /*
0926:    * Set prescalers for AHB, ADC, ABP1, ABP2.
0927:    * Do this before touching the PLL (TODO: why?).
0928:    */
0929:   rcc_set_hpre(RCC_CFGR_HPRE_SYSCLK_NODIV);  /* Set. 72MHz Max. 72MHz */
0930:   rcc_set_adcpre(RCC_CFGR_ADCPRE_PCLK2_DIV8);  /*Set. 9MHz Max. 14MHz */
0931:   rcc_set_ppre1(RCC_CFGR_PPRE1_HCLK_DIV2); /* Set. 36MHz Max. 36MHz */
0932:   rcc_set_ppre2(RCC_CFGR_PPRE2_HCLK_NODIV);  /* Set. 72MHz Max. 72MHz */
0933:
0934:   /*
0935:    * Sysclk runs with 72MHz -> 2 waitstates.
0936:    * 0WS from 0-24MHz
0937:    * 1WS from 24-48MHz
0938:    * 2WS from 48-72MHz
0939:    */
0940:   flash_set_ws(FLASH_ACR_LATENCY_2WS);
0941:
0942:   /*
0943:    * Set the PLL multiplication factor to 9.
0944:    * 8MHz (external) * 9 (multiplier) = 72MHz
0945:    */
0946:   rcc_set_pll_multiplication_factor(RCC_CFGR_PLLMUL_PLL_CLK_MUL9);
0947:
0948:   /* Select HSE as PLL source. */
0949:   rcc_set_pll_source(RCC_CFGR_PLLSRC_HSE_CLK);
0950:
0951:   /*
0952:    * External frequency undivided before entering PLL
0953:    * (only valid/needed for HSE).
0954:    */
0955:   rcc_set_pllxtpre(RCC_CFGR_PLLXTPRE_HSE_CLK);
0956:
0957:   /*  Enable PLL oscillator and wait for it to stabilize. */
0958:   rcc_osc_on(RCC_PLL);
0959:   rcc_wait_for_osc_ready(RCC_PLL);
0960:
0961:   /* Select PLL as SYSCLK source. */
0962:   rcc_set_sysclk_source(RCC_CFGR_SW_SYSCLKSEL_PLLCLK);
0963:
0964:   /* Set the peripheral clock frequencies used */
0965:   rcc_ahb_frequency = 72000000;
0966:   rcc_apb1_frequency = 36000000;
0967:   rcc_apb2_frequency = 72000000;
0968: }

该函数的基本步骤如下:
1. 开启HSI振荡器并等待其就绪(第914 - 915行)。
2. 选择HSI振荡器作为SYSCLK源(第918行)。
3. 开启HSE(8 MHz晶体振荡器)并等待其就绪(第921 - 922行)。
4. 将SYSCLK切换为使用HSE时钟(第923行),此时SYSCLK为8.000 MHz。
5. 将AHB预分频器设置为不分频,以便在后续选择PLL作为时钟源后,AHB输入时钟为72 MHz(第929行)。
6. 将ADC预分频器配置为分频系数为8,切换到PLL后频率为9 MHz(第930行),该频率不能超过14 MHz。
7. 将APB1(高级外设总线1)的预分频器设置为除以2,切换到PLL后APB1时钟为36 MHz(第931行),这是该总线的最大频率。
8. 将APB2的预分频器设置为不分频,切换到PLL后APB2频率为72 MHz(第932行),这也是APB2的最大频率。
9. 由于SYSCLK运行在72 MHz,每次访问闪存时需要插入两个等待周期(第940行)。
10. 将PLL的乘数设置为9,使其输出时钟为72 MHz(第946行)。
11. 移除HSE进入PLL时可能设置的÷ 2设置(第955行)。
12. 选择PLL作为SYSCLK源,将SYSCLK从8 MHz提高到72 MHz,此时AHB总线工作在72 MHz,APB1运行在36 MHz,APB2运行在72 MHz(第962行)。
13. 设置全局变量 rcc_ahb_frequency rcc_apb1_frequency rcc_apb2_frequency 供应用程序使用(第965 - 967行)。

这些全局变量在 rcc.h 中定义如下:

#include <libopencm3/stm32/rcc.h>
extern uint32_t rcc_ahb_frequency;
extern uint32_t rcc_apb1_frequency;
extern uint32_t rcc_apb2_frequency;
4. 外设时钟与定时器配置
4.1 APB1和APB2外设

连接到APB1总线的每个外设(如蓝板设备)通常接收36 MHz的时钟信号(除非另有配置)。每个外设都有一个独立的与门,用于启用或禁用该时钟,以节省功耗。例如,CAN外设需要自行启用其时钟,APB1定时器外设也是如此。

连接到APB2总线的外设也需要自行启用或禁用其72 MHz的时钟,APB2定时器外设同样适用。

4.2 定时器

APB1和APB2定时器都有预分频器,可以将总线时钟分频为较低的频率。但需要注意的是,当APB1/APB2预分频器设置为1时,总线频率会乘以2。

5. 时钟输出配置函数 rcc_set_mco()

rcc_set_mco() 函数用于将特定的时钟输出到MCO(微控制器时钟输出)引脚。该函数的有效参数如下表所示:
| 宏名称 | 值 | 描述 |
| ---- | ---- | ---- |
| RCC_CFGR_MCO_NOCLK | 0x0 | 无时钟输出到MCO(断开连接) |
| RCC_CFGR_MCO_SYSCLK | 0x4 | SYSCLK输出到MCO |
| RCC_CFGR_MCO_HSI | 0x5 | HSI输出到MCO |
| RCC_CFGR_MCO_HSE | 0x6 | HSE输出到MCO |
| RCC_CFGR_MCO_PLL_DIV2 | 0x7 | PLL ÷ 2输出到MCO |

仅调用 rcc_set_mco() 函数是不够的,还需要将GPIO PA8配置为复用功能I/O:

rcc_periph_clock_enable(RCC_GPIOA);
gpio_set_mode(GPIOA,
    GPIO_MODE_OUTPUT_50_MHZ,
    GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, // ALTFN
    GPIO8);                         // PA8=MCO
6. 时钟演示代码
6.1 HSI演示

HSI时钟演示代码位于 ~/stm32f103c8t6/hsi 目录下,操作步骤如下:

$ cd ~/stm32f103c8t6/hsi
$ make clobber
$ make
$ make flash

该演示不使用FreeRTOS,代码非常基础,主要是将HSI时钟输出到GPIO引脚PA8。以下是 hsi.c 演示程序的代码:

0010: int
0011: main(void) {
0012:
0013:   // LED Configuration:
0014:   rcc_periph_clock_enable(RCC_GPIOC);
0015:   gpio_set_mode(GPIOC,GPIO_MODE_OUTPUT_2_MHZ,
0016:             GPIO_CNF_OUTPUT_PUSHPULL,GPIO13);
0017:   gpio_clear(GPIOC,GPIO13);    // LED Off
0018:
0019:   // MCO Configuration:
0020:   rcc_periph_clock_enable(RCC_GPIOA);
0021:   gpio_set_mode(GPIOA,
0022:       GPIO_MODE_OUTPUT_50_MHZ,
0023:       GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,
0024:       GPIO8);                // PA8=MCO
0025:
0026:   rcc_set_mco(RCC_CFGR_MCO_HSI);
0027:
0028:   gpio_set(GPIOC,GPIO13); // LED On
0029:   for (;;);
0030:   return 0;
0031: }

除了配置LED PC13外,主要步骤如下:
1. 启用GPIOA外设时钟(第20行)。
2. 将引脚PA8所在的GPIOA配置为输出(最大50 MHz),并设置为复用功能推挽模式(第22 - 23行)。
3. 将HSI时钟输出到PA8(第26行)。

6.2 HSE演示

HSE时钟演示代码位于 ~/stm32f103c8t6/hse 目录下,操作步骤与HSI演示类似:

$ cd ~/stm32f103c8t6/hse
$ make clobber
$ make
$ make flash

该演示也不使用FreeRTOS,代码与HSI演示的唯一区别在于将 rcc_set_mco() 函数的参数设置为 RCC_CFGR_MCO_HSE

6.3 PLL ÷ 2演示

PLL ÷ 2时钟演示代码位于 ~/stm32f103c8t6/mco_pll2 目录下,操作步骤如下:

$ cd ~/stm32f103c8t6/mco_pll2
$ make clobber
$ make
$ make flash

该演示同样不使用FreeRTOS,代码与HSE演示的唯一区别在于将 rcc_set_mco() 函数的参数设置为 RCC_CFGR_MCO_PLL_DIV2 。将PLL ÷ 2时钟输出到PA8是有帮助的,因为GPIO引脚的驱动能力有限,输出72 MHz的信号可能会导致信号严重失真,并可能对相关的有源组件造成压力,而36 MHz在可接受的性能范围内。

7. 总结

本文介绍了STM32的ADC操作和时钟树系统。ADC外设具有丰富的功能,可通过简单的电位器测试来初步了解其使用方法。时钟树系统是STM32的重要组成部分,包括多个独立的时钟源(HSI、HSE、LSE、LSI),这些时钟源为系统时钟、实时时钟和看门狗时钟提供时钟信号。系统时钟可以利用PLL进行倍频,最高可达72 MHz,并由此派生AHB、APB1和APB2时钟。每个需要时钟的外设都有自己的使能门,可通过关闭不需要的时钟来节省功耗。通过HSI、HSE和PLL ÷ 2演示代码,我们可以验证内部时钟的输出情况,这些时钟输出也可用于为外部外设提供输入时钟。

在实际应用中,我们需要根据具体需求合理配置时钟源和预分频系数,以满足不同的性能和功耗要求。同时,要注意各个外设的时钟配置细节,避免出现时钟故障。希望本文能帮助读者更好地理解和使用STM32的ADC和时钟树系统。

STM32 ADC与时钟树系统详解

8. 时钟配置流程总结

为了更清晰地理解时钟配置过程,下面通过一个mermaid流程图来展示 rcc_clock_setup_in_hse_8mhz_out_72mhz() 函数的主要步骤:

graph TD;
    A[开启HSI振荡器] --> B[等待HSI就绪];
    B --> C[选择HSI作为SYSCLK源];
    C --> D[开启HSE振荡器];
    D --> E[等待HSE就绪];
    E --> F[选择HSE作为SYSCLK源];
    F --> G[设置AHB、ADC、APB1、APB2预分频器];
    G --> H[设置闪存等待周期];
    H --> I[设置PLL乘数为9];
    I --> J[选择HSE作为PLL源];
    J --> K[移除HSE进入PLL的÷2设置];
    K --> L[开启PLL振荡器];
    L --> M[等待PLL就绪];
    M --> N[选择PLL作为SYSCLK源];
    N --> O[设置外设时钟频率全局变量];

从这个流程图可以看出,时钟配置是一个逐步进行的过程,每个步骤都有其特定的目的和顺序要求。例如,在设置PLL之前需要先设置好各个总线的预分频器,以确保后续时钟频率的正确性。

9. 时钟选择的权衡因素

在选择不同的时钟源和配置时钟参数时,需要考虑多个因素,以下是一个总结表格:
| 考虑因素 | 影响 | 选择建议 |
| ---- | ---- | ---- |
| 稳定性 | 稳定性好的时钟源能提供更准确的时钟信号,适用于对时间精度要求高的应用,如串行通信。 | 对于需要高精度时钟的应用,优先选择晶体振荡器(HSE、LSE);对于对时间精度要求不高的应用,可以考虑使用RC振荡器(HSI、LSI)。 |
| 功耗 | 时钟频率越高,电流消耗越大。电池供电的系统需要尽量降低功耗。 | 对于电池供电且对速度要求不高的应用,选择低速振荡器(LSE、LSI);对于由桌面USB供电且对速度要求高的应用,可选择高速振荡器(HSI、HSE)并结合PLL进行倍频。 |
| 性能需求 | 不同的应用对时钟频率有不同的要求,如USB通信需要48 MHz的时钟。 | 根据具体应用的性能需求,合理配置系统时钟频率和预分频系数。例如,使用USB时,确保SYSCLK能通过分频得到48 MHz的USB时钟。 |
| 成本 | 使用外部晶体振荡器需要额外的硬件成本,而内部RC振荡器可以减少零件数量。 | 如果对成本敏感且对时钟精度要求不高,优先选择内部RC振荡器;如果对时钟精度要求较高,则需要使用外部晶体振荡器。 |

10. 定时器配置的注意事项

在配置APB1和APB2定时器时,需要特别注意预分频器的设置。当APB1/APB2预分频器设置为1时,定时器的输入频率会是总线频率的2倍。以下是一个简单的示例来说明这个问题:
假设APB1总线频率为36 MHz,当APB1定时器预分频器设置为1时,定时器的输入频率将变为72 MHz。这可能会影响定时器的定时精度和计数范围,因此在配置定时器时需要根据实际需求仔细计算预分频系数。

11. 常见问题及解决方法

在进行时钟配置和使用过程中,可能会遇到一些常见问题,下面列出一些问题及对应的解决方法:
| 问题 | 现象 | 解决方法 |
| ---- | ---- | ---- |
| 时钟频率不准确 | 通信波特率错误、定时器定时不准等。 | 检查时钟源的选择和预分频系数的设置是否正确,确保各个时钟配置步骤按照正确的顺序进行。可以使用示波器等工具测量时钟输出信号,验证时钟频率是否符合预期。 |
| 外设无时钟信号 | 外设无法正常工作。 | 检查外设的时钟使能位是否正确设置,确保每个需要时钟的外设都开启了相应的时钟。例如,使用 rcc_periph_clock_enable() 函数来启用外设时钟。 |
| 系统不稳定 | 系统出现死机、复位等异常情况。 | 检查时钟源的稳定性,确保时钟信号没有抖动或干扰。同时,检查闪存等待周期的设置是否正确,避免因时钟频率过高而导致闪存访问错误。 |

12. 拓展应用思路

除了上述介绍的基本时钟配置和应用,STM32的时钟系统还可以应用于更多的场景:
- 多时钟源切换 :在不同的工作模式下,可以动态切换时钟源,以实现性能和功耗的平衡。例如,在系统空闲时使用低速振荡器,在需要高性能处理时切换到高速振荡器并结合PLL。
- 外部时钟同步 :通过将外部时钟信号引入到STM32的时钟输入引脚,可以实现与外部设备的时钟同步,适用于需要精确同步的多设备系统。
- 时钟分频与组合 :利用不同的预分频系数和时钟组合方式,可以生成各种不同频率的时钟信号,满足不同外设的需求。

13. 总结回顾

本文详细介绍了STM32的ADC操作和时钟树系统,包括ADC的基本测试方法、时钟源的类型和特点、系统时钟的配置过程、外设时钟的管理以及定时器的配置注意事项等内容。通过实际的代码示例和演示,我们可以更好地理解和掌握这些知识。

在实际应用中,需要根据具体的需求和场景,合理选择时钟源和配置时钟参数,同时注意各个外设的时钟使能和配置细节,以确保系统的稳定性和性能。希望本文能为读者在使用STM32的ADC和时钟树系统时提供有益的参考和指导。

通过不断地实践和探索,我们可以进一步挖掘STM32时钟系统的潜力,实现更多复杂和高效的应用。例如,结合实时操作系统(RTOS),可以更好地管理不同任务的时钟需求,提高系统的实时性和响应速度。未来,随着嵌入式技术的不断发展,STM32的时钟系统也将在更多领域发挥重要作用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值