Farid Rener About Art CV Food we've cooked Music Writing

用 Arduino 追踪国际空间站 (ISS Tracker)

2025年4月4日

去年夏天,我收到了非常有趣的 HackPack 作为生日礼物。 每两个月,你会收到一盒零件,然后组装一个有趣的硬件项目。

这个包里的第一个物品是红外炮塔 (IR turret)。 你可以用红外遥控器控制炮塔,并且可以向目标发射小型泡沫子弹。

IR Turret

它非常酷,但我不太喜欢射击东西。 大约在同一时间,我看到了这幅 XKCD 漫画:

XKCD 2979

我喜欢去户外观看有趣的事情,特别是当国际空间站 (ISS) 从头顶经过时。 我也觉得 XKCD 漫画里的设备是个非常有趣的想法。

在与我在 Plateau Astro 的朋友 Trevor 聊天后,我想我可以改造红外炮塔,让它始终指向 ISS。 他向我推荐了 这个 很棒的项目,有人做了类似的事情。

什么是 ISS

简单回顾一下,国际空间站 (International Space Station) 是一个大型的可居住的航天器,它以大约 420 公里的高度绕地球运行。 它以大约 28,000 公里/小时的速度飞行,大约每 90 分钟完成一次轨道。 有宇航员居住在 ISS 上,他们进行很酷的实验,并且可以做一些事情,例如将自己绑在墙上,以免在睡觉时漂走。

对我来说,ISS 最棒的部分是你可以从地球上看到它! 在晚上,当它从头顶经过时,它看起来像一颗移动非常快的星星,或者是一架非常非常远的飞机。 你可以下载一个像 ISS Detector 这样的应用程序,它会提醒你任何可见的经过。 一旦你习惯了出去看 ISS,你也可以开始做一些愚蠢的事情,比如用它来反射无线电信号,或者 与在船上的宇航员交谈

构建 ISS 追踪器

为了激励你,这里是 ISS 直接经过我家时的最终产品: 你的浏览器不支持 video 标签。

指向 ISS

我们的目标是指向 ISS 的箭头(无需看手机),这样我们就能知道它何时在地平线上方,从而可以看到它。 考虑到红外炮塔的设计,我们需要两个角度:方位角 (azimuth)仰角 (elevation)(或高度)。

我们需要一些信息才能做到这一点:

查找 ISS 的位置

NORAD 跟踪地球轨道上卫星的位置,并生成所谓的 双行轨道根数 (Two-Line Element set),或 TLE。 TLE 是一种标准化的数据格式,用于描述对象的轨道参数,并且我们可以从该数据计算对象在某个时间点的位置。 对于像 ISS 这样的太空物体,TLE 每天更新几次。

CelesTrak 发布这些 TLE,并通过他们的 API 以如下格式向公众开放:

ISS (ZARYA)       
1 25544U 98067A  25093.13425953 .00020483 00000+0 37635-3 0 9994
2 25544 51.6367 318.7328 0004848  3.8316 356.2709 15.49192057503527

由于 ISS 的轨道速度比 TLE 的更新速度快得多,我们需要根据 TLE 中的参数计算其当前位置。 为此,我们使用 SGP4 算法 根据 TLE 中给出的初始条件来传播 ISS 的轨道元素。 该算法考虑了很多因素,例如地球的非球形引力场、大气阻力以及月球和太阳的引力。

通过 SGP4 算法传递 TLE 中的初始条件,我们最终得到一个基于 地心惯性坐标系 (Earth-Centered Inertial) (ECI) 的位置,ECI 是一个平面,相对于我们计算时的恒星固定(“历元”)。

我们现在需要将我们当前的位置转换为相同的参考系 (ECI),这样我们就可以绘制从我们当前位置到 ISS 当前位置的向量。 这个向量将为我们提供角度 - 方位角和仰角,我们需要指向我们的箭头。

 Direction calculateAzEl(double lat, double lon, double alt, double satX,
             double satY, double satZ, libsgp4::DateTime now)
 {
  // Convert lat/lon to radians
  const double phi{lat * pi / 180.0};
  const double lambda{lon * pi / 180.0};
  const double h{alt};
  const double sin_phi{sin(phi)};
  const double N{a / sqrt(1 - e2 * sin_phi * sin_phi)};
  // Compute ECEF coordinates of the observer
  const double Xo_ecef{(N + h) * cos(phi) * cos(lambda)};
  const double Yo_ecef{(N + h) * cos(phi) * sin(lambda)};
  const double Zo_ecef{(N * (1 - e2) + h) * sin(phi)};
  const double theta{now.ToGreenwichSiderealTime()};
  // Rotate ECEF coordinates to ECI coordinates
  const double observerX{Xo_ecef * cos(theta) - Yo_ecef * sin(theta)};
  const double observerY{Xo_ecef * sin(theta) + Yo_ecef * cos(theta)};
  const double observerZ{Zo_ecef};
  // Compute the vector from observer to satellite in ECI
  const double dX{satX - observerX};
  const double dY{satY - observerY};
  const double dZ{satZ - observerZ};
  // Compute local unit vectors (East, North, Up)
  // Up vector (U)
  const double norm_O{sqrt(observerX * observerX + observerY * observerY + observerZ * observerZ)};
  const double Ux{observerX / norm_O};
  const double Uy{observerY / norm_O};
  const double Uz{observerZ / norm_O};
  // East vector (E)
  const double norm_E{sqrt((-Uy) * (-Uy) + (Ux) * (Ux) + 0.0)};
  const double Ex{-Uy / norm_E};
  const double Ey{Ux / norm_E};
  const double Ez{0.0};
  // North vector (N) = U x E
  const double Nx{Uy * Ez - Uz * Ey};
  const double Ny{Uz * Ex - Ux * Ez};
  const double Nz{Ux * Ey - Uy * Ex};
  // Project the satellite vector onto the ENU coordinates
  const double norm_d{sqrt(dX * dX + dY * dY + dZ * dZ)};
  const double dX_unit{dX / norm_d};
  const double dY_unit{dY / norm_d};
  const double dZ_unit{dZ / norm_d};
  // Compute ENU components
  const double E_comp{Ex * dX_unit + Ey * dY_unit + Ez * dZ_unit};
  const double N_comp{Nx * dX_unit + Ny * dY_unit + Nz * dZ_unit};
  const double U_comp{Ux * dX_unit + Uy * dY_unit + Uz * dZ_unit};
  // Calculate Azimuth and Elevation
  double azimuth{atan2(E_comp, N_comp) * 180.0 / pi};
  if (azimuth < 0.0)
  {
   azimuth += 360.0;
  }
  const double elevation{asin(U_comp) * 180.0 / pi};
  return Direction{azimuth, elevation};
 }

我们最终得到两个角度:

构建追踪器:

材料

像这样组装电路:

步进电机连接到引脚 8-11,舵机连接到引脚 12。我还连接了一个开关,但这是可选的。

身体

最初的红外炮塔不太符合我的需求,因为我希望仰角“箭头”能够旋转完整的 360 度。 我也从未在 3D 中设计过任何东西,并且想学习! 当我在 Recurse Center 时,有很多好心人帮助我完成了这个过程。

该主体是在 OnShape 中设计的(文档, STL of body, STL of arrow, STL of motor horn)

这是一个有趣的设计过程。 我必须进行非常精确的测量,例如:

如果你打印上面链接的所有 STL 文件,那么所有部件如何组合在一起应该是不言自明的。 唯一幸存下来的红外炮塔部件是“腿”。 你可以尝试将“电机喇叭”拧到一块木头或其他坚固的东西上,效果应该一样。

我用黏土以某种随意的方式将所有电子元件粘在背面。 如果有 V2 版本,我将为这些东西留出更多空间。

组装

步进电机喇叭连接到基座。 显然,使用铅笔芯作为润滑剂在 3D 打印塑料上效果很好! 或者你可以简单地将喇叭拧到一块木头或其他稳定的基座上 前视图。 打开追踪器时,步进电机的顶部应指向北方 后视图。 电子元件用黏土连接 电池和开关的侧视图 连接到舵机的指针的俯视图

代码

代码库 编写起来非常有趣,而且我必须学习很多轨道力学才能让它做我想做的事情。

要使其运行:

cp ./arduino_secrets.h.example ./arduino_secrets.h
constexpr const char CATALOG_NUMBER[] = "25544";

注意

Farid Rener

Farid 的笔记集合