기술나눔

STM32-I2C

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

이 내용은 다음을 기반으로 합니다.장시 기술 STM32영상 공부 후 편집했습니다.

1. I2C 통신

1.1 I2C 통신 소개

  • I2C(Inter IC Bus)는 필립스가 개발한 범용 데이터 버스입니다.
  • 2개의 통신 라인: SCL(Serial Clock) 직렬 클록 라인, SDA(Serial Data) 직렬 데이터 라인
  • 동기식, 반이중, 단일 종단, 다중 장치
  • 데이터로 답장하기
  • 버스에 여러 장치 장착 지원(마스터 1개와 슬레이브 1개, 마스터 1개와 슬레이브 1개)
    • 하나의 마스터, 다중 슬레이브: 마이크로 컨트롤러는 호스트 역할을 하며 I2C 버스의 작동을 지배합니다. I2C 버스에 장착된 모든 외부 모듈은 슬레이브이며 호스트에 의해 명명된 후에만 I2C 버스를 제어할 수 있습니다. 충돌을 방지하기 위해 허가 없이 I2C 버스를 사용합니다.
    • 다중 마스터 및 다중 슬레이브: 버스의 모든 모듈은 능동적으로 튀어나와 마스터 역할을 할 수 있습니다. 버스 충돌이 발생하면 I2C 프로토콜이 중재를 수행합니다. 중재에서 승리한 당사자는 버스 제어권을 획득하고 패한 당사자는 자동으로 슬레이브가 됩니다.

이미지.png

1.2 하드웨어 회로

  • 모든 I2C 장치의 SCL은 함께 연결되고, SDA도 함께 연결됩니다.
  • 장치의 SCL과 SDA는 모두 오픈 드레인 출력 모드로 구성되어야 합니다.
  • SCL과 SDA 각각에 풀업 저항을 추가합니다. 저항 값은 일반적으로 약 4.7KΩ입니다.

그림 1그림 2
이미지.png

  • 하나의 마스터, 다중 슬레이브: CPU는 버스의 마스터로서 언제든지 SCL 라인을 완전히 제어할 수 있습니다. 또한, 유휴 상태에서 호스트는 슬레이브가 데이터를 보내고 슬레이브가 응답할 때만 SDA의 제어를 적극적으로 시작할 수 있습니다. 이것이 호스트의 전원입니다.
  • 제어되는 IC는 I2C 버스에 장착되는 슬레이브이며 자세 센서, OLED, 메모리, 클럭 모듈 등이 될 수 있습니다. 슬레이브의 전력은 상대적으로 작습니다. SCL 클럭 라인의 경우 슬레이브는 언제든지 수동적으로 읽을 수만 있습니다. SDA 데이터 라인의 경우 슬레이브는 SDA 제어를 적극적으로 시작할 수 없습니다. 마스터가 슬레이브를 읽으라는 명령을 보낸 후 또는 슬레이브가 응답할 때만 슬레이브가 SDA에 대한 제어권을 잠시 얻을 수 있습니다.
  • 그림 2: 왼쪽은 SCL, 오른쪽은 SDA. 모든 데이터는 데이터 버퍼나 슈미트 트리거를 통해 입력될 수 있습니다.
    • 입력은 회로에 영향을 주지 않으므로 모든 장치는 언제든지 입력을 받을 수 있습니다.
    • 출력은 오픈 드레인 출력 구성을 사용합니다. 출력이 낮을 때 스위치는 켜지고 핀은 접지에 직접 연결됩니다. 이는 출력이 높을 때 스위치가 꺼지고 핀이 접지됩니다. 아무 것도 연결되지 않고 플로팅 상태이므로 모든 장치는 하이 레벨로 인한 플로팅을 방지하기 위해 로우 레벨만 출력할 수 있습니다. SCL 및 SDA에는 외부 풀업 저항이 필요합니다. 버스는 저항을 통해 높은 레벨로 풀업되므로 약한 풀업입니다. 이러한 방식으로 첫째, 전원 단락 현상을 완전히 제거하고 회로의 안전성을 보장합니다. 둘째, 핀 모드의 빈번한 전환을 방지합니다. 오픈 드레인 모드에서 하이 레벨을 출력하는 것은 핀을 분리하는 것과 동일하므로 입력하기 전에 직접 하이 레벨을 출력할 수 있습니다. 셋째, 이 모드에는 하나 이상의 장치가 로우 레벨을 출력하는 한, 모든 장치가 하이 레벨을 출력하는 경우에만 버스가 로우 레벨이 됩니다. . 따라서 I2C는 이러한 현상을 활용하여 다중 마스터 모드에서 클럭 동기화 및 버스 중재를 수행할 수 있습니다. 따라서 여기서 SCL은 하나의 마스터 및 다중 슬레이브 모드에서 푸시-풀 출력을 사용할 수 있지만 여전히 오픈 드레인과 풀아웃 출력 모드를 사용합니다.

1.3 I2C 타이밍 기본 유닛

1.3.1 시작조건과 종료조건

  • 시작 조건: SCL 하이 레벨 동안 SDA는 하이 레벨에서 로우 레벨로 전환됩니다.
  • 종료 조건: SCL 하이 레벨 동안 SDA는 로우 레벨에서 하이 레벨로 전환됩니다.

이미지.png

  • 초기 조건에서 : I2C 버스가 유휴 상태일 때 SCL과 SDA는 모두 하이 레벨 상태입니다. 즉, SCL과 SDA에 닿는 장치가 외부 풀업 저항에 의해 하이 레벨로 당겨지고 버스는 하이 레벨 상태가 됩니다. 조용한 상태. 호스트가 데이터를 보내고 받아야 할 때 먼저 버스의 침묵을 깨고 시작 조건을 생성해야 합니다. 즉, SCL은 건드리지 않고 높은 수준에 있으며 SDA를 아래로 당겨 하강 에지를 생성해야 합니다. 슬레이브가 SCL 하이 레벨과 SDA 하강 에지 신호를 캡처하면 스스로 재설정되고 마스터의 호출을 기다립니다. SDA의 하강 에지 이후 호스트는 SCL을 다시 풀다운해야 하며 한편으로는 버스를 점유하고 다른 한편으로는 기본 유닛의 접합을 용이하게 하기 위한 것이기도 합니다. 시작 및 중지 조건을 제외하고 각 순차 단위의 SCL은 낮은 레벨로 시작하여 낮은 레벨로 끝나는 것이 나중에 보장됩니다.
  • 종료 조건 상태에서 : SCL이 먼저 놓아 높은 레벨로 리바운드한 다음 SDA가 놓아 높은 레벨로 리바운드하여 상승 에지를 생성하여 종료 조건을 트리거합니다. 동시 종료 조건 이후에는 SCL과 SDA 모두 High가 되며 초기의 평온 상태로 돌아갑니다.
    시작 및 중지는 호스트에 의해 생성되며 슬레이브는 시작 및 중지를 생성하는 것이 허용되지 않습니다. 따라서 버스가 유휴 상태일 때 슬레이브는 항상 손을 놓아야 하며 튀어나와 버스를 만지는 것이 허용되지 않습니다.

1.3.2 바이트 보내기

  • 바이트 보내기: SCL의 낮은 레벨 동안 호스트는 SDA 라인에 데이터 비트를 순서대로(높은 비트 먼저) 넣은 다음 SCL을 해제합니다. SCL의 높은 레벨 동안 슬레이브는 데이터 비트를 읽으므로 SDA는 허용되지 않습니다. SCL의 상위 레벨 동안 데이터를 가지려면 데이터가 변경되면 위의 프로세스를 8번 순환하여 1바이트를 보냅니다.

하위 레벨 호스트는 데이터를 저장하고 상위 레벨 슬레이브는 데이터를 읽습니다.
이미지.png
시작 조건 이후 첫 번째 바이트도 호스트에서 전송되어야 합니다. SCL이 로우일 때 호스트가 0을 보내려고 하면 SDA를 로우로 끌어오고, 1을 보내려면 그대로 두고 SDA는 하이 레벨로 리바운드됩니다. SCL의 낮은 레벨 동안 SDA의 레벨은 변경이 허용됩니다. 데이터가 배치된 후 호스트는 클럭 라인을 해제하고 SCL은 높은 레벨로 리바운드됩니다. High 레벨에서는 슬레이브가 SDA를 읽는 시간이므로 High 레벨에서는 SDA를 변경할 수 없습니다. SCL이 높은 수준에 도달한 후 슬레이브는 가능한 한 빨리 SDA를 읽어야 합니다. 일반적으로 슬레이브는 SCL의 상승 에지에서 읽기를 완료합니다. 클럭은 마스터에 의해 제어되기 때문에 슬레이브는 하강 에지가 언제 발생하는지 알 수 없으므로 슬레이브는 SCL의 상승 에지에서 데이터를 읽습니다. 호스트가 일정 기간 동안 SCL을 해제한 후 계속해서 SCL을 로우로 끌어오고 다음 비트를 전송할 수 있습니다. 또한 호스트는 SCL의 하강 에지 이후 가능한 한 빨리 SDA에 데이터를 저장해야 합니다. 그러나 호스트는 클록을 제어할 수 있으므로 로우 레벨이 낮을 때 언제든지 SDA에 데이터를 저장하면 됩니다. 데이터가 해제된 후 호스트는 SCL을 다시 해제하고 SCL은 높으며 슬레이브는 이 비트를 읽습니다. 이 프로세스를 반복합니다. 호스트는 SCL을 로우로 끌어오고 SDA에 데이터를 배치하며 호스트는 SCL을 해제하고 슬레이브는 SDA 데이터를 읽습니다. SCL의 동기화 하에서 마스터는 8사이클을 거쳐 1바이트인 8비트 데이터를 순차적으로 전송하고 수신합니다.
상위 비트가 먼저이므로 첫 번째 비트가 바이트의 최상위 비트 B7이고, 최하위 비트 B0이 마지막에 전송됩니다.

1.3.3 바이트 수신

  • 바이트를 받다: SCL의 낮은 레벨 동안 슬레이브는 SDA 라인에 데이터 비트를 순서대로(높은 비트 먼저) 넣은 다음 SCL을 해제합니다. SCL의 높은 레벨 동안 호스트는 데이터 비트를 읽으므로 SDA는 허용되지 않습니다. SCL의 높은 수준에서 데이터를 얻으려면 데이터가 변경되면 위 프로세스를 8번 순환하여 1바이트를 수신합니다(수신하기 전에 호스트는 SDA를 해제해야 함).

낮은 수준의 슬레이브는 데이터를 넣고, 높은 수준의 호스트는 데이터를 읽습니다.
이미지.png
SDA 라인: 마스터는 수신하기 전에 SDA를 해제해야 합니다. 이때 슬레이브는 SDA의 제어권을 얻습니다. 슬레이브가 0을 보내야 하면 SDA를 로우로 끌어오고 SDA를 보냅니다. 높은 수준으로 반등합니다. 낮은 레벨은 데이터를 변환하고, 높은 레벨은 데이터를 읽습니다. 실선은 마스터가 제어하는 ​​레벨을 나타내고 점선은 슬레이브가 제어하는 ​​레벨을 나타냅니다. SCL은 전체 프로세스에 걸쳐 호스트에 의해 제어되며, SDA 호스트는 수신 전에 해제되어야 제어를 위해 슬레이브에 전달됩니다. SCL 클럭은 호스트에 의해 제어되기 때문에 슬레이브의 데이터 변환은 기본적으로 SCL의 하강 에지에서 수행되며 호스트는 SCL이 높을 때 언제든지 읽을 수 있습니다.

1.3.4 응답 보내기 및 응답 받기

  • 답장 보내기: 호스트는 1바이트를 수신한 후 다음 클록에서 1비트의 데이터를 전송합니다. 데이터 0은 응답을 나타내고 데이터 1은 무응답을 나타냅니다.
  • 답장을 받다: 호스트가 바이트를 보낸 후 슬레이브가 응답하는지 확인하기 위해 다음 클록에서 데이터 비트를 수신합니다. 데이터 0은 응답을 나타내고, 데이터 1은 무응답을 나타냅니다(호스트는 수신하기 전에 SDA를 해제해야 함).

이미지.png
즉, 바이트를 보내는 타이밍을 호출한 후 수신 응답을 호출하는 타이밍이 뒤따라야 하며, 이는 슬레이브가 방금 주어진 데이터를 수신했는지 여부를 확인하는 데 사용됩니다. 슬레이브가 이를 수신하면 응답 비트에서 마스터가 SDA를 해제할 때 슬레이브는 즉시 SDA를 끌어내려야 하며 SCL의 상위 레벨 동안 호스트는 응답 비트를 읽습니다. 응답 비트가 0이면 슬레이브가 실제로 수신했음을 의미합니다.
바이트를 수신할 때 전송 응답을 호출해야 합니다. 응답을 보내는 목적은 슬레이브에게 계속 보내기를 원하는지 알려주는 것입니다. 슬레이브 머신이 데이터를 보낸 후 마스터로부터 응답을 받으면 슬레이브 머신은 계속해서 데이터를 보냅니다. 슬레이브 머신이 마스터 머신으로부터 응답을 받지 못하면 슬레이브 머신은 데이터를 보낸 것으로 간주합니다. 하지만 마스터 머신은 이를 무시할 수도 있습니다. 이때 슬레이브는 호스트의 후속 작업에 대한 간섭을 방지하기 위해 순종적으로 SDA를 해제하고 SDA의 제어권을 넘겨줄 것입니다.

1.4 I2C 타이밍

1.4.1 쓸 주소 지정

  • 쓸 주소 지정
  • 지정된 디바이스(Slave Address)에 대해 지정된 주소(Reg Address)(즉, 지정된 디바이스의 레지스터 주소)에 지정된 데이터(Data)를 씁니다.

이미지.png
프로세스:
(1) 시작 조건
(2) 바이트 전송 타이밍 - 0xD0 (슬레이브 주소(7bit) + 쓰기(1bit)-0) (1101 0000)
(3) 응답 수신: RA = 0(슬레이브로부터 응답 수신)
(4) 지정주소 : 0x19 (0001 1001)
(5) 응답 수신: RA = 0(슬레이브로부터 응답 수신)
(6) 지정된 데이터 쓰기: 0xAA (1010 1010)
(7) 응답 수신: RA = 0
(8) 정지 비트 P(종료 조건)

  • 시작 조건 이후에는 바이트를 보내는 타이밍이어야 합니다. 바이트의 내용은 슬레이브 주소 + 읽기 및 쓰기 비트이어야 합니다. 슬레이브 주소는 7비트이고 읽기 및 쓰기 비트는 정확히 1비트입니다. 8비트. 슬레이브 주소를 보내는 것은 통신 대상을 결정하는 것이고, 읽기 및 쓰기 비트를 보내는 것은 다음에 쓸 것인지 읽을 것인지를 확인하는 것입니다. 이제 호스트는 데이터 조각을 보냅니다. 바이트 내용은 16진수로 변환됩니다. 다음 단위는 수신 슬레이브의 응답 비트(RA)입니다. 종료되고 SCL은 로우로 풀려납니다. 그 후 호스트는 SDA를 해제하고 승인 비트 RA를 해제해야 합니다.
  • 응답 비트 RA가 끝난 후의 하이 레벨은 슬레이브가 SDA를 해제함으로써 생성됩니다. 슬레이브는 SCL의 로우 레벨에서 가능한 한 빨리 데이터를 교환하기를 원하기 때문에 SDA의 상승 에지와 SCL의 하강 에지가 거의 동시에 발생했습니다.
  • 응답이 완료된 후 계속해서 1바이트를 전송하면 두 번째 바이트는 지정된 장치 내부로 전송될 수 있습니다. 슬레이브 장치는 두 번째 자체 및 후속 바이트의 사용을 정의할 수 있습니다. 일반적으로 두 번째 바이트는 레지스터 주소나 명령 제어 워드 등이 될 수 있으며, 세 번째 바이트는 호스트가 레지스터 주소(두 번째 바이트)에 쓰려는 내용입니다.
  • P는 정지 비트입니다.

이 데이터 프레임의 목적은 슬레이브 주소 1101000을 지정하는 장치의 경우 주소 0x19의 내부 레지스터에 데이터 0xAA를 쓰는 것입니다.
0은 호스트가 후속 타이밍에 쓰기 작업을 수행함을 의미합니다.
1은 호스트가 후속 타이밍 시퀀스에서 판독 작업을 수행함을 의미합니다.

1.4.2 현재 주소 읽기

  • 현재 주소 읽기
  • 지정된 디바이스(Slave Address)에 대해 현재 주소 포인터가 가리키는 주소의 슬레이브 데이터(Data)를 읽습니다.

