第三章第九节 LCD触摸移植
打开mach-ok6410.c:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/mach-s3c64xx$ vim mach-ok6410.c
在mach-ok6410.c中添加ts.h头文件:
#include <mach/ts.h>
将dev-ts.c文件拷贝至/linux-3.8.3/arch/arm/mach-s3c64xx/目录下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/mach-s3c64xx$ ll dev-ts.c
-rwxr-xr-x 1 zhuzhaoqi zhuzhaoqi 1552 2013-04-14 00:14 dev-ts.c*
同时在dev-ts.c文件中添加:
#include <linux/gfp.h>
将ts.h文件拷贝至/linux-3.8.3/arch/arm/mach-s3c64xx/include/mach目录下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/mach-s3c64xx/include/mach$ ll ts.h
-rwxr-xr-x 1 zhuzhaoqi zhuzhaoqi 916 2013-04-14 00:15 ts.h*
在/linux-3.8.3/arch/arm/mach-s3c64xx/目录下的Makefile文件中添加:
obj-$(CONFIG_TOUCHSCREEN_S3C) += dev-ts.o
进入mach-ok6410.c文件添加触摸初始化数据:
static struct s3c_ts_mach_info s3c_ts_platform __initdata = {
.delay = 10000,
.presc = 49,
.oversampling_shift = 2,
.resol_bit = 12,
.s3c_adc_con = ADC_TYPE_2,
};
并且在mach-ok6410.c文件中添加初始化函数:
static void __init ok6410_machine_init(void)
{
……
#if 0
s3c24xx_ts_set_platdata(NULL);
#endif
s3c_ts_set_platdata(&s3c_ts_platform);
……
}
将s3c-ts.c文件拷贝至/linux-3.8.3/drivers/input/touchscreen/目录下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/drivers/input/touchscreen$ ls s3c-ts.c
s3c-ts.c
在/linux-3.8.3/drivers/input/touchscreen/目录下的Makefile添加:
obj-$(CONFIG_TOUCHSCREEN_S3C) += s3c-ts.o
在/linux-3.8.3/arch/arm/plat-samsung/include/plat/目录下添加s3c-ts.c文件所需的ADC部分控制寄存器的宏定义:
/*----------------- Common definitions for S3C ----------------*/
/* The following definitions will be applied to S3C24XX, S3C64XX, S5PC1XX.
*/
/*-------------------------------------------------------------*/
#define S3C_ADCREG(x) (x)
#define S3C_ADCCON S3C_ADCREG(0x00)
#define S3C_ADCTSC S3C_ADCREG(0x04)
#define S3C_ADCDLY S3C_ADCREG(0x08)
#define S3C_ADCDAT0 S3C_ADCREG(0x0C)
#define S3C_ADCDAT1 S3C_ADCREG(0x10)
#define S3C_ADCUPDN S3C_ADCREG(0x14)
#define S3C_ADCCLRINT S3C_ADCREG(0x18)
#define S3C_ADCMUX S3C_ADCREG(0x1C)
#define S3C_ADCCLRWK S3C_ADCREG(0x20)
/* ADCCON Register Bits */
#define S3C_ADCCON_RESSEL_10BIT (0x0<<16)
#define S3C_ADCCON_RESSEL_12BIT (0x1<<16)
#define S3C_ADCCON_ECFLG (1<<15)
#define S3C_ADCCON_PRSCEN (1<<14)
#define S3C_ADCCON_PRSCVL(x) (((x)&0xFF)<<6)
#define S3C_ADCCON_PRSCVLMASK (0xFF<<6)
#define S3C_ADCCON_SELMUX(x) (((x)&0x7)<<3)
#define S3C_ADCCON_SELMUX_1(x) (((x)&0xF)<<0)
#define S3C_ADCCON_MUXMASK (0x7<<3)
#define S3C_ADCCON_RESSEL_10BIT_1 (0x0<<3)
#define S3C_ADCCON_RESSEL_12BIT_1 (0x1<<3)
#define S3C_ADCCON_STDBM (1<<2)
#define S3C_ADCCON_READ_START (1<<1)
#define S3C_ADCCON_ENABLE_START (1<<0)
#define S3C_ADCCON_STARTMASK (0x3<<0)
/* ADCTSC Register Bits */
#define S3C_ADCTSC_UD_SEN (1<<8)
#define S3C_ADCTSC_YM_SEN (1<<7)
#define S3C_ADCTSC_YP_SEN (1<<6)
#define S3C_ADCTSC_XM_SEN (1<<5)
#define S3C_ADCTSC_XP_SEN (1<<4)
#define S3C_ADCTSC_PULL_UP_DISABLE (1<<3)
#define S3C_ADCTSC_AUTO_PST (1<<2)
#define S3C_ADCTSC_XY_PST(x) (((x)&0x3)<<0)
/* ADCDAT0 Bits */
#define S3C_ADCDAT0_UPDOWN (1<<15)
#define S3C_ADCDAT0_AUTO_PST (1<<14)
#define S3C_ADCDAT0_XY_PST (0x3<<12)
#define S3C_ADCDAT0_XPDATA_MASK (0x03FF)
#define S3C_ADCDAT0_XPDATA_MASK_12BIT (0x0FFF)
/* ADCDAT1 Bits */
#define S3C_ADCDAT1_UPDOWN (1<<15)
#define S3C_ADCDAT1_AUTO_PST (1<<14)
#define S3C_ADCDAT1_XY_PST (0x3<<12)
#define S3C_ADCDAT1_YPDATA_MASK (0x03FF)
#define S3C_ADCDAT1_YPDATA_MASK_12BIT (0x0FFF)
在/Linux/linux-3.8.3/include/linux目录下的interrupt.h文件添加:
#define IRQF_SAMPLE_RANDOM 0x00000040
并在/linux-3.8.3/drivers/input/touchscreen/目录下的Kconfig添加LCD触摸配置:
config TOUCHSCREEN_S3C
tristate "S3C touchscreen driver"
depends on ARCH_S3C2410 || ARCH_S3C64XX || ARCH_S5P64XX || ARCH_S5PC1XX
default y
help
Say Y here to enable the driver for the touchscreen on the
S3C SMDK board.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called s3c_ts.
修改完成,执行make menuconfig,进行LCD触摸配置:
Device Drivers --->
Input device support --->
<*> S3C touchscreen driver
System Type --->
- ADC common driver support
Device Drivers --->
Input device support --->
<*> Event interface
LCD的显示和触摸配置完成之后执行make uImage命令:
……
LD vmlinux.o
arch/arm/plat-samsung/built-in.o:(.data+0x878): multiple definition of `s3c_device_ts‘
arch/arm/mach-s3c64xx/built-in.o:(.data+0x34e0): first defined here
……
出现多重定义错误,则在/linux-3.8.3/arch/arm/plat-samsung目录下的devs.c注释掉s3c_device_ts即可:
//--->zzq
#undef CONFIG_SAMSUNG_DEV_TS
//<---zzq
#ifdef CONFIG_SAMSUNG_DEV_TS
如果还有错误,则可根据错误追溯源头进行修改。
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ make uImage
……
Image Name: Linux-3.8.3
Created: Sun Apr 14 01:47:28 2013
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 2156336 Bytes = 2105.80 kB = 2.06 MB
Load Address: 50008000
Entry Point: 50008040
Image arch/arm/boot/uImage is ready
将生成的uImage拷贝至tftp目录下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/boot$ cp uImage /tftpboot/
使用tftp进行烧写、调试内核。
zhuzhaoqi@zhuzhaoqi-desktop:/tftpboot$ ls
uImage
zhuzhaoqi@zhuzhaoqi-desktop:/tftpboot$ tftp
tftp>
启动ok6410开发平台,停在U-Boot烧写内核:
zzq6410 >>> tftp 0x50008000 uImage
dm9000 i/o: 0x18000300, id: 0x90000a46
DM9000: running in 16 bit mode
MAC: 00:40:5c:26:0a:5b
operating at 100M full duplex mode
Using dm9000 device
TFTP from server 192.168.1.187; our IP address is 192.168.1.100
Filename ‘uImage‘.
Load address: 0x50008000
Loading: ######################################################################################################################################################################################################################################################################################################################################################################################################################################
done
Bytes transferred = 2156400 (20e770 hex)
烧写完成之后启动内核:
zzq6410 >>>bootm 0x50008000
## Booting kernel from Legacy Image at 50008000 ...
Image Name: Linux-3.8.3
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 2156336 Bytes = 2.1 MiB
Load Address: 50008000
Entry Point: 50008040
Verifying Checksum ... OK
XIP Kernel Image ... OK
OK
Starting kernel ...
Starting kernel ...
Uncompressing Linux... done, booting the kernel.
……
S3C_LCD clock got enabled :: 133.250 Mhz
LCD TYPE :: LTE480WV will be initialized
……
S3C Touchscreen driver, (c) 2008 Samsung Electronics
S3C TouchScreen got loaded successfully : 12 bits
input: S3C TouchScreen as /devices/virtual/input/input0
……
从串口的信息输出可知LCD的显示和触摸驱动成功。
第四章第二节 字符设备驱动
Linux操作系统将所有的设备都会看成是文件,因此当我们需要访问设备时,都是通过操作文件的方式进行访问。对字符设备的读写是以字节为单位进行的。
对字符设备驱动程序的学习过程,主要以两个具有代表性且在OK6410开发平台可实践性的字符驱动展开分析,分别为:LED驱动程序、ADC驱动程序。
1.1.1 LED
驱动程序设计
为了展现LED的裸板程序和基于Linux系统的LED驱动程序区别与减少难度梯度,在写LED驱动程序之前很有必要先看一下LED的裸板程序是怎样设计的。
1. LED
裸板程序
OK6410开发平台中有4个LED灯,原理图如图4. 1所示。
从图4. 1中可知,4个LED是共阳连接,GPM0~GPM3分别控制着LED1~LED4。而GPMCON寄存器地址为:0x7F008820;GPMDAT寄存器地址为:0x7F008824。那么GPM中3个寄存器宏定义为:
/*===============================================================
** 基地址的定义
===============================================================*/
#define AHB_BASE (0x7F000000)
/****************************************************************
** GPX的地址定义
****************************************************************/
#define GPX_BASE (AHB_BASE+0x08000)
……
/****************************************************************
** GPM寄存器地址定义
****************************************************************/
#define GPMCON (*(volatile unsigned long *)(GPX_BASE +0x0820))
#define GPMDAT (*(volatile unsigned long *)(GPX_BASE +0x0824))
#define GPMPUD (*(volatile unsigned long *)(GPX_BASE +0x0828))
将GPM0~GPM3设置为输出功能:
/* GPM0,1,2,3设为输出引脚 */
/*
** 每一个GPXCON的引脚有 4位二进制进行控制
** 0000-输入 0001-输出
*/
GPMCON = 0x1111;
点亮LED1,则是让GPM3~GPM0输出:1110。
GPMDAT = 0x0e;
点亮LED3,则是让GPM3~GPM0输出:1011。
GPMDAT = 0x0b;
2. LED
驱动程序
有了LED裸板程序的基础,移植到Linux系统LED驱动设备程序,难度也不会很大了。但是在Linux中,特别注意《s3c6410用户手册》提供的GPM寄存器地址不能直接用于Linux中。
一般情况下,Linux系统中,进程的4GB( )内存空间被划分成为两个部分:用户空间(3G)和内核空间(1G),大小分别为0~3G和3~4G。如图4. 2所示。
3~4G之间的内核空间中,从低地址到高地址依次为:物理内存映射区、隔离带、vmalloc虚拟内存分配区、隔离带、高端内存映射区、专用页面映射区。
用户进程通常情况下,只能访问用户空间的虚拟地址,不能访问到内核空间。
每个进程的用户空间都是完全独立、互不相干的,用户进程各自有不同的页表。而内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。内核空间地址有自己对应的页表,内核的虚拟空间独立于其他程序。
在内核中,访问IO内存之前,我们只有IO内存的物理地址,这样是无法通过软件直接访问的,需要首先用ioremap()函数将设备所处的物理地址映射到内核虚拟地址空间(3GB~4GB)。然后,才能根据映射所得到的内核虚拟地址范围,通过访问指令访问这些IO内存资源。
一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。Linux在io.h头文件中声明了函数ioremap(),用来将I/O内存资源的物理地址映射到核心虚地址空间(3GB-4GB)中,如下所示:
void * ioremap(unsigned long phys_addr, unsigned longsize,
unsignedlong flags);
iounmap函数用于取消ioremap()所做的映射,如下所示:
void iounmap(void *addr);
到这里应该明白,像GPMCON(0x7F008820 )这个物理地址是不能直接操控的,必须通过映射到内核的虚拟地址中,才能进行操作。
现在开始设计第一个LED驱动程序。
字符驱动程序所要包含的头文件主要位于include/linux及/arch/arm/mach-s3c64xx /include/mach目录下,如下LED驱动程序所包含的头文件:
/*
* head file
*/
//moudle.h 包含了大量加载模块需要的函数和符号的定义
#include <linux/module.h>
//kernel.h以便使用printk()等函数
#include <linux/kernel.h>
//fs.h包含常用的数据结构,如struct file等
#include <linux/fs.h>
//uaccess.h 包含copy_to_user(),copy_from_user()等函数
#include <linux/uaccess.h>
//io.h 包含inl(),outl(),readl(),writel()等IO操作函数
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/pci.h>
//init.h来指定你的初始化和清理函数,例如:module_init(init_function)、module_exit(cleanup_function)
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
//irq.h中断与并发请求事件
#include <asm/irq.h>
//下面这些头文件是IO口在内核的虚拟映射地址,涉及IO口的操作所必须包含
//#include <mach/gpio.h>
#include <mach/regs-gpio.h>
#include <plat/gpio-cfg.h>
#include <mach/hardware.h>
#include <mach/map.h>
上面所列出的头文件即是本次LED驱动程序说需要包含的头文件。
#define DEVICE_NAME "led"
#define LED_MAJOR 240 /*主设备号*/
这是LED驱动程序的驱动名称和主设备号。
设备节点位于/dev目录下,如下所示,例举出了ubuntu系统/dev/vcs*的设备节点:
zhuzhaoqi@zhuzhaoqi-desktop:~$ ls -l /dev/vcs*
……
crw-rw---- 1 root tty 7, 7 2013-04-09 20:56 /dev/vcs7
crw-rw---- 1 root tty 7, 128 2013-04-09 20:56/dev/vcsa
……
/dev/vcs7设备节点的主设备号为:7,次设备号为:7;/dev/vcsa设备节点的主设备号为:7,次设备号为:128。
#define LED_ON 0
#define LED_OFF 1
这是LED灯打开或者关闭的宏定义,由于OK6410开发平台的4个LED是共阳连接,所以输出1即为熄灭LED,输出0为点亮LED。
字符驱动程序中实现了open、close、read、write等系统调用。
open函数指针的声明位于fs.h的file_operations结构体中,如下所示:
struct file_operations {
……
int (*open) (struct inode *, struct file *);
……
};
在open函数指针的回调函数led_open()完成的任务是设置GPM的输出模式。
static int led_open(struct inode *inode,struct file*file)
{
unsigned inti;
/*设置GPM0~GPM3为输出模式*/
for (i = 0;i < 4; i++)
{
s3c_gpio_cfgpin(S3C64XX_GPM(i),S3C_GPIO_OUTPUT);
printk("TheGPMCON %x is %x \n",i,s3c_gpio_getcfg(S3C64XX_GPM(i)) );
}
printk("Led open... \n");
return 0;
}
s3c_gpio_cfgpin()函数原型位于gpio-cfg.h中,如下:
extern int s3c_gpio_cfgpin(unsigned int pin, unsignedint to);
内核对这个函数是这样注释的:s3c_gpio_cfgpin()函数用于改变引脚的GPIO功能。参数pin是GPIO的引脚名称,参数to是需要将GPIO这个引脚设置成为的功能。
GPIO的名称在arch/arm/mach-s3c6400/include/mach/gpio.h进行了宏定义:
/* S3C64XX GPIO number definitions. */
#define S3C64XX_GPA(_nr) (S3C64XX_GPIO_A_START + (_nr))
#define S3C64XX_GPB(_nr) (S3C64XX_GPIO_B_START + (_nr))
#define S3C64XX_GPC(_nr) (S3C64XX_GPIO_C_START + (_nr))
#define S3C64XX_GPD(_nr) (S3C64XX_GPIO_D_START + (_nr))
#define S3C64XX_GPE(_nr) (S3C64XX_GPIO_E_START + (_nr))
#define S3C64XX_GPF(_nr) (S3C64XX_GPIO_F_START + (_nr))
#define S3C64XX_GPG(_nr) (S3C64XX_GPIO_G_START + (_nr))
#define S3C64XX_GPH(_nr) (S3C64XX_GPIO_H_START + (_nr))
#define S3C64XX_GPI(_nr) (S3C64XX_GPIO_I_START + (_nr))
#define S3C64XX_GPJ(_nr) (S3C64XX_GPIO_J_START + (_nr))
#define S3C64XX_GPK(_nr) (S3C64XX_GPIO_K_START + (_nr))
#define S3C64XX_GPL(_nr) (S3C64XX_GPIO_L_START + (_nr))
#define S3C64XX_GPM(_nr) (S3C64XX_GPIO_M_START + (_nr))
#define S3C64XX_GPN(_nr) (S3C64XX_GPIO_N_START + (_nr))
#define S3C64XX_GPO(_nr) (S3C64XX_GPIO_O_START + (_nr))
#define S3C64XX_GPP(_nr) (S3C64XX_GPIO_P_START + (_nr))
#define S3C64XX_GPQ(_nr) (S3C64XX_GPIO_Q_START + (_nr))
S3C64XX_GPIO_M_START的定义如下:
enum s3c_gpio_number {
S3C64XX_GPIO_A_START= 0,
S3C64XX_GPIO_B_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_A),
S3C64XX_GPIO_C_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_B),
S3C64XX_GPIO_D_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_C),
S3C64XX_GPIO_E_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_D),
S3C64XX_GPIO_F_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_E),
S3C64XX_GPIO_G_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_F),
S3C64XX_GPIO_H_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_G),
S3C64XX_GPIO_I_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_H),
S3C64XX_GPIO_J_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_I),
S3C64XX_GPIO_K_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_J),
S3C64XX_GPIO_L_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_K),
S3C64XX_GPIO_M_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_L),
S3C64XX_GPIO_N_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_M),
S3C64XX_GPIO_O_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_N),
S3C64XX_GPIO_P_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_O),
S3C64XX_GPIO_Q_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_P),
};
S3C64XX_GPIO_NEXT的定义:
#define S3C64XX_GPIO_NEXT(__gpio) \
((__gpio##_START)+ (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 1)
宏定义一层一层很多,但是通过这个设置,可以很方便得选择想要的任何一个GPIO口进行操作。
GPIO功能设置位于在gpio-cfg.h中:
#define S3C_GPIO_SPECIAL_MARK (0xfffffff0)
#define S3C_GPIO_SPECIAL(x) (S3C_GPIO_SPECIAL_MARK | (x))
/* Defines for generic pin configurations */
#define S3C_GPIO_INPUT (S3C_GPIO_SPECIAL(0))
#define S3C_GPIO_OUTPUT (S3C_GPIO_SPECIAL(1))
#define S3C_GPIO_SFN(x) (S3C_GPIO_SPECIAL(x))
通过上面宏定义可知,GPIO的引脚功能有输入、输出、和你想要的任何可以实现的功能设置,S3C_GPIO_SFN(x)这个函数即是通过设定x的值,实现任何存在功能的设置。如果要设置GPM0~GPM3为输出功能,则:
for (i = 0; i < 4; i++) {
s3c_gpio_cfgpin(S3C64XX_GPM(i),S3C_GPIO_OUTPUT);
}
通过这样的操作,设置就显得比较简洁实用。
s3c_gpio_getcfg(S3C64XX_GPM(i))
这行代码的作用是获取GMP(argv)的当前值。这个函数的原型在include/linux/gpio.h中:
static inline void gpio_get_value(unsigned int gpio)
{
__gpio_get_value(gpio);
}
完成端口模式设定,接下来的程序是完成LED操作。在fs.h的file_operations结构体中,有unlocked_ioctl函数指针的声明,如下:
struct file_operations {
……
long(*unlocked_ioctl) (struct file *,unsigned int,unsigned long);
……
};
unlocked_ioctl函数指针所要回调的函数led_ioctl()函数即是需要实现应用层对LED1~LED4的控制操作。
static long led_ioctl ( struct file *file, unsignedint cmd, \
unsigned long argv )
{
if (argv> 4) {
return-EINVAL;
}
printk("LED ioctl...\n");
/*获取应用层的操作 */
switch(cmd){
/*如果是点亮LED(argv) */
case LED_ON:
gpio_set_value(S3C64XX_GPM(argv),0);
printk("LED ON \n");
printk("S3C64XX_GPM(i) = %x\n",gpio_get_value(S3C64XX_GPM(argv)) );
return0;
/*如果是熄灭LED(argv) */
caseLED_OFF:
gpio_set_value(S3C64XX_GPM(argv),1);
printk("LED OFF \n");
printk("S3C64XX_GPM(i) = %x \n",gpio_get_value(S3C64XX_GPM(argv)) );
return0;
default:
return-EINVAL;
}
}
本函数调用了GPIO端口值设定函数。
gpio_set_value(S3C64XX_GPM(argv),1);
这是设定GMP(argv)输出为1。函数的原型位于include/linux/gpio.h中:
static inline void gpio_set_value(unsigned int gpio,int value)
{
__gpio_set_value(gpio,value);
}
release函数指针所要回调的函数led_release()函数:
static int led_release(struct inode *inode,struct file*file)
{
printk("zhuzhaoqi >>> s3c6410_led release \n");
return0;
}
这是驱动程序的核心控制,各个函数指针所对应的回调函数:
struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.unlocked_ioctl = led_ioctl,
.release = led_release,
};
由于Linux3.8.3内核中没有ioctl函数指针,取而代之的是unlocked_ioctl函数指针实现对led_ioctl()函数的回调。
驱动程序的加载分为静态加载和动态加载,将驱动程序编译进内核称为静态加载,将驱动程序编译成模块,使用时再加载称为动态加载。动态加载模块的扩展名为:.ko,使用insmod命令进行加载,使用rmmod命令进行卸载。
static int __init led_init(void)
{
int rc;
printk("LEDinit... \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");
return0;
}
__init修饰词对内核是一种暗示,表明该初始化函数仅仅在初始化期间使用,在模块装载之后,模块装载器就会将初始化函数释放掉,这样就能将初始化函数所占用的内存释放出来以作他用。
当使用insmod命令加载LED驱动模块时,led_init()初始化函数将被调用,向内核注册LED驱动程序。
static void __exit led_exit(void)
{
unregister_chrdev(LED_MAJOR,"led");
printk("LED exit...\n");
}
__exit这个修饰词告诉内核这个退出函数仅仅用于模块卸载,并且仅仅能在模块卸载或者系统关闭时被调用。
当使用rmmod命令卸载LED驱动模块时,led_exit ()清除函数将被调用,向内核注册LED驱动程序。
module_init(led_init);
module_exit(led_exit);
module_init和module_exit是强制性使用的,这个宏会在模块的目标代码中增加一个特殊的段,用于说明函数所在位置。如果没有这个宏,则初始化函数和退出函数永远不会被调用。
MODULE_LICENSE("GPL");
如果没有声明LICENSE,模块被加载时,会给处理内核被污染(kernel taint)的警告。如果在zzq_led.c中没有许可证(LICENSE),则会给出如下提示:
[YJR@zhuzhaoqi 3.8.3]# insmod zzq_led.ko
zzq_led: module license ‘unspecified‘ taints kernel.
Disabling lock debugging due to kernel taint
Linux遵循GNU通用公共许可证(GPL),GPL是由自由软件基金会为GNU项目设计,它允许任何人对其重新发布甚至销售。
当然,也许程序还会有驱动程序作者和描述信息:
MODULE_DESCRIPTION("OK6410(S3C6410) LEDDriver");
完成驱动程序的设计之后,将zzq_led.c驱动程序放置于/drivers/char目录下,打开Makefile文件:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/drivers/char$gedit Makefile
在Makefile中添加LED驱动:
obj-m += zzq_led.o
回到内核的根目录执行make modules命令生成LED驱动模块:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ makemodules
……
CC [M] drivers/char/zzq_led.o
……
编译完成之后在/drivers/char目录下会生成zzq_led.ko模块,将其拷贝到文件系统下面的/lib/modules/3.8.3(如果没有3.8.3目录,则建立)目录下。
加载LED驱动模块:
[YJR@zhuzhaoqi]\# cd lib/module/3.8.3/
[YJR@zhuzhaoqi]\# ls
zzq_led.ko
[YJR@zhuzhaoqi]\# insmod zzq_led.ko
LED init...
OK!
根据信息输出可知加载zzq_led.ko驱动模块成功。通过lsmod查看加载模块:
[YJR@zhuzhaoqi]\# lsmod
zzq_led 1548 0 - Live 0xbf000000
在/dev目录下建立设备文件,如下操作:
[YJR@zhuzhaoqi]\# mknod /dev/led c 240 0
是否建立成功,可以查看/dev下的节点得知:
[YJR@zhuzhaoqi]\# ls /dev/l*
/dev/led /dev/log /dev/loop-control
说明LED设备文件已经成功建立。
3. LED
应用程序
驱动程序需要应用程序对其操控。程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LED_ON 0
#define LED_OFF 1
/*
* LED 操作说明信息输出
*/
void usage(char *exename)
{
printf("How to use: \n");
printf(" %s <LED Number><on/off>\n", exename);
printf(" LED Number = 1,2, 3 or 4 \n");
}
/*
* 应用程序主函数
*/
int main(int argc, char *argv[])
{
unsigned intled_number;
if (argc !=3) {
gotoerr;
}
int fd =open("/dev/led",2,0777);
if (fd <0) {
printf("Can‘t open /dev/led \n");
return-1;
}
printf("open /dev/led ok ... \n");
led_number =strtoul(argv[1], 0, 0) - 1;
if(led_number > 3) {
gotoerr;
}
/* LED ON */
if(!strcmp(argv[2], "on")) {
ioctl(fd, LED_ON, led_number);
}
/* LED OFF*/
else if(!strcmp(argv[2], "off")) {
ioctl(fd, LED_OFF, led_number);
}
else {
gotoerr;
}
close(fd);
return 0;
err:
if (fd >0) {
close(fd);
}
usage(argv[0]);
return -1;
}
在main()函数中,涉及到了open()函数,其原型如下:
int open( const char* pathname,int flags, mode_t mode);
当然,很多open函数中的入口参数也是只有2个,原型如下:
int open( const char* pathname, int flags);
第一个参数pathname是一个指向将要打开的设备文件途径字符串。
第二个参数flags是打开文件所能使用的旗标,常用的几种旗标有:
O_RDONLY:以只读方式打开文件。
O_WRONLY:以只写方式打开文件。
O_RDWR:以可读写方式打开文件。
上述三种常用的旗标是互斥使用,但可与其他的旗标进行或运算符组合。
第三个参数mode是使用该文件的权限。比如777,755等。
通过这个应用程序实现对LED驱动程序的控制,为了更加方便快捷编译这个应用程序,为其写一个Makefile文件,如下所示:
#交叉编译链安装路径
CC = /usr/local/arm/4.4.1/bin/arm-linux-gcc
zzq_led_app:zzq_led_app.o
$(CC) -ozzq_led_appzzq_led_app.o
zzq_led_app.o:zzq_led_app.c
$(CC) -czzq_led_app.c
clean :
rm zzq_led_app.ozzq_led_app
执行Makefile之后会生成zzq_led_app可执行应用文件,如下:
zhuzhaoqi@zhuzhaoqi-desktop:~/LDD/linux-3.8.3/zzq_led$make
/usr/local/arm/4.4.1/bin/arm-linux-gcc -czzq_led_app.c
/usr/local/arm/4.4.1/bin/arm-linux-gcc -o zzq_led_appzzq_led_app.o
zhuzhaoqi@zhuzhaoqi-desktop:~/LDD/linux-3.8.3/zzq_led$ls
Makefile zzq_led_app zzq_led_app.c zzq_led_app.o zzq_led.c
将生成的zzq_led_app可执行应用文件拷贝到根文件系统的/usr/bin目录下,执行应用文件,如下操作:
[YJR@zhuzhaoqi]\# ./zzq_led_app
How to use:
./zzq_led_app <LED Number><on/off>
LED Number =1, 2, 3 or 4
根据信息提示可以进行对LED驱动程序的控制,点亮LED1,则如下:
[YJR@zhuzhaoqi]\# ./zzq_led_app 1 on
The GPMCON 0 is fffffff1
The GPMCON 1 is fffffff1
The GPMCON 2 is fffffff1
The GPMCON 3 is fffffff1
zhuzhaoqi >>> LED open...
LED ioctl...
LED ON
S3C64XX_GPM(i) = 0
LED release...
open /dev/led ok ...
此时可以看到LED1点亮。
注:本节配套视频位于光盘中“嵌入式Linux实用教程视频”目录下第四章01课(字符设备驱动之LED)。
第四章第三节 ADC驱动程序设计
A/D转换即是将模拟量转换为数字量,在物联网迅速发展的今天,作为物联网的感知前端传感器也随之迅速更新,压力、温度、湿度等众多模拟信号的处理都需要涉及到A/D转换,因此A/D驱动程序学习在嵌入式占据着重要地位。
1. S3C6410的ADC控制寄存器简介
S3C6410控制芯片自带有4路独立专用A/D转换通道,如图4. 3所示。
图4. 3A/D转换连接图
透过三星公司提供的《s3c6410用户手册》可知,ADCCON为ADC控制寄存器,地址为:0x7E00 B0000。ADCCON的复位值为:0x3FC4,即为:0011 1111 1100 0100。
#define S3C_ADCREG(x) (x)
#define S3C_ADCCON S3C_ADCREG(0x00)
ADCCON控制寄存器具有16位,每一位都能通过赋值来实现其相对应的功能。
ADCCON[0]:ENABLE_START,A/D 转换开始启用。如果READ_START 启用,这个值是无效的。ENABLE_START = 0,无行动;ENABLE_START = 1,A/D 转换开始和该位被清理后开启。ADCCON[0]的复位值为0,即复位之后默认为无行动。
#define S3C_ADCCON_NO_ENABLE_START (0<<0)
#define S3C_ADCCON_ENABLE_START (1<<0)
ADCCON[1]:READ_START,A/D 转换开始读取。READ_START = 0,禁用开始读操作;READ_START = 1,启动开始读操作。ADCCON[1]的复位值为0,禁用开始读操作。
#define S3C_ADCCON_NO_READ_START (0<<1)
#define S3C_ADCCON_READ_START (1<<1)
ADCCON[2]:STDBM,待机模式选择。STDBM = 0,正常运作模式;STDBM = 1,待机模式。ADCCON[2]的复位值为1,待机模式。
#define S3C_ADCCON_RUN (0<<2)
#define S3C_ADCCON_STDBM (1<<2)
ADCCON[5:3]:SEL_MUX,模拟输入通道选择。SEL_MUX = 000,AIN0;SEL_MUX = 001,AIN1;SEL_MUX = 010,AIN2;SEL_MUX = 011,AIN3;SEL_MUX = 100,YM;SEL_MUX = 101,YP;SEL_MUX = 110,XM;SEL_MUX = 111,XP。ADCCON[5:3]的复位值为000,选用AIN0通道。
#define S3C_ADCCON_RESSEL_10BIT_1 (0x0<<3)
#define S3C_ADCCON_RESSEL_12BIT_1 (0x1<<3)
#define S3C_ADCCON_MUXMASK (0x7<<3)
#define S3C_ADCCON_SELMUX(x) (((x)&0x7)<<3) //任意通道的选择
ADCCON[13:6]:PRSCVL,ADC 预定标器值0xFF。数据值:5~255。ADCCON[13:6]的复位值为1111 1111,即为0xFF。
#define S3C_ADCCON_PRSCVL(x) (((x)&0xFF)<<6) // 任意值设定
#define S3C_ADCCON_PRSCVLMASK (0xFF<<6) //复位值
ADCCON[14]:PRSCEN,ADC预定标器启动。PRSCEN = 0,禁用;PRSCEN = 0,启用。ADCCON[14]的复位值为0,禁用ADC预定标器。
#define S3C_ADCCON_NO_PRSCEN (0<<14)
#define S3C_ADCCON_PRSCEN (1<<14)
ADCCON[15]:ECFLG,转换的结束标记(只读)。ECFLG = 0,A/D 转换的过程中;ECFLG = 1,A/D 转换结束。ADCCON[15]的复位值为0,A/D 转换的过程中。
#define S3C_ADCCON_ECFLG_ING (0<<15)
#define S3C_ADCCON_ECFLG (1<<15)
ADCDAT0寄存器为ADC 的数据转换寄存器。地址为:0x7E00B00C。
ADCDAT0[9:0]:XPDATA,X 坐标的数据转换(包括正常的ADC 的转换数据值)。数据值: 0x000~0x3FF。
ADCDAT0[11:10]:保留。当启用12位AD时作为转换数据值使用。
#define S3C_ADCDAT0_XPDATA_MASK (0x03FF)
#define S3C_ADCDAT0_XPDATA_MASK_12BIT (0x0FFF)
上面所介绍的是专用A/D转换通道常用寄存器,LCD触摸屏A/D转换有另外A/D通道
2. ADC驱动程序
A/D转化驱动由于也属于字符设备驱动,所以其程序设计流程和LED驱动大体一致。在linux-3.8.3/drivers/char目录下新建zzqadc.c驱动文件,当然也可写好之后在拷贝到linux-3.8.3/drivers/char目录下。
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/drivers/char$ vim zzqadc.c
头文件是必不可少的,A/D驱动程序所要包含的头文件如下所示:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <mach/map.h>
#include <mach/regs-clock.h>
#include <mach/regs-gpio.h>
#include <plat/regs-adc.h>
与LED驱动程序所包含的头文件相比较,多了ADC专用的头文件,如regs-adc.h,这个头文件位于linux-3.8.3/arch/arm/plat-samsung/include/plat目录下。
static void __iomem *base_addr;
static struct clk *adc_clock;
#define __ADCREG(name) (*(unsigned long int *)(base_addr + name))
自从linux-2.6.9版本开始便把__iomem加入内核,__iomem是表示指向一个I/O的内存空间。将__iomem加入linux,主要是考虑到驱动程序的通用性。由于不同的CPU体系结构对I/O空间的表示可能不同,但是当使用__iomem时,就会忽略对变量的检查,因为__iomem使用的是void。
#define S3C_ADCREG(x) (x)
#define S3C_ADCCON S3C_ADCREG(0x00)
#define S3C_ADCDAT0 S3C_ADCREG(0x0C)
/* ADC contrl */
#define ADCCON __ADCREG(S3C_ADCCON)
/* read the ADdata */
#define ADCDAT0 __ADCREG(S3C_ADCCON)
声明ADC控制寄存器的地址。
/* The set of ADCCON */
#define S3C_ADCCON_ENABLE_START (1 << 0)
#define S3C_ADCCON_READ_START (1 << 1)
#define S3C_ADCCON_RUN (0 << 2)
#define S3C_ADCCON_STDBM (1 << 2)
#define S3C_ADCCON_SELMUX(x) ( ((x)&0x7) << 3 )
#define S3C_ADCCON_PRSCVL(x) ( ((x)&0xFF) << 6 )
#define S3C_ADCCON_PRSCEN (1 << 14)
#define S3C_ADCCON_ECFLG (1 << 15)
/* The set of ADCDAT0 */
#define S3C_ADCDAT0_XPDATA_MASK (0x03FF)
#define S3C_ADCDAT0_XPDATA_MASK_12BIT (0x0FFF)
根据上一小节对ADCCON和ADCDAT0的介绍,可以很容易写出上面宏定义。
在使用ADC之前,先得对ADC进行初始化设置,由于OK6410开发平台自带的A/D电压采样电路选用的是AIN0通道。则这里需要对进行AIN0初始化,初始化阶段需要完成的事情为:A/D 转换开始和该位被清理后开启、正常运作模式、模拟输入通道选择AIN0、ADC 预定标器值0xFF、ADC预定标器启动。
/*
* AIN0 init
*/
static int adc_init(void)
{
ADCCON = S3C_ADCCON_PRSCEN | S3C_ADCCON_PRSCVL(0xFF) | \
S3C_ADCCON_SELMUX(0x00) | S3C_ADCCON_RUN;
ADCCON |=S3C_ADCCON_ENABLE_START;
return 0;
}
open函数指针的实现函数adc_open():
/*
* open dev
*/
static int adc_open(struct inode *inode, struct file *filp)
{
adc_init();
return 0;
}
release函数指针的实现函数adc_release():
/*
* release dev
*/
static int adc_release(struct inode *inode,struct file *filp)
{
return 0;
}
read()函数指针的实现函数adc_read(),这个函数的作用是读取ADC采样数据。
/*
* adc_read
*/
static ssize_t adc_read(struct file *filp, char __user *buff,
size_t size, loff_t *ppos)
{
ADCCON |= S3C_ADCCON_READ_START;
/* check the adc Enabled ,The [0] is low*/
while(ADCCON & 0x01);
/* check adc change end */
while(!(ADCCON & 0x8000));
/* return the data of adc */
return (ADCDAT0 & S3C_ADCDAT0_XPDATA_MASK);
}
ADC驱动程序的核心控制部分:
static struct file_operations dev_fops =
{
.owner = THIS_MODULE,
.open = adc_open,
.release = adc_release,
.read = adc_read,
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = “zzqadc“,
.fops = &dev_fops,
};
加载insmod驱动程序如下所示:
static int __init dev_init()
{
int ret;
/* Address Mapping */
base_addr = ioremap(SAMSUNG_PA_ADC,0X20);
if(base_addr == NULL)
{
printk(KERN_ERR"failed to remap \n");
return -ENOMEM;
}
/* Enabld acd clock */
adc_clock = clk_get(NULL,"adc");
if(!adc_clock)
{
printk(KERN_ERR"failed to get adc clock \n");
return -ENOENT;
}
clk_enable(adc_clock);
ret = misc_register(&misc);
printk("dev_init return ret: %d \n", ret);
return ret;
}
加载insmod驱动程序,这里使用到了ioremap()函数,在内核驱动程序的初始化阶段,通过ioremap()函数将物理地址映射到内核虚拟空间;在驱动程序的mmap系统调用中,使用remap_page_range()函数将该块ROM映射到用户虚拟空间。这样内核空间和用户空间都能访问这段被映射后的虚拟地址。
ioremap()宏定义在asm/io.h内:
#define ioremap(cookie,size) __ioremap(cookie,size,0)
__ioremap函数原型为(arm/mm/ioremap.c):
void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned longflags);
phys_addr:要映射的起始的IO地址;
size:要映射的空间的大小;
flags:要映射的IO空间和权限有关的标志。
该函数返回映射后的内核虚拟地址(3G-4G),接着便可以通过读写该返回的内核虚拟地址去访问之这段I/O内存资源。
base_addr = ioremap(SAMSUNG_PA_ADC,0X20);
这行代码即是将SAMSUNG_PA_ADC(0x7E00 B000)映射到内核,返回内核的虚拟地址给base_addr。
clk_get(NULL,"adc")可以获得adc时钟,每一个外设都有自己的工作频率,PRSCVL是A/D转换器时钟的预分频功能时A/D时钟的计算公式,A/D时钟 = PCLK / (PRSCVL+1)。
注意:AD时钟最大为2.5MHZ并且应该小于PCLK的1/5。
adc_clock = clk_get(NULL,"adc");
即为获取adc的工作时钟频率。
ret = misc_register(&misc);
创建杂项设备节点。这里使用到了杂项设备,杂项设备也是在嵌入式系统中用得比较多的一种设备驱动。在 Linux 内核的include/linux目录下有miscdevice.h文件,要把自己定义的misc device从设备定义在这里。其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主编号10 ,一起归于misc device,其实misc_register就是用主标号10调用register_chrdev()的。也就是说,misc设备其实也就是特殊的字符设备,可自动生成设备节点。
(杂项设备结构体分析)
卸载rmmod驱动程序:
static void __exit dev_exit()
{
iounmap(base_addr);
/* disable ths adc clock */
if(adc_clock)
{
clk_disable(adc_clock);
clk_put(adc_clock);
adc_clock = NULL;
}
misc_deregister(&misc);
}
许可证声明、作者信息、调用加载和卸载程序:
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhuzhaoqi
jxlgzzq@163.com");
module_init(dev_init);
module_exit(dev_exit);
在/linux-3.8.3/drivers/char目录下的Makefile中添加:
obj-m += zzqadc.o
回到/linux-3.8.3根目录下:
/home/zhuzhaoqi/Linux/linux-3.8.3# make modules
将/linux-3.8.3/drivers/char目录下生成的zzqadc.ko拷贝到文件系统的/lib/module/3.8.3目录中。
3. ADC应用程序
ADC应用程序也是相对简单,打开设备驱动文件之后进行数据读取即可。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fp,adc_data,i;
fp = open("/dev/zzqadc",O_RDWR);
if (fp < 0)
{
printf("open failed! \n");
}
printf("opened ... \n");
for ( ; ; i++)
{
adc_data = read(fp,NULL,0);
printf("Begin the NO. %d test... \n",i);
printf("adc_data = %d \n",adc_data);
printf("The Value = %f V \n" , ( (float)adc_data )* 3.3 / 1024);
printf("End the NO. %d test ...... \n \n",i);
sleep(1);
}
close(fp);
return 0;
}
由于本次使用的A/D转换是10位,数据转换值即为1024,而OK6410的参考电压是3.3V,则A/D采集数据和电压之间的转换公式为:(float)adc_data )* 3.3 / 1024。
为ADC应用程序编写Makefile:
CC = /usr/local/arm/4.4.1/bin/arm-linux-gcc
zzqadcapp:zzqadcapp.o
$(CC) -o zzqadcapp zzqadcapp.o
zzqadcapp.o:zzqadcapp.c
$(CC) -c zzqadcapp.c
clean :
rm zzqadcapp.o zzqadcapp
将生成的zzqadcapp应用文件拷贝到到跟文件系统/usr/bin文件夹下。
加载zzqadc.ko设备:
[YJR@zhuzhaoqi 3.8.3]# insmod zzqadc.ko
dev_init return ret: 0
[YJR@zhuzhaoqi]\# ls -l /dev/zzqadc
crw-rw---- 1 root root 10, 60 Jan 1 08:00 /dev/zzqadc
在/dev目录下存在zzqadc设备节点,则说明ADC驱动加载成功。
执行ADC应用程序,电压采样如下所示:
[YJR@zhuzhaoqi]\# ./zzqadcapp
opened ...
……
Begin the NO. 10 test...
adc_data = 962
The Value = 3.100195 V
End the NO. 10 test ......
……
注:本节配套视频位于光盘中“嵌入式Linux实用教程视频”目录下第四章02课(字符设备驱动之ADC)。
第五章第一节 Qt编译环境搭建
在进行Qt开发之前,建立Qt编译环境、移植Qt是一个至关重要的步骤。
1.1.1 tslib
安装
OK6410开发平台在使用触摸屏时,因为电磁噪声的缘故,触摸屏容易存在点击不准确、有抖动等问题。tslib能够为触摸屏驱动获得的采样提供诸如滤波、去抖、校准等功能,通常作为触摸屏驱动的适配层,为上层的应用提供一个统一的接口。
在官方网站下载tslib-1.0.tar.bz2,地址:http://sourceforge.net/projects/tslib.berlios/files/。
将下载完成之后的tslib-1.0.tar.bz2存放在宿主机任意一个目录下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib$ ls
tslib-1.0.tar.bz2
将其解压出来:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib$ tar jxvftslib-1.0.tar.bz2
解压完成之后进入tslib-1.0目录,如下所示:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib/tslib-1.0$ls
acinclude.m4 autogen.sh COPYING m4 plugins tests
AUTHORS ChangeLog etc Makefile.am tslib.pc.in
autogen-clean.sh configure.ac INSTALL NEWS src
安装autoconf、automake、libtool:
root@zhuzhaoqi-desktop:/home/zhuzhaoqi#apt-get installautoconf
root@zhuzhaoqi-desktop:/home/zhuzhaoqi#apt-get installautomake
root@zhuzhaoqi-desktop:/home/zhuzhaoqi#apt-get installlibtool
由于open函数的语法不符合最新的gcc,在/tests/ts_calibrate.c中加入open的第三个参数:
if ((calfile = getenv("TSLIB_CALIBFILE")) !=NULL) {
cal_fd= open (calfile, O_CREAT | O_RDWR,0777);
} else {
cal_fd= open ("/etc/pointercal", O_CREAT | O_RDWR,0777);
}
执行编译:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib/tslib-1.0$./autogen.sh
……
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib/tslib-1.0$./configure --prefix=/usr/local/tslib-1.0/ --host=arm-linux ac_cv_func_malloc_0_nonnull=yes--enable-inputapi=no
……
--prefix=/usr/local/tslib-1.0/这是安装路径,进行编译、安装。
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib/tslib-1.0$make
……
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib/tslib-1.0$sudomake install
安装完成之后在/usr/local/目录下有:
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local$ ls
arm etc include lib qwt-6.0.2 sbin src
bin games info man qwt-6.0.2-arm share tslib-1.0
修改ts.conf:
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local/tslib-1.0/etc$vim ts.conf
去掉module_rawinput前面的#,注意前面这个空格也得删除,如下:
# Uncomment if you wish to use the linux input layerevent interface
module_raw input
将/usr/local/tslib-1.0/目录下的所有文件拷贝到开发板,笔者是存放在usr/local/。如下:
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local/tslib-1.0$ sudocp -r * /home/zhuzhaoqi/rootfs/usr/local/
在OK6410中设置tslib环境变量,在文件系统的/etc/profile中添加如下:
//指定帧缓冲设备
export set TSLIB_FBDEVICE=/dev/fb0
//指定触摸屏设备节点
export set TSLIB_TSDEVICE=/dev/input/event0
//指定TSLIB 配置文件的位置
export set TSLIB_CONFFILE=/usr/local/etc/ts.conf
//指定触摸屏校准文件 pintercal 的存放位置
export set TSLIB_CALIBFILE=/etc/pointercal
//指定触摸屏插件所在路径
export set TSLIB_PLUGINDIR=/usr/local/lib/ts
//设定控制台设备为 none ,否则默认为 /dev/tty ,
export TSLIB_CONSOLEDEVICE=none
在测试触摸屏之前,首先得保证在/dev目录下有触摸屏设备节点eventX:
[YJR@zhuzhaoqi]\# ls -l /dev/input/e*
crw-rw---- 1 root root 13, 64 Jan 1 08:00 /dev/input/event0
运行ts_calibrate:
[YJR@zhuzhaoqi]\# cd bin/
[YJR@zhuzhaoqi]\# ls
ts_calibrate ts_harvest ts_print ts_print_raw ts_test
[YJR@zhuzhaoqi]\# ./ts_calibrate
运行/usr/local/bin中的ts_calibrate进行校准,成功的话会出现界面,并点击十字符号,完成后会生成/etc/pointercal文件,这便是触摸屏的校准配置文件。
或者可以写一个触摸屏校准脚本calibrate,存放在/bin目录下:
#!/bin/sh
export TSLIB_FBDEVICE=/dev/fb0
export TSLIB_TSDEVICE=/dev/input/event0
export TSLIB_CONFFILE=/usr/local/tslib/etc/ts.conf
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_PLUGINDIR=/usr/local/tslib/lib/ts
export TSLIB_TSEVENTTYPE=H3600
export TSLIB_CONSOLEDEVICE=none
export QWS_KEYBOARD="TTY:/dev/tty1"
if [ -c /dev/input/event0 ]; then
if [ -e/etc/pointercal -a ! -s /etc/pointercal ] ; then
rm /etc/pointercal
fi
fi
export PATH=$QTDIR/bin:$PATH
exportLD_LIBRARY_PATH=$QTDIR/plugins/qtopialmigrate/:$QTDIR/qt_plugins/imageformats/:$QTDIR/lib:/root/tslib/build/lib:$LD_LIBRARY_PATH
exec /usr/local/tslib/bin/ts_calibrate 1>/dev/null 2>/dev/null
#exec /usr/local/tslib/bin/ts_test 1>/dev/null 2>/dev/null
执行calibrate:
[YJR@zhuzhaoqi]\# ./calibrate
如果执行的是ts_calibrate测试,效果如图5. 1所示。
如果执行的是ts_test测试,选择draw画图选项,效果如图5. 2所示。
注:本节配套视频位于光盘中“嵌入式Linux实用教程视频”目录下第五章01课(tslib安装)。
第五章第二节 安装Linux/x11版Qt-4.8.4
在官方网站下载Qt libraries 4.8.4 for Linux/X11 (225 MB)(实际是:qt-everywhere-opensource-src-4.8.4.tar.gz)。
完成之后在ubuntu宿主机解压:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4$ tarzxvf qt-everywhere-opensource-src-4.8.4.tar.gz
在装有gold linker的系统里,编译脚本会加入-fuse-ld=gold选项,但这个选项gcc是不支持的。解决办法是移除该选项,找到文件src/3rdparty/webkit/Source/common.pri,屏蔽QMAKE_LFLAGS+=-fuse-ld=gold。
linux-g++ {
isEmpty($$(SBOX_DPKG_INST_ARCH)):exists(/usr/bin/ld.gold){
message(Using gold linker)
# QMAKE_LFLAGS+=-fuse-ld=gold
}
}
配置安装路径:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/qt-everywhere-opensource-src-4.8.4$./configure --prefix=/usr/local/qt-4.8.4-x11
……
Type ‘c‘ if you want to use the Commercial Edition.
Type ‘o‘ if you want to use the Open Source Edition.
c是商业,o是开源,选择o。
……
Type ‘yes‘ to accept this license offer.
Type ‘no‘ to decline this license offer.
选择yes。
……
Qt is now configured for building. Just run ‘make‘.
Once everything is built, you must run ‘make install‘.
Qt will be installed into /usr/local/qt-4.8.4-x11
To reconfigure, run ‘make confclean‘ and ‘configure‘.
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/qt-everywhere-opensource-src-4.8.4$
Qt输出信息提示我们进行make编译:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/qt-everywhere-opensource-src-4.8.4$make
这个编译过程比较久,依每个人的电脑配置而定,大概需要1~3个小时。
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/qt-everywhere-opensource-src-4.8.4$make install
安装好之后,在/usr/local目录下面有:
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local$ ls
arm etc include lib qt-4.8.4-arm qwt-6.0.2 sbin src
bin games info man qt-4.8.4-x11 qwt-6.0.2-arm tslib-1.0
1.1.2
安装embedded
版Qt-4.8.4
Embedded版Qt4.8.4源码和Linux/x11版Qt4.8.4是一样的,将下载的源码解压在另一个文件夹,配置embedded版配置:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/arm/qt-everywhere-opensource-src-4.8.4$./configure -prefix /usr/local/qt-4.8.4-arm/-shared -no-fast -no-largefile -no-exceptions -qt-sql-sqlite -qt3support-no-xmlpatterns -multimedia -no-svg -no-mmx -no-3dnow -no-sse -no-sse2 -qt-zlib-no-webkit -qt-libtiff -qt-libpng -qt-libjpeg -make libs -nomake examples-nomake docs -nomake demo -no-optimized-qmake -no-nis -no-cups -no-iconv-no-dbus -no-separate-debug-info -no-openssl -xplatform qws/linux-arm-g++-embedded arm -little-endian -no-freetype -depths 4,8,16,32 -qt-gfx-linuxfb-no-gfx-multiscreen -no-gfx-vnc -no-gfx-qvfb -qt-kbd-linuxinput -no-kbd-tty-no-glib -armfpa -no-mouse-qvfb -qt-mouse-pc -qt-mouse-tslib -I/usr/local/tslib-1.0/include-L/usr/local/tslib-1.0/lib
执行make进行编译:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/arm/qt-everywhere-opensource-src-4.8.4$make
执行makeinstall进行安装:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/arm/qt-everywhere-opensource-src-4.8.4$make install
安装好之后,在/usr/local目录下面有:
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local$ ls
arm etc include lib qt-4.8.4-arm qwt-6.0.2 sbin src
bin games info man qt-4.8.4-x11 qwt-6.0.2-arm tslib-1.0
在qt-4.8.4-arm目录下有如下目录:
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local/qt-4.8.4-arm$ls
bin imports include lib mkspecs plugins
在文件系统/opt/目录下新建Qt-4.8.4目录,如下所示:
zhuzhaoqi@zhuzhaoqi-desktop:~/rootfs/opt$ sudo mkdirQt-4.8.4/
将imports、lib、mkspecs、plugins拷贝至/opt/Qt-4.8.4/。
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local/qt-4.8.3-arm$sudo cp -r importslibmkspecsplugins /home/zhuzhaoqi/rootfs/opt/Qt-4.8.4/
在开发板的文件系统/usr/目录下新建/qt/目录,将/qt-4.8.4-arm/lib/目录下的所有文件拷贝到/usr/qt/目录中。
zhuzhaoqi@zhuzhaoqi-desktop:~/rootfs/usr$ sudo mkdirqt
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local/qt-4.8.4-arm/lib$sudo cp -r * /home/zhuzhaoqi/rootfs/usr/qt/
为OK6410开发平台添加Qt启动环境参数,在/etc/profile中添加:
export QTDIR=/usr/qt
export QPEDIR=$QTDIR
export QT_PLUGIN_PATH=/usr/qt
export T_ROOT=/usr/local/tslib
export PATH=$QTDIR/:$PATH
export QWS_MOUSE_PROTO=Tslib:/dev/event0
export LD_LIBRARY_PATH=$T_ROOT/lib:$QTDIR
export QT_QWS_FONTDIR=/usr/qt
至此,Qt移植就完成。
注:本节配套视频位于光盘中“嵌入式Linux实用教程视频”目录下第五章02课(安装Linux和embedded版本Qt-4.8.4)。