메뉴 전체보기

회원메뉴

Whats Up V2 > 게시판

본문 바로가기

쇼핑몰 검색

NO. 3380
제목 : Whats Up V2
2026-09-23 14:31

소개: Whats Up V2


소개: Whats Up V2


소개: Whats Up V2


소개: Whats Up V2


이것은 제가 만든 What’s Up 회로의 버전 2입니다. GPS만 사용해 머리 위에 떠 있는 행성을 판단하는 휴대용 회로입니다. V2에는 더 강력한 STM32, 5년 동안의 행성 계산, 다항식 피팅, 매우 높은 정확도, LiPo 배터리, (무언가라고 불리는) 시차 보정 기능 등이 이 열쇠고리 크기의 회로에 추가되었습니다.

저는 What’s Up 회로의 버전 1을 즐겁게 사용했지만, 머리 위의 행성을 계산하는 모습을 볼 때마다 그 결과가 정확한지 100% 확신할 수 없었습니다... 디버거 없이 V1의 ATtiny3216용 코드를 작성하고, 많은 이것저것 시도해서 무언가가 작동할 때까지 계속하는 일은 언제나 마음에 걸렸습니다.

이러한 요인들로 인해 버전 2를 공유하게 되었습니다.




STEP 1. 준비물


준비물


BOM


  1. 2.2k 저항기 4개 - 0603
  2. 10k 저항기 4개 - 0603
  3. SK6812-EC20 RGB LED 9개
  4. 0.1uF 커패시터 4개 - 0603
  5. LED 2개 - 색상 무관, 0603
  6. 1개 SAM-M10Q GPS 모듈
  7. 100k 저항기 1개 - 0603
  8. 1개 USB-C 리셉터클 - SAMESKY UJ20-C-H-G-SMT-1-P16-TR
  9. TPS7A0230PDBVR 1개 - SOT-23-5
  10. 5.1k 저항기 2개 - 0603
  11. 4.7uF 커패시터 3개 - 0603
  12. 2개 버튼 - 걸윙형 5.2mm
  13. NSR0320 다이오드 1개 - SOD-323
  14. DMP2045U MOSFET 1개 - SOT-23
  15. 1개 SiP32432DR3 - SOT-363 / SC-70-6
  16. 1개 STM32G031K8Ux MCU - QFN-32 5×5mm
  17. 1uF 커패시터 2개 - 0603
  18. MCP73831-2-OT 1개 - SOT-23-5
  19. 2N7002 MOSFET 1개 - SOT-23
  20. 1개 JST S2B-PH-SM4-TB 2핀 커넥터
  21. 1개 리포
  22. 커스텀 PCB 1개 - 자세한 내용은 아래 참조

여기에서 보드 주문


PCB 링크

프로그래밍 소프트웨어

  1. STM32CubeMX
  2. STM32CubeIDE

프로그래머 도구

프로그래밍에 대한 자세한 내용은 아래 참조

  1. STLINK-V3MINIE
  2. Tag-Connect TC2050-IDC-NL-050-ALL | 일반 방향.
  3. TC2050-CLIP-3PACK 고정 클립 세트
  4. 커스텀 14핀-10핀 어댑터 PCB
  5. 100 Ω 0603 저항기.
  6. Samtec FTSH-105-01-F-DV-K-TR
  7. Samtec FTSH-107-01-L-DV-K-P-TR
  8. 커스텀 14핀-10핀 STLINK-V3MINIE PCB

전체 프로그래밍 연결은 다음과 같습니다:

컴퓨터 -> STLINK-V3MINIE -> 커스텀 14핀-10핀 어댑터 PCB -> Tag-Connect TC2050 케이블 -> alorAtMe PCB

납땜

  1. 110V 850W 납땜용 핫플레이트 (이건 큰 편입니다. 작은 제품도 분명 사용할 수 있을 겁니다)
  2. 솔더 페이스트
  3. 솔더 플럭스
  4. 스텐실 스퀴지




STEP 2. SAM-M10Q GPS


SAM-M10Q GPS


SAM-M10Q GPS


SAM-M10Q GPS


이 프로젝트에서는 UART 시리얼 출력과 CubeIDE 디버깅을 사용하기 위해 STM32CubeIDE를 사용합니다. 이러한 자유 덕분에 며칠 동안 이 GPS I2C 코드를 작성하고 최적화하는 데 시간을 쓸 수 있었습니다.

주요 특징

