FreeRTOS消息队列 传递结构体指针和结构体异同
原文链接:https://blog.csdn.net/weixin_44333597/article/details/107523343
FreeRTOS消息队列应用
https://blog.csdn.net/weixin_44333597/article/details/107725480
[VC]SendMessage和PostMessage发送消息(不同进程传递字符串
https://blog.csdn.net/slj_win/article/details/40795159?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.highlightwordscore&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.highlightwordscore
1 消息队列传递结构体指针和结构体异同
2 使用队列传递复合数据类型
接收方接收由多个发送源发送至队列中的消息时,需要判断数据的来源,使用方式:利用队列传递结构体,在结构体中包含数据值和数据来源信息。
typedef struct
{
int iValue; //数据值
int iMeaning; //数据来源信息
}xData;
3 利用队列传递数据的指针(处理速度更快)
3.1 指针指向的内存空间所有权必须明确
共享内存在其指针发送到队列之前,其内容只允许被发送任务访问;
共享内存指针从队列中被读出之后,其内容亦只允许被接收任务访问。
3.2 指针指向的内存空间必须有效
指针指向的内存空间是动态分配的,只应该有一个任务对其进行内存释放,当这段内存空间被释放后,就不应该有其他任务再次访问这段空间。
传递结构体的指针
//传递结构体的指针
/* 创建10个存储指针变量的消息队列,由于CM3/CM4内核是32位机,一个指针变量占用4个字节 */
xQueue2 = xQueueCreate(10, sizeof(struct Msg *)); //单元大小为一个指针变量的大小
//发送消息,实现结构体指针的传递
MSG_T *ptMsg; //MSG_T为结构体声明
ptMsg = &g_tMsg; /* 初始化结构体指针 */
// 初始化数组
ptMsg->ucMessageID = 0;
ptMsg->ulData[0] = 0;
ptMsg->usData[0] = 0;
//使用消息队列实现指针变量的传递
if(xQueueSend(xQueue2, /* 消息队列句柄 */
(void *) &ptMsg, // 发送结构体指针变量ptMsg的地址 “&” 取结构体指针的地址,传递指针
(TickType_t)10) != pdPASS )
//接收消息,接收结构体的指针
MSG_T *ptMsg; //定义一个结构体指针
xResult = xQueueReceive(xQueue2, /* 消息队列句柄 */
(void *)&ptMsg, // 这里获取的是结构体的地址,类似于 char *a="stm";char *b;b=a 指针赋值,a和b指向同一个地址
(TickType_t)xMaxBlockTime);/* 设置阻塞时间 */
if(xResult == pdPASS)
{
/* 成功接收,并通过串口将数据打印出来 */
printf("接收到消息队列数据ptMsg->ucMessageID = %d\r\n",ptMsg->ucMessageID);
printf("接收到消息队列数据ptMsg->ulData[0] = %d\r\n", ptMsg->ulData[0]);
printf("接收到消息队列数据ptMsg->usData[0] = %d\r\n", ptMsg->usData[0]);
}
传递结构体本身
//传递结构体本身
//创建一个消息队列
xQueue2 = xQueueCreate(10, sizeof(struct Msg)); //单员大小为结构体的大小
//发送消息,实现结构体的传递
MSG_T ptMsg; //MSG_T为结构体声明
//初始化数组
ptMsg.ucMessageID = 0;
ptMsg.ulData[0] = 0;
ptMsg.usData[0] = 0;
//使用消息队列实现指针变量的传递
if(xQueueSend(xQueue2, /* 消息队列句柄 */
(void *) &ptMsg, // 发送结构体ptMsg的值,将值拷贝至队列中
(TickType_t)10) != pdPASS )
//接收消息,接收结构体的值
MSG_T ptMsg; //定义一个结构体指针
xResult = xQueueReceive(xQueue2,
(void *)&ptMsg, // 这里获取的是结构体的值
(TickType_t)xMaxBlockTime);/* 设置阻塞时间 */
if(xResult == pdPASS)
{
/* 成功接收,并通过串口将数据打印出来 */
printf("接收到消息队列数据ptMsg.ucMessageID = %d\r\n",ptMsg.ucMessageID);
printf("接收到消息队列数据ptMsg.ulData[0] = %d\r\n", ptMsg.ulData[0]);
printf("接收到消息队列数据ptMsg.usData[0] = %d\r\n", ptMsg.usData[0]);
}
4 传递结构体指针和结构体本身的异同
-
创建消息队列时,单元大小声明不同
-
变量初始化不同,一个是定义指针,并所赋结构体实体是全局的,一个是定义结构体
-
数据输出不同,前者是用指针成员运算符“->”,后者是用结构体成员运算符
-
在发送消息和请求消息时,两者的格式是相同的,但意义不用,前者传递的是结构体指针的地址,后者传递的是结构体的值
5 验证
5.1 结构体传值模式
tcpecho.h
#ifndef LWIP_TCPECHO_H
#define LWIP_TCPECHO_H
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "queue.h"
#include "cc.h"
typedef struct _MSG_t
{
u8_t ucMessageID;
u8_t data[20];
}MSG_T;
//消息队列句柄,用户任务间进程通信,收发消息
extern QueueHandle_t Eth_Queue;
extern MSG_T g_tMsg;
#define QUEUE_LEN 5 //队列的长度,最大可包含多少个消息
#define QUEUE_SIZE 21 //队列中每个消息大小(字节),MSG_T结构体大小
void tcpecho_init(void);
#endif /* LWIP_TCPECHO_H */
tcpecho.c
#include "tcpecho.h"
#include "lwip/opt.h"
#if LWIP_NETCONN
#include "lwip/sys.h"
#include "lwip/api.h"
QueueHandle_t Eth_Queue = NULL;
void *data_pbuf;
u8_t recv_data[1024];
MSG_T g_tMsg; //全局结构体变量
/*-----------------------------------------------------------------------------------*/
static void
tcpecho_thread(void *arg)
{
struct netconn *conn, *newconn;
err_t err;
LWIP_UNUSED_ARG(arg);
/* Create a new connection identifier. */
/* Bind connection to well known port number 7. */
#if LWIP_IPV6
conn = netconn_new(NETCONN_TCP_IPV6);
netconn_bind(conn, IP6_ADDR_ANY, LOCAL_PORT);
#else /* LWIP_IPV6 */
conn = netconn_new(NETCONN_TCP);
netconn_bind(conn, IP_ADDR_ANY, LOCAL_PORT);
#endif /* LWIP_IPV6 */
LWIP_ERROR("tcpecho: invalid conn", (conn != NULL), return;);
PRINTF("本地端口号是%d\n\n",LOCAL_PORT);
/* Tell connection to go into listening mode. */
netconn_listen(conn);
BaseType_t xReturn = pdTRUE; //定义一个创建信息返回值,默认为pdTRUE
MSG_T ptMsg; //MSG_T结构体声明
while (1) {
/* Grab new connection. */
err = netconn_accept(conn, &newconn);
/*printf("accepted new connection %p\n", newconn);*/
/* Process the new connection. */
if (err == ERR_OK) {
struct netbuf *buf;
void *data;
u16_t len;
char str1[] = "netconn_write data123";
u16_t len1 = sizeof(str1);
while ((err = netconn_recv(newconn, &buf)) == ERR_OK) {
/*printf("Recved\n");*/
do {
//netbuf_data(buf, &data, &len);
netbuf_data(buf, &data_pbuf, &len);
//发送消息,实现结构体的值传递模式,不是结构体指针传递
//初始化结构体内容
ptMsg.ucMessageID = *((u8_t*)data_pbuf);
ptMsg.data[0] = *((u8_t*)data_pbuf+1);
ptMsg.data[1] = *((u8_t*)data_pbuf+2);
ptMsg.data[2] = *((u8_t*)data_pbuf+3);
ptMsg.data[3] = *((u8_t*)data_pbuf+4);
ptMsg.data[4] = *((u8_t*)data_pbuf+5);
ptMsg.data[5] = *((u8_t*)data_pbuf+6);
xReturn = xQueueSend(Eth_Queue, &ptMsg, 0); //发送结构体ptMsg的值,将值拷贝至消息队列中
if(pdPASS == xReturn)
PRINTF("消息发送成功!\n");
err = netconn_write(newconn, data_pbuf, len, NETCONN_COPY);
err = netconn_write(newconn, str1, len1, NETCONN_COPY);
#if 0
if (err != ERR_OK) {
PRINTF("tcpecho: netconn_write: error \"%s\"\n", lwip_strerr(err));
}
#endif
} while (netbuf_next(buf) >= 0);
netbuf_delete(buf);
}
/*printf("Got EOF, looping\n");*/
/* Close connection and discard connection identifier. */
netconn_close(newconn);
netconn_delete(newconn);
}
}
}
/*-----------------------------------------------------------------------------------*/
void
tcpecho_init(void)
{
sys_thread_new("tcpecho_thread", tcpecho_thread, NULL, 512, 4);
}
/*-----------------------------------------------------------------------------------*/
#endif /* LWIP_NETCONN */
main.c
/**********************************************************************
* @ 函数名 : Test2_Task
* @ 功能说明: Test2_Task任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void Test2_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE; //定义一个创建信息返回值,默认为pdTRUE
MSG_T ptMsg; //定义一个MSG_T接收结构体
while (1)
{
LED2_TOGGLE;
xReturn = xQueueReceive(Eth_Queue, //消息队列的句柄
&ptMsg, //接收的消息内容
portMAX_DELAY); //等待时间,一直等
if(pdTRUE == xReturn){
PRINTF("本次接收到的消息为:\n");
PRINTF("消息内容为:%c %c %c %c %c %c\n",ptMsg.ucMessageID, ptMsg.data[0],ptMsg.data[1],
ptMsg.data[2],ptMsg.data[3],ptMsg.data[4],ptMsg.data[5]);
}
else
PRINTF("数据接收出错,错误代码: 0x%lx\n", xReturn);
}
}
5.2 传递结构体指针模式
tcpecho.h
#ifndef LWIP_TCPECHO_H
#define LWIP_TCPECHO_H
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "queue.h"
#include "cc.h"
typedef struct _MSG_t
{
u8_t length;
u8_t data[30];
}MSG_T;
//消息队列句柄,用户任务间进程通信,收发消息
extern QueueHandle_t Eth_Queue;
extern MSG_T g_tMsg;
#define QUEUE_LEN 5 //队列的长度,最大可包含多少个消息
#define QUEUE_SIZE 4 //队列中每个消息大小(字节),MSG_T结构体指针大小
void tcpecho_init(void);
#endif /* LWIP_TCPECHO_H */
tcpecho.c
#include "tcpecho.h"
#include "lwip/opt.h"
#if LWIP_NETCONN
#include "lwip/sys.h"
#include "lwip/api.h"
QueueHandle_t Eth_Queue = NULL;
void *data_pbuf;
u8_t recv_data[1024];
MSG_T g_tMsg; //全局结构体变量
/*-----------------------------------------------------------------------------------*/
static void
tcpecho_thread(void *arg)
{
struct netconn *conn, *newconn;
err_t err;
LWIP_UNUSED_ARG(arg);
/* Create a new connection identifier. */
/* Bind connection to well known port number 7. */
#if LWIP_IPV6
conn = netconn_new(NETCONN_TCP_IPV6);
netconn_bind(conn, IP6_ADDR_ANY, LOCAL_PORT);
#else /* LWIP_IPV6 */
conn = netconn_new(NETCONN_TCP);
netconn_bind(conn, IP_ADDR_ANY, LOCAL_PORT);
#endif /* LWIP_IPV6 */
LWIP_ERROR("tcpecho: invalid conn", (conn != NULL), return;);
PRINTF("本地端口号是%d\n\n",LOCAL_PORT);
/* Tell connection to go into listening mode. */
netconn_listen(conn);
BaseType_t xReturn = pdTRUE; //定义一个创建信息返回值,默认为pdTRUE
MSG_T* ptMsg = &g_tMsg; //MSG_T结构体声明
while (1) {
/* Grab new connection. */
err = netconn_accept(conn, &newconn);
/*printf("accepted new connection %p\n", newconn);*/
/* Process the new connection. */
if (err == ERR_OK) {
struct netbuf *buf;
void *data;
u16_t len;
char str1[] = "netconn_write data123";
u16_t len1 = sizeof(str1);
while ((err = netconn_recv(newconn, &buf)) == ERR_OK) {
/*printf("Recved\n");*/
do {
//netbuf_data(buf, &data, &len);
netbuf_data(buf, &data_pbuf, &len);
//发送消息,实现结构体的指针传递模式
//初始化结构体内容
g_tMsg.length = len;
memcpy(g_tMsg.data, data_pbuf, len); //复制缓冲区数据
xReturn = xQueueSend(Eth_Queue, &ptMsg, 0); //发送结构体ptMsg的值,将值拷贝至消息队列中
if(pdPASS == xReturn)
PRINTF("消息发送成功!\n");
err = netconn_write(newconn, data_pbuf, len, NETCONN_COPY);
err = netconn_write(newconn, str1, len1, NETCONN_COPY);
#if 0
if (err != ERR_OK) {
PRINTF("tcpecho: netconn_write: error \"%s\"\n", lwip_strerr(err));
}
#endif
} while (netbuf_next(buf) >= 0);
netbuf_delete(buf);
}
/*printf("Got EOF, looping\n");*/
/* Close connection and discard connection identifier. */
netconn_close(newconn);
netconn_delete(newconn);
}
}
}
/*-----------------------------------------------------------------------------------*/
void
tcpecho_init(void)
{
sys_thread_new("tcpecho_thread", tcpecho_thread, NULL, 512, 4);
}
/*-----------------------------------------------------------------------------------*/
#endif /* LWIP_NETCONN */
main.c
/**********************************************************************
* @ 函数名 : Test2_Task
* @ 功能说明: Test2_Task任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void Test2_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE; //定义一个创建信息返回值,默认为pdTRUE
MSG_T* ptMsg; //定义一个MSG_T接收结构体指针
while (1)
{
LED2_TOGGLE;
xReturn = xQueueReceive(Eth_Queue, //消息队列的句柄
&ptMsg, //接收的消息内容
portMAX_DELAY); //等待时间,一直等
if(pdTRUE == xReturn){
PRINTF("本次接收到的消息为:\n");
for (int i=0; i<ptMsg->length; i++)
{
PRINTF("%c ", ptMsg->data[i]);
}
PRINTF("\n\n");
}
else
PRINTF("数据接收出错,错误代码: 0x%lx\n", xReturn);
}
}
5.3 memcpy 函数
memcpy函数是C/C++语言中的一个用于内存复制的函数,声明在 string.h 中(C++是 cstring)。其原型是:
void *memcpy(void *destin, void *source, unsigned n);
作用是:以source指向的地址为起点,将连续的n个字节数据,复制到以destin指向的地址为起点的内存中。
函数有三个参数,第一个是目标地址,第二个是源地址,第三个是数据长度。
使用memcpy函数时,需要注意:
数据长度(第三个参数)的单位是字节(1byte = 8bit)。
注意该函数有一个返回值,类型是void*,是一个指向destin的指针。
void *memcpy(void *dst, const void *src, size_t size)
{
char *psrc;
char *pdst;
if (NULL == dst || NULL == src)
{
return NULL;
}
if ((src < dst) && (char *)src + size > (char *)dst) // 出现地址重叠的情况,自后向前拷贝
{
psrc = (char *)src + size - 1;
pdst = (char *)dst + size - 1;
while (size--)
{
*pdst-- = *psrc--;
}
}
else
{
psrc = (char *)src;
pdst = (char *)dst;
while (size--)
{
*pdst++ = *psrc++;
}
}
return dst;
}
memcpy函数复制的数据长度
使用memcpy函数时,特别要注意数据长度。如果复制的数据类型是char,那么数据长度就等于元素的个数。而如果数据类型是其他(如int, double, 自定义结构体等),就要特别注意数据长度的值。
好的习惯是,无论拷贝何种数据类型,都用 n * sizeof(type_name)的写法。
5.4 memset函数及其用法
定义变量时一定要进行初始化,尤其是数组和结构体这种占用内存大的数据结构。在使用数组的时候经常因为没有初始化而产生“烫烫烫烫烫烫”这样的野值,俗称“乱码”。
每种类型的变量都有各自的初始化方法,memset() 函数可以说是初始化内存的“万能函数”,通常为新申请的内存进行初始化工作。它是直接操作内存空间,mem即“内存”(memory)的意思。该函数的原型为:
include <string.h>
void *memset(void *s, int c, unsigned long n);
函数的功能是:将指针变量 s 所指向的前 n 字节的内存单元用一个“整数” c 替换,注意 c 是 int 型。s 是 void* 型的指针变量,所以它可以为任何类型的数据进行初始化。
memset() 的作用是在一段内存块中填充某个给定的值。因为它只能填充一个值,所以该函数的初始化为原始初始化,无法将变量初始化为程序中需要的数据。用memset初始化完后,后面程序中再向该内存空间中存放需要的数据。
memset 一般使用“0”初始化内存单元,而且通常是给数组或结构体进行初始化。一般的变量如 char、int、float、double 等类型的变量直接初始化即可,没有必要用 memset。如果用 memset 的话反而显得麻烦。
当然,数组也可以直接进行初始化,但 memset 是对较大的数组或结构体进行清零初始化的最快方法,因为它是直接对内存进行操作的。
这时有人会问:“字符串数组不是最好用’\0’进行初始化吗?那么可以用 memset 给字符串数组进行初始化吗?也就是说参数 c 可以赋值为’\0’吗?”
可以的。虽然参数 c 要求是一个整数,但是整型和字符型是互通的。但是赋值为 ‘\0’ 和 0 是等价的,因为字符 ‘\0’ 在内存中就是 0。所以在 memset 中初始化为 0 也具有结束标志符 ‘\0’ 的作用,所以通常我们就写“0”。
memset 函数的第三个参数 n 的值一般用 sizeof() 获取,这样比较专业。注意,如果是对指针变量所指向的内存单元进行清零初始化,那么一定要先对这个指针变量进行初始化,即一定要先让它指向某个有效的地址。而且用memset给指针变量如p所指向的内存单元进行初始化时,n 千万别写成 sizeof§,这是新手经常会犯的错误。因为 p 是指针变量,不管 p 指向什么类型的变量,sizeof§ 的值都是 4。