Измеритель напряжения АЦП + последовательный: Байты, потерянные во время связи
Я хочу сделать простой цифровой монитор напряжения. Итак, у меня есть скетч:
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!
}
@xakepp35, 👍-1
Обсуждение1 ответ
С помощью комментариев я сам справился с этой задачей.
Во-первых, как указал @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
- Частота дискретизации Arduino pro mini
- Выводы Portenta для АЦП
- Хранение данных в SDRAM Arduino Portenta H7
- Использование Arduino для записи аналоговых сигналов с помощью SPI ADC + проблема с частотой дискретизации
- Отправка данных аналогового датчика на ноутбук со скоростью 1 кГц через USB
- Регистр ADCH Arduino Uno завис на значении 255 при чтении из ISR
- Печать результата функции AnalogRead() приводит к сбою Arduino
- Если я использую схему смещения для сигнала, поступающего на аналоговый вход, повлияет ли это на работу АЦП?
Либо печатая его как простой 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