We recently had to switch our project from using I2C to using serial for communicating with the Notecard.
Naturally, I anticipated this would be pretty straightforward, but unfortunately that was far from the experience I got.
The ESP32 family offers flexible pin assignments to peripherals, such as the UARTs, allowing peripherals to be used with almost any choice of IO pins. Our choice was guided by the available pins, whether they can be controlled by the uLP processor and other hardware constraints.
note-arduino
tries to own the Serial
instance, calling Serial.begin()
/Serial.end()
and then Serial.begin()
. This happens during the call to Notecard.begin()
where the serial connection is reset.
This obviously works fine when the default pins are used, but not when custom pins are used.
HardwareSerial on ESP32 has a setPins()
method which allows the developer to override the default pins. Unfortunately, this configuration is cleared when HardwareSerial.end()
is called, resulting in the default pins being used rather than the pins intended.
The workaround is to create a subclass of NoteSerial
that handles the serial port, with knowledge of how to correctly set it up.
class NoteSerialConfig : public NoteSerial {
unsigned rxPin;
unsigned txPin;
HardwareSerial& serial;
public:
NoteSerialConfig(HardwareSerial& hw) : serial(hw), rxPin(-1), txPin(-1) {}
void setPins(unsigned rxPin, unsigned txPin) {
this->rxPin = rxPin;
this->txPin = txPin;
}
bool begin() {
serial.begin(9600, SERIAL_8N1, rxPin, txPin);
return true;
}
/**************************************************************************/
/*!
@brief Determines if the Notecard Serial port has data available.
@return The number of bytes available to read.
*/
/**************************************************************************/
virtual size_t available(void) {
return serial.available();
}
/**************************************************************************/
/*!
@brief Read a byte from the Notecard Serial port.
@return A single character byte.
*/
/**************************************************************************/
virtual char receive(void) {
return serial.read();
}
/**************************************************************************/
/*!
@brief Resets the serial port.
@return `true` if the Serial port is available.
*/
/**************************************************************************/
virtual bool reset(void) {
serial.end();
return begin();
}
/**************************************************************************/
/*!
@brief Writes a message to the Notecard Serial port.
@param buffer
The bytes to write.
@param size
The number of bytes to write.
@param flush
Use `true` to flush to Serial.
@return The number of bytes transmitted.
*/
/**************************************************************************/
virtual size_t transmit(uint8_t * buffer, size_t size, bool flush) {
size_t result = serial.write(buffer, size);
if (flush) {
serial.flush();
}
return result;
}
};
Unfortunately this is mostly boilerplate code, since NoteSerial_Arduino
is a final class and can’t be extended. If it weren’t final, only the begin() method would need to be overridden.
You can use a global instance of this if Notecard.end()
is not called, or allocate one dynamically, when Notecard.end()
is used, which attempts to deallocate the instance.
// globals
HardwareSerial notecard_serial(1);
NoteSerialConfig noteSerialConfig(notecard_serial);
Notecard notecard;
During setup()
, we can then set up the pins for the serial connection, and pass it to Notecard:
noteSerialConfig.setPins(PIN_NOTECARD_RX, PIN_NOTECARD_TX);
notecard.begin(¬eSerialConfig);
Ideally note-arduino
would be a little more friendly with non-default serial settings, but for anyone who’s struggling with this, I hope this workaround helps.