Category Archives: CDMA

使用Ruby在windows下通过串口自动测试AT

原文http://deli.xmu.me/?p=7,放在Wayhome mm给我提供的空间,现在已经无法访问。

手机芯片厂家常常会更新模组软件版本,在发给客户之前,一般先对AT进行一次测试。手机设计公司收到新的软件版本,烧入Flash, 也要对模组的AT进行一次测试,验证是否正常,比较与上个版本是否有差别。手工一个个验证,乏味,容易对工作失去兴趣。

从http://rubyinstaller.rubyforge.org 可以下载 Ruby One-Click,现在最新版本安装软件为ruby186-27_rc2.exe。安装过程中,把RubyGems也勾上。在windows上用ruby访问串口,可以用win32ole模块,也可以用ruby-serialport,后者是跨平台的。安装ruby-serialport前要做一些准备工作,确认有装Microsoft’s Visual C++ 或Borland’s C++编译器,这里以VC6.0为例。
(1)设置环境变量:

PATH=C:\Program Files\Microsoft Visual Studio\VC98\Bin
INCLUDE=C:\Program Files\Microsoft Visual Studio\VC98\Include
LIB=C:\Program Files\Microsoft Visual Studio\VC98\Lib

(2)安装ruby-serialport:
开始 -> 程序 -> Ruby-186-27 -> RubyGems -> RubyGems Package Manager,输入:
gem install ruby-serialport
如果提示找不到mspdb60.dll, 请到http://www.dll-files.com 找,把它放到
C:\Program Files\Microsoft Visual Studio\VC98\Bin
接下来,就可以写一个测试脚本了。

# filename: test.rb
require 'rubygems'
require 'serialport'

# 0 is mapped to "COM1" on Windows, and 5 is COM6, 115200 is baud rate
sp = SerialPort.new(5, 115200)
sp.write "AT\r\n"
sleep(0.2)
puts sp.read   # hopefully "OK" ;-) 

注意:如果没有sleep,可能会收不到”OK”响应。

如果你弄了老半天,ruby-serialport也没装得上,那就用用win32ole,美其名曰:Windows Automation。win32ole是Masaki Suketa编写的Ruby扩展,是标准ruby发行版本的一部分。
现在写一个mscomm.rb文件

#! /usr/bin/env ruby
#
# filename: mscomm.rb
# @date 2009.5.16
#

require 'win32ole'

class MSCOMM
  def initialize(port)
    @serial = WIN32OLE.new("MSCOMMLib.MSComm")

    @serial.CommPort = port
    @serial.Settings = "115200,N,8,1"
    @serial.InputLen = 0
    @serial.PortOpen = true
  end
  def write(str)
    @serial.Output = str
  end
  def read
    str = @serial.Input
    str
  end
  def close
    @serial.PortOpen = false
  end
  def serial
    @serial
  end
end

再写一个简单的测试用例test_comm.rb:

#! /usr/bin/env ruby
#
# filename: test_comm.rb
# @date 2009.5.16
#

require 'mscomm.rb

comm  = MSCOMM.new(6)
comm.write("AT\r\n")
sleep(0.2)
puts comm.read
comm.close

和上面的test.rb用法一样,没什么神秘感,是不是很简单呢 :-)

再加个查询信号的AT,在test_comm.rb倒数第二行加上
comm.write(“AT+CSQ?\r\n”)
sleep(0.2)
puts comm.read
要是很多很多…想想,还是写个函数吧.

def exec_cmd(comm, cmd)
  comm.write("#{cmd}\r\n")
  sleep(0.2)

  begin
    result = comm.read
    result = "Command not support\n" if result.include?("ERROR\n")
  rescue
    result = "Writing serial port error\n"
  end

  puts result
end

于是,test_comm.rb中间部分的可以可以写成:
exec_cmd(comm, “AT”)
exec_cmd(comm, “AT+CSQ?”)

可是每添加一个AT… 还是得写一串的exec_cmd(comm… 。Dave Thomas大师说过:”Don’t Repeat Yourself!” ,这是ruby的设计理念。假设所有的AT都放在另外一个文件呢?我们也可以一个个读取出来。不过现在考虑的是暂时放在同个文件,那就定义一个字符串数组:
atcmd = {
‘AT’,
‘AT+CSQ?’,
‘AT+CREG?’
}
也可以这样写:
Atcmd = %w{
AT
AT+CSQ?
AT+CREG?
}

第二种对于添加AT比较方便,但缺点是AT不能出现空格。那么test_comm.rb 现在又可以写为:
atcmd.each {|at| exec_cmd(comm, at) }

可是,有的AT需要花一些时间才有响应。之前都默认是0.2秒,好吧,重新定义:
def exec_cmd(comm, cmd, timeout = 0.2)

sleep(timeout)

end

于是,就可以这样使用
exec_cmd(comm, “AT+CDV=10000”, 3)
exec_cmd(comm, “AT+CLCC?”)
exec_cmd(comm, “AT+CHV”)
exec_cmd(comm, “AT+CPOF”, 2)
exec_cmd(comm, “AT+CPON”, 5)

像这样的一组AT,具有依赖顺序而每个AT的响应时间又不一样,我们只能根据不同的情况对AT做分类,写不同的测试脚本。

再回头看看 test_commm.rb,如果我们想把输入与输出都放在同一个Excel表格,那该如何写呢?原理一样,依旧用win32ole:

#! /usr/bin/env ruby
#
# filename: excel.rb
# @date 2009.5.16
#

require 'win32ole'

class Excel
  def initialize(filename = nil)
@excel = WIN32OLE.new("excel.Application") # create Excel object

    @excel.Visible = TRUE
    if (filename == nil)
      @workbook = @excel.Workbooks.Add() # create new file
    else
      @workbook = @excel.Workbooks.Open(filename) # open exist file
    end
  end

  def setvalue(pos, data)
    @excel.Range(pos).Value = data
  end
  def save
    @excel.Save()
  end
  def close
    @excel.Quit()
  end
  def excelobj
    @excel
  end

end # end of class

我们重新写个test_cdma_at.rb

#! /usr/bin/env ruby
#
# test_cdma_at.rb
# @date 2009.5.16
#

require 'mscomm.rb'
require 'excel.rb'

def exec_cmd(comm, cmd)
  comm.write("#{cmd}\r\n")
  sleep(0.2)

  begin
    result = comm.read
    result = "Command not support\n" if result.include?("ERROR\n")
  rescue
    result = "Writing serial port error\n"
  end

  return result
end

atcmd = %w{
AT
AT+CPIN?
AT+CPINC?
AT+CSQ?
AT+CREG=2
AT+CREG?
AT+VMCC?;+VMNC?
AT+CPBS=?
AT+CPBS?
AT+CPBS="ME"
AT+CPBS?
AT+CPBW=3,13544049382,"violet",0
AT+CPBR=3
AT+CMGS=13544049382,"Hello!"
AT+CMGW=,13544049382,"Hi!"
}

comm  = MSCOMM.new(6)
excel = Excel.new("d:\\Book1.xls")

i = 1

atcmd.each {|at|
  excel.setvalue("a#{i}", at)
  excel.excelobj.Range("b#{i}").Value = exec_cmd(comm, at)
  i += 1
}

comm.close
excel.save
excel.close

代码不难理解,就是把AT输入放在Excel表格某行的A列,响应写入某行的B列,仅如此而已。

脚本测试也不是万能的,AT本身具有随意性,写脚本意在减轻编码工作量,避免重复机械的劳动。

用gsmmux 测试via cdma多路复用功能

现在杭州的威盛已实现了GSM协议07.10 multiplexer。
gsmmux 可以在 developer.berlios.de/projects/gsmmux/ 上获得.按照说明安装就可以了。
代码默认用的是AT+CMUX开启功能,而CMUX在CDMA另有别用,所以他们就用VMUX来替代,在代码里,把CMUX改为VMUX,重新编译。

运行 mux,得到两个虚拟逻辑串口 /dev/mux0 /dev/mux1,
开一个终端1 cat /dev/mux0 观察数据
再开一个终端2输入 echo -e “AT\r\n” > /dev/mux0
终端1有 “OK” 响应就行了。
同样的方法测试 mux1.

核心代码在 gsm0710.c

简单说明一下。
1190 行的 main函数,读取命令行参数,
1272 行 daemonize(_debug) 设置为unix下经典后台程序,下来是设置信号中断机制。

openDevicesAndMuxMode 函数 打开modem 初始化,发 “AT+VMUX=0” 进入mux模式。

MUX启动过程
主机发: AT+VMUX=0
模块回复:OK /*进入MUX模式*/
主机发: F9033F011CF9 /*建立DLC0*/
模块回复:F9037301D7F9
主机发: F9073F01DEF9 /*建立DLC1*/
模块回复:F907730115F9
F901EF09E305070D9AF9 /*DLC1 MSC 命令*/
主机发: F90B3F0159F9 /*建立DLC2*/
模块回复:F90B730192F9
F901EF09E3050B0D9AF9 /*DLC2 MSC 命令*/

/*…开始进行MUX协议的数据传输…*/

在 1106行的openDevicesAndMuxMode,
三次(一般是三次,可以设置)打开 /dev/ptmx ,得到三对主从终端,通过符号链接,创建两个虚拟串口

/dev/mux0 /dev/mux1 这时就可以像正常访问串口一样访问它们,一般 /dev/mux0
用来专门发AT, /dev/mux1 用来发送数据业务,比如pppd 拨号上网。

数据流大概是这样的
/dev/ttyS0 <---> | /dev/pmux <----> /dev/mux0 | <----> at command
/dev/ttyS0 <---> | /dev/pmux <----> /dev/mux1 | <----> cdma pppd

下面
1162行 – 1166行
for (i = 1; i <= numOfPorts; i++)
{
sleep(1);
write_frame(i, NULL, 0, SABM | PF);
syslog(LOG_INFO, "Connecting %s to virtual channel %d on %s\n", ptsname(mux_fd[i-1]), i, serportdev);
}

write_frame(i, NULL, 0, SABM | PF); 实现的就是发
F9033F011CF9 /*建立DLC0*/
F9073F01DEF9 /*建立DLC1*/
F90B3F0159F9 /*建立DLC2*/

然后进入 while循环,用selelct 实现 i/o 多路复用,
(1)检查物理串口,如果有可读的数据,读取放到buffer,然后解析数据帧,并发送到虚拟逻辑串口
(2)检查虚拟逻辑串口,如果有可读的数据,构造数据帧,写入物理串口。

就这样,基本原理是数据分组打包与解包。

在MTK平台上部署CDMA

原文在 2009/10/14 发表于broncho论坛,转载请写出处,谢谢!

在MTK平台上部署CDMA

[注] 我们用的MTK版本是6225 ,CDMA 模组是威盛via的。此项目没有量产,代码放在那也是加密冷藏没有价值,写思路出来与大家讨论关于程序设计。本人不是MTK专家,有分析不对之处,请指出,谢谢。

[硬件篇]
加一个cdma模组,具体操作本人不知道,就不讲述了。

[软件篇]
让我们来分析一下。既然在MTK平台,上层应用最好不要改动,就只能在底层修改。在L4层我们发现,MTK提供了mmi接口,那么我们就有机会重写这些函数,这个就是最大前提。对于CDMA,要对AT做封装,适配于L4(适配器设计模式)。我们可以开一个task,专门处理CDMA AT,串口数据的读取。考虑到CDMA的AT比较弱,特别是三方通话,呼叫保持,对AT的处理,得用较多全局变量来保存上下文。

代码目录:

代码: 全选
Vendor -|
|- ATCMD -| at_cdma
| at_gsm
| at_l4a
| at_thread

At_thread 主要是处理AT的线程.
首先创建一个task.
/* task */
void AT_Cmd_main(task_entry_struct * task_entry_ptr); 一个while 循环,处理消息。

At_gsm 是一个参考物,为了测试cdma方便,我们自己实现MTK的AT封装。At_cdma 是cdma编解码目录。可以说,at_cdma继承于at_gsm,两者有统一的接口(主要是gsm标准,cdma适应于gsm),所以很多的工作,在at_cdma做了很大的调动。如果实现和gsm不一样,我们就要rewrite接口实现,基于函数替换。

上面已有提到过,我们要重新实现mtk提供的l4a层,那么at_l4a 就是干这样的活了。为了不直接在mtk l4a的函数里修改,我们采用include 的方式。所有的处理函数放在AT_L4c_Funs_V07B.c
写我们自己的 l4a_callback_ex.c ,在ps\l4a\src\l4a_callback.c 的末尾 #include “l4a_callback_ex.c” ,然后在 l4a_main.c的 l4a_recvmsg(ilm_struct *ilm_ptr)里加以区别:如果 l4c_current_mod_id == l4c_atcmd_mod_id,就调用我们实现的接口函数处理cdma,否则,就调用mtk提供的处理gsm(不变)。

串口通讯的处理
利用w32_uart.c 里提供的一些函数,打开串口,设置波特率,数据控制流等参数。
对数据的发送,接收,加入休眠模式,以免功耗过大。

AT的处理
在MTK上对CDMA AT的处理,则采用赤裸裸的封装,源于有开发过Firebird BBS的经历,我更喜欢这种最原始的美,不要和我谈什么软件架构,设计模式的,考虑越多,项目越难以进展,领导只关心是否能跑起来,不然项目取消,大家散伙吧。说远了。AT具有随意性,我们假设发出请求的AT,就有相应的响应,设一个定时器,来处理超时问题。如果超时了,我们再发AT,如果有OK回应,就说明模组还能正常工作,那么这个AT的请求就是失败的。对自动上报的消息的处理,在获取AT响应时,我们判断是自动上报的消息,则当场处理,继续读下一行数据,直到有期望的响应值。如果请求发出一个AT, 既有自动上报消息,又有自身的请求响应,该如何处理呢?举个例子,查询网络注册情况,发出AT+CREG?后,
+CREG:1,0×3614,0x2

+CREG:1,0×3614,0x2

+CREG: 2,1,0x00C3,0x00AD

我们发现,请求的响应有4个参数,而自动上报的有三个参数。再举个例子:
开机时,模块会自动开启一个15min的计时器,如果15分钟内没有收到AT+LCT指令,模块就自动关机,如果收到了该指令,15min的计时器又重新开始计时。所以我们看到+LCT主动上报时,就发出AT+LCT。问题来了,我们打电话时,查询CLCC,那么问题出现如下:
AT+CLCC?
+LCT
AT+LCT
+CLCC:1,0,1
ok
ok

两个响应消息交错在一起,就难以分析了,我们就得保证消息串化。
等等。AT响应的处理,大部分工作是解析字符串,取出你想得到的数据,填充结构体。总之,在做AT处理时,要反复测试,考虑很多的异常,这便是软件设计的难点之一。

AT层上GSM 与CDMA的差异
就列举几个比较重要的差别,很多小细节还是非常多的,请参阅厂家的AT SPEC。
(1)AT测试命令,比如信号查询 GSM是AT+CSQ;CDMA则是 AT+CSQ? 很多种情况如此,CDMA在后面要加上一个“?”号。
(2)AT响应字符串
比如, GSM:
+CCLK: “04/01/19,15:38:32″
+CCLK:后面有空格
CDMA:
+CCLK:2008/8/6,13:57:5,3
+CCLK: 后面没有空格
响应字符串解析函数必须能处理空格问题。可以写一个类似于sscanf的函数,专用解析AT响应字符串。
(3) CDMA中”UIM”的字符串,这个主要出现在电话本与短信。MTK上是”SIM” 对应 0, “ME”对应1;CDMA把”UIM”对应为0即可。此外,短信与电话本,gsm从1开始读取,cdma则从0开始读。
(4) TE特征字符串设定
GSM: AT+CSCS
CDMA没有这方面的设定,默认为UNICODE,在电话本处理中,AT请求和响应要特别指出.
(5) 短信的PDU编解码完全不同.请参考 GSM0707与3gpp2 的C.S0015-B
(6) 电话状态查询CLCC,CDMA提供的功能很弱,不管有没有来电,拨出电话,查询CLCC总有一个+CLCC:响应,GSM则不然。CDMA处于三方通话,呼叫保持,呼叫等待时,查询CLCC也是只有一路+CLCC:响应,这样,我们得自己构建适应于GSM的CLCC,维护,更新CLCC列表。

开关机流程
(1)开机
+VPUP
模块上报开机启动标志,也可根据该命令判断模块重启,收到该命令后进行初始化设置. (模组有问题时,这个上报是不可靠的.)
+MSStatus:0
模块上报协议栈已打开。
+VROM:1
模块上报漫游状态(1 为非漫游状态)。 这个上报是不可靠的,就是说,并不是每次开机都会有这个上报。不过,如果出现这个,我们认为可以显示中国电信了,其他手机已开机就显示运营商,估计也是照这个来做。

AT+ISF?
查询模块初始化状态,2s~3s 执行一次
+ISF:1
模块初始化完成。

接下来才可以对电话本,短信,通话记录初始化。

由上,在+MSStatus:0与 +ISF:1之间,可以操作的行为有:
(0)ATE0 软件已经默认关闭回显,该步骤可省。
(1)注册网络与信号上报. AT+CREG=2 AT+ARSI=1
(2)查询UIM是否插好?AT+CPIN?
(3)查询信号 AT+CSQ?
(4)查询国家码,网络码 AT+VMCC? AT+VMNC?
(5)选择语音通道 AT+SPEAKER=0

开机流程比较繁琐,要适应与MTK的启动流程,比如开启Pin码,手机密码,网络运营商的获取,信号强度上报等。一般情况下,模块加电后会自行开启协议栈。不插UIM卡启动,模块不会自行打开协议栈。需要输入PIN的情况下,只有在用户输入正确的PIN,模块才会开启协议栈。在以上任何情形下开机,模块都会上报(+VPUP),客户可以根据此上报来判断正常使用中的模块是否重启。
(2)关机:
由于关机需要在网络上进行一些登记操作,因此正常的关机步骤是建议+CPOF关闭协议栈后延迟一段时间断电。
(3)飞行模式:
模块实现飞行模式可以直接使用+CPOF关闭协议栈,+CPON打开协议栈返回正常模式。
查询当前是否在飞行模式:+VPON.

细节:
长短信的发送:
Cdma不能无缝的发送长短信,一般要等到 +CDS:上报才能发出下一条,一般情况下,发出一条短信过4-5秒后就有+CDS上报,不过,如果发给自己的话,这个时间会更长,我们采用延时30秒。一般如果发多条短信,第二条一般会返回+CDS:2,2,66 ,就是说,发不成功。我们要把它保存起来,重发一次。

结尾
当然,要做到符合最新电信需求规范,就比较困难了。MTK本是在GSM协议上做起来的,GSM于CDMA本来就存在很大的差别。说不定过不久MTK会发布C+G的版本,啥东西人家都帮你弄好了,就等着只改界面换个图片铃声吧。

Pages: Prev 1 2