在STM32中使用printf()和scanf

在STM32中使用printf()和scanf

本文出处: https://shawnhymel.com/1873/how-to-use-printf-on-stm32/

作者:Shawn Hymel

内容来源于网络,本文只做翻译和学习,已获得原文作者许可转载,欢迎访问原文章。

赶时间可以点这里直接跳转到使用方法

这里是我做的一篇总结,不管你使用原生的keil开发,还是利用vscode插件或者clion工具开发都能使用。

本文基于STM32CubeIDE,其他编译器也可以使用。

一般来说,大多数微控制器没有控制台的概念。而在调试时将信息输出到某种控制台非常重要。一种方式是在STM32CubeIDE 中使用半主机。但是半主机可能会很慢。
另一个选择是通过串口 (UART) 输出调试信息。我们可以调用STM32的HAL函数(例如HAL_UART_Transmit)来使用串口。
但是,如果可以使用C标准库中的printf、scanf等会更方便。因此需要重新编写底层函数。

注意: 本文的代码摘自 Carmine Noviello 的Mastering STM32 书。我只是更新了如何在 STM32Cube 中重新配置的过程。所有功劳都归功于 Carmine Noviello 的代码。如果您愿意,欢迎您编写自己的 retarget.h 和 retarget.c 文件。

新建一个默认的 STM32 工程,配置启用 UART,保存并生成代码。

第一件事是禁用 syscalls.c。这个文件定义了许多函数名与我们将要创建的相同。编译时禁用它可以避免出现“多重定义”错误。例如:

multiple definition of `_isatty'

右键单击 syscalls.c文件并选择 Properties。在 C/C++ Build > Settings 下,选中 Exclude resource from build


单击应用并关闭。工程文件资源管理器中的文件名现在应该是灰色的。

这样我们就可以在 STM32CubeIDE 中编译和链接时不包含文件(或文件夹)。该过程与大多数其他基于 Eclipse 的 IDE 相似。

Inc目录中创建一个名为 retarget.h的新文件 。将以下代码复制到此文件中并保存:

// All credit to Carmine Noviello for this code
// https://github.com/cnoviello/mastering-stm32/blob/master/nucleo-f030R8/system/include/retarget/retarget.h

#ifndef _RETARGET_H__
#define _RETARGET_H__

#include "stm32l4xx_hal.h"
#include <sys/stat.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);

#endif //#ifndef _RETARGET_H__

Src目录中创建一个名为 retarget.c的新文件 。将以下代码复制到此文件中并保存:

// All credit to Carmine Noviello for this code
// https://github.com/cnoviello/mastering-stm32/blob/master/nucleo-f030R8/system/src/retarget/retarget.c

#include <_ansi.h>
#include <_syslist.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/times.h>
#include <limits.h>
#include <signal.h>
#include <../Inc/retarget.h>
#include <stdint.h>
#include <stdio.h>

#if !defined(OS_USE_SEMIHOSTING)

#define STDIN_FILENO  0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2

UART_HandleTypeDef *gHuart;

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. */
     /* 禁用 STDOUT 流的 I/O 缓冲,以便
   * 字符在打印后立即发送出去。*/ 
  setvbuf(stdout, NULL, _IONBF, 0);
}

int _isatty(int fd) {
  if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
    return 1;

  errno = EBADF;
  return 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;
}

int _close(int fd) {
  if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)
    return 0;

  errno = EBADF;
  return -1;
}

int _lseek(int fd, int ptr, int dir) {
  (void) fd;
  (void) ptr;
  (void) dir;

  errno = EBADF;
  return -1;
}

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

  if (fd == STDIN_FILENO) {
    hstatus = HAL_UART_Receive(gHuart, (uint8_t *) ptr, 1, HAL_MAX_DELAY);
    if (hstatus == HAL_OK)
      return 1;
    else
      return EIO;
  }
  errno = EBADF;
  return -1;
}

int _fstat(int fd, struct stat* st) {
  if (fd >= STDIN_FILENO && fd <= STDERR_FILENO) {
    st->st_mode = S_IFCHR;
    return 0;
  }

  errno = EBADF;
  return 0;
}

#endif //#if !defined(OS_USE_SEMIHOSTING)

您的工程目录结构应如下所示:

注意,我们添加了 retarget.hretarget.c。并且不编译 syscalls.c 文件 ,但该文件仍然存在于我们的目录中。

如果使用的不是STM32CubeIDE,没有设置屏蔽编译syscall.c文件,编译可能会报错重定义,只需要把syscall.c文件中重名的函数删掉即可。

main.c文件中包含 stdio.hretarget.h 头文件,就可以使用 printf 和 scanf了,如下所示:

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "retarget.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  char buf[100];
  /* 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();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  RetargetInit(&huart2);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    printf("\r\nYour name: ");
    scanf("%s", buf);
    printf("\r\nHello, %s!\r\n", buf);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

// ***REST OF INITIALIZATION CODE NOT SHOWN***

编译此项目并在 STM32CubeIDE 中打开调试窗口。打开串口调试软件,连接到单片机的 COM 口(波特率为 115200,8-N-1)。进入轮询后应该会对你表示欢迎。输入一些回复并按enter
注意,你将无法看到自己输入的内容。但是,程序会把你输入的内容添加加到“Hello”字符串后并输出。

!还有一点,尽量不要溢出分配的字符缓冲区,否则会发生一些莫名的错误。可以在代码中编写某种越界检查。无论如何,这只是一个开始,希望能帮助你调试 STM32 项目。

修改时间:2021.12.14

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在STM32F405微控制器上使用scanf函数,首先需要确保已经配置好了适当的UART串口。以下是一个基本的示例代码,演示如何使用scanf函数从串口接收数据: ```c #include "stm32f4xx.h" #include <stdio.h> // 配置串口 void UART_Configuration(void) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; // 使能USART2和GPIOA的时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // 配置UART引脚 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStruct); // 将引脚复用为USART2 GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2); // 配置USART2 USART_InitStruct.USART_BaudRate = 9600; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_Parity = USART_Parity_No; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, &USART_InitStruct); // 使能接收断 USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); // 配置断优先级 NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); // 使能USART2 USART_Cmd(USART2, ENABLE); } // 接收数据断处理函数 void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) { static uint8_t data = 0; data = USART_ReceiveData(USART2); // 处理接收到的数据 // ... } } int main(void) { // 初始化系统 SystemInit(); // 配置串口 UART_Configuration(); while (1) { char input_str[128]; printf("Enter a string: "); scanf("%s", input_str); printf("You entered: %s\r\n", input_str); } } ``` 这个示例代码配置了USART2作为串口,并使用printfscanf函数进行串口输入输出。在主循环,首先提示用户输入字符串,然后使用scanf函数读取用户输入的字符串,并通过printf函数将其打印出来。 请注意,以上代码仅为示例,实际应用可能需要根据具体情况进行适当的修改和完善。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值