28 KiB
[TOC]
1. 定时器概述
定时器的工作如同闹钟,单不只是闹钟这么简单。
1.1. 定时器工作原理
- 计数模式:对引脚输人的外部脉冲信号进行计数(如何实现?)。
- 定时模式:对处理器内部的周期性时钟信号进行计数(时钟信号的频率是确定的,设置一个数,一个时钟信号递减1,当减少到0的时候,这个过程的时间就是定时的时间)。
- 定时时钟:在定时模式下,输人定时器的周期性时钟信号称为定时时钟(为定时器提供的稳定时钟信号)。
- 计数时间:在定时模式下,定时器内部的计数单元记一次数所花费的时间称为计数时间,该值为定时时钟频率的倒数。
根据定时时钟和计数时间的定义,我们可以得到定时器的定时时间计算公式:
定时时间=计数值x计数时间
定时时间=计数值/定时时钟频率
1.2. STM32定时器介绍
专用定时器(略):
- 开门狗(什么是看门狗?有什么用?);
- 实时时钟:就是我们所说的时间和日期;
- 低功耗定时器:低功耗模式(如休眠)下任然工作,用于休眠中唤醒的定时器。
常规定时器:
- 基本定时器:几乎没有任何对外的输入/输出通道,常用作时间基准(时基),实现基本的定时功能。
- 通用定时器:具备多路独立的捕获/比较通道,可以完成定时/计数、输入捕获、输出比较等功能还可以连接其他的传感器接口,如编码器和霍尔传感器。
- 高级定时器:高级定时器的功能最为强大,除了具备通用定时器的功能外,还增加了重复计数器带死区控制的互补信号输出等功能,可用于电机控制等领域。
总线结构(中文手册25页)
无论哪一种定时器,最基本的功能都是定时和计数,在这两个功能的基础上又衍生出其他的功能。在实际的工程应用中,最常用的定时器功能有以下三种:(P205) ① 定时/计数功能:用于产生时间基准以及测量外部脉冲的个数。 ②输出比较功能:包括 PWM 输出、电平翻转、单脉冲输出以及强制输出等功能。 ③ 输入捕获功能:用于测量输人信号的脉冲宽度。
2. HAL库外设模块设计方法
2.1. 外设句柄设计(略p205)
本质上是用一个结构体来描述一个外设(定时器、串口、I2C等寄存器等)。
2.2. 外设编程模型
HAL库根据微控制器和外设的数据传输方式,设计了轮询(一般在主程序中去读取外设的状态,从而进行相应的处理)、中断(通过中断服务进行外设状态变化的函数回调)和 DMA(类似中断,不过关联了DMA的功能) 三种编程模型,用于外设的数据传输。 以定时器的定时功能为例,三种编程模型下的接口函数分别是:
在下面的例子中,我们将会使用到中断方式启用外设。DMA方式将在下一章,串口中进行讲解。
2.3. 外设通用接口函数设计(p208)
- 初始化函数;
- I/O操作函数;
- 控制函数;
- 状态函数;
3. 定时/计数功能
3.1. 时钟源
时钟源主要有四种(p209),我们目前主要介绍内部时钟源(CK_INT)。注意,这里所说的内部时钟源是统称,表示总线上的是时钟,不是指MCU提供时钟的时钟源。为MCU提供时钟信号的也分为内部和外部,但最终都会形成总线上的时钟信号(AHB)。
通过设置相关的寄存器,选择对应的时钟源后,该时钟源将作为时基单元的预分频时钟CK_PSC送人时基单元。时钟源选择的示意图如图8-3所示。
3.2. 时基单元
时基单元是定时器的核心控制单元,负责时钟源的分频、计数和溢出重载等基本功能。它主要由三个模块组成:预分频模块、计数模块和自动重载模块。时基单元的功能框图如图 8-4所示。
3.2.1. 预分频模块
预分频模块的工作原理如下:定时器启动后,预分频计数器的初值为0。预分频时钟CK_PSC每输入一个脉冲,预分频计数器的计数值就自动加一。当计数值等于预分频寄存器中存放的预分频系数PSC时,计数值清零,并开始下一轮计数。
预分频时钟 CK_PSC 经过预分频模块后,得到计数时钟CK_CNT,它的计算公式如下:
注意:0表示不分频,1表示2分频,因此需要PSC+1,
3.2.2. 计数模块
计数模块由核心计数器和计数器寄存器TIMx_CNT组成:核心计数器用来对预分频模块输出的计数时钟 CK_CNT进行二次计数。计数时钟CK_CNT每输入一个脉冲,核心计数器的计数值就自动加一或减一(根据用户设置的不同计数模式来决定是加一还是减一)。计数器寄存器则用来存放核心计数器运行时的计数值,便于用户读取。
3.2.3. 自动重载模块
自动重载模块由自动重载寄存器TIMx_ARR构成,用来设置计数器的计数终值或计数初值,决定计数脉冲的多少(计数模式)或定时周期(定时模式)的长短。我们将TMx_ARR寄存器的内容记为自动重载值ARR。当定时器设置为递增计数模式时,ARR作为计数器的计数终值,表示记到ARR时发生溢出。当定时器设置为递减计数模式时,ARR作为计数器的计数初值,表示从ARR开始向下计数。
3.2.4. 计数模式
定时器的计数模块支持三种计数模式:递增计数、递减计数和中心对齐计数,并产生溢出事件,作为定时器的更新中断(定时中断)。
-
递增计数:计数器从0开始向上计数,当计数值等于ARR时,产生计数器上溢事件,并从0开始新一轮的计数周期。
-
递减计数:计数器从ARR开始向下计数,当计数值等于0时,产生计数器下溢事件,并从ARR开始新一轮的计数周期。
-
中心对齐计数(递增/递减计数):计数器从0开始向上计数,当计数值等于ARR-1时,产生计数器上溢事件;然后从ARR 开始向下计数,当计数值等于1时,产生计数器下溢事件。之后再从0开始新一轮的计数周期。
三种计数模式的计数过程如图 8-6所示。
预装载功能和影子寄存器(p212):
3.2.5. 定时时间计算公式
当定时器工作于定时模式时,预分频时钟 CK_PSC 等于定时时钟 TIMx_CLK。定时时间由 TIMx_CLK 的频率、预分频系数 PSC 和自动重载值 ARR 三者决定。
假设PSC=1,ARR=36,采用递增计数模式,计数器寄存器的初值为0。定时器的时序如图 8-7 所示。
预分频时钟 CK_PSC经过分频后,得到计数时钟CK_CNT,送入计数器。计数器的计数时间为1/C_KCNT,代入式(8-3),可以得到计数时间为(PSC+1)/CK_PSC。由于实际的计数值为 ARR+1,将计数值和计数时间代入式(8-1),可以得到定时时间的计算公式:
3.3. 外部脉冲计数(p214略)
对外部的脉冲信号进行计数,例如通过传感器统计生产线上物料个数。这是外部传感器在有物料通过时可以产生一个脉冲,输入到MCU,使用定时器的计数模式进行统计。
定时器的计数模式在硬件上有极性选择、分频、滤波等电路,可以实现较高频率的脉冲计数,同时可以对输入波形进行滤波,减少干扰的影响。
其实,如果对低频脉冲的统计,可以使用IO的外部中断即可(每个中断对全局变量进行累加);但是IO中断并不提供分频、滤波等功能,因此不适合高频率的脉冲,抗干扰能力也较弱。
定时器的ETR引脚和书上的不一致,请参考芯片手册。
3.4. 定时/计数功能的数据类型(略:p215)
在 stm32f1xx_hal_tim.h 的大约 46 行:
typedef struct
{
uint32_t Prescaler; /*!< Specifies the prescaler value used to divide the TIM clock.
This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
uint32_t CounterMode; /*!< Specifies the counter mode.
This parameter can be a value of @ref TIM_Counter_Mode */
uint32_t Period; /*!< Specifies the period value to be loaded into the active
Auto-Reload Register at the next update event.
This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */
uint32_t ClockDivision; /*!< Specifies the clock division.
This parameter can be a value of @ref TIM_ClockDivision */
uint32_t RepetitionCounter; /*!< Specifies the repetition counter value. Each time the RCR downcounter
reaches zero, an update event is generated and counting restarts
from the RCR value (N).
This means in PWM mode that (N+1) corresponds to:
- the number of PWM periods in edge-aligned mode
- the number of half PWM period in center-aligned mode
GP timers: this parameter must be a number between Min_Data = 0x00 and
Max_Data = 0xFF.
Advanced timers: this parameter must be a number between Min_Data = 0x0000 and
Max_Data = 0xFFFF. */
uint32_t AutoReloadPreload; /*!< Specifies the auto-reload preload.
This parameter can be a value of @ref TIM_AutoReloadPreload */
} TIM_Base_InitTypeDef;
ClockDivision 用于计数和输入捕获的滤波(略)。
3.5. 定时/计数功的接口函数(p217)
3.5.1. 时基单元初始化函数(HAL_TIM_Base_Init)
该函数用于时基单元的初始化,将用户设定的配置参数写人对应的寄存器,具体描述如表 8-7 所示。
3.5.2. 定时器轮询方式启动函数(HAL_TIM_Base_Start)
该函数用于在轮询方式下启动定时器运行,具体描述如表 8-8所示。
3.5.3. 定时器中断方式启动函数(HAL_TIM_Base_Start_IT)
该函数用于在中断方式下启动定时器运行,具体描述如表8-9所示。
3.5.4. 定时器中断通用处理函数(HAL_TIM_IROHandler)
该函数是所有定时器中断发生后的通用处理程序。任何一个定时器的相关中断(如更新中断或捕获中断)发生后,都会通过中断向量表中的定时器中断服务程序TMxIROHandler()(x表示定时器编号1~11)调用该函数。在函数内部会进行中断类型的判断,并清除对应的中断标志,然后再根据不同的中断类型,调用不同的回调函数来完成具体的中断处理任务。具体描述如表 8-10所示。
3.5.5. 定时器更新中断回调函数(HAL_TIM_PeriodElapsedCallback)
该函数用于处理发生更新中断后的具体任务。任何一个定时器发生更新中断后,都会调用更新中断回调函数。因此,在函数内部需要判断是哪一个定时器产生的本次更新中断,然后再执行具体的中断处理任务。具体描述如表8-11所示。
注意:这个函数有缺省的实现,是用weak标识的,因此一般情况下需要重新定义该函数来完成用户逻辑的处理。
3.5.6. 定时器中断标志清除函数(__HAL_TIM_CLEAR_IT)
该函数用于清除定时器的各类中断标志,采用带参数的宏实现(宏函数),与普通函数相比,宏函数省去了函数调用的过程,执行效率较高。具体描述如表 8-12所示。
3.5.7. 定时器计数值读取函数(__HAL_TIM_GET_COUNTER)
该函数用于读取定时器的计数值,采用带参数的宏实现(宏函数),具体描述如表 8-13 所示。
3.6. 基础任务:定时闪烁指示灯
3.6.1. 实现过程
使用绿色LED(PA7)进行显示,定时器4以中断方式运行,每隔1秒钟切换一次电平。
- 以前面讲的方式建立一个项目,名称是 TimerLED;
- 设置PA7为输出模式,并设置标签为LED;
- 使用TIM4作为定时器。检查TIM4挂接的总线(STM32定时器介绍),知道是挂接在APB1上;
- 检查时钟配置:
知道APB1的定时器时钟是8M。注意:定时器的中断时间是需要按照定时器所在总线的时钟进行设置的。
- 配置时钟:
勾选2位置,以便使用内部时钟(指APB总线时钟,不是外部时钟晶体和内部RC时钟源);在3位置设置PSC为7999;在4位置设置ARR为999;根据8-4公式,可以知道此时的时钟计时周期为1秒;
- 开启TIM4的全局中断,并保存生成代码框架。
- 在 main.c 的 /* USER CODE BEGIN 2 */ 位置插入下列代码:
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim4);
/* USER CODE END 2 */
- 在 main.c 文件的 /* USER CODE BEGIN 0 */ 位置插入定时中断回调函数:
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM4)
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
/* USER CODE END 0 */
好了,下载到开发板后,绿色LED应该亮一秒,灭一秒,一直循环。
3.6.2. 代码分析
main.c 的 43 行定义了外设TIM4的句柄:
TIM_HandleTypeDef htim4;
这个书上没有讲,其实就是针对 TIM4 这个外设的结构体,包括配置信息和相关的操作函数指针(有点像Java中的类定义),有兴趣可以看看。
main.c 的 147 行左右是定时器的初始化:
static void MX_TIM4_Init(void) {
/* USER CODE BEGIN TIM4_Init 0 */
/* USER CODE END TIM4_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = { 0 };
TIM_MasterConfigTypeDef sMasterConfig = { 0 };
/* USER CODE BEGIN TIM4_Init 1 */
/* USER CODE END TIM4_Init 1 */
htim4.Instance = TIM4;
htim4.Init.Prescaler = 7999;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 999;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim4) != HAL_OK) {
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK) {
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig)
!= HAL_OK) {
Error_Handler();
}
/* USER CODE BEGIN TIM4_Init 2 */
/* USER CODE END TIM4_Init 2 */
}
/* USER CODE BEGIN 2 */ 位置的代码:
HAL_TIM_Base_Start_IT(&htim4);
表示以中断方式启动TIM4;
stm32f1xx_it.c 的大约 204 行:
void TIM4_IRQHandler(void)
{
/* USER CODE BEGIN TIM4_IRQn 0 */
/* USER CODE END TIM4_IRQn 0 */
HAL_TIM_IRQHandler(&htim4);
/* USER CODE BEGIN TIM4_IRQn 1 */
/* USER CODE END TIM4_IRQn 1 */
}
此代码是自动生成的,函数 TIM4_IRQHandler(void) 是 TIM4 溢出中断调用的函数,该函数调用了 HAL_TIM_IRQHandler(),这个函数,并传入了 htim4 这个结构体的指针,标识TIM4中断;接着看 stm32f1xx_hal_tim.c 的大约 3882 行开始的 HAL_TIM_IRQHandler() 这个函数;这个函数很复杂,在大约3959行调用了 HAL_TIM_PeriodElapsedCallback(htim); 这个函数。因此该中断的调用过程是:
TIM4_IRQHandler->HAL_TIM_IRQHandler->HAL_TIM_PeriodElapsedCallback
HAL_TIM_PeriodElapsedCallback 是我们在主函数中定义的用户逻辑函数。
3.7. 进阶任务:外部脉冲计数(略)
4. PWM输出功能
上述视频是以电压来讲的,我们试图用能量的方式给大家理解:
上图是占空比 100% 的波形,显然就是直流。那么我们看做功的关系应该怎么计算?
功率:U2/R
做功:TxU2/R
显然,这里负责的R应该是一个常量。
同样,占空比为70%的波形做功应该是:
做功:DxTxU2/R
这里的D表示占空比。
如果负载是一个LED,做功多,就亮;做功少就暗,这就达到了调光的效果;同样,也可以调整电机的快慢。
注意,PWM并不适合所有的功率调节,例如LED灯的调光如果使用简单的PWM,其实是LED不断的闪烁(亮的时间和灭的时间比例)。如果闪烁频率低,会造成人眼的疲劳,导致眼睛的疾病。目前的LED调光大多是使用调整电压(电流)来进行的。对电压或者电流的调整基础一般还是PWM,不过后级需要更多的滤波电路,以及电压(电流)检测电路来形成反馈回路,使得输出更为稳定。
4.1. 捕获/比较通道
- 输入捕获单元:用于信号测量;
- 捕获/比较寄存器(TIMx_CCR):在输入模式下,用于存放存放事件的计数(边缘检测时,计数是多少,可以测量波长和占空比);输出模式下,存放一个比较值,用于和计数器寄存器(CNT)进行比较,实现信号的反转,从而实现PWM的占空比调整(后面讲)。
- 输出比较单元:根据 CCR/ARR/CNT来产生波形(PWM)。
每个定时器都有1~4个上述的电路(通道)。因此,一个时钟可以同时进行1-4个不同的PWM或者输入信号测量。后续的代码演示中,我们一般只使用一个确定定时器的一个通道。
4.2. PWM实现原理(p233)
- 缺省情况下,输出为高电平;
- TIMx_CLK经过PSC分频后,为定时器提供计数脉冲;
- 计数脉冲使得CNT的计数从0进行累加;
- 当CNT和CCR的值相等后,输出极性反转,从高电平变为低电平;
- CNT持续计数;
- 当CNT与ARR值相等的时候,极性再次反转,从低电平变成高电平;一个周期结束,CNT被重新设置成0;
因此:
- CK_PSC(TIMx_CLK)、PSC、ARR 共同决定了PWM波形输出的频率,这一点和作为定时器是一致的;
- CCR和ARR+1的比值决定了占空比:
公式8-7与公式8-4是一致的,其基本原理也是一致的。
课堂练习:
如图所示:
需要一个频率1000Hz,占空比 30 % 的PWM 应该如何进行设置?
如果选用TIM1作为PWM的时钟,PSC、ARR、CCR的值应该如何选取?
如果选用TIM4作为PWM的时钟,PSC、ARR、CCR的值应该如何选取?
如果选用TIM2作为PWM的时钟,PSC、ARR、CCR的值应该如何选取?
4.3. PWM输出功能的数据类型(p234)
typedef struct
{
uint32_t OCMode; /*!< Specifies the TIM mode.
This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */
uint32_t Pulse; /*!< Specifies the pulse value to be loaded into the Capture Compare Register.
This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
uint32_t OCPolarity; /*!< Specifies the output polarity.
This parameter can be a value of @ref TIM_Output_Compare_Polarity */
uint32_t OCNPolarity; /*!< Specifies the complementary output polarity.
This parameter can be a value of @ref TIM_Output_Compare_N_Polarity
@note This parameter is valid only for timer instances supporting break feature. */
uint32_t OCFastMode; /*!< Specifies the Fast mode state.
This parameter can be a value of @ref TIM_Output_Fast_State
@note This parameter is valid only in PWM1 and PWM2 mode. */
uint32_t OCIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.
This parameter can be a value of @ref TIM_Output_Compare_Idle_State
@note This parameter is valid only for timer instances supporting break feature. */
uint32_t OCNIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.
This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State
@note This parameter is valid only for timer instances supporting break feature. */
} TIM_OC_InitTypeDef;
4.3.1. OCMode
4.3.2. Pulse(CCR)
整形,这个值与ARR共同确定占空比。
4.3.3. OCPolarity
输出极性,如果是低电平有效,相当于占空反向;
4.3.4. OCFastMode
PWM快速模式使能
4.3.5. PWM1/PWM2
其实就是极性发转。注意,通常使用PWM1方式,如果是PWM2方式,占空比是100%-(CCR/(ARR+1)x100%)。
4.4. PWM输出功能的接口函数、
4.4.1. PWM 轮询方式启动函数(HAL_TIM_PWM_Start)
该函数用于在轮询方式下启动PWM信号的输出,具体描述如表8-18所示。
4.4.2. 捕获/比较寄存器设置函数(__HAL_TIM_SET_COMPARE)
该函数用于设置捕获/比较寄存器TIMxCCRn的内容,采用带参数的宏实现(宏函数),具体描述如表 8-19 所示。该函数可以在任何时间改变CCR的值,用于动态改变占空比。
4.5. 基础任务:输出PWM信号(注意与书上不一致)
使用定时器,产生一个频率为1000Hz,占空比为50%的方波,用于驱动蜂鸣器。
4.5.1. 实现过程
- 建立一个项目 Buzzer
- 蜂鸣器是在PB9上,设置PB9的功能是 TIM4_CH4
- 配置TIM4:
注意:这里缺省配置的APB1上的定时器时钟是8M,要实现1000Hz的频率,PSC=79;ARR=99。注意在设置的时候,ARR+1最好是100或者是1000等10的整数倍数。因为Pulse和ARR+1的比值决定了占空比。设置Pulse(CCR)为50,这样就有了1000Hz,50%的占空比。
- 在main.c 的大约92行加入代码:
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_4);
/* USER CODE END 2 */
- 编译下载后,可以听到蜂鸣器的声音。
4.5.2. 代码分析
main.c 大约147行左右是定时器的初始化:
static void MX_TIM4_Init(void) {
/* USER CODE BEGIN TIM4_Init 0 */
/* USER CODE END TIM4_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = { 0 };
TIM_MasterConfigTypeDef sMasterConfig = { 0 };
TIM_OC_InitTypeDef sConfigOC = { 0 };
/* USER CODE BEGIN TIM4_Init 1 */
/* USER CODE END TIM4_Init 1 */
htim4.Instance = TIM4;
htim4.Init.Prescaler = 79;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 99;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim4) != HAL_OK) {
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK) {
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim4) != HAL_OK) {
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig)
!= HAL_OK) {
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 50;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_4)
!= HAL_OK) {
Error_Handler();
}
/* USER CODE BEGIN TIM4_Init 2 */
/* USER CODE END TIM4_Init 2 */
HAL_TIM_MspPostInit(&htim4);
}
main.c 的92行,用户代码:
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_4);
/* USER CODE END 2 */
上述代码是以查询方式启动TIM4的PWM(因为没有使用中断)。htim4是TIM4的结构体,该结构书上没有说明,和定时中断中的结构体是一致的,可以理解成C语言的对象,包括数据与很多函数指针。
4.6. 进阶任务:实现呼吸灯
- 查看绿色LED所属的定时器通道是:TIM3的通道2;
- 设置TIM3
- 加入代码:
/* USER CODE BEGIN 2 */
int duty = 0;
int step = 1;
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
/* USER CODE END 2 */
和:
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
for (duty = 0; duty < 100; duty = duty + step) {
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, duty);
HAL_Delay(50);
}
}
/* USER CODE END 3 */
问题:这里的呼吸灯不是真正的呼吸,而是逐渐变到最亮后突然变暗;请思考如何改进?