flipper-zero-tutorials/vgm/imu_controller/README.md
2024-03-24 14:28:10 -05:00

9.8 KiB

IMU Controller

Overview

IMU Controller project is a library that provides a way to interact with the Flipper Zero's IMU sensor in the Video Game Module. IMU stands for an Inertial Measurement Unit, which means we can accurately know the orientation of the device (regardless of where it is within the room). Pitch and Roll values (the amount of tilt) are used to trigger Up, Down, Left and Right d-pad button presses. The goal of this library is to enable games to be played using the Video Game Module's IMU sensor, with minimal additional coding required.

Orientation

The recommended orientation is with the screen facing up & the video game module facing away from you. To get accurate readings, the device should be held still for a second during initial calibration (typically when the application launches).

Pitch

Postive pitch is when the device is tilted left. Negative pitch is when the device is tilted right. The value is in degrees with the range -90 to 90 (although typically I don't see values larger than 85). The largest value I've seen is 85, when the IR port is facing the ground. The smallest value I've seen is -85, when the IR port is facing straight up. When the pitch angle exceeds the value defined in .pitch_left or .pitch_right the library will inject a left or right d-pad button press. When the pitch has moved back by pitch_hysteresis, the library will inject a release of the d-pad button.

Roll

Positive roll is when the device is tilted down. Negative roll is when the device is tilted to the up. The value is in degrees with the range -180 to 180. The smallest value I've seen is -179 and the largest value I've seen is +179, these values are when the screen is facing down. When the roll angle exceeds the value defined in .roll_up or .roll_down the library will inject an up or down d-pad button press. When the roll has moved back by roll_hysteresis, the library will inject a release of the d-pad button.

Yaw

Positive yaw is when the device is rotated counter-clockwise. Negative yaw is when the device is rotated clockwise. The value is in degrees with the range -180 to 180. The smallest value I've seen is -179 and the largest value I've seen is +179, when the device is rotated 180 degrees from the initial orientation when it was calibrated. Yaw is not used in this library.

Configuration

The ImuControllerConfig structure is used to configure the IMU Controller. The default configuration is defined in imu_controller.h and is called IMU_CONTROLLER_DEFAULT_CONFIG. If your application requires specific configuration, you can create a similar entry in your application and pass it as the second parameter to imu_controller_alloc. The default configuration is as follows:

const ImuControllerConfig IMU_CONTROLLER_DEFAULT_CONFIG = {
    .roll_up = 8.0f, // 8 degrees triggers an Up button press
    .roll_down = -8.0f, // -8 degrees triggers a Down button press
    .roll_hysteresis = 3.0f, // 3 degrees of movement is required to release the Up or Down button press
    .pitch_left = 20.0f, // 20 degrees triggers a Left button press
    .pitch_right = -20.0f, // -20 degrees triggers a Right button press
    .pitch_hysteresis = 5.0f, // 5 degrees of movement is required to release the Left or Right button press
    .button_state_long_ms = 300, // 300ms is the time required to trigger a long press event
    .button_state_repeat_ms = 150, // 150ms is the time required to trigger a repeat press event
    .vibro_duration = 25, // 25ms is the duration of the vibration when a button is pressed or released
};

Technical Details

The above image from https://blog.flipper.net/introducing-video-game-module-powered-by-raspberry-pi/ and shows the two ways the Motion-tracking sensor is connected.

The Video Game Module has a TDK ICM-42688-P, which is a 6-axis MEMS motion-tracking sensor (IMU) the combines a 3-axis gyroscope and a 3-axis accelerometer. The IMU sensor uses the SPI protocol (pins 2-6 on the Flipper Zero) to communicate directly to the Flipper Zero.

The Video Game Engine provides a imu.h file, giving us access to the last known pitch, roll and yaw values. The data provided has increased accuracy since the code uses a Madgwick AHRS algorithm. See Madgwick's paper for more information.

When you call imu_controller_alloc, it invokes imu_alloc() causing intial calibration data to be is calculated. For best results, it is recommended that the device does not move during calibration and that the orientation is similar to the orientation it will be used in. If needed, your application can invoke imu_controller_recalibrate to recalibrate the IMU sensor.

Adding the Library to your Project

Step 1: Add the Flipper Zero Game Engine Library as a Submodule

In the root of your project, where your application.fam file is located, you can add the Flipper Zero Game Engine as a submodule. To add the library to your project, use the following command:

git submodule add https://github.com/flipperdevices/flipperzero-game-engine.git engine

Step 2: Copy the imu_controller.h and imu_controller.c files to your project

Copy the imu_controller.h and imu_controller.c files from this repository to your project. It is assumed these files are in the same directory as where you ran the git submodule command.

Step 3: Update your main program to include the IMU Controller

At the top of your main program, include the imu_controller.h file, typically after the other includes. For example:

#include "imu_controller.h"

Step 4: Create a queue method to inject Input Events

Typically your application will have a message queue to handle input events. You can find furi_message_queue_put commands and see what kind of event your application expects. There are two common patterns... either using an InputEvent or a custom named event (like PluginEvent, SnakeEvent, etc.)

static void doom_imu_queue(FuriMessageQueue* event_queue, InputType type, InputKey key) {
    InputEvent input_event = {.type = type, .key = key};
    PluginEvent event = {.type = EventTypeKey, .input = input_event};
    furi_message_queue_put(event_queue, &event, 0);
}

or

static void game_imu_queue(FuriMessageQueue* event_queue, InputType type, InputKey key) {
    InputEvent input_event = {.type = type, .key = key};
    furi_message_queue_put(event_queue, &input_event, 0);
}

Step 5: Create and Start the IMU Controller

In your main application entry point, create and start the IMU Controller. For example:

    ImuController* imu_controller =
        imu_controller_alloc(event_queue, &IMU_CONTROLLER_DEFAULT_CONFIG, doom_imu_queue);
    imu_controller_start(imu_controller);

Step 6: Free the IMU Controller

In your main application entry point, free the controller when the application is done. For example:

    imu_controller_free(imu_controller);

Fine Tuning

The default configuration is a good starting point, but you may need to fine-tune the values to match your application. For example, if you find that the d-pad buttons are being triggered too easily, you can increase the roll_up, roll_down, pitch_left or pitch_right values. If you find that the d-pad buttons are not being triggered easily enough, you can decrease the values. The hysteresis values are used to prevent the d-pad buttons from being released too quickly. If you find that the d-pad buttons are being released to quickly, you can increase the hysteresis values. If you find that the d-pad buttons are being released too slowly, you can decrease the hysteresis values.

Put the following code below your #include statements to create a custom configuration:

const ImuControllerConfig my_configuration = {
    .roll_up = 8.0f, // 8 degrees triggers an Up button press
    .roll_down = -8.0f, // -8 degrees triggers a Down button press
    .roll_hysteresis = 3.0f, // 3 degrees of movement is required to release the Up or Down button press
    .pitch_left = 20.0f, // 20 degrees triggers a Left button press
    .pitch_right = -20.0f, // -20 degrees triggers a Right button press
    .pitch_hysteresis = 5.0f, // 5 degrees of movement is required to release the Left or Right button press
    .button_state_long_ms = 300, // 300ms is the time required to trigger a long press event
    .button_state_repeat_ms = 150, // 150ms is the time required to trigger a repeat press event
    .vibro_duration = 25, // 25ms is the duration of the vibration when a button is pressed or released (set to 0 for no vibration)
};

and then change the second parameter to imu_controller_alloc to use your custom configuration:

    ImuController* imu_controller =
        imu_controller_alloc(event_queue, &my_configuration, doom_imu_queue);

Support

The best way to get support is to join the Flipper Zero Tutorials (Unofficial) Discord community. Here is a Discord invite to join my Flipper Zero Tutorials (Unofficial) community.

If you want to support my work, you can donate via https://ko-fi.com/codeallnight or you can buy a FlipBoard from HackItHackin with software & tutorials from me (@CodeAllNight).