STM32 ile DC, Step ve Servo motor kontrolü



Merhabalar,
Bu yazımda, sürücü kartlarının da desteği ile Encoder ve Potansiyometre kullanarak DC, Step ve Servo motor kontrolünden bahsederken, ADC ile veri okuma, Encoder kullanımı, OLED ekran kullanımı ve PWM üretimi gibi konulara da değineceğiz.






Deneyde kullanılan malzemeler

  •   STM32F401RE Nucleo deney kartı
  •  128*64 I²C SSD1306 OLED ekran
  •  KY-040 Rotary Encoder
  • 10K Potansiyometre
  • L298N Motor sürücü
  • A4988 Step motor sürücüsü
  • 12v DC motor
  • Nema 17 step motor
  • SG90 servo motor
  • 12v Güç kaynağı
  • 5mm led 
  • 220 Ohm direnç
  • Breadboard ve kablo 




Devre Şeması





BAĞLANTILAR


Potansiyometre orta uç --> PA6          ADC 1 
Encoder -->PA8,PA9                              TIM1_CH1,CH2
Oled
   
   scl-->PB6                                              I2C1_SCL
   sda-->PB7                                             I2C1_SDA
  
L298N DC MOTOR
  
    in1-->PC2     
    in2-->PC3     
    ena-->PA15                                          TIM2_CH1
   
A4988 STEP MOTOR

    step-->PC1                                          

SERVO

     pwm-->PB0                                         TIM3_CH3



BLOK DİYAGRAM




YAZILIM

Not: STM32 F401RE Nucleo kartına sahipseniz ve ayarlarda  değişiklik yapmak istemiyorsanız sayfa sonuna geçerek  "Proje dosyaları" adlı linkte ki dosyayı indirdikten sonra doğrudan keil üzerinden karta yükleme yapabilirsiniz. 

Manuel ayarlar:

STM32F401 NUCLEO kartımızın gerekli ayarlamalarını yapmak için "CubeMX", yazılım derleyicisi için ise "KEİL" programını kullanıyoruz. Program linklerini yazı sonunda bulabilirsiniz.

CubeMX Ayarları

Adım 1: STM32CubeMX programımızı açıyoruz, ardından nucleo deney kartımızı kullanacağımızdan dolayı ACCES TO BOARD SELECTOR seçeneğine tıklıyoruz.





Adım 2: Bizim projede kullanacağımız kart STM32 F401RE modeli olduğu için arama kısmına 401re yazdıktan sonra nucleo deney kartımızı seçerek  projemizi başlatıyoruz. Eğer farklı bir kart kullanıyorsanız ACCESS TO MCU SELECTOR kısmından kendi modelinizi seçerek devam edebilirsiniz. 
!! Nucleo kartı için varsayılan ayarlarla başlatılsın mı ? gibi bir soru gelirse evet diyerek devam edebilirsiniz. 



 
Adım 3: Kristal ayarlarını yapmak üzere System Core kısmından RCC bölümüne giriyoruz, 401RE Nucleo modelinde fabrika çıkışında deney kartı üzerinde yüksek hız kristali yer almadığından dolayı  yüksek hız kristali ayarını BYPASS CLOCK SOURCE olarak seçiyoruz(401RE için varsayılan ayardır) eğer kartınızda kristal var ise Crystal/Ceramic Resonator seçebilirsiniz(401CCU için varsayılan ayardır). 




Adım 4: 401CCU gibi bir karta sahip iseniz SYS bölümündeki Debug kısmını Serial Wire yapmanız kartınıza yazılım yükleyebilmeniz için gerekli ve çok önemlidir.(401RE Nucleo varsayılan ayardır yinede kontrol etmenizde fayda var) Eğer bu ayarı yapmayı unutup bir sonraki yazılım yükleme işleminde sorun yaşarsanız Mail yoluyla benimle iletişime geçebilirsiniz.



Adım 5: Potansiyometre den veri okuyabilmek için Analog bölümünden ADC1 kısmına giriyoruz, buradan IN0 ve IN6 kanallarını aktif ediyoruz projenize göre kanal sayısını arttırıp azaltabilirsiniz kanal numara seçimleri kullanıcı iradesindedir kırmızı olanlar hariç istediğinizi seçebilirsiniz.(Buna göre kanal portları değişir)
 !! İki ve üzeri kanal seçimlerinde "Scan Conversion Mode" seçeneği aktif edilmelidir.



