最近一直在學習UWP,其中有的技術參考了WPF,所以又回頭再來學習WPF,感覺學的東西很雜,必須記錄一下,不然時間長了還得忘掉,於是申請開始寫博客,將學習的心得記錄一下,以備后用。這次是因為公司內訓,剛好想着推廣一下開源硬件,所以選擇了Arduino,而又結合WPF的強大功能,設計了串口上位機。
1.Arduino UNO作為下位機
利用Arduino作為下位機,理由很簡單,語法很簡單,上手很快。
1.電路連接
下圖為電路原理圖,主要利用模擬口A0讀取光敏電阻和普通電阻的分壓值,然后通過設定邏輯控制LED的狀態。之后通過串口將數據發送給電腦。
2.下位機程序
在arduino IDE里完成。代碼結構非常簡單,setup()中設置IO口及串口,然后在loop()中讀取數值,根據數據控制LED狀態,並將數值從串口發送出去。
1 void setup() { 2 // put your setup code here, to run once: 3 pinMode(13,OUTPUT); 4 Serial.begin(9600); 5 } 6 7 void loop() { 8 // put your main code here, to run repeatedly: 9 int val=analogRead(0); 10 int time; 11 int result; 12 for(time=0;time<20;time++) 13 { 14 result+=val; 15 } 16 result=result/20; 17 18 if(result<256) 19 { 20 digitalWrite(13,HIGH); 21 } 22 else{ 23 digitalWrite(13,LOW); 24 } 25 26 Serial.println(result); 27 delay(10); 28 }
2.WPF串口上位機。
這里主要使用WPF自帶的串口控件、進度條、以及DynamicDataDisplay控件實現上位機數據顯示。具體實現是:將arduino發過來的數據在頁面上通過進度條顯示出來,同時畫出曲線。
1.串口控件SerialPort。
對於該控件,簡單的使用過程如下:
- 實例化一個串口;
- 配置串口參數,例如波特率、數據位、串口號;
- 打開串口;
- 添加串口接收數據事件;
- 處理數據接收事件。
需要注意的是:多線程問題,由於WPF的控件都在UI線程,而串口數據在另外1個線程。一開始直接將串口數據給進度條賦值會出現錯誤,后來參考網上資料,使用了相應控件的dispatcher.invoke(Action()),解決了數據更新問題。關於多線程的問題,后續還需要再繼續學習,搞清這一部分。
而數據曲線繪制使用了DynamicDataDisplay控件,相關使用方法可參考網上,由於第1次使用該控件,感覺效果還行。
代碼寫的很嫩。
程序界面如下,這里沒有選擇串口的選型,因為提前看了串口號。
xaml代碼:

1 <Window x:Class="Communication.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:Communication" 7 xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0" 8 mc:Ignorable="d" 9 Title="MainWindow" Height="350" Width="525" 10 Loaded="Window_Loaded"> 11 <Grid> 12 <Grid.RowDefinitions> 13 <RowDefinition Height="40"/> 14 <RowDefinition Height="auto"/> 15 <RowDefinition Height="auto"/> 16 <RowDefinition Height="auto"/> 17 <RowDefinition Height="*"/> 18 </Grid.RowDefinitions> 19 <Grid.ColumnDefinitions> 20 <ColumnDefinition Width="*"/> 21 <ColumnDefinition Width="*"/> 22 </Grid.ColumnDefinitions> 23 <TextBlock HorizontalAlignment="Center" 24 Text="光照度顯示程序" 25 Grid.ColumnSpan="2" 26 FontSize="30"/> 27 <StackPanel Orientation="Horizontal" 28 Grid.Row="1" > 29 <TextBlock HorizontalAlignment="Left" 30 Grid.Row="1" 31 Text="光照值:"/> 32 <TextBlock HorizontalAlignment="Left" 33 Grid.Row="1" 34 Name="light_result"/> 35 </StackPanel> 36 37 <StackPanel Grid.Row="1" 38 Grid.Column="1" 39 Orientation="Horizontal" 40 HorizontalAlignment="Center"> 41 <Button x:Name="開始" 42 Content="開始" 43 Click="開始_Click" 44 Height="20" 45 Width="60" 46 Margin='10,0,10,0'/> 47 <Button x:Name="關閉" 48 Content="關閉" 49 Click="關閉_Click" 50 Height="20" 51 Width="60"/> 52 </StackPanel> 53 54 <ProgressBar Grid.Row="2" Grid.ColumnSpan="2" 55 Minimum="0" Maximum="1023" 56 Height="20" Width="300" 57 HorizontalAlignment="Left" 58 x:Name="light_value"/> 59 <d3:ChartPlotter x:Name="plotter" 60 Margin="10,20,10,10" 61 Grid.Row="3" 62 Grid.ColumnSpan="2"> 63 <d3:HorizontalAxis> 64 <d3:HorizontalIntegerAxis/> 65 </d3:HorizontalAxis> 66 <d3:VerticalAxis> 67 <d3:VerticalIntegerAxis/> 68 </d3:VerticalAxis> 69 <d3:Header Content="光照曲線"/> 70 <d3:VerticalAxisTitle Content="光照強度"/> 71 </d3:ChartPlotter> 72 </Grid> 73 </Window>
后台代碼:

1 using Microsoft.Research.DynamicDataDisplay; 2 using Microsoft.Research.DynamicDataDisplay.DataSources; 3 using System; 4 using System.IO.Ports; 5 using System.Threading; 6 using System.Windows; 7 using System.Windows.Media; 8 using System.Windows.Threading; 9 10 namespace Communication 11 { 12 /// <summary> 13 /// MainWindow.xaml 的交互邏輯 14 /// </summary> 15 /// 16 17 public partial class MainWindow : Window 18 { 19 20 SerialPort myPort = new SerialPort(); 21 double result; 22 bool portClosing; 23 string lightValue; 24 25 private ObservableDataSource<Point> dataSource = new ObservableDataSource<Point>(); 26 private DispatcherTimer timer = new DispatcherTimer(); 27 int i = 0; 28 29 public MainWindow() 30 { 31 InitializeComponent(); 32 } 33 34 private void 開始_Click(object sender, RoutedEventArgs e) 35 { 36 try 37 { 38 myPort.BaudRate = 9600; 39 myPort.DataBits = 8; 40 myPort.PortName = "COM3"; 41 myPort.NewLine = "\r\n"; 42 myPort.Open(); 43 portClosing = false; 44 } 45 catch (Exception err) 46 { 47 MessageBox.Show(err.Message); 48 49 } 50 51 myPort.DataReceived += MyPort_DataReceived; 52 53 timer.Interval = TimeSpan.FromMilliseconds(200); 54 timer.Tick += drawPoint; 55 timer.IsEnabled = true; 56 plotter.Viewport.FitToView(); 57 58 } 59 60 private void MyPort_DataReceived(object sender, SerialDataReceivedEventArgs e) 61 { 62 if (portClosing) 63 { 64 return; 65 } 66 67 try 68 { 69 lightValue = myPort.ReadLine(); 70 result = double.Parse(lightValue); 71 } 72 73 catch(Exception err1) 74 { 75 //MessageBox.Show(err1.Message); 76 77 } 78 79 light_value.Dispatcher.BeginInvoke(new Action(() => 80 { 81 light_value.Value = result; 82 })); 83 84 light_result.Dispatcher.BeginInvoke(new Action(() => 85 { 86 light_result.Text = result.ToString(); 87 })); 88 } 89 90 private void 關閉_Click(object sender, RoutedEventArgs e) 91 { 92 portClosing = true; 93 94 Thread.Sleep(10); 95 96 if (myPort.IsOpen) 97 { 98 myPort.Close(); 99 100 } 101 else 102 { 103 MessageBox.Show("串口已關閉"); 104 } 105 106 timer.Stop(); 107 } 108 109 private void Window_Loaded(object sender, RoutedEventArgs e) 110 { 111 plotter.AddLineGraph(dataSource, Colors.Green, 2, "光照度"); 112 113 } 114 115 private void drawPoint(object sender, EventArgs e) 116 { 117 double x = i; 118 double y = result; 119 120 Point point = new Point(x,y); 121 dataSource.AppendAsync(base.Dispatcher, point); 122 i++; 123 } 124 } 125 }
程序運行效果:
以上就是軟硬件系統的全部細節,歡迎拍磚!