Reduce modbus heap alloc

This commit is contained in:
J. Nick Koston 2025-07-04 21:51:52 -05:00
parent 58b4e7dab2
commit ab9287e959
No known key found for this signature in database
5 changed files with 64 additions and 37 deletions

View File

@ -204,49 +204,82 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
return;
}
std::vector<uint8_t> data;
data.push_back(address);
data.push_back(function_code);
// Calculate the expected message size
size_t msg_size = 4; // address + function + CRC(2)
if (this->role == ModbusRole::CLIENT) {
data.push_back(start_address >> 8);
data.push_back(start_address >> 0);
msg_size += 2; // start_address
if (function_code != 0x5 && function_code != 0x6) {
data.push_back(number_of_entities >> 8);
data.push_back(number_of_entities >> 0);
msg_size += 2; // number_of_entities
}
}
if (payload != nullptr) {
if (this->role == ModbusRole::SERVER || function_code == 0xF || function_code == 0x10) {
msg_size += 1 + payload_len; // byte count + payload
} else {
msg_size += 2; // single register value
}
}
// Use stack buffer for small messages (most common case)
static constexpr size_t STACK_BUFFER_SIZE = 64;
uint8_t stack_buffer[STACK_BUFFER_SIZE];
std::vector<uint8_t> heap_buffer;
uint8_t *data;
if (msg_size <= STACK_BUFFER_SIZE) {
data = stack_buffer;
} else {
heap_buffer.resize(msg_size);
data = heap_buffer.data();
}
// Build the message
size_t pos = 0;
data[pos++] = address;
data[pos++] = function_code;
if (this->role == ModbusRole::CLIENT) {
data[pos++] = start_address >> 8;
data[pos++] = start_address >> 0;
if (function_code != 0x5 && function_code != 0x6) {
data[pos++] = number_of_entities >> 8;
data[pos++] = number_of_entities >> 0;
}
}
if (payload != nullptr) {
if (this->role == ModbusRole::SERVER || function_code == 0xF || function_code == 0x10) { // Write multiple
data.push_back(payload_len); // Byte count is required for write
data[pos++] = payload_len; // Byte count is required for write
} else {
payload_len = 2; // Write single register or coil
}
for (int i = 0; i < payload_len; i++) {
data.push_back(payload[i]);
data[pos++] = payload[i];
}
}
auto crc = crc16(data.data(), data.size());
data.push_back(crc >> 0);
data.push_back(crc >> 8);
auto crc = crc16(data, pos);
data[pos++] = crc >> 0;
data[pos++] = crc >> 8;
if (this->flow_control_pin_ != nullptr)
this->flow_control_pin_->digital_write(true);
this->write_array(data);
this->write_array(data, pos);
this->flush();
if (this->flow_control_pin_ != nullptr)
this->flow_control_pin_->digital_write(false);
waiting_for_response = address;
last_send_ = millis();
ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty(data).c_str());
ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty(data, pos).c_str());
}
// Helper function for lambdas
// Send raw command. Except CRC everything must be contained in payload
void Modbus::send_raw(const std::vector<uint8_t> &payload) {
void Modbus::send_raw(const std::vector<uint8_t> &payload) { send_raw(std::span<const uint8_t>(payload)); }
void Modbus::send_raw(std::span<const uint8_t> payload) {
if (payload.empty()) {
return;
}
@ -255,14 +288,14 @@ void Modbus::send_raw(const std::vector<uint8_t> &payload) {
this->flow_control_pin_->digital_write(true);
auto crc = crc16(payload.data(), payload.size());
this->write_array(payload);
this->write_array(payload.data(), payload.size());
this->write_byte(crc & 0xFF);
this->write_byte((crc >> 8) & 0xFF);
this->flush();
if (this->flow_control_pin_ != nullptr)
this->flow_control_pin_->digital_write(false);
waiting_for_response = payload[0];
ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty(payload).c_str());
ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty(payload.data(), payload.size()).c_str());
last_send_ = millis();
}

View File

@ -3,6 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include <span>
#include <vector>
namespace esphome {
@ -32,6 +33,7 @@ class Modbus : public uart::UARTDevice, public Component {
void send(uint8_t address, uint8_t function_code, uint16_t start_address, uint16_t number_of_entities,
uint8_t payload_len = 0, const uint8_t *payload = nullptr);
void send_raw(const std::vector<uint8_t> &payload);
void send_raw(std::span<const uint8_t> payload);
void set_role(ModbusRole role) { this->role = role; }
void set_flow_control_pin(GPIOPin *flow_control_pin) { this->flow_control_pin_ = flow_control_pin; }
uint8_t waiting_for_response{0};
@ -65,13 +67,10 @@ class ModbusDevice {
this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload);
}
void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload); }
void send_raw(std::span<const uint8_t> payload) { this->parent_->send_raw(payload); }
void send_error(uint8_t function_code, uint8_t exception_code) {
std::vector<uint8_t> error_response;
error_response.reserve(3);
error_response.push_back(this->address_);
error_response.push_back(function_code | 0x80);
error_response.push_back(exception_code);
this->send_raw(error_response);
uint8_t error_response[3] = {this->address_, static_cast<uint8_t>(function_code | 0x80), exception_code};
this->send_raw(std::span<const uint8_t>(error_response, 3));
}
// If more than one device is connected block sending a new command before a response is received
bool waiting_for_response() { return parent_->waiting_for_response != 0; }

View File

@ -224,12 +224,11 @@ void ModbusController::on_modbus_write_registers(uint8_t function_code, const st
return;
}
std::vector<uint8_t> response;
response.reserve(6);
response.push_back(this->address_);
response.push_back(function_code);
response.insert(response.end(), data.begin(), data.begin() + 4);
this->send_raw(response);
uint8_t response[6];
response[0] = this->address_;
response[1] = function_code;
std::copy(data.begin(), data.begin() + 4, response + 2);
this->send_raw(std::span<const uint8_t>(response, 6));
}
SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {

View File

@ -77,10 +77,8 @@ void PZEMAC::dump_config() {
}
void PZEMAC::reset_energy_() {
std::vector<uint8_t> cmd;
cmd.push_back(this->address_);
cmd.push_back(PZEM_CMD_RESET_ENERGY);
this->send_raw(cmd);
uint8_t cmd[2] = {this->address_, PZEM_CMD_RESET_ENERGY};
this->send_raw(std::span<const uint8_t>(cmd, 2));
}
} // namespace pzemac

View File

@ -65,10 +65,8 @@ void PZEMDC::dump_config() {
}
void PZEMDC::reset_energy() {
std::vector<uint8_t> cmd;
cmd.push_back(this->address_);
cmd.push_back(PZEM_CMD_RESET_ENERGY);
this->send_raw(cmd);
uint8_t cmd[2] = {this->address_, PZEM_CMD_RESET_ENERGY};
this->send_raw(std::span<const uint8_t>(cmd, 2));
}
} // namespace pzemdc