这两天写了个LED驱动程序,网上也看了好多的帖子。
开始思路很清晰了,就是先看电路图,发现LED灯是接在GPM端口上的,
然后看S3C6410数据手册,先向GPMCON口写命令字,让GPM0-5设置为输出,再向GPMDAT口写数据字,在GPM0-5引脚拉低或拉高电平,
从而控制LED的亮灭。
1、电路图
很显然LED灯是接在GPM口引脚下面的
2、数据手册
3、LED驱动程序
#include
#include
#include
#include /*copy_to_user,copy_from_user*/
#include /*inl(),outl()*/
#include
#include
static long S3C64XX_GPMCON=0xF4500820; /*这里是虚拟地址,物理地址可以再S3C6410数据手册上找到。也可以根据物理地址,ioremap()获得虚拟地址。*/
static long S3C64XX_GPMDAT=0xF4500824;
#define LED_MAJOR 240 /*主设备号*/
int led_open(struct inode *inode,struct file *file)
{
unsigned tmp;
tmp=inl(S3C64XX_GPMCON);
printk("the pre GPMCON is %x",tmp);
tmp=inl(S3C64XX_GPMDAT);
printk("the pre GPMDAT is %x",tmp);
outl(0x00111111,S3C64XX_GPMCON); /*向GPMCON命令端口写命令字,设置GPM0-5为output口*/
printk("#############open#############");
return 0;
}
static ssize_t led_read(struct file *file,char __user *buf,size_t count,loff_t * f_pos)
{
unsigned tmp=inl(S3C64XX_GPMDAT);
int num=copy_to_user(buf,&tmp,count);
if(num==0)
printk("copy successfully");
else printk("sorry copy failly");
printk("the GPMDAT is %x.",tmp);
return count;
}
static ssize_t led_write(struct file * file,const char __user * buf,size_t count,loff_t * f_pos)/*我是通过write()来控制LED灯的,也可以通过ioctl()来控制*/
{
char kbuf[10];
printk("###########write###########");
int num=copy_from_user(kbuf,buf,count);
if(num==0)
printk("copy successfully");
else printk("sorry copy failly");
printk("##the kbuf is %c",kbuf[0]);
switch(kbuf[0])
{
case 0://off
outl(0xff,S3C64XX_GPMDAT); /*拉高GPMDAT[0:5]的引脚,使LED灯灭,因为LED是低电平有电流通过*/
break;
case 1://on
outl(0x00,S3C64XX_GPMDAT); /*拉低GPMDAT[0:5]的引脚,使LED灯亮*/
break;
default:
break;
}
return count;
}
int led_release(struct inode *inode,struct file *file)
{
printk("#######release##########");
return 0;
}
struct file_operations led_fops={
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
int __init led_init(void)
{
int rc;
printk("Test led dev\n");
rc=register_chrdev(LED_MAJOR,"led",&led_fops);
if(rc<0)
{
printk("register %s char dev error\n","led");
return -1;
}
printk("OK!\n");
return 0;
}
void __exit led_exit(void)
{
unregister_chrdev(LED_MAJOR,"led");
printk("module exit\n");
}
module_init(led_init);
module_exit(led_exit);
写好源码后,写Makefile
obj-m:=led_driver.o
KDIR:=/home/tmp/linux2.6.28
all:
make -C $(KDIR) M=$(shell pwd) modules CROSS_COMPILE=/usr/local/arm/4.4.1/bin/arm-linux-
/*之前make,出现一些问题,比如说缺少什么头文件啦之类,原来是没有建立内核树,到内核源码目录里面make一下就好了*/
写好驱动源代码和Makefile文件后就在本目录下make
之后生成了led_driver.ko文件,下载到开发板上
4、测试驱动程序
#include
#include
#include
#include
/*#include */
int main()
{
printf("hello led device .");
char buf[10]={0,1,0,1};
int fd=open("/dev/led",2,0777);
if(fd<0){
printf("can't open led device");
return -1;
}
printf("open the led device successfully.");
while(1)
{
int num=write(fd,&buf[0],1);
if(num<0)
printf("we set the led failly.");
else printf("we set the led off");
sleep(1);
write(fd,&buf[1],1);
printf("we set the led on");
sleep(1);
}
close(fd);
printf("bye led device .");
return 0;
}
arm-linux-gcc -o led_test.o led_test.c
编译好后,把led_test.o文件下载到开发板上
5、具体操作步骤
本人的OK6410开发板之前已装好linux-2.6.18内核的Linux系统,然后输shell命令
insmod led_driver.ko //加载led_driver.ko驱动到内核
mknod /dev/led c 240 0 //把led设备文件挂载到/dev/led上,c 代表字符设备 ,240 是主设备号 , 0 是次设备号
./led_test.o //启动测试程序,点亮LED灯
注意:
在PC机上写ARM开发板的驱动程序,需要在PC(俗称上位机)上配置交叉编译工具链 arm-linux-
开始想不通过驱动程序,通过iopl(3);提升权限,直接操作硬件的,发现在ARM开发板上不能这么做。
原来以为只能写驱动程序的方式控制寄存器的,后来发现,mmap()那个“/dev/mem”文件系统也可以的。
开始在开发板上调试,cat /var/log/messages 查看printk的输出,发现什么也没有,原来需要输入shell命令 klogd 来开启日志功能。
祝大家在开发板上玩的愉快。