ฉันจะใช้ฟังก์ชัน printf บน STM32 ได้อย่างไร


19

ฉันพยายามหาวิธีใช้ฟังก์ชั่น printf เพื่อพิมพ์ไปยังพอร์ตอนุกรม

การตั้งค่าปัจจุบันของฉันคือรหัสที่สร้างขึ้นจากSTM32CubeMXและ SystemWorkbench32 พร้อมกับบอร์ดการค้นพบ STM32F407คณะกรรมการ

ฉันเห็นใน stdio.h ว่าต้นแบบ printf ถูกกำหนดเป็น:

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

มันหมายความว่าอะไร? ตำแหน่งที่แน่นอนของนิยามฟังก์ชันนี้อยู่ที่ไหน อะไรคือจุดทั่วไปในการค้นหาวิธีการใช้ฟังก์ชั่นประเภทนี้ในการส่งออก


5
ฉันคิดว่าคุณต้องเขียนของคุณเอง "int _write (int ไฟล์ char * PTR, int len)" เพื่อส่งออกมาตรฐานของคุณกับพอร์ตอนุกรมของคุณเช่นที่นี่ ฉันเชื่อว่าปกติจะทำในไฟล์ชื่อ Syscalls.c ซึ่งจัดการ "System Calls Remapping" ลองใช้ Google "Syscalls.c"
Tut

1
นี่ไม่ใช่ปัญหาทางอิเล็กทรอนิกส์ ไปดูคู่มือสำหรับไลบรารีคอมไพเลอร์ของคุณ
Olin Lathrop

คุณต้องการที่จะทำการดีบัก printf ผ่าน semihosting (มากกว่าดีบักเกอร์) หรือเพียงแค่ printf โดยทั่วไป?
Daniel

ตามที่ @Tut กล่าวว่า_write()ฟังก์ชั่นคือสิ่งที่ฉันทำ รายละเอียดในคำตอบของฉันด้านล่าง
cp.engr

นี่คือการสอนที่ยอดเยี่ยม: youtu.be/Oc58Ikj-lNI
Joseph Pighetti

คำตอบ:


19

ฉันได้วิธีแรกจากหน้านี้ที่ทำงานกับ STM32F072 ของฉัน

http://www.openstm32.org/forumthread1055

ตามที่ระบุไว้มี

วิธีที่ฉันได้รับ printf (และฟังก์ชั่น stdio สำหรับฟังก์ชั่นอื่น ๆ ของคอนโซล) ในการทำงานคือการสร้างการใช้งานแบบกำหนดเองของฟังก์ชั่น I / O ระดับต่ำเช่น_read()และ_write()และ

ไลบรารี GCC C ทำการโทรไปยังฟังก์ชั่นต่อไปนี้เพื่อดำเนินการ I / O ระดับต่ำ:

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)

ฟังก์ชั่นเหล่านี้ถูกนำไปใช้กับห้องสมุด GCC C เป็นรูทีน stub ด้วยการเชื่อมโยง "อ่อนแอ" หากการประกาศของฟังก์ชันใด ๆ ข้างต้นปรากฏในรหัสของคุณเองชุดคำสั่งทดแทนของคุณจะแทนที่การประกาศในไลบรารีและใช้แทนชุดคำสั่งเริ่มต้น (ไม่ใช่หน้าที่)

ฉันใช้ STM32CubeMX เพื่อตั้งค่า USART1 ( huart1) เป็นพอร์ตอนุกรม เนื่องจากฉันต้องการเพียงอย่างเดียวprintf()ฉันต้องเติม_write()ฟังก์ชั่นซึ่งฉันทำดังต่อไปนี้ 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);
}

ถ้าฉันใช้ Makefile และไม่ใช่ IDE ล่ะ มีบางอย่างขาดหายไปจากการทำงานในโครงการ Makefile ของฉัน ... ใครช่วยได้บ้าง
Tedi

@Tedi คุณใช้ GCC หรือไม่ นี่เป็นคำตอบเฉพาะของคอมไพเลอร์ คุณลองทำอะไรและผลลัพธ์อะไรบ้าง
cp.engr

ใช่ฉันใช้ GCC ฉันพบปัญหา ฟังก์ชัน _write () ถูกเรียก ปัญหาอยู่ในรหัสของฉันสำหรับส่งสตริง ฉันใช้ไลบรารี RTT ของ Segger ในทางที่ผิด แต่ตอนนี้ใช้ได้ดี ขอบคุณ ทุกอย่างทำงานแล้ว
Tedi

4

_EXFUN เป็นมาโครอาจมีคำสั่งที่น่าสนใจที่บอกคอมไพเลอร์ว่าควรตรวจสอบรูปแบบของสตริงว่าสามารถใช้งานร่วมกับ printf ได้หรือไม่และตรวจสอบให้แน่ใจว่าอาร์กิวเมนต์ของ printf ตรงกับรูปแบบสตริง

เพื่อเรียนรู้วิธีใช้ printf ฉันแนะนำหน้า manและ googling อีกเล็กน้อย เขียนโปรแกรม C อย่างง่ายที่ใช้ printf และรันบนคอมพิวเตอร์ของคุณก่อนที่คุณจะลองเปลี่ยนไปใช้ในไมโคร

คำถามที่น่าสนใจจะเป็น "ข้อความที่พิมพ์หายไปไหน" บนระบบที่เหมือนยูนิกซ์มันจะไปที่ "standard out" แต่ไมโครคอนโทรลเลอร์ไม่มีสิ่งนั้น ไลบรารีดีบัก CMSIS สามารถส่งข้อความ printf ไปยังพอร์ต debug arm semihosting เช่นใน gdb หรือเซสชัน openocd ของคุณ แต่ฉันไม่ทราบว่า SystemWorkbench32 จะทำอะไร

หากคุณไม่ได้ทำงานในเครื่องมือดีบั๊กคุณอาจใช้ sprintf เพื่อจัดรูปแบบสตริงที่คุณต้องการพิมพ์จากนั้นส่งสตริงเหล่านั้นผ่านพอร์ตอนุกรมหรือไปยังจอแสดงผลที่คุณแนบไว้

ระวัง: printf และรหัสที่เกี่ยวข้องมีขนาดใหญ่มาก นั่นอาจไม่สำคัญมากนักสำหรับ 32F407 แต่มันเป็นปัญหาที่แท้จริงของอุปกรณ์ที่มีแฟลชน้อย


IIRC _EXFUN ทำเครื่องหมายฟังก์ชันที่จะส่งออกเช่นใช้ในรหัสผู้ใช้และกำหนดการประชุมที่เรียก บนแพลตฟอร์มส่วนใหญ่ (แบบฝัง) การประชุมเริ่มต้นสามารถใช้งานได้ แต่ใน x86 ที่มีไลบรารีแบบไดนามิกคุณอาจจำเป็นต้องกำหนดฟังก์ชัน__cdeclเพื่อหลีกเลี่ยงข้อผิดพลาด อย่างน้อยใน newlib / cygwin จะใช้ _EXFUN เพื่อตั้งค่า__cdeclเท่านั้น
erebos

4

@ คำตอบของ AdamHaun คือทุกสิ่งที่คุณต้องการsprintf()เพราะมันง่ายที่จะสร้างสตริงแล้วส่งมัน แต่ถ้าคุณต้องการprintf()ฟังก์ชั่นของคุณเองฟังก์ชั่นอาร์กิวเมนต์ของตัวแปร (va_list)ก็เป็นวิธีที่ดี

ด้วยva_listฟังก์ชั่นการพิมพ์แบบกำหนดเองมีลักษณะดังต่อไปนี้:

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

ตัวอย่างการใช้งาน:

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

โปรดทราบว่าในขณะที่โซลูชันนี้ให้คุณใช้งานได้สะดวก แต่มันช้ากว่าการส่งข้อมูลดิบหรือใช้งานแบบsprintf()สม่ำเสมอ ด้วยผู้มีเกียรติสูงฉันคิดว่ามันคงไม่เพียงพอ


ตัวเลือกอื่นและตัวเลือกที่น่าจะดีกว่าคือการใช้ ST-Link, SWD debugger พร้อมกับ ST-Link Utility และใช้Printf ผ่านทางโปรแกรมดู SWOนี่คือคู่มือของST-Link Utilityส่วนที่เกี่ยวข้องเริ่มต้นในหน้า 31

Printf ผ่านทาง SWO Viewer จะแสดงข้อมูล printf ที่ส่งจากเป้าหมายผ่านทาง SWO อนุญาตให้แสดงข้อมูลที่เป็นประโยชน์บางอย่างเกี่ยวกับการใช้งานเฟิร์มแวร์


คุณไม่ได้ใช้ va_arg คุณจะก้าวผ่านการขัดแย้งตัวแปรอย่างไร
iouzzr

1

printf () คือ (ปกติ) เป็นส่วนหนึ่งของไลบรารีมาตรฐาน C หากไลบรารีเวอร์ชันของคุณมาพร้อมกับซอร์สโค้ดคุณอาจพบว่ามีการใช้งานที่นั่น

มันอาจจะง่ายกว่าถ้าใช้ sprintf () เพื่อสร้างสตริงจากนั้นใช้ฟังก์ชันอื่นเพื่อส่งสตริงผ่านพอร์ตอนุกรม ด้วยวิธีนี้การจัดรูปแบบที่ยากจะทำเพื่อคุณและคุณไม่ต้องแฮ็คไลบรารีมาตรฐาน


2
printf () มักจะเป็นส่วนหนึ่งของไลบรารีใช่ แต่มันไม่สามารถทำอะไรได้จนกว่าจะมีคนตั้งค่าความสามารถพื้นฐานเพื่อส่งออกไปยังฮาร์ดแวร์ที่ต้องการ นั่นไม่ใช่ "การแฮ็ค" ไลบรารี แต่ใช้มันตามที่ตั้งใจไว้
Chris Stratton

อาจกำหนดค่าล่วงหน้าให้ส่งข้อความผ่านการเชื่อมต่อดีบักเกอร์ตามที่ Bence และ William แนะนำในคำตอบ (นั่นไม่ใช่สิ่งที่ผู้ถามต้องการแน่นอน)
Adam Haun

ซึ่งโดยทั่วไปจะเกิดขึ้นเนื่องจากรหัสที่คุณเลือกที่จะเชื่อมโยงเพื่อสนับสนุนบอร์ดของคุณ แต่ไม่ว่าคุณจะเปลี่ยนแปลงโดยการกำหนดฟังก์ชั่นการส่งออกของคุณเอง ปัญหาเกี่ยวกับข้อเสนอของคุณก็คือว่ามันไม่ได้ขึ้นอยู่กับการทำความเข้าใจว่าห้องสมุดเป็นความหมายที่จะใช้ - มากกว่าทำสิ่งที่คุณกำลังเสนอกระบวนการหลายขั้นตอนสำหรับแต่ละ ouput ซึ่งในสิ่งอื่น ๆ จะทำให้ระเบียบของแบบพกพาที่มีอยู่ รหัสที่ทำในสิ่งปกติ
Chris Stratton

STM32 เป็นสภาพแวดล้อมอิสระ คอมไพเลอร์ไม่จำเป็นต้องจัดเตรียม stdio.h - เป็นทางเลือกทั้งหมด แต่ถ้ามันไม่ให้ stdio.h ก็ต้องให้ทั้งหมดของห้องสมุด คอมไพเลอร์ระบบอิสระที่ใช้งาน printf มักจะทำหน้าที่เป็นการสื่อสารแบบ UART
Lundin

1

สำหรับผู้ที่ดิ้นรนให้เพิ่มสิ่งต่อไปนี้ใน 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);
}

เชื่อมต่อกับพอร์ต COM ของคุณผ่านผงสำหรับอุดรูและคุณควรจะดี


1

หากคุณต้องการแนวทางที่แตกต่างและไม่ต้องการเขียนทับฟังก์ชั่นหรือประกาศของคุณเองคุณสามารถใช้snprintfเพื่อบรรลุเป้าหมายเดียวกัน แต่มันก็ไม่หรูหรา

char buf[100];
snprintf(buf, 100, "%X %X", val1, val2);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 1000);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.