Linux设备驱动的Hello World—LED驱动
2013-10-30 14:41
369 查看
要看懂驱动源码,肯定是要从最基本的看起,C语言中,如printf("hello world\n");而对于驱动,肯定是LED,呵呵,恰好年轻时写过一个,还保留着,而且是流水灯式的,下面以ARM270(共有8个LED灯)为例。
一、无操作系统时的LED驱动
在嵌入式系统的设计中,LED一般直接由CPU的GPIO(通用可编程 I/O 口)控制。GPIO一般由两组寄存器控制,即一组控制寄存器和一组数据寄存器。控制寄存器可设置GPIO 口的工作方式为输入或输出。当引脚被设置为输出时,向数据寄存器的对应位写入1和0会分别在引脚上产生高电平和低电平;当引脚设置为输入时,读取数据寄存器的对应位可获得引脚上相应的电平信号。则在无操作系统的情况下,设备驱动代码如下所示。
//片选B-CS4基地址为0x10000000,数码管的偏移地址为0x500000.
#defineSHOW_LED (*((volatile unsigned int *)0x10500000))
#defineCTRL _LED (*((volatile unsigned int *)0x40E00068)) //GPIO80设置为转换功能2
//初始化LED ,一般不需要初始化,因为boot已经对其进行初始化了。
voidLightInit(void)
{
CTRL_LED = 0x1400; /*设置GPIO为输出*/
}
//点亮第n个LED
voidLightOn(void)
{
SHOW_LED &= ~(1 << n);/*在GPIO上输出低电平*/
}
//熄灭第n个LED
voidLightOff(void)
{
SHOW_LED |= (1 << n); /*在GPIO上输出高电平*/
}
上述程序中的LightInit()、LightOn()、LightOff()等函数都将作为 LED驱动提供给应用程序的外部接口函数。 程序中ToVirtual()等函数的作用是当系统启动了硬件MMU之后,根据物理地址和虚拟地址的映射关系,将寄存器的物理地址转化为虚拟地址。
二、Linux系统下的LED驱动
在Linux 操作系统下编写LED 设备的驱动时,操作硬件的LightInit()、LightOn()、LightOff()这些函数仍然需要,但是,需要遵循Linux编程的命名习惯,重新将其命名为light_init()、light_on()、light_off()。这些函数将被LED 驱动中独立于设备的针对内核的接口进行调用。
led.c:
Makefile:
test.c:
由于代码比较多,2个目录,一个驱动driver目录,一个应用程序目录test,上面程序的下载代码地址:http://download.csdn.net/detail/huangminqiang201209/4904596
大致过程如下:
1.生成led.ko
A.编写led.c
1)驱动描述:MODULE_DESCRIPTION,MODULE_LICENSE等。
2)系统操作函数:open、read、write、close等。
3)file_operation结构体的封装,2.6内核的。
4)注册设备:__init中register_chrdev。
5) 注销:__exit中unregister。
6) 模块的导入导出内核:module_init(led_init); module_exit(led_exit);
B.编写Makefile(这个Makefile还是有一点点复杂的哦),与led.c同一目录
C.make生成led.ko
还有一种生成*.ko的方法是编译内核模块,Makeconfigure:Y/N/M选择(呵呵,在此之前还要对Makefile、Kconfig等进行配置)M是将该功能编译成可以在需要时动态插入到内核中的模块,而Y是直接编译进内核,可以省略后面的几步,直接运行app。
2.生成应用程序APP
1)编写main.c,应用程序
2)/usr/local/hybus-arm-linux-R1.1/bin/arm-linux-gcc main.c -o app
3.将led.ko和app通过NFS挂在到开发板上。
4.创建led结点
mknod /dev/led c 2500
5.装载led模块并查看led.ko
insmod led.ko lsmod led.ko
6.运行应用程序./app
7.卸载led模块
rmmod led.ko
一、无操作系统时的LED驱动
在嵌入式系统的设计中,LED一般直接由CPU的GPIO(通用可编程 I/O 口)控制。GPIO一般由两组寄存器控制,即一组控制寄存器和一组数据寄存器。控制寄存器可设置GPIO 口的工作方式为输入或输出。当引脚被设置为输出时,向数据寄存器的对应位写入1和0会分别在引脚上产生高电平和低电平;当引脚设置为输入时,读取数据寄存器的对应位可获得引脚上相应的电平信号。则在无操作系统的情况下,设备驱动代码如下所示。
//片选B-CS4基地址为0x10000000,数码管的偏移地址为0x500000.
#defineSHOW_LED (*((volatile unsigned int *)0x10500000))
#defineCTRL _LED (*((volatile unsigned int *)0x40E00068)) //GPIO80设置为转换功能2
//初始化LED ,一般不需要初始化,因为boot已经对其进行初始化了。
voidLightInit(void)
{
CTRL_LED = 0x1400; /*设置GPIO为输出*/
}
//点亮第n个LED
voidLightOn(void)
{
SHOW_LED &= ~(1 << n);/*在GPIO上输出低电平*/
}
//熄灭第n个LED
voidLightOff(void)
{
SHOW_LED |= (1 << n); /*在GPIO上输出高电平*/
}
上述程序中的LightInit()、LightOn()、LightOff()等函数都将作为 LED驱动提供给应用程序的外部接口函数。 程序中ToVirtual()等函数的作用是当系统启动了硬件MMU之后,根据物理地址和虚拟地址的映射关系,将寄存器的物理地址转化为虚拟地址。
二、Linux系统下的LED驱动
在Linux 操作系统下编写LED 设备的驱动时,操作硬件的LightInit()、LightOn()、LightOff()这些函数仍然需要,但是,需要遵循Linux编程的命名习惯,重新将其命名为light_init()、light_on()、light_off()。这些函数将被LED 驱动中独立于设备的针对内核的接口进行调用。
led.c:
//head #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/cdev.h> #define BASE_NUM 0 #define COUNT 1 #define DEV_NAME "led" static char k_buf[128] = {0}; //static struct cdev *led_cdev = NULL; static struct cdev led_cdev; static dev_t dev = 0; //#define VADDR __REG(0x10500000) static volatile unsigned int pa = 0x10500000; static volatile unsigned int va; //description MODULE_AUTHOR("huangminqiang2007@163.com"); MODULE_DESCRIPTION("THIS IS FOR TESTING"); MODULE_LICENSE("GPL"); //open close read write //open static int led_open(struct inode *i_node, struct file *filep) { return 0; } //close static int led_close(struct inode *i_node, struct file *filep) { return 0; } //read static int led_read(struct file *filep,char *buf, size_t size,loff_t *offset) { int cnt = -1; if(NULL == filep) { goto _out; } cnt = copy_to_user(buf,k_buf,sizeof(k_buf)); return (sizeof(k_buf)-cnt); _out: return -1; } //write static int led_write(struct file *filep,const char *buf, size_t size,loff_t *offset) { if(NULL == filep) { goto _out; } copy_from_user(k_buf,buf,size); *(char *)va = k_buf[0]; //writew(k_buf[0],va); //*(char *)VADDR = k_buf[0]; return size; _out: return -1; } //file operations static struct file_operations led_ops = { .owner= THIS_MODULE, .open= led_open, .release= led_close, .read= led_read, .write= led_write, }; //init static int __init led_init(void) { int cnt = -1; //paddr to vaddr (int *)va = ioremap(pa,4); //alloc cdev region cnt = alloc_chrdev_region(&dev,BASE_NUM,COUNT,DEV_NAME); if(0 > cnt) { goto _out; } //alloc cdev,如果直接定义变量则不用申请内存。 //led_cdev = cdev_alloc(); //init cdev cdev_init(&led_cdev,&led_ops); //add cdev cnt = cdev_add(&led_cdev,dev,COUNT); //printk major_num printk("<0>" "major number : %d\n",MAJOR(dev)); if(0 > cnt) { goto _out; } printk("<0>" "init ok!\n"); return 0; _out: return -1; } //exit static void __exit led_exit(void) { //unregister cdev region unregister_chrdev_region(dev,COUNT); //del cdev cdev_del(&led_cdev); iounmap((char *)va); printk("<0>" "exit ok!\n"); } //add to kernel module_init(led_init); module_exit(led_exit);
Makefile:
EXTRA_CFLAGS += $(DEBFLAGS) -Wall ifneq ($(KERNELRELEASE),) obj-m := led.o else KDIR ?= /opt/270-s-2.6.9/linux-2.6.9_270-s #之前的内核位置 PWD := $(shell pwd) all: make $(EXTRA_CFLAGS) -C $(KDIR) M=$(PWD) modules endif clean: rm *.o *.ko led.mod.c
test.c:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define DEV_NAME "/dev/test" #define FLAGS (O_RDWR | O_CREAT) int main(void) { int fd = -1; int i = -1; int cnt = -1; unsigned char led = 0xff; //open fd = open(DEV_NAME, FLAGS); if(0 > fd) { perror("open err:"); goto _out; } //流水灯 while(1) { for(i = 0; i < 8; i++) { led &= ~(1 << i); cnt = write(fd, &led, 1); if(0 > cnt) { perror("write err:"); goto _out; } led = 0xff; sleep(1); } } //close close(fd); return 0; _out: return -1; }
由于代码比较多,2个目录,一个驱动driver目录,一个应用程序目录test,上面程序的下载代码地址:http://download.csdn.net/detail/huangminqiang201209/4904596
大致过程如下:
1.生成led.ko
A.编写led.c
1)驱动描述:MODULE_DESCRIPTION,MODULE_LICENSE等。
2)系统操作函数:open、read、write、close等。
3)file_operation结构体的封装,2.6内核的。
4)注册设备:__init中register_chrdev。
5) 注销:__exit中unregister。
6) 模块的导入导出内核:module_init(led_init); module_exit(led_exit);
B.编写Makefile(这个Makefile还是有一点点复杂的哦),与led.c同一目录
C.make生成led.ko
还有一种生成*.ko的方法是编译内核模块,Makeconfigure:Y/N/M选择(呵呵,在此之前还要对Makefile、Kconfig等进行配置)M是将该功能编译成可以在需要时动态插入到内核中的模块,而Y是直接编译进内核,可以省略后面的几步,直接运行app。
2.生成应用程序APP
1)编写main.c,应用程序
2)/usr/local/hybus-arm-linux-R1.1/bin/arm-linux-gcc main.c -o app
3.将led.ko和app通过NFS挂在到开发板上。
4.创建led结点
mknod /dev/led c 2500
5.装载led模块并查看led.ko
insmod led.ko lsmod led.ko
6.运行应用程序./app
7.卸载led模块
rmmod led.ko
相关文章推荐
- 深入浅出Linux设备驱动编程--内存与I/O操作
- 《linux设备驱动》英文第二版学习笔记1
- Linux设备驱动开发
- Linux设备驱动
- 面对不断升级的内核,如何学习linux设备驱动
- 深入浅出 Linux设备驱动中断处理介绍
- Linux设备驱动编程之阻塞与非阻塞(转)
- linux设备驱动编写_tasklet机制
- Linux设备驱动之I2C架构分析
- linux设备驱动中常用函数及其出处
- Linux设备驱动子系统第一弹 - I2C
- linux设备驱动学习(11) linux设备模型3
- Linux设备驱动工程师之路——设备模型(上)底层模型
- Linux设备驱动工程师之路——触摸屏驱动s3c2410_ts.c分析
- Linux设备驱动编程之 内存与I/O操作
- linux设备驱动归纳总结(五):3.IO静态映射
- linux设备驱动中ADC的使用
- linux设备驱动中的并发控制-总结
- Linux设备驱动中的并发控制
- linux设备驱动--非阻塞IO与select,poll调用