linux驱动开发:PWM驱动编写

要操控蜂鸣器,首先我们需要找到这个器件的原理图,从而找到芯片中控制这个器件的管脚,于是我们可以先去开发板的电路原理图中搜索该器件,找到原理图:

 可以看到FS4412开发板使用了其中一路PWM输出(PWM0,对应管脚为GPD0.0)接蜂鸣器。

PWM,脉宽调制器,就是一个输出脉冲宽度可以调整的硬件器件,它不仅脉冲宽度可调,频率也可以调整,核心部件是一个硬件定时器。PWM管脚默认输出高电平。

 上图可以说明PWM工作原理,在时刻1设置计数值为160,比较值设置也为109,时刻2启动定时器,PWM输出低电平,计数器开始做减法,当计数值减到和比较值一致时,输出反转,当计数达到0,完成一次计数。形成一个波形,波形周期由计数值决定,占空比由比较值决定。我们可以通过调整这两个值使蜂鸣器发出特有频率的声音。

然后我们在芯片手册中找到pwm0内部结构图:

 可以看到输入时钟为PCLK,经过八位预分频和第二次的再分频的时钟最终给到PWM0所对应的计数器0,TCNTB0是计数寄存器,用于控制PWM输出波形的频率,TCMPB0是比较寄存器,用于控制PWM输出波形的占空比。通过查看这些寄存器,并对他们进行配置,我们可以完成对PWM的驱动代码的硬件部分的编写。

首先再回顾以下我们linux字符设备驱动的编写框架:

1.实现入口函数 xxx_init() 和卸载函数 xxx_exit()
2.申请设备号 alloc_chrdev_region (与内核相关)
3.注册字符设备驱动 cdev_alloc,cdev_init,cdev_add (与内核相关)
4.利用 udev/mdev 机制创建设备文件(节点) class_create,device_create(与内核相关)
5.硬件部分初始化

        从设备树获取硬件资源platform_get_resource()
        IO 资源映射 ioremap ,内核提供 gpio库函数(与硬件相关)
        注册中断(与硬件相关)
        初始化等待队列(与内核相关)
        初始化定时器(与内核相关)
6.构建 file_operation 结构(与内核相关)
7.实现操作硬件的方法 xxx_open,xxx_read,xxx_write…(与硬件相关)

首先我们将驱动的框架搭建好,再进行填充

头文件,查找对应的头文件我们可以使用多种方法,可以在内核源码目录建立索引使用ctags,进行搜索查找,或者使用soureinsight进行查找。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <asm/io.h>

定义变量来接收对应寄存器的映射地址,以及设备号设备名等等。

unsigned int *gpd0con;
unsigned int *tcfg0;
unsigned int *tcfg1;
unsigned int *tcon;
unsigned int *tcntb0;
unsigned int *tcmpb0;

static struct resource *rescon;//接收设备树中状态寄存器资源
static struct resource *resdata;//接收设备树中数据寄存器资源
static dev_t devnum;//设备号
static char *name ="mybuzzer";//设备名

unsigned int *gpx1con;
unsigned int *gpx1data;

声明一些函数,在我们的驱动和设备匹配成功后,会调用probe方法,在移除驱动时,会调用remove方法,所以我们对应的字符设备申请,注册,初始化,硬件资源获取,地址映射等都在probe种执行,remove种执行相应的注销销毁释放,解除映射等操作。



//实现platform_driver

int my_probe(struct platform_device *pdev);
int my_remove(struct platform_device *pdev);
long my_ioctl(struct file *pf, unsigned int cmd, unsigned long args);//IO操作函数


static struct cdev mycdev ;
static struct class * myclass;//方便多个相同设备管理
static struct device * mydevice;

static struct file_operations myops={
   
};

入口函数和卸载函数,不用再将硬件操作放在加载模块部分,这样更好体现了分离的思想,加载和卸载模块只需要进行驱动的注册和注销操作即可。

static int mod_init(void)
{
    return  platform_driver_register(&mydriver);  //平台驱动注册
}
static void mod_exit(void)
{
    platform_driver_unregister(&mydriver);        //平台驱动注销
}
module_init(mod_init);//注册
module_exit(mod_exit);
MODULE_LICENSE("GPL");//模块信息

定义平台驱动对象并初始化,设置匹配规则为对应的资源通过设备树进行获取,当然我们需要到相应的设备树dts文件中,进行修改,使我们的名字能够匹配上,并且寄存器的地址也要修改和我们芯片手册上地址一样才行,然后编译得到对应的dtb文件。

//定义platform_driver对象
struct of_device_id of_matches[]={
    {.compatible="fs,mybee"},           //修改匹配规则,从设备树  
    {},                                 //获取buzzer相关硬件信息
};
static struct platform_driver mydriver ={
    .probe = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "mytest",               
        .of_match_table =  of_matches,  //通过设备树匹配
    },
    
}; 

修改设备树文件内容,名字要和我们驱动代码中一样,才能进行匹配,寄存器地址要和芯片手册对应管脚位置一样。

 获取硬件资源,由于这几个寄存器地址都是连续的所以我们只需要获取最开始的地址然后进行偏移即可,随后建立映射,字符设备驱动的注册等操作

int my_probe(struct platform_device *pdev)
{
    int ret;
    //通过设备树获取 硬件资源
    printk("match\n");
    rescon = platform_get_resource(pdev,IORESOURCE_MEM,0);
    if(rescon==NULL){
        goto failed_getcon;
    }
    printk("%#x\n",rescon->start);
    gpd0con = ioremap(rescon->start,4);//建立映射

    resdata = platform_get_resource(pdev,IORESOURCE_MEM,1);
    if(resdata==NULL){
        goto failed_getdata;
    }
    printk("%#x\n",resdata->start);
    tcfg0 = ioremap(resdata->start,4);
    tcfg1 = ioremap(resdata->start+4,4);
    tcon = ioremap(resdata->start+8,4);
    tcntb0 = ioremap(resdata->start+12,4);
    tcmpb0 = ioremap(resdata->start+16,4);

    //字符设备注册
    ret =  alloc_chrdev_region(&devnum,0,1,name);//1.申请设备号
    if(ret!=0){
        goto failed_alloc;
    }
    cdev_init(&mycdev,&myops); //2.cdev初始化

    ret = cdev_add(&mycdev,devnum,1); //3.cdev添加到内核
    if(ret!=0){
        goto failed_add;
    }
    printk("register success %d,%d\n",MAJOR(devnum),MINOR(devnum));

    myclass = class_create(THIS_MODULE,"myclass");
    if(IS_ERR(myclass)){
        goto failed_class;
    }
    mydevice = device_create(myclass,NULL,devnum,NULL,"buzzer");
    if(IS_ERR(mydevice)){
        goto failed_device;
    }
    //硬件操作
    buzzer_init();
    buzzer_off();
#endif 
    return 0;

//对应的操作失败函数
failed_device:
    class_destroy(myclass);
failed_class:
    cdev_del(&mycdev);
failed_add:
    unregister_chrdev_region(devnum,1);
failed_alloc:
    return -1;
}

卸载时对应的操作,与我们在probe方法中进行的操作恰好相反,由地址映射,先接触映射,然后销毁设备,销毁类,删除字符设备,然后注销设备号。

int my_remove(struct platform_device *pdev)
{
    printk("driver remove\n");
    iounmap(gpx1con);//解除映射
    iounmap(gpx1data);

    device_destroy(myclass,devnum);//销毁设备
    class_destroy(myclass);//销毁类
    cdev_del(&mycdev);//删除字符设备
    unregister_chrdev_region(devnum,1);//注销设备号

    return 0;
}

硬件操作pwm初始化,这里的操作就按照芯片手册对应寄存器的设置进行相应的位操作,这里我们将管脚GPD0.0设置为功能2(TOUT_0),即PWM0的输出不上拉,驱动强度为最低级别。随后进行预分频,二分频等操作。

int buzzer_init(void)
{
    u32 tmp;
    tmp = readl(gpd0con);
    tmp &= ~0xf;
    tmp |= 0x2;
    writel(tmp,gpd0con);
   
    tmp = readl(tcfg0);
    tmp |= 0xff;
    writel(tmp,tcfg0);

    tmp = readl(tcfg1);
    tmp &= ~0xf;
    tmp |= 0x3;
    writel(tmp,tcfg1);

    writel(110,tcntb0);
    writel(110/2,tcmpb0);

    tmp = readl(tcon);
    tmp |= 0x1<<3;
    tmp |= 0x1<<1;
    writel(tmp,tcon);

    tmp = readl(tcon);
    tmp &= ~(0x1<<1);
    writel(tmp,tcon);

    return 0;
}

硬件操作,为应用层操作启动PWM,停止PWM提供方法。

int buzzer_on(void)
{
    u32 tmp;
    printk("buzzer_on\n");
    tmp = readl(tcon);
    tmp |= 0x1;
    writel(tmp,tcon);
    return 0;
}
int buzzer_off(void)
{
    u32 tmp;
    printk("buzzer_off\n");

    tmp = readl(tcon);
    tmp &= ~0x1;
    writel(tmp,tcon);
    return 0;
}

当应用层调用IO操作时就会调用到我们写好的底层驱动代码,实现相应的命令操作。

#define BUZZER_ON  _IO('B',1)//命令码
#define BUZZER_OFF _IO('B',2)


static struct file_operations myops={
    .unlocked_ioctl = my_ioctl,
};


long my_ioctl(struct file *pf, unsigned int cmd, unsigned long args)
{
    switch (cmd)
    {
        case BUZZER_ON:  buzzer_on(); break;
        case BUZZER_OFF: buzzer_off();break;
        default:  return -1;
    }

    return 0;
}

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 当使用Linux时,PWM驱动程序是一种非常常见的驱动程序类型,用于控制数字信号,如LED的亮度,风扇的速度等等。 以下是一些在Linux中使用PWM驱动程序的步骤: 1. 确认您的系统支持PWM驱动程序。要检查此功能,请执行以下操作: $ ls /sys/class/pwm 如果输出结果为“pwmchip0”,则您的系统支持PWM驱动程序。 2. 选择一个PWM引脚。要选择PWM引脚,请执行以下操作: $ ls /sys/class/pwm/pwmchip0 这将显示您的系统中可用的所有PWM通道。选择一个通道,并记下其索引号。 3. 设置PWM参数。要设置PWM参数,请执行以下操作: $ cd /sys/class/pwm/pwmchip0/pwm0 $ echo 1000000 > period $ echo 500000 > duty_cycle $ echo 1 > enable 这将设置PWM周期为1秒(1000000微秒),占空比为50%(500000微秒)并启用PWM信号。 4. 您还可以使用PWM驱动程序的API来编写自己的PWM控制程序。这些API包括pwm_request,pwm_config和pwm_enable等。 这些API可用于从用户空间直接控制PWM驱动程序,以实现更高级的PWM控制功能。 希望这些步骤对您有所帮助! ### 回答2: PWM(Pulse Width Modulation)顾名思义是一种脉冲宽度调制技术,在电子工程领域中广泛应用于数字信号处理中。PWM技术可以通过改变脉冲信号的脉冲宽度,来控制电路中的电压、电流或功率大小。PWM控制技术在电机调速、LED亮度调节、UPS、DC-DC变换器等各种电子设备中均有较为广泛的应用。 在linux内核中,PWM是通过相应的PWM驱动程序来控制的。PWM驱动程序所做的工作主要包括以下几个方面: 第一,初始化PWM。在系统启动过程中,通过相应的平台设备驱动程序,向内核注册PWM设备,设置相应的参数,包括PWM的周期和脉冲宽度等信息。 第二,控制PWM输出。PWM输出可以通过向PWM设备文件写入相应的数值来完成。输出数值由占空比和周期组成,通过改变参数可以控制PWM输出的占空比和频率。 第三,提供用户接口。PWM驱动程序同时还提供相应的用户接口文件,用户可以通过读写PWM设备对应的文件来实现对PWM的控制。例如,在/sys/class/pwm目录下,可以找到相应的PWM设备子目录,通过读写相应的文件即可对PWM进行控制。 第四,支持中断处理。在PWM输出过程中,一般需要在到达周期末尾时触发中断,进行相应的处理。PWM驱动程序也需要对中断处理进行支持。 总之,PWM技术在现代电子领域中应用非常广泛,linux PWM驱动程序也是相应的关键组成部分之一。通过PWM驱动程序的实现,我们可以轻松地控制LED亮度、电机转速等各种应用场景。而PWM驱动程序在日后的不断发展和完善中,也将不断地被改进和优化,为现代电子技术的不断进步提供更为稳定可靠的支持。 ### 回答3: PWM(Pulse Width Modulation)或脉宽调制是一种控制电子器件的技术,通过调整脉冲的宽度和周期来达到控制电子设备的功率、速度和亮度的目的。Linux PWM驱动则是一种跨平台的PWM控制器,使用Linux的内核从驱动器中执行PWM协议操作。 Linux PWM驱动是基于Linux系统内核提供的高级驱动程序接口(API)实现对PWM设备的控制和管理。Linux PWM驱动使得PWM设备的编程接口更加简单和便捷,可以自由配置PWM信号的频率、占空比和周期等参数以控制输出信号。 Linux PWM驱动通常被用于控制一些设备,比如LED、电机、声音发生器和传感器等等。例如,我们可以通过PWM控制器驱动LED的亮度和颜色,也可以通过PWM控制器驱动电机来调节速度和方向。 要在Linux中使用PWM驱动程序,需要安装相应的内核模块并配置设备驱动程序参数。LinuxPWM API接口包括PWM子系统,该子系统提供了PWM设备的管理、底层硬件驱动和访问函数等。此外,Linux的设备树(Device Tree)也提供一种类似于BIOS的抽象层,用于描述硬件平台的配置,包括PWM控制器等设备的物理和逻辑地址。 总之,Linux PWM驱动是一种实现PWM控制的程序接口,可用于管理和控制PWM设备。通过LinuxPWM API和设备树,开发人员可以轻松地访问硬件PWM信号并实现有用的控制功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值