From b13fa36c7eb3533dd55199c3493ce8221257ec25 Mon Sep 17 00:00:00 2001 From: Chris <52449218+shadow578@users.noreply.github.com> Date: Wed, 5 Feb 2025 14:32:07 +0000 Subject: [PATCH 01/15] implement SoftwareSerial library --- libraries/SoftwareSerial/README.md | 87 ++++ .../examples/SoftwareSerialExample/.gitignore | 2 + .../SoftwareSerialExample/platformio.ini | 17 + .../SoftwareSerialExample/src/main.cpp | 26 + .../SoftwareSerial/src/SoftwareSerial.cpp | 481 ++++++++++++++++++ libraries/SoftwareSerial/src/SoftwareSerial.h | 239 +++++++++ tools/platformio/platformio-build-arduino.py | 3 +- 7 files changed, 854 insertions(+), 1 deletion(-) create mode 100644 libraries/SoftwareSerial/README.md create mode 100644 libraries/SoftwareSerial/examples/SoftwareSerialExample/.gitignore create mode 100644 libraries/SoftwareSerial/examples/SoftwareSerialExample/platformio.ini create mode 100644 libraries/SoftwareSerial/examples/SoftwareSerialExample/src/main.cpp create mode 100644 libraries/SoftwareSerial/src/SoftwareSerial.cpp create mode 100644 libraries/SoftwareSerial/src/SoftwareSerial.h diff --git a/libraries/SoftwareSerial/README.md b/libraries/SoftwareSerial/README.md new file mode 100644 index 0000000..0a3c58f --- /dev/null +++ b/libraries/SoftwareSerial/README.md @@ -0,0 +1,87 @@ +# Software Serial for the HC32F460 + +The Software Serial library allows serial (UART) communication on any digital pin of the board, bit-banging the protocol. +It it possible to have multiple software serial ports. + +The implementation of this library is based on the [SoftwareSerial library of the STM32duino project](https://github.com/stm32duino/Arduino_Core_STM32/blob/main/libraries/SoftwareSerial/). + + +## Configuration Options + +To configure the library, you may add the following defines to your build environment. + +| Name | Default | Description | +|-|-|-| +| `SOFTWARE_SERIAL_BUFFER_SIZE` | `32` | size of the receive buffer. it's highly likely that any transmission longer than this will be partially lost. | +| `SOFTWARE_SERIAL_OVERSAMPLE` | `3` | oversampling rate. Each bit period is equal to OVERSAMPLE ticks, and bits are sampled in the middle | +| `SOFTWARE_SERIAL_HALF_DUPLEX_SWITCH_DELAY` | `5` | bit periods before half duplex switches TX to RX | +| `SOFTWARE_SERIAL_TIMER_PRESCALER` | `2` | prescaler of the TIMER0. set according to PCLK1 and desired baud rate range | +| `SOFTWARE_SERIAL_TIMER0_UNIT` | `TIMER01B_config` | TIMER0 unit to use for software serial. Using TIMER01A is not recommended | +| `SOFTWARE_SERIAL_TIMER_PRIORITY` | `3` | interrupt priority of the timer interrupt | +| `SOFTWARE_SERIAL_FLUSH_CLEARS_RX_BUFFER` | `0` | behaviour of the `flush()` method. `0` = waits for pending TX to complete. `1` = clear RX buffer. STMduino library uses behaviour `1` | + + +### Calculating `SOFTWARE_SERIAL_TIMER_PRESCALER` + +to calculate, use the following c++ program + +```cpp +#include +#include + +float get_real_frequency(const uint32_t frequency, const uint16_t prescaler) +{ + const uint32_t base_frequency = 50000000; // 50 MHz PCLK1 + + // calculate the compare value needed to match the target frequency + // CMP = (base_freq / prescaler) / frequency + uint32_t compare = (base_frequency / uint32_t(prescaler)) / frequency; + + if (compare <= 0 || compare > 0xFFFF) + { + return -1; + } + + // calculate the real frequency + float real_frequency = (base_frequency / prescaler) / compare; + return real_frequency; +} + + +int main() +{ + const uint32_t baud = 9600; + const uint32_t oversampling = 3; + + const uint32_t frequency = baud * oversampling; + const uint16_t prescalers[] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024}; + + float min_error = 100000; + uint16_t best_prescaler = 0; + for (auto p : prescalers) + { + const float real_frequency = get_real_frequency(frequency, p); + const float error = std::abs(real_frequency - frequency); + if (error < min_error) + { + min_error = error; + best_prescaler = p; + } + + std::cout << "Prescaler: " << static_cast(p) + << ", Real frequency: " << real_frequency + << ", Error: " << error + << std::endl; + } + + float best_real_frequency = get_real_frequency(frequency, best_prescaler); + float best_baud = best_real_frequency / oversampling; + float baud_percent_error = (best_baud - baud) / baud * 100; + std::cout << "Best prescaler: " << static_cast(best_prescaler) + << ", Real frequency: " << best_real_frequency + << ", Error: " << min_error + << ", Real baud rate: " << best_baud + << ", Baud rate error: " << baud_percent_error << "%" + << std::endl; +} +``` diff --git a/libraries/SoftwareSerial/examples/SoftwareSerialExample/.gitignore b/libraries/SoftwareSerial/examples/SoftwareSerialExample/.gitignore new file mode 100644 index 0000000..b9f3806 --- /dev/null +++ b/libraries/SoftwareSerial/examples/SoftwareSerialExample/.gitignore @@ -0,0 +1,2 @@ +.pio +.vscode diff --git a/libraries/SoftwareSerial/examples/SoftwareSerialExample/platformio.ini b/libraries/SoftwareSerial/examples/SoftwareSerialExample/platformio.ini new file mode 100644 index 0000000..83ec8fa --- /dev/null +++ b/libraries/SoftwareSerial/examples/SoftwareSerialExample/platformio.ini @@ -0,0 +1,17 @@ +[env] +platform = https://github.com/shadow578/platform-hc32f46x/archive/1.1.0.zip +framework = arduino +board = generic_hc32f460 +build_flags = + -D USART_AUTO_CLKDIV_OS_CONFIG # enable auto clock division and oversampling configuration (recommended) + -D __DEBUG=1 + -D __CORE_DEBUG=1 + +[env:default] + +# required only for CI +[env:ci] +# override the framework-arduino-hc32f46x package with the local one +board_build.arduino_package_dir = ../../../../ +extra_scripts = + pre:../../../../tools/ci/patch_get_package_dir.py diff --git a/libraries/SoftwareSerial/examples/SoftwareSerialExample/src/main.cpp b/libraries/SoftwareSerial/examples/SoftwareSerialExample/src/main.cpp new file mode 100644 index 0000000..c9e874b --- /dev/null +++ b/libraries/SoftwareSerial/examples/SoftwareSerialExample/src/main.cpp @@ -0,0 +1,26 @@ +/* + * Software serial basic example. + * + * echos any character received on the software serial port back to the sender. + */ +#include +#include + +constexpr gpio_pin_t RX_PIN = PA15; +constexpr gpio_pin_t TX_PIN = PA9; + +SoftwareSerial mySerial(/* RX */ RX_PIN, /* TX */ TX_PIN); + +void setup() +{ + mySerial.begin(9600); + mySerial.println("Hello, world!"); +} + +void loop() +{ + while (mySerial.available()) + mySerial.write(mySerial.read()); + + delay(10); +} diff --git a/libraries/SoftwareSerial/src/SoftwareSerial.cpp b/libraries/SoftwareSerial/src/SoftwareSerial.cpp new file mode 100644 index 0000000..836d28b --- /dev/null +++ b/libraries/SoftwareSerial/src/SoftwareSerial.cpp @@ -0,0 +1,481 @@ +#include "SoftwareSerial.h" +#include + +#warning "SoftwareSerial on HC32F460 is experimental!" + +static_assert(SOFTWARE_SERIAL_BUFFER_SIZE > 0, "SOFTWARE_SERIAL_BUFFER_SIZE must be > 0"); +static_assert(SOFTWARE_SERIAL_OVERSAMPLE >= 3, "SOFTWARE_SERIAL_OVERSAMPLE must be >= 3"); +static_assert(SOFTWARE_SERIAL_HALF_DUPLEX_SWITCH_DELAY >= 0, "SOFTWARE_SERIAL_HALF_DUPLEX_SWITCH_DELAY must be >= 0"); + +#ifdef __CORE_DEBUG +/*static*/ uint8_t SoftwareSerial::next_id = 0; + +#define SOFTSERIAL_DEBUG_PRINTF(fmt, ...) \ + CORE_DEBUG_PRINTF("[SoftwareSerial#%u] " fmt, this->id, ##__VA_ARGS__) + +#define SOFTSERIAL_STATIC_DEBUG_PRINTF(fmt, ...) \ + CORE_DEBUG_PRINTF("[SoftwareSerial] " fmt, ##__VA_ARGS__) +#else +#define SOFTSERIAL_DEBUG_PRINTF(fmt, ...) +#define SOFTSERIAL_STATIC_DEBUG_PRINTF(fmt, ...) +#endif + +SoftwareSerial::SoftwareSerial(const gpio_pin_t rx_pin, const gpio_pin_t tx_pin, const bool invert) + : + #ifdef __CORE_DEBUG + id(next_id++), + #endif + rx_pin(rx_pin), tx_pin(tx_pin), invert(invert) +{ + this->rx_buffer = new RingBuffer(SOFTWARE_SERIAL_BUFFER_SIZE); + CORE_ASSERT(this->rx_buffer != nullptr, ""); +} + +SoftwareSerial::~SoftwareSerial() +{ + end(); + delete this->rx_buffer; +} + +void SoftwareSerial::begin(const uint32_t baud) +{ + SOFTSERIAL_DEBUG_PRINTF("begin: rx=%u, tx=%u, invert=%d, baud=%lu, half-duplex=%d\n", + rx_pin, + tx_pin, + invert, + baud, + is_half_duplex()); + + this->baud = baud; + + // half-duplex starts out in TX mode, so it is always enabled + setup_tx(); + if (!is_half_duplex()) + { + setup_rx(); + listen(); + } + + // make the timer ISR call this instance + add_listener(this); +} + +void SoftwareSerial::end() +{ + SOFTSERIAL_DEBUG_PRINTF("end\n"); + stopListening(); + remove_listener(this); +} + +bool SoftwareSerial::listen() +{ + rx_wait_ticks = 1; // next interrupt will check for start bit + rx_bit_count = -1; // wait for start bit + + // change the speed of the timer + // this function automatically waits for all pending TX operations to finish + const bool did_speed_change = timer_set_speed(baud); + + // enable RX + if (is_half_duplex()) + { + set_half_duplex_mode(true /*=RX*/); + } + else + { + rx_active = true; + } + + SOFTSERIAL_DEBUG_PRINTF("started listening @baud=%lu; did_speed_change=%d\n", baud, did_speed_change); + return did_speed_change; +} + +bool SoftwareSerial::isListening() +{ + return current_timer_speed == baud; +} + +bool SoftwareSerial::stopListening() +{ + // wait for any pending TX operations to finish + while (tx_active) + yield(); + + // disable RX + const bool was_listening = rx_active || tx_active; + if (is_half_duplex()) + { + set_half_duplex_mode(false /*=TX*/); + } + else + { + rx_active = false; + } + + // if no other instance is listening, stop the timer + bool any_listening = false; + ListenerItem *item = listeners; + while (item != nullptr) + { + if (item->listener->isListening()) + { + any_listening = true; + break; + } + + item = item->next; + } + + if (!any_listening) + { + timer_set_speed(0); + } + + SOFTSERIAL_DEBUG_PRINTF("stopped listening; was_listening=%d\n", was_listening); + return was_listening; +} + +bool SoftwareSerial::overflow() +{ + const bool overflow = did_rx_overflow; + did_rx_overflow = false; + return overflow; +} + +int SoftwareSerial::peek() +{ + return rx_buffer->peek(); +} + +size_t SoftwareSerial::write(const uint8_t byte) +{ + // in case this is half-duplex, setting tx_pending will avert the switch to RX mode + tx_pending = true; + + // wait for previous TX to finish + while (tx_active) + yield(); + + // add start and stop bits + tx_frame = (byte << 1) | 0x200; + if (invert) + { + tx_frame = ~tx_frame; + } + + // ensure timer is running at the correct speed + timer_set_speed(baud); + + // ensure TX is enabled in half-duplex mode + // this call is a no-op if not in half-duplex mode, so no additional check + set_half_duplex_mode(false /*=TX*/); + + // start transmission on next interrupt + tx_bit_count = 0; + tx_wait_ticks = 1; + + tx_pending = false; + tx_active = true; + return 1; +} + +int SoftwareSerial::read() +{ + uint8_t e; + if (rx_buffer->pop(e)) + { + return e; + } + + return -1; +} + +int SoftwareSerial::available() +{ + return rx_buffer->count(); +} + +void SoftwareSerial::flush() +{ +#if SOFTWARE_SERIAL_FLUSH_CLEARS_RX_BUFFER == 1 + // clear RX buffer + rx_buffer->clear(); +#else + // wait for any pending TX operations to finish + while (tx_active) + yield(); +#endif +} + +void SoftwareSerial::setup_rx() +{ + SOFTSERIAL_DEBUG_PRINTF("setup_rx on %u\n", rx_pin); + + // UART idle line is high, so set pull-up for non-inverted logic + // HC32 has no pull-down, so inverted logic will have to do without + pinMode(rx_pin, invert ? INPUT : INPUT_PULLUP); +} + +void SoftwareSerial::setup_tx() +{ + SOFTSERIAL_DEBUG_PRINTF("setup_tx on %u\n", tx_pin); + + // set pin level before setting as output to avoid glitches + // UART idle line is high, so set high for non-inverted logic and vice versa + if (invert) GPIO_ResetBits(tx_pin); + else GPIO_SetBits(tx_pin); + + pinMode(tx_pin, OUTPUT); +} + +void SoftwareSerial::set_half_duplex_mode(const bool rx) +{ + // if not half-duplex mode, ignore this + if (!is_half_duplex()) return; + + if (rx) + { + tx_active = false; + setup_rx(); + + rx_bit_count = -1; // waiting for start bit + rx_wait_ticks = 2; // wait 2 bit times for start bit + rx_active = true; + } + else + { + if (rx_active) + { + rx_active = false; + setup_tx(); + } + } +} + +// +// ISR +// + +void SoftwareSerial::do_rx() +{ + // if not enabled, do nothing + if (!rx_active) return; + + // if tick count is non-zero, continue waiting + rx_wait_ticks--; + if (rx_wait_ticks > 0) return; + + // read bit, invert if inverted logic + const bool bit = GPIO_GetBit(rx_pin) ^ invert; + + // waiting for start bit? + if (rx_bit_count == -1) + { + // is start bit? + // this is UART, so idle line is high and start bit is going low + if (!bit) + { + rx_frame = 0; + rx_bit_count = 0; + + // wait 1 1/2 bit times to sample in the middle of the bit + rx_wait_ticks = SOFTWARE_SERIAL_OVERSAMPLE + (SOFTWARE_SERIAL_OVERSAMPLE >> 1); + } + else + { + // waiting for start bit, but didn't get it + // wait for next interrupt to check again + rx_wait_ticks = 1; + } + } + else if (rx_bit_count >= 8) // waiting for stop bit? + { + // is stop bit? + // this is UART, so stop bit (== idle line) is high + if (bit) + { + // add byte to buffer + bool overflow; + rx_buffer->push(rx_frame, true, overflow); + + // avoid overwriting overflow flag + if (overflow) did_rx_overflow = true; + } + + // assume frame is completed, wait for next start bit at next interrupt + // even if there was no stop bit + rx_bit_count = -1; + rx_wait_ticks = 1; + } + else // data bits + { + rx_frame >>= 1; + if (bit) rx_frame |= 0x80; + rx_bit_count++; + rx_wait_ticks = SOFTWARE_SERIAL_OVERSAMPLE; + } + +} + +void SoftwareSerial::do_tx() +{ + // if not enabled, do nothing + if (!tx_active) return; + + // if tick count is non-zero, continue waiting + tx_wait_ticks--; + if (tx_wait_ticks > 0) return; + + // all bits in frame sent? + if (tx_bit_count >= 10) + { + // if no frame is pending and half-duplex, switch to RX mode + // otherwise, we're done transmitting + if (!tx_pending && is_half_duplex()) + { + // wait HALF_DUPLEX_SWITCH_DELAY bits before switching to RX + if (tx_bit_count >= 10 + SOFTWARE_SERIAL_HALF_DUPLEX_SWITCH_DELAY) + { + set_half_duplex_mode(true /*=RX*/); + } + } + else + { + tx_active = false; + } + } + + // send next bit + if (tx_frame & 1) GPIO_SetBits(tx_pin); + else GPIO_ResetBits(tx_pin); + + tx_frame >>= 1; + tx_bit_count++; + tx_wait_ticks = SOFTWARE_SERIAL_OVERSAMPLE; +} + +// +// Timer control +// + +/*static*/ uint32_t SoftwareSerial::current_timer_speed = 0; +/*static*/ Timer0 SoftwareSerial::timer(&SOFTWARE_SERIAL_TIMER0_UNIT, SoftwareSerial::timer_isr); +/*static*/ SoftwareSerial::ListenerItem *SoftwareSerial::listeners = nullptr; + +/*static*/ bool SoftwareSerial::timer_set_speed(const uint32_t baud) +{ + if (current_timer_speed == baud) return false; + + // stop timer? + if (baud == 0) + { + SOFTSERIAL_STATIC_DEBUG_PRINTF("timer_set_speed stopping timer\n"); + timer.pause(); + timer.stop(); + current_timer_speed = 0; + return true; + } + + // speed change operations are fairly costly because they block until pending TX operations finish + // so print a warning if this happens + if (current_timer_speed != 0) + { + SOFTSERIAL_STATIC_DEBUG_PRINTF("baud rate change from %lu to %lu. Consider configuring all your software serials to the same baud rate to improve performance.\n", + current_timer_speed, + baud); + + // wait for all pending TX operations in active channels to finish before changing speed + ListenerItem *item = listeners; + while (item != nullptr) + { + while (item->listener->tx_active) + yield(); + + item = item->next; + } + } + + SOFTSERIAL_STATIC_DEBUG_PRINTF("timer_set_speed baud=%lu\n", baud); + + // (re-) initialize timer to the baud rate frequency, with oversampling + // if already running, timer will automatically stop in the start() call + timer.start(baud * SOFTWARE_SERIAL_OVERSAMPLE, SOFTWARE_SERIAL_TIMER_PRESCALER); + + // set priority if initial start + if (current_timer_speed == 0) + { + setInterruptPriority(SOFTWARE_SERIAL_TIMER_PRIORITY); + } + current_timer_speed = baud; + + timer.resume(); // needed to actually start the timer + return true; + +} + +/*static*/ void SoftwareSerial::add_listener(SoftwareSerial *listener) +{ + // pause timer while modifying listener list to avoid race conditions + timer.pause(); + + ListenerItem *item = new ListenerItem; + CORE_ASSERT(item != nullptr, ""); + + item->listener = listener; + item->next = listeners; + listeners = item; + + timer.resume(); +} + +/*static*/ void SoftwareSerial::remove_listener(SoftwareSerial *listener) +{ + ListenerItem *prev = nullptr; + ListenerItem *item = listeners; + while (item != nullptr) + { + if (item->listener == listener) + { + // pause timer while modifying listener list to avoid race conditions + timer.pause(); + + if (prev == nullptr) + { + listeners = item->next; + } + else + { + prev->next = item->next; + } + + timer.resume(); + + delete item; + return; + } + + prev = item; + item = item->next; + } +} + +/*static*/ void SoftwareSerial::timer_isr() +{ + ListenerItem *item = listeners; + while (item != nullptr) + { + // only call RX/TX if instance uses the correct baud rate + if (item->listener->isListening()) + { + item->listener->do_tx(); + item->listener->do_rx(); + } + + item = item->next; + } +} + +/*static*/ void SoftwareSerial::setInterruptPriority(const uint32_t priority) +{ + timer.setCallbackPriority(priority); +} diff --git a/libraries/SoftwareSerial/src/SoftwareSerial.h b/libraries/SoftwareSerial/src/SoftwareSerial.h new file mode 100644 index 0000000..af71bc5 --- /dev/null +++ b/libraries/SoftwareSerial/src/SoftwareSerial.h @@ -0,0 +1,239 @@ +#ifndef SOFTWARESERIAL_H +#define SOFTWARESERIAL_H + +#include +#include +#include + +#ifndef SOFTWARE_SERIAL_BUFFER_SIZE +#define SOFTWARE_SERIAL_BUFFER_SIZE 32 +#endif + +#ifndef SOFTWARE_SERIAL_OVERSAMPLE +#define SOFTWARE_SERIAL_OVERSAMPLE 3 +#endif + +#ifndef SOFTWARE_SERIAL_HALF_DUPLEX_SWITCH_DELAY +#define SOFTWARE_SERIAL_HALF_DUPLEX_SWITCH_DELAY 5 // bit-periods +#endif + +#ifndef SOFTWARE_SERIAL_TIMER_PRESCALER +#define SOFTWARE_SERIAL_TIMER_PRESCALER 2 +#endif + +// recommented to not use TIMER0 Unit 1 Channel A, as it does not support sync mode +#ifndef SOFTWARE_SERIAL_TIMER0_UNIT +#define SOFTWARE_SERIAL_TIMER0_UNIT TIMER01B_config // Timer0 Unit 1, Channel B +#endif + +#ifndef SOFTWARE_SERIAL_TIMER_PRIORITY +#define SOFTWARE_SERIAL_TIMER_PRIORITY 3 +#endif + +// how SoftwareSerial behaves when flush() is called +// when 0: flush() will wait for all pending TX operations to finish (Arduinio >1.0 behavior) +// when 1: flush() will clear the RX buffer (old behaviour; how the STM32duino library does it) +#ifndef SOFTWARE_SERIAL_FLUSH_CLEARS_RX_BUFFER +#define SOFTWARE_SERIAL_FLUSH_CLEARS_RX_BUFFER 0 +#endif + +/** + * Software Serial implementation using Timer0. + * loosely based on STM32duino SoftwareSerial library. + * see https://github.com/stm32duino/Arduino_Core_STM32/blob/main/libraries/SoftwareSerial/ + * + * @note + * This SoftwareSerial implementation has some caveats due to technical limitations: + * a) While you may define as many software serial instances as you want, there can + * only ever be one active baud rate at a time. + * This means that two instances at the same baud rate can run at the same time, + * but if you write to a software serial instance with a different baud rate, + * the other instances will stop sending and receiving data until you call listen() on one of them. + * b) Switching the baud rate is fairly slow, because it waits for all pending TX operations to finish. + * Additionally, a baud rate switch may cause data loss on the RX side. + * Due to this, it is recommended to configure all software serial instances to the same baud rate. + * c) The timer prescaler must be manually chosen to match the desired baud rate range. + * Set SOFTWARE_SERIAL_TIMER_PRESCALER such that the frequency error is minimal. + * The default value of 2 is good for PCLK1=50MHz and OVERSAMPLE=3 for baud rates + * between1200 to 38400 baud, with < 0.01% error. + * For baud rates between 38400 and 115200, a prescaler of 1 is recommended (< 0.5% error). + */ +class SoftwareSerial : public Stream +{ +#ifdef __CORE_DEBUG +private: + static uint8_t next_id; + const uint8_t id; +#endif + +public: + /** + * @brief create a SoftwareSerial instance + * @param rx_pin receive pin + * @param tx_pin transmit pin + * @param invert invert high and low on RX and TX lines + * @note when rx_pin == tx_pin, half-duplex mode is enabled + */ + SoftwareSerial(const gpio_pin_t rx_pin, const gpio_pin_t tx_pin, const bool invert = false); + virtual ~SoftwareSerial(); + + /** + * @brief setup the software serial + * @param baud baud rate + */ + void begin(const uint32_t baud); + + /** + * @brief de-initialize the software serial + */ + void end(); + + /** + * @brief start listening for incoming data + * @returns true if a speed change occurred. + * If this is the case, serials using a different baud rate will + * stop sending and receiving data util listen() is called on them. + */ + bool listen(); + + /** + * @brief check if this software serial is listening + * @note + * multiple software serials can be listening at the same time, + * as long as they are using the same baud rate. + */ + bool isListening(); + + /** + * @brief stop listening for incoming data + * @returns true if this software serial was previously listening + */ + bool stopListening(); + + bool overflow(); + + int peek(); + + virtual size_t write(const uint8_t byte); + virtual int read(); + virtual int available(); + virtual void flush(); + + operator bool() + { + return true; + } + + using Print::write; + +private: // common + const gpio_pin_t rx_pin; + const gpio_pin_t tx_pin; + const bool invert; + uint32_t baud; + + inline bool is_half_duplex() + { + return rx_pin == tx_pin; + } + + /** + * @brief setup RX pin for receiving + */ + void setup_rx(); + + /** + * @brief setup TX pin for transmitting + */ + void setup_tx(); + + /** + * @brief setup RX and TX pins for half-duplex communication + * @param rx true for RX, false for TX + * @note no-op if not in half-duplex mode + */ + void set_half_duplex_mode(const bool rx); + +private: // RX logic + RingBuffer *rx_buffer; + bool did_rx_overflow : 1; + bool rx_active : 1; + + uint8_t rx_frame = 0; // 8 bits + int8_t rx_bit_count = -1; // -1 means waiting for start bit + int8_t rx_wait_ticks = 0; + + /** + * @brief receive a single bit. called by the timer ISR + */ + void do_rx(); + +private: // TX logic + bool tx_active : 1; + bool tx_pending : 1; + + uint16_t tx_frame = 0; // 10 bits + int8_t tx_bit_count = 0; + int8_t tx_wait_ticks = 0; + + /** + * @brief transmit a single bit. called by the timer ISR + */ + void do_tx(); + +private: // Timer0 ISR logic + /** + * @brief baud rate that software serial is running at (ALL of them). + * @note 0 if not initialized + */ + static uint32_t current_timer_speed; + + /** + * @brief Timer0 instance + */ + static Timer0 timer; + + /** + * @brief set the timer baud rate + * @param baud baud rate to set the timer to. 0 to stop the timer + * @return true if a speed change occurred + */ + static bool timer_set_speed(const uint32_t baud); + + struct ListenerItem + { + SoftwareSerial *listener; + ListenerItem *next; + }; + + /** + * @brief list of software serials that should be called in the timer ISR + */ + static ListenerItem *listeners; + + /** + * @brief add a listener to the timer ISR + * @param listener software serial instance to add + */ + static void add_listener(SoftwareSerial *listener); + + /** + * @brief remove a listener from the timer ISR + * @param listener software serial instance to remove + */ + static void remove_listener(SoftwareSerial *listener); + + /** + * @brief timer callback + */ + static void timer_isr(); + +public: + /** + * @brief set the interrupt priority for the SoftwareSerial timer + * @param priority interrupt priority to set + */ + static void setInterruptPriority(const uint32_t priority); +}; + +#endif // SOFTWARESERIAL_H diff --git a/tools/platformio/platformio-build-arduino.py b/tools/platformio/platformio-build-arduino.py index 2ccbf7d..f39d5ae 100644 --- a/tools/platformio/platformio-build-arduino.py +++ b/tools/platformio/platformio-build-arduino.py @@ -124,7 +124,8 @@ def get_version_defines() -> list[str]: "spi", "sram", "timera", - "usart", + "timer0", + "usart" ] for req in core_requirements: board.update(f"build.ddl.{req}", "true") From c9c83c18f83c7d0c865cd9eefbf06d4d620f181e Mon Sep 17 00:00:00 2001 From: Chris <52449218+shadow578@users.noreply.github.com> Date: Thu, 6 Feb 2025 13:46:30 +0000 Subject: [PATCH 02/15] CI: include library examples in smoke test --- .github/workflows/build_examples.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_examples.yaml b/.github/workflows/build_examples.yaml index d554f88..d66bf69 100644 --- a/.github/workflows/build_examples.yaml +++ b/.github/workflows/build_examples.yaml @@ -23,8 +23,8 @@ jobs: - name: Generate build matrix id: gen_matrix run: | - # Get all subdirectories containing platformio.ini in the ./example directory - subdirectories=$(find ./examples -type f -name "platformio.ini" -exec dirname {} \; | sort -u) + # Get all subdirectories containing platformio.ini in the ./examples and ./libraries/*/examples directories + subdirectories=$(find ./examples ./libraries/*/examples -type f -name "platformio.ini" -exec dirname {} \; | sort -u) # Convert subdirectories to JSON array json_array="[" From 6b3b5ce86a49480f1571787e6e7cb4d0ec452ce2 Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:52:41 +0100 Subject: [PATCH 03/15] fix SoftwareSerial TX RX+TX in full-duplex work fully now --- libraries/SoftwareSerial/src/SoftwareSerial.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/libraries/SoftwareSerial/src/SoftwareSerial.cpp b/libraries/SoftwareSerial/src/SoftwareSerial.cpp index 836d28b..96ac626 100644 --- a/libraries/SoftwareSerial/src/SoftwareSerial.cpp +++ b/libraries/SoftwareSerial/src/SoftwareSerial.cpp @@ -331,18 +331,22 @@ void SoftwareSerial::do_tx() { // if no frame is pending and half-duplex, switch to RX mode // otherwise, we're done transmitting - if (!tx_pending && is_half_duplex()) + if (tx_pending) + { + tx_active = false; + } + else if (is_half_duplex() && isListening()) { // wait HALF_DUPLEX_SWITCH_DELAY bits before switching to RX + // NOTE: in STM32dunio, they wait for 10 + (OVERSAMPLE * HALF_DUPLEX_SWITCH_DELAY) bits. i believe this is a bug on their end tho if (tx_bit_count >= 10 + SOFTWARE_SERIAL_HALF_DUPLEX_SWITCH_DELAY) { set_half_duplex_mode(true /*=RX*/); } } - else - { - tx_active = false; - } + + tx_wait_ticks = 1; + return; } // send next bit From 9f3b260288e3a0d2cfea9e8a0e66c0db6d57dd74 Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Fri, 7 Feb 2025 18:41:12 +0100 Subject: [PATCH 04/15] fix SoftwareSerial half-duplex mode switchover --- libraries/SoftwareSerial/src/SoftwareSerial.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/SoftwareSerial/src/SoftwareSerial.cpp b/libraries/SoftwareSerial/src/SoftwareSerial.cpp index 96ac626..44e9331 100644 --- a/libraries/SoftwareSerial/src/SoftwareSerial.cpp +++ b/libraries/SoftwareSerial/src/SoftwareSerial.cpp @@ -343,6 +343,9 @@ void SoftwareSerial::do_tx() { set_half_duplex_mode(true /*=RX*/); } + + // keep incrementing bit count to time the above check + tx_bit_count++; } tx_wait_ticks = 1; From 5c98b62fb04637630d2a4a9f839481e1cf5a83a9 Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 8 Feb 2025 11:24:59 +0100 Subject: [PATCH 05/15] print actually achieved baud rate on SoftwareSerial timer speed change --- .../SoftwareSerial/src/SoftwareSerial.cpp | 11 ++- libraries/Timer0/src/Timer0.cpp | 70 +++++++++++++++++++ libraries/Timer0/src/Timer0.h | 8 +++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/libraries/SoftwareSerial/src/SoftwareSerial.cpp b/libraries/SoftwareSerial/src/SoftwareSerial.cpp index 44e9331..1385085 100644 --- a/libraries/SoftwareSerial/src/SoftwareSerial.cpp +++ b/libraries/SoftwareSerial/src/SoftwareSerial.cpp @@ -402,8 +402,6 @@ void SoftwareSerial::do_tx() } } - SOFTSERIAL_STATIC_DEBUG_PRINTF("timer_set_speed baud=%lu\n", baud); - // (re-) initialize timer to the baud rate frequency, with oversampling // if already running, timer will automatically stop in the start() call timer.start(baud * SOFTWARE_SERIAL_OVERSAMPLE, SOFTWARE_SERIAL_TIMER_PRESCALER); @@ -415,6 +413,15 @@ void SoftwareSerial::do_tx() } current_timer_speed = baud; + #ifdef __CORE_DEBUG + const float actual_baud = (timer.get_actual_frequency() / SOFTWARE_SERIAL_OVERSAMPLE); + SOFTSERIAL_STATIC_DEBUG_PRINTF("timer_set_speed target baud=%lu; actual baud=%d.%d\n", + baud, + static_cast(actual_baud), + static_cast((actual_baud - static_cast(actual_baud)) * 100) + ); + #endif + timer.resume(); // needed to actually start the timer return true; diff --git a/libraries/Timer0/src/Timer0.cpp b/libraries/Timer0/src/Timer0.cpp index 43af4b8..a5e2e9c 100644 --- a/libraries/Timer0/src/Timer0.cpp +++ b/libraries/Timer0/src/Timer0.cpp @@ -63,6 +63,42 @@ inline en_tim0_clock_div_t numeric_to_clock_div(const uint16_t n) } } +/** + * @brief convert en_tim0_clock_div_t to numerical value + * @note assert fails if invalid value + */ +inline uint16_t clock_div_to_numeric(const en_tim0_clock_div_t div) +{ + switch (div) + { + case Tim0_ClkDiv0: + return 1; + case Tim0_ClkDiv2: + return 2; + case Tim0_ClkDiv4: + return 4; + case Tim0_ClkDiv8: + return 8; + case Tim0_ClkDiv16: + return 16; + case Tim0_ClkDiv32: + return 32; + case Tim0_ClkDiv64: + return 64; + case Tim0_ClkDiv128: + return 128; + case Tim0_ClkDiv256: + return 256; + case Tim0_ClkDiv512: + return 512; + case Tim0_ClkDiv1024: + return 1024; + default: + CORE_ASSERT_FAIL("Invalid clock divider value"); + return 1; + } +} + /** * @brief timer0 interrupt registration */ @@ -224,3 +260,37 @@ void Timer0::stop() TIMER0_DEBUG_PRINTF("stopped channel\n"); } + +float Timer0::get_actual_frequency() +{ + // get timer channel base frequency, refer to start() for details + uint32_t base_frequency; + if (this->config->peripheral.register_base == M4_TMR01 && this->config->interrupt.interrupt_source == INT_TMR01_GCMA) + { + base_frequency = LRC_VALUE; + } + else + { + update_system_clock_frequencies(); + base_frequency = SYSTEM_CLOCK_FREQUENCIES.pclk1; + } + + // get prescaler from peripheral registers + en_tim0_clock_div_t prescaler_reg; + if (this->config->peripheral.channel == Tim0_ChannelA) + { + prescaler_reg = static_cast(this->config->peripheral.register_base->BCONR_f.CKDIVA); + } + else + { + prescaler_reg = static_cast(this->config->peripheral.register_base->BCONR_f.CKDIVB); + } + + const uint16_t prescaler = clock_div_to_numeric(prescaler_reg); + + // get compare value from peripheral registers + const uint16_t compare = TIMER0_GetCmpReg(this->config->peripheral.register_base, this->config->peripheral.channel); + + // calculate actual frequency + return (static_cast(base_frequency) / static_cast(prescaler)) / static_cast(compare); +} diff --git a/libraries/Timer0/src/Timer0.h b/libraries/Timer0/src/Timer0.h index 5b5bff6..ba4c3a4 100644 --- a/libraries/Timer0/src/Timer0.h +++ b/libraries/Timer0/src/Timer0.h @@ -174,6 +174,14 @@ class Timer0 TIMER0_ClearFlag(this->config->peripheral.register_base, this->config->peripheral.channel); } + /** + * @brief get the actual frequency of the timer0 channel + * @return actual frequency of the timer0 channel + * @note calculates the frequency from the live register values, so this + * is what the timer is currently running at. + */ + float get_actual_frequency(); + private: timer0_channel_config_t *config; voidFuncPtr callback; From 29c5752c432011ed42e43593ea65919335333526 Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 8 Feb 2025 11:51:08 +0100 Subject: [PATCH 06/15] handle re-calling begin() on SoftwareSerial re-initializes the instance to the new baud rate --- .../SoftwareSerialExample/src/main.cpp | 30 ++++++++++++++++++- .../SoftwareSerial/src/SoftwareSerial.cpp | 12 ++++++-- libraries/SoftwareSerial/src/SoftwareSerial.h | 8 +++-- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/libraries/SoftwareSerial/examples/SoftwareSerialExample/src/main.cpp b/libraries/SoftwareSerial/examples/SoftwareSerialExample/src/main.cpp index c9e874b..b77328c 100644 --- a/libraries/SoftwareSerial/examples/SoftwareSerialExample/src/main.cpp +++ b/libraries/SoftwareSerial/examples/SoftwareSerialExample/src/main.cpp @@ -2,6 +2,9 @@ * Software serial basic example. * * echos any character received on the software serial port back to the sender. + * also allows to change the baud rate by sending a number from 1 to 8. + * + * without changing the system clock, the maximum baud rate is around 9600 baud. */ #include #include @@ -20,7 +23,32 @@ void setup() void loop() { while (mySerial.available()) - mySerial.write(mySerial.read()); + { + const char c = mySerial.read(); + mySerial.write(c); + + uint32_t new_baud = 0; + switch(c) + { + case '1': new_baud = 1200; break; + case '2': new_baud = 2400; break; + case '3': new_baud = 4800; break; + case '4': new_baud = 9600; break; + case '5': new_baud = 19200; break; + case '6': new_baud = 38400; break; + case '7': new_baud = 57600; break; + case '8': new_baud = 115200; break; + default: break; + } + if (new_baud != 0) + { + mySerial.print("Set baud to:"); + mySerial.print(new_baud); + delay(100); + mySerial.begin(new_baud); + mySerial.println(" - Done"); + } + } delay(10); } diff --git a/libraries/SoftwareSerial/src/SoftwareSerial.cpp b/libraries/SoftwareSerial/src/SoftwareSerial.cpp index 1385085..53655ab 100644 --- a/libraries/SoftwareSerial/src/SoftwareSerial.cpp +++ b/libraries/SoftwareSerial/src/SoftwareSerial.cpp @@ -39,6 +39,12 @@ SoftwareSerial::~SoftwareSerial() void SoftwareSerial::begin(const uint32_t baud) { + // if already started once, end first + if (this->baud != 0) + { + end(); + } + SOFTSERIAL_DEBUG_PRINTF("begin: rx=%u, tx=%u, invert=%d, baud=%lu, half-duplex=%d\n", rx_pin, tx_pin, @@ -65,6 +71,7 @@ void SoftwareSerial::end() SOFTSERIAL_DEBUG_PRINTF("end\n"); stopListening(); remove_listener(this); + this->baud = 0; } bool SoftwareSerial::listen() @@ -117,7 +124,8 @@ bool SoftwareSerial::stopListening() ListenerItem *item = listeners; while (item != nullptr) { - if (item->listener->isListening()) + // don't check this instance, we're already stopping ;) + if (item->listener != this && item->listener->isListening()) { any_listening = true; break; @@ -331,7 +339,7 @@ void SoftwareSerial::do_tx() { // if no frame is pending and half-duplex, switch to RX mode // otherwise, we're done transmitting - if (tx_pending) + if (tx_pending || !is_half_duplex()) { tx_active = false; } diff --git a/libraries/SoftwareSerial/src/SoftwareSerial.h b/libraries/SoftwareSerial/src/SoftwareSerial.h index af71bc5..96b4f3a 100644 --- a/libraries/SoftwareSerial/src/SoftwareSerial.h +++ b/libraries/SoftwareSerial/src/SoftwareSerial.h @@ -55,8 +55,10 @@ * c) The timer prescaler must be manually chosen to match the desired baud rate range. * Set SOFTWARE_SERIAL_TIMER_PRESCALER such that the frequency error is minimal. * The default value of 2 is good for PCLK1=50MHz and OVERSAMPLE=3 for baud rates - * between1200 to 38400 baud, with < 0.01% error. - * For baud rates between 38400 and 115200, a prescaler of 1 is recommended (< 0.5% error). + * between 1200 to 38400 baud, with < 0.01% error. + * For baud rates between 38400 and 115200, a prescaler of 1 is recommended (< 0.5% error), + * but a prescaler of 2 does also work. + * d) On the default clock rate of the arduino core (8MHz), the maximum baud rate is 9600, with a prescaler of 1. */ class SoftwareSerial : public Stream { @@ -130,7 +132,7 @@ class SoftwareSerial : public Stream const gpio_pin_t rx_pin; const gpio_pin_t tx_pin; const bool invert; - uint32_t baud; + uint32_t baud = 0; inline bool is_half_duplex() { From 6ae03a56cdbd207a79abfa69a76d3582c9f8d334 Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 8 Feb 2025 18:58:00 +0100 Subject: [PATCH 07/15] remove use of bitfields in SoftwareSerial class for some reason, those variables weren't initialized correctly on hardware --- libraries/SoftwareSerial/src/SoftwareSerial.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/SoftwareSerial/src/SoftwareSerial.h b/libraries/SoftwareSerial/src/SoftwareSerial.h index 96b4f3a..2ee8002 100644 --- a/libraries/SoftwareSerial/src/SoftwareSerial.h +++ b/libraries/SoftwareSerial/src/SoftwareSerial.h @@ -158,8 +158,8 @@ class SoftwareSerial : public Stream private: // RX logic RingBuffer *rx_buffer; - bool did_rx_overflow : 1; - bool rx_active : 1; + bool did_rx_overflow = false; + bool rx_active = false; uint8_t rx_frame = 0; // 8 bits int8_t rx_bit_count = -1; // -1 means waiting for start bit @@ -171,8 +171,8 @@ class SoftwareSerial : public Stream void do_rx(); private: // TX logic - bool tx_active : 1; - bool tx_pending : 1; + bool tx_active = false; + bool tx_pending = false; uint16_t tx_frame = 0; // 10 bits int8_t tx_bit_count = 0; From 4d26807c1658f6d9adcedc6992fc40745c05a0a0 Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 8 Feb 2025 19:05:53 +0100 Subject: [PATCH 08/15] add STM32duino API compatibility mode --- libraries/SoftwareSerial/README.md | 7 +++++- .../SoftwareSerial/src/SoftwareSerial.cpp | 23 +++++++++++++++---- libraries/SoftwareSerial/src/SoftwareSerial.h | 16 ++++++++++++- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/libraries/SoftwareSerial/README.md b/libraries/SoftwareSerial/README.md index 0a3c58f..08def0b 100644 --- a/libraries/SoftwareSerial/README.md +++ b/libraries/SoftwareSerial/README.md @@ -18,7 +18,12 @@ To configure the library, you may add the following defines to your build enviro | `SOFTWARE_SERIAL_TIMER_PRESCALER` | `2` | prescaler of the TIMER0. set according to PCLK1 and desired baud rate range | | `SOFTWARE_SERIAL_TIMER0_UNIT` | `TIMER01B_config` | TIMER0 unit to use for software serial. Using TIMER01A is not recommended | | `SOFTWARE_SERIAL_TIMER_PRIORITY` | `3` | interrupt priority of the timer interrupt | -| `SOFTWARE_SERIAL_FLUSH_CLEARS_RX_BUFFER` | `0` | behaviour of the `flush()` method. `0` = waits for pending TX to complete. `1` = clear RX buffer. STMduino library uses behaviour `1` | +| `SOFTWARE_SERIAL_FLUSH_CLEARS_RX_BUFFER` | `SOFTWARE_SERIAL_STM32_API_COMPATIBILITY` | behaviour of the `flush()` method. `0` = waits for pending TX to complete. `1` = clear RX buffer. STMduino library uses behaviour `1` | +| `SOFTWARE_SERIAL_STM32_API_COMPATIBILITY` | `0` | compatibility with STM32duino library. `0` = sensible API. `1` = compatible with STM32duino API. | + + +> [!TIP] +> for existing projects that originated from STM32duino, you may set `SOFTWARE_SERIAL_STM32_API_COMPATIBILITY` to `1` to maintain compatibility with the STM32duino library. ### Calculating `SOFTWARE_SERIAL_TIMER_PRESCALER` diff --git a/libraries/SoftwareSerial/src/SoftwareSerial.cpp b/libraries/SoftwareSerial/src/SoftwareSerial.cpp index 53655ab..8fd7cee 100644 --- a/libraries/SoftwareSerial/src/SoftwareSerial.cpp +++ b/libraries/SoftwareSerial/src/SoftwareSerial.cpp @@ -34,6 +34,11 @@ SoftwareSerial::SoftwareSerial(const gpio_pin_t rx_pin, const gpio_pin_t tx_pin, SoftwareSerial::~SoftwareSerial() { end(); + + #if SOFTWARE_SERIAL_STM32_API_COMPATIBILITY == 1 + remove_listener(this); + #endif + delete this->rx_buffer; } @@ -43,6 +48,9 @@ void SoftwareSerial::begin(const uint32_t baud) if (this->baud != 0) { end(); + #if SOFTWARE_SERIAL_STM32_API_COMPATIBILITY == 1 + remove_listener(this); + #endif } SOFTSERIAL_DEBUG_PRINTF("begin: rx=%u, tx=%u, invert=%d, baud=%lu, half-duplex=%d\n", @@ -70,8 +78,11 @@ void SoftwareSerial::end() { SOFTSERIAL_DEBUG_PRINTF("end\n"); stopListening(); + + #if SOFTWARE_SERIAL_STM32_API_COMPATIBILITY == 0 remove_listener(this); this->baud = 0; + #endif } bool SoftwareSerial::listen() @@ -119,6 +130,7 @@ bool SoftwareSerial::stopListening() rx_active = false; } + #if SOFTWARE_SERIAL_STM32_API_COMPATIBILITY == 0 // if no other instance is listening, stop the timer bool any_listening = false; ListenerItem *item = listeners; @@ -130,14 +142,15 @@ bool SoftwareSerial::stopListening() any_listening = true; break; } - + item = item->next; } - + if (!any_listening) { timer_set_speed(0); } + #endif SOFTSERIAL_DEBUG_PRINTF("stopped listening; was_listening=%d\n", was_listening); return was_listening; @@ -217,7 +230,8 @@ void SoftwareSerial::flush() void SoftwareSerial::setup_rx() { - SOFTSERIAL_DEBUG_PRINTF("setup_rx on %u\n", rx_pin); + // note: cannot call DEBUG_PRINTF here, this may be called from a ISR + // SOFTSERIAL_DEBUG_PRINTF("setup_rx on %u\n", rx_pin); // UART idle line is high, so set pull-up for non-inverted logic // HC32 has no pull-down, so inverted logic will have to do without @@ -226,7 +240,8 @@ void SoftwareSerial::setup_rx() void SoftwareSerial::setup_tx() { - SOFTSERIAL_DEBUG_PRINTF("setup_tx on %u\n", tx_pin); + // note: cannot call DEBUG_PRINTF here, this may be called from a ISR + // SOFTSERIAL_DEBUG_PRINTF("setup_tx on %u\n", tx_pin); // set pin level before setting as output to avoid glitches // UART idle line is high, so set high for non-inverted logic and vice versa diff --git a/libraries/SoftwareSerial/src/SoftwareSerial.h b/libraries/SoftwareSerial/src/SoftwareSerial.h index 2ee8002..a033802 100644 --- a/libraries/SoftwareSerial/src/SoftwareSerial.h +++ b/libraries/SoftwareSerial/src/SoftwareSerial.h @@ -30,11 +30,25 @@ #define SOFTWARE_SERIAL_TIMER_PRIORITY 3 #endif +// changes the way the SoftwareSerial library behaves to match the one from STM32duino. +// this is useful for compatibility with existing code, however these changes cause the +// behaviour of the API to be less intuitive. +// main changes: +// 1. flush() will clear the RX buffer instead of waiting for all pending TX operations to finish +// 2. end() will no longer fully undo begin(). +// you'll be able to write and receive even after calling end() +// 3. stopListening() will not stop the timer if it is no longer needed. +// resulting from this, the timer is never stopped. + +#ifndef SOFTWARE_SERIAL_STM32_API_COMPATIBILITY +#define SOFTWARE_SERIAL_STM32_API_COMPATIBILITY 0 +#endif + // how SoftwareSerial behaves when flush() is called // when 0: flush() will wait for all pending TX operations to finish (Arduinio >1.0 behavior) // when 1: flush() will clear the RX buffer (old behaviour; how the STM32duino library does it) #ifndef SOFTWARE_SERIAL_FLUSH_CLEARS_RX_BUFFER -#define SOFTWARE_SERIAL_FLUSH_CLEARS_RX_BUFFER 0 +#define SOFTWARE_SERIAL_FLUSH_CLEARS_RX_BUFFER SOFTWARE_SERIAL_STM32_API_COMPATIBILITY #endif /** From a28b81ef3be271dfeba6bad1df781ba4290bc43f Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 8 Feb 2025 11:51:47 +0100 Subject: [PATCH 09/15] CORE: fix wrong-way-round assertion in IRQn driver --- cores/arduino/drivers/interrupts/interrupts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/arduino/drivers/interrupts/interrupts.cpp b/cores/arduino/drivers/interrupts/interrupts.cpp index d71fa82..32ee6e8 100644 --- a/cores/arduino/drivers/interrupts/interrupts.cpp +++ b/cores/arduino/drivers/interrupts/interrupts.cpp @@ -269,7 +269,7 @@ en_result_t _irqn_aa_resign(IRQn_Type &irqn) { // do nothing since resigning the interrupt already frees the IRQn // only check that the interrupt was actually resigned before calling this function - CORE_ASSERT(ram_vector_table.irqs[irqn] != no_handler, "IRQ was not resigned before auto-assign resignment", + CORE_ASSERT(ram_vector_table.irqs[irqn] == no_handler, "IRQ was not resigned before auto-assign resignment", return Error); return Ok; } From 55320b840b2f192b959a472d3bc43efa186cb5eb Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 8 Feb 2025 20:09:38 +0100 Subject: [PATCH 10/15] SPI: move instability warning to .cpp file to avoid spamming build logs --- libraries/SPI/src/SPI.cpp | 2 ++ libraries/SPI/src/SPI.h | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/SPI/src/SPI.cpp b/libraries/SPI/src/SPI.cpp index e322e23..a7b414d 100644 --- a/libraries/SPI/src/SPI.cpp +++ b/libraries/SPI/src/SPI.cpp @@ -3,6 +3,8 @@ #include #include +#warning "SPI on the HC32F460 has not been tested yet. See https://github.com/shadow578/framework-arduino-hc32f46x/pull/29" + /** * @brief given a integer v, round up to the next power of two * @note based on https://stackoverflow.com/a/466242 diff --git a/libraries/SPI/src/SPI.h b/libraries/SPI/src/SPI.h index 6886934..e51a0a1 100644 --- a/libraries/SPI/src/SPI.h +++ b/libraries/SPI/src/SPI.h @@ -14,8 +14,6 @@ #error "SPI library requires PWC DDL to be enabled" #endif -#warning "SPI on the HC32F460 has not been tested yet. See https://github.com/shadow578/framework-arduino-hc32f46x/pull/29" - // SPI_HAS_TRANSACTION means SPI has // - beginTransaction() From 9461b012aacfd429249518f9efb8a72b7f875cab Mon Sep 17 00:00:00 2001 From: Chris <52449218+shadow578@users.noreply.github.com> Date: Mon, 10 Feb 2025 07:28:48 +0000 Subject: [PATCH 11/15] fix half-duplex rx switch timing --- libraries/SoftwareSerial/src/SoftwareSerial.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/SoftwareSerial/src/SoftwareSerial.cpp b/libraries/SoftwareSerial/src/SoftwareSerial.cpp index 8fd7cee..8fc0998 100644 --- a/libraries/SoftwareSerial/src/SoftwareSerial.cpp +++ b/libraries/SoftwareSerial/src/SoftwareSerial.cpp @@ -360,9 +360,10 @@ void SoftwareSerial::do_tx() } else if (is_half_duplex() && isListening()) { - // wait HALF_DUPLEX_SWITCH_DELAY bits before switching to RX - // NOTE: in STM32dunio, they wait for 10 + (OVERSAMPLE * HALF_DUPLEX_SWITCH_DELAY) bits. i believe this is a bug on their end tho - if (tx_bit_count >= 10 + SOFTWARE_SERIAL_HALF_DUPLEX_SWITCH_DELAY) + // wait HALF_DUPLEX_SWITCH_DELAY bit periods before switching to RX mode + // note: due to the tx_wait_ticks=1 in this branch, tx_bit_count is incremented every tick and not every bit period + // thus, to get bit periods, we need to multiply the delay by OVERSAMPLE + if (tx_bit_count >= 10 + (SOFTWARE_SERIAL_HALF_DUPLEX_SWITCH_DELAY * SOFTWARE_SERIAL_OVERSAMPLE)) { set_half_duplex_mode(true /*=RX*/); } From faa3f483497b8dcc976e6a022b7c46c1d49ad8ae Mon Sep 17 00:00:00 2001 From: Chris <52449218+shadow578@users.noreply.github.com> Date: Mon, 5 May 2025 11:18:31 +0200 Subject: [PATCH 12/15] mark Timer0::get_actual_frequency as const method Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- libraries/Timer0/src/Timer0.cpp | 2 +- libraries/Timer0/src/Timer0.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/Timer0/src/Timer0.cpp b/libraries/Timer0/src/Timer0.cpp index a5e2e9c..700b523 100644 --- a/libraries/Timer0/src/Timer0.cpp +++ b/libraries/Timer0/src/Timer0.cpp @@ -261,7 +261,7 @@ void Timer0::stop() TIMER0_DEBUG_PRINTF("stopped channel\n"); } -float Timer0::get_actual_frequency() +float Timer0::get_actual_frequency() const { // get timer channel base frequency, refer to start() for details uint32_t base_frequency; diff --git a/libraries/Timer0/src/Timer0.h b/libraries/Timer0/src/Timer0.h index ba4c3a4..5392614 100644 --- a/libraries/Timer0/src/Timer0.h +++ b/libraries/Timer0/src/Timer0.h @@ -180,7 +180,7 @@ class Timer0 * @note calculates the frequency from the live register values, so this * is what the timer is currently running at. */ - float get_actual_frequency(); + float get_actual_frequency() const; private: timer0_channel_config_t *config; From d637cd4644d5b02f64a83ea6bc5226b11382bc68 Mon Sep 17 00:00:00 2001 From: Chris <52449218+shadow578@users.noreply.github.com> Date: Mon, 5 May 2025 11:18:47 +0200 Subject: [PATCH 13/15] fix typo in softserial README Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- libraries/SoftwareSerial/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/SoftwareSerial/README.md b/libraries/SoftwareSerial/README.md index 08def0b..b4d676b 100644 --- a/libraries/SoftwareSerial/README.md +++ b/libraries/SoftwareSerial/README.md @@ -1,7 +1,7 @@ # Software Serial for the HC32F460 The Software Serial library allows serial (UART) communication on any digital pin of the board, bit-banging the protocol. -It it possible to have multiple software serial ports. +It is possible to have multiple software serial ports. The implementation of this library is based on the [SoftwareSerial library of the STM32duino project](https://github.com/stm32duino/Arduino_Core_STM32/blob/main/libraries/SoftwareSerial/). From 21a6f5390c55ed11663da08b261a4b3d4e9239fb Mon Sep 17 00:00:00 2001 From: Chris <52449218+shadow578@users.noreply.github.com> Date: Mon, 5 May 2025 11:08:20 +0000 Subject: [PATCH 14/15] prevent divide-by-zero in timer0 get_actual_frequency --- libraries/Timer0/src/Timer0.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/Timer0/src/Timer0.cpp b/libraries/Timer0/src/Timer0.cpp index 700b523..8e09413 100644 --- a/libraries/Timer0/src/Timer0.cpp +++ b/libraries/Timer0/src/Timer0.cpp @@ -275,7 +275,7 @@ float Timer0::get_actual_frequency() const base_frequency = SYSTEM_CLOCK_FREQUENCIES.pclk1; } - // get prescaler from peripheral registers + // get prescaler and compare from peripheral registers en_tim0_clock_div_t prescaler_reg; if (this->config->peripheral.channel == Tim0_ChannelA) { @@ -287,9 +287,9 @@ float Timer0::get_actual_frequency() const } const uint16_t prescaler = clock_div_to_numeric(prescaler_reg); - - // get compare value from peripheral registers const uint16_t compare = TIMER0_GetCmpReg(this->config->peripheral.register_base, this->config->peripheral.channel); + + CORE_ASSERT(compare != 0, "cannot calculate frequency, timer compare register was 0", return 0.0f); // calculate actual frequency return (static_cast(base_frequency) / static_cast(prescaler)) / static_cast(compare); From cc4102faac73ffdf03ebee3186774dffb04290bd Mon Sep 17 00:00:00 2001 From: Chris <52449218+shadow578@users.noreply.github.com> Date: Mon, 5 May 2025 13:13:50 +0200 Subject: [PATCH 15/15] fix typo in softserial header Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- libraries/SoftwareSerial/src/SoftwareSerial.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/SoftwareSerial/src/SoftwareSerial.h b/libraries/SoftwareSerial/src/SoftwareSerial.h index a033802..8f6f27d 100644 --- a/libraries/SoftwareSerial/src/SoftwareSerial.h +++ b/libraries/SoftwareSerial/src/SoftwareSerial.h @@ -21,7 +21,7 @@ #define SOFTWARE_SERIAL_TIMER_PRESCALER 2 #endif -// recommented to not use TIMER0 Unit 1 Channel A, as it does not support sync mode +// recommended to not use TIMER0 Unit 1 Channel A, as it does not support sync mode #ifndef SOFTWARE_SERIAL_TIMER0_UNIT #define SOFTWARE_SERIAL_TIMER0_UNIT TIMER01B_config // Timer0 Unit 1, Channel B #endif