2024-02-04 04:11 AM
Hi!
I have STM32F469ZGT6 which is having problems with printf function.
If I connect debugger probe STLINK-V3SET all my code runs OK.
If I disconnect probe my code does not run at all. It does not even start to run.
After further investigation, I came to conclusion that my MCU does not like prinf.
So after I commented out all printfs from my code, everything started working in standalone mode (without connected probe).
Code was created with CubeMX, with KEIL toolchain.
I have stripped my code to bare bone which demonstrates the issue.
This is my main function:
….
#include "stdio.h"
…..
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART6_UART_Init();
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, GPIO_PIN_SET);
while (1)
{
printf("TEST_");
HAL_Delay(1000);
}
}
……
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart6, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
This line of code:
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, GPIO_PIN_SET);
…. turns on a LED on a PCB.
If MCU is running in standalone mode, LED is never lit. If I connect probe to MCU, LED gets lit and serial monitor sees test messages. If I comment out printf, LED is lit in standalone mode.
Huart6 is connected to STLINK-V3SET, which emulates VCP on PC. So, if probe is disconnected, there is no way of knowing whether serial traffic occurs. I only see LED is not lit.
Any suggestions what to do next?
2024-02-04 04:21 AM
Send some output directly via HAL_UART_Transmit() so you can confirm it starts.
Do something with LEDs and output in both Error_Handler and HardFault_Handler so you can observe if it dies into those.
BOOT0 pulled low? Clocks / PLLs start? As it works without printf() assuming so.
Check _write() code in syscalls.c
2024-02-04 11:32 AM
You wrote with debugger the "code runs OK", but do you actually see the text ("TEST_") being outputted on UART6?
I have some doubts because you seem to want to override fputc, but AFAIK printf does not call that. You should override/overwrite _write instead like @Tesla DeLorean suggested.
Also, depending on which libc you are using, printf and underlying libc functions may require heap memory or a working RAM buffer. Check linker file for heap. And you may have to provide buffer with setbuf.
2024-02-04 03:05 PM
First correction of something that is false. I said:
“Huart6 is connected to STLINK-V3SET, which emulates VCP on PC. So, if probe is disconnected, there is no way of knowing whether serial traffic occurs. I only see LED is not lit.”
This is not completely true. STLINK-V3SET has UART to USB bridge, which is independent of probe's main function, which is debugging (SWDIO, SWCLK and SWO). This means so long probe (Tx and Rx of UART to USB bridge) is connected to PCB, and my UART6 is connected to that bridge, I can see on my PC transmitted data from MCU, although MCU is not in debugging mode.
To answer Mikk Leini’s question. When I say “code runs OK”, this means I can see “TEST_” messages coming from MCU in debugging mode.
To address Tesla DeLorean’s comments. This is not a hardware issue (BOOT0 pin suggestion). If it were a hardware issue, with or without printf in standalone mode, there should be no difference. But there is a difference. With printf it does NOT work, without printf it DOES work.
Both of your answers got me thinking how does code arrive from printf to implemented fputc, and where is “weak” _write() which should be overridden.
Tesla DeLorean suggested to find it in syscalls.c. If I am not mistaken file syscalls.c is specific to GNU compiler, which is not used by KEIL. They have their own arm compiler.
I was not able to find “weak” _write() in all generated code.
Now comes the part which I can not fully explain:
….after some googling I found that KEIL has ability to redirect STDOUT stream. This is apparently done by ticking checkbox (red arrow) in RTE settings:
Above picture says variant “Breakpoint” – “Stop program execution at a breakpoint when using STDOUT”.
One can change variant to “User”:
…then it says “Redirect STDOUT to a user defined output target (USART…..”
This sound like something I need.
When I clicked checkbox, project gets supplemented with new retarget_io.c file:
https://wtools.io/paste-code/bTwp
….from that heap of new code this is part which deals with STDOUT:
/**
Put a character to the stdout
\param[in] ch Character to output
\return The character written, or -1 on write error.
*/
#if defined(RTE_Compiler_IO_STDOUT)
#if defined(RTE_Compiler_IO_STDOUT_User)
extern int stdout_putchar (int ch);
#elif defined(RTE_Compiler_IO_STDOUT_ITM)
static int stdout_putchar (int ch) {
return (ITM_SendChar(ch));
}
#elif defined(RTE_Compiler_IO_STDOUT_EVR)
static int stdout_putchar (int ch) {
static uint32_t index = 0U;
static uint8_t buffer[8];
if (index >= 8U) {
return (-1);
}
buffer[index++] = (uint8_t)ch;
if ((index == 8U) || (ch == '\n')) {
EventRecordData(EventID(EventLevelOp, 0xFE, 0x00), buffer, index);
index = 0U;
}
return (ch);
}
#elif defined(RTE_Compiler_IO_STDOUT_BKPT)
static int stdout_putchar (int ch) {
__asm("BKPT 0");
return (ch);
}
#endif
#endif
Now we can see what each “variant” means.
Breakpoint variant should lead to:
#elif defined(RTE_Compiler_IO_STDOUT_BKPT)
static int stdout_putchar (int ch) {
__asm("BKPT 0");
return (ch);
}
User variant leads to:
#if defined(RTE_Compiler_IO_STDOUT_User)
extern int stdout_putchar (int ch);
Now, if I choose “user” variant, compiling fails because linker can’t find function “stdout_putchar” in my code.
…..so If I choose “user” variant and rename fputc to stdout_putchar in main.c (effectively overloading function stdout_putchar from retarget_io.c) it should work? …well kinda. It has problem with buffers described here https://community.st.com/t5/stm32cubeide-mcus/printf-not-working-write-never-gets-called/td-p/276659
Adding “\r\n” on end of my original message “TEST_\r\n” fixed the problem.
…above mentioned thread also suggests using fflush() to circumvent \r\n issue.
Above suggestions seemed ugly, so I played with this a little bit more.
I chose variant “breakpoint” in RTE settings and changed back function name to “int fputc(int ch, FILE *f)”
…..and
printf("TEST_");
started behaving as expected. It transmits messages TEST_ as they come, each 1000 ms.
I can not explain why is this working.
I don’t know whether I have been clear enough, solution to my problem was ticking checkbox and leaving variant “breakpoint”, nothing else needs to be changed. Now I get “TEST_” in standalone mode (visible via UART to USB bridge).
2024-02-04 05:40 PM
Sorry, missed the Keil thing. Typically use this type of arrangement
#include <stdio.h>
//****************************************************************************
// Hosting of stdio functionality through USART
//****************************************************************************
/* Implementation of putchar (also used by printf function to output data) */
int SendChar(int ch) /* Write character to Serial Port */
{
HAL_UART_Transmit(&huart6, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
//ITM_SendChar(ch); // From core_cm4.c
return(ch);
}
//****************************************************************************
#include <rt_misc.h>
#if 0 // Version 5 compilers
#pragma import(__use_no_semihosting_swi)
struct __FILE { int handle; /* Add whatever you need here */ };
FILE __stdout;
FILE __stdin;
#endif
int fputc(int ch, FILE *f) { return (SendChar(ch)); }
int ferror(FILE *f)
{
/* Your implementation of ferror */
return EOF;
}
void _ttywrch(int ch) { SendChar(ch); }
void _sys_exit(int return_code)
{
label: goto label; /* endless loop */
}
//****************************************************************************