ГлавнаяРегистрацияВход Сайт Сокола Сергея Вторник, 16.04.2024, 12:10
  Каталог статей Приветствую Вас Гость | RSS

 
 
Главная » Статьи » Мои статьи

Ассемблер AVR для начинающих (пятый шаг)
Наверняка любознательный читатель где-то да слышал о том, что в микроконтроллерах существует механизм прерываний. Что же это такое и зачем это нам нужно?

Все написанные нами до сих пор программы выполнялись по жестко заданному алгоритму без возможности хоть как-то его скорректировать. При этом те или иные действия осуществлялись только в тот момент времени, когда до них доходила очередь. А если нам нужно на какие-нибудь события реагировать мгновенно, а не ждать, пока до них дойдет очередь? Или, например, нужно дождаться реакции от какого-либо периферийного модуля контроллера? Глупо было бы по всей программе ставить проверки в надежде поймать нужные нам события. Вот здесь-то нам и помогут прерывания. Само название их уже говорит за себя. Это специальные подпрограммы, не вызываемые явно из основной программы, а прерывающие ход ее выполнения при наступлении какого-либо события (например, изменение состояния входа, переполнение таймера, окончание цикла преобразования АЦП).

Как же так получается, что ни с того ни с сего выполняется какая-то подпрограмма, которую никто не вызывал, да, к тому же, и прерывающая в любом месте ход выполнения основной программы?

Суть тут вот в чем. В каждом контроллере определен изначально список событий, которые могут вызвать прерывание, причем в каждом контроллере этот список свой в зависимости от количества и функциональных возможностей периферийных модулей. События, вызывающие прерывания, могут быть как внешними (например, изменение состояния входа, прием байта по интерфейсу), так и внутренними (например, переполнение таймера, окончание цикла записи АЦП). Рассмотрим такой список применительно к нашему контроллеру ATtiny13, и посмотрим, какие прерывания нам могут пригодиться, а какие вряд ли. Вообще, список этот имеется в самом конце каждого файла с расширением inc, так что можно легко узнать, какие могут присутствовать прерывания у того или иного контроллера. Приведу здесь выписку из файла tn13def.inc с некоторыми пояснениями для тех, кто не понимает по-аглицки. Заодно и рассмотрим еще несколько особенностей языка ассемблера.

;**** Interrupt Vectors ****       
.equ    INT0addr             =$001    ;External Interrupt0
.equ    PCINT0addr         =$002    ;Pin Change Interrupt0
.equ    TIM0_OVF0addr    =$003    ;Overflow0 Interrupt
.equ    EE_RDYaddr         =$004    ;EEPROM write complete
.equ    ANA_COMPaddr    =$005    ;Analog Comparator Interrupt
.equ    TIM0_COMPAaddr =$006    ;Timer/Counter0 Compare Match A    
.equ    TIM0_COMPBaddr =$007    ;Timer/Counter0 Compare Match B    
.equ    WDTaddr              =$008    ;Watchdog Timeout
.equ    ADCaddr              =$009    ;ADC Conversion Complete Handle


Итак, заголовок гласит "Interrupt Vectors", что означает "Векторы прерываний". Почему именно векторы, расскажу попозже.

Все последующие строки начинаются с одной и той же директивы equ. Она имеет следующий синтаксис. Первым аргументом ее является символическое имя. Любое, на вкус пользователя (типа объявления имени переменных или констант в языках высокого уровня). Далее ставится знак равенства, а за ним значение, присваиваемое указанному имени. По своему действию эта директива похожа на объявление константы в языках высокого уровня. Значение, присвоенное указанному имени, нельзя изменить в дальнейшем.

Еще одно новшество в синтаксисе - знак $ перед числом. Знак этот указывает, что последующее за ним число записано не в десятичной, а в шестнадцатиричной форме. Для указания этой же формы записи в языке ассемблера есть еще один префикс 0x. То есть записи $4E и 0x4E для ассемблера аналогичны и означают шестнадцатиричное число 4Е. Для записи двоичных чисел, как я упоминал уже ранее в одном из предыдущих шагов, используется префикс 0b (например, 0b000100101).

Но вернемся все же к прерываниям. Сообразительный читатель уже смог подсчитать, что контроллер ATtiny13 имеет 9 различных источников прерываний. Рассмотрим их вкратце. Более подробно о них я расскажу, когда до них дойдет очередь в наших программах.

1. External Interrupt0 - Внешнее прерывание 0. Это прерывание генерируется, если произойдет одно из предустановленных изменений состояния входа, обозначенного, как int0. Если посмотреть на список выводов контроллера (второй шаг), то можно увидеть, что этот вход объединен с выводом РВ1. В зависимости от настроек это прерывание может генерироваться при изменении состояния входа РВ1 из "0" в "1", из "1" в "0", или на протяжении всего времени, пока на входе РВ1 будет "0".

2. Pin Change Interrupt0 - Прерывание по изменению состояния выводов. В отличие от предыдущего, это прерывание может быть вызвано любым изменением состояния любого из выводов, обозначенного как PCINTx (где х может быть от 0 до 5). В настройках задается, какие именно из входов могут вызвать данное прерывание.

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

3. Overflow Interrupt0 - Прерывание по переполнению таймера 0. В контроллере ATtiny13 присутствует всего один 8-битный таймер, называемый "таймер 0". О режимах его работы я расскажу чуть ниже, а пока лишь замечу, что данное прерывание возникает при переполнении счетчика таймера, то есть при изменении его значения от 255 к 0.

4. EEPROM Write Complete - Прерывание по завершению записи в энергонезависимую память.

5. Analog Comparator Interrupt - Прерывание от аналогового компаратора. В принципе это прерывание тоже можно отнести к внешним. У контроллера ATtiny13, как и всех остальных, есть аналоговый компаратор, имеющий два входа - неинвертирующий (AIN0 - РВ0) и инвертирующий (AIN1 - РВ1). Когда величина напряжения на неинвертирующем входе становится больше напряжения на инвертирующем входе, генерируется данное прерывание.

6. Timer/Counter0 Compare Match A - Прерывание по совпадению значения счетчика таймера 0 со значением, записанным в специальном регистре OCR0A.

7. Timer/Counter0 Compare Match B - Прерывание по совпадению значения счетчика таймера 0 со значением, записанным в регистре OCR0B.

8. Watchdog Timeout - Прерывание по срабатыванию сторожевого таймера. Вообще сторожевой таймер - это довольно интересная штука. Она служит для отслеживания зависания программы и автоматического сброса контроллера при его обнаружении. При этом генерируется вот такое прерывание, чтоб пользователь мог знать, что же именно вызвало рестарт программы.

9. ADC Convertion Complete Handle - Прерывание по окончанию цикла преобразования АЦП. Модуль АПЦ, используемый в контроллерах, преобразует аналоговый сигнал в цифровой код не мгновенно, на это ему требуется некоторое время. Так вот по окончании этого самого времени и генерируется данное прерывание.

Наиболее часто по своему опыту я использовал прерывания 1, 2, 3 и 6. Прерывания 4, 5, 7 и 8 не довелось применять ни разу, возможно, ввиду специфики разрабатываемых устройств.

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

Мы уже договорились, что прерывания не вызываются из самой программы, а генерируются при наступлении определенных событий. Но обработчики прерываний-то нужно писать нам, и именно в тексте программы. Как же сказать контроллеру, что написанный нами кусок кода должен выполняться именно при наступлении какого-либо прерывания? Именно для этого и служит так называемая таблица векторов прерываний. Эта таблица располагается в начале памяти программ, причем номер прерывания соответствует адресу, по которому должен располагаться вызов подпрограммы обработки прерывания.

Попробую объяснить попроще на конкретном примере. Допустим, мы задумали выполнять какое-либо действие по переполнению счетчика таймера 0. По вышеприведенной таблице находим, что данное прерывание находится под номером 3. Это значит, что в памяти программ в ячейке с адресом 3 должна находиться команда перехода на подпрограмму, которая будет выполняться при наступлении данного прерывания. Обычно используется команда безусловного перехода rjmp. Каким же образом указать контроллеру, что мы хотим поместить данную команду именно по этому адресу?

Для этого используется еще одна директива org. Ее аргументом является число, соответствующее адресу, с которого последующая команда будет занесена в память контроллера.

Мы тут уже достаточно много понаписывали, давайте теперь разберем конкретную задачу. Задание оставим то же, что и в третьем шаге. Заставим мигать светодиод LED2, но теперь с использованием не задержек, а прерывания от таймера 0. Текст программы представлен ниже.

.include "F:\Prog\AVR\asm\Appnotes\tn13def.inc"
.org 0                 ;Задание нулевого адреса старта программы
rjmp reset          ;Безусловный переход к метке reset
.org 3                ;Задание адреса прерывания по переполнению таймера 0
rjmp timer0_ovf ;Безусловный переход к метке timer0_ovf

