linux驱动学习之LED驱动

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/fengweibo112/article/details/101271395

概述

  • linux驱动可分为三类:字符设备、块设备、网络接口。
  • 字符设备驱动程序其实就是为具体硬件的file_operations结构体编写各个函数,实现open、read、write、ioctl等系统调用。
  • 实现步骤:编写驱动程序xxx.c文件,编写Makefile、编译链接生成xxx.ko文件、insmod生成节点、编写应用测试程序、执行测试程序查看驱动效果。

基础知识

(一)杂项设备

可以理解为的特殊字符设备,在嵌入式中应用比较多。使用杂项设备原因:
① 节省主设备号
普通字符设备都会消耗一个主设备号,使用杂项设备,主设备号固定10,通过子设备号区分不同驱动。
② 使用简单
普通字符设备需注册字符驱动、创建字符class等操作,杂项设备只需把 struct miscdevice交给misc_register(),则成功创建设备驱动。
(二)MMU
MMU:(Memory ManagementUnit)内存管理单元。
功能:负责物理地址到虚拟地址的映射,并提供内存访问权限检查。
作用:
① 内存扩充:为用户提供更大的地址空间(虚拟出来的)
② 内存保护:确保每个进程分配的内存区域互不干扰。
(三)ioremap、iounmap函数介绍
在linux系统中,开启mmu后,我们不能直接使用寄存器的硬件地址(或者说我们不知道,寄存器硬件地址被映射到那块内存了),所以我们只能使用虚拟地址来操纵寄存器。而目前我们不知道虚拟地址,只知道物理地址。
所以系统给我们提供了一个接口函数,用来通过寄存器的物理地址得到寄存器的虚拟地址。

void __iomem *ioremap(phys_addr_t offset,unsigned long size)
它的第一个输入参数是物理地址,第二个输入参数是请求的寄存器的长度。输出参数是虚拟地址。

其功能是建立新的一个页表,把输入的物理地址映射到内核空间的一块虚拟地址。(记住是映射一页)
所以使用完一定要释放掉,否则块内核的内存就不能再被使用了。

static inline void iounmap(void __iomem*addr)
取消映射,使用的是要释放的虚拟地址。

驱动程序

直接上代码,详细见注释

#include <linux/fs.h>         /*包含file_operation结构体*/
#include <linux/init.h>       /* 包含module_init module_exit */
#include <linux/module.h>     /* 包含LICENSE的宏 */
#include <linux/miscdevice.h> /*包含miscdevice结构体*/
#include <linux/io.h>         /*包含ioremap等操作函数*/
#include <linux/kernel.h>     /*包含printk等操作函数*/

/**************宏定义***************/
#define PIO_SODR (*(volatile unsigned long *)(virt_addr +0x0030))/*虚拟寄存器地址*/
#define PIO_CODR (*(volatile unsigned long *)(virt_addr +0x0034))
#define PIO_PER  (*(volatile unsigned long *)(virt_addr +0x0000))
#define PIO_MDDR (*(volatile unsigned long *)(virt_addr +0x0054))
#define PIO_OER  (*(volatile unsigned long *)(virt_addr +0x0010))
#define PIO_OWDR (*(volatile unsigned long *)(virt_addr +0x00A4))
#define  LED2  1 << 29
#define  LED1  1 << 26
#define  LED_ON  1
#define  LED_OFF  0
#define  LED1_CTL 5
#define  LED2_CTL 6

/**************内部变量***************/
unsigned long virt_addr;

/* 定义一个打开设备的,open函数 */
static int led_open(struct inode *inode,struct file *file)
{
 return 0;
}

/* 定义一个打开设备的,open函数 */
static int led_open(struct inode *inode,struct file *file)
{
 return 0;
}

/* 定义一个打开设备的,ioctl函数 */
static long led_ioctl(struct file *file,unsigned int data,unsigned long arg)
{
 switch(data)
 {
  case LED1_CTL:
   if(arg == LED_ON){
    PIO_SODR |= LED1;/*对寄存器IO操作*/
   }
   else{
    PIO_CODR |= LED1;
   }
  break;
  case LED2_CTL:
   if(arg == LED_ON){
    PIO_SODR |= LED2;
   }
   else{
    PIO_CODR |= LED2;
   }
  break;
  default:
  break;
 }
 return 0;
}

/*字符设备驱动程序就是为具体硬件的file_operations结构编写各个函数*/
static const struct file_operations led_ctl={
         .owner          = THIS_MODULE,
         .unlocked_ioctl = led_ioctl,
         .open           = led_open,
};
/*杂项设备,主设备号为10的字符设备,相对普通字符设备,使用更简单*/
static struct miscdevice led_miscdev = {
         .minor          = 255,
         .name           = "led_ctl",
         .fops           = &led_ctl,
};

static int __init led_init(void)
{
 char res;
 /*注册杂项设备驱动*/
 res = misc_register(&led_miscdev);
 printk(KERN_ALERT"led_init %d\n",res);
 
/*通过物理地址,得到寄存器的虚拟地址*/
 virt_addr =(volatile unsigned long )ioremap(0xfffff200,0x200); 
 
/*对芯片寄存器操作,输出IO使能*/
 PIO_PER  |= LED1 | LED2;
 PIO_MDDR |= LED1 | LED2;
 PIO_OER  |= LED1 | LED2;
 PIO_OWDR |= LED1 | LED2;
 PIO_CODR |= LED1 | LED2;
 return res;
}
static void __exit led_exit(void)
{
 /*释放杂项设备*/
 misc_deregister(&led_miscdev);
 /*取消虚拟地址映射*/
 iounmap((unsigned long *)virt_addr);
 printk(KERN_ALERT"led_exit\r\n");
}

/*驱动模块的加载和卸载入口*/
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("boyee");
MODULE_DESCRIPTION("control output led");

Makefile

最简单的makefile如下:

KERNEL_DIR:=/home/boyee/at91/linux-at91-V3.18.0-git
CROSS=arm-linux- 
obj-m +=ledCtrl.o
all:
 make -C $(KERNEL_DIR) M=`pwd` modules
clean:
 rm -rf *.o *.ko

应用测试程序

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
char *leds = "/dev/led_ctl";
int fd,i;
if((fd = open(leds,O_RDWR))<0)
{
 printf("leds open err\r\n");
 return -1;
}
for(i=0;i<5;i++)
{
 ioctl(fd,5,1);
 ioctl(fd,6,1);
 printf("led on\r\n");
 sleep(1);
 ioctl(fd,5,0);
 ioctl(fd,6,0);
 printf("led off\r\n");
 sleep(1);
}
close(fd);
return 0;
}

执行测试

编译驱动,应用测试程序
在这里插入图片描述上传xxx.ko和应用程序到板子,并更改权限,insmod,执行测试程序
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/fengweibo112/article/details/101271395