当前位置: 首页 > news >正文

江协科技STM32课程笔记(三)—定时器TIM(输出比较)

一、STM32定时器概述

定时器可以分为以下三种:高级定时器、通用定时器和基本定时器;从高级到低级,可以向下兼容功能;

定时器类型
类型编号总线功能
高级定时器TIM1、TIM8APB2拥有通用定时器全部功能,额外具有重复计数器、死区生成,互补输出、刹车输入等功能
通用定时器TIM2、TIM3、TIM4、TIM5APB1拥有基本定时器全部功能,额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能。
基本定时器TIM6、TIM7APB1拥有定时中断、主模式触发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显示速度变量}}

http://www.dtcms.com/a/485809.html

相关文章:

  • 网站建设可行性分析报告模板支付宝小程序搭建
  • 精通网站开发书籍做游戏网站赚钱么
  • Linux 网络分析终极武器:Tcpdump 深度指南!
  • 制造业流程自动化提升生产力的全面分析
  • 主流的 MCU 开发语言为什么是 C 而不是 C++?
  • 3-AI-应用开发
  • 知识图谱增强的AI记忆觉醒革命:从Anthropic Claude 4.5看智能体的未来演进
  • Spring Boot 3零基础教程,yml配置文件,笔记13
  • 三步对接gpt-5-pro!地表强AI模型实测
  • [AI学习:SPIN -win-安装SPIN-工具过程 SPIN win 电脑安装=accoda 环境-第二篇:解决报错]
  • h5美食制作网站模板下载电子商务网站前台业务系统主要是
  • uniapp 提取 安卓平台软件包名称 公钥 证书MD5指纹
  • Redis 事务机制:Pipeline、ACID、Lua脚本
  • 【实时Linux实战系列】在实时系统中安全地处理浮点运算
  • 基于仿真和运行时监控的自动驾驶安全分析
  • Java-Spring入门指南(二十七)Android Studio 第一个项目搭建与手机页面模拟器运行
  • Highcharts 绘制之道(2):高级绘图技术与连通关系
  • 学习笔记——GPU训练
  • 数据结构——二叉搜索树Binary Search Tree(介绍、Java实现增删查改、中序遍历等)
  • 网站个人主页怎么做wordpress 网银支付
  • 网站建设常州青之峰陕西西安网站设计公司
  • FTP 抓包分析实战,命令、被动主动模式要点、FTPS 与 SFTP 区别及真机取证流程
  • Linux下的权限与文件
  • 《算法闯关指南:优选算法--二分查找》--19.x的平方根,20.搜索插入位置
  • 从中序与后序遍历序列构造二叉树
  • 【超分辨率专题】DOVE:特色双阶段训练的单步Real-World视频超分辨
  • 《Linux基础入门指令(二)》:从零开始理解Linux系统
  • 响应式网站开发图标郑州网站商城建设
  • 仓颉编程(3)基本操作符
  • 潍坊网站建设推广公司上海公司注销的流程及需提供的材料2023