├── imgs ├── file.png ├── pwm.jpeg ├── sine wave.jpeg ├── sine zoomed.jpeg └── P_20200508_000602_vHDR_On.jpg ├── I2S_adc_esp32 ├── adc.ino ├── debug_routines.ino ├── filters.h ├── i2s.ino ├── options_handler.ino ├── I2S_adc_esp32.ino ├── data_analysis.ino └── screen.ino └── README.md /imgs/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavollps/esp32TTGO_i2s_scope/HEAD/imgs/file.png -------------------------------------------------------------------------------- /imgs/pwm.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavollps/esp32TTGO_i2s_scope/HEAD/imgs/pwm.jpeg -------------------------------------------------------------------------------- /imgs/sine wave.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavollps/esp32TTGO_i2s_scope/HEAD/imgs/sine wave.jpeg -------------------------------------------------------------------------------- /imgs/sine zoomed.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavollps/esp32TTGO_i2s_scope/HEAD/imgs/sine zoomed.jpeg -------------------------------------------------------------------------------- /imgs/P_20200508_000602_vHDR_On.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavollps/esp32TTGO_i2s_scope/HEAD/imgs/P_20200508_000602_vHDR_On.jpg -------------------------------------------------------------------------------- /I2S_adc_esp32/adc.ino: -------------------------------------------------------------------------------- 1 | void characterize_adc() { 2 | esp_adc_cal_characterize( 3 | (adc_unit_t)ADC_UNIT_1, 4 | (adc_atten_t)ADC_CHANNEL, 5 | (adc_bits_width_t)ADC_WIDTH_BIT_12, 6 | 1100, 7 | &adc_chars); 8 | } 9 | -------------------------------------------------------------------------------- /I2S_adc_esp32/debug_routines.ino: -------------------------------------------------------------------------------- 1 | #ifdef DEBUG_BUF 2 | void debug_buffer() { 3 | ADC_Sampling(); 4 | i2s_adc_disable(I2S_NUM_0); 5 | delay(1000); 6 | for (uint32_t i = 0; i < B_MULT * NUM_SAMPLES; i++) { 7 | for (int j = 0; j < 1; j++) { 8 | Serial.println(i2s_buff[i]); 9 | } 10 | delay(5); 11 | } 12 | i2s_zero_dma_buffer(I2S_NUM_0); 13 | i2s_adc_enable(I2S_NUM_0); 14 | while (1); 15 | } 16 | #endif 17 | -------------------------------------------------------------------------------- /I2S_adc_esp32/filters.h: -------------------------------------------------------------------------------- 1 | class low_pass { 2 | public: 3 | 4 | low_pass(int factor) { 5 | _factor = factor; 6 | } 7 | 8 | float filter(float reading) { 9 | _value = _value * (_factor) + reading * (1.0 - _factor); 10 | return _value; 11 | } 12 | 13 | float _value = 0; 14 | float _factor = 0.99; 15 | 16 | }; 17 | 18 | 19 | class mean_filter { 20 | public: 21 | 22 | mean_filter(int values) { 23 | _values = values; 24 | } 25 | 26 | void init(float value){ 27 | for(int i=0;i<_values;i++){ 28 | _data[i] = value; 29 | } 30 | } 31 | float filter(float reading) { 32 | float temp = 0; 33 | _data[_values - 1] = reading; 34 | for (int i = 0; i < _values - 1; i++) { 35 | temp += _data[i]; 36 | _data[i] = _data[i + 1]; 37 | } 38 | temp += reading; 39 | return temp / float(_values); 40 | } 41 | 42 | int _values = 5; 43 | float _data[100] = {0}; 44 | }; 45 | -------------------------------------------------------------------------------- /I2S_adc_esp32/i2s.ino: -------------------------------------------------------------------------------- 1 | void configure_i2s(int rate) { 2 | /*keep in mind: 3 | dma_buf_len * dma_buf_count * bits_per_sample/8 > 4096 4 | */ 5 | i2s_config_t i2s_config = 6 | { 7 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), // I2S receive mode with ADC 8 | .sample_rate = rate, // sample rate 9 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // 16 bit I2S 10 | .channel_format = I2S_CHANNEL_FMT_ALL_LEFT, // only the left channel 11 | .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), // I2S format 12 | .intr_alloc_flags = 1, // none 13 | .dma_buf_count = 2, // number of DMA buffers 14 | .dma_buf_len = NUM_SAMPLES, // number of samples 15 | .use_apll = 0, // no Audio PLL 16 | }; 17 | adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN_11db); 18 | adc1_config_width(ADC_WIDTH_12Bit); 19 | i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); 20 | 21 | i2s_set_adc_mode(ADC_UNIT_1, ADC_CHANNEL); 22 | SET_PERI_REG_MASK(SYSCON_SARADC_CTRL2_REG, SYSCON_SARADC_SAR1_INV); 23 | i2s_adc_enable(I2S_NUM_0); 24 | } 25 | 26 | void ADC_Sampling(uint16_t *i2s_buff){ 27 | for (int i = 0; i < B_MULT; i++) { 28 | //TODO i2s_read_bytes is deprecated, replace with new function 29 | i2s_read_bytes(I2S_NUM_0, (char*)&i2s_buff[i * NUM_SAMPLES], NUM_SAMPLES * sizeof(uint16_t), portMAX_DELAY); 30 | } 31 | } 32 | 33 | void set_sample_rate(uint32_t rate) { 34 | i2s_driver_uninstall(I2S_NUM_0); 35 | configure_i2s(rate); 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | esp32TTGO_i2s_scope (tested on core 1.0.5, not working with esp core 2.0.3) 2 | ==================== 3 | DEMO VIDEO at https://youtu.be/4NNk67PA57Y 4 | 5 | Feel free to give me any suggestions. It is a simple work that supplied what I needed at the time and has much room for improvements. 6 | 7 | # Project Description 8 | 9 | A simple 1Msps single-channel scope with the TFT display on the ESP32-TTGO.(https://bit.ly/2WbaXDi) 10 | 11 | #### Features v0.3 12 | * Single-channel 13 | * 1Msps 14 | * 50000 @ 16bits buffer (50ms of data at 1Msps) 15 | * Scale from 10us/div to 5ms/div at 1Msps 16 | * Using 2 onboard buttons for VERY slow control (dreaming of a rotational encoder) 17 | * Frequency calculations (20hz min due to buffer size) 18 | * Simple mean filter ON/OFF 19 | * Max, min, average and Peak-Peak voltage 20 | * Time and voltage offset 21 | * Analog, Digital/Data Mode 22 | * Single TRIGGER 23 | * AUTOSCALE 24 | 25 | #### Tested and working on: 26 | * PWM 27 | * 80Khz sine wave 28 | * 40hz sine wave 29 | * Serial at 115200bps 30 | * Electric Guitar Raw signal (from 82hz to 1.3khz, <100mV peak to peak) 31 | 32 | **KEEP IN MIND TO PROTECT YOUR ADC PORT** 33 | 34 | # Building 35 | 36 | First of all, you should have your IDE with the ESP32 support installed (tested on version esp core 1.0.6). 37 | Add the Button2 library and the TFT_eSPI library (By Bodmer) to the IDE, then edit the library to work with the correct display as described below: 38 | 39 | In the file `TFT_eSPI/User_Setup_Select.h` change the line: 40 | 41 | `#include ` to: `//#include ` 42 | 43 | and 44 | 45 | `//#include ` to `#include ` 46 | 47 | The board is the ESP32 Wrover Module. 48 | With this, you should be able to build it successfully. 49 | 50 | 51 | # Button Operation 52 | 53 | Firstly, you should change the defines INSIDE the button2.h library to: 54 | ``` 55 | #ifndef DEBOUNCE_MS 56 | #define DEBOUNCE_MS 20 57 | #endif 58 | #ifndef LONGCLICK_MS 59 | #define LONGCLICK_MS 500 60 | #endif 61 | ``` 62 | Defining it in the main .ino file did nothing at all for me, probably due to the Arduino IDE thing with the .ino files organization. This is closer to the parameters used (that I sadly lost) in the DEMO video. 63 | 64 | The DEMO video shows better the working flow. 65 | 66 | - Upper button 67 | - At the initial screen 68 | - Short click - open menu 69 | - On menu 70 | - Short click - next option 71 | - Long click - Hide menu 72 | - In menu with an option selected 73 | - Short click - Increase the selected option's value (`+` symbol appearing at the right upper border of the display) 74 | - Long click - Hide menu 75 | 76 | - Lower button 77 | - At the initial screen 78 | - Long click - hide all graphics except the grid 79 | - In menu 80 | - Short click - select highlighted option or enter the value-changing state for the option, if available 81 | - Long click - reset highlighted option to the default value 82 | - In menu with an option selected 83 | - Short click - Decrease the selected option's value (`-` symbol appearing at the right upper border of the display) 84 | - Long click - exit value-changing state for the highlighted option 85 | 86 | # Version history 87 | 88 | * v0.1 simple bugged scope 89 | * v0.2 Trigger added 90 | * v0.3 Frequency calculation improved, trigger improved, overall improvements 91 | 92 | ![scope](https://github.com/gustavollps/esp32TTGO_i2s_scope/blob/master/imgs/P_20200508_000602_vHDR_On.jpg) 93 | -------------------------------------------------------------------------------- /I2S_adc_esp32/options_handler.ino: -------------------------------------------------------------------------------- 1 | int voltage_division[6] = { //screen has 4 divisions, 31 pixels each (125 pixels of height) 2 | 825, //fullscreen 3.3V peak-peak 3 | 750, 4 | 500, 5 | 250, 6 | 100, 7 | 50 8 | }; 9 | 10 | 11 | /*each sample represents 1us (1Msps), 12 | thus, the time division is the number 13 | of samples per screen division 14 | */ 15 | float time_division[9] = { //screen has 4 divisions, 60 pixel each (240 pixel of width) 16 | 10, 17 | 25, 18 | 50, 19 | 100, 20 | 250, 21 | 500, 22 | 1000, 23 | 2500, 24 | 5000 25 | }; 26 | //, //1Mhz 35ms of data (of 50ms possible) 27 | // 10000, //100khz 70ms/500ms 28 | // 25000, //100khz 175ms/500ms of data 29 | // 50000, //100khz 350ms/500ms of data 30 | // 100000 //50khz 700ms/1000ms of data 31 | //}; 32 | 33 | void menu_handler() { 34 | button_mode.loop(); 35 | button_set.loop(); 36 | } 37 | 38 | void click_long(Button2& btn) { 39 | menu_action = true; 40 | if (btn == button_mode) { 41 | uint32_t pressed = btn.wasPressedFor(); 42 | if (pressed > 1000) { 43 | opt = None; 44 | hide_menu(); 45 | set_value = false; 46 | } 47 | 48 | } 49 | else { 50 | uint32_t pressed = btn.wasPressedFor(); 51 | if (pressed > 1000) { 52 | if (set_value) { 53 | set_value = false; 54 | } 55 | else { 56 | switch (opt) { 57 | case Vdiv: 58 | v_div = 825; 59 | volts_index = 0; 60 | break; 61 | 62 | case Sdiv: 63 | s_div = 10; 64 | tscale_index = 0; 65 | break; 66 | 67 | case Offset: 68 | offset = 0; 69 | break; 70 | 71 | case TOffset: 72 | toffset = 0; 73 | break; 74 | case Filter: 75 | current_filter = 1; 76 | break; 77 | 78 | case None: 79 | info = !info; 80 | break; 81 | 82 | default: 83 | break; 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | void click(Button2& btn) { 91 | menu_action = true; 92 | 93 | if (set_value) { 94 | switch (opt) { 95 | case Vdiv: 96 | if (btn == button_set) { 97 | volts_index++; 98 | if (volts_index >= sizeof(voltage_division) / sizeof(*voltage_division)) { 99 | volts_index = 0; 100 | } 101 | } 102 | else { 103 | volts_index--; 104 | if (volts_index < 0) { 105 | volts_index = sizeof(voltage_division) / sizeof(*voltage_division) - 1; 106 | } 107 | } 108 | 109 | v_div = voltage_division[volts_index]; 110 | break; 111 | 112 | case Sdiv: 113 | if (btn == button_mode) { 114 | tscale_index++; 115 | if (tscale_index >= sizeof(time_division) / sizeof(*time_division)) { 116 | tscale_index = 0; 117 | } 118 | } 119 | else { 120 | tscale_index--; 121 | if (tscale_index < 0) { 122 | tscale_index = sizeof(time_division) / sizeof(*time_division) - 1; 123 | } 124 | } 125 | 126 | s_div = time_division[tscale_index]; 127 | break; 128 | 129 | case Offset: 130 | if (btn == button_mode){ 131 | offset += 0.1 * (v_div * 4) / 3300; 132 | } 133 | else{ 134 | offset -= 0.1 * (v_div * 4) / 3300; 135 | } 136 | 137 | if (offset > 3.3) 138 | offset = 3.3; 139 | if (offset < -3.3) 140 | offset = -3.3; 141 | 142 | break; 143 | 144 | case TOffset: 145 | if (btn == button_mode) 146 | toffset += 0.1 * s_div; 147 | else 148 | toffset -= 0.1 * s_div; 149 | 150 | break; 151 | 152 | default: 153 | break; 154 | 155 | } 156 | } 157 | else { 158 | if (btn == button_mode) { 159 | opt++; 160 | if (opt > Single) 161 | opt = None; 162 | if (opt == None) 163 | hide_menu(); 164 | else 165 | show_menu(); 166 | 167 | } 168 | else if (btn == button_set) { 169 | switch (opt) { 170 | case Autoscale: 171 | auto_scale = !auto_scale; 172 | break; 173 | 174 | case Vdiv: 175 | set_value = true; 176 | break; 177 | 178 | case Sdiv: 179 | set_value = true; 180 | break; 181 | 182 | case Offset: 183 | set_value = true; 184 | break; 185 | 186 | case Stop: 187 | stop = !stop; 188 | break; 189 | 190 | case TOffset: 191 | set_value = true; 192 | break; 193 | 194 | case Single: 195 | single_trigger = true; 196 | break; 197 | 198 | case Reset: 199 | offset = 0; 200 | v_div = 825; 201 | s_div = 10; 202 | tscale_index = 0; 203 | volts_index = 0; 204 | break; 205 | 206 | case Probe: 207 | break; 208 | 209 | case Mode: 210 | digital_wave_option++; 211 | if (digital_wave_option > 2) 212 | digital_wave_option = 0; 213 | break; 214 | 215 | case Filter: 216 | current_filter++; 217 | if (current_filter > 3) 218 | current_filter = 0; 219 | break; 220 | 221 | default: 222 | break; 223 | 224 | } 225 | } 226 | } 227 | } 228 | 229 | void hide_menu() { 230 | menu = false; 231 | } 232 | 233 | void hide_all() { 234 | menu = false; 235 | info = false; 236 | } 237 | 238 | void show_menu() { 239 | menu = true; 240 | } 241 | 242 | String strings_vdiv() { 243 | return ""; 244 | } 245 | 246 | String strings_sdiv() { 247 | return ""; 248 | } 249 | 250 | String strings_offset() { 251 | return ""; 252 | } 253 | 254 | String strings_toffset() { 255 | return ""; 256 | } 257 | 258 | String strings_freq() { 259 | return ""; 260 | } 261 | 262 | String strings_peak() { 263 | return ""; 264 | } 265 | 266 | String strings_vmax() { 267 | return ""; 268 | } 269 | 270 | String strings_vmin() { 271 | return ""; 272 | } 273 | 274 | String strings_filter() { 275 | return ""; 276 | } 277 | -------------------------------------------------------------------------------- /I2S_adc_esp32/I2S_adc_esp32.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "esp_adc_cal.h" 8 | #include "filters.h" 9 | #include 10 | 11 | //#define DEBUG_SERIAL 12 | //#define DEBUG_BUFF 13 | #define DELAY 1000 14 | 15 | // Width and height of sprite 16 | #define WIDTH 135 17 | #define HEIGHT 240 18 | 19 | #define ADC_CHANNEL ADC1_CHANNEL_5 // GPIO33 20 | #define NUM_SAMPLES 1000 // number of samples 21 | #define I2S_NUM (0) 22 | #define BUFF_SIZE 50000 23 | #define B_MULT BUFF_SIZE/NUM_SAMPLES 24 | #define BUTTON_1 35 25 | #define BUTTON_2 0 26 | 27 | TFT_eSPI tft = TFT_eSPI(); // Declare object "tft" 28 | 29 | TFT_eSprite spr = TFT_eSprite(&tft); // Declare Sprite object "spr" with pointer to "tft" object 30 | 31 | 32 | Button2 button_mode = Button2(BUTTON_1); 33 | Button2 button_set = Button2(BUTTON_2); 34 | 35 | esp_adc_cal_characteristics_t adc_chars; 36 | 37 | TaskHandle_t task_menu; 38 | TaskHandle_t task_adc; 39 | 40 | float v_div = 825; 41 | float s_div = 10; 42 | float offset = 0; 43 | float toffset = 0; 44 | uint8_t current_filter = 1; 45 | 46 | //options handler 47 | enum Option { 48 | None, 49 | Autoscale, 50 | Vdiv, 51 | Sdiv, 52 | Offset, 53 | TOffset, 54 | Filter, 55 | Stop, 56 | Mode, 57 | Single, 58 | Clear, 59 | Reset, 60 | Probe, 61 | UpdateF, 62 | Cursor1, 63 | Cursor2 64 | }; 65 | 66 | int8_t volts_index = 0; 67 | 68 | int8_t tscale_index = 0; 69 | 70 | uint8_t opt = None; 71 | 72 | bool menu = false; 73 | bool info = true; 74 | bool set_value = false; 75 | 76 | float RATE = 1000; //in ksps --> 1000 = 1Msps 77 | 78 | bool auto_scale = false; 79 | 80 | bool full_pix = true; 81 | 82 | bool stop = false; 83 | 84 | bool stop_change = false; 85 | 86 | uint16_t i2s_buff[BUFF_SIZE]; 87 | 88 | bool single_trigger = false; 89 | bool data_trigger = false; 90 | 91 | bool updating_screen = false; 92 | bool new_data = false; 93 | bool menu_action = false; 94 | uint8_t digital_wave_option = 0; //0-auto | 1-analog | 2-digital data (SERIAL/SPI/I2C/etc) 95 | 96 | void setup() { 97 | Serial.begin(115200); 98 | 99 | configure_i2s(1000000); 100 | 101 | setup_screen(); 102 | 103 | button_mode.setClickHandler(click); 104 | button_mode.setLongClickHandler(click_long); 105 | button_set.setClickHandler(click); 106 | button_set.setLongClickHandler(click_long); 107 | 108 | characterize_adc(); 109 | #ifdef DEBUG_BUF 110 | debug_buffer(); 111 | #endif 112 | 113 | xTaskCreatePinnedToCore( 114 | core0_task, 115 | "menu_handle", 116 | 10000, /* Stack size in words */ 117 | NULL, /* Task input parameter */ 118 | 0, /* Priority of the task */ 119 | &task_menu, /* Task handle. */ 120 | 0); /* Core where the task should run */ 121 | 122 | xTaskCreatePinnedToCore( 123 | core1_task, 124 | "adc_handle", 125 | 10000, /* Stack size in words */ 126 | NULL, /* Task input parameter */ 127 | 3, /* Priority of the task */ 128 | &task_adc, /* Task handle. */ 129 | 1); /* Core where the task should run */ 130 | } 131 | 132 | 133 | void core0_task( void * pvParameters ) { 134 | 135 | (void) pvParameters; 136 | 137 | for (;;) { 138 | menu_handler(); 139 | 140 | if (new_data || menu_action) { 141 | new_data = false; 142 | menu_action = false; 143 | 144 | updating_screen = true; 145 | update_screen(i2s_buff, RATE); 146 | updating_screen = false; 147 | vTaskDelay(pdMS_TO_TICKS(10)); 148 | Serial.println("CORE0"); 149 | } 150 | 151 | vTaskDelay(pdMS_TO_TICKS(10)); 152 | } 153 | 154 | } 155 | 156 | void core1_task( void * pvParameters ) { 157 | 158 | (void) pvParameters; 159 | 160 | for (;;) { 161 | if (!single_trigger) { 162 | while (updating_screen) { 163 | vTaskDelay(pdMS_TO_TICKS(1)); 164 | } 165 | if (!stop) { 166 | if (stop_change) { 167 | i2s_adc_enable(I2S_NUM_0); 168 | stop_change = false; 169 | } 170 | ADC_Sampling(i2s_buff); 171 | new_data = true; 172 | } 173 | else { 174 | if (!stop_change) { 175 | i2s_adc_disable(I2S_NUM_0); 176 | i2s_zero_dma_buffer(I2S_NUM_0); 177 | stop_change = true; 178 | } 179 | } 180 | Serial.println("CORE1"); 181 | vTaskDelay(pdMS_TO_TICKS(300)); 182 | } 183 | else { 184 | float old_mean = 0; 185 | while (single_trigger) { 186 | stop = true; 187 | ADC_Sampling(i2s_buff); 188 | float mean = 0; 189 | float max_v, min_v; 190 | peak_mean(i2s_buff, BUFF_SIZE, &max_v, &min_v, &mean); 191 | 192 | //signal captured (pp > 0.4V || changing mean > 0.2V) -> DATA ANALYSIS 193 | if ((old_mean != 0 && fabs(mean - old_mean) > 0.2) || to_voltage(max_v) - to_voltage(min_v) > 0.05) { 194 | float freq = 0; 195 | float period = 0; 196 | uint32_t trigger0 = 0; 197 | uint32_t trigger1 = 0; 198 | 199 | //if analog mode OR auto mode and wave recognized as analog 200 | bool digital_data = !false; 201 | if (digital_wave_option == 1) { 202 | trigger_freq_analog(i2s_buff, RATE, mean, max_v, min_v, &freq, &period, &trigger0, &trigger1); 203 | } 204 | else if (digital_wave_option == 0) { 205 | digital_data = digital_analog(i2s_buff, max_v, min_v); 206 | if (!digital_data) { 207 | trigger_freq_analog(i2s_buff, RATE, mean, max_v, min_v, &freq, &period, &trigger0, &trigger1); 208 | } 209 | else { 210 | trigger_freq_digital(i2s_buff, RATE, mean, max_v, min_v, &freq, &period, &trigger0); 211 | } 212 | } 213 | else { 214 | trigger_freq_digital(i2s_buff, RATE, mean, max_v, min_v, &freq, &period, &trigger0); 215 | } 216 | 217 | single_trigger = false; 218 | new_data = true; 219 | Serial.println("Single GOT"); 220 | //return to normal execution in stop mode 221 | } 222 | 223 | vTaskDelay(pdMS_TO_TICKS(1)); //time for the other task to start (low priorit) 224 | 225 | } 226 | vTaskDelay(pdMS_TO_TICKS(300)); 227 | } 228 | } 229 | } 230 | 231 | void loop() {} 232 | -------------------------------------------------------------------------------- /I2S_adc_esp32/data_analysis.ino: -------------------------------------------------------------------------------- 1 | void peak_mean(uint16_t *i2s_buffer, uint32_t len, float * max_value, float * min_value, float *pt_mean) { 2 | max_value[0] = i2s_buffer[0]; 3 | min_value[0] = i2s_buffer[0]; 4 | mean_filter filter(5); 5 | filter.init(i2s_buffer[0]); 6 | 7 | float mean = 0; 8 | for (uint32_t i = 1; i < len; i++) { 9 | 10 | float value = filter.filter((float)i2s_buffer[i]); 11 | if (value > max_value[0]) 12 | max_value[0] = value; 13 | if (value < min_value[0]) 14 | min_value[0] = value; 15 | 16 | mean += i2s_buffer[i]; 17 | } 18 | mean /= float(BUFF_SIZE); 19 | mean = to_voltage(mean); 20 | pt_mean[0] = mean; 21 | } 22 | 23 | 24 | //true if digital/ false if analog 25 | bool digital_analog(uint16_t *i2s_buffer, uint32_t max_v, uint32_t min_v) { 26 | uint32_t upper_threshold = max_v - 0.05 * (max_v - min_v); 27 | uint32_t lower_threshold = min_v + 0.05 * (max_v - min_v); 28 | uint32_t digital_data = 0; 29 | uint32_t analog_data = 0; 30 | for (uint32_t i = 0; i < BUFF_SIZE; i++) { 31 | if (i2s_buffer[i] > lower_threshold) { 32 | if (i2s_buffer[i] > upper_threshold) { 33 | //HIGH DIGITAL 34 | digital_data++; 35 | } 36 | else { 37 | //ANALOG/TRANSITION 38 | analog_data++; 39 | } 40 | } 41 | else { 42 | //LOW DIGITAL 43 | digital_data++; 44 | } 45 | } 46 | 47 | //more than 50% of data is analog 48 | if (analog_data < digital_data) 49 | return true; 50 | 51 | return false; 52 | } 53 | 54 | void trigger_freq_analog(uint16_t *i2s_buffer, 55 | float sample_rate, 56 | float mean, 57 | uint32_t max_v, 58 | uint32_t min_v, 59 | float *pt_freq, 60 | float *pt_period, 61 | uint32_t *pt_trigger0, 62 | uint32_t *pt_trigger1) { 63 | float freq = 0; 64 | float period = 0; 65 | bool signal_side = false; 66 | uint32_t trigger_count = 0; 67 | uint32_t trigger_num = 10; 68 | uint32_t trigger_temp[trigger_num] = {0}; 69 | uint32_t trigger_index = 0; 70 | 71 | //get initial signal relative to the mean 72 | if (to_voltage(i2s_buffer[0]) > mean) { 73 | signal_side = true; 74 | } 75 | 76 | 77 | //waveform repetitions calculation + get triggers time 78 | uint32_t wave_center = (max_v + min_v) / 2; 79 | for (uint32_t i = 1 ; i < BUFF_SIZE; i++) { 80 | if (signal_side && i2s_buffer[i] < wave_center - (wave_center - min_v) * 0.2) { 81 | signal_side = false; 82 | } 83 | else if (!signal_side && i2s_buffer[i] > wave_center + (max_v - wave_center) * 0.2) { 84 | freq++; 85 | if (trigger_count < trigger_num) { 86 | trigger_temp[trigger_count] = i; 87 | trigger_count++; 88 | } 89 | signal_side = true; 90 | } 91 | } 92 | 93 | //frequency calculation 94 | if (trigger_count < 2) { 95 | trigger_temp[0] = 0; 96 | trigger_index = 0; 97 | freq = 0; 98 | period = 0; 99 | } 100 | else { 101 | 102 | //simple frequency calculation fair enough for frequencies over 2khz (20hz resolution) 103 | freq = freq * 1000 / 50; 104 | period = (float)(sample_rate * 1000.0) / freq; //us 105 | 106 | //from 2000 to 80 hz -> uses mean of the periods for precision 107 | if (freq < 2000 && freq > 80) { 108 | period = 0; 109 | for (uint32_t i = 1; i < trigger_count; i++) { 110 | period += trigger_temp[i] - trigger_temp[i - 1]; 111 | } 112 | period /= (trigger_count - 1); 113 | freq = sample_rate * 1000 / period; 114 | } 115 | 116 | //under 80hz, single period for frequency calculation 117 | else if (trigger_count > 1 && freq <= 80) { 118 | period = trigger_temp[1] - trigger_temp[0]; 119 | freq = sample_rate * 1000 / period; 120 | } 121 | } 122 | 123 | //setting triggers offset and getting second trigger for debug cursor on drawn_channel1 124 | /* 125 | The trigger function uses a rise porcentage (5%) obove the mean, thus, 126 | the real waveform starting point is some datapoints back. 127 | The resulting trigger gets a negative offset of 5% of the calculated period 128 | */ 129 | uint32_t trigger2 = 0; 130 | if (trigger_temp[0] - period * 0.05 > 0 && trigger_count > 1) { 131 | trigger_index = trigger_temp[0] - period * 0.05; 132 | trigger2 = trigger_temp[1] - period * 0.05; 133 | } 134 | else if (trigger_count > 2) { 135 | trigger_index = trigger_temp[1] - period * 0.05; 136 | if (trigger_count > 2) 137 | trigger2 = trigger_temp[2] - period * 0.05; 138 | } 139 | 140 | 141 | pt_trigger0[0] = trigger_index; 142 | pt_trigger1[0] = trigger2; 143 | pt_freq[0] = freq; 144 | pt_period[0] = period; 145 | 146 | } 147 | 148 | 149 | void trigger_freq_digital(uint16_t *i2s_buffer, 150 | float sample_rate, 151 | float mean, 152 | uint32_t max_v, 153 | uint32_t min_v, 154 | float *pt_freq, 155 | float *pt_period, 156 | uint32_t *pt_trigger0) { 157 | 158 | float freq = 0; 159 | float period = 0; 160 | bool signal_side = false; 161 | uint32_t trigger_count = 0; 162 | uint32_t trigger_num = 10; 163 | uint32_t trigger_temp[trigger_num] = {0}; 164 | uint32_t trigger_index = 0; 165 | 166 | //get initial signal relative to the mean 167 | if (to_voltage(i2s_buffer[0]) > mean) { 168 | signal_side = true; 169 | } 170 | 171 | //waveform repetitions calculation + get triggers time 172 | uint32_t wave_center = (max_v + min_v) / 2; 173 | bool normal_high = (mean > to_voltage(wave_center)) ? true : false; 174 | if (max_v - min_v > 4095 * (0.4 / 3.3)) { 175 | for (uint32_t i = 1 ; i < BUFF_SIZE; i++) { 176 | if (signal_side && i2s_buffer[i] < wave_center - (wave_center - min_v) * 0.2) { 177 | 178 | //signal was high, fell -> trigger if normal high 179 | if (trigger_count < trigger_num && normal_high) { 180 | trigger_temp[trigger_count] = i; 181 | trigger_count++; 182 | } 183 | 184 | signal_side = false; 185 | 186 | } 187 | else if (!signal_side && i2s_buffer[i] > wave_center + (max_v - wave_center) * 0.2) { 188 | freq++; 189 | 190 | //signal was low, rose -> trigger if normal low 191 | if (trigger_count < trigger_num && !normal_high) { 192 | trigger_temp[trigger_count] = i; 193 | trigger_count++; 194 | } 195 | 196 | signal_side = true; 197 | } 198 | } 199 | 200 | freq = freq * 1000 / 50; 201 | period = (float)(sample_rate * 1000.0) / freq; //us 202 | 203 | if (trigger_count > 1) { 204 | //from 2000 to 80 hz -> uses mean of the periods for precision 205 | if (freq < 2000 && freq > 80) { 206 | period = 0; 207 | for (uint32_t i = 1; i < trigger_count; i++) { 208 | period += trigger_temp[i] - trigger_temp[i - 1]; 209 | } 210 | period /= (trigger_count - 1); 211 | freq = sample_rate * 1000 / period; 212 | } 213 | 214 | //under 80hz, single period for frequency calculation 215 | else if (trigger_count > 1 && freq <= 80) { 216 | period = trigger_temp[1] - trigger_temp[0]; 217 | freq = sample_rate * 1000 / period; 218 | } 219 | } 220 | 221 | trigger_index = trigger_temp[0]; 222 | 223 | if (trigger_index > 10) 224 | trigger_index -= 10; 225 | else 226 | trigger_index = 0; 227 | } 228 | 229 | 230 | 231 | 232 | pt_trigger0[0] = trigger_index; 233 | pt_freq[0] = freq; 234 | pt_period[0] = period; 235 | 236 | } 237 | -------------------------------------------------------------------------------- /I2S_adc_esp32/screen.ino: -------------------------------------------------------------------------------- 1 | void setup_screen() { 2 | // Initialise the TFT registers 3 | tft.init(); 4 | tft.setRotation(1); 5 | 6 | // Optionally set colour depth to 8 or 16 bits, default is 16 if not spedified 7 | // spr.setColorDepth(8); 8 | 9 | // Create a sprite of defined size 10 | spr.createSprite(HEIGHT, WIDTH); 11 | 12 | // Clear the TFT screen to blue 13 | tft.fillScreen(TFT_BLACK); 14 | } 15 | 16 | int data[240] = {0}; 17 | 18 | float to_scale(float reading) { 19 | float temp = WIDTH - 20 | ( 21 | ( 22 | ( 23 | (float)((reading - 20480.0) / 4095.0) 24 | + (offset / 3.3) 25 | ) 26 | * 3300 / 27 | (v_div * 4) 28 | ) 29 | ) 30 | * (WIDTH - 1) 31 | - 1; 32 | return temp; 33 | } 34 | 35 | float to_voltage(float reading) { 36 | return (reading - 20480.0) / 4095.0 * 3.3; 37 | } 38 | 39 | uint32_t from_voltage(float voltage) { 40 | return uint32_t(voltage / 3.3 * 4095 + 20480.0); 41 | } 42 | 43 | void update_screen(uint16_t *i2s_buff, float sample_rate) { 44 | 45 | float mean = 0; 46 | float max_v, min_v; 47 | 48 | peak_mean(i2s_buff, BUFF_SIZE, &max_v, &min_v, &mean); 49 | 50 | float freq = 0; 51 | float period = 0; 52 | uint32_t trigger0 = 0; 53 | uint32_t trigger1 = 0; 54 | 55 | //if analog mode OR auto mode and wave recognized as analog 56 | bool digital_data = false; 57 | if (digital_wave_option == 1) { 58 | trigger_freq_analog(i2s_buff, sample_rate, mean, max_v, min_v, &freq, &period, &trigger0, &trigger1); 59 | } 60 | else if (digital_wave_option == 0) { 61 | digital_data = digital_analog(i2s_buff, max_v, min_v); 62 | if (!digital_data) { 63 | trigger_freq_analog(i2s_buff, sample_rate, mean, max_v, min_v, &freq, &period, &trigger0, &trigger1); 64 | } 65 | else { 66 | trigger_freq_digital(i2s_buff, sample_rate, mean, max_v, min_v, &freq, &period, &trigger0); 67 | } 68 | } 69 | else { 70 | trigger_freq_digital(i2s_buff, sample_rate, mean, max_v, min_v, &freq, &period, &trigger0); 71 | } 72 | 73 | draw_sprite(freq, period, mean, max_v, min_v, trigger0, sample_rate, digital_data, true); 74 | } 75 | 76 | void draw_sprite(float freq, 77 | float period, 78 | float mean, 79 | float max_v, 80 | float min_v, 81 | uint32_t trigger, 82 | float sample_rate, 83 | bool digital_data, 84 | bool new_data 85 | ) { 86 | 87 | max_v = to_voltage(max_v); 88 | min_v = to_voltage(min_v); 89 | 90 | String frequency = ""; 91 | if (freq < 1000) 92 | frequency = String(freq) + "hz"; 93 | else if (freq < 100000) 94 | frequency = String(freq / 1000) + "khz"; 95 | else 96 | frequency = "----"; 97 | 98 | String s_mean = ""; 99 | if (mean > 1.0) 100 | s_mean = "Avg: " + String(mean) + "V"; 101 | else 102 | s_mean = "Avg: " + String(mean * 1000.0) + "mV"; 103 | 104 | String str_filter = ""; 105 | if (current_filter == 0) 106 | str_filter = "None"; 107 | else if (current_filter == 1) 108 | str_filter = "Pixel"; 109 | else if (current_filter == 2) 110 | str_filter = "Mean-5"; 111 | else if (current_filter == 3) 112 | str_filter = "Lpass9"; 113 | 114 | String str_stop = ""; 115 | if (!stop) 116 | str_stop = "RUNNING"; 117 | else 118 | str_stop = "STOPPED"; 119 | 120 | String wave_option = ""; 121 | if (digital_wave_option == 0) 122 | if (digital_data ) 123 | wave_option = "AUTO:Dig./data"; 124 | else 125 | wave_option = "AUTO:Analog"; 126 | else if (digital_wave_option == 1) 127 | wave_option = "MODE:Analog"; 128 | else 129 | wave_option = "MODE:Dig./data"; 130 | 131 | 132 | if (new_data) { 133 | // Fill the whole sprite with black (Sprite is in memory so not visible yet) 134 | spr.fillSprite(TFT_BLACK); 135 | 136 | draw_grid(); 137 | 138 | if (auto_scale) { 139 | auto_scale = false; 140 | v_div = 1000.0 * max_v / 4.0; 141 | s_div = period / 3.5; 142 | if (s_div > 7000 || s_div <= 0) 143 | s_div = 7000; 144 | if (v_div <= 0) 145 | v_div = 825; 146 | } 147 | 148 | //only draw digital data if a trigger was in the data 149 | if (!(digital_wave_option == 2 && trigger == 0)) 150 | draw_channel1(trigger, 0, i2s_buff, sample_rate); 151 | } 152 | 153 | int shift = 150; 154 | if (menu) { 155 | spr.drawLine( 0, 67, 240, 67, TFT_WHITE); //center line 156 | spr.fillRect(shift, 0, 240, 135, TFT_BLACK); 157 | spr.drawRect(shift, 0, 240, 135, TFT_WHITE); 158 | spr.fillRect(shift + 1, 3 + 10 * (opt - 1), 100, 11, TFT_RED); 159 | 160 | spr.drawString("AUTOSCALE", shift + 5, 5); 161 | spr.drawString(String(int(v_div)) + "mV/div", shift + 5, 15); 162 | spr.drawString(String(int(s_div)) + "uS/div", shift + 5, 25); 163 | spr.drawString("Offset: " + String(offset) + "V", shift + 5, 35); 164 | spr.drawString("T-Off: " + String((uint32_t)toffset) + "uS", shift + 5, 45); 165 | spr.drawString("Filter: " + str_filter, shift + 5, 55); 166 | spr.drawString(str_stop, shift + 5, 65); 167 | spr.drawString(wave_option, shift + 5, 75); 168 | spr.drawString("Single " + String(single_trigger ? "ON" : "OFF"), shift + 5, 85); 169 | 170 | spr.drawLine(shift, 103, shift + 100, 103, TFT_WHITE); 171 | 172 | spr.drawString("Vmax: " + String(max_v) + "V", shift + 5, 105); 173 | spr.drawString("Vmin: " + String(min_v) + "V", shift + 5, 115); 174 | spr.drawString(s_mean, shift + 5, 125); 175 | 176 | shift -= 70; 177 | 178 | //spr.fillRect(shift, 0, 70, 30, TFT_BLACK); 179 | spr.drawRect(shift, 0, 70, 30, TFT_WHITE); 180 | spr.drawString("P-P: " + String(max_v - min_v) + "V", shift + 5, 5); 181 | spr.drawString(frequency, shift + 5, 15); 182 | String offset_line = String((2.0 * v_div) / 1000.0 - offset) + "V"; 183 | spr.drawString(offset_line, shift + 40, 59); 184 | 185 | if (set_value) { 186 | spr.fillRect(229, 0, 11, 11, TFT_BLUE); 187 | spr.drawRect(229, 0, 11, 11, TFT_WHITE); 188 | spr.drawLine(231, 5, 238 , 5, TFT_WHITE); 189 | spr.drawLine(234, 2, 234, 8, TFT_WHITE); 190 | 191 | 192 | spr.fillRect(229, 124, 11, 11, TFT_BLUE); 193 | spr.drawRect(229, 124, 11, 11, TFT_WHITE); 194 | spr.drawLine(231, 129, 238, 129, TFT_WHITE); 195 | } 196 | } 197 | else if (info) { 198 | spr.drawLine( 0, 67, 240, 67, TFT_WHITE); //center line 199 | spr.drawRect(shift + 10, 0, 240 - shift - 10, 30, TFT_WHITE); 200 | spr.drawString("P-P: " + String(max_v - min_v) + "V", shift + 15, 5); 201 | spr.drawString(frequency, shift + 15, 15); 202 | String offset_line = String((2.0 * v_div) / 1000.0 - offset) + "V"; 203 | spr.drawString(offset_line, shift + 60, 59); 204 | } 205 | 206 | 207 | //push the drawed sprite to the screen 208 | spr.pushSprite(0, 0); 209 | 210 | yield(); // Stop watchdog reset 211 | } 212 | 213 | void draw_grid() { 214 | 215 | for (int i = 0; i < 24; i++) { 216 | spr.drawPixel(i * 10, 33, TFT_WHITE); 217 | spr.drawPixel(i * 10, 67, TFT_WHITE); 218 | spr.drawPixel(i * 10, 101, TFT_WHITE); 219 | } 220 | for (int i = 0; i < 135; i += 10) { 221 | for (int j = 0; j < 240; j += 34) { 222 | spr.drawPixel(j, i, TFT_WHITE); 223 | } 224 | } 225 | } 226 | 227 | void draw_channel1(uint32_t trigger0, uint32_t trigger1, uint16_t *i2s_buff, float sample_rate) { 228 | //screen wave drawing 229 | data[0] = to_scale(i2s_buff[trigger0]); 230 | low_pass filter(0.99); 231 | mean_filter mfilter(5); 232 | mfilter.init(i2s_buff[trigger0]); 233 | filter._value = i2s_buff[trigger0]; 234 | float data_per_pixel = (s_div / 34.0) / (sample_rate / 1000); 235 | 236 | // uint32_t cursor = (trigger1-trigger0)/data_per_pixel; 237 | // spr.drawLine(cursor, 0, cursor, 135, TFT_RED); 238 | 239 | uint32_t index_offset = (uint32_t)(toffset / data_per_pixel); 240 | trigger0 += index_offset; 241 | uint32_t old_index = trigger0; 242 | float n_data = 0, o_data = to_scale(i2s_buff[trigger0]); 243 | for (uint32_t i = 1; i < 240; i++) { 244 | uint32_t index = trigger0 + (uint32_t)((i + 1) * data_per_pixel); 245 | if (index < BUFF_SIZE) { 246 | if (full_pix && s_div > 34 && current_filter == 0) { 247 | uint32_t max_val = i2s_buff[old_index]; 248 | uint32_t min_val = i2s_buff[old_index]; 249 | for (int j = old_index; j < index; j++) { 250 | //draw lines for all this data points on pixel i 251 | if (i2s_buff[j] > max_val) 252 | max_val = i2s_buff[j]; 253 | else if (i2s_buff[j] < min_val) 254 | min_val = i2s_buff[j]; 255 | 256 | } 257 | spr.drawLine(i, to_scale(min_val), i, to_scale(max_val), TFT_BLUE); 258 | } 259 | else { 260 | if (current_filter == 2) 261 | n_data = to_scale(mfilter.filter((float)i2s_buff[index])); 262 | else if (current_filter == 3) 263 | n_data = to_scale(filter.filter((float)i2s_buff[index])); 264 | else 265 | n_data = to_scale(i2s_buff[index]); 266 | 267 | spr.drawLine(i - 1, o_data, i, n_data, TFT_BLUE); 268 | o_data = n_data; 269 | } 270 | 271 | } 272 | else { 273 | break; 274 | } 275 | old_index = index; 276 | } 277 | } 278 | --------------------------------------------------------------------------------