历史上的今天
返回首页

历史上的今天

今天是:2024年09月29日(星期日)

正在发生

2019年09月29日 | stm32库函数学习篇----通用定时器(PWM功能)

2019-09-29 来源:eefocus

上午花了半天时间熟悉了stm32的PWM模块。中午利用午饭时间把PWM功能调试成功。当然,很简单的东西,也许很多前辈估计都不屑一顾的东西。


今天最大的感叹就是网络资源实在是个巨大的宝库,真的很庆幸,在这个复杂的社会环境里,在一个到处充斥着私心、私利的时代,各个网站,各个论坛上的众多网友都时刻保持着开源的氛围。学习一定要和他人交流,而网络提供了这么一个极好的平台。


废话少说,言归正传。

实现功能:采用定时器2的通道2,使PA1输出频率1K,占空比40的PWM波形,用PA8随意延时取反led灯,指示程序运行。


首先熟悉一下定时器的PWM相关部分。看图最明白

其实PWM就是定时器的一个比较功能而已。

CNT里的值不断++,一旦加到与CCRX寄存器值相等,那么就产生相应的动作。这点和AVR单片机很类似。既然这样,我们要产生需要的PWM信号,就需要设定PWM的频率和PWM的占空比。


首先说频率的确定。由于通用定时器的时钟来源是PCLK1,而我又喜欢用固件库的默认设置,那么定时器的时钟频率就这样来确定了,如下:

AHB(72MHz)→APB1分频器(默认2)→APB1时钟信号(36MHz)→倍频器(*2倍)→通用定时器时钟信号(72MHz)。


这里为什么是这样,在RCC模块学习记录里有详细记载,不多说。

因此图中的CK_PSC就是72MHz了。

下面的资料也是网上一搜一大把,我就罗列了:

STM32的PWM输出有两种模式,模式1(PWM1)和模式2(PWM2),由TIMx_CCMRx寄存器中的OCxM位确定的(“110”为模式1,“111”为模式2)。模式1和模式2的区别如下:

110:PWM模式1-在向上计数时,一旦TIMx_CNT=TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。


111:PWM模式2-在向上计数时,一旦TIMx_CNT=TIMx_CCR1时通道1为有效电平,否则为无效电平。


由此看来,模式1和模式2正好互补,互为相反,所以在运用起来差别也并不太大。我用的是模式一,因此后面的设定都是按照模式一来设定的。

 

PWM的周期是就是由定时器的自动重装值和CNT计数频率决定的。而CNT的计数时钟是CK_PSC经分频器PSC得到,因此CNT的时钟就是CK_PSC/分频系数。这个分频系数在TIM_TimeBaseStructure.TIM_Prescaler确定。我设置的值是72,因此CNT的计数频率也就是CK_CNT的频率为1MHz。


下一步就是确定定时器自动重装值。因为CNT每自加到ARR寄存器的值时就会自动清零,当然前提是设定为为向上计数模式,而就是根据这个溢出事件来改变PWM的周期。所以PWM信号的频率由ARR的值来确定。我设置的值是1000-1,即TIM_TimeBaseStructure.TIM_Period = 1000-1;因此PWM的周期是1MHz/1000=1KHz。

接下来就要确定PWM的占空比了。因为CNT在自加到ARR值的过程中会不断和CRRX的值相比较,一旦二者相等就产生匹配事件,但要注意CNT不会理会这件事,它会继续++直到等于ARR。而CRRX的值我设定为400-1,那么占空比就随之确定为40%。


好了,下面就是库函数的配置了。

TIMER输出PWM实现步骤

1.       设置RCC时钟;

2.       设置GPIO;

3.       设置TIMx定时器的相关寄存器;

4.       设置TIMx定时器的PWM相关寄存器。

 

首先是main函数和全局变量申明,很简单,不作说明

/* Private variables ---------------------------------------------------------*/
GPIO_InitTypeDef GPIO_InitStructure;

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

TIM_OCInitTypeDef TimOCInitStructure;

int main(void)
{
  /*!< At this stage the microcontroller clock setting is already configured,
       this is done through SystemInit() function which is called from startup
       file (startup_stm32f10x_xx.s) before to branch to application main.
       To reconfigure the default setting of SystemInit() function, refer to
       system_stm32f10x.c file
     */    

 

  /* Add your application code here
     */

      rcc_cfg();
      gpio_cfg();
      tim2_cfg();
      pwm_cfg();
//  /* Infinite loop */
  while (1)
  {
    /* Set PA8 and PD2 */
    GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET);
 
 delay();

    /* Reset PA8 and PD2 */
    GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_RESET);
 
    delay();
  }
}

 

下面是IO口的配置:

void gpio_cfg()
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
 
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
}

此处要注意的是PWM输出口要配置为复用推挽输出,原因我也不知道,反正照搬就是了。

 

 

下面是TIM配置函数,注释很清楚了,不作说明:

void tim2_cfg()
{
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

  TIM_DeInit(TIM2);
  TIM_InternalClockConfig(TIM2);
  //预分频系数为72,这样计数器时钟为72MHz/72 = 1MHz
  TIM_TimeBaseStructure.TIM_Prescaler = 72;
  //设置时钟分割
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  //设置计数器模式为向上计数模式
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  //设置计数溢出大小,每计1000个数就产生一个更新事件
  TIM_TimeBaseStructure.TIM_Period = 1000-1;
  //将配置应用到TIM2中
  TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);

  //禁止ARR预装载缓冲器
  TIM_ARRPreloadConfig(TIM2, DISABLE);
 
  TIM_Cmd(TIM2, ENABLE);  //使能TIMx外设
}

 

 

接下来是关键的PWM的配置函数:

void pwm_cfg()

{

      //设置缺省值

       TIM_OCStructInit(&TimOCInitStructure);

       //PWM模式1输出

       TimOCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;

       //设置占空比,占空比=(CCRx/ARR)*100%或(TIM_Pulse/TIM_Period)*100%

       TimOCInitStructure.TIM_Pulse = 400-1;

       //TIM输出比较极性高

       TimOCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

       //使能输出状态

       TimOCInitStructure.TIM_OutputState = TIM_OutputState_Enable;

       //TIM2的CH2输出

       TIM_OC2Init(TIM2, &TimOCInitStructure);

       //设置TIM2的PWM输出为使能

       TIM_CtrlPWMOutputs(TIM2,ENABLE);

}

stm32固件库的输出比较单元结构体与定时器的时基单元是分开定义的,而PWM模式只是输出比较结构体成员TimOCInitStructure.TIM_OCMode的一个取值,当把此结构体填充完后,还要映射到某个定时器,用TIM_OCXInit函数实现,我用了一个X,说明不止一个这样的函数,事实上,stm32的通用定时器都有四个通道,每个通道对应一个初始化函数,这里真够纠结的!最后还要使能该定时器的PWM输出功能,TIM_CtrlPWMOutputs(TIM2,ENABLE)函数要注意,是outputs而不是output,说明TIM2不止一个通道嘛!够复杂,够繁琐的!

下面是输出比较单元的结构体原型:

typedef struct
{
  uint16_t TIM_OCMode;        /*!< Specifies the TIM mode.
                                   This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */

  uint16_t TIM_OutputState;   /*!< Specifies the TIM Output Compare state.
                                   This parameter can be a value of @ref TIM_Output_Compare_state */

  uint16_t TIM_OutputNState;  /*!< Specifies the TIM complementary Output Compare state.
                                   This parameter can be a value of @ref TIM_Output_Compare_N_state
                                   @note This parameter is valid only for TIM1 and TIM8. */

  uint16_t TIM_Pulse;         /*!< Specifies the pulse value to be loaded into the Capture Compare Register.
                                   This parameter can be a number between 0x0000 and 0xFFFF */

  uint16_t TIM_OCPolarity;    /*!< Specifies the output polarity.
                                   This parameter can be a value of @ref TIM_Output_Compare_Polarity */

  uint16_t TIM_OCNPolarity;   /*!< Specifies the complementary output polarity.
                                   This parameter can be a value of @ref TIM_Output_Compare_N_Polarity
                                   @note This parameter is valid only for TIM1 and TIM8. */

  uint16_t TIM_OCIdleState;   /*!< Specifies the TIM Output Compare pin state during Idle state.
                                   This parameter can be a value of @ref TIM_Output_Compare_Idle_State
                                   @note This parameter is valid only for TIM1 and TIM8. */

  uint16_t TIM_OCNIdleState;  /*!< Specifies the TIM Output Compare pin state during Idle state.
                                   This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State
                                   @note This parameter is valid only for TIM1 and TIM8. */
} TIM_OCInitTypeDef;