제공된 "sam_m10q.c" 파일에는 제가 배우고 작성하는 동안 추가한 주석도 많이 포함되어 있습니다.

  1. 데이터시트에 따르면 이 GPS는 300 kHz의 상당히 빠른 I2C 통신을 사용합니다. 이는 STM32CubeIDE에서 설정했으며, 일반적인 10k 대신 I2C 풀업 저항으로 2.2k를 사용하는 이유이기도 합니다.
  2. 모듈에 전원이 공급되고 위성 신호를 포착하면 데이터가 채워지기 시작합니다. 머리 위에 있는 GPS 위성은 온보드 안테나가 수신하는 데이터를 끊임없이 아래로 전송합니다. 그 데이터를 올바르게 비우고 NMEA 문장이 시작되는 시점을 판단하는 것은 프로그래머인 우리의 역할입니다.
  3. NMEA 문장은 위치, 시간, 위성 데이터 등을 포함하며 NMEA 표준에 따라 형식이 지정된 GPS 수신기의 일반 텍스트 출력입니다. NMEA 문장을 비우고 작성하는 과정은 제 "SAM_M10Q_Read_GGA" 함수에서 확인할 수 있습니다.
  4. NMEA 문장이 있으면 다음과 같은 형식의 데이터를 파싱해야 합니다: GPGGA,181908.00,3404.7041778,N,07044.3966270,W,4,13.... 직접 파서를 작성했지만 이미 있는 것을 다시 만드는 셈이라는 걸 알고 있었기에, 이제 GitHub의 매우 유명한 경량 C 저장소를 사용하고 있습니다: https://github.com/kosma/minmea 그리고 작동했습니다.

GPS가 작동할 때 데이터를 받는 C 구조체는 다음과 같습니다:

typedef struct
{
uint8_t valid_fix;
uint8_t fix_quality;
uint8_t satellites;
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
uint16_t millisecond;
double latitude;
double longitude;
double altitude_m;
} SAM_M10Q_Data;

또 무엇이 필요하겠어요 :)

백업 배터리

백업 배터리도 중요합니다. 이 GPS는 위성 신호를 포착하고(궁극적으로 머리 위에 어떤 행성들이 있는지를 파악하며) 백업 전원이 공급되면 훨씬 더 빠르게 위성 신호를 포착합니다.

데이터시트(11페이지의 Absolute maximum ratings)에는 백업 전압을 3.6v 미만으로 유지하라고 명시되어 있습니다. 따라서 완전히 충전된 LiPo를 직접 연결하면 GPS가 손상될 수 있습니다. 그래서 LiPo 백업 전원도 고효율 TI 3V LDO를 거칩니다.




STEP 3. 전원 및 프로그래밍


전원 및 프로그래밍


전원 및 프로그래밍


전원 및 프로그래밍


전원 및 프로그래밍


래치

제 회로 대부분과 마찬가지로 SiP32432DR3를 사용해 래치 회로를 구성합니다. SiP32432DR3는 회로가 OFF일 때 나노암페어만 사용하고, 신호를 받으면 전원을 쉽게 통과시키는 데 매우 뛰어납니다.

사용자가 ON 버튼을 누르면 STM32에 전원이 공급되어 켜지고, GPIO 핀 중 하나를 사용해 SiP32432DR3를 래치합니다. 이 래치 상태가 GPS와 LED, 그리고 자기 자신인 STM32에 계속 전원을 공급합니다.

기판에 있는 다른 버튼을 누르면 STM32 GPIO가 래치를 해제하여 회로를 끕니다.

LiPo와 USB-C 충전기도 서로 절연해야 하며, 이는 P채널 MOSFET으로 처리합니다. 회로도에 더 자세한 설명이 있습니다.

남아 있는 유일한 문제는 STM32를 프로그래밍하는 동안에도 전원을 공급해야 한다는 점인데, SiP32432DR3 래칭 시스템 자체는 STM32가 이미 프로그래밍되어 있어야 작동합니다... 무슨 말인지 이해되시나요? 그래서 USB-C가 연결될 때마다 누군가 ON 버튼을 누르는 것처럼 동작하는 N채널 MOSFET을 사용했습니다. 따라서 프로그래밍할 때는 USB-C를 반드시 연결해 두어야 합니다.

기본적으로 전원이 계속 켜져 있는 방법은 세 가지입니다:

  1. USB-C가 N채널 MOSFET을 통해 ON을 ‘누릅니다’.
  2. 사용자가 ON 버튼을 누른 채 유지합니다.
  3. STM32가 처음 전원 서지를 받은 후 스스로 ON 상태를 유지합니다.

프로그래밍

이 보드를 프로그래밍할 때는 모든 제곱밀리미터가 중요합니다. 한쪽 면에 부품을 빽빽하게 배치한 보드이므로, 프로그래밍을 쉽게 할 수 있으면서도 프로그래밍 영역은 최대한 작게 만들고 싶었습니다.

제가 선택한 방법은 STLINK-V3MINI 커넥터를 받아 실제로 필요한 핀만 연결해 주는 커스텀 중간 PCB입니다.

STLINK-V3MINI에는 14핀 커넥터가 있지만, STM32를 프로그래밍하고 디버깅하는 데 필요한 핀은 7개뿐입니다. 그래서 10핀 Tag-Connect 프로그래머를 사용합니다. 10핀은 필요한 7핀보다 많으면서도 STLINK-V3MINI의 14핀 커넥터보다 작습니다.

