neu-rah / ArduinoMenu

Arduino generic menu/interactivity system
GNU Lesser General Public License v2.1
934 stars 189 forks source link

Cant loop! #269

Open mariusdp opened 4 years ago

mariusdp commented 4 years ago

Hi all,

First I apologise if I am in the wrong place, please point me in the wright place if that is the case. The issue: I attache a second i2c oled and on it it displays value read from analogue pin in "drawA, B or C". When I press on "ADC12" in "Voltmetre" menu the data is displays on the second oled but only one time (does not update/loop). The question is how can I do that??? Tried all sort of suggestions found out there but with no success. If someone can help that would be great.

THANK YOU,

/********************
Arduino generic menu system
lolin32 menu example

output: onboard oled (i2c ssd1306 u8g2) + Serial
input: Button + Serial
mcu: wemos lolin32 (ESP32) with builtin oled

********************/
#include <Arduino.h>
#include <Wire.h>
#include <menu.h>
#include <menuIO/u8g2Out.h>
#include <menuIO/serialIO.h>
#include <menuIO/stringIn.h>
#include <menuIO/chainStream.h>
#include "AiEsp32RotaryEncoder.h"
// #include <menuIO/encoderIn.h>

// Define LEDPIN.
#define LEDPIN 14

#define MAX_DEPTH 2

// Define buttons.
#define ROTARY_ENCODER_A_PIN 0
#define ROTARY_ENCODER_B_PIN 2
#define ROTARY_ENCODER_BUTTON_PIN 4
#define ROTARY_ENCODER_VCC_PIN -1 /*put -1 of Rotary encoder Vcc is connected directly to 3,3V; else you can use declared output pin for powering rotary encoder */

// Define buttons.
// #define NAV_BTN 0 // DOWN button
// #define NAV0_BTN 2  // UP button.
// #define SEL_BTN 4 // SELECT button

using namespace Menu;

#define SDA 5
#define SCL 4

#define fontName u8g2_font_7x13_mf
#define fontX 7
#define fontY 16
#define offsetX 0
#define offsetY 3
#define U8_Width 128
#define U8_Height 64
#define USE_HWI2C
#define fontMarginX 2
#define fontMarginY 2

U8G2_SSD1306_128X64_VCOMH0_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);//allow contrast change
U8G2_SSD1306_128X64_VCOMH0_F_HW_I2C u8g2a(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);//allow contrast change

AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ROTARY_ENCODER_VCC_PIN);
int test_limits = 10;

//+++++++++++ Voltmetre variables ++++++++++++++
float vout = 0.0;
float vin = 0.0;
int value = 0;
//**********************************************

/************************ 
   define menu colors --------------------------------------------------------
   each color is in the format:
   {{disabled normal,disabled selected},{enabled normal,enabled selected, enabled editing}}
   this is a monochromatic color table
**************************/
const colorDef<uint8_t> colors[6] MEMMODE={
  {{0,0},{0,1,1}},//bgColor
  {{1,1},{1,0,0}},//fgColor
  {{1,1},{1,0,0}},//valColor
  {{1,1},{1,0,0}},//unitColor
  {{0,1},{0,0,1}},//cursorColor
  {{1,1},{1,0,0}},//titleColor
};

result doAlert(eventMask e, prompt &item);

int test=55;

int ledCtrl=LOW;

result myLedOn() {
  digitalWrite(LEDPIN, HIGH);
  return proceed;
}
result myLedOff() {
  digitalWrite(LEDPIN, LOW);
  return proceed;
}

result TEST() {
    u8g2a.firstPage();  
  do {
    u8g2a.setFont(u8g2_font_6x13_tr);        // Select font. u8g2_font_6x13B_tr = bold. u8g2_font_6x13_tr = normal.
    u8g2a.drawStr(3, 12, "Voltage pin 0 MAX128"); 
    u8g2a.setCursor(33, 32);
    u8g2a.drawRFrame(0, 0, 127, 15, 5);
    u8g2a.drawRFrame(0, 16, 127, 48, 5);
    u8g2a.drawRFrame(15, 18, 100, 20, 5);     // Draws frame with rounded edges.
    u8g2a.drawRFrame(15, 40, 100, 20, 5);
    } while( u8g2a.nextPage() );
  return proceed;
}

// int period = 300;
// unsigned long time_now = 3;

