Top.Mail.Ru

Modbus RTU Master. Библиотека для микроконтроллеров STM32.

Реализовав в предыдущей статье Modbus RTU Slave на STM32, я, естественно, не имею никакого морального права обойти вниманием Master'а. Тем более что таких примеров по какой-то причине действительно очень мало. Так что герой сегодняшней статьи – Modbus RTU Master, все так же на контроллере STM32. Кроме того, вот ссылка на описание протокола Modbus в целом, своего рода теоретическая часть, которую теперь мы продолжаем практическими действиями. Приступаем!

И сразу же выявляется неприятный факт, что FreeModbus не имеет необходимого функционала. Тем не менее была обнаружена альтернативная/доработанная библиотека. Ее адаптацией и займемся.

Двигаемся по традиционной, максимально структурированной схеме. Соответственно, начнем с создания проекта и активации необходимой периферии. Все в точности как в упомянутой статье, поэтому много внимания уделять деталям не буду. Тактирование:

STM32 Modbus RTU Master.

Приемо-передатчик USART1:

Настройки USART.

И таймер, также генерирующий прерывания по переполнению каждые 50 мкс:

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

Для обоих упомянутых периферийных модулей активируем прерывания. В результате получаем в распоряжение проект для STM32CubeIDE, в котором опять же идентичным образом добавляем папку Modules:

Пример проекта для STM32.

Поскольку это проект для Modbus Master, то я не вижу смысла тащить в него функционал для Slave, поэтому была проведена работа по уничтожению «лишнего». Таким образом, вот мой вариант библиотеки – ссылка. По итогу мы будем иметь в распоряжении проект для Slave из первой части, в котором нет ничего лишнего, и точно так же отдельный и независимый проект для Master’а, который сегодня и создадим.

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

STM32CubeIDE Source Folder.

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

Итоговая структура выглядит так:

Modbus STM32CubeIDE.

Процесс портирования FreeModbus нам уже знаком, здесь добавляется только постфикс в названиях последних трех файлов:

  • mt_port.c
  • mt_port.h
  • port.h
  • portevent_m.c
  • portserial_m.c
  • porttimer_m.c

В добавленных mt_port.c и mt_port.h обеспечиваем обработку атомарных операций – EnterCriticalSection() и ExitCriticalSection(). А также установку параметров, отвечающих за используемую периферию:

/**
  ******************************************************************************
  * @file           : mt_port.c
  * @brief          : Additional porting data
  * @author         : MicroTechnics (microtechnics.ru)
  ******************************************************************************
  */



/* Includes ------------------------------------------------------------------*/

#include "mt_port.h"



/* Declarations and definitions ----------------------------------------------*/

static uint32_t lockCounter = 0;

UART_HandleTypeDef* modbusUart;
TIM_HandleTypeDef* modbusTimer;



/* Functions -----------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
void EnterCriticalSection()
{
  __disable_irq();
  lockCounter++;
}



/*----------------------------------------------------------------------------*/
void ExitCriticalSection()
{
  lockCounter--;

  if (lockCounter == 0)
  {
    __enable_irq();
  }
}



/*----------------------------------------------------------------------------*/
void MT_PORT_SetTimerModule(TIM_HandleTypeDef* timer)
{
  modbusTimer = timer;
}



/*----------------------------------------------------------------------------*/
void MT_PORT_SetUartModule(UART_HandleTypeDef* uart)
{
  modbusUart = uart;
}



/*----------------------------------------------------------------------------*/

При помощи функций MT_PORT_SetTimerModule() и void MT_PORT_SetUartModule() мы сможем задать те конкретные аппаратные модули, которые задействованы в проекте. В действии увидим ближе к концу статьи. Пока же движемся дальше по вышеописанному списку файлов. В port.h добавляем вызов созданных в mt_port.c функций:

extern void EnterCriticalSection();
extern void ExitCriticalSection();

#define ENTER_CRITICAL_SECTION()    EnterCriticalSection()
#define EXIT_CRITICAL_SECTION()     ExitCriticalSection()

На этом с первой половиной закончено.

В исходной версии библиотеки все было завязано на операционную систему, нам же это не нужно, поэтому portevent_m.c принял вид:

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mb_m.h"
#include "mbport.h"
#include "port.h"



/* ----------------------- Defines ------------------------------------------*/

/* ----------------------- Variables ----------------------------------------*/

static eMBMasterEventType eQueuedEvent;
static BOOL xEventInQueue;



/* ----------------------- Start implementation -----------------------------*/

/*----------------------------------------------------------------------------*/
BOOL xMBMasterPortEventInit(void)
{
  xEventInQueue = FALSE;
  return TRUE;
}



/*----------------------------------------------------------------------------*/
BOOL xMBMasterPortEventPost(eMBMasterEventType eEvent)
{
  xEventInQueue = TRUE;
  eQueuedEvent = eEvent;
  return TRUE;
}



/*----------------------------------------------------------------------------*/
BOOL xMBMasterPortEventGet(eMBMasterEventType * eEvent)
{
  BOOL xEventHappened = FALSE;

  if(xEventInQueue)
  {
    *eEvent = eQueuedEvent;
    xEventInQueue = FALSE;
    xEventHappened = TRUE;
  }

  return xEventHappened;
}



/*----------------------------------------------------------------------------*/
/**
 * This function is initialize the OS resource for modbus master.
 * Note:The resource is define by OS.If you not use OS this function can be empty.
 *
 */
void vMBMasterOsResInit(void)
{
}



/*----------------------------------------------------------------------------*/
/**
 * This function is take Mobus Master running resource.
 * Note:The resource is define by Operating System.If you not use OS this function can be just return TRUE.
 *
 * @param lTimeOut the waiting time.
 *
 * @return resource taked result
 */
BOOL xMBMasterRunResTake(LONG lTimeOut)
{
  return TRUE;
}



/*----------------------------------------------------------------------------*/
/**
 * This function is release Mobus Master running resource.
 * Note:The resource is define by Operating System.If you not use OS this function can be empty.
 *
 */
