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

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

[复制链接]
sayuenala 发布时间:2016-7-17 14:31
使用ITM机制实现调试stm32单片机,实现printf与scanf。# v8 U7 c) C- b# y8 ?( Q1 H

8 r# O$ X( d) y' K1. ITM简介
* Y) H* X( _! g0 Q6 C4 lITM机制是一种调试机制,是新一代调试方式,在这之前,有一种比较出名的调试方式,称为半主机(semihosting)方式。
! u7 E- x$ C0 I( ?+ D3 \, B' w
- ]0 O  P; v' ]在pc上编写过C语言的人都知道,printf可以向控制台输出,scanf可以从控制台获取输入,这里的printf/scanf都是标准库函数,利用操作系统的这些函数,我们可以很方便的调试程序。在嵌入式设备上(如stm32单片机平台上)开发工具(如MDK/IAR)也都提供了标准库函,自然也提供了printf/scanf函数,那么这些函数是否可以使用呢? 问题来了,printf向哪里输出呢?并且大部分情况下,也没有键盘,又如何使用scanf实现输入呢?$ |, K1 L$ t1 r$ C% [
5 G/ v3 B& i% L. _/ J) Q" u
我们都知道,嵌入式设备一般的使用仿真器,如常见Jlink/ulink,可以实现烧录,单步,下断点,查看变量,等等。仿真器将PC机和单片机连接器来。聪明的设计者们就在考虑是否可以借助仿真器,使得单片机可以借助PC机的屏幕以及PC机的键盘实现printf的输出和scanf的按键获取。
% X* h& }: F$ u2 \% j* A* }也就是说,如下的hello,world程序; P, Z* v0 n9 _: Z

. r8 w' ]8 h) Z3 ]/ p% R8 \
  1. #include <stdio.h>  
    ! g, u+ d# l" b- N8 F. H
  2. int main()  ( k1 p( t& u: C
  3. {  
    ' O5 H9 V4 ]4 O; |7 H5 n" _
  4.         //硬件初始化  : P5 i% T7 @7 o$ I0 M
  5.         //....  
    2 _$ G6 g, u/ f0 @/ K
  6.         printf("hello, world");  # f' w2 J" Z2 L4 U+ ]! @; l3 W
  7.         for(;;);  4 \5 E' O% T$ A- ~
  8. }  
复制代码

. g$ k# H$ T8 S' H
( M  s' k$ ]# E: H# J1 `
( s' i" {/ M" g. c4 P3 b- G' p+ K
这个程序烧录到单片机中后,仿真器连接接单片机与PC,开始在线调试后,那么这个程序会将"Hello, world"输出到PC机上,在开发工具(MDK/IAR等)的某个窗口中显示。
- X2 |; M/ b! X" J! N" K' w: f8 l
5 L! [) D9 L" r+ j这就相当于,单片机借助了PC机的显示/输入设备实现了自己的输出/输入。这种方式无疑可以方便程序开发者调试。* @7 h6 s/ A/ }- o
' G  M8 ~" |4 D2 x7 e
这种机制有多种实现方式,比较著名的就是semihosting(半主机机制)和ITM机制。3 c; Y2 n8 J4 Q
ITM是ARM在推出semihosting之后推出的新一代调试机制。现在我们来尝试一下这种方式调试。
6 h/ u( S( a. i8 i
- u% m. T: Y& Y( i* S6 z2. stm32使用ITM调试
4 m; [" [; k  z0 t' ZMCU:stm32f207VG
( J8 A" @7 Q  w- c6 x* C9 @仿真器:Jlink V8) r2 W6 a8 n7 ^
IDE:MDK4.50
- i0 z8 ]4 s" X% D5 R0 _# i
6 y. v! K; V- n. @/ d2.1 硬件连接
! k" [0 U, Z2 Z" Z7 {0 d8 r9 kITM机制要求使用SWD方式接口,并需要连接SWO线,一般的四线SWD方式(VCC SDCLK,SDIO,GND)是不行的。标准的20针JTAG接口是可以的,只需要在MDK里设置使用SWD接口即可。
8 S) q( }; x% z7 D+ b" U2 R6 @
6 x% W3 ~7 q3 t0 y9 w/ {8 r( P. M2.2 添加重定向文件
6 q8 k% c' [2 a将下面的文件保存成任意C文件,并添加到工程中。这里对这个文件简单说明一下,要知道我们的程序是在单片机上运行的,为什么printf可以输出到MDK窗口里去呢?这是因为 标准库中的printf实际上调用 fputc实现输出,所以我们需要自己编写一个fputc函数,这个函数会借助ITM(类似于USART)提供的寄存器,实现数据的发送,仿真器会收到这些数据,并发往PC机。2 V& H9 f$ L2 p4 R$ i; U3 ^6 Y

  K  I9 z) C; |$ f  u1 K( N/ S实际上,如果你的单片机和一块LCD连接,那么你只需要重新实现fputc函数,并向LCD上输出即可,那么你调用printf时就会输出到LCD上了。这中机制,就是所谓的重定向机制。* d3 _  N7 O* F: D3 Z
- T/ S8 j/ B! f0 Z

8 P2 n; `# I% f, O7 O9 o4 r6 U
  1. #include <stdio.h>  , [: m, w' P% A8 J* ~
  2.   3 K4 G& k3 k, M" a5 p
  3. #define ITM_Port8(n)    (*((volatile unsigned char *)(0xE0000000+4*n)))  / ~/ v& @8 o) z; O
  4. #define ITM_Port16(n)   (*((volatile unsigned short*)(0xE0000000+4*n)))  
    ! Y& L$ n& V% p- i5 M
  5. #define ITM_Port32(n)   (*((volatile unsigned long *)(0xE0000000+4*n)))  5 M- x. j! L1 c5 _
  6. #define DEMCR           (*((volatile unsigned long *)(0xE000EDFC)))  5 K" j  q0 V1 X) y/ g
  7. #define TRCENA          0x01000000    g8 o( i; p9 {, c" g1 E" M* \
  8.   
    + ~$ z9 M7 J# i% b; J* U
  9. struct __FILE { int handle; /* Add whatever you need here */ };  
      U: H' ?& l4 u  P# h" s/ O3 }
  10.     FILE __stdout;  
    % a* x3 k* N& I; l8 G# i# E4 s' b
  11.     FILE __stdin;  
    % ]! N6 S8 H7 n, r, L, C; y
  12.       
    ! O) F+ s  U8 X1 k
  13. int fputc(int ch, FILE *f)   
    / y9 y* x% l0 Q: E8 G, G
  14. {  
    3 e4 U4 n/ l$ _1 c+ T# V3 s
  15.     if (DEMCR & TRCENA)   + Z. r% [( W" G6 y, b" ~
  16.     {  4 ]& s9 S. n! ~" k8 y
  17.         while (ITM_Port32(0) == 0);  
    ; y- b! x3 M' l, m9 n. L! {
  18.         ITM_Port8(0) = ch;  5 e: P6 s5 N# n7 w* U7 V3 J7 p
  19.     }  / L$ M6 c7 |* c" l  ~. M; `3 n
  20.     return(ch);  / T$ C0 ?( Z6 c! `1 M$ P
  21. }  
复制代码

% r. ~# F7 h8 a2 {7 g: G$ c6 ?* P
2.2 配置JLINK的初始化配置文件
, O5 {; f# {4 c3 I  W; v. b! A) {; w: W" I4 J6 v: n0 H
将下面文件放置在你的工程下,并取任意名称,这里笔者取名为 STM32DBG.ini
. u5 N  A% g* Y  ?6 q
  W4 }+ }( R+ [7 O
: ~/ E! X& h- f9 q, V; j$ K
  1. /******************************************************************************/  
    0 S! A- T( u1 L4 ~, P4 L" n# K
  2. /* STM32DBG.INI: STM32 Debugger Initialization File                           */  2 g  M! Z; x. {
  3. /******************************************************************************/  " [, E# h8 v* v, {9 A3 B% C: {$ p; M4 z
  4. // <<< Use Configuration Wizard in Context Menu >>>                           //   
    8 C# o$ b4 [5 t" Z
  5. /******************************************************************************/  
    - B, N: j! \( E. V$ K" i$ K$ [
  6. /* This file is part of the uVision/ARM development tools.                    */  
    + W; Z. @( s8 |5 O
  7. /* Copyright (c) 2005-2007 Keil Software. All rights reserved.                */  5 R- R  s! W0 C$ ?) ?3 M
  8. /* This software may only be used under the terms of a valid, current,        */  ' ^) u  P: s$ m# e
  9. /* end user licence from KEIL for a compatible version of KEIL software       */  
    " F! S1 g6 R  D+ u4 h# d
  10. /* development tools. Nothing else gives you the right to use this software.  */  4 W7 S7 s( Q  P* t
  11. /******************************************************************************/  ) a/ c6 T/ e" T/ M6 O: M
  12.   % H% a2 M! B( Y1 |, ?* A9 |3 @
  13.   
    5 C: }) \( ]( t6 v+ M) P+ O$ N
  14. FUNC void DebugSetup (void) {  
    - y- b3 R4 l1 ?2 n7 R- X3 k
  15. // <h> Debug MCU Configuration  
    % D0 ?5 ~. y8 h6 G4 Q$ s
  16. //   <o1.0>    DBG_SLEEP     <i> Debug Sleep Mode  
    8 L3 l/ V  \  a6 Q* K, i. r
  17. //   <o1.1>    DBG_STOP      <i> Debug Stop Mode  + y$ ~" K/ w( {2 W
  18. //   <o1.2>    DBG_STANDBY   <i> Debug Standby Mode  
    5 i) p" n* ^- e
  19. //   <o1.5>    TRACE_IOEN    <i> Trace I/O Enable   , X, Y/ ]. z6 ]& Q" g3 Q
  20. //   <o1.6..7> TRACE_MODE    <i> Trace Mode  
    1 a1 B% N) F  @4 _
  21. //             <0=> Asynchronous  5 O0 n6 Y! d5 d8 T* d# G2 z
  22. //             <1=> Synchronous: TRACEDATA Size 1  + z/ l* F4 ~5 a  C  u. j5 k/ B. y
  23. //             <2=> Synchronous: TRACEDATA Size 2  ) L8 O: ]8 ?- [
  24. //             <3=> Synchronous: TRACEDATA Size 4  
    ' [3 h2 ~; ~; u3 c0 H
  25. //   <o1.8>    DBG_IWDG_STOP <i> Independant Watchdog Stopped when Core is halted  + A: B, d! ]' }! B. i: ~; m! r0 y
  26. //   <o1.9>    DBG_WWDG_STOP <i> Window Watchdog Stopped when Core is halted  
    - s( V/ P5 P+ y% v4 m
  27. //   <o1.10>   DBG_TIM1_STOP <i> Timer 1 Stopped when Core is halted  
    6 \" ]; q; C+ ^/ g' C% w
  28. //   <o1.11>   DBG_TIM2_STOP <i> Timer 2 Stopped when Core is halted  7 P6 m/ H# ~- u7 A: a
  29. //   <o1.12>   DBG_TIM3_STOP <i> Timer 3 Stopped when Core is halted  
    7 g9 ^" e  J6 B8 I$ n0 e, E0 T
  30. //   <o1.13>   DBG_TIM4_STOP <i> Timer 4 Stopped when Core is halted  * {9 \' u# T5 f' ^1 F9 J
  31. //   <o1.14>   DBG_CAN_STOP  <i> CAN Stopped when Core is halted  1 J: f8 x& H+ r2 S6 Z
  32. // </h>  ; u& u% Y3 H% q0 H
  33. _WDWORD(0xE0042004, 0x00000027);  // DBGMCU_CR  " ^6 v% f6 @3 `: ^- [
  34. _WDWORD(0xE000ED08, 0x20000000);   // Setup Vector Table Offset Register  
    ! s5 ^9 L0 ?2 l" A
  35. }  
    % h" X5 E" a9 y% H0 ]& W  V
  36.   
    1 B" b- z+ H) p0 w$ Z
  37. DebugSetup();                       // Debugger Setup
复制代码

7 e; ^0 Y) b8 x* ?( u" _# O: k) I5 _7 r
这里对这个文件做简单的解释,6 E) E. |3 R  k: ?
_WDWORD(0xE0042004, 0x00000027); // DBGMCU_CR- _& y9 v1 a% h. r( M% s
这一句表示想 0xE0042004地址处写入 0x000000027,这个寄存器是各个位表示的含义在注释中给出了详细的解释。 0x27即表示: ^8 M/ _2 g7 t. \1 O
        BIT0 DBG_SLEEP
4 J: L6 {& O; [' E        BIT1 DBG_STOP0 G" y& |" K8 u" D* S, e  V
        BIT2 DBG_STANDBY
3 U1 z$ p6 x; J8 O7 o        BIT5 TRACE_IOEN
% Y9 e2 q% q0 {- \) k, v) i注意,要使用ITM机制,必须要打开BIT5。
" H% Z. S% R3 q& l" _- c% |6 Z* B2 h
打开MDK工程,按照下图修改。6 `' @0 \/ J4 ^4 Y' P; J+ a
1.jpg
. ^0 S2 m! X- g# b- C
9 H  M) Z2 Z" o  z0 w5 {- d
2.3 MDK中对JLINK的配置
, f1 N2 W( j& x9 ^. V% S) [9 K9 T
2.jpg - u5 z% D5 H2 n

7 h' L! \; K3 O2 {. D0 Q下图中注意两点% m6 K5 _2 z! z" k4 ]4 l
1). 这里的CoreClock是120M,因为笔者使用的是stm32F207VG这款芯片,并且时钟配置为120M,所以这里填入120M,如果你使用stm32F10x,时钟配置成72M,那么这里需要填入72M。即需要跟实际情况保持一致。: E4 f9 a% v6 m0 {& u; p
2). 最后一定要将 0处打勾,并将其他bit位上的勾去掉,最好与此图保持一致,除CoreClock外。# J1 W1 P* F2 ~9 d: o' U, W) T
3.jpg
& W0 d0 Y% O4 Y6 r9 _, g) j/ |* C7 Y

) x7 [' K/ F$ R) _2 i& @2.4 烧录程序,并启动调试。可以看到,笔者在程序源码中插入了一句printf语句输出,然后按照下图,就可以看到程序的输出了。
; D. Y4 J& N  p
4.jpg
  V) [( N% `! I/ @$ q/ O, n

- G& H/ F0 G: {' I  c- g3. 综合版本使用scanf和printf
6 ]0 g0 ]7 G% N% q/ U/ T: \) s3.1 添加retarget文件
3 R; i$ M' y7 b7 D2 }# Z% ^将如下代码保存成retarget.c,然后加入到工程中。3 ~4 G3 ]; P" Q: N# O

5 l$ ]0 l$ y5 h! K6 t! J6 d

- U$ a0 d% L" p$ Y0 t, C, Z
  1. #pragma import(__use_no_semihosting_swi)  
    ! o( D+ E5 |0 _$ d, w7 o
  2.   : g$ I7 z: }: X9 j, |2 I1 W
  3. struct __FILE { int handle; /* Add whatever you need here */ };  , u+ F4 V: p, x) j; K
  4.     FILE __stdout;  : }4 P( m' H7 y% P2 a, F% i' ?
  5.     FILE __stdin;  
    - V9 g8 b; b. v" h1 e  _5 {
  6.       
    4 w. U$ L" ~3 r8 r) b
  7. int fputc(int ch, FILE *f)   
    ! U2 c$ }4 x$ a
  8. {  - ^% c: W! M* q0 `& ~1 G
  9.     return ITM_SendChar(ch);  5 e$ W( m7 x+ W5 w8 D  J
  10. }  
    # A( \7 m" G5 T
  11.   / m3 i9 y6 K/ H2 K8 v) C# T
  12. volatile int32_t ITM_RxBuffer;  / W2 d9 q1 K- h, ?4 {
  13. int fgetc(FILE *f)  
    9 f% D" k% H% h4 M8 }, O: D
  14. {  
    ) ^. a: m, s1 F, H3 w+ ~* ]8 ^
  15.   while (ITM_CheckChar() != 1) __NOP();  2 l3 q6 q0 H% @4 I$ u' w5 T
  16.   return (ITM_ReceiveChar());  # T) R; G6 T$ a5 s
  17. }  
    9 N" T+ k4 p, @* P
  18.   - B% _* S; O# X5 ~; F/ x
  19. int ferror(FILE *f)  
    3 C  ?3 i. E* h' y" V
  20. {  0 l/ w7 t9 O. K% k! y
  21.     /* Your implementation of ferror */  2 ^4 }( [7 V# |- B  }% _) {# p
  22.     return EOF;  0 S9 j, b  ^' b1 R
  23. }  
    & i* Q% r; I! n4 }# N! K& A
  24.     ?! S) ~' {7 `2 ]$ x6 G! |
  25. void _ttywrch(int c)  
      O% @& _) P. s# ^& q3 |: ]5 b' B
  26. {  
    4 k, S( o% a2 Y
  27.     fputc(c, 0);  
    9 s5 ]8 G5 Z9 z+ S
  28. }  
    " L# I3 N( Z& w, @+ F
  29.   
    1 T" R* p: ~; o8 c5 o
  30. int __backspace()  
    ! G9 O& ?3 l% J. h! P  |
  31. {  : s3 Q( J; O! k1 D) N$ G5 P- v  K
  32.     return 0;  
    8 X# G, h9 W) B! k
  33. }  - U6 {/ w5 L4 e( y0 Q& M! K
  34. void _sys_exit(int return_code)  * q5 X- }& I* y4 w5 Q/ Q
  35. {  7 H9 k- U# Y1 w+ |5 d- p0 }% j1 a
  36. label:  ' t0 Q! a; X/ A- K5 A2 j
  37.     goto label;  /* endless loop */  
    + F% m2 E- _$ F, t. D1 A0 `
  38. }  
复制代码

+ z0 B7 J" l0 O) {  F9 v1 h5 j
) F- D% x9 c) f
- t3 `# u2 y9 E5 m4 f, q
3.2 编译运行
6 f2 p: R' }$ Q8 `6 @编译,烧录,运行,打开Debug (printf) viewer,就可以看到输入,参看下图
8 }3 `+ i3 T- E
5.jpg
: m# W$ \# n/ V0 J5 W0 K( _3 [

3 V7 a4 o. d0 m/ S- O. k" v' R# X" _这里对retarget.c文件做几点说明.
) H9 \1 X+ _' t8 T# n1). 上面的代码实际是在X:\Keil\ARM\Startup\Retarget.c上修改而成的,scanf依赖的函数共有两个,fgetc和__backspace都需要实现,如果缺少__backespace函数,则scanf胡无法从Debug Viewer Dialog 窗口获取输入。另外上面提供的代码只是个demo,用于演示效果,用于生产时应该处理的更完善一些。见参考文献[1]; v4 b8 `& ^; d6 Z5 u1 S
* e, S. L4 U* ~3 W9 i% s& t
2). 函数ITM_SendChar,ITM_CheckChar,ITM_ReceiveChar在库文件CMSIS\Include\core_cm3.h中。
! j8 W9 [) ]& \0 M. T4 G4 y9 F' T& D' h1 X, i7 L2 S
3) 查看函数的符号引用关系,可以通过生成详细的map文件来查看。命令行增加 --verbose --list rtt.map选项即可生成名为rtt.map的文件。0 e, V; i0 S) ^- a  }0 V2 t

& H8 Y7 X& l% f) F4. ITM与RTT结合(待实现)
2 X0 n$ K0 C9 {4 S( A0 ]$ c" Rgrissiom 写道:
! n# Q8 C# {& }6 E5 ]- U8 M忽然想到,或许可以把这个半主机做成 device,然后 rt_console_set_device("semi") 就可以直接用半主机做 finsh/rt_kprintf 了…… 不知可行不可行……
' K  v6 a; j& Q) V0 l7 r
0 w* @+ x! y" ?" Kprife: ITM的接收不知道是否支持中断,目前接收字符使用是轮询方式。如果是中断才有意义。这样可以把ITM设备做成一个 rtt 的device了,让finsh跑在 Debug printf Viewer窗口上。以后只要接一个jtag/SWD口就可以调试了,不用再接串口线了
% I7 J9 L# Z: S" W) z$ U& d$ T7 P) r4 D/ B# }
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
学习,,来看看。。
6 t, _" q6 j8 T7 V& x8 ^+ z
枫天123 回答时间:2018-1-9 11:46:22
搞不定,全是编译错误
jorry 回答时间:2018-9-18 21:44:12
赞赞!!!!

所属标签

相似分享

关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版