环境配置

使用STM32CubeMX初始化项目

IDE:JetBrains CLion

编译器:arm-none-eabi-gcc

Tools:CMake、Openocd

提要

在使用gcc环境开发意法单片机,重定向printf函数时的一些思考。

实现

在/Core/Src/usart.c文件中添加

/* USER CODE BEGIN 1 */
#ifdef __GNUC__
// GCC
int __io_putchar(int ch)
{
  if (HAL_UART_Transmit(&huart1, (uint8_t*) &ch, 1, HAL_MAX_DELAY) != HAL_OK)
  {
    return -1;
  }
  return ch;
}

int __io_getchar(void) {
  uint8_t ch;
  if (HAL_UART_Receive(&huart1, &ch, 1, HAL_MAX_DELAY) != HAL_OK)
  {
    return -1;
  }
  return ch;
}
#endif
/* USER CODE END 1 */

原理

仅为个人阅读源码理解,欢迎讨论(没错这是一个叠甲)

在Core/Src/syscalls.c文件中可以看到如下的代码片段:

extern int __io_putchar(int ch) __attribute__((weak));
extern int __io_getchar(void) __attribute__((weak));

syscall.c文件中使用(weak)弱符号声明了两个全局函数,查看下面代码会看到如下的函数实现:

__attribute__((weak)) int _read(int file, char *ptr, int len)
{
  (void)file;
  int DataIdx;

  for (DataIdx = 0; DataIdx < len; DataIdx++)
  {
    *ptr++ = __io_getchar();
  }

  return len;
}

__attribute__((weak)) int _write(int file, char *ptr, int len)
{
  (void)file;
  int DataIdx;

  for (DataIdx = 0; DataIdx < len; DataIdx++)
  {
    __io_putchar(*ptr++);
  }
  return len;
}

这两个函数在非嵌入式环境下分别用来从标准输入设备中读取一个字符/向标准输出设备中输出一个字符,其具体实现依赖于操作系统及其设备驱动,在glibc(GUN C Library)中提供了更高级的输入输出函数,对_write_read__io_putchar__io_getchar 等函数进行了封装以实现实际的输入输出操作。

同理,在MDK环境下重定向printf的操作中,对fputcfgetc 函数进行重定向操作也是对底层实现的修改(fputc,fgetc是Windows平台下的函数实现)

一点缺点

使用HAL库的UART操作函数:HAL_UART_TransmitHAL_UART_Receive都是阻塞式操作,在使用FreeRTOS时,会导致串口数据处理错误,因此在使用FreeRTOS等裸机操作系统时应尽可能使用中断/DMA/Queue来处理数据避免发生错误。

后记

后续使用中在使用printf打印浮点数时发生错误,应在CMakeList.txt中添加

target_link_options(${CMAKE_PROJECT_NAME} PRIVATE
        -Wl,-u,_printf_float
        -Wl,-u,_scanf_float
)

以实现浮点数打印功能。