驱动开发--创建设备文件--控制LED灯

文章介绍了如何在Linux下手动创建设备文件,包括使用mknod命令和注册字符设备驱动。同时,详细讲解了应用程序如何通过内核API(如copy_from_user,copy_to_user)与驱动交互传递数据。此外,还展示了如何通过ioremap映射物理地址到虚拟地址来控制RGBLED灯,并给出了驱动和应用层的示例代码。
摘要由CSDN通过智能技术生成

1、手动创建设备文件

cat  /proc/devices 查看主设备号

sudo  mknod hello(路径:任意的)   c/b(C代表字符设备 b代表块设备)主设备号  次设备号

生成hello:应用层可以打开的文件

设置驱动层程序:hello.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>  //添加头文件
#define CNAME "hello"
int major=0;
ssize_t mycdev_read (struct file *file, char __user *user, size_t size, loff_t * loff)
{ 
	printk("this is read\n"); 

    return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *user, size_t size, loff_t *loff)
{   
    printk("this is write\n");  

    return 0;
}
int mycdev_open (struct inode *inode, struct file *file)
{   
	printk("this is open\n");   
return 0;
}
int mycdev_release (struct inode *inode, struct file *file)
{   
	printk("this is close\n");   
	return 0;
}	
const struct file_operations fops={   

	.open=mycdev_open,   

	.read=mycdev_read,   

	.write=mycdev_write,   

	.release=mycdev_release,
};
static int __init hello_init(void)//入口
{ 
    major=register_chrdev(major,CNAME,&fops); 

    if(major<0)  

    {   

	printk("register chrdev error"); 

    } 
   return 0;
}
static void __exit hello_exit(void)//出口
{  
    unregister_chrdev(major,CNAME);
}
module_init(hello_init);//告诉内核驱动的入口

module_exit(hello_exit);//告诉内核驱动的出口

MODULE_LICENSE("GPL");

 设置应用层程序:test.c

2、应用程序如何将数据传递给驱动

(读写的方向是站在用户的角度来说的)

#include <linux/uaccess.h>

int copy_from_user(void *to, const void __user *from, int n)

功能:从用户空间拷贝数据到内核空间

参数:

@to  :内核中内存的首地址

@from:用户空间的首地址

@n   :拷贝数据的长度(字节)

返回值:成功返回0,失败返回未拷贝的字节的个数

int copy_to_user(void __user *to, const void *from, int n)

功能:从内核空间拷贝数据到用户空间

参数:

@to  :用户空间内存的首地址

@from:内核空间的首地址  __user需要加作用是告诉编译器这是用户空间地址

@n   :拷贝数据的长度(字节)

返回值:成功返回0,失败返回未拷贝的字节的个数

驱动层:hello.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/uaccess.h>   //添加头文件
#include <linux/fs.h>
#define CNAME "hello"
int major=0;
char kbuf[128]={0}; //存储数据
int dev=0;          
ssize_t mycdev_read (struct file *file, char __user *user, size_t size, loff_t * loff)
{
    //printk("this is read\n");
    if(size>sizeof(kbuf))
    {
      size=sizeof(kbuf);
    }
    dev=copy_to_user(user,kbuf,size);  //从内核空间到用户空间
    if(dev)
    {
        printk("copy to user err");
        return dev;
    }
    return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *user, size_t size, loff_t *loff)
{  
    //printk("this is write\n");  
    if(size>sizeof(kbuf))
    {
      size=sizeof(kbuf);
    }
    dev=copy_from_user(kbuf,user,size);   //从用户空间到内核空间
 return 0;
}
int mycdev_open (struct inode *inode, struct file *file)
{  
    printk("this is open\n");  
return 0;
}
int mycdev_release (struct inode *inode, struct file *file)
{  
    printk("this is close\n");  
    return 0;
}    
const struct file_operations fops={  
    .open=mycdev_open,  
    .read=mycdev_read,  
    .write=mycdev_write,  
    .release=mycdev_release,
};
static int __init hello_init(void)//入口
{
    major=register_chrdev(major,CNAME,&fops);
    if(major<0)  
  {  
    printk("register chrdev error");
   }  
return 0;
}
static void __exit hello_exit(void)//出口
{  
    unregister_chrdev(major,CNAME);
}
module_init(hello_init);//告诉内核驱动的入口
module_exit(hello_exit);//告诉内核驱动的出口
MODULE_LICENSE("GPL");

