历史上的今天
返回首页

历史上的今天

今天是:2025年01月22日(星期三)

正在发生

2021年01月22日 | STM32如何实现可调频率、占空比的PWM波形,且可指定输出脉冲个数?

2021-01-22 来源:eefocus

读者朋友“*imYan*”问:


pwm实现频率可调和占空比可调后怎么来实现输出10个脉冲呢?我这边看有门控或者单脉冲加重复计数,黄老师平时用的什么方法?


我的回答:


使用两个TIM定时器:一个输出可调频率、占空比的PWM,一个对输出PWM脉冲计数(计时)。


1.门控方式能实现,但需要复杂的配置和计算,不推荐。


2.脉冲计数是比较实际,也是比较简单的方式;


对输出PWM脉冲计数(计时)方法有多种:


1.IO中断计数,或同步定时中断计数:用另外一个定时器,按照相同频率中断计数(类似IO中断);


2.由PWM频率和脉冲个数,计算输出全部所需的时间,使用定时中断,关闭输出PWM;


3.利用定时器外部脉冲触发(外部时钟模式2功能),计数个数为所需脉冲个数(10个脉冲),则关闭输出PWM;


ⅠSTM32定时器

STM32的TIM定时器少则五六个,多则二十个。 可能许多初学者觉得:那么多定时器用的完吗? 那么多不是浪费吗?  


这么说吧,STM32的定时器功能非常强大,之所以有那么多定时器,原因在于使用定时器的地方有许多,本文要讲的这个例子只是很基础的一个例子。


当然,可能很多人想问:利用阻塞延时,控制IO高低变化输出PWM这种方式就行啦,也很简单。其实,这种方法的弊端很大。


1.输出的PWM可能存在误差;


2.对整个系统的实时性可能有影响;


所以不建议使用该方法。


Ⅱ几种实现方法

使用两个定时器配合输出可调频率、占空比的PWM波形,且可指定输出脉冲个数的方法和原理其实不难。


输出PWM的方法就是使用TIM定时器自带有的PWM模式即可完成。主要难点在于还要控制指定输出脉冲的个数。


对于如何控制输出指定脉冲个数,下面大概说下三种方法:


1.脉冲中断计数法


IO中断,或者定时器同步(脉冲)中断。


定时器同步(脉冲)中断简单的说,就是利用定时器同时产生一个相同频率(或者说波形)的中断信号,在中断里面对其累计,累加个数为指定输出波形个数则关闭PWM波形的输出,同时关闭中断计数。


比如:我输出10个波形,10次中断(每次+1)之后,关闭输出。


它的原理,大致如下图:

此方法建议在输出高频PWM时不要使用,频繁中断对系统实时性也是有一定影响。 建议低于1KHz的PWM才使用此方法。


2.定时中断法


基于上面第一种,不适合高频PWM脉冲中断。经过思考,我们是否可以将多次中断的时间累加,只响应一次中断。


原理就是把定时的时间设定为单个脉冲的n倍(n个脉冲),只使用一次中断。


它的原理,大致如下图:

看图片中的提示,建议这个地方使用一个32位的定时器,这个值可能很大。


3.脉冲触发法


此方法可以避免上面两种方法中不足的地方, 相对上面两对实用性更强。电路上面,需要将PWM输出的波形,连接到另一个定时器的ETR引脚。


它的原理没什么特殊的,就是和我们常用的定时更新中断类似,只是输入信号改成PWM脉冲波形(默认为内部时钟CK_INT  如:36M)。

下面章节我就以该方法(第3种方法),PWM波形作为定时器的输入时钟的方式,用代码给大家讲述一下。


Ⅲ外部时钟源模式2实现方法

上面说过,使用PWM作为另一个定时器的输入时钟,即可达到对PWM计数的功能。

请参看手册中TIM定时器时钟选择章节。


1.输出PWM配置



/************************************************

函数名称 : PWM_TIM_Configuration

功    能 : PWM输出定时器配置

参    数 : 无

返 回 值 : 无

作    者 : strongerHuang

*************************************************/

void PWM_TIM_Configuration(void)

