手撸 串口交互命令行 及 AT应用层协议解析框架

        在嵌入式系统开发中,命令行接口(CLI)和AT命令解析是常见的需求。CLI提供了方便的调试接口,而AT命令则常用于模块间的通信控制。本文将介绍如何手动实现一个串口交互的命令行及AT应用层协议解析框架,适用于FreeRTOS系统。

流程图:

        这个回调函数 HAL_UART_RxCpltCallback 是用于处理 UART(通用异步收发传输器)接收到的数据。在接收数据的过程中会首先对接收到的字符进行判断,并且判断接收缓冲区是否还有空间,如果已满,则调用 ProcessReceivedFrame 处理数据,并在每次接受满之后重启UART中断。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    // 用于检查当前触发中断的 UART 实例是否是我们期望处理的那个实例 xPort。
    if (huart->Instance == xPort.Instance)
    {
        // 如果这是接收到的第一个字节,清空接收缓冲区
        if (rx_index == 0)
        {
            //如果当前接收到的数据是本次接收的第一个字节(rx_index 为 0),
            //则将接收缓冲区清空。
            memset(rx_buffer, '\0', xPort_RX_BUFFER_SIZE);
        }

        // 如果接收到回车符 '\r',目前没有处理逻辑
        if (received_char == '\r') // || cRxedChar == '\r'
        {
            // 这里可以添加处理回车符的逻辑
        }
        // 如果接收到换行符 '\n'
        else if (received_char == '\n') // || cRxedChar == '\r'
        {
            if (rx_index != 0)
            {
                // 处理接收到的一帧数据
                // 在此处添加处理逻辑,例如将数据复制到另一个缓冲区或进行解析
                ProcessReceivedFrame(rx_buffer, &rx_index);
            }
        }
        else
        {
            // 将接收到的字符存储到接收缓冲区
            rx_buffer[rx_index++] = (char)received_char;

            // 检查接收缓冲区是否已满
            if (rx_index >= xPort_RX_BUFFER_SIZE)
            {
                // 处理接收到的一帧数据
                ProcessReceivedFrame(rx_buffer, &rx_index);
            }
        }

        // 重新启动 UART 接收中断,以接收下一个字节
        HAL_UART_Receive_IT(&xPort, &received_char, 1);
    }
}

        结合之前对 HAL_UART_RxCpltCallback 函数的理解,现在我们详细解释如何通过ProcessReceivedFrame 函数处理接收到的数据:

        首先通过 osPoolAlloc 从内存池中分配内存,以存储接收到的数据,接着使用 memset 函数将分配的内存初始化为零。然后将当前帧数据的长度存储到 message->len 中,并使用 strncpy 函数将缓冲区的数据拷贝到 message->buff 中,最后将拷贝的数据放入消息队列中,等待处理。重置缓冲区长度指针 plength 为 0,表示缓冲区已处理完毕。使用 memset 函数将缓冲区清空,以准备接收新的数据。

void ProcessReceivedFrame(char *buffer, uint8_t* plength)
{
    // // 处理接收到的一帧数据
    // // 例如,打印接收到的数据
    // buffer[length - 2] = '\0'; // 替换 "\r\n" 为字符串终止符
    // printf("Received frame: %s\n", buffer);
    // osMessagePut(uartQueueHandle, (uint32_t)buffer, osWaitForever);

    USART_Msg_Def *message;
    message = (USART_Msg_Def *)osPoolAlloc(uartmsgPoolHandle); // 申请内存
    //osPoolAlloc There is dirt in this memory pool
    memset(message->buff, '\0', xPort_RX_BUFFER_SIZE);
    message->len = *plength;
    strncpy(message->buff, buffer, message->len);                     // 数据拷贝
    osMessagePut(uartQueueHandle, (uint32_t)message, osWaitForever); // 写入队列
    *plength = 0;
    memset(buffer, '\0', xPort_RX_BUFFER_SIZE);
}

vUARTCommandConsoleStart 函数通过以下步骤来启动 UART 命令控制台任务:

  • 注册 CLI 命令:调用 vRegisterSampleCLICommands 函数,注册一些示例 CLI 命令,这些命令将在命令控制台任务中使用。
  • 创建一个互斥量 xTxMutex,用于保护对 UART 发送操作的访问,确保线程安全。使用 configASSERT 确保互斥量创建成功。如果创建失败,程序将进入断言。
  • 使用 xTaskCreate 创建一个新的任务,该任务将实现 UART 命令控制台。
  • 启动 UART 接收功能,以便命令控制台可以接收来自 UART 的数据。
void vUARTCommandConsoleStart(void)
{
    uint16_t usStackSize = 512;
    UBaseType_t uxPriority = 0;

    // 注册示例 CLI 命令
    vRegisterSampleCLICommands();

    // 创建用于访问 UART Tx 的信号量(互斥量)
    xTxMutex = xSemaphoreCreateMutex();
    configASSERT(xTxMutex);

    // 创建处理命令控制台的任务
    xTaskCreate(prvUARTCommandConsoleTask, // 实现命令控制台的任务函数
                "CLI",                     // 任务名称,用于调试
                usStackSize,               // 分配给任务的堆栈大小
                NULL,                      // 任务参数,这里未使用,传递 NULL
                uxPriority,                // 任务优先级
                NULL);                     // 任务句柄,这里未使用,传递 NULL

    // 启动 UART 接收
    StartUARTReception();
}

 prvUARTCommandConsoleTask 函数是一个 FreeRTOS 任务,用于处理 UART 命令控制台。这个任务从消息队列中读取接收到的 UART 数据,并将其传递给命令解释器进行处理。

static void prvUARTCommandConsoleTask(void *pvParameters)
{
    char *pcOutputString;
    BaseType_t xReturned;

    (void)pvParameters;

    /* Obtain the address of the output buffer. Note there is no mutual
    exclusion on this buffer as it is assumed only one command console interface
    will be used at any one time. */
    pcOutputString = FreeRTOS_CLIGetOutputBuffer();

    /* Send the welcome message. */
    vSerialPutString(pcWelcomeMessage, (uint16_t)strlen((char*)pcWelcomeMessage));
    
    for (;;)
    {
        osEvent eEventTmp;
        USART_Msg_Def *message;
        
        // 等待从消息队列中接收消息
        eEventTmp = osMessageGet(uartQueueHandle, 0);
        if (eEventTmp.status == osEventMessage)
        {
            // 处理接收到的数据帧
            message = (USART_Msg_Def *)eEventTmp.value.p;

            char cInputString[cmdMAX_INPUT_SIZE];
            memcpy(cInputString, message->buff, cmdMAX_INPUT_SIZE);
            int8_t cInputLen = message->len;
            
            osPoolFree(uartmsgPoolHandle, message); // 释放内存
            
#if debug
            char tmp[50];
            sprintf(tmp, "len=%d,strlen=%d\n", cInputLen, strlen(cInputString));
            HAL_UART_Transmit(&xPort, (const uint8_t *)tmp, strlen(tmp), portMAX_DELAY);
#endif
            if (xSemaphoreTake(xTxMutex, cmdMAX_MUTEX_WAIT) == pdPASS)
            {
                vSerialPutString(cInputString, cInputLen);
                
                vSerialPutString(pcNewLine, (uint16_t)strlen((char*)pcNewLine));

                do
                {
                    /* Get the next output string from the command interpreter. */
                    xReturned = FreeRTOS_CLIProcessCommand(cInputString, pcOutputString, configCOMMAND_INT_MAX_OUTPUT_SIZE);

                    /* Write the generated string to the UART. */
                    vSerialPutString(pcOutputString, (uint16_t)strlen((char*)pcOutputString));

                } while (xReturned != pdFALSE);

                vSerialPutString("\r\n>", 3);
                memset(cInputString, 0x00, cmdMAX_INPUT_SIZE);
                
                /* Must ensure to give the mutex back. */
                xSemaphoreGive(xTxMutex);
            }
        }
        osDelay(1);
    }
}

这个任务实现了一个简单的 UART 命令控制台,通过消息队列接收 UART 输入数据,使用命令解释器处理输入命令,并将结果输出到 UART。任务使用互斥量确保对 UART 发送操作的线程安全。通过这种方式,系统可以处理并执行来自 UART 的命令,同时确保并发访问的安全性。

 

下面这段代码注册了两个CLI(命令行接口)命令:EchoAT

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"

/* Standard includes. */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* FreeRTOS+CLI includes. */
#include "FreeRTOS_CLI.h"

#include "CLI.h"

static BaseType_t prvATCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString);
static const CLI_Command_Definition_t xATCommand =
	{
		"AT",
		"\r\nATcmd:\r\n +RST +MODE=0/1 +M=1/2,0~180\r\n",
		prvATCommand, /* The function to run. */
		0			  /* The user can enter any number of commands. */
};
static BaseType_t prvATCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString)
{
	BaseType_t xReturn;
	char *p = pcCommandString+2;

	ATcmdAnalyse(p);

	strncat(pcWriteBuffer, "\r\nOK", strlen("\r\nOK"));
	// strncpy( pcWriteBuffer, "what can i say, man!!!\r\n", xWriteBufferLen );
	xReturn = pdFALSE;
	return xReturn;
}

/*-----------------------------------------------------------*/

/*
 * Implements the task-stats command.
 */
static BaseType_t prvParameterEchoCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString);

/* Structure that defines the "echo_parameters" command line command.  This
takes a variable number of parameters that the command simply echos back one at
a time. */
static const CLI_Command_Definition_t xParameterEcho =
	{
		"Echo",
		"\r\nEcho <...>:\r\n Take variable number of parameters, echos each in turn\r\n",
		prvParameterEchoCommand, /* The function to run. */
		-1						 /* The user can enter any number of commands. */
};

static BaseType_t prvParameterEchoCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString)
{
	const char *pcParameter;
	BaseType_t xParameterStringLength, xReturn;
	static UBaseType_t uxParameterNumber = 0;

	/* Remove compile time warnings about unused parameters, and check the
	write buffer is not NULL.  NOTE - for simplicity, this example assumes the
	write buffer length is adequate, so does not check for buffer overflows. */
	(void)pcCommandString;
	(void)xWriteBufferLen;
	configASSERT(pcWriteBuffer);

	if (uxParameterNumber == 0)
	{
		/* The first time the function is called after the command has been
		entered just a header string is returned. */
		sprintf(pcWriteBuffer, "The parameters were:\r\n");

		/* Next time the function is called the first parameter will be echoed
		back. */
		uxParameterNumber = 1U;

		/* There is more data to be returned as no parameters have been echoed
		back yet. */
		xReturn = pdPASS;
	}
	else
	{
		/* Obtain the parameter string. */
		pcParameter = FreeRTOS_CLIGetParameter(
			pcCommandString,		/* The command string itself. */
			uxParameterNumber,		/* Return the next parameter. */
			&xParameterStringLength /* Store the parameter string length. */
		);

		if (pcParameter != NULL)
		{
			/* Return the parameter string. */
			memset(pcWriteBuffer, 0x00, xWriteBufferLen);
			sprintf(pcWriteBuffer, "%d: ", (int)uxParameterNumber);
			strncat(pcWriteBuffer, (char *)pcParameter, (size_t)xParameterStringLength);
			strncat(pcWriteBuffer, "\r\n", strlen("\r\n"));

			/* There might be more parameters to return after this one. */
			xReturn = pdTRUE;
			uxParameterNumber++;
		}
		else
		{
			/* No more parameters were found.  Make sure the write buffer does
			not contain a valid string. */
			pcWriteBuffer[0] = 0x00;

			/* No more data to return. */
			xReturn = pdFALSE;

			/* Start over the next time this command is executed. */
			uxParameterNumber = 0;
		}
	}

	return xReturn;
}
/*-----------------------------------------------------------*/

void vRegisterSampleCLICommands(void)
{
	/* Register all the command line commands defined immediately above. */
	FreeRTOS_CLIRegisterCommand(&xParameterEcho);
	FreeRTOS_CLIRegisterCommand(&xATCommand);

}

1. AT 命令

static BaseType_t prvATCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString);
static const CLI_Command_Definition_t xATCommand =
{
    "AT",
    "\r\nATcmd:\r\n +RST +MODE=0/1 +M=1/2,0~180\r\n",
    prvATCommand, /* The function to run. */
    0              /* The user can enter any number of commands. */
};
  • xATCommand 是一个 CLI_Command_Definition_t 结构体,定义了一个名为 AT 的命令。
  • 第一个参数是命令名称 "AT"
  • 第二个参数是该命令的帮助信息,用于显示在命令行上。
  • 第三个参数是该命令的处理函数 prvATCommand
  • 第四个参数表示用户可以输入任意数量的该命令。

2. AT 命令处理函数

static BaseType_t prvATCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString)
{
    BaseType_t xReturn;
    char *p = pcCommandString[2];

    ATcmdAnalyse(p);

    strncat(pcWriteBuffer, "\r\nOK", strlen("\r\nOK"));
    xReturn = pdFALSE;
    return xReturn;
}
  • prvATCommand 是处理 AT 命令的函数。
  • 它接收三个参数:pcWriteBuffer(写缓冲区),xWriteBufferLen(写缓冲区长度)和 pcCommandString(命令字符串)。
  • 函数将命令字符串的第三个字符传递给 ATcmdAnalyse 函数进行分析处理。
  • 处理完毕后,将字符串 "\r\nOK" 追加到写缓冲区中,并返回 pdFALSE

 3. Echo 命令

static BaseType_t prvParameterEchoCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString);

static const CLI_Command_Definition_t xParameterEcho =
{
    "Echo",
    "\r\nEcho <...>:\r\n Take variable number of parameters, echos each in turn\r\n",
    prvParameterEchoCommand, /* The function to run. */
    -1                        /* The user can enter any number of commands. */
};
  • xParameterEcho 是一个 CLI_Command_Definition_t 结构体,定义了一个名为 Echo 的命令。
  • 第一个参数是命令名称 "Echo"
  • 第二个参数是该命令的帮助信息,用于显示在命令行上。
  • 第三个参数是该命令的处理函数 prvParameterEchoCommand
  • 第四个参数表示用户可以输入任意数量的该命令。

4. Echo 命令处理函数 

static BaseType_t prvParameterEchoCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString)
{
    const char *pcParameter;
    BaseType_t xParameterStringLength, xReturn;
    static UBaseType_t uxParameterNumber = 0;

    // 省略部分代码...

    return xReturn;
}
  • prvParameterEchoCommand 是处理 Echo 命令的函数。
  • 它接收三个参数:pcWriteBuffer(写缓冲区),xWriteBufferLen(写缓冲区长度)和 pcCommandString(命令字符串)。
  • 函数通过调用 FreeRTOS_CLIGetParameter 逐个获取命令参数,并将其逐个写入写缓冲区中。
  • 如果没有更多参数,返回 pdFALSE

5. 注册命令函数

void vRegisterSampleCLICommands(void)
{
    FreeRTOS_CLIRegisterCommand(&xParameterEcho);
    FreeRTOS_CLIRegisterCommand(&xATCommand);
}
  • vRegisterSampleCLICommands 函数用于注册以上定义的两个命令。
  • 调用 FreeRTOS_CLIRegisterCommand 函数注册 EchoAT 命令。

这些命令通过 FreeRTOS 的 CLI 模块注册,可以在命令行界面中使用。

这段代码实现了一个简单的命令行接口(CLI),允许通过注册命令的方式来扩展功能。主要包括命令注册、命令处理和帮助命令的实现。

/* Standard includes. */
#include <string.h>
#include <stdint.h>

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"

/* Utils includes. */
#include "FreeRTOS_CLI.h"

typedef struct xCOMMAND_INPUT_LIST
{
	const CLI_Command_Definition_t *pxCommandLineDefinition;
	struct xCOMMAND_INPUT_LIST *pxNext;
} CLI_Definition_List_Item_t;

/*
 * The callback function that is executed when "help" is entered.  This is the
 * only default command that is always present.
 */
static BaseType_t prvHelpCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString);

/*
 * Return the number of parameters that follow the command name.
 */
static int8_t prvGetNumberOfParameters(const char *pcCommandString);

/* The definition of the "help" command.  This command is always at the front
of the list of registered commands. */
static const CLI_Command_Definition_t xHelpCommand =
	{
		"Help",
		"\r\nHelp:\r\n Lists all the registered commands\r\n",
		prvHelpCommand,
		0};

/* The definition of the list of commands.  Commands that are registered are
added to this list. */
static CLI_Definition_List_Item_t xRegisteredCommands =
	{
		&xHelpCommand, /* The first command in the list is always the help command, defined in this file. */
		NULL		   /* The next pointer is initialised to NULL, as there are no other registered commands yet. */
};

/* A buffer into which command outputs can be written is declared here, rather
than in the command console implementation, to allow multiple command consoles
to share the same buffer.  For example, an application may allow access to the
command interpreter by UART and by Ethernet.  Sharing a buffer is done purely
to save RAM.  Note, however, that the command console itself is not re-entrant,
so only one command interpreter interface can be used at any one time.  For that
reason, no attempt at providing mutual exclusion to the cOutputBuffer array is
attempted.

configAPPLICATION_PROVIDES_cOutputBuffer is provided to allow the application
writer to provide their own cOutputBuffer declaration in cases where the
buffer needs to be placed at a fixed address (rather than by the linker). */
static char cOutputBuffer[configCOMMAND_INT_MAX_OUTPUT_SIZE];

/*-----------------------------------------------------------*/

BaseType_t FreeRTOS_CLIRegisterCommand(const CLI_Command_Definition_t *const pxCommandToRegister)
{
	static CLI_Definition_List_Item_t *pxLastCommandInList = &xRegisteredCommands;
	CLI_Definition_List_Item_t *pxNewListItem;
	BaseType_t xReturn = pdFAIL;

	/* Check the parameter is not NULL. */
	configASSERT(pxCommandToRegister);

	/* Create a new list item that will reference the command being registered. */
	pxNewListItem = (CLI_Definition_List_Item_t *)pvPortMalloc(sizeof(CLI_Definition_List_Item_t));
	configASSERT(pxNewListItem);

	if (pxNewListItem != NULL)
	{
		taskENTER_CRITICAL();
		{
			/* Reference the command being registered from the newly created
			list item. */
			pxNewListItem->pxCommandLineDefinition = pxCommandToRegister;

			/* The new list item will get added to the end of the list, so
			pxNext has nowhere to point. */
			pxNewListItem->pxNext = NULL;

			/* Add the newly created list item to the end of the already existing
			list. */
			pxLastCommandInList->pxNext = pxNewListItem;

			/* Set the end of list marker to the new list item. */
			pxLastCommandInList = pxNewListItem;
		}
		taskEXIT_CRITICAL();

		xReturn = pdPASS;
	}

	return xReturn;
}
/*-----------------------------------------------------------*/

BaseType_t FreeRTOS_CLIProcessCommand(const char *const pcCommandInput, char *pcWriteBuffer, size_t xWriteBufferLen)
{
	static const CLI_Definition_List_Item_t *pxCommand = NULL;
	BaseType_t xReturn = pdTRUE;
	const char *pcRegisteredCommandString;
	size_t xCommandStringLength;

	/* Note:  This function is not re-entrant.  It must not be called from more
	thank one task. */

	if (pxCommand == NULL)
	{
		/* Search for the command string in the list of registered commands. */
		for (pxCommand = &xRegisteredCommands; pxCommand != NULL; pxCommand = pxCommand->pxNext)
		{
			pcRegisteredCommandString = pxCommand->pxCommandLineDefinition->pcCommand;
			xCommandStringLength = strlen(pcRegisteredCommandString);

			/* To ensure the string lengths match exactly, so as not to pick up
			a sub-string of a longer command, check the byte after the expected
			end of the string is either the end of the string or a space before
			a parameter. */
			if ((pcCommandInput[xCommandStringLength] == ' ') || (pcCommandInput[xCommandStringLength] == 0x00))
			{
				if (strncmp(pcCommandInput, pcRegisteredCommandString, xCommandStringLength) == 0)
				{
					/* The command has been found.  Check it has the expected
					number of parameters.  If cExpectedNumberOfParameters is -1,
					then there could be a variable number of parameters and no
					check is made. */
					if (pxCommand->pxCommandLineDefinition->cExpectedNumberOfParameters >= 0)
					{
						if (prvGetNumberOfParameters(pcCommandInput) != pxCommand->pxCommandLineDefinition->cExpectedNumberOfParameters)
						{
							xReturn = pdFALSE;
						}
					}

					break;
				}
			}
			else if ((strncmp("AT", pcRegisteredCommandString, 2) == 0) && (strncmp("AT", pcCommandInput, 2) == 0))
			{
				break;
			}
		}
	}

	if ((pxCommand != NULL) && (xReturn == pdFALSE))
	{
		/* The command was found, but the number of parameters with the command
		was incorrect. */
		strncpy(pcWriteBuffer, "error cmd para(s).  Enter \"Help\".\r\n\r\n", xWriteBufferLen);
		pxCommand = NULL;
	}
	else if (pxCommand != NULL)
	{
		/* Call the callback function that is registered to this command. */
		xReturn = pxCommand->pxCommandLineDefinition->pxCommandInterpreter(pcWriteBuffer, xWriteBufferLen, pcCommandInput);

		/* If xReturn is pdFALSE, then no further strings will be returned
		after this one, and	pxCommand can be reset to NULL ready to search
		for the next entered command. */
		if (xReturn == pdFALSE)
		{
			pxCommand = NULL;
		}
	}
	else
	{
		/* pxCommand was NULL, the command was not found. */
		strncpy(pcWriteBuffer, "Command not recognised.  Enter 'help' to view a list of available commands.\r\n\r\n", xWriteBufferLen);
		xReturn = pdFALSE;
	}

	return xReturn;
}
/*-----------------------------------------------------------*/

char *FreeRTOS_CLIGetOutputBuffer(void)
{
	return cOutputBuffer;
}
/*-----------------------------------------------------------*/

const char *FreeRTOS_CLIGetParameter(const char *pcCommandString, UBaseType_t uxWantedParameter, BaseType_t *pxParameterStringLength)
{
	UBaseType_t uxParametersFound = 0;
	const char *pcReturn = NULL;

	*pxParameterStringLength = 0;

	while (uxParametersFound < uxWantedParameter)
	{
		/* Index the character pointer past the current word.  If this is the start
		of the command string then the first word is the command itself. */
		while (((*pcCommandString) != 0x00) && ((*pcCommandString) != ' '))
		{
			pcCommandString++;
		}

		/* Find the start of the next string. */
		while (((*pcCommandString) != 0x00) && ((*pcCommandString) == ' '))
		{
			pcCommandString++;
		}

		/* Was a string found? */
		if (*pcCommandString != 0x00)
		{
			/* Is this the start of the required parameter? */
			uxParametersFound++;

			if (uxParametersFound == uxWantedParameter)
			{
				/* How long is the parameter? */
				pcReturn = pcCommandString;
				while (((*pcCommandString) != 0x00) && ((*pcCommandString) != ' '))
				{
					(*pxParameterStringLength)++;
					pcCommandString++;
				}

				if (*pxParameterStringLength == 0)
				{
					pcReturn = NULL;
				}

				break;
			}
		}
		else
		{
			break;
		}
	}

	return pcReturn;
}
/*-----------------------------------------------------------*/

