Всем привет! В интернете бытует заблуждение, что для управления компьютером при помощи самодельной электроники нужны только специальные платы, которые могут распознаваться как USB HID устройства. А касаемо Arduino все только и говорят о Arduino Leanardo. Такие популярные библиотеки как Keyboard и Mouse, которые позволяют создавать эмуляцию работы мыши или клавиатуры посредством микроконтроллера предназначены только для пары плат Arduino, Leonardo в их числе.
Я расскажу о том, как наладить связь любого микроконтроллера Arduino (для примера взята Arduino Uno) и своей программы на Processing. Добавив ко всему прочему знания о Java, на котором основывается Processing, можно будет дописать проект под управление всем компьютером, а не только собственным приложением. Тема управления компьютером программой на Java не есть чем то секретным, погуглите и все найдете, уверяю вас.
Скачиваем необходимые редакторы
Существует большущий ассортимент редакторов для программирования микроконтроллеров на чистом Си. Из них можно отметить самые удобные: Atollic, Eclipse, Keil.
Однако для простоты и доступности данного руководства я буду использовать редактор Arduino IDE и писать на Ардуино Си. Скачать такой редактор можно с официального сайта Arduino.
Редактор для программирования на Procrssing так же качается с официального сайта.
Стоит отметить, приличия ради, что данные редакторы очень похожи, потому что написаны на одном движке. И когда создавался Arduino основатели старались как можно больше упростить свой редактор кода, как это и было сделано в Processing редакторе.
Arduino. Собираем схему и пишем код
В данном примере я буду использовать Arduino Uno. К ней будет подключена кнопка, потенциометр и светодиод. Соответственно я могу выдавать логический 0 или 1. Читать логический 0 или 1. И проводить Аналого-цифровое преобразование(ADC или АЦП), получая числа от 0 до 1023 (в Arduino Uno 8-ми разрядный АЦП) в зависимости от положения потенциометра. Большего для примера и не нужно, так как это основные функции, которые может делать микроконтроллер.
Схема подключения:
На схеме светодиод анодом подключен к 5V через ограничивающий резистор ( минимум 220 Ом, желательно 500 Ом), катодом к пину D11. Кнопка замыкает землю и пин D2. Потенциометр меняет потенциал на пине A1.
Задача микроконтроллера следующая: Если по последовательному интерфейсу (Serial COM port) приходит сообщение «LED — H» — засветить светодиод. Если приходит сообщение «LED — L» — затушить светодиод. Каждые 250мс отправлять сообщение в последовательный порт (в данном случае на экран компьютера) сообщение «Pot — » и число, полученное аналоговым чтением пина A1. При нажатии кнопки единожды отсылать сообщение «Button is pressed!».
Вот мое предложение решения данной задачи (не пример для подражания):
#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("Programm is begin.nn");
}
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 мне очень помогли вот эти сайты:
Автор: Nikita_Kras