Показаны сообщения с ярлыком NXT. Показать все сообщения
Показаны сообщения с ярлыком NXT. Показать все сообщения

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

LEGO UART. Бит за битом

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


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


Основная наша задача в данном проекте - разобраться с тем, а что же это такое - двоичное число, как перевести привычное нам десятичное чисто в двоичное и наоборот. Но, стоп! Это же такая скукота, зачем это вообще нужно? Давайте будем действовать от реальной задачи.
Мы построили двух одинаковых роботов - Правого и Левого. Они - полные зеркальные копии друг друга. Кстати, вы тоже можете собрать таких, а чтобы облегчить вам задачу мы подготовили и выложили инструкцию по их сборке в формате LEGO Digital Designer.


Роботы друг с другом никак не соединены, но им нравится стоять рядом, ведь вместе они создают очень симметричную композицию. Мы будем поворачивать ротор у Правого робота и симметрия будет нарушаться. Задача Левого робота - поддерживать эту симметрию, поворачивая свой ротор в тоже положение, в которое повернут ротор у Правого.



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

0 - светодиод выключена
1 - светодиод включен

Если нам нужно передать число большее 1, на помощь придет двоичный код. Посмотрите на таблицу ниже, число 2 будет представлено как 10, число 6 как 110, а число 12 как 1100.


Этот принцип сохраняется и для больших чисел, просто последовательности 0 и 1 будут длиннее. Для перевода из десятичного числа в двоичное есть несколько способов, например вот такой:


Итак, угол поворота ротора в двоичном виде мы получили, как теперь его передать? Светодиод у Правого робота и датчик освещенности у Левого позволяют нам создать такое распространенное устройство как Универсальный асинхронный приёмопередатчик (УАПП, англ. Universal Asynchronous Receiver-Transmitter, UART) — узел вычислительных устройств, предназначенный для организации связи с другими цифровыми устройствами. Преобразует передаваемые данные в последовательный вид так, чтобы было возможно передать их по одной физической цифровой линии другому аналогичному устройству. 
Передача данных в UART осуществляется по одному биту в равные промежутки времени. Этот временной промежуток определяется заданной скоростью UART и для конкретного соединения указывается в бодах (что в данном случае соответствует битам в секунду).

Для начала давайте проведем небольшой эксперимент, будем включать и выключать в цикле светодиод датчика и посмотрим за сколько выполнится 1000 таких переключений: В конструкции роботов мы использовали блоки LEGO NXT, для их программирования воспользуемся языком NXC:

int t;

task main()
{
  t = 0;
  while(t < 1000)
  {
    t = t + 1;
    SetSensorLight(IN_3,true);
    SetSensorLight(IN_3,false);
  }
  ClearScreen();
  NumOut(0, LCD_LINE4, CurrentTick());
  Wait(10000);
}

Запустив эту программу мы с удивлением обнаружили цифру 42000. Значит каждое полное переключение занимает целых 42000/1000 = 42 мс, а переключение из режима в режим вероятно около - 42/2 = 21 мс. Это довольно много, быстрый UART нам не создать. С другой стороны наш проект учебный, для нас главное - наглядность и понимание принципов работы устройства, гигабитных скоростей будем достигать совсем в других проектах :).

Теперь давайте напишем программу для Правого, передающего робота. 

// угол, на который повернут ротор у Правого робота
int a;
// длительность одного бита данных, мс
int t;
// массив для хранения числа a в двоичной форме
bool b[8];
// в этой переменной запоминаем горит ли светодиод на датчике
bool y;

task main()
{

  while(true)
  {
    // светодиод на датчике выключен
    y = false;
    // сохраняем в переменной а угол поворота ротора
    a = MotorRotationCount(OUT_B);
    // Это время в мс, в течении которого передается 1 бит информации
    t = 200;

    // ограничиваем а (угол поворота ротора) пределами 0..255
    if (a < 0)
    {
      a = 0;
    }
    // Мы будем передавать только 1 байт, это максимум 255
    if(a > 255)
    {
      a = 255;
    }

    // Очищаем экран и выводим на него a в десятичной форме
    ClearScreen();
    NumOut(0,13,a);

    // Переводим a в двочиный вид и сохраняем в массив b[8]    
    // выводим на экран также двоичиное пердставление числа
    for (int i=7; i >= 0; i--)
    {
      if (a / pow(2, i) >= 1)
      {
         b[i] = true;
         a = a - pow(2,i);
         NumOut((7-i)*8,0,1);
      }
      else
      {
         b[i] = false;
         NumOut((7-i)*8,0,0);
      }
    }

    // включаем светодиод и передаем стартовый бит
    SetSensorLight(IN_3,true);
    y = true;
    // Время передачи меньше на 21 мс, именно столько включается светодиод
    Wait(t-21);

    // передаем байт данных бит за битом мз массива b[8]
    for(int i = 7;i >= 0;i--)
    {
      if(b[i] == true)
      {
        SetSensorLight(IN_3,true);
        // длительность задержки различна, в зависимости от того горел ли светодиод
        if(y == true)
        {
          Wait(t-1);
        }
        else
        {
          Wait(t-21);
        }
        y = true;
      }
      else
      {
        SetSensorLight(IN_3,false);
        if(y == false)
        {
          Wait(t-1);
        }
        else
        {
          Wait(t-21);
        }
        y = false;
      }
    }

    // включаем светодиод и передаем стоповый, 10-й, бит
    SetSensorLight(IN_3,true);
    if(y == true)
    {
      Wait(t-1);
    }
    else
    {
      Wait(t-21);
    }
    y = true;

    // выключаем светодиод, готовимся к следующей итерации цикла
    SetSensorLight(IN_3,false);
    y = false;
    Wait(t*4);
  }
}

После запуска программы робот начинает бодро моргать светодиодом датчика, передавая бит за битом показания энкодера на моторе с ротором. Теперь второму, Левому роботу, нужно принять эту двоичную посылку и перевести ее в десятичную форму. Принцип такого перекодирования также довольно прост:

После того как число переведено в десятичную форму вступит в дело ПД-регулятор и развернет ротор принимающего робота в тоже положение, что и у передающего.

Программа для Левого, принимающего робота у нас выглядит следующим образом.

// принятое число в десятичной форме
int a, a_tmp;
// массив для хранения принятого числа в двоичной форме
bool b[8];
// длительность одного бита данных, мс
int t;
// управляющее воздействие ПД-регулятора
int u;
// пропорциональный коэтф-т ПД-регулятора
int pk;
// дифф.коэф-т ПД-регулятора
int dk;
// "старая" ошибка в дифф.компоненты ПД-регулятора
int es;
// данные с датчика
int D1;
// ошибка ПД-регулятора
int e;

// ПД-регулятор устанавливает ротор в положение a градусов
task motor()
{
  // значение коэф-тов ПД-реглятора
  pk = 2;
  dk = 4;
  es = 0;
  while(true)
  {
    // вычисляем ошибку (отклонение от целевой величины)
    e = a - MotorRotationCount(OUT_B);
    // формируем управляющее воздействие
    u = e*pk+dk*(e-es);
    // juhfybxbdftv управляющее воздействие диапазоном -100..100
    if(u < -100)
    {
      u = -100;
    }
    if(u > 100)
    {
      u = 100;
    }
    // опдаем управляющее воздействие на мотор
    OnFwd(OUT_B,u);
    // сохраняем значение ошибки
    es = e;
  }
}

task main()
{

  SetSensorLight(IN_3,false);
  a = 0;
  t = 200;
  start motor;
  while(true)
  {
    // ждем стартовый бит
    while(Sensor(IN_3)<65);
    // ждем первый бит данных
    Wait(t+t/2);

    // считываем все биты данных и записываем в массив b[8]
    for(int i = 7;i >= 0;i--)
    {
      if(Sensor(IN_3)>65)
      {
        b[i] = true;
        Wait(t);
      }
      else
      {
        b[i] = false;
        Wait(t);
      }
    }
    // очищаем экран
    ClearScreen();
    // если стоповый бит не получен - ошибка приема-передачи   
    if(Sensor(IN_3)<65) TextOut(0, LCD_LINE6, "ERROR");
    
    // выводим на экран принятое двоичное число
    for(int i = 7;i >= 0;i--)    
    {
      if(b[i] == true)
      {
         NumOut((7-i)*8,0,1);
      }
      else
      {
         NumOut((7-i)*8,0,0);
      }
    }

    // переводим число в десятичное
    a_tmp = 0;
    for(int i = 7;i >= 0;i--)
    {
      if(b[i] == true)
      {
        a_tmp = a_tmp + 1 * pow(2, i);

      }
    }
    a = a_tmp;
    // выводим десятичное число на экран
    NumOut(0,13,a);
    Wait(t*2);
  }
}

С учетом стартовых и стоповых битов скорость нашего UART составляет 4 бита/сек.

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





суббота, 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;
    }
  }
}

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



суббота, 12 ноября 2016 г.

Решение задач № 17 и 20


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


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

Программа для решения задачи на языке NXC:

// Создаем переменные
float x,d;
int speed,es,D1,D2,e,u,PD,Pk;

task main()
{
  // Инициализируем датчики
  SetSensorLight(IN_2); // Датчик освещенности слева 2й порт
  SetSensorLight(IN_3); // Датчик освещенности справа 3й порт
  SetSensorLowspeed(IN_4); // Ультразвуковой датчик - 4й порт

  x = 180; // Ширина колеи
  d = 43.2; // Диаметр колес робота
  speed = 70; // Средняя скорость 
  es = 0; // "старая" ошибка ПД - регулятора
  Pk = 2; // Пропорциональный коэф-т ПД-регулятора
  PD = 4; // Дифференциальный коэф-т ПД-регулятора
  
  OnFwdSync(OUT_BC, 20, -100); // разворачиваемся в поисках банки
  while(SensorUS(IN_4) >= 50); // ожидаем банку
  while (SensorUS(IN_4) <= 50); // ожидаем потерю банки
  Off(OUT_BC); // останавливаем моторы
  // разворачиваемся для выезда рядом с банкой
  RotateMotorEx(OUT_BC, speed, x/d*85, -  100, true, true);
  Wait(50);
  OnFwdSyncEx(OUT_BC, 50, 0,RESET_NONE);
  // едем вперед пока не обнаружена линия
  while(Sensor(IN_3) >= 44 && Sensor(IN_2) >= 40.5);
  Off(OUT_BC); // останавливаемся
  Wait(50);
  // немного вперед, чтобы развернуться над линией
  RotateMotorEx( OUT_BC , speed , 200 , 0 , true , true );
  // поворачиваем вправо, УЗ-датчик смотрит в круг
  RotateMotorEx(OUT_BC , speed , x / d * 75, -100, true , true );
  // цикл для 4 банок
  for(int i=0; i<4; i++)
  {
    // пока нет банки - едем по линии на ПД-регуляторе
    while(SensorUS(IN_4) >=10)
    {
      // показания с датчиков
      D1 = Sensor(IN_3);
      D2 = Sensor(IN_2);
      // ошибка регулятора - разница показаний
      e = D2-D1;
      // управляющее воздействие по формуле ПД-регулятора
      u = e*Pk+PD*(e-es);
      // рассчитанную мощность подаем на моторы
      OnFwd(OUT_B,u+speed);
      OnFwd(OUT_C,-u+speed);
      // сохраняем ошибку для следующей итерации цикла
      es = e;
    }
    // останавливаемся при обнаружении банки
    Off(OUT_BC);
    // поворачиваемся к банке
    RotateMotorEx(OUT_BC , speed , x / d * 95, -100, true , true );
    Wait(50);
    // заталкиваем банку в центр круга
    RotateMotorEx( OUT_BC , speed , 1100 , 0 , true , true );
    Wait(50);
    // возвращаемся на линию
    RotateMotorEx( OUT_BC , -1*speed , 1100 , 0 , true , true );
    // разворачиваемся влево, чтобы продолжить движение
    RotateMotorEx(OUT_BC , speed , x / d * 87, 100, true , true );
  }
}

// создаем много переменных
int B_enkoder,C_enkoder,D1,D2,trigger;
bool out;
int e,u,Pk,PD,speed,es;
float d,x;

task main()
{
  // инициализируем датчики освещенности
  SetSensorLight(IN_2); 
  SetSensorLight(IN_3);

  x = 180; // ширина колеи
  d = 43.2; // Диаметр колес робота
  Pk = 2; // Пропорциональный коэф-т ПД-регулятора
  PD = 6; // Дифференциальный коэф-т ПД-регулятора
  speed = 75; // Средняя скорость 

  while(true)
  {
    // сбрасываем показания энкодеров
    ResetRotationCount(OUT_B);
    ResetRotationCount(OUT_C);
    // сохраняем показания энкодеров
    B_enkoder = 0;
    C_enkoder = 0;    
    es = 0; // "старая" ошибка ПД - регулятора
    out = true; // по изменению этой переменной будем вылетать из цикла
    Wait(10);
    while(out)
    {
      // показания с датчиков
      D1 = Sensor(IN_3);
      D2 = Sensor(IN_2);
      // ошибка регулятора - разница показаний
      e = D2-D1;
      // управляющее воздействие по формуле ПД-регулятора
      u = e*Pk+PD*(e-es);
      // рассчитанную мощность подаем на моторы
      OnFwd(OUT_B,u+speed);
      OnFwd(OUT_C,-u+speed);
      // сохраняем ошибку для следующей итерации
      es = e;
      // если проехали половину круга - вылетаем из цикла
      if ((MotorRotationCount(OUT_B)+MotorRotationCount(OUT_C))/2*(d*PI/360) >= (1070*PI)/2)
      {
        out = false;
      }
    }
    // стоп моторы
    Off(OUT_BC);
    // это пауза 50 мс
    Wait(50);
    
    // смотрим на каком энкодере набралось больше показаний
    // в зависимости от этого разворачиваемся к соответствующую
    // сторону и сохраняем в trigger
    if (MotorRotationCount(OUT_B)-B_enkoder>MotorRotationCount(OUT_C)-C_enkoder)
    {
      RotateMotorEx(OUT_BC, speed, x/d*90, -100, true, true);
      trigger = 1;
    }
    else
    {
      RotateMotorEx(OUT_BC, speed, x/d*90, 100, true, true);
      trigger = 2;
    }
    // выравниваемся о линию     OnRev(OUT_B,15);
    OnRev(OUT_C,15);
    while(Sensor(IN_3) >= 46 && Sensor(IN_2) >= 44)
    {
      if (Sensor(IN_3)<=44)
      {
        Off(OUT_C);
      }
      if(Sensor(IN_2)<=40.5)
      {
        Off(OUT_B);
      }
    }
    Off(OUT_BC);
    Wait(50);
    // едем через круг
    OnFwdSyncEx(OUT_BC, speed, 0,RESET_NONE);
    while(Sensor(IN_3) >= 44 && Sensor(IN_2) >= 40.5)
    {
    }
    // при обнаружении линии - останавливаемся
    Off(OUT_BC);
    Wait(50);
    // немного вперед, чтобы развернуться над линией
    RotateMotorEx(OUT_BC, speed, 200, 0, true, true);
    // в trigger - направление разворота
    if (trigger == 1)
    {
      RotateMotorEx(OUT_BC, speed, x/d*90, 100, true, true);
    }
    else
    {
      RotateMotorEx(OUT_BC, speed, x/d*90, -100, true, true);
    }
    // сохраняем показания энкодеров
    B_enkoder = MotorRotationCount(OUT_B);
    C_enkoder = MotorRotationCount(OUT_C);
  }
}










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