簡體   English   中英

在 UART 通信中接收額外的字節

[英]Recieving extra byte in UART comms

我參與了一個項目,我需要從設備中提取一些數據並將其顯示在要檢查的 PC 上。 我從中接收數據的設備會發送一個字符串,其中包括設備 ID、當前模式、溫度讀數和電池讀數。 為方便起見,這些用逗號分隔。 字符串的一個例子是:

01,03,66661242,28

所以這將 bean 設備 ID 為 1,模式為模式 3,溫度讀數為 36.6(這是 ASCII 小端格式),電池電量為 4.0V(以 ASCII 發送並除以 10)

我無法控制數據發送的格式

我為此使用了 STM32F091RC Nucleo 板,我擁有的代碼是:

#include "mbed.h"

Serial pc(PA_2, PA_3);
Serial Unit (PA_9, PA_10, 9600);                                                // 9600 baud rate - no parity - 1 stop bit

//Input pins
DigitalIn START(PB_8, PullUp);

void GetData();
void CheckData();

char Data[100];

int deviceId;
int Mode;
float TempReading;
float battReading;

unsigned char Ascii2Hex (unsigned char data)
{
    if (data > '9')data += 9;   // add offset if value > 9
    return (data &= 0x0F);
}

unsigned char Ascii2Char(unsigned char Offset)
{
    unsigned char Ans;
    Ans = Ascii2Hex(Data[Offset]);
    Ans = Ans<<4;
    Ans += Ascii2Hex(Data[Offset+1]);
    return(Ans);
}

float Ascii2Float(unsigned char Offset)
{
    float Bob;
    unsigned char Ans;
    Ans = Ascii2Hex(Data[Offset+6]);
    Ans = Ans<<4;
    Ans += Ascii2Hex(Data[Offset+7]);
    ((unsigned char*)&Bob)[3]= Ans;
    Ans = Ascii2Hex(Data[Offset+4]);
    Ans = Ans<<4;
    Ans += Ascii2Hex(Data[Offset+5]);
    ((unsigned char*)&Bob)[2]= Ans;
    Ans = Ascii2Hex(Data[Offset+2]);
    Ans = Ans<<4;
    Ans += Ascii2Hex(Data[Offset+3]);
    ((unsigned char*)&Bob)[1]= Ans;
    Ans = Ascii2Hex(Data[Offset]);
    Ans = Ans<<4;
    Ans += Ascii2Hex(Data[Offset+1]);
    ((unsigned char*)&Bob)[0]= Ans;
    return(Bob);
}

void DecodeString()
{
    char x;

    //numbers in brackets is where the data starts in the string
    deviceId = Ascii2Char(0);
    Mode = Ascii2Char(3);
    TempReading = Ascii2Float(6);
    x = Ascii2Char(15);
    battReading = (float)x/10;
    GetData();
}

void GetData()
{
    Unit.scanf("%s,",Data);  // scan the incoming data on the RX line
    pc.printf("%s,\n\r",Data);
    pc.printf("Device ID = %i\n\r", deviceId);
    pc.printf("Mode = %i\n\r", Mode);
    pc.printf("Temp = %.1f\n\r", TempReading);
    pc.printf("Bat = %.1f\n\n\r", battReading);
}

int main()
{
    while(1) {
        if(START == 0) {
            wait(0.1);
            DecodeString();
        }
    }
}

當我第一次啟動並按下按鈕獲取數據時,我收到的字符串前面多了一個 0:001,03,66661242,28

這意味着數據不正確,因為數據已經移動,但是,如果我再次按下它,它會給出正確的字符串,但打印的數據不正確再按下一次,一切正常,並將繼續工作,直到 Nucleo 板被重置。 從我的串行監視器收到的字符串和顯示數據的示例是:

001,03,33331342,28,
Device ID = 0
Mode = 0
Temp = 0.0
Bat = 0.0

01,03,CDCC1242,28,
Device ID = 0
Mode = 192
Temp = 0.0
Bat = 19.4

01,03,CDCC1242,28,
Device ID = 1
Mode = 3
Temp = 36.7
Bat = 4.0

不是專業的編碼員,我是一個初學者。 解碼字符串的代碼是由設計發送數據字符串的設備的工程師給我的。 我需要幫助,但由於在家工作並且人們忙於其他事情,這不是一個緊迫的問題,所以幫助是有限的。

我嘗試在各個地方添加一些延遲(例如在原始 scanf 之后和打印之前),並且我還嘗試了 scanf function 3 次,只是作為一個實驗,看看我是否可以繞過不正確的數據,但這些都沒有幫助。 我嘗試過使用不同的 UART 引腳(STM32F091RC 64 引腳器件有 6 個可用),但我仍然得到相同的結果。 我還將數據字節長度從 100 更改為 17,因為這是我期望收到的數量,但它仍然沒有區別。

我確保所有設備都共享一個公共 GND,並仔細檢查了所有硬件連接。

我想做的就是第一次收到正確的數據並第一次顯示正確的結果,但我似乎無法讓它工作。

編輯

我現在嘗試添加額外的幾行。 我正在使用strlen來計算字符串中的字節數。 如果超過 17,我會重試。 這樣就消除了第一個問題,但是第一組解碼后的數據還是顯示不正確:

String Length = 18

String Length = 17

01,03,66661242,28,
Device ID = 0
Mode = 192
Temp = 0.0
Bat = 19.4

String Length = 17

01,03,66661242,28,
Device ID = 1
Mode = 3
Temp = 36.6
Bat = 4.0

有什么方法可以首先確保第一次正確解碼數據,或者第一次正確讀取數據而不需要解決方法?

您似乎沒有任何消息分隔符來指示消息 stream 的開始或結束。 我認為這是因為您正在處理僅接收 ASCII 數據。

一種選擇是使用strtok將數據拆分為字符串(使用','作為分隔符)。

測試您的數組中是否返回了 4 個字符串。

然后對於第一個塊,只需使用atoi轉換為 integer。 這樣做“001”和“01”應該都轉換為1。

理想情況下,您應該在接收時檢查消息的格式,以防您沒有收到完整的消息,但從我目前在這里看到的情況來看,這並不是真正必要的。 只需檢查每個字符串的格式,例如它們是否包含非數字字符,然后應該丟棄直到並包括該點的數據。

編輯

我不明白 Temp 是如何編碼的,但我有這個示例代碼Temp 在這段代碼中不正確

#include <stdio.h>

#include <stdlib.h>
#include "string.h"

int main()
{
    char input[] = "001,03,66661242,28";

    char* pstr = strtok(input,",");
    int count =0;
    int ID =0;
    int Mode =0;
    double Temp =0.0;
    float Volt = 0.0;

    while(pstr!=NULL)
    {
        switch(count)
        {
            case 0:
                ID = atoi(pstr);
            break;
            case 1:
                Mode = atoi(pstr);
            break;

            case 2:
                Temp = strtod(pstr, NULL);
            break;

            case 3 :
                Volt = strtol(pstr, NULL ,16)/10;
            break;

        }
        printf("%s\n", pstr);
        pstr = strtok(NULL,",");
        count++;

    }

    if(count == 4)
    {
        printf("ID = %d\n", ID);
        printf("Mode = %d\n", Mode);
        printf("Temp = %.1f\n", Temp);
        printf("Voltage = %.1f\n", Volt);
    }
    else
    {
        printf("Error");

    }

}

這回答了你的問題:

按照您的代碼編寫方式, GetData()是從先前獲取的數據中打印“設備 ID”、“模式”、“溫度”和“蝙蝠”數據,而不是從最近獲取的數據中。 這就是為什么你的第一組數據是全零的; 在第一次通過時,所有這些變量仍然包含它們原始的未初始化值,因為它們是靜態分配的“全局數據”,所以它們都是全零的。 第二次,它打印您從第一次讀取獲得的結果,由於字節流開頭的額外零,這為您提供了錯誤的“設備 ID”值。 最后,第三次通過,它打印了您從第二次讀取中獲得的數據,這很好。

如果您只是重新排列一些代碼,它將根據最新的樣本打印數據。 我沒有嘗試編譯和運行它,但在我看來這可能是一個很好的重寫,它將你的DecodeString()GetData()函數組合成一個 function:

