adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
MIT License
3.97k stars 1.16k forks source link

Issue with PDMin and usb_cdc sending data to PC #9321

Open BoLiu97 opened 1 month ago

BoLiu97 commented 1 month ago

CircuitPython version

Adafruit CircuitPython 9.0.5 on 2024-05-22; Adafruit Feather M4 Express with samd51j19
Board ID:feather_m4_express

Code/REPL

import time
import array
import math
import board
import audiobusio
import usb_cdc
import struct

# Main program
mic = audiobusio.PDMIn(board.TX, board.D12, sample_rate=24000, bit_depth=16)
samples = array.array('H', [0] * 160)

print("Program started. Collecting and transmitting data...")
while True:
    mic.record(samples, len(samples))
    data = struct.pack('<' + 'H' * len(samples), *samples)
    usb_cdc.data.write(data)

Behavior

I am trying to collect PDM microphone data and send to my PC. I am using Feather M4 express. However, I am unable to send data through the code above. By adding a timesleep, I am able to send but having a different sample rate.

Description

No response

Additional information

def write_wav_data(raw_sound, filename, sample_rate):
    logging.info("Writing data to WAV file.")
    with wave.open(filename, 'wb') as wav_file:
        wav_file.setnchannels(1)  # mono
        wav_file.setsampwidth(2)  # 16 bits = 2 bytes
        wav_file.setframerate(sample_rate)
        wav_file.writeframes(raw_sound)

def main(args):
    ser = serial.Serial(args.port, args.baud_rate, timeout=10)
    logging.info(f'Connected to device on {args.port}. Ready to record.')

    while True:
        input("Press Enter to start recording...")
        logging.info('Recording started...')

        raw_sound = bytearray()
        bytes_per_sample = 2
        samples_per_chunk = 160
        bytes_per_chunk = samples_per_chunk * bytes_per_sample
        total_bytes_to_read = args.sample_rate * args.duration * bytes_per_sample
        total_chunks = total_bytes_to_read // bytes_per_chunk

        for _ in range(total_chunks):
            data = ser.read(bytes_per_chunk)
            if len(data) == bytes_per_chunk:
                # Unpack the data as it was packed on the microcontroller side
                samples = struct.unpack('<' + 'H' * samples_per_chunk, data)
                # Repack the data for WAV format (16-bit signed integers)
                raw_sound.extend(struct.pack('<' + 'h' * samples_per_chunk, *samples))
            else:
                logging.warning(f"Expected {bytes_per_chunk} bytes, received {len(data)} bytes.")

        filename = f"{args.filename}.wav"
        write_wav_data(raw_sound, filename, args.sample_rate)
        logging.info(f'Recording saved as {filename}.')
        break

Having this code to receive data from my PC side

dhalbert commented 1 month ago

However, I am unable to send data through the code above. By adding a timesleep, I am able to send but having a different sample rate.

BoLiu97 commented 1 month ago

However, I am unable to send data through the code above. By adding a timesleep, I am able to send but having a different sample rate.

  • Could you elaborate? What exactly fails with the code above?
  • Where did you add the time.sleep() and for how long
  • What do you mean by "having a different sample"?
  1. By using the code above on circuitpython, I didn't receive anything from my PC side. For example

WARNING:root:Expected 320 bytes, received 0 bytes.

2,3. By using mic.record(samples, len(samples)) data = struct.pack('<' + 'H' * len(samples), *samples) print('something') usb_cdc.data.write(data) or mic.record(samples, len(samples)) data = struct.pack('<' + 'H' * len(samples), *samples) usb_cdc.data.write(data) time.sleep(10) I was able to receive data for my PC. However, It took about 15 seconds to receive a 3 seconds audio sample and the sound is abnormal.

dhalbert commented 1 month ago

Instead of trying to read 320 bytes at once with a 10-second timeout, try reading much smaller chunks, or read with timeout=0.

I am thinking that the reader on the PC side might get out of sync and miss some of the initial bytes. That would make it be out of sync from then on, waiting and waiting, when the microcontroller side has already sent everything. To test this, I would change the microcontroller side to send some known test data, and not do PDMIn for now. Instead, send some packets that are numbered sequentially in some way so you can see if and what gets dropped on the receiving side. You could also do some synchronization. For instance, don't send anything until you've received a "go ahead" signal from the PC. That could be just a single byte indicator.