好久沒寫blog最近把這一年亂七八糟的工作做個總結吧。這一年工作主要和開發板這塊結合的比較多,公司要給別人做一些小遊戲,然後爲了節約成本,我們採用的Orange Pi PC2這樣一塊開發板,至於什麼是Orange Pi 不知道的話這裏有一個官網http://www.orangepi.org/orangepipc2/。其實就相當於一臺手機不過跟手機不同的是他支持好多底層的接口。最初選擇這個的原因是因爲它比較便宜,才145RMB。當然 買回來只是一塊沒有系統的裸板這個時候我們需要給這塊開發板刷系統。這裏我們需要一個Android5.0系統,當然官網它哪裏也有現成的系統但是好多功能無法提供,例如串口通信,所以需要我們自己定義引腳。至於引腳怎麼定義這裏就不介紹了,這裏我可以提供2個Android5.0的系統一個橫版一個豎版,並且對串口做了定義,可以直接用,如果有需要的話可以qq聯繫我或者我將它放到百度網盤上。既然對串口做了定義接下來就是我們的Unity寫的程序如何和我們的開發板進行通信的問題了。說了串口通信PC端會比較簡單因爲C#封裝了對串口的操作,所以就比較簡單但是Unity對Android串口通信可沒有封裝,所以這裏我們只有自己寫jar包然後通過Unity來調用Jar包來實現通信。串口通信的Android jar包和Unity調用Jar包裏面的方法這個網絡上好多bolg都有,大家可以依葫蘆畫瓢就可以自己折騰出來,同樣這裏我也可以把這個jar提供出來。接下來就是Unity如何調用Jar包裏面的方法及如何封裝通信這塊。首先定義一個抽象類,這個抽象類的作用就是封裝四個方法,分別是打開串口,關閉串口,發送數據,接受數據。因爲PC端也存在串口通信,所以我們可以將這所有串口操作封裝成一個整體,打包成一個Dll。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
public abstract class BaseSerialport
{
public abstract int Open();
public abstract void Close();
public abstract byte[] Read(int len);
public abstract void Write(byte[] buf);
}
然後建一個Android串口通信的類來繼承這個基類。定義爲AndroidSerialport。
using UnityEngine;
namespace Android.IO
{
public class AndroidSerialport : BaseSerialport
{
private AndroidJavaObject javaObj = null;
private AndroidJavaObject GetJavaObject()
{
if (javaObj == null)
{
javaObj = new
AndroidJavaObject("com.SerialportApi.SerialPortReadWrite");
}
return javaObj;
}
public override void Close()
{
GetJavaObject().Call("Close");
}
public override byte[] Read(int len)
{
return GetJavaObject().Call<byte[]>("Read", len);
}
public override void Write(byte[] buf)
{
GetJavaObject().Call("Write", buf);
}
public override int Open()
{
return GetJavaObject().Call<int>("Open", new object[] { "/dev/ttyS2", 115200, 8, 'N', 1 });
}
}
}
同樣給出PC端串口通信的核心代碼:
public class PcSerialport : BaseSerialport
{
private SerialPort _serialPort;
private byte[] _readBuf;
public override int Open()
{
_serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
_serialPort.ReadTimeout = 1000;
_serialPort.WriteTimeout = 1000;
try
{
_serialPort.Open();
}
catch (Exception exception)
{
Debug.LogError("ssssssssss");
}
if (_serialPort.IsOpen)
return 0;
return 1;
}
public override byte[] Read(int len)
{
try
{
_readBuf = new byte[len];
_serialPort.Read(_readBuf, 0, len);
}
catch (TimeoutException)
{
}
return _readBuf;
}
public override void Write(byte[] buf)
{
_serialPort.DiscardInBuffer();
_serialPort.Write(buf, 0, buf.Length);
}
public override void Close()
{
_serialPort.Close();
}
}
PC端這邊打開的串口1,Pc端這個打開串口方法應該定義傳進去的串口參數和頻率,而不應該寫死,這裏主要是爲了圖簡單。
接下來就是具體具體處理數據這樣一個模塊,因爲從串口讀數據的時候他存在粘包的情況,具體的解釋就是當前幀你要從串口緩存裏面讀4個字節的數據,但是你有可能只讀了2個字節的數據,而下一幀的時候纔讀到了4個字節的數據所以這6個字節的數據它的協議頭和協議尾需要自己去判斷,這裏我們將其定義爲IOMrg,然後最主要就是這樣一個read方法。我們將其定義爲OnRead,然後方法的主體就是如何將讀到的數據存到一個list裏面然後每幀去取數據然並且取到的數據是你想要的數據。
private void OnRead(int head, int tail,int headtailOffset)
{
if (!_bOpen)
return;
int sindex = 0;
int fag = 0;
bool isHead = false;
bool isTail = false;
byte[] buff = _serialport.Read(_readBufSize);
string str = "";
for (int i = 0; i < buff.Length; i++)
{
str += buff[i] + ",";
}
int num = buff.Length;
if (num > 0)
{
for (int i = 0; i < num; i++)
{
_buf.Add(buff[i]);
}
for (int i = 0; i < _buf.Count; i++)
{
if (_buf[i] == head && !isHead)
{
sindex = i;
fag = -1;
isHead = true;
isTail = false;
}
if (_buf[i] == tail && !isTail)
{
fag = 1;
var eindex = i;
bool b1 = _buf.Count > (sindex + _readBufSize - 1);
bool b2 = (eindex - sindex) == headtailOffset;
if (b1 && b2 && isHead)
{
isTail = true;
isHead = false;
int k = 0;
for (int j = sindex; j <= (sindex + _readBufSize - 1); j++)
{
_userReadBuf[k] = _buf[j];
k++;
}
VerifyData();
}
}
}
if (fag == 1)
_buf.Clear();
else if (fag == -1)
{
for (int i = 0; i < sindex; i++)
{
_buf.RemoveAt(0);
}
}
}
}
方法具體的作用是首先從串口緩存中讀取數據,然後將讀取到的數據放到一個_buf存起來。然後從_buf第0個元素查找判斷哪個一個是我們數據的協議頭如果是那麼再找協議尾,如果找到了判斷這個數據大長度是不是我們定義的headtailOffset大小。如果是我們通過VerifyData方法來處理這樣一塊數據。然後繼續讀一直到沒有找到我們需要的數據,同時那些處理掉的數據我們將它從_buf移除掉。整個過程其實不是很複雜其實就相當於我們大學的裏面那種從一串字符串裏面找到我們需要的字符串序列。這裏VerifyData處理數據根據每個人的需求不同所以具體的處理結果也不相同。
private void VerifyData()
{
if (_isInitied)
_ioReadHandler.VerifyData(_userReadBuf);
if (_coinIoHandler != null)
_coinIoHandler.VerifyData(_userReadBuf);
}
這裏的 _ioReadHandler這個變量在定義的時候我們將其定義爲BaseIOReadHandler,它是一個基類,所以這裏需要我們自己去定義一個自己數據的處理類,因爲每個公司他們協議數據不一樣。這裏給出這個基類的一些屬性及抽象方法。
using System.Collections.Generic;
namespace Android.IO
{
public abstract class BaseIOReadHandler
{
public int Head { set; get; }
public int Tail { set; get; }
public int HeadTailOffset { set; get; }
public int ReadSize { set; get; }
public List<BaseIOEvent> IoEvents { set; get; }
public abstract void Initi();
public abstract void VerifyData(byte[] bytes);
public abstract void HandlerData(BaseIOEvent ioEvent);
}
}
首先有屬性數據的協議頭,協議尾及數據長度,及一個存儲數據的list,然後有一個初始化方法(主要定義數據的協議頭,數據的協議尾,數據的長度還有list初始化等)VerifyData方法主要是判斷取到的數據是否爲我們有用數據如果有用將它存放到我們IoEvents緩存中,HandlerData方法就是處理具體的數據定義了,比如取到數據它是左鍵按下或者右鍵按下了等。
這裏給出一個例子
public class JoyStickIOReadHandler : BaseIOReadHandler
{
private IOEvent _preEvent1;
private IOEvent _preEvent2;
public override void Initi()
{
Head = 0x1F;
Tail = 0x2F;
ReadSize = 5;
HeadTailOffset = 4;
IoEvents = new List<BaseIOEvent>();
_preEvent1 = new IOEvent();
_preEvent2 = new IOEvent();
}
public override void VerifyData(byte[] bytes)
{
bool ishead = bytes[0] == Head;
bool istail = bytes[ReadSize - 1] == Tail;
IOEvent cevent = new IOEvent();
if (ishead && istail)
{
cevent.Initi(bytes);
if (!cevent.IsEqual(_preEvent2))
{
_preEvent2 = cevent;
IoEvents.Add(cevent);
}
}
}
public override void HandlerData(BaseIOEvent ioEvent)
{
var tt = ioEvent as IOEvent;
if (tt != null)
{
if (tt.OneByteClickDown(0, _preEvent1))
{
EventManager.TriggerEvent(new FirstByteClick1DownEvent());
}
if (tt.OneByteClickDown(1, _preEvent1))
{
EventManager.TriggerEvent(new FirstByteClick2DownEvent());
}
if (tt.OneByteClickDown(2, _preEvent1))
{
EventManager.TriggerEvent(new FirstByteClick3DownEvent());
}
if (tt.OneByteClickDown(3, _preEvent1))
{
EventManager.TriggerEvent(new FirstByteClick4DownEvent());
}
if (tt.OneByteClickDown(4, _preEvent1))
{
EventManager.TriggerEvent(new FirstByteClick5DownEvent());
}
if (tt.OneByteClickDown(5, _preEvent1))
{
EventManager.TriggerEvent(new FirstByteClick6DownEvent());
}
if (tt.OneByteClickDown(6, _preEvent1))
{
EventManager.TriggerEvent(new FirstByteClick7DownEvent());
}
if (tt.OneByteClickDown(7, _preEvent1))
{
EventManager.TriggerEvent(new FirstByteClick8DownEvent());
}
if (tt.OneByteClickUp(0, _preEvent1))
{
EventManager.TriggerEvent(new FirstByteClick1UpEvent());
}
if (tt.OneByteClickUp(1, _preEvent1))
{
EventManager.TriggerEvent(new FirstByteClick2UpEvent());
}
if (tt.OneByteClickUp(4, _preEvent1))
{
EventManager.TriggerEvent(new FirstByteClick5UpEvent());
}
_preEvent1 = tt;
}
}
}
這是一個簡單的左右搖桿及確定按鈕操作。由於我們下位機單片機程序採用的是查詢式的工作模式,即我們程序需要發送數據到下位機,然後下位機返回它當前的運行狀態。還有這個Android串口通信不能用線程來操作,所以我們需要將發送數據模塊放到FixedUpdate裏面來固定更新。所以如果是PC端的話我們需要用到線程來接受和發送數據。所以在發佈平臺的時候我們 _serialport = new 不同的串口通信處理類。具體的類我就不粘貼出來了,我直接放到百度網盤。鏈接: https://pan.baidu.com/s/1hjb5WOOKuaxFG5Gf0VVrdQ 提取碼: wd9e