Асемблер AVR для початківців (сьомий крок)
| ADPS2 | ADPS1 | ADPS0 | Коефіцієнт розподілу |
Як я вже казав, ці біти належать до регістру ADCSRA. Але якщо є А, значить є і B. І таки так, є регістр ADCSRB. Його біти визначають джерело запуску нового перетворення АЦП як безперервного перетворення. У нас перетворення повинні йти безпосередньо одне за одним, а цей режим встановлюється, якщо всі біти ADCSRB дорівнюють 0, тому ми цей регістр і не задіяли в програмі.
16-20 рядки. Цілком аналогічні таким у програмі шостого кроку, тому на них я не зупиняюся.
25 рядок. Мітка ADC_complete визначає початок обробника переривання після завершення циклу перетворення АЦП.
26-27 рядки. Копіювання вмісту РВВ ADCH у РВВ OCR0A через проміжний РОН r16. Як я вже говорив раніше, не можна безпосередньо скопіювати вміст одного РВВ до іншого РВВ. Для цього потрібно використовувати проміжний РОН та команди in та out. Реєстр ADCH - це старший регістр результату перетворення АЦП, молодший називається, як неважко здогадатися, ADCL, але ми його не використовуємо з описаних вище причин. Отже, що відбувається в результаті виконання цих рядків? Значення, записане в ADCH, пропорційне напрузі на вході ADC3, а яскравість свічення світлодіода пропорційна значенню, записаному в OCR0A. Таким чином, шляхом копіювання одного регістру в інший ми отримаємо, що яскравість світлодіода буде пропорційна вхідному напрузі, що потрібно за завданням.
28-31 рядки. Вони мають щось нагадувати уважному читачеві, щось давно забуте та просте. Якщо не згадали, повідаю, що схожа конструкція використовувалася нами в першій нашій програмі (у другому кроці), тільки там ми перевірялинатискання кнопки. Що ж тут відбувається? У рядках 28 та 30 ми перевіряємо 7-й біт регістру r16 (у 28 рядку на "1", а в 30 - на "0"). Чому так? За завданням нам потрібно включати світлодіод LED2, якщо напруга на вході більше 2,5 В. 2,5 В - це половина напруги живлення. У цьому регістрі ADCH буде записано число 256/2=128. У двійковій формі це число виглядає як 0b10000000. А число 127 має уявлення 0b01111111. Таким чином, якщо ADCH значення більше або дорівнює 128, то в старшому (сьомому) биті буде "1", а якщо менше, то "0". Саме цю перевірку ми і здійснюємо в рядках 28 і 30. Для особливо неуважних читачів, які дивуються, чому я розповідаю про регістр ADCH, а перевіряємо ми регістр r16, поясню: у 26 рядку ми копіюємо значення з ADCH в r16, так що надалі немає ніякої різниці, який із них перевіряти. У рядках 29 і 31 відбувається установка (рядок 29) або скидання (рядок 31) четвертого біта в регістрі PORTB, при цьому відбувається відповідно вимкнення або включення світлодіода LED2. Логіку роботи дивіться на другому кроці. Вона анітрохи не змінилася.
Та ось ніби й усе, що я мав сказати з приводу АЦП. За великим рахунком я, сам того не бажаючи, майже повністю розписав роботу модуля АЦП, хоча спочатку планував описувати тільки роботу з асемблером. Ну та гаразд. З мене не вбуде, а вам менше лазити по даташит.
Ну і насамкінець завдання для самостійного виконання.
Написати програму графічної індикації величини напруги, що надходить на вхід АЦП за допомогою світлодіодів LED1 та LED2. Якщо напруга менше 1/3 від максимального, обидва світлодіоди повинні бути погашені, якщо напруга знаходиться в межах від 1/3 до 2/3 від максимального, повинен горіти світлодіод LED1, а якщо напруга більша за 2/3 максимального, то повиннігоріти обидва світлодіоди.
Потім дозвольте відкланятися до наступного кроку. Він обіцяє бути не в приклад важчим за попередні, так що збирайтеся з силами і з духом.
Оскільки натискання кнопки викликає спадаючий фронт сигналу, ми й встановили лише біт ISC01. Слід бути уважним при використанні даного переривання, оскільки якщо обидва біти дорівнюють нулю, то генерація переривання буде здійснюватися постійно, поки на виведенні INT0 буде присутній низький рівень, а оскільки це переривання має найвищий пріоритет, виконання програми на цей час просто зупиниться.
21, 22 рядки. У регістр GIMSK записується одиниця в біт INT0. Я вже згадував про цей регістр і про цей битий у шостому кроці. Цей біт дозволяє генерацію зовнішнього переривання 0.
23, 24 рядки. Тут ми задали коефіцієнт поділу тактової частоти таймера 0 рівним 1024.
25 рядок. Дозвіл механізму роботи переривань.
Зверніть увагу, що у розділі ініціалізації немає дозволу переривання з переповнення таймера 0. Причини цього я описав вище.
30 рядок. Початок оброблювача зовнішнього переривання 0.
31-39 рядки. Стандартна процедура опитування кнопки. Чи не зупиняюся на ній.
40-45 рядки. Дії, що виконуються під час відпускання кнопки. На них зупинимося докладніше, оскільки вони цікаві.
40, 41 рядки. Ось тут і відбувається дозвіл переривання з переповнення таймера 0.
42 рядок. Очищення регістру R21. Цей регістр буде виконувати роль своєрідного прапора. Якщо він дорівнює нулю, потрібно завантажувати з пам'яті програм наступний байт, а якщо дорівнює 255, то потрібно зрушувати вже завантажений. Оскільки спочатку у нас нічого не завантажено, ми очищаємо вказаний регістр.
43 рядок. Завантаження врегістр r24 числа 3. Цей регістр у нас виконуватиме роль лічильника лічених із пам'яті програм байт. Нам потрібно вважати 4 байти, а завантажили ми число 3. Тут немає суперечності, оскільки рахунок починається з 0 (0, 1, 2, 3 - саме чотири байти).
49 рядок. Початок обробника переривання з переповнення таймера 0.
50, 51 рядок. Порівнюється вміст регістру r21 з нулем (рядок 50), і якщо r21 не дорівнює 0, відбувається перехід до мітки shift (рядок 51), з якої починається цикл зсуву завантаженого з пам'яті програм байта. Якщо ж r21 = 0, відбувається перехід до рядку 52.
53 рядок. Завантаження в регістр r23 числа 8. Цей регістр виконуватиме роль лічильника кількості зрушень завантаженого в регістр r22 байти. Оскільки в байті 8 біт, то ми і завантажили в r23 число 8. Чому так? Адже нещодавно я в такий же лічильник r24 завантажив число на одиницю менше за потрібне, кажучи, що рахунок ведеться з нуля. Так все правильно. Але ми будемо використовувати різні перевірки для закінчення рахунку, тому жодної помилки тут немає. Та й ви повинні вміти користуватись різними командами, і розуміти, коли яку краще використовувати.
54 рядок. Установка всіх бітів регістру r21 одиниці командою ser. Таким чином ми ставимо ознаку того, що байт вже завантажений, і новий завантажувати не потрібно, доки не будуть оброблені всі 8 біт завантаженого байта.
55 рядок. Мітка shift, що означає початок циклу зсуву завантаженого в регістр r22 байта.
56 – 59 рядки. Конструкція, що вже неодноразово застосовувалася нами. Перевіряється старший (7-й) біт регістру r22, і якщо він дорівнює "1", то встановлюється одиниця в нульовому біті регістра PORTB (гасіння світлодіода LED1), а якщо дорівнює "0", то встановлюється нуль у цьому біті (включення світлодіода LED1 ). Чому ми перевіряємо саме7-й біт? Тут вся справа в тому порядку, в якому ми зберегли нашу послідовність, розбивши її на чотири байти. Ми записали її зліва направо. А оскільки найлівіший біт є найстаршим, то ми й здійснюємо його перевірку.
60 рядок. Логічний зсув регістра r22 вліво командою lsl. Тепер старшим бітом стає той, який до цього був шостим, а сьомий біт для нас губиться, але він нам уже й не потрібний. Таким чином, при наступному проході циклу ми вже встановимо стан світлодіода, що відповідає шостому біту, при третьому проході - п'ятому і т.д. Ось так буде здійснюватись перевірка всіх бітів завантаженого в регістр r22 байта.
61 рядок. Віднімання з регістра r23 одиниці. Зменшуємо лічильник бітів на кожному проході циклу доти, доки він не стане рівним нулю.
62 рядок. Тут перевіряємо, чи не став регістр r23 рівним нулю, командою breq. Зверніть увагу, що їй у цьому випадку не передує команда порівняння cpi. Вона тут не потрібна, оскільки виконує ті ж дії, що й команда subi у рядку 61 тільки без збереження результату. Тож у разі відсутність команди порівняння цілком виправдано і навіть бажано. Отже, якщо регістр r23 став рівним нулю, відбувається перехід до мітки next_dig, де встановлюється ознака того, що потрібно завантажити новий байт, так як старий вже скінчився. Інакше відбувається перехід до рядка 63.
63 рядок. Безумовний перехід до мітки exit_tmr0, яка надсилає нас до виходу з переривання. Чому ж ми виходимо з переривання, а чи не повертаємося до початку циклу (мітка shift)? Насправді, тут все просто. Ми повинні змінювати стан світлодіоду лише один раз за переривання. При наступному вході у переривання у рядку 50 ми переконуємося, що r21 не дорівнює нулю та у рядку 51 здійснюємо перехід до міткиshift. Таким чином ми уникаємо непотрібного зациклювання програми на одному місці. Крім того, таке зациклювання потребувало б запровадження додаткових тривалих затримок, а це вже зовсім не комільфо.
64 рядок. Мітка next_dig. З неї починається ділянка програми, в якій встановлюється ознака того, що потрібно завантажити новий байт, а також перевірка кількості вже завантажених байт.
65 рядок. Очищення регістру R21. Тепер при черговому вході в переривання умова в рядках 50-51 не виконуватиметься, і завантаження чергового байта з пам'яті програм відбудеться. Але це буде тільки при наступному вході в обробник переривання за таймером 0!
66 рядок. Віднімання одиниці з r24. Зменшення лічильника вже завантажених та оброблених байт.
67 рядок. Тут на нас чекає команда brcc. Нагадаю ще раз її призначення. Вона перевіряє, чи не відбулося перенесення до старшого розряду чи позики зі старшого розряду. Якщо не сталося, ми переходимо до мітки exit_tmr0, виходячи з підпрограми обробки переривання. Якщо ж позика відбулася, тобто вміст регістру r24 змінилося з 0 на 255, відбувається перехід до рядка 68.
68-69 рядки. Очищення регістру TIMSK. Після виконання цих рядків переривання з переповнення таймера 0 буде заборонено. Тобто тут ми виконуємо умову нашого завдання – після закінчення циклу передачі сигналу SOS світлодіод згасає в очікуванні наступного натискання кнопки SB2.
Всі інші рядки повинні бути зрозумілі читачеві, який уважно стежить за цим циклом статей, тому я опускаю їх опис.
Не знаю, наскільки доступно мені вдалося викласти цей загалом не найпростіший для розуміння матеріал. Але в будь-якому випадку я завжди на зв'язку, і ви можете поставити свої питання на форумі або прямо тут.
До речі, зверніть увагу, що при асемблюванні цієї програми обсяг отриманого програмного коду складає всього 60 слів, або 120 байт. Це становить трохи більше 10% від і так досить маленької пам'яті програми контролера ATtiny13. Цей факт показує, наскільки компактним є код, написаний на асемблері.
Ну і нарешті завдання для самостійного виконання. Воно полягатиме у розширенні вже написаної програми.
У вихідному стані при старті живлення світлодіоди LED1 та LED2 мають бути погашені. При натисканні на кнопку SB1 за допомогою світлодіода LED1 видавати слово "asm" (.- . --), а при натисканні на кнопку SB2 за допомогою світлодіода LED2 видавати слово "AVR" (.- . - .-.). Обробку кнопки SB2 здійснити за допомогою зовнішнього переривання 0, а обробку кнопки SB1 - за допомогою переривання зміни стану висновків. Робота обох кнопок має бути незалежною, тобто будь-якої миті часу можна запустити як видачу слова asm, і видачу слова AVR. При цьому зупинка таймера повинна здійснюватися тільки після закінчення останнього знака останнього з слів, що виводяться в даний момент.
Розумію, що завдання це набагато складніше, ніж усе, що ми до цього писали, але я вірю, що ви його подужаєте, тому що ми вже практично дісталися вершини, а там немає місця слабким!