繁体   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