Микроконтроллер Arduino

Микроконтроллер ATmega328p, если аккуратно извлечь его из панельки на плате Arduino, сам по себе выглядит как микросхема в корпусе PDIP-28.

Микроконтроллер Arduino ATmega328p

Центральный процессор

Ядром любого микроконтроллера и данного микроконтроллера в частности является ЦП (CPU) - Центральный Процессор, или если по-английски, то Central Processing Unit (Центральное Процессинговое Устройство). Процессором это называется потому, что оно постоянно пребывает в процессе, а под термином процесс подразумевается в данном случае переход устройства из одного состояния в другое, то есть CPU работает как машина состояний (State Machine). В этом процессе он пребывает постоянно переходя из одного состояния в другого с каждым тактом тактирующего генератора импульсов, и всё это называется термином процессинг. Поэтому данное устройство называется процессором.

Процессоры бывают вспомогательные по своим функциям, и хотя процессор в микроконтроллере один, тем не менее он называется центральным процессором, поскольку микроконтроллер так устроен, как устройство, что в нём должен быть как минимум один центральный процессор.

Раньше у меня возникал вопрос, а бывают ли микросхемы, где было бы несколько процессоров, совершенно независимых друг от друга, то есть чип с несколькими цетральными процессорами. Впоследствии я узнал, что для таких парней как я выпускаются микросхемы ПЛИС (FPGA), которые можно запрограммировать как угодно - это просто программируемая матрица логических вентилей, поэтому если нужно создать сиамский сдвоенный CPU, то всегда пожалуйста - хоть строенный, поскольку минимальный процессор с четырьмя командами ассемблера занимает всего 32 логических вентиля, код можно найти в интернете.

Но ПЛИС -- это отдельная большая тема, и я надеюсь, что однажды на этом сайте появятся страницы, посвящённые программированию ПЛИС тоже. Пока же давайте вернёмся к любимому ATmega328p, и если Вы, уважаемый читатель, его ещё не полюбили, то однажды обязательно полюбите, как это произошло со мной, и многими энтузиастами радиоэлектроники по всему миру.

Ядро микроконтроллера

Микроконтроллер ATmega328p является одним из микроконтроллеров, ядром которых которых является арифметико-логическое устройство (АЛУ) с системой команд AVR8. Восьмёрка означает "8 бит", существуют также микроконтроллеры AVR32, освоить которые является одним из моих заветных "хочу", осуществлению которых препятствует сырое пока программное обеспечение и внушительной длины раздел Errata в документации; одним словом эти микроконтроллеры на данный момент пребывают в стадии развития, так что я пока осваиваю 32-битную плату Olimexino-STM32, совместимую с Maple Board, и уже успел стать фанатом -- да-да, не смейтесь.

Ядро AVR8 является процессором так называемой Гарвардской архитектуры -- с разделением доступа к памяти по функциональным типам. Так Flash-память микроконтроллера разделена на память программ и секцию загрузчика bootloader, а память ОЗУ (RAM) и ПЗУ с электрическим стиранием и программированием (EEPROM) -- это память для хранения данных, и к каждой из них АЛУ обращается по отдельной шине адреса и отдельной шине данных.

Ядро AVR8 микроконтроллера Arduino

Это сделано для того, чтобы процессор мог выполнять одну команду даже загружая команды из такого медленного типа память как Flash. Например в микроконтроллерах AT91SAM7S процессор может выполнять программу как из Flash-память, так и в ОЗУ, но при этом максимальная частота тактирования процессора для программы, выполняющейся в памяти Flash, равна 8 МГц, в то время как в памяти RAM та же программа может выполняться со скоростью 72 МГц, то есть в несколько раз быстрее.

Ядро AVR8 настолько популярно (на сайте Open Cores можно скачать его реализацию для ПЛИС, работающую даже на частоте 50 МГц! Но это конечно с ОЗУ она с такой скоростью работает, как несложно догадаться) благодаря тому, что все команды набора AVR8 выполняются либо за 1, либо 2 такта, занимая в памяти при этом либо 2, либо 4 байта.

Приведу пример распечатки листинга, насколько красив и просто читаем ассемблерный код AVR8:

000004f2 <__do_global_ctors>:
     4f2: 14 e0          ldi    r17, 0x04   ; 4
     4f4: c0 ec          ldi    r28, 0xC0   ; 192
     4f6: d4 e0          ldi    r29, 0x04   ; 4
     4f8: 04 c0          rjmp   .+8         ; 0x502 <__do_global_ctors+0x10>
     4fa: 22 97          sbiw   r28, 0x02   ; 2
     4fc: fe 01          movw   r30, r28
     4fe: 0e 94 3d 0a    call   0x147a      ; 0x147a <__tablejump__>
     502: cc 3b          cpi    r28, 0xBC   ; 188
     504: d1 07          cpc    r29, r17
     506: c9 f7          brne   .-14        ; 0x4fa <__do_global_ctors+0x8>
     508: 0e 94 f0 08    call   0x11e0      ; 0x11e0 <main>
     50c: 0c 94 41 0a    jmp    0x1482      ; 0x1482 <_exit>
00000510 <__bad_interrupt>:
     510: 0c 94 00 00    jmp    0           ; 0x0 <__vectors>

Глядя на эту красоту, невольно замечаешь самому себе: "Да уж это вам не ARM с загрузкой команды через пятиступенчатый конвейер..." Для справки, в AVR8 конвейер загрузки команды на выполнение имеет три ступени, и хотя предсказания ветвлений тоже нет, тем не менее 2 машинных цикла на команду rjmp -- это впечатляет.

Регистры

В тексте вышеприведённого листинга можно увидеть, что команды ассемблера оперируют числовыми константами и регистрами микроконтроллера и процессора. Это разделение на регистры двух типов является общепринятой практикой процессинга, любой микропроцессор оперирует регистрами только двух типов - регистрами общего назначения (General Purpouse Registers) и регистрами перифейных устройств (Peripheral Device Registers).

Регистры общего назначения

В ядре процессора AVR8 таких регистров 32, поскольку AVR8 -- это RISC-процессор, то есть процессор с ограниченным (Reduced) набором команд. Также существуют CISC-процессор, например процессоры x86, выпускаемые компаниями Intel и AMD. Так, например, процессоры Pentium и Athlon являются CISC-процессорами. В CISC-процессорах развитая система команд, но мало регистров, в RISC-процессорах наоборот. Ядро AVR8 сочетает в себе и довольно развитую систему команд одной, и большое число регистров общего назначения с другой стороны. Тем не менее, в сравнении с системой команд x86, это всё-таки RISC-процессор в полном смысле термина reduced.

Регистры общего назначения называются R0, R1, R2,.. R30, R31, каждый имеет разрядность 8 бит, поскольку это 8-битный микропроцессор. Регистры R26 и R27 составляют 16-битную регистровую, так называемый регистр X, регистры R28 и R29 -- регистр Y, регистры R30 и R31 -- регистр Z.

Язык компилятора Си генерирует из скетчей машинный код с соглашением о вызовах функций "вызывающий сохраняет регистры", что характерно в целом для RISC-процессоров, поскольку такой код занимает меньше места и работает быстрее. В приведённом выше из функции __do_global_ctors() вызывается функция main(), вызывается виртуальная функция (*__tablejump__)() и при этом мы не видим ни одного обращения к стеку, то есть ни одной команды ассемблера push или pop, что сразу говорит нам о том, что эти функции объявлены как

void main (void);
void (*__tablejump__) (void) [] = { 0x147a };

Регистры периферийных устройств

Интересной особенностью ядра AVR8 является адресация регистров общего назначения и регистров периферийных устройств. Все регистры просто размещены в адресном пространстве ОЗУ:

  • Адреса памяти ОЗУ 0x0000..0x001F -- это регистры общего назначения R0..R31
  • Адреса 0x0020..0x00FF -- это управляющие регистры периферийных устройств
  • Адреса 0x0100..0x3FFF -- это память данных ОЗУ

Увы, но это смешивание всех в одну корзину очень выигрышно, когда программируешь на ассемблере какой-нибудь младший процессор в линейке, например, ATmega16, где вместо переменных программа может просто хранить значения в регистрах общего назначения, но уже в ATmega32, где памяти побольше, регистров периферийных устройств весь выигрыш сходит на нет, поскольку практическое программирование на ассемблере -- это всё-таки программирование с использованием макросов, и в итоге компилятор Си выдаёт на выходе более компактный код за счёт того, что применяет команды IN и OUT для младших 32 регистров периферийных устройств, а для остальных -- LD начиная с адреса 0x0020 и выше.

Порты ввода-вывода

Помимо центрального процессора и памяти всякий микроконтроллер, почему эти устройства и называются микроконтроллерами, также оснащён портами ввода-вывода и периферийными устройствами. Подробно о портах ввода-вывода и перифейных устройствах микроконтроллера Arduino написано в статье Порты ввода-вывода Arduino.

Автор: Андрей Шаройко <vanyamboe@gmail.com>