Зробимо простий avr мікроконтролер

Мене часто запитують: "Чим відрізняється мікроконтролер від ПЛІС?" Ну що тут можна відповісти? Це як би різні речі. Мікропроцесор послідовно виконує команди, описані в його програмі. Робота ПЛІС в кінцевому рахунку визначається принциповою електричною схемою, реалізованої всередині чіпа. Архітектура мікроконтролерів, тобто тип процесора, кількість портів введення виведення, інтерфейси, визначається виробником. Мікросхема мікроконтролера виготовлена ​​на заводі і змінити її не можна. Можна тільки написати програму, яку він виконуватиме. ПЛІС - це свобода для творчості. Архітектура реалізованого пристрою може бути майже будь-яка, лише б помістилася вся логіка в чіп. У ПЛІС можна, наприклад, спробувати реалізувати навіть і мікроконтролер! Спробуємо?

Один з найпоширеніших мікроконтролерів - це 8-ми розрядні RISС процесори сімейства AVR компанії Atmel. У цій статті я розповім як реалізувати "майже" сумісний з AVR мікроконтролер всередині нашої ПЛІС на платі Марсохід.

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

Ми не будемо ставити собі за мету повністю повторити поведінку чіпа Atmel, ми хочемо зробити наш мікропроцесор лише частково сумісним. Повністю повторити можна, але потрібна ПЛІС набагато більшого обсягу. У нас на платі Марсохід варто CPLD EPM240T100C5, значить у нас є всього-на-всього 240 тригерів і логічних елементів.

Крім тригерів і логіки в нашій ПЛІС є послідовна флеш пам'ять UFM об'ємом 512 слів по 16 біт. У цій флеш пам'яті ми будемо зберігати програму мікроконтролера. Зручно, що слова, збережені у флеш, мають розрядність 16. Всі команди процесора AVR також шестнадцатіразрядного. Дещо про UFM ми вже писали на нашому сайті. У нас був проект для ПЛІС плати Марсохід. який виконував читання з UFM пам'яті.

"Оперативної пам'яті" в нашій ПЛІС немає. Ну значить не буде пам'яті у нашого мікроконтролера, шкода але це нас не зупинить.

У мікроконтролера AVR є 32 восьмирозрядних регістру загального призначення. Нижня група регістрів r0-r15 може бути використана тільки в командах з операндами-регістрами. Верхня група регістрів r16-r31 може використовуватися в командах і з безпосередніми операндами. Оскільки місця всередині нашого чіпа на платі Марсохід дійсно не багато, нам доведеться реалізувати тільки деякі регістри. Це досить суттєве обмеження, і його потрібно буде враховувати при написанні програм для нашого мікроконтролера.

Ми реалізуємо тільки 7 регістрів: r16-r22:

  • Перші 4 регістра r16. r19 - це просто регістри.
  • Регістр r20 - це теж звичайний регістр, тільки його біти ми підключимо до 8-ми светодиодам плати Марсохід.
  • Регістр r21 - це теж звичайний регістр, але його біти ми підключимо до висновків управління крокових двигунів на платі Марсохід.
  • Регістр r22 - тільки для читання. До нього підключені входи від 4-х кнопочок плати Марсохід.

Схема нашого мікроконтролера створена в середовищі Altera QuartusII і виглядає ось так (натисніть на картинку, щоб збільшити):

Зробимо простий avr мікроконтролер


Наш мікроконтролер працює по простому алгоритму:

  1. Зчитує з флеш пам'яті UFM чергову команду.
  2. Декодує команду і вибирає для неї потрібні операнди з регістрів або безпосередньо з коду команди.
  3. Виконує команду в арифметико-логічному пристрої.
  4. Запам'ятовує результат виконання команди в регістрі приймачі, визначається командою.
  5. Переходить до виконання наступної команди.

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

Хід виконання програми може змінюватися в залежності від результату виконання команд. Спеціальні команди переходів дозволяють переходити до потрібної операції в потрібних умовах.

Перерахуємо команди мікроконтролера AVR, які ми збираємося реалізувати:

ADD 0000 11rd dddd rrrr
SUB 0001 10rd dddd rrrr

AND 0010 00rd dddd rrrr
EOR 0010 01rd dddd rrrr
OR 0010 10rd dddd rrrr
MOV 0010 11rd dddd rrrr

CP 0001 01rd dddd rrrr
LSR тисяча один 010d dddd 0110

SUBI 0101 KKKK dddd KKKK
ANDI 0111 KKKK dddd KKKK
ORI 0110 KKKK dddd KKKK
CPI 0011 KKKK dddd KKKK
LDI 1110 KKKK dddd KKKK

BREQ 1111 00kk kkkk k001
BRNE 1111 01kk kkkk k001
BRCS 1111 00kk kkkk k000
BRCC 1111 01kk kkkk k000

Зліва написані назви команд, а праворуч - їх бінарне представлення (кодування). Так буква "r" позначає регістр джерело, буква "d" - регістр приймач, "K" - це безпосередньо операнд.

Звичайно - це тільки мала частина від "реальної системи команд", але вже і ці команди дозволяти писати цілком працюючі програми.
У нас буде спрощене АЛУ (Арифметико-Логічне Пристрій). Воно реалізує тільки деякі, найбільш уживані команди, а так само всього 2 прапора для умовних переходів: "Z" і "C".

Прапор "Z" встановлюється, якщо результат АЛУ це нуль. Якщо результат з АЛУ не нульовий, то прапор "Z" скидається. Прапор "C" встановлюється при виникненні переносу в арифметичних операціях ADD і SUB / SUBI або порівняння CP / CPI. Прапори впливають на виконання команд умовних переходів: прапор "Z" впливає на BREQ, BRNE, а прапор "C" впливає на BRCS, BRCC.

Взагалі всеь проект ми вже реалізували і його можна взяти тут:


Оригінальний текст нашого ядра AVR написаний на мові Verilog і його можна подивитися тут.

Тепер подивимося, як ми зможемо написати програму для нашого мікроконтролера? Для написання програми на мові асемблер скористаємося середовищем розробки компанії Atmel AVRStudio4. Цієї середи розробки можна скачати прямо з сайту компанії Атмел (після реєстрації), ось тут. Або пошукайте в Яндексі - напевно знайдете у вільному доступі.

Зробимо простий avr мікроконтролер


Створюємо проект в AVRStudio4 і пишемо просту програму. Програма буде моргати світлодіодом на платі Марсохід і опитувати стан натиснутих кнопочок. Якщо натиснути одну кнопочку, то моргаючий світлодіод "побіжить" в одну сторону, а якщо натиснути іншу кнопочку, то світлодіод "побіжить" в іншу сторону. Ось вихідний текст на асемблері для нашого прикладу:

include "1200def.inc"
.device AT90S1200

; Initial one bit in register
ldi r16, $ 80

; Read port (key status)
mov r17, r22
cpi r17, $ 0f
; Go and blink one LED if no key pressed
breq do_xor

cpi r17, $ 0E
; Go and right shift LEDs if key [0] pressed
breq do_rshift

cpi r17, $ 0d
; Go and left shift LEDs if key [1] pressed
breq do_lshift

; Jump to read keys
or r16, r16
brne rd_port

do_rshift:
cpi r16,1
breq set80
lsr r16
mov r20, r16
brne pause
set80:
ldi r16, $ 80
mov r20, r16
or r16, r16
brne pause

do_lshift:
cpi r16, $ 80
breq set1
lsl r16
mov r20, r16
brne pause
set1:
ldi r16, $ 01
mov r20, r16
or r16, r16
brne pause

do_xor:
eor r20, r16

pause:
ldi r18, $ 10
cycle2:
ldi r19, $ FF
cycle1:
or r19, r19
or r19, r19
subi r19,1
brne cycle1
subi r18,1
brne cycle2

or r16, r16
brne rd_port

Бачите? Читання стану кнопочок - це читання з регістра r22. Зміна стану світлодіодів - це запис в регістр r20.
Налаштуйте AVRStudio так, що б вихідний формат був "Generic". Це у властивостях проекту, "Assembler Options", настройка "Hex Output Format".
Після компіляції програми виходить ось такий текстовий файл з кодами програми:

Цей файл нам майже підходить для QuartusII. У нашому проекті для ПЛІС є файл avr_prog.mif (Memory Initialization File), куди ми і вставляємо отриманий з AVRStudio код (тільки потрібно додати крапку з комою в кінці кожного рядка). Таким чином, після компіляції QuartusII ці коди потраплять у флеш UFM нашої ПЛІС.

Все працює так як і було задумано!
Зверніть увагу, що після компіляції, весь проект займає лише 205 логічних елемента з 240 наявних у нашій ПЛІС. Це означає, що наш мікроконтролер можна і далі ускладнювати або додати якусь нову логіку. Так що проект може бути корисний для створення Ваших пристроїв.

Доброго дня.
А можете допомогти переробити модуль для роботи з "звичайної" пам'яттю має порти addr [8..0] data [15..0]. пробував самостійно але поки не вийшло, заплутався в сигналах opcode_ready і fix_result, я прирівнював opcode_ready = 1, а fix_result = need_jump; але напевно потрібно десь поставити затримку.


А ви де це пробувати збираєтеся? Какао мікросхему пам'яті вибрали? Або це буде використано в фпга з внутрішніми блоками пам'яті?

Доброго дня.
А можете допомогти переробити модуль для роботи з "звичайної" пам'яттю має порти addr [8..0] data [15..0]. пробував самостійно але поки не вийшло, заплутався в сигналах opcode_ready і fix_result, я прирівнював opcode_ready = 1, а fix_result = need_jump; але напевно потрібно десь поставити затримку.

прошив плату горить 1 світлодіод) у вас проект точно робочий?))

сьогодні перевірив проект - щось дивне. при компіляції його 10м Quartus II проект працює, якщо 11м - немає. Містика якась. Спробую розібратися.

Навіщо ви використовували UFM з interface: none? якби ви вибрали Parallel, то тоді можна було б спростити безглуздий введення інструкцій і не витрачати по шістнадцять тактів тільки на те, щоб витягнути команду з пам'яті.


хм. дійсно можна було б використовувати Parallel, тільки це не поліпшить ситуацію. Все одно внутрішнє уявлення UFM - serial flash і використання Parallel тільки "замаскувало" б цей факт, все одно доступ швидше 16 тактів не зробити


Ну хоча б не було цього вкрай дивного процесу зчитування даних з пам'яті, і то хліб.


З цієї точки зору напевно Ви маєте рацію

Навіщо ви використовували UFM з interface: none? якби ви вибрали Parallel, то тоді можна було б спростити безглуздий введення інструкцій і не витрачати по шістнадцять тактів тільки на те, щоб витягнути команду з пам'яті.


хм. дійсно можна було б використовувати Parallel, тільки це не поліпшить ситуацію. Все одно внутрішнє уявлення UFM - serial flash і використання Parallel тільки "замаскувало" б цей факт, все одно доступ швидше 16 тактів не зробити


Ну хоча б не було цього вкрай дивного процесу зчитування даних з пам'яті, і то хліб.

Навіщо ви використовували UFM з interface: none? якби ви вибрали Parallel, то тоді можна було б спростити безглуздий введення інструкцій і не витрачати по шістнадцять тактів тільки на те, щоб витягнути команду з пам'яті.


хм. дійсно можна було б використовувати Parallel, тільки це не поліпшить ситуацію. Все одно внутрішнє уявлення UFM - serial flash і використання Parallel тільки "замаскувало" б цей факт, все одно доступ швидше 16 тактів не зробити

Навіщо ви використовували UFM з interface: none? якби ви вибрали Parallel, то тоді можна було б спростити безглуздий введення інструкцій і не витрачати по шістнадцять тактів тільки на те, щоб витягнути команду з пам'яті.

Щось дивне у вас написано. Команда LSL в процесорі не реалізована, а в тексті програми використовується.
Як так?


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

Щось дивне у вас написано. Команда LSL в процесорі не реалізована, а в тексті програми використовується.
Як так?

Схожі статті