void vMBMasterRunResRelease(void)
{
}



/*----------------------------------------------------------------------------*/
/**
 * This is modbus master respond timeout error process callback function.
 * @note There functions will block modbus master poll while execute OS waiting.
 * So,for real-time of system.Do not execute too much waiting process.
 *
 * @param ucDestAddress destination salve address
 * @param pucPDUData PDU buffer data
 * @param ucPDULength PDU buffer length
 *
 */
void vMBMasterErrorCBRespondTimeout(UCHAR ucDestAddress, const UCHAR* pucPDUData,
                                    USHORT ucPDULength)
{
  xMBMasterPortEventPost(EV_MASTER_ERROR_RESPOND_TIMEOUT);
}



/*----------------------------------------------------------------------------*/
void vMBMasterErrorCBReceiveData(UCHAR ucDestAddress, const UCHAR* pucPDUData,
                                 USHORT ucPDULength)
{
  xMBMasterPortEventPost(EV_MASTER_ERROR_RECEIVE_DATA);
}



/*----------------------------------------------------------------------------*/
void vMBMasterErrorCBExecuteFunction(UCHAR ucDestAddress, const UCHAR* pucPDUData,
                                     USHORT ucPDULength)
{
  xMBMasterPortEventPost(EV_MASTER_ERROR_EXECUTE_FUNCTION);
}



/*----------------------------------------------------------------------------*/
void vMBMasterCBRequestScuuess(void)
{
  xMBMasterPortEventPost(EV_MASTER_PROCESS_SUCCESS);
}



/*----------------------------------------------------------------------------*/
eMBMasterReqErrCode eMBMasterWaitRequestFinish(void)
{
  eMBMasterReqErrCode eErrStatus = MB_MRE_NO_ERR;

  switch (eQueuedEvent)
  {
    case EV_MASTER_PROCESS_SUCCESS:
      break;

    case EV_MASTER_ERROR_RESPOND_TIMEOUT:
      eErrStatus = MB_MRE_TIMEDOUT;
      break;

    case EV_MASTER_ERROR_RECEIVE_DATA:
      eErrStatus = MB_MRE_REV_DATA;
      break;

    case EV_MASTER_ERROR_EXECUTE_FUNCTION:
      eErrStatus = MB_MRE_EXE_FUN;
      break;

    default:
      break;
  }

  return eErrStatus;
}



/*----------------------------------------------------------------------------*/

На очереди portserial_m.c, как следует из названия, здесь обеспечивается корректная работа с USART. Все тесно переплетается с реализацией Slave, но повторение – мать учения, поэтому пройдемся еще разок. Функция vMBMasterPortSerialEnable():

/* ----------------------- Start implementation -----------------------------*/

/*----------------------------------------------------------------------------*/
void vMBMasterPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
  if (xRxEnable == FALSE)
  {
    HAL_UART_AbortReceive_IT(modbusUart);
  }
  else
  {
    HAL_UART_Receive_IT(modbusUart, &rxByte, 1);
  }

  if (xTxEnable == FALSE)
  {
    HAL_UART_AbortTransmit_IT(modbusUart);
  }
  else
  {
    if (modbusUart->gState == HAL_UART_STATE_READY)
    {
      prvvUARTTxReadyISR();
    }
  }
}



/* --------------------------------------------------------------------------*/

В зависимости от аргументов функции производим включение, либо отключение приемника, либо передатчика. xMBMasterPortSerialInit() оставляем пустой, инициализация пусть протекает силами STM32CubeMx, иначе зачем он нужен:

/* --------------------------------------------------------------------------*/
BOOL xMBMasterPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)
{
  return TRUE;
}



/* --------------------------------------------------------------------------*/

Отправка и прием информации в виде байта данных:

/* --------------------------------------------------------------------------*/
BOOL xMBMasterPortSerialPutByte(CHAR ucByte)
{
  txByte = ucByte;
  HAL_UART_Transmit_IT(modbusUart, &txByte, 1);
  return TRUE;
}



/* --------------------------------------------------------------------------*/
BOOL xMBMasterPortSerialGetByte( CHAR * pucByte )
{
  *pucByte = rxByte;
  HAL_UART_Receive_IT(modbusUart, &rxByte, 1);
  return TRUE;
}



/* --------------------------------------------------------------------------*/

И обработка callback-ов по окончанию передачи и приема соответственно:

/* --------------------------------------------------------------------------*/
static void prvvUARTTxReadyISR(void)
{
  pxMBMasterFrameCBTransmitterEmpty();
}



/* --------------------------------------------------------------------------*/
static void prvvUARTRxISR(void)
{
  pxMBMasterFrameCBByteReceived();
}



/* --------------------------------------------------------------------------*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == modbusUart->Instance)
  {
    prvvUARTTxReadyISR();
  }
}



/* --------------------------------------------------------------------------*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == modbusUart->Instance)
  {
    prvvUARTRxISR();
  }
}



/* --------------------------------------------------------------------------*/

При использовании UART - RS-485 преобразователя не забываем переключать режим его работы с приемника на передатчик и обратно в случае необходимости.

Приближаемся к закономерному итогу в виде работающего проекта, файл porttimer_m.c. Инициализацию также не будем дублировать, это просто не имеет ни малейшего смысла, пусть этим занимается CubeMx:

/* ----------------------- Start implementation -----------------------------*/

/*----------------------------------------------------------------------------*/
BOOL xMBMasterPortTimersInit(USHORT usTim1Timerout50us)
{
  timerPeriod = usTim1Timerout50us;
  return TRUE;
}



/* --------------------------------------------------------------------------*/

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

/* --------------------------------------------------------------------------*/
inline void vMBMasterPortTimersT35Enable()
{
  vMBMasterSetCurTimerMode(MB_TMODE_T35);
  timerCounter = 0;
  HAL_TIM_Base_Start_IT(modbusTimer);
}



/* --------------------------------------------------------------------------*/
void vMBMasterPortTimersConvertDelayEnable()
{
  vMBMasterSetCurTimerMode(MB_TMODE_CONVERT_DELAY);
}



/* --------------------------------------------------------------------------*/
void vMBMasterPortTimersRespondTimeoutEnable()
{
  vMBMasterSetCurTimerMode(MB_TMODE_RESPOND_TIMEOUT);
}



/* --------------------------------------------------------------------------*/
inline void vMBMasterPortTimersDisable()
{
  HAL_TIM_Base_Stop_IT(modbusTimer);
}



/* --------------------------------------------------------------------------*/
static void prvvTIMERExpiredISR(void)
{
    (void)pxMBMasterPortCBTimerExpired();
}



/* --------------------------------------------------------------------------*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == modbusTimer->Instance)
  {
    timerCounter++;

    if (timerCounter == timerPeriod)
    {
      prvvTIMERExpiredISR();
    }
  }
}



/* --------------------------------------------------------------------------*/

Конфигурация библиотеки в файле modbus/include/mbconfig.h. В данном случае у нас только один вариант – Master RTU. Связано это с тем, что Slave по понятным причинам я искоренил, а режимы ASCII и TCP в библиотеке отсутствовали как вид. Не страшно, нам и нужен был RTU, так что дефайним:

/*! \brief If Modbus Master RTU support is enabled. */
#define MB_MASTER_RTU_ENABLED                   (1)

На этом портирование и адаптация библиотеки завершены, пишем демо-пример, для чего перемещаемся в main.c. Необходимо реализовать callback-функции:

/* USER CODE BEGIN 4 */
/*----------------------------------------------------------------------------*/
eMBErrorCode eMBMasterRegInputCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs)
{
  eMBErrorCode eStatus = MB_ENOERR;
  return eStatus;
}



/*----------------------------------------------------------------------------*/
eMBErrorCode eMBMasterRegHoldingCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode)
{
  eMBErrorCode eStatus = MB_ENOERR;
  return eStatus;
}



/*----------------------------------------------------------------------------*/
eMBErrorCode eMBMasterRegCoilsCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode)
{
  eMBErrorCode eStatus = MB_ENOERR;
  return eStatus;
}



/*----------------------------------------------------------------------------*/
eMBErrorCode eMBMasterRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
  eMBErrorCode eStatus = MB_ENOERR;
  return eStatus;
}



/*----------------------------------------------------------------------------*/
/* USER CODE END 4 */

Они будут вызваны при наступлении соответствующих событий. Например, отправляем Slave запрос на чтение регистров (с помощью eMBMasterReqReadInputRegister()). При получении корректного ответа попадем в eMBMasterRegInputCB(), в которой можно добавить код для обработки этих полученных данных. Это уже зависит от конкретной задачи. В данном случае я их оставил пустыми за ненадобностью.

А в качестве теста буду отправлять запрос на запись регистра. Этой цели служит функция:

eMBMasterReqErrCode eMBMasterReqWriteHoldingRegister(UCHAR ucSndAddr, USHORT usRegAddr, USHORT usRegData, LONG lTimeOut)

Аргументы – адрес Slave, адрес регистра, записываемое значение, величина таймаута. Но прежде производим действия по инициализации:

/* USER CODE BEGIN 2 */
MT_PORT_SetTimerModule(&htim3);
MT_PORT_SetUartModule(&huart1);

eMBErrorCode eStatus;
eStatus = eMBMasterInit(MB_RTU, 0, 19200, MB_PAR_NONE);
eStatus = eMBMasterEnable();

if (eStatus != MB_ENOERR)
{
// Error handling
}

/* USER CODE END 2 */

Задаем периферийные модули и инициализируем библиотеку. Снова, несмотря на то, что инициализацию производит CubeMx, в eMBMasterInit() надо передать актуальные параметры. Это требуется для функционирования FreeModbus. И в основном цикле выполняем следующее:

/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
	/* USER CODE END WHILE */

	/* USER CODE BEGIN 3 */
	eMBMasterPoll();

	eMBMasterReqWriteHoldingRegister(0x0A, 0x1234, 0x5678, 1000);
	HAL_Delay(1000);
}
/* USER CODE END 3 */

Собственно, раз в секунду производим отправку команды на запись в регистр 0x1234 значения 0x5678. Для проверки подключим USART1 к ПК, я взял самый что ни на есть обычный USB-UART на CP2102:

И в не менее обычном терминале анализируем данные в линии:

Команда приходит, процесс идет, проект работает 👍

Полный код измененных файлов:

/**
  ******************************************************************************
  * @file           : mt_port.c
  * @brief          : Additional porting data
  * @author         : MicroTechnics (microtechnics.ru)
  ******************************************************************************
  */



/* Includes ------------------------------------------------------------------*/

#include "mt_port.h"



/* Declarations and definitions ----------------------------------------------*/

static uint32_t lockCounter = 0;

UART_HandleTypeDef* modbusUart;
TIM_HandleTypeDef* modbusTimer;



/* Functions -----------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
void EnterCriticalSection()
{
  __disable_irq();
  lockCounter++;
}



/*----------------------------------------------------------------------------*/
void ExitCriticalSection()
{
  lockCounter--;

  if (lockCounter == 0)
  {
    __enable_irq();
  }
}



/*----------------------------------------------------------------------------*/
void MT_PORT_SetTimerModule(TIM_HandleTypeDef* timer)
{
  modbusTimer = timer;
}



/*----------------------------------------------------------------------------*/
void MT_PORT_SetUartModule(UART_HandleTypeDef* uart)
{
  modbusUart = uart;
}



/*----------------------------------------------------------------------------*/
/**
  ******************************************************************************
  * @file           : mt_port.h
  * @brief          : Additional porting data
  * @author         : MicroTechnics (microtechnics.ru)
  ******************************************************************************
  */

#ifndef MT_PORT_H
#define MT_PORT_H



/* Includes ------------------------------------------------------------------*/

#include "stm32f1xx_hal.h"



/* Declarations and definitions ----------------------------------------------*/




/* Functions -----------------------------------------------------------------*/

extern void MT_PORT_SetTimerModule(TIM_HandleTypeDef* timer);
extern void MT_PORT_SetUartModule(UART_HandleTypeDef* uart);



#endif // #ifndef MT_PORT_H
/*
 * FreeModbus Libary: BARE Port
 * Copyright (C) 2013 Armink <armink.ztl@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id: port.h ,v 1.60 2013/08/13 15:07:05 Armink add Master Functions $
 */

#ifndef _PORT_H
#define _PORT_H

#include "mbconfig.h"
#include "mt_port.h"

#include <assert.h>
#include <inttypes.h>

#define INLINE
#define PR_BEGIN_EXTERN_C           extern "C" {
#define PR_END_EXTERN_C             }

extern void EnterCriticalSection();
extern void ExitCriticalSection();

#define ENTER_CRITICAL_SECTION()    EnterCriticalSection()
#define EXIT_CRITICAL_SECTION()     ExitCriticalSection()

typedef uint8_t BOOL;

typedef unsigned char UCHAR;
typedef char    CHAR;

typedef uint16_t USHORT;
typedef int16_t SHORT;

typedef uint32_t ULONG;
typedef int32_t LONG;

#ifndef TRUE
#define TRUE            1
#endif

#ifndef FALSE
#define FALSE           0
#endif

#endif
/*
 * FreeModbus Libary: RT-Thread Port
 * Copyright (C) 2013 Armink <armink.ztl@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id: portevent_m.c v 1.60 2013/08/13 15:07:05 Armink add Master Functions$
 */

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mb_m.h"
#include "mbport.h"
#include "port.h"



/* ----------------------- Defines ------------------------------------------*/

/* ----------------------- Variables ----------------------------------------*/

static eMBMasterEventType eQueuedEvent;
static BOOL xEventInQueue;



/* ----------------------- Start implementation -----------------------------*/

/*----------------------------------------------------------------------------*/
BOOL xMBMasterPortEventInit(void)
{
  xEventInQueue = FALSE;
  return TRUE;
}



/*----------------------------------------------------------------------------*/
BOOL xMBMasterPortEventPost(eMBMasterEventType eEvent)
{
  xEventInQueue = TRUE;
  eQueuedEvent = eEvent;
  return TRUE;
}



/*----------------------------------------------------------------------------*/
BOOL xMBMasterPortEventGet(eMBMasterEventType * eEvent)
{
  BOOL xEventHappened = FALSE;

  if(xEventInQueue)
  {
    *eEvent = eQueuedEvent;
    xEventInQueue = FALSE;
    xEventHappened = TRUE;
  }

  return xEventHappened;
}



/*----------------------------------------------------------------------------*/
/**
 * This function is initialize the OS resource for modbus master.
 * Note:The resource is define by OS.If you not use OS this function can be empty.
 *
 */
void vMBMasterOsResInit(void)
{
}



/*----------------------------------------------------------------------------*/
/**
 * This function is take Mobus Master running resource.
 * Note:The resource is define by Operating System.If you not use OS this function can be just return TRUE.
 *
 * @param lTimeOut the waiting time.
 *
 * @return resource taked result
 */
BOOL xMBMasterRunResTake(LONG lTimeOut)
{
  return TRUE;
}



/*----------------------------------------------------------------------------*/
/**
 * This function is release Mobus Master running resource.
 * Note:The resource is define by Operating System.If you not use OS this function can be empty.
 *
 */
void vMBMasterRunResRelease(void)
{
}



/*----------------------------------------------------------------------------*/
/**
 * This is modbus master respond timeout error process callback function.
 * @note There functions will block modbus master poll while execute OS waiting.
 * So,for real-time of system.Do not execute too much waiting process.
 *
 * @param ucDestAddress destination salve address
 * @param pucPDUData PDU buffer data
 * @param ucPDULength PDU buffer length
 *
 */
void vMBMasterErrorCBRespondTimeout(UCHAR ucDestAddress, const UCHAR* pucPDUData,
                                    USHORT ucPDULength)
{
  xMBMasterPortEventPost(EV_MASTER_ERROR_RESPOND_TIMEOUT);
}



/*----------------------------------------------------------------------------*/
void vMBMasterErrorCBReceiveData(UCHAR ucDestAddress, const UCHAR* pucPDUData,
                                 USHORT ucPDULength)
{
  xMBMasterPortEventPost(EV_MASTER_ERROR_RECEIVE_DATA);
}



/*----------------------------------------------------------------------------*/
void vMBMasterErrorCBExecuteFunction(UCHAR ucDestAddress, const UCHAR* pucPDUData,
                                     USHORT ucPDULength)
{
  xMBMasterPortEventPost(EV_MASTER_ERROR_EXECUTE_FUNCTION);
}



/*----------------------------------------------------------------------------*/
void vMBMasterCBRequestScuuess(void)
{
  xMBMasterPortEventPost(EV_MASTER_PROCESS_SUCCESS);
}



/*----------------------------------------------------------------------------*/
eMBMasterReqErrCode eMBMasterWaitRequestFinish(void)
{
  eMBMasterReqErrCode eErrStatus = MB_MRE_NO_ERR;

  switch (eQueuedEvent)
  {
    case EV_MASTER_PROCESS_SUCCESS:
      break;

    case EV_MASTER_ERROR_RESPOND_TIMEOUT:
      eErrStatus = MB_MRE_TIMEDOUT;
      break;

    case EV_MASTER_ERROR_RECEIVE_DATA:
      eErrStatus = MB_MRE_REV_DATA;
      break;

    case EV_MASTER_ERROR_EXECUTE_FUNCTION:
      eErrStatus = MB_MRE_EXE_FUN;
      break;

    default:
      break;
  }

  return eErrStatus;
}



/*----------------------------------------------------------------------------*/
/*
 * FreeModbus Libary: BARE Port
 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */

#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "stm32f1xx_hal.h"


