PWM 设备驱动
硬件原理
脉冲宽度调制(PWM)是英文“Pulse Width Modulation”的缩写,简称脉宽调制。它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用于测量,通信,功率控制与变换等许多领域。
举个简单的例子来说明 PWM 的作用,先看一幅 PWM 波形图,如下图所示。
这是一个周期 10ms,频率 100Hz 的波形。但可以看到,在每个周期内,高低电平脉冲宽度各不相同,这就是 PWM 的本质。PWM 中有一个概念叫做“占空比”:一个周期内高电平所占的比例。比如上图中,第一部分波形占空比 40%,第二部分波形占空比 60%,第三部分占空比 80%。
在数字电路里,一个引脚的状态只有 0 和 1 两种状态。假设芯片某引脚外接一个 LED 灯,当引脚电平为高电平时,灯灭;引脚电平为低电平时,灯亮。当引脚电平不停做高低电平切换时,LED 灯也就一直在闪烁。当引脚高低电平切换的时间间隔不断减小到肉眼分不出的时候,也就是 100Hz 的频率时,LED 灯表现出的现象就是既保持亮的状态,但是又没有引脚电平保持为低电平时的亮度高。当不断调整一个周期内高低电平的时间比例,LED 灯的亮度也就随之变化,而不仅仅只有灯亮和灯灭两种状态了。
假设现在用上图所示波形去控制引脚的高低电平切换,第一部分波形,LED 灯熄灭 4ms,灯亮 6ms,亮度最高;第二部分波形,LED 灯灭 6ms,灯亮 4ms,亮度次之;第三部分波形,LED 灯灭 8ms,灯亮 2ms,亮度最低。通过调整 PWM 占空比去调整 LED 灯的亮度的是 PWM 的一个典型应用。其他的典型应用如:控制电机转速、控制风扇转速、控制蜂鸣器的响度、控制显示屏亮度等。
寄存器配置
现以 i.MX6Q 芯片为例,介绍 PWM 相关寄存器配置。
- PWMCR
PWM 控制寄存器,用于设置 PWM 使能/禁能/复位,时钟源选择,时钟预分频值设置,PWM 输出相位配置等,其中在设置 PWM 之前需将该寄存器第 0 位清零禁能,设置完成后,再将该寄存器第 0 位置 1 使能。
- PWMSR
PWM 状态寄存器,主要获取当前 PWM 相关状态信息。
- PWMIR
PWM 中断控制寄存器,主要用来设置 PWM 相关中断。
- PWMSAR
PWM 采样寄存器,用来获取 PWM 当前采样值。
- PWMPR
PWM 周期设置寄存器,用来设置 PWM 时间周期。当定时器计数值达到设置值时,定时器复位,开始下一个周期。
驱动实现
现以 SylixOS-EVB-i.MX6Q 验证平台的 BSP 为例,介绍 PWM 的驱动实现。
注意:
SylixOS-EVB-i.MX6Q 验证平台是本公司为方便 SylixOS 用户充分评估 SylixOS 功能和性能推出的高端 ARM-SMP 验证平台,相关资料请见 翼辉信息官网 。
PWM 设备控制块
首先封装 PWM 设备控制块,其具体结构如下:
#include <SylixOS.h>
typedef struct {
LW_DEV_HDR PWMC_devHdr; /* 必须是第一个结构体成员 */
LW_LIST_LINE_HEADER PWMC_fdNodeHeader;
addr_t PWMC_BaseAddr;
} __IMX6Q_PWM_CONTROLER, *__PIMX6Q_PWM_CONTROLER;
- PWMC_devHdr :PWM 设备头,必须是第一个结构体成员。
- PWMC_fdNodeHeader :PWM 设备文件节点,NEW_1 型驱动结构中文件节点引入了文件访问权限、文件用户信息、文件记录锁等内容。
- PWMC_BaseAddr :PWM 基地址。
PWM 设备接口函数
imx6qPwmOpen :PWM 设备打开函数,主要是创建文件节点,将设备打开计数加 1,并初始化 PWM 定时器。
#include <SylixOS.h>
static LONG imx6qPwmOpen (__PIMX6Q_PWM_CONTROLER pPwmDev,
PCHAR pcName,
INT iFlags,
INT iMode);
函数 imx6qPwmOpen 原型分析:
- 此函数成功返回 PWM 设备文件节点,失败返回 PX_ERROR 。
- 参数 pPwmDev 是 pPwmDev 设备结构体。
- 参数 pcName 是设备名。
- 参数 iFlags 是打开方式。
- 参数 iMode 是打开方法。
imx6qPwmClose : PWM 设备关闭函数,将设备打开计数减 1,并禁能 PWM。
#include <SylixOS.h>
static LONG imx6qPwmClose (PLW_FD_ENTRY pFdEntry);
函数 imx6qPwmClose 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR。
- 参数 pFdEntry 是 pPwmDev 文件结构。
imx6qPwmIoctl :PWM 设备I/O控制函数,主要实现 PWM 的工作周期和占空比设置。
#include <SylixOS.h>
static INT imx6qPwmIoctl (PLW_FD_ENTRY pFdEntry, INT iCmd, LONG lArg);
函数 imx6qPwmIoctl 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR。
- 参数 pFdEntry 是 pPwmDev 文件结构。
- 参数 iCmd 是控制命令。
- 参数 lArg 是控制参数。
imx6qPwmWrite : PWM 设备写入函数
#include <SylixOS.h>
static ssize_t imx6qPwmWrite (PLW_FD_ENTRY pFdentry,
PVOID pvBuf,
size_t stLen);
由于 PWM 设备不需要写入操作,所以该函数暂时为空。
imx6qPwmRead : PWM 设备读取函数,主要是读取当前定时器计数值。
#include <SylixOS.h>
static ssize_t imx6qPwmRead (PLW_FD_ENTRY pFdentry,
PVOID pvBuf,
size_t stLen);
函数 imx6qPwmRead 原型分析:
- 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR 。
- 参数 pFdEntry 是 pPwmDev 文件结构。
- 参数 pvBuf 是读取数据 Buf。
- 参数 stLen 是数据长度。
PWM 设备驱动安装
首先根据 PWM 设备接口函数填充设备文件操作块 file_operations,具体过程如下:
#include <SylixOS.h>
fileop.owner = THIS_MODULE;
fileop.fo_create = imx6qPwmOpen;
fileop.fo_open = imx6qPwmOpen;
fileop.fo_close = imx6qPwmClose;
fileop.fo_ioctl = imx6qPwmIoctl;
fileop.fo_write = imx6qPwmWrite;
fileop.fo_read = imx6qPwmRead;
调用 API_IosDrvInstallEx2 安装驱动,具体实现如下:
_G_iPwmDrvNum = iosDrvInstallEx2(&fileop, LW_DRV_TYPE_NEW_1);
可以看到,API_IosDrvInstallEx2 第一个参数为填充完成的设备文件操作块,第二个参数设置驱动类型为 NEW_1 型驱动。该函数返回驱动程序索引号 _G_iPwmDrvNum。
PWM 设备创建和管理
创建 PWM 设备控制块,填充控制块相关内容,并进行相关初始化后,即通过调用 API_IosDevAddEx 函数向系统中添加一个 PWM 设备,其具体实现如下:
INT imx6qPwmDevAdd (UINT uiIndex)
{
__PIMX6Q_PWM_CONTROLER pPwmDev;
CPCHAR pcBuffer;
if (uiIndex >= PWM_NUM) {
printk(KERN_ERR "imx6qPwmDevAdd(): pwm index invalid!\n");
return (PX_ERROR);
}
pPwmDev = &_G_pwm[uiIndex];
switch (uiIndex) {
case 0:
pcBuffer = "/dev/pwm0";
pPwmDev ->PWMC_BaseAddr = PWM1_BASE_ADDR;
pwm1_iomux_config();
break;
case 1:
pcBuffer = "/dev/pwm1";
pPwmDev ->PWMC_BaseAddr = PWM2_BASE_ADDR;
pwm2_iomux_config();
break;
case 2:
pcBuffer = "/dev/pwm2";
pPwmDev ->PWMC_BaseAddr = PWM3_BASE_ADDR;
pwm3_iomux_config();
break;
case 3:
pcBuffer = "/dev/pwm3";
pPwmDev ->PWMC_BaseAddr = PWM4_BASE_ADDR;
pwm4_iomux_config();
break;
}
if (API_IosDevAddEx(&pPwmDev->PWMC_devHdr, pcBuffer, _G_iPwmDrvNum,DT_CHR) != ERROR_NONE) {
printk(KERN_ERR "imx6qPwmDevAdd(): can not add device : %s.\n",strerror(errno));
return (PX_ERROR);
}
return (ERROR_NONE);
}
这样,应用程序中可以通过 PWM 设备名打开对应的设备,并通过文件节点对其进行读写、I/O 控制等操作。例如:SylixOS-EVB-i.MX6Q 验证平台上将两路 PWM 引脚分别接至 LCD 屏亮度调节引脚和风扇转速调节引脚,通过 I/O 控制调整 PWM 工作周期和占空比,可以分别调节屏幕亮度和风扇转速。