├── .gitignore ├── .travis.yml ├── .vscode └── extensions.json ├── README.md ├── include └── README ├── lib ├── MicrophoneController │ ├── MicrophoneController.cpp │ ├── MicrophoneController.h │ ├── iir-filter.h │ └── inmp441.m └── README ├── platformio.ini └── src ├── backup.cpp └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32-INMP441 2 | ESP32 sound level meter with multiple INMP441 MEMES microphones 3 | 4 | TODO.... -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /lib/MicrophoneController/MicrophoneController.cpp: -------------------------------------------------------------------------------- 1 | #include "MicrophoneController.h" 2 | 3 | // 4 | // IIR Filters 5 | // 6 | 7 | // 8 | // Equalizer IIR filters to flatten microphone frequency response 9 | // See respective .m file for filter design. Fs = 48Khz. 10 | // 11 | 12 | // TDK/InvenSense INMP441 13 | // Datasheet: https://www.invensense.com/wp-content/uploads/2015/02/INMP441.pdf 14 | const double INMP441_B[] = {1.00198, -1.99085, 0.98892}; 15 | const double INMP441_A[] = {1.0, -1.99518, 0.99518}; 16 | IIR_FILTER INMP441(INMP441_B, INMP441_A); 17 | 18 | // 19 | // A-weighting 6th order IIR Filter, Fs = 48KHz 20 | // (By Dr. Matt L., Source: https://dsp.stackexchange.com/a/36122) 21 | // 22 | const double A_weighting_B[] = {0.169994948147430, 0.280415310498794, -1.120574766348363, 0.131562559965936, 0.974153561246036, -0.282740857326553, -0.152810756202003}; 23 | const double A_weighting_A[] = {1.0, -2.12979364760736134, 0.42996125885751674, 1.62132698199721426, -0.96669962900852902, 0.00121015844426781, 0.04400300696788968}; 24 | IIR_FILTER A_weighting(A_weighting_B, A_weighting_A); 25 | 26 | // Data we push to 'samples_queue' 27 | struct samples_queue_t 28 | { 29 | // Sum of squares of mic samples, after Equalizer filter 30 | IIR_ACCU_T sum_sqr_SPL; 31 | // Sum of squares of weighted mic samples 32 | IIR_ACCU_T sum_sqr_weighted; 33 | }; 34 | int32_t samples[SAMPLES_SHORT]; 35 | QueueHandle_t samples_queue; 36 | 37 | MicrophoneController::MicrophoneController() 38 | { 39 | pinMode(MIC_1_CHANEL, OUTPUT); 40 | pinMode(MIC_2_CHANEL, OUTPUT); 41 | delay(500); 42 | 43 | MicrophoneController::mic_i2s_install(); 44 | MicrophoneController::mic_i2s_set_pin(); 45 | 46 | Serial.println("Microphones I2S setup DONE"); 47 | } 48 | 49 | void MicrophoneController::setData() 50 | { 51 | // MIC 1 52 | digitalWrite(MIC_1_CHANEL, LOW); 53 | digitalWrite(MIC_2_CHANEL, HIGH); 54 | 55 | delay(500); 56 | i2s_start(I2S_PORT); 57 | delay(500); 58 | 59 | Serial.print("First MIC: "); 60 | setSensorData(&sensorA); 61 | i2s_stop(I2S_PORT); 62 | 63 | // MIC 2 64 | digitalWrite(MIC_1_CHANEL, HIGH); 65 | digitalWrite(MIC_2_CHANEL, LOW); 66 | 67 | delay(500); 68 | i2s_start(I2S_PORT); 69 | delay(500); 70 | 71 | Serial.print("Second MIC: "); 72 | setSensorData(&sensorB); 73 | i2s_stop(I2S_PORT); 74 | } 75 | 76 | // Rationale for separate task reading I2S is that IIR filter 77 | // processing cam be scheduled to different core on the ESP32 78 | // while i.e. the display is beeing updated... 79 | void MicrophoneController::setSensorData(MicrophoneData *data) 80 | { 81 | //Discard first block, microphone may have startup time(i.e.INMP441 up to 83ms) 82 | size_t bytes_read = 0; 83 | i2s_read(I2S_PORT, &samples, SAMPLES_SHORT * sizeof(int32_t), &bytes_read, portMAX_DELAY); 84 | 85 | uint32_t Leq_cnt = 0; 86 | IIR_ACCU_T Leq_sum_sqr = 0; 87 | 88 | int count = 0; 89 | 90 | while (true) 91 | { 92 | //Serial.print("."); 93 | IIR_ACCU_T sum_sqr_SPL = 0; 94 | IIR_ACCU_T sum_sqr_weighted = 0; 95 | samples_queue_t q; 96 | int32_t sample; 97 | IIR_BASE_T s; 98 | 99 | i2s_read(I2S_PORT, &samples, SAMPLES_SHORT * sizeof(int32_t), &bytes_read, portMAX_DELAY); 100 | for (int i = 0; i < SAMPLES_SHORT; i++) 101 | { 102 | sample = SAMPLE_CONVERT(samples[i]); 103 | s = sample; 104 | s = MIC_EQUALIZER.filter(s); 105 | ACCU_SQR(sum_sqr_SPL, s); 106 | s = WEIGHTING.filter(s); 107 | ACCU_SQR(sum_sqr_weighted, s); 108 | } 109 | 110 | q.sum_sqr_SPL = sum_sqr_SPL; 111 | q.sum_sqr_weighted = sum_sqr_weighted; 112 | 113 | // Calculate dB values relative to MIC_REF_AMPL and adjust for reference dB 114 | double short_SPL_dB = MIC_OFFSET_DB + MIC_REF_DB + 20 * log10(sqrt(double(q.sum_sqr_SPL) / SAMPLES_SHORT) / MIC_REF_AMPL); 115 | 116 | // In case of acoustic overload, report infinty Leq value 117 | if (short_SPL_dB > MIC_OVERLOAD_DB) 118 | Leq_sum_sqr = INFINITY; 119 | 120 | Leq_sum_sqr += q.sum_sqr_weighted; 121 | Leq_cnt += SAMPLES_SHORT; 122 | if (Leq_cnt >= SAMPLE_RATE * LEQ_PERIOD) 123 | { 124 | data->leq = MIC_OFFSET_DB + MIC_REF_DB + 20 * log10(sqrt(double(Leq_sum_sqr) / Leq_cnt) / MIC_REF_AMPL); 125 | Leq_sum_sqr = 0; 126 | Leq_cnt = 0; 127 | printf("%s(%ds) : %.1f\n", LEQ_UNITS, LEQ_PERIOD, data->leq); 128 | } 129 | 130 | count++; 131 | 132 | if (count > 50) 133 | { 134 | break; 135 | } 136 | } 137 | } 138 | 139 | void MicrophoneController::mic_i2s_install() 140 | { 141 | // Setup I2S to sample mono channel for SAMPLE_RATE * SAMPLE_BITS 142 | // NOTE: Recent update to Arduino_esp32 (1.0.2 -> 1.0.3) 143 | // seems to have swapped ONLY_LEFT and ONLY_RIGHT channels 144 | const i2s_config_t i2s_config = { 145 | .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), 146 | .sample_rate = SAMPLE_RATE, 147 | .bits_per_sample = i2s_bits_per_sample_t(SAMPLE_BITS), 148 | .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, 149 | .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), 150 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 151 | .dma_buf_count = DMA_BANKS, 152 | .dma_buf_len = DMA_BANK_SIZE, 153 | .use_apll = true, 154 | .tx_desc_auto_clear = false, 155 | .fixed_mclk = 0}; 156 | 157 | i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); 158 | } 159 | 160 | void MicrophoneController::mic_i2s_set_pin() 161 | { 162 | // I2S pin mapping 163 | const i2s_pin_config_t pin_config = { 164 | .bck_io_num = MIC_1_I2S_SCK, 165 | .ws_io_num = MIC_1_I2S_WS, 166 | .data_out_num = -1, // not used 167 | .data_in_num = MIC_1_I2S_SD}; 168 | 169 | i2s_set_pin(I2S_PORT, &pin_config); 170 | 171 | //FIXME: There is a known issue with esp-idf and sampling rates, see: 172 | // https://github.com/espressif/esp-idf/issues/2634 173 | // Update (when available) to the latest versions of esp-idf *should* fix it 174 | // In the meantime, the below line seems to set sampling rate at ~47999.992Hz 175 | // fifs_req=24576000, sdm0=149, sdm1=212, sdm2=5, odir=2 -> fifs_reached=24575996 176 | // rtc_clk_apll_enable(1, 149, 212, 5, 2); 177 | } -------------------------------------------------------------------------------- /lib/MicrophoneController/MicrophoneController.h: -------------------------------------------------------------------------------- 1 | #ifndef __MicrophoneController_H 2 | #define __MicrophoneController_H 3 | #include 4 | 5 | #include 6 | #include 7 | #include "iir-filter.h" 8 | 9 | // 10 | // Configuration 11 | // 12 | 13 | #define LEQ_PERIOD 5 // second(s) 14 | #define WEIGHTING A_weighting 15 | #define LEQ_UNITS "LAeq" 16 | #define DB_UNITS "dB(A)" 17 | 18 | #define MIC_EQUALIZER INMP441 // See below for defined IIR filters 19 | #define MIC_OFFSET_DB 3.0103 // Default offset (sine-wave RMS vs. dBFS) 20 | #define MIC_REF_DB 94.0 // dB(SPL) 21 | #define MIC_BITS 24 // valid bits in I2S data 22 | #define MIC_REF_AMPL 420426 // Amplitude at 94dB(SPL) (-26dBFS from datasheet, i.e. (2^23-1)*10^(-26/20) ) 23 | #define MIC_OVERLOAD_DB 116.0 // dB - Acoustic overload point*/ 24 | #define MIC_NOISE_DB 30 // dB - Noise floor*/ 25 | 26 | // 27 | // I2S Setup 28 | // 29 | #define I2S_TASK_PRI 4 30 | #define I2S_TASK_STACK 2048 31 | #define I2S_PORT I2S_NUM_0 32 | 33 | // 34 | // I2S pins 35 | // 36 | 37 | // MIC 1 38 | #define MIC_1_I2S_WS 12 39 | #define MIC_1_I2S_SD 14 //- OK da se menit OK: 32, 35, 33 40 | #define MIC_1_I2S_SCK 13 // OK 13, 14 41 | 42 | #define MIC_1_CHANEL 18 43 | #define MIC_2_CHANEL 19 44 | 45 | // 46 | // Sampling 47 | // 48 | #define SAMPLE_RATE 48000 // Hz, fixed to design of IIR filters 49 | #define SAMPLE_BITS 32 // bits 50 | #define SAMPLE_T int32_t 51 | #define SAMPLE_CONVERT(s) (s >> (SAMPLE_BITS - MIC_BITS)) 52 | #define SAMPLES_SHORT (SAMPLE_RATE / 8) // ~125ms 53 | #define SAMPLES_LEQ (SAMPLE_RATE * LEQ_PERIOD) 54 | #define DMA_BANK_SIZE (SAMPLES_SHORT / 16) 55 | #define DMA_BANKS 32 56 | 57 | struct MicrophoneData 58 | { 59 | double leq; 60 | }; 61 | 62 | class MicrophoneController 63 | { 64 | public: 65 | MicrophoneController(); 66 | MicrophoneData sensorA; 67 | MicrophoneData sensorB; 68 | MicrophoneData sensorC; 69 | void setData(); 70 | 71 | private: 72 | void setSensorData(MicrophoneData *data); 73 | void mic_i2s_install(); 74 | void mic_i2s_set_pin(); 75 | }; 76 | 77 | #endif -------------------------------------------------------------------------------- /lib/MicrophoneController/iir-filter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Modified version of 3 | * https://github.com/tttapa/Filters/blob/master/src/IIRFilter.h 4 | * Part of: 5 | * Arduino finite impulse response and infinite impulse response filter library 6 | * https://github.com/tttapa/Filters 7 | * 8 | * Original code is licensed under GPL-3.0 9 | * 10 | * (c)2019 Ivan Kostoski 11 | * 12 | * This program is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | */ 25 | 26 | #include 27 | 28 | /******************************************************************************/ 29 | 30 | // 31 | // Fixed point .32 precision (integer math) 32 | // - On ESP32, this seems to be ~2x faster than (emulated) double-precision 33 | // and should be good enough for 2 x 6th order IIR filters 34 | // - Error (difference to double precision) is <= 0.1dB down to 10Hz 35 | // - Should not overflow with 24bit microphone values (120dBFS) 36 | // 37 | #define IIR_ACCU_T fixed_point_64_32 38 | #define IIR_BASE_T fixed_point_32_32 39 | #define ACCU_MUL(A, X, Y) A.accu_mul(X, Y) 40 | #define ACCU_SQR(A, X) A.accu_sqr(X) 41 | 42 | // 43 | // Double precision floating point math 44 | // - Works with up to 4th + 6th order IIR filters 45 | // - With 2 x 6th order filters, usees up 122ms from 125ms available 46 | // on signle core. 47 | // - Ask Espressif nicely to enable, integrate or otherwise publish the 48 | // 'double-precission accelerator instructions' with appropriate libraries 49 | // (silicon should be in the ESP32 cores), to get ~2x improvement in DP-FPU speed. 50 | // 51 | //#define IIR_ACCU_T double 52 | //#define IIR_BASE_T double 53 | //#define ACCU_MUL(A, X, Y) A += (X) * (Y) 54 | //#define ACCU_SQR(A, X) A += (X) * (X) 55 | 56 | // NOTE: IIR filters with single-precision floats may 57 | // produce errors (>19db) for low frequencies below 40Hz 58 | // (i.e. in A-weighting filter) 59 | 60 | /******************************************************************************/ 61 | 62 | #define IIR_FILTER IIRFilter 63 | 64 | /****************************************************************************** 65 | * Fixed point 64.32 and 32.32 66 | * - 32bit platforms don't get __int128 in GCC 67 | * - Minimalistic implementation (only what is needed for IIRFilter) 68 | ******************************************************************************/ 69 | 70 | #define INLINE __attribute__((optimize("-O3"), always_inline)) inline 71 | #define high_word(x) (x >> 32) 72 | #define low_word(x) (uint32_t) x 73 | 74 | struct fixed_point_32_32; 75 | 76 | struct fixed_point_64_32 77 | { 78 | int64_t v; 79 | uint32_t f; 80 | 81 | fixed_point_64_32() : v(0), f(0){}; 82 | fixed_point_64_32(double d) 83 | { 84 | v = d; 85 | int64_t r = d * 0x100000000LL; 86 | f = low_word(r); 87 | }; 88 | explicit operator double() const { return (1.0 * f / 0x100000000LL) + v; } 89 | 90 | fixed_point_64_32 &operator+=(const fixed_point_64_32 &a); 91 | void accu_mul(const fixed_point_32_32 a, const fixed_point_32_32 b); 92 | void accu_sqr(const fixed_point_32_32 a); 93 | }; 94 | 95 | struct fixed_point_32_32 96 | { 97 | int32_t v; 98 | uint32_t f; 99 | fixed_point_32_32() : v(0), f(0){}; 100 | fixed_point_32_32(int32_t i) : v(i), f(0){}; 101 | fixed_point_32_32(long long i) 102 | { 103 | v = high_word(i); 104 | f = low_word(i); 105 | }; 106 | fixed_point_32_32(long long unsigned i) 107 | { 108 | v = high_word(i); 109 | f = low_word(i); 110 | }; 111 | fixed_point_32_32(double d) 112 | { 113 | int64_t i = d * 0x100000000LL; 114 | v = high_word(i); 115 | f = low_word(i); 116 | }; 117 | fixed_point_32_32(fixed_point_64_32 fp) 118 | { 119 | v = fp.v; 120 | f = fp.f; 121 | }; 122 | explicit operator int64_t() const { return (int64_t(v) << 32) + f; }; 123 | explicit operator double() const { return (1.0 * f / 0x100000000LL) + v; } 124 | }; 125 | 126 | // Assume we have MUL32_HIGH on ESP32 127 | #define mul32x32uu(A, B) ((uint64_t)(A) * (B)) 128 | #define mul32x32su(A, B) ((int64_t)(A) * (B)) 129 | #define mul32x32ss(A, B) ((int64_t)(A) * (B)) 130 | 131 | /* 132 | * Fixed point 64.32 - Add a 133 | */ 134 | INLINE fixed_point_64_32 &fixed_point_64_32::operator+=(const fixed_point_64_32 &a) 135 | { 136 | uint64_t sumf = f + a.f; 137 | f = low_word(sumf); 138 | v += a.v + high_word(sumf); 139 | return *this; 140 | } 141 | 142 | /* 143 | * Fixed point 64.32 - Multiply a * b and accumulatee 144 | */ 145 | INLINE void fixed_point_64_32::accu_mul(const fixed_point_32_32 a, const fixed_point_32_32 b) 146 | { 147 | int64_t value = mul32x32ss(a.v, b.v); 148 | uint64_t fract = high_word(mul32x32uu(a.f, b.f)); // Discarding 32 LSB bits here 149 | int64_t p = mul32x32su(a.v, b.f); // a.v * b.f 150 | fract += low_word(p); 151 | value += high_word(p); 152 | p = mul32x32su(b.v, a.f); // b.v * a.f 153 | fract += low_word(p); 154 | value += high_word(p); 155 | fract += f; // accumulate with carry 156 | f = low_word(fract); 157 | v += value + high_word(fract); 158 | } 159 | 160 | /* 161 | * Fixed point 64.32, trades one 32x32 multiplication for two shifts 162 | */ 163 | INLINE void fixed_point_64_32::accu_sqr(const fixed_point_32_32 a) 164 | { 165 | int64_t value = mul32x32ss(a.v, a.v); 166 | uint64_t fract = high_word(mul32x32uu(a.f, a.f)); // Discarding 32 LSB bits here 167 | int64_t p = mul32x32su(a.v, a.f); // a.v * a.f 168 | fract += (low_word(p) << 1); 169 | value += (high_word(p) << 1); 170 | fract += f; 171 | f = low_word(fract); 172 | v += value + high_word(fract); 173 | } 174 | 175 | /****************************************************************************** 176 | * IIR Filter implementation, direct form I, template 177 | ******************************************************************************/ 178 | template 179 | class IIRFilter 180 | { 181 | public: 182 | template 183 | IIRFilter(const double (&b)[B], const double (&_a)[A]) : lenB(B), lenA(A - 1) 184 | { 185 | x = new CoeffT[lenB](); 186 | y = new CoeffT[lenA](); 187 | coeff_b = new CoeffT[2 * lenB - 1]; 188 | coeff_a = new CoeffT[2 * lenA - 1]; 189 | double a0 = -1 / _a[0]; 190 | const double *a = &_a[1]; 191 | for (uint8_t i = 0; i < 2 * lenB - 1; i++) 192 | coeff_b[i] = b[(2 * lenB - 1 - i) % lenB] * a0; 193 | for (uint8_t i = 0; i < 2 * lenA - 1; i++) 194 | coeff_a[i] = a[(2 * lenA - 2 - i) % lenA] * a0; 195 | } 196 | 197 | ~IIRFilter() 198 | { 199 | delete[] x; 200 | delete[] y; 201 | delete[] coeff_a; 202 | delete[] coeff_b; 203 | } 204 | 205 | INLINE CoeffT filter(CoeffT value) 206 | { 207 | AccuT filtered = 0; 208 | x[i_b] = value; 209 | CoeffT *b_shift = &coeff_b[lenB - i_b - 1]; 210 | for (uint8_t i = 0; i < lenB; i++) 211 | ACCU_MUL(filtered, x[i], b_shift[i]); 212 | CoeffT *a_shift = &coeff_a[lenA - i_a - 1]; 213 | for (uint8_t i = 0; i < lenA; i++) 214 | ACCU_MUL(filtered, y[i], a_shift[i]); 215 | y[i_a] = filtered; 216 | i_b++; 217 | if (i_b == lenB) 218 | i_b = 0; 219 | i_a++; 220 | if (i_a == lenA) 221 | i_a = 0; 222 | return filtered; 223 | } 224 | 225 | protected: 226 | const uint8_t lenB, lenA; 227 | uint8_t i_b = 0, i_a = 0; 228 | CoeffT *x, *y, *coeff_b, *coeff_a; 229 | }; -------------------------------------------------------------------------------- /lib/MicrophoneController/inmp441.m: -------------------------------------------------------------------------------- 1 | % 2 | % INMP441 Equalizer filter design 3 | % 4 | % (c)2019 Ivan Kostoski 5 | % 6 | 7 | clear; 8 | % Sampling frequency 9 | Fs = 48000; 10 | 11 | % IEC specified frequencies 12 | iec_w = [10, 12.5, 16, 20, 25, 31.5, 40, 50, 63, 80, ... 13 | 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, ... 14 | 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000, ... 15 | 10000, 12500, 16000, 20000]; 16 | 17 | % Values visually estimated from INMP441 datasheet 'Typical Frequency Response' plot 18 | % 10, 12.5, 16, 20, 25, 31.5, 40, 50, 63, 80, ... 19 | ds_dB = [ -inf, -inf, -inf, -12, -10, -8, -6, -4.4, -3.4, -2.5, ... 20 | -1.9, -1.4, -0.9, -0.5, -0.2, 0, 0, 0, 0, 0, ... 21 | 0, 0, 0, 0, 0, 0, -0.4, -0.7, -1.1, -1.8, ... 22 | -2.4, -3.0, -5.0, -inf]; 23 | 24 | % These value are selected and adjusted for better curve fit 25 | ds_l_w = [ 20, 50, 100, 1000]; 26 | ds_l_dB = [ -13, -4.1, -1.8, 0]; 27 | 28 | % Low frequency filter design 29 | % Convert Hz in rad/s and normalize for Fs 30 | ds_l_wn = ds_l_w.*((2*pi)/Fs); 31 | % Convert plot decibels to magnitude 32 | ds_l_mag = arrayfun(@db2mag, ds_l_dB); 33 | % Estimate coefficients 34 | [ds_l_B, ds_l_A] = invfreqz(ds_l_mag, ds_l_wn, 2, 2); 35 | % Stabilize and normalize the filter 36 | ds_A = polystab(ds_l_A) * norm(ds_l_A) / norm(polystab(ds_l_A)); 37 | ds_B = polystab(ds_l_B) * norm(ds_l_B) / norm(polystab(ds_l_B)); 38 | ds_H = freqz(ds_B, ds_A, iec_w, Fs); 39 | 40 | % Equalizer filter, i.e. inverse from estimated transfer filter 41 | % Swap A and B coefficients, and normalize to ds_B(1) 42 | eq_B = ds_A./ds_B(1) 43 | eq_A = ds_B./ds_B(1) 44 | eq_H = freqz(eq_B, eq_A, iec_w, Fs); 45 | 46 | clf; 47 | figure(1, 'position', [0,0,800,500]); 48 | title("INMP441 Frequency response"); 49 | grid minor; 50 | xlabel('Frequency (Hz)'); 51 | xlim([10, 20000]); 52 | ylabel('Amplitude (dB)'); 53 | ylim([-20, 20]); 54 | hold on; 55 | semilogx(iec_w, ds_dB, 'g;INMP441 Datasheet plot (approx.);'); 56 | semilogx(iec_w, 20*log10(abs(ds_H)), '--c;IIR filter frequency response;'); 57 | semilogx(iec_w, (ds_dB + 20*log10(abs(eq_H))), 'b;Adjusted frequency response;', 'linewidth', 3); 58 | legend ('boxoff'); 59 | legend ('location', 'northwest'); 60 | hold off; -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ;PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32dev] 12 | platform = espressif32 13 | board = esp32dev 14 | framework = arduino 15 | monitor_speed = 115200 16 | -------------------------------------------------------------------------------- /src/backup.cpp: -------------------------------------------------------------------------------- 1 | // #include 2 | 3 | // // Connect to ESP32: 4 | // // INMP441 >> ESP32: 5 | // // SCK >> GPIO14 6 | // // SD >> GPIO32 7 | // // WS >> GPIO15 8 | // // GND >> GND 9 | // // VDD >> VDD3.3 10 | 11 | // // TENTO JE GOOOD https://hackaday.io/project/166867-esp32-i2s-slm & https://github.com/ikostoski/esp32-i2s-slm 12 | 13 | // // dalsi...: 14 | // // nevim : https://community.hiveeyes.org/t/first-steps-digital-sound-recording/306 15 | // // vzor https://github.com/maspetsberger/esp32-i2s-mems 16 | 17 | // #include 18 | // #include 19 | // #include "iir-filter.h" 20 | 21 | // // 22 | // // Configuration 23 | // // 24 | 25 | // #define LEQ_PERIOD 5 // second(s) 26 | // #define WEIGHTING A_weighting 27 | // #define LEQ_UNITS "LAeq" 28 | // #define DB_UNITS "dB(A)" 29 | 30 | // #define MIC_EQUALIZER INMP441 // See below for defined IIR filters 31 | // #define MIC_OFFSET_DB 3.0103 // Default offset (sine-wave RMS vs. dBFS) 32 | // #define MIC_REF_DB 94.0 // dB(SPL) 33 | // #define MIC_BITS 24 // valid bits in I2S data 34 | // #define MIC_REF_AMPL 420426 // Amplitude at 94dB(SPL) (-26dBFS from datasheet, i.e. (2^23-1)*10^(-26/20) ) 35 | // #define MIC_OVERLOAD_DB 116.0 // dB - Acoustic overload point*/ 36 | // #define MIC_NOISE_DB 30 // dB - Noise floor*/ 37 | 38 | // // 39 | // // I2S pins 40 | // // 41 | 42 | // // MIC 1 43 | // #define MIC_1_I2S_WS 12 44 | // #define MIC_1_I2S_SD 14 //- OK da se menit OK: 32, 35, 33 45 | // #define MIC_1_I2S_SCK 13 // OK 13, 14 46 | 47 | // #define MIC_1_CHANEL 18 48 | // #define MIC_2_CHANEL 19 49 | 50 | // // 51 | // // Sampling 52 | // // 53 | // #define SAMPLE_RATE 48000 // Hz, fixed to design of IIR filters 54 | // #define SAMPLE_BITS 32 // bits 55 | // #define SAMPLE_T int32_t 56 | // #define SAMPLE_CONVERT(s) (s >> (SAMPLE_BITS - MIC_BITS)) 57 | // #define SAMPLES_SHORT (SAMPLE_RATE / 8) // ~125ms 58 | // #define SAMPLES_LEQ (SAMPLE_RATE * LEQ_PERIOD) 59 | // #define DMA_BANK_SIZE (SAMPLES_SHORT / 16) 60 | // #define DMA_BANKS 32 61 | 62 | // // 63 | // // IIR Filters 64 | // // 65 | 66 | // // 67 | // // Equalizer IIR filters to flatten microphone frequency response 68 | // // See respective .m file for filter design. Fs = 48Khz. 69 | // // 70 | 71 | // // TDK/InvenSense INMP441 72 | // // Datasheet: https://www.invensense.com/wp-content/uploads/2015/02/INMP441.pdf 73 | // const double INMP441_B[] = {1.00198, -1.99085, 0.98892}; 74 | // const double INMP441_A[] = {1.0, -1.99518, 0.99518}; 75 | // IIR_FILTER INMP441(INMP441_B, INMP441_A); 76 | 77 | // // 78 | // // A-weighting 6th order IIR Filter, Fs = 48KHz 79 | // // (By Dr. Matt L., Source: https://dsp.stackexchange.com/a/36122) 80 | // // 81 | // const double A_weighting_B[] = {0.169994948147430, 0.280415310498794, -1.120574766348363, 0.131562559965936, 0.974153561246036, -0.282740857326553, -0.152810756202003}; 82 | // const double A_weighting_A[] = {1.0, -2.12979364760736134, 0.42996125885751674, 1.62132698199721426, -0.96669962900852902, 0.00121015844426781, 0.04400300696788968}; 83 | // IIR_FILTER A_weighting(A_weighting_B, A_weighting_A); 84 | 85 | // // Data we push to 'samples_queue' 86 | // struct samples_queue_t 87 | // { 88 | // // Sum of squares of mic samples, after Equalizer filter 89 | // IIR_ACCU_T sum_sqr_SPL; 90 | // // Sum of squares of weighted mic samples 91 | // IIR_ACCU_T sum_sqr_weighted; 92 | // }; 93 | // int32_t samples[SAMPLES_SHORT]; 94 | // QueueHandle_t samples_queue; 95 | 96 | // // 97 | // // I2S Setup 98 | // // 99 | // #define I2S_TASK_PRI 4 100 | // #define I2S_TASK_STACK 2048 101 | // #define I2S_PORT I2S_NUM_0 102 | 103 | // void mic_i2s_install() 104 | // { 105 | // // Setup I2S to sample mono channel for SAMPLE_RATE * SAMPLE_BITS 106 | // // NOTE: Recent update to Arduino_esp32 (1.0.2 -> 1.0.3) 107 | // // seems to have swapped ONLY_LEFT and ONLY_RIGHT channels 108 | // const i2s_config_t i2s_config = { 109 | // .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), 110 | // .sample_rate = SAMPLE_RATE, 111 | // .bits_per_sample = i2s_bits_per_sample_t(SAMPLE_BITS), 112 | // .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, 113 | // .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), 114 | // .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 115 | // .dma_buf_count = DMA_BANKS, 116 | // .dma_buf_len = DMA_BANK_SIZE, 117 | // .use_apll = true, 118 | // .tx_desc_auto_clear = false, 119 | // .fixed_mclk = 0}; 120 | 121 | // i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); 122 | // } 123 | 124 | // void mic_i2s_set_pin() 125 | // { 126 | // // I2S pin mapping 127 | // const i2s_pin_config_t pin_config = { 128 | // .bck_io_num = MIC_1_I2S_SCK, 129 | // .ws_io_num = MIC_1_I2S_WS, 130 | // .data_out_num = -1, // not used 131 | // .data_in_num = MIC_1_I2S_SD}; 132 | 133 | // i2s_set_pin(I2S_PORT, &pin_config); 134 | 135 | // //FIXME: There is a known issue with esp-idf and sampling rates, see: 136 | // // https://github.com/espressif/esp-idf/issues/2634 137 | // // Update (when available) to the latest versions of esp-idf *should* fix it 138 | // // In the meantime, the below line seems to set sampling rate at ~47999.992Hz 139 | // // fifs_req=24576000, sdm0=149, sdm1=212, sdm2=5, odir=2 -> fifs_reached=24575996 140 | // // rtc_clk_apll_enable(1, 149, 212, 5, 2); 141 | // } 142 | 143 | // // 144 | // // I2S Reader Task 145 | // // 146 | // // Rationale for separate task reading I2S is that IIR filter 147 | // // processing cam be scheduled to different core on the ESP32 148 | // // while i.e. the display is beeing updated... 149 | // // 150 | // void mic_i2s_reader_task(void *parameter) 151 | // { 152 | 153 | // //mic_i2s_init(); 154 | 155 | // //Discard first block, microphone may have startup time(i.e.INMP441 up to 83ms) 156 | // size_t bytes_read = 0; 157 | // i2s_read(I2S_PORT, &samples, SAMPLES_SHORT * sizeof(int32_t), &bytes_read, portMAX_DELAY); 158 | 159 | // while (true) 160 | // { 161 | // //Serial.print("."); 162 | // IIR_ACCU_T sum_sqr_SPL = 0; 163 | // IIR_ACCU_T sum_sqr_weighted = 0; 164 | // samples_queue_t q; 165 | // int32_t sample; 166 | // IIR_BASE_T s; 167 | 168 | // i2s_read(I2S_PORT, &samples, SAMPLES_SHORT * sizeof(int32_t), &bytes_read, portMAX_DELAY); 169 | // for (int i = 0; i < SAMPLES_SHORT; i++) 170 | // { 171 | // sample = SAMPLE_CONVERT(samples[i]); 172 | // s = sample; 173 | // s = MIC_EQUALIZER.filter(s); 174 | // ACCU_SQR(sum_sqr_SPL, s); 175 | // s = WEIGHTING.filter(s); 176 | // ACCU_SQR(sum_sqr_weighted, s); 177 | // } 178 | 179 | // q.sum_sqr_SPL = sum_sqr_SPL; 180 | // q.sum_sqr_weighted = sum_sqr_weighted; 181 | // xQueueSend(samples_queue, &q, portMAX_DELAY); 182 | // } 183 | // } 184 | 185 | // double Leq_dB = 0; 186 | 187 | // void setup() 188 | // { 189 | // Serial.begin(112500); 190 | // pinMode(MIC_1_CHANEL, OUTPUT); 191 | // pinMode(MIC_2_CHANEL, OUTPUT); 192 | // delay(1000); 193 | 194 | // mic_i2s_install(); 195 | // mic_i2s_set_pin(); 196 | 197 | // Serial.println("GOOOO\n\n"); 198 | // } 199 | 200 | // bool first = true; 201 | 202 | // void loop() 203 | // { 204 | // Serial.println(first ? "FIRST" : "SECOND"); 205 | // digitalWrite(MIC_1_CHANEL, first ? LOW : HIGH); 206 | // digitalWrite(MIC_2_CHANEL, first ? HIGH : LOW); 207 | // delay(500); 208 | 209 | // i2s_start(I2S_PORT); 210 | 211 | // delay(500); 212 | 213 | // //Discard first block, microphone may have startup time(i.e.INMP441 up to 83ms) 214 | // size_t bytes_read = 0; 215 | // i2s_read(I2S_PORT, &samples, SAMPLES_SHORT * sizeof(int32_t), &bytes_read, portMAX_DELAY); 216 | 217 | // uint32_t Leq_cnt = 0; 218 | // IIR_ACCU_T Leq_sum_sqr = 0; 219 | 220 | // int count = 0; 221 | 222 | // while (true) 223 | // { 224 | // //Serial.print("."); 225 | // IIR_ACCU_T sum_sqr_SPL = 0; 226 | // IIR_ACCU_T sum_sqr_weighted = 0; 227 | // samples_queue_t q; 228 | // int32_t sample; 229 | // IIR_BASE_T s; 230 | 231 | // i2s_read(I2S_PORT, &samples, SAMPLES_SHORT * sizeof(int32_t), &bytes_read, portMAX_DELAY); 232 | // for (int i = 0; i < SAMPLES_SHORT; i++) 233 | // { 234 | // sample = SAMPLE_CONVERT(samples[i]); 235 | // s = sample; 236 | // s = MIC_EQUALIZER.filter(s); 237 | // ACCU_SQR(sum_sqr_SPL, s); 238 | // s = WEIGHTING.filter(s); 239 | // ACCU_SQR(sum_sqr_weighted, s); 240 | // } 241 | 242 | // q.sum_sqr_SPL = sum_sqr_SPL; 243 | // q.sum_sqr_weighted = sum_sqr_weighted; 244 | 245 | // // Calculate dB values relative to MIC_REF_AMPL and adjust for reference dB 246 | // double short_SPL_dB = MIC_OFFSET_DB + MIC_REF_DB + 20 * log10(sqrt(double(q.sum_sqr_SPL) / SAMPLES_SHORT) / MIC_REF_AMPL); 247 | 248 | // // In case of acoustic overload, report infinty Leq value 249 | // if (short_SPL_dB > MIC_OVERLOAD_DB) 250 | // Leq_sum_sqr = INFINITY; 251 | 252 | // Leq_sum_sqr += q.sum_sqr_weighted; 253 | // Leq_cnt += SAMPLES_SHORT; 254 | // if (Leq_cnt >= SAMPLE_RATE * LEQ_PERIOD) 255 | // { 256 | // Leq_dB = MIC_OFFSET_DB + MIC_REF_DB + 20 * log10(sqrt(double(Leq_sum_sqr) / Leq_cnt) / MIC_REF_AMPL); 257 | // Leq_sum_sqr = 0; 258 | // Leq_cnt = 0; 259 | // printf("%s(%ds) : %.1f\n", LEQ_UNITS, LEQ_PERIOD, Leq_dB); 260 | // } 261 | 262 | // count++; 263 | 264 | // if (count > 50) 265 | // { 266 | // break; 267 | // } 268 | // } 269 | 270 | // i2s_stop(I2S_PORT); 271 | // first = !first; 272 | // } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Connect to ESP32: 6 | // INMP441 >> ESP32: 7 | // SCK >> GPIO14 8 | // SD >> GPIO32 9 | // WS >> GPIO15 10 | // GND >> GND 11 | // VDD >> VDD3.3 12 | 13 | // TENTO JE GOOOD https://hackaday.io/project/166867-esp32-i2s-slm & https://github.com/ikostoski/esp32-i2s-slm 14 | 15 | // dalsi...: 16 | // nevim : https://community.hiveeyes.org/t/first-steps-digital-sound-recording/306 17 | // vzor https://github.com/maspetsberger/esp32-i2s-mems 18 | 19 | MicrophoneController microphoneController; 20 | 21 | void checkMicrophones(); 22 | 23 | Ticker timerMicrophoneController(checkMicrophones, 20000); // 4 sec 24 | 25 | void setup() 26 | { 27 | Serial.begin(112500); 28 | timerMicrophoneController.start(); 29 | 30 | Serial.println("Setup DONE\n\n"); 31 | } 32 | 33 | void loop() 34 | { 35 | timerMicrophoneController.update(); 36 | } 37 | 38 | void checkMicrophones() 39 | { 40 | microphoneController.setData(); 41 | } --------------------------------------------------------------------------------