Оглавление:

Учебник по ассемблеру AVR 3: 9 шагов
Учебник по ассемблеру AVR 3: 9 шагов

Видео: Учебник по ассемблеру AVR 3: 9 шагов

Видео: Учебник по ассемблеру AVR 3: 9 шагов
Видео: Лекция 4. Архитектура AVR. Ассемблер 2024, Июль
Anonim
Учебник по ассемблеру AVR 3
Учебник по ассемблеру AVR 3

Добро пожаловать в учебник № 3!

Прежде чем мы начнем, я хочу высказать несколько философских соображений. Не бойтесь экспериментировать со схемами и кодом, которые мы создаем в этих уроках. Меняйте провода, добавляйте новые компоненты, извлекайте компоненты, меняйте строки кода, добавляйте новые строки, удаляйте строки и смотрите, что произойдет! Очень сложно что-либо сломать, а если это так, кого это волнует? Ничего из того, что мы используем, включая микроконтроллер, не стоит очень дорого, и всегда полезно видеть, как что-то может выйти из строя. Вы не только узнаете, чего не делать в следующий раз, но, что еще более важно, узнаете, почему этого не следует делать. Если вы чем-то похожи на меня, когда вы были ребенком и приобрели новую игрушку, прошло немного времени, прежде чем вы разобрали ее по частям, чтобы посмотреть, что заставляет ее работать правильно? Иногда игрушка оказывалась непоправимо поврежденной, но ничего страшного. Позволить ребенку исследовать свое любопытство даже до поломки игрушек - вот что превращает его в ученого или инженера, а не в посудомоечную машину.

Сегодня мы собираемся подключить очень простую схему, а затем немного углубимся в теорию. Извините за это, но нам нужны инструменты! Я обещаю, что мы исправим это в уроке 4, где мы будем делать более серьезное построение схемы, и результат будет довольно крутым. Однако все эти уроки вам нужно выполнять очень медленно и созерцательно. Если вы просто пропустите, построите схему, скопируете и вставите код и запустите его, то, конечно, это сработает, но вы ничего не узнаете. Вам нужно продумать каждую строчку. Пауза. Экспериментируйте. Изобретать. Если вы сделаете это таким образом, то к концу 5-го урока вы перестанете создавать классные вещи и больше не будете нуждаться в обучении. В противном случае вы просто смотрите, а не учитесь и создаете.

В любом случае хватит философии, приступим!

В этом уроке вам понадобятся:

  1. ваша макетная доска
  2. светодиод
  3. соединительные провода
  4. резистор от 220 до 330 Ом
  5. Руководство по набору инструкций: www.atmel.com/images/atmel-0856-avr-instruction-se…
  6. Лист данных: www.atmel.com/images/Atmel-8271-8-bit-AVR-Microco…
  7. другой кварцевый генератор (опционально)

Вот ссылка на полную коллекцию руководств:

Шаг 1: Построение схемы

Построение схемы
Построение схемы

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

Подключите светодиод к PD4, затем к резистору 330 Ом, затем к земле. т.е.

PD4 - светодиод - R (330) - GND

и все!

Однако теория будет сложной задачей …

Шаг 2. Зачем нам нужны комментарии и файл M328Pdef.inc?

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

Вот код, который мы напишем сегодня, за исключением того, что я удалил комментарии и включаемый файл:

.device ATmega328P

.org 0x0000 jmp a.org 0x0020 jmp ea: ldi r16, 0x05 out 0x25, r16 ldi r16, 0x01 sts 0x6e, r16 sei clr r16 out 0x26, r16 sbi 0x0a, 0x04 sbi 0x0b, 0x04 b: cbi 0x0 rb, cbi 0x0b, 0x04 rcall c rjmp bc: clr r17 d: cpi r17, 0x1e brne d ret e: inc r17 cpi r17, 0x3d brne PC + 2 clr r17 reti

довольно просто, правда? Ха-ха. Если вы собрали и загрузили этот файл, светодиод будет мигать с частотой 1 мигание в секунду, причем мигание длится 1/2 секунды, а пауза между миганиями - 1/2 секунды.

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

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

Шаг 3: Blink.asm

Вот код, который мы обсудим сегодня:

;************************************

; автор: 1o_o7; Дата:; версия: 1.0; файл сохранен как: blink.asm; для AVR: atmega328p; тактовая частота: 16 МГц (опционально); ************************************; Функции программы: ---------------------; отсчитывает секунды по миганию светодиода;; PD4 - светодиод - R (330 Ом) - GND;; --------------------------------------.nolist.include "./m328Pdef.inc".list; ==============; Объявления:.def temp = r16.def overflows = r17.org 0x0000; память (ПК) расположение обработчика сброса rjmp Reset; jmp стоит 2 цикла процессора, а rjmp - только 1; так что если вам не нужно перескочить более чем на 8 Кбайт; вам нужен только rjmp. Только некоторые микроконтроллеры; иметь rjmp, а не jmp.org 0x0020; место в памяти обработчика переполнения Timer0 rjmp overflow_handler; перейдите сюда, если произойдет прерывание переполнения таймера 0; ============ Reset: ldi temp, 0b00000101 out TCCR0B, temp; установите биты селектора часов CS00, CS01, CS02 на 101; это переводит Timer Counter0, TCNT0 в режим FCPU / 1024; поэтому он тикает на частоте процессора / 1024 ldi temp, 0b00000001 sts TIMSK0, temp; установить бит разрешения прерывания по переполнению таймера (TOIE0); регистра маски прерывания от таймера (TIMSK0) sei; разрешить глобальные прерывания - эквивалент «sbi SREG, I» clr temp out TCNT0, temp; инициализировать таймер / счетчик на 0 sbi DDRD, 4; установить PD4 на вывод; ======================; Основная часть программы: мигает: sbi PORTD, 4; включить светодиод задержки rcall PD4; задержка составит 1/2 секунды cbi PORTD, 4; выключить светодиод на PD4 rcall delay; задержка будет 1/2 секунды мигания rjmp; вернуться к начальной задержке: clr переполняется; установить переполнение равным 0 sec_count: cpi overflows, 30; сравнить количество переполнений и 30 brne sec_count; перейти обратно к sec_count, если не равно ret; если произошло 30 переполнений, вернитесь к миганию overflow_handler: inc overflows; добавить 1 к переменной переполнения cpi overflows, 61; сравните с 61 брнэ ПК + 2; Программный счетчик + 2 (пропустить следующую строку), если не равно переполнение clr; если произошло 61 переполнение, сбросить счетчик на ноль reti; вернуться из прерывания

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

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

Сначала мы устанавливаем бит 4 PORTD с помощью «sbi PORTD, 4», это отправляет 1 на PD4, который устанавливает напряжение 5V на этом контакте. Это включит светодиод. Затем мы переходим к подпрограмме "задержки", которая отсчитывает 1/2 секунды (как она это делает позже). Затем мы возвращаемся к миганию и сбросу бита 4 на PORTD, который устанавливает PD4 на 0 В и, следовательно, отключает светодиод. Затем мы задерживаемся еще на 1/2 секунды, а затем снова возвращаемся к началу мигания с помощью «rjmp blink».

Вы должны запустить этот код и убедиться, что он делает то, что должен.

Вот и все! Это все, что этот код делает физически. Внутренняя механика того, что делает микроконтроллер, немного сложнее, и поэтому мы делаем это руководство. Итак, давайте обсудим каждый раздел по очереди.

Шаг 4. Директивы ассемблера.org

Мы уже знаем, что делают директивы ассемблера.nolist,.list,.include и.def из наших предыдущих руководств, поэтому давайте сначала взглянем на 4 строки кода, которые идут после этого:

.org 0x0000

jmp Сбросить.org 0x0020 jmp overflow_handler

