你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32串口打印的那些知识

[复制链接]
STMCU小助手 发布时间:2022-2-7 21:33
常规打印方法
在STM32的应用中,我们常常对printf进行重定向的方式来把打印信息printf到我们的串口助手。在MDK环境中,我们常常使用MicroLIB+fputc的方式实现串口打印功能,即:
要实现fputc函数的原因是:printf函数依赖于fputc函数,重新实现fputc内部从串口发送数据即可间接地实现printf打印输出数据到串口。
不知道大家有没有看过正点原子裸机串口相关的例程,他们的串口例程里不使用MicroLIB,而是使用标准库+fputc的方式。相关代码如:
  1. #if 1
    8 l7 l# F' ?; m$ f
  2. #pragma import(__use_no_semihosting)4 v3 a" f: k* n. T
  3. //标准库需要的支持函数
    ; J2 z8 e# j7 g! s/ \) w
  4. struct __FILE
    3 Q7 m7 Q+ B& Y6 C  h; a5 G
  5. {
    6 O5 R" e/ _, s* ]1 \' x0 o( }
  6.     int handle;
    ) b* n7 x. F0 @$ S/ k& ^
  7. };
    4 n- a% b) w0 V' k

  8. . f# d: g0 B/ ]- G" A( u
  9. FILE __stdout;+ m% M; z9 y2 s( `0 |& b: [0 ]
  10. /**
    ! J1 W5 p3 Z( t
  11. * @brief        定义_sys_exit()以避免使用半主机模式1 l6 C8 _" o" b3 z
  12. * @param        void6 p& M9 a( A1 Q2 |9 W9 _
  13. * @return  void. v+ {; Y& C3 p" G5 n" A
  14. */- l( Z# F9 ?# m- \4 a- ], l$ E6 D
  15. void _sys_exit(int x)
    / e8 [3 F! i7 x  w$ F* \
  16. {( g! P  ]  y  K9 i3 F, z2 z, ?1 N
  17.     x = x;
    9 K6 Y' ^( q3 A) j
  18. }1 [8 ^  @0 |$ {" K4 e
  19. 2 M3 X- q- z& K' [- E
  20. int fputc(int ch, FILE *f)
    ' u! K8 X7 c( G, y- a' A
  21. {$ y3 T/ F( g3 @$ @" x+ z' ]- o5 u# [
  22.     while((USART1->ISR & 0X40) == 0); //循环发送,直到发送完毕; J$ U+ L* ~/ J7 H3 x8 Q

  23. ; [7 r. t5 ]& D4 O* j
  24.     USART1->TDR = (u8) ch;
    3 u1 O+ W7 b% u5 u
  25.     return ch;
    ' t- h0 l& B3 D3 U- U. I  `
  26. }
    7 v" O8 W1 C' t9 e" V/ H" U- H
  27. #endif' Q* {; D& q* z6 D- K" D% C
复制代码

0 y  e* z4 W: p% l9 g6 _自己实现一个打印函数
以上的几种方法基本上是改造C库的printf函数来实现串口打印的功能。其实我们也可以自己实现一个串口打印的功能。
printf本身就是一个变参函数,其原型为:
  1. int printf (const char *__format, ...);! x  @" ?% o. i0 c  a6 T7 [( b! Y% K
复制代码

7 Z9 O" N4 M+ d% M6 \
所以,我们要重新封装的一个串口打印函数自然也应该是一个变参函数。具体实现如下:
1、基于STM32的HAL库
  1. #define TX_BUF_LEN  256     /* 发送缓冲区容量,根据需要进行调整 */" ]+ n  r8 d  x
  2. uint8_t TxBuf[TX_BUF_LEN];  /* 发送缓冲区                       */# ^& r' V" u1 |$ I* ?  u
  3. void MyPrintf(const char *__format, ...)5 F- p4 Z" |5 q5 x! J& A; N
  4. {
    8 {$ f4 s) b7 W9 N
  5.   va_list ap;& j1 N6 x+ H8 C! }
  6.   va_start(ap, __format);' H+ T/ G! g0 X" [; Q
  7.   0 Q3 Z9 l' P& \; J9 u
  8.   /* 清空发送缓冲区 */
    ! L- c  T7 W/ r  }
  9.   memset(TxBuf, 0x0, TX_BUF_LEN);% m' S  D  J, K- D! w$ N' A
  10.   2 k' j  O7 z4 a: S# o% n- I
  11.   /* 填充发送缓冲区 */
    - X$ s- P9 n5 ]$ j
  12.   vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);# {4 t! e8 A$ f. X: X" x' V
  13.   va_end(ap);
    2 ~; ~0 ?4 d# j1 }
  14.   int len = strlen((const char*)TxBuf);
    3 r. E0 I% B& M) V# C! }9 T
  15.     W$ X' V! h" D6 N4 `2 e( r
  16.   /* 往串口发送数据 */9 v1 y" J5 J) ^9 m' y' @
  17.   HAL_UART_Transmit(&huart1, (uint8_t*)&TxBuf, len, 0xFFFF);
    % W0 e1 |# c# g6 f0 c# n
  18. }
    9 y3 ]! T5 Y  x- O
复制代码

9 x. k$ o1 ?8 A" N. j( [2 D  q8 S9 F2 I
因为我们使用printf函数基本不使用其返回值,所以这里直接用void类型了。自定义变参函数需要用到va_start、va_end等宏,需要包含头文件stdarg.h。
这里我们使用的是STM32的HAL库,其给我们提供HAL_UART_Transmit接口可以直接把整个发送缓冲区的内容给一次性发出去。
2、基于STM32标准库
若是基于STM32的标准库,就需要一字节一字节的循环发送出去,具体代码如:
  1. #define TX_BUF_LEN  256     /* 发送缓冲区容量,根据需要进行调整 *// j+ O/ k; K+ z" u* Q8 m
  2. uint8_t TxBuf[TX_BUF_LEN];  /* 发送缓冲区                       */
    1 |- T: U! }4 N* |' F5 Q8 F/ J. G: d
  3. void MyPrintf(const char *__format, ...); c9 N7 B. _. w8 c8 o1 u7 c
  4. {; i  p6 g( ?0 f+ s% }8 T6 Q) l5 g
  5.   va_list ap;
    1 `* W+ c5 d1 i8 g
  6.   va_start(ap, __format);
    3 y: z1 ^4 t; N$ S
  7.     , g& U" V$ o  P& u% Z  ]5 |
  8.   /* 清空发送缓冲区 */
    4 M' j; `# ]) q4 o
  9.   memset(TxBuf, 0x0, TX_BUF_LEN);, v# e' m0 X9 f3 W4 }
  10.     9 t; ^1 |; Z) E  j
  11.   /* 填充发送缓冲区 */" p& B/ c4 R& q
  12.   vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);) w2 m7 E/ b4 [) r
  13.   va_end(ap);2 B& A8 k+ y/ `# Q8 s, W
  14.   int len = strlen((const char*)TxBuf);
    + C, Y2 _2 v- B! [* P9 U% z
  15.   
    ( ]$ I( F/ y8 {
  16.   /* 往串口发送数据 */
    / B: h& q/ F4 `( h/ t0 D
  17.   for (int i = 0; i < len; i++)
    - H6 [' V; I1 W. r4 V
  18.   {
    2 x. U+ ]  J! A8 n8 Y0 ~6 C; s
  19.         while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);   
    , }: k/ u2 M4 u$ ?
  20.         USART_SendData(USART1, TxBuf[i]);& m( ^0 p8 u8 l- K
  21.   }
    7 L2 O8 c1 o/ J0 L
  22. }$ M: P5 B6 L1 `& V; j' k& W) ]8 m
复制代码

* c- T: G  B( ^3 X- C6 F- `7 c
测试结果:
我们也可以使用我们的MyPrintf函数按照上一篇文章:======的方式封装一个宏打印函数:
, F9 u% P: c) T+ X! H3 W% p
: w- b5 ?( @) ^9 F5 P. A
以上就是我们自定义方式实现的一种串口打印函数。
但是,我想说:对于串口打印的使用,我们没必要自己创建一个打印函数。看到这,是不是有人想要打我了。。。。看了半天,你却跟我说没必要用。。。
哈哈,别急,我们不应用在串口打印调试方面,那可以用在其它方面呀。
(1)应用一:
比如最近我在实际应用中:我们的MCU跑的是我们老大自己写的一个小的操作系统+我们公司自己开发的上位机。我们MCU端与上位机使用的是串口通讯,MCU往上位机发送的数据有两种类型,一种是HEX格式数据,一种是字符串数据。
但是我们下位机的这两种数据,在通过串口发送之前都得统一把数据封包交给那个系统通信任务,然后再由通信任务发出去。在这里,就不能用printf了。老大也针对他的这个系统实现了一个deb_printf函数用于打印调试。
但是,那个函数既复杂又很鸡肋,稍微复杂一点的数据就打印不出来了。因此我利用上面的思路给它新封装了一个打印调试函数,很好用,完美地兼容了老大的那个系统。具体代码就不分享了,大体代码、思路如上。
(2)应用二:
我们在使用串口与ESP8266模块通讯时,可利用类似这样的方式封装一个发送数据的函数,这个函数的使用可以像printf一样简单。可以以很简单的方式把数据透传至服务端,比如我以前的毕设中就有这么应用:
' M5 F" R# d  V3 l4 e

2 H: |/ p1 X" H% U5 z# m8 i( w
$ g( R) w, T  h( w, e, S3 L) k
赞 收藏 评论0 发布时间:2022-2-7 21:33

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版