Почему Arduino начинает отвечать только с третьего запроса?

Есть ардуинка, которая занимается в фоне своими ардуиновскими делами и никого не трогает. Но нужна возможность периодически к ней подключаться и получать \ передавать данные. Для этого нашёл в интернете несколько функций, которые слепил вот в такую прогруммку, которая умеет отправлять и получать строки.

spoiler
header.h
#include <iostream>
#include <thread>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include "com_port.c"

short int ArduinoPort = serialport_init("/dev/ttyUSB0", 9600);
int serialport_read_until(int fd, char* buf, char until);
int serialport_write(short int &fd, const char* str);

com_port.c
int serialport_write(short int &fd, const char* str){ // Пишем данные в порт ардуинки
    static int LastTime = 0;
    int NowTime = time(NULL);
    if((NowTime - LastTime) <= 1) // Если после прошлого выхова прошло меньше секунды - делаем паузу
        std::this_thread::sleep_for (std::chrono::milliseconds(1400));
    
    LastTime = time(NULL); // Сохраняем метку для следующего вызова
    int len = strlen(str);
    int n = write(fd, str, len);
    if(n!=len)
        return -1;
    return 0;
}

int serialport_read_until(int fd, char* buf, char until){ // Читаем данные из порта ардуинки
    char b[1];
    int i=0;
    do {
        int n = read(fd, b, 1);  // Читываем по символу за раз
        if(n == -1)
            return -1;    // Если чтение не удалось - завераем работу

        if(n == 0){
            usleep( 10 * 1000 ); // ждём 10 милисекунд и повторяем попытку
            continue;
        }

        buf[i] = b[0];
        i++;
    } while( b[0] != until);

    buf[i] = 0;  // Завершаем строку
    return 0;
}

// принимает строковое имя последовательного порта (например, "/dev/tty.usbserial","COM 1")
// и скорость передачи данных в бодах (bps) и подключается к этому порту с такой скоростью и 8N1.
// открывает порт в полностью необработанном режиме, чтобы вы могли отправлять двоичные данные.
// возвращает допустимый fd или -1 при ошибке
int serialport_init(const char* serialport, int baud){
    struct termios toptions;
    int fd;
    
    //fprintf(stderr,"init_serialport: opening port %s @ %d bps\n",
    //        serialport,baud);

    fd = open(serialport, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1)  {
        perror("init_serialport: Unable to open port ");
        return -1;
    }
    
    if (tcgetattr(fd, &toptions) < 0) {
        perror("init_serialport: Couldn't get term attributes");
        return -1;
    }
    speed_t brate = baud; // let you override switch below if needed
    switch(baud) {
    case 4800:   brate=B4800;   break;
    case 9600:   brate=B9600;   break;
    #ifdef B14400
        case 14400:  brate=B14400;  break;
    #endif
        case 19200:  brate=B19200;  break;
    #ifdef B28800
        case 28800:  brate=B28800;  break;
    #endif
    case 38400:  brate=B38400;  break;
    case 57600:  brate=B57600;  break;
    case 115200: brate=B115200; break;
    }
    cfsetispeed(&toptions, brate);
    cfsetospeed(&toptions, brate);

    // 8N1
    toptions.c_cflag &= ~PARENB;
    toptions.c_cflag &= ~CSTOPB;
    toptions.c_cflag &= ~CSIZE;
    toptions.c_cflag |= CS8;
    // no flow control
    toptions.c_cflag &= ~CRTSCTS;

    toptions.c_cflag |= CREAD | CLOCAL;  // turn on READ & ignore ctrl lines
    toptions.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl

    toptions.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
    toptions.c_oflag &= ~OPOST; // make raw

    // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
    toptions.c_cc[VMIN]  = 0;
    toptions.c_cc[VTIME] = 20;
    
    if( tcsetattr(fd, TCSANOW, &toptions) < 0) {
        perror("init_serialport: Couldn't set term attributes");
        return -1;
    }

    return fd;
}

main.cpp
#include "cpp/header.h"
using namespace std;

int main(int argc, const char *argv[]){
    setlocale(LC_ALL, "ru");
    char buf[256];
    int LastSend = 0;
    char res = '1';

    while(LastSend >= 0){
        res = (res == '1') ? '0' : '1'; // Меняем единичку и нолик местами, чтоб зажигать и выключать светодиод

        LastSend =  serialport_write(ArduinoPort, (res + "cdwd"s).c_str()); // Отправялем строку ардуинке
        this_thread::sleep_for (chrono::milliseconds(1000)); // Ждём одну секунду

        serialport_read_until(ArduinoPort, buf, '\n'); // Читаем ответ
        
        cout << "Получили ответ: " << buf << endl;
    }
    
    return 0;
}


На стороне ардуинки - самая простая программа, которую пока что записал для проверки работоспособности. Если присылаем единичку - светодиод зажигается, всё остальное - светодиод гаснет.

spoiler
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(A3, OUTPUT); 
  Serial.begin(9600);
}

void loop() {

  if(Serial.available() > 0){
    String date = Serial.readString();
    Serial.print(date);

    if(date[0] == '1'){
      digitalWrite(LED_BUILTIN, HIGH);
      digitalWrite(A3, 1);
    }
    
    else{
      digitalWrite(LED_BUILTIN, LOW);
      digitalWrite(A3, 0);
    }
   }
}

Но самое интересное, что всё это работает... Только с третьей итерации цикла! Т.е. первые два запроса ардуинка полностью игнорирует (отправляется 0 байт), а с третьего - данные отправляются и возвращаются без проблем. В терминале это выглядит так:

634bfdcd17d33307780755.jpeg
Как так получается? Как сделать, чтоб запросы работали сразу, а не с третьей итерации?
  • Вопрос задан
  • 108 просмотров
Решения вопроса 1
@jenya92 Автор вопроса
Искренне до конца не понял в чём проблема, но если после открытия порта добавить тайм аут - начинает работать. Т.е. между открытием порта и отправкой первых данных добавил:

std::this_thread::sleep_for (std::chrono::milliseconds(1500));


И всё заработало. Дальше данные отправляются и принимаются как надо.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы