Набор «Робоняша»

Электронная версия брошюры из набора «Робоняша». Здесь собраны все исходные коды экспериментов, подсказки и хаки по прохождению набора.

Электронная версия

Эксперименты

1. Прожектор

Давай подключим к Iskra JS простой модуль светодиода. Он будет служить нам прожектором, освещающим путь нашему роботу.

var projector = require('@amperka/led').connect(P8);
projector.turnOn();

2. Сигнальная колонна

Привлечём к роботу побольше внимания, дадим знать всем окружающим о приближении робота. Сделаем для этого прожектор мигающим! Будем то включать, то выключать его.

var notice = require('@amperka/led').connect(P3);
notice.blink(0.1, 0.9);

3. Сенсорный выключатель

Научимся включать и выключать прожектор с помощью сенсорной кнопки. Давай приспособим для этих целей цифровой датчик линии. Получится оптический бесконтактный выключатель.

var led = require('@amperka/led').connect(P3);
var button = require('@amperka/digital-line-sensor').connect(P4);
 
function myCoolButtonHandler() {
  led.toggle();
}
 
button.on('white', myCoolButtonHandler);

4. Миксер

Главная часть любого робота — двигатель. Подключим двигатель к устройству и научимся им управлять.

var motor = require('@amperka/motor');
var leftMotor = motor.connect(motor.MotorShield.M1);
leftMotor.write(-0.85);

5. Одометр

Сколько метров проехал робот? На этот вопрос ответит одометр. Давай соберём его!

var motor = require('@amperka/motor');
var leftMotor = motor.connect(motor.MotorShield.M1);
leftMotor.write(0.3);
 
var encoder = require('@amperka/digital-line-sensor')
  .connect(P10);
 
var RADIUS = 32;
var WHEEL_LENGTH = 2 * Math.PI * RADIUS;
var STEP = 1 / 12;
var revolutions = 0;
 
encoder.on('black', function() {
  revolutions = revolutions + STEP;
  var distance = revolutions * WHEEL_LENGTH;
  print(distance, 'mm');
});

6. Спидометр

Часто, помимо пройденного расстояния, интересно знать, с какой скоростью движется робот. Соберём спидометр.

var motor = require('@amperka/motor');
var leftMotor = motor.connect(motor.MotorShield.M1);
leftMotor.write(0.3);
 
var encoder = require('@amperka/digital-line-sensor')
  .connect(P10);
 
var RADIUS = 32;
var WHEEL_LENGTH = 2 * Math.PI * RADIUS;
var STEP_LENGTH = WHEEL_LENGTH / 12 / 1000;
var speed = 0;
var lastTime = getTime();
 
encoder.on('black', function() {
  var currentTime = getTime();
  var deltaTime = currentTime - lastTime;
  speed = STEP_LENGTH / deltaTime;
  lastTime = currentTime;
  print(speed.toFixed(2), 'm/s');
});

7. Марсоход

Соберём, наконец, робота! Ни один ездящий робот не обходится без автономного питания и пульта управления.

Опечатка! На этапе сборки под номером 31 на странице 51 необходимо использовать деталь структора формата 5×5 шипов, а не 4×6 как на картинке.

var marsohod = require('@amperka/robot-2wd')
  .connect();
 
var receiver = require('@amperka/ir-receiver')
  .connect(P3);
 
receiver.on('receive', function(code) {
  if (code === receiver.keys.TOP) {
    marsohod.go({l: 0.5, r: 0.5});
  }
  if (code === receiver.keys.POWER) {
    marsohod.stop();
  }
});

Научим марсоход поворачивать.

var SPEED = 0.5;
 
var marsohod = require('@amperka/robot-2wd')
  .connect();
 
var receiver = require('@amperka/ir-receiver')
  .connect(P3);
 
receiver.on('receive', function(code) {
  if (code === receiver.keys.TOP) {
    marsohod.go({l: SPEED, r: SPEED});
  }
  if (code === receiver.keys.POWER) {
    marsohod.stop();
  }
  if (code === receiver.keys.LEFT) {
    marsohod.go({l: 0, r: SPEED});
  }
  if (code === receiver.keys.RIGHT) {
    marsohod.go({l: SPEED, r: 0});
  }
});

8. Чистюля

Добавим немного автономности. Роботы-пылесосы, несмотря на кажущуюся сложность, устроены довольно просто. У них есть бампер с датчиками, чтобы чувствовать препятствия, а также инфракрасные датчики снизу, чтобы не падать с подиумов и лестниц. Мы можем с лёгкостью сделать робота, не падающего со стола.

Чтобы понять, как работать с аналоговыми сигналами, напишем небольшую программу. Будем каждые 100 миллисекунд читать значение напряжения на сигнальном пине датчика линии.

var analogSensor = require('@amperka/analog-line-sensor')
  .connect(A0);
 
var showSignal = function() {
  print('signal:', analogSensor.read('V'), 'volts');
};
 
setInterval(showSignal, 100);

Загрузи код в Iskra JS, поставь Робоняшу на светлый стол, а затем нажми кнопку PLAY на пульте.

var FORWARD_SPEED = 0.3;
var BACKWARD_SPEED = 0.8;
var BORDER_VALUE = 0.2;
var intervalID;
 
var cleaner = require('@amperka/robot-2wd').connect();
var receiver = require('@amperka/ir-receiver')
  .connect(P3);
 
var lineSensor = require('@amperka/analog-line-sensor');
var leftSensor = lineSensor.connect(A0);
var rightSensor = lineSensor.connect(A1);
 
function clean() {
  if (leftSensor.read() > BORDER_VALUE) {
    cleaner.go({l: 0, r: -BACKWARD_SPEED});
  } else if (rightSensor.read() > BORDER_VALUE) {
    cleaner.go({l: -BACKWARD_SPEED, r: 0});
  } else {
    cleaner.go({l: FORWARD_SPEED, r: FORWARD_SPEED});
  }
}
 
receiver.on('receive', function(code) {
  if (code === receiver.keys.PLAY) {
    if (!intervalID) {
      leftSensor.calibrate({white: leftSensor.read()});
      rightSensor.calibrate({white: rightSensor.read()});
      intervalID = setInterval(clean, 10);
    } else {
      cleaner.stop();
      intervalID = clearInterval(intervalID);
    }
  }
});

9. Следопыт

Очень часто от роботов требуется доехать из одного места в другое, чтобы доставить груз. Для такой задачи роботу требуется знать маршрут движения.

var SPEED = 0.3;
 
var detective = require('@amperka/robot-2wd')
  .connect();
 
var lineSensor = require('@amperka/analog-line-sensor');
var leftSensor = lineSensor.connect(A0);
var rightSensor = lineSensor.connect(A1);
 
var lineFollower = require('@amperka/pid').create({
  target: 0,
  kp: 6,
  ki: 0.1,
  kd: -1,
  outputMin: -SPEED,
  outputMax: SPEED
});
 
lineFollower.run(function() {
  var right = rightSensor.read();
  var left = leftSensor.read();
  var error = left - right;
  var output = lineFollower.update(error);
  detective.go({
    l: SPEED + output,
    r: SPEED - output
  });
}, 0.02);

10. Нехочуха

Скоро мы соберём робоняшу полностью. Прежде чем собрать голову, научимся поворачивать шею, чтобы робоняша мог смотреть из стороны в сторону.

var neck = require('@amperka/servo').connect(P8);
 
var angle = 90;
var STEP = 5;
 
setInterval(function() {
  if (angle <= 30 || angle >= 150) {
    STEP = -STEP;
  }
  angle = angle + STEP;
  neck.write(angle);
}, 100);

11. Прилипала

Сделаем из робота верного домашнего питомца, который будет везде ходить по пятам. Задействуем для этого ультразвуковой дальномер.

var SPEED = 0.5;
var DISTANCE_MIN = 10;
var DISTANCE_MAX = 14;
 
var sticker = require('@amperka/robot-2wd')
  .connect();
 
var ultrasonic = require('@amperka/ultrasonic').connect({
  trigPin: P12,
  echoPin: P11
});
 
function check(distance) {
  if (distance > DISTANCE_MAX) {
    sticker.go({l: SPEED, r: SPEED});
  } else if (distance < DISTANCE_MIN) {
    sticker.go({l: -SPEED, r: -SPEED});
  } else {
    sticker.stop();
  }
}
 
setInterval(function() {
  ultrasonic.ping(function(error, value) {
    if (!error) {
      check(value);
    }
  }, 'cm');
}, 100);

12. Робо-сумо

Робо-сумо — популярный вид соревнований по робототехнике. В этом состязании участникам необходимо подготовить автономного робота, способного выталкивать противника за пределы ринга.

var FORWARD = 0.5;
var BACKWARD = 0.8;
var ROTATE = 0.2;
var BORDER_VALUE = 0.5;
var MAX_DISTANCE = 50;
var sumoist = require('@amperka/robot-2wd')
.connect();
 
var ultrasonic = require('@amperka/ultrasonic').connect({
  trigPin: P12,
  echoPin: P11
});
 
var lineSensor = require('@amperka/analog-line-sensor');
var leftSensor = lineSensor.connect(A0);
var rightSensor = lineSensor.connect(A1);
 
var save = false;
 
var detectBorder = function() {
  if (leftSensor.read() < BORDER_VALUE) {
    save = true;
    sumoist.go({l: 0, r: -BACKWARD});
  } else if (rightSensor.read() < BORDER_VALUE) {
    save = true;
    sumoist.go({l: -BACKWARD, r: 0});
  } else {
    save = false;
  }
};
 
setInterval(detectBorder, 10);
 
var scan = function() {
  ultrasonic.ping(function(error, value) {
    if (!error && value < MAX_DISTANCE) {
      if (!save) {
        sumoist.go({l: FORWARD, r: FORWARD});
      }
    } else {
      sumoist.go({l: ROTATE, r: -ROTATE});
    }
  }, 'cm');
};
 
setInterval(scan, 100);