Графички програмски језик који стандардно долази са Лего ЕВ3 комплетом је одличан за учење основних концепата у програмирању, међутим како програми постају сложенији, тако и програмирање превлачењем све више постаје ограничавајући фактор. Постоји више независних пројеката који омогућавају програмирање ЕВ3 робота на неком од популарних програмских језика. Већина пројеката је везанa за одређени програмски језик, као што је на пример LeJOS везан за Javu. Пројекат ev3dev је посебно занимљив јер омогућава програмирање ЕВ3 робота на било којем програмском језику који је доступан на ARM архитектури и који моће да чита и пише фајлове.

Оперативни систем

Лего ЕВ3 је прави компјутер баш као и Ваш PC рачунар, таблет или мобилни телефон са којег читате ову страницу. Исто као у случају са другим компјутерским системима, програми које пишете за ЕВ3 се извршавају уз помоћ оперативног система. Оперативни систем је скуп програма и рутина одговоран за контролу и управљање уређајима и рачунарским компонентама као и за обављање основних системских радњи. Оперативни систем обједињује у целину разнородне делове рачунара и сакрива од крајњег корисника детаље функционисања ових делова. Оперативни систем ствара за корисника радно окружење које рукује процесима и датотекама, уместо битовима, бајтовима и блоковима.

Линукс

Неки од популарних оперативних система су: Windows, OS X, iOS, Android, Linux.. Велика је вероватноћа да сте већ користили Линукс, а да можда то нисте ни знали. На пример у основи Андроид оперативног система налази се Линукс језгро. Већина STB уређаја за IPTV или кабловску телевизију ради под неком верзијом Линукса. Линукс је веома стабилан и прилагодљив систем и уз то је бесплатан, односно слободан. Програмери имају слободу да мењају Линукс према својим потребама. Различите прилагођене верзије Линукс система се називају дистрибуцијама.

Лего је при пројектовању ЕВ3 робота направио своју Линукс дистрибуцију. То је технички детаљ који приликом програмирања робота из графичког окружења није потребно знати, тако да Лего својој дистрибуцији није дао чак ни име, већ је једноставно назвао firmware. Када Лего графичко окружење тражи апдејтовање фирмвера, то у ствари значи да тражи апдејтовање Линукс оперативног система.

ev3dev

Ев3Дев је Линукс дистрибуција која се може користити уместо стандардног фирмвера. Стандардна дистрибуција је добра за покретање Лего графичког окружења, међутим она не омогућава једноставно инсталирање окружења за извршавање програма писаних на вишим програмским језицима као што су PHP, Python, C++ и други. За разлику од стандардне Лего дистрибуције, ев3дев се заснива на популарном Дебијан Линуксу, на којем су такође засновани и Убунту и Raspbian (оперативни систем за Raspberry Pi). Већина ствари које се могу урадити на Распбиану, уз помоћ ев3дева се могу урадити и на ЕВ3. Укратко, ев3дев дистрибуција омогућава већину ствари које нормалан Линукс може, док стандардна дистрибуција то не омогућава.

Инсталација

За покретање ев3дев дистрибуције потребна је microSD картица. Инсталациони имиџ фајл се може преузети са ГитХаб странице. У тренутку писања овог чланка, актуелна верзија је ev3dev-jessie-2014-10-07. На сајту ев3дев пројкта доступно је упутство за распаковање имиџ фајла на микро СД картицу из Виндоус система. Када је имиџ система распакован довољно је убацити микро СД картицу у одговарајући слот, па затим укључити Ев3 циглу. Ев3дев Линукс ће аутоматски бити покренут по укључењу. Уколико желите да се вратите на оригинални фирмвер, довољно је да искључите ЕВ3, извадите СД картицу и поново укључите уређај.

Повезивање са PC рачунаром

