19 Nisan 2019 Cuma

15 - İki STM32F4 ile Haberleşme

Merhaba,

Elektronik yazılım ile ilgilenen bir çok kişi en azından bir kaç kere iki işlemciyi veya işlemci ile bilgisayarı haberleştirmek ihtiyacı hissetmiştir. Bu yazımızda iki adet Stm32f4 kartını birbiri ile USART üzerinden haberleştireceğiz. 

Normalde bu örneği yapmayacaktım ancak bir arkadaşım rica ettiği için paylaşıyorum. Örnek kolay bir örnek olabilir, ama bir çok kişinin işine yarayacaktır. 

Öncelikle haberleşme için bir çok yöntem kullanabilirdik, örneğin; Bluetooth, SPI, I2C, USART, RF, Ethernet vb örnekleri çoğaltabiliriz. Ancak bizim kitimizde USART dahili olarak bulunduğu için bu haberleşmeyi tercih ettik. 

O halde kod yazmaya geçebiliriz.

Bu yazıda her iki kart da birbirine veri gönderebilir.

#include "stm32f4xx.h"                             //stm32f4 kütüphanesi
#include "stm32f4xx_gpio.h"                    //GPIO kütüphanesi
#include "stm32f4xx_rcc.h"                       // RCC clock kütüphanesi
#include "stm32f4xx_usart.h"                  // usart kütüphanesi

/********************************************************************************/
void GPIO_Init(void);
void USART_Init(void);
/********************************************************************************/


/********************************************************************************/
/* Gerekli değişken tanımlamaları */

unsigned char Device_ID = 1;
unsigned char Rx_Data[10];
unsigned char Tx_Data[10];
unsigned char gelen_data_sayaci= 0;
unsigned char Gelen_veri=0;
/********************************************************************************/


/* Led için konfigürasyon ayarlamaları */
void GPIO_Init(void)
{
   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
   
    GPIO_InitTypeDef  GPIO_InitStructure; // Port yönlendirmesi

/* Kart üzerindeki butonu giriş olarak tanımladık */
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;  //Pinler cikis olarak belirlendi
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

   /* PD12, 13, 14 ve PD15 pinleri kullan1lacak */
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;  //Pinler cikis olarak belirlendi
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

}


/* USART1 ayarlamaları */
/* usart kesmesi de ayarlandı. gelen data direkt olarak kesme fonksiyonu ile alınacaktır */
void USART_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

     GPIO_InitTypeDef GPIO_InitStructure;
     USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef  NVIC_InitStructure;

     GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
     GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6 | GPIO_Pin_7;
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     GPIO_Init(GPIOB, &GPIO_InitStructure);

// GPIO ALTERNATE FUNCTION olarak tanimlanan pinin konfigürasyonlari
// pin ile baglantı kuracagi modül belirlenir.
     GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_USART1);
     GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_USART1);


     // USART1 Reset
     USART_DeInit(USART1);
     // USART konfigürasyonlari:
     USART_InitStructure.USART_BaudRate = 9600;
     USART_InitStructure.USART_HardwareFlowControl= USART_HardwareFlowControl_None;
     USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
     USART_InitStructure.USART_Parity = USART_Parity_No;
     USART_InitStructure.USART_StopBits = USART_StopBits_1;
     USART_InitStructure.USART_WordLength = USART_WordLength_8b;
     USART_Init(USART1, &USART_InitStructure);

     USART_ITConfig(USART1, USART_IT_RXNE,ENABLE);

     // NVIC Init
     NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
     NVIC_Init(&NVIC_InitStructure);

     USART_Cmd(USART1, ENABLE);
}


int main(void)
{
     GPIO_Init();
     USART_Init();

/* Bütün ayarlamaları yaptık. Butona bastığımızda diğer karta veri gönderecek. Hatta bunu bir dizi paketi halinde gönderecek. Bu kartımızın kimlik bilgisi 1 olsun. diğer kartın 2. 
*/
 while(1)
 {

if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)) { while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)); //Butondan çekilene kadar bekle
Tx_Data[0] = 1;   //kimlik bilgimiz 1
Tx_Data[1] = 41;// Gidecek veri, Bunu A harfi olarak da yazabilirsiniz ancak tek karakter olması gerek
// Tx_Data[1] = 'A';   // String bir karakter için böyle de gönderebilirsiniz
USART_SendData(USART1, Tx_Data[0]);
USART_SendData(USART1, Tx_Data[1]);
// İki Datamızı da gönderdik 
}

