Измеритель напряжения АЦП + последовательный: Байты, потерянные во время связи

Я хочу сделать простой цифровой монитор напряжения. Итак, у меня есть скетч:

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

void loop() {
  int val = analogRead(A4);     // read the input pin
  val = 1;
  byte buf[4];
  buf[0] = 0;
  buf[1] = 0;
  buf[2] = val & 255;
  buf[3] = (val >> 8) & 255;
  Serial.write(buf, 4);             // report voltage value
}

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


ПС.

  • хост-ОС: win7 x64;
  • язык: c/c++
  • библиотека com - портов: CSerial

Цикл выглядит следующим образом:

typedef int32_t sample_t;
while (true) {
    int nBytesRead = serial.ReadData(lpBuffer, bufSize * sizeof(sample_t));
    size_t numRead = nBytesRead / sizeof(sample_t);
    if (numRead > 0)
        std::cout << "read: " << numRead << " samples\n"; // Calculate average here!
}

, 👍-1

Обсуждение

Либо печатая его как простой ASCII, либо обертывая значения в надлежащем формате пакета с помощью DLE., @Majenko

@Majenko Что такое "правильный пакет DLE"? Я не могу найти никаких ссылок на то, что может означать DLE., @xakepp35

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

@Majenko Я подумал, что могу префиксить каждое значение 0xAAAA (например, преамбула ethernet), затем поместить поле длины, затем добавить постфикс 0x5555, что означает "передача завершена", и проверить, равна ли длина данных фактической длине - тогда это допустимый пакет., @xakepp35

Да, это правильная идея. Однако вы должны убедиться, что 0xAA или 0x55 никогда не появятся в ваших данных. И вот тут на сцену выходит ДЛЕ., @Majenko

Например, замените "0xAA" на "0xFF 0x0A", а затем интерпретируйте эту последовательность в приемнике как " 0xAA. Конечно, "0xFF" должен сам быть экранирован - как " 0xFF 0xFF, например., @Majenko

Поэтому в приемнике, если вы видите 0xFF, то выбросьте его - но относитесь к следующему входящему символу как к чему-то особенному., @Majenko

@Majenko arduino 10-битные показания АЦП не могут превышать 1023, поэтому они оба не могут отображаться в данных. я могу даже лучше разместить там 0xFFFF, 2 байта длины, 2*длина байтов полезной нагрузки, @xakepp35

Можно. Например, вы можете получить 0xAA 0xAA 0x02 0x01 0x55 0x55 0x55, @Majenko

@Majenko так нужно устранить окончание пакета, заменить его длиной?, @xakepp35

Однако к тому времени, когда вы все это сделаете, ваш пакет уже будет больше, чем, например, "1F3\n", чего было бы достаточно. Это всего лишь максимум 4 байта (меньше, если значение < 256) - размер только ваших пре - и постамблей., @Majenko

@Majenko Вы имеете в виду Serial.println(val, HEX); и разбор этого на хосте?, @xakepp35

Это дало бы вам \r\n в качестве окончания - пустая трата байта. Serial.print(val, HEX); Serial.print("\n"); сохраняет этот один байт (если вам не все равно). Тогда да, разберите его на хосте (что достаточно просто)., @Majenko

Самый простой способ-отправить целое число в виде ascii и сделать atoi или atol на ПК. '\n' или 'etx' в конце должно быть достаточно. Если вы хотите быть уверены на 100%, то " stx " в начале и, возможно, контрольная сумма непосредственно перед "\n" или "etx". Никакого шестнадцатеричного кода, а просто Serial.print(val) для данных. Кабель между ПК и arduino - это не последовательный кабель, а USB-кабель. Байт потеряться почти невозможно, что-то еще не так., @Jot

@Majenko, Этот байт много спас, теперь я получаю 5ksp вместо 3,5! Суммирование их в течение секунды и получение среднего значения приводит к довольно точному 16~20-битному вольтметру, @xakepp35

@Jot It is solved, беда была в том, что я получал данные со сдвигом 0 или 1 байт, и мы нашли простой способ ее решения. отправка через base-10 ascii приведет к более низкой производительности, чем отправка hex, @xakepp35

@xakepp35 Для более высоких бодратов вы можете попробовать arduino leonardo или micro. У них есть USB, подключенный к самому микроконтроллеру, и связь осуществляется со скоростью usb, потому что реального последовательного порта нет. А как насчет вычисления среднего значения в arduino?, @Jot

@Jot вы думаете, что это увеличит частоту дискретизации (например, лучший уровень шума?) сколько аналоговых потоков в секунду он может вызвать? У меня mega2560. Как связать это с секундами в arduino? Мне нужна 1 мера в секунду, как можно более точная. Также суммирование нескольких тысяч выборок приведет к переполнению 16-битных целых чисел., @xakepp35

Arduino mega имеет 10-битный АЦП. Все (линейность, шум и т. Д.) Соответствует этим 10 битам. Я думаю, что хорошим началом является внешний 16-битный ацп: https://www.adafruit.com/product/1085, @Jot

@Jot когда вы суммируете несколько выборок, случайный шум ослабляется (если я суммирую 16 выборок, я мог бы получить не более 3 или 4 бит дополнительной точности, потому что случайный шум внутри каждой выборки будет подавляться сам собой, и реальные данные постоянного тока будут правильно суммироваться), @xakepp35

@xakepp35 да, я всегда использую среднее число выборок для считывания постоянного напряжения. Но линейность не увеличивается, а это было бы необходимо для измерителя напряжения. Общая точность требует лучшего АЦП, чем 10-битный., @Jot

@Jot для ваттметра 50 amps 220V я подсчитал, что мне требуется 16-битный ацп, чтобы оставаться холодным и точным менее 0,5 Вт. Могу ли я подключить много 16-битных АЦП к одной плате arduino?, @xakepp35

@xakepp35 Модуль adafruit ADS1115 имеет 4 аналоговых входа, и 4 из этих модулей могут быть подключены к одной шине i2c. Для ваттметра, какова частота дискретизации, что вам нужно? Со многими каналами АЦП с шиной spi может быть лучше, так как шина i2c медленная., @Jot

@Jot Мне нужно измерить до 50а шунта, с точностью более "0,5 Вт ошибки в день", @xakepp35


1 ответ


0

С помощью комментариев я сам справился с этой задачей.

Во-первых, как указал @Majenko, я имел дело с некоторыми многобайтовыми данными. Поэтому мне нужен какой-то управляющий символ, который разделяет пакеты. Я выбираю шестнадцатеричный вывод с делимметром "\n" в качестве управляющего символа - это хорошо послужило. Я смог считывать от 3,5 до 5 тыс. пакетов в секунду (с считыванием напряжения) с последовательного на хосте.

Второе улучшение, приписываемое @Jot, заключается в том, чтобы перед отправкой в arduino складывать некоторые последовательные значения прямо в arduino.

Наряду с увеличением частоты дискретизации это может быть очень хорошо.

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

void setup() {
  sbi(ADCSRA, ADPS2);
  cbi(ADCSRA, ADPS1);
  cbi(ADCSRA, ADPS0);
  Serial.begin(115200);
}

void loop() {
  int val = 0;
  for( int i = 0; i < 32; ++i )
    val += analogRead(A0); 
  Serial.print(val, HEX);
  Serial.print("\n");
}

на стороне приема хоста можно было бы выполнить несколько простых преобразований:

#include "Serial.h"
#include <iostream>
#include <fstream>
#include <string>
#include <ctime>

// переход от образца к напряжению
double s2v(int sample) {
    return 5.0 * (static_cast<double>(sample) / 1024);
}

int main() {
    std::ofstream ofs("test.csv", std::ios::out | std::ios::ate);

    size_t comPort = 3;
    size_t baudRate = 115200; // 57600;

    CSerial serial;
    if (serial.Open(comPort, baudRate))
    {
        std::cout << "COM" << comPort << " opened @" << baudRate << "bit/s\n";

        // serial recv buffer 
        size_t bufSize = 1048576;
        auto lpBuffer = new char[bufSize];

        // accumulator
        size_t nVals = 0;
        int nAcc = 0;

        // timer
        __time64_t destTime;
        __time64_t destTime2;
        _time64(&destTime);

        while (true) {
            SleepEx(10, false); // care of 100% cpu core usage, poll serial port lazily
            size_t nBytesRead = serial.ReadData(lpBuffer, bufSize * sizeof(lpBuffer[0]));

            //parse buffer:
            size_t cvtPtr = 0;
            for (size_t i = 0; i < nBytesRead; ++i) {
                if (lpBuffer[i] == '\n') {
                    try {
                        lpBuffer[i] = '\0';
                        auto val = std::stoi(lpBuffer + cvtPtr, nullptr, 16);
                        nAcc += val;
                        nVals++;
                    }
                    catch (...) {
                        // ignore, or warn that serial recieved junk/empty packet
                    }
                    cvtPtr = i + 1;
                }
            }

            //check if doom had come!
            _time64(&destTime2);
            if ((destTime2 - destTime) > 0) {
                auto fVoltage = ( s2v(nAcc) / 32 ) / nVals;
                std::cout << fVoltage << "\t(" << nVals << " ksps)\n";
                ofs << destTime << "," << fVoltage << "," << nVals << "\n";
                if((destTime % 30 ) == 0) // care of disk, write only once in 30 seconds
                    ofs.flush();
                destTime = destTime2;
                nVals = 0;
                nAcc = 0;
            }
        } // while
    } // if opened
    return 0;
}

Это дает вольтметр с высокой точностью, около ±0,5 мВ, который обновляется каждую секунду.

И еще! Было заявлено о 10-битном АЦП, 5,0 В/1024 = 4,88 мВ, но я получил некоторые подпороговые колебания in vivo, добавив образцы. Таким образом, вы ошибаетесь, шум может быть ослаблен, а разрешение может быть искусственно увеличено в обмен на увеличение времени дискретизации. Вот 14-битный АЦП, построенный поверх 10-битного АЦП: . Его фотоэлектрический элемент на темной улице, колебания вызваны передними фарами автомобилей. Шкала времени составляет 1000 секунд на каждый раздел сетки

,

Вы не имеете в виду +-5 мВ? Потому что 5 В / 1024 (10 бит) составляет около 4,9 мВ, @chrisl

@christl Я имею в виду, что я получил разрешение на 3-4 бита больше, чем было указано для 10-битного АЦП arduino., @xakepp35