完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
背景
现在的主流MCU都支持硬件PWM输出,以STM32F103为例,通用定时器可以支持4路占空比可调的PWM输出,高级定时器可以支持4路带互补输出的PWM输出。硬件产生PWM,具有稳定可靠、执行效率高的特点。 但是,硬件产生的PWM也有一些限制,例如:1.输出引脚位置固定,PCB连线可能会不方便;2.输出引脚的数量有限,在一些需要多通道输出的应用中(如多路控温)会占用过多定时器。 虚拟PWM库特性 由于项目需要,笔者编写了VirPwm库,以实现对任意GPIO的产生PWM的驱动。它具有如下特性: 资源占用小。只需要在一个定时器(软件定时器、硬件定时器均可)周期性的调用 void VirPwm_TimIRQHandler(VirPwm *VirPwmDef)即可。 可移植性好。本库是基于STM32F103开发的,但是充分考虑了移植到其他平台上的可能性。通过为VirPwmDef->SetOut函数指针注册端口输出函数以及修改void VirPwm_SetFreq(VirPwm *VirPwmDef, uint16_t freq)的实现,可以顺利实现移植。 灵活性强。由于开发了端口设置函数指针typedef void (*VirPwm_SetOutput)(uint8_t state),使用者可以自由编写函数(如同时驱动多路GPIO输出PWM)作为回调。 一个定时器可以驱动多路占空比不同的PWM。通过结构体VirPwm抽象了虚拟PWM,定义不同的结构体变量,设置它们的不同的占空比,在定时器的更新中断中周期性的调用void VirPwm_TimIRQHandler(VirPwm *VirPwmDef)函数,并依次传入不同的结构体变量参数,即可实现驱动频率相同、占空比不同的多组PWM输出。 源码介绍 头文件 virtual_pwm.h /* * virtual_pwm.h * * Created on: 2020年8月12日 * Author: Tao */ #ifndef LIBRARIES_SYSEXTEND_INC_VIRTUAL_PWM_H_ #define LIBRARIES_SYSEXTEND_INC_VIRTUAL_PWM_H_ #include "stm32f10x.h" #include "stm32f10x_conf.h" #ifdef USE_VIRTUAL_PWM /** * VirPwm_SetOutput是函数指针类型变量,用来回调端口输出函数 */ typedef void (*VirPwm_SetOutput)(uint8_t state); /** * 虚拟PWM的结构体类型,通过此变量类型可以完整的定义一个虚拟PWM,以供本库的函数操作 */ typedef struct{ uint8_t Status; uint8_t Idle; uint16_t Frequency; uint16_t DutyCycle; TIM_TypeDef *Basetimer; VirPwm_SetOutput SetOut; } VirPwm; /** * 与平台相关, 操作GPIO的宏定义 */ #define DIG_OUTPUT_1 GPIOA_OUT(5) extern VirPwm VirPwmDef; void VirPwm_Init(VirPwm *VirPwmDef,uint16_t freq,uint16_t dutyCycle,uint8_t idle, VirPwm_SetOutput setOutHandler); void VirPwm_SetStatus(VirPwm *VirPwmDef, uint8_t status); void VirPwm_SetFreq(VirPwm *VirPwmDef, uint16_t freq); void VirPwm_SetDutyCycle(VirPwm *VirPwmDef, uint16_t dutyCycle); void VirPwm_SetIdle(VirPwm *VirPwmDef, uint8_t idle); void VirPwm_TimIRQHandler(VirPwm *VirPwmDef); void VirPwm_SetOutHandler(uint8_t state); #endif #endif /* LIBRARIES_SYSEXTEND_INC_VIRTUAL_PWM_H_ */ 源文件 virtual_pwm.c /* * virtual_pwm.c * * Created on: 2020年8月12日 * Author: Tao */ #include "virtual_pwm.h" #ifdef USE_VIRTUAL_PWM /** * 使用虚拟PWM库时,应当在他处配置好定时器(由宏定义VIRPWM_TIMER指定), * 并在该定时器的中断服务函数中调用void VirPwm_TimIRQHandler()。 * 同时,应该在外部为void (*VirPwm_SetOutputHandler)(uint8_t state)注册一个函数, * 以实现端口的电平翻转操作。 */ VirPwm VirPwmDef = { .Status = 0, .Idle = 0, .Frequency = 100, .DutyCycle = 50, .Basetimer = TIM4, .SetOut = VirPwm_SetOutHandler, .count_P = 0, .count_N = 100 }; /** * @brief 完成初始化VirPwmDef结构体指针 * 也可以直接在定义结构体的时候完成初始化,而不必调用本函数 * @param VirPwm: 虚拟PWM的定义结构体指针 * @param freq: 虚拟PWM的频率 * @param dutyCycle: 虚拟PWM的占空比 * @param idle: 虚拟PWM的空闲状态 */ void VirPwm_Init(VirPwm *VirPwmDef,uint16_t freq,uint16_t dutyCycle,uint8_t idle) { VirPwm_SetFreq(VirPwmDef,freq); VirPwm_SetDutyCycle(VirPwmDef, dutyCycle); VirPwm_SetIdle(VirPwmDef, idle); } /** * @brief 设置虚拟PWM的工作状态 * @param VirPwm: 需要操作的虚拟PWM * @param status: 0, disable; 1, enable */ void VirPwm_SetStatus(VirPwm *VirPwmDef, uint8_t status) { if(status != 0) { VirPwmDef->Status = 1; } else { VirPwmDef->Status = 0; } //设置驱动虚拟PWM的定时器更新中断周期 VirPwm_SetFreq(VirPwmDef, VirPwmDef->Frequency); //开启或关闭驱动虚拟PWM的定时器 TIM_Cmd(VirPwmDef->Basetimer, VirPwmDef->Status); //关闭虚拟PWM之后,将输出电平恢复为idle状态 if(VirPwmDef->Status == 0) { if(VirPwmDef->Idle != 0) { VirPwmDef->SetOut(1); } else { VirPwmDef->SetOut(0); } } } /** * @brief 设置虚拟PWM的频率 * 虚拟PWM的频率*100即为定时器的更新频 * @param VirPwm: 需要操作的虚拟PWM * @param freq: frequency of the virtual pwm, ranged from 1~1000 Hz * 由于虚拟PWM拥有较低的效率,占用较多的CPU时间,因此不能设置较高的工作频率,否则会影响系统的正常工作。 */ void VirPwm_SetFreq(VirPwm *VirPwmDef, uint16_t freq) { //0~100 duty cycle uint32_t timer_freq; //Range of frequency is 1 to 1000. if (freq > 1000) { freq = 1000; } if(freq < 1) { freq = 1; } VirPwmDef->Frequency = freq; timer_freq = VirPwmDef->Frequency*100; //100~100k //timer_freq < 1k, psc = 7200, f = 10kHz 100*100 Hz (freq < 10) if(timer_freq<1000) { VirPwmDef->Basetimer->PSC = 7200 - 1; } //timer_freq < 10k, psc = 720, f = 100kHz 1k*100 Hz (freq < 100) else if(timer_freq <10000) { VirPwmDef->Basetimer->PSC = 720 - 1; } //timer_freq < 100k, psc = 72, f = 1MHz 10k*100 Hz (freq < 1000) else { VirPwmDef->Basetimer->PSC = 72 - 1; } //Set the update frequency of the timer. VirPwmDef->Basetimer->ARR = 72000000.0 / (VirPwmDef->Basetimer->PSC + 1) / timer_freq - 1; } /** * @brief 设置虚拟PWM的占空比 * @param VirPwm: 需要操作的虚拟PWM * @param dutyCycle: duty cycle of the virtual pwm, ranged from 1~100 (%). * 由于性能限制,虚拟PWM支持的占空比分辨率为1%,范围为0~100%。 */ void VirPwm_SetDutyCycle(VirPwm *VirPwmDef, uint16_t dutyCycle) { //Range of duty cycle is 0 to 100. if (dutyCycle > 100) { dutyCycle = 100; } VirPwmDef->DutyCycle = dutyCycle; } /** * @brief 设置虚拟PWM的极性 * @param VirPwm: 需要操作的虚拟PWM * @param idle: 虚拟PWM在空闲状态下的电平 */ void VirPwm_SetIdle(VirPwm *VirPwmDef, uint8_t idle) { if(idle != 0) { VirPwmDef->Idle = 1; } else { VirPwmDef->Idle = 0; } } /** * @brief 在定时器中断中周期性调用 * @param VirPwm: 需要操作的虚拟PWM */ void VirPwm_TimIRQHandler(VirPwm *VirPwmDef) { //占空比为0,则直接关闭PWM输出,将端口设置为idle状态 if(VirPwmDef->DutyCycle == 0) { if(VirPwmDef->Idle != 0) { VirPwmDef->SetOut(1); } else { VirPwmDef->SetOut(0); } return; } //占空比为100,则直接打开PWM输出,将端口设置为busy状态 if(VirPwmDef->DutyCycle >= 100) { if(VirPwmDef->Idle != 0) { VirPwmDef->SetOut(0); } else { VirPwmDef->SetOut(1); } return; } //占空比为1~99时,正常处理 if(VirPwmDef->count_P == 0) { if(VirPwmDef->count_N == 0) { VirPwmDef->count_P = VirPwmDef->DutyCycle; VirPwmDef->count_N = 100 - VirPwmDef->DutyCycle; VirPwmDef->SetOut(1); } else { VirPwmDef->count_N--; VirPwmDef->SetOut(0); } } else { VirPwmDef->count_P--; VirPwmDef->SetOut(1); } } /** * @brief 注册为实现端口输出SetOut的服务函数(与具体项目相关) * @param VirPwm: 需要操作的虚拟PWM */ void VirPwm_SetOutHandler(uint8_t state) { DIG_OUTPUT_1 = state; } #endif 使用说明 需要设置不同占空比的PWM应当分别定义一个VirPwm结构体变量: VirPwm VirPwmDef1, VirPwmDef2; 1 建议在声明的时候直接初始化这些结构体变量,如: VirPwm VirPwmDef1 = { .Status = 0, .Idle = 0, .Frequency = 100, .DutyCycle = 50, .Basetimer = TIM4, .SetOut = VirPwm_SetOutHandler1 .count_P = 0, .count_N = 100 }; VirPwm VirPwmDef2 = { .Status = 0, .Idle = 0, .Frequency = 100, .DutyCycle = 75, .Basetimer = TIM4, .SetOut = VirPwm_SetOutHandler2 .count_P = 0, .count_N = 100 }; 也可以使用void VirPwm_Init(VirPwm *VirPwmDef,uint16_t freq,uint16_t dutyCycle,uint8_t idle), void VirPwm_SetFreq(VirPwm *VirPwmDef, uint16_t freq), void VirPwm_SetDutyCycle(VirPwm *VirPwmDef, uint16_t dutyCycle), void VirPwm_SetIdle(VirPwm *VirPwmDef, uint8_t idle)去设置这些结构体变量的成员。 需要特别注意的是void VirPwm_SetFreq(VirPwm *VirPwmDef, uint16_t freq)函数不仅改变结构体变量的Frequency成员的值,同时也设定了驱动定时器的更新频率。当然只要正确初始化了虚拟PWM的结构体变量,在void VirPwm_SetStatus(VirPwm *VirPwmDef, uint8_t status)函数中打开或关闭PWM输出总开关的时候,其内部已经调用了一次void VirPwm_SetFreq(VirPwm *VirPwmDef, uint16_t freq)函数以完成驱动定时器更新频率的刷新: void VirPwm_SetStatus(VirPwm *VirPwmDef, uint8_t status) { /*此处省略了无关内容*/ //设置驱动虚拟PWM的定时器更新中断周期 VirPwm_SetFreq(VirPwmDef, VirPwmDef->Frequency); //开启或关闭驱动虚拟PWM的定时器 TIM_Cmd(VirPwmDef->Basetimer, VirPwmDef->Status); /*此处省略了无关内容*/ } 如果PWM结构体的频率相同,可以共同使用同一个定时器,例如上面.Basetimer = TIM4。在定时器的更新中断处理函数中顺序调用void VirPwm_TimIRQHandler(VirPwm *VirPwmDef),并传入相应的PWM结构体变量。如果PWM结构体的频率不同,.Basetimer需要设置不同的定时器,这是因为定时器的更新频率设置为PWM频率的100倍,如果频率不同的PWM设置了同一个定时器,或造成频率混乱。 对于不同的PWM结构体,应该有不同的端口操作回调函数注册到.SetOut。虽然理论上可将同一个端口操作回调函数注册到不同的PWM结构体.SetOut成员中,但不建议这么做,因为可能导致控制逻辑的混乱。示例如下: VirPwm VirPwmDef1 = { /*省略无关代码*/ .SetOut = VirPwm_SetOutHandler1 /*省略无关代码*/ }; VirPwm VirPwmDef2 = { /*省略无关代码*/ .SetOut = VirPwm_SetOutHandler2 /*省略无关代码*/ }; /** * @brief 注册为实现端口输出SetOut的服务函数(输出两路PWM) * @param VirPwm: 需要操作的虚拟PWM */ void VirPwm_SetOutHandler1(uint8_t state) { DIG_OUTPUT_1 = state; DIG_OUTPUT_2 = state; } /** * @brief 注册为实现端口输出SetOut的服务函数(输出三路PWM) * @param VirPwm: 需要操作的虚拟PWM */ void VirPwm_SetOutHandler2(uint8_t state) { DIG_OUTPUT_3 = state; DIG_OUTPUT_4 = state; DIG_OUTPUT_5 = state; } 关于PWM结构体变量的定义完成之后,在项目其他位置完成初始设置(示例如下)。如果在声明PWM结构体变量的时候,已经完成了初始化,则无需编写下述代码。 VirPwm_SetStatus(&VirPwmDef1,0); VirPwm_Init(&VirPwmDef1, 100, 50, 0); VirPwm_SetStatus(&VirPwmDef2,0); VirPwm_Init(&VirPwmDef2, 100, 50, 0); 在定时器的更新中断服务函数中,调用void VirPwm_TimIRQHandler(VirPwm *VirPwmDef),并传入相应的PWM结构体变量指针。 void TIM4_IRQHandler(void) { if(TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET) { VirPwm_TimIRQHandler(&VirPwmDef1); VirPwm_TimIRQHandler(&VirPwmDef2); TIM_ClearFlag(TIM4,TIM_FLAG_Update); TIM_ClearITPendingBit(TIM4,TIM_IT_Update); } } 在需要开启或关闭PWM的时候,如果每个定时器只驱动了一个虚拟PWM,可以直接通过如下函数控制: VirPwm_SetStatus(&VirPwmDef1,1); VirPwm_SetStatus(&VirPwmDef2,0); 如果定时器驱动了不止一个虚拟PWM,则不能使用上述函数分别开启或关闭PWM。因为上述函数是通过打开、关闭定时器的方式,会将该定时器驱动的全部PWM打开、关闭。如果需要单独关闭某个虚拟PWM,可以通过设置占空比为0的方式关闭该PWM。 VirPwm_SetDutyCycle(&VirPwmDef1, 0); |
|
|
|
只有小组成员才能发言,加入小组>>
imx6ull 和 lan8742 工作起来不正常, ping 老是丢包
2722 浏览 0 评论
3348 浏览 9 评论
3026 浏览 16 评论
3522 浏览 1 评论
9128 浏览 16 评论
1255浏览 3评论
643浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
634浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2382浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1946浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-2-1 16:40 , Processed in 1.086942 second(s), Total 79, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号