c++armstm32iostreamcout

STM32 C++ and Retargeting std::cout to UART


I'm having trouble trying to get std::cout working on an STM32 using the STM32CubeIDE (generally a standard install of the STM32CubeIDE out of the package).

I've reviewed many sources about redirecting UART for the purposes of stdio.h and printf, but am trying to get this all working in a C++ environment using std::cout. The prime source I've found is here: https://www.keil.com/support/man/docs/armlib/armlib_chr1358938931411.htm

I'm getting different errors depending on how and when I include headers, here is what I have tried:

retarget.h:

#ifndef _RETARGET_H__
#define _RETARGET_H__

#include "stm32f1xx_hal.h"
#include <sys/stat.h>
#include <stdio.h>

void RetargetInit(UART_HandleTypeDef *huart);

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


namespace std {

int fputc(int, FILE *);

}

#endif //#ifndef _RETARGET_H__

retarget.cc (snipped a bit) [Corrected that this is a c++ file]

void RetargetInit(UART_HandleTypeDef *huart) {
  gHuart = huart;

  /* Disable I/O buffering for STDOUT stream, so that
   * chars are sent out as soon as they are printed. */
  setvbuf(stdout, NULL, _IONBF, 0);
}

int _write(int fd, char* ptr, int len) {
  HAL_StatusTypeDef hstatus;

  if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
    hstatus = HAL_UART_Transmit(gHuart, (uint8_t *) ptr, len, HAL_MAX_DELAY);
    if (hstatus == HAL_OK)
      return len;
    else
      return EIO;
  }
  errno = EBADF;
  return -1;
}

namespace std {

struct __FILE
{
  int handle;
  /* Whatever you require here. If the only file you are using is */
  /* standard output using printf() for debugging, no file handling */
  /* is required. */
};
FILE __stdout;
FILE __stdin;
FILE __stderr;


int fputc(int c, FILE *stream)
{
      char tOut = c;

      return _write(STDOUT_FILENO, &tOut, 1);

  /* Your implementation of fputc(). */
}

}

and main.cpp (snipped a bit as well):

#include "retarget.h"
#include <iostream>

int main(void)
{
  /* HAL Init stuff Clipped */
  RetargetInit(&huart1);
  std::cout << "\n\nSTM32 main.c Startup\n" << std::endl;

  while(1){
      std::cout << "*";

      HAL_Delay(1000);
  }
}

If I go printf (change to and the std::cout to printf), everything works fine, so the _write function works correctly for sending to UART, so I know that much is working.

Now, on to the errors.

As presented, the complier throws:

In file included from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\ext\string_conversions.h:43,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\bits\basic_string.h:6557,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\string:55,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\bits\locale_classes.h:40,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\bits\ios_base.h:41,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\ios:42,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\ostream:38,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\iostream:39,
                 from ../Core/Src/main.cc:26:
c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\cstdio:111:11: error: 'int fputc(int, FILE*)' conflicts with a previous declaration
  111 |   using ::fputc;
      |           ^~~~~
In file included from ../Core/Src/main.cc:25:
../Core/Inc/retarget.h:23:5: note: previous declaration 'int std::fputc(int, FILE*)'
   23 | int fputc(int, FILE *);
      |     ^~~~~
make: *** [Core/Src/subdir.mk:41: Core/Src/main.o] Error 1 

If I flip the includes in my main.cc file so that iostream is pulled in first, I get:

In file included from ../Core/Src/main.cc:26:
../Core/Inc/retarget.h:23:22: error: 'int std::fputc(int, FILE*)' conflicts with a previous declaration
   23 | int fputc(int, FILE *);
      |                      ^
In file included from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\cstdio:42,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\ext\string_conversions.h:43,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\bits\basic_string.h:6557,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\string:55,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\bits\locale_classes.h:40,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\bits\ios_base.h:41,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\ios:42,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\ostream:38,
                 from c:{stm32 tools path snipped}arm-none-eabi\include\c++\10.3.1\iostream:39,
                 from ../Core/Src/main.cc:25:
c:{stm32 tools path snipped}arm-none-eabi\include\stdio.h:214:5: note: previous declaration 'int fputc(int, FILE*)'
  214 | int fputc (int, FILE *);
      |     ^~~~~
make: *** [Core/Src/subdir.mk:41: Core/Src/main.o] Error 1

Any suggestions? Thanks in advance.


Solution

  • Finally stumbled across the solution, and it came down to how the _write() function was compiled. This function must be compiled with a C compiler to work correctly (as far as I can tell).

    So, the solution, as far as is working for me:

    I renamed retarget.cc back to retarget.c (it remains retarget.c, unmodified except for the include path for retarget.h).

    For retarget.h I used the associated file for retarget.h wrapped the function prototypes in extern "C":"

    #ifndef _RETARGET_H__
    #define _RETARGET_H__
    
    #include "stm32f1xx_hal.h"
    #include <sys/stat.h>
    #include <stdio.h>
    
    #ifdef __cplusplus
    extern "C"
    {
    #endif
    
    void RetargetInit(UART_HandleTypeDef *huart);
    
    int _isatty(int fd);
    int _write(int fd, char* ptr, int len);
    int _close(int fd);
    int _lseek(int fd, int ptr, int dir);
    int _read(int fd, char* ptr, int len);
    int _fstat(int fd, struct stat* st);
    
    #ifdef __cplusplus
    } //extern "C"
    #endif
    
    #endif //#ifndef _RETARGET_H__
    

    Now everything is working as expected - std::cout << "Working now!" << std::endl;