目录
一、定时器简介
1. 功能简介
STM32 的定时器功能十分强 大,有 TIME1 和 TIME8 等高级定时器,也有 TIME2~TIME5 等通用定时器,还有 TIME6 和 TIME7 等基本定时器。
三种定时器功能如下:
STM32 的通用定时器是一个通过可编程预分频器(PSC)驱动的 16 位自动装载计数器(CNT) 构成。STM32 的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波 形(输出比较和 PWM)等。 使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形 周期可以在几个微秒到几个毫秒间调整。STM32 的每个通用定时器都是完全独立的,没有互相 共享的任何资源。 STM3 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定时器功能包括:
1. 16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。 |
2. 16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~ 65535 之间的任意数值。
|
3. 4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:
A.输入捕获
B.输出比较
C.PWM 生成(边缘或中间对齐模式)
D.单脉冲模式输出
|
4. 可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外 一个定时器)的同步电路。
|
5. 如下事件发生时产生中断/DMA:
A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
C.输入捕获
D.输出比较
E.支持针对定位的增量(正交)编码器和霍尔传感器电路
F.触发输入作为外部时钟或者按周期的电流管理
|
![a7aaa0a49c8a4a95bf777f7fb90aa9db.png](https://i-blog.csdnimg.cn/blog_migrate/197087a3b36dc5575580343ce77c55f9.png)
2. 计数器模式
- 向上计数模式
- 向下计数模式
- 中央对齐计数模式
在中央对齐模式,计数器从0开始计数到自动加载的值(TIMx_ARR寄存器)−1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器下溢事件;然后再从0开始重新计数。
3. 时基单元
计数器寄存器(TIMx_CNT) | 向上计数、向下计数或者中心对齐计数 |
预分频器寄存器 (TIMx_PSC) | 可将时钟频率按1到65535之间的任意值进行分频,可在运行时改变其设置值; |
自动装载寄存器 (TIMx_ARR) | 如果TIMx_CR1寄存器中的ARPE位为0,ARR寄存器的内容将直接写入影子寄存器;如果ARPE为1,ARR寄存器的那日同将在每次的更新时间UEV发生时,传送到影子寄存器; 如果TIM1_CR1中的UDIS位为0,当计数器产生溢出条件时,产生更新事件。 |
定时器频率=时钟频率/(PSC+1)(ARR+1)
4. 定时器时钟源
1)内部时钟(CK_INT)
|
2)外部时钟模式 1:外部输入脚(TIx) |
3)外部时钟模式 2:外部触发输入(ETR) |
4)内部触发输入(ITRx):使用 A 定时器作为 B 定时器的预分频器(A 为 B 提供时钟)。 |
二、PWM简介
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用 微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制,PWM 原理如图 :
三、任务要求
一、使用STM32F103的 Tim2~Tim5其一定时器的某一个通道pin(与GPIOx管脚复用,见下图),连接一个LED,用定时器计数方式,控制LED以2s的频率周期性地亮-灭。
二、接上,采用定时器pwm模式,让 LED 以呼吸灯方式渐亮渐灭,周期为1~2秒,自己调整到一个满意效果。使用Keil虚拟示波器,观察 pwm输出波形。
三、再接上,采用定时器的另外一个通道,编程采集上面的pwm输出信号,获得其周期和脉宽,并重定向输出到串口显示。
四、STM32CubeMX配置
在次实验中,使用两个通用定时器,TIM2用来定时器中断,每1s就中断一次,控制PA1电平反转(LED阳极接PA1),TIM2用来PWM输出,控制PA2(LED阳极接PA2)实现呼吸灯的效果。
1. 选择stm32f03c8t6芯片
2. 配置RCC
3. 配置sys
4. 配置UART1
5. 配置DMA
6. 配置TIM2&&TIM3
7. 配置时钟
8. 配置中断
9. 工程输出配置
五、程序编写
通过上诉配置,已经在Keil中完成了我们需要的配置,下面需要完成程序。介绍一些常用的定时器函数
1. 定时器函数介绍
__HAL_RCC_TIM3_CLK_ENABLE(); //使能 TIM3 时钟
typedef struct
{
TIM_TypeDef *Instance;
TIM_Base_InitTypeDef Init;
HAL_TIM_ActiveChannel
Channel;
DMA_HandleTypeDef
*hdma[7];
HAL_LockTypeDef Lock;
__IO HAL_TIM_StateTypeDef State;
}TIM_HandleTypeDef;
typedef struct
{
uint32_t Prescaler;
//预分频系数
uint32_t CounterMode; //计数方式
uint32_t Period;
//自动装载值 ARR
uint32_t ClockDivision; //时钟分频因子
uint32_t RepetitionCounter;
} TIM_Base_InitTypeDef;
TIM_HandleTypeDef TIM3_Handler;
//定时器句柄
TIM3_Handler.Instance=TIM3; //通用定时器 3
TIM3_Handler.Init.Prescaler= 7199;
//分频系数
TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;
//向上计数器
TIM3_Handler.Init.Period=4999;
//自动装载值
TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
HAL_TIM_Base_Init(&TIM3_Handler);
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);//使能句柄指定的定时器更新中断
__HAL_TIM_DISABLE_IT (htim, TIM_IT_UPDATE);//关闭句柄指定的定时器更新中断
__HAL_TIM_ENABLE(htim);//使能句柄 htim 指定的定时器
__HAL_TIM_DISABLE(htim);//关闭句柄 htim 指定的定时器
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim);
TIM3_IRQHandler();
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
跟串口中断回调函数一样,我们只需要重写该函数即可。对于其他类型中断,HAL 库同样
提供了几个不同的回调函数,这里我们列出常用的几个回调函数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);//更新中断
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim);//输出比较
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);//输入捕获
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim);//触发中断
2. TIM2程序编写
中断函数编写:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM2)//判断是否为TIM2触发的中断
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);//反转PA1的电平
}
}
主函数添加中断使能:
HAL_TIM_Base_Start_IT(&htim2);
3. TIM3程序编写
修改占空比编写:
while (1)
{
while(duty<=10000)
{
duty++;
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,duty);
Duty=(float)duty;
Duty/=100;
printf("%s",str1);
printf("%f",Duty);
printf("%s\n",str2);
HAL_Delay(1);
}
while(duty>=0)
{
duty--;
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,duty);
Duty=(float)duty;
Duty/=100;
printf("%s",str1);
printf("%f",Duty);
printf("%s\n",str2);
HAL_Delay(1);
}
}
PWM输出使能:
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
printf重定义函数:
int fputc(int c,FILE *f)
{
uint8_t ch;
ch = c;
HAL_UART_Transmit(&huart1,&ch,1,1000);
return c;
}
主函数完整源码:
int main(void)
{
int duty=0;
float Duty;
char str1[6]="Duty: ";
char str2[]="%";
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_TIM2_Init();
MX_USART1_UART_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); //??TIM1?PWM Channel1 ??
HAL_TIM_Base_Start_IT(&htim2);
while (1)
{
while(duty<=10000)
{
duty++;
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,duty);
Duty=(float)duty;
Duty/=100;
printf("%s",str1);
printf("%f",Duty);
printf("%s\n",str2);
HAL_Delay(1);
}
while(duty>=0)
{
duty--;
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,duty);
Duty=(float)duty;
Duty/=100;
printf("%s",str1);
printf("%f",Duty);
printf("%s\n",str2);
HAL_Delay(1);
}
}
}
六、实物效果和仿真效果
1. 实物效果
从图中看出,左边那个LED灯(PA1),1s闪烁一次,右边那个LED灯(PA6)闪烁的时间是从快到慢,再到快的,没有实现呼吸灯效果,这是由于我们配置的PWM频率为1hz,其中低电平部分时间延拓过长,人眼很容易识别出来,解决此方法的办法就是可以增大PWM的频率,如修改其周期为10ms,可以看出来有呼吸灯的效果:
修改PWM频率:
TIM3初始化代码(这里把PSC从7200-1修改为720-1,ARR从10000-1修改为1000-1)
static void MX_TIM3_Init(void)
{
/* USER CODE BEGIN TIM3_Init 0 */
/* USER CODE END TIM3_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
htim3.Instance = TIM3;
htim3.Init.Prescaler = 720-1;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 1000-1;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 50;
sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM3_Init 2 */
/* USER CODE END TIM3_Init 2 */
HAL_TIM_MspPostInit(&htim3);
}
循环内修改:
while (1)
{
while(duty<=700)//大于700的效果和700一样,设置为700效果更好
{
duty++;
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,duty);
Duty=(float)duty;
Duty/=10;
printf("%s",str1);
printf("%f",Duty);
printf("%s\n",str2);
HAL_Delay(1);
}
while(duty>=0)
{
duty--;
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,duty);
Duty=(float)duty;
Duty/=10;
printf("%s",str1);
printf("%f",Duty);
printf("%s\n",str2);
HAL_Delay(1);
}
}
修改后效果:
可以看出右边有呼吸灯效果
2. 仿真效果
环境配置:
可以看出,二者周期都是1s,下面的波形占空比先从大到小,再从小到大,波形十分明显。
七、总结与参考
1. 通过次实验,理解和掌握了定时器的原理,以及如何通过HAL库使用定时器和通过定时器输出PWM。
2. 此实验中,设置的PSC=7200-1,ARR=10000-1,系统频率为72Mhz,那么可以得出PA1灯翻转周期和PWM的输出周期都为1s,从仿真图中,可以很清楚的看出都为1s。在之前的实验中,都是通过软件延时的方式进行延时,但是软件延时存在很不好的缺点,那就是不准确且浪费CPU资源,通过定时器的方式解决了这种缺点。
3. 在进行呼吸灯效果测试时,发现当PWM周期很大时,呼吸灯效果十分不明显,可以从PWM波原理上看,修改的只是高电平的占比,那么如果周期很大,即使占空比怎么修改,人眼中还是会看出来亮灭的过程,当PWM频率提高时,人眼就无法识别出低电平的间隙,从而看出来是从亮到灭的过程。
参考:
stm32F103定时器的pwm输出模式的常用运算_pwm周期计算公式_·H··的博客-CSDN博客