With help in a comments I've managed to deal with the task myself.
First of all, as @Majenko pointed out, I was dealing with some multi-byte data. So I need some control character, that separates packets. I choose hex output with '\n' delimeter as control character - that served good. I was able to read 3.5 to 5k packets per second (with voltage reading) from serial on the host.
Second improvement, credited to @Jot, is to stack some sequential values right in arduino, before sending.
Alongside with increasing sampler frequency that could be very good.
#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");
}
at the host recieve side, some simple conversion could be done:
#include "Serial.h"
#include <iostream>
#include <fstream>
#include <string>
#include <ctime>
// sample to voltage
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;
}
That gives voltmeter with high precision, about ±0.5mV, that is updated every second.
Also! It was stated about 10bit adc, 5.0V/1024 = 4.88mV, but i got some subthreshold oscillations in vivo, adding samples up. So you are wrong, noise can be attenuated and resolution can be artificially pumbed up, in a trade for increased sampling time. Here is 14bit ADC built on top of 10bit ADC:
. Its photovoltaic cell in a dark street, oscillations are caused by cars' front lights. Timescale is 1000 seconds per grid div
\r\n
as the ending - wasting a byte.Serial.print(val, HEX); Serial.print("\n");
saves that one byte (if you care). Then, yes, parse it on the host (which is simple enough).