Avr программирование работа с портами. Изучение системы команд микроконтроллера AVR

2.1. Порты и выводы.

Чтобы общаться с внешним миром у микроконтроллера есть порты ввода-вывода, в каждом из которых есть несколько отдельных битов (считай выводов), на которых можно установить ноль (0) или единицу (1).

У таких портов 3 это порты B,C и D. На каждом порту по 8 битов (за исключением порта C он 7 - разрядный ) которыми можно (нужно) управлять. Но управлять с некоторыми ограничениями.

Ограничения:

    D0 и D1 используются для прошивки микроконтроллерах на плате Arduino через USB;

    C6 – используется для перезагрузки (reset) ;

    B6 и B7 - на этих выводах микроконтроллера подключается внешний кварцевый резонатор .

Остальные биты можно использовать если они не задействованы. Для наших изысканий будем использовать:

    порт B – B0, B1, B2, B3, B4, B5 (соответственно выводы микроконтроллера с 1 4 по 1 9 );

    порт C – С0, С1, С2, С3, С4, С5 (выводы - с 23 по 28);

    порт D – D2, D3, D4, D5, D6, D7 (выводы - 4, 5, 6, 11, 12, 13).

Необходимо учитывать что производится в разных корпусах и нумерация выводов может отличатся

2.2. Регистры управления портами.

Управление портами достаточно простое. Используется три восьми битных регистра -

DDRx, PORTx и PINx, где x имя порта (в нашем случае B,C и D ).

    DDRx – Настройка разрядов порта x на вход или выход.

    PORTx – Управление состоянием выходов порта x (если соответствующий разряд настроен как выход), или подключением внутреннего подтягивающего резистора (резистор подтягивает разряд к 1 если соответствующий разряд настроен как вход).

    PINx –Чтение логических уровней разрядов порта x.

Настройка и работа портов сводится к трем операциям в зависимости от настройки входа или выхода.

Ввод:

    В регистре DDRx на нужный разряд устанавливаем 0 обозначая его как ввод;

    При необходимости на указанном разряде устанавливаем 1 для подключения подтягивающего резистора (резистор подтягивает указанный вывод к 1), подтягивающий резистор включают для уменьшения внешних помех и его можно не подключать;

    Считываем из регистра PINx с того-же разряда состояние 0 или 1.

Вывод:

    В регистре DDRx на нужный разряд устанавливаем 1 обозначая его как вывод;

    В регистр PORTx на этот разряд устанавливаем его состояние 0 или 1;

Пример : На выводе 5 порта B установить 1 ( вывод 17 микроконтроллера переключить на логическую 1 )

регистр DDRB

Установили разряд DDRB 5 в 1 настроив вывод как вывод

регистр PORT B

PORT B 7

PORT B 6

PORT B 5

PORT B 4

PORT B 3

PORT B 2

PORT B 1

PORT B 0

Установили разряд PORT B 5 переключив вывод микроконтроллера в 1. Дальнейшее переключение этого вывода производится без изменения регистра DDRx если не понадобится переключить разряд порта на ввод.

Регистр PIN B можно не использовать, если только для проверки состояния выводов порта.

2.3. Программа

Разберем программу на C по строкам.

#include // Подключение библиотеки // ввода/вывода AVR #include // Подключение библиотеки создания задержек #define V_V 5 // Указываем макроопределение регистра 5 порта B int main() { DDRB |= 1 << V_V; // Устанавливаем 5 бит регистра DDRB в // (назначаем как вывод) while(1) { // Безконечный цикл основной программы PORTB |= 1 << V_V; // Устанавливаем вывод микроконтроллера в 1 _delay_ms(100); // Ждем 100 мс PORTB &= ~(1 << V_V); // Устанавливаем вывод микроконтроллера в 0 _delay_ms(100); // Ждем 100 мс } return 0; }

В программе вставлен бесконечный цикл while(1), чтобы микроконтроллер не выполнил ничего лишнего. Все действия с портами в программе выполнены с использованием поразрядных операций в языке C. Что дало возможность управлять только одним разрядом (одним выводом микроконтроллера) порта B.

На использованном нами выводе микроконтроллера в Arduino UNO и Arduino Nano v3 подключен светодиод, поэтому в первой программе не придется даже собирать схему, достаточно подключить Arduino к компьютеру.

2.4. Проект на C и компиляция

Программное обеспечение готово, программа написана, микроконтроллер тоже есть в виде Arduino. Начнем.

Запускаем CodeBlocks, в меню File >> New >> Project начинаем создавать проект.

Выбираем AVR Project и Go.

В поле Project title указываем название проекта, ниже в Folder to create project in указываем путь к папке куда создаем проект и жмем Next.

В следующем окне оставляем галку только Create “Release” configuration и опять Next.


Выбираем наш микроконтроллер (у меня ) устанавливаем частоту (для Arduino Nano v3 - 16МГц ) и оставляем создание только hex файла и Finish.

И наконец в созданном проекте находим файл main.c и открываем его. Внутри видим:

/* */ #include int main(void) { // Insert code while(1); return 0; }

Заменяем эту заготовку нашей программой и жмем

Происходит компиляция проекта и внизу видим

2.5. Прошиваем микроконтроллер

Все прошивка готова, она находится в папке проекта (выбранной при создании проекта). У меня C:\avr\Program1\bin\Release\Program1.hex этот файл и является нашей прошивкой.

Начнем прошивать. Запустим программу ArduinoBuilder

В окне выбираем файл hex (находится в папке проекта CodeBlocks >> bin/Release/project1.hex) нашего проекта, выбираем Arduino и частоту микроконтроллера и жмем кнопку чем программировать (у меня COM9 ) обычно это com порт отличный от 1. После сего проделанного смотрим мигающий диод.

На этом задача минимум выполнена. Рассмотрен подборка программного обеспечения, изучены порты ввода/вывода и регистры их управления, написана программа на C скомпилирована и прошита в микроконтроллер. И все это можно применить для микроконтроллеров AVR за исключением программы ArduinoBuilder которая в основном создана под Arduino, но и ее можно заменить при использовании например программатора

Порты ввода и вывода микроконтроллера AVR, необходимы для обмена данными с различными подключенными к нему устройствами, например, реле, световыми и звуковыми индикаторами, датчиками и т.п. С помощью AVR портов, осуществляется не только обмен данными, но и синхронизация схемы в целом. Количество AVR портов зависит от модели МК. В среднем имеется (1-7) портов. Обычно, порты AVR восьмиразрядные, если разрядность не ограничена количеством выводов на корпусе МК.


На, практике в принципиальных схемах порты AVR обозначаются латинскими символами, например, PORT A, PORT B - PORTG. Каждый вывод – обладает своим порядковым номером, причем, нумерация начинается с цифры 0. Если МК использует 8 разрядов, то нумерация выглядит так – PB0… PB7.


Выводы портов способны выполнять также и альтернативные задачи. Если, допустим, сигнал модуля USART совпадает с выводом BP5, то выводы BP4 и BP3, начинают работать в режимах (SCK, MISO и MOSI) и не могут быть задействованы как элементы порта ввода/вывода. Если модуль отсоединить, то эти выводы продолжают работать как элементы порта.

Управлять любым портом МК X можно тремя регистрами: DDRx; PORTx; PINx.

Например, порт PB4 где буква «B» - имя порта, а цифра - номер бита. За порт «B» в конкретном примере отвечают три восьмиразрядных регистра PORT B, PIN B, DDR B, а каждый бит в этом регистре отвечает за свою ножку порта. Т.е за порт «А» аналогичным образом отвечают PORTA, DDRA, PINA.

Регистр DDR x стандартный 8 битный порт , осуществляющий передачу данных каждой линии порта X. 0 – вход, 1 – выход. Каждый из восьми бит, отвечает только за свою линию порта Px (0-7). Т.к выводы нумеруются с (0) – первый бит отвечает за BP0, второй соответственно за BP1 и т.п. Если вам необходимо, чтобы конкретный вывод начал работать на вход – значения регистра устанавливаем равным 0, на выход = 1. При включении, параметры всех выводов, сбросятся в ноль.

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

Причем имеется две границы порогов: гарантированного нуля и гарантированной единицы - за которыми мы можем четко обозначить текущий логический уровень. Например, для пятивольтового питания это 1.4 и 1.8 вольта. То есть при снижении уровня напряжения от максимума до минимума, заданный бит в регистре PIN переключится с логической 1 на 0 только при снижении уровня напряжения ниже 1.4 вольт, а вот когда напряжение нарастает от минимума до максимума переключение бита осуществляется только по достижении уровня в 1.8 вольта. То есть появляется гистерезис переключения с логического "0" на "1", что исключает вероятность появления хаотичные переключения под действием различных помех, и соответственно ошибочное считывание логического уровня в интервалах между порогами переключения.

При снижении напряжения эти пороги также становятся ниже, график зависимости переключения от питающего напряжения можно найти в даташите на каждый микроконтроллер.

PORTx это регистр управления состоянием вывода. Если мы производим настройку вывода на вход, то от регистра PORT зависит тип входа (Hi-Z или PullUp). Если ножка настроена на выход, то значение бита в регистре PORTx зависит от состояния вывода. Например, PORTxy=1 то на выводе логическая "1", а при PORTxy=0 на нем логический ноль. Если ножка настроена на вход PORTxy=0, то вывод в режиме Hi-Z. Если PORTxy=1 то вывод в режиме PullUp с настройкой сопротивлением в 100к до питания.

Общая структура работы порта AVR показана на рисунке ниже:

Теперь о режимах работы портов:

Режим выхода если нам требуется выдать в порт логическую единицу мы включаем порт на выход (DDRxy=1) и записываем в PORTxy "1" - при этом осуществляется замыкание верхнего ключа и на выводе устанавливается напряжение близкое к уровню питанию. А если надо логический "0", то в PORTxy записываем ноль и открывается уже нижний ключ, на выводе устанавливается напряжение близкое к нулю вольт.
Вход Hi-Z - режим высокоимпендансного входа (включен по умолчанию). Все ключи разомкнуты, а сопротивление порта очень большое. Этот режим хорошо подходит для прослушивания какой либо шины данных, т.к. он не оказывает на нее абсолютно никакого влияния.
Вход PullUp - При DDRxy=0 и PORTxy=1 замыкается вентиль подтяжки и к линии подсоединяется сопротивление номиналом 100кОм, что моментально приводит неподключенную никуда в состояние логической "1". Основная задача режима - недопустить хаотичного переключения состояния на входе под действием помех. Но если на входе установится логический ноль (замыкание линии на корпус кнопкой или другим образом), то слабый 100 кОмный резистор не способен удержать напряжение на линии на уровне логической "1" и на входе установится ноль.

Также почти каждая ножка типового МК обладает дополнительными функциями. На распиновке в даташите они обычно подписаны в скобках. Это могут быть выводы разных последовательные интерфейсов, приемопередатчиков, выходы ШИМ генераторов, аналоговые входы. По умолчанию дополнительные функции всегда отключены, а вывод управляется только парой DDR и PORT, но если включить дополнительную функцию, то управление может перейти под контроль какого-либо периферийного устройства. Например, приемник USART. Как только выставляем бит разрешения приема RXEN, так RxD, сразу переходит в режим входа.

Как известно, для большинства микроконтроллеров AVR, максимальный ток нагрузки через выходной порт составляет 40 мА. Имеются ограничения по разным типам и по одновременно задействованным портам выхода, т.к допустимая мощность рассеивания, определяется видом корпуса, точнее его тепловым сопротивлением.

Типовая схема порта выхода МК AVR



VDD – источник питания; V1 источник импульсного или постоянного сигнала; M1, M2 и M3 полевые транзисторы; R1- подтягивающий» внутренний резистор (20..50 кОм и 50..150 кОм); CL емкость нагрузки или линии к нагрузке может изменяться от десятка пФ и до уровня «пока не сгорит»)

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

Например у ATmega 328p максимальное значение тока через один выход для уровней «лог. 1 или 0» не более 40 мА, а максимальный уровень суммарного тока через все выходы, должен быть не выше 200 мА. Сопротивление канала (Rdrain) RD= 45 Ом. Смотри даташит в .

Статическая характеристика порта AVR «вход -выход», представляет из себя зависимость выходного напряжения от входного. Рассмотрим схему тестирования КМОП выхода порта по входному сигналу.


Передаточная характеристика КМОП выхода для этой схемы будет следующая:


Как видно из графиков, КПОМ схема отлично работает в роли переключательного элемента. «Сквозной» ток не превышает 5 мА. Кстати, схема эта типовой логический инвертор.

Динамическая характеристика это реакция выхода схемы на входной сигнал во временной области, т.е. зависимость выходного сигнала от входного на шкале времени.


V1 - источник сигнала с параметрами меандр, частота 1 МГц, уровни от 0 и до +5 В, длительность фронтов 5нс, выходное сопротивление Rout= 1Ом; CL емкость нагрузки (0 и 1000пФ).

Причем в лияние емкости нагрузки заметно, в соответствии с рисунком ниже, где представлены динамические характеристики КМОП-выхода порта AVR при емкости нагрузки CL=0 и CL= 1nF.


