Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
22 changes: 22 additions & 0 deletions examples/RS485CrashLog/RS485CrashLog.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* This example demonstrates how to redirect the Arduino Mbed crash log output to the RS485 bus.
* This is useful for boards that support RS485 but lack easy access to other serial output options (like the Arduino Opta)
*
* This example forces a crash in the setup() function to showcase the functionality.
* Initial author: Sebastian Romero @sebromero
*/

#ifdef ARDUINO_ARCH_MBED
#include "RS485FileHandle.h"
REDIRECT_STDOUT_TO(&RS485Console) // Redirect mbed crash log output to RS485
#endif

void setup() {
// Force a crash to demonstrate the crash log over RS485
volatile int* p = nullptr;
*p = 42; // Dereference null pointer to cause a crash
}

void loop() {
// Nothing to do here
}
83 changes: 83 additions & 0 deletions src/RS485FileHandle.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include "RS485FileHandle.h"

#ifdef ARDUINO_ARCH_MBED

#include <ArduinoRS485.h>
#include <errno.h>

RS485FileHandle::RS485FileHandle() {}

void RS485FileHandle::begin() {
RS485FileHandle::begin(115200); // Default baud rate
}

void RS485FileHandle::begin(int baudRate) {
// Ensure idempotent initialization
if (!_isInitialized) {
const auto bitduration { 1.f / baudRate };
const auto wordlen { 9.6f }; // OR 10.0f depending on the channel configuration
const auto preDelayBR { bitduration * wordlen * 3.5f * 1e6 };
const auto postDelayBR { bitduration * wordlen * 3.5f * 1e6 };

RS485.begin(baudRate);
RS485.setDelays(preDelayBR, postDelayBR);
RS485.noReceive();
_isInitialized = true;
}
}

ssize_t RS485FileHandle::write(const void* buffer, size_t size) {
begin(); // Ensure RS485 is initialized

// Avoid repeatedly starting transmission if already in progress
// as mbed calls write multiple times for a single output operation.
// This otherwise results in mangled output on RS485.

if (!_inTransmission) {
RS485.beginTransmission();
_inTransmission = true;
}

size_t writtenBytes = RS485.write(static_cast<const uint8_t*>(buffer), size);
return writtenBytes; // Return the number of bytes written
}

ssize_t RS485FileHandle::read(void* buffer, size_t size) {
// Not implemented for RS485 output redirection
return -ENOSYS;
}

off_t RS485FileHandle::seek(off_t offset, int whence) {
return -ESPIPE; // Not seekable
}

int RS485FileHandle::close() {
if (_inTransmission) {
RS485.endTransmission();
_inTransmission = false;
}
RS485.end();
_isInitialized = false;
return 0;
}

short RS485FileHandle::poll(short events) const {
return POLLOUT; // Ready to write
}

int RS485FileHandle::sync() {
if (_inTransmission) {
RS485.endTransmission();
_inTransmission = false;
}
return 0;
}

int RS485FileHandle::isatty() const {
return true;
}

// Global instance for stdout redirection
RS485FileHandle RS485Console;

#endif // ARDUINO_ARCH_MBED
91 changes: 91 additions & 0 deletions src/RS485FileHandle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#ifndef RS485_FILE_HANDLE_H
#define RS485_FILE_HANDLE_H

#ifdef ARDUINO_ARCH_MBED

#include "mbed.h"

/**
* @file RS485FileHandle.h
* @brief Declares an mbed FileHandle that redirects stdio to the RS485 bus.
* This is used to capture crash logs on Mbed-based boards that support RS485 but don't have other serial output options.
*/

/**
* @brief FileHandle implementation that forwards standard I/O to ArduinoRS485.
* The baud rate is fixed at 115200 bps. The internal logic is tuned to ensure correct RS485 transmission behavior in
* the context of crash log output.
*/
class RS485FileHandle : public mbed::FileHandle {
public:
/**
* @brief Constructs a FileHandle that targets the global RS485 interface.
*/
RS485FileHandle();

/**
* @brief Writes a buffer to the RS485 bus.
* When the first write occurs, the RS485 interface is initialised automatically.
*
* @param buffer Data to send.
* @param size Number of bytes to write.
* @return Bytes written on success, or a negative error code.
*/
virtual ssize_t write(const void* buffer, size_t size) override;
/**
* @brief Reads data from the RS485 bus.
*
* @param buffer Destination buffer.
* @param size Maximum number of bytes to read.
* @return Bytes read on success, or a negative error code.
*/
virtual ssize_t read(void* buffer, size_t size) override;
/**
* @brief Changes the current position within the stream.
*
* @param offset Byte offset relative to whence.
* @param whence Reference position, defaults to SEEK_SET.
* @return New position on success, or -1 on error.
*/
virtual off_t seek(off_t offset, int whence = SEEK_SET) override;
/**
* @brief Closes the FileHandle and ends any active transmission.
*
* @return 0 on success, or a negative error code.
*/
virtual int close() override;
/**
* @brief Reports readiness for the requested events.
*
* @param events Bitmask of poll events (POLLIN, POLLOUT, ...).
* @return Bitmask indicating the ready events.
*/
virtual short poll(short events) const override;
/**
* @brief Flushes pending data to the RS485 bus.
*
* @return 0 on success, or a negative error code.
*/
virtual int sync() override;
/**
* @brief Indicates whether the handle represents a TTY-like device.
*
* @return Non-zero when treated as a TTY.
*/
virtual int isatty() const;

private:
bool _isInitialized = false;
bool _inTransmission = false;
void begin();
void begin(int baudRate);
};

/**
* @brief Global RS485 FileHandle instance used to redirect stdio.
*/
extern RS485FileHandle RS485Console;

#endif // ARDUINO_ARCH_MBED

#endif // RS485_FILE_HANDLE_H
Loading