Получение дополнительного байта в сообщениях UART - PullRequest
2 голосов
/ 27 апреля 2020

Я участвовал в проекте, где мне нужно извлечь некоторые данные из устройства и отобразить их на P C, чтобы проверить. Устройство, с которого я получаю данные, отправляет строку, которая включает идентификатор устройства, текущий режим, показания температуры и показания батареи. Для простоты они разделены запятой. Примером строки может быть:

01,03,66661242,28

, поэтому это будет идентификатор устройства 1, режим 3, показание температуры 36,6 (это в формате ASCII с прямым порядком байтов), а уровень заряда батареи составляет 4,0 В (отправляется в формате ASCII и делится на 10)

Я не контролирую формат данных, отправляемых

Я использую плату STM32F091R C 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 раза просто в качестве эксперимента, чтобы посмотреть, смогу ли я обойти неверные данные, но ничего из этого не помогло. Я пытался использовать разные выводы UART (в устройстве STM32F091R C 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

Есть ли первый способ убедиться, что данные декодируются правильно с первого раза, или что данные читаются правильно первый раз вместо необходимости обходного пути?

Ответы [ 2 ]

1 голос
/ 27 апреля 2020

Это отвечает вашей проблеме:

При написании кода GetData() печатает данные «Device ID», «Mode», «Temp» и «Bat» из ранее полученных данных, а не из самых последних полученных данных. Вот почему ваш первый набор данных - все нули; в первый раз все эти переменные все еще содержат свои исходные неинициализированные значения, которые, поскольку они статически размещаются в «глобальных данных», представляют собой все нули. И через секунду он печатает результаты, полученные из чтения first , что дает неверное значение для «идентификатора устройства» из-за дополнительного нуля в начале потоковый. Наконец, в третий раз он печатает данные, полученные при чтении 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);
}

Вы можете также получите лучшие результаты, если при запуске вы перенаправите sh ваш входящий поток данных (прочитайте и откажитесь от любого существующего буферизованного мусора), что может решить вашу проблему с дополнительным символом при первом чтении. Но если между обоими концами вашей линии связи есть условие запуска при запуске, вы можете обнаружить, что (возможно, иногда) ваш получатель начинает обрабатывать первую выборку в середине символов пакета, и, возможно, операция flu sh отбросила бы Первая часть пакета. Хотя первоначальный грипп sh является хорошей идеей, еще более важно иметь надежный способ проверки каждого пакета.

Это дополнительный комментарий о вашей ситуации:

В своем комментарии MartinJames прав, хотя, возможно, немного туповат. Последовательные потоки данных без четко определенных протокольных пакетов общеизвестно ненадежны, и регистрация данных через такой интерфейс может привести к ошибочным данным, что может иметь серьезные последствия, если вы проводите исследования или разработку результирующего набора данных. Более надежная система сообщений может начинать каждый «пакет» с известного символа или пары символов, так же, как полезный механизм resyn c: если ваш поток байтов выходит из-под синхронизации c, символ resyn c ( или пара) поможет вам быстро и легко вернуться в строй c. В вашем случае, поскольку вы читаете данные 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,

Сможет ли ваш код повторно синхронизироваться c после примера, в котором пропущено несколько символов? Как насчет того, в котором есть мусор? Ваш код должен иметь возможность разбивать последовательный поток на части, начиная с одного символа (или пары) разделителя и заканчивая непосредственно перед следующим в последовательном потоке. И он должен исследовать символы между ними и проверять, что они «имеют смысл» каким-то образом, и быть способным отклонить любой образец, который не проверяет ОК. То, что он делает, может зависеть от потребностей вашего конечного потребителя данных: возможно, вы можете просто выбросить образец и все еще быть в порядке. Или, может быть, вам следует повторить последний хороший образец, пока не дойдете до следующего хорошего. Или, возможно, вам следует подождать до следующего хорошего и затем линейно интерполировать, чтобы найти разумные оценки того, какими должны быть данные между этими хорошими выборками.

В любом случае, как я уже сказал, вам нужен какой-то способ проверки каждый образец данных. Если длина «пакета» (выборки данных) может варьироваться, то каждый пакет должен содержать некоторое указание относительно количества байтов в нем, поэтому, если вы получаете больше или меньше, вы знаете, что пакет плохой. (Кроме того, если длина неоправданна, вы также знаете, что данные неверны, и вы не позволяете алгоритму сбора данных обмануть ошибочный байт, что ваш следующий пакет равен 1. 8 гигабайт в длину ... что, вероятно, приведет к sh вашей программе, так как ваш буфер приема не такой большой.) Наконец, должна быть какая-то система контрольных сумм для всех данных в пакете; 16-битная аддитивная контрольная сумма будет работать, но CR C будет лучше. Генерируя эти метаданные пакета на отправляющей стороне и проверяя их на принимающей стороне, вы (по крайней мере, с высокой вероятностью) гарантируете достоверность своего набора данных.

Но, как вы сказали, вы не можете контролировать формат передаваемых данных. И это позор; как сказал MartinJames, тот, кто разработал протокол, похоже, не понимал ненадежность простых последовательных потоков. Поскольку вы не можете это изменить, вам просто нужно приложить все усилия, чтобы найти некоторые эвристические методы для проверки данных; возможно, вы заставите свой код запомнить последние 5 выборок в массиве и сравнить каждый новый с последними 5 предполагаемыми действительными выборками; если вы получаете значение, которое находится за пределами разумного изменения из предыдущих примеров, вы выбрасываете его и ждете следующего. Или придумайте свою собственную эвристику. Просто убедитесь, что ваша эвристика не приведет к аннулированию всех будущих выборок, если фактическое измеренное значение изменяется слишком быстро.

1 голос
/ 27 апреля 2020

У вас, кажется, нет разделителей сообщений, указывающих начало или конец потока сообщений. Я предполагаю, что это потому, что вы работаете только с получением ASCII-данных.

Один из вариантов, который вы могли бы посмотреть, это использовать strtok для разделения данных на строки (используя ',' в качестве разделителя).

Проверьте, что в вашем массиве возвращено 4 строки.

Затем для первого блока просто используйте atoi для преобразования в целое число. Выполнение этих «001» и «01» должно конвертировать в 1.

В идеале вы должны проверить формат сообщения при получении, если вы не получили полное сообщение, но из того, что я вижу здесь пока что действительно не нужно. Просто проверьте формат каждой строки, например, если они содержат нечисловые c символы, когда следует отбросить данные до этой точки включительно.

edit

Я не понял, как закодирован 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");

    }

}
...