通过字符设备驱动点亮板子上的led灯

2023-12-25 10:42:05

通过字符设备驱动点亮板子上的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)

文章来源:https://blog.csdn.net/m0_74937538/article/details/135191367
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。