正点原子linux阿尔法开发板使用——驱动开发篇

linux驱动开发篇

1、编译出厂内核源码

1.1 复制linux内核源码到Ubuntu中

创建新目录

mkdir -p IMX6/linux-imx-4.1.15-2.1.0

解压linux内核源码

tar xf linux-imx-4.1.15-2.1.0-gb78e551-v1.4.tar.xz -C IMX6/linux-imx-4.1.15-2.1.0/

进入解压的目录中

cd IMX6/linux-imx-4.1.15-2.1.0

查看解压后的文件
在这里插入图片描述
执行编译

./build.sh

编译完成,及查看 tmp 目录下的编译目标文件,如下图,包含很多 dtb 文件(设备树),及 Linux 内核 zImage,还有 modules.tar.bz2(内核模块)。
正点原子linux阿尔法开发板使用——驱动开发篇_第1张图片

2、NFS环境搭建

Windows 主机 IP:192.168.0.104
Ubuntu 虚拟机 IP:192.168.0.108
开发板 IP:192.168.0.106

执行以下指令设置开发板 IP,创建一个 get 目录,将虚拟机(192.168.0.108)NFS 共享目录挂载到到开发板的 get 目录中。

mkdir get
mount -t nfs -o nolock,nfsvers=3 192.168.0.105:/home/alientek/linux/nfs get

查看挂载的 NFS 目录:

df

正点原子linux阿尔法开发板使用——驱动开发篇_第2张图片在完成环境搭建之后,虚拟机把生成的可执行程序辅助到nfs挂载的目录下面。
在这里插入图片描述

运行QT程序在这里插入图片描述

下次开启重启无法加载的时候,重启nfs。执行以下指令重启 NFS 服务器。

sudo /etc/init.d/nfs-kernel-server restart

执行以下指令查看 NFS 共享目录。

showmount -e

在这里插入图片描述
2022.5.17 P1029字符驱动开发。
驱动加载:insmod drv.ko
驱动卸载:rmmod drv.ko

2022.5.18 视频第一节

驱动就是获取外设,获取传感器数据,控制外设。数据交给应用程序。
通过系统调用/软中断陷入内核,操作内核。

字符设备

1、驱动设备表现就是/dev/下的文件。/dev/led。应用程序调用open函数打开设备,比如led。应用程序通过write函数向/dev/led写入数据,比如1表示打开,0表示关闭。如果关闭设备就是close函数。

2、编写驱动的时候,主要就是编写驱动对应open、close、write函数。字符设备驱动fileopation_struct结构体成员。

3、我的第一个Linux驱动实验。
参考此文件下的字符驱动编写。
正点原子linux阿尔法开发板使用——驱动开发篇_第3张图片编译linux驱动的时候,需要用到linux内核源码,因此要解压缩。编译完成之后得到zImage的.dtb设备树。需要编译后zImage和.dtb。
修改一下路径:
![在这里插入图片描述](https://img-blog.csdnimg.cn/a6e856548f3842c0bbfac31d8d2fc136.png
编译通过,终于好了。
正点原子linux阿尔法开发板使用——驱动开发篇_第4张图片加载,卸载驱动设备。
正点原子linux阿尔法开发板使用——驱动开发篇_第5张图片
p1004 2022.5.18.22.41

2022.5.19 驱动视频第五节

需要向系统注册一个字符设备,需要使用函数register_chrdev,卸载驱动的时候,需要卸载到之前注册的设备。unregister_chrdev注销字符设备。
正点原子linux阿尔法开发板使用——驱动开发篇_第6张图片
设备号
正点原子linux阿尔法开发板使用——驱动开发篇_第7张图片dev_t高12位为主设备号,低20位为次设备号。

举例:同是IIC设备,同中IIC设备占用不同的次设备号。
正点原子linux阿尔法开发板使用——驱动开发篇_第8张图片次设备号
正点原子linux阿尔法开发板使用——驱动开发篇_第9张图片
设备号的操作函数或者宏:从dev_t\获取主设备号或者次设备号,MAJOR(dev_t),MINOR(dev_t)。也可以通过MKDEV(major,minor)获取设备号。

注册字符设备,当用了一个主设备号,剩下把次设备号全部都使用了。

查看设备号。

正点原子linux阿尔法开发板使用——驱动开发篇_第10张图片
file_operation结构体:
正点原子linux阿尔法开发板使用——驱动开发篇_第11张图片### 第一个linux设备驱动

/**
 *my first driver
 * 
*/


#include 
#include 
#include 
#include 
#include 
#include 

#define   CHARDEVBASE_MAJOR  200 //主设备号

#define   CHARDEVBASE_NAME "chrdevbase" //驱动名称

static int chrdev_base_open(struct inode *inode, struct file *filp)
{
	printk("chrdev_base_open\n");
	return 0;

}

static int chrdev_base_relase(struct inode *inode, struct file *filp)
{
	printk("chrdev_base_relase\n");
	return 0;
}


static ssize_t chrdev_base_read(struct file *filp, __user char *buf, size_t count,loff_t *ppos)
{
	printk("chrdev_base_read\n");
	return 0;
}


static ssize_t chrdev_base_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{
	printk("chrdev_base_write\n");
	return 0;
}

/**
 * 字符设备的操作集合
*/
static struct file_operations chrdev_base_fops = {
	.owner = THIS_MODULE,
	.open = chrdev_base_open,
	.release = chrdev_base_relase,
	.read = chrdev_base_read,
	.write = chrdev_base_write,	
};

static int __init chardevbase_init(void)
{
	int ret = 0;

	printk("权海洋,加油!\n");

	ret =  register_chrdev(CHARDEVBASE_MAJOR,CHARDEVBASE_NAME,&chrdev_base_fops);
	if(ret < 0){
		printk("chrdevbase init failed\n");
	}
	return 0;
}

static void __exit chardevbase_exit(void)
{
	unregister_chrdev(CHARDEVBASE_MAJOR,CHARDEVBASE_NAME);
	
	printk("权海洋,再见!\n");
}


//模块加载函数
module_init(chardevbase_init);

//模块卸载
module_exit(chardevbase_exit);

MODULE_LICENSE("QHY");

加载设备驱动之后,在进入/dev之后查看设备文件,并没有看到设备文件,原因是我们没有创建设备节点。
正点原子linux阿尔法开发板使用——驱动开发篇_第12张图片
创建设备节点。
正点原子linux阿尔法开发板使用——驱动开发篇_第13张图片创建设备节点成功。
正点原子linux阿尔法开发板使用——驱动开发篇_第14张图片
应用程序

#include 
#include 
#include 
#include 
#include 
#include 


int main(int argc, char **argv)
{
    int fd = 0;

    int ret = 0;

    char readbuf[10];

    char writebuf[100];

    fd = open(argv[1], O_RDWR);

    if (fd<0)
    {
        perror("open error");
        exit(-1);
    }
    
    /*read*/
    ret =  read(fd, readbuf,10);
    if (ret < 0)
    {
        perror("read error");
    }
    

    /*write*/

    ret = write(fd, writebuf,50);
    if (ret < 0)
    {
        perror("write error");
    }else{

    }
    
    /*close*/
    close(fd);

    exit(0);
}

通过应用层程序操作驱动程序成功。
正点原子linux阿尔法开发板使用——驱动开发篇_第15张图片
chardevbase虚拟设备的驱动。
要求:应用程序对驱动程序进行读写操作,读的话就是从驱动里面读取字符串,写的话就是向驱动写字符串。

应用程序不能访问内核数据!必须借助其他函数。
驱动程序:

/**
 *my first driver
 * 
*/
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define   CHARDEVBASE_MAJOR  200 //主设备号

#define   CHARDEVBASE_NAME "chrdevbase" //驱动名称

static char readbuf[100]; /*读缓冲*/

static char writebuf[100]; /*读缓冲*/

static char kernelbuf[100] = {"kernel data"};

static int chrdev_base_open(struct inode *inode, struct file *filp)
{
	//printk("chrdev_base_open\n");
	return 0;
}

static int chrdev_base_relase(struct inode *inode, struct file *filp)
{
	//printk("chrdev_base_relase\n");
	return 0;
}

static ssize_t chrdev_base_read(struct file *filp, __user char *buf, size_t count,loff_t *ppos)
{
	//printk("chrdev_base_read\n");

	int ret = 0;

	memcpy(readbuf,kernelbuf,sizeof(kernelbuf));

	ret = copy_to_user(buf,readbuf,count);
	if (ret < 0){
		
	}else{
	}
	return 0;
}


static ssize_t chrdev_base_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{
	//printk("chrdev_base_write\n");
	int ret = 0;

	ret = copy_from_user(writebuf,buf,count);
	if (ret<0){
			printk("chrdev_base_write error");
	}else{
		printk("kernel receive:%s\r\n",writebuf);
	}
	return 0;
}

/**
 * 字符设备的操作集合
*/
static struct file_operations chrdev_base_fops = {
	.owner = THIS_MODULE,
	.open = chrdev_base_open,
	.release = chrdev_base_relase,
	.read = chrdev_base_read,
	.write = chrdev_base_write,	
};

static int __init chardevbase_init(void)
{
	int ret = 0;
	ret =  register_chrdev(CHARDEVBASE_MAJOR,CHARDEVBASE_NAME,&chrdev_base_fops);
	if(ret < 0){
		printk("chrdevbase init failed\n");
	}
	return 0;
}

static void __exit chardevbase_exit(void)
{
	unregister_chrdev(CHARDEVBASE_MAJOR,CHARDEVBASE_NAME);
}


//模块加载函数
module_init(chardevbase_init);
//模块卸载
module_exit(chardevbase_exit);
MODULE_LICENSE("QHY");

测试应用程序:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

/**
 * 1 表示向驱动读取数据 
 * 
 * 2 表示向驱动写入数据
 * 
*/

int main(int argc, char **argv)
{

    if (argc != 3)
    {
        printf("Error Usage:\r\n");
        return -1;
    }
    
    int fd = 0;

    int ret = 0;

    char readbuf[10];

    char writebuf[100];

    static char usrdata[100] = {"qhy data!"};

    fd = open(argv[1], O_RDWR);
    if (fd<0)
    {
        perror("open error");
        exit(-1);
    }

    if (atoi(argv[2]) == 1){
        /*read*/
        ret =  read(fd, readbuf,10);
        if (ret < 0)
        {
            perror("read error");
        }else{
            printf("read data: %s\r\n", readbuf);
        }
    
    }else{

    }


    if (atoi(argv[2]) == 2){

        memcpy(writebuf,usrdata,sizeof(usrdata));

        /*write*/
        ret = write(fd, writebuf,50);
        if (ret < 0)
        {
            perror("write error");
        }else{

        }
    }
    
    /*close*/
    close(fd);

    exit(0);
}

实验现象

在这里插入图片描述
2022 .5.19 完成P7

2022.5.20 P8学习之旅

裸机LED灯实验就是操作+6ULL的寄存器,Linux也可以操作寄存器。了解Linux下如何操作寄存器,Linux下不能直接对寄存器的物理地址进行读写。比如寄存器A的物理地址为0x01010101。裸机可以直接对这个物理地址进行操作,但是在linux下不行,因为linux会使能MMU
正点原子linux阿尔法开发板使用——驱动开发篇_第16张图片
所以,在linux 下操作的都是虚拟地址,需要先得到物理地址转换成虚拟地址。
地址映射函数原型:
正点原子linux阿尔法开发板使用——驱动开发篇_第17张图片

第一个参数物理地址的起始地址,第二个参数是要转换的字节数量。

va = ioremap(0x01010101,10);

卸载驱动的时候,需要释放掉地址映射。
正点原子linux阿尔法开发板使用——驱动开发篇_第18张图片卸载驱动的时候:iounmap(va);

P9手撕代码。

实践操作:
内存地址映射:

/*首先定义寄存器的物理地址*/
#define  CCM_CCGR1_BASE 		(0X020C406C) 
#define  SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define  SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define  GPIO1_DR_BASE 			(0X0209C000)
#define  GPIO1_GDIR_BASE 		(0X0209C004)

/*地址映射后的虚拟地址映射*/
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

入口初始化:

	IMX6U_CCM_CCGR1 	= ioremap(CCM_CCGR1_BASE,4);
	SW_MUX_GPIO1_IO03	= ioremap(SW_MUX_GPIO1_IO03_BASE,4);
	SW_PAD_GPIO1_IO03	= ioremap(SW_PAD_GPIO1_IO03_BASE,4);
	GPIO1_DR			= ioremap(GPIO1_DR_BASE,4);
	GPIO1_GDIR			= ioremap(GPIO1_GDIR_BASE,4);

通过寄存器操作对GPIO的初始化操作:


  /*2、初始化*/
	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3<<26);/*清零bit26 27*/
	val |= (3<<26);	/*bit 26 27置1*/
	writel(val,IMX6U_CCM_CCGR1);

	writel(0x5,SW_MUX_GPIO1_IO03);//设置复用
	writel(0x10b0,SW_PAD_GPIO1_IO03);//设置电气属性

	val = readl(GPIO1_GDIR);
	val |= 1<<3;/*bit3 置为1 设置为输出*/
	writel(val,GPIO1_GDIR);

	val = readl(GPIO1_DR);
	val &= ~(1<<3);/*bit3 清0 打开led*/
	writel(val,GPIO1_DR);

如果使用的是心跳灯,必须关闭这个心跳灯,否则干扰实验现象。

echo none > /sys/class/leds/sys-led/trigger // 改变 LED 的触发模式

加载写完的驱动。
在这里插入图片描述实验现象:

通过应用程序操作led

编译内核和应用程序。
正点原子linux阿尔法开发板使用——驱动开发篇_第19张图片加载驱动,发现字符设备已经存在了。
正点原子linux阿尔法开发板使用——驱动开发篇_第20张图片创建设备节点,并且查看设备节点。
在这里插入图片描述应用程序操作led灯。
正点原子linux阿尔法开发板使用——驱动开发篇_第21张图片
本节的C应用代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 

/**
 * 1 表示向驱动读取数据 
 * 
 * 2 表示向驱动写入数据
 * 
*/

/**
 * ./ledApp  < 0 1 > 0表示关灯 1表示开灯
 * ./ledApp /dev/led 1
 * ./ledApp /dev/led 0
*/

#define LEDOFF 0

#define LEDON  1

int main(int argc, char **argv)
{
    int fd = 0;

    int ret = 0;

    unsigned char databuf[1];

    if (argc != 3)
    {
        printf("Error Usage:\r\n");
        return -1;
    }
    
    fd = open(argv[1], O_RDWR);
    if (fd<0)
    {
        perror("open error");
        exit(-1);
    }

    databuf[0] = atoi(argv[2]);

    ret = write(fd, databuf,sizeof(databuf));
    if (ret < 0)
    {
        perror("write error");
        close(fd);
        exit(-1);
    }else{

    }
    
    /*close*/
    close(fd);

    exit(0);
}

本节的驱动代码:

/**
 *my first driver
 * 
*/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define   LED_MAJOR  200 //主设备号

#define   LED_NAME "LED" //驱动名称

// static char readbuf[100]; /*读缓冲*/

// static char writebuf[100]; /*读缓冲*/

// static char kernelbuf[100] = {"kernel data"};


/*首先定义寄存器的物理地址*/
#define  CCM_CCGR1_BASE 		(0X020C406C) 
#define  SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define  SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define  GPIO1_DR_BASE 			(0X0209C000)
#define  GPIO1_GDIR_BASE 		(0X0209C004)

/*地址映射后的虚拟地址映射*/

static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;


#define LEDOFF 0 /*关闭*/

#define LEDON  1 /*打开*/

static void led_switch(u8 sta)
{
	u32 val = 0;
	if (sta == LEDON)
	{
		val = readl(GPIO1_DR);
		val &= ~(1<<3);/*bit3 清0 打开led*/
		writel(val,GPIO1_DR);
	}else if(sta == LEDOFF){
		val = readl(GPIO1_DR);
		val |= 1<<3;/*bit3 清0 打开led*/
		writel(val,GPIO1_DR);
	}
	
}

static int led_open(struct inode *inode, struct file *filp)
{
	return 0;

}

static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{
	int ret = 0;

	unsigned char databuf[1];

	ret = copy_from_user(databuf,buf,count);
	if (ret<0){
			printk("kernel_write error");
			return -EFAULT;
	}

	led_switch(databuf[0]);

	return 0;
}

/**
 * 字符设备的操作集合
*/
static const struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.release = led_release,
	.write = led_write,	
};

static int __init led_init(void)
{
	int ret = 0;
	
	u32 val = 0;

	IMX6U_CCM_CCGR1 	= ioremap(CCM_CCGR1_BASE,4);
	SW_MUX_GPIO1_IO03	= ioremap(SW_MUX_GPIO1_IO03_BASE,4);
	SW_PAD_GPIO1_IO03	= ioremap(SW_PAD_GPIO1_IO03_BASE,4);
	GPIO1_DR			= ioremap(GPIO1_DR_BASE,4);
	GPIO1_GDIR			= ioremap(GPIO1_GDIR_BASE,4);


	/*2、初始化*/
	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3<<26);/*清零bit26 27*/
	val |= (3<<26);	/*bit 26 27置1*/
	writel(val,IMX6U_CCM_CCGR1);

	writel(0x5,SW_MUX_GPIO1_IO03);//设置复用
	writel(0x10b0,SW_PAD_GPIO1_IO03);//设置电气属性

	val = readl(GPIO1_GDIR);
	val |= 1<<3;/*bit3 置为1 设置为输出*/
	writel(val,GPIO1_GDIR);

	val = readl(GPIO1_DR);
	val &= ~(1<<3);/*bit3 清0 打开led*/
	writel(val,GPIO1_DR);

	//初始化led灯。
	/**/

	ret =  register_chrdev(LED_MAJOR,LED_NAME,&led_fops);
	if(ret < 0){
		printk("LED init failed\n");
		return -EIO;
	}
	return 0;
}

static void __exit led_exit(void)
{
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);
	
	unregister_chrdev(LED_MAJOR,LED_NAME);
}


//模块加载函数
module_init(led_init);

//模块卸载
module_exit(led_exit);

MODULE_LICENSE("QHY");

你可能感兴趣的:(#,嵌入式驱动linux,linux,驱动开发,ubuntu)