如何捕获和查看 Cortex-M4 MCU 上的 ITM 跟踪信息?
How do I capture and view ITM trace information on a Cortex-M4 MCU?
我想捕获、解码和查看 Cortex-M4 MCU(在我的例子中是 Atmel SAM4S)的 ITM 跟踪信息。特别是,我想捕获与板上其他信号相关的异常和用户跟踪数据(即在同一时间线上显示所有信号和跟踪信息)。
这可以通过以下步骤完成:
- 将调试器置于 SWD 模式。如果在 Linux 上使用 J-Link Segger,这可以通过
JLinkGDBServer -if swd
完成
- 向MCU 添加代码以启用跟踪。将比特率设置为适合您需要的值(我使用 8 MHz)。示例 Ada 代码如下。
- 使用逻辑分析器从 SAM4S 处理器捕获 TRACESWO 线上的跟踪数据。我使用了采样率为 100 MHz 的 Saleae Logic Pro 16。
将数据转换为 sigrok 可用的格式。使用Saleae抓取数据,包括以下步骤:
- 仅使用前 8 个通道进行捕获(因此每个样本导出一个字节)。
- 将数据以二进制形式导出到
trace.bin
,为每个样本写入一个字节。
转换为 trace.sr 文件使用:
sigrok-cli -i trace.bin -I binary:samplerate=100000000,numchannels=4 -o trace.sr
- 在 PulseView 中打开
trace.sr
文件。
- TRACESWO通道添加UART解码器,码率8000000。
- 堆栈 ARM-ITM 解码器。
有关详细信息,请参阅 http://www.sigrok.org/blog/new-protocol-decoders-arm-tpiu-itm-etmv3。
SAM4S 跟踪的示例 Ada 代码:
sam4s-trace.ads:
with Interfaces;
package SAM4S.Trace is
pragma Preelaborate;
type Channel_Type is new Integer range 0 .. 31;
type Value_Type is new Interfaces.Unsigned_32;
procedure Initialize;
procedure Put (Channel : Channel_Type;
Value : Value_Type);
procedure Put (Channel : Channel_Type;
Message : String);
end SAM4S.Trace;
sam4s-trace.adb:
with System;
with System.Storage_Elements; use System.Storage_Elements;
package body SAM4S.Trace is
procedure Initialize is
ITM_LAR : Interfaces.Unsigned_32
with Address => System'To_Address (16#E000_0FB0#), Volatile;
ITM_TCR : Interfaces.Unsigned_32
with Address => System'To_Address (16#E000_0E80#), Volatile;
ITM_TER : Interfaces.Unsigned_32
with Address => System'To_Address (16#E000_0E00#), Volatile;
ITM_TPR : Interfaces.Unsigned_32
with Address => System'To_Address (16#E000_0E40#), Volatile;
DEMR : Interfaces.Unsigned_32
with Address => System'To_Address (16#E000_EDFC#), Volatile;
TPIU_SPP : Interfaces.Unsigned_32
with Address => System'To_Address (16#E004_00F0#), Volatile;
TPIU_FFCR : Interfaces.Unsigned_32
with Address => System'To_Address (16#E004_0304#), Volatile;
TPIU_ACPR : Interfaces.Unsigned_32
with Address => System'To_Address (16#E004_0010#), Volatile;
DWT_CTRL : Interfaces.Unsigned_32
with Address => System'To_Address (16#E000_1000#), Volatile;
use Interfaces;
begin
-- Enable write access via the Lock Access Register.
ITM_LAR := 16#C5AC_CE55#;
-- Enable the ITM, enable SWO mode behavior, enable synchronization
-- packets, enable DWT event submission, enable timestamps.
ITM_TCR := 16#0001_001F#;
-- Enable access in user mode to all 32 channels.
ITM_TPR := 16#0000_0000#;
-- Enable all 32 trace channels.
ITM_TER := 16#FFFF_FFFF#;
-- Set TRCENA bit to 1 in Debug Exception and Monitor Register.
DEMR := DEMR or 16#0100_0000#;
-- Select NRZ serial wire output.
TPIU_SPP := 16#0000_0002#;
-- Deactivate formatter.
TPIU_FFCR := 16#0000_0100#;
-- Set prescalar (/10).
-- TPIU_ACPR := 16#0000_0009#;
-- Set prescalar (/15).
TPIU_ACPR := 14;
-- Enable exception trace and exception overhead.
DWT_CTRL := DWT_CTRL or 16#0005_0000#;
end Initialize;
procedure Put (Channel : Channel_Type;
Value : Value_Type) is
Port_Reg : Value_Type with Address => System'To_Address (16#E000_0000#) +
4 * Channel_Type'Pos (Channel), Volatile;
begin
-- Port register lsb is set when the the FIFO can accept at least one
-- word.
while Port_Reg = 0 loop
null;
end loop;
Port_Reg := Value;
end Put;
procedure Put (Channel : Channel_Type;
Message : String) is
Port_Reg : Value_Type with Address => System'To_Address (16#E000_0000#) +
4 * Channel_Type'Pos (Channel), Volatile;
begin
-- Port register lsb is set when the the FIFO can accept at least one
-- word.
for Index in Message'Range loop
while Port_Reg = 0 loop
null;
end loop;
Port_Reg := Value_Type (Character'Pos (Message (Index)));
end loop;
end Put;
end SAM4S.Trace;
正如您用 "logic analyzer" 标记的那样,这可能是题外话,但我发现以下内容非常有用。使用 Keil uVision(可能还有其他 IDE),您可以使用自定义 .ini 文件将 ITM 数据重新路由到文件。
在调试器中启用 SWJ,使用 SW 端口。启用跟踪,启用您要使用的刺激端口。
写一个 .ini 文件,内容如下:
ITMLOG 0 > "debug.log"
ITMLOG 1 > "testlog.xml"
这会将 ITM 通道 0 重新路由到名为 "debug.log" 的文件,并将通道 1 重新路由到 "testlog.xml"(来自 here 的文件语法)。
为了在您的 c 代码中使用 fprinf 轻松使用通道,我使用以下设置:
struct __FILE { int channel; };
#include <stdio.h>
#define ITM_Port8(n) (*((volatile unsigned char *)(0xE0000000+4*n)))
#define ITM_Port16(n) (*((volatile unsigned short*)(0xE0000000+4*n)))
#define ITM_Port32(n) (*((volatile unsigned long *)(0xE0000000+4*n)))
#define DEMCR (*((volatile unsigned long *)(0xE000EDFC)))
#define TRCENA 0x01000000
int fputc(int ch, FILE *f) {
if (DEMCR & TRCENA) {
while (ITM_Port32(f->channel) == 0);
ITM_Port8(f->channel) = ch;
}
return(ch);
}
以及在项目中的用法:
FILE debug_stream = { .channel = 0 };
FILE test_stream = { .channel = 1 };
int main(void) {
fprinf(&debug_stream, "this is a debug message, it will be rerouted to debug.log");
fprinf(&test_stream, "this is a test message, it will be placed in testlog.xml");
}
参考:link
我想捕获、解码和查看 Cortex-M4 MCU(在我的例子中是 Atmel SAM4S)的 ITM 跟踪信息。特别是,我想捕获与板上其他信号相关的异常和用户跟踪数据(即在同一时间线上显示所有信号和跟踪信息)。
这可以通过以下步骤完成:
- 将调试器置于 SWD 模式。如果在 Linux 上使用 J-Link Segger,这可以通过
JLinkGDBServer -if swd
完成
- 向MCU 添加代码以启用跟踪。将比特率设置为适合您需要的值(我使用 8 MHz)。示例 Ada 代码如下。
- 使用逻辑分析器从 SAM4S 处理器捕获 TRACESWO 线上的跟踪数据。我使用了采样率为 100 MHz 的 Saleae Logic Pro 16。
将数据转换为 sigrok 可用的格式。使用Saleae抓取数据,包括以下步骤:
- 仅使用前 8 个通道进行捕获(因此每个样本导出一个字节)。
- 将数据以二进制形式导出到
trace.bin
,为每个样本写入一个字节。 转换为 trace.sr 文件使用:
sigrok-cli -i trace.bin -I binary:samplerate=100000000,numchannels=4 -o trace.sr
- 在 PulseView 中打开
trace.sr
文件。 - TRACESWO通道添加UART解码器,码率8000000。
- 堆栈 ARM-ITM 解码器。
有关详细信息,请参阅 http://www.sigrok.org/blog/new-protocol-decoders-arm-tpiu-itm-etmv3。
SAM4S 跟踪的示例 Ada 代码:
sam4s-trace.ads:
with Interfaces;
package SAM4S.Trace is
pragma Preelaborate;
type Channel_Type is new Integer range 0 .. 31;
type Value_Type is new Interfaces.Unsigned_32;
procedure Initialize;
procedure Put (Channel : Channel_Type;
Value : Value_Type);
procedure Put (Channel : Channel_Type;
Message : String);
end SAM4S.Trace;
sam4s-trace.adb:
with System;
with System.Storage_Elements; use System.Storage_Elements;
package body SAM4S.Trace is
procedure Initialize is
ITM_LAR : Interfaces.Unsigned_32
with Address => System'To_Address (16#E000_0FB0#), Volatile;
ITM_TCR : Interfaces.Unsigned_32
with Address => System'To_Address (16#E000_0E80#), Volatile;
ITM_TER : Interfaces.Unsigned_32
with Address => System'To_Address (16#E000_0E00#), Volatile;
ITM_TPR : Interfaces.Unsigned_32
with Address => System'To_Address (16#E000_0E40#), Volatile;
DEMR : Interfaces.Unsigned_32
with Address => System'To_Address (16#E000_EDFC#), Volatile;
TPIU_SPP : Interfaces.Unsigned_32
with Address => System'To_Address (16#E004_00F0#), Volatile;
TPIU_FFCR : Interfaces.Unsigned_32
with Address => System'To_Address (16#E004_0304#), Volatile;
TPIU_ACPR : Interfaces.Unsigned_32
with Address => System'To_Address (16#E004_0010#), Volatile;
DWT_CTRL : Interfaces.Unsigned_32
with Address => System'To_Address (16#E000_1000#), Volatile;
use Interfaces;
begin
-- Enable write access via the Lock Access Register.
ITM_LAR := 16#C5AC_CE55#;
-- Enable the ITM, enable SWO mode behavior, enable synchronization
-- packets, enable DWT event submission, enable timestamps.
ITM_TCR := 16#0001_001F#;
-- Enable access in user mode to all 32 channels.
ITM_TPR := 16#0000_0000#;
-- Enable all 32 trace channels.
ITM_TER := 16#FFFF_FFFF#;
-- Set TRCENA bit to 1 in Debug Exception and Monitor Register.
DEMR := DEMR or 16#0100_0000#;
-- Select NRZ serial wire output.
TPIU_SPP := 16#0000_0002#;
-- Deactivate formatter.
TPIU_FFCR := 16#0000_0100#;
-- Set prescalar (/10).
-- TPIU_ACPR := 16#0000_0009#;
-- Set prescalar (/15).
TPIU_ACPR := 14;
-- Enable exception trace and exception overhead.
DWT_CTRL := DWT_CTRL or 16#0005_0000#;
end Initialize;
procedure Put (Channel : Channel_Type;
Value : Value_Type) is
Port_Reg : Value_Type with Address => System'To_Address (16#E000_0000#) +
4 * Channel_Type'Pos (Channel), Volatile;
begin
-- Port register lsb is set when the the FIFO can accept at least one
-- word.
while Port_Reg = 0 loop
null;
end loop;
Port_Reg := Value;
end Put;
procedure Put (Channel : Channel_Type;
Message : String) is
Port_Reg : Value_Type with Address => System'To_Address (16#E000_0000#) +
4 * Channel_Type'Pos (Channel), Volatile;
begin
-- Port register lsb is set when the the FIFO can accept at least one
-- word.
for Index in Message'Range loop
while Port_Reg = 0 loop
null;
end loop;
Port_Reg := Value_Type (Character'Pos (Message (Index)));
end loop;
end Put;
end SAM4S.Trace;
正如您用 "logic analyzer" 标记的那样,这可能是题外话,但我发现以下内容非常有用。使用 Keil uVision(可能还有其他 IDE),您可以使用自定义 .ini 文件将 ITM 数据重新路由到文件。
在调试器中启用 SWJ,使用 SW 端口。启用跟踪,启用您要使用的刺激端口。
写一个 .ini 文件,内容如下:
ITMLOG 0 > "debug.log"
ITMLOG 1 > "testlog.xml"
这会将 ITM 通道 0 重新路由到名为 "debug.log" 的文件,并将通道 1 重新路由到 "testlog.xml"(来自 here 的文件语法)。
为了在您的 c 代码中使用 fprinf 轻松使用通道,我使用以下设置:
struct __FILE { int channel; };
#include <stdio.h>
#define ITM_Port8(n) (*((volatile unsigned char *)(0xE0000000+4*n)))
#define ITM_Port16(n) (*((volatile unsigned short*)(0xE0000000+4*n)))
#define ITM_Port32(n) (*((volatile unsigned long *)(0xE0000000+4*n)))
#define DEMCR (*((volatile unsigned long *)(0xE000EDFC)))
#define TRCENA 0x01000000
int fputc(int ch, FILE *f) {
if (DEMCR & TRCENA) {
while (ITM_Port32(f->channel) == 0);
ITM_Port8(f->channel) = ch;
}
return(ch);
}
以及在项目中的用法:
FILE debug_stream = { .channel = 0 };
FILE test_stream = { .channel = 1 };
int main(void) {
fprinf(&debug_stream, "this is a debug message, it will be rerouted to debug.log");
fprinf(&test_stream, "this is a test message, it will be placed in testlog.xml");
}
参考:link