Помощь в написании студенческих работ
Антистрессовый сервис

Реализация протокола Modbus

КурсоваяПомощь в написанииУзнать стоимостьмоей работы

В блоке инициализация необходимо сделать 2 вещи: подготовить систему к новой посылке и инициализировать UART. Для того, чтобы подготовить систему к новой посылке, необходимо записать в регистры CRCH и CRCL значение 0xFF. Этого требует алгоритм расчёта CRC, и установить значение 7 регистру BYTENUM. Так как посылка имеет длину 8, счётчик будет при поступлении новых байт декрементироваться до 0… Читать ещё >

Реализация протокола Modbus (реферат, курсовая, диплом, контрольная)

Федеральное агентство по образованию Государственное образовательное учреждение высшего профессионального образования ГОУ ВПО ЮГОРСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ Институт Систем Управления и Информационных Технологий Кафедра «Автоматизированные системы обработки информации и управления»

ПОЯСНИТЕЛЬНАЯ ЗАПИСКА К КУРСОВОЙ РАБОТЕ по дисциплине: «Языки программирования низкого уровня»

на тему «Реализация протокола Modbus»

Выполнил: Бирюков Н.А.

студент группы 1170

Специальность: 230 102

Шифр: 117 010

Проверил: преподаватель С. Н. Горбунов Ханты-Мансийск, 2011

Федеральное агентство по образованию Государственное образовательное учреждение высшего профессионального образования ЮГОРСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ Институт Систем Управления и Информационных Технологий Кафедра «Автоматизированные системы обработки информации и управления»

ЗАДАНИЕ На курсовую работу по дисциплине «Языки программирования низкого уровня»

Тема курсовой работы: «Реализация протокола Modbus для микроконтроллера семейства AVR»

На языке Assembler создать программу, реализующую протокол Modbus в качестве Slave устройства для микроконтроллера семейства AVR.

Программа должна быть реализована для стенда СУ-МК-AVR (Контроллер At Mega 128) и выполнять следующие функции:

1. осуществлять приём/передачу сообщений по UART.

2. чтение нескольких флагов.

3. чтение нескольких дискретных регистров.

4. чтение регистров хранения.

5. чтение нескольких регистров ввода.

6. запись значения одного флага.

7. запись значения в один регистр.

Задание выдал _______________ Горбунов С.Н.

Задание принял _______________Бирюков Н.А.

  • Введение
  • 1. Анализ задания на курсовую работу
  • 2. Проектирование программы
  • 2.1 Чтение флагов
  • 2.2 Чтение дискретных входов
  • 2.3 Чтение регистров хранения
  • 2.4 Чтение регистров ввода
  • 2.5 Запись одного флага
  • 2.6 Запись одного регистра хранения
  • 2.7 Исключительная ситуация
  • 2.8 Контрольная сумма
  • 3. Разработка программы
  • 3.1 Инициализация
  • 3.2 Обработка прерываний
  • 3.3 Обработка запроса
  • 3.4 Чтение флагов
  • 3.5 Чтение регистров хранения
  • 3.6 Чтение регистров ввода
  • 3.7 Запись флага
  • 3.8 Запись регистра
  • 3.9 Обработка исключительных ситуаций
  • 3.10 Контрольная сумма
  • 4. Тестирование программы
  • Заключение
  • Список использованных источников
  • Приложение А
  • Приложение Б
  • Введение
  • Целью работы является проектирование и реализация протокола Modbus для микроконтроллера семейства AVR.
  • Modbus — коммуникационный протокол, основанный на архитектуре «клиент-сервер». Широко применяется в промышленности для организации связи между электронными устройствами. Может использовать для передачи данных последовательные линии связи RS-485, RS-422, RS-232, а также сети TCP/IP (Modbus TCP).
  • Существует множество решений для реализации Modbus протокола в качестве slave устройства. Представленное решение отличается простотой и наглядностью, при этом выполняются основные функции протокола.
  • Всего в протоколе Modbus предусмотрено более семидесяти функций, некоторые из них выполняют функции проверки ошибок. В данной реализации упор сделан на функции чтения/записи данных.

1. Анализ задания на курсовую работу

Существует три основных реализации протокола Modbus, две для передачи данных по последовательным линиям связи, как медным EIA/TIA-232-E (RS-232), EIA-422, EIA/TIA-485-A (RS-485), так и оптическим и радио:

· Modbus RTU и

· Modbus ASCII,

и одна для передачи данных по сетям Ethernet поверх TCP/IP:

· Modbus TCP.

В данной курсовой работе представлен Modbus RTU, так как этот вариант наиболее подходит для программирования на микроконтроллере AVR, так как реализация стека TCP/IP необоснованно усложнило бы программу. Реализация ASCII не оправдана, так как формат сообщений Modbus RTU гораздо удобнее обрабатывать. Modbus ASCII использует 7 бит данных при передаче по последовательной линии, для идентификации начала и конца посылки используется дополнительные символы («:» в начале строки и символы перевода строки в конце).

Рассмотрим формат сообщений ModBus RTU. Структура ModBus состоит из запросов и ответов. Их основа — элементарный пакет протокола, так называемый PDU (Protocol Data Unit). Структура PDU (Рисунок 1.1) не зависит от типа линии связи и включает в себя код функции (FCode) и поле данных (Data).

Рисунок 1.1 — Структура пакета PDU

Для передачи пакета по физическим линиям связи PDU помещается в другой пакет, содержащий дополнительные поля. Этот пакет носит название ADU (Application Data Unit). Общая структура ADU пакета для ModBus RTU представлена на рисунке 1.2.

