├── LICENSE
├── README.md
├── Serial_monitor.png
├── dBlevel
├── platformio.ini
└── src
│ ├── main.cpp
│ ├── math
│ ├── c_weighting.m
│ ├── ics43432.m
│ ├── ics43434.m
│ ├── im69d130.m
│ ├── inmp441.m
│ └── sph645lm4h-b.m
│ ├── misc
│ ├── esp32-i2s-slm-bp.svg
│ └── ics-43434-afr.svg
│ └── sos-iir-filter.h
├── dBlevel_in_MQTT.png
└── dBmeterESP32.jpg
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Carl
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ESP32-dBmeter-with-MQTT
2 | This dB level meter sends the result by MQTT
3 |
4 | ## Thanks to Ivan Kostoski for the base of this project##
5 | https://github.com/ikostoski/esp32-i2s-slm
6 |
7 | ## Parts used in this project ##
8 | Board: DOIT ESP32 DEVKIT V1 Available at Banggood
9 |
10 | Microphone: MH-ET LIVE Omnidirectional Microphone Module I2S Interface INMP441 MEMS High Precision Low Power Ultra small volume for ESP32 Available at Aliexpress
11 |
12 | ## Wiring ##
13 |
14 | |From|To|
15 | |---|---|
16 | |**ESP Module**|**INMP441**|
17 | |3V3| -> VDD|
18 | |GND| -> GND|
19 | |D18| -> WS|
20 | |D19| -> SD|
21 | |D23| -> SCK|
22 | |GND| -> L/R|
23 |
24 | ## How to start ##
25 |
26 | 1. Use Visual Basic Studio
27 | 2. add PlatformIO
28 | 3. start a new project
29 | 4. Choose a name
30 | 5. Platform DOIT ESP32 DEVKIT 1
31 | 6. When project is made copy files to src folder
32 | 7. Copy the platformio.ini file to \ (to have proper libraries installed)
33 | 8. Changes lines 20 to 25 in main.cpp according your network and MQTT
34 | 9. Compile and upload.
35 |
36 |
37 | ## Project Photo
38 | #### Display type: The set up
39 | 
40 |
41 | 
42 |
43 | 
44 |
45 | ## MIT License
46 |
47 | Copyright (c) 2020 SpoturDeal | Charley
48 |
49 | Permission is hereby granted, free of charge, to any person obtaining a copy
50 | of this software and associated documentation files (the "Software"), to deal
51 | in the Software without restriction, including without limitation the rights
52 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
53 | copies of the Software, and to permit persons to whom the Software is
54 | furnished to do so, subject to the following conditions:
55 |
56 | The above copyright notice and this permission notice shall be included in all
57 | copies or substantial portions of the Software.
58 |
59 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
60 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
61 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
62 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
63 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
64 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
65 | SOFTWARE.
66 |
--------------------------------------------------------------------------------
/Serial_monitor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpoturDeal/ESP32-dBmeter-with-MQTT/c54f68c9ea82268f98eb9a3a45453a4f20a7f7e2/Serial_monitor.png
--------------------------------------------------------------------------------
/dBlevel/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:esp32doit-devkit-v1]
12 | platform = espressif32
13 | board = esp32doit-devkit-v1
14 | framework = arduino
15 | monitor_speed =115200
16 |
17 | lib_deps =
18 | PubSubClient@~2.8
19 | ESP32 BLE Arduino@~1.0.1
20 |
--------------------------------------------------------------------------------
/dBlevel/src/main.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Sound level measured by I2S Microphone and send by MQTT
3 | * can be used for home automation like openHAB
4 | *
5 | * Based on the great work from 2019 Ivan Kostoski
6 | * https://github.com/ikostoski/esp32-i2s-slm
7 | *
8 | /*
9 | *
10 | */
11 | #include
12 | #include
13 | #include "sos-iir-filter.h"
14 | #include
15 | #include
16 |
17 | //
18 | // Configuration added for MQTT and Wifi
19 | //
20 | #define SSID "***********"
21 | #define PASW "***********"
22 | #define USER_MQTT "************"
23 | #define PASW_MQTT "************"
24 | #define IP_MQTT "192.168.0.0"
25 | #define PORT_MQTT 1883
26 | int connectCount = 0;
27 | int tryConnectSTA = 0;
28 | double baseDB = 0;
29 | double dBdifference = 1.0; // increease if you want reporting less sensible
30 |
31 |
32 | //
33 | // Configuration
34 | //
35 |
36 | #define LEQ_PERIOD 1 // second(s)
37 | #define WEIGHTING C_weighting // Also avaliable: 'C_weighting' or 'None' (Z_weighting)
38 | #define LEQ_UNITS "LAeq" // customize based on above weighting used
39 | #define DB_UNITS "dBA" // customize based on above weighting used
40 |
41 | // NOTE: Some microphones require at least DC-Blocker filter
42 | #define MIC_EQUALIZER INMP441 // See below for defined IIR filters or set to 'None' to disable
43 | #define MIC_OFFSET_DB 3.0103 // Default offset (sine-wave RMS vs. dBFS). Modify this value for linear calibration
44 |
45 | // Customize these values from microphone datasheet
46 | #define MIC_SENSITIVITY -26 // dBFS value expected at MIC_REF_DB (Sensitivity value from datasheet)
47 | #define MIC_REF_DB 94.0 // Value at which point sensitivity is specified in datasheet (dB)
48 | #define MIC_OVERLOAD_DB 116.0 // dB - Acoustic overload point
49 | #define MIC_NOISE_DB 29 // dB - Noise floor
50 | #define MIC_BITS 24 // valid number of bits in I2S data
51 | #define MIC_CONVERT(s) (s >> (SAMPLE_BITS - MIC_BITS))
52 | #define MIC_TIMING_SHIFT 0 // Set to one to fix MSB timing for some microphones, i.e. SPH0645LM4H-x
53 |
54 | // Calculate reference amplitude value at compile time
55 | constexpr double MIC_REF_AMPL = pow(10, double(MIC_SENSITIVITY)/20) * ((1<<(MIC_BITS-1))-1);
56 |
57 | //
58 | // I2S pins - Can be routed to almost any (unused) ESP32 pin.
59 | // SD can be any pin, inlcuding input only pins (36-39).
60 | // SCK (i.e. BCLK) and WS (i.e. L/R CLK) must be output capable pins
61 | //
62 | // Below ones are just example for my board layout, put here the pins you will use
63 | //
64 | #define I2S_WS 18
65 | #define I2S_SCK 23
66 | #define I2S_SD 19
67 |
68 | // I2S peripheral to use (0 or 1)
69 | #define I2S_PORT I2S_NUM_0
70 |
71 |
72 | // for Internet and MQTT
73 | WiFiClient espClient;
74 | PubSubClient mqttClient(espClient); // MQTT
75 |
76 |
77 | //
78 | // IIR Filters
79 | //
80 |
81 | // DC-Blocker filter - removes DC component from I2S data
82 | // See: https://www.dsprelated.com/freebooks/filters/DC_Blocker.html
83 | // a1 = -0.9992 should heavily attenuate frequencies below 10Hz
84 | SOS_IIR_Filter DC_BLOCKER ={
85 | gain: 1.0,
86 | sos: { {-1.0, 0.0, +0.9992, 0}}
87 | };
88 |
89 | //
90 | // Equalizer IIR filters to flatten microphone frequency response
91 | // See respective .m file for filter design. Fs = 48Khz.
92 | //
93 | // Filters are represented as Second-Order Sections cascade with assumption
94 | // that b0 and a0 are equal to 1.0 and 'gain' is applied at the last step
95 | // B and A coefficients were transformed with GNU Octave:
96 | // [sos, gain] = tf2sos(B, A)
97 | // See: https://www.dsprelated.com/freebooks/filters/Series_Second_Order_Sections.html
98 | // NOTE: SOS matrix 'a1' and 'a2' coefficients are negatives of tf2sos output
99 | //
100 |
101 | // TDK/InvenSense ICS-43434
102 | // Datasheet: https://www.invensense.com/wp-content/uploads/2016/02/DS-000069-ICS-43434-v1.1.pdf
103 | // B = [0.477326418836803, -0.486486982406126, -0.336455844522277, 0.234624646917202, 0.111023257388606];
104 | // A = [1.0, -1.93073383849136326, 0.86519456089576796, 0.06442838283825100, 0.00111249298800616];
105 | SOS_IIR_Filter ICS43434 ={
106 | gain: 0.477326418836803,
107 | sos: { // Second-Order Sections {b1, b2, -a1, -a2}
108 | {+0.96986791463971267, 0.23515976355743193, -0.06681948004769928, -0.00111521990688128},
109 | { -1.98905931743624453, 0.98908924206960169, +1.99755331853906037, -0.99755481510122113 }
110 | }
111 | };
112 |
113 | // TDK/InvenSense ICS-43432
114 | // Datasheet: https://www.invensense.com/wp-content/uploads/2015/02/ICS-43432-data-sheet-v1.3.pdf
115 | // B = [-0.45733702338341309 1.12228667105574775 -0.77818278904413563, 0.00968926337978037, 0.10345668405223755]
116 | // A = [1.0, -3.3420781082912949, 4.4033694320978771, -3.0167072679918010, 1.2265536567647031, -0.2962229189311990, 0.0251085747458112]
117 | SOS_IIR_Filter ICS43432 ={
118 | gain: -0.457337023383413,
119 | sos: { // Second-Order Sections {b1, b2, -a1, -a2}
120 | {-0.544047931916859, -0.248361759321800, +0.403298891662298, -0.207346186351843},
121 | { -1.909911869441421, +0.910830292683527, +1.790285722826743, -0.804085812369134 },
122 | { +0.000000000000000, +0.000000000000000, +1.148493493802252, -0.150599527756651 }
123 | }
124 | };
125 |
126 | // TDK/InvenSense INMP441
127 | // Datasheet: https://www.invensense.com/wp-content/uploads/2015/02/INMP441.pdf
128 | // B ~= [1.00198, -1.99085, 0.98892]
129 | // A ~= [1.0, -1.99518, 0.99518]
130 | SOS_IIR_Filter INMP441 ={
131 | gain: 1.00197834654696,
132 | sos: { // Second-Order Sections {b1, b2, -a1, -a2}
133 | {-1.986920458344451, +0.986963226946616, +1.995178510504166, -0.995184322194091}
134 | }
135 | };
136 |
137 | // Infineon IM69D130 Shield2Go
138 | // Datasheet: https://www.infineon.com/dgdl/Infineon-IM69D130-DS-v01_00-EN.pdf?fileId=5546d462602a9dc801607a0e46511a2e
139 | // B ~= [1.001240684967527, -1.996936108836337, 0.995703101823006]
140 | // A ~= [1.0, -1.997675693595542, 0.997677044195563]
141 | // With additional DC blocking component
142 | SOS_IIR_Filter IM69D130 ={
143 | gain: 1.00124068496753,
144 | sos: {
145 | {-1.0, 0.0, +0.9992, 0}, // DC blocker, a1 = -0.9992
146 | { -1.994461610298131, 0.994469278738208, +1.997675693595542, -0.997677044195563 }
147 | }
148 | };
149 |
150 | // Knowles SPH0645LM4H-B, rev. B
151 | // https://cdn-shop.adafruit.com/product-files/3421/i2S+Datasheet.PDF
152 | // B ~= [1.001234, -1.991352, 0.990149]
153 | // A ~= [1.0, -1.993853, 0.993863]
154 | // With additional DC blocking component
155 | SOS_IIR_Filter SPH0645LM4H_B_RB ={
156 | gain: 1.00123377961525,
157 | sos: { // Second-Order Sections {b1, b2, -a1, -a2}
158 | {-1.0, 0.0, +0.9992, 0}, // DC blocker, a1 = -0.9992
159 | { -1.988897663539382, +0.988928479008099, +1.993853376183491, -0.993862821429572 }
160 | }
161 | };
162 |
163 | //
164 | // Weighting filters
165 | //
166 |
167 | //
168 | // A-weighting IIR Filter, Fs = 48KHz
169 | // (By Dr. Matt L., Source: https://dsp.stackexchange.com/a/36122)
170 | // B = [0.169994948147430, 0.280415310498794, -1.120574766348363, 0.131562559965936, 0.974153561246036, -0.282740857326553, -0.152810756202003]
171 | // A = [1.0, -2.12979364760736134, 0.42996125885751674, 1.62132698199721426, -0.96669962900852902, 0.00121015844426781, 0.04400300696788968]
172 | SOS_IIR_Filter A_weighting ={
173 | gain: 0.169994948147430,
174 | sos: { // Second-Order Sections {b1, b2, -a1, -a2}
175 | {-2.00026996133106, +1.00027056142719, -1.060868438509278, -0.163987445885926},
176 | { +4.35912384203144, +3.09120265783884, +1.208419926363593, -0.273166998428332 },
177 | { -0.70930303489759, -0.29071868393580, +1.982242159753048, -0.982298594928989 }
178 | }
179 | };
180 |
181 | //
182 | // C-weighting IIR Filter, Fs = 48KHz
183 | // Designed by invfreqz curve-fitting, see respective .m file
184 | // B = [-0.49164716933714026, 0.14844753846498662, 0.74117815661529129, -0.03281878334039314, -0.29709276192593875, -0.06442545322197900, -0.00364152725482682]
185 | // A = [1.0, -1.0325358998928318, -0.9524000181023488, 0.8936404694728326 0.2256286147169398 -0.1499917107550188, 0.0156718181681081]
186 | SOS_IIR_Filter C_weighting ={
187 | gain: -0.491647169337140,
188 | sos: {
189 | {+1.4604385758204708, +0.5275070373815286, +1.9946144559930252, -0.9946217070140883},
190 | { +0.2376222404939509, +0.0140411206016894, -1.3396585608422749, -0.4421457807694559 },
191 | { -2.0000000000000000, +1.0000000000000000, +0.3775800047420818, -0.0356365756680430 }
192 | }
193 | };
194 |
195 |
196 | //
197 | // Sampling
198 | //
199 | #define SAMPLE_RATE 48000 // Hz, fixed to design of IIR filters
200 | #define SAMPLE_BITS 32 // bits
201 | #define SAMPLE_T int32_t
202 | #define SAMPLES_SHORT (SAMPLE_RATE / 8) // ~125ms
203 | #define SAMPLES_LEQ (SAMPLE_RATE * LEQ_PERIOD)
204 | #define DMA_BANK_SIZE (SAMPLES_SHORT / 16)
205 | #define DMA_BANKS 32
206 |
207 | // Data we push to 'samples_queue'
208 | struct sum_queue_t {
209 | // Sum of squares of mic samples, after Equalizer filter
210 | float sum_sqr_SPL;
211 | // Sum of squares of weighted mic samples
212 | float sum_sqr_weighted;
213 | // Debug only, FreeRTOS ticks we spent processing the I2S data
214 | uint32_t proc_ticks;
215 | };
216 | QueueHandle_t samples_queue;
217 |
218 | // Static buffer for block of samples
219 | float samples[SAMPLES_SHORT] __attribute__((aligned(4)));
220 |
221 | void connect_mqtt()
222 | {
223 | // Loop until we're reconnected
224 | while (!mqttClient.connected())
225 | {
226 | // Making sure that MQTT connects properly
227 | // Create a random client ID
228 | String clientId = "dBmeter-";
229 | clientId += String(random(0xffff), HEX);
230 | if (mqttClient.connect(clientId.c_str(), USER_MQTT, PASW_MQTT))
231 | {
232 | //mqttClient.setCallback(callback); // set the call back function
233 | //mqttClient.subscribe(mqttTopicSubscribe); // subscribe to topic
234 | mqttClient.publish("dBmeter/info", clientId.c_str()); // publish first message
235 | Serial.print("MQTT .. ");
236 | }
237 | else
238 | {
239 | // not able to connect
240 | Serial.print("failed, rc=");
241 | Serial.print(mqttClient.state());
242 | Serial.println(" try again in 5 seconds");
243 | // Wait 5 seconds before retrying
244 | delay(5000);
245 | connectCount = connectCount + 1;
246 | // if we are not able to connect within 5 times we do an ESP restart
247 | if (connectCount > 5)
248 | {
249 | ESP.restart();
250 | }
251 | }
252 | }
253 |
254 | }
255 |
256 | void setupWifi() {
257 | WiFi.setAutoConnect(true);
258 | // if the EEProm holds data for fixed address
259 | WiFi.mode(WIFI_STA);
260 | WiFi.begin(SSID, PASW);
261 | delay(150);
262 | WiFi.setHostname("dBmeter");
263 | Serial.print("Wifi connect result ");
264 | Serial.println(WiFi.status());
265 |
266 | while (WiFi.status() != WL_CONNECTED)
267 | {
268 | Serial.print("Connection Failed! Retrying... ");
269 | Serial.print("Wifi reconnect result ");
270 | Serial.println(WiFi.status());
271 | delay(5000);
272 | if (tryConnectSTA > 5)
273 | {
274 |
275 | Serial.println("Connection Failed! Restarting");
276 | ESP.restart();
277 | break;
278 | }
279 | tryConnectSTA = tryConnectSTA + 1;
280 | }
281 |
282 | Serial.println("");
283 | Serial.println("WiFi connected");
284 | Serial.println("IP address: ");
285 | Serial.println(WiFi.localIP());
286 |
287 | }
288 |
289 |
290 |
291 |
292 |
293 |
294 | //
295 | // I2S Microphone sampling setup
296 | //
297 | void mic_i2s_init() {
298 | // Setup I2S to sample mono channel for SAMPLE_RATE * SAMPLE_BITS
299 | // NOTE: Recent update to Arduino_esp32 (1.0.2 -> 1.0.3)
300 | // seems to have swapped ONLY_LEFT and ONLY_RIGHT channels
301 | const i2s_config_t i2s_config ={
302 | mode: i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
303 | sample_rate: SAMPLE_RATE,
304 | bits_per_sample: i2s_bits_per_sample_t(SAMPLE_BITS),
305 | channel_format: I2S_CHANNEL_FMT_ONLY_LEFT,
306 | communication_format: i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
307 | intr_alloc_flags: ESP_INTR_FLAG_LEVEL1,
308 | dma_buf_count: DMA_BANKS,
309 | dma_buf_len: DMA_BANK_SIZE,
310 | use_apll: true,
311 | tx_desc_auto_clear: false,
312 | fixed_mclk: 0
313 | };
314 | // I2S pin mapping
315 | const i2s_pin_config_t pin_config ={
316 | bck_io_num: I2S_SCK,
317 | ws_io_num: I2S_WS,
318 | data_out_num: -1, // not used
319 | data_in_num: I2S_SD
320 | };
321 |
322 | i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
323 |
324 | #if (MIC_TIMING_SHIFT > 0)
325 | // Undocumented (?!) manipulation of I2S peripheral registers
326 | // to fix MSB timing issues with some I2S microphones
327 | REG_SET_BIT(I2S_TIMING_REG(I2S_PORT), BIT(9));
328 | REG_SET_BIT(I2S_CONF_REG(I2S_PORT), I2S_RX_MSB_SHIFT);
329 | #endif
330 |
331 | i2s_set_pin(I2S_PORT, &pin_config);
332 |
333 | //FIXME: There is a known issue with esp-idf and sampling rates, see:
334 | // https://github.com/espressif/esp-idf/issues/2634
335 | // In the meantime, the below line seems to set sampling rate at ~47999.992Hz
336 | // fifs_req=24576000, sdm0=149, sdm1=212, sdm2=5, odir=2 -> fifs_reached=24575996
337 | //NOTE: This seems to be fixed in ESP32 Arduino 1.0.4, esp-idf 3.2
338 | // Should be safe to remove...
339 | //#include
340 | //rtc_clk_apll_enable(1, 149, 212, 5, 2);
341 | }
342 |
343 | //
344 | // I2S Reader Task
345 | //
346 | // Rationale for separate task reading I2S is that IIR filter
347 | // processing cam be scheduled to different core on the ESP32
348 | //
349 | //
350 | // As this is intended to run as separate hihg-priority task,
351 | // we only do the minimum required work with the I2S data
352 | // until it is 'compressed' into sum of squares
353 | //
354 | // FreeRTOS priority and stack size (in 32-bit words)
355 | #define I2S_TASK_PRI 4
356 | #define I2S_TASK_STACK 2048
357 | //
358 | void mic_i2s_reader_task(void* parameter) {
359 | mic_i2s_init();
360 |
361 | // Discard first block, microphone may have startup time (i.e. INMP441 up to 83ms)
362 | size_t bytes_read = 0;
363 | i2s_read(I2S_PORT, &samples, SAMPLES_SHORT * sizeof(int32_t), &bytes_read, portMAX_DELAY);
364 |
365 | while (true) {
366 | // Block and wait for microphone values from I2S
367 | //
368 | // Data is moved from DMA buffers to our 'samples' buffer by the driver ISR
369 | // and when there is requested ammount of data, task is unblocked
370 | //
371 | // Note: i2s_read does not care it is writing in float[] buffer, it will write
372 | // integer values to the given address, as received from the hardware peripheral.
373 | i2s_read(I2S_PORT, &samples, SAMPLES_SHORT * sizeof(SAMPLE_T), &bytes_read, portMAX_DELAY);
374 |
375 | TickType_t start_tick = xTaskGetTickCount();
376 |
377 | // Convert (including shifting) integer microphone values to floats,
378 | // using the same buffer (assumed sample size is same as size of float),
379 | // to save a bit of memory
380 | SAMPLE_T* int_samples = (SAMPLE_T*)&samples;
381 | for (int i=0; i MIC_OVERLOAD_DB) {
446 | Leq_sum_sqr = INFINITY;
447 | }
448 | else if (isnan(short_SPL_dB) || (short_SPL_dB < MIC_NOISE_DB)) {
449 | Leq_sum_sqr = -INFINITY;
450 | }
451 |
452 | // Accumulate Leq sum
453 | Leq_sum_sqr += q.sum_sqr_weighted;
454 | Leq_samples += SAMPLES_SHORT;
455 |
456 | // When we gather enough samples, calculate new Leq value
457 | if (Leq_samples >= SAMPLE_RATE * LEQ_PERIOD) {
458 | double Leq_RMS = sqrt(Leq_sum_sqr / Leq_samples);
459 | Leq_dB = MIC_OFFSET_DB + MIC_REF_DB + 20 * log10(Leq_RMS / MIC_REF_AMPL);
460 | Leq_sum_sqr = 0;
461 | Leq_samples = 0;
462 |
463 | // Serial output, customize (or remove) as needed
464 | Serial.printf("%.1f\n", Leq_dB);
465 | mqttClient.loop();
466 | if (abs(Leq_dB-baseDB) > dBdifference) {
467 | if (!mqttClient.connected())
468 | {
469 | connect_mqtt();
470 | }
471 | String msgOne = (String)Leq_dB;
472 | int msgLen = msgOne.length();
473 | mqttClient.beginPublish("dBmeter/soundlevel", msgLen, true);
474 | mqttClient.print(msgOne);
475 | mqttClient.endPublish();
476 | // publish rssi if you have connection issues
477 | String strValue = (String) WiFi.RSSI();
478 | msgLen = strValue.length();
479 | mqttClient.beginPublish("dBmeter/rssi", msgLen, true);
480 | mqttClient.print(strValue);
481 | mqttClient.endPublish();
482 |
483 | baseDB=Leq_dB;
484 | }
485 | // Debug only
486 | //Serial.printf("%u processing ticks\n", q.proc_ticks);
487 | }
488 | }
489 | }
490 |
491 | void loop() {
492 | // Nothing here..
493 | }
494 |
--------------------------------------------------------------------------------
/dBlevel/src/math/c_weighting.m:
--------------------------------------------------------------------------------
1 | %
2 | % C-weighting IIR filter design
3 | %
4 | % (c)2019 Ivan Kostoski
5 | %
6 |
7 | clear;
8 | format long;
9 | pi = 3.14159265358979;
10 |
11 | % Sampling Rate
12 | Fs = 48000;
13 |
14 | % IEC specified frequencies
15 | IEC_f = [10, 12.5, 16, 20, 25, 31.5, 40, 50, 63, 80, ...
16 | 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, ...
17 | 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000, ...
18 | 10000, 12500, 16000, 20000];
19 |
20 | % Logarithmic frequencies for graphs
21 | f = logspace(0, 5, 2048);
22 |
23 | % IEC specified attenuation for C-weighting
24 | IEC_f_cw = [-14.3, -11.2, -8.5, -6.2, -4.4, -3.0, -2.0, -1.3, -.8, -.5, ...
25 | -.3, -.2, -.1, 0, 0, 0, 0, 0, 0, 0, ...
26 | 0, 0, -.1, -.2, -.3, -.5, -.8, -1.3, -2.0, -3.0, ...
27 | -4.4, -6.2, -8.5, -11.2];
28 |
29 | % Analog C-weighting filter according to IEC/CD 1672.
30 | f1 = 20.598997; %Hz
31 | f2 = 107.65265; %Hz
32 | f3 = 737.86223; %Hz
33 | f4 = 12194.217; %Hz
34 | C1000 = 0.0619;
35 | NUM = [(2*pi*f4)^2*(10^(C1000/20)), 0, 0];
36 | DEN = conv([1, +4*pi*f4, (2*pi*f4)^2], [1, +4*pi*f1, (2*pi*f1)^2]);
37 | % Analog transfer function
38 | H_a = freqs(NUM, DEN, f.*(2*pi));
39 |
40 | % Bilinear transformation of analog design to get the digital
41 | [B_bt, A_bt] = bilinear(NUM, DEN, 1/Fs);
42 | H_bt = freqz(B_bt, A_bt, f, Fs);
43 |
44 | % We will take the only the low frequency part of bilinear transformation
45 | % Which was calculated to be approximately (the first SOS):
46 | B_lo = [1, -2, 1];
47 | A_lo = [1, -1.9946144559930206, 0.9946217070140836];
48 | [sos_lo, gain_lo] = tf2sos(B_lo, A_lo);
49 |
50 | %
51 | % Curve fitting of the 'high' frequency part with invfreqz
52 | %
53 | f_hi = [1000, 1250, 10000, 16000, 20000, 24000];
54 | dB_hi = [ 0, 0, -4.4, -8.6, -11.3, -15.0];
55 | w_hi = f_hi.*((2*pi)/Fs);
56 | mag_hi = arrayfun(@db2mag, dB_hi);
57 | [B_hi, A_hi] = invfreqz(mag_hi, w_hi, 4, 4);
58 | [sos_hi, gain_hi] = tf2sos(B_hi, A_hi);
59 |
60 | % Merge SOS sections
61 | sos = [sos_lo; sos_hi]
62 | % Multiply the gain and reduce it a bit
63 | gain = gain_lo * gain_hi * 0.996
64 |
65 | % Convert to transfer function and try to stabilize
66 | [B_tf, A_tf] = sos2tf(sos, gain);
67 | B_tf_s = polystab(B_tf) * norm(B_tf) / norm(polystab(B_tf));
68 | A_tf_s = polystab(A_tf) * norm(A_tf) / norm(polystab(A_tf));
69 | A_tf_s0 = A_tf_s(1);
70 | % Caluclate and display stable coefficients
71 | B = B_tf_s./A_tf_s0
72 | A = A_tf_s./A_tf_s0
73 | % Display the roots
74 | roots(B)
75 | roots(A)
76 | H = freqz(B, A, f, Fs);
77 |
78 | % Frequency response graph
79 | clf;
80 | hold on;
81 | grid on;
82 | xlim([10, Fs/2]);
83 | ylim([-15, 5]);
84 | xlabel('Frequency (Hz)');
85 | ylabel('Magnitude (dB)');
86 | semilogx(f, 20*log10(abs(H_a)), 'g;IEC specified transfer function for C-weighting;');
87 | semilogx(f, 20*log10(abs(H_bt)), 'r;Bilinear trasformation;');
88 | semilogx(f, 20*log10(abs(H)), 'b;High frequency part curve fitting with invfreqz;');
89 | hold off
90 |
91 | % Filter error graph
92 | clf;
93 | hold on;
94 | grid on;
95 | grid minor;
96 | xlim([10, 20000]);
97 | ylim([-1, +1]);
98 | xlabel('Frequency (Hz)');
99 | ylabel('Filter error (dB)');
100 | semilogx(f, 20*log10(abs(H_bt)) - 20*log10(abs(H_a)), 'r;Error with just bilinear trasformation filter;');
101 | semilogx(f, 20*log10(abs(H)) - 20*log10(abs(H_a)), 'b;Error with additional curve fitting via invfreqz;');
102 | hold off;
103 |
104 | % Display the sections
105 | [sos1, gain1] = tf2sos(B, A)
106 |
--------------------------------------------------------------------------------
/dBlevel/src/math/ics43432.m:
--------------------------------------------------------------------------------
1 | %
2 | % ICS-43432 Equalizer filter design
3 | %
4 | % (c)2019 Ivan Kostoski
5 | %
6 |
7 | clear;
8 | Fs = 48000;
9 | format long G;
10 |
11 | % IEC specified frequencies
12 | iec_f = [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 ICS-43432 datasheet for IEC frequencies
18 | % https://www.invensense.com/wp-content/uploads/2015/02/ICS-43432-data-sheet-v1.3.pdf
19 | % Figure 5 on page 9
20 | % 10, 12.5, 16, 20, 25, 31.5, 40, 50, 63, 80,
21 | ds_e_dB = [ -22, -19, -15, -12, -8, -6, -4.5, -3.5, -2.1, -1.1, ...
22 | -0.4, +0.1, +0.6, +0.9, +1.0, 0.9, +0.8, +0.6, +0.4, +0.3, ...
23 | 0, +0.3, +0.4, +0.5, +0.7, +1.0, +1.2, +1.4, +1.8, +2.3, ...
24 | +3.5, +6.0, +9.0, +14.0];
25 |
26 | % Manually chosen and adjusted values for fitting the transfer function
27 | ds_f = [ 20, 100, 630, 1150, 2000, 10000, 20000];
28 | ds_dB = [-16, -0.5, +0.7, 0, +0.5, +3.5, +14];
29 | [ds_B, ds_A] = invfreqz(arrayfun(@db2mag, ds_dB), (ds_f.*((2*pi)/Fs)), 6, 4);
30 | % Stabilize polynoms
31 | ds_Bs = polystab(ds_B) * norm(ds_B) / norm(polystab(ds_B));
32 | ds_As = polystab(ds_A) * norm(ds_A) / norm(polystab(ds_A));
33 | ds_H = freqz(ds_Bs, ds_As, iec_f, Fs);
34 |
35 | % Equalizer filter, i.e. inverse from estimated transfer filter
36 | % Swap A and B coefficients, and normalize to ds_B(1)
37 | eq_B = ds_As./ds_Bs(1)
38 | eq_A = ds_Bs./ds_Bs(1)
39 | eq_H = freqz(eq_B, eq_A, iec_f, Fs);
40 | % Check for poles ouside unit circle
41 | roots(eq_B)
42 | roots(eq_A)
43 |
44 | clf;
45 | hold on;
46 | semilogx(iec_f, ds_e_dB, 'g;ICS-43432 Datasheet plot (estimated);');
47 | semilogx(iec_f, 20 * log10 (abs (ds_H)), 'r;IIR filter frequency response;');
48 | semilogx(iec_f, (ds_e_dB + 20*log10(abs(eq_H))), 'b;Adjusted frequency response;', 'linewidth', 3);
49 | title("ICS-43432 Adjusted frequency response");
50 | grid minor;
51 | xlabel('Frequency (Hz)');
52 | xlim([10, 20000]);
53 | ylabel('Amplitude (dB)');
54 | ylim([-20, 20]);
55 | legend ('boxoff');
56 | legend ('location', 'northwest');
57 | ylim ([-20, 20]);
58 | grid on;
59 | hold off;
60 |
61 | [sos, gain] = tf2sos(eq_B, eq_A)
62 |
--------------------------------------------------------------------------------
/dBlevel/src/math/ics43434.m:
--------------------------------------------------------------------------------
1 | %
2 | % ICS-43434 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_f = [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 | % IEC Class 1 tolerances (top/bottom)
18 | iec_c1_t_dB = [+3.5, +3.0, +2.5, +2.5, +2.5, +2.0, +1.5, +1.5, +1.5, +1.5, ...
19 | +1.5, +1.5, +1.5, +1.5, +1.4, +1.4, +1.4, +1.4, +1.4, +1.4, ...
20 | +1.1, +1.4, +1.4, +1.6, +1.6, +1.6, +1.6, +2.1, +2.1, +2.1, ...
21 | +2.6, +3.0, +3.5, +4.0];
22 | iec_c1_b_dB = [-inf, -inf, -4.5, -2.5, -2.5, -2.0, -1.5, -1.5, -1.5, -1.5, ...
23 | -1.5, -1.5, -1.5, -1.5, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, ...
24 | -1.1, -1.4, -1.4, -1.6, -1.6, -1.6, -1.6, -2.1, -2.6, -3.1, ...
25 | -3.6, -6.0, -17, -inf];
26 |
27 | % IEC Class 2 tolerances (top/bottom)
28 | iec_c2_t_dB = [+5.5, +5.5, +5.5, +3.5, +3.5, +3.5, +2.5, +2.5, +2.5, +2.5, ...
29 | +2.0, +2.0, +2.0, +2.0, +1.9, +1.9, +1.9, +1.9, +1.9, +1.9, ...
30 | +1.4, +1.9, +2.6, +2.6, +3.1, +3.1, +3.6, +4.1, +5.1, +5.6, ...
31 | +6.0, +6.0, +6.0, +6.0];
32 | iec_c2_b_dB = [-inf, -inf, -inf, -3.5, -3.5, -3.5, -2.5, -2.5, -2.5, -2.5, ...
33 | -2.0, -2.0, -2.0, -2.0, -1.9, -1.9, -1.9, -1.9, -1.9, -1.9, ...
34 | -1.4, -1.9, -2.6, -2.6, -3.1, -3.1, -3.6, -4.1, -5.1, -5.6, ...
35 | -inf, -inf, -inf, -inf];
36 |
37 | % Values visually estimated from ICS-43434 datasheet 'Typical Frequency Response' plot
38 | ds_dB = [ -26, -22, -17, -13, -10, -8, -6, -4, -2.8, -2.1, ...
39 | -1.8, -1.4, -1.0, -0.5, -0.5, 0, 0, 0, 0, 0, ...
40 | 0, 0, 0, 0, 0, +0.5, +0.6, +1.1, +1.8, +2.5, ...
41 | +3.1, +4.5, +8.0, +14.0];
42 |
43 | % These value are selected and adjusted for better curve fit
44 | ds_l_f = [ 10, 20, 50, 100, 1000];
45 | ds_l_dB = [-26, -12, -3.5, -1.5, 0];
46 | ds_h_f = [ 1000, 5000, 10000, 20000];
47 | ds_h_dB = [ 0, +1.1, +3.1, +14.0];
48 |
49 | % Low frequency filter design
50 | % Convert Hz in rad/s and normalize for Fs
51 | ds_l_w = ds_l_f.*((2*pi)/Fs);
52 | % Convert plot decibels to magnitude
53 | ds_l_mag = arrayfun(@db2mag, ds_l_dB);
54 | % Estimate coefficients
55 | [ds_l_B, ds_l_A] = invfreqz(ds_l_mag, ds_l_w, 2, 2);
56 | % Stabilize and normalize the filter
57 | ds_l_As = polystab(ds_l_A) * norm(ds_l_A) / norm(polystab(ds_l_A));
58 | ds_l_Bs = polystab(ds_l_B) * norm(ds_l_B) / norm(polystab(ds_l_B));
59 |
60 | % High frequency filter design
61 | [ds_h_B, ds_h_A] = invfreqz(arrayfun(@db2mag, ds_h_dB), (ds_h_f.*((2*pi)/Fs)) , 2, 2);
62 | ds_h_As = polystab(ds_h_A) * norm(ds_h_A) / norm(polystab(ds_h_A));
63 | ds_h_Bs = polystab(ds_h_B) * norm(ds_h_B) / norm(polystab(ds_h_B));
64 |
65 | % Convolve into single 4th order filter
66 | ds_A = conv(ds_l_As, ds_h_As);
67 | ds_B = conv(ds_l_Bs, ds_h_Bs);
68 | ds_H = freqz(ds_B, ds_A, iec_f, Fs);
69 |
70 | % Equalizer filter, i.e. inverse from estimated transfer filter
71 | % Swap A and B coefficients, and normalize to ds_B(1)
72 | eq_B = ds_A./ds_B(1)
73 | eq_A = ds_B./ds_B(1)
74 | eq_H = freqz(eq_B, eq_A, iec_f, Fs);
75 | % Check for poles ouside unit circle
76 | roots(eq_B)
77 | roots(eq_A)
78 |
79 | clf;
80 | semilogx(iec_f, ds_dB, 'g;ICS-43434 Datasheet plot (approx.);');
81 | hold on;
82 | title("ICS-43434 Frequency response");
83 | grid minor;
84 | xlabel('Frequency (Hz)');
85 | xlim([10, 20000]);
86 | ylabel('Amplitude (dB)');
87 | ylim([-20, 20]);
88 | legend ('boxoff');
89 | legend ('location', 'northwest');
90 | semilogx(iec_f, iec_c1_t_dB, '--r;IEC 61672-1:2013 Class 1 tolerance;');
91 | semilogx(iec_f, iec_c1_b_dB, '--r');
92 | semilogx(iec_f, iec_c2_t_dB, 'r;IEC 61672-1:2013 Class 2 tolerance;');
93 | semilogx(iec_f, iec_c2_b_dB, 'r');
94 | semilogx(iec_f, 20*log10(abs(ds_H)), '--c;IIR filter frequency response;');
95 | semilogx(iec_f, (ds_dB + 20*log10(abs(eq_H))), 'b;Adjusted frequency response;', 'linewidth', 3);
96 | hold off;
97 |
--------------------------------------------------------------------------------
/dBlevel/src/math/im69d130.m:
--------------------------------------------------------------------------------
1 | %
2 | % IM69D130 Equalizer filter design
3 | %
4 | % (c)2019 Ivan Kostoski
5 | %
6 |
7 | clear;
8 | clf;
9 | format long;
10 | pi = 3.14159265358979;
11 |
12 | % Sampling Rate
13 | Fs = 48000;
14 |
15 | % IEC specified frequencies
16 | iec_f = [10, 12.5, 16, 20, 25, 31.5, 40, 50, 63, 80, ...
17 | 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, ...
18 | 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000, ...
19 | 10000, 12500, 16000, 20000];
20 |
21 | % IEC Class 1 tolerances (top/bottom)
22 | iec_c1_t_dB = [+3.5, +3.0, +2.5, +2.5, +2.5, +2.0, +1.5, +1.5, +1.5, +1.5, ...
23 | +1.5, +1.5, +1.5, +1.5, +1.4, +1.4, +1.4, +1.4, +1.4, +1.4, ...
24 | +1.1, +1.4, +1.4, +1.6, +1.6, +1.6, +1.6, +2.1, +2.1, +2.1, ...
25 | +2.6, +3.0, +3.5, +4.0];
26 | iec_c1_b_dB = [-inf, -inf, -4.5, -2.5, -2.5, -2.0, -1.5, -1.5, -1.5, -1.5, ...
27 | -1.5, -1.5, -1.5, -1.5, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, ...
28 | -1.1, -1.4, -1.4, -1.6, -1.6, -1.6, -1.6, -2.1, -2.6, -3.1, ...
29 | -3.6, -6.0, -17, -inf];
30 |
31 | % IEC Class 2 tolerances (top/bottom)
32 | iec_c2_t_dB = [+5.5, +5.5, +5.5, +3.5, +3.5, +3.5, +2.5, +2.5, +2.5, +2.5, ...
33 | +2.0, +2.0, +2.0, +2.0, +1.9, +1.9, +1.9, +1.9, +1.9, +1.9, ...
34 | +1.4, +1.9, +2.6, +2.6, +3.1, +3.1, +3.6, +4.1, +5.1, +5.6, ...
35 | +6.0, +6.0, +6.0, +6.0];
36 | iec_c2_b_dB = [-inf, -inf, -inf, -3.5, -3.5, -3.5, -2.5, -2.5, -2.5, -2.5, ...
37 | -2.0, -2.0, -2.0, -2.0, -1.9, -1.9, -1.9, -1.9, -1.9, -1.9, ...
38 | -1.4, -1.9, -2.6, -2.6, -3.1, -3.1, -3.6, -4.1, -5.1, -5.6, ...
39 | -inf, -inf, -inf, -inf];
40 |
41 | % Values visually estimated from datasheet
42 | ds_dB = [ -inf, -inf, -inf, -5, -3.2, -2.5, -1.8, -1.1, -0.8, -0.6, ...
43 | -0.4, -0.2, -0.1, 0, 0, 0, 0, 0, 0, 0, ...
44 | 0, 0, 0, 0, 0, 0, 0, 0, -0.1, -0.2, ...
45 | -0.3, -0.4, +0.8, +2.5];
46 |
47 | % These value are selected and adjusted for better curve fit
48 | ds_l_w = [ 20, 31.5, 50, 1000];
49 | ds_l_dB = [ -4, -3.2, -1.1, 0];
50 |
51 | % Low frequency filter design
52 | % Convert Hz in rad/s and normalize for Fs
53 | ds_l_wn = ds_l_w.*((2*pi)/Fs);
54 | % Convert plot decibels to magnitude
55 | ds_l_mag = arrayfun(@db2mag, ds_l_dB);
56 | % Estimate coefficients
57 | [ds_l_B, ds_l_A] = invfreqz(ds_l_mag, ds_l_wn, 2, 2);
58 | % Stabilize and normalize the filter
59 | ds_B = polystab(ds_l_B) * norm(ds_l_B) / norm(polystab(ds_l_B));
60 | ds_A = polystab(ds_l_A) * norm(ds_l_A) / norm(polystab(ds_l_A));
61 | ds_H = freqz(ds_B, ds_A, iec_f, Fs);
62 |
63 | % Equalizer filter, i.e. inverse from estimated transfer filter
64 | % Swap A and B coefficients, and normalize to ds_B(1)
65 | eq_B = ds_A./ds_B(1)
66 | eq_A = ds_B./ds_B(1)
67 | % eq_B = 1.001240684967618 -1.996936108836719 0.995703101823296
68 | % eq_A = 1.000000000000000 -1.997675693595923 0.997677044195944
69 | eq_H = freqz(eq_B, eq_A, iec_f, Fs);
70 | % Check for poles ouside unit circle
71 | roots(eq_B)
72 | roots(eq_A)
73 |
74 |
75 | clf;
76 | semilogx(iec_f, ds_dB, 'g;IM69D130 Datasheet plot (approx.);');
77 | hold on;
78 | title("IM69D130 Adjusted Frequency Response");
79 | grid minor;
80 | xlabel('Frequency (Hz)');
81 | xlim([10, 24000]);
82 | ylabel('Amplitude (dB)');
83 | ylim([-20, 20]);
84 | legend ('boxoff');
85 | legend ('location', 'northwest');
86 | semilogx(iec_f, iec_c1_t_dB, '--r;IEC 61672-1:2013 Class 1 tolerance;');
87 | semilogx(iec_f, iec_c1_b_dB, '--r');
88 | semilogx(iec_f, iec_c2_t_dB, 'r;IEC 61672-1:2013 Class 2 tolerance;');
89 | semilogx(iec_f, iec_c2_b_dB, 'r');
90 | semilogx(iec_f, 20*log10(abs(ds_H)), '--c;IIR filter frequency response;');
91 | semilogx(iec_f, (ds_dB + 20*log10(abs(eq_H))), 'b;Adjusted frequency response;', 'linewidth', 1);
92 | hold off;
93 |
94 | [sos, gain] = tf2sos(eq_B, eq_A)
95 |
--------------------------------------------------------------------------------
/dBlevel/src/math/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_f = [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_f = [ 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_w = ds_l_f.*((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_w, 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_f, 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_f, 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('Magnitude (dB)');
53 | ylim([-20, 20]);
54 | hold on;
55 | semilogx(iec_f, ds_dB, 'g;INMP441 Datasheet plot (approx.);');
56 | semilogx(iec_f, 20*log10(abs(ds_H)), '--c;IIR filter frequency response;');
57 | semilogx(iec_f, (ds_dB + 20*log10(abs(eq_H))), 'b;Adjusted frequency response;', 'linewidth', 3);
58 | legend ('boxoff');
59 | legend ('location', 'northwest');
60 | hold off;
61 |
62 | [sos, gain] = tf2sos(eq_B, eq_A)
--------------------------------------------------------------------------------
/dBlevel/src/math/sph645lm4h-b.m:
--------------------------------------------------------------------------------
1 | %
2 | % SPH0645LM4H-B Equalizer filter design
3 | %
4 | % (c)2019 Ivan Kostoski
5 | %
6 |
7 | clear;
8 | format long;
9 |
10 | % Sampling frequency
11 | Fs = 48000;
12 |
13 | % IEC specified frequencies
14 | iec_f = [10, 12.5, 16, 20, 25, 31.5, 40, 50, 63, 80, ...
15 | 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, ...
16 | 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000, ...
17 | 10000, 12500, 16000, 20000];
18 |
19 | % IEC Class 1 tolerances (top/bottom)
20 | iec_c1_t_dB = [+3.5, +3.0, +2.5, +2.5, +2.5, +2.0, +1.5, +1.5, +1.5, +1.5, ...
21 | +1.5, +1.5, +1.5, +1.5, +1.4, +1.4, +1.4, +1.4, +1.4, +1.4, ...
22 | +1.1, +1.4, +1.4, +1.6, +1.6, +1.6, +1.6, +2.1, +2.1, +2.1, ...
23 | +2.6, +3.0, +3.5, +4.0];
24 | iec_c1_b_dB = [-inf, -inf, -4.5, -2.5, -2.5, -2.0, -1.5, -1.5, -1.5, -1.5, ...
25 | -1.5, -1.5, -1.5, -1.5, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, ...
26 | -1.1, -1.4, -1.4, -1.6, -1.6, -1.6, -1.6, -2.1, -2.6, -3.1, ...
27 | -3.6, -6.0, -17, -inf];
28 |
29 | % IEC Class 2 tolerances (top/bottom)
30 | iec_c2_t_dB = [+5.5, +5.5, +5.5, +3.5, +3.5, +3.5, +2.5, +2.5, +2.5, +2.5, ...
31 | +2.0, +2.0, +2.0, +2.0, +1.9, +1.9, +1.9, +1.9, +1.9, +1.9, ...
32 | +1.4, +1.9, +2.6, +2.6, +3.1, +3.1, +3.6, +4.1, +5.1, +5.6, ...
33 | +6.0, +6.0, +6.0, +6.0];
34 | iec_c2_b_dB = [-inf, -inf, -inf, -3.5, -3.5, -3.5, -2.5, -2.5, -2.5, -2.5, ...
35 | -2.0, -2.0, -2.0, -2.0, -1.9, -1.9, -1.9, -1.9, -1.9, -1.9, ...
36 | -1.4, -1.9, -2.6, -2.6, -3.1, -3.1, -3.6, -4.1, -5.1, -5.6, ...
37 | -inf, -inf, -inf, -inf];
38 |
39 | % Values visually estimated from ICS-43434 datasheet 'Typical Frequency Response' plot
40 | % 10, 12.5, 16, 20, 25, 31.5, 40, 50, 63, 80, ...
41 | ds_dB = [ -inf, -inf, -inf, -7.5, -6.5, -5, -3.6, -2.8, -2, -1.3, ...
42 | -1, -0.7, -0.4, -0.3, -0.1, -0.1, 0, 0, 0, 0, ...
43 | 0, 0, 0, 0, +0.1, +0.1, +0.3, +0.5, +0.8, +0.9, ...
44 | +1, +0.8, +0, -5.5];
45 |
46 | % These value are selected and adjusted for better curve fit
47 | ds_l_f = [ 20, 50, 100, 1000];
48 | ds_l_dB = [-7.5, -2.8, -1, 0];
49 |
50 | % Low frequency filter design
51 | % Convert Hz in rad/s and normalize for Fs
52 | ds_l_wn = ds_l_f.*((2*pi)/Fs);
53 | % Convert plot decibels to magnitude
54 | ds_l_mag = arrayfun(@db2mag, ds_l_dB);
55 | % Estimate coefficients
56 | [ds_l_B, ds_l_A] = invfreqz(ds_l_mag, ds_l_wn, 2, 2);
57 | % Stabilize and normalize the filter
58 | ds_l_Bs = polystab(ds_l_B) * norm(ds_l_B) / norm(polystab(ds_l_B));
59 | ds_l_As = polystab(ds_l_A) * norm(ds_l_A) / norm(polystab(ds_l_A));
60 |
61 | % High frequency filter design
62 | %ds_h_f = [ 1000, 2000, 4000, 8000, 10000, 16000, 20000];
63 | %ds_h_dB = [ 0, 0, +0.3, +0.9, +1, 0, -5.5];
64 | %[ds_h_B, ds_h_A] = invfreqz(arrayfun(@db2mag, ds_h_dB), (ds_h_f.*((2*pi)/Fs)), 4, 3);
65 | %ds_h_Bs = polystab(ds_h_B) * norm(ds_h_B) / norm(polystab(ds_h_B));
66 | %ds_h_As = polystab(ds_h_A) * norm(ds_h_A) / norm(polystab(ds_h_A));
67 |
68 | % Convolve into single 4th order filter
69 | ds_B = ds_l_Bs % conv(ds_l_Bs, ds_h_Bs);
70 | ds_A = ds_l_As % conv(ds_l_As, ds_h_As);
71 | ds_H = freqz(ds_B, ds_A, iec_f, Fs);
72 |
73 | % Equalizer filter, i.e. inverse from estimated transfer filter
74 | % Swap A and B coefficients, and normalize to ds_B(1)
75 | eq_B = ds_A./ds_B(1)
76 | eq_A = ds_B./ds_B(1)
77 | % Check for poles ouside unit circle
78 | roots(eq_B)
79 | roots(eq_A)
80 |
81 | % Add DC blocking filter
82 | [sos, gain] = tf2sos(eq_B, eq_A)
83 | sos = [sos; [1.0, -1.0, 0.0, 1.0, -0.9992, 0]];
84 | [eq_B, eq_A] = sos2tf(sos, gain)
85 | eq_H = freqz(eq_B, eq_A, iec_f, Fs);
86 |
87 | clf;
88 | semilogx(iec_f, ds_dB, 'g;SPH0645LM4H-B Datasheet plot (approx.);');
89 | hold on;
90 | title("SPH0645LM4H-B Frequency response");
91 | grid minor;
92 | xlabel('Frequency (Hz)');
93 | xlim([10, 24000]);
94 | ylabel('Amplitude (dB)');
95 | ylim([-30, 20]);
96 | legend ('boxoff');
97 | legend ('location', 'northwest');
98 | semilogx(iec_f, iec_c1_t_dB, '--r;IEC 61672-1:2013 Class 1 tolerance;');
99 | semilogx(iec_f, iec_c1_b_dB, '--r');
100 | semilogx(iec_f, iec_c2_t_dB, 'r;IEC 61672-1:2013 Class 2 tolerance;');
101 | semilogx(iec_f, iec_c2_b_dB, 'r');
102 | semilogx(iec_f, 20*log10(abs(ds_H)), '--c;IIR filter frequency response;');
103 | semilogx(iec_f, (ds_dB + 20*log10(abs(eq_H))), 'b;Adjusted frequency response;', 'linewidth', 3);
104 | hold off;
105 |
106 | % Convert to Second-Order Sections
107 | [sos, gain] = tf2sos(eq_B, eq_A)
--------------------------------------------------------------------------------
/dBlevel/src/misc/ics-43434-afr.svg:
--------------------------------------------------------------------------------
1 |
2 |
698 |
699 |
--------------------------------------------------------------------------------
/dBlevel/src/sos-iir-filter.h:
--------------------------------------------------------------------------------
1 | /*
2 | * ESP32 Second-Order Sections IIR Filter implementation
3 | *
4 | * (c)2019 Ivan Kostoski
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | #ifndef SOS_IIR_FILTER_H
21 | #define SOS_IIR_FILTER_H
22 |
23 | #include
24 |
25 | struct SOS_Coefficients {
26 | float b1;
27 | float b2;
28 | float a1;
29 | float a2;
30 | };
31 |
32 | struct SOS_Delay_State {
33 | float w0 = 0;
34 | float w1 = 0;
35 | };
36 |
37 | extern "C" {
38 | int sos_filter_f32(float *input, float *output, int len, const SOS_Coefficients &coeffs, SOS_Delay_State &w);
39 | }
40 | __asm__ (
41 | //
42 | // ESP32 implementation of IIR Second-Order Section filter
43 | // Assumes a0 and b0 coefficients are one (1.0)
44 | //
45 | // float* a2 = input;
46 | // float* a3 = output;
47 | // int a4 = len;
48 | // float* a5 = coeffs;
49 | // float* a6 = w;
50 | // float a7 = gain;
51 | //
52 | ".text \n"
53 | ".align 4 \n"
54 | ".global sos_filter_f32 \n"
55 | ".type sos_filter_f32,@function\n"
56 | "sos_filter_f32: \n"
57 | " entry a1, 16 \n"
58 | " lsi f0, a5, 0 \n" // float f0 = coeffs.b1;
59 | " lsi f1, a5, 4 \n" // float f1 = coeffs.b2;
60 | " lsi f2, a5, 8 \n" // float f2 = coeffs.a1;
61 | " lsi f3, a5, 12 \n" // float f3 = coeffs.a2;
62 | " lsi f4, a6, 0 \n" // float f4 = w[0];
63 | " lsi f5, a6, 4 \n" // float f5 = w[1];
64 | " loopnez a4, 1f \n" // for (; len>0; len--) {
65 | " lsip f6, a2, 4 \n" // float f6 = *input++;
66 | " madd.s f6, f2, f4 \n" // f6 += f2 * f4; // coeffs.a1 * w0
67 | " madd.s f6, f3, f5 \n" // f6 += f3 * f5; // coeffs.a2 * w1
68 | " mov.s f7, f6 \n" // f7 = f6; // b0 assumed 1.0
69 | " madd.s f7, f0, f4 \n" // f7 += f0 * f4; // coeffs.b1 * w0
70 | " madd.s f7, f1, f5 \n" // f7 += f1 * f5; // coeffs.b2 * w1 -> result
71 | " ssip f7, a3, 4 \n" // *output++ = f7;
72 | " mov.s f5, f4 \n" // f5 = f4; // w1 = w0
73 | " mov.s f4, f6 \n" // f4 = f6; // w0 = f6
74 | " 1: \n" // }
75 | " ssi f4, a6, 0 \n" // w[0] = f4;
76 | " ssi f5, a6, 4 \n" // w[1] = f5;
77 | " movi.n a2, 0 \n" // return 0;
78 | " retw.n \n"
79 | );
80 |
81 | extern "C" {
82 | float sos_filter_sum_sqr_f32(float *input, float *output, int len, const SOS_Coefficients &coeffs, SOS_Delay_State &w, float gain);
83 | }
84 | __asm__ (
85 | //
86 | // ESP32 implementation of IIR Second-Order section filter with applied gain.
87 | // Assumes a0 and b0 coefficients are one (1.0)
88 | // Returns sum of squares of filtered samples
89 | //
90 | // float* a2 = input;
91 | // float* a3 = output;
92 | // int a4 = len;
93 | // float* a5 = coeffs;
94 | // float* a6 = w;
95 | // float a7 = gain;
96 | //
97 | ".text \n"
98 | ".align 4 \n"
99 | ".global sos_filter_sum_sqr_f32 \n"
100 | ".type sos_filter_sum_sqr_f32,@function \n"
101 | "sos_filter_sum_sqr_f32: \n"
102 | " entry a1, 16 \n"
103 | " lsi f0, a5, 0 \n" // float f0 = coeffs.b1;
104 | " lsi f1, a5, 4 \n" // float f1 = coeffs.b2;
105 | " lsi f2, a5, 8 \n" // float f2 = coeffs.a1;
106 | " lsi f3, a5, 12 \n" // float f3 = coeffs.a2;
107 | " lsi f4, a6, 0 \n" // float f4 = w[0];
108 | " lsi f5, a6, 4 \n" // float f5 = w[1];
109 | " wfr f6, a7 \n" // float f6 = gain;
110 | " const.s f10, 0 \n" // float sum_sqr = 0;
111 | " loopnez a4, 1f \n" // for (; len>0; len--) {
112 | " lsip f7, a2, 4 \n" // float f7 = *input++;
113 | " madd.s f7, f2, f4 \n" // f7 += f2 * f4; // coeffs.a1 * w0
114 | " madd.s f7, f3, f5 \n" // f7 += f3 * f5; // coeffs.a2 * w1;
115 | " mov.s f8, f7 \n" // f8 = f7; // b0 assumed 1.0
116 | " madd.s f8, f0, f4 \n" // f8 += f0 * f4; // coeffs.b1 * w0;
117 | " madd.s f8, f1, f5 \n" // f8 += f1 * f5; // coeffs.b2 * w1;
118 | " mul.s f9, f8, f6 \n" // f9 = f8 * f6; // f8 * gain -> result
119 | " ssip f9, a3, 4 \n" // *output++ = f9;
120 | " mov.s f5, f4 \n" // f5 = f4; // w1 = w0
121 | " mov.s f4, f7 \n" // f4 = f7; // w0 = f7;
122 | " madd.s f10, f9, f9 \n" // f10 += f9 * f9; // sum_sqr += f9 * f9;
123 | " 1: \n" // }
124 | " ssi f4, a6, 0 \n" // w[0] = f4;
125 | " ssi f5, a6, 4 \n" // w[1] = f5;
126 | " rfr a2, f10 \n" // return sum_sqr;
127 | " retw.n \n" //
128 | );
129 |
130 |
131 | /**
132 | * Envelops above asm functions into C++ class
133 | */
134 | struct SOS_IIR_Filter {
135 |
136 | const int num_sos;
137 | const float gain;
138 | SOS_Coefficients* sos = NULL;
139 | SOS_Delay_State* w = NULL;
140 |
141 | // Dynamic constructor
142 | SOS_IIR_Filter(size_t num_sos, const float gain, const SOS_Coefficients _sos[] = NULL): num_sos(num_sos), gain(gain) {
143 | if (num_sos > 0) {
144 | sos = new SOS_Coefficients[num_sos];
145 | if ((sos != NULL) && (_sos != NULL)) memcpy(sos, _sos, num_sos * sizeof(SOS_Coefficients));
146 | w = new SOS_Delay_State[num_sos]();
147 | }
148 | };
149 |
150 | // Template constructor for const filter declaration
151 | template
152 | SOS_IIR_Filter(const float gain, const SOS_Coefficients (&sos)[Array_Size]): SOS_IIR_Filter(Array_Size, gain, sos) {};
153 |
154 | /**
155 | * Apply defined IIR Filter to input array of floats, write filtered values to output,
156 | * and return sum of squares of all filtered values
157 | */
158 | inline float filter(float* input, float* output, size_t len) {
159 | if ((num_sos < 1) || (sos == NULL) || (w == NULL)) return 0;
160 | float* source = input;
161 | // Apply all but last Second-Order-Section
162 | for(int i=0; i<(num_sos-1); i++) {
163 | sos_filter_f32(source, output, len, sos[i], w[i]);
164 | source = output;
165 | }
166 | // Apply last SOS with gain and return the sum of squares of all samples
167 | return sos_filter_sum_sqr_f32(source, output, len, sos[num_sos-1], w[num_sos-1], gain);
168 | }
169 |
170 | ~SOS_IIR_Filter() {
171 | if (w != NULL) delete[] w;
172 | if (sos != NULL) delete[] sos;
173 | }
174 |
175 | };
176 |
177 | //
178 | // For testing only
179 | //
180 | struct No_IIR_Filter {
181 | const int num_sos = 0;
182 | const float gain = 1.0;
183 |
184 | No_IIR_Filter() {};
185 |
186 | inline float filter(float* input, float* output, size_t len) {
187 | float sum_sqr = 0;
188 | float s;
189 | for(int i=0; i