常规打印方法

在STM32的应用中,我们常常对printf进行重定向的方式来把打印信息printf到我们的串口助手。在MDK环境中,我们常常使用MicroLIB+fputc的方式实现串口打印功能,即:

要实现fputc函数的原因是:printf函数依赖于fputc函数,重新实现fputc内部从串口发送数据即可间接地实现printf打印输出数据到串口。

不知道大家有没有看过正点原子裸机串口相关的例程,他们的串口例程里不使用MicroLIB,而是使用标准库+fputc的方式。相关代码如:

#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
}; FILE __stdout;
/**
* @brief 定义_sys_exit()以避免使用半主机模式
* @param void
* @return void
*/
void _sys_exit(int x)
{
x = x;
} int fputc(int ch, FILE *f)
{
while((USART1->ISR & 0X40) == 0); //循环发送,直到发送完毕 USART1->TDR = (u8) ch;
return ch;
}
#endif

关于这两种方法的一些说明可以查看Mculover666兄的重定向printf函数到串口输出的多种方法这篇文章。这篇文章中不仅包含上面的两种方法,而且也包含着在GCC中使用标准库重定向printf的方法。

自己实现一个打印函数

以上的几种方法基本上是改造C库的printf函数来实现串口打印的功能。其实我们也可以自己实现一个串口打印的功能。

printf本身就是一个变参函数,其原型为:

int printf (const char *__format, ...);

所以,我们要重新封装的一个串口打印函数自然也应该是一个变参函数。具体实现如下:

1、基于STM32的HAL库

#define TX_BUF_LEN  256     /* 发送缓冲区容量,根据需要进行调整 */
uint8_t TxBuf[TX_BUF_LEN]; /* 发送缓冲区 */
void MyPrintf(const char *__format, ...)
{
va_list ap;
va_start(ap, __format); /* 清空发送缓冲区 */
memset(TxBuf, 0x0, TX_BUF_LEN); /* 填充发送缓冲区 */
vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);
va_end(ap);
int len = strlen((const char*)TxBuf); /* 往串口发送数据 */
HAL_UART_Transmit(&huart1, (uint8_t*)&TxBuf, len, 0xFFFF);
}

因为我们使用printf函数基本不使用其返回值,所以这里直接用void类型了。自定义变参函数需要用到va_start、va_end等宏,需要包含头文件stdarg.h。关于变参函数的一些学习可以查看网上的一些博文,如:

https://www.cnblogs.com/wulei0630/p/9444062.html

这里我们使用的是STM32的HAL库,其给我们提供HAL_UART_Transmit接口可以直接把整个发送缓冲区的内容给一次性发出去。

2、基于STM32标准库

若是基于STM32的标准库,就需要一字节一字节的循环发送出去,具体代码如:

#define TX_BUF_LEN  256     /* 发送缓冲区容量,根据需要进行调整 */
uint8_t TxBuf[TX_BUF_LEN]; /* 发送缓冲区 */
void MyPrintf(const char *__format, ...)
{
va_list ap;
va_start(ap, __format); /* 清空发送缓冲区 */
memset(TxBuf, 0x0, TX_BUF_LEN); /* 填充发送缓冲区 */
vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);
va_end(ap);
int len = strlen((const char*)TxBuf); /* 往串口发送数据 */
for (int i = 0; i < len; i++)
{
while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);
USART_SendData(USART1, TxBuf[i]);
}
}

测试结果:

我们也可以使用我们的MyPrintf函数按照上一篇文章:======的方式封装一个宏打印函数:





以上就是我们自定义方式实现的一种串口打印函数。

但是,我想说:对于串口打印的使用,我们没必要自己创建一个打印函数。看到这,是不是有人想要打我了。。。。看了半天,你却跟我说没必要用。。。

哈哈,别急,我们不应用在串口打印调试方面,那可以用在其它方面呀。

(1)应用一:

比如最近我在实际应用中:我们的MCU跑的是我们老大自己写的一个小的操作系统+我们公司自己开发的上位机。我们MCU端与上位机使用的是串口通讯,MCU往上位机发送的数据有两种类型,一种是HEX格式数据,一种是字符串数据。

但是我们下位机的这两种数据,在通过串口发送之前都得统一把数据封包交给那个系统通信任务,然后再由通信任务发出去。在这里,就不能用printf了。老大也针对他的这个系统实现了一个deb_printf函数用于打印调试。

但是,那个函数既复杂又很鸡肋,稍微复杂一点的数据就打印不出来了。因此我利用上面的思路给它新封装了一个打印调试函数,很好用,完美地兼容了老大的那个系统。具体代码就不分享了,大体代码、思路如上。

(2)应用二:

我们在使用串口与ESP8266模块通讯时,可利用类似这样的方式封装一个发送数据的函数,这个函数的使用可以像printf一样简单。可以以很简单的方式把数据透传至服务端,比如我以前的毕设中就有这么应用:



以上就是本次的分享,如有错误,欢迎指出!谢谢


我的个人博客:https://www.lizhengnian.cn/

我的微信公众号:嵌入式大杂烩

我的CSDN博客:https://blog.csdn.net/zhengnianli

STM32串口打印的那些知识的更多相关文章

  1. STM32 串口功能 库函数 详解和DMA 串口高级运用(转载)

    数据传输时要从支持那些相关的标准?传输的速度?什么时候开始?什么时候结束?传输的内容?怎样防止通信出错?数据量大的时候怎么弄?硬件怎么连接出发,当然对于stm32还要熟悉库函数的功能 具起来rs232 ...

  2. stm32串口通讯问题

    stm32串口通讯问题 在串口试验中,串口通讯不正常,则可能会出现以下问题: 1. 配置完成后,串口没有任何消息打印. 原因:1,端口配置有问题,需要重新检查I/O口的配置 2,接线有问题,检查接线是 ...

  3. STM32串口USART1的使用方法和程序

    通用同步异步收发器(USART)提供了一种灵活的方法来与使用工业标准NR 异步串行数据格式的外部设备之间进行全双工数据交换. USART利用分数波特率发生器提供宽范围的波特率选择,支持同步单向通信和半 ...

  4. STM32串口USART的使用方法和程序

    通用同步异步收发器(USART)提供了一种灵活的方法来与使用工业标准NR 异步串行数据格式的外部设备之间进行全双工数据交换. USART利用分数波特率发生器提供宽范围的波特率选择,支持同步单向通信和半 ...

  5. stm32串口之存储与解析

    最近在做一个小项目,需要用stm32串口接受Arduino发送的一个不定长的数据,并且解析数据,执行其中的命令:秉着不在中断中做过多任务的思想,我们将从串口中接受到的字符放到一个数组当中. 定义数组 ...

  6. cotex_m3内核提供的ITM串口打印调试

    cotex_m3内核的ARM提供了ITM串口打印观测的功能,可以不用ARM单片机自己的串口就可在开发时候串口打印调试.节约了宝贵的内部资源,同时也为调试提供了方便.使用方法如下: 1 将下面的SWO_ ...

  7. STM32 串口DMA方式接收(转)

    STM32 是一款基于ARM Cortex-M3内核的32位MCU,主频最高可达72M.最近因为要在车机上集成TPMS功能, 便开始着手STM32的开发工作,STM32F10x系列共有5个串口(USA ...

  8. STM32串口寄存器操作(转)

    源:STM32串口寄存器操作 //USART.C /************************************************************************** ...

  9. STM32串口控制步进电机(原创)

    用的42步进电机: 厂家可能不一样,两项四线步进电机,里面有两个线圈.在电机什么电都没有接的情况下,用万用表测量四个管脚:两两短接(或者阻值很小)的为一组,可以分别接A+,a-剩余接B+,B-;顺序可 ...

随机推荐

  1. rabbitMQ基于spring-rabbitnq

    一.什么是MQ MQ全称为Message Queue,消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们.消息传递 ...

  2. 学习ASP.NET Core(06)-Restful与WebAPI

    上一篇我们使用Swagger添加了接口文档,使用Jwt完成了授权,本章我们简答介绍一下RESTful风格的WebAPI开发过程中涉及到的一些知识点,并完善一下尚未完成的功能 .NET下的WebAPI是 ...

  3. 二、YARN

    一.YARN 介绍 yarn 是下一代 MapReduce,即 MRv2,是在第一代 MapReduce 基础上演变而来的,主要是为了解决原始 Hadoop 扩展性较差,不支持多计算框架而提出的,通俗 ...

  4. P1880 [NOI1995]石子合并 区间dp

    P1880 [NOI1995]石子合并 #include <bits/stdc++.h> using namespace std; ; const int inf = 0x3f3f3f3f ...

  5. SSH_ProductCRUD的项目结构与配置文件

    项目结构 各类配置文件 <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hib ...

  6. Java并发编程入门(二)

    1.竞态条件 1.1 定义 当某个计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件.换句话说,正确的结果要取决于运气. 最常见的竞态条件类型:先检查后执行(Check-Then-Act)操 ...

  7. JUC(4)---java线程池原理及源码分析

    线程池,既然是个池子里面肯定就装很多线程. 如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁 线程,如此一来会大大降低系统的效率.可能出现服务器在为每个请求创建新线程和销毁 ...

  8. os.walk()的实际应用

    背景: 通过Mobaxterm从本地上传虹膜数据,一共79个类,每类里包含左右眼各400张数据,总共63200张,上传期间断网不确定是否传完. 思路: 1.首先遍历总类别数是否正确,若不足79,返回“ ...

  9. JSP+SSM+Mysql实现的图书馆预约占座管理系统

    项目简介 项目来源于:https://gitee.com/gepanjiang/LibrarySeats 因原gitee仓库无数据库文件且存在水印,经过本人修改,现将该仓库重新上传至个人gitee仓库 ...

  10. Ratel源码-C/S事件梳理

    一.Ratel介绍 Ratel 是一个可以在命令行中玩斗地主的项目,可以使用小巧的jar包在拥有JVM环境的终端中进行游戏,同时支持人人对战和人机对战两种模式,丰富你的空闲时间! 二.玩法Demo 三 ...