NXP S32K144学习系列3----基于FreeRTOS进行多个ms级任务的创建

其实拿到开发板的那一刻我就想好了,这次不从外设开始学习,而是先要把FreeRTOS移植好再把外设一个一个的学起来。而把FreeRTOS移植到S32K144的第一步就是创建几个周期性任务。

首先先大致介绍下FreeRTOS,一切资料都可以从如下官网获取(包括代码包,资料等):

FreeRTOS - Market leading RTOS (Real Time Operating System) for embedded systems with Internet of Things extensionsMarket-leading MIT licensed open source real-time operating system (RTOS) for microcontrollers and microprocessors. Includes IoT and general purpose libraries.https://www.freertos.org/

FreeRTOS和常说的uC/OS都属于是 RTOS 类操作系统,RTOS是什么?可以分为RT(Real Time)和OS(Operating System)两部分去理解,首先,这种操作系统是适合于汽车行业等对于“硬实时性”有严格要求的场合,而大家常说的windows和linux虽也是操作系统,但这类操作系统属于“软实时性”操作系统,什么是“软实时性”?“软实时性”是指响应时间若超出了期望的时间范围,并不会让user觉得这个功能失效了,而只是觉得这台电脑有点慢;“硬实时性”是指所有任务必须在期望时间内完成,没有完成意味着该功能的失效。为什么要有这个区分呢?举个例子,打开IE浏览器搜索CSDN,等了三秒才出来,你认为不过是慢了点,不影响后续操作,但汽车安全气囊或主动安全系统的触发即使比期望时间多了1ms,都有可能造成无法挽回的损失。因此,大多数嵌入式系统不仅能满足硬实时要求,也能满足软实时要求,当然这跟MCU性能是分不开的,一般Linux操作系统只会在拥有A核的SOC芯片上去完成搭建,而我们这个普普通通相貌平平的CortexM4还是算了。

话说回来,为什么要一上来就进行FreeRTOS的移植,移植好之后可以给我们带来什么?这个问题牵扯到太多,我只从代码架构的角度来简单讲下,假设没有那我们所有函数都要堆在main函数最后的while中去完成,且不说代码阅读的问题,一个更重要的问题是这样的话除了中断外就没有任何任务的时序了,即便是你通过counter实现了每个任务运行频率的控制,但也没办法实现不同任务之间的抢占和调度。

对于前面已经下载过S32DS的同学们就不必从FreeRTOS官网下载再copy到project这样做啦,因为NXP已经帮我们下载好了,而我们需要做的只是把FreeRTOS这个包导进来,具体做法如下:

在新建好工程之后找到ComponetsLib试图,双击FreeRTOS包,怎么找到这个试图参见我的上一篇文章:

NXP S32K144学习系列3----基于FreeRTOS进行多个ms级任务的创建_第1张图片

 双击OSs下的Freertos,出现Component Inspector - FreeRTOS ,这里面可以配置一些freertos的选项,这些选项在FreeRTOSConfig.h也可以修改,其实本质上是一样的,唯一有一点需要注意的是,我这里的开发板时钟主频为48MHz,后面的systick必须和这个频率一致,否则os会跑的不准:

NXP S32K144学习系列3----基于FreeRTOS进行多个ms级任务的创建_第2张图片

NXP S32K144学习系列3----基于FreeRTOS进行多个ms级任务的创建_第3张图片

配置好之后点击Generate Code:

NXP S32K144学习系列3----基于FreeRTOS进行多个ms级任务的创建_第4张图片

当看到Project里面层级中自动导入了rtos就代表你已经完成了将FreeRTOS的原始包导入工程中的任务,可以开始真正集成的工作了。

集成之前有必要先对代码的运行进行一个简单的介绍,要对代码的运行有一个时间和空间上的初步概念。一般来说,正常完整的软件启动过程是这样的:

BootROM->Boot->APP

其中BootROM是芯片厂家对芯片写死的一部分代码,这部分代码的功能是完成芯片上电后的一些自检工作,然后会jump到Boot的“main”,Boot一般有两个主要作用,1是完成刷新(刷app或自刷新),2是完成跳转;APP是我们比较熟知的一块了,正常来说我们所做的绝大部分工作都是在App这个Block中完成的。以上三个Block可以理解为具备独立的三个hex。

而我们拿到的demo工程百分之九十九不会采取上述方式让code运行,我们可以先瞅一眼demo是如何做的?从startup.s开始看起:

NXP S32K144学习系列3----基于FreeRTOS进行多个ms级任务的创建_第5张图片

 M4属于armv7架构,然后在section isr_vector段定义了一个label叫_isr_vector(什么是section什么是段?这个我会在后面的文章中讲到,专门讲一下linker),然后在这个段中首先定义了一个中断向量表(中断向量表中全部为弱定义的handler,并不是中断服务函数本体),表头又定义了一个label叫__StackTop,目的是在map中告诉你这个是栈顶(但是和我理解的stack不太一样,难道不应该是放到最后?或者说stack放到最前最后都是可以的?),接下来是一个Reset_Handler,这个东西和普通的中断handler可不一样,他决定了我们芯片在reset之后去做什么,指针指向哪里,也就解答了我们demo中的code是如何开始跑的。

看下Reset_Handler函数的具体内容:

/* Reset Handler */

    .thumb_func
    .align 2
    .globl   Reset_Handler
    .weak    Reset_Handler
    .type    Reset_Handler, %function
Reset_Handler:
    cpsid   i               /* Mask interrupts */

    /* Init the rest of the registers */
    ldr     r1,=0
    ldr     r2,=0
    ldr     r3,=0
    ldr     r4,=0
    ldr     r5,=0
    ldr     r6,=0
    ldr     r7,=0
    mov     r8,r7
    mov     r9,r7
    mov     r10,r7
    mov     r11,r7
    mov     r12,r7

#ifdef START_FROM_FLASH

    /* Init ECC RAM */

    ldr r1, =__RAM_START
    ldr r2, =__RAM_END

    subs    r2, r1
    subs    r2, #1
    ble .LC5

    movs    r0, 0
    movs    r3, #4
.LC4:
    str r0, [r1]
    add	r1, r1, r3
    subs r2, 4
    bge .LC4
.LC5:
#endif
    
    /* Initialize the stack pointer */
    ldr     r0,=__StackTop
    mov     r13,r0

#ifndef __NO_SYSTEM_INIT
    /* Call the system init routine */
    ldr     r0,=SystemInit
    blx     r0
#endif

    /* Init .data and .bss sections */
    ldr     r0,=init_data_bss
    blx     r0
    cpsie   i               /* Unmask interrupts */
    bl      main

这里注释已经写的很清晰了,依次完成了r0到r13的内核系统寄存器初始化,然后是调用了一个叫SystemInit的函数进行系统初始化,最后会跳转到大家再熟悉不过的main函数。也就是说,你可以使他跳转到任意一个函数,并不一定非要从main开始。SystemInit函数的代码基本上都是对芯片system和看门狗寄存器的操作,大致看下函数的注释内容:

/*FUNCTION**********************************************************************
 *
 * Function Name : SystemInit
 * Description   : This function disables the watchdog, enables FPU
 * and the power mode protection if the corresponding feature macro
 * is enabled. SystemInit is called from startup_device file.
 *
 * Implements    : SystemInit_Activity
 *END**************************************************************************/

写的很明白,这个函数是被startup调用的,完成的功能有去看门狗,是能浮点运算单元等。

那么还有一个问题,这个Startup.s是如何被执行的呢?这个问题我的理解是,.s文件是MCU在上电或reset时均必定会执行的一段引导程序,类似于我们电脑中启动时候的BIOS,目的是完成环境的初始化工作,当然也不一定只有.s这一种方式去完成(类似于我上面讲到的BootROM->Boot->APP这个过程)。

了解这些基本知识后,正式开始os任务的创建。

我对于FreeRTOS任务的创建架构是这样的:

NXP S32K144学习系列3----基于FreeRTOS进行多个ms级任务的创建_第6张图片

首先从main函数开始,完成必要的初始化后进行systick的配置,需要注意的是我这里并不是使用的外设timer而是内核的timer,入参ticks前面已经讲过了,使用48000。

uint16_t SysTick_Config(unsigned int ticks)
{

  SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */
  SCB->SHP[(((uint32_t)-1) & 0xFUL)-4UL] = (uint8_t)((((1UL << 4) - 1UL) << (8U - 4)) & (uint32_t)0xFFUL);
  SysTick->VAL   = 0UL;                                             /* Load the SysTick Counter Value */
  SysTick->CTRL  = (1<<2) |
                   (1<<1) |
                   (1<<0);                         /* Enable SysTick IRQ and SysTick Timer */
  return (0UL);                                                     /* Function successful */
}

 创建一个队列和一个任务:

	/* Create the queue. */
	xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof(uint8_t) );
	/* create  task, name app */
	xTaskCreate((TaskFunction_t )Os_InitTsk,
                        (const char*    )"Os_InitTsk",
                        (uint16_t       )Os_InitTsk_StckSize,
                        (void*          )NULL,
                        (UBaseType_t    )u_OsShell_InitTsk_Prio,
                        (TaskHandle_t*  )&os_InitTask_Handle);

然后在Os_InitTsk函数中创建callback函数挂载user想要初始化的任务,同时创建runnable任务:

static void Os_InitTsk(void)
{
	BaseType_t xReturn = pdPASS;
	Os_InitTsk_Callback();
	taskENTER_CRITICAL();
	xReturn = xTaskCreate((TaskFunction_t )Os_1msTsk,
                        (const char*    )"Os_1msTsk",
                        (uint16_t       )Os_1ms_StckSize,
                        (void*          )NULL,
                        (UBaseType_t    )Os_1ms_Prio,
                        (TaskHandle_t*  )&os_1msTask_Handle);

	xReturn = xTaskCreate((TaskFunction_t )Os_10msTsk,
                        (const char*    )"Os_10msTsk",
                        (uint16_t       )Os_10ms_StckSize,
                        (void*          )NULL,
                        (UBaseType_t    )Os_10ms_Prio,
                        (TaskHandle_t*  )&os_10msTask_Handle);

	xReturn = xTaskCreate((TaskFunction_t )Os_100msTsk,
                        (const char*    )"Os_100msTsk",
                        (uint16_t       )Os_100ms_StckSize,
                        (void*          )NULL,
                        (UBaseType_t    )Os_100ms_Prio,
                        (TaskHandle_t*  )&os_100msTask_Handle);
	vTaskDelete(os_InitTask_Handle);
	taskEXIT_CRITICAL();
}

需要特别注意的是,前后要加上锁中断,然后在任务创建完成之后要加上一行vTaskDelete(os_InitTask_Handle);表示init_task再也不会被运行,只会执行一次。还有就是周期短的任务优先级要高于周期长的,初始化任务优先级最高。(优先级越高数字越大)

最后在Os_InitTsk函数完全执行出来之后,使能调度:vTaskStartScheduler();

至此,集成工作已经全部完成,开始调试:

NXP S32K144学习系列3----基于FreeRTOS进行多个ms级任务的创建_第7张图片

我在每个callback里加了个counter,执行一次counter++。跑个10s左右,暂停,看到100ms任务大约跑了100次,并且每个ms任务的counter与任务周期倍数关系一致,至此,任务创建集成完毕,我的代码后续会上传上来。

还有一个问题就是这个S32DS里的FreeRTOS包中xTaskCreateStatic函数是可以创建自由分配stack大小的任务,而我现在使用的xTaskCreate函数这部分工作是自动分配的,后续会试试另一种。

 

你可能感兴趣的:(S32K,arm开发,c语言,单片机,mcu)