Инициализация портов avr: AVR. Учебный курс. Устройство и работа портов ввода-вывода

Содержание

AVR. Учебный курс. Устройство и работа портов ввода-вывода

С внешним миром микроконтроллер общается через порты ввода вывода. Схема порта ввода вывода указана в даташите:

Но новичку там разобраться довольно сложно. Поэтому я ее несколько упростил:

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

Если же ты по ошибке вкачаешь в ножку микроконтроллера вольт 6-7 при 5 вольтах питания, то никакой диод его не спасет.

Конденсатор, нарисованный пунктиром, это паразитная емкость вывода. Хоть она и крошечная, но присутствует. Обычно ее не учитывают, но она есть. Не забивай голову, просто знай это, как нибудь я тебе даже покажу как её можно применить 😉

Дальше идут ключи управления. Это я их нарисовал рубильниками, на самом деле там стоят полевые транзисторы, но особой сути это не меняет. А рубильники наглядней.
Каждый рубильник подчинен логическому условию которое я подписал на рисунке. Когда условие выполняется — ключ замыкается. PIN, PORT, DDR это регистры конфигурации порта.

Есть в каждом контроллере AVRPIC есть тоже подобные регистры, только звать их по другому).

Например, смотри в даташите на цоколевку микросхемы:

Видишь у каждой почти ножки есть обозначение 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 мы включаем порт на выход (DDRxy=1) и записываем в PORTxy единицу — при этом замыкается верхний ключ и на выводе появляется напряжение близкое к питанию. А если надо ноль, то в 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 7 | Первая программа

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

Цвет комментариев и других элементов программы можно изменять в настройках 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. c Реализация символьного ввода-вывода LCD поверх драйвера HD44780
  • 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 в вашем браузере, прежде чем продолжить.