STM32에서 printf 기능을 어떻게 사용합니까?


19

printf 함수를 사용하여 직렬 포트에 인쇄하는 방법을 알아 내려고합니다.

현재 설정은 STM32Fube 디스커버리 보드 가있는 STM32CubeMX 생성 코드 및 SystemWorkbench32입니다 .

stdio.h에서 printf 프로토 타입이 다음과 같이 정의되어 있음을 알 수 있습니다.

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

무슨 뜻인가요? 이 함수 정의의 정확한 위치는 어디에 있습니까? 이런 종류의 함수를 사용하여 출력하는 방법을 찾는 일반적인 관점은 무엇입니까?


5
표준 출력을 here 과 같은 직렬 포트로 보내려면 자체 "int _write (int file, char * ptr, int len)"을 작성해야한다고 생각합니다 . 나는 이것이 일반적으로 "시스템 호출 리매핑"을 처리하는 Syscalls.c라는 파일에서 수행된다고 생각합니다. 인터넷 검색 "Syscalls.c"를 사용해보십시오.
Tut

1
이것은 전자 문제가 아닙니다. 컴파일러 라이브러리의 매뉴얼을 참조하십시오.
Olin Lathrop

세미 호스팅 (디버거를 통한) 또는 일반적인 printf를 통해 printf 디버깅을 하시겠습니까?
Daniel

@Tut이 말했듯이 _write()기능은 내가 한 일입니다. 내 답변에 대한 자세한 내용은 아래를 참조하십시오.
cp.engr

이 훌륭한 튜토리얼이었다 youtu.be/Oc58Ikj-lNI
조셉 PIGHETTI

답변:


19

이 페이지에서 STM32F072에 대한 첫 번째 방법을 얻었습니다.

http://www.openstm32.org/forumthread1055

거기에 명시된 바와 같이

printf (및 다른 모든 콘솔 지향 stdio 함수)가 작동하는 방식은 _read()and와 같은 저수준 I / O 함수의 사용자 정의 구현을 만드는 것입니다 _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 라이브러리와 함께 구현됩니다. 위 함수 중 하나라도 자신의 코드에 선언 된 경우 대체 루틴은 라이브러리의 선언을 무시하고 기본 (비 기능적) 루틴 대신 사용됩니다.

USTM1 ( huart1)을 직렬 포트로 설정하기 위해 STM32CubeMX를 사용했습니다 . 을 원했기 때문에 다음과 같이 함수 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);
}

IDE가 아닌 Makefile을 사용하면 어떻게됩니까? 내 Makefile 프로젝트에서 작동하지 않는 것이 있습니다. 누구든지 도울 수 있습니까?
Tedi

@Tedi, GCC를 사용하고 있습니까? 이것은 컴파일러 고유의 답변입니다. 무엇을 시도했으며 결과는 어떻습니까?
cp.engr

예, GCC를 사용합니다. 나는 문제를 발견했다. _write () 함수가 호출되었습니다. 문제는 문자열을 보내는 코드에 있습니다. Segger의 RTT 라이브러리를 잘못 사용했지만 이제는 정상적으로 작동합니다. 감사. 모두 지금 일하고 있습니다.
Tedi

4

_EXFUN은 매크로로, 컴파일러에게 printf와 호환되는지 format-string을 검사하고 printf에 대한 인수가 형식 문자열과 일치하는지 확인해야한다는 흥미로운 지시문을 포함하고 있습니다.

printf 사용법을 배우려면 매뉴얼 페이지 와 좀 더 인터넷 검색을 제안합니다 . 마이크로에서 사용하도록 전환하기 전에 printf를 사용하는 간단한 C 프로그램을 작성하고 컴퓨터에서 실행하십시오.

흥미로운 질문은 "인쇄 된 텍스트는 어디에 있습니까?"입니다. 유닉스 계열 시스템에서는 "표준 출력"으로 진행되지만 마이크로 컨트롤러에는 그러한 것이 없습니다. CMSIS 디버그 라이브러리는 armf 세미 호스팅 디버그 포트 (예 : gdb 또는 openocd 세션)에 printf 텍스트를 보낼 수 있지만 SystemWorkbench32가 무엇을할지 모르겠습니다.

디버거에서 작업하지 않는 경우 sprintf를 사용하여 인쇄하려는 문자열을 포맷 한 다음 직렬 포트 또는 연결된 디스플레이로 해당 문자열을 보내는 것이 더 합리적입니다.

주의 : printf 및 관련 코드는 매우 큽니다. 32F407에서는 그다지 중요하지 않지만 플래시가 거의없는 장치에서는 실제 문제입니다.


IIRC _EXFUN은 내보낼 함수, 즉 사용자 코드에서 사용되는 함수를 표시하고 호출 규칙을 정의합니다. 대부분의 (내장) 플랫폼에서 기본 호출 규칙을 사용할 수 있습니다. 그러나 동적 라이브러리가있는 x86에서는 __cdecl오류를 피하기 위해 함수를 정의 해야합니다. 적어도 newlib / cygwin에서 _EXFUN은 설정에만 사용됩니다 __cdecl.
erebos

4

@AdamHaun의 대답은 필요한 모든 sprintf()것입니다. 문자열을 만든 다음 보내기가 쉽습니다. 그러나 실제로 자신의 printf()함수를 원한다면 Variable Argument Functions (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 Utility와 함께 ST-Link, SWD 디버거를 사용하는 것입니다. 그리고 SWO 뷰어를 통해 Printf를 사용 하십시오. 여기는 ST-Link Utility 의 매뉴얼입니다 . 관련 부분은 31 페이지에서 시작합니다.

SWO Viewer를 통한 Printf는 SWO를 통해 대상에서 전송 된 printf 데이터를 표시합니다. 실행중인 펌웨어에 대한 유용한 정보를 표시 할 수 있습니다.


당신은 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 통신으로 사용하는 경향이 있습니다.
룬딘

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.