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 принял вид:

На очереди 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:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Тельман
Тельман
2 лет назад

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

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

но 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
2 лет назад

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

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

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

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

Спасибо)

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

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

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

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

Exemption
2 лет назад

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

rentoc
2 лет назад

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

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

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

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

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

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

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

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

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

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

Если оставить 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
7 месяцев назад

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

Ivan
Ivan
6 месяцев назад

Не знаю тема ещё актуальна или нет, для чтения регистров в режиме мастера, взял функцию чтения из библиотеки на 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; }
Никита
Никита
Ответ на комментарий  Ivan
1 месяц назад

Тоже произошла такая же проблема, то Ваш вариант не помог, все равно нужно использовать последовательность eMBMasterPoll() после использования eMBMasterReqReadHoldingRegister()
посмотрел в дебаге в функции eMBMasterRTUReceive() не проходит условие if( ( usMasterRcvBufferPos >= MB_SER_PDU_SIZE_MIN ). Не подскажите, у Вас не было такой проблемы в реализации?

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

Решил эту проблему. Я использовал в своем коде не только в eMBMasterReqReadHoldingRegister
но и в только в eMBMasterReqReadHoldingRegister, а изменял только в eMBMasterReqReadHoldingRegister. Точно такие же изменения сделал и в eMBMasterReqReadHoldingRegister и все заработало. Спасибо!

Виталий Митько
5 месяцев назад

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

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

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

Про считывание регистров со слейва. У меня из-за реализации xMBMasterPortEventGet/xMBMasterPortEventPost перетиралось событие, вызывающее Callback функцию. Поправил - работает.

Игорь
Игорь
Ответ на комментарий  Petr
24 дней назад

А как поправили то? Подскажите пожалуйста

Petr
Petr
Ответ на комментарий  Игорь
24 дней назад

Сделал циклический буфер и встроил его в стек. В функции xMBMasterPortEventPost - отправка события в буфер, а в xMBMasterPortEventGet - прием из буфера.
В стеке вообще много косяков. Например, обработки нулевого указателя много где не хватает. Я напоролся на то, что еще не инициализированный стек иногда дергался в прерывании UART.

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