Оператор.org сообщает ассемблеру, где в «Программной памяти» поместить следующий оператор. По мере выполнения вашей программы «Счетчик программ» (сокращенно PC) содержит адрес текущей выполняемой строки. Таким образом, в этом случае, когда компьютер находится на 0x0000, он увидит команду «jmp Reset», находящуюся в этой области памяти. Причина, по которой мы хотим поместить jmp Reset в это место, заключается в том, что при запуске программы или сбросе микросхемы ПК начинает выполнение кода в этом месте. Итак, как мы видим, мы только что сказали ему немедленно «перейти» к разделу «Сброс». Почему мы это сделали? Это означает, что последние две строки выше просто пропускаются! Почему?

Что ж, вот тут-то и становится интересно. Теперь вам нужно открыть программу просмотра PDF-файлов с полной таблицей данных ATmega328p, на которую я указал на первой странице этого руководства (вот почему это пункт 4 в разделе «Вам понадобится»). Если ваш экран слишком мал или у вас уже открыто слишком много окон (как в случае со мной), вы можете сделать то же самое, что и я, и поместить его в Ereader или на свой телефон Android. Вы будете использовать его постоянно, если планируете писать ассемблерный код. Замечательно то, что все микроконтроллеры организованы очень похожим образом, поэтому, как только вы привыкнете читать таблицы данных и кодировать их, вы обнаружите, что сделать то же самое для другого микроконтроллера будет почти тривиально. Итак, мы фактически учимся использовать в определенном смысле все микроконтроллеры, а не только atmega328p.

Хорошо, откройте страницу 18 в таблице данных и взгляните на рисунок 8-2.

Так устроена память программ в микроконтроллере. Вы можете видеть, что он начинается с адреса 0x0000 и разделен на две части; раздел флэш-памяти приложений и раздел загрузочной флэш-памяти. Если вы кратко обратитесь к странице 277, таблица 27-14, вы увидите, что секция флэш-памяти приложения занимает места от 0x0000 до 0x37FF, а секция загрузочной флэш-памяти занимает оставшиеся места от 0x3800 до 0x3FFF.

Упражнение 1. Сколько ячеек в памяти программы? Т.е. преобразовать 3FFF в десятичное и добавить 1, поскольку мы начинаем отсчет с 0. Поскольку каждая ячейка памяти имеет ширину 16 бит (или 2 байта), каково общее количество байтов памяти? Теперь преобразуйте это в килобайты, помня, что в килобайте 2 ^ 10 = 1024 байта. Раздел загрузочной флешки идет от 0x3800 до 0x37FF, сколько это килобайт? Сколько килобайт памяти остается у нас для хранения нашей программы? Другими словами, насколько большой может быть наша программа? Наконец, сколько строк кода мы можем иметь?

Хорошо, теперь, когда мы знаем все об организации флэш-памяти программ, давайте продолжим обсуждение операторов.org. Мы видим, что первая ячейка памяти 0x0000 содержит нашу инструкцию перейти в раздел, который мы обозначили как «Сброс». Теперь мы видим, что делает оператор ".org 0x0020". Он говорит, что мы хотим, чтобы инструкция в следующей строке была размещена в ячейке памяти 0x0020. Инструкция, которую мы поместили туда, представляет собой переход к разделу нашего кода, который мы обозначили как «overflow_handler» … какого черта мы должны требовать, чтобы этот переход был помещен в ячейку памяти 0x0020? Чтобы узнать это, мы обратимся к странице 65 в таблице данных и взглянем на Таблицу 12-6.

Таблица 12-6 представляет собой таблицу «векторов сброса и прерывания», и она показывает, куда именно пойдет ПК, когда он получит «прерывание». Например, если вы посмотрите на вектор номер 1. «Источником» прерывания является «СБРОС», который определяется как «Внешний вывод, сброс при включении питания, сброс при отключении питания и сброс системы сторожевого таймера», что означает, если любое из Если что-то происходит с нашим микроконтроллером, ПК начнет выполнение нашей программы в ячейке памяти программ 0x0000. А как насчет нашей директивы.org? Итак, мы разместили команду в ячейке памяти 0x0020, и если вы посмотрите на таблицу, то увидите, что в случае переполнения таймера / счетчика0 (исходящего из TIMER0 OVF) он выполнит все, что находится в ячейке 0x0020. Поэтому всякий раз, когда это происходит, ПК переходит в точку, которую мы обозначили как «overflow_handler». Круто, правда? Через минуту вы увидите, почему мы это сделали, но сначала давайте закончим этот шаг руководства с отступлением.

Если мы хотим сделать наш код более аккуратным и аккуратным, мы действительно должны заменить 4 строки, которые мы сейчас обсуждаем, следующими (см. Стр. 66):

.org 0x0000

rjmp Reset; ПК = 0x0000 reti; ПК = 0x0002 reti; ПК = 0x0004 reti; ПК = 0x0006 reti; ПК = 0x0008 reti; ПК = 0x000A… reti; PC = 0x001E jmp overflow_handler: PC = 0x0020 reti: PC = 0x0022… reti; ПК = 0x0030 reti; ПК = 0x0032

Таким образом, если происходит данное прерывание, оно просто «reti», что означает «возврат из прерывания», и больше ничего не происходит. Но если мы никогда не «разрешим» эти различные прерывания, то они не будут использоваться, и мы сможем поместить программный код в эти места. В нашей текущей программе "blink.asm" мы собираемся разрешить только прерывание переполнения таймера0 (и, конечно, прерывание сброса, которое всегда включено), и поэтому мы не будем беспокоиться о других.

Как же тогда "разрешить" прерывание переполнения таймера0? … Это тема нашего следующего шага в этом руководстве.

Шаг 5: Таймер / Счетчик 0

Таймер / Счетчик 0
Таймер / Счетчик 0

Взгляните на картинку выше. Это процесс принятия решения «ПК», когда какое-то внешнее влияние «прерывает» выполнение нашей программы. Первое, что он делает, когда получает сигнал извне о том, что произошло прерывание, - это проверяет, установлен ли бит «разрешения прерывания» для этого типа прерывания. Если мы этого не сделали, он просто продолжит выполнение нашей следующей строки кода. Если мы установили этот конкретный бит разрешения прерывания (так, чтобы в этом месте бита была 1 вместо 0), он затем проверит, разрешили ли мы «глобальные прерывания», если нет, он снова перейдет к следующей строке кода и продолжить. Если мы также включили глобальные прерывания, тогда он перейдет в ячейку памяти программ этого типа прерывания (как показано в Таблице 12-6) и выполнит любую команду, которую мы туда поместили. Итак, давайте посмотрим, как мы реализовали все это в нашем коде.

Раздел нашего кода с меткой Reset начинается со следующих двух строк:

Сброс настроек:

ldi temp, 0b00000101 out TCCR0B, temp

Как мы уже знаем, это загружает в temp (то есть R16) сразу следующее за ним число, то есть 0b00000101. Затем он записывает это число в регистр TCCR0B, используя команду "out". Что это за регистр? Что ж, давайте перейдем к странице 614 таблицы данных. Он находится в середине таблицы, в которой перечислены все регистры. По адресу 0x25 вы найдете TCCR0B. (Теперь вы знаете, откуда взялась строка «out 0x25, r16» в моей версии кода без комментариев). По фрагменту кода выше мы видим, что мы установили 0-й бит и 2-й бит и очистили все остальное. Посмотрев на таблицу, вы увидите, что это означает, что мы установили CS00 и CS02. Теперь давайте перейдем к главе таблицы под названием «8-битный таймер / счетчик0 с ШИМ». В частности, перейдите на страницу 107 этой главы. Вы увидите такое же описание регистра «Регистр управления таймером / счетчиком B» (TCCR0B), которое мы только что видели в сводной таблице регистров (чтобы мы могли перейти прямо сюда, но я хотел, чтобы вы увидели, как использовать сводные таблицы для дальнейшего использования). Таблица продолжает давать описание каждого из битов в этом регистре и того, что они делают. Мы пока что пропустим все это и перейдем к Таблице 15-9. В этой таблице показано «Описание бита выбора тактового сигнала». Теперь просмотрите эту таблицу, пока не найдете строку, соответствующую битам, которые мы только что установили в этом регистре. В строке написано «clk / 1024 (от предделителя)». Это означает, что мы хотим, чтобы Timer / Counter0 (TCNT0) работал со скоростью, равной частоте процессора, деленной на 1024. Поскольку у нас есть микроконтроллер, питаемый кварцевым генератором с частотой 16 МГц, это означает, что скорость, с которой наш процессор выполняет инструкции, равна 16 миллионов инструкций в секунду. Таким образом, скорость, с которой будет тикать наш счетчик TCNT0, составляет 16 миллионов / 1024 = 15625 раз в секунду (попробуйте с другими битами выбора часов и посмотрите, что произойдет - помните нашу философию?). Давайте оставим число 15625 в уме на потом и перейдем к следующим двум строкам кода:

ldi temp, 0b00000001

ст. TIMSK0, темп.

Это устанавливает 0-й бит регистра TIMSK0 и очищает все остальное. Если вы посмотрите на страницу 109 в таблице данных, вы увидите, что TIMSK0 означает «Регистр маски прерывания таймера / счетчика 0», а наш код установил 0-й бит, который называется TOIE0, что означает «Разрешение прерывания переполнения таймера / счетчика 0». … Там! Теперь вы понимаете, о чем идет речь. Теперь у нас есть «установленный бит разрешения прерывания», как мы и хотели из первого решения на нашем рисунке вверху. Итак, теперь все, что нам нужно сделать, это включить «глобальные прерывания», и наша программа сможет реагировать на такие типы прерываний. Вскоре мы включим глобальные прерывания, но прежде чем мы это сделаем, возможно, вас что-то смутило … какого черта я использовал команду "sts" для копирования в регистр TIMSK0 вместо обычного "out"?

Каждый раз, когда вы видите, что я использую инструкцию, которую вы раньше не видели, первое, что вам следует сделать, это открыть страницу 616 в таблице данных. Это «Сводка набора команд». Теперь найдите инструкцию "СТС", которую я использовал. В нем говорится, что он берет номер из регистра R (мы использовали R16) и ячейки k «Сохранить прямо в SRAM» (в нашем случае это TIMSK0). Так почему же нам пришлось использовать «sts», который занимает 2 тактовых цикла (см. Последний столбец в таблице) для сохранения в TIMSK0, а нам нужно было только «out», которое занимает только один тактовый цикл, для сохранения в TCCR0B раньше? Чтобы ответить на этот вопрос, нам нужно вернуться к нашей сводной таблице регистров на странице 614. Вы видите, что регистр TCCR0B находится по адресу 0x25, но также и по адресу (0x45), верно? Это означает, что это регистр в SRAM, но это также регистр определенного типа, называемый «портом» (или регистром ввода-вывода). Если вы посмотрите на сводную таблицу инструкций рядом с командой «out», вы увидите, что она берет значения из «рабочих регистров», таких как R16, и отправляет их в ПОРТ. Таким образом, мы можем использовать "out" при записи в TCCR0B и сэкономить тактовый цикл. Но теперь найдите TIMSK0 в таблице регистров. Вы видите, что у него адрес 0x6e. Это выходит за пределы диапазона портов (которые являются только первыми 0x3F местоположениями SRAM), поэтому вам придется вернуться к использованию команды sts и потребовать для этого два тактовых цикла ЦП. Пожалуйста, прочтите примечание 4 в конце сводной таблицы инструкций на странице 615 прямо сейчас. Также обратите внимание, что все наши входные и выходные порты, такие как PORTD, расположены в нижней части таблицы. Например, PD4 - это бит 4 по адресу 0x0b (теперь вы видите, откуда в моем коде без комментариев взялось все содержимое 0x0b!).. Хорошо, быстрый вопрос: вы изменили "sts" на "out" и посмотрите, что бывает? Помните нашу философию! сломать! не верьте мне на слово.

Хорошо, прежде чем мы продолжим, на минутку откройте страницу 19 таблицы данных. Вы видите изображение памяти данных (SRAM). Первые 32 регистра в SRAM (от 0x0000 до 0x001F) являются «рабочими регистрами общего назначения» с R0 по R31, которые мы все время используем в качестве переменных в нашем коде. Следующие 64 регистра - это порты ввода-вывода до 0x005f (то есть те, о которых мы говорили, которые имеют эти адреса без скобок в таблице регистров, которые мы можем использовать команду «out» вместо «sts»). следующий раздел SRAM содержит все остальные регистры в сводной таблице до адреса 0x00FF, и, наконец, остальное - внутренняя SRAM. Теперь быстро, давайте на секунду обратимся к странице 12. Там вы видите таблицу «рабочих регистров общего назначения», которые мы всегда используем в качестве переменных. Вы видите толстую линию между числами от R0 до R15, а затем от R16 до R31? Именно поэтому мы всегда используем R16 как самый маленький, и я подробнее расскажу об этом в следующем уроке, где нам также понадобятся три 16-битных регистра косвенного адреса, X, Y и Z. Я не буду углубляйтесь в это пока, так как нам это сейчас не нужно, и мы уже достаточно увязли здесь.

Переверните одну страницу назад на страницу 11 таблицы данных. Вы увидите диаграмму регистра SREG вверху справа? Вы видите, что 7-й бит этого регистра называется «I». Теперь спуститесь вниз по странице и прочтите описание Bit 7…. ура! Это бит разрешения глобального прерывания. Это то, что нам нужно установить, чтобы пройти второе решение на нашей диаграмме выше и разрешить прерывания переполнения таймера / счетчика в нашей программе. Итак, следующая строка нашей программы должна выглядеть так:

sbi SREG, I

который устанавливает бит "I" в регистре SREG. Однако вместо этого мы использовали инструкцию

sei

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

Хорошо! Теперь у нас есть готовые прерывания переполнения, так что наш "jmp overflow_handler" будет выполняться всякий раз, когда оно возникает.

Прежде чем мы продолжим, взглянем на регистр SREG (регистр состояния), потому что он очень важен. Прочтите, что представляет каждый из флагов. В частности, многие из используемых нами инструкций постоянно устанавливают и проверяют эти флаги. Например, позже мы будем использовать команду «CPI», что означает «немедленное сравнение». Взгляните на сводную таблицу инструкций для этой инструкции и обратите внимание, сколько флагов она устанавливает в столбце «flags». Это все флаги в SREG, и наш код будет их постоянно устанавливать и проверять. Вскоре вы увидите примеры. Наконец, последний фрагмент этого раздела кода:

clr temp

выход TCNT0, темп sbi DDRD, 4

Последняя строчка здесь довольно очевидна. Он просто устанавливает 4-й бит регистра направления данных для PortD, заставляя PD4 быть ВЫХОДНЫМ.

Первый устанавливает переменную temp в ноль, а затем копирует это в регистр TCNT0. TCNT0 - это наш Таймер / Счетчик0. Это обнуляет его. Как только ПК выполнит эту строку, таймер таймера 0 запустится с нуля и будет считать 15625 раз в секунду. Проблема в следующем: TCNT0 - это «8-битный» регистр, верно? Итак, какое наибольшее число может вместить 8-битный регистр? Ну 0b11111111 это оно. Это число 0xFF. А это 255. Итак, вы видите, что происходит? Таймер движется вперед, увеличиваясь 15625 раз в секунду, и каждый раз, когда он достигает 255, он «переполняется» и снова возвращается к 0. В то же время, когда он возвращается к нулю, он отправляет сигнал прерывания переполнения таймера. ПК получает это, и вы уже знаете, что он делает? Ага. Он переходит в ячейку памяти программ 0x0020 и выполняет найденную там инструкцию.

