前言
上一節我們已經基本上把超聲波硬件的發射和接收模塊全部做好了,接下來我們着手開發一個軟硬結合的基於C#的平面定位軟件!
目錄
一、整體思路
二、效果提前展示
2-1、軟件部分展示
2-2、硬件部分展示
三、基於C#的客戶端軟件說明
3-1、整體框架介紹:
3-2、部分技術細節介紹
3-2-1、串口操作
3-2-2、JiSuan函數說明及核心算法介紹
四、階段小結
五、相關鏈接
一、整體思路
>_<" 如下圖,利用我們上三節開發的超聲波發射與接收設備構成一個:2固定接收頭+1可移動超聲波發射頭的平面定位硬件設備。由上一節我們知道:在一個采樣周期內硬件定位裝置完成2次測距,即圖中的x和y的長度。又因為我們雙接收模塊部分兩個接收頭的距離是固定的L,所以我們可以根據數學公式求出移動頭的具體坐標,然后在PC上進行仿真。
二、效果提前展示
2-1、軟件部分展示:
>_<" 模式一:用來動態獲取下位機測得x和y數據經過幾何運算將硬件結構映射到FORM窗口中,實現動態仿真。這里:COM3處表示接收模塊所連接的串口為COM3;右邊的四個數據分別表示所測實時的x,y的長度,以及串口緩沖區的有效數據剩余q(用棧來實現),和所有采集到的有效點轉換為FORM中坐標點的隊列大小(用隊列來實現)。
>_<" 模式二:每次獲得新的有效點時連接上一次的點和這一次的點形成活動端的移動路徑展示效果。(這里由於從原始數據到FORM上坐標轉換時放大了7倍,所以波動比較大,其實精度是在1cm左右波動的。
2-2、硬件部分展示:
>_<" 1個發送頭(即可移動頭)+2個接收頭[在這節中要把兩個接收頭並排固定間距為14cm,即L的長度]
>_<" 超聲波發送模塊集成電路+超聲波接收模塊集成電路
>_<" 硬件整體圖(注意這里再次強調接收頭要並排固定相距14cm!嘿嘿,由於晚上寫的,俺的手機又太渣,30萬像素Java機,也稱“學霸機”,就不能把最新的固定好的圖發給大家看了!)
三、基於C#的客戶端軟件說明
3-1、整體框架介紹:
>_<" 如下圖:上位機程序構成很簡單,初始化函數DrawHS()進行獲得當前可用串口、實例化並掛起用於數據處理的線程、啟動定時器用於周期性刷新屏幕。①當用戶選中超聲波接收模塊所對應的串口並成功連接后,原始數據的到來會觸發串口數據接收函數執行來接收數據,這里接收來的數據保存在Q的棧里;②然后JiSuan線程會提取棧內的最新數據進行處理,並把得出的有效轉化為FORM平面的點放入P的點集隊列里;③定時器的定時刷新周期到來會根據上面的計算來重繪當前界面。這里模式一和模式二按鈕決定采用Draw1()函數還是Draw2()函數進行繪圖。
3-2、部分技術細節介紹
3-2-1、串口操作
>_<" 首先,你得在FORM中加入一個ComboBox,命名為PortList,即:下拉COM口選擇框。
>_<" 其次,你得有個SerialPort控件,命名為serialPort1,即:C#提供的串口對象。
>_<" 那么,在初始化函數中寫入如下代碼獲得當前可用串口並加入ComboBox中,注意最后一句:表示如果有可用串口,默認選擇第一個。
1 //Get all port list for selection 2 //獲得所有的端口列表,並顯示在列表內 3 PortList.Items.Clear(); 4 string[] Ports = SerialPort.GetPortNames(); 5 for (int i = 0; i < Ports.Length; i++) 6 { 7 string s = Ports[i].ToUpper(); 8 Regex reg = new Regex("[^COM\\d]", RegexOptions.IgnoreCase | RegexOptions.Multiline);//正則表達式 9 s = reg.Replace(s, ""); 10 PortList.Items.Add(s); 11 } 12 if (Ports.Length > 1) PortList.SelectedIndex = 1;
>_<" 接下來,對於串口的連接就比較簡單了。如本工程,當你從ComboBox中選擇一個串口時,然后點擊連接按鈕進行連接,那么連接按鈕就是負責串口連接功能:這里根據連接按鈕的狀態判斷當前連接情況,當有連接時,點擊連接按鈕表示斷開連接;當沒有連接時,點擊連接按鈕,則會連接從ComboBox中選中的串口。這里要注意(20行):由於串口數據接收屬於觸發事件,有數據到緩沖區才會觸發並接收數據,這里實例化一個串口數據接收句柄就是負責響應觸發接收數據的函數,即:PortDataReceived~
1 SerialPort Connection = new SerialPort(); 2 private void 鏈接ToolStripMenuItem_Click(object sender, EventArgs e) 3 { 4 if (鏈接ToolStripMenuItem.Text == "關閉")//通過控件名字判斷當前狀態 5 { 6 Connection.Close(); 7 鏈接ToolStripMenuItem.Text = "鏈接";//改變控件名字 8 PortList.Enabled = true; 9 JS.Suspend();//掛起線程 10 } 11 else 12 { 13 if (!Connection.IsOpen) 14 { 15 JS.Resume();//繼續已掛起的線程 16 Connection = new SerialPort(); 17 Connection.PortName = PortList.SelectedItem.ToString(); 18 Connection.Open(); 19 Connection.ReadTimeout = 10000; 20 Connection.DataReceived += new SerialDataReceivedEventHandler(PortDataReceived); 21 鏈接ToolStripMenuItem.Text = "關閉"; 22 PortList.Enabled = false; 23 } 24 } 25 }
>_<" 如上面介紹,每當串口有數據傳來就會觸發該函數執行來接收串口緩沖區數據。這里的第6行即是從串口緩沖區讀取length長的數據放入data數組中,0表示放入data[0]開始。下面8、9兩行即把原始數據壓入棧給JiSuan線程處理。
1 private byte[] data;//接收的數據數組 2 private int length = 2;//一次從緩沖區取出數據長度 3 private void PortDataReceived(object sender, SerialDataReceivedEventArgs e) 4 { 5 data = new byte[length]; 6 if (Connection.Read(data, 0, length) != length) return; 7 Connection.DiscardInBuffer(); 8 Connection.DiscardOutBuffer(); 9 Q.Push(data[0]);//壓入棧 10 Q.Push(data[1]); 11 }
3-2-2、JiSuan函數說明及核心算法介紹
>_<" 如下圖,原始數據轉換為FORM坐標系下移動端的坐標(x,y)的求法:首先利用三角函數根據原始數據計算出a,b的值,然后根據一定比例放大a,b值再根據坐標關系求出FORM坐標系下的移動點(x,y)的坐標。
>_<" 當理解上述計算轉換過程,下面的代碼便不難理解。這個JiSuan的函數其實是個數據處理的線程,一直處於while循環下,當棧Q中存在待處理原始數據時,則取出原始數據,判斷原始數據是否正確合理,如果合理則分別找出data_x和data_y的值,即原始值(這里,由於下位機發送過來data_x和data_y的順序先后不定,於是在下位機中將data_y的數據取相反數發送過來,所以在這里能夠巧妙地區分)。接下來就是采用上述所述方法進行計算移動點的FORM坐標系下的坐標了。這里的Show字符串是為了保存最初您看到的顯示的x:23 y:21 q:0 p:123信息。
1 private string Show=""; 2 private void JiSuan() 3 { 4 while (true) 5 { 6 if (Q.Count()<2) continue; 7 data[0] = Q.Pop(); 8 data[1] = Q.Pop(); 9 //從串口讀原始數據並判斷是否合法 10 //這里串口的數據位發送到x、y接收頭的距離,並且只保留1m內的距離 11 //為了分辨清晰,把y的真實距離取相反數經串口發送 12 //當上位機串口收到2個距離放到data[2]中,當負的強制轉換為int類型時要用225-接收的數據才能轉化為原數 13 int data_x = 0, data_y = 0; 14 if ((int)data[0] >= 100 && (int)data[1] <= 100) 15 { 16 data_y = 255 - data[0]; 17 data_x = data[1]; 18 } 19 else if ((int)data[0] <= 100 && (int)data[1] >= 100) 20 { 21 data_x = data[0]; 22 data_y = 255 - data[1]; 23 } 24 else continue; 25 //下面是根據收到的發射到兩個接收頭的距離計算相應的平面坐標位置 26 //a,b為原始偏移,x,y為坐標平面的對應點坐標 27 double cos8 = (14.0 * 14.0 + data_x * data_x * 1.0 - data_y * data_y * 1.0) / (2.0 * 14.0 * data_x); 28 double a = 1.0 * data_x * Math.Sin(Math.Acos(cos8)); 29 double b = 1.0 * data_x * cos8; 30 x = ((int)(7 * a)); 31 y = ((int)(7 * b)) + 100; 32 if (x < 0) continue; 33 p3.X = x; 34 p3.Y = y; 35 P.Add(p3); 36 rect.X = x - 2; 37 rect.Y = y - 2; 38 //用於顯示收到數據 39 Show = "x:"; 40 Show += data_x; 41 Show += " y:"; 42 Show += data_y; 43 Show += " q:"; 44 Show += Q.Count(); 45 Show += " p:"; 46 Show += P.Count(); 47 canShow = true;//可以顯示 48 } 49 }
四、階段小結
>_<" 經過這四小節的學習,從最初的想法到如今的實現,收獲頗豐!對於模擬電路有了更深刻的認識,對於51單片機的應用有個進一步的提高,對於PC上位機的開發也有了新的了解和體會。可以說,硬件的難在於其變化性,即使是絕美的方案也會出現真實世界莫名因素的影響,讓你頭大;軟件的難在於邏輯之難,本項目雖小,但是用了多線程的思維,里面隱隱涉及到線程同步和死鎖的問題,只是有些錯誤在調試階段已經被淘汰,大家在這個最終的方案下也就無法想象原初的各種死鎖、運行時中斷、遲緩等問題了。此外,可能是前三篇過於偏向於硬件,所以大家可能不會理睬,但是如果您真的對該篇文章感興趣的話,還是建議您仔細閱讀下前三篇的介紹,畢竟這個過程中硬件幾乎占了大半個江山呢~其實,本來的想法是:第一階段把這個做成個平面定位的類似於電子白板的觸控模塊,第二階段把這個做成三接收頭的空間三維定位裝置,結合當前體感游戲、手勢識別等開發出更有意義的產品,但是在研究的過程中發現超聲波定位其本身存在不可忽視的缺點,容易反射相互干擾等,接下來的想法造成了本質性的阻礙。由於最近學業較忙,所以這個超聲波的研究就暫且為止!最后,感謝大家的支持!
五、相關鏈接
[自娛自樂] 1、超聲波測距模塊DIY筆記(一)鏈接:http://www.cnblogs.com/zjutlitao/p/4014855.html
[自娛自樂] 2、超聲波測距模塊DIY筆記(二)鏈接:http://www.cnblogs.com/zjutlitao/p/4029937.html
[自娛自樂] 3、超聲波測距模塊DIY筆記(三)鏈接:http://www.cnblogs.com/zjutlitao/p/4069272.html
[源碼]該項目C#工程VS2012平台:http://pan.baidu.com/s/1ntqkxNb
[源碼]該項目超聲波接收模塊51單片機代碼(和第三篇的不同):http://pan.baidu.com/s/1jGA9vqI
[打擊盜版]請進入博主主頁查看原版博文:http://www.cnblogs.com/zjutlitao/