历史上的今天
返回首页

历史上的今天

今天是: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个

TimerTypeResolutionPrescalerChannelsMAX INTERFACE CLOCKMAX TIMER CLOCK*APB
TIM1, TIM8Advanced16bit16bit4SysClk/2SysClk2
TIM2, TIM5General purpose32bit16bit4SysClk/4SysClk, SysClk/21
TIM3, TIM4General purpose16bit16bit4SysClk/4SysClk, SysClk/21
TIM9General purpose16bit16bit2SysClk/2SysClk2
TIM10, TIM11General purpose16bit16bit1SysClk/2SysClk2
TIM12General purpose16bit16bit2SysClk/4SysClk, SysClk/21
TIM13, TIM14General purpose16bit16bit1SysClk/4SysClk, SysClk/21
TIM6, TIM7Basic16bit16bit0SysClk/4SysClk, SysClk/21

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脚输出映射关系

 TIM1TIM2TIM3TIM9
CH1PA8PA0 PA5 PA15PA6 PB4PA2
CH2PA9PA1 PB3PA7 PB5PA3
CH3PA10PA2 PB10PB0
CH4PA11PA3 PB11PB1
CH1NPB13 PA7


CH2NPB14 PB0


CH3NPB15 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;

推荐阅读

史海拾趣

Acculin Inc公司的发展小趣事

随着电子行业的竞争加剧,Acculin Inc面临着市场份额下降的挑战。为了应对这一变化,公司决定调整战略方向,将重点转向智能穿戴设备市场。通过研发具有独特功能的智能手环和智能手表,Acculin成功吸引了年轻消费者的关注,并在新的市场领域取得了突破。

ACE [ACE Technology Co., Ltd.]公司的发展小趣事

随着电子行业的竞争加剧,Acculin Inc面临着市场份额下降的挑战。为了应对这一变化,公司决定调整战略方向,将重点转向智能穿戴设备市场。通过研发具有独特功能的智能手环和智能手表,Acculin成功吸引了年轻消费者的关注,并在新的市场领域取得了突破。

Desco Tools公司的发展小趣事

Desco Tools公司最初成立于上世纪70年代,专注于防静电工具的研发和生产。当时,随着电子行业的迅速发展,对静电防护的需求日益增加。Desco Tools公司凭借其敏锐的市场洞察力和技术实力,成功开发出了一系列防静电工具,如防静电手套、手腕带等,这些产品迅速在市场上获得了认可,为公司的发展奠定了坚实的基础。

博林(BL)公司的发展小趣事

博林公司自创立以来,一直致力于电子行业的技术创新。公司拥有一支专业的研发团队,不断投入巨资进行技术研发和创新。在某一时期,博林公司成功开发出了一款具有颠覆性的电子产品,凭借其卓越的性能和独特的设计,迅速在市场上获得了广泛认可。这款产品的成功推出,不仅为博林公司带来了巨大的经济效益,也进一步巩固了其在电子行业的领导地位。

Henkel公司的发展小趣事

博林公司自创立以来,一直致力于电子行业的技术创新。公司拥有一支专业的研发团队,不断投入巨资进行技术研发和创新。在某一时期,博林公司成功开发出了一款具有颠覆性的电子产品,凭借其卓越的性能和独特的设计,迅速在市场上获得了广泛认可。这款产品的成功推出,不仅为博林公司带来了巨大的经济效益,也进一步巩固了其在电子行业的领导地位。

Extech公司的发展小趣事

进入新世纪后,Extech公司迎来了新的发展机遇。2000年,公司成为国家第一批863/CIMS系统集成和咨询服务企业,同时也是国家863/CIMS示范应用工程的主流供应商。这一荣誉的获得不仅体现了公司在技术实力和服务能力方面的优势,也为公司未来的发展提供了更广阔的空间。

问答坊 | AI 解惑

三角波发生电路

要求:占空比(上升、下降的斜率)、频率、幅度都可调       方法不限,频率在10KHz到100KHz之间! 请大家多多指教! 这个小工具就当作对大家的报答吧!…

查看全部问答>

BMPtoPCB转换工具软件.rar

本帖最后由 paulhyde 于 2014-9-15 09:16 编辑 BMPtoPCB转换工具软件.rar  …

查看全部问答>

求助:电烙铁故障问题

开始用的时候可以,但是时间久了就不送锡了。 而且等锡融化时,把电烙铁头插入锡中,锡还保持着融合状态。 不知大家有没有好的办法解决?…

查看全部问答>

编译 vxworks http服务器

就在虚拟机上跑. 我的VXWORKS有http服务器的源码, 但是编译时组件树里面没有http的可选组件. 找到http的文件夹下面, 没有makefile, 不知道如何来编译. 有经验的请指教, 帮上忙的加分.…

查看全部问答>

摄相头驱动不了!!!

我有一宏基笔记本, 摄相头是本上原装的, 换了系统之后就不能用了?? 下载官方的驱动软件也驱动不了, 提示“该硬件无法驱动”。 郁闷几个星期了! 在网上搜索相关资料没一个合适的。 请有经验的师傅帮忙解决一下。…

查看全部问答>

请教几个嵌入式linux驱动的问题!谢谢!

写了个键盘驱动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年后他希望能做点不同的事,他感到自己的小圈子限制了自 ...…

查看全部问答>

zstack物理信道冲突的解决办法。

当在同一个空间的多个zigbee网络,如果都使用相同的物理信道,会导致通讯效率下降。 在zstack中,通常的解决办法就是手动修改DEFAULT_CHANLIST这个宏的取值,这个方法需要网络的所有节点重新编译程序,方法本身使用也有一些限制。 zigbee 2007/pr ...…

查看全部问答>

DE2_WEB 哪位大侠用过DE2_WEB这个程序?

我在网上下了一个DE2_WEB的程序,用NIOSII编译不能用过,说是dm9000.c里边有错误,不知道啥原因?…

查看全部问答>