فهرست منبع

move base pack here

MX 2 سال پیش
کامیت
04879fb511
53فایلهای تغییر یافته به همراه5718 افزوده شده و 0 حذف شده
  1. 17 0
      application.fam
  2. BIN
      avr_app_icon_10x10.png
  3. 179 0
      avr_isp_app.c
  4. 31 0
      avr_isp_app_i.c
  5. 44 0
      avr_isp_app_i.h
  6. 496 0
      helpers/avr_isp.c
  7. 70 0
      helpers/avr_isp.h
  8. 23 0
      helpers/avr_isp_event.h
  9. 32 0
      helpers/avr_isp_types.h
  10. 266 0
      helpers/avr_isp_worker.c
  11. 49 0
      helpers/avr_isp_worker.h
  12. 1157 0
      helpers/avr_isp_worker_rw.c
  13. 99 0
      helpers/avr_isp_worker_rw.h
  14. 321 0
      helpers/flipper_i32hex_file.c
  15. 55 0
      helpers/flipper_i32hex_file.h
  16. BIN
      images/avr_app_icon_10x10.png
  17. BIN
      images/avr_wiring.png
  18. BIN
      images/chif_not_found_83x37.png
  19. BIN
      images/chip_error_70x22.png
  20. BIN
      images/chip_long_70x22.png
  21. BIN
      images/chip_not_found_83x37.png
  22. BIN
      images/dolphin_nice_96x59.png
  23. BIN
      images/isp_active_128x53.png
  24. BIN
      images/link_waiting_77x56.png
  25. 386 0
      lib/driver/avr_isp_chip_arr.c
  26. 33 0
      lib/driver/avr_isp_chip_arr.h
  27. 639 0
      lib/driver/avr_isp_prog.c
  28. 16 0
      lib/driver/avr_isp_prog.h
  29. 97 0
      lib/driver/avr_isp_prog_cmd.h
  30. 71 0
      lib/driver/avr_isp_spi_sw.c
  31. 24 0
      lib/driver/avr_isp_spi_sw.h
  32. BIN
      lib/driver/clock.png
  33. 30 0
      scenes/avr_isp_scene.c
  34. 29 0
      scenes/avr_isp_scene.h
  35. 99 0
      scenes/avr_isp_scene_about.c
  36. 72 0
      scenes/avr_isp_scene_chip_detect.c
  37. 10 0
      scenes/avr_isp_scene_config.h
  38. 89 0
      scenes/avr_isp_scene_input_name.c
  39. 22 0
      scenes/avr_isp_scene_load.c
  40. 28 0
      scenes/avr_isp_scene_programmer.c
  41. 64 0
      scenes/avr_isp_scene_reader.c
  42. 75 0
      scenes/avr_isp_scene_start.c
  43. 44 0
      scenes/avr_isp_scene_success.c
  44. 21 0
      scenes/avr_isp_scene_wiring.c
  45. 69 0
      scenes/avr_isp_scene_writer.c
  46. 213 0
      views/avr_isp_view_chip_detect.c
  47. 32 0
      views/avr_isp_view_chip_detect.h
  48. 134 0
      views/avr_isp_view_programmer.c
  49. 27 0
      views/avr_isp_view_programmer.h
  50. 215 0
      views/avr_isp_view_reader.c
  51. 35 0
      views/avr_isp_view_reader.h
  52. 268 0
      views/avr_isp_view_writer.c
  53. 37 0
      views/avr_isp_view_writer.h

+ 17 - 0
application.fam

@@ -0,0 +1,17 @@
+App(
+    appid="avr_isp",
+    name="AVR Flasher",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="avr_isp_app",
+    requires=["gui"],
+    stack_size=4 * 1024,
+    order=20,
+    fap_icon="avr_app_icon_10x10.png",
+    fap_category="GPIO",
+    fap_icon_assets="images",
+    fap_private_libs=[
+        Lib(
+            name="driver",
+        ),
+    ],
+)

BIN
avr_app_icon_10x10.png


+ 179 - 0
avr_isp_app.c

@@ -0,0 +1,179 @@
+#include "avr_isp_app_i.h"
+
+static bool avr_isp_app_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    AvrIspApp* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool avr_isp_app_back_event_callback(void* context) {
+    furi_assert(context);
+    AvrIspApp* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+static void avr_isp_app_tick_event_callback(void* context) {
+    furi_assert(context);
+    AvrIspApp* app = context;
+    scene_manager_handle_tick_event(app->scene_manager);
+}
+
+AvrIspApp* avr_isp_app_alloc() {
+    AvrIspApp* app = malloc(sizeof(AvrIspApp));
+
+    app->file_path = furi_string_alloc();
+    furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX);
+    app->error = AvrIspErrorNoError;
+
+    // GUI
+    app->gui = furi_record_open(RECORD_GUI);
+
+    // View Dispatcher
+    app->view_dispatcher = view_dispatcher_alloc();
+    app->scene_manager = scene_manager_alloc(&avr_isp_scene_handlers, app);
+    view_dispatcher_enable_queue(app->view_dispatcher);
+
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    view_dispatcher_set_custom_event_callback(
+        app->view_dispatcher, avr_isp_app_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(
+        app->view_dispatcher, avr_isp_app_back_event_callback);
+    view_dispatcher_set_tick_event_callback(
+        app->view_dispatcher, avr_isp_app_tick_event_callback, 100);
+
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+    // Open Notification record
+    app->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    // SubMenu
+    app->submenu = submenu_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, AvrIspViewSubmenu, submenu_get_view(app->submenu));
+
+    // Widget
+    app->widget = widget_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, AvrIspViewWidget, widget_get_view(app->widget));
+
+    // Text Input
+    app->text_input = text_input_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, AvrIspViewTextInput, text_input_get_view(app->text_input));
+
+    // Popup
+    app->popup = popup_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, AvrIspViewPopup, popup_get_view(app->popup));
+
+    //Dialog
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+
+    // Programmer view
+    app->avr_isp_programmer_view = avr_isp_programmer_view_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        AvrIspViewProgrammer,
+        avr_isp_programmer_view_get_view(app->avr_isp_programmer_view));
+
+    // Reader view
+    app->avr_isp_reader_view = avr_isp_reader_view_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        AvrIspViewReader,
+        avr_isp_reader_view_get_view(app->avr_isp_reader_view));
+
+    // Writer view
+    app->avr_isp_writer_view = avr_isp_writer_view_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        AvrIspViewWriter,
+        avr_isp_writer_view_get_view(app->avr_isp_writer_view));
+
+    // Chip detect view
+    app->avr_isp_chip_detect_view = avr_isp_chip_detect_view_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        AvrIspViewChipDetect,
+        avr_isp_chip_detect_view_get_view(app->avr_isp_chip_detect_view));
+
+    // Enable 5v power, multiple attempts to avoid issues with power chip protection false triggering
+    uint8_t attempts = 0;
+    while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
+        furi_hal_power_enable_otg();
+        furi_delay_ms(10);
+    }
+
+    scene_manager_next_scene(app->scene_manager, AvrIspSceneStart);
+
+    return app;
+} //-V773
+
+void avr_isp_app_free(AvrIspApp* app) {
+    furi_assert(app);
+
+    // Disable 5v power
+    if(furi_hal_power_is_otg_enabled()) {
+        furi_hal_power_disable_otg();
+    }
+
+    // Submenu
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewSubmenu);
+    submenu_free(app->submenu);
+
+    //  Widget
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewWidget);
+    widget_free(app->widget);
+
+    // TextInput
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewTextInput);
+    text_input_free(app->text_input);
+
+    // Popup
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewPopup);
+    popup_free(app->popup);
+
+    //Dialog
+    furi_record_close(RECORD_DIALOGS);
+
+    // Programmer view
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewProgrammer);
+    avr_isp_programmer_view_free(app->avr_isp_programmer_view);
+
+    // Reader view
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewReader);
+    avr_isp_reader_view_free(app->avr_isp_reader_view);
+
+    // Writer view
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewWriter);
+    avr_isp_writer_view_free(app->avr_isp_writer_view);
+
+    // Chip detect view
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewChipDetect);
+    avr_isp_chip_detect_view_free(app->avr_isp_chip_detect_view);
+
+    // View dispatcher
+    view_dispatcher_free(app->view_dispatcher);
+    scene_manager_free(app->scene_manager);
+
+    // Notifications
+    furi_record_close(RECORD_NOTIFICATION);
+    app->notifications = NULL;
+
+    // Close records
+    furi_record_close(RECORD_GUI);
+
+    // Path strings
+    furi_string_free(app->file_path);
+
+    free(app);
+}
+
+int32_t avr_isp_app(void* p) {
+    UNUSED(p);
+    AvrIspApp* avr_isp_app = avr_isp_app_alloc();
+
+    view_dispatcher_run(avr_isp_app->view_dispatcher);
+
+    avr_isp_app_free(avr_isp_app);
+
+    return 0;
+}

+ 31 - 0
avr_isp_app_i.c

@@ -0,0 +1,31 @@
+#include "avr_isp_app_i.h"
+#include <lib/toolbox/path.h>
+#include <flipper_format/flipper_format_i.h>
+
+#define TAG "AvrIsp"
+
+bool avr_isp_load_from_file(AvrIspApp* app) {
+    furi_assert(app);
+
+    FuriString* file_path = furi_string_alloc();
+    FuriString* file_name = furi_string_alloc();
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(
+        &browser_options, AVR_ISP_APP_EXTENSION, &I_avr_app_icon_10x10);
+    browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
+
+    // Input events and views are managed by file_select
+    bool res = dialog_file_browser_show(app->dialogs, file_path, app->file_path, &browser_options);
+
+    if(res) {
+        path_extract_dirname(furi_string_get_cstr(file_path), app->file_path);
+        path_extract_filename(file_path, file_name, true);
+        strncpy(app->file_name_tmp, furi_string_get_cstr(file_name), AVR_ISP_MAX_LEN_NAME);
+    }
+
+    furi_string_free(file_name);
+    furi_string_free(file_path);
+
+    return res;
+}

+ 44 - 0
avr_isp_app_i.h

@@ -0,0 +1,44 @@
+#pragma once
+
+#include "helpers/avr_isp_types.h"
+#include <avr_isp_icons.h>
+
+#include "scenes/avr_isp_scene.h"
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/widget.h>
+#include <notification/notification_messages.h>
+#include <gui/modules/text_input.h>
+#include <dialogs/dialogs.h>
+#include <storage/storage.h>
+#include <gui/modules/popup.h>
+
+#include "views/avr_isp_view_programmer.h"
+#include "views/avr_isp_view_reader.h"
+#include "views/avr_isp_view_writer.h"
+#include "views/avr_isp_view_chip_detect.h"
+
+#define AVR_ISP_MAX_LEN_NAME 64
+
+typedef struct {
+    Gui* gui;
+    ViewDispatcher* view_dispatcher;
+    SceneManager* scene_manager;
+    NotificationApp* notifications;
+    DialogsApp* dialogs;
+    Popup* popup;
+    Submenu* submenu;
+    Widget* widget;
+    TextInput* text_input;
+    FuriString* file_path;
+    char file_name_tmp[AVR_ISP_MAX_LEN_NAME];
+    AvrIspProgrammerView* avr_isp_programmer_view;
+    AvrIspReaderView* avr_isp_reader_view;
+    AvrIspWriterView* avr_isp_writer_view;
+    AvrIspChipDetectView* avr_isp_chip_detect_view;
+    AvrIspError error;
+} AvrIspApp;
+
+bool avr_isp_load_from_file(AvrIspApp* app);

+ 496 - 0
helpers/avr_isp.c

@@ -0,0 +1,496 @@
+#include "avr_isp.h"
+#include "../lib/driver/avr_isp_prog_cmd.h"
+#include "../lib/driver/avr_isp_spi_sw.h"
+
+#include <furi.h>
+
+#define AVR_ISP_PROG_TX_RX_BUF_SIZE 320
+#define TAG "AvrIsp"
+
+struct AvrIsp {
+    AvrIspSpiSw* spi;
+    bool pmode;
+    AvrIspCallback callback;
+    void* context;
+};
+
+AvrIsp* avr_isp_alloc(void) {
+    AvrIsp* instance = malloc(sizeof(AvrIsp));
+    return instance;
+}
+
+void avr_isp_free(AvrIsp* instance) {
+    furi_assert(instance);
+
+    if(instance->spi) avr_isp_end_pmode(instance);
+    free(instance);
+}
+
+void avr_isp_set_tx_callback(AvrIsp* instance, AvrIspCallback callback, void* context) {
+    furi_assert(instance);
+    furi_assert(context);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+uint8_t avr_isp_spi_transaction(
+    AvrIsp* instance,
+    uint8_t cmd,
+    uint8_t addr_hi,
+    uint8_t addr_lo,
+    uint8_t data) {
+    furi_assert(instance);
+
+    avr_isp_spi_sw_txrx(instance->spi, cmd);
+    avr_isp_spi_sw_txrx(instance->spi, addr_hi);
+    avr_isp_spi_sw_txrx(instance->spi, addr_lo);
+    return avr_isp_spi_sw_txrx(instance->spi, data);
+}
+
+static bool avr_isp_set_pmode(AvrIsp* instance, uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
+    furi_assert(instance);
+
+    uint8_t res = 0;
+    avr_isp_spi_sw_txrx(instance->spi, a);
+    avr_isp_spi_sw_txrx(instance->spi, b);
+    res = avr_isp_spi_sw_txrx(instance->spi, c);
+    avr_isp_spi_sw_txrx(instance->spi, d);
+    return res == 0x53;
+}
+
+void avr_isp_end_pmode(AvrIsp* instance) {
+    furi_assert(instance);
+
+    if(instance->pmode) {
+        avr_isp_spi_sw_res_set(instance->spi, true);
+        // We're about to take the target out of reset
+        // so configure SPI pins as input
+        if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+        instance->spi = NULL;
+    }
+
+    instance->pmode = false;
+}
+
+static bool avr_isp_start_pmode(AvrIsp* instance, AvrIspSpiSwSpeed spi_speed) {
+    furi_assert(instance);
+
+    // Reset target before driving PIN_SCK or PIN_MOSI
+
+    // SPI.begin() will configure SS as output,
+    // so SPI master mode is selected.
+    // We have defined RESET as pin 10,
+    // which for many arduino's is not the SS pin.
+    // So we have to configure RESET as output here,
+    // (reset_target() first sets the correct level)
+    if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+    instance->spi = avr_isp_spi_sw_init(spi_speed);
+
+    avr_isp_spi_sw_res_set(instance->spi, false);
+    // See avr datasheets, chapter "SERIAL_PRG Programming Algorithm":
+
+    // Pulse RESET after PIN_SCK is low:
+    avr_isp_spi_sw_sck_set(instance->spi, false);
+
+    // discharge PIN_SCK, value arbitrally chosen
+    furi_delay_ms(20);
+    avr_isp_spi_sw_res_set(instance->spi, true);
+
+    // Pulse must be minimum 2 target CPU speed cycles
+    // so 100 usec is ok for CPU speeds above 20KHz
+    furi_delay_ms(1);
+
+    avr_isp_spi_sw_res_set(instance->spi, false);
+
+    // Send the enable programming command:
+    // datasheet: must be > 20 msec
+    furi_delay_ms(50);
+    if(avr_isp_set_pmode(instance, AVR_ISP_SET_PMODE)) {
+        instance->pmode = true;
+        return true;
+    }
+    return false;
+}
+
+bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance) {
+    furi_assert(instance);
+
+    AvrIspSpiSwSpeed spi_speed[] = {
+        AvrIspSpiSwSpeed1Mhz,
+        AvrIspSpiSwSpeed400Khz,
+        AvrIspSpiSwSpeed250Khz,
+        AvrIspSpiSwSpeed125Khz,
+        AvrIspSpiSwSpeed60Khz,
+        AvrIspSpiSwSpeed40Khz,
+        AvrIspSpiSwSpeed20Khz,
+        AvrIspSpiSwSpeed10Khz,
+        AvrIspSpiSwSpeed5Khz,
+        AvrIspSpiSwSpeed1Khz,
+    };
+    for(uint8_t i = 0; i < COUNT_OF(spi_speed); i++) {
+        if(avr_isp_start_pmode(instance, spi_speed[i])) {
+            AvrIspSignature sig = avr_isp_read_signature(instance);
+            AvrIspSignature sig_examination = avr_isp_read_signature(instance); //-V656
+            uint8_t y = 0;
+            while(y < 8) {
+                if(memcmp((uint8_t*)&sig, (uint8_t*)&sig_examination, sizeof(AvrIspSignature)) !=
+                   0)
+                    break;
+                sig_examination = avr_isp_read_signature(instance);
+                y++;
+            }
+            if(y == 8) {
+                if(spi_speed[i] > AvrIspSpiSwSpeed1Mhz) {
+                    if(i < (COUNT_OF(spi_speed) - 1)) {
+                        avr_isp_end_pmode(instance);
+                        i++;
+                        return avr_isp_start_pmode(instance, spi_speed[i]);
+                    }
+                }
+                return true;
+            }
+        }
+    }
+
+    if(instance->spi) {
+        avr_isp_spi_sw_free(instance->spi);
+        instance->spi = NULL;
+    }
+
+    return false;
+}
+
+static void avr_isp_commit(AvrIsp* instance, uint16_t addr, uint8_t data) {
+    furi_assert(instance);
+
+    avr_isp_spi_transaction(instance, AVR_ISP_COMMIT(addr));
+    /* polling flash */
+    if(data == 0xFF) {
+        furi_delay_ms(5);
+    } else {
+        /* polling flash */
+        uint32_t starttime = furi_get_tick();
+        while((furi_get_tick() - starttime) < 30) {
+            if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) {
+                break;
+            };
+        }
+    }
+}
+
+static uint16_t avr_isp_current_page(AvrIsp* instance, uint32_t addr, uint16_t page_size) {
+    furi_assert(instance);
+
+    uint16_t page = 0;
+    switch(page_size) {
+    case 32:
+        page = addr & 0xFFFFFFF0;
+        break;
+    case 64:
+        page = addr & 0xFFFFFFE0;
+        break;
+    case 128:
+        page = addr & 0xFFFFFFC0;
+        break;
+    case 256:
+        page = addr & 0xFFFFFF80;
+        break;
+
+    default:
+        page = addr;
+        break;
+    }
+
+    return page;
+}
+
+static bool avr_isp_flash_write_pages(
+    AvrIsp* instance,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+
+    size_t x = 0;
+    uint16_t page = avr_isp_current_page(instance, addr, page_size);
+
+    while(x < data_size) {
+        if(page != avr_isp_current_page(instance, addr, page_size)) {
+            avr_isp_commit(instance, page, data[x - 1]);
+            page = avr_isp_current_page(instance, addr, page_size);
+        }
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FLASH_LO(addr, data[x++]));
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FLASH_HI(addr, data[x++]));
+        addr++;
+    }
+    avr_isp_commit(instance, page, data[x - 1]);
+    return true;
+}
+
+bool avr_isp_erase_chip(AvrIsp* instance) {
+    furi_assert(instance);
+
+    bool ret = false;
+    if(!instance->pmode) avr_isp_auto_set_spi_speed_start_pmode(instance);
+    if(instance->pmode) {
+        avr_isp_spi_transaction(instance, AVR_ISP_ERASE_CHIP);
+        furi_delay_ms(100);
+        avr_isp_end_pmode(instance);
+        ret = true;
+    }
+    return ret;
+}
+
+static bool
+    avr_isp_eeprom_write(AvrIsp* instance, uint16_t addr, uint8_t* data, uint32_t data_size) {
+    furi_assert(instance);
+
+    for(uint16_t i = 0; i < data_size; i++) {
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_EEPROM(addr, data[i]));
+        furi_delay_ms(10);
+        addr++;
+    }
+    return true;
+}
+
+bool avr_isp_write_page(
+    AvrIsp* instance,
+    uint32_t mem_type,
+    uint32_t mem_size,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+
+    bool ret = false;
+    switch(mem_type) {
+    case STK_SET_FLASH_TYPE:
+        if((addr + data_size / 2) <= mem_size) {
+            ret = avr_isp_flash_write_pages(instance, addr, page_size, data, data_size);
+        }
+        break;
+
+    case STK_SET_EEPROM_TYPE:
+        if((addr + data_size) <= mem_size) {
+            ret = avr_isp_eeprom_write(instance, addr, data, data_size);
+        }
+        break;
+
+    default:
+        furi_crash(TAG " Incorrect mem type.");
+        break;
+    }
+
+    return ret;
+}
+
+static bool avr_isp_flash_read_page(
+    AvrIsp* instance,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+
+    if(page_size > data_size) return false;
+    for(uint16_t i = 0; i < page_size; i += 2) {
+        data[i] = avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_LO(addr));
+        data[i + 1] = avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr));
+        addr++;
+    }
+    return true;
+}
+
+static bool avr_isp_eeprom_read_page(
+    AvrIsp* instance,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+
+    if(page_size > data_size) return false;
+    for(uint16_t i = 0; i < page_size; i++) {
+        data[i] = avr_isp_spi_transaction(instance, AVR_ISP_READ_EEPROM(addr));
+        addr++;
+    }
+    return true;
+}
+
+bool avr_isp_read_page(
+    AvrIsp* instance,
+    uint32_t mem_type,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+
+    bool res = false;
+    if(mem_type == STK_SET_FLASH_TYPE)
+        res = avr_isp_flash_read_page(instance, addr, page_size, data, data_size);
+    if(mem_type == STK_SET_EEPROM_TYPE)
+        res = avr_isp_eeprom_read_page(instance, addr, page_size, data, data_size);
+
+    return res;
+}
+
+AvrIspSignature avr_isp_read_signature(AvrIsp* instance) {
+    furi_assert(instance);
+
+    AvrIspSignature signature;
+    signature.vendor = avr_isp_spi_transaction(instance, AVR_ISP_READ_VENDOR);
+    signature.part_family = avr_isp_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY);
+    signature.part_number = avr_isp_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER);
+    return signature;
+}
+
+uint8_t avr_isp_read_lock_byte(AvrIsp* instance) {
+    furi_assert(instance);
+
+    uint8_t data = 0;
+    uint32_t starttime = furi_get_tick();
+    while((furi_get_tick() - starttime) < 300) {
+        data = avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE);
+        if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == data) {
+            break;
+        };
+        data = 0x00;
+    }
+    return data;
+}
+
+bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock) {
+    furi_assert(instance);
+
+    bool ret = false;
+    if(avr_isp_read_lock_byte(instance) == lock) {
+        ret = true;
+    } else {
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_LOCK_BYTE(lock));
+        /* polling lock byte */
+        uint32_t starttime = furi_get_tick();
+        while((furi_get_tick() - starttime) < 30) {
+            if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == lock) {
+                ret = true;
+                break;
+            };
+        }
+    }
+    return ret;
+}
+
+uint8_t avr_isp_read_fuse_low(AvrIsp* instance) {
+    furi_assert(instance);
+
+    uint8_t data = 0;
+    uint32_t starttime = furi_get_tick();
+    while((furi_get_tick() - starttime) < 300) {
+        data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW);
+        if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == data) {
+            break;
+        };
+        data = 0x00;
+    }
+    return data;
+}
+
+bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse) {
+    furi_assert(instance);
+
+    bool ret = false;
+    if(avr_isp_read_fuse_low(instance) == lfuse) {
+        ret = true;
+    } else {
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_LOW(lfuse));
+        /* polling fuse */
+        uint32_t starttime = furi_get_tick();
+        while((furi_get_tick() - starttime) < 30) {
+            if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == lfuse) {
+                ret = true;
+                break;
+            };
+        }
+    }
+    return ret;
+}
+
+uint8_t avr_isp_read_fuse_high(AvrIsp* instance) {
+    furi_assert(instance);
+
+    uint8_t data = 0;
+    uint32_t starttime = furi_get_tick();
+    while((furi_get_tick() - starttime) < 300) {
+        data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH);
+        if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == data) {
+            break;
+        };
+        data = 0x00;
+    }
+    return data;
+}
+
+bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse) {
+    furi_assert(instance);
+
+    bool ret = false;
+    if(avr_isp_read_fuse_high(instance) == hfuse) {
+        ret = true;
+    } else {
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_HIGH(hfuse));
+        /* polling fuse */
+        uint32_t starttime = furi_get_tick();
+        while((furi_get_tick() - starttime) < 30) {
+            if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == hfuse) {
+                ret = true;
+                break;
+            };
+        }
+    }
+    return ret;
+}
+
+uint8_t avr_isp_read_fuse_extended(AvrIsp* instance) {
+    furi_assert(instance);
+
+    uint8_t data = 0;
+    uint32_t starttime = furi_get_tick();
+    while((furi_get_tick() - starttime) < 300) {
+        data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED);
+        if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == data) {
+            break;
+        };
+        data = 0x00;
+    }
+    return data;
+}
+
+bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse) {
+    furi_assert(instance);
+
+    bool ret = false;
+    if(avr_isp_read_fuse_extended(instance) == efuse) {
+        ret = true;
+    } else {
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_EXTENDED(efuse));
+        /* polling fuse */
+        uint32_t starttime = furi_get_tick();
+        while((furi_get_tick() - starttime) < 30) {
+            if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == efuse) {
+                ret = true;
+                break;
+            };
+        }
+    }
+    return ret;
+}
+
+void avr_isp_write_extended_addr(AvrIsp* instance, uint8_t extended_addr) {
+    furi_assert(instance);
+
+    avr_isp_spi_transaction(instance, AVR_ISP_EXTENDED_ADDR(extended_addr));
+    furi_delay_ms(10);
+}

+ 70 - 0
helpers/avr_isp.h

@@ -0,0 +1,70 @@
+#pragma once
+
+#include <furi_hal.h>
+
+typedef struct AvrIsp AvrIsp;
+typedef void (*AvrIspCallback)(void* context);
+
+struct AvrIspSignature {
+    uint8_t vendor;
+    uint8_t part_family;
+    uint8_t part_number;
+};
+
+typedef struct AvrIspSignature AvrIspSignature;
+
+AvrIsp* avr_isp_alloc(void);
+
+void avr_isp_free(AvrIsp* instance);
+
+void avr_isp_set_tx_callback(AvrIsp* instance, AvrIspCallback callback, void* context);
+
+bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance);
+
+AvrIspSignature avr_isp_read_signature(AvrIsp* instance);
+
+void avr_isp_end_pmode(AvrIsp* instance);
+
+bool avr_isp_erase_chip(AvrIsp* instance);
+
+uint8_t avr_isp_spi_transaction(
+    AvrIsp* instance,
+    uint8_t cmd,
+    uint8_t addr_hi,
+    uint8_t addr_lo,
+    uint8_t data);
+
+bool avr_isp_read_page(
+    AvrIsp* instance,
+    uint32_t memtype,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size);
+
+bool avr_isp_write_page(
+    AvrIsp* instance,
+    uint32_t mem_type,
+    uint32_t mem_size,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size);
+
+uint8_t avr_isp_read_lock_byte(AvrIsp* instance);
+
+bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock);
+
+uint8_t avr_isp_read_fuse_low(AvrIsp* instance);
+
+bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse);
+
+uint8_t avr_isp_read_fuse_high(AvrIsp* instance);
+
+bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse);
+
+uint8_t avr_isp_read_fuse_extended(AvrIsp* instance);
+
+bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse);
+
+void avr_isp_write_extended_addr(AvrIsp* instance, uint8_t extended_addr);

+ 23 - 0
helpers/avr_isp_event.h

@@ -0,0 +1,23 @@
+#pragma once
+
+typedef enum {
+    //SubmenuIndex
+    SubmenuIndexAvrIspProgrammer = 10,
+    SubmenuIndexAvrIspReader,
+    SubmenuIndexAvrIspWriter,
+    SubmenuIndexAvrIsWiring,
+    SubmenuIndexAvrIspAbout,
+
+    //AvrIspCustomEvent
+    AvrIspCustomEventSceneChipDetectOk = 100,
+    AvrIspCustomEventSceneReadingOk,
+    AvrIspCustomEventSceneWritingOk,
+    AvrIspCustomEventSceneErrorVerification,
+    AvrIspCustomEventSceneErrorReading,
+    AvrIspCustomEventSceneErrorWriting,
+    AvrIspCustomEventSceneErrorWritingFuse,
+    AvrIspCustomEventSceneInputName,
+    AvrIspCustomEventSceneSuccess,
+    AvrIspCustomEventSceneExit,
+    AvrIspCustomEventSceneExitStartMenu,
+} AvrIspCustomEvent;

+ 32 - 0
helpers/avr_isp_types.h

@@ -0,0 +1,32 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+
+#define AVR_ISP_VERSION_APP "0.1"
+#define AVR_ISP_DEVELOPED "SkorP"
+#define AVR_ISP_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
+
+#define AVR_ISP_APP_FILE_VERSION 1
+#define AVR_ISP_APP_FILE_TYPE "Flipper Dump AVR"
+#define AVR_ISP_APP_EXTENSION ".avr"
+
+typedef enum {
+    //AvrIspViewVariableItemList,
+    AvrIspViewSubmenu,
+    AvrIspViewProgrammer,
+    AvrIspViewReader,
+    AvrIspViewWriter,
+    AvrIspViewWidget,
+    AvrIspViewPopup,
+    AvrIspViewTextInput,
+    AvrIspViewChipDetect,
+} AvrIspView;
+
+typedef enum {
+    AvrIspErrorNoError,
+    AvrIspErrorReading,
+    AvrIspErrorWriting,
+    AvrIspErrorVerification,
+    AvrIspErrorWritingFuse,
+} AvrIspError;

+ 266 - 0
helpers/avr_isp_worker.c

@@ -0,0 +1,266 @@
+#include "avr_isp_worker.h"
+#include <furi_hal_pwm.h>
+#include "../lib/driver/avr_isp_prog.h"
+#include "../lib/driver/avr_isp_prog_cmd.h"
+#include "../lib/driver/avr_isp_chip_arr.h"
+
+#include <furi.h>
+
+#define TAG "AvrIspWorker"
+
+typedef enum {
+    AvrIspWorkerEvtStop = (1 << 0),
+
+    AvrIspWorkerEvtRx = (1 << 1),
+    AvrIspWorkerEvtTxCoplete = (1 << 2),
+    AvrIspWorkerEvtTx = (1 << 3),
+    AvrIspWorkerEvtState = (1 << 4),
+
+    //AvrIspWorkerEvtCfg = (1 << 5),
+
+} AvrIspWorkerEvt;
+
+struct AvrIspWorker {
+    FuriThread* thread;
+    volatile bool worker_running;
+    uint8_t connect_usb;
+    AvrIspWorkerCallback callback;
+    void* context;
+};
+
+#define AVR_ISP_WORKER_PROG_ALL_EVENTS (AvrIspWorkerEvtStop)
+#define AVR_ISP_WORKER_ALL_EVENTS                                                             \
+    (AvrIspWorkerEvtTx | AvrIspWorkerEvtTxCoplete | AvrIspWorkerEvtRx | AvrIspWorkerEvtStop | \
+     AvrIspWorkerEvtState)
+
+//########################/* VCP CDC */#############################################
+#include "usb_cdc.h"
+#include <cli/cli_vcp.h>
+#include <cli/cli.h>
+#include <furi_hal_usb_cdc.h>
+
+#define AVR_ISP_VCP_CDC_CH 1
+#define AVR_ISP_VCP_CDC_PKT_LEN CDC_DATA_SZ
+#define AVR_ISP_VCP_UART_RX_BUF_SIZE (AVR_ISP_VCP_CDC_PKT_LEN * 5)
+
+static void vcp_on_cdc_tx_complete(void* context);
+static void vcp_on_cdc_rx(void* context);
+static void vcp_state_callback(void* context, uint8_t state);
+static void vcp_on_cdc_control_line(void* context, uint8_t state);
+static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config);
+
+static const CdcCallbacks cdc_cb = {
+    vcp_on_cdc_tx_complete,
+    vcp_on_cdc_rx,
+    vcp_state_callback,
+    vcp_on_cdc_control_line,
+    vcp_on_line_config,
+};
+
+/* VCP callbacks */
+
+static void vcp_on_cdc_tx_complete(void* context) {
+    furi_assert(context);
+    AvrIspWorker* instance = context;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtTxCoplete);
+}
+
+static void vcp_on_cdc_rx(void* context) {
+    furi_assert(context);
+    AvrIspWorker* instance = context;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtRx);
+}
+
+static void vcp_state_callback(void* context, uint8_t state) {
+    UNUSED(context);
+
+    AvrIspWorker* instance = context;
+    instance->connect_usb = state;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtState);
+}
+
+static void vcp_on_cdc_control_line(void* context, uint8_t state) {
+    UNUSED(context);
+    UNUSED(state);
+}
+
+static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config) {
+    UNUSED(context);
+    UNUSED(config);
+}
+
+static void avr_isp_worker_vcp_cdc_init(void* context) {
+    furi_hal_usb_unlock();
+    Cli* cli = furi_record_open(RECORD_CLI);
+    //close cli
+    cli_session_close(cli);
+    //disable callbacks VCP_CDC=0
+    furi_hal_cdc_set_callbacks(0, NULL, NULL);
+    //set 2 cdc
+    furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true);
+    //open cli VCP_CDC=0
+    cli_session_open(cli, &cli_vcp);
+    furi_record_close(RECORD_CLI);
+
+    furi_hal_cdc_set_callbacks(AVR_ISP_VCP_CDC_CH, (CdcCallbacks*)&cdc_cb, context);
+}
+
+static void avr_isp_worker_vcp_cdc_deinit(void) {
+    //disable callbacks AVR_ISP_VCP_CDC_CH
+    furi_hal_cdc_set_callbacks(AVR_ISP_VCP_CDC_CH, NULL, NULL);
+
+    Cli* cli = furi_record_open(RECORD_CLI);
+    //close cli
+    cli_session_close(cli);
+    furi_hal_usb_unlock();
+    //set 1 cdc
+    furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
+    //open cli VCP_CDC=0
+    cli_session_open(cli, &cli_vcp);
+    furi_record_close(RECORD_CLI);
+}
+
+//#################################################################################
+
+static int32_t avr_isp_worker_prog_thread(void* context) {
+    AvrIspProg* prog = context;
+    FURI_LOG_D(TAG, "AvrIspProgWorker Start");
+    while(1) {
+        if(furi_thread_flags_get() & AvrIspWorkerEvtStop) break;
+        avr_isp_prog_avrisp(prog);
+    }
+    FURI_LOG_D(TAG, "AvrIspProgWorker Stop");
+    return 0;
+}
+
+static void avr_isp_worker_prog_tx_data(void* context) {
+    furi_assert(context);
+    AvrIspWorker* instance = context;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtTx);
+}
+
+/** Worker thread
+ * 
+ * @param context 
+ * @return exit code 
+ */
+static int32_t avr_isp_worker_thread(void* context) {
+    AvrIspWorker* instance = context;
+    avr_isp_worker_vcp_cdc_init(instance);
+
+    /* start PWM on &gpio_ext_pa4 */
+    furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
+
+    AvrIspProg* prog = avr_isp_prog_init();
+    avr_isp_prog_set_tx_callback(prog, avr_isp_worker_prog_tx_data, instance);
+
+    uint8_t buf[AVR_ISP_VCP_UART_RX_BUF_SIZE];
+    size_t len = 0;
+
+    FuriThread* prog_thread =
+        furi_thread_alloc_ex("AvrIspProgWorker", 1024, avr_isp_worker_prog_thread, prog);
+    furi_thread_start(prog_thread);
+
+    FURI_LOG_D(TAG, "Start");
+
+    while(instance->worker_running) {
+        uint32_t events =
+            furi_thread_flags_wait(AVR_ISP_WORKER_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever);
+
+        if(events & AvrIspWorkerEvtRx) {
+            if(avr_isp_prog_spaces_rx(prog) >= AVR_ISP_VCP_CDC_PKT_LEN) {
+                len = furi_hal_cdc_receive(AVR_ISP_VCP_CDC_CH, buf, AVR_ISP_VCP_CDC_PKT_LEN);
+                // for(uint8_t i = 0; i < len; i++) {
+                //     FURI_LOG_I(TAG, "--> %X", buf[i]);
+                // }
+                avr_isp_prog_rx(prog, buf, len);
+            } else {
+                furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtRx);
+            }
+        }
+
+        if((events & AvrIspWorkerEvtTxCoplete) || (events & AvrIspWorkerEvtTx)) {
+            len = avr_isp_prog_tx(prog, buf, AVR_ISP_VCP_CDC_PKT_LEN);
+
+            // for(uint8_t i = 0; i < len; i++) {
+            //     FURI_LOG_I(TAG, "<-- %X", buf[i]);
+            // }
+
+            if(len > 0) furi_hal_cdc_send(AVR_ISP_VCP_CDC_CH, buf, len);
+        }
+
+        if(events & AvrIspWorkerEvtStop) {
+            break;
+        }
+
+        if(events & AvrIspWorkerEvtState) {
+            if(instance->callback)
+                instance->callback(instance->context, (bool)instance->connect_usb);
+        }
+    }
+
+    FURI_LOG_D(TAG, "Stop");
+
+    furi_thread_flags_set(furi_thread_get_id(prog_thread), AvrIspWorkerEvtStop);
+    avr_isp_prog_exit(prog);
+    furi_delay_ms(10);
+    furi_thread_join(prog_thread);
+    furi_thread_free(prog_thread);
+
+    avr_isp_prog_free(prog);
+    furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
+    avr_isp_worker_vcp_cdc_deinit();
+    return 0;
+}
+
+AvrIspWorker* avr_isp_worker_alloc(void* context) {
+    furi_assert(context);
+    UNUSED(context);
+    AvrIspWorker* instance = malloc(sizeof(AvrIspWorker));
+
+    instance->thread = furi_thread_alloc_ex("AvrIspWorker", 2048, avr_isp_worker_thread, instance);
+    return instance;
+}
+
+void avr_isp_worker_free(AvrIspWorker* instance) {
+    furi_assert(instance);
+
+    furi_check(!instance->worker_running);
+    furi_thread_free(instance->thread);
+    free(instance);
+}
+
+void avr_isp_worker_set_callback(
+    AvrIspWorker* instance,
+    AvrIspWorkerCallback callback,
+    void* context) {
+    furi_assert(instance);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void avr_isp_worker_start(AvrIspWorker* instance) {
+    furi_assert(instance);
+    furi_assert(!instance->worker_running);
+
+    instance->worker_running = true;
+
+    furi_thread_start(instance->thread);
+}
+
+void avr_isp_worker_stop(AvrIspWorker* instance) {
+    furi_assert(instance);
+    furi_assert(instance->worker_running);
+
+    instance->worker_running = false;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtStop);
+
+    furi_thread_join(instance->thread);
+}
+
+bool avr_isp_worker_is_running(AvrIspWorker* instance) {
+    furi_assert(instance);
+
+    return instance->worker_running;
+}

+ 49 - 0
helpers/avr_isp_worker.h

@@ -0,0 +1,49 @@
+#pragma once
+
+#include <furi_hal.h>
+
+typedef struct AvrIspWorker AvrIspWorker;
+
+typedef void (*AvrIspWorkerCallback)(void* context, bool connect_usb);
+
+/** Allocate AvrIspWorker
+ * 
+ * @param context AvrIsp* context
+ * @return AvrIspWorker* 
+ */
+AvrIspWorker* avr_isp_worker_alloc(void* context);
+
+/** Free AvrIspWorker
+ * 
+ * @param instance AvrIspWorker instance
+ */
+void avr_isp_worker_free(AvrIspWorker* instance);
+
+/** Callback AvrIspWorker
+ *
+ * @param instance AvrIspWorker instance
+ * @param callback AvrIspWorkerOverrunCallback callback
+ * @param context
+ */
+void avr_isp_worker_set_callback(
+    AvrIspWorker* instance,
+    AvrIspWorkerCallback callback,
+    void* context);
+
+/** Start AvrIspWorker
+ * 
+ * @param instance AvrIspWorker instance
+ */
+void avr_isp_worker_start(AvrIspWorker* instance);
+
+/** Stop AvrIspWorker
+ * 
+ * @param instance AvrIspWorker instance
+ */
+void avr_isp_worker_stop(AvrIspWorker* instance);
+
+/** Check if worker is running
+ * @param instance AvrIspWorker instance
+ * @return bool - true if running
+ */
+bool avr_isp_worker_is_running(AvrIspWorker* instance);

+ 1157 - 0
helpers/avr_isp_worker_rw.c

@@ -0,0 +1,1157 @@
+#include "avr_isp_worker_rw.h"
+#include <furi_hal_pwm.h>
+#include "avr_isp_types.h"
+#include "avr_isp.h"
+#include "../lib/driver/avr_isp_prog_cmd.h"
+#include "../lib/driver/avr_isp_chip_arr.h"
+
+#include "flipper_i32hex_file.h"
+#include <flipper_format/flipper_format.h>
+
+#include <furi.h>
+
+#define TAG "AvrIspWorkerRW"
+
+#define NAME_PATERN_FLASH_FILE "flash.hex"
+#define NAME_PATERN_EEPROM_FILE "eeprom.hex"
+
+struct AvrIspWorkerRW {
+    AvrIsp* avr_isp;
+    FuriThread* thread;
+    volatile bool worker_running;
+
+    uint32_t chip_arr_ind;
+    bool chip_detect;
+    uint8_t lfuse;
+    uint8_t hfuse;
+    uint8_t efuse;
+    uint8_t lock;
+    float progress_flash;
+    float progress_eeprom;
+    const char* file_path;
+    const char* file_name;
+    AvrIspSignature signature;
+    AvrIspWorkerRWCallback callback;
+    void* context;
+
+    AvrIspWorkerRWStatusCallback callback_status;
+    void* context_status;
+};
+
+typedef enum {
+    AvrIspWorkerRWEvtStop = (1 << 0),
+
+    AvrIspWorkerRWEvtReading = (1 << 1),
+    AvrIspWorkerRWEvtVerification = (1 << 2),
+    AvrIspWorkerRWEvtWriting = (1 << 3),
+    AvrIspWorkerRWEvtWritingFuse = (1 << 4),
+
+} AvrIspWorkerRWEvt;
+#define AVR_ISP_WORKER_ALL_EVENTS                                                              \
+    (AvrIspWorkerRWEvtWritingFuse | AvrIspWorkerRWEvtWriting | AvrIspWorkerRWEvtVerification | \
+     AvrIspWorkerRWEvtReading | AvrIspWorkerRWEvtStop)
+
+/** Worker thread
+ * 
+ * @param context 
+ * @return exit code 
+ */
+static int32_t avr_isp_worker_rw_thread(void* context) {
+    AvrIspWorkerRW* instance = context;
+
+    /* start PWM on &gpio_ext_pa4 */
+    if(!furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) {
+        furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
+    }
+
+    FURI_LOG_D(TAG, "Start");
+
+    while(1) {
+        uint32_t events =
+            furi_thread_flags_wait(AVR_ISP_WORKER_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever);
+
+        if(events & AvrIspWorkerRWEvtStop) {
+            break;
+        }
+
+        if(events & AvrIspWorkerRWEvtWritingFuse) {
+            if(avr_isp_worker_rw_write_fuse(instance, instance->file_path, instance->file_name)) {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusEndWritingFuse);
+            } else {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusErrorWritingFuse);
+            }
+        }
+
+        if(events & AvrIspWorkerRWEvtWriting) {
+            if(avr_isp_worker_rw_write_dump(instance, instance->file_path, instance->file_name)) {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusEndWriting);
+            } else {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusErrorWriting);
+            }
+        }
+
+        if(events & AvrIspWorkerRWEvtVerification) {
+            if(avr_isp_worker_rw_verification(instance, instance->file_path, instance->file_name)) {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusEndVerification);
+            } else {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusErrorVerification);
+            }
+        }
+
+        if(events & AvrIspWorkerRWEvtReading) {
+            if(avr_isp_worker_rw_read_dump(instance, instance->file_path, instance->file_name)) {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusEndReading);
+            } else {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusErrorReading);
+            }
+        }
+    }
+    FURI_LOG_D(TAG, "Stop");
+
+    if(furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) {
+        furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
+    }
+
+    return 0;
+}
+
+bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+
+    FURI_LOG_D(TAG, "Detecting AVR chip");
+
+    instance->chip_detect = false;
+    instance->chip_arr_ind = avr_isp_chip_arr_size + 1;
+
+    /* start PWM on &gpio_ext_pa4 */
+    bool was_pwm_enabled = false;
+    if(!furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) {
+        furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
+    } else {
+        was_pwm_enabled = true;
+    }
+
+    do {
+        if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+            FURI_LOG_E(TAG, "Well, I managed to enter the mod program");
+            break;
+        }
+        instance->signature = avr_isp_read_signature(instance->avr_isp);
+
+        if(instance->signature.vendor != 0x1E) {
+            //No detect chip
+        } else {
+            for(uint32_t ind = 0; ind < avr_isp_chip_arr_size; ind++) {
+                if(avr_isp_chip_arr[ind].avrarch != F_AVR8) continue;
+                if(avr_isp_chip_arr[ind].sigs[1] == instance->signature.part_family) {
+                    if(avr_isp_chip_arr[ind].sigs[2] == instance->signature.part_number) {
+                        FURI_LOG_D(TAG, "Detect AVR chip = \"%s\"", avr_isp_chip_arr[ind].name);
+                        FURI_LOG_D(
+                            TAG,
+                            "Signature = 0x%02X 0x%02X 0x%02X",
+                            instance->signature.vendor,
+                            instance->signature.part_family,
+                            instance->signature.part_number);
+
+                        switch(avr_isp_chip_arr[ind].nfuses) {
+                        case 1:
+                            instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp);
+                            FURI_LOG_D(TAG, "Lfuse = %02X", instance->lfuse);
+                            break;
+                        case 2:
+                            instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp);
+                            instance->hfuse = avr_isp_read_fuse_high(instance->avr_isp);
+                            FURI_LOG_D(
+                                TAG, "Lfuse = %02X Hfuse = %02X", instance->lfuse, instance->hfuse);
+                            break;
+                        case 3:
+                            instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp);
+                            instance->hfuse = avr_isp_read_fuse_high(instance->avr_isp);
+                            instance->efuse = avr_isp_read_fuse_extended(instance->avr_isp);
+                            FURI_LOG_D(
+                                TAG,
+                                "Lfuse = %02X Hfuse = %02X Efuse = %02X",
+                                instance->lfuse,
+                                instance->hfuse,
+                                instance->efuse);
+                            break;
+                        default:
+                            break;
+                        }
+                        if(avr_isp_chip_arr[ind].nlocks == 1) {
+                            instance->lock = avr_isp_read_lock_byte(instance->avr_isp);
+                            FURI_LOG_D(TAG, "Lock = %02X", instance->lock);
+                        }
+                        instance->chip_detect = true;
+                        instance->chip_arr_ind = ind;
+                        break;
+                    }
+                }
+            }
+        }
+        avr_isp_end_pmode(instance->avr_isp);
+
+    } while(0);
+
+    if(furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4) && !was_pwm_enabled) {
+        furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
+    }
+
+    if(instance->callback) {
+        if(instance->chip_arr_ind > avr_isp_chip_arr_size) {
+            instance->callback(instance->context, "No detect", instance->chip_detect, 0);
+        } else if(instance->chip_arr_ind < avr_isp_chip_arr_size) {
+            instance->callback(
+                instance->context,
+                avr_isp_chip_arr[instance->chip_arr_ind].name,
+                instance->chip_detect,
+                avr_isp_chip_arr[instance->chip_arr_ind].flashsize);
+        } else {
+            instance->callback(instance->context, "Unknown", instance->chip_detect, 0);
+        }
+    }
+
+    return instance->chip_detect;
+}
+
+AvrIspWorkerRW* avr_isp_worker_rw_alloc(void* context) {
+    furi_assert(context);
+    UNUSED(context);
+
+    AvrIspWorkerRW* instance = malloc(sizeof(AvrIspWorkerRW));
+    instance->avr_isp = avr_isp_alloc();
+
+    instance->thread =
+        furi_thread_alloc_ex("AvrIspWorkerRW", 4096, avr_isp_worker_rw_thread, instance);
+
+    instance->chip_detect = false;
+    instance->lfuse = 0;
+    instance->hfuse = 0;
+    instance->efuse = 0;
+    instance->lock = 0;
+    instance->progress_flash = 0.0f;
+    instance->progress_eeprom = 0.0f;
+
+    return instance;
+}
+
+void avr_isp_worker_rw_free(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+
+    avr_isp_free(instance->avr_isp);
+
+    furi_check(!instance->worker_running);
+    furi_thread_free(instance->thread);
+
+    free(instance);
+}
+
+void avr_isp_worker_rw_start(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+    furi_assert(!instance->worker_running);
+
+    instance->worker_running = true;
+
+    furi_thread_start(instance->thread);
+}
+
+void avr_isp_worker_rw_stop(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+    furi_assert(instance->worker_running);
+
+    instance->worker_running = false;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtStop);
+
+    furi_thread_join(instance->thread);
+}
+
+bool avr_isp_worker_rw_is_running(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+
+    return instance->worker_running;
+}
+
+void avr_isp_worker_rw_set_callback(
+    AvrIspWorkerRW* instance,
+    AvrIspWorkerRWCallback callback,
+    void* context) {
+    furi_assert(instance);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void avr_isp_worker_rw_set_callback_status(
+    AvrIspWorkerRW* instance,
+    AvrIspWorkerRWStatusCallback callback_status,
+    void* context_status) {
+    furi_assert(instance);
+
+    instance->callback_status = callback_status;
+    instance->context_status = context_status;
+}
+
+float avr_isp_worker_rw_get_progress_flash(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+
+    return instance->progress_flash;
+}
+
+float avr_isp_worker_rw_get_progress_eeprom(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+
+    return instance->progress_eeprom;
+}
+
+static void avr_isp_worker_rw_get_dump_flash(AvrIspWorkerRW* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_check(instance->avr_isp);
+
+    FURI_LOG_D(TAG, "Dump FLASH %s", file_path);
+
+    FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_write(
+        file_path, avr_isp_chip_arr[instance->chip_arr_ind].flashoffset);
+
+    uint8_t data[272] = {0};
+    bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000);
+    uint8_t extended_addr = 0;
+
+    for(int32_t i = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset;
+        i < avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2;
+        i += avr_isp_chip_arr[instance->chip_arr_ind].pagesize / 2) {
+        if(send_extended_addr) {
+            if(extended_addr <= ((i >> 16) & 0xFF)) {
+                avr_isp_write_extended_addr(instance->avr_isp, extended_addr);
+                extended_addr = ((i >> 16) & 0xFF) + 1;
+            }
+        }
+        avr_isp_read_page(
+            instance->avr_isp,
+            STK_SET_FLASH_TYPE,
+            (uint16_t)i,
+            avr_isp_chip_arr[instance->chip_arr_ind].pagesize,
+            data,
+            sizeof(data));
+        flipper_i32hex_file_bin_to_i32hex_set_data(
+            flipper_hex_flash, data, avr_isp_chip_arr[instance->chip_arr_ind].pagesize);
+        //FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash));
+        instance->progress_flash =
+            (float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f);
+    }
+    flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_flash);
+    //FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash));
+    flipper_i32hex_file_close(flipper_hex_flash);
+    instance->progress_flash = 1.0f;
+}
+
+static void avr_isp_worker_rw_get_dump_eeprom(AvrIspWorkerRW* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_check(instance->avr_isp);
+
+    FURI_LOG_D(TAG, "Dump EEPROM %s", file_path);
+
+    FlipperI32HexFile* flipper_hex_eeprom = flipper_i32hex_file_open_write(
+        file_path, avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset);
+
+    int32_t size_data = 32;
+    uint8_t data[256] = {0};
+
+    if(size_data > avr_isp_chip_arr[instance->chip_arr_ind].eepromsize)
+        size_data = avr_isp_chip_arr[instance->chip_arr_ind].eepromsize;
+
+    for(int32_t i = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset;
+        i < avr_isp_chip_arr[instance->chip_arr_ind].eepromsize;
+        i += size_data) {
+        avr_isp_read_page(
+            instance->avr_isp, STK_SET_EEPROM_TYPE, (uint16_t)i, size_data, data, sizeof(data));
+        flipper_i32hex_file_bin_to_i32hex_set_data(flipper_hex_eeprom, data, size_data);
+        FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_eeprom));
+        instance->progress_eeprom =
+            (float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize);
+    }
+    flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_eeprom);
+    FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_eeprom));
+    flipper_i32hex_file_close(flipper_hex_eeprom);
+    instance->progress_eeprom = 1.0f;
+}
+
+bool avr_isp_worker_rw_read_dump(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+    furi_assert(file_path);
+    furi_assert(file_name);
+
+    FURI_LOG_D(TAG, "Read dump chip");
+
+    instance->progress_flash = 0.0f;
+    instance->progress_eeprom = 0.0f;
+    bool ret = false;
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
+    FuriString* file_path_name = furi_string_alloc();
+
+    if(!avr_isp_worker_rw_detect_chip(instance)) {
+        FURI_LOG_E(TAG, "No detect AVR chip");
+    } else {
+        do {
+            furi_string_printf(
+                file_path_name, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION);
+            if(!flipper_format_file_open_always(
+                   flipper_format, furi_string_get_cstr(file_path_name))) {
+                FURI_LOG_E(TAG, "flipper_format_file_open_always");
+                break;
+            }
+            if(!flipper_format_write_header_cstr(
+                   flipper_format, AVR_ISP_APP_FILE_TYPE, AVR_ISP_APP_FILE_VERSION)) {
+                FURI_LOG_E(TAG, "flipper_format_write_header_cstr");
+                break;
+            }
+            if(!flipper_format_write_string_cstr(
+                   flipper_format, "Chip name", avr_isp_chip_arr[instance->chip_arr_ind].name)) {
+                FURI_LOG_E(TAG, "Chip name");
+                break;
+            }
+            if(!flipper_format_write_hex(
+                   flipper_format,
+                   "Signature",
+                   (uint8_t*)&instance->signature,
+                   sizeof(AvrIspSignature))) {
+                FURI_LOG_E(TAG, "Unable to add Signature");
+                break;
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) {
+                if(!flipper_format_write_hex(flipper_format, "Lfuse", &instance->lfuse, 1)) {
+                    FURI_LOG_E(TAG, "Unable to add Lfuse");
+                    break;
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) {
+                if(!flipper_format_write_hex(flipper_format, "Hfuse", &instance->hfuse, 1)) {
+                    FURI_LOG_E(TAG, "Unable to add Hfuse");
+                    break;
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) {
+                if(!flipper_format_write_hex(flipper_format, "Efuse", &instance->efuse, 1)) {
+                    FURI_LOG_E(TAG, "Unable to add Efuse");
+                    break;
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) {
+                if(!flipper_format_write_hex(flipper_format, "Lock", &instance->lock, 1)) {
+                    FURI_LOG_E(TAG, "Unable to add Lock");
+                    break;
+                }
+            }
+            furi_string_printf(file_path_name, "%s_%s", file_name, NAME_PATERN_FLASH_FILE);
+            if(!flipper_format_write_string_cstr(
+                   flipper_format, "Dump_flash", furi_string_get_cstr(file_path_name))) {
+                FURI_LOG_E(TAG, "Unable to add Dump_flash");
+                break;
+            }
+
+            if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) {
+                furi_string_printf(file_path_name, "%s_%s", file_name, NAME_PATERN_EEPROM_FILE);
+                if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) {
+                    if(!flipper_format_write_string_cstr(
+                           flipper_format, "Dump_eeprom", furi_string_get_cstr(file_path_name))) {
+                        FURI_LOG_E(TAG, "Unable to add Dump_eeprom");
+                        break;
+                    }
+                }
+            }
+            ret = true;
+        } while(false);
+    }
+
+    flipper_format_free(flipper_format);
+    furi_record_close(RECORD_STORAGE);
+
+    if(ret) {
+        if(avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+            //Dump flash
+            furi_string_printf(
+                file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_FLASH_FILE);
+            avr_isp_worker_rw_get_dump_flash(instance, furi_string_get_cstr(file_path_name));
+            //Dump eeprom
+            if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) {
+                furi_string_printf(
+                    file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_EEPROM_FILE);
+                avr_isp_worker_rw_get_dump_eeprom(instance, furi_string_get_cstr(file_path_name));
+            }
+
+            avr_isp_end_pmode(instance->avr_isp);
+        }
+    }
+
+    furi_string_free(file_path_name);
+
+    return true;
+}
+
+void avr_isp_worker_rw_read_dump_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+
+    instance->file_path = file_path;
+    instance->file_name = file_name;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtReading);
+}
+
+static bool avr_isp_worker_rw_verification_flash(AvrIspWorkerRW* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_assert(file_path);
+
+    FURI_LOG_D(TAG, "Verification flash %s", file_path);
+
+    instance->progress_flash = 0.0;
+    bool ret = true;
+
+    FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_read(file_path);
+
+    uint8_t data_read_flash[272] = {0};
+    uint8_t data_read_hex[272] = {0};
+
+    uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset;
+    bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000);
+    uint8_t extended_addr = 0;
+
+    FlipperI32HexFileRet flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+        flipper_hex_flash, data_read_hex, sizeof(data_read_hex));
+
+    while(((flipper_hex_ret.status == FlipperI32HexFileStatusData) ||
+           (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) &&
+          ret) {
+        switch(flipper_hex_ret.status) {
+        case FlipperI32HexFileStatusData:
+
+            if(send_extended_addr) {
+                if(extended_addr <= ((addr >> 16) & 0xFF)) {
+                    avr_isp_write_extended_addr(instance->avr_isp, extended_addr);
+                    extended_addr = ((addr >> 16) & 0xFF) + 1;
+                }
+            }
+
+            avr_isp_read_page(
+                instance->avr_isp,
+                STK_SET_FLASH_TYPE,
+                (uint16_t)addr,
+                flipper_hex_ret.data_size,
+                data_read_flash,
+                sizeof(data_read_flash));
+
+            if(memcmp(data_read_hex, data_read_flash, flipper_hex_ret.data_size) != 0) {
+                ret = false;
+
+                FURI_LOG_E(TAG, "Verification flash error");
+                FURI_LOG_E(TAG, "Addr: 0x%04lX", addr);
+                for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) {
+                    FURI_LOG_RAW_E("%02X ", data_read_hex[i]);
+                }
+                FURI_LOG_RAW_E("\r\n");
+                for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) {
+                    FURI_LOG_RAW_E("%02X ", data_read_flash[i]);
+                }
+                FURI_LOG_RAW_E("\r\n");
+            }
+
+            addr += flipper_hex_ret.data_size / 2;
+            instance->progress_flash =
+                (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f);
+            break;
+
+        case FlipperI32HexFileStatusUdateAddr:
+            addr = (data_read_hex[0] << 24 | data_read_hex[1] << 16) / 2;
+            break;
+
+        default:
+            furi_crash(TAG " Incorrect status.");
+            break;
+        }
+
+        flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+            flipper_hex_flash, data_read_hex, sizeof(data_read_hex));
+    }
+
+    flipper_i32hex_file_close(flipper_hex_flash);
+    instance->progress_flash = 1.0f;
+
+    return ret;
+}
+
+static bool
+    avr_isp_worker_rw_verification_eeprom(AvrIspWorkerRW* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_assert(file_path);
+
+    FURI_LOG_D(TAG, "Verification eeprom %s", file_path);
+
+    instance->progress_eeprom = 0.0;
+    bool ret = true;
+
+    FlipperI32HexFile* flipper_hex_eeprom = flipper_i32hex_file_open_read(file_path);
+
+    uint8_t data_read_eeprom[272] = {0};
+    uint8_t data_read_hex[272] = {0};
+
+    uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset;
+
+    FlipperI32HexFileRet flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+        flipper_hex_eeprom, data_read_hex, sizeof(data_read_hex));
+
+    while(((flipper_hex_ret.status == FlipperI32HexFileStatusData) ||
+           (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) &&
+          ret) {
+        switch(flipper_hex_ret.status) {
+        case FlipperI32HexFileStatusData:
+            avr_isp_read_page(
+                instance->avr_isp,
+                STK_SET_EEPROM_TYPE,
+                (uint16_t)addr,
+                flipper_hex_ret.data_size,
+                data_read_eeprom,
+                sizeof(data_read_eeprom));
+
+            if(memcmp(data_read_hex, data_read_eeprom, flipper_hex_ret.data_size) != 0) {
+                ret = false;
+                FURI_LOG_E(TAG, "Verification eeprom error");
+                FURI_LOG_E(TAG, "Addr: 0x%04lX", addr);
+                for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) {
+                    FURI_LOG_RAW_E("%02X ", data_read_hex[i]);
+                }
+                FURI_LOG_RAW_E("\r\n");
+                for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) {
+                    FURI_LOG_RAW_E("%02X ", data_read_eeprom[i]);
+                }
+                FURI_LOG_RAW_E("\r\n");
+            }
+
+            addr += flipper_hex_ret.data_size;
+            instance->progress_eeprom =
+                (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize);
+            break;
+
+        case FlipperI32HexFileStatusUdateAddr:
+            addr = (data_read_hex[0] << 24 | data_read_hex[1] << 16);
+            break;
+
+        default:
+            furi_crash(TAG " Incorrect status.");
+            break;
+        }
+
+        flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+            flipper_hex_eeprom, data_read_hex, sizeof(data_read_hex));
+    }
+
+    flipper_i32hex_file_close(flipper_hex_eeprom);
+    instance->progress_eeprom = 1.0f;
+
+    return ret;
+}
+
+bool avr_isp_worker_rw_verification(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+    furi_assert(file_path);
+    furi_assert(file_name);
+
+    FURI_LOG_D(TAG, "Verification chip");
+
+    instance->progress_flash = 0.0f;
+    instance->progress_eeprom = 0.0f;
+    FuriString* file_path_name = furi_string_alloc();
+
+    bool ret = false;
+
+    if(avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+        do {
+            furi_string_printf(
+                file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_FLASH_FILE);
+            if(!avr_isp_worker_rw_verification_flash(
+                   instance, furi_string_get_cstr(file_path_name)))
+                break;
+
+            if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) {
+                furi_string_printf(
+                    file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_EEPROM_FILE);
+
+                if(!avr_isp_worker_rw_verification_eeprom(
+                       instance, furi_string_get_cstr(file_path_name)))
+                    break;
+            }
+            ret = true;
+        } while(false);
+        avr_isp_end_pmode(instance->avr_isp);
+        furi_string_free(file_path_name);
+    }
+    return ret;
+}
+
+void avr_isp_worker_rw_verification_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+
+    instance->file_path = file_path;
+    instance->file_name = file_name;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtVerification);
+}
+
+static void avr_isp_worker_rw_write_flash(AvrIspWorkerRW* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_check(instance->avr_isp);
+
+    instance->progress_flash = 0.0;
+
+    FURI_LOG_D(TAG, "Write Flash %s", file_path);
+
+    uint8_t data[288] = {0};
+
+    FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_read(file_path);
+
+    uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset;
+    bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000);
+    uint8_t extended_addr = 0;
+
+    FlipperI32HexFileRet flipper_hex_ret =
+        flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_flash, data, sizeof(data));
+
+    while((flipper_hex_ret.status == FlipperI32HexFileStatusData) ||
+          (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) {
+        switch(flipper_hex_ret.status) {
+        case FlipperI32HexFileStatusData:
+
+            if(send_extended_addr) {
+                if(extended_addr <= ((addr >> 16) & 0xFF)) {
+                    avr_isp_write_extended_addr(instance->avr_isp, extended_addr);
+                    extended_addr = ((addr >> 16) & 0xFF) + 1;
+                }
+            }
+
+            if(!avr_isp_write_page(
+                   instance->avr_isp,
+                   STK_SET_FLASH_TYPE,
+                   avr_isp_chip_arr[instance->chip_arr_ind].flashsize,
+                   (uint16_t)addr,
+                   avr_isp_chip_arr[instance->chip_arr_ind].pagesize,
+                   data,
+                   flipper_hex_ret.data_size)) {
+                break;
+            }
+            addr += flipper_hex_ret.data_size / 2;
+            instance->progress_flash =
+                (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f);
+            break;
+
+        case FlipperI32HexFileStatusUdateAddr:
+            addr = (data[0] << 24 | data[1] << 16) / 2;
+            break;
+
+        default:
+            furi_crash(TAG " Incorrect status.");
+            break;
+        }
+
+        flipper_hex_ret =
+            flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_flash, data, sizeof(data));
+    }
+
+    flipper_i32hex_file_close(flipper_hex_flash);
+    instance->progress_flash = 1.0f;
+}
+
+static void avr_isp_worker_rw_write_eeprom(AvrIspWorkerRW* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_check(instance->avr_isp);
+
+    instance->progress_eeprom = 0.0;
+    uint8_t data[288] = {0};
+
+    FURI_LOG_D(TAG, "Write EEPROM %s", file_path);
+
+    FlipperI32HexFile* flipper_hex_eeprom_read = flipper_i32hex_file_open_read(file_path);
+
+    uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset;
+    FlipperI32HexFileRet flipper_hex_ret =
+        flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_eeprom_read, data, sizeof(data));
+
+    while((flipper_hex_ret.status == FlipperI32HexFileStatusData) ||
+          (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) {
+        switch(flipper_hex_ret.status) {
+        case FlipperI32HexFileStatusData:
+            if(!avr_isp_write_page(
+                   instance->avr_isp,
+                   STK_SET_EEPROM_TYPE,
+                   avr_isp_chip_arr[instance->chip_arr_ind].eepromsize,
+                   (uint16_t)addr,
+                   avr_isp_chip_arr[instance->chip_arr_ind].eeprompagesize,
+                   data,
+                   flipper_hex_ret.data_size)) {
+                break;
+            }
+            addr += flipper_hex_ret.data_size;
+            instance->progress_eeprom =
+                (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize);
+            break;
+
+        case FlipperI32HexFileStatusUdateAddr:
+            addr = data[0] << 24 | data[1] << 16;
+            break;
+
+        default:
+            furi_crash(TAG " Incorrect status.");
+            break;
+        }
+
+        flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+            flipper_hex_eeprom_read, data, sizeof(data));
+    }
+
+    flipper_i32hex_file_close(flipper_hex_eeprom_read);
+    instance->progress_eeprom = 1.0f;
+}
+
+bool avr_isp_worker_rw_write_dump(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+    furi_assert(file_path);
+    furi_assert(file_name);
+
+    FURI_LOG_D(TAG, "Write dump chip");
+
+    instance->progress_flash = 0.0f;
+    instance->progress_eeprom = 0.0f;
+    bool ret = false;
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
+    FuriString* file_path_name = furi_string_alloc();
+
+    FuriString* temp_str_1 = furi_string_alloc();
+    FuriString* temp_str_2 = furi_string_alloc();
+    uint32_t temp_data32;
+
+    if(!avr_isp_worker_rw_detect_chip(instance)) {
+        FURI_LOG_E(TAG, "No detect AVR chip");
+    } else {
+        //upload file with description
+        do {
+            furi_string_printf(
+                file_path_name, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION);
+            if(!flipper_format_file_open_existing(
+                   flipper_format, furi_string_get_cstr(file_path_name))) {
+                FURI_LOG_E(TAG, "Error open file %s", furi_string_get_cstr(file_path_name));
+                break;
+            }
+
+            if(!flipper_format_read_header(flipper_format, temp_str_1, &temp_data32)) {
+                FURI_LOG_E(TAG, "Missing or incorrect header");
+                break;
+            }
+
+            if((!strcmp(furi_string_get_cstr(temp_str_1), AVR_ISP_APP_FILE_TYPE)) &&
+               temp_data32 == AVR_ISP_APP_FILE_VERSION) {
+            } else {
+                FURI_LOG_E(TAG, "Type or version mismatch");
+                break;
+            }
+
+            AvrIspSignature sig_read = {0};
+
+            if(!flipper_format_read_hex(
+                   flipper_format, "Signature", (uint8_t*)&sig_read, sizeof(AvrIspSignature))) {
+                FURI_LOG_E(TAG, "Missing Signature");
+                break;
+            }
+
+            if(memcmp(
+                   (uint8_t*)&instance->signature, (uint8_t*)&sig_read, sizeof(AvrIspSignature)) !=
+               0) {
+                FURI_LOG_E(
+                    TAG,
+                    "Wrong chip. Connected (%02X %02X %02X), read from file (%02X %02X %02X)",
+                    instance->signature.vendor,
+                    instance->signature.part_family,
+                    instance->signature.part_number,
+                    sig_read.vendor,
+                    sig_read.part_family,
+                    sig_read.part_number);
+                break;
+            }
+
+            if(!flipper_format_read_string(flipper_format, "Dump_flash", temp_str_1)) {
+                FURI_LOG_E(TAG, "Missing Dump_flash");
+                break;
+            }
+
+            //may not be
+            flipper_format_read_string(flipper_format, "Dump_eeprom", temp_str_2);
+            ret = true;
+        } while(false);
+    }
+    flipper_format_free(flipper_format);
+    furi_record_close(RECORD_STORAGE);
+
+    if(ret) {
+        do {
+            //checking .hex files for errors
+
+            furi_string_printf(
+                file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_1));
+
+            FURI_LOG_D(TAG, "Check flash file");
+            FlipperI32HexFile* flipper_hex_flash_read =
+                flipper_i32hex_file_open_read(furi_string_get_cstr(file_path_name));
+            if(flipper_i32hex_file_check(flipper_hex_flash_read)) {
+                FURI_LOG_D(TAG, "Check flash file: OK");
+            } else {
+                FURI_LOG_E(TAG, "Check flash file: Error");
+                ret = false;
+            }
+            flipper_i32hex_file_close(flipper_hex_flash_read);
+
+            if(furi_string_size(temp_str_2) > 4) {
+                furi_string_printf(
+                    file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_2));
+
+                FURI_LOG_D(TAG, "Check eeprom file");
+                FlipperI32HexFile* flipper_hex_eeprom_read =
+                    flipper_i32hex_file_open_read(furi_string_get_cstr(file_path_name));
+                if(flipper_i32hex_file_check(flipper_hex_eeprom_read)) {
+                    FURI_LOG_D(TAG, "Check eeprom file: OK");
+                } else {
+                    FURI_LOG_E(TAG, "Check eeprom file: Error");
+                    ret = false;
+                }
+                flipper_i32hex_file_close(flipper_hex_eeprom_read);
+            }
+
+            if(!ret) break;
+            ret = false;
+
+            //erase chip
+            FURI_LOG_D(TAG, "Erase chip");
+            if(!avr_isp_erase_chip(instance->avr_isp)) {
+                FURI_LOG_E(TAG, "Erase chip: Error");
+                break;
+            }
+
+            if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+                FURI_LOG_E(TAG, "Well, I managed to enter the mod program");
+                break;
+            }
+
+            //write flash
+            furi_string_printf(
+                file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_1));
+            avr_isp_worker_rw_write_flash(instance, furi_string_get_cstr(file_path_name));
+
+            //write eeprom
+            if(furi_string_size(temp_str_2) > 4) {
+                furi_string_printf(
+                    file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_2));
+                avr_isp_worker_rw_write_eeprom(instance, furi_string_get_cstr(file_path_name));
+            }
+            ret = true;
+            avr_isp_end_pmode(instance->avr_isp);
+        } while(false);
+    }
+
+    furi_string_free(file_path_name);
+    furi_string_free(temp_str_1);
+    furi_string_free(temp_str_2);
+
+    return ret;
+}
+
+void avr_isp_worker_rw_write_dump_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+
+    instance->file_path = file_path;
+    instance->file_name = file_name;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtWriting);
+}
+
+bool avr_isp_worker_rw_write_fuse(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+    furi_assert(file_path);
+    furi_assert(file_name);
+
+    FURI_LOG_D(TAG, "Write fuse chip");
+
+    bool ret = false;
+    uint8_t lfuse;
+    uint8_t hfuse;
+    uint8_t efuse;
+    uint8_t lock;
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
+    FuriString* temp_str = furi_string_alloc();
+
+    uint32_t temp_data32;
+
+    if(!avr_isp_worker_rw_detect_chip(instance)) {
+        FURI_LOG_E(TAG, "No detect AVR chip");
+    } else {
+        //upload file with description
+        do {
+            furi_string_printf(temp_str, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION);
+            if(!flipper_format_file_open_existing(flipper_format, furi_string_get_cstr(temp_str))) {
+                FURI_LOG_E(TAG, "Error open file %s", furi_string_get_cstr(temp_str));
+                break;
+            }
+
+            if(!flipper_format_read_header(flipper_format, temp_str, &temp_data32)) {
+                FURI_LOG_E(TAG, "Missing or incorrect header");
+                break;
+            }
+
+            if((!strcmp(furi_string_get_cstr(temp_str), AVR_ISP_APP_FILE_TYPE)) &&
+               temp_data32 == AVR_ISP_APP_FILE_VERSION) {
+            } else {
+                FURI_LOG_E(TAG, "Type or version mismatch");
+                break;
+            }
+
+            AvrIspSignature sig_read = {0};
+
+            if(!flipper_format_read_hex(
+                   flipper_format, "Signature", (uint8_t*)&sig_read, sizeof(AvrIspSignature))) {
+                FURI_LOG_E(TAG, "Missing Signature");
+                break;
+            }
+
+            if(memcmp(
+                   (uint8_t*)&instance->signature, (uint8_t*)&sig_read, sizeof(AvrIspSignature)) !=
+               0) {
+                FURI_LOG_E(
+                    TAG,
+                    "Wrong chip. Connected (%02X %02X %02X), read from file (%02X %02X %02X)",
+                    instance->signature.vendor,
+                    instance->signature.part_family,
+                    instance->signature.part_number,
+                    sig_read.vendor,
+                    sig_read.part_family,
+                    sig_read.part_number);
+                break;
+            }
+
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) {
+                if(!flipper_format_read_hex(flipper_format, "Lfuse", &lfuse, 1)) {
+                    FURI_LOG_E(TAG, "Missing Lfuse");
+                    break;
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) {
+                if(!flipper_format_read_hex(flipper_format, "Hfuse", &hfuse, 1)) {
+                    FURI_LOG_E(TAG, "Missing Hfuse");
+                    break;
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) {
+                if(!flipper_format_read_hex(flipper_format, "Efuse", &efuse, 1)) {
+                    FURI_LOG_E(TAG, "Missing Efuse");
+                    break;
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) {
+                if(!flipper_format_read_hex(flipper_format, "Lock", &lock, 1)) {
+                    FURI_LOG_E(TAG, "Missing Lock");
+                    break;
+                }
+            }
+
+            if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+                FURI_LOG_E(TAG, "Well, I managed to enter the mod program");
+                break;
+            }
+
+            ret = true;
+
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) {
+                if(instance->lfuse != lfuse) {
+                    if(!avr_isp_write_fuse_low(instance->avr_isp, lfuse)) {
+                        FURI_LOG_E(TAG, "Write Lfuse: error");
+                        ret = false;
+                    }
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) {
+                if(instance->hfuse != hfuse) {
+                    if(!avr_isp_write_fuse_high(instance->avr_isp, hfuse)) {
+                        FURI_LOG_E(TAG, "Write Hfuse: error");
+                        ret = false;
+                    }
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) {
+                if(instance->efuse != efuse) {
+                    if(!avr_isp_write_fuse_extended(instance->avr_isp, efuse)) {
+                        FURI_LOG_E(TAG, "Write Efuse: error");
+                        ret = false;
+                    }
+                }
+            }
+
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) {
+                FURI_LOG_D(TAG, "Write lock byte");
+                if(instance->lock != lock) {
+                    if(!avr_isp_write_lock_byte(instance->avr_isp, lock)) {
+                        FURI_LOG_E(TAG, "Write Lock byte: error");
+                        ret = false;
+                    }
+                }
+            }
+            avr_isp_end_pmode(instance->avr_isp);
+        } while(false);
+    }
+
+    flipper_format_free(flipper_format);
+    furi_record_close(RECORD_STORAGE);
+    furi_string_free(temp_str);
+    return ret;
+}
+
+void avr_isp_worker_rw_write_fuse_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+
+    instance->file_path = file_path;
+    instance->file_name = file_name;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtWritingFuse);
+}

+ 99 - 0
helpers/avr_isp_worker_rw.h

@@ -0,0 +1,99 @@
+#pragma once
+
+#include <furi_hal.h>
+
+typedef struct AvrIspWorkerRW AvrIspWorkerRW;
+
+typedef void (*AvrIspWorkerRWCallback)(
+    void* context,
+    const char* name,
+    bool detect_chip,
+    uint32_t flash_size);
+
+typedef enum {
+    AvrIspWorkerRWStatusILDE = 0,
+    AvrIspWorkerRWStatusEndReading = 1,
+    AvrIspWorkerRWStatusEndVerification = 2,
+    AvrIspWorkerRWStatusEndWriting = 3,
+    AvrIspWorkerRWStatusEndWritingFuse = 4,
+
+    AvrIspWorkerRWStatusErrorReading = (-1),
+    AvrIspWorkerRWStatusErrorVerification = (-2),
+    AvrIspWorkerRWStatusErrorWriting = (-3),
+    AvrIspWorkerRWStatusErrorWritingFuse = (-4),
+
+    AvrIspWorkerRWStatusReserved = 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization.
+} AvrIspWorkerRWStatus;
+
+typedef void (*AvrIspWorkerRWStatusCallback)(void* context, AvrIspWorkerRWStatus status);
+
+AvrIspWorkerRW* avr_isp_worker_rw_alloc(void* context);
+
+void avr_isp_worker_rw_free(AvrIspWorkerRW* instance);
+
+void avr_isp_worker_rw_start(AvrIspWorkerRW* instance);
+
+void avr_isp_worker_rw_stop(AvrIspWorkerRW* instance);
+
+bool avr_isp_worker_rw_is_running(AvrIspWorkerRW* instance);
+
+void avr_isp_worker_rw_set_callback(
+    AvrIspWorkerRW* instance,
+    AvrIspWorkerRWCallback callback,
+    void* context);
+
+void avr_isp_worker_rw_set_callback_status(
+    AvrIspWorkerRW* instance,
+    AvrIspWorkerRWStatusCallback callback_status,
+    void* context_status);
+
+bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance);
+
+float avr_isp_worker_rw_get_progress_flash(AvrIspWorkerRW* instance);
+
+float avr_isp_worker_rw_get_progress_eeprom(AvrIspWorkerRW* instance);
+
+bool avr_isp_worker_rw_read_dump(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+void avr_isp_worker_rw_read_dump_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+bool avr_isp_worker_rw_verification(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+void avr_isp_worker_rw_verification_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+bool avr_isp_worker_rw_check_hex(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+bool avr_isp_worker_rw_write_dump(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+void avr_isp_worker_rw_write_dump_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+bool avr_isp_worker_rw_write_fuse(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+void avr_isp_worker_rw_write_fuse_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);

+ 321 - 0
helpers/flipper_i32hex_file.c

@@ -0,0 +1,321 @@
+#include "flipper_i32hex_file.h"
+#include <string.h>
+#include <storage/storage.h>
+#include <toolbox/stream/stream.h>
+#include <toolbox/stream/file_stream.h>
+#include <toolbox/hex.h>
+
+//https://en.wikipedia.org/wiki/Intel_HEX
+
+#define TAG "FlipperI32HexFile"
+
+#define COUNT_BYTE_PAYLOAD 32 //how much payload will be used
+
+#define I32HEX_TYPE_DATA 0x00
+#define I32HEX_TYPE_END_OF_FILE 0x01
+#define I32HEX_TYPE_EXT_LINEAR_ADDR 0x04
+#define I32HEX_TYPE_START_LINEAR_ADDR 0x05
+
+struct FlipperI32HexFile {
+    uint32_t addr;
+    uint32_t addr_last;
+    Storage* storage;
+    Stream* stream;
+    FuriString* str_data;
+    FlipperI32HexFileStatus file_open;
+};
+
+FlipperI32HexFile* flipper_i32hex_file_open_write(const char* name, uint32_t start_addr) {
+    furi_assert(name);
+
+    FlipperI32HexFile* instance = malloc(sizeof(FlipperI32HexFile));
+    instance->addr = start_addr;
+    instance->addr_last = 0;
+    instance->storage = furi_record_open(RECORD_STORAGE);
+    instance->stream = file_stream_alloc(instance->storage);
+
+    if(file_stream_open(instance->stream, name, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+        instance->file_open = FlipperI32HexFileStatusOpenFileWrite;
+        FURI_LOG_D(TAG, "Open write file %s", name);
+    } else {
+        FURI_LOG_E(TAG, "Failed to open file %s", name);
+        instance->file_open = FlipperI32HexFileStatusErrorNoOpenFile;
+    }
+    instance->str_data = furi_string_alloc(instance->storage);
+
+    return instance;
+}
+
+FlipperI32HexFile* flipper_i32hex_file_open_read(const char* name) {
+    furi_assert(name);
+
+    FlipperI32HexFile* instance = malloc(sizeof(FlipperI32HexFile));
+    instance->addr = 0;
+    instance->addr_last = 0;
+    instance->storage = furi_record_open(RECORD_STORAGE);
+    instance->stream = file_stream_alloc(instance->storage);
+
+    if(file_stream_open(instance->stream, name, FSAM_READ, FSOM_OPEN_EXISTING)) {
+        instance->file_open = FlipperI32HexFileStatusOpenFileRead;
+        FURI_LOG_D(TAG, "Open read file %s", name);
+    } else {
+        FURI_LOG_E(TAG, "Failed to open file %s", name);
+        instance->file_open = FlipperI32HexFileStatusErrorNoOpenFile;
+    }
+    instance->str_data = furi_string_alloc(instance->storage);
+
+    return instance;
+}
+
+void flipper_i32hex_file_close(FlipperI32HexFile* instance) {
+    furi_assert(instance);
+
+    furi_string_free(instance->str_data);
+    file_stream_close(instance->stream);
+    stream_free(instance->stream);
+    furi_record_close(RECORD_STORAGE);
+}
+
+FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_data(
+    FlipperI32HexFile* instance,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+    furi_assert(data);
+
+    FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0};
+    if(instance->file_open != FlipperI32HexFileStatusOpenFileWrite) {
+        ret.status = FlipperI32HexFileStatusErrorFileWrite;
+    }
+    uint8_t count_byte = 0;
+    uint32_t ind = 0;
+    uint8_t crc = 0;
+
+    furi_string_reset(instance->str_data);
+
+    if((instance->addr_last & 0xFF0000) < (instance->addr & 0xFF0000)) {
+        crc = 0x02 + 0x04 + ((instance->addr >> 24) & 0xFF) + ((instance->addr >> 16) & 0xFF);
+        crc = 0x01 + ~crc;
+        //I32HEX_TYPE_EXT_LINEAR_ADDR
+        furi_string_cat_printf(
+            instance->str_data, ":02000004%04lX%02X\r\n", (instance->addr >> 16), crc);
+        instance->addr_last = instance->addr;
+    }
+
+    while(ind < data_size) {
+        if((ind + COUNT_BYTE_PAYLOAD) > data_size) {
+            count_byte = data_size - ind;
+        } else {
+            count_byte = COUNT_BYTE_PAYLOAD;
+        }
+        //I32HEX_TYPE_DATA
+        furi_string_cat_printf(
+            instance->str_data, ":%02X%04lX00", count_byte, (instance->addr & 0xFFFF));
+        crc = count_byte + ((instance->addr >> 8) & 0xFF) + (instance->addr & 0xFF);
+
+        for(uint32_t i = 0; i < count_byte; i++) {
+            furi_string_cat_printf(instance->str_data, "%02X", *data);
+            crc += *data++;
+        }
+        crc = 0x01 + ~crc;
+        furi_string_cat_printf(instance->str_data, "%02X\r\n", crc);
+
+        ind += count_byte;
+        instance->addr += count_byte;
+    }
+    if(instance->file_open) stream_write_string(instance->stream, instance->str_data);
+    return ret;
+}
+
+FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_end_line(FlipperI32HexFile* instance) {
+    furi_assert(instance);
+
+    FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0};
+    if(instance->file_open != FlipperI32HexFileStatusOpenFileWrite) {
+        ret.status = FlipperI32HexFileStatusErrorFileWrite;
+    }
+    furi_string_reset(instance->str_data);
+    //I32HEX_TYPE_END_OF_FILE
+    furi_string_cat_printf(instance->str_data, ":00000001FF\r\n");
+    if(instance->file_open) stream_write_string(instance->stream, instance->str_data);
+    return ret;
+}
+
+void flipper_i32hex_file_bin_to_i32hex_set_addr(FlipperI32HexFile* instance, uint32_t addr) {
+    furi_assert(instance);
+
+    instance->addr = addr;
+}
+
+const char* flipper_i32hex_file_get_string(FlipperI32HexFile* instance) {
+    furi_assert(instance);
+
+    return furi_string_get_cstr(instance->str_data);
+}
+
+static FlipperI32HexFileRet flipper_i32hex_file_parse_line(
+    FlipperI32HexFile* instance,
+    const char* str,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+    furi_assert(data);
+
+    char* str1;
+    uint32_t data_wrire_ind = 0;
+    uint32_t data_len = 0;
+    FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusErrorData, .data_size = 0};
+
+    //Search for start of data I32HEX
+    str1 = strstr(str, ":");
+    do {
+        if(str1 == NULL) {
+            ret.status = FlipperI32HexFileStatusErrorData;
+            break;
+        }
+        str1++;
+        if(!hex_char_to_uint8(*str1, str1[1], data + data_wrire_ind)) {
+            ret.status = FlipperI32HexFileStatusErrorData;
+            break;
+        }
+        str1++;
+        if(++data_wrire_ind > data_size) {
+            ret.status = FlipperI32HexFileStatusErrorOverflow;
+            break;
+        }
+        data_len = 5 + data[0]; // +5 bytes per header and crc
+        while(data_len > data_wrire_ind) {
+            str1++;
+            if(!hex_char_to_uint8(*str1, str1[1], data + data_wrire_ind)) {
+                ret.status = FlipperI32HexFileStatusErrorData;
+                break;
+            }
+            str1++;
+            if(++data_wrire_ind > data_size) {
+                ret.status = FlipperI32HexFileStatusErrorOverflow;
+                break;
+            }
+        }
+        ret.status = FlipperI32HexFileStatusOK;
+        ret.data_size = data_wrire_ind;
+
+    } while(0);
+    return ret;
+}
+
+static bool flipper_i32hex_file_check_data(uint8_t* data, uint32_t data_size) {
+    furi_assert(data);
+
+    uint8_t crc = 0;
+    uint32_t data_read_ind = 0;
+    if(data[0] > data_size) return false;
+    while(data_read_ind < data_size - 1) {
+        crc += data[data_read_ind++];
+    }
+    return data[data_size - 1] == ((1 + ~crc) & 0xFF);
+}
+
+static FlipperI32HexFileRet flipper_i32hex_file_parse(
+    FlipperI32HexFile* instance,
+    const char* str,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+    furi_assert(data);
+
+    FlipperI32HexFileRet ret = flipper_i32hex_file_parse_line(instance, str, data, data_size);
+
+    if((ret.status == FlipperI32HexFileStatusOK) && (ret.data_size > 4)) {
+        switch(data[3]) {
+        case I32HEX_TYPE_DATA:
+            if(flipper_i32hex_file_check_data(data, ret.data_size)) {
+                ret.data_size -= 5;
+                memcpy(data, data + 4, ret.data_size);
+                ret.status = FlipperI32HexFileStatusData;
+            } else {
+                ret.status = FlipperI32HexFileStatusErrorCrc;
+                ret.data_size = 0;
+            }
+            break;
+        case I32HEX_TYPE_END_OF_FILE:
+            if(flipper_i32hex_file_check_data(data, ret.data_size)) {
+                ret.status = FlipperI32HexFileStatusEofFile;
+                ret.data_size = 0;
+            } else {
+                ret.status = FlipperI32HexFileStatusErrorCrc;
+                ret.data_size = 0;
+            }
+            break;
+        case I32HEX_TYPE_EXT_LINEAR_ADDR:
+            if(flipper_i32hex_file_check_data(data, ret.data_size)) {
+                data[0] = data[4];
+                data[1] = data[5];
+                data[3] = 0;
+                data[4] = 0;
+                ret.status = FlipperI32HexFileStatusUdateAddr;
+                ret.data_size = 4;
+            } else {
+                ret.status = FlipperI32HexFileStatusErrorCrc;
+                ret.data_size = 0;
+            }
+            break;
+        case I32HEX_TYPE_START_LINEAR_ADDR:
+            ret.status = FlipperI32HexFileStatusErrorUnsupportedCommand;
+            ret.data_size = 0;
+            break;
+        default:
+            ret.status = FlipperI32HexFileStatusErrorUnsupportedCommand;
+            ret.data_size = 0;
+            break;
+        }
+    } else {
+        ret.status = FlipperI32HexFileStatusErrorData;
+        ret.data_size = 0;
+    }
+    return ret;
+}
+
+bool flipper_i32hex_file_check(FlipperI32HexFile* instance) {
+    furi_assert(instance);
+
+    uint32_t data_size = 280;
+    uint8_t data[280] = {0};
+    bool ret = true;
+
+    if(instance->file_open != FlipperI32HexFileStatusOpenFileRead) {
+        FURI_LOG_E(TAG, "File is not open");
+        ret = false;
+    } else {
+        stream_rewind(instance->stream);
+
+        while(stream_read_line(instance->stream, instance->str_data)) {
+            FlipperI32HexFileRet parse_ret = flipper_i32hex_file_parse(
+                instance, furi_string_get_cstr(instance->str_data), data, data_size);
+
+            if(parse_ret.status < 0) {
+                ret = false;
+            }
+        }
+        stream_rewind(instance->stream);
+    }
+    return ret;
+}
+
+FlipperI32HexFileRet flipper_i32hex_file_i32hex_to_bin_get_data(
+    FlipperI32HexFile* instance,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+    furi_assert(data);
+
+    FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0};
+    if(instance->file_open != FlipperI32HexFileStatusOpenFileRead) {
+        ret.status = FlipperI32HexFileStatusErrorFileRead;
+    } else {
+        stream_read_line(instance->stream, instance->str_data);
+        ret = flipper_i32hex_file_parse(
+            instance, furi_string_get_cstr(instance->str_data), data, data_size);
+    }
+
+    return ret;
+}

+ 55 - 0
helpers/flipper_i32hex_file.h

@@ -0,0 +1,55 @@
+#pragma once
+
+#include <furi_hal.h>
+
+typedef struct FlipperI32HexFile FlipperI32HexFile;
+
+typedef enum {
+    FlipperI32HexFileStatusOK = 0,
+    FlipperI32HexFileStatusData = 2,
+    FlipperI32HexFileStatusUdateAddr = 3,
+    FlipperI32HexFileStatusEofFile = 4,
+    FlipperI32HexFileStatusOpenFileWrite = 5,
+    FlipperI32HexFileStatusOpenFileRead = 6,
+
+    // Errors
+    FlipperI32HexFileStatusErrorCrc = (-1),
+    FlipperI32HexFileStatusErrorOverflow = (-2),
+    FlipperI32HexFileStatusErrorData = (-3),
+    FlipperI32HexFileStatusErrorUnsupportedCommand = (-4),
+    FlipperI32HexFileStatusErrorNoOpenFile = (-5),
+    FlipperI32HexFileStatusErrorFileWrite = (-6),
+    FlipperI32HexFileStatusErrorFileRead = (-7),
+
+    FlipperI32HexFileStatusReserved =
+        0x7FFFFFFF, ///< Prevents enum down-size compiler optimization.
+} FlipperI32HexFileStatus;
+
+typedef struct {
+    FlipperI32HexFileStatus status;
+    uint32_t data_size;
+} FlipperI32HexFileRet;
+
+FlipperI32HexFile* flipper_i32hex_file_open_write(const char* name, uint32_t start_addr);
+
+FlipperI32HexFile* flipper_i32hex_file_open_read(const char* name);
+
+void flipper_i32hex_file_close(FlipperI32HexFile* instance);
+
+FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_data(
+    FlipperI32HexFile* instance,
+    uint8_t* data,
+    uint32_t data_size);
+
+FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_end_line(FlipperI32HexFile* instance);
+
+const char* flipper_i32hex_file_get_string(FlipperI32HexFile* instance);
+
+void flipper_i32hex_file_bin_to_i32hex_set_addr(FlipperI32HexFile* instance, uint32_t addr);
+
+bool flipper_i32hex_file_check(FlipperI32HexFile* instance);
+
+FlipperI32HexFileRet flipper_i32hex_file_i32hex_to_bin_get_data(
+    FlipperI32HexFile* instance,
+    uint8_t* data,
+    uint32_t data_size);

BIN
images/avr_app_icon_10x10.png


BIN
images/avr_wiring.png


BIN
images/chif_not_found_83x37.png


BIN
images/chip_error_70x22.png


BIN
images/chip_long_70x22.png


BIN
images/chip_not_found_83x37.png


BIN
images/dolphin_nice_96x59.png


BIN
images/isp_active_128x53.png


BIN
images/link_waiting_77x56.png


+ 386 - 0
lib/driver/avr_isp_chip_arr.c

@@ -0,0 +1,386 @@
+#include "avr_isp_chip_arr.h"
+
+#include <furi.h>
+
+//https://github.com/avrdudes/avrdude/blob/master/src/avrintel.c
+
+const AvrIspChipArr avr_isp_chip_arr[] = {   // Value of -1 typically means unknown
+  //{mcu_name,       mcuid,  family, {sig,    na, ture}, flstart,  flsize, pgsiz, nb, bootsz, eestart, eesize, ep, rambeg, ramsiz, nf, nl,  ni}, // Source
+  {"ATtiny4",            0, F_AVR8L, {0x1E, 0x8F, 0x0A},       0, 0x00200, 0x010,  0,      0,       0,      0,  0, 0x0040, 0x0020,  1,  1,  10}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATtiny5",            1, F_AVR8L, {0x1E, 0x8F, 0x09},       0, 0x00200, 0x010,  0,      0,       0,      0,  0, 0x0040, 0x0020,  1,  1,  11}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATtiny9",            2, F_AVR8L, {0x1E, 0x90, 0x08},       0, 0x00400, 0x010,  0,      0,       0,      0,  0, 0x0040, 0x0020,  1,  1,  10}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATtiny10",           3, F_AVR8L, {0x1E, 0x90, 0x03},       0, 0x00400, 0x010,  0,      0,       0,      0,  0, 0x0040, 0x0020,  1,  1,  11}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATtiny20",           4, F_AVR8L, {0x1E, 0x91, 0x0F},       0, 0x00800, 0x020,  0,      0,       0,      0,  0, 0x0040, 0x0080,  1,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATtiny40",           5, F_AVR8L, {0x1E, 0x92, 0x0E},       0, 0x01000, 0x040,  0,      0,       0,      0,  0, 0x0040, 0x0100,  1,  1,  18}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATtiny102",          6, F_AVR8L, {0x1E, 0x90, 0x0C},       0, 0x00400, 0x010,  0,      0,       0,      0,  0, 0x0040, 0x0020,  1,  1,  16}, // atdf, avrdude, boot size (manual)
+  {"ATtiny104",          7, F_AVR8L, {0x1E, 0x90, 0x0B},       0, 0x00400, 0x010,  0,      0,       0,      0,  0, 0x0040, 0x0020,  1,  1,  16}, // atdf, avrdude, boot size (manual)
+
+  {"ATtiny11",           8,  F_AVR8, {0x1E, 0x90, 0x04},       0, 0x00400, 0x001,  0,      0,       0, 0x0040,  1, 0x0060, 0x0020,  1,  1,   5}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny12",           9,  F_AVR8, {0x1E, 0x90, 0x05},       0, 0x00400, 0x001,  0,      0,       0, 0x0040,  2, 0x0060, 0x0020,  1,  1,   6}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny13",          10,  F_AVR8, {0x1E, 0x90, 0x07},       0, 0x00400, 0x020,  0,      0,       0, 0x0040,  4, 0x0060, 0x0040,  2,  1,  10}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny13A",         11,  F_AVR8, {0x1E, 0x90, 0x07},       0, 0x00400, 0x020,  0,      0,       0, 0x0040,  4, 0x0060, 0x0040,  2,  1,  10}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny15",          12,  F_AVR8, {0x1E, 0x90, 0x06},       0, 0x00400, 0x001,  0,      0,       0, 0x0040,  2, 0x0060, 0x0020,  1,  1,   9}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny22",          13,  F_AVR8, {0x1E, 0x91, 0x06},       0, 0x00800,    -1,  0,      0,      -1,     -1, -1, 0x0060, 0x0080,  1,  1,   3}, // avr-gcc 12.2.0, boot size (manual)
+  {"ATtiny24",          14,  F_AVR8, {0x1E, 0x91, 0x0B},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny24A",         15,  F_AVR8, {0x1E, 0x91, 0x0B},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny25",          16,  F_AVR8, {0x1E, 0x91, 0x08},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  15}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny26",          17,  F_AVR8, {0x1E, 0x91, 0x09},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  2,  1,  12}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny28",          18,  F_AVR8, {0x1E, 0x91, 0x07},       0, 0x00800, 0x002,  0,      0,       0,      0,  0, 0x0060, 0x0020,  1,  1,   6}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny43U",         19,  F_AVR8, {0x1E, 0x92, 0x0C},       0, 0x01000, 0x040,  0,      0,       0, 0x0040,  4, 0x0060, 0x0100,  3,  1,  16}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny44",          20,  F_AVR8, {0x1E, 0x92, 0x07},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0060, 0x0100,  3,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny44A",         21,  F_AVR8, {0x1E, 0x92, 0x07},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0060, 0x0100,  3,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny45",          22,  F_AVR8, {0x1E, 0x92, 0x06},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0060, 0x0100,  3,  1,  15}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny48",          23,  F_AVR8, {0x1E, 0x92, 0x09},       0, 0x01000, 0x040,  0,      0,       0, 0x0040,  4, 0x0100, 0x0100,  3,  1,  20}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny84",          24,  F_AVR8, {0x1E, 0x93, 0x0C},       0, 0x02000, 0x040,  0,      0,       0, 0x0200,  4, 0x0060, 0x0200,  3,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny84A",         25,  F_AVR8, {0x1E, 0x93, 0x0C},       0, 0x02000, 0x040,  0,      0,       0, 0x0200,  4, 0x0060, 0x0200,  3,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny85",          26,  F_AVR8, {0x1E, 0x93, 0x0B},       0, 0x02000, 0x040,  0,      0,       0, 0x0200,  4, 0x0060, 0x0200,  3,  1,  15}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny87",          27,  F_AVR8, {0x1E, 0x93, 0x87},       0, 0x02000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny88",          28,  F_AVR8, {0x1E, 0x93, 0x11},       0, 0x02000, 0x040,  0,      0,       0, 0x0040,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny167",         29,  F_AVR8, {0x1E, 0x94, 0x87},       0, 0x04000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny261",         30,  F_AVR8, {0x1E, 0x91, 0x0C},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny261A",        31,  F_AVR8, {0x1E, 0x91, 0x0C},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny441",         32,  F_AVR8, {0x1E, 0x92, 0x15},       0, 0x01000, 0x010,  0,      0,       0, 0x0100,  4, 0x0100, 0x0100,  3,  1,  30}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny461",         33,  F_AVR8, {0x1E, 0x92, 0x08},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0060, 0x0100,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny461A",        34,  F_AVR8, {0x1E, 0x92, 0x08},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0060, 0x0100,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny828",         35,  F_AVR8, {0x1E, 0x93, 0x14},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny828R",        36,  F_AVR8, {0x1E, 0x93, 0x14},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  26}, // avrdude, from ATtiny828
+  {"ATtiny841",         37,  F_AVR8, {0x1E, 0x93, 0x15},       0, 0x02000, 0x010,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  30}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny861",         38,  F_AVR8, {0x1E, 0x93, 0x0D},       0, 0x02000, 0x040,  0,      0,       0, 0x0200,  4, 0x0060, 0x0200,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny861A",        39,  F_AVR8, {0x1E, 0x93, 0x0D},       0, 0x02000, 0x040,  0,      0,       0, 0x0200,  4, 0x0060, 0x0200,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1634",        40,  F_AVR8, {0x1E, 0x94, 0x12},       0, 0x04000, 0x020,  0,      0,       0, 0x0100,  4, 0x0100, 0x0400,  3,  1,  28}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1634R",       41,  F_AVR8, {0x1E, 0x94, 0x12},       0, 0x04000, 0x020,  0,      0,       0, 0x0100,  4, 0x0100, 0x0400,  3,  1,  28}, // avrdude, from ATtiny1634
+  {"ATtiny2313",        42,  F_AVR8, {0x1E, 0x91, 0x0A},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny2313A",       43,  F_AVR8, {0x1E, 0x91, 0x0A},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny4313",        44,  F_AVR8, {0x1E, 0x92, 0x0D},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0060, 0x0100,  3,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega8",           45,  F_AVR8, {0x1E, 0x93, 0x07},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0060, 0x0400,  2,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega8A",          46,  F_AVR8, {0x1E, 0x93, 0x07},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0060, 0x0400,  2,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega8HVA",        47,  F_AVR8, {0x1E, 0x93, 0x10},       0, 0x02000, 0x080,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  1,  1,  21}, // atdf, avr-gcc 12.2.0
+  {"ATmega8U2",         48,  F_AVR8, {0x1E, 0x93, 0x89},       0, 0x02000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  29}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega16",          49,  F_AVR8, {0x1E, 0x94, 0x03},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0060, 0x0400,  2,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega16A",         50,  F_AVR8, {0x1E, 0x94, 0x03},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0060, 0x0400,  2,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega16HVA",       51,  F_AVR8, {0x1E, 0x94, 0x0C},       0, 0x04000, 0x080,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  1,  1,  21}, // atdf, avr-gcc 12.2.0
+  {"ATmega16HVB",       52,  F_AVR8, {0x1E, 0x94, 0x0D},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0400,  2,  1,  29}, // atdf, avr-gcc 12.2.0
+  {"ATmega16HVBrevB",   53,  F_AVR8, {0x1E, 0x94, 0x0D},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0400,  2,  1,  29}, // atdf, avr-gcc 12.2.0
+  {"ATmega16M1",        54,  F_AVR8, {0x1E, 0x94, 0x84},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  31}, // atdf, avr-gcc 12.2.0
+  {"ATmega16HVA2",      55,  F_AVR8, {0x1E, 0x94, 0x0E},       0, 0x04000, 0x080, -1,     -1,      -1,     -1, -1, 0x0100, 0x0400,  2,  1,  22}, // avr-gcc 12.2.0
+  {"ATmega16U2",        56,  F_AVR8, {0x1E, 0x94, 0x89},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  29}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega16U4",        57,  F_AVR8, {0x1E, 0x94, 0x88},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0500,  3,  1,  43}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega32",          58,  F_AVR8, {0x1E, 0x95, 0x02},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0060, 0x0800,  2,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega32A",         59,  F_AVR8, {0x1E, 0x95, 0x02},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0060, 0x0800,  2,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega32HVB",       60,  F_AVR8, {0x1E, 0x95, 0x10},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  2,  1,  29}, // atdf, avr-gcc 12.2.0
+  {"ATmega32HVBrevB",   61,  F_AVR8, {0x1E, 0x95, 0x10},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  2,  1,  29}, // atdf, avr-gcc 12.2.0
+  {"ATmega32C1",        62,  F_AVR8, {0x1E, 0x95, 0x86},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  31}, // atdf, avr-gcc 12.2.0
+  {"ATmega32M1",        63,  F_AVR8, {0x1E, 0x95, 0x84},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega32U2",        64,  F_AVR8, {0x1E, 0x95, 0x8A},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0400,  3,  1,  29}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega32U4",        65,  F_AVR8, {0x1E, 0x95, 0x87},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0a00,  3,  1,  43}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega32U6",        66,  F_AVR8, {0x1E, 0x95, 0x88},       0, 0x08000, 0x080,  4, 0x0200,      -1,     -1, -1, 0x0100, 0x0a00,  3,  1,  38}, // avr-gcc 12.2.0, boot size (manual)
+  {"ATmega48",          67,  F_AVR8, {0x1E, 0x92, 0x05},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega48A",         68,  F_AVR8, {0x1E, 0x92, 0x05},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega48P",         69,  F_AVR8, {0x1E, 0x92, 0x0A},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega48PA",        70,  F_AVR8, {0x1E, 0x92, 0x0A},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega48PB",        71,  F_AVR8, {0x1E, 0x92, 0x10},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  27}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega64",          72,  F_AVR8, {0x1E, 0x96, 0x02},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  35}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega64A",         73,  F_AVR8, {0x1E, 0x96, 0x02},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  35}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega64HVE",       74,  F_AVR8, {0x1E, 0x96, 0x10},       0, 0x10000, 0x080,  4, 0x0400,      -1,     -1, -1, 0x0100, 0x1000,  2,  1,  25}, // avr-gcc 12.2.0, boot size (manual)
+  {"ATmega64C1",        75,  F_AVR8, {0x1E, 0x96, 0x86},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  31}, // atdf, avr-gcc 12.2.0
+  {"ATmega64M1",        76,  F_AVR8, {0x1E, 0x96, 0x84},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega64HVE2",      77,  F_AVR8, {0x1E, 0x96, 0x10},       0, 0x10000, 0x080,  4, 0x0400,       0, 0x0400,  4, 0x0100, 0x1000,  2,  1,  25}, // atdf, avr-gcc 12.2.0
+  {"ATmega64RFR2",      78,  F_AVR8, {0x1E, 0xA6, 0x02},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0200, 0x2000,  3,  1,  77}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega88",          79,  F_AVR8, {0x1E, 0x93, 0x0A},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega88A",         80,  F_AVR8, {0x1E, 0x93, 0x0A},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega88P",         81,  F_AVR8, {0x1E, 0x93, 0x0F},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega88PA",        82,  F_AVR8, {0x1E, 0x93, 0x0F},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega88PB",        83,  F_AVR8, {0x1E, 0x93, 0x16},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  27}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega103",         84,  F_AVR8, {0x1E, 0x97, 0x01},       0, 0x20000, 0x100,  0,      0,       0, 0x1000,  1, 0x0060, 0x0fa0,  1,  1,  24}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATmega128",         85,  F_AVR8, {0x1E, 0x97, 0x02},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x1000,  3,  1,  35}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega128A",        86,  F_AVR8, {0x1E, 0x97, 0x02},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x1000,  3,  1,  35}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega128RFA1",     87,  F_AVR8, {0x1E, 0xA7, 0x01},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x4000,  3,  1,  72}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega128RFR2",     88,  F_AVR8, {0x1E, 0xA7, 0x02},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x4000,  3,  1,  77}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega161",         89,  F_AVR8, {0x1E, 0x94, 0x01},       0, 0x04000, 0x080,  1, 0x0400,       0, 0x0200,  1, 0x0060, 0x0400,  1,  1,  21}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATmega162",         90,  F_AVR8, {0x1E, 0x94, 0x04},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  28}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega163",         91,  F_AVR8, {0x1E, 0x94, 0x02},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  1, 0x0060, 0x0400,  2,  1,  18}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATmega164A",        92,  F_AVR8, {0x1E, 0x94, 0x0F},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega164P",        93,  F_AVR8, {0x1E, 0x94, 0x0A},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega164PA",       94,  F_AVR8, {0x1E, 0x94, 0x0A},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega165",         95,  F_AVR8, {0x1E, 0x94, 0x10},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  22}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATmega165A",        96,  F_AVR8, {0x1E, 0x94, 0x10},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega165P",        97,  F_AVR8, {0x1E, 0x94, 0x07},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega165PA",       98,  F_AVR8, {0x1E, 0x94, 0x07},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega168",         99,  F_AVR8, {0x1E, 0x94, 0x06},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega168A",       100,  F_AVR8, {0x1E, 0x94, 0x06},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega168P",       101,  F_AVR8, {0x1E, 0x94, 0x0B},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega168PA",      102,  F_AVR8, {0x1E, 0x94, 0x0B},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega168PB",      103,  F_AVR8, {0x1E, 0x94, 0x15},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  27}, // atdf, avr-gcc 7.3.0, avrdude
+  {"ATmega169",        104,  F_AVR8, {0x1E, 0x94, 0x05},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  23}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATmega169A",       105,  F_AVR8, {0x1E, 0x94, 0x11},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega169P",       106,  F_AVR8, {0x1E, 0x94, 0x05},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega169PA",      107,  F_AVR8, {0x1E, 0x94, 0x05},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega256RFR2",    108,  F_AVR8, {0x1E, 0xA8, 0x02},       0, 0x40000, 0x100,  4, 0x0400,       0, 0x2000,  8, 0x0200, 0x8000,  3,  1,  77}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega323",        109,  F_AVR8, {0x1E, 0x95, 0x01},       0, 0x08000, 0x080,  4, 0x0200,      -1,     -1, -1, 0x0060, 0x0800,  2,  1,  21}, // avr-gcc 12.2.0, boot size (manual)
+  {"ATmega324A",       110,  F_AVR8, {0x1E, 0x95, 0x15},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega324P",       111,  F_AVR8, {0x1E, 0x95, 0x08},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega324PA",      112,  F_AVR8, {0x1E, 0x95, 0x11},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega324PB",      113,  F_AVR8, {0x1E, 0x95, 0x17},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  51}, // atdf, avrdude
+  {"ATmega325",        114,  F_AVR8, {0x1E, 0x95, 0x05},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega325A",       115,  F_AVR8, {0x1E, 0x95, 0x05},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega325P",       116,  F_AVR8, {0x1E, 0x95, 0x0D},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega325PA",      117,  F_AVR8, {0x1E, 0x95, 0x0D},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega328",        118,  F_AVR8, {0x1E, 0x95, 0x14},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega328P",       119,  F_AVR8, {0x1E, 0x95, 0x0F},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega328PB",      120,  F_AVR8, {0x1E, 0x95, 0x16},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  45}, // atdf, avr-gcc 7.3.0, avrdude
+  {"ATmega329",        121,  F_AVR8, {0x1E, 0x95, 0x03},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega329A",       122,  F_AVR8, {0x1E, 0x95, 0x03},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega329P",       123,  F_AVR8, {0x1E, 0x95, 0x0B},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega329PA",      124,  F_AVR8, {0x1E, 0x95, 0x0B},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega406",        125,  F_AVR8, {0x1E, 0x95, 0x07},       0, 0x0a000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0800,  2,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega640",        126,  F_AVR8, {0x1E, 0x96, 0x08},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x2000,  3,  1,  57}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega644",        127,  F_AVR8, {0x1E, 0x96, 0x09},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  28}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega644A",       128,  F_AVR8, {0x1E, 0x96, 0x09},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega644P",       129,  F_AVR8, {0x1E, 0x96, 0x0A},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega644PA",      130,  F_AVR8, {0x1E, 0x96, 0x0A},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega644RFR2",    131,  F_AVR8, {0x1E, 0xA6, 0x03},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0200, 0x2000,  3,  1,  77}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega645",        132,  F_AVR8, {0x1E, 0x96, 0x05},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega645A",       133,  F_AVR8, {0x1E, 0x96, 0x05},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega645P",       134,  F_AVR8, {0x1E, 0x96, 0x0D},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega649",        135,  F_AVR8, {0x1E, 0x96, 0x03},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega649A",       136,  F_AVR8, {0x1E, 0x96, 0x03},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega649P",       137,  F_AVR8, {0x1E, 0x96, 0x0B},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1280",       138,  F_AVR8, {0x1E, 0x97, 0x03},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x2000,  3,  1,  57}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1281",       139,  F_AVR8, {0x1E, 0x97, 0x04},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x2000,  3,  1,  57}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1284",       140,  F_AVR8, {0x1E, 0x97, 0x06},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x4000,  3,  1,  35}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1284P",      141,  F_AVR8, {0x1E, 0x97, 0x05},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x4000,  3,  1,  35}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1284RFR2",   142,  F_AVR8, {0x1E, 0xA7, 0x03},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x4000,  3,  1,  77}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega2560",       143,  F_AVR8, {0x1E, 0x98, 0x01},       0, 0x40000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x2000,  3,  1,  57}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega2561",       144,  F_AVR8, {0x1E, 0x98, 0x02},       0, 0x40000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x2000,  3,  1,  57}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega2564RFR2",   145,  F_AVR8, {0x1E, 0xA8, 0x03},       0, 0x40000, 0x100,  4, 0x0400,       0, 0x2000,  8, 0x0200, 0x8000,  3,  1,  77}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3250",       146,  F_AVR8, {0x1E, 0x95, 0x06},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3250A",      147,  F_AVR8, {0x1E, 0x95, 0x06},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3250P",      148,  F_AVR8, {0x1E, 0x95, 0x0E},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3250PA",     149,  F_AVR8, {0x1E, 0x95, 0x0E},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3290",       150,  F_AVR8, {0x1E, 0x95, 0x04},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3290A",      151,  F_AVR8, {0x1E, 0x95, 0x04},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3290P",      152,  F_AVR8, {0x1E, 0x95, 0x0C},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3290PA",     153,  F_AVR8, {0x1E, 0x95, 0x0C},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega6450",       154,  F_AVR8, {0x1E, 0x96, 0x06},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega6450A",      155,  F_AVR8, {0x1E, 0x96, 0x06},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega6450P",      156,  F_AVR8, {0x1E, 0x96, 0x0E},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega6490",       157,  F_AVR8, {0x1E, 0x96, 0x04},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega6490A",      158,  F_AVR8, {0x1E, 0x96, 0x04},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega6490P",      159,  F_AVR8, {0x1E, 0x96, 0x0C},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega8515",       160,  F_AVR8, {0x1E, 0x93, 0x06},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0060, 0x0200,  2,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega8535",       161,  F_AVR8, {0x1E, 0x93, 0x08},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0060, 0x0200,  2,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT43USB320",       162,  F_AVR8, {0xff,   -1,   -1},       0, 0x10000,    -1, -1,     -1,      -1,     -1, -1, 0x0060, 0x0200, -1, -1,   0}, // avr-gcc 12.2.0
+  {"AT43USB355",       163,  F_AVR8, {0xff,   -1,   -1},       0, 0x06000,    -1, -1,     -1,      -1,     -1, -1, 0x0060, 0x0400, -1, -1,   0}, // avr-gcc 12.2.0
+  {"AT76C711",         164,  F_AVR8, {0xff,   -1,   -1},       0, 0x04000,    -1, -1,     -1,      -1,     -1, -1, 0x0060, 0x07a0, -1, -1,   0}, // avr-gcc 12.2.0
+  {"AT86RF401",        165,  F_AVR8, {0x1E, 0x91, 0x81},       0, 0x00800,    -1, -1,     -1,      -1,     -1, -1, 0x0060, 0x0080,  0,  1,   3}, // avr-gcc 12.2.0
+  {"AT90PWM1",         166,  F_AVR8, {0x1E, 0x93, 0x83},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  32}, // atdf, avr-gcc 12.2.0
+  {"AT90PWM2",         167,  F_AVR8, {0x1E, 0x93, 0x81},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  32}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90PWM2B",        168,  F_AVR8, {0x1E, 0x93, 0x83},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  32}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90PWM3",         169,  F_AVR8, {0x1E, 0x93, 0x81},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  32}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90PWM3B",        170,  F_AVR8, {0x1E, 0x93, 0x83},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  32}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90CAN32",        171,  F_AVR8, {0x1E, 0x95, 0x81},       0, 0x08000, 0x100,  4, 0x0400,       0, 0x0400,  8, 0x0100, 0x0800,  3,  1,  37}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90CAN64",        172,  F_AVR8, {0x1E, 0x96, 0x81},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  37}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90PWM81",        173,  F_AVR8, {0x1E, 0x93, 0x88},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0100,  3,  1,  20}, // atdf, avr-gcc 12.2.0
+  {"AT90USB82",        174,  F_AVR8, {0x1E, 0x93, 0x82},       0, 0x02000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  29}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90SCR100",       175,  F_AVR8, {0x1E, 0x96, 0xC1},       0, 0x10000, 0x100,  4, 0x0200,      -1,     -1, -1, 0x0100, 0x1000,  3,  1,  38}, // avr-gcc 12.2.0, boot size (manual)
+  {"AT90CAN128",       176,  F_AVR8, {0x1E, 0x97, 0x81},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x1000,  3,  1,  37}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90PWM161",       177,  F_AVR8, {0x1E, 0x94, 0x8B},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  20}, // atdf, avr-gcc 12.2.0
+  {"AT90USB162",       178,  F_AVR8, {0x1E, 0x94, 0x82},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  29}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90PWM216",       179,  F_AVR8, {0x1E, 0x94, 0x83},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  32}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90PWM316",       180,  F_AVR8, {0x1E, 0x94, 0x83},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  32}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90USB646",       181,  F_AVR8, {0x1E, 0x96, 0x82},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  38}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90USB647",       182,  F_AVR8, {0x1E, 0x96, 0x82},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  38}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90S1200",        183,  F_AVR8, {0x1E, 0x90, 0x01},       0, 0x00400, 0x001,  0,      0,       0, 0x0040,  1, 0x0060, 0x0020,  1,  1,   4}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90USB1286",      184,  F_AVR8, {0x1E, 0x97, 0x82},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x2000,  3,  1,  38}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90USB1287",      185,  F_AVR8, {0x1E, 0x97, 0x82},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x2000,  3,  1,  38}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90S2313",        186,  F_AVR8, {0x1E, 0x91, 0x01},       0, 0x00800, 0x001,  0,      0,       0, 0x0080,  1, 0x0060, 0x0080,  1,  1,  11}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90S2323",        187,  F_AVR8, {0x1E, 0x91, 0x02},       0, 0x00800,    -1,  0,      0,      -1,     -1, -1, 0x0060, 0x0080,  1,  1,   3}, // avr-gcc 12.2.0, boot size (manual)
+  {"AT90S2333",        188,  F_AVR8, {0x1E, 0x91, 0x05},       0, 0x00800, 0x001,  0,      0,       0, 0x0080,  1, 0x0060, 0x0080, -1, -1,  14}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90S2343",        189,  F_AVR8, {0x1E, 0x91, 0x03},       0, 0x00800, 0x001,  0,      0,       0, 0x0080,  1, 0x0060, 0x0080,  1,  1,   3}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90S4414",        190,  F_AVR8, {0x1E, 0x92, 0x01},       0, 0x01000, 0x001,  0,      0,       0, 0x0100,  1, 0x0060, 0x0100,  1,  1,  13}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90S4433",        191,  F_AVR8, {0x1E, 0x92, 0x03},       0, 0x01000, 0x001,  0,      0,       0, 0x0100,  1, 0x0060, 0x0080,  1,  1,  14}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90S4434",        192,  F_AVR8, {0x1E, 0x92, 0x02},       0, 0x01000, 0x001,  0,      0,       0, 0x0100,  1, 0x0060, 0x0100,  1,  1,  17}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90S8515",        193,  F_AVR8, {0x1E, 0x93, 0x01},       0, 0x02000, 0x001,  0,      0,       0, 0x0200,  1, 0x0060, 0x0200,  1,  1,  13}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90C8534",        194,  F_AVR8, {0xff,   -1,   -1},       0, 0x02000,    -1, -1,     -1,      -1,     -1, -1, 0x0060, 0x0100, -1, -1,   0}, // avr-gcc 12.2.0
+  {"AT90S8535",        195,  F_AVR8, {0x1E, 0x93, 0x03},       0, 0x02000, 0x001,  0,      0,       0, 0x0200,  1, 0x0060, 0x0200,  1,  1,  17}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT94K",            196,  F_AVR8, {0xff,   -1,   -1},       0, 0x08000,    -1, -1,     -1,      -1,     -1, -1, 0x0060, 0x0fa0, -1, -1,   0}, // avr-gcc 12.2.0
+  {"ATA5272",          197,  F_AVR8, {0x1E, 0x93, 0x87},       0, 0x02000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  37}, // atdf, avr-gcc 12.2.0
+  {"ATA5505",          198,  F_AVR8, {0x1E, 0x94, 0x87},       0, 0x04000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0
+  {"ATA5700M322",      199,  F_AVR8, {0x1E, 0x95, 0x67}, 0x08000, 0x08000, 0x040,  0,      0,       0, 0x0880, 16, 0x0200, 0x0400,  1,  1,  51}, // atdf
+  {"ATA5702M322",      200,  F_AVR8, {0x1E, 0x95, 0x69}, 0x08000, 0x08000, 0x040,  0,      0,       0, 0x0880, 16, 0x0200, 0x0400,  1,  1,  51}, // atdf, avr-gcc 12.2.0
+  {"ATA5781",          201,  F_AVR8, {0x1E, 0x95, 0x64},      -1,      -1,    -1,  0,      0,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf
+  {"ATA5782",          202,  F_AVR8, {0x1E, 0x95, 0x65}, 0x08000, 0x05000, 0x040,  1, 0x5000,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf, avr-gcc 12.2.0
+  {"ATA5783",          203,  F_AVR8, {0x1E, 0x95, 0x66},      -1,      -1,    -1,  0,      0,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf
+  {"ATA5787",          204,  F_AVR8, {0x1E, 0x94, 0x6C}, 0x08000, 0x05200, 0x040,  0,      0,       0, 0x0400, 16, 0x0200, 0x0800,  1,  1,  44}, // atdf
+  {"ATA5790",          205,  F_AVR8, {0x1E, 0x94, 0x61},       0, 0x04000, 0x080,  1, 0x0800,       0, 0x0800, 16, 0x0100, 0x0200,  1,  1,  30}, // atdf, avr-gcc 12.2.0
+  {"ATA5790N",         206,  F_AVR8, {0x1E, 0x94, 0x62},       0, 0x04000, 0x080,  1, 0x0800,       0, 0x0800, 16, 0x0100, 0x0200,  1,  1,  31}, // atdf, avr-gcc 12.2.0
+  {"ATA5791",          207,  F_AVR8, {0x1E, 0x94, 0x62},       0, 0x04000, 0x080,  1, 0x0800,       0, 0x0800, 16, 0x0100, 0x0200,  1,  1,  31}, // atdf, avr-gcc 7.3.0
+  {"ATA5795",          208,  F_AVR8, {0x1E, 0x93, 0x61},       0, 0x02000, 0x040,  1, 0x0800,       0, 0x0800, 16, 0x0100, 0x0200,  1,  1,  23}, // atdf, avr-gcc 12.2.0
+  {"ATA5831",          209,  F_AVR8, {0x1E, 0x95, 0x61}, 0x08000, 0x05000, 0x040,  1, 0x5000,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf, avr-gcc 12.2.0
+  {"ATA5832",          210,  F_AVR8, {0x1E, 0x95, 0x62},      -1,      -1,    -1,  0,      0,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf
+  {"ATA5833",          211,  F_AVR8, {0x1E, 0x95, 0x63},      -1,      -1,    -1,  0,      0,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf
+  {"ATA5835",          212,  F_AVR8, {0x1E, 0x94, 0x6B}, 0x08000, 0x05200, 0x040,  0,      0,       0, 0x0400, 16, 0x0200, 0x0800,  1,  1,  44}, // atdf
+  {"ATA6285",          213,  F_AVR8, {0x1E, 0x93, 0x82},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0140,  4, 0x0100, 0x0200,  2,  1,  27}, // atdf, avr-gcc 12.2.0
+  {"ATA6286",          214,  F_AVR8, {0x1E, 0x93, 0x82},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0140,  4, 0x0100, 0x0200,  2,  1,  27}, // atdf, avr-gcc 12.2.0
+  {"ATA6289",          215,  F_AVR8, {0x1E, 0x93, 0x82},       0, 0x02000, 0x040,  4, 0x0100,      -1,     -1, -1, 0x0100, 0x0200,  2,  1,  27}, // avr-gcc 12.2.0, boot size (manual)
+  {"ATA6612C",         216,  F_AVR8, {0x1E, 0x93, 0x0A},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0
+  {"ATA6613C",         217,  F_AVR8, {0x1E, 0x94, 0x06},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0
+  {"ATA6614Q",         218,  F_AVR8, {0x1E, 0x95, 0x0F},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  26}, // atdf, avr-gcc 12.2.0
+  {"ATA6616C",         219,  F_AVR8, {0x1E, 0x93, 0x87},       0, 0x02000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0
+  {"ATA6617C",         220,  F_AVR8, {0x1E, 0x94, 0x87},       0, 0x04000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0
+  {"ATA8210",          221,  F_AVR8, {0x1E, 0x95, 0x65}, 0x08000, 0x05000, 0x040,  1, 0x5000,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf, avr-gcc 7.3.0
+  {"ATA8215",          222,  F_AVR8, {0x1E, 0x95, 0x64},      -1,      -1,    -1,  0,      0,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf
+  {"ATA8510",          223,  F_AVR8, {0x1E, 0x95, 0x61}, 0x08000, 0x05000, 0x040,  1, 0x5000,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf, avr-gcc 7.3.0
+  {"ATA8515",          224,  F_AVR8, {0x1E, 0x95, 0x63},      -1,      -1,    -1,  0,      0,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf
+  {"ATA664251",        225,  F_AVR8, {0x1E, 0x94, 0x87},       0, 0x04000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0
+  {"M3000",            226,  F_AVR8, {0xff,   -1,   -1},       0, 0x10000,    -1, -1,     -1,      -1,     -1, -1, 0x1000, 0x1000, -1, -1,   0}, // avr-gcc 12.2.0
+  {"LGT8F88P",         227,  F_AVR8, {0x1E, 0x93, 0x0F},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // avrdude, from ATmega88
+  {"LGT8F168P",        228,  F_AVR8, {0x1E, 0x94, 0x0B},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // avrdude, from ATmega168P
+  {"LGT8F328P",        229,  F_AVR8, {0x1E, 0x95, 0x0F},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  26}, // avrdude, from ATmega328P
+
+  {"ATxmega8E5",       230, F_XMEGA, {0x1E, 0x93, 0x41},       0, 0x02800, 0x080,  1, 0x0800,       0, 0x0200, 32, 0x2000, 0x0400,  7,  1,  43}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega16A4",      231, F_XMEGA, {0x1E, 0x94, 0x41},       0, 0x05000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x0800,  6,  1,  94}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega16A4U",     232, F_XMEGA, {0x1E, 0x94, 0x41},       0, 0x05000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x0800,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega16C4",      233, F_XMEGA, {0x1E, 0x94, 0x43},       0, 0x05000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x0800,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega16D4",      234, F_XMEGA, {0x1E, 0x94, 0x42},       0, 0x05000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x0800,  6,  1,  91}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega16E5",      235, F_XMEGA, {0x1E, 0x94, 0x45},       0, 0x05000, 0x080,  1, 0x1000,       0, 0x0200, 32, 0x2000, 0x0800,  7,  1,  43}, // atdf, avr-gcc 7.3.0, avrdude
+  {"ATxmega32C3",      236, F_XMEGA, {0x1E, 0x95, 0x49},       0, 0x09000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0
+  {"ATxmega32D3",      237, F_XMEGA, {0x1E, 0x95, 0x4A},       0, 0x09000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  6,  1, 114}, // atdf, avr-gcc 12.2.0
+  {"ATxmega32A4",      238, F_XMEGA, {0x1E, 0x95, 0x41},       0, 0x09000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  6,  1,  94}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega32A4U",     239, F_XMEGA, {0x1E, 0x95, 0x41},       0, 0x09000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega32C4",      240, F_XMEGA, {0x1E, 0x95, 0x44},       0, 0x09000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega32D4",      241, F_XMEGA, {0x1E, 0x95, 0x42},       0, 0x09000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  6,  1,  91}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega32E5",      242, F_XMEGA, {0x1E, 0x95, 0x4C},       0, 0x09000, 0x080,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  7,  1,  43}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64A1",      243, F_XMEGA, {0x1E, 0x96, 0x4E},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 125}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64A1U",     244, F_XMEGA, {0x1E, 0x96, 0x4E},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64B1",      245, F_XMEGA, {0x1E, 0x96, 0x52},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1,  81}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64A3",      246, F_XMEGA, {0x1E, 0x96, 0x42},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64A3U",     247, F_XMEGA, {0x1E, 0x96, 0x42},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64B3",      248, F_XMEGA, {0x1E, 0x96, 0x51},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1,  54}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64C3",      249, F_XMEGA, {0x1E, 0x96, 0x49},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64D3",      250, F_XMEGA, {0x1E, 0x96, 0x4A},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64A4",      251, F_XMEGA, {0x1E, 0x96, 0x46},       0, 0x11000, 0x100, -1,     -1,       0, 0x0800, 32,     -1,     -1, -1, -1,   0}, // avrdude
+  {"ATxmega64A4U",     252, F_XMEGA, {0x1E, 0x96, 0x46},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64D4",      253, F_XMEGA, {0x1E, 0x96, 0x47},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1,  91}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128A1",     254, F_XMEGA, {0x1E, 0x97, 0x4C},       0, 0x22000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 125}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128A1revD", 255, F_XMEGA, {0x1E, 0x97, 0x41},       0, 0x22000, 0x200, -1,     -1,       0, 0x0800, 32,     -1,     -1, -1, -1,   0}, // avrdude
+  {"ATxmega128A1U",    256, F_XMEGA, {0x1E, 0x97, 0x4C},       0, 0x22000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128B1",     257, F_XMEGA, {0x1E, 0x97, 0x4D},       0, 0x22000, 0x100,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1,  81}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128A3",     258, F_XMEGA, {0x1E, 0x97, 0x42},       0, 0x22000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128A3U",    259, F_XMEGA, {0x1E, 0x97, 0x42},       0, 0x22000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128B3",     260, F_XMEGA, {0x1E, 0x97, 0x4B},       0, 0x22000, 0x100,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1,  54}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128C3",     261, F_XMEGA, {0x1E, 0x97, 0x52},       0, 0x22000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128D3",     262, F_XMEGA, {0x1E, 0x97, 0x48},       0, 0x22000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128A4",     263, F_XMEGA, {0x1E, 0x97, 0x46},       0, 0x22000, 0x200, -1,     -1,       0, 0x0800, 32,     -1,     -1, -1, -1,   0}, // avrdude
+  {"ATxmega128A4U",    264, F_XMEGA, {0x1E, 0x97, 0x46},       0, 0x22000, 0x100,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128D4",     265, F_XMEGA, {0x1E, 0x97, 0x47},       0, 0x22000, 0x100,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1,  91}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega192A1",     266, F_XMEGA, {0x1E, 0x97, 0x4E},       0, 0x32000, 0x200, -1,     -1,       0, 0x0800, 32,     -1,     -1, -1, -1,   0}, // avrdude
+  {"ATxmega192A3",     267, F_XMEGA, {0x1E, 0x97, 0x44},       0, 0x32000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x4000,  6,  1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega192A3U",    268, F_XMEGA, {0x1E, 0x97, 0x44},       0, 0x32000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x4000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega192C3",     269, F_XMEGA, {0x1E, 0x97, 0x51},       0, 0x32000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x4000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega192D3",     270, F_XMEGA, {0x1E, 0x97, 0x49},       0, 0x32000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x4000,  6,  1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega256A1",     271, F_XMEGA, {0x1E, 0x98, 0x46},       0, 0x42000, 0x200, -1,     -1,       0, 0x1000, 32,     -1,     -1, -1, -1,   0}, // avrdude
+  {"ATxmega256A3",     272, F_XMEGA, {0x1E, 0x98, 0x42},       0, 0x42000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x4000,  6,  1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega256A3B",    273, F_XMEGA, {0x1E, 0x98, 0x43},       0, 0x42000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x4000,  6,  1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega256A3BU",   274, F_XMEGA, {0x1E, 0x98, 0x43},       0, 0x42000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x4000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega256A3U",    275, F_XMEGA, {0x1E, 0x98, 0x42},       0, 0x42000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x4000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega256C3",     276, F_XMEGA, {0x1E, 0x98, 0x46},       0, 0x42000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x4000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega256D3",     277, F_XMEGA, {0x1E, 0x98, 0x44},       0, 0x42000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x4000,  6,  1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega384C3",     278, F_XMEGA, {0x1E, 0x98, 0x45},       0, 0x62000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x8000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega384D3",     279, F_XMEGA, {0x1E, 0x98, 0x47},       0, 0x62000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x8000,  6,  1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+
+  {"ATtiny202",        280, F_AVR8X, {0x1E, 0x91, 0x23},       0, 0x00800, 0x040,  1,      0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny204",        281, F_AVR8X, {0x1E, 0x91, 0x22},       0, 0x00800, 0x040,  1,      0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny212",        282, F_AVR8X, {0x1E, 0x91, 0x21},       0, 0x00800, 0x040,  1,      0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny214",        283, F_AVR8X, {0x1E, 0x91, 0x20},       0, 0x00800, 0x040,  1,      0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny402",        284, F_AVR8X, {0x1E, 0x92, 0x27},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny404",        285, F_AVR8X, {0x1E, 0x92, 0x26},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny406",        286, F_AVR8X, {0x1E, 0x92, 0x25},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny412",        287, F_AVR8X, {0x1E, 0x92, 0x23},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny414",        288, F_AVR8X, {0x1E, 0x92, 0x22},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny416",        289, F_AVR8X, {0x1E, 0x92, 0x21},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny416auto",    290, F_AVR8X, {0x1E, 0x92, 0x28},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf
+  {"ATtiny417",        291, F_AVR8X, {0x1E, 0x92, 0x20},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny424",        292, F_AVR8X, {0x1E, 0x92, 0x2C},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny426",        293, F_AVR8X, {0x1E, 0x92, 0x2B},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny427",        294, F_AVR8X, {0x1E, 0x92, 0x2A},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny804",        295, F_AVR8X, {0x1E, 0x93, 0x25},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny806",        296, F_AVR8X, {0x1E, 0x93, 0x24},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny807",        297, F_AVR8X, {0x1E, 0x93, 0x23},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny814",        298, F_AVR8X, {0x1E, 0x93, 0x22},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny816",        299, F_AVR8X, {0x1E, 0x93, 0x21},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny817",        300, F_AVR8X, {0x1E, 0x93, 0x20},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny824",        301, F_AVR8X, {0x1E, 0x93, 0x29},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny826",        302, F_AVR8X, {0x1E, 0x93, 0x28},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny827",        303, F_AVR8X, {0x1E, 0x93, 0x27},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny1604",       304, F_AVR8X, {0x1E, 0x94, 0x25},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1606",       305, F_AVR8X, {0x1E, 0x94, 0x24},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1607",       306, F_AVR8X, {0x1E, 0x94, 0x23},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1614",       307, F_AVR8X, {0x1E, 0x94, 0x22},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1616",       308, F_AVR8X, {0x1E, 0x94, 0x21},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1617",       309, F_AVR8X, {0x1E, 0x94, 0x20},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1624",       310, F_AVR8X, {0x1E, 0x94, 0x2A},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny1626",       311, F_AVR8X, {0x1E, 0x94, 0x29},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny1627",       312, F_AVR8X, {0x1E, 0x94, 0x28},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny3214",       313, F_AVR8X, {0x1E, 0x95, 0x20},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10,  1,  31}, // avr-gcc 12.2.0
+  {"ATtiny3216",       314, F_AVR8X, {0x1E, 0x95, 0x21},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny3217",       315, F_AVR8X, {0x1E, 0x95, 0x22},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny3224",       316, F_AVR8X, {0x1E, 0x95, 0x28},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny3226",       317, F_AVR8X, {0x1E, 0x95, 0x27},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny3227",       318, F_AVR8X, {0x1E, 0x95, 0x26},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10,  1,  30}, // atdf, avrdude
+  {"ATmega808",        319, F_AVR8X, {0x1E, 0x93, 0x26},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10,  1,  36}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega809",        320, F_AVR8X, {0x1E, 0x93, 0x2A},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10,  1,  40}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1608",       321, F_AVR8X, {0x1E, 0x94, 0x27},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  36}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1609",       322, F_AVR8X, {0x1E, 0x94, 0x26},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  40}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3208",       323, F_AVR8X, {0x1E, 0x95, 0x30},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3000, 0x1000, 10,  1,  36}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3209",       324, F_AVR8X, {0x1E, 0x95, 0x31},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3000, 0x1000, 10,  1,  40}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega4808",       325, F_AVR8X, {0x1E, 0x96, 0x50},       0, 0x0c000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x2800, 0x1800, 10,  1,  36}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega4809",       326, F_AVR8X, {0x1E, 0x96, 0x51},       0, 0x0c000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x2800, 0x1800, 10,  1,  40}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AVR8EA28",         327, F_AVR8X, {0x1E, 0x93, 0x2C},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR8EA32",         328, F_AVR8X, {0x1E, 0x93, 0x2B},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR16DD14",        329, F_AVR8X, {0x1E, 0x94, 0x34},       0, 0x04000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7800, 0x0800, 16,  4,  36}, // atdf, avrdude
+  {"AVR16DD20",        330, F_AVR8X, {0x1E, 0x94, 0x33},       0, 0x04000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7800, 0x0800, 16,  4,  36}, // atdf, avrdude
+  {"AVR16DD28",        331, F_AVR8X, {0x1E, 0x94, 0x32},       0, 0x04000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7800, 0x0800, 16,  4,  36}, // atdf, avrdude
+  {"AVR16EA28",        332, F_AVR8X, {0x1E, 0x94, 0x37},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR16DD32",        333, F_AVR8X, {0x1E, 0x94, 0x31},       0, 0x04000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7800, 0x0800, 16,  4,  36}, // atdf, avrdude
+  {"AVR16EA32",        334, F_AVR8X, {0x1E, 0x94, 0x36},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR16EA48",        335, F_AVR8X, {0x1E, 0x94, 0x35},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR32DD14",        336, F_AVR8X, {0x1E, 0x95, 0x3B},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7000, 0x1000, 16,  4,  36}, // atdf, avrdude
+  {"AVR32DD20",        337, F_AVR8X, {0x1E, 0x95, 0x3A},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7000, 0x1000, 16,  4,  36}, // atdf, avrdude
+  {"AVR32DA28",        338, F_AVR8X, {0x1E, 0x95, 0x34},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x7000, 0x1000, 16,  4,  41}, // atdf, avrdude
+  {"AVR32DB28",        339, F_AVR8X, {0x1E, 0x95, 0x37},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x7000, 0x1000, 16,  4,  42}, // atdf, avrdude
+  {"AVR32DD28",        340, F_AVR8X, {0x1E, 0x95, 0x39},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7000, 0x1000, 16,  4,  36}, // atdf, avrdude
+  {"AVR32EA28",        341, F_AVR8X, {0x1E, 0x95, 0x3E},       0, 0x08000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR32DA32",        342, F_AVR8X, {0x1E, 0x95, 0x33},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x7000, 0x1000, 16,  4,  44}, // atdf, avrdude
+  {"AVR32DB32",        343, F_AVR8X, {0x1E, 0x95, 0x36},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x7000, 0x1000, 16,  4,  44}, // atdf, avrdude
+  {"AVR32DD32",        344, F_AVR8X, {0x1E, 0x95, 0x38},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7000, 0x1000, 16,  4,  36}, // atdf, avrdude
+  {"AVR32EA32",        345, F_AVR8X, {0x1E, 0x95, 0x3D},       0, 0x08000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR32DA48",        346, F_AVR8X, {0x1E, 0x95, 0x32},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x7000, 0x1000, 16,  4,  58}, // atdf, avrdude
+  {"AVR32DB48",        347, F_AVR8X, {0x1E, 0x95, 0x35},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x7000, 0x1000, 16,  4,  61}, // atdf, avrdude
+  {"AVR32EA48",        348, F_AVR8X, {0x1E, 0x95, 0x3C},       0, 0x08000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR64DD14",        349, F_AVR8X, {0x1E, 0x96, 0x1D},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x6000, 0x2000, 16,  4,  36}, // atdf, avrdude
+  {"AVR64DD20",        350, F_AVR8X, {0x1E, 0x96, 0x1C},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x6000, 0x2000, 16,  4,  36}, // atdf, avrdude
+  {"AVR64DA28",        351, F_AVR8X, {0x1E, 0x96, 0x15},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  41}, // atdf, avrdude
+  {"AVR64DB28",        352, F_AVR8X, {0x1E, 0x96, 0x19},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  42}, // atdf, avrdude
+  {"AVR64DD28",        353, F_AVR8X, {0x1E, 0x96, 0x1B},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x6000, 0x2000, 16,  4,  36}, // atdf, avrdude
+  {"AVR64EA28",        354, F_AVR8X, {0x1E, 0x96, 0x20},       0, 0x10000, 0x080,  1,      0, 0x01400, 0x0200,  8, 0x6800, 0x1800, 16,  4,  37}, // atdf, avrdude
+  {"AVR64DA32",        355, F_AVR8X, {0x1E, 0x96, 0x14},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  44}, // atdf, avrdude
+  {"AVR64DB32",        356, F_AVR8X, {0x1E, 0x96, 0x18},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  44}, // atdf, avrdude
+  {"AVR64DD32",        357, F_AVR8X, {0x1E, 0x96, 0x1A},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x6000, 0x2000, 16,  4,  36}, // atdf, avrdude
+  {"AVR64EA32",        358, F_AVR8X, {0x1E, 0x96, 0x1F},       0, 0x10000, 0x080,  1,      0, 0x01400, 0x0200,  8, 0x6800, 0x1800, 16,  4,  37}, // atdf, avrdude
+  {"AVR64DA48",        359, F_AVR8X, {0x1E, 0x96, 0x13},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  58}, // atdf, avrdude
+  {"AVR64DB48",        360, F_AVR8X, {0x1E, 0x96, 0x17},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  61}, // atdf, avrdude
+  {"AVR64EA48",        361, F_AVR8X, {0x1E, 0x96, 0x1E},       0, 0x10000, 0x080,  1,      0, 0x01400, 0x0200,  8, 0x6800, 0x1800, 16,  4,  45}, // atdf, avrdude
+  {"AVR64DA64",        362, F_AVR8X, {0x1E, 0x96, 0x12},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  64}, // atdf, avrdude
+  {"AVR64DB64",        363, F_AVR8X, {0x1E, 0x96, 0x16},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  65}, // atdf, avrdude
+  {"AVR128DA28",       364, F_AVR8X, {0x1E, 0x97, 0x0A},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  41}, // atdf, avrdude
+  {"AVR128DB28",       365, F_AVR8X, {0x1E, 0x97, 0x0E},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  42}, // atdf, avrdude
+  {"AVR128DA32",       366, F_AVR8X, {0x1E, 0x97, 0x09},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  44}, // atdf, avrdude
+  {"AVR128DB32",       367, F_AVR8X, {0x1E, 0x97, 0x0D},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  44}, // atdf, avrdude
+  {"AVR128DA48",       368, F_AVR8X, {0x1E, 0x97, 0x08},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  58}, // atdf, avrdude
+  {"AVR128DB48",       369, F_AVR8X, {0x1E, 0x97, 0x0C},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  61}, // atdf, avrdude
+  {"AVR128DA64",       370, F_AVR8X, {0x1E, 0x97, 0x07},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  64}, // atdf, avrdude
+  {"AVR128DB64",       371, F_AVR8X, {0x1E, 0x97, 0x0B},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  65}, // atdf, avrdude
+};
+
+const size_t avr_isp_chip_arr_size = COUNT_OF(avr_isp_chip_arr);

+ 33 - 0
lib/driver/avr_isp_chip_arr.h

@@ -0,0 +1,33 @@
+#pragma once
+
+#include <furi_hal.h>
+
+#define F_AVR8L 1 // TPI programming, ATtiny(4|5|9|10|20|40|102|104)
+#define F_AVR8 2 // ISP programming with SPI, "classic" AVRs
+#define F_XMEGA 4 // PDI programming, ATxmega family
+#define F_AVR8X 8 // UPDI programming, newer 8-bit MCUs
+
+struct AvrIspChipArr { // Value of -1 typically means unknown
+    const char* name; // Name of part
+    uint16_t mcuid; // ID of MCU in 0..2039
+    uint8_t avrarch; // F_AVR8L, F_AVR8, F_XMEGA or F_AVR8X
+    uint8_t sigs[3]; // Signature bytes
+    int32_t flashoffset; // Flash offset
+    int32_t flashsize; // Flash size
+    int16_t pagesize; // Flash page size
+    int8_t nboots; // Number of supported boot sectors
+    int16_t bootsize; // Size of (smallest) boot sector
+    int32_t eepromoffset; // EEPROM offset
+    int32_t eepromsize; // EEPROM size
+    int32_t eeprompagesize; // EEPROM page size
+    int32_t sramstart; // SRAM offset
+    int32_t sramsize; // SRAM size
+    int8_t nfuses; // Number of fuse bytes
+    int8_t nlocks; // Number of lock bytes
+    uint8_t ninterrupts; // Number of vectors in interrupt vector table
+};
+
+typedef struct AvrIspChipArr AvrIspChipArr;
+
+extern const AvrIspChipArr avr_isp_chip_arr[];
+extern const size_t avr_isp_chip_arr_size;

+ 639 - 0
lib/driver/avr_isp_prog.c

@@ -0,0 +1,639 @@
+#include "avr_isp_prog.h"
+#include "avr_isp_prog_cmd.h"
+
+#include <furi.h>
+
+#define AVR_ISP_PROG_TX_RX_BUF_SIZE 320
+#define TAG "AvrIspProg"
+
+struct AvrIspProgSignature {
+    uint8_t vendor;
+    uint8_t part_family;
+    uint8_t part_number;
+};
+
+typedef struct AvrIspProgSignature AvrIspProgSignature;
+
+struct AvrIspProgCfgDevice {
+    uint8_t devicecode;
+    uint8_t revision;
+    uint8_t progtype;
+    uint8_t parmode;
+    uint8_t polling;
+    uint8_t selftimed;
+    uint8_t lockbytes;
+    uint8_t fusebytes;
+    uint8_t flashpoll;
+    uint16_t eeprompoll;
+    uint16_t pagesize;
+    uint16_t eepromsize;
+    uint32_t flashsize;
+};
+
+typedef struct AvrIspProgCfgDevice AvrIspProgCfgDevice;
+
+struct AvrIspProg {
+    AvrIspSpiSw* spi;
+    AvrIspProgCfgDevice* cfg;
+    FuriStreamBuffer* stream_rx;
+    FuriStreamBuffer* stream_tx;
+
+    uint16_t error;
+    uint16_t addr;
+    bool pmode;
+    bool exit;
+    bool rst_active_high;
+    uint8_t buff[AVR_ISP_PROG_TX_RX_BUF_SIZE];
+
+    AvrIspProgCallback callback;
+    void* context;
+};
+
+static void avr_isp_prog_end_pmode(AvrIspProg* instance);
+
+AvrIspProg* avr_isp_prog_init(void) {
+    AvrIspProg* instance = malloc(sizeof(AvrIspProg));
+    instance->cfg = malloc(sizeof(AvrIspProgCfgDevice));
+    instance->stream_rx =
+        furi_stream_buffer_alloc(sizeof(int8_t) * AVR_ISP_PROG_TX_RX_BUF_SIZE, sizeof(int8_t));
+    instance->stream_tx =
+        furi_stream_buffer_alloc(sizeof(int8_t) * AVR_ISP_PROG_TX_RX_BUF_SIZE, sizeof(int8_t));
+    instance->rst_active_high = false;
+    instance->exit = false;
+    return instance;
+}
+
+void avr_isp_prog_free(AvrIspProg* instance) {
+    furi_assert(instance);
+    if(instance->spi) avr_isp_prog_end_pmode(instance);
+    furi_stream_buffer_free(instance->stream_tx);
+    furi_stream_buffer_free(instance->stream_rx);
+    free(instance->cfg);
+    free(instance);
+}
+
+size_t avr_isp_prog_spaces_rx(AvrIspProg* instance) {
+    return furi_stream_buffer_spaces_available(instance->stream_rx);
+}
+
+bool avr_isp_prog_rx(AvrIspProg* instance, uint8_t* data, size_t len) {
+    furi_assert(instance);
+    furi_assert(data);
+    furi_assert(len != 0);
+    size_t ret = furi_stream_buffer_send(instance->stream_rx, data, sizeof(uint8_t) * len, 0);
+    return ret == sizeof(uint8_t) * len;
+}
+
+size_t avr_isp_prog_tx(AvrIspProg* instance, uint8_t* data, size_t max_len) {
+    furi_assert(instance);
+    return furi_stream_buffer_receive(instance->stream_tx, data, sizeof(int8_t) * max_len, 0);
+}
+
+void avr_isp_prog_exit(AvrIspProg* instance) {
+    furi_assert(instance);
+    instance->exit = true;
+}
+
+void avr_isp_prog_set_tx_callback(AvrIspProg* instance, AvrIspProgCallback callback, void* context) {
+    furi_assert(instance);
+    furi_assert(context);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+static void avr_isp_prog_tx_ch(AvrIspProg* instance, uint8_t data) {
+    furi_assert(instance);
+    furi_stream_buffer_send(instance->stream_tx, &data, sizeof(uint8_t), FuriWaitForever);
+}
+
+static uint8_t avr_isp_prog_getch(AvrIspProg* instance) {
+    furi_assert(instance);
+    uint8_t data[1] = {0};
+    while(furi_stream_buffer_receive(instance->stream_rx, &data, sizeof(int8_t), 30) == 0) {
+        if(instance->exit) break;
+    };
+    return data[0];
+}
+
+static void avr_isp_prog_fill(AvrIspProg* instance, size_t len) {
+    furi_assert(instance);
+    for(size_t x = 0; x < len; x++) {
+        instance->buff[x] = avr_isp_prog_getch(instance);
+    }
+}
+
+static void avr_isp_prog_reset_target(AvrIspProg* instance, bool reset) {
+    furi_assert(instance);
+    avr_isp_spi_sw_res_set(instance->spi, (reset == instance->rst_active_high) ? true : false);
+}
+
+static uint8_t avr_isp_prog_spi_transaction(
+    AvrIspProg* instance,
+    uint8_t cmd,
+    uint8_t addr_hi,
+    uint8_t addr_lo,
+    uint8_t data) {
+    furi_assert(instance);
+
+    avr_isp_spi_sw_txrx(instance->spi, cmd);
+    avr_isp_spi_sw_txrx(instance->spi, addr_hi);
+    avr_isp_spi_sw_txrx(instance->spi, addr_lo);
+    return avr_isp_spi_sw_txrx(instance->spi, data);
+}
+
+static void avr_isp_prog_empty_reply(AvrIspProg* instance) {
+    furi_assert(instance);
+    if(avr_isp_prog_getch(instance) == CRC_EOP) {
+        avr_isp_prog_tx_ch(instance, STK_INSYNC);
+        avr_isp_prog_tx_ch(instance, STK_OK);
+    } else {
+        instance->error++;
+        avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+    }
+}
+
+static void avr_isp_prog_breply(AvrIspProg* instance, uint8_t data) {
+    furi_assert(instance);
+    if(avr_isp_prog_getch(instance) == CRC_EOP) {
+        avr_isp_prog_tx_ch(instance, STK_INSYNC);
+        avr_isp_prog_tx_ch(instance, data);
+        avr_isp_prog_tx_ch(instance, STK_OK);
+    } else {
+        instance->error++;
+        avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+    }
+}
+
+static void avr_isp_prog_get_version(AvrIspProg* instance, uint8_t data) {
+    furi_assert(instance);
+    switch(data) {
+    case STK_HW_VER:
+        avr_isp_prog_breply(instance, AVR_ISP_HWVER);
+        break;
+    case STK_SW_MAJOR:
+        avr_isp_prog_breply(instance, AVR_ISP_SWMAJ);
+        break;
+    case STK_SW_MINOR:
+        avr_isp_prog_breply(instance, AVR_ISP_SWMIN);
+        break;
+    case AVP_ISP_CONNECT_TYPE:
+        avr_isp_prog_breply(instance, AVP_ISP_SERIAL_CONNECT_TYPE);
+        break;
+    default:
+        avr_isp_prog_breply(instance, AVR_ISP_RESP_0);
+    }
+}
+
+static void avr_isp_prog_set_cfg(AvrIspProg* instance) {
+    furi_assert(instance);
+    // call this after reading cfg packet into buff[]
+    instance->cfg->devicecode = instance->buff[0];
+    instance->cfg->revision = instance->buff[1];
+    instance->cfg->progtype = instance->buff[2];
+    instance->cfg->parmode = instance->buff[3];
+    instance->cfg->polling = instance->buff[4];
+    instance->cfg->selftimed = instance->buff[5];
+    instance->cfg->lockbytes = instance->buff[6];
+    instance->cfg->fusebytes = instance->buff[7];
+    instance->cfg->flashpoll = instance->buff[8];
+    // ignore (instance->buff[9] == instance->buff[8]) //FLASH polling value. Same as �flashpoll�
+    instance->cfg->eeprompoll = instance->buff[10] << 8 | instance->buff[11];
+    instance->cfg->pagesize = instance->buff[12] << 8 | instance->buff[13];
+    instance->cfg->eepromsize = instance->buff[14] << 8 | instance->buff[15];
+    instance->cfg->flashsize = instance->buff[16] << 24 | instance->buff[17] << 16 |
+                               instance->buff[18] << 8 | instance->buff[19];
+
+    // avr devices have active low reset, at89sx are active high
+    instance->rst_active_high = (instance->cfg->devicecode >= 0xe0);
+}
+static bool
+    avr_isp_prog_set_pmode(AvrIspProg* instance, uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
+    furi_assert(instance);
+    uint8_t res = 0;
+    avr_isp_spi_sw_txrx(instance->spi, a);
+    avr_isp_spi_sw_txrx(instance->spi, b);
+    res = avr_isp_spi_sw_txrx(instance->spi, c);
+    avr_isp_spi_sw_txrx(instance->spi, d);
+    return res == 0x53;
+}
+
+static void avr_isp_prog_end_pmode(AvrIspProg* instance) {
+    furi_assert(instance);
+    if(instance->pmode) {
+        avr_isp_prog_reset_target(instance, false);
+        // We're about to take the target out of reset
+        // so configure SPI pins as input
+
+        if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+        instance->spi = NULL;
+    }
+
+    instance->pmode = false;
+}
+
+static bool avr_isp_prog_start_pmode(AvrIspProg* instance, AvrIspSpiSwSpeed spi_speed) {
+    furi_assert(instance);
+    // Reset target before driving PIN_SCK or PIN_MOSI
+
+    // SPI.begin() will configure SS as output,
+    // so SPI master mode is selected.
+    // We have defined RESET as pin 10,
+    // which for many arduino's is not the SS pin.
+    // So we have to configure RESET as output here,
+    // (reset_target() first sets the correct level)
+    if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+    instance->spi = avr_isp_spi_sw_init(spi_speed);
+
+    avr_isp_prog_reset_target(instance, true);
+    // See avr datasheets, chapter "SERIAL_PRG Programming Algorithm":
+
+    // Pulse RESET after PIN_SCK is low:
+    avr_isp_spi_sw_sck_set(instance->spi, false);
+
+    // discharge PIN_SCK, value arbitrally chosen
+    furi_delay_ms(20);
+    avr_isp_prog_reset_target(instance, false);
+
+    // Pulse must be minimum 2 target CPU speed cycles
+    // so 100 usec is ok for CPU speeds above 20KHz
+    furi_delay_ms(1);
+
+    avr_isp_prog_reset_target(instance, true);
+
+    // Send the enable programming command:
+    // datasheet: must be > 20 msec
+    furi_delay_ms(50);
+    if(avr_isp_prog_set_pmode(instance, AVR_ISP_SET_PMODE)) {
+        instance->pmode = true;
+        return true;
+    }
+    return false;
+}
+
+static AvrIspProgSignature avr_isp_prog_check_signature(AvrIspProg* instance) {
+    furi_assert(instance);
+    AvrIspProgSignature signature;
+    signature.vendor = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_VENDOR);
+    signature.part_family = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY);
+    signature.part_number = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER);
+    return signature;
+}
+
+static bool avr_isp_prog_auto_set_spi_speed_start_pmode(AvrIspProg* instance) {
+    AvrIspSpiSwSpeed spi_speed[] = {
+        AvrIspSpiSwSpeed1Mhz,
+        AvrIspSpiSwSpeed400Khz,
+        AvrIspSpiSwSpeed250Khz,
+        AvrIspSpiSwSpeed125Khz,
+        AvrIspSpiSwSpeed60Khz,
+        AvrIspSpiSwSpeed40Khz,
+        AvrIspSpiSwSpeed20Khz,
+        AvrIspSpiSwSpeed10Khz,
+        AvrIspSpiSwSpeed5Khz,
+        AvrIspSpiSwSpeed1Khz,
+    };
+    for(uint8_t i = 0; i < COUNT_OF(spi_speed); i++) {
+        if(avr_isp_prog_start_pmode(instance, spi_speed[i])) {
+            AvrIspProgSignature sig = avr_isp_prog_check_signature(instance);
+            AvrIspProgSignature sig_examination = avr_isp_prog_check_signature(instance); //-V656
+            uint8_t y = 0;
+            while(y < 8) {
+                if(memcmp(
+                       (uint8_t*)&sig, (uint8_t*)&sig_examination, sizeof(AvrIspProgSignature)) !=
+                   0)
+                    break;
+                sig_examination = avr_isp_prog_check_signature(instance);
+                y++;
+            }
+            if(y == 8) {
+                if(spi_speed[i] > AvrIspSpiSwSpeed1Mhz) {
+                    if(i < (COUNT_OF(spi_speed) - 1)) {
+                        avr_isp_prog_end_pmode(instance);
+                        i++;
+                        return avr_isp_prog_start_pmode(instance, spi_speed[i]);
+                    }
+                }
+                return true;
+            }
+        }
+    }
+
+    if(instance->spi) {
+        avr_isp_spi_sw_free(instance->spi);
+        instance->spi = NULL;
+    }
+
+    return false;
+}
+
+static void avr_isp_prog_universal(AvrIspProg* instance) {
+    furi_assert(instance);
+    uint8_t data;
+
+    avr_isp_prog_fill(instance, 4);
+    data = avr_isp_prog_spi_transaction(
+        instance, instance->buff[0], instance->buff[1], instance->buff[2], instance->buff[3]);
+    avr_isp_prog_breply(instance, data);
+}
+
+static void avr_isp_prog_commit(AvrIspProg* instance, uint16_t addr, uint8_t data) {
+    furi_assert(instance);
+    avr_isp_prog_spi_transaction(instance, AVR_ISP_COMMIT(addr));
+    /* polling flash */
+    if(data == 0xFF) {
+        furi_delay_ms(5);
+    } else {
+        /* polling flash */
+        uint32_t starttime = furi_get_tick();
+        while((furi_get_tick() - starttime) < 30) {
+            if(avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) {
+                break;
+            };
+        }
+    }
+}
+
+static uint16_t avr_isp_prog_current_page(AvrIspProg* instance) {
+    furi_assert(instance);
+    uint16_t page = 0;
+    switch(instance->cfg->pagesize) {
+    case 32:
+        page = instance->addr & 0xFFFFFFF0;
+        break;
+    case 64:
+        page = instance->addr & 0xFFFFFFE0;
+        break;
+    case 128:
+        page = instance->addr & 0xFFFFFFC0;
+        break;
+    case 256:
+        page = instance->addr & 0xFFFFFF80;
+        break;
+
+    default:
+        page = instance->addr;
+        break;
+    }
+
+    return page;
+}
+
+static uint8_t avr_isp_prog_write_flash_pages(AvrIspProg* instance, size_t length) {
+    furi_assert(instance);
+    size_t x = 0;
+    uint16_t page = avr_isp_prog_current_page(instance);
+    while(x < length) {
+        if(page != avr_isp_prog_current_page(instance)) {
+            --x;
+            avr_isp_prog_commit(instance, page, instance->buff[x++]);
+            page = avr_isp_prog_current_page(instance);
+        }
+        avr_isp_prog_spi_transaction(
+            instance, AVR_ISP_WRITE_FLASH_LO(instance->addr, instance->buff[x++]));
+
+        avr_isp_prog_spi_transaction(
+            instance, AVR_ISP_WRITE_FLASH_HI(instance->addr, instance->buff[x++]));
+        instance->addr++;
+    }
+
+    avr_isp_prog_commit(instance, page, instance->buff[--x]);
+    return STK_OK;
+}
+
+static void avr_isp_prog_write_flash(AvrIspProg* instance, size_t length) {
+    furi_assert(instance);
+    avr_isp_prog_fill(instance, length);
+    if(avr_isp_prog_getch(instance) == CRC_EOP) {
+        avr_isp_prog_tx_ch(instance, STK_INSYNC);
+        avr_isp_prog_tx_ch(instance, avr_isp_prog_write_flash_pages(instance, length));
+    } else {
+        instance->error++;
+        avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+    }
+}
+
+// write (length) bytes, (start) is a byte address
+static uint8_t
+    avr_isp_prog_write_eeprom_chunk(AvrIspProg* instance, uint16_t start, uint16_t length) {
+    furi_assert(instance);
+    // this writes byte-by-byte,
+    // page writing may be faster (4 bytes at a time)
+    avr_isp_prog_fill(instance, length);
+    for(uint16_t x = 0; x < length; x++) {
+        uint16_t addr = start + x;
+        avr_isp_prog_spi_transaction(instance, AVR_ISP_WRITE_EEPROM(addr, instance->buff[x]));
+        furi_delay_ms(10);
+    }
+    return STK_OK;
+}
+
+static uint8_t avr_isp_prog_write_eeprom(AvrIspProg* instance, size_t length) {
+    furi_assert(instance);
+    // here is a word address, get the byte address
+    uint16_t start = instance->addr * 2;
+    uint16_t remaining = length;
+    if(length > instance->cfg->eepromsize) {
+        instance->error++;
+        return STK_FAILED;
+    }
+    while(remaining > AVR_ISP_EECHUNK) {
+        avr_isp_prog_write_eeprom_chunk(instance, start, AVR_ISP_EECHUNK);
+        start += AVR_ISP_EECHUNK;
+        remaining -= AVR_ISP_EECHUNK;
+    }
+    avr_isp_prog_write_eeprom_chunk(instance, start, remaining);
+    return STK_OK;
+}
+
+static void avr_isp_prog_program_page(AvrIspProg* instance) {
+    furi_assert(instance);
+    uint8_t result = STK_FAILED;
+    uint16_t length = avr_isp_prog_getch(instance) << 8 | avr_isp_prog_getch(instance);
+    uint8_t memtype = avr_isp_prog_getch(instance);
+    // flash memory @addr, (length) bytes
+    if(memtype == STK_SET_FLASH_TYPE) {
+        avr_isp_prog_write_flash(instance, length);
+        return;
+    }
+    if(memtype == STK_SET_EEPROM_TYPE) {
+        result = avr_isp_prog_write_eeprom(instance, length);
+        if(avr_isp_prog_getch(instance) == CRC_EOP) {
+            avr_isp_prog_tx_ch(instance, STK_INSYNC);
+            avr_isp_prog_tx_ch(instance, result);
+
+        } else {
+            instance->error++;
+            avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+        }
+        return;
+    }
+    avr_isp_prog_tx_ch(instance, STK_FAILED);
+    return;
+}
+
+static uint8_t avr_isp_prog_flash_read_page(AvrIspProg* instance, uint16_t length) {
+    furi_assert(instance);
+    for(uint16_t x = 0; x < length; x += 2) {
+        avr_isp_prog_tx_ch(
+            instance,
+            avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_LO(instance->addr)));
+        avr_isp_prog_tx_ch(
+            instance,
+            avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(instance->addr)));
+        instance->addr++;
+    }
+    return STK_OK;
+}
+
+static uint8_t avr_isp_prog_eeprom_read_page(AvrIspProg* instance, uint16_t length) {
+    furi_assert(instance);
+    // here again we have a word address
+    uint16_t start = instance->addr * 2;
+    for(uint16_t x = 0; x < length; x++) {
+        uint16_t addr = start + x;
+        avr_isp_prog_tx_ch(
+            instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_EEPROM(addr)));
+    }
+    return STK_OK;
+}
+
+static void avr_isp_prog_read_page(AvrIspProg* instance) {
+    furi_assert(instance);
+    uint8_t result = STK_FAILED;
+    uint16_t length = avr_isp_prog_getch(instance) << 8 | avr_isp_prog_getch(instance);
+    uint8_t memtype = avr_isp_prog_getch(instance);
+    if(avr_isp_prog_getch(instance) != CRC_EOP) {
+        instance->error++;
+        avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+        return;
+    }
+    avr_isp_prog_tx_ch(instance, STK_INSYNC);
+    if(memtype == STK_SET_FLASH_TYPE) result = avr_isp_prog_flash_read_page(instance, length);
+    if(memtype == STK_SET_EEPROM_TYPE) result = avr_isp_prog_eeprom_read_page(instance, length);
+    avr_isp_prog_tx_ch(instance, result);
+}
+
+static void avr_isp_prog_read_signature(AvrIspProg* instance) {
+    furi_assert(instance);
+    if(avr_isp_prog_getch(instance) != CRC_EOP) {
+        instance->error++;
+        avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+        return;
+    }
+    avr_isp_prog_tx_ch(instance, STK_INSYNC);
+
+    avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_VENDOR));
+    avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY));
+    avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER));
+
+    avr_isp_prog_tx_ch(instance, STK_OK);
+}
+
+void avr_isp_prog_avrisp(AvrIspProg* instance) {
+    furi_assert(instance);
+    uint8_t ch = avr_isp_prog_getch(instance);
+
+    switch(ch) {
+    case STK_GET_SYNC:
+        FURI_LOG_D(TAG, "cmd STK_GET_SYNC");
+        instance->error = 0;
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_GET_SIGN_ON:
+        FURI_LOG_D(TAG, "cmd STK_GET_SIGN_ON");
+        if(avr_isp_prog_getch(instance) == CRC_EOP) {
+            avr_isp_prog_tx_ch(instance, STK_INSYNC);
+
+            avr_isp_prog_tx_ch(instance, 'A');
+            avr_isp_prog_tx_ch(instance, 'V');
+            avr_isp_prog_tx_ch(instance, 'R');
+            avr_isp_prog_tx_ch(instance, ' ');
+            avr_isp_prog_tx_ch(instance, 'I');
+            avr_isp_prog_tx_ch(instance, 'S');
+            avr_isp_prog_tx_ch(instance, 'P');
+
+            avr_isp_prog_tx_ch(instance, STK_OK);
+        } else {
+            instance->error++;
+            avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+        }
+        break;
+    case STK_GET_PARAMETER:
+        FURI_LOG_D(TAG, "cmd STK_GET_PARAMETER");
+        avr_isp_prog_get_version(instance, avr_isp_prog_getch(instance));
+        break;
+    case STK_SET_DEVICE:
+        FURI_LOG_D(TAG, "cmd STK_SET_DEVICE");
+        avr_isp_prog_fill(instance, 20);
+        avr_isp_prog_set_cfg(instance);
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_SET_DEVICE_EXT: // ignore for now
+        FURI_LOG_D(TAG, "cmd STK_SET_DEVICE_EXT");
+        avr_isp_prog_fill(instance, 5);
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_ENTER_PROGMODE:
+        FURI_LOG_D(TAG, "cmd STK_ENTER_PROGMODE");
+        if(!instance->pmode) avr_isp_prog_auto_set_spi_speed_start_pmode(instance);
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_LOAD_ADDRESS:
+        FURI_LOG_D(TAG, "cmd STK_LOAD_ADDRESS");
+        instance->addr = avr_isp_prog_getch(instance) | avr_isp_prog_getch(instance) << 8;
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_PROG_FLASH: // ignore for now
+        FURI_LOG_D(TAG, "cmd STK_PROG_FLASH");
+        avr_isp_prog_getch(instance);
+        avr_isp_prog_getch(instance);
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_PROG_DATA: // ignore for now
+        FURI_LOG_D(TAG, "cmd STK_PROG_DATA");
+        avr_isp_prog_getch(instance);
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_PROG_PAGE:
+        FURI_LOG_D(TAG, "cmd STK_PROG_PAGE");
+        avr_isp_prog_program_page(instance);
+        break;
+    case STK_READ_PAGE:
+        FURI_LOG_D(TAG, "cmd STK_READ_PAGE");
+        avr_isp_prog_read_page(instance);
+        break;
+    case STK_UNIVERSAL:
+        FURI_LOG_D(TAG, "cmd STK_UNIVERSAL");
+        avr_isp_prog_universal(instance);
+        break;
+    case STK_LEAVE_PROGMODE:
+        FURI_LOG_D(TAG, "cmd STK_LEAVE_PROGMODE");
+        instance->error = 0;
+        if(instance->pmode) avr_isp_prog_end_pmode(instance);
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_READ_SIGN:
+        FURI_LOG_D(TAG, "cmd STK_READ_SIGN");
+        avr_isp_prog_read_signature(instance);
+        break;
+    // expecting a command, not CRC_EOP
+    // this is how we can get back in sync
+    case CRC_EOP:
+        FURI_LOG_D(TAG, "cmd CRC_EOP");
+        instance->error++;
+        avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+        break;
+    // anything else we will return STK_UNKNOWN
+    default:
+        FURI_LOG_D(TAG, "cmd STK_ERROR_CMD");
+        instance->error++;
+        if(avr_isp_prog_getch(instance) == CRC_EOP)
+            avr_isp_prog_tx_ch(instance, STK_UNKNOWN);
+        else
+            avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+    }
+
+    if(instance->callback) {
+        instance->callback(instance->context);
+    }
+}

+ 16 - 0
lib/driver/avr_isp_prog.h

@@ -0,0 +1,16 @@
+#pragma once
+
+#include "avr_isp_spi_sw.h"
+#include <furi_hal.h>
+
+typedef struct AvrIspProg AvrIspProg;
+typedef void (*AvrIspProgCallback)(void* context);
+
+AvrIspProg* avr_isp_prog_init(void);
+void avr_isp_prog_free(AvrIspProg* instance);
+size_t avr_isp_prog_spaces_rx(AvrIspProg* instance) ;
+bool avr_isp_prog_rx(AvrIspProg* instance, uint8_t* data, size_t len);
+size_t avr_isp_prog_tx(AvrIspProg* instance, uint8_t* data, size_t max_len);
+void avr_isp_prog_avrisp(AvrIspProg* instance);
+void avr_isp_prog_exit(AvrIspProg* instance);
+void avr_isp_prog_set_tx_callback(AvrIspProg* instance, AvrIspProgCallback callback, void* context);

+ 97 - 0
lib/driver/avr_isp_prog_cmd.h

@@ -0,0 +1,97 @@
+#pragma once
+
+// http://ww1.microchip.com/downloads/en/appnotes/atmel-0943-in-system-programming_applicationnote_avr910.pdf
+// AVR ISP Definitions
+#define AVR_ISP_HWVER 0X02
+#define AVR_ISP_SWMAJ 0X01
+#define AVR_ISP_SWMIN 0X12
+#define AVP_ISP_SERIAL_CONNECT_TYPE 0X53
+#define AVP_ISP_CONNECT_TYPE 0x93
+#define AVR_ISP_RESP_0 0X00
+
+#define AVR_ISP_SET_PMODE 0xAC, 0x53, 0x00, 0x00
+#define AVR_ISP_READ_VENDOR 0x30, 0x00, 0x00, 0x00
+#define AVR_ISP_READ_PART_FAMILY 0x30, 0x00, 0x01, 0x00
+#define AVR_ISP_READ_PART_NUMBER 0x30, 0x00, 0x02, 0x00
+#define AVR_ISP_ERASE_CHIP \
+    0xAC, 0x80, 0x00, 0x00 //Erase Chip, Wait N ms, Release RESET to end the erase.
+//The only way to end a Chip Erase cycle is by temporarily releasing the Reset line
+
+#define AVR_ISP_EXTENDED_ADDR(data) 0x4D, 0x00, data, 0x00
+#define AVR_ISP_WRITE_FLASH_LO(add, data) 0x40, (add >> 8) & 0xFF, add & 0xFF, data
+#define AVR_ISP_WRITE_FLASH_HI(add, data) 0x48, (add >> 8) & 0xFF, add & 0xFF, data
+#define AVR_ISP_READ_FLASH_LO(add) 0x20, (add >> 8) & 0xFF, add & 0xFF, 0x00
+#define AVR_ISP_READ_FLASH_HI(add) 0x28, (add >> 8) & 0xFF, add & 0xFF, 0x00
+
+#define AVR_ISP_WRITE_EEPROM(add, data) \
+    0xC0, (add >> 8) & 0xFF, add & 0xFF, data //Send cmd, Wait N ms
+#define AVR_ISP_READ_EEPROM(add) 0xA0, (add >> 8) & 0xFF, add & 0xFF, 0xFF
+
+#define AVR_ISP_COMMIT(add) \
+    0x4C, (add >> 8) & 0xFF, add & 0xFF, 0x00 //Send cmd, polling read last addr page
+
+#define AVR_ISP_OSCCAL(add) 0x38, 0x00, add, 0x00
+
+#define AVR_ISP_WRITE_LOCK_BYTE(data) 0xAC, 0xE0, 0x00, data //Send cmd, Wait N ms
+#define AVR_ISP_READ_LOCK_BYTE 0x58, 0x00, 0x00, 0x00
+#define AVR_ISP_WRITE_FUSE_LOW(data) 0xAC, 0xA0, 0x00, data //Send cmd, Wait N ms
+#define AVR_ISP_READ_FUSE_LOW 0x50, 0x00, 0x00, 0x00
+#define AVR_ISP_WRITE_FUSE_HIGH(data) 0xAC, 0xA8, 0x00, data //Send cmd, Wait N ms
+#define AVR_ISP_READ_FUSE_HIGH 0x58, 0x08, 0x00, 0x00
+#define AVR_ISP_WRITE_FUSE_EXTENDED(data) 0xAC, 0xA4, 0x00, data //Send cmd, Wait N ms (~write)
+#define AVR_ISP_READ_FUSE_EXTENDED 0x50, 0x08, 0x00, 0x00
+
+#define AVR_ISP_EECHUNK 0x20
+
+// https://www.microchip.com/content/dam/mchp/documents/OTH/ApplicationNotes/ApplicationNotes/doc2525.pdf
+// STK Definitions
+#define STK_OK 0x10
+#define STK_FAILED 0x11
+#define STK_UNKNOWN 0x12
+#define STK_INSYNC 0x14
+#define STK_NOSYNC 0x15
+#define CRC_EOP 0x20
+
+#define STK_GET_SYNC 0x30
+#define STK_GET_SIGN_ON 0x31
+#define STK_SET_PARAMETER 0x40
+#define STK_GET_PARAMETER 0x41
+#define STK_SET_DEVICE 0x42
+#define STK_SET_DEVICE_EXT 0x45
+#define STK_ENTER_PROGMODE 0x50
+#define STK_LEAVE_PROGMODE 0x51
+#define STK_CHIP_ERASE 0x52
+#define STK_CHECK_AUTOINC 0x53
+#define STK_LOAD_ADDRESS 0x55
+#define STK_UNIVERSAL 0x56
+#define STK_UNIVERSAL_MULTI 0x57
+#define STK_PROG_FLASH 0x60
+#define STK_PROG_DATA 0x61
+#define STK_PROG_FUSE 0x62
+#define STK_PROG_FUSE_EXT 0x65
+#define STK_PROG_LOCK 0x63
+#define STK_PROG_PAGE 0x64
+#define STK_READ_FLASH 0x70
+#define STK_READ_DATA 0x71
+#define STK_READ_FUSE 0x72
+#define STK_READ_LOCK 0x73
+#define STK_READ_PAGE 0x74
+#define STK_READ_SIGN 0x75
+#define STK_READ_OSCCAL 0x76
+#define STK_READ_FUSE_EXT 0x77
+#define STK_READ_OSCCAL_EXT 0x78
+#define STK_HW_VER 0x80
+#define STK_SW_MAJOR 0x81
+#define STK_SW_MINOR 0x82
+#define STK_LEDS 0x83
+#define STK_VTARGET 0x84
+#define STK_VADJUST 0x85
+#define STK_OSC_PSCALE 0x86
+#define STK_OSC_CMATCH 0x87
+#define STK_SCK_DURATION 0x89
+#define STK_BUFSIZEL 0x90
+#define STK_BUFSIZEH 0x91
+#define STK_STK500_TOPCARD_DETECT 0x98
+
+#define STK_SET_EEPROM_TYPE 0X45
+#define STK_SET_FLASH_TYPE 0X46

+ 71 - 0
lib/driver/avr_isp_spi_sw.c

@@ -0,0 +1,71 @@
+#include "avr_isp_spi_sw.h"
+
+#include <furi.h>
+
+#define AVR_ISP_SPI_SW_MISO &gpio_ext_pa6
+#define AVR_ISP_SPI_SW_MOSI &gpio_ext_pa7
+#define AVR_ISP_SPI_SW_SCK &gpio_ext_pb3
+#define AVR_ISP_RESET &gpio_ext_pb2
+
+struct AvrIspSpiSw {
+    AvrIspSpiSwSpeed speed_wait_time;
+    const GpioPin* miso;
+    const GpioPin* mosi;
+    const GpioPin* sck;
+    const GpioPin* res;
+};
+
+AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed) {
+    AvrIspSpiSw* instance = malloc(sizeof(AvrIspSpiSw));
+    instance->speed_wait_time = speed;
+    instance->miso = AVR_ISP_SPI_SW_MISO;
+    instance->mosi = AVR_ISP_SPI_SW_MOSI;
+    instance->sck = AVR_ISP_SPI_SW_SCK;
+    instance->res = AVR_ISP_RESET;
+
+    furi_hal_gpio_init(instance->miso, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
+    furi_hal_gpio_write(instance->mosi, false);
+    furi_hal_gpio_init(instance->mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
+    furi_hal_gpio_write(instance->sck, false);
+    furi_hal_gpio_init(instance->sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
+    furi_hal_gpio_init(instance->res, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
+
+    return instance;
+}
+
+void avr_isp_spi_sw_free(AvrIspSpiSw* instance) {
+    furi_assert(instance);
+    furi_hal_gpio_init(instance->res, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+    furi_hal_gpio_init(instance->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+    furi_hal_gpio_init(instance->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+    furi_hal_gpio_init(instance->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+    free(instance);
+}
+
+uint8_t avr_isp_spi_sw_txrx(AvrIspSpiSw* instance, uint8_t data) {
+    furi_assert(instance);
+    for(uint8_t i = 0; i < 8; ++i) {
+        furi_hal_gpio_write(instance->mosi, (data & 0x80) ? true : false);
+
+        furi_hal_gpio_write(instance->sck, true);
+        if(instance->speed_wait_time != AvrIspSpiSwSpeed1Mhz)
+            furi_delay_us(instance->speed_wait_time - 1);
+
+        data = (data << 1) | furi_hal_gpio_read(instance->miso); //-V792
+
+        furi_hal_gpio_write(instance->sck, false);
+        if(instance->speed_wait_time != AvrIspSpiSwSpeed1Mhz)
+            furi_delay_us(instance->speed_wait_time - 1);
+    }
+    return data;
+}
+
+void avr_isp_spi_sw_res_set(AvrIspSpiSw* instance, bool state) {
+    furi_assert(instance);
+    furi_hal_gpio_write(instance->res, state);
+}
+
+void avr_isp_spi_sw_sck_set(AvrIspSpiSw* instance, bool state) {
+    furi_assert(instance);
+    furi_hal_gpio_write(instance->sck, state);
+}

+ 24 - 0
lib/driver/avr_isp_spi_sw.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include <furi_hal.h>
+
+typedef enum {
+    AvrIspSpiSwSpeed1Mhz = 0,
+    AvrIspSpiSwSpeed400Khz = 1,
+    AvrIspSpiSwSpeed250Khz = 2,
+    AvrIspSpiSwSpeed125Khz = 4,
+    AvrIspSpiSwSpeed60Khz = 8,
+    AvrIspSpiSwSpeed40Khz = 12,
+    AvrIspSpiSwSpeed20Khz = 24,
+    AvrIspSpiSwSpeed10Khz = 48,
+    AvrIspSpiSwSpeed5Khz = 96,
+    AvrIspSpiSwSpeed1Khz = 480,
+} AvrIspSpiSwSpeed;
+
+typedef struct AvrIspSpiSw AvrIspSpiSw;
+
+AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed);
+void avr_isp_spi_sw_free(AvrIspSpiSw* instance);
+uint8_t avr_isp_spi_sw_txrx(AvrIspSpiSw* instance, uint8_t data);
+void avr_isp_spi_sw_res_set(AvrIspSpiSw* instance, bool state);
+void avr_isp_spi_sw_sck_set(AvrIspSpiSw* instance, bool state);

BIN
lib/driver/clock.png


+ 30 - 0
scenes/avr_isp_scene.c

@@ -0,0 +1,30 @@
+#include "../avr_isp_app_i.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const avr_isp_scene_on_enter_handlers[])(void*) = {
+#include "avr_isp_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const avr_isp_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "avr_isp_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const avr_isp_scene_on_exit_handlers[])(void* context) = {
+#include "avr_isp_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers avr_isp_scene_handlers = {
+    .on_enter_handlers = avr_isp_scene_on_enter_handlers,
+    .on_event_handlers = avr_isp_scene_on_event_handlers,
+    .on_exit_handlers = avr_isp_scene_on_exit_handlers,
+    .scene_num = AvrIspSceneNum,
+};

+ 29 - 0
scenes/avr_isp_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) AvrIspScene##id,
+typedef enum {
+#include "avr_isp_scene_config.h"
+    AvrIspSceneNum,
+} AvrIspScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers avr_isp_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "avr_isp_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "avr_isp_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "avr_isp_scene_config.h"
+#undef ADD_SCENE

+ 99 - 0
scenes/avr_isp_scene_about.c

@@ -0,0 +1,99 @@
+#include "../avr_isp_app_i.h"
+#include "../helpers/avr_isp_types.h"
+
+void avr_isp_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, result);
+    }
+}
+
+void avr_isp_scene_about_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    FuriString* temp_str = furi_string_alloc();
+    furi_string_printf(temp_str, "\e#%s\n", "Information");
+
+    furi_string_cat_printf(temp_str, "Version: %s\n", AVR_ISP_VERSION_APP);
+    furi_string_cat_printf(temp_str, "Developed by: %s\n", AVR_ISP_DEVELOPED);
+    furi_string_cat_printf(temp_str, "Github: %s\n\n", AVR_ISP_GITHUB);
+
+    furi_string_cat_printf(temp_str, "\e#%s\n", "Description");
+    furi_string_cat_printf(
+        temp_str,
+        "This application is an AVR in-system programmer based on stk500mk1. It is compatible with AVR-based"
+        " microcontrollers including Arduino. You can also use it to repair the chip if you accidentally"
+        " corrupt the bootloader.\n\n");
+
+    furi_string_cat_printf(temp_str, "\e#%s\n", "What it can do:");
+    furi_string_cat_printf(temp_str, "- Create a dump of your chip on an SD card\n");
+    furi_string_cat_printf(temp_str, "- Flash your chip firmware from the SD card\n");
+    furi_string_cat_printf(temp_str, "- Act as a wired USB ISP using avrdude software\n\n");
+
+    furi_string_cat_printf(temp_str, "\e#%s\n", "Supported chip series:");
+    furi_string_cat_printf(
+        temp_str,
+        "Example command for avrdude flashing: avrdude.exe -p m328p -c stk500v1 -P COMxx -U flash:r:"
+        "X:\\sketch_sample.hex"
+        ":i\n");
+    furi_string_cat_printf(
+        temp_str,
+        "Where: "
+        "-p m328p"
+        " brand of your chip, "
+        "-P COMxx"
+        " com port number in the system when "
+        "ISP Programmer"
+        " is enabled\n\n");
+
+    furi_string_cat_printf(temp_str, "\e#%s\n", "Info");
+    furi_string_cat_printf(
+        temp_str,
+        "ATtinyXXXX\nATmegaXXXX\nAT43Uxxx\nAT76C711\nAT86RF401\nAT90xxxxx\nAT94K\n"
+        "ATAxxxxx\nATA664251\nM3000\nLGT8F88P\nLGT8F168P\nLGT8F328P\n");
+
+    furi_string_cat_printf(
+        temp_str, "For a more detailed list of supported chips, see AVRDude help\n");
+
+    widget_add_text_box_element(
+        app->widget,
+        0,
+        0,
+        128,
+        14,
+        AlignCenter,
+        AlignBottom,
+        "\e#\e!                                                      \e!\n",
+        false);
+    widget_add_text_box_element(
+        app->widget,
+        0,
+        2,
+        128,
+        14,
+        AlignCenter,
+        AlignBottom,
+        "\e#\e!        ISP Programmer       \e!\n",
+        false);
+    widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str));
+    furi_string_free(temp_str);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWidget);
+}
+
+bool avr_isp_scene_about_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void avr_isp_scene_about_on_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    // Clear views
+    widget_reset(app->widget);
+}

+ 72 - 0
scenes/avr_isp_scene_chip_detect.c

@@ -0,0 +1,72 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_chip_detect_callback(AvrIspCustomEvent event, void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void avr_isp_scene_chip_detect_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    switch(app->error) {
+    case AvrIspErrorReading:
+    case AvrIspErrorWriting:
+    case AvrIspErrorWritingFuse:
+        avr_isp_chip_detect_set_state(
+            app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateErrorOccured);
+        break;
+    case AvrIspErrorVerification:
+        avr_isp_chip_detect_set_state(
+            app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateErrorVerification);
+        break;
+
+    default:
+        avr_isp_chip_detect_set_state(
+            app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateNoDetect);
+        break;
+    }
+    app->error = AvrIspErrorNoError;
+    avr_isp_chip_detect_view_set_callback(
+        app->avr_isp_chip_detect_view, avr_isp_scene_chip_detect_callback, app);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewChipDetect);
+}
+
+bool avr_isp_scene_chip_detect_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case AvrIspCustomEventSceneChipDetectOk:
+
+            if(scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) ==
+               AvrIspViewProgrammer) {
+                scene_manager_next_scene(app->scene_manager, AvrIspSceneProgrammer);
+            } else if(
+                scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) ==
+                AvrIspViewReader) {
+                scene_manager_next_scene(app->scene_manager, AvrIspSceneInputName);
+            } else if(
+                scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) ==
+                AvrIspViewWriter) {
+                scene_manager_next_scene(app->scene_manager, AvrIspSceneLoad);
+            }
+
+            consumed = true;
+            break;
+        default:
+            break;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+    }
+    return consumed;
+}
+
+void avr_isp_scene_chip_detect_on_exit(void* context) {
+    UNUSED(context);
+}

+ 10 - 0
scenes/avr_isp_scene_config.h

@@ -0,0 +1,10 @@
+ADD_SCENE(avr_isp, start, Start)
+ADD_SCENE(avr_isp, about, About)
+ADD_SCENE(avr_isp, programmer, Programmer)
+ADD_SCENE(avr_isp, reader, Reader)
+ADD_SCENE(avr_isp, input_name, InputName)
+ADD_SCENE(avr_isp, load, Load)
+ADD_SCENE(avr_isp, writer, Writer)
+ADD_SCENE(avr_isp, wiring, Wiring)
+ADD_SCENE(avr_isp, chip_detect, ChipDetect)
+ADD_SCENE(avr_isp, success, Success)

+ 89 - 0
scenes/avr_isp_scene_input_name.c

@@ -0,0 +1,89 @@
+#include "../avr_isp_app_i.h"
+#include <gui/modules/validators.h>
+
+#define MAX_TEXT_INPUT_LEN 22
+
+void avr_isp_scene_input_name_text_callback(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, AvrIspCustomEventSceneInputName);
+}
+
+void avr_isp_scene_input_name_get_timefilename(FuriString* name) {
+    FuriHalRtcDateTime datetime = {0};
+    furi_hal_rtc_get_datetime(&datetime);
+    furi_string_printf(
+        name,
+        "AVR_dump-%.4d%.2d%.2d-%.2d%.2d%.2d",
+        datetime.year,
+        datetime.month,
+        datetime.day,
+        datetime.hour,
+        datetime.minute,
+        datetime.second);
+}
+
+void avr_isp_scene_input_name_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    // Setup view
+    TextInput* text_input = app->text_input;
+    bool dev_name_empty = false;
+
+    FuriString* file_name = furi_string_alloc();
+
+    avr_isp_scene_input_name_get_timefilename(file_name);
+    furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX);
+    //highlighting the entire filename by default
+    dev_name_empty = true;
+
+    strncpy(app->file_name_tmp, furi_string_get_cstr(file_name), AVR_ISP_MAX_LEN_NAME);
+    text_input_set_header_text(text_input, "Name dump");
+    text_input_set_result_callback(
+        text_input,
+        avr_isp_scene_input_name_text_callback,
+        app,
+        app->file_name_tmp,
+        MAX_TEXT_INPUT_LEN, // buffer size
+        dev_name_empty);
+
+    ValidatorIsFile* validator_is_file =
+        validator_is_file_alloc_init(STORAGE_APP_DATA_PATH_PREFIX, AVR_ISP_APP_EXTENSION, "");
+    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
+
+    furi_string_free(file_name);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewTextInput);
+}
+
+bool avr_isp_scene_input_name_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_previous_scene(app->scene_manager);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == AvrIspCustomEventSceneInputName) {
+            if(strcmp(app->file_name_tmp, "") != 0) {
+                scene_manager_next_scene(app->scene_manager, AvrIspSceneReader);
+            } else {
+            }
+        }
+    }
+    return false;
+}
+
+void avr_isp_scene_input_name_on_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    // Clear validator
+    void* validator_context = text_input_get_validator_callback_context(app->text_input);
+    text_input_set_validator(app->text_input, NULL, NULL);
+    validator_is_file_free(validator_context);
+    // Clear view
+    text_input_reset(app->text_input);
+}

+ 22 - 0
scenes/avr_isp_scene_load.c

@@ -0,0 +1,22 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_load_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    if(avr_isp_load_from_file(app)) {
+        scene_manager_next_scene(app->scene_manager, AvrIspSceneWriter);
+    } else {
+        scene_manager_previous_scene(app->scene_manager);
+    }
+}
+
+bool avr_isp_scene_load_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void avr_isp_scene_load_on_exit(void* context) {
+    UNUSED(context);
+}

+ 28 - 0
scenes/avr_isp_scene_programmer.c

@@ -0,0 +1,28 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_programmer_callback(AvrIspCustomEvent event, void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void avr_isp_scene_programmer_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    avr_isp_programmer_view_set_callback(
+        app->avr_isp_programmer_view, avr_isp_scene_programmer_callback, app);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewProgrammer);
+}
+
+bool avr_isp_scene_programmer_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void avr_isp_scene_programmer_on_exit(void* context) {
+    UNUSED(context);
+}

+ 64 - 0
scenes/avr_isp_scene_reader.c

@@ -0,0 +1,64 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_reader_callback(AvrIspCustomEvent event, void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void avr_isp_scene_reader_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    avr_isp_reader_set_file_path(
+        app->avr_isp_reader_view, furi_string_get_cstr(app->file_path), app->file_name_tmp);
+    avr_isp_reader_view_set_callback(app->avr_isp_reader_view, avr_isp_scene_reader_callback, app);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewReader);
+}
+
+bool avr_isp_scene_reader_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    UNUSED(app);
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        //do not handle exit on "Back"
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case AvrIspCustomEventSceneReadingOk:
+            scene_manager_next_scene(app->scene_manager, AvrIspSceneSuccess);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneExit:
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneErrorVerification:
+            app->error = AvrIspErrorVerification;
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneErrorReading:
+            app->error = AvrIspErrorReading;
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        default:
+            break;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+        avr_isp_reader_update_progress(app->avr_isp_reader_view);
+    }
+    return consumed;
+}
+
+void avr_isp_scene_reader_on_exit(void* context) {
+    UNUSED(context);
+}

+ 75 - 0
scenes/avr_isp_scene_start.c

@@ -0,0 +1,75 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_start_submenu_callback(void* context, uint32_t index) {
+    furi_assert(context);
+    AvrIspApp* app = context;
+
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void avr_isp_scene_start_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    Submenu* submenu = app->submenu;
+    submenu_add_item(
+        submenu, "Dump AVR", SubmenuIndexAvrIspReader, avr_isp_scene_start_submenu_callback, app);
+    submenu_add_item(
+        submenu, "Flash AVR", SubmenuIndexAvrIspWriter, avr_isp_scene_start_submenu_callback, app);
+    submenu_add_item(
+        submenu,
+        "ISP Programmer",
+        SubmenuIndexAvrIspProgrammer,
+        avr_isp_scene_start_submenu_callback,
+        app);
+    submenu_add_item(
+        submenu, "Wiring", SubmenuIndexAvrIsWiring, avr_isp_scene_start_submenu_callback, app);
+    submenu_add_item(
+        submenu, "About", SubmenuIndexAvrIspAbout, avr_isp_scene_start_submenu_callback, app);
+
+    submenu_set_selected_item(
+        submenu, scene_manager_get_scene_state(app->scene_manager, AvrIspSceneStart));
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewSubmenu);
+}
+
+bool avr_isp_scene_start_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexAvrIspAbout) {
+            scene_manager_next_scene(app->scene_manager, AvrIspSceneAbout);
+            consumed = true;
+        } else if(event.event == SubmenuIndexAvrIspProgrammer) {
+            scene_manager_set_scene_state(
+                app->scene_manager, AvrIspSceneChipDetect, AvrIspViewProgrammer);
+            scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+        } else if(event.event == SubmenuIndexAvrIspReader) {
+            scene_manager_set_scene_state(
+                app->scene_manager, AvrIspSceneChipDetect, AvrIspViewReader);
+            scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+        } else if(event.event == SubmenuIndexAvrIspWriter) {
+            scene_manager_set_scene_state(
+                app->scene_manager, AvrIspSceneChipDetect, AvrIspViewWriter);
+            scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+        } else if(event.event == SubmenuIndexAvrIsWiring) {
+            scene_manager_next_scene(app->scene_manager, AvrIspSceneWiring);
+            consumed = true;
+        }
+        scene_manager_set_scene_state(app->scene_manager, AvrIspSceneStart, event.event);
+    }
+
+    return consumed;
+}
+
+void avr_isp_scene_start_on_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    submenu_reset(app->submenu);
+}

+ 44 - 0
scenes/avr_isp_scene_success.c

@@ -0,0 +1,44 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_success_popup_callback(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, AvrIspCustomEventSceneSuccess);
+}
+
+void avr_isp_scene_success_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    Popup* popup = app->popup;
+    popup_set_icon(popup, 32, 5, &I_dolphin_nice_96x59);
+    popup_set_header(popup, "Success!", 8, 22, AlignLeft, AlignBottom);
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, app);
+    popup_set_callback(popup, avr_isp_scene_success_popup_callback);
+    popup_enable_timeout(popup);
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewPopup);
+}
+
+bool avr_isp_scene_success_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == AvrIspCustomEventSceneSuccess) {
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneStart);
+            return true;
+        }
+    }
+    return false;
+}
+
+void avr_isp_scene_success_on_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    Popup* popup = app->popup;
+    popup_reset(popup);
+}

+ 21 - 0
scenes/avr_isp_scene_wiring.c

@@ -0,0 +1,21 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_wiring_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    widget_add_icon_element(app->widget, 0, 0, &I_avr_wiring);
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWidget);
+}
+
+bool avr_isp_scene_wiring_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+void avr_isp_scene_wiring_on_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    widget_reset(app->widget);
+}

+ 69 - 0
scenes/avr_isp_scene_writer.c

@@ -0,0 +1,69 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_writer_callback(AvrIspCustomEvent event, void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void avr_isp_scene_writer_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    avr_isp_writer_set_file_path(
+        app->avr_isp_writer_view, furi_string_get_cstr(app->file_path), app->file_name_tmp);
+    avr_isp_writer_view_set_callback(app->avr_isp_writer_view, avr_isp_scene_writer_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWriter);
+}
+
+bool avr_isp_scene_writer_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        //do not handle exit on "Back"
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case AvrIspCustomEventSceneExitStartMenu:
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneStart);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneExit:
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneErrorVerification:
+            app->error = AvrIspErrorVerification;
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneErrorWriting:
+            app->error = AvrIspErrorWriting;
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneErrorWritingFuse:
+            app->error = AvrIspErrorWritingFuse;
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        default:
+            break;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+        avr_isp_writer_update_progress(app->avr_isp_writer_view);
+    }
+    return consumed;
+}
+
+void avr_isp_scene_writer_on_exit(void* context) {
+    UNUSED(context);
+}

+ 213 - 0
views/avr_isp_view_chip_detect.c

@@ -0,0 +1,213 @@
+#include "avr_isp_view_chip_detect.h"
+#include <avr_isp_icons.h>
+#include <gui/elements.h>
+
+#include "../helpers/avr_isp_worker_rw.h"
+
+struct AvrIspChipDetectView {
+    View* view;
+    AvrIspWorkerRW* avr_isp_worker_rw;
+    AvrIspChipDetectViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint16_t idx;
+    const char* name_chip;
+    uint32_t flash_size;
+    AvrIspChipDetectViewState state;
+} AvrIspChipDetectViewModel;
+
+void avr_isp_chip_detect_view_set_callback(
+    AvrIspChipDetectView* instance,
+    AvrIspChipDetectViewCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void avr_isp_chip_detect_set_state(AvrIspChipDetectView* instance, AvrIspChipDetectViewState state) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, AvrIspChipDetectViewModel * model, { model->state = state; }, true);
+}
+
+void avr_isp_chip_detect_view_draw(Canvas* canvas, AvrIspChipDetectViewModel* model) {
+    canvas_clear(canvas);
+
+    char str_buf[64] = {0};
+    canvas_set_font(canvas, FontPrimary);
+
+    switch(model->state) {
+    case AvrIspChipDetectViewStateDetected:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "AVR chip detected!");
+        canvas_draw_icon(canvas, 29, 14, &I_chip_long_70x22);
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(str_buf, sizeof(str_buf), "%ld Kb", model->flash_size / 1024);
+        canvas_draw_str_aligned(canvas, 64, 25, AlignCenter, AlignCenter, str_buf);
+        canvas_draw_str_aligned(canvas, 64, 45, AlignCenter, AlignCenter, model->name_chip);
+        elements_button_right(canvas, "Next");
+        break;
+    case AvrIspChipDetectViewStateErrorOccured:
+        canvas_draw_str_aligned(
+            canvas, 64, 5, AlignCenter, AlignCenter, "Error occured, try again!");
+        canvas_draw_icon(canvas, 29, 14, &I_chip_error_70x22);
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(
+            canvas, 64, 45, AlignCenter, AlignCenter, "Check the wiring and retry");
+        break;
+    case AvrIspChipDetectViewStateErrorVerification:
+        canvas_draw_str_aligned(
+            canvas, 64, 5, AlignCenter, AlignCenter, "Data verification failed");
+        canvas_draw_icon(canvas, 29, 14, &I_chip_error_70x22);
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(
+            canvas, 64, 45, AlignCenter, AlignCenter, "Try to restart the process");
+        break;
+
+    default:
+        //AvrIspChipDetectViewStateNoDetect
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "AVR chip not found!");
+        canvas_draw_icon(canvas, 29, 12, &I_chif_not_found_83x37);
+
+        break;
+    }
+    canvas_set_font(canvas, FontSecondary);
+    elements_button_left(canvas, "Retry");
+}
+
+bool avr_isp_chip_detect_view_input(InputEvent* event, void* context) {
+    furi_assert(context);
+
+    AvrIspChipDetectView* instance = context;
+
+    if(event->type == InputTypeShort) {
+        if(event->key == InputKeyBack) {
+            return false;
+        } else if(event->key == InputKeyRight) {
+            with_view_model(
+                instance->view,
+                AvrIspChipDetectViewModel * model,
+                {
+                    if(model->state == AvrIspChipDetectViewStateDetected) {
+                        if(instance->callback)
+                            instance->callback(
+                                AvrIspCustomEventSceneChipDetectOk, instance->context);
+                    }
+                },
+                false);
+
+        } else if(event->key == InputKeyLeft) {
+            bool detect_chip = false;
+            with_view_model(
+                instance->view,
+                AvrIspChipDetectViewModel * model,
+                {
+                    if(model->state != AvrIspChipDetectViewStateDetecting) {
+                        model->state = AvrIspChipDetectViewStateDetecting;
+                        detect_chip = true;
+                    }
+                },
+                false);
+            if(detect_chip) avr_isp_worker_rw_detect_chip(instance->avr_isp_worker_rw);
+        }
+    } else {
+        return false;
+    }
+
+    return true;
+}
+
+static void avr_isp_chip_detect_detect_chip_callback(
+    void* context,
+    const char* name,
+    bool detect_chip,
+    uint32_t flash_size) {
+    furi_assert(context);
+
+    AvrIspChipDetectView* instance = context;
+    with_view_model(
+        instance->view,
+        AvrIspChipDetectViewModel * model,
+        {
+            model->name_chip = name;
+            model->flash_size = flash_size;
+            if(detect_chip) {
+                model->state = AvrIspChipDetectViewStateDetected;
+            } else {
+                model->state = AvrIspChipDetectViewStateNoDetect;
+            }
+        },
+        true);
+}
+void avr_isp_chip_detect_view_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspChipDetectView* instance = context;
+    bool detect_chip = false;
+    with_view_model(
+        instance->view,
+        AvrIspChipDetectViewModel * model,
+        {
+            if(model->state == AvrIspChipDetectViewStateNoDetect ||
+               model->state == AvrIspChipDetectViewStateDetected) {
+                detect_chip = true;
+            }
+        },
+        false);
+
+    //Start avr_isp_worker_rw
+    instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context);
+
+    avr_isp_worker_rw_set_callback(
+        instance->avr_isp_worker_rw, avr_isp_chip_detect_detect_chip_callback, instance);
+
+    if(detect_chip) avr_isp_worker_rw_detect_chip(instance->avr_isp_worker_rw);
+}
+
+void avr_isp_chip_detect_view_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspChipDetectView* instance = context;
+
+    avr_isp_worker_rw_set_callback(instance->avr_isp_worker_rw, NULL, NULL);
+    avr_isp_worker_rw_free(instance->avr_isp_worker_rw);
+}
+
+AvrIspChipDetectView* avr_isp_chip_detect_view_alloc() {
+    AvrIspChipDetectView* instance = malloc(sizeof(AvrIspChipDetectView));
+
+    // View allocation and configuration
+    instance->view = view_alloc();
+
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspChipDetectViewModel));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_chip_detect_view_draw);
+    view_set_input_callback(instance->view, avr_isp_chip_detect_view_input);
+    view_set_enter_callback(instance->view, avr_isp_chip_detect_view_enter);
+    view_set_exit_callback(instance->view, avr_isp_chip_detect_view_exit);
+
+    with_view_model(
+        instance->view,
+        AvrIspChipDetectViewModel * model,
+        { model->state = AvrIspChipDetectViewStateNoDetect; },
+        false);
+    return instance;
+}
+
+void avr_isp_chip_detect_view_free(AvrIspChipDetectView* instance) {
+    furi_assert(instance);
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View* avr_isp_chip_detect_view_get_view(AvrIspChipDetectView* instance) {
+    furi_assert(instance);
+
+    return instance->view;
+}

+ 32 - 0
views/avr_isp_view_chip_detect.h

@@ -0,0 +1,32 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/avr_isp_types.h"
+#include "../helpers/avr_isp_event.h"
+
+typedef struct AvrIspChipDetectView AvrIspChipDetectView;
+
+typedef void (*AvrIspChipDetectViewCallback)(AvrIspCustomEvent event, void* context);
+
+typedef enum {
+    AvrIspChipDetectViewStateNoDetect,
+    AvrIspChipDetectViewStateDetecting,
+    AvrIspChipDetectViewStateDetected,
+    AvrIspChipDetectViewStateErrorOccured,
+    AvrIspChipDetectViewStateErrorVerification,
+} AvrIspChipDetectViewState;
+
+void avr_isp_chip_detect_view_set_callback(
+    AvrIspChipDetectView* instance,
+    AvrIspChipDetectViewCallback callback,
+    void* context);
+
+void avr_isp_chip_detect_set_state(AvrIspChipDetectView* instance, AvrIspChipDetectViewState state);
+
+AvrIspChipDetectView* avr_isp_chip_detect_view_alloc();
+
+void avr_isp_chip_detect_view_free(AvrIspChipDetectView* instance);
+
+View* avr_isp_chip_detect_view_get_view(AvrIspChipDetectView* instance);
+
+void avr_isp_chip_detect_view_exit(void* context);

+ 134 - 0
views/avr_isp_view_programmer.c

@@ -0,0 +1,134 @@
+#include "avr_isp_view_programmer.h"
+#include <avr_isp_icons.h>
+
+#include "../helpers/avr_isp_worker.h"
+#include <gui/elements.h>
+
+struct AvrIspProgrammerView {
+    View* view;
+    AvrIspWorker* worker;
+    AvrIspProgrammerViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    AvrIspProgrammerViewStatus status;
+} AvrIspProgrammerViewModel;
+
+void avr_isp_programmer_view_set_callback(
+    AvrIspProgrammerView* instance,
+    AvrIspProgrammerViewCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void avr_isp_programmer_view_draw(Canvas* canvas, AvrIspProgrammerViewModel* model) {
+    canvas_clear(canvas);
+
+    if(model->status == AvrIspProgrammerViewStatusUSBConnect) {
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_icon(canvas, 0, 0, &I_isp_active_128x53);
+        elements_multiline_text(canvas, 45, 10, "ISP mode active");
+    } else {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_icon(canvas, 51, 6, &I_link_waiting_77x56);
+        elements_multiline_text(canvas, 0, 25, "Waiting for\nsoftware\nconnection");
+    }
+}
+
+bool avr_isp_programmer_view_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    UNUSED(context);
+
+    if(event->key == InputKeyBack || event->type != InputTypeShort) {
+        return false;
+    }
+
+    return true;
+}
+
+static void avr_isp_programmer_usb_connect_callback(void* context, bool status_connect) {
+    furi_assert(context);
+    AvrIspProgrammerView* instance = context;
+
+    with_view_model(
+        instance->view,
+        AvrIspProgrammerViewModel * model,
+        {
+            if(status_connect) {
+                model->status = AvrIspProgrammerViewStatusUSBConnect;
+            } else {
+                model->status = AvrIspProgrammerViewStatusNoUSBConnect;
+            }
+        },
+        true);
+}
+
+void avr_isp_programmer_view_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspProgrammerView* instance = context;
+    with_view_model(
+        instance->view,
+        AvrIspProgrammerViewModel * model,
+        { model->status = AvrIspProgrammerViewStatusNoUSBConnect; },
+        true);
+
+    //Start worker
+    instance->worker = avr_isp_worker_alloc(instance->context);
+
+    avr_isp_worker_set_callback(
+        instance->worker, avr_isp_programmer_usb_connect_callback, instance);
+
+    avr_isp_worker_start(instance->worker);
+}
+
+void avr_isp_programmer_view_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspProgrammerView* instance = context;
+    //Stop worker
+    if(avr_isp_worker_is_running(instance->worker)) {
+        avr_isp_worker_stop(instance->worker);
+    }
+    avr_isp_worker_set_callback(instance->worker, NULL, NULL);
+    avr_isp_worker_free(instance->worker);
+}
+
+AvrIspProgrammerView* avr_isp_programmer_view_alloc() {
+    AvrIspProgrammerView* instance = malloc(sizeof(AvrIspProgrammerView));
+
+    // View allocation and configuration
+    instance->view = view_alloc();
+
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspProgrammerViewModel));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_programmer_view_draw);
+    view_set_input_callback(instance->view, avr_isp_programmer_view_input);
+    view_set_enter_callback(instance->view, avr_isp_programmer_view_enter);
+    view_set_exit_callback(instance->view, avr_isp_programmer_view_exit);
+
+    with_view_model(
+        instance->view,
+        AvrIspProgrammerViewModel * model,
+        { model->status = AvrIspProgrammerViewStatusNoUSBConnect; },
+        false);
+    return instance;
+}
+
+void avr_isp_programmer_view_free(AvrIspProgrammerView* instance) {
+    furi_assert(instance);
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View* avr_isp_programmer_view_get_view(AvrIspProgrammerView* instance) {
+    furi_assert(instance);
+
+    return instance->view;
+}

+ 27 - 0
views/avr_isp_view_programmer.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/avr_isp_types.h"
+#include "../helpers/avr_isp_event.h"
+
+typedef struct AvrIspProgrammerView AvrIspProgrammerView;
+
+typedef void (*AvrIspProgrammerViewCallback)(AvrIspCustomEvent event, void* context);
+
+typedef enum {
+    AvrIspProgrammerViewStatusNoUSBConnect,
+    AvrIspProgrammerViewStatusUSBConnect,
+} AvrIspProgrammerViewStatus;
+
+void avr_isp_programmer_view_set_callback(
+    AvrIspProgrammerView* instance,
+    AvrIspProgrammerViewCallback callback,
+    void* context);
+
+AvrIspProgrammerView* avr_isp_programmer_view_alloc();
+
+void avr_isp_programmer_view_free(AvrIspProgrammerView* instance);
+
+View* avr_isp_programmer_view_get_view(AvrIspProgrammerView* instance);
+
+void avr_isp_programmer_view_exit(void* context);

+ 215 - 0
views/avr_isp_view_reader.c

@@ -0,0 +1,215 @@
+#include "avr_isp_view_reader.h"
+#include <gui/elements.h>
+
+#include "../helpers/avr_isp_worker_rw.h"
+
+struct AvrIspReaderView {
+    View* view;
+    AvrIspWorkerRW* avr_isp_worker_rw;
+    const char* file_path;
+    const char* file_name;
+    AvrIspReaderViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    AvrIspReaderViewStatus status;
+    float progress_flash;
+    float progress_eeprom;
+} AvrIspReaderViewModel;
+
+void avr_isp_reader_update_progress(AvrIspReaderView* instance) {
+    with_view_model(
+        instance->view,
+        AvrIspReaderViewModel * model,
+        {
+            model->progress_flash =
+                avr_isp_worker_rw_get_progress_flash(instance->avr_isp_worker_rw);
+            model->progress_eeprom =
+                avr_isp_worker_rw_get_progress_eeprom(instance->avr_isp_worker_rw);
+        },
+        true);
+}
+
+void avr_isp_reader_view_set_callback(
+    AvrIspReaderView* instance,
+    AvrIspReaderViewCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void avr_isp_reader_set_file_path(
+    AvrIspReaderView* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+
+    instance->file_path = file_path;
+    instance->file_name = file_name;
+}
+
+void avr_isp_reader_view_draw(Canvas* canvas, AvrIspReaderViewModel* model) {
+    canvas_clear(canvas);
+    char str_buf[64] = {0};
+
+    canvas_set_font(canvas, FontPrimary);
+    switch(model->status) {
+    case AvrIspReaderViewStatusIDLE:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Press start to dump");
+        canvas_set_font(canvas, FontSecondary);
+        elements_button_center(canvas, "Start");
+        break;
+    case AvrIspReaderViewStatusReading:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Reading dump");
+        break;
+    case AvrIspReaderViewStatusVerification:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifyng dump");
+        break;
+
+    default:
+        break;
+    }
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str(canvas, 0, 27, "Flash");
+    snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_flash * 100));
+    elements_progress_bar_with_text(canvas, 44, 17, 84, model->progress_flash, str_buf);
+    canvas_draw_str(canvas, 0, 43, "EEPROM");
+    snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+    elements_progress_bar_with_text(canvas, 44, 34, 84, model->progress_eeprom, str_buf);
+}
+
+bool avr_isp_reader_view_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    AvrIspReaderView* instance = context;
+
+    bool ret = true;
+    if(event->key == InputKeyBack && event->type == InputTypeShort) {
+        with_view_model(
+            instance->view,
+            AvrIspReaderViewModel * model,
+            {
+                if(model->status == AvrIspReaderViewStatusIDLE) {
+                    if(instance->callback)
+                        instance->callback(AvrIspCustomEventSceneExit, instance->context);
+                    ret = false;
+                }
+            },
+            false);
+    } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
+        with_view_model(
+            instance->view,
+            AvrIspReaderViewModel * model,
+            {
+                if(model->status == AvrIspReaderViewStatusIDLE) {
+                    model->status = AvrIspReaderViewStatusReading;
+                    avr_isp_worker_rw_read_dump_start(
+                        instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+                }
+            },
+            false);
+    }
+    return ret;
+}
+
+static void avr_isp_reader_callback_status(void* context, AvrIspWorkerRWStatus status) {
+    furi_assert(context);
+    AvrIspReaderView* instance = context;
+
+    with_view_model(
+        instance->view,
+        AvrIspReaderViewModel * model,
+        {
+            switch(status) {
+            case AvrIspWorkerRWStatusEndReading:
+                model->status = AvrIspReaderViewStatusVerification;
+                avr_isp_worker_rw_verification_start(
+                    instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+                model->status = AvrIspReaderViewStatusVerification;
+                break;
+            case AvrIspWorkerRWStatusEndVerification:
+                if(instance->callback)
+                    instance->callback(AvrIspCustomEventSceneReadingOk, instance->context);
+                break;
+            case AvrIspWorkerRWStatusErrorVerification:
+                if(instance->callback)
+                    instance->callback(AvrIspCustomEventSceneErrorVerification, instance->context);
+                break;
+
+            default:
+                //AvrIspWorkerRWStatusErrorReading;
+                if(instance->callback)
+                    instance->callback(AvrIspCustomEventSceneErrorReading, instance->context);
+                break;
+            }
+        },
+        true);
+}
+
+void avr_isp_reader_view_enter(void* context) {
+    furi_assert(context);
+    AvrIspReaderView* instance = context;
+
+    with_view_model(
+        instance->view,
+        AvrIspReaderViewModel * model,
+        {
+            model->status = AvrIspReaderViewStatusIDLE;
+            model->progress_flash = 0.0f;
+            model->progress_eeprom = 0.0f;
+        },
+        true);
+
+    //Start avr_isp_worker_rw
+    instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context);
+
+    avr_isp_worker_rw_set_callback_status(
+        instance->avr_isp_worker_rw, avr_isp_reader_callback_status, instance);
+
+    avr_isp_worker_rw_start(instance->avr_isp_worker_rw);
+}
+
+void avr_isp_reader_view_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspReaderView* instance = context;
+    //Stop avr_isp_worker_rw
+    if(avr_isp_worker_rw_is_running(instance->avr_isp_worker_rw)) {
+        avr_isp_worker_rw_stop(instance->avr_isp_worker_rw);
+    }
+
+    avr_isp_worker_rw_free(instance->avr_isp_worker_rw);
+}
+
+AvrIspReaderView* avr_isp_reader_view_alloc() {
+    AvrIspReaderView* instance = malloc(sizeof(AvrIspReaderView));
+
+    // View allocation and configuration
+    instance->view = view_alloc();
+
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspReaderViewModel));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_reader_view_draw);
+    view_set_input_callback(instance->view, avr_isp_reader_view_input);
+    view_set_enter_callback(instance->view, avr_isp_reader_view_enter);
+    view_set_exit_callback(instance->view, avr_isp_reader_view_exit);
+
+    return instance;
+}
+
+void avr_isp_reader_view_free(AvrIspReaderView* instance) {
+    furi_assert(instance);
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View* avr_isp_reader_view_get_view(AvrIspReaderView* instance) {
+    furi_assert(instance);
+
+    return instance->view;
+}

+ 35 - 0
views/avr_isp_view_reader.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/avr_isp_types.h"
+#include "../helpers/avr_isp_event.h"
+
+typedef struct AvrIspReaderView AvrIspReaderView;
+
+typedef void (*AvrIspReaderViewCallback)(AvrIspCustomEvent event, void* context);
+
+typedef enum {
+    AvrIspReaderViewStatusIDLE,
+    AvrIspReaderViewStatusReading,
+    AvrIspReaderViewStatusVerification,
+} AvrIspReaderViewStatus;
+
+void avr_isp_reader_update_progress(AvrIspReaderView* instance);
+
+void avr_isp_reader_set_file_path(
+    AvrIspReaderView* instance,
+    const char* file_path,
+    const char* file_name);
+
+void avr_isp_reader_view_set_callback(
+    AvrIspReaderView* instance,
+    AvrIspReaderViewCallback callback,
+    void* context);
+
+AvrIspReaderView* avr_isp_reader_view_alloc();
+
+void avr_isp_reader_view_free(AvrIspReaderView* instance);
+
+View* avr_isp_reader_view_get_view(AvrIspReaderView* instance);
+
+void avr_isp_reader_view_exit(void* context);

+ 268 - 0
views/avr_isp_view_writer.c

@@ -0,0 +1,268 @@
+#include "avr_isp_view_writer.h"
+#include <gui/elements.h>
+
+#include "../helpers/avr_isp_worker_rw.h"
+#include <float_tools.h>
+
+struct AvrIspWriterView {
+    View* view;
+    AvrIspWorkerRW* avr_isp_worker_rw;
+    const char* file_path;
+    const char* file_name;
+    AvrIspWriterViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    AvrIspWriterViewStatus status;
+    float progress_flash;
+    float progress_eeprom;
+} AvrIspWriterViewModel;
+
+void avr_isp_writer_update_progress(AvrIspWriterView* instance) {
+    with_view_model(
+        instance->view,
+        AvrIspWriterViewModel * model,
+        {
+            model->progress_flash =
+                avr_isp_worker_rw_get_progress_flash(instance->avr_isp_worker_rw);
+            model->progress_eeprom =
+                avr_isp_worker_rw_get_progress_eeprom(instance->avr_isp_worker_rw);
+        },
+        true);
+}
+
+void avr_isp_writer_view_set_callback(
+    AvrIspWriterView* instance,
+    AvrIspWriterViewCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void avr_isp_writer_set_file_path(
+    AvrIspWriterView* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+
+    instance->file_path = file_path;
+    instance->file_name = file_name;
+}
+
+void avr_isp_writer_view_draw(Canvas* canvas, AvrIspWriterViewModel* model) {
+    canvas_clear(canvas);
+    char str_flash[32] = {0};
+    char str_eeprom[32] = {0};
+
+    canvas_set_font(canvas, FontPrimary);
+
+    switch(model->status) {
+    case AvrIspWriterViewStatusIDLE:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Press start to write");
+        canvas_set_font(canvas, FontSecondary);
+        elements_button_center(canvas, "Start");
+        snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+        snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+        break;
+    case AvrIspWriterViewStatusWriting:
+        if(float_is_equal(model->progress_flash, 0.f)) {
+            canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifying firmware");
+            snprintf(str_flash, sizeof(str_flash), "***");
+            snprintf(str_eeprom, sizeof(str_eeprom), "***");
+        } else {
+            canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Writing dump");
+            snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+            snprintf(
+                str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+        }
+        break;
+    case AvrIspWriterViewStatusVerification:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifying dump");
+        snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+        snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+        break;
+    case AvrIspWriterViewStatusWritingFuse:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Writing fuse");
+        snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+        snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+        break;
+    case AvrIspWriterViewStatusWritingFuseOk:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Done!");
+        snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+        snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+        canvas_set_font(canvas, FontSecondary);
+        elements_button_center(canvas, "Reflash");
+        elements_button_right(canvas, "Exit");
+        break;
+
+    default:
+        break;
+    }
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str(canvas, 0, 27, "Flash");
+    // snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_flash * 100));
+    elements_progress_bar_with_text(canvas, 44, 17, 84, model->progress_flash, str_flash);
+    canvas_draw_str(canvas, 0, 43, "EEPROM");
+    // snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+    elements_progress_bar_with_text(canvas, 44, 34, 84, model->progress_eeprom, str_eeprom);
+}
+
+bool avr_isp_writer_view_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    AvrIspWriterView* instance = context;
+
+    bool ret = true;
+    if(event->key == InputKeyBack && event->type == InputTypeShort) {
+        with_view_model(
+            instance->view,
+            AvrIspWriterViewModel * model,
+            {
+                if((model->status == AvrIspWriterViewStatusIDLE) ||
+                   (model->status == AvrIspWriterViewStatusWritingFuseOk)) {
+                    if(instance->callback)
+                        instance->callback(AvrIspCustomEventSceneExit, instance->context);
+                    ret = false;
+                }
+            },
+            false);
+    } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
+        with_view_model(
+            instance->view,
+            AvrIspWriterViewModel * model,
+            {
+                if((model->status == AvrIspWriterViewStatusIDLE) ||
+                   (model->status == AvrIspWriterViewStatusWritingFuseOk)) {
+                    model->status = AvrIspWriterViewStatusWriting;
+
+                    avr_isp_worker_rw_write_dump_start(
+                        instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+                }
+            },
+            false);
+    } else if(event->key == InputKeyRight && event->type == InputTypeShort) {
+        with_view_model(
+            instance->view,
+            AvrIspWriterViewModel * model,
+            {
+                if((model->status == AvrIspWriterViewStatusIDLE) ||
+                   (model->status == AvrIspWriterViewStatusWritingFuseOk)) {
+                    if(instance->callback)
+                        instance->callback(AvrIspCustomEventSceneExitStartMenu, instance->context);
+                    ret = false;
+                }
+            },
+            false);
+    }
+    return ret;
+}
+
+static void avr_isp_writer_callback_status(void* context, AvrIspWorkerRWStatus status) {
+    furi_assert(context);
+
+    AvrIspWriterView* instance = context;
+    with_view_model(
+        instance->view,
+        AvrIspWriterViewModel * model,
+        {
+            switch(status) {
+            case AvrIspWorkerRWStatusEndWriting:
+                model->status = AvrIspWriterViewStatusVerification;
+                avr_isp_worker_rw_verification_start(
+                    instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+                model->status = AvrIspWriterViewStatusVerification;
+                break;
+            case AvrIspWorkerRWStatusErrorVerification:
+                if(instance->callback)
+                    instance->callback(AvrIspCustomEventSceneErrorVerification, instance->context);
+                break;
+            case AvrIspWorkerRWStatusEndVerification:
+                avr_isp_worker_rw_write_fuse_start(
+                    instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+                model->status = AvrIspWriterViewStatusWritingFuse;
+                break;
+            case AvrIspWorkerRWStatusErrorWritingFuse:
+                if(instance->callback)
+                    instance->callback(AvrIspCustomEventSceneErrorWritingFuse, instance->context);
+                break;
+            case AvrIspWorkerRWStatusEndWritingFuse:
+                model->status = AvrIspWriterViewStatusWritingFuseOk;
+                break;
+
+            default:
+                //AvrIspWorkerRWStatusErrorWriting;
+                if(instance->callback)
+                    instance->callback(AvrIspCustomEventSceneErrorWriting, instance->context);
+                break;
+            }
+        },
+        true);
+}
+
+void avr_isp_writer_view_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspWriterView* instance = context;
+    with_view_model(
+        instance->view,
+        AvrIspWriterViewModel * model,
+        {
+            model->status = AvrIspWriterViewStatusIDLE;
+            model->progress_flash = 0.0f;
+            model->progress_eeprom = 0.0f;
+        },
+        true);
+
+    //Start avr_isp_worker_rw
+    instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context);
+
+    avr_isp_worker_rw_set_callback_status(
+        instance->avr_isp_worker_rw, avr_isp_writer_callback_status, instance);
+
+    avr_isp_worker_rw_start(instance->avr_isp_worker_rw);
+}
+
+void avr_isp_writer_view_exit(void* context) {
+    furi_assert(context);
+    AvrIspWriterView* instance = context;
+
+    //Stop avr_isp_worker_rw
+    if(avr_isp_worker_rw_is_running(instance->avr_isp_worker_rw)) {
+        avr_isp_worker_rw_stop(instance->avr_isp_worker_rw);
+    }
+
+    avr_isp_worker_rw_free(instance->avr_isp_worker_rw);
+}
+
+AvrIspWriterView* avr_isp_writer_view_alloc() {
+    AvrIspWriterView* instance = malloc(sizeof(AvrIspWriterView));
+
+    // View allocation and configuration
+    instance->view = view_alloc();
+
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspWriterViewModel));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_writer_view_draw);
+    view_set_input_callback(instance->view, avr_isp_writer_view_input);
+    view_set_enter_callback(instance->view, avr_isp_writer_view_enter);
+    view_set_exit_callback(instance->view, avr_isp_writer_view_exit);
+
+    return instance;
+}
+
+void avr_isp_writer_view_free(AvrIspWriterView* instance) {
+    furi_assert(instance);
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View* avr_isp_writer_view_get_view(AvrIspWriterView* instance) {
+    furi_assert(instance);
+
+    return instance->view;
+}

+ 37 - 0
views/avr_isp_view_writer.h

@@ -0,0 +1,37 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/avr_isp_types.h"
+#include "../helpers/avr_isp_event.h"
+
+typedef struct AvrIspWriterView AvrIspWriterView;
+
+typedef void (*AvrIspWriterViewCallback)(AvrIspCustomEvent event, void* context);
+
+typedef enum {
+    AvrIspWriterViewStatusIDLE,
+    AvrIspWriterViewStatusWriting,
+    AvrIspWriterViewStatusVerification,
+    AvrIspWriterViewStatusWritingFuse,
+    AvrIspWriterViewStatusWritingFuseOk,
+} AvrIspWriterViewStatus;
+
+void avr_isp_writer_update_progress(AvrIspWriterView* instance);
+
+void avr_isp_writer_set_file_path(
+    AvrIspWriterView* instance,
+    const char* file_path,
+    const char* file_name);
+
+void avr_isp_writer_view_set_callback(
+    AvrIspWriterView* instance,
+    AvrIspWriterViewCallback callback,
+    void* context);
+
+AvrIspWriterView* avr_isp_writer_view_alloc();
+
+void avr_isp_writer_view_free(AvrIspWriterView* instance);
+
+View* avr_isp_writer_view_get_view(AvrIspWriterView* instance);
+
+void avr_isp_writer_view_exit(void* context);