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

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

流程图:

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

cpp 复制代码
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 函数将缓冲区清空,以准备接收新的数据。

cpp 复制代码
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 的数据。
cpp 复制代码
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 数据,并将其传递给命令解释器进行处理。

cpp 复制代码
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

cpp 复制代码
/* 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 命令
cpp 复制代码
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
  • 第四个参数表示用户可以输入任意数量的该命令。
  1. AT 命令处理函数
cpp 复制代码
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
  1. Echo 命令
cpp 复制代码
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
  • 第四个参数表示用户可以输入任意数量的该命令。
  1. Echo 命令处理函数
cpp 复制代码
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
  1. 注册命令函数
cpp 复制代码
void vRegisterSampleCLICommands(void)
{
    FreeRTOS_CLIRegisterCommand(&xParameterEcho);
    FreeRTOS_CLIRegisterCommand(&xATCommand);
}
  • vRegisterSampleCLICommands 函数用于注册以上定义的两个命令。
  • 调用 FreeRTOS_CLIRegisterCommand 函数注册 EchoAT 命令。

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

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

cpp 复制代码
/* 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 指令解析器。

cpp 复制代码
#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 指令名称,在配置列表中查找对应的配置,并调用相应的回调函数执行操作。
相关推荐
yutian06068 小时前
Keil MDK下载程序后MCU自动重启设置
单片机·嵌入式硬件·keil
析木不会编程11 小时前
【小白51单片机专用教程】protues仿真独立按键控制LED
单片机·嵌入式硬件·51单片机
枯无穷肉15 小时前
stm32制作CAN适配器4--WinUsb的使用
stm32·单片机·嵌入式硬件
不过四级不改名67715 小时前
基于HAL库的stm32的can收发实验
stm32·单片机·嵌入式硬件
嵌入式大圣16 小时前
单片机UDP数据透传
单片机·嵌入式硬件·udp
云山工作室16 小时前
基于单片机的视力保护及身姿矫正器设计(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·毕设
嵌入式-老费16 小时前
基于海思soc的智能产品开发(mcu读保护的设置)
单片机·嵌入式硬件
qq_3975623118 小时前
MPU6050 , 设置内部低通滤波器,对于输出数据的影响。(简单实验)
单片机
liyinuo201718 小时前
嵌入式(单片机方向)面试题总结
嵌入式硬件·设计模式·面试·设计规范
艺术家天选18 小时前
STM32点亮LED灯
stm32·单片机·嵌入式硬件