Linux 交叉编译树莓派设备驱动代码编写和调试

本文介绍了如何在Linux环境下交叉编译树莓派的设备驱动代码,包括驱动代码的基本框架、编译配置、驱动加载与卸载。详细讲解了地址总线、物理地址和虚拟地址的概念,以及树莓派BCM2835芯片手册中的寄存器操作。同时,提到了驱动加载后的测试程序编写和权限设置。


Linux一切皆文件
fd = open ("/dev/pin4",权限);

怎么才能找到驱动
1.文件名
2.设备号

设备号 主设备号iPhone华为 次设备号iphonexiphonexr

驱动代码有一个驱动链表
管理所有设备驱动
1.添加
2.查找

编写完驱动代码,加载到内核
调用驱动程序,用户空间去open

开发:驱动插入链表的顺序有设备号检索

system_call用户和vfs接口
用户级
VFS调用对应文件系统
文件系统用对应的方式调用驱动
驱动
引脚

写代码

1.驱动代码基本框架
随便一个名字:pin4Drivers.c
主要的框架是这个函数:pin4_drv_init
其他基本是围绕它的变量写的一些定义,内容的补充

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

//变量的定义
static struct class *pin4_calss;
static struct device *pin4_dev;

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

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

//pin4_write 函数
static ssize_t pin4_write(struct file *file)//上层调用write,这里就调用pin4write
{
	printk("pin4_write\n");
	return 0;
}

static struct file_operations pin4_fops ={
	.owner  = THIS_MODULE,
	.open  	= pin4_open,
	.write 	= pin4_write,
	.read	= pin4_read,
	
};
/*
struct abc{
	int a;
	int b;	
	int c;
};
struct abc ab = {
	.a = 1; 
	.c = 2;	//直接给c赋值,如果没有.a,.c就按顺序赋值
};
*/


int _init pin4_drv_init(void)	//真实驱动入库
{
	int ret;
	devno = MKDEV(major,minor);	//创建设备号:参数是主设备号和次设备号
	ret = register_chrdev(major,module_neme,&pin4_fops); //注册驱动告诉内核,把这个驱动加入到内核,参数分别是:主设备号,模组名"pin4",操作函数结构体
	pin4_calss = class_create(THIS_MODULE,"myfirstdemo"); //让代码在/dev下自动生成设备
	//也可以手动生成sudo mknod abc 8 1 //主动生成设备,主设备号是8,次设备号是1,名字是abc
	pin4_calss_dev = device_create(pin4_calss,NULL,devno,NULL,module_neme); //创建设备文件

	return 0;
}

void _exit pin4_drv_exit(void)
{
	device_destroy(pin4_calss,devno);//销毁设备
	class_destroy(pin4_calss);//销毁类
	unregister_chrdev(major,module_neme); //卸载驱动
}

module_init(pin4_drv_init);		//入口,内核加载该驱动的时候,这个宏会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

2.把代码移动到 /linux-rpi-4.14.y/drivers/char$
驱动设备分为字符设备,块设备,网络设备驱动
不是非得放在char文件夹,无论放在哪个文件夹,都要修改那个文件夹的Makefile
/linux-rpi-4.14.y不知道是什么可以看这个博客

3.配置Makefile 让工程编译的时候可以编译pin4Drivers.c
驱动可以编译成模块,也可以编译进内核,这里ogj-m是编译成模块

obj-m                       += pin4Drivers.o

然后回到上两层目录

hgs@ubuntu:~/raspberry/linux-rpi-4.14.y/drivers/char$ cd ../..
hgs@ubuntu:~/raspberry/linux-rpi-4.14.y$ 

编译生成驱动

这个命令用来编译内核的,类似gcc,不过是比gcc长很多
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make

arm:arm架构
CROSS_COMPILE :交叉编译的意思
arm-linux-gnueabihf-:arm-linux-gnueabihf-gcc就是可以编译出树莓派的可执行文件的编译器,详情可查看交叉编译树莓派
Kernel 操作系统内核 操作系统内核是指大多数操作系统的核心部分。它由操作系统中用于管理存储器、文件、外设和系统资源的那些部分组成。操作系统内核通常运行进程,并提供进程间的通信。
下面列出了它的一些核心功能:事件的调度和同步。进程间的通信(消息传递)。存储器管理。进程管理。
KERNEL=kernel7 :操作系统内核版

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make -j4 modules
编译的结果是生成一个:pin4Drivers.ko文件

1.拷贝到树莓派

hgs@ubuntu:~/raspberry/linux-rpi-4.14.y$ scp drivers/char/pin4Drivers.ko pi@192.168.101.81:/home/pi
pi@192.168.101.81's password: 
pin4Drivers.ko                                100% 5972   545.3KB/s   00:00 

