<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Урок 7. MPU9250. Игра &quot;SPACE DEFENSE&quot; (бета-версия)]]></title><description><![CDATA[<h2>Цель урока</h2>
<p dir="auto">Привет! Сегодня мы научимся работать со встроенным датчиком MPU9250 в серебристой модели M5STACK.<br />
Датчик MPU9250 включает в себя гироскоп, акселерометр и компас. В данном уроке мы напишем игру "SPACE DEFENSE" (рис. 1), управлять кораблём будет возможно только при помощи наклона устройства. По оси X - движение корабля, а по оси Y - открытие огня. Гироскоп и компас оставим на самостоятельное изучение.</p>
<p dir="auto"><img src="https://pp.userapi.com/c841321/v841321763/75374/CH5UVNz8XFQ.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 1. Экран начала игры</p>
<h2>Краткая теория</h2>
<p dir="auto"><img src="https://pp.userapi.com/c834104/v834104988/dcd9f/5rBFXbIUwKU.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 2. Устройство механического акселерометра</p>
<p dir="auto">Что же такое акселерометр? Акселерометр – прибор (рис. 2), измеряющий проекцию кажущегося ускорения (разности между истинным ускорением объекта и гравитационным ускорением). Как правило, акселерометр представляет собой чувствительную массу, закреплённую в упругом подвесе. Отклонение массы от её первоначального положения при наличии кажущегося ускорения несёт информацию о величине этого ускорения.</p>
<p dir="auto">Более подробная информация на Wiki: <a href="https://en.wikipedia.org/wiki/Accelerometer" title="https://en.wikipedia.org/wiki/Accelerometer" target="_blank" rel="noopener noreferrer nofollow ugc">https://en.wikipedia.org/wiki/Accelerometer</a></p>
<p dir="auto"><strong>Перечень компонентов для урока</strong></p>
<ul>
<li>M5STACK серебристая модель со встроенным MPU9250.</li>
</ul>
<h2>Начнём!</h2>
<h3>Шаг 1. Нарисуем картинки</h3>
<p dir="auto">Нарисуем космический корабль, взрыв корабля и логотип игры. Используйте любой удобный для Вас графический редактор. Мы будем использовать как всегда Paint (рис. 3 – 3.2).</p>
<p dir="auto"><img src="https://pp.userapi.com/c841321/v841321072/75667/yVYe_4gJPW0.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 3. Рисуем космический корабль</p>
<p dir="auto"><img src="https://pp.userapi.com/c841321/v841321072/75654/mDfqEgQ8uvQ.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 3.1. Рисуем логотип игры</p>
<p dir="auto"><img src="https://pp.userapi.com/c841321/v841321072/7565e/gTG6W_rATAA.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 3.2. Коллекция рисунков для проекта</p>
<p dir="auto">В дальнейшем подключим изображения к нашему новому проекту:</p>
<pre><code>extern unsigned char craft[];
extern unsigned char craft_logo[];
extern unsigned char explode[];
extern unsigned char logo[];
</code></pre>
<h3>Шаг 2. Пули и космический мусор</h3>
<p dir="auto">Рассмотрим на примере пуль. Сделаем структуру bulletsObject, которая включает в себя координаты размеры и состояние пули (если пуля в полёте, то busy = true).</p>
<pre><code>struct bulletsObject
{
	int x;
	int y;
	int width = 3;
	int height = 6;
	bool busy;
};
</code></pre>
<p dir="auto">Сделаем буфер, содержащий информацию о трёх пулях:</p>
<pre><code>const int bullets_max = 3;
bulletsObject bulletsObjectArray[bullets_max];
</code></pre>
<p dir="auto">Космический мусор (если hidden = true, то объект нейтрализован):</p>
<pre><code>struct spaceDebrisObject
{
  int x;
  int y;
  int width = 15;
  int height = 15;
  bool hidden;
};
</code></pre>
<p dir="auto">Сделаем буфер, содержащий информацию о космическом мусоре:</p>
<pre><code>const int spaceDebris_max = 100;
spaceDebrisObject spaceDebrisArray[spaceDebris_max];
</code></pre>
<h3>Шаг 3. Двигаем то, чего много</h3>
<p dir="auto">Многозадачность? Нет – всё проще. Помните в одной из статей на сайте Arduino было рассказано, как реализовать мигание светодиодом без функции delay, которая заставляет микроконтроллер бездействовать? Если нет, то айда почитать <a href="https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay" title="https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay" target="_blank" rel="noopener noreferrer nofollow ugc">https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay</a>.<br />
Аналогичным образом поступим и мы (рис. 5). Когда придёт время, тогда выполним необходимые действия и обновим значение предыдущего времени millis.</p>
<p dir="auto"><img src="https://pp.userapi.com/c830209/v830209723/9fcd0/uGuUidxS0eI.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 5.</p>
<p dir="auto">Функция fire вызывается из бесконечного цикла gameLoop() и принимает в качестве аргумента целочисленное значение вектора наклона по оси Y. Если значение вектора менее, чем -250, то будет открыт огонь. О том, как получать значения векторов наклона поговорим в следующем шаге.<br />
Движение происходит на экране по оси Y всего массива с графическими объектами (рис. 5.1).</p>
<p dir="auto"><img src="https://pp.userapi.com/c834103/v834103812/dfee3/zmhyl356kS8.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 5.1. Принцип движения графических объектов</p>
<pre><code>void gameLoop() {
  while (true)
  {
    if (!moveCraft(getAccel('X'))) break;
    if (!moveSpaceDebris()) break;
    fire(getAccel('Y'));
    if (health &lt; 0) break;
  }
  craftExplode();  
}

void fire(int vector) {
  if (vector &lt; -250)
  {
    if (!openFire)
    {
      for (int i = 0; i &lt; bullets_max; i++)
      {
        if (!bulletsObjectArray[i].busy)
        {
          openFire = true;
          bulletsObjectArray[i].x = ((craft_x + (craft_width / 2)) - 1);
          bulletsObjectArray[i].y = craft_y;
          bulletsObjectArray[i].busy = true;
          break;
        }
      }
    }
  }
  else
  {
    openFire = false;
  }

  unsigned long millis_ = millis();
  if (((millis_ - moveBullets_pre_millis) &gt;= moveBullets_time))
  {
    moveBullets_pre_millis = millis_; 
    for (int i = 0; i &lt; bullets_max; i++)
    {
      M5.Lcd.fillRect(bulletsObjectArray[i].x, bulletsObjectArray[i].y, bulletsObjectArray[i].width, bulletsObjectArray[i].height, 0x0000);
      if (bulletsObjectArray[i].busy)
      {
        bulletsObjectArray[i].y--;
        M5.Lcd.fillRect(bulletsObjectArray[i].x, bulletsObjectArray[i].y, bulletsObjectArray[i].width, bulletsObjectArray[i].height, 0xff80);
        if (bulletsObjectArray[i].y &lt;= statusBar_height)
        {
          bulletsObjectArray[i].busy = false;
        }
        for (int j = 0; j &lt; spaceDebris_max; j++)
        {
          if (((((spaceDebrisArray[j].x + spaceDebrisArray[j].width) &gt;= bulletsObjectArray[i].x) &amp;&amp; (spaceDebrisArray[j].x &lt;= (bulletsObjectArray[i].x + bulletsObjectArray[i].width))) &amp;&amp; ((spaceDebrisArray[j].y + spaceDebrisArray[j].height) &gt;= (bulletsObjectArray[i].y + (bulletsObjectArray[i].height / 2))) &amp;&amp; (spaceDebrisArray[j].y &lt;= (bulletsObjectArray[i].y + bulletsObjectArray[i].height)) &amp;&amp; (!spaceDebrisArray[j].hidden)))
          {
            bulletsObjectArray[i].busy = false;
            spaceDebrisArray[j].hidden = true;
            M5.Lcd.fillRect(bulletsObjectArray[i].x, bulletsObjectArray[i].y, bulletsObjectArray[i].width, bulletsObjectArray[i].height, 0x0000);
            M5.Lcd.fillRect(spaceDebrisArray[j].x, spaceDebrisArray[j].y, spaceDebrisArray[j].width, spaceDebrisArray[j].height, 0x0000);
            score++;  
            drawHealthAndScore();
          }
        }
      }
    }
  }
}
</code></pre>
<p dir="auto">Если пуля соприкасается с космическим мусором, то счёт игры увеличиваем на единицу, состояние пули принимает значение busy = false, космический мусор становится скрытым hidden = true.</p>
<h3>Шаг 4. Получение данных с акселерометра</h3>
<p dir="auto">Как было сказано раньше – акселерометр возвращает значения углов отклонения от осей "векторы наклона" (рис. 6).</p>
<p dir="auto"><img src="https://pp.userapi.com/c840232/v840232877/86502/F3a5fzIIDJU.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 6. Векторы наклона акселерометра</p>
<p dir="auto">Прежде всего необходимо подключить библиотеку для работы с MPU9250:</p>
<pre><code>#include "utility/MPU9250.h"
</code></pre>
<p dir="auto">Объявим экземпляр класса:</p>
<pre><code>MPU9250 IMU;
</code></pre>
<p dir="auto">Произведён инициализацию и выполним калибровку датчика. Помните, что MPU9250 подключён к ESP32 через I2C интерфейс:</p>
<pre><code>void setup(){
	M5.begin();
	Wire.begin();
  	IMU.initMPU9250();
  	IMU.calibrateMPU9250(IMU.gyroBias, IMU.accelBias);
	// any actions	
}
</code></pre>
<p dir="auto">Напишем функцию, которая принимает в качестве аргумента символ, соответствующий одной из осей 'X', 'Y' или 'Z'. Возвращает функция вектор наклона по оси. Если никаких данных не получено, то функция возвращает 0:</p>
<pre><code>int getAccel(char axis) {
  if (IMU.readByte(MPU9250_ADDRESS, INT_STATUS) &amp; 0x01)
  {
    IMU.readAccelData(IMU.accelCount);
    IMU.getAres();
    switch(axis)
    {
      case 'X':
        IMU.ax = (float)IMU.accelCount[0] * IMU.aRes * 1000;
        return IMU.ax;
      case 'Y':
        IMU.ax = (float)IMU.accelCount[1] * IMU.aRes * 1000;
        return IMU.ax;
      case 'Z':
        IMU.az = (float)IMU.accelCount[2] * IMU.aRes * 1000;
        return IMU.az;
    }
  }
  return 0;
}
</code></pre>
<h3>Шаг 5. Двигаем космический корабль</h3>
<p dir="auto">Функция чрезмерно простая, принцип работы мы рассмотрели на предыдущем шаге:</p>
<pre><code>bool moveCraft(int vector) {
  unsigned long millis_ = millis();
  if (((millis_ - moveCraft_pre_millis) &gt;= moveCraft_time))
  {
    moveCraft_pre_millis = millis_;  
    int craft_x_pre = craft_x;

    if (abs(vector) &gt; 70)
    {
      if (vector &gt; 0)
        craft_x -= craft_step;
      else if (vector &lt; 0)
        craft_x += craft_step;
      M5.Lcd.fillRect(craft_x_pre, craft_y, craft_width, craft_height, 0x0000);
    }

    if ((craft_x &lt; (screen_width - craft_width - craft_step)) &amp;&amp; (craft_x &gt; craft_step))
    { 
      M5.Lcd.drawBitmap(craft_x, craft_y, craft_width, craft_height, (uint16_t *)craft);
    }
    else
    {
      return false;                                                                               
    }
  }
  return true;
}
</code></pre>
<h3>Шаг 6. Игра окончена</h3>
<pre><code>void gameOver() {
  M5.Lcd.fillScreen(0x0000);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(0x7bef);
  M5.Lcd.setCursor(35, 80);
  M5.Lcd.print("GAME OVER");
  M5.Lcd.setCursor(35, 110);
  M5.Lcd.print("score: ");
  M5.Lcd.print(score);
  M5.Lcd.setCursor(35, 140);
  M5.Lcd.print("please, press any key");  
  gameReset();
  while(true)
  {
    M5.update();
    if (M5.BtnA.wasPressed() || M5.BtnB.wasPressed() || M5.BtnC.wasPressed()) break;
  }
}
</code></pre>
<h3>Шаг 7. Запуск!</h3>
<p dir="auto">В целом это был очень интересный проект, посвященный работе с акселерометром на борту MPU9250. Давайте запустим и посмотрим, как это работает (рис. 7, 7.1.):</p>
<p dir="auto"><img src="https://pp.userapi.com/c834103/v834103208/dc770/cTQh-VxOKFY.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок 7. Игровой процесс</p>
<p dir="auto"><img src="https://pp.userapi.com/c834103/v834103208/dc784/TpC1BlB82NA.jpg" alt="" class=" img-fluid img-markdown" /></p>
<p dir="auto">Рисунок. 7.1. Игра окончена</p>
<h3>Скачать</h3>
<ul>
<li>
<p dir="auto">Видео с демонстрацией работы (YouTube): <a href="https://youtu.be/9gyiFfciUU4" title="https://youtu.be/9gyiFfciUU4" target="_blank" rel="noopener noreferrer nofollow ugc">https://youtu.be/9gyiFfciUU4</a></p>
</li>
<li>
<p dir="auto">Исходные коды (GitHub):  <a href="https://github.com/dsiberia9s/SpaceDefense-m5stack" title="https://github.com/dsiberia9s/SpaceDefense-m5stack" target="_blank" rel="noopener noreferrer nofollow ugc">https://github.com/dsiberia9s/SpaceDefense-m5stack</a></p>
</li>
<li>
<p dir="auto">Конверторы изображений (Yandex Disk):<a href="https://yadi.sk/d/m2zvebPf3T5Zrc" title="https://yadi.sk/d/m2zvebPf3T5Zrc" target="_blank" rel="noopener noreferrer nofollow ugc">https://yadi.sk/d/m2zvebPf3T5Zrc</a></p>
</li>
<li>
<p dir="auto">Изображения (Yandex Disk): <a href="https://yadi.sk/d/JMMyPHOq3T5Zmf" title="https://yadi.sk/d/JMMyPHOq3T5Zmf" target="_blank" rel="noopener noreferrer nofollow ugc">https://yadi.sk/d/JMMyPHOq3T5Zmf</a></p>
</li>
</ul>
]]></description><link>https://community.m5stack.com/topic/129/урок-7-mpu9250-игра-space-defense-бета-версия</link><generator>RSS for Node</generator><lastBuildDate>Mon, 16 Mar 2026 18:08:10 GMT</lastBuildDate><atom:link href="https://community.m5stack.com/topic/129.rss" rel="self" type="application/rss+xml"/><pubDate>Tue, 06 Mar 2018 13:17:41 GMT</pubDate><ttl>60</ttl></channel></rss>