STM32F103 CubeIDE Örnek Projesi (Kütüphanesiz)

Başlatan Tagli, 24 Haziran 2020, 19:23:38

Tagli

STM32CubeIDE'de STM32F103C8T6'lı Blue Pill kartı için oluşturduğum örnek LED yak söndür projesini paylaşayım dedim. "Kütüphanesiz" ifadesi tam doğru olmadı aslında, proje CMSIS ve ST header'larını kullanıyor. Ama Cube, HAL, StdPeripheral falan kullanmıyor, kod register seviyesinde yazılmış durumda. STM32'lere yeni başlayacaklar için referans bir örnek proje olur diye düşündüm.

Kod reposuna buradan ulaşabilirsiniz.
Gökçe Tağlıoğlu

Tagli

Aynı projenin C++ için oluşturulmuş versiyonuna buradan erişebilirsiniz.

CMSIS ve ST'nin headerları arasında F103 için gerekli olmayan alakasız dosyalar da var. Kütüphane klasörlerini olduğu gibi kopyaladığım için onları temizlemekle uğraşmadım. Hangilerinin gerekli hangilerinin gereksiz olduğunu tespit etmek bazen zaman alabiliyor.

Sadece basit bir blinky projesi olduğu için kodda C++'a özgü pek bir şey bulamayacaksınız. Ama mevcut haliyle proje bir C++ projesi, yani mesela class falan tanımlayıp kullanabilirsiniz gönlünüzce.

C++ söz konusu olduğunda dikkat edilmesi gereken birkaç maddeyi burada sıralayayım:
  • Kesme fonksiyonlarının başına extern "C" getirilmeli, yoksa bulunamazlar. Örnekte mevcut.
  • Gömülü C++'ta genelde exception kullanılmaz. Varsayılan optimizasyon ayarlarında da zaten kapalı.
  • Heap kullanımı tartışma konusudur. Genel olarak hiç kullanılmaması, veya kullanılacaksa bunun program başlangıç aşaması ile sınırlı olması gerektiği söylenmektedir. İkincisi benim kafama daha çok yatıyor. Heap kullanılmaması demek, standart kütüphane içindeki pek çok nesneyi kullanamayacağınız anlamına geliyor. Mesela std::vector kullanamazsınız.
  • Projenizde C kodları da olabilir. Proje ayarlarında C ve C++ derleyicilerine özgü ayarlar birbirinden ayrıdır.
  • Linker script içinde varsayılan bir heap alanı ayrılmış. Proje derlendiğinde fazladan 1 KiB RAM kullanılıyor gibi gözükmesinin sebebi bu. Dilerseniz sıfırlayabilirsiniz bunu.
Gökçe Tağlıoğlu

sinus

Bluepill Microsoft'un bir uygulaması mı?

https://visualbluepill.github.io/

Dün biraz uğraşmıştım, köşede Microsoft yazıyor.
Başka kartlar içinde benzeri kodlama yapılabiliyor galiba.

muhittin_kaplan

mikrozort logosu github dan dolayı. Satın almıştı.

Tagli

#4
Benim Blue Pill dediğim şey, Çinlilerin STM32F103C8 ile yaptıkları küçük mavi kart. Üzerinde pek bir şey yok. USB uygulalamaları için bir micro-B konnektör, 3.3 V regülatör, boot seçimi için jumperlar, bir tane LED, iki tane kristal ve pin headerlar. Hepsi bu (galiba). Programlayıcıyı dışarıdan bağlamak gerekiyor.

Çok sade ve kolay kullanılan bir ürün olduğu ve her yerden ucuz bir şekilde temin edilebildiği için örnek programı buna göre yazayım dedim. Aslında "buna göre" ifadesi de pek isabetli değil. Kart zaten boş olduğu için, karta göre yazılmış olan tek kısım LED'in hangi pine bağlı olduğu.

Gökçe Tağlıoğlu

DieHardMetalHead

Hocam projeniz sayesinde aslında gerçekten basit olan ancak uzun süredir aklımı karıştıran iki şeyi çözdüm çok teşekkür ederim.

Birincisi "system_stm32f10x.c" dosyası içinde 115. satırda görüldüğü üzere işlemci her seferinde 72MHz'de çalışmaya başlıyormuş ve bu hız istenildiğinde RCC registerlarıyla teker teker uğraşmadan "SystemCoreClock = 8000000U;" diyerek direk değiştirilebiliyormuş.

İkincisi ise delay elde etmek için iki saat timer birimini gerekli şekilde kurup daha sonra timer arr registerına kafa karıştırıcı bir şekilde 72 yüklemek yerine 144 yüklemek gerekmiyormuş. Systick ile yapılabiliyormuş. (TIM ARR registerına 144 yüklenmesinin nedeni işlemcinin external buffer'ı olmasından dolayı bir ISR varken interrupt registerının silinmesi. ) (yarım yamalak mcu bilgimle tam anlatamamış olabilirim ilgilenenler için link : https://www.keil.com/support/docs/3928.htm)

Bide hocam eğer peripheralları efektif bir şekilde kullanabileceğimz bu şekilde minimalist fazla dosya içermeyen projelerin varsa paylaşabilirsen sevinirim.
Murat Arslan - BŞEÜ - EEM

mr.engineer

#6
extern "C" void SysTick_Handler()

Böyle bir şeyi ben de ilk defa gördüm. Hocam C++ için demişsiniz ama C de bunun bir anlamı var mı? Fonksiyon başına extern koyulduğu bir yer hatırlamıyorum.

Delay için boş bir döngüyü toplam instruction cycle'a bakarak yapıyordum. Bu daha basitmiş.

Tagli

C++'tan çağrılacak şekilde düşünülmüş C kütüphanelerinin header dosyalarında görülür genelde bu durum. Ama çoğunlukla benim örnekteki gibi değil aşağıdaki gibi yaparlar:

#ifdef __cplusplus
extern "C" {
#endif

// Global değişkenler ve kütüphane fonksiyonu prototipleri

#ifdef __cplusplus
}
#endif

Yani bir blok olarak tüm header dosyasını kapsar.

C++ derleme sırasında değişken ve fonksiyon isimlerini kafasına göre değiştirir. Buna name mangling diyorlar. Bu şekilde değişken ve fonksiyonların içinde bulundukları class ve namespace'ler göz önünde bulunduruluyor. Ayrıca overloaded fonksiyonlar da işeniyor. Yani yazarken isimleri aynı olsa bile gerçekte kendileri aynı olmayan fonksiyon ve değişkenlere benzersiz bir isim verilerek, bunların link zamanında çakışması engelleniyor.

C'de böyle bir özellik yok. Programcı isim olarak ne vermişse derleyici ona dokunmuyor, linker da bu şekilde arıyor. foo() diye bir fonksiyon .c uzantılı bir dosyada ise bu şekilde derlenir. Ama eğer bunu bir C++ kodundan çağıracaksan, C++ tarafının bu isme dokunulmaması gerektiğini bilmesi ve bunu orijinal foo adıyla çağırması gerekir, yoksa link aşamasında aradığını bulamayıp hata verir.

STM32 kesme fonksiyonunda durum biraz daha farklı. Biz vektör tablosunun tanımlandığı startup.s (veya adı artık her ne ise) dosyasında assembly dili ile yazılmış vektör isimlerinin aynısını fonksiyonumuza vermeliyiz ki bizim fonksiyonun adresi vektör tablosuna yerleşsin. Ancak C++ derleyicisinin SysTick_Handler() ismini neye çevireceğini bilmiyoruz, kafasına göre saçma sapan bir isim verebilir. Bunu engellemek için bu fonksiyonu C Linkage ile derlenmesi gerektiğini (daha doğrusu, isimlendirilmesi gerektiğini) söylüyoruz. Böylece fonksiyonun ismi bizim yazdığımız şekli ile kalıyor.

Bu durum kesme fonksiyonlarına özel olduğu için genelde yapıldığı gibi tüm dosyayı extern "C" ile işaretlemeye gerek yok. Zaten bu durumda sorun çıkabilirdi, sonuçta biz C++ dilinde yazıyoruz ve name mangling bizim için gerekli bir özellik. Bu sebeple sadece kesme fonksiyonunu bundan hariç tutuyoruz.

Bu arada, eğer fonksiyon pointer'ları kullanıyor olsaydık buna gerek kalmazdı. Bazı projelerimde yazdığım kütüphaneleri genelleştirebilmek için çalışma anında kesme fonksiyonu atamasına ihtiyaç duyuyorum. Bunun için vektör tablosunu RAM'e kopyalayıp buradaki vektörleri çalışma anında fonksiyon pointer'larını kullanarak değiştiriyorum. Pointer'lar ile ulaşılan fonksiyonların extern "C" ile işaretlenmelerine gerek yok çünkü isimle değil ham bellek adresi ile ulaşılıyorlar.
Gökçe Tağlıoğlu

Tagli

Alıntı yapılan: DieHardMetalHead - 15 Şubat 2021, 04:11:13Birincisi "system_stm32f10x.c" dosyası içinde 115. satırda görüldüğü üzere işlemci her seferinde 72MHz'de çalışmaya başlıyormuş ve bu hız istenildiğinde RCC registerlarıyla teker teker uğraşmadan "SystemCoreClock = 8000000U;" diyerek direk değiştirilebiliyormuş.

Bu doğru değil. İşlemci HSI ile çalışmaya başlar. Bildiğim kadarıyla tüm STM32'ler böyle. HSI'nin değeri işlemci modeline göre değişebilir. Ben farklı modellerde 8 ve 16 MHz HSI değerlerini gördüm. STM32F103 için bu değer 8 MHz.

SystemCoreClock sadece bir değişken, işlemci donanımı ve hızı üzerinde hiçbir etkisi yok. Bu değişken CMSIS standartında tanımlı olarak kabul ediliyor ve bazı kütüphaneler ve RTOS'lar bu varsayıma dayanarak bu değeri işlemci hızını anlamak için okuyabiliyorlar. Benzer şekilde SystemCoreClockUpdate() fonksiyonu da CMSIS standartında var ve genelde system_xxxxx.c dosyasında tanımlı oluyor. Yaptığı şey, işlemcinin ilgili registerlarını tarayarak işlemci hızını tespit edip SystemCoreClock değerini güncellemek. Genelde program başında veya clock ayarları değiştirildiğinde çağrılır.

Ben zaten işlemcinin hızının o sırada 8 MHz olduğunu bildiğim için SystemCoreClockUpdate() fonksiyonunu çağırmadan elle doğrudan atama yaptım. Aslında o değişkeni hiç kullanmasaydım da olurdu ancak genel usul bu değişkenin program başında doğru değere sahip olması olduğu için öyle yaptım.
Gökçe Tağlıoğlu

DieHardMetalHead

Alıntı yapılan: Tagli - 15 Şubat 2021, 10:05:42Bu doğru değil. İşlemci HSI ile çalışmaya başlar. Bildiğim kadarıyla tüm STM32'ler böyle. HSI'nin değeri işlemci modeline göre değişebilir. Ben farklı modellerde 8 ve 16 MHz HSI değerlerini gördüm. STM32F103 için bu değer 8 MHz.

SystemCoreClock sadece bir değişken, işlemci donanımı ve hızı üzerinde hiçbir etkisi yok. Bu değişken CMSIS standartında tanımlı olarak kabul ediliyor ve bazı kütüphaneler ve RTOS'lar bu varsayıma dayanarak bu değeri işlemci hızını anlamak için okuyabiliyorlar. Benzer şekilde SystemCoreClockUpdate() fonksiyonu da CMSIS standartında var ve genelde system_xxxxx.c dosyasında tanımlı oluyor. Yaptığı şey, işlemcinin ilgili registerlarını tarayarak işlemci hızını tespit edip SystemCoreClock değerini güncellemek. Genelde program başında veya clock ayarları değiştirildiğinde çağrılır.

Ben zaten işlemcinin hızının o sırada 8 MHz olduğunu bildiğim için SystemCoreClockUpdate() fonksiyonunu çağırmadan elle doğrudan atama yaptım. Aslında o değişkeni hiç kullanmasaydım da olurdu ancak genel usul bu değişkenin program başında doğru değere sahip olması olduğu için öyle yaptım.


Anladım. Peki hocam bu cihazın hızını nasıl değiştiriyoruz? STM32CubeIDE ve CubeMX ile rahatça yapılıyor farkındayım, ancak ben geliştirme ortamı olarak Keil'i tercih ediyorum. Objektif bir nedeni ise yok aslında. Sadece yaptığım uygulamaları bir template haline getirip arşivliyorum ben. Neyin nasıl çalıştığını neden yapıldığını dökümanlayarak. Bu nedenden reference manual'de RCC kısmına göz attım ve kendim halletmeye çalıştım olmadı. Gezmediğim forum kalmadı. Çalışıyor denilen kodları çalıştırdım ama başarılı olamadım. Bu konuda beni yönlendirebileceğiniz bir kaynak var mıdır?
Murat Arslan - BŞEÜ - EEM

Tagli

Aslında reference manual tek başına bu iş için yeterli. Ancak clock ağacındaki değerleri CubeMX üzerinde belirleyip sonra RCC register'larını o değerlere göre atamak daha kolay oluyor. RCC register değerlerini değiştirirken rastgele davranmayıp mantıksal bir sıra ile değiştirmek gerekiyor.
Gökçe Tağlıoğlu