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

【经验分享】STM32F103自定义的printf函数的实现

[复制链接]
STMCU小助手 发布时间:2022-3-18 22:21
 在单片机中使用最多的通信接口基本就是串口了,说起串口就不得不提串口中最常用的一个函数就是打印函数printf()函数,通常使用这个函数都是直接调用库函数来实现的,在单片机中如何要使用printf()函数一般都是在串口中进行重映射。如要在串口1中使用printf()函数,可以使用下面的代码进行重映射。
/ A$ \! ?3 V# d* p6 E* _" Z" }7 i' S+ D/ v
  1. //加入以下代码,支持printf函数,而不需要选择use MicroLIB
    . i3 j5 \+ w' j
  2. #if 1
    * u9 g) Z9 z, w  F+ n2 r
  3. #pragma import(__use_no_semihosting)
    ( @# l! }7 Y- w4 t4 Z- N% r
  4. //标准库需要的支持函数; B1 x( u( \! t1 V% c# a8 F5 j" I+ a$ j
  5. struct __FILE
    . `5 `0 t+ Z+ s# [# _
  6. {
    ' T4 t! l' t4 I" Y* [, o
  7.     int handle;
    0 q7 S! `( Z% _; |) A# o, Q" w0 N
  8. };) @( w$ y+ A/ x$ O& j
  9. FILE __stdout;
    - ?" k( l5 e+ H. K4 }" Z; E
  10. //定义_sys_exit()以避免使用半主机模式* A+ C9 D; [& q  y- t% k
  11. _sys_exit(int x)
      t" c5 q8 d( G& c
  12. {0 K7 W2 D) |& [5 d: c
  13.     x = x;
    6 @2 D- H+ k/ L' q6 `1 P
  14. }9 p' Y7 W9 }6 P
  15. //重定义fputc函数+ a/ v. c- j3 v
  16. int fputc(int ch, FILE *f)
    5 y) W+ q* f; h2 N) X
  17. {7 u' L* B( z* g. Z8 t" k- y
  18.     while((USART1->SR & 0X40) == 0); //循环发送,直到发送完毕
    5 M9 N3 [* F  o' j
  19.     USART1->DR = (u8) ch;6 W# L+ U/ K5 ]* j& u% O  R: e4 X
  20.     return ch;( B- k7 h5 \/ l! ]- N- y
  21. }2 ^9 m% H; v& q( ~$ y
  22. #endif
复制代码
* y4 S2 Z% D7 N/ F, M$ u
  在串口1的c文件中,添加上面的代码后,使用printf()函数时,就可以通过串口1来打印了。那么不通过库函数的话,自己能不能实现printf()函数的功能呢?当然是可以的,下面就通过串口2来演示,如何在串口2上直接实现printf()函数的功能。
/ E5 C1 D+ B- h- v/ V8 K) O" q6 M* ]3 F9 A, Z! y9 H
  1. void UART2_Init(u32 bound)- a: w6 `) r7 h6 N, r& _: {
  2. {
      w/ [) Q/ o+ N, L
  3.     GPIO_InitTypeDef GPIO_InitStructure;
    + Y# p3 Z5 z8 t
  4.     USART_InitTypeDef USART_InitStructure;6 A, E3 z1 Z" G- k
  5.     NVIC_InitTypeDef NVIC_InitStructure;
    1 Q, j5 z: A; B5 _0 m& w. `) P

  6. 7 W9 e0 `! `7 V: {
  7.     // 1、串口时钟使能 GPIO时钟使能/ M3 R/ B5 ~6 q# q0 }
  8.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    " ?9 Z* b, V7 r! M  @
  9.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);) A7 P0 B  N) U% H
  10. 3 Y! v. |' u! W3 O8 V2 a
  11.     // 2、串口复位! @+ B$ i. _4 K
  12.     USART_DeInit(USART2);
    , o9 h! B7 O. y$ ^+ C& l3 {

  13. " j$ v0 h" h: ~5 {4 ^
  14.     // 3、GPIO端口设置, t' u* r" O1 X' ~
  15.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;                         //PA2 TX. D1 s5 u! ^3 [5 S' S7 O
  16.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;          //复用推挽输出
    / l, _+ [$ M( {8 h+ B' x9 b1 J
  17.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;; ~* V* i1 H$ S# o0 @
  18.     GPIO_Init(GPIOA, &GPIO_InitStructure);
    6 S' }8 x* ?: X( c( t7 y1 R

  19. ; a# r/ A4 C" w1 |. \2 e; f' p
  20.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;                               //PA3 RX
    8 Z7 S, ]+ V" }
  21.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入2 t5 T) Y% {* k( O
  22.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;$ W1 C1 W. J# C: m# g  E5 C
  23.     GPIO_Init(GPIOA, &GPIO_InitStructure);
    ( K# D7 j. r8 P+ G" [
  24. $ z- o' F) m1 x, v$ T
  25.     // 4、串口参数初始化5 w: z; y7 R, Y: P
  26.     USART_InitStructure.USART_BaudRate = bound;
    % w, r% G2 n1 z7 Y3 B7 F% x% @
  27.     USART_InitStructure.USART_WordLength = USART_WordLength_8b;" U! C: b* U. h8 y3 U2 U
  28.     USART_InitStructure.USART_StopBits = USART_StopBits_1;" g3 }+ C% E/ O
  29.     USART_InitStructure.USART_Parity = USART_Parity_No;
    % ^: @1 P4 m* }5 @1 \" _. I
  30.     USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    ; [1 j, ?9 X  ~
  31.     USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    7 p, Y6 k4 n! M" V+ g( Q
  32.     USART_Init(USART2, &USART_InitStructure);
    + q: Y* q/ f, l( a
  33. 9 i; b/ I) u! _
  34.     // 5、初始化NVIC# G" B& u& g/ H) T
  35.     NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
      z3 p6 d0 x: H$ M3 H; t4 j7 \
  36.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
    2 H" M" H+ o. j1 c+ m
  37.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
    # M4 Q1 j% C" }- H$ I9 A3 m
  38.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;9 v+ }; u* ~7 Z1 m8 L1 c% I( e
  39.     NVIC_Init(&NVIC_InitStructure);
    : x  `" s7 i2 C" F9 `
  40. 9 W5 ~1 h' |! g  k
  41.     // 6、开启中断
    4 h5 |- W4 f4 l, X* P3 N
  42.     USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
    : C' B+ W" K9 s9 g
  43. $ J; ]$ m5 B/ _7 G; \9 h- l
  44.     // 7、使能串口
    ; t7 R# b, m4 c8 d( }8 {
  45.     USART_Cmd(USART2, ENABLE);# x3 H: E. _8 R' t6 n
  46. ; ]4 J: P( ?' a! N
  47. }
    ' I) z. W! M" F% q" o
  48. void USART2_IRQHandler(void)
    + v! m# D" _% H/ r7 F$ L
  49. {
    , m& [( p4 n/ A. s
  50.     u8 res;7 ^3 o8 v* G3 x1 F- ~
  51.     if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    - w) t8 i2 _4 _1 P
  52.     {* V* {$ z! f/ j, {4 f5 {
  53.         res = USART_ReceiveData(USART2);
    + v$ J5 q/ E  o& L% p- q+ E
  54.         USART_SendData(USART2, res);                        //把接收到的数据发送出去
    % m6 X; f* F6 C# E4 Z" _! d4 d0 p. i9 o
  55.     }
    ; X$ B  W3 D3 Q8 a; Q4 x9 c
  56. }
复制代码

4 }0 u! p9 U) F. d& s2 o. o9 _" F  首先初始化串口2,初始化方式和正常情况下一样,初始化完成之后开始自定义一个函数来实现printf()函数的功能。9 T1 Z2 k5 |: a7 j( K4 v9 m: m
" l$ k& Z6 f$ J" Z0 ?- p* {
  1. //自定义串口2 的printf 函数) q" q& A6 P, p4 w9 y3 V8 z" ]
  2. char UART2_TX_BUF[200];1 |: @1 z! ~6 |% _: d& V% K* ?/ S
  3. void u2_printf(char* fmt, ...)    //无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
    0 v/ Z0 c; ~/ ~# D  C6 c
  4. {; N& x5 F8 n/ h$ `+ Q
  5.     u16 i, j;  a" B9 N+ M, s5 p7 r
  6.     va_list ap;          //va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
    ( ^, g0 Y7 T4 h9 i+ \% T' S
  7.     va_start(ap, fmt);   //va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束& j" L) ^7 A5 r! e6 O
  8.     vsprintf((char*)UART2_TX_BUF, fmt, ap);        // 把生成的格式化的字符串存放在这里9 t$ B+ ]  j# F& w
  9.     va_end(ap);4 L+ L, O: D7 w. `2 I
  10.     i = strlen((const char*)UART2_TX_BUF);              //此次发送数据的长度% d2 n( a) @: z; R/ f5 @3 D
  11.     for(j = 0; j < i; j++)                                                    //循环发送数据: Q# i# b& G$ P+ L$ z+ c
  12.     {
    5 ~" |" I# }0 Y
  13.         while((USART2->SR & 0X40) == 0);                    //循环发送,直到发送完毕
    5 x* e) d+ j/ B; L( B7 m) H
  14.         USART2->DR = UART2_TX_BUF[j];/ ]8 |. c* n9 A, j0 C9 T. R
  15.     }3 A$ K# {- [" u  j
  16. }
复制代码
& Z$ G. E  z2 S7 i) z7 b$ \% ~
  这个函数名定义为 :u2_printf(char* fmt, …) 使用省略号表示当前参数为可变参数。接下里就可以直接使用这个函数来打印数据了。在主函数中通过一段代码来测试串口1和串口2 printf函数的功能。4 `$ F) a# t+ m) O0 f' D( G6 x8 @' c% d
. j! c/ ~- e) g. C
  1. int main(void)8 P: p, L3 ]4 l- k5 @/ f* t% X
  2. {
    8 z6 A# d# |6 c2 E
  3.     u8 t;8 \/ h& W! R0 |4 T  ]4 ~- ^; ~
  4.     u8 len;
    6 p" [3 h/ Q) _* |0 g( z, v
  5.     u16 times = 0;( i. L9 \$ T% _* [4 I! n
  6.     delay_init();       //延时函数初始化* U& Y5 U0 A% l+ H& ^1 Y$ t' P7 ?) T
  7.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);- M7 |4 a* }4 j. a* n
  8.     uart_init(115200);
    6 b# T0 u/ F7 X( E/ n6 W
  9.     LED_Init();# u9 j# b6 o9 T6 G! L/ e7 r
  10.     UART2_Init(115200);4 I7 Y. P$ @/ i: D1 V- _
  11.     printf("USART1 AND USART2 TEST!!!");
    7 X8 e( b' x: X5 N
  12.     while(1)
    6 p* ~) j# ~3 T) F2 n0 @/ q
  13.     {) z$ I8 I9 U& f# q; A3 j, ~
  14.         if(USART_RX_STA & 0x8000)7 p/ T1 O  e7 w' K5 j
  15.         {2 e; l0 x9 E4 Z- v: E4 ^
  16.             LED1 = !LED1;1 [; ]; y- V; q% L+ O
  17.             len = USART_RX_STA & 0x3fff;                //获取本次接收数据长度
    $ p7 G3 D2 |( i9 H: E' ^: M
  18.             printf("\r\n您发送的消息为:\r\n");
    9 k$ }4 r* Z' _1 g, s
  19.             for(t = 0; t < len; t++)
    " r% U7 r5 A/ `
  20.             {3 p# N5 g! @" [! D/ s3 B0 @6 M- n
  21.                 USART_SendData(USART1, USART_RX_BUF[t]); //向串口1发送数据- V: Y' h* L8 q/ M. ^  `
  22.                 while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET); //等待发送完成( K) Q$ o$ W6 d$ @" E; v7 x
  23.             }) g' W- P/ E) a5 P3 p$ [# f
  24.             printf("\r\n");! _9 Z. _' d" S8 E
  25.             USART_RX_STA = 0;. E7 e- ?. [( N9 f- }# ]
  26.         }. T0 L( U$ e+ D9 D+ d
  27.         else
    3 g) o2 E! a( a% G7 C
  28.         {
    ' M' e6 n- H' R, I2 W, k! w
  29.             times++;
    # z" @0 h% M; n! {: F& X) o
  30.             if(times % 5000 == 0)
    ) [% A2 D6 z/ M% G
  31.             {
    ! @' E. u5 q5 n/ _7 `% V* t) a
  32.                 printf("串口实验\r\n");
    , d/ l" H: E* e; X$ ?0 B+ c
  33.                 u2_printf("串口实验\r\n");                                                                        //调用串口2 printf 函数0 O+ V2 o8 x: b: H' _& V  h: c
  34.             }+ U) q1 E# S: d$ ~, {4 h' O
  35.             if(times % 300 == 0)
    ) c- ?( [7 p, L- y9 I
  36.             {7 A! g1 A6 a2 f& V
  37.                 printf("请输入数据,以回车键结束\r\n");# \( o3 C& P; d0 D1 f. m- E
  38.                 u2_printf("请输入数据,以回车键结束\r\n");                //调用串口2 printf 函数
    8 z3 t) ?' g7 b4 r5 I- X
  39.             }( ~: m1 J" \, z$ M$ n
  40.             if(times % 30 == 0)$ M: [; }) O* }9 W( R# Q
  41.                 LED0 = !LED0;5 {3 N; C# k7 H" @! m6 b& k1 \
  42.             delay_ms(10);& [7 s' U* n: h8 c6 a# r
  43. / Q: C" x& O4 `4 q) I, T
  44.         }! P$ h2 e" f. S

  45. $ N, S' C/ A, R8 K% z! k, z
  46.     }$ [! x' E  V" S
  47. }
复制代码
$ ^' ?/ D# r$ b9 H  T6 I) D
  通过串口1和串口2输出同样的提示信息,然后用串口助手分别给串口1和串口2发送数据,当串口1和串口2接收到数据后就会通过串口打印出来。串口1和串口2输出的提示信息都是用printf函数输出的。7 ]  \3 I3 f4 v7 t! X
5 ?- Z! X+ ~+ g3 s
D5QCO6U0BN45)`L8H2)O0PQ.png 2 Z% O7 N) R" ]5 f8 }5 |& j& B
! g: {* T5 v5 T. }# S9 ~
  可以看出串口2的printf函数和功能和串口1的printf函数功能一样,可以正常输出字符串。也可以使用串口2的printf函数打印变量值,比如在代码中增加一句变量打印的功能。
, T! {1 [" ?7 K3 d5 h5 n+ f, s' v# G6 @
A_X8ZPYP8FCJ}{ZV0Q3C6%6.png
6 @' P2 I" r8 _) B/ H/ I0 X1 v1 t  l# i+ D7 Y/ V- v
  每次输出提示信息的时候,顺便打印一下变量times的值。8 r5 R+ q( h/ a0 s# F
  e; |# ]* N( g
DKY43H1Y}ZZ`{BJH__9V8)M.png , O# {$ ~7 D8 i& O
( `! w7 `1 j) l3 W8 k$ ~2 w0 B
  可以看到变量times的值也可以正常打印。( H. K" }0 _4 h7 e, w. e* @- t- {+ [

# r! B, h' _9 v! K/ o- d. L# e1 c" n0 O3 j) k  f
赞 收藏 评论0 发布时间:2022-3-18 22:21

举报

0个回答

所属标签

相似技术帖

官网相关资源

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