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

keil+stm32+jlink 用swd方式printf输出

[复制链接]
sayuenala 发布时间:2016-7-17 14:31
使用ITM机制实现调试stm32单片机,实现printf与scanf。
0 t$ T" S+ v6 W$ ]
) K1 d" X/ T1 i$ t) R% j; M1. ITM简介
* F5 X2 D4 a% B0 [  D% SITM机制是一种调试机制,是新一代调试方式,在这之前,有一种比较出名的调试方式,称为半主机(semihosting)方式。; [4 ^1 H7 Z! t2 E. [

; B8 c1 F0 E) Y  z在pc上编写过C语言的人都知道,printf可以向控制台输出,scanf可以从控制台获取输入,这里的printf/scanf都是标准库函数,利用操作系统的这些函数,我们可以很方便的调试程序。在嵌入式设备上(如stm32单片机平台上)开发工具(如MDK/IAR)也都提供了标准库函,自然也提供了printf/scanf函数,那么这些函数是否可以使用呢? 问题来了,printf向哪里输出呢?并且大部分情况下,也没有键盘,又如何使用scanf实现输入呢?2 k4 \/ N0 B  F; u8 I( }& L8 C

, ^( x" b5 v  w' G/ l我们都知道,嵌入式设备一般的使用仿真器,如常见Jlink/ulink,可以实现烧录,单步,下断点,查看变量,等等。仿真器将PC机和单片机连接器来。聪明的设计者们就在考虑是否可以借助仿真器,使得单片机可以借助PC机的屏幕以及PC机的键盘实现printf的输出和scanf的按键获取。
- F: [0 R& Q& h0 ]+ i也就是说,如下的hello,world程序
4 ~1 u5 Z1 }! ~7 ~" B
& ^! e6 D# M, l9 |- n$ B
  1. #include <stdio.h>  
    $ ^2 D+ b( {7 X, o
  2. int main()  
    2 m! g+ r3 H, Y$ z0 [
  3. {  
    3 G. y7 w5 B7 ?5 y
  4.         //硬件初始化  ! \4 o% P; v" H8 }* u
  5.         //....  * r! D/ z9 ?3 B' J( r
  6.         printf("hello, world");  ! C2 ^/ ]( I0 ~5 w" s
  7.         for(;;);  / y$ C; t4 e3 d  T( G" N- \
  8. }  
复制代码
5 \( |! n/ q8 o& s& v  v8 j
# Z+ a* q- @4 U1 H: q6 q- a

. c8 |$ l' R, R" [4 S6 X" V6 c9 U- l' R
这个程序烧录到单片机中后,仿真器连接接单片机与PC,开始在线调试后,那么这个程序会将"Hello, world"输出到PC机上,在开发工具(MDK/IAR等)的某个窗口中显示。' N) e& t4 @1 \7 h  ~

0 x* o+ \( k: N这就相当于,单片机借助了PC机的显示/输入设备实现了自己的输出/输入。这种方式无疑可以方便程序开发者调试。
7 v6 E6 v! m5 I
/ D* K- t  L* o* ?/ l这种机制有多种实现方式,比较著名的就是semihosting(半主机机制)和ITM机制。0 g# E4 j5 z" t) ~# q3 R; y( M3 b
ITM是ARM在推出semihosting之后推出的新一代调试机制。现在我们来尝试一下这种方式调试。, a8 K9 ~- M0 [- |' ^/ B) C7 r

: M& K" r6 ?5 \/ J) b2. stm32使用ITM调试8 e2 p" E# l" a
MCU:stm32f207VG
: h1 b2 R# E0 t5 e# E# T7 i仿真器:Jlink V8. K' r  \# c  x4 t' s  }. v- L
IDE:MDK4.50; e4 e  J+ d- M4 x2 b/ ?! i
! W1 u7 b* u0 X
2.1 硬件连接" ?; `9 \; z3 i: h4 m
ITM机制要求使用SWD方式接口,并需要连接SWO线,一般的四线SWD方式(VCC SDCLK,SDIO,GND)是不行的。标准的20针JTAG接口是可以的,只需要在MDK里设置使用SWD接口即可。8 J' k% x, j0 B! u* r2 p( B

  a4 Y( Y% K2 G" v2.2 添加重定向文件) h* \0 W2 B3 c/ j) S' K
将下面的文件保存成任意C文件,并添加到工程中。这里对这个文件简单说明一下,要知道我们的程序是在单片机上运行的,为什么printf可以输出到MDK窗口里去呢?这是因为 标准库中的printf实际上调用 fputc实现输出,所以我们需要自己编写一个fputc函数,这个函数会借助ITM(类似于USART)提供的寄存器,实现数据的发送,仿真器会收到这些数据,并发往PC机。# P$ n0 b* D# }/ _; u& A

$ e# E2 P, D- U实际上,如果你的单片机和一块LCD连接,那么你只需要重新实现fputc函数,并向LCD上输出即可,那么你调用printf时就会输出到LCD上了。这中机制,就是所谓的重定向机制。! n% d" e  ]: I0 s8 Y+ v/ G: R

" ]1 y8 Z" p$ y& b$ v3 F
- l4 O# L2 A$ U! b  Y
  1. #include <stdio.h>  1 P/ C( L0 W: ~; C2 W$ }  Y  _
  2.   
    6 c) _5 Y* R6 D# _8 U, F4 l. X
  3. #define ITM_Port8(n)    (*((volatile unsigned char *)(0xE0000000+4*n)))  
    4 {+ F- k4 @1 q$ [9 S
  4. #define ITM_Port16(n)   (*((volatile unsigned short*)(0xE0000000+4*n)))  
    ! j8 y  ^0 {; t/ H2 `0 N
  5. #define ITM_Port32(n)   (*((volatile unsigned long *)(0xE0000000+4*n)))  
    , ?- Z/ e5 n3 j! G  l( b2 B
  6. #define DEMCR           (*((volatile unsigned long *)(0xE000EDFC)))  6 @+ f0 ~4 ]8 R% W5 e- N% {8 r; a. I
  7. #define TRCENA          0x01000000  
    $ E$ B& T! y! w) G: O; q- v" T6 @/ n: f
  8.   
    # H, r0 g3 D4 g. V+ F6 z
  9. struct __FILE { int handle; /* Add whatever you need here */ };  
    % j6 z. t( p* \+ h3 ^' D) F
  10.     FILE __stdout;  
    9 o' \) x% B+ |0 V, Z! r; v; C% n
  11.     FILE __stdin;  1 z6 W8 x6 o0 H9 F
  12.       8 O, B$ B2 h7 }& p' [0 Q
  13. int fputc(int ch, FILE *f)   
    2 u6 S) R0 W% ~
  14. {  
    : q! f9 T3 K7 m' S' \+ {1 N: {
  15.     if (DEMCR & TRCENA)   
    ' F" \" L$ ~+ w- T/ ~7 g0 v
  16.     {    n: Y/ S8 S# |3 Z) O( w6 R# c# c
  17.         while (ITM_Port32(0) == 0);    \5 E$ d: n5 J  z4 L: M) ~
  18.         ITM_Port8(0) = ch;  
    % J5 Y. W$ R) s; n; S
  19.     }  ! ~4 f9 F! p* N8 O
  20.     return(ch);  ) p, s' \! Y  h% z, b1 e
  21. }  
复制代码
1 O  e0 c1 @3 @, ^- Y0 r- `# V/ c* `2 S

$ h& b, O- b" K& O$ W, Z+ T2.2 配置JLINK的初始化配置文件6 X' c5 b, V$ }0 k! W% e9 V1 |% Q
: o% O! P5 y' q3 k
将下面文件放置在你的工程下,并取任意名称,这里笔者取名为 STM32DBG.ini
6 D- \% ]6 g3 j" w* e7 r( Y  k$ w6 U1 k/ }2 Z

( e3 h0 a4 ^0 ^3 I% R! q! g
  1. /******************************************************************************/  ! g+ m1 b: y/ W; [6 O* z7 t" C
  2. /* STM32DBG.INI: STM32 Debugger Initialization File                           */  / z8 Y  k$ t$ C& q
  3. /******************************************************************************/  : E% S# o9 f" a! [& S
  4. // <<< Use Configuration Wizard in Context Menu >>>                           //   
    9 ]  L: v' k# `
  5. /******************************************************************************/  
    8 q0 x# ^# m: M* A( b3 P$ Q7 d' j- h
  6. /* This file is part of the uVision/ARM development tools.                    */  
    $ ^& o# g6 S' p% |
  7. /* Copyright (c) 2005-2007 Keil Software. All rights reserved.                */    G9 V4 j/ V; p$ @) A
  8. /* This software may only be used under the terms of a valid, current,        */  # I0 |* ]: v3 }  q% f
  9. /* end user licence from KEIL for a compatible version of KEIL software       */  / b8 h1 o3 Y2 W1 q" n1 j
  10. /* development tools. Nothing else gives you the right to use this software.  */  % }6 p, N6 G8 i) A
  11. /******************************************************************************/  - X2 Y$ t3 O0 r
  12.   
    9 ]- H  ~5 `: U" ^
  13.   
    7 V( s/ X6 i# U) L4 m$ K6 N- ~! q
  14. FUNC void DebugSetup (void) {  
    , `& f: }8 L3 C9 e7 z% W8 t. i
  15. // <h> Debug MCU Configuration  / t! z: [( d# F# O# J5 ^1 k, T
  16. //   <o1.0>    DBG_SLEEP     <i> Debug Sleep Mode  0 @. m3 y7 {7 B. u9 s
  17. //   <o1.1>    DBG_STOP      <i> Debug Stop Mode  - e+ d5 r' K. \" e1 v9 X
  18. //   <o1.2>    DBG_STANDBY   <i> Debug Standby Mode  
    1 N/ p# i* ]" x! M+ o5 ?
  19. //   <o1.5>    TRACE_IOEN    <i> Trace I/O Enable   
    : ?5 x4 ]! x9 ^) i" z! O7 v% P& p
  20. //   <o1.6..7> TRACE_MODE    <i> Trace Mode  
    ; U, n9 C4 y8 n
  21. //             <0=> Asynchronous  / C# R' H+ S3 q# y+ D
  22. //             <1=> Synchronous: TRACEDATA Size 1  # X  o4 Q9 v2 V% L
  23. //             <2=> Synchronous: TRACEDATA Size 2  
    # q  G9 H! {2 ?. Q5 c/ c0 i
  24. //             <3=> Synchronous: TRACEDATA Size 4  
    5 l/ l5 W3 y. i" Z
  25. //   <o1.8>    DBG_IWDG_STOP <i> Independant Watchdog Stopped when Core is halted  
    / Z! F- x9 ]7 B/ u. H+ r
  26. //   <o1.9>    DBG_WWDG_STOP <i> Window Watchdog Stopped when Core is halted  
    : u3 p9 Z& e0 b4 u) w& b+ n0 o
  27. //   <o1.10>   DBG_TIM1_STOP <i> Timer 1 Stopped when Core is halted  ) [% ^9 v1 M, M
  28. //   <o1.11>   DBG_TIM2_STOP <i> Timer 2 Stopped when Core is halted  ' I" A% T( F( N$ A+ M
  29. //   <o1.12>   DBG_TIM3_STOP <i> Timer 3 Stopped when Core is halted  # j9 L3 j. c8 m( A
  30. //   <o1.13>   DBG_TIM4_STOP <i> Timer 4 Stopped when Core is halted  1 e2 U' G& d0 ?2 ~% O
  31. //   <o1.14>   DBG_CAN_STOP  <i> CAN Stopped when Core is halted  
    9 |. r: p4 g1 Q. N, e
  32. // </h>  
    & ?/ ~6 s" X9 {3 q. P+ S
  33. _WDWORD(0xE0042004, 0x00000027);  // DBGMCU_CR  - f( E& |& H+ b$ f: V: W4 ?
  34. _WDWORD(0xE000ED08, 0x20000000);   // Setup Vector Table Offset Register  1 k' G0 v; {' v1 }6 i
  35. }  & V3 f9 E% f8 x3 [* E2 }; w
  36.   + t& a1 F" v2 H2 q% c- [# J# J$ ~+ e4 y
  37. DebugSetup();                       // Debugger Setup
复制代码

# X1 ?; \/ A! L$ \% j  M3 Q2 p9 D( m* w
这里对这个文件做简单的解释,
( V) b5 {/ s& k$ K_WDWORD(0xE0042004, 0x00000027); // DBGMCU_CR
/ _4 q) b9 W0 X! [2 e这一句表示想 0xE0042004地址处写入 0x000000027,这个寄存器是各个位表示的含义在注释中给出了详细的解释。 0x27即表示) R9 M, f" I( k
        BIT0 DBG_SLEEP
6 ^; y, ], p# [8 v' T6 i( G: p        BIT1 DBG_STOP, u# _4 Y: l. A9 D
        BIT2 DBG_STANDBY
2 l$ f1 J. C6 z* F        BIT5 TRACE_IOEN0 t0 C% Y& }4 v- e; b
注意,要使用ITM机制,必须要打开BIT5。
. K, B5 F5 b0 m& w2 ]6 r" R; E% b/ H( L! W: a* H4 v
打开MDK工程,按照下图修改。
0 I. u& A& F7 m% F% m) G& H$ S
1.jpg
4 p; g, o) }5 N3 ~4 G

( ^* m! b* W# e4 E7 R7 d2 ~9 K7 j2.3 MDK中对JLINK的配置% |# n0 [/ P' \, D; k
2.jpg / \. M, E7 G0 d) x" s7 v7 i1 ]9 H( c
  H2 H- Z# D1 u7 j
下图中注意两点
8 o  y; C/ O9 Y$ K1 v) E1). 这里的CoreClock是120M,因为笔者使用的是stm32F207VG这款芯片,并且时钟配置为120M,所以这里填入120M,如果你使用stm32F10x,时钟配置成72M,那么这里需要填入72M。即需要跟实际情况保持一致。( `4 e# ?& X( n& s
2). 最后一定要将 0处打勾,并将其他bit位上的勾去掉,最好与此图保持一致,除CoreClock外。
# W3 s, h% m6 |
3.jpg 4 N% v. l7 D; }0 O

! \3 r( s: l" w8 m( m2.4 烧录程序,并启动调试。可以看到,笔者在程序源码中插入了一句printf语句输出,然后按照下图,就可以看到程序的输出了。, w% {2 _3 x( V, H3 x
4.jpg
; \5 V0 U2 j* z/ a( `5 E

9 [, Z6 Y0 _' X( r% C8 c3. 综合版本使用scanf和printf: `# x$ W4 g1 T: N' @0 v7 Q' i- L
3.1 添加retarget文件7 I! p8 S: j  y6 t4 \" d
将如下代码保存成retarget.c,然后加入到工程中。& O; l/ B  _7 P6 q/ Y1 }

$ V% ^) ?/ H! n# t
2 b4 |% [& H6 w( ^% I  Y
  1. #pragma import(__use_no_semihosting_swi)  
    % m$ p3 E, j, E$ M+ C' [5 M
  2.   + f5 f% h& I- T: X, j1 d8 t0 G
  3. struct __FILE { int handle; /* Add whatever you need here */ };  $ a" W, ~' b6 t8 P8 P
  4.     FILE __stdout;  
    - {- d' V, f! j
  5.     FILE __stdin;  
    ; B+ ?& K' |& H0 ]3 t. o* h/ V
  6.       
    8 S# ~) Z. I1 p% d; H, G4 d* e
  7. int fputc(int ch, FILE *f)   
    " l3 c  p- k5 v7 d7 f' n
  8. {  1 e. _+ `& l# I5 g4 {' K8 Z
  9.     return ITM_SendChar(ch);  
    ) O2 |! L% L0 m; R% x
  10. }  : m6 Q" i. D; n: I1 j2 ?/ [) `
  11.   8 m5 b; c  I: y7 X& R3 b
  12. volatile int32_t ITM_RxBuffer;  
    ! L3 z4 a! Z& Y5 Q& }8 I
  13. int fgetc(FILE *f)  ' b" l0 m  ]7 S  T, S
  14. {  + M1 |0 e% p* e5 q4 c8 U
  15.   while (ITM_CheckChar() != 1) __NOP();  
    8 g! [6 R8 X9 Q3 J/ \# _
  16.   return (ITM_ReceiveChar());  ) ?4 z9 ]/ J- s
  17. }  2 Z2 l& a+ Z2 O/ [0 t
  18.   
    7 E8 f) D  p' h& U' P- D
  19. int ferror(FILE *f)  5 m' ^. r2 R3 B; J* _) u
  20. {  
    6 O) R. m- |# H; ?
  21.     /* Your implementation of ferror */  
    0 r2 j- @' ~" L: b; d
  22.     return EOF;  
    3 U4 j/ Y* p7 o9 k, D: d9 P0 b
  23. }  " Z7 _) ~$ J  I9 m4 A
  24.   9 _/ g$ A  W! \7 ?# z4 n1 n% Z
  25. void _ttywrch(int c)  
    3 N" E/ |0 E, _6 B( a
  26. {  
    & X7 J$ ~8 I/ g
  27.     fputc(c, 0);  
    - f4 q4 d* H9 y* N7 i4 ?) J/ k
  28. }  
    2 q( x4 X# J, Z% I- }
  29.   ( u2 l/ Y: m7 e# l# ~6 d! _; R
  30. int __backspace()  4 P. O- ?% t# z: p2 }
  31. {  
    3 I* D: ?  [+ `9 A4 @  K. c3 I) Z
  32.     return 0;  
    % Q4 N! O+ H1 ~8 a; u
  33. }  
    5 r. h+ F% V5 k. s; W" l( i3 C# Q1 d
  34. void _sys_exit(int return_code)  / C  e$ G1 |- _7 M! ~/ @& a, {
  35. {  
    + J) l" B6 o8 T
  36. label:  4 I' L+ ~) e4 s' D, b3 J- e
  37.     goto label;  /* endless loop */  
    - I% |; W& `9 X' F6 i" K% b
  38. }  
复制代码

' `' j( {+ Q8 S; M0 D6 B( h8 m9 X) r; _) S  |' n' d* P

, T6 z4 a+ m& X, \! }- i& D
3.2 编译运行
1 N* O) T1 `# ~编译,烧录,运行,打开Debug (printf) viewer,就可以看到输入,参看下图" P. y7 x3 p1 h- g1 n
5.jpg
) R! p  N6 G. ~9 J, o" G# y0 s
2 C4 H% i: M% }/ Z
这里对retarget.c文件做几点说明.
2 p* i2 k! l* X+ q1). 上面的代码实际是在X:\Keil\ARM\Startup\Retarget.c上修改而成的,scanf依赖的函数共有两个,fgetc和__backspace都需要实现,如果缺少__backespace函数,则scanf胡无法从Debug Viewer Dialog 窗口获取输入。另外上面提供的代码只是个demo,用于演示效果,用于生产时应该处理的更完善一些。见参考文献[1]
: F, J8 U3 L$ f9 }4 M, i, Q8 v
& b# {" ]* G" U0 F' T9 {2). 函数ITM_SendChar,ITM_CheckChar,ITM_ReceiveChar在库文件CMSIS\Include\core_cm3.h中。
  }/ _9 q" k$ [& [& Y% G: h. \/ `' h3 n, Q. F( D
3) 查看函数的符号引用关系,可以通过生成详细的map文件来查看。命令行增加 --verbose --list rtt.map选项即可生成名为rtt.map的文件。
$ v1 J# ~$ v3 b+ S0 G/ D0 `% ]8 D5 `( M$ i
4. ITM与RTT结合(待实现)
+ n: t0 U* Z, Ygrissiom 写道:5 q/ }  W6 m" I% [( e2 B' {/ C
忽然想到,或许可以把这个半主机做成 device,然后 rt_console_set_device("semi") 就可以直接用半主机做 finsh/rt_kprintf 了…… 不知可行不可行……
# Z" Z% s9 U( ]4 `" [3 l% h3 n6 G3 H' g! z0 G
prife: ITM的接收不知道是否支持中断,目前接收字符使用是轮询方式。如果是中断才有意义。这样可以把ITM设备做成一个 rtt 的device了,让finsh跑在 Debug printf Viewer窗口上。以后只要接一个jtag/SWD口就可以调试了,不用再接串口线了
/ W, _& b# Q( h
6 J% s/ h- L! F, K# L
赞 1 收藏 11 评论6 发布时间:2016-7-17 14:31

举报

6个回答
stary666 回答时间:2016-7-17 16:23:06
想带你骑单车ing 回答时间:2017-10-11 17:38:11
楼主厉害
epochal 回答时间:2017-10-11 19:37:52
hades2013 回答时间:2017-10-11 20:37:44
学习,,来看看。。
1 d! _3 a8 G3 U' n+ ^1 }
枫天123 回答时间:2018-1-9 11:46:22
搞不定,全是编译错误
jorry 回答时间:2018-9-18 21:44:12
赞赞!!!!

所属标签

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