Closed kubark42 closed 7 months ago
Thank you for the tip. I was not aware of the Continuous Mode driver. I'll surely take a look at it when I find the time. Programming is just a hobby of mine, I'm doing it while my wife is solving crosswords ...
I'm very impressed for something which is only a hobby...
It looks like the ADC can do from 83kHz down to 611Hz. For samples rates slower than that, the results could be averaged together for noise reduction.
/*!< F_sample = F_digi_con / 2 / interval. F_digi_con = 5M for now. 30 <= interva <= 4095 */
#define SOC_ADC_SAMPLE_FREQ_THRES_HIGH 83333
#define SOC_ADC_SAMPLE_FREQ_THRES_LOW 611
Hmm, this is less interesting it looks like. I take it from https://github.com/espressif/esp-idf/blob/32472536715d674c160a2565795895e0273f8cde/components/esp_adc/adc_continuous.c#L431 that this is not a true hardware-driven continuous conversion, and is instead an RTOS thread. That means that the lack of Arduino support for this feature is a big deal.
https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino looks to be supported in Arduino, but it's only valid for ESP32, and not ESP32S2, ESP32S3, etc...
So it might be that higher rate sampling is a dead end for the time being. Maybe if Espressif extends their IDF to Arduino core, or if you want to explore I2S high-frequency sampling (it might have wifi issues?), this might be worth revisiting.
I modified the code I found here: https://forum.arduino.cc/t/esp32-i2s-adc-read-two-inputs-possible/1032963/10
It seems it is possible to get 1 12 bit analog sample each 3 us. I haven't figured out everything yet, for example how to read from two GPIOs simultaneously or what would the correct value for I2S_SAMPLE_RATE be.
This is the code that seems to work:
#include <driver/i2s.h>
// I2S
#define I2S_SAMPLE_RATE (500) // (3000)
#define ADC_INPUT (ADC1_CHANNEL_4) //pin 32
#define I2S_DMA_BUF_LEN (1024) // (8)
// The 4 high bits are the channel, and the data is inverted
size_t bytes_read;
uint16_t buffer[I2S_DMA_BUF_LEN] = {0};
unsigned long lastTimePrinted;
unsigned long loopTime = 0;
void i2sInit() {
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
.sample_rate = I2S_SAMPLE_RATE, // The format of the signal using ADC_BUILT_IN
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // is fixed at 12bit, stereo, MSB
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S_MSB,
.intr_alloc_flags = 0, // ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 2,
.dma_buf_len = I2S_DMA_BUF_LEN,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
i2s_set_adc_mode(ADC_UNIT_1, ADC_INPUT);
i2s_adc_enable(I2S_NUM_0);
adc1_config_channel_atten(ADC_INPUT, ADC_ATTEN_DB_11);
}
void setup() {
Serial.begin(115200);
i2sInit();
}
void loop() {
unsigned long startMicros = micros ();
esp_err_t e = i2s_read(I2S_NUM_0, &buffer, sizeof(buffer), &bytes_read, portMAX_DELAY); /// 0);
unsigned long stopMicros = micros ();
loopTime = stopMicros - startMicros;
if (millis() - lastTimePrinted >= 100) {
Serial.printf ("------------------ loop time: %i, bytes_read: %i, e: %i\n", loopTime, bytes_read, e);
Serial.printf ("sample 0 channel: %i, sample 0 value: %i\n", buffer[0] >> 12, buffer[0] & 0x0FFF);
Serial.printf ("sample 1 channel: %i, sample 1 value: %i\n", buffer[1] >> 12, buffer[1] & 0x0FFF);
// ...
lastTimePrinted = millis();
}
}
Interesting. Not that you should take me as a reference, but for my standard usage patterns, it's rare that I have two signals I need to measure simultaneously. The ESP32's ability to measure many different pins is far more valuable, since that means that I can wire up a small ESP 32 inside a robot and then forget about it until I am curious why something isn't working and want to debug. Having lots of high-speed pins is terribly useful for this.
Hello. This is the most I can squeeze out of the current architecture, I guess. I fixed some of the bugs doing this and probably created some new ones :/ I still need to do some testing and improvements. updates.zip
I made some fixes and I believe this is more or less it. You can have up to 744 samples per screen now (when using continuous 1 signal analog sampling), providing that at least 7 us pass between two samples (max sampling frequency that ESP32 can make is 150 kHz). With higher horizontal frequency than 200 Hz the actual number of samples per screen would get lower.
/*
oscilloscope.h
This file is part of Esp32_web_ftp_telnet_server_template project: https://github.com/BojanJurca/Esp32_web_ftp_telnet_server_template
Issues:
- when WiFi is in WIFI_AP or WIFI_STA_AP mode is oscillospe causes WDT problem when working at higher frequenceses
September 9, 2023, Bojan Jurca
*/
// ----- includes, definitions and supporting functions -----
#include <WiFi.h>
#include <soc/gpio_sig_map.h> // to digitalRead PWM and other GPIOs ...
#include <soc/io_mux_reg.h> // thanks to gin66: https://github.com/BojanJurca/Esp32_oscilloscope/issues/19
#include "driver/adc.h" // to use adc1_get_raw instead of analogRead
#include <driver/i2s.h>
// #include <soc/syscon_reg.h>
// fixed size strings
#include "fsString.h"
// ----- TUNNING PARAMETERS -----
#define OSCILLOSCOPE_CONTINUOUS_BUFFER_SIZE 746 // max number of samples per screen, 746 samples * 2 bytes per sample = 1492 bytes, which must be <= HTTP_WS_FRAME_MAX_SIZE - 8
#define OSCILLOSCOPE_1SIGNAL_BUFFER_SIZE 373 // max number of samples per screen, 373 samples * 4 bytes per sample = 1492 bytes, which must be <= HTTP_WS_FRAME_MAX_SIZE - 8
#define OSCILLOSCOPE_2SIGNALS_BUFFER_SIZE 248 // max number of samples per screen, 248 samples * 6 bytes per sample = 1488 bytes, which must be <= HTTP_WS_FRAME_MAX_SIZE - 8
// some ESP32 boards read analog values inverted, uncomment the following line to invert read values back again
// #define INVERT_ADC1_GET_RAW
// some ESP32 boards read continuous analog values inverted, uncomment the following line to invert read values back again
// #define INVERT_I2S_READ
// #define OSCILLOSCOPE_READER_CORE 1 // 1 or 0 // #define OSCILLOSCOPE_READER_CORE if you want oscilloscope reader to run on specific core
#ifndef OSCILLOSCOPE_READER_PRIORITY
#define OSCILLOSCOPE_READER_PRIORITY 1 // normal priority if not define differently
#endif
// ----- CODE -----
#include "httpServer.hpp" // oscilloscope uses websockets defined in webServer.hpp
// oscilloscope samples
struct oscContinuousSample { // one sample
int16_t signal1; // signal value of 1st GPIO read by analogRead or digialRead
}; // = 2 bytes per sample
struct osc1SignalSample { // one sample
int16_t signal1; // signal value of 1st GPIO read by analogRead or digialRead
int16_t deltaTime; // sample time - offset from previous sample in ms or us
}; // = 4 bytes per sample
struct osc2SignalsSample { // one sample
int16_t signal1; // signal value of 1st GPIO read by analogRead or digialRead
int16_t signal2; // signal value of 2nd GPIO if requested
int16_t deltaTime; // sample time - offset from previous sample in ms or us
}; // = 6 bytes per sample
struct oscSamples { // buffer with samples
union {
oscContinuousSample samplesContinuousSignal [OSCILLOSCOPE_CONTINUOUS_BUFFER_SIZE];
osc1SignalSample samples1Signal [OSCILLOSCOPE_1SIGNAL_BUFFER_SIZE]; // samples
osc2SignalsSample samples2Signals [OSCILLOSCOPE_2SIGNALS_BUFFER_SIZE]; // samples
};
unsigned int sampleCount; // number of samples in the buffer
bool samplesAreReady; // is the buffer ready for sending
};
enum readerState { INITIAL = 0, START = 1, STARTED = 2, STOP = 3, STOPPED = 4 };
/* transitions:
START - set by osc main thread
STARTED - set by oscReader
STOP - set by osc main thread
STOPPED - set by oscReader
*/
struct oscSharedMemory { // data structure to be shared among oscilloscope tasks
// basic data
WebSocket *webSocket; // open webSocket for communication with javascript client
bool clientIsBigEndian; // true if javascript client is big endian machine
// sampling sharedMemory
char readType [8]; // analog or digital
bool analog; // true if readType is analog, false if digital (digitalRead)
int gpio1; // gpio where ESP32 is taking samples from (digitalRead)
adc1_channel_t adcchannel1; // channel mapped from gpio ESP32 is taking samples from (adc1_get_raw instead of analogRead)
adc1_channel_t adcchannel2; // channel mapped from gpio ESP32 is taking samples from (adc1_get_raw instead of analogRead)
int gpio2; // 2nd gpio if requested
int samplingTime; // time between samples in ms or us
char samplingTimeUnit [3]; // ms or us
unsigned long screenWidthTime; // oscilloscope screen width in ms or us
char screenWidthTimeUnit [3]; // ms or us
bool positiveTrigger; // true if posotive slope trigger is set
int positiveTriggerTreshold; // positive slope trigger treshold value
bool negativeTrigger; // true if negative slope trigger is set
int negativeTriggerTreshold; // negative slope trigger treshold value
// buffers holding samples
oscSamples readBuffer; // we'll read samples into this buffer
oscSamples sendBuffer; // we'll copy red buffer into this buffer before sending samples to the client
// reader state
readerState oscReaderState; // helps to execute a proper stopping sequence
};
// oscilloscope reader read samples to read-buffer of shared memory - it will be copied to send buffer when it is ready to be sent
inline void delayMicrosecondsUntil (unsigned long *lastTime, unsigned long period) __attribute__((always_inline));
void delayMicrosecondsUntil (unsigned long *lastTime, unsigned long period) {
unsigned long nextTime = *lastTime + period;
unsigned long waitTime = nextTime - micros ();
while (waitTime > 1500 && waitTime < period) {
vTaskDelay (pdMS_TO_TICKS (1));
waitTime = nextTime - micros ();
}
if (waitTime < period)
delayMicroseconds (waitTime);
*lastTime = micros ();
}
// oscReaders oscReaders oscReaders oscReaders oscReaders oscReaders oscReaders oscReaders oscReaders oscReaders oscReaders oscReaders oscReaders oscReaders
// The most general purpose and slow oscReader
// - it can read 1 or 2 digital signals
// - it can read 1 or 2 analog signals
// - it can work in 'sample at a time' more or 'screen at a time' mode
// The only drawback is that samplingTime and screenWidthTime must be specified in milliseconds
void oscReader_millis (void *sharedMemory) {
bool doAnalogRead = !strcmp (((oscSharedMemory *) sharedMemory)->readType, "analog");
// *not needed* bool unitIsMicroSeconds = !strcmp (((oscSharedMemory *) sharedMemory)->samplingTimeUnit, "us");
int samplingTime = ((oscSharedMemory *) sharedMemory)->samplingTime;
bool positiveTrigger = ((oscSharedMemory *) sharedMemory)->positiveTrigger;
bool negativeTrigger = ((oscSharedMemory *) sharedMemory)->negativeTrigger;
unsigned char gpio1 = (unsigned char) ((oscSharedMemory *) sharedMemory)->gpio1; // easier to check validity with unsigned char then with integer
unsigned char gpio2 = (unsigned char) ((oscSharedMemory *) sharedMemory)->gpio2; // easier to check validity with unsigned char then with integer
unsigned char noOfSignals = 1; if (gpio2 <= 39) noOfSignals = 2; // monitor 1 or 2 signals
adc1_channel_t adcchannel1 = ((oscSharedMemory *) sharedMemory)->adcchannel1;
adc1_channel_t adcchannel2 = ((oscSharedMemory *) sharedMemory)->adcchannel2;
int positiveTriggerTreshold = ((oscSharedMemory *) sharedMemory)->positiveTriggerTreshold;
int negativeTriggerTreshold = ((oscSharedMemory *) sharedMemory)->negativeTriggerTreshold;
unsigned long screenWidthTime = ((oscSharedMemory *) sharedMemory)->screenWidthTime;
oscSamples *readBuffer = &((oscSharedMemory *) sharedMemory)->readBuffer;
oscSamples *sendBuffer = &((oscSharedMemory *) sharedMemory)->sendBuffer;
// Is samplingTime large enough to fill the whole screen? If not, make a correction.
if (noOfSignals == 1) {
if ((unsigned long) samplingTime * (OSCILLOSCOPE_1SIGNAL_BUFFER_SIZE - 2) < screenWidthTime) {
samplingTime = max ((int) (screenWidthTime / (OSCILLOSCOPE_1SIGNAL_BUFFER_SIZE - 2)) + 1, 1); // + 1 just to be on the safe side due to integer calculation rounding
// DEBUG: Serial.printf ("[oscilloscope][oscReader_millis] 1 signal samplingTime was too short (regarding to buffer size) and is corrected to %i\n", samplingTime);
}
} else {
if ((unsigned long) samplingTime * (OSCILLOSCOPE_2SIGNALS_BUFFER_SIZE - 2) < screenWidthTime) {
samplingTime = max ((int) (screenWidthTime / (OSCILLOSCOPE_2SIGNALS_BUFFER_SIZE - 2)) + 1, 1); // + 1 just to be on the safe side due to integer calculation rounding
// DEBUG: Serial.printf ("[oscilloscope][oscReader_millis] 2 signals samplingTime was too short (regarding to buffer size) and is corrected to %i\n", samplingTime);
}
}
// Is samplingTime is too long for 15 bits, make a correction. Max sample time can be 32767 (15 bits) but since in some case actual sample time can be much larger than required le's keep it below 5000.
if (samplingTime > 5000) {
samplingTime = 5000;
// DEBUG: Serial.printf ("[oscilloscope][oscReader_millis] samplingTime was too long (to fit in 15 bits in (almost?) all cases) and is corrected to %i\n", samplingTime);
}
// Calculate screen refresh period. It sholud be arround 50 ms (sustainable screen refresh rate is arround 20 Hz) but it is better if it is a multiple value of screenWidthTime.
unsigned long screenRefreshMilliseconds; // screen refresh period
int noOfSamplesPerScreen = screenWidthTime / samplingTime; if (noOfSamplesPerScreen * samplingTime < screenWidthTime) noOfSamplesPerScreen ++;
unsigned long correctedScreenWidthTime = noOfSamplesPerScreen * samplingTime;
// DEBUG: Serial.printf ("[oscilloscope][oscReader] screenWidthTime = %i noOfSamplesPerScreen = %lu correctedScreenWidthTime = %i\n", screenWidthTime, noOfSamplesPerScreen, correctedScreenWidthTime);
screenRefreshMilliseconds = correctedScreenWidthTime >= 50000 ? correctedScreenWidthTime / 1000 : ((50500 / correctedScreenWidthTime) * correctedScreenWidthTime) / 1000;
// DEBUG: Serial.printf ("[oscilloscope][oscReader] screenRefreshMilliseconds = %lu ms (should be close to 50 ms) => screen refresh frequency = %f Hz (should be close to 20 Hz)\n", screenRefreshMilliseconds, 1000.0 / screenRefreshMilliseconds);
// DEBUG: Serial.printf ("[oscilloscope][oscReader_millis] samplingTime = %i screenWidthTime: %i\n", samplingTime, screenWidthTime);
// determine mode of operation sample at a time or screen at a time - this only makes sense when screenWidthTime is measured in ms
bool oneSampleAtATime = screenWidthTime > 1000;
// DEBUG: Serial.printf ("[oscilloscope][oscReader] oneSampleAtATime mode = %i unit = %s screenWidthTime = %i\n", oneSampleAtATime, unitIsMicroSeconds ? "us" : "ms", screenWidthTime);
// mark sendBuffer as already beeing sent, meaning it is free now
sendBuffer->samplesAreReady = true;
// thanks to gin66 (https://github.com/BojanJurca/Esp32_oscilloscope/issues/19 we can also read GPIOs that were configured for OUTPUT or PWM
if (gpio1 <= 39) PIN_INPUT_ENABLE (GPIO_PIN_MUX_REG [gpio1]);
if (gpio2 <= 39) PIN_INPUT_ENABLE (GPIO_PIN_MUX_REG [gpio2]);
// wait for the START signal
while (((oscSharedMemory *) sharedMemory)->oscReaderState != START) delay (1);
((oscSharedMemory *) sharedMemory)->oscReaderState = STARTED;
// --- do the sampling, samplingTime and screenWidthTime are in us ---
// triggered or untriggered mode of operation
bool triggeredMode = positiveTrigger || negativeTrigger;
TickType_t lastScreenRefreshTicks = xTaskGetTickCount (); // for timing screen refresh intervals
while (((oscSharedMemory *) sharedMemory)->oscReaderState == STARTED) { // sampling from the left of the screen - while not getting STOP signal
int screenTime = 0; // in ms - how far we have already got from the left of the screen (we'll compare this value with screenWidthTime)
unsigned long deltaTime = 0; // in ms - delta from previous sample
TickType_t lastSampleTicks = xTaskGetTickCount (); // for sample timing
TickType_t newSampleTicks = lastSampleTicks;
// Insert first dummy sample to read-buffer this tells javascript client to start drawing from the left of the screen. Please note that it also tells javascript client how many signals are in each sample
if (noOfSignals == 1) readBuffer->samples1Signal [0] = {-2, -2}; // no real data sample can look like this
else readBuffer->samples2Signals [0] = {-3, -3, -3}; // no real data sample can look like this
readBuffer->sampleCount = 1;
// DEBUG: Serial.printf ("[oscilloscope][oscReader] first (dummy) sample inserted\n");
if (triggeredMode) { // if no trigger is set then skip this (waiting) part and start sampling immediatelly
// take the first sample
union {
osc1SignalSample last1SignalSample;
osc2SignalsSample last2SignalsSample;
};
#ifdef INVERT_ADC1_GET_RAW
if (noOfSignals == 1) { if (doAnalogRead) last1SignalSample = {(int16_t) (~adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) 0}; else last2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) 0}; } // gpio1 should always be valid PIN
else { if (doAnalogRead) last2SignalsSample = {(int16_t) (~adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) (~adc1_get_raw (adcchannel2) & 0xFFF), (int16_t) 0}; else last2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) digitalRead (gpio2), (int16_t) 0}; } // gpio1 should always be valid PIN
#else
if (noOfSignals == 1) { if (doAnalogRead) last1SignalSample = {(int16_t) (adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) 0}; else last2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) 0}; } // gpio1 should always be valid PIN
else { if (doAnalogRead) last2SignalsSample = {(int16_t) (adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) (adc1_get_raw (adcchannel2) & 0xFFF), (int16_t) 0}; else last2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) digitalRead (gpio2), (int16_t) 0}; } // gpio1 should always be valid PIN
#endif
// wait for trigger condition
while (((oscSharedMemory *) sharedMemory)->oscReaderState == STARTED) {
// DEBUG: Serial.printf ("[oscilloscope][oscReader] waiting for a trigger event, state = %i\n", ((oscSharedMemory *) sharedMemory)->oscReaderState);
// wait befor continuing to next sample and calculate delta offset for it
vTaskDelayUntil (&newSampleTicks, pdMS_TO_TICKS (samplingTime));
deltaTime = pdTICKS_TO_MS (newSampleTicks - lastSampleTicks); // in ms - this value will be used for the next sample offset
lastSampleTicks = newSampleTicks;
// take the second sample
union {
osc1SignalSample new1SignalSample;
osc2SignalsSample new2SignalsSample;
};
#ifdef INVERT_ADC1_GET_RAW
if (noOfSignals == 1) { if (doAnalogRead) new1SignalSample = {(int16_t) (~adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) 0}; else last2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) 0}; } // gpio1 should always be valid PIN
else { if (doAnalogRead) new2SignalsSample = {(int16_t) (~adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) ~(adc1_get_raw (adcchannel2) & 0xFFF), (int16_t) 0}; else last2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) digitalRead (gpio2), (int16_t) 0}; } // gpio1 should always be valid PIN
#else
if (noOfSignals == 1) { if (doAnalogRead) new1SignalSample = {(int16_t) (adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) 0}; else last2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) 0}; } // gpio1 should always be valid PIN
else { if (doAnalogRead) new2SignalsSample = {(int16_t) (adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) (adc1_get_raw (adcchannel2) & 0xFFF), (int16_t) 0}; else last2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) digitalRead (gpio2), (int16_t) 0}; } // gpio1 should always be valid PIN
#endif
// Compare both samples to check if the trigger condition has occured, only gpio1 is used to trigger the sampling. Please note that it doesn't matter wether we compare last1SignalSample or last2SignalsSample since they share the same space and signal1 is always on the same place.
if ((positiveTrigger && last2SignalsSample.signal1 < positiveTriggerTreshold && new2SignalsSample.signal1 >= positiveTriggerTreshold) || (negativeTrigger && last2SignalsSample.signal1 > negativeTriggerTreshold && new2SignalsSample.signal1 <= negativeTriggerTreshold)) {
// trigger condition has occured, insert both samples into read buffer
if (noOfSignals == 1) { readBuffer->samples1Signal [1] = last1SignalSample; readBuffer->samples1Signal [2] = new1SignalSample; } // timeOffset (from left of the screen) = 0, this is the first sample after triggered
else { readBuffer->samples2Signals [1] = last2SignalsSample; readBuffer->samples2Signals [2] = new2SignalsSample; } // timeOffset (from left of the screen) = 0, this is the first sample after triggered
screenTime = deltaTime; // start measuring screen time from new sample on
readBuffer->sampleCount = 3;
// correct screenTime
screenTime = deltaTime;
// wait befor continuing to next sample and calculate delta offset for it
vTaskDelayUntil (&newSampleTicks, pdMS_TO_TICKS (samplingTime));
deltaTime = pdTICKS_TO_MS (newSampleTicks - lastSampleTicks); // in ms - this value will be used for the next sample offset
lastSampleTicks = newSampleTicks;
break; // trigger event occured, stop waiting and proceed to sampling
} else {
// Just forget the first sample and continue waiting for trigger condition - copy just signal values and let the timing start from 0. Please note that it doesn't matter wether we move new1SignalSample or new2SignalsSample since they share the same space.
last2SignalsSample.signal1 = new2SignalsSample.signal1;
last2SignalsSample.signal2 = new2SignalsSample.signal2;
}
} // while not triggered
} // if in trigger mode
// take (the rest of the) samples that fit on one screen
while (((oscSharedMemory *) sharedMemory)->oscReaderState == STARTED) { // while screenTime < screenWidthTime
// if we already passed screenWidthMilliseconds then copy read buffer to send buffer so it can be sent to the javascript client
if (screenTime >= screenWidthTime || (noOfSignals == 1 && readBuffer->sampleCount >= OSCILLOSCOPE_1SIGNAL_BUFFER_SIZE) || (noOfSignals == 2 && readBuffer->sampleCount >= OSCILLOSCOPE_2SIGNALS_BUFFER_SIZE)) {
// DEBUG: Serial.printf ("[oscilloscope] end of packet sampling - full screen: %i >=? %i samplingTime = %i samples = %i\n", screenTime, screenWidthTime, samplingTime, readBuffer->sampleCount);
// copy read buffer to send buffer so that oscilloscope sender can send it to javascript client
while (oneSampleAtATime && sendBuffer->samplesAreReady) vTaskDelay (pdMS_TO_TICKS (1)); // in oneSampleAtATime mode wait until previous frame is sent
if (!sendBuffer->samplesAreReady)
*sendBuffer = *readBuffer; // this also copies 'ready' flag from read buffer which is 'true' - tell oscSender to send the packet, this would refresh client screen
// else send buffer with previous frame is still waiting to be sent, do nothing now, skip this frame
// break out of the loop and than start taking new samples
break; // get out of while loop to start sampling from the left of the screen again
}
// one sample at a time mode requires sending (copying) the readBuffer to the sendBuffer so it can be sent to the javascript client even before it gets full (of samples that fit to one screen)
if (oneSampleAtATime && readBuffer->sampleCount) {
// DEBUG: Serial.printf ("[oscilloscope][oscReader] oneSampleAtATime mode, sampleCount = %i (ready to be sent)\n", readBuffer->sampleCount);
// copy read buffer to send buffer so that oscilloscope sender can send it to javascript client
if (!sendBuffer->samplesAreReady) {
*sendBuffer = *readBuffer; // this also copies 'ready' flag from read buffer which is 'true' - tell oscSender to send the packet, this would refresh client screen
readBuffer->sampleCount = 0; // empty read buffer so we don't send the same data again later
}
// else send buffer with previous frame is still waiting to be sent, but the buffer is not full yet, so just continue sampling into the same frame
}
// take the next sample
union {
osc1SignalSample new1SignalSample;
osc2SignalsSample new2SignalsSample;
};
#ifdef INVERT_ADC1_GET_RAW
if (noOfSignals == 1) { if (doAnalogRead) new1SignalSample = {(int16_t) (~adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) deltaTime}; else new1SignalSample = {(int16_t) digitalRead (gpio1), (int16_t) deltaTime};
readBuffer->samples1Signal [readBuffer->sampleCount ++] = new1SignalSample;
} else { if (doAnalogRead) new2SignalsSample = {(int16_t) (~adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) (~adc1_get_raw (adcchannel2) & 0xFFF), (int16_t) deltaTime}; else new2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) digitalRead (gpio2), (int16_t) deltaTime};
readBuffer->samples2Signals [readBuffer->sampleCount ++] = new2SignalsSample;
}
#else
if (noOfSignals == 1) { if (doAnalogRead) new1SignalSample = {(int16_t) (adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) deltaTime}; else new1SignalSample = {(int16_t) digitalRead (gpio1), (int16_t) deltaTime};
readBuffer->samples1Signal [readBuffer->sampleCount ++] = new1SignalSample;
} else { if (doAnalogRead) new2SignalsSample = {(int16_t) (adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) (adc1_get_raw (adcchannel2) & 0xFFF), (int16_t) deltaTime}; else new2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) digitalRead (gpio2), (int16_t) deltaTime};
readBuffer->samples2Signals [readBuffer->sampleCount ++] = new2SignalsSample;
}
#endif
screenTime += deltaTime;
// wait befor continuing to next sample and calculate delta offset for it
vTaskDelayUntil (&newSampleTicks, pdMS_TO_TICKS (samplingTime));
deltaTime = pdTICKS_TO_MS (newSampleTicks - lastSampleTicks); // in ms - this value will be used for the next sample offset
// DEBUG: Serial.printf ("[oscilloscope][oscReader] seamples in readBuffer: %i screenTime = %lu screenWidthTime: %i\n", readBuffer->sampleCount, screenTime, screenWidthTime);
lastSampleTicks = newSampleTicks;
} // while screenTime < screenWidthTime
// wait before next screen refresh
vTaskDelayUntil (&lastScreenRefreshTicks, pdMS_TO_TICKS (screenRefreshMilliseconds));
} // while sampling
// DEBUG: Serial.printf ("[oscilloscope][oscReader] stopping, state = %i\n", ((oscSharedMemory *) sharedMemory)->oscReaderState);
// wait for the STOP signal
while (((oscSharedMemory *) sharedMemory)->oscReaderState != STOP) delay (1);
((oscSharedMemory *) sharedMemory)->oscReaderState = STOPPED;
vTaskDelete (NULL);
}
// oscReader that takes digital samples (digitalRead) on time interval specified in microseconds
// - it can only read 1 or 2 digital signals
// - it can only work in 'screen at a time' mode (which is not a drawback for the sampling is measured in microseconds)
void oscReader_digital (void *sharedMemory) {
// *not needed* bool doAnalogRead = !strcmp (((oscSharedMemory *) sharedMemory)->readType, "analog");
// *not needed* bool unitIsMicroSeconds = !strcmp (((oscSharedMemory *) sharedMemory)->samplingTimeUnit, "us");
int samplingTime = ((oscSharedMemory *) sharedMemory)->samplingTime;
bool positiveTrigger = ((oscSharedMemory *) sharedMemory)->positiveTrigger;
bool negativeTrigger = ((oscSharedMemory *) sharedMemory)->negativeTrigger;
unsigned char gpio1 = (unsigned char) ((oscSharedMemory *) sharedMemory)->gpio1; // easier to check validity with unsigned char then with integer
unsigned char gpio2 = (unsigned char) ((oscSharedMemory *) sharedMemory)->gpio2; // easier to check validity with unsigned char then with integer
unsigned char noOfSignals = 1; if (gpio2 <= 39) noOfSignals = 2; // monitor 1 or 2 signals
// *not needed* adc1_channel_t adcchannel1 = ((oscSharedMemory *) sharedMemory)->adcchannel1;
// *not needed* adc1_channel_t adcchannel2 = ((oscSharedMemory *) sharedMemory)->adcchannel2;
int positiveTriggerTreshold = ((oscSharedMemory *) sharedMemory)->positiveTriggerTreshold;
int negativeTriggerTreshold = ((oscSharedMemory *) sharedMemory)->negativeTriggerTreshold;
unsigned long screenWidthTime = ((oscSharedMemory *) sharedMemory)->screenWidthTime;
oscSamples *readBuffer = &((oscSharedMemory *) sharedMemory)->readBuffer;
oscSamples *sendBuffer = &((oscSharedMemory *) sharedMemory)->sendBuffer;
// Is samplingTime large enough to fill the whole screen? If not, make a correction.
if (noOfSignals == 1) {
if ((unsigned long) samplingTime * (OSCILLOSCOPE_1SIGNAL_BUFFER_SIZE - 2) < screenWidthTime) {
samplingTime = max ((int) (screenWidthTime / (OSCILLOSCOPE_1SIGNAL_BUFFER_SIZE - 2)) + 1, 1); // + 1 just to be on the safe side due to integer calculation rounding
// DEBUG: Serial.printf ("[oscilloscope][oscReader_digital] 1 signal samplingTime was too short (regarding to buffer size) and is corrected to %i\n", samplingTime);
}
} else {
if ((unsigned long) samplingTime * (OSCILLOSCOPE_2SIGNALS_BUFFER_SIZE - 2) < screenWidthTime) {
samplingTime = max ((int) (screenWidthTime / (OSCILLOSCOPE_2SIGNALS_BUFFER_SIZE - 2)) + 1, 1); // + 1 just to be on the safe side due to integer calculation rounding
// DEBUG: Serial.printf ("[oscilloscope][oscReader_digital] 2 signals samplingTime was too short (regarding to buffer size) and is corrected to %i\n", samplingTime);
}
}
// Is samplingTime is too long for 15 bits, make a correction. Max sample time can be 32767 (15 bits) but since in some case actual sample time can be much larger than required le's keep it below 5000.
if (samplingTime > 5000) {
samplingTime = 5000;
// DEBUG: Serial.printf ("[oscilloscope][oscReader_digital] samplingTime was too long (to fit in 15 bits in (almost?) all cases) and is corrected to %i\n", samplingTime);
}
// Calculate screen refresh period. It sholud be arround 50 ms (sustainable screen refresh rate is arround 20 Hz) but it is better if it is a multiple value of screenWidthTime.
unsigned long screenRefreshMilliseconds; // screen refresh period
int noOfSamplesPerScreen = screenWidthTime / samplingTime; if (noOfSamplesPerScreen * samplingTime < screenWidthTime) noOfSamplesPerScreen ++;
unsigned long correctedScreenWidthTime = noOfSamplesPerScreen * samplingTime;
// DEBUG: Serial.printf ("[oscilloscope][oscReader] screenWidthTime = %i noOfSamplesPerScreen = %lu correctedScreenWidthTime = %i\n", screenWidthTime, noOfSamplesPerScreen, correctedScreenWidthTime);
screenRefreshMilliseconds = correctedScreenWidthTime >= 50000 ? correctedScreenWidthTime / 1000 : ((50500 / correctedScreenWidthTime) * correctedScreenWidthTime) / 1000;
// DEBUG: Serial.printf ("[oscilloscope][oscReader] screenRefreshMilliseconds = %lu ms (should be close to 50 ms) => screen refresh frequency = %f Hz (should be close to 20 Hz)\n", screenRefreshMilliseconds, 1000.0 / screenRefreshMilliseconds);
// DEBUG: Serial.printf ("[oscilloscope][oscReader_digital] samplingTime = %i screenWidthTime: %i\n", samplingTime, screenWidthTime);
// mark sendBuffer as already beeing sent, meaning it is free now
sendBuffer->samplesAreReady = true;
// thanks to gin66 (https://github.com/BojanJurca/Esp32_oscilloscope/issues/19 we can also read GPIOs that were configured for OUTPUT or PWM
if (gpio1 <= 39) PIN_INPUT_ENABLE (GPIO_PIN_MUX_REG [gpio1]);
if (gpio2 <= 39) PIN_INPUT_ENABLE (GPIO_PIN_MUX_REG [gpio2]);
// wait for the START signal
while (((oscSharedMemory *) sharedMemory)->oscReaderState != START) delay (1);
((oscSharedMemory *) sharedMemory)->oscReaderState = STARTED;
// --- do the sampling, samplingTime and screenWidthTime are in us ---
// triggered or untriggered mode of operation
bool triggeredMode = positiveTrigger || negativeTrigger;
TickType_t lastScreenRefreshTicks = xTaskGetTickCount (); // for timing screen refresh intervals
while (((oscSharedMemory *) sharedMemory)->oscReaderState == STARTED) { // sampling from the left of the screen - while not getting STOP signal
unsigned long screenTime = 0; // in us - how far we have already got from the left of the screen (we'll compare this value with screenWidthTime)
unsigned long deltaTime = 0; // in us - delta from previous sample
unsigned long lastSampleMicroseconds = micros (); // for sample timing
unsigned long newSampleMicroseconds = lastSampleMicroseconds;
// Insert first dummy sample to read-buffer this tells javascript client to start drawing from the left of the screen. Please note that it also tells javascript client how many signals are in each sample
if (noOfSignals == 1) readBuffer->samples1Signal [0] = {-2, -2}; // no real data sample can look like this
else readBuffer->samples2Signals [0] = {-3, -3, -3}; // no real data sample can look like this
readBuffer->sampleCount = 1;
// DEBUG: Serial.printf ("[oscilloscope][oscReader_digital] first (dummy) sample inserted\n");
if (triggeredMode) { // if no trigger is set then skip this (waiting) part and start sampling immediatelly
// take the first sample
union {
osc1SignalSample last1SignalSample;
osc2SignalsSample last2SignalsSample;
};
if (noOfSignals == 1) { last2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) 0}; } // gpio1 should always be valid PIN
else { last2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) digitalRead (gpio2), (int16_t) 0}; } // gpio1 should always be valid PIN
// wait for trigger condition
while (((oscSharedMemory *) sharedMemory)->oscReaderState == STARTED) {
// DEBUG: Serial.printf ("[oscilloscope][oscReader_digital] waiting for a trigger event, state = %i\n", ((oscSharedMemory *) sharedMemory)->oscReaderState);
// wait befor continuing to next sample and calculate delta offset for it
delayMicrosecondsUntil (&newSampleMicroseconds, samplingTime);
deltaTime = newSampleMicroseconds - lastSampleMicroseconds;
lastSampleMicroseconds = newSampleMicroseconds;
// take the second sample
union {
osc1SignalSample new1SignalSample;
osc2SignalsSample new2SignalsSample;
};
if (noOfSignals == 1) { new2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) 0}; } // gpio1 should always be valid PIN
else { new2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) digitalRead (gpio2), (int16_t) 0}; } // gpio1 should always be valid PIN
// Compare both samples to check if the trigger condition has occured, only gpio1 is used to trigger the sampling. Please note that it doesn't matter wether we compare last1SignalSample or last2SignalsSample since they share the same space and signal1 is always on the same place.
if ((positiveTrigger && last2SignalsSample.signal1 < positiveTriggerTreshold && new2SignalsSample.signal1 >= positiveTriggerTreshold) || (negativeTrigger && last2SignalsSample.signal1 > negativeTriggerTreshold && new2SignalsSample.signal1 <= negativeTriggerTreshold)) {
// trigger condition has occured, insert both samples into read buffer
if (noOfSignals == 1) { readBuffer->samples1Signal [1] = last1SignalSample; readBuffer->samples1Signal [2] = new1SignalSample; } // timeOffset (from left of the screen) = 0, this is the first sample after triggered
else { readBuffer->samples2Signals [1] = last2SignalsSample; readBuffer->samples2Signals [2] = new2SignalsSample; } // timeOffset (from left of the screen) = 0, this is the first sample after triggered
screenTime = deltaTime; // start measuring screen time from new sample on
readBuffer->sampleCount = 3;
// correct screenTime
screenTime = deltaTime;
// wait befor continuing to next sample and calculate delta offset for it
delayMicrosecondsUntil (&newSampleMicroseconds, samplingTime);
deltaTime = newSampleMicroseconds - lastSampleMicroseconds;
lastSampleMicroseconds = newSampleMicroseconds;
break; // trigger event occured, stop waiting and proceed to sampling
} else {
// Just forget the first sample and continue waiting for trigger condition - copy just signal values and let the timing start from 0. Please note that it doesn't matter wether we move new1SignalSample or new2SignalsSample since they share the same space.
last2SignalsSample.signal1 = new2SignalsSample.signal1;
last2SignalsSample.signal2 = new2SignalsSample.signal2;
}
} // while not triggered
} // if in trigger mode
// take (the rest of the) samples that fit on one screen
while (((oscSharedMemory *) sharedMemory)->oscReaderState == STARTED) { // while screenTime < screenWidthTime
// if we already passed screenWidthMilliseconds then copy read buffer to send buffer so it can be sent to the javascript client
if (screenTime >= screenWidthTime || (noOfSignals == 1 && readBuffer->sampleCount >= OSCILLOSCOPE_1SIGNAL_BUFFER_SIZE) || (noOfSignals == 2 && readBuffer->sampleCount >= OSCILLOSCOPE_2SIGNALS_BUFFER_SIZE)) {
// DEBUG: Serial.printf ("[oscilloscope] end of packet sampling - full screen: %i >=? %i samplingTime = %i samples = %i\n", screenTime, screenWidthTime, samplingTime, readBuffer->sampleCount);
// copy read buffer to send buffer so that oscilloscope sender can send it to javascript client
if (!sendBuffer->samplesAreReady)
*sendBuffer = *readBuffer; // this also copies 'ready' flag from read buffer which is 'true' - tell oscSender to send the packet, this would refresh client screen
// else send buffer with previous frame is still waiting to be sent, do nothing now, skip this frame
// break out of the loop and than start taking new samples
break; // get out of while loop to start sampling from the left of the screen again
}
// take the next sample
union {
osc1SignalSample new1SignalSample;
osc2SignalsSample new2SignalsSample;
};
if (noOfSignals == 1) { new1SignalSample = {(int16_t) digitalRead (gpio1), (int16_t) deltaTime};
readBuffer->samples1Signal [readBuffer->sampleCount ++] = new1SignalSample;
} else { new2SignalsSample = {(int16_t) digitalRead (gpio1), (int16_t) digitalRead (gpio2), (int16_t) deltaTime};
readBuffer->samples2Signals [readBuffer->sampleCount ++] = new2SignalsSample;
}
screenTime += deltaTime;
// wait befor continuing to next sample and calculate delta offset for it
delayMicrosecondsUntil (&newSampleMicroseconds, samplingTime);
deltaTime = newSampleMicroseconds - lastSampleMicroseconds;
lastSampleMicroseconds = newSampleMicroseconds;
} // while screenTime < screenWidthTime
// wait before next screen refresh
vTaskDelayUntil (&lastScreenRefreshTicks, pdMS_TO_TICKS (screenRefreshMilliseconds));
} // while sampling
// DEBUG: Serial.printf ("[oscilloscope][oscReader] stopping, state = %i\n", ((oscSharedMemory *) sharedMemory)->oscReaderState);
// wait for the STOP signal
while (((oscSharedMemory *) sharedMemory)->oscReaderState != STOP) delay (1);
((oscSharedMemory *) sharedMemory)->oscReaderState = STOPPED;
vTaskDelete (NULL);
}
// oscReader that takes analog samples (analogRead) on time interval specified in microseconds
// - it can only read 1 or 2 digital signals
// - it can only work in 'screen at a time' mode (which is not a drawback for the sampling is measured in microseconds)
void oscReader_analog (void *sharedMemory) {
// *not needed* bool doAnalogRead = !strcmp (((oscSharedMemory *) sharedMemory)->readType, "analog");
// *not needed* bool unitIsMicroSeconds = !strcmp (((oscSharedMemory *) sharedMemory)->samplingTimeUnit, "us");
int samplingTime = ((oscSharedMemory *) sharedMemory)->samplingTime;
bool positiveTrigger = ((oscSharedMemory *) sharedMemory)->positiveTrigger;
bool negativeTrigger = ((oscSharedMemory *) sharedMemory)->negativeTrigger;
unsigned char gpio1 = (unsigned char) ((oscSharedMemory *) sharedMemory)->gpio1; // easier to check validity with unsigned char then with integer
unsigned char gpio2 = (unsigned char) ((oscSharedMemory *) sharedMemory)->gpio2; // easier to check validity with unsigned char then with integer
unsigned char noOfSignals = 1; if (gpio2 <= 39) noOfSignals = 2; // monitor 1 or 2 signals
adc1_channel_t adcchannel1 = ((oscSharedMemory *) sharedMemory)->adcchannel1;
adc1_channel_t adcchannel2 = ((oscSharedMemory *) sharedMemory)->adcchannel2;
int positiveTriggerTreshold = ((oscSharedMemory *) sharedMemory)->positiveTriggerTreshold;
int negativeTriggerTreshold = ((oscSharedMemory *) sharedMemory)->negativeTriggerTreshold;
unsigned long screenWidthTime = ((oscSharedMemory *) sharedMemory)->screenWidthTime;
oscSamples *readBuffer = &((oscSharedMemory *) sharedMemory)->readBuffer;
oscSamples *sendBuffer = &((oscSharedMemory *) sharedMemory)->sendBuffer;
// Is samplingTime large enough to fill the whole screen? If not, make a correction.
if (noOfSignals == 1) {
if ((unsigned long) samplingTime * (OSCILLOSCOPE_1SIGNAL_BUFFER_SIZE - 2) < screenWidthTime) {
samplingTime = max ((int) (screenWidthTime / (OSCILLOSCOPE_1SIGNAL_BUFFER_SIZE - 2)) + 1, 1); // + 1 just to be on the safe side due to integer calculation rounding
// DEBUG: Serial.printf ("[oscilloscope][oscReader_analog] 1 signal samplingTime was too short (regarding to buffer size) and is corrected to %i\n", samplingTime);
}
} else {
if ((unsigned long) samplingTime * (OSCILLOSCOPE_2SIGNALS_BUFFER_SIZE - 2) < screenWidthTime) {
samplingTime = max ((int) (screenWidthTime / (OSCILLOSCOPE_2SIGNALS_BUFFER_SIZE - 2)) + 1, 1); // + 1 just to be on the safe side due to integer calculation rounding
// DEBUG: Serial.printf ("[oscilloscope][oscReader_analog] 2 signals samplingTime was too short (regarding to buffer size) and is corrected to %i\n", samplingTime);
}
}
// Is samplingTime is too long for 15 bits, make a correction. Max sample time can be 32767 (15 bits) but since in some case actual sample time can be much larger than required le's keep it below 5000.
if (samplingTime > 5000) {
samplingTime = 5000;
// DEBUG: Serial.printf ("[oscilloscope][oscReader_analog] samplingTime was too long (to fit in 15 bits in (almost?) all cases) and is corrected to %i\n", samplingTime);
}
// DEBUG: Serial.printf ("[oscilloscope][oscReader_analog] samplingTime = %i, screenWidthTime = %i\n", samplingTime, screenWidthTime);
if (noOfSignals == 2 && screenWidthTime <= 200 || noOfSignals == 1 && screenWidthTime <= 100) {
#ifdef __DMESG__
dmesg ("[oscilloscope] the settings exceed oscilloscope capabilities.");
#endif
((oscSharedMemory *) sharedMemory)->webSocket->sendString ("[oscilloscope] the settings exceed oscilloscope capabilities."); // send error to javascript client
while (((oscSharedMemory *) sharedMemory)->oscReaderState != STOP) delay (1);
((oscSharedMemory *) sharedMemory)->oscReaderState = STOPPED;
vTaskDelete (NULL);
}
// Calculate screen refresh period. It sholud be arround 50 ms (sustainable screen refresh rate is arround 20 Hz) but it is better if it is a multiple value of screenWidthTime.
unsigned long screenRefreshMilliseconds; // screen refresh period
int noOfSamplesPerScreen = screenWidthTime / samplingTime; if (noOfSamplesPerScreen * samplingTime < screenWidthTime) noOfSamplesPerScreen ++;
unsigned long correctedScreenWidthTime = noOfSamplesPerScreen * samplingTime;
// DEBUG: Serial.printf ("[oscilloscope][oscReader] screenWidthTime = %i noOfSamplesPerScreen = %lu correctedScreenWidthTime = %i\n", screenWidthTime, noOfSamplesPerScreen, correctedScreenWidthTime);
screenRefreshMilliseconds = correctedScreenWidthTime >= 50000 ? correctedScreenWidthTime / 1000 : ((50500 / correctedScreenWidthTime) * correctedScreenWidthTime) / 1000;
// DEBUG: Serial.printf ("[oscilloscope][oscReader] screenRefreshMilliseconds = %lu ms (should be close to 50 ms) => screen refresh frequency = %f Hz (should be close to 20 Hz)\n", screenRefreshMilliseconds, 1000.0 / screenRefreshMilliseconds);
// DEBUG: Serial.printf ("[oscilloscope][oscReader_analog] samplingTime = %i screenWidthTime: %i\n", samplingTime, screenWidthTime);
// mark sendBuffer as already beeing sent, meaning it is free now
sendBuffer->samplesAreReady = true;
// thanks to gin66 (https://github.com/BojanJurca/Esp32_oscilloscope/issues/19 we can also read GPIOs that were configured for OUTPUT or PWM
if (gpio1 <= 39) PIN_INPUT_ENABLE (GPIO_PIN_MUX_REG [gpio1]);
if (gpio2 <= 39) PIN_INPUT_ENABLE (GPIO_PIN_MUX_REG [gpio2]);
// wait for the START signal
while (((oscSharedMemory *) sharedMemory)->oscReaderState != START) delay (1);
((oscSharedMemory *) sharedMemory)->oscReaderState = STARTED;
// --- do the sampling, samplingTime and screenWidthTime are in us ---
// triggered or untriggered mode of operation
bool triggeredMode = positiveTrigger || negativeTrigger;
TickType_t lastScreenRefreshTicks = xTaskGetTickCount (); // for timing screen refresh intervals
while (((oscSharedMemory *) sharedMemory)->oscReaderState == STARTED) { // sampling from the left of the screen - while not getting STOP signal
unsigned long screenTime = 0; // in us - how far we have already got from the left of the screen (we'll compare this value with screenWidthTime)
unsigned long deltaTime = 0; // in us - delta from previous sample
unsigned long lastSampleMicroseconds = micros (); // for sample timing
unsigned long newSampleMicroseconds = lastSampleMicroseconds;
// Insert first dummy sample to read-buffer this tells javascript client to start drawing from the left of the screen. Please note that it also tells javascript client how many signals are in each sample
if (noOfSignals == 1) readBuffer->samples1Signal [0] = {-2, -2}; // no real data sample can look like this
else readBuffer->samples2Signals [0] = {-3, -3, -3}; // no real data sample can look like this
readBuffer->sampleCount = 1;
// DEBUG: Serial.printf ("[oscilloscope][oscReader_digital] first (dummy) sample inserted\n");
if (triggeredMode) { // if no trigger is set then skip this (waiting) part and start sampling immediatelly
// take the first sample
union {
osc1SignalSample last1SignalSample;
osc2SignalsSample last2SignalsSample;
};
#ifdef INVERT_ADC1_GET_RAW
if (noOfSignals == 1) { last2SignalsSample = {(int16_t) (~adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) 0}; } // gpio1 should always be valid PIN
else { last2SignalsSample = {(int16_t) (~adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) (~adc1_get_raw (adcchannel2) & 0xFFF), (int16_t) 0}; } // gpio1 should always be valid PIN
#else
if (noOfSignals == 1) { last2SignalsSample = {(int16_t) (adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) 0}; } // gpio1 should always be valid PIN
else { last2SignalsSample = {(int16_t) (adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) (adc1_get_raw (adcchannel2) & 0xFFF), (int16_t) 0}; } // gpio1 should always be valid PIN
#endif
// wait for trigger condition
while (((oscSharedMemory *) sharedMemory)->oscReaderState == STARTED) {
// DEBUG: Serial.printf ("[oscilloscope][oscReader_digital] waiting for a trigger event, state = %i\n", ((oscSharedMemory *) sharedMemory)->oscReaderState);
// wait befor continuing to next sample and calculate delta offset for it
delayMicrosecondsUntil (&newSampleMicroseconds, samplingTime);
deltaTime = newSampleMicroseconds - lastSampleMicroseconds;
lastSampleMicroseconds = newSampleMicroseconds;
// take the second sample
union {
osc1SignalSample new1SignalSample;
osc2SignalsSample new2SignalsSample;
};
#ifdef INVERT_ADC1_GET_RAW
if (noOfSignals == 1) { new2SignalsSample = {(int16_t) (~adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) 0}; } // gpio1 should always be valid PIN
else { new2SignalsSample = {(int16_t) (~adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) (~adc1_get_raw (adcchannel2) & 0xFFF), (int16_t) 0}; } // gpio1 should always be valid PIN
#else
if (noOfSignals == 1) { new2SignalsSample = {(int16_t) (adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) 0}; } // gpio1 should always be valid PIN
else { new2SignalsSample = {(int16_t) (adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) (adc1_get_raw (adcchannel2) & 0xFFF), (int16_t) 0}; } // gpio1 should always be valid PIN
#endif
// Compare both samples to check if the trigger condition has occured, only gpio1 is used to trigger the sampling. Please note that it doesn't matter wether we compare last1SignalSample or last2SignalsSample since they share the same space and signal1 is always on the same place.
if ((positiveTrigger && last2SignalsSample.signal1 < positiveTriggerTreshold && new2SignalsSample.signal1 >= positiveTriggerTreshold) || (negativeTrigger && last2SignalsSample.signal1 > negativeTriggerTreshold && new2SignalsSample.signal1 <= negativeTriggerTreshold)) {
// trigger condition has occured, insert both samples into read buffer
if (noOfSignals == 1) { readBuffer->samples1Signal [1] = last1SignalSample; readBuffer->samples1Signal [2] = new1SignalSample; } // timeOffset (from left of the screen) = 0, this is the first sample after triggered
else { readBuffer->samples2Signals [1] = last2SignalsSample; readBuffer->samples2Signals [2] = new2SignalsSample; } // timeOffset (from left of the screen) = 0, this is the first sample after triggered
screenTime = deltaTime; // start measuring screen time from new sample on
readBuffer->sampleCount = 3;
// correct screenTime
screenTime = deltaTime;
// wait befor continuing to next sample and calculate delta offset for it
delayMicrosecondsUntil (&newSampleMicroseconds, samplingTime);
deltaTime = newSampleMicroseconds - lastSampleMicroseconds;
lastSampleMicroseconds = newSampleMicroseconds;
break; // trigger event occured, stop waiting and proceed to sampling
} else {
// Just forget the first sample and continue waiting for trigger condition - copy just signal values and let the timing start from 0. Please note that it doesn't matter wether we move new1SignalSample or new2SignalsSample since they share the same space.
last2SignalsSample.signal1 = new2SignalsSample.signal1;
last2SignalsSample.signal2 = new2SignalsSample.signal2;
}
} // while not triggered
} // if in trigger mode
// take (the rest of the) samples that fit on one screen
while (((oscSharedMemory *) sharedMemory)->oscReaderState == STARTED) { // while screenTime < screenWidthTime
// if we already passed screenWidthMilliseconds then copy read buffer to send buffer so it can be sent to the javascript client
if (screenTime >= screenWidthTime || (noOfSignals == 1 && readBuffer->sampleCount >= OSCILLOSCOPE_1SIGNAL_BUFFER_SIZE) || (noOfSignals == 2 && readBuffer->sampleCount >= OSCILLOSCOPE_2SIGNALS_BUFFER_SIZE)) {
// DEBUG: Serial.printf ("[oscilloscope] end of packet sampling - full screen: %i >=? %i samplingTime = %i samples = %i\n", screenTime, screenWidthTime, samplingTime, readBuffer->sampleCount);
// copy read buffer to send buffer so that oscilloscope sender can send it to javascript client
if (!sendBuffer->samplesAreReady)
*sendBuffer = *readBuffer; // this also copies 'ready' flag from read buffer which is 'true' - tell oscSender to send the packet, this would refresh client screen
// else send buffer with previous frame is still waiting to be sent, do nothing now, skip this frame
// break out of the loop and than start taking new samples
break; // get out of while loop to start sampling from the left of the screen again
}
// take the next sample
union {
osc1SignalSample new1SignalSample;
osc2SignalsSample new2SignalsSample;
};
#ifdef INVERT_ADC1_GET_RAW
if (noOfSignals == 1) { new1SignalSample = {(int16_t) (~adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) deltaTime};
readBuffer->samples1Signal [readBuffer->sampleCount ++] = new1SignalSample;
} else { new2SignalsSample = {(int16_t) (~adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) (~adc1_get_raw (adcchannel2) & 0xFFF), (int16_t) deltaTime};
readBuffer->samples2Signals [readBuffer->sampleCount ++] = new2SignalsSample;
}
#else
if (noOfSignals == 1) { new1SignalSample = {(int16_t) (adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) deltaTime};
readBuffer->samples1Signal [readBuffer->sampleCount ++] = new1SignalSample;
} else { new2SignalsSample = {(int16_t) (adc1_get_raw (adcchannel1) & 0xFFF), (int16_t) (adc1_get_raw (adcchannel2) & 0xFFF), (int16_t) deltaTime};
readBuffer->samples2Signals [readBuffer->sampleCount ++] = new2SignalsSample;
}
#endif
screenTime += deltaTime;
// wait befor continuing to next sample and calculate delta offset for it
delayMicrosecondsUntil (&newSampleMicroseconds, samplingTime);
deltaTime = newSampleMicroseconds - lastSampleMicroseconds;
lastSampleMicroseconds = newSampleMicroseconds;
} // while screenTime < screenWidthTime
// wait before next screen refresh
vTaskDelayUntil (&lastScreenRefreshTicks, pdMS_TO_TICKS (screenRefreshMilliseconds));
} // while sampling
// DEBUG: Serial.printf ("[oscilloscope][oscReader] stopping, state = %i\n", ((oscSharedMemory *) sharedMemory)->oscReaderState);
// wait for the STOP signal
while (((oscSharedMemory *) sharedMemory)->oscReaderState != STOP) delay (1);
((oscSharedMemory *) sharedMemory)->oscReaderState = STOPPED;
vTaskDelete (NULL);
}
void oscReader_analog_1_signal_continuous (void *sharedMemory) {
// *not needed* bool doAnalogRead = !strcmp (((oscSharedMemory *) sharedMemory)->readType, "analog");
// *not needed* bool unitIsMicroSeconds = !strcmp (((oscSharedMemory *) sharedMemory)->samplingTimeUnit, "us");
int samplingTime = ((oscSharedMemory *) sharedMemory)->samplingTime;
bool positiveTrigger = ((oscSharedMemory *) sharedMemory)->positiveTrigger;
bool negativeTrigger = ((oscSharedMemory *) sharedMemory)->negativeTrigger;
unsigned char gpio1 = (unsigned char) ((oscSharedMemory *) sharedMemory)->gpio1; // easier to check validity with unsigned char then with integer
// * not needed * unsigned char gpio2 = (unsigned char) ((oscSharedMemory *) sharedMemory)->gpio2; // easier to check validity with unsigned char then with integer
// * not needed * unsigned char noOfSignals = 1; if (gpio2 <= 39) noOfSignals = 2; // monitor 1 or 2 signals
adc1_channel_t adcchannel1 = ((oscSharedMemory *) sharedMemory)->adcchannel1;
// * not needed * adc1_channel_t adcchannel2 = ((oscSharedMemory *) sharedMemory)->adcchannel2;
int positiveTriggerTreshold = ((oscSharedMemory *) sharedMemory)->positiveTriggerTreshold;
int negativeTriggerTreshold = ((oscSharedMemory *) sharedMemory)->negativeTriggerTreshold;
unsigned long screenWidthTime = ((oscSharedMemory *) sharedMemory)->screenWidthTime;
oscSamples *readBuffer = &((oscSharedMemory *) sharedMemory)->readBuffer;
oscSamples *sendBuffer = &((oscSharedMemory *) sharedMemory)->sendBuffer;
// calculate correct sampling time so that it will prefectly aligh with sampleRate (regarding integer calculation rounding) and that the sample buffer is large enough
unsigned long sampleRate = 1000000 / samplingTime; // samplingTime is in us
int noOfSamplesToTakeFirstTime = sampleRate * screenWidthTime / 1000000; // screenWidthTime is in us
while (samplingTime != 1000000 / sampleRate // integer clculation rounding missmatch
|| (unsigned long) samplingTime * (OSCILLOSCOPE_CONTINUOUS_BUFFER_SIZE - 2) < screenWidthTime // samples do not fill the screen
|| noOfSamplesToTakeFirstTime > (OSCILLOSCOPE_CONTINUOUS_BUFFER_SIZE - 2 - 1 - 1 - 8) // samples do not fit in the buffer ... - 1 due to i2s_read bug (we may do 2 readings so - 1 again) ... please read ahead, - 8 because i2s_read can not read less than 8 samples
|| samplingTime < 4 // dummy values -2 and -3 are already taken for different types of buffer
|| samplingTime < 7) // max ESP32 sampling rate = 150 kHz (sampling time >= 6.6 us)
{
samplingTime ++;
sampleRate = 1000000 / samplingTime;
noOfSamplesToTakeFirstTime = sampleRate * screenWidthTime / 1000000 + 1; // + 1 sample more than distance between them
}
// there is a bug in i2s_read: it odd number of samples are to be read the last one is always 0, so make sure we have even number of samples
if (noOfSamplesToTakeFirstTime % 2 != 0) noOfSamplesToTakeFirstTime ++;
if (noOfSamplesToTakeFirstTime < 8) noOfSamplesToTakeFirstTime = 8;
// Is samplingTime is too long for 15 bits, make a correction. Max sample time can be 32767 (15 bits) but since in some case actual sample time can be much larger than required le's keep it below 5000.
if (samplingTime > 32767) {
samplingTime = 32767;
// DEBUG: Serial.printf ("[oscilloscope][oscReader_oscReader_analog_1_signal_continuous] sampling with these parameters will not be precise\n");
#ifdef __DMESG__
dmesg ("[oscilloscope][oscReader_oscReader_analog_1_signal_continuous] sampling with these parameters will not be precise");
#endif
}
// DEBUG: Serial.printf ("[oscilloscope][oscReader_analog_1_signal_continuous] samplingTime: %lu sampleRate: %lu noOfSamplesToTake: %lu screenWidthTime: %lu ... positiveTriggerTreshold = %i, negativeTriggerTreshold = %i\n", samplingTime, sampleRate, noOfSamplesToTakeFirstTime, screenWidthTime, positiveTriggerTreshold, negativeTriggerTreshold);
// Calculate screen refresh period. It sholud be arround 50 ms (sustainable screen refresh rate is arround 20 Hz) but it is better if it is a multiple value of screenWidthTime.
unsigned long screenRefreshMilliseconds; // screen refresh period
int noOfSamplesPerScreen = screenWidthTime / samplingTime; if (noOfSamplesPerScreen * samplingTime < screenWidthTime) noOfSamplesPerScreen ++;
unsigned long correctedScreenWidthTime = noOfSamplesPerScreen * samplingTime;
// DEBUG: Serial.printf ("[oscilloscope][oscReader] screenWidthTime = %i noOfSamplesPerScreen = %lu correctedScreenWidthTime = %i\n", screenWidthTime, noOfSamplesPerScreen, correctedScreenWidthTime);
screenRefreshMilliseconds = correctedScreenWidthTime >= 50000 ? correctedScreenWidthTime / 1000 : ((50500 / correctedScreenWidthTime) * correctedScreenWidthTime) / 1000;
// DEBUG: Serial.printf ("[oscilloscope][oscReader] screenRefreshMilliseconds = %lu ms (should be close to 50 ms) => screen refresh frequency = %f Hz (should be close to 20 Hz)\n", screenRefreshMilliseconds, 1000.0 / screenRefreshMilliseconds);
// mark sendBuffer as already beeing sent, meaning it is free now
sendBuffer->samplesAreReady = true;
// thanks to gin66 (https://github.com/BojanJurca/Esp32_oscilloscope/issues/19 we can also read GPIOs that were configured for OUTPUT or PWM
if (gpio1 <= 39) PIN_INPUT_ENABLE (GPIO_PIN_MUX_REG [gpio1]);
// * not needed * if (gpio2 <= 39) PIN_INPUT_ENABLE (GPIO_PIN_MUX_REG [gpio2]);
// wait for the START signal
while (((oscSharedMemory *) sharedMemory)->oscReaderState != START) delay (1);
((oscSharedMemory *) sharedMemory)->oscReaderState = STARTED;
// --- do the sampling, samplingTime and screenWidthTime are in us ---
// triggered or untriggered mode of operation
bool triggeredMode = positiveTrigger || negativeTrigger;
int noOfSamplesTaken;
TickType_t lastScreenRefreshTicks = xTaskGetTickCount (); // for timing screen refresh intervals
while (((oscSharedMemory *) sharedMemory)->oscReaderState == STARTED) { // sampling from the left of the screen - while not getting STOP signal
// Insert first dummy sample to read-buffer this tells javascript client to start drawing from the left of the screen. Please note that it also tells javascript client how many signals are in each sample
readBuffer->samplesContinuousSignal [0].signal1 = -samplingTime; // no real data sample can look like this
// readBuffer->sampleCount = 1;
// DEBUG: Serial.printf ("[oscilloscope][oscReader_analog_1_signal] first (dummy) sample inserted\n");
// take (the rest of the) samples that fit on one screen: https://www.instructables.com/The-Best-Way-for-Sampling-Audio-With-ESP32
// setupI2S
esp_err_t err;
i2s_config_t i2s_config = {
.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
.sample_rate = (uint32_t) 1000000 / samplingTime, // = samplingFrequency (samplingTime is in us),
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // could only get it to work with 32bits
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // <- mono signal - stereo signal -> I2S_CHANNEL_FMT_RIGHT_LEFT, // although the SEL config should be left, it seems to transmit on right
.communication_format = i2s_comm_format_t (I2S_COMM_FORMAT_STAND_I2S), //// I2S_COMM_FORMAT_STAND_I2S, // I2S_COMM_FORMAT_I2S_MSB, - deprecated
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // Interrupt level 1
.dma_buf_count = 4, // number of buffers
.dma_buf_len = noOfSamplesToTakeFirstTime, // samples per buffer
.use_apll = true // false//,
// .tx_desc_auto_clear = false,
// .fixed_ mclk = 1
};
err = i2s_driver_install (I2S_NUM_0, &i2s_config, 0, NULL); //step 2
if (err != ESP_OK) {
Serial.printf ("Failed installing driver: %d\n", err);
#ifdef __DMESG__
dmesg ("[oscilloscope][oscReader_oscReader_analog_1_signal_continuous] failed to install the driver: ", err);
#endif
((oscSharedMemory *) sharedMemory)->webSocket->sendString ("[oscilloscope] failed to install the i2s driver."); // send error to javascript client
// wait for the STOP signal
while (((oscSharedMemory *) sharedMemory)->oscReaderState != STOP) delay (1);
((oscSharedMemory *) sharedMemory)->oscReaderState = STOPPED;
vTaskDelete (NULL);
}
// doesn't help:
// The raw ADC data is written in DMA in inverted form. This add aninversion: https://www.esp32.com/viewtopic.php?t=12324
// SET_PERI_REG_MASK(SYSCON_SARADC_CTRL2_REG, SYSCON_SARADC_SAR1_INV);
err = i2s_set_adc_mode (ADC_UNIT_1, adcchannel1);
if (err != ESP_OK) {
Serial.printf ("Failed setting up adc mode: %d\n", err);
#ifdef __DMESG__
dmesg ("[oscilloscope][oscReader_oscReader_analog_1_signal_continuous] failed setting up adc mode: ", err);
#endif
i2s_driver_uninstall (I2S_NUM_0);
((oscSharedMemory *) sharedMemory)->webSocket->sendString ("[oscilloscope] failed setting up i2s adc mode."); // send error to javascript client
// wait for the STOP signal
while (((oscSharedMemory *) sharedMemory)->oscReaderState != STOP) delay (1);
((oscSharedMemory *) sharedMemory)->oscReaderState = STOPPED;
vTaskDelete (NULL);
}
while (((oscSharedMemory *) sharedMemory)->oscReaderState == STARTED) {
// read the whole buffer
size_t bytesRead = 0;
err = i2s_read (I2S_NUM_0,
(void *) &readBuffer->samplesContinuousSignal [1], // skip the first (dummy) sample
noOfSamplesToTakeFirstTime << 1, // in bytes
&bytesRead,
portMAX_DELAY); // no timeout
if (err != ESP_OK) {
Serial.printf ("Failed reading samples: %d\n", err);
#ifdef __DMESG__
dmesg ("[oscilloscope][oscReader_oscReader_analog_1_signal_continuous] failed reading samples: ", err);
#endif
i2s_driver_uninstall (I2S_NUM_0);
((oscSharedMemory *) sharedMemory)->webSocket->sendString ("[oscilloscope] failed setting up i2s adc mode."); // send error to javascript client
// wait for the STOP signal
while (((oscSharedMemory *) sharedMemory)->oscReaderState != STOP) delay (1);
((oscSharedMemory *) sharedMemory)->oscReaderState = STOPPED;
vTaskDelete (NULL);
}
// For some strange reason the sample come swapped two-by two. Unswap them and filter out only 12 bits that actually hold the value
noOfSamplesTaken = bytesRead >> 1; // samples are 16 bit integers
// for (int i = 1; i <= noOfSamplesTaken; i++) readBuffer->samplesContinuousSignal [i].signal1 &= 0x0FFF;
#ifdef INVERT_I2S_READ
for (int i = 1; i < noOfSamplesTaken; i += 2) {
int16_t tmp = readBuffer->samplesContinuousSignal [i].signal1;
readBuffer->samplesContinuousSignal [i].signal1 = ~readBuffer->samplesContinuousSignal [i + 1].signal1 & 0xFFF;
readBuffer->samplesContinuousSignal [i + 1].signal1 = ~tmp & 0xFFF;
}
#else
for (int i = 1; i < noOfSamplesTaken; i += 2) {
int16_t tmp = readBuffer->samplesContinuousSignal [i].signal1;
readBuffer->samplesContinuousSignal [i].signal1 = readBuffer->samplesContinuousSignal [i + 1].signal1 & 0xFFF;
readBuffer->samplesContinuousSignal [i + 1].signal1 = tmp & 0xFFF;
}
#endif
// DEBUG: Serial.printf ("[oscilloscope][oscReader_analog_1_signal_continuous] noOfSamplesTaken = %i\n", noOfSamplesTaken);
// DEBUG: for (int i = 0; i <= noOfSamplesTaken; i++) Serial.printf (" %i ", readBuffer->samplesContinuousSignal [i].signal1); Serial.printf ("\n");
if (!triggeredMode) break; // if not in triggered modt then we already have what we need
// if in triggered mode try to find trigger condition
for (int i = 1; i < noOfSamplesTaken; i++) {
if ( (positiveTrigger && readBuffer->samplesContinuousSignal [i].signal1 < positiveTriggerTreshold && readBuffer->samplesContinuousSignal [i + 1].signal1 >= positiveTriggerTreshold) || (negativeTrigger && readBuffer->samplesContinuousSignal [i].signal1 > negativeTriggerTreshold && readBuffer->samplesContinuousSignal [i + 1].signal1 <= negativeTriggerTreshold) ) {
// trigger condition found at i, copy the rest of the buffer to its beginning and do another i2s_read for the samples that are missing
if (i > 1) {
int noOfSamplesToTakeSecondTime = (i - 1);
// there is a bug in i2s_read: it odd number of samples are to be read the last one is always 0, so make sure we have even number of samples
if (noOfSamplesToTakeSecondTime % 2 != 0) noOfSamplesToTakeSecondTime ++;
if (noOfSamplesToTakeSecondTime < 8) noOfSamplesToTakeSecondTime = 8;
int fromInd = noOfSamplesTaken - (i - 1) + 1;
// DEBUG: Serial.printf ("[oscilloscope][oscReader_analog_1_signal_continuous] trigger condition found at: %i, need to read additional %i samples at buffer position %i\n", i, i - 1, fromInd);
memcpy ((void*) &readBuffer->samplesContinuousSignal [1], (void*) &readBuffer->samplesContinuousSignal [i], (noOfSamplesTaken - (i - 1)) << 1);
err = i2s_read (I2S_NUM_0,
(void*) &readBuffer->samplesContinuousSignal [fromInd],
noOfSamplesToTakeSecondTime << 1, // in bytes
&bytesRead,
portMAX_DELAY); // no timeout
if (err != ESP_OK) {
Serial.printf ("Failed reading samples: %d\n", err);
#ifdef __DMESG__
dmesg ("[oscilloscope][oscReader_oscReader_analog_1_signal_continuous] failed reading samples: ", err);
#endif
i2s_driver_uninstall (I2S_NUM_0);
((oscSharedMemory *) sharedMemory)->webSocket->sendString ("[oscilloscope] failed setting up i2s adc mode."); // send error to javascript client
// wait for the STOP signal
while (((oscSharedMemory *) sharedMemory)->oscReaderState != STOP) delay (1);
((oscSharedMemory *) sharedMemory)->oscReaderState = STOPPED;
vTaskDelete (NULL);
}
noOfSamplesTaken = noOfSamplesTaken - (i - 1) + (bytesRead >> 1); // - deleted samples + newly read samples (normally we would end up with the same number)
// for (int j = fromInd; j <= noOfSamplesTaken; j++) readBuffer->samplesContinuousSignal [j].signal1 &= 0x0FFF;
// For some strange reason the sample come swapped two-by two. Unswap them and filter out only 12 bits that actually hold the value
#ifdef INVERT_I2S_READ
for (int j = fromInd; j < noOfSamplesTaken; j += 2) {
int16_t tmp = readBuffer->samplesContinuousSignal [j].signal1;
readBuffer->samplesContinuousSignal [j].signal1 = ~readBuffer->samplesContinuousSignal [j + 1].signal1 & 0xFFF;
readBuffer->samplesContinuousSignal [j + 1].signal1 = ~tmp & 0x0FFF;
}
#else
for (int j = fromInd; j < noOfSamplesTaken; j += 2) {
int16_t tmp = readBuffer->samplesContinuousSignal [j].signal1;
readBuffer->samplesContinuousSignal [j].signal1 = readBuffer->samplesContinuousSignal [j + 1].signal1 & 0xFFF;
readBuffer->samplesContinuousSignal [j + 1].signal1 = tmp & 0x0FFF;
}
#endif
}
goto passSamplesToOscSender;
}
}
// trigger condition not found, continue reading
} // while (true)
passSamplesToOscSender:
// pass readBuffer to oscSender
readBuffer->sampleCount = noOfSamplesTaken + 1; // + 1 dummy sample
// DEBUG: Serial.printf ("[oscilloscope][oscReader_analog_1_signal_continuous] noOfSamplesTaken = %i\n", noOfSamplesTaken);
if (!sendBuffer->samplesAreReady)
*sendBuffer = *readBuffer;
// uninstall the driver
i2s_driver_uninstall (I2S_NUM_0);
// wait before next screen refresh
vTaskDelayUntil (&lastScreenRefreshTicks, pdMS_TO_TICKS (screenRefreshMilliseconds));
} // while sampling
// DEBUG: Serial.printf ("[oscilloscope][oscReader] stopping, state = %i\n", ((oscSharedMemory *) sharedMemory)->oscReaderState);
// wait for the STOP signal
while (((oscSharedMemory *) sharedMemory)->oscReaderState != STOP) delay (1);
((oscSharedMemory *) sharedMemory)->oscReaderState = STOPPED;
vTaskDelete (NULL);
}
// oscSender oscSender oscSender oscSender oscSender oscSender oscSender oscSender oscSender oscSender oscSender oscSender oscSender oscSender oscSender
void oscSender (void *sharedMemory) {
unsigned char gpio1 = (unsigned char) ((oscSharedMemory *) sharedMemory)->gpio1; // easier to check validity with unsigned char then with integer
unsigned char gpio2 = (unsigned char) ((oscSharedMemory *) sharedMemory)->gpio2; // easier to check validity with unsigned char then with integer
unsigned char noOfSignals = 1; if (gpio2 <= 39) noOfSignals = 2; // monitor 1 or 2 signals
oscSamples *sendBuffer = &((oscSharedMemory *) sharedMemory)->sendBuffer;
sendBuffer->samplesAreReady = false;
bool clientIsBigEndian = ((oscSharedMemory *) sharedMemory)->clientIsBigEndian;
WebSocket *webSocket = ((oscSharedMemory *) sharedMemory)->webSocket;
unsigned long lastMillis = millis ();
while (true) {
delay (1);
// send samples to javascript client if they are ready
if (sendBuffer->samplesAreReady && sendBuffer->sampleCount) {
// DEBUG: Serial.printf ("[oscilloscope] screen refresh time (shold be close to 50): %lu ms\n", millis () - lastMillis); lastMillis = millis ();
// copy buffer with samples within critical section
oscSamples sendSamples = *sendBuffer;
sendBuffer->samplesAreReady = false; // oscRader will set this flag when buffer is the next time ready for sending
// swap bytes if javascript client is big endian
int sendBytes; // calculate the number of bytes in the buffer
// find out the type of buffer used
if (noOfSignals == 1)
if (sendSamples.samplesContinuousSignal [0].signal1 < -3)
sendBytes = sendSamples.sampleCount * sizeof (oscContinuousSample); // 1 continuous signal (continuos signal does not starts with -1, -2 or -3 dummy value)
else
sendBytes = sendSamples.sampleCount * sizeof (osc1SignalSample); // 1 signal with deltaTime
else
sendBytes = sendSamples.sampleCount * sizeof (osc2SignalsSample); // 2 signals with deltaTime
int sendWords = sendBytes >> 1; // number of 16 bit words = number of bytes / 2
if (clientIsBigEndian) {
uint16_t *w = (uint16_t *) &sendSamples;
for (size_t i = 0; i < sendWords; i ++) w [i] = htons (w [i]);
}
// DEBUG: int16_t *wp = (int16_t *) &sendSamples; for (int i = 0; i < sendBytes / 2; i++) Serial.printf (" %i ", wp [i]); Serial.println ();
if (!webSocket->sendBinary ((byte *) &sendSamples, sendBytes)) return;
// DEBUG: Serial.printf ("[oscilloscope] sent samples: %i bytes: %i\n", sendSamples.sampleCount, sendBytes);
}
// read (text) stop command form javscrip client if it arrives - according to oscilloscope protocol the string could only be 'stop' - so there is no need checking it
if (webSocket->available () != WebSocket::NOT_AVAILABLE) return; // this also covers ERROR and TIME_OUT
}
}
// main oscilloscope function - it reads request from javascript client then starts two threads: oscilloscope reader (that reads samples ans packs them into buffer) and oscilloscope sender (that sends buffer to javascript client)
void runOscilloscope (WebSocket *webSocket) {
// set up oscilloscope shared memory that will be shared among all 3 oscilloscope threads
oscSharedMemory sharedMemory = {}; // get some memory that will be shared among all oscilloscope threads and initialize it with zerros
sharedMemory.webSocket = webSocket; // put webSocket rference into shared memory
sharedMemory.readBuffer.samplesAreReady = true; // this value will be copied into sendBuffer later where this flag will be checked
// oscilloscope protocol starts with binary endian identification from the client
uint16_t endianIdentification = 0;
if (webSocket->readBinary ((byte *) &endianIdentification, sizeof (endianIdentification)) == sizeof (endianIdentification))
sharedMemory.clientIsBigEndian = (endianIdentification == 0xBBAA); // cient has sent 0xAABB
if (!(endianIdentification == 0xAABB || endianIdentification == 0xBBAA)) {
#ifdef __DMESG__
dmesg ("[oscilloscope] communication does not follow oscilloscope protocol. Expected endian identification.");
#endif
webSocket->sendString ("[oscilloscope] communication does not follow oscilloscope protocol. Expected endian identification."); // send error also to javascript client
return;
}
// oscilloscope protocol continues with (text) start command in the following forms:
// start digital sampling on GPIO 36 every 250 ms screen width = 10000 ms
// start analog sampling on GPIO 22, 23 every 100 ms screen width = 400 ms set positive slope trigger to 512 set negative slope trigger to 0
string s = webSocket->readString ();
// DEBUG: Serial.printf ("[oscilloscope] command: %s\n", s);
if (s == "") {
#ifdef __DMESG__
dmesg ("[oscilloscope] communication does not follow oscilloscope protocol. Expected start oscilloscope parameters.");
#endif
webSocket->sendString ("[oscilloscope] communication does not follow oscilloscope protocol. Expected start oscilloscope parameters."); // send error also to javascript client
return;
}
// try to parse what we have got from client
char posNeg1 [9] = "";
char posNeg2 [9] = "";
int treshold1;
int treshold2;
char *cmdPart1 = (char *) s;
char *cmdPart2 = strstr (cmdPart1, " every");
char *cmdPart3 = NULL;
if (cmdPart2) {
*(cmdPart2++) = 0;
cmdPart3 = strstr (cmdPart2, " set");
if (cmdPart3)
*(cmdPart3++) = 0;
}
// parse 1st part
sharedMemory.gpio1 = sharedMemory.gpio2 = 255; // invalid GPIO
if (sscanf (cmdPart1, "start %7s sampling on GPIO %2i, %2i", sharedMemory.readType, &sharedMemory.gpio1, &sharedMemory.gpio2) < 2) {
#ifdef __DMESG__
dmesg ("[oscilloscope] oscilloscope protocol syntax error.");
#endif
webSocket->sendString ("[oscilloscope] oscilloscope protocol syntax error."); // send error also to javascript client
return;
}
// use adc1_get_raw instead of analogRead
if (!strcmp (sharedMemory.readType, "analog")) {
switch (sharedMemory.gpio1) {
// ADC1
case 36: sharedMemory.adcchannel1 = ADC1_CHANNEL_0; break;
case 37: sharedMemory.adcchannel1 = ADC1_CHANNEL_1; break;
case 38: sharedMemory.adcchannel1 = ADC1_CHANNEL_2; break;
case 39: sharedMemory.adcchannel1 = ADC1_CHANNEL_3; break;
case 32: sharedMemory.adcchannel1 = ADC1_CHANNEL_4; break;
case 33: sharedMemory.adcchannel1 = ADC1_CHANNEL_5; break;
case 34: sharedMemory.adcchannel1 = ADC1_CHANNEL_6; break;
case 35: sharedMemory.adcchannel1 = ADC1_CHANNEL_7; break;
// ADC2 (GPIOs 4, 0, 2, 15, 13, 12, 14, 27, 25, 26), reading blocks when used together with WiFi
// other GPIOs do not have ADC
default: webSocket->sendString (string ("[oscilloscope] can't analogRead GPIO ") + string (sharedMemory.gpio1) + (char *) "."); // send error also to javascript client
return;
}
switch (sharedMemory.gpio2) {
// ADC1
case 36: sharedMemory.adcchannel2 = ADC1_CHANNEL_0; break;
case 37: sharedMemory.adcchannel2 = ADC1_CHANNEL_1; break;
case 38: sharedMemory.adcchannel2 = ADC1_CHANNEL_2; break;
case 39: sharedMemory.adcchannel2 = ADC1_CHANNEL_3; break;
case 32: sharedMemory.adcchannel2 = ADC1_CHANNEL_4; break;
case 33: sharedMemory.adcchannel2 = ADC1_CHANNEL_5; break;
case 34: sharedMemory.adcchannel2 = ADC1_CHANNEL_6; break;
case 35: sharedMemory.adcchannel2 = ADC1_CHANNEL_7; break;
// not used
case 255: break;
// ADC2 (GPIOs 4, 0, 2, 15, 13, 12, 14, 27, 25, 26), reading blocks when used together with WiFi
// other GPIOs do not have ADC
default: webSocket->sendString (string ("[oscilloscope] can't analogRead GPIO ") + string (sharedMemory.gpio2) + (char *) "."); // send error also to javascript client
return;
}
}
// parse 2nd part
if (!cmdPart2) {
#ifdef __DMESG__
dmesg ("[oscilloscope] oscilloscope protocol syntax error.");
#endif
webSocket->sendString ("[oscilloscope] oscilloscope protocol syntax error."); // send error also to javascript client
return;
}
if (sscanf (cmdPart2, "every %i %2s screen width = %lu %2s", &sharedMemory.samplingTime, sharedMemory.samplingTimeUnit, &sharedMemory.screenWidthTime, sharedMemory.screenWidthTimeUnit) != 4) {
#ifdef __DMESG__
dmesg ("[oscilloscope] oscilloscope protocol syntax error.");
#endif
webSocket->sendString ("[oscilloscope] oscilloscope protocol syntax error."); // send error also to javascript client
return;
}
// parse 3rd part
if (cmdPart3) {
switch (sscanf (cmdPart3, "set %8s slope trigger to %i set %8s slope trigger to %i", posNeg1, &treshold1, posNeg2, &treshold2)) {
case 0: // no trigger
break;
case 4: // two triggers
if (!strcmp (posNeg2, "positive")) {
sharedMemory.positiveTrigger = true;
sharedMemory.positiveTriggerTreshold = treshold2;
}
if (!strcmp (posNeg2, "negative")) {
sharedMemory.negativeTrigger = true;
sharedMemory.negativeTriggerTreshold = treshold2;
}
// don't break, continue to the next case
case 2: // one trigger
if (!strcmp (posNeg1, "positive")) {
sharedMemory.positiveTrigger = true;
sharedMemory.positiveTriggerTreshold = treshold1;
}
if (!strcmp (posNeg1, "negative")) {
sharedMemory.negativeTrigger = true;
sharedMemory.negativeTriggerTreshold = treshold1;
}
break;
default:
#ifdef __DMESG__
dmesg ("[oscilloscope] oscilloscope protocol syntax error.");
#endif
webSocket->sendString ("[oscilloscope] oscilloscope protocol syntax error."); // send error also to javascript client
return;
}
}
// DEBUG: Serial.printf ("[oscilloscope] parsing command: samplingTime = %i %s, screenWidth = %lu %s\n", sharedMemory.samplingTime, sharedMemory.samplingTimeUnit, sharedMemory.screenWidthTime, sharedMemory.screenWidthTimeUnit);
// check the values and calculate derived values
if (!(!strcmp (sharedMemory.readType, "analog") || !strcmp (sharedMemory.readType, "digital"))) {
#ifdef __DMESG__
dmesg ("[oscilloscope] wrong readType. Read type can only be analog or digital.");
#endif
webSocket->sendString ("[oscilloscope] wrong readType. Read type can only be analog or digital."); // send error also to javascript client
return;
}
if (sharedMemory.gpio1 < 0 || sharedMemory.gpio2 < 0) {
#ifdef __DMESG__
dmesg ("[oscilloscope] invalid GPIO.");
#endif
webSocket->sendString ("[oscilloscope] invalid GPIO."); // send error also to javascript client
return;
}
if (!(sharedMemory.samplingTime >= 1 && sharedMemory.samplingTime <= 25000)) {
#ifdef __DMESG__
dmesg ("[oscilloscope] invalid sampling time. Sampling time must be between 1 and 25000.");
#endif
webSocket->sendString ("[oscilloscope] invalid sampling time. Sampling time must be between 1 and 25000."); // send error also to javascript client
return;
}
if (strcmp (sharedMemory.samplingTimeUnit, "ms") && strcmp (sharedMemory.samplingTimeUnit, "us")) {
#ifdef __DMESG__
dmesg ("[oscilloscope] wrong samplingTimeUnit. Sampling time unit can only be ms or us.");
#endif
webSocket->sendString ("[oscilloscope] wrong samplingTimeUnit. Sampling time unit can only be ms or us."); // send error also to javascript client
return;
}
if (strcmp (sharedMemory.screenWidthTimeUnit, sharedMemory.samplingTimeUnit)) {
#ifdef __DMESG__
dmesg ("[oscilloscope] screenWidthTimeUnit must be the same as samplingTimeUnit.");
#endif
webSocket->sendString ("[oscilloscope] screenWidthTimeUnit must be the same as samplingTimeUnit."); // send error also to javascript client
return;
}
// DEBUG: Serial.printf ("[oscilloscope] parsing4 command: samplingTime = %i %s, screenWidth = %i %s\n", sharedMemory.samplingTime, sharedMemory.samplingTimeUnit, sharedMemory.screenWidthTime, sharedMemory.screenWidthTimeUnit);
if (sharedMemory.positiveTrigger) {
if (sharedMemory.positiveTriggerTreshold > 0 && sharedMemory.positiveTriggerTreshold <= (strcmp (sharedMemory.readType, "analog") ? 1 : 4095)) {
;// Serial.printf ("[oscilloscope] positive slope trigger treshold = %i\n", sharedMemory.positiveTriggerTreshold);
} else {
#ifdef __DMESG__
dmesg ("[oscilloscope] invalid positive slope trigger treshold (according to other settings).");
#endif
webSocket->sendString ("[oscilloscope] invalid positive slope trigger treshold (according to other settings)."); // send error also to javascript client
return;
}
}
if (sharedMemory.negativeTrigger) {
if (sharedMemory.negativeTriggerTreshold >= 0 && sharedMemory.negativeTriggerTreshold < (strcmp (sharedMemory.readType, "analog") ? 1 : 4095)) {
;//Serial.printf ("[oscilloscope] negative slope trigger treshold = %i\n", sharedMemory.negativeTriggerTreshold);
} else {
#ifdef __DMESG__
dmesg ("[oscilloscope] invalid negative slope trigger treshold (according to other settings).");
#endif
webSocket->sendString ("[oscilloscope] invalid negative slope trigger treshold (according to other settings)."); // send error also to javascript client
return;
}
}
// DEBUG: Serial.printf ("[oscilloscope] parsing5 command: samplingTime = %i %s, screenWidth = %i %s\n", sharedMemory.samplingTime, sharedMemory.samplingTimeUnit, sharedMemory.screenWidthTime, sharedMemory.screenWidthTimeUnit);
// choose the corect oscReader
void (*oscReader) (void *sharedMemory);
if (strcmp (sharedMemory.readType, "analog")) oscReader = oscReader_digital; // us sampling interval, 1-2 signals, digital reader
else {
if (sharedMemory.gpio2 <= 39) // 2 signalas
oscReader = oscReader_analog; // us sampling interval, 1-2 signals, analog reader
else // 1 signal
oscReader = oscReader_analog_1_signal_continuous; // us sampling interval, 1-2 signals, analog reader
}
if (!strcmp (sharedMemory.samplingTimeUnit, "ms")) oscReader = oscReader_millis; // ms sampling intervl, 1-2 signals, digital-analog reader with 'sample at a time' option
sharedMemory.oscReaderState = INITIAL;
#ifdef OSCILLOSCOPE_READER_CORE
BaseType_t taskCreated = xTaskCreatePinnedToCore (oscReader, "oscReader", 4 * 1024, (void *) &sharedMemory, OSCILLOSCOPE_READER_PRIORITY, NULL, OSCILLOSCOPE_READER_CORE);
#else
BaseType_t taskCreated = xTaskCreate (oscReader, "oscReader", 4 * 1024, (void *) &sharedMemory, OSCILLOSCOPE_READER_PRIORITY, NULL);
#endif
if (pdPASS != taskCreated) {
#ifdef __DMESG__
dmesg ("[oscilloscope] could not start oscReader");
#endif
webSocket->sendString ("[oscilloscope] could not start oscReader."); // send error also to javascript client
} else {
// send oscReader START signal and wait until STARTED
sharedMemory.oscReaderState = START;
while (sharedMemory.oscReaderState == START) delay (1);
// start oscilloscope sender in this thread
oscSender ((void *) &sharedMemory);
// stop reader - we can not simply vTaskDelete (oscReaderHandle) since this could happen in the middle of analogRead which would leave its internal semaphore locked
// send oscReader STOP signal
sharedMemory.oscReaderState = STOP;
// wait until oscReader STOPPED
while (sharedMemory.oscReaderState != STOPPED) delay (1);
}
return;
}
Cool! I'm trying to find an ESP32 to try this with, I only have ESP32-S3 right now. I had originally modified your code to get it to work on those newer chips, but the high-speed ADC isn't supported on them. (Why there is so much craziness to Espressif's product line naming convention is a headscratcher.)
Solved.
<3
On Wed, Nov 15, 2023 at 2:09 PM Bojan Jurca @.***> wrote:
Closed #21 https://github.com/BojanJurca/Esp32_oscilloscope/issues/21 as completed.
— Reply to this email directly, view it on GitHub https://github.com/BojanJurca/Esp32_oscilloscope/issues/21#event-10971161394, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAIQ72KIQCMEH3FWGWWM7J3YEUHODAVCNFSM6AAAAAA3Z6DMHSVHI2DSMVQWIX3LMV45UABCJFZXG5LFIV3GK3TUJZXXI2LGNFRWC5DJN5XDWMJQHE3TCMJWGEZTSNA . You are receiving this because you authored the thread.Message ID: @.*** com>
The use of continuous ADC conversions would allow for consistent measurement windows. This would reduce jitter, and also potentially allow for more datapoints to be measured within a single period. (A typical value for a DSO is 1000 points, vs. this project's 128 points.)
The architecture of this project is too complex for me to instantly see if this would work or not, but looking at
oscilloscope.h
it seems like a DMA transfer ought to be a compatible replacement for thedelayMicroseconds()
approach.