简介
本程序是将freertos移植在Linux中运行,目的是打造一种在LINUX 运行app的架构。
具体展示
#include <stdio.h>
#include <stdlib.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "timers.h"
#include "event_groups.h"
#include "semphr.h"
//事件组句柄
EventGroupHandle_t xEventGroup;
//二进制信号量句柄
SemaphoreHandle_t xBinarySemaphore;
//互斥量句柄
SemaphoreHandle_t xMutex;
//任务通知句柄
TaskHandle_t notifyTask;
/*"事件组中事件位的定义。 */
#define mainFIRST_TASK_BIT ( 1UL << 0UL ) /* 事件位0,由任务设置。 */
#define mainSECOND_TASK_BIT ( 1UL << 1UL ) /* 事件位1,由任务设置。 */
#define mainISR_BIT ( 1UL << 2UL ) /* 事件位2,由ISR设置。 */
static void vTask1( void *pvParameters );
static void vTask2( void *pvParameters );
static void vTask3( void *pvParameters );
static void vTask4( void *pvParameters );
static void vTask5( void *pvParameters );
static void vTask6( void *pvParameters );
static void vTask7( void *pvParameters );
static void vTask8( void *pvParameters );
static void vTask9( void *pvParameters );
int main()
{
static xQueueHandle xMessageQueue;
//创建消息队列用于任务间通讯
xMessageQueue = xQueueCreate( 10, ( unsigned portBASE_TYPE ) sizeof( unsigned short ) );
//创建队列收发通讯任务
xTaskCreate( vTask1, "vTask1", configMINIMAL_STACK_SIZE, ( void * ) &xMessageQueue, tskIDLE_PRIORITY, NULL );
xTaskCreate( vTask2, "vTask2", configMINIMAL_STACK_SIZE, ( void * ) &xMessageQueue, tskIDLE_PRIORITY, NULL );
/* 在使用事件组之前,必须先创建事件组。 */
xEventGroup = xEventGroupCreate();
//创建事件组任务
xTaskCreate( vTask3, "vtask3", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreate( vTask4, "vtask4", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
//创建信号量任务
xTaskCreate( vTask5, "vtask5", configMINIMAL_STACK_SIZE, NULL, 3, NULL);
xTaskCreate( vTask6, "vtask6", configMINIMAL_STACK_SIZE, NULL, 4, NULL);
//创建互斥锁任务
xTaskCreate( vTask7, "vtask7", configMINIMAL_STACK_SIZE, NULL, 5, NULL);
//创建任务通知任务
xTaskCreate( vTask8, "vtask8", configMINIMAL_STACK_SIZE, NULL, 7, NULL);
xTaskCreate( vTask9, "vtask9", configMINIMAL_STACK_SIZE, NULL, 6, ¬ifyTask);
//创建动态软件定时器
TimerHandle_t SoftTmr = NULL; /* 软件定时器句柄 */
void SoftTmr_Callback(void* parameter)
{
//定时处理函数
printf("hello world\n");
}
SoftTmr = xTimerCreate((const char*)"AutoReloadTimer",
(TickType_t)1000, /* 定时器周期 1000(tick) */
(UBaseType_t)pdTRUE,/* 周期模式 */
(void*)1, /* 为每个计时器分配一个索引的唯一 ID */
(TimerCallbackFunction_t)SoftTmr_Callback);
if (SoftTmr != NULL)
{
xTimerStart(SoftTmr, 0); // 开启周期定时器
}
//创建动态的二值信号量(动态二值信号量可以删除回收,静态二值信号量xSemaphoreCreateCountingStatic不可以)
xBinarySemaphore = xSemaphoreCreateBinary();
if (NULL == xBinarySemaphore)
{
printf("create binary semaphore failed");
return -1;
}
//创建互斥锁
xMutex = xSemaphoreCreateMutex( );
//启动调度器
vTaskStartScheduler();
printf("start system failed\n");
return 1;
}
static void vTask1( void *pvParameters )
{
unsigned short usValue = 0, usLoop;
xQueueHandle *pxQueue;
const unsigned short usNumToProduce = 3;
short sError = pdFALSE;
pxQueue = ( xQueueHandle * ) pvParameters;
for( ;; )
{
for( usLoop = 0; usLoop < usNumToProduce; ++usLoop )
{
/* Send an incrementing number on the queue without blocking. */
printf("Task1 will send: %d\r\n", usValue);
//将数据发送到队列的后端, 在中断使用则用xQueueSendToBackFromISR()
if( xQueueSendToBack( *pxQueue, ( void * ) &usValue, ( portTickType ) 0 ) != pdPASS )
{
sError = pdTRUE;
}
else
{
++usValue;
}
}
vTaskDelay( 2000 );
}
}
static void vTask2( void *pvParameters )
{
unsigned short usData = 0;
xQueueHandle *pxQueue;
pxQueue = ( xQueueHandle * ) pvParameters;
for( ;; )
{
while( uxQueueMessagesWaiting( *pxQueue ) )
{
//从队列中接收(读取)一个元素。收到的元素将从队列中删除。在中断则用xQueueReceiveFromISR()
if( xQueueReceive( *pxQueue, &usData, ( portTickType ) 0 ) == pdPASS )
{
printf("Task2 received:%d\r\n", usData);
}
}
vTaskDelay( 5000 );
}
}
static void vTask3( void *pvParameters )
{
for( ;; )
{
/*在开始下一个循环之前稍作延迟。 */
vTaskDelay( 2000 );
/*输出一条消息,表示任务即将设置事件位0,然后设置事件位0。*/
printf( "Bit setting task -\t ready to set bit 0.\r\n" );
xEventGroupSetBits( xEventGroup, mainFIRST_TASK_BIT );
/*在设置其他位之前稍作延迟。 */
vTaskDelay( 2000 );
/*输出一个消息,说事件位1即将被任务设置,然后设置事件位1。*/
printf( "Bit setting task -\t ready to set bit 1.\r\n" );
xEventGroupSetBits( xEventGroup, mainSECOND_TASK_BIT );
}
}
static void vTask4( void *pvParameters )
{
EventBits_t xEventGroupValue;
const EventBits_t xBitsToWaitFor = ( mainFIRST_TASK_BIT |
mainSECOND_TASK_BIT |
mainISR_BIT );
for( ;; )
{
/* 阻塞以等待事件位在事件组中被设置。*/
xEventGroupValue = xEventGroupWaitBits( /* 要读取的事件组。 */
xEventGroup,
/* 位测试。 */
xBitsToWaitFor,
/*如果满足解封条件,则在退出时清除位。*/
pdTRUE,
/*不要等待所有的位。对于第二次执行,该参数被设置为pdTRUE。 */
pdFALSE,
/* 不要超时。 */
portMAX_DELAY );
/*为设置的每个位打印一条消息。 */
if( ( xEventGroupValue & mainFIRST_TASK_BIT ) != 0 )
{
printf( "Bit reading task -\t Event bit 0 was set\r\n" );
}
if( ( xEventGroupValue & mainSECOND_TASK_BIT ) != 0 )
{
printf( "Bit reading task -\t Event bit 1 was set\r\n" );
}
}
}
static void vTask5( void *pvParameters )
{
BaseType_t xReturn = pdTRUE;
int iCount = 0;
for( ;; )
{
printf("start post sem");
if(iCount >= 3) //发送三次二值信号量后,删除二值信号量
{
iCount=0;
//删除二值信号量
vSemaphoreDelete(xBinarySemaphore);
xBinarySemaphore = NULL;
printf(" but delete binary semaphore\n");
}
if( xBinarySemaphore != NULL )
{
xReturn = xSemaphoreGive(xBinarySemaphore);
if (pdTRUE == xReturn)
{
printf("post sem succeed");
}
else
{
printf("post sem error");
}
iCount++;
}
printf("=========icount=%d\n", iCount);
vTaskDelay(2500);
}
}
static void vTask6( void *pvParameters )
{
BaseType_t xReturn = pdTRUE;
for( ;; )
{
printf("wait sem");
xReturn = xSemaphoreTake(xBinarySemaphore, portMAX_DELAY);
if (pdTRUE == xReturn)
{
printf("get sem succeed\r\n");
}
else
{
printf("get sem error\r\n");
}
}
}
/*
*互斥锁保护的资源区域代码内容会被当前任务独占地使用,也被称为临界资源,临界锁;
它能够很好的保护这段代码执行,但注意不要处理耗时的事情,以免影响实时性。
*关于死锁:情景一就是当任务A需要任务B才能解锁,任务B需要任务A才能解锁,就会造成死锁;
另外一种情景就是任务A获取了互斥锁,但它休眠了,锁无法得到释放。
*解决死锁的方式就是使用递归锁。
*/
static void vTask7( void *pvParameters )
{
int i = 0;
for( ;; )
{
//获取互斥锁以上锁
xSemaphoreTake(xMutex, portMAX_DELAY);
for(i=0; i<20; i++)
{
printf("###i=%d\n", i);
}
//释放互斥锁以解锁
xSemaphoreGive(xMutex);
vTaskDelay(3000);
}
}
/*
* 优点:效率高,且相比信号量、事件组、消息队列更加省内存;
* 任务通知无法把信息通知给多个任务,如需这样需使用事件组来实现。
*/
static void vTask8( void *pvParameters )
{
for( ;; )
{
#if 0
//传任意值
if(xTaskNotify(notifyTask, 88, eSetValueWithoutOverwrite) == pdPASS)
{
printf("####put data notify task9 successed\n");
}
else
{
printf("put data notify task9 faield\n");
}
#else
//传信号
if(xTaskNotifyGive(notifyTask) == pdPASS)
{
printf("$$$ send notify signal success\n");
}
else
{
printf("$$$ send notify signal failed\n");
}
#endif
vTaskDelay(2000);
}
}
static void vTask9( void *pvParameters )
{
uint32_t receData = 0;
BaseType_t xResult;
int notifySignalNum = 0;
for( ;; )
{
#if 0
//收任意值
xResult = xTaskNotifyWait( //阻塞等待,后面不执行的方式
0, //接收方进入函数时数据清0
0, //接收方退出函数时数据清0
&receData, //保存的数据
portMAX_DELAY //一直等待数据
);
if(xResult == pdPASS)
{
printf("####receive notify data: %d success\n", receData);
}
#else
//收信号
notifySignalNum = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
while (notifySignalNum--)
{
printf("$$$ i am receive task8 signal\n");
}
#endif
}
}
/*堆栈溢出检测钩子函数,(ps:因为是PC端运行freertos,所以不支持该检测,但我还是放这里,在硬件上运行时,为了方便对异常进行调试,该函数还是得加上)
* 一般将 configCHECK_FOR_STACK_OVERFLOW 设置为 2 相比设置1能够准确的找到堆栈溢出;
*/
# if 0
/* in run time check stack overflow */
void vApplicationStackOverflowHook( TaskHandle_t pxTask,
char * pcTaskName )
{
( void ) pxTask;
/* Run time stack overflow checking is performed if
* configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook
* function is called if a stack overflow is detected. This function is
* provided as an example only as stack overflow checking does not function
* when running the FreeRTOS POSIX port. */
while(1)
{
printf("task %s is stack overflow. \r\n", pcTaskName);
vTaskDelay(500);
}
}
//检测栈剩余的方法
/*
static xTaskHandle pv_handle = NULL;
if(xTaskCreate(test, "test", 512, NULL,1,&pv_handle) != pdPASS)
{
printf("[%s] test error\n",__func__);
}
void test(void *param)
{
//方式一:使用NULL,默认获取当前任务栈大小
printf("stack:%d\n",(int)uxTaskGetStackHighWaterMark(NULL));
for(;;)
{
//方式二:使用任务句柄,获取指定任务栈大小(ps:一般单独一个任务检测栈使用情况)
printf("current stack:%d\n",(int)uxTaskGetStackHighWaterMark(pv_handle));
vTaskDelay(500);
}
}
*/
#endif
PS:这里顺便补充个知识点
回调函数和钩子函数的区别
根本上,他们都是为了捕获消息而生的,但是钩子函数在捕获消息的第一时间就会执行,而回调函数是在整个捕获过程结束时,最后一个被执行的
。
回调函数其实就是调用者把回调函数的函数指针传递给调用函数,当调用函数执行完毕时,通过函数指针来调用回调函数。
关于信号量使用场景选择补充
背景
Freertos是一个多进程操作系统。多进程的一个重要控制,就是进程同步。所以信号量和互斥量,可也只能用于进程间的同步,且不能传递更多的数据。
二值信号量:一般用于进程同步,或者ISR和TASK的同步。
计数信号量:一般用于共享资源的缓冲区统计。
互斥量:一般用于共享资源的独立访问。
1.信号量
信号量在RTOS中,会引起优先级反转问题。
当低优先级任务获取信号量后,如果高优先级任务也需要这个信号量,则需要被阻塞,最终,实际上高优先级任务被降低到更低的级别了。
2.互斥量。
当任务获取一个互斥量时,它的优先级会被OS暂时提高到和所有等待该互斥量的任务中的最高优先级。从而保证了,任务在互斥访问时,不会被抢占。
当任务归还互斥量时,优先级会被OS恢复到之前的级别。这被称为优先级继承。
优先级继承不能解决优先级反转,但是能够将危害降到最低。
异常场景常见问题汇总
printf的不当使用
不适当地使用printf()是一个常见的错误来源,而且,由于没有意识到这一点,应用程序开发人员通常会进一步调用printf()来帮助调试,这样做会使问题更加严重。
许多交叉编译器供应商将提供一个适合在小型嵌入式系统中使用的 printf() 实现。即使是这样,该实现也可能不是线程安全的,可能不适合在中断服务例程中使用,而且根据输出的方向,需要花费相对较长的时间来执行。
如果没有专门为小型嵌入式系统设计的printf()实现,而使用通用的 printf() 实现,则必须特别小心,如:
仅仅包括对printf()或 sprintf() 的调用就会大量增加应用程序的可执行文件的大小。
printf() 和 sprintf() 可能会调用 malloc() ,如果使用的是heap_3以外的内存分配方案,这可能是无效的。更多信息请参见内存分配方案示例。
printf() 和 sprintf() 可能需要一个比原来大很多倍的栈。
调试运行的Demo代码
https://download.csdn.net/download/qq_32348883/88065207