Skip to main content
Summary

바퀴 회전만으로는 알 수 없는 것

앞 장에서 엔코더는 바퀴가 얼마나 돌았는지 알려주는 센서라고 배웠습니다. 엔코더가 있으면 로봇이 앞으로 얼마나 갔는지, 바퀴 속도가 목표와 얼마나 다른지 계산할 수 있습니다. 그런데 바퀴 정보만으로는 알기 어려운 움직임이 있습니다.

로봇이 비탈길을 올라가고 있는지, 갑자기 한쪽으로 기울었는지, 제자리에서 얼마나 회전했는지, 넘어지기 직전인지 같은 정보입니다. 바퀴는 열심히 돌고 있지만 바닥이 미끄러우면 실제 로봇은 거의 움직이지 않을 수도 있습니다. 반대로 바퀴가 멈춰 있어도 누군가 로봇을 들어 올리면 로봇의 자세는 바뀝니다.

이때 필요한 센서가 IMU입니다. IMU는 Inertial Measurement Unit의 줄임말로, 우리말로는 관성 측정 장치라고 부릅니다. 이름은 조금 어렵지만 역할은 단순합니다. 로봇 몸체가 어떻게 기울고, 얼마나 빠르게 회전하고, 어떤 방향으로 움직임을 느끼는지 측정하는 센서입니다.

작은 IMU 모듈 하나 안에는 보통 다음 센서가 들어 있습니다.

  • 가속도계: X, Y, Z축 방향의 가속도를 측정합니다.

  • 자이로스코프: X, Y, Z축을 중심으로 도는 회전 속도, 즉 각속도를 측정합니다.

  • 자력계: 지구 자기장을 이용해 방향을 추정합니다. 모든 IMU에 들어 있지는 않습니다.

이 장에서는 로봇 제작에서 가장 자주 쓰이는 가속도계와 자이로스코프에 집중합니다. 이 두 센서만 이해해도 균형 로봇, 드론, 자세 추정 로봇으로 넘어갈 수 있는 중요한 발판을 마련할 수 있습니다.

IMU

IMU는 로봇의 균형 감각이다

사람은 눈을 감고 있어도 몸이 기울어졌는지 어느 정도 느낄 수 있습니다. 버스가 출발할 때 몸이 뒤로 쏠리는 것도 느끼고, 회전 의자에 앉아 돌면 몸이 회전하고 있다는 것도 느낍니다. 로봇에게 이런 감각을 주는 부품이 IMU입니다.

특히 IMU는 다음과 같은 로봇에서 핵심 역할을 합니다.

  • 두 바퀴로 서 있는 균형 로봇

  • 자세를 계속 유지해야 하는 드론

  • 울퉁불퉁한 바닥을 달리는 이동 로봇

  • 사람 손이나 로봇팔의 움직임을 추적하는 장치

  • 카메라 흔들림을 줄이는 짐벌 장치

물론 IMU 하나만 붙였다고 로봇이 갑자기 균형을 잡는 것은 아닙니다. IMU는 어디까지나 센서입니다. 센서는 현재 상태를 알려줄 뿐이고, 그 값을 이용해 모터를 어떻게 움직일지는 제어 알고리즘이 결정합니다. 이 장에서는 먼저 "IMU가 무엇을 측정하는가"와 "그 값을 어떻게 각도로 바꾸는가"를 익히겠습니다.

축을 먼저 이해하자

IMU를 사용하기 전에 반드시 확인해야 할 것이 있습니다. 바로 축 방향입니다. 대부분의 IMU는 X, Y, Z 세 축의 값을 출력합니다. 모듈 위에는 작은 글씨나 화살표로 X축과 Y축이 표시되어 있는 경우가 많고, Z축은 보통 모듈 표면에 수직인 방향입니다.

책상 위에 IMU 모듈을 평평하게 놓았다고 생각해 봅시다. 모듈의 앞쪽을 X축, 오른쪽을 Y축, 위쪽을 Z축이라고 정하면 로봇의 움직임을 다음처럼 나눌 수 있습니다.

  • X축을 중심으로 앞뒤로 기울어지는 움직임은 롤 또는 피치 중 하나로 해석됩니다.

  • Y축을 중심으로 고개를 숙이거나 드는 움직임도 롤 또는 피치로 해석됩니다.

  • Z축을 중심으로 제자리에서 좌우로 도는 움직임은 요라고 부릅니다.

여기서 롤, 피치, 요라는 용어가 나옵니다. 드론이나 항공기에서 자주 쓰는 말입니다. 로봇에서는 다음처럼 이해하면 충분합니다.

  • 롤 Roll: 좌우로 기우는 움직임입니다.

  • 피치 Pitch: 앞뒤로 숙이거나 젖히는 움직임입니다.

  • 요 Yaw: 위에서 내려다봤을 때 좌우로 도는 움직임입니다.

중요한 점은 모듈마다 축 방향이 다를 수 있다는 것입니다. 같은 코드를 사용해도 모듈을 로봇에 90도 돌려 붙이면 X축과 Y축의 의미가 바뀝니다. IMU를 붙인 뒤에는 손으로 로봇을 천천히 기울여 보면서 어떤 축 값이 변하는지 확인해야 합니다. 이 작은 확인 과정이 나중에 "왜 로봇이 반대로 쓰러지지?" 같은 큰 혼란을 막아 줍니다.

가속도계: 중력 방향을 읽는 센서

가속도계는 이름 그대로 가속도를 측정합니다. 가속도란 속도가 얼마나 빠르게 변하는지를 뜻합니다. 자동차가 출발할 때 몸이 뒤로 밀리는 느낌, 엘리베이터가 올라가기 시작할 때 몸이 살짝 무거워지는 느낌이 모두 가속도와 관련 있습니다.

그런데 로봇에서 가속도계가 특별히 중요한 이유는 하나 더 있습니다. 가속도계는 정지해 있을 때도 값을 출력합니다. 왜 그럴까요? 바로 중력 때문입니다.

책상 위에 가만히 놓인 IMU는 움직이지 않습니다. 하지만 지구는 계속 아래로 잡아당기고 있습니다. 가속도계 입장에서는 이 중력 방향이 하나의 기준이 됩니다. 그래서 로봇이 가만히 있을 때 가속도계 값을 보면 "어느 방향이 아래인가"를 추정할 수 있습니다.

예를 들어 모듈을 평평하게 놓았을 때 Z축이 위를 향하고 있다면, Z축에는 약 1g에 해당하는 값이 나타납니다. 여기서 g는 중력가속도를 뜻하며, 지구 표면에서는 약 9.8m/s²입니다. 실습에서는 보통 1g라는 단위로 생각하는 편이 쉽습니다.

모듈을 앞쪽으로 기울이면 중력 방향이 X축과 Z축에 나뉘어 나타납니다. 모듈을 옆으로 기울이면 Y축과 Z축 값이 함께 변합니다. 이 관계를 이용하면 로봇의 기울기를 계산할 수 있습니다.

가속도계로 기울기 계산하기

가속도계가 출력하는 X, Y, Z축 값을 각각 ax, ay, az라고 해 보겠습니다. 이 값이 g 단위로 변환되어 있다고 가정하면, 앞뒤 기울기와 좌우 기울기를 삼각함수로 계산할 수 있습니다.

가장 많이 쓰는 형태는 다음과 같습니다.

roll  = atan2(ay, az) * 180.0 / PI;
pitch = atan2(-ax, sqrt(ay * ay + az * az)) * 180.0 / PI;

여기서 atan2()는 각도를 계산하는 함수입니다. 그냥 atan()보다 더 안전하게 사분면을 고려해 각도를 구해 줍니다. 계산 결과는 라디안 단위이므로 180.0 / PI를 곱해 도 단위로 바꿉니다.

처음 보면 공식이 조금 낯설 수 있습니다. 하지만 핵심은 단순합니다. 가속도계는 중력 방향이 각 축에 얼마나 나뉘어 걸리는지 알려주고, 우리는 그 비율을 이용해 기울어진 각도를 계산합니다.

책상 위에 모듈을 놓고 천천히 앞뒤로 기울여 보면 pitch 값이 변합니다. 옆으로 기울이면 roll 값이 변합니다. 이 순간 IMU는 마치 작은 수평계처럼 동작합니다.

움직이는 순간 가속도계가 흔들리는 이유

가속도계만 있으면 기울기를 완벽하게 알 수 있을 것 같지만, 실제 로봇에서는 그렇지 않습니다. 로봇은 가만히 서 있기만 하지 않습니다. 출발하고, 멈추고, 충격을 받고, 바퀴 진동을 그대로 몸체에 전달합니다.

문제는 가속도계가 중력만 측정하는 센서가 아니라는 점입니다. 이름 그대로 모든 가속도를 측정합니다. 로봇이 앞으로 급출발하면 X축 방향 가속도가 생깁니다. 로봇이 바닥 턱을 넘으면 순간적으로 Z축 값이 튑니다. 이때 가속도계는 중력과 실제 움직임에 의한 가속도를 구분하지 못합니다.

예를 들어 균형 로봇이 앞으로 쓰러지는 중인지, 아니면 앞으로 빠르게 출발하는 중인지 가속도계 값만 보고는 헷갈릴 수 있습니다. 두 경우 모두 특정 축의 가속도 값이 변하기 때문입니다.

그래서 가속도계로 계산한 각도는 정지 상태나 천천히 움직일 때는 꽤 믿을 만하지만, 빠르게 움직이거나 진동이 심할 때는 노이즈가 커집니다. 여기서 노이즈란 우리가 알고 싶은 실제 값과 상관없이 섞여 들어오는 흔들림이나 잡음을 뜻합니다.

로봇에서 가속도계 노이즈가 커지는 대표적인 상황은 다음과 같습니다.

  • 모터가 빠르게 회전하면서 차체에 진동이 전달될 때

  • 로봇이 급출발하거나 급정지할 때

  • 바닥의 턱이나 요철을 지나며 충격을 받을 때

  • 배선이 느슨해 센서 모듈이 미세하게 흔들릴 때

따라서 가속도계는 "장기적으로는 기울기 기준을 잘 잡지만, 순간적인 움직임에는 흔들릴 수 있는 센서"라고 생각하면 됩니다.

자이로스코프: 얼마나 빠르게 돌고 있는가

가속도계가 중력 방향을 이용해 기울기를 추정한다면, 자이로스코프는 회전 움직임 자체를 측정합니다. 자이로스코프가 출력하는 값은 각도가 아니라 각속도입니다. 각속도는 1초 동안 몇 도나 회전하는지를 나타냅니다.

예를 들어 Z축 자이로 값이 90 deg/s라면, 로봇이 Z축을 중심으로 1초에 90도 회전하고 있다는 뜻입니다. 이 상태가 2초 동안 계속된다면 로봇은 약 180도 회전했을 것입니다.

자이로스코프의 축 값은 보통 다음처럼 표현합니다.

  • gx: X축을 중심으로 회전하는 속도

  • gy: Y축을 중심으로 회전하는 속도

  • gz: Z축을 중심으로 회전하는 속도

단위는 흔히 deg/s, 즉 초당 몇 도입니다. 더 엄밀한 계산에서는 라디안/초를 쓰기도 하지만, 아두이노나 ESP32 실습에서는 도/초가 직관적입니다.

자이로스코프는 빠른 움직임을 잘 잡아냅니다. 로봇이 갑자기 기울기 시작하면 자이로 값이 즉시 반응합니다. 그래서 균형 로봇이나 드론처럼 빠른 자세 변화에 반응해야 하는 장치에서는 자이로가 매우 중요합니다.

각속도를 적분하면 각도가 된다

자이로스코프는 각도를 직접 주지 않습니다. 대신 "지금 얼마나 빠르게 돌고 있는지"를 알려줍니다. 그렇다면 각도는 어떻게 구할까요? 시간에 따라 각속도를 계속 더하면 됩니다.

예를 들어 로봇이 X축을 중심으로 1초 동안 30 deg/s로 회전했다면 각도는 약 30도 변합니다. 0.1초 동안 30 deg/s로 회전했다면 각도 변화는 3도입니다.

코드에서는 다음처럼 계산합니다.

angleX = angleX + gyroX * dt;

여기서 gyroX는 X축 각속도이고, dt는 이전 측정 이후 지난 시간입니다. 단위는 초입니다. gyroX가 deg/s이고 dt가 초라면, 둘을 곱한 값은 도가 됩니다.

실제 코드에서는 루프가 반복될 때마다 시간을 측정하고, 그 시간 간격만큼 각도 변화를 누적합니다.

unsigned long now = micros();
float dt = (now - prevTime) / 1000000.0;
prevTime = now;

angleX += gyroX * dt;
angleY += gyroY * dt;
angleZ += gyroZ * dt;

이 방식은 직관적이고 빠릅니다. 로봇이 짧은 시간 동안 어떻게 회전했는지 매우 민감하게 따라갑니다. 하지만 여기에도 함정이 있습니다.

자이로 드리프트: 조금씩 틀어지는 시계

자이로스코프에는 드리프트라는 문제가 있습니다. 드리프트는 센서 값이 시간이 지날수록 조금씩 한쪽으로 밀리는 현상을 말합니다.

이상적인 자이로스코프라면 로봇이 완전히 가만히 있을 때 각속도는 0이어야 합니다. 하지만 실제 센서는 그렇지 않습니다. 책상 위에 가만히 올려놓아도 0.3 deg/s, -0.8 deg/s처럼 작은 값이 나올 수 있습니다. 처음에는 별것 아닌 것처럼 보입니다. 그러나 적분에서는 이 작은 오차가 계속 누적됩니다.

예를 들어 가만히 있는데도 자이로가 0.5 deg/s라고 출력한다고 해 봅시다. 1초 뒤에는 각도가 0.5도 틀어집니다. 10초 뒤에는 5도, 1분 뒤에는 30도까지 밀릴 수 있습니다. 로봇은 움직이지 않았는데 코드 속 각도는 계속 돌아가고 있는 셈입니다.

이것이 자이로 드리프트입니다. 자이로는 빠른 움직임에는 강하지만, 시간이 길어질수록 기준이 조금씩 흐려집니다.

그래서 자이로 값을 사용하기 전에는 보통 영점 보정을 합니다. 로봇을 가만히 둔 상태에서 자이로 값을 여러 번 읽고 평균을 냅니다. 이 평균값을 오프셋이라고 부르며, 이후 측정값에서 빼 줍니다.

gyroX = rawGyroX / gyroScale - gyroOffsetX;
gyroY = rawGyroY / gyroScale - gyroOffsetY;
gyroZ = rawGyroZ / gyroScale - gyroOffsetZ;

영점 보정을 해도 드리프트가 완전히 사라지지는 않습니다. 온도 변화, 센서 품질, 전원 노이즈, 진동에 따라 오차가 조금씩 남습니다. 따라서 자이로만으로 장시간 정확한 각도를 유지하기는 어렵습니다.

가속도계와 자이로스코프는 서로 부족한 부분을 채운다

이제 두 센서의 성격을 비교해 봅시다.

가속도계는 중력 방향을 이용하므로 장기적으로 기울기 기준을 잡는 데 좋습니다. 시간이 지나도 계속 중력이라는 기준을 바라보고 있기 때문입니다. 하지만 로봇이 움직이거나 진동하면 값이 많이 흔들립니다.

자이로스코프는 빠른 회전 변화에 잘 반응합니다. 순간적으로 로봇이 얼마나 기울어지는지 부드럽게 따라갑니다. 하지만 시간이 지나면 드리프트 때문에 각도가 조금씩 틀어집니다.

이 둘은 단점이 서로 다릅니다. 그래서 함께 쓰면 좋습니다. 빠른 변화는 자이로를 믿고, 장기적인 기준은 가속도계를 믿는 방식입니다. 이 아이디어를 가장 단순하게 구현한 것이 상보 필터입니다.

상보 필터: 빠른 감각과 느린 기준을 섞기

상보 필터는 어렵게 들리지만 생각은 매우 간단합니다. 자이로로 계산한 각도와 가속도계로 계산한 각도를 적당한 비율로 섞습니다.

대표적인 식은 다음과 같습니다.

angle = 0.98 * (angle + gyroRate * dt) + 0.02 * accelAngle;

여기서 angle + gyroRate * dt는 자이로 적분으로 예측한 각도입니다. accelAngle은 가속도계로 계산한 각도입니다. 0.98과 0.02는 두 값을 얼마나 믿을지 정하는 비율입니다.

이 식을 말로 풀면 이렇습니다.

"각도의 빠른 변화는 자이로 값을 거의 그대로 따라가되, 시간이 지나며 조금씩 가속도계의 기울기 기준으로 되돌아오게 하자."

왜 자이로에 0.98처럼 큰 값을 줄까요? 자이로는 짧은 시간 동안 움직임을 부드럽게 잘 따라가기 때문입니다. 반대로 가속도계는 순간 노이즈가 크므로 한 번에 많이 반영하지 않고 0.02처럼 조금만 섞습니다. 그러면 로봇이 급하게 움직일 때는 자이로 덕분에 각도가 부드럽게 변하고, 시간이 지나면 가속도계 덕분에 드리프트가 서서히 보정됩니다.

비율은 반드시 0.98과 0.02일 필요는 없습니다. 로봇의 진동, 센서 품질, 제어 주기에 따라 조정할 수 있습니다. 진동이 심한 로봇에서는 가속도계 비중을 너무 크게 두면 각도가 출렁입니다. 반대로 자이로 비중을 지나치게 크게 두면 드리프트 보정이 느려집니다.

실습을 시작할 때는 0.98 / 0.02 또는 0.95 / 0.05 정도로 두고, 시리얼 모니터로 각도 변화를 보면서 조정하면 좋습니다.

I2C IMU 모듈 연결하기

실습에서는 많이 쓰이는 I2C 기반 IMU 모듈을 기준으로 생각해 보겠습니다. 대표적으로 MPU-6050 계열 모듈은 가속도계와 자이로스코프를 함께 담고 있으며, 아두이노와 ESP32에서 쉽게 사용할 수 있습니다.

I2C는 두 개의 신호선으로 여러 장치를 연결할 수 있는 통신 방식입니다. 자세한 구조는 뒤의 I2C 장에서 더 다루겠지만, 지금은 다음 네 선만 기억하면 됩니다.

  • VCC: 센서 전원

  • GND: 기준 전압, 반드시 보드 GND와 연결

  • SDA: 데이터가 오가는 선

  • SCL: 클록 신호선

아두이노 Uno와 ESP32에서 흔히 쓰는 연결 예시는 다음과 같습니다.

IMU 모듈Arduino UnoESP32 예시
VCC5V 또는 3.3V3.3V
GNDGNDGND
SDAA4GPIO 21
SCLA5GPIO 22

여기서 전원 전압은 모듈에 따라 다릅니다. 센서 칩 자체는 3.3V 기반인 경우가 많지만, 일부 모듈에는 전압 변환 회로가 있어 5V 입력을 받을 수 있습니다. 모듈 설명을 확인할 수 없다면 3.3V로 연결하는 편이 안전합니다.

또 하나 중요한 점은 GND입니다. IMU 모듈의 GND와 제어 보드의 GND는 반드시 연결되어야 합니다. 신호선 SDA와 SCL은 전압의 높고 낮음을 기준으로 데이터를 주고받는데, 기준이 되는 GND가 서로 다르면 통신이 제대로 되지 않습니다. 앞에서 전원 장과 모터 드라이버 장에서 반복했던 공통 GND 원칙이 센서 통신에서도 그대로 적용됩니다.

원시값은 바로 각도가 아니다

IMU에서 읽어오는 값은 처음부터 30도, 1g, 90deg/s처럼 보기 좋은 값으로 나오지 않는 경우가 많습니다. 센서 내부의 아날로그 측정값을 디지털 숫자로 바꾼 원시값이 나옵니다.

예를 들어 가속도계 X축 원시값이 16384라고 해 봅시다. 이 숫자만 보면 무엇을 뜻하는지 알기 어렵습니다. 센서 설정이 ±2g 범위라면, 어떤 IMU에서는 1g가 약 16384에 해당합니다. 그러면 원시값을 16384로 나누어 g 단위로 바꿀 수 있습니다.

ax = rawAx / 16384.0;
ay = rawAy / 16384.0;
az = rawAz / 16384.0;

자이로스코프도 마찬가지입니다. 예를 들어 ±250deg/s 범위에서 1deg/s가 약 131이라는 원시값에 해당한다면 다음처럼 변환합니다.

gx = rawGx / 131.0;
gy = rawGy / 131.0;
gz = rawGz / 131.0;

이 변환 계수는 센서 종류와 설정 범위에 따라 달라집니다. ±2g, ±4g, ±8g처럼 가속도 측정 범위를 바꾸면 1g에 해당하는 원시값도 달라집니다. 자이로도 ±250deg/s, ±500deg/s, ±1000deg/s처럼 범위를 바꾸면 변환 계수가 달라집니다.

처음 실습할 때는 가장 민감한 범위인 ±2g, ±250deg/s 정도로 시작하는 것이 좋습니다. 작은 기울기와 회전도 비교적 세밀하게 볼 수 있기 때문입니다. 다만 드론이나 빠르게 회전하는 장치에서는 더 넓은 범위가 필요할 수 있습니다.

IMU 원시값 읽기 실습 흐름

아래 코드는 아두이노 환경에서 I2C로 MPU-6050 계열 IMU의 원시값을 읽는 기본 흐름을 보여 줍니다. 라이브러리를 쓰면 더 간단하게 읽을 수 있지만, 여기서는 센서에서 값이 어떤 순서로 나오는지 감을 잡기 위해 Wire를 사용합니다.

#include <Wire.h>

const int MPU_ADDR = 0x68;

int16_t rawAx, rawAy, rawAz;
int16_t rawGx, rawGy, rawGz;

void setup() {
  Serial.begin(115200);
  Wire.begin();

  // 전원 관리 레지스터를 설정해 센서를 깨운다.
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x6B);
  Wire.write(0);
  Wire.endTransmission(true);

  Serial.println("IMU start");
}

void loop() {
  readIMU();

  Serial.print("ACC ");
  Serial.print(rawAx); Serial.print(" ");
  Serial.print(rawAy); Serial.print(" ");
  Serial.print(rawAz); Serial.print(" | GYRO ");
  Serial.print(rawGx); Serial.print(" ");
  Serial.print(rawGy); Serial.print(" ");
  Serial.println(rawGz);

  delay(100);
}

void readIMU() {
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x3B); // 가속도 X축 상위 바이트부터 읽기 시작
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_ADDR, 14, true);

  rawAx = Wire.read() << 8 | Wire.read();
  rawAy = Wire.read() << 8 | Wire.read();
  rawAz = Wire.read() << 8 | Wire.read();

  // 온도 값 2바이트는 이번 실습에서는 사용하지 않는다.
  Wire.read(); Wire.read();

  rawGx = Wire.read() << 8 | Wire.read();
  rawGy = Wire.read() << 8 | Wire.read();
  rawGz = Wire.read() << 8 | Wire.read();
}

코드를 업로드한 뒤 시리얼 모니터를 열고 IMU를 손으로 천천히 기울여 보세요. 어떤 축의 값이 크게 변하는지 확인하는 것이 첫 번째 목표입니다. 모듈을 책상 위에 가만히 두면 한 축에는 중력 때문에 큰 값이 나타나고, 나머지 축은 0 근처에 있을 것입니다. 완전히 0이 아니어도 놀라지 않아도 됩니다. 센서에는 항상 약간의 오차와 노이즈가 있습니다.

ESP32를 사용할 때는 보드 환경에 따라 Wire.begin(21, 22);처럼 SDA와 SCL 핀을 지정할 수 있습니다.

Wire.begin(21, 22); // ESP32: SDA=21, SCL=22 예시

원시값을 기울기와 각속도로 바꾸기

이제 원시값을 실제 단위로 변환해 보겠습니다. 아래 예시는 가속도 범위를 ±2g, 자이로 범위를 ±250deg/s로 사용한다고 가정합니다.

float ax, ay, az;
float gx, gy, gz;
float accRoll, accPitch;

void loop() {
  readIMU();

  ax = rawAx / 16384.0;
  ay = rawAy / 16384.0;
  az = rawAz / 16384.0;

  gx = rawGx / 131.0;
  gy = rawGy / 131.0;
  gz = rawGz / 131.0;

  accRoll  = atan2(ay, az) * 180.0 / PI;
  accPitch = atan2(-ax, sqrt(ay * ay + az * az)) * 180.0 / PI;

  Serial.print("roll=");
  Serial.print(accRoll);
  Serial.print(" pitch=");
  Serial.print(accPitch);
  Serial.print(" gyroZ=");
  Serial.println(gz);

  delay(50);
}

이 코드를 실행하면 가속도계로 계산한 roll과 pitch를 볼 수 있습니다. 모듈을 앞뒤로 움직이면 pitch가, 좌우로 기울이면 roll이 변할 것입니다. gyroZ는 모듈을 책상 위에서 좌우로 돌릴 때 크게 변합니다.

여기서 중요한 실습 포인트가 있습니다. IMU를 천천히 기울일 때와 빠르게 흔들 때 값을 비교해 보세요. 천천히 기울이면 가속도계 각도가 비교적 안정적으로 따라오지만, 빠르게 흔들면 각도 값이 출렁이는 것을 볼 수 있습니다. 이것이 앞에서 말한 가속도계 노이즈입니다.

자이로 영점 보정하기

자이로 값을 적분하기 전에 오프셋을 구해 보겠습니다. 센서를 가만히 둔 상태에서 여러 번 읽고 평균을 내면 됩니다. 이때 로봇을 손으로 잡고 있으면 미세하게 흔들릴 수 있으므로, 가능하면 책상 위에 놓고 기다리는 것이 좋습니다.

float gyroOffsetX = 0;
float gyroOffsetY = 0;
float gyroOffsetZ = 0;

void calibrateGyro() {
  const int samples = 1000;
  long sumX = 0;
  long sumY = 0;
  long sumZ = 0;

  for (int i = 0; i < samples; i++) {
    readIMU();
    sumX += rawGx;
    sumY += rawGy;
    sumZ += rawGz;
    delay(2);
  }

  gyroOffsetX = (sumX / (float)samples) / 131.0;
  gyroOffsetY = (sumY / (float)samples) / 131.0;
  gyroOffsetZ = (sumZ / (float)samples) / 131.0;
}

setup()에서 IMU를 초기화한 뒤 calibrateGyro()를 한 번 호출합니다.

void setup() {
  Serial.begin(115200);
  Wire.begin();

  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x6B);
  Wire.write(0);
  Wire.endTransmission(true);

  Serial.println("Keep IMU still...");
  calibrateGyro();
  Serial.println("Calibration done");
}

이제 자이로 값을 사용할 때 오프셋을 빼 줍니다.

gx = rawGx / 131.0 - gyroOffsetX;
gy = rawGy / 131.0 - gyroOffsetY;
gz = rawGz / 131.0 - gyroOffsetZ;

이 과정을 거치면 정지 상태에서 자이로 값이 0에 더 가까워집니다. 균형 로봇처럼 각도 변화에 민감한 장치에서는 이 보정 과정이 매우 중요합니다.

상보 필터로 각도 추정하기

이제 가속도계와 자이로스코프를 섞어 보겠습니다. 아래 코드는 pitch 각도 하나를 상보 필터로 추정하는 예입니다. 실제 균형 로봇에서는 보통 앞뒤 기울기인 pitch가 특히 중요합니다.

float pitch = 0;
unsigned long prevTime = 0;

void setup() {
  Serial.begin(115200);
  Wire.begin();

  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x6B);
  Wire.write(0);
  Wire.endTransmission(true);

  calibrateGyro();
  prevTime = micros();
}

void loop() {
  readIMU();

  unsigned long now = micros();
  float dt = (now - prevTime) / 1000000.0;
  prevTime = now;

  ax = rawAx / 16384.0;
  ay = rawAy / 16384.0;
  az = rawAz / 16384.0;

  gy = rawGy / 131.0 - gyroOffsetY;

  float accPitch = atan2(-ax, sqrt(ay * ay + az * az)) * 180.0 / PI;

  pitch = 0.98 * (pitch + gy * dt) + 0.02 * accPitch;

  Serial.print("accPitch=");
  Serial.print(accPitch);
  Serial.print(" filteredPitch=");
  Serial.println(pitch);

  delay(5);
}

이 코드를 실행하면 accPitch와 filteredPitch를 함께 볼 수 있습니다. accPitch는 가속도계만으로 계산한 각도이고, filteredPitch는 상보 필터를 거친 각도입니다. 모듈을 살짝 흔들어 보면 가속도계 각도는 더 요동치고, 필터 각도는 비교적 부드럽게 따라오는 것을 볼 수 있습니다.

다만 축 방향은 모듈 장착 방향에 따라 달라집니다. 위 코드에서는 Y축 자이로 gy를 pitch 변화에 사용했지만, 여러분의 모듈 방향에서는 gx를 써야 할 수도 있습니다. 손으로 앞뒤로 기울였을 때 어떤 자이로 축이 크게 변하는지 확인한 뒤 코드의 축을 맞추세요.

샘플링 주기와 dt가 중요한 이유

IMU를 다룰 때 dt는 매우 중요합니다. 자이로 각속도에 dt를 곱해 각도 변화를 계산하기 때문입니다. 만약 실제로는 0.01초마다 읽고 있는데 코드에서 0.02초라고 생각하면 각도 변화가 두 배로 계산됩니다.

그래서 delay() 값만 믿고 시간을 가정하기보다 micros()나 millis()로 실제 지난 시간을 재는 편이 좋습니다. 특히 시리얼 출력이 많거나 다른 센서 읽기, 모터 제어가 함께 들어가면 루프 시간이 일정하지 않을 수 있습니다.

균형 로봇이나 드론처럼 빠르게 반응해야 하는 시스템에서는 IMU 읽기 주기가 너무 느리면 제어가 불안정해집니다. 사람이 몸이 기우는 것을 1초 뒤에야 알아차린다면 균형을 잡기 어렵듯이, 로봇도 자세 정보를 늦게 받으면 이미 많이 기울어진 뒤에야 모터를 움직이게 됩니다.

처음 실습에서는 50Hz에서 100Hz 정도, 즉 1초에 50번에서 100번 정도 각도를 계산해 보는 것으로 충분합니다. 더 빠른 제어가 필요하면 200Hz 이상으로 올릴 수도 있지만, 그만큼 코드 구조와 통신 속도, 필터 계산도 신경 써야 합니다.

IMU 값을 믿기 전에 확인할 것들