void DecodeString()
{
    char x;

    // scan the incoming data on the RX line
    Unit.scanf("%s,",Data);

    // translate the data from ASCII into int/float native types
    // numbers in brackets is where the data starts in the string
    deviceId = Ascii2Char(0);
    Mode = Ascii2Char(3);
    TempReading = Ascii2Float(6);
    x = Ascii2Char(15);
    battReading = (float)x/10;

    // print the original unprocessed input string
    pc.printf("%s,\n\r",Data);

    // print what we translated
    pc.printf("Device ID = %i\n\r", deviceId);
    pc.printf("Mode = %i\n\r", Mode);
    pc.printf("Temp = %.1f\n\r", TempReading);
    pc.printf("Bat = %.1f\n\n\r", battReading);
}

如果在啟動時刷新傳入數據 stream (讀取並丟棄任何現有的緩沖垃圾),您也可能會獲得更好的結果,這可能會解決您在第一次讀取時出現額外字符的問題。 但是,如果在您的通信鏈路的兩端之間存在啟動競爭條件,您可能會發現(可能偶爾)您的接收器開始處理數據包字符中間的第一個樣本,並且可能該刷新操作會丟棄第一部分數據包。 雖然初始刷新是一個好主意,但更重要的是您擁有一種驗證每個數據包的可靠方法。

這是關於您的情況的附加評論:

在他的評論中,MartinJames 是正確的,盡管可能有點生硬。 眾所周知,沒有明確定義的數據包協議的串行數據流是不可靠的,並且通過這樣的接口記錄數據可能會產生錯誤的數據,如果您正在對生成的數據集進行研究或工程,這可能會產生嚴重后果。 一個更健壯的消息系統可能會以已知字符或字符對開始每個“數據包”,就像一個有用的重新同步機制一樣:如果您的字節 stream 不同步,重新同步字符(或對)可以幫助您恢復同步快速輕松。 在您的情況下,因為您正在讀取 ASCII 數據,所以這是一個'\n'"\r\n" ,所以從這個角度來看,你很好,只要你真的做一些事情來啟動和停止這些數據樣本邊界。 如果您收到這樣的數據樣本會怎樣?...

01,03,CDCC1242,28,
01,03,CDCC1240,27,
01,03,CDCC1241,29,
01,03,CDCC1243,28,
01,03,CDCC123F,2A,
01,03,CD9,
01,03,CDCC1241,29,
01,03,CDCC1241,29,
01,0yĔñvśÄ“3,CDCC1243,28,
01,03,CDCC123F,2A,
01,03,CDCC1242,29,

在缺少幾個字符的示例之后,您的代碼是否能夠重新同步? 那里面有垃圾的呢? 您的代碼需要能夠將串行 stream 分解為以一個分隔符(或對)開始並在串行 stream 中的下一個字符之前結束的塊。 它應該檢查它們之間的字符,並以某種方式驗證它們是否“有意義”,並且能夠拒絕任何檢查不正確的樣本。 然后它會做什么可能取決於您的數據最終消費者的需求:也許您可以丟棄樣本並仍然可以。 或者也許你應該重復上一個好的樣本,直到你得到下一個好的樣本。 或者,也許您應該等到下一個好的樣本,然后進行線性插值,以找到這些好的樣本之間數據應該是什么的合理估計。

無論如何,正如我所說,您需要某種方法來驗證每個數據樣本。 如果“數據包”(數據樣本)長度可以變化,那么每個數據包都應該包含一些關於其中字節數的指示,所以如果你得到更多或更少,你就知道這個數據包是壞的。 (另外,如果長度不合理,你也知道數據是壞的,並且你不允許你的數據收集算法被你的下一個數據包長 1.8 GB 的壞字節所欺騙......這可能會使你的程序崩潰,因為你的接收緩沖區沒有那么大。)最后,應該有某種校驗和系統來處理數據包中的所有數據; 16 位加法校驗和可以工作,但 CRC 會更好。 通過在發送端生成此數據包開銷元數據並在接收端對其進行驗證,您(至少很有可能)保證數據集的有效性。

但是正如您所說,您無法控制傳輸數據的格式。 這是一種恥辱; 正如 MartinJames 所說,設計協議的人似乎並不了解簡單串行字節流的不可靠性。 既然你無法改變它,你只需要盡最大努力找到一些啟發式方法來驗證數據; 也許您讓您的代碼記住數組中的最后 5 個樣本,並將每個新樣本與最后 5 個假定有效樣本進行比較; 如果您得到的值超出了前面示例的合理更改范圍,則將其丟棄並等待下一個。 或者想出你自己的啟發式方法。 如果實際測量值變化太快,請確保您的啟發式方法不會導致您使所有未來的樣本無效。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM