2022年8月10日 星期三

Porting FreeRTOS to RISC-V, Based on the Direct-Interrupt Mode.

 

※ The full source code is here.

     Continue my last post, this post would show how to make FreeRTOS run on  WCH CH32V307 RISC-V MCU based on the direct-interrupt mode.


一. Modify the startup assembly.

     we want the RISC-V runs on direct mode, so the most inportant is to set the chip runs on the mode. 

    Change the end of startup code from:

	:
	csrs mstatus, t0

	la t0, _vector_base
	ori t0, t0, 3
	csrw mtvec, t0

	jal  SystemInit
	la t0, main
	csrw mepc, t0
	mret

 To 
	:
        csrs mstatus, t0

	la t0, _exception_base
	ori t0, t0, 0  /*exceptions mode*/
	csrw mtvec, t0

	jal  SystemInit
	jal main 

It is, as FreeRTOS suggested, the chip runs on the machine mode (thus we could use the ecall instruction, to trigger a interrupt).  And call the main function directly ; Compared to the original version, the main function is called in user mode

Once the exception occurs,  the chip would go to the address of _exception_base, which essentially is the Exception_Handler function :

Exception_Handler:
	:
	csrr t0, mcause
	ble t0, x0, interrupt_handler /* Check interrupt */
	li t1, 11 /* Find an M mode ecall (11) */
	beq t1, t0, ecall_m_handler
	:
ecall_m_handler:
	:
	j Ecall_M_Handler
	
	
interrupt_handler: /* Home made vector table */
	slli t0, t0, 3 /* t0 = t0 * 8 */

	la t1, _vector_base
	add t1, t1, t0

	lw t0, 4(sp)
	addi sp, sp, 8

	jr t1
        :
       The code mean, chech it is an interrupt or not (if it is an interrupt, the highest bit of mcause is 1; in the view of numerical value, it is a negative value), otherwise, check it is M mode call or not. If  it an interrupt, find out the interrrupt number and go to the corresponding handler; if it is an M code, go to the function Ecall_M_Handler, which is just a wrapper of the function  freertos_risc_v_trap_handler for the context-switching purpose . The detail of mcause register is at page 35, Table 3.6: Machine cause register (mcause) values after trapThe RISC-V Instruction Set ManualVolume II: Privileged Architecture, as below figure :


     For the chip runs on the machine mode, the mode-checking code in the function xPortStartFirstTask needs to be modified as the FreeRTOS default code :
BaseType_t xPortStartScheduler( void )
{
extern void xPortStartFirstTask( void );

	#if( configASSERT_DEFINED == 1 )
	{
		volatile uint32_t mtvec = 0;
		/* Check the least significant two bits of mtvec are 0b11 - indicating
		multiply vector mode. */
		__asm volatile( "csrr %0, mtvec" : "=r"( mtvec ) );
		configASSERT( ( mtvec & 0x03UL ) == 0x0 );
		/* Check alignment of the interrupt stack - which is the same as the
		stack that was being used by main() prior to the scheduler being
		started. */
:		      

二.   Trap handler :

We need to simply the function freertos_risc_v_trap_handler(in protASM.S),  
Originally, the code is : 
	:
	store_x  sp, 0( t0 )	/* Write sp to first TCB member. */

	csrr a1, mepc
	store_x a1, 0( sp )		/* Save updated exception return address. */

	addi a1, x0, 0x20
	csrs 0x804, a1

	load_x sp, xISRStackTop		/* Switch to ISR stack before function call. */
	:
 The "csrs 0x804, " means to enable nested and hardware stack, as CH32V307 reads. Now is not necessary.
Mea
Change the code as :
	:
	store_x  sp, 0( t0 )	/* Write sp to first TCB member. */

	csrr a0, mcause
	csrr a1, mepc
	store_x a1, 0( sp )
	j handle_synchronous

handle_synchronous:
	addi a1, a1, 4						/* Synchronous so updated exception return address to the instruction 
										after the instruction that generated the exeption. */
	store_x a1, 0( sp )					/* Save updated exception return address. */

test_if_environment_call:
	li t0, 11 							/* 11 == environment call. */
	bne a0, t0, is_exception			/* Not an M environment call, so some other exception. */
	load_x sp, xISRStackTop				/* Switch to ISR stack before function call. */
	jal vTaskSwitchContext
 It is almost as the same as the FreeRTOS default code, but the asynchronous checking and handling have been left out.


三.  Set up the system timer and calling the e-call at its callback.

The function, vPortSetupTimerInterrupt in port.c, originally is :

void vPortSetupTimerInterrupt( void )
{
    /* set software is lowest priority */
    NVIC_SetPriority(Software_IRQn,0xf0);
    NVIC_EnableIRQ(Software_IRQn);
    /* set systick is lowest priority */
    NVIC_SetPriority(SysTicK_IRQn,0xf0);
    NVIC_EnableIRQ(SysTicK_IRQn);

    SysTick->CTLR= 0;
    SysTick->SR  = 0;
    SysTick->CNT = 0;
    SysTick->CMP = configCPU_CLOCK_HZ/configTICK_RATE_HZ;
    SysTick->CTLR= 0xf;
}

It prepares the software_interrupt for triggering the context-switching function, freertos_risc_v_trap_handler. it is useless for now :

void vPortSetupTimerInterrupt( void )
{
	/* Configure SysTick and interrupts. */
	SysTick->SR = 0UL;
	SysTick->CTLR = 0UL;
	SysTick->CNT = 0UL;

	NVIC_EnableIRQ(SysTicK_IRQn);

	SysTick->CMP = (uint64_t)((configCPU_CLOCK_HZ / (configTICK_RATE_HZ * 8)) - 1);
	SysTick->CTLR = 0x1A; /* COUNTDOWN | AUTO RELOAD | HCLK/8 | INT */
	SysTick->CTLR |= 0x20; /* INIT */
	SysTick->CTLR |= 0x01; /* EN */
}

And add the interrrupt callback functions in the ch32v30x_it.c, to deal with the two interrupts, system-timers and Ecall_M_Handler : 

__NAKED void Ecall_M_Handler(void) {
	/* Use naked function to generate a short call, without saving stack.*/
	asm("j freertos_risc_v_trap_handler");
}


extern void vPortSysTick_Handler(void);

__IRQ void SysTick_Handler(void)
{
	vPortSysTick_Handler();
}

(of course, we need to rename the function SysTick_Handler in port.c as vPortSysTick_Handler)

四. Test the porting workable :

  1. we need to prepare two tasks to verify this porting workable.  Below is the task1's implementation, to print out the incremental values :

void task1_task(void *pvParameters)
{
	uint32_t ii = 0;
	while(1)
	{
		xSemaphoreTake(g_mutex, portMAX_DELAY);
		printf("ch32v307_FreeRTOS task1 entry, %u\r\n", ii++);
		xSemaphoreGive(g_mutex);

		const TickType_t task1_delay = 250 / portTICK_PERIOD_MS;
		GPIO_SetBits(GPIOE, GPIO_Pin_11);
		vTaskDelay(task1_delay);
		GPIO_ResetBits(GPIOE, GPIO_Pin_11);
		vTaskDelay(task1_delay);
	}
}

For task2, it is the same but with the different task_delay value and different  GPIO_Pin number.

2. We need to check the interrupt mechansm works on this porting, thus we register the exernal-interrupt and its callback (for we push the button):

main.c :

void EXTI0_INT_INIT(void)
{
	GPIO_InitTypeDef GPIO_InitStructure = {0};
	EXTI_InitTypeDef EXTI_InitStructure = {0};
	NVIC_InitTypeDef NVIC_InitStructure = {0};

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA, ENABLE);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	/* GPIOA ----> EXTI_Line0 */
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
	EXTI_InitStructure.EXTI_Line = EXTI_Line0;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_Init(&EXTI_InitStructure);

	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}

ch32v30x_it.c :

__IRQ void EXTI0_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line0)!=RESET)
	{
		printf("Run at EXTI\r\n");
		EXTI_ClearITPendingBit(EXTI_Line0);     /* Clear Flag */
	}
}


The main function is straightforward, to setup the used GPIOs, register the external interrupt, initialize UART and FreeRTOS, and create the FreeRTOS tasks.

int main(void)
{
    g_mutex = xSemaphoreCreateMutex();

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n",SystemCoreClock);
    printf("FreeRTOS Kernel Version:%s\r\n",tskKERNEL_VERSION_NUMBER);

    GPIO_Toggle_INIT();
    EXTI0_INT_INIT();

    /* create two task */
    xTaskCreate((TaskFunction_t )task2_task,
                        (const char*    )"task2",
                        (uint16_t       )TASK2_STK_SIZE,
                        (void*          )NULL,
                        (UBaseType_t    )TASK2_TASK_PRIO,
                        (TaskHandle_t*  )&Task2Task_Handler);

    xTaskCreate((TaskFunction_t )task1_task,
                    (const char*    )"task1",
                    (uint16_t       )TASK1_STK_SIZE,
                    (void*          )NULL,
                    (UBaseType_t    )TASK1_TASK_PRIO,
                    (TaskHandle_t*  )&Task1Task_Handler);
    vTaskStartScheduler();

    while(1)
    {
        printf("shouldn't run at here!!\n");
    }
} 

While I press the button, the uart output is like below :


The incremental values show the system has run over 17 days (1478504 seconds), it is the evidence for this porting is stable enough.


Reference :

The RISC-V Instruction Set ManualVolume II: Privileged Architecture

RISC-V Exception and Interrupt implementation

FreeRTOS on CH32V307 :: its the reference code 



沒有留言:

張貼留言