Yeni proje: Motor rölanti devri emniyeti ve kill switch kombinasyonu

Yeni proje: Motor rölanti devri emniyeti ve kill switch kombinasyonu

Aslında mesele interrupt meselesi. Mstimer2 kütüphanesi interrupt kullanıyor. Bir ISR içinde delay komutu normalde sistemi kilitliyor. Kaldı ki her 20 mS'de bir çalışması gereken bir subroutine içinde öyle veya böyle 200 mS beklersek tüm servo zamanlaması güme gider. :)
 
Yeni proje: Motor rölanti devri emniyeti ve kill switch kombinasyonu

Bu arada düşündüm de benim için yeterince karmaşık olan bu projeye hangi akla hizmet akıllı kill switch entegre ettim acaba?! Onu iptal edip devam edeceğim.
 
Yeni proje: Motor rölanti devri emniyeti ve kill switch kombinasyonu

Kill switch işlevi iptal. Butonlar için şöyle bir çözüm düşündüm:
Dört ayrı değişkene bir önceki buton durumu kaydediliyor. Bir butona basılması halinde önceki seferde onun basılmamış olması şartı aranıyor. Yani bir basıştan sonra tekrar bir etki elde edebilmek için arada bırakılmış olması gerekiyor butonun. Tabii düşüncede güzel görünen gerçekte nasıl olacak onu da uygulamada göreceğiz.

Kod:
// Motor devrini sürekli gözleyip belirli bir değerin altına inmeye başladığında gaz verecek program

#include <EEPROM.h>
#include <MsTimer2.h>


const int throttlein = 4; // Alıcıdan throttle sinyali girişi
const int sensorin = 2; // Ateşlemeden gelen sinyal girişi

const int throttleout = 5; // Gaz servosu çıkışı
const int errorled = 13; // Hata için plaketteki LED kullanılıyor

const int sensup = 15; // A01
const int sensdn = 19; // A05
const int respup = 16; // A02
const int respdn = 18; // A04

const int revpin = 6; // Gaz servosu reverse jumper girişi
const int jumper = 7; // Reverse pin jumperi için HIGH level sağlıyor

unsigned int motorstop = 0; // Motorun stop etmiş olduğunu gösteren değişken. 0 ise motor çalışıyor 1 ise stop etmiş
unsigned int mindevir;
unsigned int devir;
volatile unsigned int rxPWM; // Throttle servo değeri
unsigned int rxPWM2;
unsigned int thrstep;

unsigned long timer;
volatile unsigned long frame; // Servo pulse oluşturmak için kullanılıyor

int flag = 0;
volatile int sensupold = 1;
volatile int sensdnold = 1;
volatile int respupold = 1;
volatile int respdnold = 1;


int n;
int x;

volatile int sensupbuton = 1;
volatile int sensdnbuton = 1;
volatile int respupbuton = 1;
volatile int respdnbuton = 1;

volatile int senschanged = 0;
volatile int respchanged = 0;


void setup()
{
  pinMode(throttleout, OUTPUT);
  pinMode(jumper, OUTPUT);
  digitalWrite(jumper, HIGH);
  pinMode(errorled, OUTPUT);

  pinMode(sensup, INPUT_PULLUP); //Sensitivite + butonu
  pinMode(sensdn, INPUT_PULLUP); //Sensitivite - butonu
  pinMode(respup, INPUT_PULLUP); //Response + butonu
  pinMode(respdn, INPUT_PULLUP); //Response - butonu
  digitalWrite(sensup, HIGH); // Pullup direnci aktive ediliyor
  digitalWrite(sensdn, HIGH); // Pullup direnci aktive ediliyor
  digitalWrite(respup, HIGH); // Pullup direnci aktive ediliyor
  digitalWrite(respdn, HIGH); // Pullup direnci aktive ediliyor

  rxPWM = pulseIn(throttlein, HIGH, 25000);
  MsTimer2::set(20, throut);
  MsTimer2::start();
  eepromcheck();
}


void loop()
{
    rxPWM = pulseIn(throttlein, HIGH, 25000);

    digitalWrite(errorled, flag);

    if (flag == 0);
    {
      devirhesapla();
      if (devir > 0)
      {
        mindevir = 1000 + 4 * EEPROM.read(0);
        thrstep = EEPROM.read(1) / 4;
        if (devir < mindevir)
        {
          if (digitalRead(revpin) == LOW)
          {
            rxPWM = rxPWM + thrstep;
          }
          else
          {
            rxPWM = rxPWM - thrstep;
          }
          if (rxPWM > 2200)
          {
            rxPWM = 2200;
          }
          if (rxPWM < 800)
          {
            rxPWM = 800;
          }

          timer = micros();
          while ((micros() - timer) < 1500000)
          {
            rxPWM2 = pulseIn(throttlein, HIGH, 25000);
            if ((digitalRead(revpin) == HIGH))
            {
              if (rxPWM2 < rxPWM)
              {
                rxPWM = rxPWM2;
              }
            }
            else
            {
              if (rxPWM2 > rxPWM)
              {
                rxPWM = rxPWM2;
              }
            }
          }
        }

      }
    }
}


void devirhesapla()
{
  motorstop = 0;
  n = pulseIn(sensorin, HIGH, 60000);
  if (n == 0)
  {
    motorstop = 1;
    devir = 0;
  }
  else
  {
    devir = 60000000 / n;
  }
}


void throut()
{
  digitalWrite(throttleout, HIGH);
  frame = micros();
  while ((micros() - frame) < rxPWM)
  {
  }
  digitalWrite(throttleout, LOW);
  sensupbuton = digitalRead(sensup);
  sensdnbuton = digitalRead(sensdn);
  respupbuton = digitalRead(respup);
  respdnbuton = digitalRead(respdn);

  if (sensupbuton != sensdnbuton)
  {
    if (sensupbuton == 0 & sensupold == 1)
    {
      mindevir++;
      senschanged = 1;
      if (mindevir > 254)
      {
        mindevir = 254;
        senschanged = 0;
      }
      else if(sensdnbuton == 0 & sensdnold == 1)
      {
        mindevir--;
        senschanged = 1;
        if (mindevir < 1)
        {
          mindevir = 1;
          senschanged = 0;
        }
      }

    }
  }
  if (senschanged == 1)
  {
    EEPROM.write(0, mindevir);
    senschanged = 0;
  }
  if (respupbuton != respdnbuton)
  {
    if (respupbuton == 0 & respupold == 1);
    {
      thrstep++;
      respchanged = 1;
      if (thrstep > 63)
      {
        thrstep = 63;
        respchanged = 0;
      }
      else if(respdnbuton == 0 & respdnold == 1)
      {
        thrstep--;
        respchanged = 1;
        if (thrstep < 1)
        {
          thrstep = 1;
          respchanged = 0;
        }
      }
    }
  }
  if (respchanged == 1)
  {
    EEPROM.write(1, thrstep * 4);
    respchanged = 0;
  }
  sensupold = sensupbuton;
  sensdnold = sensdnbuton;
  respupold = respupbuton;
  respdnold = respdnbuton;
}

void eepromcheck()
{
  x = EEPROM.read(0);
  if (x == 255)
  {
    EEPROM.write(0, 0);
    x = EEPROM.read(0);
    if (x != 0)
    {
      flag = 1;
    }
    x = EEPROM.read(1);
    if (x == 255)
    {
      EEPROM.write(1, 0);
      x = EEPROM.read(1);
      if (x != 0)
      {
        flag = 1;
      }
    }
  }
}
 
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! :lol:

[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. :)
 

Ekli dosyalar

  • Adsýz 1.jpg
    Adsýz 1.jpg
    87.9 KB · Görülme: 47
Yeni proje: Motor rölanti devri emniyeti ve kill switch kombinasyonu

Sümer Yamaner' Alıntı:
........ Dikkat ederseniz artık sistemde kill switch yok. Çünkü plakette yer kalmadı. :) Zaten kill switch'i ayrı satmak daha çok işime gelir! :lol:

Sanki ticari yaklasim on plana cikmaya basliyor gibi hissediyorum abi :laugh: ;D