Adım 6: Kanal sayısına göre "Number Of Conversion" değeri seçilerek Rank bölümündeki ayarlar şekildeki gibi yapılmalıdır.(kanal numaraları kullanıcı seçimine göre değişkenlik gösterebilir)
!! Örnekleme hızı potansiyometre den okunan verinin daha kararlı olması için 480 saykıl seçilmiştir daha hızlı okuma için küçültülebilir, çok küçük değerler kanallar arasında verilerin karışmasına neden olur.


Adım 7: Encoder ayarlarını yapmak üzere "Timers" sekmesinden TIM1 bölümüne giriyoruz, "Combined Channels "seçeneğini "Encoder Mode" yapıyoruz ve alt bölümden Encoder Mod'u Timer input1 ve Timer input 2  seçiyoruz(TI1 and TI2), bu ayar Encoder'in sağa ve sola hareketini okumak için gereklidir. 



Adım 8: Artık çıkışları ayarlamaya geçebiliriz, DC motor kontrolünde gerekli olan PWM sinyallerini üretebilmek için TIM2 bölümüne girerek Channel 1'i PWM Generation CH1 seçiyoruz.(kullanıcı istediği kanal ve Timer'i seçebilir)
Prescaler ve Counter Period bizim PWM sinyalindeki frekans ve görev döngüsü ayarlamalarını belirlememiz içindir ve şu formül yoluyla bulunur ;

ARR=f_TIM/(f_PWM (PSC+1))-1

Burada öncelikle PSC=0 ile başlanır hesaplanan değer 16 bit ile ifade edilebiliyorsa (0-65535 aralığında ise) sonuç doğrudur, aksi halde PSC=1 yapılır ve tekrar hesaplanır. İstenilen değere kadar arttırmaya devam edilir.

Bizim kartımızın çalışma frekansı 84 MHz olduğundan ve  PWM frekansımızın 1 kHz olmasını istediğimizden dolayı 
                                                       ARR=84.10*6/1.10*3.(69+1))-1
formülünü kullanarak ARR (Auto Reload Register) yani Counter Period değerimizi 1199 buluyoruz, Prescelar değerimizi formül yoluyla 69 olarak belirlemiştik bu değerleri yazarak devam ediyoruz.



Adım 9: Servo motor kontrolünde 50 hz frekansa sahip olmamız gerektiği için TIM3 bölümünden 3.cü kanalı da  pwm üretme modunda aktif ederek Prescaler değerini 1679, ARR'yi ise 999 yaparak devam ediyoruz.




Adım 10: Oled ekranımızın kullandığı haberleşme yöntemi olan I2C modülasyonunu aktifleştirmek için Connectivity sekmesinden I2C1 bölümüne girerek Mod seçimini I2C ve alt bölümde bulunan Hız modunu Fast Mode olarak değiştiriyoruz.(sizin kullandığınız ekranda farklı bir haberleşme yöntemi var ise onu aktifleştirmeniz gereklidir)



Adım 11: Son olarak l298n sürücüde yön  seçiminde gerekli olan ve step motorda gereken palsleri iletmek için kullanacağımız 3 adet portumuzu da çıkış olarak ayarlayarak (GPIO_Output) port ayarlarını bitirmiş oluyoruz.



Adım 12: Üst sekmede yer alan Clock Configuration sekmesinde AHB Clock frekansımızı nucleo kartımızın izin verdiği maks hız olan 84 MHz değerini yazarak bizim için optimal ayarları otomatik olarak belirleyecek ve sorunları çözecek olan  Resolve Clock  Issues seçeneğine tıklayarak proje ayarları bölümüne geçiyoruz.


Adım 13: Proje ismini ve kayıt yerini belirledikten sonra biz derleyici olarak  "Keil" programını kullanacağımız için derleyici bölümünü "MDK-ARM" olarak seçiyoruz. (hangi program ile yazılımı derleyecek iseniz onu seçmelisiniz)


Adım 14: Son olarak GENERATE CODE diyerek projemizin oluşturulmasını ve sonrasında çıkan Open Project seçeneğine tıklayarak derleyicimizde çalışmak üzere kod bölümüne geçiyoruz.(Eğer Keil programı yüklü değil ise open project seçeneğine tıkladığınızda projeniz açılmayacaktır, programı yükledikten sonra belirlediğiniz proje kayıt klasörüne giderek manuel olarak açabilirsiniz)



CubeMx kısmı bu kadardı :)

KOD bölümü


