STM8 ve IAR Kod Optimizasyonu

Başlatan Mucit23, 06 Mart 2021, 14:47:11

Mucit23

Selamlar

STM8 ve IAR da ufak tefek bir şeyler karalıyayım dedim. STM8S103F3'de çalışıyorum. 8kb flash hafızası var aslında pic den hatırladığım kadarıyla 8kb flash hafıza küçük çaplı uygulamalar için oldukça yeterli.

Yalnız şöyle bir durum var. IAR ve STM8'de kod yazarken program hafızası gerçekten çok çabuk tükeniyor. Derleme işi nasıl yapılıyor bilmiyorum. Basit birkaç örnek vereceğim. Sadece Basit Float bir işlem yapıyorum.

Mesafe=Sayac*0.034/2;

Bu kodu derlemesem program hafızası 3261Byte tutuyor. Derleyince 3707 Byte Tutuyor. Nerden baksanız 500 byte program hafızası tek bir satırda gidiyor. Program hafızasının bu kadar verimsiz tüketilmesine anlam veremiyorum. Kod Optimizasyonu en üst seviyede. Kod optimizasyonu çok büyük bir değişiklik yapmıyor.

Bu sorun sizce IAR ile mi alakalı. Sanki IAR yazılan kodu derlerken çok verimli yöntemler kullanmıyor. Aynı boyutta program belleği bulunan pic16 serisi mikroişlemcilere 10 larca satır kod yazar birçok işlemi yapardım.

ex_machina

STM8'lerde float unit olmadığından dolayı, float veri tipi kullanılarak yapılan hesaplamalar yazılımsal emulatör ile derleyici tarafından yapılıyor.
Ayrıca bu MCU 8 bit ALU barındırdığından dolayı, 32 bitlik float hesaplamaların emulatör ile gerçekleştirilmesi çok fazla program hafızası tüketiyor.

Çare: fixed point aritmetic.

Mucit23

Float sayılar için örnek verdim. Aynı işlemler pic16 serisi mcu larda çok çok az program hafızası tüketiyor. Bence sorun derleyici ile alakalı. Fixed point olmasa bile 32 bit float sayıları çok daha düşük kaynak tüketimi ile işlemek mümkün olmalı.

Çok basit 3-5 satırdan oluşan timer init fonksiyonu program hafızası dan yaklaşık 200 byte tüketiyor. STnin STM8 standart peripheral Kütüphanesini kullanıyorum.. Ya bu kütüphane çok verimsiz yada bu işlemci mimarisi ile bilmediğim şeyler var..

St nin 4-8kbye program hafızasına sahip düşük seviyeli mcu ları ile basit birkaç iş yapmak istesem program hafızası ile sorun yaşıyorum.. Daha yüksek program hafızasına sahip mcu lar ile low density STM32 serileri ile neredeyse aynı fiyat. Ne anladım ben bu işten.

Pic16f628 gibi 3.5 KB flash hafızaya sahip bir mcuya, 8 Kbyte flash hafızaya sahip stm8 serisi mcu dan en az iki kat daha fazla kod yazacağıma eminim.

Tagli

Floating point ya da benzeri bir kütüphaneyi kullanmanı gerektirecek ilk satırda, ilgili kütüphane bağlanır (linklenir) ve bellek kullanımında bir sıçrama görürsün. Aynı fonksiyonun sonraki çağrılışlarında bu kadar kod artışı olmaz. Yani bellek kullanım bedeli tek sefer ödenir. Ama elbette floating point benzeri bir işlemde performans bedelini her çağırmada ödersin.
Gökçe Tağlıoğlu

Mucit23

Floating point sayılara okeyim. Şöyle bir örnek vereyim. Main loop boş. Sadece Tim1'i input Capture olarak ayarlayan bir fonksiyonum var.
void TIM1_Config(void){
  
    TIM1_TimeBaseInit(16, TIM1_COUNTERMODE_UP, 55535, 0);
    TIM1_ICInit(TIM1_CHANNEL_2, TIM1_ICPOLARITY_RISING, TIM1_ICSELECTION_DIRECTTI, TIM1_ICPSC_DIV1, 1);
    TIM1_ITConfig(TIM1_IT_CC2, ENABLE);
    TIM1_Cmd(ENABLE);
    
    enableInterrupts();
}

Bu fonksiyonu eğer derlemezsem kullanılan program bellek miktarı aşağıdaki gibi.

Alıntı Yap404 bytes of readonly  code memory
  366 bytes of readonly  data memory
  282 bytes of readwrite data memory
 

Sadece yukarıdaki fonksiyonu init esnasında bir kere çağırdığımda kullanılan program bellek miktarı aşağıdaki gibi oluyor.

Alıntı Yap1 169 bytes of readonly  code memory
    481 bytes of readonly  data memory
    286 bytes of readwrite data memory
 

Tek bir fonksiyon program bellek tüketimini 600 byte'dan daha fazla arttırdı.

Neyse Daha sonra STM8 Peripheral library kullanmadan Register Seviyesinde kod yazarak aynı fonksiyonu tekrarladım.

Fonksiyonum aşağıdaki gibi oldu şimdi
void TIM1_Config(void){
 
    /* Set the Autoreload value */
  TIM1->ARRH = 0xFF;
  TIM1->ARRL = 0xFF;
    /* Set the Prescaler value */
  TIM1->PSCRH = 0;
  TIM1->PSCRL = 16;
  /* Select the Counter Mode */
  TIM1->CR1 = (uint8_t)((uint8_t)(TIM1->CR1 & (uint8_t)(~(TIM1_CR1_CMS | TIM1_CR1_DIR)))
                        | (uint8_t)(TIM1_COUNTERMODE_UP));
  /* Set the Repetition Counter value */
  TIM1->RCR = 0;
  
  TIM1->CCER1 &= (uint8_t)(~TIM1_CCER1_CC1E);
  
  /* Select the Input and set the filter */
  TIM1->CCMR1 = (uint8_t)((uint8_t)(TIM1->CCMR1 & (uint8_t)(~(uint8_t)( TIM1_CCMR_CCxS | TIM1_CCMR_ICxF ))) | 
                          (uint8_t)(( (TIM1_ICSELECTION_DIRECTTI)) | ((uint8_t)( 1 << 4))));
  TIM1->CCER1 &= (uint8_t)(~TIM1_CCER1_CC2P);//TIM IC Polarity=Rising
  
  /* Set the CCE Bit */
  TIM1->CCER1 |=  TIM1_CCER1_CC2E;
  
  TIM1->IER |= (uint8_t)TIM1_IT_CC2;
  
  TIM1->CR1 |= TIM1_CR1_CEN;
  
  enableInterrupts();
}


Bu durumdayken program bellek tüketim miktarı aynen bu şekilde

Alıntı Yap467 bytes of readonly  code memory
  366 bytes of readonly  data memory
  282 bytes of readwrite data memory

Sadece 63 byte program belliği artış gösterdi. Bu gayet kabul edilebilir.  Sorun Floating point kullanımı değil. Sorun IAR'ın gerekli optimizasyonu yapmaması. Tam olarak anlatmak istediğim budur.

quarko

floating point desteği olmayan bir mcu ile geliştirilen bir yazılımda float değişken kullanmak hiç doğru bir yaklaşım değil. TI daki IQ_Math gibi bir mantıkla sayıları, istenilen ondalıklı hassasiyet oranında belirli bir sayıyla çarpıp o şekilde kullanmak en mantıklısı. Bu şekilde olunca sayılar integer olarak tutulduğu için bellekte yer kaplama durumu olmaz. Ondalıklı bölme işlemine vs gerek olmaz. STM8 de tek cycle da çarpa bölme instruction ları var mı bilmiyorum. Varsa tadından yenmez, belleği de rahatça kullanırsınız.
"Aslanlar kendi hikayelerini yazmadıkça, avcıların kahramanlık hikayelerini dinlemek zorundayız."

Mucit23

Derdim floating point sayılarla değil

quarko

#7
Derdiniz floating point sayılarla olmayabilir hocam ama program belleğinin tek satırlık kodla dolmasının nedeni float türünden sayılarla işlem yapmanız.
"Aslanlar kendi hikayelerini yazmadıkça, avcıların kahramanlık hikayelerini dinlemek zorundayız."

tekosis

Bu kütüphaneler donanım soyutlama adına pek çok fonksiyonu iç içe çağırıyor. Yani hal libraryde bir işlemi yapayım derken bir fonksiyon diğerini çağırıyor o da diğerini çağırıyor derken iç içe çok fazla işlem yapılıyor. Ben buna bağlıyorum.
İlim ilim bilmektir, ilim kendin bilmektir, sen kendin bilmezsin, bu nice okumaktır.

Okan AKÇA

Printf komutuda aynı şekilde hafızayı yiyor. Bir  miktar optimize edilsede yinede fazla hafiza alani gidiyor.

Tagli

Bu kütüphane fonksiyonlarının içine bir bak, tahmin ettiğinden daha uzun olabilirler. Yukarıda floating point için dediğim durum burada da geçerli. Bu fonksiyonları sonraki çağrışlarında bu kadar maliyet ile karşılaşmayacaksın büyük ihtimalle.

Ama öte yandan düşük de olsa bir ihtimal, optimizasyon ile ilgili bir sorun da olabilir. Bir kütüphane çoğu zaman bir bütün olarak derlenir, yani içindeki kullanmadığın fonksiyonlar da derlenir. Derleme ve bağlama ayarlarına göre, genelde bağlayıcı (linker) kullanılmayan fonksiyonları tespit eder ve bunları bağlamaz, böylece bunlar yer kaplamazlar. GCC'de varsayılan ayarlar böyle, ama IAR ne yapıyor bilemiyorum.

Yukarıda bahsettiğim sorun özellikle SDCC gibi daha az gelişmiş derleyicilerde kendisi gösterir. STM8 için tek ücretsiz ve limitsiz derleyici olan SDCC anladığım kadarıyla her fonksiyonu ayrı section içinde derleyemiyor ve bu sebeple bağlama sırasında etkili bir ayıklama yapamıyor. SDCC sanırım bir .c dosyasını olduğu gibi bağlıyor veya hariç tutuyor. Bu yüzden SDCC için yazılmış bir kütüphanenin optimizasyona elverişli olabilmesi için her fonksiyonun ayrı bir .c dosyası içinde derlenmesi gerekiyor. Ancak bu şart sağlanırsa SDCC kullanılmayan fonksiyonu bağlamadan tamamen hariç tutabiliyor. Yani özetle, .c dosyasındaki tek bir fonksiyon bile kullanılıyorsa, o .c dosyasındaki tüm fonksiyonları bağlıyor SDCC.

Sanırım burada kilit ayar her fonksiyonun kendi section'ı ile derlenmesi. GCC'de -ffunction-sections (Place functions in their own sections) ve -fdata-sections (Place data in their own sections) derleme bayrakları bunu kontrol ediyor. Bağlayıcı (linker) tarafında ise -Wl,--gl-sections (Discard unused sections) bağlama bayrağı bununla ilgili. IAR'da da benzerleri olabilir, bir kontrol et. Ama belki gerçekten de kütüphane fonksiyonları sandığından daha büyüktür.
Gökçe Tağlıoğlu

Mucit23

#11
Alıntı yapılan: Tagli - 07 Mart 2021, 09:10:31Bu kütüphane fonksiyonlarının içine bir bak, tahmin ettiğinden daha uzun olabilirler. Yukarıda floating point için dediğim durum burada da geçerli. Bu fonksiyonları sonraki çağrışlarında bu kadar maliyet ile karşılaşmayacaksın büyük ihtimalle.

Ama öte yandan düşük de olsa bir ihtimal, optimizasyon ile ilgili bir sorun da olabilir. Bir kütüphane çoğu zaman bir bütün olarak derlenir, yani içindeki kullanmadığın fonksiyonlar da derlenir. Derleme ve bağlama ayarlarına göre, genelde bağlayıcı (linker) kullanılmayan fonksiyonları tespit eder ve bunları bağlamaz, böylece bunlar yer kaplamazlar. GCC'de varsayılan ayarlar böyle, ama IAR ne yapıyor bilemiyorum.

Yukarıda bahsettiğim sorun özellikle SDCC gibi daha az gelişmiş derleyicilerde kendisi gösterir. STM8 için tek ücretsiz ve limitsiz derleyici olan SDCC anladığım kadarıyla her fonksiyonu ayrı section içinde derleyemiyor ve bu sebeple bağlama sırasında etkili bir ayıklama yapamıyor. SDCC sanırım bir .c dosyasını olduğu gibi bağlıyor veya hariç tutuyor. Bu yüzden SDCC için yazılmış bir kütüphanenin optimizasyona elverişli olabilmesi için her fonksiyonun ayrı bir .c dosyası içinde derlenmesi gerekiyor. Ancak bu şart sağlanırsa SDCC kullanılmayan fonksiyonu bağlamadan tamamen hariç tutabiliyor. Yani özetle, .c dosyasındaki tek bir fonksiyon bile kullanılıyorsa, o .c dosyasındaki tüm fonksiyonları bağlıyor SDCC.

Sanırım burada kilit ayar her fonksiyonun kendi section'ı ile derlenmesi. GCC'de -ffunction-sections (Place functions in their own sections) ve -fdata-sections (Place data in their own sections) derleme bayrakları bunu kontrol ediyor. Bağlayıcı (linker) tarafında ise -Wl,--gl-sections (Discard unused sections) bağlama bayrağı bununla ilgili. IAR'da da benzerleri olabilir, bir kontrol et. Ama belki gerçekten de kütüphane fonksiyonları sandığından daha büyüktür.

Evet aslında demek istediğimi anladınız sanırım. Derleme aşamasında IAR'ın bütün c dosyalarını ayrı ayrı derlediğini görüyorum. Linker aşamasında bence gereksiz birçok fonksiyon birbirine bağlanıyor. Daha iyi bir optimizasyon yapılabileceğini düşünüyorum.

Dün gece uğraştım UART'ı init ettiğim fonksiyonu low level yazdım.
Normalde UART'ı ayarlamak için aşağıdaki fonksiyonlar işimi görüyordu.
    /* Configure the UART1 */
    UART1_Init((uint32_t)2400, UART1_WORDLENGTH_8D, UART1_STOPBITS_1, UART1_PARITY_NO,
                UART1_SYNCMODE_CLOCK_DISABLE, UART1_MODE_TXRX_ENABLE);
    
    /* Enable UART1 Transmit interrupt*/
    UART1_ITConfig(UART1_IT_RXNE_OR, ENABLE);
    
    /* Enable general interrupts */
    enableInterrupts(); 

Kodu bu şekilde derleyince Program boyutu aşağıdaki gibi.

Alıntı Yap2076 bytes of readonly  code memory
  362bytes of readonly  data memory
  282 bytes of readwrite data memory

Daha Sonra UART init fonksiyonunun içeriğini ben kendim Register Seviyesinde kodlayarak yazdım.

/**
  * @brief  UART1 Configuration for interrupt communication
  * @param  None
  * @retval None
  */
void UART_Config(void)
{
   uint32_t BaudRate=9600;
   uint32_t BaudRate_Mantissa = 0;
   uint32_t BaudRate_Mantissa100 = 0;
   
  // Set the word length bit according to UART1_WordLength value 
  UART1->CR1 = 0x00; //1 Start Bit, 8 Data Bit, n Stop Bit, Parity None
  //Set the STOP bits number according to UART1_StopBits value  
  UART1->CR3 = 0x00;  

  // Clear the LSB mantissa of UART1DIV  
  UART1->BRR1 = 0x00;  
  UART1->BRR2 = 0x00;  
  
  BaudRate_Mantissa  = ((uint32_t)SysClock / (BaudRate << 4));
  BaudRate_Mantissa100 = (((uint32_t)SysClock * 100) / (BaudRate << 4));
   
  // Set the fraction of UART1DIV 
  UART1->BRR2 |= (uint8_t)((uint8_t)(((BaudRate_Mantissa100 - (BaudRate_Mantissa * 100)) << 4) / 100) & (uint8_t)0x0F); 
  //Set the MSB mantissa of UART1DIV  
  UART1->BRR2 |= (uint8_t)((BaudRate_Mantissa >> 4) & (uint8_t)0xF0); 
  // Set the LSB mantissa of UART1DIV  
  UART1->BRR1 |= (uint8_t)BaudRate_Mantissa;  
  // Set the Transmitter, Receiver Enable and Reciever Interrupt Enable bit
  UART1->CR2 |= (uint8_t)(UART1_CR2_TEN | UART1_CR2_REN | UART1_CR2_RIEN);  
   
  enableInterrupts();    
}

Bu halde aynı fonksiyonu derleyince tüketilen program hafızası aşağıdaki gibi oldu.

Alıntı Yap1308 bytes of readonly  code memory
  362bytes of readonly  data memory
  282 bytes of readwrite data memory

Arada neredeyse 700 byte oynuyor. Aynı işlemleri yapıyorum halbuki. Sadece bir init fonksiyonunun düzgün bir şekilde optimize edilmemesi 4kb flash hafızaya sahip STM8S103F2 MCU'sunun 4kb lık flash hafızasının %17 sini götürdü.

Bence IAR gibi derleyicilerde hala yüksek hafızalı  MCU lar için  kullanılan yöntemlerden kalma alışkanlıklar var. Gerekli optimizasyon kesinlikle iyi yapılmıyor. Zira pic16F628 gibi 3.5k hafızaya sahip basit bir mcu ya CCS C ile onlarca satır kod yazdığımı hatırlıyorum.

Dediğim gibi STM8 serisi düşük fiyatlı MCU'lara kod yazmak için IAR gibi ortamlar çok verimli değil sanırım. Başka ayarları var mı bilmiyorum ama.

STM8'i aslında sevdim, Register Yapısı vs Alışkın olduğum STM32'ye çok benziyor. Ayarları kullanımı basit.  Fakat çalışma ortamı sıkıntılı. Yaptığım proje için stm8.h dışında hiçbir Peripheral library'i kullanmadan register seviyesinde kendim kodlama yapacağım. IAR için umarım bazı ayarlamalar vardır.

yas

Alıntı yapılan: Mucit23 - 06 Mart 2021, 22:47:06ram belliği artış gösterdi. Bu gayet kabul edilebilir.  Sorun Floating point kullanımı değil. Sorun IAR'ın gerekli optimizasyonu yapmaması. Tam olar

Evet (+1) derleyici optimizasyonu gerçekten çok önemli. Kütüphaneyi beğenmezsen kendinde yazabilirsin fakat derleyici üzerinde yapılabilecekler sınırlı. Ben pic mcu lar için proton kullanıyorum. Değişik derleyiciler kullandım ama protondan vaz geçemiyorum. Aşağıdaki resimde @Mucit23 hocanın verdiği örneği kullanarak derlemeler yaptım.
Mesafe=Sayac*0.034/2;
Belki birisinin işine yarar diye sonuçları paylaşmak istedim.

PIC18F46K22 için derleyici 278 byte program hafıza 27 byte ram kullandı.
PIC16F877A için derleyici 158 word program hafıza 27 byte ram kullandı.



Adama yağcılık olsun diye değil içimden geldiği için bunu söylemek istiyorum. Büyüksün LES amca  ::ok

Tagli

IAR ticari ve epey de pahalı bir derleyici. Kullanılmayan fonksiyonları bağlama sırasında ayıklama gibi bir optimizasyonu atlamalarına pek ihtimal vermiyorum.

Toolchain tüm derleme ve bağlama işlemlerinin ardından bir map dosyası üretiyor olmalı (belki ayarlardan bunu etkinleştirmek gerekiyor olabilir). Örneğin bir lib.c dosyası içinde A() ve B() gibi iki fonksiyon olsun. Sen A()'yı programında kullan ama B()'yi hiç kullanma. Sonra map dosyasında B adını arat, böylece kullanmadığın halde program imajına dahil edilip edilmediğini görebilirsin.
Gökçe Tağlıoğlu

Mucit23

Hocam birçok harici kütüphane kod parçacığını denedim. Her yerde fiyasko :) Aşağıdaki iki adet fonksiyon var.

rx_data=UART1_ReceiveData8();
UART1_ClearITPendingBit(UART1_IT_RXNE);

Fonksiyonların içeriği bu şekilde

uint8_t UART1_ReceiveData8(void)
{
  return ((uint8_t)UART1->DR);
}

void UART1_ClearITPendingBit(UART1_IT_TypeDef UART1_IT)
{
  assert_param(IS_UART1_CLEAR_IT_OK(UART1_IT));
  
  /* Clear the Receive Register Not Empty pending bit */
  if (UART1_IT == UART1_IT_RXNE)
  {
    UART1->SR = (uint8_t)~(UART1_SR_RXNE);
  }
  /* Clear the LIN Break Detection pending bit */
  else
  {
    UART1->CR4 &= (uint8_t)~(UART1_CR4_LBDF);
  }
}

Fonksiyonlar son derece basit. Çok dallı budaklı değiller. Buna Rağme yukarıdaki fonksiyonları kullanmayıp aşağıdaki gibi bir kullanım uygulasam program belleği tüketim miktarı 120 byte düşüyor.

rx_data=((uint8_t)UART1->DR);
UART1->SR = (uint8_t)~(UART1_SR_RXNE);  //Clear İnterrupt flag

Bu iki satırda 120 byte'lık ne yapılıyor olabilir ki? Bunu çözemiyorum. Map dosyasına biraz baktım ama çok anlamadım.

STM8 ve IAR elbette hoş. Özellikle STM32'deki gibi aktif çalışan debug seçeneğinin olması benim baya hoşuma gidiyor. Aktif olarak da kullanıyorum. Ama IAR'ın harici fonksiyolarda kod tüketimi çok fazla. Bunnu STD peripheral librar ile ilgili olduğunu da düşünmüyorum. Adamlar genel kütüphane çıkarmış sonuçta aynı kütüphaneyi birçok mcu için kullanıyor dolayısıyla içeride bir takıp ifler şartlar olması normal. Derleyicinin Bunları ince eleyip ince dokuması lazım. Burada bazı sıkıntılar olduğu aşikar.