none
該如何利用serialport接收實時高頻資料? RRS feed

  • 問題

  • 想請問如何利用serialport完整接收

    virtual comport  每秒8000筆 8byte開頭S結尾E的資料 

    資料型態: (S + X_low + X_hi + Y_lo + Y_hi + Z_lo + Z_hi + E)

    我的程式碼如下

    目前是參考

    Serial Port 系列(11) 基本篇 -- 利用執行緒讀取資料

    將資料以byte形式在背景執行續讀入

    再用switch判別 S ,E 以及資料 將完整8byte放入List中後丟出去執行去頭去尾後

    再丟出invoke執行續做轉數值的計算 

    const int S = 83;    //資料頭尾的S,E定義其ASCII轉換為10進位的數值
    const int E = 69;
    delegate void Display1(Byte[] buffer);  //宣告委派任務 (傳入資料型式 , 資料命名)
    
    
    private void DoReceive()  //接收資料副程式 (背景執行緒)
            {
                List<byte> tempList = new List<byte>();   //宣告空清單
    
                while (receving)   //傳輸參數為TRUE啟動
                {
                    try
                    {
                        int orignal_data = serialPort1.ReadByte();   //讀資料 以byte為單位
    
                        switch (orignal_data)
                        {
                            case S: //若讀到s 為開頭
    
                                tempList.Clear();   //清除先前資料清單
                                tempList.Add((Byte)orignal_data);  //將S加入清單
                                break;
                            case E://若獨到E 為結尾
                                tempList.Add((Byte)orignal_data);  //將E加入清單
                                parse(tempList);       //將資料丟入parse副程式做處理
                                serialPort1.DiscardInBuffer();   //因為傳送資料太快 所以丟完一次資料 清一次串口緩存區
                                break;
                            default:
                                tempList.Add((Byte)orignal_data);    //將資料加入清單
                                break;
                        }
                    }
                    catch (Exception)   
                    { }
     private void parse(List<byte> tempList)   //副程式2
       {
           if (tempList[0] == (byte)S && tempList[tempList.Count - 1] == (byte)E)  //將取得的資料去頭去尾
                {
                    tempList.RemoveAt(0);
                    tempList.RemoveAt(tempList.Count - 1);
                    Display1 d = new Display1(DisplayText);    //display為委派任務 在這產生一個d為一個新的委派任務  方法為displaytext副函式
                    Invoke(d, tempList.ToArray());   //利用invoke執行d的委派 傳入的資料為新的物件 { tempList.ToArray()}
    
                }
            }
    
    


     private void DisplayText(Byte[] buffer)    //  INVOKE之委派     buffer內為 含有6個byte的byte[]
            {
                try
                {
                
    
                    //..資料顯示
                    data_x = BitConverter.ToInt16(buffer, 0);  //合成 x y z (hi+lo)           原資料: S + X_LO + X_HI +  Y_LO + Y_HI  Z_LO + Z_HI + E
                    data_y = BitConverter.ToInt16(buffer, 2);  //去頭尾後剩下 X_LO + X_HI +  Y_LO + Y_HI  Z_LO + Z_HI
                    data_z = BitConverter.ToInt16(buffer, 4);
                    richTextBox1.AppendText(string.Format("{0}             {1}               {2}               {3}",
                                                                data_x, data_y, data_z, Environment.NewLine));          //顯示+換行



    能夠成功的抓到正確資料

    但遇到的第一個問題就是串口緩衝(serialport.BytetoRead)會塞車  導致圖形畫出來沒有實時

    後來我在每一次CASE E:讀完後 就 serialport.discardbuffer 清除緩衝

    可以達到實時的效果  可是資料就捨棄了很多導致數據不夠精確

    針對接收高頻數據 有甚麼更好的資料讀取方式 或者運算的編碼方式嗎?

    我問的問題如果有些沒有sense的地方還請多包容

    我剛接觸C#一段時間而已 實戰經驗也很不足

    這次遇到的問題 似乎牽扯到了指令的執行時間  對於解決方法毫無頭緒 沒有方向

    希望大家能幫幫我

     
    2019年5月19日 下午 12:53

解答

  • 這個問題說到底就是兩點:
    1. 繪圖速度要足夠快,否則肯定要丟數據。用D2D應該就足以滿足了。 3D遊戲那麼複雜的場景都能達到60fps,甚至是更高,幾百幾千個點的2D圖形根本不足掛齒。
    2. 串口數據的讀取和處理要足夠快,避免系統緩存塞車。


    這里特別針對第二點說明一下。因為這部分是在c#裡面處理的,所以需要特別注意。 c#語言本身是針對開發效率的,而不是運行效率,所以在面對一秒鐘上萬個數據時需要改變日常的一些習慣。在這種情況下開發辦公自動化軟件的那些做法此時行不通,必須要把c#當c語言來用才行。提高c#性能的關鍵是以下5點:
    1. 盡一切可能不要在託管堆上分配記憶體,也就是說不要用new來創建引用類型。在託管堆上分配記憶體本身就很耗時,釋放的時候還要依賴垃圾回收器,性能損失更嚴重。
    2. 盡一切可能不要call函數,c#中的函數大部分無法內聯,call函數會造成一定的性能損失。盡可能使用能直接生成彙編指令的方式。例如,兩個byte合成ushort,要用<<移位操作符和|操作符或加法,而不要去用BitConverter.ToInt16。 delegate是函數指標包裝類,也屬於call函數,而且比call函數開銷更大。此外虛函數開銷大於非虛函數。
    3. 調用.net api時要注意,很多api關注的是健壯性和功能,而不是性能。 .net已經開源(https://referencesource.microsoft.com),拿不准的時候先去看源碼。比如BitConverter.ToInt16的源碼(https://referencesource.microsoft.com/#mscorlib/system/bitconverter.cs,64128518fca2c03f),看一下就會發現這個函數有多複雜,要執行很多驗證,一秒鐘如果call這樣的函數幾萬次,性能怎會好?
    4. 使用List等容器時必須格外注意。在c#中List內部是陣列,只有在末尾處添加和移除時才是低開銷操作。 RemoveAt(0)這樣的操作意味著整個陣列要全部複製一遍,此外還要設置很多標記值,這是性能黑洞。
    5. 安排多執行緒並行工作來改善性能。需要注意的是BeginInvoke並非任何時候都適用,這種東西是讓任務排隊,然後讓執行緒池裡面的空閒執行緒去執行,它不創建執行緒,也不獨占執行緒。對於一個長期任務來說,更合理的做法是在一切開始之前,先用new Thread把執行緒都創建好,每個執行緒做什麼工作,提前都分配好。然後開闢一塊共享數據區,用這部分共享記憶體來傳數據,而不是切換線程。如果沒有寫入衝突,盡量不對數據區加鎖。整數和浮點數的讀寫操作本身就是原子的,只要沒有寫入衝突,並不需要加鎖。寫入衝突是指兩個或更多的執行緒同時嘗試寫入,一個執行緒寫入,其它執行緒讀取不屬於寫入衝突,最多也就是讀寫不同步,不過對於不斷重繪的內容來說根本也不需要讓讀寫同步。
    這幾點對於常規c#開發來說無關緊要,但在面對大量數據需要處理這種情況時就很關鍵了。


    • 已編輯 [-] 2019年5月22日 上午 01:00
    • 已標示為解答 minchieh-Lin 2019年5月27日 上午 10:27
    2019年5月21日 下午 10:57
  • 1. 放大尺寸是指什麼?代碼中已對Resize進行處理了啊,直接調整控件尺寸,圖形自動就變了。不是這個意思麼?

    2. 句柄就是Handle。全窗口繪圖就用窗口的Handle,否則就用控件的Handle。

    3. 代碼太多了,實在不方便在這貼了。改道去這裡:(鏈接地址見最下方)





    • 已標示為解答 minchieh-Lin 2019年5月27日 上午 10:25
    • 已編輯 [-] 2019年6月3日 上午 11:24
    2019年5月22日 下午 11:53
  • 這個問題在 tw 版塊看到過好多次了,幾乎都是一樣的問題,答案就是要用 DirectX 來處理。 DirectX 沒有什麼線程(執行緒)限制,都是用 Critical Section 來控制,因此不用來回切換線程。

    *由於論壇篇幅問題,移除此處的代碼部分,在github中提供了更完整的內容(鏈接在下面)。

    • 已編輯 [-] 2019年5月23日 下午 12:56
    • 已標示為解答 minchieh-Lin 2019年5月27日 上午 10:27
    2019年5月20日 下午 11:26
  • 1. 如果把SerialPort這部分也放到c++裡面去,當然性能會更好。也就是說最佳方案其實是WinForm只當顯示屏用,無邏輯,不過性能提高的不會很多,因此我把這部分寫在c#裡面,沒有全用c++。

    2.c++代碼分為兩部分,一部分是緩衝(緩存),一部分是繪圖。繪圖部分除非你想要畫其它圖形,比如柱狀圖,否則不理解也無所謂。要想學d2d也不是很難,代碼都是公式化的,沒什麼自由發揮的空間,基本上都是直接用微軟提供的示例,組合一下就可以了。而且要改動也只需要改Refresh和Render這兩個函數,其它部分都是資源創建,這些部分永遠都是一樣的。另外就是用來控制繪圖的計時器SetTimer函數,這裡唯一可以改動的就是33u這個參數。 33u意味著33ms,也就是每秒30幀,可以改為17u,也就是每秒60幀,除此之外也沒什麼可改的。

    3. 緩衝部分雖然是c++寫的,但是和c#也沒多大區別,這部分不理解也無所謂,因為這部分基本上沒什麼可改的內容,核心就是一個float陣列(集合)。 FillData函數其實是繪圖的一部分,寫在緩存部分裡是為了省事,畢竟這只是個示例,所以我也沒深思熟慮,當年這樣寫的後來也就沒改過。除去兩個繪圖用的FillData函數,其它部分基本沒什麼值得鑽研的,就是直接往陣列裡存數據,從0位置開始存,存滿後再回到0位置,循環存入,陣列的大小就是畫面上數據點的數量,這些已經在代碼註釋中備註了。

    最主要的兩個參數,一個是上面提到的幀數,如果數據量大的話,可以改為17u,用60fps來繪製,另一個就是Create函數中的100u,數據量大的話可以改大些,不過最好不超過1000u,否則線段太密很難觀察。這兩個參數需要根據實際運行情況來調整。可以先用我這個示例代碼測試一下,看看能否滿足需要,如果這個還不行,那也就沒必要費力去研究它了,只能另尋他法。如果能夠滿足需要,哪部分代碼不能理解可以在此處詢問。

    另外,c++部分我才用的是c++11/14/17標準,因此如果有興趣研讀請不要找錯資料。

    補充:關於幀數和數據量這兩個參數,我再仔細說明一下。幀數×數據量=一秒鐘能顯示的最大數據總數。例如,60幀,數據量為1000,意味著一秒鐘最多顯示6萬個數據點。如果是30幀,數據量100的話,那每秒鐘最多也就只能顯示3千個數據點,換句話說就是有數據顯示不出來,或者說有一部分數據被忽略了。如果你的數據是一秒鐘8000筆,每筆3個值,那一秒鐘就有2萬4千個數據點,這就需要你設置至少60幀,數據量至少500,最好是1000。但數據量太大時線很密,可能難以查看(除非你用超寬屏顯示器),否則可能需要改為柱狀圖顯示。另外如果當幀數乘以數據量得到的數據總數小於等於輸入的數據總數時,那你就看不到掃描線或圖形滾動了,你將看到的是全屏閃爍,也就是說每一次重繪實際上所有的數據點都變了,是全屏刷新。這樣的亂閃通常沒什麼意義,除非你是在做某種特殊用途的示波器。因此,幀數和數據量這兩個參數的乘積最好是輸入數據總數的兩到三倍,如果難以查看,那你可能就需要考慮減少數據輸入,一秒鐘2萬4千個數據點人眼恐怕根本看不清啊。當然,我不知道你所說的震動圖形是什麼樣子的,也許你需要的正是全屏刷新。總之你實際測試一下比較好,另外如果能夠提供波形圖示例就更好了,哪怕只是手繪的示意圖。

    • 已編輯 [-] 2019年5月23日 下午 12:57
    • 已標示為解答 minchieh-Lin 2019年5月27日 上午 10:27
    2019年5月21日 下午 01:37
  • 對於我一個月前提出的問題

    在此提出我最後的解法

    其中問題點有兩點

    1.資料處理的方式 我原本是將完整的8byte讀出後invoke出去才計算值 導致效能不好

    後來改成前輩利用在接收時 直接利用<< 移位的方式在接收資料時就計算完 最後只把資料丟出去

    2.畫圖的方式

    上述前輩提到的眾多種畫圖方式都對我有很大的啟發 也都是很好的榜樣

    但最終我找到解決的方法是回歸 c#內建的chart  其中有一個屬性是 DataBindXY

    利用資料綁定的方式 改變我綁定的資料 圖也就會跟著改變了

    但如果是大數據的話 每一點改變一次還是會卡

    所以我利用迴圈的方式 讓我的資料每改1000點 才變更一次圖

    至於動態曲線圖的方式 我是利用 queue序列的方式  (先進先出)  

    設定你要的queue大小 然後在資料滿的時候

    利用dequeue() 刪除最前面的資料 再利用enqueue()加入資料到最後面

    承如我上面所說 因為大數據時一點一點改變就變更圖面還是會卡

    我將queue的大小設為10000  資料滿的時候 就invoke到Chart畫圖

    之後利用迴圈 一次dequeue 1000個點 這時queue.count就只剩9000

    而當資料又存回10000時 又會在畫一次 達到每變動1000筆畫一次圖 且可視10000筆資料

     在此提供我最後的解法讓大家參考

     當數據量更大 要求更高的時候  這個辦法沒辦法滿足各位的時候

    其他前輩的方法也都是可行的

    謝謝各位前輩這一個月的幫助

    • 已標示為解答 minchieh-Lin 2019年6月13日 下午 01:06
    2019年6月13日 下午 01:05

所有回覆

  • 8000 筆 * 8Bytes * 8bits = 512000

    先確定你的 baud rate 有沒有可能這麼大。

    如果baud rate 上是允許的,可能就要使用平行工作的方式, 分攤不同的工作到不同的 CPU 核心 (先決條件當然是得在多核或多 CPU 的系統上跑)。

    例如: 一個執行緒專門接資料, 另一個做 parse。然後用 Control.BeginInvoke 取代 Control.Invoke。


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。 https://skilltree.my/

    2019年5月19日 下午 01:53
    版主
  • 特殊硬體的話,通常原廠會規劃專門方式處理。

    一般 SerialPort 200ms 才處理 200 bytes 左右,所以,若硬體本身每秒 8000 筆數據,硬體商會告訴你怎樣處理。


    不精確的問法,就會得到隨便猜的答案;自己都不肯花時間好好描述問題,又何必期望網友會認真回答?

    2019年5月19日 下午 02:29
  • Hi, 你這些問題跟我之前遇到的很類似, 可以參考我之前發問以及幾位前輩的回答討論, 這裡

    關於繪圖部分, 要考量是否真的有需要這麼準確? 人眼看得出那麼準確的資料嗎?

    另外我之前也是遇到繪圖跟不上資料更新的速度, 後來試著把chart的X軸縮小後(原本一個畫面X軸可畫200點->變50點),

    意外的發現畫圖速率有比較跟得上, 你可以試試


    • 已編輯 _Wayne56 2019年5月20日 上午 02:15
    2019年5月20日 上午 02:08
  • Bill,

    謝謝您的回答與建議

    我會試試看如何使用多CPU

    想請問您說的利用Beginivoke是指

    UI一個執行續

    讀取資料一個執行續

    然後parse一個執行續嗎

    所以我要在case E: 讀完完整資料後 就 begininvoke給parse嗎

    抱歉問的可能不清楚 也很細節  

    謝謝您的耐心


    2019年5月20日 上午 05:39
  • 心冷,

    謝謝您的回答與建議

    我會在詢問看看硬體設備是否有特殊的處理方法

    昨天再次詢問確定是

    每秒 7812筆 8byte資料


    2019年5月20日 上午 05:40
  • 頑張,

    謝謝您的回答與建議

    當初在找解決辦法的時候有把你這篇看完

    也是因為這篇所以我才發現是緩衝塞車的問題

    因而加入discardinbuffer清除緩衝

    但還是有幾個我比較不清楚的問題想請教您

    1)您當初接收資料 MCU的傳輸速率是多少 用何種方式才能夠全部接收並轉成數值呈現,而不丟棄任何資料

    因為像我用ReadByte()這種方式再去判斷S,E 緩衝每次都一下就衝到萬了

    所以為了達到實時,我就只能用清除緩衝的方式 在我拿到一筆完整資料後renew我的緩衝

    2)關於繪圖部分, 要考量是否真的有需要這麼準確? 人眼看得出那麼準確的資料嗎?

    關於這個問題,因為我的MCU是接受震動感測器端的資料

    所以需要很精確 才能夠呈現出震動的每個訊號

    但至於要到多精確 我目前還在找辦法提升

    或許不用到每秒收到8000筆

    可是我利用上述因為接收及處理資料延遲

    因而捨棄資料的方式

    每秒只能讀到大概50筆資料 所以遺失了非常多訊號



    2019年5月20日 上午 05:56
  • 頑張,

    謝謝您的回答與建議

    當初在找解決辦法的時候有把你這篇看完

    也是因為這篇所以我才發現是緩衝塞車的問題

    因而加入discardinbuffer清除緩衝

    但還是有幾個我比較不清楚的問題想請教您

    1)您當初接收資料 MCU的傳輸速率是多少 用何種方式才能夠全部接收並轉成數值呈現,而不丟棄任何資料

    因為像我用ReadByte()這種方式再去判斷S,E 緩衝每次都一下就衝到萬了

    所以為了達到實時,我就只能用清除緩衝的方式 在我拿到一筆完整資料後renew我的緩衝

    2)關於繪圖部分, 要考量是否真的有需要這麼準確? 人眼看得出那麼準確的資料嗎?

    關於這個問題,因為我的MCU是接受震動感測器端的資料

    所以需要很精確 才能夠呈現出震動的每個訊號

    但至於要到多精確 我目前還在找辦法提升

    或許不用到每秒收到8000筆

    可是我利用上述因為接收及處理資料延遲

    因而捨棄資料的方式

    每秒只能讀到大概50筆資料 所以遺失了非常多訊號



    我忘記當時MCU傳多快了, 雖然不到八千, 但也是很快的速率, 打開port沒多久就塞車

    但後來討論覺得不需要那麼精確, 所以降到450 bytes/s左右

    而繪圖跟上面講的一樣, 減少X軸後可跟得上更新速度, 算是取得兩邊都可以接受的範圍

    那時候有想或許可以寫個driver幫忙處理, 但覺得麻煩...

    總之我是覺得先看是否能降低到一個可接受而且資料處理跟得上的速率

    不然可能就要GPU或記憶體繪圖或driver輔助, 這些我就沒試過了.

    2019年5月20日 上午 07:53
  • 8000 筆 * 8Bytes * 8bits = 512000

    先確定你的 baud rate 有沒有可能這麼大。

    如果baud rate 上是允許的,可能就要使用平行工作的方式, 分攤不同的工作到不同的 CPU 核心 (先決條件當然是得在多核或多 CPU 的系統上跑)。

    例如: 一個執行緒專門接資料, 另一個做 parse。然後用 Control.BeginInvoke 取代 Control.Invoke。


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。 https://skilltree.my/

    請問一下你所說的平行工作方式

    是指 parallel.For嗎??

    2019年5月20日 上午 10:04
  • private void parse(List<byte> tempList)   //副程式2
       {
           if (tempList[0] == (byte)S && tempList[tempList.Count - 1] == (byte)E)  //將取得的資料去頭去尾
                {
                    tempList.RemoveAt(0);
                    tempList.RemoveAt(tempList.Count - 1);
                    Display1 d = new Display1(DisplayText);    //display為委派任務 在這產生一個d為一個新的委派任務  方法為displaytext副函式
                    BeginInvoke(d, tempList.ToArray());   //利用invoke執行d的委派 傳入的資料為新的物件 { tempList.ToArray()}
    
                }
            }
    
    Invoke 改 BeginInvoke 就是改最後一行成上述的那樣。

    平行工作不一定是使用 Parallel,至少這問題的情境不適合。我想表達的是讓兩個不同的執行緒去做不同的事情,一個做接收,另外一個做解析 (parse)


    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。 https://skilltree.my/

    2019年5月20日 下午 01:34
    版主
  • 這個問題在 tw 版塊看到過好多次了,幾乎都是一樣的問題,答案就是要用 DirectX 來處理。 DirectX 沒有什麼線程(執行緒)限制,都是用 Critical Section 來控制,因此不用來回切換線程。

    *由於論壇篇幅問題,移除此處的代碼部分,在github中提供了更完整的內容(鏈接在下面)。

    • 已編輯 [-] 2019年5月23日 下午 12:56
    • 已標示為解答 minchieh-Lin 2019年5月27日 上午 10:27
    2019年5月20日 下午 11:26
  • 感謝您的回答與建議

    但對於程式碼的部分我還是菜鳥 還需要花時間去study

    那整體的思路架構的部分是

    將資料佔存的緩衝以及繪圖的部分 利用c++包成 dll檔   在利用c#去呼叫嗎

    c#中就只操作Readbyte()讀取資料 和轉成int的部分

    概念大概是這樣嗎?  因為我還需要點時間研讀c++ 跟DLL動態資料庫的程式架構

    現在看您貼的程式有些吃力

    所以想先問問看您這個架構的方向是正確的嗎??

    以及不知道您是否能提示我幾個 主要的關鍵工具讓我有個更好的切入點去了解這塊

    十分感謝您的幫助 ~


    2019年5月21日 下午 12:01
  • 1. 如果把SerialPort這部分也放到c++裡面去,當然性能會更好。也就是說最佳方案其實是WinForm只當顯示屏用,無邏輯,不過性能提高的不會很多,因此我把這部分寫在c#裡面,沒有全用c++。

    2.c++代碼分為兩部分,一部分是緩衝(緩存),一部分是繪圖。繪圖部分除非你想要畫其它圖形,比如柱狀圖,否則不理解也無所謂。要想學d2d也不是很難,代碼都是公式化的,沒什麼自由發揮的空間,基本上都是直接用微軟提供的示例,組合一下就可以了。而且要改動也只需要改Refresh和Render這兩個函數,其它部分都是資源創建,這些部分永遠都是一樣的。另外就是用來控制繪圖的計時器SetTimer函數,這裡唯一可以改動的就是33u這個參數。 33u意味著33ms,也就是每秒30幀,可以改為17u,也就是每秒60幀,除此之外也沒什麼可改的。

    3. 緩衝部分雖然是c++寫的,但是和c#也沒多大區別,這部分不理解也無所謂,因為這部分基本上沒什麼可改的內容,核心就是一個float陣列(集合)。 FillData函數其實是繪圖的一部分,寫在緩存部分裡是為了省事,畢竟這只是個示例,所以我也沒深思熟慮,當年這樣寫的後來也就沒改過。除去兩個繪圖用的FillData函數,其它部分基本沒什麼值得鑽研的,就是直接往陣列裡存數據,從0位置開始存,存滿後再回到0位置,循環存入,陣列的大小就是畫面上數據點的數量,這些已經在代碼註釋中備註了。

    最主要的兩個參數,一個是上面提到的幀數,如果數據量大的話,可以改為17u,用60fps來繪製,另一個就是Create函數中的100u,數據量大的話可以改大些,不過最好不超過1000u,否則線段太密很難觀察。這兩個參數需要根據實際運行情況來調整。可以先用我這個示例代碼測試一下,看看能否滿足需要,如果這個還不行,那也就沒必要費力去研究它了,只能另尋他法。如果能夠滿足需要,哪部分代碼不能理解可以在此處詢問。

    另外,c++部分我才用的是c++11/14/17標準,因此如果有興趣研讀請不要找錯資料。

    補充:關於幀數和數據量這兩個參數,我再仔細說明一下。幀數×數據量=一秒鐘能顯示的最大數據總數。例如,60幀,數據量為1000,意味著一秒鐘最多顯示6萬個數據點。如果是30幀,數據量100的話,那每秒鐘最多也就只能顯示3千個數據點,換句話說就是有數據顯示不出來,或者說有一部分數據被忽略了。如果你的數據是一秒鐘8000筆,每筆3個值,那一秒鐘就有2萬4千個數據點,這就需要你設置至少60幀,數據量至少500,最好是1000。但數據量太大時線很密,可能難以查看(除非你用超寬屏顯示器),否則可能需要改為柱狀圖顯示。另外如果當幀數乘以數據量得到的數據總數小於等於輸入的數據總數時,那你就看不到掃描線或圖形滾動了,你將看到的是全屏閃爍,也就是說每一次重繪實際上所有的數據點都變了,是全屏刷新。這樣的亂閃通常沒什麼意義,除非你是在做某種特殊用途的示波器。因此,幀數和數據量這兩個參數的乘積最好是輸入數據總數的兩到三倍,如果難以查看,那你可能就需要考慮減少數據輸入,一秒鐘2萬4千個數據點人眼恐怕根本看不清啊。當然,我不知道你所說的震動圖形是什麼樣子的,也許你需要的正是全屏刷新。總之你實際測試一下比較好,另外如果能夠提供波形圖示例就更好了,哪怕只是手繪的示意圖。

    • 已編輯 [-] 2019年5月23日 下午 12:57
    • 已標示為解答 minchieh-Lin 2019年5月27日 上午 10:27
    2019年5月21日 下午 01:37
  • 這個問題說到底就是兩點:
    1. 繪圖速度要足夠快,否則肯定要丟數據。用D2D應該就足以滿足了。 3D遊戲那麼複雜的場景都能達到60fps,甚至是更高,幾百幾千個點的2D圖形根本不足掛齒。
    2. 串口數據的讀取和處理要足夠快,避免系統緩存塞車。


    這里特別針對第二點說明一下。因為這部分是在c#裡面處理的,所以需要特別注意。 c#語言本身是針對開發效率的,而不是運行效率,所以在面對一秒鐘上萬個數據時需要改變日常的一些習慣。在這種情況下開發辦公自動化軟件的那些做法此時行不通,必須要把c#當c語言來用才行。提高c#性能的關鍵是以下5點:
    1. 盡一切可能不要在託管堆上分配記憶體,也就是說不要用new來創建引用類型。在託管堆上分配記憶體本身就很耗時,釋放的時候還要依賴垃圾回收器,性能損失更嚴重。
    2. 盡一切可能不要call函數,c#中的函數大部分無法內聯,call函數會造成一定的性能損失。盡可能使用能直接生成彙編指令的方式。例如,兩個byte合成ushort,要用<<移位操作符和|操作符或加法,而不要去用BitConverter.ToInt16。 delegate是函數指標包裝類,也屬於call函數,而且比call函數開銷更大。此外虛函數開銷大於非虛函數。
    3. 調用.net api時要注意,很多api關注的是健壯性和功能,而不是性能。 .net已經開源(https://referencesource.microsoft.com),拿不准的時候先去看源碼。比如BitConverter.ToInt16的源碼(https://referencesource.microsoft.com/#mscorlib/system/bitconverter.cs,64128518fca2c03f),看一下就會發現這個函數有多複雜,要執行很多驗證,一秒鐘如果call這樣的函數幾萬次,性能怎會好?
    4. 使用List等容器時必須格外注意。在c#中List內部是陣列,只有在末尾處添加和移除時才是低開銷操作。 RemoveAt(0)這樣的操作意味著整個陣列要全部複製一遍,此外還要設置很多標記值,這是性能黑洞。
    5. 安排多執行緒並行工作來改善性能。需要注意的是BeginInvoke並非任何時候都適用,這種東西是讓任務排隊,然後讓執行緒池裡面的空閒執行緒去執行,它不創建執行緒,也不獨占執行緒。對於一個長期任務來說,更合理的做法是在一切開始之前,先用new Thread把執行緒都創建好,每個執行緒做什麼工作,提前都分配好。然後開闢一塊共享數據區,用這部分共享記憶體來傳數據,而不是切換線程。如果沒有寫入衝突,盡量不對數據區加鎖。整數和浮點數的讀寫操作本身就是原子的,只要沒有寫入衝突,並不需要加鎖。寫入衝突是指兩個或更多的執行緒同時嘗試寫入,一個執行緒寫入,其它執行緒讀取不屬於寫入衝突,最多也就是讀寫不同步,不過對於不斷重繪的內容來說根本也不需要讓讀寫同步。
    這幾點對於常規c#開發來說無關緊要,但在面對大量數據需要處理這種情況時就很關鍵了。


    • 已編輯 [-] 2019年5月22日 上午 01:00
    • 已標示為解答 minchieh-Lin 2019年5月27日 上午 10:27
    2019年5月21日 下午 10:57
  • 好多之前沒想過的部分, 感謝[-]提供的程式碼與建議, 謝謝

    原PO如果有實作成功希望可以上來分享心得 :)

    2019年5月22日 上午 07:00
  • 更正:因為平時主要做c++,很少做c#,所以上面提到的BeginInvoke部分不正確。 BeginInvoke不是在執行緒池中排隊,而是用PostMessage在窗口消息中排隊,換句話說BeginInvoke是在UI執行緒上執行的。它不是讓執行緒池中的空閒執行緒來執行,而是等著UI執行緒在空閒之後去執行。所以在平行處理過程中用BeginInvoke會讓問題更糟糕。不要這樣做。


    另外我看了一下源碼(https://referencesource.microsoft.com/#System/sys/system/io/ports/SerialPort.cs,84c8f11cd12b2130)。發現ReadByte()本身就包含IsOpen判斷,所以上面的代碼應該改為這樣。

    private void Read()
    {
        int value;
        try
        {
            while(true)
            {
                value = m_SerialPort.ReadByte();
                //......後面的部分不變
            }
        }
        catch {}
    }

    順便再提一下,不要忘記設置c++和c#中的Release優化選項,編譯時優化對c++的性能提高是很有幫助的。


    2019年5月22日 上午 09:39
  • 十分感謝前輩您的耐心回應

    您所提供的程式碼我有大致看過了

    目前對於如何丟資料的流程 大致清楚

    就差繪圖部分的源碼 可能還需要點時間鑽研

    所以想說先利用前輩的程式碼跑看看大致是如何呈現

    方便我了解繪圖的過程

    但我將前輩的dll程式碼複製上去時 發現有一些函式是未定義的

    不知道是不是跟我沒有#include "stdafx.h" 這個頭檔有關係

    想請問前輩這個頭檔裡放的是甚麼

    再來還有幾個問題想請教前輩

    1)對於我所需要呈現的圖像部分

    我所得到的資料型態: (S + X_low + X_hi + Y_lo + Y_hi + Z_lo + Z_hi + E)

    而我需要去組合 每筆資料的x ,y ,z 並且分開繪製

    我初步的想法是

        default:
                                    if (m_IsEnabled)
                                    {
                                        if (m_IsEmpty) //没有低位部分时先保存低位部分
                                        {
                                            m_IsEmpty = false;
                                            m_Temp = value;
                                        }
                                        else
                                        {
                                            m_IsEmpty = true;
                                            value = value << 8;
                                            value += m_Temp; //合并低位和高位
                                            Core.Push(value);
                                        }
                                    }
                                    break;
                            }

    在上面的匯入資料的方法中 多加三個boolen判別(x_ok , y_ok , z_ok)

    根據這三個參數來控制我資料丟進繪圖的順序

    然後在dll 緩存的部分 新增 core.push_x core.push_y core.push_z

    這樣我是否就能得到三個分開的暫存  不知道這樣是否可行?

    那繪圖的部分 如果我需要畫三張 那我的程式碼是不是也要打三次

    2)綜觀這種方法的概念  是不是我只要需要對數據做任何的行為

    都需要先封包成DLL  再利用C#引入呢?

    例如 儲存數據  平均值 標準差 FFT 等等

    3) 在我做完資料讀取後  我還需要進一步的對於數據做FFT轉換

    那就呈現速度來看的話 我是否也需要在DLL中編寫FFT程式

    然後畫圖程式的話 一樣要在寫三次?  總共6張圖

    4)程式碼的部分在繪圖(Oscilloscope) 區塊

    這兩個存入數據的差別是甚麼?

    ///<summary>存入数据</summary>
    void Oscilloscope::Push(const float value) noexcept
    {
    m_Cache.Push(value);
    }

    ///<summary>存入数据</summary>
    ///<param name="value">值</param>
    extern "C" __declspec(dllexport) void __stdcall Push(float value)
    {
    oscilloscope->Push(value);
    }

    抱歉對於您詳細的解說

    我還沒辦法一時完全領悟

    但我會努力把您寶貴的經驗都學習起來

    十分感謝您的幫助


    2019年5月22日 上午 10:45
  • 1. stdafx.h是系統自動生成的。不應該沒有。你的VS是2017版本嗎?是否有按我上面的步驟添加?選項是否選對了?添加c++ dll項目之後應該是下面這個樣子:

    2. 如果三個值是分開的,c#中的臨時值部分因該這樣做。

    3. 計算平均值等都可以在c#裡面完成,不過要單開新的執行緒。

    4. 繪圖程式怎麼寫方法很多,最好你能畫個示意圖之類的東西,以此來說明。圖片貼不上來的話可以貼到別處,在這里以文本形式放一個鏈接。

    5. Oscilloscope::Push是Oscilloscope類的成員函數,成員函數的第一個參數為this指標,這是一個隱藏參數,因此要call成員函數,必須先創建類實例,這個和c#是同一規則。 extern "C" __declspec(dllexport) void __stdcall Push(float value)是命名空間函數,相當於c#中的static函數,而且這個函數還用於導出,指定了命名規則為c語言兼容模式,這樣就能通過函數名來找到函數指標了。

    *由於論壇篇幅問題,移除此處的代碼部分,在github中提供了更完整的內容(鏈接在下面)。
    • 已編輯 [-] 2019年5月23日 下午 12:58
    2019年5月22日 下午 12:52
  • 前輩您好

    1.我是用的是2015版本 ,所以是版本的問題導致沒有出現頭檔嗎 ,我去抓2017版試試看

    2. 判斷index =5的時候 會跳else   ,請問是不是少了一句 

    m_Cache[m_CacheIndex] = value;

    不然m_Cache[5]此時應該還沒有值?

    在請問一個問題就是

    為甚麼下面有一個while(true)

    4.呈現圖的部分我會想一下,到時候在與您討論 ,感謝您



    2019年5月22日 下午 02:08
  • 寫錯了。應該是:

    private int[] m_Cache = new int[5];

    Core.Push(m_Cache[0] + (m_Cache[1] << 8), m_Cache[2] + (m_Cache[3] << 8), m_Cache[4] + (value << 8));

    也就是說最後一個數不用緩存啦。

    while(true)就是無限循環。因為ReadByte()本身就包含IsOpen判斷,當設備關閉後,會用try...catch來退出。如果while裡面再判斷IsOpen就和ReadByte()重複了。畢竟數據量很大嗎,所以即使只是一條指令,最終累積起來對性能都是會有影響的。

    當然我上面的代碼還有很多地方可以改善,這只是個雛形,等周末時我再來進一步完善它。

    2019年5月22日 下午 02:55
  • 我下載完2017版本後

    偉哉前輩的指導和程式碼 我已經可以成功地將單筆資料呈現圖形

    且有達到我所需求的速度

    詳細的DLL檔 我還需要時間吸收(600多行 有點吃力XD)

    但現在的問題是我需要把資料分成三筆, 且需要三個視窗 

    1.如果是更改core.push函數的參數為3

    那這樣在dll檔是不是要更動非常多的東西 (ex 多建兩個繪圖視窗 將所有push參數改動 多建兩個m_cache)

    2.然後現在的程式碼是直接窗口顯示 若我只是要寫成UI裡的小視窗 那我需要更動DLL裡的東西嗎?

    還是要用C#的哪個物件導入   

    3.D2D繪圖的部分 不知道能否 放大尺度 框架調整  加入x,y軸?! 還是要用label加入?   以及繪圖區"句柄"是甚麼意思

    我的最終目標大概就是  一個UI介面 有5個按鈕 PORT連接 開始 暫停 清除 以及三個繪圖窗 1個richtextbox 顯示讀出的數據

    接下來我會努力把DLL讀完 

    或許上述很多問題在我讀完後都能夠得到解答

    但暫且先提出來 也當作一個階段的紀錄

    感謝前輩給的明燈  





    2019年5月22日 下午 03:50
  • 1. 放大尺寸是指什麼?代碼中已對Resize進行處理了啊,直接調整控件尺寸,圖形自動就變了。不是這個意思麼?

    2. 句柄就是Handle。全窗口繪圖就用窗口的Handle,否則就用控件的Handle。

    3. 代碼太多了,實在不方便在這貼了。改道去這裡:(鏈接地址見最下方)





    • 已標示為解答 minchieh-Lin 2019年5月27日 上午 10:25
    • 已編輯 [-] 2019年6月3日 上午 11:24
    2019年5月22日 下午 11:53
  • 感謝前輩的幫助

    1.詳細的更改D2D畫布樣式的方法 我還在研究程式碼 可能問到了一些程式碼中有的內容很抱歉

    我會盡快讀完 之後再將問題提出請教您

    2.要更改為控件的handle該如何更改

    我從程式碼中看他是從form1的

    m_Ptr = DllOscilloscope.Initialize(Handle);  

    藉由form1  DllOscilloscope 類中的

    [DllImport(DllFileName)]

    internal extern static IntPtr Initialize(IntPtr hwnd);

    導入動態資料庫中DllOscilloscope.dll(dll檔)中

    extern "C" __declspec(dllexport) void* __stdcall Initialize(HWND hwnd)
    {
    return Oscilloscopes::Add(hwnd);
    }

    在dll檔中的Oscilloscopes的方法ADD  (順便問一下這裡的*是甚麼意思)

    Oscilloscopes* Oscilloscopes::Add(HWND const hwnd) noexcept { if (hwnd != nullptr) { critical_section::scoped_lock lock(s_CriticalSection); auto const t_Where = s_Instances.find(hwnd); if (t_Where != s_Instances.end()) return t_Where->second.get(); try { auto t_Instance = new Oscilloscopes(hwnd); s_Instances.emplace(hwnd, unique_ptr<Oscilloscopes>(t_Instance)); if (s_D2dFactory == nullptr) { s_D2dFactoryCount = (SUCCEEDED(D2D1CreateFactory(D2D1_FACTORY_TYPE::D2D1_FACTORY_TYPE_SINGLE_THREADED, s_D2dFactory.GetAddressOf())) && s_D2dFactory != nullptr) ? 1 : 0; } else { ++s_D2dFactoryCount; } return t_Instance; } catch (...) {} } return nullptr; }

    最內部ADD那層有點看不懂

    我有試過將最外層的 Handle改成 pictureBox1.Handle 但沒有效果

    請問是這樣做嗎?

    3.  承上的程式碼中 

    1)[DllImport(DllFileName)]internal extern static IntPtr Initialize(IntPtr hwnd);  //在FORM1內

    2)extern "C" __declspec(dllexport) void* __stdcall Initialize(HWND hwnd)       //在DLL內
    {
    return Oscilloscopes::Add(hwnd);
    }

    這兩句不是都是在告訴form說 我的Initialize()是一個外部(DLL)的函式嘛

    那為甚麼需要打在DLL中 ,那如果只在FORM1內打的話 沒辦法連結嗎?

    4.DLL檔中 分成Oscilloscope跟 Oscilloscopes的差別是甚麼??



    2019年5月23日 上午 10:40
  • 1. Initialize裡面的handle必須是窗口,也就是form的handle。因為要自動處理窗口最小化,並區分調用者是哪一個窗口。

    2. *的意思是這是一個指標。

    3. 【extern "C" __declspec(dllexport) ... __stdcall】這一句指定了函數的調用規則。這句話的意思就是說這個函數是c語言兼容的(https://docs.microsoft.com/en-us/cpp/cpp/extern-cpp?view=vs-2019),而不是只能被c++語言使用,命名時按stdcall要求增加參數表,且指定由被調用的這個函數自己來清棧(https://docs.microsoft.com/en-us/cpp/cpp/argument-passing-and-naming -conventions?view=vs-2019),__declspec(dllexport)說明這是一個導出函數(https://docs.microsoft.com/en-us/cpp/cpp/declspec?view=vs-2019),有經過這樣指定的函數才能被從外部調用。 [DllImport(DllFileName)]這個只是.net裡面的東西,只在.net語言裡才有用。

    4. add那部分什麼特別的東西都沒有,那一大段就是往map(相當於.net裡面的dictionary)裡面添加一項。

    5. 每一個窗口有一個Oscilloscopes,每一個Oscilloscopes有多個Oscilloscope。也就是說每一個進程可以有多個窗口,每一個窗口可以有多個示波器(其實就是圖表啦)。現在代碼我還在改,下一步將支持每一個圖表有多個圖形。最終的想法就是一個程序可以開多個窗口,每個窗口都可以有多個圖表,每個圖上可以畫多條不同顏色不同類型的圖形。最終應該是這樣的:


    • 已編輯 [-] 2019年5月23日 下午 01:15
    2019年5月23日 下午 12:52
  • 1.所以意思是  執行時整個窗口都是畫布嗎?  

    針對以下的行為的可行性有嗎?

    1)將x y z三個畫布獨立分開(我有試過調整裡面的長寬參數 跟dock 可是好像沒辦法把三個個別拆開 )

    2)在窗口中空出非畫布的空間來做其他UI的行為嗎 也就是縮放我的畫布 如你上面的示意圖  讓我能架設按鈕來設計其他行為

    3)標記X,Y軸  (X軸fixed-Y軸fixed 或 X軸fixed-Y軸free)

    4)若值是負數的 有辦法呈現嗎?  我有試過將我的每筆數據減掉穩態值 成為一個 以0上下波動的數據  執行後發現0以下的值不會繪出

    2.resize()函數 我有點看不懂他的操作  他的參數是 (ptr , oscilloscope) 不知道如何使用


    2019年5月23日 下午 04:16
  • 1. 窗口不是畫布,否則你就不可能看到三個了,你能看到三個不重疊的就說明它們是分開的。至於怎麼調,這個不也不太清楚,自從有了WPF之後我就沒做過WinForm了,時隔12年很多已經忘記,佈局方面請先自行嘗試調整一下吧。

    2. XY軸我認為直接弄張圖片應該就行了。至於小於零的值,可以將所有值加上最小負數值,使其全部變成正數。比如如果取值範圍是-100~100,那隻要全部加上100就行了。

    3. resize不需要使用,你直接設置WinForm裡面OscilloscopeControl的高寬。所有佈局操作都是基於OscilloscopeControl的。

    2019年5月23日 下午 04:43
  • github開源項目已更新。最主要的部分已完成,且已支持放大縮小,拖拽和滾動條移動。坐標軸等功能隨後逐步添加。
    • 已標示為解答 minchieh-Lin 2019年5月27日 上午 03:27
    • 已取消標示為解答 minchieh-Lin 2019年5月27日 上午 03:27
    2019年5月26日 下午 07:51
  • 十分感謝您~

    我的目前還在著手研究畫圖的程式碼

    會再看看前輩修改的程式碼做為參考

    目前有利用前輩的觀念嘗試自己畫看看

    我用的方式是參考這篇的畫圖方式並做修改

    "用C#绘制实时曲线图"

    不能貼網址 所以貼標題 抱歉

    並且用picturebox讀出每張圖的image

    一秒可以畫10張圖 1000個點  所以每秒10000筆數據

    但目前只是圖形雛型出來而已 對於畫圖的套件還是很多不懂

    仍然在努力學習中   還在研究數據如何調整尺度 

    目前一個像素只能是一個點 4000的值要4000個像素點

    希望能夠調整像素所代表的值 例如一個像素代表100 ,4000 只要40個點

    以及製造x,y軸 以及儲存當下圖片的數值等等

    感謝前輩的大力幫助

    如果我有成功實做出來

    我也會貼上成果與大家分享 

    2019年5月27日 上午 03:41
  • 很高興看到你在自己嘗試而不是僅僅只在復制粘貼。

    我的看法是在用c#繪圖時,那最好是用wpf+win2d,win2d是d2d的.net包裝。數據量大的話還是需要GPU,比如我用的d2d繪圖,在每秒12萬數據量60fps且每幀1萬點數據的時候CPU佔用率仍然是0%,在nvidia gtx560老顯卡上,GPU佔用率也只有3%。另外wpf可以用VisualTarget多線程更新UI,這樣就不會阻塞界面操作了。

    另外我的示例開源項目還會繼續做,如有興趣可以繼續關注。

    2019年5月27日 上午 08:38
  • 謝謝前輩的建議

    我會在摸索看看並持續追蹤

    想順便問一下前輩

    利用繪圖可以讓y軸的起始點不在0嗎

    也就是只設定y軸的長度

    然後讓每1000筆數據的最低數據-100成為最低y軸

    讓y軸的數值自由 但長度是固定的?

    2019年5月27日 上午 10:44
  • 先loop一遍找出最靠近坐标轴的值,然後描绘點的時候減一下就行了,参看我示例中新增的AutoTranslate参数。
    2019年5月27日 下午 11:18
  • 完工。不過由於時間匆忙,因此沒有進行仔細測試,不排除有問題,註釋部分也可能有筆誤,請諒解。如發現問題可以在此反饋或在GitHub上反饋,但無法保證能及時回复,再次請諒解。

    https://github.com/IVVOVVI/SerialPortRealTimeSignalMonitor

    2019年6月3日 上午 11:46
  • WOW 這介面也太酷了, 是用WPF?

    值得學習, 謝謝分享

    2019年6月4日 上午 02:34
  • WOW 這介面也太酷了, 是用WPF?

    值得學習, 謝謝分享

    內核還是WinForm Control,因為繪圖需要handle,WPF只有窗口有handle,因此內部還是需要WinForm,然後包裝成WPF Custom Control。 WPF直接集成DirectX需要實現IDirect3DSurface9,然後傳遞給D3DImage。但這個DirectX9早已淘汰,VS2015/2017/2019中都不集成Directx9開發工具,而近十年內微軟幾乎沒有為WPF提供任何改進。因此最後還是選擇使用WinForm Control做內核。不過除內核之外其它部分均為WPF。
    2019年6月4日 上午 07:46
  • 前輩你好

    我試著執行您的project, 出現以下錯誤

    我記得您在這篇一開始的回覆有提到如何reference這部分, 不過好像編輯拿掉了?

    我有自己加入不過還是沒解決

    2019年6月6日 上午 01:19
  • 前輩你好

    我試著執行您的project, 出現以下錯誤

    我記得您在這篇一開始的回覆有提到如何reference這部分, 不過好像編輯拿掉了?

    我有自己加入不過還是沒解決

    抱歉,不好意思,這部分忘記說了。 c++dll不用加reference,.net的reference是管不了c++dll的。需要手動在兩個c++dll項目上右擊,然後手動選擇生成。我已經添加了xcopy腳本,生成後會自動將dll複製到輸出目錄中。所以你僅需要執行右擊選擇生成這一個步驟即可。
    2019年6月6日 上午 07:15

  • 抱歉,不好意思,這部分忘記說了。 c++dll不用加reference,.net的reference是管不了c++dll的。需要手動在兩個c++dll項目上右擊,然後手動選擇生成。我已經添加了xcopy腳本,生成後會自動將dll複製到輸出目錄中。所以你僅需要執行右擊選擇生成這一個步驟即可。

    所以是在DllAlgorithms與DllOscillscope右鍵生成, "生成"是指Build嗎?

    但出現以下錯誤:

    LINK : fatal error LNK1104: cannot open file 'msvcprtd.lib'

    試著google解決方法, 在Additional Library Directories中加入該lib的路徑, RuBuild成功

    但執行project依舊有上面的錯誤

    2019年6月6日 上午 08:19

  • 抱歉,不好意思,這部分忘記說了。 c++dll不用加reference,.net的reference是管不了c++dll的。需要手動在兩個c++dll項目上右擊,然後手動選擇生成。我已經添加了xcopy腳本,生成後會自動將dll複製到輸出目錄中。所以你僅需要執行右擊選擇生成這一個步驟即可。

    所以是在DllAlgorithms與DllOscillscope右鍵生成, "生成"是指Build嗎?

    但出現以下錯誤:

    LINK : fatal error LNK1104: cannot open file 'msvcprtd.lib'

    試著google解決方法, 在Additional Library Directories中加入該lib的路徑, RuBuild成功

    但執行project依舊有上面的錯誤

    1. 生成是指Build,沒錯。
    2. 需要VS2017,最好是VS2019(昨天我把項目改為VS2019了)。
    3. 查看SerialPortRealTimeSignalMonitor.exe所在的文件夾,確認DllAlgorithms.dll和DllOscillscope.dll文件已經在裡面了。並且也看一下xcopy腳本是否還在,路徑對不對。

    別著急,這都是編譯問題,不難解決。

    2019年6月6日 上午 11:47
  • 我是使用VS2017

    檢查後兩個dll不在該目錄, XCOPY腳本還在

    目前已解決:

    於Additional Library Directories中加入缺少的lib的路徑, ReBuild

    在SerialPortRealTimeSignalMonitor-master\Debug中找到兩個dll並複製到SerialPortRealTimeSignalMonitor.exe所在的文件夾

    成功執行

    剩下的就是研讀code了

    ----------------------------------------------------

    另外請教前輩, 請問哪裡還有類似這邊這樣的C#討論區嗎? 目前主要還是在這裡還有stackoverflow尋求解決方案


    • 已編輯 _Wayne56 2019年6月10日 上午 01:57
    2019年6月10日 上午 01:56
  • 如果xcopy腳本還在,可能是因為你把項目放在了c盤或因其它某種原因導致folder受到了保護,結果復制操作因無權限失敗。 vs中的消息窗口應該有提示,當然最簡單的解決辦法就是像你所做的這樣,直接手動複製一下。

    英文討論區主要就是stackoverflow,中文的也就只有msdn了,其次就是google。簡體中文有csdn,不過由於發佈內容需要復雜的註冊,所以我沒參與討論過,不知道好壞,不推薦使用。簡體中文的csdn和博客園上有大量blog文章,不過大部分都是互相抄襲,原創內容很少,而且錯誤很多,一般我只將其列為次要參考。對於英文內容來說,如果只限於c#或微軟產品,英文版msdn似乎比stackoverflow還要好些。順便說一下,msdn的官方文檔裡面也有很多錯誤,特別是windows api和c++部分的英文文檔,閱讀時需要注意,不過官方示例代碼一般都是正確的。以上是個人體會,因供參考。

    2019年6月10日 上午 08:01
  • 前輩您好

    我也是使用VS2017

    但我在建置的時候出現了 找不到V142建置工具的問題

    請問這是要下載甚麼

    我按重定方案目標 也沒有讓我選擇平台工具的地方 

    只有選擇window SDK版本

    且最後我是在.NET上找到了某個控件

    可以解決我實時繪製大量資料的問題

    但當我做完FFT後 那個控件滿足不了我的需求

    我需要的是像前輩的一樣可以放大縮小查值 且可以丟入(X陣列,Y陣列)的控件

    但找了好久始終找不到一個好用的控件

    如果要像前輩一樣自己寫一份控件

    我怕我明年畢業前都還在寫系統 沒辦法做碩論QQ

    請問前輩有比較好的建議嗎  (對於控件的選擇)

    或者是 不要用WINDOW FORM 而用其他工具會有更好的效果呢


    2019年6月11日 下午 01:28
  • vs2017沒有v142。這個原因是因為之前我安裝了vs2019,然後項目就自動變成v142了。如果你不打算裝vs2019,那需要在兩個c++項目上右擊,然後選屬性,將其中的平台工具集改回vs2017(v141),c#部分無需改動

    至於現有的winform控件,免費的似乎沒有,即使是wpf免費且有這些功能的似乎也不好找。至少我沒找到,否則我也就用不著費力寫這麼一個示例了。我寫的這個開源項目是示例,沒有經過嚴格測試,所以如果你要拿來當控件用需要自己完成測試環節。或者你可以參考我這個示例自己來改寫,應該不用很長時間,我從零開始寫一共也只用了1週,每天也就寫2、3個小時,d2d繪圖部分1天就寫完了。自己做應該也不會寫到明年去吧。如果自己實在寫不了,我的這個也無法滿足,那隻能花時間去google,看看能不能找到合適的控件,很抱歉我實在沒有能推薦的東西給你。
    2019年6月11日 下午 03:22
  • 謝謝前輩的提點  已經可以執行前輩的程式了

    我會在抽空研究看看程式碼

    不過時間有限 功力不夠 

    可能沒辦法自己寫出一個完善的繪圖控件

    但還是很謝謝前輩開源的程式碼  

    讓我對於這一塊有更深的理解

    想順便問一下前輩

    我這幾天有在測試某些控件

    有用到teechart這個控件

    執行都沒問題

    但執行一陣子後會跳出  

    "

    類型 'System.IndexOutOfRangeException' 的未處理例外狀況發生於 TeeChart.dll

    其他資訊: 索引在陣列的界限之外。

    "

    的錯誤 落在    Application.Run(new Form1()); 的位子

    想請問這個問題有辦法解嗎

    我打開teechart.dll的封包裡面都是亂碼

    也不知道問題出在哪裡

    我的應用如下

      private void DrawData(float[] x_array, float[] y_array, float[] z_array)
            {
               
    
                            
                            if (tChart4.Series[0].Count == 20000)
                            {
                                tChart4.Series[0].Clear();
                            }
                             tChart4.Series[0].Add(x_array);
                           
                            if (tChart5.Series[0].Count == 20000)
                            {
                                tChart5.Series[0].Clear();
                            }
                                 tChart5.Series[0].Add(y_array);
               
                            if (tChart6.Series[0].Count == 20000)
                            {
                                tChart6.Series[0].Clear();
                            }
                                 tChart6.Series[0].Add(z_array);
                             
           
                        }
            

    傳入的是 float[1000]的陣列  

    我也有逐步看過 但還是找不到問題點 

    有可能是dll檔內的bug嗎

    2019年6月12日 上午 06:55
  • 關於teechart的問題你只能去問teechart,我沒有它的源碼也沒用過,無法得知是何原因。不過我猜測是快速寫入後導致的緩衝區寫入衝突。其實從這些控件的名字也可以看出,絕大部分的第三方產品都是chart,而不是示波器。 chart控件有的是,但示波器免費的且能達到所需性能的我沒見過,所以我才寫了這個示例。

    目前大部分免費示波器控件都是針對老式模擬串口的,模擬串口的BaudRate通常都不高,一秒鐘不到1KB的數據,但當前這種模擬串口已經很少見了,大部分都是USB虛擬串口。 USB虛擬串口的BaudRate可以隨意設,一秒鐘幾十、幾百KB都很普通,對於這種設備GDI繪圖肯定是不行的。

    GDI繪圖要想實現實時,唯一的方式就是像20年前老式遊戲那樣分層繪圖,每一次增加數據時只繪製新增的部分,但這樣的繪圖是無法縮放的,也無法全部刷新,當然更沒有什麼顏色、線條粗細、連接點樣式、反鋸齒、隨窗口縮放之類的功能。要在GDI中實現這些功能,那每秒60幀的情況下可能也就只能做到1000點了。所以說只要你看到繪圖部分是.Net開發的,而且沒有用Win2D,那根本不用試,肯定是做不到的,必須依靠顯卡來實現。

    另外一般真正的示波器並不是向左側滾動圖形,因為圖形快速滾動時根本無法查看,真實的示波器基本上都是掃描模式,比如醫院裡的心電圖監控等,這種模式只刷新掃描線左側的一小部分區域,其它部分的波形不變,這樣才能看得清。要實現掃描模式,GDI就更難以做到了。

    如果你必須選擇GDI控件,那隻能放棄一部分數據,將數據量縮減到1000點以下。或者放棄縮放等功能。否則我也沒有其它好辦法。這個問題不只是你一個人遇到,之前很多人有同樣的問題,但不借助C++ & DirectX或C# & Win2D都無解。此外你也可以重新發帖,看看別人是不是能給你一些幫助。

    2019年6月12日 上午 08:06
  • 對於我一個月前提出的問題

    在此提出我最後的解法

    其中問題點有兩點

    1.資料處理的方式 我原本是將完整的8byte讀出後invoke出去才計算值 導致效能不好

    後來改成前輩利用在接收時 直接利用<< 移位的方式在接收資料時就計算完 最後只把資料丟出去

    2.畫圖的方式

    上述前輩提到的眾多種畫圖方式都對我有很大的啟發 也都是很好的榜樣

    但最終我找到解決的方法是回歸 c#內建的chart  其中有一個屬性是 DataBindXY

    利用資料綁定的方式 改變我綁定的資料 圖也就會跟著改變了

    但如果是大數據的話 每一點改變一次還是會卡

    所以我利用迴圈的方式 讓我的資料每改1000點 才變更一次圖

    至於動態曲線圖的方式 我是利用 queue序列的方式  (先進先出)  

    設定你要的queue大小 然後在資料滿的時候

    利用dequeue() 刪除最前面的資料 再利用enqueue()加入資料到最後面

    承如我上面所說 因為大數據時一點一點改變就變更圖面還是會卡

    我將queue的大小設為10000  資料滿的時候 就invoke到Chart畫圖

    之後利用迴圈 一次dequeue 1000個點 這時queue.count就只剩9000

    而當資料又存回10000時 又會在畫一次 達到每變動1000筆畫一次圖 且可視10000筆資料

     在此提供我最後的解法讓大家參考

     當數據量更大 要求更高的時候  這個辦法沒辦法滿足各位的時候

    其他前輩的方法也都是可行的

    謝謝各位前輩這一個月的幫助

    • 已標示為解答 minchieh-Lin 2019年6月13日 下午 01:06
    2019年6月13日 下午 01:05