Tag-Connect에서 8핀 버전을 만들었다면 아마 그걸 샀을 겁니다.

이 노출된 프로그래밍 핀으로 디버깅, 업로드, UART 출력을 할 수 있습니다.

GND, V-SENSE, SWDIO, SWCLK, NRST, UART RX, UART TX

그래서 이제 제 아파트에는 14핀 커넥터를 10핀 커넥터로 연결하는 것이 목적인 간단한 커스텀 PCB가 여러 개 있습니다.

전체 연결은 다음과 같습니다:

컴퓨터->STLINK-V3MINI->14핀 커넥터가 있는 커스텀 PCB->10핀 수 커넥터가 있는 커스텀 PCB->Tag-Connect TC2050 케이블->제 회로 기판

사진 속 PCB 어댑터가 제공된 PCB 링크와 다르게 보인다는 점을 눈치채셨을 수도 있습니다. 사진 속 버전은 오류로 인해 여러 부품을 아예 실장하지 않은 초기 설계입니다. 제가 제공한 PCB 파일과 회로도에는 실제로 필요한 버전이 나와 있습니다.

TC2050 Tag-Connect에는 TC2050-CLIP-3PACK Retaining Clips라는 고정 클립이 있습니다. 이 클립은 프로그래밍하는 동안 케이블을 PCB에 단단히 고정합니다.

STLINK-V3MINI, 커스텀 PCB, Tag-Connect 케이블을 합치면 전체 프로그래밍 구성 비용은 약 $100 USD가 될 것으로 예상합니다.

앞으로 사용할 STM 보드에 작고 품질 좋은 프로그래밍 및 디버깅 연결을 제공하는 일회성 구매가 되기를 바랍니다.




STEP 4. RGB LED


RGB LED


RGB LED


RGB LED


RGB LED


RGB LED는 PWM 및 DMA를 사용합니다. PWM DMA가 무엇인지 궁금하시더라도 걱정하지 마세요. 먼저 STM32의 PWM DMA RGB LED에 관한 놀라운 영상이 있습니다: https://www.youtube.com/watch?v=MqbJTj0Cw6o&t 전에 본 적이 없다면, 이 채널도 전반적으로 정말 훌륭합니다.

개요

기본적으로 PWM, 즉 Pulse Width Modulation(펄스 폭 변조)은 HIGH와 LOW 펄스로 이루어진 주기적인 비트 스트림을 만들 수 있게 해 주며, 신호가 HIGH 상태로 유지되는 시간을 제어할 수 있습니다. 하나의 PWM 주기 또는 펄스는 정확히 하나의 비트를 나타내며, 0 또는 1 중 하나입니다.

간단해 보이지만, 전적으로 부품/LED 데이터시트에 따라야 합니다. 이 LED들은 HIGH와 LOW 구간이 일정한 마이크로초(100만분의 1초) 동안 유지되기를 요구합니다.

여기서 저는 이런 생각을 시작했습니다. PWM 주기의 펄스에서 이 HIGH 구간은 어디에 있을까? 시작 부분일까? 끝일까? 중간일까? 이 LED들은 특히 HIGH 신호가 PWM 펄스의 시작 부분에 있어야 한다고 명시하며, 이는 CubeIDE와 펌웨어에서 설정됩니다.

설정

데이터시트에 따르면 데이터 속도(비트 스트림)는 800 kHz여야 하고, 0을 보낼 때는 최소 0.2 µs 동안 HIGH여야 하며, 1을 보낼 때는 약 0.58 µs가 필요합니다. 또한 리셋 신호는 80 µs보다 오래 LOW 상태여야 합니다.

STM32 클록 속도를 알고 나면 계산은 매우 간단합니다. 필요한 것은 다음과 같습니다.

  1. LED에 필요한 800 kHz PWM 설정
  2. 1과 0에 대한 선행 HIGH 시간 계산

STM32 클록은 16 MHz입니다. 한 틱에 걸리는 시간은 1 / 16000000, 즉 틱당 0.0625 µs입니다.

필요한 800 kHz를 얻기 위해 PWM 주기당 타이머 틱을 20개로 설정했습니다(코드에서). 따라서 20틱 * 0.0625 µs = 1.25 µs입니다. 그리고 1 / 1.25 µs = 800 kHz입니다.

0 비트 값은 최소 0.2 µs의 HIGH 시간이 필요합니다. 타이머 틱이 0.0625 µs이므로 0.2 / 0.0625 = 3.2틱입니다. 올림하여 4틱을 사용합니다. 이 4라는 값은 0을 보낼 때의 compare value(비교값)라고 합니다. 1을 보낼 때는 비교값 10을 사용하며, 10틱 * 0.0625 µs = 0.625 µs입니다.

오실로스코프

무언가를 점검하기 위해 오실로스코프를 사용했습니다(LED 코드를 몇 주 전에 작성해서 무엇이었는지는 잊었지만). 그래도 공유할 수 있는 사진은 몇 장 찍어 두었습니다.