result drawA() {  
  // if(millis() > time_now + period){
  //       time_now = millis();
  //+++++++++++ VOLTMETRE ++++++++++++++
  value = analogRead(12);
  vout = (3.3 / 4095) * value; // see text
  vin = vout; 
  //+++++++++++ VOLTMETRE ++++++++++++++
    u8g2a.firstPage();
    // u8g2a.nextPage();  
  do {
    u8g2a.clearBuffer();
    u8g2a.setFont(u8g2_font_6x13_tr);        // Select font. u8g2_font_6x13B_tr = bold. u8g2_font_6x13_tr = normal.
    u8g2a.drawStr(3, 12, "Voltage pin 0 MAX128"); 
    u8g2a.setCursor(33, 32);
    u8g2a.drawRFrame(0, 0, 127, 15, 5);
    u8g2a.drawRFrame(0, 16, 127, 48, 5);
    u8g2a.drawRFrame(15, 18, 100, 20, 5);     // Draws frame with rounded edges.
    u8g2a.drawRFrame(15, 40, 100, 20, 5);
    u8g2a.println(vout);                        //Prints the voltage.
    u8g2a.setFont(u8g2_font_6x13B_tr);
    u8g2a.println(" mV");
    u8g2a.setCursor(33, 54);
    u8g2a.println("ADC:   ");
    u8g2a.setFont(u8g2_font_6x13_tr);
    u8g2a.println(value);    //Prints the ADC date from MAX128.
    u8g2a.sendBuffer();
    } while( u8g2a.nextPage() );
    delay(200);
    // return proceed;
        delay(300);
}

result drawB() {  
  //+++++++++++ VOLTMETRE ++++++++++++++
  value = analogRead(13);
  vout = (3.3 / 4095) * value; // see text
  vin = vout; 
  //+++++++++++ VOLTMETRE ++++++++++++++
    u8g2a.firstPage();  
  do {
    // u8g2A.setFont(u8g2_font_6x13_tr);        // Select font. u8g2_font_6x13B_tr = bold. u8g2_font_6x13_tr = normal.
    u8g2a.drawStr(3, 12, "Voltage pin 0 MAX128"); 
    u8g2a.setCursor(33, 32);
    u8g2a.drawRFrame(0, 0, 127, 15, 5);
    u8g2a.drawRFrame(0, 16, 127, 48, 5);
    u8g2a.drawRFrame(15, 18, 100, 20, 5);     // Draws frame with rounded edges.
    u8g2a.drawRFrame(15, 40, 100, 20, 5);
    u8g2a.println(vin);                        //Prints the voltage.
    u8g2a.setFont(u8g2_font_6x13B_tr);
    u8g2a.println(" mV");
    u8g2a.setCursor(33, 54);
    u8g2a.println("ADC:   ");
    u8g2a.setFont(u8g2_font_6x13_tr);
    u8g2a.println(value);    //Prints the ADC date from MAX128.
    } while( u8g2a.nextPage() );
    delay(150);
    return proceed;
}

result action1(eventMask e,navNode& nav, prompt &item) {
  Serial.print("action1 event:");
  delay(500);
  Serial.println(e);
  Serial.flush();
  delay(100);
  return proceed;
}

result action2(eventMask e) {
  Serial.print("actikon2 event:");
  delay(500);
  Serial.println(e);
  Serial.flush();
  return quit;
}

result drawC() {  
  //+++++++++++ VOLTMETRE ++++++++++++++
  value = analogRead(15);
  vout = (3.3 / 4095) * value; // see text
  vin = vout; 
  //+++++++++++ VOLTMETRE ++++++++++++++
    u8g2a.firstPage();  
  do {
    // u8g2A.setFont(u8g2_font_6x13_tr);        // Select font. u8g2_font_6x13B_tr = bold. u8g2_font_6x13_tr = normal.
    u8g2a.drawStr(3, 12, "Voltage pin 0 MAX128"); 
    u8g2a.setCursor(33, 32);
    u8g2a.drawRFrame(0, 0, 127, 15, 5);
    u8g2a.drawRFrame(0, 16, 127, 48, 5);
    u8g2a.drawRFrame(15, 18, 100, 20, 5);     // Draws frame with rounded edges.
    u8g2a.drawRFrame(15, 40, 100, 20, 5);
    u8g2a.println(vin);                        //Prints the voltage.
    u8g2a.setFont(u8g2_font_6x13B_tr);
    u8g2a.println(" mV");
    u8g2a.setCursor(33, 54);
    u8g2a.println("ADC:   ");
    u8g2a.setFont(u8g2_font_6x13_tr);
    u8g2a.println(value);    //Prints the ADC date from MAX128.
    } while( u8g2a.nextPage() );
delay(150);
return proceed;
}

