Skip to content

Commit

Permalink
Merge pull request #320 from mcci-catena/issue319
Browse files Browse the repository at this point in the history
Fix #319: cSerial<> didn't work for STM32 USB Serial.
  • Loading branch information
terrillmoore authored Sep 13, 2021
2 parents 27d4302 + b73106d commit aa6d14f
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 6 deletions.
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

0 comments on commit aa6d14f

Please sign in to comment.