Skip to content

Глава 2. Программирование и программное обеспечение

2.1 Знакомство со средой разработки Arduino IDE

В качестве инструмента для разработки программного кода при работе с рассматриваемым образовательным робототехническим набором предлагается широко распространенная открытая среда разработки Arduino IDE (рисунок 2.1).

Изображение 39

Рисунок 2.1. Внешний вид среды разработки Arduino IDE

Данная среда разработки обладает большими функциональными возможностями при достаточно простом устройстве и низкими требованиями к порогу вхождения. Основными элементами данной среды разработки являются встроенный текстовый редактор программного кода, области сообщений, окна обмена данными по порту, панели инструментов и меню. Данная среда не обладает функционалом симуляции, поэтому для загрузки и исполнения написанных управляющих программ (“Скетчей”), необходимо физическое подключение среды к аппаратной платформе либо проводным, или беспроводным методом.

2.1.1 Установка

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

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

После завершения установки достаточно запустить среду, выбрать в меню “Инструменты” - “Платы” плату Arduino Leonardo, а затем в меню “Инструменты” - “Порт” выбрать виртуальный порт, на который установлена плата при подключении к компьютеру. После этого среда готова к работе (рисунок 2.1.1.1).

Изображение 40

Рисунок 2.1.1.1. Выбор платы и порта в Arduino IDE

Изображение 41

Рисунок 2.1.1.2. Внешний вид среды разработки при подключении к контроллеру

Среда разработки Arduino IDE (рисунок 2.1.1.2) создана на основе классического языка С и максимально снижает порог вхождения в изучение языка C/C++, при этом не теряя связи со структурой и функционалом оригинального языка. На данный момент среда разработки Arduino IDE является, пожалуй, самой удобной средой для программирования существующих микроконтроллеров, входящих в состав аппаратных платформ. Структуру языка, используемого для работы в Arduino IDE можно разделить на 4 глобальных раздела: Данные, Функции, Операторы и Библиотеки.

2.1.2 Типы данных

Все вычисления в микроконтроллере выполняются с помощью арифметико-логического устройства (АЛУ), которое выполняет все арифметические и логические операции, путем прямого взаимодействия с ячейками памяти микроконтроллера. Таким образом, для АЛУ нет разницы, с чем работать - с целыми или дробными числами, строками и символами. Однако, чтобы программа выполняла именно те задачи, которые от нее требуются, приходится объяснять компилятору, с какими именно данными придется работать АЛУ. Это объяснение вытекает в четкое задание каждой переменной, используемой в программе, типа данных, которые в ней будут содержаться. Доступные для использования в Arduino IDE типы данных:

  1. Boolean (логический)

Boolean — логический тип данных. Переменная такого типа хранит одно из двух результатов: true или false. В памяти она занимает один байт.

Пример:

boolean var = true

  1. Char (символьный)

Char — символьный тип данных. Хранит один символ — букву или цифру — и занимает в памяти один байт. При объявлении символа берется в одинарные кавычки:

char letter = 'A'

Под капотом символ хранится как — код из таблиц ASCII. Именно поэтому с ним можно проводить арифметические операции: например, 'A' + 1дать 66, потому что ASCII-код 'A'— это 65.

Char — тип знака, то есть хранимое число может быть от -128 до 127.

Пример:

cpp
char var = 'A';
char var = 65; // эквивалентные варианты
  1. Byte (беззнаковое целое)

Байт — тип данных размером 8 бит. Кодирует 256 результатов: от 0 до 255.

Удобно, когда нужна беззнаковая однобайтовая переменная.

Пример:

byte var = B10010; // "B" — префикс двоичной записи (B10010 = 18 в десятичной)

или то же ультра:

byte var = 18;

  1. Int (целое)

Int — один из самых распространённых типов для хранения целых чисел. Занимает 2 байта и сохраняет значения от -32768 до 32767.

Пример:

int var = 100;

Есть один нюанс: если переменная выходит за пределы допустимого соединения, она не обрезается и не вызывает ошибки — она перескакивает на противоположный конец границы.

cpp
int x;
x = -32768;
x = x - 1; // х теперь равно 32767 — перескочил на максимум
x = 32767;
x = x + 1; // х теперь равно -32768 — перескочил на минимум
  1. unsigned int (беззнаковое целое)

Unsigned int — беззнаковый целочисленный тип. Хранит значения от 0 до 65535, занимает 2 байта.

Пример:

unsigned int var = 40000;

Как и у int, при выводе за соединение значение не обрезается — перескакивает на противоположный конец:

cpp
unsigned int x;
x = 0;
x = x - 1; // x теперь равно 65535 — перескочил на максимум
x = x + 1; // x теперь равно 0 — вернулся на минимум
  1. word (беззнаковое целое)

Word — по сути то же самое, что unsigned int: хранит значения от 0 до 65535, занимает 2 байта. Сегодня этот тип языка почти не встречается — он остался в качестве пережитка старого API.

Пример:

word var = 40000;

  1. long (расширенный)

Long — целочисленный тип для случаев, когда int уже не хватает. Хранит значения от -2147483648 до 2147483647, занимает 4 байта.

Пример:

long var = 200000L;

  1. unsigned long (беззнаковый расширенный)

Unsigned long — беззнаковый вариант long. Хранит значения от 0 до 4294967295, занимает 4 байта.

Пример:

unsigned long var = 300000L;

  1. float (число с плавающей запятой)

Float — тип для чисел с плавающей запятой, диапазон от -3,4028235E+38 до 3,4028235E+38. Занимает 32 бита, то есть 4 байта. Точность — 6-7 знаков, и это все знаки, включая те, что до пятой.

Обычно для большей точности требуется double, но на платформе Arduino, double и float имеют одинаковую точность.

Компьютер хранит числа с плавающей запятой не идеально точно. Например, если разделить 6.0 на 3.0, то ответ может оказаться не ровно 2.0, а например, 1,9999999. Сравнивая два числа с плавающей запятой следует проверять не точное равенство, а разницу между этими числами - меньше ли она выбранного нами малого порога. Если меньше, считаем их равными.

И ещё момент: арифметика с float выполняется заметно медленнее, чем с целыми цифрами.

Пример:

float var = 1.2345;

  1. double (число двойной точности с плавающей запятой)

Это метод хранения чисел, содержащих дробь. Занимает 8 байт (64 бита). Но в среде Arduino IDE double полностью идентичен типу данных float. Данный тип поддерживается только для совместимости кода с другими платформами.

  1. array (массив)

Массив — это именованный набор функций одного типа, где к каждому элементу можно обратиться по его индексу. Задать массив можно несколькими способами:

cpp
int mymass [6];
int mylength [ ] = {2,4,8,3,6};
int mysize [6] = {2,4,-6,3,2};

Массив можно объявить без идеи — так сделано с «mymass» выше. А вот «mylength», наоборот, объявлен без явного указания размера: Компилятор сам посчитает фактическое количество элементов и создаст в памяти массив необходимого размера.

Важная деталь: индексация в массиве начинается с нуля. То есть, если в массиве десять элементов, последний из них будет под индексом 9, а не 10.

cpp
int myArray[10]={9,3,2,4,3,2,7,8,9,11};
//int myArray[0] - первый элемент массива содержит 9
//int myArray[9] - последний элемент содержит 11
//int myArray[10] - это выражение неверно. Индекса 10 не существует, и такое обращение может вернуть случайное значение из памяти,

никак не относящееся к массиву

Присвоить значение массиву можно следующим образом:

myArray[0] = 10;

Извлечение значения элемента из массива делается аналогично

x=myArray[5];

Чаще всего, для перебора значений массива используется цикл for, в котором счетчик цикла используется как индекс для доступа к каждому элементу массива. Например, чтобы вывести значения, содержащиеся в массиве, можно использовать следующий вид:

cpp
int i;
for (i=0; i<5; i=i+1){
Serial.println(myArray[i]);
}
  1. string (строки)

Текстовые строки представляют собой массив символов, и в Arduino IDE они объявляются как массив типа char, но заканчивающийся символом конца строки - “\0”. Обычно строки оканчиваются нулевым символом (код 0 в ASCIL). Это позволяет функциям (таким как Serial.print()) выявлять окончание строки. В противном случае, могут считаться байты памяти, не принадлежащие переменной. Массивы символов, выделяемые под строку, должны иметь один дополнительный элемент для символа конца строки.

Есть несколько способов задания строк:

cpp
char Str1[15];
char Str2[8]={'r','o','b','o','t','i','s'};
char Str3[8]={'r','o','b','o','t','i','s','\0'};
char Str4[]="robotis";
char Str5[8]="robotis";
char Str6[15]="robotis";

Они означают следующее:

  • Str1 — массив объявлен, но значения не объявлены.

  • Str2 — присвоены значения всем элементам, кроме последнего; компилятор сам добавит до конца символьной строки.

  • Str3 — явно объявить завершающий символ.

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

  • Str5 — инициализировать массив с явным указыванием размера и присвоением строковой константы.

  • Str6 — инициализировать массив с явным указыванием дополнительного размера (с запасом), фактически превышающего размер строковой константы при начальном присвоении.

Обратите внимание, что строки всегда объявляются в двойных кавычках, а символы стандарта - в одинарных кавычках.

Если строка длинная, ее можно разбить на несколько частей при инициализации:

cpp
char myString[]=" This is my first line"
"this is the second line"
"etcetera";

При работе с большими объемами текстовой информации, например, в проектах с LCD дисплеем, бывает удобно использовать массив строк. Так как строки сами по себе массивы, массив строк будет двумерным массивом.

В примере ниже, символ звездочки после объявления типа “char*” указывает на то, что это массив указателей. В данном случае не требуется понимания всех тонкостей работы с указателями, достаточно знать, что именно так задаётся двумерный массив строк:

char*myStrings[]= {"This is string 1", "This is string 2", "This is string 3", "This is string 4", "This is string 5", "This is string 6"}

  1. void (пустой)

Void — на самом деле не тип данных, ключевое слово. Им обозначают тип функции, которая ничего не возвращает при вызове.

Пример:

В функциях “setup” и “loop” производятся некоторые действия, но ничего не возвращается во внешнюю программу:

cpp
void setup()
{
//…
}
void loop()
{
//…
}

2.1.3 Преобразование типов данных

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

Для переменной некоторого типа x:

  • char (x) – приведет значение переменной х к типу char

  • byte (x) – приведет значение переменной х к типу byte

  • int (x) – приведет значение переменной х к типу int

  • long (x) – приведет значение переменной х к типу long

  • float (x) – приведет значение переменной х к типу float.

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

2.1.4 Области видимости и квалификаторы

  1. Области видимости

Каждая объявляемая переменная в коде программы может быть использована только из определенных мест программы. Такая видимость переменной зависит от используемого языка программирования. Среда Arduino IDE построена на основе языка С, поэтому и переменные, объявляемые в коде, подчиняются законам видимости, имеющимся в классическом языке С.

Все объявленные переменные по типу видимости делятся на глобальные и локальные. Глобальные переменные видны из любого места программы и доступны как для чтения, так и для записи любой функцией. Локальные переменные видны только внутри той функции, в которой они были объявлены. В среде Arduino IDE любая переменная, объявленная вне какой-либо функции, считается глобальной.

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

Пример:

cpp
int global_var; // глобальная переменная, видимая в любой функции скетча void setup()
{
// ... }
void loop()
{
int i; // «i» видима только внутри «loop»
float f; // «f» видима только внутри «loop»
// …
for (int j = 0; j <100; j++){
// переменная j доступна только внутри этого цикла
}
}
  1. Квалификатор static

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

  1. Квалификатор volatile

Квалификатор volatile используется перед типом переменной для изменения способа интерпретации и доступа к этой переменной компилятором и, в дальнейшем, программой.

Объявление переменной с квалификатором volatile – это указание для компилятора. Квалификатор указывает компилятору загрузить переменную из ОЗУ, а не из запоминающего регистра – временной ячейки памяти, в которой хранятся переменные программы и производятся операции с ними. При определенных условиях значения переменных, хранящихся в регистрах, могут оказаться неточными.

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

  1. Квалификатор const

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

2.2 Функции

2.2.1 Цифровой ввод-вывод

  1. Функция pinMode()

Функция pinMode() устанавливает режим работы заданного пина, как входа или как выхода.

Синтаксис:

pinMode(pin, mode)

Параметры:

pin - номер вход/выхода(pin), который Вы хотите установить

mode - режим, принимает одно из двух значений: INPUT или OUTPUT, устанавливает на вход или выход, соответственно.

Возвращаемое значение:

нет

  1. Функция digitalWrite()

Функция digitalWrite() Подает HIGH или LOW значение на цифровой вход/выход (pin).

Если вход/выход (pin) был установлен в режим «Выход» (OUTPUT) функцией pinMode(), то для значения HIGH напряжение на соответствующем вход/выходе (pin) будет 5В, и напряжение 0В (земля) будет для значения LOW.

Если вход/выход (pin) был установлен в режим «Вход» (INPUT), то функция digitalWrite со значением HIGH будет подтягивать внутренний 20K нагрузочный резистор. Подача значения LOW, в свою очередь, отключает этот резистор.

Синтаксис:

digitalWrite(pin, value)

Параметры:

• pin: номер вход/выхода(pin)

• value: значение HIGH или LOW

Возвращаемое значение:

нет

  1. Функция digitalRead()

Функция digitalRead() считывает значение с заданного входа.

Синтаксис:

digitalRead(pin)

Параметры:

pin - номер вход/выхода(pin), который Вы хотите считать

Возвращаемое значение:

HIGH или LOW

2.2.2 Аналоговый ввод-вывод

  1. Функция analogRead()

Функция analogRead() считывает значение с указанного аналогового входа. Напряжение, поданное на аналоговый вход (от 0 до 5 вольт), будет преобразовано в значение от 0 до 1023, что составляет 1024 шага с разрешением 0.0049 Вольт. Разброс напряжения и шаг могут быть изменены функцией analogReference().

Считывание значения с аналогового входа занимает примерно 100 микросекунд (0.0001 сек), т.е. максимальная частота считывания составляет приблизительно 10 000 раз в секунду.

Синтаксис:

analogRead(pin)

Параметры:

pin - номер порта аналогового входа, с которого будет производиться считывание

Возвращаемое значение:

int (0 to 1023)

Замечание: если аналоговый вход не подключен, то значения, возвращаемые функцией analogRead(), могут быть случайными.

  1. Функция analogReference()

Функция analogReference() определяет опорное напряжение, относительно которого происходят аналоговые измерения. Функция analogRead() возвращает значение с разрешением 10 бит, пропорционально входному напряжению на аналоговом входе и в зависимости от опорного напряжения.

Возможные настройки:

DEFAULT - стандартное опорное напряжение 5 В

EXTERNAL - внешний источник опорного напряжения, подключенный к выводу AREF

Синтаксис:

analogReference(type)

Параметры:

type - определяет используемое опорное напряжение (DEFAULT или EXTERNAL).

Возвращаемое значение:

Нет

Использование вывода AREF:

Напряжение, подключенное к выводу AREF, конвертируется АЦП. Затем определяется значение напряжения, при котором АЦП выдает самое высокое цифровое значение, т.е. 1023. Другие значения напряжения, поступающие в АЦП, конвертируются пропорционально. Таким образом, при настройке DEFAULT 5 В, значение напряжения 2.5 В в АЦП будет конвертироваться в 512.

В стандартной конфигурации платформ Arduino вывод AREF не задействован. В этом случае, при настройке DEFAULT к выводу подключается внутреннее напряжение AVCC. Соединение является низко-импедансным, и любое напряжение, подведенное к выводу в этот момент, может повредить микросхему ATMEGA.

Рекомендуемой настройкой для вывода AREF является EXTERNAL. При этом происходит отключение обоих внутренних источников, и внешнее напряжение будет являться опорным для АЦП.

  1. Функция analogWrite()

Функция analogWrite () выдает аналоговую величину (ШИМ волну) на порт вход/выхода. Функция может быть полезна для управления яркостью подключенного светодиода или скоростью электродвигателя. После вызова analogWrite() на выходе будет генерироваться постоянная прямоугольная волна с заданной шириной импульса до следующего вызова analogWrite (или вызова digitalWrite или digitalRead на том же порту вход/выхода). Частота ШИМ сигнала приблизительно 490 Гц.

Для вызова analogWrite() нет необходимости устанавливать тип вход/ выхода функцией pinMode().

Функция analogWrite никак не связана с аналоговыми входами и с функцией analogRead.

Синтаксис:

analogWrite(pin, value)

Параметры:

• pin: порт вход/выхода, на который подается ШИМ сигнал.

• value: период рабочего цикла, значение между 0 (полностью выключено) и 255 (сигнал подан постоянно).

Возвращаемое значение:

нет

2.2.3 Дополнительные функции ввода-вывода

  1. Функция tone()

Функция tone() генерирует на порту вход/выхода сигнал — прямоугольную «волну» заданной частоты и с 50% рабочим циклом. Длительность может быть задана параметром. В противном случае, сигнал генерируется, пока не будет вызвана функция noTone(). К порту вход/выхода может быть подключен пьезо- или другой динамик для воспроизведения сигнала.

Воспроизводиться одновременно может только один сигнал. Если сигнал уже воспроизводится на одном порту, то вызов функции Tone() с номером другого порта в качестве параметра ни к чему не приведет. Если же функция Tone() будет вызвана с тем же номером порта, то будет установлена новая частота сигнала.

Синтаксис:

cpp
tone(pin, frequency)
tone(pin, frequency, duration)

Параметры:

pin - номер порта вход/выхода, на котором будет генерироваться сигнал frequency: частота сигнала в Герцах

duration - длительность сигнала в миллисекундах

Возвращаемое значение:

нет

  1. Функция noTone()

Функция noTone() останавливает сигнал, генерируемый на порту вход/выхода вызовом функции Tone(). Если сигнал не генерировался, то вызов функции noTone() ни к чему не приводит.

Замечание: если необходимы сигналы на разных портах, то следует сначала остановить один сигнал функцией noTone(), и лишь затем создавать новый сигнал на другом порту функцией Tone().

Синтаксис: noTone(pin)

Параметры:

pin - номер порта вход/выхода, на котором прекращаем сигнал Возвращаемое значение:

нет

  1. Функция shiftOut()

Функция shiftOut() выводит байт информации на порт вход/выхода последовательно (побитно). Вывод может осуществляться как с первого (левого), так и с последнего (правого) бита. Каждый бит последовательно подается на заданный порт, после чего подается сигнал на синхронизирующий порт вход/выход, информируя о доступности к считыванию бита.

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

Синтаксис:

shiftOut(dataPin, clockPin, bitOrder, value)

Параметры:

dataPin - номер порта вход/выхода, на который выводятся биты (int) clockPin: номер порта, по которому производится синхронизация (int) bitOrder: используемая последовательность вывода бит: MSBFIRST (Most Significant Bit First) — слева или LSBFIRST (Least Significant Bit First) — справа.

value - значение (байт) для вывода (byte)

Возвращаемое значение:

нет

  1. Функция pulseIn()

Функция pulsenIn() считывает длину сигнала на заданном порту (HIGH или LOW). Например, если функцией pulseIn() задано считывание значения HIGH, функция ожидает, пока на заданном порту не появится значение HIGH. Когда значение HIGH получено, включается таймер, который будет остановлен в момент появления на порту вход/выхода значения LOW. Функция pulseIn() возвращает длину сигнала в микросекундах. Функция возвращает 0, если в течение заданного времени (таймаута) не был зафиксирован сигнал на порту.

Возможны некоторые погрешности при измерении длинных сигналов. Функция может измерять сигналы длиной от 10 микросекунд до 3 минут. Синтаксис:

cpp
pulseIn(pin, value)
pulseIn(pin, value, timeout)

Параметры:

pin - номер порта вход/выхода, на котором будет ожидаться сигнал. (int) value: тип ожидаемого сигнала — HIGH или LOW.

timeout (опционально): время ожидания сигнала (таймаут) в микросекундах; по умолчанию - одна секунда. (unsigned long) Возвращаемое значение:

Длина сигнала в микросекундах или 0, если сигнал не получен до истечения таймаута. (unsigned long)

2.2.4 Работа со временем

  1. Функция millis()

Функция millis() возвращает количество миллисекунд с момента начала выполнения текущей программы на физической платформе. Это количество сбрасывается на ноль вследствие переполнения значения, приблизительно через 50 дней.

Параметры:

нет

Возвращаемое значение:

Количество миллисекунд с момента начала выполнения программы. (unsigned long)

  1. Функция micros()

Функция micros() возвращает количество микросекунд с момента начала выполнения текущей программы на плате Arduino. Значение переполняется и сбрасывается на ноль, приблизительно через 70 минут. Параметры:

нет

Возвращаемое значение: Количество микросекунд с момента начала выполнения программы. (unsigned long)

  1. Функция delay()

Функция delay() останавливает выполнение программы на заданное в параметре количество миллисекунд (1000 миллисекунд в 1 секунде). Синтаксис:

delay(ms)

Параметры:

ms - количество миллисекунд, на которое приостанавливается выполнение программы. (unsigned long)

Возвращаемое значение:

Нет

  1. Функция delayMicroseconds()

Функция delayMicroseconds() останавливает выполнение программы на заданное в параметре количество микросекунд (1 000 000 микросекунд в 1 секунде).

Для остановки выполнения программы более чем на несколько тысяч Н микросекунд рекомендуется использовать функцию delay().

Синтаксис:

delayMicroseconds(us)

Параметры:

us - количество микросекунд, на которое приостанавливается выполнение программы. (unsigned int)

Возвращаемое значение:

Нет

2.2.5 Математические функции

  1. Функция min()

Функция min(x, y) возвращает наименьшее из двух значений.

Параметры:

x - первое число, любой тип

y - второе число, любой тип

Возвращаемое значение:

меньшее из двух сравниваемых значений.

  1. Функция max()

Функция max (x, y) возвращает большее из двух значений.

Параметры: x: первое число, любой тип

y - второе число, любой тип

Возвращаемое значение:

большее из двух сравниваемых значений.

  1. Функция abs()

Функция abs(x) возвращает модуль числа.

Параметры:

x - число

Возвращаемое значение:

x - если x больше или равен 0.

-x: если x меньше 0.

  1. Функция constrain()

Функция constrain(x, a, b) проверяет и, если нужно, задает новое значение, чтобы оно было в области допустимых значений, заданной параметрами. Параметры:

x - проверяемое значение, любой тип

a - нижняя граница области допустимых значений, любой тип

b - верхняя граница области допустимых значений, любой тип Возвращаемое значение:

x - если x входит в область допустимых значений [a..b]

a - если x меньше a

b - если x больше b

  1. Функция map()

Функция map(value, fromLow, fromHigh, toLow, toHigh) пропорционально переносит значение (value) из текущего диапазона значений (fromLow ... fromHigh) в новый диапазон (toLow ... toHigh), заданный параметрами. Функция map() не ограничивает значение рамками диапазона, как это делает функция constrain(). Contrain() может быть использован до или после вызова map(), если необходимо ограничить допустимые значения заданным диапазоном.

Обратите внимание, что «нижняя граница» может быть как меньше, так и больше «верхней границы». Это может быть использовано для того, чтобы «перевернуть» диапазон:

y = map(x, 1, 50, 50, 1);

Возможно использование отрицательных значений:

y = map(x, 1, 50, 50, -100);

Функция map() оперирует целыми числами. При пропорциональном переносе дробная часть не округляется по правилам, а просто отбрасывается.

Параметры:

value - значение для переноса

fromLow - нижняя граница текущего диапазона

fromHigh - верхняя граница текущего диапазона

toLow - нижняя граница нового диапазона, в который переносится значение toHigh: верхняя граница нового диапазона

Возвращаемое значение:

Значение в новом диапазоне.

  1. Функция pow()

Функция pow(base, exponent) вычисляет значение, возведенное в заданную степень. Функция pow() может возводить в дробную степень. Параметры:

base - число (float)

exponent - степень, в которую будет возводиться число (float)

Возвращаемое значение:

Результат возведения в степень, число (double).

  1. Функция sq()

Функция sq(x) возвращает квадрат числа, заданного параметром. Параметры:

x - число, любой тип

Возвращаемое значение:

Квадрат числа

  1. Функция sqrt()

Функция sqrt(x) вычисляет квадратный корень числа, заданного параметром.

Параметры:

x - число, любой тип

Возвращаемое значение:

Квадратный корень числа (double)

2.2.6 Тригонометрические функции

  1. Функция sin()

Функция sin(rad) возвращает синус угла, заданного в радианах. Результат функции всегда в диапазоне -1...1.

Параметры:

rad - угол в радианах (float)

Возвращаемое значение:

Синус угла (double)

  1. Функция cos()

Функция cos(rad) возвращает косинус угла, заданного в радианах в параметре. Результат функции всегда находится в диапазоне -1..1. Параметры:

rad - угол в радианах (float)

Возвращаемое значение:

Косинус угла (double)

  1. Функция tan()

Функция tan(rad) возвращает тангенс угла, заданного в радианах в передаваемом параметре. Результат функции в диапазоне от минус бесконечность до плюс бесконечность.

Параметры:

rad - угол в радианах (float)

Возвращаемое значение:

Тангенс угла (double)

2.2.7 Генераторы случайных значений

  1. Функция randomSeed()

Функция RandomSeed(seed) инициализирует генератор псевдослучайных чисел. Генерируемая последовательность случайных чисел очень длинная, и всегда одна и та же. Точка в этой последовательности, с которой начинается генерация чисел, зависит от параметра seed.

Если при каждом запуске программы необходимо получать разные последовательности значений, генерируемых функцией random(), то необходимо инициализировать генератор псевдослучайных чисел со случайным параметром. Например, можно использовать значение, отдаваемое функцией analogRead() c неподключенного порта вход/выхода.

В некоторых случаях необходимо получать одинаковую последовательность при каждом запуске программы на контроллере OpenCM. В этом случае инициализировать генератор псевдослучайных чисел следует вызовом функции randomSeed() с фиксированным параметром.

Параметры:

seed - параметр, задающий начало выдачи псевдослучайных значений на последовательности. (int, long)

Возвращаемое значение:

нет

  1. Функция random()

Функция random() возвращает псевдослучайное число.

Синтаксис: random(max)

random(min, max)

Параметры:

• min: нижняя граница случайных значений, включительно. (опционально)

• max: верхняя граница случайных значений, не включительно.

Возвращаемое значение:

Случайное число между min и max-1. (long)

2.3 Операторы

2.3.1 Управляющие операторы, операторы сравнения

  1. Оператор if

Оператор if (условный оператор) проверяет достигнута ли истинность условия, указанного с помощью операторов сравнения. Например, превышает ли значение одной переменной значения другой. Синтаксис оператора следующий:

cpp
if ( "переменная" > 100)
{
//перечень команд
}

В данном примере программа проверит значение переменной “переменная” на сравнение со значением 100. Если будет достигнута истинность условия (например, если значение переменной “переменная” равно 1000), то будут выполнены последовательно команды, записанные в блоке, ограниченном фигурными скобками, и относящемуся к оператору if. Если истинность условия не достигнута (например, если значение переменной “переменная” равно 10), то программа пропустит выполнение части кода, относящемуся к блоку оператора if.

Явное выделение блока оператора if (фигурные скобки после оператора if) могут быть опущены. В этом случае будет считаться, что только следующая строка, ограниченная точкой с запятой и стоящая после оператора if, будет относиться к этому оператору.

Вариант 1:

if (x > 120) x = x + 1;

Вариант 2:

if (x > 120) x = x + 1;

Вариант 3:

if (x > 120){ x = x + 1; }

Вариант 4:

cpp
if (x > 120){
x = x + 1;
x = x + 2;
}

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

  1. Операторы сравнения

Для задания условий сравнения используются операторы сравнения. Синтаксис операторов сравнения следующий:

x == y (x равно) – двойной знак равенства

x! = y (x не равно y) – восклицательный знак со знаком равенства

x <y (x меньше, чем y) - знак меньше

x> y (x больше, чем y) – знак больше

x <= y (x меньше, чем или равно y) – знак меньше и равно

x>= y (x больше, чем или равно y) – знак больше и равно

  1. Оператор if…else

Оператор if…else (в некоторых источниках называется конструкция или выражение) позволяет осуществлять большее ветвление кода программы, чем базовый оператор if. Это достигается за счет осуществления нескольких проверок, объединенных вместе, а также возможностью перехода на определенную команду, если ни одна из проверок не достигла истинности условия. Например, значение переменной может быть сравнено с некоторой величиной и определенные действия могут выполнены как в случае истинности сравнения, так и в противоположном случае. Синтаксис оператора в этом случае будет выражен следующим образом:

cpp
if ("переменная"> 100)
{
// перечень команд 1
}
else
{
// перечень команд 2
}

Так же, else позволяет осуществить отличную от указанной ранее в if проверку. Причем, таким образом можно организовывать несколько взаимоисключающих проверок. Например:

cpp
if ("переменная"> 100)
{
// перечень команд 1
}
else if ("переменная" <50)
{
// перечень команд 2
}
else
{
// перечень команд 3
}

Число переходов else…if в коде не ограничено, при этом конструкция else…if может быть использована как с заключительным одиночным else, так и без него.

  1. Оператор switch…case

Оператор switch…case, подобно конструкции if…else, позволяет программисту выполнять ветвление программы, задавая код, который будет выполняться при разных условиях. Однако, в отличие от if…else, switch…case всегда выполняет проверку значения переменной на равенство со значением оператора case. Для работы с оператором switch необходимо использовать ключевое слово break, указанное в конце каждого case для выхода из серии проверок. Без этого слова оператор switch будет выполнять проверки до тех пор, пока не переберет все доступные варианты case и не достигнет конца оператора. Синтаксис оператора следующий:

cpp
switch ("переменная") {
case 1: //выполняемый код, когда значение переменной "переменная" равно 1
break;
case 2: // выполняемый код, когда значение переменной "переменная" равно 2
break;

default:

cpp
// выполняемый код, если значение переменной "переменная" не совпало // ни с одним значением case. default необязателен
}
  1. Оператор for

Оператор for (обычно употребляется понятие «цикл» for) используется для организации повторения выполнения определенных команд, расположенных внутри блока, относящемуся к циклу for (заключенных в фигурные скобки после цикла for). Цикл for состоит из двух частей: заголовка и тела (блока) цикла. Синтаксис следующий:

cpp
for («инициализация»; «условие»; «приращение»)
{
// перечень повторяемых команд
}

Инициализация в заголовке необходима для указания начального значения счетчика повторов. На каждой итерации цикла происходит проверка условия на соответствие текущего значения счетчика некоторому заранее определенному значению. В случае, если текущее значение счетчика меньше заранее заданного, то происходит изменение значения счетчика по закону, указанному в поле «приращение». В противном случае, цикл завершается. Пример:

cpp
for (int i = 0; i < 10; i = i + 1)
{
SerialUSB.println(i);
}

В результате работы данного цикла на экран будут выведены числа от 0 до 9.

  1. Оператор break

Оператор break (в некоторых источниках – ключевое слово break) используется для принудительного выхода из циклов, не дожидаясь выхода цикла по условию. Этот оператор также применяется в операторе выбора switch…case.

cpp
for (int i = 0; i < 10; i = i + 1)
{
SerialUSB.println(i);
break;
}

В данном примере выход из цикла осуществляется после выполнения первой итерации, при этом на экран будет выведено значение 0.

  1. Оператор while

Оператор while (обычно называют цикл while) выполняет в своем теле (блоке) набор команд непрерывно до тех пор, пока условие, заданное в круглых скобках, не станет равно логическому «Ложь». Переменная, чье значение проверяется на выполнение условию, должна изменять свое значение в теле цикла, иначе цикл станет бесконечным. Синтаксис оператора следующий:

cpp
while (условие) {
// перечень выполняемых команд
}

Пример:

cpp
var = 0;
while (var < 200)
{
var++;
SerialUSB.println(var);
}

В этом примере на экран будут выведены значения с 1 до 200.

  1. Оператор do…while

Оператор do…while (обычно называют цикл do…while) работает аналогично циклу while. Единственное отличие заключается в том, что условие выполнения цикла do…while проверяется в конце цикла. Таким образом, цикл do…while будет всегда выполняться как минимум один раз. Синтаксис оператора следующий:

do

cpp
{
// перечень выполняемых команд
}
while (проверка условия);

Пример:

var = 0;

do

cpp
{
var++;
SerialUSB.println(var);
} while (var <200);

В этом примере на экран будут выведены значения с 1 до 200.

  1. Оператор continue

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

Пример:

cpp
for (int i = 0; i < 100; i++)
{
if ((i > 25) & (i < 75))
{
continue;
}
USBSerial.println(i);
}

В результате работы этого примера на экран выдут выведены значения с 0 до 25 и с 75 до 99.

  1. Оператор return

Оператор return прекращает процесс вычислений в функции и возвращает значение из прерванной функции в возвращающую, если это необходимо.

Пример:

cpp
int testfunction()
{
int a = 0;
int b = 1;
int c;
c = a + b;
return c;
}

В данном примере оператор return вернет результат сложения двух чисел.

2.3.2 Арифметические операторы

  1. Оператор присваивания

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

Пример:

cpp
int val = 0;
USBSerial.println(val);
val = 10;
USBSerial.println(val);

В данном примере на экране будут выведены сначала значение 0, а затем значение 10.

  1. Операторы сложения, вычитания, умножения и деления

Операторы сложения «+», вычитания «-», умножения «*» и деления «/» возвращают результат соответствующих операций над двумя операндами, которыми могут быть константы, переменные, функции, выражения и т.п. Возвращаемый результат всегда имеет зависимости от типов данных и от допустимого диапазона значений определенного типа данных.

Примеры:

cpp
y = y + 3;
x = x - 7;
i = j * 6;
r = r / 5;
  1. Оператор «модуль»

Оператор «модуль» возвращает остаток от деления одного целого (типа int) операнда на другой.

Пример:

cpp
x = 7 % 5; // x имеет значение 2
x = 9 % 5; // x имеет значение 4
x = 5 % 5; // x имеет значение 0
x = 4 % 5; // x имеет значение 4

2.3.3 Логические операторы

Логические операторы используются для проверки условий. Различают три логических оператора:

Логические И (&&) – возвращает значение «ИСТИНА», если оба операнда истинны.

Пример:

cpp
int a = 5;
int b = 7;
if ((a> 3) && (b <10)) {
// выполняется ряд команд
}

Логическое ИЛИ (||) – возвращает значение «ИСТИНА», если хотя бы один операнд - «ИСТИНА».

Пример:

cpp
int a = 5;
int b = 7;
if ((a> 0) || (b> 0)) {
// выполняется ряд команд
}

Логическое отрицание (! =) – возвращает значение «ИСТИНА», если операнд «ЛОЖЬ» и наоборот.

Пример:

cpp
int a = 5;
if (a != 0) {
// выполняется ряд команд
}

2.3.4 Унарные операторы

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

  1. Операторы инкремента и декремента

Операторы инкремента (++) и декремента (--) используются для увеличения или уменьшения значения переменной соответственно. В зависимости от расположения оператора относительно операнда возвращаемый результат будет отличаться.

Пример:

x++; // увеличивает значение x на единицу и возвращает старое значение x

++x; // увеличивает значение x на единицу и возвращает новое значение x

x-- ; // уменьшает значение x на единицу и возвращает старое значение x –

x ; // уменьшает значение x на единицу и возвращает новое значение x

Пример для записи выражений:

cpp
x = 2;
y = ++x; // x теперь равно 3, y равно 3
y = x--; // x равно 2, y равно 3
  1. Операторы сокращенной записи арифметических действий

Большинство языков программирования поддерживают сокращенный способ записи арифметических действий над переменной и одним операндом. В языке С это выглядит следующим образом:

cpp
x += y; // эквивалент записи x = x + y;
x -= y; // эквивалент записи x = x - y;
x *= y; // эквивалент записи x = x * y;
x /= y; // эквивалент записи x = x / y;

Пример:

cpp
x = 2;
x += 4; // x равно 6
x -= 3; // x равно 3
x *= 10; // x равно 30
x /= 2; // x равно 15

2.4 Синтаксис

Как в любом другом языке программирования, в языке С есть определенные правила построения кода, которые обязательны для выполнения. Несоблюдение этих правил ведет к невозможности компилирования написанного кода, и, как следствие, к невозможности его исполнения. В этом разделе представлены основные моменты, которые обязательно соблюдать при написании своего кода.

  1. Точка с запятой (😉

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

Пример:

int a = 13;

В данном примере точка с запятой отделяет оператор присваивания от последующих операторов.

Забытая в конце оператора точка с запятой приводит к ошибке компиляции, причем текст ошибки может быть как видимым (компилятор укажет на отсутствие точки с запятой), так и не явным. Если появилась непонятная ошибка, то, первым делом, надо проверить наличие необходимых точек с запятыми.

  1. Фигурные скобки

Фигурные скобки ({}), которые также иногда называются «Блок», важный элемент языка программирования С. Они используются для обозначения основной части, так называемого «Тела» функции, цикла или оператора. Все операторы, выражения, конструкции, ограниченные такими скобками, будут отнесены к одному оператору или функции и будут выполнятся только при условии выполнения этой функции или оператора. Открывающаяся фигурная скобка ({) всегда должна дополняться закрывающей скобкой (}). Это условие называется «Парность» (симметричность) фигурных скобок. В случае несоблюдения этого условия, компилятор будет выдавать скрытые или непонятные ошибки при компиляции. Поэтому хорошим тоном всегда считается, в случае написания открывающейся скобки, сразу писать и закрывающуюся. Таким образом, забыть о необходимости написать закрывающуюся скобку не получится.

Примеры:

cpp
void function (тип данных аргумента) {
// список операторов
}

В этом примере список операторов относится к функции function и будет выполнен только при условии вызова (выполнения) функции function. while (логическое выражение) {

cpp
// список операторов
}

В этом примере список операторов относится к циклу while и будет выполнен, только если будет выполняться цикл while. Для других циклов – аналогично.

  1. Комментарии

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

Первый способ – однострочный комментарий. Всё, что будет находиться на одной строке после символов «//», будет восприниматься компилятором, как комментарий.

Пример:

int a = 0; // все, что расположено далее, комментарий.

Или

// int a = 0; - в этом примере вся строка является комментарием

Второй способ – многострочный комментарий. Такой комментарий используется для комментирования сразу части кода, независимо от количества строк. Для этого используются символы «/» - для открытия многострочного комментария, и «/» - для закрытия многострочного комментария.

Пример:

/* начало многострочного комментария

cpp
for (int i=0; i <10; i++) {
// внутри многострочного комментария можно использовать однострочные комментарии, но нельзя использовать многострочные
}

далее конец многострочного комментария */

2.5 Знакомство с графическим языком программирования

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

Блочный язык программирования — это способ создания программ, при котором алгоритм собирается из визуальных блоков, соединяемых между собой подобно элементам конструктора. Каждый блок представляет отдельную команду, функцию или логическое действие (например: условие, цикл, переменную, операцию ввода-вывода).

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

Блочные языки используются для:

  • обучения основам программирования;

  • разработки алгоритмов без необходимости писать текстовый код;

  • программирования микроконтроллеров и робототехнических систем;

  • быстрого прототипирования программ и устройств;

  • визуального анализа логики работы программы.

Особенно широко такие языки применяются в образовательных проектах, робототехнике и системах управления.

Использование блочного программирования имеет ряд преимуществ:

  1. Наглядность алгоритма.

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

  1. Отсутствие синтаксических ошибок.

Блоки соединяются только допустимым образом, поэтому исключаются ошибки, связанные с неправильным написанием кода.

  1. Простота освоения.

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

  1. Удобство отладки.

Логика программы легко отслеживается, а ошибки можно быстрее обнаружить.

  1. Быстрое создание прототипов.

Позволяет оперативно создавать и тестировать алгоритмы управления устройствами.

  1. Подходит для обучения алгоритмическому мышлению.

Пользователь концентрируется на логике решения задачи, а не на синтаксисе языка.

2.6 Установка

Алгоритм установки ARBlockly:

  1. Актуальную версию программы можно загрузить по ссылке.

  2. Запустим программу двойным щелчком по ярлыку (рисунок 2.6.1)

Изображение 42

Рисунок 2.6.1 – Ярлык программы

  1. При запуске программы откроется командная строка (рисунок 2.6.2)

Изображение 43

Рисунок 2.6.2 – Командная строка при запуске программы

  1. Автоматически откроется сама программа (рисунок 2.6.3):

Изображение 44

Рисунок 2.6.3. – Главное окно программы

  1. При первом запуске необходимо выполнить дополнительную установку. Для этого нажмем на кнопку настроек (рисунок 2.6.4):

Изображение 45

Рисунок 2.6.4. – Кнопка настроек

  1. В открывшемся меню можно установить пакеты для работы с различными микроконтроллерами, проверить обновления приложения, а также установить все необходимые библиотеки для работы. (рисунок 2.6.5.).

Изображение 46

Рисунок 2.6.5. – Меню обновления приложения

  1. Для первого запуска, обязательно нажмем на иконку обновления, если будет новое обновление появится кнопка для загрузки и установки, а также установим библиотеки (рисунок 2.6.6):

Изображение 47

Рисунок 2.6.6. – Обязательное обновление

  1. Настройка подключения платы (рисунок 2.6.7):
  • Выберем из списка плату Leonardo

  • Нажмем кнопку «Обновить порты»

  • Выберем соответствующий COM-порт платы из списка

Изображение 48

Рисунок 2.6.7. – Выбор платы и порта подключения

  1. Для загрузки кода необходимо нажать на кнопку загрузки (рисунок 2.6.8)

Изображение 49

Рисунок 2.6.8. – Загрузка на плату

  1. После нажатия потребуется подтверждение для загрузки блоков на плату (рисунок 2.6.9).

Изображение 50

Рисунок 2.6.9. – Подтверждение загрузки кода на плату

  1. После успешной загрузки выйдет уведомление (рисунок 2.6.10)

Изображение 51

Рисунок 2.6.10. – Успешная загрузка кода на плату

Также собранные блоки можно сохранить в виде скетча, нажав на соответствующую кнопку (рисунок 2.6.11).

Изображение 52

Рисунок 2.6.11. – Сохранение блоков в виде скетча

А также можно сохранить в виде xml файла для дальнейшей передачи (рисунок 2.6.12), после чего его можно загрузить (рисунок 2.6.13).

Изображение 53

Рисунок 2.6.12. – Сохранение блоков в виде xml

Изображение 54

Рисунок 2.6.13. – Загрузка блоков из xml файла.

2.7 Основные категории блоков в ARBlockly

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

Основная задача - создании связи между физическим миром (микроконтроллером, датчиками и исполнительными устройствами). Ниже представлены категории блоков, обеспечивающие различные аспекты такого взаимодействия. (рисунок 2.7.1)

Изображение 55

Рисунок 2.7.1. Категории блоков в ARBlockly

Логические — предназначены для работы с условными операторами и логическими выражениями. Используются для построения условий, сравнения значений и управления выполнением программы.

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

Математические — содержат инструменты для выполнения вычислений: арифметических операций, тригонометрических функций, проверки свойств чисел и генерации случайных значений.

Текстовые — предназначены для работы со строковыми данными: создание и объединение строк, определение их длины, преобразование текста в HEX-формат и извлечение подстрок.

Сигналы — обеспечивают работу с цифровыми и аналоговыми пинами Arduino: настройку пинов, чтение и запись сигналов, управление звуком и генерацией сигналов.

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

Порт — обеспечивает работу с последовательным портом (Serial): инициализацию соединения, передачу и приём данных, а также проверку их доступности.

DXL — предназначены для взаимодействия с сервомоторами Dynamixel: инициализации шины DXL, чтения и записи данных в регистры устройств.

I2C — обеспечивают работу с шиной I2C (Wire) и подключёнными устройствами, включая LCD-дисплеи: инициализацию соединения и управление устройствами на шине.

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

Серво — предназначены для управления сервоприводами, подключёнными к микроконтроллеру, включая установку их положения.

Списки — позволяют работать с массивами (списками): создавать их, определять длину и обращаться к элементам по индексу.

Функции — используются для создания пользовательских функций и процедур, а также основных методов Arduino (setup и loop). Они помогают структурировать программу и повторно использовать код.

Доп. функции — содержат вспомогательные элементы, например создание пользовательской функции и их вызов.

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

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

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

Изображение 56

Рисунок 2.7.2. – Переход к информации о блоках

Изображение 57

Рисунок 2.7.3. – Пример описания блоков в программе ARBlockly