Основой этой схемы является микроконтроллер AVR ATmega32. ЖК дисплей с разрешением 128 х 64 точек. Схема осциллографа на микроконтроллере предельно проста. Но есть один существенный минус - это достаточно низкая частота измеряемого сигнала, всего лишь 5 кГц.

Долгое время мы оставляли без внимания микроконтроллеры AVR , и вот пришла пора исправить это недоразумение! Как и для других контроллеров, будем постепенно рассматривать различную периферию AVR’ок, сначала теорию, всякие регистры, ну и под конец небольшие примерчики.

В качестве IDE я использую AVR Studio 5 , шестую версию AVR Studio даже не пробовал, не так часто последнее время мне попадаются задачи для AVR) А вообще неплохо иметь еще и установленную AVR Studio 4, потому что порой случается так, что запрограммировать контроллер из AVR Studio 5 не представляется возможным. Вот совсем недавно я хотел прошить ATMega2560 при помощи программатора STK500 и это оказалось неосуществимо через 5 студию) Хорошо осталась со старых времен AVR Studio 4, и проблема решилась в течение пары минут.

Что тут еще можно сказать?.. Да в принципе, это все, можно переходить к делу;)

Начинать, естественно, будем с GPIO – портов ввода-вывода , потому как без них никуда) И прежде чем описывать регистры, которые управляют работой портов, отмечу несколько «электрических» моментов.

На входе каждой ножки микроконтроллера заботливые разработчики поставили пару диодов, которые должны спасти микроконтроллер в случае превышения допустимого напряжения. НО! На деле все не столь радужно, и если подать на вход микроконтроллера, например, 7.5 Вольт, то контроллеру уже никто и ничто не поможет, проверено на собственном опыте. Поэтому все эксперименты нужно проводить аккуратно)

Теперь к регистрам. Вся работа с портами ввода-вывода в AVR’ках сосредоточена в трех регистрах – DDRx, PORTx, PINx. Символ «x» заменяется соответствующим названием порта (A,B…). То есть, если мы хотим работать с портом A микроконтроллера, то нам нужны регистры DDRA, PORTA, PINA. Если мы хотим работать с пятым пином порта А (РА5), то нас интересует пятый бит упомянутых выше регистров. Как видите, все довольно просто) Осталось лишь разобраться, что же и куда надо записывать, за что отвечает каждый из этих регистров. Итак, начали…

Регистр DDRx.

DDRx отвечает за направление работы соответствующих выводов микроконтроллера. Каждый пин может быть либо входом, либо выходом, третьего не дано. Для того, чтобы настроить вывод на работу в режиме входа в регистр DDR для этого порта нужно записать 0, для выхода – 1. Пусть нам нужно настроить РА6 как вход, а РА3 как выход. Что делаем? Верно, третий бит регистра DDRA выставляем в 1, а в 6 бит все того же регистра DDRA записываем 0. Готово!

Регистр PINx.

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

Регистр PORTx.

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

PORTx = 1 – при такой конфигурации мы получаем вход с подтяжкой вверх (PullUp)

PORTX = 0 – высокоимпедансный вход (Hi-Z) – это значит, что сопротивление порта настолько велико, что его можно считать бесконечным)

Итак, продолжаем с регистром PORTx. Если вывод работает в качестве выхода, а в регистре PORTx единица – то на выводе будет высокий уровень сигнала, аналогично, PORTx = 0 – низкий уровень.

Давайте небольшой пример для иллюстрации 😉

Настроим вывод РС4 на работу в режиме входа с подтяжкой вверх. Для этого в четвертый бит регистра DDRC запишем 0 (режим входа), а в регистре PORTC четвертый бит надо выставить в 1 (подтяжка вверх). Вот и все.

В принципе это все, что касается теории, углубляться дальше не будем. Осталось соблюсти традиции и поиграть с диодиком. Пусть диод подключен к выводу PВ5. Заставим его помигать! И прежде всего создадим проект. Я, как уже говорил, использую AVR Studio 5, а в качестве контроллера выберу мой любимый ATMega88)

// Подключаем нужный файл #include /*******************************************************************/ // Простенькая функция для формирования задержки void delay(unsigned int time ) { unsigned int i = 0 ; for (i = 0 ; i < time ; i++ ) ; } /*******************************************************************/ // Инициализация нашего вывода, он работает на выход void initAll() { DDRB = 0b00100000 ; } /*******************************************************************/ // Собственно тело функции main() int main(void ) { initAll() ; while (1 ) { // Зажигаем диод PORTB = 0b00100000 ; // Отдыхаем delay(50000 ) ; // Гасим диод PORTB = 0b00000000 ; delay(50000 ) ; } } /*******************************************************************/

Вот таким получилось наше первое общение с микроконтроллерами AVR . В скором времени рассмотрим по возможности всю их периферию, а потом можно замутить что-нибудь поинтересней 😉

Вот ты читаешь сейчас это и думаешь — память, регистры, стек и прочее это хорошо. Но ведь это не пощупать, не увидеть. Разве что в симуляторе, но я и на дельфи с тем же условием могу накодить. Где мясо!!!

В других курсах там, чуть ли не с первых строк, делают что то существенное — диодиком мигают и говорят, что это наш Hello World. А тут? Гыде???

Да-да-да, я тебя понимаю. Более того, наверняка ты уже сбегал к конкурентам и помигал у них диодиком;)))) Ничего, простительно.

Я просто не хотел на этом же мигании дидодиков и остановиться, а для прогресса нужно четкое понимание основ и принципов — мощная теоретическая база. Но вот пришла очередь практики.

О портах было рассказано, шаблон программы у вас уже есть, так что сразу и начнем.

Инструментарий
Работа с портами, обычно, подразумевает работу с битами. Это поставить бит, сбросить бит, инвертировать бит. Да, конечно, в ассемблере есть удобные команды

cbi/sbi, но работают они исключительно в малом адресном диапазоне (от 0 до 1F, поэтому давайте сначала напишем универсальные макросы, чтобы в будущем применять их и не парить мозг насчет адресного пространства.

Макросы будут зваться:

  • SETB byte,bit,temp
  • CLRB byte,bit,temp
  • INVB byte,bit,temp,temp2

Причем при работе с битами младших РВВ (0-1F адрес) то значение параметра TEMP можно и не указывать — он все равно подставляться не будет. За исключением команд инверсии — там промежуточные регистры полюбому нужны будут.

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

  • SETBM byte,bit
  • CLRBM byte,bit
  • INVBM byte,bit

Вот их исходный код. Как можно заметить, активно используются условия макроязыка, что дает возможность налупить универсальных макросов. Компилятор сам разберется какую версию куда ему подсунуть:)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 < 0x20 SBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ORI R17,1<<@1 OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ORI R17,1<<@1 STS @0,R17 POP R17 .endif .endif .ENDM ;SET BIT with REG .MACRO SETB .if @0 < 0x20 ; Low IO SBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ORI @2,1<<@1 OUT @0,@2 .else ; Memory LDS @2,@0 ORI @2,1<<@1 STS @0,@2 .endif .endif .ENDM ;............................................................. ;Clear BIT with REG .MACRO CLRB .if @0 < 0x20 ; Low IO CBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ANDI @2,~(1<<@1) OUT @0,@2 .else ; Memory LDS @2,@0 ANDI @2,~(1<<@1) STS @0,@2 .endif .endif .ENDM ;Clear BIT with STACK .MACRO CLRBM .if @0 < 0x20 CBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ANDI R17,~(1<<@1) OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ANDI R17,~(1<<@1) STS @0,R17 POP R17 .endif .endif .ENDM ;............................................................. .MACRO INVB .if @0 < 0x40 IN @2,@0 LDI @3,1<<@1 EOR @3,@2 OUT @0,@3 .else LDS @2,@0 LDI @3,1<<@1 EOR @2,@3 STS @0,@2 .endif .ENDM .MACRO INVBM .if @0 < 0x40 PUSH R16 PUSH R17 IN R16,@0 LDI R17,1<<@1 EOR R17,R16 OUT @0,R17 POP R17 POP R16 .else PUSH R16 PUSH R17 LDS R16,@0 LDI R17,1<<@1 EOR R17,R16 STS @0,R17 POP R17 POP R16 .endif .ENDM ;= End macro.inc ========================================

;= Start macro.inc ======================================== ;SET BIT with stack .MACRO SETBM .if @0 < 0x20 SBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ORI R17,1<<@1 OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ORI R17,1<<@1 STS @0,R17 POP R17 .endif .endif .ENDM ;SET BIT with REG .MACRO SETB .if @0 < 0x20 ; Low IO SBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ORI @2,1<<@1 OUT @0,@2 .else ; Memory LDS @2,@0 ORI @2,1<<@1 STS @0,@2 .endif .endif .ENDM ;............................................................. ;Clear BIT with REG .MACRO CLRB .if @0 < 0x20 ; Low IO CBI @0,@1 .else .if @0<0x40 ; High IO IN @2,@0 ANDI @2,~(1<<@1) OUT @0,@2 .else ; Memory LDS @2,@0 ANDI @2,~(1<<@1) STS @0,@2 .endif .endif .ENDM ;Clear BIT with STACK .MACRO CLRBM .if @0 < 0x20 CBI @0,@1 .else .if @0<0x40 PUSH R17 IN R17,@0 ANDI R17,~(1<<@1) OUT @0,R17 POP R17 .else PUSH R17 LDS R17,@0 ANDI R17,~(1<<@1) STS @0,R17 POP R17 .endif .endif .ENDM ;............................................................. .MACRO INVB .if @0 < 0x40 IN @2,@0 LDI @3,1<<@1 EOR @3,@2 OUT @0,@3 .else LDS @2,@0 LDI @3,1<<@1 EOR @2,@3 STS @0,@2 .endif .ENDM .MACRO INVBM .if @0 < 0x40 PUSH R16 PUSH R17 IN R16,@0 LDI R17,1<<@1 EOR R17,R16 OUT @0,R17 POP R17 POP R16 .else PUSH R16 PUSH R17 LDS R16,@0 LDI R17,1<<@1 EOR R17,R16 STS @0,R17 POP R17 POP R16 .endif .ENDM ;= End macro.inc ========================================

Со временем, когда пишешь на ассемблере, таких вот макросов становится очень и очень много. Они выносятся в отдельный файл и просто подключаются к любому твоему проекту, а написание кода становится легким и приятным.

Но вернемся к коду,
Мигнем уж светодиодиком то, наконец?

Да не вопрос. На демоплате уже смонтированы светодиоды, почему бы их не заюзать? Они висят на выводах порта PD4,PD5, PD7. Надо только одеть джамперы.

; Internal Hardware Init ====================================== SETB DDRD,4,R16 ; DDRD.4 = 1 SETB DDRD,5,R16 ; DDRD.5 = 1 SETB DDRD,7,R16 ; DDRD.7 = 1 ; End Internal Hardware Init ===================================

Осталось зажечь наши диоды. Зажигаются они записью битов в регистр PORT. Это уже делаем в главной секции программы.

; Main ========================================================= Main: SETB PORTD,4,R16 ; Зажгли LED1 SETB PORTD,7,R16 ; Зажгли LED3 JMP Main ; End Main =====================================================

Компилиуем, можешь в трассировщике прогнать, сразу увидишь как меняются биты. Прошиваем… и после нажатия на RESET и выгрузки bootloader (если конечно у тебя ) увидишь такую картину:


И для версии II


Во! Тока это же скучно. Давай ка ими помигаем.

Заменим всего лишь наши макрокоманды.

; Main ========================================================= Main: SETB PORTD,4,R16 ; Зажгли LED1 INVB PORTD,7,R16,R17 ; Инвертировали LED3 JMP Main ; End Main =====================================================

Зажгли, прошили…

А вот фиг — горят оба, но один чуть-чуть тусклей. На самом деле он мерцает, но очень очень быстро. Если ткнуть осциллографом в вывод PD7, то будет видно, что уровень меняется там с бешеной частотой:


Что делать? Очевидно замедлить. Как? Самый простой способ, который практикуют в подавляющем большинстве обучалок и быстрых стартов — тупой задержкой. Т.е. получают код вида:

; Main ========================================================= Main: SETB PORTD,4,R16 ; Зажгли LED1 INVB PORTD,7,R16,R17 ; Инвертировали LED3 RCALL Delay JMP Main ; End Main ===================================================== ; Procedure ==================================================== .equ LowByte = 255 .equ MedByte = 255 .equ HighByte = 255 Delay: LDI R16,LowByte ; Грузим три байта LDI R17,MedByte ; Нашей выдержки LDI R18,HighByte loop: SUBI R16,1 ; Вычитаем 1 SBCI R17,0 ; Вычитаем только С SBCI R18,0 ; Вычитаем только С BRCC Loop ; Если нет переноса - переход RET ; End Procedure ================================================

Прошили, запустили… О да, теперь мигание будет заметно.

При параметрах 255.255.255 длительность выдержки на 8Мгц будет около 2.1 секунды. Можно увеличить разрядность задержки еще на несколько байт. Тогда можно хоть час зарядить.

Но этот метод ущербен, сейчас покажу почему.

Давай добавим кнопку. LED3 пусть мигает в инверсии. А мы сделаем так, что когда кнопка нажата у нас горит LED1, а когда отпущена горит LED2.

В качестве кнопки возьмем тактовую A, подключим ее к порту PD6.


Для версии II аналогично. Только кнопку возьмем с группы кнопок и соединим вывод PD6 контроллера с штырем COL1 на кнопочной панели.

Обрати только внимание на джамперы, что стоят на перемычках кнопочного поля. Синие такие. А также на один неприметный черный джамперок, на который указывает правая стрелка. Он соединяет крайне левую колонку кнопок с землей. Набрасывается на пины GND и ROW1. На плате там все подписано.

Проверка кнопки делается командой SBIC, но вначале ее надо инициализировать. Сделать DDR=0, PORT=1 — вход с подтяжкой.

Добавляем в секцию инициализации (Internal Hardware Init) эти строчки:

; Main ========================================================= Main: SBIS PIND,6 ; Если кнопка нажата - переход RJMP BT_Push SETB PORTD,5 ; Зажгем LED2 CLRB PORTD,4 ; Погасим LED1 Next: INVB PORTD,7,R16,R17 ; Инвертировали LED3 RCALL Delay JMP Main BT_Push: SETB PORTD,4 ; Зажгем LED1 CLRB PORTD,5 ; Погасим LED2 RJMP Next ; End Main =====================================================

Ну чо, работает. Кнопочка жмется — диодики меняются. Третий же бодро подмигивает. Но есть западло:

Торомозит программка то! Я кнопочку нажал, а картинка не сменилась, нужно подождать, подержать… Почему? А это из-за нашего быдлокодинга.

Помнишь я тебе говорил, что тупые задержки, в которых МК ничего не делает это адское зло? Вот! Теперь ты в этом убедился сам. Чтож, со злом надо бороться. Как? Ну эт я тоже уже говорил — делать непрерывный цикл с флажками. Хотя бы внести в нашу задержку полезную работу.

Шарманка
Сейчас я тебе покажу как можно сделать цифровую шарманку. Помнишь как она устроена?

Там барабан с торчащими гвоздями и пружинки на разные тона. Гвозди вращаются, дергают пружинки — они звякают. Получается расколбасный музон. А что если нашу шарманку развернуть в ленту. Не правда ли гвозди похожи на единички? ;))))

Лентой будет счетчик, считающий от нуля, скажем, до FF.FF.FF.FF и потом опять до нуля или еще какой величины, сколько надо столько и сделаем. А наши подпрограммы будут играть роль пружинок, цепляясь в нужных местах — сравнивая свою константу с текущим значением счетчика.

Совпало? Делаем «ДРЫНЬ!»

Осталось только прописать на временном цикле нашей шарманки где и что должно запускаться. И тут есть одна очень удобная особенность — для построения циклических последовательностей нам достаточно отлавливать один разряд.

Скажем, считает шарманка от 0 до 1000, а нам надо 10 раз мигнуть диодом. Не обязательно втыкать 10 обработчиков с разными значениями. Достаточно одного, но чтобы он ловил значение **10. Все, остальное нам не важно. И сработает он на 0010, 0110, 0210, 0310, 0410, 0510, 0610, 0710, 0810, 0910. Более частые интервалы также делятся как нам надо, достаточно влезть в другой разряд. Тут надо только не забыть отрезать нафиг старшие разряды, чтобы не мешались.

Приступим. Вначале создадим наш счетчик в сегменте данных:

LDS R16,CCNT LDS R17,CCNT+1 LDS R18,CCNT+2 LDS R19,CCNT+3

Все, теперь в R16 самый младший байт нашего счетчика, а в R19 самый старший.

Регистры можно предварительно затолкать в стек, но я дам тебе лучше другой совет — когда пишешь программу, продумывай алгоритм так, чтобы использовать регистры как сплошной TEMP данные которого актуальны только здесь и сейчас. А что будет с ними в следующей процедуре уже не важно — все что нужно должно будет сохранено в оперативке.

По можно сделать так:

LDI R20,1 ; Нам нужна единичка CLR R15 ; А еще нолик. ADD R16,R20 ; Прибавляем 1 если в регистре 255, то будет С ADC R17,R15 ; Прибавляем 0+С ADC R18,R15 ; Прибавляем 0+С ADC R19,R15 ; Прибавляем 0+С

Пришлось потратить еще два регистра на хранение констант нашего сложения. Все от того, что AVR не умеет складывать регистры с непосредственным числом. Зато умеет вычитать.

Я уже показывал, что R-(-1)=R+1, а ведь никто не запрещает нам этот же прием устроить и тут — сделать сложение через вычитание.

1 2 3 4 SUBI R16,(-1) SBCI R17,(-1) SBCI R18,(-1) SBCI R19,(-1)

SUBI R16,(-1) SBCI R17,(-1) SBCI R18,(-1) SBCI R19,(-1)

Даст нам инкремент четырехбайтного числа R19:R18:R17:R16

А теперь я вам покажу немного целочисленной магии
Почему это будет работать? Смотри сам:

SUBI R16,(-1) это, по факту R16 — 255 и почти при всех раскладах она нам даст нам заём из следующего разряда — С. А в регистре останется то число на которое больше.

Т.е. смотри как работает эта математика, вспомним про число в доп кодах. Покажу на четырехрязрядном десятичном примере. У Нас есть ВСЕГО ЧЕТЫРЕ РАЗРЯДА, ни больше ни меньше. Отрицательное число это 0-1 так? Окей.

1 С 0000-1=9999+C

Т.е. мы как бы взяли как бы из пятиразрядной 1 С 0000 отняли 1, но разрядов то у нас всего четыре! Получили доп код 9999 и флаг заема С (сигнализирующий о том, что был заем)

Т.е. в нашей целочисленной математике 9999=-1:) Проверить легко -1+1 = 0 Верно?

9999+1 = 1 С 0000 Верно! :))) А 1 старшего разряда банально не влезла в разрядность и ушла в флаг переноса C, сигнализирующего еще и о переполнении.

Оки, а теперь возьмем и сделаем R-(-1). Пусть R=4

1 С 0004-9999 = 0005+С

Вот так вот взяли и сложили через вычитание. Просто магия, да? ;)

Прикол ассемблера в том, что это всего лишь команды, а не доктрина и не правило. И команды которые подразумевают знаковые вычисления можно использовать где угодно, лишь бы они давали нужный нам результат!

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

Флаг С не выскочит лишь когда у нас дотикает до 255 (9999 в десятичном примере), тогда будет 255-255 = 0 и вскочит лишь Z, но нам он не нужен.

STS CCNT,R16 STS CCNT+1,R17 STS CCNT+2,R18 STS CCNT+3,R19

Код Инкремента четырехбайтной константы в памяти можно свернуть в макрос, чтобы не загромождать код

; Main ========================================================= Main: SETB PORTD,4 ; Зажгли LED1 INVB PORTD,7,R16,R17 ; Инвертировали LED3 Next: INCM CCNT JMP Main

Запусти режим отладки и поставь точку останова (F9) на метку Main и загони курсор на первый брейкпоинт.

0хB6(CCNT) 0х9F(CCNT+1) 0х04(CCNT+2) 0x00(CCNT+3)

Осталось теперь только сравнить число с этим слепком.

Как сравнивать? Да довольно просто. Все зависит от того что мы хотим получить. Если ОДНО событие за ВЕСЬ период глобального счетчика нашей шарманки, то тупо, побайтно. В этом случае у тебя диодик моргнет через секунду после старта, а дальше ты будешь ждать пол часа до переполнения всего четырехбайтного счетчика.

Чтобы он моргал каждую секунду тебе надо при сравнении замаскировать старшие биты глобального счетчика, словно у него разрядность не 32 бита, а меньше (и переполняется он чаще).

Младшие байты сравниваем как есть, а самый старший только до его максимального разряда, остальные надо отрезать.

Т.е. самый старший разряд для этого случая это CCNT+2=0х04 если в двоичном представлении то 0х04 = 00000 100 так вот, счетчик у нас четырех разрядный, значит событие с маской

00 04 9F B6 (00000000 00000 100 10011111 10110110)

до переполнения возникнет дофига число раз. Видишь я нули жирным шрифтом выделил. Старший самый у мы вообще сравнивать не будем, а вот пред старший надо через AND по маске 00000111 продавить, чтобы отсечь старшие биты.

Их еще надо заполнить до переполнения и обнуления счетчика. Но если мы их замаскируем то их дальнейшая судьба нас не волнует. Пусть хоть до второго пришествия тикает — нам плевать.

LDS R16,CCNT ; Грузим числа в регистры LDS R17,CCNT+1 LDS R18,CCNT+2 ANDI R18,0x07 ; Накладываем маску CPI R16,0xB6 ; Сравниванем побайтно BRNE NoMatch CPI R17,0x9F BRNE NoMatch CPI R18,0x04 BRNE NoMatch ; Если совпало то делаем экшн Match: INVB PORTD,7,R16,R17 ; Инвертировали LED3 ; Не совпало - не делаем:) NoMatch: Next: INCM CCNT ; Проворачиваем шарманку JMP Main

Во, загрузили теперь мигает. Никаких затупов нет, нигде ничего не подвисает, а главный цикл пролетает со свистом, только успевай барабан шарманки проворачивать:)

Вот только мигает заметно медленней чем мы хотели. Не 1 секунда, а 8. Ну а что ты хотел — добавив процедуру сравнения мы удлиннили цикл еще на несколько команд. И теперь он выполняется не 25 тактов, а 36. Пересчитывай все циферки заново:)))))

Но это еще не самый цимес! Прикол в том, что у тебя часть кода выполняется, а часть нет — команды сравнения и перехода. Поэтому точно высчитать задержку по тактам это проще сразу удавиться — надо по каждой итерации высчитать когда и сколько у тебя будет переходов, сколько они тактов займут…

А если код будет еще больше, то ваще труба и погрешность накапливается с каждой итерацией!

Зато, если добавить код обработки кнопок:

; Main ========================================================= Main: SBIS PIND,6 ; Если кнопка нажата - переход RJMP BT_Push SETB PORTD,5 ; Зажгем LED2 CLRB PORTD,4 ; Погасим LED1 Next: LDS R16,CCNT ; Грузим числа в регистры LDS R17,CCNT+1 LDS R18,CCNT+2 ANDI R18,0x07 CPI R16,0xB6 ; Сравниванем побайтно BRNE NoMatch CPI R17,0x9F BRNE NoMatch CPI R18,0x04 BRNE NoMatch ; Если совпало то делаем экшн Match: INVB PORTD,7,R16,R17 ; Инвертировали LED3 ; Не совпало - не делаем:) NoMatch: NOP INCM CCNT JMP Main BT_Push: SETB PORTD,4 ; Зажгем LED1 CLRB PORTD,5 ; Погасим LED2 RJMP Next ; End Main =====================================================

То увидим, что от тормозов и следов не осталось. Кнопки моментально реагируют на нажатия, а диодик мигает сам по себе. Многозадачность! :)

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

Например, периодически сканировать клавиатуру, скажем, каждые 2048 оборотов главного цикла. Сам прикинь какое число надо нагрузить на сравнение и какую маску наложить:)

Можешь скачать и поглядеть , протрассировать его, чтобы увидеть как там все вертится.

А для точных вычислений времени существуют таймеры. Но о них разговор отдельный.

Устройство микроконтроллера:
– назначение, устройство и программирование портов ввода-вывода микроконтроллера

Доброго дня уважаемые радиолюбители!
Приветствую вас на сайте “ “

Ну вот, уважаемые радиолюбители, сегодня этой статьей я закончу загрузку ваших (и своих) мозгов чистой теорией. Дальше будет легче и приятней: теорию совместим с практикой.
Ну а сегодня мы рассмотрим очень важный и интересный вопрос – порты ввода/вывода микроконтроллера .

Порты ввода/вывода микроконтроллера AVR

Порты ввода/вывода (далее я буду писать сокращенно – ПВВ ) – предназначены для общения микроконтроллера с внешними устройствами . С их помощью мы передаем информацию другим устройствам и принимаем информацию от них. В зависимости от типа, микроконтроллер может иметь на своем борту от одного до семи ПВВ . Каждому порту ввода/вывода присвоено буквенное обозначение – A, B, C, D, E, F, G. Все порты в микроконтроллере равнозначные, восьмиразрядные (содержат восемь линий, они же выводы, они же разряды, они же биты) и двунаправленные – могут как передавать, так и принимать информацию. ПВВ в микроконтроллере обслуживают все его устройства, в том числе и периферийные. Поэтому, в зависимости от того какое устройство будет работать с портом он может принимать и передавать или цифровую информацию, или аналоговую.

Вообще, порты классифицируются по типу сигнала :
цифровые порты – которые работают с цифровыми сигналами – логическими “нулями” и логическими “единицами”
- аналоговые порты – которые работают с аналоговыми сигналами – использующими плавно весь диапазон входных напряжений от нуля вольт до напряжения питания МК
- смешанные порты – они и используются в наших МК , могут оперативно переключаться с режима “цифровой порт” в режим “аналоговый порт”, и обратно.

В технической литературе и схемам ПВВ обозначаются следующим образом:
– “Р ” – первая буква, означающая слово “порт”
– “А ” (В, С, D, E, F, G) – вторая буква, обозначающая конкретный порт
– “0 ” (1, 2, 3, 4, 5, 6, 7) – третий символ – цифра, обозначающая конкретный вывод (регистр, бит) порта.
К примеру: “порт А” – РА , “пятый разряд порта А” – РА5 .
Если в МК есть несколько портов, то не обязательно их имена могут идти по порядку – A, B, C. Может быть и так – В, С, D. Поэтому пугаться и судорожно искать где же порт А не надо.
Кроме того, хотя порты восьмиразрядные, выводов у порта не обязательно должно быть 8, может быть и меньше, к примеру 3 – PA0, PA1, PA2. В таком случае порт называют неполным, или урезанным.
Давайте посмотрим на конкретный МК – ATmega8:

Как видите, в этом МК порта с именем “А” нет (отсутствует как класс;). Порт РВ и порт PD – полные, имеют по восемь выводов. А порт С – неполный (ущемленный, нет места в корпусе МК для его вывода), в нем отсутствует восьмой разряд (реально, внутри корпуса МК, он есть, но работать мы с ним не можем).

Для управления портами в их электрической схеме имеется два переключателя, которыми мы можем “щелкать” программно, используя специальные регистры ввода/вывода . Такие переключатели имеются для каждого вывода, что означает возможность управлять любым выводом порта. К примеру, один вывод порта можно настроить на ввод информации, три разряда этого же порта на вывод, а оставшиеся вообще не настраивать, оставить их в “Z- состоянии” .
Давайте разберемся с этим вопросом конкретней, на примере вот этой схемы:

Обратите внимание на два переключателя – Sin и Sout , и сопротивление Rup .
С помощью Sin осуществляется переключение вывода порта или для работы на вход, или для работы на выход. Управляется этот переключатель с помощью регистра ввода/вывода DDRx . У каждого порта свой регистр. Каждый разряд регистра управляет соответствующим разрядом порта (нулевой – нулевым, первый – первым и т.д.). Символ “x” в названии порта заменяется соответствующим именем порта: для порта А – DDRA, для порта С – DDRC. При записи в разряд регистра DDRx “единицы”, соответствующий ему разряд порта переключается на вывод информации, а при записи “нуля” – на ввод информации. Просмотрите рисунки ниже, и вы поймете как работать с регистром DDRx.

1. Переключение всех выводов порта на вывод информации:


2. Переключение всех выводов порта на ввод информации:


3. Переключение части выводов порта на ввод, и части на вывод информации:

В “классическом” Ассемблере настройка выводов портов на ввод и вывод информации выглядит так (просто пример 3-го рисунка):

Idi R20, 0b01100010 - этой командой мы записываем в РОН R20 двоичное число 01100010, которым определяем – какой вывод порта будет работать на вывод (1), а какой на ввод (0) информации. В данном случаем разряды порта В 1,5,6 – настраиваются на вывод информации, а 0,2,3,4,7 – на ввод информации
Out DDRB, R20 - этой командой мы переносим содержимое РОН R20 в регистр ввода/вывода порта В.

В Algorithm Builder запись немного отличается:
#b01100010 –> DDRB
Дело в том, что Algorithm Builder несколько более смещен к языкам высокого уровня, поэтому мы просто прописываем “свое желание” одной строчкой, но а при компилировании (переводе в машинные коды), программа сама преобразует эту строчку как и в “классической” записи.

Второй переключатель – Sout . Этот переключатель имеет двойное назначение, в зависимости от настройки разрядов порта на вывод или ввод информации.
Если разряд порта настроен на вывод информации , то с его помощью мы устанавливаем на выходе разряда или логическую “1”, или логический “0” .
Если разряд порта настроен на ввод информации
, то с его помощью подключается так называемый “подтягивающий резистор” – Rup , или “внутренний нагрузочный резистор”
. Благодаря этому резистору упрощается подключение внешних кнопок и переключателей, т.к. обычно контакты требуют внешнего резистора.
Как и переключатель Sin, Sout – это регистр ввода/вывода под названием PORTx , где “х” – буквенное обозначение порта (к примеру для порта D регистр будет иметь вид – PORTD).
В семейств МК Mega имеется дополнительный переключатель – PUD , - 2-й разряд регистра ввода/вывода SFIOR (он называется “Регистр специальных функций”). С помощью этого PUD осуществляется общее управление подтягивающими резисторами:
- при записи в этот разряд “1” – все подтягивающие резисторы для всех портов отключаются;
– при записи в этот разряд “0” – состояние подтягивающих резисторов определяется регистром PORTx.
Зачем нужно общее отключение резисторов, да и этот PUD заодно, мы сегодня рассматривать не будем.
В режиме работы разрядов порта на вывод, задача регистра PORTx очень проста – то, что мы в него запишем, то и будет на выходе. Запишем одни “нули” – на выходах буду логические нули, запишем “единицы” – на выходе буду логические “единицы”.
Например:

Idi R20, 0b11111111
Out DDRB, R20
Выводим в разряды 0-3 логический ноль, а в разряды 4-7 логическую единицу:
Idi R20, 0b11110000
Out PORTB, R20
В Algorithm Builder:
#b11111111 –> DDRB
#b11110000 –> PORTB
Надеюсь, что пока все понятно.
Вышеприведенные примеры позволяют настроить весь порт сразу, и вывести нужные значения на все выводы порта за один раз.
Если необходимо настроить только один разряд порта на ввод или вывод, а также вывести “0” или “1” только в один разряд порта, не затрагивая состояние и содержание других разрядов этого порта, существуют следующие команды:
SBI A,b – установить разряд регистра
CBI A,b – сбросить разряд регистра
При этом: “А ” – номер регистра , “b ” – разряд этого регистра.
Данные команды работают не только с РВВ DDRx и PORTx, но и с теми, которые имеют номера от 0 до 31.


Пример:
- “классический” Ассемблер:
Настраиваем порт В на вывод информации:
Idi R20, 0b11111111
Out DDRB, R20
Нам нужно переключить 1-й разряд порта на ввод информации:
CBI $17, 1 (где $17 – номер РВВ порта В – DDRB, 1 – разряд порта В)
- Algorithm Builder:
#b11111111 –> DDRB
0 –> PORTB.1

У портов ввода/вывода есть еще один регистр: PINx , регистр выводов порта (“х” – буквенное обозначение порта)
Этот регистр предназначен для считывания информации с вывода порта, независимо в какой он конфигурации – на ввод, или на вывод. Записать в этот регистр мы ничего не можем, он предназначен только для считывания.

Состояние выводов портов в зависимости от их конфигурации:

* PUD нет в МК Tiny и в МК модели ATMega161

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