{

  GPIO_InitTypeDef        GPIO_InitStructure;

  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

  TIM_OCInitTypeDef       TIM_OCInitStructure;


  /* 时钟配置 */

  RCC_APB1PeriphClockCmd(PWM_TIM_CLK, ENABLE);

  RCC_AHB1PeriphClockCmd(PWM_TIM_GPIO_CLK, ENABLE);


  GPIO_InitStructure.GPIO_Pin = PWM_TIM_PIN;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;

  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;

  GPIO_Init(PWM_TIM_GPIO_PORT, &GPIO_InitStructure);


  /* 映射配置 */

  GPIO_PinAFConfig(PWM_TIM_GPIO_PORT, PWM_TIM_SOURCE, PWM_TIM_AF);

  

  /* 时基配置 */

  TIM_TimeBaseStructure.TIM_Prescaler = PWM_PRESCALER;               //预分频值

  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;        //向上计数

  TIM_TimeBaseStructure.TIM_Period = 0xFFFF;                         //定时周期

  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;            //分频因子

  TIM_TimeBaseInit(PWM_TIMx, &TIM_TimeBaseStructure);


  /* PWM模式配置 */

  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;                  //PWM1模式

  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;      //使能输出

  TIM_OCInitStructure.TIM_Pulse = 0xFFFF;                            //脉宽值

  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;          //输出极性

  PWM_TIM_OCxInit(PWM_TIMx, &TIM_OCInitStructure);


  TIM_Cmd(PWM_TIMx, DISABLE);

}


初始化频率和占空比填充的值是最大值,即TIM_Period = 0xFFFF;TIM_Pulse = 0xFFFF; 实际没有使能定时器(输出的配置见下面函数接口)






2.选择外部时钟,定时中断配置



/************************************************

函数名称 : CNT_TIM_Configuration

功    能 : 计时定时器配置

参    数 : 无

返 回 值 : 无

作    者 : strongerHuang

*************************************************/

void CNT_TIM_Configuration(void)

{

  GPIO_InitTypeDef        GPIO_InitStructure;

  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

  NVIC_InitTypeDef        NVIC_InitStructure;


  /* 时钟配置 */

  RCC_APB1PeriphClockCmd(CNT_TIM_CLK, ENABLE);

  RCC_AHB1PeriphClockCmd(CNT_TIM_GPIO_CLK, ENABLE);


  GPIO_InitStructure.GPIO_Pin = CNT_TIM_PIN;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;

  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;

  GPIO_Init(CNT_TIM_GPIO_PORT, &GPIO_InitStructure);

  

  /* 映射配置 */

  GPIO_PinAFConfig(CNT_TIM_GPIO_PORT, CNT_TIM_SOURCE, CNT_TIM_AF);


  /* NVIC配置 */

  NVIC_InitStructure.NVIC_IRQChannel = CNT_TIM_IRQn;

  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = CNT_TIM_Priority;

  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  NVIC_Init(&NVIC_InitStructure);


  /* 使用外部时钟源 */

  TIM_ETRClockMode2Config(CNT_TIMx, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_Inverted, 0); 

 

  /* 时基配置 */

  TIM_TimeBaseStructure.TIM_Prescaler = 0;                           //预分频值

  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;        //向上计数

  TIM_TimeBaseStructure.TIM_Period = 0xFFFF;                         //定时周期

  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;            //分频因子

  TIM_TimeBaseInit(CNT_TIMx, &TIM_TimeBaseStructure);


  TIM_ClearFlag(CNT_TIMx, TIM_FLAG_Update);

  TIM_ITConfig(CNT_TIMx, TIM_IT_Update, ENABLE);                     //使能"更新"中断


  TIM_Cmd(CNT_TIMx, DISABLE);

}


和常规的不同点在于: 使用外部时钟源


TIM_ETRClockMode2Config(CNT_TIMx, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_Inverted, 0);


注意检测(捕获)极性TIM_ExtTRGPolarity_Inverted,一般PWM都是高电平为脉冲波形,下降沿才算一个波形的计数。


3.输出PWM函数接口



/************************************************

函数名称 : PWM_Output

功    能 : 输出PWM

参    数 : Frequency --- 频率

            Dutycycle --- 占空比(12代表占空比为12%)

            NumPulse  --- 脉冲个数

返 回 值 : 无

作    者 : strongerHuang

*************************************************/

void PWM_Output(uint32_t Frequency, uint32_t Dutycycle, uint32_t NumPulse)