其中没有加色的成员是高级定时器才有的,通用定时器就不用管了。

这里还有个TimOCInitStructure.TIM_OCPolarity 成员需要注意,它有什么作用呢?在网上查的资料,如下图:

前面说到pwm有pwm1和pwm2两种模式,这两种模式只能控制到OCXREF为止,TIM_OCPolarity 能控制OC1是直接等于OCXREF,还是取反极性!OC1才是最终的PWM信号。


这里有个小插曲,我用示波器去测量PWM信号,发现信号居然是双极性的,然后改变TIM_OCPolarity ,再测,还是双极性,只是倒了个跟头。还真以为stm32单片机能输出两极性的PWM,后面把示波器改为直流档(之前用的是交流档),波形才从零电位一下纵向移上去。以后要注意!

推荐阅读

史海拾趣

Electroswitch公司的发展小趣事

在80年代,Electroswitch迎来了一个重要的技术突破期。公司成功开发了一系列符合MIL规格的旋转开关,这些产品凭借其坚固耐用和高品质的特性,迅速赢得了市场的认可。同时,Electroswitch还积极适应新的生产线,不断扩大在工业和电气实用市场的份额。此外,公司还成功收购了西屋电气开关部,进一步增强了其在电力行业中的竞争力。

得倍(DBIC)公司的发展小趣事

倍(DBIC)公司自创立之初,就致力于在电子行业中进行技术创新。公司投入大量资源进行研发,成功推出了一系列具有革命性的产品,如高效能芯片和智能传感器。这些产品不仅提高了生产效率,还满足了市场对智能化、高效能电子产品的需求。随着技术的不断进步,倍(DBIC)公司逐渐在行业中树立了技术领先的形象,吸引了众多合作伙伴和客户。

驰芯微(CHIPWISE)公司的发展小趣事

在发展过程中,驰芯微公司成功完成了多轮融资,为公司的发展提供了强有力的资金支持。这些资金主要用于车规级传感和控制类芯片的系列化业务以及新能源电池管理系统汽车电子系统一站式车规级芯片套片解决方案的研发和推广。未来,驰芯微将继续加大在技术研发和产品创新方面的投入力度,致力于成为国际领先的汽车电子芯片供应商。同时,公司还将积极拓展海外市场,提升品牌知名度和影响力。

以上五个故事框架分别围绕驰芯微公司的初创历程、技术突破、面对“芯片限制”的机遇与挑战、与明然科技的合作以及融资与未来规划等方面进行了描述。这些故事展示了驰芯微在电子行业中的发展历程和取得的成就,同时也揭示了公司未来的发展方向和目标。

Astro Tool Corp公司的发展小趣事

在市场竞争日益激烈的背景下,Astro Tool Corp公司始终坚持以创新驱动发展。公司不断投入研发资金,引进先进技术和设备,推出了一系列具有创新性和竞争力的新产品。这些产品不仅满足了客户日益多样化的需求,也为公司带来了可观的利润。同时,公司还注重人才培养和团队建设,打造了一支高效、专业的研发团队,为公司的持续发展提供了有力保障。

港源(GANGYUAN)公司的发展小趣事

