Вы находитесь на странице: 1из 88

Оглавление

Общие сведения .................................................................................................................................................... 2


Лабораторная работа вводная. Язык Си ............................................................................................................. 6
Лабораторная работа №1. Управление портами ввода/вывода ....................................................................... 7
Лабораторная работа №2. Подключение кнопки ............................................................................................ 10
Лабораторная работа №3. Таймер .................................................................................................................... 12
Лабораторная работа №4. Таймер в режиме ШИМ ........................................................................................ 17
Лабораторная работа №5. АЦП ........................................................................................................................ 21
Лабораторная работа №6. Последовательный интерфейс.............................................................................. 24
Лабораторная работа №7. Динамическая индикация ..................................................................................... 29
Лабораторная работа №8. Матричная клавиатура .......................................................................................... 36
Лабораторная работа №9. Жидкокристаллический индикатор ..................................................................... 43
Лабораторная работа №10. Интерфейс 1-Wire ................................................................................................ 52
Лабораторная работа №11. Инкрементный энкодер ....................................................................................... 62
Лабораторная работа №12. Интерфейс I2C ..................................................................................................... 67
Лабораторная работа №13. Интерфейс SPI...................................................................................................... 79

1
Общие сведения
AVR Studio - это среда разработки и отладки для микроконтроллеров семейства AVR. Эта сре-
да предлагает интерфейс программного симулятора (имитатора) и внутрисхемного эмулятора AVR
RISC. Кроме того, AVR Studio поддерживает набор разработчика STK500, позволяющий программиро-
вать AVR- устройства, а также новый встроенный эмулятор JTAG.

После установки AVR Studio на рабочем столе появится ярлык или .


Необходимо будет открыть AVR Studio (далее будет описан интерфейс для AVR Studio 4.0).

В открывшемся окне необходимо будет выбрать в главном меню Project►New Project. Появит-
ся окно создания нового проекта:

7
1. В списке «Project type» выбираем тип проекта «AVR GCC».
2. Проверяем, чтобы папка проекта «Location» была установлена как «D:\DDMP» или «C:\DDMP».
Если выбирается другая папка, проверьте чтобы в пути к папке и в имени папки не было пробе-
лов и русских букв.
3. В поле «Project Name» вводим имя проекта, например, «lab1».
4. Нажимаем кнопку «Next».
Появится следующее окно:

2
1. В нём выбираем в списке «Debug platform» - «AVR simulator»
2. Затем в списке «Device» выбираем тип процессора в проекте – «ATmega64»
3. Нажимаем кнопку «Finish».
Основное окно программы примет вид:

По центру располагается окно, где необходимо будет писать исполняемый код.


Пример исполняемого кода:

#include <avr/io.h> // подключение файла с описанием периферии используемого контроллера

3
int main(void)
{
// до цикла while (1) пишутся стартовые настройки
while (1)
{
//внутри цикла пишется основной (рабочий) код,
//который повторяется бесконечно
}
}

Слева в окне «AVR GCC» показана структура файлов, входящих в состав проекта, в частности в
группе «Source Files» располагается основной файл с исполняемым кодом.
Компиляция программы
Для компиляции проекта необходимо выбрать в меню Build -> Build. Процесс компиляции про-
граммы отобразится в нижнем окне в виде ряда строк текста. Если программа компилируется без оши-
бок и предупреждений, то в последней строке отобразится сообщение вида «Build succeeded with 0
Warnings...». Если программа компилируется с ошибками, необходимо их устранить, следуя подсказ-
кам компилятора и повторить процесс компиляции.
Отладка программы
Корректно скомпилированный проект можно подвергнуть отладке на симуляторе, встроенном в
среду AVR Studio. Для этого после компиляции выбираем в меню Debug -> Start Debugging. При этом
окно среды примет вид:

Справа в окне «IO View» представлен набор периферийных модулей, которые имеются в составе
выбранного микроконтроллера (ATmega64). Это АЦП, компаратор, таймеры, порты ввода/вывода и т.д.
Развернув соответствующую группу, можно проконтролировать (и при необходимости изменить) зна-
чение регистров специальных функций микроконтроллера, влияющих на работу данного модуля.
Слева расположено окно «Processor», в котором представлены регистры микроконтроллера,
также расположены два счётчика («Cycle Counter» и «Stop Watch»), позволяющие замерить время вы-

4
полнения части программы соответственно в циклах тактовой частоты и в секундах. Для обнуления
значений счётчиков необходимо щёлкнуть правой кнопкой мыши над окном «Processor» и появившемся
всплывающем меню выбрать «Reset Cycle Counter» или «Reset Stop Watch». Для корректного отображе-
ния времени в секундах («Stop Watch») необходимо прописать в настройках симулятора действительное
значение частоты кварцевого резонатора, используемого для тактирования микроконтроллера (меню
Debug->AVR Simulator Options, поле «Frequency»), частота вводится в МГц.
Отладка может функционировать в двух режимах – режиме свободного выполнения и пошаговом
режиме. В пошаговом режиме каждая следующая строка исходного кода выполняется за один шаг, по-
сле чего выполнение программы приостанавливается, давая возможность проанализировать значения
переменных, регистров специальных функций и т.д. В режиме свободного выполнения программа ав-
томатически выполняется строка за строкой до тех пор, пока не встретит точку останова, после чего
возвращается в пошаговый режим.
Отладчик всегда запускается в пошаговом режиме. При этом в центральном окне с исходным ко-
дом можно видеть жёлтую стрелку – маркер строки кода, которая будет выполнена на следующем шаге
отладки. Для выполнения следующего шага отладки следует выполнить команду меню Debug-
>Step Into (горячая клавиша F11).
Для перевода отладчика в режим свободного выполнения следует выполнить команды меню De-
bug->Run (горячая клавиша F5). Перед этим можно установить точки останова. Это делается с помо-
щью команды меню Debug->Toggle Breakpoint (горячая клавиша F9).
Запись программы в учебный стенд
Для записи в стенд необходим файл с именем «ИмяПроекта.hex» из папки «DDMP\default». Он
автоматически обновляется в данной папке после каждой успешной компиляции.

Технические характеристики микроконтроллера ATmega64

• FLASH-память программ объемом 64 Кбайт (число циклов стирания/записи не менее 10 000);


• оперативная память (статическое ОЗУ) объемом 4 Кбайт;
• память данных на основе ЭСППЗУ (EEPROM) объемом 2 Кбайт (число циклов стира-
ния/записи не менее 100 000);
• возможность защиты от чтения и модификации памяти программ и данных;
• возможность программирования непосредственно в системе через последовательные интер-
фейсы SPI и JTAG;
• возможность самопрограммирования;
• разнообразные способы тактирования: встроенный RC- генератор с внутренней или внешней
времязадающей RС-цепочкой, встроенный генератор с внешним кварцевым или пьезокерамическим
резонатором, внешний сигнал синхронизации;
• наличие нескольких режимов пониженного энергопотребления;
• наличие детектора пониженного напряжения питания (Brown-Out Detector—BOD);
• полностью статическая архитектура, минимальная тактовая частота равна нулю;
• арифметико-логическое устройство (АЛУ) подключено непосредственно к регистрам общего
назначения (32 регистра);
• большинство команд выполняются за один период тактового сигнала;
• векторная система прерываний, поддержка очереди прерываний;
• наличие аппаратного умножителя.
• программное конфигурирование и выбор портов ввода/вывода;
• выводы могут быть запрограммированы как входные или как выходные независимо друг от
друга;
• входные буферы с триггером Шмитта на всех выводах;
• на всех входах имеются индивидуально отключаемые внутренние подтягивающие резисторы
сопротивлением 20..50 кОм.

Периферийные устройства

5
• два 8-битных таймера/счетчика. Один из них может работать в качестве часов реального вре-
мени (в асинхронном режиме);
• два 16-битный таймера/счетчика;
• встроенная схема для реализации часов реального времени с отдельным резонатором;
• сторожевой таймер;
• 6 каналов ШИМ - сигнала. Разрешение формируемого сигнала может составлять от 1 до 16
бит;
• аналоговый компаратор;
• 8 - канальный 10-битный АЦП последовательного приближения, имеющий как несимметрич-
ные, так и дифференциальные входы;
• последовательный синхронный интерфейс SPI;
• последовательный двухпроводный интерфейс TWI (полный аналог интерфейса I2С);
• 2 полнодуплексных универсальных синхронно/асинхронных приемо-передатчика (USART).

Лабораторная работа вводная. Язык Си


Цель работы: восстановить минимальные знания языка Си.
Задание
Написать программу и проверить её работу в отладчика AVR Studio. Программа должна содер-
жать следующие смысловые части:
1. Подключение системного header-файла avr/io.h (с помощью include).
2. Объявить макроопределение OUTPORT (с помощью define) со значением, зависящим от номера
варианта задания (см. таблицу ниже).
3. Объявить макроопределение OUTPIN (с помощью define) со значением, зависящим от номера ва-
рианта задания (см. таблицу ниже).
4. Объявить глобальный массив, содержащий 10 элементов. Элементы массива должны иметь тип
«беззнаковое 8-битное целое». При объявлении заполнить массив следующими элементами: 0, 1, 1, 2, 3,
5, 8, 13, 21, 34.
5. Написать функцию с названием «write_1», которая не имеет аргументов и не возвращает значений.
Внутри функции необходимо в переменной OUTPORT установить в «1» бит с номером OUTPIN,
остальные биты переменной OUTPORT (предполагается, что она 8-битная) должны остаться без изме-
нения. Для выполнении данного действия необходимо использовать один из побитовых логических
операторов и оператор сдвига.
6. Написать функцию с названием «write_0», которая не имеет аргументов и не возвращает значений.
Внутри функции необходимо в переменной OUTPORT установить в «0» бит с номером OUTPIN,
остальные биты переменной OUTPORT должны остаться без изменения.
7. Написать основную функцию программы «main», которая не имеет аргументов и не возвращает
значений. Внутри функции организовать цикл по элементам объявленного в п.4 массива. Тип цикла
зависит от варианта задания (см. таблицу ниже). Внутри цикла проверять очередной элемент массива
(ЭМ) на выполнение некоего Условия, которое также зависит от варианта задания (см. таблицу ниже):
если элемент удовлетворяет условию, необходимо произвести вызов функции «write_1», иначе произве-
сти вызов функции «write_0».

1 2 3 4 5 6 7 8
OUTPORT PORTA PORTB PORTC PORTD PORTE PORTF PORTA PORTB
OUTPIN 0 1 2 3 4 5 6 7
Тип цикла while do … for while do … while for while do …
while while
Условие ЭМ ЭМ ЭМ ЭМ Сумма ЭМ ЭМ не ЭМ не Одна из
чётный больше 4 равен 2 меньше 2 с нулевого равен 1 кратен Цифр
и меньше или или по текущий и не ра- 3 ЭМ рав-
10 равен 8 больше 8 меньше 10 вен 13 на 3
Контрольные вопросы
6
1. Что изменится в программе при изменении размера массива?
2. С какой целью использованы в программе макроопределения OUTPORT и OUTPIN? Почему прямо
не использовать в функциях имена PORTx и номер бита от 0 до 7?
3. Как изменится программа, если элементы массива необходимо будет перебирать в обратном поряд-
ке (т.е. начиная с последнего)?
4. Переписать программу, чтобы в ней использовался другой тип цикла по выбору преподавателя.
5. Вместо двух функций «write_1» и «write_0» написать одну функцию «write», которая бы принимала
аргумент типа 8-битное беззнаковое целое. Внутри новой функции поставить проверку: если аргумент
равен 0, то выполнить те действия, которые ранее выполняла функция «write_0», иначе – действия, ко-
торые ранее выполняла функция «write_1». Как изменится тело цикла в связи с появлением новой
функции?
6. Добавить в программу отдельную функцию, которая бы занималась проверкой условия, которое
сейчас в программе проверяется в теле цикла. Функция должна принимать в качестве аргумента эле-
мент массива и возвращать 1, если условие (соответствующее вашему варианту) выполняется, или 0 -
если не выполняется. Как изменится тело цикла в связи с переносом проверки в новую функцию?
7. Какое количество раз буду выполнены следующие циклы: for (i=0; i<8; i++); while (1); while (0); do
{} while (1); do {} while (0);

Лабораторная работа №1. Управление портами ввода/вывода


Цель работы знакомство со средой AVRStudio, получить первичные навыки работы с портами
ввода-вывода микроконтроллера ATmega64, а так же применение теоретических знаний на практике.

Краткие теоретические сведения


Обращение к портам производится через регистры ввода/вывода. Под каждый порт в адресном
пространстве ввода/вывода зарезервировано по 3 адреса, по которым размещены следующие регистры:
регистр данных порта PORTx, регистр направления данных DDRx и регистр выводов порта PINx. Дей-
ствительные названия регистров получаются подстановкой названия порта вместо символа. Соответ-
ственно, регистры порта «А» называются PORTA, DDRA, PINA, порта «В» - PORTB, DDRB, PINB и т.
д. Поскольку с помощью регистров PINx осуществляется доступ к физическим значениям сигналов на
выводах порта, они доступны только для чтения, тогда как остальные два регистра доступны, и для
чтения, и для записи.
Ниже приведена таблица конфигурации портов.

Рис.1.1 Таблица настройки I/O ножек ATmega

Чтобы записать в какой-либо регистр «1» в определенный бит, можно воспользоваться побито-
вой операцией «ИЛИ». А для записи «0» в определённый бит - побитовой операцией «И». Ниже приве-
дены примеры данных операций.

Регистр |= (1 << Номер бита); //запись «1» в Регистр по Номеру бита


Регистр &= ~ (1 << Номер бита); //запись «0» в Регистр по Номеру бита.

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


очередно выставлять на ножку PB0 порта «B» в «1» и «0».

7
int main(void)
{
// до цикла while (1) пишутся стартовые настройки
DDRB |= (1 << PB0); // настраиваем ножку на выход
PORTB &= ~(1 << PB0); // на выходе «0»
while (1)
{
//внутри цикла пишется основной (рабочий) код,
//который повторяется бесконечно
PORTB |= (1 << PB0); // на выходе «1»
PORTB &= ~(1 << PB0); // на выходе «0»
}
}

Если к данной ножке порта подключить светодиод, то он будет гореть в ½ своей полной яркости.
Переключение происходит слишком быстро. Для того, чтобы увидеть переключение из «1» в «0» и об-
ратно, необходимо добавить временные задержки после установки «1» и «0». Подключив <util/delay.h>
и воспользовавшись функциями из данной библиотеки, можно получить необходимую задержку:
_delay_ms(100); // задержка на 100 мс
_delay_us(40); // задержка на 40 мкс

Но для того, чтобы программа знала, как правильно высчитать задержку, необходимо ей указать,
с какой частотой у нас работает наш контроллер. Для этого необходимо еще до подключения модуля
«delay.h» прописать:

#define F_CPU 7372800UL // контролер работает с частотой 7.3728 МГц

8
Рис.1.2 Эскиз схемы

Задание:
1. Самостоятельно вставить временные задержки, чтобы отчетливо было видно переключение
из «0» в «1» и обратно.
2. Из таблицы в соответствии с вариантами необходимо написать программу и проверить её на
стенде.

1 2 3 4 5 6 7 8 9
Светодиоды HL1, HL2, HL1, HL1, HL2, HL3, HL1, HL2, HL3,
HL3 HL4 HL2 HL4 HL3 HL4 HL5(G) HL5(R) HL5(G)
Время 0.5s / 0.1s / 0.5s / 0.1s / 0.5s / 0.1s / 1s / 1s / 0.5s /
вкл/выкл 1s 1s 1s 1s 1s 1s 0.1s 0.5s 0.5s
Контрольные вопросы
1. За что отвечают следующие регистры микроконтроллера: PORTx, DDRx, PINx?
2. Настроить вывод порта для работы в одном из режимов по выбору преподавателя?
3. Пояснить на уровне побитовых логических операций, какое действие с регистром производят
следующие строчки кода:
9
Регистр |= (1 << Номер бита);
Регистр &= ~ (1 << Номер бита);
4. Как реализована задержка в данной лабораторной работе? Как она действует? В чём минусы та-
кого способа реализации задержки?

Лабораторная работа №2. Подключение кнопки


Цель работы знакомство со средой AVRStudio, получение первичных навыков работы с порта-
ми ввода-вывода микроконтроллера ATmega64, а также применение теоретических знаний на практике.

Краткие теоретические сведения


Из лабораторной работы №1 известно, что для считывания данных с ножки необходимо:
1) Настроить в регистре DDR# нужный бит в «0»;
2) Установив нужный бит в регистре PORT# в «1» или «0» (см. Рис. 1.1);
3) Считать данные из порта используя регистр PIN#.

Ниже написан отрывок кода, который проверяет состояние PORTD.3.

int main(void)
{
// до цикла while(1) пишутся стартовые настройки
DDRD |= (1 << PD0);
PORTD &= ~(1 << PD0);
DDRD &= ~(1 << PD3); // настраиваем ножку на вход
PORTD |= (1 << PD3); // подтягиваем к «1»
while (1)
{
//внутри цикла пишется основной (рабочий) код,
//который повторяется бесконечно
if (PIND & (1 << PD3)) // если на PD3 логическая «1» - выполняем
{
PORTD |= (1 << PD0);
}
else
{
PORTD &= ~(1 << PD0);
}
}
}

Пусть на ножке PD3 находится обычная тактовая кнопка, с одной стороны подключенная к «0», а
с другой стороны к ножке МК. В тот момент, когда кнопка не нажата, а ножка (к которой подключена
кнопка) подтянута к «1», то на входе будет «1». Если кнопка нажата, то на входе МК появляется логи-
ческий «0». Но есть одна особенность механических переключателей, и она называется «дребезг кон-
тактов».

Рис. 3.1 Дребезг контактов


Таким образом, во время нажатия и отпускания кнопки может произойти n-ое количество пере-
ключений. Существует множество решений данной проблемы, использование RC-цепи, защитные триг-
геры, программная обработка и многое другое.
Задание:

10
Используя пример в качестве опорного кода, схему и полученные знания из первой лабораторной
работы необходимо:
1) программным способом избавиться от дребезга контактов
2) выполнить задание по вариантам

Задание\вариант 1 2 3 4 5
Передача сигнала
Кнопка «Test» «Бегущие» «Бегущая» Мигание «SOS» с помо- 1 Свето-
нажата огни вверх тень вверх светодиода щью звукоизлу- диод горит
чателя
Кнопка «Test» «Бегущие» «Бегущая» Горение Звукоизлучатель 2 светоди-
отжата огни вниз тень вниз светодиода отключен ода горит

Задание\вариант 6 7 8 9
Передавать пре-
Мигать всеми Включить Замкнуть
Геркон «TAM» рывистый звуко-
светодиодами четные контакты
замкнут вой сигнал зву-
на порту светодиоды реле
коизлучателем
Включить Разомкнуть
Геркон «TAM» Погасить все Звукоизлучатель
нечетные контакты
разомкнут светодиоды отключен
светодиоды реле
Контрольные вопросы
1. Зачем используется внутренний подтягивающий резистор вывода порта в МК?
2. Как настроить вывод порта на вход с подтягивающий резистором?
3. Как производится опрос состояния кнопки в данной работе?
4. Что такое дребезг контактов? Как он устраняется в данной работе? Какие ещё способы устране-
ния дребезга вы можете назвать?
5. Что изменится в схеме и в программе, если кнопка подключена не к «земле», а к «питанию»?

11
Лабораторная работа №3. Таймер
Цель работы ознакомление с таймерами/счетчиками в МК ATmega64 и практическое примене-
ние на лабораторном стенде.

Краткие теоретические сведения


Любой микроконтроллер серии AVR содержит несколько встроенных таймеров. Причем по сво-
ему назначению их можно разделить на две категории. К первой категории относятся таймеры общего
назначения. Вторую категорию составляет сторожевой таймер. Сторожевой таймер предназначен для
автоматического перезапуска микроконтроллера в случае «зависания» его программы. Таймеры общего
назначения используются для формирования различных интервалов времени и прямоугольных импуль-
сов различной частоты и скважности. Кроме того, они могут работать в режиме счетчика и подсчиты-
вать тактовые импульсы заданной частоты, измеряя, таким образом, длительность внешних сигналов, а
также при необходимости подсчитывать количество любых внешних импульсов.
По этой причине данные таймеры называют «таймеры/счетчики». В микросхемах АVR применяются
как восьмиразрядные, так и шестнадцатиразрядные таймеры/счетчики. Их количество для разных мик-
роконтроллеров изменяется от одного до четырех. Все таймеры обозначаются числами от 0 до 3.
Счетный регистр восьмиразрядного таймера именуется TCNТx, где «х» - это номер таймера. У
таймеров 0 и 2 счётный регистр имеет разрядность 8 бит. В результате максимальной значение, до кото-
рого может считать таймер, равно 255.

Рис.3.1 Регистр нулевого таймера/счетчика

Счётные регистры таймеров 1 и 3 являются 16 битными, но так как микроконтроллер 8 разряд-


ный, то данный регистр разбит на 2 части: регистр TCNTxL содержит младшие 8 бит, а TCNTxH –
старшие 8 бит. Данный таймер может считать до 216=65535.

Рис. 3.2 Регистр первого таймера/счетчика

Микроконтроллер может записать в любой счетный регистр любое число (не превышающее мак-
симальное значение) в любой момент времени, а также в любой момент прочитать содержимое любого
счетного регистра. Когда таймер включается в режим счета, то на его вход начинают поступать счетные
импульсы. После прихода каждого такого импульса содержимое счетного регистра увеличивается на
единицу. Счетными импульсами могут служить как специальные тактовые импульсы, вырабатываемые
внутри самого микроконтроллера, так и внешние импульсы, поступающие на специальные входы мик-
росхемы. При переполнении счетного регистра его содержимое обнуляется, и счет начинается сначала.
С каждый таймер завязан с системой прерываний. Вызвать прерывание может целый ряд собы-
тий, связанных с таймером. Например, существует прерывание по переполнению таймера, по срабаты-
ванию специальной схемы совпадения. Отдельные прерывания может вызывать сторожевой таймер.
Для каждого таймера присущи два разных источника тактирования. Либо это внешние импуль-
сы, либо импульсы, вырабатываемые внутренней схемой микроконтроллера. Каждая микросхема АVR
имеет свою структуру предварительного делителя для таймеров/счетчиков. Для таймера 1 в ATmega64
присущи делители: clk/1, clk/8, clk/64, clk/256, clk/1024 или внешнее тактирование через соответствую-
щие ножки микроконтроллера.

12
Таймер 1 может работать в различных режимах. Для настройки режима работы таймера исполь-
зуются регистры TCCR1A, TCCR1B, TCCR1C, OCR1A, OCR1B, OCR1C и регистр TIMSK для настрой-
ки прерываний по таймеру.

Рис. 3.3 Регистры для настройки таймера/счетчика 1

В данной работе будет использоваться режим работы таймера, называемый «CTC» (Clear Timer
on Compare). В этом режиме таймер считает от 0 до значения, заданного регистром OCR1A. При дости-
жении этого значения таймер сбрасывается в 0, и цикл повторяется снова. В момент сброса таймера в 0
устанавливается флаг OCF1A в регистре TIFR. Если при этом разрешено соответствующее прерывание
(установлен бит OCIE1A в регистре TIMSK и глобально разрешены все прерывания), то будет вызвана
соответствующая процедура прерывания.
Такой режим работы позволяет организовать прецизионный счётчик временных интервалов в
микроконтроллере.
Подробное описание всех режимов работы таймера 1 и описание всех битов управляющих реги-
стров, относящихся к таймеру, можно посмотреть в руководстве по программируемому микроконтрол-
леру.
Частоту переполнения таймера в режиме «CTC» можно рассчитать по формуле:

Где fтакт – тактовая частота процессора; P – значение предварительного делителя (выбирается из


ряда 1, 8, 64, 256, 1024); OCR1A – значение регистра OCR1A (т.к. регистр 16-разрядный, то значение
должно находиться в диапазоне от 0 до 65535).
Приведём пример вычисления значений предварительного делителя и регистра OCR1A, если
необходимо получить время переполнения таймера равное 0.1 секунде при тактовой частоте 4 МГц.
Вычислим значение в знаменателе формулы выше:
P·(1+OCR1A) = fтакт / fпп = 4000000 / (1 / 0.1) = 400000.
13
Далее начинаем перебирать значения предварительного делителя, начиная с наименьшего (с 1)
до тех пор, пока получившееся значение для OCR1A не станет меньше 65536.
Для P=1 вычисляем OCR1A = (400000 / P) – 1 = 399999 (не подходит, т.к. больше 65535).
Для P=8: OCR1A = (400000 / P) – 1 = 49999. На этом этапе останавливаемся, т.к. получили походящее
значение для OCR1A. Итак, результат P=8; OCR1A=49999.

Для запуска таймера 1 в режиме «CTC» необходимо:


- исходя из заданной частоты переполнения вычислить значение предварительного делителя и
значение, которое необходимо записать в регистр OCR1A. Значение предварительного делителя
записать в биты CS12, CS11, CS10 регистра TCCR1B в соответствии с таблицей ниже. Вычис-
ленное значение для OCR1A записать в OCR1A.

- в регистре TCCR1B установить бит WGM12 в «1»; WGM13=0;


- в регистре TCCR1A установить биты WGM11 и WGM10 в «0»;
- если предполагается использование прерываний по переполнению таймера, то необходимо:
o установить бит OCIE1A в регистре TIMSK в «1» (это разрешит прерывание от таймера);
o глобально разрешить все прерывания путём вызова функции sei();
o подключить заголовочный файл <avr/interrupt.h>
o создать процедуру обработки прерывания вида:

// Timer 1 output compare A interrupt service routine


ISR (TIMER1_COMPA_vect)
{
// здесь пишем код, который будет выполняться во время прерывания
}

Если для контроля переполнения таймера не используется прерывание, то для определения мо-
мента переполнения таймера можно использовать поллинг (т.е. постоянный опрос) бита OCF1A в реги-
стре TIFR. Если в этом бите записана «1», значит произошло переполнение таймера. Когда обнаружено
переполнение таймера, необходимо сбросить флаг переполнения. Для этого записываем «1» в бит
OCF1A регистра TIFR.

Ниже приведен пример программы настройки таймера/счетчика 1 в режиме «CTC». В основном


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

int main(void)
{
// до цикла while(1) пишутся стартовые настройки
// настраиваем вывод порта, подключённый к светодиоду
DDRD |= (1 << PD0);
PORTD &= ~(1 << PD0);

OCR1A = 49999; // устанавливаем максимальное значение счётчика


// Инициализация режима CTC
// и установка предварительного делителя = 8
TCCR1A = (0 << WGM11) | (0 << WGM10);
TCCR1B = (0 << WGM13) | (1 << WGM12) | (0 << CS12) | (1 << CS11) | (0 << CS10);
14
// очищаем флаг переполнения таймера
TIFR = (1 << OCF1A);
// очищаем счетчик таймера 1
TCNT1 = 0;

while (1)
{
//внутри цикла пишется основной (рабочий) код,
//который повторяется бесконечно

//проверяем флаг переполнения таймера 1


if (TIFR & (1 << OCF1A))
{
//очищаем бит переполнения таймера
//очищается данный бит записью «1» в него
TIFR = (1 << OCF1A);
//меняем состояние ножки на противоположное
PORTD ^= (1 << PD0);
}
}
}

Задание:
Используя изложенную выше информацию, схему (рис. 1.2) и знания, полученные в предыдущих
лабораторных работах необходимо:
1) Периодически включать/выключать светодиод HL1 на заданное время, в зависимости от вариантов.
Для отсчёта интервалов времени использовать таймер.
2) Составить отчет по лабораторной работе

Задание\вариант 1 2 3 4 5
Длительность
включённого 100 мс 100 мс 200 мс 200 мс 500 мс
состояния
Длительность
выключенного 500 мс 500 мс 300 мс 300 мс 1 сек
состояния
Использовать
Да Нет Да Нет Да
прерывания

Задание\вариант 6 7 8 9
Длительность
включённого 500 мс 200 мс 200 мс 900 мс
состояния
Длительность
выключенного 1 сек 700 мс 700 мс 500 мс
состояния
Использовать
Нет Да Нет Да
прерывания
Контрольные вопросы
1. Что такое таймер в МК? Для чего он используется? Что лежит в основе таймера? Как разрядно-
сти таймер используется в данной работе?
2. Чем отличаются режимы работы таймера/счётчика в качестве таймера и в качестве счётчика?
3. Что такое предварительный делитель? Для чего он может использоваться? Какие значения может
принимать предварительный делитель?
4. Как работает таймер (описать пошагово)? Что такое переполнение? Что происходит при пере-
полнении таймера?

15
5. Что такое поллинг значения? Если по переполнению таймера необходимо выполнить какую-либо
операцию, то какие два механизма можно использовать для обнаружения момента переполне-
ния? Назовите плюсы и минусы обоих механизмов?
6. Инициализировать таймер, чтобы он переполнялся через время, заданное преподавателем.
7. Как, используя таймер, выполнять в программе какое-либо вспомогательное периодическое дей-
ствие с большим периодом (например, 5 мин.)?

16
Лабораторная работа №4. Таймер в режиме ШИМ
Цель работы практическая реализация дополнительных возможностей таймеров/счетчиков МК
ATmega64.

Краткие теоретические сведения


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

 16-битный счетчик (то есть, позволяет так же 16-битный ШИМ)


 Два независимых выхода сравнения (compare)
 Двойная буферизация регистров порога сравнения (OCR)
 Один вход блока захвата
 Подавитель шума на входе блока захвата
 Режим сброса таймера при совпадении с порогом сравнения (этот режим («CTC») был рассмот-
рен в предыдущей лабораторной работе)
 Широтно-импульсная модуляция без генерации ложных импульсов при записи нового порога
сравнения в OCR (двойная буферизация) и фазовая коррекция
 Переменный период ШИМ
 Частотный генератор
 Счетчик внешних событий
 4 независимых источников прерывания (TOV1, OCF1A, OCF1B, and ICF1)

Представим, что у нас есть устройство, которое требует генерации стабильной частоты микро-
контроллером без фазовых искажений. Если мы будем вручную переключать состояние вывода из «0» в
«1» и обратно в основной программе (проверяя флаги таймера или пользуясь прерываниями от тайме-
ра), то длительности импульсов «1» и «0» будут подвержены дрожанию (они будут то немного больше
номинала, то меньше). Кроме того, такое управление ножками снижает быстродействие программы, т.к.
часть времени тратится на мониторинг состояния флагов (для случая программной проверки флагов)
или на вызов процедуры прерывания (в случае использования прерываний).
В микроконтроллере ATmega64 есть аппаратная возможность генерации прямоугольных им-
пульсов (с различной скважностью и частотой) с помощью таймеров. Для этого каждому таймеру выде-
лены специальные выводы микроконтроллера. Для таймера 1 это выводы PB5 (OC1A), PB6 (OC1B),
PB7 (OC1C) (см. рис. 3.6, выводы выделены оранжевым цветом).

17
Рис. 3.6 Общий вид TQFP корпуса ATmega64 с выделенными выводами, управляемыми таймерами
Если использовать таймер 1 в режиме «CTC» (этот режим применялся в предыдущей лаборатор-
ной работе), то в зависимости от комбинации битов COM1A1, COM1A0 регистра TCCR1A можно
настроить поведение вывода PB5 (OC1A). Описание действия данных битов в режиме «CTC» приведено
ниже:
COM1A1 COM1A0 Описание
Управление выводом не производится. Вывод может использоваться как
0 0
обычный порт ввода/вывода.
0 1 Переключение (инвертирование) вывода при каждом переполнении таймера
1 0 Установка в «0» вывода при каждом переполнении таймера
1 1 Установка в «1» вывода при каждом переполнении таймера
Таким образом, используя комбинацию COM1A1=0; COM1A0=1 (в этом случае вывод будет ин-
вертироваться при каждом переполнении таймера) можно получить меандр на выводе PB5 (OC1A) с ча-
стотой, равной половине частоты переполнения таймера.
Ещё одним режимом работы таймера 1 является режим «Fast PWM» (быстрая ШИМ). Этот ре-
жим может использоваться для генерации периодического сигнала с широтно-импульсной модуляцией
на выводе PB5 (OC1A), при этом имеется возможность оперативно управлять его скважностью (а в от-
дельных режимах и частотой):

18
В этом режиме таймер/счётчик считает от 0 до верхнего значения, после чего сбрасывается в 0, и
счёт начинается снова. При этом, когда во время счёта значение таймера достигает значения, записан-
ного в регистре OCR1A, происходит переключение вывода OC1A; обратное переключение вывода про-
исходит, когда счётчик достигает верхнего значения и сбрасывается в 0. Поведение вывода PB5 (OC1A),
как и в режиме «CTC», настраивается комбинацией битов COM1A1, COM1A0 регистра TCCR1A. Опи-
сание действия данных битов в режиме «Fast PWM» приведено ниже:
COM1A1 COM1A0 Описание
Управление выводом не производится. Вывод может использоваться как обычный
0 0
порт ввода/вывода.
Переключение (инвертирование) вывода при каждом переполнении таймера (толь-
0 1
ко для режима 15 (см. таблицу ниже))
Неинвертирующий режим. Сброс вывода в «0» при совпадении значения счётчи-
1 0
ка с регистром OCR1A; установка вывода в «1» при каждом переполнении таймера.
Инвертирующий режим. Установка вывода в «1» при совпадении значения счёт-
1 1
чика с регистром OCR1A; сброс вывода в «0» при каждом переполнении таймера.
Существует несколько режимов «Fast PWM», задаваемых битами WGM13, WGM12 регистра
TCCR1B, а также битами WGM11, WGM10 регистра TCCR1A. Отличия данных режимов показаны в
таблице ниже.
Режимы WGM13 WGM12 WGM11 WGM10 Верхнее значение Название
5 0 1 0 1 0x00FF 8-битный ШИМ
6 0 1 1 0 0x01FF 9-битный ШИМ
7 0 1 1 1 0x03FF 10-битный ШИМ
Определяется
14 1 1 1 0 ШИМ
регистром ICR1
Определяется
15 1 1 1 1 ШИМ
регистром OCR1A
Из таблицы видно, что режимы отличаются заданием верхнего значения счётчика (а значит, и ча-
стоты ШИМ сигнала). Для режимов 5, 6, 7 верхнее значение фиксировано; для режимов 14, 15 верхнее
значение можно изменять, меняя тем самым частоту ШИМ сигнала.
Частоту переполнения таймера 1 в режиме «Fast PWM» можно определить по формуле:

где fтакт – тактовая частота процессора; P – значение предварительного делителя (выбирается из ряда 1,
8, 64, 256, 1024); TOP – верхнее значение таймер/счётчика (зависит от режима).
Более подробное описание особенностей работы таймера 1 в различных режимах приведено в
руководстве по программируемому микроконтроллеру.

19
Примечание. Для того, чтобы функция аппаратного управления выводом работала корректно, соответ-
ствующая ножка микроконтроллера должна быть сконфигурирована на вывод информации.
Ниже приведён пример программы, использующий 7-ой режим «Fast PWM» таймера 1 для управления
светодиодом, подключённым к выводу PB5 (OC1A):

int main(void)
{
// до цикла while(1) пишутся стартовые настройки
// настраиваем на вывод ножку порта, управляемую таймером (OC1A)
DDRB |= (1 << PB5);

// Инициализация 7-го режима «Fast PWM»,


// установка предварительного делителя = 1024
// и установка неинвертирующего режима управления выводом OC1A
TCCR1A = (1 << WGM11) | (1 << WGM10) | (1 << COM1A1) | (0 << COM1A0);
TCCR1B = (0 << WGM13) | (1 << WGM12) | (1 << CS12) | (0 << CS11) | (1 << CS10);
OCR1A = 0x80; // Ножка OC1A будет установлена в «1» 1/8 часть периода таймера
while (1) // цикл пустой – все необходимые действия будут выполняться автоматически
{
}
}

Задание:
Используя изложенную выше информацию, схему (рис. 1.2) и знания, полученные в предыдущих
лабораторных работах, необходимо:
1) Периодически включать/выключать светодиод HL6 с заданным временем, в зависимости от вари-
антов. Для переключения использовать соответствующим образом настроенный таймер 1.
2) Составить отчет по лабораторной работе

Задание\вариант 1 2 3 4 5
Время включе-
200 мс 200 мс 100 мс 200 мс 200 мс
ния
Время выклю-
700 мс 600 мс 900 мс 900 мс 200 мс
чения

Задание\вариант 6 7 8 9
Время включе-
900 мс 700 мс 600 мс 500 мс
ния
Время выклю-
100 мс 200 мс 200 мс 500 мс
чения
Контрольные вопросы
1. Что такое ШИМ? Как функционирует таймер в режиме генерации ШИМ?
2. От каких параметров зависит частота ШИМ? Каким образом регулируется скважность ШИМ?
3. Перечислить режимы генерации ШИМ в данном микроконтроллере. Чем они отличаются?
4. От чего зависит частота переполнения таймера (3 параметра)?
5. Для чего может использоваться ШИМ на практике?
6. Как с помощью таймера/счётчика организовать измерение длительности импульса, частоты циф-
рового сигнала?
7. Как с помощью таймера организовать опрос состояния кнопки с устранением дребезга, но без
использования функции задержки _delay?
8. Как в данном микроконтроллере организовать комплементарный ШИМ на 2 канала (т.е. логиче-
ский уровень на выходе одного канала должен являться инверсией логического уровня другого)?
9. Какие значения записать в регистры управления таймером, чтобы получить самый быстрый (с
максимально возможной частотой переполнения) ШИМ, имеющий 16 градаций?

20
Лабораторная работа №5. АЦП
Цель работы Изучение принципов работы и функционирования АЦП в ATmega64.

Краткие теоретические сведения


ATMega64 содержит в себе 10-битовый АЦП, вход которого может быть соединён с одним из
восьми выводов PORTF. АЦП Mega64, как и любому другому АЦП, нужно опорное напряжение для це-
лей сравнения со входным (если измеряемое равно опорному, то получаем максимальный код
). Опорное напряжение подаётся на вывод AREF или может использоваться внутреннее
опорное напряжение 2,56 В. Полученный результат преобразования можно представить в таком виде:

АЦП включается установкой бита ADEN в регистре ADCSRA. После преобразования, 10-битный
результат оказывается в 8-битных регистрах ADCL и ADCH, которые вместе образуют 16-битный ре-
гистр. По умолчанию, младший бит результата находится справа (то есть в bit 0 регистра ADCL, так
называемое «правое выравнивание»). Но имеется возможность использовать и «левое выравнивание»,
когда старший бит 10-битного результата преобразования располагается в старшем, седьмом бите реги-
стра ADCH. Это бывает удобно, если требуется получить 8-битовый результат. В таком случае требует-
ся прочитать только регистр ADCH. В противном случае, Вы должны сначала прочитать регистр ADCL
первым, а ADCH вторым, чтобы быть уверенным в том, что чтение этих двух регистров относится к ре-
зультату одного преобразования. Компилятор языка «С» делает это автоматически, когда производится
чтение из 16-битного регистра c именем «ADC».
Одиночное преобразование может быть вызвано записью бита ADSC в регистр ADCSRA. Этот
бит остаётся установленным всё время, занимаемое преобразованием. Когда преобразование закончено,
бит автоматически устанавливается в 0. Можно также начинать преобразования по событиям из разных
источников. Модуль АЦП также может работать в режиме "автоматического перезапуска". В этом ре-
жиме модуль АЦП после завершения очередного преобразования записывает результат в регистры
ADCH и ADCL, после чего автоматически начинает новое измерение и т.д.
Для выполнения преобразования модулю АЦП необходима тактовая частота. Чем выше эта ча-
стота, тем быстрее будет происходить преобразование (оно, обычно, занимает 13 тактов, первое преоб-
разование после включения АЦП занимает 25 тактов). Но чем выше частота (и выше скорость преобра-
зования), тем менее точным получается результат. Для получения максимально точного результата, мо-
дуль АЦП должен тактироваться частотой в пределах от 50 до 200 КГц. Если необходим результат с
точностью менее 10 бит, то можно использовать частоту больше 200 КГц. Модуль АЦП содержит дели-
тель частоты, чтобы получать нужную тактовую частоту для преобразования из частоты процессора.

Рис. 3.8. Регистр ADMUX контролера ATmega64.

Регистр ADMUX (рис. 3.8) позволяет выбрать источник сигнала для измерения, выравнивание
результата и источник опорного напряжения. Источник сигнала выбирается битами MUX4..0.
MUX4..0 Несимметричный (недифференциальный) вход
00000 ADC0 (PORTF, вывод 0)
00001 ADC1 (PORTF, вывод 1)
00010 ADC2 (PORTF, вывод 2)
00011 ADC3 (PORTF, вывод 3)
00100 ADC4 (PORTF, вывод 4)
00101 ADC5 (PORTF, вывод 5)

21
00110 ADC6 (PORTF, вывод 6)
00111 ADC7 (PORTF, вывод 7)
В таблице выше перечислены не все значения, которые могут принимать биты MUX4..0. Приве-
дены только значения, которые позволяют настроить АЦП на работу с несимметричными (недифферен-
циальными) источниками сигнала. Другие значения битов MUX4..0 описаны в полной документации на
рассматриваемый микроконтроллер.
Если установлен бит ADLAR, то используется «левое выравнивание» результата преобразования,
иначе используется «правое выравнивание».
Источник опорного напряжения АЦП выбирается с помощью битов REFS1..0.
REFS1..0 Источник опорного напряжения
00 Внешнее напряжение, подаваемое на вывод VREF
01 Напряжение питания АЦП с вывода AVCC, при этом к выводу VREF следует
подключить дополнительный фильтрующий конденсатор
10 Данная комбинация не используется
11 Используется внутреннее опорное напряжение 2.56 В, при этом к выводу VREF
следует подключить дополнительный фильтрующий конденсатор

Регистр контроля и статуса АЦП ADCSRA (Рис. 3.10):

Рис. 3.10. Регистр контроля и статуса АЦП контролера ATmega64.

Бит ADEN=1 включает модуль АЦП.


Запись единицы в ADSC запускает цикл преобразования. В режиме "автоматического перезапус-
ка" запись «1» запускает первое преобразование, последующие запускаются автоматически.
Если бит ADATE сброшен в 0, АЦП работает в режиме одиночного преобразования. Если же бит
ADTAE установлен в 1, функционирование АЦП определяется содержимым битов ADTS2:0 регистра
ADCSRB согласно табл. ниже:

ADIF - флаг прерывания АЦП. Этот бит устанавливается в «1» когда АЦП завершил преобразо-
вание, и в регистрах ADCL и ADCH находятся актуальные данные. Этот флаг устанавливается даже в
том случае, если прерывания запрещены. Это необходимо для случая программного опроса АЦП. Если
используются прерывания, то флаг сбрасывается автоматически. Если используется программный
опрос, то флаг может быть сброшен записью лог.1 в этот бит.
ADIE - Если в этом бите установлена единица, и прерывания разрешены глобально, то при окон-
чании преобразования будет выполнен переход по вектору прерывания от АЦП.
Биты ADPS2..0 задают коэффициенты предделителя частоты (Рис.3.11):

22
Рис. 3.11.
Ниже приводится пример программы, которая осуществляет периодическое считывание напря-
жения с ножки АЦП ADC7, и если напряжение оказывается больше порогового, то зажигает светодиод,
подключённый к выводу PORTB bit 0, а если меньше – гасит его.
#include <avr/io.h>

int adc_measure(void)
{
ADCSRA |= (1 << ADSC); // начинаем новое преобразование
while (ADCSRA & (1 << ADSC)) // ожидаем окончания преобразования
;
return ADC; // считываем результат преобразования
}

int main(void)
{
// до цикла while(1) пишутся стартовые настройки
DDRB |= (1 << PB0); // настраиваем вывод светодиода на выход

// инициализируем АЦП
ADMUX = (1 << REFS1) | (1 << REFS0) | 0x07; // VRef=2.56В; вход ADC7
ADCSRA = (1 << ADEN) | 0x06; // разрешаем АЦП, уст. предделитель=64

while (1)
{
int v = adc_measure(); // вызываем процедуру измерения
if (v > 512) // значение больше порога?
PORTB |= (1 << PB0); // зажигаем светодиод
else
PORTB &= ~(1 << PB0); // иначе - гасим светодиод
}
}

Задание:
1. Пользуясь схемой из лабораторной №1, составить и проверить на стенде программу в соот-
ветствии со своим вариантом из таблицы ниже. В качестве источника сигнала для измерения
использовать напряжение с выхода переменного резистора R83.
2. Написать и сдать отчет по лабораторной работе.
Номер
Задание
варианта
1 Зажигать светодиоды HL1..HL4 пропорционально напряжению на входе АЦП. При минимальном
напряжении все светодиоды потушены, далее при повышении напряжения загораются последова-
тельно светодиоды HL1, HL2, HL3 и при максимальном напряжении должны гореть все светодиоды.
2 Управлять яркостью светодиода HL6 с помощью ШИМ пропорционально напряжению на входе
АЦП
3 Управлять светодиодом HL1 c помощью переменного резистора. Светодиод должен гореть, когда
напряжение на входе АЦП больше порогового, и быть потушен, когда напряжение меньше порого-
вого. Реализовать гистерезис, когда после зажигания светодиода, порог для выключения смещается
ниже.
4 Звукоизлучатель периодически включается и выключается. Длительность включённого состояния
фиксирована и составляет 50 мс, длительность выключенного состояния пропорциональна напряже-

23
нию на входе АЦП и варьируется в диапазоне от 100 мс до 2 сек.
5 Зажигать светодиоды HL1..HL4 обратно пропорционально напряжению на входе АЦП. При макси-
мальном напряжении все светодиоды потушены, далее при понижении напряжения загораются по-
следовательно светодиоды HL1, HL2, HL3 и при минимальном напряжении должны гореть все све-
тодиоды.
6 Управлять яркостью светодиода HL6 с помощью ШИМ обратно пропорционально напряжению на
входе АЦП
7 Управлять светодиодом HL1 c помощью переменного резистора. Светодиод должен гореть, когда
напряжение на входе АЦП меньше порогового, и быть потушен, когда напряжение больше порого-
вого. Реализовать гистерезис, когда после выключения светодиода, порог для включения смещается
ниже.
8 Звукоизлучатель периодически включается и выключается. Длительность включённого состояния
фиксирована и составляет 50 мс, длительность выключенного состояния обратно пропорциональна
напряжению на входе АЦП и варьируется в диапазоне от 100 мс до 2 сек.
9 Управлять светодиодами HL1..HL4 в зависимости от напряжения на входе АЦП. Когда напряжение
составляет половину от максимального значения, светодиоды должны быть потушены. Когда
напряжение на входе АЦП отклоняется от половины в большую сторону, должны последовательно
загораться светодиоды HL3, затем HL4 (пропорционально напряжению на входе); когда напряжение
на входе отклоняется от половины в меньшую сторону, должны последовательно загораться свето-
диоды HL2, затем HL1 с уменьшением напряжения на входе.
Контрольные вопросы
1. Как зависит результат АЦП от опорного напряжения? Какие источники опорного напряжения
можно использовать в данном микроконтроллере? Как они выбираются?
2. Какова разрядность АЦП в данном микроконтроллере? Значение в каком диапазоне мы можем
получить от АЦП?
3. Почему в микроконтроллере имеются раздельные выводы питания контроллера (VCC) и АЦП
(AVCC)?
4. Как в лабораторной работе микроконтроллер узнаёт о завершении процесса преобразования? Как
запустить АЦП и получить результат преобразования, не тратя процессорное время на ожидание
момента завершения преобразования?
5. Как запустить АЦП на работу в режиме непрерывного преобразования? Как запустить АЦП на
работу в режиме запуска преобразования в момент переполнения Таймера1? Таймера2? Как в та-
ком режиме получать результат преобразования?
6. Пояснить назначение битов регистра контроля и статуса АЦП ADCSRA.
7. Как с помощью АЦП измерить отрицательное напряжение?
8. На что влияет содержимое битов MUX4..0 регистра ADMUX? Пользуясь техническим описанием
на используемый микроконтроллер, показать, какие ещё, помимо описанных в данной работе,
источники сигнала можно использовать с АЦП в микроконтроллере?
9. Сигнал на входе АЦП ограничен полосой 500 Гц. Необходимо получить среднее значение этого
сигнала за время 1 сек. Как это сделать?

Лабораторная работа №6. Последовательный интерфейс


Цель работы Изучение принципов работы и универсального последовательного интерфейса в
микроконтроллере ATmega64.

Краткие теоретические сведения


Универсальный синхронный и асинхронный последовательный приемопередатчик (УСАПП)
предназначен для организации гибкой последовательной связи.
Отличительные особенности:
 Полнодуплексная работа (раздельные регистры последовательного приема и передачи)
 Асинхронная или синхронная работа
 Ведущее или подчиненное тактирование связи в синхронном режиме работы
 Высокая разрешающая способность генератора скорости связи
 Поддержка формата передаваемых данных с 5, 6, 7, 8 или 9 битами данных и 1 или 2 стоп-битами
24
 Аппаратная генерация и проверка бита паритета (четность/нечетность)
 Определение переполнения данных
 Определение ошибки в структуре посылки
 Фильтрация шума с детекцией ложного старт-бита и цифровым ФНЧ
 Три раздельных прерывания по завершении передачи, освобождении регистра передаваемых
данных и завершении приема
 Режим многопроцессорной связи
 Режим удвоения скорости связи в асинхронном режиме
Микроконтроллер ATmega64 имеет в своем составе два модуля универсального синхрон-
но/асинхронного приемопередатчика (USART): USART0 и USART1.
Упрощенная структурная схема одного модуля USART приведена ниже:

Как показано на рисунке, модуль состоит из трех основных частей: блока тактирования, блока
передатчика и блока приемника. Блок тактирования модулей USART содержит схему синхронизации,
которая используется при работе в синхронном режиме, и контроллер скорости передачи.
Блок передатчика включает одноуровневый буфер, сдвиговый регистр, схему формирования бита
четности и схему управления. Блок приемника, в свою очередь, содержит схемы восстановления такто-
вого сигнала и данных, схему контроля четности, двухуровневый буфер, сдвиговый регистр, а также
схему управления.
Буферные регистры приемника и передатчика располагаются по одному адресу пространства
ввода/вывода и обозначаются как регистр данных UDR (UDRn). В этом регистре хранятся младшие 8
битов принимаемых и передаваемых данных. При чтении регистра UDR выполняется обращение к бу-
ферному регистру приемника, при записи — к буферному регистру передатчика.
Для управления модулями USART используются три регистра: UCSRA (UCSRnA), UCSRB
(UCSRnB) и UCSRC (UCSRnC), где n - номер модуля USART (0 или 1).
25
RXCn - флаг завершения приема. Флаг устанавливается в 1 при наличии непрочитанных данных
в буфере приемника (регистр данных UDR). Сбрасывается флаг аппаратно после опустошения буфера.
Если бит RXCIE (RXCIEn) регистра UCSRB (UCSRnB) установлен, то при установке флага генерирует-
ся запрос на прерывание «прием завершен».
TXCn - флаг завершения передачи. Флаг устанавливается в 1 после передачи всех битов посылки
из сдвигового регистра передатчика при условии, что в регистр данных UDR не было загружено новое
значение. Если бит TXCIE (TXCIEn) регистра UCSRB (UCSRnB) установлен, то при установке флага
генерируется прерывание «передача завершена». Флаг сбрасывается аппаратно при выполнении под-
программы обработки прерывания или программно, записью в него лог. 1
UDREn - флаг опустошения регистра данных. Данный флаг устанавливается в 1 при пустом бу-
фере передатчика (после пересылки байта из регистра данных UDR в сдвиговый регистр передатчика).
Установленный флаг означает, что в регистр данных можно загружать новое значение. Если бит UDRIE
(UDRIEn) регистра UCSRB (UCSRnB) установлен, генерируется запрос на прерывание «регистр данных
пуст». Флаг сбрасывается аппаратно, при записи в регистр данных.
FEn, DORn, UPEn – флаги ошибок (ошибки кадра, переполнения и контроля чётности соответ-
ственно). Устанавливаются в «1» при обнаружении соответствующей ошибки.
U2Xn – когда установлен в «1», удваивает скорость передачи в асинхронном режиме.
MPCMn – когда установлен в «1», включает специальный режим мультипроцессорного обмена.

RXCIEn, TXCIEn, UDRIEn – биты разрешения прерываний соответственно при установке флагов
RXCn, TXCn и UDREn регистра UCSRnA.
RXENn - разрешение приема. При установке этого бита в 1 разрешается работа приемника
USART и переопределяется функционирование вывода RXD (RXDn). При сбросе бита RXENn работа
приемника запрещается, а его буфер сбрасывается.
TXENn - разрешение передачи. При установке этого бита в 1 разрешается работа передатчика
USART и переопределяется функционирование вывода TXD (TXDn). Если бит сбрасывается в 0 во вре-
мя передачи, то выключение передатчика произойдет только после завершения передачи данных, нахо-
дящихся в сдвиговом регистре и буфере передатчика.
UCSZn2 - совместно с битами UCSZn1 и UCSZn0 определяет количество бит данных в посылке
(см. таблицу ниже).
RXB8, TXB8 – используются только в режиме приёма и передачи 9-ти битных данных. Содержат
старший бит данных при приёме и передаче соответственно.

UMSELn – определяет режим работы USART. Если бит сброшен в 0, то модуль работает в асин-
хронном режиме. Если бит установлен в 1, то модуль работает в синхронном режиме.
UPMn1, UPMn0 – задают режим работы схемы контроля и формирования бита четности.
UPMn1 UPMn0 Использование чётности
0 0 Бит чётности не используется
0 1 - (не использовать)
1 0 Включена проверка на чётность при приёме; при передаче автоматически форми-
руется бит чётности как сумма по модулю 2 битов данных передаваемой посылки

26
1 1 Включена проверка на нечётность при приёме; при передаче автоматически фор-
мируется инверсия бита чётности (см. выше)
USBSn – определяет количество стоп-битов в структуре кадра. Если бит сброшен в 0, передатчик
посылает 1 стоп-бит, если установлен в 1, то 2 стоп-бита.
UCSZn1, UCSZn0 – совместно с битом UCSZn2 регистра UCSRnB определяют количество бит
данных в посылке.
UCSZn2 UCSZn1 UCSZn0 Количество бит данных в посылке
0 0 0 5 бит
0 0 1 6 бит
0 1 0 7 бит
0 1 1 8 бит
1 1 1 9 бит
UCPOLn – используется только в синхронном режиме работы USART, определяет момент выда-
чи и считывания данных.
Модуль USART при работе в асинхронном режиме использует две линии связи для обмена дан-
ными с внешним устройством: одну для передачи данных; другую – для приёма. Выводы микро-
контроллера, используемые модулями USART, являются линиями портов ввода/вывода общего назна-
чения. Модуль USART0 использует вывод PORTE.0 (RXD0) для ввода информации в микроконтроллер
и вывод PORTE.1 (TXD0) для вывода информации во внешние устройства. Модуль USART1 занимает
выводы PORTD.2 (RXD1) и PORTD.3 (TXD1) для этих целей.
Данные передаются и принимаются кадрами. Под кадром в данном случае понимается совокуп-
ность одного слова данных и сопутствующей информации.

Кадр начинается со старт-бита, за которым следует младший бит слова данных. После старшего
бита слова данных следует один или два стоп-бита. Если включена схема формирования бита четности,
он включается между старшим битом слова данных и первым стоп-битом. Формат кадра определяется
различными битами регистров UCSRnB и UCSRnС.
В асинхронном режиме скорость приема и передачи данных задается контроллером скорости пе-
редачи, работающим как делитель системного тактового сигнала с программируемым коэффициентом
деления. Коэффициент определяется содержимым регистра контроллера UBRRn. Регистр UBRRn явля-
ется 12-битным и физически размещается в двух регистрах ввода/вывода (UBRRnH и UBRRnL).
Если не используется режим удвоения скорости, то в асинхронном режиме работы USART ско-
рость определяется по следующей формуле:

где fтакт – тактовая частота процессора. BAUD – скорость, бит/с.


Например, для тактовой частоты 4МГц при записи в регистр UBRRn числа «1» скорость составит
4000000 / (16 * (1 + 1)) = 125000 бит/с.
Подробнее про работу USART в микроконтроллере ATmega64 можно прочитать в руководстве
на микроконтроллер.
Ниже приведён пример программы, которая инициализирует USART0 на работу со скоростью
230400 бит/с. В основном цикле программа ожидает приёма байта от компьютера. После приёма байта
данных программа отправляет в компьютер строку виде «Value=x.y», где х – целая часть от деления по-
27
лученного байта на 10; у – остаток от деления на 10. В программе используется функция uart_putchar()
для отправки байта данных на компьютер; функция uart_getchar(), ожидающая приёма байта данных от
компьютера и возвращающая принятый байт. Для того, чтобы используемая в программе функция
printf() могла выводить данные через USART, используется особый приём (он специфичен для исполь-
зуемого компилятора WINAVR и может не работать на других компиляторах языка «С»): объявляется
специальная переменная mystdout, ссылка на которую присваивается стандартному потоку вывода
stdout.
#include <avr/io.h>
#include <stdio.h>

// функция передачи байта через UART


static int uart_putchar(char c, FILE *stream)
{
UCSR0A = (1 << TXC0); // очищаем флаг передачи
UDR0 = c; // начинаем отправку байта
while (!(UCSR0A & (1 << TXC0))) // ждём окончания отправки
;
return 0;
}

// функция приёма байта через UART


char uart_getchar(void)
{
while (!(UCSR0A & (1 << RXC0))) // ждём получения очередного байта
;
return UDR0; // считываем байт из UART
}

// используется для замены stdout на функцию вывода байта в UART


static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

int main(void)
{
// до цикла while(1) пишутся стартовые настройки

// инициализируем UART
UCSR0A = 0x00;
UCSR0B = (1 << RXEN0) | (1 << TXEN0); // включаем режим приёма и передачи
UCSR0C = (0 << UPM01) | (0 << UPM00) | 0x06; // 8 бит данных, без чётности, 1
стоп-бит
UBRR0H = 0x00; // настраиваем скорость 230400 бит/с
UBRR0L = 0x01;

stdout = &mystdout; // переназначаем стандартный вывод на процедуру uart_putchar,


это позволит использовать printf для вывода информации

while (1)
{
char v = uart_getchar(); // ждём прихода любого байта по UART
printf("Value=%d.%d\r", v / 10, v % 10); // выводим строку через UART
}
}

Модуль USART можно использовать для обмена данными с компьютером, однако напрямую
подключить микроконтроллер к компьютеру нельзя - необходимо использовать специальную микро-
схему сопряжения, которая подключается с одной стороны к выводам одного из модулей USART мик-
роконтроллера, а с другой стороны - к USB-порту компьютера. На компьютере для работы с такой мик-
росхемой устанавливается специальный драйвер, который при подключении микросхемы к компьютеру
добавляет в систему виртуальный последовательный порт.
В лабораторном стенде используется одна из таких микросхем сопряжения CP2102 (см. схему
Рис.1.2).

Задание:

28
1. Пользуясь схемой из лабораторной №1, составить и проверить на стенде программу в соот-
ветствии со своим вариантом из таблицы ниже.
2. Написать и сдать отчет по лабораторной работе.
Номер
Задание
варианта
1 Скорость 115200 бит/с. Когда от компьютера приходит символ «1», «2», «3» или «4» (ASCII), зажечь
соответственно светодиод HL1, HL2, HL3 или HL4. Если приходит любой другой байт данных, пога-
сить все светодиоды.
2 Скорость 57600 бит/с. После приёма очередного байта от компьютера, анализировать его, и если он
чётный, выдавать в компьютер строку «Even»; иначе – строку «Odd».
3 Скорость 38400 бит/с. Когда от компьютера приходит символ «+», после этого принять ещё два бай-
та данных, затем выдать в компьютер строку вида «Sum=x», где x – сумма двух байтов, пришедших
после «+». Учесть, что сумма может превышать по разрядности один байт (8 бит).
4 Скорость 28800 бит/с. Когда от компьютера приходит символ «S», в ответ выдать строку вида
«ADC=x.y», где «x.y» - напряжение на входе ADC6 с точностью до десятой доли вольта.
5 Скорость 19200 бит/с. При нажатии кнопки «Test» посылать в компьютер один раз строку «Pressed»,
при отжатии кнопки – строку «Released», также один раз.
6 Скорость 9600 бит/с. Когда от компьютера приходит символ «Q», в ответ выдавать строку «Pressed»,
если кнопка «Test» нажата, или «Released», если кнопка отжата.
7 Скорость 4800 бит/с. Когда от компьютера приходит символ в диапазоне от «0» до «9» (ASCII),
включить звукоизлучатель на время соответственно от 100 мс до 1000 мс.
8 Скорость 2400 бит/с. Управлять яркостью светодиода HL6 с помощью ШИМ пропорционально зна-
чению байта данных, полученному от компьютера (0 – минимальная яркость; 255 – максимальная)
9 Скорость 115200 бит/с. По нажатию на кнопку «Test» выдавать в компьютер одно случайное число в
диапазоне от 0 до 255 в виде строки. Для генерации случайного числа использовать содержимое
таймера, считающего с тактовой частотой. Устранить эффект дребезга кнопки.
Контрольные вопросы
1. Каким образом передаются данные с помощью модуля UART (описать структуру посылки)? Сколь-
ко линий необходимо, чтобы связать между собой два микроконтроллера с помощью UART? Настроить
UART в микроконтроллере на работу в различных режимах по выбору преподавателя.
2. Зачем нужен бит чётности? Каким образом настраивается бит чётности? Какие режимы формирова-
ния бита чётности поддерживаются микроконтроллером? Для чего используется данный бит?
3. Какова максимальная скорость передачи данных при тактовой частоте, используемой в нашей схе-
ме? Почему в схеме используется кварцевый резонатор на частоту 7372800Гц? В чём плюсы и минусы
использования высоких скоростей по UART? Рассчитать значения, которые необходимо записать в ре-
гистры скорости передачи для заданной преподавателем пропускной способности линии в байтах/сек.
4. Как настроить модуль UART, если в работе нужен только приёмник или только передатчик?
5. Зачем нужен встроенный в модуль UART буфер на приём и на передачу?
6. Как организуется 9-битная передача данных? Написать пример кода для инициализации UART в 9-
битный режим; для отправки и приёма 9 бит данных.
7. Как реализована отправка данных через UART с помощью функции printf в данной работе? Какой
код необходимо дописать к программе и что необходимо в ней изменить, чтобы функция printf() могла
выводить данные как через порт UART0, так и через UART1?
8. В лабораторной работе в процедуре передачи данных используется флаг TXC, зачем? Как исполь-
зовать флаг UDRE для этих целей? В чём отличия в использовании обоих флагов?
9. Как связать между собой три микроконтроллера с помощью UART, чтобы двустороннюю связь
имели 1-ый с 3-им, а также 1-ый со 2-ым? Какие моменты нужно предусмотреть в программе?
10. Написать программу для приёма и записи в буфер пакетов данных следующего вида: НТ+data+КТ
(где НТ-байт, обозначающих начало сообщения (он задан в виде константы); КТ-байт, обозначающих
конец сообщения (также задан как константа); data- произвольное количество байт информации, не пре-
вышающее 10 и не содержащее байт КТ).
11. Реализовать отправку пакета данных длиной 10 байт через UART с использованием прерываний.

Лабораторная работа №7. Динамическая индикация


Цель работы: научиться выводить информацию на трёхразрядный семисегментный индикатор
методом динамической индикации.
29
Краткие теоретические сведения.
Для отображения цифровой информации в системах на базе микроконтроллеров используются
светодиодные семисегментные индикаторы. Они просты в управлении, имеет высокую яркость, широ-
кий диапазон рабочих температур и низкую стоимость. К недостаткам светодиодных индикаторов отно-
сятся – относительно высокое энергопотребление, отсутствие встроенного управляющего контроллера и
скудные возможности по выводу буквенной информации.
Светодиодный семисегментный индикатор представляет собой группу светодиодов расположен-
ных в определенном порядке и объединенных конструктивно. Зажигая одновременно несколько свето-
диодов можно формировать на индикаторе символы цифр. Индикаторы различаются по типу соедине-
ния светодиодов – общий анод, общий катод, по количеству отображаемых разрядов – однораразряд-
ные, двух разрядные, трёхразрядные и т.д. и по цвету – красные, зеленые, желтые и т.д.

Семисегментным индикатором можно управлять статически или динамически.


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

К достоинствам статической индикации относятся более простое управление состоянием каждо-


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

Динамическое управление (динамическая индикация) подразумевает поочередное зажигание


разрядов индикатора. Поскольку система восприятия зрительной информации у человека обладает
инерцией, то при выборе достаточно высокой частоты переключения будет казаться, что все разряды
горят постоянно. Практика показывает, что частота переключения разрядов должна быть не менее
50Гц*количество разрядов. Лучше использовать частоты не кратные 50 Гц, иначе при искусственном
освещении может появиться мерцание.

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

В данной работе будет использоваться отладочная плата «PinBoardII» с микроконтроллером


«ATmega16» и трёхразрядным семисегментным индикатором FYT-3631AG, который имеет следующую
внутреннюю схему:

Как видно из рисунка, это индикатор с общим катодом.


Схема отладочной платы «PinBoardII»:

31
Рис 7.1

Рекомендуемая последовательность действий для организации динамической индикации:


1. Установить порты, используемые для вывода информации в индикатор, на выход (это цепи
D0..D7 и A1..A3).
2. Инициализировать Таймер 1 на работу в режиме «CTC» таким образом, чтобы частота перепол-
нения таймера соответствовала частоте переключения разрядов индикатора.
3. Инициализировать систему прерываний, чтобы при переполнении таймера вызывалась соответ-
ствующая процедура.
4. Ввести в программу глобальный массив констант SYMBOLS[] длиной 10 элементов. Каждый
элемент массива соответствует цифре от «0» до «9» и содержит данные, которые необходимо
вывести на шину D0..D7, чтобы зажечь на индикаторе эту цифру (от «0» до «9»).
*Примечание: если в вашем варианте задания используется вывод чисел в шестнадцатеричной
системе, то массив SYMBOLS[] должен быть длиной 16 элементов (в последние 6 элементов
необходимо дописать данные по дополнительным символам шестнадцатеричной системы (бук-
вам «А», «b», «C», «d», «E», «F»).

32
5. Ввести в программу глобальный массив переменных Digs[] длиной 3 элемента. В данном массиве
будут содержаться данные, которые будут выводиться в соответствующий разряд индикатора.
6. Организовать процедуру прерывания таким образом, чтобы при каждом вызове этой процедуры
происходила:
а) активация очередного разряда индикатора (установка в «1» соответствующего вывода шины
A1..A3, остальные выводы шины должны быть установлены в «0»)
б) выводом на шину D0..D7 соответствующего элемента массива Digs[].
7. Проинициализировать массив Digs[] начальными значениями.
8. Разрешить прерывания.

Примечание:
 В отладочной плате «PinBoardII» используется микроконтроллер «ATmega16», у которого, по
сравнению с ранее использованным микроконтроллером «ATmega64», имеются отличия в орга-
низации периферии. В частности, у него не 2 блока USART, а 1, и кроме того, имеются особен-
ности при записи значений в регистры UCSRC и UBRRH, поэтому для тех вариантов, где ис-
пользуется модуль USART для обмена информацией с компьютером, используемые ранее про-
цедуры для работы с USART модифицируются следующим образом:
#include <avr/io.h>
#include <stdio.h>

// функция передачи байта через UART


static int uart_putchar(char c, FILE *stream)
{
UCSRA = (1 << TXC); // очищаем флаг передачи
UDR = c; // начинаем отправку байта
while (!(UCSRA & (1 << TXC))) // ждём окончания отправки
;
return 0;
}

// функция приёма байта через UART


char uart_getchar(void)
{
while (!(UCSRA & (1 << RXC))) // ждём получения очередного байта
;
return UDR; // считываем байт из UART
}

// используется для замены stdout на функцию вывода байта в UART


static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

int main(void)
{
// инициализируем UART
UCSRA = 0x00;
UCSRB = (1 << RXEN) | (1 << TXEN); // включаем режим приёма и передачи
UCSRC = (1 << URSEL) | (0 << UPM1) | (0 << UPM0) | 0x06; // 8 бит данных, без чёт-
ности, 1 стоп-бит
UBRRH = 0x00; // настраиваем скорость 230400 бит/с
UBRRL = 0x01;

stdout = &mystdout; // переназначаем стандартный вывод на процедуру uart_putchar,


это позволит использовать printf для вывода информации
}
 Если в вашем варианте лабораторной работы необходима генерация звукового сигнала следует
учитывать, что в отладочной плате «PinBoardII» используется звукоизлучатель без встроенного
генератора. Однако, он подключен к выводу микроконтроллера «PD7/OC2», и можно настроить
33
Таймер2 таким образом, чтобы на данном выводе появлялся меандр нужной нам частоты, что
приведёт к генерации соответствующего звукового сигнала. Для инициализации таймера и
управления звуком можно использовать следующий код:
TCCR2 = (1 << WGM21) | (0 << WGM20) | (0 << COM21) | (1 << COM20) | (1 << CS22) |
(0 << CS21) | (0 << CS20); // prescaler=64
OCR2 = 0x1C; // Sound On
OCR2 = 0; // Sound Off

Ниже приведён пример программы. Её можно взять как основу для выполнения вашего варианта.
Программа выводит на индикатор трёхзначное число, которое увеличивается на 1 каждые 0,5 сек. В не-
скольких местах программы часть кода намеренно заменена на символы «?????». Вам необходимо по
логике работы и контексту восстановить недостающую часть кода.

#include <avr/io.h>
#define F_CPU 7372800UL // контролер работает с частотой 7.3728 МГц
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>

// константы, соответствующие сегментам индикатора


#define _A (1<<7)
#define _B (1<<5)
#define _C (1<<?????)
#define _D (1<<1)
#define _E (1<<0)
#define _F (1<<?????)
#define _G (1<<4)
#define _H (1<<2)

// Массив констант
// Каждый элемент массива соответствует цифре от "0" до "9" и содержит данные,
// которые необходимо вывести на шину D0..D7, чтобы зажечь на индикаторе эту цифру
// В данном варианте массив SYMBOLS[] расширен до 16 элементов, чтобы можно было выводить
буквы "A".."F" шестнадцатеричной системы
const char SYMBOLS[16] = {
_A+_B+_C+_D+_E+_F, // 0
_B+_C, // 1
_A+_B+_G+_E+_D, // 2
_A+_B+_G+_C+_D, // 3
?????, // 4
_A+_F+_G+_C+_D, // 5
_A+_F+_E+_D+_C+_G, // 6
?????, // 7
_A+_B+_C+_D+_E+_F+_G, // 8
_A+_B+_C+_D+_F+_G, // 9
_A+_B+_C+_E+_F+_G, // A
_F+_E+_D+_C+_G, // b
_A+_F+_E+_D, // C
_B+_C+_D+_E+_G, // d
_A+_D+_E+_F+_G, // E
_A+_E+_F+_G, // F
};

// В данном массиве будут содержаться данные, которые будут выводиться в соответствующий


разряд индикатора
char Digs[3];

///////////////////////////////////////////////////////////////////////////////
34
// Прерывание по переполнению таймера 1
ISR (TIMER1_COMPA_vect)
{
// текущий разряд индикатора
static unsigned char curdig = 0;

// активация очередного разряда индикатора


PORTB = (1 << (PB5 + ?????));
// вывод данных в текущий разряд индикатора
PORTC = Digs[curdig];

// переход к следующему разряду


curdig++;
if (curdig > 2)
curdig = 0;
}

// Процедура полготовки вывода числа "v" на индикатор


void PrintDigs(int v, unsigned char dp)
{
Digs[0] = SYMBOLS[v / 100];
Digs[1] = SYMBOLS[(v % 100) / 10];
Digs[2] = SYMBOLS[?????];
if (dp <= 2)
Digs[dp] |= _H;
}

///////////////////////////////////////////////////////////////////////////////

int main(void)
{
// до цикла while (1) пишутся стартовые настройки
DDRB = 0xF0; // порт для переключения разрядов индикатора
PORTB = 0;
DDRC = 0xFF; // порт используется для управления сегментами
PORTC = 0x00;

// инициализируем таймер для работы в режиме CTC c периодом переполнения 5 мс


OCR1A = ?????; // устанавливаем максимальное значение счётчика
// Инициализация режима CTC
// и установка предварительного делителя = 1
TCCR1A = (0 << WGM11) | (0 << WGM10);
TCCR1B = (0 << WGM13) | (1 << WGM12) | (0 << CS12) | (0 << CS11) | (1 << CS10);
TIMSK |= (1 << OCIE1A);

sei(); // разрешаем прерывания

int c = 0;

while (1)
{
PrintDigs(c, 5); // выводим число на индикатор
_delay_ms(500);
c++;
}
}

35
Варианты выполнения лабораторной работы:
Номер
Задание
варианта
1 Вывести на индикатор напряжение в вольтах на средней точке потенциометра R54 с точностью 2
знака после запятой (запятую тоже отобразить на индикаторе)
2 Инициализировать USART на скорость 19200 бит/с. С компьютера на устройство посылаются байты
информации. Отображать на индикаторе сумму байтов, принятых с компьютера
3 Отображать на индикаторе количество секунд, прошедших с момента включения устройства
4 Инициализировать USART на скорость 19200 бит/с. С компьютера на устройство посылаются байты
информации. Отображать на индикаторе последний принятый байт в шестнадцатеричной системе
5 Инициализировать USART на скорость 19200 бит/с. С компьютера на устройство посылаются байты
информации. Отображать на индикаторе количество байтов, принятых с компьютера
6 Инициализировать USART на скорость 19200 бит/с. С компьютера на устройство посылается 1 байт
информации. После получения этого байта запустить таймер обратного отсчёта с отображением
оставшегося количества секунд на индикаторе. Начальное значение таймера (в секундах) должно
соответствовать полученному байту. После достижения нулевого значения отобразить на индикато-
ре «---» и включить звуковой сигнал.
7 Инициализировать USART на скорость 19200 бит/с. Отобразить начальное значение на индикаторе =
«000». С компьютера на устройство посылаются байты информации. После получения очередного
байта от компьютера, начать быстро (со временем порядка 50 мс на итерацию) менять на 1 текущее
значение на индикаторе в сторону полученного от компьютера до момента достижения полученного
значения.
8 Инициализировать USART на скорость 19200 бит/с. С компьютера посылается строка в кодировке
ACSII, содержащая 3 цифры и (необязательно) символ «.». Отобразить на индикаторе полученное
число (включая «.» если она присутствовала в посылке).
Контрольные вопросы
1. Назовите виды управления индикатором, их свойства, плюсы и минусы.
2. Что нужно изменить в принципиальной схеме и в программе при увеличении количества разрядов
индикатора? Что лучше изменить, если количество разрядов увеличится значительно (больше 8)?
3. Как выбрать частоту обновления разрядов при динамической индикации?
4. В чём отличия индикаторов с общим катодом от индикаторов с общим анодом? Как отличаются их
схемы включения?
5. Возможно ли организовать статическую индикацию для индикатора, используемого в лабораторной
работе?
6. Почему в схеме включения индикатора используются транзисторы? Можно ли обойтись без них?
Что будет, если открыть сразу два транзистора? Как выбираются номиналы и мощности резисторов,
идущих на сегменты индикатора, а также резисторы в базе транзисторов? Что будет, если убрать (зако-
ротить) резисторы, идущие на сегменты индикатора, а вместо них включить один резистор в коллектор
каждого транзистора?
7. Пояснить пошагово алгоритм, используемый для вывода информации на индикатор в режиме дина-
мической индикации.
8. Пользуясь материалом лабораторной №6, организовать вывод информации на индикатор с помо-
щью функции printf().

Лабораторная работа №8. Матричная клавиатура


Цель работы: научиться опрашивать матричную клавиатуру.
Нередко в устройстве, собранном с применением микроконтроллеров, предусмотрен ввод дан-
ных с использованием кнопок, переключателей или других контактных групп. Реализовать такое
схемное решение очень просто, учитывая то, что кроме собственно кнопки и подтягивающего резистора
(и то, в некоторых случаях он не нужен) больше ничего не надо. Но простое подключение контактных
групп к линиям ввода/вывода микроконтроллера может породить проблему нехватки этих самых линий,
если таких контактных групп много. Решение проблемы довольно простое - использование клавиатур-
ной матрицы.
Схема клавиатурной матрицы представлена на рисунке 8.1. Кнопки включены таким образом,
что при нажатии кнопка замыкает строку на столбец. Часть линий контроллера используется в качестве
сканирующих (ROW1..ROW3), а часть в качестве считывающих (COL1..COL4). Количество кнопок,
36
подключенных таким образом, определяется как количество сканирующих линий умноженное на коли-
чество считывающих. Отсюда следует, что использование матричной клавиатуры для случая, когда
кнопок меньше или равно четырем, не имеет смысла, так как понадобятся те же четыре линии, а схема и
прошивка усложнятся.

Рис 8.1
Существует модификация данной схемы, когда считывающие линии подтягиваются резисторами
к питанию, а в разрыв сканирующих линий включаются диоды:

Однако, во многих случаях достаточно в качестве подтягивающих резисторов использовать


встроенные в микроконтроллер (если таковые предусмотрены в структуре микроконтроллера), а вместо
диодов, включённых в разрыв сканирующих линий, использовать программное управление направлени-
ем сканирующих линий, когда активная линия настраивается на выход и в неё выводится «0», а неак-
тивные линии переводятся в Z-состояние.
Алгоритм опроса матричной клавиатуры
1. Активируем первую сканирующую линию. Для этого вывод микроконтроллера, подсоединённый
к цепи ROW1, устанавливается на выход и в него выводится логический «0». Остальные скани-
рующие линии (ROW2 и ROW3) устанавливаем в Z-состояние.
2. Анализируем состояние считывающих линий COL1..COL4. Если на линии логический «0», то
нажата соответствующая кнопка (K1..K4), если «1» - кнопка не нажата.

37
3. Повторяем пп. 1-2 для второй (ROW2) и третьей (ROW3) сканирующей линии, определяя теку-
щее состояние кнопок K5..K8 и K9..K12 соответственно.
Вышеприведённый алгоритм позволяет также определять одновременное нажатие нескольких кнопок.
Алгоритм также желательно дополнить процедурой устранения дребезга, которая может быть
реализована, например, следующим образом:
1. Опросить клавиатуру. Определить номер нажатой кнопки K1..K12 или зафиксировать тот факт,
что ни одна кнопка не нажата.
2. Подождать некоторое время (например, 5 мс).
3. Повторять пп. 1-2 несколько раз (чтобы суммарное время проверки превысило типовое время
дребезга контактов, ориентировочно 30-40 мс), пока номер нажатой кнопки повторяет номер, по-
лученный в предыдущей итерации. Если не повторяет, начать выполнять пп.1-2 заново.
Для опроса клавиатуры с устранением дребезга контактов удобнее всего использовать периодически
вызываемое прерывание, чтобы не тратить процессорное время на ожидание в течение времени дребез-
га.

Примечание:
 Для некоторых вариантов выполнения лабораторной работы каждой кнопке на отладочной плате
должен соответствовать символ. При выполнении таких вариантов следует руководствоваться
следующей схемой:
«1» «2» «3»
«4» «5» «6»
«7» «8» «9»
«*» 0 «#»
 Для некоторых вариантов выполнения лабораторной работы требуется генератор случайных чи-
сел. Для этих целей можно использовать значение счётного регистра 8-битного таймера, такти-
руемого с тактовой частотой. Значение из счётного регистра выбирается в момент нажатия поль-
зователем на кнопки.

