/** ************************************************************************** * @file rs_message.cpp * @brief Модуль для работы с протоколами RS/UART на Arduino. ************************************************************************** * @details * Данный модуль обеспечивает приём и передачу сообщений по протоколу RS * через UART на платформах Arduino. Поддерживаются два режима работы: * - обычный (Arduino loop); * - FreeRTOS (через StreamBuffer и отдельную задачу RS_Task). * * Основные возможности: * - Инициализация и управление структурой RS_HandleTypeDef. * - Обработка приёма байтов, сборка пакетов и определение их длины. * - Разбор полученных сообщений и формирование ответов. * - Управление состояниями приёма и передачи (флаги RX/TX, busy/free). * - Таймаут приёма и аварийная остановка передачи/приёма. * * Предоставляемые функции: * - RS_Init() — инициализация структуры RS и Serial порта. * - RS_Abort() — остановка работы RS/UART, очистка флагов. * - RS_Handle_Transmit_Start() — подготовка и отправка сообщения. * - RS_Process() — основной цикл обработки (Arduino loop). * - RS_Process_Byte() — обработка одного принятого байта (для FreeRTOS). * - RS_UART_RX_Handler() — ISR-приём данных UART (FreeRTOS). * - RS_Task() — FreeRTOS задача обработки UART. * * Пользователь должен реализовать слабые функции: * - RS_Response() — формирование ответа на сообщение. * - RS_Collect_Message() — заполнение буфера перед передачей. * - RS_Parse_Message() — разбор принятого пакета. * - RS_Define_Size_of_RX_Message() — определение длины ожидаемого пакета. * * Буфер для приёма/передачи: RS_Buffer[MSG_SIZE_MAX]. * * @note * - В режиме Arduino loop необходимо вызывать RS_Process() в основном цикле. * - В режиме FreeRTOS требуется определить макрос RS_IN_FREERTOS, * создать задачу RS_Task и подключить RS_UART_RX_Handler() к ISR UART. * - UART используется через стандартный Arduino Stream API (write, read, available). * * Пример использования (Arduino loop): * @verbatim RS_HandleTypeDef hRS; RS_MsgTypeDef msg; hRS.pMessagePtr = &msg; RS_Init(&hRS, &Serial, 115200, NULL); void loop() { RS_Process(&hRS); } * @endverbatim * Пример использования с FreeRTOS: * @verbatim RS_HandleTypeDef hRS; RS_MsgTypeDef msg; hRS.pMessagePtr = &msg; RS_Init(&hRS, &Serial, 115200, NULL); xTaskCreate(RS_Task, "RS_Task", 256, &hRS, 1, NULL); //... void eventSerial() { RS_UART_RX_Handler(&hmodbus1); } * @endverbatim **************************************************************************/ #include "rs_message.h" #ifdef RS_IN_FREERTOS #include #endif 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, uint32_t baudRate, uint8_t *pRS_BufferPtr) { if (!hRS || !SerialPort) { RS_DEBUG_PRINT("[RS] Init error: null handler or port"); return RS_ERR; } hRS->huart = SerialPort; hRS->baudRate = baudRate; 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); hRS->huart->begin(hRS->baudRate, SERIAL_8N1, hRS->rx_pin, hRS->tx_pin); 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 после аборта. * @details В 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 после инициализации передачи. * @details Определяет отвечать ли на команду или нет. */ 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; } #ifdef RS_IN_FREERTOS /** * @brief Задача обработки UART приёма (FreeRTOS). * @param pvParameters - указатель на RS_HandleTypeDef (передаётся при создании задачи). * @details Вызывает функцию обработки приема UART по протоколу. */ void RS_Task(void *pvParameters) { RS_HandleTypeDef *hRS = (RS_HandleTypeDef *)pvParameters; uint8_t rxBuf[64]; for (;;) { RS_Process(hRS); vTaskDelay(pdMS_TO_TICKS(hRS->taskDelay)); } } #endif // RS_IN_FREERTOS /** * @brief Основная функция обработки RS (вариант без FreeRTOS). * @param hRS - указатель на хендлер RS. * @note Используется в Arduino loop(), если проект без FreeRTOS. * @details Выполняет проверку таймаутов, обработку поступающих байт из UART * по протоколу */ 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 - статус о результате ответа на комманду. * @details Обработка принятой комманды и ответ на неё. */ __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 - статус о результате заполнения буфера. * @details Заполнение буффера 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 - статус о результате заполнения структуры. * @details Заполнение структуры сообщения из буффера 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 - статус о корректности рассчета кол-ва байт для принятия. * @details Определение сколько байтов надо принять по протоколу. */ __attribute__((weak)) RS_StatusTypeDef RS_Define_Size_of_RX_Message(RS_HandleTypeDef *hRS, uint32_t *rx_data_size) { return RS_ERR; }