嵌入式Linux驅動開發日記
主機硬件環境
開發機:虛擬機Ubuntu12.04
內存: 1G
硬盤:80GB
目標板硬件環境
CPU: SP5V210 (開發板:QT210)
SDRAM: 512M (4片K4T1G164Q )
Nand flash: 512M (K9F4G08)
以太網芯片: SMSC LAN9220
工具介紹
仿真器: 暫無
電纜: 串口線,USB線
Windows 操作系統軟件環境
ADS編譯工具: 暫無
仿真器軟件:暫無
調試軟件: 終端(ADB)、eclipse
Linux操作系統軟件環境
GNU交叉編譯工具: arm-linux-gcc 4.4.1、JDK1.6、git 1.7、gcc 4.5、python 2.7
Section ONE:最簡單的驅動程序:hello world
首先我們寫一個最簡單的模塊。編輯hello_module.c
#include<linux/init.h> //module_init(),module_exit()
#include<linux/module.h> //MODULE_AUTHOR(),MODULE_LICENSE()
#include<linux/kernel.h> //KERN_EMERG
static int hello_init(void){
printk(KERN_EMERG "hello world enter\n");
return 0;
}
void hello_exit(void){
printk(KERN_EMERG "hello world exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_AUTHOR("CHSRY");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple Hello World Module");
MODULE_ALIAS("a simplest module");
編寫Makefile
obj-m := hello.o all: make -C /lib/modules/$(shell uname -r)/build SUBDIRS=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build SUBDIRS=$(PWD) clean
運行#make
生成hello.ko,然后使用insmod 命令存入模塊:insmod hello.ko
使用lsmod 就能看到你新插入的模塊: lsmod | grep hello
使用rmmod移除模塊:rmmod hello
最簡單的驅動就完成了。
由於Linux內核的級別控制,導致printk打印的內容不一定都能從控制台正常輸出
最好的辦法是打開另外的一個終端,用一個終端不停地監視並且打印輸出當前系統的日志信息:
在終端下輸入:
while true
do
sudo dmesg -c
sleep 1
done
這樣這個終端就會每1秒查看當前系統的日志並清空。
Section TWO:LED驅動程序
思維導圖設計

2.1 QT210 開發板 LED驅動程序和測試程序
驅動文件:hello.c
/************************************************
LED的驅動,在Real210A開發板上做測試
維護記錄: 2011-10-31 V1.0
linux內核:2.6.35.7
驅動用法:
設備名稱:Real210-led
點亮一個燈:LED_ON
熄滅一個燈:LED_OFF
點亮所有燈:ALL_LED_ON
熄滅所有燈:ALL_LED_OFF
*************************************************/
#include<linux/init.h>
#include<linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/device.h>
#include <linux/gpio.h>
#define DEVICE_NAME "Real210-led" /* 設備名稱 */
static int LED_Major = 0; /* 主設備號 ,系統自動分配*/
#define LED_OFF 0
#define LED_ON 1
#define ALL_LED_OFF 3
#define ALL_LED_ON 4
/* 用來指定LED所用的GPIO引腳 */
static unsigned long led_table [] =
{
//S5PV210_GPH0(_nr);
//在頭文件“~/kernel/arch/arm/mach-s5pv210/include/mach/”
S5PV210_GPH0(6),
S5PV210_GPH0(7),
S5PV210_GPH0(4),
S5PV210_GPH0(5),
};
static int Real210_led_open(struct inode *inode, struct file *file)
{
// MOD_INC_USE_COUNT;
printk("Real210-LED Driver Open Called!\n");
return 0;
}
static int Real210_led_release(struct inode *inode, struct file *file)
{
// MOD_DEC_USE_COUNT;
printk("Real210-LED Driver Release Called!\n");
return 0;
}
static int Real210_led_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int i;
if (arg > 4)
{
return -EINVAL;
}
switch(cmd)
{
case LED_ON: //set the pin
gpio_set_value (led_table[arg], 0);
break;
case LED_OFF: //clr the pin
gpio_set_value (led_table[arg], 1);
break;
case ALL_LED_ON: //set all pin
for (i = 0; i < 4; i++)
gpio_set_value (led_table[i], 0);
break;
case ALL_LED_OFF: //clr all pin
for (i = 0; i < 4; i++)
gpio_set_value (led_table[i], 1);
break;
default:
return -EINVAL;
}
}
static struct file_operations Real210_led_fops =
{
.owner = THIS_MODULE,
.open = Real210_led_open,
.release = Real210_led_release,
.ioctl = Real210_led_ioctl,
};
static struct class *led_class;
static int __init Real210_led_init(void)
{
printk("Real210 LED DRIVER MODULE INIT\n");
LED_Major = register_chrdev(0, DEVICE_NAME, &Real210_led_fops);
if (LED_Major < 0)
{
printk(DEVICE_NAME " can't register major number\n");
return LED_Major;
}
printk("register Real210-LED Driver OK! Major = %d\n", LED_Major);
led_class = class_create(THIS_MODULE, DEVICE_NAME);
if(IS_ERR(led_class))
{
printk("Err: failed in Real210-LED class. \n");
return -1;
}
device_create(led_class, NULL, MKDEV(LED_Major, 0), NULL, DEVICE_NAME);
//IO初始化
//IO方向配置
gpio_direction_output (S5PV210_GPH0(6), 1);
gpio_direction_output (S5PV210_GPH0(7), 1);
gpio_direction_output (S5PV210_GPH0(4), 1);
gpio_direction_output (S5PV210_GPH0(5), 1);
//IO初始化
gpio_set_value (S5PV210_GPH0(6), 1);
gpio_set_value (S5PV210_GPH0(7), 0);
printk(DEVICE_NAME " initialized\n");
return 0;
}
static void __exit Real210_led_exit(void)
{
printk("Real210 LED DRIVER MODULE EXIT\n");
unregister_chrdev(LED_Major, DEVICE_NAME);
device_destroy(led_class, MKDEV(LED_Major, 0));
class_destroy(led_class);
}
module_init(Real210_led_init);
module_exit(Real210_led_exit);
//MODULE_LICENSE("Dual BSD/GP");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wzl");
MODULE_DESCRIPTION("This is an example of hello drivers");
MODULE_ALIAS("A simplest module.");
Makefile文件:
obj-m := hello.o KDIR := /home/work/QT210/qt210_ics_kernel3.0.8 all: make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-eabi- clean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers
測試文件:led.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <sys/ioctl.h>
int main(int argc, char **argv)
{
unsigned int on;
unsigned int led_num;
int fd;
printf("Enter the test led !\n");
fd = open("/dev/Real210-led", 0);
if (fd < 0)
{
perror("open device led");
exit(1);
}
ioctl(fd, 1, 0); //可修改本句代碼
close(fd);
return 0;
}
Makefile文件:
all: led
led:led.c
arm-none-linux-gnueabi-gcc -o led led.c -static
arm-none-linux-gnueabi-strip led
clean:
@rm -vf led *.o *~
下面是調試信息:
![]()


adb shell #mkdir mydev #exit adb push d:\android_led.ko /mydev adb shell #cd mydev #chmod 777 android_led.ko #insmod android_led.ko #lsmod //查看是否加載上了。卸載命令 rmmod android_led 不要加.ko # cat /proc/devices //也可以查看設備號和設備名。 #ls -l /dev/myled //同樣。 此時myled 權限需要修改。 #chmod 777 /dev/myled
2.2 QT210 開發板 LED流水燈 驅動程序
water_led.c的代碼
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/workqueue.h>
static struct delayed_work my_work;
//static int watercount=0;
volatile unsigned long *gph0con = NULL;
volatile unsigned long *gph0dat = NULL;
static void waterfun(void)
{
static int watercount=0;
*gph0dat = 0x1 << ((watercount++)%4);//流水燈
//printk("waterfun:watercount:%d\n",watercount);
schedule_delayed_work(&my_work,msecs_to_jiffies(1000));
}
static int __init waterinit(void)
{
int ret;
gph0con = (volatile unsigned long *)ioremap(0xE0200c00, 16);
gph0dat = gph0con + 1;
*gph0con |=0x1111 ;
*gph0con &=~0xf;
INIT_DELAYED_WORK(&my_work,waterfun);
ret = schedule_delayed_work(&my_work,msecs_to_jiffies(1000));
printk("water init:ret:%d\n",ret);
return 0;
}
static void __exit waterexit(void)
{
cancel_delayed_work(&my_work);
//destroy_wor(my_work);
//_work(&my_work);
iounmap(gph0con);
}
module_init(waterinit);
module_exit(waterexit);
MODULE_LICENSE("GPL");
Makefile的代碼:
obj-m :=water_led.o #KDIR :=/home/kernal_driver/linux-tiny6410/ KDIR :=/home/work/QT210/qt210_ics_kernel3.0.8 PWD :=$(shell pwd) all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: rm -f *.o *.bak *.order *.symvers

可以看到開發板子的 D6 D7 D8呈現流水燈(D5無法控制),證明驅動成功了。
2.3 QT210 開發板 LED驅動程序和android應用程序
目的: 編寫一個android應用程序來控制開發板上led燈的亮與滅.
編譯環境: Ubuntu12.04
Android系統: android4.0 (linux3.0.8)
一.驅動
1. 查看原理圖,QT210開發板上led D5, D6, D7, D8 對應引腳為EINT0, EINT1, EINT2, EINT3.

2. 根據底板上的EINT引腳,在核心板上找到與之相對應的引腳,最終對應到了GPIO的GPH0_0, GPH0_1, GPH0_2, GPH0_3
3. 接下來在三星 S5PV210芯片手冊上找到相應寄存器.控制led燈實際就是控制相應寄存器.
S5PV210_EVT1_Usermanual_20100218.pdf

4. 編寫驅動文件led.c
#include <linux/module.h>
#include <linux/kernel.h>
//#include <linux/io.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/pci.h>
#include <linux/ioctl.h>
#include <linux/init.h>
#include <linux/delay.h>
#define DEVICE_NAME "leds" //設備名(/dev/leds)
#define LED_MAJOR 240
unsigned long *gph0con = NULL;
unsigned long *gph0dat = NULL;
int major;
static int led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
printk(KERN_ALERT"\ncmd = %d arg = %d \n", cmd, arg);
switch(cmd)
{
case 0:
printk(KERN_ALERT"led%d off\n", arg);
switch(arg)
{
case 0:
*gph0dat &= ~0x01;
break;
case 1:
*gph0dat &= ~0x02;
break;
case 2:
*gph0dat &= ~0x04;
break;
case 3:
*gph0dat &= ~0x08;
break;
default:
break;
}
break;
case 1:
printk(KERN_ALERT"led%d on\n", arg);
switch(arg)
{
case 0:
*gph0dat |= 0x01;
break;
case 1:
*gph0dat |= 0x02;
break;
case 2:
*gph0dat |= 0x04;
break;
case 3:
*gph0dat |= 0x08;
break;
default:
break;
}
break;
case 11:
printk(KERN_ALERT"led all on\n");
*gph0dat |= 0xf;
break;
case 10:
printk(KERN_ALERT"led all off\n");
*gph0dat &= ~0xf;
break;
default:
break;
}
return 0;
}
struct file_operations led_fops={
.owner = THIS_MODULE,
.unlocked_ioctl = led_ioctl,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR, //動態設備號
.name = DEVICE_NAME,
.fops = &led_fops,
};
static int __init led_init(void)
{
int rc;
gph0con = (unsigned long *)ioremap(0xE0200C00, 16);
gph0dat = (unsigned long *)ioremap(0xE0200C04, 8);
*gph0con &= ~0xffff;
*gph0con |= 0x1111;
*gph0dat &= ~0xf;
rc = misc_register(&misc);
if(rc<0)
{
printk(KERN_ALERT"register %s char dev error\n","leds");
return -1;
}
else
printk(KERN_ALERT" lcd module OK!\n");
return 0;
}
static void __exit led_exit(void)
{
unregister_chrdev(LED_MAJOR, "leds");
printk(KERN_ALERT"module exit\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cw");
5. 編寫led.c的Makefile. 這里將led驅動編譯成模塊的方式.(注意: 編寫Makefile時,行的開頭只能用Tab,不能用空格.)
KERNELDIR :=/home/share/210/android4.0/4.0/qt210_ics_kernel3.0.8
PWD :=$(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
obj-m:=led.o
clean:
rm -rf *.o *~core .depend .*.cmd *.ko *.mod.c .tmp_versions *.order *.symvers
6. 編譯

編譯成功后生成led.ko模塊.
7. 將led.ko 拷貝到開發板上,半加載.加載之后會在/dev/目錄下生成leds這個設備.

二. Android應用程序
1. 新建一個Android應用程序


2. 打開 src/LedActivity.java, 添加public static native int led_ioctl(int i, int j);
添加一個接口是為了利用java來生成jni的頭文件. src/LedActivity.java:
package com.example.led;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class LedActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_led);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.led, menu);
return true;
}
public static native int led_ioctl(int i, int j);
}
3. 編譯整個android項目,將整個Led項目文件拷貝到ubuntu上.

新建一個jni目錄

4. 利用java文件自動生成jni頭文件.
javah -classpath bin/classes -d jni com.example.led.LedActivity

5. jni目錄下編寫led.c 即led測試程序led.c.

