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

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

[复制链接]
STMCU小助手 发布时间:2022-2-7 21:33
常规打印方法
在STM32的应用中,我们常常对printf进行重定向的方式来把打印信息printf到我们的串口助手。在MDK环境中,我们常常使用MicroLIB+fputc的方式实现串口打印功能,即:
要实现fputc函数的原因是:printf函数依赖于fputc函数,重新实现fputc内部从串口发送数据即可间接地实现printf打印输出数据到串口。
不知道大家有没有看过正点原子裸机串口相关的例程,他们的串口例程里不使用MicroLIB,而是使用标准库+fputc的方式。相关代码如:
  1. #if 13 g  Q) J, l1 p/ O0 q9 A
  2. #pragma import(__use_no_semihosting)
    % d* N3 U; A4 Y
  3. //标准库需要的支持函数
    4 h0 r/ ]- n; [, @% N. m! M0 C- P0 d
  4. struct __FILE# w  g5 p, h7 r/ R/ ]4 R5 O9 a) j
  5. {$ q. L+ J6 d. U- H! J
  6.     int handle;5 ~: n6 K+ c" m. O
  7. };
    8 f+ ?! A6 Z: o8 T# Y3 B
  8. , _( U8 g3 o6 O# }) c
  9. FILE __stdout;1 L/ h/ Z+ v  o  L% _) s
  10. /**9 h! S; s6 C2 }' O4 X: a
  11. * @brief        定义_sys_exit()以避免使用半主机模式0 I$ g; j2 a( g
  12. * @param        void
    4 B9 Q# I% l: X: |5 ~4 i; M
  13. * @return  void
    ( M& c( ?0 C5 |7 p6 ~' c
  14. */
    ' [+ n- @8 G) ?, v1 J( r
  15. void _sys_exit(int x)
    , h( h) F/ A) T
  16. {
    ; |: F5 U/ T$ W* A# r" L
  17.     x = x;
    + [& O* U2 v* @! `$ h2 Y. e
  18. }
    ( p7 S$ a- W  a- K- m( X
  19. $ U0 y1 B' b8 G' y# s; n" s
  20. int fputc(int ch, FILE *f)# w2 \8 e- B: j1 A
  21. {5 Z, C9 W! o6 |5 i
  22.     while((USART1->ISR & 0X40) == 0); //循环发送,直到发送完毕( ~. K) p7 s: T  [5 y* S
  23. 9 V1 o" S& W  c$ q; Q( c  [
  24.     USART1->TDR = (u8) ch;' n5 L2 ?8 m/ I7 I+ i2 e
  25.     return ch;
    $ A; N! q' n6 y
  26. }+ n% @* \9 }" F2 H' g" n+ g' i
  27. #endif
    / D2 S  F: C3 ?) M, M' F- L
复制代码
! E$ B, }, S+ m) A. a+ `1 `* [& A
自己实现一个打印函数
以上的几种方法基本上是改造C库的printf函数来实现串口打印的功能。其实我们也可以自己实现一个串口打印的功能。
printf本身就是一个变参函数,其原型为:
  1. int printf (const char *__format, ...);
    - P% J, ?6 N2 s4 t) W; i' O
复制代码
  O1 T# B* o- d5 F! K
所以,我们要重新封装的一个串口打印函数自然也应该是一个变参函数。具体实现如下:
1、基于STM32的HAL库
  1. #define TX_BUF_LEN  256     /* 发送缓冲区容量,根据需要进行调整 */
    . E/ X9 [6 A/ d: N8 X9 @4 ]6 J% L
  2. uint8_t TxBuf[TX_BUF_LEN];  /* 发送缓冲区                       */
    1 ]8 `4 b* v& K' }7 `# ?+ l
  3. void MyPrintf(const char *__format, ...)
    + F# k# ]* r" G. B6 f% s& Y
  4. {
    8 N% ~8 g: u$ v+ e
  5.   va_list ap;
    % h- F; @, t9 f, e+ y, y: a2 m
  6.   va_start(ap, __format);) S/ P9 h. z+ @5 j: M+ j
  7.   
    ' d1 O8 m9 u2 G6 v. o: ?  V# v
  8.   /* 清空发送缓冲区 */
    6 W5 z1 `9 z7 i9 D% |
  9.   memset(TxBuf, 0x0, TX_BUF_LEN);( }3 M1 q9 K1 G' e& i4 s
  10.   # x& Y" J) l7 J
  11.   /* 填充发送缓冲区 */2 R" l/ D0 U  n' h/ E& g
  12.   vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);) O8 c6 @6 b' h' H$ C! {
  13.   va_end(ap);
    ' f  ~5 H& y/ Y  \. x! g& y2 ~0 ~
  14.   int len = strlen((const char*)TxBuf);2 N; o1 P, v/ @7 m
  15.   + l8 Z# L# x! c' y  W% [8 N
  16.   /* 往串口发送数据 */2 V! O  @4 q, |4 G$ }1 _
  17.   HAL_UART_Transmit(&huart1, (uint8_t*)&TxBuf, len, 0xFFFF);% P+ j- n. W7 s+ X. m8 j
  18. }7 D+ Z; P' h2 F+ I, ~
复制代码

/ _; k- c, Q1 Y/ E
因为我们使用printf函数基本不使用其返回值,所以这里直接用void类型了。自定义变参函数需要用到va_start、va_end等宏,需要包含头文件stdarg.h。
这里我们使用的是STM32的HAL库,其给我们提供HAL_UART_Transmit接口可以直接把整个发送缓冲区的内容给一次性发出去。
2、基于STM32标准库
若是基于STM32的标准库,就需要一字节一字节的循环发送出去,具体代码如:
  1. #define TX_BUF_LEN  256     /* 发送缓冲区容量,根据需要进行调整 */$ ?6 J. M5 J. |# ~) e3 n% u$ t
  2. uint8_t TxBuf[TX_BUF_LEN];  /* 发送缓冲区                       */+ D# `% [! w9 J8 E  T  \
  3. void MyPrintf(const char *__format, ...)- K& H$ p9 n. k8 l  ?; f; s
  4. {6 v# y0 e" r* m5 x; m, }/ d+ j
  5.   va_list ap;
    1 U9 z" E8 f4 K% X. D
  6.   va_start(ap, __format);) O* I5 z; o$ p- V% t
  7.     2 o$ w8 D2 t; L
  8.   /* 清空发送缓冲区 */
    5 W; U8 ~$ g$ [- X" ]
  9.   memset(TxBuf, 0x0, TX_BUF_LEN);$ @4 m! u8 H2 A5 Z6 \
  10.    
    / G0 ~: O% r4 A9 d4 w$ t
  11.   /* 填充发送缓冲区 */; y8 g$ |' m  z6 t  p7 i+ U
  12.   vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);  b) a; v$ ?  r6 ^
  13.   va_end(ap);
    8 L3 ?4 C1 E& m4 i0 K9 @
  14.   int len = strlen((const char*)TxBuf);7 r" v4 D+ p% f$ @" k6 i
  15.   4 Q0 B4 {3 l& D4 T" T& V
  16.   /* 往串口发送数据 */
    4 v) V# X# O: l' ?! c
  17.   for (int i = 0; i < len; i++)
      M, R( H; d7 B7 A9 T* m8 n6 N- m* i
  18.   {6 `+ [" I9 x5 _% A& V
  19.         while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);   
    0 @( h" V0 Q4 o/ Y; E
  20.         USART_SendData(USART1, TxBuf[i]);9 i9 `, X7 f, U$ `$ Z* o
  21.   }0 U& Z/ _$ M( ~5 I
  22. }
    3 P7 C6 l5 K! z+ m& }; \4 g
复制代码

; E% _- |+ C; L8 O4 P
测试结果:
我们也可以使用我们的MyPrintf函数按照上一篇文章:======的方式封装一个宏打印函数:
7 X$ t7 \* u" d
2 C- |) p# a4 B! O4 _
以上就是我们自定义方式实现的一种串口打印函数。
但是,我想说:对于串口打印的使用,我们没必要自己创建一个打印函数。看到这,是不是有人想要打我了。。。。看了半天,你却跟我说没必要用。。。
哈哈,别急,我们不应用在串口打印调试方面,那可以用在其它方面呀。
(1)应用一:
比如最近我在实际应用中:我们的MCU跑的是我们老大自己写的一个小的操作系统+我们公司自己开发的上位机。我们MCU端与上位机使用的是串口通讯,MCU往上位机发送的数据有两种类型,一种是HEX格式数据,一种是字符串数据。
但是我们下位机的这两种数据,在通过串口发送之前都得统一把数据封包交给那个系统通信任务,然后再由通信任务发出去。在这里,就不能用printf了。老大也针对他的这个系统实现了一个deb_printf函数用于打印调试。
但是,那个函数既复杂又很鸡肋,稍微复杂一点的数据就打印不出来了。因此我利用上面的思路给它新封装了一个打印调试函数,很好用,完美地兼容了老大的那个系统。具体代码就不分享了,大体代码、思路如上。
(2)应用二:
我们在使用串口与ESP8266模块通讯时,可利用类似这样的方式封装一个发送数据的函数,这个函数的使用可以像printf一样简单。可以以很简单的方式把数据透传至服务端,比如我以前的毕设中就有这么应用:
, ^2 O+ x* r- v4 n4 U: O% z6 U0 [4 b
( G" {! a. j" E# c5 E
5 i% p" o" }0 G7 F* d4 v' Y
收藏 评论0 发布时间:2022-2-7 21:33

举报

0个回答

所属标签

相似分享

官网相关资源

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