if(Gelen_veri == 41) {
 GPIO_SetBits(GPIOD,GPIO_Pin_12);
{
else
{
 GPIO_ResetBits(GPIOD,GPIO_Pin_12);
}

     }

}


/********************************************************************************/
/* USART1 KESME FONKSİYONU */

void USART1_IRQHandler(void)
{
     if( USART_GetITStatus(USART1, USART_IT_RXNE) )
     {
          USART_ClearITPendingBit(USART1, USART_IT_RXNE);//kesme bayrağı temizlenir

/* eğer USART biriminden herhangi bir veri gelirse bu fonksiyona girecektir*/

          // Gelen veriyi okuma işlemi
          Rx_Data[gelen_data_sayaci] = USART_ReceiveData(USART1);

          // Gelen paketteki data sayacini arttir
          gelen_data_sayaci++;

/* Bize gelen verinin 2 adet olduğunu varsayıyoruz, bu yüzden aşağıdaki gibi bir işlem yapıyoruz */

          if(gelen_data_sayaci == 2)
          {
                    if(Rx_Data[0]== 1) // Yani bizim kimilik bilgimiz ile eşleşiyor ise
                    {
                              Gelen_veri = Rx_Data[1];
                              gelen_data_sayaci = 0; //Yeni gelecek veri paketi için sayacı sıfırla
                    }
                    
          }

     }
     else if( USART_GetITStatus(USART1, USART_IT_TXE) )
     {
          USART_ClearITPendingBit(USART1, USART_IT_TXE);
     }


}


Bu kodu her iki karta da yüklediğinizde, birinin butonuna basarsanız diğerinin PD12 ledi yanacaktır. aynı şekilde diğerinin butonuna bastığınızda ilk kartın ledi yanacaktır. Bu örnek üzerinde ufak oynamalar yaparak değişik işlemlerde kullanabilirsiniz.

NOT: Birinci kartın TX pinini diğer kartın Rx pinine, Birinci kartın RX pinini diğer kartın Tx pinine bağlamalısınız. Ayrıca iki kartın GND pinleri de birbirine bağlanmalıdır. Yani  birinci kartın PB6 pinini diğer kartın PB7 pinine, yine birinci kartın PB7 pinini diğer kartın PB6 pinine bağlamalısınız.

İyi Çalışmalar




8 Nisan 2019 Pazartesi

14 - STM32F4 ile Enkoder Okuma

Merhaba,

Enkoder çoğu dc motor uygulamalarında konum belirleme, hız ölçme vb sebeplerden ötürü kullanılmaktadır. Ayrıca radyo ses artırma/azaltma gibi çeşitleri de bulunmaktadır. Ya da encoderli bir çevirmeli buton ile çeşitli projeler yapabilirsiniz. Örneğin aşağıda "rotary encoder" ve üzerinde "Ok" tuşu bulunan bir ürünle projelerinizi farklı bir boyuta taşıyabilirsiniz.


Bildiğiniz üzere "rotary encoder" 'lerde A, B ve bazen de Z çıkışı bulunmaktadır. Biz bu uygulamada A ve B uçlarını kullanarak enkoder okuyacağız. Eğer bu yazıyı okuyorsanız enkoderin genel yapısını bildiğinizi düşünüyorum. Bu yüzden enkoderin A ve B çıkış sinyallerine kısaca değinip koda geçeceğiz. 


Yukarıda bir enkodere ait A ve B çıkış sinyalleri bulunmaktadır. Bu uçlar bir kare dalga üretirler. Başlangıçta A çıkışı lojik 1 olduğunda B ucu lojik 0'dadır. A ucunun lojik 1 olduğu bölgenin yarısında B ucu lojik 1 olur. A ucu lojik 0'a geçtiğinde B ucu halen lojik 1'dedir ta ki A ucunun lojik 0 olduğu bölgenin yarısına kadar, ve bu böylece devam eder. 

Eğer bir enkoder bir turda 250 darbe üretiyor diye yazıyorsa teknik dokümanında, bu sadece bir tetik için geçerlidir. Yani sadece bir kanalın ürettiği kare dalganın yükselen kenarında ölçüm yapıyor isek bir turda 250 darbe okuruz. Hem A hem de B kanalından okuma yapıyor isek bu değer iki kanal olduğu için 500'e çıkar. Hem yükselen hem de düşen kenar tetiklemesinde ölçüm yapıyor isek bir kanalda 250*2=500 darbe okuruz, iki kanal okuyorsak 250*4=1000 darbe okuruz. 

Artık koda geçebiliriz. "Stm32f4 discovery board" stm32f407vgt6 işemcisine sahiptir ve bu işlemcinin kendi enkoder modulu bulunmaktadır. Yani adamlar demişler ki eğer enkoder okuyacaksanız biz sizin için bu işi basit hale getirdik, rahatça işleminizi yapabilirsiniz, ister yükselen kenarda, ister düşen kenarda, isterseniz hem düşen hem yükselen kenardan okuyabilirsiniz diyor. Ancak bunu yaparken bir "timer" birimini enkoder için kullancaksın diyor, çünkü bu "timer" birimi sayesinde rahatça okuyorsun diyor. Şimdiye kadar yazdıklarımız tamamsa, başlayalım.

Enkoder için bir fonskiyon yazalım, bu fonksiyonda enkoderin ayarlarını yapalım.
Enkoder için "timer4" birimini kulanacağız. Siz de herhangi bi CH1 ve Ch2 çıkışı olan bir "timer" birimi kullanabilirsiniz. burada "timer4" kullanacağız. CH1 ve Ch2 için de teknik dokümanından baktığımızda GPIO_Pin_6 ve GPIO_Pin_7 kullanabiliyoruz. 

void Encoder_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; 
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_Init (GPIOB, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_TIM4);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_TIM4);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
TIM_EncoderInterfaceConfig(TIM4,TIM_EncoderMode_TI1,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);  

TIM_SetAutoreload (TIM4, 0xffff);
TIM_Cmd (TIM4, ENABLE);
}

// burada enkodere herhangi bir değer verilebilir. örneğin 50, yada 100 artık ne verirseniz
//bu fonksiyondan sonra encoderiniz bu değerden saymaya başlar
void Encoder_Set(int deger)
{
TIM_SetCounter (TIM4, deger);
}

// buradan da enkoderin hangi değerde olduğu okunabilir

int  Read_Encoder_Value(void)
{
return TIM_GetCounter (TIM4);
}


yazdığınız programda ne zaman nerde enkoderden değer okumak istiyorsanız aşağıdaki gibi yapabilirsiniz.

int encoder_value=0;

encoder_value = Read_Encoder_Value();

bu kod parçasını istediğiniz gibi kendi projenize dahil edebilirsiniz.

enkoder ileri giderken artı yönde geri giderken eksi yönde saymaktadır.

İyi Çalışmalar

27 Mart 2019 Çarşamba

13 - STM32F4 Flash Yazma-Okuma

Merhaba arkadaşlar,

Stm32f4 discovery'de flasha veri yazma okuma işlemi yapacağız. Burada uzunca özelliklerden bahsedebilirdik ancak zaman kısıtlığından dolayı direkt kodlar üzerinden gideceğiz.


Yukarıdaki resimde Stm32f40X ve Stm32f41X serilerinin flash tablosu görülmektedir. Siz de Stm32f4 Reference Manual olarak google amcadan aratarak kolayca ulaşabilirsiniz. Sayfası 75. Burada görüldüğü üzere veriler sektör sektör kaydediliyor. Siz de bu tabloya göre istediğiniz adrese verileriniz yazıp okuyabilirsiniz. Ancak dikkat edilmesi gereken bir husus daha var, verileri flasha yazmadan önce yazacağınız adresin kullanılıp kullanılmadığına bakın. Bunu basitçe St-Link Utility programı ile yapabilirsiniz. Eğer adreste herhangi bir veri kayıtlı değilse adresin değeri 0xFFFF olarak görünecektir. 

Artık kod yazmaya başlayabiliriz. 
Burada direkt olarak flasha yazma ve okuma fonksiyonları verilecektir. Siz ihtiyacınıza göre farklı şekillerde kullanabilirsiniz. 

Flash'a yazma fonksiyonu:

void Data_Write_Flash()
{
uint32_t  Address = 0x08040000;
uint16_t data = 38;

FLASH_Unlock();  // öncelikle Flash kilidi kaldırılır
// Hata Bayraklari Temizlenir
FLASH_ClearFlag(FLASH_FLAG_EOP|FLASH_FLAG_OPERR|FLASH_FLAG_WRPERR|
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
// Flash Sektörü Sil
/* Biz burada 6. söktörü kullanacağız. 6.sektör için flash adresimiz 0x0804 0000 - 0x0805 FFFF aralığında olabilir. Ancak yazmadan önce tüm sektörü silmek zorundayız. o yüzden eğer aynı sektöre birden çok ve farklı zamanlarda veri yazılacaksa silme işleminden önce sektördeki değerler bir değişkene aktarılır ve daha sonra yazılacak olan yeni veriyle birlikte hepsi yazılır. */
FLASH_EraseSector(FLASH_Sector_6, VoltageRange_3);
/* yukarıda yine VoltageRange_3 diye yazdık, bu da kullandığımız işlemciyi besleme voltajımızdır. Flash kütüphanesinin içinde bunu aşağıdaki gibi göstermiştir.*/
#define VoltageRange_1        ((uint8_t)0x00)  /*!< Device operating range: 1.8V to 2.1V */
#define VoltageRange_2        ((uint8_t)0x01)  /*!<Device operating range: 2.1V to 2.7V */
#define VoltageRange_3        ((uint8_t)0x02)  /*!<Device operating range: 2.7V to 3.6V */
#define VoltageRange_4        ((uint8_t)0x03)  /*!<Device operating range: 2.7V to 3.6V + External Vpp */
/* Biz işlemcimizi 3.3V ile beslediğimiz için VoltageRange_3 kullandık, bu da besleme voltajımız 2.7V to 3.6V arasında olduğu içindir. */

/*şimdi de kaydedilecek verimizi adrese yazabiliriz. 
FLASH_ProgramHalfWord(Address, data);
/*yukarıda sektör6 daki 0x08040000 adresine, 38 değerini yazdık. Artık bu adreste 38 verisi saklanmaktadır. Daha sonra bu adresi okuduğumuzda 38 verisini bize verecektir. */

FLASH_Lock();
/* Son olarak Flash kilitleme işlemi yapılır ve fonksiyondan çıkılır. */

}


Flash'dan okuma fonksiyonu:

void Data_Read_Flash()
{
uint32_t  Address = 0x08040000;
uint16_t data;

/* okuma işlemi çok basittir sadece daha önceden veri yazılan bir adresten veri okuma olayıdır. Yani direkt olarak adresten veriyi çekeceğiz */

data = *(uint32_t *)Address; 
/* Yani 0x08040000 adresindeki veriyi okuduk. Yukarıdaki fonksiyonda 38 olarak kayıt yapmıştık oku dediğimizde 38 değerini verecektir */

}

İyi Çalışmalar


18 Mart 2019 Pazartesi

12 - RS485 ŞEMATİK GÖSTERİMİ

Merhaba arkadaşlar,

Daha önceki yazılarımızda modbus haberleşmenin teorik anlatımı ve uygulaması paylaşılmıştı. Bu yazımızda da donanım altyapısı anlatılacaktır. 


Yukarıdaki resimde RS485 bağlantısının şematik gösterimi paylaşılmıştır. Entegre olarak DS485 kullanılmıştır. Ayrıca SP485 entegresi de aynı işlevi yapmaktadır ve hatta paket olarak birebir aynıdır. 

İster STM32F4 olsun isterse de herhangi bir işlemci olsun USART özelliği bulunan her işlemci için bu yapı kullanılabilir. İşlemcimizin Tx pini RS485 entegremizin DI pinine, Rx pini de RO pinine bağlıdır. Burada dikkat edilmesi gereken nolta DE ve RE pinleridir. Modbus haberleşmemizde DE ve RE pini birleştirilerek kullanılmıştır. Bu DERE pinini de işlemcimizin herhangi bir output pinine bağlayabiliriz. En önemli husus ise bu DERE pini veri alacağımız zaman logic 0'da, veri göndereceğimiz zaman da logic 1'de olması gerekmektedir. 

Konuyu daha açacak olursak, eğer Modbus haberleşmemizde Slave isek, başlangıçta DERE pinini logic 0'a çekeriz, yani bunu yaparak bir sorgu beklediğimizi belirtiriz. Sorguyu aldıktan sonra, cevabı göndermeden önce logic 1'e çekeriz ve cevabımızı göndeririz. Cevabı gönderdikten sonra ise tekrar logic 0'a çekerek yeni sorguyu bekleriz ve bu döngü böyle devam eder. 

Eğer Modbus haberleşmesinde biz Master isek, başlangıçta sorgu çekmemiz gerekiyor. O halde sorguyu çekmeden önce DERE pinini logic 1'e çekeriz, sorgumuzu göndeririz ve ardından DERE pinini logic 0'a çekeriz ve sorgumuzun cevabını bekleriz. Bu da böyle bir döngü içinde devam eder.

Bir diğer husus ise haberleşmede veri kayıplarını önlemek amacı ile, entegrenin A ve B pinlerinin olduğu tarafta, B pinin bir direnç ile GND'ye bağlarız. Buradaki direnç 1K ile 10K arasında olabilir. Bu aralığın dışında farklı bir direnç koymamanızı tavsiye ederim. Yine aynı şekilde A pinini de bir direnç yardımı ile Vcc besleme ucuna bağlarız. Bu direnç aralığı da aynı şekildedir. Ve son olarak, haberleşmenin sonundaki Slave'e A ve B pinleri arasına 120R'lik bir direnç koyulur. Bu da sonlandırma direnci diye geçer. Bu dirençleri koymadan da muhtemelen haberleşmemiz çalışacaktır. Ancak bazı haberleşmelerde bu dirençler olmadığı için sıkıntı oluşmaktadır. Örneğin ben kendim bizzat bu problemleri yaşadım. O yüzden eşeğinizi sağlam kazığa bağlayın ki sonradan uğraşmayın.

Daha önceki yazılarımızda yine RS485 haberleşmesi için diferansiyel bir haberleşmedir demiştik. Bu yüzden A ve B pinlerine bağlı olan telimiz ya da kablolarımız birbirine sarılarak uzatılır ya da birbirine çok yakın halde uzatılır. Yani eğer bir parazit varsa her iki pine de etki eder ve böylece haberleşme diferansiyel olduğu için haberleşmemiz etkilenmemiş olur. 

İyi Çalışmalar...

12 Mart 2019 Salı

11 - MODBUS RTU Master Örneği

Merhaba,

Bir önceki yazıda modbus rtu slave örneğini paylaşmıştık. Aslında bu örnek de ondan çok bir farkı bulunmamakta. Donanımsal altyapı olarak tamamen aynıdır. RS485 entegresinin çıkışındaki A ve B pini hat üzerindeki her slave cihazın A ve B pinlerine bağlıdır. Yani haberleşme iki tel üzerinden bütün cihazlara bağlıdır. RS232 gibi TX Rx'e, RX TX'e bağlı değildir. Yani Master'daki A pini Slave'deki A pinine, B pini de B pinine bağlıdır ve tek bir tel bütün cihazlara bağlıdır. 

Yukarıdaki resim RS485 altyapısı kullanıldığında geçerlidir. Eğer RS232 kullanılıyorsa Tx Rx'e, Rx Tx'e bağlanmalıdır.

Önceki yazımızda Slave örneği için bize sorgu gelmesini bekliyorduk. Şimdi ise Master olduğumuzdan mütevellit sorguyu bizim çekmemiz gerekiyor. 

O halde kodu yazmaya başlayabiliriz.

KOD KISMI:

Başlnagıç olarak kütüphaneler eklenir.

#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h" // GPIO kütüphanesi
#include "stm32f4xx_rcc.h" // RCC clock kütüphanesi
#include "stm32f4xx_usart.h" // Keil::Device:StdPeriph Drivers:USART
#include "stm32f4xx_it.h"
#include "stm32f4xx_tim.h"

/********************************************************************************/
// Altta kullanılacak olan fonksiyonlar burada tanıtılır //

void GPIO_Init(void);
void USART_Init(void);
void TIM6_Init(void);
void USART1_IRQHandler(void);
void TIM6_DAC_IRQHandler(void);
void ReadHoldingRegister();
char CRC_check(char package[],unsigned int package_length);
void Get_CRC(unsigned char package[],unsigned int package_length);
/********************************************************************************/

/********************************************************************************/
/* Gerekli değişken tanımlamaları */

unsigned short Readed_Array[100];
unsigned char Modbus_Receive_Array[100];
unsigned char Modbus_Trans_Array[100];
unsigned short modbus_receive_counter = 0;
unsigned int modbus_data_counter = 0;
unsigned char modbus_slave_addr = 1; //Slave adresimiz 1 olsun
unsigned short Holding_Register_Adress_Value = 255;
unsigned char frame_length;
unsigned short Tx_count= 0;
unsigned short i=0;
unsigned short data_count = 0;
/********************************************************************************/

/* Led için konfigürasyon ayarlamaları */
// ledler aslında kullanılmamıştır ancak haberleşmeyi kontrol amaçlı kullanılabilir //

void GPIO_Init(void)
{
   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

    GPIO_InitTypeDef  GPIO_InitStructure; // Port yönlendirmesi

   /* PD12, 13, 14 ve PD15 pinleri kullan1lacak */
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;  //Pinler cikis olarak belirlendi
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    /* PA0 pini buton için kullanılacak */
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;  //Pin giriş olarak belirlendi
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

}


/* USART1 ayarlamaları */
/* usart kesmesi de ayarlandı. sorgu yapıldıktan sonra gelen data direkt olarak kesme fonksiyonu ile alınacaktır */
void USART_Init(void)
{
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

     GPIO_InitTypeDef GPIO_InitStructure;
     USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef  NVIC_InitStructure;

     GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
     GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6 | GPIO_Pin_7;
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     GPIO_Init(GPIOB, &GPIO_InitStructure);

// GPIO ALTERNATE FUNCTION olarak tanimlanan pinin konfigürasyonlari
// pin ile baglant1 kuracagi modül belirlenir.
     GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_USART1);
     GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_USART1);


     // USART1 Reset
     USART_DeInit(USART1);
     // USART konfigürasyonlari:
     USART_InitStructure.USART_BaudRate = 9600;
     USART_InitStructure.USART_HardwareFlowControl= USART_HardwareFlowControl_None;
     USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
     USART_InitStructure.USART_Parity = USART_Parity_No;
     USART_InitStructure.USART_StopBits = USART_StopBits_1;
     USART_InitStructure.USART_WordLength = USART_WordLength_8b;
     USART_Init(USART1, &USART_InitStructure);

     USART_ITConfig(USART1, USART_IT_RXNE,ENABLE);

     // NVIC Init
     NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
     NVIC_Init(&NVIC_InitStructure);

     USART_Cmd(USART1, ENABLE);
}


void TIM6_Init(void)
{
/*
1sn timeout zamanı için:

TIM6 APB1’e bağlı gözüküyor. APB1’in çalışma hızı 42 MHz. Ancak Datasheet’e bakıldığında önemli bir uyarı ile karşılaşıyoruz:

       APB2’ye bağlı olan zamanlayıcılar için clock hattı 168Mhz’e kadar olabilir, APB1 içinse 84Mhz olabilir. Burada bir ikilem var gibi duruyor ancak datasheet’te bunu şöyle açıklıyor:  APB ön bölücü değeri 1 ise timer clock hattı da aynı frekansta çalışır, ancak ön bölücü değeri 1 değilse veya 1’den farklı bir değerde ise timer clock hattı APB hattının 2 katı hızda çalışır.

Timer6’ye baktığımızda APB1 bus hattına bağlıdır. Bu hattın clock frekansı 42 Mhz. Daha önce CMSIS klasöründe oluşturduğumuz system_stm32f4xx.c dosyasında ön bölücü değeri 4 olarak kullanıldığı için bu hat 42*2 yani 84 Mhz clock frekansında çalışmaktadır. Daha detaylı bilgiye datasheet’ten bakılabilir.

Prescaler değeri ise:      Timer hızı  = Bus hızı / (prescaler + 1)  formülünden bulunur.

Sorgu yapıldıktan sonra cevap beklenir burada timer ile bekleme süresini ayarlayabiliriz

Örneğin bekleme süremiz yani haberleşme timeout süremiz 1 saniye olsun:

1sn = 1/1 = 1Hz frekansa eşittir. 

Öncelikle 2Khz (2000Hz) için:            2000 = 84Mhz(buz hızı) /(prescaler+1)
                                                            2000 = 84000000 / (PRSC + 1) = > PRSC = 41999 

Periyodu da biz 2000’e kadar saydırırsak:           2000 Hz/ 2000 = 1Hz
                                                                           1Hz = 1/= 1 sn demektir.

Prescaler değeri: 42000 - 1
Periyod: 2000 -1 = 1999 (sayıma 0’dan başladığı için 1 eksiği olur)
1 sn için değerlerimizi yukarıdaki gibi hesaplarız.
*/
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
     TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure; // Struct
     NVIC_InitTypeDef NVIC_InitStructure;

     TIM_DeInit(TIM6);

     TIM_TimeBaseStructure.TIM_Period = 2000 -1;
     TIM_TimeBaseStructure.TIM_Prescaler = 42000 -1; /
     TIM_TimeBaseStructure.TIM_ClockDivision =0;  // TIM_CKD_DIV1
     TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
     TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
     TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);

     TIM_ClearITPendingBit(TIM6,TIM_IT_Update);  // Clear Update interrupt flag

     TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); // Interrupt

     NVIC_InitStructure.NVIC_IRQChannel = TIM6_DAC_IRQn; // NVIC Ayarlari
     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
     NVIC_Init(&NVIC_InitStructure);

     TIM6->CR1 &=~ 0x0001;             // Counter Disable
      // TIM6'yı daha aktif etmedik dikkat edin. !!
}
/********************************************************************************/


