news 2026/9/3 18:44:47

用VB.NET从零开发自定义串口调试助手:原理、实现与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用VB.NET从零开发自定义串口调试助手:原理、实现与避坑指南

简介:VB.NET编写的串口助手源码包,适合正在学习VB.NET串口通信或需要自制串口调试工具的开发者。相比网上常见的AXcom控件方案,作者选用SerialPort类实现,并处理了委托与线程的关系,代码风格简洁,亲测可用,兼具参考性与实用性。资源共37个文件,包括6个vb源码文件、5个config配置文件、4个exe可执行程序、2个txt说明文档,以及pdb调试符号、resx资源文件、sln工程文件等,压缩包整体仅181KB,结构清晰,便于直接打开和对比学习。已有519人学习。通过这份资源,读者可以完整查看窗体设计、SerialPort配置与数据收发逻辑,理解跨线程更新UI的委托写法,还可以直接运行exe体验功能。包内还保留了App.config、My Project等工程配置,适合用Visual Studio打开后边看边改,快速搭建属于自己的串口助手,也可作为课程设计或工具开发的起步模板。 从大学第一次调单片机开始,我就对串口助手这类工具有一种又爱又恨的感觉。爱的是它们能让我直接看到设备吐出来的数据,恨的是市面上那些串口调试助手总是差那么一点意思——要么界面老气横秋,要么收发数据时偶发卡顿,想加个自动保存日志和CRC校验功能还得付费。

后来用VB.NET自己动手写了一个串口助手,从串口参数配置、十六进制收发,到数据缓存、自动发送、日志记录,全部都按自己的使用习惯来。这个项目做下来,不仅解决了我调试设备的实际痛点,还把我对.NET中SerialPort控件、多线程委托、编码处理的理解往上拉了一大截。这篇文章我就把整个开发过程和踩过的坑完整记录下来,想自己写一个串口助手的朋友可以直接照着做,也可以在这个基础上继续扩展成更适合自己的调试工具。

1. 为什么放着现成的串口调试工具一大堆,我还是要自己写一个

1.1 现成工具的问题:功能过剩或者功能缺失

市面上能下载到的串口调试助手非常多,从SSCOM、XCOM,到各种"友善版""常兴版",基本功能都差不多——选择串口号、配置波特率、打开串口、发送接收数据、十六进制显示。

但这些工具用久了你就会发现几个问题:

  • 免费版往往夹带弹窗广告,装到工控电脑上非常碍眼,万一误点还会装上一堆全家桶。
  • 有些工具功能做得很全,但界面密密麻麻全是按钮和输入框,找自己要用的那个功能还得上下摸索半天。
  • 更关键的是,现成工具很难针对你自己的项目做定制。比如我经常测试带CRC16校验的Modbus协议,每次都要先在外部计算器里算出校验码再拼到发送帧里,步骤繁琐还容易出错。我就想把这个校验功能直接集成到串口助手的发送区里,这在大多数现成工具里根本做不到。

还有一点很现实:做嵌入式或单片机开发时,采样数据往往是高频、连续、带时间戳的,现成工具要么没有时间戳,要么导出格式乱七八糟。这些定制需求,才是推动我自己动手的核心动力。

1.2 自己写的好处:代码可控、定制自由、顺便练技术

自己开发串口助手,最大的好处就是"工具长在我手上"。

一开始我只需要一个能收发串口数据的小工具,但随着项目深入,我逐渐在它上面加了这些功能:

  • 自定义快捷发送区,把常用的AT指令、Modbus帧保存成预设按钮,点击即发送。
  • 接收数据显示时间戳,方便定位数据流中某条协议报文出现的时刻。
  • 自动保存完整日志,崩溃或关掉之后也不怕丢失现场数据。
  • 集成CRC校验计算,发Modbus帧时自动把校验字节追加到报文尾部。
  • 发送间隔可配置,用来测试设备在大流量冲击下是否会丢帧。

这些需求如果靠现成工具,可能需要同时开好几个软件配合。但自己写一个就是一个窗体的事。而且用VB.NET做WinForms开发,拖控件、写事件处理器,说实话门槛真不高,很适合作为.NET桌面开发入门到进阶的实战项目。

2. 串口通信到底在做什么:速率、数据位、校验位背后的原理

好多新手在界面上把波特率、数据位、校验位、停止位一顿乱填,然后发现设备不上数,就开始怀疑代码有问题。其实串口参数的本质是收发双方的通信协议,两边设置不一致,物理层就直接对不上了。

2.1 串口参数的逻辑:9600、8、N、1 到底代表什么

我们在配置串口时经常看到"9600, 8, N, 1"这串数字,它分别代表波特率、数据位、校验位、停止位。

  • 波特率:每秒传输的比特数。9600代表每秒钟传输9600个bit,实际去掉起始位、停止位、校验位后,数据吞吐量大约是960字节/秒。如果设备端是115200,你用9600去打开,收到的基本就是乱码。
  • 数据位:一个字节数据用几位来表示,常见的是8位,也有一些老旧设备用7位。8位数据位时可以完整表示0~255的字节值。
  • 校验位:用于检测传输过程中是否发生比特错误,常见的包括None(无校验)、Odd(奇校验)、Even(偶校验)。现在大多数场景直接用None,因为校验位只能查错不能纠错,如果数据完整性要求高,一般会在协议层用CRC校验而不是串口校验位。
  • 停止位:每个字节传输结束后的停止信号长度,常见的是1位或2位。

我用一个生活化类比来理解:串口通信就像两个人隔着一条马路用对讲机说话。波特率决定了两个人说话的语速必须一致;数据位是每句话要说几个字;校验位就像在每句话后面加一个"重复一遍关键数字"的机制;停止位则是"我说完了,换你"的提示音。任何一项设置不一致,对话就进行不下去。

在VB.NET中,这些参数在SerialPort控件上都是直接暴露的:

With SerialPort1 .PortName = "COM3" .BaudRate = 115200 .DataBits = 8 .Parity = Parity.None .StopBits = StopBits.One .Handshake = Handshake.None .ReadTimeout = 500 .WriteTimeout = 500 End With

2.2 数据的两种形态:ASCII字符与十六进制字节

串口传输的数据本质上是一串字节(Byte),每个字节是0~255之间的数值。但在调试界面里,这些字节有不同展现方式:

  • ASCII显示:把每个字节当成一个字符显示。比如设备返回0x31、0x32、0x33,ASCII模式下显示成"123"。
  • 十六进制显示:把每个字节显示成两位十六进制数,比如0x31 0x32 0x33显示成"31 32 33"。

对于只想阅读设备状态文本的场景,比如GPS模块输出的NMEA语句,直接用ASCII模式就很直观。但对于Modbus、自定义二进制协议这种面向字节流的通信,必须用十六进制模式才能看清每一帧的原始数据。

我在界面里做了一个RadioButton切换接收和发送的显示格式。发送时以字节数组为基准,如果是十六进制模式,就把用户输入的"AA BB CC"字符串解析成对应字节;如果是ASCII模式,就直接把字符串按编码转成字节。接收时则反过来。理解了"一切皆字节"这条主线,后面代码就不容易写乱。

3. 界面设计:一个顺手好用的串口助手,布局才是灵魂

先别急着写串口代码,工具类软件最重要的是"好用"两个字。我第一版界面随手乱摆控件,用起来总觉得别扭。后来重构了一版,才明白界面布局直接影响调试效率。

3.1 核心区域划分:连接区、接收区、发送区

我把窗体分成三个核心区域,分界线很清楚:

  • 顶部连接区:串口号下拉框、波特率下拉框、打开/关闭串口按钮、刷新串口按钮。
  • 中间接收区:带滚动条的TextBox(或者RichTextBox),显示所有接收到的数据,旁边放清空按钮和显示格式选项。
  • 底部发送区:发送内容输入框、发送按钮、定时发送勾选框和间隔设置,以及快捷发送按钮区。

顶部连接区要紧凑,不要占用太多垂直空间,因为接收区才是调试时主要盯着的区域。接收区我用TextBox,设置了Multiline、ScrollBars为Vertical、ReadOnly为True,同时把Font设置为等宽字体(比如Consolas),这样十六进制数据显示出来每一列都对齐,肉眼对比数据时舒服很多。

发送区和接收区之间用SplitContainer分隔,方便用户根据当前数据量随时调整上下区域的高度。这个细节我强烈建议加上,因为当协议数据帧长度差别很大时,固定比例布局会很别扭。

3.2 用ComboBox做动态串口枚举的几个细节

串口列表不能写死在界面上,否则插拔USB转串口设备后下拉框就是哑的。我用SerialPort.GetPortNames()获取当前系统里的串口列表,在窗体加载时和点击"刷新"时调用。

这里有个小坑:GetPortNames()返回的是"COM1"、"COM3"这样的字符串格式,但顺序是随机的。我按数字部分排了个序,不然COM10会排在COM2前面,看起来乱糟糟的。

Private Sub RefreshPortList() Dim ports As String() = SerialPort.GetPortNames() Array.Sort(ports, Function(a, b) Dim na As Integer = Integer.Parse(a.Replace("COM", "")) Dim nb As Integer = Integer.Parse(b.Replace("COM", "")) Return na.CompareTo(nb) End Function) cboPort.Items.Clear() cboPort.Items.AddRange(ports) If ports.Length > 0 Then cboPort.SelectedIndex = 0 End If lblComState.Text = "串口未打开" End Sub

设备插上但列表里没出现新串口,绝大多数情况是USB转串口驱动没装好。在设备管理器里看不到COM口时,优先处理驱动而不是改代码。

4. 核心实现:SerialPort控件的打开、接收与发送

界面准备好了,下面就是整个项目的核心:串口通信逻辑。

4.1 串口初始化与开关控制

我直接在窗体上拖了一个SerialPort控件,省得手动实例化传参数。打开串口时先检查是否已经打开,避免重复Open抛异常:

Private Sub btnOpen_Click(sender As Object, e As EventArgs) Handles btnOpen.Click If SerialPort1.IsOpen Then SerialPort1.Close() btnOpen.Text = "打开串口" btnSend.Enabled = False lblComState.Text = "串口已关闭" Return End If Try SerialPort1.PortName = cboPort.Text SerialPort1.BaudRate = CInt(cboBaud.Text) SerialPort1.Parity = CType(cboParity.SelectedIndex, Parity) SerialPort1.StopBits = CType(cboStopBits.SelectedIndex, StopBits) SerialPort1.DataBits = CInt(cboDataBits.Text) SerialPort1.Open() SerialPort1.DiscardInBuffer() SerialPort1.DiscardOutBuffer() btnOpen.Text = "关闭串口" btnSend.Enabled = True lblComState.Text = "串口已打开: " & cboPort.Text & " | " & cboBaud.Text Catch ex As Exception MessageBox.Show("打开串口失败:" & ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Warning) End Try End Sub

这里有个易忽略的小地方:ComboBox校验位和停止位的下拉项要小心和枚举对应。我用SelectedIndex直接转换枚举,所以下拉项的顺序必须与Parity枚举、StopBits枚举一致,否则打开串口时参数会是错的。

4.2 DataReceived事件与跨线程更新UI的Invoke问题

这是串口开发里最核心、最容易翻车的部分。SerialPort的DataReceived事件是在后台辅助线程触发的,不是UI线程。如果你在事件里直接写:

txtReceive.Text &= "some data"

程序不会报错,但UI可能闪烁、卡顿甚至崩溃,因为TextBox并不允许后台线程直接操作。

正确做法是用Invoke或BeginInvoke把UI更新操作封装到UI线程上执行。我习惯先判断IsHandleCreated,防止窗体关闭时Invoke引发ObjectDisposedException:

Private Sub SerialPort1_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived Dim bytesAvailable As Integer = SerialPort1.BytesToRead If bytesAvailable <= 0 Then Return Dim buffer(serialPort1.BytesToRead - 1) As Byte SerialPort1.Read(buffer, 0, buffer.Length) If Me.IsHandleCreated Then Me.BeginInvoke(Sub() ProcessReceivedBytes(buffer) End Sub) End If End Sub

读取串口数据用Read(byte[], offset, count)方法,一次把当前缓冲区的所有字节读出来,而不是用ReadLine或者ReadExisting。这样做的原因后面第五节会细说,先把代码骨架搭对。

4.3 发送数据的编码处理与十六进制发送

发送逻辑相对简单,但编码处理要想清楚。我的发送函数分两种情况:

  • 如果用户选择ASCII发送:直接把发送框里的字符串按Encoding.GetEncoding("GBK")转成字节数组。为什么用GBK而不用UTF-8?因为很多设备(尤其是国产工业设备和GPS模块)默认用GBK或ASCII,UTF-8编码的中文字符发过去会变成乱码。如果是纯英文数字,用ASCII就够了。
  • 如果用户选择Hex发送:把输入框里的"AA BB CC"字符串解析成字节数组。注意要去掉空格,并且每两个字符一组,非法字符直接提示用户。
Private Function ParseHexString(hexStr As String) As Byte() Dim clean = hexStr.Replace(" ", "").Replace("-", "") If clean.Length Mod 2 <> 0 Then Throw New Exception("十六进制字符串长度必须为偶数") End If Dim result(CInt(clean.Length / 2) - 1) As Byte For i As Integer = 0 To result.Length - 1 result(i) = Convert.ToByte(clean.Substring(i * 2, 2), 16) Next Return result End Function Private Sub SendBytes(data As Byte()) If Not SerialPort1.IsOpen Then MessageBox.Show("串口未打开") Return End If SerialPort1.Write(data, 0, data.Length) End Sub

发Modbus帧时我习惯在快捷发送按钮里直接写死整帧数据,或者把CRC计算也做成一个公共函数,点发送前自动计算追加,调试效率高很多。

5. 串口调试最大的坑:丢数据、粘包与乱码

开发过程中最让我头疼的,不是串口打不开,而是数据接收不稳定。在这里把排查思路完整记录下来。

5.1 为什么DataReceived里不能直接做ReadLine

我第一次写接收代码时,图省事直接在DataReceived里调用ReadLine(),结果发现数据时有时无,而且经常报"操作超时"异常。

原因是ReadLine()会一直读到换行符为止。如果设备发送的数据不是以换行符结尾,或者一帧数据还没发完就触发了DataReceived,ReadLine就会占用线程等待超时,期间新到的数据又会触发新的DataReceived,最终线程越积越多,接收就乱了。

此外,ReadLine()默认认为换行符是LF(\n),遇到CRLF结尾的帧还能读出来,但如果设备只用CR,那就是一场灾难。

我把DataReceived里的逻辑改成"先读进字节数组,再自己处理帧边界",问题就解决了。SerialPort.ReadLine和ReadExisting只适合数据量小、格式固定的简单文本协议,规规矩矩按字节流处理才是通用方案。

5.2 缓冲区读取与字节缓存:从源头解决粘包

串口数据是流式的,没有天然的消息边界。设备可能一次发来3个字节,也可能一次发来300个字节,通信驱动会在合适的时间触发DataReceived事件,但这和协议层的一帧没有必然联系。如果设备一帧是AAB1C2F4,但事件触发时你只读到了AA B1 C2,下一轮才读到F4,直接显示就会把一帧拆成两半,这就是最常见的"粘包/半包"问题。

我的处理方案是引入一个全局的接收缓存集合,把每次从串口读到的字节先追加到缓存里,然后尝试从缓存中解析出完整的帧并处理,处理完的字节就从缓存中移除。针对自己定义的协议(比如帧头+长度+数据+校验),可以写一个专门的FindFrame函数;如果只是通用调试工具,就把缓存里的字节全部追加到接收显示区,让用户自己判断帧边界。

在通用工具里,我主要保证一点:接收区显示的内容和实际收到的字节完全一致,不丢数据。具体做法就是把DataReceived里读到的buffer原封不动追加到TextBox中:

Dim text As String = Encoding.ASCII.GetString(buffer) AppendReceiveText(text)

AppendReceiveText里用TextBox.AppendText而不是直接textBox.Text &= text,因为AppendText会自动滚动到底部,频繁插入时效率也高得多。

5.3 乱码问题:编码不匹配的排查思路

乱码几乎是每个做串口调试的人都会碰到的问题。乱码的原因不外乎这两个:

  • 发送端的字符编码和接收端显示的编码不一致。设备输出UTF-8字节流,但你的助手按ASCII解析,那中文当然就是一堆锟斤拷。
  • 波特率错误导致收到大量0x7F或0x00之类的异常字节。

