[modbus] Speed up the bus with microsecond-accurate timing (#12421)

This commit is contained in:
Bonne Eggleston
2026-08-30 03:14:12 -05:00
committed by GitHub
parent cd28a8a03e
commit a811aa840c
4 changed files with 203 additions and 73 deletions
+8 -1
View File
@@ -11,7 +11,14 @@ namespace esphome::modbus::testing {
// A UART that discards all writes, for tests that never inspect the wire.
class NullUART : public uart::UARTComponent {
public:
NullUART() { this->set_baud_rate(115200); }
// 8N1, matching what the uart schema emits for a real hub; the framing drives the modbus
// interframe timing, so leaving data/stop bits at their zero defaults would not be representative.
NullUART() {
this->set_baud_rate(115200);
this->set_data_bits(8);
this->set_stop_bits(1);
this->set_parity(uart::UART_CONFIG_PARITY_NONE);
}
void write_array(const uint8_t *data, size_t len) override {}
bool peek_byte(uint8_t *data) override { return false; }
bool read_array(uint8_t *data, size_t len) override { return false; }
@@ -0,0 +1,64 @@
#include <gtest/gtest.h>
#include <cstdint>
#include "common.h"
#include "esphome/components/modbus/modbus.h"
namespace esphome::modbus::testing {
namespace {
// Exposes the timing values setup() derives from the UART framing.
class FramingProbeHub : public ModbusClientHub {
public:
uint32_t bits_per_char() const { return this->bits_per_char_; }
uint32_t frame_delay_us() const { return this->frame_delay_us_; }
};
class FramedUART : public NullUART {
public:
FramedUART(uint32_t baud_rate, uint8_t data_bits, uint8_t stop_bits, uart::UARTParityOptions parity) {
this->set_baud_rate(baud_rate);
this->set_data_bits(data_bits);
this->set_stop_bits(stop_bits);
this->set_parity(parity);
}
};
} // namespace
// 8N1 is 10 bits on the wire, so t3.5 at 9600 baud is 3.5 * 10 / 9600 = 3645.8us.
TEST(ModbusFraming, EightNoneOneDerivesTenBits) {
FramedUART uart(9600, 8, 1, uart::UART_CONFIG_PARITY_NONE);
FramingProbeHub hub;
hub.set_uart_parent(&uart);
hub.setup();
EXPECT_EQ(hub.bits_per_char(), 10u);
EXPECT_EQ(hub.frame_delay_us(), 3646u);
}
// Spec-conformant RTU framing is 11 bits, which lengthens the interframe gap to
// 3.5 * 11 / 9600 = 4010.4us, rounded up.
TEST(ModbusFraming, EightEvenOneDerivesElevenBits) {
FramedUART uart(9600, 8, 1, uart::UART_CONFIG_PARITY_EVEN);
FramingProbeHub hub;
hub.set_uart_parent(&uart);
hub.setup();
EXPECT_EQ(hub.bits_per_char(), 11u);
EXPECT_EQ(hub.frame_delay_us(), 4011u);
}
// Above 19200 baud the spec's fixed 1750us floor governs instead of 3.5 characters.
TEST(ModbusFraming, FastBaudUsesSpecFloor) {
FramedUART uart(115200, 8, 1, uart::UART_CONFIG_PARITY_NONE);
FramingProbeHub hub;
hub.set_uart_parent(&uart);
hub.setup();
EXPECT_EQ(hub.frame_delay_us(), 1750u);
}
} // namespace esphome::modbus::testing