Raspberry pi サウンドセンサーによる音量測定

Raspberry Pi 作品集
サウンドセンサー LM386搭載 9534
騒音計の作成
 
音量測定の目的:長年使用している Raspberry pi 4 を冷却しているファンの音が大きくなったので、交換を機に新品ファンとの音量差を測定してみることにした。
音量測定の結果:使い物にならず、失敗。
 
接続図
 
以下、サウンドセンサー「9534」を使った「騒音計」を作成した記録。
 
 

 

スポンサー リンク

 

 
 
 
 
 
1. 使用中の冷却ファン
 
稼働中のWebサーバー(Raspberry Pi 4 x 4台)。
Reverse Proxy と WebサーバーのすべてをSSD化
 
全てのラズパイに、【Miuzei】の「ケース+ 冷却ファン+ ヒートシンク+ 3A Micro USB-C電源アダプター」がパッケージになった商品¥1,970 を装着。
【Miuzei】の「ケース+ 冷却ファン+ ヒートシンク+ 3A Micro USB-C電源アダプター」がパッケージになった商品¥1,970
 
ファンは「YCCFAN」社製で、2019年11月より365日24時間年中無休で稼働しており、既に5年以上も経過している。
「YCCFAN」社製のファンで、2019年11月より365日24時間稼働しているので、5年以上経過している
 
交換用に購入したファンは、「easycargo」社製の 4個セット¥1,299。
交換用に購入したファン(「easycargo」社製の 4個セット¥1,299)
 
交換する前に、現在稼働中のファンに「‎KURE‎5-56」を注入してみると【音が消えた】ので、交換せずに継続して使用中。
 
 
 
2. 音量測定に使用した部品
 
①.冷却用のファンが不要な「Raspberry Pi 3 Model B」を使用。
冷却用のファンが不要な「Raspberry Pi 3 Model B」を使用。
 
②.サウンドセンサー モジュール LM386搭載 9534
(株式会社 ケイエスワイ ¥660)。
サウンドセンサー モジュール LM386搭載 9534
 
■主な仕様
・メーカー / 販売元 Waveshare
・製品型番 9534
・動作電圧 3.3V〜5.3V
・インターフェース VCC↔3.3V〜5.3V
・GND↔電源グランド
・AOUT↔MCU.IO(アナログ出力)
・DOUT↔MCU.IO(デジタル出力)
・マイク感度 52dB
・周波数範囲 50Hz~20KHz
・寸法 34.42mm×18.67mm
・取付穴サイズ 2.0mm
 
③.10bit 8ch ADコンバーター MCP3008-I/P
(株式会社秋月電子通商 ¥360)。
10bit 8ch ADコンバーター MCP3008-I/P
 
■主な仕様
・電源電圧min.:2.7V
・電源電圧max.:5.5V
・入力数:8
・分解能:10bit
・サンプリングレート:200ksps
・入力タイプ:シングルエンド・疑似差動
・インターフェイス:SPI
・変換方式:逐次比較型(SAR)
・微分非直線性誤差DNL:±1LSB
・積分非直線性誤差INL:±1LSB
・動作温度min.:-40℃
・動作温度max.:85℃
・実装タイプ:スルーホール
・パッケージ:DIP16
・パッケージタイプ:DIP16
 
Digital data output(デジタル出力)があるが、しきい値を超える音量の検知用なので、音量の測定にはアナログ出力を使用する。
 
 
 
3. 配線
 
①.サウンドセンサーの接続:
 ●VCC -> 3.3V
 ●GND -> GND
 ●AOUT -> MCP3008 の CH0 (アナログ入力ピン)
 
②.MCP3008 と Raspberry Pi の接続:
 ●VDD -> 3.3V
 ●VREF -> 3.3V
 ●AGND -> GND
 ●DGND -> GND
 ●CLK -> GPIO 11
 ●DOUT -> GPIO 9
 ●DIN -> GPIO 10
 ●CS -> GPIO 8
 
■GPIO ピンと SPI バスの対応:
Raspberry Pi で SPI 通信を使用する際は、以下の GPIO ピンが使用される
プログラム内では spi.open(0, 0) によって、SPI バス 0 の デバイス 0 を指定する。
GPIO ピンと SPI バスの対応
 
Raspberry Pi のGPIOピン配置確認コマンド。
pinout
 
Raspberry Pi のGPIOピン配置確認コマンド
 
Raspberry Pi (40ピン) と MCP3008 の配線。
Raspberry Pi (40ピン) と MCP3008 の配線
 
接続図。

接続図
 
 
 
4. 事前準備
 
spidev ライブラリのインストール。
sudo apt update
sudo apt install python3-pip
pip3 install spidev
 
SPI の有効化。
sudo raspi-config
 
Raspberry Pi Configuration を開き、3 Interfaces Options を選択する。
Raspberry Pi Configuration を開き、Interfaces Options を選択する
 
I4 SPI を選択する。
I4 SPI を選択する
 
SPI を Enabled に切り替え、OK で設定を保存する。
SPI を Enabled に切り替え、OK で設定を保存する
 
SPI が有効になっているか確認。
ls /dev/spidev*
 
正常に有効化されている場合、以下のような出力が得られる。
SPI が有効になっているか確認
/dev/spidev0.0
/dev/spidev0.1
 
 
 
5. Python プログラムと実行結果
 
Python プログラム。
import spidev
import time

# SPI インターフェイスの設定
spi = spidev.SpiDev()
spi.open(0, 0)  # SPIバスとデバイスを指定
spi.max_speed_hz = 1350000

# MCP3008 からデータを取得
def read_adc(channel):
    if channel < 0 or channel > 7:
        raise ValueError("チャンネルは 0~7 の間で指定してください")
    adc = spi.xfer2([1, (8 + channel) << 4, 0])
    data = ((adc[1] & 3) << 8) + adc[2]
    return data

# 音量測定ループ
try:
    print("音量測定を開始します。Ctrl+C で終了してください。")
    while True:
        # サウンドセンサーのデータを取得
        sound_level = read_adc(0)

        # 音量を出力 (0~1023 の範囲)
        print(f"音量レベル: {sound_level}")

        # 短い間隔を置く
        time.sleep(0.1)
except KeyboardInterrupt:
    print("終了します。")
finally:
    spi.close()
■説明
1. spidev ライブラリ:
・Raspberry Pi の SPI インターフェイスを操作します。
・MCP3008 ADC からデータを取得します。
2. 音量の測定:
・サウンドセンサーのアナログ出力を 0~1023 の範囲で取得します。
・大きい値ほど音量が大きいことを示します。
3. リアルタイム更新:
・データを約0.1秒間隔で取得・表示します。
 
静かな環境でプログラムを実行してみる・・・
表示される測定値が変動し安定しない。 → 次の対策を講じる。
 

■改善策①:グランド接続を最適化:
サウンドセンサーと Raspberry Pi のグランドを共有させる。

■改善策②:コンデンサの追加:
サウンドセンサーの電源ピン(VCC と GND)に 0.1 µF と 10 µF のコンデンサを並列に接続し、ノイズを低減する。

■改善策③:シールドケーブル:
センサーから Raspberry Pi までの配線に、シールドケーブルの代替としてアルミホイルを巻き付ける。

静かな環境でプログラムを実行してみると、表示される測定値が変動し安定しないので、
次の対策を実施する
 
対策の結果 → 測定値は安定する。
対策の結果:測定値が安定する
 
 
 
6. 音量レベルの変化が小さすぎる
 
静かな環境での音量レベルが【530】前後の値になり、
騒音を加えても音量レベルの変化が小さすぎて使い物にならない。
 
以下、この原因と対策に関する各種実験を行ってみる。
 
①.音量をパーセントに変換してみる
import spidev
import time
import RPi.GPIO as GPIO

# SPIの設定
spi = spidev.SpiDev()
spi.open(0, 0)  # SPIバス0、デバイス0
spi.max_speed_hz = 1350000

# MCP3008からアナログ値を読み取る関数
def read_adc(channel):
    if channel < 0 or channel > 7:
        return -1
    adc = spi.xfer2([1, (8 + channel) << 4, 0])
    data = ((adc[1] & 3) << 8) + adc[2]
    return data

# サウンドセンサーのピンの設定
SENSOR_CHANNEL = 0  # MCP3008のCH0に接続

# メイン処理
try:
    while True:
        # アナログ値の読み取り(0-1023の範囲)
        sound_level = read_adc(SENSOR_CHANNEL)
        # 音量をパーセントに変換(簡易的なスケーリング)
        sound_percentage = (sound_level / 1023.0) * 100
        print(f"Sound Level: {sound_level} (Approx. {sound_percentage:.2f}%)")
        time.sleep(0.5)  # 0.5秒ごとに測定

except KeyboardInterrupt:
    print("\nMeasurement stopped by user")
finally:
    spi.close()  # SPI接続を閉じる
    GPIO.cleanup()  # GPIO設定をクリーンアップ
音量をパーセントに変換して表示した例。
音量をパーセントに変換して表示した例
 
静かな環境で測定を開始し、その後(ピンク色の枠以降)ラジオの音をサウンドセンサー近づけて測定した。
 ・ 静かな環境での音量レベルと音量のパーセントが、アナログ値のほぼ中間値を示し、
 ・ ラジオの音での音量レベルと音量のパーセントが、静かな環境での値より小さくなったりする
 
 
