Поскольку наш сайт, в первую очередь, создавался и существует для людей, то когда возникает какой-то запрос, я стараюсь оперативно создать пост на интересующую вас тему 👍 Так вот, в статье про протокол Modbus возникло сразу несколько вопросов о реализации на STM32, так что именно этим сегодня и займемся. А точнее - осуществим создание проекта для Modbus RTU Slave. Приступаем!
Вопреки устоявшейся практике, я не буду создавать свою библиотеку. С одной стороны, зачастую это оправданно - сделать свой вариант, в котором будешь априори уверен на 100%. Но, с другой стороны, если постоянно изобретать то, что уже изобретено, то не останется времени изобрести что-то новое ) В данном случае уже существует один вариант, причем он многократно протестирован, так что на нем и акцентируем внимание. И вариант этот - библиотека FreeModbus, скачиваем - ссылка.
Нюанс заключается в том, что она по какой-то причине не портирована на STM32, это уже придется возложить полностью на себя. Задействуем, как обычно, ввиду наибольшей популярности STM32CubeIDE, что подразумевает использование STM32CubeMx, а также библиотеки HAL. Все это нужно гармонично связать воедино.
Как уже сказал, начинаем со скачивания исходников библиотеки. Кроме того, создадим проект, я буду использовать STM32F103C8, опять же из-за его популярности и распространенности. Из периферии, во-первых, стандартный набор - внешний кварц, SWD и настройки тактирования:
Далее периферия под конкретную задачу. Коммуникацию я буду осуществлять через USART, соответственно, далее уже сигнал может быть преобразован, например, в RS-485. Поэтому активируем USART1 и помимо прочего какой-нибудь из таймеров, пусть будет TIM3.
Настройки USART стандартные:
Таймер же настраиваем на переполнение каждые 50 мкс:
И не упускаем из виду включение прерываний как для USART1, так и для TIM3:
На этом все, генерируем проект и сразу добавляем отдельную папку для модулей, в которую и помещаем папку modbus из скачанного архива, а в нее кладем подпапку port, исходное местоположение которой - freemodbus-v1.6\demo\BARE\port.
Небольшое уточнение, на всякий случай. Добавляемые папки с кодом должны быть "Source Folder", а не "Folder":
Если добавлены "обычные" папки, то правой кнопкой на названии проекта ⇒ New ⇒ Source Folder ⇒ Справа от строки "Folder Name" кнопка "Browse" ⇒ Выбираем нужную папку ⇒ Она становится "Source Folder". Это видно по синему мини-значку с буквой "C":
Именно здесь и находятся файлы, которые предназначены для портирования под ту или иную архитектуру:
- port.h
- portevent.c
- portserial.c
- porttimer.c
Кроме того, я добавил пару дополнительных файлов для повышения структурированности - mt_port.c / mt_port.h. Да, и не упускаем из виду добавление путей ко всем файлам в настройках проекта:
Пойдем по порядку, почему нет. В port.h нас интересуют две функции, которые обеспечивают атомарность операций. Правим следующим образом:
extern void EnterCriticalSection(); extern void ExitCriticalSection(); #define ENTER_CRITICAL_SECTION() EnterCriticalSection() #define EXIT_CRITICAL_SECTION() ExitCriticalSection()
Функции EnterCriticalSection()
и ExitCriticalSection()
, в свою очередь, созданы в mt_port.c. И раз уж дошли до этого файла, то там также добавим переменные для хранения указателей на используемые модули USART и Timer, а также функции для их изменения. Это нужно для того, чтобы вся конфигурация находилась в одном единственном месте, куда и будут вноситься изменения при использовании других модулей:
/** ****************************************************************************** * @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; } /*----------------------------------------------------------------------------*/
Все, на этом заканчиваем с:
- port.h
- mt_port.c
- mt_port.h
Полный код и готовый проект как и всегда будут в конце статьи. Дальше у нас:
- portevent.c
- portserial.c
- porttimer.c
Первый из них остается без изменений. Так что сразу ко второму пункту. Добавляем в portserial.c объявления необходимых переменных:
/* ----------------------- Variables ----------------------------------------*/ extern UART_HandleTypeDef* modbusUart; uint8_t txByte = 0x00; uint8_t rxByte = 0x00;
И далее прямо по функциям. vMBPortSerialEnable()
отвечает за включение и отключение передатчика/приемника. Требуемые режимы задаются аргументами xRxEnable
и xTxEnable
. Поскольку мы используем HAL, то нам нужно обеспечить работоспособность, чтобы при этом все было логично и прозрачно. Кроме того, обойдемся без дублирования огромных кусков HAL в portserial.c. Итого имеем:
/* ----------------------- Start implementation -----------------------------*/ /*----------------------------------------------------------------------------*/ void vMBPortSerialEnable(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(); } } } /* --------------------------------------------------------------------------*/
Здесь все, в принципе, понятно, за исключением разве что вызова prvvUARTTxReadyISR()
. Данная функция дает FreeModbus знать, что передатчик свободен, соответственно при его включении он само собой готов к началу передачи.
Тело функции xMBPortSerialInit()
оставлено пустым, поскольку за инициализацию периферии отвечает STM32CubeMx, а раз мы его используем, то дублировать эту работу просто преступление:
/* --------------------------------------------------------------------------*/ BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity) { return TRUE; } /* --------------------------------------------------------------------------*/
Функции отправки и получения байта данных:
/* --------------------------------------------------------------------------*/ BOOL xMBPortSerialPutByte(CHAR ucByte) { txByte = ucByte; HAL_UART_Transmit_IT(modbusUart, &txByte, 1); return TRUE; } /* --------------------------------------------------------------------------*/ BOOL xMBPortSerialGetByte(CHAR * pucByte) { *pucByte = rxByte; HAL_UART_Receive_IT(modbusUart, &rxByte, 1); return TRUE; } /* --------------------------------------------------------------------------*/
Остается часть, завязанная на прерывания:
/* --------------------------------------------------------------------------*/ static void prvvUARTTxReadyISR(void) { pxMBFrameCBTransmitterEmpty(); } /* --------------------------------------------------------------------------*/ static void prvvUARTRxISR(void) { pxMBFrameCBByteReceived(); } /* --------------------------------------------------------------------------*/ 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(); } } /* --------------------------------------------------------------------------*/
По приему:
- Попадаем в callback
HAL_UART_RxCpltCallback()
- Оттуда в
prvvUARTRxISR()
- Из нее вызываем
pxMBFrameCBByteReceived()
По окончанию передачи все аналогично:
HAL_UART_TxCpltCallback()
prvvUARTTxReadyISR()
pxMBFrameCBTransmitterEmpty()
При использовании UART - RS-485 преобразователя не забываем переключать режим его работы с приемника на передатчик и обратно в случае необходимости.
Прекрасно, с этим покончено, на очереди таймерная часть - porttimer.c. Действуем по той же схеме, переменные:
/* ----------------------- Variables ----------------------------------------*/ extern TIM_HandleTypeDef* modbusTimer; uint16_t timerPeriod = 0; uint16_t timerCounter = 0;
И пошли по функциям по порядку их появления. Инициализация также на откуп CubeMx:
/* ----------------------- Start implementation -----------------------------*/ /*----------------------------------------------------------------------------*/ BOOL xMBPortTimersInit(USHORT usTim1Timerout50us) { timerPeriod = usTim1Timerout50us; return TRUE; } /* --------------------------------------------------------------------------*/
Аргументом задается период в отсчетах таймера, а этот отсчет - 50 мкс. Запуск и отключение таймера просты до неприличия:
/* --------------------------------------------------------------------------*/ inline void vMBPortTimersEnable() { timerCounter = 0; HAL_TIM_Base_Start_IT(modbusTimer); } /* --------------------------------------------------------------------------*/ inline void vMBPortTimersDisable() { HAL_TIM_Base_Stop_IT(modbusTimer); } /* --------------------------------------------------------------------------*/
И на финише - обработка прерывания по переполнению:
/* --------------------------------------------------------------------------*/ static void prvvTIMERExpiredISR(void) { (void)pxMBPortCBTimerExpired(); } /* --------------------------------------------------------------------------*/ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == modbusTimer->Instance) { timerCounter++; if (timerCounter == timerPeriod) { prvvTIMERExpiredISR(); } } } /* --------------------------------------------------------------------------*/
Все, портирование завершено! Так как используем Modbus RTU, то и конфигурируем библиотеку должным образом. В mbconfig.h в папке include:
/*! \brief If Modbus ASCII support is enabled. */ #define MB_ASCII_ENABLED (0) /*! \brief If Modbus RTU support is enabled. */ #define MB_RTU_ENABLED (1) /*! \brief If Modbus TCP support is enabled. */ #define MB_TCP_ENABLED (0)
Выходим на финишную, все дальнейшие манипуляции исключительно в main.c. Воссоздадим демо-пример для Modbus RTU Slave, присутствующий в составе FreeModbus. Заключается он в том, что Slave ждет запроса на чтение регистров и при получении такового отвечает Master'у. Объявим стартовый адрес этих регистров и их количество:
/* USER CODE BEGIN PD */ #define REG_INPUT_START 1000 #define REG_INPUT_NREGS 8 /* USER CODE END PD */
Кроме того, переменные, в которых зададим соответственно данные этих регистров:
/* USER CODE BEGIN PV */ static USHORT usRegInputStart = REG_INPUT_START; static USHORT usRegInputBuf[REG_INPUT_NREGS] = {'M', 'o', 'd', 'b', 'u', 's', 0, 0}; /* USER CODE END PV */
Инициализация проста, ради этого мы и сделали все максимально адекватно:
/* USER CODE BEGIN 2 */ MT_PORT_SetTimerModule(&htim3); MT_PORT_SetUartModule(&huart1); eMBErrorCode eStatus; eStatus = eMBInit(MB_RTU, 0x0A, 0, 19200, MB_PAR_NONE); eStatus = eMBEnable(); if (eStatus != MB_ENOERR) { // Error handling } /* USER CODE END 2 */
Задаем выбранные периферийные модули в одном единственном месте - MT_PORT_SetTimerModule(&htim3)
и MT_PORT_SetUartModule(&huart1)
. Далее уже следует инициализация библиотеки. При этом обратите внимание, несмотря на то, что инициализацию мы не стали дублировать в коде библиотечных функций, тем не менее передаем актуальные параметры в eMBInit()
. Требование это вытекает из того, что библиотека исходя из этих значений выставит настройки, в частности, некоторые тайминги. Присутствующее здесь значение 0x0A - это адрес Slave, который мы задаем.
В while(1) выполняем следующее:
/* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ eMBPoll(); usRegInputBuf[REG_INPUT_NREGS - 2] = HAL_GetTick() / 1000; usRegInputBuf[REG_INPUT_NREGS - 1] = HAL_GetTick(); } /* USER CODE END 3 */
Собственно, дергаем FreeModbus - eMBPoll()
, а также в последние два регистра заносим время, прошедшее с момента включения питания - в секундах и миллисекундах.
Осталось реализовать callback'и:
eMBErrorCode eMBRegInputCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs)
eMBErrorCode eMBRegHoldingCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode)
eMBErrorCode eMBRegCoilsCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode)
eMBErrorCode eMBRegDiscreteCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete)
Они будут вызваны при поступлении соответствующих запросов. В данном проекте обеспечиваем только чтение регистров, все в том же main.c:
/* USER CODE BEGIN 4 */ /*----------------------------------------------------------------------------*/ eMBErrorCode eMBRegInputCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs) { eMBErrorCode eStatus = MB_ENOERR; int iRegIndex; if ((usAddress >= REG_INPUT_START) && (usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS)) { iRegIndex = (int)(usAddress - usRegInputStart); while(usNRegs > 0) { *pucRegBuffer++ = (unsigned char)(usRegInputBuf[iRegIndex] >> 8); *pucRegBuffer++ = (unsigned char)(usRegInputBuf[iRegIndex] & 0xFF); iRegIndex++; usNRegs--; } } else { eStatus = MB_ENOREG; } return eStatus; } /*----------------------------------------------------------------------------*/ eMBErrorCode eMBRegHoldingCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode) { return MB_ENOREG; } /*----------------------------------------------------------------------------*/ eMBErrorCode eMBRegCoilsCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode) { return MB_ENOREG; } /*----------------------------------------------------------------------------*/ eMBErrorCode eMBRegDiscreteCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete) { return MB_ENOREG; } /*----------------------------------------------------------------------------*/ /* USER CODE END 4 */
Проверяем параметры, то есть адрес регистра и их количество. А дальше просто заполняем буфер ответными данными.
Тестируем все это при помощи утилиты - Modbus Poll. Из настроек, сначала - метод коммуникации (Connection ⇒ Connect):
У меня это COM24, который через переходник подключен к STM32, к USART1, что логично. Далее настройки операций (Setup ⇒ Read/Write Definitions):
Запускаем:
Получаем результат, в виде тех самых данных, которые и присутствует в проекте.
На этом заканчиваем с Modbus RTU Slave, вот полный код измененных файлов:
/** ****************************************************************************** * @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) 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$ */ #ifndef _PORT_H #define _PORT_H #include <assert.h> #include <inttypes.h> #define INLINE 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: 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$ */ /* ----------------------- Modbus includes ----------------------------------*/ #include "mb.h" #include "mbport.h" /* ----------------------- Variables ----------------------------------------*/ static eMBEventType eQueuedEvent; static BOOL xEventInQueue; /* ----------------------- Start implementation -----------------------------*/ /*----------------------------------------------------------------------------*/ BOOL xMBPortEventInit(void) { xEventInQueue = FALSE; return TRUE; } /*----------------------------------------------------------------------------*/ BOOL xMBPortEventPost(eMBEventType eEvent) { xEventInQueue = TRUE; eQueuedEvent = eEvent; return TRUE; } /*----------------------------------------------------------------------------*/ BOOL xMBPortEventGet(eMBEventType * eEvent) { BOOL xEventHappened = FALSE; if( xEventInQueue ) { *eEvent = eQueuedEvent; xEventInQueue = FALSE; xEventHappened = TRUE; } return xEventHappened; } /*----------------------------------------------------------------------------*/
/* * 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; uint8_t txByte = 0x00; uint8_t rxByte = 0x00; /* ----------------------- Start implementation -----------------------------*/ /*----------------------------------------------------------------------------*/ void vMBPortSerialEnable(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 xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity) { return TRUE; } /* --------------------------------------------------------------------------*/ BOOL xMBPortSerialPutByte(CHAR ucByte) { txByte = ucByte; HAL_UART_Transmit_IT(modbusUart, &txByte, 1); return TRUE; } /* --------------------------------------------------------------------------*/ BOOL xMBPortSerialGetByte(CHAR * pucByte) { *pucByte = rxByte; HAL_UART_Receive_IT(modbusUart, &rxByte, 1); return TRUE; } /* --------------------------------------------------------------------------*/ static void prvvUARTTxReadyISR(void) { pxMBFrameCBTransmitterEmpty(); } /* --------------------------------------------------------------------------*/ static void prvvUARTRxISR(void) { pxMBFrameCBByteReceived(); } /* --------------------------------------------------------------------------*/ 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 "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 xMBPortTimersInit(USHORT usTim1Timerout50us) { timerPeriod = usTim1Timerout50us; return TRUE; } /* --------------------------------------------------------------------------*/ inline void vMBPortTimersEnable() { timerCounter = 0; HAL_TIM_Base_Start_IT(modbusTimer); } /* --------------------------------------------------------------------------*/ inline void vMBPortTimersDisable() { HAL_TIM_Base_Stop_IT(modbusTimer); } /* --------------------------------------------------------------------------*/ static void prvvTIMERExpiredISR(void) { (void)pxMBPortCBTimerExpired(); } /* --------------------------------------------------------------------------*/ 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 "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 */ #define REG_INPUT_START 1000 #define REG_INPUT_NREGS 8 /* 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 */ static USHORT usRegInputStart = REG_INPUT_START; static USHORT usRegInputBuf[REG_INPUT_NREGS] = {'M', 'o', 'd', 'b', 'u', 's', 0, 0}; /* 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 = eMBInit(MB_RTU, 0x0A, 0, 19200, MB_PAR_NONE); eStatus = eMBEnable(); 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 */ eMBPoll(); usRegInputBuf[REG_INPUT_NREGS - 2] = HAL_GetTick() / 1000; usRegInputBuf[REG_INPUT_NREGS - 1] = HAL_GetTick(); } /* 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 eMBRegInputCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs) { eMBErrorCode eStatus = MB_ENOERR; int iRegIndex; if ((usAddress >= REG_INPUT_START) && (usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS)) { iRegIndex = (int)(usAddress - usRegInputStart); while(usNRegs > 0) { *pucRegBuffer++ = (unsigned char)(usRegInputBuf[iRegIndex] >> 8); *pucRegBuffer++ = (unsigned char)(usRegInputBuf[iRegIndex] & 0xFF); iRegIndex++; usNRegs--; } } else { eStatus = MB_ENOREG; } return eStatus; } /*----------------------------------------------------------------------------*/ eMBErrorCode eMBRegHoldingCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode) { return MB_ENOREG; } /*----------------------------------------------------------------------------*/ eMBErrorCode eMBRegCoilsCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode) { return MB_ENOREG; } /*----------------------------------------------------------------------------*/ eMBErrorCode eMBRegDiscreteCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete) { return MB_ENOREG; } /*----------------------------------------------------------------------------*/ /* 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_Slave_Project.