使用SystemView分析FreeRTOS应用
1.前言
在使用FreeRTOS进行应用开发时经常会遇到普通的方式难以调试的问题,如栈内存不足等等,同时也希望对运行的多个Task进行实时的性能及资源占用的分析,通常的调试手段在这里就变的心有余而力不足了。以FreeRTOS为例,如何在长时间的运行过程中收集调试数据进行分析,以及如何调试不同的组件(如Queue,Notification,Semaphore等等)?这个时候就需要Trace工具帮忙了。针对RTOS的Trace需求,有多种商业工具可供选择,但其中可供免费使用的产品则寥寥无几。本文将针对SEGGER开发的SystemView Trace工具进行介绍。
2. SystemView
SystemView是SEGGER开发的针对嵌入式系统的trace工具,支持多种RTOS,也支持自定义OS的移植(需实现trace API,参见User Manual)。其核心基于SEGGER RTT,一个Host-Target间的通信框架,可通过多种方式连接,除J-LINK之外还可以使用串口及TCP-IP协议,对非商业用途免费且无功能限制。
3. 环境准备
我们使用的测试环境是STM32H750VB开发板,CPU为Cortex-M7,最高主频480MHz,程序位于QSPI Flash中,通过XIP方式运行。FreeRTOS为STM32CubeMX配置的未修改版本。运行一个自定义Task,Toggle开发板上的一个LED灯。
//测试Task
TaskHandle_t xTaskHelloHandle = NULL;
void vTaskHello(void *pvParameters) {
for(;;) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);
SEGGER_SYSVIEW_PrintfTarget("Hello world @%lu\n", HAL_GetTick());
//这里使用了SystemView系统库提供的输出函数,向主机输出Message
vTaskDelay(1000);
}
vTaskDelete(NULL);
}
在启动时自动创建该Task:
xTaskCreate(vTaskHello, "hello_task", 1024, NULL, 7, NULL);
栈深度为1024字,优先级为7
4. SystemView trace库移植
首先下载目标板源码,将其中的 SEGGER
目录下代码以及 Sample
中的FreeRTOS代码添加到项目。
SystemView
├── Config
│ ├── Global.h
│ ├── SEGGER_RTT_Conf.h
│ ├── SEGGER_SYSVIEW_Conf.h
│ └── SEGGER_SYSVIEW_Config_FreeRTOS.c
└── SEGGER
├── SEGGER.h
├── SEGGER_RTT.c
├── SEGGER_RTT.h
├── SEGGER_RTT_ASM_ARMv7M.s
├── SEGGER_RTT_printf.c
├── SEGGER_SYSVIEW.c
├── SEGGER_SYSVIEW.h
├── SEGGER_SYSVIEW_ConfDefaults.h
├── SEGGER_SYSVIEW_FreeRTOS.c
├── SEGGER_SYSVIEW_FreeRTOS.h
├── SEGGER_SYSVIEW_Int.h
└── Syscalls
└── SEGGER_RTT_Syscalls_GCC.c
3 directories, 16 files
接下来我们需要实现通过串口的RTT数据收发,这里使用一个额外的Task实现这个操作。
#define SYSVIEW_COMM_APP_HELLO_SIZE 32
#define SYSVIEW_COMM_TARGET_HELLO_SIZE 32
#define SYSVIEW_SINGLE_TX 256
U8 hello_message[SYSVIEW_COMM_TARGET_HELLO_SIZE] = {
'S', 'E', 'G', 'G', 'E', 'R', ' ',
'S', 'y', 's', 't', 'e', 'm', 'V', 'i', 'e', 'w',
' ', 'V', '0' + SEGGER_SYSVIEW_MAJOR,
'.', '0' + (SEGGER_SYSVIEW_MINOR / 10),
'0' + (SEGGER_SYSVIEW_MINOR % 10),
'.', '0' + (SEGGER_SYSVIEW_REV / 10),
'0' + (SEGGER_SYSVIEW_REV % 10),
'\0', 0, 0, 0, 0, 0
};
TaskHandle_t xTaskTraceCommHandle = NULL;
void vTaskTraceComm(void *pvParameters) {
//获取Channel ID
int channel_id = SEGGER_SYSVIEW_GetChannelID();
//发送HELLO包
HAL_UART_Transmit(&huart1, hello_message, SYSVIEW_COMM_TARGET_HELLO_SIZE, 1000);
uint8_t rx_buf;
uint8_t tx_buf[SYSVIEW_SINGLE_TX];
//启动记录
SEGGER_SYSVIEW_Start();
//等待通过串口中断接收数据
HAL_UART_Receive_IT(&huart1, &rx_buf, 0x01);
for(;;) {
if(xTaskNotifyWait(0x00, 0x01, NULL, pdMS_TO_TICKS(400)) == pdTRUE) {
//接收到数据,写入到RTT缓存
SEGGER_RTT_WriteDownBufferNoLock(channel_id, &rx_buf, 0x01);
HAL_UART_Receive_IT(&huart1, &rx_buf, 0x01);
}
//获取上行缓存数据长度
unsigned int tx_length = SEGGER_RTT_GetBytesInBuffer(channel_id);
if(tx_length >= SYSVIEW_SINGLE_TX) {
//STM32 HAL代码有最大长度限制,这里默认设置为256字节,可调整
//从RTT缓存中读出数据
uint32_t num = SEGGER_RTT_ReadUpBufferNoLock(channel_id, tx_buf, SYSVIEW_SINGLE_TX);
//通过串口发送,此处可使用中断方式发送,尚未实现。
HAL_UART_Transmit(&huart1, tx_buf, num, 100);
} else if (tx_length != 0){
// 同上
uint32_t num = SEGGER_RTT_ReadUpBufferNoLock(channel_id, tx_buf, tx_length);
HAL_UART_Transmit(&huart1, tx_buf, num, 100);
}
}
vTaskDelete(NULL);
}
//串口ISR
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
//向Task发送通知,收到数据
BaseType_t higher_woken = pdFALSE;
xTaskNotifyFromISR(xTaskTraceCommHandle, 0x01, eSetBits, &higher_woken);
portYIELD_FROM_ISR(higher_woken);
}
}
创建Task:
xTaskCreate(vTaskTraceComm, "trace_task", 1024, NULL, 6, &xTaskTraceCommHandle);
在RTOS调度器启动之前执行 SEGGER_SYSVIEW_Conf()
。
默认情况下SystemView会从DWT的Cycle counter获取系统当前周期数用于生成时间戳:
/*********************************************************************
*
* SystemView timestamp configuration
*/
#if !defined(SEGGER_SYSVIEW_GET_TIMESTAMP) && !defined(SEGGER_SYSVIEW_TIMESTAMP_BITS)
#if SEGGER_SYSVIEW_CORE == SEGGER_SYSVIEW_CORE_CM3
#define SEGGER_SYSVIEW_GET_TIMESTAMP() (*(U32 *)(0xE0001004)) // Retrieve a system timestamp. Cortex-M cycle counter.
#define SEGGER_SYSVIEW_TIMESTAMP_BITS 32 // Define number of valid bits low-order delivered by clock source
#else
#define SEGGER_SYSVIEW_GET_TIMESTAMP() SEGGER_SYSVIEW_X_GetTimestamp() // Retrieve a system timestamp via user-defined function
#define SEGGER_SYSVIEW_TIMESTAMP_BITS 32 // Define number of valid bits low-order delivered by SEGGER_SYSVIEW_X_GetTimestamp()
#endif
#endif
在STM32CubeMX的初始化代码中该计数器默认未启用,在这里手工启用。
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
修改 FreeRTOSConfig.h
引入SystemView的trace宏:
/* USER CODE BEGIN Defines */
/* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */
#define INCLUDE_xTaskGetIdleTaskHandle 1
#include "SEGGER_SYSVIEW_FreeRTOS.h"
/* USER CODE END Defines */
至此,我们的移植过程基本结束。
注:在SEGGER代码中, _write
部分的实现存在签名不兼容的问题,此函数是用于将stdout通过RTT输出到SystemView,可直接删除 SEGGER/Syscalls/SEGGER_RTT_Syscalls_GCC.c
,也可修改函数签名使其与系统库一致。
注2:在初始化时使用了 strcpy()
函数,在测试环境中出现Hardfault问题,可在 SEGGER/SEGGER_RTT.c
中将
替换为
初步推测为Null-Terminated的问题,有头绪欢迎联系指正。
2020-05-07更新:使用DMA传输以减少CPU开销
更新部分代码如下
同时添加DMA传输完成中断
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
BaseType_t higher_woken = pdFALSE;
xTaskNotifyFromISR(xTaskTraceCommHandle, 0x02, eSetBits, &higher_woken);
portYIELD_FROM_ISR(higher_woken);
}
}
即可实现DMA传输
5. 软件使用
启动SystemView,在Target->Recorder Configuration中指定串口及波特率,连接后RESET目标板即可接收事件。如下图。
6. Patch
默认的FreeRTOS trace宏不足以收集足够的参数,因此需要Patch FreeRTOS。提供参考Patch如下,来自SEGGER的Sample目录
diff -ur Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h ../FreeRTOS/Source/include/FreeRTOS.h
--- Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h 2020-05-07 22:43:33.533067758 +0800
+++ ../FreeRTOS/Source/include/FreeRTOS.h 2020-05-07 22:41:30.520012215 +0800
@@ -156,6 +156,10 @@
#define INCLUDE_uxTaskGetStackHighWaterMark 0
#endif
+#ifndef INCLUDE_pxTaskGetStackStart
+ #define INCLUDE_pxTaskGetStackStart 0
+#endif
+
#ifndef INCLUDE_uxTaskGetStackHighWaterMark2
#define INCLUDE_uxTaskGetStackHighWaterMark2 0
#endif
@@ -400,6 +404,25 @@
#define tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
#endif
+#ifndef traceREADDED_TASK_TO_READY_STATE
+ #define traceREADDED_TASK_TO_READY_STATE( pxTCB ) traceMOVED_TASK_TO_READY_STATE( pxTCB )
+#endif
+
+#ifndef traceMOVED_TASK_TO_DELAYED_LIST
+ #define traceMOVED_TASK_TO_DELAYED_LIST()
+#endif
+
+#ifndef traceMOVED_TASK_TO_OVERFLOW_DELAYED_LIST
+ #define traceMOVED_TASK_TO_OVERFLOW_DELAYED_LIST()
+#endif
+
+#ifndef traceMOVED_TASK_TO_SUSPENDED_LIST
+ #define traceMOVED_TASK_TO_SUSPENDED_LIST( pxTCB )
+#endif
+
+
+
+
#ifndef traceQUEUE_CREATE
#define traceQUEUE_CREATE( pxNewQueue )
#endif
@@ -644,6 +667,18 @@
#define traceTASK_NOTIFY_GIVE_FROM_ISR()
#endif
+#ifndef traceISR_EXIT_TO_SCHEDULER
+ #define traceISR_EXIT_TO_SCHEDULER()
+#endif
+
+#ifndef traceISR_EXIT
+ #define traceISR_EXIT()
+#endif
+
+#ifndef traceISR_ENTER
+ #define traceISR_ENTER()
+#endif
+
#ifndef traceSTREAM_BUFFER_CREATE_FAILED
#define traceSTREAM_BUFFER_CREATE_FAILED( xIsMessageBuffer )
#endif
diff -ur Middlewares/Third_Party/FreeRTOS/Source/include/task.h ../FreeRTOS/Source/include/task.h
--- Middlewares/Third_Party/FreeRTOS/Source/include/task.h 2020-05-07 22:43:33.529734437 +0800
+++ ../FreeRTOS/Source/include/task.h 2020-05-07 22:41:30.516678886 +0800
@@ -1438,6 +1438,25 @@
/**
* task.h
+ * <PRE>uint8_t* pxTaskGetStackStart( TaskHandle_t xTask);</PRE>
+ *
+ * INCLUDE_pxTaskGetStackStart must be set to 1 in FreeRTOSConfig.h for
+ * this function to be available.
+ *
+ * Returns the start of the stack associated with xTask. That is,
+ * the highest stack memory address on architectures where the stack grows down
+ * from high memory, and the lowest memory address on architectures where the
+ * stack grows up from low memory.
+ *
+ * @param xTask Handle of the task associated with the stack returned.
+ * Set xTask to NULL to return the stack of the calling task.
+ *
+ * @return A pointer to the start of the stack.
+ */
+uint8_t* pxTaskGetStackStart( TaskHandle_t xTask) PRIVILEGED_FUNCTION;
+
+/**
+ * task.h
* <PRE>configSTACK_DEPTH_TYPE uxTaskGetStackHighWaterMark2( TaskHandle_t xTask );</PRE>
*
* INCLUDE_uxTaskGetStackHighWaterMark2 must be set to 1 in FreeRTOSConfig.h for
diff -ur Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c ../FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c
--- Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c 2020-05-07 22:43:33.533067758 +0800
+++ ../FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c 2020-05-07 22:41:30.536678867 +0800
@@ -492,14 +492,20 @@
save and then restore the interrupt mask value as its value is already
known. */
portDISABLE_INTERRUPTS();
+ traceISR_ENTER();
{
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )
{
+ traceISR_EXIT_TO_SCHEDULER();
/* A context switch is required. Context switching is performed in
the PendSV interrupt. Pend the PendSV interrupt. */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
+ else
+ {
+ traceISR_EXIT();
+ }
}
portENABLE_INTERRUPTS();
}
diff -ur Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/portmacro.h ../FreeRTOS/Source/portable/GCC/ARM_CM4F/portmacro.h
--- Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/portmacro.h 2020-05-07 22:43:33.533067758 +0800
+++ ../FreeRTOS/Source/portable/GCC/ARM_CM4F/portmacro.h 2020-05-07 22:41:30.536678867 +0800
@@ -89,7 +89,7 @@
#define portNVIC_INT_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT ( 1UL << 28UL )
-#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired != pdFALSE ) portYIELD()
+#define portEND_SWITCHING_ISR( xSwitchRequired ) { if( xSwitchRequired ) { traceISR_EXIT_TO_SCHEDULER(); portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; } else { traceISR_EXIT(); } }
#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )
/*-----------------------------------------------------------*/
diff -ur Middlewares/Third_Party/FreeRTOS/Source/tasks.c ../FreeRTOS/Source/tasks.c
--- Middlewares/Third_Party/FreeRTOS/Source/tasks.c 2020-05-07 22:43:33.549734367 +0800
+++ ../FreeRTOS/Source/tasks.c 2020-05-07 22:41:30.546678856 +0800
@@ -220,6 +220,17 @@
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
+
+/*
+ * Place the task represented by pxTCB which has been in a ready list before
+ * into the appropriate ready list for the task.
+ * It is inserted at the end of the list.
+ */
+#define prvReaddTaskToReadyList( pxTCB ) \
+ traceREADDED_TASK_TO_READY_STATE( pxTCB ); \
+ taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
+ vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
+ tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
/*-----------------------------------------------------------*/
/*
@@ -1126,7 +1137,7 @@
#endif /* configUSE_TRACE_FACILITY */
traceTASK_CREATE( pxNewTCB );
- prvAddTaskToReadyList( pxNewTCB );
+ prvReaddTaskToReadyList( pxNewTCB );
portSETUP_TCB( pxNewTCB );
}
@@ -1726,6 +1737,8 @@
mtCOVERAGE_TEST_MARKER();
}
+ traceMOVED_TASK_TO_SUSPENDED_LIST(pxTCB);
+
vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
#if( configUSE_TASK_NOTIFICATIONS == 1 )
@@ -3821,6 +3834,20 @@
#endif /* INCLUDE_uxTaskGetStackHighWaterMark */
/*-----------------------------------------------------------*/
+#if (INCLUDE_pxTaskGetStackStart == 1)
+ uint8_t* pxTaskGetStackStart( TaskHandle_t xTask)
+ {
+ TCB_t *pxTCB;
+ UBaseType_t uxReturn;
+ (void)uxReturn;
+
+ pxTCB = prvGetTCBFromHandle( xTask );
+ return ( uint8_t * ) pxTCB->pxStack;
+ }
+
+#endif /* INCLUDE_pxTaskGetStackStart */
+/*-----------------------------------------------------------*/
+
#if ( INCLUDE_vTaskDelete == 1 )
static void prvDeleteTCB( TCB_t *pxTCB )
@@ -3990,7 +4017,7 @@
/* Inherit the priority before being moved into the new list. */
pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
- prvAddTaskToReadyList( pxMutexHolderTCB );
+ prvReaddTaskToReadyList( pxMutexHolderTCB );
}
else
{
@@ -4080,7 +4107,7 @@
any other purpose if this task is running, and it must be
running to give back the mutex. */
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
- prvAddTaskToReadyList( pxTCB );
+ prvReaddTaskToReadyList( pxTCB );
/* Return true to indicate that a context switch is required.
This is only actually required in the corner case whereby
@@ -5112,6 +5139,7 @@
/* Add the task to the suspended task list instead of a delayed task
list to ensure it is not woken by a timing event. It will block
indefinitely. */
+ traceMOVED_TASK_TO_SUSPENDED_LIST(pxCurrentTCB);
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else
@@ -5128,12 +5156,14 @@
{
/* Wake time has overflowed. Place this item in the overflow
list. */
+ traceMOVED_TASK_TO_OVERFLOW_DELAYED_LIST();
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else
{
/* The wake time has not overflowed, so the current block list
is used. */
+ traceMOVED_TASK_TO_OVERFLOW_DELAYED_LIST();
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
/* If the task entering the blocked state was placed at the
@@ -5163,11 +5193,13 @@
if( xTimeToWake < xConstTickCount )
{
/* Wake time has overflowed. Place this item in the overflow list. */
+ traceMOVED_TASK_TO_OVERFLOW_DELAYED_LIST();
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else
{
/* The wake time has not overflowed, so the current block list is used. */
+ traceMOVED_TASK_TO_DELAYED_LIST();
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
/* If the task entering the blocked state was placed at the head of the