flipper-zero-tutorials/gpio/memsic_2125/memsic_2125_app.c
2023-03-03 15:26:06 -05:00

226 lines
7.9 KiB
C

/*
@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;
}