排查思路我按这个顺序走:

  1. 先用示波器或者逻辑分析仪确认波特率是否匹配。没有示波器的话,可以用串口助手收发纯数字或英文测速,如果能正确显示"12345",说明波特率大概率没问题。
  2. 确认设备文档里指定的编码格式。常见的有ASCII、GB2312/GBK、UTF-8。VB.NET里编码获取方式:Encoding.GetEncoding("GBK")、Encoding.UTF8。
  3. 如果设备手册说支持UTF-8,但显示还是乱,检查是不是发送时VB.NET把字符串转成了UTF-16,你得显式按字节数组发送,不能直接把String塞给Write方法。

这里再补充一个工控场景常见问题:USB转串口芯片兼容性。同一块设备,用FT232芯片的线没问题,换成CH340就可能出现丢字节或乱码。这不是代码问题,换一条线或者重装驱动就能解决。我被这个坑害过整整一个下午,排查了代码、协议、编码之后才发现是转接线的问题。

6. 从"能用"到"好用":自动发送、日志记录与其他扩展

基础版串口助手能用了之后,我开始往里加真正提升效率的功能。

6.1 定时自动发送的三种实现方式

定时发送就是每隔固定时间自动发送一次数据,用来测试设备在长时间压力下的稳定性。实现思路有三种:

  • Timer控件:最简单,在窗体上拖一个Timer,设置Interval,Tick事件里调用发送函数。适合发送间隔在几十毫秒以上的场景,精度足够。
  • 线程+循环+Slepp:在后台线程里做循环,间隔时间用Thread.Sleep控制。发送间隔可以做到几毫秒,但注意帧间隔不准且UI无法直接通知线程停止,要用标志位控制。
  • Stopwatch忙等:最高精度,但不推荐在WinForms里用,CPU占用高,而且和定时器线程抢占调度会引入不确定性。

我用的方案是第一和第二种结合。普通场景用Timer.SetInterval实现,做压力测试时切到后台线程模式,用ManualResetEvent来通知开始和停止:

Private _autoSendThread As Thread Private _autoSendCancel As New ManualResetEvent(False) Private Sub StartAutoSend() _autoSendCancel.Reset() _autoSendThread = New Thread(AddressOf AutoSendLoop) _autoSendThread.IsBackground = True _autoSendThread.Start() End Sub Private Sub AutoSendLoop() Dim interval = CInt(txtAutoInterval.Text) Dim frame = Encoding.ASCII.GetBytes(txtSend.Text & vbCrLf) While Not _autoSendCancel.WaitOne(interval) If SerialPort1.IsOpen Then SerialPort1.Write(frame, 0, frame.Length) End If End While End Sub Private Sub StopAutoSend() _autoSendCancel.Set() End Sub

用ManualResetEvent.WaitOne(interval)代替Thread.Sleep的好处是停止线程时不用打断睡眠,响应非常快。

6.2 日志保存与时间戳的经验

日志保存是个"加了就回不去"的功能。我用一个独立函数处理接收数据,每次追加到界面显示的同时写入一个StringBuilder,当接收数据量达到一定阈值(比如每500行)就刷进日志文件。

时间戳设计要注意一点:不要每收到一个字节就打一次时间戳,而应该以"行的维度"打时间戳。比如收到一帧完整数据后,在该帧开头加一行:

[2025-01-15 14:22:03.125] AA BB CC DD EE FF 12 34

如果一帧数据非常长,可以在数据里插入换行,但不加时间戳。日志文件用UTF-8带BOM保存,这样用记事本打开中文不会乱。

清理日志文件也要考虑:我设置日志文件超过10MB就自动切换新文件,旧文件按时间命名保留,避免一个文件无限膨胀到几百MB导致打开卡死。

6.3 一个更有野心的方向:串口波形显示

这个功能我放在最后分享,是因为它最能体现"自己写工具的乐趣"。很多现成串口助手没有示波器功能,但如果你调试的是传感器数据,比如温度、电压、加速度,一条条看十六进制数据效率太低。此时如果能在界面上直接画出曲线,设备状态一目了然。

实现思路不复杂:把接收到的数据帧解析成数值,塞进一个List(Of Double)或环形缓冲区,用GDI+或者简单的绘图控件(比如Chart控件)把曲线画出来。

我用的方案是.NET自带的Chart控件,把X轴设置成时间或采样序号,Y轴按传感器量程设置。数据解析这一步是自定义的,因为不同设备协议不同:

' 假设设备一帧是 "T:25.6;C:30.1",解析温度和电流并追加到Chart Dim parts = line.Split(";"c) For Each part In parts If part.StartsWith("T:") Then Dim temp As Double = Double.Parse(part.Substring(2)) ChartTemp.Series("温度").Points.AddY(temp) ElseIf part.StartsWith("C:") Then Dim cur As Double = Double.Parse(part.Substring(2)) ChartCur.Series("电流").Points.AddY(cur) End If Next

当然,这个功能要和具体的设备协议绑定,不像通用收发那么"通用"。但正因为是自研工具,我才可以随时针对当前测试项目改解析逻辑。这也正是我一开始选择"自己写串口助手"的原因。

最后再分享一个个人经验:VB.NET做串口工具项目,不需要追求一步到位。先把收发功能做稳,再逐步加入自己真正需要的定制功能,整个过程远比下载一个现成工具更有价值。工具越好用,开发越顺手,这个正循环只有自己写过的人才能体会到。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 15:48:24

北京城区道路矢量数据Shapefile包:从加载到避坑实战

简介&#xff1a;北京城区道路矢量数据包提供主城区精细化道路网络Shapefile数据&#xff0c;适合GIS开发、规划与地图制图人员直接使用。数据覆盖主干道、次干道、支路及部分街巷&#xff0c;坐标系为WGS84或CGCS2000&#xff08;以.prj为准&#xff09;&#xff0c;属性含道路…

作者头像 李华
网站建设 2026/9/3 18:43:03

理想汽车数据岗笔试备考:SQL、AB实验与车联网数据实战解析

提前把话放这儿&#xff1a;理想汽车数据岗笔试&#xff0c;不是靠刷几十道LeetCode就能过的。它更像一场“业务 工程 算法”的混合考试&#xff0c;既要你写得出手写SQL&#xff0c;又要你讲明白AB实验的分组逻辑&#xff0c;还得能读懂自动驾驶场景里的传感器数据结构。如果…

作者头像 李华
网站建设 2026/9/3 12:57:18

aubo i5与D435i视觉抓取实战:手眼标定到点云抓取全流程

简介&#xff1a;这是一份围绕aubo i5机械臂与Intel Realsense D435i深度相机联动的抓取实践源码包&#xff0c;面向机器人开发者、智能制造及机器视觉方向的技术人员&#xff0c;可用于快速复现物体识别抓取完整流程。资源共3个文件&#xff0c;包含inscode环境配置、html说明…

作者头像 李华
网站建设 2026/9/3 22:22:18

从论文复现到分布式训练:备战ECCV 2026实验室岗位

ECCV 是欧洲计算机视觉会议&#xff08;European Conference on Computer Vision&#xff09;的缩写&#xff0c;也是计算机视觉领域公认的顶级学术会议之一。2026 年这届还没开幕&#xff0c;围绕它的讨论已经从论文投稿延伸到人才招聘&#xff1a;上海AI实验室携 100 核心科研…

作者头像 李华
网站建设 2026/9/3 22:20:54

运放电路失真排查实战:常见类型、波形特征与工程解决方案

调试运放电路时&#xff0c;最让人头疼的问题之一就是“失真”。输入明明是干净的正弦波&#xff0c;输出却出现削顶、交越、振铃甚至完全不规则的波形。本文从实际测试角度出发&#xff0c;梳理运放电路常见失真类型、成因、波形特征&#xff0c;并结合几个典型电路给出完整的…

作者头像 李华
网站建设 2026/9/3 3:19:33

基于STM32的嵌入式健康监测终端:体温、心率与跌倒检测实战

简介&#xff1a;面向嵌入式开发与健康监测场景的STM32完整工程&#xff0c;基于STM32微控制器实时采集体温、心率&#xff0c;并利用加速度计/陀螺仪&#xff08;MPU6050&#xff09;实现老人跌倒检测&#xff0c;数据可通过串口、蓝牙或Wi-Fi上报&#xff0c;适用于居家看护、…

作者头像 李华