ESP32-C6-Zero, i2c, SPI hoe te gebruiken?

blackdog

Golden Member

Hi,

Ik heb met een bestelling een ESP32 Microcontroler laten mee komen, zie het plaatje hieronder.
Het wordt aangeboden door WaveShare en dit is de product pagina.

https://www.waveshare.com/wiki/ESP32-C6-Zero#If_the_Installation_Fails

https://www.bramcam.nl/Diversen/ESP32-C6-Zero-01.png
.

Ik gebruik de IDE interface om dit product te kunnen programmeren versie 2.3.3 en met de optie voor de controller => ESP32C6 Dev Module
kan ik een aantal simpele lijnen code laten werken, zoals de vier kleuren LED.

Nu wou ik ook testen met i2c en/of SPI, als je kijkt op de pagina van WaveShare dan kan je zien dat eigenlijk alle aansluitingen voor alles gebruikt kunnen worden dit op de TX en RX aansluitingen na.
Mooi die handigheid van zelf aangeven hoe je het wilt hebben!

Maar uhhh, hoe programeer je b.v. de i2c aansluitingen, dus b.v. sda op 21 en sdc op 22.
Hoe geef je dit aan in de code?
Dit ook voor SPI, er is helemaal in de berg voorbeelden van de ESP32 die ik heb, geen i2c configuratie code te vinden.

Heeft een van jullie dit al eens uitgevogeld en wil je dit met mij delen? ;)

Dank,
Bram

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.

Gebruik je Arduino ide?

Voor Arduino hier 2 voorbeelden, uit de AI toverdoos. Ik heb de controlelr hier niet liggen, dus je zal zelf moeten checken of het werkt.

Hier I2C


#include <Wire.h>

// Define custom I2C pins
#define I2C_SDA_PIN 21  // Change to your desired SDA pin
#define I2C_SCL_PIN 22  // Change to your desired SCL pin
#define I2C_FREQ 100000 // Set I2C frequency to 100kHz (default)

// Define the I2C address of the slave device (e.g., 0x3C for an OLED)
#define SLAVE_ADDRESS 0x3C

void setup() {
  Serial.begin(115200);       // Start serial communication at 115200 baud
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN, I2C_FREQ); // Initialize I2C with custom SDA, SCL, and frequency
  Serial.println("I2C Master on ESP32C6 initialized with custom pins.");
}

void loop() {
  Wire.beginTransmission(SLAVE_ADDRESS);  // Begin communication with the slave device
  Wire.write(0x00);                       // Example: sending a command byte (customize as needed)
  byte error = Wire.endTransmission();    // End transmission and get error code

  if (error == 0) {
    Serial.println("Successfully communicated with slave device.");
  } else {
    Serial.print("Error during communication, code: ");
    Serial.println(error); // Print error code
  }

  delay(1000); // Delay for 1 second before next transmission
}

Of SPI:


#include <SPI.h>

// Define custom SPI pins
#define SPI_SCLK 18  // Custom SCLK pin
#define SPI_MOSI 23  // Custom MOSI pin
#define SPI_MISO 19  // Custom MISO pin
#define SPI_CS   5   // Custom Chip Select (CS) pin

// Define custom SPI frequency (e.g., 1 MHz)
#define SPI_FREQUENCY 1000000  // Frequency in Hz

// Define SPI settings with custom frequency, MSBFIRST, and SPI mode 0
SPIClass spiCustom(VSPI); // Use VSPI for flexibility (HSPI can also be used)
SPISettings settings(SPI_FREQUENCY, MSBFIRST, SPI_MODE0);

void setup() {
  Serial.begin(115200);

  // Initialize the SPI bus with custom pins
  spiCustom.begin(SPI_SCLK, SPI_MISO, SPI_MOSI, SPI_CS);
  pinMode(SPI_CS, OUTPUT);  // Set the CS pin as an output

  Serial.println("SPI Master on ESP32C6 initialized with custom pins and frequency.");
}

void loop() {
  uint8_t dataToSend = 0x42;  // Example byte to send
  uint8_t dataReceived;

  // Start SPI communication with custom settings
  spiCustom.beginTransaction(settings); // Begin with the custom frequency and settings
  digitalWrite(SPI_CS, LOW);            // Select the slave device by setting CS low

  // Transfer data
  dataReceived = spiCustom.transfer(dataToSend); // Send data and receive response

  digitalWrite(SPI_CS, HIGH);           // Deselect the slave device by setting CS high
  spiCustom.endTransaction();           // End SPI transaction

  // Print the received data
  Serial.print("Data received: 0x");
  Serial.println(dataReceived, HEX);

  delay(1000); // Delay for 1 second before the next transmission
}

PE2BAS

