Android应用开发转车载工程师——串口通信学习
串口简介
串行接口简称串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口。串行接口(Serial Interface)是指数据一位一位地顺序传送。其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢。
串口参数
波特率:串口传输速率,用来衡量数据传输<typo id="typo-193" data-origin="的" ignoretag="true">的</typo>快慢,即单位时间内载波参数变化的次数,如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,比特率为10位*240个/秒=2400bps。波特率与距离成反比,波特率越大传输距离相应的就越短。
数据位:这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据往往不会是8位的,标准的值是6、7和8位。如何设置取决于你想传送的信息。
停止位:用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
校验位:在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。
串口地址
如下表不同操作系统的串口地址,Android是基于Linux的所以一般情况下使用Android系统的设备串口地址为/dev/ttyS0...
串行通讯特点
- 数据位的传送,按位顺序进行,最少只需一根传输线即可完成;
- 成本低但传送速度慢。串行通讯的距离可以从几米到几千米;
- 根据信息的传送方向,串行通讯可以进一步分为单工、半双工和全双工三种。
UART包含TTL电平的串口和RS232电平的串口。
- TTL电平是3.3V的
- RS232是负逻辑电平,它定义+5至+12V为低电平,而-12值-5V为高电平,MDS2710、MDS SD4、EL805等是RS232接口,EL806有TTL接口。
通信基本格式
- 字段 描述 长度(字节)
- 起始符 0F,十六进制码 1
- 信息类型 一个字节,十六进制码(0F,F0,FF等保留码不用)1
- 信息长度 是信息内容的长度,ASCII码表示(09,AF,最大长度为256)(例如长为11个,十六进制是0B,则两个字节就写0x30 0x42)。
注:因为最大长度256不能满足有些指令的要求,所以对长度做了扩展,下面是扩展说明:
如果第一个字节的最高位为1,则表示扩展长度。在扩展长度状态下,其他15个字节通过16进制大端模式来保存长度。
比如:0x80 0x12表示长度为0x001 2,0x81 0x12表示长度为0x0112。
- 2信息内容 一组十六进制码 N
- 校验 一个字节,十六进制码,是自信息类型起至对象号止所有码的异或。
- 1结束符 F0,一个字节,十六进制码 (为了保证可靠性,车机下发的结束符为F0 FF)1
Android实战蓝牙串口通讯
连接蓝牙设备——蓝牙客户端:
Android手机一般以客户端的角色主动连接SPP协议设备(接上蓝牙模块的数字传感器),客户端连接流程是:
1.使用registerReceiver注册BroadcastReceiver来获取蓝牙状态、搜索设备等消息;
**private** BroadcastReceiver searchDevices = **new** BroadcastReceiver() {
• **public** **void** onReceive(Context context, Intent intent) {
• String action = intent.getAction();
• Bundle b = intent.getExtras();
• Object[] lstName = b.keySet().toArray();
• // 显示所有收到的消息及其细节
• **for** (**int** i = 0; i < lstName.length; i++) {
• String keyName = lstName[i].toString();
• Log.*e*(keyName, String.*valueOf*(b.get(keyName)));
• }
• //搜索设备时,取得设备的MAC地址
• **if** (BluetoothDevice.*ACTION_FOUND*.equals(action)) {
• BluetoothDevice device = intent
• .getParcelableExtra(BluetoothDevice.*EXTRA_DEVICE*);
• String str= device.getName() + "|" + device.getAddress();
•
• **if** (lstDevices.indexOf(str) == -1)// 防止重复添加
• lstDevices.add(str); // 获取设备名称和mac地址
• adtDevices.notifyDataSetChanged();
• }
• }
};
2.使用BlueAdatper的搜索:btAdapt.startDiscovery();
3.在BroadcastReceiver的onReceive()里取得搜索所得的蓝牙设备信息(如名称,MAC,RSSI);
4.通过设备的MAC地址来建立一个BluetoothDevice对象;
5.由BluetoothDevice衍生出BluetoothSocket,准备SOCKET来读写设备;
6.通过BluetoothSocket的createRfcommSocketToServiceRecord()方法来选择连接的协议/服务,这里用的 是SPP(UUID:00001101-0000-1000-8000-00805F9B34FB);
**try** {
• *btSocket* = btDev.createRfcommSocketToServiceRecord(uuid);
} **catch** (IOException e) {
// **TODO** Auto-generated catch block
• Log.*e*(*TAG*, "Low: Connection failed.", e);
}
成功后进行连接:
**try** {
*btSocket*.connect();
Log.*e*(*TAG*, " BT connection established, data transfer link open.");
mangeConnectedSocket(btSocket);//自定义函数进行蓝牙通信处理
} **catch** (IOException e) {
Log.*e*(*TAG*, " Connection failed.", e);
setTitle("连接失败..");
}
7.Connect之后(如果还没配对则系统自动提示),使用
BluetoothSocket的getInputStream()和getOutputStream()来读写蓝牙设备。
读写可以归到一个独立线程去实现~ 注意:读时必须一直循环读取串口缓冲区,写可以不需要。
按以上7步逐次走过后,你就会发现Android蓝牙模块是多么的坑爹了。
出现问题:
在第6步一般初学者都会报错: 执行.connect()发生异常Connection refused
此时执行不下去咯,怎么办怎么办呢?
于是边debug边网上找攻略,总算在Google出老外的一些做法,尝试了下,貌似还可行。也即把 btSocket的建立方法采用另一种方法替代,这里都使用端口1
Method m;
**try** {
m = btDev.getClass().getMethod("createRfcommSocket", **new** Class[] {**int**.**class**});
*btSocket* = (BluetoothSocket) m.invoke(btDev, Integer.*valueOf*(1));
• } **catch** (SecurityException e1) {
• // **TODO** Auto-generated catch block
• e1.printStackTrace();
• } **catch** (NoSuchMethodException e1) {
• // **TODO** Auto-generated catch block
• e1.printStackTrace();
• } **catch** (IllegalArgumentException e) {
• // **TODO** Auto-generated catch block
• e.printStackTrace();
• } **catch** (IllegalAccessException e) {
• // **TODO** Auto-generated catch block
• e.printStackTrace();
• } **catch** (InvocationTargetException e) {
• // **TODO** Auto-generated catch block
• e.printStackTrace();
• }
至此,这个问题貌似倒也解决了,程序继续往下跑。
但这里请记住之前的异常,先别急着抛开~人家不一定一直都是异常哦。
接下来的任务是,让手机通过蓝牙跟单片机的蓝牙模块通信,并发送数据,通过电脑串口调试助手显示出来。具体实现,在mangeConnectedSocket(btSocket)方法中实现,里面通过启动另一个Activity实现。不是重点,略过。
直到这里,我们都只是把手机蓝牙模块充当客户端来使用,那什么时候会用到服务端呢?其实,之前手机蓝牙与单片机蓝牙模块的通信,单片机蓝牙模块就充当了服务端(处于监听状态,被手机蓝牙连接)。为了更好地搞清楚Android蓝牙通信,我们接下来使用2个手机的蓝牙进行通信。简单地说,就是做一个“手机蓝牙扣扣”,⊙﹏⊙b汗
一开始就想天真地把之前的程序同时烧到2部手机中,发现只有一部手机能正常建立socket连接(主动连接的那台),而另一部却迟迟没有响应。原因很简单,服务端的程序还没有编写!
于是,开始服务端程序:开辟一个新的线程实现
连接蓝牙设备——蓝牙服务端:
**class** AcceptThread **extends** Thread {
**private** **final** BluetoothServerSocket serverSocket;
**public** AcceptThread() {
• // Use a temporary object that is later assigned to mmServerSocket,
• // because mmServerSocket is final
BluetoothServerSocket tmp=**null**;
**try** {
//tmp = btAdapt.listenUsingRfcommWithServiceRecord("MyBluetoothApp", uuid);
Log.*e*(*TAG*, "++BluetoothServerSocket established!++");
Method listenMethod = btAdapt.getClass().getMethod("listenUsingRfcommOn",
**new** Class[]{**int**.**class**});
tmp = ( BluetoothServerSocket) listenMethod.invoke(btAdapt, Integer.*valueOf*( 1));
•
• } **catch** (SecurityException e) {
• // **TODO** Auto-generated catch block
• e.printStackTrace();
• } **catch** (IllegalArgumentException e) {
• // **TODO** Auto-generated catch block
• e.printStackTrace();
• } **catch** (NoSuchMethodException e) {
• // **TODO** Auto-generated catch block
• e.printStackTrace();
• } **catch** (IllegalAccessException e) {
• // **TODO** Auto-generated catch block
• e.printStackTrace();
• } **catch** (InvocationTargetException e) {
• // **TODO** Auto-generated catch block
• e.printStackTrace();
• }
• serverSocket=tmp;
}
**public** **void** run() {
•
• // Keep listening until exception occurs or a socket is returned
• //mState!=STATE_CONNECTED
**while**(**true**) {//这里是一直循环监听,也可以设置mState来判断
• **try** {
• *socket* = serverSocket.accept();
• Log.*e*(*TAG*, "++BluetoothSocket established! DataLink open.++");
• } **catch** (IOException e) {
• **break**;
• }
• // If a connection was accepted
• **if** (*socket* != **null**) {
• // Do work to manage the connection (in a separate thread)
• manageConnectedSocket();
• **try** {
• serverSocket.close();
• } **catch** (IOException e) {
• // **TODO** Auto-generated catch block
• e.printStackTrace();
• }
• **break**;
• }
• }
}
**public** **void** cancel() {
• **try** {
• serverSocket.close();
• } **catch** (IOException e) { }
}
}
安装测试:当2部手机都装上并打开同样的程序后,通过蓝牙检索并连接,经测试可以成功连接上,双双进入“聊天界面”,嘿嘿
注意,这时候重新拾回之前那个异常,把socket连接建立的方法重新改为
*btSocket* = btDev.createRfcommSocketToServiceRecord(uuid);//客户端
对应的服务端程序:
*tmp* = btAdapt.listenUsingRfcommWithServiceRecord("MyBluetoothApp", *uuid*);//服务端
这样继续重新运行安装测试,在2部手机上运行发现之前那个bug消失了~2部手机又双双进入聊天界面。
本文主要介绍了通讯串口,与一个蓝牙通讯的实战演练。许多Android应用开发想转车载,想必大家都是聪明人;车载的市场可以根据生活发现,小轿车对于我们已经不陌生了,从0几年马路上的小轿车少之又少,到如今的一人一辆。再加上新能源的普及;对于我们Android车载开发行业来说,非常的友好。
车载学习路线图:
这绝对是一个黄金赛道,Android应用开发转车载确实是个不错的选择。如何学习车载,我这里可以推荐这个《Android车载技术手册》资料由吉利车载系统开发高级工程师制作的笔记文档。
文末
串口学习只是车载系统开发中的一小部分,还有Automotive系统、车载进程通信、多媒体开发、空调系统、系统开发。等等学习的技术还有很多。技术资料需要充分利用,车载系统开发是个明智的选择。