Динамічна індикація - це метод відображення цілісного зображення шляхом швидкого послідовного відображення його частин. Видимість цілісності при цьому досягається завдяки інерційності людського зору.
В принципі, зображення на електронно променевій трубці формується саме таким чином (постороково), та і на сучасних ЖК матрицях так само використовується такий метод. Суть методу полягає в послідовному засвічуванні рядів точок-пікселів на екрані. Адже, якби всі пікселі керувалися одночасно, це вимагало б (для екрана з роздільною здатністю 800х600 точок) 800х600=480000 контактів для підєднання матриці і стільки ж ліній портів керуючого контролера! І це для монохромного дисплея, для кольорового - в 3 рази більше. Ми ж розглянемо дещо простіший варіант ніж матричний вивід - вивід на стандартні семисегментні світлодіодні індикатори.
Теорія динамічної індикації
Звичайно, якщо нам потрібно виводити число в 1-2 розряда, то можна просто приєднати індикатори до портів МК посегментно. Проте, якщо розрядів, скажімо 6 (годинник наприклад), то необхідно використати 6*7=42 вивода портів. Таку кількість мають далеко не всі МК серії АВР. Тут то і приходить на допомогу динамічна індикація. Суть її в даному випадку в тому, що ми з'єднуємо, припустимо усі одноіменні аноди розрядів (для індикаторів зі спільним катодом), і заводимо на виводи одного порта, а катоди кожного з розрядів - на інший порт (якщо індикатори великого розміру і споживають великий струм, доведеться використати додаткові ключі на транзисторах). Таким чином, використовуэться всього 8+6=14 виводів портів МК. Ще більше зменшити число задіяних виводів можна за допомогою спеціальних мікросхем-дешитфраторів, так часто роблять при великій кількості розрядів. Індикація відбувається шляхом швидкого циклічного показу кожного розряду числа з відповідно включеним в той момент катодом.
Тобто, припустимо нам треба вивести число 128563:
1. Подаємо сигнал лог.0 (підключаємо до земляного проводу) на перший катод (на інших лог. 1)
1.2. Подаємо на аноди комбінацію, що відповідає числу 1. На першому індикаторі засвітиться 1. решта будуть погашені.
1.3. Утримуємо вказаний стан на протязі певного часу, наприклад 0,01с.
2. Подаємо сигнал лог.0 (підключаємо до земляного проводу) на другий катод (на інших лог. 1)
2.2. Подаємо на аноди комбінацію, що відповідає числу 2. На другому індикаторі засвітиться 2. решта будуть погашені.
2.3. Утримуємо вказаний стан на протязі певного часу, наприклад 0,01с.
3. Подаємо сигнал лог.0 (підключаємо до земляного проводу) на третій катод (на інших лог. 1)
3.2. Подаємо на аноди комбінацію, що відповідає числу 8. На третьому індикаторі засвітиться 8. решта будуть погашені.
3.3. Утримуємо вказаний стан на протязі певного часу, наприклад 0,01с.
4. Подаємо сигнал лог.0 (підключаємо до земляного проводу) на 4-ий катод (на інших лог. 1)
4.2. Подаємо на аноди комбінацію, що відповідає числу 5. На 4-му індикаторі засвітиться 5. решта будуть погашені.
4.3. Утримуємо вказаний стан на протязі певного часу, наприклад 0,01с.
5. Подаємо сигнал лог.0 (підключаємо до земляного проводу) на 5-ий катод (на інших лог. 1)
5.2. Подаємо на аноди комбінацію, що відповідає числу 6. На 5-ому індикаторі засвітиться 6. решта будуть погашені.
5.3. Утримуємо вказаний стан на протязі певного часу, наприклад 0,01с.
6. Подаємо сигнал лог.0 (підключаємо до земляного проводу) на 6-ий катод (на інших лог. 1)
6.2. Подаємо на аноди комбінацію, що відповідає числу 3. На 6-ому індикаторі засвітиться 3. решта будуть погашені.
6.3. Утримуємо вказаний стан на протязі певного часу, наприклад 0,01с.
7. Переходимо до п.1
Як ми бачимо, насправді індикатори висвічують числа почергово. Але так як час переключення менший, ніж час інерцї людського зору (біля 0,04с), то ми будемо бачити, що усі індикатори показують одночасно. Навіть, якщо зробити більші затримки між переключеннями, ми будемо бачити суцільне зображення, але може бути помітне неприємне мерехтіння розрядів.
Апаратне забезпечення експерименту
Для експерименту зберемо схему на основі трьохрозрядного світлодіодного індикатора.
На малюнку показана схема одного розряду індикатора, позначення сегментів буквами a-h у всіх стандартизоване. До речі, насправді сегментів у більшості індикаторів 8, але восьмий сегмент (h) - це крапка і вона не приймає участь у формуванні цифри.
Наприклад можна купити індикатор RL-T5614SBAW/D15 http://radiomarket.lg.ua/product_info.php/products_id/17975
Наприклад можна купити індикатор RL-T5614SBAW/D15 http://radiomarket.lg.ua/product_info.php/products_id/17975
Це індикатор зі спільним катодом, тобто він має 8 виводів анодів-сегментів і 3 вивода спільних катодів-розрядів. Розводка виводів цього індикатора:
А розміщення виводів відповідає стандартному для мікросхем у DIP корпусах:
Я випаяв індикатор з якогось старого пристрою
Ще знадобляться 3 NPN транзистора з максимальним струмом колектора не менше 100ма. Наприклад вічні радянські КТ315 :-) Я використаю імпортні 2SC945 випаяні з тієї ж плати :-).
Максимальний струм сегмента індикатора рівний 20ма, тому індикатор слідвключати через обмежуюяі резистори. Встановимо струм сегмента у 10ма. Для червоного світлодіода падіння напруги приблизно 2,5В, напруга живлення контролера - 5В. Тому резистор слід обрати опором (5-2,5)/0,01=250 Ом. У нас послідовно з портами включені резистори на 510 Ом, тому ми їх і залишимо, просто струми будуть менші і відповідно, яскравість індикатора теж. Проте червоні індикатори мають досить високу яскравість, тому все буде видно добре. Схема підключення до макетної плати:
Повна схема пристрою:
Як бачимо, ми приєднали виводи анодів індикатора A-G до виводів РА0...РА6 відповідно. Катоди через трнзистори приєднані до виводів РВ4, РВ5, РВ6.
Відео:
Отже ми можемо записати значення, які виводяться в портА, що відповідають цифрам
0 - 0b00111111
1 - 0b00000110
2 - 0b01011011
3 - 0b01001111
4 - 0b01100110
5 - 0b01101101
6 - 0b01111101
7 - 0b00000111
8 - 0b01111111
9 - 0b01101111
Ці значення називаються кодом цифри. Припустимо, ми хочемо вивести значення числа (від 0 до 9), що знаходиться в регістрі РРЗП RZ на семисегментний індикатор. Тоді потрібно скласти підпрограму перекодування приблизно такого змісту:
1. Якщо RZ=0 то записати 0b00111111 в PORTA;
2. Якщо RZ=1 то записати 0b00000110 в PORTA;
3. Якщо RZ=2 то записати 0b01011011 в PORTA;
4. Якщо RZ=3 то записати 0b01001111 в PORTA;
5. Якщо RZ=4 то записати 0b01100110 в PORTA;
6. Якщо RZ=5 то записати 0b01101101 в PORTA;
7. Якщо RZ=6 то записати 0b01111101 в PORTA;
8. Якщо RZ=7 то записати 0b00000111 в PORTA;
9. Якщо RZ=8 то записати 0b01111111 в PORTA;
10. Якщо RZ=9 то записати 0b01101111 в PORTA;
Тобто просто lpm без параметрів. Такий формат команди підтримується усіма МК AVR, але вимагає використання саме регістра r0.
Після команди повернення з підпрограми бачимо рядок з міткою ADR0, а далі .db 0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110, 0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111 ; Таблиця кодів
Параметрами директиви є розділені комами значення даних, що зберігаються у зарезервованих байтах. Можуть також використовуватися вирази. АЛЕ, ЯКЩО ЗНАЧЕННЯ ВИРАЗУ ПЕРЕВИЩУЄ РОЗМІРИ БАЙТА, ТО ЗАПИШЕТЬСЯ ТІЛЬКИ МОЛОДШИЙ БАЙТ!!!
Відеоопис програми (повний екран, HD відео):
Демонстрація в режимі відладки (повний екран, HD відео):
Прошиваємо в МК і дивимось на результат виконання:
В наступній статті я продовжу тему динамічної індикації, напишу декілька складніших програм з її використанням. Ідеї вже є :-).
Максимальний струм сегмента індикатора рівний 20ма, тому індикатор слідвключати через обмежуюяі резистори. Встановимо струм сегмента у 10ма. Для червоного світлодіода падіння напруги приблизно 2,5В, напруга живлення контролера - 5В. Тому резистор слід обрати опором (5-2,5)/0,01=250 Ом. У нас послідовно з портами включені резистори на 510 Ом, тому ми їх і залишимо, просто струми будуть менші і відповідно, яскравість індикатора теж. Проте червоні індикатори мають досить високу яскравість, тому все буде видно добре. Схема підключення до макетної плати:
Повна схема пристрою:
Відео:
Кодування цифр для семисегментного індикатора
Тепер розглянемо принцип формування цифр семисегментним індикатором. Схематичне зображення цифр утворюється відповідною комбінацією включених сегментів. Отже, кожній цифрі від 0 до 9 відповідатиме певна комбінація лог.0 і 1 на виводах порта А, а отже, якесь число, записане у цей порт. Назвемо це число кодуванням. Складемо таблицю кодувань:Отже ми можемо записати значення, які виводяться в портА, що відповідають цифрам
0 - 0b00111111
1 - 0b00000110
2 - 0b01011011
3 - 0b01001111
4 - 0b01100110
5 - 0b01101101
6 - 0b01111101
7 - 0b00000111
8 - 0b01111111
9 - 0b01101111
Ці значення називаються кодом цифри. Припустимо, ми хочемо вивести значення числа (від 0 до 9), що знаходиться в регістрі РРЗП RZ на семисегментний індикатор. Тоді потрібно скласти підпрограму перекодування приблизно такого змісту:
1. Якщо RZ=0 то записати 0b00111111 в PORTA;
2. Якщо RZ=1 то записати 0b00000110 в PORTA;
3. Якщо RZ=2 то записати 0b01011011 в PORTA;
4. Якщо RZ=3 то записати 0b01001111 в PORTA;
5. Якщо RZ=4 то записати 0b01100110 в PORTA;
6. Якщо RZ=5 то записати 0b01101101 в PORTA;
7. Якщо RZ=6 то записати 0b01111101 в PORTA;
8. Якщо RZ=7 то записати 0b00000111 в PORTA;
9. Якщо RZ=8 то записати 0b01111111 в PORTA;
10. Якщо RZ=9 то записати 0b01101111 в PORTA;
Реалізація підпрограми перекодування на основі таблиці кодів
Проте така реалізація підпрограми перекодування не буде оптимальною, частіше застосовують інакший метод, на основі таблиці кодів. Суть полягає в тому, що в ячейках пам'яті програм, починаючи з певної адреси послідовно записують коди чисел від 0 до 9 (або інші коди, що необхідні). Тоді, якщо код нуля розміщений у ячейці з адресою ADDR0 (це символічна назва ), то код 1 буде розміщений у ячейці з адресою ADDR0+1, код 2 - у ячейці з адресою ADDR0+2 і так далі. Бачимо, що при цьому уся підпрограма буде мати вигляд:
1. Додати до ADDR0 значення РРЗП RZ
2. Зчитати з ячейки з отриманою адресою код
3. Вивести код в портА.
Бачимо, що підпрограма стала значно простішою.
При реалізації даного методу варто пам'ятати одне правило:
ТАБЛИЦЯ КОДІВ ПОВИННА ЗНАХОДИТИСЬ У ТАКОМУ МІСЦІ ПРОГРАМНОЇ ПАМ'ЯТІ, ДО ЯКОГО НІ ЗА ЯКИХ ОБСТАВИН НЕ ПЕРЕЙДЕ ВИКОНАННЯ ПРОГРАМИ!!!
Тобто, таблиці кодів чи інших констант слід розміщувати або після усіх підпрограм, або між ними після команди RET і перед міткою початку наступної підпрограми. Можна розмістити таблицю і всередині програми, перескочивши через неї командою RJMP, але це небажано, так як порушує структуру програми.
Практично підпрограма на асемблері реалізується наступним чином - нехай у нас є регістр RZ що містить початкове число для відображення (від 0 до 9). Підпрограма записує код наданого числа в регістр COD.
Recode: ;Мітка початку підпрограми перекодування
ldi ZL, LOW(ADR0*2) ; Завантажуємо у регістр ZL (r30) молодший байт адреси початку таблиці кодів
ldi ZH, HIGH(ADR0*2) ; Завантажуємо у регістр ZН (r31) старший байт адреси початку таблиці кодів
ldi COD,0 ; використаємо регістр COD для тимчасових даних, запишемо в нього 0
add ZL,RZ ; додаємо до молодшого байту адреси значення RZ
adc ZH,COD ; додаємо до старшого байту адреси 0 і флаг переносу (якщо виник у попередньому додаванні)
lpm COD,Z ; завантажуємо код з програмної памяті у регістр COD (адреса в регістровій парі Z)
ret; повернення з підпрограми
ADR0:
.db 0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110, 0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111 ; Таблиця кодів
Тепер пояснення до написаної програми.
ZL ZH - це системні символічні імена регістрів r30 і r31, що утворюють так звану регістрову пару Z. Є ще регістрові пари X(r26 і r27) та Y(r28 і r29). Регістрові пари використовуються для команд доступу до даних, що пов'язані з адресацією, адже адреси ячейок пам'яті в AVR зазвичай двохбайтні. У регістрах XL або YL або ZL зберігають молодший байит адреси , а у XН або YН або ZН - старший.
Ми завантажуємо у ZL ZH байти адреси початку нашої таблиці командами
ldi ZL, LOW(ADR0*2)
ldi ZH, HIGH(ADR0*2)
Тут використовуються функції препроцесора LOW HIGH що дають відповідно молодший і старший байт виразу. А от що за вираз в дужках?
Ми бачимо, що там знаходиться мітка початку таблиці ADR0. Справа в тому, що в асемблері текстова мітка - це фактично символічне ім'я константи, в якій знаходиться адреса даного місця в програмній пам'яті. Її значення розраховується при компіляції. Але, так як в основному команди МК займають в пам'яті програм по 2 байта (так зване слово), то мітка містить адресу, виражену в словах (так вона рахується програмним лічильником при виконанні програми). Але у командах роботи з пам'яттю адресація звичайна, побайтова, тому ми і виконуємо в дужках множення ADR0 на 2!
Далі ми записуємо у регістр COD нуль. Так як цей регістр буде містити нове значення після виконання підпрограми то ми використаємо його як тимчасовий. Це нульове значення потрібне буде для виконання команди додавання з переносом, адже чомусь Атмел не реалізували команду додавання регістру і константи, є лишень додавання двох регістрів з переносом (віднімання регістру і константи є!!!).
Група команд:
add ZL,RZ ; додаємо до молодшого байту адреси значення RZ
adc ZH,COD ; додаємо до старшого байту адреси 0 і флаг переносу (якщо виник у попередньому додаванні)
виконує додавання до двохбайтного числа, що знаходиться в репгістровій парі Z значення регістру RZ. Таким чином обчислюється адреса необхідного коду в таблиці (що відповідає значенню RZ).
Двохбайтне додавання виконується по стандартній схемі багатобайтної арифметики - з використанням флагу переносу.
Команда ADD[РРЗП1],[РРЗП2] виконує додавання до РРЗП1 значення РРЗП2. Якщо виникає переповнення (результат перевищує розмір байта, тобто більший від 255), то в РРЗП1 запишеться число (ррзп1+ррзп2-256) і буде встановлений флаг переносу С
Команда ADC[РРЗП1],[РРЗП2] виконує додавання до РРЗП1 значення РРЗП2 і значення флагу переноса С (Р1+Р2+С). У разі переповнення результату працює аналогічно до попередньої команди.
Далі ми бачимо рядок:
lpm COD,Z ; завантажуємо код з програмної памяті у регістр COD (адреса в регістровій парі Z)
Команда LPM[РРЗП],Z завантажує в РРЗП число, що знаходиться в програмній пам'яті по адресі, що записана в регістровій парі Z.
Не знаю, чи такий формат підтримується усіма МК AVR, адже часто використовується команда:
LPM завантажує в РРЗП r0 число, що знаходиться в програмній пам'яті по адресі, що записана в регістровій парі Z.
Після команди повернення з підпрограми бачимо рядок з міткою ADR0, а далі .db 0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110, 0b01101101, 0b01111101, 0b00000111, 0b01111111, 0b01101111 ; Таблиця кодів
Директива препроцесора .DB резервує байти в програмній пам'яті або в енергонезалежній (EEPROM). Якщо перед директивою є мітка, то вона буде вказувати на перший зарезервований байт. В програмній пам'яті байти будуть розміщені у "словах" по 2 байти у слові, при непарній кількості байтів в останньому слові старший байт буде заповнений нулями.
Параметрами директиви є розділені комами значення даних, що зберігаються у зарезервованих байтах. Можуть також використовуватися вирази. АЛЕ, ЯКЩО ЗНАЧЕННЯ ВИРАЗУ ПЕРЕВИЩУЄ РОЗМІРИ БАЙТА, ТО ЗАПИШЕТЬСЯ ТІЛЬКИ МОЛОДШИЙ БАЙТ!!!
Переходимо до практики.
Перше напишемо просту програму динамічної індикації, яка виводить на індикатор число 287. Зарезервуємо регістри для значень кожного розряду індикатора rozrad1, rozrad2, rozrad3 і поки що присвоїмо їм значення 7, 8 і 2 відповідно. Також визначимо регістри RZ, COD, Cathode. Далі, використавши принцип скінченого автомата напишемо підпрограму динамічної індикації. Також буде використовуватись підпрограма перекодування, написана вище.
1. Змінна стану автомата індикації Cathode=0b01000000
2. Початок головного циклу
3. Виклик підпрограми скінченого автомата підготовки даних для індикації (7)
4. Викликаємо пыдпрограму перекодування RZ в COD
5. Виводимо СОD в порт А, Cathode - в порт В.
6 Виклик підпрограми часової затримки на 0,01с
7. Перехід до п.2 (зациклюємось)
8. Скінчений автомат підготовки даних для динамічної індикації
8.1 якщо Cathode=0b01000000 то в RZ записуємо значення rozrad1;
змінюємо значення Cathode=0b00010000
Виходимо з підпрограми
8.2 якщо Cathode=0b00010000 то в RZ записуємо значення rozrad2;
змінюємо значення Cathode=0b00100000
Виходимо з підпрограми
8.3 якщо Cathode=0b00100000 то в RZ записуємо значення rozrad3;
змінюємо значення Cathode=0b01000000
Виходимо з підпрограми
Скачати ASM файл
В принципі, програма повинна бути зрозумілою, вона містить багато частин з попередніх експериментів - наприклад підпрограму часової затримки, макрос для запису константи в "молодші" РРЗП та ін. Єдине що - перед безпосередньо командами програми (перед міткою Reset) бачимо рядок:
.cseg; Початок програмного сегмента
В принципі, програма повинна бути зрозумілою, вона містить багато частин з попередніх експериментів - наприклад підпрограму часової затримки, макрос для запису константи в "молодші" РРЗП та ін. Єдине що - перед безпосередньо командами програми (перед міткою Reset) бачимо рядок:
.cseg; Початок програмного сегмента
Директива препроцесора .CSEG вказує початок програмного сегменту пам'яті (тобто все, що знаходиться нижче, якщо немає інших директив, розміщується в програмній пам'яті).
Раніше ми не використовували цю директиву, тому що крім команд МК наша програма нічого не містила. Тут же програма містить таблиці даних. В нашому випадку вони теж знаходяться у програмній пам'яті. Без директиви .CSEG компілятор може видає попередження про те, що немає відомостей про розміщення таблиці даних (таблиця з директивою .db може описувати як дані у програмній пам'яті так і в EEPROM)!Відеоопис програми (повний екран, HD відео):
Демонстрація в режимі відладки (повний екран, HD відео):
Прошиваємо в МК і дивимось на результат виконання:
В наступній статті я продовжу тему динамічної індикації, напишу декілька складніших програм з її використанням. Ідеї вже є :-).
Немає коментарів:
Дописати коментар