Искусственный интеллект. Обучаем робота фиксировать и реагировать на дорожный знак «Ограничение максимальной скорости 20, 40, 60, 90»

В этом уроке:

  • Более компактный способ записи программ
  • Установка знака «Ограничение максимальной скорости» на трассу
  • Распознаём знак и меняем после него скорость

Более удобный способ записи кода программы

Поскольку мы предполагаем использование довольно большого разнообразия знаков, предлагаем воспользоваться конструкцией switch-case. Она позволит удобнее записывать информацию о знаках в коде программы.

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

switch (var) {
    case label:
      // выполняется, когда var равно 1
      break;
    case label:
      // выполняется, когда  var равно 12
      break;
    default:
      // выполняется, если не выбрана ни одна альтернатива
      // default необязателен
  }

В данной конструкции выполняются кейсы (первый кейс — строки 2-4, второй — строки 5-7 и т.д).  В случае, если метка этого кейса label равна переменной var в операторе switch, то выполняется код данного кейса. 

Проще говоря, происходит сравнение var и label, и если они равны, кейс выполняется. 

Если не выполнился ни один кейс, то выполнится кейс «по умолчанию», записанный как default (8-10 строка). Его использование не обязательно.

Оператор break (4, 7 строки) служит для выхода из кейса после его выполнения. 

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

Аналог записи через if

Давайте рассмотрим один и тот же код программы, записанный через хорошо знакомый нам оператор if и через switch.

Совсем скоро нам необходимо будет знать, от какого модуля получен сигнал: знак это, светофор или другой автомобиль. Информация об источнике сигнала хранится в переменной device, обратиться к которой можно следующим образом: ir.device.

Блок кода для этой задачи будет выглядеть следующим образом:

if (ir.device == MODUL_TLIGHT){
  // Данные отправлены светофором
}
else if (ir.device == MODUL_SIGN){
  // Данные отправлены дорожным знаком
}
else if (ir.device == MODUL_CAR){
  // Данные отправлены другим автомобилем
}

Аналогично записанный код через switch будет выглядеть так:

switch (ir.device) {
    case MODUL_TLIGHT:
      // Данные отправлены светофором
      break;
    case MODUL_SIGN:
      // Данные отправлены дорожным знаком
      break;
    case MODUL_CAR:
      // Данные отправлены другим автомобилем
      break;
  }

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

Настройка и установка знака на трассу

Знак «Ограничение скорости» встречается в нашем наборе в четырёх вариациях: на 20, 40, 60, и 90 км/ч, поэтому ранее мы ввели именно такие значения скорости.

Обычно скорость устанавливается, исходя из следующих соображений:

  • 90 км/ч  — скорость вне населённых пунктов или на скоростных дорогах;
  • 60 км/ч — скорость в населенных пунктах;
  • 40 км/ч — скорость на участках, где необходимо повышенное внимание водителя;
  • 20 км/ч — скорость вблизи школ, в жилых зонах и прочих местах, где на проезжей части могут присутствовать люди. 

Дорожный знак программируется с указанием скорости, которую он будет ограничивать. Подробнее о программировании знака читайте в статье о знаке «Ограничение максимальной скорости»Ваш знак должен быть запрограммирован на передачу информации об ограничении скорости 60 км/ч.

В прошлом уроке мы научились преодолевать участок дороги после знака «Опасные повороты». Там машинка снижала скорость до 40 км/ч. Давайте установим знак ограничения скорости 60 км/ч после завершения это участка трассы. Фактически, так действие предыдущего знака будет отменено. 

Учим машинку реагировать на знак

Мы добавили в скетч конструкцию switch-case для определения устройства, отправляющего сигнал. Всё в точности, как мы говорили выше. 

При обнаружении знака 3.24 «Ограничение максимальной скорости» машинка должна изменить скорость и продолжить движение со скоростью, указанной на знаке.

if( !strncmp( ir.sign_str, "3.24", 4) ){               // Обнаружен знак ПДД 3.24 «Ограничение макс скорости»:
  disp.print(ir.sign[2]);                              // Выводим цифру скорости
  if( ir.sign[2]< 3 ){ speed = min_Speed; }else        // ir.sign[2] < 3 значит, на знаке написано меньше «30»
  if( ir.sign[2]< 5 ){ speed = low_Speed; }else        // ir.sign[2] < 5 значит, на знаке написано меньше «50»
  if( ir.sign[2]< 7 ){ speed = mid_Speed; }else        // ir.sign[2] < 7 значит, на знаке написано меньше «70»
  if( ir.sign[2]!=0 ){ speed = max_Speed; }            // ir.sign[2] != 0 значит, на знаке написано «90»
}     

В условии сравнения знаков (95 строка) цифра 4 означает, что сравниваются первые 4 символа строк. Таким образом, мы получаем номер знака (3.24).

Запись ir.sign[2] позволяет получить пункт знака, или, в нашем случае, информацию об указанной на нём скорости. Например, цифра 6 обозначает ограничение скорости 60 км/ч.

Выводим эту цифру на дисплей (96 строка). И ограничиваем скорость в зависимости от считанной цифры (97-100 строки).

Готовый скетч программы

#include <Wire.h>                         // Подключаем библиотеку для работы с аппаратной шиной I2C
#include <iarduino_I2C_Motor.h>           // Подключаем библиотеку для работы с мотором  I2C-flash
#include <iarduino_I2C_Bumper.h>          // Подключаем библиотеку для работы с бампером I2C-flash
#include <iarduino_I2C_IR.h>              // Подключаем библиотеку для работы с Trema модулями: ИК-приёмник/передатчик I2C-flash
#include <iarduino_I2C_Matrix_8x8.h>      // Подключаем библиотеку для работы с LED матрицей 8x8 I2C-flash
                                         
iarduino_I2C_Motor mot_R (0x0A);          // Объявляем объект mot_R для правого мотора, указав адрес модуля на шине I2C
iarduino_I2C_Motor mot_L (0x0B);          // Объявляем объект mot_L для правого мотора, указав адрес модуля на шине I2C
iarduino_I2C_Bumper bum (0x0C);           // Объявляем объект bum  для работы с бампером I2C-flash, указав адрес модуля на шине I2C
iarduino_I2C_Matrix_8x8 disp (0x0D);      // Объявляем объект disp для работы с светодиодной матрицей I2C-flash, указав адрес модуля на шине I2C
iarduino_I2C_IR ir(0x09);                 // Объявляем объект ir для работы с функциями и методами библиотеки iarduino_I2C_IR, указывая адрес модуля на шине I2C
 
                                            
const float min_Speed = 20;               // Минимальная  скорость движения в км/ч. Используется при движении вблизи школ, а также на опасных участках дороги
const float low_Speed = 40;               // Низкая скорость движения в км/ч. Используется при движении по опасным участкам дороги или при соответствующем знаке
const float mid_Speed = 60;               // Средняя скорость движения в км/ч. Используется при движении по дорогам
const float max_Speed = 90;               // Максимальная скорость движения в км/ч. Используется при движении по скоростным дорогам
 
float speed = mid_Speed;                  // Указываем, что изначально скорость равна средней
int8_t  val_Turn      = 0;                // Выбранное направление движения на перекрёстке: 0 прямо, -1 влево, +1 вправо
bool    flg_CrossWait = true ;            // Флаг ожидания перекрёстка (машина «увидела» светофор или знак, сообщающий о наличии перекрёстка)
bool    flg_CrossFind = false;            // Флаг обнаружения перекрёстка (бампер заехал на перекрёсток)
bool    flg_Turn = false;                 // Флаг необходимости разворота                  
 
//   1.12.1 «Опасные повороты»            //  Изображение знака «Опасные повороты»
byte Image_1_12_1[8] = { 0b00000000,      //
                         0b00000010,      //       #
                         0b00100010,      //   #   #
                         0b01010010,      //  # #  #
                         0b01001010,      //  #  # #
                         0b01000100,      //  #   #
                         0b01000000,      //  #
                         0b00000000 };    //
void setup() {
  mot_R.begin();                          // Инициируем работу с левым  мотором I2C-flash
  mot_L.begin();                          // Инициируем работу с правым мотором I2C-flash
  bum.begin();                            // Инициируем работу с бампером I2C-flash
  disp.begin();                           // Инициируем работу с светодиодной матрицей I2C-flash
  disp.codingDetect("п");                 // Выполняем автоопределение кодировки скетча
  mot_R.setDirection(true);               // Указываем правому мотору, что его вращение должно быть прямым (по часовой стрелке при положительных скоростях)
  mot_L.setDirection(false);              // Указываем левому мотору, что его вращение должно быть обратным (против часовой стрелки при положительных скоростях)
  ir.begin();                             // Инициируем работу с ИК-приёмником/передатчиком I2C-flash
  ir.setProtocol(IR_IARDUINO);            // Указываем протокол для приёма/передачи данных по ИК-каналу
}
void loop() { 
//    РАЗВОРОТ:
//if (bum.getCross(3, 1000)) flg_Turn = true;// Если обнаружен перекрёсток, устанавливаем флаг разворота
if( flg_Turn ){                            // 1 ФАЗА. Если под бампером обнаружен перекрёсток (указываем толщину линии трассы и время на преодоление перекрёстка в мс) ...
                                           //         Под бампером обнаружен перекрёсток:
  flg_Turn = false;                        //         Сбрасываем флаг разворота
  mot_R.setSpeed( convertSpeed(min_Speed), MOT_PWM );    // Устанавливаем скорость правого мотора в %
  mot_L.setSpeed(-convertSpeed(min_Speed), MOT_PWM );    // Устанавливаем скорость левого мотора в %
                                           //         Ждём завершение манёвра:
  while( bum.getLineSum() > 0 ) {;}        // 2 ФАЗА. Ждём выхода линии за пределы бампера. Цикл выполняется, пока под бампером есть линия
  while( bum.getLineSum() == 0 ){;}        // 3 ФАЗА. Ждём появления линии под бампером. Цикл выполняется, пока под бампером нет  линии
                                           //         Останавливаемся:
  mot_R.setStop();                         //         Останавливаем правое колесо
  mot_L.setStop();                         //         Останавливаем левое колесо
  val_Turn  = 0;                           // 4 ФАЗА. Выбираем движение прямо
  speed = mid_Speed;                       //         Снимаем наложенные ранее ограничения скорости
}
 
//   ПРОВЕРКА НАЛИЧИЯ ПЕРЕКРЁСТКОВ:                                       
  if( bum.getCross(3,1000) ){              // Если под бампером обнаружен перекрёсток (указываем толщину линии трассы и время на преодоление перекрёстка в мс) ...                            
    if( flg_CrossWait ){                   // Если ожидается появление перекрёстка ...
      flg_CrossWait = false;               // Сбрасываем флаг ожидания перекрёстка
      flg_CrossFind = true;                // Устанавливаем флаг обнаружения перекрёстка
      disp.clrScr();                       // Чистим экран светодиодной матрицы
    }                                                               
  }
else{                                      // Если под бампером обычная линия ...          
     if (flg_CrossFind) {                  // Если ранее был обнаружен перекрёсток 
       flg_CrossFind = false;              // Сбрасываем флаг обнаружения перекрёстка
       val_Turn = 0;                       // Выбираем движение прямо
       speed = mid_Speed;                  // Снимаем наложенные ранее ограничения скорости
     }                                                               
  }
 
if( val_Turn == -1 ){ bum.setTurnSignal(BUM_TURN_LEFT ); }     //  Если поворот будет осуществляться налево, включаем левый поворотник
if( val_Turn == 0 ) { bum.setTurnSignal(BUM_TURN_OFF  ); }     //  Выключаем поворотники, если машинка движется прямо
if( val_Turn == 1 ) { bum.setTurnSignal(BUM_TURN_RIGHT); }     //  Если поворот будет осуществляться направо, включаем правый поворотник 
  
//   ПРОВЕРКА НАЛИЧИЯ ПРИНЯТЫХ ИК ДАННЫХ:                                  
  if( ir.check(true) ){                                        //  Если принят пакет данных от знака
    switch (ir.device) {
      case MODUL_TLIGHT:                                       // Данные отправлены светофором
                                                               //  Пока тут пусто, светофор не подключали
      break;
        
      case MODUL_SIGN:                                         // Данные отправлены дорожным знаком
        if(!strcmp(ir.sign_str, "1.12.1")){                    //  Если номер знака 1.12.1 - «Опасные повороты»          
          speed = min_Speed;                                   //  Ограничиваем скорость до ближайшего перекрёстка или нового знака
          disp.drawImage(Image_1_12_1);                        // Выводим изображение знака
        }
        if( !strncmp( ir.sign_str, "3.24", 4) ){               // Обнаружен знак ПДД 3.24 «Ограничение максимальной скорости»:
          disp.print(ir.sign[2]);                              // Выводим цифру скорости
          if( ir.sign[2]< 3 ){ speed = min_Speed; }else        // ir.sign[2] < 3 значит, на знаке написано меньше «30»
          if( ir.sign[2]< 5 ){ speed = low_Speed; }else        // ir.sign[2] < 5 значит, на знаке написано меньше «50»
          if( ir.sign[2]< 7 ){ speed = mid_Speed; }else        // ir.sign[2] < 7 значит, на знаке написано меньше «70»
          if( ir.sign[2]!=0 ){ speed = max_Speed; }            // ir.sign[2] != 0 значит, на знаке написано «90»
        }   
      break;
         
      case MODUL_CAR:                                          // Данные отправлены другим автомобилем
                                                               // Пока тут пусто, с другими машинками не взаимодействуем
      break;
    }     
  }
 
//   ОПРЕДЕЛЯЕМ ОШИБКУ ЦЕНТРИРОВАНИЯ ЛИНИИ ПОД БАМПЕРОМ:                   
//   Если ожидается перекрёсток: машина должна ехать по центру линии, немного сместившись в сторону поворота
     float bum_Error;                                                      // Объявляем переменную: ошибка для П-регулятора
     if( flg_CrossWait ){ bum_Error = bum.getErrPID()+val_Turn;   }else    // Получаем ошибку центрирования линии, смещённую на 1 датчик в сторону ожидаемого поворота «val_Turn»
//   Если бампер заехал на перекрёсток:                                    // Машина должна ехать по краю линии, выполняя поворот
     if( flg_CrossFind ){ bum_Error = bum.getSidePID(val_Turn)*3; }else    // Получаем ошибку нахождения на краю линии, со стороны поворота «val_Turn». Умножаем ошибку — это увеличит резкость поворота (актуально для тонких линий трассы)
//   Если не ждём перекрёсток и не находимся на нём:                       // Машина должна ехать по центру линии
                        { bum_Error = bum.getErrPID();            }        // Получаем ошибку центрирования линии
                        
  float kP = 3 + 0.125*(convertSpeed(speed)-20);        // Коэффициент П-регулятора  
  float P = bum_Error * kP;                             // Получаем значение от П-регулятора
  mot_R.setSpeed(convertSpeed(speed) - P, MOT_PWM);     // Устанавливаем скорость правого мотора 
  mot_L.setSpeed(convertSpeed(speed) + P, MOT_PWM);     // Устанавливаем скорость левого мотора 
}
 
float convertSpeed(float speedLevel){                   // Функция преобразования скорости из км/ч в % ШИМ
  if (speedLevel == 0) return 0;                        // Если скорость равна 0, возвращаем 0
  else if (speedLevel < 20) return 20;                  // Если скорость меньше минимальной, устанавливаем минимальное заполнение ШИМ
  else if (speedLevel > 90) return 60;                  // Если скорость больше максимальной, устанавливаем максимальное заполнение ШИМ
  else return map(speedLevel, 20, 90, 20, 60);          // Преобразование диапазона остальных скоростей
}
Поздравляю с изучением данного урока!
Следующий урок:
№10. Получаем информацию о знаке «Дети».
приступить к изучению

Продукт в магазине

Комплект знаков для базового курса по "Роботраффику" для образования (продолжение ROBORACE)

В магазин

Обсуждение

Гарантии и возврат. Используя сайт, Вы соглашаетесь с условиями.