驱动开发(字符设备驱动)

前景了解

裸机代码
没有移植嵌入式实时操作系统的源代码,我们称之为裸机代码,托管在Github。

裸机代码的主要功能: 各传感器信号的采集、处理; 电机输出控制; 车模运行控制:直立控制、速度控制、方向控制;
车模运行流程控制:程序初始化、车模启动与结束、车模状态监控; 车模信息显示与参数设定:状态显示、上位机监控、参数设定等。

驱动
是计算机软件术语,指驱动计算机里软件的程序,其主要作用是计算机系统与硬件设备之间完成数据传送的功能。

ARM裸机代码和驱动的区别
共同点:
都能控制硬件
不同点:
arm裸机开发
单独编译单独执行,arm裸机同时只能执行一份代码,arm裸机只需要一个main就可以了,在main函数中写,相应的逻辑代码即可。
驱动
依赖内核编译,依赖内核执行
驱动可以同时执行多分代码
驱动是依赖内核框架和操作硬件的过程

APP:(0-3G)写的应用程序
kernel(内核): (3-4G)
内核的5种功能
进程管理:进程的创建、销毁、调度等功能。
内存管理:通过内存管理器对用户空间和内核空间内存的申请和释放。
网络管理:通过网络协议栈对数据进程封装和拆解过程。
文件管理:通过文件系统ext2/ext3/ext4 yaff jiffs等来组织管理文件。
设备管理:设备驱动管理
字符设备管理:
按照字节为单位进行访问,顺序访问
会创建设备文件,open read write close
块设备管理:
按照块(512字节)扇区访问,可以顺序访问,可以无序访问,会创建设备文件,open read write close来访问。
网卡设备管理:
按照网络数据包来收发的。

内核的类型
宏内核:将进程,网络,文件,设备,内存等功能集成到内核中
特点:代码运行效率高 缺点:如果有一个部分出错整个内核崩溃了
eg:Android Ubuntu
微内核:只将进程,内存机制集成到这个内核中,文件、设备、驱动在操作系统之外
通过API接口让整个系统运行起来
缺点:代码效率低 优点:稳定性强
eg:鸿蒙 Window QNX
驱动移植
一般所谓驱动移植,就是将厂商的驱动源码拿来改一改,编译成自己系统版本适配的驱动。
需要遵循驱动规则
入口(加载):资源的申请
出口(卸载):资源的释放
许可证:GPL

驱动移植

1.需要一个驱动对应的.C代码,我的理解是自己写的内核驱动源码,而这代码需要遵循驱动规则。
2.找到内核中的驱动程序(drivers)文件夹,找到对应的设备驱动,比如char(字符设备驱动)。
修改Kconfig
回到顶层(内核目录第一层)执行make menuconfig(配置内核 ,也是找到对应的驱动,对应的字符设备,修改其状态(选中,未选中,模块(modules)))
配置.config
配置Makefile
3.编译
make uImage–>(产生包含新驱动的内核),就可以进行内核移植了。
make modules–>demo.ko(生成驱动模块),也可以自己写一个Makefile(内包含modules)生成驱动模块。
4.测试
sudo insmod demo.ko 安装驱动
sudo rmmod demo 卸载驱动

根据驱动规则写驱动模块

入口(加载):资源的申请
出口(卸载):资源的释放
许可证:GPL

内核提供的API

#include <linux/module.h>
#include <linux/init.h>
static int __init hello_init(void)//入口
{
 return 0; 
}
static void __exit hello_exit(void)//出口
{
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

如何编译,需要借助内核编译,自写Makefile

KERNELDIR:= /lib/modules/$(shell uname -r)/build/  		#linux系统下的内核
#KERNELDIR:= /home/hq/linux6818/kernel/kernel-3.4.39/	#自己安装的ARM架构下(移植在板子上的内核)
PWD:=$(shell pwd)
all: 
    make -C $(KERNELDIR) M=$(PWD) modules						#生成驱动模块
clean:
    make -C $(KERNELDIR) M=$(PWD) clean							#清除
obj-m:=hello.o 	

Makefile里的小知识
= 赋值 需要等其他文件全部执行完,才执行调用的
:= 立即赋值
+= 附加赋值
?= 询问变量之前是否被赋值过,如果被赋值过本次赋值不成立,否则成立。

内核中的打印函数

和c语言类似,不过前面多了一个打印级别

printk(打印级别 "内容")
printk(KERN_ERR "Fail%d",a);
printk(KERN_ERR "%s:%s:%d\n",__FILE__,__func__,__LINE__);
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

追加查看级别:vi -t KERN_ERR

include/linux/printk.h	
	#define KERN_EMERG  "<0>"   /* system is unusable           */
	#define KERN_ALERT  "<1>"   /* action must be taken immediately */
	#define KERN_CRIT   "<2>"   /* critical conditions          */
	#define KERN_ERR    "<3>"   /* error conditions         */
	#define KERN_WARNING    "<4>"   /* warning conditions           */
	#define KERN_NOTICE "<5>"   /* normal but significant condition */
	#define KERN_INFO   "<6>"   /* informational            */
	#define KERN_DEBUG  "<7>"   /* debug-level messages         */ 	

0 ------ 7
最高的 最低的
数字越小级别越高,只有当消息的级别大于终端级别,消息才会被显示
也可使用回显命令查看:dmesg

查看终端级别与消息级别:
在这里插入图片描述修改系统默认的级别
su root //进入超级用户模式
echo 4 3 1 7 /proc/sys/kernel/printk //修改

驱动测试2(多文件编译,利用的是linux内核

  1. .c代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/printk.h>、
extern int add(int a, int b);
static int __init hello_init(void)
{
 printk("%d\n",add(2,4));  
 return 0; 
}
static void __exit hello_exit(void)
{
 printk(KERN_ERR "bai bai\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
  1. Makefile
    按照之前写的Makefile执行make,生成驱动模块.ko。
  2. 安装驱动,执行入口函数。
  3. 输入消息回显命令:dmesg。查看终端是否打印nihao。
  4. 卸载驱动,执行出口函数。
  5. 输入消息回显命令:dmesg。查看终端是否打印bai。
  6. 驱动测试1(打印,利用的是linux内核)

  7. .c代码
//使用者模块
#include <linux/module.h>
#include <linux/init.h>
static int __init hello_init(void)//入口
{
 printk(KERN_ERR "nihao\n");
 return 0; 
}
static void __exit hello_exit(void)//出口
{
 printk(KERN_ERR "bai\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
//提供者模块
int add(int a,int b)
{
 return (a+b);
}
  1. Makefile
KERNELDIR= /lib/modules/$(shell uname -r)/build/
#KERNELDIR:= /home/linux6818/kernel/kernel-3.4.39/
PWD=$(shell pwd)
all:
	make -C $(KERNELDIR) M=$(PWD) modules
clean:
	make -C $(KERNELDIR) M=$(PWD) clean

obj-m:=demo.o
demo-y+=hello.o add.o

生成demo.ko驱动模块
3. 安装驱动,执行入口函数。
4. 输入消息回显命令:dmesg。查看终端是否打印2和4相加的值。
5. 卸载驱动,执行出口函数。
6. 输入消息回显命令:dmesg。查看终端是否打印bai bai。

扩充

module_param(b,int,0664);
MODULE_PARM_DESC(_parm, desc)
功能:对变量进行描述
_parm:变量的名字
desc:描述的文字

第一模块是提供者:提供add
写个add函数
使用导出符号表:EXPORT_SYMBOL_GPL(add);

简单字符设备驱动

内核驱动层

入口:
  1. 注册一个字符设备驱动

int register_chrdev(unsigned int major, const char name, const struct file_operations fops) 功能:注册一个字符设备驱动。
参数:
major主设备号
:如果你填写的值大于0,它认为这个就是主设备号。
:如果你填写的值为0,操作系统给你分配一个主设备号。
cat /proc/devices 查看设备名和主设备号

name: 节点名字
fops : 操作方法结构体
自定义方法为应用提供接口
返回值:
major>0 ,成功返回0(主设备号),失败返回错误码(负数)

  1. 手动创建设备节点:

sudo mknod hello(你要打开的文件名称) c/b(字符设备 块设备) 主设备号 次设备号

eg:sudo mknod hello c 244 0

  1. 判断注册驱动设备是否成功。
  2. 然后写注册驱动函数里第三个参数那个结构体函数。
  3. 然后写自己的open read write close ,函数名根据自己情况编写。
  4. 然后把自己写的open read write close函数,赋值给结构体内的open read write release。(为应用提供接口)
出口:
  1. 出口内写注销设备函数。
代码块
#include <linux/module.h>		//根据所用到的内核API添加所对应的头文件
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/fs.h>

#define CNAME "hello"		//节点名字
int major=0;

ssize_t mycdev_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs)	//自写函数为应用层提供接口
{
  printk("read\n");
  return 0;
}
ssize_t mycdev_write(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
  printk("write\n");
  return 0;
}
int mycdev_open (struct inode *node, struct file *file)
{
  printk("open\n");
  return 0;
}
int  mycdev_release (struct inode *node, struct file *file)
{
  printk("open\n");
  return 0;
}

const struct file_operations fops={		//结构体,注册字符设备第三个参数
 .open=mycdev_open,
 .write=mycdev_write,
 .read=mycdev_read,
 .release=mycdev_release,
};

static int __init hello_init(void)//入口
{
  major=register_chrdev(major,CNAME,&fops);	//注册字符设备
  if(major<0)
  {
    printk("register_chrdev error\n");
	return major; 
  }
  return 0; 
}
static void __exit hello_exit(void)//出口
{
 unregister_chrdev(major,CNAME);	//注销字符设备设备
 printk(KERN_ERR "bai bai\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

应用层

代码块
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
char buf[128]={0};
int main(int argc, const char *argv[])
{
	int fd;
	fd=open("./hello",O_RDWR);	//根据对应的方法打开节点
	if(fd==-1)
	{
	 perror("open error");
	 return -1;
	}
	write(fd,buf,sizeof(buf));
	read(fd,buf,sizeof(buf));
	close(fd);
	return 0;
}

问:如何知道内核的API,下面是内核API完全手册。
https://weread.qq.com/web/reader/71d329705cd6d571d176fea?
问:如何快速的写内核驱动代码
通过软件Source Insight 3,添加内核源码后,追加代码。在这里插入图片描述

应用程序如何将数据传递给驱动(依据上面的简单字符设备驱动)

内核驱动所需要的API

int copy_from_user(void *to, const void __user *from, int n)
功能:从用户空间拷贝数据到内核层
to:内核空间首地址
from:用户空间首地址
n:拷贝数据长度

int copy_to_user(void __user *to,const void *from, unsigned long n)
功能:从内核层拷贝数据到用户层
to:用户空间首地址
from:内核空间首地址
n:拷贝数据长度

实验目的:

用户向内核写一个数据,内核打印出来,用户再读出这个数据,用户打印出来。

驱动内核层

在mycdev_write函数里,写用户层给内核层的数据。
打印出来
在 mycdev_read函数里,读取内核层kbuf里的数据。
加头文件:#include <linxu/uaccess.h>

代码块

#include <linux/module.h>
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linxu/uaccess.h>
#define CNAME "hello"
int major=0;
char kbuf[128]={0};
ssize_t mycdev_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
  printk("read\n");
  if(size>128){size=128;};
  copy_to_user(ubuf,kbuf,size);
  return 0;
}
ssize_t mycdev_write(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
  if(size>128){size=128;};
  copy_from_user(kbuf,ubuf,size);
  printk("write %s\n",kbuf);
  return 0;
}
int mycdev_open (struct inode *node, struct file *file)
{
  printk("open\n");
  return 0;
}
int  mycdev_release (struct inode *node, struct file *file)
{
  printk("open\n");
  return 0;
}
const struct file_operations fops={
 .open=mycdev_open,
 .write=mycdev_write,
 .read=mycdev_read,
 .release=mycdev_release,
};
static int __init hello_init(void)//入口
{
  major=register_chrdev(major,CNAME,&fops);
  if(major<0)
  {
    printk("register_chrdev error\n");
	return major; 
  }
  return 0; 
}
static void __exit hello_exit(void)//出口
{
 unregister_chrdev(major,CNAME);
 printk(KERN_ERR "bai bai\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
用户层

写个向内核写数据的函数write,
清空buf,
读read内核层里的数据
打印printf

代码块

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
char buf[128]={"dsdsd"};
int main(int argc, const char *argv[])
{
	int fd;
	fd=open("./hello",O_RDWR);
	if(fd==-1)
	{
	 perror("open error");
	 return -1;
	}
	write(fd,buf,sizeof(buf));
	memset(buf,0,sizeof(buf));
	read(fd,buf,sizeof(buf));
	printf("sds= %s\n",buf);
	close(fd);
	return 0;
}

控制开发板上的LED 灯(板子上移植的内核,自己移植的内核)

驱动如何操作寄存器

rgb_led灯的寄存器是物理地址,在linux内核启动之后,
在使用地址的时候,操作的全是虚拟地址。需要将物理地址
转化为虚拟地址。在驱动代码中操作的虚拟地址就相当于
操作实际的物理地址。
物理地址<------>虚拟地址

所需的API

void * ioremap(phys_addr_t offset, unsigned long size)
功能:将物理地址映射成虚拟地址
参数:
@offset :要映射的物理地址
@size :大小(字节)
返回值:成功返回虚拟地址,失败返回NULL;

void iounmap(void *addr)
功能:取消映射
参数:
@addr :虚拟地址
返回值:无

需要配置的寄存器

GPIOxOUTENB:输入输出模式
(根据你要操作的GPIO口,和设备判断是输入还是输出,如:灯为输出设备,传感器为输入设备)
GPIOxALTFN1:function寄存器
(因为芯片外界的引脚很少,用到引脚复用,每个引脚可能有多种功能,选择对应的GPIO功能)
GPIOXOUT :控制高低电平的
(灯为输出设备,根据底板原理图判定输出高电平为灯亮还是输出低电平为亮)
我们这里是控制的三个灯,找到对应得地址:
(根据底本原理图,核心原理图和用户手册得到)
GPIOA28(红灯)
GPIOXOUT:0xC001A000
GPIOxOUTENB:0xC001A004
GPIOxALTFN1:0xC001A024
GPIOE13(绿灯)
GPIOXOUT:0xC001E000
GPIOxOUTENB:0xC001E004
GPIOxALTFN0:0xC001E020
GPIOB12(蓝灯,方式二(10))
GPIOXOUT:0xC001B000
GPIOxOUTENB:0xC001B004
GPIOxALTFN0:0xC001B020

内核层代码块:借助上面学过得进一步学习
//入口:首先开辟一个设备节点用于内核与用户层交互,将物理地址映射成虚拟地址,物理地址就是各个寄存器得地址,根据用户所传输得到得控制。

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define CNAME "hello"
#define RED_BASE 0xC001A000		//红灯
#define BLUE_BASE 0xC001B000	//蓝灯
#define GREEN_BASE 0xC001E000	//绿灯
unsigned int *red_base = NULL;
unsigned int *blue_base = NULL;
unsigned int *green_base = NULL;
#define LED1_ON *red_base &=~(1<<28);		//控制得开关
#define LED1_OF *red_base |=(1<<28);
#define LED2_ON *red_base &=~(1<<12);
#define LED2_OF *red_base |=(1<<12);
#define LED3_ON *red_base &=~(1<<13);
#define LED3_OF *red_base |=(1<<13);
int major=0;
int dev;
char kbuf[3]={0};	//交互得字符串数组
int mycdev_open (struct inode *inode, struct file *file)
{
  return 0;
}
ssize_t mycdev_read (struct file *file, char __user *ubuf,
	size_t size, loff_t *offs)
{
  dev=copy_to_user(ubuf ,kbuf,size);	从内核层拷贝数据到用户层

  if(dev)
  	{
	  printk("copy to user errer");
	  return -EINVAL;
    }
  return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, 
	size_t size, loff_t *offs)
{
  dev=copy_from_user(kbuf, ubuf,size);	//从用户空间拷贝数据到内核层
  if(dev)
  	{
	  printk("copy to user errer");
	  return -EINVAL;
    }
  if(kbuf[0]==0)
  	{
     LED1_OF;
  }
  else{ LED1_ON;}
  if(kbuf[1]==0)
  {
   LED2_OF;
  }else
  {
    LED2_ON;
  }
  return 0;
}
int mycdev_release (struct inode *inode, struct file *file)
{
  return 0;
}
const struct file_operations fops={
	.open=mycdev_open,
	.read=mycdev_read,
	.write=mycdev_write,
	.release=mycdev_release,
};
static int __init mycdev_init(void)
{
  major=register_chrdev(major,CNAME,&fops);		//注册字符设备节点
  if(major<0)
  	{
 		printk("register char device error\n");
		return major;
  	}
  red_base = ioremap(RED_BASE,36);			//将物理地址映射为虚拟地址
  if(red_base == NULL){
	  printk("red ioremap error\n");
	  return -ENOMEM;
  }
  blue_base = ioremap(BLUE_BASE,36);
  if(blue_base == NULL){
	  printk("blue ioremap error\n");
	  return -ENOMEM;
  }
  green_base = ioremap(GREEN_BASE,36);
  if(green_base == NULL){
	  printk("green ioremap error\n");
	  return -ENOMEM;
  }
  *red_base &= ~(1<<28);	//初始化载灯
  *(red_base+1) |= (1<<28);
  *(red_base+9) &= ~(3<<24);
  *blue_base &= ~(1<<12);
  *(blue_base+1) |= (1<<12);
  *(blue_base+8) &= ~(3<<24);
  *(blue_base+8) |= (1<<25);
  *green_base &= ~(1<<13);
  *(green_base+1) |= (1<<13);
  *(green_base+8) &= ~(3<<26);
  return 0;
  }
static void __exit mycdev_exit(void)
{
  iounmap(red_base);		//取消映射
  unregister_chrdev(major,CNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

应用代码块
//在main函数里面写延时一秒不断的去写节向内核发送从而控制设备。

sleep(1);
buf[0]=buf[0]?0:1;
while(1)
	 {
    	write(fd,buf,sizeof(buf));
		sleep(1);
		buf[0]=buf[0]?0:1;
	 }

Makefile代码块
//这里用的是自己移植的那个内核,所以用其编译

KERNELDIR:= /home/hq/linux6818/kernel-3.4.39/ #ARM内核编译
PWD:=$(shell pwd)
all:
	make -C $(KERNELDIR) M=$(PWD) modules
	arm-none-linux-gnueabi-gcc text.c		#用户层也需要用不同的编译器编译,架构不同编译器不同
	cp *.ko a.out /opt/6818/rootfs/rootfs			#将编译后和内核驱动模块,和用户编译后的可以执行文件放在nfs(可以挂载共享使用)
clean:
	make -C $(KERNELDIR) M=$(PWD) clean
obj-m:=hello.o

补充

设备节点创建问题(udev/mdev)
#include <linux/device.h>
自动创建设备节点:
struct class *cls;
cls = class_create(owner, name)	/void class_destroy(struct class *cls)
功能:向用户空间提交目录信息
参数:
	@owner :THIS_MODULE
	@name  :目录名字
返回值:成功返回struct class *指针
		失败返回错误码指针 int (-5)
if(IS_ERR(cls)){
	
}		
struct device *device_create(struct class *class, struct device *parent,
				 dev_t devt, void *drvdata, const char *fmt, ...)
		/void device_destroy(struct class *class, dev_t devt)
功能:向用户空间提交文件信息
参数:
	@class :目录名字     cls
	@parent:NULL
	@devt  :设备号    MKDEV(major,0)
	@drvdata :NULL
	@fmt   :文件的名字
返回值:成功返回struct device *指针
		失败返回错误码指针 int (-5)
ioctl函数

注:用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道(功能:input output 的控制)
用户:
#include <sys/ioctl.h>
int ioctl(int fd, int request, …);(RED_ON)
(让点灯的代码变得简洁)
参数:
@fd : 打开文件产生的文件描述符
@request: 请求码(读写|第三个参数传递的字节的个数),
:在sys/ioctl.h中有这个请求码的定义方式。
@… :可写、可不写,如果要写,写一个内存的地址
内核:
long (*unlocked_ioctl) (struct file *, unsigned int,unsigned long)
作用:此函数指针原型位于struct file_operations结构体当中,配合应用层ioctl函数实现指令传递的功能
发现这些函数都是封装的_IOC,这个里面的第一个内容观察发现,读写可读可写(方向的不同)
看第一个可以看出这个请求码是由四个部分组成的,第一个是方向
这里再看最后一个,表示本次是读还是写的字节的长度
跳转_IOC查看封装的具体内容
#define _IOC(dir,type,nr,size)
(((dir) << _IOC_DIRSHIFT) |
((type) << _IOC_TYPESHIFT) |
((nr) << _IOC_NRSHIFT) |
((size) << _IOC_SIZESHIFT))
得出结果:
dir << 30 | size<<16 | type << 8 | nr << 0
2 14 8 8
方向 大小 类型 序号
(方向:00 01 10 11读写相关,)
(大小:sizeof(变量名))
(类型:组合成一个唯一的不重合的整数,一般传一个字符)
(序号:表示同类型中的第几个,当开灯的时候写0,那关的时候就不写0)。
_IOWR(type,nr,size) 类型 序号 大小
#define RLED_ON _IO(‘a’,0)//亮灯
#define RLED_OFF _IO(‘a’,1)//灭灯
总结:
首先:
封装个命令码-》 ON _IO(‘a’,0) OF _IO(‘a’,1)
然后:
结构体里找 long (*unlocked_ioctl) 复制到自己的文件里重新写个自己的
把自己写的 long (*unlocked_ioctl) 指定给fops-》.unlocked_ioctl = mycdev_ioctl,
填充自己写的 long (*unlocked_ioctl) 函数里的东西
switch(request){
case RLED_ON:
*GPIOA_BASE |= (1<<28);
break;
case RLED_OFF:
*GPIOA_BASE &= ~(1<<28);
break;
}
最后出口里:把映射 设备注销掉

应用test:
发命令码控制开关
while(1){
ioctl(fd,RLED_ON);
sleep(1);
ioctl(fd,RLED_OFF);
sleep(1);
}

Linux内核中断

原理:
start .s 里有异常向量表
外部中断找IRQ,根据IRQ的中断地址。找到我们使用的函数,比如叫do_irq
然后判断我们的中断号,看我们触发了哪一个中断。
处理中断
清中断
内核: 找到IRQ的标签,然后跳转,跳转时这个名字是写死的(handle_irq),在handle_irq里定义一个数组,irq_desc[],数组的每一个成员变量里存了结构体,irq_xxx。在结构体里面有个函数指针,这个指针指向了我们函数的名字。数组的下标和中断号有关系,这里是中断号但是是软中断号。软中断号是linux内核给分配的中断号。
软中断号,是内核为了兼容各种芯片,而设计的。这里兼容是通过映射实现的,我们不同的板子根据映射关系,使用中断号得到软中断号。
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char name, void dev)
功能:注册中断
参数:
@irq : 软中断号 gpio的软中断号 软中断号 = gpio_to_irq(gpio号);
gpio = m
32+n m:那一组 A B C D E 0 1 2 3 4
n:组内的序号
gpioa28 = 0
32+28 (按键)gpiob8 = 132+8
gpiob8 =1
32+8 (按键) gpiob16 = 1*32+16
控制器中断号(如ADC):
find -name irqs.h ./arch/arm/mach-s5p6818/include/mach/irqs.h
find -name s5p6818_irq.h ./arch/arm/mach-s5p6818/include/mach/s5p6818_irq.h
#define IRQ_PHY_ADC (41 + 32) //IRQ_PHY_ADC软中断号
@handler: 中断的处理函数
irqreturn_t (*irq_handler_t)(int irqno, void *dev);
IRQ_NONE //中断没有处理完成
IRQ_HANDLED //中断正常处理完成
@flags :中断的触发方式
#define IRQF_DISABLED 0x00000020 //快速中断
#define IRQF_SHARED 0x00000080 //共享中断
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
@name :名字 cat /proc/interrupts
@dev :向中断处理函数中传递参数 ,如果不想传写个NULL就行
返回值:成功0,失败返回错误码
void free_irq(unsigned int irq, void *dev_id)
功能:注销中断
参数:
@irq :软中断号
@dev_id:向中断处理函数中传递的参数 ,如果上面写的NULL,这里就写NULL
问题解决方法
[root@farsight]#insmod farsight_irq.ko
[ 21.262000] request irq146 error
insmod: can’t insert ‘farsight_irq.ko’: Device or resource busy
通过 cat /proc/interrupts
146: GPIO nxp-keypad
154: GPIO nxp-keypad
说明中断号已经被占用了
解决办法:在内核中将这个驱动删掉

如果确定驱动文件的名字是谁?
grep "nxp-keypad" * -nR
arch/arm/mach-s5p6818/include/mach/devices.h:48:
#define DEV_NAME_KEYPAD         "nxp-keypad"

grep "DEV_NAME_KEYPAD" * -nR
drivers/input/keyboard/nxp_io_key.c:324:		.name	= DEV_NAME_KEYPAD,
驱动文件的名字是nxp_io_key.c
如何从内核中将他去掉?
选项菜单的名字?Kconfig
config KEYBOARD_NXP_KEY
    tristate "SLsiAP push Keypad support"
make menuconfig
		<>SLsiAP push Keypad support
make uImage  重新编译内核
cp  arch/arm/boot/uImage ~/tftpboot

Linux内核定时器
1.定时器的当前时间如何获取?
jiffies:内核时钟节拍数
jiffies是在板子上电这一刻开始计数,只要
板子不断电,这个值一直在增加(64位)。在
驱动代码中直接使用即可。
2.定时器加1代表走了多长时间?
在内核顶层目录下有.config
CONFIG_HZ=1000
周期 = 1/CONFIG_HZ
周期是1ms;
1.分配的对象
struct timer_list mytimer;
2.对象的初始化
struct timer_list
{
unsigned long expires; //定时的时间
void (*function)(unsigned long); //定时器的处理函数
unsigned long data; //向定时器处理函数中填写的值
};
void timer_function(unsigned long data) //定时器的处理函数
{
}
mytimer.expries = jiffies + 1000; //1s
mytimer.function = timer_function;
mytimer.data = 0;
init_timer(&mytimer); //内核帮你填充你未填充的对象
3.对象的添加定时器
void add_timer(struct timer_list *timer);
//同一个定时器只能被添加一次,
//在你添加定时器的时候定时器就启动了,只会执行一次
int mod_timer(struct timer_list *timer, unsigned long expires)
//再次启动定时器 jiffies+1000
4.对象的删除
int del_timer(struct timer_list *timer)//删除定时器
补充扩展
writel(v,c)
功能:向地址中写一个值
参数:
@ v :写的值
@ c :地址
readl©
功能:读一个地址,将地址中的值给返回
参数:
@c :地址
例子:writel((readl(red_base)&(~(1<<28))),red_base);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值