江协科技STM32课程笔记(三)—定时器TIM(输出比较)
一、STM32定时器概述
定时器可以分为以下三种:高级定时器、通用定时器和基本定时器;从高级到低级,可以向下兼容功能;
类型 | 编号 | 总线 | 功能 |
高级定时器 | TIM1、TIM8 | APB2 | 拥有通用定时器全部功能,额外具有重复计数器、死区生成,互补输出、刹车输入等功能 |
通用定时器 | TIM2、TIM3、TIM4、TIM5 | APB1 | 拥有基本定时器全部功能,额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能。 |
基本定时器 | TIM6、TIM7 | APB1 | 拥有定时中断、主模式触发DAC的功能 |
STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
1、基本定时器
注:下方所有定时器中,黑色框框下有阴影的寄存器都是有缓冲寄存器的,可以配置是否使用。如PSC预分频器,自动重装载寄存器
RCC_TIMxCLK是时钟来源,一般是系统的72MHz。
首先是PSC分频器对输入频率进行分频,举个例子,如果预分频器写1,那就是2分频,输出频率=输入频率/2=36MHz,最大可写65535,即分频65536。
然后是计数器,每来一个上升沿就自动加一。因为是16位的,所以可以0加到65535,然后返回0。
自动重装载寄存器,存计数器目标;当计数器到了目标值,就会产生一个更新中断或者更新事件,更新中断如果配置好了NVIC,就可以通过NVIC由cpu响应。
主从触发模式,它能让内部的硬件在不受程序的控制下实现自动运行,减轻CPU的负担。
主模式触发DAC就是如果用DAC输出一段波形,那就需要每隔一段时间来触发DAC输出下一个电压点。如果设置一个定时器不断产生中断,在中断程序中调用代码手动触发一次DAC转换,然后DAC输出,这样会使主程序处于频繁被中断的状态,会影响主程序的运行和其他中断的响应。所以定时器设置了一个主模式,可以把这个定时器的更新事件,映射到触发输出TRGO(Trigger Out)的位置,然后TRGO直接接到DAC的触发转换引脚上,然后TRGO就会直接去触发DAC了。整个过程不需要软件的参与,实现了硬件的自动化,这就是主模式的作用。
2、通用定时器
核心结构与基本定时器差不多,都由预分频器、计数器和自动重装载寄存器组成。只是计数器多了向下计数模式 和中央对齐模式。前者从重装值递减至0产生更新中断或者事件,重新回到重装值;后者从0累加至重装值,产生中断和事件,再从重装值递减至0,产生中断和事件。
上半部分为内外时钟源和主从触发模式。除了可以选内部时钟CK_INT为72MHz外,可以选外部时钟。
引脚TIMx_ETR:经过配置外部的极性选择、边缘检测和预分频器,经过滤波后的信号一路ETRF进入触发控制器作为时钟,即外部时钟模式2,可用于对外部时钟计数。
TRGI触发连接(可以用于触发从模式,这里仅做外部时钟):当用TRGI作为外部时钟时为外部时钟模式1。ETR走ETRF和TRGI对于做外部时钟来说是一样的。ITRx是来自别的定时器的TRGO,实现定时器级联。
TI1F_ED:即从输入捕获CH1引脚上升沿和下降沿。
TI1FP1和TI2FP2:CH1和CH2引脚。
编码器接口:可以读取正交编码器的输出波形。编码器接口这部分电路可以把内部的一些事件映射到这个TRGO引脚上。
最后是下半部分,右边这一块是输出比较电路,总共有4个通道,分别对应CH1到CH4的引脚,可以用于输出PWM波形,驱动电机。左边这一块是输入捕获电路,也是有4个通道,对应的也是CH1到CH4的引脚,可以用于测量输入方波的频率等。中间这个寄存器是捕获/比较寄存器,是输入捕获和输出比较电路共用的,因为输入捕获和输出比较不能同时使用,所以这里的寄存器是共用的,引脚也是共用的。
3、高级定时器
对比通用定时器改变不大,主要有几个地方:
(1)增加重复次数计数器:可以实现每隔几个计数周期再更新一个中断或者事件,相当于再次分频。
(2)DTG(Dead Time Generate)死区生成电路:用于防止互补输出的PWM驱动桥臂时,在开关切换的瞬间,由于器件的不理想,造成短暂的直通现象。死区生成电路在开关切换的瞬间,产生一定时长的死区。让桥臂的上下管全部关断,防止直通现象。
(3)CH1~CH3输出引脚变成了两个互补的电路,可以输出一对互补的PWM,可用于驱动三相无刷电机。
(4)刹车输入:如果外部引脚BKIN(Break IN)产生了刹车信号,或者内部时钟失效,产生了故障,那么控制电路就会自动切断电机的输出,防止意外的发生,这就是刹车输入的功能。
4、定时中断基本结构
二、定时器时序
1、预分频器时序
解读1:CNT_EN使能后一个时钟周期才正式开始计算。
解读2:计数器寄存器从FC后变为00,同时产生更新事件。因此重装值为FC。
解读3:在计数周期中改变分频系数,不会立刻改变,会等待到更新事件发生,下一个计数周期才会写入预分频缓冲器,进行分频。
预分频器输出频率 CK_CNT = CK_PSC/(PSC+1)
2、计数器时序
解读1:计数器到达重装值ARR,发生溢出,产生更新事件,更新中断标志位置一,申请中断,需要自己在中断中清除标志位。
计数器溢出频率 CK_CNT_OV = CK_CNT/(ARR+1)
3、计数器无预装时序(即无缓冲寄存器的情况)
解读1:自动加载寄存器改变后,计数器到了36就溢出并产生中断了。
解读2:如果计数器已经到F1了,这个时候自动加载寄存器从FF改成36,那么计数器将一直加到FFFF,然后溢出从0开始加到36才产生更新中断和事件。
4、计数器有预装时序(即有缓冲寄存器的情况)
解读1:自动加载寄存器改为36后,等到该计数周期结束了,才写入影子寄存器。
三、RCC时钟树
从SYSCLK左边为生成时钟电路,右边为时钟分配电路。
4个振荡源:分别是内部的8MHz高速RC振荡器、外部的4~16MHz高速石英晶体振荡器,也就是晶振,一般都是接8MHz、外部的32.768KHz低速晶振,这个一般是给RTC提供时钟的,最后是内部的40KHz低速RC振荡器,这个可以给看门狗提供时钟。
重要的是内部和外部两个8MHz的振荡器,用于提供系统时钟,一般用外部晶振比较稳定,但也可以使用内部的省略外部晶振电路。
官方的systemInit函数中这样配置的:首先启动内部时钟,选择8MHz为系统时钟,暂时以内部8MHz的时钟运行,然后再启动外部时钟,进入PLL锁相环进行倍频,8MHz倍频9倍,得到72MHz,等到锁相环输出稳定后,选择锁相环输出为系统时钟,这样就把系统时钟由8MHz切换为了72MHz。具体实现在system_stm32f10x.c中。
CSS(Clock Security System)是时钟安全系统,也是负责切换时钟的,它可以监测外部时钟的运行状态,一但外部时钟失效,它就会自动把外部时钟切换为内部时钟。
系统时钟72MHz进入AHB总线,AHB总线有个预分频器,在SystemInit里配置的分配系数为1,那AHB的时钟就是72MHz,然后进图APB1总线,这里配置的分配系数是2,所以APB1总线的时钟为72MHz/2=36MHz。另外单独给定时器开了一道通道,如果分频系数为1,则频率乘2,所以定时器输入时钟还是72MHz。
最后是右边引脚输出前的与门控制时钟输出。
四、定时器使用
void TIM_DeInit(TIM_TypeDef* TIMx); // 恢复缺省配置
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
//时基单元初始化,用来配置时基单元,第一个参数用来选择某个定时器,第二个是结构体,里面包含了配置时基单元的一些参数
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
void TIM_BDTRConfig(TIM_TypeDef* TIMx, TIM_BDTRInitTypeDef *TIM_BDTRInitStruct);
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);
void TIM_BDTRStructInit(TIM_BDTRInitTypeDef* TIM_BDTRInitStruct);
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState); // 用来使能计数器,对应运行控制,第一个参数选择定时器,第二个参数选择使能还是失能
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
// 用来使能中断输出信号,对应中断输出控制,第一个参数选择定时器,第二个参数选择要配置哪个中断输出,第三个参数选择
void TIM_GenerateEvent(TIM_TypeDef* TIMx, uint16_t TIM_EventSource);
void TIM_DMAConfig(TIM_TypeDef* TIMx, uint16_t TIM_DMABase, uint16_t TIM_DMABurstLength);
void TIM_DMACmd(TIM_TypeDef* TIMx, uint16_t TIM_DMASource, FunctionalState NewState);
//下面6个函数对应时基单元的时钟选择部分,可以选择RCC内部时钟、ETR外部时钟、ITRx其它定时器、TIx捕获通道等等
void TIM_InternalClockConfig(TIM_TypeDef* TIMx); //选择内部时钟,
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
//选择ITRx其它定时器的时钟,参数是ITMx,选择要配置的定时器,TIM_InputTriggerSource选择要接入哪个其它的定时器
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,uint16_t TIM_ICPolarity, uint16_t ICFilter);
/*选择TIx捕获通道的时钟,第二个TIM_TIxExternalCLKSource选择TIx具体的某个引脚,TIM_ICPolarity和ICFilter:输入的极性和滤波器
对于外部引脚的波形,一般都会由极性选择和滤波器,这样更灵活一些*/
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
/*选择ETR通过外部时钟模式1输入的时钟,TIM_ExtTRGPrescaler:外部触发预分频器,这里可以对ETR的外部时钟再提前做一个分频,
TIM_ExtTRGPolarity和ExtTRGFilter:极性选择和过滤器*/
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
/*选择ETR通过外部时钟模式2输入的时钟,TIM_ExtTRGPrescaler:外部触发预分频器,这里可以对ETR的外部时钟再提前做一个分频,
TIM_ExtTRGPolarity和ExtTRGFilter:极性选择和过滤器*/
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
/*单独用来配置ETR引脚的预分频器、极性、滤波器这些参数的*/
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
/*单独写预分频值,Prescaler就是要写入的预分频值,TIM_PSCReloadMode写入的模式(预分频器有一个缓冲器,写入的值实在更新事件发生后才有效的
所以这里有一个写入的模式,可以选择是听从安排,在更新事件生效或者是在写入后,手动产生一个更新事件,让这个值立刻生效)*/
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
/*改变计数器的计数模式,TIM_CounterMode选择新的计数器模式*/
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);
void TIM_ForcedOC1Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC2Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC3Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC4Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
/*自动重装器预装功能配置*/
void TIM_SelectCOM(TIM_TypeDef* TIMx, FunctionalState NewState);
void TIM_SelectCCDMA(TIM_TypeDef* TIMx, FunctionalState NewState);
void TIM_CCPreloadControl(TIM_TypeDef* TIMx, FunctionalState NewState);
void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC3PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC4PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC1FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC2FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC3FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC4FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_ClearOC1Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC2Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC3Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC4Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx);
void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);
void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);
void TIM_UpdateDisableConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
void TIM_UpdateRequestConfig(TIM_TypeDef* TIMx, uint16_t TIM_UpdateSource);
void TIM_SelectHallSensor(TIM_TypeDef* TIMx, FunctionalState NewState);
void TIM_SelectOnePulseMode(TIM_TypeDef* TIMx, uint16_t TIM_OPMode);
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);
void TIM_SelectMasterSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_MasterSlaveMode);
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
/*给计数器写入一个值,如果想手动给一个计数值,就可以用这个函数*/
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);
/*给自动重装器写入一个值,如果想手动给一个自动重装值,就可以用这个函数*/
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);
void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC2Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC3Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetClockDivision(TIM_TypeDef* TIMx, uint16_t TIM_CKD);
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
/*获取当前计数器的值,如果想看当前计数器计到哪里,调用这个函数,返回值就是当前计数器的值*/
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);
/*获取当前预分频器的值,如果想看预分频器值,调用这个函数*/
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
/*上面四个函数就是用来获取和清除标志位的*/
1、定时器中断
实现一秒累加一次
#include "stm32f10x.h"
#include "OLED.h"
#include "Delay.h"
uint16_t count;void Timer_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//RCC中开启时钟TIM_InternalClockConfig(TIM2);//定时器配置为内部时钟TIM_TimeBaseInitTypeDef TIM2config;TIM2config.TIM_ClockDivision = TIM_CKD_DIV1;//用于过滤信号时采样信号的频率分频。通过判断几个采样点的数值稳定来过滤。TIM2config.TIM_CounterMode = TIM_CounterMode_Up;//向上计数TIM2config.TIM_Period = 10000 - 1;//ARR(0~65535)TIM2config.TIM_Prescaler = 7200 - 1; //预分频(0~65535)TIM2config.TIM_RepetitionCounter = 0;//重复计数器的值(仅高级寄存器)这里用不着TIM_TimeBaseInit(TIM2,&TIM2config);TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//设置更新中断,到NVICTIM_ClearFlag(TIM2,TIM_FLAG_Update);//清除中断标志位,避免刚初始化就进入中断!!!!!!!!!!因为初始化需要一个更新事件更新重装值缓冲寄存器NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置NVICNVIC_InitTypeDef nvic_config;nvic_config.NVIC_IRQChannel = TIM2_IRQn;nvic_config.NVIC_IRQChannelCmd = ENABLE;nvic_config.NVIC_IRQChannelPreemptionPriority = 2;nvic_config.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&nvic_config);TIM_Cmd(TIM2,ENABLE);//启动定时器
}void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)//获取中断标志位{count ++;TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除标志位}
}int main(void)
{OLED_Init(); //OLED初始化Timer_Init(); //timer初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Count:"); //1行1列显示字符串Count:while (1){OLED_ShowNum(1, 7, count, 5); //OLED不断刷新显示count值}
}
其中只需要注意一点,由于初始化ARR重装寄存器时需要一个更新中断来完成对缓冲寄存器的写入,所以配置完NVIC后会产生一次中断,如果不想立刻产生一次中断,则需要在配置完TIM后调用TIM_ClearFlag(TIM2,TIM_FLAG_Update);清除中断标志位。
2、定时器外部时钟
对射式红外传感器作为外部时钟,通过TIM2的ETR外部时钟引脚(引脚定义是PA0)输入。
其中设置外部时钟需要关注TIMx_SMCR寄存器,TIM_ETRClockMode2Config会用到。
#include "stm32f10x.h"
#include "OLED.h"
#include "Delay.h"
uint16_t count;void Timer_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//RCC中开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为上拉输入//TIM_InternalClockConfig(TIM2);//定时器配置为内部时钟/*外部时钟配置TIM_ExtTRGPSC_OFF预分频器不需要分频TIM_ExtTRGPolarity_NonInverted外部触发极性设置为不反向(高电平或上升沿有效)0x0F用滤波器*/TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);TIM_TimeBaseInitTypeDef TIM2config;TIM2config.TIM_ClockDivision = TIM_CKD_DIV1;//用于过滤信号时采样信号的频率分频。通过判断几个采样点的数值稳定来过滤。TIM2config.TIM_CounterMode = TIM_CounterMode_Up;//向上计数TIM2config.TIM_Period = 10 - 1;//ARR(0~65535)TIM2config.TIM_Prescaler = 1 - 1; //预分频(0~65535)TIM2config.TIM_RepetitionCounter = 0;//重复计数器的值(仅高级寄存器)TIM_TimeBaseInit(TIM2,&TIM2config);TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//设置更新中断,到NVICTIM_ClearFlag(TIM2,TIM_FLAG_Update);//清除中断标志位,避免刚初始化就进入中断!!!!!!!!!!因为初始化需要一个更新事件更新重装值缓冲寄存器NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置NVICNVIC_InitTypeDef nvic_config;nvic_config.NVIC_IRQChannel = TIM2_IRQn;nvic_config.NVIC_IRQChannelCmd = ENABLE;nvic_config.NVIC_IRQChannelPreemptionPriority = 2;nvic_config.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&nvic_config);TIM_Cmd(TIM2,ENABLE);//启动定时器
}void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)//获取中断标志位{count ++;TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除标志位}
}int main(void)
{OLED_Init(); //OLED初始化Timer_Init(); //计数传感器初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Count:"); //1行1列显示字符串Count:while (1){OLED_ShowNum(1, 7, count, 5); //OLED不断刷新显示count值}
}
3、输出比较(PWM呼吸灯)
产生PWM;输出比较可以通过比较CNT与CCR捕获比较寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形;每个高级定时器和通用定时器都拥有4个输出比较通道高级定时器的前3个通道额外拥有死区生成和互补输出的功能;高级定时器的前3个通道额外拥有死区生成和互补输出的功能
在这个图里,左边就是CNT计数器和CCR1第一路的捕获/比较寄存器。 它两进行比较,当CNT>CCR1或者CNT=CCR1时,就会给输出模式控制器传一个信号,然后输出模式控制器就会改变它输出OC1REF的高低电平。REF信号实际上就是指信号的高低电平,这个REF是reference的缩写,意思是参考信号。上面还有一个ETRF输入,这个是定时器的一个小功能,一般不用。接着REF信号可以前往主模式控制器,可以把这个REF信号映射到主模式的TRGO输出上去,不过REF的主要去向还是下面这一路,通过这一路到达TIMx_CCER,给这个寄存器写0,信号就会往上走,就是信号电平不翻转,进来啥样,出去啥样;写1的话,信号会往下走,就是信号通过一个非门取反,那输出的信号就是输入信号高低电平反转的信号;这就是极性选择,就是选择是不是要把高低电平反转一下。接着就是输出使能电路了,选择要不要输出,这个引脚就是CH1通道的引脚,最后就是OC1引脚,这个引脚就是CH1通道的引脚,在引脚定义表里就可以直到具体是哪个GPIO口了。
冻结模式:CNT和CCR没有用,处于无效状态,REF保持为原状态,这都是一样的效果。这个模式也比较简单,它根部不管CNT和CCR谁大谁小,直接REF保持不变,维持上一个状态就可以了。用处:比如正在输出PWM波、突然想暂停一会儿输出、就可以设置成这个模式。一旦切换为冻结模式后,输出就暂停了。并且高低电平也维持为暂停时刻的状态,保持不变,这就是冻结模式的作用。
匹配时置有效电平/匹配时置无效电平/匹配时电平翻转:这个有效电平和无效电平,一般是高级定时器里面的一个说法,是和关断、刹车这些功能配合表述的,它说的比较严谨,所以叫有效电平和无效电平。所以可以直接理解为置有效电平就是置高电平,置无效电平就是置低电平。这些模式就可以用作波形输出了。比如相等时电平翻转这个模式,这个可以方便地输出一个频率可调,占空比始终为50%的PWM波形,比如设置CCR为0,那CNT每次更新清0时,就会产生一次CNT=CCR的事件,这就会导致输出电平翻转一次,每更新两次、输出为一个周期,并且高电平和低电平的时间是始终相等的,也就是占空比时钟为50%。匹配时置有效电平/匹配时置无效电平都是一次性的,置完高电平/低电平后,就不管事了,所以这两个模式不适合输出连续变化的波形,如果想定时输出一次性的信号,可以考虑一下这两个模式。
强制为无效电平/强制为有效电平:这两个模式是CNT与CCR无效,REF强制为无效电平或者强制为有效电平。这两个模式和冻结模式也差不多。如果想暂停波形输出,并且在暂停期间保持低电平或者保持高电平,就可以设置这两个强制输出模式。
PWM模式1/PWM模式2:它们可以用于输出频率和占空比都可调的PWM波形,也是我们主要使用的模式
首先这个OC1和OC1N就是两个互补的输出端口,分别控制上口和下口的导通和关闭,然后是在切换上下管导通状态时,如果在上管关断的瞬间,下管就会立刻打开,那可能会因为器件的不理想,上管还没有完全关断,下管就已经导通了,出现了短暂的上下管同时导通的现象,这会导致功率损耗,引起器件发热,所以在这里为了避免这个问题,就有了死区生成电路,它会在上管关闭的时候,延迟一小段时间,再导通下管,下管关闭的时候,延时一小段时间,再导通上管,这样就可以避免上下管同时导通的现象了,这就是死区生成和互补输出的用途。
上图就是PWM的基本结构
PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
PWM占空比: Duty = CCR / (ARR + 1)
PWM分辨率: Reso = 1 / (ARR + 1)
很重要的一点:如果使用TIM2的OC1也就是CH1通道输出PWM,那就只能在PA0输出;如果选择TIM2的CH3,就只能选择PA2。其它外设也是同理,这个关系是定死的,不能更改,不过STM32还是提供了一次更改的机会,就是重定义或者叫重映射功能。比如既要用USTART2的TX引脚,又要用TIM2的CH3通道,它两冲突了,没办法同时用,那我们就可以在重映射的列表里找一下,发现TIM2的CH3通道可以重映射到PB10引脚;如果重映射列表里找不到,那外设复用的GPIO就不能挪位置,这就是重映射的功能,配置重映射是用AFIO来完成的。
呼吸灯,用了CH1和CH2通道,对应PA0和PA1
#include "stm32f10x.h"
#include "OLED.h"
#include "Delay.h"
uint8_t i; //定义for循环的变量void PWM_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO重映射*///RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,重映射必须先开启AFIO的时钟//GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //将TIM2的引脚部分重映射,具体的映射方案需查看参考手册//GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //将JTAG引脚失能,作为普通GPIO引脚使用/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; //GPIO_Pin_15;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出 //受外设控制的引脚,均需要配置为复用模式 /*对于普通的开漏/推挽输出,引脚的控制全是来自输出数据寄存器的,如果想用定时器来控制引脚,那就需要使用复用开漏/推挽输出的模式这里输出数据寄存器将被断开,输出控制全将转移给片上外设,只有把GPIO设置成复用推挽输出,引脚的控制权才能交给片上外设,PWM波形才能通过引脚输出*//*配置时钟源*/TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元/*输出比较初始化*/TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值//则最好执行此函数,给结构体所有成员都赋一个默认值//避免结构体初值不确定的问题TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值TIM_OC1Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1TIM_OC2Init(TIM2, &TIM_OCInitStructure);/*TIM使能*/TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}/*** 函 数:PWM设置CCR* 参 数:Compare 要写入的CCR的值,范围:0~100* 返 回 值:无* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比* 占空比Duty = CCR / (ARR + 1)*/
void PWM_SetCompare1(uint16_t Compare)
{TIM_SetCompare1(TIM2,Compare);
}void PWM_SetCompare2(uint16_t Compare)
{TIM_SetCompare2(TIM2,Compare);
}int main(void)
{OLED_Init(); //LED初始化PWM_Init(); //计数传感器初始化while (1){for (i = 0; i <= 100; i++){PWM_SetCompare2(100 - i); //依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮PWM_SetCompare1(i);sysDelayms(10); //延时10ms}for (i = 0; i <= 100; i++){PWM_SetCompare2(i); //依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗PWM_SetCompare1(100 - i);sysDelayms(10); //延时10ms}}
}
AFIO通过重映射TIM2_CH1到PA15,禁用JTAG引脚
#include "stm32f10x.h"
#include "OLED.h"
#include "Delay.h"void PWM_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO重映射*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,重映射必须先开启AFIO的时钟GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //将TIM2的引脚部分重映射,具体的映射方案需查看参考手册8.3.7GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //将JTAG引脚失(PA15\PB3\PB4)能,作为普通GPIO引脚使用/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出 //受外设控制的引脚,均需要配置为复用模式 /*对于普通的开漏/推挽输出,引脚的控制全是来自输出数据寄存器的,如果想用定时器来控制引脚,那就需要使用复用开漏/推挽输出的模式这里输出数据寄存器将被断开,输出控制全将转移给片上外设,只有把GPIO设置成复用推挽输出,引脚的控制权才能交给片上外设,PWM波形才能通过引脚输出*//*配置时钟源*/TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元/*输出比较初始化*/TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值//则最好执行此函数,给结构体所有成员都赋一个默认值//避免结构体初值不确定的问题TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值TIM_OC1Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1/*TIM使能*/TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}/*** 函 数:PWM设置CCR* 参 数:Compare 要写入的CCR的值,范围:0~100* 返 回 值:无* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比* 占空比Duty = CCR / (ARR + 1)*/
void PWM_SetCompare1(uint16_t Compare)
{TIM_SetCompare1(TIM2,Compare);
}int main(void)
{OLED_Init(); //LED初始化PWM_Init(); //计数传感器初始化uint8_t i; //定义for循环的变量while (1){for (i = 0; i <= 50; i++){PWM_SetCompare1(i); //依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮sysDelayms(15); //延时10ms}for (i = 0; i <= 50; i++){PWM_SetCompare1(50 - i);sysDelayms(15); //延时10ms}}
}
4、输出比较(PWM舵机)
#include "stm32f10x.h"
#include "OLED.h"
#include "Delay.h"void PWM_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1引脚初始化为复用推挽输出 //受外设控制的引脚,均需要配置为复用模式 /*对于普通的开漏/推挽输出,引脚的控制全是来自输出数据寄存器的,如果想用定时器来控制引脚,那就需要使用复用开漏/推挽输出的模式这里输出数据寄存器将被断开,输出控制全将转移给片上外设,只有把GPIO设置成复用推挽输出,引脚的控制权才能交给片上外设,PWM波形才能通过引脚输出*//*配置时钟源*/TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1; //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值,频率是50HzTIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元/*输出比较初始化*/TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值//则最好执行此函数,给结构体所有成员都赋一个默认值//避免结构体初值不确定的问题TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值TIM_OC2Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1/*TIM使能*/TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}void Servo_SetAngle(float Angle)
{TIM_SetCompare2(TIM2,(uint16_t)(Angle / 180 * 2000 + 500));
}void Key_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB1和PB11引脚初始化为上拉输入
}
uint8_t Key_GetNum(void)
{uint8_t KeyNum = 0; //定义变量,默认键码值为0if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //读PB1输入寄存器的状态,如果为0,则代表按键1按下{sysDelayms(20); //延时消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按键松手sysDelayms(20); //延时消抖KeyNum = 1; //置键码为1}if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //读PB11输入寄存器的状态,如果为0,则代表按键2按下{sysDelayms(20); //延时消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); //等待按键松手sysDelayms(20); //延时消抖KeyNum = 2; //置键码为2}return KeyNum; //返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}int main(void)
{OLED_Init(); //LED初始化PWM_Init(); //舵机初始化Key_Init(); //按键初始OLED_ShowString(1, 1, "Angle:"); //1行1列显示字符串Angle:uint8_t i; //定义for循环的变量uint8_t KeyNum; //定义用于接收键码的变量float Angle = 0;; //定义角度变量while (1){KeyNum = Key_GetNum(); //获取按键键码if (KeyNum == 1) //按键1按下{Angle += 30; //角度变量自增30if (Angle > 180) //角度变量超过180后{Angle = 0; //角度变量归零}}else if(KeyNum == 2){Angle -= 30;if(Angle < 0){Angle = 180;}}Servo_SetAngle(Angle); //设置舵机的角度为角度变量OLED_ShowNum(1, 7, Angle, 3); //OLED显示角度变量}}
5、输出比较(PWM直流电机)
void PWM_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA2引脚初始化为复用推挽输出 //受外设控制的引脚,均需要配置为复用模式 /*对于普通的开漏/推挽输出,引脚的控制全是来自输出数据寄存器的,如果想用定时器来控制引脚,那就需要使用复用开漏/推挽输出的模式这里输出数据寄存器将被断开,输出控制全将转移给片上外设,只有把GPIO设置成复用推挽输出,引脚的控制权才能交给片上外设,PWM波形才能通过引脚输出*//*配置时钟源*/TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1; //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元/*输出比较初始化*/TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值//则最好执行此函数,给结构体所有成员都赋一个默认值//避免结构体初值不确定的问题TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值TIM_OC3Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1/*TIM使能*/TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}void Motor_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA4和PA5引脚初始化为推挽输出
}void PWM_Setspeed(int16_t Speed)
{TIM_SetCompare3(TIM2,Speed);
}void Motor_SetSpeed(int8_t Speed)
{if (Speed >= 0) //如果设置正转的速度值{GPIO_SetBits(GPIOA, GPIO_Pin_4); //PA4置高电平GPIO_ResetBits(GPIOA, GPIO_Pin_5); //PA5置低电平,设置方向为正转PWM_Setspeed((uint16_t)(Speed*1.2)); //PWM设置为速度值}else //否则,即设置反转的速度值{GPIO_ResetBits(GPIOA, GPIO_Pin_4); //PA4置低电平GPIO_SetBits(GPIOA, GPIO_Pin_5); //PA5置高电平,设置方向为反转PWM_Setspeed((uint16_t)(-Speed*1.2)); //PWM设置为负的速度值,因为此时速度值为负数,而PWM只能给正数}
}void Key_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB1和PB11引脚初始化为上拉输入
}
uint8_t Key_GetNum(void)
{uint8_t KeyNum = 0; //定义变量,默认键码值为0if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //读PB1输入寄存器的状态,如果为0,则代表按键1按下{sysDelayms(20); //延时消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按键松手sysDelayms(20); //延时消抖KeyNum = 1; //置键码为1}if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //读PB11输入寄存器的状态,如果为0,则代表按键2按下{sysDelayms(20); //延时消抖while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); //等待按键松手sysDelayms(20); //延时消抖KeyNum = 2; //置键码为2}return KeyNum; //返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}int main(void)
{OLED_Init(); //LED初始化Motor_Init();PWM_Init(); //舵机初始化Key_Init(); //按键初始OLED_ShowString(1, 1, "Speed:"); //1行1列显示字符串Angle:uint8_t i; //定义for循环的变量uint8_t KeyNum; //定义用于接收键码的变量int8_t Speed = 0;; //定义速度变量while (1){KeyNum = Key_GetNum(); //获取按键键码if (KeyNum == 1) //按键1按下{Speed += 20; //角度变量自增30if (Speed > 100) //角度变量超过180后{Speed = 0; //角度变量归零}}else if(KeyNum == 2){Speed -= 20;if(Speed < -100){Speed = 0;}}Motor_SetSpeed(Speed); //设置舵机的角度为角度变量OLED_ShowSignedNum(1, 7, Speed, 3); //OLED显示速度变量}}