RTOS系统移植

news2024/10/8 14:02:20

一、完成系统移植

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

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

三、创建主函数,用于存放硬件初始化(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.coloradmin.cn/o/2196514.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Stable Diffusion绘画 |,IP角色多视图生成技巧(附插件模型)

在游戏设计、小说推文、角色设计里面&#xff0c;很多场景都运用到IP角色的多视图。 人物角色多视图 第1步&#xff0c;输入提示词&#xff1a; 第2步&#xff0c;由于要在同一张图片中生成多角度的并排展示&#xff0c;需要修改图片的分辨率&#xff08;尤其是宽度&#xff…

开源问答类知识付费网站源码系统 带完整的安装代码包以及搭建部署教程

系统概述 近年来&#xff0c;随着互联网的飞速发展&#xff0c;知识付费市场呈现出爆炸式增长。各大知识付费平台如雨后春笋般涌现&#xff0c;涵盖了从教育、科技到生活娱乐等各个领域。用户通过付费获取高质量的知识内容&#xff0c;而内容创作者则通过分享知识获得经济回报…

大模型应用探讨,免费AI写作、一键PPT、免费PDF百种应用、与AI对话

大模型应用平台知识普及, 应用可见评论区 我们生活在一个充满无限可能的数字时代&#xff0c;人工智能技术正在推动着各种创新的边界。大模型应用平台一般包含以下功能。 ## 1. 一键生成论文 写作是学生、研究人员和职场人士都无法避免的任务。大模型应用平台拥有强大的文本生…

如何让算法拥有“记忆”?一文读懂记忆化搜索

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一 什么是记忆化搜索 二 相关题目练习 2.1 斐波那契数&#xff08;详解记忆化搜索&#xff09; ​编辑 解法一&#xff08;递归&#xff09;&#xff1a; 解法二&#xff08;记…

全面整理人工智能(AI)学习路线图及资源推荐,非常详细收藏我这一篇就够了

在人工智能&#xff08;AI&#xff09;飞速发展的今天&#xff0c;掌握AI技术已经成为了许多高校研究者和职场人士的必备技能。从深度学习到强化学习&#xff0c;从大模型训练到实际应用&#xff0c;AI技术的广度和深度不断拓展。作为一名AI学习者&#xff0c;面对浩瀚的知识海…

kafka的成神秘籍(java)

kafka的成神秘籍 kafka的简介 ​ Kafka 最初是由Linkedin 即领英公司基于Scala和 Java语言开发的分布式消息发布-订阅系统&#xff0c;现已捐献给Apache软件基金会。Kafka 最被广为人知的是作为一个 消息队列(mq)系统存在&#xff0c;而事实上kafka已然成为一个流行的分布式流…

【吊打面试官系列-MySQL面试题】试述视图的优点?

大家好&#xff0c;我是锋哥。今天分享关于【试述视图的优点&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 试述视图的优点&#xff1f; (1) 视图能够简化用户的操作 (2) 视图使用户能以多种角度看待同一数据&#xff1b; (3) 视图为数据库提供了一定程度的…

8年JAVA逆袭转AI之路!成功拿下offer

前段时间有一个粉丝投稿&#xff0c;他是8年老Java程序员了&#xff0c;每天两小时的碎片化学习时间&#xff0c;不仅没有陷入程序员的年龄恐慌&#xff0c;还拿到了目前薪资翻倍的offer 问到他是什么让他坚持学了6个月&#xff0c;他用了华为总裁任正非说的“今后职场上只有…

Nginx03-使用

零、文章目录 Nginx03-使用 1、Nginx服务器启停命令 对于 Nginx 的启停在 Linux 系统中也有很多种方式&#xff0c;我们介绍两种方式&#xff1a; Nginx信号控制Nginx命令行控制 &#xff08;1&#xff09;Nginx信号控制 查看Nginx 中的 master 和 worker 进程 [rootloc…

计算机进制之间的关系

计算机中常见的进制 十进制、二进制、十六进制、八进制之间对照表 进制之间的转换 通过上面的十进制对应二进制进位的表示&#xff1a; 当二进制产生增加位数时&#xff0c;相对应十进制数为2、4、8、16、32、64、128&#xff0c;也被称为二进制的位权&#xff0c;根据规律可知…

linux中缓存,在kafka上应用总结

linux中的缓存 页缓存 pagecatch&#xff08;读缓存用于提供快速读&#xff09;块缓存&#xff08;用于提供其他设备快速写&#xff09;当对读缓存读的时候&#xff0c;修改了读的数据&#xff0c;页缓存就会被标记为脏数据&#xff0c;等到写的时候它会向块缓存同步数据&…

关于7zip解压缩的下载和使用

我们有的时候下载软件&#xff0c;后缀是 ".exe" 或者 “.zip”&#xff0c;".7z"等&#xff0c;".exe"文件还好&#xff0c;打开就能进行下载&#xff0c;但是“.zip”&#xff0c;".7z“等就需要用解压缩软件进行解压了。 今天介绍的解…

No.11 笔记 | PHP学习指南:从函数到面向对象概览

一、PHP函数&#xff1a;代码复用的艺术 1. 函数的本质与魅力 函数是PHP的核心力量&#xff0c;分为内置函数和自定义函数函数名应当简洁明了&#xff0c;以字母或下划线开头 2. 函数的构成要素 function 关键字&#xff1a;函数的开始标志函数名&#xff1a;您的函数的独特…

【Git原理与使用】远程操作标签管理

远程操作&&标签管理 1.理解分布式版本控制系统2.新建远程仓库3.克隆远程仓库4.向远程仓库推送5.拉取远程仓库6.配置 Git7.配置命令别名8.标签管理8.1创建标签8.2操作标签 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496;…

把当抠门程序员,遇到了免费AI大模型

这篇想和大家分享一下&#xff0c;一个抠门的程序员和一个免费的AI大模型的故事。 “抠门程序员<–>免费大模型”&#xff0c;让我看看&#xff0c;能不能擦出马内的火花。 故事的开始 不知道有没有程序员和我一样&#xff0c;付费的东西&#xff0c;都会省着点开。什…

远程访问服务是什么?如何通过节点小宝远程访问办公室电脑?

在家办公若能各安其位、高效完成任务&#xff0c;实为美事。然而&#xff0c;现实往往不尽如人意&#xff0c;偶尔需用到办公室电脑上的资料&#xff0c;这时便需依赖远程访问服务的助力。那么&#xff0c;远程访问服务究竟是何方神圣&#xff1f;又该如何借助节点小宝实现对办…

解锁空间距离计算的多种方式-含前端、空间数据库、后端

目录 前言 一、空间数据库求解 1、PostGIS实现 二、GIS前端组件求解 1、Leaflet.js距离测算 2、Turf.js前端计算 三、后台距离计算生成 1、欧式距离 2、Haversice球面距离 3、GeoTools距离计算 4、Gdal距离生成 5、geodesy距离计算 四、成果与生成对比 1、Java不…

CSRF | POST 型 CSRF 漏洞攻击

关注这个漏洞的其他相关笔记&#xff1a;CSRF 漏洞 - 学习手册-CSDN博客 0x01&#xff1a;POST 型 CSRF 漏洞攻击 —— 理论篇 POST 型 CSRF 漏洞是指攻击者通过构造恶意的 HTTP POST 请求&#xff0c;利用用户的登录状态&#xff0c;在用户不知情的情况下&#xff0c;诱使浏览…

Mythical Beings:Web3游戏如何平衡创造内容、关注度与实现盈利的不可能三角

Web3游戏自其诞生以来&#xff0c;以去中心化和独特的代币经济体系迅速引起关注。然而&#xff0c;如何在创造内容、吸引用户和实现盈利之间达到平衡&#xff0c;始终是Web3游戏面临的核心挑战。Mythical Beings作为一款Web3卡牌游戏&#xff0c;通过创新设计和独特机制&#x…

java集合框架都有哪些

Java集合框架&#xff08;Java Collections Framework&#xff09;是Java提供的一套设计良好的支持对一组对象进行操作的接口和类。这些接口和类定义了如何添加、删除、遍历和搜索集合中的元素。Java集合框架主要包括以下几个部分&#xff1a; 接口&#xff1a; Collection&…