Текст
                    В. С. Прокопенко
ПРОГРЙМММРОЙИЖ
микшнангролперов
I
Scan fir DjVu
BookrngofzJ
uo aiLika
WWW.MK-PRESS.COM

ББК 32.973-04 УДК 004.312 П80 Прокопенко В. С. П80 Программирование микроконтроллеров ATMEL на языке С. - К.: “МК- Пресс”, СПб.: “КОРОНА-ВЕК”, 2012. - 320с., ил. ISBN 978-5-7931-0906-2 (“КОРОНА-ВЕК”) ISBN 978-966-8806-73-5 (“МК-Пресс”) Эта книга — сборник примеров программ на языке С для микроконтроллеров произ- водства ATMEL. В качестве средств разработки и имитации использованы компилятор WinAVR, среда AVR Studio и имитатор схем Proteus ISIS. Синтаксические особенности языка С и методы работы с перечисленными выше инструментальными средствами в кни- ге подробно не рассматриваются, поскольку автор преследовал цель дать максимальное число практических примеров использования тех или иных периферийных устройств мик- роконтроллера: таймеров/счетчиков, приемопередатчика U(S)ART, аналогового компара- тора, АЦП, интерфейсов SPI и TWI, памяти Flash и EEPROM. Отдельная глава посвящена взаимодействию с ЖК-модулем. Таким образом, это издание рассчитано на тех читателей, которые, обладая базовыми познаниями языка С, желают научиться применять их в рабо- те с микроконтроллерами ATMEL. ББК 32.973-04 Прокопенко Вадим Сергеевич Программирование микроконтроллеров ATMEL на языке С Гпавный редактор: Ю. А. Шпак Подписано в печать 18.06.2012. Формат 60 х 84 1/16. Бумага газетная. Печать офсетная. Усл. печ. л. 18,6. Уч.-изд. л. 13,8. Тираж 1000 экз. Заказ № СПД Савченко Л.А., Украина, г. Киев, тел./факс: (044) 517-73-77; e-mail: info@mk-press.com. Свидетельство о внесении субъекта издательского дела в Государственный реестр издателей, производителей и распространителей издательской продукции: серия ДК №51582 от 28.11.2003г. Отпечатано в типографии ЧП Швец С.М. (свидетельство ДК №867 от 22.03.2002). 32300, Хмельницкая обл., г. Каменец-Подольский, ул. Пятницкая, 9а. Тел.: (03849) 2-72-01, 2-20-79. ISBN 978-5-7931-0906-2 (“КОРОНА-ВЕК”) © Прокопенко В.С., текст, иллюстрации, 2011 ISBN 978-966-8806-73-5 (“МК-Пресс”) © “МК-Пресс”, оформление, 2012
3 Содержание Введение..................................................8 ЧАСТЬ I. ПРИСТУПАЯ К РАБОТЕ.................................9 Глава 1. Средства разработки/имитации......................10 Среда разработки WinAVR..................................10 Среда разработки AVR Studio..............................12 Имитатор схем Proteus ISIS...............................17 ГЛАВА 2. РАБОТА С РАЗРЯДАМИ В ЯЗЫКЕ С......................20 Оператор «...............................................20 Оператор »...............................................20 Оператор ~...............................................21 Оператор а...............................................21 Оператор |...............................................21 Оператор &...............................................22 Запись лог. 1 в некоторый разряд с обнулением остальных разрядов.................................................22 Запись лог. 1 в некоторый разряд без обнуления остальных разрядов.................................................23 Запись лог. 0 в некоторый разряд без обнуления остальных РАЗРЯДОВ.................................................24 Запись лог. 0 в некоторый разряд с записью в остальные разряды лог. 1...........................................25 Проверка некоторого разряда переменной на наличие лог. 0.25 Проверка некоторого разряда переменной на наличие лог. 1.26 Ожидание появления лог. 1 в некотором разряде............27 Ожидание появления лог. 0 в некотором разряде............28 Проверка состояния определенных разрядов.................28 ЧАСТЬ II. МИКРОКОНТРОЛЛЕР AT90S2313........................30 Глава 3. Таймеры/счетчики AT90S2313........................31 Таймер/счетчик 0.........................................31 Схема..................................................32 Программа..............................................32
4 Таймер/счетчик О в режиме счета импульсов на внешнем выводе ТО..............................................34 Схема................................................34 Программа............................................34 Таймер/счетчик 1.......................................36 Схема................................................38 Программа............................................38 Таймер/счетчик 1 в режиме счета импульсов на внешнем выводе Т1..............................................40 Схема................................................41 Программа.......................................... 42 Регистр захвата ICR1 таймера/счетчика 1................43 Схема................................................43 Программа............................................44 Регистр совпадения OCR1 таймера/счетчика 1.............47 Схема................................................47 Программа...:........................................47 Использование таймера/счетчика 1 в режиме ШИМ..........50 Настройка ШИМ........................................52 Расчеты и формулы....................................53 Схема................................................55 Программа............................................55 Изменение коэффициента заполнения в режиме ШИМ.........56 Схема................................................58 Программа............................................58 ЦАП с применением ШИМ................................61 Сторожевой таймер......................................62 Схема................................................63 Программа............................................64 Глава 4. Память EEPROM AT90S2313.........................66 Запись/чтение одного байта.............................66 Запись/чтение заданного количества байт................68 Работа с EEPROM с помощью функций WinAVR...............72 Глава 5. Работа с UART в AT90S2313.......................74 Передача байта данных через UART.......................74 Схема................................................75 Программа............................................76 Передача заданного числа байт через UART...............78
5 Прием данных через UART...............................80 Схема...............................................81 Настройка виртуального терминала в Proteus..........82 Создание подключения в программе HyperTerminal......84 Программа.......................................... 86 Прием и передача байта данных через UART..............86 Прием байта, отправка строки через UART...............89 Использование потока stdout...........................91 Использование потоков stdout и stdin для передачи и приема символа...............................................93 Использование потоков stdout и stdin для передачи и приема строки................................................95 Сравнение строки, принятой через UART.................97 Управление выводами с помощью UART...................100 Реализация приглашения командной строки............103 Глава 6. Аналоговый компаратор AT90S2313...............107 Глава 7. Работа с прерываниями в AT90S2313.............109 Прерывание при переполнении счетного регистра TCNT0..109 Работа таймера/счетчика 0 в режиме счетчика импульсов на ВНЕШНЕМ ВЫВОДЕ.......................................111 Прерывание при переполнении счетного регистра TCNT1..113 Работа таймера/счетчика 1 в режиме счетчика импульсов на ВНЕШНЕМ ВЫВОДЕ.......................................114 Прерывание по сигналу на входе захвата...............116 Прерывание при совпадении регистра OCR1..............118 Внешние прерывания INTO и INT1.......................120 Прерывание при очистке регистра UDR..................123 Прерывание по окончанию приема данных................126 Прерывание по окончанию передачи данных...............128 Прерывание от аналогового компаратора................130 Использование таймера/счетчика 1 в режиме ШИМ........131 Передача данных через UART с использованием буфера...134 ЧАСТЬ III. МИКРОКОНТРОЛЛЕР ATMEGA16....................138 Глава 8. Таймеры/счетчики ATmegal6.....................139 Таймер/счетчик 0 в режиме “Normal”...................139 Схема..............................................140
6 Программа...........................................142 Таймер/счетчик 0 в режиме “СТС”.......................143 Схема...............................................144 Программа...........................................146 Таймер/счетчик О в режиме “Fast PWM”..................148 Схема...............................................148 Программа...........................................151 Таймер/счетчик О в режиме “Phase Correct PWM”.........153 Схема...............................................153 Программа...........................................156 Таймер/счетчик 1 в режиме “Normal”....................158 Схема и программа...................................159 Таймер/счетчик 1 в режиме “Normal” и с регистром сравнения ... 161 Схема и программа...................................163 Таймер/счетчик 1 в режиме “СТС”.......................165 Схема и программа...................................165 Таймер/счетчик 1 в режиме “Fast PWM”..................168 Схема и программа...................................170 Таймер/счетчик 1 в режиме “Phase Correct PWM”.........173 Схема и программа...................................175 Изменение частоты и коэффициента заполнения.........176 Таймер/счетчик 1 в режиме “Phase and Frequency Correct PWM”..................................................180 Схема и программа.................................. 181 Изменение частоты и коэффициента заполнения.........183 Сторожевой таймер.....................................185 Программа...........................................187 Глава 9. Аналоговый компаратор и АЦП ATmegal6...........189 Аналоговый компаратор.................................189 АЦП В РЕЖИМЕ НЕПРЕРЫВНОГО ПРЕОБРАЗОВАНИЯ..............191 АЦП В РЕЖИМЕ ОДИНОЧНОГО ПРЕОБРАЗОВАНИЯ................195 АЦП В РЕЖИМЕ ДИФФЕРЕНЦИАЛЬНОГО ВХОДА..................198 Глава 10. Интерфейсы передачи данных ATmegal6...........202 Интерфейс USART.......................................202 Программа.......................................... 204 Интерфейс SPI. Подключение 12-разрядного ЦАП МСР4821..208 Программа...........................................210 Интерфейс SPI. Работ а с памятью EEPROM...............214
7 Внешние функции, используемые в программе...........218 Описание микросхемы М95040..........................220 Программа...........................................225 Интерфейс SPL Работа с датчиком температуры ТС77.......236 Преобразование значения температуры из двоичной в десятичную форму..................................239 Программа...........................................241 Интерфейс SPL Работа с Flash-памятью...................243 Описание микросхемы A T25F2048......................245 Программа...........................................252 Интерфейс SPI. Работа с температурным преобразователем МАХ6675................................................264 Программа...........................................267 Интерфейс SPI. Работа с температурным преобразователем МАХ6674................................................268 Программа...........................................271 Интерфейс SPL Работа с АЦП МАХ1241.....................272 Процесс приема данных............................. 274 Программа...........................................276 Интерфейс TWI..........................................278 Схема...............................................280 Значение регистра Temperature.......................282 Программа...........................................283 Глава 11. Использование ЖК-экрана.......................286 Подключение текстового ЖК-экрана 16x2 на базе контроллера KS0066U....................................286 Программа...........................................288 Применение стандартных функций вывода при работе с ЖК-дисплеем............................................292 Вывод НА ЖК-ЭКРАН РЕЗУЛЬТАТОВ АНАЛОГО-ЦИФРОВОГО ПРЕОБРАЗОВАНИЯ.........................................295 Измерение тока, напряжения, температуры................298 Программа...........................................301 Подключение клавиатуры 3x4.............................303 Содержимое прилагаемого к книге компакт-диска...........308
8 Введение Современный мир невозможно представить без микроконтроллеров. Они применяются практически во всей электронной технике. Основная задача микроконтроллера — это прием, обработка и выдача электричес- ких сигналов. Когда и каким образом микроконтроллер принимает, об- рабатывает и выдает сигналы, определяет человек с помощью програм- мы, для создания которой можно воспользоваться одним из доступных языков программирования. Исходный текст программы компилируется и “прошивается” в микроконтроллер. Среди большого числа компаний, производящих микроконтролле- ры, огромной популярностью пользуется фирма ATMEL. Программное обеспечение для отладки и компиляции программ, предназначенных для устройств ATMEL, а также — для имитации их работы создается не только этой компанией, но и сторонними разработчиками. Основные различия между типами микроконтроллеров, выпускае- мых ATMEL, заключаются в наборе периферийных компонентов, мак- симальной частоте работы, количестве выводов, объеме памяти про- грамм и пр. Для примеров, рассматриваемых в этой книге, были выбра- ны два популярных типа устройств: AT90S2313 и ATmegal6. Для написания программ мы будем использовать язык высокого уровня С, который очень популярен во всем мире. Написанные на нем программы более компактны и удобочитаемы, чем созданные с приме- нением ассемблера. Язык С просто несравним с языками низкого уров- ня, наподобие ассемблера, поскольку он обладает всеми преимущества- ми языков высокого уровня, и одно из них — независимость от процес- сора. Научившись создавать программы для одного типа микроконтрол- леров, можно без особых усилий программировать и другие их типы. Материал этой книги излагается по принципу “от простого к слож- ному”, т.е. на первых страницах вы не увидите сложного, многостранич- ного программного кода. Осваивая и тестируя текущие примеры, автор как бы подготавливает читателя к более сложным. Цель книги — формирование с помощью небольших примеров тео- ретической и практической базы знаний для создания полноценных уст- ройств с применением микроконтроллеров ATMEL.
Чжтпь Приступая В этой части: ❖ Глава 1. Средства разработки/имитации ❖ Глава 2. Работа с разрядами в языке С
Глава 1 Среда разработки WinAVR WinAVR — это Windows-совместимый набор исполняемых про- граммных продуктов с открытым исходным кодом для RISC-микропро- цессоров Atmel серий AVR и AVR32. Каждый из этих продуктов решает определенную задачу по разработке микропрограммного обеспечения. В частности, в комплект входит компилятор GNU GCC для языков про- граммирования С и C++. Состав WinAVR: • GNU Binutils — бинарные утилиты для микроконтроллеров AVR (включая ассемблер, компоновщик и др.); • AVR GNU Compiler Collection (GCC) — компилятор на базе GCC, предназначенный для компиляции исходного кода программ для микроконтроллеров AVR в НЕХ-файлы языков С и C++ для AVR; • avr-libc 1.6.7cvs — библиотека языка С для разработки программ для микроконтроллеров Atmel AVR; • AVRDUDE 5.8cvs — консольная программа, предназначенная для “прошивки” AVR; • AVR GNU Debugger (GDB) — отладчик с интерфейсом командной строки, • Insight 6.8 — отладчик с графическим интерфейсом; • AVaRICE 2.9 — транслятор между протоколами удалённой отлад- ки GDB и AVR JTAG ICE; • SimulAVR 0.9cvs — используется вместе с GDB для симуляции ра- боты микроконтроллеров AVR; • SRecord 1.47 — коллекция мощных утилит для работы с загрузоч- ными файлами EPROM; • MFile — автоматический генератор make-файлов для AVR GCC; • Programmers Notepad 2.0.8.718 — редактор программиста и интег- рированная среда разработки (IDE); • LibUSB 0.1.12.1 — библиотека USB для AVRDUDE и AVaRICE, чтобы позволить им подключиться к Atmel JTAG ICE МКП и MKII AVRISP Atmel (драйверы для этих устройств, также включены);
Средства разработки/имитации 11 . • Cygwin DLLs — определенные библиотеки из проекта Cygwin, не- обходимые для конкретных пакетов; • множество программ и утилит Win32 GNU, включая make и bash; • Tofrodos 1.6 — конвертер текстовых файлов между форматами MS- DOS (или Windows) и Unix; • шаблон Makefile для использования в проектах; • документация для различных проектов. Загрузить последнюю версию WinAVR можно с Web-страницы http://sourceforge.net/projects/winavr. Кроме того, уста- новочный пакет WinAVR находится на прилагаемом к книге компакт- диске в папке Программы. Он представляет собой исполняемый файл . ехе, имя которого отражает дату сборки пакета. На момент написания книги установочный пакет назывался WinAVR-20100110-install. ехе, что соответствует 10 января 2010 года. Рассмотрим процесс установки WinAVR. 1. После запуска инсталляционного пакета будет предложено выбрать язык установки. Выберите русский язык (Russian) и нажмите ОК. 2. Появится окно с сообщением о необходимости закрыть все рабо- тающие приложения. Закройте такие приложения и нажмите кнопку Далее. 3. В окне с лицензионным соглашением нажмите кнопку Принимаю. 4. В следующем окне мастера установки будет предложено выбрать папку для установки пакета. Рекомендуем оставить папку, предло- женную по умолчанию. В любом случае, учтите, что среда разра- ботки не допускает в названиях папок пробелы и русские символы. 5. Далее требуется выбрать устанавливаемые компоненты (рис. 1.1). Редактор Programmers Notepad нам заменит программа AVR Studio, поэтому его можно не устанавливать. Сбросьте соответствующий флажок и нажмите кнопку Установить. 6. После завершения установки WinAVR желательно перезагрузить компьютер, чтобы актуализировать пути к компилятору и библио- текам. Теперь в системном меню Пуск ► Программы будет дос- тупно описание пакета WinAVR, а также — руководство по функ- циям библиотеки языка С avr-libc (рис. 1.2). Компилировать, “прошивать”, редактировать и отлаживать про- граммы мы будем в среде разработки с графическим интерфейсом AVR Studio, которая автоматически использует утилиты и компилятор языка С из пакета WinAVR.
12 Глава 1 Рис. 1.1. Выбор устанавливаемых компонентов WinAVR Рис. 1.2. Доступ к документации WinAVR через системное меню Пуск Среда разработки AVR Studio AVR Studio — интегрированная среда разработки (IDE) для написа- ния и отладки AVR-приложений в операционных системах Windows 9x/ME/NT/2000/XP/VISTA/7 х32/64. AVR Studio содержит в себе ме- неджер проектов, редактор исходного кода, имитатор, ассемблер и ин- терфейс для языка программирования C/C++, а также инструменты вир- туальной имитации и внутрисхемной отладки. Скачать последнюю версию пакета можно с сайта http://www. atmel. com. На момент написания данной главы была актуальна вер- сия AVR Studio 4.19, установочный пакет которой можно найти на при- лагаемом к книге компакт-диске в папке Программы.
Средства разработки/имитации 13 Рассмотрим процесс установки AVR Studio. 1, После запуска установочного файла . ехе отобразится окно с при- ветствием и сообщением о том, что среда разработки будет уста- новленная на компьютер. Нажмите кнопку Next (Далее). 2. Ознакомившись с лицензионным соглашением, выберите переклю- чатель I accept the terms of the license agreement (Я принимаю ус- ловия лицензионного соглашения) и нажмите кнопку Next. 3. На следующем этапе будет предложено выбрать папку для установ- ки AVR Studio (учтите, что путь к этой папке не должен содержать русских символов). Для перехода к очередному окну мастера уста- новки нажмите кнопку Next. 4. В ответ на предложение установить драйвер Jungo USB для различ- ных программаторов (рис. 1.3) нажмите кнопку Next. Рис. 1.3. Предложение установить драйвер Jungo USB 5- В последнем окне мастера установки нажмите кнопку Install (Уста- новить). 6- После успешной установки нажмите кнопку Finish (Завершить). Программа AVR Studio будет доступна через команду системного Меню Пуск ► Программы ► Atmel AVR Tools ► AVR Studio 4. После ee запуска откроется менеджер проектов, в котором можно открыть су- ществующий проект с помощью кнопки Open или создать новый, нажав кнопку New Project (рис. 1.4).
14 Глава 1 Рис. 1.4. Менеджер проектов AVR Studio 4 Нажмите кнопку New Project. В результате откроется окно, в кото- ром необходимо выбрать тип проекта (список Project type), а также ввести имена для создаваемого проекта (поле Project name) и его на- чального файла (поле Initial file) (рис. 1.5). Рис. 1.5. Выбор типа и имени проекта
Средства разработки/имитации 15 Пакет WinAVR используется в AVR Studio в качестве плагина. Без его установки у нас не было бы возможности программировать на языке С. Выполните в окне, показанном на рис. 1.5, следующие настройки. 1. Выберите в списке Project type элемент AVR GCC. 2. Введите имена для самого проекта и его начального файла . с. 3. Установите флажки Create initial file (Создать начальный файл) и Create Folder (Создать папку); 4. В поле Location укажите размещение рабочего каталога проекта. Этот путь не должен содержать русских символов. Рекомендуем также сделать его как можно короче. 5. Нажмите кнопку Next. Далее необходимо выбрать отладочную платформу и модель мик- роконтроллера (рис. 1.6). aVF; Simuiarof 2 |Ив1||И111 AT megs ’ 83 1в1ця1Яи iliiliiiii ATmeqal КА Рис. 1.6. Выбор отладочной платформы и модели микроконтроллера Мы будем создавать программу для ATmegal6, поэтому выберите отладчик AVR Simulator, а в списке Device (Устройство) — элемент ATmega16, и нажмите кнопку Finish. В результате появится рабочая сРеда AVR Studio, состоящая из следующих окон (рис. 1.7): • 1 — окно AVR GCC, отображающее структуру проекта (все файлы, задействованные в текущем проекте); • 2 — окно исходного кода, отображающее код текущего открытого файла . с (именно в нем вводится текст программы);
16 Глава 1 • 3 — окно I/O View, отображающее состояние всей периферии ис- пользуемого микроконтроллера (порты, таймеры/счетчики, интер- фейсы, энергонезависимая память и др.); • 4 — окно сообщений; содержит четыре вкладки: о Build — параметры командной строки, переданные компилятору; процент использованной памяти микроконтроллера; о Message — сообщения о загруженных модулях программы, а так- же — об ошибках компиляции; о Find in Files — результаты выполнения команды меню Edit ► Find in Files (Правка ► Поиск в файлах); о Breakpoints and Tracepoints — точки прерывания и трассировки; • 5 — состояние регистров периферии, когда в окне 3 выбрано какое- нибудь периферийное устройство. Это окно удобно использовать при отладке программы, поскольку, оно наглядно отображает теку- щее состояние разрядов регистров периферии. Рис. 1.7. Рабочая среда AVR Studio Примечание Частоту работы микроконтроллера, используемого в проекте, задают с помощью команды меню Project ► Configuration Options ► Frequency. Это необходимо де- лать после создания каждого проекта. Частоту для режима имитации задают с по- мощью команды меню Debug ► AVR Simulator Options (доступна только после запуска имитации по команде Build ► Build and Run)
Средства разработки/имитации Имитатор схем Proteus ISIS Пакет Proteus от компании Labcenter Electronics (официалы^й сайт http://www.labcenter.com) представляет собой систему схемо- технического моделирования. Его отличительная черта — возможность моделировать работу микроконтроллеров, микропроцессоров, ЖК-ин- дикаторов, температурных датчиков, виртуальных портов и виртуаль- ных измерительных приборов, что не всегда доступно в других подоб- ных программах. Библиотека компонентов также содержит справочные данные. Система Proteus состоит из двух компонентов: • Proteus VSM Simulator (ISIS) — программа синтеза, моделирования и отладки в режиме реального времени электронных схем; • Proteus РСВ Design (ARES) — программа разработки печатных плат. Нам потребуется только Proteus ISIS для проверки и имитации ра- ботоспособности программ и схем для микроконтроллеров. Последнюю версию Proteus можно приобрести на сайте Labcenter Electronics. Кроме того, на прилагаемом к книге компакт-диске в папке Программы\Proteus находится демонстрационная версия инсталля- ционного пакета Proteus Professional, а также файлы установки интегри- рованной среды VSM Studio IDE и драйверов Proteus VSM USB. Рассмотрим процесс установки лицензионной версии Proteus. 1. После запуска установочного файла появится окно приветствия. Нажмите в нем кнопку Next. 2. В следующем окне, ознакомившись с лицензионным соглашением, нажмите кнопку Yes (Да). 3. Укажите путь размещения лицензионного ключа: локально или на сервере (рис. 1.8), и нажмите кнопку Next. Укажите каталог для установки пакета Proteus или оставьте папку, выбранную по умолчанию, и нажмите кнопку Next. 5- В следующем окне мастера необходимо выбрать устанавливаемые компоненты пакета (рис. 1.9). Сбросьте все флажки, кроме Proteus VSM Simulator, и нажмите кнопку Next. На последнем этапе нажмите кнопку Next, а после завершения ус- тановки — Finish. Программа Proteus ISIS будет доступна в системном меню Пуск ► Программы ► Proteus 7 Professional.
18 Глава 1 Рис. 1.8. Выбор размещения лицензионного ключа ||||||||^ Г; Pruc.5U$ PC В De. sig Рис. 1.9. Выбор устанавливаемых компонентов пакета Proteus Основное окно программы состоит из следующих элементов и окой (рис. 1.10): • 1 — окно обзора, которое позволяет оперативно перемещаться схеме проекта, предоставляет краткий обзор схемы (отображаете^ только область, обозначенная синим прямоугольником; зеленый
Средства разработки/имитации 19 прямоугольник обозначает видимую область окна редактирования схемы), а также показывает выбранный компонент; • 2 — окно редактирования схемы, предназначенное для размещения и соединения элементов схемы; • 3 — окно выбора объектов, которое содержит список компонентов, необходимых для размещения в окне редактирования (в частности, оно запоминает использованные компоненты из библиотеки, чтобы в дальнейшем их можно было быстро применить); • 4 — панель выбора режимов редактора (разделена на три части: ос- новные режимы, приспособления, 20-графика); • 5 — панель управления имитацией, на которой размещено четыре кнопки: пуск, выполнение одного такта имитации, пауза, стоп. 3 •4 5 Рис. 1.10. Окно программы Proteus ISIS Примечание Для добавления в микроконтроллер программы (НЕХ-файла) необходимо щелк- нуть правой кнопкой мыши на микроконтроллере, выбрать в контекстном меню ко- манду Edit Properties ► Program File и указать интересующий файл .hex. В этом же окне можно задать частоту работы микроконтроллера. Для того чтобы один такт соответствовал необходимому временному отрезку, не- обходимо в режиме имитации выбрать команду меню System ► Set Animation Op- tions и задать параметр Single step time. Теперь один шаг имитации будет длить- ся указанное вами время.
Глава 2 Оператор << Оператор << выполняет поразрядный сдвиг числа влево. Его син- таксис: Переменная - (Переменная << Число разрядов для сдвига) ; Значение переменной сдвигается влево на указанное количество бит с заполнением освободившихся разрядов справа логическими нулями. При выходе за допустимые пределы типа данных сдвигаемые биты те- ряются. Например: Операция Число в двоичной системе счисления Десятич- ное зна- чение unsigned char n = 0x01; 0 0 0 0 0 0 0 1 1 n = (n <<-5); 0 0 1 0 0 0 0 0 32 unsigned char n = 0x06; 0 0 0 0 0 1 1 0 6 n = (n << 6) ; 1 0 0 0 0 0 0 0 128 Кроме того, можно сдвигать числовые константы, например: П = (1 << 7) ;, В данном случае единица сдвинется на семь разрядов, и мы полу- чим в переменной п двоичное значение 10000000 (шестнадцатеричное 8 0 или десятичное 12 8). Оператор » Оператор >> выполняет поразрядный сдвиг числа вправо. Его син- таксис: Переменная = (Переменная >> Число разрядов для сдвига) ;
работа с разрядами в языке С 21 Значение переменной сдвигается вправо на указанное количество бит с заполнением освободившихся разрядов слева логическими нуля- ми. При выходе за нулевой разряд переменной сдвигаемые биты теря- ются. Например: Операция Число в двоичной системе счисления Десятич- ное зна- чение unsigned char n = 0x80; 1 0 0 0 0 0 0 0 128 n = (n >> 4); 0 0 0 0 1 0 0 0 8 unsigned char n = OxAO; 1 0 1 0 0 0 0 0 160 n = (n >> 2); 0 0 1 0 1 0 0 0 40 Оператор ~ Оператор ~ инвертирует каждый разряд, т.е. 1 заменяется на 0 и на- оборот. Этот оператор — унарный, т.е. применяется только к одному операнду. Пример: n = ObOlOlOlOl; // 85 п = ~П; // п = 0Ы0101010, т.е. 170 Оператор х Оператор х применяет к операндам логическую операцию исклю- чающего “ИЛИ”, когда в каждый разряд результата записывается 1, ес- ли соответствующие разряды операндов различаются, или 0, если они совпадают. Например: А = ObOlOlOlOl; В = 0Ь00000001; с = А А В; // с = ObOlOlOlOO Оператор | Оператор | применяет к операндам логическую операцию пораз- рядного “ИЛИ”. Это значит, что разряд результата будет равен 1, если 1 СоДержится в соответствующем разряде хотя бы одного операнда. Например:
22 Глава 2 А = ObOlOlOlOl; В = ObOOOOOOOl; С = А | В;// С = ObOlOlOlOl Оператор & Оператор & применяет к операндам логическую операцию пораз- рядного “И”. Это значит, что разряд результата будет равен 1 только в том случае, когда соответствующий разряд обоих операндов содержит 1. В противном случае он будет равен 0. Например: А = ObOlOlOlOl; В = ObOOOOOOOl; С = А & В; // С = ObOOOOOOOl Запись лог. 1 в некоторый разряд с обнулением остальных разрядов Для записи логической единицы в некоторый разряд с обнулением остальных разрядов можно воспользоваться оператором: Переменная = (1 << Номер разряда) ; Запись нескольких логических единиц с обнулением остальных раз- рядов: Переменная = (1 << Номер разряда) ... | (1 << Номер разряда} ; Альтернативный вариант — просто присвоить переменной значение с логическими единицами в требуемых разрядах: n = ObOlOlOlOl; // то же самое, что п = 0x55; Рассмотрим пример выражения со схемой вычислений: n = (1 « 4) I (1 « 2) I (1 << 1) ; (1 << 4) = о 1 0 1 0 1 1 0 I 0 I I Q I 0 или (1 << 2) = 0 0 0 J I 0 0 I L 1 о 0 . или (1 << 1) = 0 0 0 0 0 0 1 0 Результат 0 0 0 1 0 1 1 0
работа с разрядами в языке С 23 Результат — ОЬОООЮНО, т.е. десятичное 22. Запись лог. 1 в некоторый разряд без обнуления остальных разрядов Для записи логической единицы в некоторый разряд без обнуления остальных разрядов можно воспользоваться оператором: Переменная |= (1 << Номер разряда); Эта запись равнозначна следующей: Переменная = Переменная | (1 << Номер разряда); - Запись нескольких логических единиц без обнуления остальных разрядов: Переменная |= (1 << Номер разряда)... | (1 << Номер разряда); Альтернативный вариант — просто применить к переменной значе- ние с логическими единицами в требуемых разрядах с помощью опера- тора “ИЛИ”, например: [ n = п | oboioioioi; у/ то же самое( что п |_ рХ55; Рассмотрим пример. Предположим, п = ОхАА. Тогда выражение П |= (1 « 4) | (1 « 2) | (1 « 1) ; будет вычислено по следующей схеме: __ п 1 I I 0 I 1 I 0 1 I 0 I 1 I 0 _ или _ (1 << 4) = 0 I I 0 I I 0 I 1 0 I 0 I 0 I I 0 .. или _J1 << 2) = 0 I I о I I 0 0 0 I I 1 I 0 0 _ или _Д1 « 1) = 0 0 0 0 0 0 1 0 .^Результат 1 0 1 1 1 1 1 0 Результат — 0Ы0111110, т.е. поверх существующего значения Переменной в заданные разряды были записаны логические единицы.
24 Глава 2 Запись лог. О в некоторый разряд без обнуления остальных разрядов Для записи логического нуля в некоторый разряд без обнуления ос- тальных разрядов можно воспользоваться оператором: Переменная 8с= ~(1 << Номер разряда); Эта запись равнозначна следующей: Переменная - Переменная & ~(1 << Номер разряда) ; Запись нескольких логических нулей без обнуления остальных раз- рядов: Переменная &= ~ (1<<Номер разряда) ... & ~ (1<<Номер разряда) ; Или: Переменная = Переменная & (~ ( (1<<Номер разряда) . . . | (1<<Номер разряда) ) ) ; Можно также применить к переменной операцию поразрядного “И” с числовым операндом, в котором в позициях обнуляемых разрядов должны быть нули. Например: П = п & ОЫ111ЮОО; Логические нули будут записаны в разряды 0-2, а остальные разря- ды переменной останутся неизменными. Рассмотрим следующим пример. Предположим, п = ОхАА. Тогда выражение п &= ~(1 << 2) & ~(1 << 3) & ~(1 << 4); будет вычислено по следующей схеме: п 1 I 0 I 1 I 0 1 1 .1 I 0 I 1 -J I __0 И & -(1 << 2) = 1 L iJ 1 1 J 1 0 I 1 I I 1 и & ~(1 « 3) = 1 LiJ I 1 Ljl I 0 I I 1 I 1 I I 1 и & ~(1 « 4) = 1 1 1 0 1 1 1 1 Результат 1 0 1 0 0 0 1 0
работа с разрядами в языке С 25 Результат— 0Ы0100010, т.е. в предыдущее значение переменной в указанные разряды были записаны логические нули. Запись лог. О в некоторый разряд с записью в остальные разряды лог. 1 Для записи логического нуля в некоторый разряд переменной с за- писью лог. 1 в остальные разряды можно воспользоваться оператором: [переменная = ~ (1«Номер_разряда} ... & ~ (1<<Номер_разряда} ; Проверка некоторого разряда переменной на наличие лог. О Возможные варианты проверки: [if (0==(Переменная & (1 << Номер_разряда) ) ) {Оператор!;} else {0ператор2;} if (-'Переменная & (1 << Номер_разряда) ) {Оператор!;} else {0ператор2;} При подключенном заголовочном файле <avr/sf r_def s . h> про- верка может выглядеть так: if (bit_is_clear (Переменная, Номер_разряда) } {Оператор!;} else {0ператор2;} Оператор! выполнится в том случае, если в указанном номере разряда переменной будет 0. В противном случае выполнится Опера - тор2 Рассмотрим следующий пример: if (~п & (1 << 4)) {Оператор!;} else {0ператор2;} Первый вариант вычислений: . п 0 1 0 1 0 1 0 1 - ~п = 1 0 1 0 1 0 1 0 _____И & _£1 << 4) = 0 0 0 1 0 0 0 0 __Результат 0 0 0 0 0 0 0 0 Результат равен нулю — значит выполнится 0ператор2.
26 Глава 2 Второй вариант вычислений: п 0 1 0 0 0 1 0 1 ~П = 1 0 1 1 1 0 1 0 И & (1 « 4) = 0 0 0 1 0 0 0 0 Результат 0 0 0 1 0 0 0 0 Результат не равен нулю — значит выполнится Оператор!. Проверка некоторого разряда переменной на наличие лог. 1 Возможные варианты проверки: if (0 != (Переменная & (1 << Номер_разряда) ) ) {Оператор!;} else {0ператор2;} if (Переменная & (1 << Номер_разряда) ) {Оператор!;} else {0ператор2;} if ( (Переменная & (1 << Номер_разряда) ) == (1 << Номер_разряда)) {Оператор!;} else {0ператор2;} При подключенном заголовочном файле <avr/sf r_def s .h> про- верка может выглядеть так: if (bit_is_set (Переменная, Номер_разряда) ) {Оператор!;} else {0ператор2;} Оператор! выполнится, если в указанном разряде переменной на- ходится единица. В противном случае выполнится 0ператор2. Рассмотрим следующий пример: if (п & (1 << 3)) {Оператор!;} else {Оператор2;} Первый вариант вычислений: п 0 1 1 I I. 0 J I 1J 1 0 1 I. _ 1 J L oj 1 1 - И & (1 « 3) = 0 0 0 0 1 0 0 0 Результат 0 0 0 0 0 0 0 0 Результат равен нулю — значит выполнится 0ператор2. Второй вариант вычислений:
работа с разрядами в языке С 27 п 0 1 0 1 1 1 0 1 и & (1 « 3) = 0 0 0 0 1 0 0 0 Результат 0 0 0 0 1 0 0 0 Результат не равен нулю — значит выполнится Оператор!. Ожидание появления лог. 1 в некотором разряде Варианты реализации такого ожидания: while (-Переменная & (1 << Номер разряда)); while (!(Переменная & (1 << Номер разряда))); При подключенном заголовочном файле <avr/sf r_def s. h> про- верку можно выполнить так: loop until bit is set(Переменная, Номер разряда); Цикл ожидания будет выполняться до тех пор, пока заданный раз- ряд переменной содержит лог. 0. Как только в нем появится лог. 1, про- изойдет выход из цикла. Рассмотрим следующий пример: while (~п & (1 « 6) ) ; Первый вариант вычислений: . п 1 0 1 0 1 0 1 0 ~п = 0 1 0 1 0 1 0 1 И & _J1 << 6) = 0 1 0 0 0 0 0 0 Результат 0 1 0 0 0 0 0 0 Результат не равен нулю — значит шестой разряд переменной равен и выход из цикла не произойдет. Второй вариант вычислений: п 1 1 1 0 1 0 1 0 - ~п = 0 0 0 1 0 1 0 1 И & << 6) = 0 1 0 0 0 0 0 0 <^Результат 0 0 0 0 0 0 0 0
28 Глава 2 Результат равен нулю — значит шестой разряд переменной равен 1, и произойдет выход из цикла. Ожидание появления лог. О в некотором разряде Варианты реализации такого ожидания: while (Переменная & (1 << Номер разряда) ) ; При подключенном заголовочном файле <avr/sfr_def s .h> про- верку можно выполнить так: loop until bit is clear (Переменная, Номер разряда) ; Цикл ожидания будет выполняться до тех пор, пока разряд в пере- менной содержит лог. 1. Как только в разряде появится лог. О, произой- дет выход из. цикла. Рассмотрим следующий пример: while (п & (1 << 3)); Первый вариант вычислений: п 1 ! I 0 L_1 J L о I 1J [ 0.. J 1 1 J 1 0 И & (1 << 3) = 0 0 0 0 1 0 0 0 Результат 0 0 0 0 1 0 0 0 Результат не равен нулю — значит третий разряд переменной со- держит 1, и выход из цикла не произойдет. Второй вариант вычислений: п 1 I 0 I 1 I I 0 I 0 I L о J L. 1 J 1... 0__ И & (1 « 3) = 0 0 0 0 1 0 0 0 Результат 0 0 0 0 0 0 0 0 Результат равен нулю — значит третий разряд переменной содер- жит 0, и произойдет выход из цикла. Проверка состояния определенных разрядов Такую проверку выполняют с помощью следующего условного оператора:
работа с разрядами в языке С 29 If ({Переменная & Требуемые_разряды) == Значение) {Оператор!;} else {Оператор?;} Оператор! выполнится только в том случае, если требуемые раз- ряды переменной совпадают с соответствующими разрядами Значе- ния. В противном случае выполнится Опера тор2. Рассмотрим пример. Предположим PIND = 0b 11110001. Тогда условие if ((FIND & ОЫ11ЮООО) == ОЫОЮОООО) {Оператор!;} else {Оператор?;} будет проверять разряды 4-7 регистра PIND на соответствие значению 1010. Вариант 1: 0Ь1'11ГО001 & ОЫ1110000 - 0Ы1110000 Результат не равен ОЫОЮОООО, а значит выполнится Опера- тор2. Вариант 2: 0Ы0100001 & 0Ы1110000 = ОЫОЮОООО Результат равен ОЫОЮОООО,а значит выполнится Опера тор!.
В этой части: ❖ Глава 3. Таймеры/счетчики AT90S2313 ❖ Глава 4. Память EEPROM AT90S2313 ❖ Глава 5. Работа с UART в AT90S2313 ❖ Глава 6. Аналоговый компаратор AT90S2313 ❖ Глава 7. Работа с прерываниями в AT90S2313
Глава 3 В этой главе мы рассмотрим методы работы с таймерами микрокон- троллера AT90S2313 и соответствующие программы на С. Таймер/счетчик О Таймер 0 настроен таким образом, что через каждые 0,000256 с (1 024 / 4 000 000 Гц) содержимое счетного регистра (счетчика) TCNT0 увеличивается на единицу. Рассмотрим программу, реализующую два условия: • если счетный регистр равен 0, то установить на всем порту D уро- вень лог. 1 (+5 В); • если счетный регистр равен 128,'то установить на всем порту D уро- вень лог. 0 (0 В). Уровень лог. 1 длится 128 отсчетов TCNT0 (от 0 до 128), уровень лог. 0 — так же 128 отсчетов TCNT0 (от 128 до 0). После того, как тай- мер выполнит 255-й отсчет, счетный регистр TCNT0 автоматически об- нуляется (рис. 3.1). +5 вольт 128 подсчетов (131072 циклов МК) О вольт 128 подсчетов (131072 циклов МК) 128 Рис. 3.1. Схема работы TCNT0 Поскольку счетному регистру для увеличения значения на единицу требуется 0,000256 с, положительный импульс (так же, как и нулевой)
32 Глава 3 будит длиться 0,000256x128 = 0,032768 с. Это наглядно показывает ос- циллограмма, изображенная на рис. 3.2 (32,76 мс = 0,032768 с). Рис. 3.2. Осциллограмма работы таймера/счетчика 0 Частота импульсов на порте D составляет 4 000 000 Гц / 1024 / 256 = 15,2587890625 Гц, т.е. напряжение на выводах меняется с +5 В на 0 В почти 16 раз в секунду. Схема Тестовая схема показана на рис. 3.3. К порту D подключены свето- диоды. Для ограничения тока последовательно со светодиодами вклю- чены резисторы. Примечание В дальнейшем в схемах эти сопротивления указываться не будут, однако их при- сутствие обязательно. Программа Программа, реализующая работу схемы, показанной на рис. 3.3, представлена в листинге 3.1.
Таймеры/счетчики AT90S2313 33 Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT90S2313\з . 01 - Таймер о. Рис. 3.3. Схема тестирования работы таймера 0 ^include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте |nt main (void) BDRD x 0x7F; // 01111111 - определяем все выводы порта D // как выходы (в порту D всего 7 выводов: // PD0-PD7) ₽ORTD = 0x7F; // 01111111 - на всех выводах порта D - // уровень лог. 1 (+5 В) TCNT0 =0; // Инициализация счетного регистра // таймера/счетчикаО, //Настройка таймера/счетчикаО
34 Глава 3 TCCR0=0x05; // Инициализация регистра управления // таймером/счетчиком 0. Значению 5 (т.е. // 0Ь101) соответствует настройка таймера // на частоту 4000000/1024 Гц. Это значит, // что каждые 0,000256 с значение счетного // регистра TCNT0 будет увеличиваться // (использован предварительный делитель) while(1) // Бесконечный цикл { if (TCNT0 == 0x00) {PORTD = 0x7F;} // Если счетный регистр = 0, то на все // выводы порта D подаем +5 В if (TCNT0 == 0x80) {PORTD = 0x00;} // Если счетный регистр = 128,) то на все // выводы порта D подаем 0 В } } Таймер/счетчик 0 в режиме счета импульсов на внешнем выводе ТО В это примере таймер/счетчик 0 настроен таким образом, что он подсчитывает импульсы на внешнем выводе ТО (PD4). После отсчета 10 импульсов на все выводы порта В подается напряжение 0 В. Когда счет- чик достигнет значения 20, на все выводы порта В подается напряжение +5 В. В качестве источника счетных импульсов используем кнопку. Схема Тестовая схема показана на рис. 3.4. Из-за дребезга контактов кноп- ки (SW0) данная схема (и программа) будет работать корректно только в имитаторе схем. Поскольку микроконтроллер работает на большой частоте, он улавливает дребезг контактов кнопки, и потому количество подсчитанных импульсов каждый раз будет разным. Программа Программа, реализующая работу схемы, показанной на рис. 3.4, представлена в листинге 3.2. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на ШТ? прилагаемом к книге компакт-диске в папке AT9OS2313\3.02 - Таймер 0 в режиме счета импульсов на выводе ТО.
раймеры/счетчики AT90S2313 35 Рис. 3.4. Схема для подсчета импульсов на выводе ТО ^include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте main (void) DDRB = Oxff; PORTB = Oxff; DDRD |= ~(1«4); SORTD |= 1«4; // Oxff = 0Ы1111111 (bin) - все выводы // порта В (РВ0-РВ7) - выходы // Oxff = Obllllllll - на все выводы порта // В подаем +5 В // Записываем 0 в 4 разряд регистра // направления передачи данных порта D, // т.е. вывод PD4 будет работать как вход // Записываем 1 в 4 разряд порта D. Это // значит что вывод PD4 будет нагружен // подтягивающим сопротивлением. Внутри // микроконтроллера к PD4 подключено
36 Глава 3 —•.-.-. ' // сопротивление, на другой конец которого // подано +5 В. //Настройка таймера/счетчика О TCCR0 = 0x07; // Инициализация регистра управления // таймером/счетчиком 0. Записав в него // Oblll(bin) = 0x7, мы определили // увеличение значения счетного регистра // TCNT0 по каждому ниспадающему фронту // (т.е. переходу напряжения с +5 В к 0 В) // на выводе T0(PD4). Поскольку внутри МК // мы подключили к выводу PD4 внутренний // подтягивающий резистор, для появления- // ниспадающего фронта необходимо подать на // вывод 0 В. while(1) // Бесконечный цикл { if (TCNT0 г == 0x14) и Если счетный регистр = 0x14 =20 PORTB = Oxff ; и то на весь порт D подаем +5 В, TCNT0 = } 0; и и начинаем счет с начала if( TCNT0 Г == 0х0А) и Если счетчик досчитал до ю, 1 PORTB = 0x00; и то на весь порт D подаем 0 В } } } Таймер/счетчик 1 Для организации работы таймера/счетчика 1 служат два регистра управления: TCCR1A и TCCR1B. В рассмотренном ниже примере тай- мер/счетчик 1 настроен на увеличение счетного регистра TCNT1 на еди- ницу в каждом 64 цикле микроконтроллера. Поскольку частота работы микроконтроллера составляет 4 МГц, а коэффициент предварительного делителя — 64, то содержимое счетного регистра TCNT1 будет увели- чиваться на единицу через каждый 64/4 000 000 = 0,000016 с. В бесконечном цикле программы проверяются два условия: • если счетный регистр TCNT1 содержит 32 768, то на все выводы порта D подается 0 В;
Таймеры/счетчики AT90S2313 37 • если счетный регистр TCNT1 содержит 0, то на все выводы порта D подается +5 В. Порт D будет находиться в состоянии лог. О (О В) 32 768 отсчетов TCNT1 или 2 097 152 циклов микроконтроллера, а в состоянии лог. 1 (+5 В) — 32 768 отсчетов, от 32 768 отсчета до переполнения счетчика (рис. 3.5). TCNTl TCNT1 TCNT1 увеличился на 1 увеличился на 32768 увеличился на 65535 TCNT1 сбросился в 0(поскольку перепопнипсяХнасчигав всего 65536 подсчетов 4194304 циклов МК Рис. 3.5. Схема работы TCNT1 Максимальное значение, которое можно записать в 16-разрядный Регистр TCNT1 — это 65 535 (0Ы111111111111111). Когда счетчик Досчитает 17-разрядного значения 0Ы0000000000000000 (65 536), он сохраняет его без старшего разряда, поскольку оно не помешается в 16-разрядный регистр. Другими словами, возникает переполнение счетчика, когда его содержимое обнуляется. Время нахождения порта D в состоянии лог. 0 совпадает с длитель- ностью состояния лог. 1 (рис. 3.6). Умножив время, за которое содер- жимое TCNT1 увеличивается на единицу, на количество раз, в котором порт D находится в том или ином состоянии, мы получим полное время в Данном состоянии: 32 768 х 0,000016 с = 0,524288 с.
38 Глава 3 Рис. 3.6. Осциллограмма работы таймера/счетчика 1 Схема Тестовая схема для данного примера показана на рис. 3.7. Программа Программа, реализующая работу схемы, показанной на рис. 3.7, представлена в листинге 3.3. е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT90S2313\3.03 - Таймер 1. #include <avr/io«h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте int main(void) { DDRD = 0x7F; // ObOlllllll - все выводы порта D // работают как выходы (в порту D всего // семь выводов: PD0-PD7) PORTD = 0x7F; // ObOlllllll - на всех выводах порта D // устанавливаем уровень лог. 1 (+5 В)
Таймеры/счетчики AT90S2313 39 > ;l // Настройка таймера/счетчика 1 // Регистр управления TCCR1 - 16-разрядный, состоит из двух // 8-разрядных регистров: TCCR1A и TCCR1B TCCR1A = 0x00; TCCR1B = 0x0 б; // Режим совпадения на выходе отключен, // поскольку мы не используем регистр // совпадения OCR1; также запрещена ШИМ // ОЬОООООНО - регистр захвата (ICR1) не // используем, поэтому разряды 6-7 = 0. // Не используем регистр совпадения (OCR1), // поэтому разряд 3=0. Выбираем источник // счетных импульсов для регистра TCNT1 // разрядами 0-2. Значение 110 в этих // разрядах означает, что источник счетных // импульсов - ниспадающий фронт (переход // сигнала с +5 на 0 В) на выводе Т1 (в МК // AT90S2313 - вывод PD5). Когда на PD5 // сигнал перейдет с +5 к 0 В, счетный // регистр TCNT1 увеличится на единицу. // Начальная инициализация счетного регистра TCNT1 // Регистр<TCNT1 - 16-разрядный, состоит из двух // восьмиразрядных регистров: TCNT1H и TCNT1L. Его // максимальное значение - 65535, достигнув которого регистр // сбрасывается в 0 и устанавливается флаг переполнения //в регистре TIFR. При записи в TCNT1 необходимо, чтобы // данные вначале записывались в TCNT1H, а потом - в TCNT1L. // При чтении TCNT1 необходимо, чтобы вначале считывался // TCNT1L, а потом - TCNT1H. TCNT1H = 0x00; // Старший байт 16-разрядного регистра // TCNT1 TCNT1L = 0x00; // Младший байт 16-разрядного регистра // TCNT1 while(1) // Бесконечный цикл { if (TCNT1L == 0x00 && TCNT1H == 0x00) // Если TCNTl=0, то { PORTD = 0x7F; } //на все выводы порта D подаем +5 В if (TCNT1L == 0x00 && TCNT1H == 0x80) // Если TCNT1=32768, { PORTD х 0x00; } //на все выводы порта D подаем 0 В } }
40 Глава 3 Рис. 3.7. Схема тестирования работы таймера 1 Таймер/счетчик 1 в режиме счета импульсов на внешнем выводе Т1 К выводу PD5 микроконтроллера подключена кнопка SW0. Счет" ный регистр TCNT1 таймера/счетчика 1 увеличивается на единицу при каждом спаде фронта напряжения на выводе PD5, т.е. при нажатии из кнопку SW0.
Таймеры/счетчики AT90S2313 41 В программе проверяются два условия: • если счетный регистр TCNT1 содержит 5, то на все выводы порта В подается О В; • если счетный регистр TCNT1 содержит 10, то на все выводы порта В подается +5 В, а счетный регистр обнуляется. Схема Тестовая схема для данного примера показана на рис. 3.8. Из-за дре- безга контактов кнопки SW0 значение счетного регистра TCNT1 будет увеличиваться не на единицу, а каждый раз — по-разному. Схема будет работать верно только в имитационной среде, где отсутствует дребезг контактов кнопки. Рис. 3.8. Схема для подсчета импульсов на выводе Т1
42 Глава 3 Программа Программа, реализующая работу схемы, показанной на рис. 3.8, представлена в листинге 3.4. е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT9OS2313\3.04 - Таймер 1 в ре- жиме счета импульсов на выводе Т1. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте int main(void) { DDRB = OxFF; //0Ы1111111 - все выводы порта В (РВ0-РВ7) // определяем как выходы PORTB = OxFF; // 0Ы1111111 - на всех выводах порта В // Устанавливаем уровень лог.^1 (+5 В) DDRD |= ~(1<<5); // Записываем 0 в разряд 5 регистра // направления передачи данных, т.е. вывод // PD5 работает как вход PORTD |= 1«5; // Запись 1 в разряд 5 регистра PORTD, т.е. // к выводу PD5 подключено внутреннее // подтягивающее сопротивление. Уровень // лог. 1 (+5 В) будет присутствовать на // нем до тех пор, пока на него не будет // подан уровень лог. 0. // Настройка таймера/счетчика 1 // Регистр управления таймером TCCR1 - 16 разрядный, состоит // из двух 8-разрядных регистров: TCCR1A и TCCR1B TCCR1A = 0x00; // Режим совпадения на выходе отключен, // поскольку мы не используем регистр // совпадения OCR1. Кроме того, запрещена // работа ШИМ. TCCR1B = 0x06; // ОЬОООООИО. Регистр захвата ICR1 не // используем, поэтому разряды 6-7 = 0. // Регистр совпадения OCR1 не используем, // поэтому разряд 3=0. Источник счетных // импульсов для регистра TCNT выбираем // с помощью разрядов 0-2. Они содержат // 110, а значит источником счетных // импульсов будет ниспадающий фронт // (переход сигнала с +5 к 0 В) на выводе
Таймеры/счетчики AT90S2313 43 // Т1 (в AT90S2313 - PD5), т.е. при // переходе сигнала на выводе PD5 с +5 В на // О В содержимое TCNT1 увеличится на 1. while(1) // Бесконечный цикл { if (TCNT1L == ОхОА && TCNT1H == 0x00) // Если TCNT1 = 10 то { PORTB = OxFF; //На все выводы порта В подаем +5 В TCNT1H = 0x00; // В счетный регистр записываем 0 TCNT1L = 0x00; } if (TCNT1L == 0x05 && TCNT1H == 0x00) // Если TCNT1 = 5, то { PORTB=0x00; } //На все выводы порта В подаем 0 В } } Регистр захвата ICR1 таймера/счетчика 1 Регистр захвата ICR1 используется для хранения значения счетного регистра TCNT1. Аппаратное копирование в этот регистр происходит при поступлении сигнала на вывод ICP (PD6 в AT90S2313). Для настройки таймера/счетчика 1 на перенос содержимого счетно- го регистра в ICR1 по ни спадающему фронту на выводе ICP (PD6) слу- жит регистр управления TCCR1 (см. листинг 3.5). Другими словами, при переходе состояния вывода ICP из +5 В в 0 В будет выполнено ап- паратное копирование содержимого регистра TCNT1 в регистр ICR1. Для предварительного делителя, связанного с регистром TCNT1, мы за- дадим коэффициент 1 024. Это значит, что значение TCNT1 будет уве- личиваться на единицу через каждые 1 024 / 4 000 000 (4 МГц) = = 0,000256 с. Схема Тестовая схема для данного примера показана на рис. 3.9. К выводу ICP (PD6 в AT90S2313) подключена кнопка. При ее нажатии на выводе напряжение падает с +5 В до 0 В (ниспадающий фронт сигнала), в Результате чего значение TCNT1 копируется в регистр захвата ICR1. К порту В подключены восемь светодиодов, из которых семь (D2- ^8) служат для индикации состояния регистра захвата ICR1, а светоди- од D1 — для индикации перехода счетного регистра TCNT1 через зна- чение 32 768.
44 Глава 3 TH Рис. 3.9. Схема для изучения работы с регистром ICR1 Программа В бесконечном цикле проверяется семь условий, на основании ко- торых определяется включение светодиодов на выводах РВ1-РВ7. Это показывает, в каком диапазоне находится значение регистра ICR1: • если ICR1 >10 000, то на выводе РВ1 — +5 В, в противном слу- чае — 0 В; • если ICR1 >15 000, то на выводе РВ2 — +5 В, в противном слу- чае — 0 В; • если ICR1 > 20 000, то на выводе РВЗ — +5 В, в противном слу- чае — 0 В;
Таймеры/счетчики AT90S2313 45 • если ICR1 > 30 000, то на выводе РВ4 — +5 В, в противном слу- чае — 0 В; • если ICR1 > 40 000, то на выводе РВ5 — +5 В, в противном слу- чае — 0 В; • если ICR1 > 50 000, то на выводе РВ6 — +5 В, в противном слу- чае — 0 В; • если ICR1 > 60 000, то на выводе РВ7 — +5 В, в противном слу- чае — 0 В. Еще одно условие проверяется для индикации диапазона счетного регистра TCNT1. Если TCNT1 > 32 768, то на выводе РВО установится лог. 1, в противном случае — лог. 0. Светодиод D1 будет включаться и отключаться через каждые 1 024 х 32 768 = 33 554 432 циклов микро- контроллера, что составляет 33 554 432 / 4 000 000 = 8,388608 сек. Программа, реализующая работу схемы, показанной на рис. 3.9, представлена в листинге 3.5. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на W»/ прилагаемом к книге компакт-диске в папке AT9OS2313\3.05 - Регистр захвата ICR1 таймера 1. йДЯйЖЙЙйй #include <avr/io.h> // Заголовочный файл подключает определения И ввода-вывода для устройства, // используемого । в проекте int main (void) { DDRB = OxFF; //Obllllllll - все выводы порта В (РВ0-РВ7) // работают как выходы PORTB = OxFF; // Obllllllll - на всех выводах порта В // устанавливаем уровень лог. 1 (+5 В) // Настройка таймера/счетчика 1 // Регистр управления TCCR1 - 16-разрядный, состоит из двух // 8-разрядных регистров: TCCR1A и TCCR1B TCCR1A = 0x00; // 0Ь00000000 - режим совпадения на выходе // отключен, поскольку мы не используем // регистр OCR1. Кроме того, запрещена // работа ШИМ. TCCR1B в 0x85; // 0Ы0000101 - используем регистр захвата // ICR1. Разряд 7 = 1, а значит включен // режим подавления входного шума на выводе // TCP (PD6 в AT90S2313) ) . Разряд 6 = 0, // т.е. по ниспадающему фронту сигнала
46 Глава 3 // (переходу напряжения с +5 к О В) на // выводе PD6 будет выполнен захват // (значение регистра TCNT1 перепишется //в регистр ICR1). Очистка счетчика по // совпадению нам не нужна, поскольку не // используем регистр совпадения OCRly // поэтому разряд 3=0. Выбираем источник // счетных импульсов для регистра TCNT //с помощью разрядов 0-2. Мы записываем //в них 101, т.е. источник счетных // импульсов - частота МК/1024. Другими // словами, значение TCNT1 будет // увеличиваться на единицу каждые // 1024/4000000(4МГц) = 0,000256 с. // Настройка вывода ICP (PD6) обязательна, даже если мы // задали параметры захвата в TCCR1 DDRD &= ~(1<<6); // Разряд 6 регистра направления передачи // данных =0, т.е. PD6 работает как вход. PORTD |= (1<<б); //К разряду порта D подключен внутренний // подтягивающий резистор (на выводе +5 В). while(1) // Бесконечный цикл { // Индикации диапазона, в котором находится значение ICR1 if (ICR1 > 0x2710) // Если ICR1 = 10000, to PORTB |= (1 « 1); // на вывод PB1 подаем +5 В else PORTB &= ~(1 « 1); /1 иначе - 0 В if (ICR1 > 0x3A98) PORTB |= else PORTB &= ~(1 « 2); (1 « 2); И 15000 if (ICR1 > 0x4E20) PORTB |= else PORTB &= ~(1 « 3); (1 « 3); И 20000 if (ICR1 > 0x7530) PORTB |= else PORTB &= ~(1 « 4); (1 « 4); И 30000 if (ICR1 > 0x9C40) PORTB |= else PORTB &= ~(1 « 5) ,• (1 « 5); // 40000 if (ICR1 > 0xC350) PORTB |= else PORTB &= ~(1 « 6); (1 « 6); // 50000 if (ICR1 > ОхЕАбО) PORTB |= else PORTB &= ~(1 « 7); (1 « 7); // 60000 if (TCNT1 >= 0x8000) // Если счетный регистр 32768, то PORTB |= (1 « 0); // на вывод РВО подаем +5 В, else PORTB &= ~(1 « 0); //в противном случае - 0 В. } }
-раймеры/счетчики AT90S2313 47 Регистр совпадения OCR1 таймера/счетчика 1 Вывод ОС1 (РВЗ) аппаратно привязан к блоку совпадения и изме- няет свое состояние при равенстве TCNT и OCR1. Определяем его как выход, и подключаем к нему светодиод DO. К выводу микроконтроллера pDO (определен как вход с нагруженным подтягивающим резистором) подключаем кнопку SW0, при нажатии на которую инициализируется и запускается таймер. Для индикации работы таймера/счетчика к выво- ду РВО, определенного как выход, подключен светодиод D1. Схема Тестовая схема для данного примера показана на рис. 3.10. — - — 1 в11Йвй111011Я1ОЯЯОвВ1И1Ж:^жв^Й^^^^^^^^^^Я1вЖ1ИИв1В^Я111®^в1ЖШвЖИввИ^®Ш| > —^хтяи ! r,P ; * - -----------gj;?5? '”м;^ 4"''' ..'"•; j ||||||||||||||[Д^ BBiWiif Рис. 3.10. Схема для изучения работы с регистром OCR1 Программа В бесконечном цикле проверяем: если напряжение на выводе PD0 Упало с +5 В до 0 В, то включаем светодиод D1 и активизируем таймер/ счетчик 1. Регистр управления таймером/счетчиком 1 (TCCR1) настроен сле- Нующим образом: * при равенстве счетного регистра TCNT1 и регистра совпадения OCR1 вывод ОС1 (РВЗ) переключается из состояния лог. 0 в со- стояние лог. 1, и наоборот;
48 Глава 3 • коэффициент предварительного делителя равен 8, т.е. счетный ре- гистр TCNT1 будет инкрементироваться каждые 8 / 4 000 000 (4 МГц) = 0,000002 с. Поскольку регистру OCR1 присвоено значение 32 768), а регистр TCNT1 проинициализирован нулем, то первое совпадение (переключе- ние состояния на выводе ОС1) произойдет через 32 768 х 0,000002 = = 0,065536 с. Последующие переключения будут происходить с интер- валом в 65 536 х 0,000002 = 0,131072 с (рис. 3.11). Рис. 3.11. Осциллограмма сигнала на выводе ОС1 Программа, реализующая работу схемы, показанной на рис. 3.10, представлена в листинге 3.6. е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT90S2313\3.06 - Регистр совпа- дения OCR1 таймера 1. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте int main(void) {
Таймеры/счетчики AT90S2313 49 // Вывод ОС1 - сравнение TCNT1 и OCR1 DDRB |= (1«3) ; //Разряд 3 регистра передачи данных DDRB // = 1, т.е. вывод РВЗ работает как выход // (к нему подключен светодиод) PORTB &= ~(1<<3); // На вывод РВЗ подаем О В // Кнопка SW0 - запуск таймера/счетчика 1 DDRD &= OxFE; // ObxxxxxxxO - вывод PD0 работает как вход PORTD |= 0x01; // Obxxxxxxxl - на вывод PD0 подключаем // подтягивающие сопротивление // Вывод РВО - индикация работы таймера/счетчика 1 DDRB |= (1«0) ; // Разряд 3 регистра передачи данных DDRB // = 1, т.е. вывод РВЗ работает как выход // (к нему подключен светодиод) PORTB &= ~ (1«0) ; // На вывод РВЗ подаем 0 В // Регистр OCR1 - 16-разрядный. В нем хранятся данные, // которые непрерывно сравниваются с текущим значением // счетного регистра TCNT1 OCR1 = 0x8000; // Когда значение TCNT1 достигнет 32768, // произойдет действие, заданное регистром // TCCR1 while(1) // Бесконечный цикл { //По нажатию кнопки SW0 настраиваем таймер/счетчик 1 //и активизируем его if (~PIND & (1 << 0)) // Если на PD0 напряжение 0 В, { PORTB |= (1 « 0) ; //Сигнализируем о том, что таймер/ // счетчик 1 запущен, установкой РВО // в состояние лог. 1. // Настройка таймера/счетчика 1 // Регистр управления TCCR1 - 16-разрядный, состоит из // двух 8-разрядных регистров: TCCR1A и TCCR1B TCCR1A = 0x40; // ОЬОЮООООО - режим совпадения на // выходе. Разряд 7=0, разряд 6=1. // Это говорит о том, что вывод ОС1 // (РВЗ) будет переключаться при // совпадении значений TCNT1 и OCR1. // Разряд 0=0, а разряд 1=0. Это // значит, что ШИМ не используется. TCCR1B = 0x02; // ОЬООООООЮ - регистр захвата ICR1 // не используем (разряд 7=0, разряд
50 Глава 3 6=0). Разряд 3=0 означает, что мы не будем обнулять регистр TCNT1, когда TCNT1=OCR1. Разряды 0-2 = 010, т.е. источник счетных импульсов - частота МК/8 (значение счетного регистра TCNT1 будет увеличиваться на единицу каждые 8/4000000 (4МГц) = 0,000002 с. } Использование таймера/счетчика 1 в режиме ШИМ Таймер/счетчик 1 переводят в режим широтно-импульсной модуля- ции (ШИМ) с помощью регистра управления TCCR1. В этом режиме вывод РВЗ (ОС1) работает следующим образом. Значение счетного ре- гистра TCNT1 изменяется циклично от 0 до “ТОР” и обратно от “ТОР” до 0, а затем цикл повторяется сначала. Если TCNT1 = OCR1, когда зна- чение TCNT1 увеличивается, то на выводе ОС1 установится лог. 0 (0 В). Если же значение TCNT1 уменьшалось, то на выводе ОС1 установится лог. 1 (+5 В). “ТОР” — это максимальное значение регистра TCNT1. В нашем примере выберем его равным 255 (восьмиразрядная ШИМ). ТОР = 2^ 1, где N — разрядность ШИМ (разряды PWM11 и PWM10 регистра TCCR1A). Регистру сравнения OCR1 присвоим 40. Счет TCNT1 выполняется до 255. В таком случае, на выводе ОС1 установится 0 В, когда TCNT1 примет значение 41, а +5 В — когда TCNT1 при обратном отсчете дой- дет до 39. Таким образом, лог. 0 (0 В) на выводе ОС1 будет длиться (255 - 41) + (255 - 39) = 430 отсчетов TCNT1. Лог. 1 (+5 В) на выводе ОС1 будет длиться от 39 до 0 и от 0 до 41, т.е. 39 + 41 = 80 отсчетов TCNT1. Такая широтно-импульсная модуляция называется неинвертирую- щей (рис. 3.12). Нам известно, сколько требуется времени на одно приращение TCNT1 (коэффициент предварительного делителя / частота МК): 1 024 / 4 000 000 = 0,000256 с.
Таймеры/счетчики AT90S2313 51 TCNT1=255 Рис. 3.12. Неинвертирующая ШИМ Положительный импульс будет длиться 80 • 0,000256 = 0,02048 с. Нулевой импульс t\ будет длиться 430 • 0,000256 = 0,11008 с. Период Тшим составляет 0,11008 + 0,02048 = 0,13056 с (рис. 3.13). рис. 3.13. Период Тшим и длительность положительного и отрицательного импульса Частота ШИМ (количество периодов за одну секунду) на выводе РВЗ (ОС1), вычисляется по формуле: /шим=А1/(2ж'-2) (Гц), Ус/с1 — частота работы таймера/счетчика 1, выбранная с помощью Разрядов CS10-CS12 регистра TCCR1B; N — разрешающая способ- ность, заданная с помощью разрядов PWM10 и PWMH регистра ^CCRlA; (2Ж|- 2) — количество отсчетов TCNT1 за один период.
52 Глава 3 /шим = (4 000 000 / 1 024) / (2;V+I - 2) = 3 906,25 /510= 7,6593 (Гц). Другими словами, за одну секунду на выводе РВЗ сигнал меняется, с 0 В на +5 В почти восемь раз. Частоту можно вычислить и по-другому. Поскольку частота F = 1 с / период, частота ШИМ будет равна 1 / 0,13056 = =7,6593 Гц (рис. 3.14). Рис. 3.14. Частота (шим Настройка ШИМ Для настройки ШИМ необходимо установить: • разрядность ШИМ (разряды PWM11 и PWM10 регистра управле- ния TCCR1 А): о 8 бит — счет в цикле от 0 до 255 и обратно; о 9 бит — счет в цикле от 0 до 511 и обратно; о 10 бит — счет в цикле от 0 до 1023 и обратно; • режим совпадения, т.е. состояние на выводе РВЗ (ОС1) в случае совпадения OCR1 с TCNT1 (устанавливается с помощью разрядов СОМ1А1 и СОМ 1 АО регистра TCCR1 А —табл. 3.1); • значение регистра совпадения OCR1, поскольку вывод ШИМ (РВЗ/ ОС1) напрямую зависит от OCR1 (когда TCNT1 = OCR1, про-
Таймеры/счетчики AT90S2313 53 изойдет действие, заданное разрядами С0М1А1 и СОМ 1 АО регист- ра TCCR1А — см. табл. 3.1). Таблица 3.1. Разряды СОМ1А1 и СОМ 1 АО регистра управления TCCR1A ^СОМ1А1 СОМ1А0 Вывод ОС1 0 0 Не подключен 0 1 Не подключен 1 0 0 В при совпадении OCR1 и TCNT1, если счет шел на увеличение, и +5 В, если счет шел на уменьшение 1 1 +5 В при совпадении OCR1 и TCNT1, если счет шел на увеличение, и 0 В, если счет шел на уменьшение • источник тактирования TCNT1 — разряды 0-2 регистра управле- ния TCCR1B. Расчеты и формулы Период ТШим — это длительность одного положительного плюс одного нулевого импульса на выводе РВЗ (ОС1). Говоря иначе, Тщим — это отрезок времени между фронтами двух соседних импульсов. т —- т /э(^+1) ШИМ “ 1 t/cl I2 - где 7)/С| — время, за которое значение TCNT1 увеличивается на едини- цу; N— разрядность ШИМ. Частота//С1 определяет значение счетного регистра TCNT1 через одну секунду и вычисляет по формуле: fvd = FMK / к, гДе FMK — частота работы микроконтроллера (4 000 000 Гц), а А: — ко- эффициент делителя (в нашем случае — 1 024). Таким образом, за одну Секунду значение TCNT1 увеличится на 4 000 000 /1 024 = 3 906,25. Если делитель не используется, то TCNT1 за одну секунду увели- чится на значение частоты микроконтроллера. Вычислим время Tt/Ci, за которое TCNT1 увеличивается на едини- цу- Поскольку частота F = 1/Т, время Tt/ci = 1 / ft/c\ = 1 с / 3 906,25 = * 0,000256 с. Другими словами, значение TCNT1 увеличивается на еди- Нииу за 0,000256 с. Значение 7vci можно рассчитать и по-другому. Длительность одного 1^кта микроконтроллера равна 1с/ Fmk, = 1 с/ 4 000 000 = 0,00000025 с. *°скольку мы используем деление на 1 024, значение TCNT1 увеличит- ся на единицу через 1 024 такта, т.е. через 1 024 • 0,00000025 с — 0,000256 с.
54 Глава з Еще одна формула расчета: Tt/d = к / FMK = 1 024 / 4 000 000 = 0,000256 с. Если предварительный делитель не используется, к = 1. Количество отсчетов TCNT1 в одном периоде составляет 2(Л/+|) - 2, где N— разрядность ШИМ. Если N= 8, то 2'ЛЧ’|) - 2 = 510. В таком слу- чае, длительность одного периода ШИМ 7шим = 0,000256 • 510 = 0,13056 с. Длительность положительного импульса 4на выводе РВЗ: 4= «ь ‘ Tt/ci, где пъ — количество отсчетов TCNT1 в положительном импульсе при неинвертирующей ШИМ (OCR1 • 2 = 40 • 2 = 80). В таком случае, дли- тельность положительного импульса 4 = 80 • = 80 • 0,000256 с = = 0,02048 с. Длительность нулевого импульса t\ на выводе РВЗ: t\- П\ • Tt/cb где п\ — количество отсчетов TCNT1 в нулевом импульсе при неинвер- тируюшей ШИМ: = 2(Л,+,) - 2 - OCR1 • 2 = 510 - 80 = 430. В таком случае, длительность нулевого импульса t\ = 430 • 0,000256 = 0,11008 с. Коэффициент заполнения — это отношение длительности поло- жительного импульса к длительности одного периода: g = t\J Тщим где /ь — длительность положительного импульса; Тщим — длительность одного периода. В таком случае, g = 0,02048 / 0,13056 = 0,156862. Поскольку Zh — 2 • OCR1, коэффициентом заполнения можно управлять с помощью регистра сравнения. Коэффициент заполнения тем выше, чем больше длительность положительного импульса. Скважность — это отношение одного периода к длительности ПО" ложительного импульса: 5 - Тщим / th = 0,13056 / 0,02048 - 6,375. Скважность выше при меньшей длительности положительного иМх пульса.
Таймеры/счетчики AT90S2313 55 Схема Тестовая схема для исследования ШИМ показана на рис. 3.15. Рис. 3.15. Схема для исследования ШИМ Программа Программа, реализующая работу схемы, показанной на рис. 3.15, представлена в листинге 3.7. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на ' прилагаемом к книге компакт-диске в папке at90S2313\3.07 - шим. ^include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте int { main (void)
56 Глава 3 DDRB |= (1 « 3); // Разряд 3 регистра передачи данных DDRB // = 1 - вывод РВЗ работает как выход // Регистр OCR1 - 16-разрядный. В нем хранятся данные, // которые непрерывно сравниваются с текущим значением // счетного регистра таймера/счетчика 1 (TCNT1) OCR1A = 0x0028; // 40 // Настройка таймера/счетчика! // Регистр управления TCCR1 - 16-разрядный, состоит из двух // 8-разрядных регистров: TCCR1A и TCCR1B // СОМ1А1 (разряд 7) =1, СОМ1АО (разряд 6) = 0 — установка // режима совпадения для ШИМ. На выводе ОС1 (РВЗ) // устанавливается лог. 0 при совпадении TCNT1 и OCR1A, когда // значение TCNT1 увеличивается. Если же значение TCNT1 // уменьшается, то при совпадении TCNT1 и OCR1 на выводе ОС1 // устанавливается лог. 1 (неинвертирующая ШИМ). // PWM10.(разряд 0) =0, PWM11 (разряд 1) = 1 — разрядность // ШИМ (8). Это значит, что счет происходит в цикле от 0 до // 255 и обратно. TCCR1A = 0x81; //0Ы0000001 // Разряд 7=0, разряд 6=0— регистр ICR1 не используем. // Разряд 3=0- регистр TCNT1 при совпадении TCNT1 и OCR1 // не обнуляется (в режиме ШИМ этот разряд не используют). // Разряды 0-2 - выбор источника счетных импульсов для // регистра TCNT1. Значение 0Ы01 - частота МК / 1024 = // 3906,25 отсчетов TCNT1 за 1 с, т.е. значение счетного // регистра TCNT1 будет увеличиваться на 1 каждые 1024 / // 4000000 (4 МГц) = 0,000256 с. TCCRlB=0x05; //0600000101 while(1) // Бесконечный цикл { } } Изменение коэффициента заполнения в режиме ШИМ Данный пример аналогичен предыдущему за тем исключением, что к микроконтроллеру подключены две кнопки: SW0 — для увеличения, и SW1 — для уменьшения коэффициента заполнения g сигнала ШИМ* Кроме того, мы используем разрядность ШИМ не 8, а 10.
Таймеры/счетчики AT90S2313 57 Количество отсчетов регистра TCNT1 для положительного и нуле- вого импульсов при значении регистра совпадения OCR.1A = 511 пока- заны на рис. 3.16. рис. 3.16. Количество отсчетов TCNT1 для положительного и нулевого импульса Длительность положительного импульса: 4 = >4' Д/ci = (OCR1 • 2) • (к / 4м к) = (511 • 2) • (1 / 4 000 000) = 0,0002555 с.
58 Глава 3 Такова исходная длительность импульсов после подачи питания, когда еще не были нажаты кнопки увеличения или уменьшения значе- ния регистра OCR1 (рис. 3.17). Рис. 3.17. Исходные длительности импульсов Длительность нулевого импульса: А = «г = ((2*+1 - 2) - (OCR1 • 2)) • (1 / Fmk) = ((2,0+1 - 2) - (511 • 2)) • (1 / 4 000 000) = 1 024 • 0,00000025 = 0,000256 с. Период Тшим = 0,000256 + 0,0002555 = 0,0005115 с. ЧастотаУшим = 1 / 0,0005115 == 1955 Гц, т.е. за одну секунду на вы воде РВЗ сигнал меняется с 0 В на +5 В 1955 раз. Схема Тестовая схема для данного примера показана на рис. 3.18. При на- жатии на кнопку SW0 увеличивается длительность положительного им- пульса и уменьшается длительность нулевого импульса, а при нажатий на кнопку SW1, наоборот, уменьшается длительность положительного импульса и увеличивается длительность нулевого. Программа Программа, реализующая работу схемы, показанной на рис. 3.18> представлена в листинге 3.8.
Таймеры/счетчики AT90S2313 59 Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом К книге КОМПакт-ДИСКе В папке AT90S2313\3.08 - Изменение ко- эффициента заполнения в режиме ШИМ. Рис. 3.18. Схема ШИМ с кнопками для изменения коэффициента заполнения мг 3.8Л1рограмма jw.c - - ' #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте ^include <util/delay.h> // Для доступа к функциям задержки |nt main (void) DDRB| = (1«3) ; // Разряду 3 регистра передачи данных DDRB // присвоили 1, т.е. РВЗ - выход // Регистр OCR1A - 16-разрядный, хранит данные, которые // непрерывно сравниваются с текущим значением // таймера/счетчика 1 (TCNT1) OCR1A = 0x0IFF; // 511 // Настройка таймера/счетчика 1 // Регистр управления таймером/счетчиком TCCR1 состоит из // двух 8-разрядных регистров: TCCR1A и TCCR1B
60 Глава 3 // СОМ1А1 (разряд 7) =1, СОМ1АО (разряд 6) = 0 - установка // режима совпадения ддя ШИМ. Теперь вывод ОС1 (РВЗ) // устанавливается в лог. 0 при совпадении TCNT1 и OCR1A, // когда TCNT1 увеличивается. Если же TCNT1 уменьшается, то // при совпадении TCNT1 и OCR1 на выводе ОС1 (РВЗ) // устанавливается лог. 1 (неинвертирующая ШИМ). // PWM10 (разряд 0) = 1, PWM11 (разряд 1) =1. Определяем // 10-разрядную ШИМ (1024 отсчетов от О.до 1023), т.е. // значение счетного регистра TCNT1 будет в цикле изменяться // от 0 до 1023 и обратно от 1023 до 0. TCCR1A = 0x83; //0Ы0000011 // ICNC1 (разряд 7) =0, ICES1 (разряд 6) = 0 - регистр // захвата ICR1 не используем. // Разряд 3=0- регистр TCNT1 при совпадении TCNT1 и OCR1 // не обнуляется (в режиме ШИМ этот разряд не используют). // Разряды 0-2 - выбор источника счетных импульсов для // регистра TCNT1. Значениею 0Ь101 соответствует частота // микроконтроллера 4 МГц, т.е. каждые 1 / 4000000 = // 0,00000025 с значение счетного регистра TCNT1 будет // увеличиваться на единицу. TCCR1B = 0x01; //ОЬООООООО! // Определяем PD0 и PD1 как входы. К этим выводам подключены // кнопки для изменения коэффициента заполнения ШИМ DDRD &= ~(1 « 0) & ~(1 « 1); // PD0 и PD1 - входы PORTD |= (1 « 0) | (1 << 1) ; // Подключаем к PD0 и PD1 // внутренние нагрузочные // сопротивления while(1) // Бесконечный цикл { // Изменение коэффициента заполнения при нажатии на кнопки if (-PIND & (1 << 0)) // Если на выводе PD0 0 В { OCR1 = OCR1 + 1; // Увеличиваем значение OCR1 на 1 _delay_loop_2(65535); // Задержка перед следующим } // нажатием кнопки if (-PIND & (1 << 1)) // Если на выводе PD1 +5 В { OCR1 = OCR1 - 1; // Уменьшаем значение OCR1 на 1 _delay_loop_2(65535); // Задержка перед следующим } // нажатием кнопки } }
-раймеры/счетчики AT90S2313 61 ** I—i—— МВНМ»— ЦАП с применением ШИМ На рис. 3.19 показана схема для опроса напряжения на выходе по- сле фильтра низкой частоты (ФНЧ). Оно увеличивается или уменьшает- ся в зависимости от нажатия кнопок SW0 и SW1. Рис. 3.19. Схема с ФНЧ
62 Глава 3 Среднее напряжение Um, которое получается, если напряжение на выводе РВЗ пропустить через ФНЧ: Um - (?ь' Uh+ А • U\) I 7шим, где /ь — длительность положительного сигнала; — напряжение по- ложительного сигнала; t\ — длительность нулевого сигнала; U\ — на- пряжение нулевого сигнала; Тщим — период ШИМ-сигнала. Um = (0,0002555 • 5 + 0,000256 • 0) / 0,0005115 » 2, 5 В. Еще один вариант включения — с полевым транзистором с индуци- рованным каналом (рис. 3.20). Рис. 3.20. Включение с полевым транзистором с индуцированным каналом Сторожевой таймер Сторожевой таймер (watchdog timer) оснащен собственным генери тором, работающим на частоте 1 МГц (если питание микроконтроллера в точности равно 5 В), и предварительным делителем. Он служит для защиты от сбоев (“зависания”). С помощью сторожевого таймера можно прервать зацикливаний программы путем сброса микроконтроллера. По прошествии времени, заданного регистром сторожевого таймера (тайм-аута), микроконтроЛ' лер перезапускается, т.е. программа начинает выполняться с самого нЗ' чала. Для того чтобы этого не произошло при нормальном выполнений
Таймеры/счетчики AT90S2313 63 программы, сторожевой таймер до наступления тайм-аута необходимо обнулять командой wdr. Схема Схема для исследования работы сторожевого таймера представлена на рис. 3.21. ШОШ Рис. 3.21. Схема для исследования работы сторожевого таймера Для того чтобы увидеть перезагрузку микроконтроллера, в начале программы на выводе РВЗ устанавливается О В (это произойдет и в том случае, если сторожевой таймер сбросит микроконтроллер). В бесконечном цикле выполняется проверка выводов PD0-PD2 на наличие низкого уровня сигнала (к этим выводам подключены кнопки). Когда на PD0 появится О В, сторожевой таймер отключится. Когда на появится О В, на светодиод, подключенный к РВЗ, будет подано ^Пряжение +5 В. Сторожевой таймер включится с тайм-аутом 2 048 мс и Начнет отсчитывать время до перезагрузки микроконтроллера. Если после включения сторожевого таймера, до того как он отсчи- тает 2 048 мс, не нажать кнопку, подключенную к выводу PD2 (сброс ТаЙмера), то программа начнет выполняться с начала (произойдет пере-
64 Глава 3 загрузка микроконтроллера). Это можно будет увидеть по погасшему светодиоду D1. На рис. 3.22 видно, что после нажатия кнопки PD1 (светодиод D1 включился) прошло время тайм-аута (светодиод D1 включен), а затем произошел сброс микроконтроллера (светодиод D1 отключился). Рис. 3.22. Осциллограмма напряжения на светодиоде D1 Для того чтобы микроконтроллер не перезагружался при нормаль- ной работе программы, каждый раз до наступления сброса необходимо обнулять сторожевой таймер командой wdr. Программа Программа, реализующая работу схемы, показанной на рис. 3.21, представлена в листинге 3.9. ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке АТ9032313\3.09 - Сторожевой таймер. ------------------------------ • •• v ... . Л.. . , #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include cutil/delay.h> // Для доступа к функциям задержки
Таймеры/счетчики AT90S2313 65 int main (void) DDRB | = (1 « 3) ? PORTB &= ~(1 « 3); DDRD &= 0xF8; // Разряд 3 регистра передачи данных // DDRB равен 1, т.е. вывод РВЗ // работает как выход // Подаем на РВЗ напряжение 0 В // ОЬхххххООО - выводы PD0-PD2 // работают как входы // Obxxxxxlll - к выводам PD0-PD2 // подключены подтягивающие // сопротивления PORTD |= 0x07; whiled) // Бесконечный цикл // Отключаем сторожевой таймер, если нажата кнопка SW0 if (~PIND & (1 << 0)) // Если на выводе PD0 0 В, то { // Одновременно записываем единицу в разряды 4 (WDTOE) и // 3 (WDE).. Это даст разрешение на отключение сторожевого // таймера. Перед его отключением необходимо прежде // записать единицу в разряды 3 и 4, а затем в течение // четырех тактов отключить сторожевой таймер. WDTCR |= (1 « 4) | (1 « 3); WDTCR &= ~(1 << 3); // Отключаем сторожевой таймер, // записав 0 в разряд WDE } // Включаем сторожевой таймер, если нажата кнопка SW1 if (-PIND & (1 << 1)) // Если на выводе PD1 0 В, то { // Разрешаем работу сторожевого таймера. Затем он // перезапустит микроконтроллер по прошествии времени // тайм-аута, если мы не отключим таймер. Этого также не // произойдет, если каждый раз перед завершением времени // обнулять сторожевой таймер командой wdr. WDTCR |= 0x0 F; PORTB « (1 « 3); // На вывод РВЗ подали +5 В } // Имитируем нормальную работу программы, сбрасывая // сторожевой таймер командой wdr (т.е. тайм-аут = 0 с) if («PIND & (1 << 2)) // Если нажата кнопка SW2 { asm("wdr"); // Сброс сторожевой таймера. На // выводе РВЗ будет 0 В
Глава 4 В этой главе мы рассмотрим методы работы с памятью EEPROM микроконтроллера AT90S2313 и соответствующие программы на С. Запись/чтение одного байта Рассмотренная ниже программа записывает в энергонезависимую память EEPROM по адресу 0x00 байт данных 0x03 (рис. 4.1). Рис. 4.1. Содержимое памяти EEPROM после записи в нее байта данных Затем она считывает этот же байт по адресу 0x00 и выводит его в порт В, к выводам которого для индикации подключены светодиоды (рис. 4.2). В нашем случае включатся два светодиода: D0 и D1. Рис. 4.2. Считанный байт данных выведен в порт В
Память EEPROM AT90S2313 67 Другими словами, мы получили индикацию двоичного числа ObOOOOOOll, что соответствует десятичному 3. В программе, представленной в листинге 4.1, для записи и чтения энергозависимой памяти используются две собственных функции: « eeprom_wr_byte (Адрес_памяти, Байт_данных) — запи- сывает байт данных в EEPROM по указанному адресу; * eeprom_rd_byte (Адрес_памяти) — считывает байт данных по указанному адресу и возвращает его в качестве результата. t Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT9OS2313\4.01 - запись-чтение одного байта. г' 4Л. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте // Функция записи байта в EEPROM. Принимает два параметра: // адрес, по которому будет произведена запись, и байт данных void еергom_wr_byte(unsigned short address, unsigned char data) { while (EECR & (1 << 1)); // Ожидаем окончания предыдущей // записи (0 в разряде EEWE) EEAR = address; // Адрес, по которому необходимо // произвести запись EEDR = data; // Данные, которые необходимо // записать EECR |= (1 << 2); //В разряд EEMWE записываем 1 // (разрешаем запись в EEPROM) EECR |= (1 << 1); //В разряд EEWE записываем 1 (байт // данных записан в EEPROM) '' Функция чтения байта данных из EEPROM. Принимает адрес, по ' которому необходимо произвести чтение байта данных. ' Возвращает прочитанный байт. ^signed char eeprom_rd_byte(unsigned short address) while (EECR & (1 « ®Ear - address; ®ECR |= (1 « 0); 1) ) ; // Ожидаем окончания предыдущей // записи (0 в разряде EEWE) // Адрес ячейки EEPROM, из которой // необходимо произвести чтение // Записываем 1 в разряд EERE. Тем // самым байт, который необходимо
68 Глава 4 return EEDR; } // считать, записан в регистр данных // EEDR // Функция вернет байт, который // находится в регистре данных EEDR int main(void) { DDRB = Oxff; // 0Ы1111111 - PORTB = 0x00; // ObOOOOOOOO - // подаем 0 В eeprom_wr_byte(0x00, 0x03); // 11 PORTB = eeprom_rd_byte(0x00); // // } все выводы порта В - выходы на все выводы порта В Запись байта 0x03 в нулевую ячейку ЕЕPROM Чтение байта из EEPROM и его вывод в порт В Запись/чтение заданного количества байт Программа, представленная в листинге 4.2, записывает 15 байт дан- ных их массива stroka_iz_ram_v_eeprom в энергонезависимую память, а затем считывает 15 байт обратно из EEPROM в переменную stroka_iz_eeprom_v_ram. Предложение “работа с eeprom” в мас- сивах размещается, начиная с нулевого элемента по 14 включительно (в данном примере мы обходимся без символа завершения строки “\0”). Содержимое массивов находятся в ОЗУ. Размерность энергонезави- симой памяти микроконтроллера AT90S2313 — 128 байт (память EEPROM выдерживает не менее 100 000 циклов записи/стирания). В программе для записи и чтения энергозависимой памяти исполь- зуются две собственных функции: • EEPROM_write_string(Appec_EEPROM, Количество_байт, Адрес_массива_данных) — запись оД' ного и нескольких байт данных в EEPROM; • EEPROM_read_string(Адрес_ЕЕРРОМ, Количество_байт, Адрес_массива) —чтение байта данный по заданному адресу памяти (функция возвращает его в качеств^ результата). ©Исходные файлы этого примера для WinAVR и AVR Studio находятся на прилагае- мом К книге компакт-диске в папке AT90S2313\4.02 - Запись-чтение задан- ного числа байт.
Память EEPROM AT90S2313 69 м~~на|1мм -.? . ^include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте // Функция записи в EEPROM void EEPROM_write_string(int address, int lenght, char *data) while (lenght) // Пока не все байты записаны в EEPROM, // выполняем цикл. Когда lenght станет // равным 0, цикл прервется, т.е. будут // записаны 14 элементов массива от 0 до 14 { while (EECR & (1 « D); // Ожидаем окончания EEAR = address; // // // предыдущей записи (ждем 0 // в разряде EEWE Адрес, по которому необходимо произвести запись в EEPROM address « address EEDR = *data; data = data + 1; EECR |= (1 « 2); EECR |= (1 « 1); + 1; H // // // // // // // // // // // // // // Переходим к следующей ячейке памяти EEPROM Копируем в регистр данных EEDR байт данных, который необходимо записать в память EEPROM. Запись *data означает "взять данные по адресу data" Поскольку data содержит адрес памяти, для того чтобы перейти к следующему элементу массива, мы увеличиваем адрес на единицу Записываем 1 в разряд EEMWE (разрешаем запись в EEPROM) Записываем 1 в разряд EEWE (байт переписан из EEDR в EEPROM) }} lenght = lenght - 1; // // Уменьшаем количество байт, оставшихся для записи // Функция чтения из EEPROM |°id EEPROM_read_string(int address, int lenght, char *data) while (EECR & (1 << 1)) ; // Ожидаем окончания предыдущей // записи (ждем 0 в разряде EEWE) while (lenght--) // Пока не считали требуемое число // байт (переменная не равна нулю) // из EEPROM, цикл выполняется.
70 Глава 4 { EEAR = address++; EECR |= (1 « 0) ; ♦data = EEDR; data++; } } // Адрес ячейки EEPROM, из которой // необходимо произвести чтение, II и переходим к следующей ячейке // Запись 1 в разряд EERE (байт, // который необходимо считать, // записан в регистр данных EEDR) // Записываем байт данных из EEPROM // в ячейку массива stroka__v_eeprom // Переходим к следующему элементу // массива int main(void) { // Массив stroka_iz_ram_v_eeprom размещается в ОЗУ static char stroka_iz_ram_v_eeprom[15] ="работа с eeprom"; // Записываем по адресу 0x00 EEPROM строку длиной 15 байт, // адрес которой - это адрес нулевого элемента массива // stroka_iz_ram_v_eeprom EEPROM_writestring(0x00, 15, &stroka_iz_ram_v_eeprom[0]); // Массив stroka_iz_eeprom будет содержать строку, считанную //из EEPROM static char stroka_iz_eeprom_v_ram[15]; // Чтение из ячейки EEPROM с адресом 0x00 строки длиной 15 // байт в ОЗУ по адресу массива stroka_iz_eeprom_v_ram EEPROM_read_string(0x00, .15, &stroka_iz_eeprom_v_ram[0]); } Содержимое памяти EEPROM отображается в окне Memory. Перед записью все ее ячейки содержат значение OxFF (рис. 4.3): ЮхПП 0( №В1Я11НИ1И jF *8 '.FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 3C FF 50 FF 64 FF FF FF FF FF FF FF FF FF ||jj^ .. FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ЧТ FF FF FF FF яяяяяяяяяяяяяяяяя яяяяяяяяяяяяяяяяя яяяяяяяяяяяяяяяяя ЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯАЯЯ яяяяяяяяяяяяяяяяя яяяяяяяяяяяяяяяяя Рис. 4.3. Память EEPROM перед записью данных
Память EEPROM AT90S2313 71 Содержание энергонезависимой памяти после записи в нее массива stroka_iz_ram_v_eeprom показано на рис. 4.4. Рис. 4.4. Память EEPROM после записи в нее данных Содержимое массива stroka_iz_eeprom_y_ram после чтения данных из памяти EEPROM можно увидеть в окне Watch (рис. 4.5). — stroka_iz_raBi_v_eeprcm. [. . . ] chax[15] 0x0060 [SFAMj [0] 0xF0 •₽• .char 0x0060 [SRAM] : Uf./. .7 . . OxEO *a’ , char . 0x0061 [SRAM] I [2] OxEl 16* ' char ' 0x0062 [SRAM] ' ’ : [3] OxEE !o‘ chat 0x0063 [SPAM] [41 0xF2 char 0x0964 [SRAM] [5] OxEO ‘a; char ijvjQcS fSFAIl] [61 0x20 ' ’ chat О-;:/-?..;: [SRAHj : £73 , ,, OxFl 'C . . char . 0x0067 [SPAM] [8] 0x20 1 char: 0x0065 [SRAM] [9] 0>:65 ’a' char 0x0069 [SEAM] Г11] 0x70 Ч.‘ char ОхОибБ [SRAM] [ [12] 0x72 'r' char 0x0060 [SRAM] [131 0x6F chat: OzOCbD [SRAM] j ’ "'[14]''"'" " '" ' 0x6D ‘m' '' char ОхООбЕ [SPAM] = S stroka_i2_eeproa_v_raa [...] char[15] 0x0070 [SPAM] / [0] OxFO ’p‘ char 0x0070 [SPAM] : i . [1] OxEO 'a* char 0x0071 [SRAM] "' [2] '' ' " " "" ' '' OxEl 'б* char ' 0x0072 [SRAM] " ! 1 [3] OxEE '0‘ 'char 0x0073 [SRAM] : J '' '[4] '0xF2 ‘t' char 0x0074 [SRAM] " . ' 1 [5] OxEO 'a1 char 0x0075 [SRAM] _ ; ""[6]"" ' 0x20 ' ' ' ’ char " 0x0076 [SRAM] " i ] [7] OxFl *C* Char 0x0077 [SRAM] j [8] 0x20 * ‘ ' char 0x0078 [SRAM]' ’ '; : [9] 0x65 'e* char 0x0079 [SRAM] ' Г " ' [10] " ' ' ' ' 0x65 !e‘ ' "char 0x007A [SRAM] ' ' : r [11] 0x70 ‘p’ char 0x007B [SRAM] . . i [ [12] 0x72 1 r' char 0x0070 [SRAM] . [ Г ' ""' £133 " 0x6F 'o' ' ' char ' " ' 0x0071) [SRAM] • | [14] 0x6D ‘в* char 0x007E [SRAM! Рис. 4.5. Данные, считанные из EEPROM в ОЗУ
72 Глава 4 Работа с EEPROM с помощью функций WinAVR В программе, представленной в листинге 4.3, используются две библиотечные функции WinAVR, предназначенные специально для ра- боты с памятью EEPROM (они определены в подключаемом файле <avr/eeprom.h>): • eeprom_read_byte (Адрес_ЕЕРРОМ) — чтение байта по ука- занному адресу памяти EEPROM; • eeprom_write_byte (Адрес_ЕЕРРОМ, Байт_данных) —за- писывает байт данных по указанному адресу памяти EEPROM. В программе переменной ee_write присваиваем значение 0x76, которое с помощью функции eeprom_write_byte записываем в пер- вую ячейку энергонезависимой памяти. Затем это значение считывается с помощью функции eeprom_read_byte и присваивается перемен- ной ee_read. Для того чтоб убедится, что запись и чтение выполнены успешно, значение переменной ee_read выводим в порт D (рис. 4.6). Рис. 4.6. Индикация значения 0x76
Память EEPROM AT90S2313 73 Значение 0x76 — это ObOlllOllO, т.е. на выводах порта D полу- чены следующие напряжения: . PD6 —+5В; . PD5—+5В; . PD4 —+5В; . PD3—0В; . PD2—+5В; . PD1—+5В; . PD0 —0 В. ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом К книге компакт-диске В папке AT90S2313\4.03 - Запись-чтение одного байта. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <avr/eeprom.h> // Подключение функций для работы // памятью EEPROM // Используем две функции из заголовочного файла eeprom.h: // eeprom_write_byte (uint8__t *_p, uint8_t _value) // uint8_t eeprom_read_byte (const uint8_t *_p) int main(void) { uint8_t eewrite; // Переменная ee_write типа unsigned char ee_write = 0x76; // Записали в переменную ObOlllOllO 11 Записываем в первую ячейку памяти EEPROM значение // переменной ee_write eeprom_write_byte((uint8_t*)1, ee_write); uint8__t ee read; // Переменная ee__read типа unsigned char // Считываем из первой ячейки EEPROM байт данных и записываем // его в переменную ee_read eeread = eeprom_read_byte((uint8_t*)1); DDRD a Oxff; // Obllllllll - все выводы порта D - выходы PORTD = 0x00; // 0b00000000 - на всех выводах порта DOB PORTD = ee_read; // Выводим в порт D значение переменной // ee_read для его индикации светодиодами
Глава 5 В этой главе мы рассмотрим методы работы с универсальным асин- хронным приемопередатчиком (UART) микроконтроллера AT90S2313 и соответствующие программы на С. Передача байта данных через UART Частота обмена данными для асинхронного приемопередатчика микроконтроллер AT90S2313 устанавливается программно, т.е. внеш- ний тактирующий сигнал не используется. Для того чтоб задать режим передачи необходимо: 1. В третий разряд (TXEN) регистра управления UART (UCR) запи- сать единицу, чтобы мы разрешить работу передатчика: UCR |= (1 « 3); 2. Установить скорость передачи данных, записав в регистр UBRR значение, которое можно найти в таблице 15 технического описания микроконтроллера AT90S2313 (AT90s2313.pdf) или вычислить по формуле: UBRR = ((Fck / BAUD) / 16) - 1, где Fck — частота работы микроконтроллера; UBRR — содержимое регистра скорости передачи UART; BAUD — скорость, которая нам необходима для передачи. Таким образом, UBRR = ((3686400/9600) / 16) - 1 = 23. После того как UART настроен в режиме передатчика, можно пере- давать данные, записывая в регистр ввода-вывода UDR байт данных для отправки. Перед следующей отправкой необходимо проверять, пустой ли регистр UDR (т.е. был ли отправлен предыдущий байт). Это можно сделать, проверив пятый разряд (UDRE) регистра состояния USR: if (USR & (1 « 5)) { UDR = 'А1; }
работа с UART в AT90S2313 75 Схема Для соединения UART-интерфейса микроконтроллера с СОМ-пор- том компьютера необходим преобразователь уровней (рис. 5.1). Рис. 5.1. Схема соединения UART микроконтроллера с С ОМ-портом
76 Глава 5 У компьютерного интерфейса RS232 уровню лог. 1 соответствует напряжение -3..-12 В, а уровню лог. О — +3..+12 В. В случае с UART микроконтроллера лог. 1 — это +5 В, а лог. О — О В. При подключении использована микросхема преобразователя уровней МАХ232. Схема соединения интерфейса UART микроконтроллера с вирту- альным терминалом показана на рис. 5.2. iiiiiii ц в in ill! fill flitl Рис. 5.2. Схема соединения интерфейса UART с виртуальным терминалом Программа Программа, реализующая работу схемы, показанной на рис. 5.1, представлена в листинге 5.1. ДИк Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT90S2313\5.01 - Передача байта данных по UART.
работа с UART в AT90S2313 77 ^include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте у/ функция инициализации UART void UART_INIT() UCR |= (1 « 3); // В разряд TXEN записываем 1, чтобы // разрешить работу передатчика UBRR « 0x17; //23 - скорость передачи 9600 бод при // частоте МК 3.6864 МГц } int main (void) { UART_INIT() ; // Инициализируем UART while(1) // Бесконечный цикл { if (USR & (1 << 5)) // Если разряд UDRE регистра USR // равен 1, то регистр данных UDR // пуст, т.е. готов принять новые // данные для отправки. { UDR = 'A'; // Передаем через UART букву "А" } } Программа в бесконечном цикле передает через UART символ “А”. Результат приема данных виртуальным терминалом показан на рис. 5.3. Рис. 5.3. Прием данных виртуальным терминалом
78 Глава 5 Передача заданного числа байт через UART В этом примере программа передает через универсальный асин- хронный приемопередатчик UART предложение “привет, это твой мик- роконтроллер =)”. Схема подключений и результат вывода данных через виртуальный терминал показаны на рис. 5.4. Рис. 5.4. Схема подключений и результат вывода через виртуальный терминал Программа, реализующая работу схемы, показанной на рис. 5.4, представлена в листинге 5.2. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT90S2313\5.02 - Передача за- данного числа байт по UART. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <string.h> // Для работы со строками // функция инициализации UART void UART_INIT() { UCR |= (1 « 3); //В разряд TXEN записываем 1, чтобы // разрешить работу передатчика UBRR = 0x17; // 23 - скорость передачи 9600 бод при
работа с UART в AT90S2313 79 // частоте МК 3.6864 МГц } int main (void) UART_INIT(); // Инициализируем UART // Объявляем массив stroka[], который будет содержать // предложение для отправки. Он состоит из 37 элементов // с индексацией от от 0 до 36 включительно. Элемент // с индексом 37 - символ конца строки с кодом 0x00. static char stroka[37] = "привет, это твой микроконтроллер =)\г\п”; static int dlina_stroki =0; // Переменная для хранения // количества байт // в передаваемом массиве dlina_stroki = strlen(stroka); // С помощью функции strlen // определяем количество байт // в массиве stroka int i=0; // Индекс байта массива для отправки while(1) // Бесконечный цикл { // Если регистр ввода-вывода UDR пуст, и передаваемый // в данный момент байт - последний, то... if (USR & (1 << 5) && i < dlina_stroki) { UDR = stroka [i] ; // Передаем через UART один байт i = i + 1; // Переходим к следующему байту Массиву символов stroka [] присвоена строка “привет, это твой Микроконтроллер =)\г\п”. Теперь к любому байту в этом массиве можно обратится так stroka [Индекс]. Компилятор при инициализации сим- вольных массивов автоматически добавляет в их конец символ “\0” joxoo), из-за чего размерность массива всегда больше на один элемент. Нашем случае 37-й элемент массива содержит 0x00 (рис. 5.5). , Мы определили количество байт в массиве stroka [] с помощью ^Ункции strlen (), объявленной в библиотечном заголовочном файле spring .h (определяет длину строки без учета символа “\0”). Мы мог- и бы воспользоваться явной инициализацией переменной для длины Р°Ки, без использования функции strlen: dlina_stroki=37. В та-
80 Глава 5 ком случае при инициализации массива st ока [] необходимо было бы указать размерность 38, т.е. static char stroka [38]. ®ая X О И stroka ;[...] ^char[37] : 0x0060 [SRM] й [0] [1] [2 J [3] [4] tsi.... [S] t"J rai [91 СЮ] [111 [12] [ 131 ' US] [161 [19] ...[20].... ’ " [21] i22j [23] [24] [25] ’ [26] ... [28] [29] [30] [31] [32] P3] ” [34] " [35] - [36] J JOxEF""‘n’..'char......""Ox'0060'"[SRAM].... i0xF0"'»p'..ichar......Г 0x0061 "[SPAM]..... JOxES' "‘и‘..ichar......' 0x0062"[SRAM].... 0xE2 ’s’' ' char " " 0x0063 [SRAM] 0xE5 'e' char 0x0064 [SRAM] 0xF2 ' *r' char. 0x0065 [SRAM] 0x0066 [SRAM] ’ 0x0067 [SRAM] 0x0068 [SRAM].....; 0x0069 [SRAM] ОхООбА [SRAM] : 0X006B [SRAMJ.... ОхООбС [SRAH1 0X006D [SRAM] ОхООбЕ [SRAH] 0X006F [SPAM] 0x0070 [SRAM] 0x0071 [SRAM] 0x0072 [SRAM] " """ЬхЬб73""рЙм1'’"""Л"'”7 0x0074 [SRAM] 0x0075 [SRAM] • ...-.г.-... -V.- 0x0076 [SRAM] 0x0077 [SRAM]" " 0x0078""[SRAM]...'"1 ' 0x0079' [SRAM] .: ' OxOOTA" [SRAM]...• ' 0x007В"[SRAM].....• "OxOOVC" [SRAM]..... " 0x007d'"[SRMI] .. " 0X007E"[SRAM].....' OxOO7F "[SRAM].... Г 0x0080 "[SRAM].... """.0x0081 "[SRAM] ''.: ' 'T 0x0082' [SRAM]...: 0x0083'" [SRAM].. 0х00Й'""['ЖИ]' """\ 0x20 ' ' .char OxH) 's' "char ...............: . ' :0x20 ' ' ' ichar 0xF2 !f' chat OxEE 'o' chat S!m! char JOxEC !и’ char 0xE8 ’ и! ‘ char ;0xEA 'k' ^char OxFO'p' char OxEE 'o' " char ......." П, OxEA 'rJ char' OxEE 'o' -char JOxED ’h‘ ^char i0xF2 ^char JOxFO *p* :char ЮхЕЕ * о' ichar ;0xEB 'л' ichar ;0хЕВ"‘л' ' ichar" i0xE5 * e ' ;char ;0xF0 *p' -char i0x20 * ’ :char ;0x3D ' = ' ichar "; 0x29 ;char' ;0x6b 1 ichar OxOA ' char Рис. 5.5. Содержимое массива stroka [] Прием данных через UART В данном примере программа принимает данные через UART и вь1' водит их в порт В. В функции UART_INIT () приемопередатчик ин*1' циализируется на прием данных записью 1 четвертый разряд (RXEN) регистра UCR:
работа с UART в AT90S2313 81 UCR |= (1 « 4); Устанавливаем скорость передачи данных: r"uBRR = 0x17; Порт В микроконтроллера определяем на вывод: DDRB = OxFF; В бесконечном цикле проверяем седьмой разряд регистра состояния USR. Если в нем установлена 1, то это говорит о завершении приема данных завершен, т.е. байт принят и переписан в регистр UDR. Как только это произойдет, байт данных выводится в порт В: PORTB = UDR Схема Схема соединения микроконтроллера со светодиодами и виртуаль- ным терминалом показана на рис. 5.6. Рис. 5.6. Схема соединения микроконтроллера со светодиодами и виртуальным терминалом
82 Глава 5 В примере, показанном на рис. 5.6, с микроконтроллером было ус- тановлено соединение через программу HyperTerminal и передан символ “1” (единица). На выводах порта В появились следующие уровни: . РВ7 —0; . РВ6 —0; . РВ5—1; . РВ4—1; • РВЗ —0; • РВ2 —0; . РВ1—0; . PB0—1. Мы получили двоичное 00110001 (шестнадцатеричное 0x31). Со- поставив этот код с таблицей символов ASCII (рис. 5.6), убеждаемся, что был принят символ “1” 00 10 20 30 40 50 60 70 80 90 А0 ВО С0 D0 Е0 F0 ► 0 р Р * p A Р а 1_ и р = э « ♦ 1 А Q а «I Б С 6 ь X -г с + 8 ♦ 2 В В Ь p В т в т ТГ т > * !! 41 3 С S с s Г У г 1 И U. У < ♦ 41 $ 4 D Т а t д ф д н - t ф f X 5 Е 11 е u E X е + F X J - & 6 F и Г V Ж ц ж il 1= ГТ ц * S. • 7 G Ы ST w 3 ч 3 71 IF ч □ t С 8 Н X h X И ш и я L 4= ш о о > 9 I У i У Й щ й j щ В + : J Z J z К ъ к II г ъ - <f + К [ k c Л ы л Тг ы $ < L X 1 М ь м и 1г ь и Г 4+ - = М ] M > Н э н JU — 4 э 2 л А ~ > N Л n О ю о d 1Г я ю • _ ▼ / ? О — о A П я п 7 ± 1 я Рис. 5.7. Таблица символов ASCII Настройка виртуального терминала в Proteus Для отображения вводимых символов в терминале внутри пр0' граммной среды Proteus необходимо щелкнуть правой кнопкой мыШ1* и установить в контекстном меню флажок Echo Typed Characters (ВЫ' давать эхом вводимые символы) (рис. 5.8). Затем следует задать шриф^ Для этого в меню, показанном на рис. 5.8, необходимо выбрать команДУ
работа с UART в AT90S2313 83 get Font и выбрать необходимые настройки в диалоговом окне Шрифт (рис. 5.9). ! Billli Ш Рис. 5.8. Отображение вводимых символов в терминале Proteus Рис. 5.9. Выбор шрифта
84 Глава 5 Создание подключения в программе HyperTerminal Для запуска программой HyperTerminal можно воспользоваться ко- мандой системного меню Пуск ► Все программы ► Стандартные к Связь ► HyperTerminal. 1. В диалоговом окне Описание подключения (рис. 5.10) вводим лю- бое название и нажимаем кнопку ОК. Рис. 5.10. Диалоговое окно Описание подключения 2. В диалоговом окне Подключение выбираем COM-порт, к которому подключен микроконтроллер (рис. 5.11) и нажимаем кнопку ОК. Рис. 5.11. Диалоговое окно Подключение
работа с UART в AT90S2313 85 3, В диалоговом окне Свойства выбираем: Скорость бит/с — 9600; Биты данных — 8; Четность — Нет; Стоповые биты — 1; Управ- ление потоком — Нет, — и нажимаем ОК. Примечание Управление потоком бывает аппаратным и программным, однако мы его не ис- пользуем. В случае программного управления типа Xon/Xoff для выдачи устройст- ву сигнала прекратить/возобновить передачу данных используются стандартные контрольные символы ASCII (коды 17 и 19). При аппаратном управлении потоком для приостановки и последующего возобновления передачи используют специаль- ные линии интерфейса RS-232 (сигналы CTS и RTS) Также, в HyperTerminal можно активизировать отображение вве- денных с клавиатуры символов. Выберите команду меню Файл ► Свойства, в диалоговом окне Свойства перейти на вкладку Парамет- ры, нажмите кнопку Параметры ASCII и установите флажок Отобра- жать введенные символы на экране (рис. 5.12). Рис. 5.12. Активизация отображения введенных с клавиатуры символов Для установки параметров шрифта выберите команду меню Вид ► Щрифт, в диалоговом окне Шрифт найдите в перечне шрифтов элемент
86 Глава 5 Courier New и выберите в раскрывающемся списке Набор символов элемент Кириллический. Программа Программа, реализующая работу схемы, показанной на рис. 5.6 представлена в листинге 5.3. ЙЙк Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT90S2313\5. оз - Прием данных через UART. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте // Функция инициализации UART void UART INITO { UCR |= (1 « 4); UBRR = 0x17; } // Разрешаем работу приемника // Устанавливаем скорость передачи 9600 бод // при частоте МК 3.6864 МГц int main(void) { UART INITO ; // Инициализация UART DDRB = OxFF; // Выводы порта В работают как выходы PORTB = 0x00; while (1) f // На все выводы порта В подаем 0 В // Бесконечный цикл t if (USR & (1 « { 7)) // Если появилась единица в седьмом // разряде (RXC) регистра USR, то..- PORTB = UDR; } } } // Выводим в порт В байт данных, принятый // через UART Прием и передача байта данных через UART В данном примере микроконтроллер в бесконечном цикле приниМЗ' ет через UART байт данных, а затем отправляет его обратно (рис. 5.13)’ Схема соединений — такая же, как на рис. 5.6.
работа с UART в AT90S2313 87 Рис. 5.13. Прием и передача данных через UART Например, мы передаем в микроконтроллер через UART символ “а”. В таком случае от микроконтроллера через UART мы получим этот же символ. Программа, реализующая прием и передачи данных через UART, представлена в листинге 5.4. ЛЬ Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке АТ9032313\5.04 - прием и пе- редача данных через UART. - -: мнг 5А Программа UART_r_and_s_b, с j #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте // Функция инициализации UART void UART INITO { UCR |= (1 « 4) | (1 « 3) ; // Разрешаем работу приемника // (1 в разряде 4 регистра // UCR) и передатчика (1 в // разряде 3 регистра UCR) UBRR = 0x17; // Устанавливаем скорость передачи 9600 бод // при частоте МК 3.6864 МГц // Функция отправки байта данных через UART {°id otpravit_bait(char odin_bait) loop_until_bit_is_set(USR, 5); // Ожидаем, пока будет // установлен в 1 разряд 5 // регистра USR. Это говорит о
88 Глава 5 // том, что регистр данных // UART пуст, и мы можем // записать в него байт данных // для передачи UDR = odin_bait; // Записываем в порт ввода-вывода // значение переменной odin_bait } int main(void) { UART_INIT(); // Инициализация UART DDRB = OxFF; // Выводы порта В работают как выходы PORTB = 0x00; // На все выводы порта В подаем 0 В while (1) // Бесконечный цикл { if (USR & (1 << 7) ) // Если появилась единица в седьмом // разряде (RXC) регистра USR, то... { char odin_bait = UDR; // Переменной odin_bait // присваиваем содержимое // регистра ввода-вывода UDR PORTB = odin bait; // Выводим в порт В содержимое // переменной odin_bait otpravit_bait(odin_bait); // Отправляем через UART // значение odin_bait } } } Окно терминала, демонстрирующее работу этой программы, пока- зано на рис. 5.14. Рис. 5.14. Каждый принятый символ дублируется переданным символом
работа с UART в AT90S2313 89 Прием байта, отправка строки через UART В этом примере программа в бесконечном цикле принимает один байт через UART, после чего программа отправляет через UART строку “МК гес1уеА-Принятый_байт”. Так, если передать символ “х”, то от микроконтроллера придет строка “МК recived-x”. Схема соединений — такая же, как на рис. 5.6. Программа, реализующая прием байта и передачу строки через UART, представлена в листинге 5.5. ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT90S2313\5.05 - прием байта и отправка строки через UART. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте // Функция инициализации UART void UART_INIT() { UCR |= (1 « 4) | (1 « 3); // Разрешаем работу приемника // (1 в разряде 4 регистра // UCR) и передатчика (1 в // разряде 3 регистра UCR) UBRR = 0x17; // Устанавливаем скорость передачи 9600 бод // при частоте МК 3.6864 МГц // Функция отправки строки через UART // Указатель *stroka содержит адрес первого элемента массива void otpravit_stroku(char *stroka) ’while (*stroka != 1 \0') // Цикл выполняется до тех пор, пока //не будеь достигнут символ "\0”. *stroka - это // содержимое байта данных по адресу stroka. Можно было // бы использовать просто цикл while (*stroka), т.е. { // "пока есть что-либо в памяти loop_until_bit_is_set(USR, 5); // Ожидаем появления 1 // в разряде 5 регистра USR TOR = *stroka; // Выводим через UART *stroka - //. значение, находящееся по адресу stroka stroka = stroka +1; // Переходим к следующей ячейке // памяти, поскольку stroka - это указатель на память
90 Глава 5 int main(void) { UART_INIT(); // Инициализация UART DDRB = OxFF; // Выводы порта В работают как выходы PORTB = 0x00; // На все выводы порта В подаем 0 В while (1) // Бесконечный цикл { if (USR & (1 << 7)) // Если появилась единица в седьмом // разряде (RXC) регистра USR, то... { char odin_bait = UDR; // Переменной odin_bait // присваиваем содержимое регистра ввода-вывода UDR PORTB = odin_bait; // Выводим в порт В содержимое // переменной odin_bait // В массив str записываем предложение "\r\nMK recived- // \r\n", где символ "\г" означает возврат каретки, // а символ "\п" - перевод строки. В 16-ю ячейку // компилятор автоматически запишет символ "\0" static char str[17]="\r\nMK recived- \г\пи; str[13] = odin_bait; // Записываем значение // переменной odin_bait в 14-ю ячейку массива otpravit_stroku(&str[0]) ; // Передаем функции адрес // первого элемента массива str[]. Можно было бы // его представить также, как &str или &str+0. } } } Окно терминала, демонстрирующее работу этой программы, пока- зано на рис. 5.15. Рис. 5.15. Прием байта и отправка строки
работа с UART в AT90S2313 91 Использование потока stdout Вместо стандартного выходного потока stdout мы воспользуемся собственным потоком mystdout (листинг 5.6). Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на |||^ прилагаемом К книге компакт-диске В папке AT90S2313\5 . Об - Использование потока stdout. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте tfinclude <stdio.h> // Стандартные средства ввода-вывода // Функция инициализации UART void UART-INITO { UCR |= (1 << 3) ; // Разрешаем работу передатчика UBRR = 0x17; // Устанавливаем скорость передачи 9600 бод // при частоте МК 3.6864 МГц } // Функция, которая будет вызываться при обращении потока на // вывод символа static int uart putchar(char simvol, FILE *stream) { loop_until_bit_is_set(USR, UDRE); // Ожидаем появления 1 // в разряде UDRE UDR = simvol; // Пересылаем через UART байт данных, // содержащийся в переменной simvol return 0; }; // Создаем поток mystdout, назначив функцию uart_putchar для вывода. Функцию для ввода не назначаем, указав NULL. Используем Только вывод, установив флаг __FDEV_SETUP_WRITE static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE); ^nt main(void) UART_INIT(); // Инициализация UART stdout = &mystdout; // Переназначаем поток stdout // стандартного вывода на наш mystdout printf("Hello, world!\r\n"); // Передаем в поток строку // "Hello, world!". Поскольку выходной поток назначен как
92 Глава 5 // функция uart_putchar, "будет вызываться данная функция, // принимая символы для отправки. return 0; } Результат работы программы показан на рис. 5.16. €1 III Рис. 5.16. Вывод строки через поток
работа с UART в AT90S2313 93 «*• 1 .-«hi.-.-..... Использование потоков stdout и stdin для передачи и приема символа В это примере программа (листинг 5.7) с помощью потока stdin принимает символ через UART, отображает его ASCII-код на светодио- дах и передает этот же символ через UART с помощью потока stdout. Выход из цикла приема/передачи символов происходит после приема кода клавиши <Enter>. Схема соединений — такая же как на рис. 5.6. ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом К книге компакт-диске В папке AT90S2313\5.07 - Использование потоков stdout и stdin для передачи и приема символа. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода -для устройства, // используемого в проекте #include <stdio.h> // Стандартные средства ввода-вывода // Функция инициализации UART void UART INITO { UCR |= (1 « 3) | (1 << 4); // Разрешаем работу II передатчика и приемника UBRR = 0x17; 11 Устанавливаем скорость передачи 9600 бод // при частоте МК 3.6864 МГц // Функция, которая будет вызываться при обращении потока на // вывод символа static int uart_putchar(char simvol, FILE *stream) loop_until_bit_is_set(USR, UDRE); // Ожидаем появления 1 // в разряде UDRE UDR = simvol; // Пересылаем через UART байт данных, // содержащийся в переменной simvol ^return 0; // Функция, которая будет вызываться при обращении потока на ' ввод символа ^atic int uart_getchar(FILE *stream) while (I(USR & (1 << 7))); // Ожидаем, пока прием символа // завершится установкой в единицу разряда 7 (RXC) // в регистре USR (прием завершен)
94 Глава 5 return UDR; //функция вернет принятый символ // Создаем поток mystdout__mystdin, назначив функцию // uart_putchar для вывода, а функцию uart_getchar - для ввода. // Используем ввод и вывод, установив флаг _FDEV_SETUP_RW static FILE mystdoutjmystdin = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW); int main(void) { DDRB = OxFF; // Выводы порта В - выходы UART_INIT(); // Инициализация UART // Передаем адрес памяти нашего потока в стандартный поток // stdout (на вывод) и в стандартный поток stdin (на ввод) stdin = stdout = &mystdout_mystdin; // Передаем через UART предложение printf("\г\пПринимаем символ через UART и отправляем его через UART\r\n 11) ; char с; while ( (с = getcharO) != OxOD) // Функция getchar() считывает // символ из стандартного потока stdin (из UART). Цикл // выполняется до тех пор, пока не будет считан символ // с ASCII-кодом 13, т.е. нажата клавиша <Enter> { putchar(c); // Передаем принятый символ через UART PORTB = с; // Выводим символ через порт В (для // отображения его ASCII-кода на светодиодах) ь printf(и\г\пЕХ1Т\г\пи); // Выводим слово "EXIT" return 0; } Результат работы программы показан на рис. 5.17. |Принимаем символ через UART и отправл ем его через UART | iliilie Рис. 5.17. Результат использования потоков для ввода*вывода символа
работа с UART в AT90S2313 95 Использование потоков stdout и stdin для передачи и приема строки Принимаемые символы записываются в массив buf []. Прием про- должается до тех пор, пока не будет превышен размер массива или не поступит код клавиши <Enter>. Код поступающих символов отобража- ется на светодиодах. По окончании приема принятая строка выводится через UART. В программе (листинг 5.8) используется собственная функция приема строки input_line, а для вывода строки — внешняя функция print f (). Схема соединений — такая же как на рис. 5.6. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT90S2313\5.08 - Использование потоков stdout и stdin для передачи и приема строки. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <stdio.h> // Стандартные средства ввода-вывода // Функция инициализации UART void UART INITO { UCR |= (1 « 3) | (1 << 4); // Разрешаем работу I/ передатчика и приемника UBRR = 0x17; // Устанавливаем скорость передачи 9600 бод // при частоте МК 3.6864 МГц // Функция, которая будет вызываться при обращении потока на // вывод символа static int uart_putchar(char simvol, FILE *stream) loop_until_bit_is_set(USR, UDRE); // Ожидаем появления 1 // в разряде UDRE UBR = simvol; // Пересылаем через UART байт данных, // содержащийся в переменной simvol return 0; // Функция, которая ' ввод символа будет вызываться при обращении потока на static int uart_getchar(FILE *stream)
96 Глава 5 while (!(USR & (1 « 7))); // Ожидаем, пока прием символа // завершится установкой в единицу разряда 7 (RXC) // в регистре USR (прием завершен) return UDR; //функция вернет принятый символ } // Создаем поток mystdout_mystdin, назначив функцию // uart_putchar для вывода, а функцию uart_getchar - для ввода. // Используем ввод и вывод, установив флаг _FDEV_SETUP_RW static FILE mystdout_mystdin = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW) ; // Функция, которая принимает строку через uart и записывает ее // в массив buf[], проинициализированный в основной программе // Параметры: // - адрес памяти нулевого элемента массива в указателе *bufl, // т.е. далее в функции, обращаясь к указателю bufl[индекс], мр // напрямую обращаемся к массиву buf []; // - length_linel - длина строки static int input_line(char *bufl, int length_linel) { int i = 0; // Для подсчета отправленных символов char с; // Переменная для принятого через UART символа length_linel = lengthlinel - 1; // Поскольку индексация // элементов массива начинается с 0, мы уменьшаем длину // на 1 для корректного обращения к последнему элементу // Цикл выполняется до тех пор, пока принятый через uart // символ не равен коду клавиши <Enter> или не будет // достигнут конец массива while ((с = getcharO) ’= OxOD && i < length_linel ) { PORTB = c; // На светодиоды порта В выводим ASCII-код // принятого символа bufl[i] = c; // Записываем принятый символ в ячейку i // массива buf [] i = i + } 1; // Увеличиваем индекс массива на 1, чтобы // перейти к следующей его ячейке bufl [i] = '\0'; // Последний элемент массива - символ конна // строки. Например, если массив состоит из 8 элементов // (buf[8]), то символ конца строки запишется в ячейку // с индексом 7, т.е. buf[7]. return i; } int main(void)
работа с UART в AT90S2313 97 DDRB = OxFF; // Выводы порта В - выходы UART_INIT(); // Инициализация UART // Передаем адрес памяти нашего потока в стандартный поток // stdout (на вывод) и в стандартный поток stdin (на ввод) stdin = stdout = &mystdout_mystdin; char buf [8]; // Массив, в который будет записана строка unsigned int length_line =0; // Эта переменная будет // хранить длину строки, которая поступит через UART // Вызываем функцию input_line и передаем в нее адрес первого // элемента массива и его размер length_line = input_line(&buf [0], sizeof(buf)); printf("\r\n строка-%8н, buf); // Передаем принятую строку // через UART. Обозначение %s // замещает выводимую строку return 0; } Результат работы программы показан на рис. 5.18. Рис. 5.18. Результат использования потоков для ввода-вывода строки Сравнение строки, принятой через UART Программа, представленная в листинге 5.9, с помощью внешней Фикции f gets принимает через UART строку и сравнивает ее со стро- хранящейся в памяти программ. Если принятая строка меньше ^Роки, хранящейся в Flash-памяти, то в UART передастся символ “<”. ^сли она больше, то передастся символ “>”, если же строки равны, то Передается символ “=”. Для завершения ввода в виртуальном терминале, Необходимо нажать комбинацию клавиш <Ctrl+Enter>.
98-Глава 5 Схема соединения микроконтроллера с терминалом представлеца на рис. 5.19. Рис. 5.19. Схема соединения микроконтроллера с терминалом Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT90S2313\5.09 - Сравнение строки, принятой через UART. #include <avr/io.h> // Заголовочный файл .подключает определения // ввода-вывода для устройства, // используемого в проекте #include <stdio.h> // Стандартные средства ввода-вывода #include <avr/pgmspace .h> // Утилиты для работы с Flash-память*0 // Функция инициализации UART void UART_INIT() { UCR |= (1 << 3) | (1 << 4); // Разрешаем работу // передатчика и приемника UBRR = 0x17; // Устанавливаем скорость передачи 9600
работа с UART в AT90S2313 99 // при частоте МК 3.6864 МГц } // Функция, которая будет вызываться при обращении потока на // вывод символа static int uart_putchar(char simvol, FILE *stream) loop_until_bit_is_set(USR, UDRE); // Ожидаем появления 1 //в разряде UDRE UDR = simvol; // Пересылаем через UART байт данных, // содержащийся в переменной simvol return 0; }; // Функция, которая будет вызываться при обращении потока на // ввод символа static int uart_getchar(FILE *stream) { while (!(USR & (1 « 7))); // Ожидаем, пока прием символа // завершится установкой.в единицу разряда 7 (RXC) // в регистре USR (прием завершен) return UDR; //функция вернет принятый символ } // Создаем поток mystdout_mystdin, назначив функцию // uart_putchar для вывода, а функцию uart_getchar - для ввода. // Используем ввод и вывод, установив флаг _FDEV_SETUP_RW static FILE mystdout_mystdin = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW); int main(void) UART_INIT(); // Инициализация UART // Передаем адрес памяти нашего потока в стандартный поток // stdout (на вывод) и в стандартный поток stdin (на ввод) stdin = stdout = &mystdout_mystdin; int kol_sim =10; // Размерность массива символов, // принимаемых через UART char from_uart[kol_sim]; // Массив из 10 элементов ^hile (1) // Бесконечный цикл // Принимаем строку из UART в массив from_uart, пока не // приняты все 10 символов или пока не будет встречен // символ новой строки ’\п1 (он также записывается в массив // from_uart после введенных символов).
100 Глава 5 fgets(&fromuart[0], kol_sim+l, &mystdout_mystdin); // Сравниваем строку, содержащуюся в переменной from__uart, // со строкой, размещенной во Flash-памяти. Если они равны, // то в переменную sravnili будет записан 0 int sravnili = strcmpJP (fromjuart, PSTR(н012345б789и)); if (sravnili < 0) putchar(1<1); // Если sravnilicO, to // from_uart<"0123456789" else if (sravnili > 0) putchar(1>1);// Если sravnili>0, to // from_uart>"0123456789" if (sravnili == 0) putchar(1=1); // Если sravnili==0, to // from_uart="0123456789" }; return 0; } Результат работы программы показан на рис. 5.20. Рис. 5.20. Результат сравнения строки Управление выводами с помощью UART Программа (листинг 5.10) выводит через UART цифровое меню» предлагая сделать выбор от 0 до 9. В зависимости от выбора напряже- ние +5 В подается на тот или иной вывод микроконтроллера. Текст для меню извлекается из массива str [], хранимого с целью экономии ОЗУ микроконтроллера в Flash-памяти программ. Схема соединений — така* же как на рис. 5.6. ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT90S2313\5.10 - Управление вы- водами с помощью UART.
работа с UART в AT90S2313 101 ^include <avr/io.h> 11 Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте ^include <stdio.h> // Стандартные*средства ввода-вывода ^include <avr/pgmspace.h> // Утилиты для работы с Flash-памятью // Функция инициализации UART void UART INITO UCR |= (1 « 3) | (1 << 4); // Разрешаем работу -// передатчика и приемника UBRR = 0x17; // Устанавливаем скорость передачи 9600 бод // при частоте МК 3.6864 МГц } // Функция, которая будет вызываться при обращении потока на // вывод символа static int uart_putchar(char simvol, FILE *stream) { loop_until_bit_is_set(USR, UDRE); // Ожидаем появления 1 // в разряде UDRE UDR = simvol; // Пересылаем через UART байт данных, // содержащийся в переменной simvol return 0; b // Функция, которая будет вызываться при обращении потока на // ввод символа static int uart_getchar(FILE *stream) while (!(USR & (1 « 7))); // Ожидаем, пока прием символа // завершится установкой в единицу разряда 7 (RXC) // в регистре USR (прием завершен) return UDR; //функция вернет принятый символ // Создаем поток mystdout_mystdin, назначив функцию // uart_putchar для вывода, а функцию uart_getchar - для ввода. // Используем ввод и вывод, установив флаг _FDEV_SETUP_RW static FILE mystdout_mystdin = 5'DEV_SETUP_STREAM(uart_putchar, uart_getchar, FDEV SETUP RW) ; // Следующая строка хранится не в ОЗУ, а в памяти программ '/ (flash). Массив можно было еще объявить как prog_char str[]= static char str[] PROGMEM = "\r\n Меню: \r\n l)PB0 1/0 \r\n 2)₽B1 1/0 \r\n 3)PB2 1/0 \r\n 4)PB3 1/0 \r\n 5)PB4 1/0 \r\n
102 Глава 5 6)РВ5 1/0 \r\n 7)РВ6 I/O \r\n 8)PB7 I/O \r\n 0)EXIT\r\n введите номер от 1 до 8, чтобы подать +5V на вывод МК, или 0 для выхода\г \п"; int main(void) { UART_INIT(); // Инициализация UART // Передаем адрес памяти нашего потока в стандартный поток // stdout (на вывод) и в стандартный поток stdin (на ввод) stdin = stdout = &mystdout_mystdin; DDRB=0xff; // Выводы порта В - выходы while (1) // Бесконечный цикл { // Выводим меню (массив str) через UART // Цикл for выполняется до тех пор, пока из массива str, // размещенного в Flash-памяти, не будет считан символ ”\0" for (int i = 0; pgm_read_byte(&str[i]); i=i+l) { putchar(pgm_read_byte(&str[i])); // Считываем байт по // адресу str[i] и выводим его через UART b char choose; choose = getcharO; // Принимаем через UART выбранный // в меню символ if (choose == ’О1) break; // Если выбран 0, выходим из // бесконечного цикла //В зависимости от выбранного пункта меню подаем +5 В на // соответствущий вывод порта В switch (choose) { case 'I's PORTB = ObOOOOOOOl; break; case 121: PORTB = ObOOOOOOlO; break; case 131: PORTB = ObOOOOOlOO; break; case '4*: PORTB = ObOOOOlOOO; break; case ’Б1: PORTB = ObOOOlOOOO; break; case 161: PORTB = ObOOlOOOOO; break; case '7’: PORTB = ObOlOOOOOO; break; case 181: PORTB = OblOOOOOOO; break; default: putchar(1?1); } b return 0; } Результат работы программы показан на рис. 5.21.
работа с UART в AT90S2313 103 Рис. 5.21. Управление выводами микроконтроллера с помощью UART Реализация приглашения командной строки Несколько модифицируем рассмотренную выше программу, чтобы реализовать в нашем меню приглашение командной строки. Для этого мы воспользуемся символьными массивами, размещенными в памяти программ (Flash) (коррективы выделены в листинге 5.11 полужирным шрифтом). Лк Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на 40 прилагаемом к книге компакт-диске в папке AT90S2313\5.11 - Реализация ко- мандной строки. ^include <avr/io.h> // Заголовочный файл подключает определения } II ввода-вывода для устройства, // используемого в проекте ^include <stdio.h> // Стандартные средства ввода-вывода ^include <avr/pgmspace.h> // Утилиты для работы с Flash-памятью // Функция инициализации UART v°id UART_INIT() |= (1 << 3) ^RR = 0x17; | (1 << 4) ; // Разрешаем работу // передатчика и приемника // Устанавливаем скорость передачи 9600 бод // при частоте МК 3.6864 МГц И // Функция, которая вЬ1вОд символа будет вызываться при обращении потока на
104 Глава 5 static int uart_putchar(char simvol, FILE *stream) loop_untj.l_bit_is_set (USR, UDRE); // Ожидаем появления i // в разряде UDRE UDR = simvol; . // Пересылаем через UART байт данных, // содержащийся в переменной simvol return 0; }; // Функция, которая будет вызываться при обращении потока на // ввод символа static int uart_getchar(FILE *stream) { while (!(USR & (1 << 7))); // Ожидаем, пока прием символа // завершится установкой в единицу разряда 7 (RXC) // в регистре USR (прием завершен) return UDR; //функция вернет принятый символ } // Создаем поток mystdout_mystdin, назначив функцию // uart_putchar для вывода, а функцию uart_getchar - для ввода. // Используем ввод и вывод, установив флаг _FDEV_SETUP_RW static FILE mystdout_mystdin = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW) ; // Функция передачи строки, хранящейся в памяти Flash // Адрес первого символа передается через указатель *FlashSTR void SendSTR_P(const char *FlashSTR) { uint8_t i; // Цикл for выполняется до тех пор, пока из Flash-памяти не // будет считан символ ”\0" for (i=0; pgmread—byte(&FlashSTR[i]); i++) { // Передаем через UART символ по адресу FlashSTR[i] putchar(pgm_read_byte(&FlashSTR[i])); } } // Следующия массивы хранятся не в ОЗУ, а в памяти программ const char menu[] PROGMEM = ”\r\n Меню: \r\n l)PB0 1/0 X*^11 2)PB1 1/0 \r\n 3)PB2 1/0 \r\n 4)PB3 1/0 \r\n 5)PB4 1/0 6)PB5 1/0 \r\n 7)PB6 1/0 \r\n 8)PB7 1/0 \r\n 0)EXIT\r\n введи”® номер от 1 до 8, чтобы подать +5V на вывод' МК, или 0 выхода\г\п";
работа с UART в AT90S2313 105 const char unknown [] PROGMEM = "\r\n Неизвестная команда\г\п"; const char quit[] PROGMEM = "\r\n Произошел выход\r\n"; const char cmd[] PROGMEM « "\r\n cmd>1'; // Объявляем массив указателей array[] на строки в памяти // программ. Тип PGM_P определен как const prog_char *, (т.е. // указатель на тип char для символа в памяти программ) pGM__P array[3] PROGMEM = {menu, unknown, quit}; int main(void) { UART_INIT(); // Инициализация UART // Передаем адрес памяти нашего потока в стандартный поток // stdout (на вывод) и в стандартный поток stdin (на ввод) stdin = stdout = &mystdout_mystdin; DDRB=0xff; // Выводы порта В - выходы while (1) // Бесконечный цикл { SendSTR_P(йаггау[0][0]); // Выводим меню (массив menu) SendSTR_JP(&cmd[0]); // Выводим приглашение командной строки char choose; choose = getcharO; // Принимаем через UART выбранный // в меню символ if (choose == ’О’) break; // Если выбран 0, выходим из // бесконечного цикла // В зависимости от выбранного пункта меню подаем +5 В на // соответствущий вывод порта В switch (choose) case ’1’: PORTB = ObOOOOOOOl; break; case ’2’: PORTB = ObOOOOOOlO; break; case ’3’: PORTB = ObOOOOOlOO; break; case ’4’: PORTB = ObOOOOlOOO; break; case ’5’: PORTB = ObOOOlOOOO; break; case *6’: PORTB = ObOOlOOOOO; break; case ’7’: PORTB = ObOlOOOOOO; break; case '8’: PORTB = OblOOOOOOO; break; default: SendSTR_P(&array[1] [0]); // Если переменная // choose не равна символам от 0 до 8, то выводим // фразу "Неизвестная команда" // Выводим строку из ОЗУ через UART
106 Глава 5 char buf[20]; // Инициализируем массив, размещенный в ОЗУ strcpy_P(buf, &quit[0]); // Копируем строку quit из Flash //в массив buf, размещенный в ОЗУ // Выводим строку через UART for (int i=0; i<20; i++) putchar(buf[i]); return 0; } Результат работы программы показан на рис. 5.22. Рис. 5.22. Реализовано приглашение командной строки
Глава 6 я За аналоговым компаратором закреплены два вывода микрокон- троллера: РВО (AINO) и РВ1 (AIN1). Когда напряжение на положитель- ном входе больше напряжения на отрицательном, устанавливается раз- ряд АСО в регистре ACSR (регистр управления и состояния аналогово- го компаратора). Для индикации того, на каком входе (AINO или AIN1) напряжение больше, мы определим выводы PD0 и PD1 как выходы и подключим к ним светодиоды (рис. 6.1). Рис. 6.1. Схема для исследования работы с аналоговым компаратором Положительный и отрицательный выводы компаратора определяем входы, и отключаем на них внутренние подтягивающие сопротив- ления:
108 Г лава 6 DDRB &= ~(1 « 0) & ~(1 « 1); PORTB &= ~(1 « 0) & -(1 << 1) ; Затем разрешаем работу компаратора и реализуем бесконечный цикл, в котором проверяем состояние пятого разряда (АСО) регистра ACSR. Если он содержит 1, напряжение на положительном входе РВО (AIN0) больше, чем на отрицательном РВ1 (AIN1). В программе (листинг 6.1) задано условие: при появлении единицы в разряде АСО подаем на вывод PD0 +5 В, а на PD1 — 0 В, и наоборот: если разряд АСО обнулился, на PD0 подаем 0 В, а на PD1 — +5 В. е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом К книге компакт-диске В папке AT90S2313\6 - Аналоговый компа- ратор. #include <avr/io.h> int main(void) { DDRD = 0x03; PORTD » 0x00; DDRB &= ~(1 « Заголовочный файл подключает определения ввода-вывода для устройства, используемого в проекте /I Выводы PD0,PD1 - выходы //На выводы порта D подаем 0 В & ~(1 « 1); PORTB &= ~(1 « 0) & -(1 « 0) ACSR &= ~(1 « 7); // // компаратор, записав // Выводы РВ0,РВ1 - входы 1);// Выводы РВ0,РВ1 - высокоомные входы (без внутренних подтягивающих сопротивлений) Разрешаем использовать аналоговый 0 в разряд 7 (ADC) регистра ACSR whiled) // Бесконечный цикл { if (ACSR & (1 << 5)) // Если в пятом разряде (АСО) { // регистра ACSR появилась единица, PORTD |= (1 « 0); // то на вывод PD0 подаем +5 В, PORTD &= ~(1 << 1); //а на вывод PD1 подаем 0 В4 } else { // Иначе, если АСО = 0, PORTD &= ~(1 << 0); // на вывод PD0 подаем 0 В, PORTD |= (1 « 1); // а на вывод PD1 - +5 В } } г
Глава 7 Прерывания (interrupts) — это вызовы определенных функций, ге- нерируемые, главным образом, аппаратной частью микроконтроллера. В результате прерывания выполнение программы приостанавливается, и происходит переход к соответствующей подпрограмме обработки прерывания. Прерывания бывают внутренними и внешними. Источни- ками внутреннего прерывания являются встроенные модули микрокон- троллера (например, таймер/счетчик или сторожевой таймер). Внешние прерывания вызываются сбросом (сигнал на выводе RESET) или сигна- лами заданного уровня на выводах INT. В программах для компилятора WinAVR подпрограммы обработки прерываний определяют с помощью следующей конструкции: ISR (Вектор_прерывания) { < // Программный код обработки прерывания U Здесь Вектор-Прерывания — это фиксированное условное обозна- чение типа прерывания, например: • TTMERO_OVFO_vect — прерывание по переполнению счетного регистра таймера/счетчика 0; • UART_UDRE_vect — прерывание при опустошении регистра дан- ных UDR приемопередатчика UART и т.д. Такие обозначение определены в библиотечном заголовочном фай- ле avr/interrupt.h. Прерывание при переполнении счетного регистра TCNT0 Схема соединений в данном примере такая же, как представленная рис. 3.3. Внутренний таймер настроен так, что через каждые 102 4000000 МГц = 0,000256 с увеличивается значение счетного регистра TCNTO.
110 Глава 7 Отличие этого примера от программы, представленной в листинге 3.1, заключается в том, что вместо условного выражения, в котором на равенство нулю проверяется значение TCNT0, мы воспользуемся пре- рыванием по переполнению таймера/счетчика 0 (листинг 7.1). Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на ЯВЛ прилагаемом к книге компакт-диске в папке AT9OS2313\7. oi - прерывание при переполнении счетного регистра TCNT0. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <avr/interrupt.h> // Для работы с прерываниями // Подпрограмма обработки прерывания при переполнении TCNT0 ISR(TIMERO_OVFO_vect) { PORTD = 0x7F; // Поскольку значение счетного регистра // сменилось с 255 на 0, на все выводы // порта D подаем +5 В } int main(void) { DDRD = 0x7F; // 01111111 - определяем все выводы порта D // как выходы (в порту D всего 7 выводов: // PD0-PD7) PORTD = 0x7F; // 01111111 - на всех выводах порта D - // уровень лог. 1 (+5 В) TCNT0 =0; // Инициализация счетного регистра // таймера/счетчикаО, //Настройка таймера/счетчикаО TCCR0=0x05; // Инициализация регистра управления // таймером/счетчиком 0. Значению 5 (т.е. // 0Ь101) соответствует настройка таймера // на частоту 4000000/1024 Гц. Это значит, // что каждые 0,000256 с значение счетного // регистра TCNT0 будет увеличиваться // (использован предварительный делитель) //Настройка прерывания TIMSK |= (1 « 1); // Первый разряд (TOIE0) регистра // маскирования прерываний TIMSK // равен 1. Это значит, что // разрешено прерывание по
работа с прерываниями в AT90S2313 111 // переполнению таймера/счетчика 0, II т.е. когда счетный регистр TCNT0, // досчитав до 255, сбросится в О, // произойдет прерывание по вектору // TIMERO_OVFO_vect sei О; // Запись единицы в разряд 7 регистра SREG, что // означает общее разрешение прерываний. while(1) // Бесконечный цикл { // Если счетный регистр -содержит 128, то на все выводы // порта D подаем О В if (TCNT0 == 0x80) { PORTD = 0x00; } } } Работа таймера/счетчика 0 в режиме счетчика импульсов на внешнем выводе Схема соединений в данном примере такая же, как представленная на рис. 3.4. Таймер/счетчик настроен на использование источника внеш- них тактовых сигналов, приходящих на вывод PD4. Отличие этого при- мера от программы, представленной в листинге 3.2, заключается в том, что в нем используется подпрограмма обработки прерывания, по пере- полнению счетного регистра TCNT0. После 20 отсчетов сработает век- тор прерывания TIMER0_OVF0_vect, и будет вызвана соответствую- щая подпрограмма обработки. Для того чтобы переполнение сработало на 20-й отсчет, счетный регистр инициализируется значением 236. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на W прилагаемом К книге компакт-диске В папке AT90S2313\7.02 - Режим счетчика импульсов на внешнем выводе ТО. ^include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте ^include <avr/interrupt.h> // Для работы с прерываниями // Подпрограмма обработки прерывания при переполнении TCNT0 *SR (TIMER0_OVF0,_vect) PORTB = Oxff; // На все выводы порта В подать +5 В TCNT0 = ОхЕС; // TCNT0=236, чтобы до переполнения
112 Глава 7 // оставалось 20 отсчетов } int main(void) DDRB = Oxff; // Obllllllll - все выводы порта В - выходы PORTB = Oxff; // Obllllllll - на все выводы порта В // подаем +5 В DDRD |*~(1«4); // Обнуляем разряд 4 регистра // направления передачи данных порта // D, т.е. вывод PD4 - вход PORTD |= 1 « 4; // Записываем 1 в разряд 4 регистра PORT. // Это значит, что вывод PD4 будет нагружен // подтягивающим сопротивлением TCNT0 ж ОхЕС; // Счетный регистр содержит 236, чтобы до // переполнения оставалось 20 отсчетов //Настройка таймера/счетчика0 TCCR0 = 0x07; // TCCR0=0b00000111 - это значит, что И значение счетного регистра TCNT0 будет и увеличиваться по ниспадающему фронту на и выводе ТО (PD4). Поскольку внутри МК мы и подключили к выводу PD4 подтягивающий и резистор, для появления ниспадающего и фронта следует подать на вывод 0 В. //Настройка прерывания TIMSK |= (1 « 1); // Первый разряд (TOIE0) регистра // маскирования прерываний TIMSK // равен 1. Это значит, что // разрешено прерывание по // переполнению таймера/счетчика 0, // т.е. когда счетный регистр TCNT0 // досчитав до 255, сбросится в 0, // произойдет прерывание по вектору // TIMERO_OVFO_vect sei(); // Запись единицы в разряд 7 регистра SREG, что // означает общее разрешение прерываний. while(1) // Бесконечный цикл { if (TCNT0 жж 0xF6) { PORTB = 0x00; } // Если счетный // содержит 0xF5 (246), т.е. счетчик сделал 10 отсчетов, // на все выводы порта D подаем 0 В
работа с прерываниями в AT90S2313 113 Прерывание при переполнении счетного регистра TCNT1 Схема соединений в данном примере такая же, как представленная йа рис. 3.7. Таймер/счетчик 1 считает в цикле от 0 до 65535. Переполне- ние счетного регистра TCNT1 возникает при переходе от 65535 к 0. В подпрограмма обработке прерывания TIMERl_OVFl_vect на все вы- воды порта D подается +5 В. Регистр управления TCCR1 настроен так, что значение счетного ре- гистра TCNT1 увеличивается на единицу каждые 0,000016 с (при часто- те микроконтроллера 4 МГц). Когда это значение достигнет 32768, т.е. через 32 768 • 0,000016 = 0,524288 с, на всех выводах порта D устано- вится 0 В. ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT9OS2313\7. оз - прерывание при переполнении счетного регистра TCNT1. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <avr/interrupt.h> // Для работы с прерываниями / / Подпрограмма обработки прерывания -при переполнении TCNT1 ISR(TIMER1 OVF1 vect) { ’ ~ PORTD = 0x7F; // На все выводы порта D подаем +5 В. int main (void) DDRD = 0x7F; // 01111111 - определяем все выводы порта D // как выходы (в порту D всего 7 выводов: // PD0-PD7) PORTD ж 0x7F; // 01111111 - на всех выводах порта D - // уровень лог. 1 (+5 В) //Настройка таймера/счетчика! TCCR1A = 0x00; // Режим совпадения на выходе отключен, // поскольку мы не используем регистр // совпадения OCR1; также запрещена ШИМ TCCR1B « 0x03; // ОЬООООООН - регистр захвата (ICR1) не // используем, поэтому разряды 6-7 - °’ // Не используем регистр совпадения (OCR1),
114 Глава 7 // поэтому разряд 3=0. Выбираем источнц* // счетных импульсов для регистра TCNT1 // разрядами 0-2. Значение 011 в этих // разрядах означает, что частота счетных // импульсов - частота МК / 64, т.е. каждые // 64/4000000(4МГц) = 0,000016 с регистр // TCNT1 увеличивается на единицу. //Настройка прерывания TIMSK |= (1 « 7); // Разряд 7 (TOIE1) регистра // маскирования прерываний содержит // единицу. Это значит, что // разрешено прерывание по // переполнению таймера/счетчика 1. // Когда содержимое счетного // регистра TCNT1, миновав 65535, // обнулится, произойдет прерывание // по вектору TIMERl_OVFl_vect (для // другого МК этот вектор прерывания // может называться иначе!) sei(); // Запись единицы в разряд 7 регистра SREG, что // означает общее разрешение прерываний. while(1) // Бесконечный цикл { if (TCNT1 == 0x8000) // Если TCNT1 содержит 32768, то { PORTD = 0x00; } //на все выводы порта D подаем 0 В } } Работа таймера/счетчика 1 в режиме счетчика импульсов на внешнем выводе Схема соединений в данном примере такая же, как представленная на рис. 3.8. В данном примере таймер/счетчик 1 настроен на использо- вание источника внешних тактовых сигналов. К выводу микроконтрол- лера PD5 подключена кнопка, и значение счетного регистра TCNT1 увеличивается на единицу по каждому ниспадающему фронту напряже- ния на выводе Т1 (PD5). После пятого нажатия кнопки на все выводы порта В подается 0 В- Повторное пятикратное нажатие кнопки приводит к прерыванию по пе- реполнению регистра TCNT1, вследствие чего вызывается соответсТ' вующая подпрограмма обработки, в которой на все выводы порта В по- дается +5 В, а счетному регистру присваивается значение 65 526. Пр*1 таком значении до следующего переполнения останется 10 отсчетов.
работа с прерываниями в AT90S2313 115 Эта схема будит правильно работать только в имитаторе, поскольку в нет отсутствует дребезг контактов. При нажатии на кнопку происхо- дит многократное замыкание и размыкание ее контактов. Поскольку микроконтроллер работает на высокой частоте, он принимает этот дре- безг за нажатия. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT9OS2313\7.04 - Режим счетчика импульсов на внешнем выводе Т1. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <avr/interrupt.h> // Для работы с прерываниями // Подпрограмма обработки прерывания при переполнении TCNT0 ISR(TIMERl_OVFl_vect) { PORTB = OxFF; // На весь порт В подать +5 В TCNT1 s 0xFFF6; // TCNT1=65526, чтобы до переполнения // оставалось 10 отсчетов } int main (void) { DDRB = OxFF; // 0Ы1111111 - все выводы порта В - выходы PORTB = OxFF; // 0Ы1111111 - на все выводы порта В // подаем +5 В DDRD |= -(1 « 5) ; // Обнуляем разряд 5 регистра // направления передачи данных порта // D, т.е. вывод PD5 - вход PORTD |= 1 « 5; // Записываем 1 в разряд 5 регистра PORT // Это значит, что вывод PD5 будет нагружен // подтягивающим сопротивлением //Настройка таймера/счетчика! TCCR1A = 0x00; // Режим совпадения на выходе отключен, // поскольку мы не используем регистр // совпадения OCR1; также запрещена ШИМ TCCR1B = 0x06; // ОЬОООООИО. Регистр захвата ICR1 не // используем, поэтому разряды 6-7 = 0. // Регистр совпадения OCR1 не используем, // поэтому разряд 3=0. Источник счетных // импульсов для регистра TCNT выбираем // с помощью разрядов 0-2. Они содержат // 110, а значит источником счетных
116 Глава 7 // импульсов будет ниспадающий фронт // (переход сигнала с +5 к О В) на выводе TCNT1 = 0xFFF6; // Т1 (в AT90S2313 - PD5), т.е. при // переходе сигнала на выводе PD5 с +5 В на // О В содержимое TCNT1 увеличится на 1. // Счетный регистр содержит 65526, чтобы до // переполнения оставалось 10 отсчетов //Настройка прерывания TIMSK |= (1 « 7); // Разряд 7 (TOIE1) регистра // маскирования прерываний содержит // единицу. Это значит, что // разрешено прерывание по // переполнению таймера/счетчика 1. // Когда содержимое счетного // регистра TCNT1, миновав 65535, // обнулится, произойдет прерывание // по вектору TIMERl_OVFl_vect (для // другого МК этот вектор прерывания // может называться иначе!) sei О; // Запись единицы в разряд 7 регистра SREG, что // означает общее разрешение прерываний. while (1) // Бесконечный цикл { if (TCNT1 == OxFFFB) // Если TCNT1 содержит 65531, т.е. z // отсчитано 5 импульсов на выводе // МК PD5, то { PORTB = 0x00; } // на все выводы порта В подаем 0 В } } Прерывание по сигналу на входе захвата Схема соединений в данном примере такая же, как представленная на рис. 3.9. Программа идентична показанной в листинге 3.5, за тем иск- лючением, что записывается 1 в разряд 3 регистра маскирования преры- ваний TIMSK, что разрешает прерывание TIMERl_CAPTl_vect при появлении сигнала на входе PD6 (ICP). При поступлении сигнала на вход PD6 содержимое счетного регистра TCNT1 копируется в регистр захвата ICR1, срабатывает вектор прерывания TIMERl_CAPTl_vect, вызывается соответствующая подпрограмма обработки, которая уста- навливает уровень лог. 1 или лог. 0 на тех или иных выводах микрокон- троллера.
Работа с прерываниями в AT90S2313 117 е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT9OS2313\7.05 - прерывание по сигналу на входе захвата. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <avr/interrupt.h> // Для работы с прерываниями // Подпрограмма обработки прерывания по сигналу на входе // захвата ICP (PD6) ISR(TIMERl_CAPTl_vect) { // Индикация диапазона, в котором находится значение регистра // захвата ICR1 if (ICR1 > 0x2710) PORTB |= (1«1) ; else PORTB &= if (ICR1 > 0x3А98) PORTB |= (1«2) ; else PORTB &= ~(1«2); if (ICR1 > 0x4E20) PORTB j= (1«3) ; else PORTB &= ~(1«3); if (ICR1 > 0x7530) PORTB j = (1«4) ; else PORTB &= ~(1«4); if (ICR1 > 0x9C40) PORTB j= (1«5) ; else PORTB &= - (1«5) ; if (ICR1 > 0xC350) PORTB j= (1«6) ; else PORTB &= ~(1«6) ; if (ICR1 > ОхЕАбО) PORTB |= (1«7) ; else PORTB &= ~(1«7); } int main(void) { DDRB = OxFF; // Obllllllll - все выводы порта В - выходы PORTB = OxFF; // Obllllllll - на все выводы порта В // подаем +5 В //Настройка таймера/счетчика! TCCR1A = 0x00; // Режим совпадения на выходе отключен, // поскольку мы не используем регистр // совпадения OCR1; также запрещена ШИМ TCCR1B = 0x85; // OblOOOOlOl - используем регистр захвата // ICR1. Разряд 7 = 1, а значит включен // режим подавления входного шума на выводе // ICP (PD6 в AT90S2313)). Разряд 6 = 0, // т.е. по ниспадающему фронту сигнала // (переходу напряжения с +5 к 0 В) на // выводе PD6 будет выполнен захват // (значение регистра TCNT1 перепишется // в регистр ICR1). Очистка счетчика по // совпадению нам не нужна, поскольку // используем регистр совпадения ' // поэтому разряд 3=0. Выбираем и к
118 Глава 7 // счетных импульсов для регистра TCNT //с помощью разрядов 0-2. Мы записываем // в них 101, т.е. источник счетных // импульсов - частота МК/1024. Другими // словами, значение TCNT1 будет // увеличиваться на единицу каждые, // 1024/4000000(4МГц) = 0,000256 с. // Настройка вывода ICP (PD6) обязательна, даже если мы // задали параметры захвата в TCCR1 DDRD &= ~(1 « 6);// Разряд 6 регистра направления передачи // данных = 0, т.е. PD6 работает как вход. PORTD |= (1 << 6);// К разряду порта D подключен внутренний // подтягивающий резистор (на выводе +5 В). //Настройка прерывания TIMSK |= (1 « 3); // Разряд 3 (TICIE1) регистра // маскирования прерываний содержит // единицу. Это значит, что // разрешено прерывание по // сигналу на входу захвата. sei(); // Запись единицы в разряд 7 регистра SREG, что // означает общее разрешение прерываний. while (1) // Бесконечный цикл { // Подаем на вывод РВО +5 В, если счетный регистр 32768. // Если он меньше, то подаем 0 В. if (TCNT1 >= 0x8000) // Если TCNT1 > 32768, то PORTB |= (1 << 0); // на РВО подаем +5 В else PORTB &= ~(1 << 0); // иначе подаем 0 В } } Прерывание при совпадении регистра OCR1 Схема соединений в данном примере такая же, как представленная на рис. 3.10. Регистр TCCR1 настроен с помощью разрядов COM 1А1 и СОМ 1 АО так, что при совпадении значений TCNT1 и OCR1 состояние вывода ОС1 (РВЗ) будет переключаться (с 0 в 1 или с 1 в 0). Значение счетного регистра TCNT1 увеличивается через каждые 0,000016 с. Реги- стру совпадения OCR1 присвоено значение 32 768. Таким образом, пер- вое переключение состояния на выводе ОС1 (РВЗ) произойдет через 32 768 • 0,000016 = 0,524288 с.
работа с прерываниями в AT90S2313 119 В регистре маскирования прерываний TIMSK установлен разряд, разрешающий прерывание по совпадению TCNT1 и OCR1. В соответст- вующей подпрограмме обработки для индикации срабатывания преры- вание включается и отключается светодиод D1. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на Z’ прилагаемом к книге компакт-диске в папке AT9OS2313\7.06 - прерывание при совпадении регистра OCR1. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <avr/interrupt.h> // Для работы с прерываниями #include cutil/delay.h> // Для доступа к функциям циклов // задержки // Подпрограмма обработки прерывания при совпадении содержимого // регистров TCNT1 и OCR1 ISR(TIMERl_COMPl_vect) { PORTB |= (1 « 0); // На РВО подаем +5 В for (int 1=0; i<5; i++) _delay_loop_2(65535); PORTB &= ~(1 « 0); // На РВО подаем 0 В } int main (void) { DDRB |= (1 « 3) ; // Разряд 3 регистра передачи данных DDRB // = 1, т.е. вывод РВЗ работает как выход // (к нему подключен светодиод) DDRB |= (1 << 0); // Вывод РВО - выход PORTB &= ~(1 « 0); // На РВО подаем 0 В // Регистр OCR1 - 16-разрядный. В нем хранятся данные, // которые непрерывно сравниваются с текущим значением // счетного регистра TCNT1 OCR1 = 0x8000; // Когда значение TCNT1 достигнет 32768, // произойдет действие, заданное регистром // TCCR1 // Настройка таймера/счетчика 1 // Регистр управления TCCR1 - 16-разрядный, состоит из // двух 8-разрядных регистров: TCCR1A и TCCR1B ^CCRIA = 0x40; // ОЬОЮООООО - режим совпадения на выходе. // Разряд 7=0, разряд 6=1. Это говорит // о том, что вывод ОС1 (РВЗ) будет
120 Глава 7 // переключаться при совпадении значений // TCNT1 и OCR1. Разряд 0=0, а разряд 1=0. // Это значит, что ШИМ не используется. TCCR1B = 0x03; // ОЬООООООН - регистр захвата ICR1 не // используем (разряд 7=0, разряд 6=0). // Разряд 3=0 означает, что мы не будем // обнулять регистр TCNT1, когда TCNT1 = // OCR1. Разряды 0-2 = 011, т.е. источник // тактирования - частота МК/64 (значение // счетного регистра TCNT1 будет // увеличиваться на единицу каждые // 64/4000000 (4МГц) = 0,000016 с. //Настройка прерывания TIMSK |= (1 « 6); // Разряц_6 (OCIE1A) регистра // маскирования прерываний содержит // единицу. Это значит, что // разрешено прерывание по // совпадению регистра OCR1, sei(); // Запись единицы в разряд 7 регистра SREG, что // означает общее разрешение прерываний. while(1) { } // Бесконечный цикл } Внешние прерывания INTO и INT1 Отличие данной программы от представленной в листинге 3.18 за- ключается в том что, вместо условий проверки состояния выводов PD0 и PD1 используются внешние прерывания INTO (вывод PD2) и INT1 (вывод PD3). Слово “внешние” говорит само за себя. Если в предыду- щих примерах мы использовали прерывания, которые генерировались внутри микроконтроллера, то в данном случае они срабатывают при по- ступлении сигналов извне. Прерывания INTO и INT1 настроены на срабатывание по ниспадаю- щему фронту. Другие варианты настройки: прерывание по нарастаю- щему фронту или по низкому уровню сигнала. Когда на выводе PD2 напряжение падает с 5 В до 0 В (ниспадаю- щий фронт), вызывается подпрограмма обработки вектора INT0_vect- В ней увеличивается на единицу значение регистра сравнения OCR1- Тем самым, если ШИМ неинвертирующая, увеличивается длительность положительного импульса. Схема соединений для данного примера показана на рис. 7.1.
работа с прерываниями в AT90S2313 121 Рис. 7.1. Схема соединений для исследования внешних прерываний INTO и INT1 Программа, реализующая работу этой схемы, представлена в лис- тинге 7.7. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT9OS2313\7.07 - Внешние преры- вания INTO и INT1. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте ttinclude <avr/interrupt.h> // Для работы с прерываниями ^include <util/delay.h> // Для доступа к функциям циклов // задержки // Подпрограмма обработки прерывания по спаду напряжения на //на выводе INTO (PD2) ISR(INTO vect) { OCR1 = OCR1 +1; // Изменение коэффициента заполнения // (увеличение длительности положительного // импульса)
122 Г лава 7 delayloop—2(65535); // Задержка перед следующим нажатием // кнопки } // Подпрограмма обработки прерывания по спаду напряжения на // на выводе INTI (PD3) ISR (INTl_vect) { OCR1 = OCR1 - 1; // Изменение коэффициента заполнения // (уменьшение длительности положительного // импульса) _delay_loop_2(65535); // Задержка перед следующим нажатием // кнопки } int main(void) { DDRB |= (1 << 3); // Разряд 3 регистра передачи данных DDRB // = 1, т.е. вывод РВЗ работает как выход // (к нему подключен светодиод) // Регистр OCR1A - 16-разрядный, хранит данные, которые // непрерывно сравниваются с текущим значением // таймера/счетчика 1 (TCNT1) OCR1A = 0x0IFF; // 511 // Настройка таймера/счетчика 1 // Регистр управления таймером/счетчиком TCCR1 состоит из // двух 8-разрядных регистров: TCCR1A и TCCR1B // СОМ1А1 (разряд 7) =1, СОМ1АО (разряд 6) = 0 - установка // режима совпадения ддя ШИМ. Теперь вывод ОС1 (РВЗ) // устанавливается в лог. 0 при совпадении TCNT1 и OCR1A, // когда TCNT1 увеличивается. Если же TCNT1 уменьшается, то // при совпадении TCNT1 и OCR1 на выводе ОС1 (РВЗ) // устанавливается лог. 1 (неинвертирующая ШИМ). // PWM10 (разряд 0) =1, PWM11 (разряд 1) =1. Определяем // 10-разрядную ШИМ (1024 отсчетов от 0 до 1023), т.е. // значение счетного регистра TCNT1 будет в цикле изменяться // от 0 до 1023 и обратно от 1023 до 0. TCCR1A = 0x83; //0Ы0000011 // ICNC1 (разряд 7) =0, ICES1 (разряд 6) = 0 - регистр // захвата ICR1 не используем. // Разряд 3=0- регистр TCNT1 при совпадении TCNT1 и OCR1 // не обнуляется (в режиме ШИМ этот разряд не используют). // Разряды 0-2 - выбор источника счетных импульсов для
работа с прерываниями в AT90S2313 123 // регистра TCNT1. Значениею 0Ы01 соответствует частота // микроконтроллера 4 МГц, т.е. каждые 1 / 4000000 = // 0,00000025 с значение счетного регистра TCNT1 будет // увеличиваться на единицу. TCCR1B = 0x01; //ObOOOOOOOl // Определяем PD2 и PD3 как входы. К этим выводам подключены // кнопки для изменения коэффициента заполнения ШИМ DDRD &= ~(1 « 2) & ~(1 « 3); // PD2 и PD3 - входы PORTD |= (1 « 2) | (1 << 3); // Подключаем к PD2 и PD3 // внутренние нагрузочные // сопротивления // Настройка прерываний MCUCR = ОхОА; // ОЬООООЮЮ - прерывания INT1 или INTO // сработают по ниспадающему фронту сигнала // (если мы нажмем кнопку и будем ее // удерживать нажатой, то прерывание // сработает только один раз, если нет // дребезга контактов) GIMSK |= (1 « 6) | (1 << 7); // Запись единицы в разряды 6 // (INTO) и 7 (INT1) регистра GIMSK // разрешает внешние прерывания на выводах // PD2 (INTO) и PD3(INT1) в МК AT90S2313. // Тип сигнала для возникновения прерывания // задают в регистре MCUCR sei(); // Запись единицы в разряд 7 регистра SREG, что // означает общее разрешение прерываний. while (1) { } // Бесконечный цикл Прерывание при очистке регистра UDR Схема соединений в данном примере такая же, как представленная На рис. 5.1. Как только регистр данных UDR приемопередатчика UART очистится, возникнет прерывание (вектора UART_UDRE_vect). В соот- Ветствующей подпрограмме обработки в регистр данных UDR записы- вается символ “А”, который и будет передан через UART (вывод TXD). Поскольку программа работает в бесконечном цикле, а подпрограмма взывается каждый раз при освобождении регистра UDR, символ “А” будет передаваться постоянно. Для того чтобы символ передался толь- один раз, необходимо после первой передачи отключить прерывание ”Ри очистке регистра данных UART (5-й разряд UDRIE регистра )
124 Глава? Программа, реализующая прерывание при очистке регистра UDR представлена в листинге 7.8. е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT9OS2313\7.08 - прерывание при очистке регистра UDR. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <avr/interrupt.h> // Для работы с прерываниями // Подпрограмма обработки прерывания при очистке регистра // данных UDR приемопередатчика UART (возникает при установке // 5-го разряда UDRE в регистре USR) ISR(UART_UDRE_vect) { UDR = ‘А’; // Передаем через UART букву "A” // Отключаем прерывание, чтобы символ "А” // передался только один раз UCR &= ~(1 « 5); } void UART INITO { UCR |= (1 « 3) | (1 << 5) ; // Разрешаем работу приемника UBRR = 0x17; // (1 в разряде 4 регистра // UCR) и передатчика (1 в // разряде 3 регистра UCR) // Устанавливаем скорость передачи 9600 бод } int main(void) { UARTINIT(); // при частоте МК 3.6864 МГц // Инициализация UART sei(); // Общее разрешение прерываний while (1) { } // Бесконечный цикл } В это примере мы реализовали передачу одного символа. РассмоТ' рим программу, которая с помощью прерывания при очистке регистр3 UDR передает через UART строку. Схема соединений в данном пример6 такая же, как представленная на рис. 5.4. В программе (листинг 7.9) глобальному указателю р мы присваив3' ем адрес нулевого элемента символьного массива. Зная адрес первог0
работа с прерываниями в AT90S2313 125 элемента массива, мы можем с помощью оператора * извлечь содержи- мое ячейки по данному адресу. В подпрограмме обработки прерывания (UART_UDRE_vect) мы проверяем наличие данных по адресу р и заносим их в регистр UDR. Если данные для отправки отсутствуют, прерывание Отключается. е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT90S2313\7.09 - Прерывание при очистке регистра UDR. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <avr/interrupt.h> // Для работы с прерываниями static volatile char *р; // Указатель // Подпрограмма обработки прерывания при очистке регистра // данных UDR приемопередатчика UART (возникает при установке // 5-го разряда UDRE в регистре USR) ISR(UART_UDRE_vect) { if (*р) // Если есть, что отправлять... { // Записываем в регистр данных передаваемый UDR = *р++; // символ и увеличиваем указатель на 1, } //т.е. переходим к следующей ячейке. else { // Если отправлять нечего, то UCR &= ~(1«5); // отключаем прерывание v°id UART_INIT() UCR |= (1 « 3) 1 <1 << 5) ; // Разрешаем работу приемника UBRR = 0x17; // (1 в разряде 4 регистра // UCR) и передатчика (1 в // разряде 3 регистра UCR) // Устанавливаем скорость передачи 9600 бод } // при частоте МК 3.6864 МГц main (void) UARTiNIT(); // Инициализация UART eei(); // Общее разрешение прерываний
126 Глава 7 // Массив stroka[] содержит предложение, которое будет // отправлено через UART. Обратиться к отдельному байту можно // как к stroka[3] (в этом байте хранится буква 'в’). // OxOD - это "\г”/ ОхОА - это "\п". static char stroka[] = 11 привет, это твой микроконтроллер =)\г\пи; р = &stroka[0J; // Сохраняем адрес памяти первого элемента // массива в глобальном указателе р // Начинаем передачу UCR |= (1 « 5); // Записав 1 в разряд UDRIE, активизируем // прерывание при очистке регистра данных while (1) { } // Бесконечный цикл } Прерывание по окончанию приема данных Схема соединений в данном примере такая же, как представленная на рис. 5.6. Программа (листинг 7.10) принимает данные через UART и выводит их в порт В. При этом используется прерывание по оконча- нию приема, которое сработает при появлении данных в регистре UDR. В подпрограмме обработки прерывания (UART_RX_vect) значение, хра- нимое в регистре UDR, копируется в порт В. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT9OS2313\7.10 - Прерывание по окончанию приема данных. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <avr/interrupt.h> // Для работы с прерываниями // Подпрограмма обработки прерывания по окончанию приема данных ISR(UART_RX_vect) { PORTB = UDR; // Выводим в порт В полученный байт } void UART INITO { UCR |= (1 « 4) | (1 << 7); // Разрешаем работу приемника // и прерывание по окончанию приема
работа с прерываниями в AT90S2313 127 UBRR = 0x17; // Устанавливаем скорость передачи 9600 бод // при частоте МК 3.6864 МГц } int main(void) { UARTINIT(); DDRB = OxFF; PORTB « 0x00; sei О ; // Инициализация UART // Все выводы порта В - выходы // На все выводы порта В подаем 0 В // Общее разрешение прерываний while (1) { } // Бесконечный цикл Еще один вариант программы — с отправкой принятого байта об- ратно через UART в подпрограмме обработки прерывания для вектора UART_RX_vect (листинг 7.11). Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на Як? прилагаемом к книге компакт-диске в папке AT90S2313\7.11 - Передача при- нятого символа. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <avr/interrupt.h> // Для работы с прерываниями // Подпрограмма обработки прерывания по окончанию приема данных ISR(UART RX vect) { char гsv = UDR; PORTB = rsv; // Сохраняем принятый символ в переменной // Выводим принятый байт в порт В, для // индикации с помощью светодиодов UDR = rsv; // Отправляем принятый символ через UART, } // чтобы увидеть, что было принято void UART INITO { // Разрешаем работу приемника, передатчика и прерывание по // окончанию приема UCR |= (1 « 4) | (1 « 7) | (1 « 3); UBRR = 0x17; // Устанавливаем скорость передачи 9600 бод // при частоте МК 3.6864 МГц
128 Глава 7 int main(void) UART INITO ; DDRB = OxFF; // Инициализация UART // Все выводы порта В - выходы PORTB = 0x00; // На все выводы порта В подаем 0 В sei(); // Общее разрешение прерываний while (1) { } Бесконечный цикл Прерывание по окончанию передачи данных Схема соединений в данном примере такая же, как представленная на рис. 5.6. Программа (листинг 7.12) принимает через UART байт, по- сле чего отправляет обратно строку “МК recived-x“, где “х” — приня- тый символ. Строка хранится в символьном массиве, на текущий элеент которого указывает глобальный указатель р. При этом в подпрограмме обработки прерывания по окончанию передачи (UART_TX_vect) р увеличивается на единицу, чтобы перейти к следующему элементу мас- сива. Если по адресу р есть какие-либо данные, то они отправляются через UART, в противном случае мы отправляем “0” для индикации окончания передачи, и отключаем прерывание. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT90S2313\7.12 - Прерывание по окончанию передачи данных. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <avr/interrupt.h> // Для работы с прерываниями static volatile char *р; // Глобальный указатель // В глобальный массив str записываем предложение "\r\nMK // recived- \r\n", где ”\г" - возврат каретки, а "\п” - символ // новой строки static volatile char str[] = "\r\nMK recived- \r\n"; // Подпрограмма обрабоки прерывания по окончанию приема // (вызывается, когда принятый символ окажется в регистре UPR) ISR(UART_RX_vect) { char гsv = UDR; // Сохраняем принятый символ в переменной
работа с прерываниями в AT90S2313 129 PORTB « rsv; // Выводим принятый байт в порт В для // индикации на светодиодах, а также str[13] = rsv; // записываем его в 13 ячейку глобального // массива со строкой для вывода p a &str [0]; // Запоминаем адрес первого символа строки UCR |a (1 « 6); // Разрешаем прерывание по окончанию // передачи (как только данные передадутся // через UART, сработает вектор прерывания // UART_TX_vect) UDR a str[0]; // Записываем в регистр данных первый // символ для передачи через UART. После // его отправки возникнет прерывание по // окончанию передачи данных. } // Подпрограмма обработки прерывания по окончанию передачи // (вызовется, когда в регистре UDR не останется данных) ISR(UART_TX_vect) t р = р + 1; // Увеличиваем адрес, чтобы перейти //к следующему элементу массива if (*р) // Если по адресу р есть символ, то... { UDR as *р; // записываем содержимое ячейки в регистр // данных (т.е. передаем через UART) } else // Если отправлять нечего, то... { UDR •О’; // Отправляем через UART символ "0" (просто // для индикации окончания передачи) UCR &= ~(1<<6); // Включаем прерывание >> void UART INITO { // Разрешаем работу приемника, передатчика и прерывание по // окончанию приема UCR |» (1 « 4) | (1 « 7) | (1 « 3); UBRR « 0x17; //23 - устанавливаем скорость передачи j // 9600 бод при частоте МК 3.6864 МГц main (void) UART_INIT() ;
130 Глава 7 DDRB = OXFF; PORTB = 0x00; sei() ; // Все выводы порта В - выходы // На всех выводах порта В - 0 В // Общее разрешение прерывания while (1) { } // Бесконечный цикл } Прерывание от аналогового компаратора Схема соединений в данном примере такая же, как представленная на рис. 6.1. Прерывание возникает при появлении ниспадающего фронта на выходе компаратора АСО. Выход компаратора переходит из 0 в 1, когда напряжение на отрицательным входе AIN1 (РВ1) становится больше, чем напряжение на положительном входе. В подпрограмме об- работки прерывания, на вывод PD0 подается 0 В, а на PD1 — +5 В. Программа, демонстрирующая использование прерывания от анало- гового компаратора, представлена’в листинге 7.13. ©I Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на i прилагаемом к книге компакт-диске в папке AT90S2313\7.13 - Прерывание от аналогового компаратора. #include~ <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <avr/interrupt.h> // Для работы с прерываниями #include <util/delay.h> // Для доступа к функциям // циклов задержки // Подпрограмма обработки прерывания от аналогового компаратора // (вызовется, когда выход компаратора АСО перейдет из 1 в 0) ISR(ANA_COMP_vect) { // Если разряд 5 регистра ACSR содержит 0, то на вывод PD0 // подаем 0 В, а на вывод PD1 — +5 В PORTD &= ~(1 « 0); PORTD |= (1 « 1); } int main(void) { DDRD = 0x03; // Выводы PD0,PDl - выходы PORTD = 0x00; //На выводы порта D<подаем 0 В
работа с прерываниями в AT90S2313 131 // Входы компаратора DDRB &= ~(1 « 0) & ~(1 « 1); // Выводы РВ0,РВ1 - входы PORTB &= ~(1 « 0) & ~(1 << 1) ;// Выводы РВ0,РВ1 - // высокоомные входы (без // внутренних подтягивающих // сопротивлений) ACSR &= ~(1 « 7); // Разрешаем использование // аналогового компаратора, записав // 0 в разряд 7 (ADC) регистра ACSR ACSR |= (1 << 3) ; // Разрешаем прерывание от // аналогового компаратора, записав // 1 в разряд 3 (ACIE) регистра ACSR // Прерывание возникает по ни спадающему фронту на выходе // компаратора, т.е. когда разряд АСО регистра ACSR перейдет // из состояния 1 в 0. Это произойдет, если напряжение на // отрицательном входе AIN1 (РВ1) станет больше напряжения на // положительном входе AIN0(РВО). ACSR &= -(1 « 0); // Разряд ACIS0=0 // Разряд ACIS1=1 // Общее разрешение прерываний ACSR |= (1 « sei(); 1); while (1) // Бесконечный цикл { if (ACSR & ( (1 « 5)) // Если разряд АСО=1, то PORTD |= (1 « 0); // на вывод PD0 подаем +5 В, PORTD &= ~(1 « 1); // а на вывод PD1 - 0 В. Использование таймера/счетчика 1 в режиме ШИМ К микроконтроллеру подключены четыре кнопки (рис. 7.2): • PDO, PD1 — для увеличения или уменьшения коэффициента запол- нения g ШИМ-сигнала; • PD2, PD3 — для увеличения или уменьшения частоты ШИМ-сигна- ла. При нажатии кнопки PD0 увеличивается длительность положитель- ного импульса и уменьшается длительность нулевого, и наоборот — При нажатии кнопки PD1 уменьшается длительность положительного Импульса, и увеличивается длительность нулевого.
132 Глава? При нажатии кнопки PD2 сокращается время полупериода (полови- на положительного или нулевого импульса), а при нажатии кнопки PD3 оно увеличивается. Программа, реализующая работу схемы, показанной на рис. 7.2, представлена в листинге 7.14. е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке AT9OS2313\7.14 - Использование таймера 1 в режиме ШИМ. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <avr/interrupt.h> // Для работы с прерываниями volatile int tcnt_ = 0; // Глобальная переменная типа int // Подпрограмма обработки прерывания при переполнении TCNT1 ISR(TIMERl_OVFl_vect) {
работа с прерываниями в AT90S2313 133 TCNT1 = TCNT1 + tcnt_; // Содержимое регистра TCNT1 // увеличится или уменьшится в зависимости от значения // переменной tcnt_ (поскольку она объявлена как // volatile, ее значение можно изменять в подпрограмме) } int main(void) { DDRB |= (1 << 3); // Разряд 3 порта В - выход ШИМ-сигнала // Настройка таймера/счетчика 1 // Регистр управления таймером/счетчиком TCCR1 состоит из // двух 8-разрядных регистров: TCCR1A и TCCR1B // СОМ1А1 (разряд 7) =1, СОМ1АО (разряд 6) = 0 - установка // режима совпадения ддя ШИМ. Теперь вывод 0С1 (РВЗ) // устанавливается в лог. О при совпадении TCNT1 и OCR1A, // когда TCNT1 увеличивается. Если же TCNT1 уменьшается, то // при совпадении TCNT1 и OCR1 на выводе 0С1 (РВЗ) // устанавливается лог. 1 (неинвертирующая ШИМ). // PWM10 (разряд 0) = 1, PWM11 (разряд 1) =1. Определяем // 10-разрядную ШИМ (1024 отсчетов от 0 до 1023), т.е. // значение счетного регистра TCNT1 будет в цикле изменяться // от 0 до 1023 и обратно от 1023 до 0. TCCR1A = 0x83; //0Ы0000011 // ICNC1 (разряд 7) =0, ICES1 (разряд 6) = 0 - регистр // захвата ICR1 не используем. // Разряд 3=0- регистр TCNT1 при совпадении TCNT1 и OCR1 //не обнуляется (в режиме ШИМ этот разряд не используют). // Разряды 0-2 - выбор источника счетных импульсов для // регистра TCNT1. Значениею 0Ь101 соответствует частота // микроконтроллера 4 МГц, т.е. каждые 1 / 4000000 = // 0,00000025 с значение счетного регистра TCNT1 будет // увеличиваться на единицу. TCCR1B = 0x01; //ObOOOOOOOl // Содержимое регистр OCR1 непрерывно сравнивается с текущим // значением таймера/счетчика 1 (TCNT1) OCR1 = OxOlFF; // 511 DDRD &= ~(1 « 0) & ~(1 « 1); // РЙО и PD1 - входы для // кнопок изменения коэффициента заполнения ШИМ PORTD |= (1 « 0) | (1 « 1); // Подключаем внутренние // нагрузочные сопротивления к выводам PD0 и PD1 DDRD &= ~(1 « 2) & ~(1 « 3); // PD2 и PD3 - входы для // кнопок изменения частоты ШИМ
134 Глава 7 PORTD |= (1 « 2) | (1 << 3); // Подключаем внутренние ' // нагрузочные сопротивления к выводам PD2 и PD3 TIMSK |= (1 « 7); sei(); // Разрешение прерывания по // переполнению таймера/счетчика 1 // Общее разрешение прерываний while (1) // Бесконечный цикл { // Изменение коэффициента заполнения if (-PIND & (1 « 0)) { OCR1 = OCR1 + 1; // Если на выводе PD0 - 0 В,... while (-PIND & (1 « } 0)); // Ожидаем отпускания кнопки if (-PIND & (1 « 1)) { OCR1 > OCR1 - 1; // Если на выводе PD1 +5 В while (-PIND & (1 « } 1)) ; // Ожидаем отпускания кнопки // Изменение частоты ШИМ if (-PIND & (1 « 2)) г // Если на выводе PD2 - 0 В tcnt_ = tent while (-PIND _ + !'• & (1 « 2)) ; // Ожидаем отпускания кнопки j if (-PIND & (1 f « 3)) // Если на выводе PD3 - 0 В t tent— = tent while (~PIND - & (1 « 3)) ; // Ожидаем отпускания кнопки Передача данных через UART с использованием буфера Схема соединений для этого примера показана на рис. 7.3. Про- грамма (листинг 7.15) передает строку “привет, это твой микроконтрол- лер =)” через приемопередатчик UART. При этом в ней используется буферный массив из 32 элементов, в котором накапливаются символы для передачи, если регистр данных UDR полон. Для обращения к адресу памяти используем указатель s без унарно- го оператора * (раскрытия ссылки). Для того чтобы извлечь содержимое
работаспрерываниями в AT90S2313 135 (в данном случае — символ) ячейки по адресу s, применяем унарный оператор раскрытия ссылки, т.е. *s. Когда регистр данных UART освобождается, возникает прерывание (вектор UART_UDRE_vect). В подпрограмме его обработки проверя- ем: если глобальная переменная UART_TxHead не равна глобальной переменной UART_TxTail (т.е. еще присутствуют символы для от- правки через UART), то записываем в регистр данных символ. В про- тивном случае отключаем прерывание. Рис. 7.3. Схема соединений для передачи данных через UART Программа, реализующая работу схемы, показанной на рис. 7.3, представлена в листинге 7.15. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на хЖ прилагаемом к книге компакт-диске в папке AT9OS2313\7.15 - передача дан- ных через UART с использованием буфера. ^include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте ^include <avr/interrupt.h> // Для работы с прерываниями
136 Глава? // Глобальная переменная, содержащая индекс элемента массива // UART_TxBuf[], в который записываем символ static volatile unsigned int UART_TxHead s 0; // Глобальная переменная, содержащая индекс элемента массива // UART_TxBuf[], который отправляем через UART static volatile unsigned int UART_TxTail=0; // Глобальный массив (буфер), содержащий символы, которые // необходимо отправить static volatile unsigned char UART_TxBuf[32]; // Подпрограмма обработки прерывания при очистке регистра UDR ISR(UART_UDRE_vect) { unsigned int tmptail; // Временная переменная if (UART_TxHead != UART_TxTail) // Если есть что-то для { // отправки... // Увеличиваем tmptail на единицу, чтобы отправить // следующий символ с индексом tmptail в массиве UART__TxBuf tmptail = (UART_TxTail + 1) & 31; UART_TxTail = tmptail; // Глобальной переменной 11 присваиваем индекс отправляемого элемента // в массиве UART_TxBuf, чтобы при следующем // вызове обработчика прерывания знать, что этот // элемент массива уже отправлен UDR = UART_TxBuf[tmptail]; // Записываем отправляемый // символ в регистр данных UDR } else // Если отправлять нечего, то отключаем прерывание { UCR &= ~(1 << 5); // Записываем 0 в разряд UDRE } } // Функция uart_putc принимает в качестве параметра символ // и записывает его в глобальный массив UART_TxBuf[] void uart_putc(unsigned char data) { unsigned int tmphead; // Временная переменная // Если UART_TxHead+l >31, то tmphead = 0, иначе tmphead // увеличится на единицу. Например, если (UART_TxHead+l) = 30 // (0Ы1110), то tmphead = 0Ы1110 & 0Ы1111 = 0Ы1110 (30). // Т.е. если (UART_TxHead+l) > 31, то tmphead будет равно 0, // потому что 0Ы00000 & 0Ы1111 = 0. , tmphead = (UART_TxHead + 1) & 31;
работа с прерываниями в AT90S2313 137 // Ожидаем, пока подпрограмма обработки прерывания отправит // символ с индексом UART_TxTail в массиве UART_TxBuf[], // чтобы в массив по этому индексу можно было записать новое // значение переменной while (tmphead == UART_TxTail) { ; // Ожидаем, когда в массиве UART_TxBuf[] появятся } // свободные ячейки UART_TxBuf[tmphead] ж data; // Записываем в массив символ UART_TxHead = tmphead; // Зписываем индекс // присвоенного элемента в глобальную переменную UCR |= (1 « 5); // Разряд UDRIE=1 - активизация прерывания // при очистке регистра данных } // Функция uartjDuts передает посимвольно содержимое всего // массива stroka в функцию uartjputc void uart_puts(char *s) // Указатель s - адрес нулевого { // элемента массива stroka while (*s) // До тех пор, пока есть какой-либо // символ в памяти по адресу s uart_putc(*s++); // Вызываем функцию отправки символа, // передав ей содержимое ячейки по адресу // s, и увеличив адрес s на 1 (переход // к следующему элементу массива). } void UART INITO { UCR |= (1 « 3) | (1 << 5); // Разрешаем работу // передатчика и активизируем прерывание при // очистке регистра ввода-вывода UDR. UBRR ж 0x17; // Скорость передачи 9600 бод при частоте // МК 3.6864 МГц } int main (void) { UART_INIT(); // Инициализация UART sei(); // Общее разрешение прерываний static char stroka[] = "привет, это твой микроконтроллер =)\г\п"; uart_puts(&stroka[0]); // Передаем адрес нулевого элемента // массива stroka while (1) { } // Бесконечный цикл
Микроконтроллер ATMEGA16 В этой части: ❖ Глава 8. Таймеры/счетчики ATmega16 ❖ Глава 9. Аналоговый компаратор и АЦП ATmega16 ❖ Глава 10. Интерфейсы передачи данных ATmegal 6 ❖ Глава 11. Использование ЖК-экрана
Глава 8 В этой главе мы рассмотрим методы работы с таймерами микрокон- троллера ATmegal6 и соответствующие программы на С. Таймер/счетчик 0 в режиме “Normal” В функции timer_counterO_INIT () инициализируется таймер/ счетчик 0. С помощью разрядов CS0-CS02 регистра TCCR0 задаем ко- эффициент предделителя 1 024, т.е. при частоте работы микроконтрол- лера 10 МГц счетчик TCNT0 будет увеличиваться на единицу за 1 024./ 10 МГц = 0,0001024 с. Таким образом, частота счетчика составляет 1 / 0,0001024 = 9765,625 Гц. Если без предделителя счетчик работал бы на частоте 10 МГц (10 миллионов приращений TCNT0 за секунду), то одно увеличение значе- ния счетного регистра TCNT0 длилось бы 1 / 10 МГц = 0,0000001 с. Те- перь же, с предделителем, на одно приращение TCNT0 уходит 1 024 такта микроконтроллера: 0,0000001 • 1 024 = 0,0001024 с. Таким образом, за одну секунду происходит 9765,625 отсчетов по 0,0001024 с каждая. С помощью разрядов WGM00 и WGM01 задаем режим “normal”, в котором таймер/счетчик работает как суммирующий счетчик (при ка- ждом импульсе тактового сигнала инкрементируется счетный регистр TCNT0). Поскольку таймер/счетчик 0 — восьмиразрядный, максималь- ное значение счетного регистра — 255. Таким образом, до его перепол- нения (перехода в 0) — 256 отсчетов. В бесконечном цикле основной программы выполняются четыре проверки содержимого счетного регистра TCNT0, на основании кото- рых инвертируется состояние (переход из 0 В к +5 В и наоборот) того Пли иного вывода микроконтроллера. Так, если значение счетного регистра TCNT0 равно нулю, то инвер- тируется состояние вывода PD0. Это будет происходить через каждые 252 отсчета TCNT0. Положительный импульс, как и отрицательный, Длится (256 - 4) -0,0001024 = 0,0258048 с (рис. 8.1).
140 Глава 8 Рис. 8.1. Осциллограмма импульсов Между изменением состояния соседних выводов проходит 63 отс- чета TCNT0, что составляет 63 -0,0001024 = 0,0064512 с. Поскольку одно увеличение TCNT0 на единицу происходит за 1 024 такта микроконтроллера, для использования условия, связанного со счетчиком TCNT0, в бесконечном цикле приходится идти на ухищре- ния. Если бы мы после выполнения условия TCNT0 == 0x00, не ин- крементировали счетчик на единицу, то данное условие было бы истин- ным не один раз, а на протяжении всех 1 024 тактов, пока TCNT0 не увеличилось на единицу. Для того чтобы вывод порта менял свое со- стояние на противоположенное один раз за 252 отсчета счетчика, мы после изменения состояния вывода микроконтроллера инкрементируем счетчик на единицу. Частота импульсов на выводах PD0-PD3 составляет F = 1 / период = 1 / (0,0258048 + 0,0258048) 19,38 Гц. Схема Схема соединений для данного примера показана на рис. 8.2. Примечание Перед “прошивкой” микроконтроллера, необходимо установить внешний источник частоты, иначе будет использован внутренний источник (рис. 8.3).
Таймеры/счетчики ATmega16 141 Рис. 8.2. Схема для исследования работы таймера/счетчика о в режиме
142 Глава 8 i I '' ' ~ ./6|| .EVEL . В rown-out deslectior? at VСО2. ? V 1111111111111111111®вЖ1Й1^ВЙ1В^ИВД№1И11ИИ!^И1»1^в1и11И11И111И111111111111И11и lint Pi С 0 sc. 1 MHz; Start-up time: 6 CK + 0 ms lint RC Osc. 1 MHz; Start-up time: 6 CK + 4 ms ..............[Int RC Osc. 1 MHz; Startup time: 6 CK * 64 ms; default value Рис. 8.3. Выбор внешнего источника частоты Программа Программа, реализующая работу схемы, показанной на рис. 8.2, представлена в листинге 8.1. е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegai6\8.01 - Таймер о в ре- жиме normal. #include <avr/io.h> // : ;v :..• '•: - . t ; m .., •: - -VV .; JVm/HVVC Заголовочный файл подключает определения ввода-вывода для устройства, используемого в проекте // Функция инициализации таймера/счетчика 0 void timer_counterO_INIT() { // Делитель с помощью разрядов CS00-CS02 задан как 1024, т.е. // на увеличение значения счетного регистра TCNT0 на единицу // уходит 1024 / 10 МГц = 0,0001024 с. TCCR0 |= (1 « 0) | (1 « 2); // CS00=l, CS02=l TCCR0 &= ~(1 « 1); // CS01=0 // Режим работы таймера/счетчика - "normal" TCCR0 &= ~(1 « 6) (1 « 3);// WGM00=0, WGM01=0 }
Таймеры/счетчики ATmega16 143 int main(void) timer_counterO_INIT (); DDRD = OxFF; PORTD = 0x00; // Инициализации таймера/счетчика 0 // Выводы порта D - выходы // На выводах порта D - 0 В while (1) { if (TCNT0 == 0x00) { PORTD = PORTD x (1 // Бесконечный цикл // Если счетный регистр содержит 0, << 0) ; // Инвертируем состояние PD0 TCNT0++; } if (TCNT0 == 0x40) { PORTD = PORTD A (1 // Увеличиваем счетный регистр на // единицу, чтобы при следующем // цикле состояние вывода не // инвертировалось обратно И 64 « 1); TCNT0++; } if (TCNT0 == 0x80) { PORTD = PORTD x (1 И 128 « 2) ; TCNT0++; } if (TCNT0 OxCO) { PORTD = PORTD x (1 И 192 « 3); TCNT0++ Таймер/счетчик 0 в режиме “СТС” В функции timer_counterO_INIT () инициализируется таймер/ счетчик 0. Разряды CS00-CS02 задают коэффициент делителя 1 024, т.е. Каждый 1024 такт микроконтроллера содержимое счетного регистра TCNT0 будет увеличиваться на единицу. С помощью разрядов WGM01 и WGM00 задан режим “СТС”, в ко- вром при равенстве содержимого регистра TCNT0 и регистра сравне- ния OCRO счетный регистр обнуляется. В нашем примере OCRO устано- вим равным 80, т.е. TCNT0 обнулится после 81 отсчетов (рис. 8.4).
144 Глава 8 Рис. 8.4. Количество отсчетов TCNT0 при OCRO, равном 80 Разряды СОМ01, СОМОО регистра TCCR0 определяют, что при ра- венстве содержимого регистров TCNT0 и OCRO будет инвертироваться состояние вывода ОСО (РВЗ) через каждые 81 920 циклов микрокон- троллера (рис. 8.5). 81920 циклов МК 81920 циклов МК вывод РВЗ ОСО Рис. 8.5. Счетчик TCNT0 и выход ОСО при OCRO, равном 80 Схема Схема соединений для данного примера показана на рис. 8.6. Вывод РВ7 микроконтроллера настроен как вход с подтягивающим сопротив- лением. К нему подключена кнопка, при нажатии которой содержимое регистра сравнения OCR0 увеличивается на единицу. Светодиод, под- ключенный к выводу РВ6, изменяет свое состояние на противополо- женное (для индикации изменений в OCR0). При увеличении или уменьшении содержимого OCR0 будет изме- няться частота на выводе ОСО (РВЗ) при неизменном коэффициенте за- полнения. Частота сигнала на выводе ОСО вычисляется по формуле: Foe = /clk_io /2 -N (1 + OCR), где N — коэффициент предделителя.
Таймеры/счетчики ATmega16 145 Рис. 8.6. Схема соединений для исследования режима “СТС” Соответственно, частота, при ОСО = 80 будет равна: Foe = 10 МГц /2 -1 024 -(1 + 80)« 60,28 Гц. Период при ОСО = 80 равен 1 / 60,28 = 0,0165888 с (или 16,5888 мс) (рис. 8.7). Время, за которое содержимое счетного регистра TCNT0 увеличи- вается на единицу: 1 024 / 10 МГц = 0,0001024 с. Длительность положительного и нулевого импульса на выводе РВЗ (ОСО) при ОСО = 80: 0,0001024 -(80 + 1) = 0,0082944 с.
146 Глава 8 Рис. 8.7. Длительность и период импульсов Для увеличение счетчика TCNT0 на единицу требуется 1 024 такта микроконтроллера. Отсюда, на инвертирование состояния вывода ОСО уйдет 1 024 -(80 + 1) = 82 944 такта микроконтроллера. Скважность и коэффициент заполнения при ОСО = 80: Скважность = период / длительность импульса = = 0,0165888/0,0082944 = 2; Коэффициент заполнения = длительность импульса / период = = 0,0082944 / 0,0165888 = 0,5. При изменении регистра OCR0 длительности положительного и ну- левого импульсов всегда равны. Соответственно, коэффициент заполне- ния и скважность не меняются. Режим “СТС” предназначен для генерирования импульсов с разной частотой при неизменной скважности и коэффициенте заполнения. Программа Программа, реализующая работу схемы, показанной на рис. 8.6, представлена в листинге 8.2. ЯЧк Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на Чь* прилагаемом к книге компакт-диске в папке ATmegai6\8.02 - таймер о в ре- жиме СТС.
Таймеры/счетчики ATmega16 147 ^include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <util/delay.h> // Для доступа к функциям циклов // задержки void timer_counterO_INIT() { // Делитель с помощью разрядов CS00-CS02 задан как 1024, т.е. // на увеличение значения счетного регистра TCNT0 на единицу // уходит 1024 / 10 МГц = 0,0001024 с. TCCR0 |= (1 « 0) |'(1 « 2); // CS00=l, CS02=l TCCR0 &= ~(1 « 1); // CS01=0 // Режим работы таймера/счетчика: СТС - сброс при совпадении TCCR0 &= ~(1 « 6); // WGM00=0 TCCR0 |= (1 « 3); // WGM01=l // Состояние вывода ОСО меняется на противоположенное при // равенстве содержимого регистров TCNT0 и OCR0 TCCR0 &= ~(1 « 5); // СОМ01=0 TCCR0 |= (1 « 4); } // СОМ00=1 int main(void) { OCR0 = 0x50; // В регистре сравнения - 80 tiiner_counterO_INIT (); // Инициализации таймера/счетчика DDRB |= (1 « 3); // Вывод РВЗ (ОСО) - выход DDRB &= -(1 « 7); // Вывод РВ7 - вход PORTB |= (1 « 7); // Вывод РВ7 - с подтягивающим // сопротивлением DDRB |= (1 « 6); // Вывод РВ6 - выход while (1) { // Бесконечный цикл if (-PINB & (1 « 7)) // Если напряжение на выводе // с подтягивающим сопротивлением // стало равным 0 В, т.е. кнопка // нажата, то... { // Ожидаем в цикле до тех пор, пока напряжение на выводе // равно 0, т.е. как только отпустят кнопку, произойдет // выход из цикла while (-PINB & (1 « 7)) _delay_loop_2(65535); _delay_loop_2(65535) ; // Пауза
148 Глава 8 PORTB "= (1 « 6); // Инвертируем состояние вывода РВ6 // (светодиод включится, если был // отключен, и наоборот) OCRO = OCRO + 1; // Увеличить содержимое регистра // сравнения (если будет 255, //и произойдет еще одно увеличение // то значение станет равным 0) Таймер/счетчик 0 в режиме “Fast PWM” В режиме “Fast PWM” счетный регистр работает как суммирующий счетчик. Состояние счетчика изменяется от 0 до 255, после чего счет- ный регистр сбрасывается, и цикл повторяется (рис. 8.8). Такой режим устанавливают с помощью разрядов WGM01, WGM00 регистра TCCR0. Рис. 8.8. Количество отсчетов TCNT0 Особенностью данного режима является двойная буферизация за- писи в регистр сравнения. Записываемое число сохраняется в буфере, а изменение регистра сравнения происходит в момент достижения счет- ного регистра максимального значения 255. Благодаря этому, исключа- ется появление несимметричных импульсов. Поведение вывода ОСО определяют с помощью разрядов СОМ01, СОМОО регистра TCCR0. На выводе будет уровень лог. О при TCNT0 = = OCRO и лог. 1 при TCNT0 = 0 (неинвертированная ШИМ) (рис. 8.9). Схема Схема соединений для данного примера показана на рис. 8.10. К вы- водам микроконтроллера РВ6 и РВ7 подключены кнопки SW0 и SW1 для изменения содержимого регистра сравнения OCRO.
Таймеры/счетчики ATmega16 149 V vv vv vv vv VV VV V 80 80 80 80 80 80 вывод РВЗ(ОСО) Рис. 8.9. Счетчик TCNT0 и выход ОСО при OCR0 = 80 I »i и 176 176 176 176 176 176 176 Рис. 8.10. Схема соединений для исследования режима “Fast PWM”
150 Г лава 8 Частота сигнала на выводе ОСО вычисляется по формуле: Foe =/clk_io IN -256, где N — коэффициент предделителя. Таким образом, Foe = 10 МГц/ (1 024 -256)^38,15 Гц. В таком случае, период равен (рис. 8.11) 1 /38,15 = 0,02621 с (26,21 мс). Рис. 8.11. Период следования импульсов Время, за которое значение счетного регистра TCNT0 увеличивает- ся на единицу: 1024 / 10 МГц = 0,0001024 с. Длительность положитель- ного импульса (при ОСО = 80): 80 -0,0001024 = 0,008192 с (рис. 8.12). Второй вариант расчета: (OCRO / ТОР+1) -Тшим = (80 / 255 + 1) -26,21 = 0,008192 с. Длительность нулевого импульса (при ОСО = 80): ((255 + 1) - 80) -0,0001024 = 176 -0,0001024 = 0,0180224 с. Режим “Fast PWM” предназначен для генерации сигнала с широтно- импульсной модуляцией при неизменной частоте сигнала.
Т'аймеры/счетчики ATmega16 151 Рис. 8.12. Длительность нулевого и положительного импульсов Программа Программа, реализующая работу схемы, показанной на рис. 8.10, представлена в листинге 8.3. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegai6\8.03 - таймер о в ре- жиме Fast PWM. ^include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте ^include cutil/delay.h> // Для доступа к функциям циклов // задержки // Функция инициализации таймера/счетчика 0 void timer counterO INITO { // Режим - "Fast PWM" TCCRO |= (1 « 6); . // WGM00=l TCCRO |= (1 « 3); // WGM01=l
152 Глава 8 //На выводе ОСО устанавливается лог. О при TCNT0 == OCRO, // лог. 1 при TCNT0 == О TCCR0 &= ~(1 « 4); // СОМ00=0 TCCR0 |= (1 « 5); // СОМ01=1 // Делитель с помощью разрядов CS00-CS02 задан как 1024, т.е. // на увеличение значения счетного регистра TCNT0 на единицу // уходит 1024 / 10 МГц = 0,0001024 с. TCCR0 |= (1 « 0) | (1 « 2); // CSOO=1, CS02=l TCCRO &= ~(1 « 1); // CS01=0 } int main (void) { OCRO = 0x50; // В регистре стравнения - 80 timer_counterO_INIT(); // Инициализация таймера/счетчика 0 DDRB |= (1 << 3) ; // Вывод РВЗ (ОСО) - выход // Кнопка увеличения OCR0 на 1 DDRB &= -(1 « 7); // Вывод РВ7 - вход PORTB |= (1 << 7); // Вывод РВ7 - с подтягивающим // сопротивлением // Кнопка уменьшения OCR0 на 1 DDRB &= -(1 « б); PORTB |= (1 « 6); //светодиод DDRB | = (1 « 5) ; PORTB &= -(1 « 5); // Вывод PB6 - вход // Вывод РВ6 - с подтягивающим // сопротивлением // Вывод РВ5 ~ выход //На выводе РВ5 - 0 В while (1) // Бесконечный цикл { if (-PINB & (1 « 7)) // Если напряжение на выводе РВ7=0, { // т.е. кнопка нажата, то... // Ожидаем в цикле до тех пор, пока напряжение на выводе // равно 0, т.е. как только отпустят кнопку, произойдет // выход из цикла while (-PINB & (1 « 7)) { _delay_loop_2(65535); } _delay_loop_2(65535) ; // Пауза PORTB '= (1 « 5); // Инвертируем состояние вывода РВ5 // (светодиод включится, если был // отключен, и наоборот) OCRO = OCRO + 1; } // Содержимое регистра сравнения + 1
Таймеры/счетчики ATmega16 153 if (-PINB & (1 « 6)) // Если напряжение на выводе РВ6=0, { //т.е. кнопка нажата, то... // Ожидаем в цикле до тех пор, пока напряжение на выводе // равно 0, т.е. как только отпустят кнопку, произойдет // выход из цикла while (-PINB & (1 « 6)) { _delay_loop_2(65535); } _delay_loop_2(65535); // Пауза PORTB (1 « 5); // Инвертируем состояние вывода РВ5 // (светодиод включится, если был // отключен, и наоборот) OCRO = OCRO - 1; // Содержимое регистра сравнения - 1 } } } Таймер/счетчик 0 в режиме “Phase Correct PWM” В режиме “Phase Correct PWM” счетный регистр работает как ре- версивный счетчик. Его состояние изменяется от 0 до 255, а затем — обратно до 0. Режим “Phase Correct PWM” устанавливается в программе с помощью разрядов WGM01 и WGM00 регистра TCCR0 в функции timer_counterO_INIT(). В ней же зададим значение регистра сравнения OCRO, равным 80, а также установим коэффициент делителя 1 024 с помощью разрядов CS00-CS02. Теперь значение счетного реги- стра TCNT0 будет увеличиваться на единицу каждый 1 024 цикл микро- контроллера. С помощью разрядов СОМ01 и СОМОО регистра TCCR0 устанавли- ваем поведение вывода ОСО. На нем установится уровень лог. 0 при прямом счете и уровень лог. 1 при обратном счете (неинвертированная ШИМ). Состояние вывода ОСО (РВЗ) меняется при равенстве содержи- мого регистров OCRO и TCNT0 в следующем отсчете счетного регистра (рис. 8.13). Схема Схема соединений для данного примера показана на рис. 8.14. Для изменения содержимого регистра сравнения OCRO будем использовать кнопки, подключенные к выводам РВ6 и РВ7. Следовательно, необхо- димо определить эти выводы как входы с подтягивающим сопротивле- нием. Для индикации нажатия любой из кнопок воспользуемся свето- диодом, подключенным к выводу РВ5 (определяем его как выход).
154 Глава 8 TCNTO=O TCNT0=255 •V V вывод РВЗОСО I 350 Il6o| 350 Il6o| 350 Il6o| 350 116o| 350 116o| 350 I । отсчетов | | отсчетов | | отсчетов | | отсчетов | | отсчетов | | отсчетов | Рис. 8.13. Счетчик TCNTO и выход ШИМ Рис. 8.14. Схема соединений для исследования режима “Phase Correct PWM” В бесконечном цикле проверяем состояние выводов РВ6 и РВ7. По- скольку они работают, как входы с подтягивающим сопротивлением, на них изначально присутствует +5 В (состояние лог. 1). Если на одном из
Таймеры/счетчики ATmega16 155 этих выводов состояние изменилось на лог. О, то увеличивается или уменьшается содержимое регистра OCRO. Частота сигнала на выводе ОСО вычисляется по формуле: Foe =/clk_io /510 -N, где N—коэффициент предделителя. Таким образом, Foe = 10 МГц/510-1 024 == 19,15 Гц. Отсюда, период составляет 1 / 19,15 = 0,05222 с. Положительный импульс будит длиться (79 + 81) -(1 024 / 10 МГц) = 0,016384 с. Нулевой импульс будит длиться ((255 + 255) - (79 + 81)) (1 024 /10 МГц) = 0,03584 с (рис. 8.15). Рис. 8.15. Длительность положительного и нулевого импульсов Количество отсчетов для положительного и нулевого импульсов по- казаны на рис. 8.16. Режим “Phase Correct PWM” предназначен для генерации сигнала с широтно-импульсной модуляцией при неизменной частоте. Особен- ность данного режима заключается в том, что изменение регистра OCR0 Происходит только при достижения максимального значения счетным Регистром TCNT0.
156 Глава 8 J 80 81 Ol » 254 255 254 80 79 I I 81920 82944 циклов МК циклов МК III 1 * *—Q 261120 440320 441344 | циклов МК циклов МК циклов МК вывод ОСО +5 Вольт 81 отсчет TCNTO 350 отсчетов TCNTO вывод ОСО в состоянии логического нуля вывод ОСО +5 Вольт 79 отсчетовТСМТО - । Рис. 8.16. Количество отсчетов TCNT0 для положительного и нулевого импульсов Программа Программа, реализующая работу схемы, показанной на рис. 8.14, представлена в листинге 8.4. ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\8.04 - Таймер 0 в ре- жиме Phase Correct PWM. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include cutil/delay.h> // Для доступа к функциям циклов // задержки // Функция инициализации таймера/счетчика 0 void timer_counterO_INIT() { // Делитель с помощью разрядов CS00-CS02 задан как 1024, т.е. // на увеличение значения счетного регистра TCNT0 на единицу // уходит 1024 / 10 МГц = 0,0001024 с. TCCR0 |= (1 « 0) | (1 « 2); // CS00=l, CS02=l TCCR0 &= ~(1 « 1); // CS01=0 // Режим - "Phase Correct PWM" TCCR0 |= (1 « 6); // WGM00=l TCCR0 &= -(1 « 3); // WGM01=0 // На выводе ОСО устанавливается лог. 0 при TCNT0 == OCRO, // лог. 1 при TCNT0 == 0 TCCR0 &= ~(1 « 4); // СОМ00=0 TCCR0 |= (1 « 5); // СОМ01=1 } int main(void)
Таймеры/счетчики ATmega16 157 • г£ТИНГ: Шй1ОЙ { OCRO = 0x50; // В регистре стравнения - 80 timer_counterO_INIT(); // Инициализация таймера/счетчика 0 DDRB |= (1 << 3); // Вывод РВЗ (ОСО) - выход // Кнопка увеличения OCRO на 1 DDRB &= ~(1 « 7) ; 7); // Вывод РВ7 - вход // Вывод РВ7 - с подтягивающим // сопротивлением PORTB |= (1 « // Кнопка уменьшения DDRB &= ~(1 « 6); PORTB |= (1 « б); DDRB |= (1 « 5); while (1) { OCRO на 1 // Вывод РВ6 - вход // Вывод РВ6 - с подтягивающим // сопротивлением // Вывод РВ5 - выход (светодиод) // Бесконечный цикл if (-PINB { & (1 « 7)) // Если напряжение на выводе РВ7=0, // т.е. кнопка нажата, то... // Ожидаем в цикле до тех пор, пока напряжение на выводе // равно 0, т.е. как только отпустят кнопку, произойдет // выход из цикла while (-PINB & (1 « 7) ) { _delay_loop_2(65535); } _delay_loop_2(65535) / // Пауза PORTB (1 « 5); // Инвертируем состояние вывода РВ5 и (светодиод включится, если был // отключен, и наоборот) } OCRO = OCRO + 1; // Содержимое регистра сравнения + 1 if : (-PINB & (1 « 6)) // Если напряжение на выводе РВ6=0, { // т.е. кнопка нажата, то... // Ожидаем в цикле до тех пор, пока напряжение на выводе // равно 0, т.е. как только отпустят кнопку, произойдет // выкод из цикла while (-PINB & (1 « 6)) { _delay_loop_2(65535); } _delay_loop_2(65535); // Пауза PORTB (1 « 5); // Инвертируем состояние вывода РВ5 // (светодиод включится, если был // отключен, и наоборот) OCRO = OCRO - 1; // Содержимое регистра сравнения - 1 } }
158 Глава 8 Таймер/счетчик 1 в режиме “Normal” Микроконтроллер работает на частоте 10 МГц, таймер/счетчик 1 — через предделитель с коэффициентом 1 024. Таким образом, его частота составляет 10 МГц / 1 024 = 9 765,625 Гц, т.е. TCNT1 за 1 секунду мо- жет сделать 9 765 отсчетов. Значение счетного регистра увеличивается на единицу через каждые 1 024 / 10 МГц = 0,0001024 с. Поскольку счетный регистр TCNT1 — 16-рязрядный, максимальное значение перед его переполнением (переходом в 0) — 65 535. После ожидания 1 024 тактов микроконтроллера при TCNT1, равном 65 535, счетчик сделает 65 536 отсчетов. Следовательно, для полного прохода счетчиком от 0 до 65 535 и сброса в 0 требуется 0,0001024 • 65 536 = = 6,7108864 с (рис. 8.17). Рис. 8.17. Длительность сигнала В бесконечном цикле значение счетного регистра TCNT1 проверя- ется на его принадлежность одному из четырех диапазонов. На основа- нии этого инвертируется состояние того или иного вывода микрокон- троллера. Условия заданы таким образом, чтобы между сменами состоя- ния выводов проходило 9 765 отсчетов счетчика, т.е. 9765 • 0,0001024 = 0,999936 с (рис. 8.18).
Т аймеры/счетчики ATmega16 159 Рис. 8.18. Время между изменениями состояния выводов микроконтроллера После инвертирования состояния вывода вызывается функция за- держки _delay_loop_2 (), один цикл которой равен четырем циклам микроконтроллера. Она необходима потому, что одно приращение зна- чения TCNT1 происходит за 1 024 такта, и условие TCNT == Значение выполнялось бы на протяжении остальных тактов. Схема и программа Схема соединений для данного примера представлена на рис. 8.19, а программа, реализующая ее работу, — в листинге 8.5. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\8.05 - Таймер 1 в ре- жиме Normal. ^include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте ^include <util/delay.h> // Для доступа к функциям циклов // задержки // Функция инициализации таймера/счетчика 1 void timer_counterl_INIT ()
160 Глава 8 { // Делитель с помощью разрядов CS10-CS12 задан как 1024, т.е. // на увеличение значения счетного регистра TCNT1 на единицу // уходит 1024 / 10 МГц = 0,0001024 с. TCCR1B |= (1 « 0) | (1 « 2); // CS10=l, CS12=1 TCCR1B &= ~(1 « 1); // CS11=O // Режим - "Normal" TCCR1A &= ~(1 « 1) & ~(1 « 0); // WGMll=0, WGM10=0 TCCR1B &= ~(1 « 4) & ~(1 « 3); // WGM13=0, WGM12=0 } int main(void) { DDRD |= OxOF; // Выводы PD0-PD3 - выходы PORTD &= ~0x0F; // Подаем 0 В на выводы PD0-PD3 timer_counterl_INIT(); // Инициализация таймера/счетчика 1 while (1) // Бесконечный цикл { if (TCNT1 == 0x0000) // Если счетный регистр равен 0 { PORTD = PORTD А (1 << 0); // Инвертируем вывод PD0 _delay_loop_2(256); // Задержка на 256*4=1024 такта МК } if (TCNT1 == 0x2625) // Если счетный регистр равен 9765 { PORTD = PORTD А (1 << 1) ; // Инвертируем вывод PD1 _delау_1оор_2(256); } if (TCNT1 == 0х4С4А) // Если счетный регистр равен 19530 { PORTD = PORTD А (1 << 2); // Инвертировать вывод PD2 _delау_1оор_2(256); } if (TCNT1 == 0x726F) // Если счетный регистр равен 29295 { PORTD = PORTD А (1 << 3) ; . // Инвертируем вывод PD3 _delay_loop_2(256); } } 1
Таймеры/счетчики ATmega16 161 Рис. 8.19. Схема для исследования режима “Normal” Таймер/счетчик 1 в режиме “Normal” и с регистром сравнения В данном примере мы используем два блока сравнения. При равен- стве значения счетного регистра TCNT1 и содержимого регистра срав- нения OCR1A или OCR1B изменяется состояние вывода микроконтрол- лера ОС1А (PD5) или ОС1В (PD4) соответственно. Каким образом тот или иной вывод будет изменять свое состояние, мы задаем с помощью разрядов СОМ1А1-СОМ1АО для вывода ОС1А и СОМ 1В 1-СОМ 1 ВО Для вывода ОС 1 В. С помощью регистра управления мы определяем установку лог. 1 на выводе ОС1В (PD4) при равенстве TCNT1 и OCR1B, и инвертирова- ние состояние вывода ОС1А (PD5) при равенстве TCNT1 и OCR1 А. Микроконтроллер работает на частоте 10 МГц, таймер/счетчик 1 — Через предделитель с коэффициентом 256. Следовательно, содержимое счетного регистра будет увеличиваться на единицу, через каждые 256 / Ю МГц = 0,0000256 с. Вывод ОС1А первый раз изменит свое состояние на лог. 1 при ра- венстве TCNT1 и OCR1A через 40000 • 0,0000256 = 1,024 с (рис. 8.20).
162 Глава 8 Поскольку мы задали для данного вывода инвертирование, в дальней- шем при равенстве TCNT1 и OCR1A он будит изменять свое состояние через 65 536 • 0,0000256 = 1,6777216 с. Рис. 8.20. Вывод ОС1А Вывод ОС1В (рис. 8.21) установится в состояние лог. 1 через 20000- 0,0000256 = 0,512 с. Рис. 8.21. Вывод ОС1В
Таймеры/счетчики ATmega16 163 Схема и программа Схема соединений для данного примера представлена на рис. 8.22, а программа, реализующая ее работу, — в листинге 8.6. Рис. 8.22. Схема для исследования режима “Normal” с регистром сравнения
164 Глава 8 Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\8 . Об - Таймер 1 в ре- жиме Normal и регистром сравнения. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте // Функция инициализации таймера/счетчика 1 void timer_counterl_INIT() { // Режим - ’’Normal" TCCR1A &= ~(1 « 1) & ~(1 « 0); // WGM11=O, WGM10=0 TCCR1B &= ~(1 « 4) & ~(1 « 3); // WGM13=0, WGM12=0 // При совпадении содержимого счетного регистра TCNT1 // и регистра сравнения OCR1A вывод ОС1А (PD5) // переключается (инвертирование состояния). TCCR1A &= ~(1 « 7); // СОМ1А1=0 TCCR1A |= (1 « 6); // СОМ1АО=1 // При совпадении содержимого счетного регистра TCNT1 // и регистра сравнения OCR1B вывод ОС1В (PD4) // устанавливается в лог. 1. TCCR1A |= (1 « 5) | (1 « 4); // СОМ1В1=1 и СОМ1ВО=1 } int main(void) { // Выводы блоков сравнения DDRD |= 0x30; // Выводы PD4 (ОС1В) и PD5 (ОС1А) - выходы PORTD &= -0x30; // Подаем 0 В на PD4 (ОС1В) и PD5 (ОС1А) // Вывод PD7 сигнализирует о старте/останове таймера/счетчика DDRD |= (1 << 7); // Вывод PD7 - выход PORTD &= -(1 « 7); // Подаем 0 В на PD7 // Кнопки SW0 и SW1 DDRB &= -(1 « 0) & -(1 « 1); // РВО и РВ1 - входы PORTB |= (1 « 0) | (1 << 1); // нагружаем выводы РВО и РВ1 // подтягивающим сопротивлением OCR1A = 0х9С40; // Регистр сравнения канала А =40000 OCR1B = 0х4Е20; // Регистр сравнения канала В =20000 timer_counterl_INIT(); // Инициализация таймера/счетчика 1
Таймеры/счетчики ATmega16 165 while (1) // Бесконечный цикл { if (0 as (PINB & (1 « 1))) // Кнопка остановки таймера { PORTD &= ~(1 << 7); // Гасим светодиод D3 на выводе PD7 И CS11=O, CS1O=O, CS12=0 TCCR1B &= -(1 « 1) & ~(1 « 0) & ~(1 « 2); } if (0 == (PINB & (1 « 0))) // Кнопка запуска таймера { PORTD |= (1 « 7); // Включаем светодиод на выводе PD7 // Делитель с помощью разрядов CS10-CS12 задан как 256, II т.е. на увеличение значения счетного регистра TCNT1 на // единицу уходит 256 / 10 МГц = 0,0000256 с. TCCR1B |= (1 « 2); // CS12=1 TCCR1B &= -(1 « 1) & ~(1 « 0); // CS11=O, CS10=0 } } } Таймер/счетчик 1 в режиме “СТС” При работе в режиме СТС (сброс при совпадении) содержимое счетного регистра TCNT1 увеличивается до максимального значения, определенного в регистре OCR1А или ICR1, а затем сбрасывается в 0. Режим работы таймера/счетчика 1 выбирается с помощью разрядов WGM10-WGM13 регистров TCCR1B/TCCR1A (мы зададим сброс при совпадении TCNT и регистра OCR1A). Для вывода ОС1А при равенстве TCNT1 и OCR1A с помощью раз- рядов СОМ1А1 и СОМ 1 АО регистра TCCR1A определяем инвертирова- ние состояния. Частота сигнала на выводе ОС1 А: /ocn=/cikj/o/ 2А(1 + X) = 10 МГц / 2 • 256 • (1 + 65 035) * 0,3 Гц. Период = 1 / 0,3 = 3,3 с (рис. 8.23). Длительность одного импульса (положительного и нулевого) = 65 035 + 1 • 0,0000256 = 1,6649216 с (рис. 8.24). Схема и программа Схема соединений для данного примера представлена на рис. 8.25, а программа, реализующая ее работу, — в листинге 8.7.
166 Глава 8 ' V. вв 1Ц ВИ Рис. 8.23. Период сигнала на выводе ОС1А Рис. 8.24. Длительность импульса на выводе ОС1А
Таймеры/счетчики ATmega16 167 Рис. 8.25. Схема для исследования режима “СТС1
168 Глава 8 ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\8.07 - Таймер 1 в ре- жиме СТС. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте // Функция инициализации таймера/счетчика 1 void timer_counterl_INIT() { // Делитель с помощью разрядов CS10-CS12 задан как 256, // т.е. на увеличение значения счетного регистра TCNT1 на // единицу уходит 256 / 10 МГц = 0,0000256 с. TCCR1B |= (1 « 2); // CS12=1 TCCR1B &= ~(1 « 1) & ~(1 « 0); // CS11=O, CS10=0 // Режим - "СТС" (TCNT1 сбрасывается при равенстве с OCR1A) TCCR1B |= (1 « 3); // WGM12=1 // При совпадении содержимого счетного регистра TCNT1 // и регистра сравнения OCR1A вывод ОС1А (PD5) // переключается (инвертирование состояния). TCCR1A |= (1 « 6); // СОМ1А0=1 TCCR1A &= -(1 « 7); // СОМ1А1=0 } int main(void) { DDRD = OxFF; // Выводы порта D - выходы PORTD = 0x00; // Подаем на порт DOB OCR1A = OxFEOB; // Регистру сравнения присваиваем 65035 timer_counterl_INIT(); // Инициализации таймера/счетчика 1 while (1) { } // Бесконечный цикл } Таймер/счетчик 1 в режиме “Fast PWM” Режим быстродействующей ШИМ с разрешением 10 бит устанав- ливаем с помощью разрядов WGMXX регистров TCCR1A и TCCR1B- Максимальным значением счетного регистра перед его переходом в О будет 1 023 (рис. 8.26). С помощью разрядов СОМ1А1 и СОМ 1 АО регистра TCCR1A опре- деляем для вывода ОС1А неинвертированный ШИМ-сигнал. На нем бу-
Таймеры/счетчики ATmega16 169 дет установлен лог. О при совпадении содержимого TCNT1 и регистра сравнения OCR1A, а лог. 1 — при переходе TCNT1 с максимального своего значения (1 023) в ноль (рис. 8.27). выводОС1А+5 Вольт 425 подсчетов TCNT1 599 подсчетов TCNT1 выводОС1А в состоянии логического нуля 1023+1 Рис. 8.26. Количество отсчетов TCNT1 при 10-разряд ной ШИМ вывод PD5(OC1 А) 599 425 599 425 599 425 599 425 599 425 599 425 599 Рис. 8.27. Выход ОС1А при OCR1А = 425 С помощью разрядов CS10-CS12 регистра TCCR1B выбираем для предделителя таймера/счетчика 1 коэффициент 256. Таким образом, для увеличения значения TCNT1 на единицу потребуется 256 / 10 МГц = = 0,0000256 с. Частота генерируемого сигнала:
170 Глава 8 где ТОР — максимальное значение счетного регистра (1023); N— ко- эффициент предделителя (256); faij/o — частота работы микроконтрол- лера (10 МГц). Таким образом, частота сигнала на выводе ОС1А (PD5) составляет 10 МГц / (256 • (1 + 1 023)) 38,147 Гц. Период = 1 / 38,147 s 0,02621 с (или 26,21 мс) (рис. 8.28). Рис. 8.28. Период сигнала на выводе ОС1А Длительность положительного импульса: 0,0000256-425 = 0,01088 с. Длительность нулевого импульса: (1024^425) • 0,0000256 = 0,0153344 с (рис. 8.29). Схема и программа Схема соединений для данного примера представлена на рис. 8.30, а программа, реализующая ее работу, — в листинге 8.8. е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\8.08 - Таймер 1 в ре- жиме Fast PWM. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте
'1'аймеры/счетчики ATmega16 171 у/ Функция инициализации таймера/счетчика 1 void timer_counterl_INIT() { // Режим - "Fast PWM" (10 бит, максимальное значение счетчика // TCNT1 - 1023 TCCR1A |= (1 « 1) | (1 « 0); // WGM11=1 и WGM10=l TCCR1B |= (1 « 3); // WGM12=1 // На выводе ОС1А (PD5) будет установлен лог. 0 при // TCNT1==OCR1A и лог. 1 при TCNT1==O TCCR1A |= (1 « 7); // СОМ1А1=1 // Делитель с помощью разрядов CS10-CS12 задан как 256, // т.е. на увеличение значения счетного регистра TCNT1 на // единицу уходит 256 / 10 МГц = 0,0000256 с. TCCR1B |= (1 « 2); // CS12=1 TCCR1B &= ~(1 « 1) & ~(1 « 0); // CS11=O, CS10=0 } int main(void) { DDRD = OxFF; // Выводы порта D - выходы PORTD = 0x00; // Подаем на порт DOB OCR1A = 0x01A9; // Регистру сравнения присваиваем 425 timer_counterl_INIT(); // Инициализация таймера/счетчика 1 while (1) { } // Бесконечный цикл } Рис. 8.29. Длительность положительного и нулевого импульсов на выводе ОС1А
172 Глава 8 Рис. 8.30. Схема соединений для исследования режима “Fast PWM”
Таймеры/счетчики ATmega16 173 Таймер/счетчик 1 в режиме “Phase Correct PWM” Режим “Phase Correct PWM” (10 бит) устанавливают с помощью разрядов WGMXX регистров TCCR1A и TCCR1B. В данном режиме счетчик считает от 0 до максимального значения ТОР (при 10 битах это 1023), а затем обратно до 0. Если в такой конфигурации изменить со- держимое регистра сравнения, то его обновление произойдет не в мо- мент изменения, а только при достижении счетным регистром своего максимального значения. С помощью разрядов СОМ1А1 и СОМ1АО регистра TCCR1A выби- раем для вывода ОС1А неинвертированный ШИМ-сигнал. При совпа- дении содержимого счетного регистра и регистра сравнения на выводе ОС1А устанавливается лог. 0 при прямом счете и лог. 1 при обратном (рис. 8.31). Рис. 8.31. Счетчик TCNT1 и выход ШИМ С помощью разрядов CS10-CS12 регистра TCCR1B выбираем ко- эффициент предделителя 256. Это значит, что при частоте 10 МГц на увеличение значения счетного регистра будет уходить 256 / 10 МГц = = 0,0000256 с. Частота сигнала на выводе ОС1А: еде ТОР — модуль счета (1 023); N— коэффициент предделителя (256); /ciki/o — частота работы микроконтроллера. Таким образом, Уосп = Ю МГц / (2 • 256 • 1023) » 19,09 Гц; а период составляет 1 / foca = 0,05237 с (или 52,37 мс) (рис. 8.32). Длительность положительного импульса: (43 + 41) • (256 / 10 МГц) 58 0,0021504 с. Длительность нулевого импульса: ((1023 + 1023) - (43 + 41)) • (1024 / 10 МГц) = 0,0502272 с (рис. 8.33).
174 Глава 8 Рис. 8.32. Частота и период ШИМ-сигнала Рис. 8.33. Длительность положительного и нулевого импульсов Количество отсчетов TCNT1 показано на рис. 8.34.
Таймеры/счетчики ATmega16 175 I 1 42 43 Ю22 1022 42 41 I OL------1------------1--------•----------------------1-----1---1--------------------1-------•---------------------IO I 256 Ю752 11008 261888 513024 513280 I циклов МК циклов МК циклов МК циклов МК циклов МК циклов MK выводОС1А+5 Вольт 43 подсчета TCNT1 1962 подсчетов TCNT1 вывод OC1А в состоянии логического нуля вывод ОС1А+5 Вольт 41 подсчет TCNT1 Рис. 8.34. Количество отсчетов TCNT1 для положительного и нулевого импульсов Схема и программа Схема соединений для данного примера представлена на рис. 8.35, а программа, реализующая ее работу, — в листинге 8.9. Рис. 8.35. Схема соединений для исследования режима “Phase Correct PWM” е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\8.09 - Таймер 1 в ре- жиме Phase Correct PWM. ^include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, используемого в проекте
176 Глава 8 // Функция инициализации таймера/счетчика 1 void timer_counterl_INIT() { // Режим - "Phase Correct PWM" (10 бит, максимальное значение // счетчика TCNT1 - 1023 TCCR1A |= (1 « 1) | (1 « 0); // WGM11=1 и WGM10=l TCCR1B &= ~(1 « 4) & ~(1 « 3); // WGM13=0 и WGM12=0 // При TCNT1==OCR1A на выводе ОС1А (PD5) будет установлен // лог. 0 при прямом счете и лог. 1 при обратном счете TCCR1A |= (1 « 7); // СОМ1А1=1 TCCR1A &= ~(1 « 6); // СОМ1А0=0 // Делитель с помощью разрядов CS10-CS12 задан как 256, // т.е. на увеличение значения счетного регистра TCNT1 на // единицу уходит 256 / 10 МГц = 0,0000256 с. TCCR1B |= (1 « 2); // CS12=1 TCCR1B &= -(1 « 1) & ~(1 « 0); // CS11=O, CS10=0 } int main(void) { DDRD = OxFF; // Выводы порта D - выходы PORTD = 0x00; // Подаем на порт DOB OCR1A = 0x002A; // Регистру сравнения присваиваем 42 timer_counterl_INIT(); // Инициализация таймера/счетчика 1 while (1) { } // Бесконечный цикл } Изменение частоты и коэффициента заполнения С помощью разрядов WGMXX устанавливаем режим 10 (согласно спецификации ATmegal6) работы таймера/счетчика 1 (не фиксирован- ное разрешение ШИМ). Данный пример отличается от предыдущего возможностью изме- нять значение регистра сравнения и максимальное значение счетного регистра. Для этого служат две кнопки (рис. 8.36): • SW0 — изменение регистра сравнения OCR1A, что соответственно корректирует скважность ШИМ-сигнала; • SW1 — изменение регистра захвата ICR1 (в режиме 10 таймера/ счетчика 1 задает разрядность счетного регистра), что корректирует частоту ШИМ-сигнала. Предельное значение счетного регистра определяет частоту ШИМ, а значение OCR1А — скважность (рис. 8.37).
таймеры/счетчики ATmega16 Рис. 8.36. С помощью двух кнопок можно корректировать скважность и частоту ШИМ-сигнала И зменение ре гистра сра в нени я и зменение модул я счетно го ре гистра происходит при происходит при ТОР(ТCNT1) минимальном е го значении Рис. 8.37. Формирование ШИМ-сигнала При TOP(TCNT1) - 755 и коэффициенте предделителя 256 частота ШИМ будет равна:Уосп - Ю МГц / (2 • 256 • 755) = 25,87 Гц, а значит пе-
178 Глава 8 риод следования импульсов на выводе ОС1А составляет 1 / 25,87 == = 0,03865 с (или 38,65 мс) (рис. 8.38). Рис. 8.38. Период следования импульсов на выводе ОС1А Программа, реализующая работу схемы, показанной на рис. 8.36, представлена в листинге 8.10. ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\8.10 - Изменение час- тоты и коээфициента заполнения. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, используемого в проекте #include <util/delay.h> // Для доступа к функциям циклов // задержки // Функция инициализации таймера/счетчика 1 void timer_counterl_INIT() { // Режим - "Phase Correct PWM" (режим 10, максимальное // значение счетчика задается регистром ICR1 TCCR1A |= (1 « 1); // WGM11=1 TCCR1B |= (1 « 4); // WGM13=1
Таймеры/счетчики ATmega16 179 II При TCNT1==OCR1A на выводе ОС1А (PD5) будет установлен // лог. О при прямом счете и лог. 1 при обратном счете TCCR1A |= (1 « 7); // СОМ1А1=1 TCCR1A &= -(1 « б); // СОМ1А0=0 // Делитель с помощью разрядов CS10-CS12 задан как 256, // т.е. на увеличение значения счетного регистра TCNT1 на // единицу уходит 256 / 10 МГц = 0,0000256 с. TCCR1B |= (1 « 2); // CS12=1 TCCR1B &= -(1 « 1) & ~(1 « 0); // CS11=O, CS10=0 } int main (void) { DDRD = OxFF; // Выводы порта D - выходы PORTD « 0x00; // Подаем на порт DOB ICR1 « 0x02F3; // Регистру захвата присваиваем 755 OCR1A = 0x002A; // Регистру сравнения присваиваем 42 timer_counter1_INIT(); // Инициализация таймера/счетчика 1 // Коррекция скважности - кнопка изменения OCR1A DDRB &= -(1 « 7); // Вывод PB7 - вход // Вывод РВ7 - с подтягивающим PORTB |= (1 « 7); // сопротивлением // Коррекция частоты - кнопка изменения ICR1 DDRB &= ~(1 « 6); и Вывод РВ6 - вход PORTB |= (1 « 6)} и Вывод РВ6 - с подтягивающим и сопротивлением while (1) f и Бесконечный цикл t if (-PINB & (1 « 7)) // Если напряжение на РВ7 = 0 В { // т.е. кнопка нажата // Ожидаем в цикле до тех пор, пока напряжение на выводе // равно 0, т.е. как только отпустят кнопку, произойдет // выход из цикла while (-PINB & (1 « 7)) { _delay_loop_2(65535); } _delay_loop_2(65535) ; // Пауза OCR1A = OCR1A +1; // Увеличиваем регистр сравнения // Если его значение больше максимального для счетного // регистра, обнуляем регистр сравнения. if (OCR1A > 755) OCR1A = 0; } if (-PINB & (1 « 6)) // Если напряжение на РВ6 = 0 В, { //т.е. кнопка нажата
180 Глава 8 . 111 1111 **» // Ожидаем в цикле до тех пор, пока напряжение на выводе /7 равно 0, т.е. как только отпустят кнопку, произойдет // выход из цикла while (-PINB & (1 « 6)) { _delay_loop_2(65535); } delay_loop_2(65535); // Пауза ICR1 = ICR1 +1; // Увеличиваем регистр захвата } } } Таймер/счетчик 1 в режиме “Phase and Frequency Correct PWM” Режим ШИМ с точной фазой и частотой — восьмой. Его задают с помощью разрядов WGMXX регистров TCCR1A и TCCR1B. В данном режиме максимальное значение счетчика TCNT1 определяется регист- ром захвата ICR1. Его отличие от “Phase Correct PWM” заключается в том, что при изменении разрядности модуля счета не появляются не- симметричные импульсы, поскольку обновление регистра сравнения происходит при нулевом значении TCNT1, в то время как в режиме “ Phase Correct PWM” изменение происходило при максимальном значе- нии TCNT1. С помощью разрядов СОМ1А1 и СОМ 1 АО регистра TCCR1A уста- навливаем для вывода ОС1А неинвертированный ШИМ-сигнал. При совпадении значений счетного регистра и регистра сравнения на выводе ОС1А устанавливается лог. 0 при прямом счете и лог. 1 при обратном. С помощью разрядов CS10-CS12 регистра TCCR1B выбираем ко- эффициент предделителя 256. Это значит, что при частоте 10 МГц на увеличение значения счетного регистра будет уходить 256 / 10 МГц ~ = 0,0000256 с. Частота сигнала на выводе ОС1А: " 2 • if где ТОР — модуль счета (755); N— коэффициент предделителя (256); Ус1н/о — частота работы микроконтроллера. Таким образом,Уосп = Ю МГц / (2 • 256 • 755) ~ 25,87 Гц; а период составляет 1 /уЬсп= 0,03866 с (или 38,66 мс). Длительность положительного импульса: 0,0000256 • (555 • 2) = 0,028416 с.
Таймеры/счетчики ATmega16 181 Длительность нулевого импульса (рис. 8.39): 0,0000256 • ((755 + 755) - (555 • 2)) = 0,01024 с. Рис. 8.39. Длительность положительного и нулевого импульсов на выводе ОС1А Схема и программа Схема соединений для данного примера представлена на рис. 8.40, а программа, реализующая ее работу, — в листинге 8.11. «оВ Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\8.11 - Таймер 1 в ре- жиме Phase and Frequency Correct PWM. ^include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, используемого в проекте // Функция инициализации таймера/счетчика 1 void timer counterl INITO { // Режим - "Phase and Frequency Correct PWM" (максимальное // максимальное значение для счетного регистра задается // регистром захвата ICR1 TCCR1B |= (1 « 4); // WGM13=1
182 Глава 8 < ста Н Г 8.11; О K0.HH S Н И0.0 ШЙЙ® ЙййЩЙ? cSSЗЙИЯЙИ TCCR1B &= ~ (1 « 3); // WGM12=0 TCCR1A &= ~(1 « 1) & ~(1 « 0); // WGM11=O и WGM10=0 // При TCNT1==OCR1A на выводе ОС1А (PD5) будет установлен // лог. 0 при прямом счете и лог. 1 при обратном счете TCCR1A |= (1 « 7); // СОМ1А1=1 TCCR1A &= ~(1 « 6); // СОМ1А0=0 // Делитель с помощью разрядов CS10-CS12 задан как 256, // т.е. на увеличение значения счетного регистра TCNT1 на // единицу уходит 256 / 10 МГц = 0,0000256 с. TCCR1B |= (1 « 2); // CS12=1 TCCR1B &= -(1 « 1) & -(1 « 0); // CS11=O, CS10=0 } int main(void) { DDRD = OxFF; // Выводы порта D - выходы PORTD = 0x00; // Подаем на порт DOB ICR1 = 0x02F3; // Регистру захвата присваиваем 755 OCR1A = 0x022В; // Регистру сравнения присваиваем 555 timer_counterl_INIT(); // Инициализация таймера/счетчика 1 while (1) { } // Бесконечный цикл } Рис. 8.40. Схема соединений для режима “Phase and Frequency Correct PWM”
Таймеры/счетчики ATmega16 183 Изменение частоты и коэффициента заполнения Этот пример отличается от предыдущего наличием кнопок SW0, SW1 для изменения модуля счета и содержимого регистра сравнения. Поскольку регистр сравнения и модуль счета обновляются одновремен- но, исключается возможность появления несимметричных импульсов (рис. 8.41). вывод PD5 ОС1А и Изменение регистра сравнения происходит при BOTTOM (TCNT1) Изменение модуля счетного регистра происходит при минимальном его значении Рис. 8.41. Формирование ШИМ-сигнала в режиме “Phase and Frequency Correct PWM” Схема соединений для данного примера показана на рис. 8.42, а со- ответствующая программа — в листинге 8.12. ЙЙЬ Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на tigs? прилагаемом к книге компакт-диске в папке ATmegal6\8.12 - Изменение час- тоты и коээфициента заполнения. ttinclude <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте ttinclude <util/delay.h> // Для доступа к функциям циклов // задержки // Функция инициализации -таймера/счетчика 1 void timer counterl INITO { // Делитель с помощью разрядов CS10-CS12 задан как 256, // т.е. на увеличение значения счетного регистра TCNT1 на // единицу уходит 256 / 10 МГц = 0,0000256 с. TCCR1B |= (1 « 2); // CS12=1 TCCR1B &= ~(1 « 1) & ~(1 « 0); // CS11=O, CS10=0
184 Глава 8 ... ..*.... // Режим - "Phase and Frequency Correct PWM" (режим 8, // максимальное значение счетчика задается регистром ICR1 TCCR1B |= (1 « 4)} // WGM13=1 TCCR1B &= ~ (1 « 3); // WGM12=0 TCCR1A &= ~(1 « 1) & ~(1 « 0)1 // WGM11=O и WGM10=0 // При TCNT1==OCR1A на выводе 0С1А (PD5) будет установлен // лог. О при прямом счете и лог. 1 при обратном счете TCCR1A |= (1 « 7); // СОМ1А1=1 TCCR1A &= ~(1 « 6); // СОМ1А0=0 } int main(void) { DDRD = OxFF; // Выводы порта D - выходы PORTD = 0x00; // Подаем на порт DOB ICR1 = 0x02F3; // Регистру захвата присваиваем 755 OCR1A = 0x022В; // Регистру сравнения присваиваем 555 timer_counterl_INIT(); // Инициализация таймера/счетчика 1 // Коррекция скважности - кнопка изменения OCR1A DDRB &= ~(1 « 7); // Вывод РВ7 - вход PORTB |= (1 « 7); // // Вывод РВ7 - с подтягивающим сопротивлением // Коррекция частоты - кнопка изменения ICR1 DDRB &= -(1 « б) ; // Вывод РВ6 - вход PORTB |= (1 « б); // Вывод РВ6 - с подтягивающим и сопротивлением while (1) f // Бесконечный цикл t if (-PINB & (1 « 7)) // Если напряжение на РВ7 = 0 В { // т.е. кнопка нажата // Ожидаем в цикле до тех пор, пока напряжение на выводе // равно 0, т.е. как только отпустят кнопку, произойдет // выход из цикла while (-PINB & (1 « 7)) { _delay_loop_2(65535); } _delay_loop_2(65535); // Пауза OCR1A = OCR1A +1; // Увеличиваем регистр сравнения // Если его значение больше максимального для счетного // регистра, обнуляем регистр сравнения. if (OCR1A > ICR1) OCR1A = 0; } if (-PINB & (1 << 6)) // Если напряжение на РВ6 = 0 В,
Таймеры/счетчики ATmega16 185 { //т.е. кнопка нажата // Ожидаем в цикле до тех пор, пока напряжение на выводе // равно 0, т.е. как только отпустят кнопку, произойдет // выход из цикла while (-PINB & (1 « 6)) { _delay_loop_2(65535); } _delay_loop_2(65535); // Пауза ICR1 = ICR1 +1; ~ // Увеличиваем регистр захвата } } } Рис. 8.42. Схема соединений для изменения частоты и коэффициента заполнения Сторожевой таймер Сторожевой таймер необходим для перезагрузки микроконтроллера в случае его “зависания”. Микроконтроллер считается “зависшим”, если таймер досчитал до заданного значения тайм-аута и не был сброшен ас- семблерной командой wdr или С-функцией wdt_reset (). В программе мы инициализируем выводы порта В как выходы для подключения светодиодов (рис. 8.43). Выводы PD0-PD2 — входы для подключения кнопок.
186 Глава 8 Рис. 8.43. Схема для исследования работы сторожевого таймера С помощью функции wdt_enable (WDTO_2S) ; мы включаем сторожевой таймер с тайм-аутом 2 с. Доступные значения тайм аута: WDTO_15MS, WDTO_3 0MS, WDTO_6 0MS, WDTO_12 0MS, WDTO_250MS, WDTO_500MS, WDTO_1S, WDTO_2S. В бесконечном цикле проверяется нажатие кнопок. Если нажата кнопка SW0, то сторожевой таймер отключается. При нажатии кнопки SWi активизируется задержка на 1 с. Поскольку тайм-аут сторожевого таймера составляет 2 с, до наступления тайм-аута будет вызвана функ- ция сброса таймера wdt_reset (), микроконтроллер перезагружен не будет, выполнение программы продолжится. При нажатии кнопки SW2 сторожевой таймер будет проинициали- зирован новым значением тайм-аута 500 мс. Теперь, если нажать кнопку
Таймеры/счетчики ATmega16 187 SW1, задержка будет превышать 500мс, функция сброса тайм-аута вы- звана не будет, и микроконтроллер перезагрузится. За сбросом микроконтроллера можно наблюдать на светодиодах, которые включаются по очереди: DI, D2, D3 и т.д. При установке тайм- аута в 500 мс (кнопка SW2) и после активизации задержки (кнопка SW1) включение светодиодов начнется с самого начала, с D1. Вместо библиотечный функций, объявленных в заголовочном фай- ле avr/wdt.h, для инициализации, сброса, включения сторожевого таймера можно обращаться напрямую к регистрам, например: Включение сторожевого таймера с тайм-аутом в 2 с: ..WDTCR |= OxOF;........... Сброс сторожевого таймера: asm("wdr"); Отключение сторожевого таймера: WDTCR |= (1 « 4) | (1 « 3) ; WDTCR &= ~(1 « 3); Программа Программа, реализующая работу схемы, показанной на рис. 8.43, представлена в листинге 8.13. ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\8.13 - Сторожевой тай- мер. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include cutil/delay.h> // Для доступа к функциям циклов // задержки #include <avr/wdt.h> // Функции для работы со сторожевым // таймером // Функция включения следующего светодиода void led () { II Сдвигаем разряды порта В влево // (например, если было 00000001, то // станет 00000010) (PORTB « 1)s PORTB
188 Глава 8 if (PORTB == 0) // Если уже нечего сдвигать, то PORTB |= (1 << 0); //в разряд 0 записываем 1 _delay_ms(300); // Задержка в 300 мс } int main(void) { DDRB = OxFF; // Все выводы порта В - выходы PORTB |= (1 << 0); // На вывод РВО подаем +5 В // Выводы PD0-PD2 - входы DDRD &= ~(1 « 0) & ~(1 « 1) & ~(1 « 2); PORTD = 0x07; // Выводы PD0-PD2 нагружены подтягивающими // сопротивлениями(т.е. на них уже +5 В) wdt_enable(WDTO_2S); // Включаем сторожевой таймер // с тайм-аутом 2 с while (1) // Бесконечный цикл { // При нажатии кнопки SW0 отключаем сторожевой таймер if (-PIND & (1 << 0)) // Если на выводе PD0 - 0 В { wdt_disable(); // Отключаем сторожевой таймер } // При нажатии кнопки SW1 имитируем зависание МК if (-PIND & (1 << 1)) // Если на выводе PD1 - 0 В { _delay_ms(1000); // Задержка в мс } // При нажатии кнопки SW2 включаем сторожевой таймер if (-PIND & (1 << 2)) // Если на выводе PD2 - 0 В { wdt_enable(WDTO_500MS); // Включаем сторожевой таймер // с тайм-аутом в 500 мс } led О; // Включаем следующий светодиод wdtresetO; // Сбрасываем сторожевой таймер (т.е. МК не // завис и сбрасывать его не надо) } }
Глава 9 В этой главе мы рассмотрим методы работы с периферией микро- контроллера ATmegal6, которая используется при аналоговых измере- ниях и преобразованиях. Аналоговый компаратор Аналоговый компаратор сравнивает напряжение на выводах AINO и AIN1. Если напряжение на входе AINO больше напряже- ния на выводе AIN1 то устанавливается в 1 разряд 5 (АСО) реги- стра ACSR. В противном случае этот разряд обнуляется. В нашем примере мы на вход AINO внешнее напряжение для сравнения по- давать не будем (рис. 9.1), а подключим внутренний источник опорного напряжения 1,22 В, установив разряд 6 регистра ACSR. Рис. 9.1. Схема соединений для исследования работы аналогового компаратора
190 Глава 9 Таким образом, если на входе AIN1 (РВЗ) напряжение меньше 1,22 В, то разряд АСО будет содержать 1; в противном случае — 0. Это сравнение реализуем в бесконечном цикле. Если АСО = 1 (1,22 В > AIN1), то на вывод PD0 подаем +5 В. Если АСО = 0 (1,22 В < AIN1), то на вывод PD1 подаем +5 В. Программа, реализующая работу схемы, показанной на рис. 9.1, представлена в листинге 9.1. ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\9.01 - Аналоговый ком- паратор. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <util/delay.h> // Для доступа к функциям циклов // задержки // Функция инициализации аналогового компаратора void analog_comparator_INIT() { ACSR &= ~(1 << 0); // Записываем 0 в разряд ACD // регистра ACSR (включаем // аналоговый компаратор) // Подключаем к неинвертирующему входу компаратора AIN0 // внутренний источник опорного напряжения 1,22 В ACSR |= (1 « б); // Разряд ACBG=1 } int main(void) { DDRB &= ~(1 « 3); // PB3 (AIN1) - вход компаратора PORTB &= ~(1 << 3); // Отключаем внутреннее // подтягивающее сопротивление на // выводе РВЗ // Светодиоды DDRD |= (1 << 0) | (1 « 1); //PD0 и PD1 - выходы PORTD &= ~(1 << 0) & ~(1 << 0) ;//Подаем на PD0 и PD1 0 В analog_comparator_INIT(); // Инициализации аналогового // компаратора while (1) { // Бесконечный цикл
Аналоговый компаратор и АЦП ATmega16 191 if (ACSR & f (1 « 5)) // Если внутренние 1,22 В > // напряжения на входе AIN1 (РВЗ), PORTD.|= (1 « 0) ; // то на вывод PD0 подаем +5 ' в, PORTD &= ~(1 << 1); // а на вывод PD1 - 0 В _delay loop 2(65535); 1 else // Если внутренние 1,22 В < r // напряжения на входе AIN1 (РВЗ), I PORTD &= ~(1 << 0); // то на вывод PD0 подаем 0 в, PORTD |= (1 « 1) ; // а на вывод PD1 подаем +5 в _delay_loop_2(65535); } } } АЦП в режиме непрерывного преобразования АЦП настроен следующим образом: • вход АЦП — несимметричный (без предварительного усиления), вывод PAI (ADC1); • частота преобразования — 16 МГц /128 = 125 кГц; • источник опорного напряжения — 2,56 В; • режим работы — непрерывное преобразование, т.е. циклы АЦП осуществляются непрерывно. На вход АЦП (ADC1) подается напряжение с делителя напряжения. Его дискретное значение отображается на светодиодах, подключенных к микроконтроллеру (рис. 9.2). Вход микроконтроллера AVCC аппаратно подключен для питания АЦП. На него подаем напряжение +5 В. Если АЦП не используется, то питание на AVCC подавать не обязательно. Поскольку разрешение АЦП 10 бит, т.е. 0Ы111111111, то для отображения дискретного значения напряжения мы используем 10 вы- водов: восемь — порта D и два — порта С. АЦП настроен на использование внутреннего источника опорного напряжения Vref = 2,56 В, т.е. на вход AREF микроконтроллера пода- вать напряжение не нужно. Результат преобразования определяется выражением: ADC = 1 024 • Vin/Vref.
192 Глава 9 Рис. 9.2. Схема соединений для исследования работы АЦП
диалоговый компаратор и АЦП ATmega16 193 Тогда определить результат преобразования в вольтах можно из следующего выражения: Vin = (ADC • Vref) 1 1 024, где: ADC — дискретное значение напряжения в десятичной системе счисления; Vref — опорное напряжение АЦП; Vin — напряжение на входе АЦП. Максимальное напряжение на входе АЦП не должно превышать Vin = (1 023 /1 024) • Vref = 2,5575 В. Результат преобразования сохраняется в виде десятиразрядного двоичного числа в регистрах ADCH и ADCL. Для его индикации мы ис- пользуем светодиоды, подключенные к выводам портов С и D. Например, если в порт С выведено “01”, а в порту D — “10001111”, то результат преобразования равен 0b0110001111 = 399. В таком слу- чае, напряжение на входе АЦП составляет Vin = (399 • 2,56) / 1 024 = = 0,9975 В. Настройка работы АЦП задается тремя регистрами: • ADCSRA — регистр управления и состояния А; • ADMUX — регистр управления мультиплексором АЦП; • SFIOR — регистр специальных функций ввода-вывода. Дискретный 10-разрядный результат преобразования сохраняется в двух восьмиразрядных регистрах данных: ADCH и ADCL. При этом шесть разрядов регистра ADCH или ADCL не используются. Непрерывное преобразование начинается с установки разряда ADSC регистра ADCSRA. Считывать результаты после каждого цикла преобразования можно после установки флага ADIF регистра ADCSRA. После того как результат считан, если не используются прерывания, флаг ADIF необходимо программно сбросить. Программа, реализующая работу схемы, показанной на рис. 9.2, представлена в листинге 9.2. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на 'ЧВ* прилагаемом к книге компакт-диске в папке ATmegai6\9.02 - ацп в режиме непрерывного преобразования. ttinclude <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте // Функция инициализации АЦП void ADC_INIT()
194 Главаg //ADCSRA - регистр управления и состояния А ADCSRA |= (1 << 7); // Разряд 7 (ADEN) - разрешить работу АЦц // ADPS2=1, ADPS1=1, ADPSO=1 - коэффициент делителя - 128 ADCSRA |= (1 « 2) | (1 « 1) | (1 « 0); //Выбор режима работы АЦП ADCSRA |= (1 << 5); // Разряд 5 (ADATE) - режим работы // определяется содержимым разрядов // регистра SFIOR // SFIOR - регистр специальных функций ввода-вывода // Источник стартового сигнала: ADTS2=0, ADTS1=O, ADTS0=0, // т.е режим непрерывного преобразования SFIOR &= ~(1 « 7) & ~(1 « 6) & -(1 « 5); // ADMUX - регистр управления мультиплексором АЦП ADMUX |= (1 « 7) | (1 << 6) ; // Внутренний источник // опорного напряжением 2,56В, // подключенный к выводу AREF // (в STK500 - снять перемычку // AREF) ADMUX |= (1 « 0) ; // Задействован несимметричный вход ADC1 ADMUX &= ~(1 << 5); //ADLAR = 0 - правостороннее // выравнивание результата преобразования // в регистре данных ADC (ADCH, ADCL) } int main(void) { ADC_INIT(); // Инициализация АЦП // Запускаем преобразование. В режиме непрерывного или // одиночного преобразования запуск - программный. Если же // используется источник стартового сигнала, то установка // разряда ADSC производится аппаратно после возникновения // заданного прерывания. ADCSRA |= (1 « б); DDRD = Oxff; // Выводы порта D - выходы DDRC = Oxff; // Выводы порта С - выходы while (1) // Бесконечный цикл { // Разряд 4 (ADIF) регистра ADCSRA по окончании // преобразования устанавливается в "1" (сбрасывается // записью "1”) if (ADCSRA & (1 « 4)) // Если преобразование завершено
диалоговый компаратор и АЦП ATmega16 195 { // PORTD = ADCL; //В порт PORTC = ADCH; //В порт ADCSRA |» (1 « 4) ; // И и и // и и } } } (установилась 1 в разряде ADIF) D - 8 бит регистра данных ADCL С - 8 бит регистра данных ADCH Обнуляем разряд ADIF, что говорит о завершении преобразования (обнуление осущестляется записью единицы!). Если бы использовалось прерывание по завершении преобразования, то этот разряд обнулился бы аппаратно. АЦП в режиме одиночного преобразования Отличие схемы в данном примере от рассмотренной выше заключа- ется только в подключенной кнопке SW0 на выводе РВО (рис. 9.3) и в режиме работы встроенного АЦП. При нажатии кнопку, устанавливается разряд ADSC в регистре ADCSRA, тем самым запуская одиночное преобразование. По оконча- нии преобразования разряд ADSC сбрасывается аппаратно. Результат выводится в порты D и С только после завершения пре- образования, если в регистре ADCSRA установлен флаг ADIF. Посколь- ку прерывание мы не используем, этот флаг аппаратно сброшен не бу- дет, поэтому его необходимо сбросить программно (листинг 9.3). ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\9.03 - АЦП в режиме одиночного преобразования. МЖ .. I Р-''‘СР -р Ррр -;:. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте // Функция инициализации АЦП void ADC_INIT() { //ADCSRA - регистр управления и состояния А ADCSRA |= (1 << 7); // Разряд 7 (ADEN) - разрешить работу АЦП // ADPS2=1, ADPS1=1, ADPS0=l - коэффициент делителя - 128 ADCSRA |= (1 « 2) | (1 « 1) | (1 « 0); ADCSRA &= ~(1 « 5); // Разряд ADATE=0 - режим одиночного // преобразования
196 Глава 9 // ADMUX - регистр управления мультиплексором АЦП ADMUX |= (1 « 7) | (1 << б); // Внутренний источник // опорного напряжением 2,56В, // подключенный к выводу AREF // (в STK500 - снять перемычку AREF) ADMUX |= (1 « 0) ; // Задействован несимметричный вход ADC1 ADMUX &= ~ (1 << 5); //ADLAR = 0 - правостороннее // выравнивание результата преобразования // в регистре данных ADC (ADCH, ADCL) } int main(void) { ADC-INITO; // Инициализация АЦП DDRD = Oxff; // Выводы порта D - выходы DDRC = Oxff; // Выводы порта С - выходы DDRB - 0x00; // Выводы порта В - входы PORTB = OxFF; // Выводы порта В нагружены подтягивающими // резисторами while (1) // Бесконечный цикл { if (-PINB & (1 « 0) ) // Если на выводе РВО - 0 В, то ADCSRA |= (1 « 6); // устанавливаем 1 в ADSC, запуская // преобразование. В режиме И одиночного преобразования разряд и ADSC по окончании преобразования // аппаратно сбрасывается в "О”. // Разряд 4 (ADIF) регистра ADCSRA по окончании // преобразования устанавливается в "1" (сбрасывается // записью "1") if (ADCSRA & (1 « 4)) // Если преобразование завершено { // (установилась 1 в разряде ADIF) PORTD = ADCL; // В порт D - 8 бит регистра данных ADCL PORTC = ADCH; // В порт С - 8 бит регистра данных ADCH ADCSRA |= (1 << 4); // Обнуляем разряд ADIF, что говорит // о завершении преобразования (обнуление // осущестляется записью единицы!). Если бы // мы использовали прерывание по // завершении преобразования, то этот // разряд обнулился бы аппаратно. } }
Аналоговый компаратор и АЦП ATmega16 197 Рис. 9.3. К выводу РВО подключена кнопка
198 Глава 9 АЦП в режиме дифференциального входа В таком режиме АЦП настроен на использование внешнего источ- ника опорного напряжения (в нашем случае AREF = +5В). Входы: неин- вертирующий +ADC1, инвертирующий -ADC0. Коэффициент усиления 10. Входное напряжение вычисляется по формуле: Vin = ADC • Vref / Gain • 512, где Gain — коэффициент усиления; Vref — опорное напряжение АЦП; ADC — результат преобразования в регистрах ADCH:ADCL. Рассмотрим два примера. 1. Результат преобразования ADC = ObOOlOOllOOl =153. Тогда Vin = 153 • 5 В / 10 • 512 = 0,14941 В. Поскольку разряд 9 (знак) равен нулю, то напряжение на положи- тельном входе больше напряжения на отрицательном входе на 0,149 В. 2. Результат преобразования ADC = 0Ы011101110. Поскольку раз- ряд 9 (знак) равен единице, то напряжение на отрицательном входе больше напряжения на положительном входе, и значение разности представлено в дополнительном коде. Преобразовываем его из до- полнительного кода, инвертировав каждый разряд и прибавив к не- му единицу. Получаем ОЬОЮООЮОЮ = 274. Таким образом, Vin = 274 • 5 В / 5120 = 0,267578 В. Максимальное значение регистра ADC = Oblllllllll = 511. То- гда максимальное значение напряжения при данных настройках: 511 -5 В/ 10 -512 = 0,499 В. Схема соединений для данного примера показана на рис. 9.4, а со- ответствующая программа — в листинге 9.4. ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegai6\9.04 - АЦП в режиме дифференциального входа. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include cutil/delay.h> // Для доступа к функциям циклов // задержки
диалоговый компаратор и АЦП ATmega16 199 Рис. 9.4. Схема для работы АЦП в режиме дифференциального входа
200 Глава 9 // Функция инициализации АЦП void ADC INITO { //ADCSRA - регистр управления и состояния А ADCSRA |= (1 << 7); // Разряд 7 (ADEN) - разрешить работу АЦП // ADPS2=1, ADPS1=1, ADPS0=l - коэффициент делителя - 128 ADCSRA |= (1 « 2) | (1 « 1) | (1 « 0); ADCSRA |= (1 << 5); // Разряд 5 (ADATE) - режим работы // определяется содержимым разрядов // регистра SFIOR // SFIOR - регистр специальных функций ввода-вывода // Источник стартового сигнала: ADTS2=0, ADTS1=O, ADTS0=0, // т.е режим непрерывного преобразования SFIOR &= ~(1 « 7) & ~(1 « б) & -(1 « 5); // ADMUX - регистр управления мультиплексором АЦП ADMUX &= ~(1 << 7) & ~ (1 << б);// Внешний источник // опорного напряжением 5 В, // подключенный к выводу AREF // (в STK500 - снять перемычку AREF) ADMUX |= (1 « 3) | (1 << 0); // Задействованы // дифференциальные входы +ADC1, -ADC0 // с коэффициентом усиления 10х ADMUX &= ~(1 << 5); //ADLAR = 0 - правостороннее // выравнивание результата преобразования // в регистре данных ADC (ADCH, ADCL) } int main(void) { ADCINITO; // Инициализация АЦП // Запускаем преобразование. В режиме непрерывного или // одиночного преобразования запуск - программный. Если же // используется источник стартового сигнала, то установка // разряда ADSC производится аппаратно после возникновения // заданного прерывания. ADCSRA |= (1 « б); DDRD = Oxff; // Выводы порта D - выходы DDRC = Oxff; // Выводы порта С - выходы while (1) // Бесконечный цикл { delay ms(1000);
Аналоговый компаратор и АЦП ATmega16 201 // Разряд 4 (ADIF) регистра ADCSRA по окончании // преобразования устанавливается в ”1” (сбрасывается // записью "1") if (ADCSRA & (1 « 4)) // Если преобразование завершено { // (установилась 1 в разряде ADIF) PORTD = ADCL; // В порт D - 8 бит регистра данных ADCL PORTC = ADCH; // В порт С - 8 бит регистра данных ADCH ADCSRA |« (1 « 4); // Обнуляем разряд ADIF, что говорит // о завершении преобразования (обнуление // осущестляется записью единицы!). Если бы // мы использовали прерывание по // завершении преобразования, то этот // разряд обнулился бы аппаратно. } }
Глава 10 В этой главе мы рассмотрим методы работы со следующими интер- фейсами передачи данных, доступными в микроконтроллере ATmegal6: • USART (универсальный синхронно-асинхронный приемопередат- чик); • SPI — последовательный интерфейс обмена данными с периферий- ными устройствами; • TWI — двухпроводной последовательный интерфейс. Интерфейс USART В этом примере микроконтроллер с помощью модуля USART выво- дит в терминал текстовое меню, предлагающее выбрать вывод микро- контроллера, а также — длительность сигналов высокого и низкого уровня в миллисекундах (рис. 10.1). Рис. 10.1. Меню Выбирая тот или иной пункт меню, мы передаем его номер из ком- пьютера в микроконтроллер через USART. После выбора пункта меню 0 запускается бесконечный цикл, в котором выполняются определенные операции с выводом микроконтроллера.
Интерфейсы передачи данных ATmega16 203 Соединение COM-порта компьютера и интерфейса USART микро- контроллера должно быть реализовано через преобразователь уровней (рис. 10.2). Рис. 10.2. Сопряжение интерфейсов СОМ и USART через преобразователь уровней В начале программы вызывается функция инициализации прие- ма/передатчика, в которую передается значение 64. Это значение запи- сывается в регистр UBRR, задающий скорость приема/передачи. Для обычного асинхронного режима значение UBRR вычисляют по форму- ле: UBRR = fosc I (16 • Скорость) -1 = 10 МГц / (16 • 9 600) - 1 = 64,104166. Значение, записываемое в UBRR, должно быть целочисленным, по- этому мы выбираем 64, однако в результате скорость обмена будет чуть выше 9 600 бод: Скорость = /osc / (16 • (UBRR + 1)) = 10 МГц / (16 • 65) = 9615, 3846. Поскольку допустимое расхождение в скорости составляет 0,5%, рассчитаем этот процент, чтобы убедится что мы его не превысили. 9 615,3846-9 600= 15,3846. Один процент от 9600 составляет 96. Тогда 15,38 бод — это 15,38 / 96 = 0,16 %, что меньше 0,5%, а значит допустимо. С помощью регистров UCSRC и UCSRB устанавливаем следующий режим работы и формат кадров: асинхронный режим, один стоп-бит, восьмиразрядное слово данных, контроль по четности отключен.
204 Глава Ю Назначаем порт В на выход и перенаправляем стандартные потоки ввода-вывода на наш поток mystdout_mystdin. В цикле микрокон- троллер передает через USART текстовое меню, в переменную choose принимает номер выбранного меню, и, в зависимости от ее значения, выполняет те или иные действия. Если от USART поступил символ “0”, происходит выход из цикла. Схема соединений для данного примера показана на рис. 10.3. Рис. 10.3. Схема соединений для тестирования интерфейса с USART Программа Программа, реализующая работу схемы, показанной на рис. 10.3, представлена в листинге 10.1.
Интерфейсы передачи данных ATmega16 205 ЛЧЬ Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegai6\io. 1 - usart. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <stdio.h> // Стандартные средства ввода-вывода (для // потока ‘stream’) #include <util/delay.h> // Для доступа к функциям циклов // задержки #include <avr/pgmspace.h> // Утилиты для работы с Flash- // памятью // Функция инициализации USART void USART-INIT(unsigned int UBRR_baud) { // Устанавливаем скорость передачи/приема. Она записывается // в два регистра: четырехразрядный UBRRH и восьмиразрядный // UBRRL. Для начала значение, хранящееся в UBRR_baud, // сдвигаем в право на 8 разрядов. UBRRH = (unsigned char)(UBRR_baud » 8); // Поскольку выполняется явное преобразование // к восьмиразрядному числу, в UBRRL будет записано 8 младших // разрядов, а все старшие после восьмого разряда будут // отброшены UBRRL = (unsigned char)UBRR_baud; // Для обращения к регистру UCSRC необходимо записать 1 //в разряд URSEL, и одновременно с этим установить // требуемые нам разряды в регистре UCSRC. // Режим работы асинхронный: разряд UMSEL=0. Отключен // контроль по четности: разряды UPM1=O и UPM0=0. Один стоп- // битов: разряд USBS=0. Восемь бит данных: разряды UCSZ1=1, // UCSZ0=l, разряд UCSZ2 регистра UCSRB =0 // В асинхронном режиме разряд UCPOL=0 UCSRC = (1 << URSEL) | (0 « UMSEL) | (0 << UPM1) | (0 « UPM0) | (0 « USBS) | (1 « UCSZ1) | (1 « UCSZ0) | (0 « UCPOL); UCSRB &= ~(1 << 2); // UCSZ2=0 (8 бит данных) // Включаем приемник и передатчик USART UCSRB |=. (1 « 4) | (1 « 3); // Разряды RXEN=1 и TXEN=1 // Разряды U2X=0 и МРСМ=0, т.е. отключаем удвоение скорости // и режим мультипроцессорного обмена
206 Глава Ю ucsra &= ~(1 « 1) & ~(1 « 0); } // Функция, которая будет вызываться при обращении потока на // вывод static int uart_putchar(char simvol, FILE *stream) { // Ожидаем освобождения регистра данных, т.е. когда он будет // готов принять символ для отправки (установится 1 в разряде // UDRE) loop_until_bit_is_set(UCSRA, 5); UDR = simvol; // Передаем символ, содержащийся // в переменной simvol, в порт UART return 0; b // Функция, которая будет вызываться при обращении потока на // ввод static int uart_getchar(FILE *stream) { // Ожидаем, пока завершится прием символа в UART (установится // 1 в разряде RXC) loopuntilbit—is_set(UCSRA, 7); return UDR; // Функция вернет принятый символ } // Инициализация ввода-вывода // Создаем поток, назначив функцию uart_putchar для вывода // данных, a uart_getchar - для ввода. Используем ввод-вывод, // установив флаг _FDEV_SETUP_RW static FILE mystdout_mystdin = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW); // Массивы символов, хранящиеся в памяти программ (Flash) const char menuO [] PROGMEM = "\r\n---------------\r\n Меню \r\nl)Ножка PB"; const char menul[] PROGMEM = ”\r\n2/3)Время низкого уровня сигнала”; const char menu2[] PROGMEM = ”\r\n4/5)Время высокого уровня сигнала”; const char menu3[] PROGMEM = ”\r\n0)Запуск\г\п cmd>"; int main(void) { USART—INIT(0x40); // Передаем в функцию инициализации USART // значение регистра UBRR: 64, т.е. 9600 бод при 10 МГц
Интерфейсы передачи данных ATmega16 207 DDRB = OxFF; // Все выводы порта В - выходы PORTB = 0x00; // На все выводы порта В подаем 0 В // Перенаправляем стандартные потоки на пользовательский stdin = stdout = &inystdout_mystdin; char choose = ’9'; // Переменная для хранения принятого // символа по USART int nogka =0; // Переменная для хранения номера // вывода, состояние которого будем // периодически инвертировать int vremja_0_signala = 5000; // Длительность сигнала // низкого уровня "0 В" int vremja_l_signala = 5000; // Длительность сигнала ' // высокого уровня "+5 В" // Цикл, в котором определеяется состояние вывода while (choose != ’О') // Выходим из, если от USART принят { // символ "0" // Выводим меню printf_P(&menu0[0]); // Функция printf_P выведет в наш // поток строку, хранимую в Flash- // памяти printf("%d\r\n", nogka); // Отображаем номер вывода МК printf_P(&menul[0]); // Продолжение меню printf ("%d\r\n11, vremja_0_signala) ; // Отображаем // длительность сигнала низкого уровня printf_P(&menu2[0] ); // Продолжение меню printf(”%d\r\n", vremja_l_signala); // Отображаем // длительность сигнала высокого уровня printf_P(&menu3[0]); // Продолжение меню // Принимаем символ через UART в переменную choose choose = getcharO; // Если выбрано 1, то меняем вывод МК if (choose == ’I1) nogka++; // Если вывод с номером 8 (т.е. такого вывода нет), тогда // меняем номер на 0 if (nogka == 8) nogka =0; // Если выбрано 2, то увеличиваем длительность сигнала // низкого уровня if (choose == '2’) vremjа_0_signala=vremja_0_signala+1000; // Если выбрано 3, то уменьшаем длительность сигнала // низкого уровня if (choose == ’ З1) vremja_0_signala=vremja_0_signala-1000; // Если выбрано 4, то увеличиваем длительность сигнала
208 Глава 10 // высокого уровня if (choose == ’41) vremja_l_signala=vremja_l_signala+1000; // Если выбрано 5, то уменьшаем длительность сигнала // высокого уровня if (choose == ’51) vremja_l_signala=vremja_l_signala-1000; } while (1) // Бесконечный цикл { PORTB |= (1 << nogka); // Устанавливаем лог. 1 на заданном // выводе порта В _delay_ms (vremja_l_signala); // Задержка в мс на // длительность сигнала // высокого уровня PORTB &= ~(1 << nogka);// Устанавливаем лог. 0 на заданном // выводе порта В delay ms (vremja_O_signala) ; // Задержка в мс на // длительность сигнала // низкого уровня } } Интерфейс SPI. Подключение 12-разрядного ЦАП МСР4821 Микросхема МСР4821 представляет собой 12-разрядный ЦАП с ин- терфейсом SPI. Ее выводы: • Vdd — вход питания 2,7..5,5 В; • /CS — вход выбора микросхемы (напряжение Vdd в моменты про- стоя, 0 В в моменты передачи данных); • SCK. — вход тактовых импульсов (0 В в режиме простоя); • SDI — вход данных; • /LDAC — синхронизирующий вход, служащий для передачи дан- ных с входного регистра в выходной при подаче на него 0 В (его можно деактивировать, подключив к “нулю”); • /SHDN — вход отключения микросхемы (для перевода микросхемы во включенное состояние необходимо подать напряжение питания); ♦ AVss — вход питания 0 В (“земля”); • Vquta — выход цифро-аналогового преобразования. Данные, передаваемые в микросхему, — 16-разрядные (рис. 10.4): • биты 12-15 — конфигурационные:
Интерфейсы передачи данных ATmega16 209 о 15 (А/В) — выбор выхода ЦАП преобразования (0 — А, 1 — В); поскольку у нас только канал А, мы передаем 0; о 14 — не используется; о 13 (GA) — выбор коэффициента усиления выхода ЦАП (Vout = = 2,048 • данные / 4 096): 1 — коэффициент 1; 0 — коэффициент 2; о 12 (SHDN) — программное отключение (0) и включение (1) мик- росхемы; • биты 0-11 — данные для установки требуемого выходного напря- жения. Upper Hatt W-x W-x W-x W-0 W-x W-x W-X W-x А® | — | GA | SHON | D11 I O10 1 D9 | D8 bit 15 Lower Halt w-x W-x W-x W-x W-x W-X W-X W-x DL 1, D6 | 05 1 51_J D3 1 02 1 1 01 I D0 bit 7 bit О Рис. 10.4. Слово данных, передаваемых в микросхему МСР4821 Передача должна осуществляться, начиная со старшего, 15-го, раз- ряда, и т. д. до нулевого. Инструкция должна передаваться в ЦАП в следующей последова- тельности (рис. 10.5). 1. На вывод /CS подать 0 В (перед этим на выводе должно присутст- вовать напряжение питания). 2. Передать 16 бит данных. 3. На вывод /CS подать +5 В. s§~\ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 М 15 (Mode 1,1) 8к1ШТГЪТЪГ1ЛЛ]ТГ1ГШПЛЛЛЛ_____1 SDI ------config bits ---------------------• 12 data bits------—-------*> foX ~-----------------------------------------------------------------DI I^D10j( D9)( D8]( D7)( D6)( [»)(D4)( РЗ)( D2^ D1 )(Бо LDAC Чэит Рис. 10.5. Последовательность передачи сигналов в ЦАП
210 Глава ю Вход LDAC мы не используем (он соединен с “нулем”). Данные мо- ментально попадают в выходной регистр после приема 16 бит и уста- новки высокого уровня на входе /CS. Программа В начале программы (листинг 10.2) мы вызываем функцию инициа- лизации аппаратного SPI-модуля SPI_MasterInit (), в которой наст- раиваем интерфейс микроконтроллера таким образом, чтоб он соответ- ствовал конфигурации приема данных микросхемой МСР4821. ЦАП на- строен на считывание данных по нарастающему фронту сигнала SCK, прием данных, начиная со старшего разряда, перевод вывода выбора микросхемы /CS в состояние лог. 1 в режиме ожидания. Устанавливаемое напряжение на выходе ЦАП (Vouta) соответству- ет 12-разрядному значению. Для его хранения в программе объявлена глобальная переменная conf ig_bits_and_data. Для того чтобы получить возможность повышать или понижать на- пряжение на выходе ЦАП, к микроконтроллеру подключены две кноп- ки, нажатие которые инкрементирует или декрементирует переменную conf ig_bits_and_data, а также отправляет ее значение. При нажатии кнопки SW0 (рис. 10.6) напряжения на выводе PD7 понижается с +5 В до 0 В, в результате чего уменьшается значение пе- ременной conf ig_bits_and_data, и обнуляются все разряды после 12-го. Для обнуления используется поразрядный оператор & (“И”). Поскольку после разряда 12 значение OxOFFF содержит нули, то операция “И”, примененная к переменной conf ig_bits_and_data и этому значении, даст в результате двоичное число, в котором все раз- ряды старше 12-го будут содержать 0. Далее в программе вызывается функция SET_Voltage (), которая реализует процедуру отправки данных. На выводе /SS устанавливается 0 В, и вызывается функция отправки SPI_MasterTransmit (), кото- рая передает восемь старших бит (четыре конфигурационных бита и че- тыре бита данных). Поскольку функция SPI_MasterTransmit () за один раз может отправить только восемь бит данных, следующие во- семь бит отправляем, вызвав ее повторно. По окончании передачи вы- вод /SS переводится в состояние лог. 1. е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\10.02 - SPI - цап.
Интерфейсы передачи данных ATmega16 211 Рис. 10.6. Схема подключения ЦАП МСР4821
212 Глава Ю ttinclude <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <util/delay.h> // Для доступа к функциям циклов // задержки // Функция инициализации нтерфейса SPI void SPI_MasterInit() { // Выводы /SS, SCK, MOSI (линия передачи данных) - выходы // РВ4 - /SS, РВ7 - SCK, РВ5 - MOST DDRB |= (1 « 4) | (1 « 7) | (1 « 5); PORTB |= (1 « 4); //На выводе /SS устанавливаем 5 В // (устройство, подключенное к SPI, // отключено) // Включаем SPI, режим "Master", частота тактового сигнала // на выводе SCK = fck/2 = 10 МГц /2=5 МГц // SPR0 (разряд 0 регистра SPCR) = 0 - частота тактового // сигнала SCK = fck/2 // SPR1 (разряд 1 регистра SPCR) = 0 - частота тактового // сигнала SCK = fck/2 // СРНА (разряд 2 регистра SPCR) = 0 - обработка данных по // переднему фронту сигнала // CPOL (разряд 3 регистра SPCR) = 0 - импульсы положительной // полярности // MSTR (разряд 4 регистра SPCR) = 1 - режим работы SPI - // "Master" // DORD (разряд 5 регистра SPCR) = 0 - передача данных, // начиная со старшего разряда // SPE (разряд 6 регистра SPCR) = 1 - SPI-интерфейс включен // SPIE (разряд 7 регистра SPCR) = 0 - прерывания запрещены SPCR = (1 « 6) | (1 « 4); SPSR |= (1 << 0); // SPI2X (разряд 0 регистра SPSR) = 1 - // частота тактового сигнала SCK = fck/2 // Функция передачи байта по интерфейсу SPI unsigned char SPI_MasterTransmit(char eData) { SPDR = eData; // Передаем байт в сдвиговый регистр SPDR while (-SPSR & (1 « 7)) {}; // Ожидаем отправки байта // (появится 1 в разряде SPIF // регистра SPSR) return SPDR; // Возвращаем принятый байт }
1/1нтерфейсы передачи данных ATmega16 213 // Переменная для хранения данных, соответствующих напряжению, II устанавливаемому на выходе ЦАП - 16 бит данных, // конфигурационные биты плюс данные, определяющие напряжение //на выходе ЦАП static unsigned int config_bits_and_data = 0x0000; // Функция отправки 16 бит данных по интерфейсу SPI static void SETVoltage() { PORTB &= ~(1 << 4); // На выводе /SS устанавливаем 0 В // Отправляем по SPI (0X01DDDD). Первые 4 бита (15-12) - // конфигурационные, остальные (11-0) - данные напряжения, // которое будет установлено на выходе ЦАП // Отправляем старшие 8 бит данных (8-15). Поскольку // переменная config_bits_and_data - 16-разрядная, сдвигаем // старшие 8 бит вправо (первыми отправляем старшие 8 бит) // и присваиваем разряду 4 (конфигурационному) единицу, т.е. // выводим ЦАП из режима SHDN SPI_MasterTransmit((config_bits_and_data >> б) | (1 << 4)); // Отправляем по SPI младшие 8 бит данных (0-7) SPI_MasterTransmit(config_bits_and_data); PORTB | = (1 « 4); // На выводе /SS устанавливаем +5 В } int main(void) { SPI_MasterInit(); // Инициализация SPI в режиме "Master" // Кнопка установки напряжения на ЦАП +1 (передача по SPI // инструкции установки напряжения) DDRD &= -*(1 « 7); // Вывод PD7 - вход PORTD |= (1 « 7); // Вывод PD7 - с подтягивающим // сопротивлением // Кнопка установки напряжения на ЦАП -1 (передача по SPI // инструкции установки напряжения) DDRD &= ~(1 « 6); 6); // Вывод PD6 - вход // Вывод PD6 - с подтягивающим // сопротивлением PORTD |= (1 « while(1) г // Бесконечный цикл t if (-PIND & (1 « 7)) // Если напряжение на выводе { // с подтягивающим сопротивлением
214 Глава ю // стало равным 0 (кнопка нажата) // Ожидаем, когда напряжение на выводе станет равным 0. // Как только отпустят кнопку, произойдет выход из цикла while (-PIND & (1 « 7)) { _delay_loop_2(65535); } _delay_loop_2(65535); // Пауза // Инкрементирование и запись нулей во все разряды после // 12-го config_bits_and_data = (config_bits_and_data + 1) & OxOFFF; SET_Voltage(); // Передача инструкции в ЦАП } if(~PIND & (1 « 6)) // Если напряжение на выводе { //с подтягивающим сопротивлением // стало равным 0 (кнопка нажата) // Ожидаем, когда напряжение на выводе станет равным 0. // Как только отпустят кнопку, произойдет выход из цикла while (-PIND & (1 « 6)) { _delay_loop_2(65535); } _delay_loop_2(65535) ; // Пауза // Декрементирование и запись нулей во все разряды после // 12-го config_bits_and_data = (config_bits_and_data - 1) & OxOFFF; SET_Voltage(); // Передача инструкции в ЦАП } } } Интерфейс SPI. Работа с памятью EEPROM В этом примере мы воспользуемся микросхемой энергонезависимой памяти М95040. Ее объем — 512 байт. Одна страница содержит 16 байт. Максимальная частота тактового сигнала на выводе SCK— 5 МГц. Микроконтроллер выступает в роли связующего звена между ком- пьютером и памятью. Его интерфейс SPI подключен к микросхеме па- мяти М95040, а интерфейс USART — к COM-порту (интерфейс RS232) компьютера. С помощью программы HyperTerminal мы передаем ко- манды микроконтроллеру, который, в свою очередь, обменивается ин- формацией с памятью, используя SPI-интерфейс (рис. 10.7). Рис. 10.7. Схема обмена данными
Интерфейсы передачи данных ATmega16 215 Схема соединений для данного примера показана на рис. 10.8. Рис. 10.8. Схема соединений для работы с памятью EEPROM
216 Глава Ю Микросхема памяти поддерживает два режима обмена данными (рис. 10.9): • SPI MODE 0 — если разряды CPOL; СРНА регистра SPCR содер- жат 0, активный уровень импульсов — высокий в режиме передачи и низкий в режиме ожидания (сначала фиксация, затем сдвиг) • SPI MODE 3 — если разряды CPOL; СРНА регистра SPCR содер- жат 1, активный уровень импульсов — низкий в режиме передачи и высокий в режиме ожидания (сначала сдвиг, затем фиксация). Рис. 10.9. Режима обмена данными микросхемы М95040 Входные данные (вывод D) для обоих режимов фиксируются по возрастающему фронту тактового сигнала (вывод С). Выходные данные для обоих режимов доступны по ниспадающему фронту тактового сиг- нала. В программе (листинг 10.3) вначале вызывается функция инициали- зации интерфейса USART микроконтроллера USART_INIT (0x40). Параметру 0x40 соответствует следующая конфигурация: 9600 бод при частоте микроконтроллера 10 МГц; контроль по четности отсутствует; один столовый бит; восемь бит данных. Далее вызывается функция SPI_MasterInit () инициализации интерфейса SPI со следующей конфигурацией: частота тактового сигна- ла SCK. —/ск/2; импульсы положительной полярности; обработка дан- ных по переднему фронту сигнала; передача данных, начиная со стар- шего разряда; режим работы “Master”. В бесконечном цикле с помощью функции printf_P выводим че- рез интерфейс USART в COM-порт компьютера текстовое меню. Зате- ем, используя функцию getchar (), ожидаем приема символа (вы- бранного пункта меню) от интерфейса RS232 компьютера и присваива- ем принятый символ переменной choose. В зависимости от выбранно- го пункта меню, с помощью оператора switch выполняем ту или иную функцию.
Интерфейсы передачи данных ATmega16 217 1. Установка триггера разрешения записи. Функции WREN (). Пе- ред тем как установить разрешение на запись, необходимо прове- рить, не занята ли микросхема памяти операцией записи. Если заня- та, необходимо дождаться завершения записи. Проверить это мож- но, прочитав регистр состояния памяти. Если разряд WIP= 1, то память занята. Если WIP = 0, то память готова к работе. Инструкция разрешения записи описана в спецификации М95040. Сначала вы- бираем (включаем) память EEPROM, подав О В на вывод /SS. От- правляем с помощь функции SPI_MasterTransmit() инструк- цию 0000X110, после чего отключаем обмен с памятью EEPROM, подав на вывод /SS напряжение +5 В. После того как триггер раз- решения записи установлен, стают доступны инструкции WRSR и WRITE. Триггер автоматически сбрасывается после выполнения инструкции WRSR или WRITE. . 2. Сброс триггера разрешения записи. Функция WRDI () — отправ- ляет инструкцию, запрещающую любую запись в память. 3. Чтение регистра состояния. Функция RDSR () — передает по ин- терфейсу SPI инструкцию чтения регистра состояния, принимает от микросхемы памяти значение регистра состояния и возвращает его в вызывающую программу. Результат RDSR () передаем через ин- терфейс USART в COM-порт компьютера с помощью стандартной функции printf_P (). 4. Запись регистра состояния. С помощью функции форматирован- ного ввода scanf_P () принимаем в переменную stat_reg число в шестнадцатеричной системе счисления и передаем его в функцию записи в регистр состояния WRSR () .Перед этим разрешаем запись, вызвав функцию WREN (), и разрешаем обмен данными с микро- схемой, установив лог. 0 на выводе /S. Передаем инструкцию запи- си в регистр состояния, передаем новое значение регистра состоя- ния. Запрещаем микросхеме обмен данными, установив лог. 1 на выводе \S. 5. Чтение данных с массива памяти. Функция READ (). Для чтения нам необходимы два значения: с какого адреса микросхемы начи- нать чтение и количество считываемых байт. С помощью функции fgets () принимаем по интерфейсу USART адрес памяти, с кото- рого начинается чтение. Поскольку функция работает с данными в символьном формате, принятый адреса, содержащийся в массиве f rom_uart_address [4], преобразовывается в целочисленную переменную address. Далее принимаем по USART число (количе-
218 Глава Ю ство байт, которое необходимо считать из памяти) в массив и пре- образовываем его в целочисленную переменную. Перед чтением убеждаемся, что память не занята процессом записи. Если она заня- та, ожидаем ее освобождения (установится 0 в разряде WIP регист- ра состояния). Разрешаем обмен данными с памятью, установив лог. 0 на выводе /SS. В зависимости от того, с какого адреса начинается чтение памяти, отправляются разные инструкции чтения. Если чте- ние выполняем до адреса 255 включительно, то по SPI отправляем инструкцию 00000011, в противном случае— 00001011. В цик- ле при помощи функции SPI_MasterReseive () считываем по- байтно данные из интерфейса SPI и выводим их в терминальный клиент компьютера, после чего устанавливаем уровень лог. 1 на вы- воде /SS. 6. Запись данных в микросхему памяти. Функция WRITE (), в кото- рой через интерфейс USART принимается два значения: 16 байт данных, которые будут записаны в микросхему памяти, и адрес па- мяти, с которого необходимо начать запись. Если адрес записи ле- жит в пределах нижней половины памяти, то отправляем инструк- цию 00000010, в противном случае — 00001010. Отправляем адрес, с которого начинается запись. Согласно спецификации, от- правляемый адрес — это восьмиразрядное значение, а значит мы не можем отправить адрес больше 255. Для записи в ячейку 256 памя- ти отправляется нулевой адрес байта, т.е “адрес минус 256”. В цик- ле побайтно передаем через интерфейс SPI данные для записи, по- сле чего устанавливаем уровень лог. 1 на выводе /SS. 7. Чтение всей памяти. Функция READ_ALL () — считывает все 512 ячеек памяти и выводит их содержимое через USART в СОМ-порт компьютера. После каждой страницы для наглядного отображения содержимого памяти передаются символы “\п” (перевод строки) и “\г” (возврат каретки). По завершения инструкции чтения уста- навливается уровень лог. 1 на выводе /SS. Во время обмена данны- ми по интерфейсу SPI (от выбора устройства SPI до окончания об- мена) нельзя обращаться к другим интерфейсам микроконтроллера (т.е. прерывать работу с SPI) и выполнять какие-либо вычислитель- ные операции посреди инструкций. Внешние функции, используемые в программе char* fgets(char *_str, int_size, FILE *_stream) Считывает из потока_stream строку символов и помещает ее в ___str. Ввод завершается после приема size-1 символов или при по-
Интерфейсы передачи данных ATmega16 219 лучении символа перехода на следующую строку. Если ввод завершил- ся символом новой строки (ОхОА, (в терминале — комбинация клавиш <Ctrl+J>), то он также будет записан в_str. При задании размера ___str следует учитывать, что функция автоматически завершает стро- ку нулевым байтом “\0” (0x00) для индикации ее окончания. Пример: char f rom_uart__address [4] ; fgets(from uart address, 4, stdin); Массив from_uart_address состоит из четырёх элементов (от [0] до [3]). С помощью функции fgets в него можно поместить макси- мум три символа, т.к. после последнего введенного символа функция завершит строку нулевым байтом “\0”. Функция принимает максимум три байта (size-1) в массив from_uart_address. Если мы ввели два байта и завершили ввод символом новой строки, то в элемент [2] запишется символ ОхОА, а в элемент [3] — символ конца строки 0x00. char* gets(char *_str) Считывает байты из потока stdin в строку_str до тех пор, пока не встретит признак новой строки. В отличие от функции fgets, здесь символ ОхОА не записывается. Строка завершается символом конца строки 0x00. Размер___str должен быть достаточно большим, чтобы вместить все, что приходит из потока, включая символ конца строки. int scanf_P(const char * fmt, argl, arg2...) Функция scanf_P () считывает из входного потока stdin значе- ния в формате, определенном в строке___fmt, и сохраняет их в пере- менных, адреса которых переданы ей в качестве аргументов. Функция идентична scanf, за исключением того что строка_fmt размещается в памяти программ, чтобы не занимать ОЗУ. Аргументы в строке ___fmt определяют тип вводимых данных и могут быть следующими: • % d — десятичное целое; • %i — целое, которое может быть восьмеричным (с 0 слева) или ше- стнадцатеричным (с Ох или 0Х слева); • %о — восьмеричное целое (с нулем слева или без него); • %и — беззнаковое десятичное целое; • %х — шестнадцатеричное целое (с Ох или 0Х слева или без них).
220 Глава Ю int atoi (const char *s) Преобразует массив ASCII-символов (цифр) в значение типа int. Строка передаваемая в функцию должна оканчиваться символом ‘ \ 0 ’ (0x00). PSTR(cTpoKa в памяти программ) Служит для объявления статических указателей на строку в памяти программ. Определена следующим образом: PSTR(s) ((const PROGMEM char *)(s)) Описание микросхемы M95040 Выводы М95040: • Q — последовательный выход данных (данные сдвигаются по нис- падающему фронту синхроимпульсов на выводе С); • D — последовательный вход данных, на который поступают инст- рукции для работы с памятью и данные для записи (данные выби- раются по нарастающему фронту синхроимпульсов на выводе С); • С — вход синхроимпульсов последовательной шины; • S — выбор устройства: +5 В — устройство отключено; 0 В — уст- ройство включено; • Hold — используется для приостановки любого обмена информаци- ей с устройством, без отключения устройства с помощью вывода S: 0 В — приостановка обмена; +5 В — обмен информацией. • /W — установка защиты от записи; запрещает использование инст- рукций записи, когда на выводе присутствует 0 В, разрешает — ко- гда на выводе +5 В; • VSS —“земля”; • VCC — напряжение питания. Инструкции микросхемы М95040 перечислены в табл. 10.1 Таблица 10.1. Инструкции микросхемы М95040 Инструкция Описание Формат инструкции WREN Установка триггера разрешения записи 0000 Х110 WRDI Сброс триггера разрешения записи 0000 Х100 _ RDSR Чтение регистра состояния 0000 Х101 WRSR Запись регистра состояния 0000 Х001 READ Чтение данных из массива памяти 0000 А8011 _ WRITE Запись данных в массив памяти 0000 А8010 _ * х — 1 или о; а8 - 1 для второй половины массива памяти (с 256 до 511 байта) и О для первой половины (с 0 до 255 байта).
Интерфейсы передачи данных ATmega16 221 Инструкция wren — “Установка триггера разрешения записи” Последовательность выполнения инструкции WREN иллюстрирует рис. 10.10. High Impedance Q 11 .. ' Рис. 10.10. Последовательность выполнения инструкции wren Каждая операция записи в память должна начинается с инструкции разрешения записи. 1. Установить на выводе /S напряжение 0 В. 2. Передать микросхеме памяти инструкцию 0000 Х110. 3. Установить на выводе /S напряжение +5 В. Триггер сбрасывается автоматически после операции записи. Инструкция wrdi — “Сброс триггера разрешения записи” Последовательность выполнения инструкции WRDI иллюстрирует рис. 10.11. ндЖлллг: -- Instruction » v______ High Impedance Q Рис. 10.11. Последовательность выполнения инструкции wrdi
222 Глава Ю 1. Установить на выводе /S напряжение 0 В. 2. Передать микросхеме памяти инструкцию 0000 Х100. 3. Установить на выводе /S напряжение +5 В. Один из способов сбросить триггер разрешения записи — отпра- вить инструкцию сброса. Триггер разрешения записи так же сбрасыва- ется при следующих событиях: • включение; • выполнении инструкции WRDI; • выполнение инструкции WRSR; • выполнение инструкции WRITE; • появление лог. 0 на выводе /W. Инструкция rdsr — “Чтение регистра состояния” Последовательность выполнения инструкции RDSR иллюстрирует рис. 10.12. Рис. 10.12. Последовательность выполнения инструкции RDSR 1. На вывод /S подать напряжение 0 В. 2. Передать код инструкции 0 0 0 0 Х101. 3. Принять байт информации (регистр состояния). 4. На вывод /S подать +5 В. Регистр состояния состоит из восьми бит, из которых разряды 4-7 всегда содержат 1. Разряды 2, 3 (ВРО и ВР1) служат для зашиты от запи- си в сектор памяти. Когда они оба содержат 0, защита не используется. Эти разряды энергонезависимы. Разряд 1 (WEL) отображает состояние триггера записи. Триггер за- писи устанавливается и сбрасывается инструкциями WREN и WRDI. Ко-
Интерфейсы передачи данных ATmega16 223 гда WEL = 0, инструкции WRITE или WRSR устройством не принимают- ся. Разряд О (WIP) показывает, занята ли память операцией записи: 1 — занята; 0 — нет. Инструкция wrsr — “Запись регистра состояния” Последовательность выполнения инструкции WRSR иллюстрирует рис. 10.13. Рис. 10.13. Последовательность выполнения инструкции wrsr Из восьми разрядов регистра состояния можно записать только два разряда защиты от записи: ВРО и ВР1. Запись регистра состояния не примется и не выполнится, если: • перед данной инструкцией не была выполнена инструкция WREN, т.е. установлен разряд WEL в регистре состояния; • если осуществляется цикл записи; • если вывод зашиты от записи /W находится в состоянии лог. 0. Процесс записи регистра состояния должен быть следующим. 1. На вывод /S подать 0 В. 2. Передать инструкцию 0000 Х001. 3. Передать требуемое значение регистра состояния. 4. На вывод /S подать +5 В. Инструкция read — “Чтение данных из массива памяти” Последовательность выполнения инструкции READ иллюстрирует рис. 10.14.
224 Глава ю Рис. 10.14. Последовательность выполнения инструкции read Процесс чтения данных из массива памяти должен быть следую- щим. 1. На вывод /S подать 0 В. 2. Передать инструкцию 0000 ООН для чтения данных из первой половины памяти (адреса с 0 по 255) или 0000 1011 — для чтения из второй половины (адреса с 256 по 511). 3. Передать адрес памяти, с которого начинается чтение данных. 4. Начать прием данных (побайтный) из массива памяти. 5. После приема необходимого количества байт подать на вывод /S на- пряжение +5 В. Если после приема первого байта на вывод /S не подать +5 В, то ад- рес автоматически инкрементируется, и можно принимать следующий байт информации. Если достигнут последний адрес памяти (511), он становится рав- ным 0, и можно продолжать чтение массива памяти с самого начала. Все данные массива памяти могут быть прочтены, используя одну инст- рукцию READ. Прервать прием можно, установив +5 В на выводе /S. Инструкция не принимается и не выполняется, если выполняется запись в память, поэтому перед выполнением данной инструкции необходимо дождаться установки 0 в разряде WIP регистра состояния. Инструкция write — “Запись данных в массив памяти” Последовательность выполнения инструкции WRITE иллюстрирует рис. 10.15.
интерфейсы передачи данных ATmega16 225 0 I 2 3 4 S 6 7 a 9 10 1112 13 14 15 16 17 18 19 20 21 22 23 ПЛЛЛМ[иШ1ЛЛЛЛ!1ЛМ[Ш1ЛЯП instruction Byte Address Data Byte High impedance a .......—— ............................. ........... Рис. 10.15. Последовательность выполнения инструкции write Процесс записи данных в массив памяти должен быть следующим. 1. Выполнить инструкцию WREN, чтобы разрешить запись. 2. На вывод /S подать О В. 3. Передать инструкцию 0000 ООН для записи данных в первую по- ловину памяти (адреса с 0 по 255) или 0000 1011 — для записи во вторую половину (адреса с 256 по 511). 4. Передать адрес байта (восьмиразрядное число, т.е. не больше 255), с которого начинать запись данных. Если адрес больше 255, то ад- рес 256 считается нулевым. 5. Передать от одного до 16 байт данных (размер одной страницы) для записи. 6. На вывод /CS памяти подать +5 В. Если после передачи одного байта для записи продолжать переда- вать данные, то они будут записаны в последующие ячейки памяти, но не более 16 байт. Когда достигнут конец текущей страницы, и запись продолжается, данные начнут перезаписываться с начала текущей страницы (одна страница —16 байт). Инструкция WRITE не будет принята и не выполнится, если: • уже выполняется цикл записи; • вывод защиты от записи /W в состоянии лог. 0 или предпринята по- пытка записи в область, защищенную разрядами ВР1 и ВРО регист- ра состояния. Программа Программа представлена в листинге 10.3.
226 Глава ю ||Як Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\ 10.03 - SPI - EEPROM. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <stdlib.h> // Общие утилиты (в данном примере нас // интересует функция atoi) #include <stdio.h> H Стандартные средства ввода-вывода для // USART) (в данном примере нас интересуют // функции fgets, putchar, gets, scanf_P) #include <util/delay.h> // Для доступа к функциям циклов // задержки #include <avr/pgmspace.h> // Утилиты для работы с Flash- // памятью #include <string.h> // Утилиты для работы со строками // (в данном примере нас интересует // функция strlen) // Функция инициализации нтерфейса SPI void SPI_MasterInit() { // Выводы /SS, SCK, MOSI (линия передачи данных) - выходы // РВ4 - /SS, РВ7 - SCK, РВ5 - MOSI DDRB |= (1 « 4) | (1 « 7) | (1 « 5); PORTB |= (1 « 4); //На выводе /SS устанавливаем 5 В // (устройство, подключенное к SPI, // отключено) DDRB &= ~(1 « 6);// Вывод MISO (РВ6) - вход (по этой линии // принимаем байт от внешних устройств) // Включаем SPI, режим "Master”, частота тактового сигнала // на выводе SCK = fck/2 = 10 МГц /2=5 МГц // SPR0 (разряд 0 регистра SPCR) = 0 - частота тактового // сигнала SCK = fck/2 // SPR1 (разряд 1 регистра SPCR) = 0 - частота тактового // сигнала SCK = fck/2 // СРНА (разряд 2 регистра SPCR) = 0 - обработка данных по // переднему фронту сигнала // CPOL (разряд 3 регистра SPCR) = 0 - импульсы положительной // полярности // MSTR (разряд 4 регистра SPCR) = 1 - режим работы SPI - // "Master" // DORD (разряд 5 регистра SPCR) = 0 - передача данных, // начиная со старшего разряда
Интерфейсы передачи данных ATmega16 227 // SPE (разряд 6 регистра SPCR) = 1 - SPI-интерфейс включен // SPIE (разряд 7 регистра SPCR) = 0 - прерывания запрещены SPCR « (1 « 6) | (1 « 4); SPSR |« (1 « 0); // SPI2X (разряд О регистра SPSR) = 1 - // частота тактового сигнала SCK = fck/2 } // Функция приема байта по интерфейсу SPI unsigned char SPI_MasterReseive() { SPDR = OxFF; // Для того чтобы начать прием, необходимо // начать передавать импульсы SCK, поэтому // передаем по интерфейсу SPI любой байт while(-SPSR & (1 << 7)){}; // Ожидаем до тех пор, пока байт // отправится (появится 1 в // разряде SPIF регистра SPSR return SPDR; //Возвращаем принятый байт } // Функция передачи байта по интерфейсу SPI unsigned char SPI_MasterTransmit(char eData) { SPDR = eData; // Передаем байт в сдвиговый регистр SPDR while (-SPSR & (1 « 7)) {}; // Ожидаем отправки байта // (появится 1 в разряде SPIF // регистра SPSR) return SPDR; // Возвращаем принятый байт } // Функция инициализации USART void USART_INIT(unsigned int UBRR_baud) { // Устанавливаем скорость передачи/приема. Она записывается // в два регистра: четырехразрядный UBRRH и восьмиразрядный // UBRRL. Для начала значение, хранящееся в UBRR_baud, // сдвигаем в право на 8 разрядов. UBRRH « (unsigned char) (UBRR_baud » 8); // Поскольку выполняется явное преобразование // к восьмиразрядному числу, в UBRRL будет записано 8 младших // разрядов, а все старшие после восьмого разряда будут // отброшены UBRRL = (unsigned char)UBRR_baud; // Для обращения к регистру UCSRC необходимо записать 1 // в разряд URSEL, и одновременно с этим установить // требуемые нам разряды в регистре UCSRC.
228 Глава Ю // Режим работы асинхронный: разряд UMSEL=0. Отключен // контроль по четности: разряды UPM1=O и UPM0=0. Один стоп- // битов: разряд USBS=0. Восемь бит данных: разряды UCSZ1=1, // UCSZ0=l, разряд UCSZ2 регистра UCSRB =0 //В асинхронном режиме разряд UCPOL=0 UCSRC = (1 « URSEL) | (0 « UMS EL) | (0 « UPM1) | (0 « UPM0) | (0 « USBS) | (1 « UCSZ1) | (1 « UCSZO) | (0 « UCPOL); UCSRB &= ~(1 « 2); // UCSZ2=0 (8 бит данных) // Включаем приемник и передатчик USART UCSRB |= (1 « 4) | (1 « 3) ; // Разряды RXEN=1 и TXEN=1 // Разряды U2X=0 и MPCM=0, т.е. отключаем удвоение скорости //и режим мультипроцессорного обмена UCSRA &= - (1 « 1) & - (1 « 0) i } // Функция, которая будет вызываться при обращении потока на // вывод static int uart_putchar(char simvol, FILE *stream) { // Ожидаем освобождения регистра данных, т.е. когда он будет // готов принять символ для отправки (установится 1 в разряде // UDRE) loop_until_bit_is_set(UCSRA, 5); UDR = simvol; // Передаем символ, содержащийся // в переменной simvol, в порт UART return 0; }; // Функция, которая будет вызываться при обращении потока на // ввод static int uart_getchar(FILE *stream) { // Ожидаем, пока завершится прием символа в UART (установится // 1 в разряде RXC) loop_until_bit_is_set(UCSRA, 7); return UDR; // Функция вернет принятый символ } // Инициализация ввода-вывода // Создаем поток, назначив функцию uart_putchar для вывода // данных, a uart_getchar - для ввода. Используем ввод-вывод, // установив флаг __FDEV_SETUP_RW static FILE mystdout_mystdin =
Интерфейсы передачи данных ATmega16 229 FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP__RW); // Функция RDSR передает no SPI в память EEPROM код инструкции // RDSR. В ответ получает значение регистра состояния static char RDSR() { char spi_receive; // Переменная для хранения содержимого // регистра состояния PORTB &= ~(1 « 4); //На вывод /SS подаем О В // (разрешаем устройству, // подключенному к SPI, передавать // нам информацию) SPI—MasterTransmit(0x05) ; // Отправляем по SPI // инструкцию 0000 Х101 spi_receive = SPI_MasterReseive(); // Принимаем содержимое // регистра состояния PORTB |= (1 « 4); //На вывод /SS подаем 5 В // (запрещаем устройству/ // подключенному к SPI, передавать // нам информацию) return spi_receive; // Функция возвращает содержимое // регистра состояния } // Функция WREN устанавливает триггер разрешения записи по SPI // в память EEPROM static void WRENO { // Если память EEPROM занята процессом записи, то ожидаем, // когда она освободится. Ожидаем появления 0 в разряде WIP // регистра состояния (т.е. микросхема освободится от // предыдущего цикла записи) do {} while (RDSR() & (1 « 0)); PORTB &= ~(1 « 4); // На вывод /SS подаем 0 В SPIJMasterTransmit (0x06) ; // Отправляем по SPI // инструкцию 0000 XII0, // разрешающую нам запись PORTB |= (1 « 4); //На вывод /SS подаем +5 В // Функция WRDI сбрасывает триггер разрешения записи по SPI // в память EEPROM static void WRDI() { PORTB &= ~ (1 << 4); // На вывод /SS подаем 0 В SPIJMasterTransmit (0x04) ; // Отправляем no SPI
230 Глава Ю уЖ ПродолжеЙдатУЖ ЯЯ Я^ЯВЯЯЯШЯЯЯЯвЯЯЯЯшЯ^ЯЯЯЯЖ^^Ш // инструкцию 0000 ХЮ0 сброса // разрешения записи PORTB |= (1 « 4); // На вывод /SS подаем +5 В } // Функция WRSR. Запись нового значения в регистр состояния, static void WRSR(unsigned int status_register) { WREN(); // Вызываем функцию, которая передает памяти SPI // инструкцию разрешения записи PORTB &= ~(1 << 4); // На вывод /SS подаем 0 В // (задействуем устройство, // подключенное к шине SPI, // разрешаем ему обмен данными с МК) SPI_MasterTransmit(0x01) ; // Отправляем по SPI код // инструкции записи регистра // состояния 0000 Х001 SPI_MasterTransmit(status_register); // Передаем новое // значение регистра состояния PORTB |= (1 « 4); //На вывод /SS подаем 5 В // (отключаем устройство, // подключенное к шине SPI, // запрещаем ему обмен данными с МК) } // Функция READ чтения данных из массива памяти SPI static void READ() { // Принимаем по USART число (адрес, по которому необходимо // начать чтение памяти SPI) printf_P(Р8ТВ("\г\пВведите номер адреса памяти (от 000 до 511), с которого начинать чтение данных(CTRL+J для завершения)\г\п")); //выводим инструкцию char from_uart_address[4]; // Массив из 4-х байт // Принимаем максимум 3 байта в массив from_uart_address из // потока stdin(USART) fgets(&from_uart_address[0], 4, stdin); int address =0; // Переменная для хранения адреса памяти, // по которому начинается чтение данных address = atoi(from_uart_address); // Преобразуем массив // from_uart_address // в переменную типа // int (address) // Принимаем по USART число: количество байт для чтения (если // всю память, - 511) printf_P(PSTR(н\г\пВведите количество байт, которые
1/1нтерфейсы передачи данных ATmega16 231 необходимо считать из SPI микросхемы (от 001 до 512) (CTRL+J для завершения)\г\п")); // Выводим инструкцию char from_uart_kolichestvo_bait[4]; // Массив из 4х байт // Принимаем в массив from_uart_kolichestvo_bait по USART // количество байт, которые необходимо считать из памяти SPI fgets(from_uart_kolichestvo_bait, 4, stdin); int kolichestvo_bait =0; // Переменная для хранения // количества байт, которые // необходимо считать из // памяти SPI // Преобразуем массив from_uart_kolichestvo_bait в переменную // типа int (kolichestvo_bait) kolichestvo_bait = atoi(from_uart_kolichestvo_bait); // Если память EEPROM занята процессом записи, то ожидаем, // когда она освободится. Если разряд WIP регистра состояния // равен 1, то память занята процессом записи. Если 0, то // память готова к следующему циклу записи do {} while (RDSR() & (1 « 0)); printf_P(PSTR("\г\пЗапрошенные ячейки памяти:\r\n")); // Считываем из памяти байты, начиная со значения, хранимого // в переменной address, общее количество считываемых байт, // хранимое в переменной kolichestvo_bait PORTB &= ~ (1 << 4); // На вывод /SS подаем 0 В // (задействуем устройство, // подключенное к шине SPI, // разрешаем ему обмен данными с МК) // Отправляем по SPI код инструкции 0000 ООН для чтения // данных из первой половины памяти (адреса с 0 по 255) или // код 0000 1011 из второй половины памяти (адреса с 256 по // 511). if (address > 255) { SPI_MasterTransmit(0x0В); } else { SPI_MasterTransmit(0x03); } SPI_MasterTransmit(address) ; // Отправляем адрес, с // которого начать считывание // В цикле принимаем байты из памяти, и выводим их через USART for (int i=0; i < kolichestvobait; i++) { putchar(SPIJMasterReseive()); } // Считать байт данных
232 Глава Ю PORTB |= (1 « 4); // На вывод /SS подаем 5 В // (отключаем устройство, // подключенное к шине SPI, } // запрещаем ему обмен данными с МК) // Функция WRITE записи по SPI в память EEPROM static void WRITE О { // Принимаем по USART данные для записи printf_P(PSTR("\г\пВведате от 1 до 16 байт данных (размер одной страницы) для программирования SPI памяти, (CTRL+J для завершения)\г\п")); char from_uart_data[17]; // Переменная для хранения 16 байт // данных. Предусмотрено место под // 16-й элемент для символа конца // строки (если будут заполнены все /7 15 элементов) // Принимаем байты по USART в массив from_uart_data для // записи в микросхему памяти gets(&from_uart_data [0]);// Функция gets принимает байты //в массив from__uart_data и // автоматически завершает символом // '\0’ следующий элемент после // записанного. Кроме того, функция //не записывает в массив символ // перевода строки. // Принимаем по USART число (адрес памяти, с которого // необходимо начать запись в микросхему памяти) printf_P(РЗТК(н\г\пВведите адрес байта (от 0 до 511), с которого начинать программирование данных, (CTRL+J для завершения)\г\пн)); char from_uart_address[4]; // Массив для хранения 3-х // символов в ячейка [0]- [2] // и завершающего символа \0 // в ячейке [3] // Принимаем в массив from_uart_address по интерфейсу USART // число бадрес памяти, с которого начать программирование). // Функция принимает количество символов -1, поэтому 4. fgets(from_uart_address, 4, stdin); long address =0; // Переменная для хранения адреса памяти, //с которого начинается запись данных // Преобразуем переменную типа char (from_uart_address) // в переменную типа int (address) address = atoi(&from_uart_address[0]);
Интерфейсы передачи данных ATmega16 233 // Запись в микросхему памяти, начиная с адреса address // байтов from_uart_data. Перед записью необходимо установить // триггер разрешения записи WREN(); // Функция, которая передает // разрешения записи PORTB &= ~(1 << 4); // На вывод /SS памяти SPI инструкцию подаем О В // (задействуем устройство, // подключенное к шине SPI, // разрешаем ему обмен данными с МК) // Отправляем по SPI код инструкции программирования памяти // ООО 0010, если запись в первую половину памяти или ООО // 1010, если запись во вторую половину памяти if (address > 255) { SPI_Mas terTransmit (0x0 А) ; address = address - 256; } else { SPI_MasterTransmit(0x02); } SPI_MasterTransmit(address); // Отправляем адрес, // с которого начинать запись // В цикле передаем памяти SPI данные для записи for (int i=0; i<strlen(from_uart_data); i++) { SPI_MasterTransmit(from_uart_data[i]); // Отправляем // один элемент массива } PORTB |= (1 « 4); //На вывод /SS подаем 5 В // (отключаем устройство, // подключенное к шине SPI, // запрещаем ему обмен данными с МК) } // Функция READ_ALL чтения всей памяти SPI, Вывод ее // содержимого через USART static void READ_ALL() { // Если память EEPROM занята процессом записи то, ожидаем, // когда она освободится. Если разряд WIP регистра состояния // равен 1, то память занята процессом записи. Если 0, то // память готова к следующему циклу-записи do {} while (RDSRO & (1 << 0)) ; printf_P(PSTR(и\г\пПамять содержит:\r\n")); ' PORTB &= ~jl « 4); //На вывод /SS подаем 0 В // (задействуем устройство,
234 Глава Ю // подключенное к шине SPI> // разрешаем ему обмен данными с МК) // Отправляем по SPI код инструкции чтения памяти 0000 А8011 // (начать чтение с первой половины памяти) SPI_MasterTransmit(0x03); //.Первая страница памяти SPIMasterTransmit(0); // Отправляем адрес, с которого // начать считывание (ячейка 0) int perenos_stranici =15; // В цикле принимаем байты из памяти и выводим их по USART for (int i=0; i<512; i++) // От адреса 0 до 511 (всего { // 512 байт) putchar(SPI_MasterReseive()); // Считать байт данных // и отправить по USART if (perenos_stranici <= i) { // Вывод форматированный по 16 байт на одну строку printfj? (PSTR("\r\n")); perenos_stranici = perenos_stranici + 16; } } PORTB |= (1 « 4); //На вывод /SS подаем 5 В // (отключаем устройство, // подключенное к шине SPI, // запрещаем ему обмен данными с МК) } int main(void) { USART_INIT(0x40); // Инициализация USART. Передаем функции // значение регистра UBRR: 64 - 9600бод при // 10 МГц // Перенаправляем стандартные потоки на пользовательский stdin = stdout = &mystdout_mystdin; SPIMasterlnit(); // Инициализации интерфейса SPI в режиме // "Master” while (1) // Бесконечный цикл { // Выводим через USART меню printf P (PSTR( "\r\n------\г\пМеню:\r\nl)Инструкция WREN - Установка триггера разрешения записи\г\п")); printf_Р(PSTR(”\r\n2)Инструкция WRDI - Сброс триггера разрешения записи\г\пи)); printf_P(PSTR("\r\n3)Инструкция RDSR - Чтение регистра состояния\г\пн));
Интерфейсы передачи данных ATmega16 235 printf_Р(PSTR("\r\n4)Инструкция WRSR - Запись регистра состояния\г\п”)); printf_P(PSTR("\г\п5)Инструкция READ - Чтение данных с массива памяти\г\п”)); printf_P(PSTR("\r\n6)Инструкция WRITE - Запись данных в массив памяти\г\п")); printf_Р(PSTR("\r\n7)Чтение всей SPI памяти и вывод её содержимого\г\п")); printf_P (PSTR("cmd: ")) ; // Выводим приглашение // командной строки char choose; choose = getcharO; // Принимаем через USART символ // в переменную choose // Если выбрали "О”, то выходим из бесконечного цикла if(choose == ’О’) { printf_P(PSTR("\г\пВыхожу...")); break; } // В зависимости от выбранного пункта меню вызываем ту или // иную функцию работы с памятью по SPI switch (choose) { case 'I1: WREN(); break; case 1 21 : WRDIO; break; case ’3’: >{ printf_P(PSTR("Чтение регистра состояния: %x(hex)\r\n"), RDSR()); }; break; case 141: { // Принимаем no USART число (новое значение // регистра состояния) printf_P(PSTR("\г\пВведите новое значение регистра состояния(hex)\r\n")); // Переменная для хранения нового значения // регистра состояния unsigned int stat_reg; // Принимаем число в шестнадцатеричном // формате в переменную stat_reg scanf_P(PSTR("%x"), &stat_reg); // Вызываем функцию записи регистра состояния WRSR(stat_reg); }; break; case '5': READ(); break; case 1 61: WRITE(); break; case 1 71: READ_ALL(); break;
236 Глава Ю .................... ............................... «« default: printf_P(PSTR("???\r\n“)); } } ргз.пЪ^РСРЗТРСХгХпвышли")) ; } Экран программы HyperTerminal для чтения всей памяти EEPROM показан на рис. 10.16. ||Ж^11И111В|ЖВв рЖЗШйВО^ШЖ^ввЖжШв р|.;ЖМйЙОШяОЙШЖ1в|в {£ЖйЖРШ;ЖЙй1О1Вй1ШВЛ |:Ж^й1ЖйШЖ!йШ1ШЖОШ1£1Вв йШЖЖ£5Ш§ШйИ1^^ рЖй^ММЖйЖШШОЖЖУЖжШ^ 1ЖШшВв? |Z Ь^Ж^Ж^ЖЖйЖ5яйВЖоШ£В||Ж ;:- <^4йЯ:Шя;яйжЬ®®:йЖ£11в^^^^ Г-;ЙЙ^И^МЙЙ*ЖВ^|^ ^яЖхя^:ял^:айЖйЖЖ1О1ШЖ^вЖ1Ш®^Жж^Ш К;.:.< ЯШ*zЙ:&Й.Й!ЯЙЯ::Ш:;Ш;Х®^^ Рис. 10.16. Чтение всей памяти EEPROM в программе HyperTerminal Интерфейс SPI. Работа с датчиком температуры ТС77 Все выводы портов А и С настроены как выходы. К ним подключе- ны светодиоды для отображения полученного значения температуры от термодатчика ТС77 (рис. 10.17).
Интерфейсы передачи данных ATmega16 237 Рис. 10.17. Схема для исследования работы датчика тепературы
238 Глава Ю Для использования выводов РС2-РС5 порта С для ввода-вывода не- обходимо отключить интерфейс JTAG (рис. 10.18). IA JTAGEM.......... П '<™~—........................ОТКЛЮЧИЛИ M SHEN...........'...'Ц.............................................. EESAVE " ' Q BOOTSZ i Boot Flash size=1G24 words start address=$1 COO П BOOTRST .......П j' 1 скор!...........П | Й BODLEVEL ‘' B?om-out detection at VCC=27V П BODEN Г] [r;] SUT_CKSEL ; Ext. Crystal/Resonator High Freq.; Start-up time: 1К CK. + 64 m iiii Рис. 10.18. Должен быть сброшен флажок JTAGEN В программе инициализация интерфейса SPI реализована в функ- ции SPI_MasterInit (). Поскольку микроконтроллер используется как ведущее устройство, вывод SCK (тактовый сигнал) назначаем как выход (тактирующих импульсов для ведомого устройства). Вывод SS ведомого устройства определяем как выход. Он будет подключен к выводу CS датчика температуры Т77. На вывод SS подаем +5 В. Согласно диаграмме, датчик передает значение температуры по низкому уровню сигнала на выводе CS, т.е. изначально ТС77 для обме- на данными не выбран. Вывод MISO, по которому будем принимать значения температуры, определяем как вход. Вывод MOSI, который служит для передачи дан- ных ведомым устройством (микроконтроллером) ведущему, мы исполь- зовать не будем.
Интерфейсы передачи данных ATmega16 239 С помощью-регистров SPCR и SPSR задаем тактовую частоту на выводе SCK: уск / 16 = 10 МГц / 16 = 625 КГц. Максимальная частота обмена данными для датчика температуры, согласно спецификации, со- ставляет 7 МГц. Включаем модуль SPI, задаем режим “Master”. Полярность тактово- го сигнала (разряд CPOL) SCK: высокий уровень, когда есть данные, низкий, когда данных нет. Фаза тактового сигнала (разряд СРНА): обра- ботка данных по переднему фронту импульсов SCK После инициализации SPI в бесконечном цикле принимаем содер- жимое регистра TEMP ведомого устройства (ТС77) и отображаем его на светодиодах. Для того чтоб начать прием данных от ведомого устройства, его не- обходимо вначале выбрать. Для этого подаем 0 В на вывод термодатчи- ка SS. Принимаем байт с помощью функции SPI_MasterReseive (). Для приема байта данных необходимо запустить тактовые импуль- сы SCK. Для этого достаточно передать датчику температуры любые данные. Мы передаем байт 11111111, записав его в восьмиразрядный сдвиговый регистр SPDR. Одновременно с передачей происходит прием в восьмиразрядный буфер. Окончанием обмена данными будет наличие единицы в разряде SPIF регистра SPSR. После этого можно считать со- держимое буфера, обратившись к сдвиговому регистру SPDR. Возвра- щаем принятый байт и выводим его в порту А для отображения на све- тодиодах. Регистр TEMP, содержащий в ТС77 значение температуры, состоит из двух байт, поэтому в программе необходимо еще раз вызвать функ- цию SPI_MasterReseive (). Принятое значение выводится в порт С. Затем отключаем прием данных от ТС77 и ожидаем 400 мс окончания следующего преобразования. Преобразование значения температуры из двоичной в десятичную форму Значение температуры поступает от датчика в виде двух байт (раз- ряды 0-15), из которых используются только 12 (3-15). Старшему байту (разряды 7-15) соответствуют целая часть значения и знак, а младшему (разряды 3-6) — дробная часть. Единице в том или ином разряде (кроме 15) соответствует определенный весовой коэффициент (рис. 10.19). Бит 15 14 13 12 11 10 9 8 7 6 5 4 3 Значение Знак 128 64 32 16 8 4 2 1 0,5 0,25 0,125 0,0625 .Рис. 10.19. Формат значения температуры, выдаваемого датчиком ТС77
240 Глава 10 Для того чтобы представить значение положительной температуры в десятичной системе, необходимо сложить все весовые коэффициенты для тех разрядов, в которых указана единица. Например: 0010 1010 1,001 0 = (+)64+ 16 + 4+ 1 + 0,125 = 85,125 (°C); ООП 1110 1,000 0 = (+) 64 + 32 + 16 + 8 + 4+ 1 = 125 (°C). В микроконтроллере для перевода положительного значения темпе- ратуры в градусы Цельсия необходимо умножить его на 0,0625 или раз- делить на 16. Умножается на 0,0625 потому, что единица полученного значения — это 0,0625 градусов. Например: 000000111000 = 56, т.е. 56 раз по 0,0625 =3,5 (°C). Отрицательная температура (1 в разряде 15) переводится из двоич- ной системы счисления в десятичную несколько иначе. Значение инвер- тируется (т.е. все нули заменяются единицами, и наоборот) и к нему прибавляется 1 в младшем разряде. Таким образом получается так на- зываемый дополнительный код, который затем преобразуется в значе- ние температуры, как было описано выше. Например, получено значение 1111 1111 1,111 1. Инвертируем его в 1000 0000 0,0000 и прибавляем 1: 1000 0000 0,0001 = -0,0625 (°C). Еще два примера... Получено значение 1111 ООН 1,000 0. После инвертирования: 1000 1100 0,1111. После добавления единицы в младший разряд: 1000 1100 1,0000, что соответствует (-) 16 + 8 + 1 = -25 (°C). Получено значение 111110000,1011. После инвертирования: 1000 0111 1,0100. Дополнительный код: 100001111,0101, что соответствует (-) 8 + 4 + 2 + 1 + 0,25 + 0,0625 = 15,3125(°С). Для перевода отрицательного значения в микроконтроллере необ- ходимо умножить его на -1 для получения дополнительного кода, а за- тем умножить на 0,0625. Например, получили значение 111100001011. Умножаем его на -1, чтобы получить 000011110101 = 245. Теперь 245 • 0,0625 = 15,3125 (°C). Сигнал на линии данных датчика для значения 28 градусов Цельсия (двоичное 00001110 00000111) показан на рис. 10.20. Верхний график —• сигнал на входе /CS, средний — на входе SCK (тактовые импульсы), нижний — на линии MISO (значение температуры).
Интерфейсы передачи данных ATmega16 241 Рис. 10.20. Сигналы на выводах датчика температуры ТС77 Программа Программа, реализующая прием данных от датчика температуры по интерфейсу SPI представлена в листинге 10.4. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegai6\io. 04 - spi - Датчик температуры. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <util/delay.h> // Для доступа к функциям циклов // задержки // Функция инициализации нтерфейса SPI void SFI_MasterInit() { // Выводы /SS, SCK, MOST (линия передачи данных) - выходы // РВ4 - /SS, РВ7 - SCK, РВ5 - MOSI DDRB |= (1 « 4) | (1 « 7) | (1 « 5); PORTB |= (1 « 4); //На выводе /S3 устанавливаем 5 В
242 Глава Ю // (устройство, подключенное к SPI, // отключено) DDRB &= ~(1 << б);// Вывод MISO (РВ6) - вход (по этой линии // принимаем байт от внешних устройств) // Включаем SPI, режим "Master”, частота тактового сигнала // на выводе SCK = fck/16. Прерывания от SPI запрещены SPSR &= ~(1 « 0); // SPI2X=0 SPCR = (1 « б) | (1 « 4) | (1 « 0);// SPE=1, MSTR=1, // SPRO=1 SPCR &= ~(1 << 3); // CPOL=0 - импульсы положительной // полярности SPCR &= -(1 << 2); // CPHA=0 - обработка данных по // переднему фронту сигнала SPCR &= ~(1 « 5); // DORD=0 - передача данных, начиная // со старшего разряда } // Функция приема байта по интерфейсу SPI unsigned char SPI_MasterReseiveО { SPDR = OxFF; // Для того чтобы начать прием, необходимо // начать передавать импульсы SCK, поэтому // передаем по интерфейсу SPI любой байт while(-SPSR & (1 << 7)){}; // Ожидаем до тех пор, пока байт // отправится (появится 1 в // разряде SPIF регистра SPSR return SPDR; //Возвращаем принятый байт } int main(void) { // Порты А и С используем для подключения светодиодов DDRA = OxFF; // Выводы порта А - выходы PORTA = 0x00; // На выводы порта А подаем 0 В DDRC = OxFF; // Выводы порта С - выходы PORTC = 0x00; // На выводы порта С подаем 0 В SPI_MasterInit(); // Инициализации интерфейса SPI _delay_ms(300); // Задержка перед первым приемом 300 мс // (после подачи питания), согласно // спецификации ТС77 while (1) // Бесконечный цикл {
1/1нтерфейсы передачи данных ATmega16 243 PORTB &= ~(1 << 4); // На вывод /SS подаем О В // Принимаем и выводим на светодиоды принятые два байта PORTA = SPI_MasterReseive{); // Старшие 8 разрядов регистра // TEMP (15-8) PORTC = SPI_MasterReseive(); // Младшие 8 разрядов регистра // ТЕМР(7-0) PORTB |= (1 « 4); // На вывод /SS подаем +5 В _delay_ms(400); // Задержка перед следующим приемом } } Интерфейс SPI. Работа с Flash-памятью К интерфейсу SPI микроконтроллера подключена микросхема Flash памяти AT25F2048 объемом 256 Кбайт (262 144 байт). Пространство адресов поделено на четыре сектора по 64 Кбайт (64 х 1 024 = 65 536 байт) каждый — 256 страниц на сектор (65 536 байт / 256 = 256 байт од- на страница). Микросхема памяти может обмениваться данными в од- ном из двух режимов: SPI MODE 0 или SPI MODE 3. Максимальная частота тактового сигнала SCK — 20 МГц. Для наглядности операций с памятью (чтение, запись, очистка) мы будем использовать интерфейс USART микроконтроллера, подключен- ный к COM-порту компьютера (рис. 10.21). Микроконтроллер через USART передает меню работы с Flash-памятью (рис. 10.22). В зависи- мости от того, какой пункт выбран (на компьютере), выполняется то или иное действие с памятью через интерфейс SPI. В начале программы инициализируются два интерфейса микрокон- троллера: USART (функция USART_INIT ()) и SPI (функция SPl_MasterInit ()). В функции инициализации интерфейса SPI назначаем выводы: • /SS — выбор подчиненной, микросхемы; • SCK — тактовая частота SPI-интерфейса; • MOSI — настраиваем как выход (для подчиненного устройства — вход); • MISO — настраиваем как вход (для подчиненного устройства — выход). Поскольку микроконтроллер управляет подчиненным устройством с помощью регистра SPCR, задаем режим “Master”. Используя регистр SPCR и SPSR, задаем тактовую частоту на выводе SCK/ск /4=10 МГц /4 = 2,5 МГц.
244 Глава Ю Рис. 10.21. Схема соединений для работы с Flash-памятью по интерфейсу SPI
Интерфейсы передачи данных ATmega16 245 Рис. 10.22. Меню команд для работы с Flash-памятью В бесконечном цикле с помощью функции printf_P выводим че- рез USART микроконтроллера в COM-порт компьютера меню для рабо- ты с Flash-памятью. Функция print f_P выводит данные, которые хра- нятся в памяти программ, чтобы не занимать ОЗУ микроконтроллера. Данные объявлены как PSTR (). Это тип, объявленный в файле <avr/ pgmspace.h> как ((const PROGMEM char *) (s)), т.е. данные, которые находятся в памяти программ микроконтроллера. После вывода меню функция getchar () ожидает передачи сим- вола от компьютера, т.е. выбора пункта меню. Принятый символ запи- сывается в переменную choose. В зависимости от ее содержимого, оператор switch выполняет ту или иную последовательность дейст- вий. Если принят символ “1”, то вызывается функция RDID (), которая передает по интерфейсу SPI Flash-памяти) инструкцию 0001 XI01. После передачи инструкции, которая занимает восемь циклов (тактовых сигналов SCK), мы принимаем от микросхемы памяти ответ (код изго- товителя и идентификационный код) и передаем его через USART мик- роконтроллера в COM-порт компьютера. Описание микросхемы AT25F2048 Схема питания микросхемы Flash-памяти AT25F2048 представлена на рис. 10.23.
246 Глава ю v... 3.7V - 5.5V IN OUT LT17S1-3.3 SHDN BYP GND 3.3V/100 mA w J pF 0.01 pF i. OUT JO pF Рис. 10.23. Схема питания микросхемы AT25F2048 Инструкции управления Flash-памятью перечислена в табл. 10.2. Таблица 10.2. Инструкции управления Flash-памятью Инструкция Код Операция WREN 0000X110 Установка триггера разрешения записи WRDI 0000X100 Сброс триггера разрешения записи RDSR 0000X101 Чтение регистра состояния WRSR 0000X001 Запись регистра состояния READ 0000X011 Чтение данных из массива памяти PROGRAM 0000X010 Запись данных в массив памяти SECTOR ERASE 0101X010 Стереть один сектор в массиве памяти CHIP ERASE 0110X010 Стереть все сектора в массиве памяти RDID 0001X101 Считать код изготовителя и идентифика- ционный номер продукта *х — 0 или 1 Инструкция rdid — “Получение кода изготовителя и идентифи- кационного номера устройства” Последовательность выполнения инструкции RDID иллюстрирует рис. 10.24. 1. На вывод /CS памяти подать 0 В. 2. Передать инструкцию 0001 Х101. 3. Принять байт данных (код изготовителя). 4. Принять байт данных (номер устройства). 5. На вывод /CS памяти подать +5 В.
Интерфейсы передачи данных ATmega16 247 О 1 2 3 4 5 в 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 SCK JWUUJOUUUVLBlUJUjluIJlJL »~й. ° % > Ло/фгтгх пттгт HIGH IMPEDANCE SO — " > - — DATA OUT MANUFACTURER DEVtCE CODE CODE fATMEL) Рис. 10.24. Последовательность выполнения инструкции rdid Инструкция rdsr — “Чтение регистра состояния памяти” Последовательность выполнения инструкции RDSR иллюстрирует рис. 10.25. 1. На вывод /CS памяти подать 0 В. 2. Передать инструкцию 0000 Х101. 3. Принять байт данных (регистр состояния). 4. На вывод /CS памяти подать +5 В. Регистр состояния Flash-памяти AT25F2048 показан на рис. 10.26.
248 Глава Ю Бит 7 Бит 6 Бит 5 Бит 4 БитЗ Бит 2 Бит1 БитО WPEN — — — ВР1 ВРО WEN /RDY~~ Рис. 10.26. Регистр состояния Flash-памяти AT25F2048 Назначение разрядов регистра состояния: • /RDY: 0 — устройство готово; 1 — происходит цикл записи; • WEN: 0 — включено разрешение записи; 1 — разрешение записи отключено; • ВР0:ВР1 — определяют, какие сектора памяти будут защищены от записи; • WPEN — разрешает/запрещает запись независимо от состояния вы- вода /WP. Аппаратная зашита от записи включена, когда вывод /WP находится в состоянии лог. 0 и WPEN = 1. Аппаратная зашита от записи отключена, когда вывод /WP находится в состоянии лог. 1 или WPEN = 0. Функция вывода /WP блокирована, когда WPEN = 0, и активна, когда WPEN = 1. Инструкция wrsr — “Программирование регистра состояния памяти” Последовательность выполнения инструкции WRSR иллюстрирует рис. 10.27. 1 О 1 2 3 4 5 6 7 8 S 10 11 12 13 34 15 sc* лълллплллл гшллал л„ , , DATA IN SI Х4 INSTRUCTION______________6 5 4 Х'зХйХ 1 so Рис. 10.27. Последовательность выполнения инструкции wrsr 1. На вывод /CS памяти подать 0 В. 2. Передать инструкцию 0000 Х001. 3. Передать байт данных (регистр состояния). 4. На вывод /CS памяти подать +5 В.
Интерфейсы передачи данных ATmega16 249 Инструкция program— “Запись данных в массив памяти” Последовательность выполнения инструкции WRSR иллюстрирует рис. 10.28. HIGH IMPEDANCE Рис. 10.28. Последовательность выполнения инструкции program Перед программированием памяти необходимо выполнить инст- рукцию WREN (разрешить запись). После программирования разрешение записи автоматически сбросится. 1. На вывод /CS памяти подать 0 В. 2. Передать инструкцию 0000 Х010. 3. Принять три байта, указывающие с какого адреса начинать запись. 4. Передать 256 байт данных. 5. На вывод /CS памяти подать +5 В. Одна инструкция записи не может записать больше 256 байт за раз. Если размер записываемых данных меньше 256 байт, то все остальные данные останутся неизмененными. Если в процессе записи достигнут конец страницы (256 байт), то программирование продолжится с перво- го байта текущей страницы. Один и тот же байт не может быть перезаписан без стирания всего сектора. В микросхеме AT25F2048 всего четыре сектора: • 1 — адреса 000000..00FFFF (от 0 до 65 535 байт); • 2 — адреса 010000..01FFFF (от 65 536 до 131 071 байт); • 3 — адреса 020000..02FFFF (от 131 072 до 196 607 байт); • 4 — адреса O3OOOO..O3FFFF (от 196 608 до 262 143 байт).
250 Глава Ю Инструкция chip erase — “Очистка всех секторов в массиве памяти” Последовательность выполнения инструкции CHIP ERASE иллю- стрирует рис. 10.29. 0 1 2 3 4 5 6 7 sck _JTTLJlBJirLBJl si х °A\° HIGH IMPEDANCE SO ------------------------------ Рис. 10.29. Последовательность выполнения инструкции chip erase Перед очисткой микросхемы памяти необходимо выполнить инст- рукцию WREN (разрешить запись). После очистки разрешение записи автоматически сбросится. 1. На вывод /CS памяти подать 0 В. 2. Передать инструкцию ОНО Х010. 3. На вывод /CS памяти подать +5 В. Инструкция sector erase — “Очистка одного сектора в масси- ве памяти” Последовательность выполнения инструкции SECTOR ERASE ил- люстрирует рис. 10.30. Перед очисткой сектора микросхемы памяти необходимо выпол- нить инструкцию WREN (разрешить запись). После очистки разрешение записи автоматически сбросится. 1. На вывод /CS памяти подать 0 В. 2. Передать инструкцию 0101 Х010. 3. Передать три байта, указывающие адрес сектора. 4. На вывод /CS памяти подать +5 В.
Интерфейсы передачи данных ATmega16 251 О 1 2 3 4 5 6 7 8 9 10 1128 20 30 31 SCK SI HIGH IMPEDANCE Рис. 10.30. Последовательность выполнения инструкции sector erase Инструкция read — “Чтение данных из массива памяти” Последовательность выполнения инструкции READ иллюстрирует рис. 10.31. Рис. 10.31. Последовательность выполнения инструкции read 1. На вывод /CS памяти подать 0 В. 2. Передать инструкцию.0000 Х011. 3. Передать три байта, указывающие адрес, с которого необходимо начать чтение данных. 4. Прочитать байт данных. Микросхема автоматически перейдет к сле- дующему адресу памяти. Таким образом, одной инструкцией можно прочитать всю память.
252 Глава Ю 5. На вывод /CS памяти подать +5 В. Инструкция wren — “Установка триггера разрешения записи” Последовательность выполнения инструкции READ иллюстрирует рис. 10.32. CS sck ___________________ттгитгтллп__________________________ в '///////////к X///////7777 so -------------—------,-------Ш-------------------------- Рис. 10.32. Последовательность выполнения инструкции wren Каждая операция записи памяти должна начинаться с инструкции разрешения записи. 1. Установить на выводе /CS 0 В. 2. Передать микросхеме памяти инструкцию 0000 Х110. 3. Установить на выводе /CS +5 В. Программа Программа представлена в листинге 10.5. Кл Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegai6\ю. 05 - SPI - Flash. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <stdlib.h> // Общие утилиты (в данном примере нас // интересует функция atoi) #include <stdio.h> // Стандартные средства ввода-вывода для // USART) (в данном примере нас интересуют // функции fgets, putchar, gets, scanf_P) #include <util/delay.h> // Для доступа к функциям циклов // задержки # include <avr/pgmspace.h> // Утилиты для работы с Flash- // памятью #include <string.h> // Утилиты для работы со строками // (в данном примере нас интересует
Интерфейсы передачи данных ATmega16 253 // функция strlen) // Функция инициализации нтерфейса SPI void SPIJMasterlnit() { // Выводы /SS, SCK, MOSI (линия передачи данных) - выходы // РВ4 - /SS, РВ7 - SCK, РВ5 - MOSI DDRB |= (1 « 4) | (1 « 7) | (1 « 5); PORTB |= (1 « 4); // На выводе /SS устанавливаем 5 В 4 // (устройство, подключенное к SPI, // отключено) DDRB &= ~(1 « б);// Вывод MISO (РВ6) - вход (по этой линии // принимаем байт от внешних устройств) // Включаем SPI, режим "Master", частота тактового сигнала // на выводе SCK = fck/4. Прерывания от SPI запрещены SPSR &= ~(1 « 0); // SPI2X=0 SPCR &= ~(1 « 1) & ~(1 « 0); // SPR1=O, SPR0=0 SPCR = (1 « б) | (1 « 4); // SPE=1, MSTR=1 // Микросхема памяти поддерживает режим Mode 0. // Устанавливаем его SPCR &= ~(1 « 3)i // CPOL=0 - импульсы положительной // полярности SPCR &« ~(1 « 2); // СРНА=0 - обработка данных по // переднему фронту сигнала SPCR &= ~(1 « 5); // DORD=0 - передача данных, начиная // со старшего разряда } // Функция приема байта по интерфейсу SPI unsigned char SPI_MasterReseiveО { SPDR = OxFF; // Для того чтобы начать прием, необходимо // начать передавать импульсы SCK, поэтому // передаем по интерфейсу SPI любой байт while(-SPSR & (1 << 7)){}; // Ожидаем до тех пор, пока байт // отправится (появится 1 в // разряде SPIF регистра SPSR return SPDR; //Возвращаем принятый байт } // Функция передачи байта по интерфейсу SPI unsigned char SPI_MasterTransmit(char eData) { // Передаем байт в сдвиговый регистр SPDR SPDR = eData;
254 Глава Ю } while (-SPSR & (1 « 7)) {}; // Ожидаем отправки байта и (появится 1 в разряде SPIF и регистра SPSR) return SPDR; и Возвращаем принятый байт // Функция инициализации USART void USART_INIT(unsigned int UBRR_baud) { // Устанавливаем скорость передачи/приема. Она записывается //в два регистра: четырехразрядный UBRRH и восьмиразрядный // UBRRL. Для начала значение, хранящееся в UBRR_baud, // сдвигаем в право на 8 разрядов. UBRRH = (unsigned char)(UBRR_baud » 8); // Поскольку выполняется явное преобразование // к восьмиразрядному числу, в UBRRL будет записано 8 младших // разрядов, а все старшие после восьмого разряда будут // отброшены UBRRL = (unsigned char)UBRR_baud; // Для обращения к регистру UCSRC необходимо записать 1 // в разряд URSEL, и одновременно с этим установить // требуемые нам разряды в регистре UCSRC. // Режим работы асинхронный: разряд UMSEL=0. Отключен // контроль по четности: разряды UPM1=O и UPM0=0. Один стоп- // битов: разряд USBS=0. Восемь бит данных: разряды UCSZ1=1, // UCSZ0=l, разряд UCSZ2 регистра UCSRB =0 // В асинхронном режиме разряд UCPOL=0 UCSRC = (1 « URSEL) | (0 « UMSEL) | (0 « UPM1) | (0 « UPM0) | (0 « USBS) | (1 « UCSZ1) | (1 « UCSZ0) | (0 « UCPOL); UCSRB &= ~(1 « 2); // UCSZ2=0 (8 бит данных) // Включаем приемник и передатчик USART UCSRB |= (1 << 4) | (1 « 3); // Разряды RXEN=1 и TXEN=1 // Разряды U2X=0 и МРСМ=0, т.е. отключаем удвоение скорости //и режим мультипроцессорного обмена UCSRA &= ~(1 « 1) & ~(1 << 0); } // Функция, которая будет вызываться при обращении потока на // вывод static int uart_putchar(char simvol, FILE *stream) { // Ожидаем освобождения регистра данных, т.е. когда он будет
Интерфейсы передачи данных ATmega16 255 // готов принять символ для отправки (установится 1 в разряде // UDRE) loop_until_bit_is_set(UCSRA, 5); UDR = simvol; // Передаем символ, содержащийся // в переменной simvol, в порт UART return 0; }; // Функция, которая будет вызываться при обращении потока на // ввод static int uart_getchar(FILE *stream) { // Ожидаем, пока завершится прием символа в UART (установится // 1 в разряде RXC) loop_until_bit_is_set(UCSRA, 7); return UDR; // Функция вернет принятый символ } // Инициализация ввода-вывода // Создаем поток, назначив функцию uart_putchar для вывода // данных, a uart_getchar - для ввода. Используем ввод-вывод, // установив флаг _FDEV_SETUP_RW static FILE mystdout_mystdin = FDEV_SETUP_STREAM(uart_putchar, uartgetchar, _FDEV_SETUP_RW) ; // Функция RDID передает Flash-памяти код инструкции RDID, // в ответ принимает код изготовителя и ID памяти. static void RDID() { char spi_receive[2]; // Массив из двух элементов для // хранения принятых байт PORTB &= -(1 << 4); // На вывод /SS подаем 0 В // (разрешаем устройству, // подключенному к SPI, передавать // нам информацию) SPI_MasterTransmit(0x15); // Отправляем по SPI код // инструкции "0001 XI01" spi_receive[0] = SPI_MasterReseiveО; // Код изготовителя spi_receive[1] = SPI_MasterReseiveО; // ID устройства PORTB |= (1 << 4); //На вывод /SS подаем 5 В // (запрещаем устройству, // подключенному к SPI, передавать // нам информацию) // Выводим через USART два принятых байта printf_P(PSTR("Manufacturer and Product ID: %x%x(hex)\r\n"), spi_receive[0],spi_receive[1]);
256 Глава Ю } /I Функция WREN устанавливает триггер разрешения записи по SPI // в Flash-память static void WREN() { PORTB &= ~(1 << 4); // На вывод /SS подаем 0 В SPI_MasterTransmit(0x06); // Отправляем по SPI // инструкцию 0000 XII0, // разрешающую нам запись PORTB |= (1 « 4); // На вывод /SS подаем +5 В } // Функция RDSR передает по SPI в Flash-память код инструкции // RDSR. В ответ получает значение регистра состояния static void RDSRО { char spi_receive; // Переменная для хранения содержимого // регистра состояния PORTB &= ~(1 « 4); И На вывод /SS подаем 0 В SPI_MasterTransmit(0x05) и и и г (разрешаем устройству, подключенному к SPI, передавать нам информацию) // Отправляем по SPI // инструкцию 0000 XI01 spi_receive = SPI_MasterReseiveО; // Принимаем содержимое PORTB |= (1 « 4); и // регистра состояния На вывод /SS подаем 5 В и и (запрещаем'устройству, подключенному к SPI, передавать и нам информацию) // Выводим через USART принятый байт (в шестнадцатиричной // системе счисления) printf_P(PSTR("Read Status Register: %x(hex)\r\n"), spi_receive); } // Функция WRSR. Запись нового значения в регистр состояния, static void WRSR(unsigned int status_register) { WRENO; // Вызываем функцию, которая передает памяти SPI // инструкцию разрешения записи PORTB &= ~(1 « 4); // На вывод /SS подаем 0 В // (задействуем устройство, // подключенное к шине SPI, // разрешаем ему обмен данными с МК)
Интерфейсы передачи данных ATmega16 257 SPIMasterTransmit(0x01); // Отправляем по SPI код // инструкции записи регистра // 'состояния 0000 Х001 SPI_MasterTransmit(status_register); // Передаем новое // значение регистра состояния PORTB |= (1 « 4); // На вывод /SS подаем 5 В // (отключаем устройство, // подключенное к шине SPI, // запрещаем ему обмен данными с МК) _delay_ms(60); // Ожидаем 60 мс .(согласно // техническому описанию микросхемы) // Выводим через USART описание инструкции printf_P(PSTR(H\r\nB регистр состояния было записано значение=%х (hex)\r\nn),status_register); } // Функция CHIP_ERASE очистки всей Flash-памяти static void CHIP_ERASE() { WREN(); // Вызываем функцию, которая передает памяти SPI // инструкцию разрешения записи PORTB &= ~(1 « 4)? // И и // SPI_MasterTransmit(0x62); PORTB |= (1 « 4); // // И . и _delay_ms(4000)} // // } На вывод /SS подаем 0 В (задействуем устройство, подключенное к шине SPI, разрешаем ему обмен данными с МК) // Отправляем по SPI код // инструкции записи регистра // состояния ОНО Х010 На вывод /SS подаем 5 В (отключаем устройство, подключенное к шине SPI, запрещаем ему обмен данными с МК) Ожидаем 4 с (согласно техническому описанию- микросхемы) // Функция SECTOR_ERASE очищает сектор Flash-памяти static void SECTOR_ERASE() { // Выводим приглашение коммандной строки printf_P(PSTR("\r\n Какой сектор памяти очистить?\г\п 1)Sector 1(000000 до 00FFFF)\г\п 2)Sector 2(010000 до 01FFFF)\г\п 3)Sector 3(020000 до 02FFFF)\г\п 4)Sector 4(030000 до 03FFFF)\r\ncmd:)); char choose;
258 Глава ю choose = getcharO; // Принимаем символ через UART // в переменную choose long int address =0; // Переменная для хранения адреса // сектора switch (choose) { case 'I1; address = 0; break; case 121: address = 0x10000; break; case *3’: address = 0x20000; break; case '41: address = 0x30000; break; defaultsprintfP(PSTR("???\r\n")); } WREN(); // Вызываем функцию, которая передает памяти SPI // инструкцию разрешения записи PORTB &= ~ (1 << 4); // На вывод /SS подаем 0 В // (задействуем устройство, // подключенное к шине SPI, // разрешаем ему обмен данными с МК) SPI_MasterTransmit(0x52) ; // Отправляем по SPI код // инструкции записи регистра // состояния 0101 Х010 // Отправляем адрес, который необходимо очистить // Вначале отсылаем первые два бита адреса SPIMasterTransmit((char)(address » 16)); // Затем - средние 8 бит адреса SPI_MasterTransmit((char)(address » 8)); // Наконец, последние 8 бит адреса SPI_MasterTransmit((char)(address)); PORTB |= (1 << 4); //На вывод /SS подаем 5 В // (отключаем устройство, // подключенное к шине SPI, // запрещаем ему обмен данными с МК) _delay_ms(1000); // Ожидаем 1 с (согласно // техническому описанию микросхемы) } // Функция READ чтения данных из массива памяти SPI static void READ() { // Принимаем по USART число (адрес, по которому необходимо // начать чтение памяти SPI) printf_P(PSTR("\г\пВведите адрес байта (от 0 до 262143), с которого начинать чтение данных (CTRL+J для завершения ввода)\г\пи)); //выводим инструкцию char from_uart_address[7]; // Массив из 7 байт
Интерфейсы передачи данных ATmega16 259 // Принимаем в массив from_uart_address по USART адрес // памяти, с которого начинать чтение fgets(&from_uart_address[0], 7, stdin); long int address =0; // Переменная для хранения адреса // памяти, с которого начинается // чтение данных из памяти SPI. // Адресе 18-разрядный, т.е. может // быть равен максимум 262143. address « atol(from_uart_address); // Преобразуем массив // from_uart_address // в переменную типа II long int (address) // Принимаем по USART число: количество байт для чтения (если // всю память, - 262144) printf_P(PSTR(”\г\пВведите количество байт, которые необходимо считать из SPI микросхемы (от 1 до 262144) (CTRL+J для завершения ввода)\г\п")); // Выводим инструкцию char from_uart_kolichestvo_bait[7]; // Массив из 7 байт // Принимаем в массив from_uart_kolichestvo_bait по USART // количество байт, которые необходимо считать из памяти SPI fgets(from_uart_kolichestvo_bait, 7, stdin); long int kolichestvo_bait = 0; // Переменная для хранения // количества байт, которые // необходимо считать из // памяти SPI // Преобразуем массив from_uart_kolichestvo__bait в переменную // типа long int (kolichestvo_bait) — kolichestvo_bait = atol(from_uart_kolichestvo_bait); // Если память EEPROM занята процессом записи, то ожидаем, // когда она освободится. Если разряд WIP регистра состояния // равен 1, то память занята процессом записи. Если 0, то // память готова к следующему циклу записи printf_J? (PSTR ("\г\п-Считали--\г\п") ) ; // Считываем из памяти байты, начиная со значения, хранимого // в переменной address, общее количество считываемых байт, // хранимое в переменной kolichestvo_bait PORTB &= -*(1 << 4); // На вывод /SS подаем 0 В // (задействуем устройство, // подключенное к шине SPI, // разрешаем ему обмен данными с МК) SPI_MasterTransmit(0x03); // Отправляем по SPI код инструкции // 0000 Х011 считывания памяти // Отправляем адрес, с которого начать считывание
260 Глава Ю // Вначале отсылаем последние два бита адреса SPI_MasterTransmit ((char)(address » 16)); // Затем средние 8 бит адреса SPI_MasterTransmit((char)(address » 8)); // Наконец, последние 8 бит адреса SPI_MasterTransmit((char)(address)); // В цикле принимаем байты из памяти и выводим их через USART for (int i=0; i < ко1ichestvo_bait; i++) { putchar(SPI_MasterReseive()); // Считать байт данных } PORTB |= (1 « 4); // На вывод /S3 подаем 5 В // (отключаем устройство, // подключенное к шине SPI, // запрещаем ему обмен данными с МК) } // Функция PROGRAM записи по SPI в Flash-память static void PROGRAM() { // Принимаем по USART данные для записи printf_P(PSTR("\г\пВведите от 1 до 256 байт данных для программирования SPI памяти, (CTRL+J для завершения)\г\пп)); char from_uart_data[257];// Переменная для хранения 257 байт // данных. Предусмотрено место под // 257-й элемент для символа конца // строки (если будут заполнены все // 256 элементов) // Принимаем байты по USART в массив from_uart_data для // записи в микросхему памяти gets(&from_uart_data[0]); // Принимаем по USART число (адрес памяти, с которого // необходимо начать запись в микросхему памяти) printf_P(Р8ТР("\г\пВведите адрес байта (от 0 до 262143), с которого начинать программирование данных, (CTRL+J для завершения)\г\пи)); char from_uart_address[7]; // Массив для хранения 6 // символов в ячейка [0]- [5] // и завершающего символа \0 // в ячейке [6] // Принимаем в массив from_uart__address по интерфейсу USART // число (адрес памяти, с которого начать программирование). gets(&from_uart_address[0]);
Интерфейсы передачи данных ATmega16 261 long int address =0; // Переменная для хранения адреса // памяти, с которого начинается // запись данных. Адресе состоит из // 18 разрядов, т.е. может быть // равен максимум 262143. // Преобразуем переменную типа char (from_uart_address) // в переменную типа long int (address) address = atol(&fromuartaddress[0]); // Запись в микросхему памяти, начиная с адреса address // байтов from_uart_data. Перед записью необходимо установить // триггер разрешения записи WRENO; // Функция, которая передает памяти SPI инструкцию V/ разрешения записи PORTB &= ~(1 << 4); // На вывод /S3 подаем 0 В // (задействуем устройство, // подключенное к шине'ЗР!, // разрешаем ему обмен данными с МК) SPI_MasterTransmit(0x02); // Отправляем по SPI код инструкции // программирования памяти 0000 Х010 // Отправляем адрес, с которого начинать запись // Вначале отсылаем последние два бита адреса SPI_MasterTransmit((char)(address » 16)); // Затем средние 8 бит адреса SPIMasterTransmit((char)(address » 8)); // Наконец, последние 8 бит адреса SPI_MasterTransmit((char)(address)); int dlin_stroki = strlen(from_uart_data); // В цикле передаем памяти SPI данные для записи for (int i=0; i<dlin_stroki; i++) { SPI_MasterTransmit(from_uart_data[i]); // Отправляем // один элемент массива } PORTB |= (1 « 4); //На вывод /SS подаем 5 В // (отключаем устройство, // подключенное к шине SPI, // запрещаем ему обмен данными с МК) } // Функция READ_Byte чтения одного байта данных из Flash-памяти static void READ_Byte() { // Принимаем по USART число (адрес памяти, с которого // необходимо начать чтение данных)
262 Глава Ю printf_P(PSTR(н\г\пВведите адрес байта (от 0 до 262143), с которого начинать программирование данных, (CTRL+J для завершения)\г\п")); char from_uart_address[7]; // Принимаем в массив через USART адресе памяти, с которого // считать байт данных fgets(from_uart_address, 7, stdin); long int address =0; // Переменная для хранения адреса // памяти, с которого начинается // запись данных. Адресе состоит из // 18 разрядов, т.е. может быть // равен максимум 262143. // Преобразуем переменную типа char (from_uart_address) // в переменную типа long int (address) address = atol(&from_uart_address[0]); print£(”\г\пБайт-\г\пи); PORTB &= ~(1 << 4); // На вывод /SS подаем 0 В // (задействуем устройство, // подключенное к шине SPI, // разрешаем ему обмен данными с МК) SPIJMasterTransmit (0x03) ; // Отправляем по SPI код инструкции // считывания памяти 0000 Х011 // Отправляем адрес, с которого начинать считывание // Вначале отсылаем последние два бита адреса SPI_MasterTransmit((char)(address » 16)); // Затем средние 8 бит адреса SPI_MasterTransmit((char)(address » 8)); // Наконец, последние 8 бит адреса SPI_MasterTransmit((char)(address)); // Принимаем байт из SPI-памяти и выводим через USART putc(SPI_MasterReseive(), &mystdout_mystdin); PORTB |= (1 << 4); //На вывод /SS подаем 5 В // (отключаем устройство, // подключенное к шине SPI, // запрещаем ему обмен данными с МК) } int main(void) { USART_INIT(0x40); // Инициализация USART. Передаем функции // значение регистра UBRR: 64 - ЭбООбод при // 10 МГц
Интерфейсы передачи данных ATmega16 263 // Перенаправляем стандартные потоки на пользовательский stdin = stdout = &mystdout_mystdin; SPI_MasterInit(); // Инициализации интерфейса SPI в режиме // "Master” WRSR(0x80); // Задействуем вывод /WP, записав 1 //в разряд WPEN регистра состояния while (1) // Бесконечный цикл { // Выводим через USART меню printf_Р (PSTR("\r\n-------\г\пМеню:\r\nl)Инструкция RDID - вывод изготовителя и идентификационного номера памяти подключенной по SPI\r\n")); printf_Р(PSTR("\г\п2)Инструкция RDSR - вывод содержимого регистра состояния памяти\г\пн)); printf_P(PSTR("\r\n3)Инструкция WRSR - программирование регистра состояния памяти\г\п")); printf_P(PSTR("\r\n4)Инструкция WREN - установка триггера разрешения записи\г\пн)); printf_Р(PSTR("\г\п5)Инструкция CHIP ERASE - очистка всего чипа\г\п")); printf_P(PSTR(н\г\пб)Инструкция SECTOR ERASE - очистка определенного сектора(одного из 4рех)\г\пн)); printf_Р(PSTR("\r\n7)Инструкция READ - чтение данных\г\п")); printf_P(PSTR("\r\n8)Инструкция PROGRAM - программирование памяти\г\пи)); printf_P(PSTR("\r\n9)Функция чтения одного байта данных\г\п")); printf_P (PSTR(”cmd:11)) ; // Выводим приглашение // командной строки char choose; choose = getcharO; // Принимаем через USART символ // в переменную choose // Если выбрали "0", то выходим из бесконечного цикла if (choose == ’О1) { printf_P(PSTR("\г\пВыхожу...”)); break; } // В зависимости от выбранного пункта меню вызываем ту или // иную функцию работы с памятью по SPI switch (choose) { case ’I1: RDID(); break;
264 Глава Ю case '21: RDSR(); break; case '3*: { unsigned int stat_reg; // Переменная // для хранения нового // значения регистра состояния // Выводим через USART инструкцию printf_P(PSTR(”\г\пВведите новое значение регистра состояния(hex)\r\n")); // Форматированный ввод данных scanf("%x", &stat_reg); // Принимаем // значение в шестнадцатиричном // формате в переменную stat_reg WRSR(stat reg); // Вызываем функцию // WRSR, передав ей новое // значение регистра состояния } break; case 141: WREN; break; case *5’: { CHIPERASE(); printfP(PSTR(”\r\nSPI flash память очищена\r\n")); } break; case 16’: SECTORERASE(); break; case 171: READ(); break; case •8': PROGRAM(); break; default: printf_P(PSTR("???\r\n")); } } printf_P(PSTR("ХгХпвышли")); } Интерфейс SPI. Работа с температурным преобразователем МАХ6675 Микросхема МАХ6675 представляет собой цифровой 12-разрядный температурный преобразователь для термопар К-типа (хромель-алю- мель). Измеряет температуру от 0°С до +1024°С. Выводы МАХ6675: • GND — вход питания 0 В (“земля”); • Т---алюмелевый вход термопары (также должен быть соединен с “землей”); • Т+ — хромелевый вход термопары; • VCC — вход питания 3..5 В (для защиты от помех должен быть за- шунтирован конденсатором 0,1 мкФ на “землю”); • SCK — вход тактовых импульсов;
1/1нтерфейсы передачи данных ATmega16 265 * /CS — вход выбора микросхемы (напряжение питания во время преобразования данных, О В во время передачи данных); • SO — последовательный вывод данных; • N.C. — не подключен. Температурный преобразователь передает 16 бит данных с выбор- кой по ниспадающему фронту тактовых импульсов (первым передается старший разряд): • разряд 15 — фиктивный, всегда содержит 0; • разряды 5-14 — целая часть значения температуры в градусах Це- льсия; • разряды 3-4 — дробная часть значения температуры в градусах Це- льсия; • разряд 2 — 0, когда термопара подключена; 1, когда термопара не подключена; • разряд 1 — идентификационный номер устройства (возвращает 0); • разряд 0 — третье состояние. Микросхема МАХ6675 измеряет температуру с разрешением 0,25 °C. Значение температуры можно вычислить следующим образом: Полученное значение • 0,25 = Температура (°C). Например, в разрядах 3-14 получено значение 0Ы10000000000 = = 12 288. Умножаем его на 0,25 и получаем 768°С. Для использования выводов РС2-РС5 порта С для ввода-вывода не- обходимо отключить интерфейс JTAG (рис. 10.33). Рис. 10.33. Интерфейс JTAG должен быть отключен Схема соединений показана на рис. 10.34.
266 Глава Ю Рис. 10.34. Схема соединений для исследования работы преобразователя МАХ6675
Интерфейсы передачи данных ATmega16 267 Для вывода принимаемых данных с МАХ6675 мы используем 16 светодиодов, подключенных к портам А и С (их выводы назначены как выходы). Функция SPI_MasterInit () настраивает интерфейс SPI микроконтроллера согласно специфике передачи данных температурно- го преобразователя. В бесконечном цикле принимаем 16 бит данных. Поскольку регистр данных (SPDR) интерфейса SPI микроконтроллера — восьмиразряд- ный, принимаем восемь бит, отображаем их на светодиодах, а затем — принимаем остальные восемь бит с последующей индикацией на свето- диодах. Программа Программа, реализующая работу схемы, показанной на рис. 10.34, представлена в листинге 10.6. ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\10.06 - SPI - Преобра- зователь МАХ6675. #include <avr/io.h> // #include <util/delay.h> Заголовочный файл подключает определения ввода-вывода для устройства, используемого в проекте // Для доступа к функциям циклов // задержки // Функция инициализации нтерфейса SPI void SPI_MasterInit() { // Выводы /SS (РВ4) и SCK (РВ7) - выходы DDRB |= (1 « 4) | (1 « 7); PORTB J= (1 « 4); // На выводе /SS устанавливаем 5 В // (устройство, подключенное к SPI, // отключено) DDRB &= ~(1 « б);// Вывод MISO (РВ6) - вход (по этой линии // принимаем байт от внешних устройств) // Включаем SPI, режим "Master”, частота тактового сигнала //на выводе SCK = fck/16. Прерывания от SPI запрещены SPCR = (1 « 6) | (1 « 4) | (1 « 0);// SPE=1, MSTR=1, // SPR0=l SPSR &= ~(1 « 0); // SPI2X=0 SPCR &= ~(1 « 3); // CPOL=0 - импульсы положительной // полярности
268 Глава Ю SPCR |= (1 « 2); SPCR &= ~(1 « 5); } // СРНА=1 - обработка данных по // заднему фронту сигнала // DORD=0 - передача данных, начиная //со старшего разряда // Функция приема байта по интерфейсу SPI unsigned char SPI_MasterReseive() { SPDR = OxFF; // Для того чтобы начать прием, необходимо // начать передавать импульсы SCK, поэтому // передаем по интерфейсу SPI любой байт while(-SPSR & (1 « 7)) {}; // Ожидаем до тех пор, пока байт // отправится (появится 1 в разряде SPIF регистра SPSR return SPDR; //Возвращаем принятый байт } int main(void) { // Порты А и С используем для подключения светодиодов DDRA = OxFF; // Выводы порта А - выходы PORTA = 0x00; // На выводы порта А подаем 0 В DDRC = OxFF; // Выводы порта С - выходы PORTC = 0x00; // На выводы порта С подаем 0 В SPI_Masterlnit(); // Инициализации интерфейса SPI while (1) // Бесконечный цикл { _delay_ms(220); // Задержка перед приемом данных (0,22 с // (уходит на преобразование согласно спецификации) PORTB &= ~(1 << 4); // На вывод /SS подаем 0 В // Принимаем и выводим на светодиоды принятые два байта PORTA = SPI—MasterReseiveО; // Старшйе 8 разрядов PORTC = SPI_MasterReseive() ; // Младшие 8 разрядов PORTB |= (1 << 4); // На вывод /SS подаем +5 В } } Интерфейс SPI. Работа с температурным преобразователем МАХ6674 Микросхема МАХ6674 представляет собой цифровой 10-разрядный температурный преобразователь для термопар К-типа (хромель-алю- мель). Измеряет температуру от 0°С до +128°С.
Интерфейсы передачи данных ATmega16 269 К выводам Т-, Т+ мы подключили термопару (рис. 10.35). Рис. 10.35. Схема соединений для исследования работы преобразователя МАХ6674
270 Глава Ю Никаких данных температурный преобразователь не принимает, по- этому вывод MOSI микроконтроллера не подключен. Выводы МАХ6674: • GND — вход питания 0 В (“земля”); • Т---алюмелевый вход термопары (также должен быть соединен с “землей”); • Т+ — хромелевый вход термопары; • VCC — вход питания 3..5 В (для защиты от помех должен быть за- шунтирован конденсатором 0,1 мкФ на “землю”); • SCK — вход тактовых импульсов; • /CS — вход выбора микросхемы (напряжение питания во время преобразования данных, 0 В во время передачи данных); • SO — последовательный вывод данных; • N.C. — не подключен. Температурный преобразователь передает 16 бит данных с выбор- кой по ниспадающему фронту тактовых импульсов (первым передается старший разряд): • разряд 15 — фиктивный, всегда содержит 0; • разряды 5-14 — значение температуры в градусах Цельсия; • разряд 4 — 0, когда термопара подключена; 1, когда термопара не подключена (этот разряд будет устанавливаться верно только в том случае; если “минус” питания микросхемы соединен с “минусом” термопары); • разряд 3 — идентификационный номер устройства (возвращает 0); • разряды 0-2 — третье состояние. Десять бит данных температуры позволяют закодировать макси- мальное число 1 023. Единица данного числа составляет 0,125 градусов Цельсия. Например, в разрядах 3,-14 получено значение 0Ь00000111111 = = 63. Умножаем его на 0,125 и получаем 7,875°С. Для индикации данных, принятых от МАХ6674, мы используем 16 светодиодов, подключенных к портам А и С (их выводы назначены как выходы). При этом следует помнить о необходимости отключить ин- терфейс JTAG (см. рис. 10.33). Функция SPI_MasterInit () настраивает интерфейс SPI микро- контроллера согласно специфике передачи данных температурного пре- образователя. В бесконечном цикле принимаем 16 бит данных. Поскольку регистр данных (SPDR) интерфейса SPI микроконтроллера — восьмиразряд-
Интерфейсы передачи данных ATmega 16 271 ный, принимаем восемь бит, отображаем их на светодиодах, а затем — принимаем остальные восемь бит с последующей индикацией на свето- диодах. Программа Программа, реализующая работу схемы, показанной на рис. 10.35, представлена в листинге 10.7. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\10.07 - SPI - преобра- зователь МАХ6674. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include cutil/delay.h> // Для доступа к функциям циклов // задержки // Функция инициализации нтерфейса SPI void SPI_MasterInit() { // Выводы /SS (РВ4) и SCK (РВ7) - выходы DDRB |= (1 « 4) | (1 « 7); PORTB |= (1 « 4); // На выводе /SS устанавливаем 5 В // (устройство, подключенное к SPI, // отключено) DDRB &= ~(1 « б);// Вывод MISO (РВ6) - вход (по этой линии // принимаем байт от внешних устройств) // Включаем SPI, режим "Master”, частота тактового сигнала // на выводе SCK = fck/16. Прерывания от SPI запрещены SPCR = (1 « б) | (1 « 4) | (1 « 0);// SPE=1, MSTR=1, // SPR0=l SPSR &= ~(1 « 0); Ц SPI2X=0 SPCR &= ~(1 << 3); // CPOL=0 - импульсы положительной // полярности SPCR |= (1 << 2); // СРНА=1 - обработка данных по // заднему фронту сигнала SPCR &= ~(1 « 5); // DORD=0 - передача данных, начиная // со старшего разряда } // Функция приема байта по интерфейсу SPI unsigned char SPI_MasterReseive()
272 Глава Ю SPDR = OxFF; while(-SPSR & (1 return SPDR; // Для того чтобы начать прием, необходимо // начать передавать импульсы SCK, поэтому // передаем по интерфейсу SPI любой байт << 7)){}; // Ожидаем до тех пор, пока байт // отправится (появится 1 в // разряде SPIF регистра SPSR //Возвращаем принятый байт int main(void) { // Порты А и С используем для подключения светодиодов DDRA = OxFF; // Выводы порта А - выходы PORTA = 0x00; // На выводы порта А подаем 0 В DDRC = OxFF; // Выводы порта С - выходы, PORTC = 0x00; // На выводы порта С подаем 0 В SPI_MasterInit(); // Инициализации интерфейса SPI while (1) { _delay_ms(220); PORTB &= ~(1 « // Бесконечный цикл // Задержка перед приемом данных (0,22 с // (уходит на преобразование согласно // спецификации) 4) ; //На вывод /SS подаем 0 В // Принимаем и выводим на светодиоды принятые два байта PORTA = SPI MasterReseive() ; // Старшие 8 разрядов PORTC = SPI_MasterReseive() ; // Младшие 8 разрядов PORTB | = (1 « 4) ; // На вывод /SS подаем +5 В } } Интерфейс SPI. Работа с АЦП МАХ1241 К интерфейсу SPI микроконтроллера подключен аналого-цифровой преобразователь МАХ 1241. Этот АЦП требует внешнего опорного на- пряжения (вывод REF) от 1 до VDD + 50 мВ и питания (вывод VDD) от 2,7 В до 5,25 В. Разрешающая способность АЦП — 12 бит. Микросхема кодирует измеряемое напряжение и передает данные по SPI интерфейсу. Так, ис- пользуя 12 бит, максимально можно закодировать число 4 096. Единица данного числа будет равна определенному значению в вольтах.
Интерфейсы передачи данных ATmega16 273 Рис. 10.36. Схема подключения АЦП МАХ1241
274 Глава Ю Например, при опорном напряжении 1 В единица закодированного числа составляет: 1 В / 4 096 = 0,000244140625 В. Или, например, с выхода SPI АЦП получено число 2 048 при 5 В опорного напряжения. Это означает, что напряжение, поданное на вход AIN, было равно 5/4 096 • 2 048 = 2,5 В. Для отображения числа, кодирующего вольты на входе АЦП, к мик- роконтроллеру подключены 12 светодиодов, поэтому в основной про- грамме порты А и С инициализируются как выходы. Вывод РВЗ назначаем как выход. С его помощью мы будем вклю- чать/выключать режим работы АЦП. В функции SPI_MasterInit () задаем режим работы интерфейса SPI микроконтроллера. Выводы /SS (выбор подчиненной микросхемы), SCK (тактовая частота интерфейса SPI), MISO — входы. Для управле- ния подчиненным устройством служит регистр SPCR. Задаем режим “Master”. Согласно спецификации АЦП, максимальная частота обмена данными SCK = 2,1 МГц, поэтому, с помощью регистров SPCR и SPSR задаем тактовую частоту на выводе SCK. меньшей или равной 2,1 МГц. В нашем случае мы задалиуск /16=10 МГц / 16 = 625 кГц. В бесконечном цикле принимаем 16 бит дынных от АЦП и выводим их на светодиоды. Для начала преобразования выводим АЦП из режима пониженного энергопотребления. Он начинает измерять напряжение на выводе AIN и преобразовывать его в 12-разрядное число после подачи на вывод /CS уровня лог. 0. Результат преобразования будет готов через время Zconv- Дождавшись завершения преобразования, вызываем функ- цию SPI_MasterReseive(), которая активизирует импульсы син- хронизации /SCK и принимает восемь старших бит. Принятые данные выводим в порт А микроконтроллера. Аналогичным образом, с помощью SPI_MasterReseive () при- нимаем восемь младших бит и выводим их в порт С. Интерфейс SPI микроконтроллера устроен так, что для приема данных по линии MISO мы должны что-либо отправить (в нашем примере — значение OxFF). После приема данных отключаем преобразование и переводим АЦП в режим пониженного энергопотребления. Процесс приема данных Процесс приема данных от АЦП МАХ 1241 проиллюстрирован на рис. 10.37.
Интерфейсы передачи данных ATmega 16 275 юс •when В в нон, вал=high -г Рис. 10.37. Процесс приема данных от АЦП МАХ1241 Интерфейс SPI микроконтроллера необходимо настроить следую- щим образом: • MSTR = 1 — режим SPI “Ведущий”; • CPOL = 0 — тактовые импульсы положительной полярности, когда на выводе SCLK нет состояния лог. 0; • СРНА = 0 — обработка данных по переднему фронту сигнала на выводе SCLK; • DORD = 0 — передача данных, начиная со старшего разряда; • частота импульсов SCLK — не выше 2,1 МГц. До начала преобразования и обмена данными с МАХ 1241 на выводе /SHDN должно быть 0 В, а на /CS — +5 В. Для активизации процесса преобразования необходимо подать на вывод /SHDN напряжение +5 В, дождаться выхода из режима понижен- ного энергопотребления, установить 0 В на выводе /CS, выждать время преобразования или появления +5 В на выводе DOUT, запустить такто- вые импульсы SCLK. Первый импульс на SCLK готовит МАХ 1241 к передачи данных. Первый принятый бит (D12) не несет информации о преобразованном напряжении, а последующие (DI 1-D0) говорят о том, какое напряжение было на выводе AIN микросхемы МАХ 1241. После того как данные приняты, устанавливаем на выводе /CS +5 В и информируем МАХ 1241 о переводе АЦП в режим пониженного энергопотребления установкой 0 на выводе /SHDN. Итак, процесс преобразования и приема данных выглядит следую- щим образом. 1. Подать +5 В на вывод /SHDN МАХ1241, чтобы вывести АЦП из режима пониженного энергопотребления. 2. Выждать время /wake выхода из режима пониженного энергопо- требления. 3. Разрешить работу МАХ 1241, подав 0 В на вывод /CS.
276 Глава Ю 4. Выждать время Zconv до завершения преобразования аналогового сигнала в цифровой. 5. Принять первый, а затем — второй байт данных с вывода DOUT. 6. Запретить работу МАХ1241, подав +5 В на вывод /CS. 7. Перевести МАХ 1241 в режим пониженного энергопотребления, по- дав О В на вывод /SHDN. Из принятых 16 бит данных нам необходимы только 12. Из первого принятого байта (старшего) нам потребуются семь бит информации (0- 6), а из второго (младшего) — пять бит (3-7). Программа Программа, реализующая работу схемы, показанной на рис. 10.36, представлена в листинге 10.8. е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\10.08 - SPI - АЦП. j 1 и сти нг 4 СМ1 Ираграм й а <10x1-2 #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include cutil/delay.h> // Для доступа к функциям'циклов // задержки // Функция инициализации нтерфейса SPI void SPI_MasterInit() { // Выводы /SS (РВ4) и SCK (РВ7) - выходы DDRB |= (1 « 4) | (1 « 7); PORTB |= (1 « 4); //На выводе /SS устанавливаем 5 В // (устройство, подключенное к SPI, // отключено) DDRB &= ~(1 « 6);// Вывод MISO (РВ6) - вход (по этой линии // принимаем байт от внешних устройств) // Включаем SPI, режим "Master”, частота тактового сигнала // на выводе SCK = fck/16. Прерывания от SPI запрещены SPSR &= ~(1 « 0); // SPI2X=0 SPCR = (1 « 6) | (1 « 4) | (1 « 0);// SPE=1, MSTR=1, // SPR0=l SPCR &= ~(1 « 3); // CPOL=0 - импульсы положительной // полярности SPCR &= ~(1 « 2); // CPHA=0 - обработка данных по // переднему фронту сигнала
Интерфейсы передачи данных ATmega16 277 SPCR &= ~(1 « 5); // DORD=0 - передача данных, начиная // со старшего разряда } // Функция приема байта по интерфейсу SPI unsigned char SPI_MasterReseive() { SPDR = OxFF; // Для того чтобы начать прием, необходимо // начать передавать импульсы SCK, поэтому // передаем по интерфейсу SPI любой байт while(-SPSR & (1 << 7)){}; // Ожидаем до тех пор, пока байт // отправится (появится 1 в // разряде SPIF регистра SPSR return SPDR; //Возвращаем принятый байт } int main(void) { // Порты А -и С используем для подключения светодиодов DDRA = OxFF; // Выводы порта А - выходы PORTA = 0x00; // На выводы порта А подаем 0 В DDRC « OxFF; // Выводы порта С - выходы PORTC = 0x00; // На выводы порта С подаем 0 В DDRB |= (1 << 3); // РВЗ - выход (к нему подключен вывод // /SHDN АЦП PORTB &= -(1 << 3); // Переводим МАХ1241 в режим // пониженного энергопотребления SPI_MasterInit(); // Инициализации интерфейса SPI while (1) // Бесконечный цикл { PORTB |= (1 « 3); // Выводим МАХ1241 из режима // пониженного энергопотребления _delay_us(5); // Выжидаем время tWAKE PORTB &= ~(1 << 4); //На вывод /SS подаем 0 В _delay_us(7.5); // Выжидаем время tCONV до // завершения преобразования // Принимаем и выводим на светодиоды принятые два байта PORTA = SPI_MasterReseive(); // Старшие 8 разрядов PORTC = SPI_MasterReseive(); // Младшие 8 разрядов PORTB |= (1 « 4); //На вывод /SS подаем +5 В PORTB &= -(1 << 3); // Переводим МАХ1241 в режим // пониженного энергопотребления _delay_ms(1000); // Задержка перед следующим опросом } }
278 Глава 10 Интерфейс TWI Мы рассмотрим работу с двухпроводным интерфейсом TWI на примере обмена данными с датчиком температуры МСР9801. Этот дат- чик способен измерять температуру в пределах от -55°С до +125°С. Значение температуры преобразуется в двоичный код и предоставляется с настраиваемым разрешением от 9 до 12 бит, т.е. от 0,5°С/бит до 0,0625 °С/бит. Датчик температуры содержит пять регистров: • Register Pointer — указатель регистра; • Configuration — конфигурационный регистр; • Temperature —регистр температуры; • THYST — гистерезис для TSET; • TSET Register — регистр предела температуры. К любому регистру можно обращается только после того как его номер записан в Register Pointer. По умолчанию после подачи питания датчик настроен следующим образом. • В Register Pointer записан номер регистра Temperature. Это говорит о том, что мы можем сразу же считать значение температуры; • Конфигурационный регистр по умолчанию настроен на непрерыв- ное преобразование температуры с разрешением 9 бит. Диаграмма чтения регистра температуры показана на рис. 10.38. 12345678 12345678 12345678 зсг-ттплплллллтши^^ I ответ от МСР9801 ответ от ATMEGA16 ответ от ATMEGA16 © S-CTAPT АСК-бит подтверждения^ на линии SDA) NAK-бит нет подтверждения^ на линии SDA) P-STOP А2.А1 ,А0 • адрес ведомого устройства R-бит направления передачи данных(в данном случае чтение) Рис. 10.38. Временная диаграмма чтения регистра температуры Процесс чтения температуры с МСР9801 по интерфейсу TWI, когда микроконтроллер — ведущий передатчик.
Интерфейсы передачи данных ATmega16 279 1. Для начала обмена данными микроконтроллер должен подать сиг- нал “START” (изменение уровня с высокого на низкий на линии данных при высоком уровне на линии тактов). 2. Отправить адрес устройства, с которым мы хотим начать обмен данными. Адрес МСР9801 состоит из семи бит. Старшие четыре разряда (4-7) — это идентификационный номер самого устройства (содержат 1001). Следующие три разряда (1-3) устанавливаются выводами А0-А2, которые находятся в состоянии лог. 0. Нулевой разряд направления передачи данных (R/W) необходимо установить равным 1 (R), потому что мы считываем значение температуры. Та- ким образом, передаваемые восемь бит данных должны быть равны 0x91. 3. После отправки адреса устройства (рис. 10.39) с битом направления передачи от подчиненного устройства приходит бит подтверждения АСК. Ш ТРСН5 76} (STL'F’«I} (MIN*-S5| pMCKAGE*>S08} (momE-ameex) ЙИМИ Рис. 10.39. Адрес устройства в Proteus Процесс чтения температуры с МСР9801 по интерфейсу TWI, когда микроконтроллер — ведущий приемник. 1. Поскольку мы отправили бит направления передачи R (чтение дан- ных), микроконтроллер ожидает приема старших восьми бит значе- ния температуры.
280 Глава Ю 2. После приема данных микроконтроллер формирует сигнал под- тверждения АСК. 3. Ожидаем приема младших восьми бит значения температуры. 4. После приема микроконтроллер формирует сигнал “нет подтвер- ждения” NACK. 5. Микроконтроллер завершает прием отправкой состояния “СТОП” (изменение уровня с низкого на высокий на линии данных при вы- соком уровне на линии тактов) Схема Для отображения значения температуры (16 бит данных регистра Temperature), мы подключили к микроконтроллеру 16 светодиодов. Во- семь светодиодов, подключенных к порту В, будут отображать старший байт, а остальные восемь (порт А) — младший байт. К линиям SDA и SCL подключены подтягивающие резисторы по 10 кОм (рис. 10.40). Из основной программы вызывается функция инициализации ин- терфейса TWI микроконтроллера TWI_MasterInit О. В ней с помо- щью регистров TWBR и TWSR задается частота синхронизации SCL: МК Максимальная частота, на которой может передавать данные МСР9801 — 400 кГц. Необходимо вычислить значение TWBR при за- данной частоте микроконтроллера и сигнала на выводе синхронизации SCL. При TWPS=0 значение TWBR можно вычислить так: TWBR = ((/clk//scL)-16)/2. У нас частота микроконтроллера составляет 10 МГц. Допустим, мы захотели установить частоту SLC равной 100 кГц. В таком случае, TWBR = (100 - 16) / 2 = 42. Устанавливаем разряды TWPSl,TWPS0 ре- гистра TWSR равными 0, и при данных значениях частота сигнала SCL составит 100 кГц. В бесконечном цикле считываем регистр температур и выводим его значение в порты А и В. Для формирования состояния “СТАРТ” на ин- терфейсе TWI вызываем функцию TWI_START(), которая устанавли- вает низкий уровень сигнала SDA при высоком уровне SCL и ожидает выполнения операции от интерфейса TWI. По окончании операции ин- терфейс TWI сбрасывает флаг TWINT.
Интерфейсы передачи данных ATmega16 281 Рис. 10.40. Схема подключения датчика температуры по интерфейсу TWI
282 Глава Ю Для получения 16 бит данных температуры нам необходимо прежде передать адрес ведомого устройства, указать ему на необходимость на- чать передачу и принять 16 бит. Вначале микроконтроллер выступает в роли ведущего передатчика. Записываем в регистр значение для передачи ведомому приемнику и сбрасываем разряд TWINT, информируя о том, что программа сформи- ровала новое состояние для интерфейса TWI. Начинается передача дан- ных. После передачи адреса и приема бита подтверждения от ведомого приемника интерфейс TWI установит разряд TWINT в 1. Это укажет на то, что модуль ожидает действия от программы. Затем микроконтроллер выступает в роли ведущего приемника, а МСР9801 — ведомого передатчика, поскольку мы передали пакет SLA+ R. Подготавливаем интерфейс TWI к приему данных, устанавливаем разряд TWEA, чтобы микроконтроллер после приема отправил бит под- тверждения АСК, а также сбрасываем разряд TWINT — программа сформировала новое состояние для интерфейса TWI, можно начать при- ем. По окончании передачи в регистре TWDR будут восемь старших бит данных от МСР9801, которые выводим на светодиоды порта В. Процесс приема младших восьми бит данных такой же, но на этот раз мы не устанавливаем разряд TWEA, поскольку нам необходимо, чтобы микроконтроллер отправил бит подтверждения “NACK”. Выво- дим полученный байт на светодиоды порта А. После приема вызываем функцию TWI_STOP (), в которой записы- ваем 1 в разряд TWSTO, говоря о том, чтобы интерфейс TWI отправил в шину состояние “СТОП”, и сбрасываем разряд TWINT, чтобы проин- формировать о том, что программа сформировала новое состояние. За- тем следует пауза приема температуры микроконтроллером от датчика в одну секунду. Микросхема МСР9801 находится в режиме непрерывного преобра- зования. Значение температуры постоянно записывается в регистр Tem- perature. Время преобразования зависит от разрешения. Чем выше раз- решение, тем больше требуется времени на преобразование. Значение регистра Temperature Считанное значение регистра Temperature состоит девяти значащих разрядов и семи неиспользуемых: • разряд 15 — знак температуры: 0 — положительная; 1 — отрица- тельная; • разряды 8-14 — целая часть значения температуры; • разряд 7 — дробная часть значения температуры (0,5°С);
Интерфейсы передачи данных ATmega16 283 • разряды 0-6 не используются при установленном разрешении 9 бит. Предположим, считано значение 0111 1101 luuu uuuu. Темпе- ратура положительная, поскольку разряд 15 = 0. Значение температуры: 251 • 0,5 = 125,5 С°. Отрицательная температура вычисляется переводом полученного значения в дополнительный код (инвертирование и при- бавление единицы) и умножением на значение весового коэффициента для первого разряда (в нашем случае — 0,5). Программа Программа, реализующая работу схемы, показанной на рис. 10.41, представлена в листинге 10.9. ©Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegai6\10.09 - twi. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <util/delay.h> // Для доступа к функциям циклов // задержки // Функция инициализации нтерфейса TWI void TWI_MasterInit() { // Устанавливаем скорость передачи с помощью разрядов TWBR7- // TWBR0 регистра TWBR и разрядов TWPSl,TWPS0 регистра TWSR TWBR = 0х2А; // Скорость работы передатчика TWSR &= ~(1 « 0) & ~(1 « 1); // Коэффициент предделителя } // Функция устанавливает состояние "СТАРТ" void TWI START О { // Устанавливаем состояние ’'СТАРТ", записав лог. 1 в TWSTA. // Разрешаем работу модуля TWI, записав лог. 1 в TWEN. // Сбрасываем флаг TWINT, говоря о том, что со стороны // программы сформировано новое состояние. // Остальные разряды регистра TWCR устанавливаем в лог. 0. TWCR = (1 « TWINT) | (1 « TWSTA) | (1 « TWEN); // Ожидаем формирования состояния "Старт": установки разряда // TWINT (это происходит после выполнения очередной операции) while (!(TWCR & (1 « TWINT)));
284 Глава 10 // Функция устанавливает состояние "СТОП" void TWI_STOP() { // Устанавливаем состояние "СТОП”, записав лог. 1 в TWSTO; // Модуль по-прежнему работает, но в состоянии ’'СТОП”. // Записываем лог. 1 в TWEN, сбрасываем флаг TWINT, говоря // о том, что со стороны программы сформировано новое // состояние. Остальные разряды регистра TWCR - лог. 0. TWCR = (1 « TWINT) | (1 « TWSTO) | (1 « TWEN); } int main(void) { // Порт А используем для подключения светодиодов DDRA = OxFF; // Выводы порта А - выходы PORTA = 0x00; // Подаем на порт А 0 В // Порт В используем для подключения светодиодов DDRB = OxFF; // Выводы порта В - выходы PORTB = 0x00; // Подаем на порт ВОВ TWI_MasterInit(); // Инициализация интерфейса TWI while (1) // Бесконечный цикл { _delay_ms(75); // Выжидаем время преобразования tCONV=75MC // (при 9-битном разрешении) TWI_START(); // Формируем состояние "Старт" // ВЕДУЩИЙ ПЕРЕДАТЧИК // Загружаем адрес подчиненного устройства в регистр данных // TWDR. Записываем 1 в разряд 0 направления передачи // данных R/W, т.е. данные будут передаваться от устройства // к микроконтроллеру. // Пакет отправки будет следующим: ID+Address Byte+R // Отправляем 0x91 = 1001 000 1 // Разряды 4-7 - идентификатор (по умолчанию в микросхеме - // 1001. Разряды 1-3 - адрес устройства, установленный If выводами А0-А2 (в нашем случае все выводы "посажены" на // "минус", поэтому все три разряда равны нулю). // Последний разряд - направление передачи данных: // 1 - чтение. TWDR = 0x91; // id+aflpec+R TWCR = (1 « TWINT) | (1 << TWEN); // Программа готова // передать адрес устройства
Интерфейсы передачи данных ATmega16 285 // Ожидаем установки в 1 разряда TWINT, означающего, что // адресный пакет передан (и принято подтверждение от // ведомого устройства) while (!(TWCR & (1 « TWINT))) {}; // ВЕДУЩИЙ ПРИЕМНИК // Считываем температуру - старшие 8 разрядов // Отклик со стороны программы (можно продолжать дальше!). // Отправляем АСК со стороны МК по завершении приема // данных в регистр TWDR TWCR = (1 « TWEA) | (1 « TWINT) | (1 « TWEN); while (!(TWCR & (1 « TWINT))); // Ожидаем установки // разряда TWINT, указывающего, что // отклик АСК передан из МК, и от // МСР9801 получены данные. // Отображаем 8 бит данных температуры на светодиодах PORTB = TWDR; // Старшие 8 бит выводим в порт В // Считываем температуру - младшие 8 разрядов // Отклик со стороны программы (можно продолжать дальше!), // NACK со стороны МК после завершения приема данных // в регистр TWDR TWCR = (1 « TWINT) | (1 « TWEN); While (1(TWCR & (1 « TWINT))); //Ожидаем установки // разряда TWINT (модуль ожидает // действия со стороны программы), // указывающего, что отклик NACK // послан от МК, и от МСР9801 // получены данные // Отображаем 8 бит данных температуры на светодиодах PORTA = TWDR; // Младшие 8 бит выводим в порт А // Формируем состояние "СТОП" TWIJ3TOPO ; _delay_ms(1000) ; // Задержка перед следующим опросом //и отображением температуры
Глава 11 В этой главе мы рассмотрим примеры взаимодействия микрокон- троллера ATmegal6 с жидкокристаллическими (ЖК) дисплеями. Подключение текстового ЖК-экрана 16x2 на базе контроллера KS0066U Схема подключения ЖК-экрана к микроконтроллеру ATmegal6 по- казана на рис. 11.1. Рис. 11.1. Схема подключения ЖК-экрана к микроконтроллеру
Использование ЖК-экрана 287 Перечислим выводы ЖК-модуля: • R/W — вход для установки направления передачи данных (по- скольку мы не считываем данные с экрана, а только передаем их, на этом выводе установлен низкий уровень напряжения); • VSS/VDD — напряжения питания, соответственно 0 и +5 В; • VEE — управление контрастностью экрана через делитель напря- жения (переменный резистор); • RS — определяет передачу данных (высокий уровень) или команды (низкий уровень); • DB0-DB7 — шина данных для передачи команды или символа. Можно использовать как восьми-, так и четырехразрядную переда- чу. При использовании четырехразрядной шины строго закреплены выводы DB4-DB7; • Е — строб. Подав сигнал на этот вход, мы сообщаем контроллеру, что на шине выставлены данные. Программа передает ЖК-модулю две строки. Первая из них извле- кается из Flash-памяти программ, чтобы не занимать ОЗУ микрокон- троллера, а вторая — из ОЗУ. Строки передаются посимвольно. В зави- симости от настройки ЖК-модуля, данные можно передавать по четы- рех- или восьмипроводной шине. Принятые ЖК-дисплеем коды символов записываются во внутрен- нюю память DDRAM. Каждая ее ячейка имеет свой порядковый номер (адрес). Нулевой адрес соответствует отображению первого символа первой строки ЖК-экрана. При записи происходит автоматический пе- реход на следующую ячейку. Отображение символа происходит следующим образом. Внутрен- ний контроллер выбирает из ячейки памяти CGROM “изображение” то- го или иного символа в зависимости от его кода в DDRAM и выводит его на экран. Объем памяти DDRAM превышает количество ячеек ЖК-экрана (2x16). Например, записав символ в ячейку 20, которая выходит за об- ласть отображения, можно сместить эту область на один знак. В нашем примере вы воспользуемся четырехразрядной передачей данных через выводы РС4-РС7 микроконтроллера, подключенные к ли- ния DB4-DB7 ЖК-модуля. Кроме того, назначим в качестве выходов выводы РСО и РС1, которые будут соединены с линией выбора регистра RS и стробирован ия/синхронизации Е. Для инициализации дисплея передаем три команды, определяющие внутренние настройки LCD.
288 Глава 11 • 0x28. Настройка ЖК-модуля на четырехбитную шину данных, две строки текста и шрифт 5x7 точек. ЖК-контроллер различает коман- ды по лог. 1 в старшем разряде. Единица в пятом разряде говорит о том, что передан код инструкции, определяющий параметры раз- вертки и ширину шины данных (разряды DL, N, F соответственно). • 0x0 F. Единица в третьем разряде определяет настройку режима отображения курсора и дисплея. Разрядами D,C,B (DB2, DB1, DB0) задаем включение дисплея и отображение мигающего курсора. • 0x06. Единица во втором разряде определяет настройку направле- ния сдвига курсора и экрана. Разрядами I/D, SH (DB1, DB0) задаем смещение счетчика на увеличение адреса DDRAM при записи в не- го (сдвиг курсора вправо), а также отключаем смещение всего экра- на при записи в DDRAM. После инициализации вызываем функцию очистки экрана cls(), которая отправляет в ЖК-модуль код очистки. Внутри модуля очистка реализована путем записи во все ячейки DDRAM пробела 0x2 0 и уста- новкой курсора (счетчика АС) в нулевую позицию (на начало). Далее выводим на ЖК-экран две строки: одну — из Flash-памяти, а другую — из ОЗУ микроконтроллера. Для перехода на вторую строку вызываем функцию secondline (), которая устанавливает адрес счетчика равным 0x40. С этого адреса DDRAM начинается вторая строка экрана. Команду установки курсора задают единицей в разряде DB7. За ним следует адрес памяти DDRAM. Поскольку мы передаем данные по четырехпроводной шине, снача- ла передается четыре старших бита, а затем — четыре младших. Про- цесс передачи восьми бит данных по четырехпроводной шине, согласно спецификации, должен быть следующим. 1. Установить сигнал в линии RS (команда или данные). 2. Передать в шину данных DB4.. .DB7 старшую тетраду. 3. Установить на линии синхронизации Е = 1. 4. Установить на линии синхронизации Е = 0. 5. Передать в шину данных DB4.. .DB7 младшую тетраду. 6. Установить на линии синхронизации Е = 1. 7. Установить на линии синхронизации Е = 0. Программа Программа, реализующая вывод двух строк на ЖК-экран, представ- лена в листинге 11.1.
1/1спользование ЖК-экрана 289 Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\ll.01 - Текстовый жк- экран. ^include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <util/<^elay.h> // Для доступа к функциям циклов // задержки #include <avr/pgmspace.h> // Утилиты для работы с Flash- // памятью МК // Функция отправки команды в ЖК-модуль void send_cmd_to_LCD(unsigned char command) { PORTC = (command & OxFO);// На выводах PC4-PC7 - старшие 4 // разряда команды PORTC &= ~(1 << 0); // РСО (RS) = 0, т.е. передаем // команду (не данные) PORTC |= (1 « 1); // PCI (Е) - синхронизация _delay_us(2); PORTC &= ~(1 « 1)} // PCI(E) - синхронизация PORTC = ((command & OxOF) « 4);// На выводах PC4-PC7 - // младшие 4 разряда команды PORTC &= ~(1 « 0); // РСО (RS) = 0, т.е. передаем // команду PORTC |= (1 « 1); // PCI(Е) - синхронизация delay_us(2); PORTC &= ~(1 « 1); // PCI(Е) - синхронизация _delay_us(50); // Операция, согласно спецификации // длится более 43 мс } 11 Функция отправки 8 бит данных в ЖК-модуль void send char to_LCD(unsigned char data) { PORTC = (data & OxFO); // На выводах PC4-PC7 - старшие 4 II разряда команды PORTC |= (1 « 0); // PCO(RS) = 1, т.е. передаем данные PORTC |= (1 « 1); // PCI (Е) - синхронизация _delay_us(2) ; PORTC &= ~(1 << 1); // PCI(Е) - синхронизация PORTC = ((data & OxOF) << 4); // На выводах РС4-РС7 - // младшие 4 разряда команды PORTC |= (1 « 0)г // РСО(RS) = 1, т.е. передаем данные PORTC |= (1 « 1); // РС1(Е) - синхронизация
290 Глава 11 _delay_us(2); PORTC &= ~ (1 << 1); // PCI (E) - синхронизация _delay__us(50); // Операция, согласно спецификации, // длится более 43 мс _delay_ms(50); // Задержка для наглядного отображения } // Функция очистки экрана void cis () { send_cmd_to_LCD(0x01); // Отправляем в ЖК-модуль код // команды очистки экрана _delay_ms(3); // Ожидаем более 1,53 мс согласно // спецификации } // Функция перехода на вторую строку void secondline() { // В двухстрочном режиме // Первая строка в DDRAM начинается с адреса 0x00 и // оканчивается адресом 0x27. Вторая строка начинается //с адреса 0x40 и оканчивается адресом 0x67. // Устанавливаем курсор на адрес 0x40, т.е. на начало второй // строки send_cmd_to_LCD(0x00) ; // 0Ы1000000-- delay ms (3) ; } // Функция отправки в ЖК-модуль строки, хранящейся во Flash- // памяти МК void send_str_from_flash_to_lcd(const char *s) { uint8_t i; // Цикл выполняется до тех пор, пока из Flash-памяти не будет // считан символ '\0' for (i = 0; pgmread—byte(&s[i]); i++) { send_char_to_LCD(pgm_read_byte(&s[i])); // Отправляем // символ на ЖК _delay_ms(1); } } // Функция отправки в ЖК-модуль строки, хранящейся в ОЗУ МК void send_str_from_data_to_lcd(char *str )
Использование ЖК-экрана 291 { while (*str) // Пока есть, что отправлять { send_char_to_LCD(*str++) ; // Отправляем символ _delay_ms(1); } } // Функция начальной инициализации ЖК-дисплея void IcdinitO { _delay_ms (60); // Ждем больше 30 мс, согласно спецификации send_cmd_to_LCD (0x28); // ОЬООЮЮОО - передача по 4 бита, //2 строки, шрифт 5x7 _delay_us(50); // Согласно спецификации, больше 39 мс send_cmd_to_LCD(OxOF); // ObOOOOllll - включение экрана, // отображение мигающего курсора _delay_us(50); // Согласно спецификации, больше 39 мс cis (); // Очистка экрана send_cmd_to_LCD(0x06); // ОЬОООООНО - инкрементирование, // сдвиг всего экрана отключен _delay_us(50); // Согласно спецификации, больше 39 мс } // Строка, размещенная в Flash-памяти МК const char pervaja_stroka[] PROGMEM =11 FLASH MK TO LCD"; int main (void) { DDRC = 0Ы1110011; // Выводы PCO, PCI, PC4-PC7 - выходы PORTC = 0x00; // На все выводы порта С подаем 0 В IcdinitO; // Инициализация ЖК-модуля cls(); // Очищаем ЖК-экран // Передаем строку из Flash-памяти МК в ЖК-модуль send__str_f rom_f lash_to_lcd (&pervaja_stroka [0] ) ; secondline(); // Для того чтобы начать запись во // вторую строку, необходимо // поместить на нее указатель адреса // Строка будет храниться в ОЗУ МК // Предложение написано на русском, декодировано для // отображения на ЖК-экране программой HD44780 cyrillic // decoder (автор Wildcat). Ее можно найти на прилагаемом // к книге компакт-диске в папке Программы\НВ44780 decoder. char vtoraja_stroka[] ="C Oa® MK Sa JKH; // Выводим вторую строку из ОЗУ МК в ЖК-модуль send_str_from_data_to_lcd(&vtoraja_stroka[0] ); }
292 Глава и Применение стандартных функций вывода при работе с ЖК-дисплеем В рассмотренном выше примере был определен набор функций для работы с ЖК-модулем: send_char_to_LCD(), ledinit), cls(); secondline (), send_cmd_to_LCD (). Для удобства доступа к ним в последующих проектах этой главы мы поместим их определения в за- головочный файл led.h, который будет подключать к основной про- грамме с помощью директивы #include "led.h" (кавычки говорят о том, что файл размещен в том же каталоге, что и сама программа). Стандартные функции вывода служат для форматирования данных перед их отправкой в ЖК-модуль. Поскольку для отображения каждого символа передается соответствующий ему код, для вывода на ЖК-экран числа каждую цифру следует предварительно преобразовывать в код символа. Для этого удобнее использовать стандартные функции из заго- ловочного файла st di о. h. В частности, для форматированного служит функция printf (). Она преобразует данные из внутреннего представления в символьный вид в соответствии с форматной строкой, а затем выводит их в выход- ной поток (по умолчанию — stdout). Для вывода на ЖК-экран этот поток необходимо перенаправить на пользовательский stdout_LCD, который использует для отправки символов определенную в файле led.h функцию send_char_to_LCD (). Флаг %i в функции printf () позволяет преобразовывать число в набор кодов символов. Вывод чисел с плавающей запятой осуществ- ляется по-другому. Вначале число переводится в набор кодов с помо- щью функции sprintf(Буфер_для_строки, Флаг_преобразо- вания, Число), после чего строка выводится функцией printf (). Для того чтобы активизировать преобразование чисел с плавающей запятой, необходимо в AVR Studio выбрать команду меню Project ► Configuration Options (Проект ► Параметры конфигурации), в диало- говом окне Project Options (Параметры проекта) перейти в раздел Lib- raries (Библиотеки), выделить в списке Available Link Objects (Доступ- ные объекты для связывания) элементы libprintf_flt.a и libm.a и нажать кнопку Add Library (Добавить библиотеку) (рис. 11.2). Затем необходимо перейти в раздел Custom options (Пользователь- ские параметры), выбрать в списке Custom Compilation Options (Поль- зовательские параметры компиляции) элемент Linker Options (Пара- метры компоновщика) и добавить с помощью кнопки Add переключа- тели -Wl, -u, vfprintf (рис. 11.3).
Использование ЖК-экрана 293 Рис. 11.2. Добавление библиотек для работы с плавающей запятой Рис. 11.3. Добавление параметров компоновщика
294 Глава 11 Схема соединений для программы, представленной в листинге 11.2, такая же, как в предыдущем примере (см. рис. 11.1). е Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegai6\n.O2 - Стандартные функции вывода. :: < ••V; ;Y?Y:?'.;WsW J i И ft Тй НТ - -S! * 4 1 й Шт Й Й f Т1 6 О 'S’ Т -ТТ - $->'?<<:'; ТТТТ- ТТ - ТТЙЙ'?-ОЙ ЙЙЙ; Т? й • #include <avr/io.h> // Заголовочный файл подключает определения // ввода- вывода для устройства, // используемого в проекте #include <util/delay.h> // Для доступа к функциям циклов и задержки #include <avr/pgmspace.h> // Утилиты для работы с Flash- и памятью МК #include <Stdio.h> и Стандартные средства ввода-вывода #include “led.h" // Библиотека функций для работы и с ЖК-модулем // Создаём поток mystdout_LCD. // FILE — структура, содержащая информацию о потоке. // Функция send_char_to_LCD - для вывода. Функцию для ввода не // назначаем (NULL). Установив флаг _FDEV_SETUP_WRITE, // используем только вывод. static FILE stdout_LCD ж FDEV_SETUP_STREAM(send_char_to_LCD, NULL, _FDEV_SETUP_WRITE); int main (void) { // Перенаправляем стандартный поток на созданный нами // stdout_LCD stdout = &stdout_LCD; DDRC = 0Ы1110011; // Выводы PCO, PCI, PC4-PC7 - выходы PORTC = 0x00; // На все выводы порта С подаем 0 В ledinit(); // Инициализация ЖК-модуля printf("STROKA ABCD"); // Вывод строки secondline(); // Переход на вторую строку printf("CHISLO -%i”,12345); // Вывод целого числа _delay_ms(1000); // Задержка 2 с _delay_ms(1000); cis О; // Очистка экрана и установка // курсора в начальное положение char mystr[7]; // Буфер // Вывод числа с плавающей запятой double chislo_s_plavauwei_zapjatoi = 0.12345;
Использование ЖК-экрана 295 // Преобразовываем число с плавающей запятой в строку, // 5 знаков после запятой sprintf(&mystr[0], "%.5£и/ chislo_s_plavauwei_zapjatoi); printf(&mystr[0]); // Отправляем символы из массива mystr[] } Вывод на ЖК-экран результатов аналого- цифрового преобразования На входы мультиплексора АЦП (ADC0-ADC4) подано пять уров- ней напряжения, значения которых выводятся на ЖК-экран (рис. 11.4). Рис. 11.4. Схема соединений для отображения на ЖК-экране уровней напряжения
296 Глава 11 Шина данных ЖК-модуля подключена к выводам РС4-РС7 микро- контроллера, линия выбора регистра RS — к выводу РС2, линия син- хронизации Е — к выводу РСЗ. В программе, представленной в листинге 11.3, все функции для ра- боты с ЖК-экраном, как и в предыдущем примере, определены в заго- ловочном файле led.h. Контроллер ЖК-дисплея настраиваем на ис- пользование четырехразрядной шины данных, размер шрифта 5x7, ото- бражение мигающего курсора. Размеры знакоместа одного символа ЖК- экрана — 5x8 точек, но для вывода курсора мы уменьшаем размер шрифта до 5x7. При инициализации АЦП определяем использование внешнего ис- точника опорного напряжения с вывода AREF микроконтроллера. Уста- навливаем режим непрерывного преобразования, не зависящего от сос- тояния флага ADIF. Его результат сохраняется в регистре данных АЦП: регистровой паре ADCH:ADCL. В бесконечном цикле отслеживаем момент завершения преобразо- вания (установился флаг ADIF). Как только это произошло, сохраняем результат в 16-разрядную переменную adc_result. Перед выводом значения напряжения на дисплей переводим его в вольты по формуле, приведенной в спецификации. Поскольку для вывода на экран необходимо передавать символы, а полученное значение напряжение — целое число, с помощью функции sprintf () преобразовываем его в символьный массив mystr [] и пе- редаем в ЖК-модуль, используя стандартную функцию форматирован- ного вывода print f (). Далее обнуляем разряд ADIF. Если этого не сделать, не будет рас- познано условие завершения преобразования. Подача аналогового сиг- нала на АЦП осуществляется через мультиплексор. Для выбора одного из его входов служит регистр ADMUX. Измеряем напряжение на одном из входов мультиплексора и переходим к следующему. Полученные ре- зультаты отображаем на ЖК-экране. Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegal6\ll. 03 - Вывод реэуль,- татов АЦП. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <util/delay.h> // Для доступа к функциям циклов // задержки
Использование ЖК-экрана 297 #include <avr/pgmspace.h> // Утилиты для работы с Flash- // памятью МК #include <stdlo.h> // Стандартные средства ввода-вывода #include 11 led.h" // Библиотека функций для работы // с ЖК-модулем // Функция инициализации АЦП void ADC_INIT() { ADCSRA |= (1 << 7) ; // ADEN=1 - разрешить работу АЦП // ADPS2=1, ADPS1=1, ADPSO=1, т.е. коэффициент деления - 128 ADCSRA |= (1 «2) | (1 « 1) | (1 « 0); ADCSRA |= (1 << 5); // ADATE=1 - режим работы II определяется содержимым регистра SFIOR II SFIOR - регистр специальных функций ввода-вывода // Источник сигнала "СТАРТ”: ADTS2=0, ADTS1=O, ADTS0=0, // т.е режим непрерывного преобразования SFIOR &= ~(1 « 7) & ~(1 « 6) & ~(1 « 5); //ADMUX - регистр управления мультиплексором АЦП ADMUX &= ~ (1 << 7) & - (1 << б) ; //Внешний источник опорного // напряжения (5 В), подключенный // к выводу AREF (в STK500 снять // перемычку AREF) ADMUX = 0; // Задействовать несимметричный вход ADC0 (РАО) } // Создаём поток mystdout_LCD. FILE — структура, содержащая // информацию о потоке. Функция send_char_to_LCD - для вывода. // Функцию для ввода не. назначаем (NULL) . Установив флаг // __FDEV_SETUP_WRITE, используем только вывод. static FILE stdout_LCD = FDEV_SETUP_STREAM(send_char_to_LCD/ NULL, _FDEV_SETUP_WRITE); int main (void) { // Перенаправляем стандартный поток на stdout_LCD stdout = &stdout_LCD; DDRC = 0Ы1111100; PORTC = 0x00; ledinit(); ADC-INITQ ; ADCSRA |= (1 « 6); // преобразование // преобразования // Выводы PC2 -PC7 - выходы // На все выводы порта С подаем 0 В // Инициализация ЖК-модуля // Инициализация АЦП // ADSC=1 - активизируем (в режиме непрерывного или одиночного активизируется программно. Если же
298 Глава 11 // источник стартового сигнала - любое из прерываний, то // установка ADSC производится аппаратно после // возникновения установленного прерывания while (1) // Бесконечный цикл { delay jms(1); // Задержка 1 мс // По окончании преобразования разряд ADIF регистра ADCSRA // устанавливается в "1". Сбрасывается записью "I”. if (ADCSRA & (1 « 4)) // Если преобразование завершено, то { unsigned int adc_result = ADC; // Считываем результат // преобразования из регистра данных АЦП (ADC) // Входное напряжение вычисляется по формуле // Vin = (ADC * REF) / 1024 double adc_result_volts = (double)(adc_result*5)/1024; char mystr[7J; // Буфер для перевода числа в строку // Вывод числа с плавающей запятой // Преобразовываем число с плавающей запятой в строку, // 2 знака после запятой sprintf(&mystr[0], adc_result_volts); printf(”%s, &mystr[0]); // Передаем строку на ЖК-экран // (значение напряжения) // Обнуляем разряд ADIF, указывая на завершение // преобразования. Это осуществляется принудительно // записью единицы. Если бы мы использовали обработчик // прерывания при завершении преобразования, то разряд // ADIF был бы обнулен аппаратно. Мы его обнуляем для // того, чтобы по окончании следующего цикла // преобразования знать, что цикл завершен и можно // считывать результат. ADCSRA |= (1 « 4); ADMUX = ADMUX +1; // Переходим к следующему выводу // мультиплексора АЦП для измерения напряжения // Если вывод 3 (РА2), то переходим к строке 2 экрана if (ADMUX == 0x03) { secondline(); } // Если вывод 6 (РА5), то возвращаемся к выводу 1 (РАО) if (ADMUX == 0x05) { _delay_ms(200) ; cis О; ADMUX = 0; } } } } Измерение тока, напряжения, температуры Схема соединений для данного примера показана на рис. 11.5.
Использование ЖК-экрана 299
300 Глава 11 Сила тока вычисляется по падению напряжения на шунте Яш (0,025 Ом). Напряжение при прохождении шунта подается на дифференциаль- ные входы АЦП (ADC0 и ADC1). Зная номинал шунта и напряжение на нем, мы можем найти и силу тока через шунт. Например, если падение напряжения на Яш = +0,0745 В, то сила тока составляет I = U / Яш = 0,0745 В / 0,025 Ом = 2,98 А. При подаче на неинвертирующий вход АЦП отрицательного, а на инвертирующий — положительного напряжения, напряжение при ин- дикации будет со знаком “минус”. Напряжение 0..50 В подается через делитель (резисторы R1 и R2) на вход ADC6. В данном случае АЦП работает в недифференциальном ре- жиме. Измеряемое напряжение отсчитывается от “земли”. Делитель да- ет не больше 5 В на вход, согласно спецификации микроконтроллера. Температура измеряется датчиком МСР9801 и передается по ин- терфейсу TWI на выводы микроконтроллера SCL и SDA. Результаты измерений (ток, напряжение, температура) отображают- ся на ЖК-экране (2x16), подключенного к выводам РС2-РС7 микрокон- троллера. В программе, представленной в листинге 11.4, функции для работы с ЖК-модулем на базе K.S0066U, температурным датчиком МСР9801 и АЦП определены в заголовочных файлах 1'cd.h, twi__МСР9801 .h и adc. h и подключены с помощью директивы #include Ка- вычки говорят о том, что эти файлы находятся в одном каталоге с ос- новным кодом программы. В бесконечном цикле с помощью функции printf () выводим в первой строке ЖК-экрана _“I_[A]:U[B]:U2[B]”, а затем, используя функ- цию secondline (), описанную в заголовочном файле led. h, отправ- ляем инструкцию перехода ко второй строке ЖК-модуля. Вызываем функцию ADC_opros (), описанную в заголовочном файле adc . h. Она переключает входы мультиплексора АЦП, считывает значения напряжений, вычисляет требуемые величины и выводит ре- зультат. Сила тока вычисляется по падению напряжения на Яш, деленному на сопротивление резистора 0,025 Ом. Напряжение U подается на вывод ADC6. Поскольку максимальное напряжение, подаваемой на вход АЦП, не должно превышать 5 В, для подачи уровня больше 5 В мы использу- ем делитель (в нашем случае — 1 к 10). Перед выводом на ЖК-экран мы умножаем полученное напряжение на 10. Напряжение U2 подается на вывод ADC7 без делителя и не должно превышать 5 В.
Использование ЖК-экрана 301 При выводе температуры после задержки индикации на четыре се- кунды очищаем экран вызовом функции cis О, выводим в первой строке ЖК-экрана слово “TEMPERATURA” и переходим ко второй строке с помощью функции secondline (). Вызываем функцию tem- peraturaO, описанную в заголовочном файле twi___MCP9801.h. Эта функция по интерфейсу TWI микроконтроллера опрашивает датчик температуры МСР9801 и отображает ее значение на ЖК-дисплее. В завершение отметим, что разряды предохранения обязательно должны быть настроены в соответствии с рис. 11.6. О tftings I HV OCDEN JTAGEN SPIEN...... EESAVE bootsz... 'BOOTRST... CKOPT BODLEVEL BODEN SUT_CK.SEL □ Boot Fla М wor • dd 1 С0( □ ...............: Browri-out detection at VCC=2.7 V Ext Crystal/Resonator High Freq.; Start-up time: 1K CK + 64 ms Г~ HIGH LOW 0xD9 I; Verify after programming ...............— ’rogram dead _ - ...... .. ..... |Re Ing addr ||||| Рис. 11.6. Настройка разрядов предохранения Программа Программа, реализующая работу схемы, показанной на рис. 11.5, представлена в листинге 11.4. W V « x - U I
302 Глава 11 Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке АТтеда1б\11.04 - Ток, напряже- ние, температура. #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, используемого в проекте #include cutil/delay.h> // Для доступа к функциям циклов // задержки #include <stdio.h> // Стандартные средства ввода-вывода #include "led.h" //• Функции для работы с ЖК-модулем #include "twi MCP9801.h" // Функций для работы с МСР9801 #include "adc.h" // Функции для работы с АЦП // Создаём поток mystdout_LCD. FILE — структура, содержащая // информацию о потоке. Функция send_char_to_LCD - для вывода. // Функцию для ввода не назначаем (NULL). Установив флаг // _FDEV_SETUP_WRITE, используем только вывод. static FILE stdout_LCD = FDEV_SETUP_STREAM(send_char_to_LCD, NULL, _FDEV_SETUP_WRITE); int main(void) { // Перенаправляем стандартный поток на stdout_LCD stdout = &stdout_LCD; DDRC = 0Ы1111100; // Выводы PC2-PC7 - выходы PORTC = 0x00; // На все выводы порта С подаем 0 i ledinit() ; // Инициализация ЖК-модуля TWIMasterlnit(); // Инициализации интерфейса TWI ADC_INIT(); // Инициализации АЦП while (1) { H Бесконечный цикл printf("%s", "I_[A]:U[B]t U2 [В] ") ; secondline(); // Переход на вторую строку ADC_opros(); // Вызываем функцию опроса выводов // АЦП (напряжений) и отображения // результатов на ЖК-экране delay jus(4000); // Задержка индикации на 4 с cis(); printf("TEMPERATURA"); // Очистка ЖК-экрана secondline(); // Переход на вторую строку temperatura(); // Вызываем функцию опроса // и индикации температуры _delay_ms(2000); // Задержка индикации 2 с cls() ; } // Очистка ЖК-экрана }
Использование ЖК-экрана 303 Подключение клавиатуры 3*4 Клавиатура подключена к выводам РА0-РА6 (рис. 11.7). Рис. 11.7. Схема подключения клавиатуры к микроконтроллеру
304 Глава 11 Выводы РАЗ-РА6 настроены как входы с внутренними подтяги- вающими резисторами, т.е. на них присутствует +5 В (лог. 1). Подавая на эти входы 0 В (GND), мы можем отслеживать состояние лог. 0. Выводы РА0-РА2 настроены как выходы. На них можно программ- но устанавливать как +5 В, так и 0 В. Программа (листинг 11.5) работает следующим образом. На одном из выходов РА0-РА2 циклично устанавливается 0 В. Предположим это вывод РАО. Тогда при нажатии на любую из кнопок [1], [4], [7], [*] на входах с подтягивающим резистором РАЗ-РА7 установится лог. 0. Счи- танное состояние выводов РА0-РА6 говорит о нажатой кнопке. При на- жатии кнопки [1] на выводе РАО — 0 В; РА1 — 5 В; РА2 — 5В, РАЗ — 0 В; РА4 — 5 В; РА5 — 5 В; РА6 — 5 В. Таким образом, опросив логи- ческое состояние выводов, получим код 0Ы110110. В основной программе инициализируем ЖК-модуль, подключен- ный к порту С. Для хранения состояния порта А, к которому подключе- ны кнопки, будем использрвать переменную key типа char. В бесконечном цикле вызываем функцию GetKeyPressedО, ко- торая выполняется до тех пор, пока не будет нажата кнопка. Результа- том функции будет код нажатой кнопки, который мы присваиваем пе- ременной key. Функция GetKeyPressed () работает следующим образом. В бес- конечном цикле оператор выбора switch изменяет состояние выходов РА0-РА2. При первом прохождении цикла на РА2 установится 0 В, при втором — на РА1, при третьем — на РАО. Последующие циклы будут повторять состояния выводов РА0-РА2. Была ли нажата кнопка, можно узнать по установке лог. 0 на одном из входов РАЗ-РА6. Если состояние одного из входов РАЗ-РА6 изменилось, то ожидаем отпускания кнопки (возврат этих входов в состояние лог. 1). Выходим из функции, вернув логическое значение на выводах РА0-РА6. Для отображения кода нажатой кнопки (состояния порта А) вызы- ваем функцию print_bin (), передав ей в качестве параметра пере- менную key. Эта функция выводит на ЖК-экран состояния выводов РА0-РА6, где лог. 1 — это 5 В, а лог. 0 — 0 В. Для индикации кнопки служит функция print_key (). Ей в каче- стве параметра передается состояние выводов РА0-РА6. В самой функ- ции используется оператор switch (key), который в зависимости от состояния порта А выводит на ЖК-экран обозначение нажатой кнопки. Мк' Исходные файлы этого примера для WinAVR, AVR Studio и Proteus находятся на прилагаемом к книге компакт-диске в папке ATmegai6\n. os - Клавиатура 3x4.
Использование ЖК-экрана 305 #include <avr/io.h> // Заголовочный файл подключает определения // ввода-вывода для устройства, // используемого в проекте #include <util/delay.h> // Для доступа к функциям циклов // задержки #include <stdio.h> // Стандартные средства ввода-вывода #include "led.h” // Функции для работы с ЖК-модулем // Функция обработки нажатой кнопки char GetKeyPressed() { while (1) // Бесконечный цикл { switch (PORTA & 0Ы11) //В зависимости от предыдущего { // состояния выводов РА0-РА2 // Устанавливаем новое состояние выводов РА0-РА2 case ObOll: PORTA = 0Ы111101; break; case 0Ы01: PORTA = 0Ы111110; break; case 0Ы10: PORTA = 0Ы111011; break; } _delay_us(1); // Ждем установки напряжения на выводах char keycode = (PINA & Oblllllll); // Запоминаем код всех // клавиш в данный момент // Если была нажата кнопка if ((keycode & 0Ы111000) != 0Ы111000) // Если изменилось { // состояние выводов РАЗ-РА6 while ((PINA & 0Ы111000) != 0Ы111000) { // Ждем, когда на РАЗ-РА6 _delay_ms(1) ; // установится +5В (отпускание } II кнопки) return (keycode); // Выходим из цикла, возвращая код //нажатой кнопки } } } // Функция вывода двоичного кода числа на ЖК-экран. // Выводит в обратном порядке, начиная с РАО. void print—bin(char bin) { do { printf("%d"t bin % 2); // Выводим остаток от деления на 2 bin = bin/2; } while (bin 1= 0 ); // Цикл выполняется до тех пор, пока // делимое не будет равно нулю }
306 Глава 11 // Функция вывода названия нажатой кнопки на ЖК-экран } void print_key(char key) { // В зависимости от состояния порта А. . . switch (key) { case 0Ы110110: printf("==1"); break; case 0Ы110101: printf ("==2") ; break; case 0Ы110011: printf ("==3"); break; case 0Ы101110: printf ("==4"); break; case ObllOllOl: printf("==5"); break; case 0Ы101011: printf ("==6"); break; case 0Ы011110: printf ("==7"); break; case 0Ы011101: printf (n==8“); break; case 0Ы011011: printf (,,==9“); break; case ObOlllllO: printf("==*"); break; case ObOllllOl: printf("==0"); break; case } ObOlllOll: printf("==#"); break; И 118 // 117 // 115 // Создаем поток mystdout_LCD. FILE — структура, содержащая // информацию о потоке. Функция send_char_to_LCD - для вывода. // Функцию для ввода не назначаем (NULL). Установив флаг // _FDEV_SETUP_WRITE, используем только вывод. static FILE stdout_LCD = FDEV_SETUP_STREAM(send_char_to_LCD, NULL, _FDEV_SETUP_WRITE); int main (void) { // ЖК-экран stdout = &stdout_LCD; // Перенаправляем стандартный поток // на stdout_LCD DDRC = 0Ы1111100; // Выводы PC2-PC7 - выходы PORTC = ObOOOOOOOO; // На все выводы порта С подаем 0 В IcdinitO; // Инициализация ЖК-модуля // Кнопки DDRA = ОЬООООШ; И РАЗ-РА6 - входы. РА0-РА2 - выходы PORTA = 0Ы111110; и РАЗ-РА6 - входы, нагруженные и подтягивающими резисторами (на // входах присутствует +5В). На РА2, и РА1 подано +5 В, а на РАО - 0 В. char key; // Переменная для хранения кода нажатой кнопки while (1) // Бесконечный цикл
Использование ЖК-экрана 307 : ;. ...,.. . ; { cls(); // Очистка экрана key = GetKeyPressed(); // Получаем код нажатой кнопки print_bin(key); // Выводим двоичный код нажатой // кнопки print_key(key); // Выводим обозначение нажатой // кнопки на ЖК-экран _delay_ms(5000); // Задержка индикации нажатой // кнопки 5 с } }
Содержимое прилагаемого к книге компакт-диска Прилагаемый к книге компакт-диск содержит четыре папки: AT90S2313 — исходные коды проектов, рассмотренных в части II; ATmegal6 — исходные коды проектов, рассмотренных в части III; Документация — PDF-файлы с техническими описаниями уст- ройств, использованных в рассмотренных проектах; Программы — установочные пакеты, необходимые для разработки, компиляции и имитации рассмотренных проектов и программ.