{

  uint32_t pwm_period;

  uint32_t pwm_pulse;


  /* 输出PWM */

  pwm_period = PWM_CK_CNT/Frequency - 1;                             //计算出计数周期(决定输出的频率)

  pwm_pulse  = (pwm_period + 1)*Dutycycle / 100;                     //计算出脉宽值(决定PWM占空比)


  TIM_Cmd(PWM_TIMx, DISABLE);                                        //失能TIM

  TIM_SetCounter(PWM_TIMx, 0);                                       //计数清零

  TIM_SetAutoreload(PWM_TIMx, pwm_period);                           //更改频率

  PWM_TIM_SetComparex(PWM_TIMx, pwm_pulse);                          //更改占空比

  TIM_Cmd(PWM_TIMx, ENABLE);                                         //使能TIM


  /* 脉冲个数计时 */

  TIM_Cmd(CNT_TIMx, DISABLE);

  TIM_SetCounter(CNT_TIMx, 0);

  TIM_SetAutoreload(CNT_TIMx, NumPulse-1);                           //设置中断更新数

  TIM_ClearFlag(CNT_TIMx, TIM_FLAG_Update);

  TIM_Cmd(CNT_TIMx, ENABLE);

}


void PWM_Output(uint32_t Frequency, uint32_t Dutycycle, uint32_t NumPulse);


我们只需要调用该函数接口就可以实现指定个数PWM输出了。中途不用软件参数,输出结束时自动响应定时中断,关闭定时器。




中断接口函数



/************************************************

函数名称 : CNT_TIM_IRQHandler

功    能 : 计时中断

参    数 : 无

返 回 值 : 无

作    者 : strongerHuang

*************************************************/

void CNT_TIM_IRQHandler(void)

{

  if(TIM_GetITStatus(CNT_TIMx, TIM_IT_Update) != RESET)

  {

    TIM_ClearITPendingBit(CNT_TIMx, TIM_IT_Update);


    TIM_Cmd(PWM_TIMx, DISABLE);                  //关闭PWM输出

    TIM_Cmd(CNT_TIMx, DISABLE);                  //关闭计数

  }

}


Ⅳ实际效果和代码

为方便大家,提供了一个简单裸机程序:



int main(void)

{

  System_Initializes();  while(1)

  {

    LED_TOGGLE();                 //LED变化

    Delay(5);                     //延时(约240ms)


    PWM_Output(1000, 20, 10);     //1KHz, 20%占空比, 10个脉冲

  }

}




main函数中实现效果:间隔240ms(软件延时不精确)输出10个PWM波形

波形具体情况:输出1KHz, 20%占空比, 10个脉冲精确的PWM波形

下载地址(STM32F401为例工程,STM32其他芯片类似):


链接:https://pan.baidu.com/s/10GPPxCky8SZmU9S9pleqJg 


密码:4jf3


推荐阅读

史海拾趣

ACCUTEK公司的发展小趣事

ACCUTEK公司深知人才是企业发展的核心力量。因此,公司始终注重人才培养和团队建设。公司建立了完善的人才培养和激励机制,鼓励员工不断学习和创新。同时,公司还注重营造良好的工作氛围和团队文化,让员工能够在轻松愉快的环境中工作。这些举措不仅提升了员工的归属感和忠诚度,也为公司的持续发展提供了有力的人才保障。

这些故事基于电子行业的一般发展规律和可能的企业发展路径构建,并不代表ACCUTEK公司的真实历史。如需了解该公司的真实发展情况,建议查阅相关资料或访问其官方网站。

Hoffman_Enclosures__Inc.公司的发展小趣事

Hoffman在追求企业发展的同时,也积极履行社会责任,致力于实现可持续发展。公司注重环保和节能,不断推出绿色、低碳的产品和技术。同时,Hoffman还积极参与公益事业,通过捐款、志愿服务等多种方式回馈社会。这些举措不仅提升了公司的社会形象,也促进了企业与社会的和谐共生。

以上五个故事展示了Hoffman Enclosures, Inc.在电子行业中的发展历程和关键节点,每个故事都体现了公司在技术创新、市场拓展、产品定制、标准制定以及社会责任等方面的努力和成就。

BETA Transformer Technology Corp公司的发展小趣事

