chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

MEMSマイクからPDM信号を取得するプログラムをno_stdのRustで書いてデータを抜いた(苦笑)

課題:MEMSマイクのPDM出力を抜き取ってΣ、フィルタで元の波形に戻るのかを確認したい
取り組み:MicroPythonの性能ではMEMSマイクのサンプリングレート(1MHz以上)に間に合わないので、(C,TinyGo,Rust等の)コンパイル型言語で、かつ、RTOSによるタスクスイッチしない素のプログラムを作ってデータを抜く
結論:no_stdの組み込みRustでどうにかこうにかデータは抜けた(と思う)
詳細:

いろいろあったが、MEMSマイクに1MHz相当のクロックを与えて、PDM出力を取得するプログラムを書いて動かした。
どうもMEMSマイクは出力が安定するまでクロックを与え続ける必要があるようで、このサンプルのように、クロックを送ったり止めたり、さらには、クロックの周波数を上げたり下げたりするのはかなりまずい実装と思える (低速モードとか、高速モードとかに切り替えていることになるので)。データサンプリングの前にクロックを送ってるのはMEMSマイクが安定して動作するまでの待ちで、データダンプ中もクロックを送ってるのは、MEMSマイクを停止させないため。(変数名もledだが、これはblinkサンプルをベースに書き直したため。本当はclkとすべき)
no_stdでどうにか動かしたけど、理想的には、RTOSを使ってクロック生成タスクと、データ引き抜きタスクで動かすべきなのではないか。RP2040のPIOとの組み合わせも良いとは思う。

#![no_std]
#![no_main]

use esp_backtrace as _;
use esp_println::println;
use hal::{peripherals::Peripherals, prelude::*};
use hal::IO;

#[entry]
fn main() -> ! {
    let peripherals = Peripherals::take();

    println!("Blink Test");

    let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
    let mut led = io.pins.gpio8.into_push_pull_output();
    let signal = io.pins.gpio9.into_pull_down_input();
    let mut idx;
    let mut ary: [u8; 8000] = [0; 8000];

    led.set_high().unwrap();

    loop {

        for _ in 0..= 10_000 {
           led.set_high().unwrap();
           led.set_low().unwrap();
        }
        for _ in 0..= 10_000_000 {
           led.set_high().unwrap();
           if signal.is_low().unwrap() {
                      break;
           }               
           led.set_low().unwrap();
        }
        idx = 0;
        loop {
           led.set_high().unwrap();
           if signal.is_high().unwrap() {
              ary[idx] = 1;
           }else{
              ary[idx] = 0;
           }
           led.set_low().unwrap();
           idx += 1;
           if idx == 7999 {
                break;
           }
        }
        println!("-------------------------------");
        for val in ary {
           led.set_high().unwrap();
           println!("{}", val);
           led.set_low().unwrap();
        }
        println!("-------------------------------");            
    }
}

ビルドと書き込みの手順は以下*1

cargo build --release
espflash flash  target/riscv32imc-unknown-none-elf/release/blink
espflash monitor | tee > near500Hz

espflash monitorを実行後、Ctrl+Rを打ち込むことで、マイコンに書き込んだプログラムを再実行させる。標準出力されるサンプリング結果をファイルに落とすため、teeコマンドを使って標準出力とファイル出力に枝分かれさせている。
メモ:データ抜き取り中のCLKは、820ns / 1.22MHz
■グラフ化
今回のテストではスマフォの波形生成アプリで500Hzを発生させてMEMSマイクで音を拾いPDM信号に変換した。テストではスマフォとマイクをほぼ密着した状態なので、ノイズ等は入っていない条件下でのテスト

PDMはパルス密度変調(Pulse-density modulation)であり、1が多いと波形の山に近づき、0が多いと波形の谷(信号がない)に近づくという程度にしか理解していません。中間はどうやって表すか?というと、101010...が続くと中央値であると(パルスの密度が50%であり、PDM信号をローパスフィルターに通すと中央値になる)。移動平均を何度か繰り返して01の繰り返しがどのように変化するかを確認した。結果は以下の図

基本的に移動平均を複数回繰り返して波をなだらかに加工しているだけ*2だが、最後はサイン波が得られた。本当に500Hz相当なのか?はサンプリング数とMEMSマイクに送ったCLKの周波数から計算が必要。
(サンプリング数を基準にすると、247 ~ 7012 の間に3波入っている、マイクが動作したクロックは1.22MHzで、1サンプルは820ns・・・)
■追記
必要に迫られないかぎり、プログラミング言語の勉強は進まない

*1:最適化させないと速度が出ないので、--releaseオプションを付けています

*2:5番目のグラフにおいて、値から平均値(中央値)を引き算することで、0を中心とする±の振幅に変換