Я расскажу о том, как наладить связь любого микроконтроллера 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!».
Вот мое предложение решения данной задачи (не пример для подражания):
#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 мне очень помогли вот эти сайты:
Комментарии (34)
Serge78rus
04.05.2019 19:31И проводить Аналого-цифровое преобразование(ADC или АЦП), получая числа от 0 до 1023 (в Arduino Uno 8-ми разрядный АЦП) в зависимости от положения потенциометра.
Для получения диапазона 0...1023 нужен 10-ти разрядный АЦП, и именно такой реализован в atmega328, на котором построен Arduino UNO, а вовсе не 8-ми разрядный
crustal
04.05.2019 19:44+4Всем привет! В интернете бытует заблуждение, что для управления компьютером при помощи самодельной электроники нужны только специальные платы, которые могут распознаваться как USB HID устройства.
Может где-то и бытует, но мне о таком заблуждении не известно. Буду знать, спасибо. Еще с древних времен, когда пики были без буквы F обычно придумывалась логика двустороннего обмена байтами между микроконтроллером и PC для конкретной задачи, в которую входили команды как со стороны микроконтроллера для выполнения компьютером, так и команды со стороны компьютера для выполнения микроконтроллером. А уж на чем вы будете писать код, кому какое дело, хоть на MS Visual Studio на стороне компьютера, а на стороне микроконтроллера — в любой какой нравится среде. Хочется рекламировать Arduino как платформу и Processing как язык, или что оно там есть? Почему нет? Только название статьи как бы должно соответствовать.
sav13
04.05.2019 20:27+2То что вы освоили обмен между Ардуино и ПК через UART интерфейс — это безусловно достижение.
Только при чем здесь сравнение с платами Leonardo? Микроконтроллеры XXXU4 могут эмулировать стандартные HID устройства операционной системы, что позволяет их использовать без каких бы то не было программ и даже дополнительных драйверов со стороны ПК.
У вас же куча кода для обмена, в пакете, который даже сервисом не оформишьNikita_Kras Автор
04.05.2019 22:43-1Конечно с HID устройством все будет работать напрямую, быстрее и проще. Тут нет никакого смысла открывать спор. Я просто показал еще один метод как можно обойтись без покупки платы Leonardo, если требуется просто передача данных между микроконтроллером и компьютером.
Спасибо за ваш комментарий.
FGV
04.05.2019 20:32+1Светодиод подключен анодом к питаю из соображений, что вывод питания может давать больший ток, чем обычный пин микроконтроллера.
Мну теряюсь в догадках, куда же стекает ток бегущий через светодиод? уж не через тот же пин микроконтроллера на землю?
DmitriyN
04.05.2019 22:09Ну, стоит отметить, что все-таки для втекающего тока у atmega328 эквивалентное сопротивление пина чуть меньше, чем для вытекающего. :)
FGV
04.05.2019 23:36Вот! Ну тогда пишите о разнице сопротивлений (хотя при дополнительных 200 ом сколь там добавится? .5%?), со ссылкой на даташит.
DmitriyN
04.05.2019 23:58А это, в свою очередь, очень сильно зависит от условий. Согласно даташиту, при TC будет около 15%.
http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf, стр. 271.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 % же вообще всё равно, куда включать светодиод. С точки зрения логики программирования на землю интуитивно понятнее — зажигается единицей.DmitriyN
05.05.2019 00:30Вы абсолютно правы, я имел ввиду, что типичная разница в выходном сопротивлении IO драйвера 15%. Про ток в данном конкретном случае я ничего не говорил. Очевидно, что здесь вообще нет никаких причин смотреть на выходное сопротивление, поскольку потом все равно запихивается балластный резистор.
К тому же, я никоим образом не оправдывал ту глупость, что написана в статье — если Вам так показалось, то советую внимательнее читать. Я лишь заметил, что, строго говоря, CMOS IO буферы не могут вести себя одинаково в обе стороны.
Что же касается того, что ног земли больше, чем ног питания — не знаю, как в атмегах, но очень часто разные ноги земли не присоединяются на чипе в один общий пауэргрид, а питают отдельно разные вещи.
VT100
05.05.2019 19:15Скорее — это идёт со времён TTL микросхем с резко несимметричной нагрузочной способностью по втекающему и вытекающему току. Засветить ими СИД подключенный к земле можно, но грустно.
olartamonov
05.05.2019 19:33Это совсем уже историческое. Я про то, почему такая рекомендация может встречаться в современных источниках (не считая ардуинщиков).
Nikita_Kras Автор
04.05.2019 23:24Давайте закроем глаза и представим, что ничего не было? (гы)
Спасибо за ваш комментарий.
Andy_Big
04.05.2019 20:57+3Откуда это нашествие ардуинщиков, да еще и с минимальным багажом знаний по темам постов/переводов, которые они пишут?
Nikita_Kras Автор
04.05.2019 22:57-1Ардуино — это платформа для обучения. Логично что те, кто программирует такие микроконтроллеры — новички. Я такой же новичок, который разобрался как микроконтроллеру взаимодействовать с компьютером простым, как мне кажется, способом. Этим и поделился в статье.
Если вы можете покритиковать публикацию — это поможет сделать ее лучше. Или улучшить уже следующие, если будут)olartamonov
04.05.2019 23:03(в сторону) Судя по всему, пора привыкать, что Хабр — это сайт для общения новичков, делающих первые шаги в различных областях.
Nikita_Kras Автор
04.05.2019 23:10-1В чем то я с вами согласен.
olartamonov
04.05.2019 23:33Я очень снисходительно отношусь к статьям «из песочницы», все мы там были. Но, пожалуйста, теперь, когда у вас есть положительная карма и все нужные права — в следующий раз уделите написанию статьи больше времени и внимания.
Примеров, когда вторая статья становилась последней, Хабр знает много.
barbos6
04.05.2019 22:34-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»Nikita_Kras Автор
04.05.2019 23:26Вы правы, спасибо что написали об этом. Нашел свои ошибки и исправил. Статью, правда, это уже не спасет… Ее ничто уже не спасет, будем честны :)
Но для себя я отметил.
VT100
05.05.2019 19:22Коли желаете советы — не пренебрегайте схемотехникой.
На схеме светодиод анодом подключен к 5V через ограничивающий резистор ( минимум 220 Ом, желательно 500 Ом), катодом к пину D11
Кто-нибудь может воспринять цифру «500» буквально, а этого номинала нет в ряду номиналов для точности 5%. Ближайшие — 470 и 510 Ом.r00tGER
06.05.2019 10:35цифру «500» буквально
Вспомнилась бородатая история, как препод отравил студента искать в магазине резистор, что то вроде «1352,34 Ом», после того, как увидел его в курсовой работе.
little-brother
Не очень понятно зачем нужны эти пляски с Processing, когда есть ATtiny85 trinket (на али за ~75руб), которая без проблем программируется в Arduino IDE, шьется напрямую через USB и потом видна как клавиатура или мышка.
tormozedison
И ещё есть DigiSpark, тоже недорогой, и суть та же.
Nikita_Kras Автор
Вы абсолютно правы в том, что есть другие более оптимальные решения, на которые не будет потрачено так много времени и сил. Пляски с Processing-ом нужны только тем, кто работает в полевых условиях и имеет под рукой только какие то Ардуины Uno/Nano и подобные. То есть это инструкция для тех, кто работает с тем, что есть.
Вот как то в университете подходит молодой преподаватель и спрашивает у всех, не одолжат ли ему Леонарду, что бы показать на паре как взаимодействовать с клавиатурой. А леонарды нет и все, пропало)
tmin10
Вроде де есть возможность перепрошить интерфейсный контроллер, чтобы была эмуляция USB HID устройства: https://m.vk.com/page-101309898_49952596