Aracın hareket fonksiyonlarını yürütecek , önde ve arkada engel ve çukurlar denetleyecek sensörlerin kontrolünü yapacak olan ilk somut uygulamamızı STELLARİS LM3S811 ile gerçekleştirdik.Bu paylaşımda sırası ile :

* Pololu Dual MC33926 motor sürücüsü kullanımı

*PWM ile motor sürme

*Seri Port haberleşmesi ile hareket fonksiyonlarını birleştirme

* ADC yapılarak engel ve çukurların tespiti

hakkında uygulamalar paylaşacağız.

 

       

  GEREKLİ DÖKÜMANLAR

Uygulamaları hazırlarken sıklıkla kullandığımız dökümanlar :

LV-MaxSonar®-EZ3™

LGP2Y0A02YK

usingADC

driverlib user guide

LGP2Y0A02YK

EKI-LM3S811_sch

 

MOTOR SÜRÜCÜ DEVRESİ


Motor sürücü devresi işlemci ve motorlar arasında tampon devre olarak görev yapmaktadır. Motorların çekeceği bütün yük işlemcimizin pinlerinden bağımsız olarak harici voltaj kaynağından sağlanmaktadır. Kartımızın bağlantıları şu şekildedir:

VIN,GND Tampon devremizin ana besleme uçlarıdır.   5 ile 28 volat arasında potansiyel uygulayabiliriz. Uygulayacağımız giriş beslemesini mikroişlemcimizinki ile farklı olmasına dikkat etmemiz işlemcinin tutarlı çalışması için tavsiye edilmektedir. Motora binen yüklere paralel olarak  ana besleme voltajında meydana gelebilecek osilasyonlar başımıza bela olabilir. Burada dizaynımıza başlamadan önce dikkat edilmesi gerek bir konu daha var. eğer oluşturulan sistem batarya grubu dışında, sabit bir voltaj kaynağı ile , beslenecekse uygulanacak VIN voltajını belirli bir değere regüle etmemiz şart değil. Bu işlemi girişlerde uygulayacağımız pwm sinyallerinin duty değerleri ile yazılımda oynayarak da halletmemiz mümkün.

M1 OUT1, M1 OUT2 Projemizde kullandığımız şasenin bir sağında birde solunda olmak üzere iki adet motor bulunmaktadır.  M1 çıkış uçları direk motorumuzun tekine, M2 çıkış uçları dise diğer motorumuzu bağlanmaktadır.  M1 OUT1, M1 OUT2 uçlarından hangisini pozitif yada negatif alacağınız çokda önemli değildir. Motorun dönüş yönünü kodda ayarlamak daha hızlı bir çözüm yolu olacaktır.

-GND işlemci ile sürücü kartının referans noktalarının aynı olması için iki kartta da orak ground kullanılmalıdır. Aksi halde girişlerden uygulanan sinyallerin nicelikleri tutarsız olacağından bu durum direk motorların sürülmesine de yansıyacaktır.

-VDD Motor sürücü devresi ile işlemci arasında kullanılacak lojik 1 seviyesi tanımlanmış olmalıdır. STELLARİS LM3S811 de bu seviye 3.3 volt tur, bu yüzden VDD ye STELLARİS LM3S811 üzerindeki 3.3 volt çıkış ucu arasında bir bağlantı yapmalıyız.

-M1 IN1,M1 IN2M2 IN1,M2 IN2 uçlarını STELLARİS LM3S811 üzerindeki PWM0,PWM1,PWM2 VE PWM3 çıkışlarına bağlıyoruz. Burada da bağlantı sıranız önemli değil, dönme yönleri konusunda yazıcağımız kod bize kolaylıklar sağlayacaktır.

-M1 PWM/D2′  +3.3 volta bağlanır. Çıkış aktif hale getirilir

– M1PWM’/D1  grounda bağlanır. Çıkış aktif hale getirilir.

-EN +3.3 volta bağlanır

 

PWM FONKSİYONLARININ KULLANIMI

Yazının bu kısmında STELLARİS LM3S811 kiti üzerinde bulunan PWM çıkışlarını aktif hale getirmek ve aracın hareket kontrolünde kullanacağımız fonksiyonlardan bahsedeceğiz.

 

 

