Oled slows down the while loop

I tried many things, spent a night with chat gpt and drank a round with the stepper motor gang, but the problem remains.

why is the Oled slowing down my while loop?

I trigger it only on encoder and button events, but that single draw slows down my sequencer clock, the lfo, envelopes…

I would love a running light for my sequencer, but the way it is, such a thing is just not possible.

I was successful showing parameter names and values, but the adc jitter triggers the oled allthe time, slowing down the code too much.

here is my code, sorry it is big, but at least for beginners, it shows some things like successful mpr121 and the oled drawing at least something…

there are oleds in everything, what is the secret?

#include "daisysp.h"
#include <stdio.h>
#include <string.h>
#include "daisy_seed.h"
#include "dev/oled_ssd130x.h"
#include "dev/mpr121.h"
#include "dsp.h"


using namespace daisysp;
using namespace daisy;

using MyOledDisplay = OledDisplay<SSD130xI2c128x64Driver>;

dsy_gpio ledR_1_pin, ledG_1_pin, ledB_1_pin;


uint16_t currTouched;
uint16_t lastTouched;


// Define the frequencies for the two octaves surrounding middle C
double scale[][12] = {
    // Octave below middle C
    {130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185.00, 196.00, 207.65, 220.00, 233.08, 246.94},
    // Middle C and the octave above
    {261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99, 392.00, 415.30, 440.00, 466.16, 493.88},
    // Octave above middle C
    {523.25, 554.37, 587.33, 622.25, 659.26, 698.46, 739.99, 783.99, 830.61, 880.00, 932.33, 987.77}};

DaisySeed         hw;
MyOledDisplay     display;
Mpr121I2C         mpr121;

Encoder encoder;
Switch  button;

static Oscillator osc;
static Oscillator lfo;
static Svf        filt;
static Metro      clock;
static Adsr       env;
static Adsr       envM;
float             gate;
float             envout;
int               seqF;
float             trig;
float             sig;
int               decO=0;
int               trigo = 0;
float             keyF=0;
int               ctfO=0;
int               beat[8] {1, 1, 1, 1, 1, 1, 1, 1};
int               beato[8] {0, 0, 0, 0, 0 ,0 ,0 ,0};
int               seqP[8] {0, 0, 0, 0, 0, 0, 0, 0};




static void AudioCallback(AudioHandle::InterleavingInputBuffer  in,
                          AudioHandle::InterleavingOutputBuffer out,
                          size_t                                size)



{
    // Declare a DelayLine of MAX_DELAY number of floats.
    
    for(size_t i = 0; i < size; i += 2)
    {
        
        // Use envelope to control the amplitude of the oscillator.
        envout = env.Process(trig && gate);
        osc.SetAmp(envout);
        sig = osc.Process();

        filt.Process(sig);


        // left out
        out[i] = filt.Low();

        // right out
        out[i + 1] = filt.Low();
    }
}

int main(void)
{
    
    // initialize seed hardware and oscillator daisysp module
    float sample_rate;
    AudioHandle::Config audio_config;
    audio_config.samplerate = SaiHandle::Config::SampleRate::SAI_96KHZ;  // Set sample rate to 96kHz
    audio_config.postgain = 1.0f; // Set post gain (volume control)

    
    hw.Configure();
    hw.Init();
    hw.SetAudioBlockSize(4);
    sample_rate = hw.AudioSampleRate();

    //Set button to pin 28, to be updated at a 1kHz  samplerate
    button.Init(hw.GetPin(9), 100);

    osc.Init(sample_rate);
    lfo.Init(sample_rate);

    //init metro object
    clock.Init(2, sample_rate);
    clock.SetFreq (200);

    //Set envelope parameters
    env.Init(sample_rate);
    env.SetTime(ADSR_SEG_ATTACK, .1);
    env.SetTime(ADSR_SEG_DECAY, .5);
    env.SetTime(ADSR_SEG_RELEASE, .5);  
    env.SetSustainLevel(.5);

    //Mod>Env Init
    envM.Init(sample_rate);
    envM.SetTime(ADSR_SEG_ATTACK, .1);
    envM.SetTime(ADSR_SEG_DECAY, .5);
    envM.SetTime(ADSR_SEG_RELEASE, .5);  
    envM.SetSustainLevel(.5);

    // Set parameters for oscillator
    osc.SetWaveform(osc.WAVE_POLYBLEP_SAW);
    osc.SetFreq(440);
    osc.SetAmp(0.5);
    
    lfo.SetWaveform(lfo.WAVE_SIN);
    lfo.SetFreq(.5);

    // Initialize Filter, and set parameters.
    filt.Init(sample_rate);
    filt.SetFreq(500.0);
    filt.SetRes(0.85);
    filt.SetDrive(0.8);
    
     
    
    // R 1 on pin 27
    ledR_1_pin.pin = hw.GetPin(28);
    ledR_1_pin.mode = DSY_GPIO_MODE_OUTPUT_PP;
    ledR_1_pin.pull = DSY_GPIO_NOPULL;
    dsy_gpio_init(&ledR_1_pin);
    dsy_gpio_write(&ledR_1_pin, true);

    // R 1 on pin 27
    ledG_1_pin.pin = hw.GetPin(29);
    ledG_1_pin.mode = DSY_GPIO_MODE_OUTPUT_PP;
    ledG_1_pin.pull = DSY_GPIO_NOPULL;
    dsy_gpio_init(&ledG_1_pin);
    dsy_gpio_write(&ledG_1_pin, true);

    // R 1 on pin 27
    ledB_1_pin.pin = hw.GetPin(30);
    ledB_1_pin.mode = DSY_GPIO_MODE_OUTPUT_PP;
    ledB_1_pin.pull = DSY_GPIO_NOPULL;
    dsy_gpio_init(&ledB_1_pin);
    dsy_gpio_write(&ledB_1_pin, true);

    
    //Configure pin 21 as an ADC input. This is where we'll read the knob.
   // Create an array of two AdcChannelConfig objects
    const int num_adc_channels = 11;
    AdcChannelConfig my_adc_config[num_adc_channels];
    
    // Initialize the first one connected to A0
    my_adc_config[0].InitSingle(hw.GetPin(21));
    my_adc_config[1].InitSingle(hw.GetPin(20));
    my_adc_config[2].InitSingle(hw.GetPin(19));
    my_adc_config[3].InitSingle(hw.GetPin(18));
    my_adc_config[4].InitSingle(hw.GetPin(17));
    my_adc_config[5].InitSingle(hw.GetPin(16));
    my_adc_config[6].InitSingle(hw.GetPin(22));
    my_adc_config[7].InitSingle(hw.GetPin(23));
    my_adc_config[8].InitSingle(hw.GetPin(24));
    my_adc_config[9].InitMux(hw.GetPin(15), 8, seed::D3, seed::D4, seed::D5);
    my_adc_config[10].InitMux(hw.GetPin(25), 8, seed::D0, seed::D1, seed::D2);
    
  

    //Initialize the adc with the config we just made
    
    hw.adc.Init(my_adc_config, num_adc_channels);
    
    
    
    //Start reading values
    hw.adc.Start();

    /** Initialize our Encoder */
    encoder.Init(seed::D6, seed::D7, seed::D8);
    
    
    // start callback
    hw.StartAudio(AudioCallback);

    // touch works
    // Initialize MPR121 with I2C configuration
    Mpr121I2C::Config mprConfig;
    mprConfig.transport_config.periph = I2CHandle::Config::Peripheral::I2C_1;
    mprConfig.transport_config.speed = I2CHandle::Config::Speed::I2C_400KHZ;
    mprConfig.transport_config.mode  = I2CHandle::Config::Mode::I2C_MASTER;
    mprConfig.transport_config.scl = Pin(PORTB,8);  // Replace with your SCL pin
    mprConfig.transport_config.sda = Pin(PORTB,9);  // Replace with your SDA pin

	// Set additional MPR121 configuration if needed
    mprConfig.touch_threshold = 12;
    mprConfig.release_threshold = 6;

    if(mpr121.Init(mprConfig) != Mpr121I2C::OK)
    {
        // Handle initialization error
        while(1)
        {
            // Error handling code (blinking LED, etc.)
        }
    }
	// Setup to print 
	hw.StartLog();
    /** Create a variable to store the value we'll print out to USB */
    int output_value = 0;
    // oled
    // Configure the Display 
    MyOledDisplay::Config disp_cfg;
    disp_cfg.driver_config.transport_config.i2c_address               = 0x3C;
    disp_cfg.driver_config.transport_config.i2c_config.periph         = I2CHandle::Config::Peripheral::I2C_1;
    disp_cfg.driver_config.transport_config.i2c_config.speed          = I2CHandle::Config::Speed::I2C_1MHZ;
    disp_cfg.driver_config.transport_config.i2c_config.mode           = I2CHandle::Config::Mode::I2C_MASTER;
    disp_cfg.driver_config.transport_config.i2c_config.pin_config.scl = {DSY_GPIOB, 8};    
    disp_cfg.driver_config.transport_config.i2c_config.pin_config.sda = {DSY_GPIOB, 9};
    // And Initialize
    display.Init(disp_cfg);
    display.Fill(false);
        display.SetCursor(20, 10);
        display.WriteString("MONOFONK", Font_11x18, true);
        display.SetCursor(2, 42);
        display.WriteString("Pointless Innovations", Font_6x8, true);
        display.Update();
    

    int step = 0;
    int cnt = 0;

    while(1) {

        encoder.Debounce();

        /* This will return a -1 if the encoder was turned counter clockwise, or 
         * a 1 if the encoder was turned clockwise, or 0 if the encoder was not rotated.
         */
        int increment = encoder.Increment();
        if((increment > 0) && (encoder.Pressed() == 0))
        {
            /** increase our output value and print it */
            output_value += 1;
            
            if (output_value > 7)
            {
            output_value = 0;
            }
            hw.PrintLine("Output Value:\t%d", output_value);
            
        }
        else if((increment < 0) && (encoder.Pressed() == 0))
        {
            /** decrease our output value and print it */
            output_value -= 1;
            
            if (output_value < 0)
            {
            output_value = 7;
            }
            hw.PrintLine("Output Value:\t%d", output_value);
          
        }
        
        //Debounce the button
        button.Debounce();
        //If the button is pressed, turn the LED on
        hw.SetLed(button.Pressed());
        if(button.RisingEdge()){
        if (beat[output_value] == 1)
            {
            beat[output_value] = 0;
            }
            else 
            {
            beat[output_value] = 1;
            }
            }


         /*  if (encoder.FallingEdge())
            {
                
            if (beat[output_value] == 1)
            {
            beat[output_value] = 0;
            }
            else 
            {
            beat[output_value] = 1;
            }
            
            
            }

*/
        if (beat[output_value] == 1)
            {
            dsy_gpio_write(&ledB_1_pin, false);
            }
            else 
            {
            dsy_gpio_write(&ledB_1_pin, true);
            }

        if((encoder.Pressed()) && (increment > 0))
        
        {
            /** increase our output value and print it */
            seqP[output_value] += 1;
            hw.PrintLine("Pitch:\t%d", seqP[output_value]);
            
            
        }
        else if((encoder.Pressed()) && (increment < 0))
        {
            /** decrease our output value and print it */
            seqP[output_value] -= 1;
            hw.PrintLine("Pitch:\t%d", seqP[output_value]);


               
        }
        
        // Read touch status
        currTouched = mpr121.Touched();

        // Process the touch status as needed
        for(int i = 0; i < 12; i++)
        {
            if((currTouched & (1 << i)) && !(lastTouched& (1 << i)))
            {
                // Channel i is touched, print the channel number
                hw.PrintLine("Channel %d touched", i);
                keyF = (scale[0][i] * .5);
                trig = 1;
                
            
                //hw.SetLed(true);
                dsy_gpio_write(&ledR_1_pin, false );
                dsy_gpio_write(&ledG_1_pin, false );
                
            }
            if(!(currTouched & (1 << i)) && (lastTouched& (1 << i)))
            {
                // Channel i is released, print the channel number
                hw.PrintLine("Channel %d released", i);
                trig = 0;
                //#hw.SetLed(false);
                dsy_gpio_write(&ledR_1_pin, true  );
                dsy_gpio_write(&ledG_1_pin, true  );
                
            }
        }
        lastTouched = currTouched;
        
//Sequencer
if (trig != trigo){
            step=7;
            cnt=0;
            clock.Reset();} 
        if (trig > 0){
        clock.SetFreq(hw.adc.GetFloat(5) * 240);
        
        if (clock.Process()){
            
            if(cnt == 0) {
                gate = 1;
                if (step == 7){
                    step=0;
                }
                else{
                    step++;
                }
            } else if(cnt == 3) {
                gate = 0;
            }
            

            if (cnt < 4) {
                cnt++;
            } else {
                cnt = 0;
            }
            
        }
        trigo=trig;
        }
        if (hw.adc.GetFloat(6) > 0)
        {
            gate = beat[step] * gate;
            seqF = mtof(seqP[step]*4);
        }
        else 
        {
            gate = 1;
            seqF = 0;
        }

        float lfoF = hw.adc.GetMuxFloat(9, 0);
        lfo.SetFreq(lfoF * lfoF * 99.99  + .01);
        float Cutfone=0;
        //osc.SetFreq(finalF * 1200 + 45);
        float Menv = envM.Process(trig && gate);
        float peak = hw.adc.GetMuxFloat(10, 5) * Menv;
        float Cut = hw.adc.GetMuxFloat(10, 4);
        
        float lfoamt = lfo.Process() * hw.adc.GetMuxFloat(9, 1) * 4000;
        filt.SetFreq((Cut * Cut + peak) * 4500 + 45 + lfoamt);
        filt.SetRes(hw.adc.GetMuxFloat(10, 6) * 1.1f);

        float baseF= (mtof(hw.adc.GetFloat(0) * 84 - 24));
        float lfopamt = lfo.Process() * hw.adc.GetMuxFloat(9, 2);
        float Menvpamt = Menv * hw.adc.GetMuxFloat(9, 7);
        float lfopwamt(lfo.Process() * hw.adc.GetFloat(7));
        osc.SetFreq((Menvpamt * Menvpamt * 4000)  +  (lfopamt * lfopamt * 1000) + keyF + baseF + seqF);
        osc.SetWaveform(hw.adc.GetMuxFloat(10, 7) * 7 );
        osc.SetPw(hw.adc.GetFloat(8)+lfopwamt);

        clock.SetFreq(4 * (hw.adc.GetFloat(6) * 1.9f + .1f));

         //Set envelope parameters
        env.SetTime(ADSR_SEG_ATTACK, hw.adc.GetMuxFloat(10, 0));
        float dec = hw.adc.GetMuxFloat(10, 1);
        env.SetTime(ADSR_SEG_DECAY, dec * dec);
        env.SetTime(ADSR_SEG_RELEASE, hw.adc.GetMuxFloat(10, 3));
        env.SetSustainLevel(trig * hw.adc.GetMuxFloat(10, 2));

         //Set Mod envelope parameters
        envM.SetTime(ADSR_SEG_ATTACK, hw.adc.GetMuxFloat(9, 3));
        float decM = hw.adc.GetMuxFloat(9, 4);
        envM.SetTime(ADSR_SEG_DECAY, decM * decM);
        envM.SetTime(ADSR_SEG_RELEASE, hw.adc.GetMuxFloat(9, 6));
        envM.SetSustainLevel(trig * hw.adc.GetMuxFloat(9, 5));

        //OlED Stuff
        /*fonepole(Cutfone, Cut, .000001);
        int newcut = Cutfone * 10000000;
        if (newcut != ctfO){
        
        display.SetCursor(40, 2);
        
        display.WriteString("Cutoff", Font_6x8, true);
        
        display.SetCursor(50, 22);

        /
        FixedCapStr<16> str("");
        str.AppendInt(newcut);
        
        display.WriteString(str, Font_11x18, true);

        display.Update();
        
        }
        
        int newdec = decM * 100;
        if (newdec != decO){
        
        display.SetCursor(4, 2);
        display.WriteString("Decay", Font_6x8, true);
        
        display.SetCursor(14, 22);

        FixedCapStr<16> str1("");
        str1.AppendInt(newdec);

        display.WriteString(str1, Font_11x18, true);

        
        display.Update();
        
        }*/
        for(int x = 0; x < 4; x++)
        {
        if(encoder.FallingEdge() || encoder.Increment() || button.FallingEdge())
        {
        display.Fill(false);
        display.SetCursor(25, 2);
        display.WriteString("Pitch & Gate", Font_7x10, true);
        display.DrawCircle(10, 53, beat[0] *4+1, true);
        display.DrawCircle(25, 53, beat[1] *4+1, true);
        display.DrawCircle(40, 53, beat[2] *4+1, true);
        display.DrawCircle(55, 53, beat[3] *4+1, true);
        display.DrawCircle(70, 53, beat[4] *4+1, true);
        display.DrawCircle(85, 53, beat[5] *4+1, true);
        display.DrawCircle(100, 53, beat[6] *4+1, true);
        display.DrawCircle(115, 53, beat[7] *4+1, true);


        display.DrawLine(output_value*15 + 4, 63, output_value*15+16, 63, true);

        display.DrawArc(10, 32, 6, 90, seqP[0]*-10, true);
        display.DrawArc(25, 32, 6, 90, seqP[1]*-10, true);
        display.DrawArc(40, 32, 6, 90, seqP[2]*-10, true);
        display.DrawArc(55, 32, 6, 90, seqP[3]*-10, true);
        display.DrawArc(70, 32, 6, 90, seqP[4]*-10, true);
        display.DrawArc(85, 32, 6, 90, seqP[5]*-10, true);
        display.DrawArc(100, 32, 6, 90, seqP[6]*-10, true);
        display.DrawArc(115, 32, 6, 90, seqP[7]*-10, true);
        
        display.Update();
        }
         
        }
        
        //decO = newdec;
        //ctfO = newcut;
        
        
        
        
        //System :: Delay(50);

    }
}

I2C transport used for this display is not using DMA, so while data is being sent there everything in your main loop waits. However it’s not a major problem generally, because the buffer to send is fairly small for 128x64 display and its configured in 1MHz mode. It’s hard follow all details as formatting is not great, but it looks like your code doesn’t distribute its load over time correctly and no amount of drinking with chatGPT helped it apparently :wink:

  1. Why are you redrawing the same thing 4 times with a for loop? Nothing in it uses x variable or updates encoder state (that happens when you call ::Debounce()), so it makes no sense to me.

  2. Your main loop function constantly runs and can constantly redraw display if your logic triggers it. A reasonable approach would be to add a 1-2 ms delay in the end of the loop (since you don’t need to update global state more often than your audio callback runs)

  3. Also make sure that display update won’t be called more frequently that your target FPS requires. This means that if you want to draw with something like 50 FPS, you should just set a “dirty” flag when you check for encoder change and run the display update code no frequently than once every 20 ms

thanks!

  1. Why are you redrawing the same thing 4 times

yes that is a mistake, leftover from trying to draw an array.

  1. Your main loop function constantly runs and can constantly redraw display if your logic triggers it. A reasonable approach would be to add a 1-2 ms delay

so the main loop is the while loop, right?
I added 2ms delay already today, but maybe it gets better without drawing 4 times.

  1. Also make sure that display update won’t be called more frequently that your target FPS requires.

sorry, bloody beginner here, what would such a flag look like? also, I assumed the display only draws once, when the logic triggers.

1mhz of thanks!

Yes, but it’s your code that tells it when to draw. If you tell it to update constantly, it will do that without giving any benefits and potentially leaving less time for other things to run in your main loop. What you could do is check current time to see how many ms elapsed to decide when you should start drawing.