一.项目描述
现在我们所用的手机都是利用GSM网络来实现通信的,为了使手机发挥更大的用途,我们就产生此次话题。
本项目的初期效果:利用我们的手机发送短信到我们的GSM MODEM上,通过短信的解码,再将收到的短信显示到LCD(我们这里用的OCMJ4X8C)上,将可以实现上下滚屏。
实验平台是三星公司的S3C44B0开发板,选用的操作系统为uClinux2.4内核。开发平台为RedHad linux 9.0系统,X86计算机。
二. 关键技术
1. 汉字编码
在我们的TC35i收到的短信中,其信息内容部分是采用Unicode编码的,而要求显示到LCD中时,则要求显示汉字,英文都要采用GB2312编码,因此就涉及到一个编码转换的问题。
Unicode编码是一种国际统一编码规则,其字符符采用16位的编码体系,即每个符号编码占用两个字节。目录的V2.0版本公布之中,汉字有20902个,其编码范围为0x4E00-0x9FFF,如下图:
因此我们要采用某种技术将Unicode编码的短信转换成GB2312编码的信息。
2. 编码转换
如前一点所述,实现从Unicode编码到GB2312编码是本项目的关键问题之一。在前面我们已经描述过,本项目的应用平如是S3C44B0,采用了uClinux操作系统。
uClinux是一个裁剪过的小型操作系统,因此它有一些库是不支持的,如本项目中的编码转换库 iconv库它就不支持,这样给我们的平台移植带来了一定的困难,为了解决这个问题,我们必须要自己构造一个转换表,通过一个映射函数完成从Unicode编码到GB2312编码的转换。下面介绍一下我们这个转换表的设计过程。
首先,我从网上download了一个转换表,编码中国网站中有一个和荣笔记不错。但下载下来的并不能直接应用,我们要将它转换成合理的数据结构,这样才可以提高我们的查找效率以及节省我们的存取空间。下面是我们这个表的数据结构:
typedef struct ST
{
unsigned int unicode;
unsigned int gb2312;
}SUG,*PSUG;
其中在这个表定下来之前,我采用的是另一数据结构(当时考虑的具有更快的查找效率的哈希函数),它依据的是汉字编码在Unicode编码体系中的分配空间,由前面我们知道它是0x4E00-0x9FFF,这样的话它占用的空间大概是4-5万个字节(有2万多个汉字,每个汉字占2个字节),而我们只是用到了一级汉字库,大概2500个汉字,这样我们的存储空间利用率是非常低的,大概在15%左右,我觉得不可取。由于当汉字采用GB2312编码时,其编码空间是连续的,而其对应的Unicode编码空间却是离散的,有些地方很大的间隔都没有汉字编码,因此,我改成了上面的数据结构,将一级汉字编码区的编码抽出来,去掉汉字编码区中没有编码的空间,并将这些编码按其Unicode编码的增序存取,这样就可以进行折半查找。这张查找表的空间利用率达到了100%,虽然牺牲一点查找时间,但却没有对系统造成什么影响。整个表的大小大约2万个字节。
3. 短信解码
GSM Modem可以采用三种方式来实现短信的接收与发送:Block Mode, Text Mode和PDU Mode。Block Mode已是昔日黄花,目前很少用了。Text Mode是纯文本方式,可使用不同的字符集,从技术上说也可用于发送中文短消息,但国内手机基本上不支持,主要用于欧美地区。PDU Mode被所有手机支持,可以使用任何字符集,这也是手机默认的编码方式。Text Mode比较简单,而且不适合做自定义数据传输了。下面介绍的内容,是在PDU Mode下发送和接收短消息的实现方法。
在PDU模式下,GSM Modem接收到的短信是采用Unicode编码的,而且在收到的短信中还有一些附加的信息,我们应当对信息进行分解,得到有用的信息。
Ø 接收短信的数据格式
接收:SMSC号码是+8613800250500,对方号码是13851872468,消息内容是“你好!”。手机接收到的PDU串可以是
08 91 68 31 08 20 05 05 F0 84 0D 91 68 31 58 81 27 64 F8 00 08 30 30 21 80 63 54 80 06 4F 60 59 7D 00 21
对照规范,具体分析:
分段 含义 说明
08 地址信息的长度 个八位字节(包括91)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,补‘F’凑成偶数个
84 基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址
0D 回复地址数字个数 共13个十进制数(不包括91和‘F’)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 58 81 27 64 F8 回复地址(TP-RA) 8613851872468,补‘F’凑成偶数个
00 协议标识(TP-PID) 是普通GSM类型,点到点方式
08 用户信息编码方式(TP-DCS) UCS2编码
30 30 21 80 63 54 80 时间戳(TP-SCTS) 2003-3-12 08:36:45 +8时区
06 用户信息长度(TP-UDL) 实际长度6个字节
4F 60 59 7D 00 21 用户信息(TP-UD) “你好!”
Ø 发送短信格式
首先,我们就向GSM Modem发送一条表示发送信息的AT指令:AT+CMGS=<length><CR>
其中length是有一个计算公式的:
Length = 15+内容的长度*4。
例如发送“你好”,则length为(15+2*4)=23。这里还要将23转换成字符串形式,即“23”来发送。
其中15是信息的头部:“0011000D9168”+<电话号码>+“000801”,以字节为单位计算,即以两个字符为作为一个字节。最后发送<Ctrl+Z>(编码为0X1A)+<CR>
三. OCMJ4X8C驱动编写
关于字符设备开发的流程在网上以及图书馆都可以找到很多的资料,我这个LCD的驱动也是作为字符设备来做的,因此开发流程是完全一致的,这里也就不再重复叙述。
其实一个驱动程序无论做的多么复杂,追根到底都是转换成对处理器的某个I/O端口的操作,LCD的驱动也不例外,当然我们这个项目用的LCD是比较简单的,它只有128*64个像素,20个引脚,可工作在串行模式或并行模式,当PSB为高电平时,模块将进入并行模式,在并列模式下可由指令DL FLAG 来选择8-位或4-位接口。这里我们用的是8位的并行口。关于并行模式下模块的操作接口时序图,可以在技术资料中找到详细的叙述。
OCMJ4X8C有两个指令集,基本指令集和扩充指令集,使用哪一种指令集可以由位RE来设定,这里用到的是基本指令集。关于模块在并行模式,基本指令集下的操作可以参考ITSN公司给的例程。
下面总结一个如何将LCD驱动程序的编写步骤以及如何将编译进内核中。
首先在驱动的入口处ocmlcd_init()中注册该驱动程序,这里要使用regitser_chrdev()来注册驱动号,即主设备号,设备名;然后我们就要实现与设备密切相关的功能函数。主要是我们在file_operations中定义的功能函数。
ocmlcd_open():这个函数是用记在打开LCD设备时调用的,我们知道linux中把所有的设备都看成一个特殊的文件,在这个函数中我们可以完成对设备的初始化,即调用我们自定义的函数lcm_init();
ocmlcd_write(),这个函数是完成对LCD设备的写操作的,在它传递的参数中,有一个指针数组,它指向了要写的数据堆,还有一个是该数据的大小。由于OCMJ4X8C是只能显示GB2312编码的汉字,所以我们应当保证我们传递的数据是GB2312编码格式的,否则的话在屏幕上显示的将是乱码,这一点是要注意的。
对LCD的操作基本是写的操作,这里在file_operations中只定义了这两个功能函数,更多更复杂的操作可以定义ioctl()函数,这一个留待以后再进行完善。
最后来看一下如何将驱动添加到内核中。
1. 在uClinux-dist/Vender/SamSong/44B0/Makefile中适当的位置添加一项:ocmlcd,c ,240,0,这里的第一个是表示设备名,如果注册成功可以在文件系统的/proc /devices下看到上面的这个设备名称,注意这个名称最好跟我们在注册设备时的名称一致。
2. 在linux2.4./drivers/char/Makefile的适当位置添加一行:
Obj-$(CONFIG_OCMLCD) += ocmlcd.o // 这里的ocmlcd为我们的C文件的文件名。
3. 在linux2.4/driver/char/config.in中添加一行:
Bool ‘ocmlcd’ CONFIG_OCMLCD //我们将会在配置内核的时候看到这个选项。
4. 修改linux2.4/driver/char/mem.c文件,在适当的位置添加:
#ifdef CONFIG_OCMLCD
Extern ocmlcd_init(); // 它是设备驱动的入口点
#endif
同时在chr_dev_init()函数中添加
#ifdef CONFIG_OCMLCD
ocmlcd_init(); // 它是设备驱动的入口点
#endif
5. 最后执行
Make menuconfig // 选中刚才我们添加的设备驱动,
Make dep
Make
即可将驱动添加到内核中
6. 启动内核,cat /proc/devices,我们就可以看到自己添加的驱动。