【STM32学习5】STM32使用printf函数 打印到电脑串口助手


本文所使用的方法与代码参考自正点原子,如果想要详细了解这方面的知识,请阅读正点原子官方提供的文档。


一、背景

在开发STM32应用时,将一些信息通过串口打印到电脑上是常用的调试手段。C语言标准库中的printf函数是我们常用的打印函数。但是在STM32应用下一般无法直接使用这个函数,正点原子给出的解释如下,有兴趣可以详细了解一下。

标准库下的 printf 为调试属性的函数,如果直接使用,会使单片机进入半主机模式(semihosting),这是一种调试模式,直接下载代码后出现程序无法运行,但是在连接调试器进行 Debug 时程序反而能正常工作的情况。半主机是 ARM 目标的一种机制,用于将输入/输出请求从应用程序代码通信到运行调试器的主机。例如,此机制可用于允许 C 库中的函数(如 printf()和 scanf())使用主机的屏幕和键盘,而不是在目标系统上设置屏幕和键盘。这很有用,因为开发硬件通常不具有最终系统的所有输入和输出设备,如屏幕、键盘等。半主机是通过一组定义好的软件指令(如 SVC)SVC 指令(以前称为 SWI 指令)来实现的,这些指令通过程序控制生成异常。应用程序调用相应的半主机调用,然后调试代理处理该异常。调试代理(这里的调试代理是仿真器)提供与主机之间的必需通信。也就是说使用半主机模式必须使用仿真器调试。
如果想在独立环境下运行调试功能的函数,我们这里是 printf,printf 对字符 ch 处理后写入文件 f,最后使用 fputc 将文件 f 输出到显示设备。对于 PC 端的设备,fputc 通过复杂的源码,最终把字符显示到屏幕上。那我们需要做的,就是把 printf 调用的 fputc 函数重新实现,重定向fputc 的输出,同时避免进入半主模式。

目前想要在SMT32上使用printf有两种方法:

  1. 通过代码取消ARM的半主机工作模式,并重定向printf函数
  2. 使用微库MicroLib,并重定向printf函数。

由于微库裁剪了许多标准库的功能,如果注重功能完整性建议使用第一种方法。

二、取消ARM的半主机工作模式

添加stdio.h头文件,并在程序中加入以下代码段即可(代码引自正点原子)

/******************************************************************************************/

/* 在合适的位置引用下面头文件 */
#include <stdio.h>

/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */

#if 1
#if (__ARMCC_VERSION >= 6010050)                    /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t");          /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t");            /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */

#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)

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

#endif

/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
    ch = ch;
    return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

char *_sys_command_string(char *cmd, int len)
{
    return NULL;
}

/* FILE 在 stdio.h里面定义. */
FILE __stdout;

/* 重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 
其中串口可根据实际使用情况调整 */
int fputc(int ch, FILE *f)
{
    while ((USART1->SR & 0X40) == 0);             /* 等待上一个字符发送完成 */

    USART1->DR = (uint8_t)ch;                     /* 将要发送的字符 ch 写入到DR寄存器 */
    return ch;
}
#endif
/***********************************************END*******************************************/

上面代码段使用的是串口1(USART1),可根据实际使用情况调整。

三、使用微库MicroLib

直接在Keil中的如下界面勾选使用微库
Keil中使用MicroLib
并添加如下代码段重定向fputc

/* 在合适的位置引用下面头文件 */
#include <stdio.h>

/* 重定义 fputc 函数, printf 函数最终会通过调用 fputc 输出字符串到串口 */
/* 串口可根据实际使用情况调整 */
int fputc(int ch, FILE *f)
{
 while ((USART1->ISR & 0X40) == 0); /* 等待上一个字符发送完成 */
 USART1->TDR = (uint8_t)ch; /* 将要发送的字符 ch 写入到 DR 寄存器 */
 return ch;
}

微库由于裁剪掉了一些功能,有着如下特点:

  • 微库会优化代码空间,但会降低某些程序的执行效率(比如: memcpy()),效率换空间
  • 微库不支持浮点运算,所以在有FPU单元的MCU上,使用MicroLIB并开启FPU会让程序死机或跑飞
  • 微库不支持C++,在使用C++开发MCU时不能使用MicroLib
  • 微库不支持操作系统函数

更详细的讲解可参见博文STM32程序不运行与MicroLIB讲解

四、应用

采用了上面任意一种方法设置后,我们便可在程序中使用printf,并通过串口打印在电脑端的串口助手上。

		printf("123\r\n");
		
		HAL_Delay(500);

在这里插入图片描述

stm32printf函数可以用来在STM32微控制器上通过串口输出数据。具体步骤如下: 1. 首先需要在代码中引入相关的库文件,如下: #include "stdio.h" #include "string.h" #include "stdarg.h" #include "stm32f10x_usart.h" #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" 2. 然后需要进行串口的初始化,如下: void USART_Config(void) { USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); // USART1_TX PA.9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); // USART1_RX PA.10 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); } 3. 接下来就可以使用stm32printf函数进行输出了,如下: void stm32printf(const char* fmt, ...) { char buf[256]; va_list args; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); int len = strlen(buf); for (int i = 0; i < len; i++) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, buf[i]); } } 4. 最后在需要输出的地方调用stm32printf函数即可,如下: stm32printf("Hello, world!\r\n"); 注意事项: 1. 如果使用printf函数进行输出,则需要在上面的代码中添加以下语句进行重定向: int fputc(int ch, FILE* f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, (uint8_t)ch); return ch; } 2. 在使用stm32printf函数进行输出时,需要注意串口的波特率和串口接口的配置要与代码中的配置相符。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值