Ев3 је могуће повезати са ПЦ рачунаром на неколико начина: путем УСБ кабла, мрежног кабла са УСБ адаптером, вифи усб адаптера и блу тута. На ев3дев сајту доступна су упутства за подешавање УСБ везе са Виндоус и Линукс системима. За комуникацију са ЕВ3 рачунаром могу се користити SSH клијент Putty, а за рад са фајловима Filezilla или WinSCP. Након успешног повезивања, за пријаву на систем може се искористити администраторски, налог root и подразумевана лозинка r00tme.

Контрола мотора и сензора

Као што је у уводу овог чланка напоменуто ев3дев није везан за одређени програмски језик. То је остварено тако што се за контролу периферних уређаја (мотора, сензора, звука, лед индикатора, екрана) користи једноставан приступ фајловима. Ако бисмо прикључили два мотора на портове Б и Ц, систем би аутоматски креирао два нова директоријума, односно симболичка линка, која садрже фајлове за контролу наведених мотора.

cd /sys/class/tacho-motor

Уколико се командом cd уђе у директоријум /sys/class/tacho-motor, па затим излиста његов садржај командом ls, може се приметити да се у директоријуму налазе два поддиректоријума, motor0 и motor1

ls

На слици изнад приказан је садржај директоријума motor0. Сваки од приказаних фајлова повезан је са одређеним својством мотора. Контрола над мотором се остварује читањем и уписивањем одговарајућих вредности у фајлове и то на исти начин као што се ради са било којим другим фајлом. Уколико желимо да прочитамо на који је порт прикључен motor0, довољно је да прикажемо садржај фајла port_name уз помоћ команде cat.

cat port_name

Покретање мотора се може обавити у два корака:

echo 30 > duty_cycle_sp
echo 1 > run

Прва линија подешава снагу мотора, док друга даје сигнал мотору да почне са радом. Задатак наредбе echo се користи за приказивање линије текста на стандардном излазу. У овом случају излаз наредбе echo је преусмерен у фајл duty_cycle_sp, односно run. Преусмеравање је постигнуто коришћењем симбола >. Наредбом echo 1 > run  брише се комплетан садржај фајла run и уписује 1. Да је требало додати текст на крај фајла, без брисања претходног садржаја, користило би се оператор за преусмеравање >>. Мотор се може покренути и тако што би се јединица уписала у фајл из текст едитора, покренутог из фајл менаџера.

Слично као и у случају мотора, за које систем има посебан директоријум /sys/class/tacho-motor, за сензоре је резервисан директоријум /sys/class/msensor/.

На слици изнад, приказан је садржај директоријума sensor1. Систем је регистровао као sensor1 жироскопски сензор. Очитана вредност из фајла value0 представља угао прочитан са сензора. Уколико ротирате робота, заједно са сензором, узастопним очитавањем фајла можете приметити како се вредност за угао мења.

Програмирање робота

Јасно је да се директном манипулацијом фајловима из командне линије не може програмирати робот. Потребан је програмски језик са одговарајућом библиотеком функција и класа и окружење које ће омогућити покретање програма написаних на том језику. Библиотека функција и класа је важна јер скрива од програмера детаље и поједностављује рад. Ев3дев дистрибуција стандардно укључује следеће програмске језике:

  • bash/dash
  • awk/gawk
  • perl
  • Lua
  • guile
  • ruby
  • python
  • Google Go (golang)
  • Node.js

Међутим, програмер може да инсталира и друга окружења и језике и да напише своје функције за контролу сензора, мотора и осталих периферних уређаја. Пошто се програмски језик PHP не налази у листи подржаних, одлучио сам да инсталирам PHP интерпретер и направим једноставну класу са функцијама за контролу робота. Класа није комплетна и покрива само мали део функционалности система. Изворни код класе Ev3 биће приказан у целости на крају текста, а пре тога следи програм који користи ту класу:

<?php //oznaka za pocetak php koda. Tekst pisan iza // je komentar i ne izvrsava se

//ukljucujem kompletnu datoteku ev3.php. U njoj se nalaze definisane funkcije.
require "ev3.php"; 
/**
Definisem funkciju koja uzima tri parametra:
    $r objekat tipa Ev3
    $snaga tipa integer, odnosno celi broj
    $vreme celobrojna vrednost koja oznacava koliko milisekundi robot treba da ide pravo
Funkcija se ne izvrsava dok je ne pozovem. 
Obratiti paznju na znak $, ovaj znak oznacava promenljivu
*/
function idiPravo($r, $snaga, $vreme)
{
    //pozivam funkciju objekta $r. Za pristup javnim funkcijama i promenljivama objekta koristi se operator ->
    $r->izgovoriTekst("Zdravo svete");
    //Funkcija izgovoriTekst je definisana u okviru klase Ev3. Ulazni parametar funkcije je string, odnosno niz znakova.
    $r->izgovoriTekst('Idem pravo');
    //$naEngleskom je takodje promenljiva, kojoj sam dodelio string 'Strait ahead'
    //obratiti paznju na to da se stringovi pisu pod znacima navoda "tekst" ili pod apostrofima 'tekst'.
    $naEngleskom = 'Strait ahead';
    $r->izgovoriTekst($naEngleskom);
    //podesavam snagu motora koji je prikljucen na port B. PORT_B je konstanta definisana u okviru klase Ev3.
    $r->snagaMotora(Ev3::PORT_B, $snaga);
    //isto i za motor C. Inace, konstante, kao sto im i ime kaze se za razliku od promenljivih 
    //ne mogu menjati nakon sto im se prvi put dodeli vrednost.
    $r->snagaMotora(Ev3::PORT_C, $snaga);
    //pokrecem motor koji je prikljucen na port B. Drugi parametar funkcije je opcioni. Da nije naveden motor bi radio neograniceno
    $r->pokreniMotor(Ev3::PORT_B, $vreme);
    //isto kao i B
    $r->pokreniMotor(Ev3::PORT_C, $vreme);
}
//ovaj kod se izvrsava posto nije u okviru funkcije:

//kreiram novi objekat klase Ev3. Klasa Ev3 je definisana u okviru datoteke ev3.php
$robot = new Ev3();
//pozivam funkciju i predajem joj vrednosti za $r, $snaga i $vreme
idiPravo($robot, 100, 2000);

//oznaka za kraj php koda:
?>

Најевећи део програма су коментари, чији је једини циљ да објасне код. За случај да коментари одвлаче пажњу, ово је исти програм без сувишних делова:

<?php 
require "ev3.php"; 
function idiPravo($r, $snaga, $vreme)
{
	$r->izgovoriTekst('Idem pravo');
	$r->snagaMotora(Ev3::PORT_B, $snaga);
	$r->snagaMotora(Ev3::PORT_C, $snaga);
	$r->pokreniMotor(Ev3::PORT_B, $vreme);
	$r->pokreniMotor(Ev3::PORT_C, $vreme);
}
$robot = new Ev3();
idiPravo($robot, 100, 2000);

Ако бисмо сачували програм под именом pravo.php у директоријум ~,  програм би могао да се изврши из командне линије на следећи начин:

cd ~
php pravo.php

Симбол ~ означава кориснички директоријум. За root корисника то јe /root, док је за остале кориснике на Дебијан систему најчешће /user/korisnicko-ime. Командом php позива се PHP програм, односно интерпретер, који чита команде из пхп фајла и извршава их.

Програм користи неколико функција, дефинисаних у оквиру класе Ev3: Ev3(), izgovoriTekst, snagaMotora и pokreniMotor. Комплетна листа јавних функција, променљивих и константи које су доступне у оквиру класе Ev3 приказана је у следећем прозору:

//konstante su definisane kako bi pisanje programa bilo jednostavnije
//na primer mnogo je jasnije ako u programu pise 
//SONAR_SENZOR umesto ev3-uart-30
const SNAGA = 'duty_cycle_sp';
const POKRENI = 'run';	
const MOD = 'run_mode';

const SONAR_SENZOR = 'ev3-uart-30';
const ZIRO_SENZOR = 'ev3-uart-32';
const BOJE_SENZOR = 'ev3-uart-29';
const DODIR_SENZOR = 'lego-ev3-touch';

const PORT_A = 'outA';
const PORT_B = 'outB';
const PORT_C = 'outC';
const PORT_D = 'outD';	

const PORT_1 = 'in1';
const PORT_2 = 'in2';
const PORT_3 = 'in3';
const PORT_4 = 'in4';	

//konstruktor, funkcija koja se poziva svaki put kada se 
//kreira novi objekat date klase 
public function __construct()

//Ucitava listu fajlova koji odgovaraju prikljucenim uredjajima
//Automatski se poziva ako je lista motora ili senzora prazna
public function init()

//Putanja do direktorijuma motora koji je prikljucen na naznaceni port
public function getMotor($port)

//Putanja do direktorijuma senzora koji je prikljucen na naznaceni port
public function getSenzor($port)

//Postavlja snagu navedenog motora
public function snagaMotora($port, $snaga)

//Pokrece motor koji je prikljucen na $port
//Promenljiva $vreme je opciona i ako se ne navede
//uzima podrazumevanu vrednost false
//Promeljive logickog tipa mogu da imaju vrednost true ili false
public function pokreniMotor($port, $vreme = false)

//Zaustavlja motor koji je prikljucen na $port
public function zaustaviMotor($port)

//ocitava vrenost senzora koji je prikljucen na $port
public function ocitajSenzor($port)

//podesava jacinu tona
public function jacinaTona($jacina)
    
//Poziva program za sintezu glasa espeak i predaje mu $tekst
public function izgovoriTekst($tekst)
    

Комплетна класа Ev3 је приказана у следећем прозору:

<?php
class Ev3
{
	
	protected $motori = [];
	protected $senzori = [];
	
	const SNAGA = 'duty_cycle_sp';
	const POKRENI = 'run';	
	const MOD = 'run_mode';
	
	const PORT_A = 'outA';
	const PORT_B = 'outB';
	const PORT_C = 'outC';
	const PORT_D = 'outD';	
	
	const PORT_1 = 'in1';
	const PORT_2 = 'in2';
	const PORT_3 = 'in3';
	const PORT_4 = 'in4';	
	
	public function init()
	{
		$this->motori = $this->listaUredjaja('/sys/class/tacho-motor/');
		$this->senzori = $this->listaUredjaja('/sys/class/msensor/');
	}
	
	public function podesiModSenzora($port, $mod)
	{
		$putanja = $this->getSenzor($port);
		$this->upisi($putanja . 'mode', $mod);
	}	
	
	public function getMotor($port)
	{
		if(empty($this->motori))
			$this->init();
		return $this->motori[$port];
	}
	
	public function getSenzor($port)
	{
		if(empty($this->senzori))
			$this->init();
		return $this->senzori[$port];		
	}
	
	/**
	* @return Vraca niz direktorijuma koji sadrze mmap fajlove motora, npr. [ 'outC' => '/sys/class/tacho-motor/motor0', 'outB' => '/sys/class/tacho-motor/motor1']
	*/
	protected function listaUredjaja($klasaDir)
	{
		$uredjaji = [];
		if ( ! ($handle = opendir($klasaDir)))
			return [];
		while( ($uredjajDir = readdir($handle)) !== false )
		{
			if($uredjajDir == '.' || $uredjajDir == '..')
				continue;
			$uredjaj = $klasaDir . $uredjajDir . '/';
			$port = trim($this->ocitaj("{$uredjaj}port_name"));
			$uredjaji[$port] = $uredjaj;
		}	
		closedir($handle);
		return $uredjaji;
	}
	
	protected function upisi($putanja, $vrednost)
	{
		$handle = fopen($putanja, 'w');
		if($handle === false)
			throw new \Exception('Greska prilikom otvaranja fajla!');
		fwrite($handle, $vrednost);
		fclose($handle);
	}

	protected function ocitaj($putanja)
	{
		$handle = fopen($putanja, 'r');
		if($handle === false)
			throw new \Exception('Greska prilikom otvaranja fajla!');	
		$ret = fread($handle, filesize($putanja));
		fclose($handle);
		return $ret;
	}
	
	public function snagaMotora($port, $snaga)
	{
		$motor = $this->getMotor($port);
		
		$snaga = intval($snaga);
		if($snaga < 0){
			echo 'motor ide unazad';
			$this->upisi($motor. 'polarity_mode', 'inverted');			
		}else{
			echo 'motor ide unapred';
			$this->upisi($motor. 'polarity_mode', 'normal');			
		}
		$this->upisi($motor . self::SNAGA, abs($snaga));
	}
		
	public function pokreniMotor($port, $vreme = false)
	{
		$motor = $this->getMotor($port);
		if( ! $motor)
			return;
		if( $vreme !== false)
		{
			$this->upisi($motor. self::MOD, 'time');
			$this->upisi($motor . 'time_sp', $vreme);
		}
		else
			$this->upisi($motor. self::MOD, 'forever');
		$this->upisi($motor . self::POKRENI, 1);
	}
	
	public function pokreniMotorPozicija($port, $pozicija, $blokiraj=false, $pulses_per_second_sp=300, $ramp_up_sp=50, $ramp_down_sp=50)
	{
		$motor = $this->getMotor($port);
		if( ! $motor)
			return;
		$this->upisi($motor.self::MOD, 'position');
		$this->upisi($motor.'position', 0);		
		$this->upisi($motor.'regulation_mode', 'on');
		$this->upisi($motor.'stop_mode', 'brake');		
		$this->upisi($motor.'ramp_up_sp', $ramp_up_sp);
		$this->upisi($motor.'ramp_down_sp', $ramp_down_sp);
		$this->upisi($motor.'pulses_per_second_sp', $pulses_per_second_sp);
		$this->upisi($motor.'position_sp', $pozicija);
		$this->upisi($motor.'position_mode', 'absolute');
		//$this->upisi($motor.'hold_mode', 'on');
		$this->upisi($motor . self::POKRENI, 1);
		if($blokiraj){
			$this->blokirajDokRadiMotor($port);
		}
	}
	
	public function motorRadi($port)
	{
		return intval($this->ocitaj($this->getMotor($port) . self::POKRENI));
	}
	
	public function blokirajDokRadiMotor($port)
	{
		while($this->motorRadi($port)){}
	}
	
	public function pozicijaMotora($port)
	{
		$motor = $this->getMotor($port);
		return $this->ocitaj($motor.'position');
	}
		
	public function zaustaviMotor($port)
	{
		$motor = $this->getMotor($port);
		if( ! $motor)
			return;
		$this->upisi($motor . self::POKRENI, 0);		
	}

	public function ocitajSenzor($port)
	{
		$senzor = $this->getSenzor($port);
		if( ! $senzor)
			return false;
		return $this->ocitaj($senzor . 'value0');
	}	

	public function jacinaTona($jacina)
	{
		if($jacina>100)
			$jacina = 100;
		if($jacina<0)
			$jacina = 0;
		shell_exec("amixer sset 'Playback' $jacina%");
	}
		
	public function izgovoriTekst($tekst)
	{
		shell_exec("espeak -s 120 -v sr '$tekst' --stdout | aplay");
	}
	
}

 

Ивица
11.01.2015
Одељци: Зид Чланци Ивица | Кључне речи:
0

Коментари:

Нови коментар

©Библиотека++ 2017 Развој сајта Ивица Лазаревић