Yeni proje: Motor rölanti devri emniyeti ve kill switch kombinasyonu
Bu projeyle uzun zamandır ilgilenemedim. Devir göstergesi aslında bu projenin temel ayağı idi. Devir göstergesini hallettikten sonra artık diğer işlevleri ekleyerek ilk prototipi devreye alabilirim diye düşündüm. Ancak işin içine LCD ekran girince hazırlamış olduğum PCB işe yaramaz hale geldi. Çünkü bu projeyi talep eden arkadaşım aynı zamanda bir de devir göstergesi olmasını arzuluyordu. Bu benim de işime geldi çünkü programı debug ederken birçok parametreyi LCD ekrana yazarak izlemek işimi çok kolaylaştıracaktı.
Ben de sıfırdan bir PCB tasarlayarak işe koyuldum. Dikkat ederseniz artık sistemde kill switch yok. Çünkü plakette yer kalmadı.
Zaten kill switch'i ayrı satmak daha çok işime gelir!
[attachimg=1]
Sistemin temeli Arduino / Atmel sisteminin iki harici interruptundan biri üzerine kurulu. Buraya ateşleme sensöründen gelen sinyali giriyoruz. Burayı da biraz açayım. Ateşlemenin zamanlaması bilindiği gibi thrust washer'e gömülü bir mıknatıs ve onu hisseden bir Hall sensörü ile gerçekleştiriliyor. Eğer bu sinyali (ki klasik üçlü servo kablosudur) bir Y kablo ile alırsak tam istediğimiz sinyali elde etmiş oluyoruz. Bazı ateşlemelerde ayrı bir RPM çıkışı olsa da bu aslında içeride bir Y kablo ile alınan sinyal oluyor.
Bu sinyalin her bir inen ya da çıkan geçişini interrupt için kullanıyoruz. Interrupt geldiğinde "readrev" işlevi çağrılıyor. Bu işlevin yaptığı çok az şey var. İki adet milisaniye sayaç değerimiz var. x1 ve x2. x1 güncel x2 bir önceki değer. Interrupt gelince x2'ye x1 değerini yazıp x2'ye güncel millis() değerini aktarıyoruz. Burada amaç iki ateşleme arasındaki süreyi bulmak. Ayrıca interrupt geldi ise motor çalışıyor demektir. engineon parametresini 1 yapıyoruz. Bir de butonları okuyoruz.
Ana loop içinde ise geçerli bir kumanda sinyali geldiği sürece şunları yapıyoruz:
* Kumanda sinyalini okuyoruz.
* İnterruptları kapatıp hemen x1 ve x2 değerlerini y1 ve y2 değerlerine aktarıyoruz ve interruptları hemen açıyoruz.
* Bu iki değer arasındaki milisaniye farkından devir sayısını hesaplıyoruz.
* Son on devir değerini içeren arrayın değerlerini birer kaydırıp son değeri yazıyoruz ve bu arada son on değerin ortalamasını alıyoruz.
* Bu ortalama değer bizim set ettiğimiz minimum devrin altında ise alarm durumu var demektir:
- 13 numaralı bacaktaki LED'i yakıyoruz.
- Servo sinyalini daha önceden set edilmiş servostep kadar değiştiriyoruz. Bunu yaparken servo normal ayarda ise değeri ekliyor, reverse ayarda ise (jumper ile belirleniyor) değeri çıkarıyoruz.
- Bu sinyali servoya gönderiyoruz.
- İki ayrı süre tutuyoruz.
. Toplam üç saniye boyunca bu alarm durumu sürüyor. Sonra normale dönüyoruz.
. Üç saniye boyunca her 200 milisaniyede bir servo artırım değerini bir azaltıp eklediğimiz gaz miktarını azaltmış oluyoruz.
. Üç saniyenin sonunda normal operasyona dönüp gelen sinyali servoya aynen göndermeye devam ediyoruz.
- Eğer butonlardan birine basıldı ise limit devri ve servo cevabını uygun şekilde artırıp azaltıyoruz.
Limit devir ve servo cevabı onchip EEPROM'un 100 ve 101 nolu adreslerinde kayıtlı. Neden? Çünkü daha önceki servo senkronizasyon projesinde 0 ile 23 arasını kullanmıştık. Bu değerler kalıcı olduğu için aynı Arduino plaketini başka işlerde kullanırken karışıklık olmasın istedim.
Veri yapısı şöyle. Her iki veri için de bir byte kullanıyorum.
RPM limiti 1000 + 6 * byte değeri olarak hesaplanıyor. Byte değeri 0 - 255 arası bir değer. Bu durumda devir limiti 1000 ile 2530 arası bir değer olabiliyor. Ehh sanırım standart motorlarımızla 1000 devrin altında ya da 2500 devrin üstünde bir rölanti kullanan olmayacaktır. Bu da yeterlidir.
Servo tepki adımı (servostep) değeri için ise 1 + byte değerini kullanıyoruz. Yani değer 1 ile 256 arasında değişiyor.
Tüm çalışma boyunca bu değerler LCD ekranda görüntüleniyor. Değer değiştiğinde EEPROM'daki yerine yazılıyor.
Kod burada:
Kod:
//LCD Ekranlı Idle Guard
/*
LCD RS digital 12
LCD Enable digital 11
LCD D4 digital 10
LCD D5 digital 9
LCD D6 digital 8
LCD D7 digital 7
LCD R/W 0V
*/
#include <LiquidCrystal.h>
#include <EEPROM.h>
const int lcdD4 = 10;
const int lcdD5 = 9;
const int lcdD6 = 8;
const int lcdD7 = 7;
const int lcdRS = 12;
const int lcdEN = 11;
const int senspin = 2;
const int thrin = 3;
const int throut = 4;
const int highlevel = 6;
const int revpin = 5;
const int alarmpin = 13;
//Buton bağlantıları ve okunması için
byte keypress = B00111111;
int key = 0;
int key1 = 0;
int key2 = 0;
int b = 0;
const int debounce = 200;
const int bounce = 160;
const int senspluspin = 14; // A0
const int sensminuspin = 17; // A3
const int resppluspin = 15; // A1
const int respminuspin = 16; // A2
volatile byte pressedkey = 0;
byte pressed = 0;
volatile unsigned long x1 = 0;
volatile unsigned long x2 = 0;
unsigned long y1;
unsigned long y2;
unsigned long devir = 0;
volatile byte engineon = 0;
unsigned long timer;
unsigned long subtimer;
unsigned int devirler[10];
unsigned int ortalama;
volatile byte flag = 0;
LiquidCrystal lcd(lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7);
byte servostep;
byte prevstep;
byte increment;
int reverse = 1;
unsigned int rpmlimit = 0;
unsigned int thrinpwm;
unsigned int throutpwm;
void setup()
{
//LCD ekran bağlantı uçları
pinMode(lcdD4, OUTPUT);
pinMode(lcdD5, OUTPUT);
pinMode(lcdD6, OUTPUT);
pinMode(lcdD7, OUTPUT);
pinMode(lcdEN, OUTPUT);
pinMode(lcdRS, OUTPUT);
//Buton bağlantı uçları (analog pinlerde)
pinMode(senspluspin, INPUT_PULLUP);
pinMode(sensminuspin, INPUT_PULLUP);
pinMode(resppluspin, INPUT_PULLUP);
pinMode(respminuspin, INPUT_PULLUP);
//Diğer bağlantılar
pinMode(throut, OUTPUT);
pinMode(highlevel, OUTPUT);
digitalWrite(highlevel, HIGH);
pinMode(alarmpin, OUTPUT);
digitalWrite(alarmpin, LOW);
//EEPROM'dan servostep ve devir limiti değerleri okunuyor
servostep = 1 + EEPROM.read(100);
rpmlimit = EEPROM.read(101);
rpmlimit = 1000 + (6 * rpmlimit); //Devir limiti yaklaşık 1000 - 2500 arasında
reverse = (2 * digitalRead(revpin)) - 1; //Throttle kanalı reverse mi diye kontrol ediliyor. Reverse ise (-1) değil ise (1) oluyor.
prevstep = servostep;
lcd.begin(16, 2); //LCD ekran çalıştırılıyor
printlcd();
attachInterrupt(0, readrev, RISING); //Devir girişi için interrupt ekleniyor
}
void loop()
{
//thrinpwm = 1500;
thrinpwm = pulseIn(thrin, HIGH, 25000);
pressedkey = button();
while (thrinpwm > 800 && thrinpwm < 2200)
{
calcrpm();
/* ortalama = 0;
for (int n = 0; n < 9; n++)
{
devirler[n + 1] = devirler[n];
ortalama = ortalama + devirler[n];
}
devirler[0] = devir;
ortalama = (ortalama + devir) / 10;
*/
printlcd();
if (ortalama < rpmlimit)
{
digitalWrite(alarmpin, HIGH);
timer = millis();
subtimer = timer;
increment = servostep;
while ((millis() - timer) < 3000)
{
throutpwm = thrinpwm + (reverse * increment);
digitalWrite(throut, HIGH);
delayMicroseconds(throutpwm);
digitalWrite(throut, LOW);
thrinpwm = pulseIn(thrin, HIGH, 25000);
//thrinpwm = 1500;
calcrpm();
printlcd();
if ((millis() - subtimer) >= 200)
{
subtimer = millis();
increment = increment - 5;
if (increment < 1) increment = 1;
}
}
increment = 0;
}
else
{
throutpwm = thrinpwm + (reverse * increment);
digitalWrite(alarmpin, LOW);
digitalWrite(throut, HIGH);
delayMicroseconds(throutpwm);
digitalWrite(throut, LOW);
}
if (pressedkey > 0) ayar();
thrinpwm = pulseIn(thrin, HIGH, 25000);
//thrinpwm = 1500;
}
}
//Interrupt ile her inen devir sinyalinde çağrılıyor. x1 ve x2 değişkenleri değiştiriliyor.
//Önceki x1 değeri x2'ye aktarılıyor. Güncel millis() değeri x1'e yazılıyor.
//Buraya gelindiğine göre ateşleme çalışıyor demektir.
//Eğer iki ateşleme arası 100 mS'den küçük ise yani devir 600'den büyükse engineon 1 yapılıyor
//Değerlerin değiştiğini gösteren flag set ediliyor
void readrev()
{
x2 = x1;
x1 = millis();
if ((x1 - x2) <= 100) engineon = 1;
flag = 1;
pressedkey = button();
}
//Parametreleri LCD ekrana yazan altprgram
void printlcd()
{
lcd.setCursor(0, 1);
lcd.print(" RPM");
if (ortalama > 9999)
{
lcd.setCursor(0 , 1);
}
else if (ortalama > 999)
{
lcd.setCursor(1 , 1);
}
else if (ortalama > 99)
{
lcd.setCursor(2 , 1);
}
else if (ortalama > 9)
{
lcd.setCursor(3 , 1);
}
else
{
lcd.setCursor(4, 1);
}
ortalama = (ortalama / 10) * 10;
lcd.print(ortalama);
lcd.setCursor(11, 0);
lcd.print(" ");
lcd.setCursor(11, 0);
lcd.print(servostep);
lcd.setCursor(11, 1);
lcd.print(" ");
lcd.setCursor(11, 1);
lcd.print(increment);
lcd.setCursor(0, 0);
lcd.print(" ");
lcd.setCursor(0, 0);
lcd.print(rpmlimit);
lcd.setCursor(5, 0);
lcd.print(" ");
lcd.setCursor(5, 0);
lcd.print(throutpwm);
}
int button() // 0: Geçerli tuş yok. 1: Sens Up 2: Sense Down 3: Response Up 4: Response Down
{
key1 = buttonbas();
key2 = debouncebutton();
if ((key1 == key2) && ((key1 * key2) != 0))
{
return key1;
}
return 0;
}
int buttonbas()
{
keypress = PINC & B00001111;
for (int n = 0; n < 10; n++)
{
keypress &= PINC;
}
if (keypress == B00001110) return 1;
if (keypress == B00001101) return 3;
if (keypress == B00001011) return 4;
if (keypress == B00000111) return 2;
return 0;
}
int debouncebutton() //Buraya gelirken key1 değişkeni basıldığı belirlenen butonu içeriyor. Yani 0 - 5 arası bir değer.
{
if (key1 == 0) return key1; //Tuşa basılmamış ise hiçbir şey yapmadan geri dön
b = 0; //Sayaç sıfırlanıyor
for (int n = 0; n < debounce; n++)
{
key = buttonbas(); //Butonlar "debounce" kez okunuyor
if (key == key1) b++; //Eğer okunan değer ilk okunan değer ile aynı ise b sayacı bir artırılıyor
else if (key != 0) return 0; //Farklı bir değer okunduysa ve bu değer 0 değil ise bu durum bounce nedeniyle değil başka butona basma nedeniyle olmuştur. 0 döndürüyoruz.
}
if (b < bounce) return 0; //Okunan değerlerin en az % 80'i key1 ile aynı olmalı. Yoksa 0 döndürüyoruz
// while (buttonbas() != 0) //Buraya geldiğimize göre "debounce" test okumasındaki değerlerin en az "bounce"'ı orijinal key1 değeri ile aynı demektir. Artık butonun bırakılmasını bekliyor ve key1 değerini döndürüyoruz
// {
// }
return key1;
}
void ayar()
{
pressed = pressedkey;
switch (pressed)
{
case 1:
rpmlimit = rpmlimit + 6;
if (rpmlimit > 2530) rpmlimit = 2530;
break;
case 2:
rpmlimit = rpmlimit - 6;
if (rpmlimit < 1000) rpmlimit = 1000;
break;
case 3:
servostep++;
if (servostep > 256) servostep = 256;
break;
case 4:
servostep--;
if (servostep < 1) servostep = 1;
break;
}
EEPROM.write(100, (servostep - 1));
EEPROM.write(101, ((rpmlimit - 1000) / 6));
}
void calcrpm()
{
noInterrupts();
y1 = x1;
y2 = x2;
interrupts();
if (y1 == y2)
{
devir = 0;
}
else if (engineon == 1)
{
devir = (60000 / (y1 - y2));
if ((y1 - y2) > 100) engineon = 0;
}
else
{
devir = 0;
}
ortalama = devir;
}
Kod compiler hatası vermiyor. Akşam bakalım breadboard üzerinde nasıl çalışacak. EDİT: Ufak düzeltmeler sonrası gayet güzel çalışıyor gibi.