static BaseType_t prvHelpCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString)
{
	static const CLI_Definition_List_Item_t *pxCommand = NULL;
	BaseType_t xReturn;

	(void)pcCommandString;

	if (pxCommand == NULL)
	{
		/* Reset the pxCommand pointer back to the start of the list. */
		pxCommand = &xRegisteredCommands;
	}

	/* Return the next command help string, before moving the pointer on to
	the next command in the list. */
	strncpy(pcWriteBuffer, pxCommand->pxCommandLineDefinition->pcHelpString, xWriteBufferLen);
	pxCommand = pxCommand->pxNext;

	if (pxCommand == NULL)
	{
		/* There are no more commands in the list, so there will be no more
		strings to return after this one and pdFALSE should be returned. */
		xReturn = pdFALSE;
	}
	else
	{
		xReturn = pdTRUE;
	}

	return xReturn;
}
/*-----------------------------------------------------------*/

static int8_t prvGetNumberOfParameters(const char *pcCommandString)
{
	int8_t cParameters = 0;
	BaseType_t xLastCharacterWasSpace = pdFALSE;

	/* Count the number of space delimited words in pcCommandString. */
	while (*pcCommandString != 0x00)
	{
		if ((*pcCommandString) == ' ')
		{
			if (xLastCharacterWasSpace != pdTRUE)
			{
				cParameters++;
				xLastCharacterWasSpace = pdTRUE;
			}
		}
		else
		{
			xLastCharacterWasSpace = pdFALSE;
		}

		pcCommandString++;
	}

	/* If the command string ended with spaces, then there will have been too
	many parameters counted. */
	if (xLastCharacterWasSpace == pdTRUE)
	{
		cParameters--;
	}

	/* The value returned is one less than the number of space delimited words,
	as the first word should be the command itself. */
	return cParameters;
}
  • 注册命令

    调用 FreeRTOS_CLIRegisterCommand 函数注册新的命令。这个函数会将新的命令节点添加到链表末尾。

  • 处理命令

    调用 FreeRTOS_CLIProcessCommand 函数处理输入的命令字符串。这个函数会遍历注册的命令链表,找到匹配的命令,并调用相应的回调函数处理。

  • 帮助命令

    当输入 Help 命令时,prvHelpCommand 回调函数会被调用,输出所有注册命令的帮助信息。

 

这段代码实现了一个简单的 AT 指令解析器。

#include "CLI.h"
#include "string.h"
// "\r\nATcmd:\r\n +RST +MODE=0/1 +M=1/2,0~180\r\n",
typedef enum
{
    AT = 0,
    MODE,
    M,
    RST,
    /**将指令添加到上面**/
    MAXCMDNUM
} ATCommand;

typedef enum 
{
	ATERROR = 0, 
	ATSUCCESS ,
	ATERRORCODE1,
}ATStatus;

typedef ATStatus (*pFuncCallback)(char *str);

typedef struct
{
    ATCommand ATCommandName;
    char *ATStr;                    // 发送的AT指令
    pFuncCallback ATCallback; // AT指令接收完成,指令处理回调函数
} ATCommandConfig;

ATStatus MODE_Callback(char *str);
ATStatus M_Callback(char *str);
ATStatus RST_Callback(char *str);

static const ATCommandConfig ATCommandList[] =
    {
        {MODE, "MODE", MODE_Callback},
        {M, "M", M_Callback},
        {RST, "RST", RST_Callback},
        NULL,
};

static void ATcmdGetRun(pATcmd, pATcmdLen, pATvalue)
{
    ATCommandConfig *pxCommand;
    const char *pcRegisteredCommandString;
    for (pxCommand = &ATCommandList[0]; pxCommand != NULL; pxCommand++)
    {
        pcRegisteredCommandString = pxCommand->ATStr;
        if (strncmp(pATcmd, pcRegisteredCommandString, pATcmdLen) == 0)
        {
            pxCommand->ATCallback(pATvalue);
        }
    }
}

void ATcmdAnalyse(char *pATcmd)
{
    if ('+' == pATcmd[0])
    {
        pATcmd++;
        char *pATvalue;
        uint8_t pATcmdLen = 0; // ATcmd lenth
        pATvalue = strstr(pATcmd, "=");
        if (pATvalue)
        {
            pATcmdLen = pATvalue - pATcmd; // ATcmd lenth
            pATvalue++;
        }
        else
        {
            pATvalue = NULL;
            pATcmdLen = strlen(pATcmd);
        }
        ATcmdGetRun(pATcmd, pATcmdLen, pATvalue);
    }
}


/*--------------------------------------------------
In fact, the last step callback function of 
the at instruction cannot directly control the hardware. 
There should be a third intermediate layer SDK
--------------------------------------------------*/
ATStatus MODE_Callback(char *str)
{
    //挂起舵机的任务
}
ATStatus M_Callback(char *str)
{
    //Sg90MotorCtl(舵机1/2  , 舵机角度) 
    //控制舵机运行到具体角度
    //内部需要对舵机能够转动的最大角度进行限位
}
ATStatus RST_Callback(char *str)
{
    //重启 mcu
}

