2021.1.24更新:
输入捕获计算占空比和频率的公式有误,当捕获频率达到50KHZ甚至更高时,需要将寄存器的值+1以确保计算结果的准确:
fDuty = ((float)__CAP_FALLVALUE(iChan) + 1)
/ ((float)__CAP_FALLVALUE(iChan)
+ (float)__CAP_RISEVALUE(iChan));
pPwmData->uiDuty = (UINT)(fDuty * 1000); /* 计算占空比 */
pPwmData->uiFrequency = uiPwmClk / (__PWM_PRESCALER(iChan) + 1)
/ (__CAP_FALLVALUE(iChan) + 1
+ __CAP_RISEVALUE(iChan)); /* 计算频率 */
具体参考这篇帖子:
https://www.firebbs.cn/forum.php?mod=viewthread&tid=14334&highlight=%E9%A2%91%E7%8E%87
脉冲宽度调制(PWM)是英文“Pulse Width Modulation”的缩写,简称脉宽调制。它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用于测量,通信,功率控制与变换等许多领域。
通俗的说,就是控制在一个周期内,控制高电平多长时间,低电平多长时间。也就是说通过调节高低电平时间的变化来调节信号、能量等的变化。如图 1.1所示。
图 1.1 周期4ms的PWM波形
- 频率:是指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期),也就是说1s里PWM有多少个周期。上图中频率= 1/(0.003+0.001) = 250HZ。
- 周期:周期=1/频率,250HZ=4ms,1s里有250个周期。
- 脉宽时间:就是高电平时间。
- 占空比:是一个脉冲周期内,高电平时间与整个周期时间的比例。上图中占空比=1/(1+3)=25%
-
- PWM原理
以单片机为例,单片机的IO口输出的是数字信号,IO口只能输出高电平和低电平。假设高电平为5V,低电平则为0V,那么要输出不同的模拟电压,就要用到PWM,通过改变IO口输出的方波的占空比从而获得使用数字信号模拟成的模拟电压信号。
电压是以一种连接(1)或断开(0)的重复脉冲序列被加到模拟负载上去的(例如LED灯,直流电机等),连接即是直流供电输出,断开即是直流供电断开。通过对连接和断开时间的控制,理论上来讲,可以输出任意不大于最大电压值(即0~5V之间任意大小)的模拟电压。比方说,占空比为50%,那就是高电平时间一半,低电平时间一半,在一定的频率下,就可以得到模拟的2.5V输出电压,那么75%的占空比得到的电压就是3.75V。如图 1.2所示。
图 1.1 PWM调节输出电压
PWM的调节作用来源于对高电平的宽度控制,高电平变宽,输出的能量就会提高,通过阻容变换电路所得到的平均电压值也会上升,高电平变窄,输出的电压信号的电压平均值就会降低,通过阻容变换电路所得到的平均电压值也会下降。也就是说,在一定频率下,通过不同的占空比即可得到不同的输出模拟电压。PWM就是通过这种原理实现D/A转换的。
一般人眼睛对于80Hz 以上刷新频率则完全没有闪烁感。频率太小的话,看起来就会闪烁。在1秒内,高电平0.5秒,低电平0.5秒,(频率1Hz)如此反复,那么看到的电灯就会闪烁;但是如果是10毫秒内,5毫秒打开,5毫秒关闭,(频率100Hz) 这时候灯光的亮灭速度赶不上开关速度(LED灯还没完全亮就又熄灭了),由于视觉暂留作用,人眼不感觉电灯在闪烁,而是感觉灯的亮度少了。因为高电平时间(占空比)为50%,亮度也就为之前的50%。
频率很高时,看不到闪烁,占空比越大,LED越亮;
频率很低时,可看到闪烁,占空比越大,LED越亮。
所以,在频率一定下,可以用不同占空比改变LED灯的亮度。 使其达到一个呼吸灯的效果。
1.3.2 PWM控制电机转速
占空比可以实现对电机转速的调节。占空比是高电平在一个周期之中的比值,高电平的所占的比值越大,占空比就越大,对于直流电机来讲,电机输出端引脚是高电平电机就可以转动,当输出端高电平时,电机会转动,但是是一点一点的提速,在高电平突然转向低电平时,电机由于电感有防止电流突变的作用是不会停止的,会保持这原有的转速,以此往复,电机的转速就是周期内输出的平均电压值,所以实质上我们调速是将电机处于一种,似停非停,似全速转动又非全速转动的状态,那么在一个周期的平均速度就是占空比调出来的速度了。
在电机控制中,电压越大,电机转速越快,而通过PWM输出不同的模拟电压,便可以使电机达到不同的输出转速。
当然,在电机控制中,不同的电机都有其适应的频率 频率太低会导致运动不稳定,如果频率刚好在人耳听觉范围,有时还会听到呼啸声。频率太高的电机可能反应不过来。正常的电机频率在 6-16kHZ之间为好。
1.3.3 PWM控制舵机转向
舵机的控制就是通过一个固定的频率,给其不同的占空比的,来控制舵机不同的转角。舵机的频率一般为频率为50HZ,也就是一个20ms左右的时基脉冲,而脉冲的高电平部分一般为0.5ms-2.5ms范围。来控制舵机不同的转角。500-2500us的PWM高电平部分对应控制180度舵机的0-180度。以180度角度伺服为例,那么对应的控制关系如图 1.3所示:
图 1.2 高电平时间和角度的对应关系
1.4 特点
PWM的一个优点是从处理器到被控系统信号都是数字形式的,无需进行数模转换,让信号保持为数字形式可将噪声影响降到最小。噪声只有在强到足以将逻辑1改变为逻辑0或将逻辑0改变为逻辑1时,也才能对数字信号产生影响。
对噪声抵抗能力的增强是PWM相对于模拟控制的另外一个优点,而且这也是在某些时候将PWM用于通信的主要原因。从模拟信号转向PWM可以极大地延长通信距离。在接收端,通过适当的RC或LC网络可以滤除调制高频方波并将信号还原为模拟形式。
- T3下的PWM功能描述
本文档是对全志T3平台上的Sylixos PWM波的捕获功能的详细分析。本文档描述的应用场景是这样的:用户可以通过驱动程序捕获外部脉冲的频率和占空比;可以设置采样时钟的分频系数。
PWM 控制器有8路通道(PWM0~7),并且两两组成1对:PWM01、PWM23、PWM45、PWM67。如图 2.1所示。每对PWM包含1个时钟模块,2个定时器逻辑模块和1个可编程死区发生器。
每个PWM通道支持两个功能,包括输出比较和输入捕获。PWM通道的时钟源有OSC24M和APB1。PWM通道能够输出单脉冲或者持续性脉冲,输出脉冲的频率变化范围从0 到OSC24M/APB1 时钟。PWM还可以捕获输入脉冲。PWM通道在外部上升沿到来的时候捕获16位递增计数器的当前值并加载到CRLR(捕获上升沿锁存器),在外部下降沿到来的时候捕获16位递增计数器的当前值并加载到CFLR(捕获下降沿锁存器),然后外部时钟的频率就可以通过CRLR和CFLR的值正确计算出来。
PWM通道可以通过配置产生中断。作为输出功能时,当16位递增计数器的数值等于整个周期时,PWM通道能够产生中断。作为输入功能时,当PWM通道捕获外部上升沿,PWM通道能够产生一次中断;当PWM通道捕获外部下降沿,PWM通道能够产生一次中断。
图 2.1 PWM01 原理图
上图表示PWM01的框图,其他PWM对的框图与PWM01对相同,每个PWM对支持PWM输出和捕获输入,当使用PWM输出功能时,每个PWM对的两个通道都配置为PWM输出;当使用捕获输入函数时,每个PWM对的两个通道被配置为捕获输入。
图 2.2 PWM01 时钟控制器
每对PWM的时钟控制器包括时钟源选择、1~256分频和时钟源旁路。PWM时钟源有OSC24M和APB1总线。时钟源旁路功能是指时钟源不经过分频直接到达PWM输出,PWM输出脉冲是时钟源脉冲。如图 2.2所示。
-
- PWM时钟控制逻辑模块
每对PWM的逻辑模块由三部分组成:两个8bit 分频器,两个定时器逻辑和一个可编程的死区发生器。以PWM01为例,图 2.3表明PWM输出逻辑模块图。
图 2.3 PWM输出逻辑模块图
在上面的图中,PWM定时器逻辑由一个16位递增计数器和一个16位比较器组成。递增计数器用来控制周期,比较器用来控制占空比。递增计数器和比较器支持高速缓存加载,PWM输出被启用后,计数器和比较器的寄存器值可以随时改变,改变后的值被缓存到缓存寄存器,当加法计数器的值等于PWM整个周期时间时,缓存寄存器的值被加载到计数器和比较器。当更新计数器值和分离器值时,缓存加载很好地避免了具有不稳定的PWM输出波形。
周期,占空比和PWM输出脉冲的有效电平是由递增计数器和比较器决定的。以有效电平为高电平为例说明PWM输出周期和占空比的公式:
图 2.4 PWM高电平状态
英文说明:
PWM01_CLK: PWM时钟
PWM01_PRESCALE_K: 预分频系数
PPR0.PWM_ENTIRE_CYCLE:PWM整个周期时间
PPR0.PWM_ACT_CYCLE: PWM高电平时间
Duty-cycle: 占空比
图 2.5 PWM01捕获逻辑模块图
以PWM0通道为例,PWM0通道有CFLR(捕获下降沿锁存器)和CRLR(捕获上升沿锁存器)在上升沿和下降沿的时候捕获递增计数器的值。具体过程如图2.6所示。可以通过这两个值计算外部时钟的周期:
图 2.6 PWM0捕获时间过程图
当输入捕获功能开启后,PWM0通道的递增计数器就开始工作。
当PWM0的定时器逻辑模块捕获一个上升沿,递增计数器的当前值就锁存到CRLR(捕获锁存寄存器)并且CRLF(捕获控制寄存器的第2位)被置1.如果捕获中断开启,相应的捕获状态位就被置1,PWM0通道发送中断请求,递增计数器重载为0并继续计数。如果捕获中断不开启,递增计数器将不会重载为0。
捕获到下降沿时与此类似。
- T3下的PWM驱动实现
现以全志T3验证平台的BSP为例,介绍PWM的驱动实现。
-
- PWM通道控制块
首先封装 PWM 通道控制块,其具体结构如下:
typedef struct { addr_t PWMCHAN_ulPhyAddrBase; /* 物理地址基地址 */ size_t PWMCHAN_stPhyAddrSize; /* 物理地址空间大小 */ UINT PWMCHAN_uiPrescaler; /* 预分频系数 */ UINT32 PWMCHAN_uiRiseValue; /* 捕获上升沿时寄存器的值 */ UINT32 PWMCHAN_uiFallValue; /* 捕获下降沿时寄存器的值 */ INT PWMCHAN_pPinMux; LW_DEV_HDR PWMCHAN_devHdr; LW_LIST_LINE_HEADER PWMCHAN_fdNodeHeader; time_t PWMCHAN_time; addr_t PWMCHAN_ulVirtAddrBase; /* 虚拟地址基地址 */ } __PWM_CHANNEL, *__PPWM_CHANNEL; |
|
-
- PWM设备接口函数
- __pwmOpen————PWM设备打开函数
__pwmOpen主要是创建文件节点,并初始化PWM寄存器。
#include <SylixOS.h> static LONG __pwmOpen (PLW_DEV_HDR pDev, PCHAR pcName, INT iFlags, INT iMode) |
|
函数__pwmOpen原型分析:
l 此函数成功返回PWM设备文件节点,失败返回PX_ERROR;
l 参数 pDev 是设备结构体;
l 参数 pcName 是设备名;
l 参数 iFlags 是打开方式;
l 参数 iMode 是打开方法。
寄存器初始化主要流程如图 3.1所示:
图 3.1寄存器初始化主要流程
- __pwmClose————PWM设备关闭函数
__pwmClose是禁能PWM,将之前的初始化操作还原。
#include <SylixOS.h> static INT __pwmClose (PLW_FD_ENTRY pFdEntry); |
函数__pwmClose原型分析:
l 此函数成功返回ERROR_NONE,失败返回 PX_ERROR;
l 参数 pFdEntry是pPwmDev文件结构。
PWM关闭主要过程如图 3.2所示:
图 3.2寄存器关闭主要流程
由于测试时会使用到多线程同时操作多个PWM通道且每对PWM通道共用时钟,所以close接口不能关闭时钟。
- __pwmIoctl————PWM设备I/O控制函数
__pwmIoctl主要实现获取外部脉冲的频率和占空比,设置预分频系数。
#include <SylixOS.h> static INT __pwmIoctl (PLW_FD_ENTRY pFdEntry, INT iCmd, LONG lArg) { UINT32 uiValue; UINT32 uiPWMCLK; float fDuty; PWM_GET_PARA *pPwmData = NULL; INTREG intReg;
__PPWM_CHANNEL pPwmChannel = container_of(pFdEntry->FDENTRY_pdevhdrHdr, __PWM_CHANNEL, PWMCHAN_devHdr); INT chan = (pPwmChannel - &__GPwmChannels[0]) / (&__GPwmChannels[1] - &__GPwmChannels[0]);
LW_SPIN_LOCK_QUICK(&__GPwmSpl[__CHANNEL(chan)], &intReg);
switch (iCmd) {
case PWM_DEV_GET_PARA: /* 获取输入捕获的频率、占空比 */ pPwmData = (PWM_GET_PARA *)lArg;
__pwmSetPrescaler (__PWM_PRESCALER(chan),chan); /* 设置预分频系数 */
uiPWMCLK = 24000000 >> PWM_CLK_DIV_M; /* 24000000 / 32 */ fDuty = (float)__CAP_FALLVALUE(chan) / ((float)__CAP_FALLVALUE(chan) + (float)__CAP_RISEVALUE(chan));
pPwmData->uiDuty = (UINT)(fDuty * 1000); pPwmData->uiFrequency = uiPWMCLK / (__PWM_PRESCALER(chan) + 1) / (__CAP_FALLVALUE(chan) + __CAP_RISEVALUE(chan));
break;
case PWM_DEV_SET_CFG: /* 设置预分频系数 */ __GPwmSet.uiPrescaler = (UINT)lArg - 1; __PWM_PRESCALER(chan) = __GPwmSet.uiPrescaler; printk(KERN_ERR "prescaler == %d,chan == %d\r\n:",__PWM_PRESCALER(chan),chan);
__pwmSetPrescaler (__PWM_PRESCALER(chan),chan);
break;
default: _ErrorHandle(ENOSYS); LW_SPIN_UNLOCK_QUICK(&__GPwmSpl[__CHANNEL(chan)], intReg); return (PX_ERROR); }
LW_SPIN_UNLOCK_QUICK(&__GPwmSpl[__CHANNEL(chan)], intReg);
return (ERROR_NONE); } |
函数__pwmIoctl原型分析:
l 此函数成功返回ERROR_NONE,失败返回PX_ERROR;
l 参数 pFdEntry是pPwmDev文件结构;
l 参数 iCmd是控制命令;
l 参数 lArg是控制参数。
-
- PWM设备驱动安装
首先根据设备接口函数填充设备文件操作块file_operations,具体过程如下:
#include <SylixOS.h> fileOper.owner = THIS_MODULE; fileOper.fo_create = __pwmOpen; fileOper.fo_open = __pwmOpen; fileOper.fo_close = __pwmClose; fileOper.fo_ioctl = __pwmIoctl; |
调用iosDrvInstallEx2安装驱动,具体实现如下:
__GiPwmDrvNum = iosDrvInstallEx2(&fileOper, LW_DRV_TYPE_NEW_1); |
可以看到,iosDrvInstallEx2第一个参数为填充完成的设备文件操作块,第二个参数设置驱动类型为NEW_1型驱动。该函数返回驱动程序索引号__GiPwmDrvNum。
-
- PWM设备创建和管理
创建PWM通道控制块,填充控制块相关内容,并进行相关初始化后,即通过调用
API_IosDevAddEx函数向系统中添加一个 PWM 设备,其具体实现如下:
INT sunxiPwmDevAdd (UINT uiChannel, INT pPinMux) { __PPWM_CHANNEL pPwmChannel; CHAR cBuffer[64];
if (uiChannel >= __PWM_CHANNEL_NR) { printk(KERN_ERR "pwmDevAdd(): PWM channel invalid!\n"); return (PX_ERROR); }
pPwmChannel = (__PPWM_CHANNEL)&__GPwmChannels[uiChannel]; pPwmChannel->PWMCHAN_time = time(LW_NULL); pPwmChannel->PWMCHAN_pPinMux = pPinMux; snprintf(cBuffer, sizeof(cBuffer), __PWM_CHANNEL_NAME_FMT, uiChannel);
if (API_IosDevAddEx(&pPwmChannel->PWMCHAN_devHdr, cBuffer, __GiPwmDrvNum, DT_CHR) != ERROR_NONE) { printk(KERN_ERR "pwmDevAdd(): can not add device : %s.\n", strerror(errno)); return (PX_ERROR); }
return (ERROR_NONE); } |