Skip to content

[WiFi] added support for ESP32 architecture and XIAO ESP32C3 board #512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions Boards.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Boards.h - Hardware Abstraction Layer for Firmata library
Copyright (c) 2006-2008 Hans-Christoph Steiner. All rights reserved.
Copyright (C) 2009-2017 Jeff Hoefs. All rights reserved.
Copyright (C) 2023 Jens B. All rights reserved.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
Expand All @@ -10,7 +11,7 @@

See file LICENSE.txt for further informations on licensing terms.

Last updated April 15th, 2018
Last updated December 17th, 2023
*/

#ifndef Firmata_Boards_h
Expand All @@ -29,7 +30,7 @@
// compile, but without support for any Servos. Hopefully that's what the
// user intended by not including Servo.h
#ifndef MAX_SERVOS
#define MAX_SERVOS 0
#define MAX_SERVOS 0
#endif

/*
Expand Down Expand Up @@ -1021,6 +1022,54 @@ writePort(port, value, bitmask): Write an 8 bit port.
#define PIN_TO_SERVO(p) (p)
#define DEFAULT_PWM_RESOLUTION 10

// XIAO ESP32C3
// note: Firmata pin numbering schema is by ESP32 GPIO -> IS_XXX checks GPIO number (Ax = Dx, Dx to GPIOy)
#elif defined(ARDUINO_XIAO_ESP32C3)
#define TOTAL_ANALOG_PINS (A2 + 1) // (max GPIOx + 1), there are 4 physical analog pins but only 3 are supported by ESP32 SDK 2.0.14 via ADC1
#define TOTAL_PINS NUM_DIGITAL_PINS // (max GPIOx + 1), there are 11 physical pins
#define PIN_SERIAL_RX RX
#define PIN_SERIAL_TX TX
#define IS_PIN_DIGITAL(p) (((p) >= D0 && (p) <= D10) || (p) == D6 || (p) == D7)
#define IS_PIN_ANALOG(p) ((p) >= A0 && (p) <= A2)
#define IS_PIN_PWM(p) 0
#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && MAX_SERVOS > 0)
#define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL)
#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK)
#define IS_PIN_INTERRUPT(p) (digitalPinToInterrupt(p) > NOT_AN_INTERRUPT)
#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL_RX || (p) == PIN_SERIAL_TX)
#define PIN_TO_DIGITAL(p) ((p) < 6? D0 + (p) : ((p) < 8? D6 + 6 - (p) : (p))) // Dx to GPIOy
#define PIN_TO_ANALOG(p) (p) // FIRMATAx to GPIOy
#define PIN_TO_PWM(p) 127 // @TODO ESP32 SDK does not support analogWrite()
#define PIN_TO_SERVO(p) 127 // @TODO ESP32 SDK does not support servos

#define DEFAULT_PWM_RESOLUTION 8 // see esp32-hal-led.c, analog_resolution
#define DEFAULT_ADC_RESOLUTION 12 // see esp32-hal-adc.h, analogSetWidth()

// WT32-ETH01 (ESP32-S1)
// note: Firmata pin numbering schema is by ESP32 GPIO -> IS_XXX checks GPIO number (Ax = Dx = IOx = GPIOx)
#elif defined(ARDUINO_WT32_ETH01)
#define TOTAL_ANALOG_PINS (IO17 + 1) // (max GPIOx + 1), there are 10 physical analog pins
#define TOTAL_PINS NUM_DIGITAL_PINS // (max GPIOx + 1), there are 15 physical pins, 3 of them are input only
#define PIN_SERIAL1_RX RX
#define PIN_SERIAL1_TX TX
#define PIN_SERIAL2_RX TXD
#define PIN_SERIAL2_TX RXD
#define IS_PIN_DIGITAL(p) (((p) <= IO5) || (p) == IO12 || (p) == IO14 || (p) == IO15 || (p) == IO17 || (p) == IO32 || (p) == IO33 || (p) == IO35 || (p) == IO39)
#define IS_PIN_ANALOG(p) (((p) <= IO5) || (p) == IO12 || (p) == IO14 || (p) == IO15 || (p) == IO17)
#define IS_PIN_PWM(p) 0
#define IS_PIN_SERVO(p) (IS_PIN_DIGITAL(p) && MAX_SERVOS > 0)
#define IS_PIN_I2C(p) ((p) == SDA || (p) == SCL)
#define IS_PIN_SPI(p) ((p) == SS || (p) == MOSI || (p) == MISO || (p) == SCK)
#define IS_PIN_INTERRUPT(p) (digitalPinToInterrupt(p) > NOT_AN_INTERRUPT)
#define IS_PIN_SERIAL(p) ((p) == PIN_SERIAL1_RX || (p) == PIN_SERIAL1_TX || (p) == PIN_SERIAL2_RX || (p) == PIN_SERIAL2_TX)
#define PIN_TO_DIGITAL(p) (p) // FIRMATAx to GPIOy
#define PIN_TO_ANALOG(p) (p) // FIRMATAx to GPIOy
#define PIN_TO_PWM(p) 127 // @TODO ESP32 SDK does not support analogWrite()
#define PIN_TO_SERVO(p) 127 // @TODO ESP32 SDK does not support servos

#define DEFAULT_PWM_RESOLUTION 8 // see esp32-hal-led.c, analog_resolution
#define DEFAULT_ADC_RESOLUTION 12 // see esp32-hal-adc.h, analogSetWidth()

// STM32 based boards
#elif defined(ARDUINO_ARCH_STM32)
#define TOTAL_ANALOG_PINS NUM_ANALOG_INPUTS
Expand Down
6 changes: 4 additions & 2 deletions Firmata.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Firmata.h - Firmata library v2.5.8 - 2018-04-15
Firmata.h - Firmata library v2.5.10 - 2023-12-16
Copyright (c) 2006-2008 Hans-Christoph Steiner. All rights reserved.
Copyright (C) 2009-2017 Jeff Hoefs. All rights reserved.

Expand Down Expand Up @@ -38,7 +38,9 @@
//#define INPUT 0x00 // defined in Arduino.h
//#define OUTPUT 0x01 // defined in Arduino.h
// DEPRECATED as of Firmata v2.5
#define ANALOG 0x02 // same as PIN_MODE_ANALOG
#ifndef ARDUINO_ARCH_ESP32
#define ANALOG 0x02 // same as PIN_MODE_ANALOG
#endif
Comment on lines +41 to +43
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why this is necessary again?

Maybe we can figure out a creative solution to work around your need. My fear is that you are going to break backward compatibility with this change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to do a similar change in ConfigurableFirmata, as the problem is that there's a conflicting definition of ANALOG (as well as INPUT and OUTPUT) in the ESP32 headers. IIRC, the values don't even match, so setting the pin mode could strangely fail, depending on which of the definitions is in scope at the calling place. But I think the right solution is to call this PIN_MODE_ANALOG, to avoid further ambiguities.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does PIN_MODE_ANALOG come from Arduino.h?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see it. It's in FirmataDefines.h. It seems like the right move is to move away from ANALOG and deprecate it altogether, as we did for INPUT and OUTPUT.

#define PWM 0x03 // same as PIN_MODE_PWM
#define SERVO 0x04 // same as PIN_MODE_SERVO
#define SHIFT 0x05 // same as PIN_MODE_SHIFT
Expand Down
2 changes: 1 addition & 1 deletion FirmataConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace firmata {
*/
static const int FIRMWARE_MAJOR_VERSION = 2;
static const int FIRMWARE_MINOR_VERSION = 5;
static const int FIRMWARE_BUGFIX_VERSION = 7;
static const int FIRMWARE_BUGFIX_VERSION = 10;

/* Version numbers for the protocol. The protocol is still changing, so these
* version numbers are important.
Expand Down
46 changes: 29 additions & 17 deletions examples/StandardFirmataWiFi/StandardFirmataWiFi.ino
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

See file LICENSE.txt for further informations on licensing terms.

Last updated August 17th, 2017
Last updated December 17th, 2023
*/

/*
Expand Down Expand Up @@ -74,7 +74,11 @@
- Arduino Mega: (D5, D7, D10, D50, D52, D53)
*/

#include <Servo.h>
#ifdef ARDUINO_ARCH_ESP32
#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
#include <Wire.h>
#include <Firmata.h>

Expand Down Expand Up @@ -235,8 +239,10 @@ void detachServo(byte pin)
} else if (servoCount > 0) {
// keep track of detached servos because we want to reuse their indexes
// before incrementing the count of attached servos
detachedServoCount++;
detachedServos[detachedServoCount - 1] = servoPinMap[pin];
if (detachedServoCount < MAX_SERVOS) {
detachedServos[detachedServoCount] = servoPinMap[pin];
detachedServoCount++;
}
}

servoPinMap[pin] = 255;
Expand Down Expand Up @@ -370,7 +376,7 @@ void setPinModeCallback(byte pin, int mode)
reportAnalogCallback(PIN_TO_ANALOG(pin), mode == PIN_MODE_ANALOG ? 1 : 0); // turn on/off reporting
}
if (IS_PIN_DIGITAL(pin)) {
if (mode == INPUT || mode == PIN_MODE_PULLUP) {
if (mode == PIN_MODE_INPUT || mode == PIN_MODE_PULLUP) {
portConfigInputs[pin / 8] |= (1 << (pin & 7));
} else {
portConfigInputs[pin / 8] &= ~(1 << (pin & 7));
Expand All @@ -390,14 +396,14 @@ void setPinModeCallback(byte pin, int mode)
Firmata.setPinMode(pin, PIN_MODE_ANALOG);
}
break;
case INPUT:
case PIN_MODE_INPUT:
if (IS_PIN_DIGITAL(pin)) {
pinMode(PIN_TO_DIGITAL(pin), INPUT); // disable output driver
#if ARDUINO <= 100
// deprecated since Arduino 1.0.1 - TODO: drop support in Firmata 2.6
digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups
#endif
Firmata.setPinMode(pin, INPUT);
Firmata.setPinMode(pin, PIN_MODE_INPUT);
}
break;
case PIN_MODE_PULLUP:
Expand All @@ -407,14 +413,14 @@ void setPinModeCallback(byte pin, int mode)
Firmata.setPinState(pin, 1);
}
break;
case OUTPUT:
case PIN_MODE_OUTPUT:
if (IS_PIN_DIGITAL(pin)) {
if (Firmata.getPinMode(pin) == PIN_MODE_PWM) {
// Disable PWM if pin mode was previously set to PWM.
digitalWrite(PIN_TO_DIGITAL(pin), LOW);
}
pinMode(PIN_TO_DIGITAL(pin), OUTPUT);
Firmata.setPinMode(pin, OUTPUT);
Firmata.setPinMode(pin, PIN_MODE_OUTPUT);
}
break;
case PIN_MODE_PWM:
Expand Down Expand Up @@ -461,7 +467,7 @@ void setPinModeCallback(byte pin, int mode)
void setPinValueCallback(byte pin, int value)
{
if (pin < TOTAL_PINS && IS_PIN_DIGITAL(pin)) {
if (Firmata.getPinMode(pin) == OUTPUT) {
if (Firmata.getPinMode(pin) == PIN_MODE_OUTPUT) {
Firmata.setPinState(pin, value);
digitalWrite(PIN_TO_DIGITAL(pin), value);
}
Expand Down Expand Up @@ -498,11 +504,11 @@ void digitalWriteCallback(byte port, int value)
// do not disturb non-digital pins (eg, Rx & Tx)
if (IS_PIN_DIGITAL(pin)) {
// do not touch pins in PWM, ANALOG, SERVO or other modes
if (Firmata.getPinMode(pin) == OUTPUT || Firmata.getPinMode(pin) == INPUT) {
if (Firmata.getPinMode(pin) == PIN_MODE_OUTPUT || Firmata.getPinMode(pin) == PIN_MODE_INPUT) {
pinValue = ((byte)value & mask) ? 1 : 0;
if (Firmata.getPinMode(pin) == OUTPUT) {
if (Firmata.getPinMode(pin) == PIN_MODE_OUTPUT) {
pinWriteMask |= mask;
} else if (Firmata.getPinMode(pin) == INPUT && pinValue == 1 && Firmata.getPinState(pin) != 1) {
} else if (Firmata.getPinMode(pin) == PIN_MODE_INPUT && pinValue == 1 && Firmata.getPinState(pin) != 1) {
// only handle INPUT here for backwards compatibility
#if ARDUINO > 100
pinMode(pin, INPUT_PULLUP);
Expand Down Expand Up @@ -725,22 +731,26 @@ void sysexCallback(byte command, byte argc, byte *argv)
Firmata.write(CAPABILITY_RESPONSE);
for (byte pin = 0; pin < TOTAL_PINS; pin++) {
if (IS_PIN_DIGITAL(pin)) {
Firmata.write((byte)INPUT);
Firmata.write((byte)PIN_MODE_INPUT);
Firmata.write(1);
Firmata.write((byte)PIN_MODE_PULLUP);
Firmata.write(1);
Firmata.write((byte)OUTPUT);
Firmata.write((byte)PIN_MODE_OUTPUT);
Firmata.write(1);
}
if (IS_PIN_ANALOG(pin)) {
Firmata.write(PIN_MODE_ANALOG);
#ifdef DEFAULT_ADC_RESOLUTION
Firmata.write(DEFAULT_ADC_RESOLUTION);
#else
Firmata.write(10); // 10 = 10-bit resolution
#endif
}
if (IS_PIN_PWM(pin)) {
Firmata.write(PIN_MODE_PWM);
Firmata.write(DEFAULT_PWM_RESOLUTION);
}
if (IS_PIN_DIGITAL(pin)) {
if (IS_PIN_SERVO(pin)) {
Firmata.write(PIN_MODE_SERVO);
Firmata.write(14);
}
Expand Down Expand Up @@ -820,7 +830,7 @@ void systemResetCallback()
setPinModeCallback(i, PIN_MODE_ANALOG);
} else if (IS_PIN_DIGITAL(i)) {
// sets the output to 0, configures portConfigInputs
setPinModeCallback(i, OUTPUT);
setPinModeCallback(i, PIN_MODE_OUTPUT);
}

servoPinMap[i] = 255;
Expand Down Expand Up @@ -871,6 +881,7 @@ void printWifiStatus() {
DEBUG_PRINT( "WiFi connection failed. Status value: " );
DEBUG_PRINTLN( WiFi.status() );
}
#ifdef SERIAL_DEBUG
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this doing for us? How does SERIAL_DEBUG get defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SERIAL_DEBUG can be found in StandardFirmataWifi.ino:90. If not enabled with ESP32 SDK (default) the compiler will raise a warning or an error (depending on the compiler settings) because of the unused local variables like "rssi" in line 898, caused by defining DEBUG_PRINT to nothing (see utility/firmataDebug.h).

else
{
// print the SSID of the network you're attached to:
Expand All @@ -888,6 +899,7 @@ void printWifiStatus() {
DEBUG_PRINT( rssi );
DEBUG_PRINTLN( " dBm" );
}
#endif
}

/*
Expand Down
29 changes: 27 additions & 2 deletions examples/StandardFirmataWiFi/wifiConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
*============================================================================*/

// STEP 1 [REQUIRED]
// Uncomment / comment the appropriate set of includes for your hardware (OPTION A, B or C)
// Arduino MKR1000 or ESP8266 are enabled by default if compiling for either of those boards.
// Uncomment / comment the appropriate set of includes for your hardware (OPTION A ... F)
// Arduino MKR1000, ESP8266 and ESP32 are enabled by default if compiling for either of those boards.

/*
* OPTION A: Configure for Arduino MKR1000 or Arduino WiFi Shield 101
Expand Down Expand Up @@ -137,6 +137,31 @@
#endif
#endif

/*
* OPTION F: Configure for ESP32
*
* This will configure StandardFirmataWiFi to use the ESP8266WiFi library for boards
* with an ESP32 chip.
*
* The appropriate libraries are included automatically when compiling for the ESP32 so
* continue on to STEP 2.
*/

#if defined(ARDUINO_ARCH_ESP32)
// automatically include if compiling for ESP32
#define ESP32_WIFI
#endif
#ifdef ESP32_WIFI
#include <WiFi.h>
#include "utility/WiFiClientStream.h"
#include "utility/WiFiServerStream.h"
#ifdef WIFI_LIB_INCLUDED
#define MULTIPLE_WIFI_LIB_INCLUDES
#else
#define WIFI_LIB_INCLUDED
#endif
#endif

//------------------------------
// TODO
//------------------------------
Expand Down
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=Firmata
version=2.5.9
version=2.5.10
author=Firmata Developers
maintainer=Firmata team
sentence=Enables the communication with computer apps using a standard serial protocol. For all Arduino/Genuino boards.
Expand Down
22 changes: 15 additions & 7 deletions utility/WiFiStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
are compatible with the Arduino WiFi library.

Copyright (C) 2015-2016 Jesse Frush. All rights reserved.
Copyright (C) 2016 Jens B. All rights reserved.
Copyright (C) 2016 Jens B. All rights reserved.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
Expand All @@ -15,7 +15,7 @@

See file LICENSE.txt for further informations on licensing terms.

Last updated April 23rd, 2016
Last updated December 17th, 2023
*/

#ifndef WIFI_STREAM_H
Expand Down Expand Up @@ -69,7 +69,7 @@ class WiFiStream : public Stream
* network configuration
******************************************************************************/

#ifndef ESP8266
#if !ESP8266 && !ESP32
/**
* configure a static local IP address without defining the local network
* DHCP will be used as long as local IP address is not defined
Expand All @@ -90,7 +90,7 @@ class WiFiStream : public Stream
_local_ip = local_ip;
_subnet = subnet;
_gateway = gateway;
#ifndef ESP8266
#if !ESP8266 && !ESP32
WiFi.config( local_ip, IPAddress(0, 0, 0, 0), gateway, subnet );
#else
WiFi.config( local_ip, gateway, subnet );
Expand All @@ -115,7 +115,11 @@ class WiFiStream : public Stream
*/
virtual bool maintain() = 0;

#ifdef ESP8266
#if ESP8266 || ESP32
enum ConnectionStatus { STATUS_CLOSED = 0,
STATUS_ESTABLISHED = 4
};

Comment on lines +118 to +122
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgive me, I'm not familiar with the ESP8266 and ESP32 Wi-Fi libraries. Can you please explain the enum a little bit better?

Using this enum and the logic below, it looks like you are translating a boolean into values from the ESP8266 enum.

If that's correct, then I don't understand why you would want to redefine these values for ESP8266.

Shouldn't the enum only be defined under #if ESP32?

Suggested change
#if ESP8266 || ESP32
enum ConnectionStatus { STATUS_CLOSED = 0,
STATUS_ESTABLISHED = 4
};
#if ESP8266 || ESP32
#if ESP32
enum ConnectionStatus {
STATUS_CLOSED = 0,
STATUS_ESTABLISHED = 4
};
#endif // ESP32

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ESP8266 SDK defines all the constants listed in the method description, but they do not exists with the same definition in the ESP32 SDK, if at all (they are related to the low level TCP socket implementation).

So similar how the Firmata library handles the PIN_MODE_XXX definitions I defined the enum to have the same definitions available, regardless which SDK is used. It would be possible to add all the constants known in the ESSP8266 SDK, but most of the other are typically of no practical value, so I added only the one that are available in both SDKs.

I hope that I got your initial idea right by implementing the status() method instead of the new connected() method to avoid having a platform specific solution. If not, just let me know.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I generally like your latest implementation. 👍

However, I'm concerned that you are "redefining" the symbols STATUS_CLOSED and STATUS_ESTABLISHED for the ESP8266 platform. They are already defined in the ESP8266 Wi-Fi library, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

STATUS_CLOSED and STATUS_ESTABLISHED are enum values and are part of the WiFiStream class namespace of Firmata.

On the other hand CLOSED, LISTEN, etc. are defined as enum values in lwip/tcp.h of the ESP8266 SDK in the global namespace. So the two sets of constants have unique names and in the case oft ESP8266 SDK the same values. To make the code more robust to changes in the ESP8266 SKD one could use a switch:

inline uint8_t status()
{
  #ifdef ESP8266
    uint8 cs = _client.status();
    switch (cs)
    {
      case CLOSED:      cs = STATUS_CLOSED; break;
      case ESTABLISHED: cs = STATUS_ESTABLISHED; break;
      default: break;
    }
    return cs;
  #elif ESP32
    return _client.connected()? STATUS_ESTABLISHED : STATUS_CLOSED;
  #endif
}

/**
* get status of TCP connection
* @return status of TCP connection
Expand All @@ -133,7 +137,11 @@ class WiFiStream : public Stream
*/
inline uint8_t status()
{
#ifdef ESP8266
return _client.status();
#elif ESP32
return _client.connected()? STATUS_ESTABLISHED : STATUS_CLOSED;
#endif
}
#endif

Expand All @@ -157,10 +165,10 @@ class WiFiStream : public Stream

WiFi.begin(ssid);
int result = WiFi.status();
return WiFi.status();
return result;
}

#ifndef ESP8266
#if !ESP8266 && !ESP32
/**
* initialize WiFi with WEP security and initiate client connection
* if WiFi connection is already established
Expand Down