Реализовав в предыдущей статье 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.