이미지.png
프로세스:
(1) 시작 조건
(2) 바이트 전송 타이밍 — 0xD1 (슬레이브 주소(7bit) + 읽기(1bit)-1) (1101 0001)
(3) 응답 수신: RA = 0(슬레이브로부터 응답 수신)
(4) 슬레이브 데이터 읽기: 0x0F (0000 1111)
(7) 응답 보내기: SA = 0
(8) 정지 비트 P(종료 조건)

  • 읽기 및 쓰기 비트는 1이며 다음 읽기 작업이 수행됨을 나타냅니다. 슬레이브가 응답(RA=0)한 후 데이터 전송 방향이 반전됩니다. 마스터는 SDA의 제어권을 슬레이브에게 넘기려 하고, 마스터는 바이트를 수신하는 타이밍을 호출하여 수신 작업을 수행합니다.
  • 두 번째 바이트에서는 슬레이브가 마스터로부터 권한을 얻고 SCL의 낮은 레벨에서 SCL에 쓸 수 있습니다. 마지막으로 마스터는 SCL의 높은 레벨에서 SDA를 읽습니다. 8비트, 슬레이브가 보낸 데이터 1바이트가 수신되는데, 이는 0x0F이다. 그런데 슬레이브의 어느 레지스터가 0x0F입니까? 읽기 타이밍에서 I2C 프로토콜은 호스트가 주소를 지정할 때 읽기 및 쓰기 플래그가 1로 설정되도록 규정합니다. 다음 바이트는 즉시 읽기 타이밍으로 전환됩니다. 따라서 호스트는 읽고 싶은 레지스터를 지정하기 전에 수신을 시작하므로 여기에 주소를 지정하는 링크가 없습니다. 슬레이브 머신에서는 모든 레지스터가 선형 영역에 할당되며 레지스터 중 하나를 나타내는 별도의 포인터 변수가 있습니다. 이 포인터는 기본적으로 전원이 켜지고 일반적으로 주소 0을 가리키며 바이트가 기록될 때마다 바이트를 읽은 후 포인터는 자동으로 한 번 증가하고 다음 위치로 이동합니다. 그런 다음 현재 주소를 읽는 타이밍을 호출할 때 호스트가 읽을 주소를 지정하지 않으면 슬레이브가 가리키는 레지스터로 돌아갑니다. 현재 포인터 값.

1.4.3 지정된 주소에서 읽기

  • 읽을 주소 지정
  • 지정된 디바이스(Slave Address)에 대해 지정된 주소(Reg Address)에서 슬레이브 데이터(Data)를 읽습니다.

이미지.png
먼저 시작하고 다시 시작하고 중지합니다.
프로세스:
(1) 시작 조건
(2) 바이트 전송 타이밍 - 0xD0 (슬레이브 주소(7bit) + 쓰기(1bit)-0) (1101 0000)
(3) 응답 수신: RA = 0(슬레이브로부터 응답 수신)
(4) 지정주소 : 0x19 (0001 1001)
(5) 응답 수신: RA = 0(슬레이브로부터 응답 수신)
(6) 시작조건을 반복한다
(7) 바이트 전송 타이밍 - 0xD1 (슬레이브 주소(7bit) + 읽기(1bit)-1) (1101 0001)
(8) 응답 수신: RA = 0
(9) 슬레이브 데이터 읽기: 0xAA (1010 1010)
(10) 응답 보내기: SA = 0
(11) 정지 비트 P(종료 조건)

  • 첫 번째 부분은 지정된 주소에 쓰는 것이지만 주소만 지정되어 있고 두 번째 부분은 현재 주소를 읽는 것입니다. 주소가 방금 지정되었으므로 현재 읽은 주소는 다음과 같습니다. 다시 전화했다.
  • 지정된 슬레이브 주소는 1101000이고 읽기-쓰기 플래그는 0이며 쓰기 작업이 수행된 후 슬레이브에 0x19 주소를 지정하기 위해 다른 바이트(두 번째 바이트)가 기록됩니다. 즉, 슬레이브가 데이터를 받은 후 레지스터 포인터는 0x19 위치를 가리킵니다.
  • Sr은 반복되는 시작 조건으로, 지정된 읽기 및 쓰기 플래그는 시작 조건의 첫 번째 바이트 뒤에만 올 수 있으므로 읽기 및 쓰기 방향을 전환하려면 다음과 같이 하면 됩니다. 또 다른 시작 조건.
  • 그런 다음 시작 조건 후에 읽기-쓰기 플래그 비트를 다시 지정하고 지정합니다. 이때 읽기-쓰기 플래그 비트는 1로 읽혀야 함을 나타냅니다. 그러면 호스트는 데이터를 받습니다. 주소 0x19의 0xAA입니다.

2. MPU6050

2.1 MPU6050 소개

  • MPU6050은 칩 자체의 X, Y, Z 축의 가속도 및 각속도 매개변수를 측정할 수 있는 6축 자세 센서로, 데이터 융합을 통해 자세각(Euler angle)을 더 많이 얻을 수 있습니다. 스스로 감지해야 하는 차량, 항공기 등의 균형을 유지합니다.
  • 3축 가속도계(Accelerometer): X, Y, Z 축의 가속도를 측정합니다.
  • 3축 자이로 센서(자이로스코프): X, Y, Z축의 각속도 측정

이미지.png

  • 항공기 동체를 예로 들면 오일러 각은 항공기 동체와 초기 세 축 사이의 각도입니다.
    • 비행기항공기 기수가 아래 또는 위로 기울어짐, 이 축 사이의 각도를정점
    • 비행기동체가 왼쪽이나 오른쪽으로 굴러갑니다., 이 축 사이의 각도를
    • 비행기동체 수평 유지항공기의 기수를 왼쪽이나 오른쪽으로 돌립니다., 이 축 사이의 각도를편주
    • 오일러 각도는 이때 항공기가 위쪽 또는 아래쪽으로 기울어졌는지, 왼쪽 또는 오른쪽으로 기울어졌는지 여부를 나타냅니다.
  • 일반적인 데이터 융합 알고리즘에는 일반적으로 보완 필터링, 칼만 필터링 등이 포함되며 관성 항법에서의 자세 계산이 포함됩니다.
  • 가속도계 : 중앙의 점선은 유도축이며 좌우로 슬라이드할 수 있는 일정한 질량을 가진 작은 슬라이더입니다. 슬라이더가 움직이면 전위차계가 움직이게 됩니다. 이 전위차계는 전압 분할 저항기로서 전위차계에 의해 출력되는 전압을 측정하여 작은 슬라이더의 가속도 값을 얻을 수 있습니다. 이 가속도계는 실제로 뉴턴의 제2법칙 F = ma에 따라 스프링 동력계입니다. 가속도 a를 측정하려면 단위 질량이 있는 물체를 찾아 힘 F를 측정하면 됩니다. X, Y, Z 축 각각에 가속도계가 있습니다. 가속도계에는 정적 안정성이 있지만 동적 안정성은 없습니다.
  • 자이로 센서 : 중앙에는 일정한 질량을 갖는 회전바퀴가 있으며, 회전바퀴가 고속으로 회전할 때 각운동량 보존의 원리에 따라 회전바퀴는 원래의 각운동량을 유지하려는 경향이 있습니다. 회전축의 방향은 변하지 않습니다. 외부 물체의 방향이 회전하면 내부 회전축의 방향은 회전하지 않으므로 밸런스 링 연결부에 각도 편차가 발생합니다. 연결부에 회전하는 전위차계를 놓고 전위차계의 전압을 측정하면 회전 각도를 얻을 수 있습니다. 자이로스코프는 각도를 직접 구할 수 있어야 하지만 MPU6050 자이로스코프는 각도를 직접 측정할 수 없습니다. 각속도, 즉 X축, Y축, Z축을 중심으로 회전하는 칩의 각속도를 측정합니다. 중심선. 각속도의 적분은 각도입니다. 그러나 물체가 정지해 있는 경우에는 노이즈로 인해 각속도 값이 완전히 0으로 돌아갈 수 없습니다. 그러면 적분이 계속 누적되면 이 작은 노이즈로 인해 계산된 각도가 천천히 드리프트됩니다. 각속도를 적분하여 얻은 각도로 시간이 지나도 견딜 수 없지만, 이 각도는 정지해 있거나 움직이고 있어도 문제가 없으며 물체의 움직임에 영향을 받지 않습니다. 자이로스코프는 정적 안정성이 아닌 동적 안정성을 갖습니다.
  • 정적 안정성은 있지만 동적 안정성이 없는 가속도계에 따르면, 자이로스코프는 동적 안정성은 있지만 정적 안정성은 없으므로 서로의 장점을 학습하고 서로의 단점을 보완할 수 있습니다. , 정적 안정성과 동적 안정성을 모두 통합할 수 있습니다. 자세가 어색합니다.

2.2 MPU6050 매개변수

  • 16비트 ADC는 센서의 아날로그 신호를 수집합니다. 양자화 범위: -32768~32767
  • 가속도계 전체 크기 선택: ±2, ±4, ±8, ±16(g)(1g = 9.8m/s2)
  • 자이로스코프 전체 크기 선택: ±250, ±500, ±1000, ±2000(°/초, 각도/초, 각속도 단위, 초당 회전 각도)(전체 크기 선택이 클수록 범위가 넓어집니다.) 측정 범위. 전체 범위가 작을수록 측정 분해능은 높아집니다.
  • 구성 가능한 디지털 저역 통과 필터: 출력 데이터의 저역 통과 필터링을 선택하도록 레지스터를 구성할 수 있습니다.
  • 구성 가능한 클록 소스
  • 구성 가능한 샘플링 주파수 분할: 클럭 소스를 주파수 분할기로 분할하여 AD 변환 및 기타 내부 회로용 클럭을 제공할 수 있습니다. 주파수 분할 계수를 제어하여 AD 변환 속도를 제어할 수 있습니다.
  • I2C 슬레이브 주소: 1101000(AD0=0) 또는 1101001(AD0=1)
    • 110 1000을 16진수로 변환하면 0x68이므로 MPU6050의 슬레이브 주소가 0x68이라고 말하는 사람도 있습니다. 하지만 I2C 통신에서는 첫 번째 바이트의 상위 7비트가 슬레이브 주소이고, 가장 낮은 비트가 읽기 및 쓰기 비트이므로 0x68이 슬레이브 주소라고 생각한다면 첫 번째 바이트를 보낼 때 먼저 변경해야 합니다. 0x68 왼쪽으로 1비트 이동(0x68 << 1)한 다음 비트 단위로 또는 위로 비트를 읽고 쓰고, 1을 읽고 0을 씁니다.
    • 또 다른 방법은 0x68의 데이터를 슬레이브 주소로 1비트(0x68 << 1)만큼 왼쪽으로 이동하는 것인데, 이 경우 MPU6050의 슬레이브 주소는 0xD0입니다. 이때 실제로 첫 번째 바이트를 보낼 때 쓰려면 첫 번째 바이트로 0xD0을 사용하고, 읽으려면 0xD0 또는 0x01(0xD0 | 0x01), 즉 첫 번째 바이트를 사용합니다. . 이 표현에는 왼쪽 시프트 작업이 필요하지 않습니다. 즉, 이 표현은 읽기 및 쓰기 비트를 슬레이브 주소에 통합합니다. 0xD0은 쓰기 주소이고 0xD1은 읽기 주소입니다.

2.3 하드웨어 회로

이미지.png

기능
VCC、GND전원 공급 장치
SCL、SDAI2C 통신 핀
XCL、XDA호스트 I2C 통신 핀
AD0슬레이브 주소의 최하위 비트
국제인터럽트 신호 출력
  • LDO: 저드롭아웃 선형 전압 레귤레이터, 3.3V 전압 레귤레이터.
  • SCL 및 SDA: I2C 통신 핀입니다. 모듈에는 4.7K 풀업 저항이 2개 내장되어 있으므로 배선 시 SDA와 SCL을 GPIO 포트에 직접 연결하면 됩니다. .
  • XCL, XDA: 호스트 I2C 통신 핀입니다. 이 두 핀은 칩 기능을 확장하도록 설계되었습니다. 일반적으로 외부 자력계 또는 기압계에 사용됩니다. 이러한 확장 칩이 연결되면 MPU6050의 호스트 인터페이스는 이러한 확장 칩의 데이터에 직접 액세스하여 MPU6050에 데이터 융합을 수행할 수 있습니다. 태도 계산.
    AD0 핀: 슬레이브 주소의 최하위 비트입니다. 로우 레벨에 연결되면 7비트 슬레이브 주소는 1101000이고, 하이 레벨에 연결되면 7비트 슬레이브 주소는 1101001입니다. 회로도에는 기본적으로 약하게 로우 레벨로 풀다운되는 저항이 있으므로 핀이 플로팅 상태로 남아 있으면 로우 레벨이 되며 이를 하이 레벨에 연결하려면 AD0을 VCC로 직접 연결하면 됩니다. 그리고 그것을 높은 곳으로 강하게 끌어당깁니다.
  • INT: 인터럽트 출력 핀 데이터 준비, I2C 호스트 오류 등과 같은 인터럽트 핀의 출력을 트리거하도록 칩 내부의 일부 이벤트를 구성할 수 있습니다.
  • 칩에는 자유 낙하 감지, 모션 감지, 제로 모션 감지 등도 내장되어 있습니다. 이러한 신호는 INT 핀을 트리거하여 레벨 전환을 생성할 수 있으며 필요한 경우 인터럽트 신호를 구성할 수 있습니다.
  • MPU6050 칩의 전원 공급 장치는 2.375-3.46V로 3.3V 전원 장치이며 5V에 직접 연결할 수 없습니다. 따라서 3.3V 전압 레귤레이터가 추가되고 입력 단자 전압 VCC_5V는 3.3V에서 5V 사이가 될 수 있습니다. 그러면 3.3V 전압 레귤레이터는 3.3V 단자에 전원이 공급되는 한 안정적인 3.3V 전압을 출력합니다. , 전원 표시등이 켜집니다.

2.4 MPU6050 블록 다이어그램

이미지.png

  • CLKIN과 CLKOUT은 클럭 입력 핀과 클럭 출력 핀이지만 일반적으로 내부 클럭을 사용합니다.
  • 회색 부분: 칩 내부의 센서, XYZ 축의 가속도계, XYZ 축의 자이로스코프입니다.
  • 온도를 측정하는 데 사용할 수 있는 온도 센서도 내장되어 있습니다.
  • 이러한 센서는 본질적으로 전압을 분압한 후 아날로그 전압을 출력하고, 변환이 완료된 후 이러한 센서의 데이터가 데이터에 균일하게 배치되는 것과 같습니다. 데이터 레지스터를 읽어서 얻을 수 있는 레지스터입니다. 센서가 측정한 값입니다. 이 칩 내의 모든 변환은 완전히 자동화됩니다.
  • 각 센서에는 칩의 품질을 확인하는 데 사용되는 자체 테스트 장치가 있습니다. 자체 테스트가 시작되면 칩은 센서에 가해지는 외부 힘을 시뮬레이션하여 센서 데이터를 더 크게 만듭니다. 평소 보단. 자체 테스트 프로세스: 먼저 자체 테스트를 활성화하고 데이터를 읽은 다음 자체 테스트를 활성화하고 데이터를 읽고 두 데이터를 뺄 수 있으며 결과 데이터를 자체 테스트 응답이라고 합니다. 이 셀프 테스트 응답에 대해 매뉴얼에는 범위가 나와 있습니다. 이 범위 내에 있으면 칩에 문제가 없다는 의미입니다.
  • 차지 펌프(Charge Pump): 차지 펌프 또는 차지 펌프는 부스트 회로입니다.
  • CPOUT 핀에는 외부 커패시터가 필요합니다.
  • 인터럽트 상태 레지스터: 어떤 내부 이벤트가 인터럽트 핀으로 출력되는지 제어할 수 있습니다.
  • FIFO: 데이터 스트림을 캐시할 수 있는 선입선출 레지스터입니다.
  • 구성 레지스터: 다양한 내부 회로를 구성할 수 있습니다.
  • 센서 레지스터: 각 센서의 데이터를 저장하는 데이터 레지스터.
  • 공장 보정됨: 이는 내부 센서가 보정되었음을 의미합니다.
  • 디지털 모션 프로세서(Digital Motion Processor): DMP는 칩에 내장된 자세 계산을 위한 하드웨어 알고리즘입니다. 공식 DMP 라이브러리를 사용하여 자세 계산에 사용할 수 있습니다.
  • FSYNC: 프레임 동기화.

3. 10-1 소프트웨어 I2C 읽기 및 MPU6050 쓰기

3.1 하드웨어 연결

소프트웨어 I2C 통신을 통해 MPU6050 칩 내부의 레지스터를 읽고 씁니다. 구성 레지스터에 쓰면 플러그인 모듈의 데이터를 얻을 수 있습니다. 읽은 데이터가 표시됩니다. OLED에서는 상위 데이터가 장치의 ID 번호입니다. 이 MPU6050의 ID 번호는 0x68로 고정되어 있습니다. 아래에서 왼쪽의 3개는 가속도 센서의 출력 데이터로 각각 X축, Y축, Z축의 가속도이고, 오른쪽의 3개는 자이로스코프 센서의 출력 데이터이고, 이는 X축, Y축, Z축의 각속도입니다.
SCL은 STM32의 PB10 핀에 연결되고, SDA는 PB11 핀에 연결됩니다. 여기서는 소프트웨어 레벨 플리핑이 구현되므로 두 개의 GPIO 포트를 마음대로 연결할 수 있습니다.

3.2 운영결과

이미지_20240406_155156.jpg

3.3 코드 흐름

STM32가 호스트이고 MPU6050이 슬레이브로 마스터-슬레이브 모드입니다.

  1. I2C 통신 계층의 .c 및 .h 모듈 설정
    1. I2C의 기본 GPIO 초기화 작성
    2. 6가지 기본 타이밍 단위: 시작, 종료, 바이트 보내기, 바이트 받기, 응답 보내기, 응답 받기
  2. MPU6050의 .c 및 .h 모듈 생성
    1. I2C 통신 모듈을 기반으로 지정된 주소에서 읽기, 지정된 주소에서 쓰기, 칩 구성을 위한 레지스터 쓰기, 센서 데이터를 얻기 위한 레지스터 읽기를 실현합니다.
  3. 메인.c
    1. MPU6050 모듈 호출, 초기화, 데이터 가져오기 및 데이터 표시

3.4 코드

  1. I2C 코드:
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_10,(BitAction)BitValue);
	Delay_us(10);
}

void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11,(BitAction)BitValue);
	Delay_us(10);
}

uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
	Delay_us(10);
	return BitValue;
}

void MyI2C_Init(void)
{
/*
软件I2C初始化:
	1. 把SCL和SDA都初始化为开漏输出模式;
	2. 把SCL和SDA置高电平;
输入时,先输出1,再直接读取输入数据寄存器就行了;
初始化结束后,调用SetBits,把GPIOB的Pin_10和Pin_11都置高电平,
此时I2C总线处于空闲状态
*/	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
	
}

/*
起始条件:SCL高电平期间,SDA从高电平切换到低电平。
如果起始条件之前,SDA和SCL都已经是高电平了,那先释放哪一个是一样的效果。
但是这个Start还要兼容重复起始条件Sr,Sr最开始,SCL是低电平,SDA电平不敢确定,
所以为保险起见,在SCL低电平时,先确保释放SDA,再释放SCL。
这时SDA和SCL都是高电平,然后再拉低SDA、拉低SCL。
这样这个Start就可以兼容起始条件和重复起始条件了。
*/
void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
}

/*
终止条件:SCL高电平期间,SDA从低电平切换到高电平
如果Stop开始时,SCL和SDA都已经是低电平了,那就先释放SCL,再释放SDA。
但在这个时序单元开始时,SDA并不一定是低电平,所以为了确保之后释放
SDA能产生上升沿,要在时序单元开始时,先拉低SDA,然后再释放SCL、释放SDA。
*/
void MyI2C_Stop(void)// 终止条件
{
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}

/*
发送一个字节:发送一个字节时序开始时,SCL是低电平。
除了终止条件SCL以高电平结束,所有的单元都会保证SCL以低电平结束。
SCL低电平变换数据;高电平保持数据稳定。由于是高位先行,所以变换数据的时候,
按照先放最高位,再放次高位,...,最后最低位的顺序,依次把每一个字节的每一位放在SDA线上,
每放完一位后,执行释放SCL,拉低SCL的操作,驱动时钟运转。
程序:趁SCL低电平,先把Byte的最高位放在SDA线上,
*/

void MyI2C_SendByte(uint8_t Byte) // 发送一个字节
{
	uint8_t i;
	for (i = 0; i < 8; i ++)
	{
		MyI2C_W_SDA(Byte & (0x80 >> i));// 右移i位
	    MyI2C_W_SCL(1);
	    MyI2C_W_SCL(0);
	}
}

/*
接收一个字节:时序开始时,SCL低电平,此时从机需要把数据放到SDA上,
为了防止主机干扰从机写入数据,主机需要先释放SDA,释放SDA相当于切换为输入模式,
那在SCL低电平时,从机会把数据放到SDA上,如果从机想发1,就释放SDA,想发0,就拉低SDA,
主机释放SCL,在SCL高电平期间,读取SDA,再拉低SCL,低电平期间,从机就会把下一位数据放到SDA上,重复8次,
主机就能读到一个字节了。
SCL低电平变换数据,高电平读取数据,实际上是一种读写分离的操作,低电平时间定义为写的时间,高电平时间定义为读的时间,

*/
uint8_t MyI2C_ReceiveByte(void) // 接收一个字节
{
	uint8_t i, Byte = 0x00;
	MyI2C_W_SDA(1);
	for (i = 0; i < 8; i ++)
	{
		MyI2C_W_SCL(1); // 主机读取数据
	    if (MyI2C_R_SDA() == 1) // 如果if成立,接收的这一位为1,
	    {
		    Byte |= (0x80 >> i);   // 最高位置1
	    }
        MyI2C_W_SCL(0);	
	}
	return Byte;
}
/*
问题:反复读取SDA,for循环中又没写过SDA,那SDA读出来应该始终是一个值啊?
回答:I2C是在进行通信,通信是有从机的,当主机不断驱动SCL时钟时,
从机就有义务去改变SDA的电平,所以主机每次循环读取SDA的时候,
这个读取到的数据是从机控制的,这个数据也正是从机想要给我们发送的数据,
所以这个时序叫做接收一个字节。
*/

void MyI2C_SendAck(uint8_t AckBit) // 发送应答
{
	// 函数进来,SCL低电平,主机把AckBit放到SDA上,
	MyI2C_W_SDA(AckBit);
	MyI2C_W_SCL(1);  // 从机读取应答
	MyI2C_W_SCL(0);  // 进入下一个时序单元
	
}

uint8_t MyI2C_ReceiveAck(void) // 接收应答
{
	// 函数进来,SCL低电平,主机释放SDA,防止从机干扰
	uint8_t AckBit;
	MyI2C_W_SDA(1);  // 主机释放SDA
	MyI2C_W_SCL(1);  // SCL高电平,主机读取应答位
	AckBit = MyI2C_R_SDA(); 
	MyI2C_W_SCL(0);	 // SCL低电平,进入下一个时序单元
	return AckBit;
}

/*问题:在程序里,主机先把SDA置1了,然后再读取SDA,
这应答位肯定是1啊,
回答:第一,I2C的引脚是开漏输出+弱上拉的配置,主机输出1,
并不是强制SDA为高电平,而是释放SDA,
第二,I2C是在通信,主机释放了SDA,从机是有义务在此时把SDA再拉低的,
所以,即使主机把SDA置1了,之后再读取SDA,读到的值也可能是0,
读到0,代表从机给了应答,读到1,代表从机没给应答,这就是接收应答的流程。


*/

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  1. MPU6050 코드:
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H

// 宏定义: 寄存器的名称   对应的地址

#define	MPU6050_SMPLRT_DIV		0x19  // 采样率分频
#define	MPU6050_CONFIG			0x1A  // 配置外部帧同步(FSYNC)引脚采样和数字低通滤波器(DLPF)设置
#define	MPU6050_GYRO_CONFIG		0x1B  // 触发陀螺仪自检和配置满量程
#define	MPU6050_ACCEL_CONFIG	0x1C  // 触发加速度计自检和配置满量程

#define	MPU6050_ACCEL_XOUT_H	0x3B  // 存储最新的加速度计测量值
#define	MPU6050_ACCEL_XOUT_L	0x3C
#define	MPU6050_ACCEL_YOUT_H	0x3D
#define	MPU6050_ACCEL_YOUT_L	0x3E
#define	MPU6050_ACCEL_ZOUT_H	0x3F
#define	MPU6050_ACCEL_ZOUT_L	0x40
#define	MPU6050_TEMP_OUT_H		0x41  // 存储最新的温度传感器测量值
#define	MPU6050_TEMP_OUT_L		0x42
#define	MPU6050_GYRO_XOUT_H		0x43  // 存储最新的陀螺仪测量值
#define	MPU6050_GYRO_XOUT_L		0x44
#define	MPU6050_GYRO_YOUT_H		0x45
#define	MPU6050_GYRO_YOUT_L		0x46
#define	MPU6050_GYRO_ZOUT_H		0x47
#define	MPU6050_GYRO_ZOUT_L		0x48

#define	MPU6050_PWR_MGMT_1		0x6B  // 电源管理寄存器1
#define	MPU6050_PWR_MGMT_2		0x6C  // 电源管理寄存器2
#define	MPU6050_WHO_AM_I		0x75  // 用于验证设备身份

#endif

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"

// 宏定义:从机地址
#define MPU6050_ADDRESS  0xD0

// 指定地址写
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);// 发送从机地址后,接收应答
	MyI2C_ReceiveAck();// 寻址找到从机之后,继续发送下一个字节
	MyI2C_SendByte(RegAddress); // 指定寄存器地址,存在MPU6050的当前地址指针里,用于指定具体读写哪个寄存器
	MyI2C_ReceiveAck();
	MyI2C_SendByte(Data);// 指定写入指定寄存器地址下的数据
	MyI2C_ReceiveAck();
	MyI2C_Stop();
}

// 指定地址读
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_ReceiveAck();
	MyI2C_SendByte(RegAddress); // 指定地址:就是设置了MPU6050的当前地址指针
	MyI2C_ReceiveAck();
	// 转入读的时序,重新指定读写位,就必须重新起始
	MyI2C_Start();// 重复起始条件
	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);// 指定从机地址和读写位,0xD0是写地址,或上0x01变为0xD1,读写位为1,接下来要读从机的数据
	MyI2C_ReceiveAck(); // 接收应答后,总线控制权就正式交给从机了,从机开始发送一个字节
	Data = MyI2C_ReceiveByte();// 主机接收一个字节,该函数返回值就是接收到的数据
	// 主机接收一个字节后,要给从机发送一个应答
	MyI2C_SendAck(1);// 参数为0,就是给从机应答,参数给1,就是不给从机应答
	// 如果想继续读多个字节,就要给应答,从机收到应答之后,就会继续发送数据,如果不想继续读了,就不能给从机应答了。
	// 主机收回总线的控制权,防止之后进入从机以为你还想要,但你实际不想要的冲突状态,
	// 这里,只需要读取1个字节,所以就给1,不给从机应答,
	MyI2C_Stop();
	return Data;
}

void MPU6050_Init(void)
{
	MyI2C_Init();
	// 写入一些寄存器对MPU6050硬件电路进行初始化配置
	// 电源管理寄存器1:设备复位:0,不复位;睡眠模式:0,解除睡眠:循环模式:0,不循环;无关位i:0;温度传感器失能:0,不失能;最后三位选择时钟:000,选择内部时钟,001,选择x轴的陀螺仪时钟,
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);// 解除睡眠,选择陀螺仪时钟
	// 电源管理寄存器2:前两位,循环模式唤醒频率:00,不需要;后6位,每一个轴的待机位:全为0,不需要待机;
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00); // 均不待机
	// 采样率分频:该8位决定了数据输出的快慢,值越小越快
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);// 采样分频:10分频
	// 配置寄存器:外部同步:全为0,不需要;数字低通滤波器:110,最平滑的滤波
	MPU6050_WriteReg(MPU6050_CONFIG,0x06);// 滤波参数给最大
	// 陀螺仪配置寄存器:前三位,自测使能:全为0,不自测;满量程选择:11,最大量程;后三位无关位:为0
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);// 陀螺仪和加速度计都选最大量程
	// 加速度计配置寄存器:前三位,自测使能:全为0,不自测;满量程选择:11,最大量程;后三位高通滤波器:用不到,为000
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
		
}

// 获取芯片的ID号
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

// 获取寄存器数据的函数,返回6个int16_t的数据,分别表示XYZ的加速度值和陀螺仪值
// 指针地址传递的方法,返回多值
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
	                 int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	uint8_t DataH, DataL;
	// 加速度计X
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL; // 高8位左移8位,再或上低8位,得到加速度计X轴的16位数据
	// 加速度计Y
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	// 加速度计Z
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	// 陀螺仪X
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	// 陀螺仪Y
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	// 陀螺仪Z
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyI2C.h"
#include "MPU6050.h"

uint8_t ID;
int16_t AX, AY, AZ, GX, GY, GZ;// 接收XYZ轴的加速度值和陀螺仪值



int main(void)
{
	OLED_Init();
//	MyI2C_Init();
	MPU6050_Init();
//	
	OLED_ShowString(1,1,"ID:");
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1, 4, ID, 2);
	
//	// 指定地址写
//	MyI2C_Start(); // 产生起始条件,开始一次传输
//	// 主机首先发送一个字节,内容是从机地址+读写位,进行寻址
//	MyI2C_SendByte(0xD0);  // 1101 000 0,0代表即将进行写入操作
//	// 发送一个字节后,要接收一下应答位,看看从机有没有收到刚才的数据
//	uint8_t Ack = MyI2C_ReceiveAck();
//	// 接收应答之后,要继续发送一个字节,写入寄存器地址
//	MyI2C_Stop();
//	
//	OLED_ShowNum(1, 1, Ack, 3);
	
//	// 指定地址读
//	uint8_t ID = MPU6050_ReadReg(0X75);// 返回值是0x68
//	OLED_ShowHexNum(1, 1, ID, 2);
	
//	// 指定地址写,需要先解除睡眠模式,否则写入无效
//	// 睡眠模式是电源管理寄存器1的这一位SLEEP控制的,把该寄存器写入0x00,解除睡眠模式
//	// 该寄存器地址是0x6B
//	MPU6050_WriteReg(0x6B, 0x00);
//	// 采样率分频寄存器,地址是0x19,值的内容是采样分频
//	MPU6050_WriteReg(0x19, 0xAA);
//	
//	uint8_t ID = MPU6050_ReadReg(0X19);
//	OLED_ShowHexNum(1, 1, ID, 2);//显示0x19地址下的内容,应该是0xAA
	
	while(1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 8, GX, 5);
		OLED_ShowSignedNum(3, 8, GY, 5);
		OLED_ShowSignedNum(4, 8, GZ, 5);
	}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

4. I2C 주변기기

4.1 I2C 주변기기 소개

  • STM32는 하드웨어 I2C 트랜시버 회로를 통합하여 하드웨어에 의한 클록 생성, 시작 및 종료 조건 생성, 응답 비트 전송 및 수신, 데이터 전송 및 수신 등의 기능을 자동으로 수행할 수 있어 CPU의 부담을 줄입니다.
  • 다중 호스트 모델 지원
  • 7비트/10비트 주소 모드 지원
  • 다양한 통신 속도, 표준 속도(최대 100kHz), 고속(최대 400kHz) 지원
  • DMA 지원
  • SMBus 프로토콜과 호환 가능
  • STM32F103C8T6 하드웨어 I2C 리소스: I2C1, I2C2

4.2 I2C 블록 다이어그램

이미지.png

  • 왼쪽에는 통신 핀이 있습니다. SDA 및 SCL은 SMBus에서 사용됩니다.
    일반 주변 장치에서 파생된 핀은 일반적으로 GPIO 포트의 다중화 모드를 통해 외부 세계에 연결됩니다(표 확인).
  • 위는 데이터 제어 부분이다: SDA 데이터 송수신의 핵심 부분은 데이터 레지스터 DR(DATA REGISTER)과 데이터 쉬프트 레지스터이다. 데이터를 전송해야 할 경우 데이터 레지스터 DR에 1바이트의 데이터를 쓸 수 있습니다. 시프트 레지스터에 시프트할 데이터가 없으면 데이터 레지스터의 값이 시프트 레지스터로 추가로 전송됩니다. 이동 프로세스 중에 다음 데이터를 데이터 레지스터에 직접 배치하고 기다릴 수 있습니다. 이전 데이터 이동이 완료되면 다음 데이터도 원활하게 연결되어 계속해서 전송될 수 있습니다. 데이터가 데이터 레지스터에서 시프트 레지스터로 전송되면 상태 레지스터의 TXE 비트가 1로 설정되어 전송 레지스터가 비어 있음을 나타냅니다.
  • 수신: 입력 데이터가 비트 단위로 핀에서 시프트 레지스터로 이동합니다. 1바이트의 데이터가 수집되면 데이터가 전체적으로 시프트 레지스터에서 데이터 레지스터로 전송되고 플래그 RXNE가 해당 위치에 설정됩니다. 동시에 수신을 나타냅니다. 레지스터가 비어 있지 않으면 데이터 레지스터에서 데이터를 읽을 수 있습니다. 언제 수신하고 언제 송신할지에 대해서는 해당 비트를 제어 레지스터에 기록해야 작동이 가능합니다. 시작 조건, 종료 조건, 응답 비트 등은 데이터 제어를 통해 완료됩니다.
  • 비교기와 주소 레지스터는 슬레이브 모드에서 사용됩니다.
  • SCL: 클럭 제어는 SCL 라인을 제어하는 ​​데 사용됩니다. 클록 제어 레지스터에 해당 비트를 쓰면 회로가 해당 기능을 수행합니다. 제어 논리 회로, 제어 레지스터에 기록하면 전체 회로를 제어할 수 있습니다. 상태 레지스터를 읽어 회로의 작동 상태를 알 수 있습니다.
  • 많은 바이트를 보내고 받을 때 DMA를 사용하면 효율성을 높일 수 있습니다.

4.3 I2C 기본 구조

이미지.png

  • SDA: I2C는 상위 우선이므로 이 시프트 레지스터는 왼쪽으로 이동합니다. 전송할 때 상위 비트가 먼저 이동된 다음 두 번째 상위 비트가 이동됩니다. SCL 클럭은 한 번 쉬프트되고 8번 쉬프트되며 SDA 라인에는 높은 비트에서 낮은 비트로 8바이트가 배치될 수 있습니다. 수신 시에는 GPIO 포트를 통해 오른쪽에서부터 데이터가 들어와 최종적으로 8회에 걸쳐 1바이트를 수신하게 된다. 출력 데이터는 GPIO 포트를 통해 포트로 출력됩니다. 입력 데이터는 GPIO 포트를 통해 시프트 레지스터에 입력됩니다.
  • GPIO 포트는 멀티플렉싱 오픈 드레인 출력 모드로 구성되어야 합니다. 멀티플렉싱은 GPIO 포트의 상태가 온칩 주변 장치에 의해 제어되고 오픈 드레인 출력이 I2C 프로토콜에 필요한 포트 구성임을 의미합니다. 오픈 드레인 출력 모드에서도 GPIO 포트를 입력할 수 있습니다.
  • SCL: 클럭 컨트롤러는 GPIO를 통해 클럭 라인을 제어합니다.
    이미지.png

4.4 호스트 전송

이미지.png
STM32가 지정된 주소에 쓰려고 할 때 송신기 전송 시퀀스 다이어그램을 따라야 합니다.

  • 7비트 주소: 시작 조건 이후 1바이트가 주소 지정됩니다.
  • 10비트 주소: 시작 조건 뒤의 2바이트는 주소 지정입니다. 첫 번째 바이트는 프레임 헤더이고 내용은 5비트 플래그 비트 11110 + 2비트 주소 + 1 읽기-쓰기 비트입니다. 순수 8비트 주소.
  • 7비트 프로세스: 시작, 슬레이브 주소, 응답, 데이터, 응답, 데이터, 응답... 중지
  1. 초기화 후 버스는 기본적으로 유휴 상태로 설정되고 STM은 기본적으로 슬레이브 모드로 설정되어 시작 조건을 생성하기 위해 STM32는 제어 레지스터(CR1)에 쓰고 1을 쓴 다음 STM32가 슬레이브 모드에서 마스터 모드로 변경됩니다. .

이미지.png

  1. EV5 이벤트는 플래그 비트로 간주될 수 있습니다. SB는 상태 레지스터의 비트로, SB=1은 시작 조건이 전송되었음을 의미합니다.

이미지.png

  1. 그런 다음 슬레이브 주소 바이트를 데이터 레지스터 DR에 써야 합니다. DR에 쓴 후 하드웨어 회로는 자동으로 주소 바이트를 시프트 레지스터로 전송하고 노드는 다음과 같습니다. I2C 버스로 보내지면 하드웨어는 자동으로 응답을 수신하고 판단합니다. 응답이 없으면 하드웨어는 응답 실패 플래그를 설정한 다음 플래그가 인터럽트를 적용하여 우리에게 상기시킬 수 있습니다.
  2. 주소 지정이 완료되면 EV6 이벤트가 발생하고 ADDR 플래그 비트는 1이 됩니다. 이 플래그 비트는 마스터 모드에서 주소 전송의 끝을 나타냅니다.

이미지.png

  1. EV8_1 이벤트는 TxE 플래그가 1이고 시프트 레지스터가 비어 있으며 데이터 레지스터가 비어 있다는 것입니다. DR에 쓴 후에는 데이터 레지스터 DR에 써야 합니다. 전송을 위해 즉시 시프트 레지스터로 전환됩니다. EV8 이벤트가 발생합니다. 시프트 레지스터가 비어 있지 않고 데이터 레지스터도 비어 있습니다. 이는 시프트 레지스터가 데이터를 전송하고 있음을 의미합니다. 따라서 이 과정에서 데이터 1의 타이밍이 생성됩니다. 이 순간 데이터 2가 데이터 레지스터에 기록되고 응답 비트를 수신한 후 데이터 비트가 전송을 위해 시프트 레지스터로 전송됩니다. 데이터 레지스터가 비어 있어서 이때 EV8 사건이 또 발생했습니다.
  2. 그러면 데이터 2가 전송되고 있는데 이번에는 다음 데이터가 데이터 레지스터에 기록되어 대기 중입니다. EV8 이벤트가 감지되면 다음 데이터를 쓸 수 있습니다.
  3. 전송하려는 데이터가 기록된 후에는 새로운 데이터가 데이터 레지스터에 기록되지 않습니다. 시프트 레지스터의 현재 데이터 시프트가 완료되면 시프트 레지스터가 비어 있고 데이터 레지스터도 비어 있습니다. EV8_2 이벤트, TxE=1은 시프트 레지스터가 비어 있고 데이터 레지스터가 비어 있음을 의미합니다. BTF: 바이트 전송 종료 플래그, 전송할 때 새 데이터가 전송되고 데이터 레지스터에 새 데이터가 기록되지 않았습니다. EV8_2가 감지되면 종료 조건 Stop이 생성될 수 있습니다. 종료 조건을 생성하려면 제어할 수 있는 제어 레지스터에 해당 비트가 있어야 합니다. 이로써 전송 순서가 종료됩니다.

4.5 호스트 리셉션

이미지.png
7비트 마스터 수신: 시작, 슬레이브 주소 + 읽기, 응답 수신, 데이터 수신, 응답 보내기... 데이터 수신, 무응답, 종료

  1. 먼저 제어 레지스터의 시작 비트를 작성하여 시작 조건을 생성한 다음 EV5 이벤트(시작 조건이 전송되었음을 나타냄)를 기다립니다.
  2. 주소 지정 후 응답이 수신되고, 종료 후 EV6 이벤트가 생성됩니다(어드레싱이 완료되었음을 나타냄).
  3. 데이터 1은 시프트 레지스터를 통해 데이터가 입력되고 있음을 의미합니다.
  4. EV6_1은 응답을 받은 후에도 여전히 데이터가 이동 중임을 나타냅니다. 이는 데이터 1의 1바이트에서 이동 레지스터가 성공적으로 이동되었음을 의미합니다. 이때 이동된 바이트는 전체적으로 데이터 레지스터로 전송되며, RxNE 플래그가 동시에 설정되어 데이터 레지스터가 비어 있지 않음을 나타냅니다. 즉, 1바이트의 데이터가 수신되었습니다. 상태는 EV7 이벤트이며, DR 레지스터를 읽으면 이벤트가 지워집니다. 데이터를 수신한 후에는 이벤트가 더 이상 발생하지 않습니다.
  5. 물론, 데이터 1을 읽지 않은 경우에는 데이터 2를 바로 쉬프트 레지스터로 이동할 수 있다. 이후 데이터 2의 쉬프트가 완료되고, 데이터 2가 수신되고, EV7 이벤트가 발생하고, 데이터 2가 읽혀지고, EV7 이벤트가 사라집니다.
  6. 더 이상 수신이 필요하지 않은 경우 마지막 타이밍 단위가 발생할 때 미리 응답 비트 제어 레지스터 ACK를 0으로 설정하고 종료 조건 요청, 즉 EV7_1 이벤트를 설정해야 합니다. STOP 비트가 설정되므로 종료 조건이 생성됩니다.

4.6 소프트웨어/하드웨어 파형 비교

이미지.png

이미지.png

5. 10-2 하드웨어 I2C 읽기 및 쓰기 MPU6050

5.1 I2C 라이브러리 기능


void I2C_DeInit(I2C_TypeDef* I2Cx);
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);

// 生成起始条件、终止条件
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);

// 配置CR1的ACK这一位,0:无应答,1:应答
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);

// 发送数据,把Data数据直接写入到DR寄存器
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);
// 读取DR,接收数据
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);

// Address参数也是通过DR发送的,但在发送之前,设置了Address最低位的读写位,
// I2C_Direction不是发送,是把Address的最低位置1(读),否则最低位清0(写)
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

5.2 하드웨어 I2C 읽기 및 쓰기 MPU6050 구현

5.2.1 하드웨어 연결

SCL은 STM32의 PB10 핀에 연결되고, SDA는 PB11 핀에 연결됩니다. 여기서는 소프트웨어 레벨 플리핑이 구현되므로 두 개의 GPIO 포트를 마음대로 연결할 수 있습니다.
OLED의 상위 데이터는 장치의 ID 번호입니다. 이 MPU6050의 ID 번호는 0x68로 고정되어 있습니다. 아래에서 왼쪽의 3개는 가속도 센서의 출력 데이터로 각각 X축, Y축, Z축의 가속도이고, 오른쪽의 3개는 자이로스코프 센서의 출력 데이터이고, 이는 X축, Y축, Z축의 각속도입니다.

5.2.2 운영 결과

이미지_20240406_172128.jpg

5.2.3 코드 구현 프로세스

  1. I2C 주변 장치 구성, I2C 주변 장치 초기화, MyI2C_Init 교체
    (1) I2C 주변장치와 해당 GPIO 포트의 클럭을 켜고,
    (2) I2C 주변 장치에 해당하는 GPIO 포트를 다중화 오픈 드레인 모드로 초기화합니다.
    (3) 구조를 사용하여 I2C 전체를 구성합니다.
    (4) I2C_Cmd, I2C 활성화
  2. 주변 회로 제어, 특정 주소에 대한 쓰기 타이밍 구현, WriteReg 교체
  3. 주변 회로를 제어하여 지정된 주소를 읽는 타이밍을 구현하고 ReadReg를 교체합니다.

5.2.4 코드

  1. MPU6050 코드:
#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"

/*
1. 配置I2C外设,对I2C外设进行初始化,替换MyI2C_Init
   (1)开启I2C外设和对应GPIO口的时钟,
   (2)把I2C外设对应的GPIO口初始化为复用开漏模式
   (3)使用结构体,对整个I2C进行配置
   (4)I2C_Cmd,使能I2C
2. 控制外设电路,实现指定地址写的时序,替换WriteReg
3. 控制外设电路,实现指定地址读的时序,替换ReadReg
*/


// 宏定义:从机地址
#define MPU6050_ADDRESS  0xD0
 
// 超时退出
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t TimeOut;
	TimeOut = 10000;
	while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS) 
	{   
		TimeOut --;
		if (TimeOut == 0)
		{
			break;// 跳出循环,直接执行后面的程序
		}
	}
}

// 指定地址写:发送器传送时序
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
//	MyI2C_Start();
//	MyI2C_SendByte(MPU6050_ADDRESS);// 发送从机地址后,接收应答
//	MyI2C_ReceiveAck();// 寻址找到从机之后,继续发送下一个字节
//	MyI2C_SendByte(RegAddress); // 指定寄存器地址,存在MPU6050的当前地址指针里,用于指定具体读写哪个寄存器
//	MyI2C_ReceiveAck();
//	MyI2C_SendByte(Data);// 指定写入指定寄存器地址下的数据
//	MyI2C_ReceiveAck();
//	MyI2C_Stop();
	
	I2C_GenerateSTART(I2C2, ENABLE); // 起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //EV5事件
	
	// 发送从机地址,接收应答。该函数自带了接收应答,如果应答错误,硬件会通过标志位和中断来提示我们
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //EV6事件
	
	// 直接写入DR,发送数据
	I2C_SendData(I2C2, RegAddress);
	// 写入了DR,DR立刻转移到移位寄存器进行发送,EV8事件出现的非常快,基本不用等。因为有两级缓存,
	// 第一个数据写进DR了,会立刻跑到移位寄存器,这时不用等第一个数据发完,第二个数据就可以写进去等着了。
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING); //EV8事件
	
	I2C_SendData(I2C2, Data);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //EV8_2事件
	
	I2C_GenerateSTOP(I2C2, ENABLE);
}

// 指定地址读:接收器传送序列
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
//	MyI2C_Start();
//	MyI2C_SendByte(MPU6050_ADDRESS);
//	MyI2C_ReceiveAck();
//	MyI2C_SendByte(RegAddress); // 指定地址:就是设置了MPU6050的当前地址指针
//	MyI2C_ReceiveAck();
//	// 转入读的时序,重新指定读写位,就必须重新起始
//	MyI2C_Start();// 重复起始条件
//	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);// 指定从机地址和读写位,0xD0是写地址,或上0x01变为0xD1,读写位为1,接下来要读从机的数据
//	MyI2C_ReceiveAck(); // 接收应答后,总线控制权就正式交给从机了,从机开始发送一个字节
//	Data = MyI2C_ReceiveByte();// 主机接收一个字节,该函数返回值就是接收到的数据
//	// 主机接收一个字节后,要给发送从机一个应答
//	MyI2C_SendAck(1);// 参数为0,就是给从机应答,参数给1,就是不给从机应答
//	MyI2C_Stop();
	
	
	I2C_GenerateSTART(I2C2, ENABLE); // 起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //EV5事件
	
	// 发送从机地址,接收应答。该函数自带了接收应答,如果应答错误,硬件会通过标志位和中断来提示我们
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //EV6事件
	
	// 直接写入DR,发送数据
	I2C_SendData(I2C2, RegAddress);
	// 写入了DR,DR立刻转移到移位寄存器进行发送,EV8事件出现的非常快,基本不用等。因为有两级缓存,
	// 第一个数据写进DR了,会立刻跑到移位寄存器,这时不用等第一个数据发完,第二个数据就可以写进去等着了。
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //EV8_2事件
	
	I2C_GenerateSTART(I2C2, ENABLE);// 重复起始条件
	
	// 主机接收
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //EV5事件
	// 接收地址
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver); // 函数内部就自动将该地址MPU6050_ADDRESS的最低位置1
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); //EV6事件
	
	// 在最后一个数据之前就要把应答位ACK置0,同时把停止条件生成位STOP置1
	I2C_AcknowledgeConfig(I2C2, DISABLE);
	I2C_GenerateSTOP(I2C2, ENABLE);
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED); //EV7事件
	// 等EV7事件产生后,一个字节的数据就已经在DR里面了。
	// 读取DR就可拿出该字节
	Data = I2C_ReceiveData(I2C2); // 返回值就是DR的数据
	// 在接收函数的最后,要恢复默认的ACK = 1。
	// 默认状态下ACK就是1,给从机应答,在收最后一个字节之前,临时把ACK置0,给非应答,
	// 所以在接收函数的最后,要恢复默认的ACK = 1,这个流程是为了方便指定地址收多个字节。
	I2C_AcknowledgeConfig(I2C2, ENABLE);
	
	return Data;
}

void MPU6050_Init(void)
{
	
//	MyI2C_Init();
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 复用开漏
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; // 模式
	I2C_InitStructure.I2C_ClockSpeed = 50000; // 时钟速度,最大400kHz的时钟频率
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	// 时钟占空比,只有在时钟频率大于100kHz,也就是进入到快速状态时才有用,小于100kHz,占空比是固定的1:1,
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // STM32作为从机,可以响应几位的地址
	I2C_InitStructure.I2C_OwnAddress1 = 0x00; // 自身地址1,也是作为从机使用,
	I2C_Init(I2C2, &I2C_InitStructure); 
	
	I2C_Cmd(I2C2,ENABLE);
	
	// 写入一些寄存器对MPU6050硬件电路进行初始化配置
	// 电源管理寄存器1:设备复位:0,不复位;睡眠模式:0,解除睡眠:循环模式:0,不循环;无关位i:0;温度传感器失能:0,不失能;最后三位选择时钟:000,选择内部时钟,001,选择x轴的陀螺仪时钟,
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);// 解除睡眠,选择陀螺仪时钟
	// 电源管理寄存器2:前两位,循环模式唤醒频率:00,不需要;后6位,每一个轴的待机位:全为0,不需要待机;
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00); // 均不待机
	// 采样率分频:该8位决定了数据输出的快慢,值越小越快
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);// 采样分频:10分频
	// 配置寄存器:外部同步:全为0,不需要;数字低通滤波器:110,最平滑的滤波
	MPU6050_WriteReg(MPU6050_CONFIG,0x06);// 滤波参数给最大
	// 陀螺仪配置寄存器:前三位,自测使能:全为0,不自测;满量程选择:11,最大量程;后三位无关位:为0
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);// 陀螺仪和加速度计都选最大量程
	// 加速度计配置寄存器:前三位,自测使能:全为0,不自测;满量程选择:11,最大量程;后三位高通滤波器:用不到,为000
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
		
}

// 获取芯片的ID号
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

// 获取寄存器数据的函数,返回6个int16_t的数据,分别表示XYZ的加速度值和陀螺仪值
// 指针地址传递的方法,返回多值
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
	                 int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	uint8_t DataH, DataL;
	// 加速度计X
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL; // 高8位左移8位,再或上低8位,得到加速度计X轴的16位数据
	// 加速度计Y
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	// 加速度计Z
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	// 陀螺仪X
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	// 陀螺仪Y
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	// 陀螺仪Z
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  1. 메인.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"

uint8_t ID;
int16_t AX, AY, AZ, GX, GY, GZ;// 接收XYZ轴的加速度值和陀螺仪值



int main(void)
{
	OLED_Init();
	MPU6050_Init();
	
	OLED_ShowString(1,1,"ID:");
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1, 4, ID, 2);
	
//	// 指定地址写
//	MyI2C_Start(); // 产生起始条件,开始一次传输
//	// 主机首先发送一个字节,内容时从机地址+读写位,进行寻址
//	MyI2C_SendByte(0xD0);  // 1101 000 0,0代表即将进行写入操作
//	// 发送一个字节后,要接收一下应答位,看看从机有没有收到刚才的数据
//	uint8_t Ack = MyI2C_ReceiveAck();
//	// 接收应答之后,要继续发送一个字节,写入寄存器地址
//	MyI2C_Stop();
//	
//	OLED_ShowNum(1, 1, Ack, 3);
	
//	// 指定地址读
//	uint8_t ID = MPU6050_ReadReg(0X75);// 返回值是0x68
//	OLED_ShowHexNum(1, 1, ID, 2);
	
//	// 指定地址写,需要先解除睡眠模式,否则写入无效
//	// 睡眠模式是电源管理寄存器1的这一位SLEEP控制的,把该寄存器写入0x00,解除睡眠模式
//	// 该寄存器地址是0x6B
//	MPU6050_WriteReg(0x6B, 0x00);
//	// 采样率分频寄存器,地址是0x19,值的内容是采样分频
//	MPU6050_WriteReg(0x19, 0xAA);
//	
//	uint8_t ID = MPU6050_ReadReg(0X19);
//	OLED_ShowHexNum(1, 1, ID, 2);//显示0x19地址下的内容,应该是0xAA
	
	
	
	while(1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 8, GX, 5);
		OLED_ShowSignedNum(3, 8, GY, 5);
		OLED_ShowSignedNum(4, 8, GZ, 5);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57