VSCode基于GCC for ARM交叉编译工具链的串口重定向printf输出和keil基于ARMcc工具链的串口重定向printf输出是不一样的,我按照以下链接①使用 VSCode 给STM32配置一个串口 printf 工程配置GCC的printf输出,以上解决了MCU—>电脑串口接收的问题,电脑串口—>MCU的发送问题还得需要拓展一下,可参考链接②基于 VSCode + GCC + STM32 环境下的串口输入输出重定向
我这边的配置思路是:
1.修改Makefile脚本
根据第1位作者,稍微修改Makefile,可能有人问了:怎么修改Makefile文件呀,修改哪里呀?不要慌,去参考上面的第1个作者链接,打开你工程的Makefile文件,快捷键按下ctrl+F搜索 LDFLAGS
,然后添加下图的红方框内容,顺便附上这段源码:
#######################################
# LDFLAGS
#######################################
# link script
LDSCRIPT = STM32F103VETx_FLASH.ld
# libraries
LIBS = -lc -lm -lnosys
LIBDIR =
LDFLAGS = $(MCU) -u _printf_float -u _scanf_float -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections
2.引入syscall.c源文件
复制下述的syscall.c代码,在工程目录下的串口usart文件夹下创建一个syscall.c源文件,粘贴代码到syscall.c源文件。
syscall.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 */
extern int __io_putchar(int ch) __attribute__((weak));
extern int __io_getchar(void) __attribute__((weak));
/* 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__ */
/**
* 函数功能: 重定向 c库函数 printf到 DEBUG_USARTx
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF); //阻塞式无限等待
return ch;
}
/**
* 函数功能: 重定向 c库函数 getchar,scanf到 DEBUG_USARTx
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
GETCHAR_PROTOTYPE
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 0xFFFF);
return ch;
}
/* 非GCC模式才允许编译使用即 Keil、IAR 等 */
#ifndef __GNUC__
/**
* @brief 重定向 C 标准库 printf 函数到串口 huart1
* 适用于 Keil、IAR 等IDE;不适用 GCC
* @usage printf("USART1_Target:\r\n");
*/
int fputc(int ch, FILE *f)
{
//采用轮询方式发送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);
*/
int fgetc(FILE *f)
{
uint8_t ch;
HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); //huart1是串口的句柄
return ch;
}
#endif /* __GNUC__ */
添加完syscall.c源文件后,我们是不是要将这个syscall.c源文件引入Makefile脚本,让脚本构建、编译文件? 所以再打开Makefile脚本,鼠标右键复制syscall.c文件的相对路径,快捷键ctrl+F搜索找到下图的C_SOURCES位置,粘贴到红色箭头处,即可成功引入源文件到Makefile,如下图所示。到这里可能有人会问了:为什么要将新建的.c源文件引入Makefile呢?好,这里请参考我写的另一篇笔记:VSCode编辑+GCC for ARM交叉编译工具链+Makefile构建+OpenOCD调试(基于STM32的HAL库)
好了,接下来开始编译……嗯……想必不会如此顺利,果不其然地会报错,如下图。
报了syscall.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;了,之所以报错,是因为syscall.c是个源文件,没有引入huart1这个结构体句柄,我们可以直接在syscall.c里面包含 #include “usart1.h” ,
同时在头文件usart1.h里面extern结构体变量huart1,如图,
这样句柄huart1就引入syscall.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打印信息了。