【STM32】RTT-Studio中HAL库开发教程十三:MSH串口组件

文章目录


一、基本概念

  在嵌入式系统开发中,与上位机进行串口通信是非常常见的场景。上位机可以通过串口发送指令或者数据给嵌入式设备,而嵌入式设备需要可靠地接收并解析这些数据,以执行相应的操作。然而,在串口通信过程中,上位机发送数据的速率往往与嵌入式设备接收和处理数据的速率不一致,这就可能导致数据的丢失或者误解析。

  为了解决这个问题,决定设计并实现一个环形缓冲区来进行数据接收管理。环形缓冲区是一种高效的数据结构,适用于数据产生速率快于消费速率的场景。它具有固定大小的缓冲区,并且可以循环利用空间,保证数据的连续存储和有效利用。

1.UART 空闲中断(IDLE)

  • 当串口 RX 线上 连续一段时间没有数据接收,USART 外设触发 空闲中断;
  • 空闲中断的主要作用是通知数据传输完成或当前帧结束。

2.DMA 接收模式

  • DMA(Direct Memory Access) 自动将串口接收到的数据存储到指定缓冲区;
  • CPU 不再需要逐字节处理接收数据,提高效率;
  • HAL_UARTEx_ReceiveToIdle_DMA:启动 DMA 接收,支持接收数据直到触发 空闲中断。

3.环形缓冲区

  • 通过固定大小的缓冲区 + 读写指针 实现数据的循环存储;
  • 用于连续接收数据,解决 DMA 数据处理问题;
  • 读写指针逻辑:
    • 写指针:指向新接收数据的位置
    • 读指针:指向待处理数据的位置

4.实现目标

  通过使用环形缓冲区管理串口接收的数据,可以实现可靠的数据接收和处理,并提高系统的稳定性和可靠性。同时,该方案也适用于其他嵌入式系统和通信场景。

  • 数据稳定接收:通过使用环形缓冲区,确保即使在接收数据速率慢于发送速率的情况下,数据也能够得到稳定的接收,避免数据丢失
  • 数据缓存和管理:环形缓冲区可以作为一个数据缓存区,将接收到的数据暂时存储起来,以便后续处理。这样可以降低数据处理的延迟和复杂性
  • 数据解析和应用:通过从环形缓冲区中读取数据,并进行解析和处理,嵌入式设备可以根据接收到的数据执行相应的操作,如控制外部设备或响应上位机指令

二、实现思路

1.DMA+串口空闲中断

(1)初始化 UART 和 DMA

  • 配置 UART 和 DMA
  • 启用 DMA 接收并启动 HAL_UARTEx_ReceiveToIdle_DMA

(2)串口接收数据

  • 数据通过 DMA 存储到 DMA 缓冲区 uart_rx_dma_buffer
  • 串口数据未停止时,DMA 自动接收,CPU 不参与

(3)触发空闲中断

  • 当 RX 线上 超过一个字节时间没有数据接收,触发 空闲中断(IDLE)
  • 调用 USARTx_IRQHandler

(4)中断处理

  • 在 USARTx_IRQHandler 中调用 HAL_UART_IRQHandler
  • HAL 库检测到 IDLE 中断,触发回调函数 HAL_UARTEx_RxEventCallback

(5)回调函数处理接收数据

  • 在 HAL_UARTEx_RxEventCallback 中,计算接收到的数据长度
  • 将 DMA 缓冲区的数据拷贝到 环形缓冲区
  • 清空 DMA 缓冲区,准备下一次接收
  • 重新启动 DMA 接收 HAL_UARTEx_ReceiveToIdle_DMA

(6)主循环读取数据

  • 通过环形缓冲区的 读写指针 提取接收到的数据,进行处理

2.环形缓冲区

(1)定义环形缓冲区的结构体:首先,需要定义一个表示环形缓冲区的结构体,其中包含以下成员变量:

  • 缓冲区的大小(capacity):表示环形缓冲区的容量,即可以存储的最大元素数量。
  • 写指针(write_ptr):表示当前可写入数据的位置。
  • 读指针(read_ptr):表示当前可读取数据的位置。
  • 数据数组(buffer):用于存储实际的数据

(2)初始化环形缓冲区:在使用环形缓冲区之前,需要进行初始化。初始化时,将缓冲区的大小、写指针和读指针都设置为初始位置,通常都是0。

(3)写入数据:当有新的数据要写入缓冲区时,需要执行以下操作:

  • 检查缓冲区是否已满,如果已满则无法写入新的数据
  • 将数据写入当前写指针所指向的位置
  • 更新写指针的位置,通常是将其加1,并考虑到环形特性,需要进行取模运算

(4)读取数据:当需要从缓冲区中读取数据时,需要执行以下操作:

  • 检查缓冲区是否为空,如果为空则无数据可读取
  • 读取当前读指针所指向的数据
  • 更新读指针的位置,通常是将其加1,并考虑到环形特性,需要进行取模运算

(5)判断缓冲区状态:为了方便使用和管理缓冲区,可以实现一些用于判断缓冲区状态的函数,例如:

  • is_full():判断缓冲区是否已满
  • is_empty():判断缓冲区是否为空

3.环形缓冲区注意事项

实现环形缓冲区时,需要注意:

  • 写指针和读指针的位置计算要考虑到环形特性,即超过缓冲区容量时需要进行取模运算
  • 缓冲区大小要合理选择,根据实际需求确定,以充分利用内存资源并避免数据丢失
  • 多线程或中断环境下的并发访问要考虑数据同步和互斥操作,以避免竞争条件和数据不一致的问题

三、串口初始化配置

1.串口配置

  代码使用于串口1,如果需要使用其他串口,可以在初始化配置里面把一些初始化函数进行更改,在标注"其他串口自行补充"字样的后面进行相应的初始化。

c 复制代码
/**
 * @brief 串口GPIO引脚初始化
 * @param config:串口配置
 */
static long Usart_GPIO_Configure(uart_conf_s *config)
{
    int uart_num = 0;
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_TypeDef *tx_port;
    GPIO_TypeDef *rx_port;
    uint16_t tx_pin;
    uint16_t rx_pin;
    uart_num = config->name[4] - '0';
    Get_Pin_By_Name(config->rx_pin_name, &rx_port, &rx_pin);
    Get_Pin_By_Name(config->tx_pin_name, &tx_port, &tx_pin);

    /* gpio ports clock enable */
    Usart_GPIO_Clk_Enable(tx_port);
    if (tx_port != rx_port)
    {
        Usart_GPIO_Clk_Enable(rx_port);
    }

    /* rx pin initialize */
    GPIO_InitStruct.Pin = tx_pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
#if defined(SOC_SERIES_STM32F2) || defined(SOC_SERIES_STM32F4) || \
    defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32G4) || \
    defined(SOC_SERIES_STM32L1) || defined(SOC_SERIES_STM32L4)
#define GPIO_AF7   ((uint8_t)0x07)
#define GPIO_AF8   ((uint8_t)0x08)
    /* uart1-3 -> AF7, uart4-8 -> AF8 */
    if (uart_num <= 3)
    {
        GPIO_InitStruct.Alternate = GPIO_AF7;
    }
    else
    {
        GPIO_InitStruct.Alternate = GPIO_AF8;
    }
#endif
    HAL_GPIO_Init(tx_port, &GPIO_InitStruct);

    /* rx pin initialize */
    GPIO_InitStruct.Pin = rx_pin;
    HAL_GPIO_Init(rx_port, &GPIO_InitStruct);

    return 0;
}

/**
 * @brief 串口时钟使能
 * @param config:串口配置
 */
static long Usart_Clk_Enable(uart_conf_s *config)
{
    /* check the parameters */
    assert_param(IS_UART_INSTANCE(config->Instance));

    /* uart clock enable */
    switch ((uint32_t)config->Instance)
    {
    #ifdef USING_UART1
        case (uint32_t) USART1:
        {
            __HAL_RCC_USART1_CLK_ENABLE();
            break;
        }
    #endif

        /* 其他串口需要自己增加配置 */

    default:
        return -1;
    }

    return 0;
}

/**
 * @brief 串口配置
 * @param huart:串口号
 * @param config:串口配置
 * @param cfg:串口配置参数
 */
static long Usart_Configure(UART_HandleTypeDef *huart, uart_conf_s *config, UART_InitTypeDef *cfg)
{
    Usart_Clk_Enable(config);

    huart->Instance          = config->Instance;
    huart->Init.BaudRate     = cfg->BaudRate;
    huart->Init.HwFlowCtl    = cfg->HwFlowCtl;
    huart->Init.Mode         = cfg->Mode;
    huart->Init.OverSampling = cfg->OverSampling;
    huart->Init.WordLength   = cfg->WordLength;
    huart->Init.StopBits     = cfg->StopBits;
    huart->Init.Parity       = cfg->Parity;

    if (HAL_UART_Init(huart) != HAL_OK)
    {
        return -1;
    }

    Usart_GPIO_Configure(config);

    return 0;
}

/**
 * @brief 串口初始化函数
 * @return 0:初始化完成  -1:失败
 */
int Bsp_Usart_Init(void)
{
    uart_conf_s *_uart_cfg;

    // 串口初始化
    for (int i = 0; i < sizeof(Usart_Config) / sizeof(Usart_Config[0]); i++)
    {
        _uart_cfg = &Usart_Config[i];
        switch ((uint32_t)_uart_cfg->Instance)
        {
            case (uint32_t) USART1:
            {
                // 串口配置
                if (Usart_Configure(&uart_handle[1], &Usart_Config[i], &Usart_Default[i]) != 0)
                    return -1;

                // 中断及DMA初始化
                Usart_Irq_DMA_Init(&Usart_Config[i]);
                break;
            }

            /* 其他串口需要自己增加配置 */
        }
    }

    return 0;
}

2.串口中断+DMA

  主要通过配置串口的空闲中断初始化,以及串口的DMA功能,同时需要加上串口DMA中断,接收数据需要在中断里面进行,并且在接收完成回调函数以及串口中断函数里面进行缓冲区的清空。

c 复制代码
/**
 * @brief 串口DMA中断初始化
 * @param config:串口配置
 */
static long Usart_Irq_DMA_Init(uart_conf_s *config)
{
    /* check the parameters */
    assert_param(IS_UART_INSTANCE(config->Instance));

    switch ((uint32_t)config->Instance)
    {
#ifdef USING_UART1
        case (uint32_t) USART1:
        {
            /* DMA controller clock enable */
            __HAL_RCC_DMA2_CLK_ENABLE();

            /* USART1_RX Init */
            hdma_rx[1].Instance = DMA2_Stream2;
            hdma_rx[1].Init.Channel = DMA_CHANNEL_4;
            hdma_rx[1].Init.Direction = DMA_PERIPH_TO_MEMORY;
            hdma_rx[1].Init.PeriphInc = DMA_PINC_DISABLE;
            hdma_rx[1].Init.MemInc = DMA_MINC_ENABLE;
            hdma_rx[1].Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
            hdma_rx[1].Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
            hdma_rx[1].Init.Mode = DMA_NORMAL;
            hdma_rx[1].Init.Priority = DMA_PRIORITY_LOW;
            hdma_rx[1].Init.FIFOMode = DMA_FIFOMODE_DISABLE;
            HAL_DMA_Init(&hdma_rx[1]);
            __HAL_LINKDMA(&uart_handle[1],hdmarx,hdma_rx[1]);

            /* USART1_TX Init */
            hdma_tx[1].Instance = DMA2_Stream7;
            hdma_tx[1].Init.Channel = DMA_CHANNEL_4;
            hdma_tx[1].Init.Direction = DMA_MEMORY_TO_PERIPH;
            hdma_tx[1].Init.PeriphInc = DMA_PINC_DISABLE;
            hdma_tx[1].Init.MemInc = DMA_MINC_ENABLE;
            hdma_tx[1].Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
            hdma_tx[1].Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
            hdma_tx[1].Init.Mode = DMA_NORMAL;
            hdma_tx[1].Init.Priority = DMA_PRIORITY_LOW;
            hdma_tx[1].Init.FIFOMode = DMA_FIFOMODE_DISABLE;
            HAL_DMA_Init(&hdma_tx[1]);
            __HAL_LINKDMA(&uart_handle[1],hdmatx,hdma_tx[1]);

            /* USART1 interrupt Init */
            HAL_NVIC_SetPriority(USART1_IRQn, 2, 0);
            HAL_NVIC_EnableIRQ(USART1_IRQn);

            /* DMA interrupt init */
            /* DMA2_Stream2_IRQn interrupt configuration */
            HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);
            HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);

            /* DMA2_Stream7_IRQn interrupt configuration */
            HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 0, 0);
            HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn);

            // 使能IDLE中断
            __HAL_UART_ENABLE_IT(&uart_handle[1], UART_IT_IDLE);
            HAL_UART_Receive_DMA(&uart_handle[1], uart1_rx_buffer, BUFFER_SIZE);

            break;
        }
#endif

        /* 其他串口需要自己增加配置 */
    }

    return 0;
}

/**
 * @brief This function handles DMA2 stream2 global interrupt.
 */
void DMA2_Stream2_IRQHandler(void)
{
    HAL_DMA_IRQHandler(&hdma_rx[1]);
}

/**
 * @brief This function handles DMA2 stream7 global interrupt.
 */
void DMA2_Stream7_IRQHandler(void)
{
    HAL_DMA_IRQHandler(&hdma_tx[1]);
}

/**
 * @brief This function handles USART1 global interrupt.
 */
void USART1_IRQHandler(void)
{
    /* 检查IDLE中断 */
    if (__HAL_UART_GET_FLAG(&uart_handle[1], UART_FLAG_IDLE) != RESET)
    {
        __HAL_UART_CLEAR_IDLEFLAG(&uart_handle[1]);
        HAL_UART_DMAStop(&uart_handle[1]);

        // 获取DMA中未传输的数据个数
        uart1_rx_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_rx[1]);

        if (uart1_rx_len > 0)
        {
            // 放到环形缓冲区
            Recv_Data_To_Buffer(&g_uart1, rx1_queue, uart1_rx_buffer, uart1_rx_len);
            memset(uart1_rx_buffer, 0, BUFFER_SIZE);
            uart1_rx_len = 0;
        }
    }

    // 重新打开DMA接收
    HAL_UART_Receive_DMA(&uart_handle[1], uart1_rx_buffer, BUFFER_SIZE);
    HAL_UART_IRQHandler(&uart_handle[1]);
}

/**
 * @brief 串口发送回调函数
 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    // USART1 发送完成
    if (huart == &uart_handle[1])
    {
        memset(&tx1_queue[g_uart1.txTail], 0, sizeof(tx1_queue[g_uart1.txTail]));
        g_uart1.txTail = (g_uart1.txTail + 1) % TX_QUEUE_SIZE;
    }
}

3.完整串口配置代码

  该串口配置可以实现串口的正常收发,并且增加了 printf 函数的重映射,实现串口的DMA收发功能,并且使用串口的空闲中断来进行数据的存储到缓冲区。可以直接进行使用,不过适配于HAL库的串口初始化,要想使用标准库,需要更改一下基础的配置函数即可,用到的缓冲区分配问题如下:

  • 在空闲中断里面接收数据的临时缓冲区大小100byte
  • 一次接收数据的大小也是100byte
  • 一次发送数据的大小是256byte
  • 接收队列包含50个,发送队列包含20个

  以上配置的参数,可以根据实际情况进行修改即可。

(1)usart.h文件

c 复制代码
/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2026-05-28     Administrator       the first version
 */
#ifndef APPLICATIONS_INC_USART_H_
#define APPLICATIONS_INC_USART_H_

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "board.h"
#include <stm32f4xx.h>

/************************************串口宏定义*************************************/
/* 定义串口宏定义和引脚号(需要使用自己定义) */
#define USING_UART1
#define UART1_TX_PIN        "PA9"
#define UART1_RX_PIN        "PA10"

#define BUFFER_SIZE         100         // 缓冲区大小
#define RX_QUEUE_SIZE       50          // 接收队列个数
#define TX_QUEUE_SIZE       20          // 发送队列个数
#define RX_SIZE             100         // 一次接收数据大小
#define TX_SIZE             256         // 一次发送数据大小

#define DATA_DEAL_ENABLE    (1)         // 接收到数据
#define DATA_DEAL_DISABLE   (0)
#define DATA_SEND_ENABLE    (1)         // 发送数据
#define DATA_SEND_DISABLE   (0)

/* 串口句柄 */
UART_HandleTypeDef uart_handle[9];
DMA_HandleTypeDef  hdma_rx[9];
DMA_HandleTypeDef  hdma_tx[9];

/* STM32串口结构体 */
typedef struct uart_config
{
    const char      *name;
    USART_TypeDef   *Instance;
    IRQn_Type       irq_type;

    const char      *tx_pin_name;
    const char      *rx_pin_name;
} uart_conf_s;

/* 串口结构体 */
typedef struct
{
    volatile uint8_t rxHead;    // 缓冲区首部
    volatile uint8_t rxTail;    // 缓冲器尾部
    volatile uint8_t txHead;    // 缓冲区首部
    volatile uint8_t txTail;    // 缓冲器尾部
    volatile uint8_t data_deal; // 数据处理标志
    volatile uint8_t data_send; // 数据发送标志

} usart_data_s;

/* 串口环形队列 */
typedef struct
{
    uint8_t  rxdata[RX_SIZE];
    uint16_t rxlen;

} UART_Rx_Queue;

typedef struct
{
    uint8_t  txdata[TX_SIZE];
    uint16_t txlen;

} UART_Tx_Queue;

usart_data_s  g_uart1;                          // 串口1参数
UART_Rx_Queue rx1_queue[RX_QUEUE_SIZE];         // 接收缓冲区
UART_Tx_Queue tx1_queue[TX_QUEUE_SIZE];         // 发送缓冲区

/* 串口配置信息(需要使用自己定义) */
#if defined(USING_UART1)
#ifndef USE_UART1_CONFIG
#define USE_UART1_CONFIG                                            \
    {                                                               \
        .name = "uart1",                                            \
        .Instance = USART1,                                         \
        .irq_type = USART1_IRQn,                                    \
        .tx_pin_name = UART1_TX_PIN,                                \
        .rx_pin_name = UART1_RX_PIN,                                \
    }
#endif

#ifndef USE_UART1_DEFAULT
#define USE_UART1_DEFAULT                                           \
    {                                                               \
        .BaudRate = 115200,                                         \
        .WordLength = UART_WORDLENGTH_8B,                           \
        .StopBits = UART_STOPBITS_1,                                \
        .Parity = UART_PARITY_NONE,                                 \
        .Mode = UART_MODE_TX_RX,                                    \
        .HwFlowCtl = UART_HWCONTROL_NONE,                           \
        .OverSampling = UART_OVERSAMPLING_16,                       \
    }
#endif
#endif /* UART1_CONFIG */
/***************************************END****************************************/

/***********************************串口外部使用************************************/
extern void Recv_Data_To_Buffer(usart_data_s *uart, UART_Rx_Queue *queue, uint8_t *buff, uint16_t len);
extern void Send_Data_To_Buffer(usart_data_s *uart, UART_Tx_Queue *queue, uint8_t *buff, uint16_t len);

extern int Bsp_Usart_Init(void);
extern int Usart_Kprintf(const char *format, ...);
extern void Uart_Data_Recv_Send(void);
/***************************************END****************************************/

#endif /* APPLICATIONS_INC_USART_H_ */

(2)usart.c文件

c 复制代码
/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2026-05-28     Administrator       the first version
 */
#include "usart.h"

/* 串口接收变量 */
uint8_t uart1_rx_buffer[BUFFER_SIZE] = {0};   // 串口1接收数据缓存数组
volatile uint8_t uart1_rx_len = 0;            // 接收一帧数据的长度

/* 默认串口信息 */
uart_conf_s Usart_Config[] =
{
#ifdef USING_UART1
    USE_UART1_CONFIG,
#endif
#ifdef USING_UART2
    USE_UART2_CONFIG,
#endif
#ifdef USING_UART3
    USE_UART3_CONFIG,
#endif
#ifdef USING_UART4
    USE_UART4_CONFIG,
#endif
#ifdef USING_UART5
    USE_UART5_CONFIG,
#endif
#ifdef USING_UART6
    USE_UART6_CONFIG,
#endif
#ifdef USING_UART7
    USE_UART7_CONFIG,
#endif
#ifdef USING_UART8
    USE_UART8_CONFIG,
#endif
};

/* 默认串口配置 */
UART_InitTypeDef Usart_Default[] =
{
#ifdef BSP_USING_UART1
    USE_UART1_DEFAULT,
#endif
#ifdef BSP_USING_UART2
    USE_UART2_DEFAULT,
#endif
#ifdef BSP_USING_UART3
    USE_UART3_DEFAULT,
#endif
#ifdef BSP_USING_UART4
    USE_UART4_DEFAULT,
#endif
#ifdef BSP_USING_UART5
    USE_UART5_DEFAULT,
#endif
#ifdef BSP_USING_UART6
    USE_UART6_DEFAULT
#endif
};

/*==========================================######  串口1中断 ######===========================================*/
/**
 * @brief This function handles DMA2 stream2 global interrupt.
 */
void DMA2_Stream2_IRQHandler(void)
{
    HAL_DMA_IRQHandler(&hdma_rx[1]);
}

/**
 * @brief This function handles DMA2 stream7 global interrupt.
 */
void DMA2_Stream7_IRQHandler(void)
{
    HAL_DMA_IRQHandler(&hdma_tx[1]);
}

/**
 * @brief This function handles USART1 global interrupt.
 */
void USART1_IRQHandler(void)
{
    /* 检查IDLE中断 */
    if (__HAL_UART_GET_FLAG(&uart_handle[1], UART_FLAG_IDLE) != RESET)
    {
        __HAL_UART_CLEAR_IDLEFLAG(&uart_handle[1]);
        HAL_UART_DMAStop(&uart_handle[1]);

        // 获取DMA中未传输的数据个数
        uart1_rx_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_rx[1]);

        if (uart1_rx_len > 0)
        {
            // 放到环形缓冲区
            Recv_Data_To_Buffer(&g_uart1, rx1_queue, uart1_rx_buffer, uart1_rx_len);
            memset(uart1_rx_buffer, 0, BUFFER_SIZE);
            uart1_rx_len = 0;
        }
    }

    // 重新打开DMA接收
    HAL_UART_Receive_DMA(&uart_handle[1], uart1_rx_buffer, BUFFER_SIZE);
    HAL_UART_IRQHandler(&uart_handle[1]);
}

/**
 * @brief 串口发送回调函数
 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    // USART1 发送完成
    if (huart == &uart_handle[1])
    {
        memset(&tx1_queue[g_uart1.txTail], 0, sizeof(tx1_queue[g_uart1.txTail]));
        g_uart1.txTail = (g_uart1.txTail + 1) % TX_QUEUE_SIZE;
    }
}
/*============================================#######  END  #######============================================*/

/*===================================================静态函数===================================================*/
/**
 * @brief 获取端口
 * @param c:字符串
 */
static int Up_Char(char *c)
{
    if ((*c >= 'a') && (*c <= 'z'))
    {
        *c = *c - 32;
    }
    return 0;
}

/**
 * @brief 获取GPIO引脚
 * @param pin_name:引脚名称
 * @param port:端口号
 * @param pin:引脚号
 */
static void Get_Pin_By_Name(const char* pin_name, GPIO_TypeDef **port, uint16_t *pin)
{
    int pin_num = atoi((char*) &pin_name[2]);
    char port_name = pin_name[1];
    Up_Char(&port_name);
    Up_Char(&port_name);
    *port = ((GPIO_TypeDef *) ((uint32_t) GPIOA
            + (uint32_t) (port_name - 'A') * ((uint32_t) GPIOB - (uint32_t) GPIOA)));
    *pin = (GPIO_PIN_0 << pin_num);
}

/**
 * @brief 串口时钟使能
 * @param config:串口配置
 */
static long Usart_Clk_Enable(uart_conf_s *config)
{
    /* check the parameters */
    assert_param(IS_UART_INSTANCE(config->Instance));

    /* uart clock enable */
    switch ((uint32_t)config->Instance)
    {
    #ifdef USING_UART1
        case (uint32_t) USART1:
        {
            __HAL_RCC_USART1_CLK_ENABLE();
            break;
        }
    #endif

        /* 其他串口需要自己增加配置 */

    default:
        return -1;
    }

    return 0;
}

/**
 * @brief 串口GPIO引脚时钟使能
 * @param gpiox:引脚
 */
static long Usart_GPIO_Clk_Enable(GPIO_TypeDef *gpiox)
{
    /* check the parameters */
    assert_param(IS_GPIO_ALL_INSTANCE(gpiox));

    /* gpio ports clock enable */
    switch ((uint32_t)gpiox)
    {
    #if defined(__HAL_RCC_GPIOA_CLK_ENABLE)
        case (uint32_t)GPIOA:
        {
            __HAL_RCC_GPIOA_CLK_ENABLE();
            break;
        }
    #endif

        /* 其他串口需要自己增加配置 */

        default:
            return -1;
    }

    return 0;
}

/**
 * @brief 串口GPIO引脚初始化
 * @param config:串口配置
 */
static long Usart_GPIO_Configure(uart_conf_s *config)
{
    int uart_num = 0;
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_TypeDef *tx_port;
    GPIO_TypeDef *rx_port;
    uint16_t tx_pin;
    uint16_t rx_pin;
    uart_num = config->name[4] - '0';
    Get_Pin_By_Name(config->rx_pin_name, &rx_port, &rx_pin);
    Get_Pin_By_Name(config->tx_pin_name, &tx_port, &tx_pin);

    /* gpio ports clock enable */
    Usart_GPIO_Clk_Enable(tx_port);
    if (tx_port != rx_port)
    {
        Usart_GPIO_Clk_Enable(rx_port);
    }

    /* rx pin initialize */
    GPIO_InitStruct.Pin = tx_pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
#if defined(SOC_SERIES_STM32F2) || defined(SOC_SERIES_STM32F4) || \
    defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32G4) || \
    defined(SOC_SERIES_STM32L1) || defined(SOC_SERIES_STM32L4)
#define GPIO_AF7   ((uint8_t)0x07)
#define GPIO_AF8   ((uint8_t)0x08)
    /* uart1-3 -> AF7, uart4-8 -> AF8 */
    if (uart_num <= 3)
    {
        GPIO_InitStruct.Alternate = GPIO_AF7;
    }
    else
    {
        GPIO_InitStruct.Alternate = GPIO_AF8;
    }
#endif
    HAL_GPIO_Init(tx_port, &GPIO_InitStruct);

    /* rx pin initialize */
    GPIO_InitStruct.Pin = rx_pin;
    HAL_GPIO_Init(rx_port, &GPIO_InitStruct);

    return 0;
}

/**
 * @brief 串口配置
 * @param huart:串口号
 * @param config:串口配置
 * @param cfg:串口配置参数
 */
static long Usart_Configure(UART_HandleTypeDef *huart, uart_conf_s *config, UART_InitTypeDef *cfg)
{
    Usart_Clk_Enable(config);

    huart->Instance          = config->Instance;
    huart->Init.BaudRate     = cfg->BaudRate;
    huart->Init.HwFlowCtl    = cfg->HwFlowCtl;
    huart->Init.Mode         = cfg->Mode;
    huart->Init.OverSampling = cfg->OverSampling;
    huart->Init.WordLength   = cfg->WordLength;
    huart->Init.StopBits     = cfg->StopBits;
    huart->Init.Parity       = cfg->Parity;

    if (HAL_UART_Init(huart) != HAL_OK)
    {
        return -1;
    }

    Usart_GPIO_Configure(config);

    return 0;
}

/**
 * @brief 串口DMA中断初始化
 * @param config:串口配置
 */
static long Usart_Irq_DMA_Init(uart_conf_s *config)
{
    /* check the parameters */
    assert_param(IS_UART_INSTANCE(config->Instance));

    switch ((uint32_t)config->Instance)
    {
#ifdef USING_UART1
        case (uint32_t) USART1:
        {
            /* DMA controller clock enable */
            __HAL_RCC_DMA2_CLK_ENABLE();

            /* USART1_RX Init */
            hdma_rx[1].Instance = DMA2_Stream2;
            hdma_rx[1].Init.Channel = DMA_CHANNEL_4;
            hdma_rx[1].Init.Direction = DMA_PERIPH_TO_MEMORY;
            hdma_rx[1].Init.PeriphInc = DMA_PINC_DISABLE;
            hdma_rx[1].Init.MemInc = DMA_MINC_ENABLE;
            hdma_rx[1].Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
            hdma_rx[1].Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
            hdma_rx[1].Init.Mode = DMA_NORMAL;
            hdma_rx[1].Init.Priority = DMA_PRIORITY_LOW;
            hdma_rx[1].Init.FIFOMode = DMA_FIFOMODE_DISABLE;
            HAL_DMA_Init(&hdma_rx[1]);
            __HAL_LINKDMA(&uart_handle[1],hdmarx,hdma_rx[1]);

            /* USART1_TX Init */
            hdma_tx[1].Instance = DMA2_Stream7;
            hdma_tx[1].Init.Channel = DMA_CHANNEL_4;
            hdma_tx[1].Init.Direction = DMA_MEMORY_TO_PERIPH;
            hdma_tx[1].Init.PeriphInc = DMA_PINC_DISABLE;
            hdma_tx[1].Init.MemInc = DMA_MINC_ENABLE;
            hdma_tx[1].Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
            hdma_tx[1].Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
            hdma_tx[1].Init.Mode = DMA_NORMAL;
            hdma_tx[1].Init.Priority = DMA_PRIORITY_LOW;
            hdma_tx[1].Init.FIFOMode = DMA_FIFOMODE_DISABLE;
            HAL_DMA_Init(&hdma_tx[1]);
            __HAL_LINKDMA(&uart_handle[1],hdmatx,hdma_tx[1]);

            /* USART1 interrupt Init */
            HAL_NVIC_SetPriority(USART1_IRQn, 2, 0);
            HAL_NVIC_EnableIRQ(USART1_IRQn);

            /* DMA interrupt init */
            /* DMA2_Stream2_IRQn interrupt configuration */
            HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);
            HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);

            /* DMA2_Stream7_IRQn interrupt configuration */
            HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 0, 0);
            HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn);

            // 使能IDLE中断
            __HAL_UART_ENABLE_IT(&uart_handle[1], UART_IT_IDLE);
            HAL_UART_Receive_DMA(&uart_handle[1], uart1_rx_buffer, BUFFER_SIZE);

            break;
        }
#endif

        /* 其他串口需要自己增加配置 */
    }

    return 0;
}
/*=====================================================END=====================================================*/

/*==================================================环形缓冲区==================================================*/
/**
 * @brief 将数据放到接收缓冲区
 * @param uart:串口
 * @param queue:发送缓冲区
 * @param buff:发送的数据
 * @param len:数据长度
 */
void Recv_Data_To_Buffer(usart_data_s *uart, UART_Rx_Queue *queue, uint8_t *buff, uint16_t len)
{
    if (uart == NULL || queue == NULL || buff == NULL || len == 0)
        return;

    uart->data_deal = DATA_DEAL_ENABLE;
    if ((uart->rxHead + 1) % (RX_QUEUE_SIZE + 1) != uart->rxTail)
    {
        if (uart->rxHead >= RX_QUEUE_SIZE && len > sizeof(queue[0].rxdata))
            return;
        memcpy(queue[uart->rxHead].rxdata, buff, len);
        queue[uart->rxHead].rxlen = len;
        uart->rxHead = (uart->rxHead + 1) % RX_QUEUE_SIZE;
    }
}

/**
 * @brief 将数据放到发送缓冲区
 * @param uart:串口
 * @param queue:发送缓冲区
 * @param buff:发送的数据
 * @param len:数据长度
 */
void Send_Data_To_Buffer(usart_data_s *uart, UART_Tx_Queue *queue, uint8_t *buff, uint16_t len)
{
    if (uart == NULL || queue == NULL || buff == NULL || len == 0)
        return;

    uart->data_send = DATA_SEND_ENABLE;
    if ((uart->txHead + 1) % (TX_QUEUE_SIZE + 1) != uart->txTail)
    {
        memcpy(queue[uart->txHead].txdata, buff, len);
        queue[uart->txHead].txlen = len;
        uart->txHead = (uart->txHead + 1) % TX_QUEUE_SIZE;
    }
}

/**
 * @brief 串口发送功能函数
 * @param buf:发送数据
 * @param len:数据长度
 */
void DMA_Usart_Send(UART_HandleTypeDef *config, uint8_t *buf, uint16_t len)
{
    switch ((uint32_t)config->Instance)
    {
        case (uint32_t) USART1:
        {
            if (uart_handle[1].gState == HAL_UART_STATE_READY)
            {
                if (HAL_UART_Transmit_DMA(&uart_handle[1], buf, len) != HAL_OK)
                {
                    printf("send error!\n");
                    return;
                }
            }
            break;
        }
    }
}
/*=====================================================END=====================================================*/

/*===================================================外部调用===================================================*/
/**
 * @brief 串口初始化函数
 * @return 0:初始化完成  -1:失败
 */
int Bsp_Usart_Init(void)
{
    uart_conf_s *_uart_cfg;

    // 串口初始化
    for (int i = 0; i < sizeof(Usart_Config) / sizeof(Usart_Config[0]); i++)
    {
        _uart_cfg = &Usart_Config[i];
        switch ((uint32_t)_uart_cfg->Instance)
        {
            case (uint32_t) USART1:
            {
                // 串口配置
                if (Usart_Configure(&uart_handle[1], &Usart_Config[i], &Usart_Default[i]) != 0)
                    return -1;

                // 中断及DMA初始化
                Usart_Irq_DMA_Init(&Usart_Config[i]);
                break;
            }

            /* 其他串口需要自己增加配置 */
        }
    }

    return 0;
}

/**
 * @brief 重映射prinf函数
 * @param c
 */
void print_char(char c)
{
    HAL_UART_Transmit(&uart_handle[1], (uint8_t *) (&c), 1, 1);
}

/**
 * @brief  串口打印函数,类似printf
 * @param  format: 格式化字符串
 * @param  ...: 可变参数列表
 * @retval 发送的字节数,失败返回-1
 */
int Usart_Kprintf(const char *format, ...)
{
    char buffer[256], tx_buffer[256];
    va_list args;

    // 初始化可变参数列表
    va_start(args, format);

    // 将格式化字符串转换为字符数组
    int len = vsnprintf(buffer, sizeof(buffer), format, args);

    if (len < TX_SIZE)
        memcpy(tx_buffer, buffer, len);

    // 其他串口使用这个函数可以修改这个函数调用,或者重新写一个
    Send_Data_To_Buffer(&g_uart1, tx1_queue, (uint8_t *)tx_buffer, len);

    // 结束可变参数列表
    va_end(args);

    return 0;
}

/**
 * @brief 串口数据收发处理
 */
void Uart_Data_Recv_Send(void)
{
    // 串口1数据处理
    if (g_uart1.data_deal == DATA_DEAL_ENABLE)
    {
        // 没有数据接收,全部清空
        if (rx1_queue[g_uart1.rxTail].rxdata == NULL || rx1_queue[g_uart1.rxTail].rxlen == 0)
        {
            g_uart1.data_deal = DATA_DEAL_DISABLE;
            memset(rx1_queue, 0, sizeof(rx1_queue));
            g_uart1.rxHead = 0;
            g_uart1.rxTail = 0;
            return;
        }

    #if 0
        // 测试发送数据
        Usart_Kprintf("Send recv data size %d\r\n", rx1_queue[g_uart1.rxTail].rxlen);
    #else
        extern int Console_CMD_Event(char *cmd, uint16_t length);
        Console_CMD_Event((char *)rx1_queue[g_uart1.rxTail].rxdata, rx1_queue[g_uart1.rxTail].rxlen);
    #endif

        memset(&rx1_queue[g_uart1.rxTail], 0, sizeof(rx1_queue[g_uart1.rxTail]));
        g_uart1.rxTail = (g_uart1.rxTail + 1) % RX_QUEUE_SIZE;
    }

    // 串口1数据发送
    if (g_uart1.data_send == DATA_SEND_ENABLE && uart_handle[1].gState == HAL_UART_STATE_READY)
    {
        // 没有数据发送,全部清空
        if (tx1_queue[g_uart1.txTail].txdata == NULL || tx1_queue[g_uart1.txTail].txlen == 0)
        {
            g_uart1.data_send = DATA_SEND_DISABLE;
            memset(tx1_queue, 0, sizeof(tx1_queue));
            g_uart1.txHead = 0;
            g_uart1.txTail = 0;
            return;
        }

        // 发送数据
        DMA_Usart_Send(&uart_handle[1], tx1_queue[g_uart1.txTail].txdata, tx1_queue[g_uart1.txTail].txlen);
    }
}

四、MSH组件

1.介绍

  MSH组件是使用DMA+串口环形缓冲区来进行的一个串口处理数据的组件,该组件主要借鉴与RTT-Thread 里面的控制台组件来搭建的一个组件。

  使用非常方便,可以直接通过串口的字符串交互,实现串口的数据通信,不用使用其他复杂的交互方式,就可以很清楚的进行操作。

  使用比较简单,直接调用下面的初始化函数,和增加需要使用的函数即可:

c 复制代码
/**
 * @brief 控制台指令事件
 * @param cmd:指令
 * @param length:长度
 * @return 0-正常  -1 -异常
 */
int Console_CMD_Event(char *cmd, uint16_t length)
{
    int cmd_ret;

    length = Remove_Newline(cmd);
//    length = strlen(cmd);
//    cmd[length - 1] = 0;
//    cmd[length - 2] = '\0';
//    length -= 2;

    // 获取第一个字符串
    while ((length > 0) && (*cmd == ' ' || *cmd == '\t'))
    {
        cmd++;
        length--;
    }

    if (length == 0)
        return 0;

    // 执行指令
    if (_msh_exec_cmd(cmd, length, &cmd_ret) == 0)
    {
        memset(cmd, 0, length);
        return cmd_ret;
    }

    // 截断第一个空格处的cmd
    {
        char *tcmd;
        tcmd = cmd;
        while (*tcmd != ' ' && *tcmd != '\0')
        {
            tcmd++;
        }
        *tcmd = '\0';
    }

    printf("%s: command not found.\n", cmd);
    memset(cmd, 0, length);
    return -1;
}

/**
 * @brief MSH初始化
 */
void Msh_Cmd_Init(void)
{
    // 1.MSH协议测试
    MSH_Cmd_Register(MSH_Test_Config,                "msh",       "MSH Test Code");
}

2.MSH组件驱动程序

c 复制代码
/**
 * @brief 解析命令行输入
 * @param cmd:待解析的命令行字符串
 * @param length:字符串长度
 * @param argv:存储解析后的参数列表
 * @return 返回解析出的参数个数
 */
static int Msh_Split(char *cmd, uint16_t length, char *argv[FINSH_ARG_MAX])
{
    char *ptr;
    uint16_t position;
    uint16_t argc;
    uint16_t i;

    ptr = cmd;
    position = 0;
    argc = 0;

    while (position < length)
    {
        // 将空格替换为字符串结束符
        while ((*ptr == ' ' || *ptr == '\t') && position < length)
        {
            *ptr = '\0';
            ptr++;
            position++;
        }

        // 检查参数是否超过最大限制
        if (argc >= FINSH_ARG_MAX)
        {
            printf("Too many args ! We only Use:\n");
            for (i = 0; i < argc; i++)
            {
                printf("%s ", argv[i]);
            }
            printf("\n");
            break;
        }

        if (position >= length)
            break;

        // 处理带引号的字符串
        if (*ptr == '"')
        {
            ptr++;
            position++;
            argv[argc] = ptr;
            argc++;

            // 跳过整个引号内的内容
            while (*ptr != '"' && position < length)
            {
                // 处理转义字符
                if (*ptr == '\\')
                {
                    if (*(ptr + 1) == '"')
                    {
                        ptr++;
                        position++;
                    }
                }
                ptr++;
                position++;
            }
            if (position >= length)
                break;

            // 替换末尾的 `"` 为 `\0`
            *ptr = '\0';
            ptr++;
            position++;
        }
        // 处理普通参数
        else
        {
            argv[argc] = ptr;
            argc++;
            while ((*ptr != ' ' && *ptr != '\t') && position < length)
            {
                ptr++;
                position++;
            }
            if (position >= length)
                break;
        }
    }

    return argc;
}

/**
 * @brief 获取指令执行的函数
 * @param cmd:指令
 * @param size:大小
 * @return 执行的函数地址
 */
static syscall_func Msh_Get_Cmd(char *cmd, int size)
{
    syscall_func cmd_func = NULL;

    for (int i = 0; i < syscall_count; ++i)
    {
        if (strncmp(syscall_table[i].name, cmd, size) == 0 &&
            syscall_table[i].name[size] == '\0')
        {
            cmd_func = (syscall_func) syscall_table[i].func;
            break;
        }
    }

    return cmd_func;
}

/**
 * @brief 执行指令
 * @param cmd:指令
 * @param length:长度
 * @param retp:执行成功标志
 * @return 0-正常  -1 -异常
 */
static int _msh_exec_cmd(char *cmd, uint16_t length, int *retp)
{
    int argc;
    uint16_t cmd0_size = 0;
    syscall_func cmd_func;
    char *argv[FINSH_ARG_MAX];

    // 查找第一个命令的大小
    while ((cmd[cmd0_size] != ' ' && cmd[cmd0_size] != '\t') && cmd0_size < length)
        cmd0_size++;
    if (cmd0_size == 0)
        return -1;

    // 获取执行函数
    cmd_func = Msh_Get_Cmd(cmd, cmd0_size);
    if (cmd_func == NULL)
        return -1;

    // 处理普通参数
    memset(argv, 0x00, sizeof(argv));
    argc = Msh_Split(cmd, length, argv);
    if (argc == 0)
        return -1;

    // 进入指令函数
    *retp = cmd_func(argc, argv);
    return 0;
}

/**
 * @brief 去除回车换行
 * @param str:取要去掉回车换行的数据
 * @return 数据的长度
 */
static size_t Remove_Newline(char *str)
{
    if (str == NULL)
        return 0;

    // 使用 strstr 查找 "\r\n" 子字符串
    char *crlf_pos = strstr(str, "\r\n");

    if (crlf_pos != NULL)
    {
        // 找到 "\r\n",截断后面的所有内容
        *crlf_pos = '\0';
        return crlf_pos - str;
    }

    // 如果没有找到中间的 "\r\n",检查末尾的 "\r\n"
    size_t len = strlen(str);
    if (len >= 2 && str[len - 2] == '\r' && str[len - 1] == '\n')
    {
        str[len - 2] = '\0';
        return len - 2;
    }

    return len;
}

/**
 * @brief 注册函数
 * @param func:函数名称
 * @param name:函数指令
 * @param desc:函数说明
 */
static void MSH_Cmd_Register(syscall_func func, char *name, char *desc)
{
    do
    {
        if (syscall_count < MAX_SYSCALL_NUM)
        {
            syscall_table[syscall_count].name = name;
            syscall_table[syscall_count].desc = desc;
            syscall_table[syscall_count].func = func;
            syscall_count++;
        }
    }
    while (0);
}

3.完整代码

(1)msh.h

c 复制代码
/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2026-05-28     Administrator       the first version
 */
#ifndef APPLICATIONS_INC_MSH_H_
#define APPLICATIONS_INC_MSH_H_

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "board.h"
#include <stm32f4xx.h>

/**============================================### MSH指令定义 ####=========================================*/
#define FINSH_ARG_MAX       10        // 参数最大限制
#define FLOAT_STR_LEN       16

#define PARAM_NUM_ONE       1
#define PARAM_NUM_TWO       2
#define PARAM_NUM_THR       3
#define PARAM_NUM_FOUR      4
#define PARAM_NUM_FIVE      5
#define PARAM_NUM_SIX       6
#define PARAM_NUM_SEVEN     7
#define PARAM_NUM_EIGHT     8

/* maximum value of base type */
#define RT_UINT8_MAX        0xFF            /**< Maximum number of UINT8  */
#define RT_UINT16_MAX       0xFFFF          /**< Maximum number of UINT16 */
#define RT_UINT32_MAX       0xFFFFFFFF      /**< Maximum number of UINT32 */
#define RT_TICK_MAX         RT_UINT32_MAX   /**< Maximum number of tick   */

typedef int (*syscall_func)(int argc, char **argv);

struct finsh_syscall
{
    const char *name;   // 命令名称
    const char *desc;   // 描述
    syscall_func func;  // 函数指针
};

/* 定义全局命令数组 */
#define MAX_SYSCALL_NUM 200                                 // 最大命令数量

extern void Msh_Cmd_Init(void);
extern int Console_CMD_Event(char *cmd, uint16_t length);

extern int MSH_Test_Config(int argc, char **argv);
/**============================================#######  END  #######========================================*/

#endif /* APPLICATIONS_INC_MSH_H_ */

(2)msh.c

c 复制代码
/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2026-05-28     Administrator       the first version
 */
#include "msh.h"
#include "usart.h"

static struct finsh_syscall syscall_table[MAX_SYSCALL_NUM]; // 命令集合
static size_t syscall_count = 0;                            // 当前命令数量

/*=============================================##### 静态调用 #####================================================*/
/**
 * @brief 解析命令行输入
 * @param cmd:待解析的命令行字符串
 * @param length:字符串长度
 * @param argv:存储解析后的参数列表
 * @return 返回解析出的参数个数
 */
static int Msh_Split(char *cmd, uint16_t length, char *argv[FINSH_ARG_MAX])
{
    char *ptr;
    uint16_t position;
    uint16_t argc;
    uint16_t i;

    ptr = cmd;
    position = 0;
    argc = 0;

    while (position < length)
    {
        // 将空格替换为字符串结束符
        while ((*ptr == ' ' || *ptr == '\t') && position < length)
        {
            *ptr = '\0';
            ptr++;
            position++;
        }

        // 检查参数是否超过最大限制
        if (argc >= FINSH_ARG_MAX)
        {
            printf("Too many args ! We only Use:\n");
            for (i = 0; i < argc; i++)
            {
                printf("%s ", argv[i]);
            }
            printf("\n");
            break;
        }

        if (position >= length)
            break;

        // 处理带引号的字符串
        if (*ptr == '"')
        {
            ptr++;
            position++;
            argv[argc] = ptr;
            argc++;

            // 跳过整个引号内的内容
            while (*ptr != '"' && position < length)
            {
                // 处理转义字符
                if (*ptr == '\\')
                {
                    if (*(ptr + 1) == '"')
                    {
                        ptr++;
                        position++;
                    }
                }
                ptr++;
                position++;
            }
            if (position >= length)
                break;

            // 替换末尾的 `"` 为 `\0`
            *ptr = '\0';
            ptr++;
            position++;
        }
        // 处理普通参数
        else
        {
            argv[argc] = ptr;
            argc++;
            while ((*ptr != ' ' && *ptr != '\t') && position < length)
            {
                ptr++;
                position++;
            }
            if (position >= length)
                break;
        }
    }

    return argc;
}

/**
 * @brief 获取指令执行的函数
 * @param cmd:指令
 * @param size:大小
 * @return 执行的函数地址
 */
static syscall_func Msh_Get_Cmd(char *cmd, int size)
{
    syscall_func cmd_func = NULL;

    for (int i = 0; i < syscall_count; ++i)
    {
        if (strncmp(syscall_table[i].name, cmd, size) == 0 &&
            syscall_table[i].name[size] == '\0')
        {
            cmd_func = (syscall_func) syscall_table[i].func;
            break;
        }
    }

    return cmd_func;
}

/**
 * @brief 执行指令
 * @param cmd:指令
 * @param length:长度
 * @param retp:执行成功标志
 * @return 0-正常  -1 -异常
 */
static int _msh_exec_cmd(char *cmd, uint16_t length, int *retp)
{
    int argc;
    uint16_t cmd0_size = 0;
    syscall_func cmd_func;
    char *argv[FINSH_ARG_MAX];

    // 查找第一个命令的大小
    while ((cmd[cmd0_size] != ' ' && cmd[cmd0_size] != '\t') && cmd0_size < length)
        cmd0_size++;
    if (cmd0_size == 0)
        return -1;

    // 获取执行函数
    cmd_func = Msh_Get_Cmd(cmd, cmd0_size);
    if (cmd_func == NULL)
        return -1;

    // 处理普通参数
    memset(argv, 0x00, sizeof(argv));
    argc = Msh_Split(cmd, length, argv);
    if (argc == 0)
        return -1;

    // 进入指令函数
    *retp = cmd_func(argc, argv);
    return 0;
}

/**
 * @brief 去除回车换行
 * @param str:取要去掉回车换行的数据
 * @return 数据的长度
 */
static size_t Remove_Newline(char *str)
{
    if (str == NULL)
        return 0;

    // 使用 strstr 查找 "\r\n" 子字符串
    char *crlf_pos = strstr(str, "\r\n");

    if (crlf_pos != NULL)
    {
        // 找到 "\r\n",截断后面的所有内容
        *crlf_pos = '\0';
        return crlf_pos - str;
    }

    // 如果没有找到中间的 "\r\n",检查末尾的 "\r\n"
    size_t len = strlen(str);
    if (len >= 2 && str[len - 2] == '\r' && str[len - 1] == '\n')
    {
        str[len - 2] = '\0';
        return len - 2;
    }

    return len;
}

/**
 * @brief 注册函数
 * @param func:函数名称
 * @param name:函数指令
 * @param desc:函数说明
 */
static void MSH_Cmd_Register(syscall_func func, char *name, char *desc)
{
    do
    {
        if (syscall_count < MAX_SYSCALL_NUM)
        {
            syscall_table[syscall_count].name = name;
            syscall_table[syscall_count].desc = desc;
            syscall_table[syscall_count].func = func;
            syscall_count++;
        }
    }
    while (0);
}
/*============================================#######  END  #######===============================================*/

/*=============================================##### 外部调用 #####===============================================*/
/**
 * @brief 控制台指令事件
 * @param cmd:指令
 * @param length:长度
 * @return 0-正常  -1 -异常
 */
int Console_CMD_Event(char *cmd, uint16_t length)
{
    int cmd_ret;

    length = Remove_Newline(cmd);
//    length = strlen(cmd);
//    cmd[length - 1] = 0;
//    cmd[length - 2] = '\0';
//    length -= 2;

    // 获取第一个字符串
    while ((length > 0) && (*cmd == ' ' || *cmd == '\t'))
    {
        cmd++;
        length--;
    }

    if (length == 0)
        return 0;

    // 执行指令
    if (_msh_exec_cmd(cmd, length, &cmd_ret) == 0)
    {
        memset(cmd, 0, length);
        return cmd_ret;
    }

    // 截断第一个空格处的cmd
    {
        char *tcmd;
        tcmd = cmd;
        while (*tcmd != ' ' && *tcmd != '\0')
        {
            tcmd++;
        }
        *tcmd = '\0';
    }

    printf("%s: command not found.\n", cmd);
    memset(cmd, 0, length);
    return -1;
}

/**
 * @brief MSH初始化
 */
void Msh_Cmd_Init(void)
{
    // 1.MSH协议测试
    MSH_Cmd_Register(MSH_Test_Config,                "msh",       "MSH Test Code");
}

/**
 * @brief MSH协议测试
 */
int MSH_Test_Config(int argc, char **argv)
{
    if (argc != PARAM_NUM_TWO && argc != PARAM_NUM_ONE)
        return -ERROR;

    switch (argc)
    {
        case PARAM_NUM_ONE:
        {
            Usart_Kprintf("Param one!\n");
            break;
        }

        case PARAM_NUM_TWO:
        {
            if(!strcmp(argv[1], "test"))
                Usart_Kprintf("Param two!\n");
            break;
        }
    }

    return 0;
}
/*============================================#######  END  #######==============================================*/

相关推荐
子朔不言2 小时前
MH2030B 一个输入IO失效故障分析(stm32F030系列有类似问题)
单片机·嵌入式硬件·mh2030b
LCG元2 小时前
STM32实战:基于STM32F103的智能饮水机(温度控制+流量计费)
stm32·单片机·嵌入式硬件
m0_377108142 小时前
stm32-DMA
stm32·单片机·嵌入式硬件
嵌入式小站2 小时前
STM32 零基础可移植教程 11:PWM 输出,让 LED 呼吸起来
stm32·单片机·嵌入式硬件
sramdram2 小时前
Cascadeteq国产替代psram芯片,国产psram芯片CSS1604S系列
单片机·嵌入式硬件·psram·cascadeteq·国产替代psram·国产psram芯片
南檐巷上学3 小时前
基于Zynq-7020的带有正弦波发生器的8051软核设计
单片机·嵌入式硬件·fpga开发·fpga
崇山峻岭之间3 小时前
单片机低功耗实验
单片机·嵌入式硬件
周周记笔记3 小时前
【元器件专题】PNP三极管如何搭建开关电路
单片机·嵌入式硬件
不脱发的程序猿4 小时前
如何创建一个标准Skill,让嵌入式经验真正复用起来
人工智能·单片机·嵌入式硬件·嵌入式·skill