Портируем FreeModbus rtu на примере STM8L

Во многих устроствах, где возникает необходимость связи с компом или с другим устройством, часто применяют свой велосипед протокол связи. При этом, в большинстве случаев возможно обойтись проверенным решением, протоколом Modbus. Эта статья описывает как можно портировать бесплатную библиотеку FreeModbus на STM8L и на любой другой.

Подготовка проекта

Подготовьте свой проект в любимой IDE, я буду использовать IAR и стандартную библиотеку драйверов. Режим modbus будет только RTU. Скачайте библиотеку FreeModbus отсюда. На данный момент рекомендованная версия 1.3, лучше качайте её. В архиве нас интересует папка Demo/BARE и папка modbus. В первой лежит платформо зависимая часть, во второй ядро. Скопируйте себе в проект содержимое архива. Каталог demo/BARE скопируйте в freemodbus-v1.3.0 и переименуйте в STM8L. Добавьте в свой проект всё сишные файлы из каталогов
  • freemodbus-v1.3.0\modbus\
  • freemodbus-v1.3.0\modbus\rtu\
  • freemodbus-v1.3.0\modbus\functions\
  • freemodbus-v1.3.0\STM8L\
  • freemodbus-v1.3.0\STM8L\port\
Вот что должно получится в итоге


В настройках проекта нужно указать пути поиска инклюдов
У меня получился такой список

$PROJ_DIR$\…
$PROJ_DIR$\..\STM8L15x_StdPeriph_Driver\inc
$PROJ_DIR$\..\freemodbus-v1.3.0\modbus\
$PROJ_DIR$\..\freemodbus-v1.3.0\modbus\rtu\
$PROJ_DIR$\..\freemodbus-v1.3.0\modbus\include\
$PROJ_DIR$\..\freemodbus-v1.3.0\STM8L\port\

В свой main файл добавьте
#include «mb.h»
#include «mbport.h»

Начало портирования

В файле mbconfig.h найдем строки
#define MB_ASCII_ENABLED                        (  1 )
/*! \brief If Modbus RTU support is enabled. */
#define MB_RTU_ENABLED                          (  1 )
/*! \brief If Modbus TCP support is enabled. */
#define MB_TCP_ENABLED                          (  0 )


Меняем на 0
#define MB_ASCII_ENABLED                        (  0 )

В файле port.h нужно определить макросы для включения и отключения прерываний на целевой платформе.
Находим

#define ENTER_CRITICAL_SECTION( )   
#define EXIT_CRITICAL_SECTION( ) 

И добавляем определения
#define ENTER_CRITICAL_SECTION() (  __disable_interrupt() )   
#define EXIT_CRITICAL_SECTION() ( __enable_interrupt() ) 

Кроме того, там же удаляем строку #include <inttypes.h> и заменяем её на #include «stm8l15x.h».

Теперь нужно реализовать таймер, таймер нужно настроить так, чтобы он сгенерировал прерывание через 50uS умноженное на значение, которое передаст ему FreeModbus. Я воспользуюсь таймером 1.

В файле porttimer.c заменяем 3 пустые функции на свои
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
  //Настройка таймера, требуется чтобы он сгенерировал прерывание через 50uS * usTim1Timerout50us
  //при этом требуется чтобы он не переполнялся в течении секунды
  
  /* Time base configuration */
  TIM1_TimeBaseInit(800, TIM1_CounterMode_Up, usTim1Timerout50us, 0); //частота таймера 20000, один тик = 50 uS
  /* Очистка флага обновления */
  TIM1_ClearFlag(TIM1_FLAG_Update);
  /* Включим прерывания по обновлению */
  TIM1_ITConfig(TIM1_IT_Update, ENABLE);//
 
  return TRUE;
}

inline void
vMBPortTimersEnable(  )
{
  TIM1_SetCounter(0);//обнулим таймер
  /* Включим таймер */
  TIM1_Cmd(ENABLE);
}

inline void
vMBPortTimersDisable(  )
{
   /* Выключим таймер */
  TIM1_Cmd(DISABLE);
}

Отдельно стоит рассказать про функцию static void prvvTIMERExpiredISR( void ) Это обработчик прерываний таймера. Его нужно модифицировать, в данном случае он превращается в

INTERRUPT_HANDLER(TIM1_UPD_OVF_TRG_COM_IRQHandler,23)
{
   TIM1_ClearITPendingBit(TIM1_IT_Update);//очистим флаг прерывания
  ( void )pxMBPortCBTimerExpired(  );
}

Кроме того, нужно закоментировать аналогичный обработчик в файлах «stm8l15x_it.h» и «stm8l15x_it.с»

Теперь применим физический канал. Для примера возьмем простой UART.
Сначала инициализация UART
В файле portserial.c
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    /* Enable USART clock */
    CLK_PeripheralClockConfig(CLK_Peripheral_USART1, ENABLE);
  
    /* Configure USART Tx as alternate function push-pull  (software pull up)*/
    GPIO_ExternalPullUpConfig(GPIOC, GPIO_Pin_3, ENABLE);
  
    /* Configure USART Rx as alternate function push-pull  (software pull up)*/
    GPIO_ExternalPullUpConfig(GPIOC, GPIO_Pin_2, ENABLE);
  
    /* USART configuration */
    USART_Init(USART1, ulBaudRate, USART_WordLength_8b, USART_StopBits_1, USART_Parity_No, USART_Mode_Rx | USART_Mode_Tx);
  
    return TRUE;
}

Обратите внимание, что по изначальной задумке, этой функции передаются номер UART, скорость, чётность, и количество бит. Для упрощения примеров я игнорирую параметры чётности и стоп бита, и прописываю их тут. Так делать не стоит :-)

Следующая функция void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ). Она должна активировать прерывание на приём/передачу.
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
  /* If xRXEnable enable serial receive interrupts. If xTxENable enable
  * transmitter empty interrupts.
  */
  if(xRxEnable == TRUE)
  {
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//включим прерывание если входной буфер не пуст
  }
  else
  {
    USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);//выключим прерывание если входной буфер не пуст
  } 
  
  if(xTxEnable == TRUE)
  {
    USART_ITConfig(USART1, USART_IT_TXE, ENABLE);//включим прерывание если выходной буфер пуст
  }
  else
  {
    USART_ITConfig(USART1, USART_IT_TXE, DISABLE);//выключим прерывание если выходной буфер пуст
  }
}

Теперь прерывание по приёму байта static void prvvUARTRxISR( void ). Аналогично прерыванию по таймеру заменяем её на
INTERRUPT_HANDLER(USART1_RX_TIM5_CC_IRQHandler,28)
{ 
  pxMBFrameCBByteReceived(  );
}

Прерывание по пустому буферу передачи static void prvvUARTTxReadyISR( void ) заменяется на
INTERRUPT_HANDLER(USART1_TX_TIM5_UPD_OVF_TRG_BRK_IRQHandler,27)
{
  pxMBFrameCBTransmitterEmpty(  );
}

Функция, которая должна отправить байт в UART выглядит так
BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
  /* Put a byte in the UARTs transmit buffer. This function is called
  * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
  * called. */
  USART_SendData8(USART1, ucByte);
  return TRUE;
}

И функция получения байта
BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
  /* Return the byte in the UARTs receive buffer. This function is called
  * by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
  */
  *pucByte = USART_ReceiveData8(USART1);
  return TRUE;
}

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

Тестирование

Содержимое тестового main файла

/* Includes ------------------------------------------------------------------*/
#include "stm8l15x.h"

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

/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 1000
#define REG_INPUT_NREGS 4

/* ----------------------- Static variables ---------------------------------*/
static USHORT   usRegInputStart = REG_INPUT_START;
static USHORT   usRegInputBuf[REG_INPUT_NREGS];

/**
  * @brief  Configure peripherals Clock   
  * @param  None
  * @retval None
  */
static void CLK_Config(void)
{
  /* High speed internal clock prescaler: 1 */
  CLK_SYSCLKDivConfig(CLK_SYSCLKDiv_1);

  /* Select HSE as system clock source */
  CLK_SYSCLKSourceSwitchCmd(ENABLE);
  CLK_SYSCLKSourceConfig(CLK_SYSCLKSource_HSI);

  while (CLK_GetSYSCLKSource() != CLK_SYSCLKSource_HSI)
  {}

  /* Enable TIM1 CLK */
  CLK_PeripheralClockConfig(CLK_Peripheral_TIM1, ENABLE);
}

/* ----------------------- Start implementation -----------------------------*/
int
main( void )
{
    CLK_Config();
    eMBErrorCode    eStatus;

     eStatus = eMBInit( MB_RTU, 0x0A, 0, 19200, 0 );// ucPort и eParity игнорируются, заданны жестко
    //так делать не стоит!

    /* Enable the Modbus Protocol Stack. */
    eStatus = eMBEnable(  );

    for( ;; )
    {
        ( void )eMBPoll(  );

        /* Here we simply count the number of poll cycles. */
        usRegInputBuf[0]++;
    }
}

eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    if( ( usAddress >= REG_INPUT_START )
        && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegInputStart );
        while( usNRegs > 0 )
        {
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
                 eMBRegisterMode eMode )
{
    return MB_ENOREG;
}


eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
               eMBRegisterMode eMode )
{
    return MB_ENOREG;
}

eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    return MB_ENOREG;
}

REG_INPUT_START определяет, какой первый адрес регистров ввода используется. REG_INPUT_NREGS количество регистров ввода. Код непрерывно инкрементирует регистр с адресом 1000. Для тестирования со стороны PC можно воспользоватся утилитой ModbusMaster
Внимательно выставьте именно такие настройки как на скрине, иначе устройство будет отвечать ошибкой.
  • Communication Protocol — RTU OVER232/485
  • Slave Address — 10
  • Start Address — 1000
  • Total Values — 4
  • Type — 3:Input Register
  • Size — Word
  • Format — какой удобнее
  • ComPort — ком порт, на который вы повесили устройство
  • BaudRate — 19200
  • Parity — NONE
  • DataBits — 8
  • StopBits — 1
После выставления настроек нажмите кнопку Repeting Scan.

Скачать проект можноТут Или с помощью торента, файл прикрепил к статье.
  • +4
  • 24 сентября 2012, 10:32
  • Yanichar
  • 2
Файлы в топике: ModbusMaster.zip, STM8FreeModbus.zip.torrent

Комментарии (66)

RSS свернуть / развернуть
В ряду стм8 прибыло :)
TIM4_TimeBaseInit(TIM4_Prescaler_128, (uint8_t)(6.25 * usTim1Timerout50us));

Так тоже делать не стоит. Тянуть дробную арифметику только для настройки скорости… Я слишкм жадный. Лучше целочисленную арифметику вида usTim1Timerout50us * 25 / 4 или usTim1Timerout50us * 6 + usTim1Timerout50us / 4 будет без переполнений при умножении.
Более того
//Настройка таймера, требуется чтобы он сгенерировал прерывание через 50uS * usTim1Timerout50us
//при этом требуется чтобы он не переполнялся в течении секунды
1сек = 20 000 * 50мкс. Считаем 6.25 * 20 000 = 125 000 > 65535 (MAX_USHORT). И более того > 255 к которым вы вольно обрезали (uint8_t).
Очевидно что требуется менять предделитель в зависимости от переданного значения.

P.S.: Комментарии «так лучше не делать» правильнее дополнить строкой
#warning TODO: parity & stops & whatever you want

мало ли спустя сколько лет будете мучаться «и почему не работает», открыв архивный проект.
+1
1сек = 20 000 * 50мкс. Считаем 6.25 * 20 000 = 125 000 > 65535 (MAX_USHORT). И более того > 255 к которым вы вольно обрезали (uint8_t).
Очевидно что требуется менять предделитель в зависимости от переданного значения.
Да, что-то я погорячился, исправил. Но в случае RTU, переполнения не происходит. Спасибо за подсказку.
P.S.: Комментарии «так лучше не делать» правильнее дополнить строкой
#warning TODO: parity & stops & whatever you want

мало ли спустя сколько лет будете мучаться «и почему не работает», открыв архивный проект.
Ну есть же комментарий.
0
Понимаете в чем дело. На комментарии далеко не всегда обращают внимания. Собралось — и ладно. Поехали с этим делом за 2000км, поставили на объект — не работает. И хрен его в чем дело, и хрен его где искать. Надо открывать и просматривать весь проект (утрирую). И хорошо если проекта 1 файл, то комментарии можно просмотреть. Вы, естественно, найдете ошибку, но время=деньги уже будут пореряны. Да и если искать будете не вы?
По скольку вы явно ввели баг, его надо явно пометить, либо проверять параметры и кидать ошибку при выполнении. Отсеять варнинг при сборке отладочной версии проще, чем потом искать ошибку в релизе из-за забытой доработки (о которой ни малейшего намека в продукте).
+2
Я говорю именно дополнить.сам варнинг дает краткое описание. А комментарий рядом с ним детально описывает в чём косяк. Так, увидив предупреждение во время сборки вида «stops & parity» естественно нифига не понятно. Но открыв файл на указанной строке уже будет виден полный текст комментария. Получаем, что кто-либо (или Вы сами) открыв проект, после простого билда получит в явном виде указания на все недоработки, без шанса что-либо упустить.
Против аргментов «это пробная версия, завтра исправлю» могу предложить аргумент «носители информации имеют свойство ломаться, и возможно через месяц это будет единственная доступная вам версия».
Учитесь на чужих ошибках, на своих всегда успеете :)
+1
По скольку вы явно ввели баг
Не надо придумывать. Багов нет. То, что параметры прописаны глубже по коду не баг.
«это пробная версия, завтра исправлю»
Это не пробная версия. Это демонстрация того, как можно сделать, и получить решение. Я не претендую то, что это правильное решение, у каждого своя голова на плечах.
На комментарии далеко не всегда обращают внимания.
ИМХО это ошибка. Коментарии пишут с какой-то целью.
Я слишкм жадный
Хорошо, а жаба не будет душить, когда для того чтоб настроить ком порт потребуется делать лишние телодвижения? Посмотрите как сделано в примерах в стандартной библиотеки, я лишние сущности не люблю.
0
То, что параметры прописаны глубже по коду не баг.
Да-да, знаем, это фича.
ИМХО это ошибка. Коментарии пишут с какой-то целью.
/* Очистка флага обновления */
  TIM1_ClearFlag(TIM1_FLAG_Update);
Тут у вас в явном виде лишний комментарий. Данная строка кода полностью документирует себя, комментарий не дает ни какой дополнительной информации. Это не правильный комментарий. Остальные так же.
Теперь скажите, как в куче бессмысленных комментариев быстро найти комментарий с описанием «не бага»? Или вы пологаете что один восклицательный знак решает все беды? Поиском его не найти, имейте ввиду — это простое отрицание, коих в коде миллион. Даже «стандартное» TODO: в нем отсутствует.
Посмотрите как сделано в примерах в стандартной библиотеки
Стандартная библиотека дает вам универсальный интерфейс. В ней избыточность кода осознанная необходимость. Да, порой их пишут так же криво и не оптимально (как ваша дробная арифметика, там где она не нужна), не спорю. В этом случае производится оптимизация библиотеки. Порой её интерфейс не соответствует принятым соглашениям, при переносе между платформами, а интерфейс библиотеки заточен под своё ядро. Тогда переписывается библиотека под свои соглашения.
я лишние сущности не люблю.
И такие вещи как дублироание кода должно быть? Тогда для чего под каждый проект писать отдельный код с его индивидуальными «не багами»? Мне, например, тоже не нравится что в FatFS функциям передается номер диска, хотя у меня физически присутствует только одна карточка, но обработку этого параметра я провожу. Потому как извне отличное значение вполне может прийти, если попытатся примонтировать не существующий диск. И вот когда это происходит — происходит корректное отфутболивание мега-программиста (порой меня же), мол «иди кури мануал». А если такую проверку не проводить, то мы возвращаем что всё у нас зашибись, а потом в один прекрасный момент теряем все данные. В этой ситуации виноват уже не только программист, выполнивший некорректное монтирование, но и программист позволивший выполнить ошибочное действие (а у него есть возможность недопустить такой ситуации).
К слову, уроку «Проверяйте все приходящие снаружи данные» хорошо учит web-программирование. И не дай бог вам писать драйвера под компы.
Всё имеет свою цену. Я жадный до ресурсов. Как ресурсов кода, так и ресурсов времени. Как времени выполнения, так и своего затраченного на разработку времени. Поиск компромиса и есть работа программиста. Я тоже выполняю её с ошибками, не идеален и опыта не так много. Но я уже достаточно напарывался на такие фичи.
0
К слову, уроку «Проверяйте все приходящие снаружи данные» хорошо учит web-программирование.
ОЙ! не надо по больной мозоли!
но да. учит хорошо.
И не дай бог вам писать драйвера под компы.
особенно виндовые. этта вааще rocket sciense с черной магией вперемешку. смешать, но не взбалтывать.
0
ИМХО это ошибка. Коментарии пишут с какой-то целью.
Это вовсе не значит, что их потом читают. Более того, в процессе изменения кода изменить комментарий зачастую забывают. В итоге получается, в лучшем случае, бессмыссленный, а то и ошибочный коментарий. Ну и правильно написанный код зачастую не нуждается в комментариях вообще.
0
А ещё код вместе с комментариями копируют в другие места проекта. Код потом правят/удаляют вовсе, а комментарии остаются.
0
Это да, копипаста зло.
0
Не надо придумывать. Багов нет. То, что параметры прописаны глубже по коду не баг.
Угу. Втихую игнорировать полученные параметры не баг. Это просто издевательство.
0
  • avatar
  • Vga
  • 25 сентября 2012, 13:50
Ну есть же комментарий.

ИМХО это ошибка. Коментарии пишут с какой-то целью.

А вот скажите, Вы, когда портировали библиотеку, просмотрели абсолютно весь ее код и перечитали в нем абсолютно все комментарии? Только честно. Вдруг, в коде библиотеки тоже есть комментарий, типа «так делать нельзя»?

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