应用程序:test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
char buf[128]="hello world!";
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("buf is :%s\n",buf);
    close(fd);
    return 0;
}

$make

$sudo insmod hello.ko

$gcc test.c

$./a.out

$dmesg

 

3、控制LED灯:

驱动如何操作寄存器

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

物理地址<------>虚拟地址

void * ioremap(phys_addr_t offset, unsigned long size)

功能:将物理地址映射成虚拟地址

参数:@offset :要映射的物理地址

          @size   :大小(字节)

返回值:成功返回虚拟地址,失败返回NULL; 

void iounmap(void  *addr)

功能:取消映射

参数: @addr :虚拟地址

返回值:无

RGB_led 

red  :gpioa28

GPIOXOUT   :控制高低电平的   0xC001A000

GPIOxOUTENB:输入输出模式    0xC001A004

GPIOxALTFN1:function寄存器  0xC001A024

green:gpioe13    0xC001e000

blue :gpiob12     0xC001b000

 R:GPIOA

G: GPIOE

 B: GPIOB

宏定义基地址 ,设置虚拟地址

 将物理地址映射成虚拟地址

 指针类型加1是加的类型大小

 添加头文件:#include <linux/io.h>

取消映射:

 

 将hello.ko拷贝到开发板内核文件夹中指针类型加1是加的类型大小

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/io.h>     //添加头文件
int major=0;
#define CNAME "hello"
char kbuf[128]={0};
int dev=0;
#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;
ssize_t mycdev_read (struct file *file, char __user *user, size_t size, loff_t * loff)
{
   //printk("this is read");
   if(size>128){
   	size=128;
	}
    dev=copy_to_user(user,kbuf,size);
	if(dev)
		{
		printk("copy to user errer");
		return dev;
	}
   return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *user, size_t size, loff_t *loff)
{
   //printk("this is write");
      if(size>128){
   	size=128;
	}
	dev=copy_from_user(kbuf,user,size);
   return 0;
}
int mycdev_open (struct inode *inode, struct file *file)
{
   printk("this is open");
   return 0;
}
int mycdev_release (struct inode *inode, struct file *file)
{
   printk("this is close");
   return 0;
}
const struct file_operations fops={
   .open=mycdev_open,
   .read=mycdev_read,
   .write=mycdev_write,
   .release=mycdev_release,
};
static int __init hello_init(void)//入口
{
  major=register_chrdev(major,CNAME,&fops);
  if(major<0)
  {
   printk("register chrdev error");
  }
  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;  //指针类型加1是加的类型大小  int占4字节
	*(red_base+9) &=~(3<<24);
  return 0;
}
static void __exit hello_exit(void)//出口
{
  iounmap(green_base);
  iounmap(blue_base);
  iounmap(red_base);
  unregister_chrdev(major,CNAME);
}
module_init(hello_init);//告诉内核驱动的入口
module_exit(hello_exit);//告诉内核驱动的出口
MODULE_LICENSE("GPL");

Makefile:

开发板上电测试:

 

4、应用层控制灯

1、 驱动层

判断语句 、宏定义开关

#define RED_ON *red_base |= 1<<28
#define RED_OF *red_base &= ~(1<<28)
ssize_t mycdev_write (struct file *file, const char __user *user, size_t size, loff_t *loff)
{
   //printk("this is write");
      if(size>128){
   	size=128;
	}
	dev=copy_from_user(kbuf,user,size);
	if(kbuf[0]==1)
		{
		RED_ON;
	}
	else{
		RED_OF;
	}
   return 0;
}

2、应用层 

需要将test.c 在开发板中进行编译,还需要拷贝编译生成的a.out,所以直接在Makefile中添加两行代码:


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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值