circuitpython: CircuitPython completely freezes upon button click

While using the cpx, the two buttons crash the entire board after playing a sound. There is no error message and the REPL and serial monitor crash. The buttons are supposed to play a sound when they’re pressed. Here is the video of the serial monitor crash: https://imgur.com/O1GLBwN. The board crash is also recorded: https://youtu.be/53wDuqXmbi8. The code that seems to be causing the crash is below

while True:
    if cpx.button_a:
        cpx.play_file("foo.wav")
    elif cpx.button_b:
        cpx.play_file("bar.wav")

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 17 (8 by maintainers)

Commits related to this issue

Most upvoted comments

I can confirm that your fix takes care of both the original code by c-k-dillard and my repro code above. My only question would be if we could still have a dma channel conflict should someone actually be playing a wav file asynchronously with using the microphone (or is that really not possible)?

next guess, which it looks like has solved the problem, but not sure if it’s cumulative with other local changes:

@@ -292,6 +296,7 @@ void audio_dma_stop(audio_dma_t* dma) {
         audio_dma_disable_channel(dma->dma_channel);
         disable_event_channel(dma->event_channel);
     }
+    audio_dma_state[dma->dma_channel] = NULL;
     MP_STATE_PORT(playing_audio)[dma->dma_channel] = NULL;
     dma->dma_channel = AUDIO_DMA_CHANNEL_COUNT;
 }

Theory: nothing is clearing out audio_dma_state for this channel (except reset); this leads to audio_dma_load_next_block being called when it shouldn’t have been, which is exactly what has been described.

I think I may have spotted the issue, but would like some other eyes to take a look if possible. Here’s what I see: When playing .wav files, in audio_dma_setup_playback(), we find a free DMA channel (usually channel 0) and store it in the dma structure. So, whenever we call audio_dma_load_next_block() we use that stored channel. Now, when using the microphone we call common_hal_audiobusio_pdmin_record_to_buffer(), which also looks for a free DMA channel (again, it seems to always get channel 0). The problem occurs when I see a stack like this fragment:

HardFault_Handler Line: 294 <signal handler called> Line: 294 audio_dma_load_next_block (audio_dma_t * dma = 0x20005488, audio_dma_t * dma@entry = 0x20005488) Line: 119 audio_dma_background Line: 332 run_background_tasks Line: 60 run_background_tasks Line: 51 common_hal_audiobusio_pdmin_record_to_buffer (uint32_t output_buffer_length = <optimized out>, uint16_t * output_buffer = <optimized out>, audiobusio_pdmin_obj_t * self = <optimized out>) Line: 412

The code at line 412 in common_hal_audiobusio_pdmin_record_to_buffer() seems to have started a DMA operation and is waiting for completion:

    // If wait_counts exceeds the max count, buffer has probably stopped filling;
    // DMA may have missed an I2S trigger event.
    while (!event_interrupt_active(event_channel) && ++wait_counts < MAX_WAIT_COUNTS) {
        #ifdef MICROPY_VM_HOOK_LOOP
            MICROPY_VM_HOOK_LOOP
        #endif
    }

So, we have an active DMA operation on channel 0 from the PDM code, and the background code now uses the channel 0 which was stored in the dma structure. Contention/.confusion? I have seen both loops and hard faults - maybe depends on what data gets dumped where?? I end up with two questions:

  1. What can we do to avoid this contention on channel 0?

  2. I am not sure why the background code is calling audio_dma_load_next_block(). The Python script looks to be waiting for completion when it plays a sound - calling cpx.play_file which waits for audio to finish playing:

       if sys.implementation.version[0] >= 3:
         with audioio.AudioOut(board.SPEAKER) as audio:
             wavefile = audioio.WaveFile(open(file_name, "rb"))
             audio.play(wavefile)
             while audio.playing:
                 pass
    

    So - what would be left for audio_dma_load_next_block() to do?