树莓派-字符设备驱动编写

学了基本的字符设备框架之后可以对树莓派的底层写个驱动,简单的写个驱动IO的控制。

编写驱动可以根据上层的应用需求进行编写。

示例:实现上层逻辑代码向底层写入想要的字符,再编写底层驱动接收到字符后实现IO口的高低电平转换

一、查阅手册

想控制IO口的高低电平转换,需要查阅芯片手册,这里树莓派3使用的是 BCM2835

查阅芯片手册,我们要看的是IO口相关寄存器,按下面的目录去找

可以看到这里有一堆寄存器,和寄存器的功能描述,直接看需要的寄存器,这里我们要知道一点,每个寄存器很多组,你想操控的IO口是属于该寄存器的第几组,对应的地址是不一样的,后面映射地址需要知道。

 1)先来看功能选择寄存器GPFSEL

 

下图的register 0  代表上面的IO口被分为第0组,其他IO口被分为其他组

以pin4引脚为例,我们要把引脚4设输出模式,则需要把此寄存器  14-12bit 位置设置为  001 

 2)输出1(高电平)寄存器GPSET

下图SETn   n表示引脚号,箭头所指就是设为1即高电平

   上图可看到引脚4依旧被分为第0组寄存器

 3)输出0(低电平)清零寄存器GPCLR

下图箭头所指即是清零

    上图可看到引脚4依旧被分为第0组寄存器

    所以实际上只需要用到3个寄存器

 二、上层代码

#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stdio.h>

int main()
{
        int fd;
        int cmd;

        fd = open("/dev/pin4",O_RDWR);
        if(fd < 0){
                printf("open failed\n");
                perror("reson");
        }else{
                printf("open success\n");
        }

        printf("input 0 / 1  0:set pin4 low  1:set pin4 high\n");
        scanf("%d",&cmd);

        if(cmd == 0){
                printf("pin4 set 0\n");
        }else if(cmd == 1){
                printf("pin4 set 1\n");
        }

        fd = write(fd,&cmd,1);
        return 0;
}

三、驱动代码

编写驱动之前注意一个问题,IO口的起始地址是0x3f000000,加上GPIO的偏移量0x20000000,所以GPIO的物理地址应该是从0x3f200000开始的,

然后在此基础上进行linux系统的MMU内存虚拟化管理,映射到虚拟地址上。

寄存器代码操作处理:

  之前已经查阅了寄存器,知道引脚4在功能选择寄存器GPFSEL上14-12的位置上,并且我们要设置为输出模式,就是001

                      31 30 ...........14 13 12 11 10 .........2 1
                                             0   0   1   0000000000

操作原则是不能影响其他bit位置上原来的数值,我们只能影响14-12bit

先把14-13bit位置上变为数值00,我们需要把数值11左移12位,然后取反再上原数值之后即可。

之后12位bit位置上变成数值1,我们需要把数值1左移12位,然后上原数值即可。

代码操作就是    *GPFSEL0 &= ~(0x6 << 12);         *GPFSEL0 |= (0x1 << 12);

下面代码是映射地址,我们知道引脚4属于这些寄存器的第0组,我们开头查看的第一列就是地址

void __iomem * __ioremap(resource_size_t  res_cookie, size_t size);

resource_size_t  是要映射的起始的IO地址

size_t 是要映射的空间的大小,我们树莓派寄存器是32位即4个字节,所以size设为4

GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0  = (volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0  = (volatile unsigned int *)ioremap(0x3f200028,4);  

针对read,我们用copy_to_user  ,把用户空间的数据读到内核空间的数据中

        copy_to_user(void * to, const void * from,unsigned long n);     

        第一参数用户空间的数据地址,

        第二参数是内核空间数据的地址,

        第三参数是复制的字节数

        返回值为实际copy的字节数

针对write,我们用copy_from_user ,把内核空间的数据写到用户空间里

        copy_from_user(void * to, const void __user *from,unsigned long n);

        第一个参数是内核空间数据的地址,kernel_buf

        第二个参数是用户空间的数据地址,即__user

        第三个参数是复制的字节数

        返回值为真实读到的字节数量

#include <linux/fs.h>		 //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>	 //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件

static struct class *pin4_class;  
static struct device *pin4_class_dev;

static dev_t devno;                //设备号
static int major =231;  		   //主设备号
static int minor =0;			   //次设备号
static char *module_name="pin4";   //模块名

volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0  = NULL;
volatile unsigned int* GPCLR0  = NULL;

//pin4_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数,和printf类似

    *GPFSEL0 &= ~(0x6 << 12);
	*GPFSEL0 |= (0x1 << 12);
    
    return 0;
}

//pin4_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
	int userCmd;//与上层写下来的数据类型对应,即int类型

	printk("pin4_write\n");

	copy_from_user(&userCmd,buf,count);//用户空间向内核空间传输数据
	

	if(userCmd == 1){
		printk("set 1\n");
		*GPSET0 |= 0x1 << 4;
	}else if(userCmd == 0){
		printk("set 0\n");
		*GPCLR0 |= 0x1 << 4;
	}else{
		printk("undo\n");
	}
	
    return 0;
}

static struct file_operations pin4_fops = {

    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
};

int __init pin4_drv_init(void)   //驱动的真正入口
{

    int ret;
    printk("insmod driver pin4 success\n");
    devno = MKDEV(major,minor);  //创建设备号
    ret   = register_chrdev(major, module_name,&pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中

    pin4_class=class_create(THIS_MODULE,"myfirstdemo");  //由代码在/dev下自动生成设备
    pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件

	GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);
	GPSET0  = (volatile unsigned int *)ioremap(0x3f20001C,4);
	GPCLR0  = (volatile unsigned int *)ioremap(0x3f200028,4);
 	
 	return 0;
}

void __exit pin4_drv_exit(void)
{
	iounmap(GPFSEL0);
	iounmap(GPSET0);
	iounmap(GPCLR0);
	
    device_destroy(pin4_class,devno);
    class_destroy(pin4_class);
    unregister_chrdev(major, module_name);  //卸载驱动

}

module_init(pin4_drv_init);  //入口:内核加载驱动的时候,这个宏会被调用,而真正的驱动入口是它调用的函数
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

四、交叉编译驱动代码和上层代码

1)进入到/home/jdr/SYSTEM/linux-rpi-4.14.y/drivers/char  目录,并把写好的驱动代码  pin4drive.c  放进来

2)编译之前需要进入Makefile (在哪个文件夹下面就修改哪个文件夹下的Makefile ),添加信息为了告诉编译器,要编译该驱动文件

               vi Makefile
              在文件中添加下面这句话,

obj-m        +=pin4driver2.o 

3)编译

          ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7  make modules

         上层代码pin4drivertest.c也要交叉编译,然后上传到树莓派,这里就不再赘述。

4)将.ko文件用scp上传到树莓派后

    scp pin4drive.ko pi@192.168.1.6:/home/pi

5)装载设备驱动
    sudo insmod pin4driver2.ko

    其他命令:
    命令 ls -l  /dev/pin4  可以看到生成的设备的详细信息
    命令 lsmod  查看挂载的驱动
    命令 sudo rmmod pin4driver2 可以删除驱动

6)装载完成后,给设备文件加权限,之后开始运行上层程序

     sudo chmod 666 /dev/pin4
     ./pin4text

     命令 dmesg 可以查看内核打印信息

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值