In arduino_webserver_post i2c-io-port integrieren

Guten Abend,
ein Arduino Mega ADK dient mir als „Hausmeister“ zur Steuerung von div Einheiten und zur Kontrolle verschiedener Signalzustände.
Dazu benötige zusätzlich ca. 70 I/O, die ich mit Hilfe des I/O-Expanders MCP23017 generieren möchte.
Als Webserver setze ich (Arduino webserver: POST Daten empfangen und warum GET vermieden werden soll) ein.
Funktioniert bisher sehr gut. Vor allem die kompakten Buttons kommen mir sehr gelegen.
Bei der Erweiterung der Buttons hat mich der Compiler, ich denke zu Recht, ausgeblockt.
Versuche an Hand folgender Programmschnipsel mein Problem aufzuzeigen:

Ohne MCP23017:
1.   const byte pinOut[] {33, 34, 35, 36, 37, 38, 39, 40};
2. steup:
  for (auto &pin : pinOut){ 
        pinMode(pin, OUTPUT);    
        digitalWrite(pin, HIGH);}
3. server:
  for (auto &pin : pinOut)
          {… };
  // Funktioniert super //

Versuch mit MCP23017:
1.  #include <Wire.h>
  #include <Adafruit_MCP23017.h>
  Adafruit_MCP23017 mcp1; // Create MCP 1
  const uint8_t addr1 = 0; // Adresse 0x20 / 0
  Quelle: https://www.nikolaus-lueneburg.de/2015/11/mcp23017-i2c-io-port-expander/
2. setup:
  mcp.begin(addr1); // Init MCP23017 at address 0x20
      for (byte i=0; i<16; i++) 
            mcp1.pinMode(i, OUTPUT);
      for (byte i=0; i<16; i++)
            mcp1.digitalWrite(i, HIGH);
  // Bis hierher funktioniert der Sketch //
3. server:
  for (auto &pin : ??? )    // ??? Hier endet meine Kunst
          {… };

Entweder ist die Anbindung der I/O des MCPs an diese Art Buttons nicht möglich
oder es gibt mindestens eine Lösung dafür.
Würde mich freuen, wenn letzteres zuträfe und mir jemand eine funktionierende Lösung
aufzeigen könnte.
Gruß
papilio

muss das nicht mcp1.begin(addr1) heissen?

Dir fehlt das Feld mcpPinOut[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; für for (auto &pin : mcpPinOut) {}.

Das gilt auch für pinMode und digitalWrite.

Sorry, war ein Übertragungsfehler. In meinem Sketch heißt das schon mcp1.begin(addr1).
Danke!

1 Like

Mit mcpPinOut[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; ergeben sich undefinierte Zustände der Buttons und lassen sich nicht definiert einschalten und undefiniert ausschalten.

McpPinOut[] {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; funktioniert, so ist es auch im Original von noiasca.

Habe nochmals verschiedene Versuche gestartet, zwei Ergebnisse (V1), (V2) möchte ich im folgenden skizzieren.

1.  #include <Wire.h>
    #include <Adafruit_MCP23017.h>
    Adafruit_MCP23017 mcp; // Create MCP
    (V1) const uint8_t mcpPinOut = 0; // Adresse 0x20 / 0  
        // ohne diesen Part schaltet die LED nicht 
    (V2) const byte mcpPinOut[] {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
        //   undefinierte Zustände der Buttons (wie oben)
        // (V1) + (V2) geht nicht - „redefinition“
2. setup:
    mcp.begin(mcpPinOut); // Init MCP23017 at address 0x20
      for (byte i=0; i<16; i++) 
          mcp.pinMode(i, OUTPUT);
      for (byte i=0; i<16; i++)
          mcp.digitalWrite(i, HIGH);
3. server:
    for (auto &pin : mcpPinOut ) 
          {… };
      // Mit V2 keine Compilermeldung
      // Mit V1 folgende Meldung:
        … In function 'void sendPage(EthernetClient&)':
        sendPage:178:22: error: 'begin' was not declared in this scope
        for (auto &pin : mcpPinOut)
                      ^~~~~~~~~
        sendPage:178:22: error: 'end' was not declared in this scope
        /pi/Arduino/webserver_receive_post_tabs_V0.09/sendPage.ino:178:22: note: suggested alternative: 'rand'
        for (auto &pin : mcpPinOut)
                      ^~~~~~~~~
                      rand
        exit status 1
        'begin' was not declared in this scope
4.void loop() 
    // Hatte/habe ich nur eingerichtet, um sicher zu stellen, dass die Hardware funktioniert
  INTERVAL(1000UL)
    mcp.digitalWrite(1, LOW);   // LED AN
  INTERVAL(3000UL)
    mcp.digitalWrite(1, HIGH);  // LED AUS

Dann haben mich Deine Programmfragmente wohl irgendwie aufs Glatteis geführt.

Einen MCP23017 an einem Nano oder Meg2560 habe ich hier rumliegen, gib mir doch bitte ein Programm zum Testen.

Sehe gerade, die Bibliothek hat ein grundlehendes Update erfahren, jetzt #include <Adafruit_MCP23X17.h>.

mach mal einen Testsketch/Erweiterung für zwei MCP23017 und stelle den ganzen Sketch hier ein. Wenn es aufgeteilt ist in mehreren Tabs, bitte in einem zip. Dann kann man mal drüber sehen. Ein paar Erweiterungen gegenüber dem Demo -Sketch wird es schon brauchen. Im Prinzip sollten zwei ineinander geschachtelte For Schleifen ja reichen. Außen für jeden MCP23017, innen für jeden Pin.

Vermutlich musst im Server tab an zwei Stellen erweitern,

a) das Parameter auslesen
b) die HTML Ausgabe

vieleicht sollte man dann eine neue Namenskonvention nehmen. Die diskreten Pins (direkt am Arduino) als pinZ und pinA bis pinP für die MCP23017 ports. Ich denke dann ist das ziemlich einfach.

Bevor ich jetzt einen Sketch zusammen bastle und noch den einen oder anderen Fehler einbaue, denke ich, ist es sinnvollen, wenn wir als Basis deinen originalen Sketch verwenden.
!!! Anscheinend darf ich als neuer Nutzer keine Dateien hochladen.
Der Sketch heißt: webserver_receive_post_tabs.ino7304226257998917226
Meine Vorlage für den MCP23017 habe ich von hier (MCP23017 I2C I/O Port Expander - NIKOLAUS-LUENEBURG.DE).

#include <Wire.h>
#include <Adafruit_MCP23017.h>

Adafruit_MCP23017 mcp1; // Create MCP 1
Adafruit_MCP23017 mcp2; // Create MCP 2

const uint8_t addr1 = 0; // Adresse 0x20 / 0
const uint8_t addr2 = 1; // Adresse 0x21 / 1
void setup() {  
  mcp1.begin(addr1);      // Start MCP 1
  mcp2.begin(addr2);      // Start MCP 2

  mcp1.pinMode(7, INPUT); // Define GPA7 on MCP1 as input
  mcp1.pullUp(7, HIGH);  // Activate Internal Pull-Up Resistor

  mcp1.pinMode(8, OUTPUT); // Define GPB0 on MCP1 as Output
  mcp2.pinMode(15, OUTPUT); // Define GPB7 on MCP2 as Output

} // End Setup

void loop() {
 // The LED on GPB0 will 'echo' the button on GPA7

  if (mcp1.digitalRead(7) == LOW)
  {
    mcp1.digitalWrite(8, HIGH);
    mcp2.digitalWrite(15, HIGH);
  }
  else
  {
    mcp1.digitalWrite(8, LOW);
    mcp2.digitalWrite(15, LOW);
  }
} // End loop

Links

Nur so als Idee:

hab nur keinen MCP hier, daher hab ich eine Dummy-Klasse geschrieben. Ist nur ganz rudimentär, akzeptiert halt die wichtigsten member functions und spaßhalber merkt sie sich den pinstatus damit ein digitalRead das zurückliefert was zuvor ein digitalWrite gesendet hat.

Nach dem der Trockentest soweit mal klappt kann man mit diesen Änderungen zwei echte MCP einbauen:

constexpr uint8_t noOfMcp = 2;

Adafruit_MCP23X17 mcp[noOfMcp];  // aktivieren

//#include "z_mcp.h"             // deaktivieren
//Dummy_MCP23X17 mcp[noOfMcp];   // deaktivieren               

const uint8_t mcpAddr[noOfMcp] {0x20, 0x21}; // address for each MCP

ACHTUNG: ich verwende die letzte Version von der Adafruit library!(!!!)

möglicherweise geht es. Viel mehr kann ich dazu nicht liefern.

webserver_receive_post_tabs_mcp.zip (5.2 KB)

Das sieht sehr vielversprechend aus.

Doch wie komme ich da hin?

Die ZIP-Datei, die du aufgelegt hast, ist unverändert die gleiche, wie auf deiner Homepage.

Ist das so gedacht?

Adafruit_MCP23X17 mcp[noOfMcp]; // 
Compiler akzeptiert „MCP23X17“ nicht, nur MCP23017
             

Habe versucht deine Ideen umzusetzen, aber leider ohne Erfolg. Trotzdem vielen Dank noiasca. Vielleicht hat jemand Lust diese Herausforderung anzunehmen. Dazu habe ich den Sketch ein Stück weit vorbereitet und mit zusätzlichen Infos versehen. Da ich als Neueinsteiger keine Dateien hochladen darf, hier der Sketch in zwei Teilen:


/* *******************************************************************
  A simple Arduino ethernet web server.
  - https://werner.rothschopf.net/202001_arduino_webserver_post.htm
  - MCP23017 Hardware ist eingebunden und funktioniert siehe dazu
  - in void loop() "Test-LED" (Ausgang A1)
  - Verwende Adafruit MCP23017 Arduino Library V 2.3.0
  - A0, A1, A2 = GND → Adresse 0x20
  - RESET = VCC
  - Problem: Einbindung im sever in Zeile 132 "for (auto &pin : ???){}"  wie???

* *************************************************************** */

#include <SPI.h>
#include <Ethernet.h>
#include <Wire.h>
#include <INTERVAL.h>
#include <Adafruit_MCP23017.h>

Adafruit_MCP23017 mcp; // Create MCP

byte mac[] {0x52, 0x64, 0x75, 0x69, 0x6E, 0x6F};         // you can change the MAC and IP addresses to suit your network
IPAddress ip(1, 1, 17, 12);
EthernetServer server(80);                               // Port 80 is HTTP port

const byte ledPin[] {2, 3, 5, 6, 7, 8, 9, A0, A1};   // define the LED pins - im sever Zeile 132 for (auto &pin : ledPin){} eingebunden 
const uint8_t addr = 0; // Adresse 0x20 / 0          // Einbindung im sever in Zeile 132 for (auto &pin : ???){} wie???

void printHardwareInfo()
{
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println(F("Ethernet shield was not found"));
  }
  else if (Ethernet.hardwareStatus() == EthernetW5100) {
    Serial.println(F("W5100 detected"));
  }
  else
  {
    if (Ethernet.hardwareStatus() == EthernetW5200) {
      Serial.println(F("W5200 detected"));
    }
    else if (Ethernet.hardwareStatus() == EthernetW5500) {
      Serial.println(F("W5500 detected"));
    }
    if (Ethernet.linkStatus() == Unknown) {      // cable connection
      Serial.println(F("Link status detection is only available with W5200 and W5500"));
    }
    else if (Ethernet.linkStatus() == LinkON) {
      Serial.println(F("Link status: On"));
    }
    else if (Ethernet.linkStatus() == LinkOFF) {
      Serial.println(F("Link status: Off"));
    }
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F("\nsimple webserver"));
  for (auto &pin : ledPin) {
    pinMode(pin, OUTPUT);    // Set the digital pins ready to write to
  }
  mcp.begin(addr);      // Start MCP
    for (byte i=0; i<16; i++) 
        mcp.pinMode(i, OUTPUT);
    for (byte i=0; i<16; i++)
        mcp.digitalWrite(i, HIGH);
  Ethernet.begin(mac, ip);   // Start the Ethernet shield
  printHardwareInfo();
  server.begin();            // Start the webserver
  Serial.print(F("server available at http://"));
  Serial.println(Ethernet.localIP());
}

void loop()
{
  // listen for incoming clients
  checkForClient();
 
  // Test-LED
  INTERVAL(1000UL)  
    mcp.digitalWrite(1, LOW);  // LED an A1  "AN"
  INTERVAL(3000UL)
    mcp.digitalWrite(1, HIGH); // LED an A1  "AUS"

    //////  alternativ   /////
/*  
   mcp.digitalWrite(1, LOW);  // LED an A1  "AN"
     delay(3000);
   mcp.digitalWrite(1, HIGH); // LED an A1  "AUS"  
     delay(1000);
*/   
}

Teil 2 (server)

/* *******************************************************************
   Webserver
   ***************************************************************** */

void checkForClient()
{
  EthernetClient client = server.available();

  if (client) {
    Serial.println(F("\n[server] client connected"));
    uint8_t i = 0;                               // index / current read position
    const uint16_t buffersize = 100;             // size of read buffer (reads a complete line) (if larger than 255, modify i also!
    const uint16_t smallbuffersize = 30;         // a smaller buffer for results
    char lineBuffer[buffersize] {'\0'};          // buffer for incomming data
    char method[8];                              // largest one 7+1. HTTP request methods in RFC7231 + RFC5789: GET HEAD POST PUT DELETE CONNECT OPTONS TRACE PATCH
    char uri[smallbuffersize];                   // the requestet page, shorter than smallbuffersize - method
    char requestParameter[smallbuffersize];      // parameter appended to the URI after a ?
    char postParameter[smallbuffersize];         // parameter transmitted in the body / by POST
    enum class Status {REQUEST, CONTENT_LENGTH, EMPTY_LINE, BODY};
    Status status = Status::REQUEST;

    while (client.connected()) {
      while (client.available()) {
        char c = client.read();
        Serial.print(c);     // Debug print received characters to Serial monitor
        if ( c == '\n' )
        {
          if (status == Status::REQUEST)         // read the first line
          {
            //Serial.print(F("lineBuffer="));Serial.println(lineBuffer);
            // now split the input
            char *ptr;
            ptr = strtok(lineBuffer, " ");       // strtok willdestroy the newRequest
            strlcpy(method, ptr, smallbuffersize);
            Serial.print(F("method=")); Serial.println(method);
            ptr = strtok(NULL, " ");
            strlcpy(uri, ptr, smallbuffersize);  // enthält noch evtl. parameter
            if (strchr(uri, '?') != NULL)
            {
              ptr = strtok(uri, "?");  // split URI from parameters
              strcpy(uri, ptr);
              ptr = strtok(NULL, " ");
              strcpy(requestParameter, ptr);
              Serial.print(F("requestParameter=")); Serial.println(requestParameter);
            }
            Serial.print(F("uri=")); Serial.println(uri);
            status = Status::EMPTY_LINE;                   // jump to next status
          }
          else if (status == Status::CONTENT_LENGTH)       // MISSING check for Content-Length
          {
            status = Status::EMPTY_LINE;
          }
          else if (status > Status::REQUEST && i < 2)      // check if we have an empty line
          {
            status = Status::BODY;
          }
          else if (status == Status::BODY)
          {
            strlcpy(postParameter, lineBuffer, smallbuffersize);
            break; // we have received one line payload and break out
          }
          i = 0;
          strcpy(lineBuffer, "");
        }
        else
        {
          if (i < buffersize)
          {
            lineBuffer[i] = c;
            i++;
            lineBuffer[i] = '\0';
          }
          // MISSING wenn status 3 und content-length --> abbrechen.
        }
      }
      if (status == Status::BODY)      // status 3 could end without linefeed, therefore we takeover here also
      {
        strlcpy(postParameter, lineBuffer, smallbuffersize);
      }
      Serial.println();                // start new line at the end of the browser input
      Serial.print(F("postParameter=")); Serial.println(postParameter);
      // more advanced evaluation of postParameter from body
      // post data looks like pinD2=On but number could have one, two or even three digits
      if ( strncmp( postParameter, "pinD", 4) == 0 ) {       // check the first 4 characters (= length of needle)
        byte pin = atoi(postParameter + 4);                  // Convert ASCII to byte from position 4 onwards
        //Serial.print(("pin=")); Serial.println(pin);
        const char * ptr = strchr(postParameter, '=');       // get a pointer to the first occurance of '='
        if (ptr != NULL)                                     // only continue when postParameter contains '='
        {
          size_t pos = ptr - postParameter +1;               // calculate from the pointer adress to the absolute position within postParameter
          if ( strncmp( postParameter + pos, "On", 2) == 0 ) {
            digitalWrite(pin, 1);
          }
          else if ( strncmp( postParameter + pos, "Off", 3) == 0 ) {
            digitalWrite(pin, 0);
          }
        }
      }
      // send back a response
      if (!strcmp(uri, "/") || !strcmp(uri, "/index.htm")   )        // the homepage
        sendPage(client);
      else if (!strcmp(uri, "/favicon.ico"))     // a favicon
        send204(client);                         // if you don't have a favicon, send 204
      else                                       // if the page is unknown, HTTP response code 404
        send404(client);
      delay(1);
      client.stop();
    }
  }
}
void sendPage(EthernetClient & client)
{
  // Serial.println("[server] 200 response send");
  client.println( "HTTP/1.0 200 OK\r\n"          // \r\n Header Fields are terminated by a carriage return (CR) and line feed (LF) character sequence
                  "Content-Type: text/html\r\n"  // The media type of the body of the request (used with POST and PUT requests)
                  "\r\n"                         // a blank line to split HTTP header and HTTP body
                  "<!doctype html>\n"            // the start of the HTTP Body - contains the HTML
                  "<html lang='en'>\n"
                  "<head>\n"
                  "<meta charset='utf-8'>\n"
                  "<meta name='viewport' content='width=device-width'>\n"
                  "<title>Webserver as pin controller</title>\n"
                  "</head>\n"
                  "<body style='font-family:Helvetica, sans-serif'>\n" // a minimum style to avoid serifs
                  "<h1>Webserver as pin controller</h1>\n"
                  "<p>Buttons turn pins on or off</p>\n"
                  "<form method='post' action='/' name='pins'>");
  for (auto &pin : ledPin)
  {
    client.print(pin);
    client.print(" <input name='pinD");
    client.print(pin);
    client.print("' type='submit' value='On'>");
    client.print("<input name='pinD");
    client.print(pin);
    client.print("' type='submit' value='Off'>");
    if (digitalRead(pin)) client.print(F(" active"));
    client.print("<br>\n");
  }
  client.print("</form>\n");
  // add any other data here before the end of the document
  client.print("</body>\n</html>");
  client.stop();
}
void send404(EthernetClient & client)
{
  // Serial.println("[server] response 404 file not found");
  client.println("HTTP/1.0 404 Not Found\r\n"
                 "Content-Type: text/plain\r\n"  // simple plain text without html tags
                 "\r\n"
                 "File Fot Found");
}
void send204(EthernetClient & client)
{
  // Serial.println("[server] response 204 no content");
  client.println("HTTP/1.0 204 no content\r\n"); // no content
}

Du verwendest eine alte Version der Bibliothek, daher kann ich Deine Programme auch nicht testen.

Laut github ist 2.3.0 (27.12.22) die aktuellste und auch hier (reference.arduino.cc/reference/en/libraries/adafruit-mcp23017-arduino-library)
ist sie als „latest“ deklaliert.

Falls du eine andere Quelle mit neuerer Version kennst, würde mich das natürlich sehr interessieren, denn schließlich ist es mir wichtig uptodate zu sein.
Nebenbei bemerkt, niemand hindert dich daran den Sketch mit einer neueren Version zu testen.
Ganz allgemein, ich habe große Hochachtung vor den Leuten hier im Forum, die freiwillig Zeit und Energie investieren, um andere bei ihren Projekten unterstützen.
Deshalb hege ich keinerlei Erwartungen, wenn ich hier ein Thema eröffne.

Vielen Dank!

jetzt sollte die richtige zip im post sein...

Übrigens, wenn ich das erste Beispiel "mcp23xxx_blink" aus der MCP library öffne dann beginnt dieses wie folgt:

// Blinks an LED attached to a MCP23XXX pin.

// ok to include only the one needed
// both included here to make things simple for example
#include <Adafruit_MCP23X08.h>
#include <Adafruit_MCP23X17.h>

#define LED_PIN 0     // MCP23XXX pin LED is attached to

// only used for SPI
#define CS_PIN 6

// uncomment appropriate line
Adafruit_MCP23X08 mcp;
//Adafruit_MCP23X17 mcp;

Jetzt war es die richtige zip und dazu noch ein Volltreffer - genial !!! :smiley:

Habe heute endlich mal Zeit gefunden deinen Dummy-Sketch mit Hardware zu kreuzen.
Hat auf Anhieb, deiner Beschreibung folgend, funktioniert.
Getestet mit 1, 2 und 3 MCPs. Alle liefern definierte Signale.

Werde bei Gelegenheit diesen Muster-Sketch in meinen Sketch einbauen, was sicher noch spannend wird, weil ich das Original umstrukturiert und erweitert habe.
Doch ich denke, das könnte ich schaffen.
Ich würde dann diesen Sketch hier veröffentlichen, denn vielleicht kann ihn jemand anders einsetzten oder nach seinen Bedürfnissen anpassen und verwenden.
Wäre bei diesem Thema mein bescheidener Beitrag für das Forum.

Vielen Dank noiasca für deine Unterstützung!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.