历史上的今天
返回首页

历史上的今天

今天是:2025年03月03日(星期一)

正在发生

2021年03月03日 | 浅谈STM32单片机学习---PWM输出

2021-03-03 来源:eefocus

首先熟悉一下定时器的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函数和全局变量申明,很简单,不作说明


GPIO_InitTypeDef GPIO_InitStructure;


TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;


TIM_OCInitTypeDef TimOCInitStructure;


int main(void)


{


rcc_cfg();


gpio_cfg();


tim2_cfg();


pwm_cfg();


//


while (1)


{


GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET);


delay();


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;


uint16_t TIM_OutputState;


uint16_t TIM_OutputNState;


uint16_t TIM_Pulse;


uint16_t TIM_OCPolarity;


uint16_t TIM_OCNPolarity;


uint16_t TIM_OCIdleState;


uint16_t TIM_OCNIdleState;


} TIM_OCInitTypeDef;


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


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

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


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


推荐阅读

史海拾趣

方向电子公司的发展小趣事

机顶盒,全称为数字视频变换盒,是连接电视机与外部信号源的关键设备,具有高度的专业性和广泛的应用性。从广义上讲,凡是能与电视机连接并处理音视频信号的网络终端设备均可视为机顶盒。它不仅能够接收来自有线电缆、卫星天线、宽带网络及地面广播的数字电视信号,还能通过内置的解码器将这些信号转换为电视机可识别的格式,从而呈现出高清乃至4K的超高清画质,大大提升了观看体验。

机顶盒不仅限于基本的电视信号接收功能,还集成了多种增值服务。例如,它提供电子节目指南,让用户轻松查找和预约节目;支持因特网网页浏览,实现网络购物、在线视频观看、游戏娱乐等多元化互动体验。随着智能化技术的发展,现代机顶盒还融入了语音助手、智能推荐等功能,进一步提升了用户的操作便捷性和内容个性化程度。

在技术层面,机顶盒的发展日新月异,不断向高清化、智能化方向迈进。5G技术的应用更是为机顶盒带来了更快的传输速度和更低的延迟,为用户提供了更为流畅的观影体验。此外,虚拟现实和增强现实技术的融入,也为机顶盒的未来发展打开了新的想象空间,将为用户带来更为沉浸式和互动式的观影享受。

综上所述,机顶盒作为现代家庭娱乐的重要组成部分,不仅极大地丰富了人们的电视观看体验,还通过不断的技术创新和服务升级,满足着用户日益多样化的需求。

DCX-CHOL Enterprises公司的发展小趣事

DCX-CHOL Enterprises成立于一个科技蓬勃发展的时代。公司的创始人是一群热衷于电子技术的年轻人,他们看到市场上对于高性能、低功耗芯片的巨大需求,于是决定自主研发。经过数年的艰苦努力,他们成功开发出了一款具有革命性意义的低功耗芯片,该芯片不仅性能卓越,而且成本远低于同类产品。这一创新成果迅速赢得了市场的认可,DCX-CHOL Enterprises因此获得了第一桶金,为公司后续的发展奠定了坚实的基础。

康龙(CONCRAFT)公司的发展小趣事

康龙(CONCRAFT)公司成立于XXXX年,创始人XXX凭借其敏锐的商业眼光和对电子行业的深入理解,决定专注于电子连接器的研发和生产。公司初始阶段面临资金、技术和市场的重重挑战,但创始人带领团队夜以继日地工作,成功开发出了具有竞争力的连接器产品。随着产品的不断完善和市场推广的加强,康龙逐渐在电子连接器领域崭露头角。

AAEON公司的发展小趣事

康龙(CONCRAFT)公司成立于XXXX年,创始人XXX凭借其敏锐的商业眼光和对电子行业的深入理解,决定专注于电子连接器的研发和生产。公司初始阶段面临资金、技术和市场的重重挑战,但创始人带领团队夜以继日地工作,成功开发出了具有竞争力的连接器产品。随着产品的不断完善和市场推广的加强,康龙逐渐在电子连接器领域崭露头角。

CHENMKO公司的发展小趣事

作为一家有社会责任感的企业,康龙公司始终关注环境保护和可持续发展。公司积极采用环保材料和生产工艺,减少生产过程中的污染和浪费。同时,康龙还积极参与社会公益事业和慈善活动,回馈社会。这些举措不仅提升了公司的品牌形象和社会声誉,也为公司的长远发展注入了正能量。

ENPIRION公司的发展小趣事

随着ENPIRION公司产品的不断推出和市场的逐步扩大,公司开始寻求与各大电子制造商的合作。通过与小米、OPPO、vivo等知名品牌建立合作关系,ENPIRION公司的电源管理解决方案得到了更广泛的应用,进一步提升了其市场影响力和竞争力。这些合作伙伴关系的建立不仅为ENPIRION公司带来了更多的商业机会,也为其技术创新和产品升级提供了更广阔的平台。

问答坊 | AI 解惑

CodeVision AVR 1.25.8 pro

发个AVR的C编译器,第一次发,不知行不行…

查看全部问答>

请问如何开发一个计算机体系结构的实验箱

我们最近要做一个计算机系统结构的实验箱,我不知从何入手。 实验箱主要是这样的, 核心FPGA板 留给用户设计各种cpu , 比如 流水cpu  超标量cpu 等,也就是用来给学生自己开发cpu。 我要做的就是做一个控制部分的电路设计。可以 控制 ...…

查看全部问答>

[请问]怎样在EVC4.0SP4中引用miracl库函数?

我在工程的Header Files里添加了miracl.h和mirdef.h两个头文件,还添加了ms32.lib文件,但在编程中使用otnum()等等miracl库中的函数时仍提示错误。 Repw.obj : error LNK2019: unresolved external symbol \"int __cdecl otnum(struct bigtype *,v ...…

查看全部问答>

关于对嵌入式开发了解的问题

  小弟由于想转入到嵌入式开发的领域中来,所以需要一些最基本的了解,以便有一个更清晰的学习开端,请多多指教:      问题1:在网上查了下嵌入式开发的理解,但越看越晕,一会儿是嵌入式开发,一会儿是linux嵌入 ...…

查看全部问答>

VC开发驱动怎么设置环境啊。急。

想在VC里面写驱动 但是搭建环境完全不会  哪个好心人给我详细说明。。…

查看全部问答>

蓝牙DUN初始化问题

蓝牙新手,想请教下 void DunInit(Task theAppTask, dun_device_type type_of_device, uint16 RI, uint16 DTR, uint16 DCD, uint16 DSR, uint16 priority); 这个DUN初始化函数的参数该如何设定 附上dun.h中的函数说明: /*!     &n ...…

查看全部问答>

求专门为IXP425定制的linux kernel或者patch

求专门为IXP425定制的linux kernel或者patch,或者告诉我下载地址,谢谢了…

查看全部问答>

MSP430中断嵌套机制

(1)430默认的是关闭中断嵌套的,除非你在一个中断程序中再次开总中断EINT。(2)当进入中断程序时,只要不在中断中再次开中断,刚总中断是关闭的,此时来中断不管是比当前中断的优先级高还是低都不执行。(3)若在中断A中开了总中断,刚可以响应 ...…

查看全部问答>

EDA实验与实践 moto_test

module moto_test(clock,key,duty_cycle,pwm_en,pwm_in,motoa,motob,led); input clock;                            //系统时钟(48MHz) input[2:0] ...…

查看全部问答>