ik heb recent ook een I2C gebruikt op een ESP8266 en idd ook geen i2c config.
blijkbaar zit het dan in de library ingebakken dat die de I2C adressen moet gebruiken. of via de ide en de config als die op ESP staan

@hardbass. dit is één van mijn werkende programmas, niks I2C in te vinden. zowel die wire als ADS hebben gewoon 'begin' staan zonder adres.
de ADS is zeker een I2C aansluiting en zou adres 0x48 hebben

#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

#include <Wire.h>
#include <ADS1X15.h>

#define inverter 15 //relais1
#define charger 13 //relais2

ADS1115 ads;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Create an Event Source on /events
AsyncEventSource events("/events");


// Initialize WiFi
void initWiFi() {
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    Serial.println();
    Serial.print("Connecting to WiFi ..");
    while (WiFi.status() != WL_CONNECTED) {
        Serial.print('.');
        delay(1000);
    }
    Serial.println(WiFi.localIP());
}



void initADS() {
  Serial.println("ADS started");
  Wire.begin();
  ads.begin();
  //  ADS.setGain(0);      //  6.144 volt
  //  ADS.setDataRate(7);  //  0 = slow   4 = medium   7 = fast
  //  ADS.setMode(0);      //  continuous mode
  //  ADS.readADC(0);      //  first read to trigger
}

in één van de examples staat namelijk dit

//  choose your sensor
//  ADS1013 ADS(0x48);
//  ADS1014 ADS(0x48);
//  ADS1015 ADS(0x48);
//  ADS1113 ADS(0x48);
//  ADS1114 ADS(0x48);
ik hou van werken ..., ik kan er uren naar kijken
blackdog

Golden Member

Hi hardbass, :-)

Ik schreef dit : Ik gebruik de IDE interface om dit product te kunnen programmeren versie 2.3.3.
Dus ja de Arduino interface versie 2.3.3

Ik denk regelmatig dat ik duidelijk genoeg ben, maar dat is dus niet zo. :+
Weer een leermomentje voor mij.

Groet,
Bram

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.

Op woensdag 30 oktober 2024 13:00:57 schreef blackdog:
Hi hardbass, :-)

Ik schreef dit : Ik gebruik de IDE interface om dit product te kunnen programmeren versie 2.3.3.
Dus ja de Arduino interface versie 2.3.3

Ik denk regelmatig dat ik duidelijk genoeg ben, maar dat is dus niet zo. :+
Weer een leermomentje voor mij.

Groet,
Bram

Ik had al zoon vermoeden dat het om Arduino ging. IDE betekent trouwens niet arduino. Er zijn meerdere IDE's op de markt, een daarvan is arduino.

@fcapri, nu snap ik het. Dat is wel verwarrend inderdaad. Beetje het nadeel van arduino is dat dit soort details ineens verborgen zijn. Die ADS1115 library zal onderwater i2c gebruiken vermoed ik. Je moet dan in de documentatie van die lib kijken hoe je pinnen kan veranderen.

[Bericht gewijzigd door hardbass op woensdag 30 oktober 2024 13:04:38 (20%)

PE2BAS
blackdog

Golden Member

hardbass en fcapri,

Dank voor de antwoorden!
hardbass, dat krijg je er van als ik alleen de Arduino IDE gebruik, dan is dat mijn wereld, vandaar het leermomentje. ;)

Ik ga even testen met jullie code.

Groet,
Bram

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.

Op woensdag 30 oktober 2024 13:02:35 schreef hardbass:
Je moet dan in de documentatie van die lib kijken hoe je pinnen kan veranderen.

het kan ook gewoon de IDE zijn die het weet.
alles wat I2C is, dat de compiler het zelf naar de I2C pinnen programmeert. immers moet je in de ide software ook gaan zeggen op welke poort die zit, en welke hardware je hebt (arduino nano 328p dan veranderen naar generic ESP8266 module). dit weet dan vanzelf waar de I2C of SPI pinnen zitten.

het rare is dat ik ook geen adres mee geef. ben eens benieuwd als ik nu 2 of 3 devices op I2C aansluit hoe die dan de addressering doet. de ADS zit misschien vast op 0x48 maar tal van apparaten kan je met jumpers instellen op andere adressen

ik hou van werken ..., ik kan er uren naar kijken

Het framework lost dat op, net als dat je Serial.xxx kan doen. Het framework snapt dat Serial dan een specifike seriele port is op je device. Als je een apparaat seleceteerd in de IDE wordt het framework juist geconfigureerd.

Dus er zullen 'default' een aantal dingen beschikbaar zijn die je zo kan gebruiken. Handig voor beginners, niet als je exact wilt weten hoe het zit.

Die Serial kan je ook weer aanpassen dacht ik zodat hij andere pinnen etc gebruikt. Wellicht kan dat ook met de 'default' i2c bus

PE2BAS
blackdog

Golden Member

Hi,

Even wat feedback geven, ik denk dat het altijd leuk om te zien, dat als je tips geeft, deze ook werken. ;)

Links de Nano die ik gebruikt heb met wat simpele code om te zien of het i2c adres van het display klopt, en ja dat was correct.
Daarna de code opnieuw gebruikt, maar dan met het stukje van hardbass voor de i2c instellingen.
Er staat nog wat code onder van de RGB LED, maar dat zit niet in de weg.

Oja, nog een puntje waarom je je soms gek zoekt, in de handleiding van deze ESP32C6 microcontroler geven ze aan dat je naar een folder moet in de settings van de Arduino IDE
die verstopt zit in de normaal niet zichtbare "AppData" in je gebruikers profiel van windows.
Dat blijft hij dan standaard onthouden terwijl het "normaal" in je mijn ~\documenten\Arduino is.
Als dat folderpath dus niet wordt terug gezet, kan hij Allerlei drivers natuurlijk niet vinden.
Nu kan ik de folder "AppData" dromen, dus snel genoeg viel het kwartje waar het mis ging.

OK, hiermee heb ik getest en het werkt.


//    Arduino IDE 2.3.3
//    Board Keuze = ESP32C6 Dev Module

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

// Define custom I2C pins
#define I2C_SDA_PIN 21                                                   // Change to your desired SDA pin
#define I2C_SCL_PIN 22                                                   // Change to your desired SCL pin
#define I2C_FREQ 100000                                                  // Set I2C frequency to 100kHz (default)

LiquidCrystal_I2C  lcd(0x3F,2,1,0,4,5,6,7);                              // 0x27 is the I2C bus address for an unmodified backpack
 
void setup() {
  Serial.begin(115200);                                                  // Start serial communication at 115200 baud
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN, I2C_FREQ);                        // Initialize I2C with custom SDA, SCL, and frequency
  

  // activate LCD module
  lcd.begin (16,2);                                                      // for 16 x 2 LCD module
  lcd.setBacklightPin(3,POSITIVE);
  lcd.setBacklight(HIGH);
  lcd.clear();
}



void loop() {

  lcd.home ();                                                            // set cursor to 0,0
  lcd.print("Thanks!"); 
  lcd.setCursor (0,1);                                                    // go to start of 2nd line
  lcd.print("hardbass, fcapri");


#ifdef RGB_BUILTIN
  digitalWrite(RGB_BUILTIN, HIGH);                                        // Turn the RGB LED white
  delay(100);
  digitalWrite(RGB_BUILTIN, LOW);                                         // Turn the RGB LED off
  delay(100);

  rgbLedWrite(RGB_BUILTIN, RGB_BRIGHTNESS, 0, 0);                         // Red
  delay(500);
  rgbLedWrite(RGB_BUILTIN, 0, RGB_BRIGHTNESS, 0);                         // Green
  delay(500);
  rgbLedWrite(RGB_BUILTIN, 0, 0, RGB_BRIGHTNESS);                         // Blue
  delay(500);
  rgbLedWrite(RGB_BUILTIN, 0, 0, 0);                                      // Off / black
  delay(500);
#endif
}

Het bewijs is de tekst op het display, i2c heb ik op pinnen 21 en 22 gezet.
Trouwens wel heel mooi wat die ESP32C6 allemaal kan, WiFi-6 op de 3.5GHz band, Zigbee en BLE, 12-Bit ADC en nog veel meer.

https://www.bramcam.nl/Diversen/ESP32-C6-Zero-02.png
.

Dank en groet,
Bram

[Bericht gewijzigd door blackdog op woensdag 30 oktober 2024 15:49:12 (13%)

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.

leuk dat het werkt, als ik ellende heb kan ik hier ook eens terug lezen.

ben zo langzaam aan ook aan het overstappen van arduino naar esp.
nano is heel handig voor kleine dingen, maar als je ermee op een netwerk moet, moet je allerhande shields enzo gaan bijgebruiken. en echt snel zijn ze niet.

werk nu al een tijdje met de simpele esp8266 en die kan vele malen meer, het geheugen is groter, standaard wifi erop...
alleen jammer van die ene analoge input

ik hou van werken ..., ik kan er uren naar kijken

Leuk om te zien dat je het aan de gang hebt. Dat is dan weer het voordeel van Arduino, de tijd tot resultaat is vrij kort.

Wat betreft die chippies van espressif. Ik vind die zelf ook erg leuk inderdaad. Goedkoop en ramvol met features.

Je hebt ze trouwens ook nog in kleinere varianten, als in fysiek kleinere pcb. Ik probeer het te vinden, maar die printjes zijn ook verkrijgbaar met allerlei andere processoren.

Ah, de esp32 xiao van seeed studio. Die heb je in allerlei smaken. Inclusief varianten die ook een batterij backup ding hebben. (zelf geen ervaring mee trouwens)

PE2BAS
blackdog

Golden Member

Hi,

De post met de met de code even opgeschoond en het plaatje bijgewerkt. ;)