Рисунок 1.2 — Общая структура ADU пакета для ModBus RTU

Пакет Modbus RTU ADU помимо PDU пакета включает в себя также Slave ID — адрес ведомого устройства и контрольную сумму CRC16 для проверки корректности пакета.

В данной реализации протокола Modbus используются следующие типы данных:

· Флаг — один бит, регистр флагов доступны как на чтение, так и на запись. Флаги хранятся в оперативной памяти микроконтроллера. Для флагов выделен 1 байт, таким образом можно обращаться к 8 флагам.

· Дискретный регистр — один бит, доступен только для чтения. Дискретный регистр является портом ввода. Дискретные регистры являются регистром статуса микроконтроллера, следовательно доступны 8 бит.

· Регистр хранения — 16-битный регистр, доступен для чтения и записи. В качестве регистров хранения выступают выделенные ячейки в оперативной памяти микроконтроллера. Для регистров хранения выделено 32 байта, таким образом, возможен доступ к 16-ти регистрам.

· Регистр ввода — 16-битный регистр, доступен только для чтения. В качестве регистров ввода используются РОН микроконтроллера. При этом старшие 8 бит регистра всегда равны «0», а младшие — содержимому запрашиваемого регистра. Для чтения доступны 32 регистра.

В данной курсовой работе реализованы следующие функции Modbus:

· 0×01 — чтение значений из нескольких регистров флагов.

· 0×02 — чтение значений из нескольких дискретных регистров.

· 0×03 — чтение из нескольких регистров хранения.

· 0×04 — чтение из нескольких регистров ввода.

Запрос состоит из адреса первого элемента таблицы, значение которого требуется прочитать, и количества считываемых элементов. Адрес и количество данных задаются 16-битными числами, старший байт каждого из них передается первым.

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

· 0×05 — запись одного значения флага.

· 0×06 — запись значения в один регистр хранения.

Команда состоит из адреса элемента (2 байта) и устанавливаемого значения (2 байта). Для регистра хранения значение является просто 16-битным словом. Для флагов значение 0xFF00 означает включённое состояние, 0×0000 — выключенное, другие значения недопустимы. Если команда выполнена успешно, ведомое устройство возвращает копию запроса.

2. Проектирование программы

Реализация протокола modbus состоит из приёма сообщений и последующей их обработки. Для необходимых функций modbus длина пакета постоянна и равна 8. Так как UART поддерживает передачу по одному байту, необходимо ввести счётчик, который будет проверять окончание посылки.

Для того чтобы осуществлять приём и передачу, необходимо инициализировать UART. Для работы с com-портом используется UART1 микроконтроллера, который подсоединяется при помощи интерфейса USB через преобразователь COM-порта. Так как в больших скоростях передачи нет необходимости, достаточно 9600 бит/с. Формат посылки следующий:

· 8 бит данных.

· 1 стоп бит.

· без контроля чётности

Кроме инициализации UART необходимо инициализировать счётчик байтов и CRC, а также разрешить прерывания.

Вся обработка сообщений и отправка ответов производится в обработке прерываний. Поэтому после инициализации системы запускается пустой бесконечный цикл, в котором происходит ожидание прерываний.

Самая важная часть программы находится в блоке обработки прерываний. Рассмотрим его более подробно. При поступлении прерывания от UART в зависимости от номера байта в посылке будут выполняться различные действия. Формат посылки приведён на рисунке 2.1.

Рисунок 2.1 — формат посылки

Где:

· S_ID — ID устройства, 0 — для широковещательной посылки. Если посылка широковещательная, ответ не отправляется. Если ID не равен 0 и не равен ID устройства, посылка не обрабатывается.

· F_ID — ID функцию, которую необходимо выполнить. Если Функция не поддерживается, отправляется исключение.

· SR_HI — старшая часть смещения стартового регистра. В связи с использованием в данной реализации адресов, которые умещаются в 8 бит, не используется.

· SR_LO — младшая часть смещения стартового регистра.

· D_HI — старшая часть информации о данных. Для функций записи это старшая часть числа, которое нужно записать, для функций чтения — старшая часть количества запрашиваемых данных. Так как все адреса данных умещаются в 8 бит, в случае чтения этот байт не используется.

· D_LO — младшая часть информации о данных. Для функций записи это младшая часть числа, которое нужно записать, для функций чтения — младшая часть количества запрашиваемых данных.

· CRC_LO — младшая часть контрольной суммы CRC.

· CRC_HI — старшая часть контрольной суммы CRC.

При поступлении прерывания от UART каждый раз выполняется действие, которое соответствует счётчику байтов. В конце обработки каждого байта, которая заключается в сохранении в памяти переданных данных, необходимо уменьшить счётчик на 1. Если выполняется обработка первых 6-ти байт, также пересчитывается CRC.

Когда счётчик байт дойдёт до 0, будет необходимо выполнить обработку всего запроса. После обработки запроса счётчик байт нужно будет сбросить (установить значение 7) и подготовить CRC к новой посылке (установить значение 0xFFFF).

Рассмотрим более подробно блок обработки запросов. В первую очередь необходимо проверить, направлялся ли данный пакет этому устройству. Если передача была широковещательной, то необходимо установить соответствующий флаг, так как ответ посылается только при уникальном запросе. Если посылка предназначалась другому устройству, она отбрасывается.

После проверки идентификатора устройства, необходимо проверить контрольную сумму. Эта проверка состоит из двух этапов: проверка старшей и младшей части. Это обусловлено тем, что CRC является 16-битным значением, а микроконтроллер AT Mega 128 работает только с 8-ми битными данными.

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

2.1 Чтение флагов

Функция чтения флагов выполняется, если посылка была не широковещательной. При чтении флагов в ответ входят:

· S_ID

· F_ID

· Количество байт данных, которые будут переданы. В данной реализации всегда будет передаваться один байт, так как максимальное число считываемых флагов — 8

· Флаги, которые были считаны по запросу

· CRC

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

2.2 Чтение дискретных входов

Функция чтения дискретных входов аналогична функции чтения данных. Отличие в том, что в данном случае запрашиваются биты регистра статуса. При возникновении исключительной ситуации отправляются те же коды ошибок, что и при чтении флагов.

2.3 Чтение регистров хранения

Функция чтения регистров хранения выполняется только если запрос не был широковещательным. В ответ входят:

· S_ID

· F_ID

· Количество байт. Так как регистры 16-битные, количество байт — количество запрашиваемых регистров, умноженное на 2

· Запрашиваемые регистры

· CRC

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

2.4 Чтение регистров ввода

Функция чтения регистров ввода аналогична функции чтения регистров хранения. Отличие в том, что в данном случае запрашиваются значения РОН микроконтроллера. При этом старшая часть регистра ввода всегда равна 0, а младшая — значению РОН.

2.5 Запись одного флага

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

· FF 00 — установить флаг

· 00 00 — сбросить флаг

В случае попытки обращения к данным вне выделенного диапазона или записи другого значения, отправляется исключение с соответствующим кодом.

2.6 Запись одного регистра хранения

Функция записи одного регистра хранения по структуре такая же, как и функция записи одного флага. Отличие в том, что устанавливается любое 16-битное значение для указанного регистра хранения.

2.7 Исключительная ситуация

Исключительная ситуация может возникнуть, если выполнилось одно из следующих условий:

· Код функции не поддерживается. Код исключения 1.

· Адреса запрашиваемых данных больше адресов предоставляемых. Код исключения 2.

· Значение устанавливаемого флага не корректно. Код исключения 3.

При возникновении исключительной ситуации в случае широковещательного запроса пакет отбрасывается, иначе отправляется пакет, содержащий следующие поля:

· S_ID

· F_ID+0×80 -код функции символизирует о возникновении исключительной ситуации

· Код ошибки

· CRC

2.8 Контрольная сумма

Контрольная сумма считается в процессе приёма или отправки сообщения. В протоколе modbus используется CRC16 с многочленом 0xA001. Перед каждым новым вычислением контрольной суммы необходимо инициализировать её значение, установив его в 0xFFFF.

Блок-схема программы приведена в приложении А.

3. Разработка программы

Для корректной работы программы понадобится ряд переменных и массивов. Все они имеют такие структуру и имена, как описано ниже:

· s_id — идентификатор устройства, является одним байтом оперативной памяти микроконтроллера.

· f_id — идентификатор функции, является одним байтом оперативной памяти.

· startreg — адрес начального регистра или флага. Является двумя байтами оперативной памяти.

· crc — два байта оперативной памяти для хранения принятой контрольной суммы.

· CRCH — старший байт высчитываемой контрольной суммы. Является РОН, специально выделенным для CRC.

· CRCL — младший байт высчитываемой контрольной суммы. Является РОН, специально выделенным для CRC.

· DATACH — старший байт для числа данных в случае чтения, или старший байт данных в случае записи. Является РОН, специально выделенным для хранения данных.

· DATACL — младший байт для числа данных в случае чтения, или младший байт данных в случае записи. Является РОН, специально выделенным для хранения данных.

· BYTENUM — счётчик принятых байтов в посылке. Является специально выделенным РОН.

· TMP — временная переменная. Является РОН.

· COUNTER — счётчик, используется в функции подсчёта CRC. Является РОН.

· MES — регистр, в котором хранится принятый по UART байт, или байт, который необходимо отправить.

· hold_regs — 32 байта оперативной памяти, которые выделены для хранения 16-ти регистров хранения

· coils — 1 байт оперативной памяти, выделенный для хранения 8 флагов.

Программа состоит из нескольких блоков. Все блоки, кроме блока инициализация выполняются в режиме обработки прерывания от UART.

3.1 Инициализация

В блоке инициализация необходимо сделать 2 вещи: подготовить систему к новой посылке и инициализировать UART. Для того, чтобы подготовить систему к новой посылке, необходимо записать в регистры CRCH и CRCL значение 0xFF. Этого требует алгоритм расчёта CRC, и установить значение 7 регистру BYTENUM. Так как посылка имеет длину 8, счётчик будет при поступлении новых байт декрементироваться до 0. Также для корректного возврата из прерываний и подпрограмм необходимо инициализировать указатель стека.

В инициализацию UART входят разрешение приёма, передачи и прерывания по приёму, настройка скорости передачи и формата посылки. Для разрешения приёма, передачи и прерывания по приёму необходимо установить в регистре UCSR1B соответствующие биты. В регистре UCSR1C устанавливаются биты, которые отвечают за формат посылки. Для настройки скорости передачи в регистр UBBR1 записывается значение делителя частоты, который высчитывается по следующей формуле:

bauddivider = XTAL/(16•baudrate)-1;

где bauddivider — значение делителя, XTAL — частота микроконтроллера, baudrate — желаемая скорость передачи. После инициализации UART необходимо глобально разрешить прерывания путём установления бита разрешения прерываний в регистре статуса командой sei. Теперь система находится в состоянии ожидания прерывания, выполняя пустой бесконечный цикл.

3.2 Обработка прерываний

В блоке обработки прерываний считывается пришедшее сообщение путём загрузки содержимого регистра UDR1 в регистр MES, и в зависимости от счётчика BYTENUM выполняется соответствующее действие.

Если BYTENUM=7, значит посылка только началась, необходимо считать S_ID. Для этого содержимое регистра MES помещается в ячейку памяти s_id. Если BYTENUM=6, значит необходимо считать F_ID, переместив содержимое регистра MES в ячейку памяти s_id.

Если BYTENUM больше или равен 2, но меньше 6, значит необходимо считать информацию о данных. В порядке уменьшения регистра BYTENUM будут считываться старшая часть startreg, младшая часть startreg, DATACH и DATACL.

После сохранения в памяти полученного значения вызывается функция getCRC, которая посчитает CRC для каждого нового байта. После подсчёта CRC декрементируется счётчик BYTENUM.

Если BYTENUM равен 1, значит нужно сохранить младшую часть CRC. После сохранения младшей части CRC функция getCRC не вызывается, но счётчик BYTENUM декрементируется.

Если BYTENUM равен 0, значит вся посылка была передана, и после сохранения в памяти старшей части CRC вызывается подпрограмма обработки запроса. После обработки запроса значения BYTENUM, CRCL и CRCH инициализируются для новой посылки.

3.3 Обработка запроса

В первую очередь необходимо проверить, предназначалась ли посылка устройству. Если s_id равен slave_id (slave_id — id данного устройства, объявлено директивой .def), то после обработки запроса отправится ответ. Если s_id равен 0, то устанавливается флаг широковещательной передачи. В другом случае посылка не обрабатывается, так как предназначалась другому устройству.

После проверки s_id сравниваются контрольные суммы, и если они не совпадают, посылка отбрасывается.

Если CRC совпадает, то происходит загрузка функции из памяти. Если необходимо выполнить функцию с кодом более 6ти, отправляется исключение. Если код функции корректный, выполняется действие, соответствующее коду. Проверка осуществляется путём цепочки условных переходов. В R17 загружается 1, в R16 — код функции. Команда cpse сравнивает эти регистры, и в случае равенства переходит по адресу обработчика функции чтения флагов. Если регистры не равны, выполняется следующая инструкция, которая осуществляет безусловный переход на следующий этап. На следующем этапе регистр R17 инкрементируется и происходит то же, что и на предыдущем этапе, только функция на каждом шаге будет своя. Была организована именно такая система переходов, так как безусловные переходы передают управление на большие расстояния. Если ни один из переходов по адресу обработки функции не был совершён, выполняется функция с кодом 7.

3.4 Чтение флагов

В первую очередь функция чтения флагов проверяет, была ли посылка широковещательной, и если была, то функция не выполняется. После этого производится проверка значений startreg и startreg+DATACL. Если одно из этих значений превосходит 8, то отправляется исключение.

Если исключения не произошло, то происходит отправка кода устройства, кода функции и количества запрашиваемых данных. Перед отправкой байта считается его CRC. Количество запрашиваемых данных всегда равно 1, так как для флагов выделен всего один байт.

Так как функции чтения флагов и чтения дискретных регистров различаются лишь тем, что флаги читаются из переменной coils, а дискретные регистры — из регистра статуса, для обработки обоих запросов используется одна и та же функция.

Если происходит запрос на чтение флагов, в регистровую пару Z загружается смещение переменной coils, и содержимое переменной загружается в регистр MES. Если читаются дискретные регистры, вызывается функция get_status_reg, которая поместит содержимое регистра статуса в регистр MES.

После загрузки флагов в регистр MES необходимо логически сдвинуть его вправо на значение startreg-1, так как нумерация адресов в протоколе modbus начинается с одного. В регистр R16 загружен startreg-1, и сдвиг происходит, пока R16 не равен нулю. Если startreg равен единице, сдвига не происходит. После сдвига регистра MES необходимо наложить на него маску, которая бы сбросила все флаги, которые не были запрошены. Число запрашиваемых флагов находится в регистре DATACL, для наложения маски вызывается функция get_mask. После наложения маски на регистр MES выполняется его отправка и расчёт контрольной суммы. После этого отправляется рассчитанное значение CRC.

Функция get_status_reg выполняет восемь проверок флагов микроконтроллера, начиная со старшего. Если бит установлен, то регистр MES сдвигается влево и инкрементируется, иначе просто сдвигается влево. Таким образом все установленные биты будут единицами в регистре MES, с сброшенные — нулями.

Функция get_mask генерирует маску «И» на первые DATACL бит. Результат помещается в регистр R25. Первые DATACL бит маски должны быть равны единицы, остальные — нулями. Для этого в регистр R25 изначально записывается 0. После этого DATACL помещается в стек, и в цикле, пока DATACL не равно нулю R25 сдвигается влево и инкрементируется. После цикла DATACL извлекается из стека.

3.5 Чтение регистров хранения

В первую очередь функция чтения регистров проверяет, была ли посылка широковещательной, и если была, то функция не выполняется. После этого производится проверка значений startreg и startreg+DATACL. Если одно из этих значений превосходит 16, то отправляется исключение.

Если исключения не произошло, то происходит отправка кода устройства, кода функции и количества запрашиваемых данных. Перед отправкой байта считается его CRC. Количество запрашиваемых данных отправляется в байтах, поэтому оно равно удвоенному числу запрашиваемых регистров.

Далее организован цикл отправки регистров. Она выполняется до тех пор, пока значение счётчика отправленных регистров не дойдёт до нуля. В цикле сначала читается и отправляется старший байт, потом указатель на ячейку памяти инкрементируется и отправляется младший байт.

После цикла отправляется посчитанная контрольная сумма.

3.6 Чтение регистров ввода

Функция чтения регистров ввода очень похожа на функцию чтения регистров хранения и отличается лишь тем, что для чтения доступно 32 регистра, а при отправке значений старшая часть всегда равна нулю, а младшая — содержимому запрашиваемого РОН. Структура функции такая же, как и при чтении регистров хранения.

3.7 Запись флага

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

После проверки адреса выполняется проверка значения флага. Если записываемое значение не равно 0xFF00 или 0×0000, то также отправляется исключение.

Если адрес и значение записываемого флага корректно, то в регистровую пару Z загружается смещение переменной coils, и содержимое этой переменной загружается в регистр R17.

Далее, необходимо либо установить бит под номером startreg-1, либо его сбросить. Для обоих случаев используется маска, которая генерируется для startreg и записывается в регистр R18. В регистре R18 после работы функции bit_to_mask установлен только один бит под номером startreg-1.

Функция bit_to_mask сначала записывает в R18 значение 1. Далее она сдвигает единицу startreg-1 раз, таким образом получая в регистре R18 необходимую маску.

Если бит нужно установить, то с помощью логического «или» регистров R17 и R18 заданный бит устанавливается в единицу. Если бит необходимо сбросить, то происходит инверсия R18 с последующим логическим «и» с регистром R17.

После установки или сброса бита происходит отправка ответа, если запрос не был широковещательным. Ответ полностью копирует запрос и отправляется функцией send_echo.

Функция send_echo последовательно отправляет данные в том же порядке, в котором она их принимала. Контрольная сумма в данном случае не считается, так как она будет одинаковой для принятого и отправляемого пакета.

3.8 Запись регистра

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

Значение смещения стартового регистра записывается в регистр R16. После успешной проверки адреса регистр R16 удваивается, так как необходимо работать с 16-битными регистрами. Адрес переменной hold_regs записывается в регистровую пару Z, после чего к ней добавляется смещение, записанное в регистре R16.

После вычисления адреса необходимого регистра в памяти, происходит запись старшего байта, инкремент адреса и запись младшего байта. После записи в память выполняется проверка флага широковещательной передачи и отправка ответа, если этот флаг не установлен.

3.9 Обработка исключительных ситуаций

В данной реализации предусмотрены следующие типы исключительных ситуаций:

· функция не поддерживается.

· запрашиваемый адрес не доступен.

· Значение флага не корректно.

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

Функция обработки исключительной ситуации проверяет флаг широковещательной передачи и отправляет данные только в том случае, если этот флаг не установлен.

После проверки флага функция инициализирует значение CRC, после чего отправляет последовательно код устройства, код функции, которая вызвала ошибку (для этого к коду функции прибавляется 0×80), код ошибки и контрольную сумму пакета. На этом обработка пакета завершается.

3.10 Контрольная сумма

Процедура подсчёта контрольной суммы вызывается каждый раз при отправке нового байта. Контрольная сумма рассчитывается в соответствии с алгоритмом CRC16 и полиномом 0xA001. Этот алгоритм и полином приняты в протоколе modbus для серийных линий.

Алгоритм заключается в следующем: в начале обработки нового байта происходит «исключающее или» с уже имеющимся значением CRC. После этого происходит 8 сдвигов значения CRC влево, при этом если выталкивается «1», то происходит исключающее или с полиномом 0xA001.

Код всей программы с комментариями приведён в приложении Б.

4. Тестирование программы

Для проверки правильной работы программы необходимо было создать такой набор тестов, который позволил бы проверить все функции с различными условиями (проверка широковещательной передачи, проверка обработки исключений). Для отправки сообщений через com-порт использовалась программа SerialNetTools. Для подсчёта контрольной суммы была написана программа.

Функция чтения дискретных входов:

Для проверки функции чтения дискретных вводов было создано 2 теста, первый запрашивает на чтение весь регистр флагов, второй — запрашивает лишние биты. В первом случае функция возвращает состояние регистра флагов, во втором — исключение. Результаты тестирование приведены на рисунке 4.1.

Рисунок 4.1 — функции чтения дискретных вводов

Функция чтения регистров ввода:

Для проверки функции чтения регистров ввода также было направлено 2 теста. Первый запрашивает старшие десять РОН микроконтроллера, второй — запрашивает недопустимые значения. В ответ на первый тест приходит содержимое старших десяти РОН, на второй — исключение. Результаты тестирования приведены на рисунке 4.2.

Рисунок 4.2 — чтение регистров ввода

Функция записи регистров хранения:

Для проверки функции записи дискретных регистров было создано четыре теста:

1. обычная запись значения регистра хранения. В ответ на этот запрос приходит его копия.

2. широковещательная запись регистра хранения. В этом случае ответ не приходит.

3. запись регистра хранения устройству с другим кодом. В этом случае ответ также не приходит.

4. запись регистра хранения по недопустимому адресу. В этом случае в ответ приходит исключение.

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

Функция чтения регистров хранения:

Для проверки функции чтения регистров хранения было создано 2 теста: первый читает первые 15 регистров, второй читает регистры по недопустимому адресу. В случае корректного чтения для регистров, чьи значения были записаны, совпадают. Стоит заметить, что значение, которое предназначалось другому устройству, записано не было. В случае чтения недопустимого адреса возвращается исключение. Результаты тестирования функций чтения и записи регистров хранения представлены на рисунке 4.3.

Рисунок 4.3 — функции чтения и записи регистров хранения

Функция записи флагов:

Для функции записи флагов было разработано следующие четыре теста:

1. установить третий флаг. Ответом на этот запрос приходит его копия.

2. установить пятый флаг всем устройствам. Ответ на этот запрос не приходит.

3. Сбросить третий флаг устройству с другим кодом. Ответ на этот запрос не приходит.

4. Записать в четвёртый флаг некорректное значение. В ответ на этот запрос приходит исключение.

Для полной проверки корректности функции записи необходимо провести чтение флагов.

Функция чтения флагов:

Для проверки функции чтения флагов было написано два теста: первый читает все флаги, второй читает флаги по недопустимому адресу. В первом случае возвращается один байт данных, в котором установлены третий и пятый бит, что говорит о том, что при попытке сбросить третий флаг для другого устройства программа отбросила пакет. Во втором случае возвращается исключение. Результаты тестирования функций чтения и записи флагов представлены на рисунке 4.4.

Рисунок 4.4 — функции чтения и записи флагов

На этапе отладки и тестирования были найдены и исправлены все ошибки, которые позволил выявить данный набор тестов.

Заключение

В результате проделанной работы была спроектирована и разработана программа, которая реализует основные функции протокола modbus для микроконтроллеров семейства AVR.

Основная задача программы — принимать сообщения и отправлять ответы в соответствии с запросами, выполнена. Так как скорость передачи по UART значительно меньше частоты контроллера, все принятые байты успевают обрабатываться до того, как будут приняты новые. Это говорит о высокой производительности системы.

К недостаткам системы можно отнести ограниченный набор функций и малый объём выделенной для хранения данных памяти. Также во время отправки ответа программа теряет много времени на ожидание готовности передатчика.

Список использованных источников

1. Ревич Ю. В. Практическое программирование микроконтроллеров Atmel AVR на языке ассемблера. — СПб.: БХВ-Петербург, 2008. — 384с.

2. Евстифеев А. В. микроконтроллеры AVR семейства Tiny и Mega фирмы «Atmel» — М.: Издательский дом «Додэка-XXI», 2004. — 560с.

3. AVR. Учебный курс. Передача данных через UART // www.easyelectronics.ru. — http://easyelectronics.ru/avr-uchebnyj-kurs-peredacha-dannyx-cherez-uart.html.

4. Modbus Specification and implementation guides // www.modbus.org. — http://modbus.org/specs.php.

Приложение А

Блок-схема инициализации программы:

Блок-схема обработки принятого байта:

Блок-схема функции обработки запроса:

Блок-схема функции чтения флагов:

Блок-схема функции чтения регистров:

Блок-схема функции записи флага:

Блок-схема функции записи регистра:

клиент протокол сервер запрос

Приложение Б

Листинг кода программы:

.include

.dseg

s_id: .byte 1 ;1 байт для хранения id устройства

f_id: .byte 1 ;1 байт для хранения функции

startreg: .byte 2 ;2 байта для хранения регистра начального адреса

hold_regs: .byte 32 ;32 байта для хранения holding registers. 16 регистров по 2 байта

coils: .byte 1 ;2 байта для хранения coils. 16 флагов по одному биту

crc: .byte 2 ;2 байта для хранения crc, которая была передана по UART

.equ XTAL = 7 372 800 ;частота контроллера

.equ baudrate = 9600 ;необходимая скорость

.equ bauddivider = XTAL/(16*baudrate)-1 ;значение предделителя UART

.equ slave_id = 1 ;адрес устройства

.def CRCH=R29 ;старший байт CRC, которая вычисляется для посылки

.def CRCL=R28 ;младший байт CRC, которая вычисляется для посылки

.def DataCH=R27 ;старший байт для числа данных в случае чтения, или самих данных в случае записи

.def DataCL=R26 ;младший байт для числа данных в случае чтения, или самих данных в случае записи

.def BYTENUM=R25 ;число байт в пакете, которые осталось передать

.def TMP=R24 ;временная переменная

.def COUNTER=R23; ;счётчик. временная переменная, используется в функции getCRC

.def MES=R22 ;сообщение, которое было передано по UART

.cseg

.org 0x0

rjmp Main

Main: ;инициализиация

ldi CRCH, 0

sts coils, CRCH

ldi CRCH, 0xFF

ldi CRCL, 0xFF

ldi BYTENUM, 7

ldi R16,0x0F

out SPH, R16

ldi r16,(1<<<

sts UCSR1B, r16

ldi r16,$ 06

sts UCSR1C, r16

ldi R16, low (bauddivider)

sts UBRR1L, r16

ldi R16, high (bauddivider)

sts UBRR1H, r16

sei

inf_loop:

;ожидание прерывания

rjmp inf_loop

.org 0x3C

rjmp InterHandler

InterHandler:

lds MES, UDR1

cpi BYTENUM, 7

breq getID ;если посылка только началась, то нужно сравнить ID

cpi BYTENUM, 6

breq getFID ;получить ID функции

cpi BYTENUM, 2

brsh getDataInf; байты 2−5 — инфо о данных

cpi BYTENUM, 1

breq getLoCRC; байт 1 — младшая часть CRC

getHiCRC:; если управление здесь, значит происходит чтение 0-го байт. старшая часть CRC, выполнение операций

sts crc+1,MES

rcall perform

ldi BYTENUM, 7 ;новая посылка

ldi CRCH,$FF ;подготовка CRC к новой посылке

ldi CRCL,$FF

reti

end_handle:

cpi BYTENUM, 2; считаем CRC для битов 7−2

BRLO noCRC

call getCRC

noCRC:

dec BYTENUM

reti

getID:

sts s_id, MES

rjmp end_handle

getFID:

sts f_id, MES

rjmp end_handle

getLoCRC:

sts crc, MES

rjmp end_handle

getDataInf:

byte5:

cpi bytenum, 5

brne byte4

sts startreg+1,mes

rjmp end_handle

byte4:

cpi bytenum, 4

brne byte3

sts startreg, mes

rjmp end_handle

byte3:

cpi bytenum, 3

brne byte2

mov DataCH, mes

rjmp end_handle

byte2:

mov DataCL, mes

rjmp end_handle

perform:

;процедура обработки запроса и отправки ответа

;свободны регистры 16−25

;R25 — код ошибки

;R21 — флаг broadcast

;R22 — MES, используется для передачи

lds R16, s_id ;считываем id

ldi R21,0 ;записываем 0 в флаг броадкаст. если R21<>0, значит посылка броадкастовая

cpi R16, slave_id ;сравниваем id с id устройства

breq unicast_request ;если равны, значит посылка unicast

cpi R16,0 ;сравниваем id с нулём (broadcast)

brne perform_end ;если не равны, значит выходим из процедуры

ldi R21,0xFF ;если равны, значит посылка broadcast, выставляем флаг

unicast_request:

lds R16, crc ;проверка crc

cpse R16, CRCL ;младшая часть

rjmp perform_end

lds R16, crc+1 ;старшая часть

cpse R16, CRCH

rjmp perform_end ;если где-то не совпало, значит в пакете ошибка

lds R16, f_id ;загружаем id функции

cpi R16,7

brsh wrong_func ;поддерживаются только первые 6 функций. для других случаев отправляем исключение

;в зависимости от функции выполняем действия

;чтобы заменить условные переходы на безусловные, вводим конструкцию:

ldi R17,1

cpse R17, R16

rjmp no1

rjmp read_coils

no1:

inc R17

cpse R17, R16

rjmp no2

rjmp read_coils

no2:

inc R17

cpse R17, R16

rjmp no3

rjmp read_hold_regs

no3:

inc R17

cpse R17, R16

rjmp no4

rjmp read_input_regs

no4:

inc R17

cpse R17, R16

rjmp write_hold_reg

rjmp write_coil

write_hold_reg:

lds R16, startreg ;считываем стартовый регистр

dec R16 ;уменьшаем на 1, так как в modbus данные передаются начиная с 1

cpi R16,16 ;16 — кол-во данных

brsh out_of_range ;если считать нужно больше, чем есть, отправляем исключение

add R16, R16 ;умножаем на 2, так как регистры 16-битные

ldi ZL, low (hold_regs)

ldi ZH, high (hold_regs)

add ZL, R16 ;загружаем в Z смещение запрашиваемых данных

ldi R16,0

adc ZH, R16 ;если произошёл перенос, необходимо это учесть

st Z, DATACH ;данные хранятся в формате СтаршийБайт: Младший

adiw Z, 1 ;запись в следующий байт

st Z, DATACL

cpi R21,0xFF ;если не стоит флаг броадкаст, посылаем ответ

breq perform_end

rjmp send_echo

perform_end:

ret

wrong_func:

;отправить исключение с кодом 1. код хранится в r25

ldi r25,1

rjmp exception

out_of_range:

;отправить исключение с кодом 2. код хранится в r25

ldi r25,2

rjmp exception

wrong_value:

;отправить ексепшн с кодом 3. код хранится в r25

ldi r25,3

rjmp exception

read_coils: ;функция для чтения coils или discrete input

cpi R21,0xFF ;если не стоит флаг броадкаст, посылаем ответ

breq perform_end

lds R16, startreg

dec R16

add R16, DATACL

cpi R16,9 ;8 — кол-во данных

brsh out_of_range

sub R16, DATACL

lds MES, s_id ;отправка s_id и f_id

ldi CRCL,$FF

ldi CRCH,$FF

rcall getCRC

rcall send_byte

lds MES, f_id

rcall getCRC

rcall send_byte

ldi MES, 1 ;посылаем 1 байт

rcall getCRC

rcall send_byte

lds MES, f_id

cpi MES, 1

breq f_coils

ldi MES, 0

rcall get_status_reg

rjmp no_coils

f_coils:

ldi ZL, low (coils)

ldi ZH, high (coils)

ld MES, Z

no_coils:

cpi R16,0

breq no_lsr

lsr_loop:

lsr MES

dec R16

cpi R16,0

brne lsr_loop

no_lsr: ;теперь нужно наложить маску и отправить DATA и CRC

rcall get_mask

and MES, R25

rcall getCRC

rcall send_byte

mov MES, CRCL

rcall send_byte

mov MES, CRCH

rcall send_byte

rjmp perform_end

wrong_value_copy:

rjmp wrong_value

out_of_range_copy:

rjmp wrong_value

perform_end_copy:

rjmp perform_end

read_hold_regs:

cpi R21,0xFF ;если не стоит флаг броадкаст, посылаем ответ

breq perform_end_copy

lds R16, startreg

dec R16

cpi R16,16

brsh out_of_range_copy

add R16, DATACL

cpi R16,17

brsh out_of_range_copy

sub R16, DATACL

add R16, R16

ldi ZL, low (hold_regs)

ldi ZH, high (hold_regs)

add ZL, R16

ldi R17,0

adc ZH, R17

lds MES, s_id

ldi CRCL,$FF

ldi CRCH,$FF

rcall getCRC

rcall send_byte

lds MES, f_id

rcall getCRC

rcall send_byte

mov MES, DATACL

add MES, MES ;отправляем удвоенное число байт

rcall getCRC

rcall send_byte

mov R17, DATACL

send_hold_loop:

ld MES, Z+ ;сначала старший байт.

ld R16, Z+ ;потом младший

rcall send_byte

rcall getCRC

mov MES, R16

rcall send_byte

rcall getCRC

dec R17

cpi R17,0

brne send_hold_loop

mov MES, CRCL

rcall send_byte

mov MES, CRCH

rcall send_byte

rjmp perform_end

read_input_regs:

cpi R21,0xFF ;если не стоит флаг броадкаст, посылаем ответ

breq perform_end_copy

lds R16, startreg

dec R16

cpi R16,32

brsh out_of_range_copy

add R16, DATACL

cpi R16,33

brsh out_of_range_copy

sub R16, DATACL

mov ZL, R16

ldi ZH, 0

lds MES, s_id

ldi CRCL,$FF

ldi CRCH,$FF

rcall getCRC

rcall send_byte

lds MES, f_id

rcall getCRC

rcall send_byte

mov MES, DATACL

add MES, MES ;отправляем удвоенное число байт

rcall getCRC

rcall send_byte

mov R17, DATACL

send_input_loop:

ldi MES, 0 ;сначала старший байт.

ld R16, Z+ ;потом младший

rcall send_byte

rcall getCRC

mov MES, R16

rcall send_byte

rcall getCRC

dec R17

cpi R17,0

brne send_input_loop

mov MES, CRCL

rcall send_byte

mov MES, CRCH

rcall send_byte

rjmp perform_end

write_coil:

lds R16, startreg

dec R16

cpi R16,8 ;8 — кол-во данных

brsh out_of_range_copy2

cpi DATACL, 0

brne wrong_value_copy2

cpse DATACH, DATACL

cpi DATACH, 0xFF

brne wrong_value_copy2 ;флаг Z установится, если DATACH=DATACL=0, или если DATACH=0xFF, DATACL=0

ldi ZL, low (coils)

ldi ZH, high (coils)

ld R17, Z

rcall bit_to_mask

cpi DATACH, 0xFF

breq set_bit

com R18

and R17, R18

st Z, R17

rjmp send_echo

set_bit:

or R17, R18

st Z, R17

sbrc R21,0 ;если не стоит флаг броадкаст, посылаем ответ

rjmp perform_end

rjmp send_echo

out_of_range_copy2:

rjmp out_of_range

wrong_value_copy2:

rjmp wrong_value

send_echo:

lds MES, s_id

rcall send_byte

lds MES, f_id

rcall send_byte

lds MES, startreg+1

rcall send_byte

lds MES, startreg

rcall send_byte

mov MES, DATACH

rcall send_byte

mov MES, DATACL

rcall send_byte

mov MES, CRCL

rcall send_byte

mov MES, CRCH

rcall send_byte

rjmp perform_end

send_byte:

lds TMP, UCSR1A

sbrs TMP, UDRE1

rjmp send_byte

sts UDR1, MES ;MES — буфер

ret

exception:

sbrc R21,0 ;если не стоит флаг броадкаст, посылаем ответ

rjmp perform_end

ldi CRCH,$FF

ldi CRCL,$FF

lds MES, s_id

rcall getCRC

rcall send_byte

lds MES, f_id

ldi r17,$ 80

add MES, r17

rcall getCRC

rcall send_byte

mov MES, r25 ;r25 — код ошибки

rcall getCRC

rcall send_byte

mov MES, CRCL

rcall send_byte

mov MES, CRCH

rcall send_byte

rjmp perform_end

getCRC:

LDI TMP, 0

EOR CRCH, TMP

EOR CRCL, MES

LDI COUNTER, 8

crcloop:

bst CRCL, 0

brtc noxor

LSR CRCL

bst CRCH, 0

bld CRCL, 7

lsr CRCH

ldi tmp, 1

eor CRCL, tmp

ldi tmp,$A0

eor CRCH, tmp

dec COUNTER

brne crcloop

ret

noxor:

LSR CRCL

bst CRCH, 0

bld CRCL, 7

lsr CRCH

dec COUNTER

brne crcloop

ret

bit_to_mask:

;R16 — регистр, который нужно преобразовать

;результат в R18

cpi R16,0

ldi R18,1

breq end_mask

cycle_mask:

lsl R18

dec R16

cpi R16,0

brne cycle_mask

end_mask:

ret

get_mask:

;нужно получить маску AND для первых DATACL бит

;результат в R25, ошибки уже не возникнет

push DATACL

ldi R25,0

cpi DATACL, 0

breq mask_end

mask_loop:

lsl R25

inc R25

dec DATACL

cpi DATACL, 0

brne mask_loop

mask_end:

pop DATACL

ret

get_status_reg:

brbc 7, no_inter

inc MES

no_inter:

lsl MES

brbc 6, no_temp

inc MES

no_temp:

lsl MES

brbc 5, no_half

inc MES

no_half:

lsl MES

brbc 4, no_sign

inc MES

no_sign:

lsl MES

brbc 3, no_over

inc MES

no_over:

lsl MES

brbc 2, no_neg

inc MES

no_neg:

lsl MES

brbc 1, no_zero

inc MES

no_Zero:

lsl MES

brbc 0, no_carry

inc MES

no_carry:

ret

Показать весь текст
Заполнить форму текущей работой