IAR ile STM8S – SPI

Özellikle sensör ve çeşitli modülleri kullanmaya çalıştığınızda görmekten kusacağınız 2 haberleşmeden biri olan SPI, ufak tefek detaylarıyla başımızı çok ağrıttığını söyleyebilirim. Merak etmeyin ağrı kesiciniz bu yazıda. Yüksek hızları, çoklu cihaz haberleşebilme özelliği ile çok yaygın hale gelip her denetleyicinin içinde yer alan bu protokol, ufak ama önemli detaylar ile dolu. Fazla dikkate alınmayan 4 farklı modu yüzünden çalışmayan haberleşmenin kullanıcılara çok fazla zaman kaybettirdiğini söyleyebilirim. Genellikle PCB üstü kurulan bu haberleşme ile sensör ve çeşitle modülleri rahatlıkla kullanabiliriz. Birazdan SPI’ın aslında bir Shift Register olduğunu göreceksiniz. Hazırsanız başlayalım.

Serial Peripheral Interface (SPI) olarak bilinen bu haberleşme Master – Slave ilişkisine dayanır.

Bu iletişimde standart olarak 4 bağlantı yapılır.

  • SCLK = Serial Clock
  • MOSI = Master Out Slave In
  • MISO = Master In Slave Out
  • CS(SS) = Chip Select(Slave Select)

Eğer biz tek bir Slave ile haberleşecek olsaydık. Şu bağlantıyı kullanırdık.


Çoklu iletişim için şu bağlantıyı kullanırız.


Bu bağlantıda kontrolü sağlayan pin CS(SS) pinidir. Biz hangi CS(SS) pinini LOW yaparsak o slave ile iletişime geçeriz. Buna “Aktif sıfır” da denir. Bu pin ters çalıştığı için CS(SS) pini üzerinden bir çizgi işareti vardır. Haberleşmede tek bir Slave var ise CS pini direk LOW yapılabilir. Bu sayede 1 pinden tasarruf edebilirsiniz ama bunun güvenli olduğunu söylemek pek mümkün değildir. Peki nasıl oluyor da bu veri seri hat’a çıkıyor gelin buna bakalım.

Size aslında SPI’ın bir Shift Register olduğunu söylemiştim. Aşağıdaki kod MSB tarafından başlıyor. Bunun anlamı bitleri soldan sağa doğru yolluyor. Buna “MSB First” adı verilir. Eğer sağdan başlasaydı “LSB First” adı verilirdi.

void SPI_Write(uint8_t data)
{
    for (uint8_t i = 0x80; i > 0; i >>= 1)
    {
        if (data & i)
            MOSI = 1;
        else 
            MOSI = 0;
        
        SCLK = 1;
        Delay();
        SCLK = 0;
        Delay();
    }
}

Bu yapı size bir yerden tanıdık geliyor olabilir. 75HC595 Shift Register’ı benzer yöntem ile sürülür.

Aslı önemli detay çalışma modlarıdır. Bu konuda çok önemli bir ayrıntı vardır. Microchip PIC denetleyicilerin de kullanılan mod ayarı ile STM8, STM32 denetleyicilerinde kullanılan mod standartları farklıdır. Bu yüzden bu hep karıştırılır. Bu dört mod CPOL ve CPHA adı verilen 2 ayar ile belirlenir.

Görüldüğü gibi sadece CKE, CPHA’nın tersidir. Bu detayı unutmadığınız sürece bu kısımda sorun yaşamayacaksınız.

CPOL yani Clock Polarity SCLK pinin karakteristiğini belirler. Kısaca pin boşta iken LOW yada HIGH konumundan hangisinde kalacağını belirler. Bu duruma göre yükselen yada düşen kenar olduğunu görebiliriz.

CPHA yani Clock Phase MISO pinin veriyi hangi kenarda kenarda alacağını belirler.

Bunlar haricinden bazı denetleyicilerde CRC özelliği de vardır. SPI aynı zamanda CRC hesabı da yapar fakat bizim uygulamalarımızda buna pek gerek duymuyoruz.

SPI kullanırken dikkat etmeniz gereken bir diğer unsur ise fiziksel hatların durumudur. SPI yüksek hızlı olduğundan uzun hatlar hemen clock sinyalini bozulmasını sağlar. Bu yüzden hatlarda mutlaka Pull-Up yada Pull-Down direnci kullanmalısınız. Hangi tipte direnç takacağınıza CPOL karar verecek. CPOL = 0 ise hattınız boştayken LOW konumunda demektir. Bu yüzden Pull-Down yapmalısınız. Eğer CPOL = 1 ise Pull-Up yapmanız gerekecek.

Kodlar geçerken ufak bir hatırlatma yapalım. Normalde CLK Register’ı ile Modüllerini Clock sinyallerini aktif etmemiz gerekiyor fakat bu ayarlar reset durumu olarak zaten 1 konumunda bu yüzden ekstra bir şey yapmanıza gerek kalmıyor. Emin olmak ve göz önünde bulunması için şu satırı keyfi olarak ekleyebilirsiniz.

CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, ENABLE);

SPI Config kısmını ise oldukça basit. Her zaman olduğu gibi tek satırda aktif ediliyor.

SPI_Init(SPI_FIRSTBIT_MSB, SPI_BAUDRATEPRESCALER_16, SPI_MODE_MASTER, SPI_CLOCKPOLARITY_LOW, SPI_CLOCKPHASE_1EDGE, SPI_DATADIRECTION_2LINES_FULLDUPLEX, SPI_NSS_SOFT, 0x07);

SPI_Cmd(ENABLE);

Yukarıda anlattığım ayarları bu satırda yapmalısınız. Burada Clock frekansının SPI_BAUDRATEPRESCALER değerine bölünerek bulunduğunu unutmayın. Ben 16 yaparak 1 MHz elde ettim. Bunun sebebei HSI aktif olması ve değerinin 16 MHz olmasıdır.

Son olarak gönderme ve TXE durumunu kontrol eden fonksiyonu yazmamız yeterli.

static void SPI_SendByte(uint8_t data)
{
   while (SPI_GetFlagStatus(SPI_FLAG_TXE)== RESET);

   SPI_SendData(data);
}

Bir test satırı ile doğrulama yapalım.

void main(void)
{
   SYSTEM_Config();
   
   for (;;)
   {
      SPI_SendByte(0xAA);
      for(uint32_t i = 0; i < 0xFFFF; i++);
   }
}


Şimdi yüksek hızda Pull-Down direncini sökeceğim ve Clock sinyaline bakın ne olacak.



Tekrardan Pull-Down ekleyelim.

Sence hangi Clock sinyali daha güzel ? Make your choice!

main.c

#include "main.h"

void main(void)
{
   SYSTEM_Config();
   
   for (;;)
   {
      SPI_SendByte(0xAA);
      for(uint32_t i = 0; i < 0xFFFF; i++);
   }
}

static void SYSTEM_Config(void)
{
   CLK_Config();
   GPIO_Config();
   SPI_Config();
}

static void CLK_Config(void)
{
   CLK_DeInit();
   CLK_HSECmd(DISABLE);
   CLK_HSICmd(ENABLE);
   CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSI, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
   CLK_SYSCLKConfig(CLK_PRESCALER_HSIDIV1);
   CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);
   
   CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, ENABLE);
}

static void GPIO_Config(void)
{
   GPIO_Init(GPIOB, GPIO_PIN_5, GPIO_MODE_OUT_PP_LOW_FAST);
}

static void SPI_Config(void)
{
   SPI_DeInit();

   SPI_Init(SPI_FIRSTBIT_MSB, SPI_BAUDRATEPRESCALER_16, SPI_MODE_MASTER, SPI_CLOCKPOLARITY_LOW, SPI_CLOCKPHASE_1EDGE, SPI_DATADIRECTION_2LINES_FULLDUPLEX, SPI_NSS_SOFT, 0x07);

   SPI_Cmd(ENABLE);
}

static void SPI_SendByte(uint8_t data)
{
   while (SPI_GetFlagStatus(SPI_FLAG_TXE)== RESET);

   SPI_SendData(data);
}

static void SPI_ReadByte(uint8_t* data)
{
   while (SPI_GetFlagStatus(SPI_FLAG_RXNE) == RESET);

   *data = SPI_ReceiveData();
}

#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{ 
   while (1);
}
#endif


main.h

#ifndef MAIN_H
#define MAIN_H

#include "stm8s.h"

#define LED_GPIO        (GPIOB)
#define LED_PIN         ((GPIO_Pin_TypeDef)GPIO_PIN_5)

static void SYSTEM_Config(void);
static void CLK_Config(void);
static void GPIO_Config(void);
static void SPI_Config(void);

static void SPI_SendByte(uint8_t data);
static void SPI_ReadByte(uint8_t* data);

#endif

 

You may also like...

3 Responses

  1. muharrem çetinkaya dedi ki:

    Üstadım paylaşımların için çok teşekkür ederim. Vermiş olduğun bilgiler ve örnek kodlar ile STM8S’e çok rahat adapte oluyorum.
    Yukarda ki anlatım da
    “Asıl önemli detay çalışma modlarıdır. Bu konuda çok önemli bir ayrıntı vardır. Microchip PIC denetleyicilerin de kullanılan mod ayarı ile STM8, STM32 denetleyicilerinde kullanılan mod standartları farklıdır. Bu yüzden bu hep karıştırılır. Bu dört mod CPOL ve CPHA adı verilen 2 ayar ile belirlenir.

    Görüldüğü gibi sadece CKE, CPHA’nın tersidir. Bu detayı unutmadığınız sürece bu kısımda sorun yaşamayacaksınız.” demişsin.
    Ben tabloda CKE ile CPHA arasında bir fark göremedim, acaba ben mi yanlış yorumluyorum.

    Sormak istediğim başka bir soru var. Compile ettikten sonra STM8 in Rom ve Ram kısmının ne kadarını kullanmışız ne kadar geriye kullanabileceğimiz alan kalmış bunu nasıl görebiliriz.
    Output klasöründe .map uzantılı dosyaya baktım ama algılayamadım, bu konuda bilgi verebilir misiniz?
    Şimdiden ilginize teşekkür ederim

  2. ozgur dedi ki:

    Sayın Hocam,
    (internet ne güzel bir nimet, 3 sene önce yazdığınız paylaştığınız ve akışa bıraktığınız bir bilgi kimlere nerelere dokunuyor.Ne yollar açıyor…)

    STM8 ile henüz yeni tanıştım. Arduino dan kurtulmak istemekteyim, ve haliyle başlanabilecek güzel bir nokta olduğunu düşünüyorum.

    Arduino una da birilerinin yaptığı bir sensör iletişimini stm8 de gerçeklemek için dertlendim. (Hem okumayı hem de yazmayı düzgün yapamadım)

    Sensör 32 bit bilgi iletebiliyor.
    her zaman olduğu gibi ilk bit yazma okuma,
    sonraki byte register
    sonra 16 bit kısımda data var
    sonra 4 bitte komut (sensörün nasıl davranacağını belirliyor sanırım sensöre özel)
    sonraki 4 bit CRC

    Arduino da 16 bit i 2 ye bölmüşler ilk 8 sonra da diğer 8 i okuyor ve uint16_t değişkene ekliyor.

    Ancak bunu yapamadım bir türlü. belki konu SPI_CLOCKPHASE_1EDGE veya SPI_BAUDRATEPRESCALER_16 ile ilgili de olabilir. (arduino da 9600 baurdrate de kurmuşlar iletişimi)

    Eğer imkanınız olur ise yanıt verebilirseniz çok sevinirim. Nasıl 16 bit yazıp okuyabilirim? küçük bir örnek verebilir misiniz?

    Bazı kaynaklarda;

    SPI.send(address| mask) şeklinde komut gördüm. Burada ki “|” operatörünün görevi nedir örneğin SPI.send(0x80|0x9) 0x89 mudur?

    kısacası baya baya dertlendim 🙂

    Saygılarımla.

Bir yanıt yazın