/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR(void);
static void prvvUARTRxISR(void);



/* ----------------------- Variables ----------------------------------------*/
extern UART_HandleTypeDef* modbusUart;

static uint8_t txByte = 0x00;
static uint8_t rxByte = 0x00;



/* ----------------------- Start implementation -----------------------------*/

/*----------------------------------------------------------------------------*/
void vMBMasterPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
  if (xRxEnable == FALSE)
  {
    HAL_UART_AbortReceive_IT(modbusUart);
  }
  else
  {
    HAL_UART_Receive_IT(modbusUart, &rxByte, 1);
  }

  if (xTxEnable == FALSE)
  {
    HAL_UART_AbortTransmit_IT(modbusUart);
  }
  else
  {
    if (modbusUart->gState == HAL_UART_STATE_READY)
    {
      prvvUARTTxReadyISR();
    }
  }
}



/* --------------------------------------------------------------------------*/
BOOL xMBMasterPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)
{
  return TRUE;
}



/* --------------------------------------------------------------------------*/
BOOL xMBMasterPortSerialPutByte(CHAR ucByte)
{
  txByte = ucByte;
  HAL_UART_Transmit_IT(modbusUart, &txByte, 1);
  return TRUE;
}



/* --------------------------------------------------------------------------*/
BOOL xMBMasterPortSerialGetByte( CHAR * pucByte )
{
  *pucByte = rxByte;
  HAL_UART_Receive_IT(modbusUart, &rxByte, 1);
  return TRUE;
}



/* --------------------------------------------------------------------------*/
static void prvvUARTTxReadyISR(void)
{
  pxMBMasterFrameCBTransmitterEmpty();
}



/* --------------------------------------------------------------------------*/
static void prvvUARTRxISR(void)
{
  pxMBMasterFrameCBByteReceived();
}



/* --------------------------------------------------------------------------*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == modbusUart->Instance)
  {
    prvvUARTTxReadyISR();
  }
}



/* --------------------------------------------------------------------------*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == modbusUart->Instance)
  {
    prvvUARTRxISR();
  }
}



/* --------------------------------------------------------------------------*/
/*
 * FreeModbus Libary: BARE Port
 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"



/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "mb_m.h"
#include "stm32f1xx_hal.h"



/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR(void);



/* ----------------------- Variables ----------------------------------------*/
extern TIM_HandleTypeDef* modbusTimer;

uint16_t timerPeriod = 0;
uint16_t timerCounter = 0;



/* ----------------------- Start implementation -----------------------------*/

/*----------------------------------------------------------------------------*/
BOOL xMBMasterPortTimersInit(USHORT usTim1Timerout50us)
{
  timerPeriod = usTim1Timerout50us;
  return TRUE;
}



/* --------------------------------------------------------------------------*/
inline void vMBMasterPortTimersT35Enable()
{
  vMBMasterSetCurTimerMode(MB_TMODE_T35);
  timerCounter = 0;
  HAL_TIM_Base_Start_IT(modbusTimer);
}



/* --------------------------------------------------------------------------*/
void vMBMasterPortTimersConvertDelayEnable()
{
  vMBMasterSetCurTimerMode(MB_TMODE_CONVERT_DELAY);
}



/* --------------------------------------------------------------------------*/
void vMBMasterPortTimersRespondTimeoutEnable()
{
  vMBMasterSetCurTimerMode(MB_TMODE_RESPOND_TIMEOUT);
}



/* --------------------------------------------------------------------------*/
inline void vMBMasterPortTimersDisable()
{
  HAL_TIM_Base_Stop_IT(modbusTimer);
}



/* --------------------------------------------------------------------------*/
static void prvvTIMERExpiredISR(void)
{
    ( void )pxMBMasterPortCBTimerExpired();
}



/* --------------------------------------------------------------------------*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == modbusTimer->Instance)
  {
    timerCounter++;

    if (timerCounter == timerPeriod)
    {
      prvvTIMERExpiredISR();
    }
  }
}



/* --------------------------------------------------------------------------*/
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2022 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "mb.h"
#include "mb_m.h"
#include "mbport.h"
#include "mt_port.h"

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
TIM_HandleTypeDef htim3;

UART_HandleTypeDef huart1;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM3_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM3_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  MT_PORT_SetTimerModule(&htim3);
  MT_PORT_SetUartModule(&huart1);

  eMBErrorCode eStatus;
  eStatus = eMBMasterInit(MB_RTU, 0, 19200, MB_PAR_NONE);
  eStatus = eMBMasterEnable();

  if (eStatus != MB_ENOERR)
  {
    // Error handling
  }

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    eMBMasterPoll();

    eMBMasterReqWriteHoldingRegister(0x0A, 0x1234, 0x5678, 1000);
    HAL_Delay(1000);
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief TIM3 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM3_Init(void)
{

  /* USER CODE BEGIN TIM3_Init 0 */

  /* USER CODE END TIM3_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM3_Init 1 */

  /* USER CODE END TIM3_Init 1 */
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 71;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 50;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM3_Init 2 */

  /* USER CODE END TIM3_Init 2 */

}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 19200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

}

/* USER CODE BEGIN 4 */
/*----------------------------------------------------------------------------*/
eMBErrorCode eMBMasterRegInputCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs)
{
  eMBErrorCode eStatus = MB_ENOERR;
  return eStatus;
}



/*----------------------------------------------------------------------------*/
eMBErrorCode eMBMasterRegHoldingCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode)
{
  eMBErrorCode eStatus = MB_ENOERR;
  return eStatus;
}



/*----------------------------------------------------------------------------*/
eMBErrorCode eMBMasterRegCoilsCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode)
{
  eMBErrorCode eStatus = MB_ENOERR;
  return eStatus;
}



/*----------------------------------------------------------------------------*/
eMBErrorCode eMBMasterRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
  eMBErrorCode eStatus = MB_ENOERR;
  return eStatus;
}



/*----------------------------------------------------------------------------*/
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

Ссылка на проект – MT_Modbus_RTU_Master_Project.

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

75 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Павел
Павел
2 лет назад

С учётом того что на чтение и запись используются разные функции где лучше добавить код чтобы выставлять 1 на отправку данных и 0 на прием для использования микросхемы sp3485 или аналогов.

Александр
Александр
2 лет назад

Думаю, что многим, как и мне, будет очень интересно портирование данной библиотеки под STM без выпиливания операционки. У меня много проектов построенных на FreeRTOS и вот сейчас потребовалось добавить ModbusRTU Master в один из проектов, а ранее с Modbus никогда не работал. Время реализации очень ограничено, вникание в вопрос может занять много времени. Не могли бы вы рассмотреть вышеописанный вопрос? Думаю у вас это не займет много времени, а нам станет намного легче. Спасибо.

tonyk
tonyk
2 лет назад

Вот толковая статья про реализацию Модбас на STM32 с использованием DMA. Если в вашем МК стоит кастрированный UART без RTO, то замените его на IDLE.

https://habr.com/ru/post/522960/

VanThanh
VanThanh
2 лет назад

Thank you very much! very helpful post.

Alex
Alex
2 лет назад

Привет, все никак не могу сообразить, а как все-таки прочитать регистры со слэйва?
Запись проходит нормально, но при чтении, например, eMBMasterReqReadInputRegister колбэки не вызываются, и непонятно, где искать буфер с ответом слэйва... Либо я совсем запутался, либо одно из двух)

Alex
Alex
Ответ на комментарий  Aveal
2 лет назад

eMBMasterFuncReadInputRegister нигде не вызывается)

Alex
Alex
Ответ на комментарий  Aveal
2 лет назад

В общем...
Дело дошло до eMBMasterRTUReceive, в котором не выполняется условие
usMBCRC16( ( UCHAR * ) ucMasterRTURcvBuf, usMasterRcvBufferPos ) == 0 )
Отдельный вопрос - зачем нужно, чтобы CRC было равно нулю, иначе функция по условию вываливается в ошибку MB_EIO (ошибка ввода-вывода)...
Но даже с игнорированием этого условия входа в eMBMasterFuncReadInputRegister() нет.
Может ли дело быть в том, что используется не реальное железо, а схема Proteus? Может, из-за симуляции какие-то проблемы с задержками/паузами...
Если у кого-нибудь есть желание проверить эту проблему на железе, был бы очень благодарен.

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

Спасибо Alex за подсказку, от которой я начал свой путь в ковырянии этой библиотеки.
К сожалению проблема не в Proteus, а в том, что библиотека мастера банально не дописана в плане функционала приема сообщений.
Во первых отсутствует файл mbrtu_m.h, почему мне не понятно.
Далее в mbrtu_m.c функция eMBMasterRTUReceive вычитывает не корректно сообщение из буффера. Конкретно в моем случае в нулевом байте буффера всегда висел ноль, он учитывался при расчете CRC и она никогда не сходилась. Исправил следующим образом:
  /* Length and CRC check */ /*Ввел смещение 1, потому что так получается, если съедет еще, то надо исправлять*/
  if( ( usMasterRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
    && ( usMBCRC16( ( UCHAR * ) ucMasterRTURcvBuf+1, usMasterRcvBufferPos-1 ) == 0 ) )
  {
   
Далее мы начинаем наконец то в функции eMBMasterPoll попадать в секцию EV_MASTER_FRAME_RECEIVED
Но тут код от слейва преподносит нам злую шутку, потому что в условии приема идет проверка ID. Исправляется просто, комментируем часть условия:

      eStatus = peMBMasterFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
      /* Check if the frame is for us. If not ,send an error process event. */
      if ( ( eStatus == MB_ENOERR ) ) //&& ( ucRcvAddress == ucMBMasterGetDestAddress() ) )

Теперь вы будете попадать в секцию EV_MASTER_EXECUTE, которая начнет вызывать нужную функцию обработки сообщения.
Но там тоже не все так просто. Откровенно говоря головой я понимаю что там написано +- правильная вещь, но она не работает. Видимо связано все также с бездумным заимствованием кода от Слейв устройства. Поскольку мне надо было решить наконец задачу приема, я написал все достаточно жестко.

eMBMasterFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
{
  UCHAR     *ucMBFrame;
  USHORT     usRegAddress;
  USHORT     usRegCount;

  eMBException  eStatus = MB_EX_NONE;
  eMBErrorCode  eRegStatus;

  /* If this request is broadcast, and it's read mode. This request don't need execute. */
  if ( xMBMasterRequestIsBroadcast() )
  {
    eStatus = MB_EX_NONE;
  }
  else if( *usLen >= MB_PDU_SIZE_MIN + MB_PDU_FUNC_READ_SIZE_MIN )
  {

   usRegAddress = ( USHORT )( pucFrame[0]); //Адрес устройства передатчика
   usRegCount = ( USHORT )( pucFrame[2]>>1);  //Кол-во регистров

   eRegStatus = eMBMasterRegHoldingCB( &pucFrame[0], usRegAddress, usRegCount, MB_REG_READ );
  }
  else
  {
    /* Can't be a valid request because the length is incorrect. */
    eStatus = MB_EX_ILLEGAL_DATA_VALUE;
  }
  return eStatus;
}

Наконец это отправит нас в долгожданный callback: eMBMasterRegHoldingCB
Где я уже работаю над разбором своих сообщений. Благо мне не нужен универсальный мастер.
Кстати метки MB_REG_READ / WRITE работают не так как ожидается. Может это я чего сломал. В любом случае я отдаю все сообщение в callback и дальше работаю над его расшифровкой как мне надо.

В любом случае автору респект за статью, благодаря ей у меня за пол часа завелся Master, который умеет отправлять сообщения в шину. А как добиться минимального функционала приема я постарался описать.

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

Подскажите пожалст, а куда данные то прочитанные складываются

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

не погли бы поделиться своим вариантом Modbus?) у меня все никак не получается починить считывание

Александр
2 лет назад

Добрый день!
Подскажите, пожалуйста, можно ли как-то реализовать на одном МК и слейв и мастера? На разных UART

Тельман
Тельман
1 год назад

где буфер куда данные приходят при чтении

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

но callback вызывается в этой функции:
eMBException eMBMasterFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
{
UCHAR   *ucMBFrame;
 USHORT   usRegAddress;
 USHORT   usRegCount;

 eMBException eStatus = MB_EX_NONE;
 eMBErrorCode eRegStatus;

 /* If this request is broadcast, and it's read mode. This request don't need execute. */
 if ( xMBMasterRequestIsBroadcast() )
 {
  eStatus = MB_EX_NONE;
 }
 else if( *usLen >= MB_PDU_SIZE_MIN + MB_PDU_FUNC_READ_SIZE_MIN )
 {

  usRegAddress = ( USHORT )( pucFrame[0]); //Адрес устройства передатчика
  usRegCount = ( USHORT )( pucFrame[2]>>1); //Кол-во регистров

  eRegStatus = eMBMasterRegHoldingCB( &pucFrame[0], usRegAddress, usRegCount, MB_REG_READ );
 }
 else
 {
  /* Can't be a valid request because the length is incorrect. */
  eStatus = MB_EX_ILLEGAL_DATA_VALUE;
 }
 return eStatus;
}

в ней в callback передается pucFrame, который сам передается в функцию eMBMasterFuncReadHoldingRegister, а эта функция вызывается так:
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0
  {MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBMasterFuncWriteMultipleHoldingRegister},
#endif

и я не вижу где здесь передается буфер для принятия данных. Подскажите как называется этот буфер

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

не могли бы вы привести пример как мне считать данные, ucMBFrame это же локальнный указатель

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

Спасибо) а где лучше переключать режим работы RS -485, в каком месте кода или в какой функции?

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

Спасибо)

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

у вас нет полностью рабочей версии modbus?

Exemption
Ответ на комментарий  Тельман
1 год назад

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

Exemption
1 год назад

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

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

HAL_UART_Receive_IT() вызывается.
Осциллограф на ножке RX контроллера показывает постоянную логическую 1, при отключенном источнике сигналов (в моем случае max485). Поэтому то данных и нет(
Но я не понимаю, почему RX в единице. Это неправильно сконфигурирован порт? или уарт не опускает ножку на прием ?

Сигналы на ножках контроллера.
Фиолетовая - DE
Голубая - TX
Желтая - RX

20230306_115444.jpg
Exemption
Ответ на комментарий  Aveal
1 год назад

Макс запитан от 5В (на самом деле не макс а плата ttl to 485).
СТМ на плате дискавери.

если я отключаю ножку TX от 485 и смотрю на нее осциллографом, то на дискавери логическая 1 (3В). При этом если померить выход макса, то там хороший (правильный) сигнал 0-5В.

А осциллограмма как на фото получается, если их подключить друг к другу, поскольку сигнал 3В(от СТМ) и 5 В (от 485) смешиваются.

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

это фото если разъединить стм от 485 на ножке пине дискавери

20230306_161135.jpg
Exemption
Ответ на комментарий  Exemption
1 год назад

это фото если отсоединить стм от 485 на ножке RO платы ttl to 485

20230306_161206.jpg
Exemption
Ответ на комментарий  Aveal
1 год назад

https://drive.google.com/drive/folders/1ZDoGKfCMvQXHqOnEUjq9gu9kYDiBZpL2?usp=sharing

проект в Keil
futaba - это экран вывода данных. он исполнению не должен мешать.

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

Поменял порт usart1 на usart2. и функция HAL_UART_RxCpltCallback теперь вызывается. И даже eMBMasterRTUReceiveс условием
"usMBCRC16( ( UCHAR * ) ucMasterRTURcvBuf, usMasterRcvBufferPos ) == 0 )" выполняется. в четверг буду пробовать выводить данные.

Видимо ножку RX спалил порта usart 1 ....

rentoc
1 год назад

Доброго времени суток! Пытаюсь тоже сделать для "black pill" на stm32f411ceu6. При активации всех 3 USART'ов CubeMX пишет что:
"Status: Parity disabled conflict with: USART6: Mode Asynchronous ". Это значит нельзя все 3 USART'а использовать?

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

Извините, да "Status: Partly disabled conflict with: USART6: Mode Asynchronous ". Там это предупреждение пропадает, если только USART1 оставить.

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

А если RTS / CTS понадобятся, то можно ли на другие ноги вывести RTS / CTS для USART1 ?

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

На pinouts diagram для USART всего 14 выводов. Но все равно в данном корпусе нет такой возможности значит ?

STM32F4x1_PinoutDiagram_RichardBalint.png
rentoc
Ответ на комментарий  Aveal
1 год назад

Это значит шанс есть разнести по разным пинам?

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

Если оставить USART1 и USART2, то RCC Partly disabled conflict with USART2. А RCC на другие пины нельзя перекинуть ?

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

Прошу прощения за глупый вопрос.... Какой средой открывается Ваш проект???

kryak87
1 год назад

Здравствуйте, спасибо большое за библиотеку!
Никак не могу понять как прочитать данные со Slave, ни в одну callback-функцию не попадаю (eMBMasterRegInputCB, eMBMasterRegHoldingCB, eMBMasterRegCoilsCB, eMBMasterRegDiscreteCB). Ответ от slave есть, смотрел на осциллографе на ноге проца.
Прошу помочь!

kryak87
1 год назад

Правильно ли я понимаю, что ответ попадает в ucMasterRTURcvBuf?

Никита
Никита
1 год назад

Добрый день! Написал код, который отправляет несколько пакетов единоразово, но столкнулся с проблемой, что контрольная сумма в каждом из пакетов остается с предыдущего пакета, причем первый пакет не отправляется. В чем может быть проблема?
Прикрепляю изображения кода, пакета №2 и пакета №3 (пакет №1 не отправляется).

3
Никита
Никита
1 год назад

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

Никита
Никита
1 год назад

И изображение второго пакета.

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

STM32F405RG

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

https://disk.yandex.ru/d/lbbKRS230omX6g

Проект сделан в STM32CubeIDE версия 1.9.0.

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

Подскажите, как использовать библиотеки для Slave и Master для разных USART в одном контроллере. Включал в полном объеме папки. Изменял имена переменных, названия функций, имена enum и содержащихся в них значений в Slave. Добавлял в main.c iclude соответствующих файлов с библиотеки Slave, но в зависимости от того чей #include mb.h первый объявляется ту библиотеку и использует. Следующий ниже, например #include mbsl.h игнорируется.

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

Не знаю тема ещё актуальна или нет, для чтения регистров в режиме мастера, взял функцию чтения из библиотеки на GitHub и немного доработал. После выполнения eMBMasterReqReadHoldingRegister ответ в буфере сразу не появляется, нужно выполнить некоторое количество раз eMBMasterPoll, после этого произойдет событие eMBMasterFuncReadHoldingRegister и запишет данные в буфер регистров. Было принято решение после запроса чтение организовать безконечный цикл ожидания ограниченный Timeout'ом, в цикле выполнять eMBMasterPoll, так же пришлось завести глобальную переменную в качестве флага.

Пример кода из файла mbfuncholding_m.c

code1
eMBMasterReqErrCode eMBMasterReqReadHoldingRegister(UCHAR ucSndAddr, USHORT usRegAddr, USHORT usNRegs, LONG lTimeOut) {   UCHAR *ucMBFrame;   eMBMasterReqErrCode eErrStatus = MB_MRE_NO_ERR;   if ((ucSndAddr > MB_MASTER_TOTAL_SLAVE_NUM) || (usNRegs > MAX_HOLDING_NREGS)) eErrStatus = MB_MRE_ILL_ARG;   else   {     rd_reg_ok = FALSE;     vMBMasterGetPDUSndBuf(&ucMBFrame);     vMBMasterSetDestAddress(ucSndAddr);     ucMBFrame[MB_PDU_FUNC_OFF]                = MB_FUNC_READ_HOLDING_REGISTER;     ucMBFrame[MB_PDU_REQ_READ_ADDR_OFF]       = usRegAddr >> 8;     ucMBFrame[MB_PDU_REQ_READ_ADDR_OFF + 1]   = usRegAddr;     ucMBFrame[MB_PDU_REQ_READ_REGCNT_OFF]     = usNRegs >> 8;     ucMBFrame[MB_PDU_REQ_READ_REGCNT_OFF + 1] = usNRegs;     vMBMasterSetPDUSndLength(MB_PDU_SIZE_MIN + MB_PDU_REQ_READ_SIZE);     (void) xMBMasterPortEventPost(EV_MASTER_FRAME_SENT);     eErrStatus = eMBMasterWaitRequestFinish();     lTimeOut *= 1000;     while(!rd_reg_ok){       eMBMasterPoll();       if (lTimeOut-- == 0){         eErrStatus = MB_MRE_TIMEDOUT;         break;       }     }   }   return eErrStatus; } eMBException eMBMasterFuncReadHoldingRegister(UCHAR * pucFrame, USHORT * usLen) {   UCHAR *ucMBFrame;   USHORT usRegAddress;   USHORT usRegCount;   eMBException eStatus = MB_EX_NONE;   eMBMasterErrorCode eRegStatus;   if (xMBMasterRequestIsBroadcast()) eStatus = MB_EX_NONE;   else if (*usLen >= MB_PDU_SIZE_MIN + MB_PDU_FUNC_READ_SIZE_MIN)   {     vMBMasterGetPDUSndBuf(&ucMBFrame);     usRegAddress = (USHORT)(ucMBFrame[MB_PDU_REQ_READ_ADDR_OFF] << 8);     usRegAddress |= (USHORT)(ucMBFrame[MB_PDU_REQ_READ_ADDR_OFF + 1]);     usRegAddress++;     usRegCount = (USHORT)(ucMBFrame[MB_PDU_REQ_READ_REGCNT_OFF] << 8);     usRegCount |= (USHORT)(ucMBFrame[MB_PDU_REQ_READ_REGCNT_OFF + 1]);     /* Check if the number of registers to read is valid. If not     * return Modbus illegal data value exception. */     if ((usRegCount >= 1) && (2 * usRegCount == pucFrame[MB_PDU_FUNC_READ_BYTECNT_OFF]))     {       /* Make callback to fill the buffer. */       eRegStatus = eMBMasterRegHoldingCB(&pucFrame[MB_PDU_FUNC_READ_VALUES_OFF], usRegAddress, usRegCount, MB_REG_READ);       /* If an error occured convert it into a Modbus exception. */       if (eRegStatus != MB_ENOERR) eStatus = prveMBError2Exception(eRegStatus);     } else eStatus = MB_EX_ILLEGAL_DATA_VALUE;   /* Can't be a valid request because the length is incorrect. */   } else eStatus = MB_EX_ILLEGAL_DATA_VALUE;   return eStatus; } Пример кода из файла mb_master.c
code2
eMBMasterErrorCode eMBMasterRegHoldingCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBMasterRegisterMode eMode) {   if (eMode == MB_REG_READ)   {     for(u8 i=0;i<usNRegs;i++) usRegHoldingBuf[i] = (u16)(pucRegBuffer[i*2]<<8)|(pucRegBuffer[1+i*2]);     rd_reg_ok = TRUE;   }   eMBMasterErrorCode eStatus = MB_ENOERR;   return eStatus; }
Виталий Митько
22 дней назад

Поскольку это проект для  Modbus Master , то я не вижу смысла тащить в него функционал для Slave

А есть вариант одной библиотеки, и для мастер и для слейва. Чет мне не получается их склеить. Нужно в контролер(ПЛК) оба варианта. т.к при связке 2х контролеров один мастер 2рой слейв.

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