Özellikle MEB’in yaptığı robot yarışmaları ile daha çok duyulan PID, robotların yol tutuşunu arttırmakta ve daha hızlı işlemcilerin kullanılmasını sağlamaktadır. PID bir fonksiyondan ibaret olup bir yazım tekniği değildir. Robot yazılımı içinde kullanılan bir hesaplama fonksiyonudur. Fonksiyon tanımlarını robot dokümanımızın global.c dosyasında, fonksiyonları ise helpers.c dosyasında bulabilirsiniz. Aşağıda PID ile yazılmış robot dosyaların tamamına ulaşabilmek için bir link bulacaksınız.

Konuyu daha iyi kavrayabilmek şekiller üzerinde anlatalım.

 

 

Yukarıdaki resimde mavi hattımız yolumuz olsun. Altındaki ise robotumuz. Robotun an0-an7 olmak üzere 7 adet sensoru var. Robotun yoldaki hareketlerine göre bu sensorlar aktif oluyorlar ve bize nasıl gitmemiz gerektiği hakkında ipucu veriyorlar. Bizim offset noktamız an4 ve an3 sensorları. Bu offset noksanından uzaklaşmasına göre de pid fonksiyonumuza ne kadar sapma olduğunu bildireceğiz. Bu sapma değerleri fonksiyon içindeki oransal değerimizi oluşturur.  An4 ve an3 tam çizginin üzerindeyse robot tam yol üzerindedir ve oransal olarak 0 değeri gönderecektir. Eğer an3 sensoru aktifse 1 değeri an4 sensoru aktif olursa -1 değeri gönderiyoruz. Olası ihtimalleri aşağıdaki tablodan inceleyelim.

Bizim için decimal değerler ve PID oransan değerler önemli. Bu değerleri programımızda kullanacağız. Mesela 24 değerine karşılık pid(0), 128 değerine karşılık olarak da pid(-7) değerini fonksiyona gönderiyoruz. PID fonksiyonu bu değerleri işliyor. Ana program parçasında şöyle görünüyor; case 24:pid(0);break; ve case 128:pid(-7);break;

Aşağıdaki fonksiyon 2016 daki robot yarışmasında yarışmış olan robotumuzun program parçasıdır. Ben programda integral değerini kullanmadım. Ancak aşağıda konu bütünlüğünü bozmamak için aşağıya integrali de dâhil edip öyle anlatacağım konuyu. Önce pid fonksiyonunu bir inceleyelim.

void pid(signed int8 pidGelen){

error=offset-pidGelen; // proportional değer

integral = integral+error; // bu kısım normalde yok

derivative=error-lastError;

lastError=error;

pidDeger=(Kp*error)+(Kd*derivative)+(Ki*integral); // (Ki*integral) kısmı programda yok

if (pidDeger>mp) pidDeger=mp;

pwmSol=mp-pidDeger;pwmSag=mp+pidDeger;

if (pwmSol<min) pwmSol=min;if (pwmSol>max) pwmSol=max;

if (pwmSag<min) pwmSag=min;if (pwmSag>max) pwmSag=max;

}

Yukarıdaki fonksiyonu anlayabilmeniz için fonksiyon içinde geçen değişkenleri de görmemiz gerekir. Aşağıda programdan alınmış değerler var. Kafa karıştırmamak için gereksiz yerleri sildim ve integral için ekleme yaptım.

Unsigned int8 offset=0;

signed int32 mp=255, pidDeger=0, anDeger=0, anKalibre=225, min=0, max=403, pwmSol=0, pwmSag=0;

float Kp=10,Kd=5, Ki=2, integral=0, derivative=0, error=0, lastError=0;

Yukarıdaki değişkenleri biraz açıklayalım.

Offset: robotun olmasını istediğimiz konumdur.

Error: offset-pidGelen; değeridir. Offset değerden ne kadar uzaklaştığımızı anlarız. Aynı zamanda oransal değerimizdir.

Mp: motor power anlamıdadır. Robot pid işlemediği zaman motorların güç değeri içindir.

Min=0; bu değer pwm in alabileceği minimum değer içindir.

