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

Yuzhanin_V.V._Nizkourovnevoe_programmirovanie_mikrokontrollerov

.pdf
Скачиваний:
2
Добавлен:
12.11.2022
Размер:
1.33 Mб
Скачать

Министерство науки и высшего образования Российской Федерации

РОССИЙСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ НЕФТИ И ГАЗА (НАЦИОНАЛЬНЫЙ ИССЛЕДОВАТЕЛЬСКИЙ УНИВЕРСИТЕТ)

имени И.М. ГУБКИНА

Кафедра автоматизации технологических процессов

В.В. Южанин

НИЗКОУРОВНЕВОЕ

ПРОГРАММИРОВАНИЕ

МИКРОКОНТРОЛЛЕРОВ

Учебное пособие

Москва 2019

УДК 004.382.7(075) Ю17

Рецензенты:

Л.И. Григорьев – д.т.н., профессор, заведующий кафедрой Автоматизированных систем управления

РГУ нефти и газа (НИУ) имени И.М. Губкина

И.Ю. Храбров – к.т.н., доцент декан факультета Автоматики и вычислительной техники

заведующий кафедрой Информационно-измерительных систем РГУ нефти и газа (НИУ) имени И.М. Губкина

Южанин В.В.

Ю17 Низкоуровневоепрограммированиемикроконтроллеров:

Учебное пособие. – М.: Издательский центр РГУ нефти и газа (НИУ) имени И.М. Губкина, 2019. – 47 с.

Рассматриваются вопросы низкоуровневого программирования микроконтроллеров.

Учебное пособие предназначено для бакалавров, обучающихся по направлениям:

09.03.01 «Информатика и вычислительная техника», 27.03.04 «Управление в технических системах».

Южанин В.В., 2019

РГУ нефти и газа (НИУ) имени И.М. Губкина, 2019

Содержание

Введение. О ЧЕМ ЭТОТ КУРС? .............................................................

4

Глава 1. КРАТКИЙ ОБЗОР ЯЗЫКА C...................................................

7

Глава 2.

ПРИМЕНЕНИЕ УКАЗАТЕЛЕЙ В ЯЗЫКЕ C .......................

32

Глава 3.

ПОБИТОВЫЕ И ЛОГИЧЕСКИЕ ОПЕРАЦИИ ...................

43

Литература....................................................................................................

47

3

Введение

О ЧЕМ ЭТОТ КУРС?

Основной предмет изучения данного курса – аппаратные средства микропроцессорной техники. Говоря точнее – даже не сами аппаратные средства, а принципы их работы, благодаря которым «примитивная» электроника (регистры, триггеры, аппаратные сумматоры) превращается в нечто значительно более «умное» – процессор, исполняющий сложнейшие программы.

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

леры.

Из микропроцессоров, как известно, получаются домашние (и не только) компьютеры. Что же можно сделать из микроконтроллеров? Лишь основные примеры – смартфон, промышленный контроллер (ПЛК) для управления технологическим процессом, а также платформа Arduino, представляющая собой своеобразный конструктор Lego для «детей» старше 20 лет.

В курсе можно выделить два крупных блока – изучение аппаратных средств и разработка программного обеспечения. Подобно своим старшим собратьям (микропроцессорам), микроконтроллеры не могут решать задачи и быть полезными без программного обеспечения. Чтобы научиться решать задачи с помощью микроконтроллеров, разобраться в их аппаратной части, в

данном курсе предлагается изучение аппаратных средств микроконтроллера через их программирование.

Программирование на уровне аппаратных средств называется низкоуровневым (не в смысле уровня квалификации програм-

4

миста, а в смысле уровня организации вычислительной машины, с которым программист взаимодействует!). Низкоуровневое программирование требует знаний аппаратных средств (мы этого и хотим от курса!), структур целых и вещественных типов данных на битовом уровне (уровне нулей и единиц).

Языком программирования микроконтроллеров обычно является язык C и наш курс не стал исключением. К слову, язык С, хотя и появился в 1970-е годы, значительно повлиял на современные языки программирования, некоторые из которых называются почти так же: C++, Java, C#.

Лабораторные стенды курса снабжены собственно микроконтроллером (серии Atmel Mega 16), к которому подключаются модули светодиодов, кнопок и ЖК-дисплея. К слову, иногда возникает вопрос, почему курс не сделан на базе упомянутой платформы Arduino. Дело в том, что эта платформа имеет слишком много готовых наработок, упрощающих жизнь любителям, но избавляющих от необходимости думать, что на практике приводит к массовому скачиванию кода из интернета на защитах лабораторных работ.

В завершении курса рассматривается важная тема: чем программирование промышленных контроллеров (ПЛК) отличается от такового микроконтроллеров, и как знание микроконтроллеров поможет в работе с ПЛК. Повторим, микроконтроллеры используется как элементная база ПЛК. Программирование промышленных контроллеров стремятся упрощать так, чтобы разработчик алгоритмов ПЛК как можно меньше думал о его устройстве, и сосредоточился на задачах управления технологическим процессом. В этом, кстати, и состоит идея высокоуровневых языков программирования в противоположность низкоуровневым. Среди разработчиков АСУТП встречаются специалисты электрики, электронщики, а также программисты. Чтобы максимально за-

5

действовать опыт и интуицию каждой из этих групп, в стандарт МЭК-61131-3 включено несколько языков программирования, каждый из которых будет более понятен соответствующей группе специалистов (нетрудно сообразить какой для кого). Однако, несмотря на общую тенденцию к упрощению, во многих ПЛК при программировании до сих пор приходится в существенной степени владеть низкоуровневым программированием.