Adım 15: Keil programı açıldıktan sonra Application/User/Core klasörüne sağ tıklayarak Add Existing File seçeneğini seçiyoruz.  



Adım 16: Core bölümündeki Inc ve Src klasörlerine girerek yazı sonundaki açıklamalarda linkini verdiğim .c ve .h dosyalarını ekliyoruz.
 !! Bu bölümde dosyaların gözükmesi için açıklamada verilen dosyaları daha önceden Projenin kayıtlı olduğu klasöre girerek Core klasörünün içine eklemelisiniz




Adım 17:  Dosyaları ekledikten ve main.c bölümüne girdikten sonra şekilde belirtilen Rebuild seçeneğine bir kez tıklayarak ekledğimiz dosyaların derlenmesini sağlıyoruz bu işlem sonrasında verilen kodları gerekli bölümlere yazarak kodumuzu yüklemeye hazır hale getiriyoruz.



Kodlar

Adım 18: Genel kütüphane tanımlamalarını yapıyoruz, eğer oled ekran kullanmayacaksanız Oled küpühane yazılı kütüphaneleri silebilirsiniz.



Değişken tanımlamalarını aşağıdaki gibi yapıyoruz:
/* USER CODE BEGIN PV */

uint16_t ort,ort2,pwmd,pwm1,step1,k,adimm=0,say=0,tur=0,selamlama=0,potkararlilik=0;
uint32_t adc1,adc2,motorhizi,motorhizort;
int16_t adim_sayisi=0;
char buf[4];
char buf2[4];
char karakter_dizisi[16];


/* USER CODE END PV */ 


/* USER CODE BEGIN PFP */
void selamlama_durumu(unsigned int);
/* USER CODE END PFP */ 


Ayarlarımızı çağırıyoruz:
  /* USER CODE BEGIN 2 */
HAL_TIM_Encoder_Start(&htim1,TIM_CHANNEL_ALL);   //Modul baslatilir
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);           //Modul baslatilir
        HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);           //Modul baslatilir
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);           //Modul baslatilir
  SSD1306_Init ();            //OLED ayarlarini cagirir, ekran kullanılmayacaksa gerekli değil.
  lcd_init ();                       //OLED ayarlarini cagirir, ekran kullanılmayacaksa gerekli değil.

  selamlama_durumu(1);        //1-aktif,0-pasif, Motorlari baslangicta test eder

  SSD1306_Fill (0);
 SSD1306_UpdateScreen(); //display

  /* USER CODE END 2 */

Döngü içerisindeki kodlar:

while (1)
 {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
HAL_GPIO_TogglePin  (GPIOA,  GPIO_PIN_5);   // LED'i aç/kapa 
HAL_Delay  (10 ); 


//ADC Ve Encoder veri okuma 
adim_sayisi= __HAL_TIM_GET_COUNTER(&htim1);
adimm=TIM_COUNTERMODE_CENTERALIGNED1;
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 1000);
adc1 = HAL_ADC_GetValue(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 1000);
adc2 = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Stop (&hadc1);
//DC Motor kontrol bolumu
//********************************************************************************************
 
//Potansiyometrenin kararli calismasi icin 5 kez veri okunup ortalamasi alinir
    motorhizi=adc2/20;
motorhizort+=motorhizi;
ort2++;
potkararlilik=5; //kararlilik degeri (yukseltildikce okuma hizi duser)(onerilen=5)
while(ort2==potkararlilik)
{
    ort2=0;
      motorhizort/=potkararlilik;
  if((95<=motorhizort)&&(105>=motorhizort))
{
  TIM2->CCR1=0;
        motorhizort=100;
    SSD1306_GotoXY (70,30);
          sprintf(buf, " %%%d     ", motorhizort-100);
            SSD1306_Puts (buf, &Font_7x10, 1);
      SSD1306_UpdateScreen(); 
  GPIOC->ODR &=0x00;
  if((105<motorhizort)&&(200>motorhizort))
{
    GPIOC->ODR &=0x00;
    GPIOC->ODR |=0x08;
    TIM2->CCR1=(motorhizort-100)*10;
    SSD1306_GotoXY (70,30);
          sprintf(buf, "CW %%%d ", motorhizort-100);
          SSD1306_Puts (buf, &Font_7x10, 1);
      SSD1306_UpdateScreen(); 
}

     
  if((1<=motorhizort)&&(95>motorhizort))
{     GPIOC->ODR &=0x00;
    GPIOC->ODR |=0x04;
    TIM2->CCR1=(100-motorhizort)*10;
    SSD1306_GotoXY (70,30);
          sprintf(buf, "CCW %%%d ", 100-motorhizort);
          SSD1306_Puts (buf, &Font_7x10, 1);
      SSD1306_UpdateScreen(); 
}
    motorhizort=0;
}
//******************************************************************************************

//Servo kontrol bolumu
if(adim_sayisi>90)
{
   adim_sayisi=90;
}
//SSD1306_Fill (0);
SSD1306_GotoXY (3,15);
SSD1306_Puts ("Servo Derece=", &Font_7x10, 1);
SSD1306_GotoXY (100,15);
sprintf(buf, "%d ", adim_sayisi*2);
SSD1306_Puts (buf, &Font_7x10, 1);
 
TIM3->CCR3=adim_sayisi+24;
SSD1306_GotoXY (3,30);
SSD1306_Puts ("DC Motor=", &Font_7x10, 1);
HAL_Delay(1);
SSD1306_UpdateScreen(); //display


 }


