Arduino_Modbus/rs_message.cpp
Razvalyaev 1c4f4d689a Работает Modbus, проверено на ESP32-S3-Zero
Заготовка для модбас на ИВР
2025-08-29 20:23:34 +03:00

290 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
**************************************************************************
* @file rs_message.cpp
* @brief Модуль для реализации протоколов по RS/UART (Arduino).
**************************************************************************\
* @details
* Данный модуль реализует основные функции для приема и передачи сообщений
* по протоколу RS через UART. Реализована обработка приёма и передачи данных,
* управление состояниями RS, а также функции для инициализации и управления
* периферией Arduino Serial.
*
* Реализованы следующие функции:
* - RS_Init() — инициализация структуры RS и привязка к Serial-порту.
* - RS_Abort() — остановка работы RS/UART с очисткой флагов и структур.
* - RS_Handle_Transmit_Start() — запуск передачи данных.
* - RS_Process() — обработка входящих данных (вызов в loop()).
*
* В модуле также определён буфер RS_Buffer[] для хранения принимаемых/передаваемых данных.
*
* Пользователь должен определить функции:
* - RS_Response() — формирование ответа на принятое сообщение.
* - RS_Collect_Message() — подготовка сообщения для передачи.
* - RS_Parse_Message() — разбор принятого сообщения.
* - RS_Define_Size_of_RX_Message() — определение размера входящих данных.
*
* @note
* Для корректной работы требуется вызывать RS_Process() в основном цикле программы.
* UART используется через стандартный Arduino Stream API (write(), read(), available()).
*
@verbatim
//-------------------Функции-------------------//
Functions: users
- RS_Parse_Message Разбор принятого сообщения
- RS_Collect_Message Заполнение структуры сообщения и буфера
- RS_Response Ответ на сообщение
- RS_Define_Size_of_RX_Message Определение размера принимаемых данных
Functions: general
- RS_Init Инициализация структуры RS и привязка Serial
- RS_Abort Остановка приёма/передачи
- RS_Handle_Transmit_Start Запуск передачи сообщения
- RS_Process Обработка приёма сообщений (в loop)
@endverbatim
*************************************************************************/
#include "rs_message.h"
uint8_t RS_Buffer[MSG_SIZE_MAX]; // uart buffer
//-------------------------------------------------------------------
//-------------------------GENERAL FUNCTIONS-------------------------
/**
* @brief Initialize UART and handle RS stucture.
* @param hRS - указатель на хендлер RS.
* @param SerialPort - указатель на структуру с настройками UART.
* @param pRS_BufferPtr - указатель на буффер для приема-передачи по UART. Если он NULL, то поставиться библиотечный буфер.
* @return RS_RES - статус о состоянии RS после инициализации.
* @note Инициализация перефирии и структуры для приема-передачи по RS.
*/
RS_StatusTypeDef RS_Init(RS_HandleTypeDef *hRS, HUART_TypeDef *SerialPort, uint8_t *pRS_BufferPtr) {
if (!hRS || !SerialPort) {
RS_DEBUG_PRINT("[RS] Init error: null handler or port");
return RS_ERR;
}
hRS->huart = SerialPort;
hRS->pBufferPtr = pRS_BufferPtr ? pRS_BufferPtr : RS_Buffer;
hRS->RS_STATUS = RS_OK;
RS_Set_Free(hRS);
RS_Reset_RX_Flags(hRS);
RS_Reset_TX_Flags(hRS);
hRS->f.RX_Half = 0;
hRS->lastByteTime = 0;
RS_EnableReceive();
RS_Set_Busy(hRS);
RS_Set_RX_Flags(hRS);
RS_DEBUG_PRINT("[RS] Initialized successfully");
return RS_OK;
}
/**
* @brief Abort RS/UART.
* @param hRS - указатель на хендлер RS.
* @param AbortMode - выбор, что надо отменить.
- ABORT_TX: Отмена передачи по ЮАРТ, с очищением флагов TX,
- ABORT_RX: Отмена приема по ЮАРТ, с очищением флагов RX,
- ABORT_RX_TX: Отмена приема и передачи по ЮАРТ,
- ABORT_RS: Отмена приема-передачи RS, с очищением всей структуры.
* @return RS_RES - статус о состоянии RS после аборта.
* @note В Arduino аппаратный UART (Serial) не отключается физически,
* т.к. стандартный API HardwareSerial не даёт прямого доступа
* к регистрах UART. RS_Abort лишь очищает внутренние флаги
* и структуры, имитируя "отключение".
*/
RS_StatusTypeDef RS_Abort(RS_HandleTypeDef *hRS, RS_AbortTypeDef AbortMode) {
if (hRS == nullptr) {
RS_DEBUG_PRINT("[RS] Abort error: null handler");
return RS_ERR;
}
if ((AbortMode & ABORT_RS) == 0) {
if ((AbortMode & ABORT_RX) == ABORT_RX) {
RS_DEBUG_PRINT("[RS] Abort RX");
RS_Reset_RX_Flags(hRS);
}
if ((AbortMode & ABORT_TX) == ABORT_TX) {
RS_DEBUG_PRINT("[RS] Abort TX");
RS_Reset_TX_Flags(hRS);
}
} else {
RS_DEBUG_PRINT("[RS] Abort RS (full reset)");
RS_Clear_All(hRS);
}
RS_Set_Free(hRS);
hRS->RS_STATUS = RS_ABORTED;
return RS_ABORTED;
}
/**
* @brief Handle for starting transmit.
* @param hRS - указатель на хендлер RS.
* @param RS_msg - указатель на структуру сообщения.
* @return RS_RES - статус о состоянии RS после инициализации передачи.
* @note Определяет отвечать ли на команду или нет.
*/
RS_StatusTypeDef RS_Handle_Transmit_Start(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg)
{
if (hRS == nullptr || RS_msg == nullptr) {
RS_DEBUG_PRINT("[RS] TX start error: null ptr");
return RS_ERR;
}
if (RS_Is_TX_Busy(hRS)) {
RS_DEBUG_PRINT("[RS] TX busy, cannot start");
return RS_BUSY;
}
RS_StatusTypeDef status = RS_Collect_Message(hRS, RS_msg, hRS->pBufferPtr);
if (status != RS_OK) {
RS_DEBUG_PRINT("[RS] TX collect message failed");
return status;
}
RS_DEBUG_PRINT2_DEC("[RS] TX start, size=", hRS->RS_Message_Size);
RS_EnableTransmit();
RS_Set_TX_Flags(hRS);
hRS->huart->write(hRS->pBufferPtr, hRS->RS_Message_Size);
RS_Set_TX_End(hRS);
RS_Set_RX_Flags(hRS);
RS_Set_RX_Flags(hRS);
RS_DEBUG_PRINT("[RS] TX finished");
return RS_OK;
}
/**
* @brief Main RS processing function.
* @param hRS - указатель на хендлер RS.
* @return None.
* @note Функция должна вызываться в основном цикле (loop()).
* Выполняет проверку таймаутов, обработку поступающих байт из UART
* и вызов пользовательских функций:
* - RS_Define_Size_of_RX_Message() для определения размера пакета;
* - RS_Parse_Message() для разбора принятого сообщения;
* - RS_Response() для ответа, если сообщение адресовано текущему устройству.
* В случае таймаута выполняется RS_Abort() с режимом ABORT_RX.
*/
void RS_Process(RS_HandleTypeDef *hRS)
{
// Локальные статические переменные для индекса приёма и ожидаемого размера пакета
static uint32_t rx_index = 0;
static uint32_t expected_size = RX_FIRST_PART_SIZE;
if (hRS == nullptr) {
RS_DEBUG_PRINT("[RS] Process error: null handler");
return;
}
// Проверка таймаута при активном приёме
if (hRS->f.RX_Ongoing) {
if (millis() - hRS->lastByteTime > hRS->sRS_Timeout) {
RS_DEBUG_PRINT("[RS] RX timeout");
RS_Abort(hRS, ABORT_RX);
return;
}
}
// Проверка наличия данных в UART
if (hRS->huart->available()) {
// Если приём ещё не активен — начинаем новый пакет
if (!hRS->f.RX_Ongoing) {
RS_DEBUG_PRINT("[RS] RX start");
RS_Set_RX_Active_Flags(hRS);
rx_index = 0;
expected_size = RX_FIRST_PART_SIZE;
RS_Clear_Buff(hRS->pBufferPtr);
}
// Обновляем время получения последнего байта
hRS->lastByteTime = millis();
// Считываем доступные байты из UART
while (hRS->huart->available() && rx_index < MSG_SIZE_MAX) {
uint8_t b = hRS->huart->read();
hRS->pBufferPtr[rx_index++] = b;
RS_DEBUG_PRINT2_HEX("[RS] RX byte: 0x", b);
// Если достигнут размер первой части пакета — определяем полный размер
if (rx_index == RX_FIRST_PART_SIZE && (hRS->f.RX_Half == 0) && hRS->sRS_RX_Size_Mode == RS_RX_Size_NotConst) {
hRS->f.RX_Half = 1;
uint32_t data_size;
RS_DEBUG_PRINT("[RS] Defining size...");
RS_Define_Size_of_RX_Message(hRS, &data_size);
expected_size = RX_FIRST_PART_SIZE + data_size;
RS_DEBUG_PRINT2_DEC("[RS] RX expected size=", expected_size);
}
// Если пакет полностью получен
if (rx_index >= expected_size) {
hRS->f.RX_Half = 0;
RS_Set_RX_End(hRS);
RS_DEBUG_PRINT("[RS] RX complete, parsing...");
// Разбираем сообщение
RS_Parse_Message(hRS, hRS->pMessagePtr, hRS->pBufferPtr);
// Если адрес совпадает — отвечаем
if (hRS->pMessagePtr->MbAddr == hRS->ID) {
RS_DEBUG_PRINT("[RS] RX for me, sending response");
RS_Response(hRS, hRS->pMessagePtr);
} else {
RS_DEBUG_PRINT2_DEC("[RS] RX not for me, Addr=", hRS->pMessagePtr->MbAddr);
}
break;
}
}
}
}
//-------------------------------------------------------------------
//--------------WEAK PROTOTYPES FOR PROCESSING MESSAGE---------------
/**
* @brief Respond accord to received message.
* @param hRS - указатель на хендлер RS.
* @param RS_msg - указатель на структуру сообщения.
* @return RS_RES - статус о результате ответа на комманду.
* @note Обработка принятой комманды и ответ на неё.
*/
__attribute__((weak)) RS_StatusTypeDef RS_Response(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg) { return RS_ERR; }
/**
* @brief Collect message in buffer to transmit it.
* @param hRS - указатель на хендлер RS.
* @param RS_msg - указатель на структуру сообщения.
* @param msg_uart_buff - указатель на буффер UART.
* @return RS_RES - статус о результате заполнения буфера.
* @note Заполнение буффера UART из структуры сообщения.
*/
__attribute__((weak)) RS_StatusTypeDef RS_Collect_Message(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg, uint8_t *msg_uart_buff) { return RS_ERR; }
/**
* @brief Parse message from buffer to process it.
* @param hRS - указатель на хендлер RS.
* @param RS_msg - указатель на структуру сообщения.
* @param msg_uart_buff - указатель на буффер UART.
* @return RS_RES - статус о результате заполнения структуры.
* @note Заполнение структуры сообщения из буффера UART.
*/
__attribute__((weak)) RS_StatusTypeDef RS_Parse_Message(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg, uint8_t *msg_uart_buff) { return RS_ERR; }
/**
* @brief Define size of RX Message that need to be received.
* @param hRS - указатель на хендлер RS.
* @param rx_data_size - указатель на переменную для записи кол-ва байт для принятия.
* @return RS_RES - статус о корректности рассчета кол-ва байт для принятия.
* @note Определение сколько байтов надо принять по протоколу.
*/
__attribute__((weak)) RS_StatusTypeDef RS_Define_Size_of_RX_Message(RS_HandleTypeDef *hRS, uint32_t *rx_data_size) { return RS_ERR; }