Просмотр исходного кода

Low frequency RFID app [Read stage] (#385)

* App Lfrfid: init
* HAL-resources: add external gpios
* HAL-pwm: fix frequency calculation
* App LFRFID: generic manchester decoder
* App LFRFID: em-marine decoder
* App iButton: fix dwt timing acquire
* App LFRFID: rfid reader
* App LFRFID: temporary read keys on read scene
* App LFRFID: remove atomic bool init.
* App LFRFID: add *.c to build
* App LFRFID: unstable HID decoder
* App LFRFID: HID-26 reading
* HAL OS: disable sleep
* App LFRFID: HID-26 reader: remove debug
* App LFRFID: static data decoder-analyzer
* App LFRFID: very raw Indala decoder
* App LFRFID: multiprotocol reader
* App LFRFID: more reliable HID decoder
* App LFRFID: syntax fix
* App LFRFID: simple read scene
* Gui: force redraw on screen stream connect
* HAL-OS: allow sleep
* App LFRFID: notify api, tune view, tune scene
* App LFRFID: simple rfid emulator
* App LFRFID: more scenes, more reliable EM decoder.
* App LFRFID: format fix
* App LFRFID: warning fix
* Api-hal-resources: add rfid pins, rename external pins
* App LFRFID: remove unused emulator
* App LFRFID: use new gpio hal api
* App accessor: use new ext gpio name
* App LFRFID: remove unused emulator
* App LFRFID: remove debug gpio
* Api-hal-resources: alternate functions init
* Api-hal-rfid: new api
* Api-hal-ibutton: new api
* Api-hal: new headers
* App LFRFID: use new api in reader subroutines
* App LFRFID: use new api in emulator subroutines
* App LFRFID: remove old app
* App LFRFID, App iButton: fix memleak
* Api-hal-rfid: comments
* App LFRFID: pulse joiner helper, it combines pulses of different polarity into one pulse suitable for a timer
* App LFRFID: pulse joiner, now can accept only ne pulse
* App LFRFID: pulse joiner, fixes
* App LFRFID: EM encoder and emulation
* App LFRFID: format fixes
* App LFRFID: emmarine encoder cleanup
* App LFRFID: HID Encoder blank
* App LFRFID: Indala Encoder blank
SG 4 лет назад
Родитель
Сommit
46bc515c6a
66 измененных файлов с 3278 добавлено и 514 удалено
  1. 4 4
      applications/accessor/helpers/wiegand.cpp
  2. 3 6
      applications/applications.c
  3. 1 0
      applications/gui/gui.c
  4. 3 4
      applications/ibutton/helpers/key-reader.cpp
  5. 6 0
      applications/ibutton/ibutton-app.cpp
  6. 1 0
      applications/ibutton/scene/ibutton-scene-generic.h
  7. 0 0
      applications/lf-rfid-old/em4100.c
  8. 48 0
      applications/lf-rfid/helpers/decoder-analyzer.cpp
  9. 21 0
      applications/lf-rfid/helpers/decoder-analyzer.h
  10. 172 0
      applications/lf-rfid/helpers/decoder-emmarine.cpp
  11. 20 0
      applications/lf-rfid/helpers/decoder-emmarine.h
  12. 185 0
      applications/lf-rfid/helpers/decoder-hid26.cpp
  13. 26 0
      applications/lf-rfid/helpers/decoder-hid26.h
  14. 170 0
      applications/lf-rfid/helpers/decoder-indala.cpp
  15. 28 0
      applications/lf-rfid/helpers/decoder-indala.h
  16. 15 0
      applications/lf-rfid/helpers/emmarine.h
  17. 58 0
      applications/lf-rfid/helpers/encoder-emmarine.cpp
  18. 23 0
      applications/lf-rfid/helpers/encoder-emmarine.h
  19. 27 0
      applications/lf-rfid/helpers/encoder-generic.h
  20. 13 0
      applications/lf-rfid/helpers/encoder-hid.cpp
  21. 19 0
      applications/lf-rfid/helpers/encoder-hid.h
  22. 27 0
      applications/lf-rfid/helpers/encoder-indala.cpp
  23. 22 0
      applications/lf-rfid/helpers/encoder-indala.h
  24. 9 0
      applications/lf-rfid/helpers/key-info.h
  25. 34 0
      applications/lf-rfid/helpers/manchester-decoder.c
  26. 31 0
      applications/lf-rfid/helpers/manchester-decoder.h
  27. 95 0
      applications/lf-rfid/helpers/pulse-joiner.cpp
  28. 36 0
      applications/lf-rfid/helpers/pulse-joiner.h
  29. 134 0
      applications/lf-rfid/helpers/rfid-reader.cpp
  30. 41 0
      applications/lf-rfid/helpers/rfid-reader.h
  31. 404 0
      applications/lf-rfid/helpers/rfid-timer-emulator.cpp
  32. 36 0
      applications/lf-rfid/helpers/rfid-timer-emulator.h
  33. 146 0
      applications/lf-rfid/lf-rfid-app.cpp
  34. 73 0
      applications/lf-rfid/lf-rfid-app.h
  35. 21 0
      applications/lf-rfid/lf-rfid-event.h
  36. 90 0
      applications/lf-rfid/lf-rfid-view-manager.cpp
  37. 42 0
      applications/lf-rfid/lf-rfid-view-manager.h
  38. 0 421
      applications/lf-rfid/lf-rfid.c
  39. 10 0
      applications/lf-rfid/lf-rfid.cpp
  40. 34 0
      applications/lf-rfid/scene/lf-rfid-scene-emulate-emmarine.cpp
  41. 12 0
      applications/lf-rfid/scene/lf-rfid-scene-emulate-emmarine.h
  42. 34 0
      applications/lf-rfid/scene/lf-rfid-scene-emulate-hid.cpp
  43. 12 0
      applications/lf-rfid/scene/lf-rfid-scene-emulate-hid.h
  44. 34 0
      applications/lf-rfid/scene/lf-rfid-scene-emulate-indala.cpp
  45. 12 0
      applications/lf-rfid/scene/lf-rfid-scene-emulate-indala.h
  46. 14 0
      applications/lf-rfid/scene/lf-rfid-scene-generic.h
  47. 34 0
      applications/lf-rfid/scene/lf-rfid-scene-read-indala.cpp
  48. 15 0
      applications/lf-rfid/scene/lf-rfid-scene-read-indala.h
  49. 80 0
      applications/lf-rfid/scene/lf-rfid-scene-read-normal.cpp
  50. 15 0
      applications/lf-rfid/scene/lf-rfid-scene-read-normal.h
  51. 77 0
      applications/lf-rfid/scene/lf-rfid-scene-start.cpp
  52. 13 0
      applications/lf-rfid/scene/lf-rfid-scene-start.h
  53. 35 0
      applications/lf-rfid/scene/lf-rfid-scene-tune.cpp
  54. 13 0
      applications/lf-rfid/scene/lf-rfid-scene-tune.h
  55. 202 0
      applications/lf-rfid/view/lf-rfid-view-tune.cpp
  56. 25 0
      applications/lf-rfid/view/lf-rfid-view-tune.h
  57. 20 0
      firmware/targets/api-hal-include/api-hal-ibutton.h
  58. 73 0
      firmware/targets/api-hal-include/api-hal-rfid.h
  59. 2 0
      firmware/targets/api-hal-include/api-hal.h
  60. 50 27
      firmware/targets/f5/api-hal/api-hal-gpio.c
  61. 106 1
      firmware/targets/f5/api-hal/api-hal-gpio.h
  62. 24 0
      firmware/targets/f5/api-hal/api-hal-ibutton.c
  63. 4 4
      firmware/targets/f5/api-hal/api-hal-pwm.c
  64. 30 36
      firmware/targets/f5/api-hal/api-hal-resources.c
  65. 12 11
      firmware/targets/f5/api-hal/api-hal-resources.h
  66. 202 0
      firmware/targets/f5/api-hal/api-hal-rfid.c

+ 4 - 4
applications/accessor/helpers/wiegand.cpp

@@ -40,11 +40,11 @@ void input_isr(void* _pin, void* _ctx) {
     uint32_t pin = (uint32_t)_pin;
     WIEGAND* _this = static_cast<WIEGAND*>(_ctx);
 
-    if(pin == ext_pa6_gpio.pin) {
+    if(pin == gpio_ext_pa6.pin) {
         _this->ReadD0();
     }
 
-    if(pin == ext_pa7_gpio.pin) {
+    if(pin == gpio_ext_pa7.pin) {
         _this->ReadD1();
     }
 }
@@ -57,8 +57,8 @@ void WIEGAND::begin() {
     _wiegandType = 0;
     _bitCount = 0;
 
-    const GpioPin* pinD0 = &ext_pa6_gpio;
-    const GpioPin* pinD1 = &ext_pa7_gpio;
+    const GpioPin* pinD0 = &gpio_ext_pa6;
+    const GpioPin* pinD1 = &gpio_ext_pa7;
 
     hal_gpio_init(pinD0, GpioModeInterruptFall, GpioPullNo, GpioSpeedLow); // Set D0 pin as input
     hal_gpio_init(pinD1, GpioModeInterruptFall, GpioPullNo, GpioSpeedLow); // Set D1 pin as input

+ 3 - 6
applications/applications.c

@@ -13,7 +13,7 @@ int32_t gui_task(void* p);
 int32_t backlight_control(void* p);
 int32_t irda(void* p);
 int32_t app_loader(void* p);
-int32_t lf_rfid_workaround(void* p);
+int32_t app_lfrfid(void* p);
 int32_t nfc_task(void* p);
 int32_t dolphin_task(void* p);
 int32_t power_task(void* p);
@@ -82,10 +82,7 @@ const FlipperApplication FLIPPER_SERVICES[] = {
 #endif
 
 #ifdef SRV_LF_RFID
-    {.app = lf_rfid_workaround,
-     .name = "lf rfid workaround",
-     .stack_size = 1024,
-     .icon = A_Plugins_14},
+    {.app = app_lfrfid, .name = "125 kHz RFID", .stack_size = 1024, .icon = A_Plugins_14},
 #endif
 
 #ifdef SRV_IRDA
@@ -158,7 +155,7 @@ const FlipperApplication FLIPPER_APPS[] = {
 #endif
 
 #ifdef APP_LF_RFID
-    {.app = lf_rfid_workaround, .name = "125 kHz RFID", .stack_size = 1024, .icon = A_125khz_14},
+    {.app = app_lfrfid, .name = "125 kHz RFID", .stack_size = 1024, .icon = A_125khz_14},
 #endif
 
 #ifdef APP_IRDA

+ 1 - 0
applications/gui/gui.c

@@ -218,6 +218,7 @@ void gui_cli_screen_stream(string_t args, void* context) {
     Gui* gui = context;
     gui_set_framebuffer_callback_context(gui, gui);
     gui_set_framebuffer_callback(gui, gui_cli_screen_stream_callback);
+    gui_redraw(gui);
     cli_getc(gui->cli);
     gui_set_framebuffer_callback(gui, NULL);
     gui_set_framebuffer_callback_context(gui, NULL);

+ 3 - 4
applications/ibutton/helpers/key-reader.cpp

@@ -140,11 +140,10 @@ void KeyReader::comparator_trigger_callback(void* hcomp, void* comp_ctx) {
     KeyReader* _this = static_cast<KeyReader*>(comp_ctx);
 
     if(hcomp == &hcomp1) {
-        _this->cyfral_decoder.process_front(
-            hal_gpio_get_rfid_in_level(), DWT->CYCCNT - last_dwt_value);
+        uint32_t current_dwt_value = DWT->CYCCNT;
 
-        _this->metakom_decoder.process_front(
-            hal_gpio_get_rfid_in_level(), DWT->CYCCNT - last_dwt_value);
+        _this->cyfral_decoder.process_front(hal_gpio_get_rfid_in_level(), current_dwt_value);
+        _this->metakom_decoder.process_front(hal_gpio_get_rfid_in_level(), current_dwt_value);
 
         last_dwt_value = DWT->CYCCNT;
     }

+ 6 - 0
applications/ibutton/ibutton-app.cpp

@@ -197,6 +197,12 @@ iButtonApp::~iButtonApp() {
     cli_delete_command(cli, "tm");
     furi_record_close("cli");
     osMessageQueueDelete(cli_event_result);
+
+    for(std::map<Scene, iButtonScene*>::iterator it = scenes.begin(); it != scenes.end(); ++it) {
+        delete it->second;
+        scenes.erase(it);
+    }
+
     api_hal_power_insomnia_exit();
 }
 

+ 1 - 0
applications/ibutton/scene/ibutton-scene-generic.h

@@ -8,6 +8,7 @@ public:
     virtual void on_enter(iButtonApp* app) = 0;
     virtual bool on_event(iButtonApp* app, iButtonEvent* event) = 0;
     virtual void on_exit(iButtonApp* app) = 0;
+    virtual ~iButtonScene(){};
 
 private:
 };

+ 0 - 0
applications/lf-rfid/em4100.c → applications/lf-rfid-old/em4100.c


+ 48 - 0
applications/lf-rfid/helpers/decoder-analyzer.cpp

@@ -0,0 +1,48 @@
+#include "decoder-analyzer.h"
+#include <furi.h>
+#include <api-hal.h>
+
+bool DecoderAnalyzer::read(uint8_t* _data, uint8_t _data_size) {
+    bool result = false;
+
+    if(ready) {
+        result = true;
+
+        for(size_t i = 0; i < data_size; i++) {
+            printf("%lu ", data[i]);
+            if((i + 1) % 8 == 0) printf("\r\n");
+        }
+        printf("\r\n--------\r\n");
+
+        ready = false;
+    }
+
+    return result;
+}
+
+void DecoderAnalyzer::process_front(bool polarity, uint32_t time) {
+    if(ready) return;
+
+    data[data_index] = time;
+
+    if(data_index < data_size) {
+        data_index++;
+    } else {
+        data_index = 0;
+        ready = true;
+    }
+}
+
+DecoderAnalyzer::DecoderAnalyzer() {
+    data = reinterpret_cast<uint32_t*>(calloc(data_size, sizeof(uint32_t)));
+    furi_check(data);
+    data_index = 0;
+    ready = false;
+}
+
+DecoderAnalyzer::~DecoderAnalyzer() {
+    free(data);
+}
+
+void DecoderAnalyzer::reset_state() {
+}

+ 21 - 0
applications/lf-rfid/helpers/decoder-analyzer.h

@@ -0,0 +1,21 @@
+#pragma once
+#include <stdint.h>
+#include <atomic>
+
+class DecoderAnalyzer {
+public:
+    bool read(uint8_t* data, uint8_t data_size);
+    void process_front(bool polarity, uint32_t time);
+
+    DecoderAnalyzer();
+    ~DecoderAnalyzer();
+
+private:
+    void reset_state();
+
+    std::atomic<bool> ready;
+
+    static const uint32_t data_size = 2048;
+    uint32_t data_index = 0;
+    uint32_t* data;
+};

+ 172 - 0
applications/lf-rfid/helpers/decoder-emmarine.cpp

@@ -0,0 +1,172 @@
+#include "emmarine.h"
+#include "decoder-emmarine.h"
+#include <furi.h>
+#include <api-hal.h>
+
+constexpr uint32_t clocks_in_us = 64;
+constexpr uint32_t short_time = 255 * clocks_in_us;
+constexpr uint32_t long_time = 510 * clocks_in_us;
+constexpr uint32_t jitter_time = 100 * clocks_in_us;
+
+constexpr uint32_t short_time_low = short_time - jitter_time;
+constexpr uint32_t short_time_high = short_time + jitter_time;
+constexpr uint32_t long_time_low = long_time - jitter_time;
+constexpr uint32_t long_time_high = long_time + jitter_time;
+
+void DecoderEMMarine::reset_state() {
+    ready = false;
+    readed_data = 0;
+    manchester_advance(
+        manchester_saved_state, ManchesterEventReset, &manchester_saved_state, nullptr);
+}
+
+void printEM_raw(uint64_t data) {
+    // header
+    for(uint8_t i = 0; i < 9; i++) {
+        printf("%u ", data & (1LLU << 63) ? 1 : 0);
+        data = data << 1;
+    }
+    printf("\r\n");
+
+    // nibbles
+    for(uint8_t r = 0; r < 11; r++) {
+        printf("        ");
+        uint8_t value = 0;
+        for(uint8_t i = 0; i < 5; i++) {
+            printf("%u ", data & (1LLU << 63) ? 1 : 0);
+            if(i < 4) value = (value << 1) | (data & (1LLU << 63) ? 1 : 0);
+            data = data << 1;
+        }
+        printf("0x%X", value);
+        printf("\r\n");
+    }
+}
+
+void printEM_data(uint64_t data) {
+    printf("EM ");
+
+    // header
+    for(uint8_t i = 0; i < 9; i++) {
+        data = data << 1;
+    }
+
+    // nibbles
+    for(uint8_t r = 0; r < EM_ROW_COUNT; r++) {
+        uint8_t value = 0;
+        for(uint8_t i = 0; i < 5; i++) {
+            if(i < 4) value = (value << 1) | (data & (1LLU << 63) ? 1 : 0);
+            data = data << 1;
+        }
+        printf("%X", value);
+        if(r % 2) printf(" ");
+    }
+    printf("\r\n");
+}
+
+void copyEM_data(uint64_t data, uint8_t* result, uint8_t result_size) {
+    furi_assert(result_size >= 5);
+    uint8_t result_index = 0;
+
+    // clean result
+    memset(result, 0, result_size);
+
+    // header
+    for(uint8_t i = 0; i < 9; i++) {
+        data = data << 1;
+    }
+
+    // nibbles
+    uint8_t value = 0;
+    for(uint8_t r = 0; r < EM_ROW_COUNT; r++) {
+        uint8_t nibble = 0;
+        for(uint8_t i = 0; i < 5; i++) {
+            if(i < 4) nibble = (nibble << 1) | (data & (1LLU << 63) ? 1 : 0);
+            data = data << 1;
+        }
+        value = (value << 4) | nibble;
+        if(r % 2) {
+            result[result_index] |= value;
+            result_index++;
+            value = 0;
+        }
+    }
+}
+
+bool DecoderEMMarine::read(uint8_t* data, uint8_t data_size) {
+    bool result = false;
+
+    if(ready) {
+        result = true;
+        copyEM_data(readed_data, data, data_size);
+        ready = false;
+    }
+
+    return result;
+}
+
+void DecoderEMMarine::process_front(bool polarity, uint32_t time) {
+    if(ready) return;
+    if(time < short_time_low) return;
+
+    ManchesterEvent event = ManchesterEventReset;
+
+    if(time > short_time_low && time < short_time_high) {
+        if(polarity) {
+            event = ManchesterEventShortHigh;
+        } else {
+            event = ManchesterEventShortLow;
+        }
+    } else if(time > long_time_low && time < long_time_high) {
+        if(polarity) {
+            event = ManchesterEventLongHigh;
+        } else {
+            event = ManchesterEventLongLow;
+        }
+    }
+
+    if(event != ManchesterEventReset) {
+        bool data;
+        bool data_ok =
+            manchester_advance(manchester_saved_state, event, &manchester_saved_state, &data);
+
+        if(data_ok) {
+            readed_data = (readed_data << 1) | data;
+
+            // header and stop bit
+            if((readed_data & EM_HEADER_AND_STOP_MASK) != EM_HEADER_AND_STOP_DATA) return;
+
+            // row parity
+            for(uint8_t i = 0; i < EM_ROW_COUNT; i++) {
+                uint8_t parity_sum = 0;
+
+                for(uint8_t j = 0; j < 5; j++) {
+                    parity_sum += (readed_data >> (EM_FIRST_ROW_POS - i * 5 + j)) & 1;
+                }
+
+                if((parity_sum % 2)) {
+                    return;
+                }
+            }
+
+            // columns parity
+            for(uint8_t i = 0; i < 4; i++) {
+                uint8_t parity_sum = 0;
+
+                for(uint8_t j = 0; j < EM_ROW_COUNT + 1; j++) {
+                    parity_sum += (readed_data >> (EM_COLUMN_POS - i + j * 5)) & 1;
+                }
+
+                if((parity_sum % 2)) {
+                    return;
+                }
+            }
+
+            // checks ok
+            ready = true;
+        }
+    }
+}
+
+DecoderEMMarine::DecoderEMMarine() {
+    reset_state();
+}

+ 20 - 0
applications/lf-rfid/helpers/decoder-emmarine.h

@@ -0,0 +1,20 @@
+#pragma once
+#include <stdint.h>
+#include <atomic>
+#include "manchester-decoder.h"
+
+class DecoderEMMarine {
+public:
+    bool read(uint8_t* data, uint8_t data_size);
+    void process_front(bool polarity, uint32_t time);
+
+    DecoderEMMarine();
+
+private:
+    void reset_state();
+
+    uint64_t readed_data = 0;
+    std::atomic<bool> ready;
+
+    ManchesterState manchester_saved_state;
+};

+ 185 - 0
applications/lf-rfid/helpers/decoder-hid26.cpp

@@ -0,0 +1,185 @@
+#include "decoder-hid26.h"
+#include <api-hal.h>
+
+constexpr uint32_t clocks_in_us = 64;
+
+constexpr uint32_t jitter_time_us = 20;
+constexpr uint32_t min_time_us = 64;
+constexpr uint32_t max_time_us = 80;
+
+constexpr uint32_t min_time = (min_time_us - jitter_time_us) * clocks_in_us;
+constexpr uint32_t mid_time = ((max_time_us - min_time_us) / 2 + min_time_us) * clocks_in_us;
+constexpr uint32_t max_time = (max_time_us + jitter_time_us) * clocks_in_us;
+
+bool DecoderHID26::read(uint8_t* data, uint8_t data_size) {
+    bool result = false;
+    furi_assert(data_size >= 3);
+
+    if(ready) {
+        result = true;
+        data[0] = facility;
+        data[1] = (uint8_t)(number >> 8);
+        data[2] = (uint8_t)number;
+
+        //printf("HID %02X %02X %02X\r\n", facility, (uint8_t)(number >> 8), (uint8_t)number);
+        ready = false;
+    }
+
+    return result;
+}
+
+void DecoderHID26::process_front(bool polarity, uint32_t time) {
+    if(ready) return;
+
+    if(polarity == true) {
+        last_pulse_time = time;
+    } else {
+        last_pulse_time += time;
+
+        if(last_pulse_time > min_time && last_pulse_time < max_time) {
+            bool pulse;
+
+            if(last_pulse_time < mid_time) {
+                // 6 pulses
+                pulse = false;
+            } else {
+                // 5 pulses
+                pulse = true;
+            }
+
+            if(last_pulse == pulse) {
+                pulse_count++;
+
+                if(pulse) {
+                    if(pulse_count > 4) {
+                        pulse_count = 0;
+                        store_data(1);
+                    }
+                } else {
+                    if(pulse_count > 5) {
+                        pulse_count = 0;
+                        store_data(0);
+                    }
+                }
+            } else {
+                if(last_pulse) {
+                    if(pulse_count > 2) {
+                        store_data(1);
+                    }
+                } else {
+                    if(pulse_count > 3) {
+                        store_data(0);
+                    }
+                }
+
+                pulse_count = 0;
+                last_pulse = pulse;
+            }
+        }
+    }
+}
+
+DecoderHID26::DecoderHID26() {
+    reset_state();
+}
+
+void DecoderHID26::store_data(bool data) {
+    stored_data[0] = (stored_data[0] << 1) | ((stored_data[1] >> 31) & 1);
+    stored_data[1] = (stored_data[1] << 1) | ((stored_data[2] >> 31) & 1);
+    stored_data[2] = (stored_data[2] << 1) | data;
+    validate_stored_data();
+}
+
+void DecoderHID26::validate_stored_data() {
+    // packet preamble
+    // raw data
+    if(*(reinterpret_cast<uint8_t*>(stored_data) + 3) != 0x1D) {
+        return;
+    }
+
+    // encoded company/oem
+    // coded with 01 = 0, 10 = 1 transitions
+    // stored in word 0
+    if((*stored_data >> 10 & 0x3FFF) != 0x1556) {
+        return;
+    }
+
+    // encoded format/length
+    // coded with 01 = 0, 10 = 1 transitions
+    // stored in word 0 and word 1
+    if((((*stored_data & 0x3FF) << 12) | ((*(stored_data + 1) >> 20) & 0xFFF)) != 0x155556) {
+        return;
+    }
+
+    // data decoding
+    uint32_t result = 0;
+
+    // decode from word 1
+    // coded with 01 = 0, 10 = 1 transitions
+    for(int8_t i = 9; i >= 0; i--) {
+        switch((*(stored_data + 1) >> (2 * i)) & 0b11) {
+        case 0b01:
+            result = (result << 1) | 0;
+            break;
+        case 0b10:
+            result = (result << 1) | 1;
+            break;
+        default:
+            return;
+            break;
+        }
+    }
+
+    // decode from word 2
+    // coded with 01 = 0, 10 = 1 transitions
+    for(int8_t i = 15; i >= 0; i--) {
+        switch((*(stored_data + 2) >> (2 * i)) & 0b11) {
+        case 0b01:
+            result = (result << 1) | 0;
+            break;
+        case 0b10:
+            result = (result << 1) | 1;
+            break;
+        default:
+            return;
+            break;
+        }
+    }
+
+    // store decoded data
+    facility = result >> 17;
+    number = result >> 1;
+
+    // trailing parity (odd) test
+    uint8_t parity_sum = 0;
+    for(int8_t i = 0; i < 13; i++) {
+        if(((result >> i) & 1) == 1) {
+            parity_sum++;
+        }
+    }
+
+    if((parity_sum % 2) != 1) {
+        return;
+    }
+
+    // leading parity (even) test
+    parity_sum = 0;
+    for(int8_t i = 13; i < 26; i++) {
+        if(((result >> i) & 1) == 1) {
+            parity_sum++;
+        }
+    }
+
+    if((parity_sum % 2) == 1) {
+        return;
+    }
+
+    ready = true;
+}
+
+void DecoderHID26::reset_state() {
+    last_pulse = false;
+    pulse_count = 0;
+    ready = false;
+    last_pulse_time = 0;
+}

+ 26 - 0
applications/lf-rfid/helpers/decoder-hid26.h

@@ -0,0 +1,26 @@
+#pragma once
+#include <stdint.h>
+#include <atomic>
+
+class DecoderHID26 {
+public:
+    bool read(uint8_t* data, uint8_t data_size);
+    void process_front(bool polarity, uint32_t time);
+    DecoderHID26();
+
+private:
+    uint32_t last_pulse_time = 0;
+    bool last_pulse;
+    uint8_t pulse_count;
+
+    uint32_t stored_data[3] = {0, 0, 0};
+    void store_data(bool data);
+    void validate_stored_data();
+
+    uint8_t facility = 0;
+    uint16_t number = 0;
+
+    std::atomic<bool> ready;
+
+    void reset_state();
+};

+ 170 - 0
applications/lf-rfid/helpers/decoder-indala.cpp

@@ -0,0 +1,170 @@
+#include "decoder-indala.h"
+#include <api-hal.h>
+
+constexpr uint32_t clocks_in_us = 64;
+
+constexpr uint32_t min_time_us = 25 * clocks_in_us;
+constexpr uint32_t mid_time_us = 45 * clocks_in_us;
+constexpr uint32_t max_time_us = 90 * clocks_in_us;
+
+bool DecoderIndala::read(uint8_t* data, uint8_t data_size) {
+    bool result = false;
+
+    if(ready) {
+        result = true;
+        printf("IND %02X %02X %02X\r\n", facility, (uint8_t)(number >> 8), (uint8_t)number);
+        ready = false;
+    }
+
+    return result;
+}
+
+void DecoderIndala::process_front(bool polarity, uint32_t time) {
+    if(ready) return;
+
+    if(polarity == false) {
+        last_pulse_time = time;
+    } else {
+        last_pulse_time += time;
+        pulse_count++;
+
+        if(last_pulse_time > min_time_us && last_pulse_time < max_time_us) {
+            if(last_pulse_time > mid_time_us) {
+                bool last_data = !(readed_data & 1);
+                pulse_count = 0;
+                readed_data = (readed_data << 1) | last_data;
+                verify();
+            } else if((pulse_count % 16) == 0) {
+                bool last_data = readed_data & 1;
+                pulse_count = 0;
+                readed_data = (readed_data << 1) | last_data;
+                verify();
+            }
+        }
+    }
+}
+
+DecoderIndala::DecoderIndala() {
+}
+
+void DecoderIndala::reset_state() {
+}
+
+void DecoderIndala::verify() {
+    // verify inverse
+    readed_data = ~readed_data;
+    verify_inner();
+
+    // verify normal
+    readed_data = ~readed_data;
+    verify_inner();
+}
+
+typedef union {
+    uint64_t raw;
+    struct __attribute__((packed)) {
+        uint8_t static0 : 3;
+        uint8_t checksum : 2;
+        uint8_t static1 : 2;
+        uint8_t y14 : 1;
+
+        uint8_t x8 : 1;
+        uint8_t x1 : 1;
+        uint8_t y13 : 1;
+        uint8_t static2 : 1;
+        uint8_t y12 : 1;
+        uint8_t x6 : 1;
+        uint8_t y5 : 1;
+        uint8_t y8 : 1;
+
+        uint8_t y15 : 1;
+        uint8_t x2 : 1;
+        uint8_t x5 : 1;
+        uint8_t x4 : 1;
+        uint8_t y9 : 1;
+        uint8_t y2 : 1;
+        uint8_t x3 : 1;
+        uint8_t y3 : 1;
+
+        uint8_t y1 : 1;
+        uint8_t y16 : 1;
+        uint8_t y4 : 1;
+        uint8_t x7 : 1;
+        uint8_t p2 : 1;
+        uint8_t y11 : 1;
+        uint8_t y6 : 1;
+        uint8_t y7 : 1;
+
+        uint8_t p1 : 1;
+        uint8_t y10 : 1;
+        uint32_t preamble : 30;
+    };
+} IndalaFormat;
+
+void DecoderIndala::verify_inner() {
+    IndalaFormat id;
+    id.raw = readed_data;
+
+    // preamble
+    //if((data >> 34) != 0b000000000000000000000000000001) return;
+    if(id.preamble != 1) return;
+
+    // static data bits
+    //if((data & 0b100001100111) != 0b101) return;
+    if(id.static2 != 0 && id.static1 != 0 && id.static0 != 0b101) return;
+
+    // Indala checksum
+    uint8_t sum_to_check = id.y2 + id.y4 + id.y7 + id.y8 + id.y10 + id.y11 + id.y14 + id.y16;
+
+    if(sum_to_check % 2 == 0) {
+        if(id.checksum != 0b10) return;
+    } else {
+        if(id.checksum != 0b01) return;
+    }
+
+    // read facility number
+    facility = (id.x1 << 7) + (id.x2 << 6) + (id.x3 << 5) + (id.x4 << 4) + (id.x5 << 3) +
+               (id.x6 << 2) + (id.x7 << 1) + (id.x8 << 0);
+
+    // read serial number
+    number = (id.y1 << 15) + (id.y2 << 14) + (id.y3 << 13) + (id.y4 << 12) + (id.y5 << 11) +
+             (id.y6 << 10) + (id.y7 << 9) + (id.y8 << 8) + (id.y9 << 7) + (id.y10 << 6) +
+             (id.y11 << 5) + (id.y12 << 4) + (id.y13 << 3) + (id.y14 << 2) + (id.y15 << 1) +
+             (id.y16 << 0);
+
+    // Wiegand checksum left
+    sum_to_check = 0;
+    for(int8_t i = 0; i < 8; i--) {
+        if((facility >> i) & 1) {
+            sum_to_check += 1;
+        }
+    }
+
+    for(int8_t i = 0; i < 4; i--) {
+        if((number >> i) & 1) {
+            sum_to_check += 1;
+        }
+    }
+
+    if(id.p1) {
+        sum_to_check += 1;
+    }
+
+    if((sum_to_check % 2) == 1) return;
+
+    // Wiegand checksum right
+    sum_to_check = 0;
+    for(int8_t i = 0; i < 12; i--) {
+        if((number >> (i + 4)) & 1) {
+            sum_to_check += 1;
+        }
+    }
+
+    if(id.p2) {
+        sum_to_check += 1;
+    }
+
+    if((sum_to_check % 2) != 1) return;
+
+    ready = true;
+}

+ 28 - 0
applications/lf-rfid/helpers/decoder-indala.h

@@ -0,0 +1,28 @@
+#pragma once
+#include <stdint.h>
+#include <limits.h>
+#include <atomic>
+
+class DecoderIndala {
+public:
+    bool read(uint8_t* data, uint8_t data_size);
+    void process_front(bool polarity, uint32_t time);
+
+    DecoderIndala();
+
+private:
+    void reset_state();
+
+    void verify();
+    void verify_inner();
+
+    uint32_t last_pulse_time = 0;
+    uint32_t pulse_count = 0;
+    uint32_t overall_pulse_count = 0;
+
+    uint64_t readed_data = 0;
+
+    std::atomic<bool> ready;
+    uint8_t facility = 0;
+    uint16_t number = 0;
+};

+ 15 - 0
applications/lf-rfid/helpers/emmarine.h

@@ -0,0 +1,15 @@
+#pragma once
+#include <stdint.h>
+
+#define EM_HEADER_POS 55
+#define EM_HEADER_MASK (0x1FFLLU << EM_HEADER_POS)
+
+#define EM_FIRST_ROW_POS 50
+#define EM_ROW_COUNT 10
+
+#define EM_COLUMN_POS 4
+#define EM_STOP_POS 0
+#define EM_STOP_MASK (0x1LLU << EM_STOP_POS)
+
+#define EM_HEADER_AND_STOP_MASK (EM_HEADER_MASK | EM_STOP_MASK)
+#define EM_HEADER_AND_STOP_DATA (EM_HEADER_MASK)

+ 58 - 0
applications/lf-rfid/helpers/encoder-emmarine.cpp

@@ -0,0 +1,58 @@
+#include "encoder-emmarine.h"
+#include <furi.h>
+
+void EncoderEM::init(const uint8_t* data, const uint8_t data_size) {
+    furi_check(data_size == 5);
+
+    // header
+    card_data = 0b111111111;
+
+    // data
+    for(uint8_t i = 0; i < 5; i++) {
+        write_nibble(false, data[i]);
+        write_nibble(true, data[i]);
+    }
+
+    // column parity and stop bit
+    uint8_t parity_sum;
+
+    for(uint8_t c = 0; c < 4; c++) {
+        parity_sum = 0;
+        for(uint8_t i = 1; i <= 10; i++) {
+            uint8_t parity_bit = (card_data >> (i * 5 - 1)) & 1;
+            parity_sum += parity_bit;
+        }
+        card_data = (card_data << 1) | ((parity_sum % 2) & 1);
+    }
+
+    // stop bit
+    card_data = (card_data << 1) | 0;
+    card_data_index = 0;
+}
+
+void EncoderEM::write_nibble(bool low_nibble, uint8_t data) {
+    uint8_t parity_sum = 0;
+    uint8_t start = 0;
+    if(!low_nibble) start = 4;
+
+    for(int8_t i = (start + 3); i >= start; i--) {
+        parity_sum += (data >> i) & 1;
+        card_data = (card_data << 1) | ((data >> i) & 1);
+    }
+
+    card_data = (card_data << 1) | ((parity_sum % 2) & 1);
+}
+
+// data transmitted as manchester encoding
+// 0 - high2low
+// 1 - low2high
+void EncoderEM::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) {
+    *period = clocks_per_bit;
+    *pulse = clocks_per_bit / 2;
+    *polarity = (card_data >> (63 - card_data_index)) & 1;
+
+    card_data_index++;
+    if(card_data_index >= 64) {
+        card_data_index = 0;
+    }
+}

+ 23 - 0
applications/lf-rfid/helpers/encoder-emmarine.h

@@ -0,0 +1,23 @@
+#pragma once
+#include "encoder-generic.h"
+
+class EncoderEM : public EncoderGeneric {
+public:
+    /**
+     * @brief init data to emulate
+     * 
+     * @param data 1 byte FC, next 4 byte SN
+     * @param data_size must be 5
+     */
+    void init(const uint8_t* data, const uint8_t data_size) final;
+
+    void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final;
+
+private:
+    // clock pulses per bit
+    static const uint8_t clocks_per_bit = 64;
+
+    uint64_t card_data;
+    uint8_t card_data_index;
+    void write_nibble(bool low_nibble, uint8_t data);
+};

+ 27 - 0
applications/lf-rfid/helpers/encoder-generic.h

@@ -0,0 +1,27 @@
+#pragma once
+#include <stdbool.h>
+#include <stdint.h>
+
+class EncoderGeneric {
+public:
+    /**
+     * @brief init encoder
+     * 
+     * @param data data array
+     * @param data_size data array size
+     */
+    virtual void init(const uint8_t* data, const uint8_t data_size) = 0;
+
+    /**
+     * @brief Get the next timer pulse
+     * 
+     * @param polarity pulse polarity true = high2low, false = low2high
+     * @param period overall period time in timer clicks
+     * @param pulse pulse time in timer clicks
+     */
+    virtual void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) = 0;
+
+    virtual ~EncoderGeneric(){};
+
+private:
+};

+ 13 - 0
applications/lf-rfid/helpers/encoder-hid.cpp

@@ -0,0 +1,13 @@
+#include "encoder-hid.h"
+#include <furi.h>
+
+void EncoderHID::init(const uint8_t* data, const uint8_t data_size) {
+    card_data = 0b1010000000000000000000000000000010011101111110011001001001010010;
+    card_data_index = 0;
+}
+
+void EncoderHID::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) {
+    *period = 100;
+    *pulse = 50;
+    *polarity = true;
+}

+ 19 - 0
applications/lf-rfid/helpers/encoder-hid.h

@@ -0,0 +1,19 @@
+#pragma once
+#include "encoder-generic.h"
+
+class EncoderHID : public EncoderGeneric {
+public:
+    /**
+     * @brief init data to emulate
+     * 
+     * @param data 1 byte FC, next 2 byte SN
+     * @param data_size must be 3
+     */
+    void init(const uint8_t* data, const uint8_t data_size) final;
+
+    void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final;
+
+private:
+    uint64_t card_data;
+    uint8_t card_data_index;
+};

+ 27 - 0
applications/lf-rfid/helpers/encoder-indala.cpp

@@ -0,0 +1,27 @@
+#include "encoder-indala.h"
+#include <furi.h>
+
+void EncoderIndala::init(const uint8_t* data, const uint8_t data_size) {
+    card_data = 0b1010000000000000000000000000000010011101111110011001001001010010;
+    last_polarity = card_data & 1;
+    card_data_index = 0;
+}
+
+void EncoderIndala::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) {
+    bool new_bit = (card_data >> (63 - card_data_index)) & 1;
+
+    *period = 2;
+    *pulse = 1;
+    *polarity = (new_bit != last_polarity);
+
+    bit_clock_index++;
+    if(bit_clock_index >= clock_per_bit) {
+        bit_clock_index = 0;
+        last_polarity = *polarity;
+
+        card_data_index++;
+        if(card_data_index >= 64) {
+            card_data_index = 0;
+        }
+    }
+}

+ 22 - 0
applications/lf-rfid/helpers/encoder-indala.h

@@ -0,0 +1,22 @@
+#pragma once
+#include "encoder-generic.h"
+
+class EncoderIndala : public EncoderGeneric {
+public:
+    /**
+     * @brief init data to emulate
+     * 
+     * @param data indala raw data
+     * @param data_size must be 5
+     */
+    void init(const uint8_t* data, const uint8_t data_size) final;
+
+    void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final;
+
+private:
+    uint64_t card_data;
+    uint8_t card_data_index;
+    uint8_t bit_clock_index;
+    bool last_polarity;
+    static const uint8_t clock_per_bit = 16;
+};

+ 9 - 0
applications/lf-rfid/helpers/key-info.h

@@ -0,0 +1,9 @@
+#pragma once
+#include <stdint.h>
+
+static const uint8_t LFRFID_KEY_SIZE = 8;
+
+enum class LfrfidKeyType : uint8_t {
+    KeyEmarine,
+    KeyHID,
+};

+ 34 - 0
applications/lf-rfid/helpers/manchester-decoder.c

@@ -0,0 +1,34 @@
+#include "manchester-decoder.h"
+#include <stdint.h>
+
+static const uint8_t transitions[] = {0b00000001, 0b10010001, 0b10011011, 0b11111011};
+static const ManchesterState manchester_reset_state = ManchesterStateMid1;
+
+bool manchester_advance(
+    ManchesterState state,
+    ManchesterEvent event,
+    ManchesterState* next_state,
+    bool* data) {
+    bool result = false;
+    ManchesterState new_state;
+
+    if(event == ManchesterEventReset) {
+        new_state = manchester_reset_state;
+    } else {
+        new_state = transitions[state] >> event & 0x3;
+        if(new_state == state) {
+            new_state = manchester_reset_state;
+        } else {
+            if(new_state == ManchesterStateMid0) {
+                *data = false;
+                result = true;
+            } else if(new_state == ManchesterStateMid1) {
+                *data = true;
+                result = true;
+            }
+        }
+    }
+
+    *next_state = new_state;
+    return result;
+}

+ 31 - 0
applications/lf-rfid/helpers/manchester-decoder.h

@@ -0,0 +1,31 @@
+#pragma once
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    ManchesterEventShortLow = 0,
+    ManchesterEventShortHigh = 2,
+    ManchesterEventLongLow = 4,
+    ManchesterEventLongHigh = 6,
+    ManchesterEventReset = 8
+} ManchesterEvent;
+
+typedef enum {
+    ManchesterStateStart1 = 0,
+    ManchesterStateMid1 = 1,
+    ManchesterStateMid0 = 2,
+    ManchesterStateStart0 = 3
+} ManchesterState;
+
+bool manchester_advance(
+    ManchesterState state,
+    ManchesterEvent event,
+    ManchesterState* next_state,
+    bool* data);
+
+#ifdef __cplusplus
+}
+#endif

+ 95 - 0
applications/lf-rfid/helpers/pulse-joiner.cpp

@@ -0,0 +1,95 @@
+#include "pulse-joiner.h"
+#include <furi.h>
+
+bool PulseJoiner::push_pulse(bool polarity, uint16_t period, uint16_t pulse) {
+    bool result = false;
+    furi_check((pulse_index + 1) < pulse_max);
+
+    if(polarity == false && pulse_index == 0) {
+        // first negative pulse is ommited
+
+    } else {
+        pulses[pulse_index].polarity = polarity;
+        pulses[pulse_index].time = pulse;
+        pulse_index++;
+    }
+
+    if(period > pulse) {
+        pulses[pulse_index].polarity = !polarity;
+        pulses[pulse_index].time = period - pulse;
+        pulse_index++;
+    }
+
+    if(pulse_index >= 4) {
+        // we know that first pulse is always high
+        // so we wait 2 edges, hi2low and next low2hi
+
+        uint8_t edges_count = 0;
+        bool last_polarity = pulses[0].polarity;
+
+        for(uint8_t i = 1; i < pulse_index; i++) {
+            if(pulses[i].polarity != last_polarity) {
+                edges_count++;
+                last_polarity = pulses[i].polarity;
+            }
+        }
+
+        if(edges_count >= 2) {
+            result = true;
+        }
+    }
+
+    return result;
+}
+
+void PulseJoiner::pop_pulse(uint16_t* period, uint16_t* pulse) {
+    furi_check(pulse_index <= (pulse_max + 1));
+
+    uint16_t tmp_period = 0;
+    uint16_t tmp_pulse = 0;
+    uint8_t edges_count = 0;
+    bool last_polarity = pulses[0].polarity;
+    uint8_t next_fist_pulse = 0;
+
+    for(uint8_t i = 0; i < pulse_max; i++) {
+        // count edges
+        if(pulses[i].polarity != last_polarity) {
+            edges_count++;
+            last_polarity = pulses[i].polarity;
+        }
+
+        // wait for 2 edges
+        if(edges_count == 2) {
+            next_fist_pulse = i;
+            break;
+        }
+
+        // sum pulse time
+        if(pulses[i].polarity) {
+            tmp_period += pulses[i].time;
+            tmp_pulse += pulses[i].time;
+        } else {
+            tmp_period += pulses[i].time;
+        }
+        pulse_index--;
+    }
+
+    *period = tmp_period;
+    *pulse = tmp_pulse;
+
+    // remove counted periods and shift data
+    for(uint8_t i = 0; i < pulse_max; i++) {
+        if((next_fist_pulse + i) < pulse_max) {
+            pulses[i].polarity = pulses[next_fist_pulse + i].polarity;
+            pulses[i].time = pulses[next_fist_pulse + i].time;
+        } else {
+            break;
+        }
+    }
+}
+
+PulseJoiner::PulseJoiner() {
+    for(uint8_t i = 0; i < pulse_max; i++) {
+        pulses[i] = {false, 0};
+    }
+}

+ 36 - 0
applications/lf-rfid/helpers/pulse-joiner.h

@@ -0,0 +1,36 @@
+#pragma once
+#include "stdint.h"
+
+class PulseJoiner {
+public:
+    /**
+     * @brief Push timer pulse. First negative pulse is ommited.
+     * 
+     * @param polarity pulse polarity: true = high2low, false = low2high
+     * @param period overall period time in timer clicks
+     * @param pulse pulse time in timer clicks
+     * 
+     * @return true - next pulse can and must be popped immediatly
+     */
+    bool push_pulse(bool polarity, uint16_t period, uint16_t pulse);
+
+    /**
+     * @brief Get the next timer pulse. Call only if push_pulse returns true.
+     * 
+     * @param period overall period time in timer clicks
+     * @param pulse pulse time in timer clicks
+     */
+    void pop_pulse(uint16_t* period, uint16_t* pulse);
+
+    PulseJoiner();
+
+private:
+    struct Pulse {
+        bool polarity;
+        uint16_t time;
+    };
+
+    uint8_t pulse_index = 0;
+    static const uint8_t pulse_max = 6;
+    Pulse pulses[pulse_max];
+};

+ 134 - 0
applications/lf-rfid/helpers/rfid-reader.cpp

@@ -0,0 +1,134 @@
+#include "rfid-reader.h"
+#include <furi.h>
+#include <api-hal.h>
+#include <stm32wbxx_ll_cortex.h>
+#include <tim.h>
+
+extern COMP_HandleTypeDef hcomp1;
+
+/**
+ * @brief private violation assistant for RfidReader
+ */
+struct RfidReaderAccessor {
+    static void decode(RfidReader& rfid_reader, bool polarity) {
+        rfid_reader.decode(polarity);
+    }
+};
+
+void RfidReader::decode(bool polarity) {
+    uint32_t current_dwt_value = DWT->CYCCNT;
+
+    switch(type) {
+    case Type::Normal:
+        decoder_em.process_front(polarity, current_dwt_value - last_dwt_value);
+        decoder_hid26.process_front(polarity, current_dwt_value - last_dwt_value);
+        //decoder_indala.process_front(polarity, current_dwt_value - last_dwt_value);
+        //decoder_analyzer.process_front(polarity, current_dwt_value - last_dwt_value);
+
+        last_dwt_value = current_dwt_value;
+        break;
+    case Type::Indala:
+        break;
+    }
+}
+
+static void comparator_trigger_callback(void* hcomp, void* comp_ctx) {
+    COMP_HandleTypeDef* _hcomp = static_cast<COMP_HandleTypeDef*>(hcomp);
+    RfidReader* _this = static_cast<RfidReader*>(comp_ctx);
+
+    if(hcomp == &hcomp1) {
+        RfidReaderAccessor::decode(
+            *_this, (HAL_COMP_GetOutputLevel(_hcomp) == COMP_OUTPUT_LEVEL_HIGH));
+    }
+}
+
+RfidReader::RfidReader() {
+}
+
+void RfidReader::start(Type _type) {
+    type = _type;
+
+    start_gpio();
+    switch(type) {
+    case Type::Normal:
+        start_timer();
+        break;
+    case Type::Indala:
+        start_timer_indala();
+        break;
+    }
+
+    start_comparator();
+}
+
+void RfidReader::stop() {
+    stop_gpio();
+    stop_timer();
+    stop_comparator();
+}
+
+bool RfidReader::read(LfrfidKeyType* type, uint8_t* data, uint8_t data_size) {
+    bool result = false;
+
+    if(decoder_em.read(data, data_size)) {
+        *type = LfrfidKeyType::KeyEmarine;
+        result = true;
+    }
+
+    if(decoder_hid26.read(data, data_size)) {
+        *type = LfrfidKeyType::KeyHID;
+        result = true;
+    }
+
+    //decoder_indala.read(NULL, 0);
+    //decoder_analyzer.read(NULL, 0);
+
+    return result;
+}
+
+void RfidReader::start_comparator(void) {
+    api_interrupt_add(comparator_trigger_callback, InterruptTypeComparatorTrigger, this);
+    last_dwt_value = DWT->CYCCNT;
+
+    hcomp1.Init.InputMinus = COMP_INPUT_MINUS_1_2VREFINT;
+    hcomp1.Init.InputPlus = COMP_INPUT_PLUS_IO1;
+    hcomp1.Init.OutputPol = COMP_OUTPUTPOL_NONINVERTED;
+    hcomp1.Init.Hysteresis = COMP_HYSTERESIS_LOW;
+    hcomp1.Init.BlankingSrce = COMP_BLANKINGSRC_NONE;
+    hcomp1.Init.Mode = COMP_POWERMODE_MEDIUMSPEED;
+    hcomp1.Init.WindowMode = COMP_WINDOWMODE_DISABLE;
+    hcomp1.Init.TriggerMode = COMP_TRIGGERMODE_IT_RISING_FALLING;
+    if(HAL_COMP_Init(&hcomp1) != HAL_OK) {
+        Error_Handler();
+    }
+
+    HAL_COMP_Start(&hcomp1);
+}
+
+void RfidReader::start_timer(void) {
+    api_hal_rfid_tim_read(125000, 0.5);
+    api_hal_rfid_tim_read_start();
+}
+
+void RfidReader::start_timer_indala(void) {
+    api_hal_rfid_tim_read(62500, 0.25);
+    api_hal_rfid_tim_read_start();
+}
+
+void RfidReader::start_gpio(void) {
+    api_hal_rfid_pins_read();
+}
+
+void RfidReader::stop_comparator(void) {
+    HAL_COMP_Stop(&hcomp1);
+    api_interrupt_remove(comparator_trigger_callback, InterruptTypeComparatorTrigger);
+}
+
+void RfidReader::stop_timer(void) {
+    api_hal_rfid_tim_read_stop();
+    api_hal_rfid_tim_reset();
+}
+
+void RfidReader::stop_gpio(void) {
+    api_hal_rfid_pins_reset();
+}

+ 41 - 0
applications/lf-rfid/helpers/rfid-reader.h

@@ -0,0 +1,41 @@
+#pragma once
+#include "decoder-analyzer.h"
+#include "decoder-emmarine.h"
+#include "decoder-hid26.h"
+#include "decoder-indala.h"
+#include "key-info.h"
+
+class RfidReader {
+public:
+    enum class Type : uint8_t {
+        Normal,
+        Indala,
+    };
+
+    RfidReader();
+    void start(Type type);
+    void stop();
+    bool read(LfrfidKeyType* type, uint8_t* data, uint8_t data_size);
+
+private:
+    friend struct RfidReaderAccessor;
+
+    //DecoderAnalyzer decoder_analyzer;
+    DecoderEMMarine decoder_em;
+    DecoderHID26 decoder_hid26;
+    DecoderIndala decoder_indala;
+
+    uint32_t last_dwt_value;
+
+    void start_comparator(void);
+    void start_timer(void);
+    void start_timer_indala(void);
+    void start_gpio(void);
+    void stop_comparator(void);
+    void stop_timer(void);
+    void stop_gpio(void);
+
+    void decode(bool polarity);
+
+    Type type = Type::Normal;
+};

+ 404 - 0
applications/lf-rfid/helpers/rfid-timer-emulator.cpp

@@ -0,0 +1,404 @@
+#include "rfid-timer-emulator.h"
+
+extern TIM_HandleTypeDef htim1;
+/*
+static uint16_t times_index = 0;
+
+constexpr uint16_t hid_237_34672_count = 528;
+constexpr uint8_t hid_237_34672[hid_237_34672_count] = {
+    8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 10,
+    10, 10, 10, 10, 10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,
+    8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,
+    10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10,
+    10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 8,  8,  8,  8,  8,
+    8,  8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,
+    8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,
+    8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,
+    8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10,
+    10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 8,  8,  8,  8,
+    8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,
+    10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  8,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10,
+    10, 8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 8,
+    8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  10, 10,
+    10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10,
+    10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10,
+    10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  8,  8,  8,
+    8,  8,  8,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10,
+    8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  10,
+    10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10,
+    10, 10, 8,  8,  8,  8,  8,  8,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 8,  8,  8,  8,  8,  8,
+};
+
+static void callback_hid(void* _hw, void* ctx) {
+    //RfidTimerEmulator* _this = static_cast<RfidTimerEmulator*>(ctx);
+    TIM_HandleTypeDef* hw = static_cast<TIM_HandleTypeDef*>(_hw);
+
+    if(hw == &htim1) {
+        hw->Instance->ARR = hid_237_34672[times_index] - 1;
+        hw->Instance->CCR1 = hid_237_34672[times_index] / 2; // - 1
+
+        times_index++;
+        if(times_index >= hid_237_34672_count) {
+            times_index = 0;
+        }
+    }
+}
+
+typedef struct {
+    uint8_t arr;
+    uint8_t ccr;
+} TimerTick;
+
+constexpr TimerTick indala_data[] = {
+    {.arr = 3, .ccr = 2}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 2},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 2}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 2}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 3, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 3, .ccr = 2}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 2},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 2}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 2}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 3, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 3, .ccr = 2}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 2},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 3, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1}, {.arr = 2, .ccr = 1},
+    {.arr = 2, .ccr = 1},
+};
+
+constexpr uint16_t indala_size = sizeof(indala_data) / sizeof(TimerTick);
+
+static void callback_indala(void* _hw, void* ctx) {
+    //RfidTimerEmulator* _this = static_cast<RfidTimerEmulator*>(ctx);
+    TIM_HandleTypeDef* hw = static_cast<TIM_HandleTypeDef*>(_hw);
+
+    if(hw == &htim1) {
+        hw->Instance->ARR = indala_data[times_index].arr - 1;
+        hw->Instance->CCR1 = indala_data[times_index].ccr;
+
+        times_index++;
+        if(times_index >= indala_size) {
+            times_index = 0;
+        }
+    }
+}
+*/
+
+RfidTimerEmulator::RfidTimerEmulator() {
+}
+
+RfidTimerEmulator::~RfidTimerEmulator() {
+    std::map<Type, EncoderGeneric*>::iterator it;
+
+    for(it = encoders.begin(); it != encoders.end(); ++it) {
+        delete it->second;
+        encoders.erase(it);
+    }
+}
+
+void RfidTimerEmulator::start(Type type) {
+    if(encoders.count(type)) {
+        current_encoder = encoders.find(type)->second;
+        uint8_t em_data[5] = {0x53, 0x00, 0x5F, 0xB3, 0xC2};
+
+        switch(type) {
+        case Type::EM:
+            current_encoder->init(em_data, 5);
+            break;
+        case Type::HID:
+            current_encoder->init(nullptr, 3);
+            break;
+        case Type::Indala:
+            current_encoder->init(nullptr, 5);
+            break;
+        }
+
+        api_hal_rfid_tim_emulate(125000);
+        api_hal_rfid_pins_emulate();
+
+        api_interrupt_add(timer_update_callback, InterruptTypeTimerUpdate, this);
+
+        for(size_t i = WWDG_IRQn; i <= DMAMUX1_OVR_IRQn; i++) {
+            HAL_NVIC_SetPriority(static_cast<IRQn_Type>(i), 15, 0);
+        }
+
+        HAL_NVIC_SetPriority(TIM1_UP_TIM16_IRQn, 5, 0);
+        HAL_NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn);
+
+        api_hal_rfid_tim_emulate_start();
+    } else {
+        // not found
+    }
+}
+
+void RfidTimerEmulator::stop() {
+    api_hal_rfid_tim_emulate_stop();
+
+    api_interrupt_remove(timer_update_callback, InterruptTypeTimerUpdate);
+}
+
+void RfidTimerEmulator::emulate() {
+}
+
+void RfidTimerEmulator::timer_update_callback(void* _hw, void* ctx) {
+    RfidTimerEmulator* _this = static_cast<RfidTimerEmulator*>(ctx);
+    TIM_HandleTypeDef* hw = static_cast<TIM_HandleTypeDef*>(_hw);
+
+    if(hw == &LFRFID_TIM) {
+        bool result;
+        bool polarity;
+        uint16_t period;
+        uint16_t pulse;
+
+        do {
+            _this->current_encoder->get_next(&polarity, &period, &pulse);
+            result = _this->pulse_joiner.push_pulse(polarity, period, pulse);
+        } while(result == false);
+
+        _this->pulse_joiner.pop_pulse(&period, &pulse);
+
+        hw->Instance->ARR = period - 1;
+        hw->Instance->CCR1 = pulse;
+    }
+}

+ 36 - 0
applications/lf-rfid/helpers/rfid-timer-emulator.h

@@ -0,0 +1,36 @@
+#pragma once
+#include <api-hal.h>
+#include "key-info.h"
+#include "encoder-generic.h"
+#include "encoder-emmarine.h"
+#include "encoder-hid.h"
+#include "encoder-indala.h"
+#include "pulse-joiner.h"
+#include <map>
+
+class RfidTimerEmulator {
+public:
+    enum class Type : uint8_t {
+        EM,
+        HID,
+        Indala,
+    };
+
+    RfidTimerEmulator();
+    ~RfidTimerEmulator();
+    void start(Type type);
+    void stop();
+    void emulate();
+
+private:
+    EncoderGeneric* current_encoder = nullptr;
+
+    std::map<Type, EncoderGeneric*> encoders = {
+        {Type::EM, new EncoderEM()},
+        {Type::HID, new EncoderHID()},
+        {Type::Indala, new EncoderIndala()},
+    };
+
+    PulseJoiner pulse_joiner;
+    static void timer_update_callback(void* _hw, void* ctx);
+};

+ 146 - 0
applications/lf-rfid/lf-rfid-app.cpp

@@ -0,0 +1,146 @@
+#include "lf-rfid-app.h"
+#include <furi.h>
+#include <api-hal.h>
+#include <stdarg.h>
+
+void LfrfidApp::run(void) {
+    LfrfidEvent event;
+    bool consumed;
+    bool exit = false;
+
+    scenes[current_scene]->on_enter(this);
+
+    while(!exit) {
+        view.receive_event(&event);
+
+        consumed = scenes[current_scene]->on_event(this, &event);
+
+        if(!consumed) {
+            if(event.type == LfrfidEvent::Type::Back) {
+                exit = switch_to_previous_scene();
+            }
+        }
+    };
+
+    scenes[current_scene]->on_exit(this);
+}
+
+LfrfidApp::LfrfidApp() {
+    api_hal_power_insomnia_enter();
+}
+
+LfrfidApp::~LfrfidApp() {
+    for(std::map<Scene, LfrfidScene*>::iterator it = scenes.begin(); it != scenes.end(); ++it) {
+        delete it->second;
+        scenes.erase(it);
+    }
+
+    api_hal_power_insomnia_exit();
+}
+
+LfrfidAppViewManager* LfrfidApp::get_view_manager() {
+    return &view;
+}
+
+void LfrfidApp::switch_to_next_scene(Scene next_scene) {
+    previous_scenes_list.push_front(current_scene);
+
+    if(next_scene != Scene::Exit) {
+        scenes[current_scene]->on_exit(this);
+        current_scene = next_scene;
+        scenes[current_scene]->on_enter(this);
+    }
+}
+
+void LfrfidApp::search_and_switch_to_previous_scene(std::initializer_list<Scene> scenes_list) {
+    Scene previous_scene = Scene::Start;
+    bool scene_found = false;
+
+    while(!scene_found) {
+        previous_scene = get_previous_scene();
+        for(Scene element : scenes_list) {
+            if(previous_scene == element || previous_scene == Scene::Start) {
+                scene_found = true;
+                break;
+            }
+        }
+    }
+
+    scenes[current_scene]->on_exit(this);
+    current_scene = previous_scene;
+    scenes[current_scene]->on_enter(this);
+}
+
+bool LfrfidApp::switch_to_previous_scene(uint8_t count) {
+    Scene previous_scene = Scene::Start;
+
+    for(uint8_t i = 0; i < count; i++) {
+        previous_scene = get_previous_scene();
+        if(previous_scene == Scene::Exit) break;
+    }
+
+    if(previous_scene == Scene::Exit) {
+        return true;
+    } else {
+        scenes[current_scene]->on_exit(this);
+        current_scene = previous_scene;
+        scenes[current_scene]->on_enter(this);
+        return false;
+    }
+}
+
+LfrfidApp::Scene LfrfidApp::get_previous_scene() {
+    Scene scene = previous_scenes_list.front();
+    previous_scenes_list.pop_front();
+    return scene;
+}
+
+/***************************** NOTIFY *******************************/
+
+void LfrfidApp::notify_init() {
+    // TODO open record
+    const GpioPin* vibro_record = &vibro_gpio;
+    hal_gpio_init(vibro_record, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
+    hal_gpio_write(vibro_record, false);
+}
+
+void LfrfidApp::notify_green_blink() {
+    api_hal_light_set(LightGreen, 0xFF);
+    delay(10);
+    api_hal_light_set(LightGreen, 0x00);
+}
+
+void LfrfidApp::notify_green_on() {
+    api_hal_light_set(LightGreen, 0xFF);
+}
+
+void LfrfidApp::notify_green_off() {
+    api_hal_light_set(LightGreen, 0x00);
+}
+
+/*************************** TEXT STORE *****************************/
+
+char* LfrfidApp::get_text_store() {
+    return text_store;
+}
+
+uint8_t LfrfidApp::get_text_store_size() {
+    return text_store_size;
+}
+
+void LfrfidApp::set_text_store(const char* text...) {
+    va_list args;
+    va_start(args, text);
+
+    vsnprintf(text_store, text_store_size, text, args);
+
+    va_end(args);
+}
+
+RfidReader* LfrfidApp::get_reader() {
+    return &reader;
+}
+
+RfidTimerEmulator* LfrfidApp::get_emulator() {
+    return &emulator;
+}

+ 73 - 0
applications/lf-rfid/lf-rfid-app.h

@@ -0,0 +1,73 @@
+#pragma once
+#include <map>
+#include <list>
+#include "lf-rfid-view-manager.h"
+
+#include "scene/lf-rfid-scene-start.h"
+#include "scene/lf-rfid-scene-emulate-indala.h"
+#include "scene/lf-rfid-scene-emulate-hid.h"
+#include "scene/lf-rfid-scene-emulate-emmarine.h"
+#include "scene/lf-rfid-scene-read-normal.h"
+#include "scene/lf-rfid-scene-read-indala.h"
+#include "scene/lf-rfid-scene-tune.h"
+
+#include "helpers/rfid-reader.h"
+#include "helpers/rfid-timer-emulator.h"
+
+class LfrfidApp {
+public:
+    void run(void);
+
+    LfrfidApp();
+    ~LfrfidApp();
+
+    enum class Scene : uint8_t {
+        Exit,
+        Start,
+        ReadNormal,
+        ReadIndala,
+        EmulateIndala,
+        EmulateHID,
+        EmulateEM,
+        Tune,
+    };
+
+    LfrfidAppViewManager* get_view_manager();
+    void switch_to_next_scene(Scene index);
+    void search_and_switch_to_previous_scene(std::initializer_list<Scene> scenes_list);
+    bool switch_to_previous_scene(uint8_t count = 1);
+    Scene get_previous_scene();
+
+    void notify_init();
+    void notify_green_blink();
+    void notify_green_on();
+    void notify_green_off();
+
+    char* get_text_store();
+    uint8_t get_text_store_size();
+    void set_text_store(const char* text...);
+
+    RfidReader* get_reader();
+    RfidTimerEmulator* get_emulator();
+
+private:
+    std::list<Scene> previous_scenes_list = {Scene::Exit};
+    Scene current_scene = Scene::Start;
+    LfrfidAppViewManager view;
+
+    std::map<Scene, LfrfidScene*> scenes = {
+        {Scene::Start, new LfrfidSceneStart()},
+        {Scene::ReadNormal, new LfrfidSceneReadNormal()},
+        {Scene::ReadIndala, new LfrfidSceneReadIndala()},
+        {Scene::EmulateIndala, new LfrfidSceneEmulateIndala()},
+        {Scene::EmulateHID, new LfrfidSceneEmulateHID()},
+        {Scene::EmulateEM, new LfrfidSceneEmulateEMMarine()},
+        {Scene::Tune, new LfrfidSceneTune()},
+    };
+
+    static const uint8_t text_store_size = 128;
+    char text_store[text_store_size + 1];
+
+    RfidReader reader;
+    RfidTimerEmulator emulator;
+};

+ 21 - 0
applications/lf-rfid/lf-rfid-event.h

@@ -0,0 +1,21 @@
+#pragma once
+#include <stdint.h>
+
+class LfrfidEvent {
+public:
+    // events enum
+    enum class Type : uint8_t {
+        Tick,
+        Back,
+        MenuSelected,
+        NextScene,
+    };
+
+    // payload
+    union {
+        uint32_t menu_index;
+    } payload;
+
+    // event type
+    Type type;
+};

+ 90 - 0
applications/lf-rfid/lf-rfid-view-manager.cpp

@@ -0,0 +1,90 @@
+#include "lf-rfid-view-manager.h"
+#include "lf-rfid-event.h"
+#include <callback-connector.h>
+
+LfrfidAppViewManager::LfrfidAppViewManager() {
+    event_queue = osMessageQueueNew(10, sizeof(LfrfidEvent), NULL);
+
+    view_dispatcher = view_dispatcher_alloc();
+    auto callback = cbc::obtain_connector(this, &LfrfidAppViewManager::previous_view_callback);
+
+    // allocate views
+    submenu = submenu_alloc();
+    add_view(ViewType::Submenu, submenu_get_view(submenu));
+
+    popup = popup_alloc();
+    add_view(ViewType::Popup, popup_get_view(popup));
+
+    tune = new LfRfidViewTune();
+    add_view(ViewType::Tune, tune->get_view());
+
+    gui = static_cast<Gui*>(furi_record_open("gui"));
+    view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen);
+
+    // set previous view callback for all views
+    view_set_previous_callback(submenu_get_view(submenu), callback);
+    view_set_previous_callback(popup_get_view(popup), callback);
+    view_set_previous_callback(tune->get_view(), callback);
+}
+
+LfrfidAppViewManager::~LfrfidAppViewManager() {
+    // remove views
+    view_dispatcher_remove_view(
+        view_dispatcher, static_cast<uint32_t>(LfrfidAppViewManager::ViewType::Submenu));
+    view_dispatcher_remove_view(
+        view_dispatcher, static_cast<uint32_t>(LfrfidAppViewManager::ViewType::Popup));
+    view_dispatcher_remove_view(
+        view_dispatcher, static_cast<uint32_t>(LfrfidAppViewManager::ViewType::Tune));
+
+    // free view modules
+    submenu_free(submenu);
+    popup_free(popup);
+    delete tune;
+
+    // free dispatcher
+    view_dispatcher_free(view_dispatcher);
+
+    // free event queue
+    osMessageQueueDelete(event_queue);
+}
+
+void LfrfidAppViewManager::switch_to(ViewType type) {
+    view_dispatcher_switch_to_view(view_dispatcher, static_cast<uint32_t>(type));
+}
+
+Submenu* LfrfidAppViewManager::get_submenu() {
+    return submenu;
+}
+
+Popup* LfrfidAppViewManager::get_popup() {
+    return popup;
+}
+
+LfRfidViewTune* LfrfidAppViewManager::get_tune() {
+    return tune;
+}
+
+void LfrfidAppViewManager::receive_event(LfrfidEvent* event) {
+    if(osMessageQueueGet(event_queue, event, NULL, 100) != osOK) {
+        event->type = LfrfidEvent::Type::Tick;
+    }
+}
+
+void LfrfidAppViewManager::send_event(LfrfidEvent* event) {
+    osStatus_t result = osMessageQueuePut(event_queue, event, 0, 0);
+    furi_check(result == osOK);
+}
+
+uint32_t LfrfidAppViewManager::previous_view_callback(void* context) {
+    if(event_queue != NULL) {
+        LfrfidEvent event;
+        event.type = LfrfidEvent::Type::Back;
+        send_event(&event);
+    }
+
+    return VIEW_IGNORE;
+}
+
+void LfrfidAppViewManager::add_view(ViewType view_type, View* view) {
+    view_dispatcher_add_view(view_dispatcher, static_cast<uint32_t>(view_type), view);
+}

+ 42 - 0
applications/lf-rfid/lf-rfid-view-manager.h

@@ -0,0 +1,42 @@
+#pragma once
+#include <furi.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/popup.h>
+#include "lf-rfid-event.h"
+#include "view/lf-rfid-view-tune.h"
+
+class LfrfidAppViewManager {
+public:
+    enum class ViewType : uint8_t {
+        Submenu,
+        Popup,
+        Tune,
+    };
+
+    osMessageQueueId_t event_queue;
+
+    LfrfidAppViewManager();
+    ~LfrfidAppViewManager();
+
+    void switch_to(ViewType type);
+
+    void receive_event(LfrfidEvent* event);
+    void send_event(LfrfidEvent* event);
+
+    Submenu* get_submenu();
+    Popup* get_popup();
+    LfRfidViewTune* get_tune();
+
+private:
+    ViewDispatcher* view_dispatcher;
+    Gui* gui;
+
+    uint32_t previous_view_callback(void* context);
+    void add_view(ViewType view_type, View* view);
+
+    // view elements
+    Submenu* submenu;
+    Popup* popup;
+    LfRfidViewTune* tune;
+};

+ 0 - 421
applications/lf-rfid/lf-rfid.c

@@ -1,421 +0,0 @@
-#include <furi.h>
-#include <api-hal.h>
-#include <gui/gui.h>
-#include <stream_buffer.h>
-
-typedef enum { EventTypeTick, EventTypeKey, EventTypeRx } EventType;
-
-typedef struct {
-    uint8_t dummy;
-} RxEvent;
-
-typedef struct {
-    union {
-        InputEvent input;
-        RxEvent rx;
-    } value;
-    EventType type;
-} AppEvent;
-
-typedef struct {
-    uint32_t freq_khz;
-    bool on;
-    uint8_t customer_id;
-    uint32_t em_data;
-    bool dirty;
-    bool dirty_freq;
-} State;
-
-static void render_callback(Canvas* canvas, void* ctx) {
-    State* state = (State*)acquire_mutex((ValueMutex*)ctx, 25);
-
-    canvas_clear(canvas);
-
-    canvas_set_color(canvas, ColorBlack);
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str(canvas, 2, 12, "LF RFID");
-
-    canvas_draw_str(canvas, 2, 24, state->on ? "Reading" : "Emulating");
-
-    char buf[30];
-
-    snprintf(buf, sizeof(buf), "%d kHz", (int)state->freq_khz);
-    canvas_draw_str(canvas, 2, 36, buf);
-
-    snprintf(buf, sizeof(buf), "%02d:%010ld", state->customer_id, state->em_data);
-    canvas_draw_str(canvas, 2, 45, buf);
-
-    release_mutex((ValueMutex*)ctx, state);
-}
-
-static void input_callback(InputEvent* input_event, void* ctx) {
-    osMessageQueueId_t event_queue = ctx;
-
-    if(input_event->type != InputTypeShort) return;
-
-    AppEvent event;
-    event.type = EventTypeKey;
-    event.value.input = *input_event;
-    osMessageQueuePut(event_queue, &event, 1, osWaitForever);
-}
-
-extern TIM_HandleTypeDef TIM_C;
-void em4100_emulation(uint8_t* data, GpioPin* pin);
-void prepare_data(uint32_t ID, uint32_t VENDOR, uint8_t* data);
-
-GpioPin debug_0 = {.pin = GPIO_PIN_2, .port = GPIOB};
-GpioPin debug_1 = {.pin = GPIO_PIN_3, .port = GPIOC};
-
-extern COMP_HandleTypeDef hcomp1;
-
-typedef struct {
-    osMessageQueueId_t event_queue;
-    uint32_t prev_dwt;
-    int8_t symbol;
-    bool center;
-    size_t symbol_cnt;
-    StreamBufferHandle_t stream_buffer;
-    uint8_t* int_buffer;
-} ComparatorCtx;
-
-void init_comp_ctx(ComparatorCtx* ctx) {
-    ctx->prev_dwt = 0;
-    ctx->symbol = -1; // init state
-    ctx->center = false;
-    ctx->symbol_cnt = 0;
-    xStreamBufferReset(ctx->stream_buffer);
-
-    for(size_t i = 0; i < 64; i++) {
-        ctx->int_buffer[i] = 0;
-    }
-}
-
-void comparator_trigger_callback(void* hcomp, void* comp_ctx) {
-    ComparatorCtx* ctx = (ComparatorCtx*)comp_ctx;
-
-    uint32_t dt = (DWT->CYCCNT - ctx->prev_dwt) / (SystemCoreClock / 1000000.0f);
-    ctx->prev_dwt = DWT->CYCCNT;
-
-    if(dt < 150) return; // supress noise
-
-    // wait message will be consumed
-    if(xStreamBufferBytesAvailable(ctx->stream_buffer) == 64) return;
-
-    hal_gpio_write(&debug_0, true);
-
-    // TOOD F4 and F5 differ
-    bool rx_value = hal_gpio_get_rfid_in_level();
-
-    if(dt > 384) {
-        // change symbol 0->1 or 1->0
-        ctx->symbol = rx_value;
-        ctx->center = true;
-    } else {
-        // same symbol as prev or center
-        ctx->center = !ctx->center;
-    }
-
-    /*
-    hal_gpio_write(&debug_1, true);
-    delay_us(center ? 10 : 30);
-    hal_gpio_write(&debug_1, false);
-    */
-
-    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
-
-    if(ctx->center && ctx->symbol != -1) {
-        /*
-        hal_gpio_write(&debug_0, true);
-        delay_us(symbol ? 10 : 30);
-        hal_gpio_write(&debug_0, false);
-        */
-
-        ctx->int_buffer[ctx->symbol_cnt] = ctx->symbol;
-        ctx->symbol_cnt++;
-    }
-
-    // check preamble
-    if(ctx->symbol_cnt <= 9 && ctx->symbol == 0) {
-        ctx->symbol_cnt = 0;
-        ctx->symbol = -1;
-    }
-
-    // check stop bit
-    if(ctx->symbol_cnt == 64 && ctx->symbol == 1) {
-        ctx->symbol_cnt = 0;
-        ctx->symbol = -1;
-    }
-
-    // TODO
-    // write only 9..64 symbols directly to streambuffer
-
-    if(ctx->symbol_cnt == 64) {
-        if(xStreamBufferSendFromISR(
-               ctx->stream_buffer, ctx->int_buffer, 64, &xHigherPriorityTaskWoken) == 64) {
-            AppEvent event;
-            event.type = EventTypeRx;
-            osMessageQueuePut(ctx->event_queue, &event, 0, 0);
-        }
-
-        ctx->symbol_cnt = 0;
-    }
-
-    hal_gpio_write(&debug_0, false);
-
-    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
-}
-
-const uint8_t ROW_SIZE = 4;
-const uint8_t LINE_SIZE = 10;
-
-static bool even_check(uint8_t* buf) {
-    uint8_t col_parity_sum[ROW_SIZE];
-    for(uint8_t col = 0; col < ROW_SIZE; col++) {
-        col_parity_sum[col] = 0;
-    }
-
-    // line parity
-    for(uint8_t line = 0; line < LINE_SIZE; line++) {
-        printf("%d: ", line);
-        uint8_t parity_sum = 0;
-        for(uint8_t col = 0; col < ROW_SIZE; col++) {
-            parity_sum += buf[line * (ROW_SIZE + 1) + col];
-            col_parity_sum[col] += buf[line * (ROW_SIZE + 1) + col];
-            printf("%d ", buf[line * (ROW_SIZE + 1) + col]);
-        }
-        if((1 & parity_sum) != buf[line * (ROW_SIZE + 1) + ROW_SIZE]) {
-            printf(
-                "line parity fail at %d (%d : %d)\n",
-                line,
-                parity_sum,
-                buf[line * (ROW_SIZE + 1) + ROW_SIZE]);
-            return false;
-        }
-        printf("\r\n");
-    }
-
-    for(uint8_t col = 0; col < ROW_SIZE; col++) {
-        if((1 & col_parity_sum[col]) != buf[LINE_SIZE * (ROW_SIZE + 1) + col]) {
-            printf(
-                "col parity fail at %d (%d : %d)\n",
-                col,
-                col_parity_sum[col],
-                buf[LINE_SIZE * (ROW_SIZE + 1) + col]);
-            return false;
-        }
-    }
-
-    return true;
-}
-
-static void extract_data(uint8_t* buf, uint8_t* customer, uint32_t* em_data) {
-    uint32_t data = 0;
-    uint8_t offset = 0;
-
-    printf("customer: ");
-    for(uint8_t line = 0; line < 2; line++) {
-        for(uint8_t col = 0; col < ROW_SIZE; col++) {
-            uint32_t bit = buf[line * (ROW_SIZE + 1) + col];
-
-            data |= bit << (7 - offset);
-            printf("%ld ", bit);
-
-            offset++;
-        }
-    }
-    printf("\r\n");
-
-    *customer = data;
-
-    data = 0;
-    offset = 0;
-    printf("data: ");
-    for(uint8_t line = 2; line < LINE_SIZE; line++) {
-        for(uint8_t col = 0; col < ROW_SIZE; col++) {
-            uint32_t bit = buf[line * (ROW_SIZE + 1) + col];
-
-            data |= bit << (31 - offset);
-            printf("%ld ", bit);
-
-            offset++;
-        }
-    }
-    printf("\r\n");
-
-    *em_data = data;
-}
-
-int32_t lf_rfid_workaround(void* p) {
-    osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(AppEvent), NULL);
-
-    // create pin
-    GpioPin pull_pin = {.pin = RFID_PULL_Pin, .port = RFID_PULL_GPIO_Port};
-    // TODO open record
-    GpioPin* pull_pin_record = &pull_pin;
-
-    hal_gpio_init(pull_pin_record, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
-
-    hal_gpio_init(&debug_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
-    hal_gpio_init(&debug_1, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
-
-    // pulldown iBtn pin to prevent interference from ibutton
-    hal_gpio_init((GpioPin*)&ibutton_gpio, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
-    hal_gpio_write((GpioPin*)&ibutton_gpio, false);
-
-    // init ctx
-    ComparatorCtx comp_ctx;
-
-    // internal buffer
-    uint8_t int_bufer[64];
-
-    comp_ctx.stream_buffer = xStreamBufferCreate(64, 64);
-    comp_ctx.int_buffer = int_bufer;
-    comp_ctx.event_queue = event_queue;
-    init_comp_ctx(&comp_ctx);
-
-    if(comp_ctx.stream_buffer == NULL) {
-        printf("cannot create stream buffer\r\n");
-        return 255;
-    }
-
-    // start comp
-    HAL_COMP_Start(&hcomp1);
-
-    uint8_t raw_data[64];
-    for(size_t i = 0; i < 64; i++) {
-        raw_data[i] = 0;
-    }
-
-    State _state;
-    _state.freq_khz = 125;
-    _state.on = false;
-    _state.customer_id = 00;
-    _state.em_data = 4378151;
-    _state.dirty = true;
-    _state.dirty_freq = true;
-
-    ValueMutex state_mutex;
-    if(!init_mutex(&state_mutex, &_state, sizeof(State))) {
-        printf("cannot create mutex\r\n");
-        return 255;
-    }
-
-    ViewPort* view_port = view_port_alloc();
-
-    view_port_draw_callback_set(view_port, render_callback, &state_mutex);
-    view_port_input_callback_set(view_port, input_callback, event_queue);
-
-    // Open GUI and register view_port
-    Gui* gui = furi_record_open("gui");
-    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
-
-    AppEvent event;
-
-    while(1) {
-        osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 1024 / 8);
-
-        if(event.type == EventTypeRx && event_status == osOK) {
-            size_t received = xStreamBufferReceive(comp_ctx.stream_buffer, raw_data, 64, 0);
-            printf("received: %d\r\n", received);
-            if(received == 64) {
-                if(even_check(&raw_data[9])) {
-                    State* state = (State*)acquire_mutex_block(&state_mutex);
-                    extract_data(&raw_data[9], &state->customer_id, &state->em_data);
-
-                    printf("customer: %02d, data: %010lu\n", state->customer_id, state->em_data);
-
-                    release_mutex(&state_mutex, state);
-
-                    view_port_update(view_port);
-
-                    api_hal_light_set(LightGreen, 0xFF);
-                    osDelay(50);
-                    api_hal_light_set(LightGreen, 0x00);
-                }
-            }
-        } else {
-            State* state = (State*)acquire_mutex_block(&state_mutex);
-
-            if(event_status == osOK) {
-                if(event.type == EventTypeKey) {
-                    // press events
-                    if(event.value.input.key == InputKeyBack) {
-                        break;
-                    }
-
-                    if(event.value.input.key == InputKeyUp) {
-                        state->dirty_freq = true;
-                        state->freq_khz += 10;
-                    }
-
-                    if(event.value.input.key == InputKeyDown) {
-                        state->dirty_freq = true;
-                        state->freq_khz -= 10;
-                    }
-
-                    if(event.value.input.key == InputKeyLeft) {
-                    }
-
-                    if(event.value.input.key == InputKeyRight) {
-                    }
-
-                    if(event.value.input.key == InputKeyOk) {
-                        state->dirty = true;
-                        state->on = !state->on;
-                    }
-                }
-            } else {
-                // event timeout
-            }
-
-            if(state->dirty) {
-                if(state->on) {
-                    hal_gpio_write(pull_pin_record, false);
-                    init_comp_ctx(&comp_ctx);
-                    api_interrupt_add(
-                        comparator_trigger_callback, InterruptTypeComparatorTrigger, &comp_ctx);
-                } else {
-                    prepare_data(state->em_data, state->customer_id, raw_data);
-                    api_interrupt_remove(
-                        comparator_trigger_callback, InterruptTypeComparatorTrigger);
-                }
-
-                state->dirty_freq = true; // config new PWM next
-
-                state->dirty = false;
-            }
-
-            if(state->dirty_freq) {
-                hal_pwmn_set(
-                    state->on ? 0.5 : 0.0, (float)(state->freq_khz * 1000), &LFRFID_TIM, LFRFID_CH);
-
-                state->dirty_freq = false;
-            }
-
-            if(!state->on) {
-                em4100_emulation(raw_data, pull_pin_record);
-            }
-            release_mutex(&state_mutex, state);
-            view_port_update(view_port);
-        }
-    }
-
-    hal_pwmn_stop(&TIM_C, TIM_CHANNEL_1); // TODO: move to furiac_onexit
-    api_interrupt_remove(comparator_trigger_callback, InterruptTypeComparatorTrigger);
-
-    hal_gpio_init(pull_pin_record, GpioModeInput, GpioPullNo, GpioSpeedLow);
-    hal_gpio_init((GpioPin*)&ibutton_gpio, GpioModeInput, GpioPullNo, GpioSpeedLow);
-
-    // TODO remove all view_ports create by app
-    view_port_enabled_set(view_port, false);
-    gui_remove_view_port(gui, view_port);
-    view_port_free(view_port);
-
-    HAL_COMP_Stop(&hcomp1);
-
-    vStreamBufferDelete(comp_ctx.stream_buffer);
-
-    osMessageQueueDelete(event_queue);
-
-    return 0;
-}

+ 10 - 0
applications/lf-rfid/lf-rfid.cpp

@@ -0,0 +1,10 @@
+#include "lf-rfid-app.h"
+
+// app enter function
+extern "C" int32_t app_lfrfid(void* p) {
+    LfrfidApp* app = new LfrfidApp();
+    app->run();
+    delete app;
+
+    return 255;
+}

+ 34 - 0
applications/lf-rfid/scene/lf-rfid-scene-emulate-emmarine.cpp

@@ -0,0 +1,34 @@
+#include "lf-rfid-scene-emulate-emmarine.h"
+
+#include "../lf-rfid-app.h"
+#include "../lf-rfid-view-manager.h"
+#include "../lf-rfid-event.h"
+#include "../helpers/key-info.h"
+
+void LfrfidSceneEmulateEMMarine::on_enter(LfrfidApp* app) {
+    LfrfidAppViewManager* view_manager = app->get_view_manager();
+
+    Popup* popup = view_manager->get_popup();
+    popup_set_header(popup, "LF-RFID", 64, 16, AlignCenter, AlignBottom);
+    app->set_text_store("EM emulation");
+    popup_set_text(popup, app->get_text_store(), 64, 22, AlignCenter, AlignTop);
+
+    view_manager->switch_to(LfrfidAppViewManager::ViewType::Popup);
+    app->get_emulator()->start(RfidTimerEmulator::Type::EM);
+}
+
+bool LfrfidSceneEmulateEMMarine::on_event(LfrfidApp* app, LfrfidEvent* event) {
+    bool consumed = false;
+
+    return consumed;
+}
+
+void LfrfidSceneEmulateEMMarine::on_exit(LfrfidApp* app) {
+    LfrfidAppViewManager* view_manager = app->get_view_manager();
+
+    Popup* popup = view_manager->get_popup();
+    popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
+    popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
+
+    app->get_emulator()->stop();
+}

+ 12 - 0
applications/lf-rfid/scene/lf-rfid-scene-emulate-emmarine.h

@@ -0,0 +1,12 @@
+#pragma once
+#include "lf-rfid-scene-generic.h"
+#include "../helpers/key-info.h"
+
+class LfrfidSceneEmulateEMMarine : public LfrfidScene {
+public:
+    void on_enter(LfrfidApp* app) final;
+    bool on_event(LfrfidApp* app, LfrfidEvent* event) final;
+    void on_exit(LfrfidApp* app) final;
+
+private:
+};

+ 34 - 0
applications/lf-rfid/scene/lf-rfid-scene-emulate-hid.cpp

@@ -0,0 +1,34 @@
+#include "lf-rfid-scene-emulate-hid.h"
+
+#include "../lf-rfid-app.h"
+#include "../lf-rfid-view-manager.h"
+#include "../lf-rfid-event.h"
+#include "../helpers/key-info.h"
+
+void LfrfidSceneEmulateHID::on_enter(LfrfidApp* app) {
+    LfrfidAppViewManager* view_manager = app->get_view_manager();
+
+    Popup* popup = view_manager->get_popup();
+    popup_set_header(popup, "LF-RFID", 64, 16, AlignCenter, AlignBottom);
+    app->set_text_store("HID emulation");
+    popup_set_text(popup, app->get_text_store(), 64, 22, AlignCenter, AlignTop);
+
+    view_manager->switch_to(LfrfidAppViewManager::ViewType::Popup);
+    app->get_emulator()->start(RfidTimerEmulator::Type::HID);
+}
+
+bool LfrfidSceneEmulateHID::on_event(LfrfidApp* app, LfrfidEvent* event) {
+    bool consumed = false;
+
+    return consumed;
+}
+
+void LfrfidSceneEmulateHID::on_exit(LfrfidApp* app) {
+    LfrfidAppViewManager* view_manager = app->get_view_manager();
+
+    Popup* popup = view_manager->get_popup();
+    popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
+    popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
+
+    app->get_emulator()->stop();
+}

+ 12 - 0
applications/lf-rfid/scene/lf-rfid-scene-emulate-hid.h

@@ -0,0 +1,12 @@
+#pragma once
+#include "lf-rfid-scene-generic.h"
+#include "../helpers/key-info.h"
+
+class LfrfidSceneEmulateHID : public LfrfidScene {
+public:
+    void on_enter(LfrfidApp* app) final;
+    bool on_event(LfrfidApp* app, LfrfidEvent* event) final;
+    void on_exit(LfrfidApp* app) final;
+
+private:
+};

+ 34 - 0
applications/lf-rfid/scene/lf-rfid-scene-emulate-indala.cpp

@@ -0,0 +1,34 @@
+#include "lf-rfid-scene-emulate-indala.h"
+
+#include "../lf-rfid-app.h"
+#include "../lf-rfid-view-manager.h"
+#include "../lf-rfid-event.h"
+#include "../helpers/key-info.h"
+
+void LfrfidSceneEmulateIndala::on_enter(LfrfidApp* app) {
+    LfrfidAppViewManager* view_manager = app->get_view_manager();
+
+    Popup* popup = view_manager->get_popup();
+    popup_set_header(popup, "LF-RFID", 64, 16, AlignCenter, AlignBottom);
+    app->set_text_store("Indala emulation");
+    popup_set_text(popup, app->get_text_store(), 64, 22, AlignCenter, AlignTop);
+
+    view_manager->switch_to(LfrfidAppViewManager::ViewType::Popup);
+    app->get_emulator()->start(RfidTimerEmulator::Type::Indala);
+}
+
+bool LfrfidSceneEmulateIndala::on_event(LfrfidApp* app, LfrfidEvent* event) {
+    bool consumed = false;
+
+    return consumed;
+}
+
+void LfrfidSceneEmulateIndala::on_exit(LfrfidApp* app) {
+    LfrfidAppViewManager* view_manager = app->get_view_manager();
+
+    Popup* popup = view_manager->get_popup();
+    popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
+    popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
+
+    app->get_emulator()->stop();
+}

+ 12 - 0
applications/lf-rfid/scene/lf-rfid-scene-emulate-indala.h

@@ -0,0 +1,12 @@
+#pragma once
+#include "lf-rfid-scene-generic.h"
+#include "../helpers/key-info.h"
+
+class LfrfidSceneEmulateIndala : public LfrfidScene {
+public:
+    void on_enter(LfrfidApp* app) final;
+    bool on_event(LfrfidApp* app, LfrfidEvent* event) final;
+    void on_exit(LfrfidApp* app) final;
+
+private:
+};

+ 14 - 0
applications/lf-rfid/scene/lf-rfid-scene-generic.h

@@ -0,0 +1,14 @@
+#pragma once
+#include "../lf-rfid-event.h"
+
+class LfrfidApp;
+
+class LfrfidScene {
+public:
+    virtual void on_enter(LfrfidApp* app) = 0;
+    virtual bool on_event(LfrfidApp* app, LfrfidEvent* event) = 0;
+    virtual void on_exit(LfrfidApp* app) = 0;
+    virtual ~LfrfidScene(){};
+
+private:
+};

+ 34 - 0
applications/lf-rfid/scene/lf-rfid-scene-read-indala.cpp

@@ -0,0 +1,34 @@
+#include "lf-rfid-scene-read-indala.h"
+
+#include "../lf-rfid-app.h"
+#include "../lf-rfid-view-manager.h"
+#include "../lf-rfid-event.h"
+#include "../helpers/key-info.h"
+
+void LfrfidSceneReadIndala::on_enter(LfrfidApp* app) {
+    LfrfidAppViewManager* view_manager = app->get_view_manager();
+
+    Popup* popup = view_manager->get_popup();
+    popup_set_header(popup, "LF-RFID read Indala", 64, 16, AlignCenter, AlignBottom);
+    app->set_text_store("[decoder not implemented]");
+    popup_set_text(popup, app->get_text_store(), 64, 22, AlignCenter, AlignTop);
+
+    view_manager->switch_to(LfrfidAppViewManager::ViewType::Popup);
+    app->get_reader()->start(RfidReader::Type::Indala);
+}
+
+bool LfrfidSceneReadIndala::on_event(LfrfidApp* app, LfrfidEvent* event) {
+    bool consumed = false;
+
+    return consumed;
+}
+
+void LfrfidSceneReadIndala::on_exit(LfrfidApp* app) {
+    LfrfidAppViewManager* view_manager = app->get_view_manager();
+
+    Popup* popup = view_manager->get_popup();
+    popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
+    popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
+
+    app->get_reader()->stop();
+}

+ 15 - 0
applications/lf-rfid/scene/lf-rfid-scene-read-indala.h

@@ -0,0 +1,15 @@
+#pragma once
+#include "lf-rfid-scene-generic.h"
+#include "../helpers/key-info.h"
+
+class LfrfidSceneReadIndala : public LfrfidScene {
+public:
+    void on_enter(LfrfidApp* app) final;
+    bool on_event(LfrfidApp* app, LfrfidEvent* event) final;
+    void on_exit(LfrfidApp* app) final;
+
+private:
+    uint32_t success_reads = 0;
+    static const uint8_t data_size = LFRFID_KEY_SIZE;
+    uint8_t last_data[data_size] = {0};
+};

+ 80 - 0
applications/lf-rfid/scene/lf-rfid-scene-read-normal.cpp

@@ -0,0 +1,80 @@
+#include "lf-rfid-scene-read-normal.h"
+
+#include "../lf-rfid-app.h"
+#include "../lf-rfid-view-manager.h"
+#include "../lf-rfid-event.h"
+#include "../helpers/key-info.h"
+
+void LfrfidSceneReadNormal::on_enter(LfrfidApp* app) {
+    LfrfidAppViewManager* view_manager = app->get_view_manager();
+
+    Popup* popup = view_manager->get_popup();
+    popup_set_header(popup, "LF-RFID read EM & HID", 64, 16, AlignCenter, AlignBottom);
+    app->set_text_store("waiting...");
+    popup_set_text(popup, app->get_text_store(), 64, 22, AlignCenter, AlignTop);
+
+    view_manager->switch_to(LfrfidAppViewManager::ViewType::Popup);
+    app->get_reader()->start(RfidReader::Type::Normal);
+}
+
+bool LfrfidSceneReadNormal::on_event(LfrfidApp* app, LfrfidEvent* event) {
+    bool consumed = false;
+
+    if(event->type == LfrfidEvent::Type::Tick) {
+        uint8_t data[data_size];
+        LfrfidKeyType type;
+
+        if(app->get_reader()->read(&type, data, data_size)) {
+            app->notify_green_blink();
+
+            if(memcmp(last_data, data, data_size) == 0) {
+                success_reads++;
+            } else {
+                success_reads = 1;
+                memcpy(last_data, data, data_size);
+            }
+
+            switch(type) {
+            case LfrfidKeyType::KeyEmarine:
+                app->set_text_store(
+                    "[EM] %02X %02X %02X %02X %02X\n"
+                    "count: %u",
+                    data[0],
+                    data[1],
+                    data[2],
+                    data[3],
+                    data[4],
+                    success_reads);
+                break;
+            case LfrfidKeyType::KeyHID:
+                app->set_text_store(
+                    "[HID26] %02X %02X %02X\n"
+                    "count: %u",
+                    data[0],
+                    data[1],
+                    data[2],
+                    success_reads);
+                break;
+            }
+            popup_set_text(
+                app->get_view_manager()->get_popup(),
+                app->get_text_store(),
+                64,
+                22,
+                AlignCenter,
+                AlignTop);
+        }
+    }
+
+    return consumed;
+}
+
+void LfrfidSceneReadNormal::on_exit(LfrfidApp* app) {
+    LfrfidAppViewManager* view_manager = app->get_view_manager();
+
+    Popup* popup = view_manager->get_popup();
+    popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
+    popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
+
+    app->get_reader()->stop();
+}

+ 15 - 0
applications/lf-rfid/scene/lf-rfid-scene-read-normal.h

@@ -0,0 +1,15 @@
+#pragma once
+#include "lf-rfid-scene-generic.h"
+#include "../helpers/key-info.h"
+
+class LfrfidSceneReadNormal : public LfrfidScene {
+public:
+    void on_enter(LfrfidApp* app) final;
+    bool on_event(LfrfidApp* app, LfrfidEvent* event) final;
+    void on_exit(LfrfidApp* app) final;
+
+private:
+    uint32_t success_reads = 0;
+    static const uint8_t data_size = LFRFID_KEY_SIZE;
+    uint8_t last_data[data_size] = {0};
+};

+ 77 - 0
applications/lf-rfid/scene/lf-rfid-scene-start.cpp

@@ -0,0 +1,77 @@
+#include "lf-rfid-scene-start.h"
+#include "../lf-rfid-app.h"
+#include "../lf-rfid-view-manager.h"
+#include "../lf-rfid-event.h"
+#include <callback-connector.h>
+
+typedef enum {
+    SubmenuIndexReadNormal,
+    SubmenuIndexReadIndala,
+    SubmenuIndexEmulateEM,
+    SubmenuIndexEmulateHID,
+    SubmenuIndexEmulateIndala,
+    SubmenuIndexTune
+} SubmenuIndex;
+
+void LfrfidSceneStart::on_enter(LfrfidApp* app) {
+    LfrfidAppViewManager* view_manager = app->get_view_manager();
+    Submenu* submenu = view_manager->get_submenu();
+    auto callback = cbc::obtain_connector(this, &LfrfidSceneStart::submenu_callback);
+
+    submenu_add_item(submenu, "Read Normal", SubmenuIndexReadNormal, callback, app);
+    submenu_add_item(submenu, "Read Indala", SubmenuIndexReadIndala, callback, app);
+    submenu_add_item(submenu, "Emulate EM", SubmenuIndexEmulateEM, callback, app);
+    submenu_add_item(submenu, "Emulate HID", SubmenuIndexEmulateHID, callback, app);
+    submenu_add_item(submenu, "Emulate Indala", SubmenuIndexEmulateIndala, callback, app);
+    submenu_add_item(submenu, "Tune", SubmenuIndexTune, callback, app);
+
+    view_manager->switch_to(LfrfidAppViewManager::ViewType::Submenu);
+}
+
+bool LfrfidSceneStart::on_event(LfrfidApp* app, LfrfidEvent* event) {
+    bool consumed = false;
+
+    if(event->type == LfrfidEvent::Type::MenuSelected) {
+        switch(event->payload.menu_index) {
+        case SubmenuIndexReadNormal:
+            app->switch_to_next_scene(LfrfidApp::Scene::ReadNormal);
+            break;
+        case SubmenuIndexReadIndala:
+            app->switch_to_next_scene(LfrfidApp::Scene::ReadIndala);
+            break;
+        case SubmenuIndexEmulateEM:
+            app->switch_to_next_scene(LfrfidApp::Scene::EmulateEM);
+            break;
+            break;
+        case SubmenuIndexEmulateHID:
+            app->switch_to_next_scene(LfrfidApp::Scene::EmulateHID);
+            break;
+        case SubmenuIndexEmulateIndala:
+            app->switch_to_next_scene(LfrfidApp::Scene::EmulateIndala);
+            break;
+        case SubmenuIndexTune:
+            app->switch_to_next_scene(LfrfidApp::Scene::Tune);
+            break;
+        }
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void LfrfidSceneStart::on_exit(LfrfidApp* app) {
+    LfrfidAppViewManager* view_manager = app->get_view_manager();
+    Submenu* submenu = view_manager->get_submenu();
+
+    submenu_clean(submenu);
+}
+
+void LfrfidSceneStart::submenu_callback(void* context, uint32_t index) {
+    LfrfidApp* app = static_cast<LfrfidApp*>(context);
+    LfrfidEvent event;
+
+    event.type = LfrfidEvent::Type::MenuSelected;
+    event.payload.menu_index = index;
+
+    app->get_view_manager()->send_event(&event);
+}

+ 13 - 0
applications/lf-rfid/scene/lf-rfid-scene-start.h

@@ -0,0 +1,13 @@
+#pragma once
+#include "lf-rfid-scene-generic.h"
+#include "../helpers/rfid-timer-emulator.h"
+
+class LfrfidSceneStart : public LfrfidScene {
+public:
+    void on_enter(LfrfidApp* app) final;
+    bool on_event(LfrfidApp* app, LfrfidEvent* event) final;
+    void on_exit(LfrfidApp* app) final;
+
+private:
+    void submenu_callback(void* context, uint32_t index);
+};

+ 35 - 0
applications/lf-rfid/scene/lf-rfid-scene-tune.cpp

@@ -0,0 +1,35 @@
+#include "lf-rfid-scene-tune.h"
+#include "../lf-rfid-app.h"
+#include "../lf-rfid-view-manager.h"
+#include "../lf-rfid-event.h"
+#include <callback-connector.h>
+
+void LfrfidSceneTune::on_enter(LfrfidApp* app) {
+    LfrfidAppViewManager* view_manager = app->get_view_manager();
+    //LfRfidViewTune* tune = view_manager->get_tune();
+
+    view_manager->switch_to(LfrfidAppViewManager::ViewType::Tune);
+
+    reader.start(RfidReader::Type::Indala);
+}
+
+bool LfrfidSceneTune::on_event(LfrfidApp* app, LfrfidEvent* event) {
+    bool consumed = false;
+
+    if(event->type == LfrfidEvent::Type::Tick) {
+        LfRfidViewTune* tune = app->get_view_manager()->get_tune();
+
+        if(tune->is_dirty()) {
+            LFRFID_TIM.Instance->ARR = tune->get_ARR();
+            LFRFID_TIM.Instance->CCR1 = tune->get_CCR();
+        }
+    }
+
+    return consumed;
+}
+
+void LfrfidSceneTune::on_exit(LfrfidApp* app) {
+    //LfRfidViewTune* tune = app->get_view_manager()->get_tune();
+
+    reader.stop();
+}

+ 13 - 0
applications/lf-rfid/scene/lf-rfid-scene-tune.h

@@ -0,0 +1,13 @@
+#pragma once
+#include "lf-rfid-scene-generic.h"
+#include "../helpers/rfid-reader.h"
+
+class LfrfidSceneTune : public LfrfidScene {
+public:
+    void on_enter(LfrfidApp* app) final;
+    bool on_event(LfrfidApp* app, LfrfidEvent* event) final;
+    void on_exit(LfrfidApp* app) final;
+
+private:
+    RfidReader reader;
+};

+ 202 - 0
applications/lf-rfid/view/lf-rfid-view-tune.cpp

@@ -0,0 +1,202 @@
+#include "lf-rfid-view-tune.h"
+#include <callback-connector.h>
+#include <gui/elements.h>
+#include <variant>
+#include <list>
+
+struct LfRfidViewTuneModel {
+    bool dirty;
+    bool fine;
+    uint32_t ARR;
+    uint32_t CCR;
+    int pos;
+};
+
+void LfRfidViewTune::view_draw_callback(Canvas* canvas, void* _model) {
+    LfRfidViewTuneModel* model = reinterpret_cast<LfRfidViewTuneModel*>(_model);
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+    if(model->fine) {
+        canvas_draw_box(
+            canvas,
+            128 - canvas_string_width(canvas, "Fine") - 4,
+            0,
+            canvas_string_width(canvas, "Fine") + 4,
+            canvas_current_font_height(canvas) + 1);
+        canvas_set_color(canvas, ColorWhite);
+    }
+    canvas_draw_str_aligned(canvas, 128 - 2, 2, AlignRight, AlignTop, "Fine");
+    canvas_set_color(canvas, ColorBlack);
+
+    constexpr uint8_t buffer_size = 128;
+    char buffer[buffer_size + 1];
+    snprintf(
+        buffer,
+        buffer_size,
+        "%sARR: %lu\n"
+        "freq = %.4f\n"
+        "%sCCR: %lu\n"
+        "duty = %.4f",
+        model->pos == 0 ? ">" : "",
+        model->ARR,
+        (float)SystemCoreClock / ((float)model->ARR + 1),
+        model->pos == 1 ? ">" : "",
+        model->CCR,
+        ((float)model->CCR + 1) / ((float)model->ARR + 1) * 100.0f);
+    elements_multiline_text_aligned(canvas, 2, 2, AlignLeft, AlignTop, buffer);
+}
+
+bool LfRfidViewTune::view_input_callback(InputEvent* event, void* context) {
+    LfRfidViewTune* _this = reinterpret_cast<LfRfidViewTune*>(context);
+    bool consumed = false;
+
+    // Process key presses only
+    if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
+        consumed = true;
+
+        switch(event->key) {
+        case InputKeyLeft:
+            _this->button_left();
+            break;
+        case InputKeyRight:
+            _this->button_right();
+            break;
+        case InputKeyUp:
+            _this->button_up();
+            break;
+        case InputKeyDown:
+            _this->button_down();
+            break;
+        case InputKeyOk:
+            _this->button_ok();
+            break;
+        default:
+            consumed = false;
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+void LfRfidViewTune::button_up() {
+    with_view_model_cpp(view, LfRfidViewTuneModel, model, {
+        if(model->pos > 0) model->pos--;
+        return true;
+    });
+}
+
+void LfRfidViewTune::button_down() {
+    with_view_model_cpp(view, LfRfidViewTuneModel, model, {
+        if(model->pos < 1) model->pos++;
+        return true;
+    });
+}
+
+void LfRfidViewTune::button_left() {
+    with_view_model_cpp(view, LfRfidViewTuneModel, model, {
+        if(model->pos == 0) {
+            if(model->fine) {
+                model->ARR -= 1;
+            } else {
+                model->ARR -= 10;
+            }
+        } else if(model->pos == 1) {
+            if(model->fine) {
+                model->CCR -= 1;
+            } else {
+                model->CCR -= 10;
+            }
+        }
+
+        model->dirty = true;
+        return true;
+    });
+}
+
+void LfRfidViewTune::button_right() {
+    with_view_model_cpp(view, LfRfidViewTuneModel, model, {
+        if(model->pos == 0) {
+            if(model->fine) {
+                model->ARR += 1;
+            } else {
+                model->ARR += 10;
+            }
+        } else if(model->pos == 1) {
+            if(model->fine) {
+                model->CCR += 1;
+            } else {
+                model->CCR += 10;
+            }
+        }
+
+        model->dirty = true;
+        return true;
+    });
+}
+
+void LfRfidViewTune::button_ok() {
+    with_view_model_cpp(view, LfRfidViewTuneModel, model, {
+        model->fine = !model->fine;
+        return true;
+    });
+}
+
+LfRfidViewTune::LfRfidViewTune() {
+    view = view_alloc();
+    view_set_context(view, this);
+    view_allocate_model(view, ViewModelTypeLocking, sizeof(LfRfidViewTuneModel));
+
+    with_view_model_cpp(view, LfRfidViewTuneModel, model, {
+        model->dirty = true;
+        model->fine = false;
+        model->ARR = 511;
+        model->CCR = 255;
+        model->pos = 0;
+        return true;
+    });
+
+    view_set_draw_callback(view, cbc::obtain_connector(this, &LfRfidViewTune::view_draw_callback));
+    view_set_input_callback(
+        view, cbc::obtain_connector(this, &LfRfidViewTune::view_input_callback));
+}
+
+LfRfidViewTune::~LfRfidViewTune() {
+    view_free(view);
+}
+
+View* LfRfidViewTune::get_view() {
+    return view;
+}
+
+bool LfRfidViewTune::is_dirty() {
+    bool result;
+    with_view_model_cpp(view, LfRfidViewTuneModel, model, {
+        result = model->dirty;
+        model->dirty = false;
+        return false;
+    });
+
+    return result;
+}
+
+uint32_t LfRfidViewTune::get_ARR() {
+    uint32_t result;
+    with_view_model_cpp(view, LfRfidViewTuneModel, model, {
+        result = model->ARR;
+        return false;
+    });
+
+    return result;
+}
+
+uint32_t LfRfidViewTune::get_CCR() {
+    uint32_t result;
+    with_view_model_cpp(view, LfRfidViewTuneModel, model, {
+        result = model->CCR;
+        return false;
+    });
+
+    return result;
+}

+ 25 - 0
applications/lf-rfid/view/lf-rfid-view-tune.h

@@ -0,0 +1,25 @@
+#pragma once
+#include <gui/view.h>
+
+class LfRfidViewTune {
+public:
+    LfRfidViewTune();
+    ~LfRfidViewTune();
+
+    View* get_view();
+
+    bool is_dirty();
+    uint32_t get_ARR();
+    uint32_t get_CCR();
+
+private:
+    View* view;
+    void view_draw_callback(Canvas* canvas, void* _model);
+    bool view_input_callback(InputEvent* event, void* context);
+
+    void button_up();
+    void button_down();
+    void button_left();
+    void button_right();
+    void button_ok();
+};

+ 20 - 0
firmware/targets/api-hal-include/api-hal-ibutton.h

@@ -0,0 +1,20 @@
+#pragma once
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void api_hal_ibutton_start();
+
+void api_hal_ibutton_stop();
+
+void api_hal_ibutton_pin_low();
+
+void api_hal_ibutton_pin_high();
+
+bool api_hal_ibutton_pin_get_level();
+
+#ifdef __cplusplus
+}
+#endif

+ 73 - 0
firmware/targets/api-hal-include/api-hal-rfid.h

@@ -0,0 +1,73 @@
+#pragma once
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief config rfid pins to reset state
+ * 
+ */
+void api_hal_rfid_pins_reset();
+
+/**
+ * @brief config rfid pins to emulate state
+ * 
+ */
+void api_hal_rfid_pins_emulate();
+
+/**
+ * @brief config rfid pins to read state
+ * 
+ */
+void api_hal_rfid_pins_read();
+
+/**
+ * @brief config rfid timer to read state
+ * 
+ * @param freq timer frequency
+ * @param duty_cycle timer duty cycle, 0.0-1.0
+ */
+void api_hal_rfid_tim_read(float freq, float duty_cycle);
+
+/**
+ * @brief start read timer
+ * 
+ */
+void api_hal_rfid_tim_read_start();
+
+/**
+ * @brief stop read timer
+ * 
+ */
+void api_hal_rfid_tim_read_stop();
+
+/**
+ * @brief config rfid timer to emulate state
+ * 
+ * @param freq timer frequency
+ */
+void api_hal_rfid_tim_emulate(float freq);
+
+/**
+ * @brief start emulation timer
+ * 
+ */
+void api_hal_rfid_tim_emulate_start();
+
+/**
+ * @brief stop emulation timer
+ * 
+ */
+void api_hal_rfid_tim_emulate_stop();
+
+/**
+ * @brief config rfid timers to reset state
+ * 
+ */
+void api_hal_rfid_tim_reset();
+
+#ifdef __cplusplus
+}
+#endif

+ 2 - 0
firmware/targets/api-hal-include/api-hal.h

@@ -23,6 +23,8 @@ template <unsigned int N> struct STOP_EXTERNING_ME {};
 #include "api-hal-flash.h"
 #include "api-hal-subghz.h"
 #include "api-hal-vibro.h"
+#include "api-hal-ibutton.h"
+#include "api-hal-rfid.h"
 
 /** Init api-hal */
 void api_hal_init();

+ 50 - 27
firmware/targets/f5/api-hal/api-hal-gpio.c

@@ -2,30 +2,34 @@
 #include <api-hal-gpio.h>
 #include <api-hal-version.h>
 
-#define GET_SYSCFG_EXTI_PORT(gpio)  (((gpio) == (GPIOA)) ? LL_SYSCFG_EXTI_PORTA :\
-                                     ((gpio) == (GPIOB)) ? LL_SYSCFG_EXTI_PORTB :\
-                                     ((gpio) == (GPIOC)) ? LL_SYSCFG_EXTI_PORTC :\
-                                     ((gpio) == (GPIOD)) ? LL_SYSCFG_EXTI_PORTD :\
-                                     ((gpio) == (GPIOE)) ? LL_SYSCFG_EXTI_PORTE : LL_SYSCFG_EXTI_PORTH)
+#define GET_SYSCFG_EXTI_PORT(gpio)                \
+    (((gpio) == (GPIOA)) ? LL_SYSCFG_EXTI_PORTA : \
+     ((gpio) == (GPIOB)) ? LL_SYSCFG_EXTI_PORTB : \
+     ((gpio) == (GPIOC)) ? LL_SYSCFG_EXTI_PORTC : \
+     ((gpio) == (GPIOD)) ? LL_SYSCFG_EXTI_PORTD : \
+     ((gpio) == (GPIOE)) ? LL_SYSCFG_EXTI_PORTE : \
+                           LL_SYSCFG_EXTI_PORTH)
 
-#define GPIO_PIN_MAP(pin, prefix)   (((pin) == (LL_GPIO_PIN_0)) ? prefix##0 :\
-                                     ((pin) == (LL_GPIO_PIN_1)) ? prefix##1 :\
-                                     ((pin) == (LL_GPIO_PIN_2)) ? prefix##2 :\
-                                     ((pin) == (LL_GPIO_PIN_3)) ? prefix##3 :\
-                                     ((pin) == (LL_GPIO_PIN_4)) ? prefix##4 :\
-                                     ((pin) == (LL_GPIO_PIN_5)) ? prefix##5 :\
-                                     ((pin) == (LL_GPIO_PIN_6)) ? prefix##6 :\
-                                     ((pin) == (LL_GPIO_PIN_7)) ? prefix##7 :\
-                                     ((pin) == (LL_GPIO_PIN_8)) ? prefix##8 :\
-                                     ((pin) == (LL_GPIO_PIN_9)) ? prefix##9 :\
-                                     ((pin) == (LL_GPIO_PIN_10)) ? prefix##10 :\
-                                     ((pin) == (LL_GPIO_PIN_11)) ? prefix##11 :\
-                                     ((pin) == (LL_GPIO_PIN_12)) ? prefix##12 :\
-                                     ((pin) == (LL_GPIO_PIN_13)) ? prefix##13 :\
-                                     ((pin) == (LL_GPIO_PIN_14)) ? prefix##14 : prefix##15)
+#define GPIO_PIN_MAP(pin, prefix)               \
+    (((pin) == (LL_GPIO_PIN_0))  ? prefix##0 :  \
+     ((pin) == (LL_GPIO_PIN_1))  ? prefix##1 :  \
+     ((pin) == (LL_GPIO_PIN_2))  ? prefix##2 :  \
+     ((pin) == (LL_GPIO_PIN_3))  ? prefix##3 :  \
+     ((pin) == (LL_GPIO_PIN_4))  ? prefix##4 :  \
+     ((pin) == (LL_GPIO_PIN_5))  ? prefix##5 :  \
+     ((pin) == (LL_GPIO_PIN_6))  ? prefix##6 :  \
+     ((pin) == (LL_GPIO_PIN_7))  ? prefix##7 :  \
+     ((pin) == (LL_GPIO_PIN_8))  ? prefix##8 :  \
+     ((pin) == (LL_GPIO_PIN_9))  ? prefix##9 :  \
+     ((pin) == (LL_GPIO_PIN_10)) ? prefix##10 : \
+     ((pin) == (LL_GPIO_PIN_11)) ? prefix##11 : \
+     ((pin) == (LL_GPIO_PIN_12)) ? prefix##12 : \
+     ((pin) == (LL_GPIO_PIN_13)) ? prefix##13 : \
+     ((pin) == (LL_GPIO_PIN_14)) ? prefix##14 : \
+                                   prefix##15)
 
-#define GET_SYSCFG_EXTI_LINE(pin)   GPIO_PIN_MAP(pin, LL_SYSCFG_EXTI_LINE)
-#define GET_EXTI_LINE(pin)          GPIO_PIN_MAP(pin, LL_EXTI_LINE_)
+#define GET_SYSCFG_EXTI_LINE(pin) GPIO_PIN_MAP(pin, LL_SYSCFG_EXTI_LINE)
+#define GET_EXTI_LINE(pin) GPIO_PIN_MAP(pin, LL_EXTI_LINE_)
 
 static volatile GpioInterrupt gpio_interrupt[GPIO_NUMBER];
 
@@ -34,7 +38,6 @@ void hal_gpio_init(
     const GpioMode mode,
     const GpioPull pull,
     const GpioSpeed speed) {
-
     uint32_t sys_exti_port = GET_SYSCFG_EXTI_PORT(gpio->port);
     uint32_t sys_exti_line = GET_SYSCFG_EXTI_LINE(gpio->pin);
     uint32_t exti_line = GET_EXTI_LINE(gpio->pin);
@@ -87,7 +90,7 @@ void hal_gpio_init(
             LL_EXTI_DisableIT_0_31(exti_line);
             LL_EXTI_DisableRisingTrig_0_31(exti_line);
             LL_EXTI_DisableFallingTrig_0_31(exti_line);
-           }
+        }
         // Set not interrupt pin modes
         if(mode == GpioModeInput) {
             LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_INPUT);
@@ -104,11 +107,31 @@ void hal_gpio_init(
     __enable_irq();
 }
 
+void hal_gpio_init_alt(
+    const GpioPin* gpio,
+    const GpioMode mode,
+    const GpioPull pull,
+    const GpioSpeed speed,
+    const GpioAltFn alt_fn) {
+    hal_gpio_init(gpio, mode, pull, speed);
+
+    __disable_irq();
+    // enable alternate mode
+    LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_ALTERNATE);
+
+    // set alternate function
+    if(gpio->pin < 8) {
+        LL_GPIO_SetAFPin_0_7(gpio->port, gpio->pin, alt_fn);
+    } else {
+        LL_GPIO_SetAFPin_8_15(gpio->port, gpio->pin, alt_fn);
+    }
+    __enable_irq();
+}
+
 static uint8_t hal_gpio_get_pin_num(const GpioPin* gpio) {
     uint8_t pin_num = 0;
     for(pin_num = 0; pin_num < GPIO_NUMBER; pin_num++) {
-        if(gpio->pin & (1 << pin_num))
-            break;
+        if(gpio->pin & (1 << pin_num)) break;
     }
     return pin_num;
 }
@@ -252,7 +275,7 @@ extern COMP_HandleTypeDef hcomp1;
 
 bool hal_gpio_get_rfid_in_level() {
     bool value = false;
-    if (api_hal_version_get_hw_version() > 7) {
+    if(api_hal_version_get_hw_version() > 7) {
         value = (HAL_COMP_GetOutputLevel(&hcomp1) == COMP_OUTPUT_LEVEL_LOW);
     } else {
         value = (HAL_COMP_GetOutputLevel(&hcomp1) == COMP_OUTPUT_LEVEL_HIGH);

+ 106 - 1
firmware/targets/f5/api-hal/api-hal-gpio.h

@@ -24,7 +24,7 @@ typedef void (*GpioExtiCallback)(void* ctx);
  */
 typedef struct {
     GpioExtiCallback callback;
-    void *context;
+    void* context;
     volatile bool ready;
 } GpioInterrupt;
 
@@ -65,6 +65,96 @@ typedef enum {
     GpioSpeedVeryHigh,
 } GpioSpeed;
 
+/**
+ * Gpio alternate functions
+ */
+typedef enum {
+    GpioAltFn0MCO = 0, /*!< MCO Alternate Function mapping */
+    GpioAltFn0LSCO = 0, /*!< LSCO Alternate Function mapping */
+    GpioAltFn0JTMS_SWDIO = 0, /*!< JTMS-SWDIO Alternate Function mapping */
+    GpioAltFn0JTCK_SWCLK = 0, /*!< JTCK-SWCLK Alternate Function mapping */
+    GpioAltFn0JTDI = 0, /*!< JTDI Alternate Function mapping */
+    GpioAltFn0RTC_OUT = 0, /*!< RCT_OUT Alternate Function mapping */
+    GpioAltFn0JTD_TRACE = 0, /*!< JTDO-TRACESWO Alternate Function mapping */
+    GpioAltFn0NJTRST = 0, /*!< NJTRST Alternate Function mapping */
+    GpioAltFn0RTC_REFIN = 0, /*!< RTC_REFIN Alternate Function mapping */
+    GpioAltFn0TRACED0 = 0, /*!< TRACED0 Alternate Function mapping */
+    GpioAltFn0TRACED1 = 0, /*!< TRACED1 Alternate Function mapping */
+    GpioAltFn0TRACED2 = 0, /*!< TRACED2 Alternate Function mapping */
+    GpioAltFn0TRACED3 = 0, /*!< TRACED3 Alternate Function mapping */
+    GpioAltFn0TRIG_INOUT = 0, /*!< TRIG_INOUT Alternate Function mapping */
+    GpioAltFn0TRACECK = 0, /*!< TRACECK Alternate Function mapping */
+    GpioAltFn0SYS = 0, /*!< System Function mapping */
+
+    GpioAltFn1TIM1 = 1, /*!< TIM1 Alternate Function mapping */
+    GpioAltFn1TIM2 = 1, /*!< TIM2 Alternate Function mapping */
+    GpioAltFn1LPTIM1 = 1, /*!< LPTIM1 Alternate Function mapping */
+
+    GpioAltFn2TIM2 = 2, /*!< TIM2 Alternate Function mapping */
+    GpioAltFn2TIM1 = 2, /*!< TIM1 Alternate Function mapping */
+
+    GpioAltFn3SAI1 = 3, /*!< SAI1_CK1 Alternate Function mapping */
+    GpioAltFn3SPI2 = 3, /*!< SPI2 Alternate Function mapping */
+    GpioAltFn3TIM1 = 3, /*!< TIM1 Alternate Function mapping */
+
+    GpioAltFn4I2C1 = 4, /*!< I2C1 Alternate Function mapping */
+    GpioAltFn4I2C3 = 4, /*!< I2C3 Alternate Function mapping */
+
+    GpioAltFn5SPI1 = 5, /*!< SPI1 Alternate Function mapping */
+    GpioAltFn5SPI2 = 5, /*!< SPI2 Alternate Function mapping */
+
+    GpioAltFn6MCO = 6, /*!< MCO Alternate Function mapping */
+    GpioAltFn6LSCO = 6, /*!< LSCO Alternate Function mapping */
+    GpioAltFn6RF_DTB0 = 6, /*!< RF_DTB0 Alternate Function mapping */
+    GpioAltFn6RF_DTB1 = 6, /*!< RF_DTB1 Alternate Function mapping */
+    GpioAltFn6RF_DTB2 = 6, /*!< RF_DTB2 Alternate Function mapping */
+    GpioAltFn6RF_DTB3 = 6, /*!< RF_DTB3 Alternate Function mapping */
+    GpioAltFn6RF_DTB4 = 6, /*!< RF_DTB4 Alternate Function mapping */
+    GpioAltFn6RF_DTB5 = 6, /*!< RF_DTB5 Alternate Function mapping */
+    GpioAltFn6RF_DTB6 = 6, /*!< RF_DTB6 Alternate Function mapping */
+    GpioAltFn6RF_DTB7 = 6, /*!< RF_DTB7 Alternate Function mapping */
+    GpioAltFn6RF_DTB8 = 6, /*!< RF_DTB8 Alternate Function mapping */
+    GpioAltFn6RF_DTB9 = 6, /*!< RF_DTB9 Alternate Function mapping */
+    GpioAltFn6RF_DTB10 = 6, /*!< RF_DTB10 Alternate Function mapping */
+    GpioAltFn6RF_DTB11 = 6, /*!< RF_DTB11 Alternate Function mapping */
+    GpioAltFn6RF_DTB12 = 6, /*!< RF_DTB12 Alternate Function mapping */
+    GpioAltFn6RF_DTB13 = 6, /*!< RF_DTB13 Alternate Function mapping */
+    GpioAltFn6RF_DTB14 = 6, /*!< RF_DTB14 Alternate Function mapping */
+    GpioAltFn6RF_DTB15 = 6, /*!< RF_DTB15 Alternate Function mapping */
+    GpioAltFn6RF_DTB16 = 6, /*!< RF_DTB16 Alternate Function mapping */
+    GpioAltFn6RF_DTB17 = 6, /*!< RF_DTB17 Alternate Function mapping */
+    GpioAltFn6RF_DTB18 = 6, /*!< RF_DTB18 Alternate Function mapping */
+    GpioAltFn6RF_MISO = 6, /*!< RF_MISO Alternate Function mapping */
+    GpioAltFn6RF_MOSI = 6, /*!< RF_MOSI Alternate Function mapping */
+    GpioAltFn6RF_SCK = 6, /*!< RF_SCK Alternate Function mapping */
+    GpioAltFn6RF_NSS = 6, /*!< RF_NSS Alternate Function mapping */
+
+    GpioAltFn7USART1 = 7, /*!< USART1 Alternate Function mapping */
+
+    GpioAltFn8LPUART1 = 8, /*!< LPUART1 Alternate Function mapping */
+    GpioAltFn8IR = 8, /*!< IR Alternate Function mapping */
+
+    GpioAltFn9TSC = 9, /*!< TSC Alternate Function mapping */
+
+    GpioAltFn10QUADSPI = 10, /*!< QUADSPI Alternate Function mapping */
+    GpioAltFn10USB = 10, /*!< USB Alternate Function mapping */
+
+    GpioAltFn11LCD = 11, /*!< LCD Alternate Function mapping */
+
+    GpioAltFn12COMP1 = 12, /*!< COMP1 Alternate Function mapping */
+    GpioAltFn12COMP2 = 12, /*!< COMP2 Alternate Function mapping */
+    GpioAltFn12TIM1 = 12, /*!< TIM1 Alternate Function mapping */
+
+    GpioAltFn13SAI1 = 13, /*!< SAI1 Alternate Function mapping */
+
+    GpioAltFn14TIM2 = 14, /*!< TIM2 Alternate Function mapping */
+    GpioAltFn14TIM16 = 14, /*!< TIM16 Alternate Function mapping */
+    GpioAltFn14TIM17 = 14, /*!< TIM17 Alternate Function mapping */
+    GpioAltFn14LPTIM2 = 14, /*!< LPTIM2 Alternate Function mapping */
+
+    GpioAltFn15EVENTOUT = 15, /*!< EVENTOUT Alternate Function mapping */
+} GpioAltFn;
+
 /**
  * Gpio structure
  */
@@ -86,6 +176,21 @@ void hal_gpio_init(
     const GpioPull pull,
     const GpioSpeed speed);
 
+/**
+ * GPIO initialization with alternative function
+ * @param gpio  GpioPin
+ * @param mode  GpioMode
+ * @param pull  GpioPull
+ * @param speed GpioSpeed
+ * @param alt_fn GpioAltFn
+ */
+void hal_gpio_init_alt(
+    const GpioPin* gpio,
+    const GpioMode mode,
+    const GpioPull pull,
+    const GpioSpeed speed,
+    const GpioAltFn alt_fn);
+
 /**
  * Add and enable interrupt
  * @param gpio GpioPin

+ 24 - 0
firmware/targets/f5/api-hal/api-hal-ibutton.c

@@ -0,0 +1,24 @@
+#include <api-hal-ibutton.h>
+#include <api-hal-resources.h>
+
+void api_hal_ibutton_start() {
+    api_hal_ibutton_pin_high();
+    hal_gpio_init(&ibutton_gpio, GpioModeOutputOpenDrain, GpioSpeedLow, GpioPullNo);
+}
+
+void api_hal_ibutton_stop() {
+    api_hal_ibutton_pin_high();
+    hal_gpio_init(&ibutton_gpio, GpioModeAnalog, GpioSpeedLow, GpioPullNo);
+}
+
+void api_hal_ibutton_pin_low() {
+    hal_gpio_write(&ibutton_gpio, false);
+}
+
+void api_hal_ibutton_pin_high() {
+    hal_gpio_write(&ibutton_gpio, true);
+}
+
+bool api_hal_ibutton_pin_get_level() {
+    return hal_gpio_read(&ibutton_gpio);
+}

+ 4 - 4
firmware/targets/f5/api-hal/api-hal-pwm.c

@@ -2,7 +2,7 @@
 
 void hal_pwm_set(float value, float freq, TIM_HandleTypeDef* tim, uint32_t channel) {
     tim->Init.CounterMode = TIM_COUNTERMODE_UP;
-    tim->Init.Period = (uint32_t)((SystemCoreClock / (tim->Init.Prescaler + 1)) / freq);
+    tim->Init.Period = (uint32_t)((SystemCoreClock / (tim->Init.Prescaler + 1)) / freq) - 1;
     tim->Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
     tim->Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
     HAL_TIM_PWM_Init(tim);
@@ -22,7 +22,7 @@ void hal_pwm_set(float value, float freq, TIM_HandleTypeDef* tim, uint32_t chann
 
 void hal_pwmn_set(float value, float freq, TIM_HandleTypeDef* tim, uint32_t channel) {
     tim->Init.CounterMode = TIM_COUNTERMODE_UP;
-    tim->Init.Period = (uint32_t)((SystemCoreClock / (tim->Init.Prescaler + 1)) / freq - 1);
+    tim->Init.Period = (uint32_t)((SystemCoreClock / (tim->Init.Prescaler + 1)) / freq) - 1;
     tim->Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
     tim->Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
     HAL_TIM_PWM_Init(tim);
@@ -48,10 +48,10 @@ void hal_pwmn_stop(TIM_HandleTypeDef* tim, uint32_t channel) {
     HAL_TIMEx_PWMN_Stop(tim, channel);
 }
 
-void irda_pwm_set(float value, float freq){
+void irda_pwm_set(float value, float freq) {
     hal_pwmn_set(value, freq, &IRDA_TX_TIM, IRDA_TX_CH);
 }
 
-void irda_pwm_stop(){
+void irda_pwm_stop() {
     hal_pwmn_stop(&IRDA_TX_TIM, IRDA_TX_CH);
 }

+ 30 - 36
firmware/targets/f5/api-hal/api-hal-resources.c

@@ -4,51 +4,45 @@
 
 const InputPin input_pins[] = {
     {.port = BUTTON_UP_GPIO_Port, .pin = BUTTON_UP_Pin, .key = InputKeyUp, .inverted = true},
-    {.port = BUTTON_DOWN_GPIO_Port,
-     .pin = BUTTON_DOWN_Pin,
-     .key = InputKeyDown,
-     .inverted = true},
+    {.port = BUTTON_DOWN_GPIO_Port, .pin = BUTTON_DOWN_Pin, .key = InputKeyDown, .inverted = true},
     {.port = BUTTON_RIGHT_GPIO_Port,
      .pin = BUTTON_RIGHT_Pin,
      .key = InputKeyRight,
      .inverted = true},
-    {.port = BUTTON_LEFT_GPIO_Port,
-     .pin = BUTTON_LEFT_Pin,
-     .key = InputKeyLeft,
-     .inverted = true},
+    {.port = BUTTON_LEFT_GPIO_Port, .pin = BUTTON_LEFT_Pin, .key = InputKeyLeft, .inverted = true},
     {.port = BUTTON_OK_GPIO_Port, .pin = BUTTON_OK_Pin, .key = InputKeyOk, .inverted = false},
-    {.port = BUTTON_BACK_GPIO_Port,
-     .pin = BUTTON_BACK_Pin,
-     .key = InputKeyBack,
-     .inverted = true},
+    {.port = BUTTON_BACK_GPIO_Port, .pin = BUTTON_BACK_Pin, .key = InputKeyBack, .inverted = true},
 };
 
 const size_t input_pins_count = sizeof(input_pins) / sizeof(InputPin);
 
-const GpioPin vibro_gpio = {VIBRO_GPIO_Port, VIBRO_Pin};
-const GpioPin ibutton_gpio = {iBTN_GPIO_Port, iBTN_Pin};
-const GpioPin cc1101_g0_gpio = {CC1101_G0_GPIO_Port, CC1101_G0_Pin};
+const GpioPin vibro_gpio = {.port = VIBRO_GPIO_Port, .pin = VIBRO_Pin};
+const GpioPin ibutton_gpio = {.port = iBTN_GPIO_Port, .pin = iBTN_Pin};
+const GpioPin cc1101_g0_gpio = {.port = CC1101_G0_GPIO_Port, .pin = CC1101_G0_Pin};
+
+const GpioPin gpio_subghz_cs = {.port = CC1101_CS_GPIO_Port, .pin = CC1101_CS_Pin};
+const GpioPin gpio_display_cs = {.port = DISPLAY_CS_GPIO_Port, .pin = DISPLAY_CS_Pin};
+const GpioPin gpio_display_rst = {.port = DISPLAY_RST_GPIO_Port, .pin = DISPLAY_RST_Pin};
+const GpioPin gpio_display_di = {.port = DISPLAY_DI_GPIO_Port, .pin = DISPLAY_DI_Pin};
+const GpioPin gpio_sdcard_cs = {.port = SD_CS_GPIO_Port, .pin = SD_CS_Pin};
+const GpioPin gpio_nfc_cs = {.port = NFC_CS_GPIO_Port, .pin = NFC_CS_Pin};
 
-const GpioPin gpio_subghz_cs = { .port=CC1101_CS_GPIO_Port, .pin=CC1101_CS_Pin };
-const GpioPin gpio_display_cs = { .port=DISPLAY_CS_GPIO_Port, .pin=DISPLAY_CS_Pin };
-const GpioPin gpio_display_rst = { .port=DISPLAY_RST_GPIO_Port, .pin=DISPLAY_RST_Pin };
-const GpioPin gpio_display_di = { .port=DISPLAY_DI_GPIO_Port, .pin=DISPLAY_DI_Pin };
-const GpioPin gpio_sdcard_cs = { .port=SD_CS_GPIO_Port, .pin=SD_CS_Pin };
-const GpioPin gpio_nfc_cs = { .port=NFC_CS_GPIO_Port, .pin=NFC_CS_Pin };
+const GpioPin gpio_spi_d_miso = {.port = SPI_D_MISO_GPIO_Port, .pin = SPI_D_MISO_Pin};
+const GpioPin gpio_spi_d_mosi = {.port = SPI_D_MOSI_GPIO_Port, .pin = SPI_D_MOSI_Pin};
+const GpioPin gpio_spi_d_sck = {.port = SPI_D_SCK_GPIO_Port, .pin = SPI_D_SCK_Pin};
+const GpioPin gpio_spi_r_miso = {.port = SPI_R_MISO_GPIO_Port, .pin = SPI_R_MISO_Pin};
+const GpioPin gpio_spi_r_mosi = {.port = SPI_R_MOSI_GPIO_Port, .pin = SPI_R_MOSI_Pin};
+const GpioPin gpio_spi_r_sck = {.port = SPI_R_SCK_GPIO_Port, .pin = SPI_R_SCK_Pin};
 
-const GpioPin gpio_spi_d_miso = { .port=SPI_D_MISO_GPIO_Port, .pin=SPI_D_MISO_Pin };
-const GpioPin gpio_spi_d_mosi = { .port=SPI_D_MOSI_GPIO_Port, .pin=SPI_D_MOSI_Pin };
-const GpioPin gpio_spi_d_sck = { .port=SPI_D_SCK_GPIO_Port, .pin=SPI_D_SCK_Pin };
-const GpioPin gpio_spi_r_miso = { .port=SPI_R_MISO_GPIO_Port, .pin=SPI_R_MISO_Pin };
-const GpioPin gpio_spi_r_mosi = { .port=SPI_R_MOSI_GPIO_Port, .pin=SPI_R_MOSI_Pin };
-const GpioPin gpio_spi_r_sck = { .port=SPI_R_SCK_GPIO_Port, .pin=SPI_R_SCK_Pin };
+const GpioPin gpio_ext_pc0 = {.port = GPIOC, .pin = GPIO_PIN_0};
+const GpioPin gpio_ext_pc1 = {.port = GPIOC, .pin = GPIO_PIN_1};
+const GpioPin gpio_ext_pc3 = {.port = GPIOC, .pin = GPIO_PIN_3};
+const GpioPin gpio_ext_pb2 = {.port = GPIOB, .pin = GPIO_PIN_2};
+const GpioPin gpio_ext_pb3 = {.port = GPIOB, .pin = GPIO_PIN_3};
+const GpioPin gpio_ext_pa4 = {.port = GPIOA, .pin = GPIO_PIN_4};
+const GpioPin gpio_ext_pa6 = {.port = GPIOA, .pin = GPIO_PIN_6};
+const GpioPin gpio_ext_pa7 = {.port = GPIOA, .pin = GPIO_PIN_7};
 
-// external gpio's
-const GpioPin ext_pc0_gpio = {.port = GPIOC, .pin = GPIO_PIN_0};
-const GpioPin ext_pc1_gpio = {.port = GPIOC, .pin = GPIO_PIN_1};
-const GpioPin ext_pc3_gpio = {.port = GPIOC, .pin = GPIO_PIN_3};
-const GpioPin ext_pb2_gpio = {.port = GPIOB, .pin = GPIO_PIN_2};
-const GpioPin ext_pb3_gpio = {.port = GPIOB, .pin = GPIO_PIN_3};
-const GpioPin ext_pa4_gpio = {.port = GPIOA, .pin = GPIO_PIN_4};
-const GpioPin ext_pa6_gpio = {.port = GPIOA, .pin = GPIO_PIN_6};
-const GpioPin ext_pa7_gpio = {.port = GPIOA, .pin = GPIO_PIN_7};
+const GpioPin gpio_rfid_pull = {.port = RFID_PULL_GPIO_Port, .pin = RFID_PULL_Pin};
+const GpioPin gpio_rfid_carrier_out = {.port = RFID_OUT_GPIO_Port, .pin = RFID_OUT_Pin};
+const GpioPin gpio_rfid_data_in = {.port = RFID_RF_IN_GPIO_Port, .pin = RFID_RF_IN_Pin};

+ 12 - 11
firmware/targets/f5/api-hal/api-hal-resources.h

@@ -57,9 +57,6 @@ extern const GpioPin vibro_gpio;
 extern const GpioPin ibutton_gpio;
 extern const GpioPin cc1101_g0_gpio;
 
-extern const GpioPin gpio_subghz_cs;
-extern const GpioPin gpio_display_cs;
-
 extern const GpioPin gpio_subghz_cs;
 extern const GpioPin gpio_display_cs;
 extern const GpioPin gpio_display_rst;
@@ -74,14 +71,18 @@ extern const GpioPin gpio_spi_r_miso;
 extern const GpioPin gpio_spi_r_mosi;
 extern const GpioPin gpio_spi_r_sck;
 
-extern const GpioPin ext_pc0_gpio;
-extern const GpioPin ext_pc1_gpio;
-extern const GpioPin ext_pc3_gpio;
-extern const GpioPin ext_pb2_gpio;
-extern const GpioPin ext_pb3_gpio;
-extern const GpioPin ext_pa4_gpio;
-extern const GpioPin ext_pa6_gpio;
-extern const GpioPin ext_pa7_gpio;
+extern const GpioPin gpio_ext_pc0;
+extern const GpioPin gpio_ext_pc1;
+extern const GpioPin gpio_ext_pc3;
+extern const GpioPin gpio_ext_pb2;
+extern const GpioPin gpio_ext_pb3;
+extern const GpioPin gpio_ext_pa4;
+extern const GpioPin gpio_ext_pa6;
+extern const GpioPin gpio_ext_pa7;
+
+extern const GpioPin gpio_rfid_pull;
+extern const GpioPin gpio_rfid_carrier_out;
+extern const GpioPin gpio_rfid_data_in;
 
 #ifdef __cplusplus
 }

+ 202 - 0
firmware/targets/f5/api-hal/api-hal-rfid.c

@@ -0,0 +1,202 @@
+#include <api-hal-rfid.h>
+#include <api-hal-ibutton.h>
+#include <api-hal-resources.h>
+
+void api_hal_rfid_pins_reset() {
+    // ibutton bus disable
+    api_hal_ibutton_stop();
+
+    // pulldown rfid antenna
+    hal_gpio_init(&gpio_rfid_carrier_out, GpioModeOutputPushPull, GpioSpeedLow, GpioPullNo);
+    hal_gpio_write(&gpio_rfid_carrier_out, true);
+
+    // from both sides
+    hal_gpio_init(&gpio_rfid_pull, GpioModeOutputPushPull, GpioSpeedLow, GpioPullNo);
+    hal_gpio_write(&gpio_rfid_pull, true);
+}
+
+void api_hal_rfid_pins_emulate() {
+    // ibutton low
+    api_hal_ibutton_start();
+    api_hal_ibutton_pin_low();
+
+    // pull pin to timer out
+    hal_gpio_init_alt(
+        &gpio_rfid_pull, GpioModeOutputPushPull, GpioSpeedLow, GpioPullNo, GpioAltFn1TIM1);
+
+    // pull rfid antenna from carrier side
+    hal_gpio_init(&gpio_rfid_carrier_out, GpioModeOutputPushPull, GpioSpeedLow, GpioPullNo);
+    hal_gpio_write(&gpio_rfid_carrier_out, true);
+}
+
+void api_hal_rfid_pins_read() {
+    // ibutton low
+    api_hal_ibutton_start();
+    api_hal_ibutton_pin_low();
+
+    // dont pull rfid antenna
+    hal_gpio_init(&gpio_rfid_pull, GpioModeOutputPushPull, GpioSpeedLow, GpioPullNo);
+    hal_gpio_write(&gpio_rfid_pull, false);
+
+    // carrier pin to timer out
+    hal_gpio_init_alt(
+        &gpio_rfid_carrier_out, GpioModeOutputPushPull, GpioSpeedLow, GpioPullNo, GpioAltFn1TIM1);
+
+    // comparator in
+    hal_gpio_init(&gpio_rfid_data_in, GpioModeAnalog, GpioSpeedLow, GpioPullNo);
+}
+
+void api_hal_rfid_tim_read(float freq, float duty_cycle) {
+    // TODO LL init
+    uint32_t period = (uint32_t)((SystemCoreClock) / freq) - 1;
+
+    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
+    TIM_MasterConfigTypeDef sMasterConfig = {0};
+    TIM_OC_InitTypeDef sConfigOC = {0};
+    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
+
+    // basic PWM setup with needed freq and internal clock
+    LFRFID_TIM.Instance = TIM1;
+    LFRFID_TIM.Init.Prescaler = 0;
+    LFRFID_TIM.Init.CounterMode = TIM_COUNTERMODE_UP;
+    LFRFID_TIM.Init.Period = period;
+    LFRFID_TIM.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
+    LFRFID_TIM.Init.RepetitionCounter = 0;
+    LFRFID_TIM.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
+    if(HAL_TIM_Base_Init(&LFRFID_TIM) != HAL_OK) {
+        Error_Handler();
+    }
+    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
+    if(HAL_TIM_ConfigClockSource(&LFRFID_TIM, &sClockSourceConfig) != HAL_OK) {
+        Error_Handler();
+    }
+    if(HAL_TIM_PWM_Init(&LFRFID_TIM) != HAL_OK) {
+        Error_Handler();
+    }
+
+    // no master-slave mode
+    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
+    sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
+    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
+    if(HAL_TIMEx_MasterConfigSynchronization(&LFRFID_TIM, &sMasterConfig) != HAL_OK) {
+        Error_Handler();
+    }
+
+    // pwm config
+    sConfigOC.OCMode = TIM_OCMODE_PWM1;
+    sConfigOC.Pulse = (uint32_t)(LFRFID_TIM.Init.Period * duty_cycle);
+    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
+    sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
+    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
+    sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
+    sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
+    if(HAL_TIM_OC_ConfigChannel(&LFRFID_TIM, &sConfigOC, LFRFID_CH) != HAL_OK) {
+        Error_Handler();
+    }
+
+    // no deadtime
+    sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
+    sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
+    sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
+    sBreakDeadTimeConfig.DeadTime = 0;
+    sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
+    sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
+    sBreakDeadTimeConfig.BreakFilter = 0;
+    sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT;
+    sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
+    sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
+    sBreakDeadTimeConfig.Break2Filter = 0;
+    sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT;
+    sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
+    if(HAL_TIMEx_ConfigBreakDeadTime(&LFRFID_TIM, &sBreakDeadTimeConfig) != HAL_OK) {
+        Error_Handler();
+    }
+}
+
+void api_hal_rfid_tim_read_start() {
+    HAL_TIMEx_PWMN_Start(&LFRFID_TIM, LFRFID_CH);
+}
+
+void api_hal_rfid_tim_read_stop() {
+    HAL_TIMEx_PWMN_Stop(&LFRFID_TIM, LFRFID_CH);
+}
+
+void api_hal_rfid_tim_emulate(float freq) {
+    // TODO LL init
+    uint32_t prescaler = (uint32_t)((SystemCoreClock) / freq) - 1;
+
+    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
+    TIM_MasterConfigTypeDef sMasterConfig = {0};
+    TIM_OC_InitTypeDef sConfigOC = {0};
+    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
+
+    // basic PWM setup with needed freq and internal clock
+    LFRFID_TIM.Instance = TIM1;
+    LFRFID_TIM.Init.Prescaler = prescaler;
+    LFRFID_TIM.Init.CounterMode = TIM_COUNTERMODE_UP;
+    LFRFID_TIM.Init.Period = 1;
+    LFRFID_TIM.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
+    LFRFID_TIM.Init.RepetitionCounter = 0;
+    LFRFID_TIM.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
+    if(HAL_TIM_Base_Init(&LFRFID_TIM) != HAL_OK) {
+        Error_Handler();
+    }
+    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
+    if(HAL_TIM_ConfigClockSource(&LFRFID_TIM, &sClockSourceConfig) != HAL_OK) {
+        Error_Handler();
+    }
+    if(HAL_TIM_PWM_Init(&LFRFID_TIM) != HAL_OK) {
+        Error_Handler();
+    }
+
+    // no master-slave mode
+    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
+    sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
+    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
+    if(HAL_TIMEx_MasterConfigSynchronization(&LFRFID_TIM, &sMasterConfig) != HAL_OK) {
+        Error_Handler();
+    }
+
+    // pwm config
+    sConfigOC.OCMode = TIM_OCMODE_PWM1;
+    sConfigOC.Pulse = 1;
+    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
+    sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
+    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
+    sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
+    sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
+    if(HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, LFRFID_CH) != HAL_OK) {
+        Error_Handler();
+    }
+
+    // no deadtime
+    sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
+    sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
+    sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
+    sBreakDeadTimeConfig.DeadTime = 0;
+    sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
+    sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
+    sBreakDeadTimeConfig.BreakFilter = 0;
+    sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT;
+    sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
+    sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
+    sBreakDeadTimeConfig.Break2Filter = 0;
+    sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT;
+    sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
+    if(HAL_TIMEx_ConfigBreakDeadTime(&LFRFID_TIM, &sBreakDeadTimeConfig) != HAL_OK) {
+        Error_Handler();
+    }
+}
+
+void api_hal_rfid_tim_emulate_start() {
+    HAL_TIM_PWM_Start_IT(&LFRFID_TIM, LFRFID_CH);
+    HAL_TIM_Base_Start_IT(&LFRFID_TIM);
+}
+
+void api_hal_rfid_tim_emulate_stop() {
+    HAL_TIM_Base_Stop(&LFRFID_TIM);
+    HAL_TIM_PWM_Stop(&LFRFID_TIM, LFRFID_CH);
+}
+
+void api_hal_rfid_tim_reset() {
+}