Кто-то другой, возьмет, выкачает ваш проет, и использует Вашу реализацию xMBPortSerialInit(). И она даже будет работать, пока он не попытается поменять параметры физ. уровня. Я там его ждет сюрприз…

у каждого своя голова на плечах

Да, безусловно, но зачем закладывать в код такой «сюрприз»?
+1
Да, безусловно, но зачем закладывать в код такой «сюрприз»?
Ну очевидно же. Что бы разработчик не скучал. А то взял — работает, поменял цифру — работает. Не интересно же и скукотень. :)
+1
Да-да, знаем, это фича.
Если программист захочет изменить параметры порта, то наткнувшись на строчку кода eStatus = eMBInit( MB_RTU, 0x0A, 0, 19200, 0 ); имхо он ДОЛЖЕН прочитать комментарий. Кроме того, хорошо я мог сделаю инициализацию отсюда, но почему авторы сделали так, что отсюда я не могу задать стоп биты? Тесть мне все равно нужно лезть в функцию xMBPortSerialInit. Вот потому я и сделал так. И да, холивор на на тему комментариев продолжать не хочется, я коментирую каждый чих. Это моё мнение, и переубедить меня не получится.
0
на строчку кода eStatus = eMBInit( MB_RTU, 0x0A, 0, 19200, 0 );
… программист должен дать по шее тому, кто подобное написал.
+2
Согласен. Кому мне давать по шее, что отсюда я не могу настроить стоп биты? Я повторю, это основная причина того, что я так сделал.
Какой? Перевод названия метода?
Это очевидно для вас, видимо новичком вы были много лет назад.
0
Согласен. Кому мне давать по шее, что отсюда я не могу настроить стоп биты?
Полагаю за написанный вами код с множеством магических циферок давать по шее вам стоит себе самому. Безотносительно настройки стопбитов.
Это очевидно для вас, видимо новичком вы были много лет назад.
Английский на уровне достаточном для чтения названий методов и констант изучают в школе. Впрочем, тут даже этого не надо, грамматика отсутствует, а слова можно глянуть в словаре.
Давайте проведем експеримент. Возьмем исходный код:

/* Очистка флага обновления */
  TIM1_ClearFlag(TIM1_FLAG_Update);

Теперь выбросим непонятные аббревиатуры, а все, что осталось, разобъем на отдельные слова (по подчеркиваниям, смене регистров и так далее):

clear flag flag update

Если выбросить дубль в средине, получится руглиш, который в точности соответствует вашему комментарию. Обратите внимание, что, если вы потом замените комментарий на, например, «мама мыла раму» не трогая сам код, то тот, кто проигнорует комментарий и будет читать именно код, получит достоверную информацию, в то время как тот, кто будет вчитываться в комментарии, будет в недоумении пытаться понять, каким боком тут мама и рама.
0
почему авторы сделали так, что отсюда я не могу задать стоп биты?

Потому, что эта информация избыточна. Есть стандарт MODBUS over Serial Line. Этот стандарт описывает настройки порта для разных режимов. Дык вот, кол-во стоп битов однозначно определяется по режиму «четности». Для всех parity корме «No parity» нужно использовать 1 стоп бит, для режима «No parity» — 2 стоп бита. Причем такой режим «No parity» — опциональный, его поддержка заявлена как recommended, режим по умолчанию — Even parity.
+1
Тогда почему до вызова функции xMBPortSerialInit это не задается? Мы снова приходим к тому, что нужно редактировать параметры тут.
0
Тогда почему до вызова функции xMBPortSerialInit это не задается?

Что именно не задается? Parity задается на самом верхнем уровне – eMBInit(). А кол-во стоп битов не задается в принципе, т. к. оно однозначно определяется в зависимости от Parity. Сделайте внутри xMBPortSerialInit проверку значения Parity и установите соответствующее кол-во стоп битов.
+1
Тут у вас в явном виде лишний комментарий
Лишний этот комментарий только для тех, кто знаком со стандартной библиотекой. Для остальных это может иметь какой то смысл.
0
Какой? Перевод названия метода?
0
Полагаю за написанный вами код
Да тут моего кода строк 10. Если под магическими цифрами вы имеет ввиду настройку таймера, то да, что такое 800 сразу непонятно.
слова можно глянуть в словаре
Вы против избыточных комментариев? Я вас понял.
0
Да тут моего кода строк 10. Если под магическими цифрами вы имеет ввиду настройку таймера, то да, что такое 800 сразу непонятно.
Не только. Вот в этой строке:

eMBInit( MB_RTU, 0x0A, 0, 19200, 0 );


понятен только первый параметр. Остальное — магические циферки.

Вы против избыточных комментариев? Я вас понял.
Помимо описания внешних интерфейсов (где комментарии уместны и полезны) во всех остальных местах появление желания написать комментарий означает одно из трех: либо то, что написано в коде имеет какие-то специфические особенности вызванные внешними обстоятельствами, либо то, что написано в коде надо переделать, либо написанное не понятно даже автору.
0
Я подозреваю, что Вы просто скопировали исходники и подключили их к своему проекту. И это логично, зачем нужна библиотека, если для ее использования нужно перепроверять каждую строчку в исходном коде.
Да, но я просмотрел коментарии, относящиеся к фронт энду библиотеки.
Угу. Втихую игнорировать полученные параметры не баг. Это просто издевательство.
Мне начинает казаться, что если бы я так не написал, то и вопросов не было бы. Я повторю свой вопрос, почему я не могу задать стоп биты при вызове eMBInit и я все равно должен лезть дальше? Ну так не логично ли все параметры задавать в одном месте?
0
Коллега, Вы продолжаете искать оправдание вместо того, чтобы признать, что лучше так не делать.

Я повторю свой вопрос, почему я не могу задать стоп биты при вызове eMBInit и я все равно должен лезть дальше?

Повторяю свой ответ: потому, что стандартом не предусмотрено свободное задание кол-ва стоп битов, в стандарте есть четкое описание, сколько стоп битов должно быть при том, или ином значении Parity. Другими словами, значение Parity, которое вы можете установить в eMBInit(), однозначно определяет кол-во стоп битов.

Если бы авторы добавили в API (параметр фунции eMBInit()) кол-во стоп битов, это было бы дублирование параметров, и породило бы неоднозначность. Я бы мог установить значение контроля четности в «Even parity» и выставит 2 стоп бита. А стандартом MODBUS такой режим не предусмотрен. Тобиш, сначала мы даем пользователю установить неправильные параметры, потом добавляем в коде проверку и возвращаем ошибку. Зачем это делать, если можно исключить ошибку на уровне интерфейса (API)?

По хорошему, внешний интерфейс проектируется так, чтобы пользователь не мог вызвать функцию неправильно.
+1
Коллега, Вы продолжаете искать оправдание вместо того, чтобы признать, что лучше так не делать.
Я признал ошибку, ещё до публикации. И написал об этом. Это моя первая статья и я не ожидал что такое допущение вызовет столько негатива.
значение Parity, которое вы можете установить в eMBInit(), однозначно определяет кол-во стоп битов.
Ткните носом в код, который это делает. Может я плохо искал? Я так понял, что однозначное определение отдано на откуп пользователям, и его надо делать в xMBPortSerialInit. Вот я и поддался искушению настраивать уже тут.
0
Я признал ошибку, ещё до публикации. И написал об этом. Это моя первая статья и я не ожидал что такое допущение вызовет столько негатива.
Да нет здесь негатива. Ни от меня, ни от других комментаторов.

Я так понял, что однозначное определение отдано на откуп пользователям, и его надо делать в xMBPortSerialInit

Да, именно так. Пользователь, в зависимости от параметра Parity, должен выставить кол-во стоп битов. Однозначное определение прописано в стандарте.
0
А что тут неясно в плане кода?
USART_Init(USART1, ulBaudRate, MB_UART_BITS_TO_SPL(ucDataBits), MB_UART_PARITY_TO_SPL(eParity)==USART_Parity_No ? USART_StopBits_1 : USART_StopBits_2, MB_UART_PARITY_TO_SPL(eParity), USART_Mode_Rx | USART_Mode_Tx);

Макросы MB_UART_x_TO_SPL должны маппить соответствующие значения FreeModbus в значения SPL.
0
  • avatar
  • Vga
  • 25 сентября 2012, 23:09
Упс, перепутал, на самом деле
MB_UART_PARITY_TO_SPL(eParity)==USART_Parity_No ? USART_StopBits_2 : USART_StopBits_1
+1
  • avatar
  • Vga
  • 25 сентября 2012, 23:10

#define ENTER_CRITICAL_SECTION() (  __disable_interrupt() )
#define EXIT_CRITICAL_SECTION() ( __enable_interrupt() )
Такое определение не совсем правильно, если происходят вложенные вызовы ENTER_CRITICAL_SECTION / EXIT_CRITICAL_SECTION

В примерах IAR есть более правильное определение
0
  • avatar
  • x893
  • 25 сентября 2012, 22:55
void vPortEnterCritical( void )
{
	portDISABLE_INTERRUPTS();
	uxCriticalNesting++;
	if( uxCriticalNesting == 1 )
	{
		configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
	}
}

//пример реализации в freertos
0
MB_UART_PARITY_TO_SPL(eParity)==USART_Parity_No? USART_StopBits_2: USART_StopBits_1
Так я ж не спорю. Это можно было сделать. Просто ещё остается номер порта. А кроме самого порта есть ещё лапы, которые надо настроить, и тактирование включить. Все эти определения надо брать откуда то по номеру. Можно было сделать как примерах стандартной библиотеки. А при переезде на другую платформу всё равно править прейдётся, я не верю в лёгкопереносимый код, всегда есть грабли. Вот потому я и постарался выкинуть сущности лишние,
0
С номером порта аналогично. Заводишь массив по количеству уартов, из которого и берешь номера/порты пинов и прочие требуемые для настройки данные.
Если UART один — можно игнорировать.

При переносе на другую архитектуру что-то править мож и придется (HAL — однозначно), но вот код, который работает с модбасом останется тем же.
0
  • avatar
  • Vga
  • 25 сентября 2012, 23:22
Если UART один — можно игнорировать.
Точнее, выдавать ошибку при попытке открыть какой-либо порт кроме первого. Алсо, вообще говоря, проверка на возможные ошибки в функции бы не помешала.
+2
  • avatar
  • Vga
  • 25 сентября 2012, 23:23
думаю автор уже сто раз раскаялся что выложил код :) а вообще, молодец +1. Еще бы мастера, да еще и под stm32 ))
0
думаю автор уже сто раз раскаялся что выложил код
В точку.
Еще бы мастера, да еще и под stm32 ))
Можно попробовать.
0
В точку.
Рилакс, народ просто пытается уберечь вас от граблей, на которые наступал. Может силу формы изложения это не очень приятно, но по сути верно, так что имеет смысл прислушаться. Ну и, к тому же, это фидбек, а его весьма часто не хватает. Так что не стоит раскаиваться.
0
поддерживаю чуть более чем полностью.
выкладывание кода в паблик дает бесценный опыт (на первых порах — просто носом ткнут) и не замыленный взгляд (что ценно даже для профи).
0
что ценно даже для профи
Я бы даже сказал «особенно ценно для профи».
+1
для профи взгляд со стороны особенно ценен. но тут есть момент — чтобы доказать позицию, нужен равный по уровню собеседник. и лично. за рюмкой чая. иначе не примет.
0
нужен равный по уровню собеседник

Не обязательно. Иногда, объясняя неподготовленному собеседнику «на пальцах» архитектуру и реализацию программы, замечаешь кучу своих косяков :)
+1
Тоже вариант. Причем довольно частый.
0
ну, я про этот момент вспоминал в параллельной дискуссии.
и да. такое бывает.
0
Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей — и все становится ясно.
+1
Я когда собеседования проводил, от молодых ребят периодически слышал весьма любопытные факты, о которых был не в курсе, хотя опыта у меня значительно больше. Так что не нужно недооценивать молодежь :)
0
а это очень хороший пинок тупо погуглить. порой такие для себя буквально открытия делаешь…
0
Естественно. Я тут недавно открыл для себя целую пачку жабовских технологий похожим образом (в смысле гугль после пинка :) ).
0
Ну если такой профи, что не примет, то такой он профи…
Обычно не требуется ничего доказывать, надо объяснить в чем может быть ошибка/не точность. Тому кто способен сам осознать не требуется доказывать. А кто уперся, всё равно сделает по своему, не важно что вы ему доказали.
0
зачем в библиотеку тянуть настройку портов и работу с ними?
то что приходится лезть в исходники библиотеки и править под свое железо — явно криворукость авторов freemodbus
0
Эти сорцы для того и предназначены — это конфиг-хедер и заготовки для HAL. Для МК — нормальная практика. Не dll-ку с HAL-ом же подключать, в самом деле. А в отвязке от железа не сделать — нужен таймер и собственно интерфейс приема и передачи данных. Поэтому приходится отдавать написание HAL'а конечному пользователю, предоставив ему заготовку.
0
  • avatar
  • Vga
  • 26 сентября 2012, 14:34
А в отвязке от железа не сделать — нужен таймер и собственно интерфейс приема и передачи данных
использовать таймер или программные задержки — это проблема пользовательского кода, библиотека должна предоставить только функции сброса, инкремента, и обработки тайм-аута приема. То же самое — с приемом/передачей. В библиотеке — только функции приема очередного байта в свой буфер, анализа принятого пакета, и генерации ответного пакета в тот же буфер. Как принимать/передавать — проблема пользовательского уровня — через RS232/Ethernet/радио или как-то еще…
0
Это проблема драйвера, а не пользовательского уровня. Вот этот драйвер тебе и предлагается написать. В некотором роде это тоже пользовательский уровень, но лежит ниже библиотеки.
0
  • avatar
  • Vga
  • 27 сентября 2012, 01:24
Все, что требует настройки, вынесено в папку «port»:
porttimer, portserial, portevent

Так что все логично
0
О, Добрый человек! Спасибо Вам за статью! Я вот только что-то не совсем догоняю: откуда стек берет данные когда к нему по протоколу обращаются? Из этих переменных:

static USHORT   usRegInputStart = REG_INPUT_START;
static USHORT   usRegInputBuf[REG_INPUT_NREGS];

?
0
Да. usRegInputStart это откуда начинаются адреса регистров ввода, а usRegInputBuf это регистры
0
Пытаюсь прикрутить к своему проекту упомянутую либу, буду крайне признателен, если поможете. Написал portserial.c и porttimer.c. Объявил eMBRegHoldingCB(), eMBRegCoilsCB(), eMBRegDiscreteCB(), eMBRegInputCB(). Компилю, зашиваю, запускаю и наблюдаю следующее: при запросе от мастера read holding register (0x03) вызывается функция eMBRegHoldingCB(), и после ее отработки стек перестает работать (в ответ мастер не получает ни ответа ни привета), прерывания по приему после этого перестают срабатывать (хотя первые 8 байт из первого запроса прерывание вызывают). Но проц не зависает, все продолжает крутиться, ошибок никаких eMBPoll() не возвращает. Я уже кажется перечитал все форумы и темы, чего то похоже не понимаю.
Запрос от мастера:

01 03 03 E9 00 04 95 B9 

portserial.c
porttimer.c
main.c

Сам проект, если что-то непонятно.

Если не затруднит, конечно и, если есть идеи — буду признателен. Спасибо!
0
А что у вас используется в качестве мастера? ПК-софт или самодельный девайс? СКАДА-система? По идее, если слейв не ответил через 3.5 символа, вы должны получить ошибку таймаута на стороне мастера. И зачем вы используете самописную ф-ю eMBRegHoldingCB, чем не устраивает реализация от автора freemodbus? И вообще, удалось ли вам запустить стек с тем примером, что идет в комплекте с библиотекой(в demo/AVR)?
0
В качестве мастера modbusPull чеерз переходник MOXA UPort1150. Ошибка таймаута — да получаю. Почему самописную — да потому что та, что в примере она для gccошной студии, а у меня IAR. И то по сути я лишь чуток изменил исходники из примера, когда переделывал по IAR, больше половины ушло как есть. Запустить стек вчера мне удалось, но получилось следующее: отправкой исходящих пакетов занимается функция pxMBFrameCBTransmitterEmpty(), которая по-сути переадресована в случае с протоколом modbus RTU на функцию xMBRTUTransmitFSM(). А поскольку эта функция вызывается из прерывания по окончанию отправки, то выходит, чтобы стек ответил на входящий запрос от мастера, нужно после того как входящий запрос был распарсен, нужно «пинуть» стек и один раз вызвать xMBRTUTransmitFSM(). Я добавил этот вызов в функции eMBRTUSend() в файле mbrtu.c. Когда я так сделал стек заработал. Вот тут можно посмотреть, что я там наизменял. Не могу понять, почему он сразу не стартует, что я не так делаю?
0
Где вы нашли IAR-специфичные вещи в ядре библиотеки? Поясните, что значит сразу не стартует? Стек работает прозрачно для приложения пользователя. Никаких лишних телодвижений делать не нужно.Ответ на запрос мастера формируется и отправляется автоматически. Может, у вас низкая «отзывчивость» стека? Посмотрите частоту поллинга.
0
не в ядре в файлах portserial.c и porttimer.c, которые идут в папке с примером.
0
А ничего посвежее нет?
0
  • avatar
  • x893
  • 21 апреля 2014, 18:12
А смысл? Ну возьмите freemodbus свежей версии
0
2010 последняя версия — хотя не важно
0
freemodbus v1.5 и есть последняя версия. Прекрасно работает. На Pinboard2
c ATMega16, ATMega8 стек сразу запустился, без каких-либо проблем.
0
Кто может подсказать, что тут за особенность: прикручиваю библиотеку к stm32. Не могу побороть ошибки вида
no definition for «eMBFuncReportSlaveID»
no definition for «eMBFuncReadInputRegister»
no definition for «eMBFuncReadHoldingRegister»
no definition for «eMBFuncWriteMultipleHoldingRegister»

должно работать, а не работает.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.