【STM32系列】多路USART串口Printf重定向详解——通用
前言
通常情况下,标准的printf函数只能将输出重定向到一个串口。然而,当我们需要在多个串口上进行输出,而又不希望为每个串口单独封装发送函数时,就可以考虑将printf
的输出重定向到多个串口。接下来,本文将详细介绍如何实现这一目标。
注:
- 本篇文章的代码根据正点原子所提供代码改写。
- 正点原子所提供代码能在不使用半主机模式下能够正确编译和运行代码,这包括声明一些特殊的汇编指令和函数定义,以避免编译器默认使用半主机模式(不需要勾选 “Use MicroLIB”)。
- 本篇文章代码是基于CubeMX编写的HAL库,但是所涉及HAL库的内容不多,稍微修改,即可兼容标准库。
单个串口的printf重定向
单个串口的重定向,可以参考正点原子所提供代码,直接cope进自己工程中即可。
//usart.c
/******************************************************************************************/
/* 加入以下代码, 支持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 ((USART_UX->ISR & 0X40) == 0); /* 等待上一个字符发送完成 */
USART_UX->TDR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
#endif
/******************************************************************************************/
多串口的printf重定向
usart.h文件
对正点原子的源码进行了一点小的改动,首先在usart.h文件中添加了一个枚举类型,方便用来索引某个串口,用于printf定向:
其次增加了一个新的串口句柄,存储当前使用的USART句柄(标准库注意修改此处):
最后添加了一个当前串口的索引,用于存储当前使用的USART索引:
以下就是上面所说的在usart.h中所需要增加的代码:
//usart.h
/******************** 以下是多路USART串口printf重定向 ********************/
/* 定义USART索引枚举 */
typedef enum {
USART_NONE, /* 无USART */
USART1_IDX, /* USART1索引 */
USART2_IDX, /* USART2索引 */
USART3_IDX, /* USART3索引 */
}Current_USART_Indx;
extern UART_HandleTypeDef* Current_USART_Handle; /* 当前某个USART的句柄 */
extern Current_USART_Indx Current_USART_Printf_Indx; /* 当前某个USART的索引 */
void Set_Current_USART(Current_USART_Indx indx); /* 函数声明,用于设置当前使用的USART */
usart.c文件
Set_Current_USART
函数用于设置当前使用的USART。它接受一个Current_USART_Indx
类型的参数,并根据该参数更新Current_USART_Handle
句柄和Current_USART_Printf_Indx
索引:
/*
* 简介:设置当前使用的USART
* 参数:indx - 要设置的USART索引
* 这个参数可以是:USARTx_IDX,其中x可以从1~3
* 使用举例:(必须要将其放在printf函数前面,指定其中一个串口)
* Set_Current_USART(USART1_IDX);
* printf("我是串口1\r\n");
*/
void Set_Current_USART(Current_USART_Indx indx)
{
switch(indx)
{
case USART1_IDX:
Current_USART_Handle = &huart1;
Current_USART_Printf_Indx = USART1_IDX;
break;
case USART2_IDX:
Current_USART_Handle = &huart2;
Current_USART_Printf_Indx = USART2_IDX;
break;
case USART3_IDX:
Current_USART_Handle = &huart3;
Current_USART_Printf_Indx = USART3_IDX;
break;
default:
Current_USART_Handle = NULL;
Current_USART_Printf_Indx = USART_NONE;
break;
}
}
fputc
函数是printf
函数输出字符时调用的底层函数,进行了一些改变,使其可以随时自定义重定向到不同串口:
/*
* 简介:重定义fputc函数,用于将字符输出到当前设置的USART
* 参数:
* ch - 要发送的字符
* f - 文件指针(在此实现中未使用)
* 返回值:发送的字符(或EOF如果出错)
*/
int fputc(int ch, FILE *f)
{
if(Current_USART_Handle == NULL){ /* 如果当前没有设置USART句柄,则返回EOF表示错误 */
return EOF;
}
/* 根据当前设置的USART句柄,选择对应的USART外设发送字符 */
if(Current_USART_Handle == &huart1){
while ((USART1->ISR & 0X40) == 0); /* 等待USART1发送完成,然后发送字符 */
USART1->TDR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
}
else if(Current_USART_Handle == &huart2){
while ((USART2->ISR & 0X40) == 0); /* 等待USART2发送完成,然后发送字符 */
USART2->TDR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
}
else if(Current_USART_Handle == &huart3){
while ((USART3->ISR & 0X40) == 0); /* 等待USART3发送完成,然后发送字符 */
USART3->TDR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
}
return ch; /* 返回发送的字符 */
}
此文件中全部的重定向代码:
#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;
}
/************************** 以下是多串口printf重定向函数 **************************/
/* FILE 在 stdio.h里面定义 */
FILE __stdout;
UART_HandleTypeDef* Current_USART_Handle = NULL;
Current_USART_Indx Current_USART_Printf_Indx = USART_NONE;
/*
* 简介:重定义fputc函数,用于将字符输出到当前设置的USART
* 参数:
* ch - 要发送的字符
* f - 文件指针(在此实现中未使用)
* 返回值:发送的字符(或EOF如果出错)
*/
int fputc(int ch, FILE *f)
{
if(Current_USART_Handle == NULL){ /* 如果当前没有设置USART句柄,则返回EOF表示错误 */
return EOF;
}
/* 根据当前设置的USART句柄,选择对应的USART外设发送字符 */
if(Current_USART_Handle == &huart1){
while ((USART1->ISR & 0X40) == 0); /* 等待USART1发送完成,然后发送字符 */
USART1->TDR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
}
else if(Current_USART_Handle == &huart2){
while ((USART2->ISR & 0X40) == 0); /* 等待USART2发送完成,然后发送字符 */
USART2->TDR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
}
else if(Current_USART_Handle == &huart3){
while ((USART3->ISR & 0X40) == 0); /* 等待USART3发送完成,然后发送字符 */
USART3->TDR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
}
return ch; /* 返回发送的字符 */
}
/*
* 简介:设置当前使用的USART
* 参数:indx - 要设置的USART索引
* 这个参数可以是:USARTx_IDX,其中x可以从1~3
* 使用举例:(必须要将其放在printf函数前面,指定其中一个串口)
* Set_Current_USART(USART1_IDX);
* printf("我是串口1\r\n");
*/
void Set_Current_USART(Current_USART_Indx indx)
{
switch(indx)
{
case USART1_IDX:
Current_USART_Handle = &huart1;
Current_USART_Printf_Indx = USART1_IDX;
break;
case USART2_IDX:
Current_USART_Handle = &huart2;
Current_USART_Printf_Indx = USART2_IDX;
break;
case USART3_IDX:
Current_USART_Handle = &huart3;
Current_USART_Printf_Indx = USART3_IDX;
break;
default:
Current_USART_Handle = NULL;
Current_USART_Printf_Indx = USART_NONE;
break;
}
}
/************************** 以下是单串口printf重定向函数 **************************/
/**
* 简介:单个串口printf重定向fputc函数
* 重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口
*
* int fputc(int ch, FILE *f)
* {
* while ((USART2->ISR & 0X40) == 0); 等待上一个字符发送完成
* USART2->TDR = (uint8_t)ch; 将要发送的字符 ch 写入到DR寄存器
* return ch;
* }
*/
#endif
main.c中引用
在使用printf
函数之前,需要先调用Set_Current_USART
函数设置当前使用的USART。然后,就可以像平常一样使用printf
函数了,输出的字符串将会通过指定的USART发送到串口。
Set_Current_USART(USART1_IDX); /* 想要指定不同串口必须在printf前加上此函数 */
printf("我是串口1\r\n");
Set_Current_USART(USART2_IDX); /* 想要指定不同串口必须在printf前加上此函数 */
printf("我是串口2\r\n");
Set_Current_USART(USART3_IDX); /* 想要指定不同串口必须在printf前加上此函数 */
printf("我是串口3\r\n");
通过这种方式,我们可以非常方便地在STM32项目中实现多路USART串口printf
重定向,从而大大提高调试的效率和便利性。
【STM32系列】多路USART串口Printf重定向详解——通用的更多相关文章
- Linux Shell系列教程之(八)Shell printf命令详解
本文是Linux Shell系列教程的第(八)篇,更多shell教程请看:Linux Shell系列教程 在上一篇:Linux Shell系列教程之(七)Shell输出这篇文章中,已经对Shell p ...
- printf命令详解
基础命令学习目录首页 本文是Linux Shell系列教程的第(八)篇,更多shell教程请看:Linux Shell系列教程 在上一篇:Linux Shell系列教程之(七)Shell输出这篇文章中 ...
- 【C语言】printf函数详解
C语言printf函数详解 一.相关基础知识 请求printf()打印变量的指令取决于变量的类型,例如打印整数用%d符号,打印字符用%c符号,这些符号称为转换说明(conversion specifi ...
- Linux I/O 重定向详解及应用实例
Linux I/O 重定向详解及应用实例 简解 > 输出 < 输入 >> 追加 & [> | < | >>]之前:输入输出; ls /dev & ...
- C#串口通信程序详解
C#串口通信程序详解 摘要:创建C#串口通信程序需要注意什么呢?创建C#串口通信程序的步骤是什么?那么本文就向你详细介绍创建C#串口通信程序集体的内容. 在.NET平台下创建C#串口通信程序,.NET ...
- Java 8系列之Stream的基本语法详解
本文转至:https://blog.csdn.net/io_field/article/details/54971761 Stream系列: Java 8系列之Stream的基本语法详解 Java 8 ...
- STM32 GPIO 配置之ODR, BSRR, BRR 详解
STM32 GPIO 配置之ODR, BSRR, BRR 详解 用stm32 的配置GPIO 来控制LED 显示状态,可用ODR,BSRR,BRR 直接来控制引脚输出状态. ODR寄存器可读可写:既能 ...
- OSGi 系列(三)之 bundle 详解
OSGi 系列(三)之 bundle 详解 1. 什么是 bundle bundle 是以 jar 包形式存在的一个模块化物理单元,里面包含了代码,资源文件和元数据(metadata),并且 jar ...
- Python操作redis系列以 哈希(Hash)命令详解(四)
# -*- coding: utf-8 -*- import redis #这个redis不能用,请根据自己的需要修改 r =redis.Redis(host=") 1. Hset 命令用于 ...
- 新浪sae url rewrite(伪静态、重定向)详解
新浪sae url rewrite(伪静态.重定向)详解 http://www.veryhuo.com phpclubs 2011-11-14 投递稿件 sae全程Sina App Engine,真是 ...
随机推荐
- pdfjs-dist v2.11.338写个react demo
app.jsx import './App.css' import * as pdfjs from "pdfjs-dist"; import "pdfjs-dist/we ...
- 红米k40刷类原生系统
下载相关文件 下载系统 这里是下载地址 (推荐)也可以按照手机代号,找到本文所需的所有资源,比如我找到的k40的所有资源 下载和安装相关驱动 进FASTBOOT 在已开机的情况下,连接电脑执行命令即可 ...
- 网格 优化&光顺
简介 网格光滑在计算机图形学中广泛应用.因为从自然界采集的雕像带有很多的噪声. 本文对 论文 Laplacian Mesh Optimization 当然错误难以避免,本人水平不足造成错误,希望读者能 ...
- ABC397
最热泪盈眶的一次,中间被 D 卡了半小时,最后极限过 EF 翻盘. 不过 D 好像就是暴力,只是因为我没开 __int128?( rk.562. A - Thermometer 按照题意模拟即可. 点 ...
- SciTech-Mathmatics-Probability+Statistics: Distinguishing(区分) Probability(attaches to Outcomes MECE) from Likelihood(attaches Hypothesis are often neither MECE)
Links PRESIDENTIAL COLUMN: Bayes for Beginners: Probability and Likelihood C. Randy Gallistel, Augus ...
- CentOS 7安装MariaDB 10详解以及相关配置-九五小庞
第一步:添加 MariaDB yum 仓库 首先在CentOS操作系统中/etc/yum.repos.d/目录下添加 MariaDB 的YUM配置文件MariaDB.repo文件. vi /etc/y ...
- Win11专业版有网络却上不了网的问题
一些雨林木风系统的用户,使用win10专业版计算机时,但是,网络没有显示红叉叉和黄色感叹号,都一切正常,但是就无法上网的问题.这发生了什么事?应该有很多小伙伴都遇到这样的情况.因此,本文中雨林木风小编 ...
- 入职3个月,为冷门编程语言 InterSystems ObjectScript 编写了一个 VSCode 插件
背景介绍 InterSystems ObjectScript 是由 InterSystems 公司开发的编程语言,诞生于 1980 年代初期.它最初是为了支持 InterSystems 的数据库产品( ...
- mybati缓存
两者区别:一级缓存的作用域是在SqlSession中,二级缓存的作用域是针对mapper做缓存. SQL里边 有flushCache属性 是Boolean类型 1.当为select语句时:flushC ...
- vscode AC5编译出错:Error: L6406E
Error: L6406E: No space in execution regions with .ANY selector matching main.o(i.main). 解决办法: 在构建配置 ...