首先先来介绍一下PWM主要会用到的一些函数:
这四个函数就是用来配置输出比较的OC(output compare),四个输出比较单元对应四个输出比较函数
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);
TIMx:选择定时器,TIM_OCInitStruct:输出比较的结构体
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);
给输出比较结构体赋为默认值。
至此输出比较的配置就差不多了,以下是一些小功能和一些运行时更改参数的函数。
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);
用来配置强制输出模式,如果运行中想要暂停波形并且强制输出高或低电平,可以使用这个函数,但一般用得不多,因为强制输出高电平和设置占空比100%是一样的,强制输出低电平和设置0%占空比是一样的。
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);
这四个函数用来配置CCR寄存器的预装功能,预装功能就是影子寄存器,写入的值不会立即生效,而是在更新事件后才会生效。
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);
用来清除Ref
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);
单独用来设置输出比较的极性的,带N的就是高级定时器互补通道的配置,OC4没有互补通道,所以就没有OC4N的函数。除了这里可以设置极性,结构体初始化那里也可以设置极性
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_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);
这四个是用来单独更改CCR寄存器值的函数,比较重要,运行时更改占空比需要用到
接下来我们就可以根据下图的指示,来进行初始化了。
首先是时基单元的初始化,在上一次实验中已经展示了怎么初始化,这里就不再复述了,直接上代码:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InternalClockConfig(TIM2);//定时器默认使用内部时钟,这句不写也能通过编译
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_OCInitTypeDef TIM_OCInitStructure;//只列出我们需要用的参数,有些是高级定时器用的
TIM_OCStructInit(&TIM_OCInitStructure);//先给所有参数默认初始值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//输出比较的模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出比较的极性
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//设置输出使能
TIM_OCInitStructure.TIM_Pulse = 0;//设置CCR,与ARR和PSC共同决定输出PWM的周期和占空比
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
因为我们先要做一个LED呼吸灯的实现,LED灯接在PA0端口,所以还需要对GPIO口进行初始化,一般建议把GPIO的初始化放在最前面:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
这里我们选择PA0口的理由如下:
图说明,TIM2的ETR引脚和通道1的引脚,都是借用了PA0这个引脚的位置,换句话说就是TIM2的引脚复用在了PA0引脚上,所以说如果我们要使用TIM2的OC1也就是CH1通道输出PWM,它就只能在PA0的引脚上输出,而不能任意选择引脚输出。同样如果使用了TIM2的CH2,那就只能够在PA1端口输出,再往下同理。
另外,我们对GPIO口的初始化模式为复用开漏/推挽输出,对于普通的开漏/推挽输出,引脚的控制权是来自于输出数据寄存器的,如果想让定时器来控制引脚,就需要使用复用开漏/推挽输出。
我们需要实现呼吸灯就需要不断改变LED的亮度,需要让PWM的占空比不断变化,我们可以通过更改CCR寄存器来实现
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare);
}
(黄色:数据重装寄存器ARR,蓝色:计数器CNT,红色:捕获比较寄存器CCR,绿色:输出比较OC)
Freq = CK_PSC / (PSC + 1) / (ARR + 1) PWM频率
Duty = CCR / (ARR + 1) PWM占空比
Reso = 1 / (ARR + 1) PWM分辨率
接下来是PWM控制舵机
驱动舵机的关键就是输出一个这样的PWM波形。
不同通道的计数器是一样的,但是CCR都是有各自的,所以占空比可以各自设定,由于计数器更新,所有的PWM同时跳变,所以相位是同步的。
舵机要求的频率是20ms,那频率就是1/20ms = 50Hz
通过数学计算出CCR与角度之间的关系是:Angle / 180 * 2000 + 500,我们可以再创建.c文件来存放驱动舵机的函数
接下来是PWM驱动直流电机,这个的接线图比较复杂,电机功率比较大,GPIO口无法直接驱动,需要一个电机驱动电路来进行驱动:
这两天刚好有点小感冒,又遇上了满课的一整天,今天刚好要学的还是最多的一课,视频总共有1小时那么多,搞了一整个晚上,两三个小时,但是今天的解析还是写的比较简单,感觉还有很多东西没有说到,但现在时间已经不早了,刚好还有点感冒,还是先早点休息吧。话说前两天趁着自己生日,奢侈了一把,买了一张sakanaction的专辑,但是从日本那边发的,感觉要好久才会收到,又花了三十块买了个光驱,不知道听起来会不会不一样,但这都是小事,最主要的还是能收藏一张专辑以后翻出来看到会很有感觉。以后也想多买点喜欢的乐队的专辑,工作后如果能买个好一点的CD机就更好不过了。现在看上一个叫巫单曲人生的CD机感觉不错,很好看,但要六七百,感觉真不是现在能负担得起的。我还想要攒钱买PS5呢。如果排个优先顺序的话,可能还是比较想要PS5,总之要好好省钱了。