Съдържание:

Урок за асемблер на AVR 3: 9 стъпки
Урок за асемблер на AVR 3: 9 стъпки

Видео: Урок за асемблер на AVR 3: 9 стъпки

Видео: Урок за асемблер на AVR 3: 9 стъпки
Видео: AVR Ассемблер. Урок 3. Таймер. Мигалка на таймере. AVR Assembler. Lesson 3. Timer. 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 - LED - R (330) - GND

и това е всичко!

Теорията обаче ще бъде трудна, но …

Стъпка 2: Защо се нуждаем от коментарите и файла M328Pdef.inc?

Мисля, че трябва да започнем, като покажем защо файлът include и коментарите са полезни. Никой от тях всъщност не е необходим и можете да пишете, сглобявате и качвате код по същия начин без тях и той ще работи перфектно добре (въпреки че без файла за включване може да получите някои оплаквания от асемблера - но без грешки)

Ето кода, който ще напишем днес, с изключение на това, че съм премахнал коментарите и файла include:

.устройство 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: sbi 0x0b, 0 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; тактова честота: 16MHz (по избор); **********************************; Функция на програмата: ---------------------; отброява секунди, като мига светодиод;; PD4 - LED - R (330 ома) - GND;; --------------------------------------.nolist.include "./m328Pdef.inc".list; ==============; Декларации:.def temp = r16.def overflows = r17.org 0x0000; памет (компютър) местоположение на манипулатора за нулиране rjmp Нулиране; jmp струва 2 цикъла на процесора, а rjmp струва само 1; така че освен ако не трябва да прескачате повече от 8k байта; имате нужда само от rjmp. Следователно само някои микроконтролери; имат rjmp, а не jmp.org 0x0020; местоположение на паметта на обработчика за препълване на Timer0 rjmp overflow_handler; отидете тук, ако възникне прекъсване на таймер0 препълване; ============ Нулиране: 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; включете LED на PD4 rcall забавяне; закъснението ще бъде 1/2 секунда cbi PORTD, 4; изключете LED на PD4 rcall забавяне; забавянето ще бъде 1/2 секунда rjmp мигане; цикъл обратно към забавяне на старта: clr прелива; задайте преливания на 0 sec_count: cpi препълнения, 30; сравнете броя на преливанията и 30 brne sec_count; разклонение за връщане към sec_count, ако не е равно ret; ако са възникнали 30 преливания, върнете се към мигащ overflow_handler: inc overflows; добавете 1 към променливата за преливане cpi преливания, 61; сравнете с 61 brne PC+2; Програмен брояч + 2 (пропуснете следващия ред), ако не е равно clr препълване; ако възникне 61 преливания, нулирайте брояча на нула reti; връщане от прекъсване

Както можете да видите, коментарите ми са малко по -кратки сега. След като разберем какви команди в набора от инструкции не трябва да обясняваме това в коментарите. Трябва само да обясним какво се случва от гледна точка на програмата.

Ще обсъждаме какво прави всичко това парче по парче, но първо нека се опитаме да получим глобална перспектива. Основната част от програмата работи по следния начин.

Първо задаваме бит 4 на PORTD с "sbi PORTD, 4", което изпраща 1 до PD4, което поставя напрежението до 5V на този щифт. Това ще включи светодиода. След това преминаваме към подпрограмата "забавяне", която отброява 1/2 в секунда (по -късно ще обясним как става това). След това се връщаме към мигащ и изчистен бит 4 на PORTD, който настройва PD4 на 0V и по този начин изключва светодиода. След това отлагаме за още 1/2 секунда и след това отново се връщаме към началото на мигане с "rjmp blink".

Трябва да стартирате този код и да видите, че той прави това, което трябва.

И ето го! Това е всичко, което този код прави физически. Вътрешната механика на това, което прави микроконтролерът, е малко по -ангажирана и затова правим този урок. Така че нека обсъдим последователно всеки раздел.

Стъпка 4:.org Асемблиращи директиви

Вече знаем какво правят директивите.nolist,.list,.include и.def от нашите предишни уроци, така че нека първо да разгледаме 4 -те реда код, които идват след това:

.org 0x0000

jmp Нулиране.org 0x0020 jmp overflow_handler

Изразът.org казва на асемблера къде в "Памет на програмата" да постави следващия израз. Докато вашата програма се изпълнява, "Броячът на програмите" (съкратен като компютър) съдържа адреса на текущия ред, който се изпълнява. Така че в този случай, когато компютърът е на 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 съдържа нашата инструкция да преминем към нашия раздел, който сме обозначили като Reset. Сега виждаме какво прави изявлението ".org 0x0020". Той казва, че искаме инструкцията на следващия ред да бъде поставена на място в паметта 0x0020. Инструкцията, която сме поставили там, е преход към раздел в нашия код, който сме обозначили като "overflow_handler" … сега защо, по дяволите, бихме искали този скок да бъде поставен на място в паметта 0x0020? За да разберем, обръщаме се към страница 65 в листа с данни и разглеждаме таблица 12-6.

Таблица 12-6 е таблица с "Нулиране и прекъсване на вектори" и показва точно къде ще отиде компютърът, когато получи "прекъсване". Например, ако погледнете векторен номер 1. „Източникът“на прекъсването е „RESET“, който е дефиниран като „External Pin, Power-on Reset, Brown-out Reset, and Watchdog system reset“, което означава, ако има някое от тези неща се случват с нашия микроконтролер, компютърът ще започне да изпълнява нашата програма на място в програмната памет 0x0000. Ами нашата директива.org тогава? Е, поставихме команда на място в паметта 0x0020 и ако погледнете надолу таблицата, ще видите, че ако се случи препълване на Timer/Counter0 (идващо от TIMER0 OVF), тя ще изпълни всичко, което е на място 0x0020. Така че винаги, когато това се случи, компютърът ще скочи до мястото, което сме обозначили като „overflow_handler“. Готино нали? Ще видите след минута защо направихме това, но първо нека завършим тази стъпка от урока настрана.

Ако искаме да направим кода си по -чист и подреден, наистина трябва да заменим 4 -те реда, които обсъждаме в момента, със следното (вижте страница 66):

.org 0x0000

rjmp Нулиране; PC = 0x0000 reti; PC = 0x0002 reti; PC = 0x0004 reti; PC = 0x0006 reti; PC = 0x0008 reti; PC = 0x000A… reti; PC = 0x001E jmp overflow_handler: PC = 0x0020 reti: PC = 0x0022 … reti; PC = 0x0030 reti; PC = 0x0032

Така че, ако възникне дадено прекъсване, то просто ще се "оттегли", което означава "връщане от прекъсване" и нищо друго не се случва. Но ако никога не „активираме“тези различни прекъсвания, те няма да бъдат използвани и можем да поставим програмния код на тези места. В настоящата ни програма „blink.asm“ние ще активираме само прекъсването на timer0 overflow (и разбира се прекъсването за нулиране, което винаги е разрешено) и така няма да се притесняваме с останалите.

Как тогава да "активираме" прекъсването при препълване на timer0? … това е темата на следващата ни стъпка в този урок.

Стъпка 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 с PWM". По -специално, отидете на страница 107 от тази глава. Ще видите същото описание на регистъра „Регистър за управление на таймер/брояч B“(TCCR0B), което току -що видяхме в обобщената таблица на регистъра (така че можехме да дойдем направо тук, но исках да видите как да използвате обобщените таблици за бъдещи справки). Информационният лист продължава да дава описание на всеки от битовете в този регистър и какво правят. Засега ще пропуснем всичко това и ще обърнем страницата към Таблица 15-9. Тази таблица показва „Описание на бита за избор на часовник“. Сега погледнете надолу в тази таблица, докато намерите реда, който съответства на битовете, които току -що зададохме в този регистър. Редът казва "clk/1024 (от предсказващо устройство)". Това означава, че ние искаме Timer/Counter0 (TCNT0) да отбележи заедно със скорост, която е честотата на процесора, разделена на 1024. Тъй като имаме нашия микроконтролер, захранван от 16MHz кристален осцилатор, това означава, че скоростта, която нашият CPU изпълнява инструкции е 16 милиона инструкции в секунда. Така че скоростта, която ще отбележи нашият брояч TCNT0, е 16 милиона/1024 = 15625 пъти в секунда (опитайте с различни битове за избор на часовник и вижте какво ще се случи - спомнете си нашата философия?). Нека да запазим числото 15625 в задната част на ума си за по -късно и да преминем към следващите два реда код:

ldi temp, 0b00000001

sts TIMSK0, темп

Това задава 0 -ия бит на регистър, наречен TIMSK0, и изчиства всички останали. Ако погледнете страница 109 в листа с данни, ще видите, че TIMSK0 означава „Регистър на маска за прекъсване на таймер/брояч 0“и нашият код е задал 0 -ия бит, наречен TOIE0, който означава „Таймер/брояч0 Прекъсване на препълване“… Там! Сега виждате за какво става дума. Сега имаме "бит за разрешаване на прекъсване", както искахме от първото решение на нашата снимка в горната част. Така че сега всичко, което трябва да направим, е да активираме „глобални прекъсвания“и нашата програма ще може да реагира на този тип прекъсвания. Скоро ще разрешим глобални прекъсвания, но преди да направим това, може би сте се объркали от нещо.. защо, по дяволите, използвах командата "sts", за да копирам в регистъра на TIMSK0 вместо обичайното "out"?

Когато ме видите да използвам инструкция, която не сте виждали преди, първото нещо, което трябва да направите, е да се обърнете към страница 616 в листа с данни. Това е „Обобщение на набора от инструкции“. Сега намерете инструкцията "STS", която използвах. Той казва, че отнема номер от регистър R (използвахме R16) и местоположение „Съхранявайте директно в SRAM“k (в нашия случай дадено от TIMSK0). И така, защо трябваше да използваме "sts", който отнема 2 цикъла на часовника (вижте последната колона в таблицата) за съхранение в TIMSK0 и се нуждаехме само от "out", който отнема само един тактов цикъл, за съхраняване в TCCR0B преди това? За да отговорим на този въпрос, трябва да се върнем към нашата обобщена таблица на регистъра на страница 614. Виждате, че регистърът TCCR0B е на адрес 0x25, но също и на (0x45), нали? Това означава, че това е регистър в SRAM, но също така е и определен тип регистър, наречен "порт" (или i/o регистър). Ако погледнете обобщената таблица на инструкциите до командата "out", ще видите, че тя приема стойности от "работещите регистри" като R16 и ги изпраща към PORT. Така че можем да използваме „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". Сега слезте надолу по страницата и прочетете описанието на Бит 7…. да! Това е битът за глобално прекъсване. Това е, което трябва да зададем, за да преминем през второто решение в нашата диаграма по -горе и да позволим прекъсвания на таймера/брояча в нашата програма. Така че следващият ред на нашата програма трябва да гласи:

sbi SREG, I

който задава бита, наречен "I" в регистъра SREG. Вместо това обаче използвахме инструкцията

sei

вместо. Този бит се задава толкова често в програмите, че те просто са направили по -прост начин да го направят.

Добре! Сега имаме прекъсвания за препълване, готови за стартиране, така че нашият "jmp overflow_handler" ще се изпълнява всеки път, когато се случи.

Преди да продължим, разгледайте набързо регистъра SREG (Регистър на състоянието), защото той е много важен. Прочетете какво представлява всяко от знамената. По -специално, много от инструкциите, които използваме, ще задават и проверяват тези флагове през цялото време. Например, по -късно ще използваме командата "CPI", което означава "сравнете незабавно". Разгледайте обобщената таблица с инструкции за тази инструкция и забележете колко флагове тя поставя в колоната "флагове". Това са всички флагове в SREG и нашият код ще ги настройва и проверява постоянно. Скоро ще видите примери. И накрая, последният бит от този раздел на кода е:

clr temp

изход TCNT0, temp sbi DDRD, 4

Последният ред тук е доста очевиден. Той просто задава четвъртия бит на регистъра за посока на данни за PortD, което води до излизане на PD4.

Първият задава променливата temp на нула и след това я копира в регистъра TCNT0. TCNT0 е нашият таймер/брояч0. Това го поставя на нула. Веднага след като компютърът изпълни този ред, timer0 ще започне от нула и ще брои със скорост 15625 пъти всяка секунда. Проблемът е следният: TCNT0 е "8-битов" регистър, нали? И така, кое е най-голямото число, което може да съдържа 8-битов регистър? Ами 0b11111111 е така. Това е числото 0xFF. Кое е 255. Виждате ли какво се случва? Таймерът се увеличава 15625 пъти в секунда и всеки път, когато достигне 255, той "прелива" и се връща отново на 0. В същото време, когато се връща към нула, той изпраща сигнал за прекъсване на таймера. Компютърът получава това и знаете какво прави досега, нали? Да. Той отива на място в програмната памет 0x0020 и изпълнява инструкцията, която намери там.

Страхотен! Ако все още сте с мен, значи сте неуморен супергерой! Нека продължим…

Стъпка 6: Манипулатор за препълване

Така че нека приемем, че регистърът timer/counter0 току -що е препълнен. Сега знаем, че програмата получава сигнал за прекъсване и изпълнява 0x0020, който казва на брояча на програмите, компютъра да премине към етикета "overflow_handler", следният код е написан след този етикет:

overflow_handler:

inc прелива cpi прелива, 61 brne PC+2 clr препълва reti

Първото нещо, което прави, е да увеличи променливата "overflows" (което е нашето име за работен регистър с общо предназначение R17), след което тя "сравнява" съдържанието на препълненията с числото 61. Начинът, по който работи инструкцията cpi, е, че просто изважда двете числа и ако резултатът е нула, той задава флага Z в регистъра SREG (казах ви, че ще виждаме този регистър през цялото време). Ако двете числа са равни, тогава флага Z ще бъде 1, ако двете числа не са равни, тогава ще бъде 0.

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

Какъв е нетният резултат от всичко това?

Виждаме, че всеки път, когато има препълване на таймер, този манипулатор увеличава стойността на "преливания" с едно. Така че променливата "преливания" отчита броя на преливанията, когато се появят. Когато числото достигне 61, го нулираме до нула.

Защо по света бихме постъпили така?

Да видим. Спомнете си, че нашата тактова честота за нашия процесор е 16MHz и ние сме го "предварително планирали", използвайки TCCR0B, така че таймерът да брои само със скорост 15625 броя в секунда, нали? И всеки път, когато таймерът достигне брой 255, той прелива. Това означава, че прелива 15625/256 = 61,04 пъти в секунда. Ние следим броя на препълненията с нашата променлива „преливания“и сравняваме това число с 61. Така виждаме, че „преливанията“ще бъдат равни на 61 веднъж на всяка секунда! Така че нашият манипулатор ще нулира „преливанията“до нула веднъж на всяка секунда. Така че, ако просто трябва да наблюдаваме променливата „overflows“и да вземаме под внимание всеки път, когато тя се нулира, щяхме да броим секунда по секунда в реално време (Обърнете внимание, че в следващия урок ще покажем как да получим по-точна забавяне в милисекунди по същия начин, по който работи рутината "забавяне" на Arduino).

Сега сме "обработили" прекъсванията на препълването на таймера. Уверете се, че разбирате как работи това и след това преминете към следващата стъпка, където използваме този факт.

Стъпка 7: Забавяне

