Это продолжение краткой истории о разработке боевого робота без интеллекта, краткое руководство по сжиганию контроллеров и Arduino 2560. О нашем участии в шоу «Бронебот: оченний разогрев». А так же мой опыт соединения Arduino и двух RC-приемников с шаговыми и коллекторными двигателями.
Под брендом GeekTimes
На съемной панели была наклейка Habrahabr. Лишившись этой панели для уменьшения веса, я брендировал робот в ручную. GT вперед!
Аккуратное подключение всех элементов системы
1-Arduino 2560
2-RC-приемник
3,4-Драйвера коллекторных двигателей
5-Блок реле
6-Батарея
Логика работы системы
Управление роботом(ровером) осуществляется средством радиоуправления двумя приемо-передающими устройствами. Первый пульт основного оператора используется для управления движением робота и орудиями, второй оператор дублирует управление орудиями. На ровере оба приемника соединены к Arduino, в которой программно считаем ШИМ от приемника, включаются/выключаются реле для управления клапанами пневмосистемы и выдается ШИМ для драйверов двигателей.
#include <math.h>
#define STEP_PIN1 12
#define STEP_PIN2 10
#define RELEPIN0 2
#define RELEPIN1 3
#define RELEPIN2 4
#define RELEPIN3 5
#define RELEPIN4 6
#define RELEPIN5 7
#define RELEPIN6 8
#define RELEPIN7 9
#define a0 = A0;
#define a1 = A1;
#define a2 = A2;
#define a3 = A3;
#define a4 = A4;
#define a5 = A5;
//Второй пульт
//Временного отключили второй пульт
//#define a6 = A8;
//#define a7 = A9;
int ch0,ch1,ch2,ch3,ch4,ch5,ch6,ch7;
//Задаем максимум и минимум для ШИМа с первого приемника
const int min0=1094;
const int min1=1094;
const int max0=813;
const int max1=813;
int speed1,speed2,speedmot1,speedmot2,sd1,sd2;
byte ch2st,ch3st,ch4st,ch5st,ch6st,ch7st;
byte tormozcount1=0;
byte tormozcount2=0;
byte wasdir1=2;
byte wasdir2=2;
byte skolkotormozit = 7;
int skolkotormozim=0;
void setup() {
Serial.begin(9600);
Serial.println("start");
pinMode(a0, INPUT);
pinMode(a1, INPUT);
pinMode(a2, INPUT);
pinMode(a3, INPUT);
pinMode(a4, INPUT);
pinMode(a5, INPUT);
//Временного отключили второй пульт
//pinMode(a6, INPUT);
//pinMode(a7, INPUT);
pinMode(STEP_PIN1, OUTPUT);
pinMode(STEP_PIN2, OUTPUT);
pinMode(RELEPIN0, OUTPUT);
pinMode(RELEPIN1, OUTPUT);
pinMode(RELEPIN2, OUTPUT);
pinMode(RELEPIN3, OUTPUT);
pinMode(RELEPIN4, OUTPUT);
pinMode(RELEPIN5, OUTPUT);
pinMode(RELEPIN6, OUTPUT);
pinMode(RELEPIN7, OUTPUT);
}
void loop() {
ch0 =pulseIn(a0, HIGH,30000);
if(ch0<900){
//Если нет сигнала с пульта
Serial.println("first no signal");
digitalWrite(STEP_PIN1,LOW);
digitalWrite(STEP_PIN2,LOW);
digitalWrite(RELEPIN0,LOW);
digitalWrite(RELEPIN1,LOW);
digitalWrite(RELEPIN2,LOW);
digitalWrite(RELEPIN3,LOW);
digitalWrite(RELEPIN4,LOW);
digitalWrite(RELEPIN5,LOW);
digitalWrite(RELEPIN6,LOW);
digitalWrite(RELEPIN7,LOW);
}
//Снимаем показания с ШИМа от первого приемника
ch1 =pulseIn(a1, HIGH);
ch2 =pulseIn(a2, HIGH);
ch3 =pulseIn(a3, HIGH);
ch4 =pulseIn(a4, HIGH);
ch5 =pulseIn(a5, HIGH);
//Временного отключили второй пульт
ch6 =1250; //pulseIn(a6, HIGH,2020);
ch7 =1250; //pulseIn(a7, HIGH,2020);
if(ch2>1500){
ch2st=1;
}else{
ch2st=0;
}
if(ch3>1500){
ch3st=0;
}else if(ch3>0){
ch3st=1;
}
if(ch4>1500){
ch4st=1;
}else{
ch4st=0;
}
if(ch5>1500){
ch5st=1;
}else{
ch5st=0;
}
//Каждый канал второго пульта управляет двумя орудиями
if(ch6>1800){
ch6st=2;
}else if(ch6<1200){
ch6st=1;
}else{
ch6st=0;
}
if(ch7>1800){
ch7st=2;
}else if(ch7<1200){
ch7st=1;
}else{
ch7st=0;
}
//Первое орудие----------------------------------------------
if(ch2st==1 or ch6st==1){
digitalWrite(RELEPIN0,HIGH);
}else{
digitalWrite(RELEPIN0,LOW);
}
//Второе орудие----------------------------------------------
if(ch3st==1 or ch6st==2){
digitalWrite(RELEPIN1,HIGH);
}else {
digitalWrite(RELEPIN1,LOW);
}
//Третье орудие----------------------------------------------
if(ch4st==1 or ch7st==1){
digitalWrite(RELEPIN2,HIGH);
}else {
digitalWrite(RELEPIN2,LOW);
}
//Если есть сигнал с первого пульта
if(ch0>0){
ch0=(ch0<55 && ch0>45)?50:ch0;
ch1=(ch1<55 && ch1>45)?50:ch1;
ch0 =ch0-min0;
ch1 =ch1-min1;
//Получаем данные о положении стиков управления от 0 до 100
ch0 =map(ch0,0,max0,0,100);
ch1 =100-map(ch1,0,max1,0,100);
//Превращаем данные о напралении движения в необходимые значения для каждого двигателя
speed1=round((ch0-50)*(abs(ch1-50)+50)/50);
speed2=round((ch1-50)*(abs(ch0-50)+50)/50);
//корректируем значения и считаем скорость моторов
speedmot1=speed1+speed2;
speedmot2=speed1-speed2;
// Выставляем ограничения
speedmot1=(speedmot1>100)?100:speedmot1;
speedmot2=(speedmot2>100)?100:speedmot2;
speedmot1=(speedmot1<-100)?-100:speedmot1;
speedmot2=(speedmot2<-100)?-100:speedmot2;
speedmot1=(speedmot1<20 && speedmot1>-20)?0:speedmot1;
speedmot2=(speedmot2<20 && speedmot2>-20)?0:speedmot2;
sd1=map(abs(speedmot1),0,100,0,255);
sd2=map(abs(speedmot2),0,100,0,255);
//Вывод значений для диагностики
Serial.print(ch0);
Serial.print(";");
Serial.print(ch1);
Serial.print(";");
Serial.print(";");
Serial.print(speed1);
Serial.print(";");
Serial.print(speed2);
Serial.print(";");
Serial.print(speedmot1);
Serial.print(";");
Serial.print(speedmot2);
Serial.print(";");
Serial.print(sd1);
Serial.print(";");
Serial.print(sd2);
Serial.print(";wasdir");
Serial.print(wasdir1);
Serial.print(";");
Serial.print(wasdir2);
Serial.println(";");
//Выдаем ШИМ для драйвера, если мы не в состоянии торможения
if(tormozcount1==0 and tormozcount2==0){
if(sd1<220){
analogWrite(STEP_PIN1,sd1);
}else{
digitalWrite(STEP_PIN1,HIGH);
}
if(sd2<220){
analogWrite(STEP_PIN2,sd2);
}else{
digitalWrite(STEP_PIN2,HIGH);
}
}else{
digitalWrite(STEP_PIN1,LOW);
digitalWrite(STEP_PIN2,LOW);
}
//торможение, выбег ротора
if(speedmot1<40 and speedmot1>-40){
Serial.println("Tormoz menshe 40 1 dvig;");
digitalWrite(RELEPIN3,HIGH);
digitalWrite(STEP_PIN1,LOW);
tormozcount1=0;
}else{
digitalWrite(RELEPIN3,LOW);
}
if(speedmot2<40 and speedmot2>-40 ){
Serial.println("Tormoz menshe 40 2 dvig;");
digitalWrite(RELEPIN4,HIGH);
digitalWrite(STEP_PIN2,LOW);
tormozcount2=0;
}else{
digitalWrite(RELEPIN4,LOW);
}
//Если мы тормозим больше циклов, чем нужно для тормоза считаем, что мы неподвижны
if(speedmot1<40 and speedmot1>-40 and speedmot2<40 and speedmot2>-40){
skolkotormozim++;
}else{
skolkotormozim=0;
}
if( skolkotormozim>skolkotormozit){
wasdir1=2;
wasdir2=2;
digitalWrite( RELEPIN5, LOW);
digitalWrite( RELEPIN6, LOW);
Serial.println("Stop for 10 cicles");
}
//смена направления хода
if((speedmot1 < -10 and wasdir1==1) || (speedmot1 >=5 and wasdir1==0)){
//Если новое направление не соответствует старому
if(tormozcount1<=0){
//если счетчик 0 тормозим
Serial.println("Tormoz vibeg rotora 1 dvig;");
digitalWrite(STEP_PIN1,LOW);
digitalWrite(RELEPIN3,HIGH);
tormozcount1=skolkotormozit;
}
if(tormozcount1>0){
//Если уже тормозим
digitalWrite(STEP_PIN1,LOW);
tormozcount1--;
}
if(tormozcount1==1){
//если оттормозили
tormozcount1--;
digitalWrite(RELEPIN3,LOW);
Serial.println("Change 1dvig ");
if(speedmot1 < 0){
wasdir1=0;
digitalWrite( RELEPIN5, HIGH);
Serial.println("vzad");
}else{
wasdir1=1;
digitalWrite( RELEPIN5, LOW);
Serial.println("vpered");
}
}
}
if((speedmot2 < -10 and wasdir2==1) || (speedmot2 >=5 and wasdir2==0)){
//Если новое направление не соответствует старому
if(tormozcount2<=0){
//если счетчик 0 тормозим
Serial.println("Tormoz vibeg rotora 2 dvig;");
digitalWrite(STEP_PIN2,LOW);
digitalWrite(RELEPIN4,HIGH);
tormozcount2=skolkotormozit;
}
if(tormozcount2>0){
//Если уже тормозим
digitalWrite(STEP_PIN2,LOW);
tormozcount2--;
}
if(tormozcount2==1){
//если оттормозили
tormozcount2--;
digitalWrite(RELEPIN4,LOW);
Serial.print("Change 2dvig ");
if(speedmot2 < 0){
wasdir2=0;
digitalWrite( RELEPIN6, HIGH);
Serial.println("vzad");
}else{
wasdir2=1;
digitalWrite( RELEPIN6, LOW);
Serial.println("vpered");
}
}
}
// Если долго никуда не ехали меняем направление сразу
if(speedmot1 < -40 and wasdir1==2){
wasdir1=0;
digitalWrite( RELEPIN5, HIGH);
Serial.println("Reverse 1 motor");
}
if(speedmot1 > 40 and wasdir1==2){
wasdir1=1;
digitalWrite( RELEPIN5, LOW);
Serial.println("Forward 1 motor");
}
if(speedmot2 < -40 and wasdir2==2){
wasdir2=0;
digitalWrite( RELEPIN6, HIGH);
Serial.println("Reverse 2 motor");
}
if(speedmot2 > 40 and wasdir2==2){
wasdir2=1;
digitalWrite( RELEPIN6, LOW);
Serial.println("Forward 2 motor");
}
/*
//Диагностика
Serial.print(tormozcount1);
Serial.print(";");
Serial.print(tormozcount2);
Serial.print(";");
Serial.print(wasdir1);
Serial.print(";");
Serial.print(wasdir2);
Serial.print(";");
*/
//delay(1000);
}else{
//Если нет сигнала с пульта
}
}
В первой версии робота мы использовали шаговые двигатели NEMA 43, для них скетч было написать несколько сложнее, т.к. приходилось либо подсчет ШИМа с приемников выводить в функцию по таймеру, либо управление ШД. Для этих целей я использовал библиотеку TimerOne.
#include <TimerOne.h>
В функции setup инициируем таймер и прикрепляем к нему функцию отправки на контроллер ШД пульса на совершение одного шага.
Timer1.initialize(500000); // initialize timer1, and set a 1/2 second period
Timer1.pwm(9, 512,300); // setup pwm on pin 9, 50% duty cycle
Timer1.attachInterrupt(callback);
Функция отправки импульса
void callback()
{
digitalWrite(STEP_PIN1, digitalRead(STEP_PIN1) ^ 1);
delayMicroseconds(10);
digitalWrite(STEP_PIN1, digitalRead(STEP_PIN1) ^ 1);
}
В ходе основного цикла программы меняем частоту таймера в зависимости от необходимой скорости.
Timer1.pwm(9, 512,speedmot1);
В случае остановки двигателя отключаем функцию от таймера
Timer1.detachInterrupt();
Конструкция и сжигание Arduino
Конструкцию продумывали и испытывали с целью создать жесткую конструкцию.
Мне кажется на кадре ниже зафиксирован момент, когда при использовании сварки кабель от Arduino 2560 попал на корпус. В результате этого у нас сгорел UART и мы не могли более производить перепрошивку через USB. Самым быстрым решением было перейти на Arduino Uno, которая оказалась под рукой. Отказались от пульта второго оператора, портов хватило в самый раз.
Тест пневмосистемы
Из всех планируемых орудий пришлось оставить только лифт, так как посчитали его самым эффективным. Пневмосистему тестировали от компрессора 6 атм. В боевом состоянии мы уже использовали баллон с CO2 и давлением после редуктора в 10 атм, так же заменили трубку на больший диаметр.
Тест орудия
Учимся ездить на роботе
На одном из таких тестов мы сожгли полевики на контроллерах. На другом залипли реле, переключающие реверс. Затем в код ардуино я ввел выбег ротора, некое время ожидания перед сменой направления движения двигателя. Робот стал хуже в управлении, но контролеры целые.
Уличные тесты робота
Epic Fail
Стоит сразу отметить, что у нас не было весов. Мы просто поднимали робота все вместе, никто не хотел говорить, что ему тяжело и мы все дружно кивали и соглашались, что робот легкий.
Организаторы, особенно хранители арены очень серьезно относятся к вопросу безопасности, многие роботы просто не прошли квалификацию перед боем. При взвешивании оказалось, что наш робот весит 161.5 кг., при максимально разрешнных 105. Питер Редмонд на отрез отказался выпускать нас на сцену. Мы вынуждены были снять всю пневмосистему, орудия, снять юбку, которая защищала нас от подбрасывания, снять защитную стенку и заменить ее на оргстекло из которого сделаны стенки арены. Из 6 аккумуляторов от 3 пришлось отказаться.
Мы прошли квалификацию, но на арене уже могли только таранить. Нас прозвали вытяжкой, а нашего противника холодильником. Наверное, когда говорят «Первый блин комом» имеют ввиду именно такой сценарий развития событий, но мы получили удовольствие.
Видео с Бронебота
Приятного просмотра видео с боя.
P.S.: Немного расстроила организация выступления, т.к. не было никакого внимания на команду, собиравшую робота и на операторов, которые управляли им. Мы готовили костюмы, хотели быть масонами с масонской пирамидой, а получилась просто вытяжка, которая ездила по сцене…
P.P.S.: Мы уже думали полностью менять корпус и концепцию робота к следующим соревнованиям, но «За одного битого двух небитых дают». С нашими шрамами от «Открывашки» корпус ценен как история. На корпусе подписи Питера Редмонда и других гостей из Ирландской Лиги, организаторов и других участников.
P.P.P.S.: Следите за нашей командой вк. Готовьтесь к конкурсу роботов-газонокосилок. На конкурс заявлено более 10 команд со всей России. Нам предложили проводить конкурс в Сколково.
Автор: Битва робокосилок