2.树莓派加载驱动到内核的驱动链表

pi@raspberrypi:~ $ sudo insmod pin4Drivers.ko 

lsmod列出内核的驱动单元
查看/dev下是否生成了pin4
卸载驱动:因为加载后文件名是pin4Drivers,sudo rmmod pin4Drivers

pi@raspberrypi:~ $ ls /dev/pin4
/dev/pin4

查看设备号是不是我们设定的

pi@raspberrypi:~ $ ls /dev/pin4 -l
crw------- 1 root root 231, 0 Apr 11 04:05 /dev/pin4

虚拟文件系统会根据我们的pin4(驱动名字)的这备号mgjor,minor在驱动链表找到我们的驱动
在这里插入图片描述
3.交叉编译测试程序
*也可以在树莓派写和编译,只是在64位host上编写和编译比在arm更顺畅
a.把程序拷贝到,Ubuntu

hgs@ubuntu:~$ cp /mnt/hgfs/share/pin4test.c .

b.交叉编译

arm-linux-gnueabihf-gcc pin4test.c -o pin4test

c.拷贝到树莓派

scp pin4test pi@192.168.101.81:/home/pi

d.运行
pi@raspberrypi:~ $ ls /dev/pin4 -l
crw------- 1 root root 231, 0 Apr 11 06:26 /dev/pin4
要用超级用户权限运行
或者修改pin4的权限
sudo chmod 666 /dev/pin4
666:让所有人都可以修改的权限
pi@raspberrypi:~ $ sudo chmod 600 /dev/pin4 :改成超级用户权限才能RW
查看内核态打印的数据:dmesg

pi@raspberrypi:~ $ ./pin4test 
open filed
reson:: Permission denied
pi@raspberrypi:~ $ sudo ./pin4test 
open succeed
pi@raspberrypi:~ $ sudo chmod 666 /dev/pin4 
pi@raspberrypi:~ $ ./pin4test 
open succeed

io操控代码编程

地址

1.总线地址
百度百科:地址总线 (Address Bus;又称:位址总线) 属于一种电脑总线 (一部份),是由CPU 或有DMA 能力的单元,用来沟通这些单元想要存取(读取/写入)电脑内存元件/地方的实体位址。

实际上是cpu能够访问内存的范围
现象:装了32位的win7,明明有8G内存条,系统只识别了3.8G,只有装了64位系统,才能识别8G
32位能表示/访问: 4,294,967,296 bit,
系统只识别了3.8G还剩0.2G哪去了,用作其他用途,就像买4G sd卡,插到电脑看到也不够4G

GbitMbitKbitbit
44,0964,194,3044,294,967,296

2.物理地址
硬件实际地址或绝对地址
硬盘上排列的地址

3.虚拟地址
百度百科:虚拟地址是Windows程序时运行在386保护模式下,这样程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指段选择器。

逻辑(基于算法的地址(软件层面,假))地址

树莓派BCM2835芯片手册:

cpu32位 FFFFFFFF 4,294,967,295 (4G):因为从零开始算所以到4,294,967,295
RAM 4000000 (1G)
虚拟地址 FFFFFFFF (4G)
在这里插入图片描述
当要运行的程序大小超过一个G,是不是跑不了?
不是:ARM MMU会把物理地址ARM Physical Addresses映射成虚拟地址:ARM Virtual Addresses
上层应用程序和底层内核操作的都是虚拟地址,它有一个页表(把1M物理地址扩充成4M虚拟地址的算法),设计好页表通过MMU执行,页表决定了这1M在10~14M还是哪里表示成4M,

32为什么跑不了Linux:因为32没有MMU单元,页表的算法支持不了,
就有人对内核进行阉割,去掉MMU的单元,修改内核的计算法则变成阉割版的Linux的系统

查看树莓派芯片手册

五个寄存器

每个寄存器分别有9个io口
在这里插入图片描述

第六章:驱动
树莓派的io控件地址是0x3f000000加上gpio的偏移量是0x200000,所以GPIO的物理地址是0x3f200000开始,然后在这个基础上,进行Linux系统的MMU虚拟化管理,映射到虚拟地址上,
特别注意:BCM2708和BCM2709 的IO起始地址不同,BNC2708是0x20000000,BCM2709是0x3f000000,这是造成大部分人驱动出现"段错误"的原因.树莓派3B的cpu为BCM2709.
如果有电路图:通过电路图找到寄存器
GPFSEL0 GPIO Function Select 0 功能选择input/output(GPIO Function Select Registers (GPFSELn))
GPFSEL0"选择寄存器0"
在这里插入图片描述

  pin4 14-12
在这里插入图片描述

  000 = GPIO Pin 9 is an input
  001 = GPIO Pin 9 is an output
GPSET1 GPIO Pin Output Set 1 输出1
在这里插入图片描述

  0 = No effect
  1 = Set GPIO pin n
GPCLR0 GPIO Pin Output Clear 0 清零
在这里插入图片描述

  0 = No effect
  1 = Clear GPIO pin n

volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略
改写代码:

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

//变量的定义
static struct class *pin4_calss;
static struct device *pin4_calss_dev;

static dev_t devno;   				//设备号
static int major = 231;				//主设备号
static int minor = 0;				//次设备号
static char *module_name = "pin4";	//模块名
//选择寄存器
volatile unsigned int* GPFSEL0 = NULL;//选择第0个寄存器
volatile unsigned int* GPSET0  = NULL;//set0寄存器
volatile unsigned int* GPCLR0  = NULL;//清0寄存器
//pin4_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
	printk("pin4_open\n"); //printk:内核的打印函数,跟printf类似
	//这里是调用已经存在链表的时候GPFSEL0
	*GPFSEL0 &= ~(0x7 << 12);//把12到14位清出来,变成000
	*GPFSEL0 |= (0x1 << 12);//把12到14位写成001
	return 0;
}

//pin4_write 函数
static ssize_t pin4_write(struct file *file,const char __user *buf,ssize_t count,loff_t *pin4_fops)//上层调用write,这里就调用pin4write
{
	int userCmd;
	printk("pin4_write\n");
	//获取上层write函数的值
	//跟这个函数对应fd = write(fd,&cmd,sizeof(cmd));
	copy_from_user(&userCmd,buf,count);//参数是void* 会有警告,不用管
	//根据值来操作io口,高电平或者低电平
	printk("get value\n");
	if(userCmd == 1){
		printk("SET1\n");
		*GPSET0 |= 0x1 << 4;//为什么左移4位,根据以往开发经验
	}else if(userCmd == 0){
		printk("SET0\n");
		*GPCLR0 |= 0x1 << 4;
	}else{
		printk("undo\n");
	}
	return 0;
}
static ssize_t pin4_read(struct file *file,const char __user *buf,ssize_t count,loff_t *pin4_fops)//上层调用read,这里就调用pin4write
{
	printk("pin4_read\n");
	return 0;
}
static struct file_operations pin4_fops = {
	.owner = THIS_MODULE,
	.open  = pin4_open,
	.write = pin4_write,
	.read  = pin4_read,
	
};
int __init pin4_drv_init(void)	//真实驱动入库,insmod 的时候被调用
{
	int ret;
	printk("insmod driver pin4 success\n");
	devno = MKDEV(major,minor);	//创建设备号:参数是主设备号和次设备号
	ret = register_chrdev(major,module_name,&pin4_fops); //注册驱动告诉内核,把这个驱动加入到内核,参数分别是:主设备号,模组名"pin4",操作函数结构体
	pin4_calss = class_create(THIS_MODULE,"myfirstdemo"); //让代码在/dev下自动生成设备
	//也可以手动生成sudo mknod abc 8 1 //主动生成设备,主设备号是8,次设备号是1,名字是abc,查看是否生成了该设备ls -l
	pin4_calss_dev = device_create(pin4_calss,NULL,devno,NULL,module_name); //创建设备文件
	/*
static dev_t devno;   				//设备号
static int major = 231;				//主设备号
static int minor = 0;				//次设备号
static char *module_name = "pin4";	//模块名*/

	//这里是加入链表时的GPFSEL0
	//把物理地址映射到虚拟地址
	GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);//物理地址转换成虚拟地址,io口寄存器映射成普通内存单元进行访问
 	GPSET0  = (volatile unsigned int *)ioremap(0x3f20001C,4);//volatile和强转是一个整体要一起写进括号,volatile一定要系统如实写入数据
 	GPCLR0  = (volatile unsigned int *)ioremap(0x3f200028,4);//这里的4 ,是size_t 4,4个字节32位
	return 0;
}

void __exit pin4_drv_exit(void)
{
	iounmap(GPFSEL0);//解除虚拟地址的映射
	iounmap(GPSET0);
	iounmap(GPCLR0);
	device_destroy(pin4_calss,devno);//销毁设备
	class_destroy(pin4_calss);//销毁类
	unregister_chrdev(major,module_name); //卸载驱动
}

module_init(pin4_drv_init);		//入口,内核加载该驱动的时候,这个宏会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

在树莓派可以用gpio readall查看对io的操作是否成功

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值