0

CppUTest’in STM32CubeIDE ile Kullanımı

    Uzun bir aradan sonra tekrardan merhaba. Bazı sebeplerden dolayı bir yıldır bir şeyler yazamamışım. Bu açığı şöyle birkaç güzel yazı ile kapatmak istiyorum. Bu aralar Test Güdümlü Geliştirme( Test Driven Development) konusuna çalışıyorum ve TDD‘ye bir alışkanlık kazanmak istiyorum. Hazır bu konuya çalışırken yaptığım örnekleri burada paylaşmak istiyorum. Merak etmeyin paylaşacağım örnekler bir led yakıp söndürme testi falan olmayacak :). Elimin altında şuan çok fazla elektronik bir ekipman, modül vb. yok idare edin. O yüzden elimde olan Nucleo kart ve ESP8266 ile TDD üzerine elimden geldiğince güzel örnekler paylaşmaya çalışacağım. Bu ilk yazıda TDD için kullanacağım Unit Test Framework’lerinden biri olan CppUTest’in STM32CubeIDE ile nasıl derlenip, kullanılacağından bahsetmek istiyorum çünkü bu aşama bile bir yazı için oldukça uzun bir hal aldı.
    İkinci yazıda ESP8266 ile TCP/IP üzerinden bilgisayar ile haberleşecek bir kütüphaneyi tamamen TDD prensibini uygulayarak geliştirmeye çalışacağım. Bu kütüphanede işin içine Ring Buffer konseptini de katacağım.
    Üçüncü yazıyı daha da ileriye taşıyarak ikincide geliştirdiğim ESP8266 kütüphanesini kullanan bir haberleşme protokolü kütüphanesi geliştireceğim( daha karar vermedim ama yüksek ihtimalle MQTT olacak). Ayrıca üçüncü yazıda kriptografinin gömülü yazılım tarafında kullanımını görmek için basit bir şifreleme algoritması kullanıp MQTT ile şifreli haberleşme uygulaması yapmayı planlıyorum. Daha önceki yazılarda yerelde bir MQTT broker kullanan örneği paylaşmıştım, burada bir cloud servis üzerinden gitmeyi planlıyorum ( HiveMQ,CloudMQTT vb.). Üçüncü yazıyı da TDD prensiplerine uyarak geliştireceğim ama yazıda daha çok MQTT ve kriptografi üzerinde duracağım. TDD tarafını incelemek için github tarafında zaten kodlara erişebileceksiniz.
    TDD tarafında kaynağım  James Grenning‘in yazdığı “Test-Driven Development for Embedded C” kitabı olacak. Framework olarak CppUTest seçmemin sebebi de budur. STM32CubeIDE Eclipse tabanlı bir IDE olduğu için bir çok Unit Test framework’ünü desteklemektedir. Hangisini kullanacağınız size kalmış.
    Lafı daha da uzatmadan bu yazının asıl konusuna gelelim. Aşağıda CppUTest’in STM32CubeIDE ile nasıl kullanılacağına ve benim nasıl bir dosya yapısı halinde kullandığımı adım adım açıkladım. Takıldığınız yerler olursa yorumlarda sorabilirsiniz.

CppUTest’in  STM32CubeIDE ile Statik Olarak Derlenmesi

Adım-1:
File->New->STM32 project  ile yeni bir proje oluşturulur. Açılan pencereden kullanacak olduğunuz MCU veya Board seçilir. Burada benim kullandığım kart Nucleo-G474RE’dir (Aslında statik derleme aşamasında mcu seçiminin bir önemi yoktur. İstediğiniz mcu’yu seçebilirsiniz.). Kullanmak istediğiniz mcu’yu seçtikten sonra aşağıdaki pencereden;
  • Targeted Language –> C++
  • Targeted Binary Type –> Static Library
  • Targeted Project Type –> Empty
seçilerek devam edilir. Burada proje ismini belirlemek size kalmış.

 

Adım-2:
  • CppUTest frameworkünü githubtan indirin.
  • cpputest\src\CppUTest ve cpputest\src\CppUTestExt dosyalarının içindeki bütün kaynak dosyalarını(.cpp uzantılı) stm32cubeide projenizin src klasörünün içine kopyalayın.
  • cpputest\src\Platforms\Gcc  klasörünün içindeki UtestPlatform.cpp dosyasını da aynı şekilde projenizin src klasörüne kopyalayın.
  • cpputest\include klasörü içindeki dosyaları projenizin Inc klasörünün içine kopyalayın.
Adım-3:
Project->Properties->C/C++ Build-> Settings->Tool Settings ->MCU G++ Compiler -> Preprocessor  penceresinde  “Define symbols” kısmında add simgesine tıklayıp CPPUTEST_STD_CPP_LIB_DISABLED ön işlemci komutunu ekleyin.

 

Adım-4:
Project->Properties->C/C++ Build-> Settings-> Tool Settings ->MCU G++ Compiler -> Optimization penceresinde;
  • Disable handling exceptions
  • Disable generation of information about every class with virtual functions
kutucuklarının işaretli olduğundan emin olun.

 

Adım-5:
Projenizin bulunduğu klasör içinde src/ IEEE754ExceptionsPlugin.cpp dosyasının içindeki  #ifdef CPPUTEST_HAVE_FENV satırını değiştirin. (CPPUTEST_HAVE_FENV den bir harf silip veya alakasız bir harf ekleyebilirsiniz. Buradaki amaç #else kısmındaki kodun derlenmesini sağlamaktır.)
Adım-6:
Build simgesine tıklayarak projeyi derleyin. Projenin Debug dosyası altındaki dosyalar cpputest in derlenmiş halidir. Asıl kodlarımızı yazıp testlerini yapacağımız projede debug dosyasını referans göstereceğiz. Bu aşamadan sonra bu projeyi kapatıp asıl proje kodlarımızı yazacağımız yeni stm32 projesi oluşturabiliriz.

 

CppUTest STM32CbeIDE ile Kullanımı

Adım-7:
Bu oluşturacağımız proje asıl kodlarımızı yazacağımız proje olacak. Burada kullanacak olduğunuz mikrodenetleyiciyi seçtikten sonra açılan pencerede aşağıdaki ayarları yapabilirsiniz. Burada “Targeted Language” kısmını C++ olarak seçeceğiz.

 

Adım-8:
Bu aşamada şöyle bir yol izliyorum. İki farklı build ve debug konfigürasyonu oluşturuyorum. Test kodları için ayrı bir dosya oluşturup bu dosya içerisindeki kodları yeni oluşturduğum build konfigürayonunda derlenmesi için ayarlıyorum. Böylece main.cpp üzerinde testini yaptığım kodları derleyerek mikrodenetleyiciye yüklüyorum. Ayrıca test kodları ROM’da yer tutmamış oluyor(asıl projede). Yani projenin dosya yapısını aşağıdaki gibi ayarlıyorum. Aşağıda görüldüğü gibi Tests dosyasının altında bir main.cpp ve Core klasörünün altında main.c bulunuyor. CppUTest isminden de anlaşılacağı gibi C++ üzerine kurulmuş bir framework ve testlerin koşturulması için .cpp uzantılı ve main fonksiyonu olan bir dosyadan çağrılması gerekmekte.  Gömülü tarafta ise çoğunlukla C dili ile çalıştığımız için böyle bir yol izleyebiliriz. Hem C de geliştirme yapmış oluruz hem de C de yazdığımız kütüphanelerin veya fonksiyonların testini yapmış oluruz.
Şimdi gelin yeni bir derleme konfigürasyonunun nasıl oluşturulduğuna bakalım. Bunun için proje üzerine sağ tıklayıp; Build Configurations -> Manage’e  ve açılan pencereden “New”butonuna tıklıyoruz.
Burada “Name” kısmına istediğiniz ismi verebilirsiniz. Burada önemli olan “Copy settings from” kısmından “Existing configuration” “Debug” olarak seçilmelidir. Bu aşamadan sonra “Ok” butonuna tıklayıp kapatabiliriz. Daha sonra bir önceki pencereden yeni oluşturduğumuz build konfigürasyonunu aktif etmemiz gerekiyor. Bunu “set active” butonuna tıklayarak yapıyoruz.

 

Adım 9:
Şimdi testler için ayrı bir dosya oluşturalım. Bunun için proje üzerine sağ tıklayıp; New->Source Folder’a tıklayalım.
Burada istediğiniz dosya ismini girip finish’e tıklayabilirsiniz. Daha sonra bu oluşturduğumuz Tests isimli dosyanın üzerine sağ tıklayıp; New->Source File a tıklayın.
Testlerimizin koşabilmesi için bir main içinden çağrılması lazım. Bu yüzden burada main.cpp isimli bir dosya oluşturuyorum. Daha sonra /Core/Src altındaki main.c nin içeriğinin hepsini kopyalayıp main.cpp içerisine yapıştırıyorum. Buradaki amaç debug yaparken test sonuçlarının UART üzerinden basılacak olmasıdır. main.c nin içerisinde UART ayarlı olduğu için burada tekrar oluşturmak yerine kopyalıyorum. Ardından main.cpp nin içine CppUtest için gerekli kütüphane dosyasını dahil ediyorum.
...
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "CppUTest/CommandLineTestRunner.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
...
Ardından Testlerin koşabilmesi için aşağıdaki iki satırlık bloğu main fonksiyonun içine ekliyorum.
...
/* USER CODE BEGIN WHILE */
 const char * av_override[] = { };
 CommandLineTestRunner::RunAllTests(0, av_override);
 while (1)
 {
 /* USER CODE END WHILE */
...
Daha sonra yine aynı şekilde Tests klasörünün içine “tests.cpp” isimli bir dosya daha ekliyorum. Bu dosya içerisinde test fonksiyonlarımızı yazacağız.
Daha sonra bu Tests isimli klasörün içine githubtan indirdiğimiz CppUTest projesinin  /cpputest/Include içindeki dosyaları kopyalıyoruz.
Adım-10:
Şimdi kendi yazacağımız ve testini yapacağımız kütüphaneler için bir dosya oluşturalım. Bunun için projeye sağ tıklayıp;
New->Source Folder’a tıklayıp açılan pencereden dosya ismini giriyoruz.
Daha sonra bu yeni oluşturduğumuz dosyaya  sağ tıklayıp; New->Source file’a tıklayıp açılan pencereden kütüphanemizin kaynak dosyasını oluşturuyoruz. Yine aynı şekilde New->header file diyerek .h uzantılı header dosyasını oluşturuyoruz.
Burada kütüphanemi C dosyası olarak oluşturuyorum çünkü burada CppUTest ile C kodlarının nasıl test edildiğini göstermek istiyorum.
Bu aşamadan sonra Proje dosya yapısı aşağıdaki gibi oluyor.
Bu kütüphaneye basit bir toplama fonksiyonu ekleyelim ve testlerimizin çalıştığından emin olalım. Bunun için my_lib.h ve my_lib.c dosyalarını aşağıdaki gibi düzenliyorum.
#ifndef MY_LIB_H_
#define MY_LIB_H_
#ifdef __cplusplus
extern "C"
{
#endif
int sumOfTwoNumbers(int a,int b);
#ifdef __cplusplus
}
#endif
#endif /* MY_LIB_H_ */
#include "my_lib.h"

int sumOfTwoNumbers(int a,int b)
{
 return a+b;
}
Adım 11:
Project->Properties->C/C++ Build-> Settings-> -> Tool Settings ->MCU G++ Compiler -> Preprocessor  penceresinden “add” butonuna tıklayıp aşağıdaki ön işlemci direktiflerini giriyoruz.
  • CPPUTEST_STD_CPP_LIB_DISABLED
  • CPPUTEST_STD_C_LIB_DISABLED
Burada önemli olan kısım, bu ayarı TestDebug için yapmamız gerek. Bu yüzden “Configuration” kısmında TestDebug seçilmiştir.
Adım-12:
Project->Properties -> C/C++ Build-> Settings-> Tool Settings ->MCU G++ Compiler -> Include paths penceresinden daha önce “Tests” dosyasının altına kopyaladığımız CppUTest kütüphanelerinin yolunu gösteriyorum.  Burada da yine “Configuration” kısmında TestDebug seçili olduğundan emin olun.
Adım-13:
Project->Properties->C/C++ Build-> Settings-> Tool Settings ->MCU G++ Compiler -> Optimization penceresinde;
  • Disable handling exceptions
  • Disable generation of information about every class with virtual functions
kutucuklarının işaretli olduğundan emin olun.
Adım-14:
Project->Properties->C/C++ Build-> Settings-> Tool Settings ->MCU G++ Linker -> Libraries penceresinden
  • “Library Search Path” e tıklayıp daha önce statik olarak derlediğimiz CppUTest projesinin Debug dosyasının yolunu giriyoruz. (Bunu add->file system ile yapabilirsiniz.)
  • Libraries kısmından “add” simgesine tıklayıp daha önce statik olarak derlediğimiz CppUTest projesinin ismini giriyoruz.

 

Adım-15:
Bu adımda kendi kütüphanemizi (my_lib.c) hem TestDebug hem de Debug yaparken kullanacak şekilde ayarlayalım. Önce normal Debug için kütüphane yolunu gösterelim. Bunun için Project Explorer kısmından Proje adına sağ tıklayıp; Properties->C/C++ General->Path and Symbols-> Includes penceresini açıyoruz.
Burada dikkat edilmesi gereken nokta “Configuration” kısmında “Debug” ın seçilmiş olmasıdır. “Language” kısmında asıl kodlarımı C de yazacağım için “languages” kısmını “GNU C” seçiyorum. Daha sonra add-> file system ile kütüphanemin olduğu yolu gösteriyorum. Daha sonra “apply and close” diyerek kapatabilirsiniz.
Ardından Project explorer penceresinden Tests dosyasına sağ tık-> Properties -> Path and Symbols -> Include penceresini açıyorum.
Burada Test kodlarımız C++ a göre derleneceği için “Languages” kısmından “GNU C++” seçiyorum. “Configuration” penceresinde ise “TestDebug” seçili olmalıdır. Ardından yine aynı şekilde add-> file system ile kütüphanenin yolunu gösteriyorum. Yine aynı pencereden bu sefer Tests klasörümün içine kopyaladığım CppUTest’in “include” kasörünü ekliyorum.
Adım 16:
Bu aşamada kodu hangi build konfigurasyonuna göre derlerseniz derleyin hata alırsınız çünkü projede iki adet main fonksiyonu var. Hem ana kodumun main fonksiyonu hem de testlerimi koşturduğum bir main fonksiyonu var. Bu sorunu şöyle çözüyorum. TestDebug konfigürasyonuna göre derleme yapılırken main.c dosyasına ihtiyacım yok. Yine aynı şekilde asıl kodumuzu test ettikten sonra mikrodenetleyiciye yükleyeceğim için yani Debug konfigürasyonuna göre derlediğimiz zaman main.cpp ile bir işimiz yok. Bu yüzden ilgili konfigürasyonlar için bu dosyaları derleme listesinden çıkarabiliriz. Bunun için; main.c’nin üzerine sağ tıklayıp Resource Configuration-> Exclude from builde tıklayalım.
Daha sonra açılan pencereden main.c’yi hangi derleme konfigürasyonundan çıkaracaksak onu seçiyoruz.
Aynı şekilde Tests dosyasına sağ tıklayıp; Resource Configuration -> Exclude From Build’e tıklayalım. Bu sefer Test dosyalarının Debug konfigürasyonuna göre derlenmesini istemediğimiz için bu pencerede sadece Debug kutucuğunu işaretliyorum.
Bu aşamadan sonra iki farklı konfigürasyona göre derleme yapıldığı zaman herhangi bir hata almamamız beklenir.
Adım-17
Şimdi gelelim test sonuçlarının UART üzerinden nasıl basılacağına bunun için iki yöntem göstereceğim. Hangisini kullanacağınızı siz seçin. Elimdeki Nucleo-G474RE’nin ve tahmin ediyorum ki çoğu Nucleo kartın şöyle güzel bir özelliği var. St-Link UART üzerinden Virtual Com Port olarak bağlı. Yani ekstra bir USB-TTL dönüştürücüye gerek kalmadan bilgisayar ile ST-Link üzerinden haberleşebiliriz. Bir diğer yöntem ise USB-TTL kullanarak başka bir UART üzerinden bilgisayara test sonuçlarını basmak. Bunun için 21. adıma kadar olan kısımları atlayıp 21. adımdan devam edebilirsiniz.
Stm32CubeIDE’de syscall.c projeye entegre olmuş halde oluşturulur. syscall.c içine tek satır kod ekleyerek test çıktılarını debug altındayken konsol ekranına basabiliriz. Bunun için syscall.c içindeki _write fonksiyonunu buluyoruz. Daha sonra ITM_SendChar fonksiyonunu aşağıdaki gibi ekleyip __io_putchar() fonksiyonunu yorum satırına alıyoruz.
Daha sonra syscall.c içinde #include “stm32g4xx.h”  satırını ekliyoruz. ( Siz hangi seriyi kullanıyorsanız ilgili kütühaneyi ekleyin.)
Adım-18:
Şimdi sırada Debug konfigurasyonu ayarları var. Build konfigurasyonunda yaptığımız gibi iki ayrı Debug konfigürasyonu oluşturalım. Bunun için debug simgesinin yanındaki üçgen simgesine ve ardından Debug Configurations’a tıklayın.
Açılan pencerede STM32 Cortex-M C/C++ Application a çift tıklayın.
Daha sonra C/C++ Application kısmından Search Project butonuna tıklayın.
Açılan pencerede “Qualifier:” kısmından Debug için konfigüre edilmiş derleme çıktısı olan .elf uzantılı dosyayı seçiyoruz. Daha sonra “OK” butonuna tıklayıp kapattıktan sonra Build Configurations kısmından Debug’ı seçiyoruz.
Ardından Debugger sekmesine gelip. Burada SWV(Serial Wire Viewer) kısmında Enable kutucuğunu işaretleyip Core Clock yerine mikrodenetleyicimizin clock frekansını giriyoruz. Eğer projenizde printf fonksiyonu falan kullanmayacaksanız SWV ayarını Debug için yapmanıza gerek yok. Bu ayar asıl TestDebug için önem arz etmektedir.
Daha sonra sonra aynı adımları bu sefer aşağıdaki gibi TestDebug için yapalım.
Debugger kısmından SWV ayarlarını da aynı şekilde yapmayı unutmayın.
Bu aşamada debug konfigürasyonlarını oluşturduk fakat debug tuşuna bastığınızda son kullanılan konfigürasyona göre debug yapar. Diğer konfigürasyonları da görmek için debug butonun yanındaki üçgen simgeye tıklayıp “Organize Favorites” e tıklıyoruz. Açılan pencerede iki debug konfigürasyonunu  da seçtiğimizde aşağıdaki resimde görüldüğü gibi alt alta sıralanır. Daha sonra hangisini kullanmak istiyorsanız o konfigürasyona tıklayıp debug yapabilirsiniz.
Adım-19:
Kodu TestDebug konfigürasyonuna göre debug edin. Run butonuna tıklamadan önce  Window->Show View->SWV->SWV ITM Data Console a tıklayın.
SWV data konsola tıkladıkttan sonra aşağıdaki console penceresinin olduğu alana SWV ITM Data Console isminde yeni bir sekme eklenecek. Bu sekmede sağ taraftaki ayarlar işaretine tıklayın.
Daha sonra açılan pencerede aşağıdaki gibi “port 0” kutucuğunu işaretleyip ardından OK butonuna tıklayıp pencereyi kapatın.
Debug yaparken SWV ITM Data Console sekmesindeki kırmızı butonun her zaman basılı olduğundan emin olun. Aksi takdirde konsolda herhangi bir şey görünmez.
Bu aşamadan sonra debug altındayken run simgesine basarak fonksiyonlarınızı rahatça test edebilirsiniz ve test sonuçlarını SWV ITM Data Console sekmesinden gözlemleyebilirsiniz.
Adım-20:
Son olarak bu adımda tests.cpp dosyamıza test fonksiyonlarımızı ve gerekli kütüphaneleri ekleyelim.
#include "CppUTest/TestHarness.h"
#include "my_lib.h"
TEST_GROUP(FirstTestGroup)
{
};
TEST(FirstTestGroup, FirstTest)
{
 STRCMP_EQUAL("hello", "world");
}
TEST(FirstTestGroup, SecondTest)
{
 LONGS_EQUAL(5,sumOfTwoNumbers(2, 3));
}
Bu şekilde TestDebug altında kodu çalıştırırsak çıktısı aşağıdaki gibi olur.
Burada görüldüğü gibi başarısız olan testler konsol ekranında basılmaktadır. Testlerin hepsinin geçildiği bir senaryo için kodu aşağıdaki gibi düzenliyorum.
#include "CppUTest/TestHarness.h"
#include "my_lib.h"
TEST_GROUP(FirstTestGroup)
{
};
TEST(FirstTestGroup, FirstTest)
{
 STRCMP_EQUAL("topuz", "topuz");
}
TEST(FirstTestGroup, SecondTest)
{
 LONGS_EQUAL(5,sumOfTwoNumbers(2, 3));
}
Bu testin çıktısı ise aşağıdaki gibidir.
Adım-21:
Şimdi test sonuçlarını başka bir UART üzerinden gönderelim. Bunun için yine syscall.c yi kullanabiliriz. Ayrıca main.cpp içinde PUTCHAR_PROTOTYPE ve __io_putchar fonksiyonlarını kullanarakta yapabiliriz. Ben syscall.c de bir kaç satır ekleme yaparak kullanmayı tercih ediyorum. Bunun için syscall.c dosyasına aşağıdaki üç satır kodu ekliyorum.
...
#include "main.h"

extern UART_HandleTypeDef huart1;
...
Daha sonra yine aynı dosyadaki  _write fonksiyonunu aşağıdaki gibi düzenliyorum.
...
__attribute__((weak)) int _write(int file, char *ptr, int len)
{
 int DataIdx;
 for (DataIdx = 0; DataIdx < len; DataIdx++)
 {
   //       __io_putchar(*ptr++);
  //          ITM_SendChar(*ptr++);
HAL_UART_Transmit(&huart1, (uint8_t *)(ptr++), 1, HAL_MAX_DELAY);
 }
 return len;
}
...
Bu aşamadan sonra TestDebug konfigürasyonuna göre debug yaptığımızda Termit, Tera Term vb. seri port görüntüleme araçları ve USB_TTL dönüştürücü ile Test sonuçlarını izleyebiliriz.

 

Mehmet Topuz

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir