通过字符设备驱动点亮板子上的led灯
通过字符设备驱动点亮板子上的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)?clean
obj-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)
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!