为了进一步扩大市场份额,BETA Transformer Technology Corp公司开始实施国际化战略。公司积极参加国际电子展览,与国际同行进行深入交流与合作。同时,BETA公司还在海外设立了研发中心和生产基地,以便更好地服务全球客户。通过不断拓展国际市场,BETA公司的品牌影响力和市场竞争力得到了显著提升。

American Power Design Inc公司的发展小趣事

随着国内市场的饱和,APDI决定实施国际化战略,以拓展海外市场。公司首先在欧洲设立了研发中心,与当地高校和研究机构合作,共同开发适应欧洲市场的电子产品。随后,APDI又在亚洲建立了生产基地,利用当地的低成本优势和高效的供应链管理,进一步降低了产品成本,提高了市场竞争力。

Danaher Corporation公司的发展小趣事

丹纳赫集团起源于1984年,由Steven Rales和Mitchell Rales兄弟二人创立。他们的愿景是建立一个致力于持续改善并提升客户满意度的制造公司。名字“Dana”源自古老的凯尔特语,寓意奔腾不息的精神和不断改善的品质。起初,丹纳赫集团并未直接涉足电子行业,但随着公司的发展,逐渐将业务范围扩展至包括电子设备和技术的领域。

Dearborn Electronics Inc公司的发展小趣事

随着技术的不断进步,Dearborn Electronics Inc.不断投入研发,推出了一系列具有创新性的产品,如钽电容、陶瓷电容器等。这些产品以其卓越的性能和可靠性赢得了市场的广泛认可。同时,公司也积极拓展市场,将产品销售到全球各地,特别是医疗、石油、交通、工业等严苛应用领域。

问答坊 | AI 解惑

国内外高端频率控制器件的技术比较

0 引言  近年来,通信业频率源的大量需求以及军工方面对频率源广泛应用,加之精密导航定位对参考时间的依赖,作为程控交换设备的一级铷原子频标的价格和体积都逐渐接近了高稳定度振荡器。另一方面目前市场用量最大的各种石英晶体谐振器和振荡器的 ...…

查看全部问答>

新手学习嵌入式从那里入手好呢??

我是学习电气自动化的应届毕业生,想在毕业后在嵌入式方向发展,但是我在学校只学过80c51单片机和c语言,请各位高手指教一下!我应该从那里入手学习嵌入式呢,谢谢…

查看全部问答>

MSP430开发环境问题

最近弄了个MSP430的开发板,由于我电脑上没并口所以装了个并口扩展卡,在IAR Embeded  Workbench开发环境编译在线调试时总提示设备未连接。重装了下扩展卡驱动还是不行,下载线好像都没问题,弄了好半天确实不知道哪出问题了,还请哪位高 ...…

查看全部问答>

光电检测试验资料

本帖最后由 paulhyde 于 2014-9-15 09:14 编辑 光电测量技术实验指导书  …

查看全部问答>

cc1101 + 单片机 还是cc2430

cc1101 + 单片机 的成本要低于 cc2430不少 请问,我是要在10米内隔几分钟发送一个数字 这两种方案,哪个更好?…

查看全部问答>

在today界面显示标签

我想在today界面的上面部分,就是在显示电量、信号量的地方,添加一个label控件,用于显示当前的温度,怎么才能做到? 谢谢各位的指教…

查看全部问答>

wince快捷方式

正在搞一个放在SD卡上面的AP,每次路径访问很麻烦,能否搞一个快捷方式从桌面上面直接启动? 我查了下网上写的,18#/windows/**.exe的方法,但是系统一直报SD卡的路径不对,各位有没有搞过一个快捷方式能直接启动放在SD上的程序? 多谢多谢!…

查看全部问答>

关于uc/OSII的调试?用什么编译器!

关于uc/OSII的调试?用什么编译器! ___最近在学uc/OSII,要用到BC4.52,但很难下载到,下到的不是IDE,不习惯用命令行方式,请问各位,还有其它更好的编译器可用来调试uc/OSII吗?谢谢!…

查看全部问答>

9263WINCE5.0下SD卡驱动的问题

9263板跑WINCE5,使用SD卡时,时常从串口输出: WaitForSingleObject == WAIT_TIMEOUT (1000) StatusMask = 0x20 status 0xc0c5 这串数据重复输出,并且SD卡读写速度变得非常之慢。 查程序发现是PLATFOM\\COMMON\\SRC\\ARM\\ATMEL\\AT91SAM926 ...…

查看全部问答>