Работа с отдельными битами мк avr. 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-микроконтроллеров работают по принципу чтение-модификация-запись при использовании их в качестве портов универсального ввода-вывода. Это означает, что изменение направления ввода-вывода одной линии порта командами SBI и CBI будет происходит без ложных изменений направления ввода-вывода других линий порта. Данное распространяется также и на изменение логического уровня (если линия порта настроена на вывод) или на включение/отключение подтягивающих резисторов (если линия настроена на ввод). Каждый выходной буфер имеет симметричную характеристику управления с высоким втекающим и вытекающим выходными токами. Выходной драйвер обладает нагрузочной способностью, которая позволяет непосредственно управлять светодиодными индикаторами. Ко всем линиям портов может быть подключен индивидуальный выборочный подтягивающий к плюсу питания резистор, сопротивление которого не зависит от напряжения питания. На всех линиях ПВВ установлены защитные диоды, которые подключены к VCC и Общему (GND), как показано на рисунке 29. Подробный перечень параметров ПВВ приведен в разделе "Электрические характеристики".

Рисунок 29 – Эквивалентная схема линии ПВВ

Ссылки на регистры и биты регистров в данном разделе даны в общей форме. При этом, символ “x” заменяет наименование ПВВ, а символ “n” заменяет номер разряда ПВВ. Однако при составлении программы необходимо использовать точную форму записи. Например, PORTB3, означающий разряд 3 порта B, в данном документе записывается как PORTxn. Адреса физических регистров ввода-вывода и распределение их разрядов приведены в разделе “Описание регистров портов ввода-вывода".

Для каждого порта ввода-вывода в памяти ввода-вывода зарезервировано три ячейки: одна под регистр данных – PORTx, другая под регистр направления данных – DDRx и третья под состояние входов порта – PINx. Ячейка, хранящая состояние на входах портов, доступна только для чтения, а регистры данных и направления данных имеют двунаправленный доступ. Кроме того, установка бита выключения подтягивающих резисторов PUD регистра SFIOR отключает функцию подтягивания на всех выводах всех портов.

Ниже приведено описание порта ввода-вывода для универсального цифрового ввода-вывода. Большинство выводов портов поддерживают альтернативные функции встроенных периферийных устройств микроконтроллера. Описание альтернативных функций приведено далее в подразделе “Альтернативные функции порта” (см. также описание функций соответствующих периферийных модулей).

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

Порты в качестве универсального цифрового ввода-вывода

Все порты являются двунаправленными портами ввода-вывода с опциональными подтягивающими резисторами. Рисунок 30 иллюстрирует функциональную схему одной линии порта ввода-вывода, обозначенный как Pxn.


Рисунок 30 – Организация универсального цифрового ввода-вывода (1)

Прим. 1: Сигналы WPx, WDx, RRx, RPx и RDx являются общими в пределах одного порта. Сигналы clkI/O, SLEEP, и PUD являются общими для всех портов.

Настройка выводов

Режим и состояние для каждого вывода определяется значением соответствующих разрядов трех регистров: DDxn, PORTxn и PINxn. Как показано в “Описании регистров портов ввода-вывода” доступ к битам DDxn возможен по адресу DDRx в пространстве ввода-вывода и, соответственно, к битам PORTxn по адресу PORTx, а к битам PINxn по адресу PINx.

Биты DDxn регистра DDRx определяют направленность линии ввода-вывода. Если DDxn = 1, то Pxn конфигурируется на вывод. Если DDxn=0, то Pxn конфигурируется на ввод.

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

Если PORTxn = 1 при конфигурации линии порта на вывод, то состояние выхода будет определяться значением PORTxn.

Поскольку одновременная запись в регистры DDRx и PORTx невозможна, то при переключении между третьим состоянием ({DDxn, PORTxn} = 0b00) и выводом лог. 1 ({DDxn, PORTxn} = 0b11) должно возникнуть промежуточное состояние или с подключенным подтягивающим резистором ({DDxn, PORTxn} = 0b01) или с выводом лог. 0 ({DDxn, PORTxn} = 0b10). Как правило, переход через состояние с подключением подтягивающего резистора эквивалентно состоянию вывода лог.1, если вывод микроконтроллера связан с высокоимпедансным входом. В противном случае, необходимо установить бит PUD регистра SFIOR для выключения всех подтягивающих резисторов на всех портах

Переключение между вводом с подтягивающими резисторами и выводом низкого уровня связано с аналогичной проблемой. Поэтому, пользователь вынужден использовать или третье состояние ({DDxn, PORTxn} = 0b00) или вывод лог. 1 ({DDxn, PORTxn} = 0b11) в качестве промежуточного шага.

В таблице 25 подытоживается действие управляющих сигналов на состояние вывода.

Таблица 25 – Настройка вывода порта

DDxn PORTxn PUD (в SFIOR) Ввод-вывод Подтягивающий резистор Комментарий
0 0 X Ввод Нет
0 1 0 Ввод Да Pxn будет источником тока при подаче внешнего низкого уровня
0 1 1 Ввод Нет Третье состояние (Z-состояние)
1 0 X Вывод Нет Вывод лог. 0 (втекающий ток)
1 1 X Вывод Нет Вывод лог. 1 (вытекающий ток)

Считывание состояние вывода

Независимо от значения бита направления данных DDxn состояние вывода порта может быть опрошено через регистровый бит PINxn. Как показано на рисунке 30 регистровый бит PINxn и предшествующая ему триггерная защелка составляют синхронизатор. Данный подход позволяет избежать метастабильности, если изменение состояния на выводе произошло около фронта внутренней синхронизации. Однако такой подход связан с возникновением задержки. На рисунке 31 представлена временная диаграмма синхронизации во время опроса внешне приложенного к выводу уровня. Длительности минимальной и максимальной задержек на распространение сигнала обозначены как tpd,max и tpd,min, соответственно.


Рисунок 31 – Синхронизация во время опроса приложенного к выводу порта уровня

В следующих примерах показано как установить на линиях 0 и 1 порта В уровень лог. 1, а на линиях 2 и 3 – лог. 0, а также как настроить линии 4…7 на ввод с подключением подтягивающих резисторов на линиях 6 и 7. Результирующее состояние линий считываются обратно, но, с учетом сказанного выше, включена инструкция nop для обеспечения возможности обратного считывания только что назначенного состояния некоторых выводов.

Пример кода на Ассемблере (1) ... ; Разрешаем подтягивание и устанавливаем высокие выходные уровни; Определяем направления данных линий портов ldi r16,(1<

Прим. 1: В программе на Ассемблере используются два временных регистра для минимизации интервала времени от настройки подтягивающих резисторов на разрядах 0, 1, 6 и 7 до корректной установки бит направления, разрешающих вывод лог. 0 на линиях 2 и 3 и заменяющих высокий уровень на разрядах 0 и 1, образованный за счет подключения подтягивающих резисторов, на высокий уровень сильноточного драйвера.

Разрешение цифрового ввода и режимы сна

Как показано на рисунке 30 входной цифровой сигнал может быть зашунтирован к общему на входе триггера Шмита. Сигнал, обозначенный на рисунке как SLEEP, устанавливается при переводе микроконтроллера в режим выключения (Power-down), экономичный режим, дежурный режим и расширенный дежурный режим. Это позволяет избежать повышения потребляемого тока в случае, если некоторые входные сигналы окажутся в плавающем состоянии или уровень входного аналогового сигнала будет близок к VCC/2.

Сигнал SLEEP игнорируется по входам внешних прерываний. Если запросы на внешнее прерывание отключены, то SLEEP действует и на эти выводы. SLEEP также игнорируется на некоторых других входах при выполнении их альтернативных функций (см. “Альтернативные функции порта ”).

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

Неподключенные выводы

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

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

Рассмотрим примеры настройки портов в CodeVision AVR
Например DDRB=0x02; данная запись означает что вторая ножка порта В настроена как выход, но откуда взялось это число????

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

Переключаем его в режим шестнадцатеричной системы (галочка HEX), и вводим наше число 0х02 просто как 2.

теперь не нажимаем никаких равно и пр. просто переключаемся в двоичную систему счисления (галочка Bin)

Получили число 10. Что же оно значит? У нашей ATmega8 имеется 8 ножек на порту В (обведены на картинке)

так вот если представить число 10 как 00000010, то это будет означать что только вторая ножка настроена как выход, остальные как вход.

PORTB.7 PORTB.6 PORTB.5 PORTB.4 PORTB.3 PORTB.2 PORTB.1 PORTB.0
0 0 0 0 0 0 1 0

Тут нужно отметить, что если вместо
DDRB=0x02;
запишем
DDRB=0b00000010;
то оно тоже будет работать, т.е. это равнозначные записи одного и того же числа, в разных системах счисления.

С DDRB разобрались 1-выход, 0-вход, а что же означает
PORTB=0x01;
тут принцип тот же самый, но:
если нога сконфигурирована как выход и значение PORTB будет равно единице на этой же ноге, то ножка после прошивки будет включена по умолчанию (т.е. на ней будет напряжение), если 0, то ножка будет выключена. В первом уроке мы могли бы заменить запись PORTB.0=1; записью PORTB=0x01; и получили тот же самый результат.

На самом деле, правильным решением включения/выключения ножки с точки зрения языка Си, являются следующие конструкции:

PORTD |= (1<<3); //включить ножку 3 порта D PORTD |= (1<<2)|(1<<5)|(1<<6); //включить ножки 2, 5, 6 порта D PORTD &= ~(1<<2); //выключить ножку 2 PORTD &= (~((1<<0)|(1<<2)|(1<<4))); //выключить ножки 0, 2, 4

Если нога сконфигурирована как вход и значение PORTB будет равно единице на этой же ноге, то к ножке будет подключен подтягивающий резистор, для устранения помех. Если вам внутренний резистор не нужен то просто установите 0 на этой ножке.

Надеюсь все понятно, если что пишите, задавайте вопросы.

При программировании микроконтроллеров постоянно приходится работать с битами. Устанавливать их, сбрасывать, проверять их наличие в том или ином регистре. В AVR ассемблере для этих целей существует целый ряд команд. Во-первых, это группа команд операций с битами – они предназначены для установки или сброса битов в различных регистрах микроконтроллера, а во-вторых, группа команд передачи управления – они предназначены для организации ветвлений программ. В языке Си естественно нет подобных команд, поэтому у начинающих программистов часто возникает вопрос, а как в Си работать с битами. Эту тему мы сейчас и будем разбирать.

В Си существуют 6 операторов для манипулирования битами. Их можно применять к любым целочисленным знаковым или беззнаковым типам переменных.

<< - сдвиг влево
>> - сдвиг вправо
~ - поразрядная инверсия
| - поразрядное ИЛИ
& - поразрядное И
^ - поразрядное исключающее ИЛИ

_______________ сдвиг влево << _______________

Сдвигает число на n разрядов влево. Старшие n разрядов при этом исчезают, а младшие n разрядов заполняются нулями.


unsigned char tmp = 3; //0b00000011
tmp = tmp << 1;
//теперь в переменной tmp число 6 или 0b00000110

Tmp = tmp << 3;
//теперь в переменной tmp число 48 или 0b00110000

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

Tmp = 7; //0b00000111
tmp <<= 2; //сокращенный вариант записи
//теперь в переменной tmp число 28 или 0b00011100

Операция сдвига влево на n разрядов эквивалентна умножению переменной на 2 n .

_______________ сдвиг вправо >> _______________

Сдвигает число на n разрядов вправо. Младшие n разрядов при этом теряются. Заполнение старших n разрядов зависит от типа переменной и ее значения. Старшие n разрядов заполняются нулями в двух случаях – если переменная беззнакового типа или если переменная знаковая и ее текущее значение положительное. Когда переменная знаковая и ее значение отрицательное – старшие разряды заполняются единицами.

Пример для беззнаковой переменной

unsigned char tmp = 255; //0b11111111
tmp = tmp >> 1;
//теперь в переменной tmp число 127 или 0b01111111

Tmp >>= 3; //сокращенный вариант записи
//теперь в переменной tmp число 15 или 0b00001111

Пример для переменной знакового типа

int tmp = 3400; //0b0000110101001000
tmp >>= 2;
//теперь в переменной число 850 или 0b0000001101010010

Tmp = -1200; //0b1111101101010000
tmp >>= 2;
//теперь в tmp число -300 или 0b1111111011010100
//видите - два старших разряда заполнились единицами

Операция сдвига вправо на n разрядов эквивалентна делению на 2 n . При этом есть некоторые нюансы. Если потерянные младшие разряды содержали единицы, то результат подобного “деления” получается грубоватым.

Например 9/4 = 2,5 а 9>>2 (1001>>2) равно 2
11/4 = 2,75 а 11>>2 (1011>>2) равно 2
28/4 = 7 а 28>>2 (11100>>2) равно 7

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

_______________поразрядная инверсия ~ _______________

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

unsigned char tmp = 94; //0b01011110
tmp = ~tmp;
//теперь в переменной tmp число 161 или 0b10100001

Tmp = ~tmp;
//теперь в tmp снова число 94 или 0b01011110

_______________ поразрядное ИЛИ | ______________

Оператор | осуществляет операцию логического ИЛИ между соответствующими битами двух операндов. Результатом операции логического ИЛИ между двумя битами будет 0 только в случае, если оба бита равны 0. Во всех остальных случаях результат будет 1. Это проиллюстрировано в табице истинности.

Оператор | обычно используют для установки заданных битов переменной в единицу.

Tmp = 155
tmp = tmp | 4; //устанавливаем в единицу второй бит переменной tmp

155 0b100110 11
4 0b000001 00
159 0b100111 11

Использовать десятичные числа для установки битов довольно неудобно. Гораздо удобнее это делать с помощью операции сдвига влево <<.


tmp = tmp | (1<<4); //устанавливаем в единицу четвертый бит переменной tmp

Читаем справа налево – сдвинуть единицу на четыре разряда влево, выполнить операцию ИЛИ между полученным числом и значением переменной tmp, результат присвоить переменной tmp.


Установить несколько битов в единицу можно так

Tmp = tmp | (1<<7)|(1<<5)|(1<<0);
//устанавливаем в единицу седьмой, пятый и нулевой биты переменной tmp

С помощью составного оператора присваивания |= можно сделать запись компактней.

Tmp |= (1<<4);
tmp |= (1<<7)|(1<<5)|(1<<0);

_______________ побитовое И & _______________

Оператор & осуществляет операцию логического И между соответствующими битами двух операндов. Результатом операции логического И между двумя битами будет 1 только в том случае, если оба бита равны 1. Во всех других случаях результат будет 0. Это проиллюстрировано в таблице истинности.

Оператор & обычно применяют, чтобы обнулить один или несколько битов.

Tmp = 155;
tmp = tmp & 247; //обнуляем третий бит переменной tmp

155 0b10011 011
&
247 0b11110 111
147 0b10010 011

Видите, третий бит стал равен 0, а остальные биты не изменились.

Обнулять биты, используя десятичные цифры, неудобно. Но можно облегчить себе жизнь, воспользовавшись операторами << и ~

Tmp = 155;
tmp = tmp & (~(1<<3)); //обнуляем третий бит

1<<3 0b00001 000
~(1<<3) 0b11110 111
tmp & (~(1<<3)) 0b10011 011 & 0b11110 111
результат 0b10010 011

Читаем справа налево – сдвинуть единицу на три разряда влево, выполнить инверсию полученного числа, выполнить операцию & между значением переменной tmp и проинвертированным числом, результат присвоить переменной tmp.


Обнулить несколько битов можно так

tmp = tmp & (~((1<<3)|(1<<5)|(1<<6))); //обнуляем третий, пятый и шестой биты



Используя составной оператор присваивания &= ,можно записать выражение более компактно

Tmp &= (~((1<<3)|(1<<5)|(1<<6)));

Как проверить установлен ли бит в переменной?
Нужно обнулить все биты, кроме проверочного, а потом сравнить полученное значение с нулем

if ((tmp & (1<<2)) != 0){
// блок будет выполняться, только если установлен

}

if ((tmp & (1<<2)) == 0){
// блок будет выполняться, только если не установлен
// второй бит переменной tmp

}

_______________побитовое исключающее ИЛИ ^ _______________


Оператор ^ осуществляет операцию логического исключающего ИЛИ между соответствующими битами двух операндов. Результатом операции логического исключающего ИЛИ будет 0 в случае равенства битов. Во всех остальных случаях результат будет 1. Это проиллюстрировано в табице истинности.

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


tmp = 155;
tmp = tmp ^ 8; // инвертируем четвертый бит переменой tmp

155 0b10011 011
^
8 0b00001 000
147 0b10010 011

Четвертый бит изменил свое значение на противоположное, а остальные биты остались без изменений.

Tmp = tmp ^ 8; // опять инвертируем четвертый бит переменой tmp

147 0b10010 011
^
8 0b0000 1 000
155 0b10011 011

Видите, четвертый бит снова изменил свое значение на противоположное.

Так записывать выражение намного удобнее

Tmp = tmp ^ (1<<3); // инвертируем третий бит переменой tmp

А так и удобно и компактно

Tmp ^= (1<<4); //инвертируем четверый бит

Можно инвертировать несколько битов одновременно

Tmp ^= ((1<<4)|(1<<2)|(1<<1)); //инвертируем 4,2 и 1 биты

У поразрядного исключающего ИЛИ есть еще одно интересное свойство . Его можно использовать, для того чтобы поменять значения двух переменных местами. Обычно для этого требуется третья переменная.


tmp = var1;
var1 = var2;
var2 = tmp;

Но используя оператор ^ переставить значения можно так:

var1 ^= var 2;
var 2 ^= var 1;
var 1 ^= var 2;

Чистая магия, хотя, честно говоря, я ни разу не пользовался таким приемом.

________________Директива #define__________________


Теперь мы знаем, как устанавливать, обнулять и инвертировать биты, знаем, как проверять установлен ли бит или нет. Рассмотренные выше выражения довольно громоздки, но с помощью директивы препроцессора #define , им можно придать более приятный вид.

Директива #define используется для присваивания символических имен константам и для макроопределений. Использование символических имен делают программу более модифицируемой и переносимой.

Например, вы используете в тексте программы константу, и вдруг вам понадобилось изменить ее значение. Если она встречается всего в трех местах, то исправить ее можно и в ручную, а что делать, если она встречается в пятидесяти строчках? Мало того, что исправление займет много времени, так еще и ошибиться в этом случае проще простого. Здесь то, как раз и выручает директива #define . В начале программы задается символическое имя константы, которое используется по ходу программы. Если нам нужно изменить это значение, это делается всего лишь в одном месте. А перед компиляцией препроцессор сам подставит во все выражения вместо имени константы ее значение.

Программирование микроконтроллера неразрывно связано с его аппаратной частью и чаще всего с внешней обвязкой. Взять хотя бы кнопки - опрашивая их в своей программе, мы обращаемся к реальным выводам микроконтроллера. А если нам вдруг понадобилось использовать программу опроса кнопок в другой схеме, где кнопки подключены к другим выводам? Придется исправлять программу. Опять таки, задав с помощью директивы #define символическое имя для соответствующих выводов, модифицировать программу будет проще простого


Пример:

#include "iom8535.h"

//порт, к которому подключены кнопки
#define PORT_BUTTON PORTA
#define PIN_BUTTON PINA
#define DDRX_BUTTON DDRA

//выводы, к которым подключены кнопки
#define DOWN 3
#define CANCEL 4
#define UP 5
#define ENTER 6

int main()
{
//конфигурируем порт на вход,
//и включаем подтягивающие резисторы

DDRX_BUTTON = 0;
PORT_BUTTON = 0xff;

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

#define MASK_BUTTONS ((1<

пример использования:
tmp = PORTB & MASK_BUTTONS;

Используя #define не жалейте скобок чтобы четко задать последовательность вычисления выражений!

Некоторые выражения можно замаскировать под «функции».

#define ADC_OFF() ADCSRA = 0

пример использования:
ADC_OFF();

Можно использовать многострочные определения, используя в конце каждой строки символ \

#define INIT_Timer() TIMSK = (1< TCCR0 = (1< TCNT0 = 0;\
OCR0 = 0x7d

пример использования:
INIT_Timer();

Ну и самое мощное применение директивы #define – это задание макроопределений (или просто макросов). Вот как с помощью #define можно задать макросы для рассмотренных ранее операций с битами

#define SetBit(reg, bit) reg |= (1<#define ClearBit(reg, bit) reg &= (~(1<#define InvBit(reg, bit) reg ^= (1<#define BitIsSet(reg, bit) ((reg & (1<#define BitIsClear(reg, bit) ((reg & (1<

пример использования:

SetBit(PORTB, 0); //установить нулевой бит порта B
InvBit(tmp,6); //инвертировать шестой бит переменной tmp


if (BitIsClear(PIND, 0)) { //если очищен нулевой бит в регистре PIND
….. //выполнить блок
}

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

Макросы очень мощное средство, но использовать их нужно осторожно. Вот самые распространенные грабли, о которых написано во всех учебниках по программированию.

Определим макрос, вычисляющий квадрат числа:

#define SQUARE(x) x*x

выражение
tmp = SQUARE(my_var);
даст корректный результат.

А что будет если в качестве аргумента макроопределения использовать выражение my_var+1

tmp = SQUARE(my_var +1);

Препроцессор заменит эту строчку на

tmp = my_var + 1 * my_var +1;

а это вовсе не тот результат, который мы ожидаем.

Чтобы избежать таких ошибок не скупитесь на скобки при объявлении макросов!

Если объявить макрос так

#define SQUARE(x) ((x)*(x))

выражение
tmp = SQUARE(my_var +1);
даст корректный результат, потому что препроцессор заменит эту строчку на
tmp = ((my_var + 1) * (my_var +1));

записываем их в папку проекта, а в начале файла main.c прописываем #include "bits_macros.h"

Порты ввода и вывода микроконтроллера 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 кГц.