从定时器到时钟:ARM 嵌入式学习总结

1. 引言

在 ARM 嵌入式开发中,定时器(Timer)与时钟(Clock)是绕不开的基础模块。无论是实现延时、产生 PWM 波形、捕获输入信号,还是驱动操作系统的心跳节拍,背后都离不开定时器与时钟系统的支撑。本文结合定时器与时钟的学习过程,梳理 ARM 嵌入式开发中的关键知识点与实践心得。

2. 时钟系统基础

2.1 为什么需要时钟

时钟是嵌入式系统的"心跳"。CPU、外设、总线都需要时钟信号来同步工作。时钟频率的高低直接影响系统的运行速度与功耗。

2.2 时钟树结构

ARM 芯片内部通常包含一个复杂的时钟树,常见结构如下:
#mermaid-svg-J3KcPKhnFytoguPQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-J3KcPKhnFytoguPQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-J3KcPKhnFytoguPQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-J3KcPKhnFytoguPQ .error-icon{fill:#552222;}#mermaid-svg-J3KcPKhnFytoguPQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-J3KcPKhnFytoguPQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-J3KcPKhnFytoguPQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-J3KcPKhnFytoguPQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-J3KcPKhnFytoguPQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-J3KcPKhnFytoguPQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-J3KcPKhnFytoguPQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-J3KcPKhnFytoguPQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-J3KcPKhnFytoguPQ .marker.cross{stroke:#333333;}#mermaid-svg-J3KcPKhnFytoguPQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-J3KcPKhnFytoguPQ p{margin:0;}#mermaid-svg-J3KcPKhnFytoguPQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-J3KcPKhnFytoguPQ .cluster-label text{fill:#333;}#mermaid-svg-J3KcPKhnFytoguPQ .cluster-label span{color:#333;}#mermaid-svg-J3KcPKhnFytoguPQ .cluster-label span p{background-color:transparent;}#mermaid-svg-J3KcPKhnFytoguPQ .label text,#mermaid-svg-J3KcPKhnFytoguPQ span{fill:#333;color:#333;}#mermaid-svg-J3KcPKhnFytoguPQ .node rect,#mermaid-svg-J3KcPKhnFytoguPQ .node circle,#mermaid-svg-J3KcPKhnFytoguPQ .node ellipse,#mermaid-svg-J3KcPKhnFytoguPQ .node polygon,#mermaid-svg-J3KcPKhnFytoguPQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-J3KcPKhnFytoguPQ .rough-node .label text,#mermaid-svg-J3KcPKhnFytoguPQ .node .label text,#mermaid-svg-J3KcPKhnFytoguPQ .image-shape .label,#mermaid-svg-J3KcPKhnFytoguPQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-J3KcPKhnFytoguPQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-J3KcPKhnFytoguPQ .rough-node .label,#mermaid-svg-J3KcPKhnFytoguPQ .node .label,#mermaid-svg-J3KcPKhnFytoguPQ .image-shape .label,#mermaid-svg-J3KcPKhnFytoguPQ .icon-shape .label{text-align:center;}#mermaid-svg-J3KcPKhnFytoguPQ .node.clickable{cursor:pointer;}#mermaid-svg-J3KcPKhnFytoguPQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-J3KcPKhnFytoguPQ .arrowheadPath{fill:#333333;}#mermaid-svg-J3KcPKhnFytoguPQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-J3KcPKhnFytoguPQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-J3KcPKhnFytoguPQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J3KcPKhnFytoguPQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-J3KcPKhnFytoguPQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J3KcPKhnFytoguPQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-J3KcPKhnFytoguPQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-J3KcPKhnFytoguPQ .cluster text{fill:#333;}#mermaid-svg-J3KcPKhnFytoguPQ .cluster span{color:#333;}#mermaid-svg-J3KcPKhnFytoguPQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-J3KcPKhnFytoguPQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-J3KcPKhnFytoguPQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-J3KcPKhnFytoguPQ .icon-shape,#mermaid-svg-J3KcPKhnFytoguPQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J3KcPKhnFytoguPQ .icon-shape p,#mermaid-svg-J3KcPKhnFytoguPQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-J3KcPKhnFytoguPQ .icon-shape .label rect,#mermaid-svg-J3KcPKhnFytoguPQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J3KcPKhnFytoguPQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-J3KcPKhnFytoguPQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-J3KcPKhnFytoguPQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 外部晶振 HSE
PLL 锁相环
系统时钟 SYSCLK
AHB 总线时钟
APB1 外设时钟
APB2 外设时钟
定时器 TIM2-TIM5
定时器 TIM1/TIM8

2.3 时钟配置要点

  • HSE(高速外部时钟):通常由外部晶振提供,精度高。
  • PLL(锁相环):用于倍频,将低频晶振倍频到系统所需的高频。
  • 分频器:通过 AHB、APB1、APB2 预分频器为不同外设提供合适的时钟频率。

