Serial Comm. проблема синхронизации между Arduino и Pyserial

Это мой первый пост в Stack Exchange, поэтому прошу простить за любые ошибки форматирования.

Я провожу эксперимент с 7 датчиками: 5 термопар, 2 датчика влажности и 1 датчик давления. Я читаю их сигналы через аналоговые входные порты Arduino Mega. Я решил использовать Python для сохранения и компиляции данных в хороший CSV-файл, поэтому я использую Pyserial.

Моя проблема в том, что я не могу увеличить скорость выше 1 выборки в секунду. Как вы можете видеть в моем коде, у меня есть задержка в 1 секунду в Arduino и Python. Я провел успешные калибровочные тесты с моими термопарами, поэтому я знаю, что при такой скорости Python и Arduino хорошо взаимодействуют. Что-то быстрее, чем 1 выборка в секунду (т.е. я изменяю свои задержки на 0,5 или 0,25 секунды), возникает ошибка синхронизации, и мои данные «разрезаются» и отправляются нечетными фрагментами вместо стандартного макета, который я запрограммировал.

Кроме того, я попытался увеличить скорость передачи данных с 9600, и все, что выше, будет отправлять "мусорные" символы ASCII (например, маленькие пустые блоки и нечетные символы) в Serial Comm. Я изменил скорость передачи данных в Arduino, Python и COM-порт в настройках диспетчера устройств, но безрезультатно.

Мне бы очень хотелось увеличить частоту дискретизации, чтобы увеличить отношение сигнал/шум, и я считаю хорошей практикой метролога иметь частоту дискретизации выше 1 выборки в секунду. Я отправляю ~ 1072 бита каждый раз, когда пишу в Serial Comm (если я правильно считаю ??), поэтому у меня должно быть достаточно места, чтобы увеличить количество битов, отправляемых в секунду (примерно в 3 раза больше, чем я сейчас отправляю) .

Подводя итог, у меня две основные проблемы: если я увеличиваю скорость передачи данных, я получаю мусор в своем последовательном соединении, и если я пытаюсь увеличить свою "частоту дискретизации", уменьшая мои задержки, появляются проблемы с синхронизацией, и мой данные разбиты ошибочно. Должен ли я использовать другую функцию, например Serial.flush()?

Я очень ценю любые отзывы, спасибо за ваше время!

// Сбор данных
// обновлено 30 марта 2017 г.


//Примечание: эта версия предназначена для данных ТЕМПЕРАТУРЫ, ВЛАЖНОСТИ и ДАВЛЕНИЯ
acquisition ALONE. No motor/conveyor control.

//***ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ***//

float lfactor = 125.0; 
float afactor = 1.15;

//////////////////////////

//Стандартная установка с аналоговой ссылкой на 5 Вольт.

void setup() {

Serial.begin(9600);  
}

void loop() {


//******ТЕРМОПАРЫ*******//

// получаем показания напряжения
// [код]; Номер ТК — Местоположение

float tcv1 = analogRead(A4) * ( 5.0 / 1024.0 ); //ТК 1
float tcv2 = analogRead(A7) * ( 5.0 / 1024.0 ); //ТК 2
float tcv3 = analogRead(A8) * ( 5.0 / 1024.0 ); //ТК 3
float tcv4 = analogRead(A9) * ( 5.0 / 1024.0 ); //ТК 4
float tcv5 = analogRead(A10) * ( 5.0 / 1024.0 ); //ТК 5

//преобразование в температуру с использованием пользовательского уравнения на основе опорного напряжения 5 В
float tc1 = ( 188.7755 * tcv1 ) - 245.3959;  
float tc2 = ( 188.7755 * tcv2 ) - 245.3959;  
float tc3 = ( 188.7755 * tcv3 ) - 245.3959;  
float tc4 = ( 188.7755 * tcv4 ) - 245.3959;  
float tc5 = ( 188.7755 * tcv5 ) - 245.3959;    

//******ДАТЧИКИ ВЛАЖНОСТИ********//

float hs1 = ((( analogRead(A13) * ( 5.0 / 1024.0) ) / 5.0 ) * 100.0 ); //ГС
1
float hs2 = ((( analogRead(A15) * ( 5.0 / 1024.0) ) / 5.0 ) * 100.0 ); //ГС
2

//******ДАТЧИКИ ДАВЛЕНИЯ********//

//получить сигнал напряжения от датчика Sensirion
float pvolt = analogRead(A0) * (4.995 / 1024.0); 

// конвертируем в паскали, используя уравнение из таблицы данных, включая высоту
compensation
//P = Iфактор * (напряжение [В] - 0,250) / 3,750

float pressure =  lfactor * ( pvolt - .250 ) / 3.750;  
float pascals = pressure * afactor; 

Serial.print("TC1");
Serial.print(":");
Serial.print(tc1);
Serial.print(";");

Serial.print("TC2");
Serial.print(":");
Serial.print(tc2);
Serial.print(";");

Serial.print("TC3");
Serial.print(":");
Serial.print(tc3);
Serial.print(";");

Serial.print("TC4");
Serial.print(":");
Serial.print(tc4);
Serial.print(";");

Serial.print("TC5");
Serial.print(":");
Serial.print(tc5);
Serial.print(";");    

Serial.print("RH 1");
Serial.print(":");
Serial.print(hs1);
Serial.print(";");  

Serial.print("RH 2");
Serial.print(":");
Serial.print(hs2);
Serial.print(";");

Serial.print("Pressure");
Serial.print(":");
Serial.print(pascals);
Serial.print(";");

Serial.println("");
delay(1000); 

  }

Код Python:

from threading import Thread
import time
import serial
import os 
global datalist
global fileName

global motorspeed

motorspeed = 0 

def serInitialization():
    #activate the serial port, if possible
    try:
        ser = serial.Serial('COM9', 9600) #initialize the serial port
        print "Serial connection successful." 
        return ser
    except:
        print "Error: serial port cannot be initialized"
        while(1):
            voidholder = 1

def getDateTime():
  """Function grabs current time and date, then returns values in a 2-
 element list. """
   timeNow = time.strftime("%H:%M:%S")
   dateToday = time.strftime("%m/%d/%y")
  return [dateToday, timeNow]

def writetocsv(data):

    """ function writes datalist values to a csv file. If daily csv file 
exists already, 
    list values are simply appended to end of file. If it does not, function 
creates the file, 
    then appends values. 
    """

    global csv_success

    header = ["date", "time", " ", "TC1", " ", "TC2", " ", 
"TC3", " ", "TC4", " ", "TC5", " ", "HS1", " ", "HS2"," ", 
"PRESSURE", " ", "PRESSURE(alt)", "\n"]

    fileName = str(time.strftime("%m_%d_%y_")+ "log.csv")

    if os.path.exists(fileName):
        f = open(fileName, "a")

    else:
        f = open(fileName, "a+")

        for element in header:
            f.write(element + ",")
        f.write("\n")

    for element in data:
        if type(element)==str:
            f.write(element + ",")
        if type(element) == list:
            for i in element:
                f.write(i + ",")

    f.write("\n")
    f.close()
    csv_success = True

def mainprogram():

    data = []


    if ser.inWaiting():
        datetimeData = getDateTime()

        for i in datetimeData:
            data.append(i)

        val = ser.readline().strip('\n\r').split(';')
        print "Current readings: "
        for i in range(0,len(val)):
            sensorData = val[i].split(':')
            data.append(sensorData)

            print sensorData

        writetocsv(data)

        time.sleep(1) 


        print "\n" * 50

#Initializes the serial port with the arduino so now we can read what 
Arduino is sending us!
print "*******************************"
print "DATA ACQUISITION PROGRAM       "
print "*******************************"
print "Data files are saved under "
print "C:\\Users\\lpaw\\Downloads\\python workspace"
print "*******************************"


ser = serInitialization()

while True:

   mainprogram()

Скриншоты ошибок: Arduino Serial Comm и Python IDLE — данные перепутались

Ошибка скорости передачи

, 👍2

Обсуждение

Я подозреваю, что уже знаю, где у вас проблема с частотой дискретизации, но можете ли вы опубликовать пример «хорошего» и «плохого» набора полученных данных?, @brhans


3 ответа


0

Вы обязательно должны использовать один Serial.flush в конце цикла()

Serial.println("");
Serial.flush()
delay(100); 

Помните, что у вас ограниченное количество SRAM для хранения символов. Я бы посоветовал вам использовать встроенный последовательный монитор в Arduino IDE с вышеуказанным изменением и увеличить скорость передачи данных. Не должно быть проблем с отправкой данных на скорости 115 кбод.

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

,

это не проблема, @Chris Stratton

@ChrisStratton ... тогда, пожалуйста, поделитесь своими знаниями., @Jack Creasey

Последовательный вывод блокируется до тех пор, пока в буфере не появится место. Все, что вы сделаете, это расширит интервал выборки за пределы того, что предполагается временем передачи того, что еще не было отправлено, в то время как без flush() он расширяется только на любую часть, которая еще не помещается в исходящий буфер данных., @Chris Stratton


3

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

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

Но, похоже, вы также хотите записывать данные упорядоченным образом в виде наборов измерений. Для этого вы должны отклонять данные до тех пор, пока не увидите первый «TC1», затем собирать строки для заполнения блока данных до тех пор, пока не будет распознано условие конца данных (в вашем случае пустая строка), и записать его на диск в файл с отметкой времени. Вы также можете включить код, который оставляет поврежденный блок незаписанным, если какой-либо из заголовков промежуточного чтения не соответствует ожидаемому, и возвращается к поиску следующего, надеюсь, чистого TC1, или помечает его как ошибочный, или что-то еще.

Чтобы использовать другую скорость передачи данных, вы должны применить соответствующие настройки как на Arduino, так и на Python.

Также обратите внимание, что хотя последовательный монитор Arduino действительно полезен для отладки, вы не должны открывать его при попытке собрать данные с помощью python — это либо вызовет ошибку, связанную с тем, что одна программа запрещена , или две программы, которые борются за данные и получают неполные результаты.

Другой метод, который может быть простым и полезным, особенно во время разработки, заключается в записи каждой полученной строки последовательных данных в текстовый файл с числовой меткой времени в начале каждой строки. Затем у вас может быть второй процесс, который читает этот файл, получает полные многострочные записи и записывает их, используя отметку времени первого или последнего элемента записи. В частности, это позволяет вам получить промежуточное представление данных, чтобы понять любые странности. С помощью различных приемов командной строки в стиле Unix вы даже можете запустить первую программу, сохраняя промежуточный формат и загружая вторую программу извлечения записей в режиме реального времени.

,

Ладно, я понимаю, что ты говоришь. Причина, по которой у меня есть задержка, заключается в том, что мне нужно иметь возможность наблюдать за данными в режиме реального времени, и я не смог запустить графический интерфейс. Так что у меня программа отложена, чтобы у меня было достаточно времени, чтобы прочитать данные в оболочке Python. Я просто стисну зубы и изучу многопоточность (я боролся с этим, поэтому я пошел со своей текущей [дерьмовой] настройкой). Большое спасибо за Вашу помощь!!, @L. Paw

Threading также не является решением этой проблемы. Что вам нужно сделать, так это передать вывод через пейджер или записать его в файл и просмотреть его отдельно., @Chris Stratton

Но разве потоки не позволяют мне одновременно непрерывно «захватывать» данные и распечатывать их в пользовательском интерфейсе?, @L. Paw

Я посмотрю на это, еще раз спасибо @Chris Stratton, @L. Paw


0

Есть ли в вашем коде Python способ определения начала & конец набора данных?
Предполагает ли он, что когда он считывает фрагмент данных из последовательного порта, первый полученный байт всегда будет началом «Cond.temp», а последний байт будет ; в конце Press(alt)?
После чтения фрагмента данных у вас есть какая-либо проверка, чтобы убедиться, что он завершен, или вам следует подождать несколько десятков миллисекунд и вернуться, чтобы прочитать еще немного?

Я предполагаю, что ответы на эти вопросы: "Нет", "Да" и "amp; №

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

Если вы используете адаптер USB-Serial, ваши данные, скорее всего, будут поступать в виде битов. части, которые вам нужно будет собрать заново - просто по характеру того, как эти звери часто работают - они могут буферизовать некоторое количество последовательных байтов, прежде чем упаковывать их вместе в один пакет USB.

Вам нужен какой-то способ отметить начало и/или конец ваших наборов данных. Вам может подойти даже такая простая вещь, как символ новой строки \n в конце каждой строки.
Затем вам нужно прочитать и буферизовать полученные данные, пока вы не поймете, что достигли конца набора данных.
Итак, для примера \n:
- прочесть кое-что
- добавить в буфер
- у вас есть \n где-нибудь в буфере?
-- yes - вытащить этот раздел от начала буфера до \n & обработать его
-- нет - вернуться к началу и ждать продолжения
- после того, как вы вытащили что-то из буфера для обработки,
не забывайте об остальном — это может стать началом вашего следующего набора данных!
- переместите его в начало вашего буфера
- полоскание и усиление повторить

Что касается вашей проблемы со скоростью передачи данных, я не могу ничего сказать наверняка. Мусор, который вы видите, типичен для ситуации с несоответствием скорости передачи данных.
Убедитесь, что ваш конкретный Arduino поддерживает скорость передачи данных, которую вы пытаетесь использовать, на любой тактовой частоте, на которой вы его используете.
Убедитесь, что ваш адаптер USB-Serial (если вы его используете) работает на этих скоростях (либо замкните контакты 2 и 3 и наберите себе в терминале, либо получите другой, желательно другой, и соедините их вместе с помощью 3-проводного связать контакты 2–3, контакты 3–2, контакты 5–5).

,