打印
[STM32F1]

【免费开源】STM32F107VCT6金龙107开发板 Freertos 实验

[复制链接]
2649|8
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 szopenmcu 于 2015-5-9 11:36 编辑

实验一、任务调度

本节我们主要介绍的是Freertos的任务创建,及任务调度。
1、任务创建
对于操作系统的学习,创建任务是首要工作,freertos对任务的数量没有限制,而且同一个优先级可以有多个任务。创建任务使用FreeRTOSAPI函数xTaskCreate(),其函数原型及参数描述如下:
portBASE_TYPE xTaskCreate( pdTASK_CODE pvTaskCode,
const signed portCHAR * const pcName,
unsigned portSHORT usStackDepth,
void *pvParameters,
unsigned portBASE_TYPE uxPriority,
xTaskHandle *pxCreatedTask );
pvTaskCode          任务函数名,实现常通常是一个死循环。
pcName                  具有描述性的任务名。(最大长度通过由config_MAX_TASK_NAME_LEN来定义,大于最大长度会被自动截断)
usStackDepth          内核为任务分配堆栈空间大小usStackDepth字。(应用程序通过定义常量configMINIMAL_STACK_SIZE来决定空闲任务任用的栈空间大小)
pvParameters          任务函数接受一个指向void的指针(void*)pvParameters的值即是传递到任务中的值。
uxPriority                  指定任务执行的优先级。优先级的取值范围可以从最低优先级0到最高优先级(configMAX_PRIORITIES1),数值越小优先级越高。
pxCreatedTask  pxCreatedTask用于传出任务的句柄。这个句柄将在API调用中对该创建出来的任务进行引用,比如改变任务优先级,或者删除任务。如果应用程序中不会用到这个任务的句柄,则pxCreatedTask可以被设为NULL
返回值                  有两个可能的返回值:
1pdTRUE 表明任务创建成功。
2errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 由于内存堆空间不足,FreeRTOS无法分配足够的空间来保存任务结构数据和任务栈,因此无法创建任务。
2、任务调度方法
FreeRTOS支持多个任务具有相同的优先级,因此,当它被配置为可抢占内核时,调度算法既支持基于优先级的调度,也支持时间片轮流调度。任何时候调度器运行时它都选择处于就绪状态下的优先级最高的那个任务;如果有多个任务处于同一优先级,则FreeRTOS每个时钟节拍的中断服务程序中,将对这些任务应用换调度算法,轮流执行这些任务。
优先级的调度执行流程
时间片的调度执行流程
3、实验分析
本次实验创建两个优先级不同的任务,采用优先级调度,两个任务分别控制两个LED灯的点亮与熄灭,向串口打印任务名后延时挂起。其源代码如下:
/*****主函数*****/
int main(void)
{
        USART_Configuration();          //串口初始化
        LED_Configuration();
        xTaskCreate( vLED1Task, ( signed portCHAR * ) "vLED1Task", configMINIMAL_STACK_SIZE, NULL, vLED1Task_PRIORITY, NULL );
//创建一个让LED1闪烁的任务        
        xTaskCreate( vLED2Task, ( signed portCHAR * ) "vLED2Task", configMINIMAL_STACK_SIZE, NULL, vLED2Task_PRIORITY, NULL );
//创建一个让LED2闪烁的任务
        vTaskStartScheduler();        //开始任务调度
        while(1);
}

//LED1闪烁任务
void vLED1Task( void *pvParameters )
{
        while(1)
{
                GPIO_ResetBits(LED1_GPIO,LED1_GPIO_Pin);   //点亮LED1
                vTaskDelay(3000);
                GPIO_SetBits(LED1_GPIO,LED1_GPIO_Pin);           //熄灭LED1
                printf("\r\nvLED1Task");
                vTaskDelay(3000);
        }
}
//LED2闪烁任务
void vLED2Task( void *pvParameters )
{
        while(1)
{
                GPIO_ResetBits(LED2_GPIO,LED2_GPIO_Pin);   //点亮LED2
                vTaskDelay(1000);
                GPIO_SetBits(LED2_GPIO,LED2_GPIO_Pin);           //熄灭LED2
                printf("\r\nvLED2Task");
                vTaskDelay(1000);
        }
}
如下图是串口打印的数据,通过修改vTaskDelay()的延时值,可以看到打印速度的变化及两个字符串打印出来的比例。

沙发
szopenmcu|  楼主 | 2015-5-5 15:43 | 只看该作者
实验二、消息队列



本节我们主要介绍的是Freertos的通信方式--消息队列
1、队列介绍
FreeRTOS中所有的通信与同步机制都是基于队列实现的,队列可以保存有限个具有确定长度的数据单元。队列可以保存的最大单元数目被称为队列的“深度”。在队列创建时需要设定其深度和每个单元的大小。通常情况下,队列被作为FIFO(先进先出)使用,即数据由队列尾写入,从队列首读出。当然,由队列首写入也是可能的。往队列写入数据是通过字节拷贝把数据复制存储到队列中;从队列读出数据使得把队列中的数据拷贝删除。下图展现了队列的写入与读出过程,以及读写操作对队列中数据的影响
2、队列操作
Freertos提供了一系列函数对队列进行创建、接收和发送等。
1)创建队列xQueueCreate()
xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength,
unsigned portBASE_TYPE uxItemSize );
uxQueueLength          队列能够存储的最大单元数目,即队列深度。
uxItemSize                  队列中数据单元的长度,以字节为单位。
返回值                                NULL表示没有足够的堆空间分配给队列而导致创建失败。
NULL值表示队列创建成功。此返回值应当保存下来,以作为操作此队列的句柄。
2)将数据发送到队列尾xQueueSendToBack()与头xQueueSendToFront()
portBASE_TYPE xQueueSendToFront( xQueueHandle xQueue,
const void * pvItemToQueue,
portTickType xTicksToWait );
portBASE_TYPE xQueueSendToBack( xQueueHandle xQueue,
const void * pvItemToQueue,
portTickType xTicksToWait );
xQueue                          目标队列的句柄。
pvItemToQueue          发送数据的指针。
xTicksToWait                  阻塞超时时间。如果在发送时队列已满,这个时间即是任务处于阻塞态等待队列空间有效的最长等待时间。
返回值                          有两个可能的返回值:
1pdPASS                         数据被成功发送到队列中。
2errQUEUE_FULL 如果由于队列已满而无法将数据写入,则将返回errQUEUE_FULL
3)从队列中接收数据,接收后删除xQueueReceive()与不删除xQueuePeek()
portBASE_TYPE xQueueReceive( xQueueHandle xQueue,
const void * pvBuffer,
portTickType xTicksToWait );
portBASE_TYPE xQueuePeek( xQueueHandle xQueue,
const void * pvBuffer,
portTickType xTicksToWait );
xQueue                  被读队列的句柄。
pvBuffer                  接收缓存指针。
xTicksToWait          阻塞超时时间。
返回值                  有两个可能的返回值:
1.pdPASS                 成功地从队列中读到数据。
2.errQUEUE_FULL 如果在读取时由于队列已空而没有读到任何数据,则将返回errQUEUE_FULL
4)查询队列中有效数据的个数uxQueueMessagesWaiting()
unsigned portBASE_TYPE uxQueueMessagesWaiting( xQueueHandle xQueue );
xQueue  被查询队列的句柄。的返回值。
返回值  当前队列中保存的数据单元个数。返回0表明队列为空。
3、实验分析
实验创建两个任务,一个用于向队列发送数据,另一个接收数据。发送任务不停向队列发送数据Send,当队列满后,打印“不能发送数据到消息队列”。接收任务不停的查询队列中有多少个数据,并打印出来,如果有数据就读取出来,并使用串口打印出接收到的数据。其源代码如下:
/*****主函数*****/
int main(void)
{
USART_Configuration();          //串口初始化
        LED_Configuration();
        xQueue = xQueueCreate( 5, sizeof( long ) );         //创建队列
        //创建一个发送数据任务
        xTaskCreate( vSendTask, ( signed portCHAR * ) "LED", configMINIMAL_STACK_SIZE, NULL, vSendTask_PRIORITY, NULL );
        //创建一个接收数据任务
        xTaskCreate( vReceiveTask, ( signed portCHAR * ) "LED", configMINIMAL_STACK_SIZE, NULL, vReceiveTask_PRIORITY, NULL );
        vTaskStartScheduler();        //开始任务调度
        while(1);
}
//队列发送任务
void vSendTask( void *pvParameters )
{
        long Send=1;
        portBASE_TYPE xStatus;
       
        while(1)
{
                xStatus = xQueueSendToBack(xQueue, &Send,0);
                if(xStatus != pdPASS)
                {
                        printf("\r\n不能发送数据到消息队列\r\n");
                }
                else Send++;               
                vTaskDelay(500);
        }
}
//队列接收任务
void vReceiveTask( void *pvParameters )
{
        long Received;
        portBASE_TYPE xStatus;
        portBASE_TYPE num;
        const portTickType xTicksToWait = 100 / portTICK_RATE_MS;
       
        while(1){
                if( (num=uxQueueMessagesWaiting( xQueue )) != 0 )
                {
                        printf( "\r\n消息队列中有%d个数据 ",num );
                }
                else
                        printf( "\r\n消息队列为空");
                xStatus = xQueueReceive( xQueue, &Received, xTicksToWait );
                if( xStatus == pdPASS )
                {
                        printf( "\r\n读出的数据为%d", Received );         //打印数据
                }
                else
                {
                        /* 等待100ms也没有收到任何数据。
                                必然存在错误,因为发送任务在不停地往队列中写入数据*/
                        printf( "不能从消息队列中读取数据\r\n" );
                }                
                vTaskDelay(700);
        }
}
如下图是串口打印的数据:

使用特权

评论回复
板凳
mmuuss586| | 2015-5-5 22:16 | 只看该作者

开源免费的系统;

使用特权

评论回复
地板
szopenmcu|  楼主 | 2015-5-6 10:40 | 只看该作者
mmuuss586 发表于 2015-5-5 22:16
开源免费的系统;

实验三、二值信号量

本节我们主要介绍的是Freertos的二值信号量
1、信号量介绍
信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用。如果大于0则资源可以请求,等于0则无资源可用,进程会进入睡眠状态直至资源可用。
Freertos提供了两种信号量:二值信号量和计数信号量。
二值信号量只有两种状态即01,当任务调用PTake-信号量减1)操作判断信号量是否为1,若不为1则获取失败进入阻塞,等待信号量为1,当其他任务或中断进行VGive-信号量加1)操作,此时信号量为1,使得阻塞的任务解除阻塞P操作成功对信号量减1后为0。如下图是实现任务与中断同步:
在中断以相对较慢的频率发生的情况下,上面描述的流程是足够而完美的。如果在延迟处理任务完成上一个中断事件的处理之前,新的中断事件又发生了,等效于将新的事件锁存在二值信号量中,使得延迟处理任务在处理完上一个事件之后,立即就可以处理新的事件。也就是说,延迟处理任务在两次事件处理之间,会有进入阻塞态的机会,因为信号量中锁存有一个事件,所以当SempaphoreTake()调用时,信号量立即有效。
一个二值信号量只可以锁存一个中断事件,在锁存的事件还未被处理之前,如果还有中断事件发生,那么后续发生的中断事件将会丢失。如果用计数信号量代替二值信号量,那么,这种丢中断的情形将可以避免。如下图是计数信号量对事件“计数(Count)”:
2、信号量操作
Freertos提供了一系列函数对信号量进行创建、获取和给出等。
1)创建信号量
void vSemaphoreCreateBinary( xSemaphoreHandle xSemaphore );//二值信号量
xSemaphoreHandle xSemaphoreCreateCounting( unsigned portBASE_TYPE                                 uxMaxCount, unsigned portBASE_TYPE uxInitialCount );//计数信号量
xSemaphore                 信号量的句柄
uxMaxCount          最大计数值
uxInitialCount  信号量的初始计数值
返回值                  NULL创建失败  否则信号量的句柄
2)信号量获取xSemaphoreTake( )
portBASE_TYPE xSemaphoreTake( xSemaphoreHandle xSemaphore,                                                                                         portTickType xTicksToWait )
xSemaphore                信号量的句柄
xTicksToWait          阻塞超时时间。任务进入阻塞态以等待信号量有效的最长时间。如果xTicksToWait0,则xSemaphoreTake()在信号量无效时会立即返回。如果设置为portMAX_DELAY,并且在FreeRTOSConig.h中设定INCLUDE_vTaskSuspend1,那么阻塞等待将没有超时限制。
返回值                  pdPASS获取成功  pdFALSE获取失败
3)信号量给出xSemaphoreGiveFromISR()xSemaphoreGive()
portBASE_TYPE xSemaphoreGiveFromISR( xSemaphoreHandle xSemaphore,
portBASE_TYPE *pxHigherPriorityTaskWoken )        //中断模式下
xSemaphoreGive(xSemaphoreHandle  xSemaphore )                //任务模式下
xSemaphore                信号量的句柄
pxHigherPriorityTaskWoken        pdTRUE:在中断退出前应当进行一次上下文切换,pdFALSE:若给出信号量是的解除阻塞任务优先级高于当前任务优先级(被中断的任务)则,函数内部将该值设为pdTRUE
返回值                        pdPASS给出成功 pdFAIL 无法给出。
3、实验分析
本实验分为中断管理的二值信号量同步和计数信号量同步,在工程中创建两个任务,阻塞在获取信号量,分别使用两个外部中断给出信号量使得程序继续运行,其源代码如下(二值信号量和计数信号量创建不同):
/*****主函数*****/
int main(void)
{
        USART_Configuration();          //串口初始化
        LED_Configuration();       
        vSemaphoreCreateBinary( xBinarySemaphore1 );//创建二值信号量
        vSemaphoreCreateBinary( xBinarySemaphore2 );//创建二值信号量
        xSemaphoreTake(xBinarySemaphore1,portMAX_DELAY) ;               
//由于创建时进行了一次V操作,因此这里进行一次P操作
        xSemaphoreTake(xBinarySemaphore2,portMAX_DELAY) ;
        NVIC_Configuration();
        GpioLed_Init();                                 //LED灯初始化
        USART1_Configuration();       
        printf("\r\n");
        printf("\r\n二值信号量实验");
        xTaskCreate( vLED1Task, ( signed portCHAR * ) "LED", configMINIMAL_STACK_SIZE, NULL, vLED1Task_PRIORITY, NULL );
//创建一个让LED1闪烁的任务       
        xTaskCreate( vLED2Task, ( signed portCHAR * ) "LED", configMINIMAL_STACK_SIZE, NULL, vLED2Task_PRIORITY, NULL );
//创建一个让LED2闪烁的任务
        vTaskStartScheduler();        //开始任务调度
        while(1);
}
//LED1闪烁任务
void vLED1Task( void *pvParameters )
{
        while(1)
{
                xSemaphoreTake(xBinarySemaphore1,portMAX_DELAY) ;
                printf("\r\nvLED1Task");
        }
}
//LED2闪烁任务
void vLED2Task( void *pvParameters )
{
        while(1)
{
                xSemaphoreTake(xBinarySemaphore2,portMAX_DELAY) ;
                printf("\r\nvLED2Task");
        }
}
void EXTI9_5_IRQHandler(void)
{
        static portBASE_TYPE xHigherPriorityTaskWoken1;
        xHigherPriorityTaskWoken1 = pdFALSE;
        if(EXTI_GetITStatus(EXTI_Line8)!= RESET)  
        {  
                EXTI_ClearITPendingBit(EXTI_Line8);
xSemaphoreGiveFromISR( xBinarySemaphore1, &xHigherPriorityTaskWoken1 );
        }  
}
void EXTI3_IRQHandler(void)
{
        static portBASE_TYPE xHigherPriorityTaskWoken2;
        xHigherPriorityTaskWoken2 = pdFALSE;
        if(EXTI_GetITStatus(EXTI_Line3)!= RESET)  
        {  
                EXTI_ClearITPendingBit(EXTI_Line3);
xSemaphoreGiveFromISR( xBinarySemaphore2, &xHigherPriorityTaskWoken2 );
        }  
}
程序使用按键USER1USER2连接到外部中断线,USER1控制向xBinarySemaphore1给出信号,USER2控制向xBinarySemaphore2给出信号,如下图是按下USER1USER2后的串口输出结果:

使用特权

评论回复
5
szopenmcu|  楼主 | 2015-5-6 15:39 | 只看该作者
实验四、计数信号量

上一节我们对计数信号量和二值信号量进行了说明,本节我们主要分析Freertos的计数信号量程序
1、实验分析
本实验在工程中创建两个任务,任务一每次发送三次信号,任务二阻塞在获取信号量,并打印状态,其源代码如下:
/*****主函数*****/
int main(void)
{
        USART_Configuration();          //串口初始化
        LED_Configuration();       
xSemaphore = xSemaphoreCreateCounting( 10,5);
        //信号量,改变初始值可以看到第一次打印的接收数量有变化
        if(xSemaphore == NULL)
        {
                printf("\r\n创建信号量失败\r\n");
                while(1);
        }
        xTaskCreate( vLED1Task, ( signed portCHAR * ) "LED", configMINIMAL_STACK_SIZE, NULL, vLED1Task_PRIORITY, NULL );
//创建一个让LED1闪烁的任务       
        xTaskCreate( vLED2Task, ( signed portCHAR * ) "LED", configMINIMAL_STACK_SIZE, NULL, vLED2Task_PRIORITY, NULL );
//创建一个让LED2闪烁的任务
        vTaskStartScheduler();        //开始任务调度
        while(1);
}
//LED1闪烁任务
void vLED1Task( void *pvParameters )
{
        for(;;){
                printf("\r\n发送数据3\r\n");
                xSemaphoreGive(xSemaphore);        //三次给出信号量
                xSemaphoreGive(xSemaphore);
                xSemaphoreGive(xSemaphore);                       
                                       
                vTaskDelay(1000);
        }
}
//LED2闪烁任务
void vLED2Task( void *pvParameters )
{
        portBASE_TYPE xStatus;
        while(1)
        {
                xStatus=xSemaphoreTake( xSemaphore, portMAX_DELAY );       
//得到一次信号量
                printf("\r\n接收到数据\r\n");                //打印提示
        }
}
如下图是串口输出结果:

使用特权

评论回复
6
yhy123456| | 2015-5-6 17:36 | 只看该作者
写的很好 ,赞一个  

使用特权

评论回复
7
zh113214| | 2015-5-6 22:46 | 只看该作者
很好的学习资料 感谢分享
很强大!谢谢。。。。

使用特权

评论回复
8
szopenmcu|  楼主 | 2015-5-7 10:21 | 只看该作者
yhy123456 发表于 2015-5-6 17:36
写的很好 ,赞一个

:handshake:handshake感谢支持

使用特权

评论回复
9
szopenmcu|  楼主 | 2015-6-15 14:41 | 只看该作者
zh113214 发表于 2015-5-6 22:46
很好的学习资料 感谢分享
很强大!谢谢。。。。

谢谢支持:handshake

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:专业生产销售STM32开发板,仿真器,http://openmcu.taobao.com/

71

主题

283

帖子

11

粉丝