M5Dial Reponsiveness with I2C Output

First post here and I'm new to Arduino so please don't be too harsh.

I've started a project that will use an M5Dial as an encoder which will provide an output via it's I2C interface. I've set up the encoder with a display on the LCD which works really nicely and have moved on to the I2C output which is where I have hit an issue.

When using the dial without the I2C interface active it works well and is very responsive, the dial position is presented on the screen as a series of dots around the outside and a number in the middle. To test the I2C output I have connected an LCD display which just shows the dial position, it works but the extra code dulls the responsiveness of the dial, as if the time taken to write out the data to I2C means that not every dial movement is captured.

I've tried running the output within the main loop and in it's own loop (as it is below) and the results are the same. Writing out to I2C seems like it should be a minimal overhead but clearly it isn't, or I'm doing something wrong.

Any feedback on my code and why I am seeing this issue would be greatly appreciated.

#include "M5Dial.h"
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

int x[] = { 33, 23, 20, 23, 33, 49, 70, 94, 120, 146, 170, 191, 207, 217, 220, 217, 207 };
int y[] = { 170, 146, 120, 94, 70, 49, 33, 23, 20, 23, 33, 49, 70, 94, 120, 146, 170 };

int dots;

int dialPosition = 0;
int dialOld = 1;

int exactPosition = 0;
int exactOld = 1;

bool inRange(int val, int minimum, int maximum)
{
  return ((minimum <= val) && (val <= maximum));
}

void setup() {
  auto cfg = M5.config();
  M5Dial.begin(cfg, true, false);
  M5Dial.Display.setTextColor(GREEN);
  M5Dial.Display.setTextDatum(middle_center);
  M5Dial.Display.setTextFont(&fonts::Font7);
  M5Dial.Display.setTextSize(1.5);

  lcd.init();
  lcd.backlight();

  Serial.begin(9600);

}

void loop() {
  M5Dial.update();

  exactPosition = M5Dial.Encoder.read();

  if (exactPosition < 0){
    exactPosition = 0;
    M5Dial.Encoder.write(exactPosition);
  }

  if (exactPosition > 68){
    exactPosition = 68;
    M5Dial.Encoder.write(exactPosition);
    }

  if (exactOld != exactPosition){
    Serial.println(exactPosition);
    exactOld = exactPosition;
  }

    dialPosition = exactPosition/4;
  
  if (dialPosition != dialOld && inRange(dialPosition, 0, 16) ) {
    M5Dial.Speaker.tone(800, 50);
    M5Dial.Display.fillScreen(BLACK);
    M5Dial.Display.drawString(String(dialPosition),
                              M5Dial.Display.width() / 2,
                              M5Dial.Display.height() / 2);
    dots = dialPosition;
    while (dots > -1){
      if ( inRange(dots, 0, 2) ){
        M5Dial.Lcd.fillCircle(x[dots],y[dots],8,BLUE);
      }else if ( inRange(dots, 3, 13) ){
        M5Dial.Lcd.fillCircle(x[dots],y[dots],8,YELLOW);
      }else if ( dots > 13 ){
        M5Dial.Lcd.fillCircle(x[dots],y[dots],8,RED);
      }
      dots = (dots - 1);
    }
    dialOld = dialPosition;

    LCD();

  }

  if (M5Dial.BtnA.wasPressed()) {
    M5Dial.Encoder.readAndReset();
  }
}

void LCD(){

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Exact Pstn: ");
  lcd.print(exactPosition);

  lcd.setCursor(0, 1);
  lcd.print("Dial Pstn: ");
  lcd.print(dialPosition);

}

You might add a line Wire.setClock(400000); to double the speed of the I2C bus.

And I miss a Wire.begin() call.

Thanks for the fast response.

I've tried making the changes that you suggested but sadly it hasn't made any noticable difference :frowning:

you can save a micro-second here as the if's cannot be both true..

  if (exactPosition < 0){
    exactPosition = 0;
    M5Dial.Encoder.write(exactPosition);
  }
 else if (exactPosition > 68){
    exactPosition = 68;
    M5Dial.Encoder.write(exactPosition);
    }

Next step is to analyze where the most time is spent.

use two variables

uint32_t start, stop;

and measure individual steps in millis() one at the time.
This will take 5-10 runs to get the most "expensive" code in terms of time.

(of course, Serial.println(Stop - start); to get the durations)

you can save some function calls to inRange

    while (dots > -1) {
      if ( dots < 2  )    M5Dial.Lcd.fillCircle(x[dots],y[dots],8,BLUE);
      else if (dots < 13) M5Dial.Lcd.fillCircle(x[dots],y[dots],8,YELLOW);
      else                M5Dial.Lcd.fillCircle(x[dots],y[dots],8,RED);
      dots = (dots - 1);
    }

Did the measurements as you suggested and the main loop takes about 15mS to execute when the dial is moved (0mS if it's static) but the LCD loop takes 50mS which is therefore where all the delay is being added.

Setting Wire.setClock to 400000 cuts the LCD loop time to 20mS and it does seem to work better but I'm thinking that I've hit a fundamental issue with the limitation of the I2C write-out time so will need to think on this again.

Thanks for your help.

Every time you call this routine, you needlessly do the following

  1. clear the entire display
  2. write constant text "Exact Postn: "
  3. write constant text "Dial Pstn "

You should do these things once and then your function could be

void LCD(){
  lcd.setCursor(12, 0);
  if ( exactPosition < 10 ) lcd.print(' ');
  lcd.print(exactPosition);

  lcd.setCursor(12, 1);
  if ( dialPosition < 10 ) lcd.print(' ');
  lcd.print(dialPosition);
}

That will save you some milliseconds

blh64 That did the trick, cut the LCD loop time to 4mS and it all works smoothly.

Thanks for your help. And robtillaart too.