STM32-UART HABERLEŞMEDE RING BUFFER KULLANIMI

STM32 ile UART haberleşmesi için yoklama (polling) ya da kesme (interrupt) tekniklerini kullanabiliyoruz. Gelen ve giden verileri “halka tampon” (ring buffer) adını verdiğimiz, sonuna ulaşılınca başa dönüp oradan kayda devam ettiğimiz türden tampon dizilere yerleştirmek verimli bir yöntem. Burada UART işlemleri için HAL kütüphanesini kullanmıyorum.

Bu yayında sürekli olarak kullanmakta olduğum bu tekniği anlatacağım.

RING BUFFER YÖNTEMİ

Halka tampon dediğimiz, -Ring Buffer- başı ve sonu birbirine bağlı, kayıtların sürekli dönerek yapıldığı bir diziden ibaret. Önden giden bir yazıcı yeni gelen veriyi kaydederken arkadan gelen bir okuyucu da kaydedilenleri okuyup işliyor. Son kaydedilen veri ile son okunan verinin yerlerini baş -head- ve kuyruk -tail- isimlerini verdiğimiz işaretçilerde tutuyoruz.

24 Byte lık bir klavye halka tamponu. Şekil Wikipedia “circular buffer” başlığından.

Buna göre “kuyruk” gelen veriyi okurken “başa” yetişirse okunacak yeni veri kalmamış anlamına geliyor. Tersine, baş ilerlerken kuyruğa arkadan çarpmış ise tampon dolmuş demektir, bu durumda veriler okunarak tamponda yer açılana kadar veri alışını durdurmak gerekir, ya da gelen veriler kaybolur.

Bu durumda tampon boyutunun gelecek veri paketlerini alabilecek şekilde seçilmesi, okuma sıklık ve hızının da gelen veri yoğunluğuna uygun olarak ayarlanması gereklidir.

Kullanmakta olduğum ve burada anlatacağım yöntemi zamanında “ControllersTech” adlı blog sitesinden ilham alarak geliştirdim. Oradan aldığım temel kodlarda değişiklik ve ilaveler yaparak şu özellikleri kazandırdım:

  • İstenen Uart girişine kütüphane kodlarında değişiklik yapmadan erişebilmek için fonksiyonlara pointer argümanlar,
  • İki Uart girişini aynı anda kullanabilmek için ayrı ayrı tampon ve indeksler,
  • Paket başını ve sonunu algılama algoritmaları,
  • Mod256/8 Check sum doğrulaması,
  • ….

PROGRAMIN KURULUMU

ÖZETLE KURULUM ADIMLARI

Bu kadar ayrıntıya girmeden önce bir kontrol listesi halinde yapılması gereken işlemleri sıralıyayım:

  • Projenin Cube IDE ile oluşturulması, başlangıç ayarları SWD, CLK
  • TFT için SPI1 in etkinleştirilmesi
  • USART1 ve USART2 nin etkinleştirilmesi, IT etkin olarak
  • USART2 NVIC önceliğinin (priority) “1” olarak ayarlanması
  • TFT için TFT_CS, TFT_RST ve TFT_RS pinlerinin etkinleştirilmesi
  • UART_RingBuffer.c/h dosyalarının projeye eklenmesi, path ayarları
  • TFT_Basic dosyalarının projeye eklenmesi, path ayarları
  • stm32fXxx_it.c nin USART1_IRQ_Handler ve USART2_IRQ_Handler fonksiyonlarından uart_isr ye yönlendirmelerin yapılması
  • stm32fXxx_it.c ye extern uart_isr bildiriminin eklenmesi
  • stm32fXxx_it.c içinde SysTick Timer sayaç eklemelerinin yapılması
  • main.c User Code bölümlerindeki ilaveler
  • project/properties.. ayarlarındaki TFT boyut ve arayüz tanımlamalarının yapılması

DETAY ADIMLAR

STM32 Cube IDE platformunu kullanacağız. Hızlıca yeni proje oluşturma adımlarını geçelim.

SYS ayarlarında sadece Serial Wire seçeneğini etkinleştiriyoruz.

Konumuz UART olduğuna göre USART1 ve USART2 yi etkinleştireceğiz. İletişim hızı olarak 9600 baud seçiyorum. Kesme-Interrupt kullanacağımızdan NVIC ayarlarından Global Interrupt’ları da etkinleştiriyoruz.

