Предопределенные константы Arduino

Платы и модули для функционала

Существует много плат Arduino, описание которых говорит о различиях в объеме памяти, портах, питании, тактовой частоте и др. Одни предназначены для решения простых задач, другие — для решения более сложных.

К популярным платам относятся следующие виды:

  1. Arduino Uno. Наиболее распространенная плата. Есть большой выбор уроков. Плата допускает замену контроллера. Оснащена 14 цифровыми вводами-выводами (6 ШИМ), 6 аналоговыми входами, флеш-памятью 32 Кб (ATmega328), из которых 0,5 Кб использует загрузчик.
  2. Arduino Mega 2560. Создана на базе микроконтроллера ATmega2560. Флеш-память — 256 Кб, из которых 8 Кб использует загрузчик. Имеет 54 цифровых вводов-выводов (14 ШИМ), 16 аналоговых входов, 8 Кб оперативной памяти. Среди всех плат «Ардуино» у этой самый большой размер.
  3. Arduino Due. Оснащена 54 цифровыми вводами-выводами (12 ШИМ), 12 аналоговыми входами (2 выходами). Создана на базе микроконтроллера AT91SAM3X8E с рабочим напряжением 3,3 В и флеш-памятью 512 Кб.
  4. Arduino Pro Mini 3.3V. Самая миниатюрная плата в семействе Arduino. Напряжение — 3,3 В. Требует использования внешнего программатора. Память данных составляет 2 Кб. Создана на базе микроконтроллера ATmega328P. Количество цифровых выводов — 14 линий (6 из которых — ШИМ), аналоговых — 6.
  5. Arduino Pro Mini 5V. Аналог предыдущей модели с напряжением 5 В.
  6. Arduino Nano V3.0. Создана на базе ATmega328. Сдержит 32 Кб памяти, из которых 2 Кб использует загрузчик. Имеет 14 цифровых вводов-выводов (6 ШИМ), 6 аналоговых входов, встроенный порт USB. Напряжение — 5 В.
  7. Arduino Micro. Разновидность платы c возможностью имитировать различные USB-устройства при подключении к ПК. Оснащена 20 цифровыми вводами-выводами (7 ШИМ), 12 аналоговыми входами.

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

  1. Датчики. Системы, считывающие, отправляющие и обрабатывающие информацию. Расширяют аппаратные функции проекта.
  2. Модули. Дополнения, которые позволяют расширить вычислительные мощности проекта. К ним относят карты памяти, вспомогательные процессы.

Датчики можно разделить на категории:

  1. Устройства получения информации. Датчики и сканеры, позволяющие получить сведения об окружающей среде: давлении, температуре, влажности, расстоянии до объектов. Есть возможность вводить параметры, зависящие от этих показаний. С помощью датчика расстояния можно создавать роботы-пылесосы, которые передвигаются по комнате, избегая препятствий.
  2. Устройства обработки информации. Реализуются отдельно или совместно с предыдущими датчиками. Используются для совершения промежуточных операций.
  3. Устройства вывода информации. Это ЖК-экраны, светодиодные индикаторы, сенсорные экраны, динамики и т. д.

Среди наиболее популярных модулей «Ардуино» можно выделить:

  1. Ультразвуковой дальномер HC-SR04. Датчик, позволяющий с помощью ультразвука измерить расстояние от 2 см до 4 м.
  2. Инфракрасный дальномер Sharp. Измеряет расстояние от 20 см до 1,5 м посредством инфракрасного излучения.
  3. Модуль температуры и влажности DHT11. Измеряет температуру в диапазоне от 0 до +50°C и влажность от 20 до 90%. Используется для теплиц или в качестве комнатного термометра. Часто приобретается для умного дома.
  4. Датчик влажности почвы FC-28. Измеряет влажность почвы или другой среды. Нужен для автоматизированного полива растений.
  5. Bluetooth HC-06. Помогает организовать беспроводную связь с другими устройствами.

Старый путь: использование виртуальной файловой системы sysfs для доступа к GPIO

