В этом уроке:
- Устанавливаем знак «Движение без остановки запрещено» на трассу
- Учимся откладывать момент выполнения действия
- Функции задержки вывода символов для дисплея
- Добиваемся корректной работы со знаком

Установка знака на трассу
В прошлом уроке мы установили знак «Конец всех ограничений», и теперь наша машинка движется со скоростью 60 км/ч. Но впереди нас ждёт железнодорожный переезд, и, чтобы убедиться в безопасности движения, необходимо остановиться, после чего можно двигаться дальше.
Давайте установим знак «Движение без остановки запрещено» на отведённое дня него место. Не забывайте предварительно его собрать и запрограммировать, если еще этого не сделали. Теперь трасса выглядит следующим образом:

Учим машинку корректно реагировать на знак
Мы привыкли выполнять ключевое действие (например, изменение скорости) сразу же при фиксации знака. При этом не забывайте, что его регистрация происходит не один раз, а многократно — несколько раз в секунду. В случае, когда мы говорим об изменении скорости, в этом нет проблемы. Переменная скорости просто будет перезаписываться на новое (такое же) значение.
Однако, в данном случае автомобиль должен остановиться. Причём сделать это не сразу при фиксации знака, а чуть позже, ведь машинка зафиксирует знак примерно за 20 см до переезда.
Таким образом, если мы используем наш стандартный алгоритм, машинка зафиксирует знак, остановится, тронется с места, снова остановится, и так рывками будет выбираться из зоны действия знака. Поэтому нам необходимо немного усложнить алгоритм работы с данным знаком. Давайте посмотрим код:
if( !strcmp( ir.sign_str, "2.5" )){ // Если номер знака 2.5 - «Движение без остановки запрещено»
if (!flg_25) { // Если не установлен флаг знака (знак зафиксирован первый раз)
timeSign = millis(); // Сбрасываем значение переменной времени обнаружения знака
flg_25 = true; // Устанавливаем флаг знака
}
При фиксации знака (126 строка) проверяем, не зафиксировали ли мы его ранее (127 строка). Если нет, отмечаем время и меняем флаг (128, 129 строки).
Далее, уже в блоке основного кода, независимо от приёма сигнала от знака, проверяем, прошло ли время время (3 секунды) в случае, если флаг знака установлен, т.е. мы работаем сейчас с ним (140 строка). Через 3 секунды условие сработает, а флаг сбросится.
// Срабатывание по времени знака «Движение без остановки запрещено»
if(flg_25 && timeSign + 3000 < millis()){ // Если флаг установлен, и с момента последней фиксации прошло больше 3 сек
flg_25 = false; // Сбрасываем флаг
disp.setTimeIdleFirst(100); // Указываем бегущей строке задерживаться на первом символе в течение 100 мс (допускаются значения от 0 до 2550 мс)
disp.setTimeIdleLast(100); // Указываем бегущей строке задерживаться на последнем символе в течение 100 мс (допускаются значения от 0 до 2550 мс)
disp.scrollPos(0); // Переходим к началу бегущей строки
disp.print("STOP"); // Загружаем текст для бегущей строки
disp.autoScroll(245); // Выводим загруженный текст однократно со скоростью 245
mot_R.setSpeed( 0, MOT_PWM ); // Выключаем правый мотор
mot_L.setSpeed( 0, MOT_PWM ); // Выключаем левый мотор
delay(2000); // Задержка 2 сек (остановка)
}
Теперь основной код: мы добавили две необязательные функции (142,143 строки) для отображения текста на дисплее с небольшими задержками на первом и последнем символе. Остальная работа с дисплеем аналогична работе в предыдущих уроках.
Далее останавливаем моторы (147,148 строки) и выполняем задержку (149 строка). Мы используем delay(), хотя ранее писали, что его использование крайне нежелательно, — контроллер не может параллельно выполнять никаких действий. Но в данном случае мы уверены, что никаких действий выполнять не нужно, — машинка просто стоит на месте. Поэтому в данной ситуации мы упростили код, обойдясь без введения новых переменных для другого таймера.
Меняя значения времени в 140 и 149 строках, можно изменить время движения после первого обнаружения знака и остановки соответственно. Вам наверняка потребуется сделать это, если у Вас установлено напряжение питания моторов выше минимального (подробнее об этом мы писали в первой главе).
Готовый скетч урока:
#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; // Флаг необходимости разворота
bool flg_25 = false; // Флаг повторной фиксации знака 2.5 «Движение без остановки запрещено»
uint32_t timeSign; // Время для отсчёта времени перед повторной фиксацией знака
// 1.12.1 «Опасные повороты» // Изображение знака «Опасные повороты»
byte Image_1_12_1[8] = { 0b00000000, //
0b00000010, // #
0b00100010, // # #
0b01010010, // # # #
0b01001010, // # # #
0b01000100, // # #
0b01000000, // #
0b00000000 }; //
// 3.31 «Конец зоны ограничений» //
byte Image_3_31[8] = { 0b00111100, // ####
0b01000010, // # #
0b10000101, // # # #
0b10001001, // # # #
0b10010001, // # # #
0b10100001, // # # #
0b01000010, // # #
0b00111100 }; // ####
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»
}
if( !strcmp( ir.sign_str, "1.23" ) ){ // Eсли номер знака 1.23 - «Дети»
disp.scrollPos(0); // Переходим к началу бегущей строки
disp.print("Дети"); // Загружаем текст для бегущей строки
disp.autoScroll(245); // Выводим загруженный текст однократно со скоростью 245
speed = min_Speed; // Ограничиваем скорость до ближайшего перекрёстка или нового знака
}
if( !strcmp( ir.sign_str, "3.31" ) ){ // Если обнаружен знак ПДД 3.31 «Конец зоны ограничений»
disp.drawImage(Image_3_31); // Выводим изображение знака
speed = mid_Speed; // Снимаем наложенные ранее ограничения скорости
}
if( !strcmp( ir.sign_str, "2.5" )){ // Если номер знака 2.5 - «Движение без остановки запрещено»
if (!flg_25) { // Если не установлен флаг знака (знак зафиксирован первый раз)
timeSign = millis(); // Сбрасываем значение переменной времени обнаружения знака
flg_25 = true; // Устанавливаем флаг знака
}
}
break;
case MODUL_CAR: // Данные отправлены другим автомобилем
// Пока тут пусто, с другими машинками не взаимодействуем
break;
}
}
// Срабатывание по времени знака «Движение без остановки запрещено»
if(flg_25 && timeSign + 3000 < millis()){ // Если флаг установлен, и с момента последней фиксации прошло больше 3 сек
flg_25 = false; // Сбрасываем флаг
disp.setTimeIdleFirst(100); // Указываем бегущей строке задерживаться на первом символе в течении 100 мс (допускаются значения от 0 до 2550 мс)
disp.setTimeIdleLast(100); // Указываем бегущей строке задерживаться на последнем символе в течении 100 мс (допускаются значения от 0 до 2550 мс)
disp.scrollPos(0); // Переходим к началу бегущей строки
disp.print("STOP"); // Загружаем текст для бегущей строки
disp.autoScroll(245); // Выводим загруженный текст однократно со скоростью 245
mot_R.setSpeed( 0, MOT_PWM ); // Выключаем правый мотор
mot_L.setSpeed( 0, MOT_PWM ); // Выключаем левый мотор
delay(2000); // Задержка 2 сек (остановка)
}
// ОПРЕДЕЛЯЕМ ОШИБКУ ЦЕНТРИРОВАНИЯ ЛИНИИ ПОД БАМПЕРОМ:
// Если ожидается перекрёсток: машина должна ехать по центру линии, немного сместившись в сторону поворота
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); // Преобразование диапазона остальных скоростей
}
Обсуждение