目录
本次笔记借鉴于B站江科大STM32视频和B站keysking的视频。
TIM定时器简介
TIM(Timer)定时器
1.定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
2.16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
3.不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
4.根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
1.定时器定时中断
1.1定时中断基本结构
1.2预分频器
将输入的时钟源进行分频,内部时钟源时钟频率为72MHz。假如不对其进行分频,计数器为16位,也就是最多计数65536。72MHz的意思是一秒计数72000000个数。由此可见,如果不加上预分频器定时时间最多为65536/72000000次/秒=0.0009秒。为了延长定时时间所以我们加上了预分频器。
下面将举几个对时钟频率进行分频的例子:如果不分频进入分频器的脉冲信号会被原封不动的输出。如果预分频器设置1就是进行2分频,预分频器会计数0,1。将每两个脉冲合成为一个脉冲然后输出新的脉冲信号。
预分频器的时序
注意上图中的预分频缓冲器,当你设置预分频控制器中的分频值时。不会立马按照修改好的分频值进行分频。而是要等更新事件或中断出现时按照新的分频值进行分频。(后面写定时器中断代码时要在设置好时基单元后进行中断标志位清零,否则定时器只会从1开始加,这是因为设置好预分频器的值后,为了使其立马生效,系统会生成一个更新事件或中断,从而导致立马进入中断进行加1操作)。
1.3写定时器中断代码的思路为:
1.先给TIM2时钟
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState)
2.选择内部时钟源
void TIM_InternalClockConfig(TIM_TypeDef* TIMx)
3.设置时基单元
TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)
4.进行中断初始化(中断输出控制)
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE)
5.进行中断优先级分组
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
7.开启定时器
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
1.4代码展示(实现定时一秒,进入中断并对Num++):
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint8_t KeyNum;
int16_t Num;
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1, 3, "Num:");
while(1)
{
OLED_ShowNum(1,7,Num,5);
}
}
void TIM2_IRQHandler(void)
{
if( TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
Num++;
}
}
Timer.c
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=10000-1;
TIM_TimeBaseInitStruct.TIM_Prescaler=7200-1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2, & TIM_TimeBaseInitStruct);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;
NVIC_Init(&NVIC_InitStruct);
TIM_Cmd(TIM2, ENABLE);
}
/*void TIM2_IRQHandler(void)
{
if( TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
Num+;
}
}
*/
Timer.h
#ifndef _Timer_H_
#define _Timer_H_
void Timer_Init(void);
#endif
另外说明一下在哪找中断函数:Start->以IRQHandler 结尾的函数就是中断函数。
NVIC的函数在:Library->misc.h文件中。
注意中断函数中不要执行长时间的操作,尤其是Delay。
2.定时器中断外部时钟
2.1时钟源选择
stm32定时器不仅可以用内部时钟作为时钟源,还可以使用GPIO口上的信号作为外部时钟源
在stm32f103上每个定时器都有四个输入通道,为TI1,TI2,TI3,TI4。由于TI3和T14没有接入触发控制器,在此就不再展示。
2.2边沿检测器
根据设置触发设置,对进入的信号进行处理。比如上升沿触发,就是当来一个上升沿输出脉冲。
2.3边沿检测器输出的脉冲
对通道来说,输入的信号经过边沿检测器输出的脉冲信号有两个。其中TI1FP2和TI2FP2现在用不到先不用管。
TI1输出的脉冲还有一个TI1_ED,与其他两个脉冲的区别是只能选择双边沿触发
下面是外部时钟模式1和外部时钟模式2:外部时钟模式1和外部时钟模式2的区别在于,外部时钟模式1使用外部输入脚(TIx)作为计数器时钟的时钟源,而外部时钟模式2使用外部触发输入(ETR)或内部触发输入(ITRx)作为计数器时钟的时钟源1。
这次的代码就是用的ETR外部触发器,也就是外部时钟模式2。
2.4编程思路
我们本次使用的的是对射式红外传感器来模拟外部时钟。使用TIM2的外部触发器ETR的引脚。首先要找到ETR外部触发器复用在GPIO哪个引脚上。(由于STM32f103C8T6引脚数目有限TIM3与TIM4并没有引出外部触发器ETR的引脚)下面是引脚定义图
由图可知TIM2的外部触发器引脚复用在PA0端口。下面是接线图:
1.先开启TIM2和GPIO的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
2.配置GPIO
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0 ;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
3.对TIM2的外部触发器ETR进行初始化
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0F);
4.配置时基单元
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
5.进行中断输出控制
TIM_ITConfig(TIM2, TIM_IT_Update,ENABLE);
6.进行中断分组
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 )
NVIC_Init(&NVIC_InitStruct);
7.启动定时器
TIM_Cmd(TIM2,ENABLE)
2.5代码展示(实现计数器CNT加到10,Num++)
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1,1, "Num:");
OLED_ShowString(2,1, "CNT:");
while(1)
{
OLED_ShowNum(1,5,Num,5);
OLED_ShowNum(2,5,TIM_GetCounter(TIM2),5);
}
}
void TIM2_IRQHandler(void)
{
if( TIM_GetITStatus(TIM2, TIM_IT_Update)==SET)
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
Num++;
}
}
Timer.c
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
//开TIM2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0 ;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//内部时钟源 72MHz
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0F);
//设置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
//选择计数方式
TIM_TimeBaseInitStruct.TIM_CounterMode= TIM_CounterMode_Up;
//计数器
TIM_TimeBaseInitStruct.TIM_Period=10-1;
//分频
TIM_TimeBaseInitStruct.TIM_Prescaler=1-1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
//这样就会从0开始计数
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
//中断输出控制
TIM_ITConfig(TIM2, TIM_IT_Update,ENABLE);
//NVIC优先分组
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );
NVIC_InitTypeDef NVIC_InitStruct;
//定时器2在NVIC里的通道
NVIC_InitStruct.NVIC_IRQChannel= TIM2_IRQn ;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE ;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;
NVIC_Init(&NVIC_InitStruct);
//启动计数器
TIM_Cmd(TIM2,ENABLE);
}
uint16_t Get_TimerCount(void)
{
return TIM_GetCounter(TIM2);
}
/*void TIM2_IRQHandler(void)
{
if( TIM_GetITStatus(TIM2, TIM_IT_Update)==SET)
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
Num++;
}
}*/
Timer.h
#ifndef _TIMER_H_
#define _TIMER_H_
void Timer_Init(void);
uint16_t Get_TimerCount(void);
#endif
3.定时器的输出比较模式
3.1输出比较模式简介
OC(Output Compare)输出比较
输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
每个高级定时器和通用定时器都拥有4个输出比较通道
高级定时器的前3个通道额外拥有死区生成和互补输出的功能
3.2PWM信号
下图所示就是PWM信号,一组高低电平所占用的时间就是周期。
T=1ms,也就是说1ms内有1000组这样的高低电平,我们将这个数字称作PWM信号的频率1000Hz。频率就是1s除以一个周期的时间,f=1/T。
PWM就是用数字信号模拟模拟信号的技术
3.2输出比较通道
3.4PWM驱动呼吸灯
我们要用定时器的输出比较模式的PWM模式。通过调整比较寄存器的值,从而控制PWM占空比,将数字信号模拟成模拟信号然后通过GPIO口输出,在此我们使用的是TIM2定时器的CH1(OC1)通道,通过引脚定义图可知输出端口是PA0。将下图电路打通就可以实现本次代码的功能。
接线图
3.4.1编程思路
1.打开GPIO和TIM2的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
2.将PA0口设置为复用推挽输出。这样输出控制权将转移给片上外设,这里的片上外设引脚连接的就是TIM2的CH1通道, 这样PWM波形才能通过引脚输出。
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
3.选择内部时钟源
TIM_InternalClockConfig(TIM2);
4.配置时基单元,设置重装值和预分频值
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//滤波器采样频率
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1 ;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=100-1; //ARR
TIM_TimeBaseInitStruct.TIM_Prescaler=720-1; //PSC
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
5.设置输出比较端口,可以先用TIM_OCStructInit(&TIM_OCInitStruct),初始化一下
//选择输出比较端口1
TIM_OCInitTypeDef TIM_OCInitStruct;
//初始化一下
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1 ;
//不翻转
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High ;
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable ;
TIM_OCInitStruct. TIM_Pulse=0; //CCR
TIM_OC1Init(TIM2, &TIM_OCInitStruct);
6.打开定时器
TIM_Cmd(TIM2,ENABLE);
7.设置一个函数不断更新比较寄存器的值
void PWM_SetCompare(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}
3.4.2代码展示
mian.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "PWM.h"
uint8_t i;
int main(void)
{
PWM_Init();
while(1)
{
for(i=0;i<=100;i++)
{
PWM_SetCompare(i);
Delay_ms(10);
}
for(i=0;i<=100;i++)
{
PWM_SetCompare(100-i);
Delay_ms(10);
}
}
}
PWM.c
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*
复用推挽输出,这样输出控制权将转移给片上外设,这里的片上外设引脚连接的就是TIM2的CH1通道。
这样PWM波形才能通过引脚输出
*/
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//滤波器采样频率
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1 ;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=100-1; //ARR
TIM_TimeBaseInitStruct.TIM_Prescaler=720-1; //PSC
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
//选择输出比较端口1
TIM_OCInitTypeDef TIM_OCInitStruct;
//初始化一下
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1 ;
//不翻转
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High ;
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable ;
TIM_OCInitStruct. TIM_Pulse=0; //CCR
TIM_OC1Init(TIM2, &TIM_OCInitStruct);
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}
PWM.h
#ifndef _PWM_H_
#define _PWM_H_
void PWM_Init(void);
void PWM_SetCompare(uint16_t Compare);
#endif
Delay.c
#include "stm32f10x.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
Delay.h
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif
3.5PWM驱动舵机
由上图分析周期为20ms,故频率为50hz,也就是一秒钟计数50个相同的周期。我们通过配置内部时钟频率(72Mhz),预分频值,重装值来匹配一秒钟计数50个相同的周期。
故预分频值:72,重装值:20000。72000000/72=1000000,即1秒钟计数1000000个相同的周期。计数20000个相同的周期,需要20ms。(以上值并不唯一)
比较计数器中的值涉及占空比,高电平宽度为0.5ms~2.5ms。
重装寄存器 20000 20ms 20000 20ms
比较寄存器 500 0.5ms 2500 2.5ms
故比较寄存器的值要在500~2500之间。
3.5.1编程思路
此代码实现的功能是按键每按下一次,舵机角度加30,加到180度清零并在led屏幕上展示出来。
使用TIM2计数器的CH2输出通道来输出PWM波形。查找引脚定义图此通道对应GPIO口的PA1引脚。我们会新建一个Servo.c文件,里面由初始化函数和设置舵机角度的函数组成。我们需要把比较寄存器中的值与度数对应起来:
0 500
180 2500
故对应公式为:Angle/180*2000+500
1.开启TIM2的时钟和TIM2的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
2..将PA0口设置为复用推挽输出。这样输出控制权将转移给片上外设,这里的片上外设引脚连接的就是TIM2的CH2通道, 这样PWM波形才能通过引脚输出。
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
3.选择内部时钟源
TIM_InternalClockConfig(TIM2);
4.配置时基单元(重装值和分频值上面已给出)
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//滤波器采样频率
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1 ;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=20000-1; //ARR
TIM_TimeBaseInitStruct.TIM_Prescaler=72-1; //PSC
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
5.设置输出比较端口,可以先用TIM_OCStructInit(&TIM_OCInitStruct),初始化一下
//选择输出比较端口2
TIM_OCInitTypeDef TIM_OCInitStruct;
//初始化一下
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1 ;
//不翻转
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High ;
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable ;
TIM_OCInitStruct. TIM_Pulse=0; //CCR
TIM_OC2Init(TIM2, &TIM_OCInitStruct);
6.启动定时器
TIM_Cmd(TIM2,ENABLE);
7.设置比较寄存器2的值
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2,Compare);
}
8.创建Servo.c,设置舵机旋转角度
void Servo_Init(void)
{
PWM_Init();
}
/*
0度 500
180度 2500
*/
void Servo_SetAngle(float Angle)
{
PWM_SetCompare2(Angle/180*2000+500);
}
3.5.2代码展示
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "PWM.h"
#include "Servo.h"
uint8_t KeyNum;
float angle;
int main(void)
{
OLED_Init();
Servo_Init();
Key_Init();
OLED_ShowString(1,1,"Angle:");
while(1)
{
KeyNum=Key_GetNum();
if(KeyNum==1)
{
angle+=30;
if(angle>180)
{
angle=0;
}
}
Servo_SetAngle(angle);
OLED_ShowNum(1,7,angle,3);
}
}
PWM.c
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*
复用推挽输出,这样输出控制权将转移给片上外设,这里的片上外设引脚连接的就是TIM2的CH2通道。
这样PWM波形才能通过引脚输出
*/
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//滤波器采样频率
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1 ;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=20000-1; //ARR
TIM_TimeBaseInitStruct.TIM_Prescaler=72-1; //PSC
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
//选择输出比较端口2
TIM_OCInitTypeDef TIM_OCInitStruct;
//初始化一下
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1 ;
//不翻转
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High ;
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable ;
TIM_OCInitStruct. TIM_Pulse=0; //CCR
TIM_OC2Init(TIM2, &TIM_OCInitStruct);
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2,Compare);
}
PWM.h
#ifndef _PWM_H_
#define _PWM_H_
void PWM_Init(void);
void PWM_SetCompare2(uint16_t Compare);
#endif
Servo.c
#include "stm32f10x.h" // Device header
#include "PWM.h"
void Servo_Init(void)
{
PWM_Init();
}
/*
0度 500
180度 2500
*/
void Servo_SetAngle(float Angle)
{
PWM_SetCompare2(Angle/180*2000+500);
}
Servo.h
#ifndef _SERVO_H_
#define _SERVO_H_
void Servo_Init(void);
void Servo_SetAngle(float Angle);
#endif
Key.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode= GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_11;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,& GPIO_InitStruct);
}
uint8_t Key_GetNum(void)
{
uint8_t KeyNum=0;
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
{
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
Delay_ms(20);
KeyNum=1;
}
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0)
{
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11)==0)
Delay_ms(20);
KeyNum=2;
}
return KeyNum;
}
Key.h
#ifndef _KEY_H_
#define _KEY_H_
void Key_Init(void);
uint8_t Key_GetNum(void);
#endif
3.6PWM驱动直流电机
3.6.1直流电机及驱动简介
3.6.2TB6612驱动电路
反转逻辑IN1:L,IN2:H。通过不断不断翻转的PWM信号,来实现翻转制动的操作,可以通过设置PWM的占空比,从而控制电机旋转的速度,正转逻辑一样。
3.6.3接线图
3.6.4编程思路
这次编程实现的功能是通过按键控制直流电机的速度,并在led屏幕上设置直流电机的旋转速度。
此次代码使用的是TIM2的CH3通道,此通道复用在GPIOA的PA2口。通过PA2口将PWM信号输入驱动芯片,通过控制AIN1和AIN2的电平控制电机旋转方向。
PWM模块代码基本和以上舵机代码相同,只需改下GPIOA的输出端口改为PA2和TIM2的输出通道,再将设置比较寄存器的函数名更改一下,这里就不再展示PWM模块的代码。
由于人耳听到的频率20Hz~20KHz,所以为避免出现蜂鸣。所以将PWM频率调到20Hz~20KHz,预分频值设为36。
我们再新建一个Motor.c的文件里面对PWM和引脚进行初始化,设置电机旋转的速度,AIN1和AIN2分别接在PA4和PA5。
3.6.5代码展示
Motor.c
#include "stm32f10x.h" // Device header
#include "PWM.h"
void Motor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
PWM_Init();
}
void Moto_SetSpeed(int8_t Speed)
{
if(Speed>=0)
{
//设置速度
GPIO_SetBits(GPIOA,GPIO_Pin_4);
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
PWM_SetCompare3(Speed);
}
else
{
GPIO_SetBits(GPIOA,GPIO_Pin_5);
GPIO_ResetBits(GPIOA,GPIO_Pin_4);
PWM_SetCompare3(-Speed); //比较寄存器不能设置负值
}
}
Motor.h
#ifndef _MOTOR_H_
#define _MOTOR_H_
void Motor_Init(void);
void Moto_SetSpeed(int8_t Speed);
#endif
4.定时器的输入捕获模式
4.1输入捕获模式简介
下图是对输入捕获模式的简要概括
一个输入通道的输入捕获只能进行上升沿捕获或下降沿捕获,为同时捕获上升沿和下降沿故进行了如下设计
4.2测量PWM的频率
4.3定时器的从模式
接入从模式的通道如下:当从模式控制器处于复位模式就不能再通过外部时钟模式1提供外部时钟源,需要选择新的时钟源,可以是内部时钟和通过ETR输入的时钟源。
借助下图我阐述一下从模式控制器的复位模式:通过内部时钟提供时钟源,从模式控制器设置为复位模式,边沿检测器设置为上升沿触发。当检测到由TI1输入的脉冲为上升沿时会发出TI1FP1脉冲信号,经过触发器进入从模式控制器中触发更新事件将计数器清零。
4.4输入捕获基本结构
4.4.1本次代码的接线图
4.4.2编程思路和部分代码
通过TIM2的CH1通道输出PWM波形,用TIM3的输入捕获通道1(复用在PA6上)对PWM对频率进行测量,我们测量的方法是测周法。TIM3的频率设置为1000000Hz,重装载值设置为65536。
在输入捕获模式下,比较寄存器化身捕获寄存器,将计数器中的值捕获到捕获寄存器中。
本次使用从模式控制器的复位模式。
总的来说就是配置TIM2输出PWM波形,将此波形输入TIM3的输入捕获通道,当检测到TI1FP1上升沿时,触发从模式控制器的复位模式将,将计数器清零。当下一个上升沿来临时将计数器的值送到捕获寄存器并将计数器中的值清零(硬件直接完成),设置的频率值比上捕获的值就是PWM的频率。
红色框是输出通道,黑色框是输入通道。
1.首先设置TIM2使其输出PWM波形(PWM.c)
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*
复用推挽输出,这样输出控制权将转移给片上外设,这里的片上外设引脚连接的就是TIM2的CH1通道。
这样PWM波形才能通过引脚输出
*/
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//滤波器采样频率
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1 ;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=100-1; //ARR
TIM_TimeBaseInitStruct.TIM_Prescaler=720-1; //PSC
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
//选择输出比较端口1
TIM_OCInitTypeDef TIM_OCInitStruct;
//初始化一下
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1 ;
//不翻转
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High ;
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable ;
TIM_OCInitStruct. TIM_Pulse=0; //CCR
TIM_OC1Init(TIM2, &TIM_OCInitStruct);
TIM_Cmd(TIM2,ENABLE);
}
2.然后设置两个函数用来设置预分频值和占空比(PWM.c)
void PWM_SetCompare(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}
void PWM_SetPrescaler(uint16_t Prescaler)
{
TIM_PrescalerConfig(TIM2,Prescaler, TIM_PSCReloadMode_Immediate);
}
3.配置TIM3的输入捕获通道
void IC_Init(void)
{
RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6 ;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
TIM_InternalClockConfig(TIM3);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision= TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up ;
TIM_TimeBaseInitStruct.TIM_Period=65536-1;
TIM_TimeBaseInitStruct.TIM_Prescaler=72-1;// 计数标准频率
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
TIM_ICInitTypeDef TIM_ICInitStruct;
TIM_ICInitStruct.TIM_Channel=TIM_Channel_1 ;//选择通道
TIM_ICInitStruct.TIM_ICFilter=0xF;
TIM_ICInitStruct.TIM_ICPolarity=TIM_ICPolarity_Rising ;
TIM_ICInitStruct.TIM_ICPrescaler=TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICSelection=TIM_ICSelection_DirectTI;
TIM_ICInit(TIM3,&TIM_ICInitStruct);
TIM_SelectInputTrigger( TIM3,TIM_TS_TI1FP1);//选择从模式触发源
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
TIM_Cmd(TIM3,ENABLE);
}
4.返回测得的PWM频率
uint32_t IC_GetFreq(void)
{
return 1000000/(TIM_GetCapture1(TIM3)+1);
}
4.5PWMI模式测频率和占空比
4.5.1编程思路和部分代码
将GPIO输入的PWM波形,经过滤波器和边沿检测器(选择上升沿触发或者下降沿触发),输出TI1FP1和TI1FP2脉冲分别输入CCR1和CCR2。当TI1FP1触发从模式的复位模式会将CNT中的值清零,而TI1FP2不会,当下降沿来临时其中的值就是高电平的持续时间。
配置CH1的代码:
TIM_ICInitTypeDef TIM_ICInitStruct;
TIM_ICInitStruct.TIM_Channel=TIM_Channel_1 ;//选择通道
TIM_ICInitStruct.TIM_ICFilter=0xF;
TIM_ICInitStruct.TIM_ICPolarity=TIM_ICPolarity_Rising ;
TIM_ICInitStruct.TIM_ICPrescaler=TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICSelection=TIM_ICSelection_DirectTI;
TIM_ICInit(TIM3,&TIM_ICInitStruct);
配置CH2的代码:
可以直接复制但要将上升沿触发改为下降沿触发,直连通改为交叉连通。也可以直接使用库函数,下面是使用库函数的代码。
TIM_PWMIConfig(TIM3, &TIM_ICInitStruct);
这样CCR1里面的数值就是一个周期的计数值,CCR2里面的数值就是高电平的计数值,二者相除就是占空比。
uint32_t IC_GetFreq(void)
{
return 1000000/(TIM_GetCapture1(TIM3)+1);
}
uint32_t IC_GetDuty(void)
{
return TIM_GetCapture2(TIM3)*100/TIM_GetCapture1(TIM3);
}
4.6编码器接口测速
4.6.1编码器简介
Encoder Interface 编码器接口 编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度 每个高级定时器和通用定时器都拥有1个编码器接口 两个输入引脚借用了输入捕获的通道1和通道2
4.6.2编码器接口的基本结构
边沿检测极性选择是控制A,B相信号是否取反的。
编码器通过GPIO接入定时器,之前使用内部时钟源72MHz和时基单元初始化的计数方向在此不会使用,由编码器接口直接托管。
4.6.3编程思路和部分代码
代码要实现编码器旋转测速,将编码器A相和B相接入TIM3的CH1通道和CH2通道。旋转编码器会使计数器自动加1,然后将此时的数值读出并且清零。然后用定时器2设计一个一秒进中断的函数,在中断中不断将Speed的值读出,然后在屏幕上显示。
Encode.c
#include "stm32f10x.h" // Device header
void Encoder_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=65536-1;
TIM_TimeBaseInitStruct.TIM_Prescaler=0;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
TIM_ICInitTypeDef TIM_ICInitStruct;
TIM_ICStructInit(&TIM_ICInitStruct);
TIM_ICInitStruct.TIM_Channel=TIM_Channel_1;
TIM_ICInitStruct.TIM_ICFilter=0xF;
TIM_ICInit(TIM3, &TIM_ICInitStruct);
TIM_ICInitStruct.TIM_Channel=TIM_Channel_2;
TIM_ICInitStruct.TIM_ICFilter=0xF;
TIM_ICInit(TIM3, &TIM_ICInitStruct);
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
TIM_Cmd(TIM3,ENABLE);
}
int16_t Encoder_Get(void)
{
int16_t Temp;
Temp=TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3,0);
return Temp;
}
Timer.c
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
//开TIM2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//内部时钟源 72MHz
TIM_InternalClockConfig(TIM2);
//设置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
//选择计数方式
TIM_TimeBaseInitStruct.TIM_CounterMode= TIM_CounterMode_Up;
//计数器
TIM_TimeBaseInitStruct.TIM_Period=10000-1;
//分频
TIM_TimeBaseInitStruct.TIM_Prescaler=7200-1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
//这样就会从0开始计数
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
//中断输出控制
TIM_ITConfig(TIM2, TIM_IT_Update,ENABLE);
//NVIC优先分组
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );
NVIC_InitTypeDef NVIC_InitStruct;
//定时器2在NVIC里的通道
NVIC_InitStruct.NVIC_IRQChannel= TIM2_IRQn ;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE ;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=2;
NVIC_Init(&NVIC_InitStruct);
//启动计数器
TIM_Cmd(TIM2,ENABLE);
}
main.c
#include "stm32f10x.h" // Device header
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "Timer.h"
#include "Encoder.h"
int16_t Speed;
int main(void)
{
OLED_Init();
Encoder_Init();
Timer_Init();
OLED_ShowString(1, 1, "Speed:");
while(1)
{
OLED_ShowSignedNum(1,7,Speed,5);
}
}
void TIM2_IRQHandler(void)
{
if( TIM_GetITStatus(TIM2, TIM_IT_Update)==SET)
{
Speed=Encoder_Get();
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
旋转编码器旋转一周 20个脉冲由于双计特性一周计数40次。由于舵机最多旋转180度,为了让舵机和旋转编码器角度匹配。所以旋转编码器只需要旋转半周也就是10个脉冲,计数20次。