Table of Contents
(Фрагмент курса аппаратного программирования от Юрия Панчула)
Часть 5. Программирование на голом железе и зачатие операционной системы
(почти готово)
- Лаба - знакомство с платой chipKit32 / PIC32 / MIPS и программирование её на С с помощью Arduino-подобного GUI. Кнопочки, лампочки, дисплейчик, IO Shield.
- Введение в ассемблер на примере MIPS
- Лаба - загрузка в память программок на ассемблере
- Концепция простейшей многозадачной операционной системы
- Лаба - своя игрушечная многозадачная ОС, которая бутится, ставит обработку прерывания по таймеру, в котором переключает задачи по схеме round-robin.
В качестве среды программирования используется пакет MPIDE фирмы chipKIT. Это бесплатное программное обеспечение, сделанное на основе компилятора GCC и визуальной среды программирования Arduino. Его можно установить под Linux, Windows или Mac OS X. Скачать пакет можно здесь: https://github.com/chipKIT32/chipKIT32-MAX/downloads
Для первых двух примеров используется визуальная среда программирования. В третьем и четвёртом примерах от студентов потребуется умение работать в режиме командной строки и пользоваться командой make.
Загрузка программ на плату Uno32 (или Max32) производится через порт USB с помощью утилиты avrdude, входящей в состав MPIDE. Отдельный программатор не требуется.
5.1. Знакомство с платой Uno32
Лабораторная работа: знакомство с платой Uno32. Процессор PIC32 с архитектурой MIPS32. Простая среда разработки chipKit MPIDE и программирование на С. Подключение кнопочек и семисегментного индикатора с помощью макетной платы. Концепция прерывания.
Необходимое оборудование:
- Плата Uno32
- Адаптер PICkit 3 (начиная с раздела 5.3)
- Компьютер с Linux или Windows
- Кабель mini-USB
- Универсальная макетная плата
- Две кнопки с проводами
- Семисегментный светодиодный индикатор
- Девять резисторов 220 Ом с проводами
Пример 1: управление двумя светодиодами, имеющимися на плате Uno32, с помощью двух внешних кнопок.
Пример 2: управление семисегментным индикатором. Игра “нажми вместе”. При нажатии двух кнопок на индикаторе отображается разность времени нажатия в миллисекундах. Используется аппаратное прерывание от таймера.
Студентам предлагается доработать примеры в некотором направлении. Возможный список задач:
- Сделать счетчик нажатий кнопок. Одна кнопка увеличивает счётчик, другая уменьшает.
- Секундомер. Первая кнопка запускает отсчет. Вторая увеличивает темп в 10 или 100 раз.
- “Электронный кубик” - генератор случайных чисел.
Пример 5.1.1.
Среда MPIDE. Управление двумя светодиодами, имеющимися на плате Uno32, с помощью двух внешних кнопок.
TODO: нарисовать схему подключения кнопок. Требуются два резистора 10кОм для подтяжки к +3.3В.
Исходные тексты: example5-1-1.pde (просмотреть).
Пример 5.1.2
Среда MPIDE. Управление семисегментным индикатором. Игра “нажми вместе”. При нажатии двух кнопок на индикаторе отображается разность времени нажатия в миллисекундах. Используется аппаратное прерывание от таймера.
TODO: нарисовать примерную схему подключения 7-сегментного индикатора. Требуются восемь резисторов 220Ом для ограничения тока через светодиоды. Годится любой светодиодный индикатор с общим катодом. Можно с общим анодом, если поменять местами LATxSET и LATxCLR в функции display().
Контакт Uno32 Digital | Сегмент LED | ||
2 | A | ||
3 | B | ||
4 | C | ||
5 | D | ||
6 | E | ||
7 | F | ||
8 | G | ||
9 | H |
Исходные тексты: example5-1-2.pde (просмотреть).
5.2. Введение в ассемблер MIPS
Организация оперативной памяти, разбиение на слова. Понятие машинной инструкции и счётчика команд. Регистры общего назначения, номера и имена регистров. Передача параметров и возврат значения в регистрах. Регистр адреса возврата. Стек, регистр стека, место в стеке для каждой вызываемой функции (фрейм).
Основные инструкции системы команд MIPS. Разбиение по функциональным группам. Псевдоинструкции LI, LA.
Управление периферийными модулями: таймер, сигналы ввода-вывода. Концепция управляющих регистров, отображаемых на память.
5.3. Практическая работа на ассемблере
Пример: управление двумя светодиодами с помощью двух внешних кнопок. То же, что в разделе 5.1, но на ассемблере.
Для сборки используется утилита make (скачать makefile). В начале файла makefile следует установить путь к каталогу с установленным пакетом MPIDE, например:
MPIDE_DIR = /opt/mpide-0022-linux32-20110822
Для загрузки программы в плату надо вызывать:
sudo make upload
Задание для продвинутых студентов: переписать на ассемблере пример 2 из раздела 5.1.
Пример 5.3
Управление двумя светодиодами с помощью двух внешних кнопок. Тот же, что в разделе 5.1, но на ассемблере.
TODO: нарисовать схему подключения кнопок. Требуются два резистора 10кОм для подтяжки к +3.3В.
Исходные тексты: example5-3.zip (просмотреть).
5.4. Концепция многозадачного выполнения
В основе многозадачности лежит стремление к простоте. В примере 5.1.1 нам приходилось обрабатывать всего два входных сигнала (кнопки). Тем не менее, основной цикл программы выглядит неочевидным. В реальных системах микроконтроллеру приходится обрабатывать десятки или даже сотни входных событий. Хороший способ справиться с этим - разбить программу на независимые части меньшего размера, выполняющиеся параллельно, не мешая друг другу. Такие части называют задачами (tasks). В больших операционных системах задачи, выполняющиеся в раздельных адресных пространствах, называются процессами.
Вот как могли бы выглядеть задачи в примере 5.1.1. Намного проще, не правда ли?
void task1() { for (;;) { if (нажата первая кнопка) { гасим первый светодиод; delay (150); } зажигаем первый светодиод; delay (150); } } void task2() { for (;;) { if (нажата вторая кнопка) { гасим второй светодиод; delay (150); } зажигаем второй светодиод; delay (150); } }
Хотелось бы иметь возможность запускать на одном процессоре несколько задач, но чтобы они оставались независимыми и не мешали друг другу. Для этого нам понадобятся два новых понятия:
- Контекст выполнения
- Прерывания
Контекстом выполнения называется содержимое регистров процессора и стека выполняемой задачи. Ход программы полностью определяется контекстом выполнения. Если бы мы смогли в какой-то момент остановить процессор, запомнить куда-нибудь значения всех регистров (а указатель стека тоже находится в регистре), установить новые значения для регистров и пустить выполнение дальше, мы получили бы совсем другую задачу. Такое действие называется переключением контекста.
Если переключать контекст достаточно быстро, например 1000 раз в секунду, будет создаваться впечатление параллельного и одновременного выполнения всех задач. Скажем, 1 миллисекунду работает первая задача, потом 1 миллисекунду вторая, и дальше по кругу. Такой алгоритм распределения процессорного времени обычно называют циклическим (round robin). В больших операционных системах применяются и другие алгоритмы, более сложные.
Прерывания это особый механизм, посредством которого процессор приостанавливает выполнение текущей программы, и совершает необходимые срочные действия. При этом управление передаётся на специальный фиксированный адрес, по которому расположена функция обработки прерывания. После её завершения происходит выход из прерывания, и выполнение программы продолжается. Для обработки прерываний в процессоре есть специальные регистры (EPC, Status) и инструкции (ERET, EI, DI).
Для получения периодических прерываний в процессоре MIPS есть два 32-битных регистра: Count и Compare. Регистр Count - это счётчик, который растет с фиксированной частотой, в нашем случае 40 МГц. Когда значение Count становится равным регистру Compare, возникает прерывание с номером 0 и происходит переход по адресу 0x9d000200. Старое значение счетчика команд PC прерванной программы заносится в специальный регистр EPC. В регистре Status устанавливается бит EXL, блокирующий обработку других прерываний (если они возникнут). Дальше начинает выполняться функция обработки прерывания. В нашем примере она записывает значения всех регистров (контекст) в стек прерванной задачи, переключает регистр стека на другую задачу, восстанавливает все регистры из стека, и выполняет инструкцию ERET. Это специальная команда, которая снимает бит EXL регистра Status (этим разблокируя прерывания) и пересылает значение регистра EPC в счетчик команд PC. Продолжается выполнение новой задачи, до следующего прерывания.
5.5. Пример реализации многозадачности
Переключение двух задач по таймеру. Исходные тексты расположены в двух файлах:
- example5-5.c — основная программа на языке Си.
- timer-interrupt.S — функция обработки прерывания на ассемблере. Сохраняет и восстанавливает контекст выполнения в стеке. Для управления таймером и переключения задач вызывает функцию на языке Си.
Для сборки используется утилита make (скачать makefile). В начале файла makefile следует установить путь к каталогу с установленным пакетом MPIDE, например:
MPIDE_DIR = /opt/mpide-0022-linux32-20110822
Для загрузки программы в плату надо вызывать:
sudo make upload
Исходные тексты: example5-5.zip (просмотреть).
Задание для продвинутых студентов: обобщить пример для произвольного количества задач.
Задание для особо продвинутых студентов: реализовать механизм взаимодействия задач. На выбор: семафоры, мьютексы, рандеву, мониторы, передача сообщений.