码农学freeRTOS

来源:互联网 发布:微企源码 编辑:程序博客网 时间:2024/06/12 21:39

          这是我第一次写博客,为啥想起写这篇文章呢。起因是这样的,我大部分时间的单片机开发是用裸机来实现。在做项目的时候,写过android的驱动以及app,以及一些常用的电脑辅助工具。他们都具备操作系统,操作系统让程序变得简单,也比较容易使用的。前两天,突然有人问我会freeRTOS么。我说了解过,应该很简单。那么今天我就以一个编程老鸟的身份带领大家玩玩freeRTOS。

          1:为啥用操作系统,啥时候用。

          小伙伴们一看到这个论题的时候,可能心里都有这种答案。有人说你作为编程老鸟,你咋看待这个问题。下面我就给大家说说。如果系统任务较少,大部分时间是做数据处理。而且处理速度要求比较快,又经常接收发送数据,数据量较大,这种情况下,不推荐用操作系统。这种情况裸机处理较为灵活,因为单片机本身没啥多余的资源给系统调度,维护RTOS的运行。为了不丢数据,时间需要非常紧迫。常见的比如将can等信号通过uart转发给其他处理单元。Uart的带宽比can小很多,只是数据量是突发的,我们转发的时候把突发数据缓存慢慢的发送到其他单元。这个时候为了保证带宽,操作系统就显得多余。不用操作系统基本除了中断和收发数据的时候需要做保护数据机制外,没有额外开销,中断执行力较强,延时较低,采用RTOS必然会多出任务切换等更多的中断保护机制,中断延时较高,可能会导致数据的丢失。另外一种场景是这样的。比如一个通信器件要求你给它弄一个上电复位,先上电,然后300ms电源稳定后,拉下复位100ms,100ms后拉高复位脚,然后再通信。做这个过程可能发生在我们系统运行的任何时刻,我们为了不让这个时间耽搁我们完成其他任务,我们必须使用状态机机制来实现。在等待的时候,直接跳出该任务,去执行其他不等待的任务。状态机无疑让我们多写了代码。友好性变低,效率当然还是极高的。但如果有操作系统,我们调用xTaskDelay()函数的时候,他会去完成其他任务。就避免了像大学里学的for来延时的尴尬。写法又简单,又可以不耽误其他任务,这就是操作系统的好处。这一过程只是增加点ram和上下文切换时间消耗,对现在处理器来说这点资源不算啥了。

          2: 着手freeRTOS

         平常我用stm32裸机比较多,身边到处是stm32的产品,学习freeRTOS就不用买开发板了,随便找了一块板,上面有2颗灯,芯片是stm32f103c8的,不错,有灯,freeRTOS的简单任务演示就可以开始了。硬件平台有了,如何动工呢,先去找一个FreeRTOS下来。当然这里去stm32官网下载一个开发板软件包。在Utilities文件夹下有个Third_Party文件夹,里面放了第三方软件,有lwip网络协议,fat文件系统,还有我们需要的FreeRTOS_v6.1.0。进去后后看到4个c文件,然后老鸟花了4个小时看完了这点代码。

list.c主要是一些链表的操作,queue.c主要是一些邮箱操作。用来发送数据啥的,类似于裸机自己实现的fifo序列,不过他是分块的而已。task.c主要是任务调度的一些实现,croutine.c一看也是任务调度,和task差不多,不过一看就很简单,任务调度基本就是注册函数指针,调用函数指针来实现,和我们状态机差不多,所以这个调度方式不是我们需要的,这个文件还在代码里,主要是因为他消耗更低,代码保护措施更少,更像裸机,当然这个调度方式我们后面会屏蔽。

           3:移植freeRTOS

           注意我们这里用的是自己的平台,不是开发板,有以前的产品代码,而且老鸟的个人习惯是不用库函数,网上下载别人移植好的,基本都是库函数操作。这里我们自己实现移植。第一步我们把4个c文件拷贝到以前的工程文件夹里面。这4个c文件说明白了就是核心,同时把include里面的头文件也复制过来。复制过来之后,复制来的代码与我们本身的代码是没有交集。在读4个c的时候发现task的切换实际上用了portable里面的接口。我使用的keil4的编译器。所以就把MDK-ARM/ARM_CM3/下的两个文件拷贝进工程。这两个文件主要是和具体mcu相关的,这里文件是符合stm32的,为啥呢,因为老鸟在stm32官网下的嘛。

              4:编译freeRTOS

            在工程里加入拷贝来的额外代码(上面提到的操作系统文件)。加入后如下图,这里只是新加入部分,以前项目的文件没截图,和移植关系不大:


开始编译,报错,发现没有freeRTOS配置文件,文件包里找也没找到,就自己新建一个配置FreeRTOSConfig.h文件。网上随便找下配置粘贴进去即可。里面的参数要改改,老鸟平常用stm32,根据实际需求cpu只跑了32M,这样功耗低点,稳定点也完全够用,所以freeRTOS里面的configCPU_CLOCK_HZ由72M改为我以前用的32M。configTICK_RATE_HZ这个时钟滴答,我下载下来里面是250,其实我写裸机习惯5ms,改为200,用回5ms。改后如下:

#ifndef FREERTOS_CONFIG_H  
#define FREERTOS_CONFIG_H  
   
/*Here is a good place to include header files that are required across 
yourapplication. */  
//#include "something.h"  
   
#define configUSE_PREEMPTION                    1  
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0  
#define configUSE_TICKLESS_IDLE                 0  
#define configCPU_CLOCK_HZ                      32000000  
#define configTICK_RATE_HZ                      200  
#define configMAX_PRIORITIES                    5  
#define configMINIMAL_STACK_SIZE                128  
#define configTOTAL_HEAP_SIZE                   10240  
#define configMAX_TASK_NAME_LEN                 16  
#define configUSE_16_BIT_TICKS                  0  
#define configIDLE_SHOULD_YIELD                 1  
#define configUSE_TASK_NOTIFICATIONS            1  
#define configUSE_MUTEXES                       0  
#define configUSE_RECURSIVE_MUTEXES             0  
#define configUSE_COUNTING_SEMAPHORES           0  
#define configUSE_ALTERNATIVE_API               0/* Deprecated! */  
#define configQUEUE_REGISTRY_SIZE               10  
#define configUSE_QUEUE_SETS                    0  
#define configUSE_TIME_SLICING                  0  
#define configUSE_NEWLIB_REENTRANT              0  
#define configENABLE_BACKWARD_COMPATIBILITY     0  
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5  
   
/*Hook function related definitions. */  
#define configUSE_IDLE_HOOK                     0  
#define configUSE_TICK_HOOK                     0  
#define configCHECK_FOR_STACK_OVERFLOW          0  
#define configUSE_MALLOC_FAILED_HOOK            0  
   
/*Run time and task stats gathering related definitions. */  
#define configGENERATE_RUN_TIME_STATS           0  
#define configUSE_TRACE_FACILITY                0  
#define configUSE_STATS_FORMATTING_FUNCTIONS    0  
   
/*Co-routine related definitions. */  
#define configUSE_CO_ROUTINES                   0  
#define configMAX_CO_ROUTINE_PRIORITIES         1  
   
/*Software timer related definitions. */  
#define configUSE_TIMERS                        1  
#define configTIMER_TASK_PRIORITY               3  
#define configTIMER_QUEUE_LENGTH                10  
#define configTIMER_TASK_STACK_DEPTH            configMINIMAL_STACK_SIZE  
   
/*Interrupt nesting behaviour configuration. */  
#define configKERNEL_INTERRUPT_PRIORITY        255 
#define configMAX_SYSCALL_INTERRUPT_PRIORITY   191  
#define configMAX_API_CALL_INTERRUPT_PRIORITY  15 
   
/*Define to trap errors during development. */  
//#define configASSERT( ( x ) )     if( ( x ) == 0) vAssertCalled( __FILE__, __LINE__ ) 
   
/*FreeRTOS MPU specific definitions. */  
#define configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS 0  
   
/*Optional functions - most linkers will remove unused functions anyway. */  
#define INCLUDE_vTaskPrioritySet                1  
#define INCLUDE_uxTaskPriorityGet               1  
#define INCLUDE_vTaskDelete                     0  
#define INCLUDE_vTaskSuspend                    1  
#define INCLUDE_xResumeFromISR                  1  
#define INCLUDE_vTaskDelayUntil                 0  
#define INCLUDE_vTaskDelay                      1  
#define INCLUDE_xTaskGetSchedulerState          0  
#define INCLUDE_xTaskGetCurrentTaskHandle       0  
#define INCLUDE_uxTaskGetStackHighWaterMark     0  
#define INCLUDE_xTaskGetIdleTaskHandle          0  
#define INCLUDE_xTimerGetTimerDaemonTaskHandle  0  
#define INCLUDE_pcTaskGetTaskName               0  
#define INCLUDE_eTaskGetState                   0  
#define INCLUDE_xEventGroupSetBitFromISR        1  
#define INCLUDE_xTimerPendFunctionCall          0  
   
/* Aheader file that defines trace macro can be included here. */  
   
#endif/* FREERTOS_CONFIG_H*/  

然后编译,这时候编译妥妥的,没错了。

       5:写freeRTOS任务测试

         用了自己的板子,初始化用以前的代码,初始化完成后,在我们进入while这个死循环前加入代码即可,加入两个函数,第一个函数创建任务,第二个函数启动任务。创建函数前,我们先爽一把,写个任务,代码如下:

void test_handler(void)
{
while(1)
{
GREEN_LED_ON;
vTaskDelay(100);
GREEN_LED_OFF;
vTaskDelay(100);
}
}

vTaskDelay(100)延时是500ms哈,因为老鸟时钟滴答是5ms,100*5=500ms不会算的回去复习数学。

然后我们创建个任务

xTaskCreate((pdTASK_CODE)test_handler,"test_handler",configMINIMAL_STACK_SIZE,( void * ) NULL,4,( xTaskHandle * ) NULL);

上面参数啥意思,读代码就发现了哈,

第一个是就是函数名字,

第二个就是你给你的任务起的名字(别名),用来打印任务用,

第三个参数堆栈大小,我们这里堆栈没啥要求,最小的128就够了,

第四个参数用来给任务传参数,我们这里也不需要,

第五个是任务的优先级,这个我们用4即可,看了代码,同一优先级可以放多个任务,他们会按次序执行,没有所谓的任务个数限定,任务会消耗资源,尽量少弄任务。

第六个参数,看了下源码,是返回的句柄,可以用来删除任务,这里老鸟不用,给他个空。顺便说说,删除任务的功能在配置里我都去掉了。 INCLUDE_vTaskDelete这项,不用去掉可以减少代码。

启动这个任务

vTaskStartScheduler();

         6:调试freeRTOS

          经过上面的折腾,灯没闪哈,为啥呢,看源代码出啥问题了。看了下创建任务没问题,那么问题肯定是启动任务出问题了。通过查看代码发现,任务启动函数发出了个svc中断信号,中断函数实现任务切换。原因找到了,中断没调用这个函数。按照裸机加中断的方法,添加svc中断,调用这个函数。

__asm void vPortSVCHandler( void )
{
PRESERVE8


ldr r3, =pxCurrentTCB/* Restore the context. */
ldr r1, [r3] /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
ldr r0, [r1] /* The first item in pxCurrentTCB is the task top of stack. */
ldmia r0!, {r4-r11}/* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
msr psp, r0 /* Restore the task stack pointer. */
mov r0, #0
msr basepri, r0
orr r14, #0xd
bx r14
}

中断代码添加如下:

extern void vPortSVCHandler( void );
void SVC_Handler(void)
{
vPortSVCHandler();
}

添加的方法和裸机添加中断一样,然后看了下时钟滴答,以及挂起中断也需要添加

extern void xPortSysTickHandler( void );
void SysTick_Handler(void)
{
xPortSysTickHandler();
}

extern void xPortPendSVHandler( void );
void PendSV_Handler(void)
{
xPortPendSVHandler();
}

添加好后,灯就500ms一亮一灭了。

搞定这个系统耗时不到1天,当然老鸟平常的理论看得也比较多,对菜鸟的建议是多看理论,多积累,遇到问题解决都会比较快的。在这个基础上多加一个任务测试下,结果也是ok的,两个灯都亮了。代码如下:

void test_handler(void)
{
while(1)
{
GREEN_LED_ON;
vTaskDelay(100);
GREEN_LED_OFF;
vTaskDelay(100);
}
}


void test_handler1(void)
{
while(1)
{
YELLOW_LED_ON;
vTaskDelay(100);
YELLOW_LED_OFF;
vTaskDelay(100);
}
}

xTaskCreate((pdTASK_CODE)test_handler,"test_handler",configMINIMAL_STACK_SIZE,( void * ) NULL,4,( xTaskHandle * ) NULL);
xTaskCreate((pdTASK_CODE)test_handler1,"test_handler1",configMINIMAL_STACK_SIZE,( void * ) NULL,4,( xTaskHandle * ) NULL);
vTaskStartScheduler();

以上是使用以前的裸机板子,以前代码减少了配置板子的时间,不用看硬件原理图啥的,直接把系统核心加上去。这样的实验是有意义的,以后裸机代码就可以直接通过加入系统核心代码了,不用把网上的低效率库函数版本改回寄存器版本,改比较费时间。老鸟上官网的时候,发现stm32 有很多汇编资料,寄存器资料,有兴趣的可以自己去阅读。另外说到库函数版本。给大家说个工具,方便对stm32不熟的,但是会其他单片机的工程师通过半天就变成高手。STM32CubeMX这个工具。比较简单,主要是用鼠标就可以设置stm32的硬件底层,生成基于库函数配置c文件。这样工程师就可以快速到达应用层,加速开发,让stm32成为最简单好用的单片机。

原创粉丝点击