前导:本文的目的与,意在于面向应用的学习单片机,故不会涉及太多的原理知识,例如寄存器之类的。
主要目的在于面向应用的学习单片机,学会单片机的基础用法,开发板采取野火的指南者f103。
作者大二小白,写的不好的地方轻点喷,欢迎评论区交流
全部工程代码开源在Gitee仓库
1 通用定时器简介
通用定时器框图
1.1简介
通用定时器包括TIM2、TIM3、TIM4和TIM5。
不同于基本定时器TIM6、7
通用定时器具有向上向下的计数功能,并且有4路的IO口引出,后文会说成四个通道,可以进行输入捕获和输出比较,PWM等等的,PWM其实就是输出比较的一个特例。也有PWM输入模式,也是输入捕获的一个特例
1.2 输出比较
输出比较:当计数器CNT的值和比较器CCR(Capture Compare Register)的值相等的时候,输出信号的极性就会改变。例如翻转极性,拉高极性,或者拉低极性。直到CNT的值等于ARR的值时,再次发生跳变。此时一个周期完整结束
例如 psc配置71,period(ARR)配置999。也就是我们之前的1ms计一次。那么CCR的值配置为200。
假设最开始是高电平,每次CNT+1,加到200的时候,电平翻转,再走完剩下的800,电平再次翻转。实现百分之20占空比的方波
PWM模式原理。
?
1.3 输入捕获
输入捕获说的通俗一点就是用定时器来记录某一段脉冲的时间,或者者我们只捕获脉冲的上升沿或者下降沿。
基本工作过程就是捕捉上升沿或者下降沿,然后触发中断。
之前的两次按键间隔计时,在那边我们用的是计时计算,也就是1ms进一次中断,通过使能和失能定时器
如果是要计算一次按键按下的时间的话,我们就可以利用输入捕获,按键按下,产生上升沿,维持一定宽度的矩形脉冲,按键松手的时候,产生下降沿,结束这个矩形脉冲。然后我们就可以通过捕获的数据,计算按键按下的时间
2 实验
2.1 输出PWM
2.1.1提要
采用比较输出模式生成pwm的实现方法较为复杂,博主还没研究好,这里先不提及。
我们先用PWM模式生成pwm,较为直观简单
两种方式的差别是 PWM它只能生成四路频率相同的PWM,当你设定了PSC,ARR(自动重装载寄存器),这时PWM的频率就被定下来了。你可以通过改变各个通道的CCR寄存器来改变占空比。
但是想生成多路不同频率的PWM的话,使用PWM模式这个方法只能使用多个定时器了,但输出比较的模式,可以生成多路不同频率及占空比的PWM。
2.1.2 思路
- 开启IO和TIM的时钟,使用复用功能
- 配置定时器的基本功能
- 配置定时器的输出功能,设置占空比
- 使能
2.1.3 代码编写
基本配置部分,我们在前面的定时器里已经说过了,这里不多赘述。
查阅数据手册,使用PB0,PB1输出方波,可以发现PB0和PB1分别挂在了TIM3通道3,和TIM3通道4上
开启复用
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
定时器基本配置
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
输出功能配置
这里我们去头文件里找,发现有这个结构体,我们需要1,2,4,5个成员,分别配置,输出模式,输出使能,CCR寄存器,输出有效极性。
找参数的方法之前也说过了,就不提了,关于参数里的TIM_OCMode_PWM1和TIM_OCMode_PWM2的差别。
有以下说法,理解为两个模式产生的效果是相反的
代码配置如下
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
然后我们需要把这个输出结构体,初始化到指定的通道中(这些函数都可以在手册或者stm32f10x_tim.h里找)
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_Cmd(TIM3, ENABLE);
关于倒数二三行可以参考TIM_OC3PreloadConfig的作用
完整配置代码如下
void TIM3_Init(int psc,int arr)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_Cmd(TIM3, ENABLE);
}
主函数中
int main (void)
{
KEY_Init();
LED_Init();
TIM3_Init(72-1,1000-1);
SysTick_Config(SystemCoreClock / 1000);
ILI9341_Init();
LCD_SetColors(BLACK,WHITE);
LCD_SetFont(&Font8x16);
ILI9341_Clear(0,0,LCD_X_LENGTH,LCD_Y_LENGTH);
ILI9341_GramScan ( 6 );
LED_Color(LED_OFF);
while (1)
{
}
}
到这里,我们就实现了PB0和PB1,百分之50的PWM占空比输出
2.1.4 改变占空比
可是如果要改变通道的占空比呢?
之前说过,PWM模式本质上是输出比较模式的一个特例,通过比较CNT和CCR的值来改变的电平
而有这样一套函数就是专门用来设置CCR的值的
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
我们这里用的是通道3,4,arr设置的是1000,假设要改成1个通道输出20占空比的方波,一个输出60,那就这样改
TIM_SetCompare3(TIM3,200);
TIM_SetCompare4(TIM3,600);
主函数如下
int main (void)
{
TIM_SetCompare3(TIM3,200);
TIM_SetCompare4(TIM3,600);
while (1)
{
}
}
2.2 PWM控制颜色的亮度
首先由于小灯泡是置低电平触发,所以我们先把初始化配置里的输出极性改为Low
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
主函数设置延时就可以了
while (1)
{
for(i=0;i<100;i+=5)
{
TIM_SetCompare3(TIM3,i);
Delay(0xFFFFF);
}
for(i=100;i>0;i-=5)
{
TIM_SetCompare3(TIM3,i);
Delay(0xFFFFF);
}
}
2.3 捕获PWM周期和占空比
2.3.1 PWM输入模式简介
PWM输入模式,这个模式是STM32输入捕获检测脉宽和频率的一种硬件处理机制,说白了就是STM32芯片专门用来进行对PWM进行捕获的一个功能;此方法相比较于传统的PWM的捕获方法,大大减小了代码量,提高了检测效率。
PWM输入模式中,需要两个通道的协同工作,官方给出的是CH1和CH2为一组。
为什么需要两个通道的协同工作来计算占空比的周期?
假设PWM波从0电平开始跳变 我们假设从机是通道2,主机通道1
- 第一个上升沿到来时,1,2通道同时检测到上升沿,通道1设置为复位模式。然后将TIM的CNT计数值复位到0,所以不会产生中断。
- 下一个到来的是下降沿,此时通道2发生捕获事件产生中断,将计数值存入自己的CCR2中
- 第二个上升沿来时,通道1发生捕获事件产生中断,将计数值存入自己的CCR1中,然后再次复位清零计数值
- 这样,一次捕获完成了。
- 占空比就是
CCR1/CCR2 * 100% - 频率就是
SysytemClockCore/输入的psc/CCR2
2.3.2 使用方法
只给其中一个通道分配gpio时钟即可,另一个在内部使用,我们无需顾虑。给一个通道分配gpio时钟后,设置另一个为从机且复位模式。
代码思路
- 配置中断
- 配置时基结构体TIM_TimeBaseInitTypeDef
- 配置输入捕获结构体
- 设置捕获通道,触发方式
- 设置捕获管角映射
- 初始化到PWM输入模式中
- 设置主从模式触发源
- 设置从机复位
- 使能主从模式并初始化定时器
2.2.3 代码
void Tim4_Capture_Init()
{
NVIC_InitTypeDef NVIC_InitStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_TimeBaseStructure.TIM_Period= 0xFFFF;
TIM_TimeBaseStructure.TIM_Prescaler= 72-1;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0;
TIM_PWMIConfig(TIM4, &TIM_ICInitStructure);
TIM_SelectInputTrigger(TIM4, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_Reset);
TIM_SelectMasterSlaveMode(TIM4,TIM_MasterSlaveMode_Enable);
TIM_ITConfig(TIM4, TIM_IT_CC1, ENABLE);
TIM_ClearITPendingBit(TIM4, TIM_IT_CC1);
TIM_Cmd(TIM4, ENABLE);
}
中断代码
void TIM4_IRQHandler(void)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_CC1);
IC1Value = TIM_GetCapture1(TIM4);
IC2Value = TIM_GetCapture2(TIM4);
if (IC1Value != 0)
{
DutyCycle = (float)((IC2Value+1) * 100) / (IC1Value+1);
Frequency = (SystemCoreClock/72-1)/(float)(IC1Value+1);
}
else
{
DutyCycle = 0;
Frequency = 0;
}
}
|