让我们逐步解释它的功能和结构:

  1. 枚举类型定义:

    • ATCommand 枚举定义了一组 AT 指令的名称,包括 ATMODEMRST,以及一个特殊的枚举 MAXCMDNUM,用于表示指令数量上限。
  2. AT 状态枚举:

    • ATStatus 枚举定义了一组 AT 操作的状态,包括成功、失败以及可能的其他错误码。
  3. 函数指针定义:

    • pFuncCallback 是一个函数指针类型,指向一个接受 char* 参数并返回 ATStatus 类型的函数。
  4. AT 指令配置结构体:

    • ATCommandConfig 结构体定义了一个 AT 指令的配置,包括指令名称、发送的指令字符串以及指令完成时的回调函数。
  5. AT 指令配置列表:

    • ATCommandList 是一个数组,存储了所有 AT 指令的配置信息,包括指令名称、发送的指令字符串和回调函数。列表以 NULL 结尾。
  6. AT 指令解析函数:

    • ATcmdAnalyse 函数用于解析接收到的 AT 指令。它接收一个指向 AT 指令字符串的指针,并根据指令名称调用相应的回调函数。
  7. AT 指令回调函数:

    • MODE_CallbackM_CallbackRST_Callback 是对应于不同指令的回调函数,用于执行具体的操作。这些函数会根据指令参数进行不同的处理。
  8. AT 指令执行函数:

    • ATcmdGetRun 函数根据接收到的 AT 指令名称,在配置列表中查找对应的配置,并调用相应的回调函数执行操作。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/683170.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】顺序表专题(学习记录)

正文开始 课前预备 1. 课程目标 C语言语法基础到数据结构与算法&#xff0c;前⾯已经掌握并具备了扎实的C语言基础&#xff0c;为什么要学习数据结构课程&#xff1f;⸺通讯录项目 2. 需要的储备知识 简单了解&#xff0c;通讯录具备增加、删除、修改、查找联系⼈等操作。要想…

Linux进程无法被kill

说明&#xff1a;记录一次应用进程无法被kill的错误&#xff1b; 场景 在一次导出MySQL数据时&#xff0c;使用下面的命令&#xff0c;将数据库数据导出为.sql文件&#xff0c;数据量大&#xff0c;导出时间长&#xff0c;于是我就将服务器重启了。 mysqldump -u username -…

队列及其应用

实验内容 请设计一个简单的模拟银行排队系统&#xff0c;要求程序具有以下4项菜单&#xff1a; 1.取号。选择该菜单后&#xff0c;为客户产生一个排队号。 2.叫号。选择该菜单后&#xff0c;显示可服务的客户排队号。 3.查看队伍。从队首到队尾列出所有排队客户的排队号。 4.退…

Vue 学习笔记 总结

Vue.js 教程 | 菜鸟教程 (runoob.com) 放一下课上的内容 Vue练习 1、练习要求和实验2的用户注册一样&#xff0c;当用户输入后&#xff0c;能在下方显示用户输入的各项内容&#xff08;不需要实现【重置】按钮&#xff09; 2、实验报告中的实验小结部分来谈谈用JS、jQuery和…

流量分析——一、蚁剑流量特征

君衍. 一、Webshell特征流量分析二、环境介绍三、使用Wireshark进行流量分析1、环境说明2、HTTP追踪流分析3、蚁剑请求体中代码块解读 四、使用BurpSurite进行流量分析1、环境配置2、抓包分析 六、总结 一、Webshell特征流量分析 对于重保、护网等攻防演练的防守方来说&#x…

AIGC专栏11——EasyAnimateV2结构详解与Lora训练 最大支持768x768 144帧视频生成

AIGC专栏11——EasyAnimateV2结构详解与Lora训练 最大支持768x768 144帧视频生成 学习前言源码下载地址EasyAnimate V2简介技术储备Diffusion Transformer (DiT)Motion ModuleU-VITLora 算法细节算法组成视频VAE视频DIT 数据处理视频分割视频筛选视频描述 模型训练视频VAE视频D…

【数智化CIO展】吉家宠物CIO张志伟:深度挖掘数据价值是数字化发展趋势,才能实现企业精细化运营...

张志伟 本文由吉家宠物CIO张志伟投递并参与由数据猿联合上海大数据联盟共同推出的《2024中国数智化转型升级优秀CIO》榜单/奖项评选。丨推荐企业&#xff1a;观远数据 大数据产业创新服务媒体 ——聚焦数据 改变商业 中国“宠物经济”热潮不断攀升&#xff0c;国内宠物市场的竞…

InnoDB存储引擎非常重要的一个机制--MVCC(多版本并发控制)

Mysql是如何实现隔离性的&#xff1f;&#xff08;锁MVCC&#xff09; 隔离性是指一个事务内部的操作以及操作的数据对正在进行的其他事务是隔离的&#xff0c;并发执行的各个事务之间不能相互干扰。隔离性可以防止多个事务并发执行时&#xff0c;可能存在交叉执行导致数据的不…

Android 如何保证开启debug模式之后再启动

很多时候会需要debug看Android启动时候的一些数据&#xff0c;但很多时候会存在自己开启debug后app已经过了自己要debug的那段代码的时机了。 那么怎么样可以保证一定能让启动后不会错过自己要debug的那段代码执行的时机呢&#xff1f; 可以用下面这行命令&#xff0c;其中co…

记忆化搜索汇总

记忆化搜索简介 记忆化搜索&#xff08;Memoization Search&#xff09;&#xff1a;是一种通过存储已经遍历过的状态信息&#xff0c;从而避免对同一状态重复遍历的搜索算法。 记忆化搜索是动态规划的一种实现方式。在记忆化搜索中&#xff0c;当算法需要计算某个子问题的结果…

Nginx+Tomcat负载均衡、动静分离集群

目录 1.Nginx负载均衡 1.1 负载均衡概念 1.2 负载均衡原理 1.3 Nginx配置反向代理 1.3.1 反向代理概念 1.3.2 反向代理主要参数 2.Nginx动静分离 2.1 动静分离的概念 2.2 Nginx 静态处理优势 2.3 动静分离原理 3. NginxTomcat动静分离的实验设计 3.1 准备三台虚拟机…

Java速成要多久?这篇文章告诉你答案!

Java速成要多久&#xff1f;这篇文章告诉你答案&#xff01; Java作为一门用途广泛且经久不衰的编程语言&#xff0c;吸引了无数学习者的目光。许多人希望能够快速掌握Java&#xff0c;以便进入软件开发行业或者提升自身的竞争力。那么&#xff0c;Java速成究竟要多久呢&#x…

【遗传算法】【机器学习】【Python】常见交叉方法(二)、多点交叉和均匀交叉

一、遗传算法流程图 交叉过程即存在于上图的”交叉“&#xff08;crossover&#xff09;步骤中。 二、多点交叉 多点交叉的原理就是&#xff0c;随机地从父代两个基因型中&#xff0c;选择n个位点进行交换&#xff0c;其中n小于等于父代基因型长度&#xff08;假设双亲基因长…

基于小波变换和峰值搜索的光谱检测matlab仿真,带GUI界面

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于小波变换和峰值搜索的光谱检测matlab仿真,带GUI界面.对光谱数据的成分进行提取&#xff0c;分析CO2&#xff0c;SO2&#xff0c;CO以及CH4四种成分比例。 2.…

【越界写null字节】ACTF2023 easy-netlink

前言 最近在矩阵杯遇到了一道 generic netlink 相关的内核题&#xff0c;然后就简单学习了一下 generic netlink 相关概念&#xff0c;然后又找了一到与 generic netlink 相关的题目。简单来说 generic netlink 相关的题目仅仅是将用户态与内核态的交互方式从传统的 ioctl 变成…

使用from…import语句导入模块

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在使用import语句导入模块时&#xff0c;每执行一条import语句都会创建一个新的命名空间&#xff08;namespace&#xff09;&#xff0c;并且在该命名…

类的特殊成员函数

使用类的嵌套&#xff0c;并自定义析构函数 #include <iostream>using namespace std; class Per{ private:string name;int age;double hight;double weight; public:Per(string name,int age,double hight,double weight):name(name),age(age),hight(hight),weight(we…

当边缘计算用在定位设备

什么是边缘计算&#xff1f; 边缘计算是个比较高大上的概念&#xff0c;在这里就不提众多官方与非官方的定义了&#xff0c;只说说自己的理解。 边缘计算就是在最靠近物理设备的使用现场&#xff0c;利用有限的硬件资源&#xff0c;完成设备层数据采集、协议转换、数据上传、…

微信小程序开发的详细解读

目录 小程序的ID 小程序的项目结构 小程序调试基础库 小程序调试 小程序配置文件 Pages配置 Windows配置 tabbar配置 页面配置 项目配置文件 sitemap文件配置 样式与组件 小程序常用组件 轮播图组件 图片组件 Text组件 跳转方式 滚动方式 字体图表使用 背景…

用python写一个基于PyQt5和OpenAI的智能问答项目

摘要&#xff1a; 使用python写一个可以对话的智能问答机器人&#xff0c;界面是使用PyQt5写的&#xff0c;通过调用OpenAl的免费接口&#xff0c;实现实时聊天功能。 1.申请免费的API key 前往页面https://github.com/chatanywhere/GPT_API_free 点击下面链接&#xff1a; …