数据传输方法
环形buffer
环形buffer的本质就是一个循环队列,但是有一些不同
- 空:当头指针和尾指针相等时,表示缓冲区为空。
- 满:当尾指针的下一个位置等于头指针时,表示缓冲区已满(在环形结构中,尾指针要循环到数组的开始位置)。
队列
队列的本质
在嵌入式中队列运行就像一个流水线工作,其中有两个工人
A的作用
(1)写队列
(2)如果队列已满则可以进行对任务A处理(具体是把任务A放入SenderList链表以及阻塞Block链表)
(3)任务B如何被唤醒,要么由任务A唤醒任务B,要么结束阻塞,通过Tick中断唤醒B,把任务B放入ReadyList
B的作用
(1)查看流水线有无任务(产品),没有的话将进行阻塞(眯一会)(相当于把任务放入Receiverlist链表以及阻塞Block链表)
(2)读队列
(3)任务B读队列,唤醒任务A,通过Tick中断唤醒A,把任务A放入ReadyList
队列的阻塞访问
只要知道队列的句柄,谁都可以读、写该队列。任务、ISR 都可读、写队列。可以多个
任务读写队列。
任务读写队列时,简单地说:如果读写不成功,则阻塞;可以指定超时时间。口语化地
说,就是可以定个闹钟:如果能读写了就马上进入就绪态,否则就阻塞直到超时。
某个任务读队列时,如果队列没有数据,则该任务可以进入阻塞状态:还可以指定阻塞
的时间。如果队列有数据了,则该阻塞的任务会变为就绪态。如果一直都没有数据,则时间
到之后它也会进入就绪态。
既然读取队列的任务个数没有限制,那么当多个任务读取空队列时,这些任务都会进入
阻塞状态:有多个任务在等待同一个队列的数据。当队列中有数据时,哪个任务会进入就绪
态?
⚫ 优先级最高的任务⚫ 如果大家的优先级相同,那等待时间最久的任务会进入就绪态
跟读队列类似,一个任务要写队列时,如果队列满了,该任务也可以进入阻塞状态:还
可以指定阻塞的时间。如果队列有空间了,则该阻塞的任务会变为就绪态。如果一直都没有
空间,则时间到之后它也会进入就绪态。
既然写队列的任务个数没有限制,那么当多个任务写 " 满队列 " 时,这些任务都会进入阻
塞状态:有多个任务在等待同一个队列的空间。当队列中有空间时,哪个任务会进入就绪态?
⚫ 优先级最高的任务⚫ 如果大家的优先级相同,那等待时间最久的任务会进入就绪态
队列函数
创建
队列的创建有两种方法:动态分配内存、静态分配内存
动态分配内存
动态分配内存: xQueueCreate ,队列的内存在函数内部动态分配QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
参数说明:
- uxQueueLength:队列中可以存放的最大项数(队列长度)。
- uxItemSize:每个队列项的大小(以字节为单位)。如果要存储的项是结构体或者其他数据类型,确保传入正确的大小。
返回值:
- 返回一个队列句柄(
QueueHandle_t
),如果创建成功,句柄有效;如果失败,返回NULL
。
静态分配内存
静态分配内存: xQueueCreateStatic ,队列的内存要事先分配好QueueHandle_t xQueueCreateStatic( UBaseType_t uxQueueLength, UBaseType_t uxItemSize, uint8_t *pucQueueStorageBuffer, StaticQueue_t *pxQueueBuffer );
参数说明:
- uxQueueLength:队列可以存放的最大项数(队列长度)。
- uxItemSize:每个队列项的大小(以字节为单位)。
- pucQueueStorageBuffer:指向用于存储队列项的缓冲区的指针。这个缓冲区的大小应该为
uxQueueLength * uxItemSize
。- pxQueueBuffer:指向一个
StaticQueue_t
结构体的指针,用于存储队列的控制结构。此结构体在 FreeRTOS 中被用于管理队列的状态。返回值:
- 返回一个队列句柄(
QueueHandle_t
),如果创建成功,句柄有效;如果失败,返回NULL
。
写队列
/* 等同于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 );
参数说明:
- xQueue:要发送项的队列句柄。
- pvItemToQueue:指向要发送到队列的项的指针。此项的数据类型应与队列在创建时指定的数据类型匹配。
- xTicksToWait:如果队列已满,调用任务将等待的时间(以滴答数为单位)。可以使用
portMAX_DELAY
表示无限等待,或使用0
表示不等待。返回值:
- 返回值为
BaseType_t
,表示发送操作的结果。可能的返回值包括:
pdPASS
:成功将项发送到队列。errQUEUE_FULL
:队列已满,未能发送项(且没有设置等待时间或等待时间已到)。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 input_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 idata.val = UPT_MOVE_NONE; g_last_val = idata.val; xQueueSendFromISR(g_xQueuePlatform, &idata, NULL); return 0; }
读队列
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait ); BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, void *pvBuffer, BaseType_t *pxTaskWoken );
1.
xQueueReceive
参数说明:
xQueue
:要从中接收数据的队列句柄。pvBuffer
:指向接收数据存储位置的指针。接收到的数据将存储在这里。xTicksToWait
:如果队列为空,任务等待的最大时间(以滴答为单位)。可以使用portMAX_DELAY
表示无限等待。返回值:
- 如果成功接收数据,返回
pdPASS
;如果超时或发生错误,返回errQUEUE_EMPTY
。2.
xQueueReceiveFromISR
参数说明:
xQueue
:要从中接收数据的队列句柄。pvBuffer
:指向接收数据存储位置的指针。pxTaskWoken
:指向BaseType_t
类型的指针,如果接收数据后需要唤醒一个任务,设置为pdTRUE
,否则设置为pdFALSE
。返回值:
- 与
xQueueReceive
相似,成功时返回pdPASS
,失败时返回errQUEUE_EMPTY
。/* 挡球板任务 */ static void platform_task(void *params) { byte platformXtmp = platformX; uint8_t dev, data, last_data; struct input_data idata; // Draw platform draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0); draw_flushArea(platformXtmp, g_yres - 8, 12, 8); while (1) { //if (0 == IRReceiver_Read(&dev, &data)) xQueueReceive(g_xQueuePlatform, &idata, portMAX_DELAY); uptMove = idata.val; // 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; } }
注意
写队列就是在中断中写取数据,在进行复杂的操作时,连接一个中间的任务处理数据然后再提交
/* 挡球板任务 */ static void platform_task(void *params) { byte platformXtmp = platformX; uint8_t dev, data, last_data; struct input_data idata; // Draw platform draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0); draw_flushArea(platformXtmp, g_yres - 8, 12, 8); while (1) { //if (0 == IRReceiver_Read(&dev, &data)) xQueueReceive(g_xQueuePlatform, &idata, portMAX_DELAY); uptMove = idata.val; // 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; } } static void RotaryEncoderTask(void *params) { struct rotary_data rdata; struct input_data idata; int left; int i, cnt; while (1) { /* 读旋转编码器队列 */ xQueueReceive(g_xQueueRotary, &rdata, portMAX_DELAY); /* 处理数据 */ /* 判断速度: 负数表示向左转动, 正数表示向右转动 */ if (rdata.speed < 0) { left = 1; rdata.speed = 0 - rdata.speed; } else { left = 0; } //cnt = rdata.speed / 10; //if (!cnt) // cnt = 1; if (rdata.speed > 100) cnt = 4; else if (rdata.speed > 50) cnt = 2; else cnt = 1; /* 写挡球板队列 */ idata.dev = 1; idata.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT; for (i = 0; i < cnt; i++) { xQueueSend(g_xQueuePlatform, &idata, 0); } } }