• ,
  • вход на сайт

    Имя пользователя :
    Пароль :

    Восстановление пароля Регистрация
    Как передать параметры по UART

      Как передать один или несколько параметров, значений переменных по UARTу, особенно если нужно передать число с плавающей запятой? Копания форумов и литры выпитого чая дали несколько вариантов решения этого вопроса.

      Проблема возникла впервые, когда возникла потребность передать значение, например, 1,481. Первая мысль - перевести в строку, а потом распознать на принимающей стороне. Но в этом случае встал вопрос быстродействия алгоритма, которое завязано на скорости обработки входных данных, а их было как минимум по одному значению с одного канала из 3х. По этому для ATmega88 пришлось писать более шустрое решение. Рассмотрим подробнее задачу.

     Задача:

          - передать значение переменной типа float (в AVR-GCC float занимает 4 байта);

          - принять данные и сохранить данные того же типа на принимающей стороне;

          - обеспечить игнорирование битых пакетов;

          - обеспечить высокое быстродействие;

     Решение:

      Исходя из того, что в регистра передатчика/приемника имеет размер 1байт, то нужно разбить число с плавающей запятой и собрать в таком же порядке на стороне приемника. Для этого написал такую функцию:

    
     void tx_float(float a)
    {
      unsigned char *ad,i;
      ad=(unsigned char*)&a;
      for(i=0;i<4;i++)
      {
        delay_ms(1);
        putchar(*(ad++));
      }
    }
    

     

      Для меня осознание предназначения указателей некоторое время оставалось трудностью, оставлю здесь заметку для таких же. Кратко об указателях:

          - Указатель – это вид переменной, которая хранит адрес в памяти;

          - Объявляется указатель с помощью звездочки;

          - Чтобы обратиться к значению по адресу указателя используется разыменование;

          - У любой переменной можно взять адрес памяти. Для этого используется символ &;

          - Так как указатель есть адрес памяти, то ему можно присвоить адрес памяти от любой переменной;

          - У указателя двойственная природа. Можно обрабатывать адрес памяти, а можно значение внутри этого адреса;

    Для сборки числа обратно, была написана не менее простая функция:

    
    float char2float(char in_a, char in_b, char in_c, char in_d) // передаем принятые байты в том же порядке, что и принимали
    {
        char arr[4]= {in_a,in_b,in_c,in_d};
        float *p_out= arr;
        return *p_out;
    }
    

    Думаю здесь можно не комментировать. Для того чтобы отсеивать битые пакеты, просто передаю "заголовок" состоящий из 2х символов. Я обычно ставлю "A" и "z", так как они достаточно далеко находятся по таблице ANCI.

    Например:

    
    putchar('A');
    putchar('z');
    tx_float(Gx);
    

      А дальше просто отлавливаю в входном буфере этот заголовок и отсчитываю от него 4 байта. Можно конечно заморочиться с подсчитываем CRC8, но это на будущее.

     

      Рассмотрим второй вариант.

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

    Задача:

          - передать одно или несколько значений переменной типа float;

          - принять данные и сохранить данные того же типа на принимающей стороне;

          - игнорировать битые пакеты.

    Решение:

     Исходя из того, что второй вариант я реализовывал на STM32F105, то вычислительная мощность позволяла очень быстро преобразовывать float в string.

     Стандартной функции я не нашел, зато позаимствовал неплохое решение с какого-то англоязычного форума:

    
    void reverse(char *str, int len)
    {
        int i=0, j=len-1, temp;
        while (i<j)
        {
            temp = str[i];
            str[i] = str[j];
            str[j] = temp;
            i++; j--;
        }
    }
     
     // Converts a given integer x to string str[].  d is the number
     // of digits required in output. If d is more than the number
     // of digits in x, then 0s are added at the beginning.
    int intToStr(int x, char str[], int d)
    {
        int i = 0;
        while (x)
        {
            str[i++] = (x%10) + '0';
            x = x/10;
        }
     
        // If number of digits required is more, then
        // add 0s at the beginning
        while (i < d)
            str[i++] = '0';
     
        reverse(str, i);
        str[i] = '\0';
        return i;
    }
     
    // Converts a floating point number to string.
    void ftoa(float n, char *res, int afterpoint)
    {
        // Extract integer part
        int ipart = (int)n;
     
        // Extract floating part
        float fpart = n - (float)ipart;
     
        // convert integer part to string
        int i = intToStr(ipart, res, 0);
     
        // check for display option after point
        if (afterpoint != 0)
        {
            res[i] = '.';  // add dot
     
            // Get the value of fraction part upto given no.
            // of points after dot. The third parameter is needed
            // to handle cases like 233.007
            fpart = fpart * pow(10, afterpoint);
     
            intToStr((int)fpart, res + i + 1, afterpoint);
        }
    

     Функция работает хорошо и шустро, но заметил один баг, так что пока поставил костыль..(

    Баг заключается в том, что при значениях числа (float) <1 получаем результат не "0,541", а " ,541".

    Костыль:

    
    ftoa(range_result, rez_tx, 3);  // range_result = число с плавающей запятой,
                                       //rez_tx - выходная строка, 3 - число знаков после запятой
    Usart2_Send_String("Az");     // ф-ция передачи по UART, передаю заголовок
    if (range_result<1)
      {
        Usart2_Send_symbol(0x30);  // вручную дописываем символ "0"
      }
    Usart2_Send_String(rez_tx);
    

      Когда приняли всю строку, используя функцию substring() с библиотек Arduino или ее аналог в С++ "string substr(int position, int length);", получаем подстроку с интересующим нас значением. После этого преобразуем строку в значение.

    Приведу пример, написанный под Arduino:

    
    string input_string = "";
    float output = 0.0;
    // входящая строка:"Az1.252"
    if (input_string.substring(1,2) == "z") // Проверка: если второй символ строки "z"
                {
                  output=(input_string.substring(2)).toFloat();// все что после второго символа преобразовываем в float 
                }
    

      Более подробно о работе со строками - можно почитать в любом справочнике)

    И на последок приведу пример для распознавания нескольких параметров.

      Посылаем строку формата:
    «Начальный тег параметра + значение параметра+конечный тег параметра и т.д. »
    «T11251TT21262T»
    T1 — начальный тег параметра 1
    125 — значение параметра 1
    1T — конечный тег параметра 1
    T2 — начальный тег параметра 2
    126 — значение параметра 2
    2T — конечный тег параметра 2

    Принимаем данные на Arduino:

    
    Serial.begin(9600);
    String Var=""; //переменная для приема строки
    while (!Serial.available()) delay(20);//пока нет данных стоим
      delay(200);//ожидаем прием всей строки
    while (Serial.available()) //пока есть данные, читаем
      Var = Var + (char)(Serial.read());
    Serial.end();
    

     

    А теперь парсим строку:

    
    String T1, T2;
    T1=Var.substring(Var.indexOf("T1")+2,Var.indexOf("1T"));//копируем часть строки от начального до конечного тега
    T2=Var.substring(Var.indexOf("T2")+2,Var.indexOf("2T"));
    

    Получаем T1=125, T2=126.

    А дальше то же, что и во втором варианте.

     

    Удачи в начинаниях!)

    P.S. приношу свои извинения за возможную неточность в определениях)

     


    Теги:
    • 0
    Новость опубликована 18-09-2015, 21:56, её прочитали 3907 раз(а).
    Понравилась тема? Посмотрите эти:
    Информация
    Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.