Hi,

Voor mijn oven projectje heb ik een simpel menutje nodig op het 16x2 LCD schermpje.
Ik dacht, laat ik een van een AI gebruik maken en wel een die voor Arduino online is gelaatst.

Dit is het verzoek dat ik heb laten uitvoeren:
Arduino code, 4 menu page, 1 button on pin-2, 4 relais output on pin 2,3,4,5, i2c LCD 16x2 display

Daar kwam het volgende uit:


#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Define the pins for the relays  
#define RELAY1 3  
#define RELAY2 4  
#define RELAY3 5  
#define RELAY4 6

// Button pin  
#define BUTTON_PIN 2

// Create an instance of the LCD  
LiquidCrystal_I2C lcd(x27, 16, 2); // Change x27 to your LCD's I2C address if necessary

// Variables for menu control  
int currentMenu = ;
const int totalMenus = 4;
bool lastButtonState = HIGH;
unsigned long lastDebounceTime = ;
unsigned long debounceDelay = 50;

void setup() {
    // Initialize the LCD  
    lcd.begin();
    lcd.backlight();
    
    // Set relay pins as OUTPUT  
    pinMode(RELAY1, OUTPUT);
    pinMode(RELAY2, OUTPUT);
    pinMode(RELAY3, OUTPUT);
    pinMode(RELAY4, OUTPUT);
    
    // Set button pin as INPUT  
    pinMode(BUTTON_PIN, INPUT_PULLUP);
    
    // Initialize relays to OFF  
    digitalWrite(RELAY1, LOW);
    digitalWrite(RELAY2, LOW);
    digitalWrite(RELAY3, LOW);
    digitalWrite(RELAY4, LOW);
    
    displayMenu();
}

void loop() {
    // Read the button state  
    bool buttonState = digitalRead(BUTTON_PIN);
    
    // Check if the button state has changed  
    if (buttonState != lastButtonState) {
        lastDebounceTime = millis();
    }

    // If the button state has been stable for longer than the debounce delay  
    if ((millis() - lastDebounceTime) > debounceDelay) {
        // If the button is pressed (LOW)
        if (buttonState == LOW) {
            currentMenu = (currentMenu + 1) % totalMenus; // Go to the next menu  
            displayMenu();
            activateRelay();
        }
    }
    
    lastButtonState = buttonState;
}

void displayMenu() {
    lcd.clear();
    switch (currentMenu) {
        case :
            lcd.print("Menu 1");
            break;
        case 1:
            lcd.print("Menu 2");
            break;
        case 2:
            lcd.print("Menu 3");
            break;
        case 3:
            lcd.print("Menu 4");
            break;
    }
}

void activateRelay() {
    // Turn off all relays first  
    digitalWrite(RELAY1, LOW);
    digitalWrite(RELAY2, LOW);
    digitalWrite(RELAY3, LOW);
    digitalWrite(RELAY4, LOW);
    
    // Activate the corresponding relay based on the current menu  
    switch (currentMenu) {
        case :
            digitalWrite(RELAY1, HIGH);
            break;
        case 1:
            digitalWrite(RELAY2, HIGH);
            break;
        case 2:
            digitalWrite(RELAY3, HIGH);
            break;
        case 3:
            digitalWrite(RELAY4, HIGH);
            break;
    }
}

Er zitten wat fouten in en de aansturing van het LCD display wat ik gebruik kan niet met de code voor het LCD schermpje.
Een paar foutjes heb ik er uit gekregen, zoals case 0 geen waarde had.

Ik heb de code zo goed als ik kon werkend gemaakt en heb op de "relais" uitgangen voor iedere uitgang een LED en een weerstadn geplaatst.
De LED komt overeen met de menu tekst voor menu 1 t/m 4.
Wat echter niet werkt is de "Debounce Code" ik heb twee schakelaars geprobeert, ook een type waarvan ik weet dat hij netjes schakeld, ook een condensator geplaatst, maar ik krijg het niet werken.
Ook niet door het aanpassen van de debounce tijd.

Dit is de code waarbij het menu werkt, maar het debouncen niet.


#include <Wire.h>
#include "DFRobot_RGBLCD1602.h"
// #include <LiquidCrystal_I2C.h>

// Define the pins for the relays  
#define RELAY1 3  
#define RELAY2 4  
#define RELAY3 5  
#define RELAY4 6

// Button pin  
#define BUTTON_PIN 2