TOGGLE(ledCtrl,setLed,"Led: ",doNothing,noEvent,noStyle//setLed,,doExit,enterEvent,noStyle
  ,VALUE("On",HIGH,myLedOn,noEvent)
  ,VALUE("Off",LOW,myLedOff,noEvent)
);

int selTest=0;
SELECT(selTest,selMenu,"Select",doNothing,noEvent,noStyle
  ,VALUE("Zero",0,doNothing,noEvent)
  ,VALUE("One",1,doNothing,noEvent)
  ,VALUE("Two",2,doNothing,noEvent)
);

int chooseTest=-1;
CHOOSE(chooseTest,chooseMenu,"Choose",doNothing,noEvent,noStyle
  ,VALUE("First",1,doNothing,noEvent)
  ,VALUE("Second",2,doNothing,noEvent)
  ,VALUE("Third",3,doNothing,noEvent)
  ,VALUE("Last",-1,doNothing,noEvent)
);

//customizing a prompt look!
//by extending the prompt class
class altPrompt:public prompt {
public:
  altPrompt(constMEM promptShadow& p):prompt(p) {}
  Used printTo(navRoot &root,bool sel,menuOut& out, idx_t idx,idx_t len,idx_t panelNr) override {
    return out.printRaw(F("special prompt!"),len);;
  }
};

MENU(subMenu,"Sub-Menu",doNothing,noEvent,wrapStyle
// MENU(subMenu,"Sub-Menu",doNothing,noEvent,noStyle
  ,OP("Sub1",TEST,enterEvent)
  ,altOP(altPrompt,"",doNothing,noEvent)
  ,EXIT("<Back")
);

MENU(volDmm,"Voltmetre",doNothing,noEvent,wrapStyle
  ,OP("ADC12",drawA,enterEvent)
  ,OP("ADC13",drawB,enterEvent)
  ,OP("ADC15",drawC,enterEvent)
  ,EXIT("<Back")
);

// uint16_t hrs=0;
// uint16_t mins=0;

//define a pad style menu (single line menu)
//here with a set of fields to enter a date in YYYY/MM/DD format
// altMENU(menu,timeMenu,"Time",doNothing,noEvent,noStyle,(systemStyles)(_asPad|Menu::_menuData|Menu::_canNav|_parentDraw)
//   ,FIELD(hrs,"",":",0,11,1,0,doNothing,noEvent,noStyle)
//   ,FIELD(mins,"","",0,59,10,1,doNothing,noEvent,wrapStyle)
// );

// char* constMEM hexDigit MEMMODE="0123456789ABCDEF";
// char* constMEM hexNr[] MEMMODE={hexDigit};
// char buf1[]="0000";

// char* constMEM alphaNum MEMMODE=" 0123456789.ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz,\\|!\"#$%&/()=?~*^+-{}[]€";
// char* constMEM alphaNumMask[] MEMMODE={alphaNum};
// char name[]="          ";

MENU(mainMenu,"Main menu",doNothing,noEvent,wrapStyle
  ,OP("Op1",action1,anyEvent)
  ,OP("Op2",action2,enterEvent)
  // ,FIELD(test,"Test","%",0,100,10,1,doNothing,noEvent,wrapStyle)
  // ,EDIT("Name",name,alphaNumMask,doNothing,noEvent,noStyle)
  ,SUBMENU(volDmm)
  // ,SUBMENU(timeMenu)
  ,SUBMENU(subMenu)
  ,SUBMENU(setLed)
  // ,OP("LED On",myLedOn,enterEvent)
  // ,OP("LED Off",myLedOff,enterEvent)
  // ,OP("TEST",TEST,enterEvent)
  ,SUBMENU(selMenu)
  ,SUBMENU(chooseMenu)
  ,OP("Alert test",doAlert,enterEvent)
  // ,EDIT("Hex",buf1,hexNr,doNothing,noEvent,noStyle)
  ,EXIT("<Exit")
);

#define MAX_DEPTH 2

serialIn serial(Serial);

//define output device serial
idx_t serialTops[MAX_DEPTH]={0};
serialOut outSerial(*(Print*)&Serial,serialTops);

//define output device oled
idx_t gfx_tops[MAX_DEPTH];
PANELS(gfxPanels,{0,0,U8_Width/fontX,U8_Height/fontY});
u8g2Out oledOut(u8g2,colors,gfx_tops,gfxPanels,fontX,fontY,offsetX,offsetY,fontMarginX,fontMarginY);

//define outputs controller
menuOut* outputs[]{&outSerial,&oledOut};//list of output devices
outputsList out(outputs,sizeof(outputs)/sizeof(menuOut*));//outputs list controller

// MENU_INPUTS(in, &joystickBtns, &serial);
NAVROOT(nav,mainMenu,MAX_DEPTH,serial,out);

result alert(menuOut& o,idleEvent e) {
  if (e==idling) {
    o.setCursor(0,0);
    o.print("alert test");
    o.setCursor(0,1);
    o.print("press [select]");
    o.setCursor(0,2);
    o.print("to continue...");
  }
  return proceed;
}

result doAlert(eventMask e, prompt &item) {
  nav.idleOn(alert);
  return proceed;
}

//when menu is suspended
result idle(menuOut& o,idleEvent e) {
  o.clear();
  switch(e) {
    case idleStart:o.println("suspending menu!");break;
    case idling:o.println("suspended...");break;
    case idleEnd:o.println("resuming menu.");break;
  }
  return proceed;
}

void rotary_loop() {
    // //first lets handle rotary encoder button click
    // if (rotaryEncoder.currentButtonState() == BUT_RELEASED) {
    //  //we can process it here or call separate function like:
    //  rotary_onButtonClick();
    // }

    //lets see if anything changed
    int16_t encoderDelta = rotaryEncoder.encoderChanged();

    //optionally we can ignore whenever there is no change
    if (encoderDelta == 0) return;

    //for some cases we only want to know if value is increased or decreased (typically for menu items)
    if (encoderDelta>0) nav.doNav(upCmd);
    if (encoderDelta<0) nav.doNav(downCmd);

    //for other cases we want to know what is current value. Additionally often we only want if something changed
    //example: when using rotary encoder to set termostat temperature, or sound volume etc

    //if value is changed compared to our last read
    if (encoderDelta!=0) {
        //now we need current value
        // int16_t encoderValue = rotaryEncoder.readEncoder();
        //process new value. Here is simple output.
        // Serial.print("Value: ");
        // Serial.println(encoderValue);
      }
  } 

#define SOFT_DEBOUNCE_MS 100

void setup() {
  // pinMode(LEDPIN,OUTPUT);
  Serial.begin(115200);
  while(!Serial);
  pinMode(LEDPIN, OUTPUT);
  rotaryEncoder.begin();
    rotaryEncoder.setup([]{rotaryEncoder.readEncoder_ISR();});
    //optionally we can set boundaries and if values should cycle or not
    rotaryEncoder.setBoundaries(-20, 25, true); //minValue, maxValue, cycle values (when max go to min and vice versa)
  // pinMode(NAV_BTN,INPUT_PULLUP);
  // pinMode(NAV0_BTN,INPUT_PULLUP);
  // pinMode(SEL_BTN,INPUT_PULLUP);
  Serial.println("menu 4.x test");Serial.flush();
  Wire.begin(SDA,SCL);
  u8g2.setI2CAddress(0x3c * 2);
  u8g2.begin();
  u8g2.setFont(fontName);
  u8g2a.setI2CAddress(0x3d * 2);
  u8g2a.begin();
  u8g2a.setFont(fontName);
  // disable second option
  mainMenu[1].enabled=disabledStatus;
  // mainMenu[0].enabled=disabledStatus;
  // volDmm[1].enabled=disabledStatus;
  nav.idleTask=idle;//point a function to be used when menu is suspended
  Serial.println("setup done.");Serial.flush();
  u8g2.firstPage();
  do {
    u8g2.drawStr(0,fontY,"ArduinoMenu 4.x");
    u8g2.drawStr(0,fontY<<1,"on lolin32");
    u8g2.drawStr(0,fontY+(fontY<<1),"with buitin oled");
  } while(u8g2.nextPage());
  for(int c=255;c>0;c--) {
    u8g2.setContrast(255-255.0*log(c)/log(255));
    delay(8);
  }
  u8g2.setContrast(255);
   delay(500);
  nav.timeOut=60;//seconds
}

void loop() {
  if (!digitalRead(ROTARY_ENCODER_BUTTON_PIN)) {
    delay(SOFT_DEBOUNCE_MS);
    while(!digitalRead(ROTARY_ENCODER_BUTTON_PIN));
    nav.doNav(enterCmd);
    delay(SOFT_DEBOUNCE_MS);
  }
  // if (!digitalRead(SEL_BTN)) {
  //   delay(SOFT_DEBOUNCE_MS);
  //   while(!digitalRead(SEL_BTN));
  //   nav.doNav(enterCmd);
  //   delay(SOFT_DEBOUNCE_MS);
  // }
  // if (!digitalRead(NAV_BTN)) {
  //   delay(SOFT_DEBOUNCE_MS);
  //   while(!digitalRead(NAV_BTN));//wait for button release
  //   nav.doNav(upCmd);
  //   delay(SOFT_DEBOUNCE_MS);
  // }
  // if (!digitalRead(NAV0_BTN)) {
  //   delay(SOFT_DEBOUNCE_MS);
  //   while(!digitalRead(NAV0_BTN));//wait for button release
  //   nav.doNav(downCmd);
  //   delay(SOFT_DEBOUNCE_MS);
  // }
  nav.doInput();
  digitalWrite(LEDPIN, !ledCtrl);//no led on this board
  if (nav.changed(0)) {//only draw if menu changed for gfx device
    u8g2.firstPage();
    do nav.doOutput(); while(u8g2.nextPage());
  }

  delay(100);//simulate other tasks delay
  rotary_loop();
  delay(50);                                                             
    if (millis()>20000) rotaryEncoder.enable ();

}
neu-rah commented 4 years ago

Hi! this is the right place, welcome

on my first look on your code:

you have one automated input serialIn serial(Serial);

and two menu outputs,

//define outputs controller
menuOut* outputs[]{&outSerial,&oledOut};//list of output devices
outputsList out(outputs,sizeof(outputs)/sizeof(menuOut*));//outputs list controller

on the loop: if (nav.changed(0)) { this will check if visible menu on device index 0 has changed and therefor needs to be drawn, on your case device index 0 is &outSerial (not the gfx) but that should work

my question is, does your code work if you remove all the extra rotary code and delays and use serial as sole input instead? please use keys + - * \ followed by enter to navigate menu on serial monitor

mariusdp commented 4 years ago

Thank you for looking in! Yes it works, started like that and added rotary after. The "result drawA" could never get it to loop. Tried normal delay(xxx); if millis xxx do... Even tried to use two oled drivers hopping that "draw" would be more independent - result was the same (was interesting to see it displaying menu with u8g2 and draw with adafruit_ssd1306). thank you!

mariusdp commented 4 years ago

PS: navigation works fine on all 3 way tried so far: serial, encoder and btn. Could never get it to work with your encoder though, that is why I added that encoder bit.

neu-rah commented 4 years ago

the builtin encoder was done and tested for avr's, it depends on PCINT library that eventually can work on other platforms, but not tested. There is also clickencoder driver, the original as you can see here https://github.com/0xPIT/encoder is also for AVR's but its because of its timer1 dependency and some includes that can be discarded, I've used this driver with an esp8266 just by replacing the timer and removing avr specific includes. I can provide esp8266 code fot that. Since AM4 now has code driven navigation we can adapt any input driver by just calling the navigation functions... and after that didn't worry much on providing input specif drivers...

now having a second display to for independent draw, since you want results to be fixed and updating i would use menu handler to set a drawing type flag only and then I would do an independent switch/case (inside loop) to draw updated info on the second screen and it will keep drawing updated info according to selected flag value. This is the only way to have parallel draw and free the menu for navigation

mariusdp commented 4 years ago
  1. "now having a second display..." can you point me towards an example on how to achieve this? I´m kind of new to this world :P

  2. The code for esp8266 with 0xPIT/encoder would be great.

Thank you,