reset:
ldi r16, RAMEND ;Загрузка в регистр r16 адреса верхней границы ОЗУ
out SPL, r16       ;Копирование значения из r16 в регистр указателя стека SPL
ldi r16, (1<<TOIE0) ;Загрузка в регистр r16 "1", смещенной на TOIE0
out TIMSK0,r16    ;Копирование значения из регистра r16 в регистр TIMSK0
ldi r16, (1<<CS00)|(1<<CS02);Загрузка двух "1", смещенных на CS00 и CS02
out TCCR0B,r16  ;Копирование значения из регистра r16 в регистр TCCR0B
ldi r17,(1<<4)    ;Загрузка в регистр r17 "1", смещенной на 4 разр. влево
out DDRB, r17    ;Копирование из r17 в DDRB (РВ4 - выход)
clr r16               ;Очистка регистра r16
sei                    ;Глобальное разрешение прерываний

main:               ;Основной цикл программы
rjmp main         ;Вернуться к метке main

timer0_ovf:      ;Прерывание по переполнению таймера 0
eor r16,r17       ;Исключающее ИЛИ регистров r16 и r17
out PORTB,r16  ;Копирование из r16 в PORTB
reti                  ;Возврат из прерывания


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

Команда clr имеет один операнд - РОН. Результатом ее выполнения является очистка указанного РОН, то есть запись во все его биты значения "0". По своему действию аналогична записи "ldi r16, 0".

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

Команда reti также не имеет операндов. Этак команда аналогична команде ret по своему назначению, только командой ret завершалась обычная подпрограмма, а reti завершает подпрограмму обработки прерывания. В чем же отличие между ними? Дело в том, что при наступлении прерывания в статусном регистре SREG (о котором я уже упоминал ранее) сбрасывается флаг, разрешающий прерывания. Таким образом, пока  происходит обработка уже произошедшего прерывания, запрещается наступление новых прерываний. Так вот, команда reti, помимо восстановления счетчика программ из стека, снова устанавливает флаг разрешения прерываний. Так все хитро тут устроено.

Теперь рассмотрим работу каждой строки.

1 строка. Подключение файла tn13def.inc к нашему ассемблерному файлу.

2 строка. Директивой org задается нулевой адрес в памяти программ, по которому будет располагаться следующая команда.

3 строка. Командой rjmp осуществляется переход на метку reset.

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

4 строка. Директивой org задается адрес 3. Если посмотреть на таблицу векторов прерываний, то можно видеть, что по этому адресу находится прерывание по переполнению таймера 0.

5 строка. Командой rjmp осуществляется переход на метку timer0_ovf. Именно с этой метки в нашей программе начинается обработчик прерывания по переполнению таймера 0.

Таким образом, в результате выполнения строк 4 и 5, контроллер будет знать, в какую часть программы ему переходить при переполнении таймера 0.

6 строка. Пустая для отделения таблицы векторов прерываний от основной программы

7 строка. Метка reset. Отсюда будет стартовать наша программа при включении контроллера.

8 строка. Командой ldi в регистр r16 загружается адрес верхней границы ОЗУ.

9 строка. Копирование содержимого регистра r16 в регистр указателя стека SPL

10 строка. Здесь мы встречаем уже знакомую нам по предыдущему шагу конструкцию загрузки в регистр единицы, смещенной влево. Только вот величина смещения, названная TOIE0, выглядит несколько непонятно. Поясню ее чуть позднее, наберитесь терпения.

11 строка. Копирование содержимого r16 в опять же неизвестный нам регистр с именем TIMSK0.

Итак, после выполнения строк 10 и 11 в неизвестном нам РВВ TIMSK0 оказывается единица в бите с опять же неизвестным нам названием TOIE0. Начнем разбираться, что оно такое.

TIMSK0 - это регистр ввода-вывода, который содержит в себе биты, разрешающие или запрещающие различные прерывания от таймера 0. Тут мы подходим к одному важному моменту. Даже если мы задействовали механизм прерываний в контроллере, не все возможные источники прерываний смогут их вызвать. Помимо глобального разрешения всех прерываний нужно еще индивидуально разрешать те прерывания, которые нам необходимы. Поскольку в программе мы решили использовать только одно прерывание по переполнению таймера 0, то только его мы и разрешим, а все остальные оставим неактивными. Так вот, к чему я веду... Установка в единицу бита TOIE0 регистра TIMSK0 как раз и разрешает именно это прерывание. Кроме того, с помощью регистра TIMSK0 можно установить и остальные прерывания, связанные с таймером 0: бит OCIE0A разрешает прерывание по совпадению с регистром OCR0A, а бита OCIE0B - прерывание по совпадению с регистром OCR0B.

Подытожим. В результате выполнения строк 10-11 разрешается прерывание по переполнению таймера 0.

12 строка. Снова имеем знакомую конструкцию сдвига, но теперь аж двух единиц. При этом одна единица смещается на бит CS00, а вторая - на бит CS02, и между полученными значениями выполняется операция побитового ИЛИ "|", что равносильно сложению в обычной алгебре. Таким образом, имеем в регистре r16 не одну, а две единицы, каждую в нужном месте.

13 строка. Копирование содержимого регистра r16 в регистр TCCR0B.

Теперь снова разъяснение смысла того, что мы сделали. Таймер 0 может работать в различных режимах и иметь разную частоту тактирования. Для его настройки служат два регистра TCCR0A и TCCR0B. Сейчас я не буду расписывать что для чего служит. По мере необходимости я буду снабжать вас новыми знаниями. Пока же нам нужно знать только одно - как задать частоту работы таймера. Для этого имеется так называемый предделитель. Коэффициент деления предделителя задается тремя битами CS00, CS01 и CS02, расположенными в регистре TCCR0B. Зависимость его от значений этих битов представлена в таблице:

 Делитель CS02CS01
 CS00
 1 00
1
 8 01
0
 64 01
1
 256 10
0
 1024 10
1

Поскольку мы установили в единицу биты CS02 и CS00, то тактовая частота контроллера будет разделена на 1024. Попробуем теперь посчитать частоту мигания светодиода.

Тактовая частота контроллера равно 1 МГц, делитель частоты для таймера равен 1024, тогда тактовая частота таймера будет составлять 1000000/1024 = 977 Гц. Это значит, что 977 раз в секунду будет происходить наращивание счетчика таймера на 1. Переполнение наступает при достижении счетчиком значения 255, при этом он сбрасывается в 0. Значит, прерывание будет генерироваться 977 / 256 = 3,8 раза в секунду. Поскольку при каждом генерировании прерывания светодиод изменяет свое состояние на противоположное, то полная частота мигания составит 3,8 / 2 = 1,9 Гц. То есть светодиод будет мигать приблизительно 2 раза в секунду.

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

Подытожим еще раз. В строках 12-13 мы задали тактовую частоту для таймера 0, равную 1/1024 частоты процессора.

Таким образом, в строках 10-13 мы настроили таймер 0 и разрешили прерывание по его переполнению.

14 строка. Загрузка в регистр r17 единицы, смещенной на 4 разряда влево.

15 строка. Копирование регистра r17 в регистр DDRB для инициализации вывода РВ4 как цифрового выхода.

16 строка. Очистка регистра r16. Регистр этот будет использоваться для переключения светодиода LED2, и в принципе его можно было бы не очищать, поскольку на выход настроен всего один вывод. Но тогда бы происходило постоянное включение-выключение подтягивающих резисторов на тех входах, где осталась бы "1" в регистре r16, а это уже не комильфо. Так что очищаем, это правила хорошего тона в программировании.

17 строка. Команда sei разрешает глобально механизм обработки прерываний. Даже если мы уже разрешили прерывание, задав бит TOIE0 в регистре TIMSK0, оно все равно останется неактивным, пока не будет установлен флаг прерываний в регистре SREG командой sei.

18 строка. Пустая для отделения блока инициализации от основного цикла программы

19 строка. Метка main - начало основного цикла программы

20 строка. Команда безусловного перехода к метке main. Окончание основного цикла программы.

"Как же так?" - спросит удивленный читатель - "Неужели в главном цикле ничего не делается?!". Да, ничего. Именно так должна выглядеть правильно составленная программа для контроллера. Сначала инициализация всех необходимых модулей, а затем пустой цикл с прерываниями. Все нужные действия будут выполняться по мере возникновения тех или иных событий, в нашем случае - при переполнении счетчика таймера 0.

21 строка. Пустая для отделения основного цикла программы от обработчика прерывания.

22 строка. Метка timer0_ovf - начало обработчика прерывания по переполнению таймера 0. Именно сюда нас отправляла команда безусловного перехода в пятой строке.

23 строка. Уже знакомая нам операция исключающего ИЛИ между регистрами r16 и r17. Поскольку в регистр r17 мы в строке 14 загрузили единицу только в четвертый разряд, то в регистре r16 будет переключаться именно этот бит, а он, как мы помним, соответствует выводу РВ4, на котором находится светодиод LED2.

24 строка. Копирование содержимого r16 в PORTB для непосредственного управления светодиодом LED2.

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

Вот, собственно, и все, на этот раз. Я стал замечать, что с каждым разом статьи мои становятся все больше и пространее. Но что поделать: сложность материала тоже растет.

В заключение традиционные уже задания для самостоятельного выполнения.

1. Изменить программу таким образом, чтобы мигал не только светодиод LED2, но и LED1, притом, чтобы мигание осуществлялось в противофазе. Но с одним условием. Объем hex-файла не должен измениться. Думайте, данных для выполнения этого задания вам достаточно.

2. Попытайтесь изменить программу так, чтобы светодиод мигал с частотой 0,5 Гц. Я уже говорил, что для этого придется использовать некоторые ухищрения, но, в принципе, вы тоже можете сообразить, как это сделать.

3. Добавить в программу обработку кнопок SB1 и SB2. При нажатии кнопки SB1 должно начинаться мигание светодиода LED2, а при нажатии кнопки SB2 - прекращаться. Дам небольшую подсказку. Обработку нажатия кнопок можно осуществлять в основном цикле программы, а начало или прекращение мигания производить путем установки или очистки бита TOIE0 в регистре TIMSK0.

Ну вот теперь уж точно все!

Если у вас возникнут вопросы, задавайте их на форуме или здесь в виде комментариев к статье.

Желаю успехов!
Категория: Мои статьи | Добавил: mimino (04.01.2012)
Просмотров: 30136 | Комментарии: 31 | Рейтинг: 3.8/6
Всего комментариев: 201 2 »
20 Sergalome  
0
<a href=http://zmkshop.ru/index.1.php>токарно фрезерные работы на заказ в москве</a>

18 Александр  
0
Добрый день, не могли бы Вы помочь, я работаю на AtMega16 и у меня такая проблема, что при выполнении внешнего прерывания нет возвращения в основную программу, в чем может быть проблема??? Вот мой код(суть программы в том, что в основной программе нужно выдать меандр с частотой 10кГц, а в прерывании изменение состояния светодиода на противоположное):

.include "m16def.inc"
.org 0
rjmp reset
.org $0002
rjmp int_0

reset:
ldi r16,low(RAMEND)
out spl,r16
ldi r16,high(RAMEND)
out sph,r16

ldi r16,(1<<0)|(1<<2)
out DDRB,r16
ldi r16,0
out DDRD,r16
ldi r20,0
ldi r17,(1<<INT0)
out GICR,r17
ldi r18,(1<<ISC00)
out MCUCR,r18
sei

main:
sbi PORTB,0
clr r22
label1:
inc r22
cpi r22,178
brne label1
cbi PORTB,0
clr r22
label2:
inc r22
cpi r22,178
brne label2
rjmp main

int_0:
ldi r19,(1<<2)
out DDRB,r19
eor r20,r19
out PORTB,r20
label:
sbis PIND, 2
rjmp label
wait:
sbic PIND, 2
rjmp wait
reti

Заранее спасибо)

19 mimino  
0
Добрый день!

Мне думается, Вы зациклили выполнение процедуры обработки прерывания вот тут:
Кодlabel:  
  sbis PIND, 2  
  rjmp label  
  wait:  
  sbic PIND, 2  
  rjmp wait
У Вас же безусловный переход на метку label, и точно также ниже на метку wait, поэтому дальше программа не может выполниться.

С уважением,
Сергей

17 Олег  
0
Что то Андрей там намудрил по полной программе!!!

вот вариант нормально работающей программки

.device atmega16
.include "m16def.inc"

jmp RESET ; Reset Handler
;jmp EXT_INT0 ; IRQ0 Handler
;jmp EXT_INT1 ; IRQ1 Handler
;jmp TIM2_COMP ; Timer2 Compare Handler
;jmp TIM2_OVF ; Timer2 Overflow Handler
;jmp TIM1_CAPT ; Timer1 Capture Handler
;jmp TIM1_COMPA ; Timer1 CompareA Handler
;jmp TIM1_COMPB ; Timer1 CompareB Handler
;jmp TIM1_OVF ; Timer1 Overflow Handler
.org OVF0addr
jmp TIM0_OVF ; Timer0 Overflow Handler
;jmp SPI_STC ; SPI Transfer Complete Handler
;jmp USART_RXC ; USART RX Complete Handler
;jmp USART_UDRE ; UDR Empty Handler
;jmp USART_TXC ; USART TX Complete Handler
;jmp ADC ; ADC Conversion Complete Handler
;jmp EE_RDY ; EEPROM Ready Handler
;jmp ANA_COMP ; Analog Comparator Handler
;jmp TWSI ; Two-wire Serial Interface Handler
;jmp EXT_INT2 ; IRQ2 Handler
;jmp TIM0_COMP ; Timer0 Compare Handler
;jmp SPM_RDY ; Store Program Memory Ready Handler

RESET:
ldi r16, high(RAMEND)
out SPH, r16
ldi r16, low(RAMEND)
out SPL, r16

ldi r16, 0x01
out TIMSK,r16
ldi r16, 0x05
out TCCR0,r16
clr r16
ldi r16, 0x08
out DDRB, r16
ldi r17, 0x18
out DDRB, r17
sei

main:
sbi PORTB, 0
sbi PORTB, 1
sbis PINB, 0
jmp but_1
sbis PINB, 1
jmp but_2
jmp main

but_1:
ldi r18, 0x00
out TIMSK, r18
jmp main
but_2:
ldi r18, 0x05
out TIMSK, r18
jmp main

TIM0_OVF:
eor r16,r17
out PORTB,r16
reti

честно только не могу понять как сделать мигание в 0,5Гц

16 Андрей  
0
код программы:

.include "tn13def.inc"

; ФЬЮЗЫ - 9,6 МГЦ И ДЕЛЕНИЕ НА 8
;МИГАНИЕ 2 ДИОДОВ С ПЕРЕКЛЮЧЕНИЕМ 2 КНОПКАМИ

.org 0                 ;Задание нулевого адреса старта программы
rjmp reset          ;Безусловный переход к метке reset
.org 3                ;Задание адреса прерывания по переполнению таймера 0
rjmp timer0_ovf ;Безусловный переход к метке timer0_ovf

reset:
ldi r16, RAMEND ;Загрузка в регистр r16 адреса верхней границы ОЗУ
out SPL, r16       ;Копирование значения из r16 в регистр указателя стека SPL

ldi r16, (1<<CS00)|(1<<CS02);Загрузка двух "1", смещенных на CS00 и CS02
out TCCR0B,r16  ;Копирование значения из регистра r16 в регистр TCCR0B
ldi r17,17    ;Загрузка в регистр r17 - 2х "1" (10001)
out DDRB, r17    ;Копирование из r17 в DDRB (РВ0,РВ4 - выход)
ldi r17,1    ;Загрузка в регистр r17 - "1" (00001)
out PORTB, r17    ;Копирование из r17 в PORTB

sbi PORTB, 1    ;Включение подтягивающего резистора на РВ1 (кнопка SB2)
sbi PORTB, 2    ;Включение подтягивающего резистора на РВ2 (кнопка SB1)

sei                    ;Глобальное разрешение прерываний

main:               ;Основной цикл программы

sbis PINB, 2       ;Если РВ2=0 (кнопка нажата), пропустить след. строку
rcall knop1
sbis PINB, 1       ;Если РВ1=0 (кнопка нажата), пропустить след. строку
rcall knop2
rjmp main         ;Возврат к началу цикла

knop1:
sbic PINB, 2       ;Если РВ2=0 (кнопка нажата), пропустить след. строку
rjmp main
rcall delay         ;Вызов подпрограммы задержки на дребезг контактов
wait:                 ;Цикл ожидания, пока нажата кнопка
sbis PINB, 2      ;Если РВ2=1 (кнопка отпущена), пропустить след. строку
rjmp wait          ;иначе перейти к началу цикла ожидания
rcall delay         ;Вызов подпрограммы задержки на дребезг контактов
sbic PINB, 2      ;Если РВ2=0 (кнопка нажата), пропустить след. строку
ldi r20, (1<<TOIE0) ;Загрузка в регистр r20 "1", смещенной на TOIE0
out TIMSK0,r20    ;Копирование значения из регистра r20 в регистр TIMSK0

rjmp main         ;Возврат к началу цикла

knop2:
sbic PINB, 1       ;Если РВ1=0 (кнопка нажата), пропустить след. строку
rjmp main
rcall delay         ;Вызов подпрограммы задержки на дребезг контактов
wait1:                 ;Цикл ожидания, пока нажата кнопка
sbis PINB, 1      ;Если РВ1=1 (кнопка отпущена), пропустить след. строку
rjmp wait1          ;иначе перейти к началу цикла ожидания
rcall delay         ;Вызов подпрограммы задержки на дребезг контактов
sbic PINB, 1      ;Если РВ1=0 (кнопка нажата), пропустить след. строку
ldi r20, 0        ;Загрузка в регистр r20 "0"
out TIMSK0,r20    ;Копирование значения из регистра r20 в регистр TIMSK0
sbi PORTB, 1    ;Включение подтягивающего резистора на РВ1 (кнопка SB2)
rjmp main         ;Вернуться к метке main

delay:              ;Начало подпрограммы задержки
ldi r18, 255       ;Загрузка значения в регистр r18
ldi r19, 31        ;Загрузка значения в регистр r19
del:                 ;Цикл задержки
subi r18, 1       ;Вычитание 1 из регистра r18
sbci r19, 0       ;Вычитание 0 из регистра r19 с учетом переноса
brcc del           ;Если не было переноса вернуться к метке del
ret                  ;Возврат из подпрограммы

timer0_ovf:      ;Прерывание по переполнению таймера 0
com r17       ;Инверсия регистра r17
out PORTB,r17  ;Копирование из r17 в PORTB
reti                  ;Возврат из прерывания

15 Андрей  
0
Сергей ! Программа работает в Протеусе , но через несколько переключений кнопкамиPB1,PB2 - Протеус выдает ошибку - "invalid opcode 0xffff at
PC=0X006C". Непойму где у меня ошибка.Андрей.  .include "tn13def.inc" ; ФЬЮЗЫ - 9,6 МГЦ И ДЕЛЕНИЕ НА 8 ;МИГАНИЕ 2 ДИОДОВ С ПЕРЕКЛЮЧЕНИЕМ 2 КНОПКАМИ .org 0                 ;Заданиенулевого адреса старта программыrjmp reset          ;Безусловныйпереход к метке reset.org 3                ;Заданиеадреса прерывания по переполнению таймера 0rjmp timer0_ovf ;Безусловный переход к метке timer0_ovf reset:ldi r16, RAMEND ;Загрузка в регистр r16 адреса верхней границы ОЗУout SPL, r16       ;Копированиезначения из r16 в регистр указателя стека SPL  ldi r16, (1<<CS00)|(1<<CS02);Загрузка двух "1",смещенных на CS00 и CS02out TCCR0B,r16  ;Копированиезначения из регистра r16 в регистр TCCR0Bldi r17,17    ;Загрузка в регистрr17 - 2х "1" (10001)out DDRB, r17    ;Копирование изr17 в DDRB (РВ0,РВ4 - выход)ldi r17,1    ;Загрузка в регистрr17 - "1" (00001)out PORTB, r17    ;Копирование изr17 в PORTB   sbi PORTB, 1    ;Включениеподтягивающего резистора на РВ1 (кнопка SB2)sbi PORTB, 2    ;Включениеподтягивающего резистора на РВ2 (кнопка SB1) sei                    ;Глобальноеразрешение прерываний main:               ;Основной циклпрограммы sbis PINB, 2       ;Если РВ2=0(кнопка нажата), пропустить след. строкуrcall knop1sbis PINB, 1       ;Если РВ1=0(кнопка нажата), пропустить след. строкуrcall knop2rjmp main         ;Возврат к началуцикла knop1:sbic PINB, 2       ;Если РВ2=0(кнопка нажата), пропустить след. строкуrjmp mainrcall delay         ;Вызовподпрограммы задержки на дребезг контактовwait:                 ;Цикложидания, пока нажата кнопкаsbis PINB, 2      ;Если РВ2=1(кнопка отпущена), пропустить след. строкуrjmp wait          ;иначе перейти кначалу цикла ожиданияrcall delay         ;Вызовподпрограммы задержки на дребезг контактовsbic PINB, 2      ;Если РВ2=0(кнопка нажата), пропустить след. строкуldi r20, (1<<TOIE0) ;Загрузка в регистр r20 "1",смещенной на TOIE0out TIMSK0,r20    ;Копированиезначения из регистра r20 в регистр TIMSK0 rjmp main         ;Возврат к началуцикла knop2:sbic PINB, 1       ;Если РВ1=0(кнопка нажата), пропустить след. строкуrjmp mainrcall delay         ;Вызовподпрограммы задержки на дребезг контактовwait1:                 ;Цикложидания, пока нажата кнопкаsbis PINB, 1      ;Если РВ1=1(кнопка отпущена), пропустить след. строкуrjmp wait1          ;иначе перейтик началу цикла ожиданияrcall delay         ;Вызовподпрограммы задержки на дребезг контактовsbic PINB, 1      ;Если РВ1=0(кнопка нажата), пропустить след. строкуldi r20, 0        ;Загрузка врегистр r20 "0"out TIMSK0,r20    ;Копированиезначения из регистра r20 в регистр TIMSK0sbi PORTB, 1    ;Включениеподтягивающего резистора на РВ1 (кнопка SB2)rjmp main         ;Вернуться кметке main delay:              ;Началоподпрограммы задержкиldi r18, 255       ;Загрузказначения в регистр r18ldi r19, 31        ;Загрузка значенияв регистр r19del:                 ;Цикл задержкиsubi r18, 1       ;Вычитание 1 изрегистра r18sbci r19, 0       ;Вычитание 0 изрегистра r19 с учетом переносаbrcc del           ;Если не былопереноса вернуться к метке delret                  ;Возврат изподпрограммы timer0_ovf:      ;Прерывание попереполнению таймера 0com r17       ;Инверсия регистраr17out PORTB,r17  ;Копирование из r17в PORTBreti                  ;Возврат изпрерывания

14 Владимир  
0
Разобрался !
поделил на 8 при частоте 1000000 Гц
= 1 Гц частота мигания светодиода.
Изучаю таймер Т1

13 Владимир  
0
Сергей ! снова вопрос:
светодиод мигает через несколько секунд,
очень долго, надо частоту генератора поднять ?

12 Владимир  
0
Точно ошибся, благодарю Сергей !

10 Владимир  
0
Сергей, вопросик: симулятор выдает "Error single step"
в этом коде программы, что делаю не так ?

;####################################
;# Пример PWM с 1-им светодиодом #
;# на ATmega-8 #
;# #
;####################################

.include "m8def.inc"; Присоединение файла описантй
.org 0 ; Задание нулевого адреса старта программы
rjmp reset ; Безусловный переход к метке reset
.org 8 ; Задание адреса прерываний по окончанию преобразованию АЦП
rjmp TIMER1_OVF ; Безусловный переход к метке OVF

reset: ; Начало раздела инициализации контроллера
ldi r16,high(ramend) ; Загрузка в r16 верхней границы ОЗУ
out SPH,r16 ; Копипрование значений в r16 верхней границы ОЗУ
ldi r16,low(ramend)
out SPL,r16
ldi r16, (1<<TOIE1) ;Загрузка в регистр r16 "1", смещенной на TOIE1
out TIMSK,r16 ;Копирование значения из регистра r16 в регистр TIMSK
ldi r16, (1<<CS12)|(1<<CS10);Загрузка двух "1", смещенных на CS00 и CS02
out TCCR1B,r16 ;Копирование значения из регистра r16 в регистр TCCR0B
ldi r17,(1<<2) ;Загрузка в регистр r17 "1", смещенной на 4 разр. влево
out DDRB, r17 ;Копирование из r17 в DDRB (РВ4 - выход)
clr r16
sei

main: ;Основной цикл программы
nop
rcall main

TIMER1_OVF: ;Прерывание по переполнению таймера 0
eor r16,r17 ;Исключающее ИЛИ регистров r16 и r17
out PORTB,r16 ;Копирование из r16 в PORTB
reti ;Возврат из прерывания

11 mimino  
0
Владимир!

Мне думается, что дело вот тут:

main: ;Основной цикл программы
nop
rcall main

Зачем Вы использовали rcall? Тут надо rjmp

8 Владимир  
0
Yes, готово отключение и включение двух светодиодов в противофазе !

9 mimino  
0
Поздравляю! Теперь можно двигаться дальше smile

1-10 11-14
Имя *:
Email *:
Код *:
 
 
Категории раздела
Мои статьи [20]

Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0

Вход на сайт

Поиск

Посетители

Погода
GISMETEO: Погода по г.Мариуполь

 

Copyright MyCorp © 2024
Бесплатный конструктор сайтов - uCoz