adafruit / circuitpython

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

MP3Decoder causes RGBMatrix to flicker #6200

Open phantomsixthplayer opened 2 years ago

phantomsixthplayer commented 2 years ago

CircuitPython version

adafruit-circuitpython-bundle-7.x-mpy-20220322

UF2 Bootloader v3.14.0 SFHWRO
Model: Metro M4 Express
Board-ID: SAMD51J19A-Metro-v0

Code/REPL

from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
from adafruit_led_animation.animation.rainbowsparkle import RainbowSparkle
import audioio
import audiomp3
import board
import digitalio
import displayio
import framebufferio
import neopixel
import rgbmatrix
import time

# RGBMatrix
displayio.release_displays()

matrix = rgbmatrix.RGBMatrix(
   width = 64,
   height = 32,
   bit_depth = 4,
   rgb_pins = [board.D6, board.D5, board.D9, board.D11, board.D10, board.D12],
   addr_pins = [board.A5, board.A4, board.A3, board.A2],
   clock_pin = board.D13,
   latch_pin = board.D0,
   output_enable_pin = board.D1
)
display = framebufferio.FramebufferDisplay(matrix)

# display group
arcade_group = displayio.Group()

# fonts
font_1 = bitmap_font.load_font("/fonts/font_1.bdf")
font_2 = bitmap_font.load_font("/fonts/font_2.bdf")

# arcade_group graphics
ag_time = label.Label(font_1, text = "TIME", color = 0xB35A00)
ag_time.x = 1
ag_time.y = 4

ag_time_c = label.Label(font_2, text = "60", color = 0x00B300)
ag_time_c.x = 8
ag_time_c.y = 16

ag_score = label.Label(font_1, text = "SCORE", color = 0x0000B3)
ag_score.x = 29
ag_score.y = 4

ag_score_c = label.Label(font_2, text = "0", color = 0xFFFFFF)
ag_score_c.x = 43
ag_score_c.y = 16

ag_hiscore = label.Label(font_2, text = "HISCORE", color = 0x00B3B3)
ag_hiscore.x = 7
ag_hiscore.y = 28

ag_hiscore_c = label.Label(font_2, text = "22", color = 0xB30000)
ag_hiscore_c.x = 52
ag_hiscore_c.y = 28

arcade_group.append(ag_time)
arcade_group.append(ag_time_c)
arcade_group.append(ag_score)
arcade_group.append(ag_score_c)
arcade_group.append(ag_hiscore)
arcade_group.append(ag_hiscore_c)

# show the arcade_group
display.show(arcade_group)

# audio files
speaker = audioio.AudioOut(board.A0)
audio_file = {
   "countdown": "/audio/countdown.mp3"
}
mp3stream = audiomp3.MP3Decoder(open(audio_file["countdown"], "rb"))

# NeoPixels
led_pin = board.D25
num_leds = 54
leds = neopixel.NeoPixel(led_pin, num_leds, brightness = 1)
rainbow_sparkle = RainbowSparkle(leds, speed = 0.30, period = 5, num_sparkles = 27, precompute_rainbow = True)

# variables in the loop
game_start_time = 0

while True:
   # game variables
   current_time = 0
   game_time = 60

   display.show(arcade_group)
   game_start_time = time.time()

   while int(ag_time_c.text) > -1:
      # set LED color to shifting rainbow that sparkles
      rainbow_sparkle.animate()

      # update the time left in the round
      ag_time_c.text = str(game_time - int(time.time() - game_start_time))

      # change the time value's color and NeoPixel lights depending on time left in game
      if int(ag_time_c.text) <= 60 and int(ag_time_c.text) >= 21:
         if int(ag_time_c.text) == 60:
            ag_time_c.color = 0x00B300

      elif int(ag_time_c.text) <= 20 and int(ag_time_c.text) >= 11:
         if int(ag_time_c.text) == 20:
            ag_time_c.color = 0xB3B300
         if int(ag_time_c.text) == 11:
            mp3stream.file = open(audio_file["countdown"], "rb")
            speaker.play(mp3stream)

      elif int(ag_time_c.text) <= 10 and int(ag_time_c.text) >= 0:
         if int(ag_time_c.text) == 10:
            ag_time_c.color = 0xB30000

-----------------------------------------------------------

from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
from adafruit_led_animation.animation.solid import Solid
import audioio
import audiomp3
import board
import digitalio
import displayio
import framebufferio
import neopixel
import rgbmatrix
import time

# RGBMatrix
displayio.release_displays()

matrix = rgbmatrix.RGBMatrix(
   width = 64,
   height = 32,
   bit_depth = 4,
   rgb_pins = [board.D6, board.D5, board.D9, board.D11, board.D10, board.D12],
   addr_pins = [board.A5, board.A4, board.A3, board.A2],
   clock_pin = board.D13,
   latch_pin = board.D0,
   output_enable_pin = board.D1
)
display = framebufferio.FramebufferDisplay(matrix)

# display groups
arcade_group = displayio.Group()

# font
font_1 = bitmap_font.load_font("/fonts/font_1.bdf")
font_2 = bitmap_font.load_font("/fonts/font_2.bdf")

# arcade_group graphics
ag_time = label.Label(font_1, text = "TIME", color = 0xB35A00)
ag_time.x = 1
ag_time.y = 4

ag_time_c = label.Label(font_2, text = "60", color = 0x00B300)
ag_time_c.y = 16

ag_score = label.Label(font_1, text = "SCORE", color = 0x0000B3)
ag_score.x = 29
ag_score.y = 4

ag_score_c = label.Label(font_2, text = "0", color = 0xFFFFFF)
ag_score_c.y = 16

ag_hiscore = label.Label(font_2, text = "HISCORE", color = 0x00B3B3)
ag_hiscore.x = 7
ag_hiscore.y = 28

ag_hiscore_c = label.Label(font_2, text = "22", color = 0xB30000)
ag_hiscore_c.x = 52
ag_hiscore_c.y = 28

# add graphics to display groups
arcade_group.append(ag_time)
arcade_group.append(ag_time_c)
arcade_group.append(ag_score)
arcade_group.append(ag_score_c)
arcade_group.append(ag_hiscore)
arcade_group.append(ag_hiscore_c)

# audio files
speaker = audioio.AudioOut(board.A0)
audio_file = {
   "countdown": "/audio/countdown.mp3"
}
mp3stream = audiomp3.MP3Decoder(open(audio_file["countdown"], "rb"))

# NeoPixels
led_pin = board.D25
num_leds = 5
leds = neopixel.NeoPixel(led_pin, num_leds, brightness = 0.20)
leds_green = Solid(leds, color = 0x00B300)

display.show(arcade_group)
score = 0
start_time = time.time()

mp3stream.file = open(audio_file["countdown"], "rb")

while True:
   if time.time() >= start_time + 10:
      leds_green.animate()
      speaker.play(mp3stream)
      start_time = time.time()
      score += 1   
      ag_score_c.text = str(score)
      ag_score_c.color = 0xFFFFFF

Behavior

NeoPixels and MP3Decoder cause labels to flicker when they activate.

Description

NeoPixels: Tried the RGBMatrix + NeoPixels and the labels flicker when NeoPixels activate. I noticed the lower you go with the number of pixels the less visible the flicker, tried 20, 10, 5 and 1. Having it set to one is the least noticeable if at all. If the NeoPixels are static and the colour only changes at the specified times (30, 20, 10 seconds) that is when the label will flicker. This test is independent of the Audio playing.

MP3Decoder: Tried the RGBMatrix + MP3 Playback and the labels flicker when audio starts, so in this example at 60 and 11 seconds.

I am using:

Unfortunately the flicker shows up on camera less noticeable:

Other notes: Metro M4, NeoPixels, Speaker and RGBMatrix share the 10A power supply, also tried powering each individually. Tried powering the NeoPixels and RGBMatrix with a source other than 10A, still the same. Purchased another Metro M4 and no difference also tried on a Feather M4 Express.

Additional information

When I used 'from adafruit_display_text import bitmap_label as label' instead of 'from adafruit_display_text import label' in combination with time() that specific label would flicker extremely fast, not sure if somehow related.

Other troubleshooting

jepler commented 2 years ago

Thank you for your detailed report.

RGBMatrix is continuously scanned by software inside the microcontroller using a timer interrupts. NeoPixel has to disable interrupts in order to correctly 'bit bang' the WS2812 protocol. So in this case it is unfortunately going to lead to the matrix glitching or blanking. You may get different results if you can use SPI as the neopixel communication method instead: https://learn.adafruit.com/circuitpython-neopixels-using-spi

I am not sure why MP3 playback would ever disable interrupts for a long period of time, so that is worth investigating further as a core bug. There may also turn out to be a reason that interrupts have to be disabled e.g., when reading mp3 data from flash, but I don't know it just offhand.

phantomsixthplayer commented 2 years ago

@jepler thanks SPI resolved label flicker cause from NeoPixels. Lets see if anyone has any info on the audio side.

jepler commented 2 years ago

I was unable to identify any reasons that MP3 playback or audio playback would disable interrupts long enough to interfere with the RGBMatrix display, so that is still a surprise and needs further investigation.

phantomsixthplayer commented 2 years ago

np, if anyone has any ideas to try help narrow it down let me know.

phantomsixthplayer commented 2 years ago

I've just noticed the following:

Audio file is 2 minutes long audio file plays only for 1 minute before entering another function speaker.stop() is used as 1 minute left on previous audio file next audio file starts and you hear an audible pop as well as labels flicker

Audio file is 2 minutes long audio file plays for 2 minutes before entering another function (no repeat) speaker.stop() is used next audio file starts and the labels flicker

Audio file is 2 minutes long audio file plays for 2 minutes before entering another function (no repeat) speaker.stop() has been commented out next audio file starts and the labels flicker very subtlety

phantomsixthplayer commented 2 years ago

Not sure if this helps identify anything. I decided to try wave format and the issue does not occur from the file I tested.

wav file: https://learn.adafruit.com/circuitpython-essentials/circuitpython-audio-out

import displayio
import rgbmatrix
import board
import framebufferio
from adafruit_display_shapes.rect import Rect
import audioio
import audiocore
from time import sleep
import gc

# RGBMatrix
displayio.release_displays()

matrix = rgbmatrix.RGBMatrix(
    width=64,
    height=32,
    bit_depth=4,
    rgb_pins=[board.D8, board.D9, board.D10, board.D11, board.D12, board.D13],
    addr_pins=[board.D4, board.D6, board.D3, board.D5],
    clock_pin=board.D1,
    latch_pin=board.D2,
    output_enable_pin=board.D0
)
display = framebufferio.FramebufferDisplay(matrix)

# display groups
sg = displayio.Group()

# start screen graphics to help see flicker
sg_bg0 = Rect(0, 1, 64, 6, fill=0xFF0000)
sg_bg1 = Rect(0, 7, 64, 6, fill=0xFFFF00)
sg_bg2 = Rect(0, 13, 64, 6, fill=0x00FF00)
sg_bg3 = Rect(0, 19, 64, 6, fill=0x00FFFF)
sg_bg4 = Rect(0, 25, 64, 6, fill=0xFF00FF)

sg.append(sg_bg0)
sg.append(sg_bg1)
sg.append(sg_bg2)
sg.append(sg_bg3)
sg.append(sg_bg4)

# audio
wave_file = open("StreetChicken.wav", "rb")
wave = audiocore.WaveFile(wave_file)
audio = audioio.AudioOut(board.A0)

# variables
screen_states = {
    1: "start_screen"
}

screen_state = "start_screen"

def start_screen():
    global screen_state

    # show start screen
    display.show(sg)

    while screen_state == screen_states[1]:
        sleep(3)

        # play and repeat audio
        if not audio.playing:
            audio.play(wave)

# variables
screens = {
    "start_screen": start_screen
}

# main loop, run approriate screen function given screen state
while True:
    gc.collect()
    screens[screen_state]()