adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.06k stars 1.2k forks source link

audiobusio.PDMIn: sample_rate setting is ignored #5914

Closed zapwizard closed 1 year ago

zapwizard commented 2 years ago

CircuitPython version

7.1.1
7.2.0-alpha.1-199-gbed724fc1 (attempted for fix by danh)

Code/REPL

sample_frequency = 16000
sample_size = 1024
mic = audiobusio.PDMIn(board.GP0, board.GP1, sample_rate=sample_frequency, bit_depth=8, mono=True)
print ("sample_size=",sample_size,"sample_frequency=",sample_frequency,"returned mic.sample_rate=",mic.sample_rate)
time_per_sample = None
samples = array.array('B', [1] * sample_size)
check = array.array('B', [1] * sample_size)

def benchmark_capture():
    global sample_frequency, sample_size, time_per_sample
    start_capture_time = time.monotonic_ns()
    mic.record(samples, len(samples))
    if samples == check:
        print("Failed to capture samples")
    else:
        sample_duration = time.monotonic_ns() - start_capture_time
        time_per_sample = sample_duration / sample_size
        if time_per_sample:
            sample_frequency = round((1 / time_per_sample) * 10**9)
            print(sample_size, "samples took",sample_duration,"ns or", time_per_sample, "ns per sample", "| Max Sample Frequency =", sample_frequency)

while True:
    benchmark_capture()
    time.sleep(5)

Behavior

Regardless of the sample_rate, the output takes the same amount of time, and returns a max sample rate of ~21845hz. mic.sample_rate always returns 44099

sample_size= 512 sample_frequency= 16000 returned mic.sample_rate= 44099
512 samples took 24414062 ns or 47683.7 ns per sample | Max Sample Frequency = 20972
512 samples took 23437500 ns or 45776.4 ns per sample | Max Sample Frequency = 21845

sample_size= 512 sample_frequency= 40000 returned mic.sample_rate= 44099
512 samples took 24414063 ns or 47683.7 ns per sample | Max Sample Frequency = 20972
512 samples took 23437500 ns or 45776.4 ns per sample | Max Sample Frequency = 21845

sample_size= 1024 sample_frequency= 16000 returned mic.sample_rate= 44099
1024 samples took 47851563 ns or 46730.0 ns per sample | Max Sample Frequency = 21400
1024 samples took 46875000 ns or 45776.4 ns per sample | Max Sample Frequency = 21845

Description

I need to interface an ultrasonic 80kHz capable PDM microphone. (Knowles SPH0641LU4H-1)

The microphone seems to work as far as capturing normal audio frequencies, but the sample_rate settings seems to be fixed or ignored in PDMin.

The calculated time it takes to record a number of samples always returns ~21845Hz. This is regardless of the sample size or sample_rate setting.

Additional information

Knowles SPH0641LU4H-1 PDM microphone Pi Pico CLK on GP0 DATA on GP1 SELECT to GND

tannewt commented 2 years ago

What board/port are you using? It sounds like a port-specific bug.

zapwizard commented 2 years ago

It's in the additional info: Pi Pico CLK on GP0 DATA on GP1

I have also tried on GP3/GP4, and GP0/GP4 with no change. GP0 and GP1 were both listed as valid using the code listed here

Running that code, it looks like nearly every pin is a clock /data combo.

nuwaveit commented 2 years ago

Im doing it with CLK=GP26, DATA=GP27, same result, sample rate is 44099. It makes the PDM breakout useless...

Adafruit CircuitPython 7.1.0 on 2021-12-28; Raspberry Pi Pico with rp2040

UPDATE: upgrading to 7.1.1 still shows the bug, but at least its usable, it doesnt choke like it did on 7.1.0

DavePutz commented 2 years ago

@tannewt - it looks like the issue may be that we're using a constant for frequency (44100 32 2) when we call common_hal_rp2pio_statemachine_construct() in common-hal/audiobusio/PDMIn.c. But, I am unsure what we should be using (sample_rate 32 2 ?). Any thoughts on what the calculation should be?

dhalbert commented 2 years ago

I gave a user a test build that used sample_rate * 32 *2, but it didn't help solve their problem of requesting a higher sample rate. However, there might still be some error in setting the sample rate that's returned when it's requested , so I'm not sure it didn't do the intended thing. I haven't had time to debug it further yet.

DavePutz commented 2 years ago

@zapwizard - what are your expected values? I built a 7.2.0-alpha1 from current source with the frequency set to `sample_rate 32 2, and got the following results: sample rate at 16000 sample_size= 1024 sample_frequency= 16000 returned mic.sample_rate= 16000 1024 samples took 127929688 ns or 124931.0 ns per sample | Max Sample Frequency= 8004

sample rate at 32000 sample_size= 1024 sample_frequency= 32000 returned mic.sample_rate= 32000 1024 samples took 64453125 ns or 62942.5 ns per sample | Max Sample Frequency =15888

sample rate at 64000 sample_size= 1024 sample_frequency= 64000 returned mic.sample_rate= 63995 1024 samples took 32226563 ns or 31471.3 ns per sample | Max Sample Frequency =31775

sample rate at 80000 sample_size= 1024 sample_frequency= 80000 returned mic.sample_rate= 80000 1024 samples took 26367188 ns or 25749.2 ns per sample | Max Sample Frequency =38836

zapwizard commented 2 years ago

@DavePutz On the Pi Pico I am not getting any samples when using 7.2.0-alpha1. The array after mic.record is just all 1's.

My full code:

import board
import audiobusio
import array
import time

sample_frequency = 22000
sample_size = 1024
mic = audiobusio.PDMIn(board.GP3, board.GP4, sample_rate=sample_frequency, bit_depth=8, mono=True)
print ("sample_size=",sample_size,"sample_frequency=",sample_frequency,"returned mic.sample_rate=",mic.sample_rate)
time_per_sample = None
samples = array.array('B', [1] * sample_size)
check = array.array('B', [1] * sample_size)

def benchmark_capture():
    global sample_frequency, sample_size, time_per_sample
    start_capture_time = time.monotonic_ns()
    mic.record(samples, len(samples))
    if samples == check:
        print("Failed to capture samples", samples[0:10])
    else:
        sample_duration = time.monotonic_ns() - start_capture_time
        time_per_sample = sample_duration / sample_size
        if time_per_sample:
            sample_frequency = round((1 / time_per_sample) * 10**9)
            print(sample_size, "samples took",sample_duration,"ns or", time_per_sample, "ns per sample", "| Max Sample Frequency =", sample_frequency)

while True:
    benchmark_capture()
    time.sleep(5)

Output:

sample_size= 1024 sample_frequency= 22000 returned mic.sample_rate= 44099
Failed to capture samples array('B', [1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
DavePutz commented 2 years ago

@zapwizard - Yes, you will need the fix for PR #5984 added to CP 7.2.0-alpha-1 in order for PDMIn to work at all. I am more interested in what values you expect from your script at 80 kHz.

zapwizard commented 2 years ago

@DavePutz, sorry I didn't answer your question. Yes the values you put up is exactly what I would expect from the benchmark code.

zapwizard commented 2 years ago

@DavePutz The updated build fixes my issue. I am getting good FFT results on frequencies around 40KHz! I am using this to make a 1950's mechanical ultrasonic remote work.

(Also, I don't know if I close this or you guys after release)

dhalbert commented 1 year ago

Fixed by #5993.