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

Fix #319: cSerial<> didn't work for STM32 USB Serial. #320

Merged
merged 4 commits into from
Sep 13, 2021
Merged
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
2 changes: 1 addition & 1 deletion src/CatenaBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Copyright notice:
(((major) << 24u) | ((minor) << 16u) | ((patch) << 8u) | (local))

#define CATENA_ARDUINO_PLATFORM_VERSION \
CATENA_ARDUINO_PLATFORM_VERSION_CALC(0, 21, 2, 0) /* v0.21.2 */
CATENA_ARDUINO_PLATFORM_VERSION_CALC(0, 21, 3, 1) /* v0.21.3-1 */

#define CATENA_ARDUINO_PLATFORM_VERSION_GET_MAJOR(v) \
(((v) >> 24u) & 0xFFu)
Expand Down
106 changes: 101 additions & 5 deletions src/Catena_Serial.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ Copyright and License:

namespace McciCatena {

///
/// \brief Simple architecture that lets us use serial-like ports interchangably.
///
/// \details
/// The Arduino system omittted a virutal base class that all Serial ports
/// The Arduino system omitted a virtual base class that all Serial ports
/// can be derived from. This means you can't have a pointer to a "serial-like"
/// thing, which is very inconvenient. Even if we put one in our Arduino
/// variant, it would not help with third-party libraries.
Expand All @@ -46,7 +47,7 @@ namespace McciCatena {
class cSerialAbstract
{
public:
// constructor
/// \brief constructor
cSerialAbstract() {};

// neither copyable nor movable.
Expand Down Expand Up @@ -100,8 +101,10 @@ template <class T>
class cSerial : public cSerialAbstract
{
public:
// constructor
/// \brief constructor in case caller has a pointer to the underlying port.
cSerial(T *pPort) : m_pPort(pPort) {}

/// \brief constructor in case caller has the LV of the underlying port.
cSerial(T &Port) : m_pPort(&Port) {}

// neither copyable nor movable.
Expand All @@ -110,48 +113,141 @@ class cSerial : public cSerialAbstract
cSerial(const cSerial&&) = delete;
cSerial& operator=(const cSerial&&) = delete;

/// \brief return the number of bytes available in the read buffer.
virtual int available() const override
{
return this->m_pPort->available();
}

/// \brief initialize the port, setting the baudrate.
virtual void begin(unsigned long ulBaudRate) const override
{
this->m_pPort->begin(ulBaudRate);
}

private:
//
// The following section deals with the polymorphism in underlying serial ports:
// some have port::begin(baudrate, config), others only have begin(). We use
// C++ template metaprogramming to allow us to use the advanced APIs if available,
// otherwise to ignore the config parameter.
//
// We follow Walter Brown's method https://youtu.be/a0FliKwcwXE?t=2276 for querying
// properties at compile time.
//
// First, the default `hasBeginWithConfig`. The compiler will choose this template
// if no template is found that is well-formed and more specific, and this will
// result in a structure that is a specialiation of std::false_type.
//
template <class, class = void>
struct hasBeginWithConfig
: /* inherit from */ std::false_type /* so it has ::value == false */
{ /* no extra contents */ };

//
// Second, we define a version that will only be well-formed if the target class
// has U::begin(unsigned long, uint16_t), and it will be essentially identical to
// std::true_type.
//
template <class U>
struct hasBeginWithConfig<
U,
std::void_t<
/* packing a list of types, so take the type of... */
decltype(
/* check whether we can invoke begin() with our two params */
std::declval<U>().begin(
(unsigned long)0,
(std::uint16_t)0
)
)
>
>
: /* inherit from */ std::true_type /* so it has ::value == true */
{ /* no extra contents */ };

//
// Now overload beginWithConfig() with two versions, one that calls the 2-arg
// version of begin(), the other that calls the one-arg version.
//
// If we had C++ 2017, we could use if constexpr(), but it's not much worse to have
// overloads with two functions.
//
// This matches if the third argment is of type std::true_type.
//
template <class U>
static void beginWithConfig(U *pT, unsigned long ulBaudRate, uint16_t config, std::true_type)
{
pT->begin(ulBaudRate, config);
}

//
// This overload matches if the the third argument is of type std::false_type, and
// uses the one-argument begin().
//
template <typename U>
static void beginWithConfig(U* pT, unsigned long ulBaudRate, uint16_t config, std::false_type)
{
pT->begin(ulBaudRate);
}

public:
//
// finally, the begin() method here uses the hasBeginWithConfig<> template
// to select the approprialte beginWithConfig() overload.
//

///
/// \brief initialize port, setting baud rate and configuring.
///
virtual void begin(unsigned long ulBaudRate, uint16_t config) const override
{
this->m_pPort->begin(ulBaudRate, config);
this->beginWithConfig(
this->m_pPort,
ulBaudRate,
config,
hasBeginWithConfig<T> /* create object */ {}
);
}

///
/// \brief wait for the write buffer to be drained to serial port.
///
virtual void drainWrite() const override
{
this->m_pPort->flush();
}

///
/// \brief read one byte from a port.
///
/// \returns next byte (in 0..255) or a negative number if read buffer is empty.
///
virtual int read() const override
{
return this->m_pPort->read();
}

/// \brief write a buffer, and return the number of bytes actually written.
virtual size_t write(const uint8_t *buffer, size_t size) const override
{
return this->m_pPort->write(buffer, size);
}

/// \brief invoke end() method of concrete port.
virtual void end() const override
{
this->m_pPort->end();
}

/// \brief return pointer to underlying port viewed as a stream.
virtual Stream & stream() override
{
return *m_pPort;
}

private:
T *m_pPort;
T *m_pPort; ///< pointer to concrete UART.
};

} // namespace McciCatena
Expand Down