随着技术的不断成熟和市场的逐步认可,Astro Tool Corp公司开始积极拓展海外市场。通过与国际知名电子制造商建立合作伙伴关系,公司成功将产品打入国际市场。同时,公司还积极参与国际行业展会和交流活动,不断提升品牌知名度和影响力。这些努力不仅为公司带来了更多的订单和合作伙伴,也进一步巩固了其在全球电子制造工具市场的地位。

DAYA公司的发展小趣事

在快速变化的电子市场中,大雅智能始终保持敏锐的市场洞察力。通过深入研究市场需求和消费者行为,大雅智能准确把握市场趋势,及时调整产品策略。例如,在智能家居市场兴起之际,大雅智能迅速推出了一系列智能家居产品,成功抢占了市场先机。这种对市场趋势的敏锐洞察和快速响应能力,让大雅智能在电子行业中不断取得新的突破。

问答坊 | AI 解惑

在Open at中如何获得手机号码

我用open at写一个程序,记录拨进来的电话号码和呼叫记录.... 请问怎么获得对方的电话号码和呼叫时间????…

查看全部问答>

wince 6.0 的label中文换行问题

在WINCE 6.0系统中,运行.NET编写的C#应用程序.发现Label控件的的字很多时候需要换行时候,英文可以自动换行,而中文只能显示一行字,其它字被截断,不能显示.采用容器进行FILL仍然不行,请高手帮忙解决!…

查看全部问答>

求购vxworks 6.6 Bsp for atmel sam9260

那位手头有vxworks 6.6 Bsp for atmel sam9260的?并能提供技术支持的,留个联系方式好吧。谢谢…

查看全部问答>

wince s3c2440使用COM1读写都很正常,但是COM3、COM4不行

COM3、COM4是可以打开的。但是发送数据异常,没有看到数据输出。一开始是死在了WriteFile上面,后来我加入了超时间隔等参数,可以返回,但是写数据就失败了。有谁遇到过这样的问题吗?…

查看全部问答>

BootLoader 创建一个构建环境问题

C:\\Documents and Settings\\Administrator>SET _WINCEROOT=C:\\WINCE600 C:\\Documents and Settings\\Administrator>CD %_WINCEROOT%\\PUBLIC\\COMMON\\OAK\\MISC C:\\WINCE600\\PUBLIC\\COMMON\\OAK\\MISC>WINCE.BAT _WINCEDRIVE=C: C:\\WI ...…

查看全部问答>

NMAKE : U1073: don't know how to make

WinCE5.0 生成系统时出现: BUILD: [01:0000000913:ERRORE] NMAKE :  U1073: don\'t know how to make \'D:\\WINCE500\\platform\\common\\lib\\ARMV4I\\retail\\oal_memory_s3c2440a.lib\' BUILD: [01:0000000915:ERRORE] NMAKE.EXE ...…

查看全部问答>

请教一个硬件设计问题

现在有五个数,FF C0 00 C0 FF,在第一数(FF)的下降沿开始进行计时,到第五个下降 沿停止,现在想判断从开始计时到停止计时,所花的时间是不是小于5ms,现在有计时芯 片,示波器,不知道怎么办,怎么才能知道发生的时间…

查看全部问答>

fpga 的fifo制作

有没有人懂得怎么样用fpga开发板用vhdl语言做一个48进32出的fifo?有相关资料也行!大家帮帮忙咯!感激不尽! [ 本帖最后由 hefeifei1990 于 2010-12-22 16:14 编辑 ]…

查看全部问答>

FPGA利用DSP中的FLASH?

如题,一般FPGA都要使用外带的FLASH芯片存放程序,为节约成本不知道可不可以使用同一块板上的DSP内部FLASH存放程序,各位高人都来讨论讨论…

查看全部问答>

帮忙看下我写这个程序有什么不对的地方

帮忙看下我写这个程序有什么不对的地方 现在的情况是下载到单片机上就第一个数码管显示一个零,然后就停在那里 单片机原理图在这里http://wenku.baidu.com/view/0fc085f5f90f76c661371a1e.html #include #define uint unsigned int #defin ...…

查看全部问答>