Ниже приведён пример программы. Её можно взять как основу для выполнения вашего варианта.
В программе организуется ввод с клавиатуры и вывод на семисегментный индикатор трёхзначного чис-
ла. В нескольких местах программы часть кода намеренно заменена на символы «?????». Вам необхо-
димо по логике работы и контексту восстановить недостающую часть кода.

#include <avr/io.h>
#define F_CPU 7372800UL // контролер работает с частотой 7.3728 МГц
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>

// частота переключения звукоизлучателя


#define BUZ_FREQ 0x1C

// константы, соответствующие сегментам индикатора


#define _A (1<<7)
#define _B (1<<5)
#define _C (1<<?????)
#define _D (1<<1)
#define _E (1<<0)
#define _F (1<<?????)
#define _G (1<<4)
#define _H (1<<2)

// Массив констант
// Каждый элемент массива соответствует цифре от "0" до "9" и содержит данные,
// которые необходимо вывести на шину D0..D7, чтобы зажечь на индикаторе эту цифру

38
// В данном варианте массив SYMBOLS[] расширен до 16 элементов, чтобы можно было выводить
буквы "A".."F" шестнадцатеричной системы
const char SYMBOLS[16] = {
_A+_B+_C+_D+_E+_F, // 0
_B+_C, // 1
_A+_B+_G+_E+_D, // 2
_A+_B+_G+_C+_D, // 3
?????, // 4
_A+_F+_G+_C+_D, // 5
_A+_F+_E+_D+_C+_G, // 6
?????, // 7
_A+_B+_C+_D+_E+_F+_G, // 8
_A+_B+_C+_D+_F+_G, // 9
_A+_B+_C+_E+_F+_G, // A
_F+_E+_D+_C+_G, // b
_A+_F+_E+_D, // C
_B+_C+_D+_E+_G, // d
_A+_D+_E+_F+_G, // E
_A+_E+_F+_G, // F
};

// В данном массиве будут содержаться данные, которые будут выводиться в соответствующий


разряд индикатора
char Digs[3];

// номер нажатой кнопки (0xFF - ни одна кнопка не нажата)


volatile unsigned char key = 0xFF;

// массив перекодировки номера нажатой кнопки в цифру


const char KEY2DIG[12] = {1, 4, 7, '*', 2, ?????, ?????, 0, 3, 6, 9, '#'};

///////////////////////////////////////////////////////////////////////////////
// Timer 1 output compare A interrupt service routine
ISR (TIMER1_COMPA_vect)
{
// текущий разряд индикатора и текущий номер сканирующей линии
static unsigned char curdig = 0;
// номер нажатой в предыдущий цикл кнопки
static char oldkey = 0xFF;
// кол-во циклов, в течение который считывался один и тот же номер кнопки
static char oldkeycntr = 0;
// номер нажатой в данный момент кнопки
static char curkey = 0xFF;

// активация очередной сканирующей линии


DDRB = (1 << (????? + curdig)) | 0xF0;
// активация очередного разряда индикатора
PORTB = (1 << (PB5 + ?????));
// вывод данных в текущий разряд индикатора
PORTC = Digs[curdig];

// считываем состояния всех кнопок в текущей сканирующей линии


char k = (PINA & 0x1E);
// получаем номер нажатой кнопки в зависимости от номера сканирующей и считывающей ли-
ний
if (k != ?????)
{
char kk;

39
if ((k & (1 << 1)) == 0)
kk = 0;
else if ((k & (1 << 2)) == 0)
kk = 1;
else if ?????
?????;
else
?????
curkey = (curdig * ?????) + kk;
}

// переход к следующему разряду


curdig++;
if (curdig > 2)
{
curdig = 0;
// устраняем дребезг клавиатуры
if (curkey != ?????) // если в текущем опросе получили номер кнопки, от-
личный от предыдущего
{
oldkey = curkey; // запоминаем новый номер кнопки
oldkeycntr = ?????;// переинициализируем счётчик
}
else if (oldkeycntr) // иначе декрементируем счётчик
if (--oldkeycntr == 0) // если достигли 0, то фиксируем нажатие
кнопки
key = oldkey;
curkey = 0xFF;
}
}

// Процедура полготовки вывода числа "v" на индикатор


void PrintDigs(int v, unsigned char dp)
{
Digs[0] = SYMBOLS[v / 100];
Digs[1] = SYMBOLS[(v % 100) / 10];
Digs[2] = SYMBOLS[?????];
if (dp <= 2)
Digs[dp] |= _H;
}

void sound_on(void)
{
OCR2 = BUZ_FREQ;
}

void sound_off(void)
{
OCR2 = 0;
}

// ожидание нажатия кнопки


char WaitKeyDown(void)
{
while (?????)
;
return KEY2DIG[key];
}

40
// ожидание отпускания кнопки
void WaitKeyUp(void)
{
while (?????)
;
}

// генерация короткого звукового сигнала, затем ожидание отпускания кнопки


void BeepWaitKeyUp(void)
{
sound_on();
_delay_ms(10);
sound_off();
while (?????)
;
}

///////////////////////////////////////////////////////////////////////////////

int main(void)
{
// до цикла while (1) пишутся стартовые настройки
DDRB = 0xF0; // порт для переключения разрядов индикатора
PORTB = 0;
DDRC = 0xFF; // порт используется для управления сегментами
PORTC = 0x00;
DDRA= 0x00; // считывающие линии клавиатуры
PORTA = 0x1E;
DDRD = (1 << PD7); // sound pin

// инициализируем таймер для работы в режиме CTC c периодом переполнения 5 мс


OCR1A = ?????; // устанавливаем максимальное значение счётчика
// Инициализация режима CTC
// и установка предварительного делителя = 1
TCCR1A = (0 << WGM11) | (0 << WGM10);
TCCR1B = (0 << WGM13) | (1 << WGM12) | (0 << CS12) | (0 << CS11) | (1 << CS10);
TIMSK |= (1 << OCIE1A);

OCR2 = 0; // No sound
TCCR2 = (1 << WGM21) | (0 << WGM20) | (0 << COM21) | (1 << COM20) | (1 << CS22) |
(0 << CS21) | (0 << CS20); // pre=64

sei();

int c = 0;
PrintDigs(c, 5);

while (1)
{ // вводим с клавиатуры и выводим на индикатор трехзначное число
c = 0;
c += WaitKeyDown() * 100;
PrintDigs(c, 5);
BeepWaitKeyUp();
c += WaitKeyDown() * 10;
PrintDigs(c, 5);
BeepWaitKeyUp();
c += WaitKeyDown() * 1;

41
PrintDigs(c, 5);
BeepWaitKeyUp();
}
}

Варианты выполнения лабораторной работы:


Номер
Задание
варианта
1 Используется клавиатура и семисегментный индикатор. В программе в виде константы задаётся
цифровой трёхзначный код (пароль). Пользователь последовательно вводит три цифры на кнопоч-
ной клавиатуре, каждая введённая цифра отображается на соответствующем разряде индикатора. По
окончании ввода последней цифры код проверяется на совпадение с паролем. Если пароль совпал,
отобразить на индикаторе «OPn», иначе – «Err».
2 Используется клавиатура и семисегментный индикатор. Пользователь последовательно вводит три
цифры на кнопочной клавиатуре, каждая введённая цифра отображается на соответствующем разря-
де индикатора. Эти три цифры образуют первое число. Затем вводится «*», после чего пользователь
вводит второе число. После нажатия «#» на индикатор вводится сумма двух введённых чисел.
3 Используется клавиатура и звукоизлучатель. Пока нажата кнопка, издавать звуковой сигнал опреде-
лённой частоты, зависящий от номера кнопки («1» - 523 Гц; «2»-587; «3»-659; «4»-698; «5»-783; «6»-
880; «7»-987; «8»-1046; «9»-1174; «*»-1318; «0»-1396; «#»-1568). Когда ни одна кнопка не нажата,
выключать звук.
4 Используется клавиатура и семисегментный индикатор. Пользователь последовательно вводит циф-
ры на кнопочной клавиатуре, каждая введённая цифра отображается на соответствующем разряде
индикатора. Реализовать возможность выводить на индикатор также символы-буквы из шестнадца-
теричной системы («А», «b», «C», «d», «E», «F»). Буква должна вводиться путём нажатия на кнопку
«*» и последующего нажатия на кнопки «1»..«6» соответственно.
5 Используется клавиатура и USART на скорости 19200 бит/с. При нажатии на кнопку отправлять в
компьютер строку «Pressed x», где x – ASCII символ, соответствующий нажатой кнопке. При отпус-
кании отправлять строку «Released x». Программа должна выдавать корректные данные также в слу-
чае, когда пользователь нажимает/отпускает кнопку, удерживая нажатой другую кнопку или не-
сколько кнопок.
6 Используется клавиатура и семисегментный индикатор. Генерируется 2 случайных числа от 0 до
255. На экране показываются в течение секунды сначала одно, затем второе, затем индикатор очи-
щается. Пользователь последовательно вводит три цифры на кнопочной клавиатуре, каждая введён-
ная цифра отображается на соответствующем разряде индикатора. Проверяется, чтобы введённое
число было равно сумме двух предложенных ранее чисел. Если сумма совпала, отобразить на инди-
каторе «УРА», иначе – «Err».
7 Используется клавиатура и USART на скорости 19200 бит/с. Пользователь последовательно вводит
цифры на кнопочной клавиатуре, ASCII-код каждой введённой цифры должен отправляться в ком-
пьютер. В момент нажатия на кнопку должен раздаваться короткий звуковой сигнал.
8 Используется клавиатура и семисегментный индикатор. Генерируется случайная цифра от 0 до 9 и
отображается на индикаторе, также на индикаторе отображается номер раунда (для начала «1»). В
течение 1 секунды пользователь должен успеть нажать на кнопку, соответствующую показанной
цифре. Если пользователь успел нажать на кнопку, номер раунда увеличивается на 1; генерируется и
отображается следующая случайная цифра, но время, отведённое на ввод пользователя, уменьшается
на 50 мс. Если пользователь не успел, то на индикаторе отображается в течение 1 сек. надпись «Err»,
после чего игра начинается заново, с 1-го раунда и максимального времени отображения.
Контрольные вопросы
1. Матричное включение клавиатуры: достоинства и недостатки.
2. Пояснить пошагово алгоритм, используемый для опроса матричной клавиатуры.
3. Как в программе устраняется эффект дребезга клавиатуры? Какое максимальное время дребезга до-
пускается? Учитывается ли дребезг при отпускании кнопки? Что следует изменить в программе, если
будут использоваться кнопки с бОльшим временем дребезга?
4. Для чего в «классической» схеме подключения матричной клавиатуры используются резисторы и
диоды? Почему в нашей схеме не используются резисторы и диоды при подключении клавиатуры? Ка-
кие отрицательные последствия может принести отсутствие внешних резисторов?
5. При каком числе кнопок клавиатуры выгоднее использовать матричное включение?

42
6. Как модифицировать программу, чтобы можно было определить факт нажатия сразу двух кнопок?
Любого числа кнопок?
7. Когда микроконтроллер находится в глубоком спящем режиме, программа не выполняется, но со-
храняется состояние выводов. Предположим, что пробудить микроконтроллер их спящего режима мо-
жет любое изменение цифровых сигналов на входах PA1..PA4 (к которым подключены считывающие
линии клавиатуры). Как сконфигурировать выводы микроконтроллера перед переходом в спящий ре-
жим, чтобы он пробуждался из спящего режима при нажатии любой клавиши?

Лабораторная работа №9. Жидкокристаллический индикатор


Цель работы: научиться выводить данные на жидкокристаллический индикатор.
Многие фирмы выпускают жидкокристаллические индикаторы (ЖКИ) со встроенными контрол-
лерами, облегчающими реализацию интерфейса ЖКИ и микропроцессора. В данной лабораторной ра-
боте используется алфавитно-цифровой ЖКИ, построенный на базе контроллера HD44780. Интерфейс к
данному контроллеру стал промышленным стандартом де-факто на рынке символьных алфавитно-
цифровых дисплеев.
Алфавитно-цифровые ЖКИ-модули представляют собой недорогое и удобное решение, позво-
ляющее сэкономить время и ресурсы при разработке новых изделий, при этом обеспечивают отображе-
ние большого объема информации при хорошей различимости и низком энергопотреблении. Возмож-
ность оснащения ЖКИ-модулей задней подсветкой позволяет эксплуатировать их в условиях с пони-
женной или нулевой освещенностью, а исполнение с расширенным диапазоном температур (-
20°С...+70°С) в сложных эксплуатационных условиях, в том числе в переносной, полевой и даже, ино-
гда, в бортовой аппаратуре.
Контроллер HD44780 потенциально может управлять 2-мя строками по 40 символов в каждой
(для модулей с 4-мя строками по 40 символов используются два однотипных контроллера), при матрице
символа 5 х 7 точек.
Существует несколько различных более-менее стандартных форматов ЖКИ-модулей (символов
х строк): 8 х 2, 16 х 1, 16 х 2, 16 х 4, 20 х 1, 20 х 2, 20 х 4, 24 х 2, 40 х 2, 40 х 4.
В рамках одного формата могут производиться ЖКИ-модули нескольких конструктивов, отли-
чающихся как габаритами ЖКИ (и, как следствие, размерами символов), так и размерами платы и по-
садки.
В рамках одного конструктива ЖКИ-модуль может иметь еще ряд модификаций. В частности,
могут применяться несколько типов ЖКИ, отличающихся цветом фона и цветом символов, а также по
применяемым ЖК-материалам и структуре: TN, STN и FSTN типа. ЖКИ STN и FSTN типа имеют более
высокую стоимость, но одновременно обладают повышенной контрастностью и вдвое большим макси-
мальным углом обзора, причем ЖКИ FSTN типа имеют лучшие характеристики, чем STN.
ЖКИ-модули могут оснащаться задней подсветкой, размещаемой между ЖКИ и печатной пла-
той, для чего ЖКИ производятся с полупрозразным или прозрачным задним слоем (в последнем случае
считывание информации возможно только при наличии подсветки). Собственно подсветка может быть
реализована несколькими способами: с помощью электролюминисцентной панели, представляющей
собой тонкую пленку, излучающую свет при прикладывании переменного тока повышенного напряже-
ния порядка 100...150 В; люминисцетной лампой с холодным катодам (также раюотающей при повы-
шенном напряжении), излучение которой равномерно распределяется по всей площади ЖКИ с помо-
щью отражателя или плоского световода; третий вариант - подсветка на основе светодиодной матрицы.
Первые два способа подсветки обеспечивают высокую яркость и могут иметь белый тон свече-
ния при относительно низком потреблении, но требуют наличия источника повышенного напряжения,
что создает некоторые трудности при создании аппаратуры с автономным питанием. Напротив, свето-
диодная подсветка не требует высоковольтного источника (прямое падение напряжения составляет 4,2
В) и при использовании несложного источника тока позволит производить питание от источника с
напряжением 5 В. Кроме того, светодиодная подсветка имеет значительно большее (в десятки раз) вре-
мя наработки, а также только она допустима к эксплуатации в расширенном диапазоне температур (-
20°С...+70°С).
В настоящее время выпускаются алфавитно-цифровые OLED дисплеи, подключаемые по тому
же интерфейсу. Они обладают существенно большей контрастностью; более широким температурным
диапазоном (от -40°С); низкой инерционностью изображения (что особенно заметно при низких темпе-

43
ратурах при сравнении с ЖКИ); большими углами обзора; более низким энергопотреблением (если
сравнивать с ЖКИ, имеющим подсветку сравнимой яркости) и не имеют отдельной подсветки, посколь-
ку каждый пиксель сам является источником света.
Сопряжение с индикатором
Для соединения ЖКИ-модуля с управляющей системой используется параллельная синхронная
шина, насчитывающая 8 линий данных D0...D7, линию выбора операции чтения/записи R/W, линию
выбора регистра RS и линию стробирования/синхронизации Е. Кроме линий управляющей шины име-
ются две линии для подачи напряжения питания 5 В - GND и VCC, и линия для подачи напряжения пи-
тания драйвера ЖКИ и регулировки контрастности CONTR. Ещё два вывода LED+ и LED- используют-
ся для подачи напряжения подсветки. На эти выводы можно спокойно подавать напряжение 5В – эле-
менты, используемые для задания тока через светодиоды подсветки, расположены на плате самого ин-
дикатора.
Таким образом, для соединения ЖКИ и микроконтроллера потребуется 11 сигнальных линий.
Существует возможность сократить число линий до 7, использовав не 8-битную шину данных, а 4-
битную. В последнем случае к микроконтроллеру подключаются только линии D4..D7. При работе в 4-
битном режиме имеют особенности в процедурах инициализации и обмена данными с ЖКИ. Подробнее
об этом можно прочитать в документации на контроллер HD44780. В данной работе будет использован
8-битная шина.
Схема включения ЖКИ, рассчитанного на стандартный диапазон температур, используемая в ла-
бораторном стенде, показана ниже. Подстроечный резистор R57 позволяет плавно менять напряжение
питания драйвера ЖКИ на входе CONTR, что приводит к изменению угла поворота жидких кристаллов.
Этим резистором можно отрегулировать фактическую контрастность при некотором преимуществен-
ном угле наблюдения (снизу-вверх или сверху-вниз). Если предполагается использовать ЖКИ-модуль,
рассчитанный на расширенный диапазон температур, на вход CONTR необходимо подавать отрица-
тельное напряжение относительно GND. Для этого может понадобиться использовать отдельный мало-
мощный источник отрицательного напряжения –5В.

44
Последовательности действий, которые необходимо выполнять управляющему микроконтролле-
ру при совершении операций записи и чтения приведены ниже.
Операция записи:
1. Установить значение линии RS, в зависимости от того, к какому регистру идёт обращение
2. Установить линию R/W = 0
3. Установить линии шины D0...D7 на выход
4. Вывести значение байта данных на линии шины D0...D7
5. Установить линию E = 1
6. Установить линию E = 0
Операция чтения:
1. Установить линии шины D0...D7 на вход
2. Установить значение линии RS, в зависимости от того, к какому регистру идёт обращение
45
3. Установить линию R/W = 1
4. Установить линию Е = 1
5. Считать значение байта данных с линий шины D0...D7
6. Установить линию Е = 0
Приведенные выше операции подразумевают, что время выполнения каждого шага составляет не
менее 250 нс. При использовании современных быстродействующих микроконтроллеров это условие
может быть легко нарушено, поэтому необходимо тщательно контролировать минимальные значения
временных интервалов, чтобы они всегда находились в области допустимых значений, приводимых в
документации на индикатор, и при необходимости вводить задержки.
На основе этих двух операций (чтения и записи) строится всё взаимодействие с ЖКИ.
Внутренняя структура индикатора
Упрощенная структурная схема контроллера приведена на рис. ниже. Можно сразу выделить ос-
новные элементы с которыми приходится взаимодействовать при программном управлении: регистр
данных (DR), регистр команд (IR), видеопамять (DDRAM), ОЗУ знакогенератора (CGRAM), счетчик
адреса памяти (АС), флаг занятости контроллера.

Другие элементы не являются объектом прямого взаимодействия с управляющей программой -


они участвуют в процессе регенерации изображения на ЖКИ: знакогенератор, формирователь курсора,
сдвиговые регистры и драйверы.
Управление контроллером ведется посредством интерфейса управляющей системы. Основными
объектами взаимодействия являются регистры DR и IR. Выбор адресуемого регистра производится ли-
нией RS, если RS = 0 - адресуется регистр команд (IR), если RS = 1 - регистр данных (DR).
Данные через регистр DR, в зависимости от текущего режима, могут помещаться (или прочиты-
ваться) в видеопамять (DDRAM) или в ОЗУ знакогенератора (CGRAM) по текущему адресу, указывае-
мому счетчиком адреса (АС). Информация, попадающая в регистр IR, интерпретируется устройством
выполнения команд как управляющая последовательность. Прочтение регистра IR возвращает в 7-ми
младших разрядах текущее значение счетчика АС, а в старшем разряде флаг занятости (BF).
Видеопамять, имеющая общий объем 80 байтов, предназначена для хранения кодов символов,
отображаемых на ЖКИ. Видеопамять организована в две строки по 40 символов в каждой. Эта привязка
является жесткой и не подлежит изменению. Другими словами, независимо от того, сколько реальных
строк будет иметь каждый конкретный ЖКИ-модуль, скажем, 80 х 1 или 20 х 4, адресация видеопамяти
всегда производится как к двум строкам по 40 символов.

46
Будучи устройством с динамической индикацией, контроллер циклически производит обновле-
ние информации на ЖКИ.
У контроллера HD44780 существует набор внутренних флагов, определяющих режимы работы
различных элементов контроллера. Они описаны в таблице ниже. Во второй колонке приведены
начальные значения каждого флага при подаче питания на индикатор.
Название
НЗ Функция и значение
флага
I/D 1 режим смещения счетчика адреса АС, 0 - уменьшение, 1 - увеличение.
S 0 флаг режима сдвига содержимого экрана. 0 - сдвиг экрана не производится, 1 - после за-
писи в DDRAM очередного кода экран сдвигается в направлении, определяемым флагом
I/D: 0 - вправо, 1 - влево. При сдвиге не производится изменение содержимого DDRAM.
изменяются только внутренние указатели расположения видимого начала строки в
DDRAM.
S/C - флаг-команда, производящая вместе с флагом R/L операцию сдвига содержимого экрана
(так же, как и в предыдущем случае, без изменений в DDRAM) или курсора. Определяет
объект смещения: 0 - сдвигается курсор, 1 - сдвигается экран.
R/L - флаг-команда, производящая вместе с флагом S/C операцию сдвига экрана или курсора.
Уточняет направление сдвига: 0 - влево, 1 - вправо.
D/L 1 флаг, определяющий ширину шины данных: 0 - 4 разряда, 1 - 8 разрядов.
N 0 режим развертки изображения на ЖКИ: 0 - одна строка, 1 - две строки
F 0 размер матрицы символов: 0 - 5 х 8 точек, 1 - 5 х 10 точек.
D 0 наличие изображения: 0 - выключено, 1 – включено
С 0 курсор в виде подчерка: 0 - выключен, 1 – включен
В 0 курсор в виде мерцающего знакоместа: 0 - выключен, 1 - включен.
Переопределение значений флагов производится специальными командами, записываемыми в
регистр IR, при этом комбинации старших битов определяют группу флагов или команду, а младшие
содержат собственно флаги.
Полный список команд контроллера HD44780 приведён в таблице ниже.
Код Описание команды
RS R/W D7 D6 D5 D4 D3 D2 D1 D0
0 0 0 0 0 0 0 0 0 1 Очистить дисплей и установить курсор в нулевую по-
зицию (адрес 0)
0 0 0 0 0 0 0 0 1 * Установить курсор в нулевую позицию (адрес 0).
Установить дисплей относительно буфера DDRAM в
начальную позицию. Содержимое DDRAM при этом
не меняется.
0 0 0 0 0 0 0 1 I/D S Установить направление сдвига курсора впра-
во (I/D=1) или влево (I/D=0) при записи/чтении оче-
редного кода в DDRAM. Разрешить (S=1) сдвиг дис-
плея вместе со сдвигом курсора.
0 0 0 0 0 0 1 D C B Включить(D=1)/выключить(D=0) дисплей. За-
жечь(C=1)/погасить(C=0) курсор. Изображение кур-
сора сделать мигающим (B=1).
0 0 0 0 0 1 S/C R/L * * Переместить курсор (S/C=0) или сдвинуть дисплей
(S/C=1) вправо (R/L=1) или влево(R/L=0).
0 0 0 0 1 DL N F * * Установить разрядность шины данных 4 бита (DL=0)
или 8 бит (DL=1), количество строк дисплея - одна
(N=0) или две (N=1), шрифт - 5х7 точек (F=0) или
5х10 точек (F=1).
0 0 0 1 ACG Установка адреса CGRAM. После этой команды дан-
ные будут записываться/считываться в/из CGRAM.
0 0 1 ADD Установка адреса DDRAM. После этой команды дан-
47
ные будут записываться/считываться в/из DDRAM.
0 1 B AC Чтение состояния busy-флага (BF) и счетчика адреса.
F
1 0 Данные Запись данных в DDRAM или CGRAM.
1 1 Данные Чтение данных из DDRAM или CGRAM.
Таблица 9.1
Необходимо учитывать, что большинство операций, выполняемых контроллером, занимают зна-
чительное время, около 40 мкс, а время выполнения некоторых доходит до единиц миллисекунд (опера-
ции очистки дисплея и установки курсора), поэтому цикл ожидания снятия флага BF должен обязатель-
но присутствовать в программах драйвера ЖКИ-модуля и предшествовать совершению любой опера-
ции (естественно, кроме операции проверки флага BF).
Вывод на экран символа производится записью его кода в регистр DR. При этом символ разме-
щается в DDRAM по текущему адресу, указываемому АС, а значение АС увеличивается или уменьша-
ется на 1. Чтобы произвести переустановку курсора на нужную позицию, необходимо присвоить АС
соответствующее значение. Для двухстрочного индикатора адрес первого символа 1-ой строки равен
0x00; второй строки – 0x40.
Кодовая таблица
Необходимо учитывать, что контроллеры, устанавливаемые на ЖКИ-модули, могут иметь раз-
личные наборы символов, в т.ч. могут встречаться индикаторы, не предназначенные для российского
рынка и поэтому не содержащие полного набора символов русского алфавита. Наибольшее распростра-
нение на российском рынке получила кодировка, изначально принятая фирмой Epson и ставшая стан-
дартом для русскоязычных индикаторов.
Полная таблица символов ЖКИ с такой кодировкой приведена в таблице ниже.

Рис 9.1
Номер колонки 0..F образует старшую часть кода; номер строки 0..F – младшую. Таким образом,
например, символ «А» имеет код 0x41. Следует учитывать, что кодировка Epson по расположению рус-
ских букв не соответствует кодировке Windows-1251, используемой при написании программы на ком-
пьютере - перед выводом текста на русском на дисплей его необходимо конвертировать.

48
Из допустимых для размещения в DDRAM кодов символы с кодами 0x00...0x07 (и их дубликат с
кодами 0x08...0x0F) имеют специальное назначение - это переопределяемые символы, графическое
изображение которых может назначить сам потребитель, разместив соответствующую информацию в
области CGRAM. Для программирования доступны 8 переопределяемых символов. Для кодирования
матрицы используются горизонтально "уложенные" байты, пять младших битов которых несут инфор-
мацию о рисунке (причем 1 означает, что сегмент будет включен), 4-й разряд каждого из 8-ми (или 11-
ти в режиме 5 х 10) байтов матрицы определяет левую колонку символа, а 0-й - правую. Старшие три
бита не используются. Обратите внимание, что матрица программируемых символов допускает исполь-
зование полной высоты строки (8 строчек для режима 5 х 7), то есть можно размещать точки в области
подчеркивающего курсора. Чтобы определить собственный символ необходимо установить счетчик АС
на адрес начала матрицы требуемого символа в CGRAM – 0x00, 0x08, 0x10 и т.д. и произвести переза-
пись всех байтов матрицы, начиная с верхней строки. После этого, записав в DDRAM код запрограмми-
рованного символа: 0x00, 0x01, 0x02, на экране в соответствующем месте будет отображаться пере-
определенный символ.
Инициализация индикатора
Несколько слов о процессе инициализации ЖКИ-модуля. Производитель контроллера рекомен-
дует выполнять следующую последовательность действий для инициализации. Выдержать паузу не ме-
нее 15 мс между установлением рабочего напряжения питания (> 4,5 В) и выполнением каких-либо
операций с контроллером. Первой операцией выполнить команду, выбирающую разрядность шины (это
должна быть команда 0x30 независимо от того, какой разрядности интерфейс вы собираетесь использо-
вать в дальнейшем), причем перед выполнением этой операции не проверять значение флага BF. Далее
опять выдержать паузу не менее 4,1 мс и повторить команду выбора разрядности шины, причем перед
подачей команды вновь не производить проверку флага BF. Следующим шагом необходимо вновь вы-
держать паузу, на этот раз 100 мкс, и в третий раз повторить команду установления разрядности шины,
вновь без проверки BF. Эти три операции являются инициализирующими и призваны вывести контрол-
лер в исходный режим работы (то есть перевести в режим работы с 8-ми разрядной шиной) из любого
состояния. Следом за ними нормальным порядком (без выдерживания пауз, но с проверкой флага BF)
выполняется инициализация режимов работы с выдачей инициализирующей последовательности, (со-
держащей в том числе команду выбора необходимой разрядности шины).
Так как на момент включения ЖКИ-модуль ничего не отображает (флаг D = 0), то для того, что-
бы вывести какой-либо текст необходимо, как минимум, включить отображение, установив флаг D = 1.
Вот пример широко распространенной последовательности для инициализации ЖКИ-модуля: 0x38,
0xOC, 0x06. 0x38 устанавливает режим отображения 2-х строк с матрицей 5 х 8 точек и работу с 8-ми
разрядной шиной данных; 0xOC включает отображение на экране ЖКИ-модуля, без отображения кур-
соров; 0x06 устанавливает режим автоматического перемещения курсора слева-направо после вывода
каждого символа.

Ниже приведён пример программы. Её можно взять как основу для выполнения вашего варианта.
В программе проводится начальная инициализация ЖКИ, производится вывод текста на первую и вто-
рую строку индикатора. В нескольких местах программы часть кода намеренно заменена на символы
«?????». Вам необходимо по логике работы и контексту восстановить недостающую часть кода.

#include <avr/interrupt.h>
#include <stdio.h>
#define F_CPU 7372800UL // контролер работает с частотой 7.3728 МГц
#include <util/delay.h>

// обозначения выводов, используемых для взаимодействия с LCD


#define LCD_DATA_DDR ?????
#define LCD_DATA_PORT PORTC
#define LCD_DATA_PIN PINC
#define LCD_CTRL_PORT ?????
#define LCD_RS PB5
#define LCD_RW ?????
#define LCD_E PB7

49
static void _lcd_delay(void)
{
_delay_us(1);
}

// проверка готовности LCD


void _lcd_ready(void)
{
char flag;
LCD_DATA_DDR = 0x00; // порт данных - на вход
LCD_CTRL_PORT |= (1<<LCD_RW); //RW=1
LCD_CTRL_PORT &= ~(1<<LCD_RS);//RS=0
do {
LCD_CTRL_PORT |= (1<<LCD_E); //E=1
_lcd_delay();
flag = LCD_DATA_PIN;
LCD_CTRL_PORT &= ~(1<<LCD_E); //E=0
} while (flag & (1 << 7)); // проверим флаг BUSY
}

// запись байта в LCD


void _lcd_write_data(unsigned char data)
{
LCD_CTRL_PORT &= ~(1<<LCD_RW);//RW=0
LCD_DATA_DDR = ?????; // порт данных - на выход
LCD_DATA_PORT = data;
LCD_CTRL_PORT |= (1<<?????); //E=1
_lcd_delay();
LCD_CTRL_PORT &= ~(1<<LCD_E); //E=0
}

// запись байта в регистр команд


void lcd_control(unsigned char control)
{
_lcd_ready();
_lcd_write_data(control);
}

// функция передачи байта в LCD, совместимая со стандартной putchar()


static int LCD_putchar(char c, FILE *stream)
{
_lcd_ready();
LCD_CTRL_PORT |= (1<<LCD_RS);//RS=1
_lcd_write_data(c);
return 0;
}

// очистка содержимого экрана


void lcd_clear(void)
{
lcd_control(0x01); // Очистить дисплей и установить курсор в начало
}

// начальная инициализация LCD


void lcd_init(void)
{
LCD_CTRL_PORT &= ~(1<<LCD_E); //E=0

50
LCD_CTRL_PORT &= ~(1<<LCD_RS);//RS=0

_delay_ms(15);
_lcd_write_data(0x38);
_delay_ms(4);
_lcd_write_data(0x38);
_delay_us(100);
_lcd_write_data(0x38);
lcd_control(?????); // Дисплей включить, курсор выключить
lcd_control(0x06); // Направление сдвига вправо, сдвиг только курсора
}

// используется для замены stdout на функцию вывода байта в LCD


static FILE LCD_stdout = FDEV_SETUP_STREAM(LCD_putchar, NULL, _FDEV_SETUP_WRITE);

///////////////////////////////////////////////////////////////////////////////

int main(void)
{
// до цикла while (1) пишутся стартовые настройки
DDRB = 0xF0; // порт для управления LCD

stdout = &LCD_stdout; // переназначаем стандартный вывод на процедуру LCD_putchar, это


позволит использовать printf для вывода информации

lcd_init();
lcd_clear();

while (1)
{
lcd_clear();
_delay_ms(500);
lcd_control(0x80 + 8); // курсор - на середину первой строки
printf("Line1"); // выводим строку 1
_delay_ms(500);
lcd_control(0xC0 + 0); // курсор - на начало второй строки
printf("Line2"); // выводим строку 2
_delay_ms(500);
}
}

Варианты выполнения лабораторной работы:


Номер
Задание
варианта
1 Используется клавиатура и ЖКИ. Генерируется случайная цифра от 0 до 9 и отображается на инди-
каторе, также на индикаторе отображается номер раунда (для начала «1»). В течение 1 секунды
пользователь должен успеть нажать на кнопку, соответствующую показанной цифре. Если пользова-
тель успел нажать на кнопку, номер раунда увеличивается на 1; генерируется и отображается следу-
ющая случайная цифра, но время, отведённое на ввод пользователя, уменьшается на 50 мс. Если
пользователь не успел, то на индикаторе отображается в течение 1 сек. надпись «Error», после чего
игра начинается заново, с 1-го раунда и максимального времени отображения.
2 Используется клавиатура и ЖКИ. Пользователь последовательно вводит цифры на кнопочной кла-
виатуре, ASCII-код каждой введённой цифры должен отображаться в очередной позиции на индика-
торе. В момент нажатия на кнопку должен раздаваться короткий звуковой сигнал.
3 Используется клавиатура и ЖКИ. Генерируется 2 случайных числа от 0 до 255 и отображаются на
индикаторе. Пользователь последовательно вводит три цифры на кнопочной клавиатуре, каждая
введённая цифра отображается в соответствующей позиции индикатора. Проверяется, чтобы вве-

51
дённое число было равно сумме двух предложенных ранее чисел. Если сумма совпала, отобразить на
индикаторе «Ok», иначе – «Error».
4 Используется клавиатура и ЖКИ. При нажатии на кнопку отображать на индикаторе строку «Pressed
x», где x – ASCII символ, соответствующий нажатой кнопке. При отпускании отображать строку
«Released x». Программа должна выдавать корректные данные также в случае, когда пользователь
нажимает/отпускает кнопку, удерживая нажатой другую кнопку или несколько кнопок.
5 Используется клавиатура и ЖКИ. Пользователь последовательно вводит цифры на кнопочной кла-
виатуре, каждая введённая цифра отображается в соответствующей позиции индикатора. Реализо-
вать возможность выводить на индикатор также символы-буквы из шестнадцатеричной системы
(«А», «b», «C», «d», «E», «F»). Буква должна вводиться путём нажатия на кнопку «*» и последующе-
го нажатия на кнопки «1»..«6» соответственно.
6 Используется клавиатура и звукоизлучатель. Пока нажата кнопка, издавать звуковой сигнал опреде-
лённой частоты, зависящий от номера кнопки («1» - 523 Гц; «2»-587; «3»-659; «4»-698; «5»-783; «6»-
880; «7»-987; «8»-1046; «9»-1174; «*»-1318; «0»-1396; «#»-1568). Одновременно отображать на ин-
дикаторе значение текущей частоты в Гц. Когда ни одна кнопка не нажата, выключать звук и очи-
щать дисплей.
7 Используется клавиатура и ЖКИ. Пользователь последовательно вводит три цифры на кнопочной
клавиатуре, каждая введённая цифра отображается в соответствующей позиции индикатора. Эти три
цифры образуют первое число. Затем вводится «*», которое отображается на индикаторе как «+»,
после чего пользователь вводит второе число. После нажатия «#» на индикатор вводится «=» и сум-
ма двух введённых чисел.
8 Используется клавиатура и ЖКИ. В программе в виде константы задаётся цифровой трёхзначный
код (пароль). Пользователь последовательно вводит три цифры на кнопочной клавиатуре, каждая
введённая цифра отображается в соответствующей позиции индикатора. По окончании ввода по-
следней цифры код проверяется на совпадение с паролем. Если пароль совпал, отобразить на инди-
каторе «Open», иначе – «Error».
Контрольные вопросы
1. Перечислите виды ЖКИ. Назовите их достоинства и недостатки.
2. Назовите варианты подключения ЖКИ к микроконтроллеру (2 варианта). Каковы достоинства и не-
достатки обоих способов? Каково общее количество выводов, используемых для подключения ЖКИ к
микроконтроллеру в первом и во втором случае?
3. Зачем нужна процедура инициализации ЖКИ и в чём она состоит?
4. Поясните назначение выводов E, RW, RS ЖКИ.
5. Поясните пошагово алгоритм, используемый для обмена информацией с ЖКИ.
6. Флаг занятости (busy) ЖКИ: для каких целей используется, когда и каким образом его следует про-
верять?
7. Написать код для перевода ЖКИ в один из режимов, указанных преподавателем и вывода символа
или группы символов в указанную позицию экрана.
8. Как в лабораторной работе реализован вывод данных на ЖКИ с помощью функции printf ()?
9. Для чего используются выводы 15, 16 ЖКИ? Для чего нужен резистор R57? Как изменится схема,
если заменить используемый ЖКИ на такой же (по цоколёвке), но с расширенным температурным диа-
пазоном?
10. Напишите код для добавления в ЖКИ пользовательского символа, например, такого: и вывода
его на экран.
11. Русские буквы в кодовой таблице Windows-1251, которую использует компилятор при компиляции
программы, имеют коды: «А»-192, «Б»-193, …, «Я»-223; «а»-224, «б»-225, …, «я»-255. Т.е. их коды не
совпадают с кодами соответствующих букв из кодовой таблицы индикатора (см. рис. 9.1 выше), в ре-
зультате, если мы напишем в нашей программе printf("Привет"), то надпись будет выведена на ЖКИ
некорректно. Предложите способ модификации программы, позволяющий корректно выводить на ЖКИ
через printf() строки, содержащие русские буквы.

Лабораторная работа №10. Интерфейс 1-Wire


Цель работы: научиться взаимодействовать с устройствами, подключёнными по шине 1-Wire.
Интерфейс 1-Wire разработан фирмой Dallas Semiconductor. В настоящее время данная фирма
вошла в состав компании Maxim Integrated. Данный интерфейс отличается малым количеством выводов
микроконтроллера (МК), требующихся для подключения практически неограниченного количества
52
микросхем. Для организации двустороннего обмена необходима всего 1 сигнальная линия. В настоящее
время, ассортимент устройств с этим интерфейсом весьма широк: помимо хорошо известных ключей-
«таблеток», используемых в домофонах, это датчики температуры, электронные потенциометры, энер-
гонезависимые ОЗУ, EEPROM и др. Протокол обмена по интерфейсу 1-Wire сравнительно прост и лег-
ко реализуется программно практически на любых МК.

Аппаратная реализация интерфейса 1-Wire


На рисунке выше показана упрощенная схема аппаратной реализации интерфейса 1-Wire. Вывод
DQ устройства представляет собой вход КМОП-логического элемента, который может быть зашунти-
рован (замкнут на общий провод) полевым транзистором. Сопротивление канала этого транзистора в
открытом состоянии - около 100 Ом. Когда транзистор заперт - имеется небольшой ток утечки (пример-
но 5 мкА) на общий провод.
Шина 1-Wire должна быть подтянута отдельным резистором к напряжению питания устройств
(которое может быть от 3 до 5 В). Сопротивление этого резистора 4.7 К, однако, это значение рекомен-
довано только для достаточно коротких линий. Если шина 1-Wire используется для подключения уда-
ленных на большое расстояние устройств, то сопротивление этого резистора следует уменьшить. Ми-
нимально допустимое его сопротивление - около 300 Ом, а максимальное - несколько десятков килоом.
Данные величины - ориентировочные, при проектировании схемы их нужно уточнить по характеристи-
кам конкретного устройства 1-Wire его максимальный втекающий ток линии DQ, который, собственно,
и определяет минимум внешнего сопротивления.
Подключение шины 1-Wire к МК показано условно в двух вариантах: с использованием 2 от-
дельных выводов МК (один в качестве выхода, а другой в качестве входа), так и одного, работающего и
на ввод и на вывод. Разделение этих способов показано пунктирной линией, условно обозначающей
границу корпуса МК. С некоторой натяжкой можно представить себе логическое строение шины 1-Wire
как всем известное соединение выводов микросхем с открытым коллектором по схеме "монтажное
ИЛИ". Очевидно, что передача какой-либо информации при этом возможна только выдачей низкого
уровня в линию, т.е. замыканием ее на общий провод, а в высокий логический уровень линия вернется
сама, благодаря наличию внешнего подтягивающего резистора. Так же очевидно, что одновременная
передача нескольких устройств обречена на неудачу из-за полного искажения информации (все переда-
ваемые единицы одного устройства будут подавлены передаваемыми нулями от другого устройства).
Обмен информацией
Основные моменты, касающиеся обмена информацией.
1. Обмен всегда ведется по инициативе одного ведущего устройства, которое в большинстве случа-
ев является микроконтроллером.
2. Любой обмен информацией начинается с подачи импульса сброса ("Reset Pulse" или просто
RESET) в линию 1-Wire ведущим устройством.
3. Для интерфейса 1-Wire в общем случае предусматривается "горячее" подключение и отключение
устройств.
4. Любое устройство, подключенное к 1-Wire после получения питания выдает в линию DQ им-
пульс присутствия, называемый "Presence pulse" (далее PRESENCE). Этот же импульс устрой-
ство всегда выдает в линию, если обнаружит сигнал RESET.

53
5. Появление в шине 1-Wire импульса PRESENCE после выдачи RESET однозначно свидетельству-
ет о наличии хотя бы одного подключенного устройства.
6. Обмен информации ведется так называемыми тайм-слотами: один тайм-слот служит для обмена
одним битом информации.
7. Данные передаются побайтно, бит за битом, начиная с младшего бита. Достоверность передан-
ных/принятых данных (проверка отсутствия искажений) гарантируется путем подсчета цикличе-
ской контрольной суммы.
Основные постулаты определяют логический низкоуровневый протокол обмена данными.
Инициализация
На рисунке ниже показана диаграмма сигналов RESET и PRESENCE, с которых всегда начинает-
ся любой обмен данными. Кстати, выдача импульса RESET в процессе обмена служит для досрочного
завершения процедуры обмена информацией.

Диаграмма сигналов инициализации обмена


Как видим, длительность большинства временных интервалов очень приблизительная и имеет
только ограничение только по минимуму (не меньше указанного). Условные обозначения линий, пока-
занные на рисунке, будут использоваться и далее.
Импульс RESET формирует ведущий МК, переводя в низкий логический уровень шину 1-Wire и
удерживая ее в этом состоянии минимум 480 микросекунд. Затем МК должен "отпустить" шину. Через
некоторое время, зависящее от емкости линии и сопротивления подтягивающего резистора, в линии
установится высокий логический уровень. Протокол 1-Wire ограничивает это время "релаксации" диа-
пазоном от 15 до 60 микросекунд, что и является определяющим для выбора подтягивающего резистора
(как правило, емкость линии мы менять существенно не можем, а именно она оказывает существенное
влияние на время возврата линии к высокому уровню).
Обнаружив импульс RESET, ведомое устройство приводит свои внутренние узлы в исходное со-
стояние и формирует ответный импульс PRESENCE, как следует из рисунка - не позже 60 микросекунд
после завершения импульса RESET. Для этого устройство переводит в низкий уровень линию DQ и
удерживает ее в этом состоянии от 60 до 240 микросекунд. Конкретное время удержания зависит от
многих параметров, но всегда находится в указанном диапазоне. После этого устройство так же "отпус-
кает" шину.
Но после завершения импульса PRESENCE устройству дается еще некоторое время для завер-
шения внутренних процедур инициализации, таким образом, МК должен приступить к любому обмену
с устройством не ранее, чем через 480 микросекунд после завершения импульса RESET.
Итак, процедура инициализации интерфейса, с которой начинается любой обмен данными между
устройствами, длится минимум 960 микросекунд, состоит из передачи от МК сигнала RESET и прие-
мом от устройства сигнала PRESENCE. Если сигнал PRESENCE не обнаружен - значит на шине 1-Wire
нет готовых к обмену устройств.
Обмен данными
Теперь рассмотрим процедуры обмена битами информации, который, как было сказано выше,
осуществляется определенными тайм-слотами. Тайм-слот - это по существу определенная, довольно
жестко лимитированная по времени последовательность смены уровней сигнала в линии 1-Wire. Разли-
54
чают 4 типа тайм-слотов (будем использовать термин МК, как синоним "ведущего устройства" и просто
"устройство", как синоним "ведомого"): передача "1" от МК, передача "0" от МК, прием "1" от устрой-
ства и прием "0" от устройства.
Любой тайм-слот всегда начинает МК путем перевода шины 1-Wire в низкий логический уро-
вень. Длительность любого тайм-слота должна находиться в пределах от 60 до 120 микросекунд. Между
отдельными тайм-слотами всегда должен предусматриваться интервал не менее 1 микросекунды (кон-
кретное значение определяется параметрами ведомого устройства).
Тайм-слоты передачи отличаются от тайм-слотов приема поведением МК: при передаче он толь-
ко формирует сигналы, при приеме, кроме того, еще и опрашивает (т.е. принимает) уровень сигнала в
линии 1-Wire. Рисунок ниже демонстрирует временные диаграммы тайм-слотов всех 4-х типов: вверху
показаны тайм-слоты передачи от МК, внизу - приема от устройства.

Диаграммы тайм-слотов при обмене данными


Тайм-слот передачи "0" заключается просто в удержании шины 1-Wire в низком уровне в тече-
ние всей длительности тайм-слота. Передача "1" осуществляется путем "отпускания" шины 1-Wire со
стороны МК не ранее чем через 1 микросекунду после начала тайм-слота, но не позже чем через 15
микросекунд. Ведомое устройство опрашивает уровень в шине 1-Wire в течение временного интервала,
условно показанного в виде серого прямоугольника, т.е. начиная с 15-й микросекунды от начала тайм-
слота и заканчивая 60-й микросекундой от начала. Типичный момент ввода уровня в устройство (т.е.
характерный для большинства устройств) - около 30-й микросекунды от начала тайм-слота.
Заштрихованная область - это область "нарастания" уровня в шине 1-Wire, которая зависит от
емкости линии и сопротивления подтягивающего резистора, она приведена для справки.
Тайм-слоты приема информации отличаются тем, что МК формирует только начало тайм-слота
(абсолютно так же, как при передаче "1"), а затем управление уровнем шины 1-Wire берет на себя
устройство, а МК осуществляет ввод этого уровня так же в определенной зоне временных интервалов.
Зона эта, как видно из рисунка, довольно мала. Как и раньше, заштрихованная область - область не-
определенности, поэтому для ввода, собственно говоря, контроллеру остается даже не промежуток, а
скорее конкретный момент, когда он должен ввести уровень сигнала из линии. Этот момент времени -
14-я или 15-я микросекунда от начала тайм-слота. Разумеется, если линия имеет малую емкость, а под-
тягивающий резистор мал, зона опроса несколько расширяется, однако рекомендую ориентироваться на
худший вариант (как, кстати, рекомендует и фирма-производитель), чтобы всегда обеспечить надежный
обмен данными.
55
Итак, подведем итоги. МК начинает тайм слот с выдачи в шину 1-Wire "0" в течение 1 микросе-
кунды. Последующий уровень зависит от типа тайм слота: для приема и передачи "1" уровень должен
стать высоким, а для передачи "0" - оставаться низким вплоть до конца тайм-слота, т.е. не менее 60 и не
более 120 микросекунд. Если МК принимает данные, то опрос уровня в шине он должен сделать на
промежутке от 13-й до 15-й микросекунде тайм-слота. МК должен обеспечить интервал между отдель-
ными тайм-слотами не менее 1 микросекунды (лучше - больше, максимальное значение не ограничено).
Важно понимать, что следует очень тщательно подходить к обеспечению в шине 1-Wire требуе-
мых временных интервалов, т.к., например, увеличение длительности тайм-слота вывода "0" свыше ре-
комендованного значения может привести к ошибочному восприятию этого тайм-слота, как сигнала
RESET, и, разумеется, после этого вся процедура обмена пойдет насмарку. Но так же следует учитывать
влияние самой линии на длительность фронтов импульсов. Поэтому в общем случае, это не простая за-
дача. Но выполнение несложных рекомендаций позволит ее решить достаточно простыми средствами:
во-первых, все сигналы, которые должен формировать МК, следует формировать по принципу необхо-
димого минимума длительности (т.е. немного больше, чем указанная минимальная длительность), а от
устройства следует ожидать сигналов по принципу наихудшего (т.е. ориентироваться на самые худшие
варианты временных параметров сигнала).
Если вы разрабатываете схему, которая целиком умещается на одной плате вместе со всеми
устройствами на шине 1-Wire, то, ориентируясь на самый первый рисунок, вы получите практически
идеальную линию: фронты нарастания высокого уровня в шине будут минимальными - это избавит вас
от большинства проблем. Но если Вы подключаете несколько устройств через длинный соединитель-
ный шлейф - придется бороться с погонной емкостью линии.
Прием и передача байтов всегда начинается с младшего бита. Порядок следования байтов при
передаче и приеме адреса устройства так же ведется от младшего к старшему. Порядок передачи другой
информации зависит от конкретного устройства, поэтому следует обращаться к документации на при-
меняемые вами устройства.
Адресация устройств на линии
Каждое устройство 1-Wire обладает уникальным идентификационным 64-битным номером, про-
граммируемым на этапе производства микросхемы. Уникальным - это значит, что фирма-производитель
гарантирует, что не найдется двух микросхем с одинаковым идентификационным номером (по крайней
мере в течении нескольких десятков лет при существующих темпах производства).
Если на шине 1-Wire имеется более одного устройства, то перед МК должен решать дополни-
тельные задачи: определение количества имеющихся устройств и выбор (адресация) одного конкретно-
го из них для обмена данными. Для решения этих задач фирма-производитель заложила в протокол
специальные команды и алгоритмы, которые используют тот факт, что каждое устройство имеет уни-
кальный идентификационный номер. С рекомендациями по реализации данных алгоритмов можно
ознакомиться на сайте фирмы-производителя.
В данной работе мы будем использовать шину в конфигурации, когда на шине имеется только
одно ведомое устройство. В этом случае алгоритм взаимодействия между МК и устройством значи-
тельно упрощается.
Алгоритм взаимодействия с ведомым устройством
Данный алгоритм состоит из трёх шагов:
1. Инициализация (подача импульса RESET и получение импульса присутствия PRESENCE).
2. Подача команд взаимодействия с ПЗУ устройства и получение ответа на них.
3. Подача функциональных команд и (опционально) получение ответа на них.
Набор функциональных команд зависит от конкретного устройства (например для термометра
DS18B20 это могут быть команды запуска измерения или считывания результата; для микросхем АЦП
дополнительных команд может быть около десятка; а для ключа-таблетки DS1990A не определены ни-
какие функциональные команды, т.к. в нём нет других функциональных частей, кроме ПЗУ).
Команды взаимодействия с ПЗУ
Существует несколько общих команд для всех типов 1-Wire-устройств, а так же могут быть ко-
манды, уникальные для отдельных типов. Среди общих команд отметим следующие:
Команда Значе- Описание
56
ние
байта
команды
Search 0xF0 Поиск адресов – используется для определения адресов подключенных устройств,
ROM если на линии может присутствовать более одного ведомого устройства. Подробнее
про данную команду можно прочитать на сайте фирмы-производителя.
Read 0x33 Чтение адреса устройства - используется для определения адреса единственного
ROM устройства на шине.
После того, как МК выдаст команду, от устройства поступит 8 байт его собственно-
го уникального адреса - МК должен их принять.
Match 0x55 Выбор адреса - используется для установки адреса одного активного устройства из
ROM многих подключенных, если на линии может присутствовать более одного ведомо-
го устройства.
После нее МК должен передать 8 байт адреса ведомого устройства, с которым бу-
дет осуществляться последующий обмен данными. Приняв эту команду, каждое
устройство сравнивает передаваемый адрес со своим собственным. Все устройства,
адрес которых не совпал, прекращают анализ и выдачу сигналов в линии 1-Wire, а
опознавшее адрес устройство продолжает работу. Теперь все данные, передаваемые
МК будут попадать только к этому "адресованному" устройству.
Skip 0xCC Игнорировать адрес - используется для обращения к единственному устройству на
ROM шине, при этом адрес устройства игнорируется (можно обращаться к неизвестному
устройству).
Позволяет ускорить процесс взаимодействия с устройством. Поучив эту команду,
устройство сразу считает адрес совпавшим, хотя никакого адреса за этой командой
не следует. Некоторые процедуры не требуют приема от устройства никаких дан-
ных, в этом случае команду SKIP ROM можно использовать для передачи какой-то
информации сразу всем устройствам. Это можно использовать, например, для од-
новременного запуска цикла измерения температуры несколькими датчиками-
термостатами типа DS18S20.
Вычисление контрольной суммы
При получении данных от ведомого устройства предусмотрен дополнительный контроль пра-
вильности приёма с использованием контрольной суммы.
Так, ранее упоминавшийся уникальный 64-битный адрес устройства на самом деле состоит фак-
тически из 8 отдельных байт: одного байта идентификатора семейства, шести байт (48 бит) собственно
уникального адреса и одного байта контрольной суммы всех предыдущих байтов.
При чтении результата измерения датчика температуры DS18B20 чтению подлежат 9 байт ин-
формации, причём в байтах с 1 по 8 содержится основная информация, а последний, 9-ой байт, является
байтом контрольной суммы всех предыдущих байтов.
Контрольная сумма в семействе устройств 1-Wire вычисляется по алгоритму циклический избы-
точного кода (CRC-8). Байт контрольной суммы передается самым последним и вычисляется по специ-
альному алгоритму на основе значения всех предыдущих информационных байтов данных. При приёме
информации от ведомого устройства микроконтроллер сам вычисляет значение контрольной суммы на
основании принятых данных и сравнивает его байтом CRC из посылки. Если значения совпадают, то с
высокой вероятностью данные передались без искажений (а искажения вполне возможны, если вспом-
нить характер аппаратной реализации интерфейса).
Алгоритм вычисления CRC-8 представлен на рисунке ниже.

Квадратиками на рисунке обозначены биты 0..7 байта контрольной суммы. Справа расположен
младший нулевой бит (LSB), слева – 7-ой (MSB). Перед началом работы алгоритма все биты инициали-
57
зируются нулём. Кругами с надписью «XOR» на рисунке обозначены функциональные элементы, вы-
полняющие операцию «Исключающее ИЛИ». На вход «INPUT» последовательно поступают биты дан-
ных, начиная с младшего (нулевого) бита 1-го байта данных; далее идёт первый бит 1-го байта данных и
т.д. и заканчивается всё старшим битом последнего байта данных. Когда на вход приходит очередной
бит данных, осуществляется операция «Исключающее ИЛИ» между ним и младшим битом байта кон-
трольной суммы на данный момент времени. Если результат операции «0», то просто сдвигаем байт
контрольной суммы на один бит вправо; если «1», то сначала сдвигаем байт контрольной суммы на
один бит вправо, а затем ещё инвертируем биты 7, 3 и 2 байта контрольной суммы.
Для ускорения расчёта контрольной суммы может использоваться табличный метод, который
выполняется намного быстрее, поскольку оперирует не отдельными битами, а сразу байтами информа-
ционных данных, однако требует больше памяти для размещения таблицы.
Набор функциональных команд микросхемы термометра DS18B20
В некоторых вариантах данной лабораторной работы будет использоваться микросхема цифро-
вого термометра DS18B20. Помимо команд взаимодействия с ПЗУ, описанных выше, данная микросхе-
ма поддерживает ряд функциональных команд, часть из которых, применяемых при выполнения рабо-
ты, описана ниже:
Команда Значе- Описание
ние
байта
команды
Convert T 0x44 Запуск единичного измерения температуры. Никакие дополнительные данные с
данной командой не передаются и не принимаются.
В режиме «паразитного» питания, который предполагается использовать в данной
работе, не позднее чем через 10 мкс после подачи данной команды необходимо
установить мощную «1» (не менее 1.5 мА) на выводе МК, подключённом к
устройству. После подачи данной команды следует ожидать не менее 750 мс (для
режима максимального разрешения по температуре), прежде чем можно будет
считать результат измерения.
Результат измерения – 2-х байтовое число – будет сохранён в ОЗУ устройства.
Read 0xBE Чтение ОЗУ устройства – используется, в частности, для чтения значения темпе-
scratchpad ратуры, полученного в результате выполнения команды измерения температуры.
После того, как МК выдаст команду, от устройства поступит 9 байт содержимого
ОЗУ - МК должен их принять. Последний принятый байт – контрольная сумма.
Внутренняя структура ОЗУ устройства показана ниже:
Байт 0 Двухбайтовый регистр, содержащий результат измерения температуры (в
Байт 1 байте 0 – младшая часть регистра)
Байт 2 Верхнее «тревожное» значение температуры или пользовательские данные 1
Байт 3 Нижнее «тревожное» значение температуры или пользовательские данные 2
Байт 4 Регистр конфигурации
Байт 5 Зарезервировано, читается как 0xFF
Байт 6 Зарезервировано
Байт 7 Зарезервировано, читается как 0x10
Байт 8 Контрольная сумма
Двухбайтовый регистр, содержащий результат измерения температуры, имеет следующий фор-
мат:
Бит15 Бит14 Бит13 Бит12 Бит11 Бит10 Бит9 Бит8 Бит7 Бит6 Бит5 Бит4 Бит3 Бит2 Бит1 Бит0
S S S S S 26 25 24 23 22 21 20 2-1 2-2 2-3 2-4
где S-знак («0» - «+»; «1» - «-»). Фактически, это дробное число с фиксированной точкой, содержащее
температуру в градусах Цельсия. Младшие 4 бита определяют дробную часть числа.

С другими функциональными командами, описанием регистров и возможностями данной микро-


схемы можно ознакомиться, прочитав техническое описание микросхемы.

58
Ниже приведён пример программы. Её можно взять как основу для выполнения вашего варианта.
В программе имеются функции для работы с устройством, подключённым по шине 1-Wire. Это функ-
ции нижнего уровня для обмена данными и проверки: write_1(), write_0(), read_data_slot(), write_byte(),
read_byte(), CountCRC8(). И функции верхнего уровня: start() – инициализация устройства и
send_cmd_read_data() – передача команды в устройство и (опционально) последующий приём данных из
устройства. С помощью данного набора функций можно осуществить взаимодействие с устройством. В
нескольких местах программы часть кода намеренно заменена на символы «?????». Вам необходимо по
логике работы и контексту восстановить недостающую часть кода.

#include <avr/io.h>
#define F_CPU 7372800UL // контролер работает с частотой 7.3728 МГц
#include <util/delay.h>
#include <avr/interrupt.h>

typedef unsigned char uint8;

// обозначения выводов, используемых для взаимодействия с 1-Wire


#define TM_DDR ?????
#define TM_PORT ?????
#define TM_PIN PINE
#define TM PE5

// процедура передачи логической "1"


void write_1(void)
{
TM_DDR |= (1<<TM); // уст. на выход и логический "0"
_delay_us(8); // Tw1l=1-15 us
TM_DDR &= ~(1<<TM);// уст. вход с подтягивающим резистором
_delay_us(70); // Tslot>61 us
}

// процедура передачи логического "0"


void write_0(void)
{
TM_DDR |= (1<<TM); // уст. на выход и логический "0"
_delay_us(70); // Tw0l=60-120 us
TM_DDR &= ~(1<<TM);// уст. вход с подтягивающим резистором
_delay_us(8); // Trec > 1 us
}

// процедура приёма бита


uint8 read_data_slot(void)
{
TM_DDR |= (1<<TM); // уст. на выход и логический "0"
_delay_us(5); // Trl=1..(15-delta)
TM_DDR &= ~(1<<TM);// уст. вход с подтягивающим резистором
_delay_us(5); // Tmsr=(Trl+delta)..15
uint8 rslt = ????? & (1<<TM);
_delay_us(68); // Tslot>61 us
return rslt;
}

// процедура передча байта


void write_byte(uint8 data)
{
for (uint8 i = 0; i < 8; i++) // цикл по битам в байте
{
if (data & 1) // младший бит=1?
59
?????; // передаём "1"
else
?????; // передаём "0"
data >>= 1; // сдвигаем вправо
}
}

// процедура приёма байта


uint8 read_byte(void)
{
uint8 rslt = 0;
for (uint8 i = 0; i < 8; i++) // цикл по битам в байте
{
rslt ????? 1; // сдвигаем результат вправо
if (read_data_slot()) // если приняли "1"
rslt |= (1 << ?????); // добавляем её в старший бит
}
return rslt;
}

// вычисление контрольной суммы для 1 байта данных


uint8 CountCRC8(uint8 data, uint8 CRC)
{
for (uint8 i = 0; i < ?????; i++) // цикл по битам в байте
{
if ((data ^ CRC) & 0x01)
CRC = (CRC >> 1) ^ 0x8C;
else
CRC >>= 1;
data >>= 1;
}
return CRC;
}

// Инициализация
// Возвращает 0, если ошибка (нет устройства на линии); или 1 - если есть
uint8 start(void)
{
TM_DDR |= (1<<TM); // уст. на выход и логический "0"
_delay_us(?????); // Trstl>480 us
TM_DDR &= ~(1<<TM);// уст. вход с подтягивающим резистором
_delay_us(65); // Tpdhigh=15-60 us
if (????? & (1<<TM)) // нет ответа
return 0;
_delay_us(5);
if (TM_PIN & (1<<TM)) // нет ответа
return 0;
_delay_us(240); // rest of Tpdlow
if (!(TM_PIN & (1<<TM))) // к этому времени должна быть 1, иначе, возможно, это замыка-
ние съёмника
return 0;
_delay_us(200); // конец reset-слота
return 1;
}

// Чтение данных из устройства


// pData-указатель на массив, в котором разместить полученные данные; datasize - размер
получаемых данных

60
// Возвращает 0, если ошибка (не сошлась контрольная сумма или приняты все "0"); или 1 -
если приняты корректные данные
uint8 read_data(uint8 *pData, uint8 datasize)
{
uint8 anti00 = 0x00; // борьба с данными 0000..00
uint8 CRC = 0x00; // контрольная сумма
for (uint8 i = 0; i < ?????; i++) // цикл по количеству получаемых данных
{
uint8 data = read_byte(); // принимает очередной байт информации
CRC = CountCRC8(data, CRC);
*pData++ = ?????; // записываем данные в память
anti00 |= data;
}
return ((CRC == 0) && (anti00));
}

// Запуск единичного измерения температуры и ожидание окончания измерения


void temp_convert(void)
{
write_byte(?????); // Подаём команду на запуск единичного измерения температуры
_delay_ms(?????);
}
///////////////////////////////////////////////////////////////////////////////

int main(void)
{
// до цикла while (1) пишутся стартовые настройки
DDRA |= (1 << PA7); // вывод, поключённый к звукоизлучателю

while (1)
{
uint8 data[8]; // массив для хранения принятых данных
if (start()) // Устройство на линии обнаружено?
{
write_byte(?????); // подаём команду чтения адреса устройства
if (read_data(data, ?????)) // данные приняты корректно?
{
PORTA |= (1 << PA7); // включить звук
}
else
PORTA &= ~(1 << PA7); // выключить звук
}
else
PORTA &= ~(1 << PA7); // выключить звук
_delay_ms(100);
}
}

В лабораторной работе будет использоваться стенд, который использовался в работах 1-6 с мик-
роконтроллером ATmega64; схема стенда приведена в лабораторной работе №1.
Помимо цифрового термометра в лабораторной работе могут использоваться два ключа-
«таблетки» DS1990A с номерами (8900000DF5B88A01) и (6B00000EB8D8AE01). Серийный номер циф-
рового датчика температуры DS18B20 – (B90115611CF8FF28).

Варианты выполнения лабораторной работы:


Номер
Задание
варианта

61
1 В работе используются два ключа DS1990A и датчик температуры DS18B20 в качестве устройства с
уникальным серийным номером. При подключении к считывателю ключа №1 зажигать 1-ый свето-
диод; при подключении ключа №2 – второй; при подключении датчика температуры – третий. Если
ни одно устройство не подключено к считывателю, все светодиоды погасить. В момент обнаружения
подключения устройства к считывателю, должен раздаваться короткий звуковой сигнал.
2 В работе используется датчик температуры DS18B20. Периодически измерять температуру и от-
правлять на компьютер по UART со скоростью 19200 бит/с в виде строки «t=xx.yyyy°C». После пе-
редачи номера передать символ перевода строки.
3 В работе используется любое 1-Wire устройство c уникальным серийным номером. После сброса
программа ожидает подключения любого 1-Wire устройства к считывателю. Как только устройство
подключено, его серийный номер запоминается. После этого микроконтроллер должен реагировать
только на подключение этого устройства: в случае обнаружения подключения инвертировать состо-
яние первого светодиода.
4 В работе используется любое 1-Wire устройство c уникальным серийным номером. При подключе-
нии устройства 1-Wire к считывателю, отправлять его уникальный номер (в кодировке ASCII) в
компьютер по UART со скоростью 19200 бит/с. После передачи номера передать символ перевода
строки.
5 В работе используются два ключа DS1990A. При подключении к считывателю ключа №1 зажигать
все 4 светодиода; при подключении ключа №2 гасить все 4 светодиода. В момент обнаружения под-
ключения устройства к считывателю, должен раздаваться короткий звуковой сигнал.
6 В работе используется датчик температуры DS18B20. После сброса программа производит первое
измерение температуры (когда датчик подключается к считывателю) и запоминает измеренное зна-
чение. Далее программа периодически производит измерение температуры и включает звуковой
сигнал, как только обнаруживает изменение температуры на величину более 3 градусов по сравне-
нию с первоначально измеренной.
7 В работе используются два ключа DS1990A. При подключении к считывателю ключа №1 должен
раздаваться непрерывный звуковой сигнал. При подключении к считывателю ключа №2 должен раз-
даваться прерывистый звуковой сигнал в виде меандра с периодом 200 мс. Если ни одно устройство
не подключено к считывателю, выключить звук.
8 В работе используются два ключа DS1990A. С помощью четырёх светодиодов организовать отобра-
жение состояния 4-х разрядного двоичного счётчика (каждый светодиод отображает состояние од-
ного разряда («0» - потушен; «1» - зажжён)). При подключении к считывателю ключа №1 увеличи-
вать значение счётчика на 1; при подключении ключа №2 – уменьшать на 1.
Контрольные вопросы
1. Достоинства и недостатки шины 1-Wire. Что нужно сделать, чтобы увеличить дальность подключе-
ния (назовите программные и аппаратные меры)?
2. Стадии обмена данными по шине 1-Wire.
3. Как передаются и принимаются биты данных по шине?
4. Как формируется контрольная сумма? Зачем она нужна и как она используется?
5. Можно ли подключать несколько устройств на одну линию? Если да, то как адресовать сообщение
конкретному устройству (рассмотреть несколько ситуаций: когда на линии может быть только одно ве-
домое устройство с заранее неизвестным ID, когда на линии присутствует несколько устройств с зара-
нее известными ID, когда на линии может быть несколько устройств с заранее неизвестными ID).
6. Какова максимальная скорость, с которой можно обмениваться данными с ведомым устройством по
шине 1-Wire?
7. Пусть к шине 1-Wire может быть подключен либо ключ типа DS1990A, либо датчик температуры
типа DS18B20 с неизвестными заранее ID. Как программно определить, подключен сейчас ключ ли датчик тем-
пературы?
8. Как считать из датчика типа DS18B20 и вывести на экран ЖКИ значение температуры с точностью
до 0.0001?

Лабораторная работа №11. Инкрементный энкодер


Цель работы: научиться взаимодействовать с Инкрементным энкодером.
Инкрементный энкодер является одной из разновидностей датчика угла поворота. Датчик угла
поворота (сокр. ДУП) — устройство, предназначенное для преобразования угла поворота вращающего-
ся объекта (вала) в электрические сигналы, позволяющие определить угол его поворота. Датчики угла
поворота имеют множество применений. Они широко применяются в промышленности (в частности в
62
сервоприводах), в роботостроении, в автомобилестроении (например, для определения угла поворота
рулевого колеса), в компьютерной технике (для определения угла поворота колеса компьютерной мы-
ши) и т. п.
ДУПы подразделяются: по способу выдачи информации на накапливающие (инкрементные) и
абсолютные (позиционные); по принципу действия на оптические, резистивные, магнитные, индуктив-
ные, механические; по допустимому углу поворота вала на ДУПы с ограниченным диапазоном работы и
ДУПы с неограниченным диапазоном работы.
Накапливающие ДУПы, на выходе формируют импульсы, по которым принимающее устройство
определяет текущее положение вала путём подсчета числа импульсов счётчиком. Сразу же после вклю-
чения накапливающего ДУПа положение вала неизвестно. Для привязки системы отсчета к началу от-
счёта инкрементные датчики имеют нулевые (референтные) метки, через которые нужно пройти после
включения оборудования. К недостаткам такого типа датчиков угла положения также относится то, что
невозможно определить пропуск импульсов от ДУПа по каким-либо причинам. Это приводит к накоп-
лению ошибки определения угла поворота вала до тех пор, пока не будет пройдена нуль-метка. Для
определения направления вращения применяются два измерительных канала («синусный» и «косинус-
ный»), в которых идентичные последовательности импульсов (меандр) сдвинуты на 90° относительно
друг друга:

Абсолютные ДУПы выдают на выходе сигналы, которые можно однозначно интерпретировать


как угол поворота вала датчика угла. Датчики угла этого типа не требуют привязки системы отсчёта к
какому-либо нулевому положению.
Оптические ДУПы имеют жёстко закреплённый на валу стеклянный диск с оптическим растром.
При вращении вала растр перемещается относительно неподвижного растра, при этом модулируется
световой поток, принимаемый фотодатчиком. Абсолютные оптические датчики угла — это датчики уг-
ла поворота, в которых каждому положению вала соответствует цифровой выходной код, который
наряду с числом оборотов является основным рабочим параметром датчика. Абсолютные оптические
ДУПы, так же как и накапливающие, считывают и фиксируют параметры вращения оптического диска.
Магнитные ДУПы регистрируют прохождение магнитных полюсов вращающегося магнитного
элемента непосредственно вблизи чувствительного элемента, преобразуя эти данные в соответствую-
щий цифровой код или сигнал.
Механические и оптические ДУПы с последовательным выходом содержат диск из диэлектрика
или стекла с нанесёнными выпуклыми, проводящими или непрозрачными участками. Считывание абсо-
лютного угла поворота диска производится линейкой переключателей или контактов в случае механи-
ческой схемы и линейкой оптронов в случае оптической. Выходные сигналы представляют собой код
Грея, позволяющий избавиться от неоднозначности интерпретации сигнала. Основным недостатком ме-

63
ханического ДУПа является дребезг контактов, который может приводить к неправильному подсчету и
определению направления вращения. Оптические и магнитные ДУПы лишены данного эффекта.
Ниже приведена схема механического инкрементного энкодера, используемого в данной работе,
а также диаграмма изменения сигналов на его выводах «А» и «В» при вращении вала в прямом и обрат-
ном направлениях.

Если записать сигналы на выводах «А» и «В» в двоичной форме, то получится следующая таб-
лица, характеризующая изменение этих сигналов при вращении вала в прямом направлении:
A 0 0 1 1 0 0 1 1 0 0 1 1 0
B 0 1 1 0 0 1 1 0 0 1 1 0 0
Если собрать биты «А» и «В» в одно двухразрядное двоичное число, в котором бит «А» - млад-
ший, а «В» - старший, то получится, что код на выходе энкодера при вращении в прямом направлении
меняется следующим образом: 0, 2, 3, 1, 0, 2, 3, 1, … При вращении в обратном направлении: 1, 3, 2, 0,
1, 3, 2, 0, ….
Существует несколько способов программного отслеживания вращения вала энкодера. Один из
них состоит в том, чтобы завести линию «А» энкодера на такой вход микроконтроллера, который мож-
но настроить специальным образом - чтобы вызывалось прерывание при изменении логического сигна-
ла на этом входе с «0» на «1». В процедуре прерывания анализировать состояние линии «В» энкодера,
и, если там «1» - считаем, что вал энкодера провернулся на один «тик» в прямом направлении, а если
«0» - в обратном. Однако, такой способ не очень подходит для механического энкодера, которому, как
было сказано выше, свойственен эффект дребезга при смене сигналов на линиях «А» и «В». В этом слу-
чае лучше подходит метод периодического опроса состояния линий «А» и «В», собирания их в одно
двухразрядное число и отслеживание последовательности изменения этого числа от опроса к опросу.
Если число меняется следующим образом: 0, 2, 3, 1, 0, 2, 3, 1, … то считаем, что вал вращается в пря-
мом направлении; если меняется как 1, 3, 2, 0, 1, 3, 2, 0, …, то – в обратном. Период опроса состояния
линий должен быть как минимум в 4 раза больше, чем максимальная ожидаемая частота вращения вала
энкодера, умноженная на параметр энкодера «число импульсов на оборот».
В данной лабораторной работе используется механический инкрементный энкодер типа EC12E,
выдающий 24 импульса на оборот.
Ниже приведён пример программы. Её можно взять как основу для выполнения вашего варианта.
В программе используется второй метод отслеживания вращения вала энкодера: с помощью таймера1
производится периодический (с периодом 1 мс) опрос состояния линий энкодера с отслеживанием из-
менения кода числа, образованного линиями «А» и «В». Текущее относительное положение вала энко-
дера сохраняется в переменной EncData. Также в программе реализован абсолютный счётчик времени
(переменная tickcount), который увеличивается на 1 каждые 100 мс. Основная программа следит за из-
менением положения вала энкодера; как только обнаружено изменение положения, программа ожидает
2 сек, и если за это время положение вала более не меняется, то выдаёт короткий звуковой сигнал. В не-
скольких местах программы часть кода намеренно заменена на символы «?????». Вам необходимо по
логике работы и контексту восстановить недостающую часть кода. Данный пример можно использовать
как базу для выполнения вашего варианта задания.

#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 7372800UL // контролер работает с частотой 7.3728 МГц
#include <util/delay.h>

64
///////////////////////////////////////////////////////////////////////////////

#define false 0
#define true 1

// текущее положение энкодера


volatile uint8_t EncData;
// счётчик 100мс интервалов
volatile uint8_t tickcount;

// Timer 1 output compare A interrupt service routine


ISR (TIMER1_COMPA_vect)
{
static uint8_t EncState = 1;
static uint8_t cntr100 = 100; // счётчик до 100 мс

uint8_t New = (PIND >> 2) & 0x03; // берем текущее значение AB

if ((EncState == 2) && (New == 0)) // и сравниваем со старым, при определённых комби-


нациях
?????--; // изменяем на 1 переменную, хранящую текущее
else if ((EncState == 0) && (New == 2)) // положение энкодера
?????++;
EncState = ?????; // запоминаем новое значение
cntr100--;
if (cntr100 == 0) // прошло 100 мс?
{
cntr100 = 100;
?????++; // увеличиваем на 1 счётчик 100мс интервалов
}
}

///////////////////////////////////////////////////////////////////////////////

int main(void)
{
DDRD = (1 << PD7);

// инициализируем таймер1: режим CTC, переполнение каждую 1мс


OCR1A = F_CPU / 1000.0 + 0.5 - 1.0;
TCCR1A = (0 << WGM11) | (0 << WGM10);
TCCR1B = (0 << WGM13) | (1 << WGM12) | (0 << CS12) | (0 << CS11) | (1 << CS10);
TIMSK = (1 << ?????); // разрешаем прерывание от таймера

// инициализируем таймер2 для управления звукоизлучателем


TCCR2 = (1 << WGM21) | (0 << WGM20) | (0 << COM21) | (1 << COM20) | (1 << CS22) | (0 <<
CS21) | (0 << CS20);

sei(); // разрешаем прерывания

uint8_t olddata=EncData; // предыдущее положение энкодера


uint8_t tick = 0; // время последнего изменения положения
uint8_t fChange = false; // флаг: =true, если положение изменилось
while (1)
{
if ((EncData) != olddata) // положение энкодера изменилось?
{
????? = ?????; // запоминаем новое положение

65
fChange = true;
tick = ?????; // запоминаем момент времени
}
else if ((?????) && ((uint8_t)(tickcount - tick) > 20)) // положение энкодера измени-
лось и прошло более 2 сек?
{
fChange = false; // сбрасываем флаг
OCR2 = 0x1C; // воспроизводим звуковой сигнал
_delay_ms(200);
OCR2 = 0;
}
}
}

Варианты выполнения лабораторной работы:


Номер
Задание
варианта
1 Используется энкодер и ЖКИ. С помощью энкодера вводится и отображается на ЖКИ число от 1 до
99. Моментом окончания ввода числа считается, когда пользователь сделал паузу после окончания
вращения вала более 3 сек. После окончания ввода числа запустить таймер обратного отсчёта с
отображением оставшегося количества секунд на индикаторе. Начальное значение таймера (в секун-
дах) должно соответствовать введённому числу. После достижения нулевого значения отобразить на
индикаторе «End» , включить на 1 сек звуковой сигнал и приготовиться к вводу нового числа.
2 Используется энкодер и ЖКИ. В программе в виде константы задаётся цифровой трёхзначный код
(пароль). Пользователь последовательно вводит три цифры с помощью энкодера, каждая введённая
цифра отображается в соответствующей позиции на экране индикатора. Моментом окончания ввода
очередной цифры считается, когда пользователь сделал паузу после окончания вращения вала более
3 сек. По окончании ввода последней цифры код проверяется на совпадение с паролем. Если пароль
совпал, отобразить на индикаторе «Open», иначе – «Error».
3 Используется энкодер и ЖКИ. С помощью энкодера вводится и отображается на ЖКИ первое число
от 0 до 99. После чего на индикаторе отображается знак «+». Затем снова с помощью энкодера вво-
дится и отображается на ЖКИ второе число от 0 до 99. После чего на индикатор необходимо выве-
сти знак «=» и результат, равный сумме введённых чисел. Моментом окончания ввода очередного
числа считается, когда пользователь сделал паузу после окончания вращения вала более 3 сек.
4 Используется энкодер и ЖКИ. Генерируется 2 случайных числа от 0 до 49 и отображаются на инди-
каторе в виде «Число1+Число2=». С помощью энкодера вводится и отображается на ЖКИ число от 0
до 99. После окончания ввода проверяется, чтобы введённое число было равно сумме двух предло-
женных ранее чисел. Если сумма совпала, отобразить на индикаторе «Ok», иначе – «Error». Момен-
том окончания ввода числа считается, когда пользователь сделал паузу после окончания вращения
вала более 3 сек.
5 Используется энкодер и звукоизлучатель. Частота звукового сигнала звукоизлучателя может прини-
мать 12 дискретных значений: (f1: 523 Гц; f2: 587; f3: 659; f4: 698; f5: 783; f6: 880; f7: 987; f8: 1046;
f9: 1174; f10: 1318; f11: 1396; f12: 1568). Необходимо последовательно переключать частоты в соот-
ветствии с положением вала энкодера.
6 Используется энкодер и ЖКИ. Генерируется 7 случайных цифр от 0 до 9. Цифры одновременно по-
казываются на экране индикатора, и после паузы в 5 сек индикатор очищается. Пользователь с по-
мощью энкодера последовательно вводит 7 цифр, которые отображаются на индикаторе. Моментом
окончания ввода очередной цифры считается, когда пользователь сделал паузу после окончания
вращения вала более 3 сек. После окончания ввода проверяется, чтобы введённые цифры совпадали
со сгенерированными. Если цифры совпали, отобразить на индикаторе «Ok», иначе – «Error».
7 Используется энкодер и ЖКИ. С помощью энкодера вводится и отображается на ЖКИ число от 0 до
999. В зависимости от скорости поворота вала энкодера обеспечить увеличение/уменьшение вводи-
мого числа либо на +1/-1 (при низкой скорости поворота вала), либо на +10/-10 (при высокой скоро-
сти). Высокой считать скорость поворота, превышающую 24 импульса/сек.
8* Используется энкодер и ЖКИ. В программе в виде константы задаются три числа в диапазоне от 0
до 99 (код от сейфа). Пользователь последовательно вводит три числа в диапазоне от 0 до 99 с по-
мощью энкодера, каждое вводимое число отображается в соответствующей позиции на экране инди-
катора. При вводе чисел допустимо переполнение, когда число «0» при движении вала влево пере-
ходит в «99», а число «99» при движении вала вправо – в «0». Ввод первого числа начинается со

66
значения «0», а каждого следующего – со значения предыдущего введённого числа. Моментом
окончания ввода очередного числа считается, когда пользователь сделал паузу после окончания
вращения вала более 3 сек. По окончании ввода последнего числа код проверяется на совпадение с
заданным кодом от сейфа, кроме того проверяется, чтобы первое и третье числа были получены
движением вала по часовой стрелке, а второе – против. Если код и направления совпали, отобразить
на индикаторе «Open», иначе – «Error».
Контрольные вопросы
1. Классификация энкодеров. Сфера применения энкодеров. Где можно встретить энкодер в окружа-
ющем нас мире?
2. Принципы работы различных типов энкодеров. Основной параметр энкодера.
3. Достоинства и недостатки инкрементных энкодеров. Достоинства и недостатки механических энко-
деров.
4. Диаграммы изменения сигналов на выходе энкодера в лабораторной работе. Каким образом в про-
грамме определяется направление вращения энкодера (пояснить с использованием диаграммы)?
5. Как организован опрос изменения положения энкодера в программе? Из каких соображений выбра-
на частота, с которой вызывается прерывание от Таймера1?
6. Зачем нужны и из каких соображений выбираются номиналы подтягивающих резисторов, подклю-
чаемых к выводам «A» и «B» энкодера?
7. Для каких целей используется переменная tickcount в программе?

Лабораторная работа №12. Интерфейс I2C


Цель работы: научиться взаимодействовать с устройствами, подключёнными по шине I2C.
Интерфейс I2С разработан фирмой Philips. Он позволяет объединить вместе до 128 различных
устройств с помощью двунаправленной шины, состоящей всего из двух линий: линии тактового сигнала
(SCL) и линии данных (SDA). Единственными дополнительными элементами для реализации шины яв-
ляются два подтягивающих резистора, по одному на каждую линию.

Шинные формирователи всех I2C-совместимых устройств выполняются по схеме с открытым


коллектором (стоком), что позволяет реализовать функцию «монтажное И». Соответственно, НИЗКИЙ
уровень на линии устанавливается тогда, когда одно или более устройств выставляют на линию сигнала
лог. «0», а ВЫСОКИЙ уровень на линии устанавливается, когда все устройства, подключенные к ней,
устанавливают свои выходы в третье состояние.
При последующем рассмотрении модуля I2C будут часто встречаться следующие термины:
 Ведущий (Master) - устройство, инициирующее и завершающее передачу данных по шине, а
также генерирующее тактовый сигнал SCL.
 Ведомый (Slave) - устройство, адресуемое ведущим.
 Передатчик - устройство, выдающее данные на шину.
 Приемник - устройство, считывающее данные с шины.
Протокол интерфейса I2C позволяет подключать к шине несколько ведущих устройств (режим
Multi-Master).
Принципы обмена данными по шине I2C

67
Поскольку шина I2C является последовательной, все данные передаются по ней (по линии SDA)
побитно. Каждый передаваемый бит сопровождается импульсом на линии тактового сигнала SCL. При-
чем сигнал на линии SDA должен быть стабильным в течение всего времени, пока на шине SCL присут-
ствует сигнал лог. 1.

Единственным исключением из этого правила являются два особых состояния шины I2C —
СТАРТ и СТОП. Состояния СТАРТ и СТОП формируются ведущим соответственно в начале и в конце
передачи данных. Между этими состояниями шина считается занятой, и другие ведущие не должны пы-
таться управлять ею. Если ведущий хочет начать передачу нового блока данных без поте-
ри/восстановления контроля над шиной, он может сформировать состояние СТАРТ до формирования
состояния СТОП. Формируемое таким образом состояние называется «повторный СТАРТ» (ПО-
ВСТАРТ), а шина считается занятой до последующего формирования состояния СТОП. Состояния
СТАРТ и СТОП формируются путем изменения уровня сигнала на линии SDA при ВЫСОКОМ уровне
на линии SCL. Состоянию СТАРТ соответствует смена уровня с 1 на 0, а состоянию СТОП — наоборот,
с 0 на 1.

Формат адресного пакета


Все адресные пакеты, передаваемые по шине I2C, имеют длину 9 бит. Пакет содержит 7-битный
адрес (первым передается старший бит), управляющий бит R/W и бит квитирования АСК. Значение
управляющего бита R/W определяет направление передачи данных по шине. Бит, сброшенный в 0,
означает передачу данных, а установленный в 1 — запрос данных (чтение). Пакеты со сброшенным и
установленным управляющим битом обозначаются соответственно SLA-W и SLA-R.

При распознавании ведомым своего адреса он должен сформировать подтверждение (АСК) пу-
тем выдачи на линию SDA сигнала НИЗКОГО уровня во время 9-го тактового импульса. Если ведомый
по каким-либо причинам не может обслужить запрос ведущего, он должен во время 9-го тактового им-
68
пульса удерживать на линии сигнал ВЫСОКОГО уровня (NACK — нет подтверждения). Ведомым
устройствам могут быть назначены любые адреса, за исключением нулевого адреса и адресов из диапа-
зона 1111000... 1111111. Нулевой адрес зарезервирован для реализации так называемых общих вызовов,
а адреса формата 1111xxx должны быть зарезервированы для использования в дальнейшем.
Формат пакета данных
Все пакеты данных, передаваемые по шине I2C, тоже имеют длину 9 бит. Пакет состоит из байта
данных (первым передается старший бит) и бита квитирования АСК.

Генерация тактового сигнала и формирование состояний СТАРТ и СТОП осуществляются ве-


дущим. Приемник, в свою очередь, должен после приема каждого байта формировать подтверждение
(АСК) путем выдачи на линию SDA сигнала НИЗКОГО уровня во время 9-го тактового импульса. Если
приемник получил последний байт или по каким-либо другим причинам не может продолжать прием
данных, он должен во время 9-го тактового импульса удерживать на линии сигнал ВЫСОКОГО уровня
(NACK). Не получив подтверждения от приемника, ведущий может прекратить передачу данных,
сформировав состояние СТОП.
На практике каждый цикл обмена по шине I2C состоит из следующих этапов:
1. Формирование состояния СТАРТ.
2. Передача адресного пакета SLA+R/W.
3. Передача одного или нескольких пакетов данных.
4. Формирование состояния СТОП.

Обзор модуля TWI


В микроконтроллерах AVR семейства Mega за реализацию интерфейса I2C отвечает модуль, ко-
торый называется TWI (Two-wire Serial Interface). Взаимодействие программы с модулем TWI осу-
ществляется посредством пяти регистров ввода/вывода.
TWBR Регистр скорости передачи
TWSR Регистр состояния
TWAR Регистр адреса
TWDR Регистр данных
69
TWCR Регистр управления
Аппаратно для подключения микроконтроллера модуля к линиям шины I2C используются выво-
ды SCL и SDA. Оба вывода являются линиями портов ввода/вывода микроконтроллера. В соответствии
со спецификацией интерфейса выходные каскады, подключенные к выводам, содержат ограничители
скорости нарастания сигнала, а входные каскады содержат фильтр, подавляющий паразитные импуль-
сы длительностью менее 50 нс. При использовании указанных выводов микроконтроллера модулем
TWI к ним, как и при обычном их использовании, можно подключить внутренние подтягивающие рези-
сторы. Это позволит в ряде случаев обойтись без внешних подтягивающих резисторов, требуемых спе-
цификацией интерфейса TWI.
Задание скорости передачи
Если микроконтроллер используется в качестве ведущего, он отвечает за формирование тактово-
го сигнала на линии SCL. Его частота определяется по формуле:

Где fCLK — тактовая частота процессора; TWBR — значение, записанное в регистре TWBR;
TWPS - значение битов TWPS1:TWPS0 регистра TWSR.
Регистр управления
Регистр управления (TWCR) используется для управления всем модулем TWI. С его помощью
можно разрешить/запретить использование модуля TWI, сформировать состояние СТАРТ и СТОП на
шине, сгенерировать сигнал подтверждения (ACK) при приёме данных от ведомого, разре-
шить/запретить прерывания от модуля TWI. Также он содержит ряд флагов, сигнализирующих об изме-
нении состояния модуля.

TWINT Флаг прерывания от модуля TWI. Этот флаг устанавливается аппаратно после выполнения оче-
редной операции, когда модуль ожидает отклика со стороны программы. Если установлены флаги I
регистра SREG и TWIE регистра TWCR, генерируется прерывание и осуществляется вызов соответ-
ствующего обработчика. Пока флаг TWINT установлен, на линии SCL удерживается сигнал НИЗ-
КОГО уровня. Сброс флага может быть осуществлен только записью в него лог. 1
TWEA Разрешение бита подтверждения (ACK). Бит TWEA управляет формированием бита подтвер-
ждения. Если этот бит установлен в 1, то устройство формирует сигнал подтверждения, когда это
необходимо. При сбросе бита в 0, устройство виртуально отключается от шины TWI, т. е. бит под-
тверждения не формируется.
TWSTA Используется для формирования состояния СТАРТ. При записи в бит лог. 1 модуль проверяет
состояние шины TWI и, если шина свободна, формирует состояние СТАРТ Если шина занята, мо-
дуль TWI ожидает появления состояния СТОП и только после этого формирует состояние СТАРТ.
Флаг должен сбрасываться программно по окончании формирования состояния СТАРТ.
TWSTO Используется для формирования состояния СТОП. В режиме ведущего установка флага
TWSTO в 1 приводит к формированию на шине состояния СТОП. Флаг сбрасывается аппаратно по
окончании формирования состояния СТОП.
TWWC Флаг конфликта записи в регистр данных TWDR. Флаг устанавливается в 1 при попытке записи
в регистр TWDR, когда флаг прерывания TWINT сброшен. Флаг сбрасывается при записи в регистр
TWDR, когда флаг прерывания TWINT установлен.
TWEN Разрешение работы модуля TWI. Бит TWEN управляет работой модуля TWI. При записи в этот
бит лог. 1 модуль TWI включается и берет на себя управление контактами ввода/вывода микро-
контроллера, соответствующих выводам SCL и SDA. При сбросе бита TWEN в 0 модуль TWI вы-
ключается.
TWIE Разрешение прерывания от модуля TWI. Если в этом бите записана лог. 1 и флаг I регистра
SREG также установлен в 1, то прерывание от модуля TWI разрешено
Регистр статуса

70
TWS7...TWS3 Состояние модуля TWI. Значение, содержащееся в этих битах, отражает состояние узлов
модуля и шины TWI. Биты доступны только для чтения
TWPS1:TWPS0 Коэффициент деления предделителя контроллера скорости передачи. Состояние этих
битов определяет коэффициент деления предделителя контроллера скорости передачи,
управляя частотой генерируемого сигнала SCL
Взаимодействие прикладной программы с модулем TWI
Взаимодействие прикладных программ с модулем TWI базируется на использовании прерывания
от модуля, которое возникает после каждого события, произошедшего на шине (прием байта, формиро-
вание состояний СТАРТ/СТОП и т. п.). Соответственно, во время передачи данных по шине программа
может выполнять другие задачи. Если прерывание от модуля TWI по каким-либо причинам использо-
вать нельзя, его можно запретить. В этом случае программа должна будет постоянно следить за состоя-
нием флага TWINT для реагирования на события, происходящие на шине. Установка флага TWINT ре-
гистра TWCR означает, что модуль TWI закончил выполнение очередной операции и ожидает реакции
программы. В старших пяти битах (TWS7...0) регистра TWSR при этом формируется некоторое значе-
ние, характеризующее текущее состояние шины TWI. Соответственно, программа должна проанализи-
ровать это значение и задать дальнейшее поведение модуля TWI, манипулируя содержимым регистров
TWCR и TWDR.
На рис. ниже показано взаимодействие прикладной программы с модулем TWI на простейшем
примере передачи одного байта данных от ведущего к ведомому.

Передача данных происходит в следующей последовательности:


1. Первой операцией при передаче данных по шине TWI является формирование состояния СТАРТ.
Для этого следует записать определенное значение в регистр TWCR, в соответствии с которым модуль
TWI сформирует на шине состояние СТАРТ. При этом бит записываемого значения, соответствующий
флагу TWINT, должен быть установлен в 1 (для сброса этого флага). Формирование состояния СТАРТ
начнется сразу же после сброса флага TWINT.
2. После формирования состояния СТАРТ устанавливается флаг TWINT. Число, находящееся в ре-
гистре состояния TWSR, индицирует результат выполнения этой операции.
3. Необходимо удостовериться в успешном формировании состояния СТАРТ, проверив содержи-
мое регистра TWSR. Если код статуса соответствует ожидаемому, следует загрузить в регистр TWDR
содержимое пакета SLA+W и сформировать в регистре TWCR команду на передачу пакета (не забывая
сбросить при этом флаг TWINT). Передача адресного пакета начнется сразу же после сброса флага.

71
4. По окончании передачи адресного пакета устанавливается флаг TWINT. Код статуса, находя-
щийся в регистре TWCR, позволяет определить, была ли передача пакета успешной, а также было ли
получено подтверждение от ведомого устройства.
5. Необходимо удостовериться в успешной передаче пакета и получении подтверждения, проверив
содержимое регистра TWSR. Если код статуса соответствует ожидаемому, следует загрузить данные в
регистр TWDR, а затем сформировать в регистре TWCR команду на передачу пакета (не забывая сбро-
сить при этом флаг TWINT). Передача пакета данных начнется сразу же после сброса флага.
6. По окончании передачи пакета данных устанавливается флаг TWINT. Код статуса, находящийся
в регистре TWSR, позволяет определить, была ли передача пакета успешной, а также было ли получено
подтверждение от ведомого устройства.
7. Необходимо удостовериться в успешной передаче пакета и получении подтверждения, проверив
содержимое регистра TWSR. Если код статуса соответствует ожидаемому, следует записать в регистр
TWCR значение (не забывая сбросить при этом флаг TWINT), в соответствии с которым модуль TWI
сформирует на шине состояние СТОП. Формирование состояния СТОП начнется сразу же после сброса
флага TWINT.

На рис. ниже показано взаимодействие прикладной программы с модулем TWI на простейшем


примере передачи одного байта данных от ведомому к ведущему.

Этапы 1..4 совпадают с описанными выше в примере передачи одного байта данных от ведущего
к ведомому, за исключением того, что в п.3 передаётся пакет не SLA+W, а SLA+R. Далее:
5. Необходимо удостовериться в успешной передаче пакета и получении подтверждения, проверив
содержимое регистра TWSR. Если код статуса соответствует ожидаемому, сформировать в регистре
TWCR команду на приём пакета (не забывая сбросить при этом флаг TWINT). Если пакет, который сей-
час будет приниматься от ведомого, последний в данном цикле приёма данных, необходимо сбросить
флаг TWEA в регистре TWCR (иначе флаг нужно установить). Приём пакета данных начнется сразу же
после сброса флага TWINT.
6. По окончании приёма пакета данных устанавливается флаг TWINT. Код статуса, находящийся в
регистре TWSR, позволяет определить, был ли приём пакета успешным.
7. Необходимо удостовериться в успешном приёме пакета, проверив содержимое регистра TWSR.
Если код статуса соответствует ожидаемому, следует записать в регистр TWCR значение (не забывая
сбросить при этом флаг TWINT), в соответствии с которым модуль TWI сформирует на шине состояние
СТОП. Формирование состояния СТОП начнется сразу же после сброса флага TWINT.

Из сказанного выше видно, что любой этап взаимодействия прикладной программы с модулем
TWI состоит из трех частей:
1. После завершения модулем выполнения какой-либо операции он устанавливает флаг TWINT ре-
гистра TWCR и ожидает реакции программы. Пока флаг установлен в 1, на линии SCL удерживается
НИЗКИЙ уровень.
72
2. После установки флага TWINT пользователь должен занести в регистры модуля значения, соот-
ветствующие следующему этапу обмена.
3. После обновления содержимого регистров модуля пользователь должен сформировать в регистре
TWCR команду на выполнение следующего этапа обмена. При загрузке в регистр нового значения
необходимо сбросить флаг TWINT, записав в него лог. 1. После сброса флага модуль начнет выполне-
ние операции, определяемой содержимым регистра TWCR.

Возможности и количество режимов модуля TWI гораздо шире, чем описано выше. Более по-
дробно ознакомиться с возможностями модуля можно с помощью руководства по программируемому
микроконтроллеру.
В нашей лабораторной работе мы будем использовать модуль TWI для работы в режиме «Веду-
щий-передатчик» и «Ведущий-приёмник».
Комбинирование режимов передачи и приёма
На практике, для выполнения какой-либо операции по шине TWI каждому устройству приходит-
ся использовать режимы приёма и передачи, переключаясь между ними при необходимости. В качестве
примера рассмотрим операцию чтения данных из внешнего EEPROM. Все операции такого рода можно
разбить на четыре этапа:
1. Инициирование обмена.
2. Передача адреса, по которому требуется прочитать данные.
3. Выполнение чтения.
4. Завершение передачи.
Из сказанного видно, что при выполнении операции происходит передача информации как от ве-
дущего к ведомому, так и наоборот. После инициирования обмена по шине ведущий должен находиться
в режиме «Ведущий-передатчик», чтобы сообщить ведомому адрес, по которому он намеревается осу-
ществить чтение. Для выполнения следующего этапа операции (чтение данных) ведущий должен пере-
ключиться в режим «Ведущий-приемник». При этом он должен сохранять контроль над шиной во время
выполнения всех этапов операции. Для этого используется состояние «повторный СТАРТ». В рассмат-
риваемом случае ведущий формирует это состояние между передачей адреса и приемом данных, как
показано на рис. ниже

Микросхема EEPROM 24C64WP


Задания данной лабораторной работы будут связаны с работой с микросхемой EEPROM (Electri-
cally Erasable Programmable Read-Only Memory — электрически стираемое перепрограммируемое по-
стоянное запоминающее устройство) типа 24C64WP. Микросхема имеет объём памяти в 8Кбайт, разде-
лённой на страницы по 32 байта, и подключается к микроконтроллеру по интерфейсу I2C.

73
7-битный адрес микросхемы на шине состоит из фиксированной 4-битной старшей части, равной
(0b1010) и конфигурируемой с помощью выводов A2,A1,A0 3-битной младшей части. Как видно из
схемы, на выводы A2,A1,A0 с помощью внешнего набора переключателей K1 могут быть поданы как
лог. «0», так и лог. «1». Кроме того, микросхема имеет вывод \WC, служащий для защиты от записи,
который в нашем случае должен быть подключен к «земле».
В нашей работе взаимодействие с микросхемой будет осуществляться в побайтовом режиме, т.е.
за одно обращение из её памяти будет прочитано или в неё записано не более 1 байта информации.
Микросхема EEPROM поддерживает и другие режимы работы с ней, познакомиться с которыми можно,
изучив её спецификацию.
Для записи байта используется следующая последовательность действий:

74
где «Start» - это состояние СТАРТ шины I2C; «Dev sel» - это 7-битный адрес микросхемы, описанный
выше и равный 0b1010A2A1A0; «R/W» - бит – признак команды записи = «0»; «addrH» - старшая часть
адреса памяти, по которому производится запись информации; «addrL» - младшая часть; «Data in» -
байт данных для записи; «ACK» - бит подтверждения, который выдаёт микросхема памяти в ответ на
передачу каждого байта от микросконтроллера; «Stop» - это состояние СТОП шины I2C.
Процесс записи информации в микросхеме памяти занимает не менее 5 мс, поэтому после подачи
команды записи до выполнения других действий с микросхемой необходимо сделать паузу не менее
указанного времени.
Для чтения байта используется следующая последовательность действий:

Из диаграммы видно, что в случае чтения сначала шина инициализируется микроконтроллером в


режиме «Ведущий-передатчик», после чего ведомому устройству (микросхеме памяти) передаётся ад-
рес. Затем, используя состояние «повторный СТАРТ», шина переключается в режим «Ведущий-
приемник». Можно заметить, что после приёма байта информации «Data out» (требуемое содержимое
памяти) от ведомого, микроконтроллер не устанавливает ведомому бит подтверждения «NO ACK», т.к.
в данном случае это последний и единственный прочитанный из микросхемы байт данных.

Ниже приведён пример программы. Её можно взять как основу для выполнения вашего варианта.
В программе имеются функции для работы с устройством, подключённым по шине I2C к микро-
контроллеру, в режимах «Ведущий-передатчик» и «Ведущий-приёмник»: i2c_init(), i2c_start(),
i2c_write(), i2c_read(), i2c_stop(). Также имеются функции для работы с внешней микросхемой
EEPROM: eeprom_read_byte() и eeprom_write_byte(). Функции сопровождены комментариями, поясня-
ющими их работу. В нескольких местах программы часть кода намеренно заменена на символы «?????».
Вам необходимо по логике работы и контексту восстановить недостающую часть кода.

#include <avr/io.h>
#define F_CPU 7372800UL // контролер работает с частотой 7.3728 МГц
#include <util/delay.h>
#include <util/twi.h>

// адрес EEPROM
#define EE_BUS_ADDR ?????

///////////////////////////////////////////////////////////////////////////////

#define F_SCL 100000UL // частота на шине TWI


#define Prescaler 1
// флаги чтения/записи
#define I2C_READ ?????
#define I2C_WRITE 0

// инициализация модуля TWI - установка скорости


void i2c_init(void)
{
????? = (uint8_t)((((F_CPU / F_SCL) / Prescaler) - 16 ) / 2);
}

75
// старт операции и ожидание её окончания
static void _i2c_set_CR_wait(uint8_t cmd)
{
// загружаем регистр управления TWI
????? = cmd;
// ожидание окончания операции
while (!(TWCR & (1<<TWINT)));
}

// передача байта данных ведомому устройству


static uint8_t _i2c_write_byte(uint8_t data)
{
// загружаем данные для ведомого устройства в регистр данных TWI
????? = data;
// передаём данные
_i2c_set_CR_wait(1<<TWINT | 1<<TWEN);
// возвращаем статус
return ????? & TW_STATUS_MASK;
}

// передача состояния START, адреса и флага R/W ("address") ведомому устройству


// возвращает 0, если передача завершилась удачно, иначе возвращает не 0
uint8_t i2c_start(uint8_t address)
{
// сброс регистра управления TWI
TWCR = 0;
// передача состояния START
_i2c_set_CR_wait(1<<TWINT | 1<<TWSTA | 1<<TWEN);
// проверка, что состояние START передано успешно
if ((TWSR & 0xF8) != TW_START){ return 2; }
// передаём адрес ведомого устройства
// и проверяем, что ведомый подтвердил вход в режим чтения или записи
uint8_t twst = _i2c_write_byte(address);
return ((twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK));
}

// передача байта данных "data" ведомому устройству


// возвращает 0, если передача завершилась удачно, иначе возвращает не 0
uint8_t i2c_write(uint8_t data)
{
// передаём данные ведомому устройству
// и проверяем, что ведомый подтвердил приём данных
return (?????(data) != TW_MT_DATA_ACK);
}

// приём байта данных от ведомого устройства


// Если bSetACK!=0, то ведомому устройству даётся подтверждение, иначе - нет
// возвращает принятый байт данных
uint8_t i2c_read(uint8_t bSetACK)
{
if (?????) // нужно давать подтверждение ведомому?
_i2c_set_CR_wait(1<<TWINT | 1<<TWEN | 1<<TWEA);
else
_i2c_set_CR_wait(1<<TWINT | 1<<TWEN);
// возвращаем данные, переданные ведомым
return ?????;
}

76
// передача состояния STOP
void i2c_stop(void)
{
TWCR = 1<<TWINT | 1<<TWEN | 1<<TWSTO;
}

///////////////////////////////////////////////////////////////////////////////

// читает из внешней EEPROM байт данных по адресу address и записывает в *pData


// возвращает 1, если чтение прошло удачно, иначе возвращает 0
uint8_t eeprom_read_byte(uint16_t address, uint8_t *pData)
{
uint8_t rslt = 0; // возвращаемое значение
do {
// передача состояния START, адреса ведомого и флага "Write"
if (?????(0xA0 | ?????<<1 | I2C_WRITE)) break;
if (i2c_write(?????)) break; // передаём старшую часть адреса
if (i2c_write(?????)) break; // передаём младшую часть адреса
if (i2c_start(0xA0 | ?????<<1 | I2C_READ)) break; // повторный START
*pData = i2c_read(0); // читаем данные
rslt = 1;
} while (0);
?????(); // передача состояния STOP
return rslt;
}

// записывает байт данных data во внешнюю EEPROM по адресу address


// возвращает 1, если запись прошла удачно, иначе возвращает 0
uint8_t eeprom_write_byte(uint16_t address, uint8_t data)
{
uint8_t rslt = 0; // возвращаемое значение
do {
// передача состояния START, адреса ведомого и флага "Write"
if (?????(0xA0 | ?????<<1 | I2C_WRITE)) break;
if (i2c_write(?????)) break; // передаём старшую часть адреса
if (i2c_write(?????)) break; // передаём младшую часть адреса
if (i2c_write(data)) break; // передаём байт данных для записи
rslt = 1;
} while (0);
?????(); // передача состояния STOP
return rslt;
}

///////////////////////////////////////////////////////////////////////////////

// частота переключения звукоизлучателя


#define BUZ_FREQ 0x1C

void sound_on(void)
{
OCR2 = BUZ_FREQ;
}

void sound_off(void)
{
OCR2 = 0;
}

77
int main(void)
{
// инициализация для управления звукоизлучателем
DDRD = (1 << PD7);
TCCR2 = (1 << WGM21) | (0 << WGM20) | (0 << COM21) | (1 << COM20) | (1 << CS22) | (0 <<
CS21) | (0 << CS20); // pre=64

i2c_init(); // инициализация модуля TWI

uint16_t eeaddr = 0x00; // адрес для записи


uint8_t eedata = 0x86; // данные для записи

uint8_t r = eeprom_write_byte(eeaddr, eedata); // записываем байт данных


if (r) // удачно?
{
_delay_ms(10); // ожидаем окончания записи
uint8_t rdata;
uint8_t r = eeprom_read_byte(eeaddr, &rdata); // читаем байт данных
if (r) // удачно?
if (rdata == eedata)// совпадает с ранее записанным?
{
sound_on(); // генерируем короткий звуковой сигнал
_delay_ms(20);
sound_off();
}
}

while (1)
;
}

Варианты выполнения лабораторной работы:


Обратите внимание, что для каждого варианта в колонке «Адрес памяти» задаётся своя младшая
часть адреса микросхемы EEPROM, которая устанавливается комбинацией соответствующих логиче-
ских уровней на выводах A2,A1,A0 микросхемы с помощью набора переключателей K1:

№ Адрес
вар- памя- Задание
та ти
1 0 Используется энкодер, UART на скорости 19200 бит/с и внешняя EEPROM. При изменении поло-
жения энкодера на компьютер должен передаваться код текущего положения энкодера (от 0 до
255). Необходимо реализовать запоминание текущего положения энкодера во внешней EEPROM,
чтобы после снятия и последующего восстановления питания восстанавливалось последнее запом-
ненное значение. Поскольку EEPROM имеет ограничение на число перезаписей, запись произво-
дить только когда пользователь сделал паузу после окончания вращения вала более 2 сек.
2 1 Используется UART на скорости 19200 бит/с и внешняя EEPROM. Когда от компьютера приходит
пакет вида: «S»+«Строка» (где «S» - байт команды; «Строка» – произвольная строка из ASCII-
символов длиной не более 10 байт, оканивающаяся байтом с кодом «0»), проверить путём считы-
вания из внешней EEPROM, если данная строка совпадает с записанной в EEPROM, то выдать в
компьютер строку «Match»; если нет - записать «Строку» в EEPROM и выдать в компьютер строку
«Written».
3 2 Используется UART на скорости 19200 бит/с и внешняя EEPROM. Когда от компьютера приходит
пакет вида: «W»+ addrH+addrL+data (где «W» - байт команды; addrH – старший байт адреса памя-
ти; addrL – младший байт; data – байт данных), записать байт data во внешнюю EEPROM по адресу
addrH:addrL, затем проверить правильность записи (считать байт данных по адресу addrH:addrL и

78
сравнить с записанным), если записано правильно, выдать в компьютер строку «Ок».
4 3 Используется UART на скорости 19200 бит/с и внешняя EEPROM. Когда от компьютера приходит
пакет вида: «R»+ addrH+addrL (где «R» - байт команды; addrH – старший байт адреса памяти; ad-
drL – младший байт), считать байт из внешней EEPROM по адресу addrH:addrL и выдать в компь-
ютер. Если при приёме пакета между байтами пакета обнаруживается таймаут более 1 сек, начи-
нать приём пакета заново.
5 4 Используется UART на скорости 19200 бит/с и внешняя EEPROM. Когда от компьютера приходит
пакет вида: «S»+«Строка» (где «S» - байт команды; «Строка» – произвольная строка из ASCII-
символов длиной не более 10 байт, оканивающаяся байтом с кодом «0»), записать «Строку » во
внешнюю EEPROM затем проверить правильность записи (считать данные из памяти и сравнить с
записанными), если записано правильно, выдать в компьютер строку «Ок».
6 5 Используется UART на скорости 19200 бит/с и внешняя EEPROM. Необходимо подсчитывать ко-
личество байт, принятых от компьютера (от 0 до 65535). Реализовать запоминание текущего зна-
чения счётчика во внешней EEPROM, чтобы после снятия и последующего восстановления пита-
ния восстанавливалось последнее запомненное значение. Поскольку EEPROM имеет ограничение
на число перезаписей, запись производить только когда от компьютера перестали поступать новые
данные более 2 сек. Сразу после окончания записи выводить в компьютер текущее значение счёт-
чика.
7 6 Используется UART на скорости 19200 бит/с и внешняя EEPROM. Когда от компьютера приходит
пакет вида: «F»+ addrH+addrL+len (где «F» - байт команды; addrH – старший байт начального ад-
реса блока памяти; addrL – младший байт; len – байт, содержащий длину блока памяти), считать из
внешней EEPROM блок данных, начиная с адреса addrH:addrL длиной len, вычислить его кон-
трольную сумму (по алгоритму, используемому в протоколе 1-Wire) и выдать в компьютер.
8* 7 Используется UART на скорости 19200 бит/с, клавиатура и внешняя EEPROM. Реализовать запо-
минание нажатых пользователем кнопок во внешней памяти, причём после снятия и последующе-
го восстановления питания запись должна продолжаться с того места, где остановилась во время
снятия питания. Когда от компьютера приходит команда «R», в ответ необходимо выдать в ком-
пьютер все запомненные на данный момент коды нажатых пользователем кнопок в той последова-
тельности, в которой они нажимались. Когда от компьютера приходит команда «E», все запомнен-
ные данные удаляются, и процесс регистрации начинается заново.
Контрольные вопросы
1. Принципы организации и обмена данными по шине I2C. Какое устройство является ведущим и ве-
домым, передатчиком и приёмником, например, при взаимодействии микроконтроллера и внешней
EEPROM?
2. Достоинства и недостатки шины I2C. Что нужно сделать, чтобы увеличить дальность подключения
(назовите программные и аппаратные меры)?
3. Из каких этапов состоит цикл обмена информации по шине?
4. Каков формат передачи информации по шине?
5. Как в микроконтроллере определяется скорость обмена данными по шине, если микроконтроллер -
ведущий? А если ведомый? Какая скорость используется в программе?
6. Принципы взаимодействия программы микроконтроллера с модулем TWI. Как взаимодействовать с
модулем с использованием/без использования прерываний?
7. Привести пример комбинирования режимов передачи и приёма. За счёт чего сохраняется контроль
ведущего над шиной при переходе от передачи к приёму?
8. Изучив техническое описание на микросхему внешней EEPROM, модифицировать функцию
eeprom_read_byte(), чтобы с её помощью можно прочитывать не 1, а 3 байта информации за один цикл
обмена с микросхемой памяти.
9. Изучив техническое описание на микросхему внешней EEPROM, модифицировать функцию
eeprom_write_byte(), чтобы с её помощью можно записывать не 1, а 4 байта информации за один цикл
обмена с микросхемой памяти.
10. Как реализовать запись в микросхему внешней EEPROM с использованием функции printf()?

Лабораторная работа №13. Интерфейс SPI


Цель работы: научиться взаимодействовать с устройствами, подключёнными по шине SPI.
SPI (англ. Serial Peripheral Interface, SPI bus — последовательный периферийный интерфейс, ши-
на SPI) — последовательный синхронный стандарт передачи данных в режиме полного дуплекса, пред-
79
назначенный для обеспечения простого и недорогого высокоскоростного сопряжения микроконтролле-
ров и периферии. SPI также иногда называют четырёхпроводным интерфейсом.
В отличие от стандартного последовательного порта, SPI является синхронным интерфейсом, в
котором любая передача синхронизирована с общим тактовым сигналом, генерируемым ведущим
устройством (процессором). Принимающая (ведомая) периферия синхронизирует получение битовой
последовательности с тактовым сигналом. К одному последовательному периферийному интерфейсу
ведущего устройства-микросхемы может присоединяться несколько микросхем. Ведущее устройство
выбирает ведомое для передачи, активируя сигнал «выбор кристалла» (англ. chip select) на ведомой
микросхеме. Периферия, не выбранная процессором, не принимает участия в передаче по SPI.
В SPI используются четыре цифровых сигнала:
 MOSI — выход ведущего, вход ведомого (англ. Master Out Slave In). Служит для передачи дан-
ных от ведущего устройства ведомому.
 MISO — вход ведущего, выход ведомого (англ. Master In Slave Out). Служит для передачи дан-
ных от ведомого устройства ведущему.
 SCLK — последовательный тактовый сигнал (англ. Serial Clock). Служит для передачи тактового
сигнала для ведомых устройств.
 CS или SS — выбор микросхемы, выбор ведомого (англ. Chip Select, Slave Select).

Частота следования битовых интервалов в линиях передачи данных определяется


синхросигналом SCK, который генерирует ведущее устройство, ведомые устройства используют
синхросигнал для определения моментов изменения битов на линии данных, при этом ведомые
устройства никак не могут влиять на частоту следования битовых интервалов. Как в ведущем
устройстве, так и в ведомом устройстве имеется счетчик импульсов синхронизации (битов). Счетчик в
ведомом устройстве позволяет последнему определить момент окончания передачи пакета. В ведомом
устройстве счетчик обычно сбрасывается деактивацией интерфейсного сигнала SS.
Передача данных осуществляется пакетами. Длина пакета, как правило, составляет 1 байт (8
бит). Ведущее устройство инициирует цикл связи установкой низкого уровня на выводе выбора
подчиненного устройства (SS) того устройства, с которым необходимо установить соединение. При
низком уровне сигнала SS:
 схемотехника ведомого устройства находится в активном состоянии;
 вывод MISO переводится в режим «выход»;
 тактовый сигнал SCLK от ведущего устройства воспринимается ведомым и вызывает
считывание на входе MOSI значений передаваемых от ведущего битов и сдвиг регистра
ведомого устройства.
Подлежащие передаче данные ведущее и ведомое устройства помещают в сдвиговые регистры.
После этого ведущее устройство начинает генерировать импульсы синхронизации на линии SCLK, что
приводит к взаимному обмену данными. Передача данных осуществляется бит за битом от ведущего по
линии MOSI и от ведомого по линии MISO. Передача осуществляется, как правило, начиная со старших
битов, но некоторые производители допускают изменение порядка передачи битов программными
методами. После передачи каждого пакета данных ведущее устройство, в целях синхронизации
ведомого устройства, может перевести линию SS в высокое состояние.
Режимы работы интерфейса SPI
Возможны четыре комбинации фазы (CPHA) и полярности (CPOL) сигнала SCLK по отношению
к сигналам данных. Режимы работы определяются комбинацией бит CPHA и CPOL:
CPOL = 0 — сигнал синхронизации начинается с низкого уровня;
CPOL = 1 — сигнал синхронизации начинается с высокого уровня;
CPHA = 0 — выборка данных производится по переднему фронту сигнала синхронизации;
CPHA = 1 — выборка данных производится по заднему фронту сигнала синхронизации.

80
Для обозначения режимов работы интерфейса SPI принято следующее соглашение:
 режим 0 (CPOL = 0, CPHA = 0);
 режим 1 (CPOL = 0, CPHA = 1);
 режим 2 (CPOL = 1, CPHA = 0);
 режим 3 (CPOL = 1, CPHA = 1).
Преимущества и недостатки интерфейса
Преимущества:
 Полнодуплексная передача данных по умолчанию.
 Более высокая пропускная способность по сравнению с I²C или SMBus.
 Возможность произвольного выбора длины пакета, длина пакета не ограничена восемью битами.
 Простота аппаратной реализации:
 более низкие требования к энергопотреблению по сравнению с I²C и SMBus;
 возможно использование в системах с низкостабильной тактовой частотой;
 ведомым устройствам не нужен уникальный адрес, в отличие от таких интерфейсов, как I²C,
GPIB или SCSI.
 Используется только четыре вывода, что гораздо меньше, чем для параллельных интерфейсов.
 Однонаправленный характер сигналов позволяет при необходимости легко организовать
гальваническую развязку между ведущим и ведомыми устройствами.
 Максимальная тактовая частота ограничена только быстродействием устройств, участвующих в
обмене данными.
Недостатки:
 Необходимо больше выводов, чем для интерфейса I²C.
 Ведомое устройство не может управлять потоком данных.
 Нет подтверждения приема данных со стороны ведомого устройства (ведущее устройство может
передавать данные «в никуда»).
 Нет определенного стандартом протокола обнаружения ошибок.
 По дальности передачи данных интерфейс SPI уступает таким стандартам, как UART и CAN.
 Наличие множества вариантов реализации интерфейса.
 Отсутствие поддержки горячего подключения устройств.
Модуль SPI микроконтроллера AVR
За реализацию интерфейса SPI в микроконтроллере AVR отвечает одноименный модуль.
Модуль SPI использует четыре вывода микроконтроллера. Как и в большинстве прочих
периферийных устройств, эти выводы мультиплексированы с линиями портов ввода/вывода общего
назначения. Модуль SPI может реализовывать как функцию ведущего, так и функцию ведомого.
Для управления модулем SPI предназначен регистр управления SPCR:

81
Бит Описание
SPIE Разрешение прерывания от SPI (1-разрешение)
SPE Включение/выключение SPI (1-включение)
DORD Порядок передачи данных (0 – первым передаётся старший бит; 1 - младший)
MSTR Выбор режима работы (Ведущий/Ведомый) (1-Ведущий)
CPOL Данные два бита задают полярность и фазу тактового сигнала (см. режимы работы интер-
СРНА фейса SPI выше)
SPR1:SPR0 Скорость передачи - данные два бита совместно с битом SP2X регистра SPSR определяют
скорость передачи данных (см. ниже)
Контроль состояния модуля, а также дополнительное управление скоростью обмена осуществ-
ляются с помощью регистра состояния SPSR:

Бит Описание
SPIF Флаг прерывания от SPI. Данный флаг устанавливается в 1 по окончании передачи очередного
байта. Если флаг SPIE регистра SPCR установлен в 1 и прерывания разрешены, то одновре-
менно с установкой флага генерируется прерывание от SPI. Флаг сбрасывается аппаратно либо
при старте подпрограммы обработки прерывания, либо после чтения регистра состояния SPI с
последующим обращением к регистру данных SPI (SPDR)
WCOL Флаг конфликта записи. Данный флаг устанавливается в 1 при попытке записи в регистр дан-
ных (SPDR) во время передачи очередного байта.
SPI2X Удвоение скорости обмена. Данный бит совместно с битами SPR1:SPR0 регистра SPСR опре-
деляют скорость передачи данных (см. ниже)
Частота тактового сигнала SCK и соответственно скорость передачи данных по интерфейсу
определяются состоянием битов SPR1:SPR0 регистра SPCR и бита SPI2X регистра SPSR:
SPI2X SPR1 SPR0 Частота сигнала SCK
0 0 0 fclk/4
0 0 1 fclk/16
0 1 0 fclk/64
0 1 1 fclk/128
1 0 0 fclk/2
1 0 1 fclk/8
1 1 0 fclk/32
1 1 1 fclk/64
Примечание: fclk — тактовая частота микроконтроллера.
Передаваемые данные записываются, а принимаемые — считываются из регистра данных SPDR.
Запись в этот регистр инициирует начало передачи, а при его чтении считывается содержимое буфера
сдвигового регистра приёмника.
Порядок работы с модулем SPI
Перед выполнением обмена необходимо, прежде всего, разрешить работу модуля SPI, устано-
вить режим работы (ведущий/ведомый); порядок передачи данных, задать полярность и фазу тактового
сигнала и скорость передачи путём записи соответствующих значений в регистры SPCR и SPSR.
В режиме ведущего (который будет использоваться в данной работе) передача данных осуществ-
ляется следующим образом. На линии SS (выбор микросхемы ведомого) вручную (т.е. программно)
устанавливается активный низкий уровень. При записи в регистр данных SPI ведущего микроконтрол-
лера (SPDR) автоматически запускается генератор тактового сигнала модуля SPI, и данные начинают

82
побитно выдаваться на вывод MOSI и соответственно поступать на вывод MOSI ведомого микро-
контроллера. После выдачи последнего бита текущего байта генератор тактового сигнала останавлива-
ется с одновременной установкой в 1 флага SPIF регистра SPSR. После этого микроконтроллер может
начать передачу следующего байта либо, подав на вход SS ведомого напряжение ВЫСОКОГО уровня,
перевести его в состояние ожидания. Одновременно с передачей данных от ведущего к ведомому про-
исходит передача и в обратном направлении. По окончании передачи очередного байта ведущий мик-
роконтроллер может считать принятый от ведомого байт данных, прочитав регистр данных SPDR. Та-
ким образом, в каждом цикле сдвига происходит обмен данными между устройствами.
Подробное описание функционирования модуля SPI микроконтроллера в режиме ведомого мож-
но посмотреть в руководстве по программируемому микроконтроллеру.
Микросхема акселерометра ADXL345
Задания данной лабораторной работы будут связаны с работой с микросхемой акселерометра
ADXL345, выпускаемой компанией Analog Devices. Акселерометр представляет собой датчик, измеря-
ющий проекции ускорения на три ортогональные пространственные оси X, Y и Z (в т.ч. и ускорение
свободного падения). Учитывая величину ускорения свободного падения (g) и данные измерения по
трем осям, можно определить ориентацию акселерометра в пространстве.

Расположение осей акселерометра (выходное напряжение Ux, Uy или Uz напряжение увеличивается,


когда микросхема испытывает ускорение вдоль соответствующей оси)

Акселерометр ADXL345 способен измерять ускорение величиной до ±16 g, с максимальным раз-


решением 13 бит, частота измерения может достигать 3200 Гц. Обладает низким энергопотреблением,
максимум 140 мкА, напряжение питания может находиться в пределах 2…3,6 В. Акселерометр поддер-
83
живает два распространенных интерфейса связи SPI и I2C, а также имеет два выхода прерываний и
встроенный буфер для хранения данных.
Акселерометр ADXL345 имеет относительно сложную внутреннюю структуру и значительное
количество регистров, позволяющих настроить его для работы в различных режимах. Для простоты в
нашей работе мы будем использовать микросхему в режиме по умолчанию, который активируется при
подаче питания на акселерометр (частота измерения 100 Гц; разрешение 10 бит; диапазон измеряемых
ускорений вдоль каждой оси ±2 g). Единственное, что нужно будет сделать для инициализации микро-
схемы после включения питания – это перевести её в активный режим (режим измерений), записав чис-
ло 0b00001000 в её внутренний регистр POWER_CTL_REG (имеющий адрес 0x2D). Для получения из-
меренных акселерометром значений ускорений вдоль осей X, Y и Z можно периодически читать содер-
жимое его внутренних регистров DATAX0_REG, DATAY0_REG и DATAZ0_REG соответственно. Ад-
реса этих регистров: 0x32, 0x34 и 0x36 соответственно. Данные регистры имеют размерность 16 бит и
могут содержать знаковые числа в диапазоне от -511 до 511, что будет соответствовать значениям уско-
рения от -2g до +2g.
Микросхема подключается к микроконтроллеру по интерфейсу SPI (см. рисунок ниже). Для за-
писи значения во внутренний регистр акселерометра необходимо передать по SPI последовательность
из двух байтов:
Передача адреса Передача байта данных
регистра для записи в регистр
Для чтения двухбайтового значения из внутреннего регистра акселерометра необходимо пере-
дать по SPI 1-байтовый адрес регистра с дополнительно установленными 7-ым и 6-ым битами, и, вслед
за этим, принять от устройства последовательность из двух байтов:
Передача адреса Приём младшего Приём старшего
регистра + 0xC0 байта данных байта данных
При выполнении задания лабораторной работы руководствуйтесь приведённой ниже принципи-
альной схемой. Можно обратить внимание, что питание акселерометра осуществляется с вывода POR-
TA.7 микроконтроллера, поэтому в программе данный вывод следует сконфигурировать как выход и
подать на него лог. «1».

84
Ниже приведён пример программы. Её можно взять как основу для выполнения вашего варианта.
В программе имеется функция spi() для работы с ведомым устройством, подключённым по интерфейсу
SPI к микроконтроллеру. Также имеются функции для работы с внешней микросхемой акселерометра:
ADXL345_writeToSPI() и ADXL345_readFromSPI(). Функции сопровождены комментариями, поясняю-
щими их работу. В нескольких местах программы часть кода намеренно заменена на символы «?????».
Вам необходимо по логике работы и контексту восстановить недостающую часть кода.

#include <avr/io.h>
#define F_CPU 7372800UL // контролер работает с частотой 7.3728 МГц
#include <util/delay.h>

// вывод микроконтроллера, подключённый к выводу "выбор микросхемы" акселерометра


#define PORT_CS PORTB

85
#define CS PB4

///////////////////////////////////////////////////////////////////////////////
// функция передачи байта по SPI и чтения возвращаемого по SPI значения
// data - байт для передачи. Возвращает принятый по SPI байт
uint8_t spi(uint8_t data)
{
????? = data; // передаваемый байт записываем в регистр данных SPI
while ((????? & (1<<SPIF)) == 0) // ожидаем окончания обмена по SPI
;
return ?????; // результат читаем из регистра данных
}

///////////////////////////////////////////////////////////////////////////////
// адреса некоторых регистров акселерометра
#define ADXL345_POWER_CTL_REG 0x2D // регистр управления питанием
#define ADXL345_DATAX0_REG 0x32 // регистр данных по оси "Х"
#define ADXL345_DATAY0_REG 0x34 // регистр данных по оси "Y"
#define ADXL345_DATAZ0_REG 0x36 // регистр данных по оси "Z"

// запись байта данных val в регистр reg_address акселерометра


void ADXL345_writeToSPI(uint8_t reg_address, uint8_t val)
{
PORT_CS &= ~(1<<CS); // активируем сигнал "выбор микросхемы"
spi(?????); // передаём адрес
spi(?????); // передаём данные
PORT_CS |= (1<<CS); // снимаем сигнал "выбор микросхемы"
}

// чтение одного или нескольких байт данных из акселерометра


// reg_address - адрес регистра; num - кол-во байт для чтения; buff - адрес буфер для за-
писи результата
void ADXL345_readFromSPI(uint8_t reg_address, uint8_t num, uint8_t* buff)
{
uint8_t address = 0x80 | reg_address; // уст. флаг чтения
if (num > ?????) // если кол-во данных для чтения больше 1,
address |= 0x40; // уст. флаг

PORT_CS &= ~(1<<?????); // активируем сигнал "выбор микросхемы"


spi(address); // передаём адрес
for (uint8_t i=0; i<?????; i++) // принимаем массив данных
*buff++ = spi(0x00);
PORT_CS |= (1<<?????); // снимаем сигнал "выбор микросхемы"
}

///////////////////////////////////////////////////////////////////////////////

#define BUZ_FREQ 0x1C // частота переключения звукоизлучателя

void sound_on(void) {
OCR2 = BUZ_FREQ;
}

void sound_off(void) {
OCR2 = 0;
}

int main(void)
{
DDRA = 1<<7; // подаём питание на акселерометр
PORTA = 1<<7;
DDRB = 1<<7 | 1<<5 | 1<<4; // инициализируем выводы SPI и SS
PORTB = 1<<4;
86
DDRD = (1 << PD7); // sound pin

// инициализируем SPI
SPCR = 1<<SPE | 0<<DORD | 1<<MSTR | 1<<CPOL | 1<<CPHA | 0<<SPR1 | 1<<SPR0;
// таймер для генерации звука
TCCR2 = (1 << WGM21) | (0 << WGM20) | (0 << COM21) | (1 << COM20) | (1 << CS22) | (0 <<
CS21) | (0 << CS20); // prescaler=64

ADXL345_writeToSPI(ADXL345_POWER_CTL_REG, 1<<3); // запускаем измерения

while (1)
{
int16_t v;
ADXL345_readFromSPI(ADXL345_DATAX0_REG, 2, (void *)&v); // читаем данные по оси "Х" в
переменную v
if (v > 0) // направление "вверх"?
sound_on(); // включаем звук
else
sound_off(); // выключаем звук
_delay_ms(10);
}
}

Варианты выполнения лабораторной работы:



вар- Задание
та
1 Используется UART на скорости 19200 бит/с и акселерометр. Периодически передавать на компьютер
значение угла между осью «Х» акселерометра и вертикалью в градусах.
2 Используется светодиод и акселерометр. Менять яркость свечения светодиода LED0 пропорционально
значению угла между осью «Х» акселерометра и вертикалью.
3 Используется звукоизлучатель и акселерометр. Частота звукового сигнала, издаваемого звукоизлучателем,
должна быть пропорциональна значению угла между осью «Х» акселерометра и вертикалью.
4 Используется звукоизлучатель и акселерометр. Если в течение 1 секунды акселерометр испытал не менее
трёх сильных ударов вдоль оси «Х», издавать звуковой сигнал длительностью 1 сек (тревога).
5 Звукоизлучатель периодически включается и выключается. Длительность включённого состояния фикси-
рована и составляет 50 мс, длительность выключенного состояния должна быть пропорциональна значе-
нию угла между осью «Х» акселерометра и вертикалью и варьируется в диапазоне от 100 мс до 2 сек.
6 Используется UART на скорости 19200 бит/с и акселерометр. Подсчитывать количество полных оборотов,
которые совершил акселерометр вокруг оси «Y», причём, если вращение идёт в одну сторону, увеличивать
значение счётчика; если в другую – уменьшать значение. При измерении значения счётчика передавать его
на компьютер через UART.
7 Используется звукоизлучатель и акселерометр. При включении питания запоминать положение акселеро-
метра, и при любом отклонении его на угол более 30º от запомненного положения издавать звуковой сиг-
нал; при возвращении его в допустимое положение – выключать звукоизлучатель.
8* Разработать систему ввода пароля с помощью акселерометра, состоящего из нескольких последователь-
ных жестов (поворотах акселерометра вдоль одной из трёх осей). Предусмотреть возможность програм-
мирования нового пароля.
Контрольные вопросы
1. Принципы организации и обмена данными по шине SPI. Какое количество линий используется?
Ведущее и ведомые устройства. Как организовать взаимодействие между ведущим и несколькими ве-
домыми устройствами?
2. Достоинства и недостатки интерфейса SPI. Что нужно сделать, чтобы увеличить дальность подклю-
чения (назовите программные и аппаратные меры)?
3. Как происходит обмен информацией по SPI (порядок передачи информации, режимы работы,
управление сигналом «выбор микросхемы»)?
4. Как в микроконтроллере определяется скорость обмена данными по шине, если микроконтроллер -
ведущий? А если ведомый? Какая скорость используется в программе?
5. Принципы взаимодействия программы микроконтроллера с модулем SPI. Как взаимодействовать с
модулем с использованием/без использования прерываний?
87
6. Изучив техническое описание на микросхему акселерометра, написать код, прочитывающий за
один раз (т.е. без отпускания сигнала «выбора микросхемы») последовательно содержимое трех внут-
ренних регистров микросхемы DATAX0_REG, DATAY0_REG и DATAZ0_REG.
7. Изучив техническое описание на микросхему акселерометра, написать код, переводящий микро-
схему в режим сна.

88