Mesmic Mx2125 Dual-Axis Accelerometer

This commit is contained in:
Derek Jamison 2023-03-03 15:26:06 -05:00
parent 91da549b6a
commit b6fef49575
6 changed files with 324 additions and 0 deletions

View File

@ -35,6 +35,8 @@ Feel free to reach out to me at CodeAllNight@outlook.com with any questions or l
### gpio-gpio-interrupt-demo
[tutorial](./gpio/gpio_interrupt_demo/README.md) - This is a "hello world" demonstration of triggering a callback when a GPIO pin transitions from VCC to GND.
### gpio-memsic_2125
[tutorial](./gpio/memsic_2125/README.md) - This is a demostration of using GPIO interrupts to interpret data from the Memsic 2125 (Mx2125) Dual-Axis Accelerometer.
## Subghz [folder](./subghz/README.md)

View File

@ -0,0 +1,86 @@
# MEMSIC 2125
## Introduction
This is a demostration of using GPIO interrupts to interpret data from the Memsic 2125 (Mx2125) Dual-Axis Accelerometer.
## Installation Directions
This project is intended to be overlayed on top of an existing firmware repo.
- Clone, Build & Deploy an existing flipper zero firmware repo. See this [tutorial](/firmware/updating/README.md) for updating firmware.
- Copy the "memsic_2125" [folder](..) to the \applications\plugins\memsic_2125 folder in your firmware.
- Build & deploy the firmware. See this [tutorial](/firmware/updating/README.md) for updating firmware.
- NOTE: You can also extract the memsic_2125.FAP from resources.tar file and use qFlipper to copy the file to the SD Card/apps/Misc folder.
## Connecting the hardware
The Memsic Mx2125 is a 6-pin IC. From the top view the pins look like the following:
<br/>
<img alt="Mx2125 chip with pin 1 on top-left" src="./memsic_2125_chip.jpg" height=150/>
<br/>
The pins should be connected to the Flipper Zero as follows:
| Memsic | Name | Purpose | Flipper |
| ------ | ---- | ------------------------------------------ | ------------- |
| Pin 1 | Tout | Temperature Out | not connected |
| Pin 2 | Yout | Y-axis PWM Out (100Hz, duty cycle = value) | C0 |
| Pin 3 | GND | Ground | GND |
| Pin 4 | GND | Ground | GND |
| Pin 5 | Xout | X-axis PWM Out (100Hz, duty cycle = value) | C1 |
| Pin 6 | Vdd | Drain voltage (3.3V to 5V DC) | 3v3 |
## Running the updated firmware
These directions assume you are starting at the flipper desktop. If not, please press the back button until you are at the desktop.
- Press the OK button on the flipper to pull up the main menu.
- Choose "Applications" from the menu.
- Choose "Misc" from the sub-menu.
- Choose "Memsic 2125"
- The flipper should say "Memsic 2125 C1:X C0:Y". This is a reminder that the X-Axis (pin 5) connects to pin C1 and Y-Axis (pin 2) connects to pin C0.
- A circle should show in the middle of the screen when the Mx2125 is on a level surface.
- Tilting along the X and Y axis should move the circle.
- The values at the bottom of the screen show the X and Y duty cycle amounts.
- NOTE: A value of 50% means that the device is level.
- Press the BACK button to exit.
## How it works
- application.fam
- See [basic demo](../../plugins/basic/README.md) for explanation.
- basic_demo.png
- See [basic demo](../../plugins/basic/README.md) for explanation.
- basic_demo_app.c
- See [basic demo](../../plugins/basic/README.md) for explanation.
- GPIO specific:
- AxisData is a structure to hold information about an axis.
- pin is the pin we are monitoring, so we can read the state later.
- high is the timestamp when the pin last transitioned to high.
- low is the timestamp when the pin last transitioned to low.
- value is the duty cycle (which represents the amount of tilt.)
- reading is set to true when the UI is reading this structure. If this value is true, then in our callback we will not modify value. This is used to ensure access to value is thread safe.
- DemoData has two AxisData references (for x-axis data and y-axis data.)
- Our entry point initializes xData with the c1 pin & yData with the c0 pin.
- furi_hal_gpio_init(xData->pin, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedVeryHigh) initializes an interrupt to happen on the Rise or Fall of the c1 pin. We don't need a pull-up since the pin will be set to GND or 3.3V by the Mx2125.
- furi_hal_gpio_add_int_callback(xData->pin, pulse_callback, xData) sets the callback routine to pulse_callback and will pass our xData whenever the pin changes state.
- We do the same process as the xData for the yData using the c0 pin.
- pulse_callback(void \*) is our GPIO interrupt callback.
- The input parameter is actually a pointer to AxisData.
- furi_hal_cortex_timer_get(0) gets the current timestamp.
- furi_hal_gpio_read(d->pin) gets the state of the pin (true=HIGH/false=LOW).
- if we transitioned from high to low to high & our render_callback is not currently reading the value, then we update the value with the new duty cycle number.
- When exiting we remove the callback routine using furi_hal_gpio_remove_int_callback.
- In our render_callback(Canvas*, void*) we render the UI
- We set xData->reading to true before accessing xData->value and then set it to false once we are done accessing the data.
- We calcuate the X and Y coordinate to draw a circle based on xData->value and yData->value.

