STM32F429-DISCO 学习之FreeRTOS写第一个程序

/ 0评 / 0

之前虽然也有移植FreeRTOS也写过一个程序 ,当那是复制例程的,并没什么用,我们得开始写个程序,点灯开始,Blink板载的两个LED,一个是PG13,一个是PG14.他们看起来要"同时"翻转.比如LED应该是这么做的.

static void LED1_Task( void )
{
    uint32_t i = 0;
    for( ;; )
    {
        GPIO_ToggleBits(GPIOG, GPIO_Pin_13);
        for(i = 0; i < 0x5FFFFF; i++)
        {
        }
    }
}

他们在死循环里面,永远没法执行结束.两个LED程序都是这样的.他们要靠调度器,那么我们一开始就要接触一个非常复杂的函数xTaskCreate!需要传入的定义就这么这么长.
QQ截圖20151119164308
其中pvTaskCode任务就是一个永不退出的C程序.接着是pcName,其实就是个象征性的名字,usStackDepth是要分配的栈空间,通常,我们都不知道要多少的,通常是指定一个空间,然后实现功能后慢慢裁剪.除非你是土豪,太多都是会浪费的. pvParameters是给任务的参数,任务的函数是可以有参数的,uxPriority指定任务的优先级,数字越高,自然优先级就越高. pxCreatedTask叫传出句柄,我还不懂,直接写NULL来忽略就可以了.当然这个函数也是有返回的, pdTRUE就是申请OK,否则失败,失败还要查原因哦.创建两个任务,他们优先级都是一样的,就函数名不一样.然后开始执行调度.

    xTaskCreate( LED1_Task, ( const char * ) "LED1_Task", 1000, NULL, 1, NULL );
    xTaskCreate( LED2_Task, ( const char * ) "LED2_Task", 1000, NULL, 1, NULL );
    vTaskStartScheduler();

既然他是能够传入参数的,那么我们的LED也得改改闪烁函数.虽然我们没用到那个参数,但这是一个标准嘛.

static void LED1_Task( void *pvParameters )
{
    uint32_t i = 0;
    for( ;; )
    {
        GPIO_ToggleBits(GPIOG, GPIO_Pin_13);
        for(i = 0; i < 0x5FFFFF; i++)
        {
        }
    }
}

最后发现LED就开始同时闪呢,其实.. 他们是独立的两个任务.他们跑的方式如图示.
QQ截圖20151119164308
但是也可以通过一个任务,来开启另一个任务.

static void LED1_Task( void *pvParameters )
{
    uint32_t i = 0;
    xTaskCreate( LED2_Task, ( const char * ) "LED2_Task", 1000, NULL, 1, NULL );
    for( ;; )
    {
        GPIO_ToggleBits(GPIOG, GPIO_Pin_13);
        for(i = 0; i < 0x5FFFFF; i++)
        {
        }
    }
}

这样实验效果是一样的,我们一开始创建的两个任务,优先级都是一样的,我们可以改变优先级,使得LED2的优先级比较高.

    xTaskCreate( LED1_Task, ( const char * ) "LED1_Task", 1000, NULL, 1, NULL );
    xTaskCreate( LED2_Task, ( const char * ) "LED2_Task", 1000, NULL, 2, NULL );
    vTaskStartScheduler();

发现结果是,只有LED2在做了,因为,LED1优先级太低了,根本没他什么事,他自己就没法执行了.再偷一官方图来看就是如下:
QQ截圖20151119164308
用空循环导致调度器以为你一直很忙,但是这个空循环又没什么用.还把任务堵着了,所以可以用操作系统的延迟功能,vTaskDelay,通常结合常数portTICK_RATE_MS使用,vTaskDelay表示延迟多少个时钟滴答,结合这个常数可以做到延迟多少毫秒这样的效果.保持当前优先级,修改LED的程序.

static void LED1_Task( void *pvParameters )
{
    for( ;; )
    {
        GPIO_ToggleBits(GPIOG, GPIO_Pin_13);
        vTaskDelay( 500 / portTICK_RATE_MS );
    }
}
static void LED2_Task( void *pvParameters )
{
    for( ;; )
    {
        GPIO_ToggleBits(GPIOG, GPIO_Pin_14);
        vTaskDelay( 500 / portTICK_RATE_MS );
    }
}

因为调用vTaskDelay时候,这个LED2任务,就相当于短暂的消失了,所以LED1就可以被执行了.在FreeRTOS文档叫阻塞.另外有 vTaskDelayUntil函数.vTaskDelayUntil()类似于 vTaskDelay(), API 函数 vTaskDelayUntil()可以用于实现一个固定执行周期的需求(当你需要让你的任务以固定频率周期性执行的时 候),由于调用此函数的任务解除阻塞的时间是绝对时刻,比起相对于调用时刻的相对 时间更精确(即比调用 vTaskDelay()可以实现更精确的周期性).原型如下:

void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement );

他们可以实现同样的方法,这个xLastWakeTime(即pxPreviousWakeTime)会被不断更新.

static void LED1_Task( void *pvParameters )
{
    portTickType xLastWakeTime;
    xLastWakeTime = xTaskGetTickCount();
    for( ;; )
    {
        GPIO_ToggleBits(GPIOG, GPIO_Pin_13);
        vTaskDelayUntil( &xLastWakeTime, ( 500 / portTICK_RATE_MS ) );
    }
}
static void LED2_Task( void *pvParameters )
{
    portTickType xLastWakeTime;
    xLastWakeTime = xTaskGetTickCount();
    for( ;; )
    {
        GPIO_ToggleBits(GPIOG, GPIO_Pin_14);
        vTaskDelayUntil( &xLastWakeTime, ( 500 / portTICK_RATE_MS ) );
    }
}

这个是周期性的,原因是,当vTaskDelayUntil的执行完成时,任务休眠,到需要的时刻,就会变成最高优先级,执行然后..然后... 当然,在我们的系统里面,应该还有一个叫空闲函数的东西,这个函数可以通过Hook调用外部函数,这个函数的目的,是可以统计如CPU负载等等.这样要用的话应该把configUSE_IDLE_HOOK设置成1,在我们的配置文件内修改即可.再实现一个函数:

unsigned long ulIdleCycleCount = 0UL;
void vApplicationIdleHook( void )
{
	ulIdleCycleCount++;
}

可以计算空闲量.如果我们的任务是空for死循环,是浪费CPU的,没意义,用系统的延迟,这个空闲加数才会有效.今天先说到这里,下节再来了.

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注