Програми за управление
Осъществяване на закъснение
Едночиповите микрокомпютри се използват обикновено за управление на някакви обекти. Аритметичните действия, които могат да извършват , се използват именно за целите на управлението, а не за решаване на математически задачи. Една от най-важните процедури в това отношение е осигуряване на закъснение. Закъснение е необходимо за реализиране на задържане на някакъв процес определено време ( реле за време), за получаване на импулсна поредица с регулируем период или регулируема честота, и др.
Принципът за осигуряване на закъснение се състои в това, да се изпълнява някаква инструкция циклично определен брой пъти. Времето за изпълнението и е строго регламентирано и зависи от честотата на тактовия генератор на процесора. В списъка на инструкциите се дава обикновено и броят на тактовете, за които се изпълнява дадена инструкция. Този брой е най-често 1 такт, но има инструкции, които се изпълняват до 4 такта. Всеки такт е с регламентирано време. Общото време на закъснението зависи от общия брой на тактовете до прекратяване на цикъла.
В следващия пример е илюстриран един възможен вариант за осъществяване на закъснение. Процедурата за осъществяване на закъснението е оформено като подпрограма, за да може да се използва и при други програми. Оформено е като отделен файл Delay.asm.
;Delay.asm
CLR R0
CLR R1
L1:
DEC R0
BRNE L1
DEC R1
BRNE L1
DEC R2
BRNE L1
Регистърът R2 се зарежда с константа извън подпрограмата, при което той се явява вход. Другите два регистъра R0 и R1 се нулират. Съдържанието им е обхванато от два вложени цикъла, като при всяка итерация, то намалява с 1. Най-напред намалява съдържанието на R0, като BRNE следи, кога той ще стане отново нула. За целта трябва да преминат 256 такта от тактовия генератор. На всяко нулиране на R0, R1 ще намалява с единица. Следователно R1 ще се нулира след 256*256=65536 такта.
Регистърът R2 е инициализиран в основната програма с някаква константа (в десетичен формат). Такова инициализиране е допустимо и за R1, и за R0, ако числото 65536 не е подходящо. Изменяйки инициализиращата константа, може да се задава различно време на закъснение.
;Program.asm
.include “m16def.inc”
LDI R16,(100)
MOV R2,R16
.INCLUDE “DELAY.ASM”
Подпрограмата за закъснение е включена в основната програма чрез директивата .INCLUDE.
Преобразуване на код в период
Периодът представлява циклично повтаряне на определено време на закъснение. Началото и края на отчитането е обикновено фронт на импулс. Следователно за да има периодичност, циклично трябва да се повтаря подпрограма за закъснение, която да предизвиква изменение на напрежението на някои от крачетата на интегралната схема.
Крачетата на интегралната схема на контролера в повечето случаи са многофункционални, т.е. едно и също краче се използва за различни цели. Това зависи от състоянието на определени регистри от списъка на регистрите за вход/изход на информация. Тук ще бъде разгледано използването на портовете за паралелен вход/изход на информация. Броят на тези портове е различен за различните контролери. Те обикновено са 6 или 8 битови. Характеризират се с три регистъра за достъп. Единият е регистър за посока на данните за съответния порт. Носи името DDRA до DDRF в зависимост от това, за кой порт се отнася. Ако в даден разред на регистъра се запише единица, съответното краче на интегралната схема става вход, а ако се запише нула – става изход. Данните, които постъпват от външно устройство, когато портът е вход, или се изпращат към външно устройство, когато е изход, става чрез така наречения регистър за данни. И неговото име зависи от порта, за който се отнася, и може да бъде от PORTA до PORTF. Характерно за тези два регистъра е, че в тях може да се записва и от тях може да се чете.
Третият регистър е съответно PINA до PINF. Той не съществува физически, но има собствен адрес. От този адрес само може да се чете и на него се появяват директно логическите нива на крачетата на съответния порт.
В следващия пример е използван Порт В като изход. Той е 6 битов. В примера е демонстрирано, как може да се работи с един единствен оперативен регистър, което е характерно за редица процесори на други фирми. За целта е удобно съдържанието му да се съхрани временно в стека, когато това се налага.
.include “m16def.inc”
LDI R16,LOW(RAMEND) ;Инициализиране на стека
OUT SPL,R16
LDI R16,HIGH(RAMEND)
OUT SPH,R16
LDI R16,0xFF
OUT DDRB,R16 ;Дефиниране на 8 изхода
CLR R16
OUT PORTB,R16 ;Нулиране на изхода
L0:
LDI XL,0×60 ;Адрес на кода на заданието
CLR XH
LD R16,X+ ;Зарежда старшия байт на кода
L1:
PUSH R16 ;Съхранява R16 в стека
LD R16,X ;Зарежда младшия байт на заданието
L2:
DEC R16 ;Цикъл за закъснение
BRNE L2
POP R16 ;Възстановява R16 от стека
DEC R16 ;Външен цикъл за закъснение
BRNE L1
IN R16,PORTB ;Чете старото съдържание на порта
COM R16 ;и го инвертира
OUT PORTB,R16 ;Изпраша отново на порта
JMP L0 ;Започва формирането на следващия период.
Закъснението се формира в резултат на действието на два вложени цикъла. Заданието се записва и в двата случая в R16, след което съдържанието му намалява с единица, докато не се нулира. Ако задаващото число и по-голямо, нулирането ще настъпва след по-голям брой тактове на тактовия генератор, т.е. периодът ще бъде по-дълъг и обратно.
Формирането на изходен импулс се извършва, като на съответния извод на Порт В се промени състоянието от 0 в 1 или обратно. Тук се използват и шестте разреда на Порт В.
Съществуват два подхода. При избрания подход всеки път, когато се нулира външния цикъл, съдържанието на изхода се прочита чрез IN в R 16, инвертира се и се връща отново на PORTВ чрез OUT. Получената импулсна поредица е напълно симетрична, но с два пъти по-малка честота. Ако симетрията не е от съществено значение, а е по-важно постигането на по-висока честота, може да се изпраща някакво число към изхода, предварително записано в оперативен регистър, след което процесът да се връща в началото, където изходът отново се нулира.
Преобразуване на код в честота
Известно е, че честотата е реципрочна стойност на периода. Следователно, ако зависимостта на задаващото число е право пропорционална на периода, то зависимостта на това число ще бъде обратно пропорционална на честотата и обратно. С други думи ако програмата за преобразуване на код в период се използва за регулиране на честота, зависимостта няма да бъде линейна, което е нежелателно.
За линейна зависимост между задаващо число и честота се използва друг алгоритъм. Това число се сумира само със себе си, докато не предизвика пренос. Колкото е по-голямо числото, толкова по-бързо би се запълнил даден оперативен регистър и толкова по-рано би възникнал този пренос. Ако процесът се повтаря циклично, появата на преноса ще бъде с пропорционална на заданието честота.
Възниква проблем, когато задаващото число е голямо и пренос би възникнал след няколко такта, дори още на първия такт. Това води до една грешка, заложена в самия метод. Тя не може да се избегне, но може да се намали значително, като не се допускат големи задаващи числа. Вместо да се ограничават числата, е по-добре, да се увеличи разредността така, че всички еднобайтови числа да се смятат за достатъчно малки. Може да се премине към 16 битова структура, като резултатът от сумирането се натрупва в 16 битов регистър. Това обаче ще забави възникването на пренос 256 пъти, което означава, че не могат да се получават високи изходни честоти. На практика обикновено се търси компромис между точността и максималната възможна изходна честота.
Задаващото число може да се получава от някой от портовете, инициализиран да работи като вход. Тук е предпочетено, то да бъде заложено в клетка от оперативната памет. Изходът е предвидено да се формира на изводите на Порт В. За да се инициализира като изход, в регистъра DDRB се записват 8 единици. В регистъра PORTB се записва двоичното число 01010101. При всеки пренос това число се инвертира. На практика на всеки извод от PORTB се получава импулсната поредица с желаната честота, но два съседни извода се взаимно инверсни.
.include “m16def.inc”
LDI XL,0×60 ;Задава адрес на заданието
CLR XH
LDI R16,0xFF ;Инициализира изхода
OUT DDRB,R16
LDI R16,0×55 ;Записва 01010101 в PORTB
L2:
OUT PORTB,R16 ;Изпраща състоянието на PORTB
LD R2,X ;Чете заданието
MOV R1,R2 ;В R1 ще се натрупва сумата
LDI R17,0×3F ;Задава 6 старши адреса
L1:
ADD R1,R2 ;Сумира само се със себе си
BRCC L1 ;Ако няма пренос
DEC R17 ;Ако има пренос, R17 намалява с 1
BRNE L1 ;Дали е още различно от 0
COM R16 ;Ако стане нула, R16 се инвертира
JMP L2 ;Безусловен преход към L2
Вместо R17 да намалява от предварително зададено число, би могло то да се увеличава от нула с преноса, който ще се натрупва. Тогава нещата напълно биха съответствали на описания алгоритъм. За да се спре процеса до шестия разред обаче, трябва да се използва инструкция за сравнение със зададено число, което би усложнило нещата. По-прост начин би било, ако се предвидят само четири допълнителни разреди и се следи за възникване на полупренос в R17. Избраният подход обаче и в този случай е по-кратък.
Широчинно-импулсна модулация по метода на линейното нарастване
За реализация на ШИМ са предвидени вградени в контролера броячи, но тук въпросът ще бъде разгледан принципно, за да се види, как едно схемно решение може да бъде реализирано по програмен път.
При формирането на ШИМ, като едно трионообразно напрежение с постоянна честота Uл се сравнява непрекъснато с управляващо напрежение Uу. В точката, в която двете напрежения се изравят, се формира фронт на импулс. Ако управляващото напрежение расте или намалява, моментът на съвпадение също се променя, а заедно с него и моментът на възникване на фронта. Тъй като периодът на трионообразното напрежение не се променя, периодът на формираните импулси също не се променя, но се променя отношението между времето на импулса и времето на пауза, от където се изменя средната стойност на изходното напрежение.
Този резултат може да се постигне схемно чрез един ГЛИН с трионообразна форма на изходното напрежение и един компаратор, реализиран най-често с операционен усилвател.
Вместо да се сравняват две напрежения, могат да се сравняват две числа – едното управляващо, а другото - линейно нарастващо. За целта управляващото напрежение може да се запише в един операционен регистър, а линейно нарастващото да се формира в друг операционен регистър. Това нарастване трябва да бъде циклично, като всеки път двата регистъра се сравняват. Ако числото в управляващия регистър е по-голямо, на изхода на контролера трябва да се формира едно логическо ниво, а когато това число остане по-малко, изходното състояние трябва да се инвертира. Това е илюстрирано в програмата от следващия пример.
.include “m16def.inc”
LDI XL,0×60
CLR XH
LDI R16,0xFF ;Инициализира канал В като изход
OUT DDRB,R16
LDI R16,0×55 ;Едното изходно състояние
LDI R17,0xAA ;Другото изходно състояние
CLR R5 ;Нулира R5 , в който числото ще расте
L2: LD R6,X ;Чете управляващото число
INC R5 ;Нараства с 1
CP R5,R6 ;Сравнява двата регистъра
BRSH L1 ;Ако R5 е по-голям или равен
OUT PORTB,R16
JMP L2 ;Безусловен преход
L1: OUT PORTB,R17
JMP L2
Четенето на заданието може да се извършва от някой от другите портове, вместо от паметта. Това е илюстрирано в следващия пример. Предвидено е заданието да се чете от PORTC, който се намира на адрес 0х15. За да бъде инициализиран като вход, в DDRC се записано 0х00.
.include “m16def.inc”
LDI R16,0×00
OUT DDRC,R16
LDI R16,0xFF
OUT DDRB,R16
LDI R16,0×55
LDI R17,0xAA
CLR R5
L2: IN R6,PORTC
INC R5
CP R5,R6
BRSH L1
OUT PORTB,R16
JMP L2
L1: OUT PORTB,R17
JMP L2
Широчинно-импулсна модулация по метода на двата брояча
Схема на ШИМ, реализирана с два брояча. За простота е разгледана четири битова структура.
Броячите 74193 са реверсивни броячи с възможност за запис. Когато входът XS се нулира, числото х1х2х3х4 се записва в брояча. От този момент нататък това число нараства, ако постъпи импулс на входа за натрупвани и намалява на всеки импулс, постъпил на входа за изваждане. Когато броячът се нулира, на изхода Qп- се формира логическа нула. При всички други състояния, на този извод има логическо ниво 1.
Двата брояча работят в режим на изваждане, като тактовите импулси постъпват едновременно на входовете им. Бр1 е оставен да намалява свободно, като минава през нулата след 16 импулса. Точно в този момент попада ниско логическо ниво на входа XS на Бр2 и в него се записва задаващото число, което е винаги по-малко от 16. При следващите импулси двата брояча намаляват едновременно. Пръв ще се нулира Бр2, защото той започва да брои от по-малко число. В момента на нулирането му, изходът за пренос при изваждане преобръща тригера Тр. Когато се нулира и Бр1, тригерът се възстановява по същия начин. Така на изходите на тригера се формират две взаимно инверсни импулсни поредици, чиято широчина зависи от големината на задаващото число.
Програмата в следващия пример извършва същото. Заданието се чете от PORTC и се записва в R5. Този регистър отговаря на Бр2. На Бр1 отговаря R6. В началото на програмата той се нулира. В регистрите R16 и R17 се записват двете взаимно инверсни състояния на изхода. Когато се нулира R5, се изпраща едното състояние към PORTB, а когато се нулира R6, се изпраща другото състояние.
.include “m16def.inc”
LDI R16,0xFF
OUT DDRB,R16
CLR R16
OUT DDRC,R16
CLR R6
LDI R16,0xAA
LDI R17,0×55
L2: OUT PORTB,R16
IN R5,PORTC
L3: DEC R6
BRNE L1
JMP L2
L1: DEC R5
BRNE L3
OUT PORTB,R17
JMP L3
Синтезатор на функции
Ще бъде разгледан пример, когато към изхода на контролера се изпращат циклично дискретни състояния, описващи някаква функция. Тази функция може да формира променливо напрежение със съответната форма. Ако се работи в звуковия обхват от честоти, така могат да се формират сигнали с различен тембър, защото тембърът зависи изключително от формата на напрежението или тока във високоговорител. Като най-популярна, нека това е синусоидалната функция.
Показаната фигура не описва истинска синусоида, а стъпаловидна крива, апроксимираща синусоида чрез дискретни състояния в различни моменти от време. В цифровите системи дискретизацията се извършва по време и по ниво. Дискретизацията по време се определя от широчината на така наречения период на квантуване по оста на времето. В случая е избрано един период от изходната функция да бъде формиран от 12 такива интервала. Колкото е по-голям този брой, толкова по точна апроксимация може да се очаква.
Точността зависи и от квантуването по ниво. В примера възможните състояния се изменят в интервала от 0 до 255, което определя 8 битово число. За по-голяма точност е препоръчително да се премине към по-голяма разредност.
За простота е прието синусоидалната функция да бъде изместена над абсцисната ос, т.е. да се работи само с положителни числа. Отделните състояния в различните интервали са записани в таблицата. От тази таблица те трябва да се прехвърлят в последователни клетки от паметта. Избрано е това да става от адрес 0х0060.
Тези данни могат да се изплащат към регистъра за данни на някой от портовете, инициализиран като изход. Към изводите на съответния порт може да се свърже външно цифрово-аналогов преобразувател. На изходът му трябва да се свърже нискочестотен филтър, чиято срязваща честота да бъде много по-малка от честотата на квантуване. Честотата на получената функция може да се регулира чрез въвеждане на закъснения по познатите вече начини.
За формиране на изходния сигнал е възможен и друг подход. Вместо външен ЦАП, може текущото дискретно състояние да се модулира широчинно-импулсно, т.е. преди то да се изпрати към изходния регистър, да се прекара като вход през подпрограма за ШИМ. Максималната честота на изходния сигнал обаче ще бъде значително по-малка.
.include “m16def.inc”
LDI R16,0xBF
STS 0×0060,R16
LDI R16,0xED
STS 0×0061,R16
LDI R16,0xFE
STS 0×0062,R16
LDI R16,0xED
STS 0×0063,R16
LDI R16,0xBF
STS 0×0064,R16
LDI R16,0×7F
STS 0×0065,R16
LDI R16,0×40
STS 0×0066,R16
LDI R16,0×12
STS 0×0067,R16
LDI R16,0×00
STS 0×0068,R16
LDI R16,0×12
STS 0×0069,R16
LDI R16,0×40
STS 0×006A,R16
LDI R16,0×7F
STS 0×006B,R16
LDI R16,0xFF
OUT DDRB,R16 ;0×00018
CLR XH
L2: LDI XL,0×60
L1: LD R16,X+
OUT PORTB,R16
CPI XL,0×6C
BRNE L1
JMP L2
В началото на програмата се инициализира масива в паметта. Тази част може да се оформи в отделен файл. При реални условия е препоръчително масивът да се запише заедно с програмата във флаш-паметта. Контролерите AVR позволяват такива таблици от данни за се записват еднократно в тази памет и от нея да се чете.
Следващата част от програмата изпълнява безкраен цикъл по описания алгоритъм.
Сходни статии:
- По-важни алгоритми Програма за преобразуване на еднобайтово число в допълнителен код Инструкцията NEG преобразува в допълнителен код, като инвертира всички 8 бита на числото и добавя единица. За знак използва флага N...
- Изваждане на BCD числа За изваждане на BCD числа не могат да се използват инструкциите SUB, SBC, SBCI, защото код BCD и двоичният код са съвсем различни неща. Тук ще бъде използван алгоритъм, който...
- Програма за преобразуване на четирибайтови числа в допълнителен код Програма за преобразуване на четирибайтови числа в допълнителен код Зададеното число се разполага в четири последователни клетки от паметта. И тук е прието най-старшият байт да е с най-малък адрес....
- Асемблер - въведениe в решаването на задачи Решаването на всяка задача може да се разгледа като последователност от действия за постигането на крайната цел. Формалният запис на крайна последователност от действия, водещи до решаването на определена задача,...
- Програмиране на AVR-контролери Програмен модел Аритметическите и логическите операции се изпълняват от така нареченото Аритметично-логическо устройство (АЛУ). То има два входа и един изход. На входовете се въвеждат операндите, а на изхода се...