Не стабильно работает Serial.read()

Тема в разделе "Arduino & Shields", создана пользователем neyasbltb_88, 30 янв 2017.

  1. neyasbltb_88

    neyasbltb_88 Нуб

    Нужно считывать циклом определенное количество символов из приходящей строки с COM-порта, обрабатывать эти символы, и считывать следующее количество и т.д.. Но при этом возникают различные чудеса)) В скетче пока прописано обратное возвращение на порт разбитых строк.
    Например, вот этот текст я отправляю через монитор порта:
    Lorem Ipsum - это текст-"рыба", часто используемый в печати и вэб-дизайне. Lorem Ipsum является стандартной "рыбой" для текстов на латинице с начала XVI века. В то время некий безымянный печатник создал большую коллекцию размеров и форм шрифтов, используя Lorem Ipsum для распечатки образцов. Lorem Ipsum не только успешно пережил без заметных изменений пять веков, но и перешагнул в электронный дизайн. Его популяризации в новое время послужили публикация листов Letraset с образцами Lorem Ipsum в 60-х годах и, в более недавнее время, программы электронной вёрстки типа Aldus PageMaker, в шаблонах которых используется Lorem Ipsum.
    А в ответе получаю:
    L
    -------------------
    Strok: 1
    Loop: 1
    -------------------
    o
    r
    em
    Ipsum
    - это
    текст-"ры
    ба", часто
    используемый
    в печати и вэб-
    дизайне. Lorem Ip
    sum является станда
    ктной "рыбой" для те
    ачал на латинице с н

    мя нI века. В то вре
    тник безымянный печа
    екцидал большую колл
    ифтозмеров и форм шр
    psumспользуя Lorem I
    зцов распечатки обра
    ько rem Ipsum не тол
    замешно пережил без
    веко изменений пять
    элео и перешагнул в
    попнный дизайн. Его
    ремяизации в новое в
    я лилужили публикаци
    зцам Letraset с обра
    годrem Ipsum в 60-х
    ее в, в более недавн
    трон, программы элек
    us вёрстки типа Ald
    х коMaker, в шаблона
    oremх используется L!
    Ipsum.спользуется L"
    -------------------
    Strok: 34
    Loop: 34
    -------------------

    Строки делятся не понятно как, возникают лишние символы, или наоборот, исчезают нужные. Испробовал разные циклы, но каждый раз своя странность возникает. Может кто-нибудь здесь занимался текстом на Ардуино, и сможет подсказать на какие подводные камни я натыкаюсь?
    Код (C++):
    int strok=0;
    int nloop=0;
    const int widthScreen = 21;
    char incomingByte[widthScreen-1];
    String incomingBytes;
    boolean flag1 = false;
    boolean flag2 = false;

    void setup() {
    Serial.begin(9600);
    }

    if (Serial.available() > 0)
       {
        nloop++;
          flag1=true;
          int i=0;
          while(Serial.available())
            {
              incomingByte[i] = Serial.read();
              i++;
              if (i==widthScreen-1) i=0;
            }
                  strok++;
          Serial.println(incomingByte);
            }

    if (flag1 && Serial.available() <= 0)
    flag2=true;


    if(flag1&&flag2)
      {
    Serial.println("-------------------");
    Serial.print("Strok: ");
    Serial.println(strok);
    Serial.print("Loop: ");
    Serial.println(nloop);
    strok=0;
    nloop=0;
    Serial.println("-------------------");
        flag1=false;
        flag2=false;
      }
    }
     
    Последнее редактирование: 30 янв 2017
  2. rkit

    rkit Гуру

    У вас вывод сразу, как только входной буфер опустеет, а не когда заполнится строка. Вообще чересчур сложно как-то.
    Код (C++):
    static int pos = 0;
    char x = Serial.read();
    if (x) Serial.write(x);
    pos++;
    if (pos == widthScreen) {
      Serial.write('\n');
      pos = 0;
    }
    Что-то в таком духе достаточно.
     
    Последнее редактирование: 30 янв 2017
  3. neyasbltb_88

    neyasbltb_88 Нуб

    Да вот в том то и дело, что это пока у меня используется для отслеживания того, как будут заполняться строки. Немного подробнее объясню. Я хочу сделать нормальное отображение текста экране, а не вот это вот:
    [​IMG]
    Дело в том, что экран сам разбивает текст, (в данном случае по 14 символов, а позже, я планирую использовать OLED дисплей, где на строку будет умещаться 21 символ) и он становится плохо читаемым.
    В компиляторе я уже написал функцию, которая следит за наполнением строки, и правильно расставляет переносы: https://code.sololearn.com/cYZpnPwBjBxo/#cpp
    [​IMG]
    Но я уже изначально понимал, что эта функция не оптимизирована, так как текст преобразовывается трижды, как следствие, это занимает время и много памяти. Что, собственно, и вылезло при переносе на Ардуино. Но зато на выходе получается очень симпатичный двухмерный построчный массив. С коротким текстом оно работает хорошо, НО только первый раз. При повторной отправке того же текста опять начинают вылезать странные вещи. Если отправлять достаточно длинный текст, символов 300, то косяки начинают всплывать сразу. Поэтому я хотел сделать что-то типа буфера на 2 строки. Получаем 28 символов, обрабатываем в 2-3 строки двухмерного массива, затем считываем следующие 28 символов, и продолжаем заполнять массив с правильной расстановкой переносов. Может быть есть более простой способ все это провернуть, но я не знаю как, поэтому и прошу помощи. Думаю, что это будет актуально для всех, кому нужно будет выводить тот или иной текст на экран.
     
  4. sslobodyan

    sslobodyan Гик

    У вас немного странноватый алгоритм, который не будет работать с длинным текстом. Поясню.
    Скорость порта выбрана достаточно низкая 9600. У дуньки сделан кольцевой приемный буфер, куда сначала и попадает весь входящий поток. Как только придет первый байт, available сразу же взведется и ваша прога ринется вычитывать буфер. Естественно, что она очень резво этот буфер вычитает, потому что более ничем не занимается, и перепрыгнет на следующую строку. Вариант, который предложил rkit будет оптимальным при условии, что слово можно разрезать в любом месте. Если же необходимо строку делить по пробелам, то вам надо определять конец посылки не только по available, а еще подождать время, достаточное для передачи пары слов и только после этого выводить последнюю строку.
     
  5. rkit

    rkit Гуру

    Да и с переносом слова всё довольно просто. Завести два чередующихся буфера, и переносить слово в следующий, если места не хватило.
     
    sslobodyan нравится это.
  6. neyasbltb_88

    neyasbltb_88 Нуб

    sslobodyan, на счет алгоритма не спорю, я в программировании новичок, и когда только задумал его, то мне реализация показалась нереально сложной, но понемногу удалось добиться поставленной цели.
    Да, именно для этого я и затеял все это. С простым получением текста не плохо справляется вот такая функция:
    Код (C++):
    while(Serial.available())
              {
                incomingBytes = Serial.readString();
              }
    Я попробовал отказаться на дуине от финального преобразования в двухмерный массив, что сэкономило половину оперативы. Сейчас максимальная длина приходящего текста сама ограничивается 1070 символами.

    В общем то, это уже приемлемо. НО... опять есть но)) Несколько раз подряд отправляя один и тот же текст, периодически бывает такое, что приходит только первые 64 байта, это не каждый раз, но не приятно. Подозреваю, что входящий буфер как раз и составляет эти 64 байта. Еще из минусов этого - неудобнее делать прокрутку. Сейчас текст является одной строкой, и я сделал уход Y координаты за границу экрана. К тому же, не совсем точно определяется количество занимаемых строк. В итоге, пара строк может остаться за экраном, или же наоборот, финальная строка слишком высоко уползти. При этом, скорее всего не получится выводить текст, скажем, только на половине экрана. Имея же текст в массиве, можно на нужные координаты подставлять нужные строки.
    Код (C++):
    #include <SPI.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_PCD8544.h>
    //#include <cstring>
    //#include <string>

    using namespace std;

    //(CLK, DIN, DC, CE, RST)
    Adafruit_PCD8544 display = Adafruit_PCD8544(8, 9, 10, 11, 12);
    int Vcc = 7;
    int Bl = 6;
    int Gnd = 5;

    int widthScreen = 14;
    //long int incomingInt = 0;
    char incomingBytes1;
    char incomingByte;
    int i = 0;
    boolean flag = false;
    boolean flag2 = false;
    String result;
    int incomingBytes_length;
    boolean spaceFlag = false;
    int stroki=0;

    void setup() {
    pinMode(Vcc, OUTPUT);
    digitalWrite(Vcc, HIGH);
    pinMode(Bl, OUTPUT);
    analogWrite(Bl, 150);
    pinMode(Gnd, OUTPUT);
    digitalWrite(Gnd, LOW);
    Serial.begin(19200);
    display.begin();
    display.cp437(true);
    display.setContrast(50);
    display.setTextColor(BLACK);
    display.clearDisplay();
    display.display();

    }

    void loop() {

    if (Serial.available() > 0)
       {
        String incomingBytes;
        delay(1);
            flag2 = true;
            stroki=0;
            while(Serial.available())
              {
                incomingBytes = Serial.readString();
                delay(1);
              }
            incomingBytes_length = incomingBytes.length();
            Serial.println(incomingBytes_length);
            Serial.println("--------------");

    //for(int c=0; c<6; c++)
    //incomingBytes=""; //Очистка строки полностью
    //Massiv[0]=0; //Очистка массива
    //memset(arr, 0, sizeof(arr));
    //memset(mas, 0, sizeof mas);
    //memset(mas, 0, sizeof(int)*N); //Обнулит первые N элементов целочисленного массива.
    //for (int i=0; i<n; i++) mas[i] = 0; // обнуляем массив состоящий из n элементов
    //incomingBytes[c]='\r'; //!!!!!_Очищает определенный символ строки

    incomingBytes[0]='\r';
    //Тут код из онлайн
    if (incomingBytes_length > widthScreen)
            {
                int j = -1;
                for (int i = 0; i < incomingBytes_length; i++)
              {
                if ( incomingBytes[i]==0 && i!=0) stroki= stroki+1;
                j++;
                if(j == widthScreen)
                {
                    for (int k=widthScreen; k > 0; k--)
                        {
                            if ( incomingBytes[i+1] == ' ' && incomingBytes[i] != ' ' )
                            {
                                incomingBytes[i+1] = '\r';
                                j = 0;
                                i = i+2;
                                stroki= stroki+1;
                             break; }
                            if (incomingBytes[i-(widthScreen-k)] == ' ')// && k != widthScreen)
                                {
                                    incomingBytes[i-(widthScreen-k)] = '\n';
                                    j = 0;
                                    i = i-(widthScreen-k);
                                    stroki= stroki+1;
                                    break;
                                }
                            j = 0;
                        }
                }
               
              }
             
             
            }
    //Тут код из онлайн закончился
    //char*incomingBytes1=&incomingBytes[0];



    if (flag || flag2)
    {
    Serial.println(incomingBytes);
    Serial.println("--------------");
    Serial.print(stroki);
    Serial.println(" Strok");
    int sCount=-1;
    int sC=0;
    for(int s=1; s<=stroki*8-8; s++)
        {
          if (Serial.available() > 0)
          {
            incomingBytes="";
            delay(0);
            break;
          }
          sCount++;
            display.clearDisplay();
    //        display.setCursor(0, (0-(s*8)-8));
            display.setCursor(0, (2-s*2));
            display.setTextSize(1);
            display.print(incomingBytes);
           
            display.display();
            if (sCount==0)
              {
                delay(2400);
              }
            if (sCount==4)
              {
                sCount=0;
                delay(800);
              }
            else
            delay(0);
           
        }
            flag = false;
            flag2 = false;
    }
       }
    }//Конец loop--------------------------------------------------
     
     
  7. neyasbltb_88

    neyasbltb_88 Нуб

    А можно поподробнее? Кажется, я совсем не понял как это должно выглядеть.
     
  8. sslobodyan

    sslobodyan Гик

    Попробуйте проще. Читайте из сериала в свой буфер до получения пробела. Если количество считанных букв меньше чем остаток в строке, то выводите этот буфер на экран и обнуляете указатель. Потом продолжаете считывать сериал в свой буфер (начиная от начала буфера) до получения пробела. Опять смотрим длину слова в буфере. Если она больше остатка места в строке - перевод каретки и вывод слова из буфера в новую строку.
    Свой буфер должен быть больше самого длинного слова.
    Не забудьте о скорости приема и вывода - они разные. Размер кольцевого буфера самого сериала по умолчанию 64 байта, но это настраивается.
     
  9. neyasbltb_88

    neyasbltb_88 Нуб

    Т.е. сразу по одному слову собирать двухмерный массив? Если так, то это действительно должно лучше работать, но как сделать считывание буфера сериал до пробела?
     
  10. sslobodyan

    sslobodyan Гик

    Нет, зачем двумерный? Обычный одномерный массив. Примерно вот так, но не проверял. Сама идея.
    Код (C++):
    N = 0;
    char mas[50];
    uint32_t t; // отслеживаем тайmаут
    byte ostatok;
    byte max_len=20; // длина строки

    t = millis() + 500;

    ostatok = max_len;

    while ( millis() < t ) {

        while ( Serial.available() ) {
            t = millis() + 500;
            mas[N] = Serial.read();
            if (mas[N] == ' ') {
                // считали пробел
                do_print_new_word_from_mas();
                N = 0;
            } else {
                N = N + 1;
                if (N == sizeof(mas)) {
                    // переполнение буфера - выходим из цикла
                    t = millis();
                }  
            }
        }

    }

    function do_print_new_word_from_mas() {
        if ( ostatok >= N-1 ) {
            // еще выводим в текущую строку
            for (byte i=0; i<N; i++) Serial.print( mas[i] );
            ostatok += N;
            if (ostatok < max_len-1) {
                Serial.print(' ');
                ostatok += 1;
            }
        } else {
            // новая строка
            Serial.println();
            ostatok = max_len;
        }
    }
     
     
  11. neyasbltb_88

    neyasbltb_88 Нуб

    sslobodyan, спасибо! Пока нет времени нормально переделать свой код, но ваш попробовал запустить, и с небольшими правками он работает хорошо, буквы не теряются:) Я заметил, что у вас используется millis() для прерывания функции, но почему бы не использовать стандартный break? В этом есть какие то преимущества?
    Вы, видимо, не совсем поняли мою задумку. До прихода нового текста я хочу чтобы этот текст был сформатирован под экран, и был управляем. Т.е. чтобы его можно было, например, проскролить кнопками вверх-вниз, и/или чтобы его можно было выводить только в определенной области на экране
     
    Последнее редактирование: 2 фев 2017
  12. sslobodyan

    sslobodyan Гик

    а там из двух циклов выходить надо, так проще :) хотя не совсем правильно
     
    Последнее редактирование: 2 фев 2017