- •Медицинские микропроцессорные системы
- •Анисимов а.А.
- •Isbn © сПбГэту «лэти», 2019 введение
- •1. Общая структура микроконтроллеров avr
- •2. Программирование микроконтроллеров на языке ассемблер
- •3. Работа с отладочной платой attiny104-xnano
- •4. Устройство портов ввода-вывода
- •5. Таймеры-счётчики
- •6. Широтно-импульсная модуляция
- •7. Использование аналого-цифрового преобразователя
- •8. Передача данных по uart
- •9. Последовательный интерфейс spi
- •Приложение 1. Основные команды языка assembler для микроконтроллера attiny104
- •Список литературы
- •Оглавление
- •Медицинские микропроцессорные системы
- •197376, С.-Петербург, ул. Проф. Попова, 5
4. Устройство портов ввода-вывода
На рисунке 4.1. представлена упрощенная схема, позволяющая максимально быстро и доступно объяснить принцип работы портов ввода-вывода микроконтроллера. С подробной схемой можно ознакомиться самостоятельно с помощью фирменной документации фирмы Atmel.
Рис.4.1. Упрощённая схема порта ввода-вывода.
Итак, рассмотрим поподробнее, что же представляет собой единичный вывод микроконтроллера. На входе стоит небольшая защита из диодов, которая призвана защитить вход микроконтроллера от превышения напряжения питания. Если напряжение на входе будет выше напряжения питания, то верхний диод откроется, и это напряжение уйдёт на шину питания, где с ним будет бороться источник питания и его фильтры. Если на вход попадет отрицательное напряжение (ниже уровня земли), то оно будет нейтрализовано через нижний диод и уйдет на землю. Диоды эти достаточно маломощные (ввиду их небольших размеров), поэтому защитить они могут только от небольших скачков напряжения. Отклонение от стандартного напряжения питания (в нашем случае это +5 вольт) может составлять плюс-минус 10%, то есть 5,5 В порт может выдержать, но если подать на вывод больше 6 В, то диоды не помогут, порт может закоротить, и микроконтроллер выйдет из строя, частично или целиком, как повезёт. Поэтому крайне нежелательно экспериментировать с напряжением питания контроллера. Конденсатором обозначена паразитная емкость, которая присутствует на каждом выводе контроллера. Особой роли она не играет, но помнить о ней тем не менее не помешает.
Управляющая логика, определяющая конфигурацию порта (направление передачи данных) изображена в виде простейших переключателей. На самом деле там стоят полевые транзисторы, но для простоты объяснения используются ключи, смысл от этого не изменяется. Более подробно этот вопрос будет обсуждаться чуть дальше, когда речь зайдет о режимах работы портов ввода-вывода.
Немного об условных обозначениях. Все регистры микроконтроллера и относящиеся к ним отдельные биты в технической документации обозначаются в едином стиле: Pxn, где х представляет собой буквенное обозначение порта (PORTA, PORTB и т.д.), а n – номер конкретного бита (PORTA1, PORTB7). За каждый порт ввода-вывода отвечают три восьмибитных регистра.
PINх – этот регистр доступен только для чтения. В регистре PINx содержится информация о текущем логическом уровне на каждом из выводов порта, независимо от его настроек. Поэтому если мы хотим узнать, что у нас на входе – читаем соответствующий бит регистра PINx. Причем существует две границы: граница гарантированного нуля и граница гарантированной единицы – пороги, за которыми мы можем однозначно определить текущий логический уровень. Для напряжения питания в 5 В это 1,4 В и 1,8 В соответственно. То есть при снижении напряжения от максимума до минимума бит в регистре PIN переключится с 1 на 0 только при снижении напряжения ниже 1,4 В, и наоборот, переключение бита с 0 на 1 осуществится только по достижении напряжения в 1,8 В. То есть возникает гистерезис переключения с нуля на единицу, что исключает хаотичные переключения под действием помех и наводок, а также ошибочное считывание логического уровня между порогами переключения. При снижении напряжения питания эти пороги также снижаются, график зависимости порогов переключения от питающего напряжения можно найти в соответствующей документации на микроконтроллер.
DDRx – этот регистр определяет направление работы порта. Порт в каждый конкретный момент времени может быть либо входом, либо выходом (но для состояния битов регистра PIN это значения не имеет, оттуда можно всегда считать реальное текущее значение вывода). Если DDRxn=0 – вывод работает как ВХОД, DDRxn=1 – вывод работает как ВЫХОД.
PORTx – данный регистр управляет состоянием вывода. Когда мы настраиваем его на вход, то от PORTx зависит тип входа (Hi-Z или PullUp, об этом чуть ниже). Когда вывод настроена как выход, то значение соответствующего бита в регистре PORTx определяет состояние вывода. Если PORTxn=1, то на выходе логическая 1, если PORTxn=0, то на выходе логический ноль. Когда ножка настроена на вход и PORTxn=0, то вывод находится в режиме Hi-Z. Если PORTxn=1, то вывод переходит в режим PullUp (подтягивается к шине питания через резистор в 40 кОм).
После подачи питания все выводы микроконтроллера по умолчания находятся в дефолтном состоянии – во всех регистрах записаны нули, соответственно порты ввода-вывода работают на вход и без подтяжки к питанию (так называемое третье состояние, Hi-Z). Для наглядности сведем эти данные в таблицу 4.1.
Таблица 4.1. Управляющие регистры портов ввода-вывода
DDxn |
PORTxn |
Ввод/Вывод |
Pull-up |
Комментарий |
0 |
0 |
Вход |
нет |
Вывод находится в третьем состоянии (Hi-Z) |
0 |
1 |
Вход |
есть |
Если вывод на земле, Pxn станет источником тока |
1 |
0 |
Выход |
нет |
На выходе – низкий уровень (логический 0) |
1 |
1 |
Выход |
нет |
На выходе – высокий уровень (логическая 1) |
Рассмотрим каждый из режимов работы портов ввода-вывода поподробнее.
Режим выхода
Здесь всё предельно просто – если нам необходимо выдать в порт логическую единицу, мы переводим порт в режим работы на выход (DDRxn=1) и записываем в регистр PORTxn единицу – при этом замыкается верхний переключатель, и на нужном выводе появляется напряжение питания, соответствующее логической единице. И наоборот, если нам на выходе нужен логический ноль, записываем в PORTxn ноль (всё предельно логично), и получаем на соответствующем выводе напряжение земли, что соответствует уровню логического нуля.
Для справки: состояние высокого импеданса или Z-состояние (в англоязычной литературе Hi-Z) – это такое состояние контакта логической схемы, при котором сопротивление между этим контактом и остальной схемой очень велико (принимается близким к бесконечности, то есть эквивалентно обрыву цепи, в реальности сотни МОм). Физически реализуется закрытым полевым транзистором, работающим в ключевом режиме.
Вывод, переведённый в состояние Hi-Z, ведёт себя как оторванный от схемы. Внешние устройства, подключенные к этому выводу, могут изменять напряжение на нём по своему усмотрению (в некоторых пределах), не влияя на работу остальной схемы. Этот режим в большинстве микроконтроллерных устройств включен по умолчанию, после сброса по питанию. Все переключатели разомкнуты, а сопротивление порта очень велико (в идеале – стремится к бесконечности). То есть электрически вывод как будто вообще никуда не подключен и ни на что не влияет. Но при этом он постоянно считывает свое состояние в регистр PINxn, и мы всегда можем узнать, что находится на входе – логическая единица или ноль.
PullUp – вход с подтяжкой к питанию.
При DDRxn=0 и PORTxn=1 замыкается переключатель подтягивающего резистора, и к линии питания подключается резистор с сопротивлением примерно 40кОм, что моментально переводит линию в состояние логической единицы. Цель подтяжки к питанию проста и понятна – она гарантирует независимость состояния нашего вывода от различных помех и наводок извне, без неё наш микроконтроллер может вести себя абсолютно непредсказуемо (особенно если устройство работает в промышленном цеху или неподалёку от телевизионной башни), хаотически переключаясь с одного логического уровня к другому. При этом, если на входе появляется логический ноль, резистор с достаточно большим сопротивлением не может удержать линию под напряжением питания и тут же отпускает её на землю, на входе при этом появляется уровень логического ноля, что можно с легкостью зафиксировать. Это обстоятельство можно использовать при подключении к микроконтроллеру различных кнопок и переключателей.
Теперь, разобравшись в режимах работы портов ввода-вывода, можно перейти к первой практической работе. По старой доброй традиции, начнем с самого простого – будем мигать светодиодом (своеобразный Hello-World микроконтроллерного мира). У нашей отладочной платы ATTINY104-XNANO есть один пользовательский светодиод, доступный для наших нужд, катод которого подключен к пятому выводу порта A (см. рисунок 4.2), а анод к плюсу питания. То есть для того, чтобы зажечь светодиод, нам необходимо настроить нужный порт на выход, и подать нужный вывод логический ноль. Для ограничения тока используется стандартный резистор для поверхностного монтажа с сопротивлением 360 Ом, дающий примерно 14мА на светодиод, чего вполне достаточно для стабильного, в меру яркого свечения.
Рис.4.2. Устройство отладочной платы ATTINY104-XNANO
В качестве первого задания предлагается следующий алгоритм работы:
Инициализируем периферию микроконтроллера (вывод А5 должен работать на выход)
Зажигаем светодиод, делаем задержку примерно в 1 секунду
Гасим светодиод, делаем ещё одну задержку в 1 секунду
Повторяем до отключения питания в бесконечном цикле
В ассемблерной программе задержки делаем с помощью вложенного цикла, в программе на Си – через функцию delay (это достаточно просто, зато позволяет сразу оценить все преимущества языка Си перед Ассемблером).
//****************************************************************//
// Инициализация периферии
*****************************************************************//
LDI r16,0b00100000
OUT DDRA, R16
OUT PORTA, R16
//****************************************************************//
// Основной цикл программы
Main:
LDI r16, 0b11011111
OUT PORTA, r16
RCALL Delay // вызываем задержку
LDI r16, 0b00100000
OUT PORTA, r16
RCALL Delay // вызываем задержку
RJMP Main
//***************************************************************//
// подпрограмма задержки
.equ LowByte = 255
.equ MedByte = 255
.equ HighByte = 2
Delay:
LDI R16,LowByte //Грузим три байта
LDI R17,MedByte // Нашей задержки
LDI R18,HighByte
loop:
DEC R16
BRNE loop
DEC R17
BRNE loop
DEC R18
BRNE loop
RET
// Конец подпрограммы задержки *****************************************************************//
Та же самая программа, написанная на языке Си. Оцените разницу!
//***************************************************************
#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRA |= (1 << DDRA3); // PORTA5 на выход
while (1) // Бесконечный цикл
{
PORTA |= (1 << PORTA3); // Включаем Светодиод
_delay_ms(500); // Ждем 500 мс
PORTA &= ~(1 << PORTA3); // Выключаем светодиод
_delay_ms(500); // Снова ждем 500 мс
}
}
//***************************************************************
Несмотря на то, что это крайне простая программа, она использует некоторые основные функции микроконтроллера AVR, которые необходимо знать. Тактовая частота ATtiny104 на плате XNANO составляет 1 МГц. Мы должны объявить это значение, прежде чем подключать библиотеку delay.h. Библиотека задержек (delay), доступная в Atmel Studio, позволяет легко задавать промежутки бездействия или ожидания микроконтроллера. В нашем случае, если мы просто включим светодиод на полной тактовой частоте, он будет мигать так быстро, что мы не сможем этого увидеть. Но благодаря задержке в 500 мс после включения и выключения, мы заставляем светодиод мигать примерно раз в секунду.
Выводы GPIO на микроконтроллере AVR по умолчанию являются входами. Для мигания светодиодом, нам нужно перевести один вывод в режим работы на выход, что позволит току протекать через светодиод и зажигать его. Конфигурация порта задается путем внесения необходимых изменений в регистр направления передачи данных Data Direction Register (DDR) для соответствующего порта. Светодиод подключен к выводу номер 3 на порту A (PA3), поэтому мы изменяем значения в регистре DDRA. Если в разряд регистра DDRA записана логическая единица, то соответствующий вывод будет сконфигурирована как выход. Ноль означает, что порт сконфигурирован как вход. Для задания PA3 к работе на выход мы записываем в соответствующий бит регистра единицу, при этом оставляя все остальные в нулевом состоянии. Мы может проверить список регистров DDRA в технической документации на наш микроконтроллер, чтобы убедиться в правильности написанного кода (рис.4.3).
Рис.4.3. Описание регистра DDRA ATtiny104
Аналогичная логика используется для установки высокого и низкого уровней на выводе для мигания светодиода. Однако, на этот раз нам нужно внести изменения в регистр PORTA, чтобы задать состояние вывода. Когда в соответствующем бите регистра установлена логическая единица, на выход подается высокий логический уровень (5 В), и светодиод загорается. Когда записывается логический ноль, то на ножке будет низкий уровень (0 В), светодиод выключается. Обратите внимание, что синтаксис, необходимый для изменения одного бита, отличается от синтаксиса, необходимого для установки бита в регистре. Мы создаем инвертированное битовое поле с заменого только необходимого бита, что очищает нужный бит и оставляет все остальные без изменений.
