2269.Учебная работа .Тема:Внутренний аудит учета материальнопроизводственных запасов

1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (5 оценок, среднее: 4,60 из 5)
Загрузка...

Тема:Внутренний аудит учета материальнопроизводственных запасов»,»

Федеральное агентство по образованию

Государственное образовательное учреждение высшего профессионального образования

ГОУ ВПО ЮГОРСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ

Институт Систем Управления и Информационных Технологий

Кафедра «Автоматизированные системы обработки информации и управления»

ПОЯСНИТЕЛЬНАЯ ЗАПИСКА К КУРСОВОЙ РАБОТЕ

по дисциплине: «Языки программирования низкого уровня»

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

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

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

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

Шифр: 117010

Проверил: преподаватель

С.Н. Горбунов

ХантыМансийск, 2011

Федеральное агентство по образованию

Государственное образовательное учреждение высшего профессионального образования

ЮГОРСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ

Институт Систем Управления и Информационных Технологий

Кафедра «Автоматизированные системы обработки информации и управления»

ЗАДАНИЕ

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

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

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

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

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

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

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

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

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

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

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

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

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

Содержание

Введение

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

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

.1 Чтение флагов

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

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

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

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

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

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

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

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

.1 Инициализация

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

.3 Обработка запроса

.4 Чтение флагов

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

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

.7 Запись флага

.8 Запись регистра

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

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

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

Заключение

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

Приложение А

Приложение Б

Введение

Целью работы является проектирование и реализация протокола Modbus для микроконтроллера семейства AVR. коммуникационный протокол, основанный на архитектуре «клиентсервер». Широко применяется в промышленности для организации связи между электронными устройствами. Может использовать для передачи данных последовательные линии связи RS485, RS422, RS232, а также сети TCP/IP (Modbus TCP).

Существует множество решений для реализации Modbus протокола в качестве slave устройства. Представленное решение отличается простотой и наглядностью, при этом выполняются основные функции протокола.

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

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

Существует три основных реализации протокола Modbus, две для передачи данных по последовательным линиям связи, как медным EIA/TIA232E (RS232), EIA422, EIA/TIA485A (RS485), так и оптическим и радио:

·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:

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

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

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

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

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

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

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

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

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

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+0x80 код функции символизирует о возникновении исключительной ситуации

·Код ошибки

·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 записывается значение делителя частоты, который высчитывается по следующей формуле:

= 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 необходимо логически сдвинуть его вправо на значение startreg1, так как нумерация адресов в протоколе modbus начинается с одного. В регистр R16 загружен startreg1, и сдвиг происходит, пока 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 или 0x0000, то также отправляется исключение.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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.широковещательная запись регистра хранения. В этом случае ответ не приходит.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Для проверки функции чтения флагов было написано два теста: первый читает все флаги, второй читает флаги по недопустимому адресу. В первом случае возвращается один байт данных, в котором установлены третий и пятый бит, что говорит о том, что при попытке сбросить третий флаг для другого устройства программа отбросила пакет. Во втором случае возвращается исключение. Результаты тестирования функций чтения и записи флагов представлены на рисунке 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. <#»»left»»>Приложение А

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

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

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

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

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

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

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

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

Приложение Б

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

.include <m128def.inc>

.dseg_id: .byte 1;1 байт для хранения id устройства_id: .byte 1;1 байт для хранения функции: .byte 2;2 байта для хранения регистра начального адреса

hold_regs: .byte 32;32 байта для хранения holding registers. 16 регистров по 2 байта: .byte 1;2 байта для хранения coils. 16 флагов по одному биту: .byte 2;2 байта для хранения crc, которая была передана по UART

.equ XTAL = 7372800;частота контроллера

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

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

.equslave_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 0x0Main

:;инициализиацияCRCH,0coils,CRCHCRCH,0xFFCRCL,0xFFBYTENUM,7R16,0x0FSPH,R16r16,(1<<RXEN1)|(1<<TXEN1)|(1<<RXCIE1)UCSR1B,r16r16,$06UCSR1C,r16R16,low(bauddivider)UBRR1L,r16R16,high(bauddivider)UBRR1H,r16_loop:

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

.org 0x3C

rjmp InterHandler

:MES,UDR1

cpi BYTENUM,7getID;если посылка только началась, то нужно сравнить IDBYTENUM,6getFID;получить ID функцииBYTENUM,2getDataInf; байты 25 инфо о данныхBYTENUM,1getLoCRC; байт 1 младшая часть CRC:; если управление здесь, значит происходит чтение 0го байт. старшая часть CRC, выполнение операций

sts crc+1,MESperformBYTENUM,7;новая посылка

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

ldi CRCL,$FF_handle:

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

BRLO noCRCgetCRC:BYTENUM

:s_id,MESend_handle

:f_id,MESend_handle

:crc,MESend_handle

:_5:bytenum,5byte_4startreg+1,mesend_handle_4:bytenum,4byte_3startreg,mesend_handle_3:bytenum,3byte_2DataCH,mesend_handle_2:DataCL,mes end_handle

:

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

;свободны регистры 1625

;R25 код ошибки

;R21 флаг broadcast

;R22 MES, используется для передачиR16,s_id;считываем idR21,0;записываем 0 в флаг броадкаст. если R21<>0, значит посылка броадкастоваяR16,slave_id;сравниваем id с id устройстваunicast_request;если равны, значит посылка unicastR16,0;сравниваем id с нулём (broadcast)perform_end;если не равны, значит выходим из процедурыR21,0xFF;если равны, значит посылка broadcast, выставляем флаг

unicast_request:R16,crc;проверка crcR16,CRCL;младшая частьperform_endR16,crc+1;старшая часть R16,CRCHperform_end;если гдето не совпало, значит в пакете ошибкаR16,f_id;загружаем id функцииR16,7wrong_func;поддерживаются только первые 6 функций. для других случаев отправляем исключение

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

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

ldi R17,1R17,R16no_1read_coils_1:R17R17,R16no_2read_coils_2:R17R17,R16no_3read_hold_regs_3:R17R17,R16no_4read_input_regs_4:R17R17,R16write_hold_regwrite_coil_hold_reg:

lds R16,startreg;считываем стартовый регистрR16;уменьшаем на 1, так как в modbus данные передаются начиная с 1R16,16;16 колво данныхout_of_range;если считать нужно больше, чем есть, отправляем исключениеR16,R16;умножаем на 2, так как регистры 16битные

ldi ZL,low(hold_regs)ZH,high(hold_regs)

add ZL,R16;загружаем в Z смещение запрашиваемых данныхR16,0ZH,R16;если произошёл перенос, необходимо это учестьZ,DATACH;данные хранятся в формате СтаршийБайт:МладшийZ,1;запись в следующий байтZ,DATACLR21,0xFF;если не стоит флаг броадкаст, посылаем ответ

breq perform_endsend_echo_end:

_func:

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

ldi r25,1exception

_of_range:

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

ldi r25,2exception

_value:

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

ldi r25,3exception

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

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

breq perform_end

R16,startregR16R16,DATACLR16,9;8 колво данныхout_of_rangeR16,DATACL

MES,s_id;отправка s_id и f_idCRCL,$FFCRCH,$FFgetCRCsend_byteMES,f_idgetCRCsend_byteMES,1;посылаем 1 байтgetCRCsend_byteMES,f_idMES,1f_coilsMES,0get_status_regno_coils_coils:ZL,low(coils)ZH,high(coils)MES,Z_coils:R16,0no_lsr_loop:MESR16

cpi R16,0lsr_loop_lsr:;теперь нужно наложить маску и отправить DATA и CRC

rcall get_maskMES,R25getCRCsend_byteMES,CRCLsend_byteMES,CRCHsend_byteperform_end_value_copy:wrong_value

_of_range_copy:wrong_value

_end_copy:perform_end

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

breq perform_end_copyR16,startregR16R16,16out_of_range_copyR16,DATACLR16,17out_of_range_copyR16,DATACLR16,R16

ZL,low(hold_regs)ZH,high(hold_regs)ZL,R16R17,0ZH,R17

MES,s_idCRCL,$FFCRCH,$FFgetCRCsend_byteMES,f_idgetCRCsend_byteMES,DATACL

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

rcall getCRCsend_byte

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

ld R16,Z+;потом младшийsend_bytegetCRCMES,R16send_bytegetCRCR17R17,0send_hold_loopMES,CRCLsend_byteMES,CRCHsend_byteperform_end

_input_regs:

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

breq perform_end_copyR16,startregR16R16,32out_of_range_copyR16,DATACLR16,33out_of_range_copyR16,DATACL

ZL,R16ZH,0

MES,s_idCRCL,$FFCRCH,$FFgetCRCsend_byteMES,f_idgetCRCsend_byteMES,DATACL

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

rcall getCRCsend_byte

R17,DATACL

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

ld R16,Z+;потом младшийsend_bytegetCRCMES,R16send_bytegetCRCR17R17,0send_input_loopMES,CRCLsend_byteMES,CRCHsend_byteperform_end

_coil:R16,startregR16R16,8;8 колво данныхout_of_range_copy2DATACL,0wrong_value_copy2DATACH,DATACLDATACH,0xFFwrong_value_copy2;флаг Z установится, если DATACH=DATACL=0, или если DATACH=0xFF, DATACL=0ZL,low(coils)ZH,high(coils)R17,Zbit_to_maskDATACH,0xFFset_bitR18R17,R18Z,R17send_echo_bit:R17,R18Z,R17

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

rjmp perform_endsend_echo

_of_range_copy2:out_of_range_value_copy2:wrong_value

_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_byteperform_end

_byte:TMP,UCSR1ATMP,UDRE1send_byteUDR1,MES;MES буфер

ret

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

rjmp perform_endCRCH,$FFCRCL,$FFMES,s_idgetCRCsend_byteMES,f_idr17,$80MES,r17getCRCsend_byteMES,r25;r25 код ошибкиgetCRCsend_byteMES,CRCLsend_byteMES,CRCHsend_byteperform_end

:

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

noxor:

LSR CRCL

bst CRCH,0

bld CRCL,7

lsr CRCH

dec COUNTER

brne crcloop

_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

_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

_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: