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

70 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Павел
Павел
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
1 год назад

Thank you very much! very helpful post.

Alex
Alex
1 год назад

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

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

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

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

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

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

Спасибо 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?) у меня все никак не получается починить считывание

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

Добрый день!
Подскажите, пожалуйста, можно ли как-то реализовать на одном МК и слейв и мастера? На разных 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 на другие пины нельзя перекинуть ?

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

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

kryak87
11 месяцев назад

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

kryak87
11 месяцев назад

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

Никита
Никита
8 месяцев назад

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

3
Никита
Никита
8 месяцев назад

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

Никита
Никита
8 месяцев назад

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

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

STM32F405RG

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

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

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

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