RTOS系统移植

一、完成系统移植

系统移植上官网寻找合适的系统包,下载后将文件移植入工程文件

二、创建任务句柄、内核对象句柄(信号量,消息队列,事件标志组,软件定时器)、声明全局变量、声明函数

三、创建主函数,用于存放硬件初始化(bsp_init())、创建主任务任务(用于创建多任务)( xTaskCreate())、启动任务调度( vTaskStartScheduler())

int bsp_init()

{

/*

主要实现如

gpio引脚:

gpio组时钟使能(RCC)、gpio初始化(GPIO_INIT())、gpio输入输出cpu

UART:

gpio组时钟使能(RCC)、gpio初始化(GPIO_INIT())、gpio复用(GPIO_PINGAFCONFIG())、uart结构体定义、uart初始化(uart_init())、中断配置(uart_itconfig())、NVIC控制器(nvic_init())、uart使能(uart_cmd())、收发数据

IIC:

SPI:

CAN:

gpio组时钟使能(RCC)、gpio初始化(GPIO_INIT())、gpio复用(GPIO_PINGAFCONFIG())、can结构体定义、can初始化(can_init())、筛选器配置(CAN_FilterInit())、can使能(can_cmd())、收发数据

ETH:

gpio组时钟使能(RCC)、gpio初始化(GPIO_INIT())、gpio复用(GPIO_PINGAFCONFIG())、eth结构体定义、eth初始化(HAL_ETH_Init())、配置发送接收描述符(HAL_ETH_DMATxDescListInit()、HAL_ETH_DMARxDescListInit())、启动ETH通信(HAL_ETH_Start())、收发数据

}

 xTaskCreate()

 /* 创建主任务 */
  xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */
                        (const char*    )"AppTaskCreate",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )1, /* 任务的优先级 */
                        (TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ 
 

四、创建主任务函数AppTaskCreate()

static void AppTaskCreate(void)
{
  taskENTER_CRITICAL();           //进入临界区
 
  /* 创建xxx_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )xxx_Task, /* 任务入口函数 */
                        (const char*    )"LED_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,    /* 任务入口函数参数 */
                        (UBaseType_t    )2,        /* 任务的优先级 */
                        (TaskHandle_t*  )&LED_Task_Handle);/* 任务控制块指针 */

/* 创建xxx2_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )xxx2_Task,  /* 任务入口函数 */
                        (const char*    )"KEY_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&KEY_Task_Handle);/* 任务控制块指针 */ 
    
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

五、创建xxx_Task、xxx2_Task任务函数(实现具体的功能)(static void xxx_Task())

static void xxx_Task(void* parameter)
{    
    while (1)
    {
        //使用BSP_init()中封装的内容实现具体的功能,如收发数据
    }
}

六、根据需要,实现任务的抢占

FREERTOS是一个抢占式实时任务系统,多任务情况下涉及一个任务的状态迁移,一般来说任务抢占,会在任务函数中去实现,完成挂起,延时、删除、恢复的操作。

1.任务挂起函数   vTaskSuspend()
     挂起指定任务。被挂起的任务绝不会得到 CPU 的使用权,不管该任务具有什么优先级。 任务可以通过调用 vTaskSuspend()函数都可以将处于任何状态的任务挂起,被挂起的 任务得不到 CPU 的使用权,也不会参与调度,它相对于调度器而言是不可见的,除非它从 挂起态中解除。

2.vTaskSuspendAll()
        这个函数就是比较有意思的,将所有的任务都挂起,其实源码很简单,也很有意思, 不管三七二十一将调度器锁定,并且这个函数是可以进行嵌套的,说白了挂起所有任务就 是挂起任务调度器。调度器被挂起后则不能进行上下文切换,但是中断还是使能的。 当调 度器被挂起的时候,如果有中断需要进行上下文切换, 那么这个任务将会被挂起,在调度 器恢复之后才执行切换任务。

3.任务恢复函数     vTaskResume()
       既然有任务的挂起,那么当然一样有恢复,不然任务怎么恢复呢,任务恢复就是让挂 起的任务重新进入就绪状态,恢复的任务会保留挂起前的状态信息,在恢复的时候根据挂 起时的状态继续运行。如果被恢复任务在所有就绪态任务中,处于最高优先级列表的第一 位,那么系统将进行任务上下文的切换。

4.任务删除函数   vTaskDelete()
     用于删除一个任务。当一个任务删除另外一个任务时,形参为要删除任 务创建时返回的任务句柄,如果是删除自身, 则形参为 NULL。 要想使用该函数必须在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelete 定义为 1,删除的任务将从所有就绪,阻塞, 挂起和事件列表中删除。

5.任务延时函数    vTaskDelay()
       vTaskDelay()在我们任务中用得非常之多,每个任务都必须是死循环,并且是必须要有 阻塞的情况,否则低优先级的任务就无法被运行了。要想使用 FreeRTOS 中的 vTaskDelay() 函数必须在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelay 定义为 1 来使能。

6.vTaskDelayUntil()
      在 FreeRTOS 中,除了相对延时函数,还有绝对延时函数 vTaskDelayUntil(),这个绝 对延时常用于较精确的周期运行任务,比如我有一个任务,希望它以固定频率定期执行, 而不受外部的影响,任务从上一次运行开始到下一次运行开始的时间间隔是绝对的,而不 是相对的。

七、创建、写、读、删除消息队列

队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之 间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的 长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列用来传递消息的,所以 也称为消息队列。

1、数据存储

通常队列采用先进先出(FIFO)的存储缓冲机制,也就是往队列发送数据的时候(也叫入队)永远都是发送到队列的尾部,而从队列提取数据的时候(也叫出队)是从队列的头部提取的。但是也可以使用 LIFO 的存储缓冲,也就是后进先出,FreeRTOS 中的队列也提供了 LIFO 的存储缓冲机制。 数据发送到队列中会导致数据拷贝,也就是将要发送的数据拷贝到队列中,这就意味着在 队列中存储的是数据的原始值,而不是原数据的引用(即只传递数据的指针),这个也叫做值传递。

2、多任务访问

队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息

3、出队阻塞

当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任务从队列中读取消息无效的时候任务阻塞的时间。出队就是就从队列中读取消息,出队阻塞是 针对从队列中读取消息的任务而言的

4、入队阻塞

入队说的是向队列中发送消息,将消息加入到队列中。和出队阻塞一样,当一个任务向队 列发送消息的话也可以设置阻塞时间

5、消息队列在任务中实现

一般来说,消息队列的创建、读取、写入、删除都在任务函数中实现

1. 消息队列创建函数 xQueueCreate()

2 消息队列静态创建函数 xQueueCreateStatic()

3.读队列   xQueueReceive()

 4.写队列  xQueueSend()

5.消息队列删除函数 vQueueDelete()

6.复位   xQueueReset()

 7.查询  uxQueueMessagesWaiting()(列中可用数据的个数)、uxQueueSpacesAvailable()(队列中可用空间的个数)

在主任务创建消息队列,在任务中读、写、删除、复位、查询队列

static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区
  
  /* 创建Test_Queue */
  Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */
                            (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */

  if(NULL != Test_Queue)
    printf("创建Test_Queue消息队列成功!\r\n");
  
  /* 创建Receive_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Receive_Task, /* 任务入口函数 */
                        (const char*    )"Receive_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,    /* 任务入口函数参数 */
                        (UBaseType_t    )2,        /* 任务的优先级 */
                        (TaskHandle_t*  )&Receive_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Receive_Task任务成功!\r\n");
  
  /* 创建Send_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Send_Task,  /* 任务入口函数 */
                        (const char*    )"Send_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&Send_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Send_Task任务成功!\n\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}
 

static void Receive_Task(void* parameter)
{    
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdTRUE */
  uint32_t r_queue;    /* 定义一个接收消息的变量 */
  while (1)
  {
    xReturn = xQueueReceive( Test_Queue,    /* 消息队列的句柄 */
                             &r_queue,      /* 发送的消息内容 */
                             portMAX_DELAY); /* 等待时间 一直等 */

    if(pdTRUE == xReturn)
      printf("本次接收到的数据是%d\n\n",r_queue);
    else
      printf("数据接收出错,错误代码0x%lx\n",xReturn);
  }
}
 

static void Send_Task(void* parameter)
{     
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  uint32_t send_data1 = 1;
  uint32_t send_data2 = 2;
  while (1)
  {
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {/* K1 被按下 */
      printf("发送消息send_data1!\n");
      xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
                            &send_data1,/* 发送的消息内容 */
                            0 );        /* 等待时间 0 */

      if(pdPASS == xReturn)
        printf("消息send_data1发送成功!\n\n");
    } 
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {/* K2 被按下 */
      printf("发送消息send_data2!\n");
      xReturn = xQueueSend( Test_Queue, /* 消息队列的句柄 */
                            &send_data2,/* 发送的消息内容 */
                            0 );        /* 等待时间 0 */

      if(pdPASS == xReturn)
        printf("消息send_data2发送成功!\n\n");
    }
    vTaskDelay(20);/* 延时20个tick */
  }
}

八、创建信号量

信号量常常用于控制对共享资源的访问和任务同步

1.二值信号量(只有0、1,用于临界资源的保护,作用约等于上锁解锁)

二值信号量既可以用于临界资源访问也可以用于同步功能。 二值信号量和互斥信号量(以下使用互斥量表示互斥信号量)非常相似,但是有一些细 微差别:互斥量有优先级继承机制,二值信号量则没有这个机制

2.计数信号量(0~N的计数,用于事件计数或者某个资源的管理计数)

计数信号量肯定是用于计数的,在实际的使用中,我们常将计数信号量用于事件计数与资源管理。每当某个事件发生时,任务或者中断将释放一个信号量(信号量 计数值加 1),当处理被事件时(一般在任务中处理),处理任务会取走该信号量(信号 量计数值减 1),信号量的计数值则表示还有多少个事件没被处理。此外,系统还有很多资源,我们也可以使用计数信号量进行资源管理,信号量的计数值表示系统中可用的资源数目,任务必须先获取到信号量才能获取资源访问权,当信号量的计数值为零时表示系统 没有可用的资源,但是要注意,在使用完资源的时候必须归还信号量,否则当计数值为 0 的时候任务就无法访问该资源了。 计数型信号量允许多个任务对其进行操作,但限制了任务的数量

3.互斥信号量(和二值信号量相似,多出一个优先级继承机制(暂时提高某个占有某种资源的低优先级任务的优先级,使之与在所有等待该 资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该 资源时,优先级重新回到初始设定值))

互斥信号量其实是特殊的二值信号量,由于其特有的优先级继承机制从而使它更适用于简单互锁,也就是保护临界资源。

 用作互斥时,信号量创建后可用信号量个数应该是满的,任务在需要使用临界资源时, (临界资源是指任何时刻只能被一个任务访问的资源),先获取互斥信号量,使其变空, 这样其他任务需要使用临界资源时就会因为无法获取信号量而进入阻塞,从而保证了临界 资源的安全。

在操作系统中,我们使用信号量的很多时候是为了给临界资源建立一个标志,信号量表示了该临界资源被占用情况。这样,当一个任务在访问临界资源的时候,就会先对这个 资源信息进行查询,从而在了解资源被占用的情况之后,再做处理,从而使得临界资源得 到有效的保护

4.递归信号量(可以重复调用信号量,在信号量递归调用未完全归还前,其他任务无法获取信号量)

信号量是可以重复获取调用的, 对于已经获取递归互斥量的任务可以重复获取该递归互斥量,该任务拥有递归信号量的所有权。任务成功获取几次递 归互斥量,就要返还几次,在此之前递归互斥量都处于无效状态,其他任务无法获取,只 有持有递归信号量的任务才能获取与释放

5.信号量在任务、中断中实现(创建、获取、删除、释放)

互斥信号量只能在任务中实现,无法在中断使用

1.创建二值信号量 xSemaphoreCreateBinary()
2. 创建计数信号量 xSemaphoreCreateCounting()
3.创建互斥信号量 xSemaphoreCreateMutex()
4..创建递归互斥信号量xSemaphoreCreateRecursiveMutex()
5.信号量删除函数 vSemaphoreDelete()
6.信号量释放函数 xSemaphoreGive()(任务)、xSemaphoreGiveFromISR()(中断)
7.递归互斥量释放函数 xSemaphoreGiveRecursive()
8.信号量获取函数xSemaphoreTake()(任务)、xSemaphoreTakeFromISR()(中断)
9.递归互斥量获取函数 xSemaphoreTakeRecursive()

6.二值、计数信号量在任务中实现

static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区
  
  /* 创建 BinarySem */
  BinarySem_Handle = xSemaphoreCreateBinary();    /*xSemaphoreCreateBinary  创建二值信号量*/
  if(NULL != BinarySem_Handle)
    printf("BinarySem_Handle二值信号量创建成功!\r\n");

 /* 创建Test_Queue */
  CountSem_Handle = xSemaphoreCreateCounting(10,10);      /*xSemaphoreCreateCounting  创建计数信号量*/
  if(NULL != CountSem_Handle)
    printf("CountSem_Handle计数信号量创建成功!\r\n");

  /* 创建Receive_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Receive_Task, /* 任务入口函数 */
                        (const char*    )"Receive_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,    /* 任务入口函数参数 */
                        (UBaseType_t    )2,        /* 任务的优先级 */
                        (TaskHandle_t*  )&Receive_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Receive_Task任务成功!\r\n");
  
  /* 创建Send_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Send_Task,  /* 任务入口函数 */
                        (const char*    )"Send_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&Send_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Send_Task任务成功!\n\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

static void Receive_Task(void* parameter)
{    
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  while (1)
  {
    //获取二值信号量 xSemaphore,没获取到则一直等待
        xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄   xSemaphoreTake获取一个信号量,可以是二值信号量、计数信号量、互斥量。*/
                              portMAX_DELAY); /* 等待时间 */

    if(pdTRUE == xReturn)
      printf("BinarySem_Handle二值信号量获取成功!\n\n");

       /* 获取一个计数信号量 */
      xReturn = xSemaphoreTake(CountSem_Handle,    /* 计数信号量句柄 */
                             0);     /* 等待时间:0 */

            if ( pdTRUE == xReturn ) 
                printf( "KEY1被按下,成功申请到停车位。\n" );
            else
                printf( "KEY1被按下,不好意思,现在停车场已满!\n" );                            
  }
}
 
static void Send_Task(void* parameter)
{     
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  while (1)
  {
    /* K1 被按下 */
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量 xSemaphoreGive 释放信号量
      if( xReturn == pdTRUE )
        printf("BinarySem_Handle二值信号量释放成功!\r\n");
      else
        printf("BinarySem_Handle二值信号量释放失败!\r\n");
    } 
    /* K2 被按下 */
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {
      xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
      if( xReturn == pdTRUE )
        printf("BinarySem_Handle二值信号量释放成功!\r\n");
      else
        printf("BinarySem_Handle二值信号量释放失败!\r\n");

       /* 获取一个计数信号量 */
      xReturn = xSemaphoreGive(CountSem_Handle);//给出计数信号量                  
            if ( pdTRUE == xReturn ) 
                printf( "KEY2被按下,释放1个停车位。\n" );
            else
                printf( "KEY2被按下,但已无车位可以释放!\n" );                            
    }
    vTaskDelay(20);
  }
}

7.互斥信号量在任务中实现

static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区
  
  /* 创建MuxSem */
  MuxSem_Handle = xSemaphoreCreateMutex();//创建互斥量
 
if(NULL != MuxSem_Handle)
    printf("MuxSem_Handle互斥量创建成功!\r\n");
 
  xReturn = xSemaphoreGive( MuxSem_Handle );//释放互斥量
//  if( xReturn == pdTRUE )
//    printf("释放信号量!\r\n");
    
  /* 创建LowPriority_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )LowPriority_Task, /* 任务入口函数 */
                        (const char*    )"LowPriority_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,    /* 任务入口函数参数 */
                        (UBaseType_t    )2,        /* 任务的优先级 */
                        (TaskHandle_t*  )&LowPriority_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建LowPriority_Task任务成功!\r\n");
  
  /* 创建MidPriority_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )MidPriority_Task,  /* 任务入口函数 */
                        (const char*    )"MidPriority_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&MidPriority_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建MidPriority_Task任务成功!\n");
  
  /* 创建HighPriority_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )HighPriority_Task,  /* 任务入口函数 */
                        (const char*    )"HighPriority_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )4, /* 任务的优先级 */
                        (TaskHandle_t*  )&HighPriority_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建HighPriority_Task任务成功!\n\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

static void LowPriority_Task(void* parameter)
{    
  static uint32_t i;
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  while (1)
  {
    printf("LowPriority_Task 获取互斥量\n");
    //获取互斥量 MuxSem,没获取到则一直等待
        xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
                              portMAX_DELAY); /* 等待时间 */

    if(pdTRUE == xReturn)
    printf("LowPriority_Task Runing\n\n");
    
    for(i=0;i<2000000;i++)//模拟低优先级任务占用互斥量
        {
            taskYIELD();//发起任务调度
        }
    
    printf("LowPriority_Task 释放互斥量!\r\n");
    xReturn = xSemaphoreGive( MuxSem_Handle );//释放互斥量
      
        LED1_TOGGLE;
    
    vTaskDelay(1000);
  }
}
 
static void MidPriority_Task(void* parameter)
{     
  while (1)
  {
   printf("MidPriority_Task Runing\n");
   vTaskDelay(1000);
  }
}
 
static void HighPriority_Task(void* parameter)
{    
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  while (1)
  {
    printf("HighPriority_Task 获取互斥量\n");
    //获取互斥量 MuxSem,没获取到则一直等待
        xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
                              portMAX_DELAY); /* 等待时间 */

    if(pdTRUE == xReturn)
      printf("HighPriority_Task Runing\n");
        LED1_TOGGLE;
    
    printf("HighPriority_Task 释放互斥量!\r\n");
    xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
 
  
    vTaskDelay(1000);
  }
}
 

九、事件组

1.事件位(事件标志)

事件位用来表明某个事件是否发生,事件位通常用作事件标志

 当收到一条消息并且把这条消息处理掉以后就可以将某个位(标志)置 1,当队列中没有消息需要处理的时候就可以将这个位(标志)置 0

当把队列中的消息通过网络发送输出以后就可以将某个位(标志)置 1,当没有数据需要从网络发送出去的话就将这个位(标志)置0

当需要向网络中发送一个心跳信息,将某个位(标志)置 1,当不需要向网络中发送心跳信息,这个位(标志)置0

2.事件组

事件组的数据类型为 EventGroupHandle_t,当configUSE_16_BIT_TICKS 为 1 的时候 事件标志组可以存储 8 个事件位,当 configUSE_16_BIT_TICKS 为 0 的时候事件标志组存储 24 个事件位

一个事件组就是一组的事件位,事件组中的事件位通过位编号来访问

事件标志组的 bit0 表示队列中的消息是否处理掉

事件标志组的 bit1 表示是否有消息需要从网络中发送出去

事件标志组的 bit2 表示现在是否需要向网络发送心跳信息

3.事件应用场景

事件来做标志位,判断某些事件是否发生了,然后根据结果做处理(事件可使用于多种场合,能够在一定程度上替代信号量,用于任务与任务间,中断与任务间的同步)

 接收事件时,可以根据感兴趣的参事件类型接收事件的单个或者多个事件类型。事件接收成功后,必须使用 xClearOnExit 选项来清除已接收到的事件类型,否则不会清除已接收 到的 事件 ,这样就需要用户显式清除事件位。用户可以自定义通过传入参数xWaitForAllBits 选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件。 

设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位为1,可 以一次同时写多个事件类型,设置事件成功可能会触发任务调度。 

清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清0操作。 事件不与任务相关联,事件相互独立,一个 32位的变量(事件集合,实际用于表示事 件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表 示该事件类型未发生、1表示该事件类型已经发生)

4.事件控制块

事件标志组存储在一个 EventBits_t 类型的变量中,该变量在事件组结构体中定义,如果宏 configUSE_16_BIT_TICKS 定义为 1,那么变量 uxEventBits 就 是 16 位 的 , 其 中 有 8 个 位 用来存储 事 件 组 , 如 果 宏 configUSE_16_BIT_TICKS 定义为 0,那么变量 uxEventBits 就是 32 位的,其中有 24 个位 用来存储事件组,每一位代表一个事件的发生与否,利用逻辑或、逻辑与等实现不同事件的不同唤醒处理

除了事件标志组变量之外,FreeRTOS 还使用了一个链表来记录等待事件的任务,所有 在等待此事件的任务均会被挂载在等待事件列表 xTasksWaitingForBits

typedef struct xEventGroupDefinition {
   EventBits_t uxEventBits;
    List_t xTasksWaitingForBits;
 
 #if( configUSE_TRACE_FACILITY == 1 )
   UBaseType_t uxEventGroupNumber;
 #endif
 
 #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) \
   && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
   uint8_t ucStaticallyAllocated;
 #endif
   } EventGroup_t;

5.事件组实现(创建、置位、等待、清除位、删除)

1.事件创建函数 xEventGroupCreate()
2.事件删除函数 vEventGroupDelete()
3.事件组置位函数 xEventGroupSetBits()(任务)
4.等待事件函数 xEventGroupWaitBits()
5. 事件组清除函数位 xEventGroupClearBits()(任务)与 xEventGroupClearBitsFromISR()(中断)

6.事件组在任务中实现

static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区
  
  /* 创建 Event_Handle */
  Event_Handle = xEventGroupCreate();     
  if(NULL != Event_Handle)
    printf("Event_Handle 事件创建成功!\r\n");
    
  /* 创建LED_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
                        (const char*    )"LED_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,    /* 任务入口函数参数 */
                        (UBaseType_t    )2,        /* 任务的优先级 */
                        (TaskHandle_t*  )&LED_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建LED_Task任务成功!\r\n");
  
  /* 创建KEY_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )KEY_Task,  /* 任务入口函数 */
                        (const char*    )"KEY_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&KEY_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建KEY_Task任务成功!\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

static void LED_Task(void* parameter)
{    
  EventBits_t r_event;  /* 定义一个事件接收变量 */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
    {
    /*******************************************************************
     * 等待接收事件标志 
     * 
     * 如果xClearOnExit设置为pdTRUE,那么在xEventGroupWaitBits()返回之前,
     * 如果满足等待条件(如果函数返回的原因不是超时),那么在事件组中设置
     * 的uxBitsToWaitFor中的任何位都将被清除。 
     * 如果xClearOnExit设置为pdFALSE,
     * 则在调用xEventGroupWaitBits()时,不会更改事件组中设置的位。
     *
     * xWaitForAllBits如果xWaitForAllBits设置为pdTRUE,则当uxBitsToWaitFor中
     * 的所有位都设置或指定的块时间到期时,xEventGroupWaitBits()才返回。 
     * 如果xWaitForAllBits设置为pdFALSE,则当设置uxBitsToWaitFor中设置的任何
     * 一个位置1 或指定的块时间到期时,xEventGroupWaitBits()都会返回。 
     * 阻塞时间由xTicksToWait参数指定。          
      *********************************************************/
    r_event = xEventGroupWaitBits(Event_Handle,  /* 事件对象句柄 */
                                  KEY1_EVENT|KEY2_EVENT,/* 接收线程感兴趣的事件 */
                                  pdTRUE,   /* 退出时清除事件位 */
                                  pdTRUE,   /* 满足感兴趣的所有事件 */
                                  portMAX_DELAY);/* 指定超时事件,一直等 */

                        
    if((r_event & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT)) 
    {
      /* 如果接收完成并且正确 */
      printf ( "KEY1与KEY2都按下\n");        
      LED1_TOGGLE;       //LED1    反转
    }
    else
      printf ( "事件错误!\n");    
  }
}
 
static void KEY_Task(void* parameter)
{     
    /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )       //如果KEY2被单击
        {
      printf ( "KEY1被按下\n" );
            /* 触发一个事件1 */
            xEventGroupSetBits(Event_Handle,KEY1_EVENT);//事件组置位                      
        }
    
        if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )       //如果KEY2被单击
        {
      printf ( "KEY2被按下\n" );    
            /* 触发一个事件2 */
            xEventGroupSetBits(Event_Handle,KEY2_EVENT);//事件组置位                 
        }
        vTaskDelay(20);     //每20ms扫描一次        
  }
}

十、任务通知

FreeRTOS 从V8.2.0版本开始提供任务通知这个功能,每个任务都有一个32位的通知值,在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件组,也可以替代长度为1的消息队列(可以保存一个 32位整数或指针值)。相对于以前使用 FreeRTOS 内核通信的资源,必须创建队列、二进制信号量、计数信 号量或事件组的情况,使用任务通知显然更灵活

任务通知的使用无需创建队列,想要使用任务通知, 必须将 FreeRTOSConfig.h 中的宏定义 configUSE_TASK_NOTIFICATIONS 设置为 1,其实 FreeRTOS 默认是为 1 的,所以任务通知是默认使能的

 FreeRTOS 提供以下几种方式发送通知给任务 :

     发送通知给任务, 如果有通知未读,不覆盖通知值

     发送通知给任务,直接覆盖通知值

     发送通知给任务,设置通知值的一个或者多个位,可以当做事件组来使用

     发送通知给任务,递增通知值,可以当做计数信号量使用。 通过对以上任务通知方式的合理使用,可以在一定场合下替代 FreeRTOS 的信号量, 队列、事件组等。

有以下限制 :

     只能有一个任务接收通知消息,因为必须指定接收通知的任务

     只有等待通知的任务可以被阻塞,发送通知的任务,在任何情况下都不会因为发送失败而进入阻塞态

1.任务通知实现

1.指定任务通知函数 xTaskNotifyGive()(任务)vTaskNotifyGiveFromISR()(中断)
2.发送任务通知函数 xTaskNotify()(任务)xTaskNotifyFromISR()(中断)
3.发送任务通知并返回上一个任务通知值函数 xTaskNotifyAndQuery()(任务)xTaskNotifyAndQueryFromISR()(中断)
4.获取任务通知函数(二值、计数信号量) ulTaskNotifyTake()
5.等待任务通知函数 xTaskNotifyWait()

2.任务通知在任务中实现


static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区
 
  /* 创建Receive1_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Receive1_Task, /* 任务入口函数 */
                        (const char*    )"Receive1_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,    /* 任务入口函数参数 */
                        (UBaseType_t    )2,        /* 任务的优先级 */
                        (TaskHandle_t*  )&Receive1_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Receive1_Task任务成功!\r\n");
  
  /* 创建Receive2_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Receive2_Task, /* 任务入口函数 */
                        (const char*    )"Receive2_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,    /* 任务入口函数参数 */
                        (UBaseType_t    )3,        /* 任务的优先级 */
                        (TaskHandle_t*  )&Receive2_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Receive2_Task任务成功!\r\n");
  
  /* 创建Send_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Send_Task,  /* 任务入口函数 */
                        (const char*    )"Send_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )4, /* 任务的优先级 */
                        (TaskHandle_t*  )&Send_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Send_Task任务成功!\n\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

static void Receive1_Task(void* parameter)
{    
  while (1)
  {
    /* uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); 
   
 * xClearCountOnExit:pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量
     * pdFALSE 在退出函数ulTaskNotifyTakeO的时候任务通知值减一,类似计数型信号量。
     */
    ulTaskNotifyTake(pdTRUE,portMAX_DELAY);//获取任务通知 ,没获取到则一直等待
    
    printf("Receive1_Task 任务通知获取成功!\n\n");
    
        LED1_TOGGLE;
  }
}
 
static void Receive2_Task(void* parameter)
{    
  while (1)
  {
    /* uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ); 
     * xClearCountOnExit:pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量
     * pdFALSE 在退出函数ulTaskNotifyTakeO的时候任务通知值减一,类似计数型信号量。
     */
    ulTaskNotifyTake(pdTRUE,portMAX_DELAY);//获取任务通知 ,没获取到则一直等待
    
    printf("Receive2_Task 任务通知获取成功!\n\n");
    
        LED2_TOGGLE;
  }
}
 
static void Send_Task(void* parameter)
{     
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  while (1)
  {
    /* KEY1 被按下 */
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      /* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
      xReturn = xTaskNotifyGive(Receive1_Task_Handle);//指定任务通知函数
      /* 此函数只会返回pdPASS */
      if( xReturn == pdTRUE )
        printf("Receive1_Task_Handle 任务通知发送成功!\r\n");
    } 
    /* KEY2 被按下 */
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {
      xReturn = xTaskNotifyGive(Receive2_Task_Handle);//指定任务通知函数
      /* 此函数只会返回pdPASS */
      if( xReturn == pdPASS )
        printf("Receive2_Task_Handle 任务通知发送成功!\r\n");
    }
    vTaskDelay(20);
  }
}

十一、软件定时器

软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制的定时器服务,它实现的功能与硬件定时器也是类似的,使用软件定时器时,需要我们在创建软件定时器时指定时间到达后要调用的函数(也称超时函数/回调函数(任务)),在回调函数中处理信息

FreeRTOS 软件定时器功能上支持:

 裁剪:能通过宏关闭软件定时器功能。

 软件定时器创建。

 软件定时器启动。

 软件定时器停止。

 软件定时器复位。

 软件定时器删除。

      FreeRTOS 提供的软件定时器支持单次模式和周期模式,单次模式和周期模式的定时时 间到之后都会调用软件定时器的回调函数,用户可以在回调函数中加入要执行的工程代码。

     单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数之后就将该定时器进入休眠状态,不再重新执行。

     周期模式:这个定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器删除。

FreeRTOS通过一个 prvTimerTask 任务(也叫守护任务 Daemon)管理软定时器,它是在启动调度器时自动创建的,为了满足用户定时需求,prvTimerTask 任务会在其执行期间检查用户启动的时间周期溢出的定时器,并调用其回调函数。只有设置 FreeRTOSConfig.h 中的宏定义 configUSE_TIMERS 设置为 1 ,将相关代码编译进来,才能正常使用软件定时器相关功能,软件定时器以系统节拍周期为计时单位。系统节拍是系统的心跳节拍,表示系统时钟的频率,就类似人的心跳,1s 能跳动多少下,系统节拍配置为 configTICK_RATE_HZ,该宏在 FreeRTOSConfig.h 中有定义,默认是 1000。那么系统的时 钟节拍周期就为 1ms(1s跳动 1000 下,每一下就为 1ms)

      软件定时器是可选的系统资源,在创建定时器的时候会分配一块内存空间。当用户创建并启动一个软件定时器时, FreeRTOS 会根据当前系统时间及用户设置的定时确定该定时器唤醒时间,并将该定时器控制块挂入软件定时器列表,FreeRTOS 中采用两个定时器列 表维护软件定时器,pxCurrentTimerListpxOverflowTimerList 是列表指针,在初始化的时 候分别指向 xActiveTimerList1xActiveTimerList2

软件定时器用到的列表:

 PRIVILEGED_DATA static List_t xActiveTimerList1;
 PRIVILEGED_DATA static List_t xActiveTimerList2;
 PRIVILEGED_DATA static List_t *pxCurrentTimerList;
 PRIVILEGED_DATA static List_t *pxOverflowTimerList;

xCurrentTimerList:系统新创建并激活的定时器都会以超时时间升序的方式插入到 pxCurrentTimerList 列表中。系统在定时器任务中扫描 pxCurrentTimerList 中的第一个定时 器,看是否已超时,若已经超时了则调用软件定时器回调函数。否则将定时器任务挂起, 因为定时时间是升序插入软件定时器列表的,列表中第一个定时器的定时时间都还没到的话,那后面的定时器定时时间自然没到

pxOverflowTimerList :是在软件定时器溢出的时候使用,作用与 pxCurrentTimerList 一致

1.系统时钟运行

系统在不断运行,而 xTimeNow(xTickCount) 随着 SysTick 的触发一直在增长(每一次硬件定时器中断来临,xTimeNow 变量会加 1), 在软件定时器任务运行的时候会获取下一个要唤醒的定时器,比较当前系统时间 xTimeNow 是否大于或等于下一个定时器唤醒时间 xTicksToWait,若大于则表示已经超时, 定时器任务将会调用对应定时器的回调函数,否则将软件定时器任务挂起,直至下一个要唤醒的软件定时器时间到来或者接收到命令消息

使用软件定时器时候要注意以下几点:

     软件定时器的回调函数中应快进快出,绝对不允许使用任何可能引软件定时器起任务挂起或者阻塞的API接口,在回调函数中也绝对不允许出现死循环。

     软件定时器使用了系统的一个队列和一个任务资源,软件定时器任务的优先级默认为configTIMER_TASK_PRIORITY,为了更好响应,该优先级应设置为所有任 务中最高的优先级,创建单次软件定时器,该定时器超时执行完回调函数后,系统会自动删除该软件 定时器,并回收资源

      定时器任务的堆栈大小默认为 configTIMER_TASK_STACK_DEPTH 个字节。

2.软件定时器实现(创建、启动、停止、任务、删除)

1.软件定时器创建函数 xTimerCreate()
2.软件定时器启动函数 xTimerStart()
3.软件定时器停止函数  xTimerStop()
4. 软件定时器任务 xTimerCreateTimerTask()
5.软件定时器删除函数 xTimerDelete()

3.软件定时器在任务中实现

static TimerHandle_t Swtmr1_Handle =NULL; /* 软件定时器句柄 */

static TimerHandle_t Swtmr2_Handle =NULL; /* 软件定时器句柄 */

static uint32_t TmrCb_Count1 = 0; /* 记录软件定时器1回调函数执行次数 */

static uint32_t TmrCb_Count2 = 0; /* 记录软件定时器2回调函数执行次数 */

static void AppTaskCreate(void)
{
  taskENTER_CRITICAL();           //进入临界区
    
  /************************************************************************************
   * 创建软件周期定时器
   * 函数原型
   * TimerHandle_t xTimerCreate(    const char * const pcTimerName,
                                const TickType_t xTimerPeriodInTicks,
                                const UBaseType_t uxAutoReload,
                                void * const pvTimerID,
                TimerCallbackFunction_t pxCallbackFunction )
    * @uxAutoReload : pdTRUE为周期模式,pdFALS为单次模式
   * 单次定时器,周期(1000个时钟节拍),周期模式
   *************************************************************************************/
  Swtmr1_Handle=xTimerCreate((const char*        )"AutoReloadTimer",
                            (TickType_t            )1000,/* 定时器周期 1000(tick) */
                            (UBaseType_t        )pdTRUE,/* 周期模式 */
                            (void*                  )1,/* 为每个计时器分配一个索引的唯一ID */
                            (TimerCallbackFunction_t)Swtmr1_Callback); //创建软件定时器

  if(Swtmr1_Handle != NULL)                          
  {
    /***********************************************************************************
     * xTicksToWait:如果在调用xTimerStart()时队列已满,则以tick为单位指定调用任务应保持
     * 在Blocked(阻塞)状态以等待start命令成功发送到timer命令队列的时间。 
     * 如果在启动调度程序之前调用xTimerStart(),则忽略xTicksToWait。在这里设置等待时间为0.
     **********************************************************************************/
    xTimerStart(Swtmr1_Handle,0);    //开启周期定时器
  }                            
  /************************************************************************************
   * 创建软件周期定时器
   * 函数原型
   * TimerHandle_t xTimerCreate(    const char * const pcTimerName,
                                const TickType_t xTimerPeriodInTicks,
                                const UBaseType_t uxAutoReload,
                                void * const pvTimerID,
                TimerCallbackFunction_t pxCallbackFunction )
    * @uxAutoReload : pdTRUE为周期模式,pdFALS为单次模式
   * 单次定时器,周期(5000个时钟节拍),单次模式
   *************************************************************************************/
    Swtmr2_Handle=xTimerCreate((const char*            )"OneShotTimer",
                             (TickType_t            )5000,/* 定时器周期 5000(tick) */
                             (UBaseType_t            )pdFALSE,/* 单次模式 */
                             (void*                      )2,/* 为每个计时器分配一个索引的唯一ID */
                             (TimerCallbackFunction_t)Swtmr2_Callback); //创建软件定时器

  if(Swtmr2_Handle != NULL)
  {
   /***********************************************************************************
   * xTicksToWait:如果在调用xTimerStart()时队列已满,则以tick为单位指定调用任务应保持
   * 在Blocked(阻塞)状态以等待start命令成功发送到timer命令队列的时间。 
   * 如果在启动调度程序之前调用xTimerStart(),则忽略xTicksToWait。在这里设置等待时间为0.
   **********************************************************************************/   
    xTimerStart(Swtmr2_Handle,0);    //开启周期定时器
  } 
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}


static void Swtmr1_Callback(void* parameter)
{        
  TickType_t tick_num1;
 
  TmrCb_Count1++;                        /* 每回调一次加一 */
 
  tick_num1 = xTaskGetTickCount();    /* 获取滴答定时器的计数值 */
  
  LED1_TOGGLE;
  
  printf("Swtmr1_Callback函数执行 %d 次\n", TmrCb_Count1);
  printf("滴答定时器数值=%d\n", tick_num1);
}
 
static void Swtmr2_Callback(void* parameter)
{    
  TickType_t tick_num2;
 
  TmrCb_Count2++;                        /* 每回调一次加一 */
 
  tick_num2 = xTaskGetTickCount();    /* 获取滴答定时器的计数值 */
 
  printf("Swtmr2_Callback函数执行 %d 次\n", TmrCb_Count2);
  printf("滴答定时器数值=%d\n", tick_num2);
}

十二、内存管理(静态内存管理、动态内存管理)

 FreeRTOS 操作系统将内核与内存管理分开实现,操作系统内核仅规定了必要的内存管理函数原型,而不关心这些内存管理函数是如何实现的,所以在 FreeRTOS 中提供了多种内存分配算法(分配策略),但是上层接口(API)却是统一的。这样做可以增加系统的灵活性:用户可以选择对自己更有利的内存管理策略,在不同的应用场合使用不同的内存分配策略

嵌入式程序设计中内存分配应该是根据所设计系统的特点来决定选择使用动态内存分配还是静态内存分配算法

可靠性要求非常高的系统应选择使用静态的,而普通的业务系统可以使用动态来提高内存使用效率。静态可以保证设备的可靠性但是需要考虑内存上限,内存使用效率低,而动态则是相反

FreeRTOS 内存管理模块管理用于系统中内存资源,它是操作系统的核心模块之一。主 要包括内存的初始化、分配以及释放

为什么不直接使用 C 标准库中的内存管理函数呢?在电脑中我们可以用 malloc()和 free()这两个函数动态的分配内存和释放内存。但是,在嵌入式实时操作系统中,调用 malloc()和 free()却是危险的,原因有以下几点:

 1.这些函数在小型嵌入式系统中并不总是可用的,小型嵌入式设备中的 RAM 不足。

 2.它们的实现可能非常的大,占据了相当大的一块代码空间。

 3.他们几乎都不是安全的。

 4.它们并不是确定的,每次调用这些函数执行的时间可能都不一样。

 5. 它们有可能产生碎片。

 6. 这两个函数会使得链接器配置得复杂。

 7.如果允许堆空间的生长方向覆盖其他变量占据的内存,它们会成为debug的灾难。

嵌入式实时操作系统中,对内存的分配时间要求更为苛刻,分配内存的时间 必须是确定的。一般内存管理算法是根据需要存储的数据的长度在内存中去寻找一个与这段数据相适应的空闲内存块,然后将数据存储在里面。而寻找这样一个空闲内存块所耗费的时间是不确定的,因此对于实时系统来说,这就是不可接受的,实时系统必须要保证内存块的分配过程在可预测的确定时间内完成,否则实时任务对外部事件的响应也将变得不可确定

FreeRTOS 对内存管理做了很多事情,FreeRTOS 的 V9.0.0 版本为我们提供了 5 种内存 管理算法,分别是 heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c,源文件存放于 FreeRTOS\Source\portable\MemMang 路径下,在使用的时候选择其中一个添加到我们的工 程中去即可

1.内存管理应用场景

内存管理的主要工作是动态划分并管理用户分配好的内存区间,主要是在用户需要使用大小不等的内存块的场景中使用,当用户需要分配内存时,可以通过操作系统的内存申 请函数索取指定大小内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用(heap_1.c 的内存管理除外)

2.heap_4.c(常用内存管理)

内存分配时需要的总的堆空间由文件FreeRTOSConfig.h 中的宏configTOTAL_HEAP_SIZE配置,单位为字。通过调用函数 xPortGetFreeHeapSize() 我们可 以知道还剩下多少内存没有使用,但是并不包括内存碎片。这样一来我们可以实时的调整 和优化 configTOTAL_HEAP_SIZE 的大小

heap_4.c 方案的空闲内存块以单链表的形式连接起来的,BlockLink_t 类型的局部静态变量 xStart 表示链表头,但 heap_4.c 内存管理方案的链表尾部则保存在内存堆空间最 后位置,并使用BlockLink_t 指针类型局部静态变量 pxEnd 指向这个区域(而 heap_2.c 内 存管理方案则使用 BlockLink_t 类型的静态变量 xEnd 表示链表尾)

      heap_4.c 内存管理方案的空闲块链表不是以内存块大小进行排序的,而是以内存块起始地址大小排序,内存地址小的在前,地址大的在后,因为 heap_4.c 方案还有一个内存合并算法,在释放内存的时候,假如相邻的两个空闲内存块在地址上是连续的,那么就可以合并为一个内存块,这也是为了适应合并算法而作的改变

      heap_4.c 方案具有以下特点:

     1、可用于重复删除任务、队列、信号量、互斥量等的应用程序

     2、可用于分配和释放随机字节内存的应用程序,但并不像 heap2.c 那样产生严重的内 存碎片

     3、具有不确定性,但是效率比标准C库中的malloc函数高得多

3.内存管理实现

1.内存申请函数 pvPortMalloc()

2.内存释放函数 vPortFree()

3.内存剩余获取函数 xPortGetFreeHeapSize()

4.内存管理在任务中实现

static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区
  
  /* 创建Test_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Test_Task,  /* 任务入口函数 */
                        (const char*    )"Test_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&Test_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Test_Task任务成功\n\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}

static void Test_Task(void* parameter)
{     
  uint32_t g_memsize;
  while (1)
  {
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
    {
      /* KEY1 被按下 */
      if(NULL == Test_Ptr)
      {
                  
        /* 获取当前内存大小 */
        g_memsize = xPortGetFreeHeapSize();//获取剩余内存大小
        printf("系统当前内存大小为 %d 字节,开始申请内存\n",g_memsize);
        Test_Ptr = pvPortMalloc(1024);//申请内存大小
        if(NULL != Test_Ptr)
        {
          printf("内存申请成功\n");
          printf("申请到的内存地址为%#x\n",(int)Test_Ptr);
 
          /* 获取当前内剩余存大小 */
          g_memsize = xPortGetFreeHeapSize();//获取剩余内存大小
          printf("系统当前内存剩余存大小为 %d 字节\n",g_memsize);
                  
          //向Test_Ptr中写入当数据:当前系统时间
          sprintf((char*)Test_Ptr,"当前系统TickCount = %d \n",xTaskGetTickCount());
          printf("写入的数据是 %s \n",(char*)Test_Ptr);
        }
      }
      else
      {
        printf("请先按下KEY2释放内存再申请\n");
      }
    } 
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
    {
      /* KEY2 被按下 */
      if(NULL != Test_Ptr)
      {
        printf("释放内存\n");
        vPortFree(Test_Ptr);    //释放内存
        Test_Ptr=NULL;
        /* 获取当前内剩余存大小 */
        g_memsize = xPortGetFreeHeapSize();获取剩余内存大小
        printf("系统当前内存大小为 %d 字节,内存释放完成\n",g_memsize);
      }
      else
      {
        printf("请先按下KEY1申请内存再释放\n");
      }
    }
    vTaskDelay(20);/* 延时20个tick */
  }
}

十三、中断管理

FreeRTOS 的中断管理支持:

1.开/关中断。

2.恢复中断。

3.中断使能。

4.中断屏蔽。

5.可选择系统管理的中断优先级。

与中断相关的硬件可以划分为三类:外设、中断控制器、CPU本身

外设:当外设需要请求CPU时,产生一个中断信号,该信号连接至中断控制器。

中断控制器:中断控制器是CPU众多外设中的一个,它一方面接收其他外设中断信号 的输入,另一方面,它会发出中断信号给 CPU。可以通过对中断控制器编程实现对中断源 的优先级、触发方式、打开和关闭源等设置操作。在 Cortex-M 系列控制器中常用的中断控 制器是 NVIC(内嵌向量中断控制器 Nested Vectored Interrupt Controller)。

CPU:CPU会响应中断源的请求,中断当前正在执行的任务,转而执行中断处理程序。 NVIC 最多支持 240个中断,每个中断最多 256 个优先级。

1.中断处理流程

当中断产生时,处理机将按如下的顺序执行:

1. 保存当前处理机状态信息

2. 载入异常或中断处理函数到PC寄存器

3. 把控制权转交给处理函数并开始执行

4. 当处理函数执行完成时,恢复处理器状态信息

5. 从异常或中断中返回到前一个程序执行点

 ARM Cortex-M系列内核的中断是由硬件管理的,而FreeRTOS是软件,它并不接管由硬件管理的相关中断(接管简单来说就是,所有的中断都由RTOS的软件管理,硬件来了中断时,由软件决定是否响应,可以挂起中断,延迟响应或者不响应),只支持简单的开关中断等,所以 FreeRTOS 中的中断使用其实跟裸机差不多的,需要我们自己配置中断,并且使能中断,编写中断服务函数,在中断服务函数中使用内核IPC通信机制,一般建议使用信号量、消息队列或事件标志组等标志事件的发生,将事件发布给处理任务,等退出中断后再由相关处理任务具体处理中断

用户可以自定义配置系统可管理的最高中断优先级的宏定义configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY ,它是用于配置内核中的basepri寄存器(优先级、抢占优先级个数配置)的,当 basepri 设置为某个值的时候,NVIC 不会响应比该优先级低的中断, 而优先级比之更高的中断则不受影响。就是说当这个宏定义配置为 5 的时候,中断优先级 数值在 0、1、2、3、4 的这些中断是不受FreeRTOS屏蔽的,也就是说即使在系统进入临界段的时候,这些中断也能被触发而不是等到退出临界段的时候才被触发,当然,这些中 断服务函数中也不能调用 FreeRTOS 提供的 API 函数接口,而中断优先级在5到15的这些中断是可以被屏蔽的,也能安全调用FreeRTOS提供的 API 函数接口。

ARM Cortex-M系列处理器上,所有中断都采用中断向量表的方式进行处理, 即当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳转到相应的固定位置进行处理,每个中断服务例程必须排列在一起放在统一的地址上(这个地址必须要设置到 NVIC 的中断向量偏移寄存器中)。中断向量表一般由一个 数组定义(或在起始代码中给出)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/441093.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue2电商项目(七)、订单与支付

文章目录 一、交易业务Trade1. 获取用户地址2. 获取订单信息 二、提交订单三、支付1. 获取支付信息2. 支付页面--ElementUI(1) 引入Element UI(2) 弹框支付的业务逻辑(这个逻辑其实没那么全)(3) 支付逻辑知识点小总结 四、个人中心1. 搭建二级路由2. 展示动态数据(1). 接口(2).…

【计算机网络 - 基础问题】每日 3 题(二十九)

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞…

【Docker】03-自制镜像

1. 自制镜像 2. Dockerfile # 基础镜像 FROM openjdk:11.0-jre-buster # 设定时区 ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # 拷贝jar包 COPY docker-demo.jar /app.jar # 入口 ENTRYPOINT ["ja…

Redis:通用命令 数据类型

Redis&#xff1a;通用命令 & 数据类型 通用命令SETGETKEYSEXISTSDELEXPIRETTLTYPEFLUSHALL 数据类型 Redis的客户端提供了很多命令用于操控Redis&#xff0c;在Redis中&#xff0c;key的类型都是字符串&#xff0c;而value有多种类型&#xff0c;每种类型都有自己的操作命…

Redis篇(最佳实践)(持续更新迭代)

介绍一&#xff1a;键值设计 一、优雅的key结构 Redis 的 Key 虽然可以自定义&#xff0c;但最好遵循下面的几个最佳实践约定&#xff1a; 遵循基本格式&#xff1a;[业务名称]:[数据名]:[id]长度不超过 44 字节不包含特殊字符 例如&#xff1a; 我们的登录业务&#xff0…

Leetcode—76. 最小覆盖子串【困难】

2024每日刷题&#xff08;167&#xff09; Leetcode—76. 最小覆盖子串 C实现代码 class Solution { public:string minWindow(string s, string t) {int bestL -1;int l 0, r 0;vector<int> cnt(128);for(const char c: t) {cnt[c];}int require t.length();int m…

【实战教程】SpringBoot全面指南:快速上手到项目实战(SpringBoot)

文章目录 【实战教程】SpringBoot全面指南&#xff1a;快速上手到项目实战(SpringBoot)1. SpringBoot介绍1.1 SpringBoot简介1.2系统要求1.3 SpringBoot和SpringMVC区别1.4 SpringBoot和SpringCloud区别 2.快速入门3. Web开发3.1 静态资源访问3.2 渲染Web页面3.3 YML与Properti…

[SpringBoot] 苍穹外卖--面试题总结--上

前言 1--苍穹外卖-SpringBoot项目介绍及环境搭建 详解-CSDN博客 2--苍穹外卖-SpringBoot项目中员工管理 详解&#xff08;一&#xff09;-CSDN博客 3--苍穹外卖-SpringBoot项目中员工管理 详解&#xff08;二&#xff09;-CSDN博客 4--苍穹外码-SpringBoot项目中分类管理 详…

pytest(六)——allure-pytest的基础使用

前言 一、allure-pytest的基础使用 二、需要掌握的allure特性 2.1 Allure报告结构 2.2 Environment 2.3 Categories 2.4 Flaky test 三、allure的特性&#xff0c;allure.step()、allure.attach的详细使用 3.1 allure.step 3.2 allure.attach&#xff08;挺有用的&a…

Redis入门第四步:Redis发布与订阅

欢迎继续跟随《Redis新手指南&#xff1a;从入门到精通》专栏的步伐&#xff01;在本文中&#xff0c;我们将深入探讨Redis的发布与订阅&#xff08;Pub/Sub&#xff09;模式。这是一种强大的消息传递机制&#xff0c;适用于各种实时通信场景&#xff0c;如聊天应用、实时通知和…

3、Redis Stack扩展功能

文章目录 一、了解Redis产品二、申请RedisCloud实例三、Redis Stack体验1、RedisStack有哪些扩展&#xff1f;2、Redis JSON1、Redis JSON是什么2、Redis JSON有什么用3、Redis JSON的优势 3、Search And Query1、传统Scan搜索2、Search And Query搜索 4、Bloom Filter1、布隆过…

LabVIEW提高开发效率技巧----阻塞时钟

在LabVIEW开发中&#xff0c;阻塞时钟&#xff08;Blocking Timed Loops&#xff09;是一种常见且强大的技术&#xff0c;尤其适用于时间关键的应用。在这些应用中&#xff0c;精确控制循环的执行频率是关键任务。阻塞时钟通过等待循环的执行完成后再进入下一次迭代&#xff0c…

如何设置LTE端到端系统

LTE Setup Guide Baseline Hardware Requirements 基础硬件要求 需要2个RF前端和2个装有基于Linux的操作系统的PC。系统架构如下&#xff1a; srsUE&#xff1a;需要1个RF前端和1个PC。srsENB&#xff1a;需要1个RF前端和1个PC。srsEPC&#xff1a;需要1个PC。 系统硬件要…

python实现RC4加解密算法

RC4算法 一、算法介绍1.1 背景1.2 密钥调度算法(KSA)1.3 伪随机生成算法(PRGA) 二、代码实现三、演示效果 一、算法介绍 1.1 背景 RC4算法是由Ron Rivest在1987年为RSA数据安全公司设计的一种流密码算法&#xff0c;其安全性主要依赖于其密钥流的随机性和不可预测性。该算法因…

碰撞检测 | 图解视线生成Bresenham算法(附ROS C++/Python/Matlab实现)

目录 0 专栏介绍1 Bresenham算法介绍2 图解Bresenham算法3 算法流程4 仿真实现4.1 ROS C实现4.2 Python实现4.3 Matlab实现 0 专栏介绍 &#x1f525;课设、毕设、创新竞赛必备&#xff01;&#x1f525;本专栏涉及更高阶的运动规划算法轨迹优化实战&#xff0c;包括&#xff…

架构设计之解析CQRS架构模式!

文章首发到公众号&#xff1a;月伴飞鱼 文章内容收录到个人网站&#xff0c;方便阅读&#xff1a;http://hardyfish.top/ 文章内容收录到个人网站&#xff0c;方便阅读&#xff1a;http://hardyfish.top/ 文章内容收录到个人网站&#xff0c;方便阅读&#xff1a;http://har…

【可视化大屏】Python Flask框架介绍

为了能显示真实数据&#xff0c;使用flask快速搭建了一个web应用&#xff0c;然后连接数据库&#xff0c;读取数据库里的数据来进行大屏可视化显示&#xff08;btw&#xff1a;数据是从车主之家网站上爬虫爬的&#xff09; 家人们&#xff01;记得使用专业版的pycharm&#xf…

保证文件只能在公司打开,走出公司就打不开这一神操作如何实现?一文告诉你详情!

在现代企业中&#xff0c;信息安全已经成为一项至关重要的任务。随着企业数据量的不断增加&#xff0c;如何确保敏感信息不被泄露成为企业面临的重要挑战。 其中&#xff0c;一种常见的需求是确保文件只能在公司内部环境中打开&#xff0c;一旦离开公司就无法访问。 本文将详…

计算机组成原理实验三 数据寄存器组R0..R3, MAR, ST, OUT

实验目的和要求 目的&#xff1a;了解模型机中各种寄存器结构、工作原理及其控制方法。 要求&#xff1a;利用CP226 实验系统上的K16..K23 开关做为DBUS 的数据&#xff0c;其它开关做为控制信号&#xff0c;将数据写入寄存器&#xff0c;数据寄存器组R0..R3&#xff0c;地址…

stm32开发环境的配置

keli5的安装 安装上以后&#xff0c;用管理员身份打开软件 复制里面的CID到破解软件里面 将Target调到ARM&#xff0c;然后生成 将注册码复制进软件那个界面&#xff0c;然后AddLIC就破解成功了 调试工具STLink驱动的安装 如果发现带感叹号代表驱动没有安装&#xff0c;但是设…