在 UART 通信中接收额外字节
Recieving extra byte in UART comms
我参与了一个项目,我需要从设备中提取一些数据并将其显示在 PC 上以供检查。我从中接收数据的设备发送一个字符串,其中包括设备 ID、当前模式、温度读数和电池读数。为方便起见,这些用逗号分隔。字符串的一个例子是:
01,03,66661242,28
所以这意味着设备 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 函数 3 次作为实验,看看我是否可以绕过不正确的数据但是 none 其中有帮助。我尝试使用不同的 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
有什么方法可以确保数据第一次被正确解码,或者数据第一次被正确读取而不需要解决方法?
您似乎没有任何消息定界符来指示消息流的开始或结束。我认为这是因为您只处理接收 ASCII 数据。
一种选择是使用 strtok
将数据拆分为字符串(使用 ','
作为分隔符)。
测试数组中是否返回了 4 个字符串。
然后对于第一个块只需使用 atoi
转换为整数。这样做“001”和“01”应该都转换为 1。
理想情况下,您应该在接收时检查邮件的格式,以防您没有收到完整的邮件,但据我目前所见,这并不是真正必要的。只需检查每个字符串的格式,例如如果它们包含非数字字符,则应丢弃直到并包括该点的数据。
编辑
我不明白 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”、“模式”、“温度”和“蝙蝠”数据获取的数据,而不是来自最近获取的数据。这就是为什么您的第一组数据全为零;在第一次通过时,所有这些变量仍然包含它们原始的未初始化值,因为它们是静态分配的“全局数据”,所以这些值全为零。 second time through, it's printing the results you obtained from the first reading,这给了你错误的“设备ID”值,因为字节流开头的额外零。最后,第三次,它打印了你从 second 阅读中获得的数据,这很好。
如果您只是重新排列部分代码,它会根据最近的样本打印数据。我没有尝试编译 & 运行 它,但在我看来这可能是对它的一个很好的重写,它将你的 DecodeString()
和 GetData()
函数组合成一个函数:
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);
}
如果在启动时刷新传入数据流(读取并丢弃任何现有的缓冲垃圾),您也可能会获得更好的结果,这可能会解决您第一次读取时的额外字符问题。但是如果你的通信两端之间存在启动竞争条件link,你可能会发现(可能偶尔)你的接收器开始处理数据包字符中间的第一个样本,并且可能那个刷新操作会丢弃数据包的第一部分。虽然初始刷新是个好主意,但更重要的是您有一种可靠的方法来验证每个数据包。
这是对您的情况的补充评论:
在他的评论中,MartinJames 是正确的,尽管可能有点直率。众所周知,没有明确定义的数据包协议的串行数据流是不可靠的,通过此类接口记录数据很可能会产生错误数据,如果您正在对结果数据集进行研究或工程设计,这可能会产生严重后果。一个更健壮的消息系统可能会以已知字符或字符对开始每个“数据包”,就像有用的重新同步机制一样:如果您的字节流不同步,重新同步字符(或对)可以帮助您恢复同步快速轻松。在你的情况下,因为你正在阅读 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,
您的代码能否在缺少多个字符的示例后重新同步?那里面有垃圾的呢?您的代码需要能够将串行流分成以一个分隔符(或一对)开始并在串行流中的下一个分隔符之前结束的块。它应该检查之间的字符并以某种方式验证它们“有意义”,并能够拒绝任何未检查为 OK 的样本。然后它的作用可能取决于您的数据最终消费者的需求:也许您可以扔掉样本并仍然可以。或者,也许您应该重复上一个好的样本,直到找到下一个好的样本。或者,也许您应该等到下一个好的样本,然后进行线性插值,以找到这些好的样本之间的数据应该是什么的合理估计值。
无论如何,正如我所说,您需要一些方法来验证每个数据样本。如果“数据包”(数据样本)长度可以变化,那么每个数据包都应该包含一些关于其中字节数的指示,所以如果你得到或多或少,你就知道这个数据包是坏的。 (此外,如果长度不合理,您也知道数据是错误的,并且您不会让您的数据收集算法被错误字节所愚弄,即您的下一个数据包长 1.8 GB……这可能会使您的程序崩溃,因为您的接收缓冲区没有那么大。)最后,数据包中的所有数据都应该有某种校验和系统; 16 位附加校验和可以工作,但 CRC 会更好。通过在发送端生成此数据包开销元数据并在接收端对其进行验证,您(至少在一定程度上)保证了数据集的有效性。
但是正如您所说,您无法控制传输数据的格式。真可惜;正如 MartinJames 所说,设计该协议的人似乎并不了解简单串行字节流的不可靠性。由于您无法更改它,因此您只需要尽最大努力找到一些启发式方法来验证数据;也许你让你的代码记住数组中的最后 5 个样本,并将每个新样本与最后 5 个假设有效的样本进行比较;如果你从前面的样本中得到一个超出合理变化范围的值,你就把它扔掉并等待下一个。或者想出你自己的启发式方法。如果实际测量值变化太快,请确保您的启发式方法不会导致您使所有未来样本无效。
我参与了一个项目,我需要从设备中提取一些数据并将其显示在 PC 上以供检查。我从中接收数据的设备发送一个字符串,其中包括设备 ID、当前模式、温度读数和电池读数。为方便起见,这些用逗号分隔。字符串的一个例子是:
01,03,66661242,28
所以这意味着设备 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 函数 3 次作为实验,看看我是否可以绕过不正确的数据但是 none 其中有帮助。我尝试使用不同的 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
有什么方法可以确保数据第一次被正确解码,或者数据第一次被正确读取而不需要解决方法?
您似乎没有任何消息定界符来指示消息流的开始或结束。我认为这是因为您只处理接收 ASCII 数据。
一种选择是使用 strtok
将数据拆分为字符串(使用 ','
作为分隔符)。
测试数组中是否返回了 4 个字符串。
然后对于第一个块只需使用 atoi
转换为整数。这样做“001”和“01”应该都转换为 1。
理想情况下,您应该在接收时检查邮件的格式,以防您没有收到完整的邮件,但据我目前所见,这并不是真正必要的。只需检查每个字符串的格式,例如如果它们包含非数字字符,则应丢弃直到并包括该点的数据。
编辑
我不明白 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”、“模式”、“温度”和“蝙蝠”数据获取的数据,而不是来自最近获取的数据。这就是为什么您的第一组数据全为零;在第一次通过时,所有这些变量仍然包含它们原始的未初始化值,因为它们是静态分配的“全局数据”,所以这些值全为零。 second time through, it's printing the results you obtained from the first reading,这给了你错误的“设备ID”值,因为字节流开头的额外零。最后,第三次,它打印了你从 second 阅读中获得的数据,这很好。
如果您只是重新排列部分代码,它会根据最近的样本打印数据。我没有尝试编译 & 运行 它,但在我看来这可能是对它的一个很好的重写,它将你的 DecodeString()
和 GetData()
函数组合成一个函数:
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);
}
如果在启动时刷新传入数据流(读取并丢弃任何现有的缓冲垃圾),您也可能会获得更好的结果,这可能会解决您第一次读取时的额外字符问题。但是如果你的通信两端之间存在启动竞争条件link,你可能会发现(可能偶尔)你的接收器开始处理数据包字符中间的第一个样本,并且可能那个刷新操作会丢弃数据包的第一部分。虽然初始刷新是个好主意,但更重要的是您有一种可靠的方法来验证每个数据包。
这是对您的情况的补充评论:
在他的评论中,MartinJames 是正确的,尽管可能有点直率。众所周知,没有明确定义的数据包协议的串行数据流是不可靠的,通过此类接口记录数据很可能会产生错误数据,如果您正在对结果数据集进行研究或工程设计,这可能会产生严重后果。一个更健壮的消息系统可能会以已知字符或字符对开始每个“数据包”,就像有用的重新同步机制一样:如果您的字节流不同步,重新同步字符(或对)可以帮助您恢复同步快速轻松。在你的情况下,因为你正在阅读 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,
您的代码能否在缺少多个字符的示例后重新同步?那里面有垃圾的呢?您的代码需要能够将串行流分成以一个分隔符(或一对)开始并在串行流中的下一个分隔符之前结束的块。它应该检查之间的字符并以某种方式验证它们“有意义”,并能够拒绝任何未检查为 OK 的样本。然后它的作用可能取决于您的数据最终消费者的需求:也许您可以扔掉样本并仍然可以。或者,也许您应该重复上一个好的样本,直到找到下一个好的样本。或者,也许您应该等到下一个好的样本,然后进行线性插值,以找到这些好的样本之间的数据应该是什么的合理估计值。
无论如何,正如我所说,您需要一些方法来验证每个数据样本。如果“数据包”(数据样本)长度可以变化,那么每个数据包都应该包含一些关于其中字节数的指示,所以如果你得到或多或少,你就知道这个数据包是坏的。 (此外,如果长度不合理,您也知道数据是错误的,并且您不会让您的数据收集算法被错误字节所愚弄,即您的下一个数据包长 1.8 GB……这可能会使您的程序崩溃,因为您的接收缓冲区没有那么大。)最后,数据包中的所有数据都应该有某种校验和系统; 16 位附加校验和可以工作,但 CRC 会更好。通过在发送端生成此数据包开销元数据并在接收端对其进行验证,您(至少在一定程度上)保证了数据集的有效性。
但是正如您所说,您无法控制传输数据的格式。真可惜;正如 MartinJames 所说,设计该协议的人似乎并不了解简单串行字节流的不可靠性。由于您无法更改它,因此您只需要尽最大努力找到一些启发式方法来验证数据;也许你让你的代码记住数组中的最后 5 个样本,并将每个新样本与最后 5 个假设有效的样本进行比较;如果你从前面的样本中得到一个超出合理变化范围的值,你就把它扔掉并等待下一个。或者想出你自己的启发式方法。如果实际测量值变化太快,请确保您的启发式方法不会导致您使所有未来样本无效。