/sys/class/gpio

  1. Определить номер линии (или номер ножки процессора) GPIO;
  2. Экспортировать номер GPIO, записав его номер в /sys/class/gpio/export;
  3. Конфигурировать линию GPIO как вывод, указав это в /sys/class/gpio/gpioX/direction;
  4. Установить значение «1» или «0» для линии GPIO /sys/class/gpio/gpioX/value;
  1. Экспорт линии GPIO не связан с процессом, поэтому если процесс использующий линию GPIO аварийно завершит свою работу, то эта линия GPIO так и останется  экспортированной;
  2. Учитываю первый пункт возможен совместный доступ к одной и той же линии GPIO, что приведет к проблеме совместного доступа. Процесс не может «узнать» у ОС используется ли та или иная линия GPIO в настоящий момент;
  3. Для каждой линии GPIO приходится выполнять множество операций open()/read()/write()/close(), а так же указывать параметры (export, direction, value, и т.д.) используя методы работы с файлами. Это усложняет программный код;
  4. Невозможно включить/выключить сразу несколько линий GPIO одним вызовом;
  5. Процесс опроса для перехвата событий (прерываний от линий GPIO) ненадежен;
  6. Нет единого интерфейса (API) для конфигурирования линий GPIO;
  7. Номера, присвоенные линиям GPIO непостоянны, их приходится каждый раз экспортировать;
  8. Низкая скорость работы с линиями GPIO;

Исходный код программы (скетча)

Arduino

#include <Arduino_FreeRTOS.h>
void TaskBlink1( void *pvParameters );
void TaskBlink2( void *pvParameters );
void Taskprint( void *pvParameters );
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);
xTaskCreate(
TaskBlink1
, «task1»
, 128
, NULL
, 1
, NULL );
xTaskCreate(
TaskBlink2
, «task2»
, 128
, NULL
, 1
, NULL );
xTaskCreate(
Taskprint
, «task3»
, 128
, NULL
, 1
, NULL );
vTaskStartScheduler();
}
void loop()
{
}
void TaskBlink1(void *pvParameters) {
pinMode(8, OUTPUT);
while(1)
{
Serial.println(«Task1»);
digitalWrite(8, HIGH);
vTaskDelay( 200 / portTICK_PERIOD_MS );
digitalWrite(8, LOW);
vTaskDelay( 200 / portTICK_PERIOD_MS );
}
}
void TaskBlink2(void *pvParameters)
{
pinMode(7, OUTPUT);
while(1)
{
Serial.println(«Task2»);
digitalWrite(7, HIGH);
vTaskDelay( 300 / portTICK_PERIOD_MS );
digitalWrite(7, LOW);
vTaskDelay( 300 / portTICK_PERIOD_MS );
}
}
void Taskprint(void *pvParameters) {
int counter = 0;
while(1)
{
counter++;
Serial.println(counter);
vTaskDelay(500 / portTICK_PERIOD_MS); }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <Arduino_FreeRTOS.h>

voidTaskBlink1(void*pvParameters);

voidTaskBlink2(void*pvParameters);

voidTaskprint(void*pvParameters);

voidsetup(){

// initialize serial communication at 9600 bits per second:

Serial.begin(9600);

xTaskCreate(

TaskBlink1

,»task1″

,128

,NULL

,1

,NULL);

xTaskCreate(

TaskBlink2

,»task2″

,128

,NULL

,1

,NULL);

xTaskCreate(

Taskprint

,»task3″

,128

,NULL

,1

,NULL);

vTaskStartScheduler();

}

voidloop()

{
}

voidTaskBlink1(void*pvParameters){

pinMode(8,OUTPUT);

while(1)

{

Serial.println(«Task1»);

digitalWrite(8,HIGH);

vTaskDelay(200portTICK_PERIOD_MS);

digitalWrite(8,LOW);

vTaskDelay(200portTICK_PERIOD_MS);

}

}

voidTaskBlink2(void*pvParameters)

{

pinMode(7,OUTPUT);

while(1)

{

Serial.println(«Task2»);

digitalWrite(7,HIGH);

vTaskDelay(300portTICK_PERIOD_MS);

digitalWrite(7,LOW);

vTaskDelay(300portTICK_PERIOD_MS);

}

}

voidTaskprint(void*pvParameters){

intcounter=;

while(1)

{

counter++;

Serial.println(counter);

vTaskDelay(500portTICK_PERIOD_MS);}

}

Программирование STM32 с помощью PlatformIO

Для поклонников микроконтроллеров, построенных на основе платформы STM32, мы в данной статье рассмотрим их программирование с помощью PlatformIO. Процесс программирования STM32 будет практически таким же, как и рассмотренная процедура программирования Arduino UNO с помощью PlatformIO. Различие будет состоять в том, что необходимо будет выбрать соответствующую плату STM32 когда будете создавать новый проект для STM32. Достоинством PlatformIO в данном случае является то, что при работе в ней нет необходимости отдельно скачивать какие либо внешние пакеты для работы с любой платой, PlatformIO скачивает все необходимые пакеты автоматически, поэтому нам необходимо просто выбрать плату и затем можно переходить к редактору. Мы будем использовать внешний JLink/ JTAG/ STLink/ Serial Programmer чтобы загружать скетч в STM32. Также следует отметить, что STM32 можно программировать и с помощью Arduino IDE.

Чтобы произвести программирование STM32 с помощью PlatformIO выполните следующую последовательность шагов.

Дайте имя новому проекту – мы его назвали ‘Blink STM32’. Затем выберите необходимую плату STM32 – в нашем случае это ‘BluePill F103C8(Generic)’. Выберите фреймворк для Arduino. После заполнения всех полей нажмите на Finish и подождите некоторое время пока проект создастся и загрузит все необходимые пакеты для работы с STM32.

Повторное создание проекта для работы с STM32 произойдет гораздо быстрее потому что все необходимые пакеты будут уже загружены при создании первого проекта. Теперь перейдите в пункт меню Untitled(Workspace) -> src -> main.cpp как мы это уже рассматривали ранее в статье при работе с платой Arduino.

Следующим шагом идет выбор программатора – он весьма важный. Для программирования плат STM32 могут быть использованы такие программаторы как JTAG, STLink, JLink, Serial и т.п. Все они рабочие, но вам необходимо соответствующим образом сконфигурировать страницу конфигурации ‘platformio.ini’

В этом проекте мы используем программатор Serial Programmer CP210x USB to UART Bridge. Подсоедините этот последовательный программатор к плате STM32 (как показано в следующей таблице) и к вашему компьютеру.

USB to Serial Programmer STM32 Board
5V 5V
Gnd Gnd
Rx A9
Tx A10

После этого перейдите в менеджер проектов (project explorer), откройте в нем страницу ‘platformio.ini’ и внесите в нее изменения как показано на следующем рисунке. Протокол загрузки кода (upload_protocol) определяет какой программатор следует использовать ((STLink, JLink, Serial и т.д.). В качестве параметра upload_port следует указать используемый вами COM порт. Вы можете изменить/выбрать используемый COM порт в пункте меню ‘Devices’ на главной странице PlatformIO.

После этого перейдите в ‘main.cpp’ и напишите/измените программу мигания светодиодом. Далее загрузите код программы в плату. В случае успешной загрузки кода будут показаны параметры его загрузки. После загрузки кода вы сможете наблюдать мигание светодиода, подключенного к контакту PC13 платы STM32.

Часто используемые термины в RTOS

Задача (Task) – кусок кода, который выполняется в процессоре по расписанию (в определенное время).

Планировщик (Scheduler) – отвечает за выбор и перевод задач из состояния готовности (ready state) в состояние выполнения (running state).

Вытеснение (Preemption) – действие, направленное на временное прерывание выполняющейся задачи с целью удаления ее из состояния выполнения.

Переключение контекста (Context Switching) — процесс переключения процессора на другую задачу (процесс, поток) (например, при обработке прерывания), обычно сопровождающийся операциями сохранения в стеке состояния текущего потока (регистров и другой информации), восстановлением ранее сохранённого состояния другого потока и передачей ему управления. Во время работы в моменты прерывания планировщик сравнивает приоритет задач, которые находятся в состоянии готовности, с приоритетом выполняемой задачи. Если приоритет какой-нибудь задачи, находящейся в состоянии готовности, выше, чем приоритет выполняемой задачи, происходит событие переключения контекста. Состояние задачи, выполнение которой прерывается, обычно сохраняется в стеке.

Типы политики (плана действий) планировщика:

  • приоритетное планирование (Preemptive Scheduling) – в этом случае все задачи выполняются примерно одинаковую часть времени независимо от их приоритетов;
  • планирование на основе приоритетов (Priority-based Preemptive) – задачи с высоким приоритетом выполняются в первую очередь;
  • совместное планирование (Co-operative Scheduling) – операция переключения контекста случается только при взаимодействии (сотрудничестве) выполняемых задач.

Объекты ядра (Kernel Objects) – для информирования задач о том, что они должны выполнить определенную работу, используется процесс синхронизации. Для осуществления этого процесса и используются объекты ядра, такие, к примеру, как события (Events), семафоры (Semaphores), очереди (Queues), флаги/мьютексы (Mutex), почтовые ящики (Mailboxes) и т.д.

Аналоговые, цифровые и шим пины Ардуино

Все пины можно разделить на несколько видов, различие будет только в количестве данных выводов на различных платах. Например, на Arduino Mega 2560 цифровых и аналоговых портов, значительно больше, чем на Uno или Nano из-за большего размера платы и производительности микроконтроллера. В остальном характеристики и способы программирования пинов не отличаются друг от друга.

  1. Power Pins — порты питания, режим их работы нельзя запрограммировать или изменить. Они выдают стабилизированное напряжение 5V или 3,3V, Vin выдает напряжение от источника питания, а GND — это заземление (общий минус);
  2. PWM Pins — порты с ШИМ модуляцией, которые можно запрограммировать, как цифровой выход/вход. Данные порты обозначены на плате знаком тильда (˜);
  3. Analog In — порты, принимающие аналоговый сигнал от датчиков, работают на вход. Данные порты тоже можно запрограммировать, как цифровой вход/выход. Данные пины не поддерживают ШИМ модуляцию.

Режим пинов назначается в процедуре void setup с помощью pinMode(), например:

void setup() {
    pinMode(10, OUTPUT); // объявляем пин 10 как выход
    pinMode(A2, OUTPUT); // объявляем пин A2 как выход

    pinMode(12, INPUT); // объявляем пин 12 как вход
    pinMode(A1, INPUT); // объявляем пин A1 как вход
}

Пояснения к коду:

  1. к выходу 10 и A2 можно подключить светодиод, который будет включаться и выключаться при вызове команды в программе;
  2. пин 10 может использоваться для ШИМ сигнала, например, чтобы плавно включить светодиод, а пин A2 может выдавать только цифровой сигнал (0 или 1);
  3. к входу 12 и A1 можно подключить цифровой датчик и микроконтроллер будет проверять наличие сигнала на этих пинах (логический нуль или единицу);
  4. к входу A1 можно подключить аналоговый датчик тогда микроконтроллер будет получать не только сигнал но и узнавать характеристику сигнала.

Мы не случайно разделили пины с ШИМ модуляцией (PWM Pins) и аналоговые. PWM пины создают аналоговый сигнал, к ним подключают сервопривод, шаговый двигатель и другие устройства, где требуется подавать сигнал с разными характеристиками. Аналоговые пины (Analog In) используются для подключения аналоговых датчиков, с них входящий сигнал преобразуется в цифровой с помощью встроенного АЦП.

Ардуино Uno пины: шим, аналоговые, цифровые

Arduino UNO распиновка платы на русском

ШИМ (PWM) порты
(Analog Out)
3, 5, 6, 9, 10, 11
Аналоговые порты
(Analog In)
A0, A1, A2, A3, A4, A5
на некоторых платах: A6, A7
Цифровые порты
(Digital In/Out)
все порты со 2 по 13 пин
можно использовать: A0 — A7

Из таблицы видно, какие пины на Arduino UNO поддерживают шим. Аналоговые пины (Analog In) используют, как цифровые если недостаточно портов общего назначения, например, вы хотите подключить к плате 15 светодиодов. Кроме того, на плате Arduino Uno и Nano порты A4 и A5 используются для I2C протокола (SDA и SCL пины) — они работают параллельно с пинами A4 и A5. Об этом мы расскажем чуть позже.

Ардуино Nano пины: шим, аналоговые, цифровые

Arduino Nano распиновка платы на русском

ШИМ (PWM) порты
(Analog Out)
3, 5, 6, 9, 10, 11
Аналоговые порты
(Analog In)
A0, A1, A2, A3, A4, A5
на некоторых платах: A6, A7
Цифровые порты
(Digital In/Out)
все порты со 2 по 13 пин
можно использовать: A0 — A7

Если вы заметили, то пины на Arduino Nano и Uno имеют одинаковое количество и назначение. Платы отличаются лишь своими габаритами. Nano — более компактная и удобная плата, для экономии места на нее не ставят разъем питания, для этого используются пины Vin и GND на которое подается питание от источника.

Ардуино Mega пины: шим, аналоговые, цифровые

Схема распиновки платы Arduino Mega 2560 r3

ШИМ (PWM) порты
(Analog Out)
все порты со 2 по 13 пин
дополнительно: 44, 45, 46 пин
Аналоговые порты
(Analog In)
с A0 до A15
Цифровые порты
(Digital In/Out)
все порты со 2 по 13, со 22 по 52 пин
можно использовать: A0 — A15

Пины коммуникации нежелательно использовать, как обычные цифровые порты. Особенно при таком количестве портов общего назначения, как на Mega 2560. Более подробную информацию о рассмотренных платах, а также о других микроконтроллерах семейства Arduino (Pro Mini, Leonardo, Due и пр.): характеристики, описание пинов, габариты и т.д. можно узнать в разделе Микроконтроллеры на нашем сайте.

Объяснение программы для Arduino

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

1. Сначала в программе нам необходимо подключить библиотеку для работы с ЖК дисплеем и инициализировать используемые контакты.

Arduino

#include<LiquidCrystal.h>
LiquidCrystal lcd (7,8,9,10,11,12); // Define LCD display pins RS, E, D4, D5, D6, D7

1
2
#include<LiquidCrystal.h>

LiquidCrystallcd(7,8,9,10,11,12);// Define LCD display pins RS, E, D4, D5, D6, D7

2. Внутри функции void setup () мы на экране ЖК дисплея покажем пару приветственных сообщений.

Arduino

lcd.begin(16,2);
lcd.setCursor(0,0);
lcd.print(«CIRCUIT DIGEST»);
lcd.setCursor(0,1);
lcd.print(«ArduinoInterrupt»);
delay(3000);
lcd.clear();

1
2
3
4
5
6
7
lcd.begin(16,2);

lcd.setCursor(,);

lcd.print(«CIRCUIT DIGEST»);

lcd.setCursor(,1);

lcd.print(«ArduinoInterrupt»);

delay(3000);

lcd.clear();

3. Также в функции void setup () необходимо задать режим работы для контакта D13 (к нему подключен светодиод) – на вывод данных.

Arduino

pinMode(13,OUTPUT);

1 pinMode(13,OUTPUT);

4. Далее следует такая важная часть для нашей программы, как использование функции attachInterrupt() – это тоже происходит в функции void setup().

Arduino

attachInterrupt(digitalPinToInterrupt(2),buttonPressed1,RISING);
attachInterrupt(digitalPinToInterrupt(3),buttonPressed2,RISING);

1
2
attachInterrupt(digitalPinToInterrupt(2),buttonPressed1,RISING);

attachInterrupt(digitalPinToInterrupt(3),buttonPressed2,RISING);

В первой из этих команд контакт 2 назначается для обработки внешнего прерывания, а функция buttonPressed1 будет вызываться всегда, когда будет происходить повышение уровня (с LOW до HIGH) на контакте D2. Контакт 3 назначается для обработки внешнего прерывания, а функция buttonPressed2 будет вызываться всегда, когда будет происходить повышение уровня (с LOW до HIGH) на контакте D3.

5. Внутри функции void loop() число i инкрементируется начиная с 0 и выводится на экран ЖК дисплея.

Arduino

lcd.clear();
lcd.print(«COUNTER:»);
lcd.print(i);
++i;
delay(1000);

1
2
3
4
5
lcd.clear();

lcd.print(«COUNTER:»);

lcd.print(i);

++i;

delay(1000);

Также в функции void loop() функция digitalWrite() включает или выключает светодиод (в зависимости от состояния переменной output), подключенный к контакту D13.

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

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

Arduino

void buttonPressed1()
{
output = LOW;
lcd.setCursor(0,1);
lcd.print(«Interrupt 1»);
}

1
2
3
4
5
6
voidbuttonPressed1()

{

output=LOW;

lcd.setCursor(,1);

lcd.print(«Interrupt 1»);

}

Эта функция будет выполняться когда будет происходить повышение уровня на контакте D2. Эта функция будет изменять состояние переменной output на LOW, что будет приводить к выключению светодиода, а также будет выводить сообщение “interrupt1” на экран ЖК дисплея.

Arduino

void buttonPressed2()
{
output = HIGH;
lcd.setCursor(0,1);
lcd.print(«Interrupt2»);
}

1
2
3
4
5
6
voidbuttonPressed2()

{

output=HIGH;

lcd.setCursor(,1);

lcd.print(«Interrupt2»);

}

Эта функция будет выполняться при нажатии кнопки подключенной к контакту D3. Эта функция будет изменять состояние переменной output на HIGH, что будет приводить к включению светодиода, а также будет выводить сообщение “interrupt2” на экран ЖК дисплея.

Синтаксис функции

pinMode(<номер пина>, <тип>)

Функция не возвращает значения.

Описание параметров

В параметре <номер пина> указываем порт, который хотим сконфигурировать. Как правило, функция используется для установки цифровых пинов, т.к. аналоговые пины ардуино чаще всего используются как входные, а входной режим используется Arduino по умолчанию.

В параметре <тип> указываем тип режима работы пина (более подробно о возможных режимах мы поговорим чуть позже).

Константы INPUT, OUTPUT и INPUT_PULLUP

В Ardino предусмотрены константы, обозначающие варианты типов:

  • INPUT (значение по умолчанию);
  • OUPUT;
  • INPUT_PULLUP.

Примеры использования

  • pinMode(13, OUTPUT); // Установили 13 пин как выход
  • pinMode(2, INPUT);      // Так делать не надо. По умолчанию, все пины работают в режиме входа.

Using Arduino INPUT_PULLUP

Let’s use the exact same circuit, but this time with INPUT_PULLUP instead of INPUT for the pinMode function.

#define BUTTON_PIN 4

void setup() {
  Serial.begin(9600);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}

void loop() {
  Serial.println(digitalRead(BUTTON_PIN));
  delay(10);
}

If you run this code and open the Serial Plotter, you’ll see that the default value is 1 (HIGH). When you press the button the state directly goes to 0 (LOW) and comes back to HIGH when you release the button.

Well now it’s much better. Problem solved!

When you set the mode to INPUT_PULLUP, an internal resistor – inside the Arduino board – will be set between the digital pin 4 and VCC (5V). This resistor – value estimated between 20k and 50k Ohm – will make sure the state stays HIGH. When you press the button, the states becomes LOW.

volatile

volatilevolatile

byte A = ;
byte B;

void loop() {
  A++;
  B = A + 1;
}
  1. Загрузить из памяти значение A в регистр Р1
  2. Загрузить в регистр Р2 константу 1
  3. Сложить значение Р2 с Р1 (результат в Р2)
  4. Сохранить значение регистра Р2 в памяти по адресу A
  5. Сложить содержимое регистра Р1 с константой 2
  6. Сохранить значение регистра Р1 в памяти по адресу B

loopvolatilevolatilevolatilevolatile

#define interruptPin 2
volatile byte f = ;

void setup() {
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), buttonPressed, FALLING);
}

void loop() {
  while (f == ) {
    
  }
  
}

void buttonPressed() {
  f = 1;
}

loopvolatilevolatileinterruptsnoInterruptsатомарно исполняемый блок кода

Библиотека libgpiod и инструменты управления GPIO

libgpiodLibgpiodLibGPIOdlibgpiod

  • gpiodetect — выведет список всех чипов GPIO, их метки и количество линий;
  • gpioinfo — выведет информацию о линиях GPIO конкретного контроллера GPIO. В таблице вывода по колонкам будет указано: номер линии, название контакта, направление ввода/вывода, текущее состояние;
  • gpioget — считает текущее состояние линии GPIO;
  • gpioset — установит значение для линии GPIO;
  • gpiofind — выполняет поиск контроллера GPIO и линии по имени;
  • gpiomon — осуществляет мониторинг состояния линии GPIO и выводит значение при изменение состояния.

libgpiod

C++, Python, C#,

3Параллельные процессы без оператора «delay()»

Вариант, при котором Arduino будет выполнять задачи псевдо-параллельно, предложен разработчиками Ардуино. Суть метода в том, что при каждом повторении цикла loop() мы проверяем, настало ли время мигать светодиодом (выполнять фоновую задачу) или нет. И если настало, то инвертируем состояние светодиода. Это своеобразный вариант обхода оператора delay().

const int soundPin = 3;  // переменная с номером пина пьезоэлемента
const int ledPin = 13;  // переменная с номером пина светодиода
const long ledInterval = 200; // интервал мигания светодиодом, мсек.

int ledState = LOW;  // начальное состояние светодиода
unsigned long previousMillis = 0;  // храним время предыдущего срабатывания светодиода

void setup() {
    pinMode(soundPin, OUTPUT); // задаём пин 3 как выход.
    pinMode(ledPin, OUTPUT);   // задаём пин 13 как выход.
}

void loop() {
    // Управление звуком:
    tone(soundPin, 700); 
    delay(200);
    tone(soundPin, 500); 
    delay(200);
    tone(soundPin, 300);
    delay(200);
    tone(soundPin, 200);
    delay(200);
     
    // Мигание светодиодом:
    // время с момента включения Arduino, мсек:
    unsigned long currentMillis = millis(); 
    // Если время мигать пришло,
    if (currentMillis - previousMillis >= ledInterval) {
        previousMillis = currentMillis;  // то запоминаем текущее время
        if (ledState == LOW) {  // и инвертируем состояние светодиода
              ledState = HIGH;
        } else {
              ledState = LOW;
        }
        digitalWrite(ledPin, ledState); // переключаем состояние светодиода
    }
}

Существенным недостатком данного метода является то, что участок кода перед блоком управления светодиодом должен выполняться быстрее, чем интервал времени мигания светодиода «ledInterval». В противном случае мигание будет происходить реже, чем нужно, и эффекта параллельного выполнения задач мы не получим. В частности, в нашем скетче длительность изменения звука сирены составляет 200+200+200+200 = 800 мсек, а интервал мигания светодиодом мы задали 200 мсек. Но светодиод будет мигать с периодом 800 мсек, что в 4 раза больше того, что мы задали.

Вообще, если в коде используется оператор delay(), в таком случае трудно сымитировать псевдо-параллельность, поэтому желательно его избегать.

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

Using digitalRead

void setup()   {                
  Serial.begin(38400);
  pinMode(7, INPUT);
}

void loop()                     
{
  if (digitalRead(7) == HIGH) {
    Serial.println("Button is not pressed...");
  } else {
    Serial.println("Button pressed!!!");
  }
  delay(250);
}

In this example, the «if» statement is used, together with «else».
The condition after «if» is tested, and either the code in the first
or the second set of curly braces is run.

Using digitalRead(7) will result in either HIGH or LOW. This code
compares digitalRead(7) to HIGH using the «==» comparison. A subtle
but very important detail is the double equal. Using only a single
equal attempts to write or assign a value, which would be silly since
you can’t change what digitalRead(7) found. A double equal means to
compare 2 numbers, rather than set the first equal to the second.

When you run the program and use the Serial Monitor, you will see
messages that change when you press the pushbutton.

Общие рекомендации по написанию обработчиков прерываний

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

  • Во-первых, делайте обработчики предельно короткими. Ведь они прерывают выполнение основной программы, а также блокируют обработку других прерываний. По возможности обработчик должен фиксировать только факт возникновения события, изменяя значение переменной. А сама реакция на событие должна выполняться в основной программе при анализе этой переменной.
  • Как уже было сказано, при входе в обработчик устанавливается глобальный запрет на обработку других прерываний. А это в свою очередь влияет на работу функций, использующих прерывания. Будьте с ними осторожнее. Если не уверены в безопасности их вызова, то лучше откажитесь от их использования в обработчике.
  • Возьмите за правило объявлять разделяемые между основной программой и обработчиком переменные как volatile. И не забывайте, что этого квалификатора недостаточно в случае многобайтных переменных — используйте при работе с ними атомарно исполняемые блоки или interrupts/noInterrupts

следующей части