3. 定时器的工作原理

3.1 定时器的基本组成

定时器本质上是一个计数器,在时钟脉冲的驱动下不断累加,当计数值达到预设值时触发中断或事件。

3.2 定时器的分类

类型 特点 典型应用
基本定时器 功能简单,仅支持定时 定时中断、延时
通用定时器 支持输入捕获、输出比较、PWM 测频、PWM 输出
高级定时器 功能最全,支持互补输出、死区控制 电机控制、三相逆变

3.3 定时时间的计算

定时器的定时时间由以下公式决定:

复制代码
定时时间 = (预分频值 + 1) × (自动重装载值 + 1) / 定时器时钟频率

例如,定时器时钟为 72 MHz,预分频值为 7199,自动重装载值为 999,则定时时间为:

复制代码
(7199 + 1) × (999 + 1) / 72_000_000 = 0.1 秒

4. 定时器编程实践

4.1 定时器初始化流程

以 STM32 通用定时器为例,初始化步骤如下:

  1. 使能定时器时钟;
  2. 配置预分频器与自动重装载值;
  3. 配置计数模式(向上/向下/中心对齐);
  4. 使能更新中断;
  5. 配置 NVIC 中断优先级;
  6. 启动定时器。

4.2 代码示例:定时器定时中断

c 复制代码
#include "stm32f10x.h"

void TIM2_Init(void)
{
    TIM_TimeBaseInitTypeDef TIM_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;

    // 1. 使能定时器时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    // 2. 配置定时器基础参数
    TIM_InitStruct.TIM_Prescaler = 7199;        // 预分频值
    TIM_InitStruct.TIM_Period = 999;            // 自动重装载值
    TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_InitStruct);

    // 3. 使能更新中断
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

    // 4. 配置中断优先级
    NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    // 5. 启动定时器
    TIM_Cmd(TIM2, ENABLE);
}

void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
    {
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        // 在此处添加定时中断处理逻辑
    }
}

4.3 代码示例:PWM 输出

c 复制代码
void PWM_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    TIM_OCInitTypeDef TIM_OCInitStruct;

    // 使能 GPIO 和定时器时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

    // 配置 PA6 为复用推挽输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 配置定时器
    TIM_TimeBaseInitTypeDef TIM_InitStruct;
    TIM_InitStruct.TIM_Prescaler = 71;
    TIM_InitStruct.TIM_Period = 999;
    TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_InitStruct);

    // 配置 PWM 输出通道
    TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStruct.TIM_Pulse = 500;           // 占空比 50%
    TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM3, &TIM_OCInitStruct);

    // 启动定时器
    TIM_Cmd(TIM3, ENABLE);
}

5. 学习心得与常见问题

5.1 学习心得

  • 先理解时钟树,再学定时器:定时器的频率来源于时钟树,不理解时钟配置就无法准确计算定时时间。
  • 动手验证公式:通过修改预分频值和重装载值,观察 LED 闪烁频率的变化,能加深对定时器原理的理解。
  • 善用调试工具:使用逻辑分析仪或示波器观察 PWM 波形,验证配置是否正确。

5.2 常见问题

  • 定时时间不准确:多为时钟源配置错误或分频计算有误。
  • 中断不触发:检查中断使能、NVIC 配置以及中断标志位是否清除。
  • PWM 无输出:检查 GPIO 复用配置、输出通道使能以及定时器是否启动。

6. 总结

定时器与时钟是 ARM 嵌入式开发的核心基础。掌握时钟树的配置方法,理解定时器的工作原理与计算公式,并通过实际编程验证,是学好嵌入式开发的关键路径。希望本文的总结能对正在学习 ARM 嵌入式的你有所帮助。

相关推荐
祖力558 小时前
ARM时钟
嵌入式硬件·arm·时钟·imx6ull
koi77u8 小时前
嵌入式学习——ARM(3)
arm
学linux的QQ蛋10 小时前
i.MX6ULL GIC 中断整体知识点总结和ARM时钟重点总结
arm·时钟·中断
G.E.M.小白11 小时前
【IMX6ULL ARM内核启动与时钟系统超详解】PLL/PFD/分频/根时钟配置
嵌入式·arm·时钟·imx6ull
koi77u1 天前
嵌入式学习——ARM(1)
arm
myy-learn1 天前
ARM-点亮第一颗LED
arm
zbyyd1 天前
i.MX6ULL 裸机开发进阶|C 驱动、SDK、BSP 工程、链接脚本
arm
一直C1 天前
ARM裸机开发|IMX6ULL GIC中断详解,中断流程、GIC版本分类、CP15协处理器
arm·imx6ull·寄存器
myy-learn1 天前
ARM-复用寄存器以及电气寄存器深化-蜂鸣器
arm