RTOS队列的创建、写入与读取

我的理解:其实队列的本质就是一个数组,但是可以在多任务中读取,写入数据。而环形数组就不能这样子,会出现一些错误。

队列服从先进先出的的顺序,读取和写入顺序是一模一样的。

使用队列传输数据时有两种方法:

⚫ 拷贝:把数据、把变量的值复制进队列里

⚫ 引用:把数据、把变量的地址复制进队列里

FreeRTOS 使用拷贝值的方法,这更简单:

⚫ 局部变量的值可以发送到队列中,后续即使函数退出、局部变量被回收,也

不会影响队列中的数据

⚫ 无需分配 buffer 来保存数据,队列中有 buffer

⚫ 局部变量可以马上再次使用

⚫ 发送任务、接收任务解耦:接收任务不需要知道这数据是谁的、也不需要发

送任务来释放数据

⚫ 如果数据实在太大,你还是可以使用队列传输它的地址

⚫ 队列的空间有 FreeRTOS 内核分配,无需任务操心

⚫ 对于有内存保护功能的系统,如果队列使用引用方法,也就是使用地址,必

须确保双方任务对这个地址都有访问权限。使用拷贝方法时,则无此限制:内核

有足够的权限,把数据复制进队列、再把数据复制出队列。

 队列的阻塞访问

只要知道队列的句柄,谁都可以读、写该队列。任务、ISR 都可读、写队列。可以多个

任务读写队列。

任务读写队列时,简单地说:如果读写不成功,则阻塞;可以指定超时时间。口语化地

说,就是可以定个闹钟:如果能读写了就马上进入就绪态,否则就阻塞直到超时。

某个任务读队列时,如果队列没有数据,则该任务可以进入阻塞状态:还可以指定阻塞

的时间。如果队列有数据了,则该阻塞的任务会变为就绪态。如果一直都没有数据,则时间 到之后它也会进入就绪态。

既然读取队列的任务个数没有限制,那么当多个任务读取空队列时,这些任务都会进入

阻塞状态:有多个任务在等待同一个队列的数据。当队列中有数据时,哪个任务会进入就绪 态?

⚫ 优先级最高的任务

⚫ 如果大家的优先级相同,那等待时间最久的任务会进入就绪态

跟读队列类似,一个任务要写队列时,如果队列满了,该任务也可以进入阻塞状态:还 可以指定阻塞的时间。如果队列有空间了,则该阻塞的任务会变为就绪态。如果一直都没有 空间,则时间到之后它也会进入就绪态。

既然写队列的任务个数没有限制,那么当多个任务写"满队列"时,这些任务都会进入阻 塞状态:有多个任务在等待同一个队列的空间。当队列中有空间时,哪个任务会进入就绪态?

⚫ 优先级最高的任务

⚫ 如果大家的优先级相同,那等待时间最久的任务会进入就绪态

队列的API函数

创建队列

QueueHandle_t        QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

uxQueueLength                  队列长度,最多能存放多少个数据(item)

uxItemSize                          每个数据(item)的大小:以字节为单位

返回值

非 0:成功,返回句柄,以后使用句柄来操作队列

NULL:失败,因为内存不足

删除队列的函数为

vQueueDelete(),只能删除使用动态方法创建的队列,它会释放内存。

原型如下:void vQueueDelete( QueueHandle_t xQueue );传入参数为队列句柄

写入队列函数

xQueue     队列句柄

pvItemToQueue          数据指针,这个数据的值会被复制进队列, 复制多大的数据?在创建队列时已经指定了数据大小 .

xTicksToWait如果队列满则无法写入新数据,可以让任务进入阻塞状态,

xTicksToWait 表示阻塞的最大时间(Tick Count)。

如果被设为 0,无法写入数据时函数会立刻返回;

如果被设为 portMAX_DELAY,则会一直阻塞直到有空间可写。

返回值

pdPASS:数据成功写入了队列

errQUEUE_FULL:写入失败,因为队列满了。

/* 等同于xQueueSendToBack

* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait

*/

BaseType_t xQueueSend(

QueueHandle_t       xQueue,

const void              *pvItemToQueue,

TickType_t              xTicksToWait

);

 

/*

* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait

*/

BaseType_t xQueueSendToBack(

QueueHandle_t              xQueue,

const void                       *pvItemToQueue,

TickType_t                       xTicksToWait

);

 

/*

* 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞

*/

BaseType_t xQueueSendToBackFromISR(

QueueHandle_t xQueue,

const void *pvItemToQueue,

BaseType_t *pxHigherPriorityTaskWoken

);

 

/*

* 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait

*/

BaseType_t xQueueSendToFront(

QueueHandle_t xQueue,

const void *pvItemToQueue,

TickType_t xTicksToWait

);

 

/*

* 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞

*/

BaseType_t xQueueSendToFrontFromISR(

QueueHandle_t xQueue,

const void *pvItemToQueue,

BaseType_t *pxHigherPriorityTaskWoken

);

读取队列函数

BaseType_t xQueueReceive(

QueueHandle_t        xQueue,

void * const             pvBuffer,

TickType_t               xTicksToWait );

BaseType_t xQueueReceiveFromISR(

QueueHandle_t        xQueue,

void                         *pvBuffer,

BaseType_t             *pxTaskWoken

);

xQueue     队列句柄,要读哪个队列

pvBuffer

bufer 指针,队列的数据会被复制到这个 buffer

复制多大的数据?在创建队列时已经指定了数据大小

xTicksToWait 队列空则无法读出数据,可以让任务进入阻塞状态,

xTicksToWait 表示阻塞的最大时间(Tick Count)。

如果被设为 0,无法读出数据时函数会立刻返回;

如果被设为 portMAX_DELAY,则会一直阻塞直到有数据可写

返回值

pdPASS:从队列读出数据入

errQUEUE_EMPTY:读取失败,因为队列空了

使用示例

注意事项

/*
创建队列的位置会影响程序的运行,特别是在你的 platform_task 中,
你尝试在任务的循环中等待队列消息,但是队列在创建之前就已经被使用了。
具体来说,platform_task 中的循环会永远等待队列消息,因为队列 xQueue1 在 game1_task 中创建,
而不是在 platform_task 中创建。这导致 xQueueReceive 函数一直阻塞,因为没有可用的队列。
由于 portMAX_DELAY 被用于阻塞等待队列,这会导致程序卡死。
要解决这个问题,
你应该在 platform_task 中等待队列消息之前创建队列。
确保在任何尝试使用队列之前都创建了它。
简单来说,你需要确保在 xQueueReceive 调用之前创建了队列 xQueue1。
所以,将队列的创建移到 platform_task 之前的部分,或者在程序初始化时创建队列,而不是在任务中创建。
这样,当 platform_task 开始运行时,队列已经存在,它就可以正确地等待并接收到消息了。
*/

 

1.创建游戏任务

void MX_FREERTOS_Init(void) {
  /* USER CODE BEGIN Init */
  LCD_Init();
  LCD_Clear();
  
  
  IRReceiver_Init();

  /* USER CODE END Init */

  /* USER CODE BEGIN RTOS_MUTEX */
  /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */

  /* USER CODE BEGIN RTOS_SEMAPHORES */
  /* add semaphores, ... */
  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */
  /* start timers, add new ones, ... */
  /* USER CODE END RTOS_TIMERS */

  /* USER CODE BEGIN RTOS_QUEUES */
  /* add queues, ... */
  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */
  /* creation of defaultTask */
  
  //defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
  /* 创建任务:游戏 */
  //extern void PlayMusic(void *params);
  xTaskCreate(game1_task, "GameTask", 128, NULL, osPriorityNormal, NULL);

  /* 创建任务: 光 */
  xLightTaskHandle = xTaskCreateStatic(Led_Test, "LightTask", 128, NULL, osPriorityNormal, g_pucStackOfLightTask, &g_TCBofLightTask);

  /* 创建任务: 色 */
  //xColorTaskHandle = xTaskCreateStatic(ColorLED_Test, "ColorTask", 128, NULL, osPriorityNormal, g_pucStackOfColorTask, &g_TCBofColorTask);

  /* USER CODE END RTOS_THREADS */

  /* USER CODE BEGIN RTOS_EVENTS */
  /* add events, ... */
  /* USER CODE END RTOS_EVENTS */

}

2.创建挡球板和旋转编码器队列,创建挡球板任务和旋转编码器任务

void game1_task(void *params)
{		

    
    g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
    draw_init();
    draw_end();
    /*  10代表能10个数据长度,sizeof,每个数据的大小*/
    //创建队列位置一定在接收任务队列前面不然会卡死程序
    xQueue_IR=    xQueueCreate(10, sizeof(struct ir_data)  );//创建一个挡球板队列
    xQueue_rotary=xQueueCreate(10, sizeof(struct rotary_data));//创建一个旋转编码器队列
	uptMove = UPT_MOVE_NONE;

	ball.x = g_xres / 2;
	ball.y = g_yres - 10;
        
	ball.velX = -0.5;
	ball.velY = -0.6;
//	ball.velX = -1;
//	ball.velY = -1.1;

	blocks = pvPortMalloc(BLOCK_COUNT);
    memset(blocks, 0, BLOCK_COUNT);
	
	lives = lives_origin = 3;
	score = 0;
	platformX = (g_xres / 2) - (PLATFORM_WIDTH / 2);

    xTaskCreate(platform_task, "platform_task", 128, NULL, osPriorityNormal, NULL);//创建一个挡球板任务
    xTaskCreate(rotary_task, "rotary_task", 128, NULL, osPriorityNormal, NULL);//创建一个旋转编码器任务 
    
    while (1)
    {
        game1_draw();//画球
        //draw_end();
        vTaskDelay(50);
    }
}

3.旋转编码器和挡球板任务中读取队列 

void rotary_task(void *params)
{  
    byte platformXtmp = platformX;    
    struct rotary_data rdata;
    struct ir_data idata;
    int32_t cnt;
    int i,left,speed;
     while (1)
    {
      idata.dev = 1;  
      xQueueReceive( xQueue_rotary,&idata ,portMAX_DELAY);
        speed=rdata.speed;        
      if(speed<0)
      {
      left=1;
          idata.val=UPT_MOVE_LEFT;
    speed=-speed;   
      }
      if(speed>0)
      {
           idata.val=UPT_MOVE_RIGHT;
        left=0;    
      }
      
      if (speed > 100)
			cnt = 4;
	  else if (speed > 50)
			cnt = 2;
	  else
			cnt = 1;
				
		/* 写挡球板队列 */

		//idata.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;
		for (i = 0; i < cnt; i++)
		{
			xQueueSend(xQueue_IR, &idata, NULL);
		}

      
      
    }
    
}
/*
创建队列的位置会影响程序的运行,特别是在你的 platform_task 中,
你尝试在任务的循环中等待队列消息,但是队列在创建之前就已经被使用了。
具体来说,platform_task 中的循环会永远等待队列消息,因为队列 xQueue1 在 game1_task 中创建,
而不是在 platform_task 中创建。这导致 xQueueReceive 函数一直阻塞,因为没有可用的队列。
由于 portMAX_DELAY 被用于阻塞等待队列,这会导致程序卡死。
要解决这个问题,
你应该在 platform_task 中等待队列消息之前创建队列。
确保在任何尝试使用队列之前都创建了它。
简单来说,你需要确保在 xQueueReceive 调用之前创建了队列 xQueue1。
所以,将队列的创建移到 platform_task 之前的部分,或者在程序初始化时创建队列,而不是在任务中创建。
这样,当 platform_task 开始运行时,队列已经存在,它就可以正确地等待并接收到消息了。
*/

/* 挡球板任务 */
static void platform_task(void *params)
{
    /*在这里创建队列会卡死程序 xQueue1=xQueueCreate(10, sizeof(struct ir_data)  );//创建一个挡球板队列*/
    byte platformXtmp = platformX;    
    uint8_t dev,data, last_data;
    struct ir_data idata;
    // Draw platform
    draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
    draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
    int bRunning;
	
	TaskHandle_t xSoundTaskHandle = NULL;
	BaseType_t ret;
    while (1)
    {
         
        /* 读取红外遥控器 */
        //if (0 == IRReceiver_Read(&dev, &data))
        /*队列读取*/
       xQueueReceive( xQueue_IR,&idata ,portMAX_DELAY);
        uptMove=idata.val;

            if (uptMove == music_play) /* play */
			{
				/* 创建播放音乐的任务 */
			  extern void PlayMusic(void *params);
			  if (xSoundTaskHandle == NULL)
			  {

					ret = xTaskCreate(PlayMusic, "SoundTask", 128, NULL, osPriorityNormal+3, &xSoundTaskHandle);
					bRunning = 1;
			  }
			  else
			  {
				  /* 要么suspend要么resume */
				  if (bRunning)
				  {
	
					  vTaskSuspend(xSoundTaskHandle);
					  PassiveBuzzer_Control(0); /* 停止蜂鸣器 */
					  bRunning = 0;
				  }
				  else
				  {
				
					  vTaskResume(xSoundTaskHandle);
					  bRunning = 1;
				  }
			  }
			}
			
			else if (uptMove == music_power) /* power */
			{
				/* 删除播放音乐的任务 */
				if (xSoundTaskHandle != NULL)
				{
	
					vTaskDelete(xSoundTaskHandle);
					PassiveBuzzer_Control(0); /* 停止蜂鸣器 */
					xSoundTaskHandle = NULL;
				}
			}


            
            // Hide platform
            draw_bitmap(platformXtmp, g_yres - 8, clearImg, 12, 8, NOINVERT, 0);
            draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
            
            // Move platform
            if(uptMove == UPT_MOVE_RIGHT)
                platformXtmp += 3;
            else if(uptMove == UPT_MOVE_LEFT)
                platformXtmp -= 3;
            uptMove = UPT_MOVE_NONE;
            
            // Make sure platform stays on screen
            if(platformXtmp > 250)
                platformXtmp = 0;
            else if(platformXtmp > g_xres - PLATFORM_WIDTH)
                platformXtmp = g_xres - PLATFORM_WIDTH;
            
            // Draw platform
            draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);//画挡球板
            draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
            
            platformX = platformXtmp;
            
		
    }
}

4.写旋转编码器队列

void RotaryEncoder_IRQ_Callback(void)
{
    
    uint64_t time;
    static uint64_t pre_time = 0;
    struct rotary_data R_data;    
    
	/* 1. 记录中断发生的时刻 */	
	time = system_get_ns();

    /* 上升沿触发: 必定是高电平 
     * 防抖
     */
    mdelay(2);
    if (!RotaryEncoder_Get_S1())
        return;

    /* S1上升沿触发中断
     * S2为0表示逆时针转, 为1表示顺时针转
     */
    g_speed = (uint64_t)1000000000/(time - pre_time);
    if (RotaryEncoder_Get_S2())
    {
        g_count++;
    }
    else
    {
        g_count--;
        g_speed =  - g_speed;
    }
    pre_time = time;
    
      R_data.cnt=  g_count;
      R_data.speed=g_speed;
       xQueueSendToBackFromISR(xQueue_rotary , &R_data, NULL);
}

5.写挡球板队列

static int IRReceiver_IRQTimes_Parse(void)
{
	uint64_t time;
	int i;
	int m, n;
	unsigned char datas[4];
	unsigned char data = 0;
	int bits = 0;
	int byte = 0;
    struct ir_data idata;
	/* 1. 判断前导码 : 9ms的低脉冲, 4.5ms高脉冲  */
	time = g_IRReceiverIRQ_Timers[1] - g_IRReceiverIRQ_Timers[0];
	if (time < 8000000 || time > 10000000)
	{
		return -1;
	}

	time = g_IRReceiverIRQ_Timers[2] - g_IRReceiverIRQ_Timers[1];
	if (time < 3500000 || time > 55000000)
	{
		return -1;
	}

	/* 2. 解析数据 */
	for (i = 0; i < 32; i++)
	{
		m = 3 + i*2;
		n = m+1;
		time = g_IRReceiverIRQ_Timers[n] - g_IRReceiverIRQ_Timers[m];
		data <<= 1;
		bits++;
		if (time > 1000000)
		{
			/* 得到了数据1 */
			data |= 1;
		}

		if (bits == 8)
		{
			datas[byte] = data;
			byte++;
			data = 0;
			bits = 0;
		}
	}

	/* 判断数据正误 */
	datas[1] = ~datas[1];
	datas[3] = ~datas[3];
	
	if ((datas[0] != datas[1]) || (datas[2] != datas[3]))
	{
        g_IRReceiverIRQ_Cnt = 0;
        return -1;
	}
    
//	PutKeyToBuf(datas[0]);
//	PutKeyToBuf(datas[2]);
    idata.dev = datas[0];
	
	if (datas[2] == 0xe0)
		idata.val = UPT_MOVE_LEFT;
	else if (datas[2] == 0x90)
		idata.val = UPT_MOVE_RIGHT;
    else if (datas[2] == 0xa8)
        idata.val =music_play;
    else if (datas[2] == 0xa2)
        idata.val =music_power;
	else
		idata.val = UPT_MOVE_NONE;
	g_last_val = idata.val;

    xQueueSendToBackFromISR( xQueue_IR, &idata, NULL);

    return 0;
}

效果展示

基于RTOS使用红外遥控器和旋转编码器控制挡球板移动,还能播放音乐。

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值