灵动微电子 · 2022年12月27日 · 北京市朝阳区

灵动微课堂 | MM32L013x——LPTIM的应用介绍

低功耗技术旨在减少设备电流消耗,延长电池寿命,可以有效的延长产品的使用寿命。这就需要合理分配运行时间和空闲时间,在空闲时可以通过使MCU进入低功耗模式降低系统的功耗,在需要MCU工作时唤醒MCU,低功耗定时器(LPTIM)有助于降低功耗,特别是当系统处于低功耗模式时(如 stop模式)可以在休眠模式下实现外部脉冲计数功能。通过外部输入的触发信号,能够实现低功耗超时唤醒。LPTIM 具有外部时钟计数,超时唤醒功能和 PWM 输出等多种用途。

本文主要介绍 MM32 全新低功耗系列 MM32L013x 产品中的 LPTIM 外设模块,它允许系统执行简单的任务,同时功耗保持在绝对最小。

1 LPTIM 简介

LPTIM 由一个 16 位计数器组成,可以为用户提供便捷的计数和定时功能。LPTIM 运行在 CORE 电源域,可以工作在低功耗模式下,具有低功耗的特点,当然也可以被当做一个普通的 16 位基础定时器来使用。

2 LPTIM 常见用途

  • 脉冲计数功能
  • PWM 生成器
  • 周期性地超时唤醒sleep模式、stop模式

以上所有功能应用均为普通 TIM 所不能实现的,正是由于 LPTIM 的时钟源具有多样性,使得其能够在所有电源模式(standby模式除外)下保持运行状态。

3 LPTIM 主要特性

  • 16 位递增计数器,相当于 LPT_CNT 值由模块时钟驱动加一递增最大到 65535,并且包括一个 16 位比较寄存器和目标值寄存器
  • 3 位异步时钟预分频器,可以将输入进模块的时钟进行多种系数的预分频,包括: 1/2/4/8/16/32/64/128 分频
  • 多种内部和外部的时钟源可选,包括:LSI/LSE/PCLK2

image.png

  • 可以选择不同边沿的脉冲极性用作触发计数,触发源包括:LPTIM1_TRIGGER 引脚输入触发、COMP OUT 事件触发
  • 可以设定不同的 LPT_CMP 和 LPT_TARGET 值从而通过 LPTIM1_OUT 引脚输出不同占空比的PWM 波形或者方波
  • 可以设定不同的 LPT_TARGET 值从而进行不同时长的低功耗唤醒
  • 可以开启不同的中断,包括:外部触发、比较匹配和计数器溢出中断,在唤醒 STOP 模式时,除了需要使能相应的中断外,还需配置 EXTI_Line23 并且使能相关的功能

4 LPTIM 功能框图

LPTIM 的功能框图如下,主要包括了:触发源选择模块、时钟输入及分频模块、计数模块以及比较匹配输出单元等。

image.png

涉及到时钟选择的部分需要设置RCC_CFGR2[30:29],在 UM 手册中时钟配置寄存器2(RCC_CFGR2):

image.png

使能LPTIM时钟需要设置RCC_APB2ENR[31]位:
image.png
涉及到 LPTIM1_TRIGGER 和 LPTIM1_OUT 引脚的功能定义情况在 DS 手册中引脚复用及复用列表:
image.png

5 LPTIM 寄存器表

由于 MM32L013x 系列产品中只有一个 LPTIM 模块,所以在文档和程序中也习惯称作 LPTIM1,它的基地址为 0x40012800,所有寄存器设计为 16 位,预留出 16位保持 32 位对齐,寄存器占用情况如下:

image.png

其中 LPT_CFG 、 LPT_IE 、 LPT_CTRL 、LPT_CMP 、 LPT_TARGET 为可读可写操作的,LPT_IF 为可读并且写 1 清零操作的,LPT_CNT 为只读操作的,具体功能描述参见 UM 对应章节内容。

6 LPTIM 功能实现

6.1  带溢出中断的普通定时器

  • 使能 APB2 总线上的 LPTIM 外设时钟,用于同步
  • 初始化配置 LPT_CFG 寄存器 MODE=0,计数器被触发后保持运行,直到被关闭为止
  • 初始化配置 LPT_CFG 寄存器 TMODE=00,选择普通计数器模式
  • 选择外部时钟或内部时钟作为计数器的时钟源
  • 配置 LPT_IE =1,使能 LPTIM 计数器溢出中断
  • 配置 LPT_CTRL 寄存器 LPTEN=1,使能 LPTIM 计数器
  • 计数器使能后有两个周期的同步过程,同步完成后,计数器开始工作,计数达到目标之后回到 0 重新开始计数,并产生溢出中断

按上述流程的代码配置如下:

void LPTIM1_Init(u16 arr)
{
    LPTIM_TimeBaseInit_TypeDef  init_struct;

    RCC_APB2PeriphClockCmd(RCC_APB2ENR_LPTIM1, ENABLE);

    LPTIM_TimeBaseStructInit(&init_struct);
    init_struct.ClockSource            = LPTIM_PCLK_Source;
    init_struct.CountMode              = LPTIM_CONTINUOUS_COUNT_Mode;
    init_struct.OutputMode             = LPTIM_NORMAL_WAV_Mode;
    init_struct.Waveform               = LPTIM_AdjustPwmOutput_Mode;
    init_struct.Polarity               = LPTIM_Positive_Wave;
    init_struct.ClockDivision          = LPTIM_CLK_DIV1;

    if(init_struct.ClockSource == LPTIM_LSE_Source) {
        RCC_APB1PeriphClockCmd(RCC_APB1ENR_PWR | RCC_APB1ENR_BKP, ENABLE);
        PWR_BackupAccessCmd(ENABLE);
        RCC_LSEConfig(RCC_LSE_ON);
        DELAY_Ms(5000);
        while(!RCC_GetFlagStatus(RCC_FLAG_LSERDY));
        LPTIM_CLKConfig(LPTIM1, LPTIM_LSE_Source);
    }
    else if(init_struct.ClockSource == LPTIM_LSI_Source) {
        RCC_APB1PeriphClockCmd(RCC_APB1ENR_PWR | RCC_APB1ENR_BKP, ENABLE);
        PWR_BackupAccessCmd(ENABLE);
        RCC_LSICmd(ENABLE);
        DELAY_Ms(500);
        while(!RCC_GetFlagStatus(RCC_FLAG_LSIRDY));
        LPTIM_CLKConfig(LPTIM1, LPTIM_LSI_Source);
    }
    else { //(init_struct.ClockSource == LPTIM_PCLK_Source)
        LPTIM_CLKConfig(LPTIM1, LPTIM_PCLK_Source);
    }

    LPTIM_TimeBaseInit(LPTIM1, &init_struct);

    LPTIM_SetTarget(LPTIM1, arr);
}

void LPTIMER1_IRQHandler(void)
{
    if(LPTIM_GetITStatus(LPTIM1, LPTIF_OVIF)) {
        LPTIM_ClearITPendingBit(LPTIM1, LPTIF_OVIF);
        LED3_TOGGLE();
        LED4_TOGGLE();
    }
}

void NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStruct;

    NVIC_InitStruct.NVIC_IRQChannel = LPTIMER1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPriority = 2;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
}

s32 main(void)
{
    DELAY_Init();
    LED_Init();
    LPTIM1_Init(4000 - 1);
    NVIC_Configuration();
    LPTIM_ITConfig(LPTIM1, LPTIE_OVIE, ENABLE);
    LPTIM_Cmd(LPTIM1, ENABLE);
    while (1)
    {
        LED1_TOGGLE();
        LED2_TOGGLE();
        DELAY_Ms(1000);
    }
}

6.2  PWM 输出

  • 初始化中使用以上相同的步骤,需要额外配置 LPT_CFG 寄存器 PWM=1,选择 PWM 输出模式
  • 再配置 LPT_CMP 和 LPT_TARGET 寄存器,设定比较值和目标值,PWM 的占空比由比较值和目标值决定,输出在计数器值等于比较值翻转为 1,在等于目标值时翻转为 0
  • 另外需要配置一组 LPTIM1_OUT 引脚用于输出 PWM 波形,这里以 PB14 为例

代码配置如下:

void LPTIM_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    RCC_GPIO_ClockCmd(GPIOB, ENABLE);

    //set PB14 as LPTIM1 Output Pin
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource14, GPIO_AF_3);

    GPIO_InitStruct.GPIO_Pin  =  GPIO_Pin_14;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStruct);
}

void LPTIM1_Init(u16 arr)
{
    LPTIM_TimeBaseInit_TypeDef  init_struct;

    RCC_APB2PeriphClockCmd(RCC_APB2ENR_LPTIM1, ENABLE);

    LPTIM_TimeBaseStructInit(&init_struct);
    init_struct.ClockSource            = LPTIM_PCLK_Source;
    init_struct.CountMode              = LPTIM_CONTINUOUS_COUNT_Mode;
    init_struct.OutputMode             = LPTIM_NORMAL_WAV_Mode;
    init_struct.Waveform               = LPTIM_AdjustPwmOutput_Mode;
    init_struct.Polarity               = LPTIM_Positive_Wave;
    init_struct.ClockDivision          = LPTIM_CLK_DIV1;

    if(init_struct.ClockSource == LPTIM_LSE_Source) {
        RCC_APB1PeriphClockCmd(RCC_APB1ENR_PWR | RCC_APB1ENR_BKP, ENABLE);
        PWR_BackupAccessCmd(ENABLE);
        RCC_LSEConfig(RCC_LSE_ON);
        DELAY_Ms(5000);
        while(!RCC_GetFlagStatus(RCC_FLAG_LSERDY));
        LPTIM_CLKConfig(LPTIM1, LPTIM_LSE_Source);
    }
    else if(init_struct.ClockSource == LPTIM_LSI_Source) {
        RCC_APB1PeriphClockCmd(RCC_APB1ENR_PWR | RCC_APB1ENR_BKP, ENABLE);
        PWR_BackupAccessCmd(ENABLE);
        RCC_LSICmd(ENABLE);
        DELAY_Ms(500);
        while(!RCC_GetFlagStatus(RCC_FLAG_LSIRDY));
        LPTIM_CLKConfig(LPTIM1, LPTIM_LSI_Source);
    }
    else { //(init_struct.ClockSource == LPTIM_PCLK_Source)
        LPTIM_CLKConfig(LPTIM1, LPTIM_PCLK_Source);
    }

    LPTIM_TimeBaseInit(LPTIM1, &init_struct);
}

s32 main(void)
{
    DELAY_Init();
    LED_Init();
    LPTIM1_Init(4000 - 1);
    LPTIM_SetCompare(LPTIM1, 4000 / 2 - 1);
    LPTIM_SetTarget(LPTIM1, 4000 );
    LPTIM_GPIO_Init();
    LPTIM_Cmd(LPTIM1, ENABLE);
    while (1)
    {
        LED1_TOGGLE();
        LED2_TOGGLE();
        DELAY_Ms(1000);
    }
}

6.3  Trigger 脉冲触发计数

  • 初始化中使用以上相同的步骤,需要修改配置 LPT_CFG 寄存器 TMODE=01,选择 Trigger 脉冲触发计数模式
  • 配置 LPT_CFG 寄存器 TRIGSEL = 0,选择外部引脚触发计数
  • 配置 LPT_CFG 寄存器 TRIGCFG,选择外部触发信号的有效沿
  • 用户可以根据需求选择是否使能滤波器,配置LPT_CFG 寄存器
  • 根据需求是否使能外部触发中断位
  • 另外需要配置一组 LPTIM1_TRIGGER 引脚用于触发输入,这里以 PB13 为例

代码配置如下:

void LPTIM_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    RCC_GPIO_ClockCmd(GPIOB, ENABLE);

    //set PB13 as LPTIM1 Trigger Pin
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_3);

    GPIO_InitStruct.GPIO_Pin  =  GPIO_Pin_13;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStruct);
}

void LPTIM1_Init(u16 arr)
{
    LPTIM_TimeBaseInit_TypeDef  init_struct;

    RCC_APB2PeriphClockCmd(RCC_APB2ENR_LPTIM1, ENABLE);

    LPTIM_TimeBaseStructInit(&init_struct);
    init_struct.ClockSource            = LPTIM_PCLK_Source;
    init_struct.CountMode              = LPTIM_CONTINUOUS_COUNT_Mode;
    init_struct.OutputMode             = LPTIM_PULSE_TRIG_Mode;
    init_struct.Waveform               = LPTIM_AdjustPwmOutput_Mode;
    init_struct.Polarity               = LPTIM_Positive_Wave;
    init_struct.ClockDivision          = LPTIM_CLK_DIV1;

    if(init_struct.ClockSource == LPTIM_LSE_Source) {
        RCC_APB1PeriphClockCmd(RCC_APB1ENR_PWR | RCC_APB1ENR_BKP, ENABLE);
        PWR_BackupAccessCmd(ENABLE);
        RCC_LSEConfig(RCC_LSE_ON);
        DELAY_Ms(5000);
        while(!RCC_GetFlagStatus(RCC_FLAG_LSERDY));
        LPTIM_CLKConfig(LPTIM1, LPTIM_LSE_Source);
    }
    else if(init_struct.ClockSource == LPTIM_LSI_Source) {
        RCC_APB1PeriphClockCmd(RCC_APB1ENR_PWR | RCC_APB1ENR_BKP, ENABLE);
        PWR_BackupAccessCmd(ENABLE);
        RCC_LSICmd(ENABLE);
        DELAY_Ms(500);
        while(!RCC_GetFlagStatus(RCC_FLAG_LSIRDY));
        LPTIM_CLKConfig(LPTIM1, LPTIM_LSI_Source);
    }
    else { //(init_struct.ClockSource == LPTIM_PCLK_Source)
        LPTIM_CLKConfig(LPTIM1, LPTIM_PCLK_Source);
    }

    LPTIM_TimeBaseInit(LPTIM1, &init_struct);

    LPTIM1->CFGR = LPTIM_ExInputUpEdge;
    LPTIM1->CFGR = LPTIM_External_PIN_Trig;
}

6.4  1s 周期性唤醒 STOP 模式

详细可以参见灵动微课堂 | 使用MM32F0270 LPTIM从STOP模式唤醒
image.png

作者:灵动MM32
文章来源:灵动MM32MCU

推荐阅读

更多MM32F5系列资料请关注灵动MM32 MCU专栏。如想进行MM32相关芯片技术交流,请添加极术小姐姐微信(id:aijishu20)加入微信群。
推荐阅读
关注数
6151
内容数
276
灵动MM32 MCU相关技术知识,欢迎关注~
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息