Andrew Ressa
Andrew Ressa

Reputation: 11

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.

Upvotes: 1

Views: 2946

Answers (2)

stoeffel83
stoeffel83

Reputation: 1

The redirection of the std streambuffer function (like _write()) affects other stream buffer treatments, e.g. if you use an ostraem somewhere else in the project...

Hence my approach: create a separate stream buffer class in which the write event function is handled according to your need. When the constructor is called, forward the ostream to be modified and overwrite it's stream buffer. The disadvantage is that the original stream buffer remains as an unused static memory allocation.

template <class Elem = char, class Tr = std::char_traits<Elem> >
    class redirectostream : public std::basic_streambuf<Elem, Tr>
{
public:
    redirectostream(std::ostream &streamToChange)
    {
        //redirect the ostream
        streamToChange.rdbuf(this);
    };

    std::streamsize xsputn(const Elem *_Ptr, std::streamsize _Count)
    {
        HAL_StatusTypeDef status = HAL_UART_Transmit_IT(&huart1, _Ptr, _Count);
        return HAL_OK == status ? _Count: 0;
    }
};

//redirect std::cout
redirectostream<> redirectCoutObj(std::cout);

Once the UART is initialized, you can use std:cout as you are used to.

#include <iostream>

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_USART2_UART_Init();

    std::cout << "Hallo World!";

    while (1)
    {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
        HAL_Delay(500);
    }
}

Upvotes: 0

Andrew Ressa
Andrew Ressa

Reputation: 11

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;

Upvotes: 0

Related Questions