用 Arduino 追踪国际空间站 (ISS Tracker)
Farid Rener About Art CV Food we've cooked Music Writing
用 Arduino 追踪国际空间站 (ISS Tracker)
2025年4月4日
去年夏天,我收到了非常有趣的 HackPack 作为生日礼物。 每两个月,你会收到一盒零件,然后组装一个有趣的硬件项目。
这个包里的第一个物品是红外炮塔 (IR turret)。 你可以用红外遥控器控制炮塔,并且可以向目标发射小型泡沫子弹。
它非常酷,但我不太喜欢射击东西。 大约在同一时间,我看到了这幅 XKCD 漫画:
我喜欢去户外观看有趣的事情,特别是当国际空间站 (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 的位置,无论是 现在 还是过去某个已知时间
- 我们当前的位置和海拔
- 我们需要将这两个信息放在相同的坐标系中
查找 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};
}
我们最终得到两个角度:
- 方位角 (Azimuth):这是从北方开始的角度,以度为单位。
- 仰角 (Elevation):这是从地平线开始的角度,以度为单位。
构建追踪器:
材料
- Arduino Uno R4 Wifi
- 28BYJ-48 步进电机 + 电机驱动模块(用于 方位角 (azimuth) 旋转)
- SG90 微型舵机(用于 仰角 (elevation) 旋转)
- 一个 5V 电源。 我使用了 HackPack 红外炮塔附带的电池组。 如果是电池,则有助于电缆管理(稍后会详细介绍)。
- 各种电线。
- 黏土,用于将所有东西组装在一起……
像这样组装电路:
步进电机连接到引脚 8-11,舵机连接到引脚 12。我还连接了一个开关,但这是可选的。
身体
最初的红外炮塔不太符合我的需求,因为我希望仰角“箭头”能够旋转完整的 360 度。 我也从未在 3D 中设计过任何东西,并且想学习! 当我在 Recurse Center 时,有很多好心人帮助我完成了这个过程。
该主体是在 OnShape 中设计的(文档, STL of body, STL of arrow, STL of motor horn)
这是一个有趣的设计过程。 我必须进行非常精确的测量,例如:
如果你打印上面链接的所有 STL 文件,那么所有部件如何组合在一起应该是不言自明的。 唯一幸存下来的红外炮塔部件是“腿”。 你可以尝试将“电机喇叭”拧到一块木头或其他坚固的东西上,效果应该一样。
我用黏土以某种随意的方式将所有电子元件粘在背面。 如果有 V2 版本,我将为这些东西留出更多空间。
组装
步进电机喇叭连接到基座。 显然,使用铅笔芯作为润滑剂在 3D 打印塑料上效果很好! 或者你可以简单地将喇叭拧到一块木头或其他稳定的基座上
前视图。 打开追踪器时,步进电机的顶部应指向北方
后视图。 电子元件用黏土连接
电池和开关的侧视图
连接到舵机的指针的俯视图
代码
代码库 编写起来非常有趣,而且我必须学习很多轨道力学才能让它做我想做的事情。
要使其运行:
- 首先断开电池与 Arduino 的连接
- 从 GitHub 下载 Arduino IDE v2.3.3。 较新的版本会创建更大的二进制文件,这些文件大于 R4 上可用的程序内存。
- 创建一个新的 arduino_secrets.h 版本,并使用你的 WiFi 信息和当前位置填充它。
cp ./arduino_secrets.h.example ./arduino_secrets.h
- 将其刷新到 Arduino 上。
- 默认情况下,
src/Config.h
中的 Debug 标志已设置,这意味着它会将调试信息打印到 Serial 控制台。 如果一切顺利,你将看到它打印出:- Wifi 状态
- 当前的 TLE
- 然后每秒钟它都会打印出它认为 ISS 当前所处的方位角和仰角。 你可以使用 ISS Detector 应用程序验证其准确性
- 一旦所有这些都运行正确,你就可以断开 USB 电缆并连接电池。
- 在打开它之前,你应该将追踪器设置成步进电机的顶部指向北方。
- 打开它。 几秒钟后,它应该连接到你的 wifi 网络,并移动电机以指向 ISS。
- 现在它每秒钟都会更新其位置。 当 ISS 离你最近时,它最令人印象深刻,因为相对角度大得多。
- 如果你愿意,你可以通过更改 这一行 代码为你想跟踪的 NORAD 目录编号来跟踪其他卫星。
constexpr const char CATALOG_NUMBER[] = "25544";
注意
- 我使用了 这里 的 SGP4 C++ 库。
- 在尝试在 Arduino 上运行它时遇到的一个问题是,在编写它时,代码库太大而无法适应! 这是我第一次不得不考虑编译后的二进制文件的大小 - 最令人沮丧的部分是弄清楚使用 C++
stringstream
会向我的二进制文件中添加过多的代码。 - 如果你最终构建了其中一个,请联系我!
Farid Rener
- Farid Rener
- faridrener at gmail dot com
- proteusvacuum
Farid 的笔记集合