Test esnasında kullanacağım TFT için SPI1 i etkinleştireceğim:

TFT kullanımı için gereken TFT_RST, TFT_CS ve TFT_RS pinleri için gereken ayarları yapıyorum.

UART RING BUFFER KODLARI

UART iletişim için kullanacağım fonksiyonlar UartRingBuffer.c/h dosyalarında. Bu dosyaları diskte bulup klasörleri ile birlikte Drivers klasörüne ithal-import ediyorum.

TEST FONKSİYONU

Biraz ters sırada olacak ama önce test fonksiyonunu verip, burada kullanılan alt fonksiyonları sonra vereceğim.

Test fonksiyonu, UART kesme fonksiyonu tarafından algılanarak halka buffer’lara yerleştirilen karakterleri, her bir USART Port için ayrı tampon dizilere yerleştirip görüntülüyor.

Bunlardan USART2 den gelenleri sadece görüntülüyor,

USART1 için ise biraz daha kapsamlı bir işlem yapıyor. Gelen karakterleri buffer[] adlı bir başka tampon diziye kaydediyor. USART1 den satırbaşı ‘\n’ karakteri geldiğinde, bunu izleyen iki karakteri checkSum[] adlı mini diziye de kaydediyor. Bundan sonra da gelen satırı check-sum karakterleri ile birlikte ekranda görüntülüyor.

Belirli aralıklarla USART1 üzerinden bir ‘*’ karakteri gönderiyor. Bu gönderme işleminde herhangi bir tampon ya da kesme kullanmıyorum. Sadece programın çalışmakta ve dinlemede olduğunu karşıya bildirmeye yarıyor.

Burada kullanılan IsDataAvailable() ve Uart_read() fonksiyonlarına gelelim.

RING BUFFER FONKSİYONLARI

IsDataAvailable() fonksiyonu, alış tampon dizisinde okunacak karakter olup olmadığını kontrol ediyor.

Uart_read() fonksiyonu, okunabilecek yeni veri var ise bir adet karakter okuyup kuyruk göstergesini bir ileriye alıyor. Eğer kuyruk başa denk gelmiş ise okunacak karakter yok demektir, geriye “-1” mesajını gönderiyor. Kuyruk tampon boyutuna ulaştı ise bu indeksi dizinin başına alıyor.

Uart_write() fonksiyonunu bu test için kullanmıyorum. Ama kod zaten basit, açıklamaya gerek duymuyorum.

USART KESME FONKSİYONU – USAR_isr()

Bu fonksiyon, her yeni gelen karakter ile kontrolu ele alıp yakalanan karakteri USART1 ya da USART2 ye ait halka tamponlara yerleştiriyor.

UART_isr(), gelen her bir karakteri tampona kaydetmek üzere aşağıdaki store_char() fonksiyonuna gönderiyor.

Eğer gelen karakteri kaydedeceği yer kuyruk -tail- pozisyonuna ulaşmadı ise kayıt işlemini yapıyor ve baş-head indeksini ilerletiyor. Aksi halde kaydetmiyor. Bu durumda, gelen yeni karakterler alınmaya devam ediliyor ama kaydedilemediklerinden kayboluyorlar.

UART_RingBuffer KULLANICI ARAYÜZÜ

Yukarıda bu paketin kullanımına yönelik bir test fonksiyonu verdim. Ama gerçek bir uygulamada kullanacak isek bunun için bir arayüz fonksiyonunuz var. Bu benim CNC uygulamam için geliştirdiğim kod.

Bu fonksiyon, aslında paketteki en önemli bloklardan birisi, esas oğlan bu yani. Test programına benziyor ama Mod256 -8 check sum kontrolu yaparak gelen paketin doğrulamasını da yapıyor.

Her bir paketin tilde ‘~’ ile başlayıp satırbaşı ‘\n’ ile sonlanması, bunun da ardından iki karakterlik checksum bilgisinin gelmesi gerekiyor. Fonksiyonun içinde yeteri kadar açıklama var sanırım, herseyi burada tekrarlamaya gerek görmüyorum.

Buffer taşması halinde 2, checksum doğrulaması başarılı ise 1, başarısız ise 0 olarak dönüyor. Başarılı alış durumunda, gelen karakterleri argüman listesindeki buffer[] dizisinde geri gönderiyor.

Bir eksiği var : Zaman aşımı kontrolunu henüz koymadım. Timer2 sayacı bu işe yarayacak. Aksi halde sonunda ‘\n’ gelmeyen bir paket olursa program burada kilitlenebilir.

UART_RingBuffer.C İÇİNDEKİ DİĞER FONKSİYON VE BİLDİRİMLER

UART kesmelerini etkinleştirmek için bir Ringbuf_init() fonksiyonumuz var, bunu main.c içinden her iki USART port için birer defa çağırıyoruz.

UartRingBuffer.c içindeki alış ve gönderme tampon dizilerinin tanımları aşağıdaki gibi.

Bunlar da UartRingBuffer.c nin başındaki ekleme ve tanımlar.

UartRingBuffer.h dosyasındaki tanım ve bildirimler. Tampon boyutunu 64 olarak seçtim, bu gelebilecek en uzun veri paketine göre ayarlanabilir.

GELEN KESMELERİN Uart_isr’ye YÖNLENDİRİLMESİ

Eğer aşağıda anlatacağım işlemleri yapmazsak USART kesmeleri varsayılan olarak HAL_UART_IRQHandler() fonksiyonuna yönlendirilir. Bu yönlendirme işlemi STM32F1xx_it.c dosyası içindeki, IRQ handler fonksiyonlarından yapılıyor.

Bu dosya içindeki iki USART IRQ handler fonksiyonunda HAL_UART_IRQ_Handler fonksiyon çağrılarını kapatıp yerlerine kendi Uart_isr() fonksiyon çağrılarımızı koymamız gerekiyor.

Uart_isr() fonksiyonunun projedeki bir başka dosyada (UART_RingBuffer.c) bulunduğunu bildirmek üzere :
extern void Uart_isr(UART_HandleTypeDef *huart);
şeklinde bir bildirimin de başlardaki User Code bölgelerinden birisine konulması gerekiyor. Ben User Code EV bölümüne koydum.

SAYAÇ – TIMER FONKSİYONLARI

UART Test programı içinde belirli aralıklarla (saniyede 1 ya da 2 defa) Usart Tx veriş hattından dışarıya bir karakter gönderiyoruz. Bu gönderimin zamanlaması için bir zamanlayıcı kullanıyoruz.

Timer3 adlı bu zamanlayıcıyı SysTick_Handler ile çalıştırıyoruz. Set ettiğimiz değerden başlayarak 10ms de bir sayaç eksiliyor. SysTick_Handler de stm32fxxx_it.c içinde yer alıyor.

MAIN.C İÇİNDEKİ DÜZENLEMELER

Ana program bloku içinde de yapılması gereken birkaç düzenleme var.

Görüldüğü gibi main.c içinde bu uygulamaya yönelik fazla birşey yok. USART kesmelerini etkinleştirmek üzere Ringbuf_init() fonksiyonu iki defa çağrılıp, ondan sonra test programı çalıştırılıyor.

DERLEME

Yapılacak ekleme ve değişiklikler bu kadar. Derlemeye geçmeden önce project/Properties menülerinden UART_RingBuffer klasörüne yol-path tanımlamasının yapılmasını, define menülerinde de gereken eklemeleri yapmayı unutmayalım. Unutursak zaten kendini hatırlatacaktır.

TEST SONUCU

UART1’e PC den bir terminal uygulaması ile, UART2 ye de tuş takımından kod göndererek aşağıdaki gibi bir çıktı elde ediyorum.

SONUÇ

Bu paketi UART iletişimine ihtiyaç duyduğum her durumda kullanıyorum. Bunun için main.c içine konabilecek HAL_CallBack fonksiyonlarını neden kullanmadığıma gelince, iki sebep var.

Birincisi HAL Uart IRQ handler içinde arada bir başını kaldıran bir “over run error!” sorunu var, bundan kaçınmak için UART IT söz konusu olunca HAL kütüphanesinden uzak durmak istedim.

İkincisi, main.c içinde mümkün olduğu kadar az kod bulundurup kendi başına yeterli bir kütüphaneye sahip olma isteğim. Bu şekilde her yeni projede sadece UART_RingBuffer.c/h dosyalarını projeye katarak, main.c de bir şey yazmadan işimi görebiliyorum.

Bu yayının sonu – Selçuk Özbayraktar Ekim 2020

Leave a Reply

Your email address will not be published. Required fields are marked *