树莓派字符驱动

先看测试代码,目的打开树莓派/dev/pin4模块,向其中写入一个整型数。

测试程序 pin4test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<stdio.h>
int main()
{
        int data=0;
        int fd;
        fd=open("/dev/pin4",O_RDWR);
        if(fd<0){
                printf("opne pin4 defeat!\n");
        }
        else{
                printf("opne success!\n");
        }
        printf("please input a int num:\n");
        scanf("%d",&data);
        printf("data=%d\n",data);
        write(fd,&data,sizeof(data));
        return 0;
}

编译测试程序: gcc   pin4test.c -o pin4test 

直接运行测试程序./pin4test一定是不会成功的,因为/dev下根本就没有pin4这个模块,因此我们接下来的工作就是让dev下产生pin4这个模块。

首先我们需要根据驱动框架编写好自己的驱动代码,代码如下。

这个驱动是作用数将树莓派的第四个引脚配置成输出模式,并根据上层传递过来的整型数来控制输出高低电平。传入1输出高电平,传入0输出低电平。

驱动框架pin4driver.c 

#include <linux/fs.h> // 声明了文件操作相关的函数,比如文件打开,读写,关闭等
#include <linux/module.h> // 声明模块初始化和退出相关的函数
#include <linux/init.h> // 声明了初始化相关的宏,比如__init和__exit
#include <linux/device.h> // 设备相关的结构体和函数
#include <linux/uaccess.h> // 用户空间和内核空间数据交换相关的函数
#include <linux/types.h> // 定义了设备号等数据类型
#include <asm/io.h> // IO内存映射相关的函数
// 定义了一个结构体指针变量pin4_class,用于表示设备所属的类别
static struct class *pin4_class;  
// 定义了一个设备结构体指针变量pin4_class_dev,用于表示设备本身
static struct device *pin4_class_dev;
// 定义了一个设备号变量devno
static dev_t devno;                //设备号
// 定义了主设备号major
static int major =231;  		   //主设备号
// 定义了次设备号minor
static int minor =0;			   //次设备号
// 定义了一个字符指针变量module_name,用于存储模块名“pin4”
static char *module_name="pin4";   //模块名

volatile unsigned int *GPFSET0=NULL;//根据手册 GPFSET0寄存器 用来选择引脚
volatile unsigned int *GPSET0 =NULL;//根据手册 GPFSET0寄存器 用来置高电平
volatile unsigned int *GPCLR0 =NULL;//根据手册 GPFSET0寄存器 用来清0

// 参数inode代表打开的文件,file代表打开文件对应的file结构体
static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数和printf类似

    //根据手册把GPFSET0第12~14位配置成001可以将树莓派第四个引脚配置成输出模式(output)
    *GPFSET0 &= ~(0x6<<12); //将二进制数0110左移十二位取反,然后与等于,将寄存器第13、14位置0
    *GPFSET0 |=(0x1<<12);//根让寄存器的第12位置1

    return 0; 
}

// 参数file代表写入的文件,buf是写入的数据缓冲区,count是写入的字节数,ppos是写入的文件位置指针
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    int userCmd;
	printk("pin4_write\n");
	copy_from_user(&userCmd,buf,count);//内核编程中常用的函数,它的主要作用是从用户空间复制数据到内核空间
	if(userCmd==1){
		printk("set 1\n");
		*GPSET0 |=0x01<<4;	//将树莓派第四个引脚置为高电平
	}
	else if(userCmd==0){
		printk("set 0\n");
		*GPCLR0 |=0x01<<4;    //将树莓派第四个引脚置为低电平
	}
	else{
		printk("undo\n");
	}

    return 0; 
}

// 定义了一个文件操作结构体变量pin4_fops,用于存储文件操作函数
static struct file_operations pin4_fops = {
    .owner = THIS_MODULE, // 表示文件操作的所有者是当前模块
    .open  = pin4_open,  // 表示文件打开时调用的函数是pin4_open
    .write = pin4_write,  // 表示文件写入时调用的函数是pin4_write
};

// pin4_drv_init函数的函数声明,这个函数是模块的初始化函数
int __init pin4_drv_init(void) 
{
    int ret; 
    devno = MKDEV(major,minor);  // 使用MKDEV宏生成设备号,这个宏在types.h中定义,主设备号和次设备号相加得到设备号devno
    ret   = register_chrdev(major, module_name,&pin4_fops);  // 使用register_chrdev函数注册字符设备驱动,告诉内核这个驱动加入到内核驱动的链表中

    pin4_class=class_create(THIS_MODULE,"myfirstdemo"); // 使用class_create函数创建设备类别,类别名是"myfirstdemo"
    pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  // 使用device_create函数创建设备文件,参数包括类别、父设备、设备号、名称等

    GPFSET0=(volatile unsigned int *)ioremap(0x3f200000,4);//将物理地址映射成虚拟地址,内核和上层空间都是访问虚拟地址的
    GPSET0 =(volatile unsigned int *)ioremap(0x3f20001c,4);
    GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);

    return 0; 
}

// pin4_drv_exit函数的函数声明,这个函数是模块的退出函数
void __exit pin4_drv_exit(void) 
{

    iounmap(GPFSET0);//将之前通过ioremap函数映射的IO地址空间解除映射
	iounmap(GPSET0);
	iounmap(GPCLR0);
    
    device_destroy(pin4_class,devno); // 使用device_destroy函数销毁创建设备文件
    class_destroy(pin4_class); // 使用class_destroy函数销毁创建的设备类别
    unregister_chrdev(major, module_name);  //卸载驱动
}
module_init(pin4_drv_init);  //入口宏
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

将这个代码在虚拟机上拷贝到linux内核的/drivers/char字符驱动目录下,为了使该驱动文件能够被编译到,这里需要修改Makefile,Makefile文件的意义在于自动化构建项目,也就是为了更简便地执行程序,Makefile文件描述了整个工程所有文件的编译顺序、编译规则,它存在于项目的根目录下,并且可以在项目的任何子目录中存在,但通常在项目的根目录下创建或维护。

使用以下命令编辑Makefile,模仿其他的文件加入下面的语句,文件的后缀名改为.0,前面的-m表示以模块的方式加载到内核。

vi Makefile

然后回到内核目录下:编译内核模块。

还记的编译内核的代码吗,如下所示。这里只需要编译modules即可,因此使用如下第一个命令即可。

使用这个:

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules 
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make -j4 zImage modules dtbs

"ARCH": "定义目标体系结构,这里是arm,表示你正在为基于ARM的处理器构建内核。",
"CROSS_COMPILE": "定义交叉编译器的前缀,这里是指用于ARM架构的GNU编译器的前缀,包括编译器、链接器和其它工具。它的形式通常是 'arm-linux-gnueabihf-'.",
"KERNEL": "定义内核的版本或者名称,这里是kernel7。",
"make -j4": "使用make命令并且并行编译的数量为4,这可以大大提高编译速度。'-j'参数代表并行编译,后面的数字4代表同时编译4个文件。",
"zImage": "这是编译的内核镜像文件的名称,'zImage'是压缩内核镜像的简称。",
"modules": "这部分是编译所有的内核模块。",
"dtbs": "这个部分是编译设备树源文件(.dts)并生成设备树二进制文件(.dtb)和设备树自解压文件(.dtbs)。设备树描述了硬件的配置信息。"

编译通过以后会在linux-rpi-4.14.y/drivers/char路径下生成pin4driver.ko文件。

然后将这个文件传到树莓派的工作目录下/home/pi。

接着加载驱动,命令如下:

 sudo insmod pin4driver.ko

命令执行完成后可以执行lsmod命令,它用于列出已加载的内核模块     这里可以看到pin4driver已经被加载进去了。

同时树莓派/dev目录下会多一个  pin4  的文件

名字的来源为字符驱动框架程序中的如下语句

static char *module_name="pin4";   //模块名

使用命令 ls pin4 -l可以查看文件的详细信息,231为主设备号,0为从设备号。生成的主次设备号由在驱动代码的变量major minor决定的。

既然已经在/dev目录下生成了pin4模块,那么这里就可以执行测试程序pin4test了。

 这里看到运行 ./pin4test运行结果是打开失败,是因为加载驱动的时候使用的是超级用户权限,导致普通用户没有pin4的权限,所以这里需要使用超级用户权限改变/dev/pin4的权限,命令如下

sudo chmod 666/dev/pin4

然后在执行./pin4test就会正常运行。

使用dmesg命令可以打印内核的状态,内核成功执行了驱动中的 pin4_open函数,成功打印了pin4_open。


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值