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

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

[复制链接]
STMCU小助手 发布时间:2022-2-7 21:33
常规打印方法
在STM32的应用中,我们常常对printf进行重定向的方式来把打印信息printf到我们的串口助手。在MDK环境中,我们常常使用MicroLIB+fputc的方式实现串口打印功能,即:
要实现fputc函数的原因是:printf函数依赖于fputc函数,重新实现fputc内部从串口发送数据即可间接地实现printf打印输出数据到串口。
不知道大家有没有看过正点原子裸机串口相关的例程,他们的串口例程里不使用MicroLIB,而是使用标准库+fputc的方式。相关代码如:
  1. #if 1' t/ @1 a4 l6 Y  f4 p
  2. #pragma import(__use_no_semihosting)
    ' D* u, c9 _3 u8 S8 K
  3. //标准库需要的支持函数
    ! ^; o& t8 C$ k! D# W; H
  4. struct __FILE; r/ J$ ]  b: X) K4 f6 Z' K% B
  5. {
    . p1 C3 {8 {9 H, ~* u* F* g
  6.     int handle;
    9 h# ^: t1 P: L0 [0 n8 ^$ R0 V
  7. };: `/ Y- C& \& h' Y
  8. 5 `) E' {4 \3 x# n
  9. FILE __stdout;
    ' X7 U" G  J$ L3 o/ n: d
  10. /**
    ' \. g$ s+ D1 s
  11. * @brief        定义_sys_exit()以避免使用半主机模式
    $ L+ ]0 O1 J5 j8 `& ^
  12. * @param        void$ E( S' F9 y/ X; p2 T0 s. \. O
  13. * @return  void
    9 m' t: T$ ]3 X4 e
  14. */, ]( ]5 h& b7 t/ T' D
  15. void _sys_exit(int x)
    7 C/ b* a% @0 U
  16. {
    0 t: I! ?: d. O; d5 s% ~8 i4 M% |
  17.     x = x;
    # E6 I. e) u: Y
  18. }
    4 A2 _" h' @% F0 A( R2 a
  19. ' [6 t- m! p  g( D6 M
  20. int fputc(int ch, FILE *f)0 Q% R9 v! m4 i* ~9 y3 k6 \$ B9 f) M* p
  21. {/ M1 ~: F: z+ n  @/ J( D  q. u
  22.     while((USART1->ISR & 0X40) == 0); //循环发送,直到发送完毕8 e9 Q' U$ \- W

  23. ) N+ |6 S# F: ?# q
  24.     USART1->TDR = (u8) ch;: S* i5 J- l% t5 F! ?# Q5 M! }6 q# v
  25.     return ch;
    ' }; r$ V% @5 _7 G7 U
  26. }2 Y3 n( U$ Y& L4 R
  27. #endif
    ; y& a9 _5 f+ H% N1 I, s* ?
复制代码
5 i& W& g$ N, e9 U
自己实现一个打印函数
以上的几种方法基本上是改造C库的printf函数来实现串口打印的功能。其实我们也可以自己实现一个串口打印的功能。
printf本身就是一个变参函数,其原型为:
  1. int printf (const char *__format, ...);
    ) _; u7 M  l, P8 \2 `
复制代码

, a+ D; `6 _# j* l* ^
所以,我们要重新封装的一个串口打印函数自然也应该是一个变参函数。具体实现如下:
1、基于STM32的HAL库
  1. #define TX_BUF_LEN  256     /* 发送缓冲区容量,根据需要进行调整 */
    * H; C( p: T, V
  2. uint8_t TxBuf[TX_BUF_LEN];  /* 发送缓冲区                       */
    7 \# O6 @( X+ ~% _. v3 \
  3. void MyPrintf(const char *__format, ...)
    ! ^% V& T$ \0 f& _( l) Z
  4. {
    , T1 f# W  _8 T# V
  5.   va_list ap;: e" l7 c; z( W" _/ i
  6.   va_start(ap, __format);
      Q: P* M" B- M! `& v
  7.   9 V! t0 O% V/ x, K! S$ H0 x
  8.   /* 清空发送缓冲区 */' m# i  s5 J5 \/ Z( o
  9.   memset(TxBuf, 0x0, TX_BUF_LEN);; X8 P2 g% T' g# U, u
  10.   - m" D2 n$ T  {- y# f
  11.   /* 填充发送缓冲区 */
    ( Y2 s2 R. E9 S+ {
  12.   vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);7 v* @7 J6 U1 n
  13.   va_end(ap);
    # \' s9 I3 E) j, |. F& `
  14.   int len = strlen((const char*)TxBuf);
    : N3 C; L' d8 y7 X
  15.   
      j( S  N4 p# s; Y
  16.   /* 往串口发送数据 */2 I6 o8 P* e# b5 F0 L6 c9 S
  17.   HAL_UART_Transmit(&huart1, (uint8_t*)&TxBuf, len, 0xFFFF);% ?* F3 ]9 ^8 a4 |% V
  18. }+ q/ |. G9 ~# C2 o. R* T! }
复制代码

8 [* I7 N  B* z8 w  i5 B
因为我们使用printf函数基本不使用其返回值,所以这里直接用void类型了。自定义变参函数需要用到va_start、va_end等宏,需要包含头文件stdarg.h。
这里我们使用的是STM32的HAL库,其给我们提供HAL_UART_Transmit接口可以直接把整个发送缓冲区的内容给一次性发出去。
2、基于STM32标准库
若是基于STM32的标准库,就需要一字节一字节的循环发送出去,具体代码如:
  1. #define TX_BUF_LEN  256     /* 发送缓冲区容量,根据需要进行调整 */6 T% @6 Y# c: d8 Q7 w( u
  2. uint8_t TxBuf[TX_BUF_LEN];  /* 发送缓冲区                       */# \/ I3 p8 a2 V9 W% Q; A1 E+ @% K
  3. void MyPrintf(const char *__format, ...)
    9 t' |9 H" n; `" c% n
  4. {" C* z7 w$ S9 e" |$ h
  5.   va_list ap;
    - e+ i0 z0 g+ K7 H
  6.   va_start(ap, __format);# F3 F3 a$ W2 `& R
  7.     ( K1 j8 Y) s$ t9 b8 X3 S
  8.   /* 清空发送缓冲区 */
    # d+ s3 s$ h+ h- q0 S5 H
  9.   memset(TxBuf, 0x0, TX_BUF_LEN);! V# X; M' I2 ], V6 R
  10.    
    1 [+ j( {/ }) q5 x
  11.   /* 填充发送缓冲区 */! o3 G2 G/ s4 r! @5 v8 T! a' ]
  12.   vsnprintf((char*)TxBuf, TX_BUF_LEN, (const char *)__format, ap);
    4 j" x7 E; D( M7 ]% U9 a; \1 p9 g, d
  13.   va_end(ap);1 T! i5 _  n8 s- s" M; b: \
  14.   int len = strlen((const char*)TxBuf);
    # G1 V$ O% G) }) c9 n
  15.   
    # ], z* I; i. v# H, C5 w. Q7 _
  16.   /* 往串口发送数据 */
    4 R* p! m2 j2 T# n
  17.   for (int i = 0; i < len; i++)
    $ b6 j% a: f- u
  18.   {
    / y0 Y8 d0 O# I
  19.         while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);    ' e+ I8 C  q. g3 b% U& m( @
  20.         USART_SendData(USART1, TxBuf[i]);0 f. F5 R+ ]6 Q
  21.   }
    % O/ s6 U4 B7 n  I* h
  22. }
    2 f8 Z) ~/ t' z: m2 N
复制代码

: y% @7 J8 K" [6 F! A4 {
测试结果:
我们也可以使用我们的MyPrintf函数按照上一篇文章:======的方式封装一个宏打印函数:
6 @6 C9 Y& d' R6 \7 [( x% V$ l- s
' ~. t6 \4 i+ }6 ^* b! Z" G
以上就是我们自定义方式实现的一种串口打印函数。
但是,我想说:对于串口打印的使用,我们没必要自己创建一个打印函数。看到这,是不是有人想要打我了。。。。看了半天,你却跟我说没必要用。。。
哈哈,别急,我们不应用在串口打印调试方面,那可以用在其它方面呀。
(1)应用一:
比如最近我在实际应用中:我们的MCU跑的是我们老大自己写的一个小的操作系统+我们公司自己开发的上位机。我们MCU端与上位机使用的是串口通讯,MCU往上位机发送的数据有两种类型,一种是HEX格式数据,一种是字符串数据。
但是我们下位机的这两种数据,在通过串口发送之前都得统一把数据封包交给那个系统通信任务,然后再由通信任务发出去。在这里,就不能用printf了。老大也针对他的这个系统实现了一个deb_printf函数用于打印调试。
但是,那个函数既复杂又很鸡肋,稍微复杂一点的数据就打印不出来了。因此我利用上面的思路给它新封装了一个打印调试函数,很好用,完美地兼容了老大的那个系统。具体代码就不分享了,大体代码、思路如上。
(2)应用二:
我们在使用串口与ESP8266模块通讯时,可利用类似这样的方式封装一个发送数据的函数,这个函数的使用可以像printf一样简单。可以以很简单的方式把数据透传至服务端,比如我以前的毕设中就有这么应用:
3 Y" U0 J5 Y/ s" D5 Q' A& o# B
& @( T8 Y6 [7 U  r2 u

1 b  T) \4 S9 X) B3 S, u4 h
收藏 评论0 发布时间:2022-2-7 21:33

举报

0个回答

所属标签

相似技术帖

官网相关资源

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