Сега, когато видяхме, че нашата манипулатор на прекъсване на таймера за препълване "overflow_handler" ще зададе променливата "overflows" на нула веднъж всяка секунда, можем да използваме този факт за проектиране на подпрограма "забавяне".

Разгледайте следния код под нашето забавяне: label

забавяне:

clr препълнения sec_count: cpi препълнения, 30 brne sec_count ret

Ще извикваме тази подпрограма всеки път, когато се нуждаем от забавяне в нашата програма. Начинът, по който работи, е, че първо задава променливата "overflows" на нула. След това влиза в област, обозначена като "sec_count" и сравнява преливанията с 30, ако те не са равни, се разклонява обратно към етикета sec_count и сравнява отново, и отново и т.н., докато накрая са равни (не забравяйте, че през цялото време това става на нашия таймер манипулатор на прекъсване продължава да увеличава променливите преливания и затова се променя всеки път, когато обикаляме тук. Когато преливането накрая е равно на 30, то излиза от цикъла и се връща там, където сме нарекли закъснение: от. Нетният резултат е закъснение от 1/2 секунда

Упражнение 2: Променете процедурата overflow_handler на следното:

overflow_handler:

inc прелива reti

и стартирайте програмата. Има ли нещо различно? Защо или защо не?

Стъпка 8: Мигайте

И накрая, нека да разгледаме мигащата рутина:

мига:

sbi PORTD, 4 rcall delay cbi PORTD, 4 rcall delay rjmp мига

Първо включваме PD4, след това извикваме подпрограмата за забавяне. Използваме rcall, така че когато компютърът стигне до оператор "ret", той ще се върне на реда след rcall. След това рутината на забавяне за 30 броя в променливата за препълване, както видяхме и това е почти точно 1/2 секунда, след това изключваме PD4, забавяме още 1/2 секунда и след това се връщаме отново към началото.

Нетният резултат е мигащ светодиод!

Мисля, че сега ще се съгласите, че "мигането" вероятно не е най -добрата програма "здравей свят" на асемблер.

Упражнение 3: Променете различните параметри в програмата, така че светодиодът да мига с различна скорост като секунда или 4 пъти в секунда и т.н. Например включете за 1/4 секунда и след това изключете за 2 секунди или нещо подобно. Упражнение 5: Променете битовете за избор на часовник TCCR0B на 100 и след това продължете нагоре по таблицата. В кой момент става неразличим от нашата програма "hello.asm" от урок 1? Упражнение 6 (по избор): Ако имате различен кристален осцилатор, като 4 MHz или 13,5 MHz или каквото и да е друго, сменете своя 16 MHz осцилатор на вашия макет за новия и вижте как това влияе на скоростта на мигане на светодиода. Сега трябва да можете да преминете през точното изчисление и да предвидите как точно ще се отрази на лихвата.

Стъпка 9: Заключение

За онези от вас, които издържат трудностите, стигнали дотук, Честито!

Осъзнавам, че е доста трудно да се занимаваш, когато четеш и гледаш нагоре, отколкото свързваш и експериментираш, но се надявам да си научил следните важни неща:

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

Сега, когато имаме много теория, не можем да напишем по -добър код и да контролираме по -сложни неща. Така че следващият урок ще правим точно това. Ще изградим по -сложна, по -интересна схема и ще я контролираме по забавни начини.

Упражнение 7: „Разчупете“кода по различни начини и вижте какво се случва! Научно любопитство бебе! Някой друг може да измие чиниите правилно. Упражнение 8: Съберете кода, като използвате опцията "-l", за да генерирате файл със списък. Т.е. "avra -l blink.lst blink.asm" и погледнете файла със списъка. Допълнителен кредит: Кодът без коментар, който дадох в началото, и коментираният код, който обсъждаме по-късно, се различават! Има един ред код, който е различен. Можете ли да го намерите? Защо тази разлика няма значение?

Дано се забавлявате! Ще се видим следващия път…

Препоръчано: