|
|
민선생의 전자공방 ・ 2026. 8. 7. 9:42
| 피지컬 코딩 실습 · 인버티드 펜듈럼 프로젝트 15- 하 편 15- 下 편. 스윙업 성공! — 97°의 벽을 넘어 180°까지 가성비 부품의 한계를 알고리즘으로 넘은 이야기 · 에너지 제어 · 3콤보 임펄스 · 고각도 2차 타격 아두이노 프로젝트 · 민선생의 전자공방 |
| 이번 편에서 다루는 것 | 내용 |
| 스윙업의 의미 | 왜 인버티드 펜듈럼의 백미라 불리는가 |
| 하드웨어의 한계 | 스텝모터·MPU6050·LM가이드 — 가성비 부품의 제약 |
| 실패의 기록 | 97°에서 멈췄던 1차 도전과 그 원인 |
| 다섯 가지 발견 | 재도전을 가능하게 한 결정적 깨달음들 |
| 알고리즘 해설 | 3콤보 임펄스, 고각도 2차 타격, 활주로 분리 |
| 코드와 로그 | 실제 코드와 시리얼 로그를 읽는 법 |
▶ 1. 15- 上편에서 못다 한 이야기 — 스윙업 성공!!, 나이가 들어도.. 공학도에겐 이런게 기쁨입니다.^^
지난 15편에서 저는 이렇게 썼습니다. "결론부터 말씀드리면 — 실패했습니다." 일곱 번의 코드 개정과 두 차례의 하드웨어 보강에도 진자는 97°에서 더 올라가지 못했고, 저는 스윙업을 보류한 채 직립 PID 제어로 넘어갔습니다.
그런데 PID 제어를 시도하던 몇 주 동안 직립 제어를 위해 진자를 정밀하게 측정하고 센서를 뜯어보고 제어 루프를 파고드는 과정에서, 스윙업이 실패했던 진짜 원인을 하나 둘씩 유추할 수 있었습니다.
스윙업을 통해 직립한 펜듈럼을 PID제어로 유지하고자 했던 본래의 목표를 위해 다시 한번 스윙업을 시도해 봐야 겠다 생각했습니다.
그래서 다시 도전했습니다. 그리고 — 성공했습니다.(이번 방학 나만의 숙제는 성공입니다 ^^)
초기화를 마치고 아래쪽을 향하고 있는 진자를 슬쩍 흔들어주면 동작을 시작합니다.( 스윙업에 이어 PID직립제어 까지 완성하면 스위치를 달아 모든 동작의 시작과 끝을 스위치로 마무리 할 생각입니다. )
스윙업 단계어서는 펜들럼이 스스로 좌우로 흔들며 에너지를 쌓아 올려, 170도 이상을 세우면 성공으로 판정해 동작을 멈추고 중앙 위치로 이동하도록 만들었습니다. 성공률은 아직 40% 정도입니다. 열 번 시도하면 네 번쯤 올라갑니다. 완벽하진 않지만, 97°에서 멈추던 것을 생각하면 저에게는 충분히 만족스럽습니다.
스텝모터로 구동하여 높은 가속도는 얻을 수 없지만 적절한 시간에 에너지를 주입하고, 화분에 꽃 지지대 막대를 사용한 펜듈럼이 가벼워 에너지를 주입하기 어려워 끝단에 테입을 감아 무게 중심에 변화를 주어 스윙업 에너지는 얻을 수 있었습니다.
그렇지만 동작중에 LM가이드가 짧아 펜듈럼에 스윙업 에너지 주입 운동중 양쪽 리밋스위치를 터치해 중단하는 경우가 많이 있었습니다. 가이드 레일 길이가 조금더 길었다면 성공률은 훨씬더 높았을것 같습니다. 제가 펜듈럼 하드웨어를 만들때 제가 가지고 있던 LM가이드를 그냥 사용해서 생긴 제약입니다. ^^
스윙업으로 직립상태까지 도달하게 했으니 이제 다음단계어서 직립상태를 유지하도록 하면 인버티드 펜듈럼 프로젝트가 마무리 됩니다. ㅎㅎ
PID제어로 직립상태를 잘 유지하도록 하는것이 이제 마지막남은 과제입니다.
오늘은 스윙업에 대한 내용입니다.
스윙업 성공, 끝까지 봐주세요^^ (영상 32초)
모터 소리와 카트와 펜듈럼의 움직임을 같이 비교해가며 보시면 제어의 변주가 보입니다.
스윙업 성공2, 목표값 170도 도달후 중앙으로 이동.. ^^
▶ 2. 인버티드 펜듈럼에서 스윙업은 왜 중요한가?
인버티드 펜듈럼에서 스윙업(Swing-up)은 아래로 축 늘어진 진자를 카트의 움직임만으로 꼭대기까지 차올리는 기동입니다. 손으로 세워주고 시작하는 것과는 차원이 다릅니다.
■ 시스템이 '스스로 완결'된다는 의미
손으로 세워 시작하는 제어는 사람의 개입이 전제입니다. 반면 스윙업이 되면 전원을 넣고 버튼 하나만 누르면 기계가 알아서 진자를 세우고 균형을 잡습니다. 자동화의 관점에서 이 둘은 완전히 다른 시스템입니다.
■ 비선형 제어의 교과서
직립 제어는 수직 근처의 작은 각도만 다루므로 선형 근사가 통합니다. 그러나 스윙업은 0°에서 180°까지 전 영역을 다루므로 근사가 통하지 않습니다. 에너지 기반 제어, 위상 동기, 비최소위상 특성 같은 비선형 제어의 핵심 개념이 전부 등장합니다.
■ '적은 힘으로 큰 일을 하는' 공학의 정수
카트는 진자를 직접 들어올리지 못합니다. 오직 좌우로 밀 뿐이지요. 그런데 그 미는 타이밍을 진자의 리듬에 맞추면 조금씩 에너지가 쌓여 결국 꼭대기까지 올라갑니다. 그네를 구르는 아이가 자기 몸무게보다 훨씬 큰 위치에너지를 만들어내는 것과 같은 원리입니다.
| 💡 이번 프로젝트에서 스윙업이 특히 어려웠던 이유 보통 인버티드 펜듈럼은 DC 모터 + 엔코더 + 긴 레일로 만듭니다. 그런데 저는 '누구나 저렴하게 따라 할 수 있는' 것을 제1 목표로 삼았기에 스텝모터와 MPU-6050을 썼습니다. 제가 갖고 있던 부품을 최대한 활용하고, 처음 접하는 학생들도 저렴한 비용으로 시도해봤으면 하는 바람도 있었습니다. 처음 하드웨어를 공부하는 이를 위한 가성비 높은 저가 부품을 학습하다, 이 모든 하드웨어를 조합해 완성도 높은 프로젝트를 완성하는 커리큘럼을 보여주고 싶어서, 예제에 사용한 하드웨어로 만든 인버티드펜듈럼은 사실 그리 성능 좋은 하드웨어 사양과 조건은 아닙니다. — 그래서 더 값진 도전이었습니다. |
▶ 3. 우리 하드웨어의 제약 — 가성비의 대가
먼저 제 장치의 사양과 그것이 만드는 불리한 조건을 정리해 보겠습니다. 하드웨어의 제약들을 알고 나면, 왜 알고리즘이 그토록 정교해져야 했는지 이해가 됩니다.
| 부품 / 요소 | 선택 이유 | 그로 인한 제약 |
| 스텝모터 + DRV8825 | 저가·간단(STEP/DIR 두 핀), 위치를 공짜로 앎 | 속도↑ 토크 급감, 무리하면 탈조(스텝 이탈)로 위치 상실 |
| MPU-6050 | 수천 원대, 널리 쓰여 자료 풍부 | 자이로 편향·진동 정류, 가속도계가 카트 가속에 오염됨 |
| 타이밍 벨트 구동 | 저렴하고 조립 쉬움 | 탄성 흡수 — 명령 가속의 절반 정도만 전달(실측 47%) |
| LM 가이드 레일 | 직선성 좋고 저렴 | 마찰 저항 존재, 갖고 있던 부품이 유효 길이 336mm로 짧지만 사용하였음. |
| 진자 축 베어링 | 일반 소형 베어링 | 마찰 손실이 에너지 축적의 상한을 만듦 |
| 전원 20V | 노트북 어댑터 재활용 | 최고 속도 260mm/s가 물리적 상한 |
이 표에서 스윙업에 가장 치명적인 것은 첫 줄과 셋째 줄입니다. 스윙업은 짧고 강한 임펄스를 반복해야 하는데, 스텝모터는 급가속하면 탈조하고 벨트는 그 임펄스의 절반을 삼켜버립니다. 15편에서 제가 "소프트웨어의 한계가 아니라 시스템의 구조적 한계"라고 판단한 근거가 이것이었습니다.
| ✅ 그런데 결과는 달랐습니다 하드웨어는 단 하나도 바뀌지 않았습니다. 모터도, 센서도, 레일도, 벨트도 그대로입니다. 바뀐 것은 오직 알고리즘과 — 진자에 감은 테이프 한 롤뿐입니다. 제약을 없앤 것이 아니라, 제약을 '정확히 알고' 그 안에서 최선을 짜낸 것입니다. |
3-1. 하드웨어 구성 부품 목록 (브레드보드 IO 10편 참조)
인버티드 펜듈럼 하드웨
| 구분 | 부품 | 규격·설정 | 역할 |
| 제어기 | Arduino UNO R4 WiFi | ARM Cortex-M4 48MHz | 메인 제어 · Non-blocking 스케줄러 |
| 스텝모터 | NEMA 17 | 1.8°/step · 200 step/rev | 카트 구동 |
| 모터드라이버 | DRV8825 | 1/8 마이크로스텝 M0=H · M1=H · M2=L | 스텝 펄스 증폭 및 전류 제어 |
| 각도센서 | MPU-6050 | I2C 0x68 · Roll 값 사용 | 펜듈럼 각도·각속도 측정 |
| 디스플레이 | I2C LCD 20×4 | I2C 0x27 | 초기화·호밍 상태 표시 |
| 안전장치 | 리밋스위치 ×2 | 병렬 연결 → D3 평시 LOW · 터치 HIGH | 호밍 끝점 감지 + 비상정지 |
| 기구부 | LM 가이드 | 전체 420mm(유효 이동 길이 336mm) | 카트 직선 이동 레일 |
| 벨트 | GT2 타이밍 벨트 | 20T × 2mm 풀리 200 step = 40mm | 모터 → 카트 동력 전달 |
| 펜듈럼 막대 | 카본 낚싯대 | 750mm · 42g | 가볍고 긴 막대 → 제어 여유 확보 |
| 전원 | 12V DC | 아두이노 + 드라이버 공유 | 모터 토크 충분히 확보 |
✅ 부품 선정 핵심 이유 (★아두이노 R4 선택의 이유)
• Arduino UNO R4 WiFi 선택의 결정적 이유 (핵심 두뇌): 기존 아두이노 R3(ATmega328, 8비트, 16MHz)는 복잡한 소수점 연산을 소프트웨어로 흉내 내기 때문에 연산 속도가 매우 느립니다. 반면 R4는 32비트 ARM Cortex-M4(48MHz) 코어를 탑재하고 있으며, 특히 하드웨어적으로 소수점 연산을 가속하는 **FPU(부동소수점 연산 장치)**를 내장하고 있습니다. 덕분에 atan2 각도 계산, 칼만 필터, PID 제어의 복잡한 실수 연산을 수 마이크로초(μs) 이내에 처리하여 스텝모터의 초고속 제어(2,000Hz 이상)를 가능하게 해줍니다.
• 카본봉 (750mm / 42g): 길고 가벼울수록 쓰러지는 주파수가 낮아져 제어 반응 시간에 여유가 생깁니다. (무거운 쇠봉을 썼던 30년 전의 실패를 반면교사 삼아 선택!)
• DRV8825 1/8 마이크로스텝: 1 step = 0.025mm의 고분해능으로 덜컹거림 없는 미세 위치 제어가 가능합니다.
• LM 가이드: 마찰력이 낮고 직선 정밀도가 뛰어나 모터의 부하를 최소화하고 제어 안정성을 극대화합니다.
3-2. 제어부 배선
Inverted Pendulum(역진자) 브레드보드 회로 배선도(배선도 툴 Wokwi)
실제 제어부 — Arduino R4 · DRV8825 드라이버 · 브레드보드 배선
3-2. 핀배선 정리
| 아두이노 핀 | 연결 부품 | 신호·기능 | 비고 |
| D3 | 리밋스위치 ×2 | Homing 끝점 + 비상정지 | INPUT_PULLUP · 디바운싱 20ms |
| D4 | DRV8825 DIR | 모터 방향 제어 | CW=HIGH / CCW=LOW |
| D5 | DRV8825 STEP | 스텝 펄스 출력 | micros() Non-blocking |
| D6 | DRV8825 EN | 드라이버 Enable | Active LOW · 정지 시 HIGH |
| A4 (SDA) | MPU-6050 / LCD | I2C 데이터 | 스윙업 후 LCD 갱신 차단 |
| A5 (SCL) | MPU-6050 / LCD | I2C 클럭 | MPU-6050 전용화 |
▶ 4. 1차 도전이 97°에서 멈춘 이유
15-상편에서 정리했던 네 가지 제약을 요약하면 이렇습니다.
• 속도–토크 특성과 탈조 : 세게 치면 모터가 스텝을 놓치고 헛돌며, 그 진동이 자이로까지 오염시켰습니다.
• 가속의 딜레마 : 탈조를 막으려 가속을 제한하면 에너지 주입률도 함께 깎였습니다. 세게 치면 헛돌고, 살살 치면 안 쌓입니다.
• 벨트의 탄성 흡수 : 짧고 강한 임펄스일수록 벨트가 더 많이 삼켰습니다(실효 47%).
• 레일 336mm : 카트가 힘을 실어줄 무대가 좁았습니다.
이 넷을 종합하면 "1스윙당 주입 에너지"와 "마찰·벨트 손실"이 균형을 이루는 지점이 생기고, 그 균형점이 97°였다는 것이 당시의 결론이었습니다. 이 진단 자체는 지금도 옳다고 생각합니다. 다만 — 그때 저는 몇 가지를 **잘못 알고 있었습니다.**
▶ 5. 재도전을 가능하게 한 다섯 가지 발견
직립 PID 제어를 개발하면서 우연히 드러난 것들입니다. 하나하나가 스윙업 실패의 숨은 원인이었습니다.
■ 진자의 '진짜 길이'를 잘못 계산했습니다.
제어 설계를 위해 진자의 유효 길이를 실측해야 했습니다. 방법은 아주 간단합니다 — 진자를 아래로 늘어뜨리고 살짝 밀어 10회 왕복하는 시간을 재는 것입니다.
| 자유 진동 주기로 유효 길이 실측하기 |
| 단진자 주기 공식으로 유효 길이 역산 T = 2π √(L_eff / g) → L_eff = g · (T / 2π)² 측정 : 10회 왕복 17.0초 → T = 1.70초 계산 : L_eff = 9810 × (1.70 / 6.2832)² = 718 mm ※ 봉 길이는 750mm. 균일한 봉이라면 L_eff는 500mm(=2L/3)여야 합니다. 718mm가 나왔다는 것은 질량이 끝단에 크게 몰려 있다는 뜻입니다. |
저는 그때까지 진자를 '균일한 봉'으로 가정하고 계산하고 있었습니다. 실제와 40% 이상 차이 나는 값으로 모든 판단을 하고 있었던 것입니다. 펜듈럼이 에너지를 조금더 받게 하기위해 펜듈럼 막대 끝에 테입을 감아 무게를 조금 증가 올렸습니다. 그것이 주기를 늘려주고 에너지 주입에 도움이 되었습니다.
■ 에너지 계산식이 틀려 있었습니다
스윙업 알고리즘의 심장은 "지금 진자가 목표 에너지의 몇 퍼센트에 와 있는가"를 계산하는 부분입니다. 그런데 균일봉 가정으로 만든 식은 운동에너지를 약 3배나 과소평가하고 있었습니다.
| 에너지 계산식 정정 |
| // [기존 — 균일봉 가정, 꼭대기 = 1.0] float kinetic = wRad * wRad * PEND_LEN / (6.0f * GRAV); float potential = 0.5f * (1.0f - cosf(cfAngle * DEG2RAD)); // [정정 — 실측 L_eff 기반, 꼭대기 = 2.0] // 운동방정식 θ̈ = (g / L_eff)·sinθ 에서 유도한 정규화 에너지 // 하사점 정지 = 0.0 / 수평 = 1.0 / 꼭대기 = 2.0 float kinetic = L_EFF_M * wRad * wRad / (2.0f * GRAV); float potential = 1.0f - cosf(cfAngle * DEG2RAD); float E = kinetic + potential; // 검산 : 0°→0.000 90°→1.000 180°→2.000 ✓ |
이 식을 고치자 비로소 "지금 몇 퍼센트인가"가 정확해졌고, 타격 거리를 조절하는 로직도 제대로 작동하기 시작했습니다. 참고로 정정된 기준으로 보면 1차 도전의 97°는 56%, 최종 성공한 170°는 99%입니다.
■ 자이로가 포화되고 있었습니다
고각도로 올라갈수록 하사점 통과 속도가 빨라집니다. 그런데 MPU-6050의 자이로 측정 범위를 ±500°/s로 설정해 두었더군요.
| 진폭 | 하사점 각속도 | ±500dps 설정에서 |
| 130° | 384 °/s | 여유 23% |
| 150° | 409 °/s | 여유 18% |
| 170° | 422 °/s | 여유 16% — 순간 초과 위험 |
| 180° | 424 °/s | 포화 시 크로싱 판정 붕괴 |
포화되면 각속도가 실제보다 작게 읽히고, 하사점 크로싱 판정과 에너지 계산이 동시에 무너집니다. 설정 한 줄(±1000dps)을 바꾸는 것만으로 이 벽이 사라졌습니다.
■ 90°를 넘으면 밀어야 할 방향이 반대입니다
이것이 가장 큰 발견이었습니다. 에너지 주입 효율은 진자 각도의 코사인에 비례합니다.
| 각도별 에너지 주입 효율 |
| 에너지 주입률 ∝ ω · cos θ θ= 0° cos= +1.00 효율 100% ← 하사점, 최고 효율 θ= 60° cos= +0.50 효율 50% θ= 85° cos= +0.09 효율 9% ← 거의 무의미 θ= 95° cos= -0.09 ★부호 반전! 같은 방향으로 밀면 에너지를 빼앗음 θ=120° cos= -0.50 효율 50% (반대 방향으로) θ=158° cos= -0.93 효율 93% ← 두 번째 노른자! |
즉 하사점에서 한 번 밀고 끝내는 것이 아니라, 90°를 넘은 뒤에는 카트를 반대 방향으로 급반전시켜 두 번째 타격을 넣어야 한다는 뜻입니다. 이것을 코드로 구현한 것이 SW_PUSH2 상태입니다.
■ 고각도 로직이 한 번도 작동하지 않고 있었습니다
코드에는 "진자가 110°를 넘었으면 카트를 풀 스트로크로 밀어라"는 분기가 있었습니다. 그런데 이 판정이 하사점에서 호출되는 함수 안에 있었습니다.
| 조건이 영원히 성립하지 않던 버그 |
| // [버그] firePush()는 '하사점 크로싱' 순간에 호출된다 // 따라서 이 시점의 cfAngle은 항상 ±2° 부근! if (fabsf(cfAngle) >= FURUTA_GEAR) { // 110° 조건 → 영원히 거짓 dist = DIST_MAX_MM; } // [수정] '직전 극점 각도'를 기억해 두고 그것으로 판정 if (lastApexAngle >= FURUTA_GEAR) { // 이제 제대로 작동 dist = DIST_MAX_MM; } |
이런 종류의 버그는 컴파일도 잘 되고 동작도 멈추지 않기 때문에 발견이 어렵습니다. 로그에 "직전 극점 각도"를 찍어보고서야 그 값이 판정에 쓰이지 않고 있다는 걸 알았습니다.
| 💡 교훈 — 로그에 '판단의 근거'를 함께 찍으세요 저는 오랫동안 각도와 속도만 로그로 찍고 있었습니다. 그런데 결정적 단서는 언제나 '제어기가 무엇을 보고 판단했는가'에 있었습니다. 각 항의 기여도(P항·D항), 판정에 쓰인 값, 클램프 상태 — 이것들을 찍기 시작하자 몇 주를 헤매던 문제가 로그 한 줄로 드러나곤 했습니다. |
▶ 6. 알고리즘 해설 — 그네의 물리를 코드로
■ 기본 골격 : 그네 동기 4상태
아이가 그네를 구르는 동작을 그대로 상태 기계로 옮겼습니다.
| 상태 | 진자 상황 | 카트가 하는 일 |
| SW_WAIT_BOTTOM | 첫 하사점 통과 대기 | 정지, 크로싱 감시 |
| SW_PUSH | 하사점 → 상승 중 | 진행 반대 방향으로 밀기 (1차 타격) |
| SW_PUSH2 | 95° 이상 상승 중 | ★급반전 (2차 타격) — cos 부호가 뒤집혔으므로 |
| SW_WAIT_FALL | 극점 부근 | 정지 — 그네에서 발을 구르지 않는 순간 |
| SW_PRELOAD | 낙하 중 | 낙하 방향으로 동행 (다음 급정지의 원료 축적) |
■ 하사점 3콤보
이 시스템의 가장 강력한 임펄스는 '급정지'입니다. 출발 임펄스는 벨트가 절반을 삼키지만, 급정지는 벨트가 이미 팽팽한 상태라 거의 그대로 전달됩니다.
| 스윙업 한 사이클의 3단계 |
| 하사점 크로싱 순간, 1.5ms 안에 세 동작을 연속 실행 ① 급정지 : 도움닫기 속도 230mm/s에서 즉시 정지 (제1타) → 벨트가 팽팽 + 모터 홀딩토크 = 소프트닝 면역! ② 드웰 : 2ms 대기 (방향 전환 시 탈조 방지) ③ 역방향 출발 : 반대로 240mm/s 출발 (제2타) → 전체 Δv ≈ 470mm/s. 진자 회전은 1° 이내 = 사실상 같은 지점에 2연타 |
💡 "도립진자 스윙업의 핵심, '3콤보 임펄스'란?"
"무작정 힘만 준다고 진자가 꼭대기까지 올라갈까요?"
도립진자에서 가성비 스텝 모터의 제한된 힘만으로 진자를 180도 위로 튕겨 올리려면, 무식한 힘이 아니라 **'정확한 리듬과 타이밍'**이 필요합니다. 우리가 놀이터에서 그네를 탈 때를 떠올려 보세요. 그네가 내 쪽으로 떨어져 내려오는 정확한 타이밍에 맞춰 밀어주어야 그네가 훨씬 더 높이 올라가죠?
도립진자의 스윙업도 이와 똑같습니다. 이를 달성하기 위해 우리 코드는 다음 '3콤보 임펄스(충격량)'과정을 거칩니다.
•
1콤보 [도움닫기 (Preload)]:카트가 진자의 반대편 레일 끝으로 재빠르게 달려가며, 다음 타격을 위해 충분히 밀어줄 수 있는 '활주로'를 확보합니다. (마치 멀리뛰기를 위해 뒤로 물러나는 것과 같습니다.)
•
2콤보 [선제 제동 (Catch & Wait)]:진자가 중력을 받아 바닥(하사점)으로 맹렬하게 떨어지기 직전, 카트는 질주를 멈추고 브레이크를 밟아 단단히 버팁니다. 야구에서 포수가 공이 오기 전에 미리 미트를 대고 멈춰서 기다리는 것과 같습니다. (이 동작이 없으면 모터가 엇박자를 내며 엉켜버립니다.)
•
3콤보 [풀파워 타격 (Push)]:진자가 최하점(0도)을 휙! 하고 스치며 지나가는 찰나의 순간을 센서로 감지하여, 카트를 진자 진행 방향의 반대로 급가속합니다. 이때 모터가 낼 수 있는 최대의 운동 에너지(임펄스)가 진자에게 고스란히 전달됩니다.
이 1-2-3 콤보가 진자의 고유한 진동 주기에 맞춰 완벽한 타이밍으로 반복되면, 진자의 에너지는 눈덩이처럼 불어나 마침내 97도의 마의 벽을 뚫고 180도 꼭대기를 향해 솟구치게 됩니다!
■ 고각도 2차 타격 (SW_PUSH2)
발견 ④를 코드로 옮긴 부분입니다. 여기서 중요한 실패 경험이 하나 있습니다.
| 코스팅은 오히려 독이었다 |
| // [시도했다가 실패한 버전] // "85~120°는 효율이 0에 가까우니 레일을 아끼자" → 카트를 세웠더니 // 반전 시 Δv가 486 → 260 으로 반토막! 극점이 167° → 115°로 폭락했습니다. // 85~120°는 cosθ≈0 이라 카트가 달려도 손해가 없습니다. // → 세울 이유가 없고, 오히려 '달리는 채로' 반전해야 임펄스가 2배가 됩니다. if (minTimeOK && rising && absAngle >= SECOND_PUSH_ANGLE) { // 95° fireSecondPush(); // 달리는 상태 그대로 급반전 → Δv = 2v } |
또 한 가지 함정도 있었습니다. "효율이 좋은 120°에서 반전하자"고 문턱을 올렸더니, 진자가 2차 타격 없이는 115°까지밖에 못 올라가서 조건이 영영 성립하지 않았습니다. 닭과 달걀 문제였지요. 문턱은 '반드시 도달 가능한' 값이어야 합니다.
■ 활주로 분리 — 마지막 열쇠
성공률을 끌어올린 마지막 처방입니다. 하나의 상수(SOFT_LIMIT)가 서로 충돌하는 세 가지 역할을 겸하고 있었습니다.
| 하나의 상수가 겸하던 세 역할을 분리 |
| [문제] SOFT_LIMIT 하나가 세 역할을 동시에 맡고 있었다 ① 도움닫기 목표 → 짧아야 낙하 시간 안에 완주 가능 ② 2차 타격 활주로 → 길어야 결정적 구간(140~180°)까지 밀 수 있음 ③ 비상 정지 한계 → 레일 실측 ±169mm 대비 여유 ★ ①과 ②는 정면충돌 — 하나로 묶여 있으면 둘 다 만족 불가 [해결] 세 상수로 분리 #define PRELOAD_EDGE_MM 110.0f // ① 완주 보장 #define SECOND_PUSH_LIMIT_MM 145.0f // ② 결정적 구간 활주로 #define SOFT_LIMIT_MM 150.0f // ③ 비상 정지 |
■ 악순환 탈출
로그를 분석하다 흥미로운 현상을 발견했습니다. 극점이 높으면 낙하 시간이 길어져 도움닫기가 완주하고, 그러면 다음 타격이 완벽해져 극점이 더 높아집니다. 반대로 한 번 낮아지면 계속 낮아집니다. 성공률이 들쭉날쭉했던 이유가 이 선순환/악순환이었습니다.
| 악순환 자동 탈출 |
| // 직전 극점이 낮으면(짧은 낙하) 도움닫기 목표를 줄여 반드시 완주시킨다 // → 온전한 하사점 타격 → 극점 상승 → 스스로 선순환으로 복귀 curEdgeMM = (lastApexAngle < ESCAPE_APEX_DEG) ? ESCAPE_EDGE_MM // 90mm : PRELOAD_EDGE_MM; // 110mm |
▶ 7. 코드와 로그 읽기
■ 소스코드 : 스윙업 최종 코드 (v29)
첨부파일
swing_0806_succes_final_copy_20260807084340.ino
/* * ================================================================ * Inverted Pendulum — 스윙업 v29.1 "선제 제동 동기화 (Catch & Throw)" * * [핵심 제어 원리] * 1. 3분할 역할 분리: 하드웨어 한계(150mm), 2차 타격(145mm), 도움닫기(110mm) * 2. 선제 제동 동기화: 진자가 하사점에 진입하기 전 카트 속도를 줄여 탈조 원천 차단 * 3. 동적 상보 필터: 고속 회전 시 원심력으로 인한 센서 왜곡 방지 * ================================================================ */ #include <Wire.h> #include <LiquidCrystal_I2C.h> #include <FspTimer.h> ....... 소스코드는 길어서 ^^ 첨부한 파일을 봐주세요^^
소스 코드 플로우 차트
■ 시리얼 모니터 로그, 5회 시도 기록 (스윙업 성공 시리얼 로그 (SUCCESS 출력 포함)
전체 로그는 첨부파일의 자료를 참고해 주세요 ^^
첨부파일
스윙업 코드 로그자료.pdf
■ 로그를 읽는 법
시리얼 로그는 50ms마다 한 줄씩 나옵니다. 각 항목의 의미는 이렇습니다.
| 항목 | 의미 | 무엇을 봐야 하나 |
| ang | 진자 각도(°). 0=아래, 180=꼭대기 | 극점이 회를 거듭할수록 올라가는가 |
| gyro | 각속도(°/s) | 하사점에서 420 이상이면 꼭대기 도달 가능 수준 |
| E | 정규화 에너지 (꼭대기=2.0) | 2.0에 얼마나 근접하는가 = 진행률 |
| pos | 카트 위치(mm, 중앙=0) | 한쪽 끝에 붙어 정지하고 있지 않은가 |
| vSet / vAct | 명령 속도 / 실측 속도 | 둘이 크게 다르면 탈조 발생 중 |
| cart | 카트 상태(0=정지, ±1=타격, ±2=도움닫기) | 각 국면이 제대로 전환되는가 |
| 💡 성공/실패를 가르는 한 줄 — margin 극점에서의 에너지가 2.0을 넘느냐가 성공의 전부입니다. 성공한 회차 : 극점 직전 E 2.039 → 여유 +0.039 → 꼭대기 도달 실패한 회차 : 극점 직전 E 2.006 → 여유 +0.006 → 아깝게 되돌아옴 차이는 0.03에 불과했습니다. 그야말로 종이 한 장 차이입니다. |
▶ 8. 진화의 기록 — 97°에서 170°까지
| 단계 | 최고 도달각 | 진행률 | 결정적 변화 |
| 1차 도전 종료 | 97° | 56% | 3콤보 완성, 그러나 여기서 보류 선언 |
| 에너지식 정정 + 20V | 130° | 82% | L_eff 718mm 실측, 자이로 ±1000dps |
| 대칭 스트로크 | 144° | 90% | 좌우 스윙의 성적 편차 발견·보정 |
| 고각도 2차 타격 | 167° | 99% | 90° 이후 카트 급반전 도입 |
| ★활주로 분리 | 170° | 100% | SUCCESS! (성공률 약 40%) |
표에서 보시듯 하드웨어는 하나도 바뀌지 않았습니다. 진자에 테이프를 감아 무게중심을 끝단으로 옮긴 것이 유일한 물리적 변경이고, 그마저도 시간 여유를 20% 늘려준 보조적 역할이었습니다. 나머지는 전부 "정확히 알고 정확히 겨냥한" 알고리즘의 몫이었습니다.
▶ 9. 성공률 40%가 의미하는 것
스윙업을 10번 시도하면 아직 4번 정도밖에 성공하지 못합니다. 시도중 리밋 스위치를 터치해 멈추는 경우가 더 많습니다.
그런데 이 40%라는 숫자가 오히려 이 시스템의 상태를 정확히 말해줍니다. 실패하는 회차들도 이미 필요 에너지의 96~99%까지는 도달합니다. 즉 우리는 지금 "에너지가 부족한" 단계가 아니라 "매 사이클의 편차를 줄이는" 단계에 있습니다.
■ 남은 편차의 원인 세 가지
• 도움닫기 완주 여부 : 낙하 시간이 짧은 회차에는 카트가 목표 위치까지 못 가서 다음 타격이 약해집니다.
• 고속 구간의 손실 : 각속도의 제곱에 비례하는 손실(공기저항·점성마찰)이 큽니다. 진자를 굵게 감은 것이 공기저항을 늘렸을 가능성도 있습니다.
• 마지막 15~25°는 무동력 : 카트가 레일 끝에 닿아 정지한 뒤 남은 관성으로만 올라갑니다.
■ 다음에 시도해 볼 것
• 진자 회전축 베어링 점검·윤활 — 손실 곡선을 낮추는 것은 주입을 늘리는 것과 등가입니다.
• 진자를 매끄럽고 가늘게 — 무게는 유지하되 공기저항을 줄이는 방향(테이프 대신 끝단 추).
• 레일 연장 — 가장 확실한 투자이지만 책상 공간이 허락해야 합니다.
• 그리고 최종 목표 : 스윙업 성공 직후 직립 PID 제어로 자동 인계 — 진짜 완결된 시스템.
▶ 10. 마치며 — 실패의 기록이 성공의 재료가 되었습니다
15-상편을 쓸 때 저는 조금 많이 아쉬웠고 씁쓸했습니다. 성공기가 아니라 도전기라고, 무엇을 시도했고 왜 안 됐는지를 기록한다고 썼지요.
그렇지만 실패 원인을 정량적으로 남겨두었기 때문에, 나중에 새로운 사실을 알게 되었을 때 "아, 그때 그 판단의 전제가 틀렸구나"를 즉시 알아볼 수 있었습니다.
그리고 이번 과정에서 제가 가장 크게 배운 것은 이것입니다 — 문제는 대개 '힘이 부족해서'가 아니라 '내가 무언가를 잘못 알고 있어서' 풀리지 않습니다. 진자의 길이, 에너지의 정의, 센서의 측정 범위, 밀어야 할 방향의 부호. 이 넷은 전부 제가 확인 없이 '당연히 그럴 것'이라 여겼던 것들이었습니다.
이 연재는 AI와 함께 진행하고 있습니다. 로그를 들이밀며 왜 안 되는지 따져 묻고, 가설을 세우고, 계산으로 검증하고, 때로는 서로 다른 AI의 분석을 맞붙여 보기도 했습니다. 사람의 관찰("카트가 반대로 움직이는 것 같아")과 AI의 정량 분석(실효 47% 실측)이 만나 원인을 규명해 가는 이 과정이, 요즘 시대의 새로운 공부법이라는 생각을 다시 하게 됩니다.
더디지만 천천히 도전해 보겠습니다. (牛步千里).
97°에서 멈췄던 진자가 이제 꼭대기에 섭니다.
다음은 그 진자를 PID제어로 계속 세워두는 일입니다........ ^^
| ▶ 다음 편 예고 — 16편. PID 직립 제어 손으로 세운 진자를 카트가 이어받아 직립상태를 유지하도록 하자^^ • 상보필터로는 부족했다 — 칼만 필터 도입기 • PID → 상태 피드백 → 캐스케이드, 제어기의 진화 • 센서가 거짓말하는 순간 — 가속도계 겉보기 기울기와 자이로 진동 정류 • '멈추려면 먼저 가속하라' — 비최소위상 시스템의 역설 • 그리고 최종 목표 : 스윙업 → 직립 자동 인계와 유지 |
민선생의 전자공방 · 피지컬 코딩 실습 — 인버티드 펜듈럼 프로젝트 15-하편
태그#아두이노#인버티드펜듈럼#도립진자#스윙업#스텝모터#MPU6050#아두이노R4#자동제어#제어공학#피지컬컴퓨팅#아두이노프로젝트#PID제어#비선형제어#에너지제어#메이커프로젝트#define
