基于字符设备平台驱动利用pwd控制蜂鸣器

发布于:2022-12-31 ⋅ 阅读:(466) ⋅ 点赞:(0)

目录

1、平台驱动总体思想

2、内核驱动架构

2.1驱动主题框架

2.1加载驱动 static int mod_init(void)

2.2 int my_probe (struct platform_device *pdev)

2.3 int my_remove (struct platform_device *pdev)

整体代码

应用层程序

具体实验


1、平台驱动总体思想

1、平台驱动架构,分为内核驱动与设备树。
    内核驱动:为固定的逻辑架构,解决向上为应用层提供调用接口,向下连接内核。
    设备树:为驱动提供设备具体参数(寄存器地址等)。
2、写驱动就是在内核编写几个函数,而编写的这些函数需要具备内核调用资格,就必须符合内核的规则,所以有了“架构”的概念。随便的自定义函数可能实现不了功能甚至导致内核崩溃进而使整个操作系统崩溃。所以写内核必须按照框架写。
3、因为编写的驱动需要符合内核的规则,就必须参考内核源码。可以借用source insight来看源码。

2、内核驱动架构

2.1驱动主题框架

1、头文件(具体头文件来源,可以用source insight查看内核源码)。
#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>
​
2、 加载驱动
static int mod_init(void)
    注册平台驱动。
    
4、卸载驱动
static void mod_exit(void)
    卸载平台驱动。
    
5、注册
module_init(mod_init);
module_exit(mod_exit);
这两个函数是内核提供的两个接口,就是来承接我们自己写的函数。也就是说,在我们在应用层加载内核时,内核就会调用   module_init(mod_init);。应用层卸载内核时,内核调用module_exit(mod_exit);。其中mod_init(),mod_exit()就是我们自己写的驱动函数。
    
6、版本
    MODULE_LICENSE("GPL");
    
以上就是整体框架, module_init(mod_init); module_exit(mod_exit); 是驱动的入口!!!!!!!分别是加载、卸载的入口。函数名不能改。括号内自定义函数名可以改。
    

2.1加载驱动 static int mod_init(void)

//定义platform_driver对象
struct of_device_id of_matches[]={
    {.compatible="fs,mybee"},
    {},
};
​
​
struct platform_driver mydriver ={ //平台驱动结构体
    .probe  =  my_probe, //驱动注册
    .remove =  my_remove, //驱动注销
    .driver={
        .of_match_table =  of_matches,  //通过设备树匹配
    },
};
​
static int mod_init(void)
{
     return platform_driver_register(&mydriver);//平台驱动注册
}
​
应用层 insmod xxx.ko (加载内核)时,内核就会调用 module_init(mod_init); 进而调用自定义加载函数 static int mod_init(void)。在这个函数中只做一件事 注册平台驱动。这个接口不应该被其它文件随便调用,所以要加static修饰。
    而平台驱动要干的事就多了,所以需要定义一个结构体。struct platform_driver mydriver。
 .probe  =  my_probe, //(驱动注册时最终调用的函数)
        1、连接硬件并获取硬件资源
         2、字符设备注册
            2.1 申请设备号
            2.2 初始化设备结构体
            2.3 设备注册到内核 //告诉内核现在由这个设备
            2.4 自动创设备文件(/dev/),设备驱动在应用层的接口。
         3、其它初始化,对于本次实验。通过pwd控制蜂鸣器。就可以在这里初始化一些变量以便后面调用,比如寄存器(pwd、蜂鸣器)的初始化。
            
          4、资源回收,上面一系列的初始化,可能失败。内核中必须回收资源。详细见下文。
.remove =  my_remove, //(驱动注销时最终调用的函数)
    驱动注销也是一堆释放资源。
    
.of_match_table =  of_matches,  //通过设备树匹配 
​
​
//定义platform_driver对象
struct of_device_id of_matches[]={
    {.compatible="fs,mybee"},
    {},
};
这个结构体对应设备树
设备树源码位置:linux-3.14/arch/arm/boot/dts
 设备树文件中,一个节点。关于pwd、蜂鸣器的节点。rge是一个数组,<> 代表一个元素,内核驱动中可以直接读取这些元素。   
   mybee@11000ca0{
         compatible ="fs,mybee";
        reg = <0x114000a0 0x4>,<0x139d0000 0x14>;
     }; 

2.2 int my_probe (struct platform_device *pdev)

int my_probe (struct platform_device  *pdev) //驱动注册
{
    printk("match success\n");
​
    //获取硬件资源
    gpio1 = platform_get_resource(pdev,IORESOURCE_MEM,0);//获取硬件资源
    if(gpio1 ==NULL)
    {
        goto failed_gpx2con;
    }
    gpd0 = (int*)ioremap(gpio1->start,4);
    printk(":%#x\n",gpio1->start);
​
    gpio2 = platform_get_resource(pdev,IORESOURCE_MEM,1);//获取硬件资源
    if(gpio2 ==NULL)
    {
        goto failed_gpx2dat;
    }
    gpdx = (int*)ioremap(gpio2->start,4);
    printk(":%#x\n",gpio2->start);
​
    在这里拿到设备资源(寄存器地址)后,就可以在后面进行操作了。
​
​
    //字符设备的注册
        ret = alloc_chrdev_region(&devnum,0,1,name); //申请设备号
        if(ret !=0)
       goto failed_alloc;
​
        cdev_init(&mycdev,&myops); //初始化设备
        ret =cdev_add(&mycdev,devnum,1);//设备加入内核
        if(ret !=0)
        {
            goto failed_add ;
        }
​
        printk("%d  %d\n",MAJOR(ret),MINOR(ret));
​
        //自动创建设备文件
        myclass =class_create(THIS_MODULE,"myclass"); // /sys/class 中创建一个类,方便管理
        if(IS_ERR(myclass))
        {
            goto failed_class;
        }
        mydevice= device_create(myclass,NULL,devnum,NULL,"buzzer"); //自动创建设备文件“myled”
        if(IS_ERR(mydevice))
        {
             goto  failed_device;
        }
        get_buzzer_adress();//获取蜂鸣器寄存器地址
​
        buzzer_init();//蜂鸣器初始化
​
​
//硬件操作
​
return 0;
//失败资源回收
failed_device:
    class_destroy(myclass);
failed_class:
    cdev_del(&mycdev);
failed_add:
    unregister_chrdev_region(devnum,1);
failed_alloc:
    iounmap(gpd0);
failed_gpx2dat:
    iounmap(gpdx);
failed_gpx2con:
return -1;
​
}
​
​

2.3 int my_remove (struct platform_device *pdev)

​
int my_remove (struct platform_device  *pdev)
{
    iounmap(gpd0);//注销映射
    iounmap(gpdx);//注销映射
    device_destroy(myclass,devnum);//注销
    class_destroy(myclass);//注销
    cdev_del(&mycdev);//注销设备结构体
    unregister_chrdev_region(devnum,1);//注销设备号
    printk("driver remove\n");
    return 0;
}
​

整体代码

#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>
​
#define BUZZER_ON  _IO('B',1)
#define BUZZER_OFF _IO('B',2)
​
​
long myioctl (struct file *pf, unsigned int cmd, unsigned long arg); 
int my_remove (struct platform_device  *pev);
int my_probe (struct platform_device  *pev);
int get_buzzer_adress(void);//获取蜂鸣器寄存器地址
 int buzzer_init(void);//蜂鸣器初始化
​
​
static dev_t devnum;  //设备号
static struct class* myclass; 
static char *name ="mydev";
static int ret;
struct cdev mycdev; //字符设备结构体
struct device* mydevice; 
struct resource *gpio1 =NULL;
struct resource *gpio2 =NULL;
​
static unsigned int * gpd0;//设备树第一个地址,蜂鸣器起始地址gdp_con
static unsigned int * gpdx;//设备树第二个地址,pwd起始地址gdpx_fg0
static unsigned int * gdpx_fg1;//二级分频
static unsigned int * gdpx_fcon;//pwd控制器
static unsigned int * gdpx_tb0;//设置最大值
static unsigned int * gdpx_tb1;//设置最小值
​
​
//定义platform_driver对象
struct of_device_id of_matches[]={
    {.compatible="fs,mybee"},            
    {}, 
};
​
struct file_operations myops={
    .unlocked_ioctl = myioctl,
}; //字符设备集(驱动具体功能实现)
struct platform_driver mydriver ={
    .probe  =  my_probe, //驱动注册
    .remove =  my_remove, //驱动注销
    .driver={
        .name = "mytest",
        .of_match_table =  of_matches,  //通过设备树匹配
    },
};
​
static int mod_init(void)
{
     return platform_driver_register(&mydriver);//平台驱动注册
}
​
static void mod_exit(void)
{
    platform_driver_unregister(&mydriver);//平台驱动注销
}
​
int my_probe (struct platform_device  *pdev) //驱动注册
{
    printk("match success\n");
​
    //获取硬件资源
    gpio1 = platform_get_resource(pdev,IORESOURCE_MEM,0);//连接硬件
    if(gpio1 ==NULL)
    {
        goto failed_gpx2con;
    }
    gpd0 = (int*)ioremap(gpio1->start,4); 
    printk(":%#x\n",gpio1->start);
    
    gpio2 = platform_get_resource(pdev,IORESOURCE_MEM,1);//连接硬件
    if(gpio2 ==NULL)
    {
        goto failed_gpx2dat;
    }
    gpdx = (int*)ioremap(gpio2->start,4);
    printk(":%#x\n",gpio2->start);
​
    
​
​
    //字符设备的注册
        ret = alloc_chrdev_region(&devnum,0,1,name); //申请设备号
        if(ret !=0)
       goto failed_alloc;
​
        cdev_init(&mycdev,&myops); //初始化设备
        ret =cdev_add(&mycdev,devnum,1);//设备加入内核
        if(ret !=0)
        {
            goto failed_add ;  
        }
        
        printk("%d  %d\n",MAJOR(ret),MINOR(ret));
​
        //自动创建设备文件
        myclass =class_create(THIS_MODULE,"myclass"); // /sys/class 中创建一个类,方便管理
        if(IS_ERR(myclass))
        {
            goto failed_class;
        }
        mydevice= device_create(myclass,NULL,devnum,NULL,"buzzer"); //自动创建设备文件“myled”
        if(IS_ERR(mydevice))
        {
             goto  failed_device;
        }
        get_buzzer_adress();//获取蜂鸣器寄存器地址
      
        buzzer_init();//蜂鸣器初始化
      
​
return 0;
//失败资源回收
failed_device:
    class_destroy(myclass);
failed_class:
    cdev_del(&mycdev);
failed_add:
    unregister_chrdev_region(devnum,1);
failed_alloc:
    iounmap(gpd0);
failed_gpx2dat:
    iounmap(gpdx);
failed_gpx2con:
return -1;
​
}
​
   int get_buzzer_adress(void)//获取蜂鸣器寄存器地址
   {
     gpd0 = (int*)ioremap(gpio1->start,4); 
         gpdx = (int*)ioremap(gpio2->start,4);
       
        gdpx_fg1 = (int*)ioremap(gpio2->start+4,4);
       
        gdpx_fcon = (int*)ioremap(gpio2->start+8,4);
​
        gdpx_tb0 = (int*)ioremap(gpio2->start+12,4);
​
         gdpx_tb1 = (int*)ioremap(gpio2->start+16,4);
​
    return 0;
   }
   
 int buzzer_init(void)//蜂鸣器初始化
{
     u32 tmp;
    printk("init\n");
​
    *gpd0 &= ~0xf;
    *gpd0 |= 0x2;  //设置蜂鸣器为pwm输出
   *gpdx |= 0xff;//预分频
    *gdpx_fg1 = (*gdpx_fg1 &(~0xf)) | 0x3;//二级分频
    *gdpx_tb0 = 110;//自减初值
    *gdpx_tb1 = 55;//比较值
    *gdpx_fcon |= (0x1<<3); 
    *gdpx_fcon |= (0x1<<1);
    
    //  *gdpx_fcon &= ~(0x1<<1);
   tmp = readl(gdpx_fcon);
   tmp &= ~(0x1<<1);
   writel(tmp,gdpx_fcon);
    
    
    return 0;
}
​
​
int my_remove (struct platform_device  *pdev)
{
    iounmap(gpd0);
    iounmap(gpdx);
    device_destroy(myclass,devnum);
    class_destroy(myclass);
    cdev_del(&mycdev);
    unregister_chrdev_region(devnum,1);
    printk("driver remove\n");
    return 0;
}
​
​
long myioctl (struct file *pf, unsigned int cmd, unsigned long arg)
{
    switch (cmd)
    {
    case BUZZER_ON:
        *gdpx_fcon |= (0x1<<0);
       printk("o22n %d   %d\n",*gpdx,*gdpx_tb0 );
       printk("o22n    %d\n",*gdpx_tb0);
       printk("o22n    %d\n",*gdpx_tb0);
        break;
     case BUZZER_OFF:
     printk("o22n %d   %d\n",*gpdx,*gdpx_tb0 );
     *gdpx_fcon &= ~(0x1<<0);
       printk("off22\n");
        break;
​
    default:
        break;
    }
​
    return 0;
}
​
​
module_init(mod_init);
module_exit(mod_exit);
MODULE_LICENSE("GPL");
​

应用层程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
​
#define BUZZER_ON  _IO('B',1)
#define BUZZER_OFF _IO('B',2)
​
int main(void)
{
    int fd; 
    int ret;
    fd = open("/dev/buzzer",O_RDWR);
    if(fd==-1){
        perror("open");
        return -1; 
    }   
    //printf("open device success %d\n",fd);
​
    while(1){
        ioctl(fd,BUZZER_ON);
        sleep(1);
        ioctl(fd,BUZZER_OFF);
        sleep(1);
    }   
 
    close(fd);
​
    return 0;
}

具体实验

修改makefile
​

obj-m := driver.o ///最终生成 driver.ko
driver-objs := test1.o
KERNELDIR ?= /home/wjt/mytftp/kernel/linux-3.14 // 改为源码目录
PWD := $(shell pwd) 
 
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules  
    cp driver.ko /home/wjt/nfserver/rootfs  -rf  //拷贝目标到自定义目录
​
clean:
    rm  *.mod.c  *.mod.o  *.ko  *.o  .*.o.* .*.ko.*  *.order  Module.symvers .tmp_versions -rf
        
        
        
   1、编译生成 driver.ko 目标文件
   2、insmod driver.ko 加载驱动
   3、编译应用层文件
   4、运行应用层文件。     
​
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到