суббота, 7 января 2017 г.

Начинаем программировать EV3 на Python

Год назад мы перешли с графического программирование нашего робота LEGO Mindstorms EV3 на текстовое и первым языком программирования, который мы освоили, стал EV3 BASIC. Нам очень понравились его простота, отзывчивость к первым, зачастую неумелым попыткам выжать из него что-то большее, то, что мы могли получить на графическом "леговском" EV3-G. За этот год мы создали с использование BASICа целый ряд проектов (NAVIDOZ3R, Music Station, Телеграф, НУ ПОГОДИ, Саймон сказал), которые, как нам кажется, неплохо раскрыли возможности этого языка. Программы, написанные не нем работают существенно быстрее программ на "языке из кубиков". Бейсик не навязывает "хороший стиль" программирования, он дает свободу программисту - в этом его основная фишка.


Однако чем более сложными становились наши проекты с использованием EV3 BASIC, тем все более отчетливо мы ощущали его ограничения:

  • все переменные - глобальные
  • так же как и в EV3-G нет возможности работать с многомерными массивами без использования костылей
  • нельзя создать функцию с передачей в нее параметров и возвратом из нее результата
  • проблемы с поддержкой датчиков сторонних производителей

Хорошей альтернативой и следующей ступенькой в полноценном программировании EV3 традиционно считается ROBOTC, однако это достаточно дорогая ступенька, данная среда разработки далеко не бесплатна. Кроме этого ROBOC требует перепрошивки блока EV3 на свою прошивку, это не для всех приемлемо.
В мире opensource существует несколько свободных альтернатив для программирования платформы EV3 с использованием "взрослых" языков. Это LeJOS и язык Java, Monobrick с языком С# и ev3dev с целым рядом языков - Python, C, C++, JavaScript. Go.


Мы решили начать с ev3dev и языка Python, так как ходили слухи что язык этот имеет достаточно низкий порог вхождения подобно Бейсику, при этом не имея присущих ему недостатков. Еще на летних каникулах мы проштудировали замечательную детскую книгу по Python под названием "Hello World! Занимательное программирование" (Картер Сэнд, Уоррен Сэнд, 2016). На примере создания простейших игр книга действительно занимательно погружает читателя в основы языка Python.


Начав разбираться с ev3dev нам показалось, что информации из книги для погружения в Python не достаточно и в поисках ее дополнительных источников мы набрели на крутые онлайн-курсы на портале Stepic.org



В процессе прохождения первого курса, в попытках решения учебных задач с Python на EV3 пришло понимание того, что базовые навыки работы в ОС Linux пришлись бы юным робототехникам как нельзя кстати. Курс на том же Stepic под названием Введение в Linux оставил только положительные эмоции и дал массу полезной информации, в том числе и для размышления. 


Очень полезным источником информации для нас стал сайт по EV3 Python. Его автор, Nigel Ward, уже помогал нам год назад в адаптации EV3 BASIC для русскоязычных робототехников. Его материалы мы взяли за основу и в этот раз. Мы подготовили русскоязычное руководство по Python для EV3, которое состоит из двух частей:


В первой части - описание процесса установки и первоначальной настройки ev3dev. Во второй - справочник по "командам" и примеры программ на Python для платформы EV3.

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


Исходные кода всех программ, а также инструкцию к роботу, способному их решить вы можете скачать по ссылке.



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


# подключаем модуль поддержки оборудования EV3
from ev3dev.ev3 import *
# из модуля time будем использовать задержку - sleep
from time import sleep
# кроме это нам понадобится модуль математики
import math

# Это скорость разворота робота, в градусах/сек
speed = 50
# переменная для хранения значений с датчика-гироскопа
GyVal = 0

# создаем объект Gyro связанный с датчиком-гироскопа на 1 порту
Gyro = GyroSensor("in1")
# и объект IR, связанный с ИК-датчиком на порту 4
IR = InfraredSensor("in4")

# нам понадобится пара моторов, порты A и D
mA = LargeMotor('outA')
mD = LargeMotor('outD')

# устанавливаем гироскоп в режим измерения угла
Gyro.mode = "GYRO-ANG"

# создаем объект lcd для работы с экраном блока EV3
lcd = Screen()
# рисуем в буфере пару кругов (это скелет карты)
lcd.draw.ellipse(( 84, 59,  94, 69))
lcd.draw.ellipse(( 25, 0,  153, 128))
# выводим инфу из экранного буфера на экран
lcd.update()

# создаем список (массив) для хранения карты 
g = [0 for i in range(360)]

# запускаем робота на вращение вокруг своей оси
mA.run_forever(speed_sp=speed)
mD.run_forever(speed_sp=-1*speed)

# во время вращения заполняем массив g данными
# с ИК-датчика с привязкой у направлению (гироскоп)
while Gyro.value() < 360:
    GyVal =  abs(Gyro.value())    
    g[GyVal] = IR.value()*0.7

# после полного круга останавливаемся
mA.stop(stop_action="hold")
mD.stop(stop_action="hold")  

# выводим карту из массива в экранный буфер
# в виде лепестковой диаграммы
for i in range(90):
    lcd.draw.line(( 89, 64,  89+g[i]*math.sin(math.radians(i)), 64-g[i]*math.cos(math.radians(i))))
    lcd.draw.line(( 89, 64,  89+g[i+90]*math.cos(math.radians(i)), 64+g[i+90]*math.sin(math.radians(i))))
    lcd.draw.line(( 89, 64,  89-g[i+180]*math.sin(math.radians(i)), 64+g[i+180]*math.cos(math.radians(i))))
    lcd.draw.line(( 89, 64,  89-g[i+270]*math.cos(math.radians(i)), 64-g[i+270]*math.sin(math.radians(i))))

# выводим информацию из буфера на экран
lcd.update()
# издаем какой-то звук
Sound.beep()

# и замираем на 10 секунд чтобы успеть посмотреть карту
sleep(10)

2. Плавное изменение цветов и яркости подсветки по нажатию кнопок на блоке, например вверх-вниз изменяют яркость, влево-вправо изменяет цвет, нажатие в центр запускает автоматические переливы. Голосом озвучивается изменения режимов.

# подключаем модуль поддержки оборудования EV3
from ev3dev.ev3 import *
# из модуля time будем использовать задержку - sleep
from time import sleep

# объект для работы с кнопками на блоке  btn = Button()

tr = 1

# список цветов подсветки color = [Leds.RED, Leds.GREEN, Leds.AMBER, Leds.ORANGE, Leds.YELLOW]
cs = 0

# выключаем подсветку Leds.all_off()

# текущая яркость подсветки
on = 127

# цикла - пока не нажата кнопка назад while not btn.backspace:
    # если нажата кнопка влево
    if btn.check_buttons(buttons=['left']):
        cs=cs-1
        if cs<0:
            cs=4
        # проговариваем ее название
        # и изменяем цвет с выбранной яркостью         Sound.speak("Button left")
        Leds.set_color(Leds.LEFT, color[cs], on/255)
        Leds.set_color(Leds.RIGHT, color[cs], on/255)
        sleep(0.5)   

    # тоже самое для кнопки вправо     
elif btn.check_buttons(buttons=['right']):
        cs=cs+1
        if cs>4:
            cs=0

        Sound.speak("Button right")
        Leds.set_color(Leds.LEFT, color[cs], on/255)
        Leds.set_color(Leds.RIGHT, color[cs], on/255)
        sleep(0.5)

    # если нажата кнопка вверх
    if btn.check_buttons(buttons=['up']):
        # увеличиваем яркость подсветки
        on=on+10
        if on>240:
            Sound.speak("Maximum")
            on=255

        Leds.set_color(Leds.LEFT, color[cs], on/255)
        Leds.set_color(Leds.RIGHT, color[cs], on/255)
        sleep(0.1)

    # если нажата кнопка вниз
    elif btn.check_buttons(buttons=['down']):
        # уменьшаем яркость
        on=on-10
        if on<10:
            Sound.speak("Minimum")
            sleep(2)
            on=0
        
        Leds.set_color(Leds.LEFT, color[cs], on/255)
        Leds.set_color(Leds.RIGHT, color[cs], on/255)
        sleep(0.1)

    # при нажатии центральной кнопки     
if btn.check_buttons(buttons=['enter']):
        Sound.speak("Random color")
        tr = 1
        # цикл пока не нажата кнопка назад
        while not btn.backspace or tr!=0:
            Leds.set_color(Leds.RIGHT, Leds.RED,0)
            Leds.set_color(Leds.LEFT, Leds.GREEN,0)
            
            # "переливы" цветов и яркости подсветки
            for i in range(0,255,5):
                Leds.set_color(Leds.LEFT, Leds.GREEN,i/255)
                if btn.check_buttons(buttons=['backspace']):
                    tr = 0            
            if tr == 0:
                break                

            for i in range(255,0,-5):
                Leds.set_color(Leds.RIGHT, Leds.ORANGE,(255-i)/255) 
                Leds.set_color(Leds.LEFT, Leds.GREEN,i/255) 
                
                if btn.check_buttons(buttons=['backspace']) or tr == 0:
                    break              
        
            for i in range(0,255,5):
                Leds.set_color(Leds.LEFT, Leds.GREEN,i/255)
                if btn.check_buttons(buttons=['backspace']):
                    tr = 0
            if tr == 0:
                break
            Leds.set_color(Leds.RIGHT, Leds.ORANGE,1)

            for i in range(255,0,-5):
                Leds.set_color(Leds.RIGHT, Leds.ORANGE,i/255)
                Leds.set_color(Leds.LEFT, Leds.GREEN,i/255)

                if btn.check_buttons(buttons=['backspace']): 
                    break  
            for i in range(0,255,5):
                Leds.set_color(Leds.LEFT, Leds.YELLOW,i/255)
                if btn.check_buttons(buttons=['backspace']):
                    tr = 0
            if tr == 0:
                break

            for i in range(255,0,-5):
                Leds.set_color(Leds.RIGHT, Leds.RED,(255-i)/255)
                Leds.set_color(Leds.LEFT, Leds.YELLOW,i/255)

                if btn.check_buttons(buttons=['backspace']) or tr == 0:
                    break

            for i in range(0,255,5):
                Leds.set_color(Leds.LEFT, Leds.YELLOW,i/255)
                if btn.check_buttons(buttons=['backspace']):
                    tr = 0
            if tr == 0:
                break
            Leds.set_color(Leds.RIGHT, Leds.RED,1)

            for i in range(255,0,-5):
                Leds.set_color(Leds.RIGHT, Leds.RED,i/255)
                Leds.set_color(Leds.LEFT, Leds.YELLOW,i/255)

                if btn.check_buttons(buttons=['backspace']):
                    break

3. Робот должен проехать полный круг по линии и остановиться, определив и произнеся вслух диаметр круга, который на момент старта ему неизвестен. Гироскоп использовать нельзя.

# подключаем модуль для работы с EV3
from ev3dev.ev3 import *
# модуль для работы со временем
from time import *
# также нам потребуется pi из модуля математики
from math import pi

# объект для работы с кнопками на блоке
btn = Button()

# пара объектов для работы с моторами
mA = LargeMotor('outA')
mD = LargeMotor('outD')

# сбрасываем состояние энкодеров
# при старте программы они автоматически не сбрасываются!
mA.reset()
mD.reset()

# пара объектов для работы с датчиками освещенности
# у нас датчики от NXT 1.0, если будете использовать
# датчики цвета EV3, используйте класс ColorSensor
CS1 = LightSensor('in2')
CS2 = LightSensor('in3')

# режим работы датчиков - отраженный свет
# для EV3-датчиков цвета используйте 'COL-REFLECT' CS1.mode='REFLECT'
CS2.mode='REFLECT'

# средняя скорость движения по линии, градусов на энкодерах в сек speed = 800
# начальные сведения о радиусе круга - он неизвестен
r = 0

# параметры работы ПД-регулятора
u = 0
e = 0
es = 0
Pk = 1.1
Dk = 2

# создаем функцию для ограничения скорости моторов
# диапазоном -900..900 град/сек
def motor(speed):
    if speed>900:
        speed = 900
    elif speed<-900:
        speed = -900
    return speed

# цикл пока не нажата кнопка "назад"
while not btn.backspace:
    # рассчитываем ошибку для ПД-регулятора
    e = CS1.value() - CS2.value()
    # рассчитываем управляющее воздействие регулятора
    u = e*Pk + (e-es)*Dk
    # сохраняем ошибку для следующей итерации
    es = e

    # подаем управляющее воздействие на моторы
    # используя функцию, ограничивающую скорость  
    mA.run_forever(speed_sp=motor(speed+u))
    mD.run_forever(speed_sp=motor(speed-u))

    # рассчитываем радиус круга     
    if mD.position != mA.position:
        r = (61*(mA.position + mD.position))/(abs(mD.position-mA.position))
    # выводим на консоль текущий радиус круга
    print(r)   
    # небольшая задержка для работы регулятора
    sleep(0.01)
    # если проехали полный круг - вылет из цикла
    if (mA.position*(pi*56/360)+mD.position*(pi*56/360))/2 > pi*(r*2) and time()>3:
        break

# останавливаем моторы методом торможения
mA.stop(stop_action="hold")
mD.stop(stop_action="hold")

# произносим рассчитанный в процессе движения диаметр круга
Sound.speak('Stoped').wait()
Sound.speak(round(r*2)).wait()

4. Робот, имеющий в своей конструкции гироскоп, должен описать круг вокруг кегли и, вернувшись на место старта, остановиться.

# подключаем модуль для работы с EV3
from ev3dev.ev3 import *
# из модуля для работы со временем нам понадобится sleep
from time import sleep
# подключаем модуль математики
from math import *

# переменные для хранение данных с энкодеров
ma = 0
md = 0

# объект для работы с кнопками на блоке
btn = Button()
# средняя скорость робота, град/сек
speed = 200

# объекты для работы с моторами mA = LargeMotor('outA')
mD = LargeMotor('outD')

# объекты для работы с датчиками - инфракрасным и гироскопом IR = InfraredSensor('in4')
Gyro = GyroSensor("in1")

# устанавливаем режим работы гироскопа (измерение угла) Gyro.mode = 'GYRO-ANG'

# создаем функцию для ограничения скорости моторов
# диапазоном -900..900 град/сек
def motor(speed):
    if speed>900:
        speed = 900
    elif speed<-900:
        speed = -900
    return speed

# переменные для хранения расстояния и направления на ближайшую банку
MIN = 100
MINGRAD = 0

# в массиве g будем хранить данным с гироскопа
g = [0 for i in range(360)]

# вращаемся в поисках банки
mA.run_forever(speed_sp=speed)
mD.run_forever(speed_sp=-1*speed)

# находим ближайшую банку
while Gyro.value() < 359:
    GyVal =  Gyro.value()
    g[GyVal] = IR.value()*0.7

    if g[GyVal] < MIN:
        MIN = g[GyVal]
        MINGRAD = GyVal    

# останавливаемся
mA.stop(stop_action="hold")
mD.stop(stop_action="hold")

Sound.beep()

# расчет угла для поворота к банке боком
MINGRAD = MINGRAD + 90

# разворачиваемся к банке левым бортом
while abs(Gyro.value()-MINGRAD) > 1:
    speedL = -1*(Gyro.value()-MINGRAD)*5
    speedR = (Gyro.value()-MINGRAD)*5
    mA.run_forever(speed_sp=motor(speedL))
    mD.run_forever(speed_sp=motor(speedR))

# останавливаемся
mA.stop(stop_action="hold")
mD.stop(stop_action="hold")

sleep(2)

# сбрасываем гироскоп Gyro.mode = 'GYRO-RATE'
Gyro.mode = 'GYRO-ANG'

sleep(1)

grad = 0
# расчет длины стороны 360-ти угольника
i = 2*(MIN*6+70)*tan(radians(0.5))

# сброс энкодеров
mA.reset()
mD.reset()

# снижаем скорость до 50 граду/сек
speed = 50

# цикл - пока не проехали полный круг
# или не нажата кнопка
while grad > -360 and not btn.backspace:
    ob = 56*pi/360
    # если проехали 1 сторону 360-ти угольника
    if ((mA.position-ma)*ob+(mD.position-md)*ob)/2 > i:
        # поворачиваемся на 1 градус
        grad = grad-1
        ma = mA.position
        md = mD.position
    
    # пропорциональный регулятор от grad     
    speedL = speed-(Gyro.value()-grad)*16
    speedR = speed+(Gyro.value()-grad)*16

    # перед подачей на моторы ограничиваем скорость ф. motor()     
    mA.run_forever(speed_sp=motor(speedL))
    mD.run_forever(speed_sp=motor(speedR))

# останавливаемся после того как проехали круг
mA.stop(stop_action="hold")
mD.stop(stop_action="hold") 

5. Робот, двигаясь по линии круга, после прохождения полной окружности должен закрутить траекторию движения правильной спиралью к центру круга. Достигнув центра круга робот должен остановиться. В программе должно быть предусмотрена возможность указания количества витков спирали, которое сделает робот до того, как остановится в центре круга.

# подключаем модуль для работы с EV3
from ev3dev.ev3 import *
# нам потребуются модули математики и времени
from math import *
import time

# объект для работы с кнопками на блоке
btn = Button()

# средняя скорость движения робота, град/сек
speed = 300

# объекты для работы с моторами
mA = LargeMotor('outA')
mD = LargeMotor('outD')

# объекты для работы с датчиками
LS1 = LightSensor('in2')
LS2 = LightSensor('in3')
# режим работы датчиков освещенности - отраженный свет
LS1.mode = 'REFLECT'
LS2.mode = 'REFLECT'

# объект для работы с гироскопом
Gyro = GyroSensor("in1")
# режим работы гироскопа - измерение угла
Gyro.mode = "GYRO-ANG"

# функция для ограничения скорости мотора (-900..900)
def motor(speed):    
    if speed>900:   
        speed = 900                             
    elif speed<-900:      
        speed = -900 
    return speed

# сброс энкодеров
mA.reset()
mD.reset()

# параметры ПД-регулятора
e = 0
es = 0
u = 0
Pk = 3
Dk = 6

# диаметр круга роботу неизвестен
D = 0

# пока не нажата кнопка назад
while not btn.backspace:
    e = LS1.value()/10 - LS2.value()/10
    u = e*Pk + (e-es)*Dk    
    es = e
      
    mA.run_forever(speed_sp=motor(speed+u))
    mD.run_forever(speed_sp=motor(speed-u))

    if mA.position != mD.position:
        D = (61*(mA.position + mD.position))/(abs(mA.position-mD.position))
    print(D)

    if (mA.position*(56*pi/360)+mD.position*(56*pi/360))/2 > pi*(D*2) and time.time() > 3:
        break

# аналогично третьей задаче останавливаемся 
# после того как робот проехал полный круг

mA.stop(stop_action="hold")
mD.stop(stop_action="hold")
Sound.beep()

# сброс гироскопа
grad = 0
ma = 0
mb = 0
time.sleep(1)
Gyro.mode = 'GYRO-RATE'
Gyro.mode = 'GYRO-ANG'

speedL = 0
speedR = 0

ma = mA.position
mb = mD.position

tmp = D
speed = 50

# аналогично третьей задаче движемся по правильному
# 360-ти угольнику, но длину сторону уменьшаем 
# после каждой стороны, делая "круг" меньше
# как только диаметр вписанной окружности станет
# менее 1 см - останавливаемся

while not btn.backspace:
    ob = 56*pi/360
    if ((mA.position-ma)*ob+(mD.position-mb)*ob)/2 > (2*D)*tan(radians(0.5)):

        if mA.position > mD.position:
            grad = grad+1             
        else:
            grad = grad-1
        
        D = D-tmp/720
        ma = mA.position
        mb = mD.position

    speedL = speed-(Gyro.value()-grad)*10
    speedR = speed+(Gyro.value()-grad)*10

    mA.run_forever(speed_sp=motor(speedL))
    mD.run_forever(speed_sp=motor(speedR))

    print(D)
    if D<0.5:
        break

mA.stop(stop_action="hold")
mD.stop(stop_action="hold")
Sound.beep()

суббота, 31 декабря 2016 г.

С Новым Годом!

Я, Большой железный робот и мои создатели, команда робототехников Карандаш и Самоделкин, поздравляем всех с наступающим Новым годом!
Хочется всем пожелать, чтобы в наступающем году с нами произошло то самое чудо, о котором мы все так мечтаем. Хотя у каждого оно свое, но оно обязательно самое необходимое и самое важное. Желаю никому не ржаветь, иметь под рукой свежую батарейку, на которую можно всегда рассчитывать. Желаю Вам в грядущем году быть в окружении исключительно положительных роботов и доброжелательных людей, переживать только приятные эмоции без багов, радоваться каждому прожитому циклу, дарить радость и энергию окружающим. Пусть этот Новый год станет для Вас особенным. С Новым Годом!


суббота, 3 декабря 2016 г.

Решение задач №5 и 6

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



В первой задаче наш робот будет объезжать препятствия на линии и возвращаться на нее для продолжения движения. 
Решение задачи на языке NXC:

float x,d,speed,es,D1,D2,e,u,PD,Pk;

task main()
{
  // инициализация датчиков
  SetSensorLight(IN_2);
  SetSensorLight(IN_3);
  SetSensorLowspeed(IN_4);
  // размер колеи
  x = 180;
  // диаметр колес
  d = 43.2;
  // средняя скорость
  speed = 30;
  // "старая" ошибка Д-регулятора
  es = 0;
  // пропорциональный коэф-т
  Pk = 2;
  // дифференциальный коэф-т
  PD = 4;

  // сбрасываем энкодеры  
  ResetRotationCount(OUT_BС);

  while(true)
  {
    // движемся по линии пока не обнаружим препятствие
    while(SensorUS(IN_4) > 28)
    {
      // показания датчиков линии
      D1 = Sensor(IN_2);
      D2 = Sensor(IN_3);
      // ошибка регулятора
      e = D2-D1;
      // управляющее воздействие
      u = e*Pk+PD*(e-es);
      // подаем его на моторы
      OnFwd(OUT_B,u+speed);
      OnFwd(OUT_C,-u+speed);
      // сохраняем ошибка для Д-регулятора
      es = e;
    }
    // моторы - стоп
    Off(OUT_BC);
    // поворот в сторону внутреннего изгиба трассы
    if (MotorRotationCount(OUT_B)>MotorRotationCount(OUT_C))
    {
      RotateMotorEx(OUT_BC, speed, x/d*52, -100, true, true);
    }
    else
    {
      RotateMotorEx(OUT_BC, speed, x/d*52, 100, true, true);
    }
    Wait(500);
    // съезжаем с линии
    RotateMotorEx(OUT_BC, speed, 1000, 0, true, true);
    // поворот в сторону линии
    if (MotorRotationCount(OUT_B)<MotorRotationCount(OUT_C))
    {
      RotateMotorEx(OUT_BC, speed, x/d*52, -100, true, true);
      OnRev(OUT_B,-18);
      OnRev(OUT_C,-15);
    }
    else
    {
      RotateMotorEx(OUT_BC, speed, x/d*52, 100, true, true);
      OnRev(OUT_B,-15);
      OnRev(OUT_C,-18);
    }
    Wait(500);
    
    // едем до линии
    while(Sensor(IN_3) >= 46 && Sensor(IN_2) >= 44)
    {
      if (Sensor(IN_3)<=46)
      {
        Off(OUT_C);
      }
      if(Sensor(IN_2)<=44)
      {
        Off(OUT_B);
      }
    }
    Off(OUT_BC);
    Wait(1000);
    // пересекаем линию
    RotateMotorEx(OUT_BC, 25, 200, 0, true, true);
    Wait(1000);
    // разворот для продолжения движения по линии
    if (MotorRotationCount(OUT_B)>MotorRotationCount(OUT_C))
    {
      RotateMotorEx(OUT_BC, 25, x/d*90, -100, true, true);
    }
    else
    {
      RotateMotorEx(OUT_BC, 25, x/d*30, 100, true, true);
    }
  }
}

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

float speed,es,D1,D2,e,u,PD,Pk,speednew1,speednew2;
long time1,time2,time3;
float es1,PD1,PD2,D,es2,e2,e1,u1,u2,Pk1,Pk2;x,d;y;
bool t,r;
int robot;

task main()
{
  // инициализация дальномера
  SetSensorLowspeed(IN_4);
  // направление синхронного разворота
  r = true;
  // номер робота 1 или 2
  robot = 1;
  // у нас в роботах разные датчики, поэтому разная инициализация
  if(robot == 2)
  {
    SetSensorLight(IN_2);
    SetSensorLight(IN_3);
    // один робот будет убегать
    t = true;
  }
  else
  {
    SetSensorColorRed(IN_2);
    SetSensorColorRed(IN_3);
    // другой робот будет догонять
    t = false;
  }

  while(true)
  {
    // размер колеи
    x = 180;
    // диаметр колес, мм
    d = 43.2;

    if (t == true)
    {
      // таймер для синхронного разворота
      time2 = CurrentTick();
      // таймер для периодической смены скорости
      time1 = CurrentTick();
      // первые 2 секунды робот равняется, вперед не едет
      speed = 0;
      // "старая" ошибка Д-регулятора
      es = 0;
      // пропорциональный коэф-т
      Pk = 2;
      // дифференциальный коэф-т
      PD = 4;
      // таймер для 2-секундного выравнивания перед началом движения 
      time3 = CurrentTick();
      // запоминаем, было ли уже выравнивание
      y = 0;
      // фактическая средняя скорость      
      speednew1 = 0;

      while(true)
      {
        // через две секунды начинаем движение после выравнивания
        if(CurrentTick() > time3+2000 && y == 0)
        {
          speednew1 = speed + Random(80);
          y = 1;
        }

        // смена ролей между роботами каждые 29 секунд
        if(CurrentTick() > time2+29000)
        {
          break;
        }
        
        // каждые 5 секунд смена скорости
        if(CurrentTick() > time1+5000)
        {
          speednew1 = speed + Random(80);
          time1 = CurrentTick();
        }

        // показания датчиков
        D1 = Sensor(IN_3);
        D2 = Sensor(IN_2);
        // ошибка регулятора
        e = D2-D1;
        // управляющее воздействие ПД-регулятора
        u = e*Pk+PD*(e-es);
        // подаем его на моторы
        OnFwd(OUT_B,u+speednew1);
        OnFwd(OUT_C,-u+speednew1);
        // запоминаем ошибку для Д-регулятора
        es = e;
      }
      // моторы - стоп
      Off(OUT_BC);
      Wait(1000);
      // смена ролей - разворот
      if (r == true) RotateMotorEx(OUT_BC, 30, x/d*150, 100, true, true);
      else RotateMotorEx(OUT_BC, 30, x/d*150, -100, true, true);
      Wait(1000);
      t = !t;
      r = !r;
    }
    else
    {
      time2 = CurrentTick();
      time3 = CurrentTick();
      speed = 0;
      es1 = 0;
      PD1 = 0;
      Pk1 = 2.5;
      es2 = 0;
      Pk2 = 2;
      PD2 = 6;
      y = 0;

      while(true)
      {
        if(CurrentTick()> time3+2000 && y == 0)
        {
          speed = 50;
          y = 1;
        }
        if(CurrentTick() > time2+29000)
        {
          break;
        }

        D = SensorUS(IN_4);
        D2 = Sensor(IN_2);
        D1 = Sensor(IN_3);
        
        // ограничиваем зону видимости робота - 60 см
        if (D > 60)
        {
          D = 60;
        }

        // здесь у нас целых два регулятора
        // один ведет по линии
        // второй - подстраивает скорость движения
        e2 = D2 - D1;
        e1 = D - 40;
        u2 = e2*Pk2+PD2*(e2-es2);
        u1 = e1*Pk1+PD1*(e1-es1);
        OnFwd(OUT_B,speed+u1+u2);
        OnFwd(OUT_C,speed+u1-u2);
        
        es1 = e1;
        es2 = e2;
      }
      Off(OUT_BC);
      Wait(1000);
      // смена ролей - разворот
      if (r == true) RotateMotorEx(OUT_BC, 30, x/d*150, 100, true, true);
      else RotateMotorEx(OUT_BC, 30, x/d*150, -100, true, true);
      Wait(1000);
      t = !t;
      r = !r;
    }
  }
}

На видео можно посмотреть как роботы решают эти задачи:



Самое популярное