Асемблер AVR для початківців(дев’ятий крок) - Мої статті - Каталог статей - Персональний сайт
Сподіваюся, дорогі мої читачі, ви благополучно впоралися із запропонованим мною у попередньому кроці завданням і поспішайте до нових звершень. Не будемо зволікати!
Давайте цього разу зробимо так само, як і минулого. Відразу поставимо собі завдання, а потім її вирішуватимемо. Проте, на відміну минулого разу, сьогодні завдання сформулюємо максимально конкретно.
Необхідно по перериванню від переповнення таймера 0 безперервно і автоматично змінювати вміст регістру r16 від 0 до 255, а потім у зворотному порядку з кроком 1. Крім того, необхідно змінювати яскравість світлодіоду LED1 за допомогою ШИМ таким чином, щоб ця яскравість була пропорційна квадрату регістру r16.
Отже, як бачимо, можна поставлене завдання розбити дві подзадачи: управління вмістом регістру r16 і математична обробка цього самого вмісту. І якщо з першим підзавданням все більш-менш ясно, то над другим потрібно трохи подумати.
Нам потрібно змінювати яскравість світлодіода пропорційно квадрату R16. Але значення r16 змінюється не більше від 0 до 255, отже, його квадрат - від 0 до 65025. Це значення виходить межі однобайтового числа, що може бути записано в регістр OCR0A. Тобто ми повинні записати в OCR0A 255, а не 65025 при r16 = 255. Таким чином, перед нами постає завдання масштабування, вирішуючи яку ми маємо отримати коефіцієнт пропорційності між r16 2 і OCR0A.
За завданням мінімальна яскравість повинна відповідати нульовому значенню r16, тому якщо r16=0, то і OCR0A=0. А щоб отримати коефіцієнт масштабування, нам, як більшість читачів вже здогадалося, потрібно розділити 65025 на 255. При цьому виходить число 255 (що, власне, не дивно). Таким чином, щоб укластися в однобайтове число, нампотрібно обчислювати значення OCR0A за такою формулою:
Тобто для виконання поставленої задачі нам потрібно помножити вміст r16 саме на себе і розділити отримане значення на 255. Саме в такій послідовності, інакше для всіх значень r16, крім 255, OCR0A буде 0 (оскільки розподіл цілочисельне, і дробова частина просто відсікається) .
Таке завдання на Сі вирішується одним рядком без жодних роздумів. Але в асемблері вона ускладнюється низкою фактів. По-перше, тут відсутні команди множення та поділу, тому нам доведеться розробляти алгоритми, що реалізують ці дії на підставі наявних – додавання та віднімання. По-друге, навіть елементарні операції: додавання та віднімання - нам доведеться робити над двобайтовими числами, що теж має свої нюанси.
Але давайте не будемо довго міркувати, а відразу приступимо до розгляду програми, і в ході опису я розповім, що до чого.
Команда sbc виконує арифметичне віднімання другого регістра з першого з урахуванням перенесення. Тобто з першого РОН віднімається другий, а також віднімається значення біта С. При виконанні цієї команди також можлива позика зі старшого розряду з автоматичною установкою біта С.
Команда cp виконує порівняння вмісту зазначених РОН. За своїм змістом вона схожа на команду cpi, тільки порівнює не РОН і константу (як cpi), а два РОН. Команда cp віднімає з першого регістра другий, але здійснює збереження результату у першому регістрі. Вона лише встановлює прапори виконання операцій у статусному регістрі SREG. При виконанні цієї команди так само, як і в командах віднімання, може відбуватися позика зі старшого розряду через біт.
Команда cpc виконує порівняння вмісту зазначених РОН з урахуванням перенесення. З першого регіструвіднімається другий, і навіть біт перенесення З. І як і, як й у попередніх операцій, у результаті виконання може встановлюватися біт З.
Ну ось якось так. Розберемо тепер застосування цих команд практично, у написаної нами програмі.
2-5 рядки. Таблиця переривання векторів. Тут усе вже відомо з попередніх кроків.
6-20 рядки. Розділ ініціалізації. У ньому також усі команди та використовувані регістри контролера були описані раніше. Зупинюся лише на призначенні регістрів, зазначених у рядках 16,17,18.
Регістр r17 є прапором, що визначає напрямок зміни яскравості. Якщо він дорівнює 0, відбувається зменшення яскравості, якщо 255, то збільшення. Спочатку ми командою ser записуємо в нього 255, тим самим задаючи, що при старті програми яскравість збільшуватиметься.
Регістр r18 командою clr обнулюється. Про його призначення розповім при описі алгоритмів множення та поділу.
У регістр r22 командою ser записується число 255. Цей регістр гратиме роль дільника у формулі масштабування, записаної вище.
25 рядок. Мітка tmr0 - початок підпрограми обробки переривання з переповнення таймера 0.
26-33 рядки. У них відбувається встановлення та скидання прапора r17 при досягненні регістром r16 крайніх значень (0 або 255). Розглянемо докладніше, як це працює.
У рядках 26-27 відбувається порівняння регістра r16 з нулем, і якщо ця рівність виконується, то здійснюється перехід до мітки step_up (рядок 32), за якою слідує команда ser, що записує в r17 число 255 (рядок 33). Таким чином виходить, що при досягненні регістром r16 значення "0" ми встановлюємо в r17 число 255, визначаючи, що тепер вміст регістру r16 має збільшуватися.
У рядках 28 та 29 відбувається порівняння регістру r16 з255 і якщо рівність не виконується (команда brcc), відбувається перехід до мітки set (рядок 34), а інакше відбувається перехід до наступного рядка (30), в якій здійснюється скидання прапора r17 (командою clr). Таким чином, при досягненні регістром r16 значення "255" r17 встановлюється значення "0", що визначає, що далі слід зменшувати вміст регістра r16.
35-38 рядки. Конструкція знайома з минулих кроків. Ми перевіряємо молодший біт регістра r17 (рядки 35, 37), і в залежності від його значення виконуємо збільшення вмісту регістра r16 на 1, якщо цей біт дорівнює 1 (рядок 36), або зменшення на 1, якщо він дорівнює 0 (рядок 38) . За великим рахунком, можна було б перевіряти абсолютно будь-який біт регістра r17, тому що ми встановлюємо і скидаємо всі вісім біт відразу.
39 рядок. Виклик підпрограми множення, що виконує зведення вмісту регістру r16 квадрат
40 рядок. Виклик підпрограми розподілу, що виконує розподіл отриманого результату на 255.
41 рядок. Копіювання результату двох попередніх операцій (регістр r23) у регістр OCR0A для безпосередньої зміни яскравості світлодіода.
44 рядок. Мітка multiply – початок підпрограми множення. Для початку опишу алгоритм роботи цієї підпрограми, а потім розглянемо його реалізацію.
Власне, алгоритм дуже простий. Він заснований на багаторазовому додатку до твору одного з множників усередині циклу. Кількість проходів циклу дорівнює другому множнику. Таким чином, множення зводиться до багаторазового підсумовування, причому чим більше другий множник, тим більше часу знадобиться контролеру на цю операцію. Тепер обіцяні подробиці.
45, 46 рядки. Очищення регістрів R19, R20. У цих регістрах ми і накопичуватимемо квадрат вмісту r16. Я вжеговорив на самому початку, що для зберігання квадрата однобайтового числа буде потрібно два байти. Вважатимемо, що r19 містить у собі молодший байт результату, а r20 - старший.
47 рядок. Копіювання в РОН r21 вмісту r16. Регістр r21 виконуватиме роль другого множника. Оскільки нам потрібно звести r16 у другий ступінь, то логічно, що обидва множники однакові.
48 рядок. Мітка sum означає початок циклу підсумовування.
49,50 рядки. Таким чином здійснюється додавання двох двобайтових чисел. Спочатку проводиться підсумовування молодших байт командою add (рядок 49), та був - старших байт з урахуванням перенесення командою adc (рядок 50). Додавання має здійснюватися саме цими командами і саме в такій послідовності. Власне, це має бути зрозуміло зі звичайної математики – ми завжди підсумовуємо, починаючи з молодшого розряду, і якщо здійснюється перенесення з молодшого розряду, ми його враховуємо та додаємо до старшого. Тут настав час пояснити призначення регістру r18. Незважаючи на те, що ми додаємо лише молодші розряди, а в старшому накопичуємо лише переноси, все одно через синтаксис команди adc потрібно використовувати два регістри. І якщо ми хочемо додавати лише біт перенесення, то другий регістр (r18) потрібно зробити рівним нулю, що ми і зробили у 17 рядку.
51 рядок. Віднімання одиниці з регістра r21. Тут, гадаю, теж має бути зрозуміло. Ми спочатку завантажили в нього потрібний множник, а потім на кожному проході циклу віднімаємо з нього по "1". Коли в r21 залишиться 0, ми виконали всі ітерації, і цикл можна завершити.
52 рядок. Власне, тут ми й перевіряємо, чи не вийшло в результаті попередньої операції нуля, і якщо не вийшло (команда brne), переходимо до мітки sum - початку циклу.
53 рядок. Повернення з підпрограми множення.
Хочу ще помітити, що при додаванні чисел більшої розрядності (3, 4 і т.д. байт) алгоритм не змінюється. Потрібно додавати молодші байти командою add, проте наступні - у порядку зростання командою adc.
55 рядок. Мітка divide - початок підпрограми поділу. Як і у випадку з попередньою підпрограмою, спочатку розглянемо алгоритм її роботи.
Але це так, до речі. Тепер нарешті розглянемо сам алгоритм. Сенс його полягає в багаторазовому відніманні дільника з діленого всередині циклу доти, поки ділене стане менше дільника. Одночасно з цим кожному проході циклу до приватного додається одиниця. Таким чином, після виходу з циклу ми отримуємо приватне, а в регістрі, в якому було ділене - залишок від поділу. Розглянемо тепер реалізацію алгоритму у програмі.
56 рядок. Очищення регістру R23. У ньому ми накопичуватимемо приватне.
57 рядок. Мітка minus - початок циклу віднімання дільника з діленого та нарощування приватного.
58-59 рядки. Порівняння двох двобайтових чисел: ділимого (r19, r20) та дільника (r18, r22). Тут логіка та сама, як і підсумовуванні: спочатку порівнюються молодші байти з допомогою нормальної команди рівняння (СР), та був старші з урахуванням перенесення (СРС). Тут теж варто згадати математику - віднімання так само, як і додавання, починається з молодшого розряду з можливістю позики одиниці зі старшого. Зверніть увагу, що для порівняння старших розрядів ми використовуємо в дільнику той самий регістр r18, рівний 0, оскільки наш дільник однобайтний (r22 = 255), а для синтаксису команди срс потрібно порівнювати два регістри.
60 рядок. Якщо ділимо менше дільника (команда brlo), то виходимо з циклу (мітка exit), інакше переходимо до наступного рядка.
61-62рядки. Віднімаємо з діленого дільник. Логіка тут абсолютно така сама, як і для рядків 58-59, єдина відмінність у тому, що після виконання команд sub і sbc відбувається зміна регістрів r19,r20
63 рядок. Додаток до приватної (r23) одиниці.
64 рядок. Безумовний перехід на початок циклу (мітка minus). Таким чином, з циклу ми можемо вийти тільки через рядок 60.
65 рядок. Мітка exit служить для визначення місця виходу з циклу для рядка 60.
66 рядок. Повернення з підпрограми поділу.
Після цього рядка ми потрапляємо до 41 рядка, де приватне (r23) копіюється в регістр OCR0A.
Ну от як би і все, що стосується роботи програми. Що тут ще можна додати. При завантаженні програми в контролер світлодіод почне плавно змінювати свою яскравість від мінімальної до максимальної та назад. Можливо, уважний читач запитає: "Ну і де тут видно, що воно пропорційне квадрату r16?" Тут повинен зізнатися чесно, це не дуже відмінно неозброєним оком. Але для порівняння спробуйте у рядку 41 у OCR0A копіювати не r23, а r16, і ви побачите, що характер зміни яскравості став дещо іншим.
Насамкінець традиційне завдання для самостійного рішення.
Написати програму індикації величини напруги, що знімається з двигуна змінного резистора за допомогою світлодіода LED1. При цьому яскравість світлодіода повинна бути пропорційна кубу напруги, що зчитується.
Тут матиме місце такий собі симбіоз програм із нинішнього та з 7-го кроків, так що особливих труднощів виникнути не повинно.
Попереду на нас чекає десятий крок, яким я планую завершити поточний цикл. Якщо він здасться цікавим для читачів, то маю вже задуми щодо його продовження. Тож чекаю відгуків.