Руководство по программированию Arduino

Ответить
Oleg
Сообщения: 186
Зарегистрирован: 12 июл 2023, 12:09
Руководство по программированию Arduino

Сообщение Oleg »

Функции setup и loop

Функции - это блоки программного кода, делающие что-либо. В каждом скетче Arduino должны быть своя функция setup() и своя функция lоор (). Чтобы рассмотреть функции setup() и loop() в действии, давайте исследуем скетч Blink, который уже загружен в Arduinо:

Код: Выделить всё

int led - 13;
// процедура запуска выполняется один раз 
// после того, как вы нажмете кнопку сброса:
void setup(){
// инициализируем цифровой контакт как вывод.
pinMode(led, OUTPUT);
// циклическая процедура повторяется снова и снова,
// и так до бесконечности:
void loop(){

digitalWrite(led, HIGH); // включить светодиод
                                    // (HIGH это уровень напряжения)
delay(1000);                  // подождать 1 секунду
digitalWrite(led, LOW);// выключить светодиод,
                                   // переведя напряжение на уровень LOW
delay(1000);                // подождать 1 секунду
В скетче вы видите немало текстовых строк, перед которыми стоят символы // Они означают, что весь текст после // и до конца строки считается комментарием. Это не программный код, а просто пояснения, помогающие человеку, читающему код, понять, что происходит.

Как понятно из комментариев, строки кода в функции setup() выполняются всего один раз точнее, всякий раз, как только на Arduino подается питание или после нажатия кнопки сброса. То есть, функция setup() используется для выполнения всех однократных операций, которые нужно выполнить при запуске программы. В примере с Blink нам всего лишь требуется указать, что контакт светодиода сконфигурирован как вывод.

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

Здесь я не буду останавливаться на том, что именно делают в скетче Blink команды, находящиеся в функциях setup() и loop(), но не волнуйтесь - вскоре мы об этом поговорим.
Oleg
Сообщения: 186
Зарегистрирован: 12 июл 2023, 12:09
Re: Руководство по программированию Arduino

Сообщение Oleg »

Переменные

Код: Выделить всё

int led - 13;
// процедура запуска выполняется один раз 
// после того, как вы нажмете кнопку сброса:
void setup(){
// инициализируем цифровой контакт как вывод.
pinMode(led, OUTPUT);
// циклическая процедура повторяется снова и снова,
// и так до бесконечности:
void loop(){

digitalWrite(led, HIGH); // включить светодиод
                                    // (HIGH это уровень напряжения)
delay(1000);                  // подождать 1 секунду
digitalWrite(led, LOW);// выключить светодиод,
                                   // переведя напряжение на уровень LOW
delay(1000);                // подождать 1 секунду
При помощи переменных можно присваивать значениям имена. Первая строка скетча Blink (не считая комментариев) такова:

int led - 13;

В ней определяется переменная led, получающая исходное значение 13. Значение 13 выбрано по имени того контакта Arduinо, к которому подключен светодиод L, a int это тип переменной. Слово int является сокращением от integer, что в переводе с английского означает «целое число» (без десятичных знаков).

Хотя и не обязательно давать отдельное имя переменной для каждого контакта, с которым вы работаете, лучше все-таки это делать, поскольку так становится проще понять, какой контакт для чего используется. Кроме того, если вы захотите перейти на другой контакт, то вам потребуется изменить имя переменной всего в одном месте там, где вы ее определили.

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

const int led - 13;

Ключевое слово const сообщает Arduino IDE, что на самом деле это никакая не переменная, а константа, иными словами, ее значение равно 13, и никогда не меняется. В результате скетчи становятся немного компактнее и работают быстрее - рекомендуется, чтобы использование этого слова вошло у вас в привычку.
Oleg
Сообщения: 186
Зарегистрирован: 12 июл 2023, 12:09
Re: Руководство по программированию Arduino

Сообщение Oleg »

Цифровые выводы

Скетч Blink - хороший пример цифрового вывода. Ранее мы присвоили перемен ной 10 значение 13, а в следующей строке контакт 13 в функции setup() был сконфигурирован на вывод:

pinMode(led, OUTPUT);

Эта операция делается именно в функции sеtup(), поскольку ее нужно выполнить всего один раз. Как только контакт сконфигурирован на вывод, он так и будет работать на вывод, пока вы не отдадите другую команду.

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

Код: Выделить всё

digitalWrite(led, HIGH); // включить светодиод
                                    // (HIGH это уровень напряжения)
delay(1000);                  // подождать 1 секунду
digitalWrite(led, LOW);// выключить светодиод,
                                   // переведя напряжение на уровень LOW
delay(1000);                // подождать 1 секунду
У функции digitalWrite(), как можно видеть, имеются два параметра - они приводятся в скобках и разделены запятой. Первый параметр- это контакт Arduino, на который пойдет запись, а второй значение, которое будет туда записано. Итак, значение HIGH дает нам 5 В на выходе (и светодиод включается), а значение LOW соответствует 0 В (и светодиод выключается).

Функция delay() приостанавливает выполнение программы на некоторое время (в миллисекундах) - это время задается в качестве параметра. В одной секунде 1000 миллисекунд, поэтому каждая функция delay(1000) приостановит выполне ние программы на 1 секунду.
Oleg
Сообщения: 186
Зарегистрирован: 12 июл 2023, 12:09
Re: Руководство по программированию Arduino

Сообщение Oleg »

Цифровые входы

Вы должны знать и о цифровых входах, через которые можно подсоединять к Arduino переключатели и датчики. В качестве цифрового входа контакт Arduino можно определить при помощи функции ріnMode ();

pinMode(7, INPUT)

Здесь мы конфигурируем на вход контакт 7. Разумеется, вместо номера контакта 7 можно использовать имя переменной.
Так же, как и в случае с выводным контактом, входной контакт задается при помощи функции sетup(), поскольку на этапе выполнения скетча менять режим работы контакта приходится редко. С контакта, сконфигурированного на вход, потом можно считывать информацию, определяя, ближе к какому значению находится напряжение на контакте: 5 В (HIGH) или 0 В (LOW). В следующем примере светодиод будет включен, если на момент проверки вход будет иметь значение LOW (после этого светодиод так и останется гореть, поскольку в коде нет команды, которая бы его выключала):

Код: Выделить всё

void loop()
{
if (digitalRead(7) == HIGH)
{
digitalWrite(led, LOW)
}
}
Что ж, наш код стал немного усложняться, поэтому разберем его построчно.

Во второй строке у нас присутствует символ {. Иногда он ставится на той же строке, что и looр(), а иногда переносится на следующую. Это дело вкуса - оба варианта кода выполняются одинаково. Символ отмечает начало блока кода, а заканчивается этот блок парным символом }. Таким образом все строки кода, относящиеся здесь к функции looр(), группируются вместе.

В первой из этих строк стоит оператор if. Сразу после слова if идет условие. В нашем случае условие таково: (digitalRead(7)== HIGH). Двойной знак равенства (==) означает сравнение значений, расположенных слева и справа от него. Итак, если в нашем случае контакт 7 имеет значение HIGH, то после оператора if выполнится блок кода, записанный между { и }, - в противном случае этого не про- изойдет. Если выровнять по вертикали открывающие и закрывающие блоки кода символы { и }, становится понятнее, какая } замыкает какую {.

Мы уже видели, как выполняется код, если условие соблюдается, тогда срабатывает функция digitalWrite(), включающая светодиод.

В этом примере мы предположили, что цифровой вход может иметь строго одно из двух значений: HIGH или LOW. Если вы используете переключатель, подсоединенный к цифровому входу, то этот переключатель позволяет только лишь закрыть (замкнуть) соединение. Как правило, в таком случае имеется в виду подсоединение цифрового входа к заземлению (0 В). Если соединение на переключателе открыто, то принято говорить, что цифровой вход является неподключенным (незамкнутым). Иными словами, он не имеет никакого электрического соединения. Такой вход будет принимать электрические помехи и зачастую может самопроизвольно переключаться между высоким и низким состояниями. Во избежание такого нежелательного процесса обычно используют так называемый подтягивающий резистор (рис.).
Снимок экрана 2023-08-11 150709.jpg
Когда переключатель не замкнут (как показано на рисунке), резистор «подтягивает» входной контакт до напряжения 5 В. Когда мы, нажав на кнопку, переключатель замыкаем, то слабое подтягивание входа перекрывается переключателем, подсоединяющим цифровой вход к заземлению.

Вы, конечно, можете пользоваться отдельными подтягивающими резисторами, однако на входах Arduino имеются встроенные подтягивающие резисторы со значением около 40 кОм, которые включаются, если установить для контакта режим работы INPUT_PULLUP, а не просто INPUT. Следующий код демонстрирует, как за дать для контакта цифрового входа режим для работы с переключателем без необ- ходимости использования внешнего подтягивающего резистора, показанного на рисунке;

pinMode (switchPin, INPUT_PULLUP);
Oleg
Сообщения: 186
Зарегистрирован: 12 июл 2023, 12:09
Re: Руководство по программированию Arduino

Сообщение Oleg »

Аналоговые входы

Аналоговые входы позволяют измерять напряжение в диапазоне от 0 до 5 В на лю бом из специальных аналоговых контактов Arduino, пронумерованных от А0 до А5. В отличие от работы с цифровыми выводами и входами, задействуя аналоговые входы, не нужно использовать функцию pinMode() на этапе setup.

Для считывания значения аналогового входа служит функция analogRead(), одним из параметров которой является имя того контакта, который вы собираетесь считывать. В отличие от функции digitalRead(), функция analogRead() возвращает число, а не просто значения «истина» или «ложь». Число, которое вы получите от analogRead(), будет находиться в диапазоне от 0 до 1023. Результат 0 соответствует 0 В, а результат 1023 - 5 В. Чтобы преобразовать число, полученное от функции analoghead(), в фактическое значение напряжения, нужно умножить число на 5, а затем разделить на 1023, либо просто разделить его на 204,6. Вот как это дела ется на языке С для платформы Arduino:

int raw analogRead(AO));
float volts - raw / 204, 6;


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

К аналоговым входам можно подключать различные датчики - например, использовать аналоговый вход совместно с фоторезисторным датчиком и как подключать переменный резистор.
Oleg
Сообщения: 186
Зарегистрирован: 12 июл 2023, 12:09
Re: Руководство по программированию Arduino

Сообщение Oleg »

Аналоговые выводы

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

В качестве аналоговых выводов способны функционировать не все контакты Arduino Uno, а только D3. D5, D6, D9, D10 и D11. На плате Arduino эти контакты помечены символом тильды (~).

Для управления аналоговым выводом используется функция analogWrite(), принимающая в качестве параметра число в диапазоне от 0 до 255. Значение 0 означает 0 В (полностью выключено), а значение 255 включено на полную мощность.

Можно было бы предположить, что аналоговый вывод просто принимает напряжение в диапазоне от 0 В до 5 В, и, если подключить вольтметр между контактом, используемым в качестве аналогового вывода, и заземлением, то при изменении значения параметров функции алаlogwrite() от 0 до 255 действительно покажется, что напряжение меняется между 0 и 5 В. Тем не менее, на самом деле все обстоит несколько сложнее. На рисунке показано, что фактически происходит с таким выводом - это явление называется широтно-импульсной модуляцией (ШИМ).

Каждый контакт, работающий в режиме аналогового вывода, генерирует 490 импульсов в секунду (кроме контактов D5 и D6, которые выдают 980 импульсов в секунду). Ширина импульсов, т. е. их длительность, с помощью параметров функции analogWrite() может быть изменена. При этом, чем больше время, в течение которого импульс остается высоким, тем больше мощность, подводимая к выходу, и, следовательно, тем ярче горит светодиод или быстрее вращается двигатель.
Снимок экрана 2023-08-11 152518.jpg
Вольтметр фиксирует этот процесс как изменение напряжения лишь потому, что не может реагировать достаточно быстро, поэтому он как бы усредняет получаемое напряжение, и нам кажется, что оно меняется постепенно.
Oleg
Сообщения: 186
Зарегистрирован: 12 июл 2023, 12:09
Re: Руководство по программированию Arduino

Сообщение Oleg »

Оператор If...Else

Если вы помните, в разд. «Цифровые входы» мы использовали оператор if для выполнения указанного действия, если определенное условие соблюдается. Чтобы еще лучше контролировать выполнение кода, можно использовать оператор if...else, который выполняет один код, если условие соблюдается, и другой если не соблюдается.

В следующем примере светодиод будет включен или выключен, в зависимости от того, каково аналоговое значение: больше 500 либо меньше или равно 500:

Код: Выделить всё

if (analogRead(A0) > 500)
{
digitalWrite(led, HIGH);
}
else
{
digitalWrite(led, LOW);
}
До сих пор нам встречались два оператора сравнения: == (равно) и > (больше) На самом деле, можно выполнять и другие сравнения:
<=(меньше или равно)
>=(больше или равно)
!=(не равно)

Можно также сравнивать не только пары значений, но и делать более сложные сравнения, пользуясь операторами && (И) и ||(ИЛИ). Например, если вы хотите включать светодиод только при значении между 300 и 400, то могли бы написать следующее:

Код: Выделить всё

int reading - analogRead (A0);

if ((reading >=300) && (reading <=400)
{
digitalWrite(led, HIGH)
}
else
{
digitalWrite(led, LOW);
}
Oleg
Сообщения: 186
Зарегистрирован: 12 июл 2023, 12:09
Re: Руководство по программированию Arduino

Сообщение Oleg »

Циклы

Цикл позволяет повторить код заданное количество раз либо повторять его до тех пор, пока определенное условие не изменится. Для этого можно пользоваться циклами for или while: циклы for удобнее для выполнения того или иного действия фиксированное число раз, а while для выполнения операции до тех пор, пока не будет соблюдено конкретное условие.

В следующем примере цикл for заставит светодиод 10 раз (обратите внимание: цикл for находится в функции sеtup(), а не в loop(), поскольку, будучи в loop(),он заставил бы индикатор мигнуть еще 10 раз после первых 10, а мы добиваемся не такого эффекта):

Код: Выделить всё

for (int i = 0; i < 10; i++)
{
digitalWrite(led, HIGH); 
delay(1000);
digitalWrite(led, LOW);
delay(1000);
}
Если бы мы хотели, чтобы все светодиоды мигали до тех пор, пока нажата кнопка, подключенная к цифровому входу, то воспользовались бы циклом while:

Код: Выделить всё

while (digitalRead(9))
{
digitalWrite(led, HIGH);
delay(1000);
digitalWrite(led, LOW);
delay(1000);
}
Этот код предполагает, что контакт D9 подсоединен к переключателю
Oleg
Сообщения: 186
Зарегистрирован: 12 июл 2023, 12:09
Re: Руководство по программированию Arduino

Сообщение Oleg »

Функции

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

Если бы вы попробовали ознакомиться с внутренним устройством Arduino, то заметили бы, что встроенные функции, например, digitalWrite(), мягко говоря, сложноваты. Далее приведен код функции digitalWrite() (не пытайтесь раз браться, что он делает, просто радуйтесь, что не приходится набирать весь этот код всякий раз, когда вы хотите перевести контакт из высокого состояния в низкое):

Код: Выделить всё

void digitalWrite(uint8_t pin, uint8_t val)
{
uint8_t timer = digitalPinToTimer (pin);

uint8_t bit - digitalPinToBitMask (pin); 
uint8_t port - digitalPinToport (pin);
volatile uint8_t out*out;

if (port == NOT_A_PIN) return;

// Если контакт поддерживает ШИМ-вывод, то его нужно отключить,
 // прежде чем выполнять цифровую запись

if (timer == NOT_ON_TIMER) turnOffPWM(timer);

out=portoutputRegister (port);

uint8_t oldSREG = SREG;

cli();

if (val == LOW) {
 *out &= ~bit;
}
 else {
 *out |= bit;
}
SREGoldSREG;
}
Присвоив имя такому большому фрагменту кода, нам достаточно будет обратиться к нему по имени, и использовать.

Наряду со встроенными функциями (например, той же digitalWrite), можно создавать собственные функции для объединения операций, которыми вы пользуетесь. Так, можно написать функцию, которая заставит светодиод мигнуть столько раз, сколько указано в параметре. Контакт, на который нужно передать команду «мигай», также можно указать как параметр. Это показано в следующем скетче: мы пишем функцию blink и вызываем ее на этапе startup(), так что светодиод на плате Arduino мигнет 5 раз после сброса:

Код: Выделить всё

const int ledpin = 13;
void setup()
{
pinMode(ledPin, OUTPUT);
blink (ledPin, 5);
}
void loop() {}
void blink(int pin, int n)
{
for (int i = 0; i < n; i++)
{
digitalWrite(ledPin, HIGH);
delay(500);
digitalWrite(ledPin, LOW);
delay(500);
}
}
Функция setup() здесь задает ledPin в качестве вывода, а затем вызывает функцию blink, сообщая ей имя контакта, который должен мигать, и сколько раз он должен мигнуть. Функция loop() пуста и ничего не делает, но Arduino IDE настаbвает, что она должна здесь быть

Сама функция blink начинается со слова void, указывающего, что функция ничего не возвращает. Иными словами, результат вызова этой функции нельзя присвоить переменной, что, возможно, мы захотели бы сделать, если бы функция выполняла какие-либо вычисления. Затем мы видим имя функции (blink) и принимаемые ею параметры, заключенные в круглые скобки и разделенные запятыми. Определяя функцию, нужно задать тип каждого параметра (т. е. int, float или др.). В нашем случае как сам контакт, который будет мигать (ріn), так и количество проблесков (n) являются целыми числами (int).

В языке С, как и в большинстве других языков программирования, различаются локальные и глобальные переменные. Глобальные переменные (как ledPin в предыдущем примере) могут использоваться в любой точке программы. С другой стороны, локальные переменные, например, параметры функций (в нашем случае pin и n), и даже переменная і в цикле for - все они доступны лишь в рамках той функции, в которой определены.

Итак, в функции setup() строка blink (ledPin, 5) передает глобальную переменную ledPin в функцию 511пк, где она присваивается локальной переменной ріn. Логично спросить: а зачем это делается? Дело в том, что, передавая рin в blink,Мы делаем функцию blink универсальной, и можем с ее помощью зажигать любой указанный контакт, а не только ledPin.

В теле функции blink есть цикл for, который будет повторять вложенные в него функции digitalWrite() и delay() 5 раз. Впрочем, если сделать равным 3, то светодиод мигнет 3 раза.

Ответить