From 91da549b6af747018ac5d6ba59610e031c40766a Mon Sep 17 00:00:00 2001 From: Derek Jamison Date: Thu, 2 Mar 2023 15:20:43 -0500 Subject: [PATCH] Demo of GPIO interrupt --- README.md | 2 + gpio/gpio_interrupt_demo/README.md | 70 +++++ gpio/gpio_interrupt_demo/application.fam | 10 + .../gpio_interrupt_demo.png | Bin 0 -> 1817 bytes .../gpio_interrupt_demo_app.c | 260 ++++++++++++++++++ 5 files changed, 342 insertions(+) create mode 100644 gpio/gpio_interrupt_demo/README.md create mode 100644 gpio/gpio_interrupt_demo/application.fam create mode 100644 gpio/gpio_interrupt_demo/gpio_interrupt_demo.png create mode 100644 gpio/gpio_interrupt_demo/gpio_interrupt_demo_app.c diff --git a/README.md b/README.md index 7d1789c..8a174b3 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ Feel free to reach out to me at CodeAllNight@outlook.com with any questions or l ### gpio-gpio-polling-demo [tutorial](./gpio/gpio_polling_demo/README.md) - This is a "hello world" demonstration of reading a GPIO pin using polling. +### 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. diff --git a/gpio/gpio_interrupt_demo/README.md b/gpio/gpio_interrupt_demo/README.md new file mode 100644 index 0000000..edffd14 --- /dev/null +++ b/gpio/gpio_interrupt_demo/README.md @@ -0,0 +1,70 @@ +# BASIC DEMO +## Introduction +This is a basic demo of using GPIO interrupts. + +## 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 "gpio_interrupt_demo" [folder](..) to the \applications\plugins\gpio_interrupt_demo folder in your firmware. +- Build & deploy the firmware. See this [tutorial](/firmware/updating/README.md) for updating firmware. +- NOTE: You can also extract the gpio_interrupt_demo.FAP from resources.tar file and use qFlipper to copy the file to the SD Card/apps/Misc folder. + + +## Connecting the hardware +GPIO pin C3 is our interrupt pin (with internal pull-up resistor). I connect pin +to a 220 ohm resistor and then the other side of the resistor to one side of a +momentary switch. Other side of the switch connects to our GND pin. + +GPIO A7, A6 and A4 connect to 220ohm resistors then the + or - pins of LED... +LED1: +A7 -A6 +LED2: +A6 -A7 +LED3: +A7 -A4 +LED4: +A4 -A7 +LED5: +A6 -A4 +LED6: +A4 -A6 + +## 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 "GPIO" from the sub-menu. +- Choose "GPIO Interrupt Demo" + +- Message "GND pin C3 to stop." should display +- LEDs should blink in order. +- press switch (or connect wire/resistor between C3 and GND) +- LEDs should stop and display should say "Stopped on LED 4." (or whatever LED was lit.) +- Press DOWN button on Flipper Zero to start over. + +- 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 + - GPIO specific code: + - furi_hal_gpio_init(&gpio_ext_pc3, GpioModeInterruptFall, GpioPullUp, GpioSpeedVeryHigh); + - gpio_ext_pc3 is the GPIO pin we are using for interrupt input. + - GpioModeInterruptFall - means trigger callback on falling edge (VCC->GND) + - GpioModeInterruptRise - means trigger callback on rising edge (GND->VCC) + - GpioModeInterruptRiseFall - means trigger callback on either edge + - GpioPullUp - Means enable internal pull-up resistor between VCC and pin. + - GpioPullDown - Means enable internal pull-down resistor between GND and pin. + - GpioPullNo - Means do not enable internal pull-up/pull-down resistor. + - GpioSpeedVeryHigh - priority of input? + - furi_hal_gpio_add_int_callback(&gpio_ext_pc3, demo_gpio_fall_callback, NULL); + - gpio_ext_pc3 is the GPIO pin we are using for interrupt input. + - second parameter is callback with signature: "void fnName(void* ctx)" + - third parameter is object to pass into callback for context (or NULL if no object). + - once furi_hal_gpio_add_int_callback is invoked, the callback is enabled. + - demo_gpio_fall_callback(void* ctx) + - this method gets invoked by our callback (because of previous add_int_callback) wheneven pin C3 gets pulled to GND. + - interrupt callbacks should try to do mininal amount of processing. + - furi_hal_gpio_remove_int_callback(&gpio_ext_pc3); + - removes the callback, so method is no longer invoked on pin transistion. + \ No newline at end of file diff --git a/gpio/gpio_interrupt_demo/application.fam b/gpio/gpio_interrupt_demo/application.fam new file mode 100644 index 0000000..39b705a --- /dev/null +++ b/gpio/gpio_interrupt_demo/application.fam @@ -0,0 +1,10 @@ +App( + appid="Gpio_Interrupt_Demo", + name="GPIO Interrupt Demo", + apptype=FlipperAppType.EXTERNAL, + entry_point="gpio_interrupt_demo_app", + requires=["gui", "gpio"], + stack_size=2 * 1024, + fap_icon="gpio_interrupt_demo.png", + fap_category="Gpio", +) diff --git a/gpio/gpio_interrupt_demo/gpio_interrupt_demo.png b/gpio/gpio_interrupt_demo/gpio_interrupt_demo.png new file mode 100644 index 0000000000000000000000000000000000000000..522b64971b2a44e7d665d4beaa1d5285b30ddb74 GIT binary patch literal 1817 zcma)7eM}Q)7(XO1`I_0rlo+;L4vji&?|M*>3$=r`LN^X8-|P#I-dlRo_HK7qT0TOI zu&EmgWHS>ao5;`&4a=A;a|(V;C8EMImL+40825+EVw?n~b75icwdJFZeKcwBeV^y| z{NCU5yw5$|Yst-uiCi6tAV`ck+n5KR$~tm+DE$5Mj=mW|LTWjK!D2QTP>aahI0rzG z^`i$`M-z?PVu$or`^7zERMgw=&183G(2jT88rzfiZ3{otmU{NvYuZ`k9GW9vVi ztUOQ-Uwz2t*TDki0z5ebYXMNu$=hW^?HEZHq#@ibYOt08KyItFLyRlmwm%a!6GtVA> z(nIQ#RD*O;VmNbovWvJ97g9H!wDG$vJaqcXe*LY1iOKwp<`d5qb?q6!I5PIn4)tZpE9g znef7s_g}TY((umITPr7ajk)co#>#8%yetm)ywH8~;qui-zOIXKPgRW$-;TeL$rcX& zvg~uu;rzR)>bj7Fs*0Jfl7^Lg3Af}Fm}X~Z;j-|;SUr412-&3)fDjN77W17 zW!H!xzb3c9T2pCFV=?{an8Bxw;gTq?4)YOHaIKhe4D~_eN*z{0TTlZp z0#vKkstF8+qiDLw+NeC^mOwb%=`g!23lxrfJRY?tNzIEDxF#(v4JSyPBvlZhlB!)Y z<5jt&1V2fD#t0;<$O$sXyHJIeDd*j?4ucAO(twv+Aa_ZD0>Dz>iU-%I2}P_AD%g3M zm+ZVSFD3{-?+yx*Iom(*E1n=Itl84cUPgEnNYnogbvox|OR}j7CLE|=kmNaHsi0Z_ zcpi{=w`c{XD&Ue6FjOf3W#ApWm=C)E=rB#dX5qZRQDWqrz$L-5LI<(fQ~HQ!zA%0= znr9(b5{53=;j9RZ3LPfpWo4pflOaWumXe|()$mZIQk2=rRDc}ds*vse0~f5t92;a* z5^=bd8!vQmFm*COQL^CBVW}iOM_u}NJG={uhMmeXGGk<9D3~N*pJ_-HNfZ!TDw(7- z+C(CaB4Fx1Fwb(f>i>fJx*LU`bQnX^R5mBcyjVSN4)kn3czuy6V03WGK`@B1Dyiu( zLF8Gt6|g=5&@S;d*~5q+qXOQa4$H9FIGBDlYEk+Y)u=UuI%N*)u>;5uoRcP*A~wzC zgfA(;*Go$X@Z0~N06%sq0YODwEFc|M-rU7iNynEepdYvBjrS$tx59A(cQ9^lHYvOL z`378YsEKfTE!)Y&!pZlH*_crfS8RPgtf{GqSQ* +#include + +#include +#include + +#include +#include + +#define TAG "gpio_interrupt_demo_app" + +typedef enum { + DemoEventTypeKey, + // You can add additional events here. + DemoEventTypeTick, +} DemoEventType; + +typedef struct { + DemoEventType type; // The reason for this event. + InputEvent input; // This data is specific to keypress data. + // You can add additional data that is helpful for your events. +} DemoEvent; + +typedef struct { + FuriString* buffer; + // You can add additional state here. +} DemoData; + +typedef struct { + FuriMessageQueue* queue; // Message queue (DemoEvent items to process). + FuriMutex* mutex; // Used to provide thread safe access to data. + DemoData* data; // Data accessed by multiple threads (acquire the mutex before accessing!) +} DemoContext; + +static uint8_t currentLed; // The current LED to light (1-6) +static uint8_t selectedLed; // The LED at the time of GPIO interrupt (or 0 if none). + +// 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 demo_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 from our timer. We queue a message and then return to the caller. +// @ctx our message queue. +static void demo_tick(void* ctx) { + furi_assert(ctx); + FuriMessageQueue* queue = ctx; + DemoEvent event = {.type = DemoEventTypeTick}; + furi_message_queue_put(queue, &event, 0); +} + +// Invoked when our GPIO pin transitions from VCC to GND. +// @ctx unused for this method. +static void demo_gpio_fall_callback(void* ctx) { + UNUSED(ctx); + selectedLed = currentLed; +} + +// 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 demo_render_callback(Canvas* canvas, void* ctx) { + // Attempt to aquire context, so we can read the data. + DemoContext* demo_context = ctx; + if(furi_mutex_acquire(demo_context->mutex, 200) != FuriStatusOk) { + return; + } + + DemoData* data = demo_context->data; + if(selectedLed) { + furi_string_printf(data->buffer, "Stopped on LED %d.", selectedLed); + } else { + furi_string_printf(data->buffer, "GND pin C3 to stop."); + } + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, 10, 20, AlignLeft, AlignTop, furi_string_get_cstr(data->buffer)); + + // Release the context, so other threads can update the data. + furi_mutex_release(demo_context->mutex); +} + +// Turns on one of the LEDs. +// @pin the LED to turn on (value of 1-6) +void update_led(uint8_t led) { + if(led == 1 || led == 3) { + // Pin A7 is 3.3 volts for LED 1 & 3. + furi_hal_gpio_init(&gpio_ext_pa7, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_ext_pa7, true); + } else { + furi_hal_gpio_init(&gpio_ext_pa7, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); + // Pin A7 is GND (false) for LED 2 & 4. + furi_hal_gpio_write(&gpio_ext_pa7, !(led == 2 || led == 4)); + } + + if(led == 2 || led == 5) { + // Pin A6 is 3.3 volts for LED 2 & 5. + furi_hal_gpio_init(&gpio_ext_pa6, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_ext_pa6, true); + } else { + furi_hal_gpio_init(&gpio_ext_pa6, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); + // Pin A6 is GND (false) for LED 1 & 6. + furi_hal_gpio_write(&gpio_ext_pa6, !(led == 1 || led == 6)); + } + + if(led == 4 || led == 6) { + // Pin A4 is 3.3 volts for LED 4 & 6. + furi_hal_gpio_init(&gpio_ext_pa4, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(&gpio_ext_pa4, true); + } else { + furi_hal_gpio_init(&gpio_ext_pa4, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); + // Pin A4 is GND (false) for LED 3 & 5. + furi_hal_gpio_write(&gpio_ext_pa4, !(led == 3 || led == 5)); + } +} + +// Disconnects a GpioPin via OutputOpenDrive, PushPullNo, output true. +// @pin pointer to GpioPin to disconnect. +void disconnect_pin(const GpioPin* pin) { + furi_hal_gpio_init(pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_write(pin, true); +} + +// Program entry point +int32_t gpio_interrupt_demo_app(void* p) { + UNUSED(p); + + // Configure our initial data. + DemoContext* demo_context = malloc(sizeof(DemoContext)); + demo_context->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + demo_context->data = malloc(sizeof(DemoData)); + demo_context->data->buffer = furi_string_alloc(); + + // Queue for events (tick or input) + demo_context->queue = furi_message_queue_alloc(8, sizeof(DemoEvent)); + + // LEDs connected on GPIO output pins (A7, A6, A4) via 220 ohm resistors. + currentLed = 1; + selectedLed = 0; + update_led(currentLed); + + // GPIO pin (C3) is pull-up to VCC. Add switch to ground for change in value. + // I use 220ohm resistor from switch to C3 (but optional if you are SURE + // the pin is in input/interrupt mode). + // + // GpioModeInterruptRiseFall means callback invoked when going from VCC (from our + // pull-up resistor) to GND. + // + // NOTE: You can use GpioModeInterruptRise for invoking on a GND->VCC and + // GpioModeInterruptRiseFall for invoking on both transitions. + // + furi_hal_gpio_init(&gpio_ext_pc3, GpioModeInterruptFall, GpioPullUp, GpioSpeedVeryHigh); + + // NOTE: "add_int_callback" does "enable_int_callback" automatically. + // For the 3rd parameter, you can pass any object that you want to be passed + // to your callback method. + furi_hal_gpio_add_int_callback(&gpio_ext_pc3, demo_gpio_fall_callback, NULL); + + // Timer triggers every 70ms (incrementing our current LED). + FuriTimer* timer = furi_timer_alloc(demo_tick, FuriTimerTypePeriodic, demo_context->queue); + furi_timer_start(timer, 70); + + // Set ViewPort callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, demo_render_callback, demo_context); + view_port_input_callback_set(view_port, demo_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, 1000) == 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; + } else if(event.input.type == InputTypeShort && event.input.key == InputKeyDown) { + // Clear our LED stop value. + selectedLed = 0; + } + break; + case DemoEventTypeTick: + if(selectedLed == 0) { + if(++currentLed > 6) { + currentLed = 1; + } + update_led(currentLed); + } + break; + default: + break; + } + + // Send signal to update the screen (callback will get invoked at some point later.) + view_port_update(view_port); + } + } while(processing); + + // Free resources + furi_hal_gpio_remove_int_callback(&gpio_ext_pc3); + + furi_timer_free(timer); + + // Pull all pins open. + disconnect_pin(&gpio_ext_pc3); + disconnect_pin(&gpio_ext_pa7); + disconnect_pin(&gpio_ext_pa6); + disconnect_pin(&gpio_ext_pa4); + + 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_mutex_free(demo_context->mutex); + furi_string_free(demo_context->data->buffer); + free(demo_context->data); + free(demo_context); + + return 0; +} \ No newline at end of file