Selamlama alt programı: 

void selamlama_durumu(unsigned int selamlama)
{
if(selamlama==1)
{
SSD1306_Fill (0);
SSD1306_GotoXY (10,20);
SSD1306_Puts ("DC Motor Testi", &Font_7x10, 1);
SSD1306_GotoXY (50,40);
SSD1306_Puts ("Test ", &Font_7x10, 1);
SSD1306_UpdateScreen(); //display
 
 GPIOC->ODR |=0x08;
for(pwmd=0;pwmd<500;pwmd+=50)
{
TIM2->CCR1=pwmd;
HAL_Delay  (100);
}

 HAL_Delay (100);

 SSD1306_GotoXY (50,40);
SSD1306_Puts ("Test .", &Font_7x10, 1);
SSD1306_UpdateScreen(); //display

 for(pwmd=500;pwmd>0;pwmd-=50)
{
TIM2->CCR1=pwmd;
HAL_Delay  (100);
}


 GPIOC->ODR &=0x00;
 HAL_Delay  (10);
 GPIOC->ODR |=0x04;

 SSD1306_GotoXY (50,40);
SSD1306_Puts ("Test ..", &Font_7x10, 1);
SSD1306_UpdateScreen(); //display 

for(pwmd=0;pwmd<500;pwmd+=50)
{
TIM2->CCR1=pwmd;
HAL_Delay  (100);
}

 HAL_Delay  (2000);

 for(pwmd=500;pwmd>0;pwmd-=50)
{
TIM2->CCR1=pwmd;
HAL_Delay  (100);
}
 GPIOC->ODR &=0x00;

 SSD1306_GotoXY (50,40);
SSD1306_Puts ("BITTI ", &Font_7x10, 1);
SSD1306_UpdateScreen(); //display
HAL_Delay  (1000);
 SSD1306_Fill (0);
SSD1306_GotoXY (10,20);
SSD1306_Puts ("Step Motor Testi", &Font_7x10, 1);
SSD1306_GotoXY (50,40);
SSD1306_Puts ("Test ", &Font_7x10, 1);
SSD1306_UpdateScreen(); //display

while(tur!=8)
{
 for(step1=0;step1<50; step1++)
  {
    GPIOC->ODR |=0x02;
    HAL_Delay  (3);
    GPIOC->ODR &=0x00;
    HAL_Delay  (3);
  }
 HAL_Delay  (1000);
tur++;
}
SSD1306_GotoXY (50,40);
SSD1306_Puts ("BITTI ", &Font_7x10, 1);
SSD1306_UpdateScreen(); //display
HAL_Delay  (1000);
 SSD1306_Fill (0);
SSD1306_GotoXY (10,20);
SSD1306_Puts ("Servo Motor Testi", &Font_7x10, 1);
SSD1306_GotoXY (50,40);
SSD1306_Puts ("Test ", &Font_7x10, 1);
SSD1306_UpdateScreen(); //display

TIM3->CCR3=0;
HAL_Delay(1000);
TIM3->CCR3=30;
HAL_Delay(1000);
TIM3->CCR3=0;
SSD1306_GotoXY (50,40);
SSD1306_Puts ("BITTI ", &Font_7x10, 1);
SSD1306_UpdateScreen(); //display
HAL_Delay(1000);
    }
}



Linkler


Core dosyaları https://drive.google.com/file/d/1EeeW_GvcrCamsp9I6XMe5HSqcDjvyOHT/view?
usp=sharing

YOUTUBE ANLATIM VİDEOSU






Yorumlar