[英]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.