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

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

[复制链接]
STMCU小助手 发布时间:2022-2-7 21:33
常规打印方法
在STM32的应用中,我们常常对printf进行重定向的方式来把打印信息printf到我们的串口助手。在MDK环境中,我们常常使用MicroLIB+fputc的方式实现串口打印功能,即:
要实现fputc函数的原因是:printf函数依赖于fputc函数,重新实现fputc内部从串口发送数据即可间接地实现printf打印输出数据到串口。
不知道大家有没有看过正点原子裸机串口相关的例程,他们的串口例程里不使用MicroLIB,而是使用标准库+fputc的方式。相关代码如:
  1. #if 1
    3 `; I& K( G/ ^; `: X% a9 S" p& e* ]
  2. #pragma import(__use_no_semihosting)2 |1 e3 J- F' X
  3. //标准库需要的支持函数
    1 U# b) u. j. O6 \+ _3 c
  4. struct __FILE
    ' D, S! A' Y# l' Q
  5. {; N5 K% a0 b! u! L' u
  6.     int handle;
    6 v$ y; b( z# e$ F: D9 X" d
  7. };! |8 f& e, K4 x1 o. Q# J

  8. % S$ H6 y8 Z, s' n, y& {9 \4 O
  9. FILE __stdout;  y/ w$ {9 F$ _) z$ L! p; o
  10. /**, |' Q) V+ G7 d" n4 r
  11. * @brief        定义_sys_exit()以避免使用半主机模式
    * G8 `1 p9 f# Q8 Z4 |  P$ X# K. _
  12. * @param        void
    % I4 C4 ~8 ^& c, L9 I3 R$ P
  13. * @return  void
    ; x6 P) v/ g9 l' e, o& Z* s
  14. */6 G- S2 {& i+ [; S" j
  15. void _sys_exit(int x)+ C8 p; Q9 Q: ^  w
  16. {
    ) V; W  N; `4 w1 ]1 Q0 u
  17.     x = x;% g3 Z$ M) F5 t0 e0 k) h. y5 N! A
  18. }
    ) Q2 @6 j8 G9 u+ m
  19. ; w1 \/ I9 Q  U8 q2 j
  20. int fputc(int ch, FILE *f)
    % t- \  P7 ^" [, Y" r
  21. {
    * s, ~5 {8 y) D1 Z3 @; `
  22.     while((USART1->ISR & 0X40) == 0); //循环发送,直到发送完毕" Z/ D" g- o9 h/ g( V! o2 i4 }

  23. $ q, f" \/ a# o, K
  24.     USART1->TDR = (u8) ch;
    . H2 h# M1 E: b- q  [3 d4 j
  25.     return ch;
    2 O: S  k& z$ h+ C8 g( U
  26. }/ C# d4 ^6 y6 V8 h
  27. #endif
    $ @4 Q% y3 i* `! q6 O7 ?
复制代码

% y- z  `$ v2 o8 o自己实现一个打印函数
以上的几种方法基本上是改造C库的printf函数来实现串口打印的功能。其实我们也可以自己实现一个串口打印的功能。
printf本身就是一个变参函数,其原型为:
  1. int printf (const char *__format, ...);
    " c) d/ P1 N# P1 B; c) y
复制代码
- y; g! c; {7 {# T3 v3 f) U
所以,我们要重新封装的一个串口打印函数自然也应该是一个变参函数。具体实现如下:
1、基于STM32的HAL库
  1. #define TX_BUF_LEN  256     /* 发送缓冲区容量,根据需要进行调整 */5 T& d. V8 B' L4 e- ^) p
  2. uint8_t TxBuf[TX_BUF_LEN];  /* 发送缓冲区                       */
    3 t; U- c7 Z8 M5 ]5 Q
  3. void MyPrintf(const char *__format, ...)9 ~% a) ~: f+ ]' O* _& k# E& l' Z2 c
  4. {8 N8 X% O- W9 H. U" b
  5.   va_list ap;
    & r4 B2 c+ P# D6 L& V7 c. i
  6.   va_start(ap, __format);1 L9 Y4 a6 M: R
  7.   3 \% |' f$ b& M, D' a' p
  8.   /* 清空发送缓冲区 */* j0 t& `" ?  b9 {7 Z
  9.   memset(TxBuf, 0x0, TX_BUF_LEN);# X0 z; F. ?" {% P. ~) m& I  E! ~; e
  10.   
    $ g! N4 O5 s0 k# D5 r# g
  11.   /* 填充发送缓冲区 */* }4 c% L0 A2 |* \* N) ?9 E9 F
  12.   vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);  G3 [4 n) z/ m3 C& f3 l# n4 i3 w
  13.   va_end(ap);( P: f2 `( A! s& O+ E1 Z. d
  14.   int len = strlen((const char*)TxBuf);
    * I6 r; R$ D2 V. P- I8 X/ R  l9 G
  15.   % f* t, H0 n2 x3 N
  16.   /* 往串口发送数据 */: F$ p, S8 w# E8 J3 y
  17.   HAL_UART_Transmit(&huart1, (uint8_t*)&TxBuf, len, 0xFFFF);
    9 C: x* t! e/ l4 ?1 P
  18. }
    1 b6 T5 f5 y" Z& w* W
复制代码

8 k8 z5 y6 |8 [$ F: n/ {
因为我们使用printf函数基本不使用其返回值,所以这里直接用void类型了。自定义变参函数需要用到va_start、va_end等宏,需要包含头文件stdarg.h。
这里我们使用的是STM32的HAL库,其给我们提供HAL_UART_Transmit接口可以直接把整个发送缓冲区的内容给一次性发出去。
2、基于STM32标准库
若是基于STM32的标准库,就需要一字节一字节的循环发送出去,具体代码如:
  1. #define TX_BUF_LEN  256     /* 发送缓冲区容量,根据需要进行调整 */
    . ^/ n6 m% y% O* J, h: D
  2. uint8_t TxBuf[TX_BUF_LEN];  /* 发送缓冲区                       */
    ) b( s, X- X5 X) v4 m  C$ k7 P; u
  3. void MyPrintf(const char *__format, ...)( |; O  O, G' {$ s
  4. {
    8 |& K* O9 A, K
  5.   va_list ap;
    9 T% B  @# T+ s/ Z; k5 r
  6.   va_start(ap, __format);
    7 @( C7 r/ Z+ r- H7 X0 }; n
  7.     ( q& A. |2 O% ^  P. N' O
  8.   /* 清空发送缓冲区 */
    + R( E8 `! ^/ o5 d5 F& P3 M
  9.   memset(TxBuf, 0x0, TX_BUF_LEN);. n. i5 B/ U  t+ g) v
  10.     8 R- q9 v  H+ |3 r, w7 a
  11.   /* 填充发送缓冲区 */6 l5 R* f% _! Q2 E1 K
  12.   vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);
    . W. l( P7 s9 z( Q
  13.   va_end(ap);
    # q+ c% A7 Y2 \2 v
  14.   int len = strlen((const char*)TxBuf);
    0 E. o, ~3 m& q# m
  15.   8 b* k2 c9 k0 k
  16.   /* 往串口发送数据 */4 y. g6 @4 r, g" b' P2 o
  17.   for (int i = 0; i < len; i++)9 @  m- w/ W/ h1 {, x2 H
  18.   {: q5 H7 _! w& e  a* b& D, w
  19.         while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);    - R& j" N+ R9 n) ~3 M) V6 w% Q
  20.         USART_SendData(USART1, TxBuf[i]);
    ' ?1 z. V( Q) K. |8 V
  21.   }
    + F4 h* U" @9 M9 n) H! b' j
  22. }5 q7 D% P5 Z% e5 Z$ o! }! d
复制代码
1 s8 X8 H: j9 k1 w4 B# Y8 K  l
测试结果:
我们也可以使用我们的MyPrintf函数按照上一篇文章:======的方式封装一个宏打印函数:

% g  n& @  C5 U& M2 f/ s/ u9 y' j& R0 U9 c1 G. l
以上就是我们自定义方式实现的一种串口打印函数。
但是,我想说:对于串口打印的使用,我们没必要自己创建一个打印函数。看到这,是不是有人想要打我了。。。。看了半天,你却跟我说没必要用。。。
哈哈,别急,我们不应用在串口打印调试方面,那可以用在其它方面呀。
(1)应用一:
比如最近我在实际应用中:我们的MCU跑的是我们老大自己写的一个小的操作系统+我们公司自己开发的上位机。我们MCU端与上位机使用的是串口通讯,MCU往上位机发送的数据有两种类型,一种是HEX格式数据,一种是字符串数据。
但是我们下位机的这两种数据,在通过串口发送之前都得统一把数据封包交给那个系统通信任务,然后再由通信任务发出去。在这里,就不能用printf了。老大也针对他的这个系统实现了一个deb_printf函数用于打印调试。
但是,那个函数既复杂又很鸡肋,稍微复杂一点的数据就打印不出来了。因此我利用上面的思路给它新封装了一个打印调试函数,很好用,完美地兼容了老大的那个系统。具体代码就不分享了,大体代码、思路如上。
(2)应用二:
我们在使用串口与ESP8266模块通讯时,可利用类似这样的方式封装一个发送数据的函数,这个函数的使用可以像printf一样简单。可以以很简单的方式把数据透传至服务端,比如我以前的毕设中就有这么应用:

6 [/ ^1 N+ y" q  \
: D& t% V5 a' M+ B3 N; j! C% l

% @7 I4 X+ [3 U: l% T; I/ A. T
收藏 评论0 发布时间:2022-2-7 21:33

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版