STM32'deki printf işlevini nasıl kullanırım?


19

Seri bağlantı noktasına yazdırmak için printf işlevini kullanmayı anlamaya çalışıyorum.

Şu anki kurulumum STM32CubeMX tarafından üretilen kod ve STM32F407 keşif kartı ile SystemWorkbench32 .

Stdio.h dosyasında printf prototipinin şu şekilde tanımlandığını görüyorum:

int _EXFUN(printf, (const char *__restrict, ...)
               _ATTRIBUTE ((__format__ (__printf__, 1, 2))));

Bu ne demek? Bu işlev tanımının tam yeri nerededir? Bu tür bir işlevi çıktı olarak nasıl kullanacağınızı öğrenmenin genel anlamı ne olabilir?


5
Sanırım standart çıkışınızı burada gibi seri bağlantı noktasına göndermek için kendi "int _write (int dosyası, char * ptr, int len)" yazmanız gerekiyor . Bunun normalde "Sistem Çağrılarını Yeniden Eşleme" işleyen Syscalls.c adlı bir dosyada yapıldığına inanıyorum. Googling "Syscalls.c" dosyasını deneyin.
Tut

1
Bu bir elektronik problemi değil. Derleyici kitaplığınızın kılavuzuna bakın.
Olin Lathrop

Semifhost (hata ayıklayıcı üzerinden) üzerinden veya sadece printf üzerinden printf hata ayıklama yapmak mı istiyorsunuz?
Daniel

@Tut'un dediği gibi, _write()işlev yaptığım şey. Aşağıdaki cevabımdaki detaylar.
cp.engr

Bu harika bir öğreticiydi: youtu.be/Oc58Ikj-lNI
Joseph Pighetti

Yanıtlar:


19

STM32F072 üzerinde çalışan bu sayfadan ilk yöntemi aldım.

http://www.openstm32.org/forumthread1055

Orada belirtildiği gibi,

İşe ben printf var yolu (ve tüm diğer konsol odaklı stdio fonksiyonlar) gibi G / Ç fonksiyonları düşük seviyede özel uygulamaları oluşturarak oldu _read()ve _write().

GCC C kütüphanesi, düşük seviyeli G / Ç gerçekleştirmek için aşağıdaki işlevlere çağrı yapar:

int _read(int file, char *data, int len)
int _write(int file, char *data, int len)
int _close(int file)
int _lseek(int file, int ptr, int dir)
int _fstat(int file, struct stat *st)
int _isatty(int file)

Bu işlevler GCC C kütüphanesinde "zayıf" bağlantıya sahip saplama rutinleri olarak uygulanır. Yukarıdaki işlevlerden herhangi birinin bildirimi kendi kodunuzda görünürse, yedek yordamınız kitaplıktaki bildirimi geçersiz kılar ve varsayılan (işlevsel olmayan) yordam yerine kullanılır.

huart1Seri port olarak USART1 ( ) kurmak için STM32CubeMX kullandım . Sadece istediğim için printf(), sadece _write()şu şekilde yaptığım işlevi doldurmam gerekiyordu . Bu geleneksel olarak içinde bulunur syscalls.c.

#include  <errno.h>
#include  <sys/unistd.h> // STDOUT_FILENO, STDERR_FILENO

int _write(int file, char *data, int len)
{
   if ((file != STDOUT_FILENO) && (file != STDERR_FILENO))
   {
      errno = EBADF;
      return -1;
   }

   // arbitrary timeout 1000
   HAL_StatusTypeDef status =
      HAL_UART_Transmit(&huart1, (uint8_t*)data, len, 1000);

   // return # of bytes written - as best we can tell
   return (status == HAL_OK ? len : 0);
}

IDE yerine Makefile kullanırsam ne olur? Makefile projemde bunun için bir şey eksik ... Herkes yardımcı olabilir mi?
Tedi

@Tedi, GCC kullanıyor musunuz? Bu derleyiciye özgü bir cevaptır. Ne denediniz ve sonuçlar nelerdi?
cp.engr

Evet, GCC kullanıyorum. Sorunu buldum. _Write () işlevi çağrıldı. Sorun benim dize göndermek için kodum oldu. Segger'in RTT kütüphanesini yanlış kullandım ama şimdi iyi çalışıyor. Teşekkürler. Şimdi hepsi çalışıyor.
Tedi

5

@ AdamHaun'un cevabı tek ihtiyacınız olan sprintf()şey, bir dize oluşturmak ve daha sonra göndermek kolaydır. Ancak gerçekten printf()kendinize ait bir işlev istiyorsanız , Değişken Bağımsız Değişken İşlevleri (va_list) yoldur.

İle va_listaşağıdaki gibi bir özel baskı fonksiyonu görünüyor:

#include <stdio.h>
#include <stdarg.h>
#include <string.h>

void vprint(const char *fmt, va_list argp)
{
    char string[200];
    if(0 < vsprintf(string,fmt,argp)) // build string
    {
        HAL_UART_Transmit(&huart1, (uint8_t*)string, strlen(string), 0xffffff); // send message via UART
    }
}

void my_printf(const char *fmt, ...) // custom printf() function
{
    va_list argp;
    va_start(argp, fmt);
    vprint(fmt, argp);
    va_end(argp);
}

Kullanım örneği:

uint16_t year = 2015;
uint8_t month = 12;
uint8_t day   = 18;
char* date = "date";

// "Today's date: 2015-12-18"
my_printf("Today's %s: %d-%d-%d\r\n", date, year, month, day);

Bu çözümün size kullanışlı bir işlev sunmasına rağmen, ham veri göndermekten ve hatta kullanmaktan daha yavaş olduğunu unutmayın sprintf(). Yüksek veri hızları ile bunun yeterli olmayacağını düşünüyorum.


Başka bir seçenek ve muhtemelen daha iyi bir seçenek ST-Link, SWD hata ayıklayıcı ST-Link Utility ile birlikte kullanmaktır. Ve SWO görüntüleyici üzerinden Printf kullanın , burada ST-Link Utility kılavuzu , ilgili bölüm sayfa 31'de başlıyor.

SWO ile Printf Görüntüleyicisi, hedeften SWO aracılığıyla gönderilen printf verilerini görüntüler. Çalışan ürün yazılımı hakkında bazı yararlı bilgilerin görüntülenmesini sağlar.


va_arg kullanmıyorsanız, değişken argümanlarında nasıl ilerlersiniz?
iouzzr

4

_EXFUN, büyük olasılıkla derleyiciye biçim dizesini printf uyumlu olup olmadığını denetlemesi ve printf değişkenlerinin biçim dizesiyle eşleştiğinden emin olması gerektiğini bildiren bazı ilginç yönergeler içeren bir makrodur.

Printf'in nasıl kullanılacağını öğrenmek için man sayfasını ve biraz daha fazla googling . Mikro kullanarak kullanmaya başlamadan önce printf kullanan bazı basit C programlarını yazın ve bilgisayarınızda çalıştırın.

İlginç soru "basılı metin nereye gidiyor?" Unix benzeri bir sistemde, "standart çıkış" a gider, ancak mikrodenetleyicinin böyle bir şeyi yoktur. CMSIS hata ayıklama kitaplıkları kol yarı ana bilgisayar hata ayıklama bağlantı noktasına, yani gdb veya openocd oturumunuza printf metni gönderebilir, ancak SystemWorkbench32'nin ne yapacağına dair hiçbir fikrim yok.

Bir hata ayıklayıcıda çalışmıyorsanız, yazdırmak istediğiniz dizeleri biçimlendirmek için sprintf kullanmak ve daha sonra bu dizeleri bir seri bağlantı noktası üzerinden veya eklediğiniz herhangi bir ekrana göndermek daha mantıklı olabilir.

Dikkat: printf ve ilgili kodu çok büyük. Bu muhtemelen 32F407'de çok önemli değil, ancak az flaşlı cihazlarda gerçek bir sorun.


IIRC _EXFUN dışa aktarılacak, yani kullanıcı kodunda kullanılacak bir işlevi işaretler ve çağrı kuralını tanımlar. Çoğu (gömülü) platformda varsayılan çağrı kuralı kullanılabilir. Ancak dinamik kitaplıklara sahip x86'da, __cdeclhataları önlemek için işlevi tanımlamanız gerekebilir . En azından newlib / cygwin'de, _EXFUN yalnızca ayarlamak için kullanılır __cdecl.
erebos

1

printf () (genellikle) C standart kütüphanesinin bir parçasıdır. Kitaplık sürümünüz kaynak koduyla birlikte gelirse, orada bir uygulama bulabilirsiniz.

Bir dize oluşturmak için sprintf () kullanmak daha sonra dizeyi seri bağlantı noktası üzerinden göndermek için başka bir işlev kullanmak muhtemelen daha kolay olacaktır. Bu şekilde tüm zor biçimlendirme sizin için yapılır ve standart kitaplığı kesmek zorunda kalmazsınız.


2
printf () genellikle kütüphanenin bir parçasıdır, evet, ancak birisi istenen donanıma çıkış için temel yeteneği ayarlayana kadar hiçbir şey yapamaz . Bu, kütüphaneyi "hacklemek" değil, istendiği gibi kullanmaktır.
Chris Stratton

Bence ve William'ın cevaplarında önerdiği gibi, hata ayıklayıcı bağlantısı üzerinden metin göndermek için önceden yapılandırılmış olabilir. (Elbette bu
sorucının

Bu genellikle yalnızca kartınızı desteklemek için bağlamayı seçtiğiniz kodun bir sonucu olur, ancak kendi çıkış işlevinizi tanımlayarak bunu değiştirirsiniz. Teklifinizle ilgili sorun, kütüphanenin nasıl kullanılması gerektiğini anlamaya değil, her çıkış için çok adımlı bir süreç önermekten ziyade, mevcut şeylerin herhangi bir taşınabilir ortamın dağınıklığına neden olacağıdır. kodları normal şekilde yapar.
Chris Stratton

STM32 bağımsız bir ortamdır. Derleyicinin stdio.h sağlaması gerekmez - tamamen isteğe bağlıdır. Ancak stdio.h sağlıyorsa, tüm kütüphaneyi sağlamalıdır . Printf uygulayan bağımsız sistem derleyicileri bunu UART iletişimi olarak yapma eğilimindedir.
Lundin

1

Mücadele edenler için, syscalls.c dosyasına aşağıdakileri ekleyin:

extern UART_HandleTypeDef huart1; // access huart1 instance
//extern int __io_putchar(int ch) __attribute__((weak)); // comment this out

__attribute__((weak)) int __io_putchar(int ch)
{
    HAL_StatusTypeDef status = HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
    return (status == HAL_OK ? ch : 0);
}

COM portunuza macun ile bağlanın ve iyi olmalısınız.


1

Farklı bir yaklaşım istiyorsanız ve işlevlerin üzerine yazmak veya kendinizinkini beyan etmek istemiyorsanız snprintf, aynı hedefi elde etmek için kullanabilirsiniz . Ama o kadar zarif değil.

char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.