前FIRST导师提出面向周期性编程理念,批评FRC机器人程序过度工程化
来源标题:Periodic-Oriented Programming for First Robotics
一位在FIRST机器人竞赛(FRC)编程约17年的前导师退休后提出「面向周期性编程」(Periodic-Oriented Programming),主张在周期函数中尽可能完成更多工作。他认为FIRST和WPILib多年来鼓励学生严重过度设计机器人程序,导致编程实践偏离正轨,应予纠正。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
我最近从FIRST Robotics Competition(FRC)队伍的导师岗位上退休了。2010年我以学生身份加入,毕业后直接开始做导师,所以算下来我写FRC程序大约有17年了。
然而随着时间推移,我越来越对FRC程序的典型写法感到失望。几十年来,我觉得FIRST和WPILib一直在鼓励学生把机器人程序严重过度工程化。随着时间流逝,我对FRC编程的现状越来越不满,我们队伍的代码风格也越来越偏离主流。
说清楚一点,我对FIRST和WPILib维护者每年投入的时间和精力抱有最大的敬意,正是他们的努力才让FRC队伍每年都有可靠且完整的库。但我认为FRC中被广泛接受的编程实践正在把学生引向歧途,应该被纠正。
所以在我离开FIRST之际,我决定把我在FRC中的机器人编程哲学写下来,我给它起了个精辟的名字:面向周期编程(Periodic-Oriented Programming)。
面向周期编程非常简单:尽可能多地在periodic函数里做事。少用抽象,把大部分逻辑写在一个大函数里,从上到下。这简单得、初学者得看起来根本不像能用于正经程序。但它是可行的,只要你 carefully 建立几条简单的边界和做法。
作为背景,FRC程序的基本结构通常是使用WPILib提供的TimedRobot框架。使用时,你只需实现periodic函数,机器人框架每秒调用它们50次。例如,下面是一个完整的Python“坦克驱动”机器人程序: import wpilib import rev class MyRobot(wpilib.TimedRobot): def robotInit(self): self.gamepad = wpilib.XboxController(0) self.leftMotor = rev.SparkMax(10, rev.SparkLowLevel.MotorType.kBrushless) self.rightMotor = rev.SparkMax(11, rev.SparkLowLevel.MotorType.kBrushless) def teleopPeriodic(self): self.leftMotor.set(self.gamepad.getLeftY()) self.rightMotor.set(self.gamepad.getRightY()) 每秒50次,左右电机速度会被更新为使用左右手柄轴的值。其余的事情由机器人框架和硬件抽象层完成。
本质上,每个FRC程序都在periodic中做以下工作:
在面向周期编程中,我基本上鼓励学生拥抱这种模式。POP代码应该少用抽象,大部分逻辑应该在一个函数里从上到下写出来。并且尽可能让所有东西在每个periodic中都运行——没有模式、没有命令、没有状态。
随着程序变大,需要更多结构,但核心哲学保持不变。最重要的结构是子系统。
“子系统”类的使用对大多数FRC队伍来说应该很熟悉,但这里的细节才是关键:
这些规则把面向周期的思想延伸到子系统代码中。对于每个子系统,其所有逻辑都应该集中在一个函数里,并且从上到下可读。大多数情况下,子系统periodic是整个程序中唯一真正接触硬件的地方。这有很多好处:它在构造上确保硬件每个tick只被访问一次,安全约束100%的时间都被遵守,日志和遥测也一致地运行。
把大部分逻辑集中在periodic里,也为自动和遥操代码提供了良好的平台。子系统的API表面可以由非常高层级的函数组成,比如driveFieldRelative(vx, vy)或goToElevatorPreset(coralOrAlgae, level),它们基本上只是设置实例变量,供之后在periodic中读取。这些子系统函数也提供了一致的位置来验证输入(例如不可能的速度、角度或预设值)、转换单位等。
顺便说一句,如果两个子系统的逻辑交织在一起,不要害怕把它们合并。例如,2025年我们的机器人有一个装在升降机构上的臂,而臂的安全角度取决于升降机构的高度。因此,我们做了一个合并的ElevatorAndArm子系统。(如果你听过“类应该只关于一件事”的建议,忽略它,那是错的。[1])
正如我之前描述的,每个FRC程序都计算硬件的期望状态,然后更新输出以朝那个方向移动。这在闭环控制(通常是PID)中最为明显,你只需设置一个目标值(例如“升降机构在6英尺处”),然后计算输出让硬件朝该目标移动。
成功的面向周期程序把这一哲学应用到整个机器人程序。我想要什么硬件状态,以及如何到达那里?这种思维方式适用于执行器层面(例如电机速度)、机构层面(例如臂角度)以及整机层面(例如“臂只有在升降机构降下时才能伸出”这类安全约束)。它普遍适用于所有机器人控制。
“我想要什么硬件状态?”这个问题自然落在teleopPeriodic和autonomousPeriodic中。它们自然地定义了对这些问题的答案:场地上的哪里?什么速度?什么升降位置?什么臂角度?什么射手RPM?
“我如何到达那里?”这个问题则自然落在子系统periodic中。通常它简单到只是elevatorMotor.setReference(elevatorHeight),把实际的闭环行为交给电机控制器。但额外的行为也很容易在这里分层加入;例如,之前描述的整机约束可以简单到: def periodic(self): newArmAngle = self.desiredArmAngle if self.getElevatorHeight() > constants.kMaxElevatorHeightForArm: newArmAngle = 0 # pull arm in if elevator is up self.armMotor.setReference(newArmAngle)
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。