历史上的今天
今天是:2024年08月27日(星期二)
2021年08月27日 | STM32F401的PWM输出
2021-08-27 来源:eefocus
PWM的说明
PWM有三个关键指标: PWM频率, 占空比, 区分度
对于同一个时钟频率下工作的单片机, 区分度是和PWM工作频率相关的, 因为总频率是固定的, PWM工作频率越高, 留下给区分度的部分就越低, 因此区分度就越低. 对于STM32, 如果时钟是72MHz, 在PWM频率为1KHz时, 区分度为16bit, 在281KHz时, 为8bit, 在4.5MHz时, 就时4bit了.
STM32F4 Timers
STM32的PWM功能是定时器功能的一部分, STM32F4系列完整的定时器是14个
| Timer | Type | Resolution | Prescaler | Channels | MAX INTERFACE CLOCK | MAX TIMER CLOCK* | APB |
|---|---|---|---|---|---|---|---|
| TIM1, TIM8 | Advanced | 16bit | 16bit | 4 | SysClk/2 | SysClk | 2 |
| TIM2, TIM5 | General purpose | 32bit | 16bit | 4 | SysClk/4 | SysClk, SysClk/2 | 1 |
| TIM3, TIM4 | General purpose | 16bit | 16bit | 4 | SysClk/4 | SysClk, SysClk/2 | 1 |
| TIM9 | General purpose | 16bit | 16bit | 2 | SysClk/2 | SysClk | 2 |
| TIM10, TIM11 | General purpose | 16bit | 16bit | 1 | SysClk/2 | SysClk | 2 |
| TIM12 | General purpose | 16bit | 16bit | 2 | SysClk/4 | SysClk, SysClk/2 | 1 |
| TIM13, TIM14 | General purpose | 16bit | 16bit | 1 | SysClk/4 | SysClk, SysClk/2 | 1 |
| TIM6, TIM7 | Basic | 16bit | 16bit | 0 | SysClk/4 | SysClk, SysClk/2 | 1 |
F401属于低端系列, 定时器只有一部分, 内置的定时器为
1个高级定时器TIM1
三相 PWM 输出, 4个独立通道(如果正反算两个的话有8个). It has complementary PWM outputs with programmable inserted dead times
7个通用定时器
全功能的: TM2&5, TIM3&4, 4个独立通道 for input capture/output compare, PWM or one-pulse mode output.
普通的: TIM9, TIM10,11. TIM10和TIM11有1个独立通道, TIM9有2个独立通道 for input capture/output compare, PWM or one-pulse mode output.
2个watchdog timers
每个定时器都有对应的通道数, 一般都有CH1 - CH4, 对于TIM1, 还有CH1N - CH4N
关于CH1和CH1N
后者输出相对于前者反相的PWM信号, CH1和CH1N两个通道互补输出. 在设置这两个通道输出的时候如果开启了互补输出, 那么这两个引脚的输出电平始终相反, 也就是一个引脚输出低电平, 另一个引脚自动输出高电平, 反之亦然. 这样的输出方式一般用于电机驱动控制.
STM32F4的TIMx PIN脚输出映射关系
| TIM1 | TIM2 | TIM3 | TIM9 | |
|---|---|---|---|---|
| CH1 | PA8 | PA0 PA5 PA15 | PA6 PB4 | PA2 |
| CH2 | PA9 | PA1 PB3 | PA7 PB5 | PA3 |
| CH3 | PA10 | PA2 PB10 | PB0 | |
| CH4 | PA11 | PA3 PB11 | PB1 | |
| CH1N | PB13 PA7 | |||
| CH2N | PB14 PB0 | |||
| CH3N | PB15 PB1 |
设置PWM输出电平的模式
PWM输出模式的配置主要有两个
1. TIM_OCMode: TIM输出比较和PWM模式
TIM_OCMode_Timing 在比较成功后不在对应输出管脚上产生输出, TIM_OCMode_Timing does not produce output on the corresponding output pin after a successful comparison
TIM_OCMode_Active
TIM_OCMode_Inactive
TIM_OCMode_Toggle 计数达到比较值时翻转对应输出管脚上的电平, TIM_OCMode_Toggle is to flip the level on the corresponding output pin after a successful comparison
TIM_OCMode_PWM1 常用的模式, CNT < CRRx时为有效电平, CNT > CRRx为无效电平
TIM_OCMode_PWM2 与PWM1相反, CNT小于时为无效电平, 高于时为有效电平, 配合TIM_OCPolarity可以做到和PWM1一样的输出
2. TIM_OCPolarity: PWM的有效电平
与TIM_OCMode_PWM1和TIM_OCMode_PWM2配合, TIM_OCPolarity_High表示有效电平是高电平, TIM_OCPolarity_Low是低电平
上面两个配置结合产生的效果
TIM_OCMode_PWM1模式下
设置TIM_OCPolarity_High, TIMx_CNT > TIMx_CCR输出高电平, TIMx_CNT < TIMx_CCR输出低电平
设置TIM_OCPolarity_Low, TIMx_CNT > TIMx_CCR输出低电平, TIMx_CNT < TIMx_CCR输出高电平
TIM_OCMode_PWM2模式下
设置TIM_OCPolarity_High, TIMx_CNT > TIMx_CCR输出低电平, TIMx_CNT < TIMx_CCR输出高电平
设置TIM_OCPolarity_Low, TIMx_CNT > TIMx_CCR输出高电平, TIMx_CNT < TIMx_CCR输出低电平
设置PWM频率
设置PWM频率, 即设置PWM完整周期的时钟计数次数. 这个是通过TIM_BaseStruct.TIM_Period(ARR寄存器)设置的, 要设置这个值, 首先你要知道这个值的上限, 即定时器的最大值, 例如 16bit 即 65535, 要计算出PWM频率, 可以这样计算
PWM_frequency = timer_tick_frequency / (TIM_Period + 1)
也可以通过PWM频率倒推时钟周期计数值
TIM_Period = timer_tick_frequency / PWM_frequency - 1
例如, 如果需要的PWM频率为10KHz, 则时钟的周期计数值为
TIM_Period = (84000000 / 10000) - 1; // 8399
如果需要17.57 Khz, 就是
TIM_Period = (SystemCoreClock / 17570 ) - 1;
如果通过这个式子算出来的计数值大于定时器长度(例如超过了65535), 你需要增大 prescaler, 降低系统时钟频率
如果需要在运行时修改, 可以使用函数TIM_PrescalerConfig(TIM2, 35999, TIM_PSCReloadMode_Immediate), 这个函数的作用就是在定时器工作时改变预分频器的值.
设置PWM占空比
设置占空比, 需要通过设置 TIM_Pulse 参数, 这个值就是用于比较的触发值CRR, TIM_OCInitStructure.TIM_Pulse = 100表示触发值为100, 这个值的计算要结合PWM周期总计数值TIM_Period和需要的占空比百分比, 例如
pulse_length = ((TIM_Period + 1) * DutyCycle) / 100 - 1
# 其中DutyCycle是一个百分比, 例如对于TIM_Period为8399, 如果需要25%占空比
pulse_length = (8399 + 1) * 0.25 - 1 = 2099
如果需要在运行时修改, 你可以:
You just write the updated width to the TIMx_CCRy register. Changed the CCR value in the relevant timer register and this did the trick. In my case, the code used to change the duty cycle is 'TIM3 -> CCR4 = {required value}'
通过调用TIM_SetCompare[x](TIMx, Compare1)这个函数,修改CCR的值,改变输出占空比, 例如TIM_SetCompare1函数名中的数字1代表的是TIMx的通道1, 参数TIMx可以是TIM1, TIM2等, 第二个参数 Compare1, 是用于与TIMx计数值比较的数, 在TIMx达到这个计数值时将根据当前的模式和极性, 进行电平变换. TIM_SetCompareX这个函数有四个, 分别是TIM_SetCompare1, TIM_SetCompare2, TIM_SetCompare3, TIM_SetCompare4. 对应不同的CHx使用, 例如TIMx_CH1使用 TIM_SetCompare1, TIMx_CH2使用TIM_SetCompare2, 等等.
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1) {
/* Check the parameters */
assert_param(IS_TIM_LIST8_PERIPH(TIMx));
/* Set the Capture Compare1 Register value */
TIMx->CCR1 = Compare1;
}
代码
启动对应输出口的定时器, 这里是TIM4
void TM_TIMER_Init(void) {
TIM_TimeBaseInitTypeDef TIM_BaseStruct;
/* 开启TIM4时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
/*
TIM4连接的是 APB1 总线, 在F407上时钟是 42MHz, 但是有内部PLL, 将频率翻倍为 84MHz. 注意: 也有定时器是接在 APB2 总线上的, 默认工作在 84MHz, 通过内部PLL翻倍至 168MHz
设置预分频 timer prescaller
时钟被设置为 timer_tick_frequency = Timer_default_frequency / (prescaller_set + 1)
在这个例子中, 我们希望使用最大频率, 所以 prescaller 设置为 0, 所以时钟与总线时钟一致, 频率为
timer_tick_frequency = 84000000 / (0 + 1) = 84000000
*/
TIM_BaseStruct.TIM_Prescaler = 0;
/* 使用上升沿计数 */
TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
/*
设置一个PWM完整周期的时钟计数次数, 首先你要知道定时器的最大值, 在这个例子中是16bit, 即 65535, 要计算出你的PWM频率, 可以这样计算
PWM_frequency = timer_tick_frequency / (TIM_Period + 1)
通过这个算式也可以通过PWM频率倒推时钟周期计数值
TIM_Period = timer_tick_frequency / PWM_frequency - 1
在这个例子中, 如果需要的PWM频率为10KHz, 则时钟的周期计数值为
TIM_Period = 84000000 / 10000 - 1 = 8399
如果通过这个式子算出来的计数值大于定时器长度(例如超过了65535), 你需要增大 prescaler 降低系统时钟频率
*/
TIM_BaseStruct.TIM_Period = 8399; /* 10kHz PWM */
/* TIM_ClockDivision的设置不影响PWM频率 */
TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_BaseStruct.TIM_RepetitionCounter = 0;
/* TIM4 初始化 */
TIM_TimeBaseInit(TIM4, &TIM_BaseStruct);
/* TIM4 开始计数 */
TIM_Cmd(TIM4, ENABLE);
}
初始化PWM 4个通道
void TM_PWM_Init(void) {
TIM_OCInitTypeDef TIM_OCStruct;
/* 通道的公用配置 */
/* PWM 模式 2 = Clear on compare match 达到预设值时拉低电平 */
/* PWM 模式 1 = Set on compare match 达到预设值时拉高电平 */
TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
/*
要得到期望的占空比(DutyCycle, 一个百分比), 通过这个式子计算定时器触发值
pulse_length = ((TIM_Period + 1) * DutyCycle) / 100 - 1
例如
25% 占空比: pulse_length = ((8399 + 1) * 25) / 100 - 1 = 2099
50% 占空比: pulse_length = ((8399 + 1) * 50) / 100 - 1 = 4199
75% 占空比: pulse_length = ((8399 + 1) * 75) / 100 - 1 = 6299
100% 占空比: pulse_length = ((8399 + 1) * 100) / 100 - 1 = 8399
注意: 如果触发值大于时钟周期总长度 TIM_Period, 这个PWM将一直输出同样的电平
*/
TIM_OCStruct.TIM_Pulse = 2099; /* 25% duty cycle */
TIM_OC1Init(TIM4, &TIM_OCStruct);
TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_OCStruct.TIM_Pulse = 4199; /* 50% duty cycle */
TIM_OC2Init(TIM4, &TIM_OCStruct);
TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_OCStruct.TIM_Pulse = 6299; /* 75% duty cycle */
TIM_OC3Init(TIM4, &TIM_OCStruct);
TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);
TIM_OCStruct.TIM_Pulse = 8399; /* 100% duty cycle */
TIM_OC4Init(TIM4, &TIM_OCStruct);
TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
}
初始化GPIO输出
void TM_LEDS_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
/* GPIOD 时钟 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
/* 设置这些PIN脚的功能复用 */
GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource13, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_TIM4);
/* 设置PIN脚 */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOD, &GPIO_InitStruct);
}
完整的代码
#include "defines.h"
#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_tim.h"
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
u16 TimerPeriod = 0;
u16 Channel1Pulse = 0, Channel2Pulse = 0, Channel3Pulse = 0, Channel4Pulse = 0;
void DecreasePuls() {
Channel1Pulse = (Channel1Pulse <= 10)? TimerPeriod : Channel1Pulse - 10;
Channel2Pulse = (Channel2Pulse <= 10)? TimerPeriod : Channel2Pulse - 10;
Channel3Pulse = (Channel3Pulse <= 10)? TimerPeriod : Channel3Pulse - 10;
Channel4Pulse = (Channel4Pulse <= 10)? TimerPeriod : Channel4Pulse - 10;
}
void TIM_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* GPIOA, GPIOB Clocks enable */
RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB , ENABLE);
/* GPIOA Configuration: Channel 1, 2, 3, 4 as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
下一篇:stm32cubemx配置PWM
史海拾趣
|
就在虚拟机上跑. 我的VXWORKS有http服务器的源码, 但是编译时组件树里面没有http的可选组件. 找到http的文件夹下面, 没有makefile, 不知道如何来编译. 有经验的请指教, 帮上忙的加分.… 查看全部问答> |
|
我有一宏基笔记本, 摄相头是本上原装的, 换了系统之后就不能用了?? 下载官方的驱动软件也驱动不了, 提示“该硬件无法驱动”。 郁闷几个星期了! 在网上搜索相关资料没一个合适的。 请有经验的师傅帮忙解决一下。… 查看全部问答> |
|
写了个键盘驱动key_scan.c, 1.请问想编译成key_scan.o该在Makefile中怎么写编译语句?谢谢。 2.int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);函数的参数 name该用什么?是key_scan吗?它与mknod的 ...… 查看全部问答> |
|
wince 烧写中giveio.inf与giveio.sys驱动安装错误分析 我用的是杭州高联dm 2410b+arm9开发实验平台,在开机检测中遇到一个麻烦,顺利解决后觉得有必要写下,与大家分享。 在开机前,有一步是安装驱动,烧写boot.bin与eboot-rtc.nt0两个文件进nand flash的0块与2块。在用sjf烧写前,有一步 ...… 查看全部问答> |
|
37岁的爱德格•卡梅兹(Edgar Camez)是一名工程师,在密歇根州迪尔伯恩(Dearborn)的福特设计中心(Ford Design Center)工作时,他成为了一名为汽车设计发动机支架的专才。但在工作了11年后他希望能做点不同的事,他感到自己的小圈子限制了自 ...… 查看全部问答> |
|
当在同一个空间的多个zigbee网络,如果都使用相同的物理信道,会导致通讯效率下降。 在zstack中,通常的解决办法就是手动修改DEFAULT_CHANLIST这个宏的取值,这个方法需要网络的所有节点重新编译程序,方法本身使用也有一些限制。 zigbee 2007/pr ...… 查看全部问答> |




