这两天移植在实验室和师兄测试接收机,没有什么时间学嵌入式。现在这个程序还只是一个半成品,暂且贴在这,有很多地方还不完美,回来再写个更好的。果然把裸机代码加个init open read write close exit的外壳就成驱动了,但是要使用ioremap把寄存器物理地址映射为虚拟地址,否则会出现segmentation fault错误。最后别忘了iounmap取消映射。
led_driver.c(半成品)
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
MODULE_LICENSE("GPL"); //使用GPL版权协议
MODULE_AUTHOR("wolf"); //作者
/*********GPIO寄存器物理地址*********/
/*按键寄存器*/
#define pGPGCON 0x56000060
#define pGPGDAT 0x56000064
#define pGPGUP 0x56000068
/*LED寄存器*/
#define pGPBCON 0x56000010
#define pGPBDAT 0x56000014
#define pGPBUP 0x56000018
/*********GPIO寄存器物理地址*********/
static int *GPGCON, *GPGDAT, *GPGUP; //按键寄存器虚拟地址
static int *GPBCON, *GPBDAT, *GPBUP; //LED寄存器虚拟地址
static int major_devnum; //主设备号
//static int mode; //模式1跑马灯,模式2按键点灯
/*********打开设备*********/
static int openDevice(struct inode* inode, struct file* file)
{
/*LED全亮*/
*GPBDAT &= ~((1<<5)|(1<<6)|(1<<7)|(1<<8));
printk(KERN_ALERT "Device Opened.");
return 0;
}
/*********打开设备*********/
/*********关闭设备*********/
static int closeDevice(struct inode* inode, struct file* file)
{
/*LED全灭*/
*GPBDAT |= ((1<<5)|(1<<6)|(1<<7)|(1<<8));
printk(KERN_ALERT "Device Closed.");
return 0;
}
/*********关闭设备*********/
/*文件操作结构体*/
struct file_operations fOps =
{
.open = openDevice,
.release = closeDevice
};
/*********装载驱动*********/
static int __init loadDriver(void)
{
/*地址映射*/
GPGCON = ioremap(pGPGCON, 4);
GPGDAT = ioremap(pGPGDAT, 4);
GPGUP = ioremap(pGPGUP, 4);
GPBCON = ioremap(pGPBCON, 4);
GPBDAT = ioremap(pGPBDAT, 4);
GPBUP = ioremap(pGPBUP, 4);
/*设置管脚属性*/
*GPGCON &= ~((3<<6)|(3<<10)|(3<<12)|(3<<14));
*GPGUP &= ~((1<<3)|(1<<5)|(1<<6)|(1<<7));
*GPBCON &= ~((1<<11)|(1<<13)|(1<<15)|(1<<17));
*GPBCON |= ((1<<10)|(1<<12)|(1<<14)|(1<<16));
*GPBUP &= ~((1<<5)|(1<<6)|(1<<7)|(1<<8));
/*LED全灭*/
*GPBDAT |= ((1<<5)|(1<<6)|(1<<7)|(1<<8));
/*注册文件操作结构体*/
major_devnum = register_chrdev(0, "wolf_key_LED", &fOps);
if(major_devnum < 0)
{
printk(KERN_ALERT "Driver load failed.");
return major_devnum;
}
else
{
printk(KERN_ALERT "Driver %d loaded.", major_devnum);
return 0;
}
}
/*********装载驱动*********/
/*********卸载驱动*********/
static void __exit unloadDriver(void)
{
*GPBDAT &= ~((1<<5)|(1<<6)|(1<<7)|(1<<8));
/*取消地址映射*/
iounmap(GPGCON);
iounmap(GPGDAT);
iounmap(GPGUP);
iounmap(GPBCON);
iounmap(GPBDAT);
iounmap(GPBUP);
/*注销文件操作结构体*/
unregister_chrdev(major_devnum, "wolf_key_LED");
printk(KERN_ALERT "Driver unloaded.");
}
/*********卸载驱动*********/
/*设置装载卸载驱动时执行的函数*/
module_init(loadDriver);
module_exit(unloadDriver);
【运行现象】
加载insmod led_driver.ko,灯全灭;
卸载rmmod led_driver ,灯全亮。open和close函数还没有测试。
网上的程序都信誓旦旦地说只要insmod或rmmod,LED灯就会出现相应的变化,但是事实上,尽管已经打印出了printk里面的信息,但是LED灯还是按照二进制时钟的方式运行。纠结了一天,才发现原来在insmod执行的一刹那,LED灯确实灭了,但是之后又继续它的二进制时钟,应该是GPBDAT里面的数据又被别的程序改了的缘故。
解决方法是:打开/etc/init.d/rcS,这个文件是mini2440的启动流程,其中/etc/rc.d/init.d/leds start那一行是启动LED二进制时钟程序。就是这个程序在一直改GPBDAT里面的内容,用#把这行注释掉。这样开机的时候就不会启动LED service了,自己写的驱动程序的作用就显现出来了。
【注意事项】
1. __init函数不能返回正值,否则在insmod的时候会说什么“init suspiciously returned xxx, it should follow 0/-E conventionsys_init_module: loadingmodule anyway..”加载失败
2.写应用程序时用的函数都是C库函数,而驱动程序用的都是内核提供的函数,互相之间不能通用。否则应用程序也可以随便行使内核功能,就天下大乱了。
3.本来我想在一个for循环里不停地给*GPBDAT赋值,来让LED灯的状态持续的时间更长一些的。没想到不论我把循环次数设得多大,这个循环都能瞬间完成。后来才想到是编译器觉得这个for循环纯属废话,所以就把它给优化了,把它改成赋一次值就完了。编译器本想让程序运行的更迅速的,它不知道我的本意恰恰是延时。解决办法是,给GPBDAT设定为volatile的,于是和它有关的运算就都不会被优化了,延时就实现了。
4.指针++,--,+n,-n实际上是加减这么多个sizeof(它所指向的类型),比如int*型指针p,p++其实是p+sizeof(int),而不是真的给p加1.
【遗留问题】
1.深究一下request_mem_region和release_mem_region函数
2.深究一下volatile的用法
3.把程序完善了,写个测试程序