Обмен технологиями

STM32-I2C

2024-07-12

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

Этот контент основан наТехнология Цзянси STM32Составлено после видео исследования.

Каталог статей

1. Связь I2C

1.1 Введение в связь I2C

  • I2C (Inter IC Bus) — универсальная шина данных, разработанная Philips.
  • Две линии связи: последовательная линия синхронизации SCL (Serial Clock), последовательная линия передачи данных SDA (Serial Data).
  • Синхронный, полудуплексный, несимметричный, для нескольких устройств
  • Ответить с данными
  • Поддерживает установку нескольких устройств на шине (одно ведущее и несколько ведомых, несколько главных и несколько ведомых)
    • Один ведущий, несколько ведомых: микроконтроллер выступает в роли хоста и доминирует над работой шины I2C. Все внешние модули, установленные на шине I2C, являются ведомыми. без разрешения шины I2C для предотвращения конфликтов.
    • Мультиведущий и мультиведомый: любой модуль на шине может активно выступать в роли ведущего. При возникновении конфликта шины протокол I2C проводит арбитраж. Сторона, выигравшая арбитраж, получает контроль над шиной, а проигравшая сторона автоматически становится подчиненной.

изображение.png

1.2 Аппаратная схема

  • SCL всех устройств I2C соединены вместе, а SDA подключен вместе.
  • И SCL, и SDA устройства должны быть настроены в режиме выхода с открытым стоком.
  • Добавьте подтягивающий резистор к каждому из SCL и SDA, значение сопротивления обычно составляет около 4,7 кОм.

Рисунок 1фигура 2
изображение.png

  • Один ведущий и несколько ведомых: ЦП представляет собой однокристальный микрокомпьютер. В качестве ведущего устройства он обеспечивает полный контроль над линией SCL. Ведущий имеет полный контроль над линией SCL в любое время. Кроме того, в состоянии ожидания хост может активно инициировать управление SDA. Только когда ведомое устройство отправляет данные и ведомое устройство отвечает, хост передает управление SDA ведомому устройству.
  • Управляемая микросхема представляет собой подчиненное устройство, установленное на шине I2C, которым может быть датчик ориентации, OLED, память, модуль часов и т. д. Мощность ведомого устройства относительно невелика. Что касается линии синхронизации SCL, оно может только пассивно читать в любое время. Ведомому устройству не разрешено управлять линией 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 и SDA переводятся на высокий уровень с помощью внешних подтягивающих резисторов, и шина находится в режиме ожидания. в спокойном состоянии. Когда хосту необходимо отправить и получить данные, он должен сначала нарушить молчание шины и сгенерировать условие запуска, то есть SCL находится на высоком уровне, не касаясь его, а затем потянуть SDA вниз, чтобы сгенерировать спадающий фронт. Когда ведомое устройство захватывает высокий уровень SCL и сигнал заднего фронта SDA, оно сбрасывается и ждет вызова ведущего устройства. После спадающего фронта SDA хосту необходимо снова отключить SCL. С одной стороны, он занимает шину, а с другой стороны, это также облегчает соединение базового блока. Позже будет обеспечено, что, за исключением условий запуска и остановки, SCL каждого последовательного блока начинается с низкого уровня и заканчивается низким уровнем.
  • В состоянии завершения : SCL отпускает первым и восстанавливается до высокого уровня, затем SDA отпускает и восстанавливается до высокого уровня, создавая нарастающий фронт, который запускает условие завершения. После условия одновременного завершения SCL и SDA имеют высокий уровень и возвращаются в исходное спокойное состояние.
    Запуск и остановка генерируются хостом, а ведомому устройству не разрешено генерировать запуск и остановку. Поэтому, когда шина простаивает, ведомый всегда должен освобождать руки и не имеет права выпрыгивать и трогать шину.

1.3.2 Отправка байта

  • Отправить байт: Во время низкого уровня SCL хост последовательно помещает биты данных в линию SDA (сначала старший бит), а затем освобождает SCL. Ведомое устройство будет читать биты данных во время высокого уровня SCL, поэтому SDA не разрешен. чтобы иметь какие-либо данные на высоком уровне вероятности нежелательной почты. Когда данные изменяются, выполните описанный выше процесс 8 раз, чтобы отправить один байт.

Хост низкого уровня помещает данные, а ведомый узел высокого уровня считывает данные.
изображение.png
После условия запуска первый байт также должен быть отправлен хостом. Когда SCL низкий, если хост хочет отправить 0, он переводит SDA на низкий уровень, если он хочет отправить 1, он отпускает, и SDA восстанавливается до высокого уровня; Во время низкого уровня SCL уровень SDA может изменяться. После размещения данных хост освобождает линию синхронизации, и уровень SCL восстанавливается до высокого уровня. На высоком уровне это время, когда ведомое устройство считывает SDA, поэтому на высоком уровне SDA не может изменяться. После того, как SCL достигнет высокого уровня, ведомому устройству необходимо прочитать SDA как можно быстрее. Обычно ведомое устройство завершает чтение по нарастающему фронту SCL. Поскольку тактовый сигнал контролируется ведущим устройством, ведомое устройство не знает, когда наступит спадающий фронт, поэтому ведомое устройство будет считывать данные по нарастающему фронту сигнала SCL. После того, как хост на какое-то время отпускает SCL, он может продолжать устанавливать низкий уровень SCL и передавать следующий бит. Хосту также необходимо поместить данные в SDA как можно скорее после спада SCL. Но хост имеет контроль над тактовой частотой, поэтому ему нужно помещать данные в SDA только в тот момент, когда низкий уровень низкий. После освобождения данных хост снова освобождает SCL, SCL становится высоким, и ведомое устройство считывает этот бит. Зациклите этот процесс: хост понижает уровень SCL, помещает данные в SDA, хост освобождает SCL, а ведомое устройство считывает данные SDA. При синхронизации SCL ведущий передает, а ведомый принимает последовательно. После 8 циклов отправляются 8-битные данные, составляющие один байт.
Поскольку сначала идет старший бит, первый бит является старшим битом B7 байта, а младший бит B0 отправляется последним.

1.3.3 Получить байт

  • получить байт: Во время низкого уровня SCL ведомое устройство последовательно помещает биты данных в линию SDA (сначала старший бит), а затем освобождает SCL. Хост будет читать биты данных во время высокого уровня SCL, поэтому SDA не разрешен. чтобы иметь какие-либо данные на высоком уровне SCL. Когда данные изменяются, выполните описанный выше процесс 8 раз, чтобы получить один байт (хост должен освободить SDA перед получением).

Подчиненное устройство низкого уровня передает данные, хост высокого уровня считывает данные.
изображение.png
Линия SDA: ведущему необходимо освободить SDA перед получением. В это время ведомое устройство получает контроль над SDA. Если ведомому устройству необходимо отправить 0, оно устанавливает низкий уровень SDA. Если ведомому устройству необходимо отправить 1, оно отпускает и SDA. восстанавливается до высокого уровня. Низкий уровень преобразует данные, высокий уровень считывает данные. Сплошная линия представляет уровень, контролируемый ведущим устройством, а пунктирная линия представляет уровень, контролируемый ведомым устройством. SCL контролируется хостом на протяжении всего процесса, а хост SDA должен быть освобожден перед приемом и передан на управление подчиненному устройству. Поскольку тактовый сигнал SCL контролируется хостом, преобразование данных ведомого устройства в основном выполняется по заднему фронту SCL, и хост может читать в любое время, когда уровень SCL высокий.

1.3.4 Отправка и получение ответа

  • Отправить ответ: После получения одного байта хост отправляет один бит данных в следующий такт. Данные 0 указывают на ответ, а данные 1 указывают на отсутствие ответа.
  • получить ответ: После того, как хост отправит байт, он получает бит данных в следующий такт, чтобы определить, отвечает ли подчиненное устройство. Данные 0 указывают на ответ, данные 1 указывают на отсутствие ответа (хост должен выпустить SDA перед получением).

изображение.png
То есть после вызова времени отправки байта должно следовать время вызова ответа на прием, который используется для определения того, получил ли ведомый только что переданные ему данные. Если ведомое устройство получает его, то в бите ответа, когда ведущее устройство отпускает SDA, ведомое устройство должно немедленно опустить SDA, а затем во время высокого уровня SCL хост считывает бит ответа. Если бит ответа равен 0, это означает, что ведомое устройство действительно его получило.
При получении байта необходимо вызвать ответ на отправку. Цель отправки ответа — сообщить ведомому устройству, хотите ли вы продолжить отправку. Если ведомая машина получит ответ от ведущего после отправки части данных, ведомая машина продолжит отправку. Если ведомая машина не получит ответ от главной машины, ведомая машина будет думать, что часть данных получена. было отправлено, но главная машина меня игнорирует. Возможно, хост этого не хочет. В это время подчиненное устройство послушно освободит SDA и передаст управление SDA, чтобы предотвратить вмешательство в последующие операции хоста.

1.4 Синхронизация I2C

1.4.1 Укажите адрес для записи

  • Укажите адрес для записи
  • Для указанного устройства (Slave Address) запишите указанные данные (Data) по указанному адресу (Reg Address) (то есть адресу регистра указанного устройства)

изображение.png
процесс:
(1) Начальные условия
(2) Время отправки байта — 0xD0 (ведомый адрес (7 бит) + запись (1 бит)-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 бит. Отправка подчиненного адреса предназначена для определения объекта связи, а отправка битов чтения и записи — для подтверждения, следует ли записывать или читать дальше. Теперь хост отправляет часть данных. Содержимое байта преобразуется в шестнадцатеричное. Первым идет старший бит, который равен 0xD0. Следующий бит — это бит ответа (RA) принимающего ведомого устройства. Бит записи заканчивается, и уровень SCL устанавливается на низкий уровень. После этого хост должен выпустить SDA, а затем бит подтверждения RA.
  • Высокий уровень после окончания ответного бита RA генерируется ведомым устройством, отпускающим SDA. спадающий фронт SCL произошел почти одновременно.
  • Если после завершения ответа вы продолжите отправлять один байт, второй байт может быть отправлен внутрь назначенного устройства. Подчиненное устройство может определить использование второго собственного и последующих байтов. Как правило, второй байт может быть адресом регистра или словом управления командой и т. д., а третий байт — это содержимое, которое хост хочет записать по адресу регистра (второй байт).
  • P — стоповый бит.

Цель этого кадра данных: для устройства, которое указывает адрес подчиненного устройства 1101000, записать данные 0xAA в его внутренний регистр по адресу 0x19.
0 означает: хост выполнит операцию записи в последующее время;
1 означает: хост выполнит операцию считывания в последующей временной последовательности;

1.4.2 Чтение текущего адреса

  • Текущий адрес прочитан
  • Для указанного устройства (Адрес ведомого) прочтите данные ведомого устройства (Данные) по адресу, указанному указателем текущего адреса.

изображение.png
процесс:
(1) Начальные условия
(2) Время отправки байта — 0xD1 (ведомый адрес (7 бит) + чтение (1 бит)-1) (1101 0001)
(3) Получение ответа: RA = 0 (получение ответа от ведомого устройства)
(4) Чтение данных подчиненного устройства: 0x0F (0000 1111)
(7) Отправить ответ: SA = 0
(8) Стоповый бит P (условие завершения)

  • Бит чтения и записи равен 1, что указывает на то, что должна быть выполнена следующая операция чтения. После того как ведомое устройство ответит (RA=0), направление передачи данных изменится на противоположное. Ведущий хочет передать управление SDA ведомому, и ведущий вызывает время приема байта для выполнения операции приема.
  • Во втором байте ведомое устройство получает разрешение от ведущего устройства и может осуществлять запись в SCL на низком уровне SCL. Ведущее устройство считывает SDA на высоком уровне SCL. Наконец, ведущее устройство последовательно читает на высоком уровне SCL. 8 бит, принимается один байт данных, отправленных ведомым устройством, который равен 0x0F. А какой регистр слейва 0x0F? Что касается времени чтения, протокол I2C предусматривает, что при адресации хоста флаг чтения и записи устанавливается в 1. Следующий байт немедленно переключится на время чтения. Следовательно, хост начнет получать до того, как успеет указать, какой регистр он хочет прочитать, поэтому ссылки для указания адреса здесь нет. В ведомой машине все регистры выделены в линейную область, и будет отдельная переменная-указатель, указывающая один из регистров. Этот указатель по умолчанию указывает на включение питания, обычно указывает на адрес 0 и каждый раз, когда записывается байт. После прочтения байта указатель автоматически увеличится один раз и перейдет на следующую позицию. Затем при вызове тайминга чтения текущего адреса, если хост не укажет, какой адрес читать, слейв вернется в регистр, на который указывает байт. текущее значение.

1.4.3 Чтение по указанному адресу

  • Укажите адрес для чтения
  • Для указанного устройства (Slave Address) по указанному адресу (Reg Address) прочитайте данные подчиненного устройства (Data).

изображение.png
Сначала начните, затем повторите запуск, затем остановитесь
процесс:
(1) Начальные условия
(2) Время отправки байта — 0xD0 (ведомый адрес (7 бит) + запись (1 бит)-0) (1101 0000)
(3) Получение ответа: RA = 0 (получение ответа от ведомого устройства)
(4) Указанный адрес: 0x19 (0001 1001).
(5) Получение ответа: RA = 0 (получение ответа от ведомого устройства)
(6) Повторите начальное условие.
(7) Время отправки байта — 0xD1 (ведомый адрес (7 бит) + чтение (1 бит)-1) (1101 0001)
(8) Получение ответа: RA = 0
(9) Чтение данных подчиненного устройства: 0xAA (1010 1010)
(10) Отправить ответ: SA = 0
(11) Стоповый бит P (условие завершения)

  • Первая часть — запись по указанному адресу, но указан только адрес, а времени на запись нет. Вторая часть — чтение текущего адреса, поскольку адрес только что был указан, поэтому читается текущий адрес; позвонил еще раз.
  • Указанный адрес подчиненного устройства равен 1101000, флаг чтения-записи равен 0, и после ответа подчиненного устройства записывается еще один байт (второй байт), указывающий адрес 0x19. указатель адреса, то есть после того, как ведомое устройство получит данные, указатель его регистра указывает на позицию 0x19.
  • Sr — это повторяющееся условие запуска, что эквивалентно запуску нового времени, поскольку указанный флаг чтения и записи может следовать только за первым байтом условия запуска, поэтому, если вы хотите переключить направление чтения и записи, вы можете только это сделать. другое стартовое условие.
  • Затем после условия запуска переадресуйте и укажите бит флага чтения-записи. В это время бит флага чтения-записи равен 1, что указывает на то, что он должен быть прочитан. Затем хост получает байт, который является данными. 0xAA по адресу 0x19.

2. МПУ6050

2.1 Знакомство с MPU6050

  • MPU6050 — это 6-осевой датчик положения, который может измерять параметры ускорения и угловой скорости собственных осей X, Y и Z. Благодаря объединению данных можно дополнительно получить угол положения (угол Эйлера). Он часто используется в . балансировка транспортных средств, самолетов и т. д., которым необходимо обнаружить сцену жеста.
  • 3-осевой акселерометр (Акселерометр): измеряет ускорение по осям X, Y и Z.
  • 3-осевой гироскоп (гироскоп): измеряет угловую скорость по осям X, Y и Z.

изображение.png

  • Если взять в качестве примера фюзеляж самолета, то угол Эйлера — это угол между фюзеляжем самолета и первоначальными тремя осями.
    • самолетНос самолета наклоняется вниз или вверх, угол между этой осью называетсяПодача
    • самолетФюзеляж кренится влево или вправо, угол между этой осью называетсяРулон
    • самолетДержите фюзеляж на уровнеПоверните нос самолета влево или вправо, угол между этой осью называетсярыскание
    • Угол Эйлера отражает положение самолета в данный момент, независимо от того, наклонен ли он вверх или вниз, влево или вправо.
  • Общие алгоритмы объединения данных обычно включают дополнительную фильтрацию, фильтрацию Калмана и т. д., а также расчет ориентации в инерциальной навигации.
  • Акселерометр : Пунктирная линия посередине — это ось индукции. В середине находится небольшой ползунок определенной массы, который может скользить влево и вправо. Слева и справа от него находится пружина. Когда ползунок перемещается, он приводит в движение потенциометр, расположенный на нем. Этот потенциометр представляет собой резистор, делящий напряжение. Измеряя выходное напряжение потенциометром, вы можете получить значение ускорения небольшого ползунка. Этот акселерометр на самом деле представляет собой пружинный динамометр. Согласно второму закону Ньютона F = ma. Если вы хотите измерить ускорение a, вы можете найти объект с единичной массой и измерить силу F. Вот и все. На каждой из осей X, Y и Z имеется акселерометр. Акселерометры обладают статической стабильностью, но не динамической стабильностью.
  • Гироскопический датчик : В середине находится вращающееся колесо с определенной массой. Когда вращающееся колесо вращается с высокой скоростью, в соответствии с принципом сохранения углового момента вращающееся колесо имеет тенденцию сохранять свой первоначальный угловой момент. Эта тенденция может сохраняться. направление оси вращения не изменилось. Когда направление внешнего объекта вращается, направление внутренней оси вращения не будет вращаться, что приведет к угловому отклонению при соединении балансировочного кольца. Если присоединить к соединению вращающийся потенциометр и измерить напряжение потенциометра, можно получить угол поворота. Гироскоп должен иметь возможность напрямую определять угол, но гироскоп этого MPU6050 не может напрямую измерять угол. Он измеряет угловую скорость, то есть угловую скорость вращения чипа вокруг осей X, Y и Z. -ось. Интеграл угловой скорости — это угол. Однако, когда объект неподвижен, значение угловой скорости не может быть полностью возвращено к нулю из-за шума. Тогда после непрерывного накопления интегралов этот небольшой шум приведет к медленному дрейфу рассчитанного угла. который представляет собой угол, полученный путем интегрирования угловой скорости. Он не выдерживает испытания временем, но этот угол не представляет проблемы, независимо от того, является ли он неподвижным или движущимся, и на него не влияет движение объекта. Гироскопы обладают динамической, а не статической стабильностью.
  • Согласно акселерометру, который имеет статическую стабильность, но не имеет динамической стабильности; гироскоп имеет динамическую стабильность, но не имеет статической стабильности. Эти две характеристики позволяют нам учиться на сильных сторонах друг друга и дополнять слабые стороны друг друга, выполняя дополнительную фильтрацию. , мы можем объединить как статическую, так и динамическую устойчивость. Поза неудобная.

2.2 Параметры MPU6050

  • 16-битный АЦП собирает аналоговый сигнал датчика, диапазон квантования: -32768~32767.
  • Выбор полной шкалы акселерометра: ±2, ±4, ±8, ±16 (g) (1g = 9,8 м/с2)
  • Полномасштабный выбор гироскопа: ±250, ±500, ±1000, ±2000 (°/сек, градус/секунда, единица угловой скорости, сколько градусов вращения в секунду) (чем больше полномасштабный выбор, тем шире диапазон диапазон измерения: чем меньше полномасштабный диапазон, тем выше будет разрешение измерения.
  • Настраиваемый цифровой фильтр нижних частот: регистр можно настроить для выбора фильтрации нижних частот выходных данных.
  • Настраиваемый источник синхронизации
  • Настраиваемое разделение частоты дискретизации: источник тактового сигнала можно разделить с помощью делителя частоты, чтобы обеспечить тактовый сигнал для преобразования АЦП и других внутренних схем. Управляя коэффициентом деления частоты, вы можете контролировать скорость AD-преобразования.
  • Адрес подчиненного устройства I2C: 1101000 (AD0=0) или 1101001 (AD0=1)
    • 110 1000 преобразуется в шестнадцатеричное число, то есть 0x68, поэтому некоторые говорят, что подчиненный адрес MPU6050 равен 0x68. Но при связи I2C старшие 7 бит первого байта — это адрес подчиненного устройства, а младший бит — бит чтения и записи. Поэтому, если вы считаете, что 0x68 — это адрес подчиненного устройства, при отправке первого байта вы должны сначала изменить его. 0x68 Сдвиг влево на 1 бит (0x68 << 1), затем чтение и запись побитно или вверх, чтение 1 и запись 0.
    • Другой метод заключается в сдвиге данных 0x68 влево на 1 бит (0x68 << 1) в качестве подчиненного адреса, который равен 0xD0. В этом случае подчиненный адрес MPU6050 равен 0xD0. В настоящее время при фактической отправке первого байта, если вы хотите записать, просто используйте 0xD0 в качестве первого байта, если вы хотите прочитать, используйте 0xD0 или 0x01 (0xD0 | 0x01), то есть 0xD1 в качестве первого байта. . Это представление не требует операции сдвига влево, или, другими словами, это представление объединяет биты чтения и записи в адрес подчиненного устройства. 0xD0 — адрес записи, а 0xD1 — адрес чтения.

2.3 Аппаратная схема

изображение.png

приколотьФункция
VCC, земляисточник питания
SCL, SDAКонтакт связи I2C
XCL, XDAКонтакты связи хоста I2C
АД0Младший бит адреса подчиненного устройства
ИНТВыход сигнала прерывания
  • LDO: линейный стабилизатор напряжения с низким падением напряжения, стабилизатор напряжения 3,3 В.
  • SCL и SDA: это контакты связи I2C. Модуль имеет встроенные два подтягивающих резистора 4,7 кОм, поэтому при подключении просто подключите SDA и SCL непосредственно к порту GPIO. Нет необходимости подключать внешние подтягивающие резисторы. .
  • XCL, XDA: контакты связи хоста I2C. Эти два контакта предназначены для расширения функций чипа. Обычно используется для внешних магнитометров или барометров. Когда эти микросхемы расширения подключены, главный интерфейс MPU6050 может напрямую обращаться к данным этих микросхем расширения и считывать данные этих микросхем расширения в MPU6050, оснащенный блоком DMP. расчет отношения.
    Вывод AD0: это младший бит адреса ведомого устройства. Если он подключен к низкому уровню, 7-битный адрес ведомого устройства равен 1101000; если он подключен к высокому уровню, 7-битный адрес ведомого устройства равен 1101001. На принципиальной схеме есть резистор, который по умолчанию слабо притянут к низкому уровню, поэтому, если вывод остается плавающим, это низкий уровень. Если вы хотите подключить его к высокому уровню, вы можете напрямую подключить AD0 к VCC. и сильно потяните его вверх до высокого уровня.
  • INT: вывод прерывания. Вы можете настроить некоторые события внутри чипа для запуска вывода вывода прерывания, например, готовность данных, ошибка хоста I2C и т. д.
  • Чип также имеет встроенные функции: обнаружение свободного падения, обнаружение движения, обнаружение нулевого движения и т. д. Эти сигналы могут запускать вывод INT для генерации перехода уровня, а сигналы прерывания можно настроить при необходимости.
  • Питание микросхемы MPU6050 составляет 2,375-3,46 В, что является устройством питания 3,3 В и не может быть напрямую подключено к 5 В. Поэтому добавляется стабилизатор напряжения 3,3 В, а напряжение на входной клемме VCC_5 В может находиться в диапазоне от 3,3 В до 5 В. Затем регулятор напряжения 3,3 В выдает стабильное напряжение 3,3 В для питания чипа, пока на клемму 3,3 В подается питание. , Загорится индикатор питания.

2.4 Блок-схема MPU6050

изображение.png

  • CLKIN и CLKOUT — это входные и выходные контакты тактового сигнала, но обычно мы используем внутренние тактовые сигналы.
  • Серая часть: датчик внутри чипа, акселерометр по оси XYZ и гироскоп по оси XYZ.
  • Также имеется встроенный датчик температуры, который можно использовать для измерения температуры.
  • Эти датчики по сути эквивалентны переменным резисторам. После деления напряжения они выдают аналоговое напряжение, а затем выполняют аналого-цифровое преобразование через АЦП. После завершения преобразования данные с этих датчиков равномерно помещаются в данные. регистр, который можно получить, прочитав регистр данных. Значение, измеренное датчиком. Все преобразования внутри этого чипа полностью автоматизированы.
  • Каждый датчик имеет блок самотестирования, который используется для проверки качества чипа. При запуске самотестирования чип имитирует внешнюю силу, действующую на датчик. Эта внешняя сила приводит к искажению данных датчика. больше, чем обычно. Процесс самотестирования: сначала вы можете включить самотестирование, прочитать данные, затем включить самотестирование, прочитать данные, вычесть два данных, и полученные данные называются ответом самотестирования. Для этого ответа самотестирования в руководстве указан диапазон. Если он находится в этом диапазоне, это означает, что с чипом проблем нет.
  • Насос заряда: это насос заряда или насос заряда.
  • Для вывода CPOUT требуется внешний конденсатор.
  • Регистр состояния прерывания: может контролировать, какие внутренние события выводятся на вывод прерывания.
  • FIFO: регистр «первым пришел — первым обслужен», который может кэшировать поток данных.
  • Регистр конфигурации: вы можете настроить различные внутренние схемы.
  • Регистр датчиков: регистр данных, в котором хранятся данные каждого датчика.
  • Заводская калибровка: это означает, что внутренние датчики откалиброваны.
  • Цифровой процессор движения: для краткости DMP — это аппаратный алгоритм расчета положения, встроенный в чип. Его можно использовать для расчета положения с помощью официальной библиотеки DMP.
  • FSYNC: Синхронизация кадров.

3. 10-1 Чтение и запись программного обеспечения I2C MPU6050

3.1 Подключение оборудования

Через программную связь I2C читайте и записывайте регистры внутри чипа MPU6050. Записывая в регистр конфигурации, вы можете настроить подключаемый модуль. Прочитав регистр данных, вы можете получить данные подключаемого модуля. На OLED-экране будут отображаться считанные данные. Идентификационный номер этого MPU6050 имеет фиксированное значение 0x68. Ниже три слева — это выходные данные датчика ускорения, которые представляют собой ускорение по осям X, Y и Z соответственно. Три справа — это выходные данные датчика гироскопа. которые представляют собой угловую скорость осей X, Y и Z.
SCL подключен к выводу PB10 STM32, а SDA подключен к выводу PB11. Поскольку здесь реализовано переключение программного уровня, два порта GPIO можно подключить по своему желанию.

3.2 Результаты работы

IMG_20240406_155156.jpg

3.3 Поток кода

STM32 является хостом, а MPU6050 — ведомым, что соответствует режиму «главный-подчиненный».

  1. Установите модули .c и .h уровня связи I2C.
    1. Напишите базовую инициализацию GPIO I2C.
    2. 6 основных единиц синхронизации: начало, завершение, отправка байта, получение байта, отправка ответа, получение ответа.
  2. Создайте модули .c и .h MPU6050.
    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, которая может автоматически выполнять такие функции, как генерация тактовых импульсов, генерация начальных и конечных условий, передача и прием ответных битов, а также передача и прием данных аппаратным обеспечением, снижая нагрузку на ЦП.
  • Поддержка модели с несколькими хостами
  • Поддержка 7-битного/10-битного режима адреса.
  • Поддерживает различные скорости связи: стандартную скорость (до 100 кГц), быструю (до 400 кГц)
  • Поддержка прямого доступа к памяти
  • Совместимость с протоколом SMBus.
  • Аппаратные ресурсы I2C STM32F103C8T6: I2C1, I2C2

4.2 Блок-схема I2C

изображение.png

  • Слева расположены контакты связи: SDA и SCL используются SMBus;
    Выводы, полученные от обычных периферийных устройств, обычно подключаются к внешнему миру через режим мультиплексирования порта GPIO (см. таблицу).
  • Вышеупомянутая часть управления данными: SDA Основная часть передачи и приема данных — это регистр данных DR (РЕГИСТР ДАННЫХ) и регистр сдвига данных. Когда данные необходимо отправить, один байт данных может быть записан в регистр данных DR. Когда в сдвиговом регистре нет данных для сдвига, значение регистра данных будет далее передано в сдвиговый регистр. Во время процесса смещения следующие данные могут быть непосредственно помещены в регистр данных и подвергнуты ожиданию. Как только предыдущий сдвиг данных будет завершен, следующие данные могут быть легко подключены и продолжены. Когда данные передаются из регистра данных в сдвиговый регистр, бит TXE регистра состояния устанавливается в 1, что указывает на то, что регистр передачи пуст.
  • Прием: входные данные побитно перемещаются из вывода в сдвиговый регистр. При сборе одного байта данных данные передаются из сдвигового регистра в регистр данных целиком, при этом устанавливается флаг RXNE. одновременно, что указывает на прием. Регистр не пуст, тогда данные можно считать из регистра данных. Что касается того, когда получать и когда отправлять, вам необходимо записать соответствующие биты в регистр управления для работы. Условия запуска, условия завершения, биты ответа и т. д. завершаются посредством управления данными.
  • Компаратор и адресный регистр используются в подчиненном режиме.
  • SCL: Управление тактовым сигналом используется для управления линией SCL. Запишите соответствующий бит в регистр управления часами, и схема выполнит соответствующую функцию. Логическая схема управления, запись в регистр управления может управлять всей схемой. Рабочее состояние схемы можно узнать, прочитав регистр состояния.
  • При отправке и получении большого количества байтов DMA можно использовать для повышения эффективности.

4.3 Базовая структура I2C

изображение.png

  • SDA: поскольку I2C имеет старший порядок, этот регистр сдвига сдвигается влево. При отправке сначала удаляется старший бит, затем второй старший бит. Тактовый сигнал SCL сдвигается один раз и сдвигается 8 раз, а в линии SDA можно разместить 8 байтов от старшего бита к младшему. При приеме данные перемещаются справа через порт GPIO и, наконец, 8 раз принимается один байт. Выходные данные выводятся в порт через порт GPIO. Входные данные вводятся в сдвиговый регистр через порт GPIO.
  • Порт GPIO необходимо настроить на мультиплексный режим вывода с открытым стоком; мультиплексирование означает, что состояние порта GPIO контролируется встроенными периферийными устройствами, а выход с открытым стоком — это конфигурация порта, требуемая протоколом I2C. Даже в режиме вывода с открытым стоком порт GPIO может быть входным.
  • SCL: Контроллер часов управляет линией синхронизации через GPIO.
    изображение.png

4.4 Хост отправляет

изображение.png
Когда STM32 хочет записать по указанному адресу, ему необходимо следовать диаграмме последовательности передачи передатчика.

  • 7-битный адрес: адресуется один байт после условия запуска.
  • 10-битный адрес: два байта после условия запуска являются адресацией. Первый байт — это заголовок кадра, а содержимое — 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, поскольку сдвиговый регистр пуст, 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. В это время сдвинутый байт передается в регистр данных целиком, и одновременно устанавливается флаг RxNE, указывающий, что регистр данных не пуст, то есть получен один байт данных. Статус — событие EV7, RxNE=1. Чтение регистра DR очищает событие, что означает. что данные получены. После того, как мы прочитали данные, события больше нет.
  5. Конечно, когда данные 1 не были прочитаны, данные 2 могут быть непосредственно перемещены в сдвиговый регистр. После этого сдвиг данных 2 завершен, данные 2 получены, генерируется событие EV7, данные 2 считываются и. событие EV7 исчезло.
  6. Когда прием больше не требуется, регистр управления битом ответа ACK должен быть установлен в 0 заранее, когда происходит последний блок синхронизации, и устанавливается запрос условия завершения, то есть событие EV7_1. После этого происходит отсутствие ответа NA. Будет задан бит 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 подключен к выводу PB10 STM32, а SDA подключен к выводу PB11. Поскольку здесь реализовано переключение программного уровня, два порта GPIO можно подключить по своему желанию.
Верхние данные OLED — это идентификационный номер устройства. Идентификационный номер этого MPU6050 имеет фиксированное значение 0x68. Ниже три слева — это выходные данные датчика ускорения, которые представляют собой ускорение по осям X, Y и Z соответственно. Три справа — это выходные данные датчика гироскопа. которые представляют собой угловую скорость осей X, Y и Z.

5.2.2 Результаты работы

IMG_20240406_172128.jpg

5.2.3 Процесс реализации кода

  1. Настройте периферийные устройства I2C, инициализируйте периферийные устройства I2C, замените MyI2C_Init
    (1) Включите часы периферийного устройства I2C и соответствующего порта GPIO,
    (2) Инициализируйте порт GPIO, соответствующий периферийному устройству I2C, в мультиплексированном режиме с открытым стоком.
    (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