การอ่านค่าสัญญาณเสียงแอนะล็อกด้วย ESP32 และแสดงผลด้วย Python#
- การวัดสัญญาณแอนะล็อกจากโมดูลไมโครโฟนเสียง
- โค้ด Arduino Sketch สำหรับ ESP32
- โค้ด Python สำหรับรับข้อมูลและวาดกราฟด้วยคอมพิวเตอร์
▷ การวัดสัญญาณแอนะล็อกจากโมดูลไมโครโฟนเสียง#
โมดูลเซนเซอร์เสียงมีหลายชนิด สามารถจำแนกได้ตามชนิดของสัญญาณเอาต์พุต เช่น มีเอาต์พุตเป็นสัญญาณแอนะล็อก หรือ เชื่อมต่อแบบดิจิทัลจากไมโครโฟนแบบ MEMS เพื่อรับข้อมูลตามรูปแบบของ I2S
บทความที่เกี่ยวข้องคือ "การใช้งานโมดูล MAX4466 Sound Sensor" ซึ่งกล่าวถึง โมดูลไมโครโฟนเสียงแบบ Electret Microphone และมีวงจรขยายเสียงที่ใช้ไอซี MAX4466 ซึ่งให้เอาต์พุตเป็นสัญญาณแอนะล็อก สัญญาณเสียงที่ถูกแปลงเป็นสัญญาณไฟฟ้าแบบแอนะล็อก จะถูกแปลงเป็นข้อมูลแบบดิจิทัลได้ โดยการใช้วงจร ADC (Analog-to-Digital Converter) อาจเป็นไอซีภายนอก หรือ วงจรภายในชิปไมโครคอนโทรลเลอร์ เช่น ESP32
ในบทความนี้ สาธิตการเขียนโค้ด Arduino-ESP32 (v3.0.0) เพื่อใช้งานวงจรภายใน ADC จำนวน 1 ช่องสัญญาณ และส่งข้อมูลที่ได้ไปยังคอมพิวเตอร์ผู้ใช้ ผ่านทาง Serial Port และมีการเขียนโค้ด Python เพื่อรับค่าและนำไปประมวลผลเชิงตัวเลข เช่น การวิเคราะห์สัญญาณแบบฟูเรียร์ ด้วย FFT (Fast-Fourier Transform)
▷ โค้ด Arduino Sketch สำหรับ ESP32#
โค้ดต่อไปนี้ สาธิตการอ่านค่าจาก ADC ที่ขา GPIO34 โดยใช้วงจร
Hardware Timer ทำหน้าที่กำหนดอัตราการอ่านค่าจาก ADC
โดยเรียกใช้ฟังก์ชัน timer_callback()
ในโค้ดตัวอย่าง ได้กำหนดอัตราการชักตัวอย่างจากสัญญาณแอนะล็อกไว้ที่ความเร็ว Fs = 10000 ตัวอย่างต่อวินาที
การอ่านข้อมูลจากอินพุตและเก็บข้อมูลลงในอาร์เรย์ samples[]
จะเริ่มต้นหลังจากได้รับข้อความหนึ่งบรรทัด (จบท้ายด้วย \n
)
จากพอร์ต Serial และทำต่อเนื่องจนได้ข้อมูลครบ N = 1024 (เลือกจำนวนค่าตัวเลขให้เหมาะกับการนำไปคำนวณด้วย FFT)
เมื่อได้ข้อมูลครบแล้ว ในขั้นตอนถัดไป ข้อมูลแต่ละตัวในอาร์เรย์ จะถูกส่งเป็นข้อความทีละบรรทัดผ่าน Serial ด้วยความเร็ว Baudrate = 921600 จนครบ แล้วจึงเริ่มการอ่านค่า ADC และเก็บข้อมูลลงในอาร์เรย์ในรอบถัดไป
const int ADC_PIN = 34; // ADC1_CH6 / GPIO34 pin
const int LED_PIN = 22; // LED pin
uint32_t sample_index = 0;
bool sampling = false;
QueueHandle_t adc_queue;
const uint32_t Fs = 10000; // Sampling frequency (Hz)
const uint32_t N = 1024; // Number of samples
uint32_t sample_count = 0;
uint16_t samples[N];
// Callback function of the hardware timer.
void IRAM_ATTR timer_callback() {
if (!sampling)
return;
digitalWrite( LED_PIN, HIGH );
// Read the ADC input channel.
uint16_t value = (uint16_t)analogReadMilliVolts( ADC_PIN );
samples[sample_count++] = value;
if ( sample_count == N ) {
sampling = false; // Pause the ADC reading.
sample_count = 0; // Reset the sample count.
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR( adc_queue, &sampling,
&xHigherPriorityTaskWoken );
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
digitalWrite( LED_PIN, LOW );
}
// Initialize the ADC input channel.
void initADC() {
// Set ADC resolution to 12 bits
analogSetWidth( 12 );
// Set attenuation level to 11 dB.
analogSetPinAttenuation( ADC_PIN, ADC_11db );
}
// Initialize the hardware timer.
void initTimer( uint32_t hw_timer_unit=0 ) {
static hw_timer_t *timer = NULL;
timer = timerBegin( 1000000UL ); // 1MHz (1us tick)
timerWrite(timer, 0);
// Attach the callback function (ISR) to the timer
timerAttachInterrupt( timer, &timer_callback );
timerAlarm(timer, (1000000UL/Fs) /*interval*/,
true /*reload*/, 0 /*reload value*/);
timerRestart(timer);
}
void setup() {
Serial.begin(921600);
Serial.setTxBufferSize(1024);
Serial.flush();
pinMode( LED_PIN, OUTPUT );
digitalWrite( LED_PIN, LOW );
adc_queue = xQueueCreate(1, sizeof(uint32_t));
initADC(); // Initialize the ADC in one-shot mode.
initTimer(); // Initialize the hardware timer.
}
void loop() {
uint32_t flag;
while (Serial.available()) {
if (Serial.read() == '\n') {
sampling = true; // Start the input sampling.
}
}
if (xQueueReceive(adc_queue, &flag, pdMS_TO_TICKS(5))) {
Serial.println("+++"); // Used as the frame-start symbol
Serial.flush();
for ( uint32_t i=0; i < N; i++ ) {
// Send the sample as a string to serial.
Serial.printf("%u\n", samples[i] );
}
Serial.flush();
sampling = true;
}
}
รูป: การต่อวงจรทดลองโดยใช้บอร์ด ESP32 และโมดูล MAX4466 (ใช้แรงดันไฟเลี้ยง +3.3V)
▷ โค้ด Python สำหรับรับข้อมูลและวาดกราฟด้วยคอมพิวเตอร์#
ตัวอย่างถัดไปเป็นโค้ด Python สาธิตการรับข้อความจากพอร์ต Serial โดยใช้แพ็คเกจ PySerial แล้วนำมาแสดงผลในรูปของกราฟแบบ Time Series และแสดงสเปกตรัมความถี่สำหรับขนาด (Magnitude Spectrum) เมื่อนำไปผ่านการแปลงข้อมูลด้วยวิธี FFT โดยใช้แพ็คเกจ NumPy / Matplotlib
จำนวนข้อมูลที่อ่านจากพอร์ต Serial ในแต่ละรอบ จะเท่ากับขนาดของอาร์เรย์ samples[]
ในโค้ด Arduino Sketch
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import signal
import serial
# Initialize serial port
serial_port = 'COM13' # 'COMx' (Windows) or '/dev/ttyUSBx' (Linux)
ser = serial.Serial(serial_port, baudrate=921600, timeout=0.04)
ser.write(b'\r\n') # Send a newline char to start the sampling
ser.flush()
N_SAMPLES = 1024 # Number of samples
SAMPLE_RATE = 10000 # Set sampling rate.
# Aliases
N = N_SAMPLES
Fs = SAMPLE_RATE
# Initialize plots
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 6))
# Plot for samples
ts = np.arange(0, N/Fs, 1/Fs)
data1, = ax1.plot(ts, np.zeros(N))
ax1.set_title('Samples')
ax1.set_xlabel('Time [s]')
ax1.set_ylabel('Amplitude')
ax1.set_xlim(ts[0], ts[-1])
amplitude_limits = [0, 3300]
ax1.set_ylim(amplitude_limits)
ax1.grid(False)
# Plot for FFT-based frequency spectrum
freq_steps = np.fft.fftfreq(N, d=1/Fs)
data2, = ax2.plot(freq_steps[:N//2], np.zeros(N//2) )
ax2.set_title("FFT-based Frequency Spectrum")
ax2.set_xlabel(f'Frequency (Hz): Fs={int(SAMPLE_RATE/1e3)} kHz')
ax2.set_ylabel('Magnitude (dB)')
ax2.set_xlim( 0, Fs//2)
magnitude_limits = [-80, 60] # in dB
ax2.set_ylim(magnitude_limits)
ax2.grid(True)
# Adjust vertical spacing between subplots
plt.subplots_adjust(hspace=0.4)
def read_data_from_serial(n):
samples = []
err_count = 0
while len(samples) < n:
try:
line = ser.readline()
except ValueError:
print( "No data received from serial..")
return None
try:
# Read a line from serial and convert to float
line = line.decode().strip()
if line[0:3] == "+++":
samples.clear()
continue
value = float(line)
if value > 3300:
value = 3300
elif value < 0:
value = 0
samples.append(value)
except ValueError as ex:
err_count += 1
#print(ex, err_count)
if err_count >= 3:
return None
return samples
def read_test_data(n):
f0 = 500 # The fundamental frequency
A = 1000 # The amplitude
noise = 0.01*np.random.uniform(-1,1,size=n)
data = A*( 1.67 + np.sin( 2*np.pi*f0*np.arange(n)/Fs)
+ noise ).astype(np.float32)
return data
saved_data = np.zeros(N)
# Function to update the plot
def update_plot(frame):
global data1, data2
# Use generated data.
#data = read_test_data(N)
# Read the data from the serial port
data = read_data_from_serial(N)
if data is None:
data = saved_data
# Apply a window function (Hanning window)
window_coeffs = np.hanning(N)
windowed_data = data * window_coeffs
# Compute FFT
spectrum = np.fft.fft(windowed_data)/N
# Update time-domain plot
data1.set_ydata(data)
# Update frequency spectrum in logarithmic scale (dB)
spectrum_mag = 20*np.log10( np.abs(spectrum[:N//2])+1e-6 )
data2.set_ydata( spectrum_mag[:N//2] )
return data1, data2
# Function to handle Ctrl+C (KeyboardInterrupt)
def signal_handler(signal, frame):
print("Ctrl+C detected. Terminating the program.")
plt.close('all') # Close all matplotlib windows
ser.close() # Close serial port
exit(0)
# Register the signal handler
signal.signal(signal.SIGINT, signal_handler)
# Create animation (set the update interval in msec)
ani = FuncAnimation( fig, update_plot, blit=True,
interval=25, save_count=1 )
# Show the plot in non-blocking mode
plt.show()
หากทดสอบการทำงานของระบบ โดยใช้สัญญาณเสียงความถี่คงที่ เช่น Sine Wave 1kHz จะได้ผลในลักษณะต่อไปนี้
รูป: ตัวอย่างสเปกตรัมความถี่ของสัญญาณเสียง เมื่อทดสอบกับสัญญาณที่มีความถี่ 1kHz
รูป: ตัวอย่างสเปกตรัมความถี่ของสัญญาณเสียง เมื่อทดสอบกับสัญญาณที่มีความถี่ 440Hz
▷ กล่าวสรุป#
บทความนี้นำเสนอ ตัวอย่างการเขียนโค้ด Arduino-ESP32 เพื่ออ่านค่าจากโมดูลเซนเซอร์เสียงแบบแอนะล็อก และส่งข้อมูลเข้าคอมพิวเตอร์ผู้ใช้ผ่านทางพอร์ต Serial และเขียนโค้ด Python เพื่อรับข้อมูลจากบอร์ด ESP32 นำมาแสดงรูปกราฟ
บทความที่เกี่ยวข้อง
- การใช้งานโมดูลไมโครโฟนเสียง MAX4466 Sound Sensor
- การอ่านค่าสัญญาณเสียงด้วย Python และการแสดงรูปสเปกตรัมเชิงความถี่
- การใช้งาน ESP32 เพื่อประมวลผลข้อมูลด้วย FFT (Fast-Fourier Transform)
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Created: 2023-11-17 | Last Updated: 2023-11-21