使用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
- #include <stdio.h>
$ ^2 D+ b( {7 X, o - int main()
2 m! g+ r3 H, Y$ z0 [ - {
3 G. y7 w5 B7 ?5 y - //硬件初始化 ! \4 o% P; v" H8 }* u
- //.... * r! D/ z9 ?3 B' J( r
- printf("hello, world"); ! C2 ^/ ]( I0 ~5 w" s
- for(;;); / y$ C; t4 e3 d T( G" N- \
- }
复制代码 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
- #include <stdio.h> 1 P/ C( L0 W: ~; C2 W$ } Y _
-
6 c) _5 Y* R6 D# _8 U, F4 l. X - #define ITM_Port8(n) (*((volatile unsigned char *)(0xE0000000+4*n)))
4 {+ F- k4 @1 q$ [9 S - #define ITM_Port16(n) (*((volatile unsigned short*)(0xE0000000+4*n)))
! j8 y ^0 {; t/ H2 `0 N - #define ITM_Port32(n) (*((volatile unsigned long *)(0xE0000000+4*n)))
, ?- Z/ e5 n3 j! G l( b2 B - #define DEMCR (*((volatile unsigned long *)(0xE000EDFC))) 6 @+ f0 ~4 ]8 R% W5 e- N% {8 r; a. I
- #define TRCENA 0x01000000
$ E$ B& T! y! w) G: O; q- v" T6 @/ n: f -
# H, r0 g3 D4 g. V+ F6 z - struct __FILE { int handle; /* Add whatever you need here */ };
% j6 z. t( p* \+ h3 ^' D) F - FILE __stdout;
9 o' \) x% B+ |0 V, Z! r; v; C% n - FILE __stdin; 1 z6 W8 x6 o0 H9 F
- 8 O, B$ B2 h7 }& p' [0 Q
- int fputc(int ch, FILE *f)
2 u6 S) R0 W% ~ - {
: q! f9 T3 K7 m' S' \+ {1 N: { - if (DEMCR & TRCENA)
' F" \" L$ ~+ w- T/ ~7 g0 v - { n: Y/ S8 S# |3 Z) O( w6 R# c# c
- while (ITM_Port32(0) == 0); \5 E$ d: n5 J z4 L: M) ~
- ITM_Port8(0) = ch;
% J5 Y. W$ R) s; n; S - } ! ~4 f9 F! p* N8 O
- return(ch); ) p, s' \! Y h% z, b1 e
- }
复制代码 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- /******************************************************************************/ ! g+ m1 b: y/ W; [6 O* z7 t" C
- /* STM32DBG.INI: STM32 Debugger Initialization File */ / z8 Y k$ t$ C& q
- /******************************************************************************/ : E% S# o9 f" a! [& S
- // <<< Use Configuration Wizard in Context Menu >>> //
9 ] L: v' k# ` - /******************************************************************************/
8 q0 x# ^# m: M* A( b3 P$ Q7 d' j- h - /* This file is part of the uVision/ARM development tools. */
$ ^& o# g6 S' p% | - /* Copyright (c) 2005-2007 Keil Software. All rights reserved. */ G9 V4 j/ V; p$ @) A
- /* This software may only be used under the terms of a valid, current, */ # I0 |* ]: v3 } q% f
- /* end user licence from KEIL for a compatible version of KEIL software */ / b8 h1 o3 Y2 W1 q" n1 j
- /* development tools. Nothing else gives you the right to use this software. */ % }6 p, N6 G8 i) A
- /******************************************************************************/ - X2 Y$ t3 O0 r
-
9 ]- H ~5 `: U" ^ -
7 V( s/ X6 i# U) L4 m$ K6 N- ~! q - FUNC void DebugSetup (void) {
, `& f: }8 L3 C9 e7 z% W8 t. i - // <h> Debug MCU Configuration / t! z: [( d# F# O# J5 ^1 k, T
- // <o1.0> DBG_SLEEP <i> Debug Sleep Mode 0 @. m3 y7 {7 B. u9 s
- // <o1.1> DBG_STOP <i> Debug Stop Mode - e+ d5 r' K. \" e1 v9 X
- // <o1.2> DBG_STANDBY <i> Debug Standby Mode
1 N/ p# i* ]" x! M+ o5 ? - // <o1.5> TRACE_IOEN <i> Trace I/O Enable
: ?5 x4 ]! x9 ^) i" z! O7 v% P& p - // <o1.6..7> TRACE_MODE <i> Trace Mode
; U, n9 C4 y8 n - // <0=> Asynchronous / C# R' H+ S3 q# y+ D
- // <1=> Synchronous: TRACEDATA Size 1 # X o4 Q9 v2 V% L
- // <2=> Synchronous: TRACEDATA Size 2
# q G9 H! {2 ?. Q5 c/ c0 i - // <3=> Synchronous: TRACEDATA Size 4
5 l/ l5 W3 y. i" Z - // <o1.8> DBG_IWDG_STOP <i> Independant Watchdog Stopped when Core is halted
/ Z! F- x9 ]7 B/ u. H+ r - // <o1.9> DBG_WWDG_STOP <i> Window Watchdog Stopped when Core is halted
: u3 p9 Z& e0 b4 u) w& b+ n0 o - // <o1.10> DBG_TIM1_STOP <i> Timer 1 Stopped when Core is halted ) [% ^9 v1 M, M
- // <o1.11> DBG_TIM2_STOP <i> Timer 2 Stopped when Core is halted ' I" A% T( F( N$ A+ M
- // <o1.12> DBG_TIM3_STOP <i> Timer 3 Stopped when Core is halted # j9 L3 j. c8 m( A
- // <o1.13> DBG_TIM4_STOP <i> Timer 4 Stopped when Core is halted 1 e2 U' G& d0 ?2 ~% O
- // <o1.14> DBG_CAN_STOP <i> CAN Stopped when Core is halted
9 |. r: p4 g1 Q. N, e - // </h>
& ?/ ~6 s" X9 {3 q. P+ S - _WDWORD(0xE0042004, 0x00000027); // DBGMCU_CR - f( E& |& H+ b$ f: V: W4 ?
- _WDWORD(0xE000ED08, 0x20000000); // Setup Vector Table Offset Register 1 k' G0 v; {' v1 }6 i
- } & V3 f9 E% f8 x3 [* E2 }; w
- + t& a1 F" v2 H2 q% c- [# J# J$ ~+ e4 y
- 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
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
/ \. 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 |
4 N% v. l7 D; }0 O
! \3 r( s: l" w8 m( m2.4 烧录程序,并启动调试。可以看到,笔者在程序源码中插入了一句printf语句输出,然后按照下图,就可以看到程序的输出了。, w% {2 _3 x( V, H3 x
; \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# t2 b4 |% [& H6 w( ^% I Y
- #pragma import(__use_no_semihosting_swi)
% m$ p3 E, j, E$ M+ C' [5 M - + f5 f% h& I- T: X, j1 d8 t0 G
- struct __FILE { int handle; /* Add whatever you need here */ }; $ a" W, ~' b6 t8 P8 P
- FILE __stdout;
- {- d' V, f! j - FILE __stdin;
; B+ ?& K' |& H0 ]3 t. o* h/ V -
8 S# ~) Z. I1 p% d; H, G4 d* e - int fputc(int ch, FILE *f)
" l3 c p- k5 v7 d7 f' n - { 1 e. _+ `& l# I5 g4 {' K8 Z
- return ITM_SendChar(ch);
) O2 |! L% L0 m; R% x - } : m6 Q" i. D; n: I1 j2 ?/ [) `
- 8 m5 b; c I: y7 X& R3 b
- volatile int32_t ITM_RxBuffer;
! L3 z4 a! Z& Y5 Q& }8 I - int fgetc(FILE *f) ' b" l0 m ]7 S T, S
- { + M1 |0 e% p* e5 q4 c8 U
- while (ITM_CheckChar() != 1) __NOP();
8 g! [6 R8 X9 Q3 J/ \# _ - return (ITM_ReceiveChar()); ) ?4 z9 ]/ J- s
- } 2 Z2 l& a+ Z2 O/ [0 t
-
7 E8 f) D p' h& U' P- D - int ferror(FILE *f) 5 m' ^. r2 R3 B; J* _) u
- {
6 O) R. m- |# H; ? - /* Your implementation of ferror */
0 r2 j- @' ~" L: b; d - return EOF;
3 U4 j/ Y* p7 o9 k, D: d9 P0 b - } " Z7 _) ~$ J I9 m4 A
- 9 _/ g$ A W! \7 ?# z4 n1 n% Z
- void _ttywrch(int c)
3 N" E/ |0 E, _6 B( a - {
& X7 J$ ~8 I/ g - fputc(c, 0);
- f4 q4 d* H9 y* N7 i4 ?) J/ k - }
2 q( x4 X# J, Z% I- } - ( u2 l/ Y: m7 e# l# ~6 d! _; R
- int __backspace() 4 P. O- ?% t# z: p2 }
- {
3 I* D: ? [+ `9 A4 @ K. c3 I) Z - return 0;
% Q4 N! O+ H1 ~8 a; u - }
5 r. h+ F% V5 k. s; W" l( i3 C# Q1 d - void _sys_exit(int return_code) / C e$ G1 |- _7 M! ~/ @& a, {
- {
+ J) l" B6 o8 T - label: 4 I' L+ ~) e4 s' D, b3 J- e
- goto label; /* endless loop */
- I% |; W& `9 X' F6 i" K% b - }
复制代码
' `' j( {+ Q8 S; M0 D6 B( h8 m9 X) r; _) S |' n' d* P
, T6 z4 a+ m& X, \! }- i& D3.2 编译运行
1 N* O) T1 `# ~编译,烧录,运行,打开Debug (printf) viewer,就可以看到输入,参看下图" P. y7 x3 p1 h- g1 n
) 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 |