Програмиране на AVR-контролери
Програмен модел
Аритметическите и логическите операции се изпълняват от така нареченото Аритметично-логическо устройство (АЛУ). То има два входа и един изход. На входовете се въвеждат операндите, а на изхода се формира резултатът. Входовете и изходите са буферирани по време чрез така наречените оперативни регистри. Преди да се изпълни аритметичното действие, операндите трябва да се въведат в тези регистри, от където АЛУ може да ги прочете. Резултатът се записва обикновено в регистъра на един от двата операнда, като старото му съдържание се изтрива.
Посредничеството на оперативните регистри е задължително. Данните могат да се намират в най-различни области от паметта, но за да се обработят, трябва преди това да се запишат в някои от оперативните регистри.
При повечето процесори оперативните регистри са един или два. Наричат се акумулатори. При процесорите AVR те са 32 на брой, което е огромно предимство. Достъпът до тях може да се осъществи чрез име, което има вида R0 до R31. Програмистът избира с кои от тези регистри да работи. Например ако се използват регистрите R5 и R6, фрагментът от програмата, който извършва това действие ще бъде:
ADD R5,R6
Това означава, че процесорът ще събере съдържанията на R5 и R6 и резултатът ще се запише в първия от тях, т.е. в R5. Данните се зареждат в оперативните регистри при изпълнение на съответните инструкции. Те се изпълняват една след друга, като за това следят две устройства в процесора. Едното е регистърът на инструкциите, който е свързан към входа на декодиращо устройство. Постъпилата инструкция се декодира от процесора и вътрешната му структура се конфигурира така, че данните да пристигнат по предназначение и да се извърши съответното действие над тях. Този процес наподобява конфигурирането на Националната ЖП мрежа, за да пристигне даден влак от една гара в друга.
Второто устройство е програмният брояч РС. Той отброява автоматично изпълнението на поредната инструкция от програмата, като всеки път нараства с единица. Благодарение на това се знае във всеки момент коя част от програмата се изпълнява и до къде е достигнал процесът на обработка на информацията.
Всяка инструкция има неповторим двоичен код, дефиниран от фирмата – производител, по който процесорът разпознава предназначението и́. С колкото повече разреди е тази инструкция, толкова повече информация може да се съдържа в нея. При процесорите AVR този код е 16 битов или 32 битов. Част от битовете се използват за така нареченият код на операцията, който определя действието на самата инструкция, а другите битове определят адреса, от където се вземат данните, над които ще се върши съответното действие.
Инструкциите се записват в отделна памет, предназначена само за програмния код. Нарича се програмна памет. Данните, над които се извършват действията и се формират резултатите, се записват в друга памет, наречена памет за данни (RAM), която е 8 битова. Такава архитектура, при която паметта за данни е различна от програмната памет е сравнително нов подход в производството на процесори и се нарича Харвардска архитектура. При по-старите процесори паметта беше обща, като програмистът решава, в коя част от нея да разположи програмата и в коя част данните.
Програмата се въвежда в програмната памет чрез програматор. Тя е от типа FLASH и позволява многократен презапис, като записът не се изтрива при изключване на захранването, т.е. това е енергонезависима памет.
До тук може да се каже, че това е основното ядро или самият процесор. Вътре в чипа обаче има вградени и допълнителни устройства, които при по-старите процесори бяха във вид на отделни интегрални схеми и образуваха така наречената периферия. Процесорите плюс периферните модули е прието да се наричат контролери. Различните контролери от серията AVR притежават различна част от всички възможни периферни модули. Допустимо е към тях да се добавят и външни периферни модули.
В рамките на единна базова архитектура AVR-микроконтролерите се делят на три класа:
Classic AVR —със средна производителност. Притежават FLASH програмна памет с обем 2–8 Кбайта, ЕEPROM за данни с обем 64–512 байт, SRAM 128–512 байт;
mega AVR с висока производителност за сложни приложения, изискващи голям обем на паметта Обемът на FLASH паметта е 4–128 Кбайт. Паметта за данни ЕEPROM и с обем 64–512 Кбайт, SRAM 2–4 Кбайт, SRAM 4 Кбайт. Има вграден 10-разреден 8-канален АЦП, апаратен умножител 8×8;
tiny AVR — евтини микроконтролери с 8-извода и ограничени възможности.
3.2. Статично ОЗУ за данни
Паметта за данни е организирана като непрекъсната поредица от клетки, но в тях са оформени отделни области със специфично предназначение. Адресите от 0х00 до 0×0F са предназначени за оперативни регистри от R0 до R15. Данните могат да се записват само ако се прехвърлят от друг регистър. След това АЛУ може да извършва над тях аритметически и логически операции. Записът може да се извърши чрез инструкцията MOV. Например MOV R7,R25 ще прехвърли съдържанието на регистър R25 в регистър R7. Този начин на въвеждане на данни в регистрите се нарича регистрова адресация.
Адресите от 0х10 до 0×1F са предназначени за регистрите от R16 до R31. При тях също може да се използва регистрова адресация, но може да се използва и така наречената непосредствена адресация, където вторият параметър е константа. За да разпознае (декодира) процесорът това, кодът на операцията е друг, и самата инструкция има различен вид. Например LDI R16,0×3E ще запише в регистър R16 непосредствено числото 0х3Е. Това число може също да се прехвърли след това в регистър R23 чрез инструкцията MOV R23,R16.
Още по-големи възможности имат регистрите с от R26 до R31. Те могат да се използват наравно с другите оперативни регистри, но могат да образуват три двойки регистри, които позволяват да се адресират горната област на паметта за данни. За тях ще стане въпрос по-късно.
Следващите адреси от 0×20 до 0×5F са предназначени за така наречените регистри за вход/изход. Чрез тях се изпращат данни към устройствата за вход/изход на информация или се получават данни от същите тези устройства. Ще бъдат изучавани постепенно. Към тях е включен обаче и един много важен регистър, наречен флагов регистър, който се означава обикновено със SREG. Съдържа 8 независими запомнящи елемента, наречени флагове. Предназначението на всеки от тях е регламентирано от производителя и заема състояние 1 или 0 в зависимост от това, дали е настъпило или не очакваното събитие. Чрез тези флагове се въвеждат разклонения в програмите, като се следят определени логически условия. Намира се на адрес 0×3F, но при някои контролери може да бъде на адрес 0×5F. Битовете му се означават с букви и са подредени в следната последователност I_Т_H_S_V_N_Z_C. Значението им е следното:
I - Флаг за прекъсване. Когато е в състояние 0, прекъсванията са забранени, а когато е 1, програмата може да се прекъсва. Задава се от програмиста.
T - В този бит може да се копира всеки бит от оперативните регистри, което програмистът използва, както намери за добре
H - Става 1 автоматически, когато при събиране е възникнал пренос между тетрадите, т.е. между четвъртия и петия разред от съответния оперативен регистър. Нарича се полупренос. Ако няма полупренос, присвоява 0.
S - Бит за знак. Следи едновременно флага N и флага V, като изпълнява между тях операцията сума по модул 2.
V - Това е флаг за препълване. Използва се за откриване на грешки при предаване на данни. Един от методите за това е, чрез допълнение до две.
N - Този флаг автоматично става 1, когато резултатът от последната операция е отрицателен.
Z - Този флаг става 1, когато резултатът от последната операция е нулев.
C - Присвоява 1, ако при последната операция възникне пренос от осмия разред навън.
Всички регистри за вход изход могат да се четат или да се записва в тях чрез инструкциите OUT и IN. Често в програмата трябва да се съхрани временно съдържанието на флаговия регистър, след което да се възстанови. В следващият пример е илюстрирано, как флаговият регистър се съхранява в оперативния регистър R7 и след това се възстановява запомненото му съдържание
.def SREG=0×3F ;Дефинира идентификатор
;……. Код на програмата ……
IN R7,SREG ; Записва SREG в R7
;……Код на програмата ……
OUT SREG,R7 ; Записва R7 в SREG
;……..Код на програмата ……
На първия ред е дефиниран идентификатор на флаговия регистър чрез директивата .def. Директивите са служебни думи в езика за програмиране, които не водят до генериране на изпълним код, както инструкциите, а определят условията, при които ще протече изпълнението на програмата. При асемблиране, текстовият файл се прочита от компилатора и се преобразува в изпълним двоичен код. На местата, където той среща SREG, навсякъде ще го замени с 0×3F.
На всеки ред програмистът може да поставя коментар, който задължително започва със символа точка и запетая. Когато компилаторът срещне при асемблирането този символ, пропуска всичко, написано след него до края на реда.
3.3. Примерни програми
3.3.1. Събиране и изваждане на еднобайтови числа
При реални условия двата операнда, над които ще се извършва действието, могат да се задават от устройство за вход/изход. За простота на примерните програми обаче операндите ще бъдат зададени във вид на константи. Нека първият операнд е 0х04, а вторият 0х03. За да се намери техния сбор, може да се изпълни следният програмен код
LDI R16,0×04
LDI R17,0×03
ADD R16,R17
Инструкцията LDI записва в регистъра, указан като първи параметър, константата, указана като втори параметър. Първата инструкция зарежда в регистър R16 константата на първия операнд, а втората зарежда константата на втория операнд в регистър R17. Инструкцията ADD събира R16 и R17, като резултатът остава в R16, от където може да се прочете.
- LDI R17,0×03
- CLC
- ADC R16,R17
Както е известно, изваждането на двоични числа може да се извърши чрез събиране на числа с алгебричен знак, представени в допълнителен код. Конструкторите на контролера са създали удобство, като са предвидили инструкциите SUB и SBC, които автоматично преобразуват в допълнителен код операндите и извършват действието. Първата не отчита състоянието на флага С, а втората го отчита. Вместо да се говори за пренос, състоянието на С при изваждане се нарича заем.
Ето как би изглеждала програмата за изваждане:
- LDI R16,0×07
- LDI R17,0×03
- CLC
- SBC R16,R17
Важно при изваждане е да се помни от кой регистър се изважда, защото разместителният закон тук не важи. Винаги се изважда от първия регистър и в него се записва резултата.
3.3.2. Аритметични действия над многобайтови числа
Макар и 8 битов, процесорът може да обработва неограничено големи числа. Действията се извършват байт по байт, като се започне от най-младшия и се върви към старшите. Нека са дадени числата 0х13Е8 и 0х2СВ6, и нека се търси техния сбор. За илюстрация тези числа ще бъдат преобразувани в двоичен код и събирането ще бъде извършено ръчно
старши младши
00010011 11101000
00101100 10110110
01000000 [1] 10011110
При събирането възникна пренос между старшия и младшия байт. Този пренос се записва автоматично във флага С и трябва да се добави при сумирането със следващия байт. Следователно ако се съставя програма за събиране на многобайтови числа, най-младшите байтове на операндите ще се съберат без отчитане на пренос, а при следващите байтове задължително ще се използва инструкцията ADC. Една примерна програма за събиране на двубайтови числа би изглеждала така:
- LDI R16,0×43 ;Старши байт първи операнд
- MOV R0,R16
- LDI R16,0×0C ;Младши байт първия операнд
- MOV R1,R16
- LDI R16,0xB8 ;Старши байт втори операнд
- MOV R2,R16
- LDI R16,0xAE ;Младши байт втори операнд
- MOV R3,R16
- ADD R1,R3 ;Младши байт на резултата
- ADC R0,R2 ;Старши байт на резултата
Изваждането може да се извърши по аналогичен начин, като ADD и ADC се заменят съответно със SUB и SBC. За изваждане специално на двубайтови числа има предвидена обаче много удобна инструкция SUBI, която работи само с константи, не по-големи от 65535. В следващия фрагмент от програма ще бъде илюстрирано използването иґ. Нека първият операнд е 0х10ЕА, а вторият е десетичната константа 1000. Първият операнд трябва да се въведе в два регистъра и нека това са R16 и R17.
- LDI R16,0×10
- LDI R17,0xEA
- SUBI R17,LOW(1000)
- SUBI R16,HIGH(1000)
Директивата LOW(1000) означава, че става въпрос за младшия байт на константата 1000, докато HIGH(1000) работи със старшия байт на тази константа.
Когато става въпрос за числа с голям брой байтове, действието може да се повтори, като се напише програмен код за обработването на всеки байт, но тогава програмата става много дълга и програмната памет ще се използва нерационално. Съществува друг, по-интелигентен подход, в който данните се записват в последователни клетки от горната област на RAM паметта, наречена SRAM и достъпът до тях се извършва чрез индексиране. За целта обаче трябва да бъдат разгледани допълнителните възможности на регистрите от R26 до R31.
3.3.Достъп до адресите на SRAM
SRAM памет за данни заема адресите от $0060 нататък и достига до адрес, определен от наличната памет за съответния контролер. Един възможен начин на достъп е чрез така наречената пряка адресация. За четене от паметта се използва инструкцията LDS. Първият иґ параметър е оперативният регистър, в който трябва да се запишат данните, а вторият параметър е адресът на клетката, от която трябва да се вземат.
За запис в клетка от паметта се използва по аналогичен начин инструкцията STS Например, нека чрез тези две инструкции данните от клетка с адрес 0х0060 да се прехвърлят в клетка с адрес 0х0063. Фрагментът от програмата би изглеждал така
- LDS R16,0×0060
- STS 0×0063,R16
Другият начин е, чрез така нареченото косвено адресиране. Достъпът до клетките се осъществява чрез три адресни регистъра, означавани съответно като регистри X,Y и Z. Това са 16 битови регистри, но физически те не съществуват, а се образуват от двойките оперативни регистри, съответно R26:R27, R28:R29 и R30:R31 Регистърът с по-малък номер се използва за младшия байт на адреса.
Когато програмистът иска да се обърне към клетка от оперативната памет, той трябва да запише адресът на същата клетка в един от адресните регистри. Четенето от клетката се извършва от инструкцията LD. Първият иґ параметър е регистърът, в който трябва да се прехвърлят прочетените данни, а вторият е символът X, Y или Z , в зависимост от това, кой адресен регистър се използва. Записът в клетка от паметта се извършва чрез инструкцията ST, при която местата на параметрите са разменени. Например
- LDI R26,0×60 ;Младши бит на адреса
- CLR R27 ;Старшият бит е 00
- LD R16,X ;Прочита клетка 0х0060
- INC R26 ;Позиционира следващата клетка
- ST X,R16 ;Записва на адрес 0х0061
В примера клетка 0х0061е адресирана, като съдържанието на регистър R26 е увеличено с 1 чрез инструкцията INC. Този похват в програмирането се налага много често и за това е предвидена възможност нарастването с 1 да става автоматично. За целта след символа на съответния регистър се поставя знакът плюс. Ако адресът трябва да се изменя в низходящ ред , се поставя знак минус преди символа на съответния оперативен регистър.
В следващия пример е илюстрирано прехвърлянето на съдържанията на пет клетки от паметта от едно място на друго. Използвана е и директивата .def за дефиниране на идентификатори на регистрите, с което прегледността на програмата се подобрява.
.def XL=R26
.def XH=R27
.def ZL=R30
.def ZH=R31
LDI XL,0×60 ;Начален адрес на първата област
CLR XH
LDI ZL,0×6C ;Начален адрес на втората област
CLR ZH
LD R16,X+ ;Първи транш
ST Z+,R16
LD R16,X+ ;Втори транш
ST Z+,R16
LD R16,X+ ;Трети транш
ST Z+,R16
LD R16,X+ ;Четвърти транш
ST Z+,R16
LD R16,X+ ;Пети транш
ST Z+,R16
Вместо два адресни регистъра, може да се използва един, а достъпът до двете области от паметта да се извършва чрез така нареченото отместване. Този вид адресиране се нарича индексно адресиране. Четенето и записът в този случай се извършва с инструкциите LDD и STD, а в регистрите Y или Z се записва така наречения базов адрес. Регистър Х не позволява този вид адресиране.
В следващия пример е повторена програмата от предишния пример, като е използван един адресен регистър и отместване на 5 адреса след базовия адрес
.def ZL=R30
.def ZH=R31
LDI ZL,0×60 ;Начален адрес на първата област
CLR ZH
LD R16,Z ;Първи транш
STD Z+5,R16
INC ZL
LD R16,Z ;Втори транш
STD Z+5,R16
INC ZL
LD R16,Z ;Трети транш
STD Z+5,R16
INC ZL
LD R16,Z ;Четвърти транш
STD Z+5,R16
INC ZL
LD R16,Z ;Пети транш
STD Z+5,R16
INC ZL
В регистър Z е записан базовият адрес на първата област, от която ще се четат данните. Втората област, в която ще се прехвърлят, е отместена на 5 адреса след нея. При първия транш се четат данните от адрес, който се сочи от регистър Z и се записват в R16. След това STD Z+5,R16 изпраща данните на 5 адреса след този, който е записан в регистър Z. Преминаването към следващия транш е осъществено чрез увеличаване съдържанието на регистър ZL с единица.
3.4.Приложение на флаговия регистър
Както беше споменато, отделните битовe I T H S V N Z C на флаговия регистър реагират при настъпване или не на определени събития. Изключение правят двата най-старши бита, които се задават от програмиста. При контролерите AVR са предвидени набор от инструкции, които да реагират на състоянието на даден бит или комбинация. Първите две букви на всяка от тях започват с буквите BR (Branch), което означава клон, разклонение. Следващите две букви се определят от съкращението на думите, определящи действието.
Например BRNE означава, че инструкцията ще извърши разклонение, ако резултатът от сравнението на две величини определя, че те не са еквивалентни. Сравнението винаги се извършва чрез изваждане. Двете величини са еквивалентни, ако резултатът от това действие е равен на нула. Следователно BRNE извършва преход, ако резултатът от последното действие не е нула.
Друг пример са инструкциите BRPL и BRMI, които извършват разклонение в програмата съответно при положителен или отрицателен резултат от предишната операция. Те следят флага N.
С тези уговорки може вече да се осмисли достъпът до последователни клетки от SRAM чрез организиране на цикъл. Може да се използва както непряко, така и индексно адресиране. Организирането на цикъл изисква някои правила, които трябва да се спазват рутинно. Началото на цикъла се фиксира с етикет, който представлява символен низ. Той може да се състои само от букви или от букви и цифри, но винаги трябва да започва с буква и да завършва с двоеточие. След това се изрежда последователност от инструкции, които определят действието на цикъла. Накрая следва инструкция за преход, която следи някакво логическо условие. Ако то е изпълнено, процесът се връща към мястото от програмата, където е поставен етикета, а ако не е изпълнено, преход не се осъществява и се изпълнява следващата инструкция. Едно пълно завъртане на цикъла се нарича итерация.
Ще бъде разгледан пример за прехвърляне на данни от една област на паметта в друга.
.def XL=R26
.def XH=R27
.def ZL=R30
.def ZH=R31
.def BUF=R7
LDI XL,0×75
LDI XH,0×00 ;X сочи адрес 0×0075
LDI ZL,0×65
LDI ZH,0×00 ;Z сочи адрес 0×0065
LDI R20,5 ;Брояч на итерациите до 5
LABEL1: ;Етикет за начало на цикъла
LD BUF,-X
ST -Z,BUF
DEC R20
BRNE LABEL1
В тази програма е дефиниран идентификатор BUF за прегледност, който съответства на R7. В R20 е организиран брояч. След всяка итерация DEC_R20 намалява съдържанието на брояча с единица. Той ще се нулира на петото прехвърляне и тогава BRNE няма да предизвика преход, т.е. изпълнението на програмата ще се прекрати.
Вместо да се организира брояч на циклите в отделен регистър, може да се използва един от двата адресни регистри, като се следи моментът, когато той ще достигне определено число. Инструкцията за сравнение с константа е CPI. Ето как би изглеждала преработена програмата от горния пример с индексно адресиране:
.def ZL=R30
.def ZH=R31
.def BUF=R6
LDI ZL,0X60
CLR ZH
L1:
LD BUF,Z+
STD Z+0×0F,BUF
CPI ZL,0×65
BRNE L1
Отместването в случая е 0х0F.
3.5. Стек
Програмистът винаги се стреми да раздели една сложна задача на подзадачи и да ги решава поотделно. Подзадачите в програмирането се наричат подпрограми и представляват относително самостоятелна част от една голяма програма, която може да се изпълнява отделно и да се вмъква на различни места в главната програма. Извикването на подпрограмите се извършва с някоя от инструкциите CALL, ICALL или RCALL.
При обръщането към подпрограма се налага да бъде съхранено съдържанието на програмния брояч, за да може изпълнението на главната програма да продължи след приключване действието на подпрограмата. Инструкцията CALL автоматично съхранява съдържанието на програмния брояч в стека. При начално установяване обаче указателят на стека SP е нулиран, т.е. върхът на стека е 0000. За това винаги, когато в програмата ще се използват подпрограми, стекът трябва да се инициализира.
При контролерите AVR стекът се организиран в паметта за данни.Удобно е, указателят на стека да се зареди с последния адрес от паметта. При запис в стека този адрес ще намалява с единица, а при четене, ще се увеличава.
При различните контролери последният адрес зависи от наличната памет. За да се създаде универсалност т.е. програма, писана за един процесор, да може бързо и лесно да се преработи за друг, е удобно да се използват предвидените в Studio_4 файлове с дефиниции. Характерните адреси са дефинирани с идентификатори, които са еднакви за всички контролери, но за всеки контролер имат еквивалентна числена стойност на адреса. За контролера ATmega16 файлът е m16def.inc. Например LOW(RAMEMD) означава младшия байт на адреса на последната клетка на паметта, а HIGH(RAMEND) означава старшият байт на последния адрес. Тези идентификатори са дефинирани за конкретен адрес във файла m16def.inc.
Инициализирането на стека става със следния програмен фрагмент.
.include “m16def.inc”
LDI R16,LOW(RAMEND)
OUT SPL,R16
LDI R16,HIGH(RAMEND)
OUT SPH,R16
………………………………….
Най-напред са зарежда в R16 младшият байт на адреса и се изпраша в SPL. След това в R16 се зарежда старшият байт и се изпраща в SPH. Ако това не се направи, програмата няма да работи правилно. SPL и SPH са дефинирани в m16def.inc
Ще бъде разгледан пример за използване на подпрограма и инициализирането на стека. Задачата ще бъде, едно 8 битово число да се преобразува в десетичен код. Числото трябва да бъде зададено някъде. Нека това е първата клетка от паметта за данни с адрес 0х0060. Оттам то ще бъде прочетено и ще бъде заредено в оперативен регистър от процесора. Нека това е регистър R5. Обработването ще се извърши по следния начин.
От зададеното число ще започне циклично изваждане на числото 100 до тогава, докато не се получи отрицателен остатък, след което процесът ще се върне с една стъпка назад, и ще се възстанови последният положителен остатък. Итерациите ще се броят, като при всяко изваждане някакъв регистър нараства с единица. Нека този брояч е организиран в R17. При отрицателен остатък броят на стотиците ще се получи, като R17 се намали с 1, т.е. връща се с една стъпка назад.
По аналогичен начин се постъпва и с десетиците, и накрая с единиците. Предвижда се резултатите да се изпращат в следващите клетки от SRAM с адреси 0х0061, 0х0062 и 0х0063. В тези три клетки ще се поместят десетичните цифри на десетичния еквивалент на числото. Програмата би изглаждала така:
.include “m16def.inc”
;Инициализиране на стека
LDI R16,LOW(RAMEND)
OUT SPL,R16
LDI R16,HIGH(RAMEND)
OUT SPH,R16
;Инициализиране на адресен регистър
LDI XL,0×60
CLR XH
LD R5,X+ ;Въвеждане в оперативен регистър
LDI R16,(100) ;Преброяване на стотиците
CALL PP1 ;Извикване на подпрограмата РР1
LDI R16,(10) ;Преброяване на десетиците
CALL PP1 ;Извикване на подпрограмата РР1
LDI R16,1 ;Преброяване на единиците
CALL PP1 ;Извикване на подпрограмата РР1
JMP END ;Скок към края на програмата
PP1: ;Начало на подпрограматаРР1
CLR R17 ;Нулира брояча на циклите
PP1L1: ;Начало на цикъла
MOV R6,R5 ;Запомня регистъра
INC R17 ;Отброява първата итерация;
SUB R5,R16 ;От R5 изважда R16
BRPL PP1L1 ;Ако резултатът е положителен
MOV R5,R6 ;Връща стъпка назад
DEC R17 ;Връща стъпка назад
ST X+,R17 ;Записва резултата
RET ;Връщане от подпрограма.
END:
Подпрограмата започва от мястото, където е поставено PP1: и завършва с RET. Тази инструкция означава “връщане от подпрограма” и винаги стои последна. Задачата и е да възстанови съдържанието на програмния брояч, за да продължи основната програма.
Действието на програмата е следното.
След инициализиране на стека, се инициализира адресен регистър и е избрано това да бъде регистър Х. Задава му се начален адрес 0х0060. На този адрес предварително трябва да е записано числото, чийто десетичен еквивалент се търси.
Чрез LD R5,X+ съдържанието му се зарежда в оперативния регистър R5, като адресният регистър нараства с единица и сочи вече адресът 0х0061, където ще се записва цифрата на стотиците.
В регистър R16 се записва константата 100 в десетичен формат. Това става, като 100 се загради в скоби. В противен случай трябва да се запише шестнайсетичният еквивалент на числото 100, който е 0х64. В средата на Studio_4 се извършва преобразуването автоматично.
Следва извикването на подпрограмата. Изпълнението и започва с инструкцията CLR R17, която нулира регистъра R17, защото е предвидено в него да се организира брояч на итерациите. Следва цикъл, чиято първа итерация се отброява чрез INC_R17, при което R17 нараства с 1. Следва изваждане от съдържанието на регистър R5 съдържанието на R16 с SUB_R5,R16 и резултатът остава в R5. Преди това обаче се запомня старото съдържание на R5, защото ако се получи отрицателен резултат, трябва да се възстанови последният положителен остатък.
На следващ етап се проверява логическото условие, дали полученият резултат е все още положителен. Ако е, се извършва преход към мястото, където е поставен етикет PP1L1: и процесът се повтаря.
Ако не е положителен, преход не се извършва, а се изпълнява следващата инструкция. Тя е MOV_R5,R6, която възстановява последният положителен остатък. Броячът на итерациите също трябва да се върне с една стъпка назад, защото той е нараснал преди проверката за положителен резултат. За това е поставена инструкцията DEC_R17, която намалява R17 с единица. Накрая съдържанието на R17 се изпраща на адрес 0х0061. Следва връщане към основната програма.
Обработването на десетиците започва с LDI_R16,(10). Отново се извиква подпрограмата чрез CALL_PP1, като този път в R16 е числото 0х0А. Извършват се същите действия, след което в основната програма се зарежда R16 с 1 и подпрограмата се вика трети път.
Важен момент е начинът, по който завършва програмата. Накрая е поставена инструкцията JMP_END, която извършва безусловен преход към мястото, където е поставен етикет END. Ако това действие не се предвиди, ще започне да се изпълняват редовете от подпрограмата, без някой да я е викал за четвърти път и ще настъпи грешка.
3.6. EEPROM
Това е енергонезависима памет, която може да се записва и чете от процесора, но може записът да се направи предварително чрез програматор. Допустимо е самопрограмиране, т.е. чрез изпълнение на предварителна програма от самия контролер, която да записва в клетките на EEPROM. Предназначена е за съхранение на таблици, константи или резервен програмен код.
Достъпът до клетките на EEPROM се извършва чрез посредничеството на три регистъра – адресен регистър, регистър за данни и управляващ регистър. Адресният регистър е EEAR. При по-мощните процесори той е 16 битов и се образува от два байта EEARL и EEARH. Записаното в него число определя адресът на клетката, която ще бъде достъпна за четене или запис.
Това, дали ще бъде достъпна за четене или за запис, зависи от състоянието на управляващия регистър EECR. Той е 8 битов, но се използват само младшите четири разреда. Режимът “четене” се определя от състоянието на най-младшия бит. Когато той е 1, данните от адресираната клетка постъпват в регистъра за данни EEDR, от където чрез инструкцията IN могат да се изпратят в оперативен регистър. За отбелязване е, че след прочитането на дадена клетка управляващият регистър се нулира автоматично и при следващото четене трябва отново да се запише 1 в най-младшия му бит. Нулирането се прави с цел, да не се извършва случаен запис, при което да се повреди информацията.
Когато трябва да се записва в клетката с избрания адрес, данните трябва да се изпратят в регистъра за данни EEDR. Това още не означава, че записът ще бъде извършен. За целта трябва да се запишат единици в битовете 1 и 2 на EECR. Бит 1 се нулира автоматично след 4 такта от програмното му установяване. Следователно преди всеки запис или четене, съдържанието на EECR трябва да се актуализира.
Бит 3 от EECR е предназначен за управление на прекъсването. Когато във флаг I от SREG и третия бит на управляващия регистър се запише 1, е разрешено прекъсването на достъпа до EEPROM. Ако тези два бита са нули, прекъсването е забранено. Заявка за прекъсване се прави автоматично след всяко нулиране на втория бит на EECR.
Следващият пример демонстрира записът и четенето от EEPROM. Дефинирани са две подпрограми – едната запис WTITE, а другата за четене. READ. Щом се използват подпрограми, стекът трябва непременно да бъде инициализиран, което е направено още в началото.
include “m16def.inc”
LDI R16,LOW(RAMEND) ;Инициализиране на стека
OUT SPL,R16
LDI R16,HIGH(RAMEND)
OUT SPH,R16
CLR R16 ;Задава старшия байт на адреса от EEPROM
OUT EEARH,R16
LDI XL,0×60 ;Начален адрес на SRAM
CLR XH
CALL WRITE ;Извиква подпрограмата за запис
LDI XL,0×70 ;Задава нова стойност на регистър Х
CALL READ ;Извиква подпрограмата за четене
JMP END ;Край на програмата
WRITE: ;Подпрограма за запис
LDI R17,0×06 ;Код за инициализиране на EECR
LDI R20,0×05 ;Брояч на 5 итерации
L1: OUT EEARL,R20 ;Младши байт на адреса
LD R16,X+ ;Чете клетка от SRAM
OUT EEDR,R16 ;Зарежда съдържанието и в EEDR
OUT EECR,R17 ;Разрешава записа
DEC R20 ;Една итерация е отработена
BRNE L1 ;Дали това е петата итерация
RET ;Връщане от подпрограмата
READ: ;Подпрограма за четене
LDI R17,0×01 ; Код за инициализиране на EECR
LDI R20,0×05 ;Брояч на 5 итерации
L2: OUT EEARL,R20 ;Младши байт на адреса
OUT EECR,R17 ;Разрешава четенето
IN R16,EEDR ;Прехвърля данните от EEDR в R16
ST X+,R16 ;Изпраща данните от R16 в SRAM
DEC R20
BRNE L2
RET
END:
И при двете подпрограми в R17 се записват константи. При подпрограмата за запис това е 0х06, защото първият и вторият бит са единици. При подпрограмата за четене се записва 0х01, защото нулевият бит трябва да е 1.
И при двете подпрограми е организиран брояч на итерациите до 5, защото е прието, че да са прехвърля съдържанието на 5 клетки. Едновременно с това съдържанието на брояча се използва и като младши байт на адреса от EEPROM, като адресирането започва от 0х0005 в низходящ ред. Процесът се прекратява, когато R20 стане 00, а адресът 0х0000.
Записът на данните трябва да се извърши предварително в SRAM от адрес 0х0060. Програмата Studio позволява това. Тези данни се изпращат чрез подпрограмата WRITE в EEPROM. След това подпрограмата за четене ги прочита от EEPROM и ги изпраща отново в SRAM, но в област от 0х0070 нагоре.
Сходни статии:
- По-важни алгоритми Програма за преобразуване на еднобайтово число в допълнителен код Инструкцията NEG преобразува в допълнителен код, като инвертира всички 8 бита на числото и добавя единица. За знак използва флага N...
- Програми за управление Осъществяване на закъснение Едночиповите микрокомпютри се използват обикновено за управление на някакви обекти. Аритметичните действия, които могат да извършват , се използват именно за целите на управлението, а не за...
- Изваждане на BCD числа За изваждане на BCD числа не могат да се използват инструкциите SUB, SBC, SBCI, защото код BCD и двоичният код са съвсем различни неща. Тук ще бъде използван алгоритъм, който...
- Програмиране в C и C++ Кодирането или съставянето на програмата е реализация на алгоритмите чрез език за програмиране. Езиците за програмиране от високо ниво, какъвто е и програмният език C, се характеризират със задължителни синтактични...
- Програма за събиране на четирибайтови числа Програма за събиране на четирибайтови числа Операндите трябва да се заредят най-напред в оперативната памет. За първия операнд са отделени клетките 0х60, 0х61, 0х62 и 0х63; като първата от тях...