AVR: modbus

Возникла задача — соединить устройство на микроконтроллере atmega avr с компьютером по rs232. Что бы не изобретать велосипед, в качестве протокола обмена был выбран modbus.

Почему modbus:

  1. Поддерживается большинством  промышленных  (scada) систем.
  2. Существуют большое количество свободно распространяемых библиотек/программ с открытым исходным кодом.

Для отладки собрал стенд: stk500 c atmega avr подключенный к обычному компьютеру через переходник rs232-usb.  Роль мастера (master)  будет выполнять ПК,  роль ведомого (slave) — avr atmega на stk500.

За основу возьмем:

  • Для ведомого (slave) — freemodbus;
  • Для ведущего (master) — qmodbus;

freemodbus

freemodbus — реализация ведомого  устройства (slave),  поддерживает  modbus-rtu, modbus-ascii, modbus-tcp, портирован на большое количество архитектур, в том числе и для atmega avr. Скачать исходники можно тут тут ( версия  1.5.0).

Компиляция freemodbus

Скачиваем, разархивируем, заходим в freemodbus-v1.5.0/demo/AVR.

Запускаем make и… ничего не собирается, получаем ошибку:

make: /opt/gcc-avr/bin/avr-gcc: Command not found
make: *** [demo.o] Error 127

Что бы поправить сборку под avr редактируем Makefile, изменяем переменные СС, OBJCOPY, AVRDUDE. В них надо указать путь к соответствующим программам. У меня путь указан в переменной окружения PATH, по этому просто указываю имена программ:

CC=avr-gcc
OBJCOPY=avr-objcopy
AVRDUDE=avrdude

Далее задаем используемый avr микроконтроллер:

MCU = atmega16

В CFLAGS указываем какой используется кварц — 8 МГц:

-DF_CPU=8000000UL

И из правила сборки по-умолчанию убираем cof и eep, оставляем elf и hex:

all: $(TARGET).elf  $(TARGET).hex

Теперь все собирается.

Доступ к данным в modbus

В  описании протокола  modbus определено  четыре типа данных:

  • Discrete Inputs:  размер — один бит, доступен только на чтение.
  • Coils: размер — один бит, доступен на чтение и на запись.
  • Input Registers — 16-битный регистр, доступен только на чтение.
  • Holding Registers — 16-битный регистр, доступен на чтение и на запись.

Для каждого типа в freemodbus надо реализовать функцию, обеспечивающую доступ к данным.

Для Input Registers:

eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer,
                            USHORT usAddress, USHORT usNRegs );

Для Holding Registers:

eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress,
                              USHORT usNRegs, eMBRegisterMode eMode );

Для Coils:

eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress,
                            USHORT usNCoils, eMBRegisterMode eMode );

Для Discrete Inputs:

eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress,
                               USHORT usNDiscrete );

Где:
pucRegBuffer — массив в который будет записаны значения регистров при чтении,  или из которого будут считаны значения регистров которые надо записать.
usAddress — начальный адрес регистров
usNRegs, usNDiscrete, usNCoils — количество регистров
eMode —  чтение или запись

На stk500 есть 8 светодиодов и 8 кнопок, сделаем так что бы управление светодиодами осуществлялось через Holding Register, а состояние кнопок считывалось из Input Register. При  при помощи шлейфов PORTВ подключим к светодиодам а PORTA к кнопкам.

Получается у нас будет один регистр хранения (Holding Register) с адресом 1 и один регистр ввода (Input Register) тоже с адресом 1.
У каждого типа регистров в modbus свое адресное пространство, по этому регистры разного типа могут иметь одинаковые адреса.

В файле freemodbus-v1.5.0/demo/AVR/demo.c изменим код функций eMBRegInputCB и eMBRegHoldingCB:

eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
  eMBErrorCode  eStatus = MB_ENOERR;
  if( ( usAddress == 1 ) && ( usNRegs == 1 ) ) {
      *pucRegBuffer++ = 0;
      *pucRegBuffer++ = ~PINA;
  }
  else {
    eStatus = MB_ENOREG;
  }
  return eStatus;
}
 
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
                 eMBRegisterMode eMode )
{
  eMBErrorCode  eStatus = MB_ENOERR;
  if( ( usAddress == 1 ) && ( usNRegs == 1 ) ) {
    if( eMode == MB_REG_READ ) {
      *pucRegBuffer++ = 0;
      *pucRegBuffer++ = ~PINB;
    }
    else {
      pucRegBuffer++;
      PORTB = ~(*pucRegBuffer++);
    }
  }
  else {
    eStatus = MB_ENOREG;
  }
  return eStatus;
}

В качестве реализаций   eMBRegCoilsCB и eMBRegDiscreteCB оставим функции — ‘заглушки':

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

Так же надо не забыть настроить PORTB на выход и выставить начальное значение, для этого в функции main добавим строки:

   DDRB = 0xFF;  //настраиваем PORTB на выход
   PORTB = 0xFF; //выключаем все светодиоды

Настройка подключения modbus

Согласно описанию протокола modbus существует три варианта реализации : RTU, ASCII и TCP.

  • modbus ASCII — все данные передаются в текстовом виде, удобно отлаживать, но уменьшатся скорость
  • modbus RTU — данные в бинарном виде,  скорость выше чем у modbus ASCII
  • modbus TCP  — для обмена  поверх протокола TCP

Наш выбор — modbus RTU.

Для настройки соединения  в freemodbus  надо вызывать функцию eMBInit:

eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort,
                      ULONG ulBaudRate, eMBParity eParity );

Где:

  •  eMode — тип реализации, MB_RTU или  MB_ASCII
  • ucSlaveAddress — адрес устройства на шине modbus
  • ucPort — номер CОМ порта, в реализации для avr atmega не имеет значения
  • ulBaudRate — скорость обмена, например 38400
  • eParity — контроль четности:   MB_PAR_NONE — без контроля четности, MB_PAR_ODD — дополнение до нечетности ,    MB_PAR_EVEN —  дополнение до четности.

Пример настройки: протокол modbus RTU, адрес контроллера  10, скорость 38400, включен контроль  дополнения до четности.

eStatus = eMBInit( MB_RTU, 10, 0, 38400, MB_PAR_EVEN );

Так как modbus TCP и modbus ASCII использовать не будем, отключим ненужные части кода, что бы уменьшить размер программы. Для этого отредактируем файл modbus/include/mbconfig.h, заменим (1) на (0) у дефайнов MB_ASCII_ENABLED и MB_TCP_ENABLED.

/*! \brief If Modbus ASCII support is enabled. */
#define MB_ASCII_ENABLED                        ( 0 )
 
/*! \brief If Modbus RTU support is enabled. */
#define MB_RTU_ENABLED                          ( 1 )
 
/*! \brief If Modbus TCP support is enabled. */
#define MB_TCP_ENABLED                          ( 0 )

QModBus

QModBus, кросcплатформенная реализация  на Qt,  компилируется под windows и под linux. Поддерживает modbus-rtu и modbus-ascii.  Скачать исходники можно тут (версия 0.2.1 )

Компиляция QModBus

Скачиваем исходники тут. разархивируем. пытаемся собрать

qmake && make

И опять компиляция не проходит, выдает предупреждения и ошибки:

WARNING: Failure to find: 3rdparty/libmodbus/modbus.c
WARNING: Failure to find: 3rdparty/libmodbus/modbus-data.c
WARNING: Failure to find: 3rdparty/libmodbus/modbus-rtu.c
WARNING: Failure to find: 3rdparty/libmodbus/modbus-tcp.c
WARNING: Failure to find: 3rdparty/libmodbus/modbus.h
 
src/mainwindow.h:30:20: fatal error: modbus.h: No such file or directory
compilation terminated.

Изменяем  файл проекта qmodbus.pro, исправляем перменные SOURCESHEADERS, INCLUDEPATH:

SOURCES += src/main.cpp \
src/mainwindow.cpp \
3rdparty/qextserialport/qextserialport.cpp \
3rdparty/libmodbus/src/modbus.c \
3rdparty/libmodbus/src/modbus-data.c \
3rdparty/libmodbus/src/modbus-rtu.c \
3rdparty/libmodbus/src/modbus-tcp.c
HEADERS += src/mainwindow.h \
3rdparty/qextserialport/qextserialport.h \
3rdparty/qextserialport/qextserialenumerator.h \
3rdparty/libmodbus/src/modbus.h
 
INCLUDEPATH += 3rdparty/libmodbus/src 3rdparty/libmodbus 3rdparty/qextserialport

Выполняем

qmake && make

Теперь сборка работает.

Обмен данными с avr по modbus

Запускам QModBus

чтение состояние кнопок

Устанавливаем соответствующий COM-порт, скорость 38400, 8 бит данных, контроль дополнения до четности, 1 стоп бит.  Slave ID — адрес контроллера avr на шине modbus равен 10.

Читаем состояние кнопок, для этого выбираем команду «Read Input Registers», выставляем адрес регистра = 0, и количество = 1. После нажатия на кнопку Send происходит обмен. Считанное из avr значение отображается в поле Data (на картинке прочитано число 128).

Для управления светодиодам используем команду «Write Single Register».  В поле Data надо занести значение, которое будет записано в PORTB. После нажатия Send светодиоды, подключенные к  avr, загораются.

 

 Исходники

Архив проекта со всеми изменениями можно скачать тут

Запись опубликована в рубрике Микроконтроллеры avr с метками , , , . Добавьте в закладки постоянную ссылку.

35 комментариев: AVR: modbus

  1. Rpma говорит:

    Ti mojesh mne pomoci…mne nujen project cotorii…zdelal ata Проектирование и разработка алгоритмов и программ передачи данных на РС компьютер для микросистемы типа EasyAVR6.

  2. Артём Двинин говорит:

    Если смогу — помогу :) Расскажи подробней, в чём нужна помощь, что надо сделать? Можешь писать на почту: artem_dvinin@mail.ru

  3. Леван говорит:

    Ам, у меня такие вопросы)
    Для чего меня родные функции на свои, как считывать данные переданные на мк, в смысле когда мы получаем данные в массиве pucRegBuffer лежат наши данные их просто нужно прочитать, верно
    И можно примерчик сделать для АВР))) Принять просто данные и записать в массив))
    Просто одну функциё МЭИН написать можешь))

  4. Леван говорит:

    Артём, ответь пожалуйста, просто я не могу отправить с ПК (мастер) на МК(слейв) инфу, отправлять с МК я могу инфу через функцию eMBRegInputCB(), подскажи, как получить МК инфу с ПК, использовать eMBRegHoldingCB()?

  5. Артём Двинин говорит:

    Да, надо использовать eMBRegHoldingCB, передаваемые данные находятся в pucRegBuffer.

  6. Леван говорит:

    Худо бедно понял )) Прога QModBus не оч понравилась, не много непонятная , по-моему Modbus Poll удобнее

  7. Артём Двинин говорит:

    Использовал QModBus потому что к нему есть исходники, и его можно собрать под linux.

  8. Артём Двинин говорит:

    Пример программы для МК с записью в массив есть в исходниках freemodbus, которые на sourceforge лежат.

  9. nokfyt говорит:

    Здравствуйте, Артем. Подскажите, пожалуйста, у меня в институте есть вот такой стенд EV8031/AVR LCD. Мне поставили задачу реализовать на нем протокол ModBus RTU, чтобы он выступал как мастер по отношению к каким-либо устройствам, а потом сделать из него слейв по отношению к ПК, а я не знаю с чего начать. Подскажите какие-нибудь ориентиры на которые стоит обратить внимание, может литературу. С микроконтроллерами до этого я дела не имел. Огромное количество информации, не знаю за что хвататься. Заранее спасибо.

  10. Артём Двинин говорит:

    Здравствуйте, мне кажется, что лучше пойти путем «от простого к сложному»:
    1) научиться компилировать программы под avr
    2) написать программу мигающую светодиодом
    3) написать программу выводящую что-то в uart
    4) научиться работать с той периферией, что установлена на плате и к которой нужен доступ по modbus ( LCD и т.д. )
    5) написать итоговую программу

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

    P/S создал группу вконтакте, http://vk.com/club51735424 может там будет удобней обсуждать.

  11. Anton говорит:

    Здравсвуйте, Артем. Прочитал Вашу статью — стало многое ясно… но не всё: если не сложно, пожалуйста, подскажите как физически подключить и что использовать при подключении к сети (на RS485) несколько устройств с управление от МК avr. В итоге необходимо получить, что-то такое: ПК (через приложение) управляет, к примеру 2-3 устройствами на основе МК… пожалуйста подскажите…

  12. Артём говорит:

    День добрый, Антон.
    Рад, что моя статья пригодилась :)
    Создал заметку с кратким описанием rs485, http://mainloop.ru/interfaces/rs485.html
    Будут вопросы — пишите.

  13. Сергей говорит:

    Добрый день.

    Не хочет компилироваться freemodbus со словами
    port/port.h:90:25: error: attempt to use poisoned «SIG_UART_DATA»
    #define SIG_USART_DATA SIG_UART_DATA

    Я не великий программер на С. Не подскажете, куда копать?

  14. Сергей говорит:

    Кое что продвинулось. Оказывается, это «старые» названия векторов прерывания. Они депрекейтед.
    Таблетка: -D__AVR_LIBC_DEPRECATED_ENABLE__=1 в MakeFile

    Теперь другая проблема:
    /usr/lib/gcc/avr/4.8.2/../../../../avr/lib/avr4/crtm8.o: In function `__bad_interrupt':
    ../../../../crt1/gcrt1.S:195: undefined reference to `main’
    collect2: error: ld returned 1 exit status
    make: *** [demo.elf] Ошибка 1

    Не линкуется…

  15. Артём говорит:

    Да, вы правы, чтобы новые версии компилятора работали со «старыми» названиями векторов прерываний надо добавить в Makeflie строку:
    CFLAGS += -D__AVR_LIBC_DEPRECATED_ENABLE__

    А вот почему появилась вторая проблема — непонятно,
    на avr-gcc 4.7.0 линковка успешно проходит.

  16. Сергей говорит:

    А вот на 4.8.2 не работает…

  17. Сергей говорит:

    Резюме:

    0. Живу под Федорой 20.
    1. Накатил в виртуальной машине Федору 18. В её состав входит avr-gcc 4.7.2 Всё прекрасно собралось иработает.
    2. Если исправить константы в инклюдах
    SIG_OUTPUT_COMPARE1A -> TIMER1_COMPA_vect
    SIG_UART_RECV -> USART_RXC_vect
    SIG_UART_DATA -> USART_UDRE_vect
    то всё компилируется и без -D__AVR_LIBC_DEPRECATED_ENABLE__ Но не линкуется всё с той-же ошибкой.

  18. Артём говорит:

    Так, по идее, надо переименовывать не в инклюдах, а в исходниках.
    в port/portserial.c и в port/porttimer.c

  19. Данил Кошелев говорит:

    Простите хотел уточнить, про freemodbus, та программа на которую Вы ссылаетесь под linux? Правильно ли я понял, что она генерирует код под разные виды микроконтроллеров? Есть ли ATXmega 128a1 в списке? Если нет, то можно ли попросить у Вас код для atmega16. Я переделаю для IAR и для своего контроллера и выставлю в общее пользование. Моя цель подключения сенсорных панелей Siemens и контроллеров Atmel.

  20. Артём Двинин говорит:

    freemodbus — это библиотека с примерами под разные микроконтроллеры. Примеров для ATXmega 128a1 нет. Код для atmega16 тут: http://mainloop.ru/downloads/modbus-avr.zip

  21. Ruslan говорит:

    А на atmega8515 будет работать?

  22. Артём Двинин говорит:

    Под atmega8515 работать будет, только придется некоторые имена регистров поменять (касающиеся настройки uart и таймера).

  23. Ruslan говорит:

    Передача по UART работает, все скомпилировалось, но qmodbus выдаёт ошибку: Slave threw exception «Connection timed out» or function not implemented.

  24. Ruslan говорит:

    Ошибка появляется только если использовать адаптер USB to COM. Если использовать встроенный порт компьютера, то всё нормально. Как это объяснить?

  25. Артём Двинин говорит:

    Может адаптер не совсем рабочий? Какой адаптер вы используете?

  26. SV говорит:

    Воспользовался Вашим примером использования протокола ModBus.
    Повторил Ваши указания.
    Согласно примеру шлю данные с ПК-мастер (QModBus) -> принимаю на целевом slave-устройстве (на базе ATmega32), включаю нужный пин порта Б в зависимости от переданного значения в поле Data (QModBus). Здесь все нормально!
    Моя задача состоит в коммуникации (минимум) двух целевых устройств (на базе ATmega32). Одно устройство (slave) измеряет температуру нескольких объектов. По запросу от master-устройства необходимо, что бы slave передал значения измеренной температуры.
    Прочел комментарии к статье. Там указывается, что для передачи данных используется функция eMBRegInputCB(), но как мне оформить этот момент недопонимаю!!! В какой то
    «степени» пользуюсь IAR-ом, но даже здесь не имею возможности использовать debug и прогнать код.
    И еще вопрос. Реализация приведенного кода freemodbus универсальна(?) для master и slave-устройств, или придется что то менять для каждого отдельного варианта
    (master и slave)???

    С уважением, Владимир.

  27. Артём Двинин говорит:

    Здравствуйте, Владимир.

    freemodbus- это реализация modbus только для slave. Кода для режима мастера в этой библиотеке нет.

    Для передачи данных надо изменить код функции eMBRegInputCB
    Если есть несколько датчиков то код будет выглядеть следующим образом.

    eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
    {
      eMBErrorCode eStatus = MB_ENOERR;
      unsigned short *ptr = (unsigned short *)pucRegBuffer;
      while( usNRegs-- ) {
        switch( usAddress ) {
          case 1: *ptr = d1; break; //данные первого датчика
          case 2: *ptr = d2; break;//данные второго датчика
          case 3: *ptr = d3; break;//данные третьего датчика
          default:  return  MB_ENOREG;
        }
        usAddress++;
        ptr++;
      }
      return eStatus;
    }
  28. Вячеслав говорит:

    Статья очень понравилась собираюсь попробовать эту библиотеку на pic16f877 хотесь бы посмотреть пример когда контролер мастер общается с контроллером слэйв. Хочу менять параметры в частотном приводе altivar

  29. Артём Двинин говорит:

    Вячеслав, здравствуйте!
    Расскажите потом, пожалуйста, о своём опыте с pic16f877 и freemodbus.

    А реализации мастера для микроконтроллеров в freemodbus нет, увы…

  30. Евгений говорит:

    Добрый день, использую ATmega8535, в файле portserial.c есть код

    #ifdef RTS_ENABLE
    SIGNAL( SIG_UART_TRANS )
    {
        RTS_LOW;
    }
    #endif

    RTS_ENABLE я прописал в 1 (#define RTS_ENABLE 1) но в данном контроллере не знаю на что заменить вектор SIG_UART_TRANS

  31. Артём Двинин говорит:

    Евгений, доброго времен суток!
    SIG_UART_TRANS надо поменять на UART_TX_vect

  32. Евгений говорит:

    Добрый день, подскажите пожалуйста, хочу использовать функцию записи нескольких значений:
    16 (0x10) — запись значений в несколько регистров хранения (Preset Multiple Registers)
    Я использую функцию eMBRegHoldingCB( ) ставлю адреса по порядку но в программе QModBUS необходимо еще Num of Coils, если ставить начальный адрес (например 5) и Num of Coils 1, то работает, а когда ставлю Num of Coils 2, т.е. должно быть адрес 5 — данные и адрес 6 — данные, выдает ошибку…

  33. zhogar говорит:

    Необходимо реализовать следующие функции:
    От Master, передача двух байт информации, и ответа от Slave для подтверждения приема.
    От Slave, прием двух байт информации, и отправка Master-у техже двух байт для подтверждения приема.
    Не понятна структура freemodbus, что за что отвечает, как и какие функции реализовать?

  34. zhogar говорит:

    И еще, реализовал Demo/AVR пример из пакета freemodbus, но при попытке соединить устройство с ModBus Pull, все время пишет: Insufficient bytes received уже все перепробовал ничего не помогает. freemodbus настроен вроде верно: четность, биты, скорость и т.д. физически на стороне портов тоже все в порядке.

  35. zhogar говорит:

    запрос такой [0A][03][03][F8][00][0A][45][03] ответ [00] ну судя по всему его просто нет.

Добавить комментарий

Ваш e-mail не будет опубликован.

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>