Как работают порты ввода-вывода микроконтроллеров AVR. Какие регистры используются для настройки портов. Как инициализировать порты на вход и выход. Как управлять состоянием выводов и считывать входные сигналы.
Устройство портов ввода-вывода микроконтроллеров AVR
Порты ввода-вывода являются основным средством взаимодействия микроконтроллера с внешними устройствами. Каждый порт ввода-вывода микроконтроллеров AVR состоит из нескольких составляющих:
- Выводы микроконтроллера, подключенные к внешним цепям
- Регистр данных порта (PORTx)
- Регистр направления передачи данных (DDRx)
- Регистр входных данных (PINx)
- Внутренние подтягивающие резисторы
- Защитные диоды
Где x — буква, обозначающая конкретный порт (A, B, C, D и т.д.).
Регистры для работы с портами ввода-вывода
Для управления работой портов ввода-вывода используются 3 основных регистра:
1. Регистр данных порта (PORTx)
Этот регистр используется для установки выходных значений на линиях порта, когда они сконфигурированы как выходы. Также управляет подключением внутренних подтягивающих резисторов, когда линии настроены как входы.

2. Регистр направления передачи данных (DDRx)
Регистр DDRx определяет направление передачи данных для каждой линии порта. Установка бита в 1 настраивает соответствующую линию на выход, 0 — на вход.
3. Регистр входных данных (PINx)
Этот регистр позволяет считывать текущее состояние входных линий порта, независимо от их конфигурации. Доступен только для чтения.
Конфигурация линий порта
Для настройки линии порта используется следующая таблица:
DDRxn | PORTxn | Режим линии |
---|---|---|
0 | 0 | Вход без подтяжки |
0 | 1 | Вход с подтяжкой |
1 | 0 | Выход, низкий уровень |
1 | 1 | Выход, высокий уровень |
Где n — номер бита в регистре (0-7).
Инициализация портов ввода-вывода
Для настройки портов ввода-вывода необходимо выполнить следующие действия:
- Определить, какие линии порта будут использоваться как входы, а какие как выходы
- Настроить соответствующие биты в регистре DDRx
- При необходимости включить подтягивающие резисторы для входных линий через регистр PORTx
- Установить начальные значения для выходных линий через регистр PORTx
Пример инициализации порта B
Рассмотрим пример настройки порта B, где линии PB0-PB3 будут выходами, а PB4-PB7 входами с подтяжкой:

Установка высокого уровня на линии:
«`c PORTB |= (1 << PB0); // Установка высокого уровня на PB0 ```Установка низкого уровня на линии:
«`c PORTB &= ~(1 << PB1); // Установка низкого уровня на PB1 ```Переключение состояния линии:
«`c PORTB ^= (1 << PB2); // Инверсия состояния PB2 ```Считывание входных сигналов
Для чтения состояния входных линий используется регистр PINx. Вот несколько примеров:
«`c // Чтение состояния одной линии if(PINB & (1 << PB4)) { // PB4 в высоком состоянии } else { // PB4 в низком состоянии } // Ожидание изменения состояния линии while(!(PINB & (1 << PB5))); // Ждем высокого уровня на PB5 while(PINB & (1 << PB5)); // Ждем низкого уровня на PB5 // Чтение состояния всего порта uint8_t port_state = PINB; // Проверка состояния нескольких линий if((PINB & ((1 << PB6) | (1 << PB7))) == ((1 << PB6) | (1 << PB7))) { // PB6 и PB7 оба в высоком состоянии } ```Особенности работы с портами ввода-вывода
При работе с портами ввода-вывода микроконтроллеров AVR следует учитывать несколько важных моментов:
- Не все выводы микроконтроллера могут использоваться как универсальные порты ввода-вывода. Некоторые имеют альтернативные функции (например, АЦП, ШИМ, UART и т.д.).
- Максимальный ток, который может обеспечить или принять одна линия порта, ограничен (обычно 20-40 мА). При необходимости управления более мощной нагрузкой следует использовать дополнительные драйверы.
- Входы портов имеют защитные диоды, но они рассчитаны на небольшие токи. При возможности попадания на вход напряжения выше питания или ниже земли, необходимо использовать внешние защитные цепи.
- Внутренние подтягивающие резисторы имеют большое сопротивление (20-50 кОм) и не могут обеспечить большой ток. В некоторых случаях может потребоваться использование внешних подтягивающих резисторов.
Рекомендации по использованию портов ввода-вывода
Для эффективной и безопасной работы с портами ввода-вывода рекомендуется следовать следующим правилам:

- Всегда инициализируйте все используемые порты в начале программы.
- Неиспользуемые входы рекомендуется настраивать как входы с включенной подтяжкой для предотвращения ложных срабатываний от наводок.
- При подключении внешних устройств учитывайте максимально допустимые токи портов.
- Используйте макросы или inline-функции для работы с портами, чтобы сделать код более читаемым и менее подверженным ошибкам.
- При работе с кнопками или другими механическими контактами используйте программную или аппаратную защиту от дребезга.
- Будьте осторожны при использовании портов, имеющих альтернативные функции, чтобы избежать конфликтов.
Отладка работы с портами ввода-вывода
При возникновении проблем с работой портов ввода-вывода можно использовать следующие методы отладки:
- Используйте светодиоды для индикации состояния выходных линий.
- Применяйте осциллограф или логический анализатор для просмотра сигналов на линиях портов.
- Используйте отладочные выводы в программе для печати состояния портов через UART.
- Проверяйте правильность инициализации портов в начале программы.
- Убедитесь, что не происходит конфликтов с альтернативными функциями выводов.
Правильное использование портов ввода-вывода является ключевым аспектом разработки встраиваемых систем на базе микроконтроллеров AVR. Понимание принципов работы портов и следование рекомендациям позволит создавать надежные и эффективные устройства.

AVR. Учебный курс. Устройство и работа портов ввода-вывода
С внешним миром микроконтроллер общается через порты ввода вывода. Схема порта ввода вывода указана в даташите:
Но новичку там разобраться довольно сложно. Поэтому я ее несколько упростил:
Итак, что же представляет собой один вывод микроконтроллера. Вначале на входе стоит небольшая защита из диодов, она призвана защитить ввод микроконтроллера от превышения напряжения. Если напряжение будет выше питания, то верхний диод откроется и это напряжение будет стравлено на шину питания, где с ним будет уже бороться источник питания и его фильтры. Если на ввод попадет отрицательное (ниже нулевого уровня) напряжение, то оно будет нейтрализовано через нижний диод и погасится на землю. Впрочем, диоды там хилые и защита эта помогает только от микроскопических импульсов от помех. Если же ты по ошибке вкачаешь в ножку микроконтроллера вольт 6-7 при 5 вольтах питания, то никакой диод его не спасет.
Конденсатор, нарисованный пунктиром, это паразитная емкость вывода. Хоть она и крошечная, но присутствует. Обычно ее не учитывают, но она есть. Не забивай голову, просто знай это, как нибудь я тебе даже покажу как её можно применить 😉
Дальше идут ключи управления. Это я их нарисовал рубильниками, на самом деле там стоят полевые транзисторы, но особой сути это не меняет. А рубильники наглядней.
Каждый рубильник подчинен логическому условию которое я подписал на рисунке. Когда условие выполняется — ключ замыкается.
Есть в каждом контроллере AVR (в PIC есть тоже подобные регистры, только звать их по другому).
Например, смотри в даташите на цоколевку микросхемы:
Видишь у каждой почти ножки есть обозначение Pxx. Например, PB4 где буква «B» означает имя порта, а цифра — номер бита в порту. За порт «B» отвечают три восьмиразрядных регистра PORTB, PINB, DDRB, а каждый бит в этом регистре отвечает за соответствующую ножку порта. За порт «А» таким же образом отвечают PORTA, DDRA, PINA.
PINх
Это регистр чтения. Из него можно только читать. В регистре PINx содержится информация о реальном текущем логическом уровне на выводах порта. Вне зависимости от настроек порта. Так что если хотим узнать что у нас на входе — читаем соответствующий бит регистра PINx Причем существует две границы: граница гарантированного нуля и граница гарантированной единицы — пороги за которыми мы можем однозначно четко определить текущий логический уровень. Для пятивольтового питания это 1.4 и 1.8 вольт соответственно. То есть при снижении напряжения от максимума до минимума бит в регистре PIN переключится с 1 на 0 только при снижении напруги ниже 1.4 вольт, а вот когда напруга нарастает от минимума до максимума переключение бита с 0 на 1 будет только по достижении напряжения в 1. 8 вольта. То есть возникает гистерезис переключения с 0 на 1, что исключает хаотичные переключения под действием помех и наводок, а также исключает ошибочное считывание логического уровня между порогами переключения.
DDRx
Это регистр направления порта. Порт в конкретный момент времени может быть либо входом либо выходом (но для состояния битов PIN это значения не имеет. Читать из PIN реальное значение можно всегда).
- DDRxy=0 — вывод работает как ВХОД.
- DDRxy=1 вывод работает на ВЫХОД.
PORTx
Режим управления состоянием вывода. Когда мы настраиваем вывод на вход, то от PORT зависит тип входа (Hi-Z или PullUp, об этом чуть ниже).
Когда ножка настроена на выход, то значение соответствующего бита в регистре PORTx определяет состояние вывода. Если PORTxy=1 то на выводе лог1, если PORTxy=0 то на выводе лог0.
Когда ножка настроена на вход, то если PORTxy=0, то вывод в режиме Hi-Z. Если PORTxy=1 то вывод в режиме PullUp с подтяжкой резистором в 100к до питания.
Есть еще бит PUD (PullUp Disable) в регистре SFIOR он запрещает включение подтяжки сразу для всех портов. По дефолту он равен 0. Честно говоря, я даже не знаю нафиг он нужен — ни разу не доводилось его применять и даже не представляю себе ситуацию когда бы мне надо было запретить использование подтяжки сразу для всех портов. Ну да ладно, инженерам Atmel видней, просто знай что такой бит есть. Мало ли, вдруг будешь чужую прошивку ковырять и увидишь что у тебя подтяжка не работает, а вроде как должна. Тогда слазаешь и проверишь этот бит, вдруг автор прошивки заранее где то его сбросил.
Общая картина работы порта показана на рисунке:
Теперь кратко о режимах:
- Режим выхода
Ну тут, думаю, все понятно — если нам надо выдать в порт 1 мы включаем порт на выход (А если надо ноль, то в PORTxy записываем 0 и открывается уже нижний вентиль, что дает на выводе около нуля вольт.
- Вход Hi-Z — режим высокоимпендансного входа.
Этот режим включен по умолчанию. Все вентили разомкнуты, а сопротивление порта очень велико. В принципе, по сравнению с другими режимами, можно его считать бесконечностью. То есть электрически вывод как бы вообще никуда не подключен и ни на что не влияет. Но! При этом он постоянно считывает свое состояние в регистр PIN и мы всегда можем узнать что у нас на входе — единица или ноль. Этот режим хорош для прослушивания какой либо шины данных, т.к. он не оказывает на шину никакого влияния. А что будет если вход висит в воздухе? А в этом случае напряжение будет на нем скакать в зависимости от внешних наводок, электромагнитных помех и вообще от фазы луны и погоды на Марсе (идеальный способ нарубить случайных чисел!). Очень часто на порту в этом случае нестабильный синус 50Гц — наводка от сети 220В, а в регистре PIN будет меняться 0 и 1 с частотой около 50Гц - Вход PullUp — вход с подтяжкой.
При DDRxy=0 и PORTxy=1 замыкается ключ подтяжки и к линии подключается резистор в 100кОм, что моментально приводит неподключенную никуда линию в состояние лог1. Цель подтяжки очевидна — недопустить хаотичного изменения состояния на входе под действием наводок. Но если на входе появится логический ноль (замыкание линии на землю кнопкой или другим микроконтроллером/микросхемой), то слабый 100кОмный резистор не сможет удерживать напряжение на линии на уровне лог1 и на входе будет нуль.
Также почти каждая ножка имеет дополнительные функции. На распиновке они подписаны в скобках. Это могут быть выводы приемопередатчиков, разные последовательные интерфейсы, аналоговые входы, выходы ШИМ генераторов. Да чего там только нет. По умолчанию все эти функции отключены, а вывод управляется исключительно парой DDR и PORT, но если включить какую-либо дополнительную функцию, то тут уже управление может полностью или частично перейти под контроль периферийного устройства и тогда хоть запишись в DDR/PORT — ничего не изменится. До тех пор пока не выключишь периферию занимающую эти выводы.
Например, приемник USART. Стоит только выставить бит разрешения приема RXEN как вывод RxD, как бы он ни был настроен до этого, переходит в режим входа.
Совет:
С целью снижения энергопотребления и повышения надежности рекомендуется все неиспользованные пины включить в режим PullUp тогда их не будет дергать туда сюда помехой, а если на порт свалится грубая сила (например, монтажник отвертку уронит и коротнет на землю) то линия не выгорит.
Как запомнить режимы, чтобы не лазать каждый раз в справочник:
Чем зазубривать или писать напоминалки, лучше понять логику разработчиков, проектировавших эти настройки, и тогда все запомнится само.
Итак:
- Самый безопасный для МК и схемы, ни на что не влияющий режим это Hi-Z.
- Очевидно что этот режим и должен быть по дефолту.
- Значения большинства портов I/O при включении питания/сбросе = 0х00, PORT и DDR не исключение.
- Соответственно когда DDR=0 и PORT=0 это High-Z — самый безопасный режим, оптимальный при старте.
- Hi-Z это вход, значит при DDR=0 нога настроена на вход. Запомнили.
- Однако, если DDR=0 — вход, то что будет если PORT переключить в 1?
- Очевидно, что будет другой режим входа. Какой? Pullup, другого не дано! Логично? Логично. Запомнили.
- Раз дефолтный режим был входом и одновременно в регистрах нуль, то для того, чтобы настроить вывод на выход надо в DDR записать 1.
- Ну, а состояние выхода уже соответствует регистру PORT — высокий это 1, низкий это 0.
- Читаем же из регистра PIN.
Есть еще один способ, мнемонический:
1 похожа на стрелку. Стрелка выходящая из МК — выход. Значит DDR=1 это выход! 0 похож на гнездо, дырку — вход! Резистор подтяжки дает в висящем порту единичку, значит PORT в режиме Pullup должен быть в единичке!
Все просто! 🙂
Для детей в картинках и комиксах 🙂
Для большей ясности с режимами приведу образный пример:
Уровень напряжения на выводе словно планка, которая может двигаться вертикально вверх или вниз. В режиме Hi-Z мы можем на эту планку только смотреть, а двигать или как то на нее воздействовать мы не можем. Поэтому любая помеха может ее дрыгать как угодно, но зато если мы ее куда прицепим, то ее уровень будет зависеть только от другой цепи и ей мы не помешаем.
В режиме PullUp эту планку мы пружиной подтянули кверху. Слабые помехи не смогут больше ее дрыгать как угодно. С другой стороны шине она может помешать, но не факт что заблокирует ее работу. От шины зависит и ее силы. А еще мы можем отслеживать тупую внешнюю силу, вроде кнопки, которая может взять и придавить ее к земле. Тогда мы узнаем что кнопка нажата.
В режиме OUT у нас планка прибита гвоздями к земле или прижата домкратом к питанию. Внешняя сила может ее пересилить только сломав домкрат или сломается сама. Тупая внешняя сила просто разрушает наш домкрат или вырывает гвозди из пола с мясом. В любом случае — девайс в помойку.
Устройство и работа портов ввода-вывода микроконтроллеров AVR. Часть 1 / Хабр
Работа портов ввода/вывода
Изучив данный материал, в котором все очень детально и подробно описано с большим количеством примеров, вы сможете легко овладеть и программировать порты ввода/вывода микроконтроллеров AVR.
- Часть 1. Работа портов ввода/вывода
- Часть 2. Подключение светодиода к линии порта ввода/вывода
- Часть 3. Подключение транзистора к линии порта ввода/вывода
- Часть 4. Подключение кнопки к линии порта ввода/вывода
Пример будем рассматривать на микроконтроллере ATMega8.
Программу писать будем в Atmel Studio 6.0.
Эмулировать схему будем в Proteus 7 Professional.
С внешним миром микроконтроллер общается через порты ввода вывода. Схема порта ввода вывода указана в даташите:
Но новичку разобраться довольно со схемой довольно сложно. Поэтому схему упростим:
Pxn – имя ножки порта микроконтроллера, где x буква порта (A, B, C или D), n номер разряда порта (7… 0).
Cpin — паразитная емкость порта.
VCC — напряжение питания.
Rpu — отключаемый нагрузочный верхний резистор (pull-up).
PORTxn — бит n регистра PORTx.
PINxn — бит n регистра PINx.
DDRxn — бит n регистра DDRx.
Рассмотрим, что же представляет собой вывод микроконтроллера. На входе микроконтроллера стоит небольшая защита из двух диодов (см.1), она предназначенная для защиты ввода микроконтроллера от кратковременных импульсов напряжения, превышающих напряжение питания. Если напряжение будет выше питания, то верхний диод откроется и это напряжение будет стравлено на шину питания, где с ним будет уже бороться источник питания и его фильтры. Если на ввод попадет отрицательное (ниже нулевого уровня) напряжение, то оно будет нейтрализовано через нижний диод и погасится на землю. Впрочем, диоды там хилые и защита эта помогает только от микроскопических импульсов и помех. Если же на ножку микроконтроллера подать вольт 6-7 при 5 вольтах питания, то внутренние диоды его не спасут.
Конденсатор (см.2) — это паразитная емкость вывода. Хоть она и крошечная, но присутствует. Обычно ее не учитывают, но она есть. Не забивай голову, просто знай это.
Дальше идут ключи управления (см.3,4). Каждый ключ подчинен логическому условию, которые нарисованы на рисунке. Когда условие выполняется — ключ замыкается.
Каждый порт микроконтроллера AVR (обычно имеют имена A, B и иногда C или даже D) имеет 8 разрядов, каждый из которых привязан к определенной ножке корпуса. Каждый порт имеет три специальных регистра DDRx, PORTx и PINx (где x соответствует букве порта A, B, C или D). Назначение регистров:
DDRx – Настройка разрядов порта x на вход или выход.
PORTx – Управление состоянием выходов порта x (если соответствующий разряд настроен как выход), или подключением внутреннего pull-up резистора (если соответствующий разряд настроен как вход).
PINx –Чтение логических уровней разрядов порта x.
PINхn – это регистр чтения. Из него можно только читать. В регистре PINxn содержится информация о реальном текущем логическом уровне на выводах порта. Вне зависимости от настроек порта. Так что если хотим узнать что у нас на входе — читаем соответствующий бит регистра PINxn. Причем существует две границы: граница гарантированного нуля и граница гарантированной единицы — пороги за которыми мы можем однозначно четко определить текущий логический уровень. Для пятивольтового питания это 1.4 и 1.8 вольт соответственно. То есть при снижении напряжения от максимума до минимума бит в регистре PINx переключится с 1 на 0 только при снижении напряжение ниже 1.4 вольт, а вот когда напряжение нарастает от минимума до максимума переключение бита с 0 на 1 будет только по достижении напряжения в 1.8 вольта. То есть возникает гистерезис переключения с 0 на 1, что исключает хаотичные переключения под действием помех и наводок, а также исключает ошибочное считывание логического уровня между порогами переключения.
При снижении напряжения питания разумеется эти пороги также снижаются.
DDRxn – это регистр направления порта. Порт в конкретный момент времени может быть либо входом либо выходом (но для состояния битов PINxn это значения не имеет. Читать из PINxn реальное значение можно всегда).
DDRxy = 0 – вывод работает как ВХОД.
DDRxy = 1 – вывод работает на ВЫХОД.
PORTxn – режим управления состоянием вывода. Когда мы настраиваем вывод на вход, то от PORTх зависит тип входа (Hi-Z или PullUp, об этом чуть ниже).
Когда ножка настроена на выход, то значение соответствующего бита в регистре PORTx определяет состояние вывода. Если PORTxn=1 то на выводе лог.1, если PORTxn=0 то на выводе лог.0.
Когда ножка настроена на вход, то если PORTxn=0, то вывод в режиме Hi-Z. Если PORTxn=1 то вывод в режиме PullUpс подтяжкой резистором в 100к до питания.
Таблица. Конфигурация выводов портов.
DDRxn PORTxn I/O Comment
0 0 I (Input) Вход Высокоимпендансный вход. (Не рекомендую использовать, так как могут наводится наводки от питания)
0 1 I (Input) Вход Подтянуто внутренне сопротивление.
1 0 O (Output) Выход На выходе низкий уровень.
1 1 O (Output) Выход На выходе высокий уровень.
Общая картина работы порта показана на рисунках:
Рис. DDRxn=0 PORTxn=0 – Режим: HI-Z – высоко импендансный вход.
Рис. DDRxn=0 PORTxn=1 – Режим: PullUp – вход с подтяжкой до лог.1.
Рис. DDRxn=1 PORTxn=0 – Режим: Выход – на выходе лог.0. (почти GND)
Рис. DDRxn=1 PORTxn=1 – Режим: Выход – на выходе лог.1. (почти VCC)
Вход Hi-Z — режим высокоимпендансного входа.
Этот режим включен по умолчанию. Все ключи разомкнуты, а сопротивление порта очень велико. В принципе, по сравнению с другими режимами, можно его считать бесконечностью. То есть электрически вывод как бы вообще никуда не подключен и ни на что не влияет. Но! При этом он постоянно считывает свое состояние в регистр PINn и мы всегда можем узнать что у нас на входе — единица или ноль. Этот режим хорош для прослушивания какой либо шины данных, т.к. он не оказывает на шину никакого влияния. А что будет если вход висит в воздухе? А в этом случае напряжение будет на нем скакать в зависимости от внешних наводок, электромагнитных помех и вообще от фазы луны и погоды на Марсе (идеальный способ нарубить случайных чисел!). Очень часто на порту в этом случае нестабильный синус 50Гц — наводка от сети 220В, а в регистре PINn будет меняться 0 и 1 с частотой около 50Гц
Вход PullUp — вход с подтяжкой.
При DDRxn=0 и PORTxn=1 замыкается ключ подтяжки и к линии подключается резистор в 100кОм, что моментально приводит не подключенную никуда линию в состояние лог.1. Цель подтяжки очевидна — не допустить хаотичного изменения состояния на входе под действием наводок. Но если на входе появится логический ноль (замыкание линии на землю кнопкой или другим микроконтроллером/микросхемой), то слабый 100кОмный резистор не сможет удерживать напряжение на линии на уровне лог. 1 и на входе будет лог.0.
Режим выхода.
Тут, думаю, все понятно — если нам надо выдать в порт лог.1, мы включаем порт на выход (DDRxn=1) и выдаем лог.1 (PORTxn=1) — при этом замыкается верхний ключ и на выводе появляется напряжение, близкое к питанию. А если надо лог.0, то включаем порт на выход (DDRxn=1) и выдаем лог.0 (PORTxn=1) — при этом открывается уже нижний вентиль, что дает на выводе около нуля вольт.
Порты ввода-вывода микроконтроллера ⋆ diodov.net
В этом статье мы напишем первую программу и научимся программировать порты ввода-вывода микроконтроллера.
Наша первая программа будет управлять, по началу, одним из выводов микроконтроллера. Для того чтобы удостоверится в том, что программа работает, к управляемому выводу через токоограничивающий резистор мы подключим светодиод, анод которого соединен с выводом МК, а катод с минусом (общим проводом).
По умолчанию, на всех выводах незапрограммированного микроконтроллера напряжение близкое к нулю, поэтому светодиод не будет светиться. Наша задача состоит в том, чтобы написать программу, с помощью которой на выводе МК появится напряжение +5 В. Это напряжения (точнее буде ток) засветит светодиод.
Микроконтроллер ATmega8 имеет 28 выводов, каждый из них выполняет определенные функции. Светодиод можно подключить к большинству выводов, однако, не ко всем, ведь минимум пара выводов занята под питание. Чтобы четко знать назначение каждого вывода МК воспользуется даташитом. В даташите находим распиновку (обозначение) всех выводов.
Почти каждый вывод может выполнять несколько функций. Сокращения названий функций приводятся в скобках рядом с выводами. В последующих статья мы обязательно их все рассмотрим. Сейчас же нас интересуют некоторые из них.
Для работы микроконтроллера, впрочем, как и любой другой микросхемы, необходимо напряжение. Во избежание ложных срабатываний МК нужно питать только стабильным напряжением от 4,5 В до 5,5 В. Этот диапазон напряжений строго регламентирован и приведен в даташите.
Плюс («+») источника питания подсоединяется в 7-й ножке, обозначенной VCC. Минус («-») – к 8-й или 22-й ножке, которые имеют обозначение GND (GND – сокращенно от ground – «земля»).
Остальные ножки позволяют микроконтроллеру взаимодействовать с внешними устройствами. Они объединены в отдельные группы и называются порты ввода-вывода микроконтроллера. С помощью них можно как подавать сигналы на вход МК, например с различных датчиков, так и выдавать сигналы для управления другими устройствами или для отображения информации на различных индикаторах и дисплеях.
Микроконтроллер ATmega8 имеет три порта ввода-вывода: B, C и D. Порты могут быть полными и неполными. Полный порт состоит из восьми бит и соответственно имеет столько же одноименных выводов. У неполного порта меньше 8 бит, поэтому число выводов такого порта также менее восьми.
У данного МК порты B и D полные. А порт C неполный и имеет семь бит. Еще раз обращаю внимание, что нумерация битов начинается с нуля, например PB0, PB1, PB2…
Не удивляйтесь, что у ATmega8 отсутствует порт A. Другие МК, имеющие большее число выводов, могут содержать как порт A, так и порт E. У микроконтроллеров с меньшим числом выводов может быть только один порт и тот неполный.
Знакомство с Atmel Studio 7Теперь перейдем к написанию кода программы. Наша первая программа будет устанавливать + 5 В на нулевом бите порта C PC0, т.е. на 23-м выводе микроконтроллера.
Запускаем Atmel Studio.
Сначала необходимо создать проект. Для этого в открывшемся окне кликаем по вкладке New Project.
Также проект можно создать, кликнув по вкладке File. В выпавшем меню следует выбрать New и далее Project. Или нажать комбинацию клавиш Ctrl+Shift+N.
В появившемся окне выбираем язык программирования C/C++ и кликаем по вкладке.
Далее нам нужно задать имя и место на диске для нашего проекта. Назовём наш первый проект именем 1 в строке для ввода Name. Изменить место расположения файла можно кликнув по кнопке Browse напротив строки Location. Если оставить галочку возле Create directory for solution, то в выбранном месте автоматически создастся папка с именем проекта. В данной папке помимо проекта будут созданы и другие вспомогательные файлы, поэтому я рекомендую не убирать галочку.
После того, как имя проекта и его место выбраны, нажимаем кнопку OK. Снова появляется окно. В нем нам нужно выбрать тип микроконтроллера. В нашем случае – это ATmega8. Кликаем по вкладке Device Family. В выпавшем меню выбираем серию микроконтроллеров ATmega.
С помощью прокрутки находим и выделяем микроконтроллер ATmega8 и наживаем кнопку OK.
В открывшемся окне мы видим, что Atmel Studio нам автоматически сформировала заготовку или же шаблон программы.
Рассмотрим все по порядку.
Текст, выделенный зеленым цветом – это комментарии. В данном случае приводится имя проекта и автора, а также время и дата создания проекта. Комментарии не являются программным кодом, поэтому они игнорируются компилятором. Комментарии позволяют программисту улучшить читаемость кода, что особенно помогает при поиске ошибок и отладке программы.
Цвет комментариев и других элементов программы можно изменять в настройках Atmel Studio.
/*
* 1.c
*
* Created: 07.08.2017 16:57:59
* Author : ZBL
*/
Комментарии бываю однострочные и многострочные. В данном шаблоне программы применяются многострочные комментарии. Они начинаются косой линией со звездочкой, а заканчиваются звездочкой с косой линией.
/* — начало комментария
.
.
.
.
*/ — конец комментария
Весь текс, который помещен между /* и */ полностью пропускается компилятором.
Однострочный комментарий обозначается двумя косыми линиями и действует в пределах одной строки. Текст перед двумя косыми распознается компилятором как код программы, а после – как комментарий.
// Здесь пишется однострочный комментарий
На практике использование комментариев считается хорошим тоном программирования. В дальнейшем мы будем применять оба их типа.
Директива препроцессораСледующим элементом программы является строка
#include <avr/io.h>
Эта строка указывает компилятору, что к данному файлу нужно подключить другой файл с именем io.h, который находится в папке avr. В подключаемом файле находится информация о базовых настройках микроконтроллера.
По сути, можно было бы и не подключать файл io.h, а набрать его содержимое вручную, однако это очень неудобно.
Знак # означает, что данная команда – это директива препроцессора. Дело в том, что прежде чем скомпилировать файл компилятору необходимо выполнит предварительную его обработку. Поэтому сначала выполняется некая подготовка файла к компиляции путем добавления некоторых инструкций, прописанных в файле io. h
io – название файла, которое происходит от сокращения input/output – ввод/вывод.
.h – расширение файла, название его происходит от слова header – заголовок.
Теперь все вместе. io.h – это заголовочный файл, в котором записана информация о настройках ввода-вывода микроконтроллера.
Главная функция mainНиже нам встречается следующая строка:
int main(void)
В данной строке объявляется функция, носящая имя main. С нее начинается выполнение программы. Эта функция является как бы точкой начала всей программы, написанной в текущем файле. Имя main зарезервировано в языке Си, поэтому во избежание конфликтов, таким именем нельзя называть другую функцию, находящуюся внутри данной. main переводится главный, т. е. данная функция является главной.
В синтаксисе языка Си идентификатором функции служат круглые скобки
()
Внутри скобок помещено слово void (void). Оно обозначает пустота. Это указывает на то, что функция main ничего не принимает, т. е. не принимает никаких аргументов. По мере написания более сложных программ, мы детальнее остановимся на этом моменте.
int – это целочисленный тип данных. В данном случае функция работает с целыми числами: как положительными, так и отрицательными. Существуют и другие типы данных, например с плавающей запятой, символьные и др. Более подробно мы будем рассматривать типы данных по мере необходимости или в отдельной статье. Однако для функции main рекомендуется всегда использовать тип данных int, поскольку конструкция int main(void) определена стандартом языка Си и распознается любым компилятором.
Область действия функции определяется фигурными скобками
{
.
. → тело функции
.
}
Код программы, помещенный между открывающейся и закрывающейся скобками, относится к телу функции.
В общем случае любая функция имеет следующую конструкцию:
тип данных имя функции(агрумент)
{
тело функции (код)
}
Функция whileВнутри функции main находится функция while:
while (1)
{
}
While переводится с английского «пока». Это говорит о том, что программа, которая находится в теле данной функции, будет выполняться до тех пор, пока условие истинно. Единица в круглых скобках указывает, что условие истинно, поэтому код программы, написанный в данной функции, будет повторяться бесконечное число раз, т.е. программа будет зациклена. Для чего это делается? Дело в том, что микроконтроллер должен непрерывно выполнять записанную программу. Поэтому программа не может просто взять и оборваться. Микроконтроллер всегда опрашивает порты ввода-вывода либо выдает в них сигналы, даже находясь в ждущем режиме.
Теперь, когда мы рассмотрели основные элементы конструкции программы, давайте посмотрим целиком на базовый шаблон. Без комментариев он имеет следующий вид:
#include <avr/io.h>
int main(void)
{
while (1)
{
}
}
Программирование портов ввода-вывода микроконтроллера ATmega8Сейчас мы уже можем дополнить программу нужным нам кодом. Первым делом необходимо настроить нулевой бит порта C PC0 на выход.
Мы уже знаем, что МК может, как принимать, так и выдавать сигнал, т.е. выводы (порты) его могут работать как входы и как выходы. Поэтому предварительно нужно настроить вывод МК на соответствующий режим. Для этого в микроконтроллере есть специальный регистр, который называется DDR – direct data register – регистр направления данных.
У каждого порта есть свой такой регистр. Например, регистр порта C называется DDRC, порта B – DDRB, порта D – DDRD.
Чтобы настроить вывод порта на вход в регистр DDR необходимо записать ноль, а на выход – единицу.
Команда настройки нулевого бита порта C выглядит следующим образом
DDRC = 0b0000001;
Данной командой в регистр DDRC записывается двоичное число равное десятичному 1. Префикс 0b идентифицирует данное число, как двоичное.
Двоичная форма записи очень удачно сочетается с количеством битов порта, так как количество битов соответствует количеству выводов порта, а порядковый номер бита отвечает номеру бита внутри порта.
Также можно записать в регистр шестнадцатеричное число:
DDRC = 0x1;
Однако двоичная форма записи более наглядна, поэтому ее мы и будем использовать на начальных этапах программирования микроконтроллеров.
Давайте рассмотрим еще один пример. Допустим нам необходимо настроить нулевой, третий и седьмой биты порта B на выход, а остальные биты на вход. Для этого случая код имеет такой вид:
DDRB = 0b10001001;
С целью закрепления навыков рекомендую потренироваться настраивать разные порты ввода-вывода микроконтроллера на вход и на выход.
Регистр микроконтроллера PORTПосле того, как мы настроили нулевой бит порта C PC0 на выход, нужно еще выполнить настройку, чтобы на данном выводе появилось напряжение +5 В. Для этого необходимо установить нулевой бит в регистре PORT. Если бит установлен в единицу, то на выводе будет +5 В (точнее говоря величина напряжения питания микроконтроллера, которая может находится в пределах 4,5…5,5 В для микроконтроллера ATmega8). Если бит установлен в ноль, — то на выводе будет напряжение, величина которого близка к нулю.
Каждый порт имеет свой регистр: порт A – PORTA, порт B – PORTB, порт C – PORTC.
И так, чтобы получить на выводе PC0 напряжение +5 В, необходимо записать такую команду:
PORT = 0b0000001;
Обратите внимание на то, что каждая команда заканчивается точкой с запятой.
Таким образом, чтобы засветить светодиод, нам необходимы всего лишь две команды:
DDRC = 0b0000001;
PORTС = 0b0000001;
Первой командой мы определяем вывод PC0 как вход, а второй устанавливаем на нем напряжение +5 В.
Полный код программы выглядит так:
#include <avr/io.h>
int main(void)
{
DDRC = 0b0000001;
while (1)
{
PORTC = 0b0000001;
}
}
Здесь необходимо заметить следующее: команда DDRC = 0b0000001; выполнится всего один раз, а команда PORTC = 0b0000001; будет выполняться все время в цикле, поскольку она находится в теле функции while (1). Но даже если мы вынесем команду за пределы функции и поместим ее после DDRC = 0b0000001;, светодиод и в этом случае будет светиться все время. Однако, разместив команду PORTC = 0b0000001; в теле while (1), мы делаем явный акцент на том, что светодиод должен светится все время.
Компиляция файлаТеперь, когда код полностью готов, его нужно скомпилировать. Для этого необходимо нажать клавишу F7 или кликнуть по кнопке Build и в выпавшем меню выбрать Build Solution.
Если ошибок в коде нет, то файл скомпилируется, а в нижней части экрана появится запись о том, что проект скомпилирована успешно: Build succeeded.
Таким образом программируются порты ввода-вывода микроконтроллера практически любого типа. Следующий наш шаг – это запись кода в микроконтроллер. Также можно проверить корректность работы кода с помощью программы-симулятора микроконтроллеров – Proteus.
Скачать файлы Программа 1
Использование стандартных средств ввода-вывода
В этом проекте показано, как использовать стандартные средства ввода-вывода (stdio), предоставляемые этой библиотекой. Он предполагает базовые знания о том, как подсистема stdio используется в стандартных приложениях C, и концентрируется на различиях в реализации этой библиотеки, которые в основном возникают из-за различий в среде микроконтроллера по сравнению с размещенной средой стандартного компьютера.
Эта демонстрация предназначена для дополнения документации, а не для ее замены.
Демо настроено таким образом, чтобы его можно было запустить на ATmega16, который поставляется с комплектом для разработки STK500. Порт UART должен быть подключен к «запасному» порту RS-232 с помощью соединительного кабеля, который соединяет PD0 с RxD и PD1 с TxD. Канал RS-232 настроен как стандартный ввод (
stdin
) и стандартный вывод ( stdout
) соответственно.
Чтобы иметь другое устройство, доступное для стандартного канала ошибки ( stderr
), был выбран стандартный ЖК-дисплей с HD44780-совместимым ЖК-контроллером. Этот дисплей необходимо подключить к порту A STK500 следующим образом:
Port | Header | Function |
A0 | 1 | LCD D4 |
A1 | 2 | LCD D5 |
A2 | 3 | LCD D6 |
A3 | 4 | LCD D7 |
A4 | 5 | LCD R/~W |
A5 | 6 | LCD E |
A6 | 7 | LCD RS |
A7 | 8 | unused |
GND | 9 | GND |
VCC | 10 | Vcc |
Подключение STK500
Контроллер ЖК-дисплея используется в 4-битном режиме, включая опрос флага «занято», поэтому необходимо подключить линию R/~W от контроллера ЖК-дисплея. Обратите внимание, что контроллер ЖК-дисплея имеет еще один контакт питания, который используется для регулировки контрастности ЖК-дисплея (V5). Обычно этот контакт подключается к потенциометру между Vcc и GND. Часто может работать простое подключение этого контакта к GND, в то время как оставление его неподключенным обычно приводит к нечитаемому отображению.
Порт A был выбран, так как для подключения ЖК-дисплея необходимо 7 контактов, но все остальные порты уже частично используются: порт B имеет контакты для внутрисистемного программирования (ISP), порт C имеет порты для JTAG (может использоваться для отладки), а порт D используется для соединения UART.
Проект состоит из следующих файлов:
-
stdiodemo.c
Это основной файл примера. -
defines.h
Содержит некоторые глобальные определения, например проводку ЖК-дисплея -
hd44780.c
Реализация драйвера ЖК-дисплея HD44780 -
hd44780.h
Объявления интерфейса для драйвера HD44780 -
lcd.
Реализация символьного ввода-вывода LCD поверх драйвера HD44780c
-
lcd.h
Объявления интерфейса для драйвера LCD -
uart.c
Реализация символьного драйвера ввода/вывода для внутреннего UART -
uart.h
Объявления интерфейса для драйвера UART
stdiodemo.c
Как обычно, включаемые файлы идут первыми. В то время как обычно файлы заголовков системы (в угловых скобках <
... >
) идут перед файлами заголовков приложения (в двойных кавычках), defines.h
идет здесь как первый файл заголовка. Основная причина в том, что этот файл определяет значение F_CPU
, которое необходимо знать перед включением
.
Функция ioinit()
суммирует все задачи по инициализации оборудования. Поскольку эта функция объявлена только внутренней для модуля ( static
), компилятор заметит ее простоту и при разумном уровне оптимизации встроит эту функцию. Это необходимо иметь в виду при отладке, потому что встраивание может привести к тому, что отладчик будет "дико прыгать" на первый взгляд при пошаговом выполнении.
Определения uart_str
и lcd_str
устанавливают два потока stdio. Инициализация выполняется с помощью FDEV_SETUP_STREAM()
Макрос шаблона инициализатора, поэтому можно создать статический объект, который можно использовать для целей ввода-вывода. Этот макрос инициализатора принимает три аргумента, два макроса функций для подключения соответствующих функций вывода и ввода соответственно, третий описывает намерение потока (чтение, запись или оба). Те функции, которые не требуются для указанного намерения (например, функция ввода для lcd_str
, которая указана только для выполнения операций вывода), могут быть заданы как НОЛЬ
.
Поток uart_str
соответствует операциям ввода и вывода, выполняемым через соединение RS-232 с терминалом (например, с/на ПК, на котором запущена терминальная программа), а поток lcd_str
обеспечивает метод отображения символьных данных на жидкокристаллический текстовый дисплей.
Функция delay_1s()
приостанавливает выполнение программы примерно на одну секунду. Это делается с помощью функции _delay_ms()
из
, который, в свою очередь, нуждается в макросе F_CPU
для настройки количества циклов. Поскольку функция _delay_ms()
имеет ограниченный диапазон допустимых значений аргументов (в зависимости от F_CPU
), в качестве базовой задержки было выбрано значение 10 мс, которое было бы безопасным для частот ЦП примерно до 26 МГц. Затем эта функция вызывается 100 раз, чтобы учесть фактическую задержку в одну секунду.
В практическом приложении длительные задержки, подобные этой, лучше обрабатывать с помощью аппаратного таймера, чтобы основной ЦП был свободен для других задач во время ожидания или мог быть переведен в спящий режим.
В начале main()
, после инициализации периферийных устройств, потоки stdio по умолчанию stdin
, stdout
и stderr
настраиваются с использованием существующих статических объектов потока FILE
. Хотя это не является обязательным, наличие
stdin
и stdout
позволяет использовать сокращенные функции (например, printf()
вместо fprintf()
), а stderr
можно мнемонически ссылаться при отправке. диагностические сообщения.
В демонстрационных целях stdin
и stdout
подключены к потоку, который будет выполнять ввод-вывод через UART, а stderr
предназначен для вывода данных на текстовый ЖК-дисплей.
Наконец, следует основной цикл, который принимает простые «команды», вводимые через соединение RS-232, и выполняет несколько простых действий на основе этих команд.
Сначала отправляется приглашение с использованием printf_P()
(которая принимает строку пространства программы). Строка считывается во внутренний буфер как одна строка ввода, используя fgets()
. Хотя также можно было бы использовать gets()
(который неявно считывается из stdin
), gets()
не контролирует, чтобы пользовательский ввод не переполнял предоставленный входной буфер, поэтому его вообще никогда не следует использовать. .
Если fgets()
ничего не читает, основной цикл остается. Конечно, обычно предполагается, что основной цикл приложения микроконтроллера никогда не завершится, но опять же, в демонстрационных целях, это объясняет обработку ошибок stdio. fgets()
вернет NULL в случае ошибки ввода или условия конца файла при вводе. Оба эти условия находятся в области действия функции, которая используется для установления потока, в данном случае uart_putchar()
. Короче говоря, эта функция возвращает EOF в случае, если в последовательной линии было распознано условие «разрыва» последовательной линии (условие расширенного запуска). Обычные терминальные программы ПК позволяют утверждать это состояние как своего рода внеполосную сигнализацию на соединении RS-232.
При выходе из основного цикла на стандартный вывод ошибок (т. е. на ЖК-дисплей) отправляется сообщение «прощай», за которым следуют три точки с интервалом в одну секунду, после чего следует последовательность, которая очистит ЖК-дисплей. Наконец,
main()
будет завершена, и библиотека добавит бесконечный цикл, так что только сброс ЦП сможет перезапустить приложение.
Распознаются три «команды», каждая из которых определяется первой буквой введенной строки (преобразованной в нижний регистр):
- Команда 'q' (выход) имеет тот же эффект, что и выход из основного цикла.
- Команда 'l' (LCD) принимает второй аргумент и отправляет его на LCD.
- Команда 'u' (UART) принимает второй аргумент и отправляет его обратно в соединение UART.
Распознавание команд выполняется с помощью sscanf()
, где первый формат в строке формата просто пропускает саму команду (поскольку задан модификатор подавления присваивания *
).
defines.h
Этот файл содержит только несколько определений периферийных устройств.
Макрос F_CPU
определяет тактовую частоту ЦП, которая будет использоваться в петлях задержки, а также при расчете скорости передачи UART.
Макрос UART_BAUD
определяет скорость передачи RS-232. В зависимости от фактической частоты процессора может поддерживаться только ограниченный диапазон скоростей передачи данных.
Остальные макросы настраивают порт ввода-вывода и контакты, используемые для драйвера ЖК-дисплея HD44780. Каждое определение состоит из буквы, обозначающей порт, к которому подключен этот контакт, и соответствующего битового номера. Для доступа к строкам данных только первая строка данных получает собственный макрос (строка D4 на HD44780, строки с D0 по D3 не используются в 4-битном режиме), все остальные строки данных должны располагаться в порядке возрастания рядом с D4. .
hd44780.h
В этом файле описывается общедоступный интерфейс низкоуровневого драйвера ЖК-дисплея, который взаимодействует с контроллером ЖК-дисплея HD44780. Доступны общедоступные функции для инициализации контроллера в 4-битном режиме, для ожидания сброса бита занятости контроллера и для чтения или записи одного байта из или в контроллер.
Поскольку существует две разные формы ввода-вывода контроллера: одна для отправки команды или получения состояния контроллера (сброс сигнала RS), а другая для отправки или получения данных в/из SRAM контроллера (подтверждение RS), предусмотрены макросы, которые строить на упомянутых функциональных примитивах.
Наконец, для всех команд контроллера предусмотрены макросы, позволяющие использовать их символически. Техническое описание HD44780 объясняет эти основные функции контроллера более подробно.
hd44780.c
Это реализация низкоуровневого драйвера контроллера LCD HD44780.
Кроме того, несколько приемов склеивания препроцессора используются для установления символического доступа к контактам аппаратного порта, к которым подключен контроллер ЖК-дисплея, на основе определений приложения, сделанных в defines.h.
Функция hd44780_pulse_e()
подает короткий импульс на контакт E (включение) контроллера. Поскольку обратное считывание данных, заявленных контроллером ЖК-дисплея, необходимо выполнять, пока E активен, эта функция считывает и возвращает входные данные, если параметр readback
имеет значение true. При вызове с постоянным параметром времени компиляции, который имеет значение false, компилятор полностью исключит неиспользуемую операцию обратного чтения, а также возвращаемое значение в рамках своей оптимизации.
Поскольку контроллер используется в режиме 4-битного интерфейса, все байтовые операции ввода-вывода в/из контроллера должны обрабатываться как операции ввода-вывода двух полубайтов. Функции hd44780_outnibble()
и hd44780_innibble()
реализуют это. Они не принадлежат общедоступному интерфейсу, поэтому объявляются статическими.
Опираясь на это, общедоступные функции hd44780_outbyte()
и hd44780_inbyte()
передают один байт в/из контроллера.
Функция hd44780_wait_ready()
ожидает готовности контроллера, постоянно опрашивая состояние контроллера (которое считывается путем чтения байта со сброшенным сигналом RS) и проверяя флаг BUSY в байте состояния. Эту функцию необходимо вызывать перед выполнением любого ввода-вывода контроллера.
Наконец, hd44780_init()
инициализирует контроллер ЖК-дисплея в 4-битном режиме на основе последовательности инициализации, указанной в техническом описании. Поскольку на данный момент флаг BUSY еще не может быть проверен, это единственная часть этого кода, где используются временные задержки. Хотя контроллер может выполнять сброс при включении питания, когда соблюдаются определенные ограничения на время нарастания напряжения питания, всегда вызывая процедуру инициализации программного обеспечения при запуске, гарантируется, что контроллер будет в известном состоянии. Эта функция также переводит интерфейс в 4-битный режим (который не будет выполняться автоматически после сброса при включении питания).
lcd.h
Эта функция объявляет общедоступный интерфейс высокоуровневого (символьного ввода-вывода) драйвера LCD.
lcd.c
Реализация высокоуровневого драйвера LCD. Этот драйвер основан на низкоуровневом драйвере контроллера ЖК-дисплея HD44780 и предлагает символьный интерфейс ввода-вывода, подходящий для прямого использования стандартными средствами ввода-вывода. В то время как низкоуровневый драйвер HD44780 имеет дело с настройкой адресов SRAM контроллера, записью данных в SRAM контроллера и управлением функциями дисплея, такими как очистка дисплея или перемещение курсора, этот высокоуровневый драйвер позволяет просто записывать символ на ЖК-дисплей. , в предположении, что это каким-то образом отобразится на дисплее.
Управляющие символы могут обрабатываться на этом уровне и использоваться для выполнения определенных действий на ЖК-дисплее. В настоящее время обрабатывается только один управляющий символ: символ новой строки ( \n
) воспринимается как указание на очистку дисплея и установку курсора в исходное положение при приеме следующего символа, поэтому " новая строка» текста может отображаться. Таким образом, полученный символ новой строки запоминается до тех пор, пока приложение не отправит больше символов, и только после этого перед продолжением работы экран очищается. Это обеспечивает удобную абстракцию, при которой полные строки текста могут быть отправлены драйверу, и они останутся видимыми на ЖК-дисплее до тех пор, пока не будет отображена следующая строка.
Могут быть реализованы дополнительные управляющие символы, например. грамм. используя набор escape-последовательностей. Таким образом можно было бы реализовать самопрокручивающиеся строки дисплея и т.д. хотелось бы (экран очищен, немигающий курсор включен, адреса SRAM увеличиваются, поэтому символы будут записываться слева направо).
Общественная функция lcd_putchar()
принимает аргументы, которые делают его пригодным для передачи в качестве указателя функции put()
на функции инициализации потока stdio и макросы ( fdevopen()
, FDEV_SETUP_STREAM()
и т. д.). Таким образом, он принимает два аргумента: отображаемый символ и ссылку на базовый объект потока, и ожидается, что он вернет 0 в случае успеха.
Эта функция запоминает последний необработанный символ новой строки, обнаруженный в локальной статической переменной функции nl_seen
. Если встречается символ новой строки, он просто установит для этой переменной истинное значение и вернется к вызывающей стороне. Как только первый символ, не являющийся новой строкой, должен отображаться с
nl_seen
, все еще истинным, контроллер ЖК-дисплея получает команду очистить дисплей, поместить курсор в исходное положение и перезапустить по адресу SRAM 0. Все остальные символы отправляются на дисплей. .
Для этой цели работает единственная статическая функция-внутренняя переменная nl_seen
. Если несколько ЖК-дисплеев должны управляться с использованием одного и того же набора функций драйвера, это больше не будет работать, поскольку необходим способ различать различные дисплеи. Здесь можно использовать второй параметр, ссылку на сам поток: вместо того, чтобы хранить состояние внутри приватной переменной функции, его можно хранить внутри приватного объекта, присоединенного к самому потоку. Ссылка на этот частный объект может быть прикреплена к потоку (например, внутри функции lcd_init()
, которому затем также необходимо передать ссылку на поток) с помощью fdev_set_udata()
, и к нему можно получить доступ внутри lcd_putchar()
с помощью fdev_get_udata().
uart.h
Определение общедоступного интерфейса для драйвера RS-232 UART, очень похожее на lcd.h, за исключением того, что теперь также доступна функция ввода символов.
Поскольку в этом примере вход RS-232 буферизуется строкой, макрос RX_BUFSIZE
определяет размер этого буфера.
uart.c
Реализует stdio-совместимый драйвер RS-232, использующий стандартный UART AVR (или USART в асинхронном режиме работы). Реализованы как операции вывода символов, так и операции ввода символов. Вывод символов заботится о преобразовании внутренней новой строки \n
во внешнее представление возврата каретки/перевода строки ( \r\n
).
Ввод символов организован как операция с буферизацией строки, что позволяет минимально редактировать текущую строку до тех пор, пока она не будет «отправлена» в приложение при возврате каретки ( 9r (control-R, ASCII DC2) отправляет \r
, затем повторно печатает буфер (обновляет)
\t
(табулятор) заменяется одинарным пробелом Функция uart_init()
выполняет всю аппаратную инициализацию, необходимую для перевода UART в режим с 8 битами данных, без четности, с одним стоповым битом (обычно называемый 8N1) при скорости передачи данных, настроенной в определениях. .час. На низких тактовых частотах ЦП устанавливается бит
U2X
в UART, уменьшая передискретизацию с 16x до 8x, что позволяет использовать 9Скорость 600 бод должна быть достигнута с допустимой ошибкой при использовании RC-генератора с частотой 1 МГц по умолчанию.
Общедоступная функция uart_putchar()
снова имеет подходящие аргументы для прямого использования интерфейсом потока stdio. Он выполняет преобразование \n
в \r\n
, рекурсивно вызывая себя, когда видит символ \n
. Только для демонстрационных целей символ \a
(звуковой звонок, ASCII BEL) реализован путем отправки строки на stderr
, поэтому он будет отображаться на ЖК-дисплее.
Открытая функция uart_getchar()
реализует редактор строк. Если в линейном буфере есть доступные символы (переменная rxp
не равна NULL
), следующий символ будет возвращен из буфера без какого-либо взаимодействия с UART.
Если внутри строкового буфера нет символов, будет введен цикл ввода. Символы будут считываться с UART и обрабатываться соответствующим образом. Если UART сигнализировал об ошибке кадрирования ( FE
бит), обычно вызываемый терминалом, отправляющим условие разрыва строки (начальное условие удерживается намного дольше, чем один период символа), функция вернет условие конца файла, используя _FDEV_EOF
. Если на входе было состояние переполнения данных (установлен бит DOR
), состояние ошибки будет возвращено как _FDEV_ERR
.
Символы редактирования строки обрабатываются внутри цикла, потенциально изменяя состояние буфера. При попытке ввести символы, выходящие за пределы буфера строк, их прием отклоняется и выдается \ на терминал отправляется символ
. Если встречается символ \r
или \n
, переменная rxp
(указатель приема) устанавливается в начало буфера, цикл прекращается, и первый символ буфера будет возвращен в заявление. (Если никакие другие символы не были введены, это будет просто символ новой строки, и буфер немедленно снова помечается как исчерпанный.)
- stdiodemo.c
- определяет.h
- hd44780.h
- hd44780.c
- ЖК.ч
- жк.с
- uart.h
- uart.c
как инициализировать ПОРТ A 18F4520 в качестве входа
Добро пожаловать на EDAboard.com
Добро пожаловать на наш сайт! EDAboard.com — это международный дискуссионный форум по электронике, посвященный программному обеспечению EDA, схемам, схемам, книгам, теории, документам, asic, pld, 8051, DSP, сети, радиочастотам, аналоговому дизайну, печатным платам, руководствам по обслуживанию... и многому другому. более! Для участия необходимо зарегистрироваться. Регистрация бесплатна. Нажмите здесь для регистрации.
Регистрация Авторизоваться
JavaScript отключен. Для лучшего опыта, пожалуйста, включите JavaScript в вашем браузере, прежде чем продолжить.
- Автор темы самз казы
- Дата начала
- Статус
- Закрыто для дальнейших ответов.
самз казы
Уровень новичка 6
привет... я использую PIC 18f4520 в своем проекте... я пишу код на языке C, используя программное обеспечение MikroC... я должен дать выход компаратора (0v или 5v) в качестве входа для PIC 18f4520... когда я запускаю компаратор отдельно на симуляторе, он работает ... но когда я подключаю его к PIC как вход к порту А, он не работает ... кто-нибудь поможет мне в этом? мне нужен код для инициализации PORT A PIC в качестве входных данных от компаратора ... plzzz помогите plzzz.
хемнат
Расширенный член уровня 3
вы должны назначить определенный контакт в качестве входа.
по определению,
TRISA = 0b00001000; // 0 - вывод, 1 - ввод. здесь RA3 как вход и остальные как выход.
С наилучшими пожеланиями
самз казы
Уровень новичка 6
я пробовал это, мой друг... но это не работает... например, когда я подключаю переключатель к любому контакту порта А, то согласно программе светодиод o/p должен включаться, когда переключатель замкнут.. это этого не происходит, когда я использую порт A.... но та же функция работает, когда я использую порт B в качестве входного порта... мне нужно больше контактов порта, поэтому я ищу PORTA, иначе я мог бы использовать порт B в качестве входа. .. так что плиззз плиззз помогите.
пасикр
Продвинутый член уровня 1
Привет, Кази,
PORTA — это двунаправленный порт, может быть цифровым или аналоговым,
Регистр направления данных — это TRISA, если вы установите «1» в регистре TRISA, контакт будет входным, если вы установите «0», контакт будет выходным (Hemnath объясните это в предыдущем посте)
Регистр ADCON1 настраивает функцию контактов на PORTA, чтобы они были цифровыми или аналоговыми,
, если вам нужен цифровой ввод, вы должны установить биты PCFG<3:0> в регистре ADCON1, например ADCON1 = 0b00001111 для использования контактов PORTA как цифровой,
вы должны прочитать руководство 18F4520, подробно описанное в модуле преобразователя AD,
с наилучшими пожеланиями
хемнат
Расширенный член уровня 3
выложи свой код и схему.
эйфан
Участник уровня 3
Я предполагаю, что выход компаратора цифровой. В этом случае вы должны объявить связанный контакт PORTA цифровым контактом.
самз казы
Уровень новичка 6
спасибо, друзья... ADCON1 = 0b00001111 оператор отсутствует... так что теперь все в порядке... спасибо...
- - - Обновлено - - -
, ребята...пожалуйста, помогите..следующая программа не работает ..если вы, ребята, нашли какую-то ошибку..дайте мне знать, пожалуйста..мы сделали этот код самостоятельно..так что есть возможность получить больше ошибок...пожалуйста, помогите.
в соответствии с программой, если какой-либо один датчик сухой, или пара датчиков или 3 или 4 датчика сухие, двигатель должен быть включен, а электромагнитный клапан, соответствующий этому датчику, должен быть включен.. например, если датчик 1 и 2 сухой , тогда соленоиды 1 и 2 должны включиться, а все остальные должны быть выключены. .... так что, пожалуйста, проверьте и дайте мне знать, если есть какая-либо ошибка...
для этого я использую программное обеспечение MikroC и пытаюсь запустить его на симуляторе Proteus.....спасибо
/* PIC 18F4520
Внутренний осциллятор инициализирован
Статус соленоида > 0 = ВКЛ
Все светодиоды Общий анод
Датчик сухой > Выход 0 Sensor Wet > Output 1
TRIS Initialization Important
*/
#define s0 PORTA.F0 /* Датчик уровня */
#define s1 PORTA.F1 /* Датчик 1 */
#define s2 PORTA.F2 /* Датчик 2 */
#define s3 PORTA.F3 /* Датчик 3 */
#define s4 PORTA.F4 /* Датчик 4 */
#define motor LATB.F0 /* Двигатель */
#define motor_s LATB.F1 /* Состояние двигателя * /
#define sol_1 LATC.F1 /* Соленоид 1 */
#define sol_2 LATC.F2 /* Соленоид 2 */
#define sol_3 LATC.F3 /* Соленоид 3 */
#define sol_4 LATC.F4 /* Соленоид 4 */
пустота main(){
ТРИСА = 0xff; /* Ввод PORTA */
TRISB = 0x00; /* Выход PORTB */
TRISC = 0x00; /* Выход PORTC */
PORTA = 0x00;
ПОРТБ = 0x00;
ПОРТС = 0x00;
CMCON = 0x07; /* Отключить компаратор */
ADCON0 = 0b00000000; /* Настройте все порты ввода-вывода как цифровые */
ADCON1 = 0b00001111; /* Настройте все порты ввода-вывода как цифровые */
while (1) /* Непрерывный цикл */
{
if (s0==1) /* Достаточно воды в баке */
{
if ((s1||s2||s3||s4)==0) /* Датчик 1, Датчик 2, Датчик 3 или Датчик 4 сухой */
{
motor = 1; /* Двигатель включен */
motor_s = 0; /* Состояние двигателя включено */
}
else if (s1==0) /* Датчик 1 сухой */
{
sol_1=0; /* Соленоид 1 включен */
sol_2=1; /* Соленоид 2 ВЫКЛ */
sol_3=1; /* Соленоид 3 ВЫКЛ */
sol_4=1; /* Соленоид 4 ВЫКЛ. */
}
else if (s2==0) /* Датчик 2 сухой */
{
sol_1=1; /* Соленоид 1 ВЫКЛ */
sol_2=0; /* Соленоид 2 включен */
sol_3=1; /* Соленоид 3 ВЫКЛ */
sol_4=1; /* Соленоид 4 ВЫКЛ */
}
else if (s3==0) /* Датчик 3 сухой */
{
sol_1=1; /* Соленоид 1 ВЫКЛ */
sol_2=1; /* Соленоид 2 ВЫКЛ */
sol_3=0; /* Соленоид 3 включен */
раствор_4=1; /* Соленоид 4 ВЫКЛ */
}
else if (s4==0) /* Датчик 4 сухой */
{
sol_1=1; /* Соленоид 1 ВЫКЛ */
sol_2=1; /* Соленоид 2 ВЫКЛ */
sol_3=1; /* Соленоид 3 ВЫКЛ */
sol_4=0; /* Соленоид 4 включен */
}
else if ((s1&&s2)==0) /* Датчик 1 и датчик 2 сухие */
{
sol_1=0; /* Соленоид 1 включен */
sol_2=0; /* Соленоид 2 включен */
раствор_3=1; /* Соленоид 3 ВЫКЛ */
sol_4=1; /* Соленоид 4 выключен */
}
else if ((s2&&s3)==0) /* Датчик 2 и датчик 3 сухие */
{
sol_1=1; /* Соленоид 1 ВЫКЛ */
sol_2=0; /* Соленоид 2 включен */
sol_3=0; /* Соленоид 3 включен */
sol_4=1; /* Соленоид 4 выключен */
}
else if ((s3&&s4)==0) /* Датчик 3 и датчик 4 сухие */
{
соль_1=1; /* Соленоид 1 ВЫКЛ */
sol_2=1; /* Соленоид 2 ВЫКЛ */
sol_3=0; /* Соленоид 3 включен */
sol_4=0; /* Соленоид 4 включен */
}
else if ((s1&&s3)==0) /* Датчик 1 и датчик 3 сухие */
{
sol_1=0; /* Соленоид 1 включен */
sol_2=1; /* Соленоид 2 ВЫКЛ */
sol_3=0; /* Соленоид 3 включен */
sol_4=1; /* Соленоид 4 ВЫКЛ. */
}
else if ((s1&&s4)==0) /* Датчик 1 и датчик 4 сухие */
{
sol_1=0; /* Соленоид 1 включен */
sol_2=1; /* Соленоид 2 ВЫКЛ */
sol_3=1; /* Соленоид 3 ВЫКЛ */
sol_4=0; /* Соленоид 4 включен */
}
else if ((s2&&s4)==0) /* Датчик 2 и датчик 4 сухие */
{
sol_1=1; /* Соленоид 1 ВЫКЛ */
sol_2=0; /* Соленоид 2 включен */
раствор_3=1; /* Соленоид 3 ВЫКЛ */
sol_4=0; /* Соленоид 4 включен */
}
else if ((s1&&s2&&s3)==0) /* Датчик 1, Датчик 2 и Датчик 3 сухой */
{
sol_1=0; /* Соленоид 1 включен */
sol_2=0; /* Соленоид 2 включен */
sol_3=0; /* Соленоид 3 включен */
sol_4=1; /* Соленоид 4 ВЫКЛ */
}
else if ((s2&&s3&&s4)==0) /* Датчик 2, Датчик 3 и Датчик 4 сухой */
{
соль_1=1; /* Соленоид 1 ВЫКЛ */
sol_2=0; /* Соленоид 2 включен */
sol_3=0; /* Соленоид 3 включен */
sol_4=0; /* Соленоид 4 включен */
}
else if ((s1&&s3&&s4)==0) /* Датчик 1, Датчик 3 и Датчик 4 сухой */
{
sol_1=0; /* Соленоид 1 включен */
sol_2=1; /* Соленоид 2 ВЫКЛ */
sol_3=0; /* Соленоид 3 включен */
sol_4=0; /* Соленоид 4 включен */
}
else if ((s1&&s2&&s3&&s4)==0) /* Датчик 1, Датчик 2, Датчик 3 и Датчик 4 сухой */
{
sol_1=0; /* Соленоид 1 включен */
sol_2=0; /* Соленоид 2 включен */
sol_3=0; /* Соленоид 3 включен */
sol_4=0; /* Соленоид 4 включен */
}
else if ((s1&&s2&&s3&&s4)==1) /* Датчик 1, Датчик 2, Датчик 3 и Датчик 4 влажные */
{
sol_1=1; /* Соленоид 1 включен */
раствор_2=1; /* Соленоид 2 включен */
sol_3=1; /* Соленоид 3 включен */
sol_4=1; /* Соленоид 4 включен */
мотор=0; /* Двигатель выключен */
motor_s=1; /* Состояние мотора ВЫКЛ */
}
else
{
мотор=0; /* Двигатель выключен */
motor_s=1; /* Состояние мотора ВЫКЛ */
}
}
else
{
мотор=0; /* Двигатель выключен */
motor_s=1; /* Состояние двигателя ВЫКЛ. */
}
}
}
нихилсигма
Полноправный член уровня 2
При использовании любого микроконтроллера всегда проверяйте, какие периферийные устройства подключены к порту/выводу, который вы используете, и отключайте все остальное.....
Я не знаю, почему ребята из Microchip сохранили некоторые периферийные устройства (например, ADC/LVP и т. д.) Включено по умолчанию... так что вам нужно отключить их перед использованием...
Это не проблема с atmel, так как они отключили все по умолчанию.... и вы включаете все, что хотите использовать.. ...
скоростьEC
Полноправный член уровня 6
Сначала используйте только один датчик и заставьте его работать правильно. Если это работает, вы можете без проблем расширить столько датчиков, сколько вам нужно. Таким образом, код может быть коротким и понятным. Вы и все можете легко определить ошибку и заставить программу работать.
Выход датчика цифровой или аналоговый?
Редактировать: Использовать
Код:
тег, чтобы увидеть код очистить
Последнее редактирование:
самз казы
Уровень новичка 6
это цифровой....выход компаратора (0В или 5В) подается на pic18f4520 как вход
эйфан
Участник уровня 3
Почему бы вам тогда не использовать порт D. Он чисто цифровой. Так проще.
самз казы
Уровень новичка 6
мне нужно использовать только этот порт. .. потому что у меня есть еще несколько подключений к другому порту, например модуль gsm, датчик уровня воды и так далее и функционировать как или функция», например. (s1&&s2) работает как (s1||s2) и наоборот
эйфан
Участник уровня 3
И и ИЛИ конечно разные. Но они имеют одинаковый выход, когда оба входа равны нулю.
самз казы
Уровень новичка 6
Эйфан сказал:
И и ИЛИ конечно разные. Но они имеют одинаковый выход, когда оба входа равны нулю.
Нажмите, чтобы развернуть...
тогда как насчет (s1&&s2&&s3).... он работает как s1||s2||s3)
джаянтх.деварайанадурга
Запрещено
& отличается от && и || отличается от || оператор.
эйфан
Участник уровня 3
я имею в виду (0&&0&&0 => 0) , (0||0||0 => 0)...
самз казы
Уровень новичка 6
Эйфан сказал:
я имею в виду (0&&0&&0 => 0) , (0||0||0 => 0)...
Нажмите, чтобы развернуть...
ok..тогда что я должен написать, если я хочу проверить, являются ли 3 датчика равными 0??? (s1&&s2&&s3==0) это правильно?
если нет.. то скажите плз что мне писать?
джаянтх.деварайанадурга
Запрещено
Попробуйте, если((s1 == 0) && (s2 == 0) && (s3 == 0))
самз казы
Уровень новичка 6
спасибо. ..спасибо...спасибо...спасибо...спасибо...спасибо...большое.....работаетggggggggggggg
- - - Обновлено - - -
мне нужен код для отправки сообщения через модуль GSM.... вы можете мне помочь?
i m с использованием модуля GSM SIM 300, микросхемы PIC18F4520 и написания кода на языке C с использованием MiKroC....условие состоит в том, что если датчик s0=1, то модуль GSM должен активироваться и отправить сообщение "Уровень воды недостаточен" на любой конкретный номер 1234567800...
скоростьEC
Полноправный член уровня 6
Кодирование для приема, чтения, разбора и действия на основе SMS-команд немного сложнее. Если вы используете ЖК-модуль и клавиатуру, то все в порядке. Вы можете напрямую хранить необходимые номера телефонов. Но если вы не используете клавиатуру, вы должны кодировать последовательность, которую я упомянул. Дайте мне знать, используете ли вы ЖК-дисплей и клавиатуру. ПРИМЕЧАНИЕ. Тем не менее, вам все равно нужно кодировать в соответствии с моей последовательностью, чтобы запускать / выключать двигатели на основе SMS-команды (НЕ из мелодии звонка, это довольно просто по сравнению с SMS-командой).
- Статус
- Закрыто для дальнейших ответов.
С
Nodemcu как программатор SPI для USBASP AVR Обновление прошивки
- Автор: sungavali02
- Ответов: 1
Микроконтроллеры
М
Порт USB невозможно открыть в терминале Tera или в реальном времени
- Запущено MD18
- Ответов: 2
Микроконтроллеры
STM32 в качестве хоста и устройство на базе Ch440 в качестве USB-устройства
- Инициировано Mithun_K_Das
- Ответов: 0
Микроконтроллеры
п
Как добиться преобразования с одним АЦП в ATtiny
- Автор Prince Charming
- Ответов: 7
Микроконтроллеры
п
[РЕШЕНО] Как настроить PIC16F в качестве ведомого устройства I2C
- Автор ProminentRD
- Ответов: 3
Микроконтроллеры
Делиться:
Фейсбук Твиттер Реддит Пинтерест Тамблер WhatsApp Эл. адрес Делиться Ссылка на сайт
Верх
Режимы адресаAVR для доступа к регистру портов Режимы адреса
AVR для доступа к регистру порта Pfad: Главная => AVR-RU => Введение для начинающих => Доступ к реестру портов ( Diese Seite на немецком языке: )- доступ к ячейкам SRAM,
- доступ к адресам регистра портов,
- Доступ к регистрам портов,
- Доступ к классическим регистрам порта,
- Доступ к расширенным регистрам портов,
- Доступ к регистрам портов с помощью указателей (круглый светодиод),
- доступ к локациям EEPROM и
- доступ к ячейкам флэш-памяти.
- Физические адреса и
- Адреса указателей.
Этот тип доступа использует две инструкции OUT, порт OUT, регистр . для записи и IN зарегистрируйтесь, введите для чтения содержимого этих места. Используемый здесь тип адреса — это физические адреса.
Доступ с OUT и IN ограничен 64 классическими регистрами порта. Расширенные регистры порта недоступны с этими двумя инструкциями. (см. ниже, как получить доступ к этим работам).
Если вам нужно установить или сбросить только один из битов в расположении порта, вы можно использовать Порт SBI, бит для настройки или Порт CBI, бит для очистка бита. Эти инструкции заменяют следующие инструкции:
в R16, порт; Прочитать порт ; Параметр sbr R16,1<<бит ; Установка бита или: или R16,1<<бит ; альтернатива установки бита в регистре ; Клиринг cbr R16,1<<бит ; Очистка бита или: и R16,256-1<<бит ; альтернатива очистке бита в регистре выходной порт,R16 ; Пишите в портХотя этот (более длинный) метод можно использовать для всех 64 регистров портов, ВОО а CBI работает только для нижней половины регистров портов.

SBI и CBI требуют двух тактов, в то время как альтернативный одношаговый метод требуется три и один дополнительный регистр.
Переключение одного бита в порту может быть выполнено с помощью EXOR порта с помощью регистр, в котором установлены биты, которые должны быть переключены:
в R16, порт; Прочитать порт ldi R17,1<<бит ; Бит порта, который нужно переключить, или ldi R17,(1<Если порт для переключения является портом ввода-вывода, вы можете, в большинстве современных устройств AVR, в качестве альтернативы запишите во входной порт ввода-вывода, чтобы переключить один или несколько битов. Это переключает биты 1 и 3 порта ввода/вывода PORTA: ldi R16,(1<<1)|(1<<3) выход ПИНА,R16Если OUT на регистр порта заканчивается сообщением об ошибке ассемблера что порт вне диапазона, этот порт находится в расширенном реестре портов диапазон за пределами физического адреса 0x3F.Это имеет место в более крупных ATtiny или Устройства ATmega, где 64 регистра портов не предоставили достаточно адреса пространство.
В этом случае вам придется использовать адрес указателя и либо инструкция STS или ST для записи данных в этот порт. В этом случае адрес, указанный в def.inc, уже добавил 0x20 и является указателем адрес, так что вы можете просто заменить OUT на STS (или IN на LDS) в это место.
Конечно, инструкции SBI/CBI и SBIC/SBIS нельзя использовать для эти расширенные регистры портов. Вот почему портовые регистры, которые требуют изменяться с меньшей вероятностью, помещаются в расширенный зона портового реестра.
Теперь предположим, что вам нужен 32-битный световой ряд, в котором один из 32 светодиодов указывая на следующий аварийный выход. Поскольку весь цикл должен быть одним секунды, частота 32 Гц увеличивает светодиод и каждый светодиод должен быть включен в течение 21,25 мс.Для этого требуется контроллер с четырьмя полными 8-битными портами ввода/вывода.
Выдержка в окне выбора устройства avr_sim показаны все эти устройства AVR. Мы можем использовать один из таких, например ATmega324PA.
Это необходимое оборудование. Выглядит довольно просто, много резисторов и светодиодов. Если мы уверены, что программа будет переключать только один светодиод из всех 32 одновременно время, мы можем уменьшить количество резисторов до одного и подключить все катоды с этим единственным резистором. Если количество включенных светодиодов равно одному в каждом 8-битный порт, мы можем уменьшить количество резисторов до четырех, подключив все светодиодные катоды в одном 8-битном порту к одному резистору.
Первый шаг в программном обеспечении, чтобы вывести все биты направления ввода/вывода и установить бит 0 PORTA на единицу, а все остальные на ноль, можно сделать с помощью или без указателей. Версия без указателя будет:
лди R16,0xFF ; Все биты на выходе из DDRA, R16 из DDRB, R16 из DDRC, R16 из DDRD, R16 клр R16 ; Все старшие 24 бита очищены из PORTD, R16 выход PORTC,R16 выход PORTB,R16 лди R16,0x01 ; Набор младших битов из порта,R16Это регистровые порты, управляющие портами ввода-вывода в ATmega324PA.Все адреса образуют ряд из трех регистровых портов: PIN, DDR и PORT. Физический адрес, а также адрес указателя увеличиваются на единицу. Так, с базовым адресом указателя 0x20 в Y или Z, эти порты регистрации можно получить со смещениями 0, 3, 6 и 9для PIN-кода Порт ввода/вывода, с 1, 4 и 7 порты ввода/вывода DDR смещены и с 2, 5 и 8 порты PORT I/O смещены. Если вам интересно, что подразумевается под термином "смещение", см. главу о смещение в SRAM.
Версия с указателем использовала бы это смещение и выглядела бы как это:
лди R16,0xFF ; Все биты на выходе ldi YH, высокий (PINA+0x20) ; Укажите Y на адрес указателя PINA ldi YL, низкий (PINA+0x20) стандарт Y+1,R16 ; Доступ к регистрам порта DDR стандарт Y+4, R16 стандарт Y+7,R16 стандарт Y+10,R16 клр R16 ; Верхние 24 бита очищены стандарт Y+5,R16 ; Доступ к регистрам порта PORT стандарт Y+8,R16 стандарт Y+11,R16 лди R16,0x01 ; Самый младший бит установлен стандарт Y+2,R16Теперь это явно менее эффективно, потому что каждый доступ к STD потребляет два такта вместо одного для OUT.Поэтому мы бы предпочли классический метод OUT вместо метода указателя в этом случае.
А теперь давайте напишем процедуру обработки прерывания для сравнения прерывание таймера, который контролирует скорость нашего ряда светодиодов. Быстро и грязно это было бы так:
Tc0CmpIsr: ; 7 тактов для int+rjmp в R16,PORTA ; +1 = 8 лсл R16 ; +1 = 9 выход PORTA,R16 ; +1 = 10 в R16,PORTB ; +1 = 11 ролик R16 ; +1 = 12 выход PORTB,R16 ; +1 = 13 в R16,PORTC ; +1 = 14 ролик R16 ; +1 = 15 выход PORTC,R16 ; +1 = 16 в R16,PORTD ; +1 = 17 ролик R16 ; +1 = 18 выход PORTD,R16 ; +1 = 19brcc Tc0CmpIsrReti ; +1/2 = 20/21 в R16,PORTA ; +1 = 21 ролик R16 ; +1 = 22 выход PORTA,R16 ; +1 = 23 Tc0CmpIsrReti: ; 21/23 цикла рети; +4 = 25/27 циклов ; Всего циклов: 31 * 25 + 27 = 802 циклаДанные тактовые циклы относятся к каждому прерыванию. 31 раз прерывание не требует перезапуска, потребляя по 25 тактов один раз в перезапуск необходим и потребляет 27 циклов. Это в сумме составляет 802 цикла.в целом. Вместе с 32 выходами из сна и 64 циклами прыжков обратно в сон, мы примерно на 866 циклов в одну секунду. В Тактовая частота 1 МГц, что составляет долю времени сна, равную 9.9,13%.
Но: Три из четырех инструкций OUT лишние, т.к. активный светодиод не находится в этом порту ввода/вывода. Поэтому запись только одного порта с текущего активного светодиода будет достаточно.
В тех редких случаях, когда активный светодиод меняет порт ввода/вывода (каждую восьмичасовую смену), как в старом, так и в следующем порту быть написанным. Это подводит нас к другому алгоритму, на этот раз с указатели. Сначала инициализация:
ldi XH, высокий (PORTA+0x20) ; Адрес указателя на X, +1 = 1 ldi XL, низкий (PORTA+0x20) ; дто. Младший бит, +1 = 2 ldi rShift,0x01 ; Старт сдвигового регистра, +1 = 3 ст X,rShift ; Напишите это в PORTA, +2 = 5Поскольку эти пять циклов должны быть выполнены только один раз во время инициализации, мы их не считаем. И процедура обслуживания прерывания с движущимся указателем будет выглядеть так:OC0AIsr: ; 7 циклов для int+rjmp lsl rShift ; +1 = 8 ст X,rShift ; +2 = 9 brcc Tc0CmpIsrReti ; +1/2 = 10/11 ; Следующий порт ввода/вывода ади XL,3 ; Укажите на следующий канал, +2 = 12 cpi XL, низкий (PORTD+3+0x20) ; +1 = 13 brne Tc0CmpIsr1 ; +1/2 = 14/15 ldi XH, высокий (PORTA+0x20) ; +1 = 16 ldi XL, низкий (PORTA+0x20) ; +1 = 17 Tc0CmpIsr1: ; 15/17 циклов ; Перезапустить с самого начала ldi rShift,0x01 ; Установите бит 0, +1 = 16/18 ст X,rShift ; +2 = 18/20 Tc0CmpIsrReti: ; 18.Количество циклов чуть больше половины от классического метода без указателей. Это явный показатель того, что метод с подвижным указатель почти вдвое эффективнее просто потому, что он не тратит впустую время на ненужные входы и выходы. Это увеличивает долю сна до 99,43%, а также снижает потребление тока контроллером, поэтому чтобы аварийный источник питания работал дольше.11.20 рети; +4 = 15/22/24 ; Всего циклов: = 28*15 + 3*22 + 1*24 = 510
Теперь представьте, что мы подключаем катоды светодиода к контактам ввода/вывода, а резистор(ы) к плюсу. Есть только несколько изменений, которые необходимо внести в программное обеспечение: CLR rShift превращается в SER rShift , LDI rShift,0x01 превращается в LDI rShift,0xFE и brcc Tc0CmpIsrReti превращается в brcs Tc0CmpIsrReti . Эти изменения касаются инициирования портов ввода-вывода, а также службы прерывания рутина. Чуть сложнее то, что lsl rShift в ISR должен сместить один, например.
с сек и рол rShift вместо из lsl rShift . Теперь программа готова вытащить светодиоды. к GND вместо подачи тока на выходные контакты.
Программное обеспечение на ассемблере для этого можно загрузить с здесь. Позволяет увеличить или уменьшить время круга от 50 до 2000 миллисекунд, а также для выбора аноды и катоды, подключенные к контактам ввода-вывода (см. константы поверх исходного кода).
Что делать, если мы хотим включить более одного светодиода? Если разрешить четыре светодиода быть включенным на каждом этапе цикла светодиодов, программное обеспечение довольно простое: просто вывод регистра rShift на каждый из четырех портов ввода-вывода, без использование указателей. Если вы хотите, чтобы два светодиода горели одновременно (например, L1 и L16, L2 и L17 и т. д.), алгоритм требует указателей и немного сложнее, потому что перезапуск двух указателей назад к PORTA, происходит в двух разных фазах порта. Если восемь светодиодов должны быть на каждом этапе, рассмотрите возможность получения состояния сдвига из таблицы во флэш-памяти.