基于arm-none-eabi-gcc编译工具链的串口重定向printf

VSCode基于arm-none-eabi-gcc交叉编译工具链的串口重定向printf输出和Keil MDK基于armcc工具链的串口重定向printf输出是不一样的,我按照以下链接①使用 VSCode 给STM32配置一个串口 printf 工程配置GCC的printf输出,以上解决了MCU—>电脑串口接收的问题,电脑串口—>MCU的发送问题还得需要拓展一下,可参考链接②基于 VsCode + GCC + STM32 环境下的串口输入输出重定向

我这边的配置思路是:

1.修改Makefile脚本

该步骤是为了解决无法使用 printf 和 sprintf 打印浮点数的问题。
根据第1位作者,稍微修改Makefile,可能有人问了:怎么修改Makefile文件呀,修改哪里呀?不要慌,去参考上面的第1个作者链接,打开你工程的Makefile文件,快捷键按下trl+F搜索 LDFLAGS ,然后添加下图的红方框内容,
在这里插入图片描述

顺便附上这段源码:

# libraries
LIBS = -lc -lm -lnosys
LIBDIR =
LDFLAGS = $(MCU) -flto -u_printf_float -u_scanf_float -specs=nano.specs -specs=nosys.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections,--print-memory-usage,--no-warn-rwx-segments

2.引入syscalls.c源文件

在工程目录下的HARDWARE文件夹下建立一个syscalls.c源文件,复制下述的syscalls.c代码,粘贴代码到新建立的syscalls.c源文件。
在这里插入图片描述
syscalls.c:

/**
   Support files for GNU libc.  Files in the system namespace go here.
   Files in the C namespace (ie those that do not start with an
   underscore) go in .c.  
**/

/**
 * @file      syscalls.c
**/

/* Includes */
#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <sys/times.h>
#include "usart1.h"

/* Variables */
// #undef errno
extern int errno;
extern int __io_putchar(int ch) __attribute__((weak));
extern int __io_getchar(void) __attribute__((weak));

register char *stack_ptr asm("sp");

char *__env[1] = {0};
char **environ = __env;

/* Functions */
void initialise_monitor_handles()
{
}

int _getpid(void)
{
	return 1;
}

int _kill(int pid, int sig)
{
	errno = EINVAL;
	return -1;
}

void _exit(int status)
{
	_kill(status, -1);
	while (1)
	{
	} /* Make sure we hang here */
}

/* Functions */
__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;
}


// 条件编译
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#define GETCHAR_PROTOTYPE int __io_getchar(void)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#define GETCHAR_PROTOTYPE int fgetc(FILE *f)
#endif /* __GNUC__ */

/* 使用GCC编译器时,调用 _write 和 _read */
/* #ifdef __GNUC__ */
/**
  * 函数功能: 重定向 c库函数 printf到 DEBUG_USARTx
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF); //阻塞式无限等待
  
  
  ## 注释:如使用STM32标准库,请注释掉上述的HAL_UART_Transmit(),使用该代码段。
  ## 注释:下述宏定义USART1存在于头文件#include "stm32f4xx.h",故将顶部头文件包含的 #include "usart1.h" 替换成 #include "stm32f4xx.h"# while (!USART_GetFlagStatus(USART1, USART_FLAG_TXE)) // 假如没有收到
  # {
  # }
  # USART_SendData(USART1, (u8)ch);
  
  ## 注释:如使用STM32标准库,请注释掉上述的HAL_UART_Transmit(),使用该代码段。
   
  return ch;
}

/**
  * 函数功能: 重定向 c库函数 getchar,scanf到 DEBUG_USARTx
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:无
  */
GETCHAR_PROTOTYPE
{
  uint8_t ch = 0;
  HAL_UART_Receive(&huart1, &ch, 1, 0xFFFF);
  
  
  # 注释:如使用STM32标准库,请注释掉上述的HAL_UART_Receive(),使用该代码段。
  # 注释:下述宏定义USART1存在于头文件#include "stm32f4xx.h",故将顶部头文件包含的 #include "usart1.h" 替换成 #include "stm32f4xx.h"# uint8_t ch = 0;
  # while (!USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
  # {
  # }
  # USART_ReceiveData(USART1);
  
  # 注释:如使用STM32标准库,请注释掉上述的HAL_UART_Receive(),使用该代码段。
  
  return ch;
}


/* 非GCC模式才允许编译使用,即使用 Keil、IAR 的工具链等,调用 fputc 和 fgetc */
#ifndef __GNUC__

/**
 * @brief 重定向 C 标准库 printf 函数到串口 huart1
 * 适用于 Keil、IAR 等IDE;不适用 GCC
 * @usage printf("USART1_Target:\r\n");
 */
PUTCHAR_PROTOTYPE
{
    //采用轮询方式发送1字节数据,超时时间为无限等待
    HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,HAL_MAX_DELAY); //huart1是串口的句柄 
    return ch;
}

/**
 * @brief fgets 重定向
 * 重定向 C 标准库 scanf 函数到串口 huart1
 * 注意以 空格 为结束
 * @param f 
 * @return int 
 * 
 * @usage scanf("%c", &RecData);
 */
GETCHAR_PROTOTYPE
{
  uint8_t ch;
  HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); //huart1是串口的句柄 
  return ch;
}

#endif /* __GNUC__ */

caddr_t _sbrk(int incr)
{
	extern char end asm("end");
	static char *heap_end;
	char *prev_heap_end;

	if (heap_end == 0)
		heap_end = &end;

	prev_heap_end = heap_end;
	if (heap_end + incr > stack_ptr)
	{
		//		write(1, "Heap and stack collision\n", 25);
		//		abort();
		errno = ENOMEM;
		return (caddr_t)-1;
	}

	heap_end += incr;

	return (caddr_t)prev_heap_end;
}

int _close(int file)
{
	return -1;
}

int _fstat(int file, struct stat *st)
{
	st->st_mode = S_IFCHR;
	return 0;
}

int _isatty(int file)
{
	return 1;
}

int _lseek(int file, int ptr, int dir)
{
	return 0;
}

int _open(char *path, int flags, ...)
{
	/* Pretend like we always fail */
	return -1;
}

int _wait(int *status)
{
	errno = ECHILD;
	return -1;
}

int _unlink(char *name)
{
	errno = ENOENT;
	return -1;
}

int _times(struct tms *buf)
{
	return -1;
}

int _stat(char *file, struct stat *st)
{
	st->st_mode = S_IFCHR;
	return 0;
}

int _link(char *old, char *new)
{
	errno = EMLINK;
	return -1;
}

int _fork(void)
{
	errno = EAGAIN;
	return -1;
}

int _execve(char *name, char **argv, char **env)
{
	errno = ENOMEM;
	return -1;
}

上述笔者贴出的syscalls.c系统调用文件,是在ST官方的 syscalls.c原文件基础上,增加了__GNUC__和非__GNUC__的条件编译代码。
syscalls.c官方原文件获取方式:
① 打开ST官网—STM32CubeF1,滑动到最底部,点击Get from GitHub在这里插入图片描述
② 下载STM32CubeF1-1.8.6.zip后将其解压缩;在这里插入图片描述
③ 根据以下路径【.\STM32CubeF1-1.8.6\Projects\STM32F103RB-Nucleo\Examples\UART\UART_Printf\SW4STM32】,可获取到syscalls.c文件。

添加完syscalls.c源文件后,我们是不是要将这个syscalls.c源文件引入Makefile脚本,让脚本构建、编译文件?
所以使用鼠标右键复制工程目录下syscalls.c文件的相对路径,再打开Makefile脚本,快捷键ctrl+F搜索找到下图的C_SOURCES位置,将相对路径粘贴到红色箭头处,即可成功引入源文件到Makefile,如下图所示。
到这里可能有人会问了:为什么要将新建的.c源文件引入Makefile呢?
好的,这里请参考我写的另一篇笔记:VSCode+arm-none-eabi-gcc交叉编译+Makefile构建+OpenOCD(基于STM32的HAL库)
在这里插入图片描述
接下来我们开始编译……嗯……想必过程不会如此顺利,果不其然地会报错,如下图。
在这里插入图片描述

报了syscalls.c中没有定义结构体句柄huart1的错误,这里先附上我的usart1.c和usart1.h代码:
usart1.c:

#include "usart1.h"

UART_HandleTypeDef huart1;

/*************************************************************************************************
 *	函 数 名:	HAL_UART_MspInit
 *	入口参数:	huart - UART_HandleTypeDef定义的变量,即表示定义的串口
 *	返 回 值:	无
 *	函数功能:	初始化串口引脚
 *	说    明:	无
 *************************************************************************************************/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    if (huart->Instance == USART1)
    {
        __HAL_RCC_USART1_CLK_ENABLE(); // 开启 USART1 时钟

        GPIO_USART1_TX_CLK_ENABLE; // 开启 USART1 TX 引脚的 GPIO 时钟
        GPIO_USART1_RX_CLK_ENABLE; // 开启 USART1 RX 引脚的 GPIO 时钟

        GPIO_InitStruct.Pin = USART1_TX_PIN;          // TX引脚
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;       // 复用推挽输出
        GPIO_InitStruct.Pull = GPIO_PULLUP;           // 上拉
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 速度等级

        HAL_GPIO_Init(USART1_TX_PORT, &GPIO_InitStruct);

        GPIO_InitStruct.Pin = USART1_RX_PIN; // RX引脚
        HAL_GPIO_Init(USART1_RX_PORT, &GPIO_InitStruct);
    }
}

/*************************************************************************************************
 *	函 数 名:	USART1_Init
 *	入口参数:	无
 *	返 回 值:	无
 *	函数功能:	初始化串口配置
 *	说    明:	无
 *************************************************************************************************/
void USART1_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = USART1_BaudRate;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;

    while (HAL_UART_Init(&huart1) != HAL_OK)
    {
    }
}

usart1.h:

#ifndef __USART1_H
#define __USART1_H

#include <stdio.h>
#include "stm32f1xx_hal.h"


/*-------------------------------------------- USART配置宏 ---------------------------------------*/

#define USART1_BaudRate 115200

#define USART1_TX_PIN GPIO_PIN_9                               // TX 引脚
#define USART1_TX_PORT GPIOA                                   // TX 引脚端口
#define GPIO_USART1_TX_CLK_ENABLE __HAL_RCC_GPIOA_CLK_ENABLE() // TX 引脚时钟

#define USART1_RX_PIN GPIO_PIN_10                              // RX 引脚
#define USART1_RX_PORT GPIOA                                   // RX 引脚端口
#define GPIO_USART1_RX_CLK_ENABLE __HAL_RCC_GPIOA_CLK_ENABLE() // RX 引脚时钟

/*---------------------------------------------- 函数声明 ---------------------------------------*/

void USART1_Init(void); // USART1初始化函数

#endif

我在源文件里面已经定义结构体句柄UART_HandleTypeDef huart1;了,之所以报错,是因为syscalls.c是个源文件,没有引入huart1这个结构体句柄,我们需要在syscalls.c里面包含 #include “usart1.h” ,
在这里插入图片描述
同时在头文件usart1.h里面extern结构体句柄huart1,如下图所示
在这里插入图片描述

这样句柄huart1就引入syscalls.c里面了,最终在main.c里写上测试代码,看看能否打印 整型 和 单/双精度浮点型 数据(实验归实验,大家还是少用extern,因为这样会提高程序的耦合性,后续难以移植)
main.c:

int main(void)
{
  uint16_t a = 128;             // 测试整形变量
  float b = 9.123456;           // 测试单精度浮点型变量,小数点后6位
  double c = 3.141592653589793; // 测试双精度浮点型变量,小数点后15位
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  USART1_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */
  printf("STM32 串口实验 \r\n");
  printf("printf函数测试 \r\n");
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    // cnt--;
    // while (!cnt)
    // {
    //   cnt = 0;
    // }

    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_0);
    HAL_Delay(500);
    /* USER CODE END WHILE */
    printf("十进制格式:  %d\r\n", a);
    printf("十六进制格式:%x\r\n", a);
    printf("单精度小数格式:    %f\r\n", b);
    printf("双精度小数格式:    %.15lf\r\n", c);
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

对整个工程编译,到这里就是大家最喜欢看到的0 error,0 warning了,下载程序,观察到实验现象,结束。后面就可以开开心心地使用printf打印信息了。
在这里插入图片描述
在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值