我将首先简要介绍我正在做的事情。
我正在为微控制器设备编写一个库,可用于通过UART控制PC上的某个应用程序。该应用程序的初稿纯粹是顺序的 - 可以从库中调用一组函数,以便将某些数据发送到PC。这有效,但存在潜在问题,因为执行是顺序的,如果出现问题,整个程序就会崩溃。我决定将 freeRTOS 合并到库中,以便更好地控制执行,问题就随之而来。我将尽我所能,通过仅提供基本的代码部分来使问题更接近您。
让我简要地回顾一下该库的第一个版本。我将只提供我认为必不可少的代码。
/* main.c
Contains a task that uses library functions to send commands.
*/
/* prvControlTask
Uses library functions to control PC application over UART.
*/
static void prvControlTask( void *pvParameters )
{
TickType_t xNextWakeTime;
/* Remove compiler warning about unused parameter. */
( void ) pvParameters;
xNextWakeTime = xTaskGetTickCount();
/* Using some library functions */
connect(0x00, "Mike", "Mike-123"); // Connects to the PC app
play(0x00, "move", "right"); // Control some app aspect
play(0x00, "move", "right-up");
play(0x00, "move", "right-down");
play(0x00, "jump", "left-down");
for( ;; )
{
/* Place this task in the blocked state until it is time to run again. */
vTaskDelayUntil( &xNextWakeTime, mainQUEUE_SEND_FREQUENCY_MS );
}
}
在库中,库使用 3 种数据结构:
/* library
Contains structures and functions for UART-PC control
*/
char uart_bytes[100]; // Used to send bytes to uart
struct CONNECT{ // To help store data for connect function
uint8_t player_type;
char* username;
char* password;
};
struct PLAY{ // To help store data for play function
uint8_t player_type;
char* command;
char* direction;
};
函数本身非常简单。被调用的函数填充结构并将其发送到另一个函数,该函数将其数据转换为字节并填充uart_bytes并通过UART发送这些字节。
/* library
Contains structures and functions for UART-PC control
*/
void connect(uint8_t player_type, char* username, char* password){
struct CONNECT connect_data = {0};
connect_data.player_type = player_type;
connect_data.username = username;
connect_data.password = password;
send_connect_struct(&connect_data);
}
void send_connect_struct(struct CONNECT * connect_data){
/*
Turns data player_type, username, password into byte array and fills uart_bytes and sends to PC over UART.
*/
}
/*
Exactly the same for the play functions.
*/
这一切都按预期工作。我正在观察正在发送的数据,它与调用该函数的内容一致。这表明,如果正确给出数据,函数本身没有问题。
当连接或某些播放命令出现延迟或错误时,可能会出现问题,如果失败,所有其他命令也会失败,因为它在错误的命令上断开连接。在这里,我想到了使用 freeRTOS。
这个想法是在库中有一个任务,该任务将从队列中获取数据并根据该数据调用相应的命令。当用户从main.c调用函数时,该函数将简单地创建该命令的结构,将其存储在队列中并完成。然后队列将有挂起的命令,任务可以简单地从这些命令中逐个获取并执行适当的发送函数。
这是将存储在队列中的结构。
struct COMMAND{ // Used to hold commands into a queue for the task to execute
uint8_t command_type;
struct CONNECT * connect_data;
struct PLAY * play_data;
}
下面是保存命令的队列。
QueueHandle_t xCommandQueue; // global variable
void start_tasks(void){
xCommandQueue = xQueueCreate(32, sizeof(struct COMMAND*));
/* Creating a task */
xTaskCreate( prvLIBTask, "LibTask", configMINIMAL_STACK_SIZE, NULL, TASK_PRIORITY, &xTaskHandle );
}
这是将运行的任务。
void prvLIBTask( void *pvParameters ) // Task that receives from the queue and sends command
{
struct COMMAND* rec_command;
/* Remove compiler warning about unused parameter. */
( void ) pvParameters;
for( ;; )
{
/* Wait until something is received from the queue */
while(!xQueueReceive( xCommandQueue, &rec_command, portMAX_DELAY ));
if (rec_command->command_type == 0x00)
{
send_connect_struct(&(*rec_command->connect_data));
free(rec_command);
}
else if (rec_command->command_type == 0x01)
{
send_play_struct(&(*rec_command->play_data));
free(rec_command);
}
}
}
从主节点(连接和播放)调用的函数如下所示。
void connect(uint8_t player_type, char* username, char* password){
struct CONNECT *connect_data = malloc(sizeof(struct CONNECT));
connect_data->player_type = player_type;
connect_data->username = malloc(strlen(username)+1);
memcpy(connect->data.username, username, strlen(connect_data.username)+1);
connect_data->password = malloc(strlen(password)+1);
memcpy(connect_data->password, password, strlen(connect_data.password)+1);
struct COMMAND* latest_command = malloc(sizeof(struct COMMAND));
latest_command->command_type = 0x00;
latest_command->connect_data = &(*connect_data);
xQueueSend( xCommandQueue, &(latest_command), portMAX_DELAY );
}
/*
Almost exactly the same process for play.
Note that in the send_connect_struct and send_play_struct, at the end of the function I am freeing all the char* fields first and then the passed structure.
Example:
free(connect_data->username);
free(connect_data->password);
free(connect_data);
Since the queue will copy the contents of the pointer, the receive end of the queue will see the actual memory location of the allocated data and therefore can free it at the end of the execution.
*/
问题是 CONNECT 命令运行良好,我看到发送了正确的数据,但 PLAY 命令存在问题,有时第一个 PLAY 会去,但第二个会弄乱数据。有时它甚至在 CONNECT 函数之后卡住,因为我看到该函数甚至没有退出 play()。这绝对是动态内存分配的东西,但我不知道我做错了什么。
最奇怪的是,当我不分配结构字段而只是说例如(connect()的一部分):
struct CONNECT connect_data = malloc(sizeof(struct CONNECT));
connect_data->player_type = player_type;
connect_data->username = username;
它确实为连接和播放发送了正确的数据,但这不可能,因为结构的指针将指向堆栈内存(它保存用户名并从传递给函数的参数传递),对吧?
你在这里看到错误了吗?
编辑:
以下是我调用的函数:
connect(0x00, "Mike", "Mike123");
play(0x00, "move", "right");
play(0x00, "move", "right-up");
play(0x00, "move", "right-down");
这是队列:
xCommandQueue = xQueueCreate(32, sizeof(struct COMMAND));
这是接收任务:
void prvLIBTask( void *pvParameters ) // Task that receives from the queue and sends command
{
struct COMMAND rec_command;
/* Remove compiler warning about unused parameter. */
( void ) pvParameters;
for( ;; )
{
/* Wait until something is received from the queue */
while(!xQueueReceive( xCommandQueue, &rec_command, portMAX_DELAY ));
if (rec_command.command_type == 0x00)
{
/* functions to print structure field */
send_connect_struct(rec_command.connect_data);
}
else if (rec_command.command_type == 0x01)
{
/* functions to print structure field */
send_play_struct(rec_command.play_data);
}
}
}
以下是功能:
void connect(uint8_t player_type, char* username, char* password){
struct CONNECT *connect_data = malloc(sizeof(struct CONNECT));
connect_data->player_type = player_type;
connect_data->username = malloc(strlen(username)+1);
memcpy(connect_data->username, username, strlen(username)+1);
connect_data->password = malloc(strlen(password)+1);
memcpy(connect_data->password, password, strlen(password)+1);
struct COMMAND* latest_command = malloc(sizeof(struct COMMAND));
latest_command->command_type = 0x00;
latest_command->connect_data = connect_data;
xQueueSend( xCommandQueue, latest_command, portMAX_DELAY );
}
void play(uint8_t player_type, char* command, char* direction){
struct PLAY *play_data = malloc(sizeof(struct PLAY));
play_data->player_type = player_type;
play_data->command = malloc(strlen(command)+1);
memcpy(play_data.command, command, strlen(command)+1);
play_data->direction = malloc(strlen(direction)+1);
memcpy(play_data->direction, direction, strlen(direction)+1);
struct COMMAND* latest_command = malloc(sizeof(struct COMMAND));
latest_command->command_type = 0x01;
latest_command->play_data = play_data;
xQueueSend( xCommandQueue, latest_command, portMAX_DELAY );
}
结果:
从队列接收后立即打印函数的结果:
CONNECT
type = 0x00
username = Mike
password = Mike123
然后代码块在发布函数中的某个位置。我没有收到任何发布消息。
但是,如果我不为发布中的 char* 字段分配,而是使用:
play_data->player_type = player_type;
play_data->player_type = command;
play_data->player_type = direction;
我得到以下输出:
CONNECT
type = 0x00
username = Mike
password = Mike123
PUBLISH
type = 0x00
username = move
password = right
PUBLISH
type = 0x00
username = move
password = right-up
PUBLISH
type = 0x00
username = move
password = 0
所以前两个发布是可以的,但第二个说 0。我不明白这一点,因为它们是由相同的函数创建的。
当像前三种情况一样,当给出正确的数据时,send_connect_struc和send_play_struc工作正常。这两个函数都属于此模板:
void send_play_struc(struc PLAY* play_data) {
/* fill uart_bytes */
free(play_data->command);
free(play_data->direction);
free(play_data);
}
函数connect()
有一些问题:
void connect(uint8_t player_type, char* username, char* password){
// you must check for NULL
struct CONNECT *connect_data = malloc(sizeof(struct CONNECT));
connect_data->player_type = player_type;
// you must check for NULL
connect_data->username = malloc(strlen(username)+1);
// this line is wrong
//memcpy(connect->data.username, username, strlen(connect_data.username)+1);
// corrected version
memcpy(connect_data->username, username, strlen(username) + 1);
// you must check for NULL
connect_data->password = malloc(strlen(password)+1);
// this line is wrong
//memcpy(connect_data->password, password, strlen(connect_data.password)+1);
// corrected version
memcpy(connect_data->password, password, strlen(password) + 1);
// you must check NULL
struct COMMAND* latest_command = malloc(sizeof(struct COMMAND));
latest_command->command_type = 0x00;
// NOTE: `&(*p)` is equivalent to `p`
latest_command->connect_data = &(*connect_data);
// here you must pass `latest_command` as argument, not `&(latest_command)`
xQueueSend( xCommandQueue, &(latest_command), portMAX_DELAY );
}
编辑:您创建队列的代码也不正确:由于您正在推送类型struct COMMAND
的项目,因此创建队列的代码应该是:
xCommandQueue = xQueueCreate(32, sizeof(struct COMMAND)); // this will hold 32 items of type `struct COMMAND`
此外,如果此代码看起来有效:
// NOTE: DON'T DO THIS
connect_data.username = username;
connect_data.password = password;
。这是因为您可能将字符串文字作为参数传递(因此username
和password
是字符串文字),并且这些通常在只读内存区域中分配,并且它们通常具有与程序本身相同的生存期(即:字符串文字在您从函数返回后不会变得无效,因为它们没有在堆栈上分配, 通常)。然而,这是一个危险且限制性的假设。
malloc()
有关的问题。您可以考虑使用pvPortMalloc()
而不是malloc()
(以及pvPortFree()
而不是free()
)。值得注意的是,pvPortMalloc()
是线程安全的,而malloc()
则不是。