RFID-модуль RC522: описание, подключение, схема, характеристики | ВИКИ

1описание метеодатчика bl999 и его информационного протокола

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

  • диапазон измеряемых температур: −40… 50°C;
  • диапазон измеряемой влажности: 1…99%;
  • период измерений: 30 сек;
  • рабочая радиочастота: 433,325 МГц;
  • число каналов: 3;
  • рабочее расстояние: до 30 м на открытых пространствах.

К одной метеостанции можно подключить до трёх таких датчиков. Номер (канал) датчика устанавливается переключателем, который расположен под съёмной крышкой батарейного отсека (трёхпозиционная кнопка SW1 на фото ниже). Фактически, канал здесь – это просто признак в структуре пакета данных датчика, никакого физического смысла (например, изменение рабочей частоты) он в себе не несёт.

Чтобы лучше понять протокол датчика, с помощью которого он отправляет данные метеостанции, можно попытаться воспользоваться радиоприёмником и разбираться с тем, что приходит из радиоэфира. Но на популярной частоте 433 МГц работает множество бытовых устройств, и приёмник будет ловить большое количество посторонних шумов. Этот факт не позволит нам спокойно изучить протокол датчика.

Внешний вид и внутренности метеодатчика BL999
Внешний вид и внутренности метеодатчика BL999

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

Места подключения щупа осциллографа к метеодатчику BL999
Места подключения щупа осциллографа к метеодатчику BL999

Чтобы изучить генерируемый датчиком сигнал, нужен хороший осциллограф. Данные отправляются пакетами длительностью примерно 500…600 мс. Вот как выглядит типичный пакет с датчика BL999 на экране осциллографа.

Изучение сигнала метеодатчика BL999
Изучение сигнала метеодатчика BL999

Снимок экрана для этого пакета. Здесь красным цветом показан аналоговый сигнал, а голубым – оцифрованный сигнал, без присущих аналоговым сигналам искажений.

Осциллограмма типичного пакета метеодатчика BL999
Осциллограмма типичного пакета метеодатчика BL999

Вот представлены 4 оцифрованных информационных пакета, сгенерированных датчиком. Эти пакеты пришли друг за другом с разницей в 30 секунд. Именно с такой периодичностью датчик BL999 отсылает свои данные.

Информационные пакеты метеодатчика BL999
Информационные пакеты метеодатчика BL999

Посмотрим на этот сигнал. С первого взгляда бросается в глаза, что:

  • данные передаются пакетами;
  • каждый пакет начинается с короткого импульса, за которым следует относительно длительный промежуток времени с нулевым уровнем;
  • в каждом пакете присутствует 4 группы импульсов, разделённых такими же длительными паузами;
  • в каждой группе содержатся импульсы, следующие друг за другом через короткие или вдвое более длинные паузы;
  • всего имеются 3 вида промежутков между импульсами: самые короткие (условно назовём их типа A), вдвое более длинные (B) и вчетверо более длинные (C);
  • в каждой группе ровно по 37 импульсов;
  • все 4 группы каждого пакета одинаковые (содержат повторяющиеся последовательности импульсов).

Очевидно, что в данном случае применяется некое временное кодирование (скорее всего, фазо-импульсное или частотно-импульсное), когда значимая информация скрыта в длительности пауз между импульсами. В случае датчика BL999 короткая пауза между соседними импульсами (A) означает логический нуль, а длинная (B) – логическую единицу. Изучим сигнал более детально.

Как видно, в сигнале присутствует ряд коротких импульсов. Длительность всех импульсов одинакова и равна примерно 486 мкс. Длительность коротких промежутков (логический “0”) равна примерно 2,4 мс, длительность средних промежутков (логическая “1”) равна примерно 4,5 мс. Продолжительность самых длинных промежутков – около 9,4 мс.

Как уже было упомянуто, в пакете присутствуют 4 группы по 37 импульсов. Этими импульсами закодированы 36 битов, которые можно условно разбить на участки по 9 полубайтов. Следующий рисунок показывает, что закодировано в этих 36-ти битах:

Назначение битов информационного пакета метеодатчика BL999 в одной группе
Назначение битов информационного пакета метеодатчика BL999 в одной группе

Полубайт также называют «ниббл» (англ. nibble) или тетрада. Это единица измерения информации, содержащая четыре бита.

Давайте разберём реальный пример, и на его основе расшифруем закодированные в нём данные. Возьмём одну группу из 36-ти битов из вот такого пакета, пришедшего от датчика BL999:

Пример информационного пакета метеодатчика BL999
Пример информационного пакета метеодатчика BL999

В пакете, согласно схеме, присутствуют следующие части:

ОбозначениеНомера битовОписаниеЗначение из примера
ID35…32, 29…28Это идентификатор датчика. Он задаётся произвольным образом и изменяется при каждом включении.0101_11 = 23
Chan31…30Номер канала датчика. Кодируется обычным двоичным кодом: “01” – 1, “10” – 2, “11” – 3.01 = 1ый канал
Bat27Уровень заряда батареи: “0” – норма, “1” – низкий заряд.0 = норма
?26…24Нет данных.100
Temperature23…12Данные температуры. Число записано в обратном порядке и умножено на 10. Отрицательные температуры, кроме этого, хранятся в дополнительном коде (*).0111_1111_0000 обращение 0000_1111_1110 = 254 деление на 10 25,4°C
Humidity11…4Влажность. Записывается как результат вычитания из 100, в дополнительном коде (*).0000_1101 обращение 1011_0000 инверсия битов 0100_1111=79 1 =80 вычитание из 100% 100 − 80 = 20%
Checksum3…0Контрольная сумма. Вычисляется как сумма 8-ми полубайтов, записанных в обратном порядке. От получившегося числа берутся 4 младших разряда и также записываются в обратном порядке.0101 0111 0100 0111 1111 0000 0000 1101 0100 1010 1110 0010 1110 1111 0 0 1011 = 100_0010 обращаем 0010 0100

(*) Дополнительный код числа – это специальный вид представления чисел, который часто используется в вычислительной технике. Онлайн-калькулятор и хорошая статья на эту тему здесь.

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

2скетч ntp сервера для arduino

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

Общий алгоритм следующий. Сначала будем опрашивать приёмник спутникового сигнала, пока не получим от него NMEA пакет с корректным значением времени. Нужный нам пакет с временем начинается с заголовка “$GPRMC”.

Формат пакета NMEA с данными о времени
Формат пакета NMEA с данными о времени

Когда получим значение времени, запишем его в модуль RTC. Подробно работу с часами реального времени мы рассматривали здесь.

Далее запустим сервер и в цикле будем постоянно слушать входящие запросы по протоколу UDP на порту 123 (это стандартный порт протокола NTP). Как только сервер получит NTP запрос, прочитаем время из модуля часов реального времени, «упакуем» в ответный NTP пакет и отправим клиенту, который запросил время.

В конце статьи приложена программа для тестирования связи с NTP сервером.

Скетч сервера времени NTP и Arduino (разворачивается)
#define debug true // для вывода отладочных сообщений
#include <SoftwareSerial.h>
#include <Wire.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
SoftwareSerial Serial1(10, 11);
EthernetUDP Udp;
// MAC, IP-адрес и порт NTP сервера:
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // задайте свой MAC
IPAddress ip(192, 168, 0, 147); // задайте свой IP
#define NTP_PORT 123 // стандартный порт, не менять
#define RTC_ADDR 0x68 // i2c адрес RTC
static const int NTP_PACKET_SIZE = 48;
byte packetBuffer[NTP_PACKET_SIZE];
int year;
byte month, day, hour, minute, second, hundredths;
unsigned long date, time, age;
uint32_t timestamp, tempval;
void setup() {
Wire.begin(); // стартуем I2C
#if debug
Serial.begin(115200);
#endif
Serial1.begin(4800); // старт UART для GPS модуля
getGpsTime(); // получаем время GPS
writeRtc(); // записываем время в RTC
// запускаем Ethernet шилд в режиме UDP:
Ethernet.begin(mac, ip);
Udp.begin(NTP_PORT);
#if debug
Serial.println("NTP started");
#endif  
}
void loop() {
processNTP(); // обрабатываем приходящие NTP запросы
}
String serStr; // строка для хранения пакетов от GPS приёмника
// Читает пакеты GPS приёмника из COM-порта и пытается найти в них время
// Если время найдено, возвращает True, иначе - False
void getGpsTime() {
bool timeFound = false;
while (!timeFound) {
while (Serial1.available()>0) {
char c = Serial1.read();
if (c != 'n') {
serStr.concat(c);
} else {
timeFound = decodeTime(serStr);
serStr = "";
}
}
}
}
// Декодирует вермя по NMEA пакету 
// и возвращает True в случае успеха и False в обратном случае
bool decodeTime(String s) {
#if debug
Serial.println("NMEA Packet = "   s);
#endif
if (s.substring(0,6)=="$GPRMC") {
String validFlag = s.substring(18,20);
// Ждём валидные данные (флаг "V" - данные не валидны, "A" - данные валидны):
if (validFlag == "A") {
String timeStr = s.substring(7,17); // строка времени в формате ччммсс.сс
hour = timeStr.substring(0,2).toInt();
minute = timeStr.substring(2,4).toInt();
second = timeStr.substring(4,6).toInt();
hundredths = timeStr.substring(7,10).toInt();
// ищем индекс 4-ой запятой с конца, после которой идёт дата
int commaIndex = 1;
for (int i=0;i<5;i  ) {
commaIndex = s.lastIndexOf(",", commaIndex-1);
}
String date = s.substring(commaIndex 1, commaIndex 7); // строка даты в формате ддммгг
day = date.substring(0,2).toInt();
month = date.substring(2,4).toInt();
year = date.substring(4,6).toInt(); // передаются только десятки и единицы года
#if debug
printDate();
#endif
return true;
}
}
return false;
}
// Запоминает время в RTC
void writeRtc() {
byte arr[] = {0x00, dec2hex(second), dec2hex(minute), dec2hex(hour), 0x01, dec2hex(day), dec2hex(month), dec2hex(year)};
Wire.beginTransmission(RTC_ADDR);
Wire.write(arr, 8);
Wire.endTransmission();
#if debug
Serial.print("Set date: ");
printDate();
#endif
}
// Преобразует число из dec представления в hex представление
byte dec2hex(byte b) {
String bs = (String)b;
byte res;
if (bs.length()==2) {
res = String(bs.charAt(0)).toInt() * 16   String(bs.charAt(1)).toInt();
} else {
res = String(bs.charAt(0)).toInt();
}
#if debug
Serial.println("dec "   (String)b   " = hex "   (String)res);
#endif  
return res;
}
// Читает из RTC время и дату
void getRtcDate() {
Wire.beginTransmission(RTC_ADDR);
Wire.write(byte(0));
Wire.endTransmission();
Wire.beginTransmission(RTC_ADDR);
Wire.requestFrom(RTC_ADDR, 7);
byte t[7];
int i = 0;
while(Wire.available()) {
t[i] = Wire.read();
i  ;
}
Wire.endTransmission();
second = t[0];
minute = t[1];
hour = t[2];
day = t[4];
month = t[5];
year = t[6];
#if debug
Serial.print("Get date: ");
printDate();
#endif
}
// Обрабатывает запросы к NTP серверу
void processNTP() {
int packetSize = Udp.parsePacket();
if (packetSize) {
Udp.read(packetBuffer, NTP_PACKET_SIZE);
IPAddress remote = Udp.remoteIP();
int portNum = Udp.remotePort();
#if debug
Serial.println();
Serial.print("Received UDP packet size ");
Serial.println(packetSize);
Serial.print("From ");
for (int i=0; i<4; i  ) {
Serial.print(remote[i], DEC);
if (i<3) { Serial.print("."); }
}
Serial.print(", port ");
Serial.print(portNum);
byte LIVNMODE = packetBuffer[0];
Serial.print("  LI, Vers, Mode :");
Serial.print(packetBuffer[0], HEX);
byte STRATUM = packetBuffer[1];
Serial.print("  Stratum :");
Serial.print(packetBuffer[1], HEX);
byte POLLING = packetBuffer[2];
Serial.print("  Polling :");
Serial.print(packetBuffer[2], HEX);
byte PRECISION = packetBuffer[3];
Serial.print("  Precision :");
Serial.println(packetBuffer[3], HEX);
for (int z=0; z<NTP_PACKET_SIZE; z  ) {
Serial.print(packetBuffer[z], HEX);
if (((z 1) % 4) == 0) {
Serial.println();
}
}
Serial.println();
#endif
// Упаковываем данные в ответный пакет:
packetBuffer[0] = 0b00100100;   // версия, режим
packetBuffer[1] = 1;   // стратум
packetBuffer[2] = 6;   // интервал опроса
packetBuffer[3] = 0xFA; // точность
packetBuffer[7] = 0; // задержка
packetBuffer[8] = 0;
packetBuffer[9] = 8;
packetBuffer[10] = 0;
packetBuffer[11] = 0; // дисперсия
packetBuffer[12] = 0;
packetBuffer[13] = 0xC;
packetBuffer[14] = 0;
getRtcDate();
timestamp = numberOfSecondsSince1900Epoch(year,month,day,hour,minute,second);
#if debug
Serial.println("Timestamp = "   (String)timestamp);
#endif
tempval = timestamp;
packetBuffer[12] = 71; //"G";
packetBuffer[13] = 80; //"P";
packetBuffer[14] = 83; //"S";
packetBuffer[15] = 0; //"0";
// Относительное время 
packetBuffer[16] = (tempval >> 24) & 0xFF;
tempval = timestamp;
packetBuffer[17] = (tempval >> 16) & 0xFF;
tempval = timestamp;
packetBuffer[18] = (tempval >> 8) & 0xFF;
tempval = timestamp;
packetBuffer[19] = (tempval) & 0xFF;
packetBuffer[20] = 0;
packetBuffer[21] = 0;
packetBuffer[22] = 0;
packetBuffer[23] = 0;
// Копируем метку времени клиента 
packetBuffer[24] = packetBuffer[40];
packetBuffer[25] = packetBuffer[41];
packetBuffer[26] = packetBuffer[42];
packetBuffer[27] = packetBuffer[43];
packetBuffer[28] = packetBuffer[44];
packetBuffer[29] = packetBuffer[45];
packetBuffer[30] = packetBuffer[46];
packetBuffer[31] = packetBuffer[47];
// Метка времени 
packetBuffer[32] = (tempval >> 24) & 0xFF;
tempval = timestamp;
packetBuffer[33] = (tempval >> 16) & 0xFF;
tempval = timestamp;
packetBuffer[34] = (tempval >> 8) & 0xFF;
tempval = timestamp;
packetBuffer[35] = (tempval) & 0xFF;
packetBuffer[36] = 0;
packetBuffer[37] = 0;
packetBuffer[38] = 0;
packetBuffer[39] = 0;
// Записываем метку времени 
packetBuffer[40] = (tempval >> 24) & 0xFF;
tempval = timestamp;
packetBuffer[41] = (tempval >> 16) & 0xFF;
tempval = timestamp;
packetBuffer[42] = (tempval >> 8) & 0xFF;
tempval = timestamp;
packetBuffer[43] = (tempval) & 0xFF;
packetBuffer[44] = 0;
packetBuffer[45] = 0;
packetBuffer[46] = 0;
packetBuffer[47] = 0;
// Отправляем NTP ответ 
Udp.beginPacket(remote, portNum);
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
}
// Выводит отформатированноую дату
void printDate() {
char sz[32];
sprintf(sz, "d.d.d d:d:d.d", day, month, year 2000, hour, minute, second, hundredths);
Serial.println(sz);
}
const uint8_t daysInMonth [] PROGMEM = { 31,28,31,30,31,30,31,31,30,31,30,31 }; // число дней в месяцах
const unsigned long seventyYears = 2208988800UL; // перевод времени unix в эпоху
// Формирует метку времени от момента 01.01.1900
static unsigned long int numberOfSecondsSince1900Epoch(uint16_t y, uint8_t m, uint8_t d, uint8_t h, uint8_t mm, uint8_t s) {
if (y >= 1970) { y -= 1970; }
uint16_t days = d;
for (uint8_t i=1; i<m;   i) {
days  = pgm_read_byte(daysInMonth   i - 1);
}
if (m>2 && y%4 == 0) {   days; }
days  = 365 * y   (y   3) / 4 - 1;
return days*24L*3600L   h*3600L   mm*60L   s   seventyYears;
}

Функция getGpsTime() постоянно читает приходящие от ГНСС приёмника пакеты, и когда получает очередной пакет, проверяет, нет ли в нём валидных данных времени. Если время есть, то происходит его разбор. Также время можно сохранить в модуле RTC и таким образом проводить периодическую синхронизацию.

Проверка NMEA пакетов осуществляется в функции decodeTime().

Несколько слов о функции dec2hex(). В ней несколько извращённо число переводится из десятичного представления в 16-ное. Точнее, так. Модуль часов показывает время в виде, например, 16:52:08. Но здесь каждое из этих чисел не десятичное, а 16-ное.

То есть, в действительности это время в RTC хранится так: 0x16:0x52:0x08. А с GPS-приёмника мы получаем время в десятичном формате. И чтобы записать те же 16 часов в модуль RTC, нужно преобразовать десятичное 16 в шестнадцатеричное 0x16, что является десятичным 22.

3подключение к arduino модуля с часами реального времени ds1302

Модуль DS1302 может выглядеть, например, так:

Модуль часов реального времени DS1302
Модуль часов реального времени DS1302

На нижней стороне модуля никаких компонентов нет. Как видно, вся «обвязка» микросхемы DS1302 – это кварцевый резонатор.

Нижняя сторона модуля часов реального времени DS1302
Нижняя сторона модуля часов реального времени DS1302

Назначение выводов микросхемы DS1302 такое (слева в DIP-корпусе, справа – в планарном):

Выводы микросхемы DS1302
Выводы микросхемы DS1302
Назначение выводов RTC DS1302
Название вывода DS1302Назначение
X1, X2Входы для подачи частоты 32,768 кГц с кварцевого резонатора.
SCLKВход тактовой частоты последовательных данных.
I/OВход/выход последовательных данных.
CEВход выбора чипа. Активируется высоким уровнем.
VCC1Дополнительное резервное питание (например, от батареи) для сохранения настроек времени в ПЗУ, 3 В.
VCC2Первичное питание микросхемы, 5 В.
GNDЗемля

Соответствие выводов микросхемы DS1302 выводам модуля, думаю, очевидно: VCC – это первичное питание 5 В, GND – земля. CLK – вход тактовых импульсов. DAT – ввод/вывод последовательных данных. RST – это CE, который включает логику и показывает микросхеме RTC, что происходит обмен данными (чтение или запись).

Типичная схема подключения RTC микросхемы DS1302:

Типичная схема подключения микросхемы DS1302
Типичная схема подключения микросхемы DS1302

Самый простой способ управлять DS1302 – это, конечно же, воспользоваться одной из множества готовых библиотек для Arduino, например, этой (она приложена также архивом внизу статьи). Она позволяет выставлять время и считывать его, а также записывать и читать данные из ПЗУ часов.

Подключение DS1302 к Arduino
Подключение DS1302 к Arduino

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

Для обмена с микросхемой DS1302 используется последовательный интерфейс, похожий на SPI. Диаграмма передачи данных показана ниже. Видно, что во время чтения или записи данных сначала следует выставить логическую “1” на линии CE. Затем сгенерировать 16 тактовых синхронизирующих импульсов. В это время передаются 16 бит информации.

Диаграмма чтения и записи данных DS1302
Диаграмма чтения и записи данных DS1302

В первых 8-ми битах передаётся команда (командный байт), а следующие 8 бит – данные. Структура командного байта показана ниже. В нём старший бит всегда “1”, младший – признак операции (чтение RD=1 или запись WR=0), а остальные биты – это адрес регистра, с которым взаимодействуем.

Структура командного байта DS1302
Структура командного байта DS1302

Кроме того, DS1302 поддерживает множественную передачу (burst mode). Для этого следует удерживать высокий уровень на линии CE и генерировать необходимое число тактовых импульсов. Данные будут читаться (или записываться) из регистров или ПЗУ последовательно, начиная с заданного адреса и далее.

Карта регистров часов реального времени DS1302
Карта регистров часов реального времени DS1302

Предлагаю для изучения DS1302 воспользоваться отладочной платой с микросхемой FT2232H и программы SPI via FTDI. Это позволит избежать постоянного программирования Arduino и проводить все эксперименты с часами «на лету».

Единственная сложность в том, что микросхема FT2232H использует 3.3-вольтовую логику, а часы DS1302 – 5-вольтовую. Но ничего страшного, воспользуемся преобразователем логического уровня, благо стоит он копейки, и в применении исключительно прост.

У него есть две стороны: одна отвечает за низковольтовую часть (LV), другая – за высоковольтную (HV). У него есть 4 низковольтных входа-выхода (LV1…LV4) и соответствующие им 4 высоковольтных входа-выхода (HV1…HV4).

Схема подключения FT2232H к DS1302 через логик шифтер
Схема подключения FT2232H к DS1302 через логик шифтер

К высоковольтной стороне преобразователя подключается модуль DS1302, к низковольтной – микросхема FT2232H. Соответствие выводов такое: CLK – ADBUS0, DAT – ADBUS1 и ADBUS2, RST – ADBUS3. Подключаем соответственно через преобразователь напряжения. Вот так это выглядит вживую:

Управление часами DS1302 с помощью FT2232H
Управление часами DS1302 с помощью FT2232H

Когда собрали схему, запустим программу SPI via FTDI и в меню «Устройство» выберем интерфейс SPI, потом нажмём «Подключить». Теперь в левой части главного окна, в рамке «Настройки SPI» снимем галочки с CS active LOW (активация часов DS1302 высоким уровнем, вывод CE) и MSB first (передача байта старшим битом вперёд). Остальные параметры оставим как есть.

Теперь попробуем прочитать 1 байт из регистра секунд 0x81. Он должен меняться каждую секунду, и мы сразу увидим, что наша схема работает. Для чтения регистра секунд настройки программы будут такие (обратите внимание на раздел «Чтение»):

Чтение 1 байта из регистра секунд 0x81 часов DS1302
Чтение 1 байта из регистра секунд 0x81 часов DS1302

Чтобы увидеть принятые данные, нужно нажать на кнопку с изображением таблицы слева от кнопки «Прочитать».

Чтобы прочитать данные всех регистров, нужно отправить команду BF и запросить столько регистров, сколько нужно. Все данные о дате хранятся в 7-ми регистрах, а восьмой – данные о запрете записи (WP, write protect).

Чтение всех регистров часов DS1302 в режиме множественной передачи
Чтение всех регистров часов DS1302 в режиме множественной передачи (burst mode)

Кстати, если вместо числа “1” ввести число раз “0” (справа от кнопки чтения), то программа будет постоянно опрашивать часы DS1302, и вы увидите в таблице принятых данных как идёт время часов DS1302.

Для записи данных в ПЗУ часов DS1302 в режиме множественной передачи (не по одному байту) следует отправить команду FE и дальше нужные данные. Для чтения данных из ПЗУ в режиме множественной передачи нужно отправить команду FF:

Чтение ПЗУ часов DS1302 в режиме множественной передачи
Чтение ПЗУ часов DS1302 в режиме множественной передачи (burst mode)

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

3пример простейшей передачи данных по радиоканалу с помощью arduino

Особенностью радиопередачи является то, что длительные сигналы одного уровня передавать невозможно, передача будет срываться. Для более-менее устойчивой передачи необходимо передавать переменный сигнал. Причём необходимо каким-то образом выделять полезный сигнал из шума, который всегда присутствует в радиоэфире.

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

Первый скетч – для передатчика. Он довольно простой.

#define prd 4 // пин DATA передатчика FS1000A 
#define ledPin 13 // вывод встроенного светодиода Arduino
void setup() {
pinMode(ledPin, OUTPUT); 
pinMode(prd, OUTPUT); 
}void loop() {
sendCommand(); // отправляем команду
delay(5000); // делаем задержку на 5 сек
}
// посылает команду в эфир 
void sendCommand() {
digitalWrite(ledPin, HIGH); // на время отправки команды зажигаем встроенный светодиод
// команда представляет собой три импульса наподобие «тире-точка-тире»
digitalWrite(prd, HIGH);
delay(100); 
digitalWrite(prd, LOW);
delay(50); 
digitalWrite(prd, HIGH);
delay(50); 
digitalWrite(prd, LOW);
delay(50); 
digitalWrite(prd, HIGH);
delay(100); 
digitalWrite(prd, LOW);
delay(50); 
digitalWrite(ledPin, LOW);  // по окончании передачи команды гасим светодиод
}

Временная диаграмма команды приведена на рисунке:

Передаваемая в радиоэфир команда
Передаваемая в радиоэфир команда

Скетч для приёмника, ввиду описанных выше причин, сложнее. Поэтому для начала давайте просто периодически читать данные на входе приёмника и выводить то, что принимаем, в последовательный порт.

#define prm 2 // пин DATA приёмника XY-MK-5V
#define ledPin 13 // встроенный светодиод
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
}void loop() {
int data = digitalRead(prm); // читаем данные с входа приёмника
Serial.println(data);
delay(10);
}

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

Данные шум на входе приёмника
Данные шум на входе приёмника

Как видно, на входе приёмника постоянно присутствует шумовой сигнал. Моменты, когда излучает передатчик, легко отслеживаются на глаз (на рисунке они выделены синими пунктирными рамками). После окончания передачи ненадолго устанавливается нулевой уровень, но затем система автоматической регулировки усиления снова усиливает шумы, и на входе приёмника появляется хаотичная смена логических уровней.

Однако, выделение полезного сигнала из шума с помощью аппаратуры не так просто, как на глаз.

Существует т.н. теорема Котельникова, которая говорит о том, что при дискретизации аналогового сигнала потерь информации не будет только в том случае, если частота полезного сигнала равна половине или меньше частоты дискретизации (т.н. «частоты Найквиста»).

Для простоты возьмём период опроса данных (период дискретизации) со входа приёмника 50 мс – это число равно длительности самой короткой части команды, которую мы каждые 5 секунд отправляем передатчиком в радиоэфир. Снимая по одному отсчёту за 50 мс мы, конечно же, нарушаем теорему Котельникова (период дискретизации надо брать хотя бы 25 мс или меньше). Но для максимального упрощения примера оставим так и посмотрим, сможем ли выделить из шума в радиоэфире нашу команду.

Длительность логических уровней передаваемой команды
Длительность логических уровней передаваемой команды

Опрашивая данные с приёмника каждые 50 мс (зелёные вертикальные штрихи на рисунке – моменты опроса приёмника), мы должны увидеть последовательность “110101100”. При этом вероятность ложных срабатываний нашего детектора будет достаточно высока, т.к. есть немалый шанс, что в моменты считывания данных с приёмника случайный шумовой сигнал будут иметь точно такие же значения.

Если взять больше нулей, то вероятность ложных срабатываний уменьшится. Но надо понимать, что поиск совпадения большего по размерности массива будет занимать больше времени, и 50 мс станут уже не 50-тью, а несколько больше. Из-за этого постепенно будут сдвигаться моменты, в которые мы опрашиваем приёмник («убегание» частоты).

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

const int prm = 2; // пин входа приёмника XY-MK-5V
const int ledPin = 13; // пин встроенного светодиода Arduino
const int len = 14; // длина массивов
bool state = false; // текущее состояние светодиода
int pattern[len] = {1,1,0,1,0,1,1,0,0,0,0,0,0,0}; // эталонный массив - маска команды, которую нужно «словить»
int testReg[len] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0}; // тестовый регистр - массив оцифрованных значений с входа приёмника
void setup() {
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
}void loop() {
int data = digitalRead(prm); // читаем значение на входе приёмника
ShiftReg(data, testReg); // вдвигаем полученное число в тестовый регистр
if (IsCommandDetected) { // проверяем, нет ли в тестовом регистре искомой последовательности
state = !state; // если есть, меняем состояние светодиода
digitalWrite(ledPin, state);
}
delay(50); 
}
// вдвигает в тестовый регистр новое значение
void ShiftReg(int newVal, int *arr) {
for (int i=0; i<len; i  ) {
arr[i] = testReg[i 1]; // смещаем значения в регистре на 1 позицию
}
arr[len-1] = newVal; // последнюю позицию заменяем только что принятым измерением
}
// проверяет, обнаружена ли команда на входе приёмника
bool IsCommandDetected() {
for (int i=0; i<len; i  ) {
if (testReg[i] != pattern[i]) { // почленно сравниваем 2 массива
return false;
}
}
return true;
}

Функция ShiftReg() получает на вход два аргумента: текущее содержимое тестового регистра и последнее полученное со входа приёмника значение. Она сдвигает все значения в регистре на 1 позицию, а в младший разряд регистра помещает текущее принятое значение.

Таким образом, в регистре постоянно хранятся 16 (в данном конкретном случае) последних считанных с приёмника значений. Если мы посмотрим как меняется содержимое регистра, которое формируется функцией ShiftReg(), то увидим примерно следующее:

0000000000000000
0000000000000001
0000000000000010
0000000000000100
0000000000001000
0000000000010000
0000000000100001
0000000001000010
0000000010000101
0000000100001010
0000001000010101
0000010000101010
0000100001010101
0001000010101010
0010000101010100
0100001010101001
1000010101010010
0000101010100101
0001010101001010
0010101010010101
0101010100101010
1010101001010101
0101010010101011
1010100101010110
0101001010101101
1010010101011010
0100101010110101
1001010101101010
0010101011010101
0101010110101010
1010101101010101
0101011010101011
1010110101010110
0101101010101101
1011010101011010
0110101010110101
1101010101101010
1010101011010101
0101010110101010
1010101101010100
0101011010101000
1010110101010000
0101101010100001
1011010101000010
0110101010000101
1101010100001010
1010101000010101
0101010000101010
1010100001010101
0101000010101010
1010000101010100
0100001010101001
1000010101010010
...и т.д.

Функция IsCommandDetected() почленно сравнивает два массива – эталонный массив (искомая последовательность) и содержимое тестового регистра (массив полученных с приёмника значений), и если массивы одинаковы, значит мы «поймали» команду. В таком случае меняем состояние встроенного светодиода.

4подключение к arduino модуля с инфракрасным приёмником

ИК датчик может состоять из одного только инфракрасного приёмника, как в этом случае:

ИК приёмник
ИК приёмник

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

К Arduino модуль с ИК приёмником подключается тоже очень просто:

Пин модуляПин ArduinoНазначение
DATЛюбой цифровойПризнак наличия ИК излучения на входе приёмника
VCC 5VПитание
GNDGNDЗемля
Подключение ИК приёмника к Arduino
Подключение ИК приёмника к Arduino

Напишем скетч, в котором будем просто показывать с помощью встроенного светодиода, что на входе приёмника присутствует ИК излучение. В данном модуле аналогично с ранее рассмотренным на выходе DAT уровень “0”, когда ИК излучение попадает на приёмник, и “1” когда ИК излучения нет.

const int ir = 2;
void setup() {
pinMode(LED_BUILTIN, OUTPUT); // это 13-ый вывод Arduino со встроенным светодиодом
pinMode(ir, INPUT);
}
void loop() {
int r = digitalRead(ir);
digitalWrite(LED_BUILTIN, r!=HIGH); // зажигаем светодиод, если модуль среагировал на ИК излучение
// в противном случае - гасим
}

Если загрузить этот скетч в Arduino, направить на ИК приёмник ИК пульт и нажимать на нём разные кнопки, то мы увидим, что светодиод нашего индикатора быстро мигает. Разные кнопки – по-разному мигает.

Чтение команд ИК пульта с Arduino
Чтение команд ИК пульта с Arduino

Очевидно, что каждая команда закодирована своей бинарной последовательностью. Хотелось бы увидеть, какие именно команды приходят от пульта. Но прежде чем ответить на этот вопрос, нужно посмотреть другим способом, что же отправляет пульт. А именно – с помощью осциллографа.

Осциллограф отображает часть команды ИК пульта
Осциллограф отображает часть команды ИК пульта

На осциллограмме видна серия «пачек» импульсов примерно одинаковой длительности. Каждая «пачка» состоит из 24-х импульсов.

Осциллограф отображает часть команды ИК пульта
Осциллограф отображает часть команды ИК пульта

В таком виде довольно трудно увидеть, какой сигнал передаётся от пульта ДУ. Прелесть нашего приёмника в том, что он выполняет рутинную работу по оцифровке аналогового инфракрасного сигнала и выдаёт уже «красивый» цифровой сигнал. Давайте посмотрим его на осциллографе.

Подключение выхода с ИК приёмника и выхода ИК пульта к осциллографу
Подключение выхода с ИК приёмника и выхода ИК пульта к осциллографу

Вот так выглядит посылка пульта целиком. Здесь жёлтая линия – аналоговый сигнал пульта ДУ, голубая – цифровой сигнал с выхода ИК приёмника. Видно, что продолжительность передачи составляет примерно 120 мс. Очевидно, время будет несколько варьироваться исходя из того, какие биты присутствуют в пакете.

Осциллограмма пакета с ИК пульта ДУ
Осциллограмма пакета с ИК пульта ДУ

При большем приближении видно, что высокочастотное заполнение, которое имеется в аналоговом сигнале, в цифровом сигнале с ИК приёмника отсутствует. Приёмник прекрасно справляется со своей задачей и показывает чистый цифровой сигнал. Видна последовательность коротких и длинных прямоугольных импульсов. Длительность коротких импульсов примерно 1,2 мс, длинных – в 2 раза больше.

Биты пакета ИК пульта, масштаб: 1 клетка – 200 мкс
Биты пакета ИК пульта, масштаб: 1 клетка – 200 мкс

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

Если зарисовать этот пакет, то получится как-то так:

Один из пакетов ИК пульта
Один из пакетов ИК пульта

Дальнейшие исследования показали, что все пакеты данного пульта ДУ состоят из двух пачек импульсов. Причём первая всегда содержит 35 бит, вторая – 32.

Есть несколько вариантов, как поступить для получения цифровых данных пакета:

  1. опрашивать пакет через равные промежутки времени (т.н. «стробирование»), а затем принимать решение, это логический “0” или “1”;
  2. ловить фронты импульсов (детектор фронта), затем определять их длительность и также принимать решение, какой это бит.

Напомню, что будем считать короткие импульсы логическим нулём, длинные – логической единицей.

Для реализации первого варианта понятно, с какой частотой необходимо опрашивать ИК датчик, чтобы принимать с него корректные данные: 600 мкс. Это время в два раза меньшее, чем длительность коротких импульсов сигнала (логических нулей). Или, если рассматривать с точки зрения частоты, опрашивать приёмник нужно в 2 раза большей частотой (вспомним Найквиста и Котельникова). Напишем скетч, реализующий вариант со стробированием.

Скетч для чтения пакета от ИК пульта методом стробирования
const int ir = 2; // с выхода ИК приёмника
int t = 600; // период стробирования, мкс
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(ir, INPUT);
}
void loop() {
int r = digitalRead(ir); // читаем значение ИК сенсора
digitalWrite(LED_BUILTIN, r!=HIGH); // зажигаем светодиод, если сенсор сработал
// Если зафиксировали ИК излучение, обрабатываем команду с пульта:
if (r==LOW) {
precess_ir(); 
}
}
// читает пакет ИК пульта
void precess_ir(){
delay(13); // пропустим стандартное начало пакета
byte bits[100]; // 100 бит должно хватить
// читаем пакет
for (int i=0; i<100; i  ){
int bit = readBit();
bits[i] = bit;    
}
// выводим пакет в монитор;
for (int i=0; i<100; i  ){
Serial.print(bits[i]);
}
Serial.println();
}
// читает 1 бит пакета
int readBit() {
// дожидаемся уровня HIGH и ставим первый строб
int r1;
do { 
r1 = digitalRead(ir);
} while (r1 != HIGH);
delayMicroseconds(t); // ждём
// затем ставим второй строб
int r2 = digitalRead(ir);
delayMicroseconds(t);  // ждём
if (r2 == LOW) {
return 0;
}
else {
// третий строб 
delayMicroseconds(t);  // ждём
return 1;
}
}

Поэкспериментируем с данным скетчем и ИК приёмником. Загрузим скетч в память Ардуино. Запустим последовательный монитор. Нажмём на пульте несколько раз одну и ту же кнопку и посмотрим, что мы увидим в мониторе.

Выводим принятые пакеты ИК пульта в последовательный монитор
Выводим принятые пакеты ИК пульта в последовательный монитор

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

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

Перепишем скетч, используя метеод детекции фронтов.

Скетч для чтения пакета от ИК пульта методом детекции фронтов
const int ir = 2;
int t_low = 600 10; // длительность "0" (с запасом), мкс
int t_max = t_low * 4; // таймаут, мкс
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(ir, INPUT);
}
void loop() {
int r = digitalRead(ir);
digitalWrite(LED_BUILTIN, r!=HIGH);
// если зафиксировали ИК излучение, обрабатываем команду пульта
if (r==LOW) {
precess_ir();
}
}
// читает пакет ИК пульта
void precess_ir() {
delay(13); // пропустим стандартное начало пакета
byte bits[100];
for (int i=0; i<100; i  ){
int bit = readBit();
bits[i] = bit;
}
for (int i=0; i<100; i  ){
Serial.print(bits[i]);
}
Serial.println();
}
// читает 1 бит пакета
int readBit() {
int r1;
do {
r1 = digitalRead(ir);
} while (r1 != HIGH); // ждём передний фронт импульса
int t1 = micros(); // запоминаем время начала импульса
int t2;
int t; 
do {
r1 = digitalRead(ir);
t2 = micros(); // запоминаем время опроса (оно же длительность импульса)
t = t2 - t1; // длительность импульса
} while ((r1 != LOW) && (t < t_max)); // ждём задний фронт импульса, но не больше таймаута
//Serial.println(t); // можно вывести длительность импульса
if (t < t_low) {
return 0;
}
else {
return 1;
}
}

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

Загрузим скетч, запустим монитор, нажмём несколько раз ту же кнопку пульта.

Выводим принятые пакеты ИК пульта в последовательный монитор
Выводим принятые пакеты ИК пульта в последовательный монитор

Результат, как видно, более стабильный.

4управление шаговым двигателем с помощью arduino

Шаговый двигатель позволяет вращать ротор на определённый угол. Это бывает полезно, когда необходимо задать положение какому-либо механизму или его узлу. Шагом двигателя называется минимальный угол, на который можно повернуть ротор двигателя. Угол поворота и направление движения задаются в управляющей программе.

Шаговый двигатель с контроллером
Шаговый двигатель с контроллером

Характеристики двигателя 28BYJ-48:

ХарактеристикаЗначение
Количество фаз4
Напряжение питанияот 5 до 12 В
Число шагов64
Размер шага5,625°
Скорость вращения15 об./сек
Крутящий момент450 г/см

Модуль с микросхемой драйвера для управления шаговым двигателем выглядит так:

Модуль с драйвером ULN2003
Модуль с драйвером ULN2003

На входы IN1…IN4 подаются управляющие сигналы от Arduino. Используем любые 4 цифровых пина, например, D8…D11. На вход питания необходимо подать постоянное напряжение от 5 до 12 В. Двигателю желательно обеспечить отдельное питание. Но в данном случае, т.к. не планируется использовать двигатель на постоянной основе, можно подать питание и от Arduino.

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

Схема подключения шагового двигателя с драйвером ULN2003 к Arduino
Схема подключения шагового двигателя с драйвером ULN2003 к Arduino

Соберём всё по схеме.

Подключение шагового двигателя 28BYJ-48 к Arduino
Подключение шагового двигателя 28BYJ-48 к Arduino

Для Arduino «из коробки» существует готовая библиотека для управления шаговыми двигателями. Она называется Stepper. Можно посмотреть готовые примеры в среде разработки для Arduino: File Examples Stepper.

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

Двигатель 28BYJ-48 имеет 4 фазы. Это означает, что у него имеются 4 электромагнитные катушки, которые под действием электрического тока притягивают сердечник. Если напряжение подавать на катушки поочерёдно, это заставит сердечник вращаться. Рисунок иллюстрирует данный принцип.

Схема работы шагового двигателя
Схема работы шагового двигателя

Здесь на (1) напряжение подано на катушки A и D, на (2) – на A и B, (3) – B и С, (4) – C и D. Далее цикл повторяется. И таким образом ротор двигателя вращается по кругу.

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

Простейший скетч управления шаговым двигателем (разворачивается)
// порты для подключения драйвера ULN2003 к Arduino
#define in1 8
#define in2 9
#define in3 10
#define in4 11
int del = 5; // время задержки между импульсами
void setup() {
pinMode(in1, OUTPUT);
pinMode(in2, OUTPUT);
pinMode(in3, OUTPUT);
pinMode(in4, OUTPUT);
}
void loop() {
//фаза 1:
digitalWrite(in1, HIGH); 
digitalWrite(in2, LOW); 
digitalWrite(in3, LOW); 
digitalWrite(in4, HIGH);
delay(del);
//фаза 2:
digitalWrite(in1, HIGH); 
digitalWrite(in2, HIGH); 
digitalWrite(in3, LOW); 
digitalWrite(in4, LOW);
delay(del);
//фаза 3:
digitalWrite(in1, LOW); 
digitalWrite(in2, HIGH); 
digitalWrite(in3, HIGH); 
digitalWrite(in4, LOW);
delay(del);
//фаза 4:
digitalWrite(in1, LOW); 
digitalWrite(in2, LOW); 
digitalWrite(in3, HIGH); 
digitalWrite(in4, HIGH);
delay(del);
}

Как можно догадаться, задержка del определяет скорость вращения двигателя. Уменьшая или увеличивая её можно ускорять или замедлять двигатель.

Если загрузить этот скетч, то увидим, что шаговый двигатель вращается против часовой стрелки. Соответственно, можно вынести цикл вращения в одну сторону в отдельную функцию rotateCounterClockwise(). И сделать аналогичную функцию вращения в противоположную сторону rotateClockwise(), в которой фазы будут следовать в обратном порядке.

Скетч управления шаговым двигателем (разворачивается)
// порты для подключения модуля ULN2003 к Arduino
#define in1 8
#define in2 9
#define in3 10
#define in4 11
int del = 5; // время задержки между импульсами
void setup() {
pinMode(in1, OUTPUT);
pinMode(in2, OUTPUT);
pinMode(in3, OUTPUT);
pinMode(in4, OUTPUT);
}
void loop() {
rotateClockwise();
}
// Вращение шагового двигателя по часовой стрелке
void rotateClockwise(){
phase4();
phase3();
phase2();
phase1();
}
// Вращение шагового двигателя против часовой стрелки
void rotateCounterClockwise(){
phase1();
phase2();
phase3();
phase4();
}
// Фазы 1...4 шагового двигателя:
void phase1(){
digitalWrite(in1, HIGH); 
digitalWrite(in2, LOW); 
digitalWrite(in3, LOW); 
digitalWrite(in4, HIGH);
delay(del);
}
void phase2(){
digitalWrite(in1, HIGH); 
digitalWrite(in2, HIGH); 
digitalWrite(in3, LOW); 
digitalWrite(in4, LOW);
delay(del);
}
void phase3(){
digitalWrite(in1, LOW); 
digitalWrite(in2, HIGH); 
digitalWrite(in3, HIGH); 
digitalWrite(in4, LOW);
delay(del);
}
void phase4(){
digitalWrite(in1, LOW); 
digitalWrite(in2, LOW); 
digitalWrite(in3, HIGH); 
digitalWrite(in4, HIGH);
delay(del);
}

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

Финальный скетч управления шаговым двигателем (разворачивается)
// порты для подключения модуля ULN2003 к Arduino
#define in1 8
#define in2 9
#define in3 10
#define in4 11
int del = 5; // время задержки между импульсами => скорость вращения
void setup() {
pinMode(in1, OUTPUT);
pinMode(in2, OUTPUT);
pinMode(in3, OUTPUT);
pinMode(in4, OUTPUT);
}
void loop() {
rotateClockwise(100);
delay(1000);
rotateCounterClockwise(100);
delay(1000);
}
// Вращение шагового двигателя по часовой стрелке
void rotateClockwise(int n){
for (int i=0; i<n; i  ) {
phase4();
phase3();
phase2();
phase1();
}
}
// Вращение шагового двигателя против часовой стрелки
void rotateCounterClockwise(int n){
for (int i=0; i<n; i  ) {
phase1();
phase2();
phase3();
phase4();
}
}
// Фазы 1...4 шагового двигателя:
void phase1(){
digitalWrite(in1, HIGH); 
digitalWrite(in2, LOW); 
digitalWrite(in3, LOW); 
digitalWrite(in4, HIGH);
delay(del);
}
void phase2(){
digitalWrite(in1, HIGH); 
digitalWrite(in2, HIGH); 
digitalWrite(in3, LOW); 
digitalWrite(in4, LOW);
delay(del);
}
void phase3(){
digitalWrite(in1, LOW); 
digitalWrite(in2, HIGH); 
digitalWrite(in3, HIGH); 
digitalWrite(in4, LOW);
delay(del);
}
void phase4(){
digitalWrite(in1, LOW); 
digitalWrite(in2, LOW); 
digitalWrite(in3, HIGH); 
digitalWrite(in4, HIGH);
delay(del);
}

Вот теперь совсем другое дело! Мы можем управлять скоростью шагового двигателя, задавая задержку после каждой фазы. Мы можем менять направление движения ротора двигателя. И, наконец, мы умеем поворачивать ротор на некоторый угол. Осталось только определить, какое число необходимо передавать в функции поворота rotateClockwise() и rotateCounterClockwise(), чтобы ротор шагового двигателя 1 раз провернулся на 360° вокруг своей оси. Собственно, дальнейшие наработки – вопрос фантазии или необходимости.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *