Bu yazımızda Magnetometre kullanarak aracımızın yönünü tespit edeceğiz ve bu işlemi Stellaris LM3S811 üzerinde gerçekleştireceğiz. Modül arasındaki haberleşme protokolleri ise I2C ve UART üzerinden olacaktır. Bu uygulama ile birlikte aracımızın nereye baktığını bulabilecek ve böylelikle hedef konuma göre yönünü ne tarafa çevirmesi gerektiğini yorumlayabileceğiz.

 

 

 

 

Magnetometer İle Yön Tayini

Yön tayinine neden ihtiyacımız var gibi bir soru sorulabilir. Bu sorunun cevabı şu şekilde özetlenebilir; GPS üzerinden şu an bulunduğumuz kordinatı alabiliyoruz aynı zamanda bir hedef kordinatın şu anki kordinatımıza göre yönünü de yine GPS ile alabiliyoruz fakat robotumuzun nereye baktığını GPS üzerinden okuyamıyoruz. Bu sorunu çözmek için bir pusula kullanacağız ve o pusula yardımıyla robotun yönünü belirleyebileceğiz. Eğer hedef kordinat şu anki konumumuzun batısında ise ve bizim robotumuzun önü doğuya bakıyorsa haliyle önce hedefe yönelmesi gerekecektir.

Uygulamamızda MAG3110 adlı modülü kullanacağız. Bu modül bize lokal olarak manyetik alan kordinatlarını verecektir.  Modül üzerindeki haberleşme protokolü, I2C üzerinden olduğu için bizde işlemcimizi bu protokole göre ayarlayıp modül işlemci bağlantılarını yapacağız.

I2C Haberleşmesi

I2C haberleşmesinin tercih sebebi ,hızlı olması, aynı anda bir çok cihazla haberleşebilmesi ve kısa mesafeli bir protokol olması olarak özetlenebilir. Üzerinde 2 adet kablo bulunur. Bunlar SCL(Serial Clock) ve SDA(Serial Data) olarak adlandırılırlar. Ve bu kablolar yardımıyla cihaz ile haberleşmesini sağlar.

Bu iki kabloda open-drain şeklinde sürülürler. Bunun anlamı kablolar ‘High’ durumdayken çalışmaz, ‘Low’ durumdayken sürülmeye başlarlar. Haliyle bu sistemi pull-up direnç yardımıyla desteklemek gerekir. Kullandığımız modülün şematiğine bakacak olursak pull-up dirençler modül üzerinde olduğu için biz böyle bir güncelleme yapmaya gerek duymadık.

Şekilde de görebileceğimiz gibi işlemciye ne kadar cihaz bağlanırsa bağlansın birer adet pull-up direnç yeterli olacaktır. Peki 2 adet kablo hangi cihazla haberleşeceğini nereden bilecek diye sorabiliriz. Her cihazın bir adresi vardır. Ve bu adresler cihazın datasheeti üzerinde yazar. Örneğin bizim modülümüzün adresi 0x0E dir.

Sistemde bir master birde slave vardır. Master sistemi kontrol eden cihazdır. Her zaman SCL bacağını master sürer. SCL bacağı devamlı clock sinyali veren kısımdır. SDA bacağı ise bilgiyi çift taraflı taşır. Hem master, slave üzerine SDA üzerinden veri gönderebilir hemde slave veri gönderebilir. Hiç bir zaman slave data transferini başlatamaz. Her zaman master işlemi başlatır ve bitirir. Başlangıçta hangisinin master ya da slave olacağını elbette tanımlamamız gerekecektir. I2C haberleşmesi belirli aşamalarla olur.  Önce start gelir daha sonra veri gönderilir ve ardından stop gelir. Gönderilen veri 8 bitlik olup her seferinde bu işlem tekrarlanır.

Start yada stop işlemi SCL high durumda iken yani clock 1 durumunu gösteriyorken SDA üzerindeki verinin değişmesi ile olur. SCL low konumda iken data transferi gerçekleşir ve SDA buna tüm transfer boyunca uyar. Bitler SDA yoluna her zaman MSB(Most Significant Bit) den başlayarak verilir.

 

Sistemde her data alış verişinden sonra SCL üzerinde ACK sinyali oluşur. Bu sinyali Slave oluşturur ve haliyle 8 bitlik veri aktarımında scope üzerinde 9 adet clock görmemiz gerekir. ACK bitinin 0 gelmesi slave in hala veri istediği anlamına gelirken 1 gelmesi artık slave alacağını aldı stop bitini gönder anlamına gelmektedir.

Modül adresleri genellikle 7 bit tir. Mesela bizim cihazımızın adresi 7 bitlik bir 0x0E dir. Fakat data transferinin 8 bit olduğu söyledik. Kalan son bit ise bize okuma yada yazma yapacağımızı söyleyen bittir. Eğer 0 gönderiyorsak master slave üzerine veri gönderiyor, 1 gönderiyorsak master slave üzerinden okuma yapacak anlamına gelmektedir.

Yazılım Protokolü

  • Master tarafından start sinyali gönderilir,
  • Master cihazın adresini gönderir,
  • Master Slave üzerinde yazmak yada okumak istediği registerın adresini gönderir,
  • Yazma yapacaksa registera yazacağı değeri gönderir, okuma yapacaksa registerdan gelen değeri okur,
  • Data transferini bitirdikten sonra stop bitini gönderir şeklindedir.

Stellaris Üzerinde I2C Haberleşmesi

Öncelikle stellaris üzerinde kullanacağımız çevresel aygıtları tanımlamamız gerekiyor. Gelen verileri kontrol etmek ve okuyabilmek için UART haberleşmesinden faydalanacak ve ekrana değerleri basacağız. Ayrıca stellaris üzerindeki mini lcd ile bu verileri anlaşılır halde göstermeye çalışacağız. Kullanacağımız çevresel aygıtlar;

  • UART0
  • I2C
  • LCD Ekran olacaktır. Öncelikle datasheet üzerinden gerekli pinlere bakıp cihaz tanımlamalarını ve çevresel aygıt tanımlamalarını yapıyoruz.
init_mcu   
Display96x16x1Init(false);//LCD ekran
 
	//
    // 	  Osilatör ayarı
    //
    SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN |
                   SYSCTL_XTAL_6MHZ);
 
	//
    // I2C0 çevresel aygıtını enable yap
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);
 
    //
    // I2C PortB[3:2] pinleri üzerinde
    // 
 
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
	GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_2 | GPIO_PIN_3);
 
    //
    // Master Modülü tanımla
	// En sağdaki parametre false olursa 100kbps true olursa 400kbps hızında data rate oluyoe
    //
    I2CMasterInitExpClk(I2C0_MASTER_BASE, SysCtlClockGet(), false);
//
    // UART0 PortA[1:0] kullanıldığı için bu çevresel aygıtı ENABLE yapıyoruz
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
 
    //
    // Konsol I/O için UART tanımlaması yap
    //
    UARTStdioInit(0);
 

Cihazımızı hazırladıktan sonra modül üzerindeki registerlara göndereceği verileri, modül ayarlarını yapmamız gerekiyor. modülün datasheeti üzerindeki ayarlardan faydalanarak kendimize 80Hz hızında veri gönderecek şekilde modülü ayarlamalarımızı yapıyoruz. Bu işlemleri modül üzerindeki özel registerlara özel değerlerini yazarak gerçekleştiriyoruz.

Resimdeki ayarlardan faydalanarak cihaz ayarlarımızı yapıyoruz. Yukarıda belirtilen registerların önce adreslerini stellaris üzerine göndereceğiz daha sonra adreslerine yazacağımız verileri göndereceğiz.

I2C Scope üzerinde çıktılarını resimde görebiliriz. 0x0E adresinin gönderildiğini saymak mümkün.

config   
void
Config()
{
	  unsigned long ulDataTx[NUM_I2C_DATA];
      int i=0;
	  //
	  //   80Hz hesaplama için ayarlamalar
	  //   1. Enable automatic resets by setting bit AUTO_MRST_EN in CTRL_REG2. (CTRL_REG2 = 0x80)
	  //   2. Put MAG3110 in active mode 80 Hz with OSR = 1 by writing 0x01 to CTRL_REG1 (CTRL_REG1 = 0x01)
	  //
 
	  ulDataTx[0] = 0x11;  //	CTRL_REG2 Adresi
      ulDataTx[1] = 0x80;  //	CTRL_REG2 Değeri
      ulDataTx[2] = 0x10;  //	CTRL_REG1 Adresi
      ulDataTx[3] = 1;	   //	CTRL_REG1 Değeri
 
 for(i=0;i<NUM_I2C_DATA;i++)
 {
	I2CMasterSlaveAddrSet(I2C0_MASTER_BASE, SLAVE_ADDRESS, false);	   //slave cihazın adresini gonder
	I2CMasterDataPut(I2C0_MASTER_BASE, ulDataTx[i]);		 //internal reg adresini sda ya koy
	I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_START);//start
	while(I2CMasterBusy(I2C0_MASTER_BASE))					//transfer bitene kadar bekle
	{
	}
	I2CMasterDataPut(I2C0_MASTER_BASE, ulDataTx[i+1]);					//reg in datasını sda ya koy
	I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH); //finish operation
	while(I2CMasterBusy(I2C0_MASTER_BASE))								  //transfer bitene kadar bekle
	{
	}
	i++;
  }

Gerekli ayarları yaptıktan sonra okuma işlemine geçebiliriz. Okuma işlemi için yine yukarıda olduğu gibi önce start biti daha sonra slave adresi daha sonra okuyacağımız register adresini göndereceğiz.  Main üzerinde bu okuma işlemini polling yöntemi ile yapacağız o yüzden main üzerinde while(1) döngüsü içinde yer alacaktır. 

read   
void
Read()
{
	unsigned long x1, y1, z1;
    unsigned char Data[6];
 
	I2CMasterSlaveAddrSet(I2C0_MASTER_BASE, SLAVE_ADDRESS, false);	//slave adresini okumak için gönder
	I2CMasterDataPut(I2C0_MASTER_BASE, 0x01);						//01 adresi x msbsi
	I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_SINGLE_SEND);	//gönder
	while(I2CMasterBusy(I2C0_MASTER_BASE))							//transfer bitene kadar bekle
	{
	}
 
	SysCtlDelay(SysCtlClockGet() / 3000);						   //delay
 
	I2CMasterSlaveAddrSet(I2C0_MASTER_BASE, SLAVE_ADDRESS, true);  //oku
	I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_RECEIVE_START);
	while(I2CMasterBusy(I2C0_MASTER_BASE))
    {
    }
	Data[0] = I2CMasterDataGet(I2C0_MASTER_BASE);
 
    I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_RECEIVE_CONT);
    while(I2CMasterBusy(I2C0_MASTER_BASE))
    {
    }
    Data[1] = I2CMasterDataGet(I2C0_MASTER_BASE);
 
        I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_RECEIVE_CONT);
 
	    while(I2CMasterBusy(I2C0_MASTER_BASE))
        {
        }
        Data[2] = I2CMasterDataGet(I2C0_MASTER_BASE);
 
        I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_RECEIVE_CONT);
        while(I2CMasterBusy(I2C0_MASTER_BASE))
        {
        }
        Data[3] = I2CMasterDataGet(I2C0_MASTER_BASE);
 
        I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_RECEIVE_CONT);
        while(I2CMasterBusy(I2C0_MASTER_BASE))
        {
        }
        Data[4] = I2CMasterDataGet(I2C0_MASTER_BASE);
 
        I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_RECEIVE_FINISH);
        while(I2CMasterBusy(I2C0_MASTER_BASE))
        {
        }
        Data[5] = I2CMasterDataGet(I2C0_MASTER_BASE);
 
        x1 = (Data[0] << 8) | Data[1];
        y1 = (Data[2] << 8) | Data[3];
        z1 = (Data[4] << 8) | Data[5];
	 //	UARTprintf("x: '%05d'", x1);
	//	UARTprintf(" y: '%05d'\n", y1);
		//UARTprintf(" z: '%05d'\n", z1);
 
	 if(counter == 100)
	 {
	 	getHeading(x1,y1,z1);
	 	counter=0;
	 }
	 counter++;
}

LCD ekran üzerinde anlamlı şeyler görebilmek adına fonksiyonun içerisinde getHeading fonksiyonunu tanımladık. Aldığımız değerler lokal manyetik alan değerleri olduğu için çok anlamlı çıktılar vermiyorlardı. Bizde çıktılara göre kendi sistemimizi tasarladık ve basit bir analiz fonksiyonu hazırladık.

void
getHeading(unsigned long x, unsigned long y, unsigned long z)
{
	 int direction=0;
 
	 if(x>3000 && x<3200) //batı
	 {
	 	if(y>63300 && y< 63450) //kuzey batı
	 	{
		   direction=1;
	 	  // UARTprintf("kuzey-batı\n");
		   Display96x16x1Clear();
		   Display96x16x1StringDraw("kuzey-bati", 0, 0);
		}
		else if(y>63450 && y<63615) // Batı
		{
		   direction=2;
		  // UARTprintf("batı\n");
		   Display96x16x1Clear();
		   Display96x16x1StringDraw("bati", 0, 0);
		}
		else if(y>63615 && y<63850) //güney batı
	 	{
		   direction=3;
		  // UARTprintf("güney-batı\n");
		   Display96x16x1Clear();
		   Display96x16x1StringDraw("guney-bati", 0, 0);
		}
	 }
	 else if(x>2200 && x<2880) //doğu
	 {
	 	if(y>63300 && y< 63500) //Kuzey doğu
	 	{
		   direction=4;
	 	//	UARTprintf("kuzey-doğu\n");
			Display96x16x1Clear();
			Display96x16x1StringDraw("kuzey-dogu", 0, 0);
		}
		else if(y>63500 && y<63700) // Doğu
		{
		   direction=5;
		  // UARTprintf("doğu\n");
		   Display96x16x1Clear();
		   Display96x16x1StringDraw("dogu", 0, 0);
		}
		else if(y>63700 && y<63900) //Güney doğu
	 	{
		   direction=6;
		  // UARTprintf("güney-doğu\n");
		   Display96x16x1Clear();
		   Display96x16x1StringDraw("guney-dogu", 0, 0);
		}
	 }
	 else if(x>2750 && x<3000) //Kuzey-Güney
	 {
	 	if(y>63700 && y<63850) // Güney
	 	{
		   direction=7;
	 	//	UARTprintf("güney\n");
			Display96x16x1Clear();
			Display96x16x1StringDraw("guney", 0, 0);
		}
		else if(y>63300 && y<63400)		//kuzey
		{
		   direction=8;
		  // UARTprintf("kuzey\n");
		   Display96x16x1Clear();
		   Display96x16x1StringDraw("kuzey", 0, 0);
		}
 
	 }
 
}

Böylelikle cihaz üzerinde magnetometreyi kullanabilir hale geldik . Analiz fonksiyonumuz çok hassas çıktılar vermese de işimizi görmektedir. Duruma göre o fonksiyon değiştirilip ayrıca ivme ölçer ile desteklenebilir.

Video üzerinde de görülebileceği gibi magnetometre ve telefon üzerindeki pusulayı aynı anda çalıştırıyoruz. Normalde bu şekilde yakın kullanınca magnetometre hatalı sonuçlar vermektedir. Fakat videoda göstermek açısından analiz fonksiyonunu bu video için değiştirdik. Ufak hatalarla şu an düzgün çalışmaktadır.

Son olarak bu yazıyı hazırlamamda katkısı olan Muhammed Fatih İNANÇ arkadaşıma yardımlarından dolayı teşekkür ederim.

 Proje Kodları