Due to high demand expect some shipping delays at this time, orders may not ship for 1-2 business days.
0

NeoTrellis M4 and driving a Monochrome 1.3" 128x64 OLED
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

NeoTrellis M4 and driving a Monochrome 1.3" 128x64 OLED

by smarkwell on Thu Apr 29, 2021 4:30 pm

Recently picked up a NeoTrellis M4 for a little project and wanted to attach a little display to it since it'll be primarily run solo.
I picked up the 1.3" Monochrome OLED ( https://www.adafruit.com/product/938 ) to pair with it since I2C and library compatibility seemed like a good fit for easy "plug and play" setup.

What I'm finding is that I can't drive with displayio AND play WAV files via audioio at the same time without the sound clipping/distorting.
I'm new to CircutPython and these boards, and figured I'd work with Circut before digging into Ardunio for ease of use (especially loading external files onto the flash).

I am curious if
  • Am I doing something fundamentally wrong with CircutPython (timing/etc)?
  • Is this board simply not powerful enough to stream/mix WAV files and run displayIO in general?
  • Could I make this work if I switched from CircutPython to Arduino?

Code below, hopefully not to long. I am disabling auto refresh in displayio, and limiting the refresh rate of the timer display to once every 600ms.
In experimentation, leaving the label in place in displayio, but never refreshing again, prevents any sound distortion issues.

Conversely if anyone wants to create interesting distortion effects, there is some very interesting/pleasing effects you can get out the M4 when you push it over the edge this way.

Code: Select all | TOGGLE FULL SIZE
# https://learn.adafruit.com/monochrome-oled-breakouts/circuitpython-usage
# https://circuitpython.readthedocs.io/projects/display_text/en/latest/index.html
# https://circuitpython.readthedocs.io/projects/display_text/en/latest/api.html

# https://github.com/adafruit/Adafruit_CircuitPython_SSD1306
# https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_SSD1306/
# https://github.com/adafruit/Adafruit_SSD1306
# https://learn.adafruit.com/circuitpython-display-support-using-displayio/external-display

# https://circuitpython.readthedocs.io/en/latest/shared-bindings/audiomixer/index.html#audiomixer.Mixer

# https://circuitpython.readthedocs.io/en/latest/shared-bindings/time/index.html

# https://circuitpython.readthedocs.io/projects/trellism4/en/latest/api.html


import time
import adafruit_trellism4

trellis = adafruit_trellism4.TrellisM4Express()

import board

import displayio
import adafruit_displayio_ssd1306
import terminalio
from adafruit_display_text import label


import audioio
import audiocore
import audiomixer


WIDTH=128
HEIGHT=64

i2c = board.I2C()
displayio.release_displays()
display_bus = displayio.I2CDisplay(i2c, device_address=0x3d)
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=WIDTH, height=HEIGHT, auto_refresh=False)

display_group = displayio.Group(max_size=10)
display.show(display_group)
 
# color_bitmap = displayio.Bitmap(WIDTH, HEIGHT, 1)
# color_palette = displayio.Palette(1)
# color_palette[0] = 0x000000
 
# bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
# display_group.append(bg_sprite)


voices = {}
voice_mixer = None

def load_voices():   
    voices = {}
    voices["default"] = audiocore.WaveFile(open("voice01.wav","rb"))
    voices[1] = voices["default"]
    voices[2] = audiocore.WaveFile(open("voice02.wav","rb"))
    voices[3] = audiocore.WaveFile(open("voice03.wav","rb"))
    voices[4] = audiocore.WaveFile(open("voice04.wav","rb"))
    return voices
   

def close_voices(voices):
    for voice in voices:
        voice.close()

       
def wheel(pos):
    if pos < 0 or pos > 255:
        return 0, 0, 0
    if pos < 85:
        return int(255 - pos * 3), int(pos * 3), 0
    if pos < 170:
        pos -= 85
        return 0, int(255 - pos * 3), int(pos * 3)
    pos -= 170
    return int(pos * 3), 0, int(255 - (pos * 3))


trellis.pixels.fill((50,50,50))

voices = load_voices()

wav = voices["default"]
voice_mixer = audiomixer.Mixer(voice_count=4, sample_rate=wav.sample_rate, channel_count=wav.channel_count, bits_per_sample=wav.bits_per_sample, samples_signed=True)

sound_status_display = label.Label(
    terminalio.FONT, color=0xFFFFFF, x=0,y=10,
    text="LOADING>>>>"
)
display_group.append(sound_status_display)
sound_status_check_time = 0
sound_status_check_rate = 600 * 1000000

display.refresh()
request_refresh = False

trellis.pixels.brightness=0.1

with audioio.AudioOut(left_channel=board.A1,right_channel=board.A0) as audio:

   

    audio.play(voice_mixer)
    current_press = set()
    while True:
        looptime = time.monotonic_ns()

        pressed = set(trellis.pressed_keys)
        for press in pressed - current_press:
            if press:
                voice_mixer.play(voices[press[1]+1], voice=press[1])
                pixel = (press[1] * 8) + press[0]
                pixel_index = pixel * 256 // 32
                trellis.pixels.fill(wheel(pixel_index & 255))
        for release in current_press - pressed:
            if release:
                trellis.pixels.fill((50,50,50))
       

        time.sleep(0.2)


        if looptime >= sound_status_check_time:

            statustext = str(sound_status_check_time)

            sound_status_check_time = looptime + sound_status_check_rate         

            # for mixerVoice in voice_mixer.voice:
            #     statustext += "Playing: %s\n" % mixerVoice.playing
           
            sound_status_display.text = statustext
            sound_status_check_time = looptime + sound_status_check_rate
            request_refresh = True

        if request_refresh:           
            display.refresh()
            request_refresh = False

        current_press = pressed


close_voices(voices)


smarkwell
 
Posts: 3
Joined: Wed Apr 28, 2021 12:13 am

Re: NeoTrellis M4 and driving a Monochrome 1.3" 128x64 OLED

by tannewt on Sun May 02, 2021 3:53 pm

Controlling when the display refreshes is a good start. The other thing to try is increasing the audio buffer sizes. Doing that will allow for longer periods of time between audio buffer fills done internally.

tannewt
 
Posts: 2393
Joined: Thu Oct 06, 2016 8:48 pm

Re: NeoTrellis M4 and driving a Monochrome 1.3" 128x64 OLED

by smarkwell on Sun May 02, 2021 4:42 pm

Thank you! That solved it, pushing the buffer to 16KB, from the 1KB default fixed it!

Missed seeing that the buffer size was configurable in audiomixer, thank you for pointing me in the right direction.

smarkwell
 
Posts: 3
Joined: Wed Apr 28, 2021 12:13 am

Re: NeoTrellis M4 and driving a Monochrome 1.3" 128x64 OLED

by smarkwell on Sun May 02, 2021 5:56 pm

Interestingly doing a DisplayIO Label with no carriage returns is performant at 10 fps (or higher), but once carriage returns are introduced start running into issues again, even when bumping up the buffer size (until running out of RAM). At least I now to tweak things and where I can work around issues.

I adjusted my code to be able to flip between different displays, to test different scenario's performance. With a DisplayIO label containing carriage returns, seems pretty easy to run out of memory and have DisplayIO (or something) corrupt a buffer *somewhere* so the audio samples mix incorrectly, even after returning to a scenario where the label does not contain carriage returns.

Adafruit CircuitPython 6.2.0 on 2021-04-05; Adafruit Trellis M4 Express with samd51g19

Anyways, as said above, got plenty of dials to tweak now to make things work.

smarkwell
 
Posts: 3
Joined: Wed Apr 28, 2021 12:13 am

Re: NeoTrellis M4 and driving a Monochrome 1.3" 128x64 OLED

by tannewt on Mon May 03, 2021 12:58 pm

If the buffer corruption is repeatable please file an issue with all of your setup details. That way we can try and fix it at some point. Issues are here: https://github.com/adafruit/circuitpyth ... new/choose

tannewt
 
Posts: 2393
Joined: Thu Oct 06, 2016 8:48 pm

Please be positive and constructive with your questions and comments.