Кухонный робототехник часть 2 или еще один аватар на Blynk -1



Вступление. Лирика, можно и пропустить


И снова здравствуйте! В этом повествовании я бы хотел продолжить тему домашнего «роботостроения», это своего рода продолжение предыдущего поста.

Сразу предупрежу: Я не специалист в данной отрасли, только учусь и долго так сказать «стеснялся» излагать свои мысли и выкладывать самоделки здесь. Давно читаю публикации на Хабре, иногда диву даешься от того, что в мире творится! Читаешь бывает пост, не понимаешь, от куда автор все это знает! Как в этом вообще можно разобраться! В свете всего этого думал, что мое «корявое» изложение будет ни кому не интересно, однако предыдущий рассказ посмотрели более 9к человек, для меня безусловно это успех, думаю многие, как и я «обычные люди», без дипломов " Массачусетского технологического института", поэтому эта инфа им доступнее. Итак поехали…

Переделка конструкции


За основу своего проекта я взял предыдущее «творение», чупокабру, который был наряжен и под свето-звуковое представление, вел праздник. Делал я его ради одного дня и из подручных средств, что прослеживается в его внешнем виде, представление носило сатирический характер. Выглядело примерно так:


Прежде всего я убрал все лишнее, купил алюминиевых профилей и перфорированных уголков для новой конструкции.

Мне очень понравилось исполнение робота телеприсутствия от забугорной компании «Origibot»

image

image

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

Ну да ладно, вдохновились и вперед! Переделывая «своего», решил разделить производство поэтапно:

  1. Изготовление колесной базы на основе двух ведущих колес и одного поддерживающего сзади.
  2. Написание скетча для управления.
  3. Монтаж камеры или телефона. Высоту размещения выбирал исходя из высоты плиты на кухне, чтобы можно было заглянуть в кастрюлю и посмотреть из чего сварен борщ.
  4. Установка сервопривода на камеру для того чтобы можно было смотреть под «ноги» и на потолок. Заморачиваться с горизонтальным перемещением не стал, поворачивать можно базой.
  5. Установка на «изделие» манипулятора с захватом.

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

С первым пунктом проблем не возникло, база была готова из предыдущей поделки, убрал все лишнее, роутер TPlink MR3020 с самодельной выносной антенной. В предыдущем выступлении были «толстые» стены, пришлось колхозить, результат +9db. На роутер приходили команды управления движением, прошивка cyberWRT.

Со вторым пунктом было интереснее, мой уровень в ардуино программировании «Светодиодный мигальщик», на просторах интернета нашел готовый скетч, к сожалению автора не помню, не много переделал его под себя методом научного тыка, им пользуются те кто слабо понимает теорию. Так как программирование на языках даже низшего уровня для меня темный лес, нужно было выбрать платформу, конструктор на котором можно было бы собрать свой пульт управления. Важным аспектом для меня была возможность управлять роботом через облачный сервис, так как управление будет из под «серых адресов». Такой платформой стал сервис Blynk, описывать достоинства не буду, в сети много информации. В первом варианте использовал виджет джойстик для управления движением, код приведен ниже:

#define BLYNK_PRINT Serial
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>
 #include <AccelStepper.h>
#include <Servo.h>
#include <SimpleTimer.h>
// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char auth[] = "****";
// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "****";
char pass[] = "****";
#define EspSerial Serial3
#define ESP8266_BAUD 115200

ESP8266 wifi(&EspSerial);
BlynkTimer timer;
Servo servo; 
Servo servo2;

// These are used to set the direction of the bridge driver.
#define ENB 5      //ENB В моем случае драйвер двигателя monstr motor shield пины 5 и 6 ШИМом регулируют скорость двигателя
#define MOTORB_1 4 //IN3
#define MOTORB_2 9 //IN4
#define MOTORA_1 8 //IN1
#define MOTORA_2 7 //IN2
#define ENA 6      //ENA

int motor_right_speed = 0;
int motor_left_speed = 0;

AccelStepper Stepper1(4, 10, 11, 12, 13); //4 тип двигателя, дальше пины к которому подключен
     int steeps = 192;              // количество шагов, ну уверен что надо, но пусть будет
    
// SETUP
void setup()
{
    Serial.begin(9600);
    delay(10);
EspSerial.begin(ESP8266_BAUD);
delay(10);
  // Connect Blynk
  Blynk.begin(auth, wifi, ssid, pass);
  Blynk.connect();
    // Configure pins
  pinMode(ENA, OUTPUT);
  pinMode(MOTORA_1, OUTPUT);
  pinMode(MOTORA_2, OUTPUT);
  pinMode(ENB, OUTPUT);
  pinMode(MOTORB_1, OUTPUT);
  pinMode(MOTORB_2, OUTPUT);
  digitalWrite(ENA,LOW);
  digitalWrite(ENB,LOW);
    Stepper1.setMaxSpeed(10000);     //устанавливаем максимальную скорость вращения ротора двигателя (шагов/секунду)
  Stepper1.setAcceleration(10000);  //устанавливаем ускорение (шагов/секунду^2)
  
  servo.attach(1);
  servo2.attach(3);
  servo2.write(55);
  timer.setInterval(500L, StopServo);
  // Start serial communication
 
}
BLYNK_WRITE(V1) /с помощью виджета кнопка работал захват, два положения, открыт/закрыт 
{
  if (param.asInt() == 1) {
    servo.attach(1);
    servo.write(165); // High gear
    timer.setTimeout(500L, StopServo);
  } else {
    servo.attach(1);
    servo.write(115);  // Low gear
    timer.setTimeout(500L, StopServo);
  }
}
BLYNK_WRITE(V2)
{
  servo2.write(param.asInt());
}
void StopServo() /поворт камеры
{
  servo.detach();
}
// JOYSTICK
BLYNK_WRITE(V0) {
  int nJoyY = param[0].asInt(); // read x-joystick
  int nJoyX = param[1].asInt(); // read y-joystick
  
  // OUTPUTS
  int nMotMixL; // Motor (left) mixed output
  int nMotMixR; // Motor (right) mixed output

  // CONFIG
  // - fPivYLimt  : The threshold at which the pivot action starts
  //                This threshold is measured in units on the Y-axis
  //                away from the X-axis (Y=0). A greater value will assign
  //                more of the joystick's range to pivot actions.
  //                Allowable range: (0..+127)
  float fPivYLimit = 1023.0;
      
  // TEMP VARIABLES
  float   nMotPremixL;    // Motor (left) premixed output
  float   nMotPremixR;    // Motor (right) premixed output
  int     nPivSpeed;      // Pivot Speed
  float   fPivScale;      // Balance scale between drive and pivot

  // Calculate Drive Turn output due to Joystick X input
  if (nJoyY >= 0) {
    // Forward
    nMotPremixL = (nJoyX>=0)? 1023.0 : (1023.0 + nJoyX);
    nMotPremixR = (nJoyX>=0)? (1023.0 - nJoyX) : 1023.0;
  } else {
    // Reverse
    nMotPremixL = (nJoyX>=0)? (1023.0 - nJoyX) : 1023.0;
    nMotPremixR = (nJoyX>=0)? 1023.0 : (1023.0 + nJoyX);
  }

  // Scale Drive output due to Joystick Y input (throttle)
  nMotPremixL = nMotPremixL * nJoyY/900.0; // поправка для равномерного движения вперед
  nMotPremixR = nMotPremixR * nJoyY/1023.0;

  // Now calculate pivot amount
  // - Strength of pivot (nPivSpeed) based on Joystick X input
  // - Blending of pivot vs drive (fPivScale) based on Joystick Y input
  nPivSpeed = nJoyX;
  fPivScale = (abs(nJoyY)>fPivYLimit)? 0.0 : (1.0 - abs(nJoyY)/fPivYLimit);

  // Calculate final mix of Drive and Pivot
  nMotMixL = (1.0-fPivScale)*nMotPremixL + fPivScale*( nPivSpeed);
  nMotMixR = (1.0-fPivScale)*nMotPremixR + fPivScale*(-nPivSpeed)/ 1.1;
    motor_left_speed = nMotMixL;
  motor_right_speed = nMotMixR;
    
 if (motor_right_speed > 20) {
    digitalWrite(MOTORA_1,HIGH);
    digitalWrite(MOTORA_2,LOW);
  }
  else if (motor_right_speed < -20) {
    digitalWrite(MOTORA_1,LOW);
    digitalWrite(MOTORA_2, HIGH);  
  }
  else {
    digitalWrite(MOTORA_1, LOW);
    digitalWrite(MOTORA_2, LOW);
  }
  if (motor_left_speed > 20) {
    digitalWrite(MOTORB_1, LOW);
    digitalWrite(MOTORB_2, HIGH);
  }
  else if (motor_left_speed < -20) {
    digitalWrite(MOTORB_1,HIGH);
    digitalWrite(MOTORB_2,LOW);
  }
  else {
    digitalWrite(MOTORB_1, LOW);
    digitalWrite(MOTORB_2, LOW);
  }
  analogWrite(ENB, abs(motor_left_speed));
  analogWrite(ENA, abs(motor_right_speed));  
}
BLYNK_WRITE(V3) {  // Motor Speed - Slider set with 0-100 and Send On Relese OFF
  int pinValue = param.asInt();
  Stepper1.move(pinValue);  
  }
// MAIN CODE
void loop()
{
  Blynk.run();
  timer.run();
   Stepper1.run();
}


Далее монтаж камеры или телефона, пробовал и то и другое.

image

На фото манипулятор стоит отдельно.

Теперь пришло время испытаний! Воспользовался возможностью разделения экрана на телефоне (доступно в последних прошивках android), верхняя часть видео, низ управление. Чтобы запускать все одновременно скачал с Play Market приложение «Screens» и поехали…


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

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

Если вы обратили внимание на фото «Origibot», там в качестве привода стоит что-то страшное, на их сайте я прочитал что манипулятор способен поднять до 1 кг, фантастика!
Если применить к примеру сервопривод с усилием 20кг/см, то на расстоянии 2 см можно поднять нагрузку 10 кг, а если вес этой нагрузки неравномерно распределен по оси и ось около метра, то и еще меньше.

Хорошо что под рукой оказалась старая поворотная камера видеонаблюдения, из нее я извлек два шаговых двигателя, отлично! Один двигатель будет поднимать ось, а второй использую на атоматическую штору. Давно хотел дома сделать, а заодно разобраться как работать с шаговиками! Так сказать двух зайцев, сразу. Ага! Главная «беда» ждала впереди, я забыл сказать, что для своей самоделки использовал контроллер Wemos D1 R1, внешне схож с Arduino Uno, только на борту есть wifi. Пока не было манипулятора, пинов платы хватало, добавил шаговый двигатель, для управления обмотками нужно четыре пина, где взять? Оказалось, что пины платы, это пины самой есп, которая обеспечивает связь, пины с 0-ого по 8-ой дальше повторяются. Ну что же, как говорится: «Дурная голова рукам покоя не дает!»
Цена ошибки — перенос результатов работы на плату Wemos 2560 esp8266 эта плата представляет из себя обычную Мегу и Есп. Брал для автоматизации теплицы, конечно же сразу не пошло, скетч не коомпелировался, вылетали ошибки. Решение нашел на сайте:
community.alexgyver.ru/threads/robotdyn-mega-wifi-r3-connect-blynk.1270/#post-16746
Ниже приведен скетч для Меги с кнопочным управлением:

#define BLYNK_PRINT Serial
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>
#include <AccelStepper.h>
#include <Servo.h>
#include <SimpleTimer.h>

#define EspSerial Serial3
#define ESP8266_BAUD 115200
ESP8266 wifi(&EspSerial);

#define ENB 5      //ENB В моем случае драйвер двигателя monstr motor shield пины 5 и 6 ШИМом регулируют скорость двигателя
#define MOTORB_1 4 //IN3
#define MOTORB_2 9 //IN4
#define MOTORA_1 8 //IN1
#define MOTORA_2 7 //IN2
#define ENA 6      //ENA
BlynkTimer timer;
Servo servo; 
Servo servo2;

AccelStepper Stepper1(4, 10, 11, 12, 13); //4 тип двигателя, дальше пины к которому подключен
 
char auth[] = "****";
char ssid[] = "****";
char pass[] = "****";   

// SETUP
void setup()
{
Serial.begin(9600);
delay(10);
EspSerial.begin(ESP8266_BAUD);
delay(10);
  // Connect Blynk
  Blynk.begin(auth, wifi, ssid, pass);
  Blynk.connect();
  // Configure pins
  pinMode(ENA, OUTPUT);
  pinMode(MOTORA_1, OUTPUT);
  pinMode(MOTORA_2, OUTPUT);
  pinMode(ENB, OUTPUT);
  pinMode(MOTORB_1, OUTPUT);
  pinMode(MOTORB_2, OUTPUT);
  
  Stepper1.setMaxSpeed(100);     //устанавливаем максимальную скорость вращения ротора двигателя (шагов/секунду)
  Stepper1.setAcceleration(96);  //устанавливаем ускорение (шагов/секунду^2) 
  servo.attach(2); // подключение захвата
  servo.write(115); // начальное положение захвата
  servo2.attach(3); // камера
  servo2.write(55); // начальное положение камеры
  //timer.setInterval(500L, StopServo);
  // Start serial communication  
}
BLYNK_WRITE(V6) 
{
 servo.write(param.asInt());
 //timer.setTimeout(2500L, StopServo);
}
BLYNK_WRITE(V7)
{
  servo2.write(param.asInt());
}
//void StopServo() {
 // servo.detach();
//}
BLYNK_WRITE(V8) {  
  int pinValue = param.asInt();
   Stepper1.move(pinValue);
      }
BLYNK_WRITE(V4) 
{
        int speedL = param.asInt(); // подключаю к слайдеру регулирование скорости двигателей. 1,13 поправочный коэффициент чтобы колеса крутились равномерно
        analogWrite(ENA, speedL);      
        analogWrite(ENB, speedL* 1.13);
} 
  / FORWARD
BLYNK_WRITE(V0) 
{
  int button = param.asInt(); // read button
  if (button == 1) {
   
    digitalWrite(MOTORA_1,HIGH);
    digitalWrite(MOTORA_2,LOW);
    digitalWrite(MOTORB_1,HIGH);
    digitalWrite(MOTORB_2,LOW);
  }
  else {
      
    digitalWrite(MOTORA_1,LOW);
    digitalWrite(MOTORA_2,LOW);
    digitalWrite(MOTORB_1,LOW);
    digitalWrite(MOTORB_2,LOW);
  }
}

// RIGHT
BLYNK_WRITE(V1) {
  int button = param.asInt(); // read button
  if (button == 1) {
       
    digitalWrite(MOTORA_1,LOW);
    digitalWrite(MOTORA_2,HIGH);
    digitalWrite(MOTORB_1,HIGH);
    digitalWrite(MOTORB_2,LOW);
  }
  else {
    digitalWrite(MOTORA_1,LOW);
    digitalWrite(MOTORA_2,LOW);
    digitalWrite(MOTORB_1,LOW);
    digitalWrite(MOTORB_2,LOW);
  }
}

// LEFT
BLYNK_WRITE(V2) {
  int button = param.asInt(); // read button
  if (button == 1) {
    digitalWrite(MOTORA_1,HIGH);
    digitalWrite(MOTORA_2,LOW);
    digitalWrite(MOTORB_1,LOW);
    digitalWrite(MOTORB_2,HIGH);
  }
  else {
    digitalWrite(MOTORA_1,LOW);
    digitalWrite(MOTORA_2,LOW);
    digitalWrite(MOTORB_1,LOW);
    digitalWrite(MOTORB_2,LOW);
  }
}
// BACKWARD
BLYNK_WRITE(V3) {
  int button = param.asInt(); // read button
  if (button == 1) {     
    digitalWrite(MOTORA_1,LOW);
    digitalWrite(MOTORA_2,HIGH);
    digitalWrite(MOTORB_1,LOW);
    digitalWrite(MOTORB_2,HIGH);
  }
  else {    
    digitalWrite(MOTORA_1,LOW);
    digitalWrite(MOTORA_2,LOW);
    digitalWrite(MOTORB_1,LOW);
    digitalWrite(MOTORB_2,LOW);
}
}
// MAIN CODE
void loop()
{
  Blynk.run();
  timer.run();
  Stepper1.run();


В итоге мощность китайского ноунейма оказалась еще меньше — 5кг/см, решил пробовать через ремень:

image

Ремень проскальзывал не смотря на приделанный ролик натяжитель (На фото нет). Исключительный психоз и нервопатия вызванная чередой технических неудач прервалась новой идеей. Необходимо уменьшить вес конструкции — отказаться от двух сервоприводов на конце оси, для дополнительных степеней свободы и уменьшить длину оси манипулятора. (Надеюсь запал останется и это решение временно).

image

Также на фото установил выносной динамик, который припаял к камере ds-2cd2432f-iw производитель Хиквижн. Штатный динамик показался слабеньким.

Теперь внешний вид следующий.

image

Итоги


Имеем камеру на колесах, почему пока отказался от телефона. Skype не корректно работает в режиме нескольких окон на моем телефоне, при управлении видеоизображение сворачивается до размера маленького окна, что не очень удобно при движении робота. Поэтому остановился пока на камере, для просмотра видео все также использую платную версию программы «Tiny cam pro», доступ из любой части мира, камера подключена по беспроводной сети к роутеру на котором проброшены ее порты и используется сервис доменных имен No-Ip. Хорошо что провайдер пока предоставляет «белый » адрес. Почему я не использую Blynk для передачи видео, спросите вы, ведь там есть штатный виджет для передачи rtsp протокола. Отвечаю, хочу использовать весь функционал камеры, а именно двустороннюю связь. Пусть даже в таком виде как ее предоставляет приложение, в котором используются аудиокодеки, сильно снижающие качество звука. Самое главное протокол rtsp в приложении Blynk работает с небольшой задержкой, что критично для поездок на механизме который весит 10 кг.

Питание, отдельная интересная тема. Вопрос автономной зарядной станции пока не решен, не успел. На предыдущей модели использовал Li — ion батарей спаянные попарно параллельно, для увеличения напряжения до 12В и увеличения емкости, работающие через контроллер заряда. В этом проекте от лития отказался, больно страшно взрываются батареи, из неудачного опыта! Аккумулятор свинцово — кислотный 7А/ч установлен в кормовой части агрегата, для равномерного распределения нагрузки при подъеме груза манипулятором, К аккумулятору подключен dc преобразователь, который уменьшает напряжение 5В, для питания контроллера и логики двух драйверов двигателей MonstrmotorShield по одному на каждое колесо, потому как в обоих платах работала только пол драйвера, скорее всего заводской брак, ума дать не смог, ну не выкидывать же?.. Драйвер двигателя ln298 нужен для работы двух шаговых двигателей.

Из пока еще не решенных проблем, автономная зарядка, хочу повторить так



Так же не совсем разобрался с библиотекой AccelStepper, если подать питание на обмотки двигателей, после окончания работы манипулятора, питание остается, что увеличивает ток разряда батареи примерно на 400мА, что не экономно, необходимо придумать логику когда манипулятор в нижнем положении, питание обмоток отключается. Также хотелось бы обратную связь, чтобы положение сервоприводов и шаговиков приходили в Blynk при включении робота. Ну и конечно же аккумулятор должен отключать нагрузку при падении напряжения до 10.5В, что предотвратит его преждевременную деградацию. Нет возможности управлять с компьютера. Ну и конечно коду требуется оптимизация.

Тем кто не боится многАбукав и дочитал до конца, спасибо, всем удачи, всем добра!
Напоследок немного напакостим!




К сожалению, не доступен сервер mySQL