int main(void)
{
unsigned short adres_baslangic = 0; //ahngi adresten veri istenilecek
     GPIO_Init();
     USART_Init();

/* modbus sorgusunu çektikten sonra gelen paketi değerlendirmek için 1sn timeout zamanı olsun dedik. bu süreyi siz istediğiniz gibi değiştirebilirsiniz.
    Bu zamanı timer6 birimi ile yapmak istiyoruz. o yüzden timer6 yı yaklaşık olarak 1sn timeout süresine ayarlamamız gerekli.
    yukarıdaki hesabımıza göre bize yaklaşık olarak 9600 baudrate hızında,
    1sn için hesaplamaları alttaki fonksiyon içerisinde yapalım
*/
     TIM6_Init();

/* Bütün ayarlamaları yaptık. Artık iş sorguyu çekmek ve gelen veriyi analiz etmede. Cevap için usart kesmesinden gelen veri takip edilecek. verinin bittiği timer ile anlaşılacak.
*/

/* Burada öncelikle sorgu yapacağımız paketi oluşturalım */
Modbus_Trans_Array[0] = 1; // sorgu çekilecek slave adresi, biz 1.slave sorgu yapacağız Modbus_Trans_Array[1] = 03; // fonksiyon kodu, 03 Read Holding Register Modbus_Trans_Array[2] = adres_baslangic>>8; // hangi adresten bilgi isteyeceğiz Modbus_Trans_Array[3] = adres_baslangic; // hangi adresten bilgi isteyeceğiz Modbus_Trans_Array[4] = 2>>8; // yukarıdaki adresten kaç adet bilgi isteyeceğiz, 2 adet dedik Modbus_Trans_Array[5] = 2; // yukarıdaki adresten kaç adet bilgi isteyeceğiz, 2 adet dedik Get_CRC(Modbus_Trans_Array,8); // Gidecek paketin son 2 byte'ına CRC kodunu ekledik 

     while(1)
     {
/* butona her basıldığında sorgu çekilsin.
          öncelikle gidecek paketi hazırlayalım
       */

// RTU istegi Yapilacak
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0))
{
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)); // tuşa basılıp çekildikten sonra

for(Tx_count=0;Tx_count<8;Tx_count++)
{
   while(!(USART_GetFlagStatus(USART1, USART_FLAG_TXE)));
   USART_SendData(USART1, Modbus_Trans_Array[Tx_count]);
// Clear & Start Timer
   TIM6->CNT = 0; // TIM6 Clear
   TIM_Cmd(TIM6, ENABLE); //  Enable TIM6
// timeri çalıştırdık ve artık timeout sonunda veriyi inceleyeceğiz
// veriyi gönderdik ve cevabı bekliyoruz
// cevap usart kesmesine gelecek
  }

}


     }

}


/********************************************************************************/
/* USART1 KESME FONKSİYONU */

void USART1_IRQHandler(void)
{
     if( USART_GetITStatus(USART1, USART_IT_RXNE) )
     {
          USART_ClearITPendingBit(USART1, USART_IT_RXNE);//kesme bayrağı temizlenir

/* Burada slave örneğinde olduğu gibi bir timeri veya farklı bir gecikme fonksiyonu uygulayarak gelen paket takibi yapılabilirdi. ancak buraya yazılması uzun olacağı için gerek duyulmadı. Bu şekilde olan haberleşme de gayet sağlıklı bir şekilde çalışmaktadır. Program timer6 kesmesine girdiğinde eğer veri gelmiş ise gelen veri değerlendirilir, eğer veri yoksa timeout'a düşmüştür yani ya slave cihazından veri gelmemiştir ya da slave cihazı bizim belirlediğimiz timeout süresi içerisinde bize cevap göndermemiştir*/

          // Gelen veriyi oku ve diziye atamasını yap
          Modbus_Receive_Array[modbus_receive_counter ] = USART_ReceiveData(USART1);

          // Gelen paketteki veri sayacini arttir
          modbus_receive_counter ++;

  
     }
     else if( USART_GetITStatus(USART1, USART_IT_TXE) )
     {
          USART_ClearITPendingBit(USART1, USART_IT_TXE);
     }


}



/********************************************************************************/
/* TIMER6 KESME FONKSİYONU */
void TIM6_DAC_IRQHandler(void)
{

/* Yukarıda da anlatıldığı üzere, eğer program bu kesme fonksiyonuna girmiş ise sorgu gönderilmiş ve Slave cihazdan cevap gelmiş demektir ( eğer haberleşmede bir yanlışlık yok ise). Gelen veri paketini usart kesmesinde almıştık. şimdi paketi inceleyelim
*//

TIM_ClearITPendingBit(TIM6,TIM_IT_Update);  // Clear Update interrupt flag

// Counter Disable
TIM6->CR1 &=~ 0x0001;
TIM_Cmd(TIM6, DISABLE); //  Disable TIM6


        //eğer veri gelmiş ise, sorgu yaptığımız slave adresi 1
if (Modbus_Receive_Array[0]  == 1)
        {
                //CRC dogru ise, yani paketimiz güvenli bir şekilde bize ulaştı mı
if(CRC_check((char*)Modbus_Receive_Array,modbus_receive_counter )==1)
{
       // Modbus haberleşmesinde 3 numaralı fonksiyon Read Holding Register fonksiyonu
       if ( (Modbus_Receive_Array[1] == 3)  ) // fonksiyon kodu 3 ise
        {

             data_count=0;
             for(i=0;i<Modbus_Receive_Array[2]/2;i++)
             {                  Readed_Array[i] = panelcontrol.RxBuf[data_count+3]*256 + panelcontrol.RxBuf[data_count+4];                  data_count = data_count+2;               }

                    /* Readed_Array dizimize gelen verileri yükledik.
                   burada artık gelen veri ile ne yapmak istiyorsak yapabiliriz
                    Led kontrolu
                     slave'den alınan sensör bilgileri
                    istenildi ise parametre değerleri
                    .... bu liste uzatılabilir artık ne yapılmak isteniliyorsa..
                    */
              }

}

}

modbus_receive_counter = 0;




}



char CRC_check(char package[],unsigned int package_length)
{
volatile unsigned int crc[2];
volatile unsigned int CRCFull = 0xFFFF;
volatile unsigned int CRCHigh = 0xFF, CRCLow = 0xFF;
volatile unsigned int CRCLSB;
volatile unsigned int i=0;
volatile unsigned int j=0;
char CRC_OK=0;

    for (i = 0; i < package_length-2; i++)
    {
        CRCFull = (unsigned int)(CRCFull ^ package[i]);

        for (j = 0; j < 8; j++)
        {
            CRCLSB =  (unsigned int)( CRCFull & 0x0001);
            CRCFull = (unsigned int)((CRCFull >> 1) & 0x7FFF);

            if (CRCLSB == 1)
                CRCFull = (unsigned int)(CRCFull ^ 0xA001);
        }
    }
    crc[1] = CRCHigh = (unsigned int)((CRCFull >> 8) & 0xFF);
    crc[0] = CRCLow  = (unsigned int)( CRCFull & 0xFF);

    if((crc[0] == package[package_length-2]) && (crc[1] == package[package_length-1]))
    CRC_OK = 1;
    else
    CRC_OK = 0;

    return CRC_OK;
}


void Get_CRC(unsigned char package[],unsigned int package_length)
{
    volatile unsigned int crc[2];
    volatile unsigned int CRCFull = 0xFFFF;
    volatile unsigned int CRCHigh = 0xFF, CRCLow = 0xFF;
    volatile unsigned int CRCLSB;
    volatile unsigned int i=0;
    volatile unsigned int j=0;
    volatile char CRC_OK=0;

    for (i = 0; i < package_length-2; i++)
    {
        CRCFull = (unsigned int)(CRCFull ^ package[i]);

        for (j = 0; j < 8; j++)
        {
            CRCLSB = (unsigned int)(CRCFull & 0x0001);
            CRCFull = (unsigned int)((CRCFull >> 1) & 0x7FFF);

            if (CRCLSB == 1)
                CRCFull = (unsigned int)(CRCFull ^ 0xA001);
        }
    }
    crc[1] = CRCHigh = (unsigned int)((CRCFull >> 8) & 0xFF);
    crc[0] = CRCLow  = (unsigned int)( CRCFull & 0xFF);
    package[package_length-2] = crc[0];
    package[package_length-1] = crc[1];
}


/* 
Anlamadığınız yerleri sorabilirsiniz.
İyi Çalışmalar

*/