처음 스코프를 설정했을 때, 신호를 확인하도록 제대로 트리거하기 전에는 약 5볼트의 delta voltage(전압 차이)를 보고하고 있었습니다. ‘큰일이다, 5볼트로 STM32를 태우고 있구나’라고 생각했습니다. 알고 보니 PWM 신호의 빠른 에지에서 발생한 ringing(링잉)이었습니다. 질량-스프링 시스템이 초과 진동하는 것과 비슷하게, 회로에는 커패시턴스와 인덕턴스라는 형태의 자체적인 ‘스프링’과 ‘질량’이 있습니다. 신호 에지가 매우 빠르면 전압이 오버슈트하고 안정되는 데 시간이 걸릴 수 있습니다. 이는 물리 현상이며 손상을 일으키지 않습니다.

사진에는 기본적으로 완벽한 800 kHz 신호가 보이며, 보드가 약 0.252 µs 동안 0 데이터 펄스를 보내는 모습도 포착되었습니다!

24비트 GBR

각 LED는 R, G, B에 대해 0–255 범위의 값을 사용합니다. 0–255를 표현하려면 8비트가 필요하므로, 각 색상마다 LED 하나에는 총 3*8=24비트가 필요합니다. LED 하나에 표시할 RGB 값을 알려 주려면 PWM 펄스 24개가 필요합니다. 48비트를 보내면 첫 번째 LED가 처음 24비트를 자신에게 사용하고, 나머지 24비트를 다음 LED로 전달합니다.

LED가 9개라면 한 번의 전체 기록에 필요한 양은 9 * 24 = 216비트, 즉 PWM 펄스 216개입니다. 800 kHz에서는 LED 9개 모두에 기록하는 데 216 / 800,000 ≈ 0.00027초가 걸립니다. 나쁘지 않죠! 처음 전송되는 비트는 최상위 비트이며, 이 LED들은 데이터를 G-R-B 순서로 요구합니다.

DMA

이 PWM 데이터는 정확한 타이밍으로 제어해야 하므로 DMA를 사용합니다. DMA는 정확히 Direct Memory Access(직접 메모리 액세스)를 뜻합니다. STM32 코어 CPU가 216개의 PWM 값을 정확한 시점마다 일일이 트리거하도록 하는 대신, 전체 데이터 시퀀스를 미리 배열에 구성하고 DMA가 타이머에 맞춰 해당 값들을 공급하도록 하여 CPU를 해방할 수 있습니다.

"sk6805.c"에서 DMA 전송을 다음과 같이 확인할 수 있습니다.

HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_1, pwm_data, PWM_BUF_LEN);

DMA는 pwm_data[]를 순서대로 처리하면서 다음 비교값을 TIM2에 계속 기록합니다.

DMA가 버퍼 끝에 도달하면 STM32는 PWM 완료 콜백 함수를 호출합니다. 더 많은 정보는 제 코드 노트에서 sk6805.h/.c를 둘 다 확인해 보겠습니다.

문제 하나

한 가지 의외로 필요한 사항은 다음과 같습니다.

while (!sk6805_done){}

일반적인 RGB 테스트에서는 며칠 동안 아무 문제도 보지 못했습니다. 빠르고 화려한 LED 애니메이션을 만든 뒤에야 문제가 있다는 것을 알아차렸습니다. 흔하지는 않았지만(약 10초마다 한 번 정도), 갑자기 LED 하나가 오작동하거나, 꺼지지 않거나, 극도로 밝아지거나, 잘못된 색상을 표시했습니다. 완벽했던 PWM과 DMA 어딘가에서 문제가 발생하고 있었습니다…

LED 코드가 새 프레임을 시작하기 전에 DMA 프레임 전송이 완전히 끝나지 않아서 비트가 서로 섞였던 것 같습니다… (아마도요.)

"while (!sk6805_done){}"을 호출하면 다음 전송 전에 DMA 전송이 완료되었는지 확인할 수 있습니다. 이 방법으로 오작동이 해결되었습니다. 하지만 (!sk6805_done){}은 블로킹 방식입니다. 그래도 블로킹 시간은 극도로 짧다고 꽤 확신합니다.




STEP 5. STM32 천체물리학 시간


STM32 천체물리학 시간


STM32 천체물리학 시간


STM32 천체물리학 시간


STM32 천체물리학 시간


잠깐, 천체물리학이 아니라고?

V1에는 궤도 위치와 속도를 저장한 다음 Newton의 F = ma를 사용해 미래 시점으로 전파하는 온보드 궤도역학이 많이 포함되어 있었습니다. V2에서 STM32는 전혀 다른 일을 합니다.

Newton의 법칙을 사용해 행성 궤도를 계산하지 않습니다. 대신 데이터 과학을 사용합니다. STM32는 계산 결과를 아주 작게 압축한 수학적 근사치를 받습니다. V2는 칩 위의 궤도역학이라기보다는 칩 위의 선형대수에 가깝습니다.

데이터 피팅

잡음이 섞인 데이터 점들이 많이 있고, 이 점들이 직선을 따른다고 생각해 봅시다.

y = mx + b

모든 x 값과 뒤죽박죽인 y 값은 알고 있지만, m (기울기) 또는 b (오프셋)는 모릅니다.

이를 행렬 문제로 다시 작성해 봅시다:

Mv ~ y

여기서 M에는 x 값이 들어 있고, v 에는 미지의 변수인 mb가 들어 있습니다. 컴퓨터는 mb 를 가정한 직선과 데이터 사이의 오차가 최소화되도록 찾습니다. 자세한 내용은 이 글에서 다룰 필요 이상으로 깊이 들어가지만, 제 생각에는 이것이 선형대수학의 보석 중 하나입니다.

결국 데이터 피팅은 데이터를 받아 최적 적합 함수를 제공합니다. 그런 다음 어떤 x 값(이 경우에는 시간)이든 입력하면 최적 적합 함수가 행성의 위치와 같은 결과를 출력합니다.

직선에서 체비셰프 다항식으로

이런 종류의 궤도 데이터에는 일반 다항식보다 체비셰프 다항식이 더 좋다고 말하는 천문학 자료를 많이 읽었습니다. NASA/JPL의 연구자들이 천체력에 체비셰프 다항식 근사를 사용하는 것은 유명합니다. 그래서 저도 이를 사용합니다 ̄\_(ツ)_/ ̄

체비셰프 다항식은 직선과 같은 아이디어를 사용합니다.

일반적인 다항식은 a0 + a1x + a2x² + a3x³ + ...와 같은 형태입니다.

앞서 살펴본 식 y = mx + b 은 사실 1차 다항식 a0x⁰ + a1x¹ 이며, a0 = b이고 a1 = m입니다.

x의 일반적인 거듭제곱을 사용하는 대신 체비셰프 다항식을 사용합니다.

체비셰프 피팅은 대략 다음과 같은 형태입니다: f(u) = c0T0(u) + c1T1(u) + c2T2(u) + ...

여기서 T 항들은 다항식 함수이고, u는 시간 구간이 -1에서 +1까지가 되도록 스케일링한 값입니다.

체비셰프 예시:

  1. T0(u) = 1
  2. T1(u) = u
  3. T2(u) = 2u² - 1
  4. T3(u) = 4u³ - 3u

등등입니다.

내가 실제로 체비셰프 다항식을 사용한 이유

여기서 잠깐 푸념을 하겠습니다.

“똑똑한 사람들은 체비셰프 피팅을 사용한다”는 말을 맹목적으로 받아들이는 대신, 스스로 납득하고 체비셰프 다항식에 대해 더 배워보고 싶었습니다.

그래서 체비셰프 대신 일반 다항식을 사용하도록 Python 코드를 전부 다시 작성해 보았고... 일반 다항식도 매우 잘 작동했습니다. 여전히 정확도가 매우 높았습니다. 이런 종류의 피팅에서 체비셰프 다항식이 더 나은 데에는 분명 근본적이고 심오한 수학적 이유가 있을 것이라고 확신하지만, 저는 스스로 납득하지 못했습니다.

체비셰프 표현을 받아들이게 된 이유는 정확도가 향상되어서가 아니라 계수 때문이었습니다.

정규화한 방향 벡터에서는 체비셰프 계수가 매우 작은 값으로 유지됩니다. 덕분에 중요한 메모리 절약 기법을 사용할 수 있었습니다.

천체력을 생성할 때 계수 범위를 ±1.15로 허용합니다. Python 천체력 생성기를 수정해 1.15 값을 초과하면 오류가 발생하며, 피팅 또는 허용 범위 중 하나를 조정해야 합니다.

그런 다음 이 작은 계수들을 부호 있는 16비트 정수로 매핑할 수 있습니다! 모든 행성의 계수를 32비트 또는 64비트 부동소수점 수로 저장하는 대신 int16 값을 저장합니다. 그러면 STM32가 다항식을 사용할 때 스케일링을 되돌립니다.

(근사) 계산에 따르면 이 int16 표현으로 31 KiB 이상을 절약할 수 있습니다!!! 64 KiB밖에 없지만 강력한 STM32의 메모리를 크게, 결정적으로 개선하는 효과입니다.

STM32 천체력 구축

모든 작업은 Python 스크립트가 수행합니다. 누가 그걸 C로 작성하고 싶겠습니까? 먼저 Skyfield를 사용해 JPL DE440s 천체력을 불러옵니다. 다음으로 Python 코드가 5년간의 천체력을 여러 구간으로 나눕니다. 이 설정은 원하는 대로 조정할 수 있습니다. 행성마다 서로 다른 다항식 차수와 세그먼트 길이(구간)를 사용합니다.

the Moon 은 빠르게 변하므로 5차 다항식을 12일 세그먼트 길이에 걸쳐 사용합니다. 해왕성은 훨씬, 훨씬, 훨씬 느리게 변하므로 160일 구간에 3차 다항식을 사용할 수 있습니다.

스크립트는 자동으로 master_ephemeris.c를 생성합니다. 이 C 파일에는 계수 배열과 함께 각 천체의 다항식 차수, 세그먼트 길이, 세그먼트 수를 STM32에 알려주는 소량의 데이터가 들어 있습니다.

또한 달력 조회 테이블도 생성하여 STM32가 GPS에서 받은 UTC 날짜를 천체력 계수가 생성된 이후의 초로 저렴하게 변환할 수 있도록 합니다. STM32에는 체비셰프 계수 생성 시각과 GPS로 수신한 UTC 시각 사이의 시간 차이(초)가 필요합니다. 이것 역시 큰 장점입니다. 천체력에 달력 조회 테이블을 저장하기 전에는 C로 윤년 함수를 작성하고, C로 월별 일수 함수를 작성하는 등의 작업을 직접 해야 했습니다. 이제 Python이 천체력이 생성된 이후 며칠이 지났는지 계산하도록 하면 됩니다.

모든 내용은 코드에 문서화되어 있으니, 궁금하다면 살펴보세요.

STM32가 실제로 하는 일

GPS가 현재 UTC 시각을 수신하면 STM32는 이를 천체력 시작 시점부터 경과한 초로 변환합니다. 그런 다음 각 행성에 대해 현재 시각이 포함된 체비셰프 세그먼트를 찾습니다. STM32는 체비셰프 급수를 계산하면서 int16 계수를 다시 부동소수점 값으로 변환합니다. X, Y, Z를 계산하고 나면 STM32는 행성의 방향을 매우, 매우 정확하게 구할 수 있습니다.

하지만... 우리는 빠르게 자전하는 공 위에 살고 있습니다.

그래서 천체물리학이 필요합니다

먼저 행성에 대해 저장된 데이터는 관성 기준 좌표계에 있습니다(체비셰프로 계산한 X Y Z). 우주 분야의 용어로는 ECI 프레임입니다. 이 좌표계는 우리가 행성을 자연스럽게 상상하는 방식에 더 가깝기 때문에 유용합니다. ECI에서 행성의 움직임은 느리고 매끄럽습니다.

하지만 우리는 아주 빠르게 자전하는 공 위에 살고 있습니다.

따라서 ECI 프레임에서 행성의 위치를 구한 뒤에는, 지구에서 실제로 보이는 모습에 맞도록 그 위치를 회전해야 합니다. 지구 고정 좌표계는 ECEF라고 합니다.

그렇다면 처음부터 ECEF 값을 저장하고 체비셰프로 계산하면 되지 않을까요?

그 값들은 빠르게 변합니다. 행성이 태양계를 이동하는 움직임과 지구의 일일 자전을 모두 포함하기 때문입니다. 이 추가적인 회전으로 위치 변화가 훨씬 빨라져, 낮은 차수의 다항식으로 데이터를 효율적으로 피팅하기가 더 어려워집니다.

따라서 C 코드의 천체력 생성 과정에서 Python은 ECI에서 지구 고정 좌표계로 변환하는 데 필요한 회전 행렬도 생성합니다.

지구는 약 86,164.0905초의 항성일을 기준으로 매우 일정하게 자전하므로 회전 행렬은 하나만 있으면 됩니다. 지구의 자전은 100년마다 몇 밀리초씩 느려집니다. 그러니 2126년에 이 글을 보게 된다면 항성일의 길이를 확인해 보세요. 하지만 우리 생애 동안은 문제없습니다.

최종 STM32 런타임

GPS가 UTC 시각을 수신한 후

  1. Chebyshev 구간 찾기
  2. X, Y, Z를 계산하여 ECI 방향을 구합니다
  3. 지구와 함께 회전시켜 ECEF 방향을 구합니다

그런 다음 GPS 위도와 경도가 지구상에서 현재 위치의 로컬 벡터를 구성합니다. 위도와 경도는 정의상 ECEF 프레임이며, 우리가 이 모든 수고를 들여 변환한 것과 동일한 ECEF 프레임입니다. 간단한 벡터 연산을 사용하면 행성의 ECEF 방향과 로컬 “위도·경도” 벡터로 행성의 고도를 구할 수 있습니다! 이는 행성을 보기 위해 수평선 위로 고개를 기울여야 하는 각도입니다.

양의 고도는 물체가 수학적 수평선 위에 있음을 의미합니다. 음의 고도는 물체가 수평선 아래에 있어 여러분의 위에 있지 않음을 의미합니다. 그리고 해당 LED가 켜집니다. Voilà. GPS Planet Overhead.

달…

달은 너무 가까이 있기 때문에 한 단계가 더 필요합니다.

과장해서 그린 제 그림을 보세요.

달이 이른바 topocentric parallax라는 현상의 영향을 받는다는 것을 잘 보여 주기를 바랍니다. 다른 사람들과 마찬가지로 저도 topocentric parallax 를 들어본 적이 전혀 없었습니다. 제 달 계산은 다른 어떤 행성보다 오차가 훨씬 컸습니다. 처음에는 문제가 다항식 계수나 회전 계산에 있다고 생각했지만, 달의 벡터도 다른 행성만큼 정확하다는 것을 쉽게 확인할 수 있었습니다. 그러다 진짜 문제를 발견했습니다. 달은 지구에 매우 가까워서 지구 지각의 어디에 서 있느냐에 따라 겉보기 위치가 달라질 수 있습니다. 다시 말해, topocentric parallax입니다.

이를 보정하기 위해 지구의 반지름과 달의 평균 거리를 사용하여 관측점을 지구 중심에서 지표면의 관측자 위치로 이동시킵니다. 이렇게 하면 달의 시차 오차가 보정되어 천정 결과의 정확도가 다시 99.9% 이상으로 올라갑니다. 이 내용은 아래의 c/.py 코드에도 문서화되어 있습니다.

다른 행성들은 너무 멀리 있기 때문에 이 보정이 필요하지 않습니다.




STEP 6. 정확도 및 보너스


제 아파트는 해수면 근처에 있지만, 회로의 근사값을 실제 값과 비교해 테스트하면 꾸준히 99.9% 이상을 얻습니다. 수평선 근처의 수만 개 지점을 테스트했을 때만 오차를 만들어 낼 수 있었습니다. 완성된 코드에는 문제 해결 과정이 포함되어 있지 않지만, 개발 중에는 Chebyshev 구간, u 값, 경과 시간(초), 행성 위치 등의 값을 UART를 통해 확인했습니다. STM32는 Python 코드와 항상 동일한 결과를 반환했습니다.

너무 정확해서 행성이 천정에 있는지만 표시하려고 모든 데이터를 단순히 “버리는” 것이 미안하게 느껴질 정도입니다.

제가 있는 곳에서 달이 떠오를 때 STM32는 매초 고도의 변화를 출력했습니다. 창문으로 달을 바라보면서 계산된 달의 고도가 초당 0.002도씩 올라가는 것을 말 그대로 지켜볼 수 있었습니다.

달이 수평선에서 떠오를 때 UART로 확인한 실제 테스트 데이터입니다.

moon alt=-0.028540
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:43.000
moon alt=-0.025908
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:43.000
moon alt=-0.025908
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:44.000
moon alt=-0.023287
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:44.000
moon alt=-0.023287
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:45.000
moon alt=-0.020639
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:45.000
moon alt=-0.020639
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:46.000
moon alt=-0.018009
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:46.000
moon alt=-0.018009
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:47.000
moon alt=-0.015369
GGA captured=1, valid_fix=1, fix_quality=1, sats=5, UTC=2026-08-19 17:58:47.000
moon alt=-0.015369

언젠가 이 해상도를 활용하는 다른 회로를 생각해 낼 수 있을지도 모르겠습니다.

보너스

OFF 버튼을 3초 동안 누르고 있으면 회로가 현재 위치의 위도와 경도를 표시합니다.

위도와 경도를 4진수, 즉 base-four 표현으로 표시합니다:

  1. 0 = OFF
  2. 1 = 빨간색
  3. 2 = 초록색
  4. 3 = 파란색

예를 들어 뉴욕시에 있다면 5초 동안 경도에 해당하는 B B B G R B가 표시되고, 그다음 위도에 해당하는 R B _ B G G _가 표시됩니다. 흰색 LED가 숫자의 끝을 나타냅니다.

직접 코드를 해독해 볼 수도 있지만, 이 회로가 위도와 경도를 알려 주기를 원한다면(약 1km 범위의 불확실성!), OFF 버튼을 3초 동안 누르고 있으면 됩니다.

RGB 위도와 경도를 표시하는 LED 사진은 보여 드리지 않겠습니다. 제 아파트 위치를 여러분이 알게 되는 것은 원하지 않으니까요 :)




STEP 7. V2와 V1 비교


  1. 정확도와 속도가 가장 큰 차이입니다. V1은 더 느렸고 블로킹이 발생할 수 있었습니다. V2는 훨씬 정확하고, 더 큰 ephemeris를 사용하며, 테스트도 훨씬 광범위하게 진행되었습니다.
  2. V2는 3V LS14250 대신 LiPo도 사용합니다. 솔직히 어느 배터리가 더 좋은지는 잘 모르겠습니다. 제 400 mAh LiPo는 공간을 덜 차지하지만 충전을 지원하기 위한 추가 부품이 필요합니다. LiPo는 시간이 지나면서 충전량도 감소합니다. LS14250은 보관 수명이 훌륭합니다. 실제로는 LS14250 쪽으로 다시 마음이 기우는 것 같습니다.
  3. V1은 WS2812B-2020 주소 지정 RGB를 사용했고, V2는 SK6812-EC20 RGB LED를 사용합니다. 둘 다 24비트 RGB급 주소 지정 LED이며, 제가 알기로 SK6812-EC20이 더 최신 제품이어서 이를 선택했습니다. 그런데 두 제품의 빛 확산 각도가 매우 달랐습니다. V1 LED는 확산 각도가 훨씬 커서 색상이 시각적으로 섞여 노란색, 보라색 등으로 보이기 쉽습니다. SK6812-EC20은 색상이 자연스럽게 혼합되기보다는 선명한 빨강, 초록, 파랑 점들이 서로 나란히 놓인 것처럼 보입니다. SK6812-EC20 LED를 사용하면 제 Venus 색상은 노란색으로 보이지 않습니다… 이 두 “2020” LED는 전기적 폼 팩터가 사실상 동일한데도 외관상 크게 다릅니다. 또 하나의 교훈을 얻었습니다. V3에서는 확실히 WS2812B-2020으로 돌아갈 것입니다.




STEP 8. 조립


스텐실을 기판 위에 맞춰 놓습니다. 구멍 위에 솔더 페이스트를 펴 바르고 스퀴지로 밀어 페이스트가 통과하도록 합니다. 스텐실이 기판에 밀착되어 있는지, 모든 부분이 깨끗한지 확인하세요.

그다음 모든 부품을 올바른 위치에 조심스럽게 배치합니다.

핫플레이트로 리플로우하기

제 작업 과정은 다음과 같습니다:

  1. 모든 부품과 솔더 페이스트가 올라간 기판을 핫플레이트가 꺼진 상태에서 올려놓습니다.
  2. 그런 다음 전원을 켜고 온도가 215°C에 도달할 때까지 기다립니다.
  3. 약 200°C가 되면 솔더가 녹기 시작하는 것을 볼 수 있습니다.
  4. 215°C에 도달하면 핫플레이트를 끕니다.
  5. 기판을 조심스럽게(저는 롱노즈 플라이어를 사용합니다) 냉각 표면(예: 쿠키 시트)으로 옮깁니다.

리플로우 후 세척

솔더 브리지, 즉 패드를 의도치 않게 연결하는 솔더 덩어리가 있는지 확인합니다. 발견했다면:

  1. 브리지에 플럭스를 조금 묻힙니다.
  2. 뜨거운 납땜 인두를 사용해 해당 부위를 부드럽게 가로질러 끌어 줍니다.
  3. 이소프로필 알코올과 Q-tip으로 플럭스를 닦아냅니다. 탄 플럭스는 끈적이고 황갈색으로 변합니다.

또한 납땜과 젖은 스펀지로 닦아 납땜 인두 팁을 깨끗하게 유지하세요.




STEP 9. STM32 CubeIDE 및 코드


STM32 CubeIDE 및 코드


STM32 CubeIDE 및 코드


STM32 CubeIDE 및 코드


STM32 CubeIDE 및 코드


사용된 GPIO에 대한 STM32CubeIDE 개요:

  1. UART TX/RX 핀: USART2를 115200 baud, 데이터 비트 8개, 패리티 없음, 스톱 비트 1개, TX/RX 모드로 구성합니다.
  2. 사용자 버튼 핀은 내부 pull-up/down 없이 일반 GPIO 입력으로 설정합니다.
  3. 전원 래치 핀을 push-pull GPIO 출력으로 설정하고, 풀 저항은 사용하지 않으며, 저속 출력으로 설정합니다. LOW로 시작합니다.
  4. SDA/SCL에서 I2C1을 300 kHz로 사용합니다. 7비트 주소 지정을 유지합니다.
  5. TIM2 Channel 1을 PWM 출력으로 사용합니다. 이 빌드에서는 주소 지정 가능 LED에 TIM2 CH1을 사용하고 DMA로 구동합니다. 코드에서는 Prescaler = 0, Period = 19를 사용합니다.
  6. TIM2 CH1 및 해당 DMA 인터럽트용 DMA입니다.
  7. TIM3을 인터럽트가 있는 기본 타이머로 사용합니다. GPIO 디바운싱에 타이머와 인터럽트를 사용하려는 시도입니다. 현재 설정은 Prescaler = 15999, Period = 9입니다. 버튼은 10ms마다 확인합니다. (Period+1) * (clock speed) / (Prescaler+1)은 10ms가 됩니다.
  8. TIM3의 주기 콜백은 OFF 버튼에 대한 플래그만 설정합니다.

minmea.c/.h를 다음에서 가져옵니다: https://github.com/kosma/minmea




STEP 10. 끝


제 프로젝트를 확인해 주셔서 감사합니다!


원문: https://www.instructables.com/Whats-Up-V2/

좋아요 0
게시판

회사명 공방유니언 주소 각 공방 주소 참조
사업자 등록번호 0000 대표 공방유니언 전화 019-60105846 팩스 0000 이메일 gongbangunion@gmail.com
통신판매업신고번호 0000 개인정보관리책임자 공방유니언
Copyright © 2017 공방유니언. All Rights Reserved.