View File

@ -0,0 +1,10 @@
App(
appid="Memsic_2125",
name="[Mx2125] Accelerometer",
apptype=FlipperAppType.EXTERNAL,
entry_point="memsic_2125_app",
requires=["gui"],
stack_size=2 * 1024,
fap_icon="memsic_2125.png",
fap_category="Gpio",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,226 @@
/*
@CodeAllNight
https://github.com/jamisonderek/flipper-zero-tutorials
This is a demostration of using GPIO interrupts to interpret data from the
Memsic 2125 (Mx2125) Dual-Axis Accelerometer.
Memsic - Name - Purpose - Flipper
====== ==== ========================================== =============
Pin 1 - Tout - Temperature Out - not connected
Pin 2 - Yout - Y-axis PWM Out (100Hz, duty cycle = value) - C0
Pin 3 - GND - Ground - GND
Pin 4 - GND - Ground - GND
Pin 5 - Xout - X-axis PWM Out (100Hz, duty cycle = value) - C1
Pin 6 - Vdd - Drain voltage (3.3V to 5V DC) - 3v3
*/
#include <furi.h>
#include <furi_hal.h>
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <gui/gui.h>
#include <locale/locale.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#define TAG "memsic_2125_app"
typedef enum {
DemoEventTypeKey,
} DemoEventType;
typedef struct {
DemoEventType type; // The reason for this event.
InputEvent input; // This data is specific to keypress data.
} DemoEvent;
typedef struct {
const GpioPin* pin; // pin being monitored.
uint32_t high; // timestamp of when pin went high.
uint32_t low; // timestamp of when pin when low.
float value; // duty cycle (0.0 to 100.0)
bool reading; // when true, then value will not be updated.
} AxisData;
typedef struct {
FuriString* buffer;
AxisData* xData;
AxisData* yData;
} DemoData;
typedef struct {
FuriMessageQueue* queue; // Message queue
DemoData* data;
} DemoContext;
// Invoked when input (button press) is detected.
// We queue a message and then return to the caller.
// @input_event the button that triggered the callback.
// @queue our message queue.
static void input_callback(InputEvent* input_event, FuriMessageQueue* queue) {
furi_assert(queue);
DemoEvent event = {.type = DemoEventTypeKey, .input = *input_event};
furi_message_queue_put(queue, &event, FuriWaitForever);
}
// Invoked whenever the monitored pin changes state.
// @data pointer to an AxisData.
void pulse_callback(void* data) {
// Get the current time from high-resolution timer.
FuriHalCortexTimer timer = furi_hal_cortex_timer_get(0);
uint32_t now = timer.start;
// Our parameter is a pointer to the axis data.
furi_assert(data);
AxisData* d = (AxisData*)data;
// Get the current state of the pin (true = 3.3volts, false = GND)
bool state = furi_hal_gpio_read(d->pin);
if(state) {
// state=true, so we are in GND->3v3 transition.
// See if we have timings for both the high & low transitions.
if((d->high != 0) && (d->low != 0) && (d->high < d->low) && !d->reading) {
uint32_t durationCycle = now - d->high;
uint32_t durationLow = d->low - d->high;
// Update the value of the axis to reflect the duty cycle.
d->value = (100.0f * durationLow) / durationCycle;
}
// Store the current time that the rise transition happened.
d->high = timer.start;
} else {
// Store the current time that the fall transition happened.
d->low = timer.start;
}
}
// Invoked by the draw callback to render the screen.
// We render our UI on the callback thread.
// @canvas the surface to render our UI
// @ctx a pointer to a DemoContext object.
static void render_callback(Canvas* canvas, void* ctx) {
// Attempt to aquire context, so we can read the data.
DemoContext* demo_context = ctx;
DemoData* data = demo_context->data;
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 1, 1, AlignLeft, AlignTop, "Memsic 2125 C1:X C0:Y");
data->xData->reading = true;
data->yData->reading = true;
furi_string_printf(
data->buffer, "%0.3f %0.3f", (double)data->xData->value, (double)data->yData->value);
data->xData->reading = false;
data->yData->reading = false;
canvas_draw_str_aligned(
canvas, 30, 55, AlignLeft, AlignTop, furi_string_get_cstr(data->buffer));
// Draw a circle based on the xData & yData information.
data->xData->reading = true;
uint8_t x = (0.5f + ((50.0f - data->xData->value) / 12.0f)) * 128;
data->xData->reading = false;
x = MAX(0, MIN(x, 127));
data->yData->reading = true;
uint8_t y = (0.5f + (((float)data->yData->value - 50.0f) / 12.0f)) * 64;
data->yData->reading = false;
y = MAX(0, MIN(y, 63));
canvas_draw_circle(canvas, x, y, 2);
}
// Program entry point
int32_t memsic_2125_app(void* p) {
UNUSED(p);
// Configure our initial data.
DemoContext* demo_context = malloc(sizeof(DemoContext));
demo_context->data = malloc(sizeof(DemoData));
demo_context->data->buffer = furi_string_alloc();
AxisData* xData = malloc(sizeof(AxisData));
xData->pin = &gpio_ext_pc1;
xData->high = 0;
xData->low = 0;
xData->reading = false;
xData->value = 0;
demo_context->data->xData = xData;
AxisData* yData = malloc(sizeof(AxisData));
yData->pin = &gpio_ext_pc0;
yData->high = 0;
yData->low = 0;
yData->reading = false;
yData->value = 0;
demo_context->data->yData = yData;
// Queue for events (tick or input)
demo_context->queue = furi_message_queue_alloc(8, sizeof(DemoEvent));
// x-axis is a 100Hz pulse, with variable duty-cycle (which we need to measure).
// Invoke the pulse_callback method passing the xData structure whenever the pin
// transitions (Rise GND->3v3 or Fall 3v3->GND).
furi_hal_gpio_init(xData->pin, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_add_int_callback(xData->pin, pulse_callback, xData);
// y-axis is a 100Hz pulse, with variable duty-cycle (which we need to measure).
// Invoke the pulse_callback method passing the xData structure whenever the pin
// transitions (Rise GND->3v3 or Fall 3v3->GND).
furi_hal_gpio_init(yData->pin, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_add_int_callback(yData->pin, pulse_callback, yData);
// Set ViewPort callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, demo_context);
view_port_input_callback_set(view_port, input_callback, demo_context->queue);
// Open GUI and register view_port
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Main loop
DemoEvent event;
bool processing = true;
do {
if(furi_message_queue_get(demo_context->queue, &event, FuriWaitForever) == FuriStatusOk) {
FURI_LOG_T(TAG, "Got event type: %d", event.type);
switch(event.type) {
case DemoEventTypeKey:
// Short press of back button exits the program.
if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) {
FURI_LOG_I(TAG, "Short-Back pressed. Exiting program.");
processing = false;
}
break;
default:
break;
}
// Send signal to update the screen (callback will get invoked at some point later.)
view_port_update(view_port);
} else {
// We had an issue getting message from the queue, so exit application.
processing = false;
}
} while(processing);
// Free resources
furi_hal_gpio_remove_int_callback(yData->pin);
furi_hal_gpio_remove_int_callback(xData->pin);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_record_close(RECORD_GUI);
furi_message_queue_free(demo_context->queue);
furi_string_free(demo_context->data->buffer);
free(demo_context->data);
free(demo_context);
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB