Всем привет! В интернете бытует заблуждение, что для управления компьютером при помощи самодельной электроники нужны только специальные платы, которые могут распознаваться как USB HID устройства. А касаемо Arduino все только и говорят о Arduino Leanardo. Такие популярные библиотеки как Keyboard и Mouse, которые позволяют создавать эмуляцию работы мыши или клавиатуры посредством микроконтроллера предназначены только для пары плат Arduino, Leonardo в их числе.

Я расскажу о том, как наладить связь любого микроконтроллера Arduino (для примера взята Arduino Uno) и своей программы на Processing. Добавив ко всему прочему знания о Java, на котором основывается Processing, можно будет дописать проект под управление всем компьютером, а не только собственным приложением. Тема управления компьютером программой на Java не есть чем то секретным, погуглите и все найдете, уверяю вас.

Скачиваем среды для разработки (IDE)


Существует много интегрированных сред разработки для программирования микроконтроллеров на чистом Си. Из них можно отметить самые удобные: Atollic, Eclipse, Keil.

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

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

Стоит отметить, приличия ради, что данные IDE очень похожи, потому что написаны на одном движке. И когда создавался Arduino основатели старались как можно больше упростить свой редактор кода, как это и было сделано в Processing редакторе.

Arduino. Собираем схему и пишем код


В данном примере я буду использовать Arduino Uno. К ней будет подключена кнопка, потенциометр и светодиод. Соответственно я могу выдавать логический 0 или 1. Читать логический 0 или 1. И проводить Аналого-цифровое преобразование(ADC или АЦП), получая числа от 0 до 1023 (в Arduino Uno 10-ми разрядный АЦП) в зависимости от положения потенциометра. Большего для примера и не нужно, так как это основные функции, которые может делать микроконтроллер.

Схема подключения:



На схеме светодиод анодом подключен к 5V через ограничивающий резистор ( минимум 220 Ом, желательно 500 Ом), катодом к пину D11. Кнопка замыкает землю и пин D2. Потенциометр меняет потенциал на пине A1.

Задача микроконтроллера следующая: Если по последовательному интерфейсу (Serial COM port) приходит сообщение «LED — H» — засветить светодиод. Если приходит сообщение «LED — L» — затушить светодиод. Каждые 250мс отправлять сообщение в последовательный порт (в данном случае на экран компьютера) сообщение «Pot — » и число, полученное аналоговым чтением пина A1. При нажатии кнопки единожды отсылать сообщение «Button is pressed!».

Вот мое предложение решения данной задачи (не пример для подражания):

Прошивка для Arduino Uno
#define pinPot A1
#define pinLed 11
#define pinBtn 2

void setup() {

  pinMode(pinPot, INPUT);
  pinMode(pinLed, OUTPUT);
  pinMode(pinBtn, INPUT_PULLUP);


  Serial.begin(9600);
  Serial.println("The program starts.\n\n");

}

void loop() {

  /* INITIAL VARIABLES. Segment 1 */
  static char potMes[]  = "Pot - ";
  static char btnMes[] = "Button is pressed!";
  static char passLight[] = "Led - ";
  static int passLength = sizeof(passLight) - 1;
  static int sizepm = sizeof(potMes) - 1;
  static int sizebtn = sizeof(btnMes) - 1;

  static bool flagLedState = LOW;
  static bool flagBtnPress = false;
  static long int curTime = 0;
  static const int period = 200; 
  static bool flagEnableRead = false;
  /* INITIAL VARIABLES. Segment 1 */

  /* FUNCTIONS CALL. Segment 2 */

  /*
   * Led is attached to HIGH voltage from one side
   * And to pin on the other side
   * By that the inverting logic
   */
  ReadSerialForLed(passLight, passLength, &flagLedState);
  digitalWrite(pinLed, !flagLedState);
  
  /*
   * Button pin always is pulled to the HIGH voltage
   * And only when button is pressed - Voltage on pin goes to GROUND
   * So it is need to invert logic when read pins 
  */
  if(!Bounce(pinBtn) && flagBtnPress == false){
   
    for(int i = 0; i < sizebtn; i++){
       Serial.write(btnMes[i]);
    }
    Serial.print("\n");
    flagBtnPress = true;
    
    if(!flagEnableRead){
      curTime = millis();
      flagEnableRead = true;
    }
    
  }else if(Bounce(pinBtn)){
    flagBtnPress = false;
  }

  /*
   * Read and send Info "Pot - " + var Only after first press on button
   * Every 'period'ms
   */
  if(millis() - curTime > period && flagEnableRead){
    SendData(pinPot, potMes, sizepm);
    curTime = millis();
  }
  /* FUNCTIONS CALL. Segment 2 */
  
}
/*
 * Pot - pin with potentiometer
 * pMes - Array with message before Pot value
 * sp - size of potentiometer message
 */
void SendData(int Pot, char* pMes, int sp){

  static int varP[2];
  
  varP[0] = analogRead(Pot);
  
  varP[1] = varP[0]/256; // 0 - 3 (256 - 1024)
  varP[0] = varP[0]%256; // 0 - 255

  //Send Message
  for(int i = 0; i < sp; i++){
    Serial.write(char(pMes[i]));
  }
  //Send 2 bits of data
  //Serial.write(varP[0]);
  //Serial.write(varP[1]);

  Serial.print(analogRead(Pot));
  Serial.print("\n");

}

/*
 * Function, which is reads button pin with the bounce
 */
bool Bounce(int btn){
  
  if(digitalRead(btn) == true){
    delay(15);
    if(digitalRead(btn) == true){
      return true;
    }else{
      return false;
    }
  }else{
    return false;
  }

}

/*
 * If Message from Serial port, which you read will be the same to passLight
 * So look at the next symbol after Pass Message. If it is symbol 'H' - make LED to light
 * If it is 'L' - make LED off.
 */
void ReadSerialForLed(char *passLight_f, int passLength_f, bool* flagLedState_f){
  
  static char sym;
  static int cntPass = 0;
  static bool readyGetLed = LOW;

  while (Serial.available() > 0) {
    
    sym = Serial.read();

    if(sym == passLight_f[cntPass] && !readyGetLed){
      cntPass++;
    }else if (!readyGetLed){
      cntPass = 0;
    }else if(readyGetLed){
      if(sym == 'H'){
        *flagLedState_f = HIGH;
      }else if(sym == 'L'){
        *flagLedState_f = LOW;
      }
    }

    if(cntPass == passLength_f){
      readyGetLed = HIGH;
    }
  }
  
}

Комментарий: Светодиод подключен анодом к питаю. Это инвертирует логику состояния светодиода и больше никакой пользы не приносит. Кнопка не обвязана подтягивающим резистором из соображений экономии, так как в Arduino Uno имеются встроенные подтягивающие резисторы, которые включаются в схему при инициализации пина в режим INPUT_PULLUP.
Так же в прошивке сообщения о значении снятого с потенциометра отсылаются только после первого нажатия на кнопку!


Что бы залить прошивку в плату не забывайте выбрать порт и плату.



Если вы не знаете какой COM порт у вас отведен для платы Arduino, то на Windows заходим в
Панель управления -> Диспетчер устройств и нажимаем на вкладку «Порты COM»



Если у вас COM порт не подписан как у меня — всегда можно отсоединить Arduino и посмотреть который порт пропадет. А вот если никакой не пропал и Ардуина вовсе не распознается компьютером — значит пора поискать решение в интернете. Но начните с обновления драйверов или смены платы.

Когда все получится — попробуйте открыть монитор порта и ввести «Led — H», «Led — L», по нажимайте на кнопку, покрутите потенциометр и смотрите на экран, все ли правильно выводится.

Наигрались — поменяйте слегка код.

Замените последнюю строку кодом из комментария.

  //Send 2 bits of data
  //Serial.write(varP[0]);
  //Serial.write(varP[1]);

  Serial.print(analogRead(Pot));

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

Processing. Пишем программу, которая взаимодействует с микроконтроллером


Суть связи программы на Processing и микроконтроллера очень проста. Для этого языка программирования существует библиотека Serial, которая позволяет принимать сообщения, отправленные как Serial.write();, а так же позволяет отправлять сообщения как Serial.print();. Важно отметить, что при подобной отправке сообщения оно будет записано в буфер порта, а значит будет прочитано микроконтроллером. Так что нам осталось только подключиться к нужному Serial порту и принимать/отправлять на него сообщения.

Следующая программа подключит библиотеку Serial и напишет в консоли редактора список всех COM портов, к которым можно подключиться.

import processing.serial.*;

void setup()
{
  String[] port = Serial.list();
  for(int i = 0; i < port.length; i++){
    print("Port number #" + i + "  ");
    println(Serial.list()[0]);
  }
}

void draw() {}

Когда вы напишете код в редактор и нажмете на кнопку «Пуск» (стрелочка 1 на картинке), то появится окно приложения(2) и в консоли(3) выведется список COM портов.



У меня только один такой COM порт и в листе, как в массиве, он будет находиться под номером 0. Из этих соображений объекту класса Serial: Serial port; при его создании будет указан именно первый элемент списка портов port = new Serial(this, Serial.list()[0], 9600);

Залейте в Ардуину нашу последнюю прошивку с изменением. После чего напишите вот эту программу и запустите ее. В ней Каждые 500 миллисекунд отправляется сообщение в COM порт потушить или зажечь светодиод. И если все у вас сделано правильно, то после запуска приложения светодиод должен мигать.

import processing.serial.*; 
Serial port;  // Create object from Serial class

void setup(){
  port = new Serial(this, Serial.list()[0], 9600); 
}

void draw(){
  delay(500);
  port.write("Led - H");
  delay(500);
  port.write("Led - L");
}

Или вот другой пример. Светодиод будет менять свое состояние после любого нажатия на окно приложения (размеры которого 800х800px) кнопкой мыши.

import processing.serial.*; 
Serial port;  // Create object from Serial class

int cnt = 0;

void setup(){
  size(800, 800);
  port = new Serial(this, Serial.list()[0], 9600); 
}

void draw(){}

void mousePressed() {
  cnt++;
  if(cnt % 2 == 1){
    port.write("Led - H");
  }else{
    port.write("Led - L");
  }
}

Processing. Пример многофункционального приложения


Данное элементарное приложение симулирует «полет в космосе», если это можно так назвать. Значение с потенциометра изменяет скорость полета, нажатие на кнопку меняет направление полета. А любое нажатие кнопки мыши на окно приложения — меняет состояние светодиода (да, ничего оригинальнее я не придумал).

Мой код далек от совершенства, не принимайте его как хороший пример. Это просто пример, который работает. Вот, собственно, он.

Пример многофункциональной программы
import processing.serial.*; 
Serial port;  // Create object from Serial class
int val;      // Data received from the serial port (symbol)
int pot;      // Data from potentiometer

String potMes  = "Pot - "; 
String btnMes = "Button is pressed!";

int cntPM = 0; // Counter Potentiometer Message. 
               // When it equals to length of Pot Mess - get value.
int cntBM = 0;
int cntBtnPress = 0;
int cntMousePress = 0;

Star[] stars = new Star[1000];
float speed;
int dir = 1;

void setup(){
  size(800, 800);
  for(int i = 0; i < stars.length; i++){
    stars[i] = new Star();
  }
  frameRate(60); // 60 Frames per second
  
  port = new Serial(this, Serial.list()[0], 9600); 
  
  // Wait for first message from Arduino
  delay(2000);
  while (port.available() > 0) {   
    val = port.read();            
    print(char(val));
  } 
  
}

void draw(){
   if (port.available() > 0) {     
    val = port.read();            
    
    cntPM = CheckSymbol(potMes, cntPM, char(val), cntPM);
    cntBM = CheckSymbol(btnMes, cntBM, char(val), cntBM);
  } 
  
  DrawRain(pot, 0, 1023);
}

void DrawRain(int speed_f, int min, int max){
  background(0);
  translate(width/2,height/2);
  speed = dir*map(speed_f, min, max, 0, 50);
  for(int i = 0; i < stars.length; i++){
    stars[i].go();
    stars[i].update();
    stars[i].show();
  }
}

int CheckSymbol(String mes, int index, char sym, int ret_val){
  
  if(mes.charAt(index) == sym && ret_val < (mes.length() - 1)){
    
    return (ret_val + 1);
    
  }else if( ret_val == (mes.length() - 1) && mes.equals(potMes) ){
    
    if(port.available() > 0){
      pot = port.read();            // First 0-255 value
    }
    if(port.available() > 0){
      pot += 256*port.read();       // Last 2 bits 256 - 1024
    }

  }else if( ret_val == (mes.length() - 1) && mes.equals(btnMes) ){
    
    cntBtnPress++;
    dir = -dir;
    
  }
  
  return 0;
}

void mousePressed() {
  cntMousePress++;
  if(cntMousePress % 2 == 1){
    port.write("Led - H");
  }else{
    port.write("Led - L");
  }
}

Заключение


Думаю, нужно написать, что идею последней программы я подцепил у одного программиста — Daniel Shiffman, который снимает ролики, понятные даже детям, о программировании на Processing (решено более 140 визуальных задач).

Когда я пытался сам разобраться в том что и как нужно делать для связи Processing и Arduino мне очень помогли вот эти сайты:

  1. developer.alexanderklimov.ru/arduino/processing.php
  2. arduino-diy.com/arduino-processing-osnovi

Комментарии (34)


  1. little-brother
    04.05.2019 18:41

    Не очень понятно зачем нужны эти пляски с Processing, когда есть ATtiny85 trinket (на али за ~75руб), которая без проблем программируется в Arduino IDE, шьется напрямую через USB и потом видна как клавиатура или мышка.


    1. tormozedison
      04.05.2019 21:58

      И ещё есть DigiSpark, тоже недорогой, и суть та же.


    1. Nikita_Kras Автор
      04.05.2019 22:11
      +1

      Вы абсолютно правы в том, что есть другие более оптимальные решения, на которые не будет потрачено так много времени и сил. Пляски с Processing-ом нужны только тем, кто работает в полевых условиях и имеет под рукой только какие то Ардуины Uno/Nano и подобные. То есть это инструкция для тех, кто работает с тем, что есть.

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


      1. tmin10
        05.05.2019 11:20

        Вроде де есть возможность перепрошить интерфейсный контроллер, чтобы была эмуляция USB HID устройства: https://m.vk.com/page-101309898_49952596


  1. Serge78rus
    04.05.2019 19:31

    И проводить Аналого-цифровое преобразование(ADC или АЦП), получая числа от 0 до 1023 (в Arduino Uno 8-ми разрядный АЦП) в зависимости от положения потенциометра.
    Для получения диапазона 0...1023 нужен 10-ти разрядный АЦП, и именно такой реализован в atmega328, на котором построен Arduino UNO, а вовсе не 8-ми разрядный


    1. Nikita_Kras Автор
      04.05.2019 22:15

      Спасибо за вашу бдительность!


  1. crustal
    04.05.2019 19:44
    +4

    Всем привет! В интернете бытует заблуждение, что для управления компьютером при помощи самодельной электроники нужны только специальные платы, которые могут распознаваться как USB HID устройства.
    Может где-то и бытует, но мне о таком заблуждении не известно. Буду знать, спасибо. Еще с древних времен, когда пики были без буквы F обычно придумывалась логика двустороннего обмена байтами между микроконтроллером и PC для конкретной задачи, в которую входили команды как со стороны микроконтроллера для выполнения компьютером, так и команды со стороны компьютера для выполнения микроконтроллером. А уж на чем вы будете писать код, кому какое дело, хоть на MS Visual Studio на стороне компьютера, а на стороне микроконтроллера — в любой какой нравится среде. Хочется рекламировать Arduino как платформу и Processing как язык, или что оно там есть? Почему нет? Только название статьи как бы должно соответствовать.


  1. sav13
    04.05.2019 20:27
    +2

    То что вы освоили обмен между Ардуино и ПК через UART интерфейс — это безусловно достижение.
    Только при чем здесь сравнение с платами Leonardo? Микроконтроллеры XXXU4 могут эмулировать стандартные HID устройства операционной системы, что позволяет их использовать без каких бы то не было программ и даже дополнительных драйверов со стороны ПК.
    У вас же куча кода для обмена, в пакете, который даже сервисом не оформишь


    1. Nikita_Kras Автор
      04.05.2019 22:43
      -1

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

      Спасибо за ваш комментарий.


  1. FGV
    04.05.2019 20:32
    +1

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

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


    1. DmitriyN
      04.05.2019 22:09

      Ну, стоит отметить, что все-таки для втекающего тока у atmega328 эквивалентное сопротивление пина чуть меньше, чем для вытекающего. :)


      1. FGV
        04.05.2019 23:36

        Вот! Ну тогда пишите о разнице сопротивлений (хотя при дополнительных 200 ом сколь там добавится? .5%?), со ссылкой на даташит.


        1. DmitriyN
          04.05.2019 23:58

          А это, в свою очередь, очень сильно зависит от условий. Согласно даташиту, при TC будет около 15%.
          http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf, стр. 271.


          1. olartamonov
            05.05.2019 00:16
            +1

            Я, наверное, плохо умею читать графики, но для 5 В питания, светодиода с падением 2,2 В и резистора 200 Ом:

            1) втекающий ток — (5-0,3-2,2)/200 = 12,5 мА
            2) вытекающий ток — (4,65 — 2,2)/200 = 12,25 мА

            Больше похоже на 2 %, чем на 15 %.

            И да, практику подключения светодиодов к плюсу питания вместо земли придумали, потому что:
            1) у микроконтроллера обычно ног земли больше, чем питания, пусть лучше через них течёт
            2) полно чипов, у которых на выходе OD или weak pull-up

            При этом п. 1 может не иметь смысла, если вам нужно обеспечить максимальную стабильность «земли», а светодиодов много, а п. 2 не относится к микроконтроллерам.

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


            1. DmitriyN
              05.05.2019 00:30

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

              К тому же, я никоим образом не оправдывал ту глупость, что написана в статье — если Вам так показалось, то советую внимательнее читать. Я лишь заметил, что, строго говоря, CMOS IO буферы не могут вести себя одинаково в обе стороны.

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


            1. VT100
              05.05.2019 19:15

              Скорее — это идёт со времён TTL микросхем с резко несимметричной нагрузочной способностью по втекающему и вытекающему току. Засветить ими СИД подключенный к земле можно, но грустно.


              1. olartamonov
                05.05.2019 19:33

                Это совсем уже историческое. Я про то, почему такая рекомендация может встречаться в современных источниках (не считая ардуинщиков).


                1. VT100
                  05.05.2019 21:54

                  Шаманская традиция-же!


    1. Nikita_Kras Автор
      04.05.2019 23:24

      Давайте закроем глаза и представим, что ничего не было? (гы)

      Спасибо за ваш комментарий.


  1. Andy_Big
    04.05.2019 20:57
    +3

    Откуда это нашествие ардуинщиков, да еще и с минимальным багажом знаний по темам постов/переводов, которые они пишут?


    1. Nikita_Kras Автор
      04.05.2019 22:57
      -1

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

      Если вы можете покритиковать публикацию — это поможет сделать ее лучше. Или улучшить уже следующие, если будут)


      1. olartamonov
        04.05.2019 23:03

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


        1. Nikita_Kras Автор
          04.05.2019 23:10
          -1

          В чем то я с вами согласен.


          1. olartamonov
            04.05.2019 23:33

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

            Примеров, когда вторая статья становилась последней, Хабр знает много.


            1. Nikita_Kras Автор
              04.05.2019 23:53

              Спасибо за совет. Приму к сведению.


  1. barbos6
    04.05.2019 22:34
    -1

    ассортимент редакторов для программирования микроконтроллеров

    Оказывается, годным редактором можно и мелкоконтроллер запрограммировать.
    А я думал, что редакторы обычно новости на хабре публикуют.
    Век живи, век учись , так дураком и помрёшь… :)


    1. Nikita_Kras Автор
      04.05.2019 23:00

      Спасибо, сейчас исправлю))


  1. bezev
    04.05.2019 23:10

    из соображений, что вывод питания может давать больший ток, чем обычный пин
    вывод питания то даёт, но ограничения вывода 11 не зависят от направления тока.
    «DC Current per I/O Pin = 40.0 mA» и «ThePort B output buffers have symmetrical drive characteristics»


    1. Nikita_Kras Автор
      04.05.2019 23:26

      Вы правы, спасибо что написали об этом. Нашел свои ошибки и исправил. Статью, правда, это уже не спасет… Ее ничто уже не спасет, будем честны :)
      Но для себя я отметил.


  1. Misery
    05.05.2019 13:05

    Programm is begin


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


    1. VT100
      05.05.2019 19:13

      Эта статья — перевод?


      1. Nikita_Kras Автор
        05.05.2019 19:50

        Нет, статья моя. Как и все коды, в которых допущены ошибки.


  1. VT100
    05.05.2019 19:22

    Коли желаете советы — не пренебрегайте схемотехникой.

    На схеме светодиод анодом подключен к 5V через ограничивающий резистор ( минимум 220 Ом, желательно 500 Ом), катодом к пину D11

    Кто-нибудь может воспринять цифру «500» буквально, а этого номинала нет в ряду номиналов для точности 5%. Ближайшие — 470 и 510 Ом.


    1. r00tGER
      06.05.2019 10:35

      цифру «500» буквально

      Вспомнилась бородатая история, как препод отравил студента искать в магазине резистор, что то вроде «1352,34 Ом», после того, как увидел его в курсовой работе.