Сетевой модуль W5500 и ардуино

Это сравнительно новый сетевой модуль, который пришел на смену китайским платам на чипах W5100 и ENC28J60. Питается как от 5 так и 3,3 вольта. Подключается на протоколу SPI.

Данный модуль хорошо работает со встроенной в Arduino IDE библиотекой Ethernet. Библиотеку требуется обновить до версии 2.0.

Скетч ниже сделан на базe библиотечного примера webserver.ino. Добавлен вывод нескольких кнопок, которые управляют блоком реле, подключенным к цифровым пинам 4 — 7.

IP адрес модуля в данном коде — 192.168.0.177

#include <SPI.h>
#include <Ethernet.h>
 
// введите ниже MAC-адрес и IP-адрес вашего контроллера;
// IP-адрес будет зависеть от вашей локальной сети:
byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,0, 177);
 
// инициализируем библиотеку Ethernet Server, указывая нужный порт
// (по умолчанию порт для HTTP – это «80»):
EthernetServer server(80);
 
// задаем контакт и начальное состояние для реле:
String relay1State = "Off";
String relay2State = "Off";
const int relay4 = 7;
const int relay3 = 6;
const int relay2 = 5;
const int relay1 = 4;
 
// задаем переменные для клиента:
char linebuf[80];
int charcount=0;
 
void setup() {
  // подготавливаем реле-модуль:
  pinMode(relay1, OUTPUT);
  digitalWrite(relay1, HIGH);
  pinMode(relay2, OUTPUT);
  digitalWrite(relay2, HIGH);
 
  // открываем последовательную коммуникацию на скорости 9600 бод:
  Serial.begin(9600);
 
  // запускаем Ethernet-коммуникацию и сервер:
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print("server is at ");  //  "сервер на "
  Serial.println(Ethernet.localIP());
}
 
// Показываем веб-страницу с кнопкой «вкл/выкл» для реле:
void dashboardPage(EthernetClient &client) {
  client.println("<!DOCTYPE HTML><html><head>");
  client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"></head><body>");                                                            
  client.println("<h3>Arduino Web Server - <a href=\"/\">Refresh</a></h3>");
  
  // генерируем кнопку для управления реле:
  client.println("<h4>Relay 1 - State: " + relay1State + "</h4>");
  // если реле выключено, показываем кнопку «вкл»:          
  if(relay1State == "Off"){
    client.println("<a href=\"/relay1on\"><button>ON</button></a>");
  }
  // если реле включено, показываем кнопку «выкл»:
  else if(relay1State == "On"){
    client.println("<a href=\"/relay1off\"><button>OFF</button></a>");                                                                    
  }

    // генерируем кнопку для управления реле:
  client.println("<h4>Relay 2 - State: " + relay2State + "</h4>");
  // если реле выключено, показываем кнопку «вкл»:          
  if(relay2State == "Off"){
    client.println("<a href=\"/relay2on\"><button>ON</button></a>");
  }
  // если реле включено, показываем кнопку «выкл»:
  else if(relay2State == "On"){
    client.println("<a href=\"/relay2off\"><button>OFF</button></a>");                                                                    
  }
  
  client.println("</body></html>");
}
 
 
void loop() {
  // прослушиваем входящих клиентов:
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");  //  "новый клиент"
    memset(linebuf,0,sizeof(linebuf));
    charcount=0;
    // HTTP-запрос заканчивается пустой строкой:
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
       char c = client.read();
        // считываем HTTP-запрос, символ за символом:
        linebuf[charcount]=c;
        if (charcount<sizeof(linebuf)-1) charcount++;
        // если вы дошли до конца строки (т.е. если получили
        // символ новой строки), это значит,
        // что HTTP-запрос завершен, и вы можете отправить ответ:
        if (c == '\n' && currentLineIsBlank) {
          dashboardPage(client);
          break;
        }
        if (c == '\n') {
          if (strstr(linebuf,"GET /relay1off") > 0){
            digitalWrite(relay1, HIGH);
            relay1State = "Off";
          }
          else if (strstr(linebuf,"GET /relay1on") > 0){
            digitalWrite(relay1, LOW);
            relay1State = "On";
          }

          if (strstr(linebuf,"GET /relay2off") > 0){
            digitalWrite(relay2, HIGH);
            relay2State = "Off";
          }
          else if (strstr(linebuf,"GET /relay2on") > 0){
            digitalWrite(relay2, LOW);
            relay2State = "On";
          }
          
          // если получили символ новой строки...
          currentLineIsBlank = true;
          memset(linebuf,0,sizeof(linebuf));
          charcount=0;          
        }
        else if (c != '\r') {
          // если получили какой-то другой символ...
          currentLineIsBlank = false;
        }
      }
    }
    // даем веб-браузеру время на получение данных:
    delay(1);
    // закрываем соединение:
    client.stop();
    Serial.println("client disonnected");  //  "Клиент отключен"
  }
}

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

8-канальный блок реле и ардуино

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

Есть несколько особенностей, которые нужно иметь в виду:

  • потребление силовой части при всех включенных реле — 1 А
  • потребление управляющей части при всех включенных реле — 30 мА
  • для рекомендуемого раздельного питания частей необходимо снять джампер (в этом случае питание силовой части подключается через контакты GND и JD-VCC)
  • модули продаются двух типов: срабатывающих на высокий входной уровень (+5V) и низкий (0V). Здесь важно не запутаться в терминалогии продавцов с Алиэкспресса. Они называют модуль «низкого уровня», если высокий уровень на входе выключит катушку реле, а низкий уровень — включит. И наоборот. Также продаются модули, позволяющие пользователю выставлять режим срабатывания с помощью джампера.

Модуль реле имеет цифровые входы, т.о. его можно напрямую подключать к цифровым пинам ардуины. Однако в этом случае для управления потребуется тянуть 8 сигнальных проводов плюс два провода питания. Кроме того будут заняты 8 дефицитных ножек процессора. В тех случаях, когда это нежелательно, можно воспользоваться расширителем портов PCF8574.

В простом варианте потребуется 2 провода для шины I2C и питание. Однако и тут есть ньюанс. При подаче питания на PCF8574 все ее выходы по-умолчанию имеют низкий логический уровень (0V). Это значит на модуле реле «низкого уровня», подключенного к ней, сразу включатся все реле. Решением этой проблемы может быть использование модуля реле «высокого уровня» (не мой случай) или программный перевод выходов PCF8574 в низкий уровень при старте прошивки (на что требуется время, в течении которого реле могут включиться).

Так как мной был куплен блок реле, срабатывающий от «низкого уровня», пришлось сделать следующее:

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

Мной также был проведен эксперимент. Я испытал данную связку без управляющей линии (фиолетовый провод). Оказалось, что при включении прошивка переводит ножки PCF8574 в высокое состояние достаточно быстро. Реле не успевают включаться. Хотя светодиоды управляющей части блока и подмигивают. Т.о. упраляющую линию на практике можно и не использовать.

Ниже скетч (используется управляющая линия):

#include <Wire.h>    // для работы с I2C
#include "PCF8574.h" // для работы с PCF8574

PCF8574 expander; // PCF8574 instance

void setup() {
  pinMode(7, OUTPUT); // управляющая линия для включения платы реле
  
  expander.begin(0x20);
  
  // перевод пинов PCF8574 в высокий логический уровень
  // это выключит реле на моем модуле "низкого уровня"
  expander.pinMode(0, OUTPUT);
  expander.digitalWrite(0, HIGH);
  expander.pinMode(1, OUTPUT);
  expander.digitalWrite(1, HIGH);
  expander.pinMode(2, OUTPUT);
  expander.digitalWrite(2, HIGH);
  expander.pinMode(3, OUTPUT);
  expander.digitalWrite(3, HIGH);
  expander.pinMode(4, OUTPUT);
  expander.digitalWrite(4, HIGH);
  expander.pinMode(5, OUTPUT);
  expander.digitalWrite(5, HIGH);
  expander.pinMode(6, OUTPUT);
  expander.digitalWrite(6, HIGH);
  expander.pinMode(7, OUTPUT);
  expander.digitalWrite(7, HIGH);
}

void loop() {
  digitalWrite(7, HIGH);   // включение платы реле
  expander.digitalWrite(0, LOW); // пример включения реле 1
/*  
  expander.digitalWrite(1, LOW);
  expander.digitalWrite(2, LOW);
  expander.digitalWrite(3, LOW);
  expander.digitalWrite(4, LOW);
  expander.digitalWrite(5, LOW);
  expander.digitalWrite(6, LOW);
  expander.digitalWrite(7, LOW);
  delay(500);
 
  expander.toggle(0); // пример инвертирования реле 1
*/
}

Расширитель портов PCF8574

Зачем это нужно? Представьте ситуацию, вы подключаете к ардуино блок 8-канального реле. Для управления им потребуется тянуть 8 сигнальных проводов плюс два провода питания. Кроме того будут заняты 8 дефицитных ножек процессора.

Плата PCF8574 может решить эту проблему. Она подключается к ардуино по I2C (два провода плюс питание) и имеет 8 цифровых пинов, которые могут работать как на вход, так и на выход. Кроме того имеется один пин прерывания при помощи которого можно отслеживать изменение на одном из цифровых входов.

Продолжить чтение «Расширитель портов PCF8574»

Сканер I2C шины

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

#include <Wire.h>
String stringOne;
void setup()
{
  Wire.begin();
  Serial.begin(9600);
  while (!Serial);         
}
 
void loop()
{
  byte error, address;
  int nDevices;
  Serial.println("Scanning...");
  nDevices = 0;
  for(address = 1; address < 127; address++ ) 
  {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
 
if (error == 0)
  {
  String stringOne =  String(address, HEX);
  Serial.print("0x");     Serial.print(stringOne); Serial.print(" - ");
    if(stringOne=="0A") Serial.println("'Motor Driver'");
    if(stringOne=="0F") Serial.println("'Motor Driver'");
    if(stringOne=="1D") Serial.println("'ADXL345 Input 3-Axis Digital Accelerometer'");
    if(stringOne=="1E") Serial.println("'HMC5883 3-Axis Digital Compass'");
    if(stringOne=="5A") Serial.println("'Touch Sensor'");
    if(stringOne=="5B") Serial.println("'Touch Sensor'");
    if(stringOne=="5C") Serial.println("'BH1750FVI digital Light Sensor' OR 'Touch Sensor"  );
    if(stringOne=="5D") Serial.println("'Touch Sensor'");
    if(stringOne=="20") Serial.println("'PCF8574 8-Bit I/O Expander' OR 'LCM1602 LCD Adapter' ");   
    if(stringOne=="21") Serial.println("'PCF8574 8-Bit I/O Expander'");
    if(stringOne=="22") Serial.println("'PCF8574 8-Bit I/O Expander'");
    if(stringOne=="23") Serial.println("'PCF8574 8-Bit I/O Expander' OR 'BH1750FVI digital Light Sensor'");
    if(stringOne=="24") Serial.println("'PCF8574 8-Bit I/O Expander'");
    if(stringOne=="25") Serial.println("'PCF8574 8-Bit I/O Expander'");
    if(stringOne=="26") Serial.println("'PCF8574 8-Bit I/O Expander'");
    if(stringOne=="27") Serial.println("'PCF8574 8-Bit I/O Expander' OR 'LCM1602 LCD Adapter '");   
    if(stringOne=="39") Serial.println("'TSL2561 Ambient Light Sensor'");    
    if(stringOne=="40") Serial.println("'BMP180 barometric pressure sensor'"    ); 
    if(stringOne=="48") Serial.println("'ADS1115 Module 16-Bit'");
    if(stringOne=="49") Serial.println("'ADS1115 Module 16-Bit' OR 'SPI-to-UART'");
    if(stringOne=="4A") Serial.println("'ADS1115 Module 16-Bit'");
    if(stringOne=="4B") Serial.println("'ADS1115 Module 16-Bit'");
    if(stringOne=="50") Serial.println("'AT24C32 EEPROM'"); 
    if(stringOne=="53") Serial.println("'ADXL345 Input 3-Axis Digital Accelerometer'");
    if(stringOne=="68") Serial.println("'DS3231 real-time clock' OR 'MPU-9250 Nine axis sensor module'");
    if(stringOne=="7A") Serial.println("'LCD OLED 128x64'");
    if(stringOne=="76") Serial.println("'BMP280 barometric pressure sensor'");
    if(stringOne=="77") Serial.println("'BMP180 barometric pressure sensor' OR 'BMP280 barometric pressure sensor'");
    if(stringOne=="78") Serial.println("'LCD OLED 128x64'" );
   nDevices++;
  }
    else if (error==4) 
    {
      Serial.print("Unknow error at address 0x");
      if (address<16) 
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
 
  delay(5000);          
}

Модуль офф-лайн распознавания голоса

Купить модуль можно тут: ссылка на алиэкспресс.

Если в проекте требуется голосовое управление, модуль Elechouse V3 может быть интересен для вас. Он не требует подключения к Интернет и может работать как с внешним контроллером (обрабатывается до 80 голосовых команд), так и полностью автономно (до 7 голосовых команд).

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

Питание модуля — 5 V. Длина каждой голосовой команды – до 1,5 секунды.

Работа с ардуино

Все примеры библиотеки данного модуля написаны для подключения, показанного на схеме ниже:

  • TXD модуля -> пин 2 на ардуино
  • RXD модуля -> пин 3 на ардуино

Важно не перепутать полярность при подключении.

Библиотека для работы с модулем находиться здесь: voicerecognitionv3.h

Как установить библиотеку в Arduino IDE. Если кратко, то нужно вставить папку библиотеки в C:\Users\username\Documents\Arduino\libraries

Запись базы голосовых команд (тренировка модуля)

  • после установки библиотеки в Arduino IDE открыть скетч для тренировки модуля: File -> Examples -> VoiceRecognitionV3 -> vr_sample_train
  • загрузить данный скетч в микроконтроллер
  • открыть монитор последовательного порта (установить скорость 115200, включить опцию «Newline» внизу справа). В окне будет выведено текстовое меню. Для записи голосовых команд используется инструкция «train X», где Х — адрес/номер команды (от 0 до 79). Например: train 15, train 46, train 75. Перед тренировкой нового модуля можно использовать инструкцию «clear» — она очистит все ранее записанные голосовые команды.
  • для записи первой команды вверху окна терминала ввести «train 0», дождаться сообщения «Speak now», проговорить команду в микрофон, дождаться сообщения «Speak again», проговорить команду в микрофон еще раз, дождаться сообщения «Success». Если команда была произнесена невнятно, то придется повторять ее несколько раз.
  • повторить предыдущий пункт для записи всех остальных команд.

Скетч, зажигающий светодиод на плате Arduino Uno

Для тестирования модуля можно использовать пример из библиотеки, который включает светодиод, подключенный к пину 13: File -> Examples -> VoiceRecognitionV3 -> vr_sample_control_led.

Перед загрузкой скетча необходимо записать в память модуля две голосовые команды на включение и выключение светодиода. В коде примера эти команды прописаны по адресам 0 и 1 соответственно — «#define onrecord (0)» и «#define offrecord (1)».

DFPlayer Mini и ардуино

Купить модуль можно тут: ссылка на алиэкспресс.

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

Модуль подключается к ардуино через резисторы 1 кОм (хотя он может работать и автономно). Пины линии данных инвертируются: Rx на модуле > Tx на ардуино.

Модуль имеет встроенный усилитель мощностью 3W, к которому подключается динамик (пины SPK_1 и SPK_2). Так же есть выходы на внешний усилитель — пины DAC_R — правый канал и DAC_L — левый канал.

Модуль требователен к питанию. Рекомендуется питать его от миниатюрного DC-DC преобразователя. Иначе время от времени наблюдается неприятное сипение.

Подготовка microSD карточки

  • новую карту отформатировать в FAT16 или FAT32 (поддерживаются карты до 32G)
  • создать папку mp3
  • записать файлы mp3 с именами 0001.mp3, 0002.mp3 и т.д. (допускается 0001_file_name.mp3)

Библиотека для работы с модулем

В сети есть несколько библиотек, но я пользуюсь вот этой: DFPlayer Mini Mp3 by Makuna

Как установить библиотеку в Arduino IDE. Если кратко, то нужно вставить папку библиотеки в C:\Users\username\Documents\Arduino\libraries

Пример кода

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

#include <SoftwareSerial.h>
#include <DFPlayer_Mini_Mp3.h>
void setup () {
  Serial.begin (9600);
  mp3_set_serial (Serial);
  delay (100);
  mp3_set_volume (15);
}
void loop () {        
 delay (100);
 mp3_play (1);  //проигрываем "mp3/0001.mp3"
 delay (15000); //ждем пока проиграется файл
 mp3_play (2); //проигрываем "mp3/0002.mp3"
 delay (15000); //ждем пока проиграется файл
 mp3_play (3); //проигрываем "mp3/0003.mp3"
 delay (15000); //ждем пока проиграется файл
}

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

#define SerialP Serial // пин 1 через резистор 1 КОм к RX плеера
#define PLAY  0x03
#define VOL   0x08
#define STOP  0x16
#define BUSY_MP3  12     // ножка BUSY занятость мп3 (0-играет, 1-молчит)
byte buff[10] = {0x7E, 0xFF, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF};
byte nomPlay = 1;
bool oneplay = 1;

void setup() {
  SerialP.begin (9600);  // плеер
  playToEnd(1);
 
}

void loop() {
}

void play (byte np) {     // проигрываем запись номер nm в любой момент
  mp3_cmd(PLAY, np);
  delay(100);
}

void playToEnd (byte np) {  // проигрываем запись номер nm и ждем пока она закончится
  play (np);
  while (!digitalRead(BUSY_MP3));
}

void playIfEnd (byte np) {  // проигрываем запись номер nm если закончилась предыдущая
  if (!digitalRead(BUSY_MP3)) return;
  play (np);
}

void playWaitEnd (byte np) {  // ждем когда закончится предыдущая и проигрываем запись номер nm
  while (!digitalRead(BUSY_MP3));
  play (np);
}

void stopPlay () {
  mp3_cmd(STOP, 0);
  delay(100);
}

void mp3_cmd (byte com, byte atr) {  // команда на плеер
  buff[3] = com;
  buff[6] = atr;
  mp3_check(buff);
  for (byte i = 0; i < 10; i++) {
    SerialP.write(buff[i]);
  }
}

void mp3_check (uint8_t *buf) {      // КС для плеера
  uint16_t sum = 0;
  for (byte i = 1; i < 7; i++) {
    sum += buf[i];
  }
  sum = -sum;
  *(buf + 7) = (uint8_t)(sum >> 8);
  *(buf + 8) = (uint8_t)sum;
}

Функции библиотеки

  • mp3_play (); // Запуск воспроизведения
  • mp3_play (5); // Воспроизвести файл «mp3/0005.mp3»
  • mp3_next (); // Следующий трек
  • mp3_prev (); // Предыдущий трек
  • mp3_set_volume (uint16_t volume); // Уровень громкости 0~30
  • mp3_set_EQ (); // Эквалайзер 0~5 (0 — Normal, 1 — Pop, 2 — Rock, 3 — Jazz, 4 — Classic, 5 — Bass)
  • mp3_pause (); // пауза
  • mp3_stop (); // остановить воспроизведение
  • mp3_random_play (); // Воспроизвести в случайнВоспроизвести в случайном порядке.
  • void mp3_get_state (); //send get state command
  • void mp3_get_volume ();
  • void mp3_get_u_sum ();
  • void mp3_get_tf_sum ();
  • void mp3_get_flash_sum ();
  • void mp3_get_tf_current ();
  • void mp3_get_u_current ();
  • void mp3_get_flash_current ();
  • void mp3_single_loop (boolean state); //set single loop
  • void mp3_DAC (boolean state);

OLED 0.96″ дисплей и ардуино

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

Дисплей подключается к ардуино по протоколу I2C четырьмя проводами. Питание как 3,3V, так и +5V.

Для работы с данным дисплеем в сети можно найти несколько библиотек. Мой выбор пал на две из них из-за их компактного размера:

Как установить библиотеку в Arduino IDE. Если кратко, то нужно вставить папку библиотеки в C:\Users\username\Documents\Arduino\libraries

#include <OLED_I2C.h>

OLED  myOLED(SDA, SCL);

extern uint8_t SmallFont[];
extern uint8_t MediumNumbers[];
extern uint8_t BigNumbers[];

void setup()
{
  if(!myOLED.begin(SSD1306_128X64))
    while(1);    
  myOLED.setFont(SmallFont);
}

void loop()
{
  myOLED.setFont(BigNumbers);
  myOLED.printNumI(123456789, RIGHT, 33);

  myOLED.setFont(SmallFont);
  myOLED.print("Timer", LEFT, 15);
  myOLED.update();

  myOLED.clrScr();
}
SPI дисплеи (возможные обозначения контактов)
Sr.Pin NameAlso known asFunction
1GNDGround  Ground pin of OLED
2Vdd5V, 3v3, Vcc  Power pin (3.3V to 5V)
3SCKD0, CLK, SCL  Works as clock pin, It is used for both SPI & I2C
4SDAMOSI, D1  Data pin of OLED, It is used for both SPI & I2C
5RESRESET, RST  To reset OLED, part of SPI configuration
6DCA0  Data Command pin, part of SPI configuration
7CSChip Select  Useful when we have multiple SPI module on same controller
Подключение дисплея по SPI интерфейсу
Arduino PinOLED Pin
3.3VVdd, Vcc, 3v3
GroundGND
9SDA, D1, MOSI
10SCK, D0, SCL,CLK
11DC, A0
12CS, Chip Select
13RES, RST, RESET

Энкодер с RC-фильтром и ардуино

Купить энкодер можно тут: ссылка на алиэкспресс.

Можно было бы купить дешевый энкодер без платы. Однако энкодер — это электро-механическое устройство и подвержено так называемому «дребезгу контактов». Данная модель имеет на борту RC-фильтр для каждого контакта. Это решает проблему дребезга (схема ниже).

Импульсы на выходе инкрементального энкодера должны сообщать не только о повороте вала, но и о направлении поворота. Поэтому используется 2 сигнала, обозначенные на схеме S1 и S2.

  • Если в момент перехода сигнала S1 из высокого состояния в низкое сигнал S2 находится в высоком уровне, то произошел поворот против часовой стрелки.
  • Если в момент перехода сигнала S2 из высокого состояния в низкое сигнал S1 находится в высоком уровне, то был поворот по часовой стрелке.

Подключение энкодера к ардуино показано на рисунке ниже:

Далее простой скетч, который позволяет определить кодировку энкодера. Если плавно вращать вал по часовой стрелке, монитор последовательного порта покажет: 1-1 -> 1-0 -> 0-0 -> 0-1 -> 1-1. При вращении  против часовой стрелки: 1-1 -> 1-0 -> 0-0 -> 1-0 -> 1-1. Все как на диаграмме выше.

int last_state_S1 = 1;
int last_state_S2 = 1;
 
void setup() {
  Serial.begin(9600); // инициализируем порт для вывода данных  
}
 
void loop() {
  // читаем значения на выводах энкодера
  int current_state_S1 = digitalRead(8);
  int current_state_S2 = digitalRead(9);

  // выводим состояние ножек в монитор порта только если есть вращение энкодера
  if (current_state_S1 != last_state_S1 || current_state_S2 != last_state_S2) {
    Serial.print(current_state_S1);
    Serial.print(" - ");
    Serial.println(current_state_S2);
    last_state_S1 = current_state_S1;
    last_state_S2 = current_state_S2;
    delay(200); // задержка подобрана экспериментально
  } 
}

Далее рабочий пример. Обработка энкодера здесь идет в прерывании по таймеру каждые 250 мс. В основном цикле выводит текущее положение вала в монитор последовательного порта. При нажатии на кнопку сбрасывает текущее положение в 0.

#include <TimerOne.h>
 
int encoder_value = 1; // накопленное значение энкодера

int min_encoder_value = 1; // задаем диапазон значения энкодера
int max_encoder_value = 10;

int last_state_S1 = 1;
int last_state_S2 = 1;
 
void setup() {
  Serial.begin(9600); // инициализируем порт для вывода данных в монитор
 
  Timer1.initialize(9000); // инициализация таймера 1
  Timer1.attachInterrupt(timerInterrupt); // задаем обработчик прерываний
}
 
void loop() {
// сброс накопленного значения энкодера в 0 при нажатии кнопки 
  if( digitalRead(10) == 0 ) {
    encoder_value = 0;
  }

}
 
void timerInterrupt() {
  // читаем значения на выводах энкодера
  int current_state_S1 = digitalRead(8);
  int current_state_S2 = digitalRead(9);

  // изменяем накопленное значение энкодера только если есть вращение
  if (current_state_S1 != last_state_S1 && current_state_S1 == 0 && current_state_S2 == 1) {
    if (encoder_value > min_encoder_value) {
      encoder_value--;
    } else {
      encoder_value = max_encoder_value;
    }
    Serial.println(encoder_value); // вывод положения значения энкодера в монитор
  }
  if (current_state_S2 != last_state_S2 && current_state_S2 == 0 && current_state_S1 == 1) {
    if (encoder_value < max_encoder_value) {
      encoder_value++;
    } else {
      encoder_value = min_encoder_value;
    }
    Serial.println(encoder_value); // вывод положения значения энкодера в монитор
  }
    last_state_S1 = current_state_S1;
    last_state_S2 = current_state_S2;    
}

Емкостной датчик влажности почвы и ардуино

Купить датчик можно тут: ссылка на алиэкспресс.

До недавнего времени для полива домашних растений я использовал дешевые резисторные датчики влажности почвы. Их недостаток — недолговечность. Даже графитовые стержни в качестве электродов со временем разрушаются.

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

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

Скетч простейший. Читаем значение на аналоговом пине и выводим в монитор порта. У меня значения влажности лежат в диапазоне от 820 (сухая почва) до 440 (чистая вода).

// Объявляем пин, к которому подключен выход датчика
const int Humidity_Sensor = A2; 

void setup()
{
  Serial.begin(9600); //подключаем монитор порта  
}

void loop()                    
{
  //Читаем значение влажности с датчика
  int sensorValue = analogRead(Humidity_Sensor); 

  //Выводим измеренную влажность в монитор порта
  Serial.print("Значение влажности - "); 
  Serial.println(sensorValue);   
} 

Инфракрасный PIR датчик и ардуино

Купить датчик можно тут: ссылка на алиэкспресс.

Инфракрасный PIR датчик движения (или датчик присутствия) HC-SR501 позволяет улавливать движение теплых тел на расстоянии до 7 метров в области 70-110 градусов (именно движение, а не присутствие).

Основное применение — это включение нагрузки при появлении человека.

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

Схема подключения простая. Просто нужно подать питание и воткнуть один вход на любой цифровой пин Ардуино.

Скетч, который при приближении объекта, зажигает светодиод 13. Гаснет светодиод после задержки, установленой переменным резистором на самом датчике.

#define pirPin 2
#define ledPin 13
 
void setup()
{
  Serial.begin(9600);
  pinMode(pirPin, INPUT);
  pinMode(ledPin,OUTPUT);
}
 
void loop()
{
  int pirVal = digitalRead(pirPin);
 
  //Если обнаружили движение
  if(pirVal == HIGH)
  {
    digitalWrite(ledPin, HIGH);
    Serial.print("Motion detected");
    delay(2000);
  }
  else
  {
    Serial.print("No motion");
    digitalWrite(ledPin,LOW);
  }
}

Обработка задержки выключения датчика программным способом:

//Время калибровки датчика после включения
int calibrationTime = 10;      
//Время, в которое был принят сигнал отсутствия движения(LOW)
long unsigned int lowIn;       
//Пауза, после которой движение считается оконченным
long unsigned int pause = 5000;
//Флаг: false - значит движение уже обнаружено, true - уже известно, что движения нет
boolean lockLow = true;
//Флаг: Сигнализирует о необходимости запомнить время начала отсутствия движения
boolean takeLowTime;
int pirPin = 2;    //вывод подключения PIR датчика
int ledPin = 13;   //вывод сигнального диода
 
void setup()
{
  Serial.begin(9600);
  pinMode(pirPin, INPUT);
  pinMode(ledPin, OUTPUT); 
  digitalWrite(pirPin, LOW);
  //дадим датчику время на калибровку
  Serial.print("Calibrating");
  for(int i = 0; i < calibrationTime; i++)
  {
    Serial.print(".");
    delay(1000);
  }
  Serial.println(" done");
  Serial.println("SENSOR ACTIVE");
  delay(50);
}
 
void loop()
{
  //Если обнаружено движение
  if(digitalRead(pirPin) == HIGH)
  {
    //Если еще не вывели информацию об обнаружении
    if(lockLow)
    {
      lockLow = false;     
      Serial.println("Motion detected");
      delay(50);
    }        
    takeLowTime = true;
  }
 
  //Ели движения нет
  if(digitalRead(pirPin) == LOW)
  {      
    //Если время окончания движения еще не записано
    if(takeLowTime)
    {
      lowIn = millis();          //Сохраним время окончания движения
      takeLowTime = false;       //Изменим значения флага, чтобы больше не брать время, пока не будет нового движения
    }
    //Если время без движение превышает паузу => движение окончено
    if(!lockLow && millis() - lowIn > pause)
    { 
      //Изменяем значение флага, чтобы эта часть кода исполнилась лишь раз, до нового движения
      lockLow = true;               
      Serial.println("Motion finished");
      delay(50);
    }
  }
}
Создайте подобный сайт на WordPress.com
Начало работы