【凌鸥LKS32】基础篇(二)·Timer定时器实现LED闪烁

目录

[1. 简介](#1. 简介)

[2. 模式](#2. 模式)

[2.1 计数器](#2.1 计数器)

[2.2 捕获模式](#2.2 捕获模式)

[2.3 比较模式(边沿对齐PWM)](#2.3 比较模式(边沿对齐PWM))

[2.4 单次触发](#2.4 单次触发)

[2.5 中心模式(互补 PWM)](#2.5 中心模式(互补 PWM))

[3. 代码编写](#3. 代码编写)

[3.1 定时器初始化](#3.1 定时器初始化)

[3.2 中断服务函数](#3.2 中断服务函数)

[3.3 主函数](#3.3 主函数)


1. 简介

通用定时器 TIMER 主要包括 4 个独立的 Timer。分别可以独立配置运行计数时钟和滤波常数。每个 Timer 可以用于输出特定周期占空比的波形,也可以捕获外部波形进行周期占空比的检测。

定时器模块有以下特点:

  • 独立工作,可工作在不同频率下
  • Timer0/1 为 16bit 通用定时器
  • Timer2/3 为 32bit 通用定时器
  • 每个通用定时器处理 2 个外部输入信号(捕获模式),或者产生 2 个输出信号(比较模式)
  • 对每个输入信号可以进行最大 2040 个系统主时钟的滤波,即,当芯片工作在 96MHz 时钟频率下时,可以滤除 21μs 宽度以下毛刺

2. 模式

2.1 计数器

Timer 中的计数器可以选择递增或递减方向计数,默认为递增计数。

  • 若为递增计数,则计数器从 0 计数到 TH 值,再回到 0 重新开始计数,计数器回到 0 时,产生回零中断。
  • 若为递减计数,则计数器从 TH 计数到 0 值,再回到 TH 重新开始计数,计数器回到 0 时,产生回零中断。

实际计数周期为(TH+1)/clk_freq,其中clk_freq是定时器的时钟频率。

2.2 捕获模式

捕获模式下,可以使用 Timer 来检测输入信号的上升 / 下降或者双沿,发生捕获事件(即输入信号电平变化)时,定时器计数值存入 TIMERx_CMP 寄存器,并产生捕获中断。计数器回零时,仍然会产生回零中断。

2.3 比较模式(边沿对齐PWM)

比较模式下,计数器计数到 TIMERx_CMP 值时,产生比较中断。比较模式可以驱动一个比较脉冲发生,在回零时,向 IO 口输出一个电平(极性可配置),在比较事件发生时,电平翻转,向 IO 口输出另一个电平。计数器回零时,仍然会产生回零中断。设置 TIMERx_CMP0=0,可使得 Timer X 通道 0 为恒 1,设置 TIMERx_CMP0=TIMERx_TH+1,可使得 Timer 通道 0 为恒 0。

2.4 单次触发

定时器配置在比较模式(边沿对齐 PWM),且 TIMERx_CFG.EN = 0(定时器处于空闲、未计数状态),软件向寄存器位 ONE_TRIG 写 1,硬件自动输出单个完整周期、指定占空比的 PWM 波形。

注意:如果定时器正在执行单次波形计数过程中,再次对 ONE_TRIG 写 1,本次触发命令直接忽略;定时器会继续跑完当前周期,不会重新启动。

也就是:周期未完成前,频繁软件触发无效。

2.5 中心模式(互补 PWM)

除递增计数和递减外,计数器还可以设置为中心计数模式,即 TIMERx_CFG.CENTER=1。此时,计数器从 0 递增计数至 TH 后再递减计数回 0,开始下一个周期的中心计数。实际计数周期为2TH/clk_freq,clk_freq 指定时器的时钟频率。

中心计数模式可以用于输出互补 PWM 波形,通过合理设置 TIMERx_CMP0/1 可以产生上下管导通死区,死区时间=TIMERx_CMP1-TIMERx_CMP0。此外,需要合理设置通道的初始值以产生互补波形。Timer 在递增计数和递减计数期间都会有TIMERx_CNT=TIMER_CMP0/1 的时刻,但只有递增计数阶段会产生中断事件。

3. 代码编写

这里只说明计数器的用法,也就是定时器中断,实现功能是每隔1s让LED1电平反转一次,代码沿用上一章用系统定时器做LED闪烁的代码:

【凌鸥LKS32】基础篇(一)·点亮一颗LED-CSDN博客

3.1 定时器初始化

我们先来看一下定时器初始化结构体部分:

cpp 复制代码
typedef struct
{
    FuncState Timer_EN;         /**< 定时器使能,ENABLE使能,DISABLE失能*/
    uint32_t Timer_CAP1_CLR_EN; /**< 当发生CAP1捕获事件时,清零 Timer 计数器,高有效*/
    uint32_t Timer_CAP0_CLR_EN; /**< 当发生CAP0捕获事件时,清零 Timer 计数器,高有效*/

    uint32_t Timer_ONE_TRIG; /**< 单次发送模式,此位需要在Timer比较模式下使用,且对应定时器使能需设置为0*/

    uint32_t Timer_CENTER; /**< 中心计数模式使能,0:Timer向上从0计数至TH,然后回0,或Timer向下
                           从TH计数至0,然后回到TH,1:Timer向上从0计数至TH,然后向下计数至0*/
    uint32_t Timer_DIR;    /**< 0:0->TH 递增计数,1:TH->递减计数*/
                           /**
                            * @brief 定时器分频 \n
                            * @see TIM_Clk_Div1 \n
                            * @see TIM_Clk_Div2 \n
                            * @see TIM_Clk_Div4 \n
                            * @see TIM_Clk_Div8 \n
                            * @see TIM_Clk_Div16 \n
                            * @see TIM_Clk_Div32 \n
                            * @see TIM_Clk_Div64 \n
                            * @see TIM_Clk_Div128
                            */
    uint32_t Timer_ClockDiv;
    uint32_t Timer_ETON;    /**< Timer计数器计数外部启动使能,0: 自动运行,1:外部事件触发计数*/
    uint32_t Timer_GATE_EN; /**< Timer暂停使能,0:不暂停 1:当外部信号为低时,Timer暂停计数,
                              外部信号根据TIMER1_EVT.EVT_SRC 进行选择*/
    uint32_t Timer_RL_EN;   /**< Timer重装使能,0:禁用外部事件重装,1:使能外部事件重装*/
    uint32_t Timer_XCLK_EN; /**< Timer时钟源,0: 芯片内部时钟,1:外部时钟*/
                            /**
                             * @brief 定时器分频 \n
                             * @see CAP_SRC_TCH0 \n
                             * @see CAP_SRC_TCH1 \n
                             * @see CAP_SRC_CLU0 \n
                             * @see CAP_SRC_CLU1 \n
                             * @see CAP_SRC_CLU2 \n
                             * @see CAP_SRC_CLU3 \n
                             * @see CAP_SRC_CMP0 \n
                             * @see CAP_SRC_CMP1 \n
                             * @see CAP_SRC_CMP2 \n
                             * @see CAP_SRC_CHXOR
                             */
    uint32_t Timer_SRC1;
    uint32_t Timer_CH1Output;     /**< Timer 通道1在比较模式下的输出极性控制,当计数器 CNT<CMP1时的输出值,0:低电平,1:高电平*/
    uint32_t Timer_CH1_WorkMode;  /**< Timer 通道1的工作模式选择,0: 比较模式 ,1: 捕获模式*/
    uint32_t Timer_CH1_FE_CAP_EN; /**< Timer通道1下升沿捕获事件使能,1:使能,0:关闭*/
    uint32_t Timer_CH1_RE_CAP_EN; /**< Timer通道1上升沿捕获事件使能,1:使能,0:关闭*/
                                  /**
                                   * @brief 定时器分频 \n
                                   * @see CAP_SRC_TCH0 \n
                                   * @see CAP_SRC_TCH1 \n
                                   * @see CAP_SRC_CLU0 \n
                                   * @see CAP_SRC_CLU1 \n
                                   * @see CAP_SRC_CLU2 \n
                                   * @see CAP_SRC_CLU3 \n
                                   * @see CAP_SRC_CMP0 \n
                                   * @see CAP_SRC_CMP1 \n
                                   * @see CAP_SRC_CMP2 \n
                                   * @see CAP_SRC_CHXOR
                                   */
    uint32_t Timer_SRC0;          /**< Timer 捕获模式通道0信号来源*/
    uint32_t Timer_CH0Output;     /**< Timer 通道0在比较模式下的输出极性控制,当计数器 CNT<CMP1时的输出值,0:低电平,1:高电平*/
    uint32_t Timer_CH0_WorkMode;  /**< Timer 通道0的工作模式选择,0: 比较模式 ,1: 捕获模式*/
    uint32_t Timer_CH0_FE_CAP_EN; /**< Timer通道0下升沿捕获事件使能,1:使能,0:关闭*/
    uint32_t Timer_CH0_RE_CAP_EN; /**< Timer通道0上升沿捕获事件使能,1:使能,0:关闭*/

    uint32_t Timer_TH;   /**< Timer计数器计数门限*/
    uint32_t Timer_CNT;  /**< Timer计数器计数门限*/
    uint32_t Timer_CMP0; /**< 通道0比较捕获寄存器*/
    uint32_t Timer_CMP1; /**< 通道1比较捕获寄存器*/
    /**
     * @brief 定时器分频 \n
     * @see TIMER0_EVT_CH0 \n
     * @see TIMER0_EVT_CH1 \n
     * @see ......      \n
     * @see TIMER_EVT_TADC2 \n
     * @see TIMER_EVT_TADC3
     */
    uint32_t Timer_EVT; /**< Timer外部事件选择*/
    uint32_t Timer_FLT; /**< 通道0/1信号滤波宽度选择。取值范围0~255,滤波公式=Timer_FLT*8*定时器时钟*/
    /**
     * @brief 定时器中断使能 \n
     * @see Timer_IRQEna_CH0     \n
     * @see Timer_IRQEna_CH1     \n
     * @see Timer_IRQEna_ZC      \n
     * @see Timer0_IRQEna_FAIL   \n
     * @see Timer_IRQEna_CH0_RE  \n
     * @see Timer_IRQEna_CH1_RE  \n
     * @see Timer_IRQEna_ZC_RE   \n
     * @see Timer0_IRQEna_FAIL_RE
     */
    uint32_t Timer_IRQEna; /**< 中断使能*/

    uint32_t Timer0_UPDATE;      /**< 软件更新,1:将 TH/CMP0/CMP1 的预装载值加载到影子寄存器 STH/SCMP0/SCMP1
                                  之中,0:无作用*/
    uint32_t Timer0_SHADOW;      /**< 0:软件写入TH/CMP0/CMP1 时直接更新对应的影子寄存器,1:启用影子寄存器,
                                  软件写入 TH/CMP0/CMP1 时进仅仅更新预装载值,当Timer发生过零事件时才将预
                                  装载值写入影子寄存器*/
    uint32_t Timer0_CH1_DEFAULT; /**< 定时器0发生Fail事件MOE清零后,CH1通道输出值*/
    uint32_t Timer0_CH0_DEFAULT; /**< 定时器0发生Fail事件MOE清零后,CH0通道输出值*/
    uint32_t Timer0_HALT_PRT;    /**< MCU 进入HALT状态,Timer输出值选择。1:正常输出;0:输出 CH1_DEFAULT 和 CH0_DEFAULT。*/
    uint32_t Timer0_FAIL_SEL;    /**< FAIL信号选择,0:TIMER0_FAIL,来自GPIO,1:CLU0输出*/
    uint32_t Timer0_FAIL_POL;    /**< FAIL信号极性,0:高电平FAIL,1:低电平FAIL*/
    uint32_t Timer0_FAIL_EN;     /**< FAIL 信号使能,0:禁用 FAIL,1:使能 FAIL*/
} TIM_TimerInitTypeDef;

可以看到有很多参数,不过在这里我们有些暂时都用不到,我们将这些参数按照功能维度分类:

功能域 涉及的字段(默认值=0) 为什么不需要动
捕获模式 Timer_CHx_WorkMode, Timer_SRCx, Timer_CHx_FE/RE_CAP_EN, Timer_CAPx_CLR_EN, Timer_FLT 没开捕获模式,相关寄存器无论如何取值都不参与硬件行为
PWM/比较输出 Timer_CHxOutput, Timer_CMP0, Timer_CMP1 没使能 CH0/CH1 比较中断,无 PWM 输出需求
外部事件 Timer_ETON, Timer_GATE_EN, Timer_RL_EN, Timer_XCLK_EN, Timer_EVT 时钟源选内部(96MHz PLL),不需要外部触发/门控/重载
Fail 安全 Timer0_FAIL_EN/SEL/POL, Timer0_CHx_DEFAULT, Timer0_HALT_PRT 电机控制的保护机制,与定时演示无关
影子寄存器 Timer0_UPDATE, Timer0_SHADOW 仅在运行时动态修改 TH/CMP 才需要,初始化阶段不用

这里我们真正起作用的参数:

Timer_ClockDiv:决定计数频率

Timer_TH :决定中断周期

Timer_IRQEna :决定触发哪种中断

Timer_EN :决定定时器开关

配置如下:

cpp 复制代码
void Timer0_Init(void)
{
    TIM_TimerInitTypeDef TIM_InitStruct;
    TIM_TimerStrutInit(&TIM_InitStruct);

    TIM_InitStruct.Timer_CH0_WorkMode = TIMER_OPMode_CMP;
    TIM_InitStruct.Timer_CH1_WorkMode = TIMER_OPMode_CMP;
    TIM_InitStruct.Timer_TH   = 37499;
    TIM_InitStruct.Timer_ClockDiv = TIMER_CLK_DIV128;
    TIM_InitStruct.Timer_IRQEna = Timer_IRQEna_ZC;
    TIM_InitStruct.Timer_EN = ENABLE;

    TIM_TimerInit(UTIMER0, &TIM_InitStruct);

    NVIC_EnableIRQ(TIMER0_IRQn);
}

这里还配置了CH0/CH1 的工作模式,二者只在触发比较中断或输出 PWM 时才生效,对仅用过零中断的场景,这两个寄存器位写什么都不会改变行为,至于为什么在这里重新声明一边,官方SDK给的直接沿用了,并且这样写万一 SDK 升级改了 StructInit 的默认值,显式赋值不受影响。

这里准备使用Timer0,我们知道Timer0是16位寄存器,最大数据为65535,又由于此时我们此时使用的是96MHz的主频,根据公式:

计数周期 = (TH+1)/(主频/分频系数)

举个例子:50ms周期

定时器时钟 = 96MHz / 128 = 750kHz

计数周期 = (37499 + 1) / 750000 = 0.05s = 50ms

分频系数 定时器时钟 需要的 TH 值 16位能装下
DIV1 96 MHz 4,799,999 ❌
DIV2 48 MHz 2,399,999 ❌
DIV4 24 MHz 1,199,999 ❌
DIV8 12 MHz 599,999 ❌
DIV16 6 MHz 299,999 ❌
DIV32 3 MHz 149,999 ❌
DIV64 1.5 MHz 74,999 ❌ (超 65535)
DIV128 750 kHz 37,499 ✅

因此这里只能给到128分频,TH值为37499,中断选择过零中断:

cpp 复制代码
/**
 * @brief 中断使能配置定义
 */
typedef enum
{
    Timer_IRQEna_None  = 0,    /**< 关闭所有中断*/
    Timer_IRQEna_CH0   = BIT0, /**< 通道 0 中断*/
    Timer_IRQEna_CH1   = BIT1, /**< 通道 1 中断*/
    Timer_IRQEna_ZC    = BIT2, /**< 计数器过 0 中断*/
    Timer0_IRQEna_FAIL = BIT3, /**< 定时器0,FAIL事件中断*/

    Timer_IRQEna_CH0_RE   = BIT8,  /**< 通道0比较/捕获DMA请求使能*/
    Timer_IRQEna_CH1_RE   = BIT9,  /**< 通道1比较/捕获DMA请求使能*/
    Timer_IRQEna_ZC_RE    = BIT10, /**< 计数器过0 DMA请求使能*/
    Timer0_IRQEna_FAIL_RE = BIT11, /**< 定时器0,FAIL事件DMA请求使能*/

    Timer_IRQEna_All = 0xf /**< 开启所有中断*/
} Timer_IRQEnaDef;

3.2 中断服务函数

我们知道刚刚设置的是每隔50ms产生一次中断,然后这里通过 tick_cnt 进行累加计数,实现1s定时器的操作,实现每隔1s让LED1电平反转一次:

cpp 复制代码
void TIMER0_IRQHandler(void)
{
	static uint8_t tick_cnt = 0;

	if (TIM_GetIRQFlag(UTIMER0, TIMER_IF_ZERO))
	{
			TIM_ClearIRQFlag(UTIMER0, Timer_IRQEna_ZC);

			tick_cnt++;
			if (tick_cnt >= 20)
			{
					tick_cnt = 0;
					if (GPIO_ReadOutputDataBit(LED1_PORT, LED1_PIN))
					{
							GPIO_ResetBits(LED1_PORT, LED1_PIN);
					}
					else
					{
							GPIO_SetBits(LED1_PORT, LED1_PIN);
					}
			}
	}
}

3.3 主函数

主函数将LED1部分删除即可:

cpp 复制代码
int main(void)
{
	Hardware_init(); /* 硬件初始化 */
	
	while(1)
	{
		GPIO_ResetBits(LED2_PORT, LED2_PIN);  /* LED2 灭*/
		SoftDelay(0x68A000);                
		
		GPIO_SetBits(LED2_PORT, LED2_PIN);    /* LED2 亮*/
		SoftDelay(0x68A000);    
	}
}

然后就会发现 LED1 和 LED2 开始每隔1s同步闪烁,不过由于0x68A000是我们计算的定时,可能不准确,后续可能就不会同步了。

工程源码:

凌鸥LKS32MC071系列单片机定时器实现LED闪烁资源-CSDN下载

凌鸥LKS32_时光の尘的博客-CSDN博客

相关推荐
女神下凡7 分钟前
芯参谋(38):NAND_MCP_电路设计指南
arm开发·单片机·嵌入式硬件·设计规范
嵌入式学习菌29 分钟前
FreeRTOS 中断管理实战:BASEPRI 寄存器实验
单片机·嵌入式硬件
HRTOS1 小时前
HRTOS:一个名字背后的目标——不断逼近硬实时
经验分享·单片机·系统架构·51单片机
UrSpecial4 小时前
GPIO 是什么?
单片机·嵌入式硬件
修船大队长6 小时前
配置接受报文每个报文对应一个mailbox配置流程记录
网络·单片机·mcu
YCOSA20256 小时前
雨晨 Windows 11 IoT 企业版 Edge 轻装 VCDX 29680.1000
windows·物联网
搁浅小泽7 小时前
二极管选型技术规范
单片机·嵌入式硬件·汽车
搁浅小泽8 小时前
电感选型技术规范
单片机·嵌入式硬件·汽车
..Dauntless..8 小时前
【stm32】串口——在MCU内部的工作过程
stm32·单片机·嵌入式硬件
lzqrzpt9 小时前
临沂LED驱动电源工程选型标准与工艺对比解析
python·单片机·嵌入式硬件·物联网