/** ******************************************************************************* * @file rs_message.c * @brief Реализация протоколов обмена по RS/UART ******************************************************************************* * @details Модуль реализует асинхронный обмен сообщениями через UART с использованием: - Прерываний по приему/передаче - Детектирования конца фрейма по IDLE линии - Таймаутов через таймер - Двухстадийного приема (заголовок + данные) @section arch Архитектура: В режиме слейв: - Инициализация приема с сообщения с максимальным размером RS_MSG_SIZE_MAX - При срабатывании прерывания IDLE - обработка полученного сообщения В режиме мастер (пока не реализовано): - Отправка запроса и переход в режим приема сообщения с максимальным размером RS_MSG_SIZE_MAX - При срабатывании прерывания IDLE - обработка полученного ответа @section ithandler Необходимые обработчики: - RS_UART_Handler() в UARTx_IRQHandler вместо HAL_UART_IRQHandler() - RS_TIM_Handler() в TIMx_IRQHandler вместо HAL_TIM_IRQHandler() ******************************************************************************/ #include "rs_message.h" #include "modbus_diag.h" uint8_t RS_Buffer[RS_MSG_SIZE_MAX]; // uart buffer extern void RS_UART_Init(void); extern void RS_UART_DeInit(UART_HandleTypeDef *huart); extern void RS_TIM_Init(void); extern void RS_TIM_DeInit(TIM_HandleTypeDef *htim); //------------------------------------------------------------------- //-------------------------GENERAL FUNCTIONS------------------------- /** * @brief Начать прием по прерываниям. * @param hRS Указатель на хендлер RS. * @param RS_msg Указатель на структуру сообщения. * @return RS_RES Статус о состоянии RS после инициализации приема. */ RS_StatusTypeDef RS_Receive_IT(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg) { RS_StatusTypeDef RS_RES = 0; HAL_StatusTypeDef uart_res = 0; //-------------CHECK RS LINE---------------- // check that receive isnt busy if( RS_Is_RX_Busy(hRS) ) // if tx busy - return busy status return RS_BUSY; //-----------INITIALIZE RECEIVE------------- // if all OK: start receiving RS_EnableReceive(); RS_Set_Busy(hRS); // set RS busy RS_Set_RX_Flags(hRS); // initialize flags for receive hRS->pMessagePtr = RS_msg; // set pointer to message structire for filling it from UARTHandler fucntions if(!hRS->f.RX_Continue) // if not continue receiving hRS->RS_Message_Size = 0; // set ptr to start buffer // start receiving __HAL_UART_ENABLE_IT(hRS->huart, UART_IT_IDLE); uart_res = HAL_UART_Receive_IT(hRS->huart, &hRS->pBufferPtr[hRS->RS_Message_Size], RS_MSG_SIZE_MAX); // receive until ByteCnt+1 byte, // then in Callback restart receive for rest bytes // if receive isnt started - abort RS if(uart_res != HAL_OK) { RS_RES = RS_Abort(hRS, ABORT_RS); printf_rs_err("Failed to start RS receiving..."); TrackerCnt_Err(hRS->rs_err); } else { RS_RES = RS_OK; printf_rs("Start Receiving..."); TrackerCnt_Ok(hRS->rs_err); } hRS->RS_STATUS = RS_RES; return RS_RES; // returns result of receive init } /** * @brief Начать передачу по прерываниям. * @param hRS Указатель на хендлер RS. * @param RS_msg Указатель на структуру сообщения. * @return RS_RES Статус о состоянии RS после инициализации передачи. */ RS_StatusTypeDef RS_Transmit_IT(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg) { RS_StatusTypeDef RS_RES = 0; HAL_StatusTypeDef uart_res = 0; //-------------CHECK RS LINE---------------- // check that transmit isnt busy if( RS_Is_TX_Busy(hRS) ) // if tx busy - return busy status return RS_BUSY; // check receive line //------------COLLECT MESSAGE--------------- RS_RES = RS_Collect_Message(hRS, RS_msg, hRS->pBufferPtr); if (RS_RES != RS_OK) // if message isnt collect - stop RS and return error in RS_RES {// need collect message status, so doesnt write abort to RS_RES RS_Abort(hRS, ABORT_RS); RS_Handle_Receive_Start(hRS, hRS->pMessagePtr); // restart receive } else // if collect successful { //----------INITIALIZE TRANSMIT------------- RS_EnableTransmit(); RS_Set_Busy(hRS); // set RS busy RS_Set_TX_Flags(hRS); // initialize flags for transmit IT hRS->pMessagePtr = RS_msg; // set pointer for filling given structure from UARTHandler fucntion if(hRS->RS_Message_Size <= 0) { RS_RES = RS_Abort(hRS, ABORT_RS); TrackerCnt_Err(hRS->rs_err); return RS_ERR; } // if all OK: start transmitting uart_res = HAL_UART_Transmit_IT(hRS->huart, hRS->pBufferPtr, hRS->RS_Message_Size); // if transmit isnt started - abort RS if(uart_res != HAL_OK) { RS_RES = RS_Abort(hRS, ABORT_RS); printf_rs_err("Failed to start RS transmitting..."); TrackerCnt_Err(hRS->rs_err); } else { RS_RES = RS_OK; printf_rs("Start Transmitting..."); TrackerCnt_Ok(hRS->rs_err); } } hRS->RS_STATUS = RS_RES; return RS_RES; // returns result of transmit init } /** * @brief Инициалазация структуры @ref RS_HandleTypeDef. * @param hRS Указатель на хендлер RS. * @param suart Указатель на структуру с настройками UART. * @param stim Указатель на структуру с настройками таймера. * @param pRS_BufferPtr Указатель на буффер для приема-передачи по UART. Если он NULL, то поставиться библиотечный буфер. * @return RS_RES Статус о состоянии RS после инициализации. * @details Инициализация перефирии и структуры для приема-передачи по RS. */ RS_StatusTypeDef RS_Init(RS_HandleTypeDef *hRS, UART_HandleTypeDef *huart, TIM_HandleTypeDef *htim, uint8_t *pRS_BufferPtr) { // check that hRS is defined if (hRS == NULL) return RS_ERR; // check that huart is defined if (huart == NULL) return RS_ERR; hRS->huart = huart; hRS->htim = htim; // check that buffer is defined if (hRS->pBufferPtr == NULL) { hRS->pBufferPtr = RS_Buffer; // if no - set default } else hRS->pBufferPtr = pRS_BufferPtr; // if yes - set by user return RS_OK; } /** * @brief Отменить прием/передачу RS/UART. * @param hRS Указатель на хендлер RS. * @param AbortMode Выбор, что надо отменить. - ABORT_TX: Отмена передачи по ЮАРТ, с очищением флагов TX, - ABORT_RX: Отмена приема по ЮАРТ, с очищением флагов RX, - ABORT_RX_TX: Отмена приема и передачи по ЮАРТ, - ABORT_RS: Отмена приема-передачи RS, с очищением всей структуры. * @return RS_RES Статус о состоянии RS после аборта. * @details Отмена работы UART в целом или отмена приема/передачи RS. Также очищается хендл hRS. */ RS_StatusTypeDef RS_Abort(RS_HandleTypeDef *hRS, RS_AbortTypeDef AbortMode) { HAL_StatusTypeDef uart_res = 0; RS_Timeout_Stop(hRS); if((AbortMode&ABORT_RS) == 0x00) { if((AbortMode&ABORT_RX) == ABORT_RX) { uart_res = HAL_UART_AbortReceive(hRS->huart); // abort receive RS_Reset_RX_Flags(hRS); } if((AbortMode&ABORT_TX) == ABORT_TX) { uart_res = HAL_UART_AbortTransmit(hRS->huart); // abort transmit RS_Reset_TX_Flags(hRS); } } else { uart_res = HAL_UART_Abort(hRS->huart); RS_Clear_All(hRS); } hRS->RS_STATUS = RS_ABORTED; return RS_ABORTED; } //-------------------------GENERAL FUNCTIONS------------------------- //------------------------------------------------------------------- //------------------------------------------------------------------- //--------------------CALLBACK/HANDLER FUNCTIONS--------------------- /** * @brief Обработчик для начала приема. * @param hRS Указатель на хендлер RS. * @param RS_msg Указатель на структуру сообщения. * @return RS_RES Статус о состоянии RS после инициализации приема или окончания общения. * @details Определяет начинать прием команды/ответа или нет. */ RS_StatusTypeDef RS_Handle_Receive_Start(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg) { RS_StatusTypeDef RS_RES = 0; switch(hRS->sRS_Mode) { // В режиме мастер case RS_MASTER_REQUEST: RS_Timeout_Start(hRS); // сразу запускаем таймаут и начинаем прием // В режиме слейв case RS_SLAVE_ALWAYS_WAIT: RS_RES = RS_Receive_IT(hRS, RS_msg); // Просто запускаем фоновый прием break; case RS_RESERVED: RS_Set_Free(hRS); RS_RES = RS_OK; break; // end RS communication (set RS unbusy) } if(RS_RES != RS_OK) { TrackerCnt_Err(hRS->rs_err); } return RS_RES; } /** * @brief Обработчик для начала передачи. * @param hRS Указатель на хендлер RS. * @param RS_msg Указатель на структуру сообщения. * @return RS_RES Статус о состоянии RS после инициализации передачи. * @details Определяет отвечать ли на команду или нет. */ RS_StatusTypeDef RS_Handle_Transmit_Start(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg) { RS_StatusTypeDef RS_RES = 0; switch(hRS->sRS_Mode) { case RS_SLAVE_ALWAYS_WAIT: // in slave mode always response case RS_RESERVED: // transmit response case RS_MASTER_REQUEST: // transmit response RS_RES = RS_Transmit_IT(hRS, RS_msg); break; } if(RS_RES != RS_OK) { if(hRS->sRS_Mode < RS_MASTER_MODE_START) { RS_Handle_Receive_Start(hRS, RS_msg); } TrackerCnt_Err(hRS->rs_err); } return RS_RES; } /** * @brief UART TX Callback: коллбек после окончания передачи. * @param hRS Указатель на хендлер RS. * @return RS_RES Статус о состоянии RS после обработки приема. * @details Определяет поведение RS после передачи сообщения. */ RS_StatusTypeDef RS_UART_TxCpltCallback(RS_HandleTypeDef *hRS) { RS_StatusTypeDef RS_RES = RS_OK; HAL_StatusTypeDef uart_res = 0; //--------------ENDING TRANSMITTING------------- RS_Set_TX_End(hRS); //-----------START RECEIVING or END RS---------- RS_RES = RS_Handle_Receive_Start(hRS, hRS->pMessagePtr); return RS_RES; } /** * @brief Обработчик прерывания UART. * @param hRS Указатель на хендлер RS. * @details Обрабатывает ошибки если есть и вызывает RS Коллбеки. * Добавить вызов этой функции в UARTx_IRQHandler() ВМЕСТО HAL_UART_IRQHandler(). */ void RS_UART_Handler(RS_HandleTypeDef *hRS) { if(hRS->huart == NULL) { return; } RS_UART_Handler_ENTER(); //-------------CHECK IDLE FLAG FIRST------------- /* Проверяем флаг IDLE в первую очередь - это гарантирует обработку только после idle */ if(__HAL_UART_GET_FLAG(hRS->huart, UART_FLAG_IDLE) && __HAL_UART_GET_IT_SOURCE(hRS->huart, UART_IT_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(hRS->huart); // Важно: очистить флаг IDLE //-------------STANDARD UART HANDLING------------- HAL_UART_IRQHandler(hRS->huart); hRS->f.RX_Continue = 0; // Если прием активен и мы получили IDLE - это конец фрейма if(RS_Is_RX_Busy(hRS) && hRS->f.RX_Ongoing) { // Получаем количество фактически принятых байтов hRS->RS_Message_Size += hRS->huart->RxXferSize - hRS->huart->RxXferCount; if(hRS->RS_Message_Size > 0) { // Принудительно завершаем прием (получили сообщение) HAL_UART_AbortReceive(hRS->huart); // abort receive // Завершаем прием в нашей структуре RS_Set_RX_End(hRS); // Парсим наше сообщение hRS->RS_STATUS = RS_Parse_Message(hRS, hRS->pMessagePtr, hRS->pBufferPtr); // Если сообещине принято корректно if(hRS->RS_STATUS == RS_OK) { RS_Timeout_Stop(hRS); hRS->lastPacketTick = local_time(); if(hRS->sRS_Mode < RS_MASTER_MODE_START) { RS_Response(hRS, hRS->pMessagePtr); // отвечаем на запрос } else { RS_Set_Free(hRS); // освобожднаем RS if(hRS->pCallback) { hRS->pCallback(hRS, hRS->pMessagePtr); // обрабатываем ответ } } } else { RS_Handle_Receive_Start(hRS, hRS->pMessagePtr); // если сообщение пришло не полностью - продолжаем прием до таймаута } } } return; // Выходим после обработки IDLE } else { //-------------STANDARD UART HANDLING------------- HAL_UART_IRQHandler(hRS->huart); } //-------------CALL RS CALLBACKS------------ /* IF NO ERROR OCCURS */ if(hRS->huart->ErrorCode == 0) { // if first byte is received and receive is active if((hRS->huart->RxXferCount+1 == hRS->huart->RxXferSize) && RS_Is_RX_Busy(hRS)) { RS_Timeout_Start(hRS); } RS_Timeout_Update(hRS); /* RX Callback - теперь НЕ вызываем здесь, ждем IDLE */ /* TX Callback */ if ((hRS->huart->TxXferCount == 0U) && RS_Is_TX_Busy(hRS) && // if all bytes are transmited and transmit is active hRS->huart->gState != HAL_UART_STATE_BUSY_TX) // also check that receive "REALLY" isnt busy RS_UART_TxCpltCallback(hRS); /* NOTE: RX Callback больше не вызывается здесь - ждем IDLE для гарантии конца фрейма */ } //----------------ERRORS HANDLER---------------- else { if (hRS->huart->ErrorCode & HAL_UART_ERROR_ORE) { MB_Diagnostics_CharacterOverrunCnt(); // <-- Обнаружено переполнение } //TrackerCnt_Err(hRS->rs_err); /* de-init uart transfer */ RS_Abort(hRS, ABORT_RS); RS_Handle_Receive_Start(hRS, hRS->pMessagePtr); // later, maybe, will be added specific handlers for err } RS_UART_Handler_EXIT(); } /** * @brief Обработчик прерывания TIM. * @param hRS Указатель на хендлер RS. * @details Попадание сюда = таймаут и перезапуск RS приема * Добавить вызов этой функции в TIMx_IRQHandler() ВМЕСТО HAL_TIM_IRQHandler(). */ void RS_TIM_Handler(RS_HandleTypeDef *hRS) { if(hRS->htim == NULL) { return; } RS_TIM_Handler_ENTER(); HAL_TIM_IRQHandler(hRS->htim); RS_Abort(hRS, ABORT_RS); hRS->RS_STATUS = RS_TIMEOUT; if(hRS->sRS_Mode < RS_MASTER_MODE_START) if(hRS->pMessagePtr->DeviceAddr == hRS->ID) // ошибка если таймаут по нашему сообщению TrackerCnt_Err(hRS->rs_err); if(hRS->sRS_Mode >= RS_MASTER_MODE_START) { // Мастер: коллбек и освобождение для нового запроса RS_Set_Free(hRS); if(hRS->pCallback) { hRS->pCallback(hRS, hRS->pMessagePtr); // обрабатываем ответ } } else { // Слейв: перезапускаем прием RS_Handle_Receive_Start(hRS, hRS->pMessagePtr); } RS_TIM_Handler_EXIT(); } /** * @brief Запуск таймаута приема. * @param hRS Указатель на хендлер RS. * @return RS_RES Статус операции. * @details Запускает таймер для отсчета времени ожидания следующего байта. */ RS_StatusTypeDef RS_Timeout_Start(RS_HandleTypeDef *hRS) { if(hRS->htim) { hRS->htim->Instance->CNT = 0; // reset cnt; if(hRS->sRS_Timeout) // if timeout setted { hRS->htim->Instance->ARR = hRS->sRS_Timeout; HAL_TIM_Base_Start_IT(hRS->htim); RS_Set_RX_Active_Flags(hRS); } } return RS_OK; } /** * @brief Остановка таймаута приема. * @param hRS Указатель на хендлер RS. * @return RS_RES Статус операции. * @details Останавливает таймер ожидания. */ RS_StatusTypeDef RS_Timeout_Stop(RS_HandleTypeDef *hRS) { if(hRS->htim) { // Останавливаем таймаут if(hRS->sRS_Timeout) HAL_TIM_Base_Stop_IT(hRS->htim); hRS->htim->Instance->CNT = 0; __HAL_TIM_CLEAR_IT(hRS->htim, TIM_IT_UPDATE); } return RS_OK; } /** * @brief Обновление (сброс) таймаута приема. * @param hRS Указатель на хендлер RS. * @return RS_RES Статус операции. * @details Сбрасывает счетчик таймера в 0. */ RS_StatusTypeDef RS_Timeout_Update(RS_HandleTypeDef *hRS) { if(hRS->htim) { hRS->htim->Instance->CNT = 0; // reset cnt; } return RS_OK; } //--------------------CALLBACK/HANDLER FUNCTIONS--------------------- //------------------------------------------------------------------- //------------------------------------------------------------------- //--------------WEAK PROTOTYPES FOR PROCESSING MESSAGE--------------- /** * @brief Пользовательская функция для ответа на запрос по UART. * @param hRS Указатель на хендлер RS. * @param RS_msg Указатель на структуру сообщения. * @return RS_RES Статус о результате ответа на комманду. */ __weak RS_StatusTypeDef RS_Response(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg) { /* Redefine function for user purposes */ return RS_ERR; } /** * @brief Пользовательская функция для сбора сообщения в буфер UART. * @param hRS Указатель на хендлер RS. * @param RS_msg Указатель на структуру сообщения. * @param msg_uart_buff Указатель на буффер UART. * @return RS_RES Статус о результате заполнения буфера. */ __weak RS_StatusTypeDef RS_Collect_Message(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg, uint8_t *msg_uart_buff) { /* Redefine function for user purposes */ return RS_ERR; } /** * @brief Пользовательская функция для парса сообщения из буфера UART. * @param hRS Указатель на хендлер RS. * @param RS_msg Указатель на структуру сообщения. * @param msg_uart_buff Указатель на буффер UART. * @return RS_RES Статус о результате заполнения структуры. */ __weak RS_StatusTypeDef RS_Parse_Message(RS_HandleTypeDef *hRS, RS_MsgTypeDef *RS_msg, uint8_t *msg_uart_buff) { /* Redefine function for user purposes */ return RS_ERR; } //--------------WEAK PROTOTYPES FOR PROCESSING MESSAGE--------------- //-------------------------------------------------------------------