最近在学习嵌入式Linux驱动开发,大致了解了驱动的基本开发流程,本文主要针对字符设备驱动开发做一个简要介绍,也当作是对这几天工作的一个小小总结。
计算机系统是由软硬件相互协调共同完成工作的,作为专用计算机系统的嵌入式系统也不例外,既要有CPU、SDRAM、FLASH、IO等硬件,同时也少不了操作系统和应用软件等软件的支持,而作为应用程序与硬件的桥梁——驱动程序,是整个嵌入式系统开发过程中的关键环节。驱动开发涉及底层,而了解底层作用机制对于整个系统的开发意义重大。
Linux内核中有60%以上是驱动程序,它不仅支持驱动以静态形式编译进内核,而且允许驱动以模块的形式动态加载进内核,大大减小了内核的大小,同时便于调试分析。
Linux将所有的设备当作文件进行处理,Linux系统的设备分为三类:字符设备、块设备和网络设备。字符设备的驱动有一个固定的模板,主要编写file_operations结构体中的成员函数,这些函数最终会在应用程序进行Linux的open()、write()、read()、ioctl()、close()等系统调用时被调用。
驱动开发作用在内核空间,应用程序开发作用在用户空间。下面以GPIO端口驱动LED亮灭为例。
1、查看开发板原理图和芯片数据手册;
从上图图中可以看出GPF4~GPF7 分别控制D12~D9,当GPF4~GPF7被配置为输出模式,同时向该引脚输出0时LED亮,输出1时LED灭。
从下图可以看出GPFCON控制寄存器对应的每个引脚由两位决定,01时表示输出,所以GPF7~GPF4为0101时,四个引脚配置为输出,所以GPFCON=0x55FF。GPFDAT的八位分别对应八个引脚,0表示输出0,1表示输出1,譬如要使D9~D12都亮,则GPFDAT=0x0F。GPFUP对应的八位为1时表示上拉失效,为0时对应位上拉。同时可以看出GPFCON、GPFDAT、GPFUP的物理地址分别是0x56000050、0x56000054、0x56000058。
2、在虚拟机的shell终端下,编写和编译驱动程序和测试程序,步骤如下图;
源码如下:
led.c
<span style="font-size:12px;">#include<linux/module.h> #include<linux/kernel.h> #include<linux/fs.h> #include<linux/init.h> #include<linux/delay.h> #include<linux/device.h> #include<linux/types.h> #include<linux/ioctl.h> #include<linux/cdev.h> #include<linux/errno.h> #include<asm/io.h> #include<asm/hardware.h> #include<asm/arch/regs-gpio.h> #include<asm/arch-s3c2410/hardware.h> #define S3C2410_GPFCON S3C2410_GPIOREG(0x50) #define S3C2410_GPFDAT S3C2410_GPIOREG(0x54) #define S3C2410_GPFUP S3C2410_GPIOREG(0x58) #define GPFCON *(volatile unsigned int *)S3C2410_GPFCON #define GPFDAT *(volatile unsigned int *)S3C2410_GPFDAT #define GPFUP *(volatile unsigned int *)S3C2410_GPFUP void delay_1(void) { int i,j; for(i=0;i<1000;i++) for(j=0;j<10000;j++); } #define LED1_ON() (GPFDAT &=~0x8f) #define LED2_ON() (GPFDAT &=~0x4f) #define LED3_ON() (GPFDAT &=~0x2f) #define LED4_ON() (GPFDAT &=~0x1f) #define LED1_OFF() (GPFDAT |=0x80) #define LED2_OFF() (GPFDAT |=0x40) #define LED3_OFF() (GPFDAT |=0x20) #define LED4_OFF() (GPFDAT |=0x10) static int LedStatus; void LedSet(int led) { LedStatus = led; if(LedStatus&1) LED1_ON(); else LED1_OFF(); if(LedStatus&2) LED2_ON(); else LED2_OFF(); if(LedStatus&4) LED3_ON(); else LED3_OFF(); if(LedStatus&8) LED4_ON(); else LED4_OFF(); } void LedDisp(void) { LedSet(0x08); delay_1(); LedSet(0x04); delay_1(); LedSet(0x02); delay_1(); LedSet(0x01); delay_1(); LedSet(0x02); delay_1(); LedSet(0x04); delay_1(); LedSet(0x08); delay_1(); } #define DEVICE_NAME "led" #define LED_MAJOR 220 static int led_open(struct inode *inode,struct file *file) { GPFCON=0x5500; GPFUP=0xff; printk("LED Driver Open Called!\n"); return 0; } static int led_release(struct inode *inode,struct file *file) { printk("LED Driver Release Called\n"); return 0; } static int led_ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg) { int err=0; if(cmd==1){ while(arg--) { LedDisp(); printk("...."); } printk("\n"); return 0; } return err; } static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .ioctl = led_ioctl, }; static int __init led_init(void) { int result=0; result=register_chrdev(LED_MAJOR,DEVICE_NAME,&led_fops); if(result < 0) { printk("failed to register!\n"); return result; } printk("success to register\n"); return 0; } static void __exit led_exit(void) { printk("Led Driver Module Exit\n"); unregister_chrdev(LED_MAJOR,DEVICE_NAME); } module_init(led_init); module_exit(led_exit); MODULE_AUTHOR("njust_sxy"); MODULE_DESCRIPTION("Led Driver"); MODULE_LICENSE("GPL");</span>
Makefile
<span style="font-size:12px;">obj-m :=led.o KDIR := /home/sxy/linux-2.6.8.1-zzm PWD :=$(shell pwd) CC=arm-linux-gcc default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -rf .*.cmd *.o *.mod.c *.ko</span>
test.c
<span style="font-size:12px;">#include<stdio.h> #include<stdlib.h> #include<fcntl.h> #include<errno.h> #include<unistd.h> #include<linux/delay.h> #include<sys/ioctl.h> int main(int argc, char *argv[]) { int fd; int val=-1; fd=open("/dev/led",0); if(fd<0) { perror("can not open device"); exit(1); } while(1){ printf("please select number to run program\n"); printf("1:led on \n2:quit "); scanf("%d",&val); if(val==1) ioctl(fd,1,10); else if(val==2){ close(fd); } } return 0; }<strong> </strong></span>
3、在串口终端上,加载驱动模块和测试驱动程序;
mount:挂载虚拟机的/home目录到开发板的/tmp/led目录下;
insmod:加载模块;
mknod:创建设备节点;
rmmod:卸载模块;
led.ko:led设备驱动模块。
实验结果:在终端打印出“...."的同时,LED从D12~D9相继亮灭(流水灯)。
总结:整个LED驱动开发并不算难,如果有了裸机编程的基础,加上一定的Linux基础,上手会更加容易。期间也碰到不少疑问,而最后大部分也都得到了解答。总之,基础还是很重要的,理论+实践才是王道。
ps:曾经尝试调用内核中的s3c2410_gpio_cfgpin()和s3c2410_gpio_setpin()两个函数来实现端口输出配置和向数据寄存器写数据,但会出现下面这个问题:
s3c2410_gpio_cfgpin()和s3c2410_gpio_setpin()这两个函数在/arch/arm/mach-s3c2410/gpio.c文件中定义,在asm-arm/arch-s3c2410/hardware.h中extern声明,最初编译时警告说这两个函数是”undefined“的,后来我在gpio.c文件中添加EXPORT_SYMBOL(s3c2410_gpio_cfgpin);EXPORT_SYMBOL(s3c2410_gpio_setpin);导出符号,却出现“no
CRC”这个警告,这个问题已经折腾我好几天了,求高手解答。
嵌入式Linux学习笔记之LED驱动