При сбросе или включении питания микроконтроллера все выводы всех портов (за очень-очень редким случаем) переводятся в высокоимпедансное состояние – “Z- состояние”. Этот момент следует учитывать в реальных схемах. Если нагрузкой выхода служит транзисторный ключ, то для того, чтобы его база (затвор полевого транзистора) не болтались в воздухе, необходимо ставить дополнительные внешние резисторы сопротивлением 10-100 кОм.

Если вы не используете выводы порта, то не следует их оставлять “парящими в воздухе” – из-за этого повышается потребляемый ток МК (почему – не так важно, но это так). Все неиспользуемые выходы в схеме рекомендуется нагружать на сопротивления 10-100 кОм (можно использовать и внутренние подтягивающие резисторы), или переводить выводы в режим цифровых выходов.

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

Подтягивающие резисторы не совсем “резисторы” – их роль выполняют полевые транзисторы, которые имеют большой технологический разброс – номинал подтягивающего сопротивления может колебаться в пределах 30-100 кОм. При мощных помехах, да и в других “критических случаях” рекомендуется (хотя такой рекомендации и нет в даташитах) подключать дополнительные подтягивающие резисторы номиналом 2-5 кОм. Такие резисторы следует устанавливать на вывод “Reset”, на выводы внешних прерываний, если они не используются. Также следует устанавливать резисторы при работе выводов МК на общую шину (I2C, или просто при подсоединении выхода МК к выходу другого устройства с открытым коллектором, при подключении к двухвыводным кнопкам). Сопротивление встроенного резистора в таких случаях слишком велико, чтобы отсеивать электромагнитные помехи.

Создаем программу «мигалку»

Итак, уважаемые читатели, мы уже ознакомились со структурой микроконтроллера, разобрали простые команды ассемблера. Теперь можно приступить к написанию простой программы.

Для этого нам понадобится среда AVRStudio (о которой упоминалось раньше) и середа для симуляции микроконтроллера – Proteus 7. В сети маса примеров по установке этих программ, так что на этом останавливаться не будем.

Первая наша программа будет состоять из:

Подключения файла директив, инициализации МК;

Настройки портов ввода-вывода МК;

Простейшего цикла переключения портов из логического состояния «0» в «1»;

Подпрограммы простой задержки с использованием регистров общего назначения.

При штатной установке программы AVR Studio, файлы с директивами микроконтроллера располагается по следующему адресу C:\Program Files\Atmel\AVR Tools\AvrAssembler\Appnotes.

В нашем примере будем использовать микроконтроллер Attiny2313. Его inc файл имеет название 2313def.

Для начала откроем программу AVR Studio 4 и создадим проект.

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

В последнем окне необходимо выбрать симулятор и тип нашего МК. Далее, нажимаем на клавишу «finish» и можно будет увидеть, как откроется новое окно нашего проекта.

Наш проект уже создан и его можно наполнять программным кодом. Как говорилось раньше, первым делом нужно подключить файл директив данного микроконтроллера. Если возникнет необходимость проводить симуляцию проекта в среде AVR Studio 4, то желательно указать еще и имя нашего МК. Для этого нужно прописать следующую строку «.device ATtiny2313».

Для подключения inc файла, нужно прописать.include “tn2313def.inc”. Тем самым мы разрешим компилятору использовать файл директив данного МК.

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

К примеру, на следующем рисунке обозначена строка значения ОЗУ нашего МК. В программе мы пишем «spl», хотя можно написать и« $3d».

Правильно будет в обоих случаях, и компилятор не выдаст вам ошибок и предупреждений. Но так сложнее зрительно воспринимать команды.

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

Итак, на Листинге 1 приведу пример нашей простой программы.

Листинг 1.

Device Attiny2313 ; указываем тип устройства

Include “tn2313def.inc” ; подключаем файл директив МК ATtiny2313

Def temp = r16 ; задаем имя нашему регистру общего назначения

Org 0x0000 ; начало программы с 0 адреса

ldi temp,ramend ; грузим значение ramend в регистр temp

ser temp ; настраиваем все выводы порта В на выход

out DDRB, temp ;

sbi portb,5; устанавливаем логическую «1» в PORTB5

Итак, разберем все по строкам, что мы сделали.

Первым делом, на всякий случай указали тип устройства.device Attiny2313.

Подключили файл директив.include “tn2313def.inc”.

Для простоты написания программы задали регистру R16 имя.def temp = r16. Такая операция хорошо будет упрощать написание программы в дальнейшем. Ведь словесное название регистра нам проще запомнить, нежели просто писать R16. Таким образом, можно присвоить имя любому регистру начинаю от R0 и заканчивая R31.

Командой ser temp мы грузим в регистр temp значение 255 и выгружаем его в out DDRB. Тем самым конфигурируем порт на выход. В дальнейшем, при симуляции программы в Proteus 7, мы увидем как данные порты приймут состояние логического нуля.

Устанавливаем на порте вывода PB5 логическую единицу с помощью команды sbi portb,5.

В самом конце необходимо организовать какой-то цикл, чтобы микроконтроллер не завис.

После того как наша программа написана, можно компилировать проект. Для этого нажимаем клавишу F7. Если программа написана без ошибок, то появится диалоговое окно внизу проекта с зеленым кружочком и отчетом об использовании памяти и ошибок.

Открываем среду моделирования Proteus 7 и смотрим результат.

Теперь немного усложним задачу и заставим порт вывода переключаться с логического нуля в единицу. Для этого нам необходимо немного доработать нашу программу, Листинг 2. Все изменения происходит только в цикле «main», так что весь код не будем повторять.

Смотрим результат моделирования в среде Proteus 7, пподключив к выводу PB5 осциллограф.

Как видно, сигнал на выходе порта появился. Однако частота переключения близка к частоте микроконтроллера.

Чтобы понизить скорость переключения, нам необходимо воспользоваться простой задержкой. На Листинге 3 показан простой пример реализации задержки.

Листинг 3.

sbi portb,5; устанавливаем логическую “1” в PORTB5

rcall delay ;вызываем подпрограмму задержки

cbi portb,5; устанавливаем логический “0” в PORTB5

clr r20; очистить регистры

inc r20; добавить 1

cpi r20,200 ; сравниваем, R20 = 200 ?

brne d_1; если не равно, то переходим по метке d_1, иначе пропускаем

После выполнения данной программы скорость переключения порта снизилась до 100мс. Задавая значения сравнения в регистры R20 и R21 можно регулировать этот интервал. На следующем рисунке видим результат работы программы.

На этом закончим. В следующей части мы разберем примеры программы с подключением кнопок, напишем цикл бегущей строки.

Предыдущие статьи:

♦Арифметико-логическое устройство и организация памяти – память программ, память данных, энергонезависимая память