GD32VF103移植SVSTEMVIEW
最近兆易推出了基于RISCV的MCU-GD32VF103,得到了一个GD32VF103C-START 学习板,以前只用过arm内核mcu,从没用过RISCV内核,打算玩一下这块板子。这次打算移植一下SEGGER 的SystemView,RTOS选择FreeRTOS。本文开发环境为Linux Mint 19.1。
SystemView
SEGGER SystemView是一种用于嵌入式系统的实时记录和可视化工具,可以显示应用程序的真实运行时行为,远远超过调试器提供的系统洞察。在开发和使用包含多个线程和中断的复杂嵌入式系统时,尤其有效。SystemView可以确保系统按设计执行,可以追踪低效率,并显示意外的交互和资源冲突,重点关注每个系统的详细信息。并且可以免费使用。
目前官方最新版本是V3.10,在这个版本里提供了UART采集的功能,正好GD32VF103目前使用的gdlink不是jlink,在之前的systemview版本里都只支持jlink采集不支持UART采集的功能.
V3.10版本下载地址:https://www.segger.com/downloads/systemview/
但是这个页面上并未提供串口采集移植的示例,不过在SEGGER的官方论坛里的一个帖子找到了一个示例工程:https://forum.segger.com/index.php/Thread/6874-SOLVED-SystemView-serial-port/
移植源文件加入工程
将sysvtemiew的target源码加入工程编译,这里需要注意用于ARM的专用汇编代码可不必加入编译,Syscalls的四个文件可根据需要加入编译,这里我没有加入.
修改freertos源码
这个步骤分为两步,首先根据FreeRTOSV10_Core.patch内容修改freertos源码
然后为gd32vf103的port.c、portasm.h文件里的内核tick中断添加trace调用包含以下四处
void vPortSysTickHandler(void)
{
volatile uint64_t * mtime = (uint64_t*) (TIMER_CTRL_ADDR + TIMER_MTIME);
volatile uint64_t * mtimecmp = (uint64_t*) (TIMER_CTRL_ADDR + TIMER_MTIMECMP);
UBaseType_t uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
#if CONFIG_SYSTEMVIEW_EN
traceISR_ENTER();
#endif
uint64_t now = *mtime;
now += (configRTC_CLOCK_HZ / configTICK_RATE_HZ);
*mtimecmp = now;
/* 调用freertos的tick增加接口 */
if( xTaskIncrementTick() != pdFALSE )
{
#if CONFIG_SYSTEMVIEW_EN
traceISR_EXIT_TO_SCHEDULER();
#endif
portYIELD();
}
#if CONFIG_SYSTEMVIEW_EN
else
{
traceISR_EXIT();
}
#endif
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
}
#if CONFIG_SYSTEMVIEW_EN
#define portEND_SWITCHING_ISR(xSwitchRequired) { if( xSwitchRequired != pdFALSE) { traceISR_EXIT_TO_SCHEDULER(); portYIELD(); } else {traceISR_EXIT(); } }
#else
#define portEND_SWITCHING_ISR(xSwitchRequired) if( xSwitchRequired != pdFALSE) portYIELD()
#endif
添加串口移植
创建SYSVIEW_Serial.c文件,内容如下,这里使用串口0来输出trace采集信息。
#include "SEGGER_RTT.h"
#include "SEGGER_SYSVIEW.h"
#include "SYSVIEW_Serial_Conf.h"
#include "gd32vf103.h"
#define _SERVER_HELLO_SIZE (4)
#define _TARGET_HELLO_SIZE (4)
static struct
{
U8 NumBytesHelloRcvd;
U8 NumBytesHelloSent;
int ChannelID;
} _SVInfo;
static const U8 _abHelloMsg[_TARGET_HELLO_SIZE] = {'S', 'V', (SEGGER_SYSVIEW_VERSION / 10000), (SEGGER_SYSVIEW_VERSION / 1000) % 10}; // "Hello" message expected by SysView: [ 'S', 'V', <PROTOCOL_MAJOR>, <PROTOCOL_MINOR> ]
/**
* @brief This function starts and initializes a SystemView session, if necessary.
*
*/
static void _StartSysView(void)
{
int r;
r = SEGGER_SYSVIEW_IsStarted();
if (r == 0)
{
SEGGER_SYSVIEW_Start();
}
}
/**
* @brief This function is called when the UART receives data.
*
* @param Data
*/
static void _cbOnRx(U8 Data)
{
if (_SVInfo.NumBytesHelloRcvd < _SERVER_HELLO_SIZE)
{ // Not all bytes of <Hello> message received by SysView yet?
_SVInfo.NumBytesHelloRcvd++;
/* 目前版本V3.10,增加这个判断才能正确启动 modify by QQM */
if(_SVInfo.NumBytesHelloRcvd == _SERVER_HELLO_SIZE-1)
{
_StartSysView();
}
goto Done;
}
_StartSysView();
SEGGER_RTT_WriteDownBuffer(_SVInfo.ChannelID, &Data, 1); // Write data into corresponding RTT buffer for application to read and handle accordingly
Done:
return;
}
/**
* @brief This function is called when the UART should transmit data.
*
* @param pChar
* @return int
*/
static int _cbOnTx(U8 *pChar)
{
int r;
if (_SVInfo.NumBytesHelloSent < _TARGET_HELLO_SIZE)
{ // Not all bytes of <Hello> message sent to SysView yet?
*pChar = _abHelloMsg[_SVInfo.NumBytesHelloSent];
_SVInfo.NumBytesHelloSent++;
r = 1;
goto Done;
}
r = SEGGER_RTT_ReadUpBufferNoLock(_SVInfo.ChannelID, pChar, 1);
if (r < 0)
{ // Failed to read from up buffer?
r = 0;
}
Done:
return r;
}
void vSYSVIEWUARTEnableTXEInterrupt(U32 NumBytes)
{
usart_interrupt_enable(USART0, USART_INT_TBE);
}
/**
* @brief sysview uart handle
*
*/
void vSYSVIEWUARTInterruptHandler(void)
{
U8 cChar;
if(RESET != usart_interrupt_flag_get(CONFIG_SYSVIEW_UART_PORT, USART_INT_FLAG_RBNE)){
/* receive data */
cChar = usart_data_receive(CONFIG_SYSVIEW_UART_PORT);
_cbOnRx(cChar);
}
if(RESET != usart_interrupt_flag_get(CONFIG_SYSVIEW_UART_PORT, USART_INT_FLAG_TBE)){
if (0 == _cbOnTx(&cChar))
{
usart_interrupt_disable(CONFIG_SYSVIEW_UART_PORT, USART_INT_TBE);
}
else
{
/* transmit data */
usart_data_transmit(CONFIG_SYSVIEW_UART_PORT, cChar);
}
}
}
/**
* @brief sysview uart init
*
*/
void vSYSVIEWUARTInit(void)
{
_SVInfo.ChannelID = SEGGER_SYSVIEW_GetChannelID(); // Store system view channel ID for later communication
if(CONFIG_SYSVIEW_UART_PORT == USART0)
{
/* 初始化uart0 TX PA9 RX PA10 */
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_USART0);
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
usart_deinit(CONFIG_SYSVIEW_UART_PORT);
usart_baudrate_set(CONFIG_SYSVIEW_UART_PORT, 115200U);
usart_word_length_set(CONFIG_SYSVIEW_UART_PORT, USART_WL_8BIT);
usart_stop_bit_set(CONFIG_SYSVIEW_UART_PORT, USART_STB_1BIT);
usart_parity_config(CONFIG_SYSVIEW_UART_PORT, USART_PM_NONE);
usart_hardware_flow_rts_config(CONFIG_SYSVIEW_UART_PORT, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(CONFIG_SYSVIEW_UART_PORT, USART_CTS_DISABLE);
usart_receive_config(CONFIG_SYSVIEW_UART_PORT, USART_RECEIVE_ENABLE);
usart_transmit_config(CONFIG_SYSVIEW_UART_PORT, USART_TRANSMIT_ENABLE);
usart_interrupt_disable(CONFIG_SYSVIEW_UART_PORT, USART_INT_TBE);
usart_interrupt_enable(CONFIG_SYSVIEW_UART_PORT, USART_INT_RBNE);
usart_enable(CONFIG_SYSVIEW_UART_PORT);
eclic_irq_enable(USART0_IRQn, 1, 0);
}
}
修改systemview配置
- SEGGER/Config/SEGGER_RTT_Conf.h,增加SEGGER_RTT_LOCK、SEGGER_RTT_UNLOCK定义,通过eclic控制器mth阈值来关闭中断上锁
/*********************************************************************
*
* RTT lock configuration for SEGGER Embedded Studio,
* Rowley CrossStudio and GCC
*/
#if (defined(__SES_ARM) || defined(__CROSSWORKS_ARM) || defined(__GNUC__) || defined(__clang__)) && !defined (__CC_ARM)
#if (defined(__ARM_ARCH_6M__) || defined(__ARM_ARCH_8M_BASE__))
#define SEGGER_RTT_LOCK() { \
unsigned int LockState; \
__asm volatile ("mrs %0, primask \n\t" \
"movs r1, $1 \n\t" \
"msr primask, r1 \n\t" \
: "=r" (LockState) \
: \
: "r1" \
);
#define SEGGER_RTT_UNLOCK() __asm volatile ("msr primask, %0 \n\t" \
: \
: "r" (LockState) \
: \
); \
}
#elif (defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_8M_MAIN__))
#ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY
#define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20)
#endif
#define SEGGER_RTT_LOCK() { \
unsigned int LockState; \
__asm volatile ("mrs %0, basepri \n\t" \
"mov r1, %1 \n\t" \
"msr basepri, r1 \n\t" \
: "=r" (LockState) \
: "i"(SEGGER_RTT_MAX_INTERRUPT_PRIORITY) \
: "r1" \
);
#define SEGGER_RTT_UNLOCK() __asm volatile ("msr basepri, %0 \n\t" \
: \
: "r" (LockState) \
: \
); \
}
#elif defined(__ARM_ARCH_7A__)
#define SEGGER_RTT_LOCK() { \
unsigned int LockState; \
__asm volatile ("mrs r1, CPSR \n\t" \
"mov %0, r1 \n\t" \
"orr r1, r1, #0xC0 \n\t" \
"msr CPSR_c, r1 \n\t" \
: "=r" (LockState) \
: \
: "r1" \
);
#define SEGGER_RTT_UNLOCK() __asm volatile ("mov r0, %0 \n\t" \
"mrs r1, CPSR \n\t" \
"bic r1, r1, #0xC0 \n\t" \
"and r0, r0, #0xC0 \n\t" \
"orr r1, r1, r0 \n\t" \
"msr CPSR_c, r1 \n\t" \
: \
: "r" (LockState) \
: "r0", "r1" \
); \
}
#else
#include "portmacro.h"
#define SEGGER_RTT_LOCK() { \
unsigned int LockState = xPortSetInterruptMask();
#define SEGGER_RTT_UNLOCK() vPortClearInterruptMask(LockState); \
}
#endif
#endif
- SEGGER/Config/SEGGER_SYSVIEW_Conf.h,定义SEGGER_SYSVIEW_ON_EVENT_RECORDED为触发USART_INT_TBE中断
#ifndef SEGGER_SYSVIEW_ON_EVENT_RECORDED
extern void vSYSVIEWUARTEnableTXEInterrupt(U32 NumBytes);
#define SEGGER_SYSVIEW_ON_EVENT_RECORDED(NumBytes) vSYSVIEWUARTEnableTXEInterrupt(NumBytes) // Needed for SystemView via non-J-Link Recorder. Macro to enable the UART or notify IP task.
#endif
- SEGGER/FreeRTOSV10/Config/SEGGER_SYSVIEW_Config_FreeRTOS.c定义中断号查询以及tick数获取函数,以及一些设备信息的填写,包括设备名称,应用名称,内核名称,cpu时钟等
/*********************************************************************
*
* Defines, configurable
*
**********************************************************************
*/
// The application name to be displayed in SystemViewer
#define SYSVIEW_APP_NAME "GD32VF103_SYSVIEW"
// The target device name
#define SYSVIEW_DEVICE_NAME "GD32VF103"
// The target core name
#define SYSVIEW_CORE_NAME "RV32"
// Frequency of the timestamp. Must match SEGGER_SYSVIEW_GET_TIMESTAMP in SEGGER_SYSVIEW_Conf.h
#define SYSVIEW_TIMESTAMP_FREQ (configRTC_CLOCK_HZ)
// System Frequency. SystemcoreClock is used in most CMSIS compatible projects.
#define SYSVIEW_CPU_FREQ configCPU_CLOCK_HZ
// The lowest RAM address used for IDs (pointers)
#define SYSVIEW_RAM_BASE (0x20000000)
/*********************************************************************
*
* _cbSendSystemDesc()
*
* Function description
* Sends SystemView description strings.
*/
static void _cbSendSystemDesc(void) {
SEGGER_SYSVIEW_SendSysDesc("N="SYSVIEW_APP_NAME",D="SYSVIEW_DEVICE_NAME",C="SYSVIEW_CORE_NAME",O=FreeRTOS");
SEGGER_SYSVIEW_SendSysDesc("I#7=SysTick");
}
/*********************************************************************
*
* Global functions
*
**********************************************************************
*/
void SEGGER_SYSVIEW_Conf(void) {
SEGGER_SYSVIEW_Init(SYSVIEW_TIMESTAMP_FREQ, SYSVIEW_CPU_FREQ,
&SYSVIEW_X_OS_TraceAPI, _cbSendSystemDesc);
SEGGER_SYSVIEW_SetRAMBase(SYSVIEW_RAM_BASE);
}
U32 SEGGER_SYSVIEW_X_GetTimestamp(void)
{
return (*(U32 *)(TIMER_CTRL_ADDR+TIMER_MTIME));
}
U32 SEGGER_SYSVIEW_X_GetInterruptId(void)
{
return 0xFFF&read_csr(mcause);
}
- gd32vf103_it.c 添加Application/gd32vf103_it.c到USART0_IRQHandler内
/*!
\brief this function handles USART RBNE interrupt request and TBE interrupt request
\param[in] none
\param[out] none
\retval none
*/
void USART0_IRQHandler(void)
{
#if CONFIG_SYSTEMVIEW_EN
vSYSVIEWUARTInterruptHandler();
#endif
}
完成移植
在main函数调用SEGGER_SYSVIEW_Conf()和vSYSVIEWUARTInit()函数,创建两个测试任务看看。
void task1(void *p)
{
for(;;)
{
gpio_bit_write(GPIOA, GPIO_PIN_7, (bit_status)(1-gpio_input_bit_get(GPIOA, GPIO_PIN_7)));
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void task2(void *p)
{
char *taskStatus = (char *)pvPortMalloc( uxTaskGetNumberOfTasks() * sizeof( TaskStatus_t ) );
for(;;)
{
vTaskList(taskStatus);
printf("\nTaskName\tStatus\tPRI\tStack\tTaskNumber\n%s",taskStatus);
printf("current tick is %ld\n",xTaskGetTickCount());
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
int main(void)
{
eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL4_PRIO0); //四位优先级组全配置为lvl
eclic_global_interrupt_enable(); //使能全局中断
#if CONFIG_SYSTEMVIEW_EN
SEGGER_SYSVIEW_Conf();
printf("Segger Sysview Control Block Detection Address is 0x%lx\n", (uint32_t)&_SEGGER_RTT);
vSYSVIEWUARTInit();
#endif
#if UARTLOGEN
uart_log_init();
#endif
/* 初始化led PA7 */
rcu_periph_clock_enable(RCU_GPIOA);
gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
gpio_bit_reset(GPIOA, GPIO_PIN_7);
xTaskCreate(task1,"task1",521,NULL,2,NULL);
xTaskCreate(task2,"task2",521,NULL,2,NULL);
vTaskStartScheduler();
}
编译生成bin
通过gdb调试下载运行
打开systemview设置串口com号和波特率,这里systemview我安装在虚拟机里,因为我的主机开发环境是linux,systemview官方一直是支持linux和mac环境的,但是V3.10这个版本存在bug,只能使用windous版本,目前官方已知道该问题后续版本会修复,但截止本文完成还未发布新版本,所以这里只能用虚拟机里跑windous版本的systemview
点击开始采集,最终效果如下
本文使用的源码工程已开源到我的github:https://github.com/QQxiaoming/gd32vf103_freertos