Led/jni/led.c:
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <android/log.h>
#define LOG_TAG "LED" //android logcat
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__ )
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS_ _)
//int main(int argc, char **argv)
jint JNICALL Java_com_example_led_LedActivity_led_1ioctl(JNIEnv *env, jclass thiz, jint led_nu, jint on)
{
int fd;
fd = open("/dev/leds", O_RDWR);
if(fd < 0)
printf("Can't open /dev/leds!\n");
ioctl(fd, on, led_nu);
LOGI("led_nu=%d,state=%d\n", led_nu, on);
close(fd);
return 0;
}
6. jni目錄下編寫Android.mk
Led/jni/Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := Led_ctrl LOCAL_SRC_FILES := led.c LOCAL_LDLIBS := -llog LOCAL_C_INCLUDES := $(MY_ANDROID_SOURCE)/frameworks/base/core/jni/android/graphics \ $(MY_ANDROID_SOURCE)/external/skia/include/core \ $(MY_ANDROID_SOURCE)/external/skia/include/images \ $(MY_ANDROID_SOURCE)/frameworks/base/include \ $(MY_ANDROID_SOURCE)/system/core/include include $(BUILD_SHARED_LIBRARY)
7. Led目錄下運行ndk-bluild, 將led.c文件編譯成so庫文件.
前提是你已經安裝了android-ndk 工具(http://blog.csdn.net/colwer/article/details/8944166)

8. 將生成的libLed_ctrl.so拷貝到eclipse下Led應用程序中的libs/armeabi目錄(如果沒有armeabi目錄需手動創建一個)

9. 在應用程序布局文件中加入4個開關按鈕,對應四個led開關,也可以再添加一個總開關來同時控制四個led.
res/layout/activity_led.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".LedActivity" > <ToggleButton android:id="@+id/btn1" android:layout_width="140dip" android:layout_height="wrap_content" android:textOn="led1 on" android:textOff="led1 off" android:layout_gravity="center_horizontal" /> <ToggleButton android:id="@+id/btn2" android:layout_width="140dip" android:layout_height="wrap_content" android:textOn="led2 on" android:textOff="led2 off" android:layout_gravity="center_horizontal" /> <ToggleButton android:id="@+id/btn3" android:layout_width="140dip" android:layout_height="wrap_content" android:textOn="led3 on" android:textOff="led3 off" android:layout_gravity="center_horizontal" /> <ToggleButton android:id="@+id/btn4" android:layout_width="140dip" android:layout_height="wrap_content" android:textOn="led4 on" android:textOff="led4 off" android:layout_gravity="center_horizontal" /> </LinearLayout>
10. 編寫java文件.
package com.example.led;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.ToggleButton;
public class LedActivity extends Activity {
private static final String TAG = "LED";
private ToggleButton button1;
private ToggleButton button2;
private ToggleButton button3;
private ToggleButton button4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_led);
button1 = (ToggleButton)findViewById(R.id.btn1);
button2 = (ToggleButton)findViewById(R.id.btn2);
button3 = (ToggleButton)findViewById(R.id.btn3);
button4 = (ToggleButton)findViewById(R.id.btn4);
button1.setOnClickListener(new Button.OnClickListener()
{
public void onClick(View v)
{
if (button1.isChecked())
led_ioctl(0, 1); //led1 on
else
led_ioctl(0, 0); //led1 off
}
});
button2.setOnClickListener(new Button.OnClickListener()
{
public void onClick(View v)
{
if (button2.isChecked())
led_ioctl(1, 1); //led2 on
else
led_ioctl(1, 0); //led2 off
}
});
button3.setOnClickListener(new Button.OnClickListener()
{
public void onClick(View v)
{
if (button3.isChecked())
led_ioctl(2, 1); //led3 on
else
led_ioctl(2, 0); //led3 off
}
});
button4.setOnClickListener(new Button.OnClickListener()
{
public void onClick(View v)
{
if (button4.isChecked())
led_ioctl(3, 1); //led4 on
else
led_ioctl(3, 0); //led4 off
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.led, menu);
return true;
}
public static native int led_ioctl(int i, int j);
static
{
System.loadLibrary("Led_ctrl"); // libs/armeabi/libLed_ctrl.so
}
}
編譯生成Led.apk,並安裝到開發板上.
至此,整個工作已經完成,開發板上打開應用程序就可以控制led了.但有幾點需要注意
1. 開發板上D5這個燈已經被占用了,所以led1不能控制D5了.
2. 記住運行應用程序前確保內核中led.ko已被加載,並修改 /dev/leds的權限,否則led燈不受控制.
