STM32 | 串口打印知多少?

发布者:梦想学院最新更新时间:2021-08-11 来源: eefocus关键字: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函数按照上一篇文章:《C语言、嵌入式中几个非常实用的宏技巧》的方式封装一个宏打印函数:

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

但是,我想说:对于串口打印的使用,我们没必要自己创建一个打印函数。

看到这,是不是有人想要打我了。。。。看了半天,你却跟我说没必要用。。。

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

(1)应用一:

比如最近我在实际应用中:我们的MCU跑的是我们老大自己写的一个小的操作系统+我们公司自己开发的上位机。


我们MCU端与上位机使用的是串口通讯,MCU往上位机发送的数据有两种类型,一种是HEX格式数据,一种是字符串数据。


但是我们下位机的这两种数据,在通过串口发送之前都得统一把数据封包交给那个系统通信任务,然后再由通信任务发出去。


在这里,就不能用printf了。老大也针对他的这个系统实现了一个deb_printf函数用于打印调试。


但是,那个函数既复杂又很鸡肋,稍微复杂一点的数据就打印不出来了。


因此我利用上面的思路给它新封装了一个打印调试函数,很好用,完美地兼容了老大的那个系统。具体代码就不分享了,大体代码、思路如上。


(2)应用二:

我们在使用串口与ESP8266模块通讯时,可利用类似这样的方式封装一个发送数据的函数,这个函数的使用可以像printf一样简单。


可以以很简单的方式把数据透传至服务端,比如我以前的毕设中就有这么应用:

关键字:STM32  串口打印  串口助手 引用地址:STM32 | 串口打印知多少?

上一篇:STM32 | 学习STM32的一些经验分享
下一篇:STM32 | TCP通信实例分析

推荐阅读最新更新时间:2024-11-10 10:21

STM32时钟基础知识
STM32F4时钟树 STM32一共有5个时钟源,分别为外部高速时钟(HSE)、内部高速时钟(HSI)、外部低速时钟(LSE)、内部低速时钟(LSI)以及锁相环时钟(PLL); 从时钟树可以看出,系统时钟主要有三个作用,一是为Cotex-M3内核提供运行时钟,二是直接或经过分频后为挂载在各种总线上的外设提供时钟,第三则是以后为RTC(实时时钟)提供时钟源。 下面是对应序号的时钟解释: 1,外部高速时钟(HSE) HSE 是高速的外部时钟信号,可以由有源晶振或者无源晶振提供,频率从 4-26MHZ不等。当使用有源晶振时,时钟从 OSC_IN 引脚进入, OSC_OUT 引脚悬空,当选用无源晶振时,时钟从 OSC_IN 和
[单片机]
<font color='red'>STM32</font>时钟基础知识
STM32中对SysTick_Init()函数(sysTick_Config()、TimingDelay_Decrement()自定义)和Delay_us()的理解
STM32中对SysTick_Init()函数(sysTick_Config()、TimingDelay_Decrement()自定义)和Delay_us()的理解: 实验:3个LED灯以500ms的频率闪烁。
[单片机]
<font color='red'>STM32</font>中对SysTick_Init()函数(sysTick_Config()、TimingDelay_Decrement()自定义)和Delay_us()的理解
使能STM32串口后有时发送0x00的问题
int main() { init_usart(); printf( hellon ); } 在发送完“hellon”后,会自动发送一个 0x00 ; 而 int main() { init_usart(); printf( hellon ); while(1); } 在发送完“hellon”后,不会自动发送一个 0x00 在此记录一个现象,原因不详
[单片机]
STM32——GPIO设置:快速点亮第一个LED灯
简介 不同的开发板的原理图结构也不尽相同,笔者在这里使用野火的MINI-V3(F103VET6)简易开发板对GPIO口的设置做一个简单的介绍,并实现按键控制LED灯的亮灭。方便读者可以快速熟悉并灵活应用。 原理图分析 首先我们来看一下发光二极管部分和按键部分的原理图。 下面是按键的原理图部分: 在这里,我们将使用KEY1(PA0)来控制红灯PB5的亮灭。由原理图可知,其控制LED灯的PB5引脚为低电平时,灯亮。当KEY1按下时,PA0引脚由之前的低电平转为高电平(3V3)。明确了目的之后我们就可以分析GPIO口并进行设置了。 GPIO设置 经过上述的功能明确之后,我们将其分为两大类,分别是输入类(如按键)和输出类
[单片机]
<font color='red'>STM32</font>——GPIO设置:快速点亮第一个LED灯
stm32串口DMA方式发送数据
DMA发送数据 启动DMA并发送完成后,产生DMA发送完成中断,在DMA中断服务函数中执行以下操作: 在数据发送缓冲区内放好要发送的数据(此数据缓冲区的首地址必须要在DMA初始化时写入到DMA配置中去) 将数据缓冲区内要发送的数据字节数传给DMA通道(串口发送和接收不是同一个通道) 开启DMA,一旦开启,则DMA开始发送数据, 等待数据发送完成标志! 判断数据发送完成: 清DMA发送完成标志 关闭串口发送DMA通道 给前台(应用)程序设置一个软件标志位,说明数据发送完成。 DMA接收数据 串口接收DMA在初始化时就处于开启状态,一直等待数据的到来,串口中断IDLE在串口一直没有数据时,是不会产生的,
[单片机]
STM32键盘-库函数
昨天晚上老大回来看我频繁使用寄存器写程序给出指点,告诉我尽量使用库函数,少使用寄存器,毕竟寄存器在后面使用的时候还要一个一个查找手册可能太麻烦了,nice! 这次使用库函数操作键盘,很基础的实验,其中掺杂了之前的蜂鸣器和LED内容。 感觉基础键盘实验比较有价值的还是那个键盘操作函数,以下给出标准模板,mode作为参数时置0为不支持连续摁键,置1时为支持连续摁键。 //摁键函数,0为不支持连摁,1为支持连摁 u8 Key_Scan(u8 mode) { static u8 flag = 1; if(mode) flag = 1; if(flag && (KEY0 == 0 || KEY1 == 0 || K
[单片机]
STM32定时器BURST模式
STM32 Burst模式可以方便的设置定时器的预分频值TIMx_PSC寄存器、自动重载值TIMx_ARR寄存器、定时器重复计数寄存器TIMx_RCR寄存器、定时器输出比较寄存器TIMx_CCR寄存器。这样就很容易使用DMA的性能改变定时器的频率、占空比。注意地址值的对应。
[单片机]
<font color='red'>STM32</font>定时器BURST模式
STM32之Hex文件格式解析
Hex文件如果用特殊的程序来查看(一般记事本就可以实现)。打开后可发现,真个文件以行为单位,每行以冒号开头,内容全部为16进制码。Hex文件可以按照如下的方式进行拆分来分析其中的内容:   例如:   :020000040000FA , 我把它看做 0x02 0x00 0x00 0x04 0x00 0x00 0xFA   第一个 0x02 为数据长度。   紧跟着后面的0x00 0x00 为地址。   再后面的0x04为数据类型,类型共分以下几类:   '00' Data Record   '01' End of File Record   '02' Extended Seg
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved