☰
STM32 C++实战:扫掠式超声波测距云台完整实现
2026/10/2 1:06:44 网站建设 项目流程

第5篇更新完,后台就看到好几条留言,意思都差不多:"类也学了、模板也看了、串口也打通了,但你让我自己上手写个东西,我还是觉得心里没底。"这话说到点子上了。STM32嵌入式C++学到一定程度,瓶颈往往不再是语法,而是"怎么把学过的外设知识组织成一个能跑起来的整体"。所以这篇我不打算再讲新概念,直接带大家做一件实事——一个扫掠式超声波测距云台,用C++把STM32的GPIO、定时器输入捕获、步进电机驱动、串口日志全部串起来。

项目最终效果是这样:步进电机带动HC-SR04超声波探头在0°到180°之间来回扫,每到一个固定角度就停下来测一次前方距离,把"角度:距离"的数据通过串口实时发到电脑上;一旦某个方向测到距离小于安全阈值,蜂鸣器立刻报警。主控用STM32F103C8T6,开发环境是VSCode加CMake,代码纯C++,工程结构按"驱动层+业务层"分开。整个项目不算大,但足够把前面几篇讲的东西全部盘活。这篇填的坑,就是标题里那句"咱们还差活滴"。

1. 把零散外设串成一个项目:选型与方案设计

1.1 为什么这个项目适合当"第一件完整的活"

很多人的嵌入式学习路线是:点灯、按键、串口、定时器、中断,一路学完,最后面对毕设题目或者比赛需求时照样发懵。原因很简单,前面那些都是"单一外设demo",而真实产品是多个外设协同工作的。单纯会配置一个定时器,和知道"定时器怎么跟传感器配合完成一次测量",完全是两码事。

我选的这个超声波云台,传感器、执行器、通信、交互全都有——超声波负责采集、步进电机负责动作、串口负责上报、按键和蜂鸣器负责人机交互。但它们的协作方式并不复杂,业务链路短,非常适合作为第一个整合类项目。项目跑通之后,再回看嵌入式学习路线,你会很清楚下一步该补什么:觉得角度控制不够精确,去学闭环PID;觉得数据可视化太弱,去学上位机或者Qt;觉得传输距离不够,再去看Modbus、CAN这类工业总线。这就是典型的倒推式学习,比照着网上的大纲一章一章刷视频高效得多。

1.2 硬件清单与引脚分配

我实际搭建测试台使用的配置如下:

模块型号/规格引脚连接备注
主控STM32F103C8T6外置8MHz晶振72MHz主频,64KB Flash
超声波HC-SR04Trig = PB5,Echo = PA0Echo接TIM2_CH1做输入捕获
步进电机28BYJ-48 + ULN2003驱动板IN1~IN4 = PB0~PB3五线四相,12V或5V版本都可以
有源蜂鸣器低电平触发/高电平触发均可PB12有源模块给电平就响
按键轻触开关,接10k上拉PB13下降沿触发EXTI外部中断
USB转TTLCH340USART1: TX = PA9,RX = PA10115200-8-N-1

引脚分配这里有个常见的坑:STM32F103C8T6的PB3和PB4默认是JTAG调试引脚,直接当普通IO用会失灵。很多新手一上来就把步进电机接在PB3/PB4上,然后发现电机纹丝不动,还以为是驱动代码写错了。我的做法是全部避开JTAG/SWD相关引脚,把四根电机控制线放在PB0~PB3,这样默认状态就能直接跑。

还有一个硬件细节容易忽略:HC-SR04的Echo引脚输出的是5V电平。F103的GPIO明确标注5V容忍,可以直接直连,这也是F103至今在DIY圈子里生命力顽强的原因之一。但如果你换成STM32G0、H7这类不标注5V容忍的新系列,就必须在Echo上做电阻分压,否则有烧引脚的风险。

1.3 模块划分与工程目录

不管项目大小,我建议都先把目录划好,后面加功能才不会越来越乱。这次我用的目录结构是这样的:

ultrasonic_radar/ ├── CMakeLists.txt ├── src/ │ ├── main.cpp // 初始化 + 主循环 │ ├── app.cpp / app.hpp // 扫掠状态机、业务逻辑 │ ├── gpio.cpp / gpio.hpp // 引脚封装 │ ├── timer_ic.cpp / .hpp // 输入捕获定时器封装 │ ├── ultrasonic.cpp / .hpp // 超声波传感器类 │ ├── stepper.cpp / .hpp // 步进电机类 │ └── logger.cpp / .hpp // 串口日志类 └── stm32f103c8t6_linker.ld // 链接脚本

main.cpp里只放初始化和主循环;硬件驱动按外设拆分;业务逻辑单独放app模块。这是嵌入式C++和传统单片机C代码之间最大的区别之一——从一开始就考虑"这个模块以后能不能复用"。比如GpioPin这个类,以后做任何其他项目都能直接拷过去用,因为它不包含任何业务知识。

2. 用C++给STM32外设"立规矩":核心类的封装思路

2.1 封装究竟封到什么程度合适

嵌入式C++最容易犯的毛病是矫枉过正。有些人一上来就搞完整的继承体系,抽象接口、虚函数、工厂模式全上,结果代码量比原来翻了三倍,编译出来Flash不够用,调试还更费劲。我的经验是:在中小型项目里,把引脚、定时器这类最小硬件单元封装成类就够用了,业务模块之间用普通函数或者轻量级的类组合,不要强行引入复杂的多态层次。

底层驱动库的选择上,我更推荐LL库而不是HAL库。LL库跟寄存器几乎一一对应,封装薄,生成的代码本质就是寄存器操作的另一种写法,性能上基本无损。而且裸机编译的情况下,用LL库调试起来很直观——你说操作某个寄存器,库函数里就能直接看到对应寄存器位。HAL库虽然上手友好,但层层封装导致参数多、路径长,出了问题反而不好定位。

2.2 GpioPin类:一个引脚一个对象

GPIO是STM32最基础的外设,我把它做成一个小而完整的类:

class GpioPin { public: GpioPin(GPIO_TypeDef* port, uint16_t pin) : port_(port), pin_(pin) {} void init(GPIO_InitTypeDef* config) { config->Pin = pin_; HAL_GPIO_Init(port_, config); } void setHigh() { HAL_GPIO_WritePin(port_, pin_, GPIO_PIN_SET); } void setLow() { HAL_GPIO_WritePin(port_, pin_, GPIO_PIN_RESET); } bool read() const { return HAL_GPIO_ReadPin(port_, pin_) == GPIO_PIN_SET; } void toggle() { HAL_GPIO_TogglePin(port_, pin_); } private: GPIO_TypeDef* const port_; const uint16_t pin_; };

这里用了构造函数初始化列表,把port_和pin_标记为const。这样设计有几个好处:引脚配置在对象创建那一刻就固定下来;整个生命周期内不会因为误操作把pin改成别的引脚;读引脚和写引脚的方法非常短,编译器大概率内联,运行效率不会比直接操作寄存器差。

用起来感受一下差距。以前写点灯,每换一个引脚就要重写一遍GPIO_InitTypeDef配置结构体,初始化代码堆在一起。现在就是一行"GpioPin led(GPIOC, GPIO_PIN_13);"——LED那个引脚变成了一个明明白白的对象,后面所有逻辑里只需要调用led.toggle(),完全不需要关心底层寄存器长什么样。

2.3 类之间的引用关系与构造顺序

UltrasonicSensor这个类要依赖Trig引脚、Echo引脚和输入捕获定时器,这里要决定用"拥有"还是"引用"的关系。我的做法是让传感器持有这些依赖的引用,不复制底层对象:

class UltrasonicSensor { public: UltrasonicSensor(GpioPin& trigPin, GpioPin& echoPin, InputCaptureTimer& timer) : trig_(trigPin), echo_(echoPin), timer_(timer) {} private: GpioPin& trig_; GpioPin& echo_; InputCaptureTimer& timer_; };

引用成员有一个使用要求:必须在构造函数初始化列表里初始化,而且被引用的对象必须先于当前对象构造。在main.cpp里,这个顺序就是"先创建GpioPin对象,再创建InputCaptureTimer对象,最后创建UltrasonicSensor对象"。

翻译成实际工程语言就是:硬件初始化顺序对应了真实的上电时序。时钟、引脚、定时器、业务对象,一级一级往上搭。如果谁把这个顺序写反了,编译器可能直接报错,也可能在运行时出现野指针访问——后者更隐蔽,排查起来非常痛苦。这个"构造顺序对应硬件依赖关系"的思想,比单纯记住语法规则有价值得多。

3. 超声波测距:输入捕获定时器的实测逻辑

3.1 HC-SR04的测距原理

HC-SR04是最常见的超声波测距模块,模块上只有Trig和Echo两个控制引脚。工作流程是:给Trig引脚一个10us以上的高电平脉冲,模块内部就会发射8个40kHz的超声波脉冲,同时把Echo引脚拉高;超声波碰到障碍物返回后,模块内部接收电路检测到回波,把Echo拉低。所以Echo高电平持续的时间,就是超声波从发射出去到返回回来的总时长。

声速在空气中大约340m/s,超声波走的是往返双程,所以距离等于声速乘以时间除以2。工程上有个常用的简化公式:距离(cm)≈ 回波脉宽(us)/ 58。也就是说Echo高电平持续5800us,距离就是100cm。这个公式我用了很多年,实测校准之后误差在2cm以内,非常省事。

3.2 用TIM2输入捕获而不是傻等轮询

很多入门教程教大家测距就是死循环轮询Echo引脚,读到一个高电平就开一个延时,读到低电平就停。这个方法在纯教学demo里能用,但在我们这个云台项目里不行——因为系统还要驱动步进电机、处理按键中断、打印日志,一个阻塞式测距函数就可能把整个系统卡死。

正确的做法是把Echo接到定时器的输入捕获通道上。我把Echo接到PA0,对应的就是TIM2_CH1。TIM2配置成输入捕获模式,预分频设成71,这样计数频率是1MHz——每计数一次代表1微秒。然后利用输入捕获的边沿触发机制:上升沿到来时,在中断里把计数器清零,同时把捕获边沿切换成下降沿;下降沿到来时,在中断里读取当前计数器值,这个值就是Echo高电平的微秒数。

中断处理的核心代码结构大概是这样的:

void TIM2_IRQHandler() { if (LL_TIM_IsActiveFlag_CC1(TIM2)) { LL_TIM_ClearFlag_CC1(TIM2); if (edge_ == Rising) { LL_TIM_SetCounter(TIM2, 0); // 上升沿清零 LL_TIM_IC_SetActiveEdge(TIM2, LL_TIM_ACTIVEEDGE_FALLING); // 切换下降沿 edge_ = Falling; } else { pulseWidthUs_ = LL_TIM_IC_GetCapture1(TIM2); // 下降沿读脉宽 LL_TIM_IC_SetActiveEdge(TIM2, LL_TIM_ACTIVEEDGE_RISING); // 恢复上升沿 edge_ = Rising; pulseReady_ = true; } } }

这里有个细节必须提醒:F103的TIM2是16位计数器。HC-SR04最远测距4米,对应脉宽约23ms,按1us计数就是23000,刚好在16位范围内。但如果你把预分频算错了,让计数频率超过1MHz,脉宽计数就可能溢出,导致读到的是被截断的错误值。我之前犯过这个错误,现象是"近距离正常、远距离集体冒出一个错乱值",最后查了很久才发现是计数器溢出了。安全起见,把自动重载值ARR设成最大65535,计数器频率压到1MHz,余量就很充足了。

3.3 距离计算与低通滤波

拿到脉宽之后,距离换算公式就一行:return pulseWidthUs_ / 58.0f;。但实际测起来,HC-SR04单次读数抖动比较严重,尤其是目标表面带倾斜角或者有环境噪声的时候,连续两次读数能差出十几厘米。

我在UltrasonicSensor类内部加了个简单的滤波:维护最近5次有效测量值,去掉最大值和最小值,剩下三个取平均。如果连续3次测量都返回超时(比如没有回波),就判定为"无目标"状态,业务层就不更新对应角度的记录。这个滤波逻辑完全封装在类内部,上层调用measureCm()拿到的永远是处理过的数据,业务代码不需要关心这些细节。

实测效果很明显:同样测一面墙,未滤波时数据在98cm到105cm之间抖,加滤波之后稳定在100cm到101cm之间。对于这个项目来说,2cm级别的误差完全够用。如果你未来要做的项目要求毫米级精度,超声波传感器本身的天花板就在那里,建议直接考虑ToF激光测距方案。

4. 步进电机相序表:让云台按角度走位的驱动方法

4.1 28BYJ-48和ULN2003:硬件搭配的讲究

云台用的电机是28BYJ-48,一颗非常经典的减速步进电机。它叫"五线四相",五根线里有一根是公共线,接电源正极,剩下四根分别对应A、B、C、D四个绕组相。电机内部转子每走一步,对应的相序就要切换一次。

重点说一下为什么必须搭配ULN2003驱动板。单片机引脚输出电流只有几毫安,而步进电机绕组瞬态电流能达到两三百毫安,直接驱动不仅力矩不够,还可能把MCU引脚烧掉。ULN2003内部是达林顿管阵列,输入侧用逻辑电平就能控制输出侧的通断,而且内部已经集成续流二极管到COM引脚。这里有个容易忽略的接法:驱动板上的COM引脚必须接到电机电源的正极上,续流二极管才能把绕组断电时产生的反向电动势泄放掉。我见过有人不接COM,电机也能转,但驱动板摸起来发烫,时间长了容易烧芯片。

4.2 八拍驱动时序的查表实现

步进电机的驱动核心是相序表。28BYJ-48常用的驱动方式是单双八拍,比四拍更平滑,力矩也更足。相序顺序如下:

步骤ABCD二进制
110000b0001
211000b0011
301000b0010
401100b0110
500100b0100
600110b1100
700010b1000
810010b1001

把这8个值做成一个constexpr数组,放在StepperMotor类里。每走一步,从数组取当前相序值,分别写到四个IO引脚上,然后延时一小段时间,再进入下一步。反方向转就是倒着查表。核心代码片段如下:

void StepperMotor::stepOnce(int dir) { phase_ = (phase_ + dir + 8) % 8; uint8_t state = phaseTable_[phase_]; in1_.setHigh(state & 0x01); in2_.setHigh(state & 0x02); in3_.setHigh(state & 0x04); in4_.setHigh(state & 0x08); delay_us(stepDelayUs_); }

写这个类时有个经验:把相序表直接用static constexpr数组固化在类的内部,不要放在外部作为全局变量,否则容易和别的模块撞名字,也破坏封装性。

4.3 角度换算:这笔账必须算清楚

28BYJ-48最坑的地方在于减速比。电机内部转子每8拍走5.625°,但输出轴经过一个1:64的减速齿轮箱,所以输出轴实际的每一步角度是5.625°除以64,约等于0.0879°。也就是说,输出轴转一整圈需要4096步。如果你忽略了减速比,直接按内部步距角算角度,让电机转动力矩杆走20步你以为是112°,实际上输出轴才转了1.76°,结论会离谱得自己都找不到原因。

换算常数我放在StepperMotor类里:

static constexpr float kStepAngleDeg = 5.625f / 64.0f; // 输出轴每步约0.0879°

云台需要扫0°到180°,那单程需要的步数就是180/0.0879≈2048步。这个数会和后面的业务状态机直接挂钩。

步进速度由两个参数控制:两拍之间的延时stepDelayUs_和单步走完后是否停顿。实测5V供电下,28BYJ-48的拍间隔低于1ms时经常丢步,尤其是云台上还挂着一个超声波探头和支架的时候。我最后把默认拍间隔设在1500us,稳定可靠。如果你用的是12V驱动方案,可以把间隔缩到800us左右,速度能上去一截,但要注意驱动板温度。

为了让扫掠过程既稳定又高效,我的策略是:电机连续走32步就停一下,这32步对应的角度是32×0.0879≈2.8°。停稳50ms后测一次距离,记录当前角度和测距结果,然后继续走下一步段。按这个策略,扫完180°单程大约需要2048/32=64个测距点,总耗时在30秒上下。这个速度对展示演示来说很合适,人眼能清楚看到探头在转,同时数据更新节奏也舒服。

5. 业务层代码:自动扫掠与距离告警的状态机

5.1 用状态机管理扫掠过程

业务层如果不用状态机,直接在main函数里写两个嵌套for循环,也不是不能跑。但一旦要加"按键暂停""方向切换""报警恢复"这些需求,代码很快就会乱成一锅粥。我的做法是定义三个枚举状态:

  • SCAN_FORWARD:正向扫掠,从0°到180°
  • SCAN_BACKWARD:反向扫掠,从180°回到0°
  • ALARM:检测到目标距离小于阈值,进入告警并保持当前角度

主循环的大致逻辑是:

switch (state_) { case SCAN_FORWARD: if (currentAngle_ < 180.0f) { motor_.stepN(32); // 走2.8° currentAngle_ += 2.8f; delay_ms(50); // 等机构稳定 float dist = ultrasonic_.measureCm(); logger_.sendAngleDist(currentAngle_, dist); if (dist > 0 && dist < threshold_) { state_ = ALARM; } } else { state_ = SCAN_BACKWARD; } break; case SCAN_BACKWARD: // 逻辑与正向对称,角度递减 break; case ALARM: buzzer_.on(); float dist = ultrasonic_.measureCm(); if (dist >= threshold_) { buzzer_.off(); state_ = (currentAngle_ < 180.f) ? SCAN_FORWARD : SCAN_BACKWARD; } break; }

写状态机有个心得:把"状态改变"和"状态下的行为"分开。case分支里执行具体动作,状态转换放在条件判断里,一个分支不要同时干太多事。这样调试的时候,只需要关注"当前状态对不对""当前状态该干什么"两件事。

5.2 按键和中断:绝对不要在中断里干重活

按键接在PB13,配置成外部中断下降沿触发。我要求按键的功能是:在"安全距离1.0m"和"警戒距离0.5m"两档阈值之间切换。这个需求实现上有一点很关键——中断服务函数里只做一件事:把按键标志位置true,并把一个计数器加1。真正的阈值切换逻辑放在主循环里判断标志位后执行。

为什么非要绕这一圈?因为中断服务函数必须尽快返回。如果直接在中断里执行状态切换、蜂鸣器开关、串口打印这些操作,一方面会拉长中断响应时间,影响其他中断的实时性;另一方面,如果中断里调用的函数需要访问一个主循环正在修改的变量,还可能产生数据竞争问题,轻则逻辑错乱,重则程序跑飞。在主循环里通过标志位处理按键,虽然响应会慢几毫秒,但换来的是整个系统的稳定,这笔账很划算。

蜂鸣器那部分没什么技术含量,有源蜂鸣器给高电平就响。唯一注意的点是初始化时先给低电平,防止上电瞬间蜂鸣器突然叫一声吓人一跳。

6. 串口日志与数据帧:把雷达数据搬上电脑

6.1 串口驱动与环形缓冲区

USART1配置为115200-8-N-1,发送用查询方式,接收用中断加环形缓冲区。发送查询方式的好处是逻辑简单,数据量也不大——每次最多几十个字节,一帧发出去的时间大约1ms以内,不影响主循环节奏。

接收端我做了个128字节的环形缓冲区,中断里把收到的字节读进缓冲区,主循环按需取走。之所以用环形缓冲区而不是简单用一个数组,是因为串口数据是异步到达的,随时可能来,主循环如果正在忙别的,不能丢数据。环形缓冲区天然解决生产者和消费者的速度不匹配问题。代码实现很简单:

class RingBuffer { public: bool push(uint8_t b) { uint16_t next = (head_ + 1) % sizeof(buf_); if (next == tail_) return false; // 满 buf_[head_] = b; head_ = next; return true; } bool pop(uint8_t& b) { if (head_ == tail_) return false; // 空 b = buf_[tail_]; tail_ = (tail_ + 1) % sizeof(buf_); return true; } private: uint8_t buf_[128]; volatile uint16_t head_ = 0; volatile uint16_t tail_ = 0; };

这个版本虽然简单,但"头尾指针取模"的思路已经够用。以后要改成保序、可查询长度、支持覆盖写等高级功能,都是在这个骨架上加。

6.2 数据帧格式:先让人能看懂,再考虑让机器能解析

为了让上位机好解析,我定义了一个简单的文本帧:

A:12.3,D:45.2,S:OK A:14.1,D:35.0,S:ALARM

三个字段分别对应角度、距离、状态。文本帧的好处是调试阶段用串口助手直接能看到原始内容,遇到异常数据肉眼就能判断问题出在哪。等以后要接Qt上位机画实时曲线,再改成二进制帧或者JSON都不迟——文本帧的解析逻辑简单,上位机那边处理成本也低。

这里我想多说一句:日志输出这种事,千万不要在主逻辑代码里到处写printf。你在业务循环里塞五六个printf,代码可读性立刻崩掉。把串口封装成Logger类,对外暴露sendAngleDist()这种语义化方法,内部再做格式化输出。这样以后想改输出格式,只需要动Logger一个文件。哪怕项目只有你一个人写,三个月后回头看代码,你会感谢现在愿意多花这十几分钟做封装的自己。

7. 实测表现、参数调优与避坑记录

7.1 实测数据与误差分析

我在办公室里用一把椅子和一面白墙做了几组测试。固定目标距离1.0m时,连续测10次,数据落在0.98m到1.02m之间,最大偏差约2cm。云台扫掠时,目标放在大约90°方向,扫过80°到100°区间能稳定识别出目标存在,角度分辨率跟预设的2.8°一致,没有出现漏检。

误差来源主要有三个:一是HC-SR04本身的波束角大约有15°,遇到倾斜表面时反射路径不稳定;二是扫掠采样角度间隔2.8°,目标边缘的角度定位会有半格左右的偏差;三是Echo信号边沿本身存在几十微秒的硬件抖动,对应距离误差在1~2cm。对这类教学演示型项目,这个精度完全够用。真到了工业场景,超声波的物理上限摆在那里,直接换激光ToF传感器更实在。

7.2 分步排查的经历:那次测距数据"集体乱跳"

调试过程中最难定位的一个问题,是测出来的距离数据完全随机跳动,60cm、30cm、140cm乱出。一开始我以为是定时器配置错了,反复检查了TIM2的预分频和捕获极性,都没问题。后来用示波器量Echo引脚波形才发现,Echo信号高电平很干净,问题不在定时器。

真正的原因是供电和共地。超声波模块我单独用了一个5V电源适配器供电,而STM32主板用USB供电,两个电源的负端没有连在一起。Echo信号相对于STM32的地是"浮动"的,参考地不一致,GPIO读取到的边沿当然是不确定的。把两个电源的地线用杜邦线连在一起之后,测距数据立刻恢复正常。这是一个非常典型的低级错误,但也是DIY项目的经典坑——多电源供电时务必共地,这句话写在任何嵌入式教程里都不嫌多。

7.3 完整的避坑清单:这次踩过的五个坑

  • 输入捕获边沿切换顺序:上升沿触发后要立刻清零计数器再切换边沿,否则脉宽里包含上升沿之前的时间偏移,测出来的距离整体偏大。
  • TIM2溢出问题:F103的TIM2是16位,必须把预分频设为71把计数频率压到1MHz,ARR设成最大65535。否则测距目标4米时脉宽23000us,接近16位上界,出现整周翻转误读数。
  • GPIO初始化前先置低:步进电机的四个控制引脚在配置成推挽输出前,如果处于浮空状态,驱动板输入端悬空可能导致内部状态不确定,上电瞬间电机可能乱跳一步。初始化函数里先全部写低电平,再启动电机。
  • 方向反转抖动:扫掠从正向切反向时,如果直接倒查相序表,电机会有明显的顿挫和抖动。我在反向切换前先插入一次"同向补偿步",让动作过渡更平滑。
  • VSCode调试svd文件缺失:用ST-Link调试F103时,launch.json里要配置正确的svd文件路径,否则寄存器窗口显示的全是错乱地址。这个坑我在第4篇里说过,结果这次新建工程又忘了配置,花了几分钟才反应过来。

7.4 项目跑通之后的进阶方向

如果你按照这篇文章把云台跑起来了,接下来有三个升级方向可以参考。一是给云台加一个磁编码器,比如AS5600,用PID控制步进电机精确转到指定角度,这就把控制理论部分真正落到工程里了。二是把串口通信升级成CAN或RS485,再接Modbus RTU协议,可以学习工业现场的设备通信套路,参考开源库agile_modbus能省不少事。三是把Logger从"串口打印"升级成带日志等级、时间戳、Flash掉电存储的完整模块,这会让你的代码更接近商业产品形态。

最后说点这一趟干下来最深的体会。一开始写这个项目时,我其实也不太确定,把GPIO、定时器、电机、串口全部包成类是不是绕远路了,直接写寄存器操作不是更省事吗?但真把代码重构完才发现,C++封装最大的收益不是代码看上去多高级,而是你在写业务逻辑的时候,脑子里只需要装三件事——电机转到哪个角度、传感器测到多少距离、要不要报警——至于寄存器长什么样、相序表怎么查,全部被关在驱动类的小黑屋里。这就是"封装"应该有的样子。还差活滴?干完这件,你大概不会再问这句话了。下一期我打算把这个云台数据接到Qt上位机上画实时扫描曲线,有兴趣的话可以先预习一下QSerialPort的基础用法,我们下篇再见。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询