Naprijed u labirintu
Cilj
Pomaknuti robota za jedno polje naprijed.
Početak
void RobotMaze::moveAhead() {
bool encodersOver = false; // One tile's length covered.
Used only when encoders available.
// When encoders available, timeout is only a safety measure,
to avoid possible endless loop. When not, it marks end of tile.
static uint32_t startedAtMs;
if (setup())
startedAtMs = millis();
bool timeOver = millis() - startedAtMs > MOVE_AHEAD_TIMEOUT_MS;
...
}
U našem primjeru nećemo koristiti enkodere pa na početku potavimo tako varijablu "encodersOver".
Zapamtimo vrijeme polaska, koje ćemo koristiti za detekciju timeouta.
"timeOver" će postati "istina" ako je nastupio timeout. U prvom prolazu se to očito neće dogoditi.
Kraj kretanja
void RobotMaze::moveAhead() {
...
// If any of the 3 conditions satisfied, break the movement: encoder, timeout,
or a wall to close ahead.
if (encodersOver || timeOver ||
distance(directionCurrent, false) < WALL_FOLLOW_DISTANCE ||
distance(directionCurrent, true) < WALL_FOLLOW_DISTANCE) {
if (testMode)
motorGroup->go(0, 0),end();
else{
...
}
}
...
}
Proučimo trebamo li zaustaviti kretanje. To će se dogoditi, ako je nastupio bar jedan od uvjeta:
- enkoderi su odbrojali kraj ("encodersOver"), što ovdje ne koristimo,
- nastupio je timeout ("timeOver"),
- previše smo se približili zidu ispred, s jednim ili drugim prednjim senzorom.
U tom slučaju, zaustavljamo motore i program, ako smo u test načinu rada.
Drugi ćemo slučaj proučiti dolje.
Dolazna pločica
void RobotMaze::moveAhead() {
...
if (encodersOver || timeOver ||
distance(directionCurrent, false) < WALL_FOLLOW_DISTANCE
|| distance(directionCurrent, true) < WALL_FOLLOW_DISTANCE) {
if (testMode)
motorGroup->go(0, 0),end();
else{
// The robot's position changed so we need to
calculate new coordinates.
int8_t newX = tileCurrent->x;
int8_t newY = tileCurrent->y;
if (directionCurrent == UP)
newY++;
else if (directionCurrent == LEFT)
newX--;
else if (directionCurrent == DOWN)
newY--;
else
newX++;
print("Coord: (%i, %i) ", newX, newY); // Debug.
Tile* targetTile = tileContaining(newX, newY); //Search chain for
a tile already containing (x, y) - new robot's coordinates.
...
}
}
...
}
Sad proučimo dio u kojem treba završiti funkciju, ali nismo u testnom radu.
Prvo izračunamo nove x i y koordinate ("newX" i "newY"), iz postojećih i smjera kretanja.
Onda provjerimo postoji je već polje s tim koordinatama među prijeđenim pločicama.
Ako postoji, spremimo ga u "targetTile", dolaznu pločicu.
Kraj akcije
void RobotMaze::moveAhead() {
...
if (encodersOver || timeOver ||
distance(directionCurrent, false) < WALL_FOLLOW_DISTANCE ||
distance(directionCurrent, true) < WALL_FOLLOW_DISTANCE) {
if (testMode)
...
else{
...
if (targetTile != NULL) { // A tile
found. That means we returned to the already
visited tile.
print("tile exists\n\r"); // Debug.
tileCurrent = targetTile; // Set the position to
the found tile.
actionSet(actionDecide); // Movement over. The next
action will be decision what to do next.
No mapping is needed here as the
tile has already been mapped.
}
else { // No such a tile. Therefore, this coordinate has
not been visited yet.
We have to create a new Tile object.
print("new tile\n\r");
// Set the position to a newly created tile, with
new (x, y) coordinates. Breadcrumb direction
will be the opposite direction to the current
one.
tileCurrent = new Tile(newX, newY,
(Direction)((directionCurrent + 2) % 4));
actionSet(actionMap); // The next action will be tile
mapping as this is a new tile and its walls
are not mapped yet.
}
}
}
...
}
Ako "targetTile" nije "NULL", našli smo pločicu u lancu prijeđenih.
Kao ciljnu pločicu ćemo postaviti nađenu.
Postavljamo novu akciju, koja će završiti postojeću.
U suprotnom, ako "targetFile" jest "NULL", to je nova pločica.
Kreiramo novi objekt tipa "Tile", s koordinatama odredišne pločice i putem povratka prema postojećoj pločici.
Postavimo sljedeću akciju kao mapiranje i tamo ćemo unijeti novu pločicu u mapu.
Idi naprijed
void RobotMaze::moveAhead() {
if (encodersOver || timeOver ||
distance(directionCurrent, false) < WALL_FOLLOW_DISTANCE ||
distance(directionCurrent, true) < WALL_FOLLOW_DISTANCE) {
...
}
else { // End of tile not reached yet. Note that this part will
execute many times during one-tile trip.
Direction closestWall = wallClosest(); // Find the wall
most appropriate to align with.
if (closestWall == NOWHERE) // No alignable wall, the
movement can be corrected only by compass (IMU).
imuFollow();
else // Yes, a wall found in one direction. Follow it.
wallFollow(closestWall);
}
}
Vraćamo se na "else" od 1. "if"a.
To znači da pokret nije gotov.
Nađemo najbliži zid.
Ako ga nema, pokrećemo funkciju za praćenje kompasa ("imuFollow()").
Ako ga ima, pratimo taj zid ("wallFollow()"):
Test
void RobotMaze::moveAhead1TileTest(){
testMode = true;
directionCurrent = Direction::UP;
actionSet(actionMoveAhead);
}
Našu ćemo funkciju testirati pokretanjem "moveAhead1TileTest()".
Postavimo da je testni način rada, da idemo gore i pokrenemo akciju koja će izvršavati našu funkciju.
Zadatak: timeout.
U slučaju da nastupi timeout, ispišite to na 8x8 displeju.
Primjedbe
Projekt "Uvod u robotiku" sufinanciran je iz Europskog socijalnog fonda, poziv "Jačanje kapaciteta organizacija civilnoga društva za popularizaciju STEM-a".
Relevantne stranice:
Sadržaj vježbe za virtualne radionice isključiva je odgovornost Hrvatskog društva za robotiku.