Yukarıdaki blok şemasında görüldüğü gibi STELLARİS LM3S811 kitinin 6 adet PWM çıkışı, temelde 3 adet PWM üretici modül tarafından sağlanmaktadır. Projemizde 4 adet PWM sinyali kullanacağımızdan ilk iki PWM üreticisi gerekli ayarlamalar yapılarak atif hale getirilir ve PWM OUTPUT CONTROL LOGIC  yapısı yardımı ile çıkış sinyalleri kontrol altında tutulur.Peki bu bahsettiklerimiz yazılım dünyasında nasıl sağlanır?

– İlk olarak işlemciye, PWM sinyal üretecek çevresel donanımın aktif hale gelmesi için tanımlama yapılır

SysCtlPeripheralEnable(SYSCTL_PERIPH_PWM);

 

– Çıkış olarak kullanacağımız pinler D ve B portlarının 0 ıncı ve 1 inci pinleri olduğu tanımlanır

GPIOPinTypePWM(GPIO_PORTD_BASE, GPIO_PIN_0 | GPIO_PIN_1);
GPIOPinTypePWM(GPIO_PORTB_BASE, GPIO_PIN_0 | GPIO_PIN_1);

 

– Üreteceğimiz sinyalin frekansı 5kHz olarak hesaplanıp bir değişkene atanır

ulPeriod = SysCtlClockGet() /5000;

 

– Kullanacağımız 2 adet PWM üretici modülün uygulamasını istediğiiz modları seçilir

PWMGenConfigure(PWM_BASE, PWM_GEN_0,PWM_GEN_MODE_UP_DOWN | PWM_GEN_MODE_NO_SYNC); 

PWMGenConfigure(PWM_BASE, PWM_GEN_1,PWM_GEN_MODE_UP_DOWN | PWM_GEN_MODE_NO_SYNC);

 

– Modüllerin çalışmasını istediğimiz frekansının tanımlaması yapılır

PWMGenPeriodSet(PWM_BASE, PWM_GEN_0, ulPeriod);
PWMGenPeriodSet(PWM_BASE, PWM_GEN_1, ulPeriod);

 

– Her çıkışın periyodu tanımlanır. Kaynak kodun ikinci versiyonunda bu kısım, seri port interrupt fonksiyonuna bağlı olarak beagle board dan gelecek hız emrine göre bir fonksiyon içerisinde ayarlanabilir şekilde yazılmıştır.

PWMPulseWidthSet(PWM_BASE, PWM_OUT_0, ulPeriod * 4 / 8);
PWMPulseWidthSet(PWM_BASE, PWM_OUT_1, ulPeriod * 4 / 8);
PWMPulseWidthSet(PWM_BASE, PWM_OUT_2, ulPeriod * 4 / 8);
PWMPulseWidthSet(PWM_BASE, PWM_OUT_3, ulPeriod * 4 / 8);

 

-En son olarak PWM üretici modüller aktif hale getirilmiştir.

PWMGenEnable(PWM_BASE, PWM_GEN_0);
PWMGenEnable(PWM_BASE, PWM_GEN_1);

 

* burada sadece PWM üretici modüller aktif hale getirilmiştir. PWM OUTPUT CONTROL LOGIC tarafından kontrol edilcek çıkışlarımız henüz aktif hale getirilmemiştir. Çıkışlar hareket fonksiyonlarımız tarafından açılıp kapanmaktadır.

 

– Aşağıda ileri gitme fonsiyonumuz gösterilmiştir. Temel olarak yapmak istediğimiz şey motorlarımızın birer kutubuna PWM sinyalini uygulamak , diğer kutuplarını ise ground seviyesine çekerek devreyi tamamlamaktır. Dönme fonksiyonlarımız da bu düşünce ile çalışmaktadır.

void forward(void)
{
	Display96x16x1Init(false);
    Display96x16x1StringDraw("ileri", 6, 0);
 
    PWMOutputState(PWM_BASE, PWM_OUT_0_BIT | PWM_OUT_2_BIT, true);
    PWMOutputState(PWM_BASE, PWM_OUT_1_BIT | PWM_OUT_3_BIT, false);
}

 

SERİ PORT HABERLEŞMESİNİ KULLANMA

Beagleboard ile STELLARİS LM3S811 arasındaki haberleşmeyi seri port ile sağlayacağız. Haberleşme sırasında, asıl karar verme yetkisine sahip algoritmaları barındıran beagleboard STELLARİS LM3S811 ‘e iler, geri, sağ,sol ve durma komutlarını seri haberleşme yolu ile iletecektir. STELLARİS LM3S811 ise seri port interrupt fonksiyonu ile hareket fonksiyonlarını çağıracak ve devamlı kontrol ettiği sensörlerde engel bulduğunda aracı durduracak ve beagleboarda neden durduğunu bildirecektir. Beagleboard ise üzerindeki algoritmalar sayesinde kendine yeni yön çizecek ve bunları STELLARİS LM3S811 e uygulatacaktır.

İlk aşamada mikrokontrolerımızı kolayca deneyebilmek için UART0 ve üzerindeki donanımı kullanacağız. Kullanacağımız haberleşme ayarları :

IntMasterEnable();
 
	GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
 
	UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 9600,
                        (UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE |
                         UART_CONFIG_PAR_NONE));
 
	IntEnable(INT_UART0);
    UARTIntEnable(UART0_BASE, UART_INT_RX | UART_INT_RT);

şekindedir. Begleboar dan gelecek komutları yakalayacak ve hareket fonksiyonlarını çağıracak kesme fonksiyonumuz ise :

void UARTIntHandler(void)
{
	Display96x16x1Init(false);
	Display96x16x1StringDraw("asdasd", 6, 0);
 
    unsigned long ulStatus;
	char  temp[1]; 
 
    ulStatus = UARTIntStatus(UART0_BASE, true);   
 
    temp[0] = UARTCharGet(UART0_BASE);
 
		if (temp[0] == 'w')
		{
			x=1;
   			forward();
		}
		if (temp[0] == 's')
		{
			x=0;
			rear();
		}
		if (temp[0] == 'd')
		{
			x=0;
			right();
		}
		if (temp[0] == 'a')
		{
			x=0;
			left();
		}
		if (temp[0] == ' ')
		{
			x=0;
			stop();
		}
 
	UARTIntClear(UART0_BASE, ulStatus);		// seri port interrupt bayrağını temizler  
}

Burada temelde dikkat etmemiz gereken nokta bu fonksiyonun olabildiğince kısa yazılmış olmasıdır. Eğer bu şekilde yazılmış olmasaydı, aracın hareketinde fonksiyonlardan kaynaklanan gecikmeden dolayı tutarsız yönelmeler gözlenebilincekti.

ADC KULLANARAK ENGEL ALGILAMA

Yaptığımız testlerde LGP2Y0A02YK IR sensörünün manuelinde yazdığı gibi düzgün şekilde ölçüm yapmaya başlaması 20 cm den sonra olmaktadır. LV-MaxSonar®-EZ3™ ultrasonik sensörünün ise yaklaşık 10 cm den sonra ölçümleri kararlı olmaktadır.Bu yüzden yan taraftaki resimde görüldüğü gibi, IR sensörü karşıdan gelecek engelleri algılamak için aracın ortasına yerleştirdik. Ultrasonik sensörü ise yere doğru bakacak şekilde çukur takibi için kullanacağız.

*IR sensörü ADC1 kanalına

*Ultrasonik sensörü ADC2 kanalına bağlayacağız.

Mikrokontroler ımızın adc donanımını ayarladığımız kodlar:

ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_PROCESSOR, 0);
	ADCSequenceEnable(ADC0_BASE, 3);
    ADCIntClear(ADC0_BASE, 3);

Bu kodlarda adc modülünü önceden tanımladığımız bir timer kesmesi ile değil işlemciye kendimiz tetikleterek aktif hale getireceğimiz belirttik.  Fakat burada hangi kanalı kullanacağımız hakkında bir ayarlama henüz yapmadık. O ayarlamayı ise bir fonksiyon yazarak istenilen kanalı aktif hale getirerek yaptık.

 

void adc_port_ayar( int n)
{
	if (n ==1)
	{	
		ADCSequenceDisable(ADC0_BASE,3);
		ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_CH1 | ADC_CTL_IE | ADC_CTL_END);
		ADCSequenceEnable(ADC0_BASE,3);
	}
	if (n ==2)
	{	
		ADCSequenceDisable(ADC0_BASE,3);
		ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_CH2 | ADC_CTL_IE | ADC_CTL_END);
		ADCSequenceEnable(ADC0_BASE,3);
	}	
}

Registerlerde herhangi bir ayarlama yapmadan önce donanımın disable  edilip sonrasında enable edilmesi oluşabilcek donma problemlerini engelleyecektir.

Engelleri nasıl algılarız?

adc_port_ayar(1); 		// IR sensörün çıktısını okumak için  kanal1 i aktif ederiz
    	ADCProcessorTrigger(ADC0_BASE, 3);// adc modülünü tetikleriz
        while(!ADCIntStatus(ADC0_BASE, 3, false)) {}// adc dönüşümünün tamamlanmasını bekleriz
        ADCSequenceDataGet(ADC0_BASE, 3, &adc_temp1);// adc_temp1 e IR sensörümüzün datasını atarız				
 
		if (adc_temp1 > 700 && x==1)//  eğer engel sınır değeri aşmışsa ve ileri fonksiyonu çalışır halde ise
		{
			stop();// araç durdurulur
			UARTSend((unsigned char *)"f1", 2);// beagleboarda hata kodu gönderilir
		}

Projenin kaynak kodları :

 

#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/debug.h"
#include "driverlib/gpio.h"
#include "driverlib/interrupt.h"
#include "driverlib/sysctl.h"
#include "driverlib/uart.h"
#include "driverlib/pwm.h"
#include "drivers/display96x16x1.h"
#include "driverlib/adc.h"
volatile int x=0;
#ifdef DEBUG
void
__error__(char *pcFilename, unsigned long ulLine)
{
}
#endif
 
/////// hareket fonksiyonları  ///////
 
void forward(void)
{
	Display96x16x1Init(false);
    Display96x16x1StringDraw("ileri", 6, 0);
 
    PWMOutputState(PWM_BASE, PWM_OUT_0_BIT | PWM_OUT_2_BIT, true);
    PWMOutputState(PWM_BASE, PWM_OUT_1_BIT | PWM_OUT_3_BIT, false);
}
void rear (void)
{
	Display96x16x1Init(false);
    Display96x16x1StringDraw("geri", 6, 0);
 
    PWMOutputState(PWM_BASE, PWM_OUT_1_BIT | PWM_OUT_3_BIT, true);
    PWMOutputState(PWM_BASE, PWM_OUT_0_BIT | PWM_OUT_2_BIT, false);
}
void left(void)
{
	Display96x16x1Init(false);
    Display96x16x1StringDraw("sol", 6, 0);
 
    PWMOutputState(PWM_BASE, PWM_OUT_1_BIT | PWM_OUT_2_BIT, true);
    PWMOutputState(PWM_BASE, PWM_OUT_0_BIT | PWM_OUT_3_BIT, false);
 
}
void right (void)
{
	Display96x16x1Init(false);
    Display96x16x1StringDraw("sag", 6, 0);
 
    PWMOutputState(PWM_BASE, PWM_OUT_0_BIT | PWM_OUT_3_BIT, true);
    PWMOutputState(PWM_BASE, PWM_OUT_1_BIT | PWM_OUT_2_BIT, false);
}
void stop (void)
{
	Display96x16x1Init(false);
    Display96x16x1StringDraw("dur", 6, 0);
 
    PWMOutputState(PWM_BASE, PWM_OUT_0_BIT | PWM_OUT_1_BIT|PWM_OUT_2_BIT | PWM_OUT_3_BIT, false);
}
////////////////////////////////////////
 
void UARTIntHandler(void)
{
	Display96x16x1Init(false);
	Display96x16x1StringDraw("asdasd", 6, 0);
 
    unsigned long ulStatus;
	char  temp[1]; 
 
    ulStatus = UARTIntStatus(UART0_BASE, true);   
 
    temp[0] = UARTCharGet(UART0_BASE);
 
		if (temp[0] == 'w')
		{
			x=1;
   			forward();
		}
		if (temp[0] == 's')
		{
			x=0;
			rear();
		}
		if (temp[0] == 'd')
		{
			x=0;
			right();
		}
		if (temp[0] == 'a')
		{
			x=0;
			left();
		}
		if (temp[0] == ' ')
		{
			x=0;
			stop();
		}
 
	UARTIntClear(UART0_BASE, ulStatus);		// seri port interrupt bayrağını temizler
}
 
void UARTSend(const unsigned char *pucBuffer, unsigned long ulCount)
{
    while(ulCount--)
    {
        UARTCharPutNonBlocking(UART0_BASE, *pucBuffer++);
    }
}
 
void adc_port_ayar( int n)
{
	if (n ==1)
	{
		ADCSequenceDisable(ADC0_BASE,3);
		ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_CH1 | ADC_CTL_IE | ADC_CTL_END);
		ADCSequenceEnable(ADC0_BASE,3);
	}
	if (n ==2)
	{
		ADCSequenceDisable(ADC0_BASE,3);
		ADCSequenceStepConfigure(ADC0_BASE, 3, 0, ADC_CTL_CH2 | ADC_CTL_IE | ADC_CTL_END);
		ADCSequenceEnable(ADC0_BASE,3);
	}
}
 
int main(void)
{
 
    unsigned long ulPeriod;
	unsigned long adc_temp1;
 
    SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN |
                   SYSCTL_XTAL_6MHZ);
    SysCtlPWMClockSet(SYSCTL_PWMDIV_1);
 
    Display96x16x1Init(false);	// ekranı temizleme
    Display96x16x1StringDraw("   oto-pilot ", 6, 0);
    Display96x16x1StringDraw("bitirme projesi", 6, 1);
	SysCtlDelay(SysCtlClockGet() /2);
	///////////////////////////////////////////////////////////////////
	SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_PWM);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
	/////////////////seri port ayarlama///////////////////////////////
 
    IntMasterEnable();
 
	GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
 
	UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), 9600,
                        (UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE |
                         UART_CONFIG_PAR_NONE));
 
	IntEnable(INT_UART0);
    UARTIntEnable(UART0_BASE, UART_INT_RX | UART_INT_RT);
 
	Display96x16x1Init(false);
	Display96x16x1StringDraw("seri port hazir", 6, 0);
    UARTSend((unsigned char *)"seri port hazir", 16);
    SysCtlDelay(SysCtlClockGet() /2);
    ////////////////////  PWM AYARLAMASI  ///////////////////////////
 
    GPIOPinTypePWM(GPIO_PORTD_BASE, GPIO_PIN_0 | GPIO_PIN_1);
    GPIOPinTypePWM(GPIO_PORTB_BASE, GPIO_PIN_0 | GPIO_PIN_1);
 
    ulPeriod = SysCtlClockGet() /5000;
 
    PWMGenConfigure(PWM_BASE, PWM_GEN_0,
                    PWM_GEN_MODE_UP_DOWN | PWM_GEN_MODE_NO_SYNC);
	PWMGenConfigure(PWM_BASE, PWM_GEN_1,
                    PWM_GEN_MODE_UP_DOWN | PWM_GEN_MODE_NO_SYNC);
 
    PWMGenPeriodSet(PWM_BASE, PWM_GEN_0, ulPeriod);
	PWMGenPeriodSet(PWM_BASE, PWM_GEN_1, ulPeriod);
 
    PWMPulseWidthSet(PWM_BASE, PWM_OUT_0, ulPeriod * 7 / 10);
    PWMPulseWidthSet(PWM_BASE, PWM_OUT_1, ulPeriod * 7 / 10);
	PWMPulseWidthSet(PWM_BASE, PWM_OUT_2, ulPeriod * 7 / 10);
    PWMPulseWidthSet(PWM_BASE, PWM_OUT_3, ulPeriod * 7 / 10);
 
    PWMGenEnable(PWM_BASE, PWM_GEN_0);
	PWMGenEnable(PWM_BASE, PWM_GEN_1);
 
    Display96x16x1Init(false);
	Display96x16x1StringDraw("pwm hazir", 6, 0);
	SysCtlDelay(SysCtlClockGet() /2);
 
	//////////////////////   ADC BİRİMLERİ AYARLANMASI   //////////
 
	ADCSequenceConfigure(ADC0_BASE, 3, ADC_TRIGGER_PROCESSOR, 0);
	ADCSequenceEnable(ADC0_BASE, 3);
    ADCIntClear(ADC0_BASE, 3);    
 
    Display96x16x1Init(false);
	Display96x16x1StringDraw("adc hazir", 6, 0);
	SysCtlDelay(SysCtlClockGet() /2);
    ///////////////////////////////////////////////////////////////
    while(1)
    {
    	adc_port_ayar(1);
    	ADCProcessorTrigger(ADC0_BASE, 3);
        while(!ADCIntStatus(ADC0_BASE, 3, false)) {}
        ADCSequenceDataGet(ADC0_BASE, 3, &adc_temp1);				
 
		if (adc_temp1 > 700 && x==1)
		{
			stop();
			UARTSend((unsigned char *)"f1", 2);
		}
 
		SysCtlDelay(SysCtlClockGet() /20);
 
	adc_port_ayar(2);
    	ADCProcessorTrigger(ADC0_BASE, 3);
        while(!ADCIntStatus(ADC0_BASE, 3, false)) {}
        ADCSequenceDataGet(ADC0_BASE, 3, &adc_temp1);				
 
		if (adc_temp1 < 30 && x==1)
		{
			stop();
			UARTSend((unsigned char *)"f2", 2);
		}
 
		SysCtlDelay(SysCtlClockGet() /20;     
 
    }
}