Большой! Если ты все еще со мной, значит ты неутомимый супергерой! Давайте продолжим…

Шаг 6: Обработчик переполнения

Итак, предположим, что регистр таймера / счетчика 0 только что переполнился. Теперь мы знаем, что программа получает сигнал прерывания и выполняет 0x0020, который сообщает Program Counter, PC о переходе к метке overflow_handler, следующий код мы написали после этой метки:

overflow_handler:

inc переполняет переполнение cpi, 61 brne PC + 2 clr переполняет reti

Первое, что он делает, это увеличивает переменную «переполнения» (это наше имя для рабочего регистра общего назначения R17), затем он «сравнивает» содержимое переполнения с числом 61. Инструкция cpi работает так: она просто вычитает два числа, и если результат равен нулю, он устанавливает флаг Z в регистре SREG (я сказал вам, что мы будем видеть этот регистр все время). Если два числа равны, тогда флаг Z будет равен 1, если два числа не равны, то будет 0.

В следующей строке написано «brne PC + 2», что означает «ветвь, если не равно». По сути, он проверяет флаг Z в SREG, и если он НЕ является единицей (т.е. два числа не равны, если бы они были равны, был бы установлен нулевой флаг), ПК переходит на ПК + 2, что означает, что он пропускает следующий строка и переходит прямо к "reti", которая возвращается из прерывания в то место, где оно было в коде, когда прибыло прерывание. Если инструкция brne обнаружит 1 в бите нулевого флага, она не будет разветвляться, а вместо этого просто перейдет к следующей строке, которая приведет к переполнению clr, сбросив его до 0.

Каков чистый результат всего этого?

Мы видим, что каждый раз, когда происходит переполнение таймера, этот обработчик увеличивает значение «переполнения» на единицу. Таким образом, переменная «переполнения» подсчитывает количество переполнений по мере их возникновения. Когда число достигает 61, мы сбрасываем его на ноль.

Зачем нам это делать?

Давайте посмотрим. Вспомните, что наша тактовая частота для нашего процессора составляет 16 МГц, и мы «предварительно масштабировали» ее с помощью TCCR0B, чтобы таймер считал только со скоростью 15625 отсчетов в секунду, верно? И каждый раз, когда таймер достигает счетчика 255, он переполняется. Это означает, что он переполняется 15625/256 = 61,04 раза в секунду. Мы отслеживаем количество переполнений с помощью нашей переменной «overflows» и сравниваем это число с 61. Итак, мы видим, что «переполнение» будет равно 61 раз в секунду! Таким образом, наш обработчик будет сбрасывать «переполнение» в ноль раз в секунду. Поэтому, если бы мы просто отслеживали «переполнение» переменной и записывали каждый раз, когда она сбрасывается до нуля, мы бы считали каждую секунду в реальном времени (обратите внимание, что в следующем уроке мы покажем, как получить более точное задержка в миллисекундах точно так же, как работает процедура «задержки» Arduino).

Теперь мы «обработали» прерывания переполнения таймера. Убедитесь, что вы понимаете, как это работает, а затем переходите к следующему шагу, где мы воспользуемся этим фактом.

Шаг 7: задержка

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

Взгляните на следующий код из-под нашей задержки: label

задерживать:

clr overflows sec_count: cpi overflows, 30 brne sec_count ret

