Реализовав в предыдущей статье Modbus RTU Slave на STM32, я, естественно, не имею никакого морального права обойти вниманием Master'а. Тем более что таких примеров по какой-то причине действительно очень мало. Так что герой сегодняшней статьи – Modbus RTU Master, все так же на контроллере STM32. Кроме того, вот ссылка на описание протокола Modbus в целом, своего рода теоретическая часть, которую теперь мы продолжаем практическими действиями. Приступаем!
И сразу же выявляется неприятный факт, что FreeModbus не имеет необходимого функционала. Тем не менее была обнаружена альтернативная/доработанная библиотека. Ее адаптацией и займемся.
Двигаемся по традиционной, максимально структурированной схеме. Соответственно, начнем с создания проекта и активации необходимой периферии. Все в точности как в упомянутой статье, поэтому много внимания уделять деталям не буду. Тактирование:
Приемо-передатчик USART1:
И таймер, также генерирующий прерывания по переполнению каждые 50 мкс:
Для обоих упомянутых периферийных модулей активируем прерывания. В результате получаем в распоряжение проект для STM32CubeIDE, в котором опять же идентичным образом добавляем папку Modules:
Поскольку это проект для Modbus Master, то я не вижу смысла тащить в него функционал для Slave, поэтому была проведена работа по уничтожению «лишнего». Таким образом, вот мой вариант библиотеки – ссылка. По итогу мы будем иметь в распоряжении проект для Slave из первой части, в котором нет ничего лишнего, и точно так же отдельный и независимый проект для Master’а, который сегодня и создадим.
Небольшое уточнение, на всякий случай. Добавляемые папки с кодом должны быть "Source Folder", а не "Folder":
Если добавлены "обычные" папки, то правой кнопкой на названии проекта ⇒ New ⇒ Source Folder ⇒ Справа от строки "Folder Name" кнопка "Browse" ⇒ Выбираем нужную папку ⇒ Она становится "Source Folder". Это видно по синему мини-значку с буквой "C".
Итоговая структура выглядит так:
Процесс портирования FreeModbus нам уже знаком, здесь добавляется только постфикс в названиях последних трех файлов:
- mt_port.c
- mt_port.h
- port.h
- portevent_m.c
- portserial_m.c
- porttimer_m.c
В добавленных mt_port.c и mt_port.h обеспечиваем обработку атомарных операций – EnterCriticalSection()
и ExitCriticalSection()
. А также установку параметров, отвечающих за используемую периферию:
/** ****************************************************************************** * @file : mt_port.c * @brief : Additional porting data * @author : MicroTechnics (microtechnics.ru) ****************************************************************************** */ /* Includes ------------------------------------------------------------------*/ #include "mt_port.h" /* Declarations and definitions ----------------------------------------------*/ static uint32_t lockCounter = 0; UART_HandleTypeDef* modbusUart; TIM_HandleTypeDef* modbusTimer; /* Functions -----------------------------------------------------------------*/ /*----------------------------------------------------------------------------*/ void EnterCriticalSection() { __disable_irq(); lockCounter++; } /*----------------------------------------------------------------------------*/ void ExitCriticalSection() { lockCounter--; if (lockCounter == 0) { __enable_irq(); } } /*----------------------------------------------------------------------------*/ void MT_PORT_SetTimerModule(TIM_HandleTypeDef* timer) { modbusTimer = timer; } /*----------------------------------------------------------------------------*/ void MT_PORT_SetUartModule(UART_HandleTypeDef* uart) { modbusUart = uart; } /*----------------------------------------------------------------------------*/
При помощи функций MT_PORT_SetTimerModule()
и void MT_PORT_SetUartModule()
мы сможем задать те конкретные аппаратные модули, которые задействованы в проекте. В действии увидим ближе к концу статьи. Пока же движемся дальше по вышеописанному списку файлов. В port.h добавляем вызов созданных в mt_port.c функций:
extern void EnterCriticalSection(); extern void ExitCriticalSection(); #define ENTER_CRITICAL_SECTION() EnterCriticalSection() #define EXIT_CRITICAL_SECTION() ExitCriticalSection()
На этом с первой половиной закончено.
В исходной версии библиотеки все было завязано на операционную систему, нам же это не нужно, поэтому portevent_m.c принял вид:
/* ----------------------- Modbus includes ----------------------------------*/ #include "mb.h" #include "mb_m.h" #include "mbport.h" #include "port.h" /* ----------------------- Defines ------------------------------------------*/ /* ----------------------- Variables ----------------------------------------*/ static eMBMasterEventType eQueuedEvent; static BOOL xEventInQueue; /* ----------------------- Start implementation -----------------------------*/ /*----------------------------------------------------------------------------*/ BOOL xMBMasterPortEventInit(void) { xEventInQueue = FALSE; return TRUE; } /*----------------------------------------------------------------------------*/ BOOL xMBMasterPortEventPost(eMBMasterEventType eEvent) { xEventInQueue = TRUE; eQueuedEvent = eEvent; return TRUE; } /*----------------------------------------------------------------------------*/ BOOL xMBMasterPortEventGet(eMBMasterEventType * eEvent) { BOOL xEventHappened = FALSE; if(xEventInQueue) { *eEvent = eQueuedEvent; xEventInQueue = FALSE; xEventHappened = TRUE; } return xEventHappened; } /*----------------------------------------------------------------------------*/ /** * This function is initialize the OS resource for modbus master. * Note:The resource is define by OS.If you not use OS this function can be empty. * */ void vMBMasterOsResInit(void) { } /*----------------------------------------------------------------------------*/ /** * This function is take Mobus Master running resource. * Note:The resource is define by Operating System.If you not use OS this function can be just return TRUE. * * @param lTimeOut the waiting time. * * @return resource taked result */ BOOL xMBMasterRunResTake(LONG lTimeOut) { return TRUE; } /*----------------------------------------------------------------------------*/ /** * This function is release Mobus Master running resource. * Note:The resource is define by Operating System.If you not use OS this function can be empty. * */ void vMBMasterRunResRelease(void) { } /*----------------------------------------------------------------------------*/ /** * This is modbus master respond timeout error process callback function. * @note There functions will block modbus master poll while execute OS waiting. * So,for real-time of system.Do not execute too much waiting process. * * @param ucDestAddress destination salve address * @param pucPDUData PDU buffer data * @param ucPDULength PDU buffer length * */ void vMBMasterErrorCBRespondTimeout(UCHAR ucDestAddress, const UCHAR* pucPDUData, USHORT ucPDULength) { xMBMasterPortEventPost(EV_MASTER_ERROR_RESPOND_TIMEOUT); } /*----------------------------------------------------------------------------*/ void vMBMasterErrorCBReceiveData(UCHAR ucDestAddress, const UCHAR* pucPDUData, USHORT ucPDULength) { xMBMasterPortEventPost(EV_MASTER_ERROR_RECEIVE_DATA); } /*----------------------------------------------------------------------------*/ void vMBMasterErrorCBExecuteFunction(UCHAR ucDestAddress, const UCHAR* pucPDUData, USHORT ucPDULength) { xMBMasterPortEventPost(EV_MASTER_ERROR_EXECUTE_FUNCTION); } /*----------------------------------------------------------------------------*/ void vMBMasterCBRequestScuuess(void) { xMBMasterPortEventPost(EV_MASTER_PROCESS_SUCCESS); } /*----------------------------------------------------------------------------*/ eMBMasterReqErrCode eMBMasterWaitRequestFinish(void) { eMBMasterReqErrCode eErrStatus = MB_MRE_NO_ERR; switch (eQueuedEvent) { case EV_MASTER_PROCESS_SUCCESS: break; case EV_MASTER_ERROR_RESPOND_TIMEOUT: eErrStatus = MB_MRE_TIMEDOUT; break; case EV_MASTER_ERROR_RECEIVE_DATA: eErrStatus = MB_MRE_REV_DATA; break; case EV_MASTER_ERROR_EXECUTE_FUNCTION: eErrStatus = MB_MRE_EXE_FUN; break; default: break; } return eErrStatus; } /*----------------------------------------------------------------------------*/
На очереди portserial_m.c, как следует из названия, здесь обеспечивается корректная работа с USART. Все тесно переплетается с реализацией Slave, но повторение – мать учения, поэтому пройдемся еще разок. Функция vMBMasterPortSerialEnable()
:
/* ----------------------- Start implementation -----------------------------*/ /*----------------------------------------------------------------------------*/ void vMBMasterPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable) { if (xRxEnable == FALSE) { HAL_UART_AbortReceive_IT(modbusUart); } else { HAL_UART_Receive_IT(modbusUart, &rxByte, 1); } if (xTxEnable == FALSE) { HAL_UART_AbortTransmit_IT(modbusUart); } else { if (modbusUart->gState == HAL_UART_STATE_READY) { prvvUARTTxReadyISR(); } } } /* --------------------------------------------------------------------------*/
В зависимости от аргументов функции производим включение, либо отключение приемника, либо передатчика. xMBMasterPortSerialInit()
оставляем пустой, инициализация пусть протекает силами STM32CubeMx, иначе зачем он нужен:
/* --------------------------------------------------------------------------*/ BOOL xMBMasterPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity) { return TRUE; } /* --------------------------------------------------------------------------*/
Отправка и прием информации в виде байта данных:
/* --------------------------------------------------------------------------*/ BOOL xMBMasterPortSerialPutByte(CHAR ucByte) { txByte = ucByte; HAL_UART_Transmit_IT(modbusUart, &txByte, 1); return TRUE; } /* --------------------------------------------------------------------------*/ BOOL xMBMasterPortSerialGetByte( CHAR * pucByte ) { *pucByte = rxByte; HAL_UART_Receive_IT(modbusUart, &rxByte, 1); return TRUE; } /* --------------------------------------------------------------------------*/
И обработка callback-ов по окончанию передачи и приема соответственно:
/* --------------------------------------------------------------------------*/ static void prvvUARTTxReadyISR(void) { pxMBMasterFrameCBTransmitterEmpty(); } /* --------------------------------------------------------------------------*/ static void prvvUARTRxISR(void) { pxMBMasterFrameCBByteReceived(); } /* --------------------------------------------------------------------------*/ void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == modbusUart->Instance) { prvvUARTTxReadyISR(); } } /* --------------------------------------------------------------------------*/ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == modbusUart->Instance) { prvvUARTRxISR(); } } /* --------------------------------------------------------------------------*/
При использовании UART - RS-485 преобразователя не забываем переключать режим его работы с приемника на передатчик и обратно в случае необходимости.
Приближаемся к закономерному итогу в виде работающего проекта, файл porttimer_m.c. Инициализацию также не будем дублировать, это просто не имеет ни малейшего смысла, пусть этим занимается CubeMx:
/* ----------------------- Start implementation -----------------------------*/ /*----------------------------------------------------------------------------*/ BOOL xMBMasterPortTimersInit(USHORT usTim1Timerout50us) { timerPeriod = usTim1Timerout50us; return TRUE; } /* --------------------------------------------------------------------------*/
Ну и остальные функции этого файла, суть которых, в целом, ясна из их названий. Весь код измененных файлов, как и готовый проект будут в конце статьи 👍
/* --------------------------------------------------------------------------*/ inline void vMBMasterPortTimersT35Enable() { vMBMasterSetCurTimerMode(MB_TMODE_T35); timerCounter = 0; HAL_TIM_Base_Start_IT(modbusTimer); } /* --------------------------------------------------------------------------*/ void vMBMasterPortTimersConvertDelayEnable() { vMBMasterSetCurTimerMode(MB_TMODE_CONVERT_DELAY); } /* --------------------------------------------------------------------------*/ void vMBMasterPortTimersRespondTimeoutEnable() { vMBMasterSetCurTimerMode(MB_TMODE_RESPOND_TIMEOUT); } /* --------------------------------------------------------------------------*/ inline void vMBMasterPortTimersDisable() { HAL_TIM_Base_Stop_IT(modbusTimer); } /* --------------------------------------------------------------------------*/ static void prvvTIMERExpiredISR(void) { (void)pxMBMasterPortCBTimerExpired(); } /* --------------------------------------------------------------------------*/ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == modbusTimer->Instance) { timerCounter++; if (timerCounter == timerPeriod) { prvvTIMERExpiredISR(); } } } /* --------------------------------------------------------------------------*/
Конфигурация библиотеки в файле modbus/include/mbconfig.h. В данном случае у нас только один вариант – Master RTU. Связано это с тем, что Slave по понятным причинам я искоренил, а режимы ASCII и TCP в библиотеке отсутствовали как вид. Не страшно, нам и нужен был RTU, так что дефайним:
/*! \brief If Modbus Master RTU support is enabled. */ #define MB_MASTER_RTU_ENABLED (1)
На этом портирование и адаптация библиотеки завершены, пишем демо-пример, для чего перемещаемся в main.c. Необходимо реализовать callback-функции:
/* USER CODE BEGIN 4 */ /*----------------------------------------------------------------------------*/ eMBErrorCode eMBMasterRegInputCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs) { eMBErrorCode eStatus = MB_ENOERR; return eStatus; } /*----------------------------------------------------------------------------*/ eMBErrorCode eMBMasterRegHoldingCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode) { eMBErrorCode eStatus = MB_ENOERR; return eStatus; } /*----------------------------------------------------------------------------*/ eMBErrorCode eMBMasterRegCoilsCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode) { eMBErrorCode eStatus = MB_ENOERR; return eStatus; } /*----------------------------------------------------------------------------*/ eMBErrorCode eMBMasterRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete ) { eMBErrorCode eStatus = MB_ENOERR; return eStatus; } /*----------------------------------------------------------------------------*/ /* USER CODE END 4 */
Они будут вызваны при наступлении соответствующих событий. Например, отправляем Slave запрос на чтение регистров (с помощью eMBMasterReqReadInputRegister()
). При получении корректного ответа попадем в eMBMasterRegInputCB()
, в которой можно добавить код для обработки этих полученных данных. Это уже зависит от конкретной задачи. В данном случае я их оставил пустыми за ненадобностью.
А в качестве теста буду отправлять запрос на запись регистра. Этой цели служит функция:
eMBMasterReqErrCode eMBMasterReqWriteHoldingRegister(UCHAR ucSndAddr, USHORT usRegAddr, USHORT usRegData, LONG lTimeOut)
Аргументы – адрес Slave, адрес регистра, записываемое значение, величина таймаута. Но прежде производим действия по инициализации:
/* USER CODE BEGIN 2 */ MT_PORT_SetTimerModule(&htim3); MT_PORT_SetUartModule(&huart1); eMBErrorCode eStatus; eStatus = eMBMasterInit(MB_RTU, 0, 19200, MB_PAR_NONE); eStatus = eMBMasterEnable(); if (eStatus != MB_ENOERR) { // Error handling } /* USER CODE END 2 */
Задаем периферийные модули и инициализируем библиотеку. Снова, несмотря на то, что инициализацию производит CubeMx, в eMBMasterInit()
надо передать актуальные параметры. Это требуется для функционирования FreeModbus. И в основном цикле выполняем следующее:
/* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ eMBMasterPoll(); eMBMasterReqWriteHoldingRegister(0x0A, 0x1234, 0x5678, 1000); HAL_Delay(1000); } /* USER CODE END 3 */
Собственно, раз в секунду производим отправку команды на запись в регистр 0x1234 значения 0x5678. Для проверки подключим USART1 к ПК, я взял самый что ни на есть обычный USB-UART на CP2102:
И в не менее обычном терминале анализируем данные в линии:
Команда приходит, процесс идет, проект работает 👍
Полный код измененных файлов:
/** ****************************************************************************** * @file : mt_port.c * @brief : Additional porting data * @author : MicroTechnics (microtechnics.ru) ****************************************************************************** */ /* Includes ------------------------------------------------------------------*/ #include "mt_port.h" /* Declarations and definitions ----------------------------------------------*/ static uint32_t lockCounter = 0; UART_HandleTypeDef* modbusUart; TIM_HandleTypeDef* modbusTimer; /* Functions -----------------------------------------------------------------*/ /*----------------------------------------------------------------------------*/ void EnterCriticalSection() { __disable_irq(); lockCounter++; } /*----------------------------------------------------------------------------*/ void ExitCriticalSection() { lockCounter--; if (lockCounter == 0) { __enable_irq(); } } /*----------------------------------------------------------------------------*/ void MT_PORT_SetTimerModule(TIM_HandleTypeDef* timer) { modbusTimer = timer; } /*----------------------------------------------------------------------------*/ void MT_PORT_SetUartModule(UART_HandleTypeDef* uart) { modbusUart = uart; } /*----------------------------------------------------------------------------*/
/** ****************************************************************************** * @file : mt_port.h * @brief : Additional porting data * @author : MicroTechnics (microtechnics.ru) ****************************************************************************** */ #ifndef MT_PORT_H #define MT_PORT_H /* Includes ------------------------------------------------------------------*/ #include "stm32f1xx_hal.h" /* Declarations and definitions ----------------------------------------------*/ /* Functions -----------------------------------------------------------------*/ extern void MT_PORT_SetTimerModule(TIM_HandleTypeDef* timer); extern void MT_PORT_SetUartModule(UART_HandleTypeDef* uart); #endif // #ifndef MT_PORT_H
/* * FreeModbus Libary: BARE Port * Copyright (C) 2013 Armink <armink.ztl@gmail.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * File: $Id: port.h ,v 1.60 2013/08/13 15:07:05 Armink add Master Functions $ */ #ifndef _PORT_H #define _PORT_H #include "mbconfig.h" #include "mt_port.h" #include <assert.h> #include <inttypes.h> #define INLINE #define PR_BEGIN_EXTERN_C extern "C" { #define PR_END_EXTERN_C } extern void EnterCriticalSection(); extern void ExitCriticalSection(); #define ENTER_CRITICAL_SECTION() EnterCriticalSection() #define EXIT_CRITICAL_SECTION() ExitCriticalSection() typedef uint8_t BOOL; typedef unsigned char UCHAR; typedef char CHAR; typedef uint16_t USHORT; typedef int16_t SHORT; typedef uint32_t ULONG; typedef int32_t LONG; #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #endif
/* * FreeModbus Libary: RT-Thread Port * Copyright (C) 2013 Armink <armink.ztl@gmail.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * File: $Id: portevent_m.c v 1.60 2013/08/13 15:07:05 Armink add Master Functions$ */ /* ----------------------- Modbus includes ----------------------------------*/ #include "mb.h" #include "mb_m.h" #include "mbport.h" #include "port.h" /* ----------------------- Defines ------------------------------------------*/ /* ----------------------- Variables ----------------------------------------*/ static eMBMasterEventType eQueuedEvent; static BOOL xEventInQueue; /* ----------------------- Start implementation -----------------------------*/ /*----------------------------------------------------------------------------*/ BOOL xMBMasterPortEventInit(void) { xEventInQueue = FALSE; return TRUE; } /*----------------------------------------------------------------------------*/ BOOL xMBMasterPortEventPost(eMBMasterEventType eEvent) { xEventInQueue = TRUE; eQueuedEvent = eEvent; return TRUE; } /*----------------------------------------------------------------------------*/ BOOL xMBMasterPortEventGet(eMBMasterEventType * eEvent) { BOOL xEventHappened = FALSE; if(xEventInQueue) { *eEvent = eQueuedEvent; xEventInQueue = FALSE; xEventHappened = TRUE; } return xEventHappened; } /*----------------------------------------------------------------------------*/ /** * This function is initialize the OS resource for modbus master. * Note:The resource is define by OS.If you not use OS this function can be empty. * */ void vMBMasterOsResInit(void) { } /*----------------------------------------------------------------------------*/ /** * This function is take Mobus Master running resource. * Note:The resource is define by Operating System.If you not use OS this function can be just return TRUE. * * @param lTimeOut the waiting time. * * @return resource taked result */ BOOL xMBMasterRunResTake(LONG lTimeOut) { return TRUE; } /*----------------------------------------------------------------------------*/ /** * This function is release Mobus Master running resource. * Note:The resource is define by Operating System.If you not use OS this function can be empty. * */ void vMBMasterRunResRelease(void) { } /*----------------------------------------------------------------------------*/ /** * This is modbus master respond timeout error process callback function. * @note There functions will block modbus master poll while execute OS waiting. * So,for real-time of system.Do not execute too much waiting process. * * @param ucDestAddress destination salve address * @param pucPDUData PDU buffer data * @param ucPDULength PDU buffer length * */ void vMBMasterErrorCBRespondTimeout(UCHAR ucDestAddress, const UCHAR* pucPDUData, USHORT ucPDULength) { xMBMasterPortEventPost(EV_MASTER_ERROR_RESPOND_TIMEOUT); } /*----------------------------------------------------------------------------*/ void vMBMasterErrorCBReceiveData(UCHAR ucDestAddress, const UCHAR* pucPDUData, USHORT ucPDULength) { xMBMasterPortEventPost(EV_MASTER_ERROR_RECEIVE_DATA); } /*----------------------------------------------------------------------------*/ void vMBMasterErrorCBExecuteFunction(UCHAR ucDestAddress, const UCHAR* pucPDUData, USHORT ucPDULength) { xMBMasterPortEventPost(EV_MASTER_ERROR_EXECUTE_FUNCTION); } /*----------------------------------------------------------------------------*/ void vMBMasterCBRequestScuuess(void) { xMBMasterPortEventPost(EV_MASTER_PROCESS_SUCCESS); } /*----------------------------------------------------------------------------*/ eMBMasterReqErrCode eMBMasterWaitRequestFinish(void) { eMBMasterReqErrCode eErrStatus = MB_MRE_NO_ERR; switch (eQueuedEvent) { case EV_MASTER_PROCESS_SUCCESS: break; case EV_MASTER_ERROR_RESPOND_TIMEOUT: eErrStatus = MB_MRE_TIMEDOUT; break; case EV_MASTER_ERROR_RECEIVE_DATA: eErrStatus = MB_MRE_REV_DATA; break; case EV_MASTER_ERROR_EXECUTE_FUNCTION: eErrStatus = MB_MRE_EXE_FUN; break; default: break; } return eErrStatus; } /*----------------------------------------------------------------------------*/
/* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter <wolti@sil.at> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * File: $Id$ */ #include "port.h" /* ----------------------- Modbus includes ----------------------------------*/ #include "mb.h" #include "mbport.h" #include "stm32f1xx_hal.h" /* ----------------------- static functions ---------------------------------*/ static void prvvUARTTxReadyISR(void); static void prvvUARTRxISR(void); /* ----------------------- Variables ----------------------------------------*/ extern UART_HandleTypeDef* modbusUart; static uint8_t txByte = 0x00; static uint8_t rxByte = 0x00; /* ----------------------- Start implementation -----------------------------*/ /*----------------------------------------------------------------------------*/ void vMBMasterPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable) { if (xRxEnable == FALSE) { HAL_UART_AbortReceive_IT(modbusUart); } else { HAL_UART_Receive_IT(modbusUart, &rxByte, 1); } if (xTxEnable == FALSE) { HAL_UART_AbortTransmit_IT(modbusUart); } else { if (modbusUart->gState == HAL_UART_STATE_READY) { prvvUARTTxReadyISR(); } } } /* --------------------------------------------------------------------------*/ BOOL xMBMasterPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity) { return TRUE; } /* --------------------------------------------------------------------------*/ BOOL xMBMasterPortSerialPutByte(CHAR ucByte) { txByte = ucByte; HAL_UART_Transmit_IT(modbusUart, &txByte, 1); return TRUE; } /* --------------------------------------------------------------------------*/ BOOL xMBMasterPortSerialGetByte( CHAR * pucByte ) { *pucByte = rxByte; HAL_UART_Receive_IT(modbusUart, &rxByte, 1); return TRUE; } /* --------------------------------------------------------------------------*/ static void prvvUARTTxReadyISR(void) { pxMBMasterFrameCBTransmitterEmpty(); } /* --------------------------------------------------------------------------*/ static void prvvUARTRxISR(void) { pxMBMasterFrameCBByteReceived(); } /* --------------------------------------------------------------------------*/ void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == modbusUart->Instance) { prvvUARTTxReadyISR(); } } /* --------------------------------------------------------------------------*/ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == modbusUart->Instance) { prvvUARTRxISR(); } } /* --------------------------------------------------------------------------*/
/* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter <wolti@sil.at> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * File: $Id$ */ /* ----------------------- Platform includes --------------------------------*/ #include "port.h" /* ----------------------- Modbus includes ----------------------------------*/ #include "mb.h" #include "mbport.h" #include "mb_m.h" #include "stm32f1xx_hal.h" /* ----------------------- static functions ---------------------------------*/ static void prvvTIMERExpiredISR(void); /* ----------------------- Variables ----------------------------------------*/ extern TIM_HandleTypeDef* modbusTimer; uint16_t timerPeriod = 0; uint16_t timerCounter = 0; /* ----------------------- Start implementation -----------------------------*/ /*----------------------------------------------------------------------------*/ BOOL xMBMasterPortTimersInit(USHORT usTim1Timerout50us) { timerPeriod = usTim1Timerout50us; return TRUE; } /* --------------------------------------------------------------------------*/ inline void vMBMasterPortTimersT35Enable() { vMBMasterSetCurTimerMode(MB_TMODE_T35); timerCounter = 0; HAL_TIM_Base_Start_IT(modbusTimer); } /* --------------------------------------------------------------------------*/ void vMBMasterPortTimersConvertDelayEnable() { vMBMasterSetCurTimerMode(MB_TMODE_CONVERT_DELAY); } /* --------------------------------------------------------------------------*/ void vMBMasterPortTimersRespondTimeoutEnable() { vMBMasterSetCurTimerMode(MB_TMODE_RESPOND_TIMEOUT); } /* --------------------------------------------------------------------------*/ inline void vMBMasterPortTimersDisable() { HAL_TIM_Base_Stop_IT(modbusTimer); } /* --------------------------------------------------------------------------*/ static void prvvTIMERExpiredISR(void) { ( void )pxMBMasterPortCBTimerExpired(); } /* --------------------------------------------------------------------------*/ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == modbusTimer->Instance) { timerCounter++; if (timerCounter == timerPeriod) { prvvTIMERExpiredISR(); } } } /* --------------------------------------------------------------------------*/
/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2022 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "mb.h" #include "mb_m.h" #include "mbport.h" #include "mt_port.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ TIM_HandleTypeDef htim3; UART_HandleTypeDef huart1; /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_TIM3_Init(void); static void MX_USART1_UART_Init(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM3_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ MT_PORT_SetTimerModule(&htim3); MT_PORT_SetUartModule(&huart1); eMBErrorCode eStatus; eStatus = eMBMasterInit(MB_RTU, 0, 19200, MB_PAR_NONE); eStatus = eMBMasterEnable(); if (eStatus != MB_ENOERR) { // Error handling } /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ eMBMasterPoll(); eMBMasterReqWriteHoldingRegister(0x0A, 0x1234, 0x5678, 1000); HAL_Delay(1000); } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /** * @brief TIM3 Initialization Function * @param None * @retval None */ static void MX_TIM3_Init(void) { /* USER CODE BEGIN TIM3_Init 0 */ /* USER CODE END TIM3_Init 0 */ TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; /* USER CODE BEGIN TIM3_Init 1 */ /* USER CODE END TIM3_Init 1 */ htim3.Instance = TIM3; htim3.Init.Prescaler = 71; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 50; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; if (HAL_TIM_Base_Init(&htim3) != HAL_OK) { Error_Handler(); } sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN TIM3_Init 2 */ /* USER CODE END TIM3_Init 2 */ } /** * @brief USART1 Initialization Function * @param None * @retval None */ static void MX_USART1_UART_Init(void) { /* USER CODE BEGIN USART1_Init 0 */ /* USER CODE END USART1_Init 0 */ /* USER CODE BEGIN USART1_Init 1 */ /* USER CODE END USART1_Init 1 */ huart1.Instance = USART1; huart1.Init.BaudRate = 19200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN USART1_Init 2 */ /* USER CODE END USART1_Init 2 */ } /** * @brief GPIO Initialization Function * @param None * @retval None */ static void MX_GPIO_Init(void) { /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); } /* USER CODE BEGIN 4 */ /*----------------------------------------------------------------------------*/ eMBErrorCode eMBMasterRegInputCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs) { eMBErrorCode eStatus = MB_ENOERR; return eStatus; } /*----------------------------------------------------------------------------*/ eMBErrorCode eMBMasterRegHoldingCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode) { eMBErrorCode eStatus = MB_ENOERR; return eStatus; } /*----------------------------------------------------------------------------*/ eMBErrorCode eMBMasterRegCoilsCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode) { eMBErrorCode eStatus = MB_ENOERR; return eStatus; } /*----------------------------------------------------------------------------*/ eMBErrorCode eMBMasterRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete ) { eMBErrorCode eStatus = MB_ENOERR; return eStatus; } /*----------------------------------------------------------------------------*/ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */
Ссылка на проект – MT_Modbus_RTU_Master_Project.
С учётом того что на чтение и запись используются разные функции где лучше добавить код чтобы выставлять 1 на отправку данных и 0 на прием для использования микросхемы sp3485 или аналогов.
Я бы поставил перед HAL_UART_Transmit_IT() выставление в 1 и в HAL_UART_TxCpltCallback() - в 0. Но надо проверить, что хватит быстродействия микросхемы, так как отправка сразу после подачи 1 начинается.
Думаю, что многим, как и мне, будет очень интересно портирование данной библиотеки под STM без выпиливания операционки. У меня много проектов построенных на FreeRTOS и вот сейчас потребовалось добавить ModbusRTU Master в один из проектов, а ранее с Modbus никогда не работал. Время реализации очень ограничено, вникание в вопрос может занять много времени. Не могли бы вы рассмотреть вышеописанный вопрос? Думаю у вас это не займет много времени, а нам станет намного легче. Спасибо.
Добрый день!
В ближайшее время не обещаю... Можно попробовать от реализации armink оттолкнуться, а там уже дальше под свои цели адаптировать.
Вот толковая статья про реализацию Модбас на STM32 с использованием DMA. Если в вашем МК стоит кастрированный UART без RTO, то замените его на IDLE.
https://habr.com/ru/post/522960/
Thank you very much! very helpful post.
Thank you, glad to hear!
Привет, все никак не могу сообразить, а как все-таки прочитать регистры со слэйва?
Запись проходит нормально, но при чтении, например, eMBMasterReqReadInputRegister колбэки не вызываются, и непонятно, где искать буфер с ответом слэйва... Либо я совсем запутался, либо одно из двух)
Привет, по идее должно в eMBMasterFuncReadInputRegister() попадать, оттуда в eMBMasterRegInputCB().
eMBMasterFuncReadInputRegister нигде не вызывается)
Она через xMasterFuncHandlers
В общем...
Дело дошло до eMBMasterRTUReceive, в котором не выполняется условие
usMBCRC16( ( UCHAR * ) ucMasterRTURcvBuf, usMasterRcvBufferPos ) == 0 )
Отдельный вопрос - зачем нужно, чтобы CRC было равно нулю, иначе функция по условию вываливается в ошибку MB_EIO (ошибка ввода-вывода)...
Но даже с игнорированием этого условия входа в eMBMasterFuncReadInputRegister() нет.
Может ли дело быть в том, что используется не реальное железо, а схема Proteus? Может, из-за симуляции какие-то проблемы с задержками/паузами...
Если у кого-нибудь есть желание проверить эту проблему на железе, был бы очень благодарен.
Да, в идеале, конечно, в железе пробовать, может и в симуляции проблема.
Спасибо Alex за подсказку, от которой я начал свой путь в ковырянии этой библиотеки.
К сожалению проблема не в Proteus, а в том, что библиотека мастера банально не дописана в плане функционала приема сообщений.
Во первых отсутствует файл mbrtu_m.h, почему мне не понятно.
Далее в mbrtu_m.c функция eMBMasterRTUReceive вычитывает не корректно сообщение из буффера. Конкретно в моем случае в нулевом байте буффера всегда висел ноль, он учитывался при расчете CRC и она никогда не сходилась. Исправил следующим образом:
/* Length and CRC check */ /*Ввел смещение 1, потому что так получается, если съедет еще, то надо исправлять*/
if( ( usMasterRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
&& ( usMBCRC16( ( UCHAR * ) ucMasterRTURcvBuf+1, usMasterRcvBufferPos-1 ) == 0 ) )
{
Далее мы начинаем наконец то в функции eMBMasterPoll попадать в секцию EV_MASTER_FRAME_RECEIVED
Но тут код от слейва преподносит нам злую шутку, потому что в условии приема идет проверка ID. Исправляется просто, комментируем часть условия:
eStatus = peMBMasterFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
/* Check if the frame is for us. If not ,send an error process event. */
if ( ( eStatus == MB_ENOERR ) ) //&& ( ucRcvAddress == ucMBMasterGetDestAddress() ) )
Теперь вы будете попадать в секцию EV_MASTER_EXECUTE, которая начнет вызывать нужную функцию обработки сообщения.
Но там тоже не все так просто. Откровенно говоря головой я понимаю что там написано +- правильная вещь, но она не работает. Видимо связано все также с бездумным заимствованием кода от Слейв устройства. Поскольку мне надо было решить наконец задачу приема, я написал все достаточно жестко.
eMBMasterFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
{
UCHAR *ucMBFrame;
USHORT usRegAddress;
USHORT usRegCount;
eMBException eStatus = MB_EX_NONE;
eMBErrorCode eRegStatus;
/* If this request is broadcast, and it's read mode. This request don't need execute. */
if ( xMBMasterRequestIsBroadcast() )
{
eStatus = MB_EX_NONE;
}
else if( *usLen >= MB_PDU_SIZE_MIN + MB_PDU_FUNC_READ_SIZE_MIN )
{
usRegAddress = ( USHORT )( pucFrame[0]); //Адрес устройства передатчика
usRegCount = ( USHORT )( pucFrame[2]>>1); //Кол-во регистров
eRegStatus = eMBMasterRegHoldingCB( &pucFrame[0], usRegAddress, usRegCount, MB_REG_READ );
}
else
{
/* Can't be a valid request because the length is incorrect. */
eStatus = MB_EX_ILLEGAL_DATA_VALUE;
}
return eStatus;
}
Наконец это отправит нас в долгожданный callback: eMBMasterRegHoldingCB
Где я уже работаю над разбором своих сообщений. Благо мне не нужен универсальный мастер.
Кстати метки MB_REG_READ / WRITE работают не так как ожидается. Может это я чего сломал. В любом случае я отдаю все сообщение в callback и дальше работаю над его расшифровкой как мне надо.
В любом случае автору респект за статью, благодаря ей у меня за пол часа завелся Master, который умеет отправлять сообщения в шину. А как добиться минимального функционала приема я постарался описать.
Подскажите пожалст, а куда данные то прочитанные складываются
По приему данных программа попадет автоматически в один из callback'ов, допустим в eMBMasterRegHoldingCB(). То есть по приему данных произойдет вызов этой функции, у нее первый аргумент: pucRegBuffer. В этой переменной будут принятые данные, которые внутри функции можно обработать.
не погли бы поделиться своим вариантом Modbus?) у меня все никак не получается починить считывание
Добрый день!
Подскажите, пожалуйста, можно ли как-то реализовать на одном МК и слейв и мастера? На разных UART
Ну технически-то можно, в целом ) Но библиотеку порядочно модифицировать придется.
где буфер куда данные приходят при чтении
В callback. И сверху комментарий на предмет модификаций, если прием не работает должным образом.
но callback вызывается в этой функции:
eMBException eMBMasterFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
{
UCHAR *ucMBFrame;
USHORT usRegAddress;
USHORT usRegCount;
eMBException eStatus = MB_EX_NONE;
eMBErrorCode eRegStatus;
/* If this request is broadcast, and it's read mode. This request don't need execute. */
if ( xMBMasterRequestIsBroadcast() )
{
eStatus = MB_EX_NONE;
}
else if( *usLen >= MB_PDU_SIZE_MIN + MB_PDU_FUNC_READ_SIZE_MIN )
{
usRegAddress = ( USHORT )( pucFrame[0]); //Адрес устройства передатчика
usRegCount = ( USHORT )( pucFrame[2]>>1); //Кол-во регистров
eRegStatus = eMBMasterRegHoldingCB( &pucFrame[0], usRegAddress, usRegCount, MB_REG_READ );
}
else
{
/* Can't be a valid request because the length is incorrect. */
eStatus = MB_EX_ILLEGAL_DATA_VALUE;
}
return eStatus;
}
в ней в callback передается pucFrame, который сам передается в функцию eMBMasterFuncReadHoldingRegister, а эта функция вызывается так:
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0
{MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBMasterFuncWriteMultipleHoldingRegister},
#endif
и я не вижу где здесь передается буфер для принятия данных. Подскажите как называется этот буфер
В mb_m.c:
Буфер:
не могли бы вы привести пример как мне считать данные, ucMBFrame это же локальнный указатель
main.c:
Спасибо) а где лучше переключать режим работы RS -485, в каком месте кода или в какой функции?
На прием/передачу? Лучше непосредственно перед передачей уходить в режим передатчика и сразу по окончанию - обратно в режим приемника. Но тут надо проверить будет, что отправка успевает пройти без проблем. Либо как вариант в режим приемника переходить перед вызовом HAL_UART_Receive_IT().
Спасибо)
у вас нет полностью рабочей версии modbus?
Я ее для своих задач использовал теста ради - все работало четко, а дальше уже не ковырялся со всеми режимами и т. д.
Получилось ли у вас реализовать прием данных? как вы вышли из ситуации ?
Доброго дня. Как я понял у меня не вызывается HAL_UART_RxCpltCallback, понимаю что вопрос уже не совсем по теме, подскажите пожалуйста, куда копать ? и как можно проверить причину?
Доброго,
для начала можно проверить, происходит ли вызов HAL_UART_Receive_IT(), тогда если после него HAL_UART_RxCpltCallback() не вызывается, то видимо данные не поступают, как вариант что-то с внешними соединениями. Можно осциллографом глянуть на ножке Rx контроллера, далее уже два варианта - либо данных нет физически, либо они не принимаются программно.
HAL_UART_Receive_IT() вызывается.
Осциллограф на ножке RX контроллера показывает постоянную логическую 1, при отключенном источнике сигналов (в моем случае max485). Поэтому то данных и нет(
Но я не понимаю, почему RX в единице. Это неправильно сконфигурирован порт? или уарт не опускает ножку на прием ?
Сигналы на ножках контроллера.
Фиолетовая - DE
Голубая - TX
Желтая - RX
Так, ну на Tx идет запрос, на Rx - ответ, вопрос, почему на Rx такие уровни.
Земли же соединены у STM и MAX485?
Макс запитан от 5В (на самом деле не макс а плата ttl to 485).
СТМ на плате дискавери.
если я отключаю ножку TX от 485 и смотрю на нее осциллографом, то на дискавери логическая 1 (3В). При этом если померить выход макса, то там хороший (правильный) сигнал 0-5В.
А осциллограмма как на фото получается, если их подключить друг к другу, поскольку сигнал 3В(от СТМ) и 5 В (от 485) смешиваются.
это фото если разъединить стм от 485 на ножке пине дискавери
это фото если отсоединить стм от 485 на ножке RO платы ttl to 485
А проект можешь скинуть?
https://drive.google.com/drive/folders/1ZDoGKfCMvQXHqOnEUjq9gu9kYDiBZpL2?usp=sharing
проект в Keil
futaba - это экран вывода данных. он исполнению не должен мешать.
Поменял порт usart1 на usart2. и функция HAL_UART_RxCpltCallback теперь вызывается. И даже eMBMasterRTUReceiveс условием
"usMBCRC16( ( UCHAR * ) ucMasterRTURcvBuf, usMasterRcvBufferPos ) == 0 )" выполняется. в четверг буду пробовать выводить данные.
Видимо ножку RX спалил порта usart 1 ....
Эх, ну главное, что причина найдена.
Доброго времени суток! Пытаюсь тоже сделать для "black pill" на stm32f411ceu6. При активации всех 3 USART'ов CubeMX пишет что:
"Status: Parity disabled conflict with: USART6: Mode Asynchronous ". Это значит нельзя все 3 USART'а использовать?
Доброго!
"Parity disabled" или "Partly disabled"?
Извините, да "Status: Partly disabled conflict with: USART6: Mode Asynchronous ". Там это предупреждение пропадает, если только USART1 оставить.
Просто часть функций пересекаются. Например, CubeMx ставит UART6_Tx / UART6_Rx на PA11 / PA12. А у USART1 на этих же ногах RTS и CTS сигналы, поэтому помечается предупреждением, что "частично функционал недоступен".
В данном случае, если RTS / CTS и не нужны, то все в порядке, можно работать.
А если RTS / CTS понадобятся, то можно ли на другие ноги вывести RTS / CTS для USART1 ?
Это уже надо смотреть для конкретного контроллера, есть ли remap доступный.Там 48-ми выводный корпус, насколько я помню, так что может и не быть возможности.
На pinouts diagram для USART всего 14 выводов. Но все равно в данном корпусе нет такой возможности значит ?
Именно USART1+RTS+CTS+UART6 похоже нет, в CubeMx можно пройтись по выводам, он покажет все доступные функции для каждого.
Это значит шанс есть разнести по разным пинам?
Ну я пробежался, для RTS/CTS не видел других вариантов.
Если оставить USART1 и USART2, то RCC Partly disabled conflict with USART2. А RCC на другие пины нельзя перекинуть ?
RCC нет, но там конфликтует только Audio Clock Input.
Прошу прощения за глупый вопрос.... Какой средой открывается Ваш проект???
Приветствую, STM32CubeIDE.
Здравствуйте, спасибо большое за библиотеку!
Никак не могу понять как прочитать данные со Slave, ни в одну callback-функцию не попадаю (eMBMasterRegInputCB, eMBMasterRegHoldingCB, eMBMasterRegCoilsCB, eMBMasterRegDiscreteCB). Ответ от slave есть, смотрел на осциллографе на ноге проца.
Прошу помочь!
Доброго времени суток! Вот в этом комменте полезные модификации - https://microtechnics.ru/modbus-rtu-master-biblioteka-dlya-mikrokontrollerov-stm32/#comment-80130, надо попробовать, не аналогичная ли проблема.
Правильно ли я понимаю, что ответ попадает в ucMasterRTURcvBuf?
Да
Добрый день! Написал код, который отправляет несколько пакетов единоразово, но столкнулся с проблемой, что контрольная сумма в каждом из пакетов остается с предыдущего пакета, причем первый пакет не отправляется. В чем может быть проблема?
Прикрепляю изображения кода, пакета №2 и пакета №3 (пакет №1 не отправляется).
Не знал, что отправится только последнее изображение. Ниже скрин кода.
И изображение второго пакета.
А контроллер какой?
STM32F405RG
А проект можешь выложить?
https://disk.yandex.ru/d/lbbKRS230omX6g
Проект сделан в STM32CubeIDE версия 1.9.0.
Хм, навскидку как вариант попробовать задержку миллисекунд 5-10 добавить после HAL_GPIO_WritePin(RSE_GPIO_Port, RSE_Pin, GPIO_PIN_SET); чтобы пакет не пропадал.
Подскажите, как использовать библиотеки для Slave и Master для разных USART в одном контроллере. Включал в полном объеме папки. Изменял имена переменных, названия функций, имена enum и содержащихся в них значений в Slave. Добавлял в main.c iclude соответствующих файлов с библиотеки Slave, но в зависимости от того чей #include mb.h первый объявляется ту библиотеку и использует. Следующий ниже, например #include mbsl.h игнорируется.
Только полностью все адаптировать/переписывать, библиотека изначально не заточена под такое...
Не знаю тема ещё актуальна или нет, для чтения регистров в режиме мастера, взял функцию чтения из библиотеки на GitHub и немного доработал. После выполнения eMBMasterReqReadHoldingRegister ответ в буфере сразу не появляется, нужно выполнить некоторое количество раз eMBMasterPoll, после этого произойдет событие eMBMasterFuncReadHoldingRegister и запишет данные в буфер регистров. Было принято решение после запроса чтение организовать безконечный цикл ожидания ограниченный Timeout'ом, в цикле выполнять eMBMasterPoll, так же пришлось завести глобальную переменную в качестве флага.
А есть вариант одной библиотеки, и для мастер и для слейва. Чет мне не получается их склеить. Нужно в контролер(ПЛК) оба варианта. т.к при связке 2х контролеров один мастер 2рой слейв.
Я целенаправленно не искал, но есть ощущение, что готового варианта не найти...