Przeglądaj źródła

Merge pull request #22 from kbembedded/gblink-refactor

Implement flexible gblink api, add support for, and autodetect MALVEKE board
Esteban Fuentealba 2 lat temu
rodzic
commit
2c7e87eb84
14 zmienionych plików z 277 dodań i 81 usunięć
  1. 3 0
      .gitmodules
  2. 4 1
      README.md
  3. 7 4
      README_catalog.md
  4. 10 3
      application.fam
  5. 4 0
      changelog.md
  6. 1 0
      lib/flipper-gblink
  7. 20 0
      pokemon_app.c
  8. 5 0
      pokemon_app.h
  9. 10 0
      scenes/pokemon_menu.c
  10. 1 0
      scenes/pokemon_menu.h
  11. 169 0
      scenes/pokemon_pins.c
  12. 9 0
      scenes/pokemon_pins.h
  13. 33 73
      views/trade.c
  14. 1 0
      views/trade.h

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "lib/flipper-gblink"]
+	path = lib/flipper-gblink
+	url = https://github.com/kbembedded/flipper-gblink

+ 4 - 1
README.md

@@ -21,7 +21,9 @@ This is a Pokemon exchange application from Flipper Zero to Game Boy [(Generacti
 It currently trades a Pokemon based on your choice of Pokemon, Level, Stats and 4 Moves.
 
 ## Hardware Interface
-The Game Boy is connected to the Flipper Zero's GPIO pins via a GBC style Game Link Cable. A Flipper GPIO module with a proper Game Link Cable connector [is available here](https://www.tindie.com/products/kbembedded/game-link-gpio-module-for-flipper-zero-game-boy/)!
+The Game Boy is connected to the Flipper Zero's GPIO pins via a GBC style Game Link Cable. The [Flipper GB Link module](https://www.tindie.com/products/kbembedded/game-link-gpio-module-for-flipper-zero-game-boy/) is an easy way to connect a Game Boy via a Game Link Cable to the Flipper Zero.
+
+Additionally, the [MALVEKE - GAME BOY Tools for Flipper Zero](https://www.tindie.com/products/efuentealba/malveke-game-boy-tools-for-flipper-zero/) is supported by this tool.
 
 Details on the hardware interface, as well as how to create your own adapter board, can be found in the [How Does It Work](#how-does-it-work) section below.
 
@@ -53,6 +55,7 @@ And use [**qFlipper**](https://flipperzero.one/update) to copy the generated **p
 
 These instructions assume that you are starting at the Flipper Zero desktop. Otherwise, press the Back button until you are at the desktop.
 
+- If using a MALVEKE board, plug it in to the GPIO header now. The app will auto-detect and select the correct pinout to support the MALVEKE EXT1 interface. If using the Flipper GB Link board, or any other pinout, they can be connected to the Flipper Zero now, or at any point in the future.
 - Press the `OK` button on the Flipper to open the main menu.
 - Choose `Applications` from the menu.
 - Choose `GPIO` from the submenu.

+ 7 - 4
README_catalog.md

@@ -2,14 +2,16 @@
 
 ## Introduction
 
+Now supports MALVEKE board!
+
 This is a Pokemon exchange application from Flipper Zero to Game Boy (Generación I). Flipper Zero emulates a "Slave" Game Boy connected to a Game Link Cable to be able to exchange any Pokemon from the First Generation (Red, Blue, Yellow) to a real Game Boy.
 
-It is a Proof of Concept (POC) for using views, GPIO, and FURI (Flipper Universal Registry Implementation).
+If a MALVEKE board is plugged in to GPIO before starting the app, the app will default to using the MALVEKE EXT1 interface.
 
 
 ## Connection: Flipper Zero GPIO - Game Boy
 
-The pins should be connected as follows:
+The original pinout is as follows:
 
 | Cable Game Link (Socket) | Flipper Zero GPIO |
 | ------------------------ | ----------------- |
@@ -18,6 +20,9 @@ The pins should be connected as follows:
 | 3 (SI)                   | 7 (C3)            |
 | 2 (SO)                   | 5 (B3)            |
 
+Using the "Select Pinout" option, the Original, MALVEKE, or any custom pin configuration can be selected.
+
+
 ## How does it work?
 
 The method used to communicate 2 Game Boys is based on the SPI protocol, which is a very simple serial communication protocol in which a master device communicates with one or more slave devices. The protocol is bidirectional and synchronous, and uses three basic signals:
@@ -34,5 +39,3 @@ The Game Boy link protocol is synchronous and requires the slave device to respo
 ##  Tested In
 - Game Boy Color (GBC)
 - Game Boy Advance (GBA)
-
-

+ 10 - 3
application.fam

@@ -5,10 +5,17 @@ App(
     entry_point="pokemon_app",
     requires=["gui"],
     stack_size=2 * 1024,
-    fap_version=[1,4],
+    fap_version=[1,5],
     fap_category="GPIO",
     fap_icon="pokemon_10px.png",
     fap_icon_assets="assets",
-    fap_author="Esteban Fuentealba",
-    fap_weburl="https://github.com/EstebanFuentealba"
+    fap_author="Esteban Fuentealba, Kris Bahnsen, Darryn Cull",
+    fap_weburl="https://github.com/EstebanFuentealba",
+    fap_description="Pokemon exchange from Flipper Zero to Game Boy for Generation I (Pokemon Red, Blue, Yellow)",
+    fap_private_libs=[
+        Lib(
+            name="flipper-gblink",
+            sources=["gblink.c"],
+        ),
+    ],
 )

+ 4 - 0
changelog.md

@@ -1,5 +1,9 @@
 # Changelog - Patch Notes
 
+## Version 1.5
+- **Add Features:** Incorporate flipper-gblink library; Add support for MALVEKE board as well as custom pin selection; If MALVEKE board is detected, default to that pinout, otherwise use the original documented pinout.
+- **BUG:** The current MALVEKE pinout and interrupt use breaks the OK button after entering the trade screen.
+
 ## Version 1.4
 - **Bug Fixes:** More robust trade logic fixes issues with names, remove ability to use numbers in Pokemon/Trainer names as the game itself will not allow that, fix trade animation not always being animated, make FAP icon 1bpp.
 - **Add Features:** Implement trade patch list that Game Boy expects and uses, add ability to return to main menu to modify a Pokemon traded to the Flipper and re-enter trade without the Game Boy needing to power cycle and re-connect through the Link Club, add back debug logging.

+ 1 - 0
lib/flipper-gblink

@@ -0,0 +1 @@
+Subproject commit 91c64228207b2312289c67a83e321084381ebe50

+ 20 - 0
pokemon_app.c

@@ -2136,6 +2136,20 @@ static void trade_block_free(TradeBlock* trade) {
     free(trade);
 }
 
+/* The MALVEKE board has an esp32 which is set to TX on the flipper's default
+ * UART pins. If this pin shows signs of something connected, assume a MALVEKE
+ * board is being used.
+ */
+static bool detect_malveke(void) {
+    bool rc;
+
+    furi_hal_gpio_init(&gpio_usart_rx, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
+    rc = furi_hal_gpio_read(&gpio_usart_rx);
+    furi_hal_gpio_init_simple(&gpio_usart_rx, GpioModeAnalog);
+
+    return rc;
+}
+
 PokemonFap* pokemon_alloc() {
     PokemonFap* pokemon_fap = (PokemonFap*)malloc(sizeof(PokemonFap));
 
@@ -2157,6 +2171,11 @@ PokemonFap* pokemon_alloc() {
     // Set up defaults
     pokemon_fap->curr_pokemon = 0;
     pokemon_fap->curr_stats = 0;
+    pokemon_fap->malveke_detected = detect_malveke();
+    memcpy(
+        &pokemon_fap->pins,
+        &common_pinouts[pokemon_fap->malveke_detected],
+        sizeof(struct gblink_pins));
 
     // Set up trade party struct
     pokemon_fap->trade_block = trade_block_alloc();
@@ -2189,6 +2208,7 @@ PokemonFap* pokemon_alloc() {
     pokemon_fap->trade = trade_alloc(
         pokemon_fap->trade_block,
         pokemon_fap->pokemon_table,
+        &pokemon_fap->pins,
         pokemon_fap->view_dispatcher,
         AppViewTrade);
 

+ 5 - 0
pokemon_app.h

@@ -10,6 +10,7 @@
 #include <gui/modules/submenu.h>
 #include <gui/modules/text_input.h>
 #include <gui/modules/variable_item_list.h>
+#include <gblink.h>
 
 #include "pokemon_data.h"
 
@@ -75,6 +76,10 @@ struct pokemon_fap {
      */
     TradeBlock* trade_block;
 
+    /* Pin definition to actual Game Link Cable interface */
+    struct gblink_pins pins;
+    int malveke_detected;
+
     /* The currently selected pokemon */
     int curr_pokemon;
 

+ 10 - 0
scenes/pokemon_menu.c

@@ -11,6 +11,7 @@
 #include "pokemon_ot_id.h"
 #include "pokemon_ot_name.h"
 #include "pokemon_trade.h"
+#include "pokemon_pins.h"
 
 static void scene_change_from_main_cb(void* context, uint32_t index) {
     PokemonFap* pokemon_fap = (PokemonFap*)context;
@@ -95,6 +96,12 @@ void main_menu_scene_on_enter(void* context) {
         pokemon_fap->submenu, buf, SelectOTNameScene, scene_change_from_main_cb, pokemon_fap);
     submenu_add_item(
         pokemon_fap->submenu, "Trade PKMN", TradeScene, scene_change_from_main_cb, pokemon_fap);
+    submenu_add_item(
+        pokemon_fap->submenu,
+        "Select Pinout",
+        SelectPinsScene,
+        scene_change_from_main_cb,
+        pokemon_fap);
 
     submenu_set_selected_item(
         pokemon_fap->submenu,
@@ -128,6 +135,7 @@ void (*const pokemon_scene_on_enter_handlers[])(void*) = {
     select_ot_id_scene_on_enter,
     select_ot_name_scene_on_enter,
     trade_scene_on_enter,
+    select_pins_scene_on_enter,
 };
 
 void (*const pokemon_scene_on_exit_handlers[])(void*) = {
@@ -143,6 +151,7 @@ void (*const pokemon_scene_on_exit_handlers[])(void*) = {
     select_ot_id_scene_on_exit,
     select_ot_name_scene_on_exit,
     null_scene_on_exit,
+    select_pins_scene_on_exit,
 };
 
 bool (*const pokemon_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
@@ -158,6 +167,7 @@ bool (*const pokemon_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
     null_scene_on_event,
     null_scene_on_event,
     null_scene_on_event,
+    null_scene_on_event,
 };
 
 const SceneManagerHandlers pokemon_scene_manager_handlers = {

+ 1 - 0
scenes/pokemon_menu.h

@@ -18,6 +18,7 @@ typedef enum {
     SelectOTIDScene,
     SelectOTNameScene,
     TradeScene,
+    SelectPinsScene,
     SceneCount,
 } AppScene;
 

+ 169 - 0
scenes/pokemon_pins.c

@@ -0,0 +1,169 @@
+#include <gui/modules/variable_item_list.h>
+#include <furi.h>
+
+#include "../pokemon_app.h"
+#include "pokemon_menu.h"
+
+struct named_pins {
+    const char* text;
+    const GpioPin* pin;
+};
+
+static const struct named_pins named_pins[] = {
+    {"PA7", &gpio_ext_pa7},
+    {"PA6", &gpio_ext_pa6},
+    {"PA4", &gpio_ext_pa4},
+    {"PB3", &gpio_ext_pb3},
+    {"PB2", &gpio_ext_pb2},
+    {"PC3", &gpio_ext_pc3},
+    {"PC1", &gpio_ext_pc1},
+    {"PC0", &gpio_ext_pc0},
+    {},
+};
+
+#define NUM_PINS 8
+
+/* This must match gblink's enum order */
+static const char* named_groups[] = {
+    "Original",
+    "Malveke",
+    "Custom",
+    "",
+};
+
+struct itemlist_builder {
+    VariableItem* named;
+    VariableItem* serin;
+    VariableItem* serout;
+    VariableItem* clk;
+    uint8_t named_index;
+    uint8_t serin_index;
+    uint8_t serout_index;
+    uint8_t clk_index;
+};
+
+/* Just make it a global, whatever */
+static struct itemlist_builder builder = {0};
+static void select_pins_rebuild_list(PokemonFap* pokemon_fap);
+
+static void select_pins_set(PokemonFap* pokemon_fap) {
+    pokemon_fap->pins.serin = named_pins[builder.serin_index].pin;
+    pokemon_fap->pins.serout = named_pins[builder.serout_index].pin;
+    pokemon_fap->pins.clk = named_pins[builder.clk_index].pin;
+}
+
+static void select_named_group_callback(VariableItem* item) {
+    uint8_t index = variable_item_get_current_value_index(item);
+    PokemonFap* pokemon_fap = variable_item_get_context(item);
+
+    variable_item_set_current_value_text(item, named_groups[index]);
+    builder.named_index = index;
+    select_pins_rebuild_list(pokemon_fap);
+    variable_item_list_set_selected_item(pokemon_fap->variable_item_list, 0);
+}
+
+static void select_pins_serin_callback(VariableItem* item) {
+    uint8_t index = variable_item_get_current_value_index(item);
+    PokemonFap* pokemon_fap = variable_item_get_context(item);
+
+    variable_item_set_current_value_text(item, named_pins[index].text);
+    builder.serin_index = index;
+    select_pins_rebuild_list(pokemon_fap);
+    variable_item_list_set_selected_item(pokemon_fap->variable_item_list, 1);
+}
+
+static void select_pins_serout_callback(VariableItem* item) {
+    uint8_t index = variable_item_get_current_value_index(item);
+    PokemonFap* pokemon_fap = variable_item_get_context(item);
+
+    variable_item_set_current_value_text(item, named_pins[index].text);
+    builder.serout_index = index;
+    select_pins_rebuild_list(pokemon_fap);
+    variable_item_list_set_selected_item(pokemon_fap->variable_item_list, 2);
+}
+
+static void select_pins_clk_callback(VariableItem* item) {
+    uint8_t index = variable_item_get_current_value_index(item);
+    PokemonFap* pokemon_fap = variable_item_get_context(item);
+
+    variable_item_set_current_value_text(item, named_pins[index].text);
+    builder.clk_index = index;
+    select_pins_rebuild_list(pokemon_fap);
+    variable_item_list_set_selected_item(pokemon_fap->variable_item_list, 3);
+}
+
+static void select_pins_rebuild_list(PokemonFap* pokemon_fap) {
+    int num;
+
+    /* HACK: TODO: It would be better to do this programmatically, but, I'm kind
+     * of done working on this feature so its going to be hardcoded for now.
+     */
+    switch(builder.named_index) {
+    case 0: // Original
+        num = 1;
+        builder.serin_index = 5;
+        builder.serout_index = 3;
+        builder.clk_index = 4;
+        break;
+    case 1: // MALVEKE
+        num = 1;
+        builder.serin_index = 1;
+        builder.serout_index = 0;
+        builder.clk_index = 3;
+        break;
+    default:
+        num = NUM_PINS;
+        break;
+    }
+
+    /* HACK: */
+    pokemon_fap->malveke_detected = builder.named_index;
+
+    select_pins_set(pokemon_fap);
+
+    variable_item_list_reset(pokemon_fap->variable_item_list);
+
+    builder.named = variable_item_list_add(
+        pokemon_fap->variable_item_list, "Mode", 3, select_named_group_callback, pokemon_fap);
+    builder.serin = variable_item_list_add(
+        pokemon_fap->variable_item_list, "SI:", num, select_pins_serin_callback, pokemon_fap);
+    builder.serout = variable_item_list_add(
+        pokemon_fap->variable_item_list, "SO:", num, select_pins_serout_callback, pokemon_fap);
+    builder.clk = variable_item_list_add(
+        pokemon_fap->variable_item_list, "CLK:", num, select_pins_clk_callback, pokemon_fap);
+
+    variable_item_set_current_value_index(builder.named, builder.named_index);
+    variable_item_set_current_value_text(builder.named, named_groups[builder.named_index]);
+
+    variable_item_set_current_value_index(builder.serin, (num == 1 ? 0 : builder.serin_index));
+    variable_item_set_current_value_text(builder.serin, named_pins[builder.serin_index].text);
+
+    variable_item_set_current_value_index(builder.serout, (num == 1 ? 0 : builder.serout_index));
+    variable_item_set_current_value_text(builder.serout, named_pins[builder.serout_index].text);
+
+    variable_item_set_current_value_index(builder.clk, (num == 1 ? 0 : builder.clk_index));
+    variable_item_set_current_value_text(builder.clk, named_pins[builder.clk_index].text);
+}
+
+void select_pins_scene_on_exit(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewMainMenu);
+    view_dispatcher_remove_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}
+
+void select_pins_scene_on_enter(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    /* TODO: Figure out what defaults we should use for pins based on attached board! */
+    /* HACK: */
+    if(builder.named_index < 2) builder.named_index = pokemon_fap->malveke_detected;
+
+    select_pins_rebuild_list(pokemon_fap);
+
+    view_dispatcher_add_view(
+        pokemon_fap->view_dispatcher,
+        AppViewOpts,
+        variable_item_list_get_view(pokemon_fap->variable_item_list));
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}

+ 9 - 0
scenes/pokemon_pins.h

@@ -0,0 +1,9 @@
+#ifndef POKEMON_PINS_H
+#define POKEMON_PINS_H
+
+#pragma once
+
+void select_pins_scene_on_enter(void* context);
+void select_pins_scene_on_exit(void* context);
+
+#endif // POKEMON_PINS_H

+ 33 - 73
views/trade.c

@@ -83,14 +83,11 @@
 
 #include <gui/view.h>
 #include <pokemon_icons.h>
+#include <gblink.h>
 
 #include "../pokemon_app.h"
 #include "trade_patch_list.h"
 
-#define GAME_BOY_CLK gpio_ext_pb2
-#define GAME_BOY_SI gpio_ext_pc3
-#define GAME_BOY_SO gpio_ext_pb3
-
 #define DELAY_MICROSECONDS 15
 #define PKMN_BLANK 0x00
 
@@ -167,6 +164,8 @@ struct trade_ctx {
     TradeBlock* input_block;
     const PokemonTable* pokemon_table;
     struct patch_list* patch_list;
+    void* gblink_handle;
+    struct gblink_pins* gblink_pins;
 };
 
 /* These are the needed variables for the draw callback */
@@ -629,7 +628,7 @@ static uint8_t getTradeCentreResponse(struct trade_ctx* trade) {
     return send;
 }
 
-void transferBit(void* context) {
+static void transferBit(void* context, uint8_t in_byte) {
     furi_assert(context);
 
     struct trade_ctx* trade = (struct trade_ctx*)context;
@@ -638,58 +637,23 @@ void transferBit(void* context) {
     with_view_model(
         trade->view, struct trade_model * model, { status = model->gameboy_status; }, false);
 
-    /* Shift data in every clock */
-    trade->in_data <<= 1;
-    trade->in_data |= furi_hal_gpio_read(&GAME_BOY_SI);
-    trade->shift++;
+    trade->in_data = in_byte;
 
     /* Once a byte of data has been shifted in, process it */
-    if(trade->shift == 8) {
-        trade->shift = 0;
-        switch(status) {
-        case GAMEBOY_CONN_FALSE:
-            trade->out_data = getConnectResponse(trade);
-            break;
-        case GAMEBOY_CONN_TRUE:
-            trade->out_data = getMenuResponse(trade);
-            break;
-        case GAMEBOY_COLOSSEUM:
-            trade->out_data = trade->in_data;
-            break;
-        /* Every other state is trade related */
-        default:
-            trade->out_data = getTradeCentreResponse(trade);
-            break;
-        }
-    }
-}
-
-void input_clk_gameboy(void* context) {
-    furi_assert(context);
-
-    struct trade_ctx* trade = (struct trade_ctx*)context;
-    static uint32_t time;
-    /* Clocks idle between bytes is nominally 430 us long for burst data,
-     * 15 ms for idle polling (e.g. waiting for menu selection), some oddball
-     * 2 ms gaps that appears between one 0xFE byte from the Game Boy every trade;
-     * clock period is nominally 122 us.
-     * Therefore, if we haven't seen a clock in 500 us, reset our bit counter.
-     * Note that, this should never actually be a concern, but it is an additional
-     * safeguard against desyncing.
-     */
-    const uint32_t time_ticks = furi_hal_cortex_instructions_per_microsecond() * 500;
-
-    if(furi_hal_gpio_read(&GAME_BOY_CLK)) {
-        if((DWT->CYCCNT - time) > time_ticks) {
-            trade->in_data = 0;
-            trade->shift = 0;
-        }
-        transferBit(trade);
-        time = DWT->CYCCNT;
-    } else {
-        /* On the falling edge of each clock, set up the next bit */
-        furi_hal_gpio_write(&GAME_BOY_SO, !!(trade->out_data & 0x80));
-        trade->out_data <<= 1;
+    switch(status) {
+    case GAMEBOY_CONN_FALSE:
+        gblink_transfer(trade->gblink_handle, getConnectResponse(trade));
+        break;
+    case GAMEBOY_CONN_TRUE:
+        gblink_transfer(trade->gblink_handle, getMenuResponse(trade));
+        break;
+    case GAMEBOY_COLOSSEUM:
+        gblink_transfer(trade->gblink_handle, in_byte);
+        break;
+    /* Every other state is trade related */
+    default:
+        gblink_transfer(trade->gblink_handle, getTradeCentreResponse(trade));
+        break;
     }
 }
 
@@ -697,6 +661,7 @@ void trade_enter_callback(void* context) {
     furi_assert(context);
     struct trade_ctx* trade = (struct trade_ctx*)context;
     struct trade_model* model;
+    struct gblink_def gblink_def = {0};
 
     model = view_get_model(trade->view);
 
@@ -713,9 +678,15 @@ void trade_enter_callback(void* context) {
 
     view_commit_model(trade->view, true);
 
-    trade->in_data = 0;
-    trade->out_data = 0;
-    trade->shift = 0;
+    /* TODO: This should be moved further back to struct pokemon_fap for whole
+     * app flexibility since it would probably be written to by a different scene
+     */
+    gblink_def.pins = trade->gblink_pins;
+    gblink_def.callback = transferBit;
+    gblink_def.cb_context = trade;
+
+    trade->gblink_handle = gblink_alloc(&gblink_def);
+    gblink_nobyte_set(trade->gblink_handle, SERIAL_NO_DATA_BYTE);
 
     /* Every 250 ms, trigger a draw update. 250 ms was chosen so that during
      * the trade process, each update can flip the LED and screen to make the
@@ -724,18 +695,6 @@ void trade_enter_callback(void* context) {
     trade->draw_timer = furi_timer_alloc(trade_draw_timer_callback, FuriTimerTypePeriodic, trade);
     furi_timer_start(trade->draw_timer, furi_ms_to_ticks(250));
 
-    // B3 (Pin6) / SO (2)
-    furi_hal_gpio_write(&GAME_BOY_SO, false);
-    furi_hal_gpio_init(&GAME_BOY_SO, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
-    // B2 (Pin5) / SI (3)
-    furi_hal_gpio_write(&GAME_BOY_SI, false);
-    furi_hal_gpio_init(&GAME_BOY_SI, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
-    // // C3 (Pin7) / CLK (5)
-    furi_hal_gpio_init(&GAME_BOY_CLK, GpioModeInterruptRiseFall, GpioPullUp, GpioSpeedVeryHigh);
-    furi_hal_gpio_remove_int_callback(&GAME_BOY_CLK);
-
-    furi_hal_gpio_add_int_callback(&GAME_BOY_CLK, input_clk_gameboy, trade);
-
     /* Create a trade patch list from the current trade block */
     plist_create(&(trade->patch_list), trade->trade_block);
 }
@@ -758,9 +717,8 @@ void trade_exit_callback(void* context) {
     furi_timer_free(trade->draw_timer);
     trade->draw_timer = NULL;
 
-    /* Unset our interrupt callback */
-    furi_hal_gpio_remove_int_callback(&GAME_BOY_CLK);
-    disconnect_pin(&GAME_BOY_CLK);
+    /* Unset the pin settings */
+    gblink_free(trade->gblink_handle);
 
     /* Destroy the patch list, it is allocated on the enter callback */
     plist_free(trade->patch_list);
@@ -770,6 +728,7 @@ void trade_exit_callback(void* context) {
 void* trade_alloc(
     TradeBlock* trade_block,
     const PokemonTable* table,
+    struct gblink_pins* gblink_pins,
     ViewDispatcher* view_dispatcher,
     uint32_t view_id) {
     furi_assert(trade_block);
@@ -782,6 +741,7 @@ void* trade_alloc(
     trade->input_block = malloc(sizeof(TradeBlock));
     trade->pokemon_table = table;
     trade->patch_list = NULL;
+    trade->gblink_pins = gblink_pins;
 
     view_set_context(trade->view, trade);
     view_allocate_model(trade->view, ViewModelTypeLockFree, sizeof(struct trade_model));

+ 1 - 0
views/trade.h

@@ -9,6 +9,7 @@
 void* trade_alloc(
     TradeBlock* trade_block,
     const PokemonTable* table,
+    struct gblink_pins* gblink_pins,
     ViewDispatcher* view_dispatcher,
     uint32_t view_id);