Skip to content
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

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
37 changes: 35 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,16 @@
// 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
class Servo
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not the appropriate place to define a class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe. I dont' want this class at all. The source comments give the impression that the Firmata application can do without servos, but than the examples do not build. Adding a new Servo.h to the library itself is also not a good idea because it will create conflicts.
So where put it? Into each example?

Copy link
Contributor

Choose a reason for hiding this comment

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

In the example, is it not possible to include ESP32Servo.h?

In theory, you could use the same switch as you are using elsewhere (i.e. ARDUINO_ARCH_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.

I already considered using ESP32Servo.h. On first glance it should be compatible, but I did not try so far and I do not have the hardware to verify.

Problem I see here: it's an extra library that needs to be installed separately. So most users will choose the Firmata example, build and get a fail. Only some will read the docs and add the library first. It would be preferable to have the example work out of the box without this extra step and make it still an option. To do this the Servo.h include should be disabled and that can be done with the ARDUINO_ARCH_ESP32 define in the Firmata examples. The result could look like this:

_
#ifdef ARDUINO_ARCH_ESP32
// comment in if ESP32Servo library is installed and servos are required
//#include <ESP32Servo.h>
#else
#include <Servo.h>
#endif
_

What remains to do is to find a solution for the example code that depends on Servo.h. I know this is not ConfigurableFirmata, but previously I had to remove the servo code manually from the INO to make it work without (especially to save codespace for other purposes on small MCUs like the Atmega 328P). Here I see 2 options:

  • use define ARDUINO_ARCH_ESP32
    advantage: compact binary
    disadvantage: ugly because several code blocks must be taken out
  • use skeleton for Servo class, could be a file name FirmataServoSkeleton.h
    disadvantage: less compact binary

Copy link
Contributor

Choose a reason for hiding this comment

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

Problem I see here: it's an extra library that needs to be installed separately.

There is already a precedent for this behavior, because the regular sample requires the standard Servo library to be installed.

Please remove the class definition and proceed with the following:

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, see latest commit. I considered using a preprocessor check "#if __has_include("ESP32Servo.h)" to create a more descriptive error message if the library was not added, but this check is not a C standard and may cause problems.

Copy link
Contributor

Choose a reason for hiding this comment

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

@jnsbyr You can add a library dependency by adding a depends line to library.properties, as done here: https://github.com/firmata/ConfigurableFirmata/blob/master/library.properties Then the library manager should automatically grab the dependency.

{
public:
uint8_t attach(int pin) { return 0; };
uint8_t attach(int pin, int min, int max) { return 0; };
void detach() {};
void write(int value) {};
bool attached() { return false; };
};
#endif

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

// XIAO ESP32C2
// note: Firmata pin numbering schema is by ESP32 GPIO -> IS_XXX checks GPIO number
#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_ANALOG_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>
#ifndef ARDUINO_ARCH_ESP32
// NOTE: ESP32 SKD does not provide an implementation for class Servo
// -> requires a skeleton implementation for Servo in Boards.h to be able to compile
#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_ANALOG_RESOLUTION
Firmata.write(DEFAULT_ANALOG_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
20 changes: 14 additions & 6 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) && (!ARDUINO_ARCH_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) && (!ARDUINO_ARCH_ESP32)
WiFi.config( local_ip, IPAddress(0, 0, 0, 0), gateway, subnet );
#else
WiFi.config( local_ip, gateway, subnet );
Expand Down Expand Up @@ -135,6 +135,14 @@ class WiFiStream : public Stream
{
return _client.status();
}
#elif ARDUINO_ARCH_ESP32
/**
* check if TCP connection is established
*/
inline uint8_t connected()
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are you creating an ESP32 specific API?

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 ESP32 SDK does not provide the same functionality as the ESP8266 SDK. For some use cases it is still relevant to know if the stream is connected, as this is not the same as checking the WiFi status. One alternative would be to mimic the ESP8266 implementation and create a status value from the bool provided by the ESP32 SDK.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think that's the way to go, especially since there is precedent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that's the way to go.

You refer to the alternative I mentioned (to use the previous interface)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, see latest commit. Also added an enum for the possible return values of method status() as the ESP32 SDK does not define constants like the ESP8266 SDK does.

{
return _client.connected();
}
#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) && (!ARDUINO_ARCH_ESP32)
/**
* initialize WiFi with WEP security and initiate client connection
* if WiFi connection is already established
Expand Down