С последней статьи прошло достаточно много времени. Frank очень сильно изменился. Он, конечно, не стал более самостоятельным, но, можно с уверенностью сказать, что платформа для экспериментов почти готова, и я больше времени теперь буду проводить за написанием кода, нежели чем за конструктором Lego или паяльником.
Для тех, кто не читал последние статьи, я предлагаю с ними ознакомиться, пройдя по следующим ссылкам. Для тех, кто уже в курсе этого проекта — добро пожаловать под кат.
«Создание автономного робота Frank. Часть первая»
«Создание автономного робота Frank. Часть вторая»
Во-первых, полностью изменился принцип работы Френка. С четырех колес он пересел на четыре гусеницы, что значительно облегчило управление и написание кода. Из-за того, как был собран корпус и шасси, внутри просто не оставалось места для того, чтобы поместить туда электронику. Единственное, что помещалось внутрь — была 6v батарея и 4ре серво-мотора, которые вращали колеса и отвечали за угол поворота. Вторая попытка заключалась в том, чтобы полностью избавиться от поворотного механизма и перейти на управления за счет дифференцирования скорости колес. Эта затея тоже провалилась, так как трение между колесами и полом не позволяло колесам немного проскальзывать, и все, чего я добился при помощи дополнительных редукторов — были проскальзывающие шестеренки, которые в конце эксперимента стесались и сразу же оказались в мусорном ведре.
С гусеницами все оказалось проще. Не смотря на то, что резиновые гусеницы в Лего достаточно плохого качества, редуктор сделал свое дело. В гонках Френк участвовать, конечно, не сможет, но тащить за собой груз у него явно получится.
Итак, поместив все внутрь Френка и собрав все внутри одного корпуса я решил сделать несколько изменений. Во-первых, я все еще хотел экспериментировать с камерами и со стерео зрением. Во-вторых, надо было сделать что-то быстрое взамен камер, чтобы у него было хоть какое-то представление об окружающим мире. Так родилась его голова.
В голове я закрепил две камеры, которые пока никак не связаны с микросхемами. Так же туда залез ультразвуковой сенсор и серво-мотор, который позволяет голове вращаться.
Так как я обзавелся батареей, он стал полностью автономным, в том смысле, что он уже не должен быть подключен к ноутбуку по USB кабелю. Все общение происходит через последовательный беспроводной порт — XBee, о котором я писал раньше. Все это и многое другое заставило меня изменить микросхемы, которых теперь три, если считать Arduino Due.
Вот изменения, который произошли.
1) Я наконец-то припаял XBee на схему. Теперь она часть устройства.
2) Для интереса я засунул туда температурный датчик.
3) Я сделал входы для 4х серво-моторов, ультразвукового сенсора, аккумулятора и трех солнечных батарей (до них очередь еще не дошла)
4) Вывел один интрефейс NXT наружу если мне вдруг понадобится подключить какой-то сенсор для Lego (компас или гироскоп, например)
5) Так как теперь все работает от одной батареи, то я сделал внешний выключатель, и так же припаял конденсатор, чтобы избавиться от «шума» серво-двигателей.
6) Так как подключение аккумулятора и солнечных батарей находится на внешней плате, а сама разводка питания (которая еще не закончена) на средней, то я сделал дополнительные соединения между ними
7) Питание от аккумулятора я завел на один из аналоговых входов, чтобы мерять напряжение.
Естественно изменился и софт!
Как и в прошлый раз, его можно скачать с моего сайта Frank — Autonomous Vehicle
Что же изменилось в ПО?
То, как я пытался общаться с Arduino через XBee, используя стороннюю библиотеку, было в корне не верно. Все оказалось гораздо проще. Используя встроенную библиотеку Serial, все начало работать и посылаться. Есть небольшие нюансы, но о них позже.
Теперь я посылаю все данные, которые на данный момент мне доступны в iFrank, а именно:
1) Заряд батареи
2) Расстояние до ближайшего объекта (с ультразвукового сенсора)
3) Температура
4) Текущие переменные по управлению серво-двигателями.
Все вычисления связанные с данными происходят в iFrank, чтобы не загружать Arduino ненужной работой. iFrank в свою очередь занимается мониторингом данных, и если необходимо их изменение, отсылает обновление. Таким образом работает поворот головы и управление серво-двигателями. Если в iFrank я нажимаю клавиши управления поворотом головы, то происходит расхождение «желаемых» данных и текущих, о чем iFrank сообщает Arduino, тем самым изменяя данные. Так же есть таймер в iFrank, который плавно изменяет эту разницу и, собственно, отсылает нужный сигнал.
Как вы увидите в коде iFrank и Arduino, в конце сигнала я посылаю символ "*" (в случае arduino->iFrank) и "\n" (в случае iFrank->arduino). Так как при чтении из последовательного порта информация приходит кусками, то в iFrank я организовал буфер, в который сваливается информация. Асинхронный таймер периодически считывает оттуда информацию и обрабатывает кусками, разделенными "*".
Ниже находится код Arduino:
Код Arduino
#include <Servo.h>
#include <math.h>
//Sensors
int dataTemperature = 0;
int dataBattaryVoltage = 0;
int dataSolarVoltage = 0;
int dataMotor1 = 0;
int dataMotor2 = 0;
int dataSteering1 = 0;
int dataSteering2 = 0;
int dataCamPan = 0;
int dataCamTilt = 0;
int dataGyrX = 0;
int dataGyrY = 0;
int dataGyrZ = 0;
int dataAccX = 0;
int dataAccY = 0;
int dataAccZ = 0;
int dataGPSTime = 0;
int dataGPSLong = 0;
int dataGPSLat = 0;
int dataGPSSat = 0;
float dataDistance = 0;
int dataTemp2 = 0;
int dataTemp3 = 0;
int dataTemp4 = 0;
int dataTemp5 = 0;
int dataTemp6 = 0;
int dataTemp7 = 0;
int dataTemp8 = 0;
#define trigPin 31
#define echoPin 30
//Voltage
const float referenceVolts = 3.3;
const float resistor1 = 20000;
const float resistor2 = 2200;
const float resistorFactor = resistor2/(resistor1+resistor2);
const float boardVoltage = 3.3;
const float analogVoltMult = boardVoltage/1024;
const int batteryPin = 1;
// Servo Code
Servo steering1;
Servo steering2;
Servo motor1;
Servo motor2;
// Steering initial states
float st1_start = 0.0;
float st1_end = 180.0;
float st1_step = (st1_end-st1_start)/256.0;
float st2_start = 0.0;
float st2_end = 180.0;
float st2_step = (st2_end-st2_start)/256.0;
// Steering function 0-256
float steer1(float p){ float v=st1_start+st1_step*p; steering1.write(v); return v;}
float steer2(float p){ float v=st2_start+st2_step*p; steering2.write(v); return v;}
int pos = 0;
// XBEE Code
/**
XBee xbee = XBee();
unsigned long start = millis();
uint8_t payload[] = { 'H', 'i','t','h','e','r','e' };;
Tx16Request tx = Tx16Request(0x4000, payload, sizeof(payload));
TxStatusResponse txStatus = TxStatusResponse();
*/
void setup() {
// Serial
Serial.begin(19200);
// Sensors
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
// Servos
steering1.attach(2);
steering2.attach(3);
motor1.attach(4);
motor2.attach(5);
dataMotor1 = 94;
dataMotor2 = 94;
dataSteering1 = 130;
dataSteering2 = 130;
}
void loop() {
// Read serial input
while (Serial.available() > 0) {
int st1 = Serial.parseInt();
int st2 = Serial.parseInt();
int mt1 = Serial.parseInt();
int mt2 = Serial.parseInt();
if (Serial.read() == '\n') {
dataSteering1 = st1;
dataSteering2 = st2;
dataMotor1 = mt1;
dataMotor2 = mt2;
}
}
// Read temperature
dataTemperature = analogRead(0);delay(15);
dataTemperature = analogRead(0);
dataBattaryVoltage = analogRead(1);delay(15);
dataBattaryVoltage = analogRead(1);
Serial.print(dataTemperature);Serial.print("|");
Serial.print(dataBattaryVoltage);Serial.print("|");
Serial.print(dataSolarVoltage);Serial.print("|");
Serial.print(dataMotor1);Serial.print("|");
Serial.print(dataMotor2);Serial.print("|");
Serial.print(dataSteering1);Serial.print("|");
Serial.print(dataSteering2);Serial.print("|");
Serial.print(dataCamPan);Serial.print("|");
Serial.print(dataCamTilt);Serial.print("|");
Serial.print(dataGyrX);Serial.print("|");
Serial.print(dataGyrY);Serial.print("|");
Serial.print(dataGyrZ);Serial.print("|");
Serial.print(dataAccX);Serial.print("|");
Serial.print(dataAccY);Serial.print("|");
Serial.print(dataAccZ);Serial.print("|");
Serial.print(dataGPSTime);Serial.print("|");
Serial.print(dataGPSLong);Serial.print("|");
Serial.print(dataGPSLat);Serial.print("|");
Serial.print(dataGPSSat);Serial.print("|");
Serial.print(dataDistance);Serial.print("|");
Serial.print(dataTemp2);Serial.print("|");
Serial.print(dataTemp3);Serial.print("|");
Serial.print(dataTemp4);Serial.print("|");
Serial.print(dataTemp5);Serial.print("|");
Serial.print(dataTemp6);Serial.print("|");
Serial.print(dataTemp7);Serial.print("|");
Serial.print(dataTemp8);
Serial.print("*");
steer1(dataSteering1);
steer2(dataSteering2);
motor1.write(dataMotor1);
motor2.write(dataMotor2);
delay(100);
// Read echo
long duration, distance;
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
dataDistance = pulseIn(echoPin, HIGH);
}
Еще немного фотографий Френка