Мы будем вызывать эту подпрограмму каждый раз, когда нам понадобится задержка в нашей программе. Это работает следующим образом: сначала устанавливается значение переменной «overflows» равным нулю. Затем он входит в область с меткой «sec_count» и сравнивает переполнения с 30, если они не равны, он переходит обратно к метке sec_count и сравнивает снова, и снова, и т. Д., Пока они, наконец, не станут равными (помните, что все время это происходит в нашем обработчике прерывания таймера продолжает увеличивать переполнение переменной, и поэтому он меняется каждый раз, когда мы идем сюда. Когда переполнение, наконец, становится равным 30, он выходит из цикла и возвращается туда, куда мы вызвали задержку: от. Конечный результат - это задержка 1/2 секунды

Упражнение 2: Измените процедуру overflow_handler на следующее:

overflow_handler:

inc переполняет Рети

и запускаем программу. Что-нибудь другое? Почему или почему нет?

Шаг 8: Моргай

Наконец, давайте посмотрим на процедуру мигания:

мигать:

sbi PORTD, 4 rcall delay cbi PORTD, 4 rcall delay rjmp blink

Сначала мы включаем PD4, затем вызываем нашу подпрограмму задержки. Мы используем rcall, чтобы, когда ПК переходит к оператору "ret", он возвращается к строке, следующей за rcall. Затем процедура задержки задерживает 30 отсчетов в переменной переполнения, как мы видели, и это почти ровно 1/2 секунды, затем мы выключаем PD4, задерживаем еще на 1/2 секунды и затем снова возвращаемся к началу.

Конечный результат - мигающий светодиод!

Думаю, теперь вы согласитесь, что "blink", вероятно, не лучшая программа "hello world" на языке ассемблера.

Упражнение 3: Измените различные параметры в программе так, чтобы светодиод мигал с разной частотой, например, секунду или 4 раза в секунду и т. Д. Упражнение 4: Измените его так, чтобы светодиод включался и выключался в течение разного времени. Например, включено на 1/4 секунды, а затем выключено на 2 секунды или что-то в этом роде. Упражнение 5: Измените биты выбора часов TCCR0B на 100, а затем продолжите движение вверх по таблице. В какой момент он становится неотличимым от нашей программы "hello.asm" из урока 1? Упражнение 6 (необязательно): если у вас другой кварцевый генератор, например, 4 МГц, 13,5 МГц или что-то еще, замените свой генератор на 16 МГц на макетной плате для нового и посмотрите, как это повлияет на частоту мигания светодиода. Теперь вы должны иметь возможность произвести точный расчет и точно предсказать, как это повлияет на скорость.

Шаг 9: Заключение

Для тех из вас, кто зашел так далеко, поздравляем!

Я понимаю, что это довольно утомительно, когда вы больше читаете и смотрите вверх, чем подключаете и экспериментируете, но я надеюсь, что вы усвоили следующие важные вещи:

  1. Как работает программная память
  2. Как работает SRAM
  3. Как искать регистры
  4. Как найти инструкции и узнать, что они делают
  5. Как реализовать прерывания
  6. Как CP выполняет код, как работает SREG и что происходит во время прерываний
  7. Как делать петли, прыжки и подпрыгивать в коде
  8. Как важно читать даташит!
  9. Как только вы узнаете, как сделать все это для микроконтроллера Atmega328p, изучение любых новых контроллеров, которые вас интересуют, станет относительной прогулкой.
  10. Как преобразовать время процессора в реальное время и использовать его в процедурах задержки.

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

Упражнение 7: «Взломайте» код различными способами и посмотрите, что произойдет! Научное любопытство малышки! Кто-нибудь еще может мыть посуду? Упражнение 8: Соберите код, используя параметр «-l» для создания файла списка. Т.е. "avra -l blink.lst blink.asm" и просмотрите файл списка. Дополнительная благодарность: код без комментариев, который я дал в начале, и код с комментариями, который мы обсудим позже, отличаются! Есть одна строка кода, которая отличается. Вы можете это найти? Почему эта разница не имеет значения?

Надеюсь, вам было весело! Увидимся в следующий раз …

Рекомендуемые: