Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

OMT (1)

.pdf
Скачиваний:
17
Добавлен:
17.05.2015
Размер:
2.52 Mб
Скачать

виконується мікропроцесором 8051 за допомогою такої послідовності команд, що виконують оброблення 8-розрядних даних:

mov A, FirstVar

Вибрати першу змінну

add A, SecondVar

Скласти з другою змінною

mov FirstVar, A

 

При використанні 16-розрядних змінних програмний код ускладнюється:

mov A, FirstVar add A, SecondVar mov FirstVar, A mov A, FirstVar +1

addc A, SecondVar + 1 mov FirstVar + 1, А

Скласти молодші 8 біт

Скласти старші 8 біт Додавання з переносом

Цей програмний код реалізується мікроконтролерами, що виконують команди додавання з переносом (addc). Для мікроконтролерів, які не виконують команди додавання з переносом, програмний код ще більше ускладнюється:

mov A, FirstVar add A, SecondVar mov FirstVar, A jnc Skip

inc FirstVar + 1

Skip

mov A, FirstVar + 1 add A, SecondVar + 1 mov FirstVar + 1, A

Скласти молодші 8 біт

Якщо перенос не встановлений, то пропустити збільшення Збільшити старші 8 біт результату

У даному прикладі представлений відносно простий варіант виконання таких операцій. Якщо операція стає більш складною (наприклад, «FirstVar = SecondVar + (ThirdVar * FourthVar)»), то ускладнення програмного коду має експонентний характер. При цьому використання даних великої розрядності може привести до надмірного уповільнення чи обчислень, зажадати більшого обсягу пам’яті, ніж є в системі.

Операції над числами, що мають розрядність більше базової (8 біт), часто реалізуються за допомогою бібліотечних функцій, що збільшують час виконання й обсяг необхідної пам’яті. Для багатьох застосувань дуже корисне використання 16-розрядних даних, оброблення яких забезпечується за допомогою невеликого збільшення обсягу програмного коду і пам’яті

100

даних. У той же час 16-розрядні дані забезпечують досить великий діапазон подання оброблюваних даних. При правильній організації обчислень обробка даних практично для всіх вбудованих додатків, що використовують 8-розрядні мікроконтролери, може бути виконана з використанням 8- і 16-розрядних цілих чисел.

У складних операціях (таких, як «FirstVar = SecondVar + (ThirdVar * FourthVar)») програмний код може бути реалізований за допомогою ряду стекових операцій

push SecondVar push ThirdVar push FourthVar mul

add

pop FirstVar

Ці стекові операції не є командами процесора, а є послідовністю операцій, що забезпечують одержання необхідного результату. Компілятори часто роблять оптимізацію цієї послідовності операцій, перш ніж перетворити їх у команди процесора. У деяких процесорах такий код може бути дуже ефективним. У залежності від якості компілятора отриманий об’єктний код може бути також ефективний як код, написаний вручну, або в 100 разів (за кількістю необхідних команд і циклів їхнього виконання) менш ефективним.

Для деяких компіляторів подання реалізованої процедури за допомогою декількох командних рядків може бути більш ефективним у порівнянні з їх поданням у вигляді одного рядка. Наприклад, процедура, описана рядком

FirstVar = SecondVar + ( ThirdVar * FourthVar );

більш ефективно реалізується у вигляді такої послідовності операцій:

Temp = ThirdVar * FourthVar;

FirstVar = SecondVar + Temp;

Інше важливе розуміння стосується того, як компілятор перетворить числа й обробляє проміжні змінні. Якщо в приведеному вище прикладі змінна «FirstVar» не визначена як порт введення-виведення, то можна подати програмний код у такому вигляді:

FirstVar = ThirdVar * FourthVar;

FirstVar = FirstVar + SecondVar;

Таке подання є оптимізацією програми, тому що компілятор враховує, що змінна «FirstVar» не використовується при виконанні операції, і її можна використовувати для збереження проміжних результатів. Проблеми можуть виникнути, якщо змінна «FirstVar» слугує для подання вмісту регістрів введення-виведення. У цьому випадку запис проміжного результату може бути сприйнято зовнішнім устаткуванням, як змінна «FirstVar».

101

У багатьох мовах високого рівня існує два типи змінних. «Глобальні» змінні визначені для використання протягом усієї програми, і їхні значення не можуть перевизначатися. Як глобальні, звичайно, задаються змінні, котрі використовуються в основному тілі програми чи потрібні при виконанні багатьох підпрограм (у цьому випадку передавання значення змінної як параметр підпрограми не ефективна). Глобальні змінні – це єдиний тип змінних, використаних при програмуванні мовою асемблера доти, поки не будуть визначені локальні змінні.

«Локальні» («автоматичні») змінні використовуються при виконанні конкретної підпрограми і створюються при звертанні до неї. Призначення однієї локальної змінної в двох підпрограмах, що не вкладені одна в одну, означає, що компілятор буде використовувати тільки одне значення змінної. Звичайно, локальні змінні завантажуються в стек при виклику підпрограми і витягаються з нього, коли керування повертається програмі, що викликала. Значення локальної змінної губиться після виходу з підпрограми. Параметри, передані підпрограмі, звичайно (але не завжди) є локальними змінними. Це означає, що вони можуть модифікуватися при виконанні підпрограми, але їхні первісні значення, передані підпрограмі, будуть зберігатися.

Покажчики і структури даних є важливими засобами підвищення ефективності програмування при використанні великих обсягів пам’яті. Покажчики звичайно використовуються в системах, де пам’ять є керованим ресурсом. Звичайно, вони не використовуються при програмуванні мікроконтролерів, що мають малий обсяг пам’яті. При розробці прикладних програм для цих мікроконтролерів замість покажчиків використовуються інші методи. Основний метод заміни покажчиків полягає в створенні масиву на початку розділу пам’яті, що передбачається використовувати. Потім для звертання до будь-якого елемента в цій пам’яті використовуються індекси. Організація структур даних дуже корисна при багатьох застосуваннях мікроконтролерів. Структурою даних називається блок пам’яті, що використовується для визначення стандартного записування даних. Наприклад, структуру процесорної команди мовою С можна визна-

чити в такий спосіб:

 

struct instruct

// Формат команди

int

address;

// Адреса команди

char instruct;

// Команда

int

value;

// 16бітні дані

Звичайно, на структуру посилаються за допомогою покажчика, але ті ж дані можна помістити в масив 16-розрядних слів (цілочислові дані типу int мовою С) і посилатися на нього за допомогою індексу. Щоб вибрати необхідний елемент у масиві даних, звертаються до значення індексу, що задає адресу початкового елемента масиву, додаючи зсув, що вказує позицію обираного елемента.

102

Покажемо як це реалізується на практиці. Якщо потрібно вибрати деяку величину з області даних, то при використанні покажчика необхідний програмний код має такий вигляд:

struct instruct * Ptr

// Визначення покажчика на структуру

i = Ptr-> value;

//Прочитати «value» з поточного елемента

При організації масиву ця процедура виглядає в такий спосіб:

struct instruct Array[100] // Визначення масиву структур

:

i == Array[ Index ].value;

Цей код набагато складніший коду з покажчиком, але має одну перевагу: в ньому не використовується покажчик, за яким необхідно стежити, що б він завжди коректно звертався до необхідного елемента структури.

Інша структура даних, яку необхідно розглянути – це таблиця. Таблицю можна подати як одновимірний масив незмінних рядків даних. Звичайно, це інформація про стан системи чи повідомлення для користувача мовою С, і виглядає в такий спосіб:

char Greeting [13] = "Hello there I";

Компілятор мови повинний розмістити ці таблиці в пам’яті програм (ROM), щоб зберегти вільну оперативну пам’ять RAM, при використанні якої треба було б розміщати в ній елементи масиву і робити їхню ініціалізацію. Інформація в таблиці вибирається як масив, ідентичний масивам, що зчитується з RAM.

У мікроконтролерах програмний код повинний мати доступ до апаратних регістрів. Звичайно, це робиться двома методами. Перший – дозволити вставку асемблерних інструкцій у тіло програми, написаної мовою високого рівня. Другий – дозволити користувачу визначити деякі позиції в адресному просторі даних для звертання до цих регістрів. Обидва методи є відхиленням від стандарту мови, прийнятого для персональних комп’ютерів і робочих станцій. Але ці методи, звичайно, підтримуються мовами високого рівня, використовуваними для програмування мікроконтролерів.

Останнє, що залишилося розглянути – це спосіб передачі символічної інформації до симулятора чи емулятора. Символічна інформація – це посилання на використовувані рядки, мітки, змінні і вказання на їхнє розміщення в пам’яті. Ця інформація використовується симуляторами і емуляторами, щоб відображати виконання вихідного програмного коду. У такий спосіб налагодження ведеться на рівні вихідного тексту, а не на рівні асемблера.

Не слід думати, що всі компілятори видають однакову символічну інформацію (чи видають її взагалі). Може скластися така ситуація, коли не

103

вдається знайти версію компілятора для обраної мови, що передавала б необхідну символічну інформацію для використовуваного симулятора.

Існує ряд основних особливостей мов високого рівня, які варто враховувати при їхньому виборі для програмування мікроконтролера. Приведені тут коментарі стосуються як проектування програмного забезпечення, так і особливостей функціонування мікроконтролера. Облік цих особливостей допоможе вибрати найбільш ефективний варіант компілятора.

Залишається ще одне питання: навіщо взагалі використовувати мову високого рівня? Існує кілька причин для цього.

Перша – перенос коду. При використанні мов високого рівня існує безліч готових програм і алгоритмів, який можна безпосередньо вставити в розроблювальну прикладну програму. Може виникнути необхідність перенесення програм на інші мікроконтролери даного сімейства чи на інші сімейства мікроконтролерів. При використанні мов високого рівня таке перенесення виконується набагато простіше.

Інша причина, чому варто розробляти додаток мовою високого рівня - відсутність обмежень, властивих програмуванню на асемблері. При цьому користувач одержує різні додаткові можливості, наприклад, автоматично вводити в програму коди складних операцій чи робити ефективний контроль синтаксичних помилок. Необхідно відзначити, що програмна реалізація складних операцій через труднощі й об’ємність програмування мовою асемблер може привести до менш ефективного програмного коду, ніж код, отриманий компілятором з мови високого рівня. Остання і ймовірно найбільш важлива причина використання мов високого рівня – необхідність підтримки програми. Часто потрібно модифікувати чи налагодити написаний код через декілька місяців чи років. При розробці програм мовою високого рівня ця задача значно спрощується.

3.5 Програми, критичні до часу виконання

Хоча мікроконтролери мають багато вбудованих апаратних засобів для виконання точних тимчасових операцій, існують випадки, коли необхідно створити програмний код, що буде робити операції введеннявиведення за графіком. Прикладом цього може служити керування асинхронним послідовним обміном без використання апаратних засобів послідовного зв’язку. Це означає, що інтервали часу між посилкою чи опитуванням бітів повинні бути точно витримані.

Перше, що приходить у голову – це використання таймера і переривань, але такий спосіб часто не дає необхідної точності для розроблювальної програми. Розглянемо приклад програми для читання-запису послідовних даних зі швидкістю 9600 бод, що реалізується мікроконтролером, який має тривалість командного циклу 1,0 мкс. При ілюстрації деяких виникаючих проблем припустимо, що всі команди виконуються за один цикл, крім команд переходів, що займають три цикли.

104

Швидкість 9600 бод відповідає періоду часу 104,167 мкс. Це означає, що часовий інтервал для читання і запису даних повинний займати точно 104 цикли (помилка порядку 0,16%). Нехай для читання даних (приймання) використовується програмний код:

mov

Count, 8

 

Loop

 

Одержати 8 біт і зберегти в «Char»

mov

A, Dеlay

Затримка на 104 циклу

DelayLoop:

 

 

dec

A

 

jnz

DelayLoop

 

rr

Char

Зрушити "Char" вправо

mov

A, Port

Останній прочит.біт тепер на 6-й позиції

and

А, 0х001

Одержати дані з порту А

jz

Skip

Перевірити біт 0 у порту А

or

Char, 0х080

Установити старший біт у Char

Skip

 

 

dec

Count

Прочитано 8 біт ?

jnz

Loop

Якщо «не нуль», то «ні»

Тепер треба визначити значення змінної «Dеlay».

Спочатку обчислимо «власне» час циклу — час, необхідний для організації його запуску. Дивлячись на дану програму можна помітити таку проблему: існує різниця в один командний цикл між двома вітками програми – коли біт встановлений у “1” чи “0”.

Якщо вхідний біт встановлений у “0”, то виконується перехід за командою «jz» (умова правильна), що займає три командних цикли. Якщо цей біт встановлений у “1”, то команди «jz» (умова помилкова, і перехід не відбувається) і «or» виконуються за два командних цикли. Щоб виконання програми завжди забирало однаковий час, необхідно додати дві команди після команди «or Char, 0х080».

Після цього час програмного циклу складе 14 командних циклів. Необхідно додатково виконати 90 циклів для реалізації необхідної затримки. Кожен прохід програми затримки вимагає чотири цикли (один для команди «dec А» і три – для команди «jnz DelayLoop»). У такий спосіб значення «Delay» варто прийняти рівним 23 (при цьому програма затримки буде пройдена 22 рази), а загальний час затримки містить 88 командних циклів. Далі додаємо до цього 13, і усе разом складе 101 командний цикл (помилка 2.98%). Додаючи три команди «or» чи один перехід, одержуємо точно 104 командних циклів.

Щоб визначити, який рівень помилки є прийнятним, необхідне розуміння того, що виконує програма. Для асинхронного інтерфейсу ця помилка збільшується, як мінімум на десять, тому що передаються вісім бітів даних, один старт-біт і один стоп-біт. У такий спосіб помилка в 0.86% при передачі одного біта приводить до помилки 8,6% при передаванні всього

105

пакета, що має довжину 10 біт. Імовірно, це прийнятно. Помилка в 2.98% на один біт приведе до майже 30% помилки наприкінці передавання байта, що може бути неприйнятно для ряду додатків.

Створення програми для послідовного передавання (записування) даних, багато в чому, аналогічно створенню програмного коду для приймання (читання), але з одним важливим зауваженням. Кожна подія, виведення “1” чи “0”, повинна відбуватися протягом того самого проміжку часу, інакше приймач може пропустити дані. Нижче даний приклад програмного коду для передавання байта зі швидкістю 9600 біт.

mov

Char, 8

 

Loop

 

Вивести 8 біт

mov

A, Dеlay

Затримка на 104 циклу

DelayLoop

 

 

dec

A

 

jnz

DelayLoop

 

mov

A, Char

Вивести 0 чи 1

and

A, 1

Послати молодші біт першим

jz

SendZero

Вивести 0

or

Port, 2

Використовувати біт 1 для виводу

goto

SentBit

 

SentZero

 

Вивести нуль

and

PortO,0x0FD

 

SentBit

 

 

rr

Char

Наступний біт на місце молодшого

 

 

dec

Count

Зробити 8 разів

 

 

jnz

Loop

 

У прикладі читання дані, що надходять, запитувались програмно. При записуванні передані дані видаються в двох різних точках. Значення „1” видається на три цикли раніш, ніж значення „0”. Це можна легко виправити шляхом введення двох команд «jz» перед виконанням команди «or Port, 2». Однак у цьому випадку при посиланні „0” перехід до мітки «SentBit» реалізується на три команди раніш, ніж при посиланні „1”. Можна поставити команду безумовного переходу «goto SentBit» слідом за командою «and Port, 0x0FD». У такому випадку при будь-якому значенні даних вони будуть йти на вихід порту протягом того самого числа циклів після початку процедури «Loop». Після того, як усі можливі вітки програми збалансовані за часом виконання, і виведення даних забезпечене у ті самі моменти часу, можна зробити розрахунок параметра «Delay», як це було виконано в прикладі читання даних.

Цей приклад ймовірно покажеться трохи надуманим і надмірно складним. У дійсності ж він є відносно простим, якщо врахувати як реально виконуються в мікроконтролерах командні цикли й умовні розгалужен-

106

ня. Деякі процесори мають набагато ширший діапазон необхідних командних циклів, ніж прийнято в розглянутих прикладах.

В області додатків, що вимагають точного обліку часу виконання програм, використання RISC-системи команд і Гарвардської архітектури може дати значні переваги. У цьому випадку команди завантажуються протягом одного циклу (звичайно при виконанні попередньої команди) і виконуються в наступному циклі. Тому різниця в часі виконання команд виявляється істотно менша, і розробка програмного забезпечення для формування необхідних затримок значно спрощується.

3.6 Макроси й умовна компіляція

Застосування процедур макросів і умовної компіляції дозволяє спростити розробку і читання (а виходить, і розуміння) програм. Ці засоби дозволяють писати узагальнений програмний код, що може бути використаний для багатьох специфічних випадків, замість того, щоб створювати спеціальне програмне забезпечення для кожного з цих випадків окремо.

Макрос може розглядатися як функція, що заміщає в програмі її оператор (макровиклик), у той час як підпрограма розміщується поза основним програмним кодом. Наприклад, при програмуванні інтерфейсу, що вимагає подачі тактового сигналу Е, можна використовувати код:

push

A

Зберегти акумулятор

mov

А, 0х0001

Установити високий рівень сигналу Е

or

Port, A

 

xor

Port, A

Установити знову низький рівень сигналу Е

pop

A

Відновити акумулятор

Якщо ця процедура використовується багаторазово, то вихідний текст програми стає громіздким і важким для розуміння. Щоб спростити вихідний текст, приведений програмний код можна подати у вигляді макросу:

pulse_E macro

Імпульс по лінії «Е»

push

A

Зберегти акумулятор

mov

А,0х000

Установити високий рівень сигналу Е

or

1

 

xor

Port, A

Установити знову низький рівень сигналу Е

pop A

Port, A

 

macroend

 

 

Тепер щоразу, коли треба сформувати імпульс на лінії Е, вищенаведений код буде вставлений у вихідний текст замість макровиклику: pulse_E

107

Макроси можуть також містити параметри. Якщо в приведеному прикладі потрібно забезпечити видачу імпульсу на довільний вивід порту, то варто виконати необхідні маніпуляції з відповідним бітом у регістрі порту. Для цього макрос перетвориться:

pulse macro bit

push

A

mov

А, 1 « bit

or

Port, A

xor

Port, A

pop A

 

macroend

 

Імпульс на лінії Е Зберегти акумулятор Визначити біт Установити біт у 1 Скинути біт у ПРО

Тепер замість макросу, що призначений для роботи з додатками, де лінія Е підімкнена до виводу „0” порту, можна використовувати макрос, що формує імпульс на будь-якому заданому виводі порту. Наприклад, макровиклик : pulse 2 вставить у програму макрос, що забезпечить видачу імпульсу на вивід порту. Поряд з макросами в програмі може використовуватися умовно комплексний код, що визначає, чи треба виконувати задану процедуру, у залежності від значення спеціальних параметрів. Ці параметри використовуються тільки під час компіляції і вказують, чи треба включати визначену частину програмного коду у вихідний текст.

Повертаючись до першого прикладу, можна ввести макрозмінну «debug», що дозволить виконувати спеціальні процедури в процесі налагодження. У нашому прикладі формування сигналу Е для ЖК-індикатора. Можна реалізувати інверсію сигналу на виводі, що підімкнений до світлодіоду, щоразу, коли подається імпульс Е. У такий спосіб буде забезпечена візуальна індикація (засвічення світлодіода), коли відбувається записування даних у ЖКІ.

Більшість трансляторів перевіряють виконання умов компіляції перед тим, як компілювати файл. В асемблерах ці умови, звичайно, подані у формі «If (Умова)/еlsе/еnd» чи у формі «ifdef (Параметр)». Остання форма дозволяє компілювати програмний код за умови, що параметр визначений раніше. У мовах високого рівня, що використовують формат «Іf(Умова)/else/end» використовуються трохи інші оператори умовної компіляції, наприклад, у мові С використовується оператор «%if».

Тепер перетворимо наш макрос таким чином, щоб інвертувати біт 7 порту А, до виводу якого підімкнутий світлодіод, коли параметр «Debug» визначений. Одержуємо макрос:

pulse macro bit

Імпульс по лінії Е

push

A

Зберегти акумулятор

mov

А, 1<< bit

Визначити біт

or

Port, A

Установити біт у 1

108

xor

Port, A

Скинути біт у 0

ifdef

Debug

Якщо "Debug" визначений

mov

А, 0х080

Інвертувати біт 7

xor

Port, A

 

endif

A

 

pop

 

 

macroend

 

 

Умовно комплексний програмний код можна поміщати не тільки в макросах. Вони можуть бути використані й у тілі основної програми. Умовно комплексні коди обробляються в один час з макросами, тому вони, звичайно, розглядаються разом з ними. Навіть якщо Ви ніколи раніше не використовували асемблер, макроси й умовне компілювання, імовірно, Вам знайомі. Що Ви могли не знати – це те що макроси й умовне компілювання широко застосовуються в мовах високого рівня. Твердження «#define» у мові С – фактично макрос, а оператор «#if/#else/ #end» використовується для умовної компіляції.

4 МІКРОКОНТРОЛЕРИ ФІРМИ ATMEL

Отже, тепер, коли ми маємо первинне уявлення про мікроконтролери

іпро те, що можна зробити з їх допомогою, прийшов час поговорити про створення конструкцій на мікроконтролерах.

Один з варіантів послідовності дій при розробці конструкцій на мікроконтролерах приведений нижче.

1.По-перше, дуже важливо точно визначити технічні вимоги до конструкції, причому робити це бажано письмово – звичайно в процесі записування виявляється багато нюансів, не відразу помітних при обмірковуванні конструкцій.

2.Скласти докладний опис конструкції так званого верхнього рівня –

іна цьому етапі ще невідомо ні типу мікроконтролера, ні типу використаних мікросхем і схемних рішень, тому структурна схема являє собою набір прямокутників з надписами найменування вузла, наприклад, АЦП. Складається узагальнена блок-схема, що описує роботу програми. Якщо потрібно

– тимчасові діаграми.

3.Визначитися з вибором апаратних вузлів (мікросхем і т.д.) для

схеми.

4.Вибрати тип мікроконтролера.

5.Переконатися в тім, що мікроконтролер підходить для реалізації схеми. Варто враховувати швидкодію мікроконтролера, наявність потрібної периферії, число ліній введення-виведення, споживану потужність і інші, істотні для конкретної конструкції параметри. Не є доцільним «бити з гармати по горобцях» – використовувати більш могутній мікроконтролер

109

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]