// Create an instance of the LCD  
DFRobot_RGBLCD1602 lcd(/*RGBAddr*/0x60 ,/*lcdCols*/16,/*lcdRows*/2);  //16 characters and 2 lines of show
// LiquidCrystal_I2C lcd(16, 2); // Change x27 to your LCD's I2C address if necessary

// Variables for menu control  
int currentMenu = 1;
const int totalMenus = 4;
bool lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 30;

void setup() {
    // Initialize the LCD  
//    lcd.begin();
//    lcd.backlight();
// Setup LCD Scherm
    lcd.init();
    lcd.setCursor(0, 0);
    lcd.setRGB(100, 0, 70);


    // Set relay pins as OUTPUT  
    pinMode(RELAY1, OUTPUT);
    pinMode(RELAY2, OUTPUT);
    pinMode(RELAY3, OUTPUT);
    pinMode(RELAY4, OUTPUT);
    
    // Set button pin as INPUT  
    pinMode(BUTTON_PIN, INPUT_PULLUP);
    
    // Initialize relays to OFF  
    digitalWrite(RELAY1, LOW);
    digitalWrite(RELAY2, LOW);
    digitalWrite(RELAY3, LOW);
    digitalWrite(RELAY4, LOW);
    
    displayMenu();
}

void loop() {
    // Read the button state  
    bool buttonState = digitalRead(BUTTON_PIN);
    
    // Check if the button state has changed  
    if (buttonState != lastButtonState) {
        lastDebounceTime = millis();
    }

    // If the button state has been stable for longer than the debounce delay  
    if ((millis() - lastDebounceTime) > debounceDelay) {
        // If the button is pressed (LOW)
        if (buttonState == LOW) {
            currentMenu = (currentMenu + 1) % totalMenus; // Go to the next menu  
            displayMenu();
            activateRelay();
        }
    }
    
    lastButtonState = buttonState;
}

void displayMenu() {
    lcd.clear();
    switch (currentMenu) {
        case 0:
            lcd.print("Menu 1");
            break;
        case 1:
            lcd.print("Menu 2");
            break;
        case 2:
            lcd.print("Menu 3");
            break;
        case 3:
            lcd.print("Menu 4");
            break;
    }
}

void activateRelay() {
    // Turn off all relays first  
    digitalWrite(RELAY1, LOW);
    digitalWrite(RELAY2, LOW);
    digitalWrite(RELAY3, LOW);
    digitalWrite(RELAY4, LOW);
    
    // Activate the corresponding relay based on the current menu  
    switch (currentMenu) {
        case 0:
            digitalWrite(RELAY1, HIGH);
            break;
        case 1:
            digitalWrite(RELAY2, HIGH);
            break;
        case 2:
            digitalWrite(RELAY3, HIGH);
            break;
        case 3:
            digitalWrite(RELAY4, HIGH);
            break;
    }



}

Het zal wel simpel zijn, maar ik zie niet hoe ik dit op kan lossen...

Dank vast en groet.
Bram

Zo te zien loopt hij direct door naar het einde van het menu, zodra de debounce is afgelopen. Er ontbreekt een test op button los.

Dat kun je op veel manieren oplossen.
Bijvoorbeeld zo:


void loop() 
{
   // Read the button state  
   bool buttonState = digitalRead(BUTTON_PIN);

   if (buttonState == LOW)
   {  switch(lastButtonState)
      {  case 0:
         {  lastDebounceTime = millis();
            lastButtonState  = 1;
            break;
         }
         case 1:  
         {  if ((millis() - lastDebounceTime) > debounceDelay)
            {  currentMenu = (currentMenu + 1) % totalMenus; // Go to the next menu  
               displayMenu();
               activateRelay();
               lastButtonState = 2;
            }
            break;
         }
         case 2:
         {  break;
         }
      }
   }
   else
   {  lastButtonState = 0;
   }
}

[Bericht gewijzigd door deKees op (75%)]

Hi deKees,

Ik heb er al weer wat foutjes en aanpassingen bij.

Mijn menu begint nu netjes bij het eerste menu en ook de relais(LED) uitgangen staan nu goed bij het opstarten.

Ik heb de code voor de AI een beetje aangepast en bij die code die ik niet gebruik stond onder in de loop "delay(200);"
Met deze delay in de code werkt de menuknop normaal, maar 200mSec delay is natuurlijk niet normaal. :-)

Ik was al aan het zoeken hoe het goed opgelost kan worden, ik heb een simpele library die veel van drukknopjes uit handen neemt.
Alleen zal ik moeten testen of het in deze code past.

Groet,
Bram

Ja, delay 200 werkt ook ongeveer. Dan krijg je zelfs een auto-repeat cadeau. :)

Hi deKees

Ik krijg een error en die is zichtbaar in het onderstaande klikbare plaatje.

https://www.bramcam.nl/NA/NA-HF-Doosje-Oven/Button-Menu-01-Klein.pngs

Groet,
Bram

Hi de Kees,

Ontbreekt er in jouw stukje code niet "case 3"

Groet,
Bram

Je zult een int van je LastButtonState moeten maken, een boolean kan niet gebruikt worden voor switch.

Dat zegt de foutmelding ook.

Ik denk dat het sneller gaat om die paar regels nieuw in te kloppen... :+

Hi,

De delay is er nu uit en dit is nu aangepast: int lastButtonState = HIGH;
En dat werkt nu zonder problemen.

Verder de kleuren van het display aangepast aan het men nummer, ieder menu een eigen kleur.
Dit alleen om te zien of het werkt, de kleur gaat de status aangeven, groen is b.v. op temperatuur en geen fouten,
Rood is dan een fout situatie en/of de oven nog niet op temperatuur, Blauw al de oven aan het opwarmen is en b.v oranje als de oven binnen 2C van de temperatuur is aangekomen.

Ik moet nog een opzetje maken hoe ik het een en ander ga uitvoeren zonder dat het een kermis attractie wordt. :)

Dank in ieder geval, ik kan weer even verder met functies toevoegen.

Groet,
Bram

Hi,

Ik begrijp een deel nog niet van de code die deKees hier plaatste voor het debouncen en het doorlopen van de menu pagina's.

Het gaat om deze regel:


currentMenu = (currentMenu + 1) % totalMenus; // Go to the next menu

Het is mij duidelijk dat als de debounce achter de rug is, dat de conter voor het menu met 1 warde omhoog gaat of weer naar het begin.

Maar wat doet dit nu => % totalMenus

In een van mijn Arduino C boeken wordt er gesproken over een Modulo operator %
En er is wat uitleg over een deelfactor en wat de restwaarde is.

Dit kan ik niet rijmen met wat ik denk dat deze regel doet en ik denk het volgende menu item 1 t/m 4 doorlopen en dan weer na 4 doorspringen naar menu nummer 1.

Kunnen jullie wat uitleg hierover geven?

Dank en groet,
Bram

Modulo funktie is inderdaad : integer deling en alleen de rest overhouden.

Dus 'modulo 4' zorgt ervoor dat het resultaat altijd in de range 0 - 3 valt.

Hi deKees,

Als het currentMenu nu 4 is en totalMenus is ook 4, wat is de actie nu dat bij de volgende drukknop actie currentMenu weer 1 wordt.

switch en case
Nog iets wat ik niet zag/begreep is, switch en de case stapjes er onder.
Omdat ik op meerdere plekken "case" zag staan, dacht ik dat de case in het uitlezen van de drukknop code,
het zelfde was als die van de relais en de display functie.
De case hoort bij de switch functie, het is dus steeds een andere case, case display is niet dezelfde als relais of drukknop.
Dat is nu ook weer duidelijk, weer wat geleerd.

Als het currentMenu nu 4 is en totalMenus is ook 4, wat is de actie nu dat bij de volgende drukknop actie currentMenu weer 1 wordt.

Het currentMenu wordt nooit 4. Die heeft wel 4 standen maar loopt van 0 t/m 3.

3 + 1 = 4
4 / 4 = 1, rest 0
Dus ((3 + 1) modulo 4) = 0

En switch wordt gebruikt om een enkele variabele te testen op meerdere mogelijkheden. Dus na de switch staat de variabele die getest wordt, en dan een aparte case voor elke waarde die je wilt testen. En dan vooral niet de break vergeten aan het eind van de case-blok, anders loopt de code gewoon door in de volgende case. Soms is dat wel handig, vaak ook niet.

Hi,

Nog een probleempje, voor het tunen van de P.I.D. regelaar wil ik de drie waarden op een apart schermpje hebben en ik ga ze aanpassen met drie encoders.

Dit zodat ik zonder steeds de code in de software moet uploaden kan uitvinden wat goed is voor het gebruikte oventje.
Daarna kunnen de drie waarden vast in de P.I.D. software worden opgenomen.

Waar loop ik nu tegen aan.
Ik heb van DFRobot een 2x16 LCD display met een drie kleuren achtergrond verlichting, RGB LED dus.

Ik wil met een extra i2C 2x16 LCD scherm de waarden van de P.I.D. in beeld brengen.
De twee schermen hebben ieder hun eigen driver, dus daar zit denk ik geen conflict.
Maar beide schermen maken gebruik van lcd.clear, lcd.setCursor, lcd.print enz.

Hoe kan ik die twee displays uit elkaar houden wat opdrachten betreft.
Hieronder een stukje proefcode dat ik gebruikte om het extra i2c LCD scherm werkte.


/* Demonstration sketch for PCF8574T I2C LCD Backpack 
Uses library from https://bitbucket.org/fmalpartida/new-liquidcrystal/downloads GNU General Public License, version 3 (GPL-3.0) */
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
 
LiquidCrystal_I2C  lcd(0x3f,2,1,0,4,5,6,7);            // 0x27 is the I2C bus address for an unmodified backpack
 
void setup()
{
  // activate LCD module
  lcd.begin (16,2);                                    // for 16 x 2 LCD module
  lcd.setBacklightPin(3,POSITIVE);
  lcd.setBacklight(HIGH);
}
 
void loop()
{
  lcd.home ();                                        // set cursor to 0,0
  lcd.print("T1=23,6 T2=45,7"); 
  lcd.setCursor (3,1);                                // go to start of 2nd line
  lcd.print("Ook Leuk!");
  delay(1000);
  lcd.setBacklight(LOW);                             // Backlight off
  delay(250);
  lcd.setBacklight(HIGH);                            // Backlight on
  delay(1000);
}

Hoe pas ik b.v. van de code die hierboven staat zo aan, zodat deze lcd.xxx van het hoofdscherm niet in de weg zit.

Dank en groet,
Bram

Hi,

Deze keer snel wat gevonden, door dit te doen LiquidCrystal_I2C "lcd2"
En verder alle data voor het tweede scherm dus lcd2 te gebruiken.

Dat ga ik er nu even inplakken en kijen of het echt werkt.

Groet,
Bram

Hi,

Even laten zien wat devolgende stap is in het over project.
Dat is een stukje code om de kleur van mijn DFRobot display te bepalen.

Ik wil dus kunnen opgeven dat de achtergrond kleur b.v. Rood is, dat is 255, 000, 000.
Dus ik ga een lijstje maken van een aantal kleuren die ik ga gebruiken met de daarbij behorende RGB waarden.

Dat is voor het aantal kleuren dat ik ga gebruiken eigenlijk onzin, maar je leert pas een beetje programmeren als je het ook toepast.

Dus het onderstaande heb ik in elkaar gefrut om de RGB waarde op het DFRobot display te kunnen aflezen...
De debug code van deKees om de waarde van de drie encoders in de serial monitor af te lezen staat nog steeds in in de code voor deze test is dat niet van belang
Ik gebruik een test bestand dat is meegelevert met het drie voudig i2c encoder printje, hartstikke handig trouwens dat printje.
Heb alleen nog wat goede encoders nodig, de gene die ik hier op voorraad had, was de pinning anders dan het printje gebruikte.
Vandaar de draden aan de encoders.


#include <I2C_Rotary_003.h>
#include "DFRobot_RGBLCD1602.h"

// Create an instance of the DFRobot LCD  
DFRobot_RGBLCD1602 lcd(/*RGBAddr*/0x60 ,/*lcdCols*/16,/*lcdRows*/2);  //16 characters and 2 lines of show


// Declare 3 rotary objects.
// - All use the same I2C address
// - But different switch nrs 
I2C_Rotary Rotary_SW1(0xA0, 1);  // SW1
I2C_Rotary Rotary_SW2(0xA0, 2);  // SW2
I2C_Rotary Rotary_SW3(0xA0, 3);  // SW3

void setup() 
{
   Wire.begin();
   Serial.begin(38400);


    // Setup DFRobot LCD
    lcd.init();
    lcd.setRGB(255, 255, 255);

   Serial.println();
   Serial.println( F("Turn a rotary an see what happens") );
   Serial.println();
   
   // Set parameters for Rotary_SW1
   Rotary_SW1.m_MinValue =   0;
   Rotary_SW1.m_MaxValue = 255;
   Rotary_SW1.m_StepSize =   1;
   Rotary_SW1.m_Value    =   0;
   Rotary_SW1.SetDirection(1);
   Rotary_SW1.SetAccelleration(1);

   // Set parameters for Rotary_SW2
   Rotary_SW2.m_MinValue =   0;
   Rotary_SW2.m_MaxValue = 255;
   Rotary_SW2.m_StepSize =   1;
   Rotary_SW2.m_Value    =   0;
   Rotary_SW2.SetDirection(1);
   Rotary_SW2.SetAccelleration(1);

   // Set parameters for Rotary_SW3
   Rotary_SW3.m_MinValue =    0;
   Rotary_SW3.m_MaxValue =  255;
   Rotary_SW3.m_StepSize =    1;
   Rotary_SW3.m_Value    =    0;
   Rotary_SW3.SetDirection(1);
   Rotary_SW3.SetAccelleration(1);
}


void loop() 
{
   delay(100);

   // Scan status of the rotary buttons:
   byte Button1 = Rotary_SW1.Button();
   byte Button2 = Rotary_SW2.Button();
   byte Button3 = Rotary_SW3.Button();

   // Scan status of the rotary clicks:
   int  Clicks1 = Rotary_SW1.Clicks();
   int  Clicks2 = Rotary_SW2.Clicks();
   int  Clicks3 = Rotary_SW3.Clicks();


    lcd.clear();
 //   lcd.setRGB(255, 255, 255);
    lcd.setCursor(2, 0);
    lcd.print("NoiseAmp ASD");

    lcd.setCursor(0, 1);
    lcd.print("R");
    lcd.print(Rotary_SW1.m_Value, 0);

    lcd.setCursor(5, 1);
    lcd.print("G");
    lcd.print(Rotary_SW2.m_Value, 0);

    lcd.setCursor(10, 1);
    lcd.print("B");
    lcd.print(Rotary_SW3.m_Value, 0);

    lcd.setRGB(Rotary_SW1.m_Value, Rotary_SW2.m_Value, Rotary_SW3.m_Value);
    

   // Just write the result to the serial port
   if(Button1)
   {  Serial.print( F("SW1 : Button = ") );
      Serial.println( Button1 );
      Serial.println();
   }
   if(Button2)
   {  Serial.print( F("SW2 : Button = ") );
      Serial.println( Button2 );
      Serial.println();
   }
   if(Button3)
   {  Serial.print( F("SW3 : Button = ") );
      Serial.println( Button3 );
      Serial.println();
   }
   
   if(Clicks1)
   {  Serial.print( F("SW1 : Clicks = ") );
      Serial.println( Clicks1 );
      Serial.print( F("SW1 : Value  = ") );
      Serial.println( Rotary_SW1.m_Value);
      Serial.println();
   }
   if(Clicks2)
   {  Serial.print( F("SW2 : Clicks = ") );
      Serial.println( Clicks2 );
      Serial.print( F("SW2 : Value  = ") );
      Serial.println( Rotary_SW2.m_Value);
      Serial.println();
   }
   if(Clicks3)
   {  Serial.print( F("SW3 : Clicks = ") );
      Serial.println( Clicks3 );
      Serial.print( F("SW3 : Value  = ") );
      Serial.println( Rotary_SW3.m_Value);
      Serial.println();
   }
}

Hieronder is de contraptie te zien de ik heb samen gesteld uit de volgende onderdelen:
Arduino Nano op een aansluitbordje.
De 3x rotary Encoder print van deKees met bedrade encoders omdat de pinning dus anders is van deencoders die ik beschikbaar had.
En het DFRobot 2x16 LCD met RGB achtergrond verlichting.

Ik heb een beetje gespeeld met de camera om het display leesbaar te maken en de rest nog redelijk belicht is, hier onder een paar kleuren.
Oja, de encoder met de knop is een test om de dynamiek van de versnelling van de encoders te testen, dat wordt bepaald doordeze regel: Rotary_SW3.SetAccelleration(1)

https://www.bramcam.nl/NA/NA-HF-Doosje-Oven/Display-Kleur-Code-01.png
.

De waarde voor oranje/rood
https://www.bramcam.nl/NA/NA-HF-Doosje-Oven/Display-Kleur-Code-02.png
.

Hard rose?
https://www.bramcam.nl/NA/NA-HF-Doosje-Oven/Display-Kleur-Code-03.png
.

En deze waarde komt in de buurt van wit, blauw is wat dominant in dit display.
Ik moest hiervoor wat meer ruimte verlichting aanzetten anders was de witbalans niet goed genoeg van mijn telefoon.
https://www.bramcam.nl/NA/NA-HF-Doosje-Oven/Display-Kleur-Code-04.png
.

Nu kan ik weer verder met de volgende stukjes code.

Groet,
Bram