②.データをスケーリングして変化を強調してみる
# データのスケーリング
def scale_data(raw_value, min_value=0, max_value=1023, new_min=0, new_max=100):
    """データを指定された範囲にスケーリング"""
    return (raw_value - min_value) * (new_max - new_min) / (max_value - min_value) + new_min

# 測定ループ内
raw_sound_level = read_adc(0)
scaled_sound_level = scale_data(raw_sound_level, 0, 1023, 0, 100)  # 0~100 の範囲にスケーリング
print(f"スケーリング後の音量レベル: {scaled_sound_level:.2f}")
 
③.音声信号のピークを検出して、最大値と最小値を抽出してみる
# ピーク検出
def detect_peak(values):
    """値のリストから最大値と最小値を検出"""
    return max(values), min(values)
 
②にスケーリングと ③のピーク検出を同時に実装した Python プログラム。
import spidev
import time

# SPI インターフェイスの設定
spi = spidev.SpiDev()
spi.open(0, 0)  # SPIバスとデバイスを指定
spi.max_speed_hz = 1350000

# MCP3008 からデータを取得
def read_adc(channel):
    if channel < 0 or channel > 7:
        raise ValueError("チャンネルは 0~7 の間で指定してください")
    adc = spi.xfer2([1, (8 + channel) << 4, 0])
    data = ((adc[1] & 3) << 8) + adc[2]
    return data

# データのスケーリング
def scale_data(raw_value, min_value=0, max_value=1023, new_min=0, new_max=100):
    """データを指定された範囲にスケーリング"""
    return (raw_value - min_value) * (new_max - new_min) / (max_value - min_value) + new_min

# ピーク検出
def detect_peak(values):
    """値のリストから最大値と最小値を検出"""
    return max(values), min(values)

# 音量測定ループ
try:
    print("音量測定を開始します。Ctrl+C で終了してください。")

    # サンプルバッファを作成
    sample_buffer = []
    buffer_size = 10  # サンプル数

    while True:
        # サウンドセンサーのデータを取得
        raw_sound_level = read_adc(0)

        # スケーリング
        scaled_sound_level = scale_data(raw_sound_level, 0, 1023, 0, 100)

        # バッファに追加
        sample_buffer.append(scaled_sound_level)

        if len(sample_buffer) >= buffer_size:
            # ピーク検出
            max_level, min_level = detect_peak(sample_buffer)
            print(f"【ピーク検出】 最大: {max_level:.2f}, 最小: {min_level:.2f}")

            # バッファをリセットして再収集を開始
            sample_buffer = []
        else:
            # バッファ収集中の音量を表示
            print(f"音量レベル: {scaled_sound_level:.2f} (バッファ収集中)")

        # 短い間隔を置く
        time.sleep(0.1)

except KeyboardInterrupt:
    print("終了します。")
finally:
    spi.close()
ラジオの音をサウンドセンサー近づけて「音声信号のピークを検出」した、最大値と最小値の変化。
スケーリングとピーク検出対策を行った結果
 
音声信号の最大値と最小値がスケーリングされて表示されるようになったが、【騒音】が大きいか/小さいかを簡単に判定できるような値にはならない。
 
 
④.静かな環境でキャリブレーションを行い「基準値を設定」し、
その後「基準値との差」を測定して音量レベルを表示する。
import spidev
import time

# SPI インターフェイスの設定
spi = spidev.SpiDev()
spi.open(0, 0)  # SPIバスとデバイスを指定
spi.max_speed_hz = 1350000

# MCP3008 からデータを取得
def read_adc(channel):
    if channel < 0 or channel > 7:
        raise ValueError("チャンネルは 0~7 の間で指定してください")
    adc = spi.xfer2([1, (8 + channel) << 4, 0])
    data = ((adc[1] & 3) << 8) + adc[2]
    return data

# データのスケーリング
def scale_data(raw_value, min_value=0, max_value=1023, new_min=0, new_max=100):
    """データを指定された範囲にスケーリング"""
    return (raw_value - min_value) * (new_max - new_min) / (max_value - min_value) + new_min

# キャリブレーション
def calibrate(channel, duration=5):
    """静かな環境で基準値を計測"""
    print(f"キャリブレーションを開始します。{duration}秒間、静かな環境を保ってください...")
    start_time = time.time()
    samples = []

    while time.time() - start_time < duration:
        raw_value = read_adc(channel)
        samples.append(raw_value)
        time.sleep(0.1)  # サンプリング間隔

    baseline = sum(samples) / len(samples)
    print(f"キャリブレーション完了: 基準値 = {baseline:.2f}")
    return baseline

# 音量測定ループ
try:
    print("音量測定を開始する前に、キャリブレーションを行います。")
    input("キャリブレーションを開始するには Enter を押してください...")

    # キャリブレーション
    channel = 0  # サウンドセンサーが接続されているチャンネル
    baseline_value = calibrate(channel)

    print("キャリブレーション完了。音量測定を開始します。Ctrl+C で終了してください。")

    while True:
        # サウンドセンサーのデータを取得
        raw_sound_level = read_adc(channel)

        # スケーリング
        scaled_sound_level = scale_data(raw_sound_level, 0, 1023, 0, 100)

        # 基準値との差を計算
        relative_sound_level = scaled_sound_level - scale_data(baseline_value, 0, 1023, 0, 100)

        # 結果を表示
        print(f"基準値との差: {relative_sound_level:.2f}, 音量レベル (スケール): {scaled_sound_level:.2f}")

        time.sleep(0.1)

except KeyboardInterrupt:
    print("終了します。")
finally:
    spi.close()
「基準値との差」を測定して音量レベルを表示した結果。
「基準値との差」を測定して音量レベルを表示した結果
 
ラジオの音をサウンドセンサーに近づけて測定してみるも、「基準値との差」がマイナスになったり、【騒音】が大きいと判定できるような値は表示されない。
 
 
⑤.差分値を拡大するスケーリング(ゲイン)を適用
    gain = 5  # 差分を拡大する倍率

        # 差分を拡大
        amplified_relative_level = relative_sound_level * gain

        # 結果を表示
        print(f"基準値との差 (拡大): {amplified_relative_level:.2f}, 音量レベル (スケール): {scaled_sound_level:.2f}")
差分値を拡大して表示した結果。
差分値を拡大して表示した結果
 
「基準値との差」がマイナスになるケースがほとんどで、【騒音】が大きいと判定できるような値は一瞬しか表示されない。
 
 
 
7. キャリブレーションによる音量測定
 
最後に、たどり着いた Python プログラム。
 
基準値だけでなく、音量測定もキャリブレーション方式にする。
import spidev
import time

# SPI インターフェイスの設定
spi = spidev.SpiDev()
spi.open(0, 0)  # SPIバスとデバイスを指定
spi.max_speed_hz = 1350000

# MCP3008 からデータを取得
def read_adc(channel):
    if channel < 0 or channel > 7:
        raise ValueError("チャンネルは 0~7 の間で指定してください")
    adc = spi.xfer2([1, (8 + channel) << 4, 0])
    data = ((adc[1] & 3) << 8) + adc[2]
    return data

# データのスケーリング
def scale_data(raw_value, min_value=0, max_value=1023, new_min=0, new_max=100):
    """データを指定された範囲にスケーリング"""
    return (raw_value - min_value) * (new_max - new_min) / (max_value - min_value) + new_min

# キャリブレーション
def calibrate(channel, duration=5):

    print(f"{duration}秒間キャリブレーションを行います。")
    start_time = time.time()
    samples = []

    while time.time() - start_time < duration:
        raw_value = read_adc(channel)
        samples.append(raw_value)
        time.sleep(0.1)  # サンプリング間隔

    baseline = sum(samples) / len(samples)
    print(f"キャリブレーション完了: 測定値 = {baseline:.2f}")
    return baseline

# 音量測定ループ
try:
    print("音量測定を開始する前に、基準値測定を行います。静かな環境を保ってください...")
    input("開始するには Enter を押してください...\n")

    # キャリブレーション
    channel = 0  # サウンドセンサーが接続されているチャンネル
    baseline_value = calibrate(channel)

    print("基準値測定の完了。\n")

    while True:
        print("音量測定を行います。Ctrl+C で終了します。")
        input("開始するには Enter を押してください...\n")

        # サウンドセンサーのデータを取得
        channel = 0  # サウンドセンサーが接続されているチャンネル
        raw_sound_level = calibrate(channel)

        # スケーリング
        scaled_sound_level = scale_data(raw_sound_level, 0, 1023, 0, 100)

        # 基準値との差を計算
        relative_sound_level = scaled_sound_level - scale_data(baseline_value, 0, 1023, 0, 100)

        # 結果を表示
        print(f"基準値との差: {relative_sound_level:.2f}, 音量レベル (スケール): {scaled_sound_level:.2f}\n")

except KeyboardInterrupt:
    print("終了します。")
finally:
    spi.close()
 
音量測定もキャリブレーション方式にして、ラジオの音を測定した結果。
音量測定もキャリブレーション方式にして実行した結果
 
音量測定もキャリブレーション方式にすることで、「測定」という行為が明確になるが、測定値は実用に耐えない。
 
 
以上、サウンドセンサー「9534」を使った「騒音計」の作成に挑戦した記録。
 
 
以上。
(2025.05.01)
 

 

スポンサー リンク

 

             

 

 

 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

This site uses Akismet to reduce spam. Learn how your comment data is processed.