Hrvatsko društvo za robotiku - Uvod u robotiku - vježbe

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.