Pull to refresh

Arduino и Processing. Как управлять микроконтроллером по COM порту. Двустороннее общение

Reading time 8 min
Views 38K
Всем привет! В интернете бытует заблуждение, что для управления компьютером при помощи самодельной электроники нужны только специальные платы, которые могут распознаваться как 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
Tags:
Hubs:
+8
Comments 34
Comments Comments 34

Articles