Max=403; bu değer pwm in alabileceği maximum değer içindir. Normalde max değer 255 olabilirken özel bir fonksiyon yardımıyla biz çözünürlüğümüzü arttırarak max değerimizi 403 yaptık. Çoğu projelerde bu değer 255 olabilir.

pwmSol=0; sol motora verilecek pwm değeri içindir.

pwmSag=0; sağ motora verilecek pwm değeri içindir.

Kp=10,Kd=5, Ki=2; bu değerler formülde kullanılacak ilgili sabitler olup robotun tepkisine göre sizin belirleyeceğiniz sayılardır. Genellikle deneme yanılma yoluyla yapılırlar. Kp=0.000025 de olabilirdi. Bu durumda pid gelen değerleri daha yüksek olmalıdır. 30 ,40 gibi değerler.

Önemli Not: Kp değeri düz yoldaki hataları düzeltmek için kullanılan sabittir. Kd değeri virajlı yollarda kullanılan ve tutunmayı sağlayan değerdir. Ki değeri ise yolun tamamı için (sık virajlı veya daha çok düz yol olmasına göre) kendini düzenleyen bir değerdir. Türev 2.dereceden bir fonksiyonu 1.dereceye indirger. Burada matematiksel fonksiyonları hatırlayacak olursanız düz bir doğru için denklemimiz 1.dereceden olmaktadır. Yine 2.dereceden fonksiyonların şekli virajlı yollar gibidir. Virajlı yolları daha düzgün gidebilmek için türev kullanılır. 

PID İngilizce oransal terimin (proportional), integral teriminin (integral) ve türev teriminin (derivative ) baş harfleridir. Oransal değer(Error); robotunuzun çizgiye göre konumunu yaklaşık olarak orantılayan değerdir. Biz robotun daima çizgi üzerinde olmasını isteriz. Eğer robotumuz çizgi üzerindeyse kayma orantımız sıfırdır diyebiliriz. Yani, robotunuz hat üzerinde tam merkezlenmişse, oransal değer tam olarak 0 olacak şekilde bekleriz. Eğer robotumuzun kafası çizginin solundaysa (orta sensorun sağındaki sensorlar görüyor demektir), orantı değerimiz pozitif bir sayı olacaktır. Robot çizginin sağındaysa orantı değerimiz negatif bir sayı olacaktır. Orantı değerinin, robotun solda olmasına göre pozitif olması veya tam tersine sağında olmasına göre negatif olması tamamen sizin yazdığınız fonksiyonun işlevine bağlıdır ve görecelidir. Konuyu kavradığımızda ne demek istediğimi tam olarak anlayacaksınız. Okumaya devam edelim…

İntegral değer; robotumuzun tüm hareketlerini kayıt altına alır. Oransal değerlerin toplamına eşittir. İntegral değer ne kadar küçükse yol o kadar düz olduğu anlaşılır.

Türev değeri; oransal değerin değişim oranıdır. Son iki oransal değer arasındaki fark kadardır. Robotumuz viraja girdiğinde hep aynı yönde fark oluşacaktır. Fark oluştuğu sürece de toparlanma hızlandırılacaktır. Türev önüne çıkan virajı (2.dereceden grafik gibi) doğrultmaya çalışarak (1.dereceden grafik gibi) yol tutuşunu arttıracaktır. Aşağıdaki örnek 2 şekli inceleyin.

 

 

Ben yaptığım çalışmalarda integral değerinin etkisini çok göremediğimden yazılıma eklemedim. Ayrıca hesaplamalar sonucunda çıkan değerleri de sınırlar içinde tutmanız gerekir. Motorlara verdiğiniz pwm değeri sınırı ne ise bu sınırlara göre pid’den gelen pwm değerini sınırlamanız gerekir. Pid fonksiyonundaki son 4 satır bunun için.

Pwm değerleri elde edilirken (pwmSol=mp-pidDeger;pwmSag=mp+pidDeger;) mp değerine ekleme ve çıkartma yapılır. Motorların ortalama hızları hep aynı kalır. Mp değeri virajlarda düşürülmesi gereken bir değer olmalıdır. Bunun için biraz daha kafa patlatmak gerek. Mp değerini başlangıçta düşük seçerseniz toplamda zaman kaybı olacaktır. Yazdığımız ifadeyi rakamlar kullanarak daha anlaşılır hale getirelim. Mp=255 olsun. Hesaplanan pidDeger ise 55 olsun. Bu değerlere göre yukarıdaki formüle göre pwmSol değerimiz 200 ve pwmSag değerimiz 310 olur. Robotun viraj üzerindeki ortalama hızı (200+310)/2 = 255 olur. Benzer şekilde Hesaplanan pidDeger ise 30 olsun. Bu değerlere göre yukarıdaki formüle göre pwmSol değerimiz 225 ve pwmSag değerimiz 285 olur. Robotun viraj üzerindeki ortalama hızı (225+285)/2 = 255 olur. Mp değeri değişmediği sürece bu değer böyle kalacaktır.

ALINTIDIR.

KAYNAK

 

Examples içinde yer alan sweep isimli programda değişiklikler yapılmıştır. Normalde sürekli ileri geri 180 derece dönen motor programına buton eklenerek kontrol sağlanmıştır. Pull-up bağlantı yapılmıştır. Yani butona basıldığında arduinonun 5 nolu pinine 0 (sıfır) gönderilmektedir. Daha hızlı dönüşler için pos=pos+1 komutunda olduğu gibi derece 1 yerine 5 arttırılmalı ya da delay komutu ile sağlanan gecikme düşürülmelidir. (setup fonksiyonunda yer alan pinMode(buton1,INPUT); komutu yerine pinMode(buton2,INPUT_PULLUP); yazıldığında 10K değeirndeki dirence gerek kalmaz. Butonun bir ucu GND' ye diğer ucu arduinonun 2 veya 3 nolu pinine bağlanır)

 

 

#include <Servo.h>  
Servo myservo;   
int pos = 0;  
int buton1=2; // buton1, 2 nolu pine bağlanacak
int buton2=3; // buton2, 3 nolu pine bağlanacak 
void setup() 
{ 
  myservo.attach(5);  // servonun data ucu 5 nolu pine takılacak.İstediğiniz pine takabilirsiniz.
  pinMode(buton1,INPUT);
  pinMode(buton2,INPUT);
}  
void loop() 
{    
  if (digitalRead(buton1)==0&&(pos<180))
                                         // buton1 e basılmışsa ve motor en sağa ulaşmamışsa
  {                                       // buton basılı olduğu sürece sağa döner
    pos=pos+1;                         
    myservo.write(pos);             
    delay(5);                         // bu değer düşürülerek daha hızlı döndürülebilir                 
  }   
  if (digitalRead(buton2)==0&&(pos>0))
                                        // buton2 ye basılmışsa ve motor en sola ulaşmamışsa
  {                                     // buton basılı olduğu sürece sola döner
    pos=pos-1;
    myservo.write(pos);              
    delay(5);                       
  } 
} 

 

NRF24L01 kablosuz modül, 2.4 GHz frekansında kablosuz haberleşme yapılmasını sağlayan düşük güç tüketimli bir modüldür.

NRF24L01 SPI  arabirimini desteklemektedir.Yaptığı işe göre fiyatı da gayet uygun bir ürün olmasıyla dikkat çekiyor.

Özellikleri:

  • 1,9-3,6 voltaj beslemesi
  • Düşük güç tüketimi
  • Dünya çapında lisans gerektirmeyen 2.4GHz ISM band işletimi
  • Açık alanda 250 m haberleşme mesafesi
  • Boyutları:15×29 mm
  • Alıcı hassasiyeti<90dB
  • Verici sinyal gücü:+7dB

Uygulama Alanları:

  • Ev ve ticari otomasyonlar
  • Oyuncaklar
  • Hobi elektroniği
  • Gelişmiş medya merkezlerinde uzaktan kontrol
  • Mouse,klavye
  • Oyun konsolları

Arduino ile NRF24L01 Bağlantısı

 

NRF24L01 Arduino NRF24L01 Arduino
x x 7 MISO 12
6 MOSI 11 5 SCK 13
4 SCN 10 3 CE 9
2 VCC (+3.3V) VCC (+3.3V) 1 GND GND

 kütüphaneleri buradan indirebilirsiniz. TIKLAYIN

Alıcı Kodları

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
int msg[1];
RF24 radio(9,10);
const uint64_t pipe = 0xE8E8F0F0E1LL;
int LED1 = 3;
int LED2 = 5;
 
void setup(void){
Serial.begin(9600);
radio.begin();
radio.openReadingPipe(1,pipe);
radio.startListening();
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);}
 
void loop(void){
if (radio.available()){
bool done = false;
while (!done){
done = radio.read(msg, 1);
Serial.println(msg[0]);
if (msg[0] == 111){delay(10);digitalWrite(LED1, HIGH);}
else if (msg[0] == 112) {digitalWrite(LED1, LOW);}
else if (msg[0] == 113) {delay(10);digitalWrite(LED2,HIGH);}
else {digitalWrite(LED2, LOW);}
delay(10);}}
else{Serial.println("boş");}}

 

Verici Kodları

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
int msg[1];
RF24 radio(9,10);
const uint64_t pipe = 0xE8E8F0F0E1LL;
int SW1 = 7;
int SW2 = 6;
 
void setup(void){
Serial.begin(9600);
radio.begin();
radio.openWritingPipe(pipe);}
 
void loop(void){
if (digitalRead(SW1) == HIGH){
msg[0] = 111;
radio.write(msg, 1);}
if (digitalRead(SW1) == LOW){
msg[0] = 112;
radio.write(msg, 1);}
if (digitalRead(SW2) == HIGH){
msg[0] = 113;
radio.write(msg, 1);}
if (digitalRead(SW2) == LOW){
msg[0] = 114;
radio.write(msg, 1);}}

 

Arduino kartları ile kullanabileceğiniz  hem kumanda yön kontrolü, hem oyun uygulamalarında işinizi basitleştirecek bir karttır.

Kart üzerinde butonlu iki eksenli bir joystick, 4 adet  büyük ve 2 adat küçük kontrol butonu bulunmaktadır. Bununla beraber nRF24L01 kablosuz haberleşme modülünü bağlayabileceğiniz pinler shield üzerinde yer almaktadır. Bu sayede joystick shield'i kolay bir şekilde kablosuz hale getirebilirsiniz. Bunlara ek olarak kart üzerindeki anahtar sayesinde kartın lojik seviyesi 3.3V veya 5V olarak seçebilirsiniz.

 
 
 
/* 6th December 2013 - By Kyle Fieldus

This example sketch is designed to show the inputs and outputs of the Funduino Joystick Shield V1.A
The shield this sketch was developed with was provded by ICStation http://www.icstation.com/
*/

int up_button = 2;
int down_button = 4;
int left_button = 5;
int right_button = 3;
int start_button = 6;
int select_button = 7;
int analog_button = 8;
int PIN_ANALOG_X = A0;
int PIN_ANALOG_Y = A1;
int buttons[] = {up_button, down_button, left_button, right_button, start_button, select_button, analog_button};


void setup() {
  for (int i; i < 7; i++)
  {
   pinMode(buttons[i], INPUT);
   digitalWrite(buttons[i], HIGH);
  }
  Serial.begin(9600);
}

void loop() {
  Serial.print("UP = "),Serial.print(digitalRead(up_button)),Serial.print("\t");
  Serial.print("DOWN = "),Serial.print(digitalRead(down_button)),Serial.print("\t");
  Serial.print("LEFT = "),Serial.print(digitalRead(left_button)),Serial.print("\t");
  Serial.print("RIGHT = "),Serial.print(digitalRead(right_button)),Serial.print("\t");
  Serial.print("START = "),Serial.print(digitalRead(start_button)),Serial.print("\t");
  Serial.print("SELECT = "),Serial.print(digitalRead(select_button)),Serial.print("\t");
  Serial.print("ANALOG = "),Serial.print(digitalRead(analog_button)),Serial.print("\t");
  Serial.print("x: "),Serial.print(analogRead(PIN_ANALOG_X)),Serial.print("\t");
  Serial.print("y: "),Serial.print(analogRead(PIN_ANALOG_Y)),Serial.print("\n");  
//  Serial.print("X = "),Serial.print(map(analogRead(x_axis), 0, 1000, -1, 1));Serial.print("\t");
//  Serial.print("Y = "),Serial.print(map(analogRead(y_axis), 0, 1000, -1, 1));Serial.print("\n");  
  delay(100);
  
 }