IMU는 작은 모듈이지만 생각보다 예민합니다. 값이 이상하게 나온다고 바로 센서가 고장났다고 판단하기 전에 몇 가지를 확인해 보세요.

  • 전원 전압이 모듈 규격에 맞는지 확인합니다.

  • SDA와 SCL이 서로 바뀌지 않았는지 확인합니다.

  • 보드와 IMU의 GND가 연결되어 있는지 확인합니다.

  • I2C 주소가 코드와 일치하는지 확인합니다.

  • 모듈을 고정하지 않은 채 배선에 매달려 흔들리고 있지 않은지 확인합니다.

  • 모터 전원선과 센서 신호선이 너무 가까이 붙어 노이즈가 들어오지 않는지 확인합니다.

특히 모터가 도는 순간 IMU 값이 심하게 튄다면 전원과 배선을 의심해야 합니다. 모터는 큰 전류를 쓰고 전기적 노이즈를 많이 만들기 때문에, 센서 전원에 영향을 줄 수 있습니다. 앞에서 배운 것처럼 모터 전원과 보드 전원을 적절히 분리하고, GND는 공통으로 묶으며, 배선을 짧고 단단하게 정리하는 것이 좋습니다.

또한 IMU 모듈은 로봇 프레임에 단단히 고정되어야 합니다. 센서가 양면테이프 끝에서 덜렁거리면 로봇 몸체의 움직임이 아니라 센서 자체의 흔들림을 측정하게 됩니다. 균형 로봇에서는 이것만으로도 제어가 크게 불안정해질 수 있습니다.

균형 로봇으로 이어지는 생각

두 바퀴 균형 로봇을 떠올려 봅시다. 로봇이 앞으로 기울면 바퀴를 앞으로 굴려 몸체를 다시 세워야 합니다. 뒤로 기울면 바퀴를 뒤로 굴려야 합니다. 이때 필요한 가장 중요한 정보가 바로 기울기 각도입니다.

IMU는 이 기울기 각도를 알려줍니다. 하지만 각도만 안다고 끝은 아닙니다. 로봇은 다음 질문에도 답해야 합니다.

  • 목표 각도는 몇 도인가?

  • 현재 각도는 목표보다 얼마나 벗어났는가?

  • 얼마나 빠르게 기울어지고 있는가?

  • 모터를 어느 방향으로 얼마나 세게 돌릴 것인가?

이 질문들은 뒤에서 배울 제어의 기초, 모터 속도 제어, PID 제어와 연결됩니다. IMU가 현재 자세를 알려주면, 제어 알고리즘은 목표 자세와 비교해 모터 출력을 조정합니다. 그러면 로봇은 넘어지는 방향으로 도망가듯 바퀴를 굴리며 균형을 잡습니다.

드론도 비슷합니다. 드론은 공중에서 계속 roll, pitch, yaw를 측정하고, 목표 자세와 비교해 네 개의 모터 출력을 조정합니다. 카메라 짐벌도 IMU로 흔들림을 감지하고 서보나 브러시리스 모터로 반대 방향 보정을 합니다. 결국 IMU는 "내 몸이 지금 어떤 자세인가"를 알려주는 로봇의 균형 감각입니다.

이번 장의 핵심 정리

IMU는 로봇이 자신의 자세와 회전 움직임을 이해하기 위해 사용하는 중요한 센서입니다. 엔코더가 바퀴 회전량을 알려준다면, IMU는 로봇 몸체의 기울기와 회전을 알려줍니다.

가속도계는 중력 방향을 이용해 기울기를 추정할 수 있습니다. 정지 상태나 천천히 움직일 때는 좋은 기준이 되지만, 급출발, 급정지, 진동, 충격이 있을 때는 노이즈가 커집니다.

자이로스코프는 각속도를 측정합니다. 각속도에 시간 간격을 곱해 계속 더하면 각도를 추정할 수 있습니다. 빠른 움직임을 잘 따라가지만, 작은 오차가 누적되어 시간이 지나면 드리프트가 생깁니다.

상보 필터는 이 둘의 장점을 섞는 간단하고 강력한 방법입니다. 자이로로 빠른 변화를 따라가고, 가속도계로 장기적인 기준을 보정합니다. 이 방식만 제대로 이해해도 균형 로봇과 자세 추정의 첫 문턱을 넘은 것입니다.

다음 장에서는 로봇이 바닥의 선을 따라가거나 색을 구분하는 센서를 다룹니다. IMU가 로봇 자신의 자세를 느끼는 감각이라면, 라인 센서와 색상 센서는 로봇이 바닥과 주변 표식을 읽는 감각입니다. 이제 로봇은 자신의 움직임뿐 아니라 길 위의 단서도 읽기 시작합니다.

책 목록

센서와 모터로 시작하는 실전 로봇 만들기

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기