Возникла задача — соединить устройство на микроконтроллере atmega avr с компьютером по rs232. Что бы не изобретать велосипед, в качестве протокола обмена был выбран modbus.
Почему modbus:
- Поддерживается большинством промышленных (scada) систем.
- Существуют большое количество свободно распространяемых библиотек/программ с открытым исходным кодом.
Для отладки собрал стенд: 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, исправляем перменные SOURCES, HEADERS, 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, загораются.
Исходники
Архив проекта со всеми изменениями можно скачать тут
Ti mojesh mne pomoci…mne nujen project cotorii…zdelal ata Проектирование и разработка алгоритмов и программ передачи данных на РС компьютер для микросистемы типа EasyAVR6.
Если смогу — помогу :) Расскажи подробней, в чём нужна помощь, что надо сделать?
Ам, у меня такие вопросы)
Для чего меня родные функции на свои, как считывать данные переданные на мк, в смысле когда мы получаем данные в массиве pucRegBuffer лежат наши данные их просто нужно прочитать, верно
И можно примерчик сделать для АВР))) Принять просто данные и записать в массив))
Просто одну функциё МЭИН написать можешь))
Артём, ответь пожалуйста, просто я не могу отправить с ПК (мастер) на МК(слейв) инфу, отправлять с МК я могу инфу через функцию eMBRegInputCB(), подскажи, как получить МК инфу с ПК, использовать eMBRegHoldingCB()?
Да, надо использовать eMBRegHoldingCB, передаваемые данные находятся в pucRegBuffer.
Худо бедно понял )) Прога QModBus не оч понравилась, не много непонятная , по-моему Modbus Poll удобнее
Использовал QModBus потому что к нему есть исходники, и его можно собрать под linux.
Пример программы для МК с записью в массив есть в исходниках freemodbus, которые на sourceforge лежат.
Здравствуйте, Артем. Подскажите, пожалуйста, у меня в институте есть вот такой стенд EV8031/AVR LCD. Мне поставили задачу реализовать на нем протокол ModBus RTU, чтобы он выступал как мастер по отношению к каким-либо устройствам, а потом сделать из него слейв по отношению к ПК, а я не знаю с чего начать. Подскажите какие-нибудь ориентиры на которые стоит обратить внимание, может литературу. С микроконтроллерами до этого я дела не имел. Огромное количество информации, не знаю за что хвататься. Заранее спасибо.
Здравствуйте, мне кажется, что лучше пойти путем «от простого к сложному»:
1) научиться компилировать программы под avr
2) написать программу мигающую светодиодом
3) написать программу выводящую что-то в uart
4) научиться работать с той периферией, что установлена на плате и к которой нужен доступ по modbus ( LCD и т.д. )
5) написать итоговую программу
Если будут вопросы по любому из пунктов, пишите, постараюсь помочь.
P/S создал группу вконтакте, http://vk.com/club51735424 может там будет удобней обсуждать.
Здравсвуйте, Артем. Прочитал Вашу статью — стало многое ясно… но не всё: если не сложно, пожалуйста, подскажите как физически подключить и что использовать при подключении к сети (на RS485) несколько устройств с управление от МК avr. В итоге необходимо получить, что-то такое: ПК (через приложение) управляет, к примеру 2-3 устройствами на основе МК… пожалуйста подскажите…
День добрый, Антон.
Рад, что моя статья пригодилась :)
Создал заметку с кратким описанием rs485, http://mainloop.ru/interfaces/rs485.html
Будут вопросы — пишите.
Добрый день.
Не хочет компилироваться freemodbus со словами
port/port.h:90:25: error: attempt to use poisoned «SIG_UART_DATA»
#define SIG_USART_DATA SIG_UART_DATA
Я не великий программер на С. Не подскажете, куда копать?
Кое что продвинулось. Оказывается, это «старые» названия векторов прерывания. Они депрекейтед.
Таблетка: -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
Не линкуется…
Да, вы правы, чтобы новые версии компилятора работали со «старыми» названиями векторов прерываний надо добавить в Makeflie строку:
CFLAGS += -D__AVR_LIBC_DEPRECATED_ENABLE__
А вот почему появилась вторая проблема — непонятно,
на avr-gcc 4.7.0 линковка успешно проходит.
А вот на 4.8.2 не работает…
Резюме:
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__ Но не линкуется всё с той-же ошибкой.
Так, по идее, надо переименовывать не в инклюдах, а в исходниках.
в port/portserial.c и в port/porttimer.c
Простите хотел уточнить, про freemodbus, та программа на которую Вы ссылаетесь под linux? Правильно ли я понял, что она генерирует код под разные виды микроконтроллеров? Есть ли ATXmega 128a1 в списке? Если нет, то можно ли попросить у Вас код для atmega16. Я переделаю для IAR и для своего контроллера и выставлю в общее пользование. Моя цель подключения сенсорных панелей Siemens и контроллеров Atmel.
freemodbus — это библиотека с примерами под разные микроконтроллеры. Примеров для ATXmega 128a1 нет. Код для atmega16 тут: http://mainloop.ru/downloads/modbus-avr.zip
А на atmega8515 будет работать?
Под atmega8515 работать будет, только придется некоторые имена регистров поменять (касающиеся настройки uart и таймера).
Передача по UART работает, все скомпилировалось, но qmodbus выдаёт ошибку: Slave threw exception «Connection timed out» or function not implemented.
Ошибка появляется только если использовать адаптер USB to COM. Если использовать встроенный порт компьютера, то всё нормально. Как это объяснить?
Может адаптер не совсем рабочий? Какой адаптер вы используете?
Воспользовался Вашим примером использования протокола ModBus.
Повторил Ваши указания.
Согласно примеру шлю данные с ПК-мастер (QModBus) -> принимаю на целевом slave-устройстве (на базе ATmega32), включаю нужный пин порта Б в зависимости от переданного значения в поле Data (QModBus). Здесь все нормально!
Моя задача состоит в коммуникации (минимум) двух целевых устройств (на базе ATmega32). Одно устройство (slave) измеряет температуру нескольких объектов. По запросу от master-устройства необходимо, что бы slave передал значения измеренной температуры.
Прочел комментарии к статье. Там указывается, что для передачи данных используется функция eMBRegInputCB(), но как мне оформить этот момент недопонимаю!!! В какой то
«степени» пользуюсь IAR-ом, но даже здесь не имею возможности использовать debug и прогнать код.
И еще вопрос. Реализация приведенного кода freemodbus универсальна(?) для master и slave-устройств, или придется что то менять для каждого отдельного варианта
(master и slave)???
С уважением, Владимир.
Здравствуйте, Владимир.
freemodbus- это реализация modbus только для slave. Кода для режима мастера в этой библиотеке нет.
Для передачи данных надо изменить код функции eMBRegInputCB
Если есть несколько датчиков то код будет выглядеть следующим образом.
Статья очень понравилась собираюсь попробовать эту библиотеку на pic16f877 хотесь бы посмотреть пример когда контролер мастер общается с контроллером слэйв. Хочу менять параметры в частотном приводе altivar
Вячеслав, здравствуйте!
Расскажите потом, пожалуйста, о своём опыте с pic16f877 и freemodbus.
А реализации мастера для микроконтроллеров в freemodbus нет, увы…
Добрый день, использую ATmega8535, в файле portserial.c есть код
RTS_ENABLE я прописал в 1 (#define RTS_ENABLE 1) но в данном контроллере не знаю на что заменить вектор SIG_UART_TRANS
Евгений, доброго времен суток!
SIG_UART_TRANS надо поменять на UART_TX_vect
Добрый день, подскажите пожалуйста, хочу использовать функцию записи нескольких значений:
16 (0x10) — запись значений в несколько регистров хранения (Preset Multiple Registers)
Я использую функцию eMBRegHoldingCB( ) ставлю адреса по порядку но в программе QModBUS необходимо еще Num of Coils, если ставить начальный адрес (например 5) и Num of Coils 1, то работает, а когда ставлю Num of Coils 2, т.е. должно быть адрес 5 — данные и адрес 6 — данные, выдает ошибку…
Необходимо реализовать следующие функции:
От Master, передача двух байт информации, и ответа от Slave для подтверждения приема.
От Slave, прием двух байт информации, и отправка Master-у техже двух байт для подтверждения приема.
Не понятна структура freemodbus, что за что отвечает, как и какие функции реализовать?
И еще, реализовал Demo/AVR пример из пакета freemodbus, но при попытке соединить устройство с ModBus Pull, все время пишет: Insufficient bytes received уже все перепробовал ничего не помогает. freemodbus настроен вроде верно: четность, биты, скорость и т.д. физически на стороне портов тоже все в порядке.
запрос такой [0A][03][03][F8][00][0A][45][03] ответ [00] ну судя по всему его просто нет.