Просмотр исходного кода

virtual terminal v1.0.0 #1

virtual terminal v1.0.0
RT-Trace 3 месяцев назад
Родитель
Сommit
573e86cf59
8 измененных файлов с 1253 добавлено и 2 удалено
  1. 38 2
      README.md
  2. 533 0
      RT_Verm_print.c
  3. 219 0
      RT_Vterm.c
  4. 154 0
      RT_Vterm.h
  5. 54 0
      RT_Vterm_examples/RT_Vterm_example_basic.c
  6. 9 0
      RT_Vterm_examples/SConscript
  7. 17 0
      SConscript
  8. 229 0
      drv_Vterm.c

+ 38 - 2
README.md

@@ -1,2 +1,38 @@
-# Virtual-Terminal
-RT-Thread Virtual-Terminal
+# Virtual-Terminal 软件包
+
+## 简介
+
+RT_Vterm 是基于 RT-Thread 内核与 RT-Tunnel 组件的**虚拟终端组件**,核心功能是实现嵌入式系统的 I/O 重定向、双向数据传输与命令交互。该组件通过 RT-Tunnel 的 “上下行隧道”(up_tunnel/down_tunnel)实现数据可靠传输,兼容 RT-Thread 字符设备模型,支持将控制台 / FinSH 输出切换到虚拟终端,同时提供命令注册、输入辅助等功能,适用于远程调试、串口复用、Web 终端扩展等场景。
+
+
+
+## 主要特性
+
+- **基于 RT-Tunnel 双向传输**:通过 `up_tunnel`(上行,设备→主机,ID:0x56545455)和 `down_tunnel`(下行,主机→设备,ID:0x56545444)实现数据隔离与可靠传输,支持线程安全读写。
+- **RT-Thread 设备模型兼容**:注册为 “vterm” 字符设备,支持 `open/read/write` 标准接口,可直接作为控制台或 FinSH 的 I/O 设备。
+- **灵活 I/O 切换与恢复**:提供 `vterm_console` 命令将控制台 / FinSH 切换到虚拟终端,`restore_original` 命令恢复原始 I/O 设备(如物理串口),切换过程无数据丢失。
+- **命令注册与输入辅助**:支持自定义命令注册(最大 8 条,`vterm_cmd_register` 接口),提供 `vterm_input_assist_thread` 线程处理输入回显、换行转换(\n→\r\n),适配终端显示习惯。
+- 多模式数据读写:
+  - 阻塞读:`RT_Vterm_WaitKey` 阻塞等待并读取 1 字节;
+  - 非阻塞读:`RT_Vterm_GetKey` 无数据时返回 -1;
+  - 单字符写:`RT_Vterm_PutChar` 简化单个字符传输;
+  - 缓冲区状态查询:`RT_Vterm_HasData` 检查下行数据是否就绪。
+- **自动换行转换**:写入数据时自动将 `\n` 转为 `\r\n`,确保终端显示换行正常,无需手动处理格式。
+
+
+
+## 快速上手
+
+### 初始化流程
+
+RT_Vterm 需在 RT-Tunnel 初始化后启动,推荐初始化顺序:
+
+1. **初始化 RT-Tunnel**:确保 `RT_Tunnel_Init` 先执行(通常自动初始化);
+2. **初始化 RT_Vterm**:调用 `RT_Vterm_Init` 分配上下行隧道并配置操作模式;
+3. **shell切换到虚拟终端设备**:调用 `rt_hw_vterm_console_init` 注册 “vterm” 字符设备,并对接shell;
+
+
+
+## 注意
+
+确保 RT-Tunnel 已启用,且 `TUNNEL_NUM` 至少为 2

+ 533 - 0
RT_Verm_print.c

@@ -0,0 +1,533 @@
+#include "rt_Vterm.h"
+// #include "rt_Vterm_Conf.h"
+#include "RT_tunnel.h"
+#include <rtthread.h>
+#include <stdio.h>
+
+/*********************************************************************
+ *
+ *       Defines, configurable
+ *
+ **********************************************************************
+ */
+/**< Default buffer size for RT_Vterm_printf (configurable) */
+#ifndef RT_Vterm_PRINTF_BUFFER_SIZE
+#define RT_Vterm_PRINTF_BUFFER_SIZE (64)
+#endif
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+
+/**< Format flag: Left-justify the output (default is right-justify) */
+#define FORMAT_FLAG_LEFT_JUSTIFY (1u << 0)
+/**< Format flag: Pad with '0' instead of spaces */
+#define FORMAT_FLAG_PAD_ZERO (1u << 1)
+/**< Format flag: Always print sign (+ for positive, - for negative) */
+#define FORMAT_FLAG_PRINT_SIGN (1u << 2)
+/**< Format flag: Use alternate output format (e.g., 0x prefix for hex) */
+#define FORMAT_FLAG_ALTERNATE (1u << 3)
+
+/**
+ * @struct RT_Vterm_PRINTF_DESC
+ * @brief  Descriptor structure for managing printf buffer and output state
+ */
+typedef struct
+{
+    char    *pBuffer;     /**< Pointer to the temporary printf buffer */
+    uint32_t BufferSize;  /**< Size of the temporary printf buffer */
+    uint32_t Cnt;         /**< Current number of characters stored in the buffer */
+    int      ReturnValue; /**< Total number of characters output (negative on error) */
+} RT_Vterm_PRINTF_DESC;
+
+/*****************************************************************************
+ * @brief        Store a single character to the printf buffer (internal use)
+ *
+ * @param[in,out] p  Pointer to the printf descriptor structure
+ * @param[in]     c  Character to store
+ *
+ * @note         1. Automatically adds '\r' before '\n' for line ending consistency
+ *               2. Writes buffer to Vterm upstream tunnel when buffer is full
+ *               3. Updates ReturnValue (increments on success, sets to -1 on error)
+ *****************************************************************************/
+static void _StoreChar(RT_Vterm_PRINTF_DESC *p, char c)
+{
+    uint32_t Cnt;
+
+    // Add '\r' before '\n' to ensure correct line ending on all terminals
+    if (c == '\n')
+    {
+        Cnt = p->Cnt;
+        if ((Cnt + 1u) <= p->BufferSize)
+        {
+            *(p->pBuffer + Cnt) = '\r';
+            p->Cnt              = Cnt + 1u;
+            p->ReturnValue++;
+        }
+        // Flush buffer if full after adding '\r'
+        if (p->Cnt == p->BufferSize)
+        {
+            if (RT_Vterm_Write(p->pBuffer, p->Cnt) != p->Cnt)
+            {
+                p->ReturnValue = -1; // Mark write error
+            }
+            else
+            {
+                p->Cnt = 0u; // Reset buffer counter after successful flush
+            }
+        }
+    }
+
+    // Store the target character
+    Cnt = p->Cnt;
+    if ((Cnt + 1u) <= p->BufferSize)
+    {
+        *(p->pBuffer + Cnt) = c;
+        p->Cnt              = Cnt + 1u;
+        p->ReturnValue++;
+    }
+    // Flush buffer if full after storing the character
+    if (p->Cnt == p->BufferSize)
+    {
+        if (RT_Vterm_Write(p->pBuffer, p->Cnt) != p->Cnt)
+        {
+            p->ReturnValue = -1; // Mark write error
+        }
+        else
+        {
+            p->Cnt = 0u; // Reset buffer counter after successful flush
+        }
+    }
+}
+
+/*****************************************************************************
+ * @brief        Print an unsigned integer to Vterm (internal use)
+ *
+ * @param[in,out] pBufferDesc  Pointer to the printf descriptor structure
+ * @param[in]     v            Unsigned integer value to print
+ * @param[in]     Base         Numeric base (e.g., 10 for decimal, 16 for hex)
+ * @param[in]     NumDigits    Minimum number of digits to print (pads with 0 if needed)
+ * @param[in]     FieldWidth   Total field width (pads with spaces/0 if value is shorter)
+ * @param[in]     FormatFlags  Format control flags (left-justify, pad-zero, etc.)
+ *
+ * @note         Handles digit extraction, padding, and alignment per format flags
+ *****************************************************************************/
+static void _Printunsigned(RT_Vterm_PRINTF_DESC *pBufferDesc, uint32_t v, uint32_t Base, uint32_t NumDigits, uint32_t FieldWidth, uint32_t FormatFlags)
+{
+    // Lookup table for converting numeric values to ASCII characters (0-9, A-F)
+    static const char _aV2C[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+    uint32_t          Div;    // Divisor for extracting digits
+    uint32_t          Digit;  // Current digit position (e.g., 1000 for 4th digit in decimal)
+    uint32_t          Number; // Copy of input value (used for calculating digit count)
+    uint32_t          Width;  // Actual number of digits in the input value
+    char              c;      // Padding character (space or '0')
+
+    Number = v;
+    Digit  = 1u;
+
+    // Step 1: Calculate the actual number of digits in the input value
+    Width = 1u;
+    while (Number >= Base)
+    {
+        Number = (Number / Base);
+        Width++;
+    }
+    // Use user-specified minimum digits if larger than actual digit count
+    if (NumDigits > Width)
+    {
+        Width = NumDigits;
+    }
+
+    // Step 2: Print leading padding (spaces or '0') if not left-justified
+    if ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u)
+    {
+        if (FieldWidth != 0u)
+        {
+            // Choose padding character: '0' if pad-zero flag is set (and no min digits), else space
+            if (((FormatFlags & FORMAT_FLAG_PAD_ZERO) == FORMAT_FLAG_PAD_ZERO) && (NumDigits == 0u))
+            {
+                c = '0';
+            }
+            else
+            {
+                c = ' ';
+            }
+            // Add padding until field width is satisfied
+            while ((FieldWidth != 0u) && (Width < FieldWidth))
+            {
+                FieldWidth--;
+                _StoreChar(pBufferDesc, c);
+                if (pBufferDesc->ReturnValue < 0)
+                {
+                    break; // Exit on write error
+                }
+            }
+        }
+    }
+
+    // Step 3: Calculate the highest digit position (e.g., 1000 for 4-digit decimal)
+    if (pBufferDesc->ReturnValue >= 0)
+    {
+        while (1)
+        {
+            if (NumDigits > 1u)
+            { // Ensure we loop at least NumDigits times (for leading zeros)
+                NumDigits--;
+            }
+            else
+            {
+                Div = v / Digit;
+                if (Div < Base)
+                { // Stop when Digit is larger than the highest digit of v
+                    break;
+                }
+            }
+            Digit *= Base;
+        }
+
+        // Step 4: Extract and print each digit from highest to lowest
+        do
+        {
+            Div = v / Digit;
+            v -= Div * Digit; // Remove the current digit from v
+            _StoreChar(pBufferDesc, _aV2C[Div]);
+            if (pBufferDesc->ReturnValue < 0)
+            {
+                break; // Exit on write error
+            }
+            Digit /= Base; // Move to the next lower digit position
+        } while (Digit);
+
+        // Step 5: Print trailing spaces if left-justified
+        if ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == FORMAT_FLAG_LEFT_JUSTIFY)
+        {
+            if (FieldWidth != 0u)
+            {
+                while ((FieldWidth != 0u) && (Width < FieldWidth))
+                {
+                    FieldWidth--;
+                    _StoreChar(pBufferDesc, ' ');
+                    if (pBufferDesc->ReturnValue < 0)
+                    {
+                        break; // Exit on write error
+                    }
+                }
+            }
+        }
+    }
+}
+
+/*****************************************************************************
+ * @brief        Print a signed integer to Vterm (internal use)
+ *
+ * @param[in,out] pBufferDesc  Pointer to the printf descriptor structure
+ * @param[in]     v            Signed integer value to print
+ * @param[in]     Base         Numeric base (e.g., 10 for decimal, 16 for hex)
+ * @param[in]     NumDigits    Minimum number of digits to print (pads with 0 if needed)
+ * @param[in]     FieldWidth   Total field width (pads with spaces/0 if value is shorter)
+ * @param[in]     FormatFlags  Format control flags (left-justify, pad-zero, etc.)
+ *
+ * @note         1. Handles negative values by prepending '-'
+ *               2. Handles sign printing (+ for positive) if FORMAT_FLAG_PRINT_SIGN is set
+ *               3. Delegates unsigned digit printing to _Printunsigned
+ *****************************************************************************/
+static void _PrintInt(RT_Vterm_PRINTF_DESC *pBufferDesc, int v, uint32_t Base, uint32_t NumDigits, uint32_t FieldWidth, uint32_t FormatFlags)
+{
+    uint32_t Width;  // Actual number of digits in the input value
+    int      Number; // Absolute value of the input (for digit count calculation)
+
+    // Get absolute value of v (handles negative numbers)
+    Number = (v < 0) ? -v : v;
+
+    // Step 1: Calculate the actual number of digits in the input value
+    Width = 1u;
+    while (Number >= (int)Base)
+    {
+        Number = (Number / (int)Base);
+        Width++;
+    }
+    // Use user-specified minimum digits if larger than actual digit count
+    if (NumDigits > Width)
+    {
+        Width = NumDigits;
+    }
+    // Reserve 1 character width for sign (+/-) if needed
+    if ((FieldWidth > 0u) && ((v < 0) || ((FormatFlags & FORMAT_FLAG_PRINT_SIGN) == FORMAT_FLAG_PRINT_SIGN)))
+    {
+        FieldWidth--;
+    }
+
+    // Step 2: Print leading spaces (if not pad-zero or left-justified)
+    if ((((FormatFlags & FORMAT_FLAG_PAD_ZERO) == 0u) || (NumDigits != 0u)) && ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u))
+    {
+        if (FieldWidth != 0u)
+        {
+            while ((FieldWidth != 0u) && (Width < FieldWidth))
+            {
+                FieldWidth--;
+                _StoreChar(pBufferDesc, ' ');
+                if (pBufferDesc->ReturnValue < 0)
+                {
+                    break; // Exit on write error
+                }
+            }
+        }
+    }
+
+    // Step 3: Print sign (+/-) if needed
+    if (pBufferDesc->ReturnValue >= 0)
+    {
+        if (v < 0)
+        {
+            v = -v; // Convert to positive for digit printing
+            _StoreChar(pBufferDesc, '-');
+        }
+        else if ((FormatFlags & FORMAT_FLAG_PRINT_SIGN) == FORMAT_FLAG_PRINT_SIGN)
+        {
+            _StoreChar(pBufferDesc, '+'); // Print '+' for positive values
+        }
+
+        // Step 4: Print leading zeros (if pad-zero flag is set)
+        if (pBufferDesc->ReturnValue >= 0)
+        {
+            if (((FormatFlags & FORMAT_FLAG_PAD_ZERO) == FORMAT_FLAG_PAD_ZERO) && ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u) && (NumDigits == 0u))
+            {
+                if (FieldWidth != 0u)
+                {
+                    while ((FieldWidth != 0u) && (Width < FieldWidth))
+                    {
+                        FieldWidth--;
+                        _StoreChar(pBufferDesc, '0');
+                        if (pBufferDesc->ReturnValue < 0)
+                        {
+                            break; // Exit on write error
+                        }
+                    }
+                }
+            }
+
+            // Step 5: Print the unsigned part of the number (delegate to _Printunsigned)
+            if (pBufferDesc->ReturnValue >= 0)
+            {
+                _Printunsigned(pBufferDesc, (uint32_t)v, Base, NumDigits, FieldWidth, FormatFlags);
+            }
+        }
+    }
+}
+
+/*****************************************************************************
+ * @brief        Formatted output to Vterm upstream tunnel (va_list version)
+ *
+ * @param[in]    sFormat     Format string (supports %c, %d, %u, %x/%X, %s, %p, %%)
+ * @param[in]    pParamList  Pointer to va_list containing variable arguments
+ *
+ * @retval       int         Total number of characters printed (positive on success)
+ *                           -1 if write error occurs
+ *
+ * @note         1. Uses a temporary buffer (size = RT_Vterm_PRINTF_BUFFER_SIZE) to reduce tunnel writes
+ *               2. Automatically flushes buffer when full or at the end of output
+ *               3. Handles line endings by converting '\n' to "\r\n"
+ *****************************************************************************/
+int RT_Vterm_vprintf(const char *sFormat, va_list *pParamList)
+{
+    char                 c;                                     // Current character in format string
+    RT_Vterm_PRINTF_DESC BufferDesc;                            // Printf buffer descriptor
+    int                  v;                                     // Temporary storage for variable arguments
+    uint32_t             NumDigits;                             // Minimum digits (from format string: %.2d)
+    uint32_t             FormatFlags;                           // Format flags (from format string: %-0+#)
+    uint32_t             FieldWidth;                            // Field width (from format string: %5d)
+    char                 acBuffer[RT_Vterm_PRINTF_BUFFER_SIZE]; // Temporary buffer
+
+    // Initialize printf descriptor
+    BufferDesc.pBuffer     = acBuffer;
+    BufferDesc.BufferSize  = RT_Vterm_PRINTF_BUFFER_SIZE;
+    BufferDesc.Cnt         = 0u;
+    BufferDesc.ReturnValue = 0;
+
+    // Parse format string character by character
+    do
+    {
+        c = *sFormat;
+        sFormat++;
+        if (c == 0u)
+        {
+            break; // Exit if end of format string
+        }
+
+        // Handle format specifier (starts with '%')
+        if (c == '%')
+        {
+            // Step 1: Parse format flags (-0+#)
+            FormatFlags = 0u;
+            v           = 1;
+            do
+            {
+                c = *sFormat;
+                switch (c)
+                {
+                case '-':
+                    FormatFlags |= FORMAT_FLAG_LEFT_JUSTIFY;
+                    sFormat++;
+                    break;
+                case '0':
+                    FormatFlags |= FORMAT_FLAG_PAD_ZERO;
+                    sFormat++;
+                    break;
+                case '+':
+                    FormatFlags |= FORMAT_FLAG_PRINT_SIGN;
+                    sFormat++;
+                    break;
+                case '#':
+                    FormatFlags |= FORMAT_FLAG_ALTERNATE;
+                    sFormat++;
+                    break;
+                default:
+                    v = 0; // Exit loop if no more flags
+                    break;
+                }
+            } while (v);
+
+            // Step 2: Parse field width (e.g., %5d → FieldWidth=5)
+            FieldWidth = 0u;
+            do
+            {
+                c = *sFormat;
+                if ((c < '0') || (c > '9'))
+                {
+                    break; // Exit if not a digit
+                }
+                sFormat++;
+                FieldWidth = (FieldWidth * 10u) + ((uint32_t)c - '0');
+            } while (1);
+
+            // Step 3: Parse precision (e.g., %.2d → NumDigits=2)
+            NumDigits = 0u;
+            c         = *sFormat;
+            if (c == '.')
+            {
+                sFormat++;
+                do
+                {
+                    c = *sFormat;
+                    if ((c < '0') || (c > '9'))
+                    {
+                        break; // Exit if not a digit
+                    }
+                    sFormat++;
+                    NumDigits = NumDigits * 10u + ((uint32_t)c - '0');
+                } while (1);
+            }
+
+            // Step 4: Skip length modifiers (l/h) (not supported in this implementation)
+            c = *sFormat;
+            do
+            {
+                if ((c == 'l') || (c == 'h'))
+                {
+                    sFormat++;
+                    c = *sFormat;
+                }
+                else
+                {
+                    break;
+                }
+            } while (1);
+
+            // Step 5: Handle format specifiers (c/d/u/x/X/s/p/%)
+            switch (c)
+            {
+            case 'c': // Print single character
+            {
+                char c0;
+                v  = va_arg(*pParamList, int); // va_arg returns int for char (promotion)
+                c0 = (char)v;
+                _StoreChar(&BufferDesc, c0);
+                break;
+            }
+            case 'd': // Print signed decimal integer
+                v = va_arg(*pParamList, int);
+                _PrintInt(&BufferDesc, v, 10u, NumDigits, FieldWidth, FormatFlags);
+                break;
+            case 'u': // Print unsigned decimal integer
+                v = va_arg(*pParamList, int);
+                _Printunsigned(&BufferDesc, (uint32_t)v, 10u, NumDigits, FieldWidth, FormatFlags);
+                break;
+            case 'x': // Print unsigned hexadecimal (uppercase, same as 'X' here)
+            case 'X':
+                v = va_arg(*pParamList, int);
+                _Printunsigned(&BufferDesc, (uint32_t)v, 16u, NumDigits, FieldWidth, FormatFlags);
+                break;
+            case 's': // Print null-terminated string
+            {
+                const char *s = va_arg(*pParamList, const char *);
+                do
+                {
+                    c = *s;
+                    s++;
+                    if (c == '\0')
+                    {
+                        break; // Exit at end of string
+                    }
+                    _StoreChar(&BufferDesc, c);
+                } while (BufferDesc.ReturnValue >= 0);
+            }
+            break;
+            case 'p': // Print pointer (8-digit hexadecimal)
+                v = va_arg(*pParamList, int);
+                _Printunsigned(&BufferDesc, (uint32_t)v, 16u, 8u, 8u, 0u);
+                break;
+            case '%': // Print literal '%'
+                _StoreChar(&BufferDesc, '%');
+                break;
+            default: // Ignore unknown specifiers
+                break;
+            }
+            sFormat++; // Move past the specifier character
+        }
+        else
+        {
+            // Not a format specifier: store the character directly
+            _StoreChar(&BufferDesc, c);
+        }
+    } while (BufferDesc.ReturnValue >= 0);
+
+    // Flush remaining characters in the buffer (if any)
+    if (BufferDesc.ReturnValue > 0)
+    {
+        if (BufferDesc.Cnt != 0u)
+        {
+            RT_Vterm_Write(acBuffer, BufferDesc.Cnt);
+        }
+        BufferDesc.ReturnValue += (int)BufferDesc.Cnt;
+    }
+
+    return BufferDesc.ReturnValue;
+}
+
+/*****************************************************************************
+ * @brief        Formatted output to Vterm upstream tunnel (user-facing)
+ *
+ * @param[in]    sFormat  Format string (supports %c, %d, %u, %x/%X, %s, %p, %%)
+ * @param[in]    ...      Variable arguments matching the format string
+ *
+ * @retval       int      Total number of characters printed (positive on success)
+ *                        -1 if write error occurs
+ *
+ * @note         Wraps RT_Vterm_vprintf and handles variable argument list setup/teardown
+ *****************************************************************************/
+int RT_Vterm_printf(const char *sFormat, ...)
+{
+    int     r;         // Return value from RT_Vterm_vprintf
+    va_list ParamList; // Variable argument list
+
+    va_start(ParamList, sFormat); // Initialize argument list
+    r = RT_Vterm_vprintf(sFormat, &ParamList);
+    va_end(ParamList); // Clean up argument list
+
+    return r;
+}
+
+/*************************** End of file ****************************/

+ 219 - 0
RT_Vterm.c

@@ -0,0 +1,219 @@
+#include "RT_Vterm.h"
+#include <finsh.h>
+#include <rtthread.h>
+#include <string.h>
+
+#include "RT_tunnel.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/**< Virtual Terminal device name */
+#define VTERM_DEVICE_NAME "VTerm"
+
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+
+/**< Tunnel instance for upstream data transmission  */
+RT_tunnel_t up_tunnel = RT_NULL;
+/**< Tunnel instance for downstream data transmission */
+RT_tunnel_t down_tunnel = RT_NULL;
+
+/*****************************************************************************
+ * @brief        Initialize the Virtual Terminal module
+ *
+ * @details      This function acquires free tunnel instances for upstream and
+ *               downstream data transmission, and configures their operation modes.
+ *               Upstream tunnel is set to write mode, downstream to read mode.
+ *
+ * @retval       int         RT_EOK on success (0), error code otherwise
+ *
+ * @note         Initialized after tunnel system but before device drivers
+ *****************************************************************************/
+int RT_Vterm_Init(void)
+{
+    up_tunnel   = Get_Free_Tunnel();
+    down_tunnel = Get_Free_Tunnel();
+
+    if(!up_tunnel||!down_tunnel)
+    {
+        LOG_ERROR("no enough tunnel");
+        return -RT_ENOMEM;
+    }
+
+    Set_Tunnel_Operation(up_tunnel, tunnel_write);
+    Set_Tunnel_Operation(down_tunnel, tunnel_read);
+    up_tunnel->ID   = 0x56545455; // VTTU
+    down_tunnel->ID = 0x56545444; // VTTD
+
+    return RT_EOK;
+}
+// Ensure initialization after tunnel system and before device drivers
+// INIT_PREV_EXPORT(RT_Vterm_Init);
+MSH_CMD_EXPORT(RT_Vterm_Init, RT_Vterm_Init)
+
+/*****************************************************************************
+ * @brief        Write data to upstream tunnel buffer
+ *
+ * @param[in]    pBuffer     Pointer to data buffer to be written
+ * @param[in]    NumBytes    Number of bytes to write
+ *
+ * @retval       uint32_t    Actual number of bytes written, 0 if tunnel is invalid
+ *
+ * @note         Thread-safe operation with internal locking
+ *****************************************************************************/
+uint32_t RT_Vterm_Write(const void *pBuffer, uint32_t NumBytes)
+{
+    if (up_tunnel)
+        return up_tunnel->write(up_tunnel, (void *)pBuffer, NumBytes);
+    return 0;
+}
+
+/*****************************************************************************
+ * @brief        Write data to downstream tunnel buffer
+ *
+ * @param[in]    pBuffer     Pointer to data buffer to be written
+ * @param[in]    NumBytes    Number of bytes to write
+ *
+ * @retval       uint32_t    Actual number of bytes written, 0 if tunnel is invalid
+ *
+ * @note         Thread-safe operation with internal locking
+ *****************************************************************************/
+uint32_t RT_Vterm_WriteDownBuffer(const void *pBuffer, uint32_t NumBytes)
+{
+    if (down_tunnel)
+        return down_tunnel->write(down_tunnel, (void *)pBuffer, NumBytes);
+    return 0;
+}
+
+/*****************************************************************************
+ * @brief        Read data from upstream tunnel buffer
+ *
+ * @param[out]   pData       Pointer to buffer to store read data
+ * @param[in]    BufferSize  Maximum number of bytes to read
+ *
+ * @retval       uint32_t    Actual number of bytes read, 0 if tunnel is invalid
+ *
+ * @note         Thread-safe operation with internal locking
+ *****************************************************************************/
+uint32_t RT_Vterm_ReadUpBuffer(void *pData, uint32_t BufferSize)
+{
+    if (up_tunnel)
+        return up_tunnel->read(up_tunnel, pData, BufferSize);
+    return 0;
+}
+
+/*****************************************************************************
+ * @brief        Read data from downstream tunnel buffer
+ *
+ * @param[out]   pData       Pointer to buffer to store read data
+ * @param[in]    BufferSize  Maximum number of bytes to read
+ *
+ * @retval       uint32_t    Actual number of bytes read, 0 if tunnel is invalid
+ *
+ * @note         Thread-safe operation with internal locking
+ *****************************************************************************/
+uint32_t RT_Vterm_Read(void *pData, uint32_t BufferSize)
+{
+    if (down_tunnel)
+        return down_tunnel->read(down_tunnel, pData, BufferSize);
+    return 0;
+}
+
+/**
+ * @brief  Check used bytes in downstream tunnel buffer
+ * @retval uint32_t  Number of available bytes in downstream buffer, 0 if tunnel invalid
+ */
+uint32_t RT_Vterm_HasData(void)
+{
+    if (down_tunnel)
+        return Get_Tunnel_Buffer_Used(down_tunnel);
+    return 0;
+}
+
+/**
+ * @brief  Check used bytes in upstream tunnel buffer
+ * @retval uint32_t  Number of available bytes in upstream buffer, 0 if tunnel invalid
+ */
+uint32_t RT_Vterm_HasDataUp(void)
+{
+    if (up_tunnel)
+        return Get_Tunnel_Buffer_Used(up_tunnel);
+    return 0;
+}
+
+/**
+ * @brief  Check if there's available data in downstream buffer
+ * @retval int       1 if data exists, 0 otherwise
+ */
+int RT_Vterm_HasKey(void)
+{
+    return RT_Vterm_HasData() > 0 ? 1 : 0;
+}
+
+/*****************************************************************************
+ * @brief        Block until data is available in downstream buffer, then read 1 byte
+ *
+ * @retval       int         The read byte (as unsigned char) when available
+ *
+ * @note         This function blocks with 1ms delay between checks
+ *               Never returns -1 (infinite loop until data arrives)
+ *****************************************************************************/
+int RT_Vterm_WaitKey(void)
+{
+    int  r;
+    char c;
+    do
+    {
+        r = RT_Vterm_Read(&c, 1);
+        if (r == 1)
+            return (unsigned char)c;
+        rt_thread_mdelay(1);
+    } while (1);
+}
+
+/**
+ * @brief  Read one byte from downstream buffer (non-blocking)
+ * @retval int  The read byte (as unsigned char) if available, -1 otherwise
+ */
+int RT_Vterm_GetKey(void)
+{
+    char c;
+    int  r = RT_Vterm_Read(&c, 1);
+    if (r == 1)
+        return (unsigned char)c;
+    return -1;
+}
+
+/**
+ * @brief  Write a single character to upstream buffer
+ * @param[in] c  Character to write
+ * @retval uint32_t  1 if successful, 0 otherwise
+ */
+uint32_t RT_Vterm_PutChar(char c)
+{
+    return RT_Vterm_Write(&c, 1);
+}
+
+/**
+ * @brief  Write a single character to upstream buffer (direct call)
+ * @param[in] c  Character to write
+ * @retval uint32_t  1 if successful, 0 otherwise
+ */
+uint32_t RT_Vterm_PutCharSkip(char c)
+{
+    if (up_tunnel)
+        return up_tunnel->write(up_tunnel, &c, 1);
+    return 0;
+}
+
+/**
+ * @brief  Get available write space in upstream buffer
+ * @retval uint32_t  Number of free bytes in upstream buffer, 0 if tunnel invalid
+ */
+uint32_t RT_Vterm_GetAvailWriteSpace(void)
+{
+    if (up_tunnel)
+        return Get_Tunnel_Buffer_Free(up_tunnel);
+    return 0;
+}

+ 154 - 0
RT_Vterm.h

@@ -0,0 +1,154 @@
+#ifndef RT_Vterm_H
+#define RT_Vterm_H
+
+#include "RT_tunnel.h"
+#include <stdarg.h>
+#include <stdlib.h>
+
+/**< tunnel handle for upstream data transmission */
+extern RT_tunnel_t up_tunnel;
+/**< tunnel handle for downstream data transmission */
+extern RT_tunnel_t down_tunnel;
+
+/**
+ * @brief        Write data to upstream buffer
+ * @param[in]    pBuffer     Pointer to data buffer
+ * @param[in]    NumBytes    Number of bytes to write
+ * @retval       uint32_t    Actual bytes written
+ */
+uint32_t RT_Vterm_Write(const void *pBuffer, uint32_t NumBytes);
+
+/**
+ * @brief        Write data to downstream buffer
+ * @param[in]    pBuffer     Pointer to data buffer
+ * @param[in]    NumBytes    Number of bytes to write
+ * @retval       uint32_t    Actual bytes written
+ */
+uint32_t RT_Vterm_WriteDownBuffer(const void *pBuffer, uint32_t NumBytes);
+
+/**
+ * @brief        Read data from upstream buffer
+ * @param[out]   pData       Buffer to store read data
+ * @param[in]    BufferSize  Maximum bytes to read
+ * @retval       uint32_t    Actual bytes read
+ */
+uint32_t RT_Vterm_ReadUpBuffer(void *pData, uint32_t BufferSize);
+
+/**
+ * @brief        Read data from downstream buffer
+ * @param[out]   pData       Buffer to store read data
+ * @param[in]    BufferSize  Maximum bytes to read
+ * @retval       uint32_t    Actual bytes read
+ */
+uint32_t RT_Vterm_Read(void *pData, uint32_t BufferSize);
+
+/**
+ * @brief        Write a single character to upstream buffer
+ * @param[in]    c           Character to write
+ * @retval       uint32_t    1 if successful, 0 otherwise
+ */
+uint32_t RT_Vterm_PutChar(char c);
+
+/**
+ * @brief        Write a single character to upstream buffer (direct operation)
+ * @param[in]    c           Character to write
+ * @retval       uint32_t    1 if successful, 0 otherwise
+ */
+uint32_t RT_Vterm_PutCharSkip(char c);
+
+/**
+ * @brief  Check available data in downstream buffer
+ * @retval uint32_t  Number of used bytes in downstream buffer
+ */
+uint32_t RT_Vterm_HasData(void);
+
+/**
+ * @brief  Check available data in upstream buffer
+ * @retval uint32_t  Number of used bytes in upstream buffer
+ */
+uint32_t RT_Vterm_HasDataUp(void);
+
+/**
+ * @brief  Check if there's available key data in downstream buffer
+ * @retval int       1 if data exists, 0 otherwise
+ */
+int RT_Vterm_HasKey(void);
+
+/**
+ * @brief  Block until a key is available in downstream buffer, then return it
+ * @retval int       The received character (as unsigned char)
+ */
+int RT_Vterm_WaitKey(void);
+
+/**
+ * @brief  Read a key from downstream buffer (non-blocking)
+ * @retval int       The received character (as unsigned char) if available, -1 otherwise
+ */
+int RT_Vterm_GetKey(void);
+
+/**
+ * @brief  Get available write space in upstream buffer
+ * @retval uint32_t  Number of free bytes in upstream buffer
+ */
+uint32_t RT_Vterm_GetAvailWriteSpace(void);
+
+/**
+ * @brief        Formatted output to virtual terminal (upstream)
+ * @param[in]    sFormat     Format string
+ * @param[in]    ...         Variable arguments
+ * @retval       int         Number of characters written, negative on error
+ */
+int RT_Vterm_printf(const char *sFormat, ...);
+
+/**
+ * @brief        Formatted output with va_list to virtual terminal (upstream)
+ * @param[in]    sFormat     Format string
+ * @param[in]    pParamList  Pointer to va_list
+ * @retval       int         Number of characters written, negative on error
+ */
+int RT_Vterm_vprintf(const char *sFormat, va_list *pParamList);
+
+/**
+ * @brief  Callback function type for virtual terminal commands
+ * @param[in] input  Command input string
+ */
+typedef void (*vterm_cmd_callback_t)(const char *input);
+
+/**
+ * @struct vterm_cmd_entry_t
+ * @brief  Structure for virtual terminal command registration
+ */
+typedef struct
+{
+    char                 cmd[32];  /**< Command string (max 31 characters) */
+    vterm_cmd_callback_t callback; /**< Corresponding callback function */
+} vterm_cmd_entry_t;
+
+/**
+ * @brief  Switch to virtual terminal mode
+ */
+void vterm_console(void);
+
+/**
+ * @brief  Restore original terminal mode
+ */
+void restore_original(void);
+
+/**
+ * @brief  Initialize virtual terminal
+ * @retval int  0 on success, negative error code otherwise
+ */
+int RT_Vterm_Init(void);
+
+/**< Default mode: Do not block, skip output when buffer is full */
+#define RT_Vterm_MODE_NO_BLOCK_SKIP (0)
+/**< Mode: Do not block, write partial data when buffer space is insufficient */
+#define RT_Vterm_MODE_NO_BLOCK_TRIM (1)
+/**< Mode: Block execution if buffer is full until space is available */
+#define RT_Vterm_MODE_BLOCK_IF_FIFO_FULL (2)
+/**< Mode: Overwrite old data when buffer is full */
+#define RT_Vterm_MODE_OVERWRITE (3)
+
+#endif
+
+/*************************** End of file ****************************/

+ 54 - 0
RT_Vterm_examples/RT_Vterm_example_basic.c

@@ -0,0 +1,54 @@
+#include "rt_Vterm.h"
+#include <rtthread.h>
+
+/**
+ * @brief        Demonstrates basic Vterm read/write operations
+ *
+ * @note         1. Writes a message to the upstream buffer using RT_Vterm_Write
+ *               2. Checks if there's data in the downstream buffer using RT_Vterm_HasData
+ *               3. Reads data from downstream buffer if available
+ *               4. Prints confirmation messages to the system console
+ */
+void vterm_example_basic(void)
+{
+    char msg[]   = "Vterm Basic Example!"; // Message to send to upstream
+    char buf[32] = {0};                  // Buffer to store downstream data
+
+    // Write message to upstream buffer (exclude null terminator)
+    RT_Vterm_Write(msg, sizeof(msg) - 1);
+    rt_kprintf("已写入上行: %s\n", msg);
+
+    // Check if downstream buffer has data
+    if (RT_Vterm_HasData())
+    {
+        // Read data from downstream buffer
+        RT_Vterm_Read(buf, sizeof(buf));
+        rt_kprintf("收到下行: %s\n", buf);
+    }
+}
+MSH_CMD_EXPORT(vterm_example_basic, Vterm basic read / write example);
+
+/**
+ * @brief        Demonstrates various formatted output using RT_Vterm_printf
+ *
+ * @note         1. Shows different data types (integer, hexadecimal, string, float)
+ *               2. Uses various format specifiers to illustrate RT_Vterm_printf capabilities
+ *               3. Outputs results to Vterm upstream tunnel and confirms completion via system console
+ */
+void vterm_example_printf(void)
+{
+    int         num  = 123;
+    unsigned    hex  = 0xABCD;
+    const char *str  = "hello";
+    float       fval = 3.1415f;
+
+    RT_Vterm_printf("==== Vterm printf 示例 ====\n");
+    RT_Vterm_printf("整数: %d\n", num);
+    RT_Vterm_printf("十六进制: 0x%X\n", hex);
+    RT_Vterm_printf("字符串: %s\n", str);
+    RT_Vterm_printf("混合: num=%d, hex=0x%X, str=%s\n", num, hex, str);
+    RT_Vterm_printf("请在调试器端查看输出\n");
+
+    rt_kprintf("Vterm printf example done.\n");
+}
+MSH_CMD_EXPORT(vterm_example_printf, Vterm printf example);

+ 9 - 0
RT_Vterm_examples/SConscript

@@ -0,0 +1,9 @@
+from building import *
+
+cwd     = GetCurrentDir()
+src     = Glob('*.c')
+CPPPATH = [cwd]
+
+group = DefineGroup('VIRTUAL_TERMINAL_TEST', src, depend = [], CPPPATH = CPPPATH)
+
+Return('group')

+ 17 - 0
SConscript

@@ -0,0 +1,17 @@
+from building import *
+
+cwd     = GetCurrentDir()
+src     = Glob('*.c')
+
+# 扫描当前目录下的所有子目录,并收集其中的 .c 文件
+import os
+subdirs = [d for d in os.listdir(cwd) if os.path.isdir(os.path.join(cwd, d))]
+for subdir in subdirs:
+    sub_src = Glob(os.path.join(subdir, '*.c'))
+    src += sub_src
+
+CPPPATH = [cwd] + [os.path.join(cwd, d) for d in subdirs]
+
+group = DefineGroup('VIRTUAL_TERMINAL', src, depend = [], CPPPATH = CPPPATH)
+
+Return('group')

+ 229 - 0
drv_Vterm.c

@@ -0,0 +1,229 @@
+#include "RT_Vterm.h"
+#include "shell.h"
+#include "string.h"
+#include <ctype.h>
+#include <rtdevice.h>
+#include <rtthread.h>
+#include <stdio.h>
+
+/**< Maximum number of registered Vterm commands */
+#define Vterm_CMD_MAX 8
+/**< Size of input buffer for Vterm command processing */
+#define Vterm_INPUT_BUF_SIZE 128
+
+/**< Table storing registered Vterm commands and their callbacks */
+static vterm_cmd_entry_t vterm_cmd_table[Vterm_CMD_MAX];
+/**< Current count of registered Vterm commands */
+static int vterm_cmd_count = 0;
+
+/**
+ * @struct vterm_device
+ * @brief  Virtual terminal device structure, extending RT-Thread device
+ */
+struct vterm_device
+{
+    struct rt_device parent;           /**< Inherited RT-Thread device base */
+    rt_device_t      original_console; /**< Original console device (for restoration) */
+    rt_device_t      original_finsh;   /**< Original FinSH device (for restoration) */
+};
+
+/**< Global instance of virtual terminal device */
+static struct vterm_device vterm_dev;
+extern struct finsh_shell *shell; /**< External reference to FinSH shell instance */
+
+/****************************************/
+/**
+ * @brief        Open virtual terminal device
+ *
+ * @param[in]    dev     Pointer to RT-Thread device structure
+ * @param[in]    oflag   Open flags (unused in this implementation)
+ *
+ * @retval       rt_err_t  RT_EOK always (success)
+ *
+ * @note         No special initialization needed for open operation
+ */
+static rt_err_t vterm_open(rt_device_t dev, rt_uint16_t oflag)
+{
+    return RT_EOK; // Vterm open requires no specific setup
+}
+
+/**
+ * @brief        Write data to virtual terminal
+ *
+ * @param[in]    dev     Pointer to RT-Thread device structure
+ * @param[in]    pos     Offset (unused for character devices)
+ * @param[in]    buffer  Pointer to data buffer to write
+ * @param[in]    size    Number of bytes to write
+ *
+ * @retval       rt_size_t  Number of bytes written (always equals input size)
+ *
+ * @note         1. Converts '\n' to "\r\n" for proper line endings
+ *               2. Uses RT_Vterm_PutChar for actual data transmission
+ */
+static rt_ssize_t vterm_write(rt_device_t dev, rt_off_t pos,
+                              const void *buffer, rt_size_t size)
+{
+    const char *ptr = (const char *)buffer;
+    rt_size_t   i;
+
+    // Write each character, converting newline to CRLF
+    for (i = 0; i < size; i++)
+    {
+        if (ptr[i] == '\n')
+        {
+            RT_Vterm_PutChar('\r'); // Prepend carriage return before newline
+        }
+        RT_Vterm_PutChar(ptr[i]);
+    }
+    return size; // All bytes are written (logical success)
+}
+
+/**
+ * @brief        Read data from virtual terminal
+ *
+ * @param[in]    dev     Pointer to RT-Thread device structure
+ * @param[in]    pos     Offset (unused for character devices)
+ * @param[out]   buffer  Pointer to buffer to store read data
+ * @param[in]    size    Maximum number of bytes to read
+ *
+ * @retval       rt_ssize_t  Number of bytes actually read
+ *
+ * @note         Wraps RT_Vterm_Read for data retrieval from downstream buffer
+ */
+static rt_ssize_t vterm_read(rt_device_t dev, rt_off_t pos,
+                             void *buffer, rt_size_t size)
+{
+    rt_size_t ret = (rt_size_t)RT_Vterm_Read(buffer, size);
+    return ret;
+}
+
+/****************************************/
+/**
+ * @brief        Indicate received data to FinSH shell
+ *
+ * @param[in]    dev     Pointer to RT-Thread device structure (unused)
+ * @param[in]    size    Size of received data (unused)
+ *
+ * @retval       rt_err_t  RT_EOK if shell semaphore is released successfully
+ *
+ * @note         Triggers FinSH processing by releasing its receive semaphore
+ */
+static rt_err_t finsh_rx_ind(rt_device_t dev, rt_size_t size)
+{
+    if (shell)
+        rt_sem_release(&shell->rx_sem); // Notify shell of available data
+    return RT_EOK;
+}
+
+/****************************************/
+/**
+ * @brief        Switch all I/O (console and FinSH) to virtual terminal
+ *
+ * @note         1. Saves original console and FinSH devices for later restoration
+ *               2. Redirects console and FinSH output to "vterm_buffer" device
+ *               3. Prints confirmation message via original console
+ */
+void vterm_console(void)
+{
+    /* Save original I/O devices */
+    vterm_dev.original_console = rt_console_get_device();
+    vterm_dev.original_finsh   = rt_device_find(finsh_get_device());
+
+    /* Redirect console and FinSH to Vterm */
+    rt_console_set_device("vterm");
+    finsh_set_device("vterm");
+
+    rt_kprintf("[OK] All I/O now via Vterm\n"); // Confirmation message
+}
+MSH_CMD_EXPORT(vterm_console, Switch ALL I / O to Vterm);
+
+/****************************************/
+/**
+ * @brief        Restore original I/O devices (console and FinSH)
+ *
+ * @note         1. Restores console and FinSH to their original devices
+ *               2. Does nothing if original devices are not saved
+ *               3. Prints confirmation message via restored console
+ */
+void restore_original(void)
+{
+    if (vterm_dev.original_console)
+    {
+        rt_console_set_device(vterm_dev.original_console->parent.name);
+    }
+    if (vterm_dev.original_finsh)
+    {
+        finsh_set_device(vterm_dev.original_finsh->parent.name);
+    }
+    rt_kprintf("[OK] Restored original I/O devices\n"); // Confirmation message
+}
+MSH_CMD_EXPORT(restore_original, Restore original I / O devices);
+
+/****************************************/
+/**
+ * @brief        Thread entry for polling Vterm data and notifying FinSH
+ *
+ * @param[in]    p  Unused (thread parameter placeholder)
+ *
+ * @note         1. Runs indefinitely, checking for downstream data every 10ms
+ *               2. Triggers FinSH data processing via finsh_rx_ind when data is available
+ */
+static void vterm_check(void)
+{
+    if (RT_Vterm_HasData())                 // Check if downstream buffer has data
+        finsh_rx_ind(&vterm_dev.parent, 1); // Notify FinSH of new data
+}
+
+/****************************************/
+/**
+ * @brief        Initialize Vterm buffer device and related components
+ *
+ * @retval       int  RT_EOK on success, error code otherwise
+ *
+ * @note         1. Registers "vterm" as a character device
+ *               2. Sets up device operations (open, read, write)
+ *               3. Sets an idle hook for data detection
+ */
+int vterm_device_init(void)
+{
+    rt_err_t ret;
+
+    // Initialize Vterm device structure
+    vterm_dev.parent.type  = RT_Device_Class_Char;
+    vterm_dev.parent.open  = vterm_open;
+    vterm_dev.parent.read  = vterm_read;
+    vterm_dev.parent.write = vterm_write;
+
+    // Register Vterm device with RT-Thread device manager
+    ret = rt_device_register(&vterm_dev.parent, "vterm", RT_DEVICE_FLAG_RDWR);
+    if (ret != RT_EOK)
+    {
+        return ret;
+    }
+
+    rt_thread_idle_sethook(vterm_check);
+
+    return RT_EOK;
+}
+MSH_CMD_EXPORT(vterm_device_init, Initialize Vterm buffer device);
+
+/****************************************/
+/**
+ * @brief        Switch the device of the shell terminal to vterm
+ *
+ * @retval       int  RT_EOK on success, error code otherwise
+ *
+ * @note         1. Initialize Vterm buffer device and related components
+ *               2. Switch all I/O (console and FinSH) to virtual terminal
+ */
+int rt_hw_vterm_console_init(void)
+{
+    int ret = vterm_device_init();
+    if (ret != RT_EOK)
+    {
+        return ret;
+    }
+    vterm_console();
+    return RT_EOK;
+}
+MSH_CMD_EXPORT(rt_hw_vterm_console_init, Initialize Vterm buffer device);