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

195 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Эдуард
1 год назад

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

Павел
Павел
1 год назад

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

Сергей
Сергей
1 год назад

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

Андрей
Андрей
1 год назад

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

Андрей
Андрей
Ответ на комментарий  Aveal
1 год назад

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

Андрей
Андрей
Ответ на комментарий  Aveal
1 год назад

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

Андрей
Андрей
Ответ на комментарий  Aveal
1 год назад

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

Андрей
Андрей
Ответ на комментарий  Aveal
1 год назад

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

Василий
Василий
1 год назад

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

Василий
Василий
Ответ на комментарий  Aveal
1 год назад

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

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

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

Рашид
Ответ на комментарий  Василий
1 год назад

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

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

Не могу разобраться, где в функции vMBPortSerialEnable() требуется переключать пин приема- передачи. Я сделал так:
if (modbusUart->gState == HAL_UART_STATE_READY)
  {
   rs485On();
   prvvUARTTxReadyISR();
   rs485Off();
  }

Передача не работает.

Игорь
Игорь
Ответ на комментарий  Aveal
8 месяцев назад

Спасибо! Правильно работает по второму варианту.

ru3dnw
1 год назад

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

ru3dnw
Ответ на комментарий  Aveal
1 год назад

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

ru3dnw
Ответ на комментарий  Aveal
1 год назад

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

ru3dnw
Ответ на комментарий  Aveal
1 год назад

Еще одна беда возникала постоянно: в файле 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
1 год назад

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

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

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

Рашид
Ответ на комментарий  ru3dnw
1 год назад

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

Alexander
Alexander
1 год назад

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

Алексей
Алексей
1 год назад

Добрый день. Спасибо за урок и библиотеку, но я присоединяюсь к возникшей проблеме: стабильно через 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. Нафиг она такая примитивная нужна?

Константин
Константин
1 год назад

День добрый пытался по вашему проекту портировать Fremodbus под stm32f446re в IAR. Когда подключаю модпул Происходит ошибка Timeout error.

зображення_2022-10-21_122009802.png
Константин
Константин
Ответ на комментарий  Aveal
1 год назад

Чесно не не приходят я думал с таймером проблема. (Таймер вроди работает ) По поводу отправка прийомка не переключается. Но чесно сложно опеределить в чом проблема. Точно могу сказать что в начале проекта когда прокинул все файлы есть 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);
}

проект скомпилился но не заработал должным образом.

зображення_2022-10-24_074401561.png
Константин
Константин
Ответ на комментарий  Aveal
1 год назад

КАК ?

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

Здравствуйте. Взял готовый проект 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();
  }
 }
}

. Не работает. Куда смотреть? буду признателен за помощь.

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

Здравствуйте. Скачал готовый проект. Ничего в нем не менял. Скачал Modbus Poll для проверки. запустил. И только TX тикает, а данные никакие не появляются. (Где должно отображаться 'M' 'o' 'd' 'b' 'u' 's'). Компилировал в CubeIDE. Настройки все выставил в точности как в описании и все перепроверил. Почему не принимает слово "Modbus" из проекта?

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

Да. Данные прилетают но при этом Modbus не показывает

QIP Shot - Screen 067.png
Сергей
Сергей
Ответ на комментарий  Сергей
6 месяцев назад

Да. Данные прилетают но при этом Modbus не показывает

QIP Shot - Screen 066.png
Сергей
Сергей
6 месяцев назад

А для особо неопытных можно пояснить, за что отвечает каждый callback?

QIP Shot - Screen 068.png
Сергей
Сергей
Ответ на комментарий  Aveal
6 месяцев назад

Спасибо, благодаря данной статье разобрался)

Виталий Карпов
6 месяцев назад

Приделал трансмиттер MAX485, выход А8 сделал управляющим. Все работает. Спасибо автору за подробную статью.

Новый рисунок (13).bmp
Виталий Карпов
Ответ на комментарий  Aveal
6 месяцев назад

На осциллографе смотрится вот так. У меня другая необходимость, менять скорость и адрес устройства в процессе работы. HOLDING REGISTER я приделал, данные с компа через ModScan в регистры я записываю. Инициализацию Modbus сделал отдельной процедурой и могу вызывать ее при необходимости (в ардуино я так и сделал, все работает) и тем самым по команде изменять параметры

IMG_20230812_164001.jpg
Эндрю
Эндрю
6 месяцев назад

Что нужно доделать, чтобы еще и записать регистры?

Владимир
Владимир
4 месяцев назад

Огромное спасибо автору, получилось запустить учебный пример с управлением MAX485.

Владимир
Владимир
4 месяцев назад

По шаблону добавил регистры хранения, работает, не получается с Coil, ответ на запрос приходит, но реакция бредовая.

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

Спасибо за внимание, и прошу не пинать меня строго. Я уже на пенсии, сейчас программирование для меня хобби, и на си, начал только изучать азы.
И так:

#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
4 месяцев назад

Конечно, а как это сделать?

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

Не получается зарегистрироваться на яндексе, наверное потому, что живу в Харькове.

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

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

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

Отправил.

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

А скрин из какой программы?
В Modbus Poll, в OPC сервере Овен у меня все одинаково, могу поменять значение,только младшего регистра включенного в работу.

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

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

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

Наверное вопрос дилетанта из какой программы скрин, вас удивил, но разобрался, правда значения отображаются в десятичном формате.
При установке из Modbus Poll значения в Coils[99] "0" или "1" в usRegCoilsBuf[0] устанавливаются значения 171 и 362 соответственно. Можно установить все usRegCoilsBuf в 256, в Modbus Poll "1" будет только в Coils[99]. То есть чтото в программе я сделал не то.

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

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

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

Все получилось.

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

Все типы регистров.

IMG_20231008_202042.jpg
Владимир
Владимир
4 месяцев назад

Здравствуйте, я не смог понять, как modbas связан именно с UART1 и TIM3, что будет, если в программе будут еще подключены таймера и UART.

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

Стыдно приставать с тупыми вопросами, но, увы, это мой уровень знаний.
Я не понимаю, где в,
extern void MT_PORT_SetTimerModule(TIM_HandleTypeDef* timer);
extern void MT_PORT_SetUartModule(UART_HandleTypeDef* uart);
появляются волшебные цифры timer3 и uart1.

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

Спасибо, пойду учить азбуку.

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

Мда, все по притче " смотришь в книгу, видиш ..........".
/* USER CODE BEGIN 2 */
MT_PORT_SetTimerModule(&htim3);
MT_PORT_SetUartModule(&huart1);

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

Еще раз огромное спасибо за Ваше терпение.

Дмитрий
Дмитрий
4 месяцев назад

Спасибо за урок!) Можете подсказать для чего нужны функции pxMBFrameCBByteReceived и pxMBFrameCBTransmitterEmpty? Пытаюсь воспользоваться этой библиотекой модбас при помощи цмсис в Кейл. Компилятор ругается Error: L6218E: Undefined symbol pxMBFrameCBByteReceived (referred from portserial.o).

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

Нет, include не потерялся. Но, я не могу ни в одном файле библиотеки найти тело этих функций.

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

Разобрался, файла mb.c не хватало в библиотеке) Спасибо)

Rkjeka
Rkjeka
2 месяцев назад

Добрый день, сделал все как в уроке(как кажется) в Modbus Poll ошибка Checksum error. Что может быть?

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

Вот так пишет.
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

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

Не могу разобраться, с чем это связано.

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

Уходит вроде, что надо, а у вас есть проект под 4хх?
Есть подозрение, что проблема в конвекторе.

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

Tx - я как понял, это от ПО, а Rx - от МК

DimTech
29 дней назад

Добрый день, Aveal!
Большое спасибо за проделанную работу. Перенес проект на NUCLEO-F401RE. По Вашим примерам подставил функции 01, 02, 03, 04, 05, 06. Я начинающий и это мой первый микроконтроллер. Сейчас смотрю как сделать функции 15 и 16. Пользуюсь Modbus Poll.
Есть некоторые проблемы:

  1. Периодически, примерно через каждые 5 чтений, 2-3 таймаута. Настройки сейчас все те же, что у Вас. Частота ядра 84, но и 72 пробовал, результат тот же.
  2. При записи нулевого значения в любой байт по команде 06 обмен прекращается и по команде 16 тоже запись нулей вызывает прекращение обмена. Буду смотреть. По команде 05 ноль пишется нормально, по команде 15 нет реакции.
  3. Так и не понял почему Prescaler=71
  4. Собственно 15 и 16 пока не реализовал.

Буду благодарен и части ответов/предположений.

DimTech
Ответ на комментарий  Aveal
29 дней назад

Осциллографом или есть другие способы?

DimTech
Ответ на комментарий  Aveal
29 дней назад

Вот они, сырехонькие!
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

DimTech
Ответ на комментарий  DimTech
29 дней назад

Запустил Debug. Не нашел что-то того, что нужно.

DimTech
Ответ на комментарий  Aveal
29 дней назад

А вот это нормально?
while (1)
{
eMBPoll(); // дергаем Modbus
}
Не может он дергаться, когда на линии его не ждут?

DimTech
Ответ на комментарий  DimTech
29 дней назад

В общем, пока информация такая, иногда приходит eMBErrorCode = MB_EIO;
Смотрю дальше.
Какая-то рассинхронизация, что ли, то нарастает количество ошибок, то уменьшается.

DimTech
Ответ на комментарий  DimTech
29 дней назад

В общем, вот здесь
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

DimTech
Ответ на комментарий  DimTech
28 дней назад

Что-то с переходами
STATE_RX_IDLE:
STATE_RX_RCV:
Таймер там этот...

DimTech
Ответ на комментарий  DimTech
28 дней назад

Нужно сказать, что из этих трех состояний:
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
он не сваливается.


DimTech
Ответ на комментарий  DimTech
28 дней назад

Не прирастает до минимального размера (4) usRcvBufferPos

DimTech
Ответ на комментарий  Aveal
26 дней назад

Да. обычный UART-USB конвертер, PL2303 китайский.

DimTech
Ответ на комментарий  Aveal
25 дней назад

Конечно, буду благодарен.
Сейчас отправлю на почту aveal@microtechnics.ru

DimTech
Ответ на комментарий  Aveal
25 дней назад

Еще со скоростью не очень. Больше 19200 не отвечает. Пока не анализировал, но пробовал разные варианты скорости, настройки sysclk и TIM.

DimTech
Ответ на комментарий  DimTech
22 дней назад

Спасибо автору за предоставленный материал и поиск неисправности. В итоге дело оказалось в 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

DimTech
Ответ на комментарий  Aveal
22 дней назад

Скорость поддерживает разную. А вот с четностью и нечетностью пока не работает.

Раиль
9 дней назад

День добрый. Залил библиотеку под STM32F103VET6. Пины такие же как и таймер.
Пользуюсь средой IAR. Иногда и кубиком балуюсь.
Сделал всё как в примере. Но постоянно выдаёт ошибку 02.
На кристалле F103C8T6 в кубике, то же самое.
Хотелось бы понять, почему(.

la
Раиль
Ответ на комментарий  Aveal
9 дней назад

А как понять куда мы записали слово Modbus?

Раиль
Ответ на комментарий  Aveal
9 дней назад

Если даже взять полностью ваш пример, получается то же самое.