STM32中使用printf打印字符串为何字符串第一个字符无法打印?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32中使用printf打印字符串为何字符串第一个字符无法打印?相关的知识,希望对你有一定的参考价值。

如题,用fputc将打印定向到USART上了,但串口输出到电脑后发现,字符串的第一个字符没有被打印!!??如图程序中为“abcd……”但打印后a不见了!!!
求解……

先用16进制接收试试,看看接收的内容有没有61,如果有的话就是你上位机的事儿。
首先你printf打印的是ascii码,每个字符占1字节 ,8位。而接收区通常需要显示中文,中文是UNICODE,是16位,两字节。所以在接收到以后,它会先把两个ASCII码读成一个UNICODE,而后再分开来为两个UNICODE,但你发出的程序是9字节,不是偶数,串口助手如果有BUG的话,就不能完全显示 。所以你再加上一个字母试试。
参考技术A 请问你输出其他的也是第一个字母有问题吗,如果是这样的话,可能是你的printf函数没有移植好。 参考技术B 先清楚两标志位就不会出现第一个字母不显示的问题了追问

哦?清除两标志位?具体什么意思,如何操作?还望继续解答……谢谢

追答

你把你usart.c的文件贴上来

追问

贴不上来啊 ,没办法只有截图了……劳大侠费眼了……

STM32串口打印的那些知识

常规打印方法

在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中使用printf打印字符串为何字符串第一个字符无法打印?的主要内容,如果未能解决你的问题,请参考以下文章

STM32为啥要用重定向printf来打印串口数据?直接用串口发送函数不行吗

STM32串口打印的那些知识

STM32串口打印的那些知识

STM32标准库_03 | 串口printf打印

STM32G0学习手册——FreeRTOS中使用printf-stdarg.c进行串口打印

STM32G0学习手册——FreeRTOS中使用printf-stdarg.c进行串口打印