hardbass, even opgezocht, eigenlijk is kleiner niet mogelijk dan de esp32 xiao, wil je tenminste op een normalen manier nog iets aan kunnen sluiten.

Groet,
Bram

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.

Haha, mooie aanpassing. (niet per ongelijk vergeten te verwijderen als het straks bij een klant staat of zo.)

Die xiao lijkt me best een grappig printje, al heb ik er op het moment geen usecase voor. Wellicht dat dit nog eens veranderd.

PE2BAS
blackdog

Golden Member

Hi,

Om niet weer een nieuw topic te maken en het HF Oven topic met niet te veel experimenteer spul vol te duwen, plaats ik hier maar even mijn testen met twee displays op één controler.

Het doel hiervan is als ik de PID software in werking zet, ik de drie waarden P, I en D apart kan instellen zonder steeds de software opnieuw te moeten uploaden.

De bedoeling is dus deze waarden "Live" te kunnen aanpassen.
Hiervoor komt er dus een tweede display tijdelijk aan de controler te hangen en wat ik onder meer gisteren vandaag heb gedaan is uitzoeken hoe ik dit goed werkend kan krijgen.

Ik test dit nu even met een Arduino Nano en met het DFRobot 2x16 met RGB achtergrond dislay en het tweede 2x16 LCD display met de gele achtergrond.
Op dit tweede gele LCD komt dus de PID waarde en b.v. de "window" tijd, zeg maar de PWM frequentie.

Het geheel is nu dus werkend en dit is de code hoe ik de twee displays aan stuur.
Wat er op de displays staat is een beetje willekeur, maar daar zit ook niet de moeilijkheid, dat is gewoon de goede variabele in de lcd.print,
of voor het tweede tijdelijke display lcd2.print te zetten.

Nog niet opgeschoonde code.


#include <I2C_Rotary_003.h>
#include "DFRobot_RGBLCD1602.h"
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#include "DHT.h"

#define DHTPIN 4 
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

LiquidCrystal_I2C  lcd2(0x3F,2,1,0,4,5,6,7); // 0x3F is the I2C bus address for an unmodified backpack


// 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);

  lcd2.begin (16,2); // for 16 x 2 LCD module
  lcd2.setBacklightPin(3,POSITIVE);
  lcd2.setBacklight(HIGH);


    // 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(0);
   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(0);
   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(0);
   Rotary_SW3.SetAccelleration(1);

     dht.begin();
}


void loop() 
{
   delay(100);

  
lcd2.home (); // set cursor to 0,0
lcd2.print(dht.readHumidity(), 1); 
lcd2.setCursor (0,1);        // go to start of 2nd line
lcd2.print(dht.readTemperature(), 1);




   // 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();
   }
}

Het DFRobot kleuren schermpje geeft nu de PID waarden weer en voor het tweede scherm staat op de eerste regel de lucht vochtigheid waarde en op de tweede regel de temperatuur.
Beide waarden komen uit een AM2302 sensor, dit is de eerste keer dat ik deze echt test, ik heb er vier op voorraad liggen, ik denk een jaar of zeven geleden gekocht en ze werken nog. :-)
Bagger traag trouwens, maar dit is verder niet zo van belang, ik wou iets "live" op het display hebben en ik kon niet even snel een andere sensor op mijn i2c bus prikken.
De AM2302 gaat verder helemaal niet gebruikt worden in dit project.

Plaatje van heden avond van de contraptie die hier voor mij op tafel ligt.
https://www.bramcam.nl/Diversen/Test-Dual-Display-01.png
.

Gisteren werd ik natuurlijk weer getergt door slechte verbindingen, doordat de displays redelijk wat stroom trekken, geeft dit aardig wat storingen en uitvallen van de verlichting
en blokkeren van de code.
Een deel heb ik maar vast gesoldeerd waar het kon, en vandaag hierdoor geen uitval meer gehad.

Nu ik steeds meer stukjes code werkend krijg moet ik nu gaan nadenken hoe ik dit met timers ga beheren en uitlezen.
Hoe vaak ga ik het hoofd display verversen.
Hoe vaak lees ik de omgeving temperatuur uit.
Hoe vaak lees ik de Luchtvochtigheid uit.
Hoe vaak lees ik de oven temperatuur uit + middeling enz.
Meting van de stroom en de spanning, berekening van het oven vermogen enz.
Misschien ga ik wat logggen, als ik dat doe dan b.v. naar een van mijn servers enz (Low Priority eis)

Ik denk dat ik op het grote Wite Board bij het bedrijf van mijn broer maar eens wat ga kledderen.
Dit heb ik altijd prettig gevonden om zaken wat duidelijker te maken voor mijn brein