6

Глава 1

КРАТКИЙ ОБЗОР ЯЗЫКА C

За многие годы было написано большое количество книг по этому языку, поэтому нет смысла писать ещё одну, подробно описывая все возможности не самого нового языка. Вместо этого, будут на примерах кода разобраны элементы языка, в первую очередь нужные при программировании микроконтроллеров.

Первая программа

Традиционно при изучении нового языка программирования первой программой становится одна-единственная строка, выводящая на экран сакральное «Hello world!». Однако у микроконтроллера нет ни экрана, ни вообще какого-либо стандартного средства вывода. Поэтому сейчас мы ограничимся написанием чуть более сложной программы, состоящей из целых 11 строк.

int main()

{

int a = 2; int b = 3; int c = 1; int D;

D = (b * b) - (4 * a * c);

return 0;

}

Эта небольшая программа находит дискриминант квадратного уравнения при заданных величинах a, b и c. Конечно, она не несёт никакой практической ценности, так как значения «зашиты» в программу на этапе компиляции, однако, в качестве примера эта программа позволит нам понять некоторые ключевые концепции языка С.

Разберём этот код построчно. На первой строке мы видим

7

объявление функции. Функция это основной «строительный блок», из которых собирается программа. Она представляет собой отдельный участок кода, к которому можно обратиться по имени из другого места программы. Перед именем функции main

расположен тип возвращаемого ей значения. На данный мо-

мент не будем заострять на этом внимание, и вернёмся к этому вопросу позже, при более подробном рассмотрении функций. Скажем лишь, что любая функция имеет вид:

Тип имя_функции([список_параметров])

{

Код_функции

}

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

В самом начале блока, обозначенного фигурными скобками, расположены объявления переменных. Переменная это просто участок памяти, к которому мы можем обратиться по имени. Размер этого участка определяется типом переменной. Например, если мы объявим переменную int foo, в памяти будет выделен блок размером два байта именно столько занимает тип данных int в нашем микроконтроллере (в других процессорах это не так, в частности в архитектурах x86/x64!).

Теперь, если мы будем обращаться в коде к переменной foo, компилятор поймёт, что ему необходимо обратиться к определённой области памяти, выделенной именно под эту переменную.

Таким образом, тип переменной отвечает за то, сколько памяти будет выделено на хранение информации, содержащейся в

8

этой переменной. Стандарт С определяет несколько основных типов данных (см. табл. 4).

Стоит заметить, что при программировании микроконтроллеров типы данных с плавающей точкой используются довольно редко, так как операции с ними отнимают слишком много ресурсов.

Базовые типы можно расширить с помощью спецификаторов. Спецификатор это ключевое слово, которое располагается перед базовым типом переменной. Существует 4 спецификатора: signed, unsigned, short, long. Первые два влияют на диапазон значений, принимаемых переменной, а два других на размер памяти, выделяемой для переменной.

Количество байт, выделяемых для переменной, влияет на диапазон значений, которые эта переменная может принять. Например, для типа char выделяется один байт памяти. Как известно, один байт состоит из восьми бит, а каждый бит, в свою очередь, может принимать два значения: 1 или 0. Таким образом, набор из восьми бит может принять 28, то есть 256 возможных значений. Значит, если мы хотим хранить в переменной типа char как отрицательные, так и положительные числа, в один байт у нас влезет диапазон значений от 128 до 127. Если же мы хотим хранить только положительные значения, у нас будет диапазон от 0 до 255. Как вы уже догадались, мы вплотную подошли к пониманию смысла спецификаторов signed и unsigned. Спецификатор signed указывает на то, что в переменной могут храниться как положительные, так и отрицательные значения, тогда как unsigned разрешает переменной принимать лишь положительные значения.

Особо любознательные найдут интересным тот факт, что в памяти значения signed и unsigned переменных одного типа имеют один и тот же вид. Например, содержимое байта памяти:

Номер бита 7 6 5 4 3 2 1 0 Значение бита 1 0 0 1 1 0 1 1

9

будет интерпретировано как число 101, если эта область памяти принадлежит переменной типа signed char, и как число 155 в случае unsigned char. По сути, спецификаторы signed и unsigned определяют интерпретацию последнего, восьмого бита. В случае signed-переменной «1» в этом бите означает отрицательное число, а «0» положительное. Младшие 7 бит определяют значение. В случае же unsigned-переменной значение восьмого бита принимает полноправное участие в формировании значения байта.

Вернёмся теперь к спецификаторам типов. Мы уже выяснили, как работают спецификаторы signed и unsigned, разберёмся теперь с long и short. Эти два спецификатора влияют на размер памяти, выделяемой для переменной. Например, для short int будет выделено 2 байта, в то время как для long int 4. Эти два спецификатора неприменимы к типу char.

По умолчанию переменные типа char имеют спецификатор signed, а переменные типа int signed и short. Таким образом, эти записи будут эквивалентны:

signed int foo; // Явно знаковый, неявно short

int bar;

// Неявно знаковый, неявно short

short baz;

// Неявно знаковый, явно short

Ещё один важный аспект, касающийся переменных, и требующий внимания это их инициализация. В простейшем случае, переменную можно объявить двумя способами:

int foo;

int bar = 123;

В первом случае происходит только выделение памяти для переменной foo, однако в эту память не записывается никакого значения. Это означает, что наша переменная будет содержать то значение, которое хранилось в этой области памяти до её объявления. При написании программы мы ни в коем случае не должны делать предположений о значениях неинициализированных

10