欢迎光临
我们一直在努力

MSP430 实战教程-06:定时器与 PWM 输出

1. Timer_A 定时器硬件结构与核心原理

Timer_A 是 MSP430FR 系列单片机中功能最强大、应用最广泛的 16 位定时器外设,具备定时、计数、捕获、比较、PWM 生成等多种功能,是实现低功耗定时、电机驱动、信号调制的核心基础。

1.1 Timer_A 核心硬件组成

MSP430FR 系列的 Timer_A 模块通常包含以下核心单元:

  • 16 位主计数器 (TAR):定时器的核心,用于累计时钟脉冲数,可读写
  • 时钟源选择与分频器:支持 4 种时钟源,配合 1/2/4/8 分频器灵活调节计数频率
  • 多个捕获 / 比较模块 (CCR):每个 Timer_A 模块包含 3~7 个独立的 CCR 寄存器,可独立配置为捕获或比较模式
  • 输出单元:每个 CCR 对应一个输出引脚,可生成 PWM 波形或触发外部事件
  • 中断逻辑:支持计数器溢出中断和每个 CCR 的比较 / 捕获中断

1.2 Timer_A 时钟体系

Timer_A 支持 4 种独立的时钟源,可通过TAxCTL寄存器的TASSELx位选择:

时钟源符号典型频率适用场景
辅助时钟 ACLK 32768Hz 低功耗长定时
子系统主时钟 SMCLK 1MHz~16MHz 高精度短定时、PWM 生成
外部输入时钟 TACLK 外部提供 外部事件计数
内部不可编程时钟 INCLK 器件特定 备用时钟源

选择时钟源后,可通过IDx位设置 1/2/4/8 分频,进一步降低计数频率,满足不同定时需求。

工程提示:低功耗应用优先选择 ACLK 作为时钟源,因为 ACLK 在 LPM3 低功耗模式下仍可运行;而 SMCLK 在 LPM3 模式下会停止,仅适用于活动模式下的高精度定时。


2. Timer_A 三种工作模式详解与配置

Timer_A 通过TAxCTL寄存器的MCx位选择三种工作模式,不同模式适用于不同的应用场景。

2.1 增计数模式 (Up Mode)

工作原理:计数器从 0 开始递增计数,当计数到TAxCCR0的值时,计数器清零并重新开始计数,同时产生溢出事件。

时序特性:

  • 计数周期:T = (TAxCCR0 + 1) × 时钟周期
  • 每个周期产生一次溢出中断
  • 是生成固定频率 PWM 最常用的模式

寄存器配置示例:

// 配置Timer_A为增计数模式,时钟源SMCLK=8MHz,分频1,周期1ms
TA0CTL = TASSEL__SMCLK | ID__1 | MC__UP; // 选择SMCLK,不分频,增计数模式
TA0CCR0 = 8000 – 1; // 计数到7999,共8000个时钟周期,8MHz/8000=1kHz=1ms

2.2 连续计数模式 (Continuous Mode)

工作原理:计数器从 0 开始递增计数,当计数到 0xFFFF (65535) 时,计数器清零并重新开始计数,同时产生溢出事件。

时序特性:

  • 计数周期固定:T = 65536 × 时钟周期
  • 可通过多个 CCR 实现多个不同时间点的事件触发
  • 适用于需要多个独立定时任务的场景

寄存器配置示例:

// 配置Timer_A为连续计数模式,时钟源ACLK=32768Hz,分频1
TA0CTL = TASSEL__ACLK | ID__1 | MC__CONTINUOUS; // 选择ACLK,不分频,连续计数模式
TA0CCR1 = 32768; // 1秒后触发CCR1中断
TA0CCR2 = 65536; // 2秒后触发CCR2中断

2.3 增 / 减计数模式 (Up/Down Mode)

工作原理:计数器从 0 开始递增计数到TAxCCR0,然后递减计数到 0,如此循环往复。在递增到TAxCCR0和递减到 0 时各产生一次事件。

时序特性:

  • 计数周期:T = 2 × TAxCCR0 × 时钟周期
  • 生成的 PWM 波形为中心对齐,谐波含量更低
  • 适用于电机驱动、音频输出等对波形质量要求高的场景

寄存器配置示例:

// 配置Timer_A为增/减计数模式,时钟源SMCLK=8MHz,分频1,周期2ms
TA0CTL = TASSEL__SMCLK | ID__1 | MC__UPDOWN; // 选择SMCLK,不分频,增/减计数模式
TA0CCR0 = 8000; // 计数到8000再减到0,共16000个时钟周期,8MHz/16000=500Hz=2ms


3. 定时器中断的初始化与服务函数编写

定时器中断是 MSP430 实现异步事件处理的核心机制,Timer_A 支持两种类型的中断:计数器溢出中断 (TAIFG)和捕获 / 比较中断 (CCRIFG)。

3.1 中断初始化流程

标准的定时器中断初始化包含以下步骤:

  • 停止定时器,清除所有中断标志
  • 配置时钟源、分频器和工作模式
  • 配置 CCR 寄存器的值
  • 使能相应的中断
  • 开启全局中断
  • 启动定时器
  • 完整初始化代码示例:

    void TimerA_Init(void)
    {
    // 1. 停止定时器,清除所有中断标志
    TA0CTL = TACLR; // 清除计数器、分频器和中断标志

    // 2. 配置时钟源、分频器和工作模式
    TA0CTL = TASSEL__SMCLK | ID__1 | MC__UP; // SMCLK=8MHz,不分频,增计数模式

    // 3. 配置CCR0的值,定时1ms
    TA0CCR0 = 8000 – 1;

    // 4. 使能CCR0比较中断
    TA0CCTL0 = CCIE; // 使能CCR0中断

    // 5. 开启全局中断
    __enable_interrupt();
    }

    3.2 中断服务函数编写规范

    MSP430 的中断服务函数必须遵循严格的编写规范,以确保系统稳定运行:

    • 使用#pragma vector=中断向量声明中断向量
    • 使用__interrupt关键字修饰函数
    • 函数名无参数、无返回值
    • 必须手动清除中断标志 (部分中断会自动清除)
    • 中断服务函数应尽可能简短,避免执行耗时操作

    标准中断服务函数示例:

    // Timer_A0 CCR0中断服务函数
    #pragma vector=TIMER0_A0_VECTOR
    __interrupt void Timer0_A0_ISR(void)
    {
    static uint16_t count = 0;

    count++;
    if(count >= 1000) // 1ms × 1000 = 1秒
    {
    count = 0;
    P1OUT ^= BIT0; // 翻转P1.0引脚,LED闪烁
    }

    // CCR0中断标志会自动清除,无需手动操作
    }

    // Timer_A0其他CCR和溢出中断服务函数
    #pragma vector=TIMER0_A1_VECTOR
    __interrupt void Timer0_A1_ISR(void)
    {
    switch(TA0IV)
    {
    case TA0IV_TA0CCR1: // CCR1中断
    // 处理CCR1事件
    break;
    case TA0IV_TA0IFG: // 溢出中断
    // 处理溢出事件
    break;
    default:
    break;
    }
    }

    工程提示:TIMER0_A0_VECTOR仅对应 CCR0 的中断,而TIMER0_A1_VECTOR对应 CCR1~CCRn 和溢出中断,需要通过TAxIV寄存器判断具体的中断源。


    4. PWM 波形生成原理与参数调节

    PWM (脉冲宽度调制) 是一种通过改变脉冲宽度来模拟模拟信号的技术,广泛应用于 LED 调光、电机调速、电源控制等领域。Timer_A 的比较模式是生成 PWM 波形的最佳方式。

    4.1 PWM 生成基本原理

    当 Timer_A 工作在比较模式时,计数器会不断与 CCR 寄存器的值进行比较:

    • 当计数器值等于 CCR 值时,输出引脚电平发生翻转
    • 当计数器溢出时,输出引脚电平再次翻转

    通过改变 CCR 的值,可以改变高电平持续时间与周期的比例,即占空比。

    4.2 边沿对齐 PWM (增计数模式)

    工作原理:计数器从 0 递增到 CCR0,输出引脚在计数器等于 CCRn 时翻转,在计数器溢出时再次翻转。

    参数计算公式:

    • PWM 频率:f_PWM = f_CLK / (TAxCCR0 + 1)
    • PWM 占空比:Duty = (TAxCCRn / (TAxCCR0 + 1)) × 100%

    输出模式配置:

    • 模式 7 (复位 / 置位):当计数器等于 CCRn 时输出低电平,当计数器溢出时输出高电平
    • 模式 3 (置位 / 复位):当计数器等于 CCRn 时输出高电平,当计数器溢出时输出低电平

    代码示例:

    // 配置P1.2为Timer_A0 CCR1输出,边沿对齐PWM,频率1kHz,占空比50%
    P1DIR |= BIT2; // P1.2设为输出
    P1SEL1 &= ~BIT2; // 选择P1.2的Timer_A功能
    P1SEL0 |= BIT2;

    TA0CTL = TASSEL__SMCLK | ID__1 | MC__UP; // SMCLK=8MHz,增计数模式
    TA0CCR0 = 8000 – 1; // PWM频率=8MHz/8000=1kHz
    TA0CCTL1 = OUTMOD_7; // 输出模式7:复位/置位
    TA0CCR1 = 4000; // 占空比=4000/8000=50%

    4.3 中心对齐 PWM (增 / 减计数模式)

    工作原理:计数器从 0 递增到 CCR0 再递减到 0,输出引脚在计数器递增等于 CCRn 时翻转,在计数器递减等于 CCRn 时再次翻转。

    参数计算公式:

    • PWM 频率:f_PWM = f_CLK / (2 × TAxCCR0)
    • PWM 占空比:Duty = (TAxCCRn / TAxCCR0) × 100%

    特点:中心对齐 PWM 的谐波含量比边沿对齐低,在电机驱动等应用中可以降低噪声和振动。

    代码示例:

    // 配置P1.2为Timer_A0 CCR1输出,中心对齐PWM,频率500Hz,占空比50%
    P1DIR |= BIT2;
    P1SEL1 &= ~BIT2;
    P1SEL0 |= BIT2;

    TA0CTL = TASSEL__SMCLK | ID__1 | MC__UPDOWN; // SMCLK=8MHz,增/减计数模式
    TA0CCR0 = 8000; // PWM频率=8MHz/(2×8000)=500Hz
    TA0CCTL1 = OUTMOD_3; // 输出模式3:置位/复位
    TA0CCR1 = 4000; // 占空比=4000/8000=50%

    4.4 PWM 参数动态调节

    在实际应用中,经常需要动态改变 PWM 的频率和占空比:

    • 改变占空比:直接修改TAxCCRn的值即可
    • 改变频率:需要同时修改TAxCCR0和TAxCCRn的值,以保持占空比不变

    动态调节占空比函数:

    void PWM_SetDutyCycle(uint16_t duty)
    {
    if(duty > 1000) duty = 1000; // 限制占空比范围0~100%,精度0.1%
    TA0CCR1 = (TA0CCR0 + 1) * duty / 1000;
    }


    5. 定时器高精度延时的工程实现

    传统的软件延时通过空循环实现,精度低且占用 CPU 资源,无法在低功耗模式下使用。利用 Timer_A 定时器可以实现高精度、低功耗的延时功能。

    5.1 阻塞式高精度延时

    阻塞式延时在延时期间 CPU 处于等待状态,适用于短时间延时。

    代码实现:

    void Delay_us(uint16_t us)
    {
    if(us == 0) return;

    TA0CTL = TASSEL__SMCLK | ID__1 | MC__CONTINUOUS | TACLR; // SMCLK=8MHz,连续计数模式
    TA0CCR1 = TA0R + us * 8; // 计算比较值,8MHz时钟,1us=8个时钟周期
    TA0CCTL1 &= ~CCIFG; // 清除中断标志

    while(!(TA0CCTL1 & CCIFG)); // 等待比较事件发生

    TA0CTL = MC__STOP; // 停止定时器
    }

    void Delay_ms(uint16_t ms)
    {
    while(ms–)
    {
    Delay_us(1000);
    }
    }

    5.2 非阻塞式低功耗延时

    非阻塞式延时利用定时器中断实现,在延时期间 CPU 可以进入低功耗模式,极大地降低系统功耗。

    代码实现:

    static volatile uint16_t delay_count = 0;
    static volatile bool delay_done = false;

    void Delay_ms_LPM(uint16_t ms)
    {
    delay_count = ms;
    delay_done = false;

    TA0CTL = TASSEL__ACLK | ID__1 | MC__UP | TACLR; // ACLK=32768Hz,增计数模式
    TA0CCR0 = 32 – 1; // 约1ms中断一次(32768/32≈1024Hz)
    TA0CCTL0 = CCIE; // 使能CCR0中断

    __enable_interrupt();

    while(!delay_done)
    {
    __low_power_mode_3(); // 进入LPM3低功耗模式,等待中断唤醒
    }

    TA0CTL = MC__STOP; // 停止定时器
    }

    #pragma vector=TIMER0_A0_VECTOR
    __interrupt void Timer0_A0_ISR(void)
    {
    if(delay_count > 0)
    {
    delay_count–;
    if(delay_count == 0)
    {
    delay_done = true;
    __low_power_mode_off_on_exit(); // 退出低功耗模式
    }
    }
    }

    工程提示:非阻塞式延时是 MSP430 低功耗开发的核心技巧,在电池供电的设备中应优先使用。


    6. 实战案例:LED 呼吸灯完整实现

    呼吸灯是 PWM 最经典的应用案例,通过逐渐改变 PWM 的占空比,实现 LED 从暗到亮再从亮到暗的平滑过渡效果。

    6.1 硬件连接

    • LED 正极通过 220Ω 电阻连接到 MSP430FR5994 的 P1.2 引脚 (Timer_A0 CCR1 输出)
    • LED 负极连接到 GND

    6.2 完整代码实现

    #include <msp430fr5994.h>
    #include <stdint.h>
    #include <stdbool.h>

    #define PWM_FREQ 1000 // PWM频率1kHz
    #define PWM_MAX_DUTY 1000 // 最大占空比100%,精度0.1%
    #define BREATH_STEP 5 // 呼吸步长,值越大呼吸越快

    void SystemClock_Init(void)
    {
    // 配置系统时钟为8MHz
    FRCTL0 = FRCTLPW | NWAITS_1; // 配置FRAM等待状态

    CSCTL0_H = CSKEY_H; // 解锁CS寄存器
    CSCTL1 = DCOFSEL_6; // DCO频率8MHz
    CSCTL2 = SELA__VLOCLK | SELS__DCOCLK | SELM__DCOCLK; // ACLK=VLO=10kHz,SMCLK=MCLK=DCO=8MHz
    CSCTL3 = DIVA__1 | DIVS__1 | DIVM__1; // 所有时钟不分频
    CSCTL0_H = 0; // 锁定CS寄存器
    }

    void PWM_Init(void)
    {
    // 配置P1.2为Timer_A0 CCR1输出
    P1DIR |= BIT2;
    P1SEL1 &= ~BIT2;
    P1SEL0 |= BIT2;

    // 配置Timer_A0为增计数模式,生成边沿对齐PWM
    TA0CTL = TASSEL__SMCLK | ID__1 | MC__UP; // SMCLK=8MHz,增计数模式
    TA0CCR0 = (8000000 / PWM_FREQ) – 1; // 计算CCR0值
    TA0CCTL1 = OUTMOD_7; // 输出模式7:复位/置位
    TA0CCR1 = 0; // 初始占空比0%
    }

    int main(void)
    {
    WDTCTL = WDTPW | WDTHOLD; // 停止看门狗

    SystemClock_Init();
    PWM_Init();

    PM5CTL0 &= ~LOCKLPM5; // 解锁GPIO

    uint16_t duty = 0;
    int8_t step = BREATH_STEP;

    while(1)
    {
    // 改变占空比
    duty += step;

    // 达到最大值或最小值时改变方向
    if(duty >= PWM_MAX_DUTY)
    {
    duty = PWM_MAX_DUTY;
    step = -BREATH_STEP;
    }
    else if(duty <= 0)
    {
    duty = 0;
    step = BREATH_STEP;
    }

    // 设置PWM占空比
    TA0CCR1 = (TA0CCR0 + 1) * duty / PWM_MAX_DUTY;

    // 延时10ms,控制呼吸速度
    __delay_cycles(80000); // 8MHz时钟,10ms=80000个周期
    }
    }

    6.3 代码解析

  • 系统时钟初始化:配置 DCO 为 8MHz,作为 SMCLK 的时钟源
  • PWM 初始化:配置 P1.2 为 Timer_A0 CCR1 输出,设置 PWM 频率为 1kHz
  • 主循环:
    • 定义duty变量存储当前占空比
    • 定义step变量控制占空比变化方向和速度
    • 每次循环改变占空比,达到边界时反转方向
    • 设置 PWM 占空比并延时 10ms
  • 效果:LED 会以约 4 秒为一个周期 (2 秒渐亮,2 秒渐暗),实现平滑的呼吸效果。

    6.4 进阶优化:低功耗呼吸灯

    上述代码在主循环中使用软件延时,CPU 一直处于活动状态,功耗较高。我们可以利用定时器中断实现低功耗呼吸灯:

    #include <msp430fr5994.h>
    #include <stdint.h>
    #include <stdbool.h>

    #define PWM_FREQ 1000
    #define PWM_MAX_DUTY 1000
    #define BREATH_STEP 5

    volatile uint16_t duty = 0;
    volatile int8_t step = BREATH_STEP;

    void SystemClock_Init(void)
    {
    FRCTL0 = FRCTLPW | NWAITS_1;

    CSCTL0_H = CSKEY_H;
    CSCTL1 = DCOFSEL_6;
    CSCTL2 = SELA__LFXTCLK | SELS__DCOCLK | SELM__DCOCLK; // ACLK=LFXT=32768Hz
    CSCTL3 = DIVA__1 | DIVS__1 | DIVM__1;
    CSCTL4 &= ~LFXTOFF; // 使能LFXT
    while(!(CSCTL5 & LFXTOFFG)); // 等待LFXT稳定
    CSCTL0_H = 0;
    }

    void PWM_Init(void)
    {
    P1DIR |= BIT2;
    P1SEL1 &= ~BIT2;
    P1SEL0 |= BIT2;

    TA0CTL = TASSEL__SMCLK | ID__1 | MC__UP;
    TA0CCR0 = (8000000 / PWM_FREQ) – 1;
    TA0CCTL1 = OUTMOD_7;
    TA0CCR1 = 0;
    }

    void BreathTimer_Init(void)
    {
    // 配置Timer_A1为10ms中断一次,用于更新占空比
    TA1CTL = TASSEL__ACLK | ID__1 | MC__UP | TACLR; // ACLK=32768Hz
    TA1CCR0 = 328 – 1; // 约10ms中断一次
    TA1CCTL0 = CCIE; // 使能CCR0中断
    }

    int main(void)
    {
    WDTCTL = WDTPW | WDTHOLD;

    SystemClock_Init();
    PWM_Init();
    BreathTimer_Init();

    PM5CTL0 &= ~LOCKLPM5;

    __enable_interrupt();

    while(1)
    {
    __low_power_mode_3(); // 进入LPM3低功耗模式,等待中断唤醒
    }
    }

    // 呼吸灯定时器中断服务函数,每10ms执行一次
    #pragma vector=TIMER1_A0_VECTOR
    __interrupt void Timer1_A0_ISR(void)
    {
    duty += step;

    if(duty >= PWM_MAX_DUTY)
    {
    duty = PWM_MAX_DUTY;
    step = -BREATH_STEP;
    }
    else if(duty <= 0)
    {
    duty = 0;
    step = BREATH_STEP;
    }

    TA0CCR1 = (TA0CCR0 + 1) * duty / PWM_MAX_DUTY;
    }

    优化效果:CPU 大部分时间处于 LPM3 低功耗模式,系统功耗可降低至微安级别,非常适合电池供电的设备。


    7. 常见问题与工程优化技巧

    7.1 常见问题排查

  • PWM 输出无波形:

    • 检查 GPIO 引脚配置是否正确 (SEL0/SEL1 位)
    • 检查定时器时钟源是否正常工作
    • 检查 CCR0 和 CCRn 的值是否合理
  • 定时器中断不触发:

    • 检查是否开启了全局中断 (__enable_interrupt())
    • 检查是否使能了相应的中断 (CCIE 位)
    • 检查中断向量是否正确
  • PWM 占空比不准确:

    • 确保 CCR0 的值大于 CCRn 的值
    • 注意增计数模式和增 / 减计数模式的占空比计算公式不同
    • 避免在中断服务函数中修改 CCR 值时产生的竞争条件
  • 7.2 工程优化技巧

  • 提高 PWM 精度:

    • 选择更高频率的时钟源
    • 适当降低 PWM 频率
    • 使用 16 位定时器的全部分辨率
  • 降低系统功耗:

    • 优先选择 ACLK 作为定时器时钟源
    • 使用非阻塞式延时,让 CPU 进入低功耗模式
    • 不需要时及时停止定时器
  • 提高代码可移植性:

    • 将定时器相关代码封装为独立的函数
    • 使用宏定义配置定时器参数
    • 避免直接操作寄存器,使用 HAL 库或驱动库

  • 下一篇预告

    下一篇《MSP430 实战教程 – 07:中断系统详解》,我们将深度剖析 MSP430FR 中断系统核心机制,详解中断优先级、中断嵌套原理,规范中断服务函数编写格式,讲解各类中断源使用方法,为后续低功耗开发筑牢核心基础。

    原创不易,如果本文对你有帮助,欢迎点赞、收藏、关注三连!有任何问题都可以在评论区留言,我会及时回复。

     

    赞(0)
    未经允许不得转载:171主机测评 » MSP430 实战教程-06:定时器与 PWM 输出
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址