Hoe doen jullie dat, ik bedoel dan een structuur maken voor code in grote lijnen, zodat je later de weg nog terug kan vinden.

Groet,
Bram

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.

Ik gebruik drawio en maak een flowchart. Daarnaast gebruik ik in de software statemachines en geen delays.

https://learn.adafruit.com/multi-tasking-the-arduino-part-1/using-mill…

It's the rule that you live by and die for It's the one thing you can't deny Even though you don't know what the price is. It is justified.

ik maak altijd een ruwe samenvatting wat ik wil, en dit staat dan ook zo in blokken in mijn programma.
bv
display: elke seconde,
meting: elke 100mS en na 10x een gemiddelde eruit (waarde veranderd dus elke seconde wat dan weer overeenkomt met het display)

jouw loop is gigantisch groot, ik probeer altijd 10-15regels max te gebruiken, is het meer dan splits ik op/
die "lcd." regels zou ik er allemaal uitgooien en een subroutine maken met update_display();

en dan met een timer bv elke 500ms roep je dan die update display op.

dit is de loop van mijn 'dashboarddisplay' in mijn autos. hier zaten geen timers, elke seconde doet die een update van een scherm, en daartussen doet die metingen. hoefde niks anders te doen, dan maar simpel een delay

void loop(void) {
  // picture loop  
  u8g2.firstPage();  
  do {
    u8g2_prepare();
      draw_screen32w();
  } while( u8g2.nextPage() );
  
  readInput();                        // read analog inputs fuel-voltage-temp
  calculateNumbers();
  printSerial();
  litersold = liters;
  delay(1000);

}

en dit is een loop van mijn serial decoder. normaal stond ook hier een 3de if tussen voor de heartbeat, maar die heb ik gewoon in de metingenloop gezet. hier nog eens elke seconde een millis waarde bijhouden enzo, terwijl ik 10metingen doe over 100ms, en elke keer ik een gemiddelde waarde bereken, geef ik een heartbeat. als die stopt, meet die niks meer en weet ik het ook wel. als mijn waarde in html hetzelfde blijft zou ik anders nooit weten of de waarde identiek is of de boel vasthangt

void loop() {
  if (millis() < lastTime) { //arduino reset
    lastTime = millis();
    measureTime = millis();
    htmlTime = millis();
    starttestTime = millis();
  }

  if ( ((millis() - lastTime) > timerDelay) && go ) { //every 5 seconds run a html update
    getal = getal + 5;
    // getal = (millis() - starttestTime) / 1000;
    if (chargemode == 1) {
      batcharge();
    } else if (invertermode) {
      battest();
    } else  if (automode) {
      autobat();
    } else {
      normalmode();
    }
    lastTime = millis();
    measureTime = millis();
  } else   if ( (millis() - measureTime) > 100 ) {    //if no 5sec past, check to run a measurement and heartbeat
    measureTime = millis();
    measureBat();
  }
  if ( (millis() - htmlTime) > 500 ) {
    htmlTime = millis();
    if (htmlqueue.length() > 1) {
      String bericht = htmlqueue.substring(htmlqueue.indexOf("{") + 1, htmlqueue.indexOf("@"));
      String ontvanger = htmlqueue.substring(htmlqueue.indexOf("@") + 1 , htmlqueue.indexOf("}"));
      htmlqueue = htmlqueue.substring((bericht.length() + ontvanger.length() + 3) , htmlqueue.length());
      events.send(String(bericht).c_str(), ontvanger.c_str(), millis());
    }
    receiver = "";
  }

}

ik heb ooit eens met threads gewerkt, maar dit vind ik altijd moeilijk werken in 'development' mode waar ik dan continue heen en weer moet gaan scrollen.
dan liever in de loop alles definieren en daar ook de tijden wanneer die iets moet doen

ik hou van werken ..., ik kan er uren naar kijken

Je kan ook overwegen om een soort taak scheduling te doen. Dat kan handig zijn als je meerdere stukken code op een vast interval wil uitvoeren. Bijvoorbeeld de 2 pid controllers moeten op een vast interval draaien, anders kloppen de delta T berekeningen niet meer. Ik heb een klein voorbeeld voor je gemaakt van zoon scheduling.


// Arduino Task Scheduler Example
// This code defines a simple task scheduler to run tasks at specific intervals.

#include <Arduino.h>

void task1(TaskContext* context);
void task2(TaskContext* context);

// Struct to define a task's attributes and control its timing
struct TaskContext
{
    void (*taskFunction)(TaskContext* context);  // Pointer to the task function
    unsigned long intervalMs = 1000;             // Default interval in milliseconds (1 second)
    unsigned long previousExecution = 0;         // Last execution timestamp in milliseconds
    unsigned long nextExecution = 0;             // Next execution timestamp in milliseconds
};

// Define the number of tasks and initialize them in an array
#define TASK_COUNT 2
TaskContext taskList[TASK_COUNT] = 
{
    {task1, 1000},    // task1 runs every 1000 ms (1 second)
    {task2, 5000}     // task2 runs every 5000 ms (5 seconds)
};

// Task function 1 - executes every interval set in the taskList
void task1(TaskContext* context)
{
    // This task is scheduled to run based on the interval defined in taskList.
    
    // Changing 'nextExecution' affects only the next run of this task:
    // Here, we delay the next execution by setting it to 5 seconds from now,
    // meaning this task will be called again in 5 seconds, regardless of the interval.
    context->nextExecution = millis() + 5000;

    // Changing 'intervalMs' updates the task's default interval permanently:
    // This means every execution of this task after the next one
    // will occur at this new interval (2.5 seconds in this case).
    context->intervalMs = 2500;
}

// Task function 2 - placeholder for another task to run
void task2(TaskContext* context)
{
    // This is a placeholder function for the second task
    // Here you would add code to perform specific operations periodically.
}

void loop()
{
    // Iterate over the task list and check if each task needs to run
    for (int i = 0; i < TASK_COUNT; i++)
    {
        unsigned long currentTime = millis();  // Get the current time in milliseconds

        // Check if it's time to execute the task
        if (currentTime >= taskList[i].nextExecution)
        {
            // Set the next execution time according to the task's interval
            taskList[i].nextExecution = currentTime + taskList[i].intervalMs;

            // Execute the task function, passing the current task's context
            taskList[i].taskFunction(&taskList[i]);

            // Update the previous execution time to the current time
            taskList[i].previousExecution = currentTime;
        }
    }
}


PE2BAS

Trouwens, op de ESP kan je ook freertos scheduling gebruiken. Echter heeft dat behoorlijk wat dingen waar je rekening mee moet houden. Dus ik zou je afraden dat te gebruiken. Voor nu zou ik alles vanuit de main aanroepen. Die scheduling uit mijn post doet dat ook.

Er is ook nog ruimte voor verbetering / uitbreiding maar ik heb de boel wat eenvoudig proberen te houden.

PE2BAS
blackdog

Golden Member

Hi,

Dank weer voor alle info!

Even dit, het laatste stukje code dat ik hier liet zien was alleen voor de data zichtbaar te maken op de twee displays, dus hoe je dat aanpakt.
De Delay in de loop, is voor het niet zo laten knipperen van de displays, dat irriteerd als het frutsel in mijn ooghoek naast mij ligt.

Per menu pagina, wil ik de "vaste tekst" één keer plaatsen en dat de waarden alleen veranderen als nodig, hier zal ik nog mee moeten gaan experimenteren of dit zinnig is en hoe te doen.

In de laatste code staat nog een heel stuk "serial print" debug code, die is natuurlijk niet meer nodig, alles werk nu voor het aansturen van de displays.
Het ging mij er dus om, te leren hoe ik twee displays ga aansturen zonder dat deze elkaar in de weg zitten en dat werkt nu.

Dan komen we aan bij jullie adviezen. ;)
Mooi, als eerste die van Roland die een link van Adafruit stuurde, een voor mij zeer leesbaar stukje over een State Machine,
deKees heeft me daar een tijd eerder ook al eens iets over laten zien.
Daar ga ik vandaag mee aan de gang met een andere Arduino Nano, daar slingeren er hier genoeg van rond.

Ik ga voor het HF Ovenproject, het houden bij de Teensy LC, anders komt er weer meer uitzoek werk bij en dat past even niet in mijn brein, heb de laatste twee weken even genoeg last van "Aura's" gehad. ;)

Wat hardbass en fcapri laten zien, bevatten if statements met timing en counters.
Ik begrijp wat de bedoeling is, maar het lezen van deze stukjes tekst, geeft niet direct inzicht in hoe/wat/waarom er iets gebeurd.

Niet zoals je mij b.v. een voeding circuits of een versterker schema laat zien, dat ik je direct de functies van de onderdelen van de schema's kan uitleggen.
Dat is het gevolg van mijn taalprobleem, wat meespeelt bij mij is onder meer dit, dat er b.v. van rechts naar links wordt gewerkt en dan weer andersom.
Zo is nu eenmaal C, C++ enz opgebouwd, ik zal het er mee moeten doen. ;)

Als je goed wil leren zal het je meestal een keer of zeven moeten worden uitgelegd/geprobeerd en het helpt bij mij, als ik namen ga geven aan de variabelen die mij aanspreken als het kwartje echt niet wil vallen.
Voorbeeld, deKees gebruikte in een stukje code b.v. % in een regel, daar had hij een goede en galante oplossing mee bedacht, maar dat heeft aardig wat breintijd gekost voor ik het door had.
Zo'n hele regel waar % in staat,valt dan in mijn hoofd onder het kopje chaos, ik begreep wel wat de bedoeling was, maar begreep de eigenschappen van de toegepast % niet.

Mijn Dyslectie zorgt er voor, dat ik nog veel meer dan een normaal persoon, het heel veel moet doen, in die kleine voor mij hapklare brokjes code.
Voor jullie info, mensen met dyslectie raken bij het lezen van een zin, zeg als ze op 60% van de tekst zijn, het voorgaande wat ze net gelezen hebben kwijt en/of het verband rammelt hun hoofd uit.

Ik pas verschillende technieken voor het onthouden toe en je kan in mijn schema's b.v. zien, dat meestal van links naar rechts opgebouwd wordt,
met zo min mogelijk kruislijnen enz. in mijn ogen een mooie structuur.

Dit wringt dan met de code die ik probeer te begrijpen die heen en weer springt(daar kan een hele goede rede voor zijn).
Zo'n 30 jaar geleden schreef ik uitgebreide Batch code met "Go To's" en dat zag er verder mooi gestructureerd uit kan ik zeggen en werkte ook nog eens goed.
Gebouwd met de Norton Editor en daarna omgezet naar een .com bestand.
Als je tegenwoordig als je dit zo doet met Go To's, dan wordt je door de Code politie opgepakt. *grin*

Ik ga vandaag frutten met het lesje van Adafruit dat Roland liet zien en nadenken over jullie tips.

Groet,
Bram

You have your way. I have my way. As for the right way, the correct way, and the only way, it does not exist.

Ik snap het hoor, ben zelf ook dyslect, dus wat je schrijft, herken in wel. Krijg ook wel eens de opmerking 'heb je het überhaupt wel gelezen'. Maar goed, neem de tijd, als je deze Schedler te veel vindt voor nu, lekker laten doen.

Om je toch een indruk te geven, ik heb hier een voorbeeld waarbij ik alle code heb weg gelaten die niet relevant is. Zo zie je dat het een stuk minder ingewikkeld is om te gebruiken.


#define TASK_COUNT 2

TaskContext taskList[TASK_COUNT] = 
{
    {task1, 1000},    // task1 runs every 1000 ms (1 second)
    {task2, 5000}     // task2 runs every 5000 ms (5 seconds)
};

void task1(TaskContext* context)
{
	// Toggle led 1
	led1 = !led1;
}


int count = 0;
void task2(TaskContext* context)
{
	count++;
	lcd.printf(count);
}


@hieronder, klopt. Je kunt in de 'taken' beter geen delays gebruiken. Freertos lost dat wel weer voor je op. Tis ook allemaal een beetje afhankelijk van wat je wilt / kunt.

PE2BAS

Daarbij is het wel belangrijk in de gaten te houden dat elke taak niet langer mag duren dan de kortste interval tijd (in bovenstaand voorbeeld 1 seconde). Anders gaat de boel alsnog uit de pas lopen.

Fan van Samsung (en repareer ook TV's). :)

Wel een mooi voorbeeld van hardbass. Met zo een mechanisme kun je op eenvoudige manier een soort van multi-tasking voor elkaar krijgen. En dat is toch wel krachtig.

Maar toch, zie je vooral low-level code in het voorbeeld en dan heb je nog geen idee wat de applicatie nu eigenlijk doet.

Eigenlijk wil je op top-level geen low-level details, maar alleen de hoofdzaken van de applicatie.
Dan krijg je bijv een loop als :


void loop()
{
   UpdatePidSettings();
   ReadAmbientTemperature();
   ReadHumidity();
   ReadOvenTemperature();
   CalculateOvenPower();
   UpdateDisplay();
}

Dan kun je later die functies invullen en een stapje dieper in de implementatie duiken. Bijv:


void ReadAmbientTemperature()
{  
   static int State = 0;

   switch (State)
   {  case 0:
      {  if( StartAdc() )
         {  state += 1;
         }
         break;
      }
      case 1:
      {  if( ReadAdc() )
         {  CalculateTemperature();
            State = 0;
         }
         break;
      }  
   }
}

En weer, op dit niveau weet je wel dat je de A/D converter nodig hebt om de temperatuur te meten, maar je wilt nog geen low-level details van die ADC. Dat komt later wel als je die StartAdc() en de ReadAdc() functies gaat implementeren.

Uiteraard wil je wat lagen aanbrengen in je code. De tasks kunnen ook betere namen krijgen. Maar goed, t was maar een voorbeeldje natuurlijk. Tis maar wat je wil maken en bovendien, wat voor de programmeur te begrijpen valt.

De functies die in de loop staan van deKees, zouden zo de 'tasks' kunnen zijn in de scheduler.

@Blackdog, lekker proberen. Tis helemaal geen ramp als de code niet heel netjes is. Zolang jij het maar snapt. Als je ergens niet uitkomt of voorbeelden nodig hebt. Een AI kan je goed opweg helpen. Ook kan die goed uitleggen hoe bepaalde stukjes werken. Of als je een probleem beschrijft en de code geeft, kan die vaak ook aangeven waarom iets gebeurt. Ik weet niet of je dit al wist, maar je kan je code opsplitsen in meerdere bestanden. Als je dat doet dan staan alle ingewikkelde dingen niet meer direct in je code. Dat houd het overzichtelijker dan een hele grote file waar alle code in staat.

---

Wellicht nog even goed om aan te geven, de code van mij geplaatst hiervoor heeft wel een aantal 'issues'. Dit is grotendeels wel op te lossen, maar heb ik expres niet gedaan om het simpel te houden. Een paar punten die ik zo kan bedenken:

Korte Executietijd van Taken Vereist:
Taken moeten snel uitgevoerd worden, omdat de scheduler probeert om elke taak op vaste intervallen aan te roepen. Dit betekent dat taken worden uitgevoerd op basis van een tijdstip ("every second on the second"). Wanneer een taak te lang duurt, kan de scheduler deze timing niet langer compenseren, en andere taken kunnen vertraging oplopen.
Mogelijke oplossing: Een soort watchdog kan worden toegevoegd die waarschuwingen genereert als een taak te lang duurt.

Interval aanpassingen worden pas na de volgende iteratie doorgevoerd:
Wanneer je de interval van een taak aanpast, gaat deze wijziging pas in na de volgende geplande uitvoering. Dit kan verwarrend zijn als je verwacht dat de nieuwe interval meteen effect heeft.
Mogelijke oplossing: Je zou functies kunnen toevoegen aan de TaskContext struct om deze intervallen direct aan te passen, terwijl de tijdwaarden (nextExecution, previousExecution) private blijven en via de functies worden beheerd.

Geen rekening gehouden met millis() overflow:
De millis() functie in Arduino reset naar nul na een bepaalde tijd (ongeveer 50 dagen bij 32-bit Arduino’s). Deze overflow wordt momenteel niet opgevangen, wat de timing kan verstoren naarmate de scheduler langer draait.
Mogelijke oplossing: Je kan de overflow detecteren en compenseren.

Geen prioriteitsbeheer tussen taken:
In deze simpele scheduler worden alle taken in een vaste volgorde uitgevoerd zonder prioriteit. Dit kan problemen opleveren als sommige taken belangrijker of tijdkritischer zijn dan andere.

PE2BAS

als ik intressante code vind op internet, probeer ik altijd een basic versie ervan te maken voor in mijn sketchbook. dit is een demo code voor een programma dat 3leds die op verschillende tijd laat knipperen. zonder delays


int led1 = 9;
int led2 = 8;
int led3 = 7;

int led1State, led2State, led3State = LOW;             // ledState used to set the LED
unsigned long led1OldTime = 0;
unsigned long led2OldTime = 0;
unsigned long led3OldTime = 0;
const int led1Interval = 100;
const int led2Interval = 200;
const int led3Interval = 400;
const long interval = 1000;           // interval at which to blink (milliseconds)

void setup() {
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(led3, OUTPUT);
}

void loop()
{
  unsigned long currentMillis = millis();
  checkled1(currentMillis);
  checkled2(currentMillis);
  checkled3(currentMillis);
}

void checkled1(unsigned long millies){
    if(millies - led1OldTime >= led1Interval) {
       led1OldTime = millies;   // save the last time you blinked the LED
       if (led1State == LOW)    // if the LED is off turn it on and vice-versa:
         led1State = HIGH;
       else
         led1State = LOW;
       digitalWrite(led1, led1State);  //change state
    }
}

void checkled2(unsigned long millies){
    if(millies - led2OldTime >= led2Interval) {
       led2OldTime = millies;   // save the last time you blinked the LED
       if (led2State == LOW)    // if the LED is off turn it on and vice-versa:
         led2State = HIGH;
       else
         led2State = LOW;
       digitalWrite(led2, led2State);  //change state
    }
}

void checkled3(unsigned long millies){
    if(millies - led3OldTime >= led3Interval) {
       led3OldTime = millies;   // save the last time you blinked the LED
       if (led3State == LOW)    // if the LED is off turn it on and vice-versa:
         led3State = HIGH;
       else
         led3State = LOW;
       digitalWrite(led3, led3State);  //change state
    }
}

dit kan je dan ook maken dat die elke 100ms je menu checked.
elke 500ms een meting doet
elke 1s je scherm update.

gewoon voor elke taak een aparte subroutine.

ik hou van werken ..., ik kan er uren naar kijken