内核移植
首先是下载内核源码
借用韦东山老师的一张图片对源码的介绍
有了内核源码之后就将源码添加到一个裸机工程下面,FreeRTOS 帮我们实现了 SysTick 的启动的配置:在 port.c 文件 中已经实现 vPortSetupTimerInterrupt()函数,并且 FreeRTOS 通用的 SysTick 中断服务函数 也实现了:在 port.c 文件中已经实现 xPortSysTickHandler()函数,所以移植的时候只需要我 们在stm32f10x_it.c 中就需要我们注释掉 PendSV_Handler()与 SVC_Handler()这两个函数 。最后选好头文件路径即可。
添加完成后,运行一下,没有错误即可。
任务调度
任务是一个无限循环且不带返回值的 C 函数。
创建任务函数
参数说明
任务函数样式
void ATaskFunction( void *pvParameters )
{
/* 对于不同的任务,局部变量放在任务的栈里,有各自的副本 */
int32_t lVariableExample = 0;
/* 任务函数通常实现为一个无限循环 */
for( ;; )
{
/* 任务的代码 */
}
/* 如果程序从循环中退出,一定要使用vTaskDelete删除自己
* NULL表示删除的是自己
*/
vTaskDelete( NULL );
/* 程序不会执行到这里, 如果执行到这里就出错了 */
}
移植内核的更改设置
将中断函数里面的中断服务函数, PendSV_Handler()与 SVC_Handler()函数,系统时钟函数Systick_handler屏蔽。
在FreeRTOSConfig.h配置文件中,更换为freertos的中断服务函数
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
#define xPortSysTickHandler SysTick_Handler
随便找个串口的文件,拷贝到当前工程中,用于调试任务切换打印信息
void task1(void *pvParameters)
{
while(1)
{
a=1;
int u=0;
USART_SendString(USART1,temp1);
for(u=0;u<1000;u++)
{
}};
}
void task2(void *pvParameters)
{
while(1)
{
a=0;
int u=0;
USART_SendString(USART1,temp2);
for(u=0;u<1000;u++)
{
}
};
}
int main(void)
{
Usart1_Init();
xTaskCreate(task1, "Task 1", 1000, NULL, 1, NULL);//创建任务
xTaskCreate(task2, "Task 2", 1000, NULL, 1, NULL);
vTaskStartScheduler();//开启任务调度器
return 0;
}
结果如下
到现在,我们以及基本完成内核的移植工作,用为任务已经在调度切换了,但很明显,因为这个串口中断的问题,我们不停的将字符串放到串口的移位寄存器中,导致任务的执行时间不均,注释掉串口,光用变量来观察任务的执行时间
很明显,任务的调度时间分配均匀,很合理。
删除任务
void vTaskDelete( TaskHandle_t xTaskToDelete );
参数是任务的句柄,NULL表示删除自己
tick
两次中断之间的时间称为时间片
时间片的长度由configTICK_RATE_HZ 这个宏决定;
任务优先级的设置
获取优先级函数:
#if ( INCLUDE_uxTaskPriorityGet == 1 )
UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask )
{
TCB_t *pxTCB;
UBaseType_t uxReturn;
taskENTER_CRITICAL();
{
/* If null is passed in here then it is the priority of the that
called uxTaskPriorityGet() that is being queried. */
pxTCB = prvGetTCBFromHandle( xTask );
uxReturn = pxTCB->uxPriority;
}
taskEXIT_CRITICAL();
return uxReturn;
}
#endif /* INCLUDE_uxTaskPriorityGet */
在内核中INCLUDE_uxTaskPriorityGet已经被设置为1了,函数的参数是任务的句柄,NULL表示自己。函数的返回值是任务的优先级
2.设置任务优先级函数
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority ) PRIVILEGED_FUNCTION;
参数1:任务的句柄,NULL表示自己
参数2:设置的优先级
无返回值
任务的状态
运行态:正在运行的任务,
就绪态:即将运行的任务,挂在就绪链表里的函数
阻塞态:被迫停止:等待某个事件发生或者延时,不占用cpu资源。包含任务被挂起、任务被延时、任务 正在等待信号量、读写队列或者等待读写事件等。
挂起态:主动停止:占用cpu资源
延时函数
void vTaskDelay( const TickType_t xTicksToDelay ); /* xTicksToDelay: 等待多少给Tick */
BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement );
/* pxPreviousWakeTime: 上一次被唤醒的时间
* xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement)
* 单位都是Tick Count
*/
空闲任务
一个良好的程序,它的任务都是事件驱动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。
空闲任务优先级为0:它不能阻碍用户任务运行
空闲任务要么处于就绪态,要么处于运行态,永远不会阻塞
任务调度
通过配置宏来控制调度方法,支持的话就将对应的宏配置为1
支持抢占吗:configUSE_PREEMPTION
支持抢占时,高优先级任务在就绪链表中就绪时,立马就可以执行;不支持抢占时,只能等当前运行的任务主动释放cpu控制权,其它任务不能打断当前运行的任务,阻塞时间到了,高优先级任务准备好了都不可以打断。
在支持抢占时,是否支持任务的轮转执行:configUSE_TIME_SLICING
支持轮转时,同级任务轮转获得cpu的使用权
在支持抢占和支持任务的轮转执行时,是否支持空闲任务的让位:configIDLE_SHOULD_YIELD
支持时,空闲任务运行时间很短,就算是遇到同级任务也会让位;不支持时,就和普通任务一样。
同步与互斥
.任务通知
.事件组:
事件组可以简单的认为是一个整数,其中的高8位留给内核使用,数的长度由宏configUSE_16_BIT_TICKS决定,如果该宏设置为1,则表明事件组的长度为16位,设置为0,则表明事件组的长度为32,内核中初始设置为0,就是留了24位的用于事件组。
#define configUSE_16_BIT_TICKS 0
作用:
事件组用它的每一位来表明一件事件是否发生,事件发生这对应的一位就被设置为1,一个或多个任务、ISR都可以去写这些位;一个或多个任务、ISR都可以去读这些位,可以等待某一位或多位置1.
使用:
一.事件组的创建:
//动态创建
//无参数
//返回值是事件组的句柄
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
EventGroupHandle_t xEventGroupCreate( void )
{
EventGroup_t *pxEventBits;//创建一个EventGroup_t类型结构体指针
/* Allocate the event group. */
pxEventBits = ( EventGroup_t * ) pvPortMalloc( sizeof( EventGroup_t ) );/*分配结构的内存*/
if( pxEventBits != NULL )
{
pxEventBits->uxEventBits = 0;//将事件组设置为0
vListInitialise( &( pxEventBits->xTasksWaitingForBits ) );
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
/* Both static and dynamic allocation can be used, so note this
event group was allocated statically in case the event group is
later deleted. */
pxEventBits->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
traceEVENT_GROUP_CREATE( pxEventBits );
}
else
{
traceEVENT_GROUP_CREATE_FAILED();
}
return ( EventGroupHandle_t ) pxEventBits;//返回指针
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
//静态创建
/* 创建一个事件组,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticEventGroup_t结构体,并传入它的指针
* 返回值: 返回句柄,非NULL表示成功
*/
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer )
{
EventGroup_t *pxEventBits;
/* A StaticEventGroup_t object must be provided. */
configASSERT( pxEventGroupBuffer );
/* The user has provided a statically allocated event group - use it. */
pxEventBits = ( EventGroup_t * ) pxEventGroupBuffer; /*lint !e740 EventGroup_t and StaticEventGroup_t are guaranteed to have the same size and alignment requirement - checked by configASSERT(). */
if( pxEventBits != NULL )
{
pxEventBits->uxEventBits = 0;
vListInitialise( &( pxEventBits->xTasksWaitingForBits ) );
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
{
/* Both static and dynamic allocation can be used, so note that
this event group was created statically in case the event group
is later deleted. */
pxEventBits->ucStaticallyAllocated = pdTRUE;
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
traceEVENT_GROUP_CREATE( pxEventBits );
}
else
{
traceEVENT_GROUP_CREATE_FAILED();
}
return ( EventGroupHandle_t ) pxEventBits;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
删除事件
//传入要删除任务的句柄
void vEventGroupDelete(EventGroupHandle_t xEventGroup)
二.创建完事件后,就是设置位
//xEventGroup: 任务句柄,表示哪个事件组
/*uxBitsToSet:一个无符号32位常量,类型在源码定义如下:typedef TickType_t EventBits_t;
typedef uint32_t TickType_t;*/
//返回值:返回原来的事件值(无意义)
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet )
//在中断里的使用
//xEventGroup:任务句柄,表示哪个事件组
//uxBitsToSet:一个无符号32位常量,如上
//pxHigherPriorityTaskWoken:有没有导致更高优先级的任务进入就绪态? pdTRUE:有, pdFALSE:没有
//返回值:pdPASS:成功, pdFALSE:失败
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken )
三.能设置事件组的位后,就是具体使用事件组,等待某些事件的产生
//xEventGroup:事件组句柄
//uxBitsToWaitFor:等待的位,例如:等待第一位就设置0x1,等待1,2位就设置0x3
//xClearOnExit:清除的位,pdTRUE: 清除uxBitsToWaitFor指定的位,pdFALSE: 不清除
//xWaitForAllBits:测试方法,pdTRUE: 等待的位,全部为1;pdFALSE: 等待的位,某一个为1即可
/*xTicksToWait:事件未发生,任务阻塞的时间。事件发生,返回返回的是"非阻塞条件成立"时的事件值;超时,返回超时时刻的事件值,它可设置为portMAX_DELAY:一定等到成功才返回;可以设置为期望的Tick Count,一般用pdMS_TO_TICKS()把ms转换为Tick Count*/
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait )
事件组实验
#include "FreeRTOS.h"
#include "task.h"
#include "list.h"
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Led.h"
#include "Usart.h"
#include "event_groups.h"
EventGroupHandle_t event_handle;//事件组句柄
volatile int a=0;
void task1(void *pvParameters)
{
while(1)
{
//三个tick后,将事件组0位设置为1
vTaskDelay( pdMS_TO_TICKS( 50UL ));
xEventGroupSetBits(event_handle, 0x01);
}
}
void task2(void *pvParameters)
{
while(1)
{a=1;
//等待事件组的第0位置1
xEventGroupWaitBits( event_handle,0x1,pdTRUE,pdTRUE, portMAX_DELAY );
a=0;
vTaskDelay( pdMS_TO_TICKS( 20UL ));
}
}
int main(void)
{
event_handle=xEventGroupCreate();
xTaskCreate(task1,"task1",500,NULL,2,NULL);
xTaskCreate(task2,"task2",500,NULL,2,NULL);
vTaskStartScheduler();
while(1){}//无用,运行不到这里来
return 0;
}
结果
分析:启动任务调度器之后,就绪链表指向任务2,所以任务二先执行;a=1,但此时,任务在死等事件组的第1位置1,进入阻塞;任务1就运行,任务1延迟5再将事件组置位,置位后任务2接着运行,将a设置为0后,又阻塞2,任务1执行,阻塞2时,切换到任务2,将a设置为1,以此循环。