通过字符设备驱动点亮板子上的led灯
app: test.c char buf[3]
1 0 0
0 1 0
0 0 1
------------------|------------------------
kernel: led_driver.c
-------------------|------------------------
hardware: RGB_led
- 应用程序如何将数据传递给驱动(读写的方向是站在用户的角度来说的)
函数
1)从用户空间拷贝数据到内核空间(用户需要写数据的时候)
#include <linux/uaccess.h>
int copy_from_user(void *to, const void __user *from, int n)
功能:从用户空间拷贝数据到内核空间(用户需要写数据的时候)
参数:
@to :内核中内存的首地址
@from:用户空间的首地址
@n :拷贝数据的长度(字节)
返回值:成功返回0,失败返回未拷贝的字节的个数
2)从内核空间拷贝数据到用户空间(用户开始读数据)
int copy_to_user(void __user *to, const void *from, int n)
功能:从内核空间拷贝数据到用户空间(用户开始读数据)
参数:@to :用户空间内存的首地址@from:内核空间的首地址@n :拷贝数据的长度(字节)
返回值:成功返回0,失败返回未拷贝的字节的个数
驱动如何操作寄存器
rgb_led灯的寄存器是物理地址,在linux内核启动之后,在使用地址的时候,操作的全是虚拟地址。需要将物理地址转化为虚拟地址。在驱动代码中操作的虚拟地址就相当于操作实际的物理地址。
物理地址<---转化--->虚拟地址
3)将物理地址映射成虚拟地址
虚拟首地址 = ioremap(物理基地址,字节大小)
void * ioremap(phys_addr_t offset, unsigned long size)
(当__iomen告诉编译器,取的时候是一个字节大小)
功能:将物理地址映射成虚拟地址
参数:
@offset :要映射的物理的首地址
@size :大小(字节)(映射是以业为单位,一页为4K,就是当你小于4k的时候映射的区域都为4k)
返回值:成功返回虚拟地址,失败返回NULL((void *)0);
4)取消映射
void iounmap(void *addr)
功能:取消映射
参数:
@addr :虚拟首地址
返回值:无
#define ENOMEM 12 /* Out of memory */
操作步骤
1、//通过虚拟地址对寄存器更改设置值,实行对硬件的初始化
用户使用发送对硬件控制的指令给底层驱动,底层驱动驱动硬件做对应动作:
应用层write,驱动对应write获取到传送的指令:
copy_from_user(内核空间首地址,用户空间地址,拷贝字节数);
2、 //将用户空间数据拷贝到内核空间
应用层read,驱动对应read需要将内核空间数据拷贝用户空间:
copy_to_user(用户空间地址,内核空间首地址,拷贝字节数);、
Eg:点灯
- 软件编程控制硬件的思想:
只需要向控制寄存器中写值或者读值,就可以让我们处理器完成一定的功能。
RGB_led
1》GPIOxOUT:控制引脚输出高低电平
RED_LED--->GPIOA28
GPIOAOUT ---> 0xC001A000
GPIOA28输出高电平:
GPIOAOUT[28] <--写-- 1
GPIOA28输出低电平:
GPIOAOUT[28] <--写-- 0
2》GPIOxOUTENB:控制引脚的输入输出模式
GPIOAOUTENB ---> 0xC001A004
设置GPIOA28引脚为输出模式:
GPIOAOUTENB[28] <--写-- 1
3》GPIOxALTFN:控制引脚功能的选择
GPIOAALTFN1 ---> 0xC001A024
设置GPIOA28引脚为GPIO功能:
GPIOAALTFN1[25:24] <--写-- 0b00
00 = ALT Function0
01 = ALT Function1
10 = ALT Function2
11 = ALT Function3
GPIO引脚功能的选择:每两位控制一个GPIO引脚
red :gpioa28
GPIOXOUT :控制高低电平的 0xC001A000
GPIOxOUTENB:输入输出模式 0xC001A004
GPIOxALTFN1:function寄存器 0xC001A024
(一个寄存器36个字节)
green:gpioe13
0xC001e000
blue :gpiob12
0xC001b000
练习:
1.字符设备驱动实现流水灯(30分钟)
//读,改,写
writel(v,c)
功能:向地址中写一个值
参数:
@ v :写的值
@ c :地址
readl(c)
功能:读一个地址,将地址中的值给返回
参数:
@c :地址
七色LED流水灯驱动
内核层 led驱动函数
#include <linux/module.h>
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#define NAME "chrdev_devled"
//定义宏保存物理地址基地址
#define RED_BASE 0xc001a000
#define GRE_BASE 0xc001e000
#define BLU_BASE 0xc001b000
//定义指针保存映像后的虚拟地址首地址
unsigned int *red_addr = NULL;
//定义指针保存映像后的虚拟地址首地址
unsigned int *gre_addr = NULL;
//定义指针保存映像后的虚拟地址首地址
unsigned int *blu_addr = NULL;
int size=32;
int major = 0; //保存主设备号
char kbuf[32];//
char kbuf_r[32]="welcome to hqyj";
// open read write release 初始化
int myopen(struct inode *node, struct file *file_t)
{printk("%s %s %d\n", __FILE__, __func__, __LINE__);return 0;
}
ssize_t myread(struct file *file_t, char __user *ubuf, size_t n, loff_t *off_t)
{printk("%s %s %d\n", __FILE__, __func__, __LINE__);//将内核空间数据拷贝到用户空间if(sizeof(kbuf_r)<size)
size=sizeof(kbuf_r);if(copy_to_user(ubuf,kbuf_r,size)!=0){printk("copy_to_user err.");return -EINVAL;}// printk("kbuf=%s\n",kbuf);return 0;
}
ssize_t mywrite(struct file *file_t, const char __user *ubuf, size_t n, loff_t *off_t)
{printk("%s %s %d\n", __FILE__, __func__, __LINE__);//将用户空间—(ubuf) 的数据拷贝到内核空间(kbuf)if(sizeof(kbuf)<size)
size=sizeof(kbuf);if(copy_from_user(kbuf,ubuf,size)!=0){printk("copy_from_user err.");return -EINVAL;}printk("kbuf=%s\n",kbuf);if(kbuf[0]==1){//红灯开*red_addr |= (1<<28);}else if(kbuf[0]==0){//红灯关*red_addr &= (~(1<<28));}if(kbuf[1]==1){//红灯开*gre_addr |= (1<<13);}else if(kbuf[1]==0){//红灯关*gre_addr &= (~(1<<13));}if(kbuf[2]==1){//蓝灯开*blu_addr |= (1<<12);}else if(kbuf[2]==0){//蓝灯关*blu_addr &= (~(1<<12));}return 0;
}
int myclose(struct inode *node, struct file *file_t)
{printk("%s %s %d\n", __FILE__, __func__, __LINE__);return 0;
}
struct file_operations fops = {.open = myopen,.read = myread,.write = mywrite,.release = myclose,
};
//入口函数
static int __init chrdev_init(void)
{printk("%s %s %d\n", __FILE__, __func__, __LINE__);//注册字符设备驱动: 主设备号 驱动名 结构体
major=register_chrdev(major, NAME, &fops);//容错判断if(major < 0){printk("chrdev_register err.\n");return -EINVAL;}//建立虚拟地址和物理地址之间的映射关系——控制红灯
red_addr = (unsigned int *)ioremap(RED_BASE,40);if(red_addr == NULL){printk("ioremap red err.\n");return -EINVAL;}//初始化红灯*(red_addr+9) &=(~(3<<24));//选择GPIOA28功能*(red_addr+1) |=(1<<28);//选择输出使能*red_addr &=(~(1<<28));//红灯关闭//建立虚拟地址和物理地址之间的映射关系——控制绿灯
gre_addr = (unsigned int *)ioremap(GRE_BASE,40);if(red_addr == NULL){printk("ioremap red err.\n");return -EINVAL;}//初始化绿灯*(gre_addr+8) &=(~(3<<26));//选择GPIOE13功能*(gre_addr+1) |=(1<<13);//选择输出使能*gre_addr &=(~(1<<13));//绿灯关闭//建立虚拟地址和物理地址之间的映射关系——控制蓝灯
blu_addr = (unsigned int *)ioremap(BLU_BASE,40);if(blu_addr == NULL){printk("ioremap red err.\n");return -EINVAL;}//初始化蓝灯*(blu_addr+8) &=(~(0<<24));//选择GPIOB12功能*(blu_addr+8) |=(1<<25);//选择GPIOB12功能*(blu_addr+1) |=(1<<12);//选择输出使能*blu_addr &=(~(1<<12));//蓝灯关闭return 0;
}
//出口函数
static void __exit chrdev_exit(void)
{printk(KERN_ERR "%s %s %d\n", __FILE__, __func__, __LINE__);//取消映射iounmap(red_addr);//注销字符设备驱动unregister_chrdev(major, NAME);
}
module_init(chrdev_init);//入口
module_exit(chrdev_exit);//出口
MODULE_LICENSE("GPL");//协议
Makefile
KERNEL_PATH=/home/hq/kernel/kernel-3.4.39 #开发板的路径
#KERNEL_PATH=/lib/modules/$(shell uname -r)/build #虚拟机路径PWD=$(shell pwd) #将shell命令pwd执行的结果赋值给PWD变量
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
# -C 路径:指定到切换到那个路径,执行make modules命令
# M=路径:指定需要编译的驱动代码所在的路径.PHONY:clean
clean:
make -C $(KERNEL_PATH) M=$(PWD) cleanobj-m += chrdev.o#要编译生成驱动模块的目标程序
应用层
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>#include <stdio.h>void delay()
{int i;while(i<100){
i++;}
}
int main(int argc,char *argv[])
{int fd = open(argv[1],O_RDWR);if(fd < 0){printf("open %s failed\n",argv[1]);return 2;}char buf[32] = {1,0,0};int i=0;int j=0;int z=0;while(1){write(fd,buf,sizeof(buf));// for(;i<3;i++)// {// buf[0]=buf[0]?0:1;// delay();// for(;j<2;j++)// {// buf[1]=buf[1]?0:1;// delay();// for(;z<1;z++)// {// buf[2]=buf[2]?0:1;// delay();// }// }// }sleep(1);
buf[0]=1;
buf[1]=0;
buf[2]=0;write(fd,buf,sizeof(buf));sleep(1);
buf[0]=0;
buf[1]=1;
buf[2]=0;write(fd,buf,sizeof(buf));sleep(1);
buf[0]=0;
buf[1]=0;
buf[2]=1;write(fd,buf,sizeof(buf));sleep(1);
buf[0]=1;
buf[1]=1;
buf[2]=0;write(fd,buf,sizeof(buf));sleep(1);
buf[0]=1;
buf[1]=0;
buf[2]=1;write(fd,buf,sizeof(buf));sleep(1);
buf[0]=0;
buf[1]=1;
buf[2]=1;write(fd,buf,sizeof(buf));sleep(1);
buf[0]=1;
buf[1]=1;
buf[2]=1;write(fd,buf,sizeof(buf));// sleep(1);// // buf[2]=buf[2]?0:1;// buf[0]=buf[0]?0:1;// // buf[1]=buf[1]?0:1;// write(fd,buf,sizeof(buf));// sleep(1);// // buf[0]=buf[0]?0:1;// buf[1]=buf[1]?0:1;// // buf[2]=buf[2]?0:1;// write(fd,buf,sizeof(buf));// sleep(1);// // buf[1]=buf[1]?0:1;// buf[2]=buf[2]?0:1;// write(fd,buf,sizeof(buf));}close(fd);return 0;
}
设备节点创建问题(udev/mdev)
(mknod hello c 243 0,手动创建设备节点hello)
(宏有返回值:为最后一句话执行的结果)
mknod 设备节点名 c/b 主设备号 次设备号
1、设置自动创建设备节点:
struct class *cls=class_create(THIS_MODULE,名字);
struct device *dev=device_create(cls,NULL,\
MKDEV(主设备号,次设备号),NULL,设备节点的名字);
2、卸载驱动:设置自动销毁设备节点
device_destroy(cls,MKDEV(major,0));
class_destroy(cls);
1)自动创建设备节点:
struct class *cls;
#include <linux/device.h>
cls = class_create(owner, name)
void class_destroy(struct class *cls)//销毁
功能:向用户空间提交目录信息(内核目录的创建)
参数:
@owner :THIS_MODULE(看到owner就添THIS_MODULE)
@name :目录名字
返回值:成功返回struct class *指针
失败返回错误码指针 int (-5)
if(IS_ERR(cls))
{
return PTR_ERR(cls);(PTR_ERR:把错误码指针转换成错误码)
}
IS_ERR(): 返回值为0,不在错误码地址范围,非0,在错误码地址范围,内核从0xffffffff 地址开始往地址减少的方向,预留了4K空间用来作为错误码的地址。
2)向用户空间提交文件信息
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 :目录名字@parent:NULL@devt :设备号 (major<<12 |0 < = > MKDEV(major,0))@drvdata :NULL@fmt :文件的名字
返回值:成功返回struct device *指针
失败返回错误码指针 int (-5)