使用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 \- #include <stdio.h>
! g, u+ d# l" b- N8 F. H - int main() ( k1 p( t& u: C
- {
' O5 H9 V4 ]4 O; |7 H5 n" _ - //硬件初始化 : P5 i% T7 @7 o$ I0 M
- //....
2 _$ G6 g, u/ f0 @/ K - printf("hello, world"); # f' w2 J" Z2 L4 U+ ]! @; l3 W
- for(;;); 4 \5 E' O% T$ A- ~
- }
复制代码
. 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- #include <stdio.h> , [: m, w' P% A8 J* ~
- 3 K4 G& k3 k, M" a5 p
- #define ITM_Port8(n) (*((volatile unsigned char *)(0xE0000000+4*n))) / ~/ v& @8 o) z; O
- #define ITM_Port16(n) (*((volatile unsigned short*)(0xE0000000+4*n)))
! Y& L$ n& V% p- i5 M - #define ITM_Port32(n) (*((volatile unsigned long *)(0xE0000000+4*n))) 5 M- x. j! L1 c5 _
- #define DEMCR (*((volatile unsigned long *)(0xE000EDFC))) 5 K" j q0 V1 X) y/ g
- #define TRCENA 0x01000000 g8 o( i; p9 {, c" g1 E" M* \
-
+ ~$ z9 M7 J# i% b; J* U - struct __FILE { int handle; /* Add whatever you need here */ };
U: H' ?& l4 u P# h" s/ O3 } - FILE __stdout;
% a* x3 k* N& I; l8 G# i# E4 s' b - FILE __stdin;
% ]! N6 S8 H7 n, r, L, C; y -
! O) F+ s U8 X1 k - int fputc(int ch, FILE *f)
/ y9 y* x% l0 Q: E8 G, G - {
3 e4 U4 n/ l$ _1 c+ T# V3 s - if (DEMCR & TRCENA) + Z. r% [( W" G6 y, b" ~
- { 4 ]& s9 S. n! ~" k8 y
- while (ITM_Port32(0) == 0);
; y- b! x3 M' l, m9 n. L! { - ITM_Port8(0) = ch; 5 e: P6 s5 N# n7 w* U7 V3 J7 p
- } / L$ M6 c7 |* c" l ~. M; `3 n
- return(ch); / T$ C0 ?( Z6 c! `1 M$ P
- }
复制代码
% 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
- /******************************************************************************/
0 S! A- T( u1 L4 ~, P4 L" n# K - /* STM32DBG.INI: STM32 Debugger Initialization File */ 2 g M! Z; x. {
- /******************************************************************************/ " [, E# h8 v* v, {9 A3 B% C: {$ p; M4 z
- // <<< Use Configuration Wizard in Context Menu >>> //
8 C# o$ b4 [5 t" Z - /******************************************************************************/
- B, N: j! \( E. V$ K" i$ K$ [ - /* This file is part of the uVision/ARM development tools. */
+ W; Z. @( s8 |5 O - /* Copyright (c) 2005-2007 Keil Software. All rights reserved. */ 5 R- R s! W0 C$ ?) ?3 M
- /* This software may only be used under the terms of a valid, current, */ ' ^) u P: s$ m# e
- /* end user licence from KEIL for a compatible version of KEIL software */
" F! S1 g6 R D+ u4 h# d - /* development tools. Nothing else gives you the right to use this software. */ 4 W7 S7 s( Q P* t
- /******************************************************************************/ ) a/ c6 T/ e" T/ M6 O: M
- % H% a2 M! B( Y1 |, ?* A9 |3 @
-
5 C: }) \( ]( t6 v+ M) P+ O$ N - FUNC void DebugSetup (void) {
- y- b3 R4 l1 ?2 n7 R- X3 k - // <h> Debug MCU Configuration
% D0 ?5 ~. y8 h6 G4 Q$ s - // <o1.0> DBG_SLEEP <i> Debug Sleep Mode
8 L3 l/ V \ a6 Q* K, i. r - // <o1.1> DBG_STOP <i> Debug Stop Mode + y$ ~" K/ w( {2 W
- // <o1.2> DBG_STANDBY <i> Debug Standby Mode
5 i) p" n* ^- e - // <o1.5> TRACE_IOEN <i> Trace I/O Enable , X, Y/ ]. z6 ]& Q" g3 Q
- // <o1.6..7> TRACE_MODE <i> Trace Mode
1 a1 B% N) F @4 _ - // <0=> Asynchronous 5 O0 n6 Y! d5 d8 T* d# G2 z
- // <1=> Synchronous: TRACEDATA Size 1 + z/ l* F4 ~5 a C u. j5 k/ B. y
- // <2=> Synchronous: TRACEDATA Size 2 ) L8 O: ]8 ?- [
- // <3=> Synchronous: TRACEDATA Size 4
' [3 h2 ~; ~; u3 c0 H - // <o1.8> DBG_IWDG_STOP <i> Independant Watchdog Stopped when Core is halted + A: B, d! ]' }! B. i: ~; m! r0 y
- // <o1.9> DBG_WWDG_STOP <i> Window Watchdog Stopped when Core is halted
- s( V/ P5 P+ y% v4 m - // <o1.10> DBG_TIM1_STOP <i> Timer 1 Stopped when Core is halted
6 \" ]; q; C+ ^/ g' C% w - // <o1.11> DBG_TIM2_STOP <i> Timer 2 Stopped when Core is halted 7 P6 m/ H# ~- u7 A: a
- // <o1.12> DBG_TIM3_STOP <i> Timer 3 Stopped when Core is halted
7 g9 ^" e J6 B8 I$ n0 e, E0 T - // <o1.13> DBG_TIM4_STOP <i> Timer 4 Stopped when Core is halted * {9 \' u# T5 f' ^1 F9 J
- // <o1.14> DBG_CAN_STOP <i> CAN Stopped when Core is halted 1 J: f8 x& H+ r2 S6 Z
- // </h> ; u& u% Y3 H% q0 H
- _WDWORD(0xE0042004, 0x00000027); // DBGMCU_CR " ^6 v% f6 @3 `: ^- [
- _WDWORD(0xE000ED08, 0x20000000); // Setup Vector Table Offset Register
! s5 ^9 L0 ?2 l" A - }
% h" X5 E" a9 y% H0 ]& W V -
1 B" b- z+ H) p0 w$ Z - 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
. ^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
- 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
& W0 d0 Y% O4 Y6 r9 _, g) j/ |* C7 Y
) x7 [' K/ F$ R) _2 i& @2.4 烧录程序,并启动调试。可以看到,笔者在程序源码中插入了一句printf语句输出,然后按照下图,就可以看到程序的输出了。
; D. Y4 J& N p
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- #pragma import(__use_no_semihosting_swi)
! o( D+ E5 |0 _$ d, w7 o - : g$ I7 z: }: X9 j, |2 I1 W
- struct __FILE { int handle; /* Add whatever you need here */ }; , u+ F4 V: p, x) j; K
- FILE __stdout; : }4 P( m' H7 y% P2 a, F% i' ?
- FILE __stdin;
- V9 g8 b; b. v" h1 e _5 { -
4 w. U$ L" ~3 r8 r) b - int fputc(int ch, FILE *f)
! U2 c$ }4 x$ a - { - ^% c: W! M* q0 `& ~1 G
- return ITM_SendChar(ch); 5 e$ W( m7 x+ W5 w8 D J
- }
# A( \7 m" G5 T - / m3 i9 y6 K/ H2 K8 v) C# T
- volatile int32_t ITM_RxBuffer; / W2 d9 q1 K- h, ?4 {
- int fgetc(FILE *f)
9 f% D" k% H% h4 M8 }, O: D - {
) ^. a: m, s1 F, H3 w+ ~* ]8 ^ - while (ITM_CheckChar() != 1) __NOP(); 2 l3 q6 q0 H% @4 I$ u' w5 T
- return (ITM_ReceiveChar()); # T) R; G6 T$ a5 s
- }
9 N" T+ k4 p, @* P - - B% _* S; O# X5 ~; F/ x
- int ferror(FILE *f)
3 C ?3 i. E* h' y" V - { 0 l/ w7 t9 O. K% k! y
- /* Your implementation of ferror */ 2 ^4 }( [7 V# |- B }% _) {# p
- return EOF; 0 S9 j, b ^' b1 R
- }
& i* Q% r; I! n4 }# N! K& A - ?! S) ~' {7 `2 ]$ x6 G! |
- void _ttywrch(int c)
O% @& _) P. s# ^& q3 |: ]5 b' B - {
4 k, S( o% a2 Y - fputc(c, 0);
9 s5 ]8 G5 Z9 z+ S - }
" L# I3 N( Z& w, @+ F -
1 T" R* p: ~; o8 c5 o - int __backspace()
! G9 O& ?3 l% J. h! P | - { : s3 Q( J; O! k1 D) N$ G5 P- v K
- return 0;
8 X# G, h9 W) B! k - } - U6 {/ w5 L4 e( y0 Q& M! K
- void _sys_exit(int return_code) * q5 X- }& I* y4 w5 Q/ Q
- { 7 H9 k- U# Y1 w+ |5 d- p0 }% j1 a
- label: ' t0 Q! a; X/ A- K5 A2 j
- goto label; /* endless loop */
+ F% m2 E- _$ F, t. D1 A0 ` - }
复制代码
+ z0 B7 J" l0 O) { F9 v1 h5 j
) F- D% x9 c) f
- t3 `# u2 y9 E5 m4 f, q3.2 编译运行
6 f2 p: R' }$ Q8 `6 @编译,烧录,运行,打开Debug (printf) viewer,就可以看到输入,参看下图
8 }3 `+ i3 T- E
: 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# }
|