Questions about my synth project

Hi guys,

I'm building a synth based on the seeeduino Xiao samd21.

The synth consist of a bytebeat formula input with help of buttons and an OLED interface, the evaluation of this formula and the output of the result on the Xiao DAC.

The problem is the audio output is very noisy with high frequencies. I thought that I ask too many to the chip with the several peripherals because when I deactivate the OLED screen the audio output is far better.

My first idea was to add some filters/buffers, but I don't know where the noise come from. It is the SPI OLED that generate some EMI or the chip that is too solicited ?

Here is the schematic of the synth.

My second idea is to port the code to a seeeduino Xiao RP2040 that is more powered with it two cores. RP2040 doesn't have a DAC, but I think it is not very important with bytebeat sounds.
When trying to output a PWM on the pin0 of the xiao RP2040, I get nothing on my scope. When using the original code with pinToUse = 25, I can see the build in LED blinking

I used this code on the RP2040

/****************************************************************************************************************************
  basic_pwm.ino
  For RP2040 boards
  Written by Dr. Benjamin Bird

  A basic example to get you up and running.

  Library by Khoi Hoang https://github.com/khoih-prog/RP2040_PWM
  Licensed under MIT license

  The RP2040 PWM block has 8 identical slices. Each slice can drive two PWM output signals, or measure the frequency
  or duty cycle of an input signal. This gives a total of up to 16 controllable PWM outputs. All 30 GPIO pins can be driven
  by the PWM block
*****************************************************************************************************************************/

#define _PWM_LOGLEVEL_        3
#include "RP2040_PWM.h"

//creates pwm instance
RP2040_PWM* PWM_Instance;

float frequency;
float dutyCycle;

#define pinToUse      0

void setup()
{
  PWM_Instance = new RP2040_PWM(pinToUse, 20000, 0);
}

void loop()
{
  delay(1000);
  frequency = 20000;
  dutyCycle = 90;

  PWM_Instance->setPWM(pinToUse, frequency, dutyCycle);

  delay(1000);
  dutyCycle = 10;

  PWM_Instance->setPWM(pinToUse, frequency, dutyCycle);
}

I don't know if I better stick to the samd21 with some circuitry to improve the DAC output or port the code to a more powerful RP2040 that will handle better the computing and further enhancement.

The delay(1000) causes a 1 second hang up, twice in loop. That looks bad.
Where is the OLED code?

In this piece of code the delay is not a problem because it's a test, and it will hang up a first duty cycle value then the second, letting time analyze the pwm output

The OLED code

configure() is called in main setup(), updateDisplay() is called in every loop(), then user input set the interface values for displaying needed screen

#ifndef DISPLAY_HELPER_H
#define DISPLAY_HELPER_H

#define I2C_ADDRESS 0x3c

namespace display
{
    Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

    void configure();

    void displayCursor(int);
    void displaySettings(int);
    void displayMenuState(int);
    void displayCompileFlagState(bool);
    void displaySavedFormulas();
    void displayCurrentFile();
    void displayLiveMode();
    void updateDisplay();

    void configure()
    {
        display.setTextSize(1);             // Normal 1:1 pixel scale
        display.setTextColor(SH110X_WHITE); // Draw white text
        // display.cp437(true);

        display.begin(I2C_ADDRESS);
        // if (!display.begin(I2C_ADDRESS, 0x3C))
        // { // Address 0x3C for 128x64
        //     Serial.println(F("SSD1306 allocation failed"));
        //     for (;;)
        //         ; // Don't proceed, loop forever
        // }
        display.clearDisplay();
        display.display();
    }

    void displaySavedFormulas()
    {
        display.drawBitmap(0, 0, assetsBmp::FORMULAS_BMP, 128, 64, SH110X_WHITE);

        int cursorX = 44;
        int cursorY = 12;

        display.setCursor(cursorX, cursorY);
        display.setTextWrap(0);

        int page = interface::savedFormulasIndex / 6;
        for (int i = 0; i < 6; i++)
        {
            byte index = i + (page * 6);

            char buffer[4];
            snprintf(buffer, sizeof(buffer), "%02d", index + 1);

            if (index == interface::savedFormulasIndex)
            {
                display.fillRect(cursorX - 1, cursorY - 1, (CHAR_WIDTH * 2) + 1, CHAR_HEIGHT + 1, SH110X_WHITE);
                display.setTextColor(SH110X_BLACK);
            }
            display.print(buffer);
            if (index == interface::savedFormulasIndex)
            {
                display.setTextColor(SH110X_WHITE);
            }
            display.print(" ");
            display.print(formula::savedFormulas[index]);
            cursorY += CHAR_HEIGHT;
            display.setCursor(cursorX, cursorY);
        }
        display.setTextWrap(1);
    }

    void displaySettings(int settingsCursorState)
    {
        display.drawBitmap(0, 0, assetsBmp::SETTINGS_BMP, 128, 64, SH110X_WHITE);
        if (settingsCursorState == 0)
        {
            display.drawBitmap(48, 16, assetsBmp::SETTINGS_CURSOR_SELECTED_BMP, 10, 10, SH110X_WHITE);
        }
        else if (settingsCursorState == 1)
        {
            display.drawBitmap(48, 43, assetsBmp::SETTINGS_CURSOR_SELECTED_BMP, 10, 10, SH110X_WHITE);
        }
        display.drawBitmap(56, 50, assetsBmp::SETTINGS_WAVEFORM_SINE_NO_SELECTED, 15, 10, SH110X_WHITE);
        display.drawBitmap(96, 50, assetsBmp::SETTINGS_WAVEFORM_SQR_NO_SELECTED, 15, 10, SH110X_WHITE);
    }

    void displayExprMode()
    {
        display.setCursor(34, 1);
        display.setTextColor(SH110X_BLACK);
        if (interface::exprModeIndex == interface::SYMBOLS)
        {
            display.print(F("+-x"));
        }
        else if (interface::exprModeIndex == interface::NUMBERS)
        {
            display.print(F("123"));
        }
        else if (interface::exprModeIndex == interface::FUNCTIONS)
        {
            display.drawTriangle(34, 6, 34, 1, 40, 6, SH110X_BLACK);
        }
        display.setTextColor(SH110X_WHITE);
    }

    void displayLiveMode()
    {
        display.setCursor(58, 1);
        display.setTextColor(SH110X_BLACK);
        if (formula::liveMode)
        {
            display.print(F("live"));
        }
        else
        {
            display.print(F("hold"));
        }
        display.setTextColor(SH110X_WHITE);
    }

    void displayCompileFlagState(bool compileFlag)
    {
        if (compileFlag == 1)
        {
            display.drawBitmap(114, 1, assetsBmp::YES_COMPILE_BMP, 7, 7, SH110X_WHITE);
        }
        else
        {
            display.drawBitmap(114, 1, assetsBmp::NO_COMPILE_BMP, 7, 7, SH110X_WHITE);
        }
    }

    void displayMenuState(int symbolsState)
    {
        display.setTextColor(SH110X_BLACK);
        display.setCursor(50, 2);
        if (symbolsState == 0)
        {
            display.print(F("+ - x"));
        }
        else if (symbolsState == 1)
        {
            display.print(F("1 2 3"));
        }
        else
        {
            display.drawTriangle(50, 6, 64, 6, 50, 1, SH110X_BLACK);
        }
        display.setTextColor(SH110X_WHITE);
    }

    void displayCursor(int indexFormula)
    {
        int underlineY = indexFormula / LINE_CHAR_WIDTH;
        int underlineX = indexFormula;
        for (int i = 0; i < underlineY; i++)
        {
            underlineX = underlineX - LINE_CHAR_WIDTH;
        }
        underlineX = underlineX * CHAR_WIDTH;
        underlineY = (((underlineY + 1) * CHAR_HEIGHT) + (CHAR_HEIGHT + 2)) - 1;
        display.drawFastHLine(underlineX, underlineY, CHAR_WIDTH, 1);
    }

    void displayPreview()
    {
        display.setCursor(7 * CHAR_WIDTH, 0);
        display.print("PREVIEW");
        display.setCursor(0, 8);
        display.print(formula::savedFormulas[interface::savedFormulasIndex]);
    }

    void displayStatusBar()
    {
        displayCurrentFile();
        displayExprMode();
        displayLiveMode();
    }

    void displayCurrentFile()
    {
        char buffer[4];
        snprintf(buffer, sizeof(buffer), "%03d", interface::currentFileIndex);
        display.setCursor(8, 0);
        display.print(buffer);
    }

    void displayChaosMode()
    {
        int x = display.getCursorX();
        int y = display.getCursorY();
        if (modes::bytebeatMode == modes::bytebeat_modes::CHAOS)
        {
            display.setCursor(87, 1);
            display.setTextColor(SH110X_BLACK);
            display.print("666");
            display.setTextColor(SH110X_WHITE);
        }
        display.setCursor(x, y);
    }

    void displayEditor(int prevY)
    {
        display.drawBitmap(0, 0, assetsBmp::STATUSBAR_BMP, 128, 9, SH110X_WHITE);

        displayStatusBar();

        displayChaosMode();
        displayCompileFlagState(formula::compileFlag);
        displayCursor(interface::indexFormula);

        display.setCursor(0, 10);
        display.print((const char *)formula::formula);

        display.setCursor(interface::indexFormula * CHAR_WIDTH, prevY);
    }

    bool needClear()
    {
        return !(modes::bytebeatMode == modes::VISUALIZER) && !(modes::bytebeatMode == modes::POPUP);
    }

    void updateDisplay()
    {
        // if (interface::needDisplay)
        // {
        if (!(modes::bytebeatMode == modes::VISUALIZER) && !(modes::bytebeatMode == modes::POPUP))
        {
            display.clearDisplay();
        }

        switch (modes::bytebeatMode)
        {
        case modes::bytebeat_modes::CHAOS:
        case modes::bytebeat_modes::EDITOR:
        {
            int prevY = display.getCursorY();

            display.clearDisplay();
            display.setCursor(0, 0);

            displayEditor(prevY);

            break;
        }
        case modes::bytebeat_modes::VISUALIZER:
        {
            visualizer::displayVisualizer(display);
            break;
        }
        case modes::bytebeat_modes::POPUP:
        {
            display.drawBitmap(24, 24, assetsBmp::SAVE_POPUP_BMP, 81, 35, SH110X_WHITE);
            break;
        }
        case modes::bytebeat_modes::CONFIG:
        {
            if (interface::tabMenuState == 0)
            {
                if (interface::funcState == 1)
                {
                    displayPreview();
                }
                else
                {
                    displaySavedFormulas();
                }
            }
            else if (interface::tabMenuState == 999)
            {
                displaySettings(interface::settingsIndex);
            }
            break;
        }
        default:
        {
            break;
        }
        }

        display.display();
        interface::needDisplay = 0;
        // }
    }
}

#endif

Something I noticed is trying to optimize calls to display() cause more problems than when not optimized.

Please post the entire code. Know that all helpers are newbies looking at Your project.
How is the project powered? Can the power consumption of the OLED possibly affect the sound machine?
One general rule is to update any kind of display only when data have changed. OLEDs surely consume considerable controller power. That affects the sound "machine".

How are you powering everything? I build power distribution boards with both individual .1uF bypasses for each device, and 470uF for everyone. Practice good lead dress, and keep your audio signals away from the digitals as much as possible. Use shielded cable for audio leads. No breadboards or long leads.

The synth is powered directly via mcu usb-c so 5V

How are you powering everythinh. I build power distribution boards with both individual .1uF bypasses for each device and 470uF for everyone. Practice good lead dress and keep your audio signals away from the digitals as much as possible. Use shielded cable for audio leads. No breadboards or long leads.

MCU +5 is NOT a power supply for more than an LED. Distribute hard +5 TO the 5V pin and everything else.

1 Like

The source is currently the Laptop usb or power strip with usb, it's not enough ?

Not if you're feeding from the USB to the mcu 5v pin. Do NOT use the mcu as a passthru power source.

Should I put a big capacitor in between GND and 5V pins ? It would be a shame if I cannot power the circuit through the MCU USB.

The problem of noise on the dac would come from a power undersupply ?

use an external +5 supply and feed everything from that. Do not use the mcu as a passthru as it is not designed for that app.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.