Постоянное чтение последовательных данных из Arduino в Python

Используя шаговый двигатель и дальномер (TFLuna), я пытаюсь построить «Лидар»; (как Радар, но со светом). Вот алгоритм:

  • Поверните двигатель на один шаг, рассчитайте угол, измерьте расстояние до объекта, с которым он столкнется, и получите полярные координаты;
  • Вычислить декартовы координаты и отправить в Python;
  • Соберите несколько сотен измерений и покажите диаграмму рассеяния.

Это работает, но только если я одновременно перезапущу скетч и программу Python. После того, как я собрал все данные для одного графика, я не могу перезапустить скрипт Python для сбора еще одного раунда данных — это показывает, что данных в ожидании нет. Кроме того, сценарий Arduino никогда не зависает из-за переполнения буфера (поскольку Python перестал читать).

Как я могу сделать так, чтобы Arduino и двигатель выполняли свою работу (раскачивались вперед и назад на 180°, смотрели на вещи), и я мог периодически собирать объем данных в Python - всякий раз, когда я решу перезапустить сценарий еще раз?

Вот код. Во-первых, Ардуино. "считать"; — количество шагов, на которые прокрутился двигатель; tflI2C — объект дальномера.

#include <Stepper.h>
#include <Arduino.h>
#include <Wire.h>        // Создаем экземпляр библиотеки Wire
#include <TFLI2C.h>      // Библиотека TFLuna-I2C v.0.1.1
#include <math.h>

TFLI2C tflI2C;
int16_t  tfDist;    // расстояние в сантиметрах
int16_t  tfAddr = TFL_DEF_ADR;  // Используйте этот адрес I2C по умолчанию

const int stepsPerRevolution = 2048;  // изменяем это, чтобы оно соответствовало количеству шагов за оборот

// инициализируем библиотеку шаговых двигателей на контактах с 8 по 11:
Stepper myStepper(stepsPerRevolution, 8, 10, 9, 11);
int stepCount = 0;         // количество шагов, которые сделал мотор

void serialFlush(){
  while(Serial.available() > 0) {
    char t = Serial.read();
  }
}


void setup() {
  // инициализируем последовательный порт:
  Serial.begin(115200);
  Wire.begin();           // Инициализируем библиотеку Wire
  myStepper.setSpeed(10);
  //myStepper.step(stepsPerRevolution/2);
  //задержка(1000);
  //myStepper.step(-stepsPerRevolution/2);
}

void coordinates(int count){
  struct Polar {
    float angle;
    int16_t distance;
  } pPoint;
  struct Cartesian {
    float xCoord;
    float yCoord;
  } cPoint;

  float angle = count*(2.0*PI)/stepsPerRevolution;
  byte flag = 0;
  //Последовательный.flush();
  while (Serial.availableForWrite() < sizeof(struct Cartesian)){}
  if(tflI2C.getData(tfDist, tfAddr)){
    pPoint.angle = angle + PI/2.0;
    pPoint.distance = tfDist;
    cPoint.xCoord = pPoint.distance * cos(pPoint.angle);
    cPoint.yCoord = pPoint.distance * sin(pPoint.angle);
    if (pPoint.distance <= 50 ){
      Serial.write((char*)&cPoint, sizeof(struct Cartesian));
    }
  }
  return;
}

void loop() {
  // шаг один шаг:
  for (int k=0; k<stepsPerRevolution/4; k++){
    myStepper.step(1);
    coordinates(stepCount);
    stepCount += 1;
    delay(10);
  }
  for (int k=0; k<stepsPerRevolution/2; k++){
    myStepper.step(-1);
    coordinates(stepCount);
    stepCount -= 1;
    delay(10);
  }
  for (int k=0; k<stepsPerRevolution/4; k++){
    myStepper.step(1);
    coordinates(stepCount);
    stepCount += 1;
    delay(10);
  }
  delay(5000);
}

а вот Python:

ardu = serial.Serial('/dev/cu.usbserial-1460', baudrate=115200, timeout=.1)
radarImage = []
structSize = struct.calcsize("ff")
ardu.reset_input_buffer()
    
i = 0;
while True:
    if ardu.in_waiting >= structSize:
        arduRead = ardu.read(structSize)
        point = list(struct.unpack("ff",arduRead))
        if i <= 10:
            print(point)
        radarImage.append(point)
        i += 1
    else:
        pass
    if i > 2000:
        break
ardu.close()

Вот образец из «первого раунда» Сбор данных. Да, у меня на столе беспорядок...

, 👍-1

Обсуждение

код Arduino не компилируется без ошибок, @jsotola

Я не удивлен — я дал вам только код, относящийся к последовательной связи. Я выложу все это, если это поможет. Подожди..., @Matthias

RE: «Я не могу перезапустить скрипт Python, чтобы собрать еще один раунд данных» — возможно, у вас тоже проблема с кодом Python?, @VE7JRO

Какой Ардуино вы используете? Мне неясно, что именно происходит, когда вы перезапускаете скрипт Python. Когда именно вы пытались его перезапустить? Во время задержки в 5 секунд? Или во время замера? Что именно тогда происходит? В настоящее время ваш серийный код не имеет возможности синхронизироваться между Arduino и Python. Сценарий Python не может обнаружить начало новой структуры точки внутри последовательного потока (для этого вы не реализовали какой-либо протокол)., @chrisl

Возможно, вы захотите свести исходники к **минимальному**, воспроизводимому и компилируемому примеру, демонстрирующему такое поведение. Например, начать удалять лидарную штуку и при необходимости заменять ее простой пустышкой., @the busybee

@chrisl: Я пытался перезапустить Python на разных этапах выполнения Arduino. Я попытался напечатать «in_waiting», но это дает мне ноль. Вы правы насчет синхронизации как таковой, но я бы принял бессмысленные данные для первого черновика. Я озадачен, почему код Arduino просто не останавливается на «Serial.availableForWrite()», когда я прекращаю читать данные на стороне Python, — он продолжает работать. Если мне не хватает простой идиомы, я попробую работать с явными байтами «начать работу» и «остановить работу» от Python до Arduino... намного сложнее, чем я хотел, для доказательства принципа., @Matthias

@thebusybee: Понятно. Однако я очень надеялся, что кто-нибудь знает «идиому» кода, которая содержала бы правильную комбинацию «ожидания данных» и/или «очистки буфера» для перезапуска передачи данных в середине выполнения. Если нет – ничего страшного. Сокращение кода до минимума — это такая же большая работа, как и обновление кода для обеспечения правильного взаимодействия с помощью рукопожатия с явными «командами запуска и остановки», выдаваемыми кодом Python… гораздо больше усилий, которых я хотел, потому что *в конечном итоге* я просто хочу визуализировать данные на TFT-дисплее., @Matthias

Ну, вы же не ожидаете, что каждый из нас потратит время на изучение всего этого кода, не так ли? ;-) Урок, который нужно усвоить: разрабатывайте программное обеспечение постепенно, а не масштабно. Отдельные вопросы. Экспериментируйте, чтобы понять, как все работает., @the busybee

@thebusybee: Абсолютно нет!! Если решение неочевидно или то, что мне не хватает, нетривиально, я бы не ожидал, что кто-то приложит больше усилий по устранению неполадок, чем я готов. Однако иногда случается, что проблема имеет О, ты просто делаешь...» решение. Стоит проверить, есть ли. :) Очень сложно быть прилежным, когда цель кода — просто проверить, стоит ли качество предоставленных данных продолжать проект дальше., @Matthias


1 ответ


Лучший ответ:

2

Я озадачен, почему код Arduino просто не останавливается на «Serial.availableForWrite()»; когда я перестаю читать данные на стороне Python, он продолжает работать.

Это зависит от используемой платы Arduino, особенно от того, имеет ли она встроенную поддержку USB. Платы без встроенной поддержки USB (например, Uno или Nano) используют второй чип, который получает данные UART (последовательный порт) от основного чипа и подключает их к USB. Основной чип не может знать о состоянии USB-чипа, поэтому он отправляет данные независимо от прослушивающей программы.

Платы со встроенной поддержкой USB в некоторой степени зависят от ОС/драйвера/программы для сбора данных с конечных точек USB. Если ваша программа не собирает эти данные, но и не закрывает соединение, тогда данные будут накапливаться в буфере, и availableForWrite() должен возвращать 0, когда буфер заполнен (блокировка дальнейшей запись в Serial). Но если ваша программа правильно закрывает соединение (как и должно быть при выходе), то последовательные данные даже не отправляются, а отбрасываются. См. ответ Маженко на этот вопрос, который здесь уместен.

В общем: нет, Arduino не прекратит отправку данных только потому, что вы не слушаете. Создание простой системы команд, сообщающей Arduino о начале/остановке отправки данных, является распространенным способом справиться с этой ситуацией. Или вы можете реализовать протокол, который позволит сценарию Python правильно определять начало и конец каждого пакета данных.

,

Это потребовало гораздо больше усилий, чем требовалось для быстрого дампа данных и визуализации, но в конечном итоге мне это удалось, как бы, реализовав команду «Отправить команду TURN» в Arduino, Arduino вычисляет структуру и отправляет в Python, Python отправляет следующую команду «TURN» — песню и танец. После этого он ВСЕ ЕЩЕ зависал случайным образом, пока я не добавил целую кучу явных сбросов буфера и тщательно рассчитал время «delay()-s» в оба кода. Ирония в том, что данные в конечном итоге даже не выглядели такими впечатляющими — недостаточно впечатляющими, чтобы дорабатывать проект дальше., @Matthias