Bagaimana cara menggunakan fungsi printf pada STM32?

19

Saya mencoba mencari cara untuk menggunakan fungsi printf untuk mencetak ke port serial.

Pengaturan saya saat ini adalah kode yang dihasilkan STM32CubeMX dan SystemWorkbench32 dengan papan penemuan STM32F407 .

Saya melihat di stdio.h bahwa prototipe printf didefinisikan sebagai:

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

Apa artinya? Di mana lokasi pasti dari definisi fungsi ini? Apa yang menjadi poin umum untuk mengetahui bagaimana menggunakan fungsi semacam ini untuk menghasilkan?

pengguna505160
sumber
5
Saya pikir Anda perlu menulis "int _write (file int, char * ptr, int len)" Anda sendiri untuk mengirim output standar ke port serial Anda seperti di sini . Saya percaya ini biasanya dilakukan dalam file bernama Syscalls.c yang menangani "System Calls Remapping". Coba Googling "Syscalls.c".
Tut
1
Ini bukan masalah elektronik. Pergi melihat manual untuk perpustakaan kompiler Anda.
Olin Lathrop
Apakah Anda ingin melakukan printf debugging melalui semihosting (melalui debugger) atau hanya printf secara umum?
Daniel
Seperti @Tut mengatakan, _write()fungsi adalah apa yang saya lakukan. Rincian dalam jawaban saya di bawah.
cp.engr
ini adalah tutorial yang bagus: youtu.be/Oc58Ikj-lNI
Joseph Pighetti

Jawaban:

19

Aku punya metode pertama dari halaman ini bekerja pada STM32F072 saya.

http://www.openstm32.org/forumthread1055

Sebagaimana dinyatakan di sana,

Cara saya mendapatkan printf (dan semua fungsi stdio berorientasi konsol lainnya) bekerja adalah dengan membuat implementasi kustom dari fungsi I / O tingkat rendah seperti _read()dan _write().

Pustaka GCC C membuat panggilan ke fungsi berikut untuk melakukan I / O tingkat rendah:

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)

Fungsi-fungsi ini diimplementasikan dengan perpustakaan GCC C sebagai rutinitas tulisan rintisan dengan tautan "lemah". Jika deklarasi salah satu dari fungsi di atas muncul dalam kode Anda sendiri, rutin pengganti Anda akan menggantikan deklarasi di pustaka dan digunakan sebagai ganti rutin default (non-fungsional).

Saya menggunakan STM32CubeMX untuk mengatur USART1 ( huart1) sebagai port serial. Karena saya hanya ingin printf(), saya hanya perlu mengisi _write()fungsi, yang saya lakukan sebagai berikut. Ini terkandung secara konvensional dalam 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);
}
cp.engr
sumber
Bagaimana jika saya menggunakan Makefile dan bukan IDE? Ada yang kurang dari ini untuk bekerja di proyek Makefile saya ... Adakah yang bisa membantu?
Tedi
@ Dedi, apakah Anda menggunakan GCC? Ini adalah jawaban khusus-kompiler. Apa yang sudah Anda coba, dan apa hasilnya?
cp.engr
Ya saya menggunakan GCC. Saya menemukan masalahnya. Fungsi _write () dipanggil. Masalahnya ada pada kode saya untuk mengirim string. Saya menyalahgunakan perpustakaan RTT Segger tetapi sekarang berfungsi dengan baik. Terima kasih. Semua bekerja sekarang.
Tedi
4

_EXFUN adalah makro, mungkin berisi beberapa arahan menarik yang memberi tahu kompiler bahwa ia harus memeriksa format-string agar kompatibel dengan printf dan memastikan bahwa argumen untuk printf cocok dengan string format.

Untuk mempelajari cara menggunakan printf, saya sarankan halaman manual dan lebih banyak googling. Tulis beberapa program C sederhana yang menggunakan printf dan jalankan di komputer Anda sebelum Anda mencoba beralih ke menggunakannya di mikro.

Pertanyaan yang menarik adalah "kemana teks yang dicetak pergi?". Pada sistem seperti unix, ia pergi ke "keluar standar", tetapi mikrokontroler tidak memiliki hal seperti itu. Perpustakaan debug CMSIS dapat mengirim teks printf ke port debug lengan semihosting, yaitu ke sesi gdb atau openocd Anda, tetapi saya tidak tahu apa yang akan dilakukan SystemWorkbench32.

Jika Anda tidak bekerja dalam debugger, mungkin lebih masuk akal untuk menggunakan sprintf untuk memformat string yang ingin Anda cetak dan kemudian mengirim string itu melalui port serial atau ke tampilan apa pun yang mungkin telah Anda pasang.

Hati-hati: printf dan kode yang terkait sangat besar. Itu mungkin tidak terlalu menjadi masalah pada 32F407, tetapi ini adalah masalah nyata pada perangkat dengan sedikit flash.

William Brodie-Tyrrell
sumber
IIRC _EXFUN menandai fungsi yang akan diekspor, yaitu untuk digunakan dalam kode pengguna, dan mendefinisikan konvensi pemanggilan. Pada sebagian besar platform (tertanam), konvensi panggilan standar dapat digunakan. Tetapi pada x86 dengan pustaka dinamis, Anda mungkin perlu mendefinisikan fungsi __cdecluntuk menghindari kesalahan. Setidaknya pada newlib / cygwin, _EXFUN hanya digunakan untuk mengatur __cdecl.
erebos
4

@ AdamHaun menjawab semua yang Anda butuhkan, dengan sprintf()mudah membuat string lalu mengirimkannya. Tetapi jika Anda benar-benar menginginkan printf()fungsi Anda sendiri, maka Variable Argument Functions (va_list) adalah caranya.

Dengan va_list fungsi cetak khusus tampak seperti berikut:

#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);
}

Contoh penggunaan:

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);

Perhatikan bahwa sementara solusi ini memberi Anda fungsi yang nyaman untuk digunakan, tetapi lebih lambat daripada mengirim data mentah atau bahkan menggunakan sprintf() . Dengan flatates tinggi saya pikir itu tidak akan cukup.


Opsi lain, dan mungkin opsi yang lebih baik adalah menggunakan ST-Link, SWD debugger bersama dengan ST-Link Utility. Dan gunakan Printf via SWO viewer , di sini adalah manual ST-Link Utility , bagian yang relevan dimulai pada halaman 31.

Printf via SWO Viewer menampilkan data printf yang dikirim dari target melalui SWO. Memungkinkan untuk menampilkan beberapa informasi bermanfaat tentang firmware yang sedang berjalan.

Bence Kaulics
sumber
Anda tidak menggunakan va_arg, bagaimana Anda melangkah melalui argumen variabel?
iouzzr
1

printf () adalah (biasanya) bagian dari perpustakaan standar C. Jika versi perpustakaan Anda dilengkapi dengan kode sumber, Anda mungkin menemukan implementasi di sana.

Mungkin akan lebih mudah menggunakan sprintf () untuk menghasilkan string, kemudian menggunakan fungsi lain untuk mengirim string melalui port serial. Dengan begitu semua pemformatan yang sulit dilakukan untuk Anda dan Anda tidak perlu meretas pustaka standar.

Adam Haun
sumber
2
printf () biasanya merupakan bagian dari perpustakaan, ya, tetapi tidak dapat melakukan apa - apa sampai seseorang mengatur kemampuan yang mendasari untuk output ke perangkat keras yang diinginkan. Itu bukan "meretas" perpustakaan, tetapi menggunakannya sebagaimana dimaksud.
Chris Stratton
Mungkin sudah dikonfigurasikan untuk mengirim teks melalui koneksi debugger, seperti yang disarankan Bence dan William dalam jawaban mereka. (Tentu saja bukan itu yang diinginkan si penanya.)
Adam Haun
Itu biasanya hanya akan terjadi karena kode yang Anda pilih untuk ditautkan untuk mendukung papan Anda, tetapi terlepas dari apa pun Anda mengubahnya dengan mendefinisikan fungsi output Anda sendiri. Masalah dengan proposal Anda adalah bahwa itu tidak didasarkan pada pemahaman bagaimana perpustakaan dimaksudkan untuk digunakan - daripada melakukan itu Anda mengusulkan proses multi-langkah untuk setiap ouput, yang antara lain akan membuat kekacauan dari setiap portable yang ada kode yang melakukan hal-hal dengan cara normal.
Chris Stratton
STM32 adalah lingkungan yang berdiri sendiri. Kompiler tidak perlu menyediakan stdio.h - sepenuhnya opsional. Tetapi jika memang menyediakan stdio.h, itu harus menyediakan semua perpustakaan. Penyusun sistem berdiri bebas yang menerapkan printf cenderung melakukannya sebagai komunikasi UART.
Lundin
1

Bagi yang kesulitan, tambahkan yang berikut ke syscalls.c:

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);
}

Hubungkan ke port COM Anda melalui dempul dan Anda harus baik.

Michael Brown
sumber
1

Jika Anda menginginkan pendekatan yang berbeda dan tidak ingin menimpa fungsi atau mendeklarasikan sendiri, Anda dapat menggunakannya snprintfuntuk mengarsipkan tujuan yang sama. Tapi itu tidak elegan.

char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
LeoDJ
sumber