센서 실험실 #1: 가속도계 센서 알아보기 (feat. MPU6050)

기운찬곰·2025년 10월 10일

센서 실험실

목록 보기
1/3
post-thumbnail

가속도계 센서

가속도계 센서(Accelerometer)는 물체의 가속도를 측정하는 장치입니다. 가속도는 시간에 대한 속도 변화의 비율을 뜻합니다. (속도 벡터가 단위시간 동안 얼마나 변했는지 나타내는 벡터량)

Pitch, Roll, Yaw

3차원 공간에서 X축, Y축, Z축을 따라 가속도를 측정하는 것이 3축 가속도 센서입니다.

여기에 대해 피치(Pitch), 롤(Roll), 요(Yaw)는 3차원 공간에서 물체의 자세(Attitude) 또는 회전을 설명하는 세 가지 기본 축을 나타내는 용어입니다. 상식으로 알고 계십쇼.

💻 이미지 출처: https://simple.wikipedia.org/wiki/Pitch,_yaw,_and_roll

피치 (Pitch) - 횡축 회전 ↕️: 좌우 방향(날개)을 관통하는 횡축(Lateral Axis, Y축)을 중심으로 하는 회전입니다. 비행기가 상승하거나 하강할 때 발생합니다.

롤 (Roll) - 종축 회전 ↔️: 물체의 앞뒤 방향(기수부터 꼬리까지)을 관통하는 종축(Longitudinal Axis, X축)을 중심으로 하는 회전입니다. 비행기가 선회(Turns)하기 위해 자세를 잡을 때 사용됩니다.

요 (Yaw) - 수직축 회전 🔄: 물체의 위아래 방향을 관통하는 수직축(Vertical Axis, Z축)을 중심으로 하는 회전입니다. 비행기의 방향(Heading)을 수평면에서 미세하게 바꾸기 위해 사용합니다.

측정 가능한 것들

가속도계는 정적인 상태에서는 중력 가속도를 감지하고, 움직일 때는 중력 가속도가 3축으로 얼만큼의 영향을 주었는가를 측정하여 운동 변화(진동, 충격 등)를 감지합니다.

다음의 정보를 파악할 수 있습니다.

  • 방향 및 기울기: 정지 상태의 기울기(Static Tilt). 중력 가속도를 측정하여 물체의 수평면에 대한 각도를 파악할 수 있습니다. (예: 스마트폰 화면 자동 회전, 수평계)
  • 움직임 및 상태: 진동(Vibration). 모터, 기계류, 교량 등의 미세하고 반복적인 움직임의 크기와 주파수를 측정하여 장비의 상태를 모니터링합니다.
  • 충격 및 낙하: 순간적인 충격(Impact). 물체가 충돌하거나 낙하할 때 발생하는 급격한 가속도 변화를 측정합니다. (예: 에어백)
  • 운동량 추정: 속도(Velocity) 및 변위(Displacement). 가속도 데이터를 시간에 대해 한 번 적분하여 속도를, 두 번 적분하여 변위(위치 변화)를 간접적으로 계산할 수 있습니다. (예: 만보기, 피트니스 트래커)

예를 들어볼까요? 스마트폰을 기울였을 때 화면 회전을 생각해보면, 수평 상태일 때는 X=0g, Y=0g, Z=1g (중력이 Z축에만)인데, 90도를 기울이면 X=1g, Y=0g, Z=0g (중력이 X축으로 이동)가 되겠죠.

또한, 선형 가속도 측정을 통해 좌우 움직임(X축 가속도), 전후 움직임(Y축 가속도), 상하 움직임(Z축 가속도)을 알 수 있습니다.

💻 참고: 스마트폰의 3차원 가속도 센서(오일러 각, 롤 피치 요)

💻 이미지 출처. https://koreascience.kr/article/JAKO201914260900141.pdf

비행기를 생각해보세요. 처음에는 Z축에만 중력 가속도 영향 받습니다. 비행기가 이륙한다고 생각했을 때 Y축이 서서히 올라가다가 세워지는 순간 Y축에만 중력 가속도가 영향을 받습니다. 이번에는 비행기가 극단적으로 좌측 혹은 우측으로 돈다고 생각해보면 X축에만 중력 가속도가 영향을 받겠죠.

가속도계 센서 한계

가속도계는 단독으로 사용될 때 몇 가지 주요 문제에 직면하며, 이로 인해 정확한 위치, 방향, 회전각을 계산하는 데 한계가 있습니다.

#1. 회전 및 방위각 측정 불가

가속도계만으로는 중력 방향과 일치하는 축을 중심으로 한 회전을 감지할 수 없습니다.

Z축 중심 회전 (요/Yaw) 감지 불가: 물체가 수평면에서 회전하는 방위각(Yaw)은 가속도의 크기(중력 방향)를 변화시키지 않기 때문에 가속도계만으로는 이 회전 각도를 전혀 측정할 수 없습니다. 이 문제를 해결하기 위해서는 자이로스코프 센서를 함께 사용하여 회전 속도 정보를 보완해야 합니다.

스마트폰을 수평면에다가 놓고 회전시켜봐도 Z축에만 중력 가속도가 영향을 받고, 나머지 X축, Y축은 전혀 영향이 없으니까 그런것이죠. (아하...)

#2. 중력과 운동 가속도의 구별 불가

가속도계는 자신이 겪는 모든 가속도를 하나의 힘으로 측정합니다.

가속도계가 자유 낙하할 때는 중력의 영향을 받지 않는(무중력 상태와 같은) 것으로 간주되어 측정값이 0g로 나옵니다. 반면, 지표면에 가만히 놓여 있을 때는 지구 중력에 의해 1g의 가속도가 측정됩니다.

가속도계는 중력과 순수한 운동으로 인한 가속도를 분리하여 인식할 수 없습니다. 따라서 움직이는 상태에서 중력의 영향을 보정하여 순수한 운동 가속도만을 얻으려면 별도의 계산과 필터링이 필요합니다.

#3. 적분 오차의 누적 (Drift)

가속도계는 속도(vv)와 위치(xx)를 계산하기 위해 측정된 가속도(aa)를 시간(tt)에 대해 적분합니다. 이 과정에서 작은 오차들이 누적되어 속도와 위치 정보의 정확도가 크게 떨어집니다.

  • 노이즈 및 바이어스(Bias): 센서의 미세한 노이즈(잡음)나 초기 바이어스(0점 오차)가 적분될 때 계속해서 커집니다.
  • 드리프트 (Drift): 시간이 지남에 따라 계산된 위치가 실제 위치와 점점 멀어지는 현상이 발생합니다. 이로 인해 장기간의 정밀한 위치 추적에는 적합하지 않습니다.

IMU / MEMS

이러한 한계 때문에 실제로 자세, 속도, 위치 등 복잡한 움직임을 정밀하게 파악하기 위해서는 관성 측정 장치(IMU, Inertial Measurement Unit)가 필수적으로 사용됩니다.

IMU 란 물체의 속도, 방향, 중력, 회전(각속도)과 같은 관성력을 측정하기 위해 여러 센서를 하나로 통합한 전자 장치입니다. IMU는 일반적으로 다음과 같은 센서들로 구성됩니다.

  • 가속도계 (Accelerometer): 3개의 축(X, Y, Z)을 따라 발생하는 선형 가속도를 측정합니다. 이를 통해 물체의 기울기(Pitch, Roll)와 움직임, 충격 등을 감지할 수 있습니다.
  • 자이로스코프 (Gyroscope): 3개의 축(X, Y, Z)을 중심으로 하는 회전 속도(각속도)를 측정합니다. 물체가 얼마나 빠르게 회전하는지(Pitch, Roll, Yaw)를 파악합니다.
  • 지자기 센서 (Magnetometer) (선택 사항): 지구 자기장을 측정하여 절대적인 방향(방위각, Heading)을 파악하는 데 사용됩니다. 나침반과 같은 역할을 하여 자이로스코프의 오차(Drift)를 보정해 줍니다.

참고. MPU6050 IMU 센서 (MPU-6050 IMU Sensor)

MEMS(Micro-Electro-Mechanical Systems, 미세 전자기계 시스템)는 초소형 기계 부품과 전자 회로를 하나의 실리콘 칩 위에 집적하는 반도체 제조 기술입니다. 마이크로미터(μm, 100만 분의 1미터) 크기의 초소형 정밀 기계 및 전자 부품을 만들 수 있습니다.

오늘날 우리가 사용하는 대부분의 IMU에 들어가는 가속도계와 자이로스코프 센서가 바로 이 MEMS 기술로 만들어집니다. MEMS 기술 덕분에 과거에는 크고 비쌌던 관성 센서들을 매우 작고 저렴하게 만들 수 있게 되었고, 이는 스마트폰이나 드론과 같은 소형 기기에 IMU를 탑재하는 것을 가능하게 했습니다.

💻 이미지 출처. Concordia University > MEMS 기술 (정말 대단하네요...)

활용 분야

가속도계 센서는 그 유용성 덕분에 일상생활부터 첨단 산업까지 폭넓게 활용됩니다.

  • 스마트폰 및 웨어러블 장치: 화면 자동 회전, 만보기(걸음 수 측정), 피트니스 트래커, 게임 컨트롤러의 모션 감지, 제스처 인식, 수면 추적 등
  • 자동차: 에어백 전개 시스템(충돌 감지), 차량 자세 제어 및 안정성 시스템 (ESC/ABS)
  • 산업 및 건설: 기계 장비의 진동 모니터링 (고장 예측), 교량이나 건물의 구조 건전성 모니터링
  • 항공우주: 관성 항법 시스템(Inertial Navigation System, INS), 드론 및 무인 항공기의 자세 안정화

가속도계 센서 동작 원리

가속도계 센서의 기본 작동 원리는 뉴턴의 제2법칙(F = ma)관성력에 기반을 둡니다. 센서 내부에 있는 작은 질량체에 가해지는 힘(F)을 측정하여, 그 힘에 비례하는 가속도(a)를 간접적으로 알아내는 방식입니다.

현대 소형 기기에 사용되는 MEMS(미세 전자기계 시스템) 가속도계를 중심으로 원리를 설명드리겠습니다.

핵심 구성 요소

가속도계 센서는 매우 작은 크기로 제작되며, 다음의 핵심 구성 요소를 포함합니다.

  1. 검증 질량 (Proof Mass, 또는 관성 질량): 센서의 측정 축 방향으로 자유롭게 움직일 수 있도록 스프링(또는 유연한 빔)에 매달려 있는 작은 질량체(Mass) 입니다.
  2. 스프링/서스펜션 구조: 검증 질량을 센서 본체에 연결하고 지지하는 탄성 구조물입니다.
  3. 변위 감지부 (Transducer, 또는 Electrodes): 질량의 움직임(변위)을 전기적 신호로 변환하는 장치입니다.

💻 이미지 출처. CircuitBread > How does an Accelerometer work?

작동 원리 - 정전 용량형

가장 흔하게 사용되는 정전 용량형(Capacitive) 가속도계의 작동 원리는 다음과 같습니다.

#1. 관성력의 발생

센서에 외부에서 가속도(a)가 가해지면, 검증 질량(m)은 그 관성 때문에 가속도와 반대 방향으로 움직이려는 힘(F)을 받습니다. 이 힘이 바로 관성력 입니다.

관성력(FF)은 질량체에 작용하여 스프링을 압축하거나 늘어나게 만들어 변위(xx)를 발생시킵니다.

#2. 정전 용량의 변화

이때 검증 질량은 두 개의 고정된 전극(Capacitor Plates) 사이에 위치합니다.

질량체가 한쪽 전극으로 가까워지면 (사이의 거리가 좁아지면) 그 전극과의 정전 용량(C)이 증가합니다. 동시에 다른 쪽 전극과는 멀어지면서 (사이의 거리가 넓어지면) 정전 용량이 감소합니다.

정전 용량(C)은 전극 면적(A)에 비례하고, 전극 사이의 거리(d)에 반비례합니다.

#3. 전기 신호 변환 및 출력

센서 내부 회로는 이 두 전극 사이의 정전 용량 차이(ΔC)를 측정합니다.

이 정전 용량 변화량(ΔC)은 가속도(aa)에 정확히 비례합니다. 이 변화량을 전압 신호로 변환하고 증폭하여 최종적으로 가속도 값(일반적으로 g 단위 또는 m/s2 단위)으로 출력합니다.

대부분의 현대 가속도계는 3차원 공간에서의 움직임을 감지하는 3축 가속도계입니다. 센서 칩 내부에 서로 직교하는 X, Y, Z축 방향으로 각각의 검증 질량-스프링 구조가 독립적으로 설계되어 있습니다.

이제 3차원 모두에서 움직임을 감지할 수 있게 되었습니다. 가속도계는 이 원리를 이용해 두 가지 종류의 가속도를 모두 측정합니다.

  • 동적 가속도 (Dynamic Acceleration): 물체의 움직임(속도 변화)으로 인해 발생하는 가속도입니다. 예: 차량의 출발 및 정지, 진동, 충격 등
  • 정적 가속도 (Static Acceleration): 움직임이 없는 상태에서 중력(1g)에 의해 발생하는 가속도입니다. 센서가 정지해 있을 때도 검증 질량은 중력(1g) 방향으로 힘을 받아 기울어져 있습니다. 이 기울어진 정도(정전 용량 변화)를 측정하여 물체의 기울기(Pitch, Roll)를 파악할 수 있습니다.

가속도계 센서와 자이로스코프

가속도계 센서(Accelerometer)와 자이로스코프 센서(Gyroscope)는 모두 물체의 움직임을 감지하는 관성 센서이지만, 측정하는 물리량이 근본적으로 다릅니다. 이 두 센서는 상호 보완적으로 사용되어 물체의 자세와 움직임을 정확하게 파악합니다.

아래 그림에서 왼쪽은 가속도만 측정한 경우, 오른쪽은 가속도와 자이로스코프 둘 다 측정하는 경우 입니다. 당연하게도 하나 보다는 두 개를 측정하는 게 더 정확하겠죠?

가속도에 대해서는 위에서 이야기 했으니 생략하고 자이로스코프에 대해 간단히 알아봅시다. 자이로스코프는 회전 운동을 측정합니다. 물체의 각속도(ω\omega, 단위: deg/s 또는 rad/s)를 측정합니다.

동작 원리는 코리올리 힘(Coriolis Force)을 이용합니다.

  • MEMS 자이로스코프는 내부에 미세하게 진동하는 구조물(질량체)을 가지고 있습니다.
  • 센서가 회전하면, 이 진동하는 질량체에 회전 방향에 수직인 코리올리 힘이 발생하여 진동 축이 미세하게 변화합니다.
  • 이 힘에 의한 변화를 감지하여 각속도(ω\omega)를 측정합니다.

측정된 각속도를 시간에 대해 적분하면 물체의 회전 각도나 방위각을 알 수 있습니다.


가속도계 센서와 자이로스코프, 두 센서는 서로 다른 유형의 움직임을 측정하며, 각각의 단점을 상호 보완합니다.

구분가속도계 (Accelerometer)자이로스코프 (Gyroscope)
측정 물리량선형 가속도 (aa) 및 중력 (gg)각속도 (ω\omega, 회전 속도)
측정하는 움직임기울기, 직선 이동, 진동, 충격회전, 자세 변화의 속도
장점기울기(정적 자세) 감지에 정확함회전 움직임(동적 자세) 감지에 정확함
단점중력/가속도 구분 불가, 위치 오차 누적각도 오차 누적 (Drift), 직선 이동 감지 불가

두 센서를 하나로 통합한 장치를 위에서 말한 관성 측정 장치(IMU, Inertial Measurement Unit)라고 하며, 두 센서의 데이터를 융합하여 정밀한 움직임과 자세 정보를 얻습니다.

  • 가속도계: 단기적으로 정확한 기울기를 제공하여 자이로스코프의 드리프트를 보정합니다.
  • 자이로스코프: 단기적으로 정확한 회전 속도를 제공하여 가속도계의 움직임으로 인한 오류를 상쇄시킵니다.
  • 칼만 필터(Kalman Filter) 또는 상보 필터(Complementary Filter): 이와 같은 알고리즘을 사용하여 두 센서 데이터를 결합함으로써 각각의 장점은 취하고 단점은 보완하는 고도로 정확하고 안정적인 자세(Roll, Pitch, Yaw) 정보를 실시간으로 계산합니다.

이 두 센서는 스마트폰의 화면 회전, 드론의 자세 제어, 내비게이션 시스템, 로봇 공학, 게임 컨트롤러 등 현대 모바일 및 정밀 제어 장치에서 필수적으로 사용됩니다.


MPU6050 센서 모듈

MPU-6050은 관성 센서 모듈 중 가장 널리 사용되는 제품 중 하나이며, 3축 가속도계와 3축 자이로스코프를 단일 칩(Single Chip)에 통합한 6축 관성 측정 장치(6-Axis IMU, Inertial Measurement Unit)입니다.

💻 구매처: https://www.devicemart.co.kr/goods/view?no=1247052

GY-521 MPU6050 모듈에 대한 간단한 스펙입니다.

  • 6축 센서: 3축 자이로스코프와 3축 가속도계가 통합된 모션 감지 모듈입니다.
  • I2C 통신: 표준 I2C 프로토콜을 사용하여 아두이노 등의 마이크로컨트롤러와 쉽게 연결됩니다.
  • 고정밀 측정: 16비트 AD 변환기로 정밀한 데이터를 제공하며, 자이로스코프(±250~2000°/s)와 가속도계(±2~16g) 범위를 설정할 수 있습니다.
  • 온도 센서 내장: 칩 자체의 온도를 측정하여 센서 교정과 보정에 활용됩니다.
  • 간편한 사용: 3V-5V의 넓은 작동 전압 범위로 다양한 DIY 프로젝트(드론, 로봇, 자세 제어 등)에 활용 가능합니다.

구체적인 내용은 InvenSense > MPU-6050 페이지나 데이터시트를 참고하십쇼.

위 그림을 확인하면 칩을 자세히 보면 좌상단에 동그라미 표시가 있죠? 이게 기준입니다. 이 상태를 기준으로 x, y, z 축이 어디인지, 움직이면 값이 어떻게 변할 것인지 예측 가능할 겁니다.

센서별 측정 범위 - 민감도

MPU-6050은 디지털 센서로, 사용자가 측정 범위를 선택할 수 있으며, 이에 따라 센서의 민감도(Sensitivity)가 달라집니다.

📌 3축 가속도계 측정 범위: ±2g, ±4g, ±8g, ±16g

g는 중력 가속도 단위입니다. 지구의 중력 가속도는 약 9.8 m/s²이고, 이를 1g라고 합니다. 따라서 2g는 중력의 2배 가속도, 8g는 중력의 8배 가속도를 의미합니다.

센서가 "±2g"라는 범위를 가진다는 것은 이 센서가 정확하게 측정하고 출력할 수 있는 최대 가속도가 양방향으로 2g 라는 것을 의미합니다. 예를 들어 ±16g 범위로 설정하면 센서는 16g의 큰 충격까지 측정할 수 있습니다.

사용 목적에 따라 적절한 범위를 선택해야 합니다.

  • 저속/정밀 자세 제어 (예: 드론 자세 유지): 움직임이 크지 않으므로 ±2g 나 ±4g 처럼 작은 범위를 선택합니다.
  • 충격 측정/고속 움직임 (예: 자동차 충돌 테스트): 10g 이상의 큰 가속도를 측정해야 하므로 ±8g 나 ±16g와 같은 큰 범위를 선택합니다.

가속도계는 측정된 아날로그 신호를 16비트와 같은 정해진 비트 수의 디지털 값으로 변환합니다 (ADC). 이때 측정 범위를 어떻게 설정하느냐에 따라 분해능(Resolution)이 달라집니다.

  • ±2g (작은 범위): 분해능이 가장 높습니다. 1g 당 출력 변화 폭이 가장 큽니다.
  • ±16g (큰 범위): 분해능이 가장 낮습니다. 1g 당 출력 변화 폭이 가장 작습니다.

예를 들어, MPU-6050의 16비트 ADC 기준으로 설정하자면 ±2g는 총 4g4g의 범위를 2162^{16}개(65,536개)의 디지털 값으로 나누어 측정합니다. 따라서 정밀한 측정이 가능합니다. 반면에, ±16g는 총 32g32g의 범위를 동일한 2162^{16}개(65,536개)의 디지털 값으로 나누어 측정합니다. 측정 단위당 가속도 값이 커져 정밀도가 낮아집니다.


📌 3축 자이로스코프 측정 범위: ±250, ±500g, ±1000g, ±2000 deg/s

자이로스코프는 물체의 각속도(ω\omega), 즉 1초 동안 회전하는 각도를 측정합니다. ±250로 설정했다면 이 센서는 양방향으로 초당 최대 250도의 회전 속도까지 정확하게 측정할 수 있습니다.

  • ±250 deg/s: 매우 느리거나 미세한 회전을 정밀하게 감지하는 데 사용됩니다 (예: 정교한 자세 제어, 미세한 떨림 감지).
  • ±2000 deg/s: 매우 빠른 회전이나 급격한 움직임을 측정하는 데 사용됩니다 (예: 스포츠 장비, 고속 드론의 급격한 회전).

가속도계와 마찬가지로, 자이로스코프도 측정 범위를 어떻게 설정하느냐에 따라 분해능(Resolution)이 달라집니다. 센서의 ADC(아날로그-디지털 변환기) 비트 수(예: 16비트)는 고정되어 있습니다.

따라서, 미세한 움직임을 감지할 때는 가장 작은 범위를 선택하고, 최대 측정 가능한 회전 속도가 중요할 때는 가장 큰 범위를 선택해야 합니다.

블록 다이어그램

MPU6050 데이터시트 중에 블록 다이어그램을 확인해보면서 핵심 구성요소와 동작과정에 대해 가볍게 살펴보겠습니다.

💻 데이터시트 참고: https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Datasheet1.pdf

핵심 구성 요소는 센서, ADC(아날로그-디지털 변환기), DMP, 그리고 통신 인터페이스입니다.

  • 3축 자이로스코프 및 가속도계 (Sensors): 초소형 정밀 기계 기술(MEMS)로 구현된 기계적 구조물입니다. 두 센서 모두 움직임에 비례하는 아날로그 전압 신호를 생성합니다.
  • ADC (Analog-to-Digital Converter): 센서에서 출력된 아날로그 전압 신호를 마이크로컨트롤러가 처리할 수 있는 디지털 데이터로 변환합니다. MPU-6050은 일반적으로 16비트 ADC를 사용하여 높은 정밀도로 데이터를 변환합니다.
  • DMP (Digital Motion Processor): 칩 내부에 내장된 보조 프로세서입니다. 복잡한 6축 MotionFusion 알고리즘을 처리합니다.
  • 온도 센서: 센서 칩 내부의 온도를 측정합니다. 온도 변화는 센서의 바이어스(Bias)를 유발할 수 있으므로, 측정된 온도 정보를 이용해 데이터의 온도 드리프트 오차를 보정하는 데 사용됩니다.
  • I²C 인터페이스: MPU-6050과 호스트 MCU(마이크로컨트롤러) 간의 디지털 통신 통로입니다.

참고로, MotionFusion 알고리즘은 주로 TDK InvenSense의 모션 센서(예: MPU-6050, MPU-9150 등)에 내장된 독점 펌웨어 알고리즘을 의미합니다. 이는 여러 센서의 데이터를 통합하여 정확한 모션 추적 및 방향 인식을 가능하게 하는 센서 융합(Sensor Fusion) 기술입니다.

이 알고리즘은 별도로 DMP에서 실행되기 때문에 호스트 프로세서(스마트폰의 메인 CPU 등)의 개입 없이 복잡한 계산이 이루어져 시스템 전력 소비를 줄이고 개발을 간소화할 수 있습니다.

  • 실시간 보정: 런타임 보정 펌웨어가 포함되어 있어 센서 데이터의 편향(bias)을 실시간으로 보정하고 최적의 성능을 보장합니다.
  • 출력 형식: 처리된 데이터는 회전 행렬, 쿼터니언(quaternion), 또는 오일러 각(Euler Angle)과 같은 다양한 형식으로 출력되어 애플리케이션에서 활용하기 용이합니다.

MPU-6050 동작 과정은 다음과 같은 단계로 데이터를 생성하고 출력합니다.

  1. 물리적 움직임 감지: 물체가 회전하거나 가속하면, 내부 MEMS 센서가 움직임을 감지하여 아날로그 전압 신호를 생성합니다.
  2. 아날로그-디지털 변환: 생성된 아날로그 신호는 ADC로 전송되어 16비트의 디지털 데이터로 변환됩니다. 이 데이터는 센서의 레지스터(Register)에 저장됩니다.
  3. 데이터 처리 (선택적 DMP 사용): DMP를 사용하면 DMP가 ADC에서 변환된 가속도와 자이로스코프 데이터를 입력받아 칩 내부에서 실시간으로 융합 및 보정 처리를 수행합니다. 만약 사용하지 않으면 호스트 MCU가 레지스터의 원시 데이터를 직접 읽어와 자세 계산을 수행하면 됩니다.
  4. 호스트 MCU 통신: 호스트 MCU는 I²C 통신을 사용하여 MPU-6050에 명령을 보내고 필요한 데이터를 요청합니다. MCU는 데이터를 수신하여 최종 애플리케이션(예: 드론 모터 제어)에 사용합니다.

실습 해보기

하드웨어 연결

💻 참고. Cirkit Designer: How to Use MPU6050: Examples, Pinouts, and Specs

마이크로컨트롤러로는 ESP32 를 사용할 겁니다. 먼저 MPU6050 핀에 대해 알아봅시다.

여기서는 공급 전압이 2.3V ~ 3.4V 로 적혀있더라고요. 다른 곳에서는 5V도 가능하다고 했는데... 뭐가 맞는 걸까요. 확인해보니 센서 칩 자체의 공급 전압은 2.3V ~ 3.4V 가 맞고, 모듈의 일반적인 작동 전압은 3.3V ~ 5V 라고 합니다. (아핫... Cirkit Designer 가 좀 헷갈리게 적어놨네요)

📌 필수 연결 핀 (I²C 통신)

ESP32와 MPU-6050 모듈을 연결하는 방법은 주로 I²C(Inter-Integrated Circuit) 통신을 이용하며, 필요한 핀은 VCC, GND, SCL, SDA 네 가지입니다. 나머지 핀(XDA, XCL, AD0, INT)은 특정 고급 기능에 사용되며 일반적인 사용에서는 연결하지 않아도 됩니다.

MPU-6050 핀ESP32 핀설명
VCC (전원)3.3V 또는 VCC센서 모듈에 전원을 공급합니다. 일반적으로 3.3V에 연결합니다.
GND (접지)GND회로의 공통 접지입니다.
SCL (클럭)GPIO 22 (I²C SCL) - 변경 가능I²C 통신을 위한 동기 클럭 신호선입니다.
SDA (데이터)GPIO 21 (I²C SDA) - 변경 가능I²C 통신을 위한 양방향 데이터 신호선입니다.

📌 고급 및 옵션 핀 설명

일반적인 가속도 및 자이로스코프 데이터 측정에서는 아래 핀들을 연결할 필요가 없으며, 특정 고급 기능을 사용할 때만 연결합니다.

MPU-6050 핀설명연결 여부
XDA, XCL보조 I²C 버스 (Auxiliary I²C Bus)MPU-6050에 추가적인 외부 센서 (예: 지자기 센서)를 연결하여 9축 IMU처럼 사용할 때만 사용됩니다. ESP32와는 연결하지 않습니다.
AD0I²C 주소 선택이 핀의 상태(GND 연결 또는 VCC 연결)에 따라 MPU-6050의 I²C 주소 (0x68 또는 0x69)가 결정됩니다. 일반적으로 내부 풀다운 저항으로 GND에 연결된 상태(0x68)로 사용되므로 별도로 연결하지 않습니다.
INT인터럽트(Interrupt)MPU-6050에서 특정 이벤트 (예: 동작 감지, 데이터 준비 완료)가 발생했을 때 ESP32에 신호를 보내기 위해 사용됩니다. 실시간 반응이 중요한 경우에만 ESP32의 임의의 GPIO 핀에 연결하여 사용합니다.

따라서 연결은 VCC, GND, I2C 통신을 위한 SCL, SDA. 총 4개 핀만 연결하면 된다는 거네요.


🤔 I2C 연결은 풀업 저항이 필요한거 아니야? 연결 안해도 괜찮아?

좋은 질문입니다. I²C 통신의 표준 규격 상 풀업 저항(Pull-up Resistor)은 필수적입니다. 하지만, MPU-6050 센서 모듈을 사용할 때는 보통 이 저항을 따로 연결하지 않아도 괜찮습니다.

먼저, 풀업 저항이 필요한 이유는 I²C 통신은 데이터 라인(SDA)과 클럭 라인(SCL)이 오픈 드레인(Open-Drain) 또는 오픈 컬렉터(Open-Collector) 방식으로 작동하기 때문입니다.

풀업 저항은 다음과 같은 역할을 합니다.

  • High 상태 유지: 통신 라인이 아무런 신호를 보내지 않을 때 HIGH (논리 1, VCC 전압 레벨) 상태를 유지할 수 있도록 VCC 전압으로 끌어올리는 역할을 합니다.
  • 데이터 쓰기: 통신 마스터(ESP32)나 슬레이브(MPU-6050)가 데이터를 보낼 때, LOW (논리 0, GND) 신호를 보내기 위해 라인을 접지(GND)에 연결합니다. 신호가 끊어지면 풀업 저항이 다시 라인을 HIGH로 빠르게 복귀시킵니다.
  • 데이터 충돌 방지: 여러 장치가 같은 버스를 공유할 때, 모두가 LOW 신호를 강제할 수는 있지만, HIGH 신호는 풀업 저항에 의해 부드럽게 복구되므로 충돌 없이 통신이 가능합니다.

MPU-6050 모듈에서 풀업 저항 연결이 필요 없는 이유는 MPU-6050이 개별 칩(IC)이 아닌 모듈(Breakout Board) 형태이고, 대부분의 모듈에는 이미 이 풀업 저항이 내장되어 있습니다.

  • 내장 저항: 시중에서 흔히 볼 수 있는 MPU-6050 모듈은 사용 편의성을 위해 SCL 라인과 SDA 라인에 이미 4.7kΩ 나 10kΩ 정도의 풀업 저항이 부착되어 있습니다.
  • 편의성: 따라서 사용자는 ESP32의 I²C 핀(GPIO 22, 21)에 직접 연결하기만 하면 됩니다.

만약 사용하는 모듈이 풀업 저항을 내장하고 있지 않다면, 4.7kΩ 또는 10kΩ 저항을 SCL-VCC, SDA-VCC 사이에 연결해야 합니다.

만약 ESP32 개발 보드 자체에도 I²C 풀업 저항이 내장되어 있고, MPU-6050 모듈에도 풀업 저항이 있다면, 두 개의 풀업 저항이 병렬로 연결됩니다. 일반적으로는 문제가 발생하지 않지만, 저항 값이 너무 낮아지면(병렬 연결 시 저항 감소), 통신이 불안정해지거나 전력 소모가 증가할 수 있습니다.

주요 레지스터

코드를 작성하기 전에 MPU-6050 에서 주로 사용되는 레지스터에 대해 알아보도록 하겠습니다.

💻 참고: https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Register-Map1.pdf

#1. Power Management 1 - PWR_MGMT_1

파워 관리 1 (PWR_MGMT_1) 레지스터는 MPU-6050 칩의 전반적인 동작 모드, 슬립 상태, 리셋, 그리고 클럭 소스를 제어하는 데 사용되는 가장 중요한 레지스터 중 하나입니다.

이 레지스터의 8개 비트는 각각 고유한 기능을 담당합니다.

비트비트 이름 - 기능설명 및 용도
Bit 7DEVICE_RESET초기화. 센서 사용 전, 레지스터 상태를 확실히 초기값으로 되돌리고 싶을 때 사용합니다.
Bit 6SLEEP슬립 모드(저전력 모드). 1로 설정하면 센서와 내부 회로가 저전력 슬립 모드로 진입합니다. 데이터를 측정할 때는 반드시 0으로 설정해야 합니다.
Bit 5CYCLE활성 모드(주기 모드). 1로 설정하면 센서가 주기적으로 깨어나 측정하고 다시 잠드는 저전력 모드로 작동합니다. 깨어나는 주기는 LP_WAKE_CTRL(레지스터 108)로 설정합니다. 슬립 모드(Bit 6)가 0일 때만 유효합니다.
Bit 4TEMP_DIS온도 센서 비활성화. 1로 설정하면 온도 센서 기능을 비활성화하여 전력 소모를 줄입니다.
Bit 3-0CLKSEL [3:0]클럭 소스 선택. 센서의 내부 동작 클럭으로 사용할 소스를 선택합니다.

클럭 소스 선택은 센서의 측정 안정성과 정확도에 영향을 미치므로 중요합니다. 일반적으로 내부 발진기(Internal Oscillator)보다 자이로스코프를 참조하는 PLL(Phase-Locked Loop) 클럭이 더 안정적입니다.

MPU-6050은 3가지 클럭 소스 중 선택 가능 합니다. 내부 8MHz 오실레이터 (기본값), 자이로스코프 기반 클록, 외부 클럭 소스.

전원 켜지면 기본적으로 내부 오실레이터를 사용하지만, 안정성 향상을 위해 자이로스코프 클럭(또는 외부 클럭)을 사용하도록 설정하는 것을 강력히 권장합니다.

따라서 안정적인 동작을 위해 센서를 초기화할 때 일반적으로 PWR_MGMT_1 레지스터에 "0x01"을 씁니다. 이 설정은 센서를 활성화하고, 가장 안정적인 클럭 소스를 사용하도록 합니다.


#2. Sample Rate Divider - SMPRT_DIV

샘플링 속도 분배기 레지스터 (SMPLRT_DIV)는 MPU-6050이 내부적으로 센서 데이터를 얼마나 자주 업데이트할지 결정하는 레지스터입니다. 8비트 데이터 전체가 분배기 값(DIV)을 설정하는 데 사용됩니다.

SMPLRT_DIV 레지스터에 기록된 값(DIV)은 자이로스코프 출력 속도를 나누어 실제 출력 데이터율 (Output Data Rate, ODR)을 결정합니다.

MPU-6050의 자이로스코프는 기본적으로 1kHz 로 동작합니다. 단, DLPF (디지털 저역 통과 필터)를 비활성화하면 내부 속도가 8kHz로 빨라집니다. 기본적으로는 활성화되어있습니다. 가속도계는 보통 1kHz(또는 DLPF 설정에 따라 4kHz)로 동작하며, 자이로스코프의 샘플링 속도에 맞춰 동기화됩니다.

🔢 DIV 값에 따른 Sample Rate 계산 예시

DIV 값1 + DIVSample Rate (Hz)설명
0(0000_0000)11000 Hz최대 속도. 1ms 마다 데이터 갱신
9(0000_1001)10100 Hz10ms 마다 데이터 갱신
19(0001_0011)2050 Hz20ms 마다 데이터 갱신
124(0111_1100)1258 Hz125ms 마다 데이터 갱신

따라서 자신이 원하는 데이터 출력 속도를 설정하고 싶으면 이 값을 변경하면 되겠습니다. 주의할 점은 SMPLRT_DIV 설정은 센서가 데이터를 생성하는 속도입니다. 이 속도는 센서가 데이터를 읽어가는 호스트 MCU (ESP32)의 I²C 읽기 주기와 일치해야 가장 효율적입니다.

쉽게 말해, 생산자 속도와 소비자 속도가 서로 일치하는 게 좋겠죠? 두 주기가 일치해야 ESP32가 매번 가장 최신의, 중복되지 않은 센서 데이터를 얻을 수 있습니다.


#3. Accelerometer Measurements - ACCEL_XOUT

ACCEL_XOUT은 MPU-6050 센서에서 측정된 X축 방향의 가속도 원시 데이터(Raw Data)를 저장하는 레지스터 세트의 이름입니다. 가속도계 측정값은 16비트 정수(int16_t)형태로 저장되며, 8비트씩 두 개의 레지스터에 나뉘어 저장됩니다.

MPU-6050은 데이터를 Big-Endian 형식으로 전송하기 때문에, 통신 시 상위 바이트(H)를 먼저 읽고 하위 바이트(L)를 나중에 읽습니다.

ACCEL_XOUT_H(0x3B)가 중요한 이유는 자이로스코프, 온도 센서 등의 다른 측정값 레지스터의 시작 주소가 될 수 있기 때문입니다. 이 주소부터 총 14바이트를 연속적으로 읽으면 X, Y, Z축 가속도, 온도, X, Y, Z 축 각속도 데이터를 모두 얻을 수 있습니다.

ESP32와 같은 마이크로컨트롤러가 ACCEL_XOUT 데이터를 사용할 수 있는 실제 g 값으로 변환하는 과정은 다음과 같습니다.

A. Raw Data 재조립 (Parsing) - 두 개의 8비트 레지스터(H, L)을 하나로 합치는 과정입니다.

B. 물리량 스케일링 - 재조립된 Raw Data를 센서의 민감도 계수(Sensitivity Scale Factor)로 나누어 실제 가속도 g 단위로 변환합니다.

가속도계 민감도를 ±2g (기본값)으로 설정 시, 전체 4g 범위를 65536(2^16)으로 나누면 65536 / 4 = 16384 LSB/g 가 됩니다. 실제 가속도(g) = raw_값 / 16384.0 인거죠. 예를 들어, Z축 가속도계 값이 16384로 읽히면 16384 / 16384 = 1g (지구 중력 1g)가 나옵니다.

자이로스코프 민감도를 ±250 deg/s (기본값)으로 설정 시, 전체 500°/s 범위를 65536으로 나누면 65536 / 500 = 131.072 ≈ 131 LSB/(°/s) 가 됩니다. 실제 각속도(deg/s) = raw_값 / 131.0 인거죠.

📌 참고. 센서의 측정범위(민감도)를 설정하는 레지스터는 GYRO_CONFIG(0x1B), ACCEL_CONFIG(0x1C) 입니다.

AFS_SEL[1:0] 으로 설정할 수 있습니다.

I2C 통신

데이터시트를 보다보니, I2C 통신에 대한 과정도 설명이 잘 되어있어서 간단히 살펴보겠습니다. (어차피 코드 작성할 때 이 내용을 알아야 합니다. 복습할 겸 정리해봤습니다)

I²C는 두 개의 신호선만 사용하며, 여러 개의 장치가 하나의 버스에 연결될 수 있는 마스터-슬레이브(Master-Slave) 구조입니다. 두 라인은 모두 풀업 저항을 통해 HIGH 상태로 유지됩니다. 또한, I²C는 반이중 방식입니다. 송신과 수신이 동시에 이루어질 수 없다는 거죠.

  • SCL (Serial Clock): 마스터 장치가 통신의 타이밍과 동기화를 위해 생성하는 클럭 신호선입니다.
  • SDA (Serial Data): 마스터와 슬레이브 사이에 양방향 데이터를 전송하는 데이터 신호선입니다.

I²C 통신은 데이터 전송을 위해 정해진 4가지 주요 단계(시작, 주소 지정, 데이터 전송, 정지)를 거칩니다.

📌 A. 시작 조건 - 통신을 시작하는 신호입니다. 마스터가 SDA 라인을 HIGH에서 LOW로 바꾸는 동안 SCL 라인은 HIGH 상태여야 합니다. 이 신호는 버스에 연결된 모든 슬레이브 장치에게 통신이 시작되었음을 알립니다.

📌 B. 주소 지정 및 읽기/쓰기 비트 전송 - 통신을 시작한 마스터는 어떤 슬레이브와 대화할지 지정해야 합니다. 마스터는 통신하려는 슬레이브의 7비트 고유 주소를 SDA 라인을 통해 전송합니다. 주소 뒤에 1비트를 추가하여 마스터가 데이터를 쓰려는지(0) 또는 읽으려는지(1)를 결정합니다. SDA가 변경되는 구간에서 SCL은 LOW 상태여야 합니다.

주소 전송과 읽기/쓰기 비트 전송이 끝나면, 선택된 슬레이브는 SDA 라인을 LOW로 끌어내려 ACK (Acknowledge, 응답) 신호를 보냅니다. 이는 슬레이브가 주소를 인식했고 통신 준비가 되었음을 의미합니다.

📌 C. 데이터 전송 (Data Transfer) - ACK 신호를 받은 후, 마스터는 데이터를 전송하기 시작합니다. 데이터는 8비트(1바이트) 단위로 전송됩니다. 1바이트의 데이터가 전송될 때마다, 수신자는 ACK(LOW) 또는 NACK(HIGH) 신호로 다음 바이트를 받을 준비가 되었는지 응답합니다.

  • 쓰기 과정(마스터 → 슬레이브): 슬레이브가 ACK를 보냅니다.
  • 읽기 과정(슬레이브 → 마스터): 마스터가 ACK를 보냅니다. 마스터가 데이터를 더 이상 받고 싶지 않으면 NACK(HIGH)를 보냅니다.

📌 D. 정지 조건 - 모든 데이터 전송이 완료되면, 마스터는 통신을 종료합니다. SCL가 HIGH일 때, SDA를 LOW에서 HIGH 로 바꿉니다. 이 신호는 버스가 해제되었으며, 다른 마스터가 통신을 시작할 수 있음을 나타냅니다.


정리해봅시다. 마스터가 슬레이브 레지스터의 데이터를 쓰고 싶다고 해봅시다. 전송 과정은 다음과 같습니다.

마스터가 시작 신호를 보내고, 슬레이브의 주소와 쓰기 비트를 보냅니다. 슬레이브는 ACK 신호를 보냅니다. 마스터는 슬레이브 레지스터의 주소를 보냅니다. 슬레이브는 ACK 신호를 보냅니다. 마스터가 슬레이브에게 데이터를 보냅니다. 그러면 해당 레지스터의 데이터가 써집니다. ACK를 보냅니다. 마지막으로 정지 신호를 보냅니다.

반대로, 마스터가 슬레이브 레지스터의 데이터를 읽고 싶다고 해봅시다. 이 과정은 쓰기(레지스터 주소 지정)와 읽기(데이터 수신)의 두 부분으로 구성됩니다.

마스터가 시작 신호를 보내고, 슬레이브의 주소와 쓰기 비트를 보냅니다. ACK 신호를 응답받습니다. 슬레이브의 레지스터 주소를 보냅니다. ACK 응답 받습니다.

이제 마스터는 반복 시작 조건(Repeated START)을 전송하여 쓰기 모드를 종료하고 읽기 모드로 전환합니다. ACK 신호를 응답받습니다. 슬레이브가 지정된 레지스터부터 데이터를 전송합니다. 마스터가 ACK를 보내면 데이터를 받았고 다음 바이트도 받겠다는 응답입니다. 마스터가 NACK를 보내면 데이터를 받았고 더 이상 데이터를 받지 않겠다는 종료 신호입니다. 마지막으로 정지 신호를 보냅니다.

코드 작성 (ESP-IDF)

ESP32가 MPU-6050 센서와 I²C 통신을 통해 데이터를 읽어오고, 100Hz의 샘플링 속도를 구현하여 1초에 100번 데이터가 출력되도록 해보겠습니다.

A. 매크로 정의 및 설정

코드 상단에는 센서와 통신에 필요한 상수 값들이 정의되어 있습니다.

#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <driver/i2c.h>
#include <esp_log.h>

// --- MPU-6050 및 I2C 설정 ---
#define I2C_MASTER_SCL_IO           22        /*!< I2C master SCL gpio num */
#define I2C_MASTER_SDA_IO           21        /*!< I2C master SDA gpio num */
#define I2C_MASTER_NUM              I2C_NUM_0 /*!< I2C port number for master dev */
#define I2C_MASTER_FREQ_HZ          100000    /*!< I2C master clock frequency */

#define MPU6050_SENSOR_ADDR         0x68      /*!< MPU6050 address */
#define MPU6050_PWR_MGMT_1_REG_ADDR 0x6B
#define MPU6050_SMPLRT_DIV_REG_ADDR 0x19
#define MPU6050_DATA_START_ADDR     0x3B      /*!< ACCEL_XOUT_H register address */

static const char *TAG = "MPU6050_TEST";

B. MPU-6050 레지스터에 데이터 쓰기 함수

MPU-6050의 특정 레지스터 주소에 하나의 8비트(1바이트) 데이터를 기록하여 센서 설정을 변경할 때 사용됩니다.

// --- MPU-6050 레지스터에 데이터 쓰기 함수 ---
static esp_err_t mpu6050_register_write(uint8_t reg_addr, uint8_t data)
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (MPU6050_SENSOR_ADDR << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg_addr, true);
    i2c_master_write_byte(cmd, data, true);
    i2c_master_stop(cmd);
    
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    return ret;
}
  • i2c_cmd_link_create(): I²C 명령들을 순서대로 담을 수 있는 명령 링크(Command Link)를 생성합니다.
  • i2c_master_start(cmd): I²C 시작 조건(START Condition)을 생성합니다.
  • 슬레이브 주소 + 쓰기 비트: 7비트 슬레이브 주소(0x68)를 1비트 왼쪽으로 밀어 상위 7비트로 위치시킵니다. 쓰기 비트를 최하위 비트에 붙여 8비트를 만듭니다. 전송합니다.
  • 레지스터 주소 전송 (reg_addr): 마스터가 데이터를 쓸 슬레이브 내부의 레지스터 주소를 전송하고 ACK를 기다립니다.
  • 데이터 값 전송 (data): 레지스터에 실제로 기록할 1바이트의 설정 데이터를 전송하고 ACK를 기다립니다.
  • i2c_master_stop(cmd): 정지 조건(STOP Condition)을 생성하여 통신을 종료합니다.
  • i2c_master_cmd_begin(...): 생성된 모든 명령(시작, 주소, 레지스터 주소, 데이터, 정지)을 I²C 버스를 통해 실제 실행합니다. 타임아웃은 1초로 설정합니다. I2C 통신 성공/실패 여부가 ret에 저장됩니다.
  • i2c_cmd_link_delete(): I2C 명령 큐가 사용했던 메모리를 해제합니다. 통신 성공/실패 관계없이 항상 호출해서 메모리 누수 방지 필수.

C. MPU-6050 레지스터에서 데이터 읽기 함수

MPU-6050의 특정 레지스터부터 시작하여 연속적인 여러 바이트의 데이터를 읽어올 때 사용됩니다. 센서 측정값은 연속된 레지스터에 저장되므로 이 방법이 효율적입니다.

static esp_err_t mpu6050_read_bytes(uint8_t reg_addr, uint8_t *data, size_t len)
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    // 1. 읽기 시작 레지스터 주소 지정
    i2c_master_write_byte(cmd, (MPU6050_SENSOR_ADDR << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg_addr, true);
    
    // 2. 읽기 명령 전송
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (MPU6050_SENSOR_ADDR << 1) | I2C_MASTER_READ, true);
    
    if (len > 1) {
        i2c_master_read(cmd, data, len - 1, I2C_ACK_VAL);
    }
    i2c_master_read_byte(cmd, data + len - 1, I2C_NACK_VAL);
    
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    return ret;
}
  • 레지스터 주소 지정 (쓰기 트랜잭션): 시작 후, 슬레이브 주소와 쓰기 모드(0)를 전송하여 센서를 호출합니다. 데이터를 읽기 시작할 레지스터 주소를 슬레이브에 전달합니다. 슬레이브는 이 주소를 내부 포인터로 설정합니다.
  • 데이터 읽기 (읽기 트랜잭션): 반복 시작 조건(Re-START)을 사용하여 첫 번째 트랜잭션을 끝내지 않고 곧바로 읽기 트랜잭션으로 전환합니다. 슬레이브 주소 + 읽기 비트를 전송합니다. 이제 데이터 방향이 슬레이브에서 마스터로 바뀝니다. 마스터는 요청한 길이(len) 중 마지막 바이트를 제외한 모든 데이터를 슬레이브로부터 받습니다. 데이터를 성공적으로 받았으므로 ACK로 응답하여 연속적인 데이터 수신을 요청합니다. 마지막 바이트를 받은 후에는 NACK로 응답합니다. 슬레이브는 이 신호를 받고 데이터 전송을 멈춥니다. 정지 조건으로 통신을 완전히 종료합니다.

D. MPU-6050 초기화 및 100Hz 샘플링 설정

이 함수는 MPU-6050을 작동 가능한 상태로 만들고 100 Hz 샘플링을 설정합니다.

static esp_err_t mpu6050_init(void)
{
    // 1. 파워 관리 레지스터(0x6B): 장치 리셋 및 클럭 소스 설정 (PLL with X-axis Gyro reference)
    esp_err_t ret = mpu6050_register_write(MPU6050_PWR_MGMT_1_REG_ADDR, 0x01);
    if (ret != ESP_OK) return ret;
    ESP_LOGI(TAG, "MPU6050: Power Management Reset OK.");
    
    // 2. 샘플링 속도 분배기(0x19): 100 Hz 설정
    // 기본 샘플링 속도 1000 Hz / (1 + DIV) = 100 Hz -> DIV = 9
    ret = mpu6050_register_write(MPU6050_SMPLRT_DIV_REG_ADDR, 0x09);
    if (ret != ESP_OK) return ret;
    ESP_LOGI(TAG, "MPU6050: Sample Rate set to 100 Hz (Divider=9).");

    // 기타 설정 (옵션): DLPF, Gyro Range, Accel Range 등 필요에 따라 추가
    // ex) mpu6050_register_write(0x1A, 0x06); // DLPF 설정 (5Hz)
    
    return ESP_OK;
}
  • 파워 관리 설정 (0x6B): 센서의 슬립 모드를 해제하고(0x00), 클럭 소스를 자이로스코프의 X축 참조(0x01)로 설정하여 안정성을 높입니다.
  • 샘플링 속도 분배기 설정 (0x19): 샘플링 분배기 값을 9로 설정합니다. 이로 인해 센서의 데이터 갱신 속도는 100 Hz 가 됩니다.

E. I2C 마스터 드라이버 초기화

이 함수는 ESP32를 I²C 마스터로 설정하여 MPU-6050과 통신할 수 있도록 준비합니다.

static void i2c_master_init(void)
{
    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = I2C_MASTER_FREQ_HZ,
    };
    i2c_param_config(I2C_MASTER_NUM, &conf);
    i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
    ESP_LOGI(TAG, "I2C Master Initialized.");
}
  • mode = I2C_MODE_MASTER: ESP32를 마스터 장치로 설정합니다.
  • sda_pullup_en, scl_pullup_en: I²C 라인에 내부 풀업 저항을 활성화합니다.
  • master.clk_speed: 통신 속도를 위에서 정의한 100 kHz 로 설정합니다.

F. 메인 루프

app_main 함수는 센서 초기화 후 데이터를 반복적으로 읽어 출력하는 핵심 부분입니다.

void app_main(void)
{
    i2c_master_init();
    
    if (mpu6050_init() != ESP_OK) {
        ESP_LOGE(TAG, "MPU6050 Initialization Failed!");
        return;
    }
    
    uint8_t data[14];
    int16_t acc_x, acc_y, acc_z, temp_raw, gyro_x, gyro_y, gyro_z;
    
    // 100 Hz 출력을 위한 딜레이 설정 (10 / 1000 = 10ms)
    const TickType_t delay_ticks = 10 / portTICK_PERIOD_MS;

    while (1) {
        // MPU6050_DATA_START_ADDR(0x3B)부터 14바이트를 읽음 (Acc_XYZ, Temp, Gyro_XYZ)
        esp_err_t ret = mpu6050_read_bytes(MPU6050_DATA_START_ADDR, data, 14);

        if (ret == ESP_OK) {
            // 1. 데이터 파싱 (Big-Endian 형식)
            acc_x = (int16_t)(data[0] << 8 | data[1]);
            acc_y = (int16_t)(data[2] << 8 | data[3]);
            acc_z = (int16_t)(data[4] << 8 | data[5]);
            temp_raw = (int16_t)(data[6] << 8 | data[7]);
            gyro_x = (int16_t)(data[8] << 8 | data[9]);
            gyro_y = (int16_t)(data[10] << 8 | data[11]);
            gyro_z = (int16_t)(data[12] << 8 | data[13]);
            
            // 2. 센서값 출력
            printf("ACC: X=%d, Y=%d, Z=%d | ", acc_x, acc_y, acc_z);
            // 온도 계산 (옵션)
            float temperature = (temp_raw / 340.0) + 36.53;
            printf("TEMP: %.2f C | ", temperature);
            printf("GYRO: X=%d, Y=%d, Z=%d\n", gyro_x, gyro_y, gyro_z);
            printf("\n");
            
        } else {
            ESP_LOGE(TAG, "I2C Read Failed: %d", ret);
        }

        // 100 Hz (10ms) 주기를 맞추기 위한 딜레이
        vTaskDelay(delay_ticks);
    }
}
  • 100Hz 주기를 맞추기 위해 10ms의 지연시간(10 / portTICK_PERIOD_MS)을 설정합니다.
  • 데이터 읽기: 0x3B 주소부터 연속된 14바이트(가속도 6, 온도 2, 자이로 6)를 읽어 data 배열에 저장합니다.
  • 데이터 파싱 및 변환: acc_x = (int16_t)(data[0] << 8 | data[1])와 같이, 읽어온 상위 바이트(data[0])와 하위 바이트(data[1])를 합쳐 16비트 정수(int16_t) 값으로 재조립합니다 (Big-Endian 처리).
  • 출력 및 지연: 파싱된 Raw Data 및 계산된 온도를 시리얼 모니터로 출력합니다. 10ms 동안 태스크 실행을 중지하여, 다음 읽기까지 10ms를 기다립니다.

이 코드는 Raw Data를 출력하고 있지만, 실제 애플리케이션에서는 이 Raw Data를 민감도 계수로 나누어 g와 deg/s 단위의 물리량으로 변환한 후 사용해야 합니다. (아래 참고)

// --- 민감도 계수 (Sensitivity Scale Factors) 정의 ---
// 기본 설정: Accel = +/- 2g (16384 LSB/g), Gyro = +/- 250 deg/s (131.0 LSB/(deg/s))
#define ACCEL_SCALE_FACTOR          16384.0f
#define GYRO_SCALE_FACTOR           131.0f

...

// Raw Data 파싱 이후 코드 추가

// 물리량 변환 (스케일링)
// 가속도: Raw Data를 16384.0f로 나누어 g 단위로 변환
acc_x_g = (float)acc_x_raw / ACCEL_SCALE_FACTOR;
acc_y_g = (float)acc_y_raw / ACCEL_SCALE_FACTOR;
acc_z_g = (float)acc_z_raw / ACCEL_SCALE_FACTOR;

// 자이로스코프: Raw Data를 131.0f로 나누어 deg/s 단위로 변환
gyro_x_dps = (float)gyro_x_raw / GYRO_SCALE_FACTOR;
gyro_y_dps = (float)gyro_y_raw / GYRO_SCALE_FACTOR;
gyro_z_dps = (float)gyro_z_raw / GYRO_SCALE_FACTO

실행 결과

제대로 실행되는지 확인해봤습니다. 음... 출력이 안되더군요. 근데 종료하니까 데이터가 출력되긴 합니다. 아무래도 1초에 100번 출력은 좀 빡센거 같아보입니다.

그래서 1초에 10번 출력(100Hz)으로 바꿨습니다. delay_ticks 값도 수정하고요. 샘플링 속도 분배기 레지스터 값도 변경해줍니다. 99(0x63) 으로 변경. 그랬더니 출력이 되더군요.

잘 바뀌는거 같습니다. 하지만 오디오 실험할 때도 그랬습니다만... 숫자로 보려니까 제대로 변하고 있는지 한번에 확인하기 어렵습니다. 역시 이것도 그래프로 실시간으로 확인해보고 싶습니다.

파이썬으로 코드를 작성하면 되는데... 글이 너무 길어져서 다음 글에서 다뤄봐야 될 거 같네요.


참고 자료

profile
행동하는 바보가 돼라. 생각을 즉시 행동으로 옮기는 사람이 되어라

0개의 댓글