Başlamadan Önce
Yazımızı okumadan önce ön bilgi amaçlı aşağıdaki yazıları okumanızı tavsiye ederim. Aksi halde yazının anlaşılması zor olabilir.
Hareketli Ortalamalar Filtresi
MPU6050 İnceleme yazısı sensörlerin çalışma mantığının anlaşılması için gereklidir. Filtre yazıları ise yazımızın sonundaki uygulamayı anlamanız için gerekli olacaktır.
Arduino ile MPU6050 Nasıl Kullanılır?
MPU6050 I2C protokolüyle haberleşen bir IMU sensörüdür. İvme, açısal hız ve sıcaklık okuma için kullanılır. Arduino ile kullanabilmek için Arduino’nun I2C özelliğini kullanmamız gerekmektedir. Arudino için çeşitli MPU6050 kütüphaneleri mevcuttur. Biz bu yazımızda Arudino’nun I2C kütüphanesi olan Wire.h kütüphanesini kullanacağız. Böylece dışarıdan bir kütüphane kullanmadan sensörü kullanmış olacağız.
MALZEME LİSTESİ
1 x GY-521 MPU6050 Sensör Modülü
1 x Arduino Mega (Kodlar Mega için yazılmıştır. Uno da kullanılabilir.)
Dişi-Erkek Jumper Kablo
ARDUINO MEGA İÇİN BAĞLANTILAR
Gerekli yazıları okuyup devreyi kurduktan sonra artık kod kısmına geçebiliriz. Sensör içerisinde bulunan İvmeölçer, jiroskop ve sıcaklık sensörlerinin her biri için ayrı ayrı kodlar incelenecektir.
ARDUINO MEGA – SICAKLIK SENSÖRÜ KODLARI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#include<Wire.h>; //I2C Kütüphanesi const int MPU_addr = 0x68; //MPU6050 I2C Slave Adresi unsigned long previousMillis = 0; //Önceki millis değeri değişkeni const long interval = 50; //Ölçümler arasındaki süre (ms) void setup() { Serial.begin(9600); //Seri haberlşeme başlatılır Serial.println ("Başlamak için bir tuşa basınız"); //Devem edilmesi için girdi beklenir (Seri) while (Serial.available() == 0); Wire.begin(); //I2C Başlatılır Wire.beginTransmission(MPU_addr); //0x6B register'ındaki setup değerleri 0 yapılır Wire.write(0x6B); Wire.write(0x00); Wire.endTransmission(true); Serial.begin(9600); } void loop() { unsigned long currentMillis = millis(); //Millis değeri alınır if (currentMillis - previousMillis >= interval) { //İnterval süresi geçtiğinde içeri girilir previousMillis = currentMillis; Wire.beginTransmission(MPU_addr); //MPU6050 ile I2C haberleşme başlatılır Wire.write(0x41); //Sıcaklık bilgisinin olduğu 0x41 ve 0x42 için request gönderilir Wire.endTransmission(false); Wire.requestFrom(MPU_addr, 2, true); int16_t tempFull = Wire.read() << 8 | Wire.read(); //8-bitlik okunan iki değerden büyük ağırlıklı olanı 8 bit sola kaydırılıp küçük olanla veyalanır. float tempFinal = (float) tempFull / 340.0 + 35; //datasheet'te verilen değerlere göre sıcaklık hesaplanır Serial.print("Temp = "); //Sıcaklık bilgisi Santigrad Derece cinsinden seri porttan basılır. Serial.print(tempFinal); Serial.println("C"); } } |
ARDUINO MEGA – İVME SENSÖRÜ KODLARI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
#include<Wire.h>; //I2C Kütüphanesi #include<Math.h>; //Trigonometrik işlemler için kütüphane const int MPU_addr = 0x68; //MPU6050 I2C Slave adresi unsigned long previousMillis = 0; //Önceki millis değeri için değişken const long interval = 30; //Ölçümler arasındaki süre (ms) float ang_x; //X ekseni ile yapılan açı için değişken float ang_y; //Y ekseni ile yapılan açı için değişken float ang_z; //Z ekseni ile yapılan açı için değişken int X_offset = 0; //X ekseni için offset değeri int Y_offset = 0; //Y ekseni için offset değeri int Z_offset = 1700;//Z ekseni için offset değeri void setup() { Serial.begin(9600); //Seri haberlşeme başlatılır Serial.println ("Başlamak için bir tuşa basınız"); //Devem edilmesi için girdi beklenir (Seri) while (Serial.available() == 0); Wire.begin(); //I2C Başlatılır Wire.beginTransmission(MPU_addr); //0x6B register'ındaki setup değerleri 0 yapılır Wire.write(0x6B); Wire.write(0x00); Wire.endTransmission(true); Serial.begin(9600); } void loop() { unsigned long currentMillis = millis(); //Millis değeri alınır if (currentMillis - previousMillis >= interval) { //İnterval süresi geçtiğinde içeri girilir previousMillis = currentMillis; Wire.beginTransmission(MPU_addr); //MPU6050 ile I2C haberleşme başlatılır Wire.write(0x3B); //İvme bilgisinin olduğu 0x3B-0x40 için request gönderilir Wire.endTransmission(false); Wire.requestFrom(MPU_addr, 6, true); int16_t XAxisFull = (Wire.read() << 8 | Wire.read())+X_offset; //8-bitlik okunan iki değerden büyük ağırlıklı olanı 8 bit sola kaydırılıp küçük olanla veyalanır. Offset eklenir. int16_t YAxisFull = (Wire.read() << 8 | Wire.read())+Y_offset; //8-bitlik okunan iki değerden büyük ağırlıklı olanı 8 bit sola kaydırılıp küçük olanla veyalanır. Offset eklenir. int16_t ZAxisFull = (Wire.read() << 8 | Wire.read())+Z_offset; //8-bitlik okunan iki değerden büyük ağırlıklı olanı 8 bit sola kaydırılıp küçük olanla veyalanır. Offset eklenir. float XAxisFinal = (float) XAxisFull / 16384.0; //Datasheet'te yazan değerlere göre "g" cinsinden ivme bulunur. (X ekseni için) float YAxisFinal = (float) YAxisFull / 16384.0; //Datasheet'te yazan değerlere göre "g" cinsinden ivme bulunur. (Y ekseni için) float ZAxisFinal = (float) ZAxisFull / 16384.0; //Datasheet'te yazan değerlere göre "g" cinsinden ivme bulunur. (Z ekseni için) if(XAxisFinal>0.99) XAxisFinal=1; //0.99 olan değerler 1'e tamamlanır. if(YAxisFinal>0.99) YAxisFinal=1; if(ZAxisFinal>0.99) ZAxisFinal=1; if(XAxisFinal<-0.99) XAxisFinal=-1; //-0.99 olan değerler 1'e tamamlanır. if(YAxisFinal<-0.99) YAxisFinal=-1; if(ZAxisFinal<-0.99) ZAxisFinal=-1; ang_x = atan(YAxisFull/(sqrt(pow(XAxisFull,2)+pow(ZAxisFull,2)))) * 57296 / 1000; //Euler Açı formülüne göre açı hesabı. (X-Ekseni) ang_y = atan(YAxisFull/(sqrt(pow(XAxisFull,2)+pow(ZAxisFull,2)))) * 57296 / 1000; //Euler Açı formülüne göre açı hesabı. (Y-Ekseni) Serial.print("X Axis = "); //Her eksen için g değerleri seri porttan basılır Serial.print(XAxisFinal); Serial.print("g"); Serial.print("\t"); Serial.print("Y Axis = "); Serial.print(YAxisFinal); Serial.print("g"); Serial.print("\t"); Serial.print("Z Axis = "); Serial.print(ZAxisFinal); Serial.println("g"); // Serial.print("X angle = "); //X ve Y eksenleri için açı değerleri seri porttan basılır // Serial.print(ang_x); // Serial.print(""); // Serial.print("\t"); // Serial.print("Y angle = "); // Serial.println(ang_y); } } |
ARDUINO MEGA – GYRO SENSÖRÜ KODLARI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
#include<Wire.h>; //I2C Kütüphanesi const int MPU_addr = 0x68; //MPU6050 I2C Slave adresi float old_x = 0; //Önceki okunan açısal hız değeri. (X ekseni için) float old_y = 0; //Önceki okunan açısal hız değeri. (Y Ekseni için) float prev_angle_x = 0; //Önceki okunan açı değeri. (X ekseni için) float prev_angle_y = 0; //Önceki okunan açı değeri. (Y ekseni için) unsigned long previousMillis = 0; //Önceki millis değeri int interval = 30; //Ölçümler arasındaki bekleme süresi(ms) int X_offset = 520; //X ekseni offset değeri int Y_offset = 0; //Y ekseni offset değeri int Z_offset = 0; //Z ekseni offset değeri void setup() { Serial.begin(9600); //Seri haberlşeme başlatılır Serial.println ("Başlamak için bir tuşa basınız"); //Devem edilmesi için girdi beklenir (Seri) while (Serial.available() == 0); Wire.begin(); //I2C Başlatılır Wire.beginTransmission(MPU_addr); //0x6B register'ındaki setup değerleri 0 yapılır Wire.write(0x6B); Wire.write(0x00); Wire.endTransmission(true); Serial.begin(9600); } void loop() { unsigned long currentMillis = millis(); //Millis değeri alınır if (currentMillis - previousMillis >= interval) { //İnterval süresi geçtiğinde içeri girilir previousMillis = currentMillis; Wire.beginTransmission(MPU_addr); //MPU6050 ile I2C haberleşme başlatılır Wire.write(0x43); //Gyro bilgisinin olduğu 0x43-0x48 için request gönderilir Wire.endTransmission(false); Wire.requestFrom(MPU_addr, 6, true); int16_t XGyroFull = (Wire.read() << 8 | Wire.read()) + X_offset; //8-bitlik okunan iki değerden büyük ağırlıklı olanı 8 bit sola kaydırılıp küçük olanla veyalanır. Offset eklenir. int16_t YGyroFull = (Wire.read() << 8 | Wire.read()) + Y_offset; //8-bitlik okunan iki değerden büyük ağırlıklı olanı 8 bit sola kaydırılıp küçük olanla veyalanır. Offset eklenir. int16_t ZGyroFull = (Wire.read() << 8 | Wire.read()) + Z_offset; //8-bitlik okunan iki değerden büyük ağırlıklı olanı 8 bit sola kaydırılıp küçük olanla veyalanır. Offset eklenir. float XGyroFinal = (float)XGyroFull / 131; //Datasheet'te yazan değerlere göre "deg/s" cinsinden açısal hız bulunur. (X ekseni için) float YGyroFinal = (float)YGyroFull / 131; //Datasheet'te yazan değerlere göre "deg/s" cinsinden açısal hız bulunur. (Y ekseni için) float ZGyroFinal = (float)ZGyroFull / 131; //Datasheet'te yazan değerlere göre "deg/s" cinsinden açısal hız bulunur. (Z ekseni için) //X ekseni için açı hesabı float delta_angle_x = (0.03 * old_x) + ((0.03 * (XGyroFinal - old_x)) / 2); //açısal hız ve geçen süreye bağlı olarak taranan açı hesaplanır float angle_x = (prev_angle_x + delta_angle_x); //taranan açı değeri ile bir önceki açı değeri hesaplanarak prev_angle_x = angle_x; //güncel açı değeri bir sonraki döngüde kullanılmak üzere önceki açı değeri olarak kaydedilir old_x = XGyroFinal; //güncel açısal hız değeri bir sonraki döngüde kullanılmak üzere önceki açısal hız değeri olarak kaydedilir //Y ekseni için açı hesabı float delta_angle_y = (0.03 * old_y) + ((0.03 * (YGyroFinal - old_y)) / 2); //yukarıdaki işlemlerin aynısı Y ekseni için de yapılır float angle_y = (prev_angle_y + delta_angle_y); prev_angle_y = angle_y; old_y = YGyroFinal; //Açısal hız değerleri seri porttan basılır Serial.print("X Axis = "); Serial.print(XGyroFinal); Serial.print("deg/s"); Serial.print("\t"); Serial.print("Y Axis = "); Serial.print(YGyroFinal); Serial.print("deg/s"); Serial.print("\t"); Serial.print("Z Axis = "); Serial.print(ZGyroFinal); Serial.println("deg/s"); //Açı değerleri seri porttan basılır // Serial.print("X angle = "); // Serial.print(angle_x); // Serial.print(""); // Serial.print("\t"); // Serial.print("Y angle = "); // Serial.println(angle_y); } } |
NOT: Gyro sensörü ile yapılan açı hesapları tek eksende yapılan hareketler için doğru sonuç vermektedir. Birden fazla eksende aynı anda hareket edilmesi halinde doğru sonuç vermeyecektir.
OFFSET DEĞERİ ÖLÇÜMÜ
Bu tip sensörlerin kullanımında offset değerleri oldukça önemlidir. Offset değerleri sensörün sıfır ya da belirli bir değer vermesi gerektiği durumlarda farklı bir değer vermesidir. Bu değer genellikle beklenenden çok farklı olmaz fakat yine de sonucu etkilemektedir. Bu etkiyi kaldırmak için Offset değerleri bulunup yapılan ölçüme eklenir. Offset değerleri manuel olarak test edilerek bulunabileceği gibi yazılımsal olarak da bulunabilir. Kalibrasyon sürecinin bir parçasıdır.
FİLTRE UYGULAMASI
Daha önce dijital ortamda okunan verilere filtre uygulanmasından bahsetmiştik. Bu uygulamamızda da ivmeölçerden okumuş olduğumuz X eksenine ait olan veri üzerinde filtre uygulayacağız. Uygulamayı anlayabilmeniz için yazının başında vermiş olduğum filtre yazılarına bakmanız gerekmektedir. Uygulamada anlatılan 3 filteri aynı anda uygulayıp sonuçlarını gözlemleyeceğiz. Kodun sadece daha önce gösterilmemiş olan bütün filtrelerin uygulandığı kısmına yorum eklenmiştir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
#include<Wire.h>; const int MPU_addr = 0x68; float kalman_old = 0; float cov_old = 1; float val1, val2, val3, val4, val5; int med1_sort, med2_sort, med3_sort, med4_sort; float avg; int med; int m; int med_sort[5]; int c_avg = 1; int c_med = 1; int d = 0; float old_x = 0; float real_angle = 0; float prev_angle = 0; unsigned long previousMillis = 0; const long interval = 50; void setup() { Serial.begin(9600); Serial.println ("Hit a key to start"); // signal initalization done while (Serial.available() == 0); Wire.begin(); Wire.beginTransmission(MPU_addr); Wire.write(0x6B); Wire.write(0x00); Wire.endTransmission(true); delay(50); Wire.beginTransmission(MPU_addr); Wire.write(0x1C); Wire.write(0x00); Wire.endTransmission(true); Serial.begin(9600); } void loop() { unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; Wire.beginTransmission(MPU_addr); Wire.write(0x3B); Wire.endTransmission(false); Wire.requestFrom(MPU_addr, 2, true); int16_t XAxisFull = (Wire.read() << 8 | Wire.read()); float XAxisFinal = (float) XAxisFull / 16384.0; int XAxis_Filtered = general_filter(c_avg, c_med, XAxisFull); int XAxis_kalman = kalman_filter(XAxisFull); int XAxis_movavg = mov_avg(c_avg, XAxisFull); int XAxis_median = median(c_med, XAxisFull); Serial.print(XAxis_Filtered); Serial.print("\t"); Serial.print(XAxis_kalman); Serial.print("\t"); Serial.print(XAxis_movavg); Serial.print("\t"); Serial.print(XAxis_median); Serial.print("\t"); Serial.println(XAxisFull); c_avg = c_avg + 1; c_med = c_med + 1; if (c_avg > 5 && c_med > 5) { c_avg = 6; c_med = 6; } } } float kalman_filter (float input) { float kalman_new = kalman_old; float cov_new = cov_old + 0.50; float kalman_gain = cov_new / (cov_new + 0.9); float kalman_calculated = kalman_new + (kalman_gain * (input - kalman_new)); cov_new = (1 - kalman_gain) * cov_old; cov_old = cov_new; kalman_old = kalman_calculated; return kalman_calculated; } float mov_avg (int counter, float input) { avg = 0; m = 0; if (counter == 1) { val1 = input; avg = val1; } else if (counter == 2) { val2 = input; avg = val2; } else if (counter == 3) { val3 = input; avg = val3; } else if (counter == 4) { val4 = input; avg = val4; } else if (counter == 5) { val5 = input; avg = val5; } else if (counter > 5 ) { counter = 6; if (val1 == 0) { m = m + 1; } if (val2 == 0) { m = m + 1; } if (val3 == 0) { m = m + 1; } if (val4 == 0) { m = m + 1; } if (val5 == 0) { m = m + 1; } if (input == 0) { m = m + 1; } d = 6 - m; if (d == 0) { avg = input; counter = 1; } else { avg = (val1 + val2 + val3 + val4 + val5 + input) / d; } val1 = val2; val2 = val3; val3 = val4; val4 = val5; val5 = input; } return avg; } float median (int counter, int input) { if (counter == 1) { med1_sort = input; med_sort[0] = med1_sort; } else if (counter == 2) { med2_sort = input; med_sort[1] = med2_sort; } else if (counter == 3) { med3_sort = input; med_sort[2] = med3_sort; } else if (counter == 4) { med4_sort = input; med_sort[3] = med4_sort; } else if (counter >= 5) { counter = 6; med_sort[4] = input; sort(med_sort, 5); med = med_sort[2]; med1_sort = med2_sort; med2_sort = med3_sort; med3_sort = med4_sort; med4_sort = input; med_sort[0] = med1_sort; med_sort[1] = med2_sort; med_sort[2] = med3_sort; med_sort[3] = med4_sort; } return med; } void sort(int a[], int size) { for (int i = 0; i < (size - 1); i++) { for (int o = 0; o < (size - (i + 1)); o++) { if (a[o] > a[o + 1]) { int t = a[o]; a[o] = a[o + 1]; a[o + 1] = t; } } } } //Bütün Filtrelerin Uygulandığı Fonksiyon float general_filter (int counter_avg, int counter_med, float input) { float input_movavg = mov_avg(counter_avg, input); //Moving Avg Uygulaması float input_med = median(counter_med, input_movavg); //Median Uygulaması float input_filtered = kalman_filter(input_med); //Kalman Uygulaması return input_filtered; } |
Test Sonuçları:
Bu uygulama sadece test amaçlıdır. Sistemin yapısına göre kullanılması gereken filtreler de değişiklik gösterecektir. Her filtrenin sisteme etkisi farklı olmaktadır. Bu yüzden hazır olarak filtre kullanımı sıkıntı çıkarabilmektedir.
Bu yazımızda MPU6050 sensöründen nasıl ivme, gyro ve sıcaklık bilgilerini okunduğunu gösterdik. Okunan değerlerle ilgili uygulamalar yaptık ve en son da bir filtre uygulaması yaptık. Sorunuz olursa yorumlarda lütfen yorumlarda belirtin. İyi eğlenceler!!
Öncelikle yazı güzel olmuş, tebrikler. Yalnız benim bir kaç sorum olacak.
İvme sensörü kodlarında 53. ve 54. satırlarda yapılan işlemlerde neden 57296 ile çarpım sonra 1000’e böldünüz.
Diğeri ise; hesaplanan ang_x ve ang_y açıları hangi eksenlerle yapılan açılar. Yani bu açılar yere göre yapılan açılar mı yoksa başka bir şey mi.
Yorumunuz için teşekkür ederiz. Yapılan matematiksel işlem euler açı formülüne göre yapılmıştır. ang_x ve ang_y senörün x ve y düzlemleri ile yaptığı açıyı göstermektedir. Yere göe yaptığı açılar da diyebiliriz. Moddülün üzerinde eksenler zaten belirtilmiştir.
elinize sağlık çok güzel olmuş.
ben math.h kütüphanesini bulamadım link paylaşabilirminsiniz
tekrardan merhaba,
math.h kütüphane sorunu çözdüm
bu sensör ile kuvvet ölçmek istiyorum. yukarıdaki ivme sensörü kodlarını kullanarak
integral alınıp(hız konum orada da kuvvet) kuvvet ölçülebilir mi ? ..
ilk hız ve son hız gerekli bana bunu nasıl alabilirim sensörden.
şimdiden teşekkürler
ivmenin integralini alıp zamana bölerek hızı bulabilirsiniz. Kuvvet için kütle ile ivmeyi çarpmanız lazım.
merhabalar bu sensör ile düşme algılamak istiyorum nasıl algoritmasını oluşturabilirim
merhabalar bu sensör ile düşme algılamak istiyorum nasıl bir yol izlemeleyim
Merhabalaryaptığım bir proje (araç) için benim yönleri (kuzey, güney, doğu, batı) bulmam gerekiyor elimde mpu6050 var internette biraz araştırdım görsel olarak bunu yapanlar var ama bana görsel olarak lazım değil benim amacım araca kuzeye dön komutu geldiğinde yönünü kuzeye doğru döndürmesi bunu nasıl yapabilirim bilgisi olan varsa yardımcı olursa sevinirim bir, iki gündür bunu araştırıyorum ama herhangi bir sonuç elde etmiş değilim.
Merhabalar, yönü algılayabilmeniz için pusula sensör kullanmanız gerekli. Eğer MPU6050 kullanacaksanız bir referans vermeniz lazım. İlk pozisyonunu bildiği zaman yapılması mümkün.