Top.Mail.Ru

Modbus RTU Slave. Пример реализации на микроконтроллере STM32.

Поскольку наш сайт, в первую очередь, создавался и существует для людей, то когда возникает какой-то запрос, я стараюсь оперативно создать пост на интересующую вас тему 👍 Так вот, в статье про протокол Modbus возникло сразу несколько вопросов о реализации на STM32, так что именно этим сегодня и займемся. А точнее - осуществим создание проекта для Modbus RTU Slave. Приступаем!

Вопреки устоявшейся практике, я не буду создавать свою библиотеку. С одной стороны, зачастую это оправданно - сделать свой вариант, в котором будешь априори уверен на 100%. Но, с другой стороны, если постоянно изобретать то, что уже изобретено, то не останется времени изобрести что-то новое ) В данном случае уже существует один вариант, причем он многократно протестирован, так что на нем и акцентируем внимание. И вариант этот - библиотека FreeModbus, скачиваем - ссылка.

Нюанс заключается в том, что она по какой-то причине не портирована на STM32, это уже придется возложить полностью на себя. Задействуем, как обычно, ввиду наибольшей популярности STM32CubeIDE, что подразумевает использование STM32CubeMx, а также библиотеки HAL. Все это нужно гармонично связать воедино.

Как уже сказал, начинаем со скачивания исходников библиотеки. Кроме того, создадим проект, я буду использовать STM32F103C8, опять же из-за его популярности и распространенности. Из периферии, во-первых, стандартный набор - внешний кварц, SWD и настройки тактирования:

Проект для Modbus RTU Slave на STM32.
STM32CubeMx SWD.
STM32CubeMx HSE.
STM32CubeMx RCC.

Далее периферия под конкретную задачу. Коммуникацию я буду осуществлять через USART, соответственно, далее уже сигнал может быть преобразован, например, в RS-485. Поэтому активируем USART1 и помимо прочего какой-нибудь из таймеров, пусть будет TIM3.

Настройки USART стандартные:

Настройки USART.

Таймер же настраиваем на переполнение каждые 50 мкс:

Настройки таймера.

И не упускаем из виду включение прерываний как для USART1, так и для TIM3:

Прерывания USART.
Прерывания таймера.

На этом все, генерируем проект и сразу добавляем отдельную папку для модулей, в которую и помещаем папку modbus из скачанного архива, а в нее кладем подпапку port, исходное местоположение которой - freemodbus-v1.6\demo\BARE\port.

Небольшое уточнение, на всякий случай. Добавляемые папки с кодом должны быть "Source Folder", а не "Folder":

STM32CubeIDE Source Folder.

Если добавлены "обычные" папки, то правой кнопкой на названии проекта ⇒ New ⇒ Source Folder ⇒ Справа от строки "Folder Name" кнопка "Browse" ⇒ Выбираем нужную папку ⇒ Она становится "Source Folder". Это видно по синему мини-значку с буквой "C":

Структура проекта Modbus RTU Slave.

Именно здесь и находятся файлы, которые предназначены для портирования под ту или иную архитектуру:

  • port.h
  • portevent.c
  • portserial.c
  • porttimer.c

Кроме того, я добавил пару дополнительных файлов для повышения структурированности - mt_port.c / mt_port.h. Да, и не упускаем из виду добавление путей ко всем файлам в настройках проекта:

Include Paths STM32CubeIDE.

Пойдем по порядку, почему нет. В 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):

Modbus Poll.

У меня это COM24, который через переходник подключен к STM32, к USART1, что логично. Далее настройки операций (Setup ⇒ Read/Write Definitions):

Modbus RTU settings.

Запускаем:

Тестирование Modbus RTU Slave.

Получаем результат, в виде тех самых данных, которые и присутствует в проекте.

На этом заканчиваем с 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.

Подписаться
Уведомить о
guest

62 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Эдуард
5 месяцев назад

Я видел раньше реализацию FreeModbus под STM32, но там такой мрак был.
Я видел реализации других идей Автором. И пользовался ими.
Надеюсь данная реализация внесёт больше понимания темы.
Мне этот протокол нужен как воздух.
Поэтому постараюсь разобраться как можно быстрее и отписаться.

Павел
Павел
5 месяцев назад

Благодарю, завтра начну тестировать)

Сергей
Сергей
5 месяцев назад

Благодарю за реализацию. 
Пытаюсь сконфигурировать Ваш пример для работы в ASCII:
1)поменял MB_ASCII_ENABLED в значение 1 и соответственно MB_RTU_ENABLED в 0. 
2)выставил MB_ASCII в первом параметре функции eMBInit.
и возник вопрос: 
программа Modbus poll - также корректно работает по протоколу MB_ASCII, НО
если заглянуть в меню Display/Communication то мы увидим что в ответе устройства за место стартового символа-0x3A идёт значение 0x08.
В программе-сниффере отображается тоже самое. 
Могли бы Вы подсказать с чем это связано?

Андрей
Андрей
5 месяцев назад

Если можно, то чуть подробнее про добавление файлов в проект. После нажатия "Аdd" во вкладке "Include path" появляется путь , но в кавычках. Типа "F:\Projekt\Modbus1\Modules\modbus\ascii". В Вашем проекте это выглядит по другому. Похоже, что у меня эти файлы не собираются. Если несложно, опишите этот момент поподробнее. Путь прописывается только в этом месте?

Андрей
Андрей
Ответ на комментарий  Aveal
5 месяцев назад

Спасибо, помогло. А как Вы относительный путь вручную прописать? Просто удалить лишнее?

Андрей
Андрей
Ответ на комментарий  Aveal
5 месяцев назад

Еще раз здравствуйте! Переделал Ваш проект под STM32F107VCT6 на отладочной плате EasyMx PRO for STM32. Modbus Poll выдает timeout error. То есть "ответа нет". При работе по простой передаче данных на этом же UART все нормально проходит. Где посоветуете искать проблему? Переходник UART-USB (на плате) не может ведь влиять именно на модбас?

Андрей
Андрей
Ответ на комментарий  Aveal
5 месяцев назад

Спасибо. Все заработало.

Андрей
Андрей
Ответ на комментарий  Aveal
5 месяцев назад

Единственный вопросик: первая буква "М" (Modbus) не отображается ни у Вас, ни у меня. Так должно быть?

Василий
Василий
5 месяцев назад

Спасибо большое, все заработало!

Василий
Василий
Ответ на комментарий  Aveal
5 месяцев назад

Единственная правка: Для тех кто будет использовать преобразователь RS-485. Не забыть в portserial.c переключать пин приема-передачи в функции vMBPortSerialEnable()

Сергей
Сергей
Ответ на комментарий  Василий
4 месяцев назад

Если не трудно напишите, как это корректно сделать начиная с STM32Cube (я про Hardware Flow Control (RS485))

Рашид
Ответ на комментарий  Василий
4 месяцев назад

а можно поподробнее? Что значит переключать? Я не совсем силен пока в микроконтроллерах и программировании.

ru3dnw
4 месяцев назад

Добрый день! Сломал голову! Помогите...
Пытался перенести Ваш код на STM32L476, на плате NUCLEOL476RG. Все перепроверил, даже ноги с STM на ноги преобразователь позванивал Timeout Error. Попробовал QModBus - она пишет нет ответа на запрос. Помогите пожалуйста!!!

ru3dnw
Ответ на комментарий  Aveal
4 месяцев назад

Добрый день! Теоретически попадает, но проблема в моей "слепоте" - даже самого "говняного" осциллографа нет... Попробую сейчас по новой с "нуля" собрать, параллельно может блох своих подчищу... Посоветуйте как можно еще проверить прерывания, посоветуйте пожалуйста. Может какой-нибудь монитор порта навороченный...

ru3dnw
Ответ на комментарий  Aveal
4 месяцев назад

Спасибо! ...точно... Ща буду разбираться - я отношусь скорее к огромному отряду "ламеров"...придется разбираться 🙂 Спасибо!!!!

ru3dnw
Ответ на комментарий  Aveal
4 месяцев назад

Еще одна беда возникала постоянно: в файле 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;
   |           ^~~~~~~~~~~~~~
типа переменная установлена, но не используется...пробовал за"ремить" - собирается без проблем, но тут, по моему, размер принимаемых данных...

че-то не хочет скриншот вставлять

ru3dnw
Ответ на комментарий  Aveal
4 месяцев назад

Простите, за глупый вопрос, а Вы не пробовали менять величину адресного поля? Сделать ее скажем на 500-1000 абонентов?

Сергей
Сергей
Ответ на комментарий  Aveal
3 месяцев назад

Добрый день! Пытаюсь разобраться c отсутствием ответа (timeout error" - пошагово прогонял отладчиком: зависает в библиотеке HAL в вычислении HAL_GetTick. Дума из-за того, что обновиться сей час целая история. Снес все и переставил скачанное через WPN все равно... не было у вас такого при отладке?

Рашид
Ответ на комментарий  ru3dnw
4 месяцев назад

Тоже на такой же отладке пытаюсь повторить. При запуске проги Modbus Poll скажем так первые 16 запросов проходят нормально, а потом пишет "Timeout еrror". В чем может быть проблема?
Если нажать кнопку RESET на отладке, всё начинается заново.

Alexander
Alexander
3 месяцев назад

Добрый день. Возможно, вопрос глупый, но за что отвечает таймер, генерирующий прерывания каждые 50 мкс? Почему именно 50 мкс и где можно об этом поподробнее почитать?

Алексей
Алексей
2 месяцев назад

Добрый день. Спасибо за урок и библиотеку, но я присоединяюсь к возникшей проблеме: стабильно через 61 цикл опроса устройство перестает отвечать. Запрос добегает устройства. В чем может быть может какой буфер переполняется? Плата STM32F103.

Алексей
Алексей
Ответ на комментарий  Aveal
1 месяц назад

Здравствуйте.
Проблема на этапе отправки данных, выглядит так, как будто таймер перестает работать на 62 шаге опроса (без опроса по модбасу все работает). Далее отладчиком я не смог пролезть. Но учитывая переписку в этой ветке - данная проблема не только у меня. Пробовал на разных платах, история одна и та же. Проект (IAR) по ссылке: https://www.dropbox.com/s/s59jxufrpfdu5cg/MT_Modbus_RTU_Slave_Project%20_v0.rar?dl=0. За основу взят проект, указанный в статье, с тем лишь отличием, что под IDE IAR. Коллеги, может кто нашел решение данной проблемы?

Алексей
Алексей
Ответ на комментарий  Aveal
1 месяц назад

Черт возьми оно работает.. Я пару недель проковырялся с этим багом, исползал все форумы))) Автор, огромная Вам благодарность и респект... Я новичок в микроконтроллерах и моя попытка отладки в прерывании не давала результата...
Если у Вас будет время и возможность, описать метод отладки в прерывании - как Вы нашли это проблему, я думаю многим новичкам, это пошло бы на пользу... Еще раз огромное спасибо Вам за то что вы делаете)

Андрей
Андрей
1 месяц назад

Добрый день. Скачал готовый проект, переделал его под Keil, реализовал регистр х4 по принципу вашего х3. Но при работе примерно через 3 минуты происходит краш данных, модбас пул шлет пакеты, но стм при этом перестает их читать. Проверял осциллографом тайминг отправки данных не нарушается, если параллельно читать тем же акцеспул то видно что мастер отсылает пакеты. При этом в дебаге keil видно что программа словно зацикивается в каком то месте. и счетчик перестает работать, вот ссылка на проект собранный для keil подскажите пожалуйста в чем может быть проблемма? пробовал на другом чипе f411 ситуация такая же. https://drive.google.com/file/d/1TbiqAKSgq31a8khUYqQEZWB2X4VnPDg7/view?usp=sharing При перезагрузке МК все начинает работать опять на пару минут. на втором скрине, показано какой задействован регистр когда идет обмен данными по USART . Когда пакеты перестают читаться вот такие вот как на третьем скрине читаются регистры

Снимок экрана 2022-08-03 203625.png
Сергей
Сергей
Ответ на комментарий  Aveal
1 месяц назад

С нетерпением жду. Я тоже сегодня портировал код выше на Nucleo-l476, изменения минорные: таймер tim7, uart3, f1xx заменены на l4xx - остальное копипаста. В итоге при подключении сразу же timeout error. Я очень удивлен: там же по сути ломаться нечему. Возможно, индусы опять напутали с прерываниями в HAL_UART_AbortTransmit_IT.

Сергей
Сергей
Ответ на комментарий  Aveal
1 месяц назад

Посидел с отладчиком, постоянно попадаю в неготовность таймера
HAL_TIM_Base_Start_IT
...
 /* Check the TIM state */
 if (htim->State != HAL_TIM_STATE_READY)
 {
  return HAL_ERROR; // <-
 }

Сергей
Сергей
Ответ на комментарий  Aveal
1 месяц назад

Прилагаю настройки. Вроде, не чащу. а ни одно запроса без ошибки. Выше еще человек писал о проблемах с L4, может, это какая-то фигня, специфичная для линейки?..

image.png
Сергей
Сергей
Ответ на комментарий  Aveal
1 месяц назад

Не помогло, все равно тайм-аут... Дело или в хале, или в тактировании,которое в L серии забубенное, или в особенностях серии, или в расширенных настройках (прилагаю дефальтные). Хочу посидеть в отладчике, чтобы разобраться с ошибками, заодно сравнить с полностью работающим примером на откопанной в ящике bluepill. Поправьте меня, если я ошибаюсь в работе библиотеки и примера при правильном приеме фрейма:

  1. После инициализации включаем прерывание uart на прием и запускаем t3.5 таймер
  2. После приема каждого байта вызывается коллбек rx uart, в нем вызываем HAL_Receive_IT на следующий байт и перезапускаем таймер (основная движуха в xMBRTUReceiveFSM)
  3. После приема фрейма истекает таймер, постится эвент, который проверяется в eMBPoll. Если фрейм валидный и пришел по адресу, то на следующем вызове eMBPoll вызываются обработчики в соответствие с кодом функции. При успешном выполнении обработчиков шлем ответ, тоже по прерываниям tx uart
  4. По окончании отправки переходим в п.1

Здесь может быть хрупкое место, что мы меняем флаги прерываний внутри обработчика прерываний и внутри critical_section; может так получиться, что F серия такие вольности позволяет, а L уже нет...

settings.png
tonyk
tonyk
Ответ на комментарий  Сергей
1 месяц назад

Таймер на 3.5 символа должен включаться после приёма первого байте. Этот таймер нужен для детектирования конца фрейма, посему зачем его включать раньше?

Запрещать-разрешать прерывания можно и нужно именно внутри критических секций, собственно, они для подобных целей и предназначены. И это никакая не вольность. Видимо, нет понимания о механизмах работы прерываний.

Сергей
Сергей
Ответ на комментарий  Сергей
1 месяц назад

Виноват, заработало. Оказалось, что у меня провод помирал, а поведение между сериями совпадает полностью...

tonyk
tonyk
Ответ на комментарий  Сергей
1 месяц назад

При чём тут провод? Не понял. На всякий случай напомню, что Модбас как и любой промышленный протокол должен быть устойчив к обрывам связи. Более того, сбой обмена, обрывы и замыкания линии должны считаться нормальным состояние и корректно, без зависаний и перегрузки процессора, обрабатываться.

Сергей
Сергей
Ответ на комментарий  tonyk
1 месяц назад

Хрен знает. Покурил спеку, почитал код. Ни строчки не менял относительно исходного из статьи и значений по умолчанию, кроме других таймера и уарта, на удачу заменил провод - заработало. Ошибка выше была из-за того, что я не тормозил таймер при остановке на брейкпоинте (DBGMCU->APB1FZR1 |= DBGMCU_APB1FZR1_DBG_TIM7_STOP;)

Андрей
Андрей
Ответ на комментарий  Андрей
1 месяц назад

Доброго вечера, я с этим примером (а он не только как на этом сайте, в интернете куча подобных примеров с реализацией 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.

Gleboss
Ответ на комментарий  Андрей
1 месяц назад

Андрей, можете пожалуйста поделиться исходниками проекта с решенной проблемой?

tonyk
tonyk
1 месяц назад

Прикольная библиотека. 20000 раз в секунду дёргать прерывания процессора от таймера ради тиков в 50мкс, пусть даже только во время приёма и передачи фрейма.
В STM32 даже кастрированный UART позволяет принимать Модбас-фрейм, вызывая _одно_ прерывание, и отправлять, расходуя _два_ прерывания. И всё это используя DMA. Если для Модбас используется только один порт, то можно и вычисление CRC делать, используя аппаратный блок, имеющийся в STM32.

Ещё не заметил как реализовано управление направлением приёма-передачи трансвера или предполагается, что у всех есть куча MAX13487?

Эдуард
Ответ на комментарий  tonyk
1 месяц назад

Не на всех ядрах UART умеет фреймы принимать. Только те, которые в UART имеют регистры таймаута.
Остальные только с библиотекой.

tonyk
tonyk
Ответ на комментарий  Эдуард
1 месяц назад

UART умеет принимать фреймы _на_всех_ STM32. Просто по-умному это делается с использованием RTO, а когда его нет, то вполне себе применимо IDLE. Проверено на F4 без RTO и на F7 где он есть. В настройках OPC-сервера перед запросом достаточно добавить небольшую задержку, чтоб было почти по честному.

Сергей
Сергей
Ответ на комментарий  tonyk
1 месяц назад

Не подскажете, как тогда реализовать t1.5, а не только t3.5 на RTO? Если уж по феншую, с минимумом прерываний, то и спеку хорошо бы полностью реализовывать. На приеме по байту можно просто ставить ARR = CNT + t1.5, а после первого срабатывания ARR = CNT + t2, чтобы различать межбайтовый интервал и межфреймовый, но красота с приемом по DMA теряется.

tonyk
tonyk
Ответ на комментарий  Сергей
1 месяц назад

Во-первых, а зачем _точно_ измерять эти интервалы? Тут написано "не менее", тут "не более". Главное поймать конец фрейма. Перед отправкой фрейма делается задержка, например, на 2мс (>19200)- и всё. Она всё равно нужна для трансивера.
Во-вторых, IDLE, похоже, как раз и был создан как детектор ошибок. У него и длина около межсимвольной паузы.

Все современные МК используют FIFO и DMA, поэтому идёт _сплошной_ поток байт. Все промышленные платы RS-485/422 имеют буферы на весь фрейм, поэтому обмен идёт без пауз. Для работы Модбас на STM32 достаточно двух прерываний от DMA, одного от UART (RTO и/или IDLE). Всё остальное- это блажь из мира умерших восьмибитников а-ля AVR.

Во freemodbus не предусмотрена работа нескольких мастеров и слэйвов на одном МК. Я уж не говорю об одновременной работе RTU и TCP. Нафиг она такая примитивная нужна?

62
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x