Rending the DOOM logo with an esp8266.

Table of Contents

The Source

For the fast ones, here is the source: https://github.com/pauls-3d-things/arduino-doom-fire-animation

Intro

This tutorial relies on having read my previous howto: Procedural Flame Animation with Arduino.

The core idea of this post is to convert an image to raw RGB data, save it as a header file to be included as an array of bytes of RGB pixel data, to be referenced when pushing these pixels to the screen.

Steps

Create an image with the right size first:

convert doom_image.png -scale 160x80 doom_image.rgb # result is 160x77

Then, create a header file doom_image.h that contains the data.

xxd -i doom_image.rgb > src/doom_image.h

This, we can now include in our main.cpp

#include #include <doom_image.h>

… and update the draw function

// draw fire
tft.startWrite();
tft.setAddrWindow(0, 0, 160, 80);
for (uint8_t y = 0; y < 80; y++) {
    for (uint8_t x = 0; x < 160; x++) {
        if (firePixels[y][x]) { // draw fire
            tft.writePixel(doomColorMap[firePixels[y][x]]);
        } else if (y < 77) { // draw DOOM logo (it is only 77 pixels high)
            uint16_t o = (y * 160 + x) * 3; // offset because of rgb data
            tft.writePixel(rgb(doom_image[o], doom_image[o + 1], doom_image[o + 2]));
        }
    }
}
tft.endWrite();

Adding an MPU6050 Accelerometer

If you have an MPU6050 lying around, you can add this to the setup and play around with it.

Include the wire library, and add some variables:

#include <Wire.h>

#define MPUADDR 0x68
int16_t AcX, AcY, AcZ, Tmp, GyX, GyY, GyZ;

Initialize the sensor:

void setup(void) {
  Wire.begin();
  Wire.beginTransmission(MPUADDR);
  Wire.write(0x6B); // PWR_MGMT_1 register
  Wire.write(0);    // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);
  
  // [...]
}

Read sensor data in every loop, and filter it to avoid noise:

void loop(void) {
  Wire.beginTransmission(MPUADDR);
  Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPUADDR, 14); // request a total of 14 registers

  // low pass filter to remove noise:
  AcX = AcX * 0.95 + 0.05 * (Wire.read() << 8 | Wire.read()); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
  AcY = AcY * 0.95 + 0.05 * (Wire.read() << 8 | Wire.read()); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  AcZ = AcZ * 0.95 + 0.05 * (Wire.read() << 8 | Wire.read()); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
  Tmp = Wire.read() << 8 | Wire.read(); // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
  GyX = Wire.read() << 8 | Wire.read(); // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
  GyY = Wire.read() << 8 | Wire.read(); // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
  GyZ = Wire.read() << 8 | Wire.read(); // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)

  // [...]
}

Then, let the sensor values affect the flame calculation. The x axis for speed, and y axis for wind.

// recalculate fire
for (uint8_t y = 0; y < 79; y++) {
    for (uint8_t x = 0; x < 160; x++) {
        uint8_t wind = x + random(2) + (abs(AcY) / 15000); // here
        wind = wind >= 160 ? wind - 160 : wind;
        uint8_t speed = y + random(2) + (abs(AcX) / 15000); // here
        speed = speed >= 80 ? 79 : speed;
        firePixels[y][x] = firePixels[speed][wind] - random(3);
        firePixels[y][x] = firePixels[y][x] > 35 ? 0 : firePixels[y][x]; // fix overflow
    }
}