Микроконтроллер Arduino
Микроконтроллер ATmega328p, если аккуратно извлечь его из панельки на плате Arduino, сам по себе выглядит как микросхема в корпусе PDIP-28.
Центральный процессор
Ядром любого микроконтроллера и данного микроконтроллера в частности является ЦП (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) -- это память для хранения данных, и к каждой из них АЛУ обращается по отдельной шине адреса и отдельной шине данных.
Это сделано для того, чтобы процессор мог выполнять одну команду даже загружая команды из такого медленного типа память как 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>