C#(.NET 8.0)创建串口通讯超声波雷达扫描程序

因为最近在调试小车的超声波雷达,想着用弄个炫酷的小程序测试它,但是没找到合适的,就自己用C#开发了一个。测试没问题,比较简单直接上源码。

需要注意的是,单片机那边需要按协议发送数据:
//协议格式:"A<角度>,D<距离>\n"
//例如STM32 USART串口输出:printf("A%d,D%d\n", angle, distance);

下面开始正文。

WinForm如上,注意添加图片属性,或者全部复制下面的代码。

//Program.cs //并不需要修改

namespace UltrasonicRadar
{
    internal static class Program
    {
        /// <summary>
        ///  The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // To customize application configuration such as set high DPI settings or default font,
            // see https://aka.ms/applicationconfiguration.
            ApplicationConfiguration.Initialize();
            Application.Run(new Form1());
        }
    }
}

//Form.Designer.cs //可以自己定义,或者直接复制

namespace UltrasonicRadar
{
    partial class Form1
    {
        /// <summary>
        ///  Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        ///  Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        ///  Required method for Designer support - do not modify
        ///  the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            btnConnect = new Button();
            btnScanToggle = new Button();
            btnClear = new Button();
            radarDisplay = new PictureBox();
            lblStatus = new Label();
            lblPort = new Label();
            cmbPorts = new ComboBox();
            lblBaudRate = new Label();
            cmbBaudRate = new ComboBox();
            lblDataBits = new Label();
            cmbDataBits = new ComboBox();
            lblStopBits = new Label();
            cmbStopBits = new ComboBox();
            lblParity = new Label();
            cmbParity = new ComboBox();
            panel1 = new Panel();
            ((System.ComponentModel.ISupportInitialize)radarDisplay).BeginInit();
            panel1.SuspendLayout();
            SuspendLayout();
            // 
            // btnConnect
            // 
            btnConnect.Location = new Point(618, 28);
            btnConnect.Name = "btnConnect";
            btnConnect.Size = new Size(80, 40);
            btnConnect.TabIndex = 0;
            btnConnect.Text = "连接串口";
            btnConnect.UseVisualStyleBackColor = true;
            btnConnect.Click += btnConnect_Click;
            // 
            // btnScanToggle
            // 
            btnScanToggle.Location = new Point(714, 28);
            btnScanToggle.Name = "btnScanToggle";
            btnScanToggle.Size = new Size(80, 40);
            btnScanToggle.TabIndex = 1;
            btnScanToggle.Text = "启动扫描";
            btnScanToggle.UseVisualStyleBackColor = true;
            btnScanToggle.Click += btnScanToggle_Click;
            // 
            // btnClear
            // 
            btnClear.Location = new Point(696, 343);
            btnClear.Name = "btnClear";
            btnClear.Size = new Size(80, 40);
            btnClear.TabIndex = 2;
            btnClear.Text = "清除位置";
            btnClear.UseVisualStyleBackColor = true;
            btnClear.Click += btnClear_Click;
            // 
            // radarDisplay
            // 
            radarDisplay.Dock = DockStyle.Fill;
            radarDisplay.Location = new Point(0, 0);
            radarDisplay.Name = "radarDisplay";
            radarDisplay.Size = new Size(600, 559);
            radarDisplay.TabIndex = 3;
            radarDisplay.TabStop = false;
            // 
            // lblStatus
            // 
            lblStatus.AutoSize = true;
            lblStatus.Font = new Font("Microsoft YaHei UI", 10.5F, FontStyle.Bold, GraphicsUnit.Point, 134);
            lblStatus.ForeColor = Color.Orange;
            lblStatus.Location = new Point(627, 89);
            lblStatus.Name = "lblStatus";
            lblStatus.Size = new Size(66, 25);
            lblStatus.TabIndex = 4;
            lblStatus.Text = "未连接";
            // 
            // lblPort
            // 
            lblPort.AutoSize = true;
            lblPort.Location = new Point(611, 141);
            lblPort.Name = "lblPort";
            lblPort.Size = new Size(54, 20);
            lblPort.TabIndex = 5;
            lblPort.Text = "串口号";
            // 
            // cmbPorts
            // 
            cmbPorts.DropDownStyle = ComboBoxStyle.DropDownList;
            cmbPorts.FormattingEnabled = true;
            cmbPorts.Location = new Point(677, 136);
            cmbPorts.Name = "cmbPorts";
            cmbPorts.Size = new Size(121, 28);
            cmbPorts.TabIndex = 6;
            // 
            // lblBaudRate
            // 
            lblBaudRate.AutoSize = true;
            lblBaudRate.Location = new Point(611, 181);
            lblBaudRate.Name = "lblBaudRate";
            lblBaudRate.Size = new Size(54, 20);
            lblBaudRate.TabIndex = 7;
            lblBaudRate.Text = "波特率";
            // 
            // cmbBaudRate
            // 
            cmbBaudRate.DropDownStyle = ComboBoxStyle.DropDownList;
            cmbBaudRate.FormattingEnabled = true;
            cmbBaudRate.Location = new Point(677, 176);
            cmbBaudRate.Name = "cmbBaudRate";
            cmbBaudRate.Size = new Size(121, 28);
            cmbBaudRate.TabIndex = 8;
            // 
            // lblDataBits
            // 
            lblDataBits.AutoSize = true;
            lblDataBits.Location = new Point(611, 221);
            lblDataBits.Name = "lblDataBits";
            lblDataBits.Size = new Size(54, 20);
            lblDataBits.TabIndex = 9;
            lblDataBits.Text = "数据位";
            // 
            // cmbDataBits
            // 
            cmbDataBits.DropDownStyle = ComboBoxStyle.DropDownList;
            cmbDataBits.FormattingEnabled = true;
            cmbDataBits.Location = new Point(677, 216);
            cmbDataBits.Name = "cmbDataBits";
            cmbDataBits.Size = new Size(121, 28);
            cmbDataBits.TabIndex = 10;
            // 
            // lblStopBits
            // 
            lblStopBits.AutoSize = true;
            lblStopBits.Location = new Point(611, 261);
            lblStopBits.Name = "lblStopBits";
            lblStopBits.Size = new Size(54, 20);
            lblStopBits.TabIndex = 11;
            lblStopBits.Text = "停止位";
            // 
            // cmbStopBits
            // 
            cmbStopBits.DropDownStyle = ComboBoxStyle.DropDownList;
            cmbStopBits.FormattingEnabled = true;
            cmbStopBits.Location = new Point(677, 256);
            cmbStopBits.Name = "cmbStopBits";
            cmbStopBits.Size = new Size(121, 28);
            cmbStopBits.TabIndex = 12;
            // 
            // lblParity
            // 
            lblParity.AutoSize = true;
            lblParity.Location = new Point(611, 301);
            lblParity.Name = "lblParity";
            lblParity.Size = new Size(54, 20);
            lblParity.TabIndex = 13;
            lblParity.Text = "校验位";
            // 
            // cmbParity
            // 
            cmbParity.DropDownStyle = ComboBoxStyle.DropDownList;
            cmbParity.FormattingEnabled = true;
            cmbParity.Location = new Point(677, 296);
            cmbParity.Name = "cmbParity";
            cmbParity.Size = new Size(121, 28);
            cmbParity.TabIndex = 14;
            // 
            // panel1
            // 
            panel1.Controls.Add(radarDisplay);
            panel1.Dock = DockStyle.Left;
            panel1.Location = new Point(0, 0);
            panel1.Name = "panel1";
            panel1.Size = new Size(600, 559);
            panel1.TabIndex = 15;
            // 
            // Form1
            // 
            AutoScaleDimensions = new SizeF(9F, 20F);
            AutoScaleMode = AutoScaleMode.Font;
            ClientSize = new Size(811, 559);
            Controls.Add(panel1);
            Controls.Add(cmbParity);
            Controls.Add(lblParity);
            Controls.Add(cmbStopBits);
            Controls.Add(lblStopBits);
            Controls.Add(cmbDataBits);
            Controls.Add(lblDataBits);
            Controls.Add(cmbBaudRate);
            Controls.Add(lblBaudRate);
            Controls.Add(cmbPorts);
            Controls.Add(lblPort);
            Controls.Add(lblStatus);
            Controls.Add(btnClear);
            Controls.Add(btnScanToggle);
            Controls.Add(btnConnect);
            FormBorderStyle = FormBorderStyle.FixedDialog;
            MaximizeBox = false;
            Name = "Form1";
            Text = "STM32F4超声波雷达";
            FormClosing += Form1_FormClosing;
            ((System.ComponentModel.ISupportInitialize)radarDisplay).EndInit();
            panel1.ResumeLayout(false);
            ResumeLayout(false);
            PerformLayout();
        }

        #endregion

        private Button btnConnect;
        private Button btnScanToggle;
        private Button btnClear;
        private PictureBox radarDisplay;
        private Label lblStatus;
        private Label lblPort;
        private ComboBox cmbPorts;
        private Label lblBaudRate;
        private ComboBox cmbBaudRate;
        private Label lblDataBits;
        private ComboBox cmbDataBits;
        private Label lblStopBits;
        private ComboBox cmbStopBits;
        private Label lblParity;
        private ComboBox cmbParity;
        private Panel panel1;
    }
}

//Form1.cs
 

using System.Drawing.Drawing2D;
using System.IO.Ports;
using System.Reflection;
using System.Text;
//协议格式:"A<角度>,D<距离>\n"
//例如STM32 USART串口输出:printf("A%d,D%d\n", angle, distance);
namespace UltrasonicRadar
{
    public partial class Form1 : Form
    {
        private readonly RadarController radarController = new RadarController();
        private readonly SerialPort serialPort = new SerialPort();
        private readonly System.Windows.Forms.Timer scanTimer = new System.Windows.Forms.Timer();
        private readonly object serialLock = new object();
        private StringBuilder serialBuffer = new StringBuilder();
        private bool isUpdating = false;

        public Form1()
        {
            InitializeComponent();
            InitializeRadar();
            InitializeSerialPort();
            InitializeTimer();
        }

        private void InitializeRadar()
        {
            radarDisplay.Paint += new PaintEventHandler(RadarDisplay_Paint);
            radarDisplay.BackColor = Color.FromArgb(15, 20, 30);
            radarDisplay.Dock = DockStyle.Fill;

            // 启用双缓冲
            SetDoubleBuffered(radarDisplay, true);
        }

        private void InitializeSerialPort()
        {
            // 获取可用串口
            string[] ports = SerialPort.GetPortNames();
            cmbPorts.Items.AddRange(ports);

            if (ports.Length > 0)
                cmbPorts.SelectedIndex = 0;

            // 波特率设置
            cmbBaudRate.Items.AddRange(new object[] { "9600", "19200", "38400", "57600", "115200" });
            cmbBaudRate.SelectedItem = "115200";

            // 数据位
            cmbDataBits.Items.AddRange(new object[] { "5", "6", "7", "8" });
            cmbDataBits.SelectedItem = "8";

            // 停止位
            cmbStopBits.Items.AddRange(new object[] { "1", "1.5", "2" });
            cmbStopBits.SelectedItem = "1";

            // 校验位
            cmbParity.Items.AddRange(new object[] { "None", "Odd", "Even", "Mark", "Space" });
            cmbParity.SelectedItem = "None";

            // 设置串口数据接收事件
            serialPort.DataReceived += SerialPort_DataReceived;
        }

        private void InitializeTimer()
        {
            scanTimer.Interval = 20; // 20 FPS
            scanTimer.Tick += scanTimer_Tick;
        }

        // 通过反射设置双缓冲属性
        private void SetDoubleBuffered(Control control, bool value)
        {
            Type controlType = typeof(Control);
            PropertyInfo? doubleBufferedProp = controlType.GetProperty(
                "DoubleBuffered",
                BindingFlags.NonPublic | BindingFlags.Instance);

            if (doubleBufferedProp == null)
            {
                throw new InvalidOperationException("控件类型中未找到 DoubleBuffered 属性");
            }

            doubleBufferedProp.SetValue(control, value, null);
        }

        private void RadarDisplay_Paint(object? sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.SmoothingMode = SmoothingMode.AntiAlias;

            int width = radarDisplay.Width;
            int height = radarDisplay.Height;
            int centerX = width / 2;
            int centerY = height / 2;
            int radius = Math.Min(centerX, centerY) - 20;

            // 绘制雷达背景网格
            DrawRadarGrid(g, centerX, centerY, radius);

            // 绘制扫描线
            DrawScanLine(g, centerX, centerY, radius, radarController.CurrentAngle);

            // 绘制检测到的点
            DrawDetectedPoints(g, centerX, centerY, radius, radarController.DetectedPoints);

            // 绘制雷达中心
            g.FillEllipse(Brushes.LimeGreen, centerX - 5, centerY - 5, 10, 10);

            // 在右下角添加单位说明
            string unitText = "单位:厘米";
            SizeF textSize = g.MeasureString(unitText, new Font("Arial", 9));
            g.DrawString(unitText, new Font("Arial", 9), Brushes.LimeGreen,
                        width - textSize.Width - 10, height - textSize.Height - 5);
        }

        private void DrawRadarGrid(Graphics g, int centerX, int centerY, int radius)
        {
            // 绘制同心圆
            for (int i = 1; i <= 5; i++)
            {
                int r = i * radius / 5;
                g.DrawEllipse(new Pen(Color.FromArgb(30, 144, 255, 30)),
                             centerX - r, centerY - r, r * 2, r * 2);

                // 绘制距离标签
                int distance = i * radarController.RadarRange / 5;
                g.DrawString($"{distance}", new Font("Arial", 8),
                            Brushes.LimeGreen, centerX + r - 20, centerY);
            }

            // 绘制角度线
            for (int angle = 0; angle < 360; angle += 30)
            {
                double radian = angle * Math.PI / 180;
                int x = centerX + (int)(radius * Math.Sin(radian));
                int y = centerY - (int)(radius * Math.Cos(radian));
                g.DrawLine(new Pen(Color.FromArgb(30, 144, 255, 30)),
                          centerX, centerY, x, y);

                // 绘制角度标签
                if (angle % 90 == 0)
                {
                    string text = $"{angle}°";
                    SizeF textSize = g.MeasureString(text, new Font("Arial", 8));
                    int labelX = centerX + (int)((radius + 15) * Math.Sin(radian)) - (int)textSize.Width / 2;
                    int labelY = centerY - (int)((radius + 15) * Math.Cos(radian)) - (int)textSize.Height / 2;
                    g.DrawString(text, new Font("Arial", 8), Brushes.LimeGreen, labelX, labelY);
                }
            }
        }

        private void DrawScanLine(Graphics g, int centerX, int centerY, int radius, float angle)
        {
            // 计算扫描线终点(雷达坐标系:0°=北,顺时针)
            double theta = angle * Math.PI / 180;
            int x = centerX + (int)(radius * Math.Sin(theta));
            int y = centerY - (int)(radius * Math.Cos(theta));

            // 绘制扫描线
            g.DrawLine(new Pen(Color.LimeGreen, 2), centerX, centerY, x, y);

            // 计算扇形的GDI+角度
            float startRadar = angle - 60;  // 扇形起始角度(雷达)
            float startGdi = (startRadar - 90) % 360;
            if (startGdi < 0) startGdi += 360;     // 修正为非负角度

            // 绘制扇形
            GraphicsPath path = new GraphicsPath();
            path.AddArc(centerX - radius, centerY - radius, radius * 2, radius * 2, startGdi, 60);
            path.AddLine(x, y, centerX, centerY);
            path.CloseFigure();
            g.FillPath(new SolidBrush(Color.FromArgb(30, 50, 200, 50)), path);
        }

        private void DrawDetectedPoints(Graphics g, int centerX, int centerY, int radius, IReadOnlyList<RadarPoint> points)
        {
            foreach (RadarPoint point in points)
            {
                double radian = point.Angle * Math.PI / 180;
                double scaledDistance = (point.Distance / (double)radarController.RadarRange) * radius;
                int x = centerX + (int)(scaledDistance * Math.Sin(radian));
                int y = centerY - (int)(scaledDistance * Math.Cos(radian));

                // 新点显示为黄色,旧点显示为红色
                Color pointColor = point.IsNew ? Color.Yellow : Color.Red;
                g.FillEllipse(new SolidBrush(pointColor), x - 3, y - 3, 6, 6);

                // 显示距离
                g.DrawString($"{point.Distance}", new Font("Arial", 8),
                            Brushes.Orange, x + 5, y - 10);
            }
        }

        private void scanTimer_Tick(object? sender, EventArgs e)
        {
            radarController.UpdateAngle();
            radarController.UpdatePointLifecycle();

            // 批量更新:避免短时间内多次Invalidate
            if (!isUpdating)
            {
                isUpdating = true;
                BeginInvoke(new Action(() => {
                    radarDisplay.Invalidate();
                    isUpdating = false;
                }));
            }
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            if (serialPort.IsOpen)
            {
                serialPort.Close();
                btnConnect.Text = "连接串口";
                lblStatus.Text = "已断开";
                lblStatus.ForeColor = Color.Orange;
            }
            else
            {
                try
                {
                    serialPort.PortName = cmbPorts.SelectedItem!.ToString();
                    serialPort.BaudRate = int.Parse(cmbBaudRate.SelectedItem!.ToString()!);
                    serialPort.DataBits = int.Parse(cmbDataBits.SelectedItem!.ToString()!);

                    // 处理停止位(需要将"1.5"转换为StopBits.OnePointFive)
                    string stopBitsStr = cmbStopBits.SelectedItem!.ToString()!;
                    if (stopBitsStr == "1.5")
                        serialPort.StopBits = StopBits.OnePointFive;
                    else
                        serialPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), stopBitsStr);

                    serialPort.Parity = (Parity)cmbParity.SelectedIndex;
                    serialPort.Open();

                    btnConnect.Text = "断开连接";
                    lblStatus.Text = "已连接: " + serialPort.PortName;
                    lblStatus.ForeColor = Color.LimeGreen;
                }
                catch (Exception ex)
                {
                    MessageBox.Show("连接失败: " + ex.Message, "错误",
                                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        private void btnScanToggle_Click(object sender, EventArgs e)
        {
            if (scanTimer.Enabled)
            {
                scanTimer.Stop();
                btnScanToggle.Text = "启动扫描";
            }
            else
            {
                scanTimer.Start();
                btnScanToggle.Text = "停止扫描";
            }
        }

        private void btnClear_Click(object sender, EventArgs e)
        {
            radarController.ClearPoints();
            radarDisplay.Invalidate();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (serialPort.IsOpen)
                serialPort.Close();
            scanTimer.Stop();
        }

        // 串口数据接收处理
        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                // 加锁保护数据读取和缓冲区操作
                lock (serialLock)
                {
                    string data = serialPort.ReadExisting();
                    serialBuffer.Append(data);
                }

                // 在UI线程中处理数据
                this.Invoke(new Action(ProcessSerialData));
            }
            catch (Exception ex)
            {
                // 记录日志
                Console.WriteLine($"串口数据接收错误: {ex.Message}");
                Console.WriteLine(ex.StackTrace);

                this.Invoke(new Action(() => {
                    lblStatus.Text = "数据接收错误,请查看日志";
                    lblStatus.ForeColor = Color.Red;
                }));
            }
        }

        // 处理串口数据
        private void ProcessSerialData()
        {
            string data;
            lock (serialLock)
            {
                data = serialBuffer.ToString();
                serialBuffer.Clear();
            }

            // 假设数据格式: "A<角度>,D<距离>\n"
            string[] lines = data.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string line in lines)
            {
                if (string.IsNullOrWhiteSpace(line)) continue;

                try
                {
                    // 解析角度和距离
                    string[] parts = line.Split(',');
                    if (parts.Length >= 2)
                    {
                        float angle = 0;
                        int distance = 0;

                        if (parts[0].StartsWith("A") && float.TryParse(parts[0].Substring(1), out angle) &&
                            parts[1].StartsWith("D") && int.TryParse(parts[1].Substring(1), out distance))
                        {
                            // 确保角度在0-360范围内
                            angle = angle % 360;
                            // 确保距离在有效范围内
                            distance = Math.Max(0, Math.Min(distance, radarController.RadarRange));

                            // 添加检测点
                            this.Invoke(new Action(() => {
                                radarController.ProcessDetectedPoint(angle, distance);
                                radarDisplay.Invalidate();
                            }));
                        }
                    }
                }
                catch (Exception ex)
                {
                    // 记录格式错误
                    Console.WriteLine($"数据格式错误: {line}");
                }
            }
        }
    }

    // 雷达控制器 - 负责雷达核心逻辑
    public class RadarController
    {
        private float currentAngle = 0;
        private readonly int radarRange = 500;
        private readonly List<RadarPoint> detectedPoints = new List<RadarPoint>();

        // 角度索引(键为角度区间,值为点列表)
        private readonly Dictionary<int, List<RadarPoint>> angleIndex = new Dictionary<int, List<RadarPoint>>();

        public float CurrentAngle => currentAngle;
        public int RadarRange => radarRange;
        public IReadOnlyList<RadarPoint> DetectedPoints => detectedPoints;

        public void UpdateAngle()
        {
            currentAngle = (currentAngle + 2) % 360;
        }

        public void ProcessDetectedPoint(float angle, int distance)
        {
            // 计算角度区间键(每5度一个区间)
            int angleKey = (int)(angle / 5) * 5;

            // 检查相邻区间(±5度)
            bool pointExists = false;
            for (int offset = -5; offset <= 5; offset += 5)
            {
                int keyToCheck = angleKey + offset;
                if (angleIndex.TryGetValue(keyToCheck, out List<RadarPoint>? pointsInRange))
                {
                    foreach (var point in pointsInRange)
                    {
                        if (Math.Abs(point.Angle - angle) < 5)
                        {
                            // 更新已有点的信息
                            point.Distance = distance;
                            point.DetectedCount++;
                            point.MissedCount = 0;
                            point.IsNew = false;
                            pointExists = true;
                            return;
                        }
                    }
                }
            }

            // 如果是新点,则添加
            if (!pointExists)
            {
                RadarPoint newPoint = new RadarPoint(angle, distance);
                detectedPoints.Add(newPoint);

                // 更新索引
                if (!angleIndex.TryGetValue(angleKey, out List<RadarPoint>? points))
                {
                    points = new List<RadarPoint>();
                    angleIndex[angleKey] = points;
                }
                points.Add(newPoint);
            }
        }

        public void UpdatePointLifecycle()
        {
            // 处理所有点:增加未检测计数,移除长时间未被检测的点
            for (int i = detectedPoints.Count - 1; i >= 0; i--)
            {
                detectedPoints[i].MissedCount++;

                if (detectedPoints[i].MissedCount > 5)
                {
                    // 从索引中移除
                    int angleKey = (int)(detectedPoints[i].Angle / 5) * 5;
                    if (angleIndex.TryGetValue(angleKey, out List<RadarPoint>? points))
                    {
                        points.Remove(detectedPoints[i]);
                        if (points.Count == 0)
                            angleIndex.Remove(angleKey);
                    }

                    detectedPoints.RemoveAt(i);
                }
            }
        }

        public void ClearPoints()
        {
            detectedPoints.Clear();
            angleIndex.Clear();
        }
    }

    // 雷达检测点类
    public class RadarPoint
    {
        public float Angle { get; set; }
        public int Distance { get; set; }
        public int DetectedCount { get; set; }      // 被检测到的次数
        public int MissedCount { get; set; }       // 连续未被检测到的次数
        public bool IsNew { get; set; } = true;    // 是否为新点

        public RadarPoint(float angle, int distance)
        {
            Angle = angle;
            Distance = distance;
            DetectedCount = 1;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值