【凌鸥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博客

相关推荐
Lzh编程小栈2 小时前
【STM32底层精讲】RCC时钟系统超全详解(时钟树+源码+避坑指南)
c语言·stm32·单片机·嵌入式硬件·面试
蓝斯49716 小时前
从.到实现一个Windows上的虚拟hid键盘设备
windows·单片机·计算机外设
伟大的前程17 小时前
unsigned int 定义的变量 在 电脑的 64 位系统中 和 32 位 cortex-m0 和 8 位的 51 单片机
单片机·嵌入式硬件
microrain18 小时前
从协议孤岛到统一接入:SagooIoT多协议接入层的架构演进
物联网·开源·go·sagooiot
小渔村的拉线工18 小时前
1.HPM6E80 解析芯片整体工作原理和存储架构
单片机·嵌入式硬件·mcu·架构·hpm6e80
熙芯XiChip18 小时前
CAN与CANopen的本质区别
单片机
科技新芯19 小时前
通义千问进入特斯拉中国车机深度测试阶段
人工智能·物联网·生活
神罚天下ET20 小时前
典型arm位单片机启动流程(从上电到main.c)
c语言·arm开发·单片机
microrain21 小时前
从云端到边缘:SagooIoT边缘计算架构的落地实践
物联网·开源·go·sagooiot