本文来介绍RTOS调试利器,可视化Trace工具,常见的有以下几个
Percepio的Tracealyzer;
SEGGER的SystemView;
Micrium的μC/Probe。
lμC/Probe是元老级别工具,相信很多人最开始接触RTOS Trace的技术就是从它开始的,它也是伴随着uC/OS而知名,甚至很早前还流出过其上位机源码,是配合uCOS调试的最佳利器。当然也可以调试其他RTOS甚至NO OS,其特点是上位机GUI灵活的配置,可以拖拽GUI显示不同的界面,类似GUIBuild。Target源码可以从以下地址下载
https://github.com/weston-embedded/uC-Probe-Target。
分享上位机工具下载
链接:https://pan.baidu.com/s/1s60qEaSoFEmGBxm3sFb-dA?pwd=mzy7
提取码:mzy7
lSystemView是SEGGER开发,原配自家RTOS embOS,当然也支持其他主流RTOS比如uC/OS-III, Micrium OS Kernel, FreeRTOS, NuttX 和 Zephyr ,上位机和Target源码可从如下地址下载https://www.segger.com/downloads/systemview/
lPercepio则是目前最亮眼之星,丰富的事件,信号等图表,使用简单,UI个人感觉更适合时序分析。分享上位机工具如下:
链接:https://pan.baidu.com/s/1agHqqQ0M-Yo9wv30Ft-C3g?pwd=kzcq
提取码:kzcq
注意:安装软件仅供学习使用,请支持正版软件。
双击打开Tracealyzer-4.6.5-windows64.exe,弹出用户账户控制,点击是
点击Next
选择安装路径,点击Install
点击Finish
使用Tracealyzer.exe, Tracealyzer.exe.config替换安装目录D:\Program Files\Percepio\Tracealyzer 4下的文件.
打开Tracealyzer.exe
解压I_LOVE_DVT.rar
打开TracealyzerKeyfileMaker.exe,点击Generate!生成license文件
弹出对话框点击确认,点击Exit退出。把license文件保存在某个地方,我这里放在安装路径D:\Program Files\Percepio\Tracealyzer 4下。
运行Tracealyzer,指定刚生成的license文件
https://github.com/percepio/TraceRecorderSource/tags
选择对应版本
查看工具版本,我这里是4.6.5
Help->About Percepio Tracealyzer
git clone --branch Tz4/4.6/v4.6.5 https://github.com/percepio/TraceRecorderSource.git
需要以下源码
以下源码无需任何修改
|-- trcAssert.c
|-- trcCounter.c
|-- trcDiagnostics.c
|-- trcEntryTable.c
|-- trcError.c
|-- trcEvent.c
|-- trcEventBuffer.c
|-- trcExtension.c
|-- trcHardwarePort.c
|-- trcHeap.c
|-- trcISR.c
|-- trcInternalEventBuffer.c
|-- trcInterval.c
|-- trcMultiCoreEventBuffer.c
|-- trcObject.c
|-- trcPrint.c
|-- trcSnapshotRecorder.c
|-- trcStackMonitor.c
|-- trcStateMachine.c
|-- trcStaticBuffer.c
|-- trcStreamingRecorder.c
|-- trcString.c
|-- trcTask.c
`-- trcTimestamp.c
配置如下头文件包含路径
TraceRecorderSource/include
TraceRecorderSource/config
配置头文件包含路径,这里以FreeRTOS为例,其他平台参考kernelports对应目录
TraceRecorderSource/kernelports/FreeRTOS/config
TraceRecorderSource/kernelports/FreeRTOS/include
TraceRecorderSource/kernelports/FreeRTOS/config/trcKernelPortConfig.h下修改
#define TRC_CFG_FREERTOS_VERSION FREERTOS_VERSION_NOT_SET
为
#define TRC_CFG_FREERTOS_VERSION TRC_FREERTOS_VERSION_10_2_1
我这里使用的是10.2.1版本。
TraceRecorderSource/config/trcConfig.h中
注释掉
#error "Trace Recorder: Please include your processor's header file here and remove this line."
添加
#include "FreeRTOS.h"
我这里直接使用
TraceRecorderSource/kernelports/FreeRTOS/,其他平台可以根据模板修改,实现对应的接口。
添加头文件包含路径
TraceRecorderSource/kernelports/FreeRTOS/config
TraceRecorderSource/kernelports/FreeRTOS/include
和源码
TraceRecorderSource/kernelports/FreeRTOS/trcKernelPort.c
该源码需要实现
xTraceKernelPortEnable
xTraceKernelPortInitialize
在FreeRTOSConfig.h的最后#include "trcRecorder.h"
portmacro.h中
#define portBASE_TYPE int32_t
改为
#define portBASE_TYPE int
TraceRecorderSource/include/trcDefines.h下定义了支持的port,
/****** Port Name ************************************* Code ** Official ** OS Platform *********/
如果你的硬件在上面中存在,则在文件TraceRecorderSource/config/trcConfig.h中定义宏TRC_CFG_HARDWARE_PORT为上述指定值。
我这里将
#define TRC_CFG_HARDWARE_PORT TRC_HARDWARE_PORT_NOT_SET
改为上述指定的值,我这里改为TRC_HARDWARE_PORT_APPLICATION_DEFINED即自行实现
#define TRC_CFG_HARDWARE_PORT TRC_HARDWARE_PORT_APPLICATION_DEFINED
可以看到TraceRecorderSource/include/trcHardwarePort.h中根据配置宏TRC_CFG_HARDWARE_PORT实现的接口
比如配置为TRC_HARDWARE_PORT_APPLICATION_DEFINED如下
#elif (TRC_CFG_HARDWARE_PORT == TRC_HARDWARE_PORT_APPLICATION_DEFINED)
#if !( defined (TRC_HWTC_TYPE) && defined (TRC_HWTC_COUNT) && defined (TRC_HWTC_PERIOD) && defined (TRC_HWTC_FREQ_HZ) && defined (TRC_IRQ_PRIORITY_ORDER) )
#error "The hardware port is not completely defined!"
#endif
则需要在上述位置实现以下接口
TRC_HWTC_TYPE
可配置为如下值
#define TRC_FREE_RUNNING_32BIT_INCR 1 /* 从0计数到0xFFFFFFFF */
#define TRC_FREE_RUNNING_32BIT_DECR 2 /* 从0xFFFFFFFF计数到0 */
#define TRC_OS_TIMER_INCR 3 /*以OS TICK为单位 0 计数到(TRC_HWTC_PERIOD-1)*/
#define TRC_OS_TIMER_DECR 4 /*以OS TICK为单位 (TRC_HWTC_PERIOD-1)计数到0*/
#define TRC_CUSTOM_TIMER_INCR 5 /* 从0计数到TRC_HWTC_PERIOD-1 */
#define TRC_CUSTOM_TIMER_DECR 6 /*从TRC_HWTC_PERIOD-1 计数到 0*/
TRC_HWTC_COUNT
获取当前的时间戳,原型为TraceUnsignedBaseType_t TRC_HWTC_COUNT(void)
TRC_HWTC_PERIOD
HWTC_COUNT获取的时间戳的范围
如果前面TRC_HWTC_TYPE使用TRC_FREE_RUNNING_32BIT_INCR/DECR则必须配置为0
TRC_HWTC_FREQ_HZ
HWTC_COUNT获取的时间戳的频率
TRC_IRQ_PRIORITY_ORDER
配置为1表示高优先级任务的优先级数越大,配置为0则反之。FREERTOS配置为1。
我这里最终实现如下
extern uint32_t my_stream_get_tick(void);
需要实现获取时间戳接口
我这里xx_timer_get_time是获取的1uS为单位的定时器的时间
uint32_t my_stream_get_tick(void)
{
return xx_timer_get_time();
}
streamports下有模板,需要和模板实现一样的目录和文件名
我这里新建一个文件夹MY_STREAM,内容如下,可以直接从其他模板复制。
|-- config
| `-- trcStreamPortConfig.h
|-- include
| `-- trcStreamPort.h
`-- trcStreamPort.c
需要将上述trcStreamPortConfig.h,trcStreamPort.h添加到头文件包含路径
TraceRecorderSource/streamports/MY_STREAM/include
TraceRecorderSource/streamports/MY_STREAM/config
TraceRecorderSource/streamports/MY_STREAM/trcStreamPort.c添加到工程源码中。
该文件下需要实现以下接口
其中以下几个是必须需要实现的
/**
* @internal Stream port initialize callback.
*
* This function is called by the recorder as part of its initialization phase.
*
* @param[in] pxBuffer Buffer
*
* @retval TRC_FAIL Initialization failed
* @retval TRC_SUCCESS Success
*/
traceResult xTraceStreamPortInitialize(TraceStreamPortBuffer_t* pxBuffer);
/**
* @brief Reads data through the stream port interface.
*
* @param[in] pvData Destination data buffer
* @param[in] uiSize Destination data buffer size
* @param[out] piBytesRead Bytes read
*
* @retval TRC_FAIL Read failed
* @retval TRC_SUCCESS Success
*/
traceResult xTraceStreamPortReadData(void* pvData, uint32_t uiSize, int32_t* piBytesRead);
/**
* @brief Writes data through the stream port interface.
*
* @param[in] pvData Data to write
* @param[in] uiSize Data to write size
* @param[out] piBytesWritten Bytes written
*
* @retval TRC_FAIL Write failed
* @retval TRC_SUCCESS Success
*/
traceResult xTraceStreamPortWriteData(void* pvData, uint32_t uiSize, int32_t* piBytesWritten);
以下几个可以使用内部Buffer,使用宏
#define TRC_USE_INTERNAL_BUFFER 1
/**
* @brief Allocates data from the stream port.
*
* @param[in] uiSize Allocation size
* @param[out] ppvData Allocation data pointer
*
* @retval TRC_FAIL Allocate failed
* @retval TRC_SUCCESS Success
*/
#define xTraceStreamPortAllocate xTraceInternalEventBufferAlloc
/**
* @brief Commits data to the stream port, depending on the implementation/configuration of the
* stream port this data might be directly written to the stream port interface, buffered, or
* something else.
*
* @param[in] pvData Data to commit
* @param[in] uiSize Data to commit size
* @param[out] piBytesCommitted Bytes committed
*
* @retval TRC_FAIL Commit failed
* @retval TRC_SUCCESS Success
*/
#define xTraceStreamPortCommit xTraceInternalEventBufferAllocCommit
#define xTraceStreamPortOnEnable(uiStartOption) ((void)(uiStartOption), TRC_SUCCESS)
#define xTraceStreamPortOnDisable() (TRC_SUCCESS)
#define xTraceStreamPortOnTraceBegin() (TRC_SUCCESS)
#define xTraceStreamPortOnTraceEnd() (TRC_SUCCESS)
TraceRecorderSource/streamports/MY_STREAM/trcStreamPort.c如下
/**
* @brief Reads data through the stream port interface.
*
* @param[in] pvData Destination data buffer
* @param[in] uiSize Destination data buffer size
* @param[out] piBytesRead Bytes read
*
* @retval TRC_FAIL Read failed
* @retval TRC_SUCCESS Success
*/
traceResult xTraceStreamPortReadData(void* pvData, uint32_t uiSize, int32_t* piBytesRead)
{
(void)pvData;
(void)uiSize;
(void)piBytesRead;
return TRC_SUCCESS;
}
traceResult xTraceStreamPortWriteData(void* pvData, uint32_t uiSize, int32_t* piBytesWritten)
{
(void)pvData;
(void)uiSize;
(void)piBytesWritten;
return TRC_SUCCESS;
}
traceResult xTraceStreamPortInitialize(TraceStreamPortBuffer_t* pxBuffer)
{
TRC_ASSERT_EQUAL_SIZE(TraceStreamPortBuffer_t, TraceStreamPortRTT_t);
if (pxBuffer == 0)
{
return TRC_FAIL;
}
return xTraceInternalEventBufferInitialize(pxBuffer->buffer,sizeof(pxBuffer->buffer));
return TRC_SUCCESS;
}
TraceRecorderSource/streamports/MY_STREAM/include/trcStreamPort.h内容如下
extern "C" {
typedef struct TraceStreamPortBuffer /* Aligned */
{
uint8_t buffer[(TRC_STREAM_PORT_USB_BUFFER_SIZE) + (TRC_STREAM_PORT_INTERNAL_BUFFER_SIZE) + sizeof(TraceUnsignedBaseType_t)];
} TraceStreamPortBuffer_t;
/**
* @internal Stream port initialize callback.
*
* This function is called by the recorder as part of its initialization phase.
*
* @param[in] pxBuffer Buffer
*
* @retval TRC_FAIL Initialization failed
* @retval TRC_SUCCESS Success
*/
traceResult xTraceStreamPortInitialize(TraceStreamPortBuffer_t* pxBuffer);
/**
* @brief Reads data through the stream port interface.
*
* @param[in] pvData Destination data buffer
* @param[in] uiSize Destination data buffer size
* @param[out] piBytesRead Bytes read
*
* @retval TRC_FAIL Read failed
* @retval TRC_SUCCESS Success
*/
traceResult xTraceStreamPortReadData(void* pvData, uint32_t uiSize, int32_t* piBytesRead);
/**
* @brief Writes data through the stream port interface.
*
* @param[in] pvData Data to write
* @param[in] uiSize Data to write size
* @param[out] piBytesWritten Bytes written
*
* @retval TRC_FAIL Write failed
* @retval TRC_SUCCESS Success
*/
traceResult xTraceStreamPortWriteData(void* pvData, uint32_t uiSize, int32_t* piBytesWritten);
/**
* @brief Allocates data from the stream port.
*
* @param[in] uiSize Allocation size
* @param[out] ppvData Allocation data pointer
*
* @retval TRC_FAIL Allocate failed
* @retval TRC_SUCCESS Success
*/
/**
* @brief Commits data to the stream port, depending on the implementation/configuration of the
* stream port this data might be directly written to the stream port interface, buffered, or
* something else.
*
* @param[in] pvData Data to commit
* @param[in] uiSize Data to commit size
* @param[out] piBytesCommitted Bytes committed
*
* @retval TRC_FAIL Commit failed
* @retval TRC_SUCCESS Success
*/
}
TraceRecorderSource/streamports/MY_STREAM/config/trcStreamPortConfig.h内容如下
/*
* Trace Recorder for Tracealyzer v4.8.2
* Copyright 2023 Percepio AB
* www.percepio.com
*
* SPDX-License-Identifier: Apache-2.0
*
* The configuration for trace streaming ("stream ports").
*/
extern "C" {
/**
* @def TRC_CFG_STREAM_PORT_DELAY_ON_BUSY
*
* @brief The time to wait if the USB interface is busy.
*/
/**
* @def TRC_CFG_STREAM_PORT_USB_BUFFER_SIZE
*
* @brief Specifies the size of the usb buffer.
*/
/**
* @def TRC_CFG_STREAM_PORT_INTERNAL_BUFFER_SIZE
*
* @brief Configures the size of the internal buffer if used.
*/
/**
* @def TRC_CFG_STREAM_PORT_INTERNAL_BUFFER_WRITE_MODE
*
* @brief This should be set to TRC_INTERNAL_EVENT_BUFFER_OPTION_WRITE_MODE_DIRECT for best performance.
*/
/**
* @def TRC_CFG_STREAM_PORT_INTERNAL_BUFFER_TRANSFER_MODE
*
* @brief Defines if the internal buffer will attempt to transfer all data each time or limit it to a chunk size.
*/
/**
* @def TRC_CFG_STREAM_PORT_INTERNAL_BUFFER_CHUNK_SIZE
*
* @brief Defines the maximum chunk size when transferring
* internal buffer events in chunks.
*/
/**
* @def TRC_CFG_STREAM_PORT_INTERNAL_BUFFER_CHUNK_TRANSFER_AGAIN_SIZE_LIMIT
*
* @brief Defines the number of transferred bytes needed to trigger another transfer.
* It also depends on TRC_CFG_STREAM_PORT_INTERNAL_BUFFER_CHUNK_TRANSFER_AGAIN_COUNT_LIMIT to set a maximum number
* of additional transfers this loop.
* This will increase throughput by immediately doing a transfer and not wait for another xTraceTzCtrl() loop.
*/
/**
* @def TRC_CFG_STREAM_PORT_INTERNAL_BUFFER_CHUNK_TRANSFER_AGAIN_COUNT_LIMIT
*
* @brief Defines the maximum number of times to trigger another transfer before returning to xTraceTzCtrl().
* It also depends on TRC_CFG_STREAM_PORT_INTERNAL_BUFFER_CHUNK_TRANSFER_AGAIN_SIZE_LIMIT to see if a meaningful amount of data was
* transferred in the last loop.
* This will increase throughput by immediately doing a transfer and not wait for another xTraceTzCtrl() loop.
*/
}
需要注意的是,使用TRC_USE_INTERNAL_BUFFER=1时
TraceRecorderSource/streamports/MY_STREAM/config/trcStreamPortConfig.h中
可能太大导致RAM不够,可以改小该内部存储。
typedef struct TraceStreamPortBuffer
{
uint8_t buffer[(TRC_STREAM_PORT_USB_BUFFER_SIZE) + (TRC_STREAM_PORT_INTERNAL_BUFFER_SIZE) + sizeof(TraceUnsignedBaseType_t)];
} TraceStreamPortBuffer_t;
在 vStartScheduler();前 调用如下接口初始化
/* First initialize */
xTraceInitialize();
/*启动kernel*/
/* Start tracing */
xTraceEnable(TRC_START);或者xTraceEnable(TRC_START_FROM_HOST);
由于中断中回调执行一些trace回调函数,所以中断栈注意要加大一点。
注意设置任务栈大小要合适
TraceRecorderSource/config/trcConfig.h
#define TRC_CFG_CTRL_TASK_STACK_SIZE 2048
注意内部堆大小设置
/streamports/MY_STREAM/config/trcStreamPortConfig.h
#define TRC_CFG_STREAM_PORT_INTERNAL_BUFFER_SIZE 10240
系统使用堆大小,FreeRTOSConfig.h中
#define configTOTAL_HEAP_SIZE ((size_t)((unsigned char *)&_heap_size))
打开Tracealyzer软件
创建流trace,对应源码
TRC_USE_TRACEALYZER_RECORDER配置为1 使能trace
TRC_CFG_RECORDER_MODE 配置为TRC_RECORDER_MODE_STREAMING 流模式
配置通讯接口我这里使用CDC,和串口一样,点击OK
点击Start Session,启动。
抓取到数据后,各窗口显示相应的图表
比如事件窗口可以清晰的看到任务的调度过程
比如CPU负载
我们这里搭建了基于FreeRTOS的Tracealyzer环境,后续我们再分享一系列文章,分享下Tracealyzer的原理,然后分享一些典型的案例。
注意代码和Tracealyzer工具的版本要匹配,否则会提示错误
比如如下提示代码版本高于工具
另外就是注意堆栈的设置,以及数据收发接口,最好是使用fifo方式。