Поскольку наш сайт, в первую очередь, создавался и существует для людей, то когда возникает какой-то запрос, я стараюсь оперативно создать пост на интересующую вас тему 👍 Так вот, в статье про протокол 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.
Я видел раньше реализацию FreeModbus под STM32, но там такой мрак был.
Я видел реализации других идей Автором. И пользовался ими.
Надеюсь данная реализация внесёт больше понимания темы.
Мне этот протокол нужен как воздух.
Поэтому постараюсь разобраться как можно быстрее и отписаться.
Благодарю, завтра начну тестировать)
Благодарю за реализацию.
Пытаюсь сконфигурировать Ваш пример для работы в ASCII:
1)поменял MB_ASCII_ENABLED в значение 1 и соответственно MB_RTU_ENABLED в 0.
2)выставил MB_ASCII в первом параметре функции eMBInit.
и возник вопрос:
программа Modbus poll - также корректно работает по протоколу MB_ASCII, НО
если заглянуть в меню Display/Communication то мы увидим что в ответе устройства за место стартового символа-0x3A идёт значение 0x08.
В программе-сниффере отображается тоже самое.
Могли бы Вы подсказать с чем это связано?
Сейчас протестирую.
Вроде все нормально.
Если можно, то чуть подробнее про добавление файлов в проект. После нажатия "Аdd" во вкладке "Include path" появляется путь , но в кавычках. Типа "F:\Projekt\Modbus1\Modules\modbus\ascii". В Вашем проекте это выглядит по другому. Похоже, что у меня эти файлы не собираются. Если несложно, опишите этот момент поподробнее. Путь прописывается только в этом месте?
Я добавил про папки описание, скорее всего с этим проблема:
"Небольшое уточнение, на всякий случай. Добавляемые папки с кодом должны быть "Source Folder", а не "Folder"..."
А по поводу пути, у меня относительные - чтобы не зависело от расположения самого проекта, а тут - "F:\Projekt\Modbus1\Modules\modbus\ascii" абсолютный, можно вручную поменять, как у меня - то есть относительно проекта.
Спасибо, помогло. А как Вы относительный путь вручную прописать? Просто удалить лишнее?
Ну да, я просто удаляю и к такому виду как на скриншоте привожу.
Еще раз здравствуйте! Переделал Ваш проект под STM32F107VCT6 на отладочной плате EasyMx PRO for STM32. Modbus Poll выдает timeout error. То есть "ответа нет". При работе по простой передаче данных на этом же UART все нормально проходит. Где посоветуете искать проблему? Переходник UART-USB (на плате) не может ведь влиять именно на модбас?
Привет! Думаю переходник вряд ли, а можете проект прислать? Я посмотрю.
Спасибо. Все заработало.
Отлично!
Единственный вопросик: первая буква "М" (Modbus) не отображается ни у Вас, ни у меня. Так должно быть?
Вот она)
Спасибо большое, все заработало!
Отлично!
Единственная правка: Для тех кто будет использовать преобразователь RS-485. Не забыть в portserial.c переключать пин приема-передачи в функции vMBPortSerialEnable()
Добавлю в текст об этом.
Если не трудно напишите, как это корректно сделать начиная с STM32Cube (я про Hardware Flow Control (RS485))
а можно поподробнее? Что значит переключать? Я не совсем силен пока в микроконтроллерах и программировании.
Не могу разобраться, где в функции vMBPortSerialEnable() требуется переключать пин приема- передачи. Я сделал так:
if (modbusUart->gState == HAL_UART_STATE_READY)
{
rs485On();
prvvUARTTxReadyISR();
rs485Off();
}
Передача не работает.
Привет, перед HAL_UART_Transmit_IT() переводи в режим передатчика, обратно по окончанию передачи в HAL_UART_TxCpltCallback().
Либо как вариант перед началом передачи - в режим передатчика, перед началом приема - в режим приемника.
Спасибо! Правильно работает по второму варианту.
Отлично )
Привет, поясните, при назначении пинов, мы используем 2 пина UART TX(PA2) и RX(PA3) в моем случае. Но в преобразователе RS485 необходимо объединить пины DE и RE для управления приемом\передачей. В Вашем проекте на какой пин STM 32 идет коммутация этой функции??
Я на RS232 тестировал, а в целом можно любой свободный пин взять и им переключать направление передачи.
Добрый день! Сломал голову! Помогите...
Пытался перенести Ваш код на STM32L476, на плате NUCLEOL476RG. Все перепроверил, даже ноги с STM на ноги преобразователь позванивал Timeout Error. Попробовал QModBus - она пишет нет ответа на запрос. Помогите пожалуйста!!!
Привет, а в прерывания по USART программа попадает?
Добрый день! Теоретически попадает, но проблема в моей "слепоте" - даже самого "говняного" осциллографа нет... Попробую сейчас по новой с "нуля" собрать, параллельно может блох своих подчищу... Посоветуйте как можно еще проверить прерывания, посоветуйте пожалуйста. Может какой-нибудь монитор порта навороченный...
Да под отладчиком брейкпоинт просто поставить.
Спасибо! ...точно... Ща буду разбираться - я отношусь скорее к огромному отряду "ламеров"...придется разбираться 🙂 Спасибо!!!!
Еще одна беда возникала постоянно: в файле mbrtu.c при запуске bulid выдавалось предупреждение: ../Modules/modbus/rtu/mbrtu.c: In function 'eMBRTUReceive':
../Modules/modbus/rtu/mbrtu.c:152:21: warning: variable 'xFrameReceived' set but not used [-Wunused-but-set-variable]
152 | BOOL xFrameReceived = FALSE;
| ^~~~~~~~~~~~~~
типа переменная установлена, но не используется...пробовал за"ремить" - собирается без проблем, но тут, по моему, размер принимаемых данных...
че-то не хочет скриншот вставлять
xFrameReceived можно удалить, не нужна)
Простите, за глупый вопрос, а Вы не пробовали менять величину адресного поля? Сделать ее скажем на 500-1000 абонентов?
Добрый день! Пытаюсь разобраться c отсутствием ответа (timeout error" - пошагово прогонял отладчиком: зависает в библиотеке HAL в вычислении HAL_GetTick. Дума из-за того, что обновиться сей час целая история. Снес все и переставил скачанное через WPN все равно... не было у вас такого при отладке?
Тоже на такой же отладке пытаюсь повторить. При запуске проги Modbus Poll скажем так первые 16 запросов проходят нормально, а потом пишет "Timeout еrror". В чем может быть проблема?
Если нажать кнопку RESET на отладке, всё начинается заново.
Надо пройтись поэтапно.
Если плата перестает отвечать, то либо не получает запрос, либо не отправляет ответ.
Если не отправляет ответ, то проверить, вызывается ли функция отправки по USART, либо до этого этапа не доходит.
Ну и далее по шагам причина выявится, без вариантов.
Я выявил баг с зависанием, проект перезалил в статье.
Добрый день. Возможно, вопрос глупый, но за что отвечает таймер, генерирующий прерывания каждые 50 мкс? Почему именно 50 мкс и где можно об этом поподробнее почитать?
Привет, посмотри в функции eMBRTUInit():
Можно и не 50 мкс сделать, просто величины поменять в соответствии с новым периодом.
Добрый день. Спасибо за урок и библиотеку, но я присоединяюсь к возникшей проблеме: стабильно через 61 цикл опроса устройство перестает отвечать. Запрос добегает устройства. В чем может быть может какой буфер переполняется? Плата STM32F103.
Добрый день, надо под отладчиком смотреть, что и как, на каком этапе проблема может быть.
Здравствуйте.
Проблема на этапе отправки данных, выглядит так, как будто таймер перестает работать на 62 шаге опроса (без опроса по модбасу все работает). Далее отладчиком я не смог пролезть. Но учитывая переписку в этой ветке - данная проблема не только у меня. Пробовал на разных платах, история одна и та же. Проект (IAR) по ссылке: https://www.dropbox.com/s/s59jxufrpfdu5cg/MT_Modbus_RTU_Slave_Project%20_v0.rar?dl=0. За основу взят проект, указанный в статье, с тем лишь отличием, что под IDE IAR. Коллеги, может кто нашел решение данной проблемы?
С Вашим проектом наконец-то удалось воспроизвести проблему у себя ) Причем абсолютно идентичный проект, собранный в CubeIde работает стабильно.
По итогу пришлось погрузиться в отладочные дебри, даже думаю отдельно распишу что и как происходило, для истории 😀 В результате проблема наитупейшая - кто-то во время отладки оставил после себя кусок в mbrtu.c:
Естественно, со всеми вытекающими последствиями с обращением к памяти вне объявленного массива *facepalm*
Черт возьми оно работает.. Я пару недель проковырялся с этим багом, исползал все форумы))) Автор, огромная Вам благодарность и респект... Я новичок в микроконтроллерах и моя попытка отладки в прерывании не давала результата...
Если у Вас будет время и возможность, описать метод отладки в прерывании - как Вы нашли это проблему, я думаю многим новичкам, это пошло бы на пользу... Еще раз огромное спасибо Вам за то что вы делаете)
Спасибо за отличный отзыв! Думаю сегодня дойдут руки и опишу, причина проблемы, конечно, по итогу наиглупейшая )
Добрый день. Скачал готовый проект, переделал его под Keil, реализовал регистр х4 по принципу вашего х3. Но при работе примерно через 3 минуты происходит краш данных, модбас пул шлет пакеты, но стм при этом перестает их читать. Проверял осциллографом тайминг отправки данных не нарушается, если параллельно читать тем же акцеспул то видно что мастер отсылает пакеты. При этом в дебаге keil видно что программа словно зацикивается в каком то месте. и счетчик перестает работать, вот ссылка на проект собранный для keil подскажите пожалуйста в чем может быть проблемма? пробовал на другом чипе f411 ситуация такая же. https://drive.google.com/file/d/1TbiqAKSgq31a8khUYqQEZWB2X4VnPDg7/view?usp=sharing При перезагрузке МК все начинает работать опять на пару минут. на втором скрине, показано какой задействован регистр когда идет обмен данными по USART . Когда пакеты перестают читаться вот такие вот как на третьем скрине читаются регистры
Добрый день! Я попробую сегодня воспроизвести проблему у себя.
С нетерпением жду. Я тоже сегодня портировал код выше на Nucleo-l476, изменения минорные: таймер tim7, uart3, f1xx заменены на l4xx - остальное копипаста. В итоге при подключении сразу же timeout error. Я очень удивлен: там же по сути ломаться нечему. Возможно, индусы опять напутали с прерываниями в HAL_UART_AbortTransmit_IT.
Доброго времени, я выявил баг с зависанием, проект перезалил в статье.
Посидел с отладчиком, постоянно попадаю в неготовность таймера
HAL_TIM_Base_Start_IT
...
/* Check the TIM state */
if (htim->State != HAL_TIM_STATE_READY)
{
return HAL_ERROR; // <-
}
А какой период опроса? У меня не повторилось, минут 35 крутилось:
Прилагаю настройки. Вроде, не чащу. а ни одно запроса без ошибки. Выше еще человек писал о проблемах с L4, может, это какая-то фигня, специфичная для линейки?..
Можно для теста период таймера увеличить, допустим до 600 мс. Тогда в mbrtu.c меняем:
на
и
на
Не помогло, все равно тайм-аут... Дело или в хале, или в тактировании,которое в L серии забубенное, или в особенностях серии, или в расширенных настройках (прилагаю дефальтные). Хочу посидеть в отладчике, чтобы разобраться с ошибками, заодно сравнить с полностью работающим примером на откопанной в ящике bluepill. Поправьте меня, если я ошибаюсь в работе библиотеки и примера при правильном приеме фрейма:
Здесь может быть хрупкое место, что мы меняем флаги прерываний внутри обработчика прерываний и внутри critical_section; может так получиться, что F серия такие вольности позволяет, а L уже нет...
Таймер на 3.5 символа должен включаться после приёма первого байте. Этот таймер нужен для детектирования конца фрейма, посему зачем его включать раньше?
Запрещать-разрешать прерывания можно и нужно именно внутри критических секций, собственно, они для подобных целей и предназначены. И это никакая не вольность. Видимо, нет понимания о механизмах работы прерываний.
Виноват, заработало. Оказалось, что у меня провод помирал, а поведение между сериями совпадает полностью...
При чём тут провод? Не понял. На всякий случай напомню, что Модбас как и любой промышленный протокол должен быть устойчив к обрывам связи. Более того, сбой обмена, обрывы и замыкания линии должны считаться нормальным состояние и корректно, без зависаний и перегрузки процессора, обрабатываться.
Хрен знает. Покурил спеку, почитал код. Ни строчки не менял относительно исходного из статьи и значений по умолчанию, кроме других таймера и уарта, на удачу заменил провод - заработало. Ошибка выше была из-за того, что я не тормозил таймер при остановке на брейкпоинте (DBGMCU->APB1FZR1 |= DBGMCU_APB1FZR1_DBG_TIM7_STOP;)
Доброго вечера, я с этим примером (а он не только как на этом сайте, в интернете куча подобных примеров с реализацией free modbas), убил неделю, и всегда проблема возникала которую выше описал, в итогде запилил все через прерывание IDLE, добавив лишь пару строк в прерывание для расчета CRC, и все на ура заработало с первого раза, притом что на самой высокой скорости 115200, и с самым минимальным интервалом отправки фреймов, тестировал на нескольких умтроймтвах в том числе на ПК, все отлично.
Если что пользовался в качестве примера для работы с IDLE вот этим вот ресурсом https://www.youtube.com/watch?v=2qkGNR6aKGY&feature=youtu.be&ab_channel=%D0%9E%D0%BB%D0%B5%D0%B3%D0%92%D0%BE%D0%BB%D0%BA%D0%BE%D0%B2, для расчета контролькой суммы вот этим вот https://www.youtube.com/watch?v=0sDySNuS9dI&t=127s&ab_channel=%D0%90%D0%BB%D0%B5%D0%BA%D1%81%D0%B0%D0%BD%D0%B4%D1%80%D0%9F%D0%B8%D1%81%D0%B0%D0%BD%D0%B5%D1%86. Что касается драйвера интерфейса купил за 20 рублей MAX232 и по даташитку припаял к нему 5 конденсаторов на 0.1uf.
Андрей, можете пожалуйста поделиться исходниками проекта с решенной проблемой?
Доброго времени, я выявил баг с зависанием, проект перезалил в статье.
Прикольная библиотека. 20000 раз в секунду дёргать прерывания процессора от таймера ради тиков в 50мкс, пусть даже только во время приёма и передачи фрейма.
В STM32 даже кастрированный UART позволяет принимать Модбас-фрейм, вызывая _одно_ прерывание, и отправлять, расходуя _два_ прерывания. И всё это используя DMA. Если для Модбас используется только один порт, то можно и вычисление CRC делать, используя аппаратный блок, имеющийся в STM32.
Ещё не заметил как реализовано управление направлением приёма-передачи трансвера или предполагается, что у всех есть куча MAX13487?
Не на всех ядрах UART умеет фреймы принимать. Только те, которые в UART имеют регистры таймаута.
Остальные только с библиотекой.
UART умеет принимать фреймы _на_всех_ STM32. Просто по-умному это делается с использованием RTO, а когда его нет, то вполне себе применимо IDLE. Проверено на F4 без RTO и на F7 где он есть. В настройках OPC-сервера перед запросом достаточно добавить небольшую задержку, чтоб было почти по честному.
Не подскажете, как тогда реализовать t1.5, а не только t3.5 на RTO? Если уж по феншую, с минимумом прерываний, то и спеку хорошо бы полностью реализовывать. На приеме по байту можно просто ставить ARR = CNT + t1.5, а после первого срабатывания ARR = CNT + t2, чтобы различать межбайтовый интервал и межфреймовый, но красота с приемом по DMA теряется.
Во-первых, а зачем _точно_ измерять эти интервалы? Тут написано "не менее", тут "не более". Главное поймать конец фрейма. Перед отправкой фрейма делается задержка, например, на 2мс (>19200)- и всё. Она всё равно нужна для трансивера.
Во-вторых, IDLE, похоже, как раз и был создан как детектор ошибок. У него и длина около межсимвольной паузы.
Все современные МК используют FIFO и DMA, поэтому идёт _сплошной_ поток байт. Все промышленные платы RS-485/422 имеют буферы на весь фрейм, поэтому обмен идёт без пауз. Для работы Модбас на STM32 достаточно двух прерываний от DMA, одного от UART (RTO и/или IDLE). Всё остальное- это блажь из мира умерших восьмибитников а-ля AVR.
Во freemodbus не предусмотрена работа нескольких мастеров и слэйвов на одном МК. Я уж не говорю об одновременной работе RTU и TCP. Нафиг она такая примитивная нужна?
День добрый пытался по вашему проекту портировать Fremodbus под stm32f446re в IAR. Когда подключаю модпул Происходит ошибка Timeout error.
Добрый день, данные в uart поступают?
Чесно не не приходят я думал с таймером проблема. (Таймер вроди работает ) По поводу отправка прийомка не переключается. Но чесно сложно опеределить в чом проблема. Точно могу сказать что в начале проекта когда прокинул все файлы есть 12 ошибок(Прикрепил изображение ). решил проблему таким способом в файле MB.h прописал следующее
extern eMBException eMBFuncReportSlaveID ( UCHAR * pucFrame, USHORT * usLen );
extern eMBException eMBFuncReadInputRegister ( UCHAR * pucFrame, USHORT * usLen );
extern eMBException eMBFuncReadHoldingRegister ( UCHAR * pucFrame, USHORT * usLen );
extern eMBException eMBFuncWriteMultipleHoldingRegister ( UCHAR * pucFrame, USHORT * usLen );
extern eMBException eMBFuncWriteHoldingRegister ( UCHAR * pucFrame, USHORT * usLen );
extern eMBException eMBFuncReadWriteMultipleHoldingRegister ( UCHAR * pucFrame, USHORT * usLen );
extern eMBException eMBFuncReadCoils ( UCHAR * pucFrame, USHORT * usLen );
extern eMBException eMBFuncWriteCoil ( UCHAR * pucFrame, USHORT * usLen );
extern eMBException eMBFuncWriteMultipleCoils ( UCHAR * pucFrame, USHORT * usLen );
extern eMBException eMBFuncReadDiscreteInputs ( UCHAR * pucFrame, USHORT * usLen );
А в Файле porttimer.c
extern void vMBPortTimersEnable()
{
timerCounter = 0;
HAL_TIM_Base_Start_IT(modbusTimer);
}
/* --------------------------------------------------------------------------*/
extern void vMBPortTimersDisable()
{
HAL_TIM_Base_Stop_IT(modbusTimer);
}
проект скомпилился но не заработал должным образом.
Можешь проект выложить?
КАК ?
https://drive.google.com/file/d/1psebUtIa_ukoVxnnNBbgMBMWY_fKNBn3/view?usp=sharing
https://drive.google.com/file/d/15xtCmrCoBrr5HLhZHLHLNo7KgrjDx8C0/view?usp=sharing
Вот ссылка на гугл диск
Если не приходит ответ, то 2 глобальных варианта:
Надо под отладчиком изучить, какой вариант имеет место, принимается ли команда от мастера.
И взять надо с нуля изначальный проект из статьи и его портировать под STM32F446 (ничего кроме этого не меняя), без дополнительных библиотек, модулей и периферии.
Здравствуйте. Взял готовый проект MT_Modbus_RTU_Slave_Project на пробу под stm32f103. изменил порт с 1го на 2й с соответствующими настройками (на первом нет возможности повторить) и подправил для управляемого драйвером 485.
void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
if (xRxEnable == FALSE)
{
//SLAVE_RS485_SEND_MODE;
HAL_UART_AbortReceive_IT(modbusUart);
}
else
{
// SLAVE_RS485_RECEIVE_MODE;
HAL_GPIO_WritePin(nRE_DE_485_GPIO_Port, nRE_DE_485_Pin, RESET); //reset - rx
HAL_UART_Receive_IT(modbusUart, &rxByte, 1);
}
if (xTxEnable == FALSE)
{
HAL_UART_AbortTransmit_IT(modbusUart);
}
else
{
if (modbusUart->gState == HAL_UART_STATE_READY)
{
HAL_GPIO_WritePin(nRE_DE_485_GPIO_Port, nRE_DE_485_Pin, SET); //set - tx
prvvUARTTxReadyISR();
}
}
}
. Не работает. Куда смотреть? буду признателен за помощь.
Добрый день, пропустил комментарий... Удалось запустить?
Здравствуйте. Скачал готовый проект. Ничего в нем не менял. Скачал Modbus Poll для проверки. запустил. И только TX тикает, а данные никакие не появляются. (Где должно отображаться 'M' 'o' 'd' 'b' 'u' 's'). Компилировал в CubeIDE. Настройки все выставил в точности как в описании и все перепроверил. Почему не принимает слово "Modbus" из проекта?
Доброго времени, сначала надо проверить, есть ли связь в принципе - то есть принимает ли контроллер запрос и вообще хоть какие-то байты.
Да. Данные прилетают но при этом Modbus не показывает
Да. Данные прилетают но при этом Modbus не показывает
Да, так по трафику вообще все четко, проблема в самой Modbus Poll значит. Настройки?
А для особо неопытных можно пояснить, за что отвечает каждый callback?
Чтение разных типов регистров - https://microtechnics.ru/protokol-modbus-obzor-opisanie-i-primery-ispolzovaniya/. Название колбэка - по соответствующему типу.
Спасибо, благодаря данной статье разобрался)
Отлично )
Приделал трансмиттер MAX485, выход А8 сделал управляющим. Все работает. Спасибо автору за подробную статью.
Отлично, спасибо за отзыв 👍
На осциллографе смотрится вот так. У меня другая необходимость, менять скорость и адрес устройства в процессе работы. HOLDING REGISTER я приделал, данные с компа через ModScan в регистры я записываю. Инициализацию Modbus сделал отдельной процедурой и могу вызывать ее при необходимости (в ардуино я так и сделал, все работает) и тем самым по команде изменять параметры
Что нужно доделать, чтобы еще и записать регистры?
Тоже через callback'и:
Аргумент eMode:
Огромное спасибо автору, получилось запустить учебный пример с управлением MAX485.
Отлично!
По шаблону добавил регистры хранения, работает, не получается с Coil, ответ на запрос приходит, но реакция бредовая.
Какие именно симптомы?
Спасибо за внимание, и прошу не пинать меня строго. Я уже на пенсии, сейчас программирование для меня хобби, и на си, начал только изучать азы.
И так:
#define REG_COILS_START 0x100
#define REG_COILS_NREGS 4
static USHORT usRegCoilsStart = REG_INPUT_START;
static USHORT usRegCoilsBuf[REG_INPUT_NREGS];
дальше в callback для Coils скопировал код для Holding,
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
и далее по тексту,
(пока не понял как вставить код, получается какая то каша)
и заменил в нем holding на coils
Вопрос, корретно ли это сделано?
Симптомы, изменить значение можно только в одном из всех регистров, если исключить его из работы, значение можно изменить в другом, тоже только в одном.
А можете весь проект скинуть?
Конечно, а как это сделать?
На яндекс диск как вариант и ссылку сюда просто.
Не получается зарегистрироваться на яндексе, наверное потому, что живу в Харькове.
На почту может тогда отправите архив? aveal@microtechnics.ru
Да спасибо. Сейчас почищу мусор,перекомпилирую, проверю и отправлю. Еще раз повторюсь, для меня это учебная вещь, и чтобы вас это не напрягало.
Отправил.
Отлично, получил. А запросы через Modbus Poll отправляете?
Я может проблему не понял... Запустил у себя по-быстрому, функционирует, поменял значение трех элементов на скрине.
А скрин из какой программы?
В Modbus Poll, в OPC сервере Овен у меня все одинаково, могу поменять значение,только младшего регистра включенного в работу.
0A 01 00 63 00 08 CC A9
0A 01 01 00 53 AC
0A 05 00 63 FF 00 7D 5F
0A 01 01 01 92 6C
0A 05 00 64 FF 00 CC 9E ,или
0A 05 00 65 FF 00 60 5E ,или любой другой адрес
0A 01 01 01 92 6C
Наверное вопрос дилетанта из какой программы скрин, вас удивил, но разобрался, правда значения отображаются в десятичном формате.
При установке из Modbus Poll значения в Coils[99] "0" или "1" в usRegCoilsBuf[0] устанавливаются значения 171 и 362 соответственно. Можно установить все usRegCoilsBuf в 256, в Modbus Poll "1" будет только в Coils[99]. То есть чтото в программе я сделал не то.
А, понятно, я разобью на несколько комментариев, чтобы изображения прикрепить удобно было.
В целом, в каждом callback'е (под каждую команду) надо учитывать формат этой команды. Допустим, Write Single Coil, оставляем текущую обработку:
В итоге, если coil в состоянии "On", то получим в элементе usRegCoilsBuf[] значение 0x01. Вообще coil по своей сути - это флаг/один бит, поэтому правильнее в программе завести переменную, в которой для каждого coil будет выделен один бит. Но пока оставим так для наглядности, итак, я отправляю команды:
0A 05 00 63 FF 00 7D 5F
0A 05 00 65 FF 00 9D 5E
0A 05 00 67 FF 00 3C 9E
0A 05 00 69 FF 00 5D 5D
То есть меняю через одну относительно адресов как в Вашей программе, получаю в итоге такие значения (на скрине). По значениями понимаем, что все произошло верно, в соответствии с алгоритмом.
Дальше переходим к чтению, команда Read Coils, прочитаем значения 8-ми ячеек. По формату пакетов видим, что ответ на команду подразумевает, что мы отправим в ответ на запрос один байт, в котором каждый бит отвечает за один coil. Пока для простоты набросаю именно для 8-ми, если больше ячеек, то соответственно, нужно упаковывать уже в два байта в ответе на команду:
Если в нашем массиве не нулевое значение, то выставляем соответствующий текущей ячейке бит в единицу. Отправляем через Modbus Poll запрос на чтение:
0A 01 00 63 00 08 CC A9
Получаем ответ:
Тут все совпадает с тем, что мы записали.
Это на скорую руку пример, на память, может где напутал команды, но в целом функционирует - это факт ) Вот тут описание команд есть без лишней воды - ссылка.
Спасибо огромное за внимание и потраченное время.
Я догадывался, что обработка 1-битных регистров должна отличаться от 16-битных, но в демопримерах нужного не нашлось, а тупое копирование привело к забавным результатам.
Еще раз благодарю Вас за помощь, думаю, смогу дальше продвинуться более самостоятельно.
Все получилось.
Отлично, обращайтесь если что!
Все типы регистров.
Здравствуйте, я не смог понять, как modbas связан именно с UART1 и TIM3, что будет, если в программе будут еще подключены таймера и UART.
Добрый день, по-моему я сделал так, чтобы все настраивалось только двумя функциями MT_PORT_SetTimerModule() и MT_PORT_SetUartModule()... То есть, если надо другие использовать для Modbus, то все через аргументы этих функций. А если другая периферия используется для других задач, то ничего не надо делать, просто используем спокойно и все.
Стыдно приставать с тупыми вопросами, но, увы, это мой уровень знаний.
Я не понимаю, где в,
extern void MT_PORT_SetTimerModule(TIM_HandleTypeDef* timer);
extern void MT_PORT_SetUartModule(UART_HandleTypeDef* uart);
появляются волшебные цифры timer3 и uart1.
Да нормальные вопросы вполне )
Получается такая последовательность - в CubeMx выбираем TIM3 и USART1, он генерирует код с инициализацией. В этом коде для доступа к этим модулям созданы переменные - htim3 и huart1, и вот указатели на них передаем в функции:
Спасибо, пойду учить азбуку.
Мда, все по притче " смотришь в книгу, видиш ..........".
/* USER CODE BEGIN 2 */
MT_PORT_SetTimerModule(&htim3);
MT_PORT_SetUartModule(&huart1);
Еще раз огромное спасибо за Ваше терпение.
Не за что, рад, что получилось помочь )
Спасибо за урок!) Можете подсказать для чего нужны функции pxMBFrameCBByteReceived и pxMBFrameCBTransmitterEmpty? Пытаюсь воспользоваться этой библиотекой модбас при помощи цмсис в Кейл. Компилятор ругается Error: L6218E: Undefined symbol pxMBFrameCBByteReceived (referred from portserial.o).
Доброго времени суток!
Это callback-функции, чтобы драйверу сообщить об окончании операций приема/передачи. Может include потерялся в portserial.c?
Нет, include не потерялся. Но, я не могу ни в одном файле библиотеки найти тело этих функций.
Они как указатели на функции используются, в зависимости от режима будут разные вызовы:
Разобрался, файла mb.c не хватало в библиотеке) Спасибо)
Отлично )
Добрый день, сделал все как в уроке(как кажется) в Modbus Poll ошибка Checksum error. Что может быть?
Приветствую, надо проверить контрольную сумму в пакете - посчитать вручную по данным и сравнить с тем, что отправляется.
Вот так пишет.
Tx:000205-0A 04 03 E7 00 08 40 C4
Rx:000206-9D 01 AA 01 9D 01 AA 01 9D 01 AA 01 9D 01 AA 01 9E 01 AA 01 9D 01
Tx:000207-0A 04 03 E7 00 08 40 C4
Rx:000208-AA 01 9D 01 AA 01 9D 01 AA 01 9D 01 AA 01 9D 01 AA 01 9D 01 AA 01
Tx:000209-0A 04 03 E7 00 08 40 C4
Tx - это от платы в Modbus Poll? В Tx пакетах правильная CRC, а в Rx вообще какие-то левые будто данные.
А, или наоборот Tx - это запрос от Modbus Poll, а Rx отправляет плата в ответ? Тогда со стороны отправки что-то совсем не то отправляется.
Не могу разобраться, с чем это связано.
Надо под отладчиком смотреть, что в UART уходит.
Уходит вроде, что надо, а у вас есть проект под 4хх?
Есть подозрение, что проблема в конвекторе.
Готового нет, но там все то же самое должно быть. А что за плата, как все подключено?
Tx - я как понял, это от ПО, а Rx - от МК
Добрый день, Aveal!
Большое спасибо за проделанную работу. Перенес проект на NUCLEO-F401RE. По Вашим примерам подставил функции 01, 02, 03, 04, 05, 06. Я начинающий и это мой первый микроконтроллер. Сейчас смотрю как сделать функции 15 и 16. Пользуюсь Modbus Poll.
Есть некоторые проблемы:
Буду благодарен и части ответов/предположений.
Приветствую!
Так, ну в целом, для начала нужно попробовать локализовать проблемы с обменом. По факту первично проблему можно разделить на два глобальных варианта - либо проблема с отправляемым запросом, либо с ответом на этот запрос/команду. Может быть в какой-то момент запросы перестают отправляться, либо в ответе неверные данные и т. п.
Осциллографом или есть другие способы?
Можно осцилом или логическим анализатором. Либо на контроллере под отладчиком посмотреть, что программно в UART уходит, а в Modbus Poll там вроде есть какое-то отдельное окно, где сырые данные можно посмотреть.
Вот они, сырехонькие!
Tx:000000-01 01 00 63 00 04 CD D7
Rx:000001-01 01 01 05 91 8B
Tx:000002-01 01 00 63 00 04 CD D7
Rx:000003-01 01 01 05 91 8B
Tx:000004-01 01 00 63 00 04 CD D7
Tx:000005-01 01 00 63 00 04 CD D7
Rx:000006-01 01 01 05 91 8B
Tx:000007-01 01 00 63 00 04 CD D7
Rx:000008-01 01 01 05 91 8B
Tx:000009-01 01 00 63 00 04 CD D7
Rx:000010-01 01 01 05 91 8B
Tx:000011-01 01 00 63 00 04 CD D7
Tx:000012-01 01 00 63 00 04 CD D7
Tx:000013-01 01 00 63 00 04 CD D7
Tx:000014-01 01 00 63 00 04 CD D7
Rx:000015-01 01 01 05 91 8B
Tx:000016-01 01 00 63 00 04 CD D7
Запустил Debug. Не нашел что-то того, что нужно.
Вот на том этапе, когда на запрос нет ответа, надо понять - почему. Либо байты вообще не попадают в контроллер (это вряд ли), либо с ними что-то происходит не то, и драйвер не распознает modbus-команду, ну либо команда принимается, но ответ не отправляется (это я думаю тоже менее вероятно).
А вот это нормально?
while (1)
{
eMBPoll(); // дергаем Modbus
}
Не может он дергаться, когда на линии его не ждут?
В общем, пока информация такая, иногда приходит eMBErrorCode = MB_EIO;
Смотрю дальше.
Какая-то рассинхронизация, что ли, то нарастает количество ошибок, то уменьшается.
В общем, вот здесь
case EV_FRAME_RECEIVED:
eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
Когда usLength > 3, то все ОК!, если <= 3, то MB_EIO
Вот это с шибкой
ucRcvAddress UCHAR 1 '\001'
ucMBFrame UCHAR * 0x20000241 <ucRTUBuf+1> "\201\002
usLength USHORT 2
Тоже с ошибкой
ucRcvAddress UCHAR 1 '\001'
ucMBFrame UCHAR * 0x20000241 <ucRTUBuf+1> "\001\001\005
usLength USHORT 3
Еще с ошибкой
ucRcvAddress UCHAR 1 '\001'
ucMBFrame UCHAR * 0x20000241 <ucRTUBuf+1> "
usLength USHORT 3
Это нормальный прием
ucRcvAddress UCHAR 1 '\001'
ucMBFrame UCHAR * 0x20000241 <ucRTUBuf+1> "\001
usLength USHORT 5
Что-то с переходами
STATE_RX_IDLE:
STATE_RX_RCV:
Таймер там этот...
Нужно сказать, что из этих трех состояний:
STATE_RX_IDLE, /*!< Receiver is in idle state. */
STATE_RX_RCV, /*!< Frame is beeing received. */
STATE_RX_ERROR /*!< If the frame is invalid. */
в STATE_RX_ERROR
он не сваливается.
Не прирастает до минимального размера (4) usRcvBufferPos
Не получилось вчера ответить... А как там все электрически подключено, то есть снаружи контроллера что используется? Просто UART-USB конвертер и в ПК?
Да. обычный UART-USB конвертер, PL2303 китайский.
Если есть возможность проект скинуть, могу посмотреть, может у себя запущу, если найду плату подходящую.
Конечно, буду благодарен.
Сейчас отправлю на почту aveal@microtechnics.ru
Кидай тогда на почту - aveal@microtechnics.ru. Надеюсь наладим работу по итогу )
Еще со скоростью не очень. Больше 19200 не отвечает. Пока не анализировал, но пробовал разные варианты скорости, настройки sysclk и TIM.
Спасибо автору за предоставленный материал и поиск неисправности. В итоге дело оказалось в USB-UART преобразователе PL2303 китайском (подделка к Prolific...), поставил другой китайский CH340 USB - Serial. и в результате стабильная работа на исходных скоростях. Теперь можно отлаживать дальше.
Tx:000432-01 01 00 63 00 04 CD D7
Rx:000433-01 01 01 05 91 8B
Tx:000434-01 01 00 63 00 04 CD D7
Rx:000435-01 01 01 05 91 8B
Tx:000436-01 01 00 63 00 04 CD D7
Rx:000437-01 01 01 05 91 8B
Tx:000438-01 01 00 63 00 04 CD D7
Rx:000439-01 01 01 05 91 8B
Tx:000440-01 01 00 63 00 04 CD D7
Rx:000441-01 01 01 05 91 8B
Tx:000442-01 01 00 63 00 04 CD D7
Rx:000443-01 01 01 05 91 8B
Отлично, разобрались в итоге )
Скорость поддерживает разную. А вот с четностью и нечетностью пока не работает.
День добрый. Залил библиотеку под STM32F103VET6. Пины такие же как и таймер.
Пользуюсь средой IAR. Иногда и кубиком балуюсь.
Сделал всё как в примере. Но постоянно выдаёт ошибку 02.
На кристалле F103C8T6 в кубике, то же самое.
Хотелось бы понять, почему(.
Может по тому адресу, что в запросе, в устройстве ничего нет?
А как понять куда мы записали слово Modbus?
В примере дефайном, насколько я помню: REG_INPUT_START. Потом с этого адреса читаем.
Если даже взять полностью ваш пример, получается то же самое.
Ну у тебя запросы на плату кто-то внешний отправляет же?
Естественно.
И команда "Read holding registers" в коде другим колбэком обслуживается.
А вот тут поподробней пожалуйста))
Это 4 колбэка в "USER CODE 4"?
Да, вот эти:
а вот и дефайны
Ну там 107, а тут 1000, 999 поставь вместо 107.
всё равно ошибка выдаётся((
Попробуй количество 8 вместо 10.
о_О Заработало!
А можно объяснить, почему???
Спасибо большое!
Обращайся если что, рад, что заработало)
В примере просто 8 регистров используются, он при попытке 10 считать диагностирует неверный адрес и возвращает ошибку. Это условие не срабатывает:
Ещё один вопрос остался.
А амплитуда сигнала у всех устройств одинаковая?
В общем.
Зависит от физического уровня, ты можешь эти данные гнать, допустим, по юарту или по rs-485, уровни регламентируются физическим каналом связи.
rs485 интересует. Например если уменя уровень сигнала с MAX485 выходит 3.3В.
А я не знаю сколько выходит с плк. Боюсь что сгорит мк, да и вся периферия в целом((
MAX485 должна нормально переваривать весь диапазон допустимых для 485 напряжений, можно в даташите посмотреть более детально. Вообще под 3.3В там линейка MAX3485, насколько я помню.
Попробовал написать функцию на чтение дискретных входов.
Прописал в массиве: {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}.
Но Poll считывает: {0, 0, 0, 0, 0, 0, 0, 0, 1, 0}.
Не могу понять почему.
И хотелось бы ещё разобраться, как на писать функцию на чтение\запись выходов.
И как вообще правильно отправлять команды, например на старт\стоп чего-либо?
С чтением разобрался, осталось только с записью.))
Можете подсказать, в чем был затык по чтению. Тоже столкнулся с такой проблемой, читаются нули на Poll при массиве {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}. .
Правильно ли написан код на чтение\запись?
usRegInputDO 16-битный, а при записи пишутся только 8 бит.
Ну и Coils по идеологии Modbus - это один бит.
Вечер добрый.
Появилась проблема записи значений в массив, для отправки мастеру.
Выдаёт ошибку, которую я никак не могу понять.
Помогите пожалуйста. Что я делаю не так?
Тип данных неверный, скинь весь проект.
contactors.c
usRegInputStartAI - не массив, uint16_t usRegInputStartAI.
Я скачал, ссылку тогда не буду публиковать.
а как мне правильно в массив тогда записывать?
А всё, я очень невнимательный.
Спасибо))
Нет проблем, бывает )
Добрый день, попытался все внимательно прочитать, дошел до шага добавления файлов mt_port.c / mt_port.h, но так и не сообразил, откуда Вы их добавили\взяли???
Добрый день, эти создаются - новые файлы.
Спасибо за ответ, с этим разобрался, но вылезла при сборке проекта новая проблема. Ошибка вызова колбека., при этом в проекте есть ещё один таймер, который тоже работает по вызову. Может ли быть конфликт в написании кода в этой ситуации??
А какой текст ошибки?
...............................arm-none-eabi/bin/ld.exe: ./Modules/modbus/port/porttimer.o: in function `HAL_TIM_PeriodElapsedCallback':
Полностью надо.
Может HAL_TIM_PeriodElapsedCallback() в нескольких местах переопределен?
Подскажите, как корректно перезапустить протокол?
Добрый день, помогите пожалуйста советом. Возникла проблема, что наблюдаю неправильный ответ на линии UART-RS485, он и содержательно не похож на корректный, и отправляется неравномерными порциями, прикрепляю скрин, где пытаюсь читать два Holding-reg.
Использовала библиотеку для проекта c RS485 на STM32-NUCLEO-G071RB, поднимаю и опускаю ногу DE вручную. Прием посылок идет корректно, ответ формируется корректный (вижу по переменным txByte и rxByte). При отправке в UART используется как в примере HAL_UART_Transmit_IT. BaudeRate 115200.
Можно полностью исключить ошибку в формировании пакета, допустим в xMBPortSerialPutByte() каждый txByte сохранять в тестовом массиве, чтобы посмотреть ответный пакет целиком. Это для подстраховки, я так понял, что Вы и так проверили txByte по отдельности.
Если по части формирования данных все четко, то видимо в самом передающем тракте что-то не так. Можно опять же в txByte захардкодить какой-нибудь фиксированный байт, тогда на приеме должны получать только его. Дальше уже смотреть по тому, что реально принимается, в чем может быть проблема.
Может проблема в тактировании таймера? Для частоты 48 МГц настроила следующим образом:
Но если сырые байты на выходе верные, то они так и так в терминале должны быть верные, если от модбаса в целом абстрагироваться.
А настройки в целом вроде в норме.
Проблема решена, все оказалось намного прозаичнее, я использовала переходник для RS-USB вместо UART-RS-USB. Спасибо за отклик!)
Отлично, что решилось )
Здраствуйте! Помогите пожалуйста, ошибка timeout error
вот силка на проект
https://drive.google.com/drive/folders/1cN0CZFq831oa8yLAlrJcXomT3ytox4cf?usp=drive_link
Навскидку ничего в глаза не бросилось. Смотри под отладчиком - срабатывает ли прерывание по таймеру, приходит ли запрос по USART, отправляется ли ответ. В одном из этих трех мест будет проблема.
вот такая картина
Это понятно, вопрос - почему нет ответов. Есть 3 глобальные опции а данном случае:
а как ето проверить? Просто я новичек в етом но хочу розобраться
Самое первое, под отладчиком - поставить брейкпоинты в функциях:
Чтобы посмотреть, отрабатывают ли они вообще. В большинстве случаев оказывается, что проблема в физическом канале связи, соответственно, уже в HAL_UART_RxCpltCallback() не попадает.
а как поставить брейкпоинт, и как я понял ето в main.с, user code 4
Вот навскидку нашел базовый мануал по отладке - ссылка.
я добавил светлодиодик на 15 пин, переделал код и светлодиод не моргает
eMBErrorCode eMBRegInputCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs)
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
// Увімкнення LED на початку функції
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
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--;
}
// Вимкнення LED після успішного виконання
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
}
else
{
eStatus = MB_ENOREG;
// Блимаємо LED у випадку помилки
for (int i = 0; i < 5; i++)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_15);
HAL_Delay(200);
}
}
return eStatus;
}