From 04879fb511038fc78dc6a367d98ebaced8a769bc Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 12 Aug 2023 04:31:55 +0300 Subject: [PATCH] move base pack here --- application.fam | 17 + avr_app_icon_10x10.png | Bin 0 -> 3614 bytes avr_isp_app.c | 179 +++++ avr_isp_app_i.c | 31 + avr_isp_app_i.h | 44 ++ helpers/avr_isp.c | 496 ++++++++++++ helpers/avr_isp.h | 70 ++ helpers/avr_isp_event.h | 23 + helpers/avr_isp_types.h | 32 + helpers/avr_isp_worker.c | 266 +++++++ helpers/avr_isp_worker.h | 49 ++ helpers/avr_isp_worker_rw.c | 1157 ++++++++++++++++++++++++++++ helpers/avr_isp_worker_rw.h | 99 +++ helpers/flipper_i32hex_file.c | 321 ++++++++ helpers/flipper_i32hex_file.h | 55 ++ images/avr_app_icon_10x10.png | Bin 0 -> 3614 bytes images/avr_wiring.png | Bin 0 -> 4513 bytes images/chif_not_found_83x37.png | Bin 0 -> 3742 bytes images/chip_error_70x22.png | Bin 0 -> 3688 bytes images/chip_long_70x22.png | Bin 0 -> 3656 bytes images/chip_not_found_83x37.png | Bin 0 -> 3779 bytes images/dolphin_nice_96x59.png | Bin 0 -> 2459 bytes images/isp_active_128x53.png | Bin 0 -> 3961 bytes images/link_waiting_77x56.png | Bin 0 -> 3883 bytes lib/driver/avr_isp_chip_arr.c | 386 ++++++++++ lib/driver/avr_isp_chip_arr.h | 33 + lib/driver/avr_isp_prog.c | 639 +++++++++++++++ lib/driver/avr_isp_prog.h | 16 + lib/driver/avr_isp_prog_cmd.h | 97 +++ lib/driver/avr_isp_spi_sw.c | 71 ++ lib/driver/avr_isp_spi_sw.h | 24 + lib/driver/clock.png | Bin 0 -> 3649 bytes scenes/avr_isp_scene.c | 30 + scenes/avr_isp_scene.h | 29 + scenes/avr_isp_scene_about.c | 99 +++ scenes/avr_isp_scene_chip_detect.c | 72 ++ scenes/avr_isp_scene_config.h | 10 + scenes/avr_isp_scene_input_name.c | 89 +++ scenes/avr_isp_scene_load.c | 22 + scenes/avr_isp_scene_programmer.c | 28 + scenes/avr_isp_scene_reader.c | 64 ++ scenes/avr_isp_scene_start.c | 75 ++ scenes/avr_isp_scene_success.c | 44 ++ scenes/avr_isp_scene_wiring.c | 21 + scenes/avr_isp_scene_writer.c | 69 ++ views/avr_isp_view_chip_detect.c | 213 +++++ views/avr_isp_view_chip_detect.h | 32 + views/avr_isp_view_programmer.c | 134 ++++ views/avr_isp_view_programmer.h | 27 + views/avr_isp_view_reader.c | 215 ++++++ views/avr_isp_view_reader.h | 35 + views/avr_isp_view_writer.c | 268 +++++++ views/avr_isp_view_writer.h | 37 + 53 files changed, 5718 insertions(+) create mode 100644 application.fam create mode 100644 avr_app_icon_10x10.png create mode 100644 avr_isp_app.c create mode 100644 avr_isp_app_i.c create mode 100644 avr_isp_app_i.h create mode 100644 helpers/avr_isp.c create mode 100644 helpers/avr_isp.h create mode 100644 helpers/avr_isp_event.h create mode 100644 helpers/avr_isp_types.h create mode 100644 helpers/avr_isp_worker.c create mode 100644 helpers/avr_isp_worker.h create mode 100644 helpers/avr_isp_worker_rw.c create mode 100644 helpers/avr_isp_worker_rw.h create mode 100644 helpers/flipper_i32hex_file.c create mode 100644 helpers/flipper_i32hex_file.h create mode 100644 images/avr_app_icon_10x10.png create mode 100644 images/avr_wiring.png create mode 100644 images/chif_not_found_83x37.png create mode 100644 images/chip_error_70x22.png create mode 100644 images/chip_long_70x22.png create mode 100644 images/chip_not_found_83x37.png create mode 100644 images/dolphin_nice_96x59.png create mode 100644 images/isp_active_128x53.png create mode 100644 images/link_waiting_77x56.png create mode 100644 lib/driver/avr_isp_chip_arr.c create mode 100644 lib/driver/avr_isp_chip_arr.h create mode 100644 lib/driver/avr_isp_prog.c create mode 100644 lib/driver/avr_isp_prog.h create mode 100644 lib/driver/avr_isp_prog_cmd.h create mode 100644 lib/driver/avr_isp_spi_sw.c create mode 100644 lib/driver/avr_isp_spi_sw.h create mode 100644 lib/driver/clock.png create mode 100644 scenes/avr_isp_scene.c create mode 100644 scenes/avr_isp_scene.h create mode 100644 scenes/avr_isp_scene_about.c create mode 100644 scenes/avr_isp_scene_chip_detect.c create mode 100644 scenes/avr_isp_scene_config.h create mode 100644 scenes/avr_isp_scene_input_name.c create mode 100644 scenes/avr_isp_scene_load.c create mode 100644 scenes/avr_isp_scene_programmer.c create mode 100644 scenes/avr_isp_scene_reader.c create mode 100644 scenes/avr_isp_scene_start.c create mode 100644 scenes/avr_isp_scene_success.c create mode 100644 scenes/avr_isp_scene_wiring.c create mode 100644 scenes/avr_isp_scene_writer.c create mode 100644 views/avr_isp_view_chip_detect.c create mode 100644 views/avr_isp_view_chip_detect.h create mode 100644 views/avr_isp_view_programmer.c create mode 100644 views/avr_isp_view_programmer.h create mode 100644 views/avr_isp_view_reader.c create mode 100644 views/avr_isp_view_reader.h create mode 100644 views/avr_isp_view_writer.c create mode 100644 views/avr_isp_view_writer.h diff --git a/application.fam b/application.fam new file mode 100644 index 00000000000..19556d03d3a --- /dev/null +++ b/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", + ), + ], +) diff --git a/avr_app_icon_10x10.png b/avr_app_icon_10x10.png new file mode 100644 index 0000000000000000000000000000000000000000..533787fe3569f70196d3f71e8373102dfd3967a0 GIT binary patch literal 3614 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 Vpqb^9w@m;7?5&;gRaoD1{{|C}E&c!i literal 0 HcmV?d00001 diff --git a/avr_isp_app.c b/avr_isp_app.c new file mode 100644 index 00000000000..740dc3610a6 --- /dev/null +++ b/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; +} diff --git a/avr_isp_app_i.c b/avr_isp_app_i.c new file mode 100644 index 00000000000..7a7fa6d7f18 --- /dev/null +++ b/avr_isp_app_i.c @@ -0,0 +1,31 @@ +#include "avr_isp_app_i.h" +#include +#include + +#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; +} diff --git a/avr_isp_app_i.h b/avr_isp_app_i.h new file mode 100644 index 00000000000..17c69f8f2e2 --- /dev/null +++ b/avr_isp_app_i.h @@ -0,0 +1,44 @@ +#pragma once + +#include "helpers/avr_isp_types.h" +#include + +#include "scenes/avr_isp_scene.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); \ No newline at end of file diff --git a/helpers/avr_isp.c b/helpers/avr_isp.c new file mode 100644 index 00000000000..283c17bfd48 --- /dev/null +++ b/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 + +#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); +} \ No newline at end of file diff --git a/helpers/avr_isp.h b/helpers/avr_isp.h new file mode 100644 index 00000000000..476fc3d6427 --- /dev/null +++ b/helpers/avr_isp.h @@ -0,0 +1,70 @@ +#pragma once + +#include + +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); \ No newline at end of file diff --git a/helpers/avr_isp_event.h b/helpers/avr_isp_event.h new file mode 100644 index 00000000000..c704b5b356a --- /dev/null +++ b/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; diff --git a/helpers/avr_isp_types.h b/helpers/avr_isp_types.h new file mode 100644 index 00000000000..5e174ec3b9c --- /dev/null +++ b/helpers/avr_isp_types.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#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; \ No newline at end of file diff --git a/helpers/avr_isp_worker.c b/helpers/avr_isp_worker.c new file mode 100644 index 00000000000..dfe1f43c2c3 --- /dev/null +++ b/helpers/avr_isp_worker.c @@ -0,0 +1,266 @@ +#include "avr_isp_worker.h" +#include +#include "../lib/driver/avr_isp_prog.h" +#include "../lib/driver/avr_isp_prog_cmd.h" +#include "../lib/driver/avr_isp_chip_arr.h" + +#include + +#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 +#include +#include + +#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; +} diff --git a/helpers/avr_isp_worker.h b/helpers/avr_isp_worker.h new file mode 100644 index 00000000000..cd9897dff3f --- /dev/null +++ b/helpers/avr_isp_worker.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +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); diff --git a/helpers/avr_isp_worker_rw.c b/helpers/avr_isp_worker_rw.c new file mode 100644 index 00000000000..121f085650f --- /dev/null +++ b/helpers/avr_isp_worker_rw.c @@ -0,0 +1,1157 @@ +#include "avr_isp_worker_rw.h" +#include +#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 + +#include + +#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); +} \ No newline at end of file diff --git a/helpers/avr_isp_worker_rw.h b/helpers/avr_isp_worker_rw.h new file mode 100644 index 00000000000..2c52a8700d5 --- /dev/null +++ b/helpers/avr_isp_worker_rw.h @@ -0,0 +1,99 @@ +#pragma once + +#include + +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); \ No newline at end of file diff --git a/helpers/flipper_i32hex_file.c b/helpers/flipper_i32hex_file.c new file mode 100644 index 00000000000..a8c20a0bd94 --- /dev/null +++ b/helpers/flipper_i32hex_file.c @@ -0,0 +1,321 @@ +#include "flipper_i32hex_file.h" +#include +#include +#include +#include +#include + +//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; +} \ No newline at end of file diff --git a/helpers/flipper_i32hex_file.h b/helpers/flipper_i32hex_file.h new file mode 100644 index 00000000000..765b94beb59 --- /dev/null +++ b/helpers/flipper_i32hex_file.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +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); \ No newline at end of file diff --git a/images/avr_app_icon_10x10.png b/images/avr_app_icon_10x10.png new file mode 100644 index 0000000000000000000000000000000000000000..533787fe3569f70196d3f71e8373102dfd3967a0 GIT binary patch literal 3614 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 Vpqb^9w@m;7?5&;gRaoD1{{|C}E&c!i literal 0 HcmV?d00001 diff --git a/images/avr_wiring.png b/images/avr_wiring.png new file mode 100644 index 0000000000000000000000000000000000000000..957012405127ce4504ef8cd4e805b1fc31963026 GIT binary patch literal 4513 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 literal 0 HcmV?d00001 diff --git a/images/chif_not_found_83x37.png b/images/chif_not_found_83x37.png new file mode 100644 index 0000000000000000000000000000000000000000..b03bf3567ade9b2e4ce2d29328032ebd40c89401 GIT binary patch literal 3742 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 literal 0 HcmV?d00001 diff --git a/images/chip_error_70x22.png b/images/chip_error_70x22.png new file mode 100644 index 0000000000000000000000000000000000000000..16f81178c0ec857591db3fe40830a5955db54e73 GIT binary patch literal 3688 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 literal 0 HcmV?d00001 diff --git a/images/chip_long_70x22.png b/images/chip_long_70x22.png new file mode 100644 index 0000000000000000000000000000000000000000..3edfff82de952d6f49a012154a23104e3fd935fa GIT binary patch literal 3656 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 literal 0 HcmV?d00001 diff --git a/images/dolphin_nice_96x59.png b/images/dolphin_nice_96x59.png new file mode 100644 index 0000000000000000000000000000000000000000..a299d3630239b4486e249cc501872bed5996df3b GIT binary patch literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!QjS=r55&&8K)tZJ>! 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 B&F}yK literal 0 HcmV?d00001 diff --git a/images/link_waiting_77x56.png b/images/link_waiting_77x56.png new file mode 100644 index 0000000000000000000000000000000000000000..d7d32aed59a19e24a7e1f2216c806872610a0af5 GIT binary patch literal 3883 zcmaJ@cUV*DvpxvYn@AB5Hx%h4gla-hp$7zl1h54X0%AyjXebK;N)s1Eqzhu97o`}b zNtGf30)o<|iO2#gMNrxe`&;*S|G3`soHE}$^S(3lesj)qVo%$c@o`IV0|3BhVUDtA zJ~7Pe6elb5o^UGiF96_1dm)jhEs#hM)sN!gb(shN0V7!sB&@@NsKMsMI>IU@?5-8X zUW5~5kBAEsPLx-c<`T4wk$x~NV%Ky8jb@YV$cbT%j}N;gVyDV`llue5tn|b9>yKh? zzTTb+e&jt=xB01i@7a69`I5D)%3h8}PTmxAO*`!{-a^EQBOkA~x3*2qf{nwu<*0xl zXC*<}e^-_T*b3FxSCMJtcnPos4DfIQjhM_v_2bd|0$&j6XIa8-ur$&VPg!w>2?NGK z@rXRY*pwwKD^1=3$YBF6cDcLF0H@V}iwf614FF+TTj{|pfa_gp5tf`p0CbcXg91GD zfRf%bH_-r29T4`gYJ~wG)Btr0Cl7Pr>2sj5N06ri;N%6=?P4O80JdP@Vu!430B|E6 z5H?+P(*LSbCOEImR4TnfzgiB44tM2L^W|`I0-sRqu@F-c*1;dbXBdN<1JlJd!nFiG zuDt<(oJ0|3w`;orJ^W=oJv#9W{tIef8rb(`+}vjN=6Z{%#sDxy3+>xeg;Yv}>9L2A z_a2^HX7fDHlXGP=&Z9!W_!*G1FygdEJ`p$R4TrLZj2} zca&b8?B6F$PpWRS8cu2hPcIp=1ShH$oO5UWW~CsAqcu)%0>El5 zrRkj_Cu^AJ^{HO^{)*AAS?Xy!a4t5J4$h-^>5&)~x0^WGcuukO$Svt6b2gzkIZ$Veu$_!mqP98I{w5aW zXfCyC;CBcXeb%%lQLh8gh}em$GlSj@udp+C$NLOfU7#y*!}KA~TLKN5ksz9r`PQ#W z!r+$9gZa0`o&qBYhRAmH#?Qw%G+QsLgWFmV<)>7+lH9w>WlDI9+a#WzDPgUR-Ei+M zr?Ux#qZ_$&*ysol{)CA+&KhU)!Mp%;Tu$rA2$wDw>kYeR1(~D*t19`LBi~z(xoJS7 zaptPBLqZ8hA%ej%$W~oBp;)AbLiO!K7Uhqz{X+ew{XX`x3#x^gTILe6Nu47E?+Oms zT~&}uN91hQY|E_XtmLfpsw;Pvo3ZcXEr)4E``4E&#peX)wC31}X&NSuk237X3m#yP zXeYQJN*^%npV&ng9M!s#0qedlYGIXI`Y?Gw!c)w1)9cA+TFsI1#^YTTBTyKvdDT-$v<2XhVryqNgW}PQK5GUS_Ro8_srp>1dq*EMm$_(Y-MG{|g zCtD`VCrc_ru!Ti=MH59lj%$ux*o4CK4k2Zxj+zcLglRz&W4oO43o~_XARc$|$^cbqZ@%KFE8*I$^5xybzh70ZP1}{K zjWZ}Jd;mjgT538~+OOU9Fyfd=^WC~fv*DUo%uihly*VMgqBN}}nWtr44JDrSE=oyF z!4;bq+ZCHF*6WllCXn`uQKnLm<1@UGk6o4KrRGdnKtuS9O%Nh2V z>O7@9J!?Jd_U<>`54(rbwKEN%?=|K#=QH1DPCmcr65yiBC}6xGT2#!sU<(y zV9vQXN0)Pzrlnb>Cx>cFYx9rfSKB1n6lV{STAqGobTSH`i$9(Fz&={WATvVnBsVeA z^H*gp%SrV~AvGa?>>6;~dZ?x_!Wjky7zisJ2ezcqGGvc|QtnNKo5^9UI4JSRDmxZ`P5}iulKYgA{ zFWSVfh#7t}^t(S}IHRvSp)uin;f-$N^N#0Twk?$G3z3t^YqI-<{h<9mAV2IR3yC#0 z+$7xf(Dqi)@6rwNM(|PMw~FB^~mEN3B>q+eK;*UHX z`g!Or2mTX2t|gRLAu>ABDat6G8iSMQgQjZJ`^J#|lc*o46x2i}32F;_qGqYBY*+-o zq(7otqg7+n2KI1%GlG)iJIk~g67CoIc%`+1$mImoKM-6>0@`R3X5B-3B4Zu9t)o))UsXqQ;JeQrSkjm4UbguO`fS*+W3YZg`{>X zj@DjhAgdoW=)b5V=6CjV>ltAmW7n}iusX~ARPwCYuNd6 z)RDyzGw3l$+_u=R+%zhSEn3)0*(RSWwITa1wX^oK?sCZTGu~If8BU=j)C8mk=4N8K#*I z8QZRIt~IuA4Eu(@Oa$$ijs7NZPfOo9&~gpi={2$tF_1)B?Y)(ioD~uZ{yuhb^dTd7 z-o0n?k^p6;MvykukKT`)*Q?X(IlKCTwpuYdchu>HQ^phc1@af#7yZ4Y0o(T4d$k#5 z)n~n{mxJn`1$%5RNM`HyjIY-Reihvx8q9_njMuLPQ8r&~ZcK`fhx#e(_H@+_(-oFW z>ul>TtQ#+x3?s**2aR0!#y+f!UAxps&spmmGuvd3yxzN)xRD@$Je-i8&=tiOwU~X% z5C)qz^4ne5$w&4QdgZgl_8#tam5GT$LbnDN-}m&T^*u;kO-*Vb|DL=1rEyXG$!J@1 z+liN*0h-YB>u0u?n&@M6sg*~Q0=BcigRUv=dwwt9aCn=)og|)=w9m$xwzjjPeK&&n zUnx#Q<7f^P4;mfsM+8g=6gMKsf{Z5-?TL6opl>Hp9{^Yty|6eM4r2{>r;x$;gBWlC znaV^1fWA=x74Pm%q=DRsBrhKWnU&fG8ITvjK*mWMqmH2>iJo5OL4HJsARDZEkheRG zAY)_*(hq<$3CKhm9uz>n?Bfp)Fp&A17tXW~+z=Vi-yt+_1DXF6g~OZ%At`=DkS-Xi z=B}=;4$_5zi3Gfco2CceT@|FEt^tKWnWwHAR2QzH35UW!{~R*Rgnk4MxIN1BpLEQX zfs7}OMukHlbUGbO*924iNDwFt27{<;Kr}Sem=S9Jfj%^RfSQlL>`w+1(cj(Ai%RpN z_<#-=@otnWGy@rCvH$6UO#PSE$NwLtn3_QX@KgvCtbWkd&p-_3{|_aT|Bd#i*%SX; z@Bc~cj}4>}A@)Rn$`wC%=H7Y89;Bkek$yxxjpB!;P%i%z^0X&~M)CKgP(d1+U?@lt zgLn7xIq)d`4Z&dG7C!zoypKE40%ah>BmsMQ5twSCbkW*qZKOI=XDAwl(9$(UYeO}l zXs9U~iq`yzMN!x+ z{=pJ{U5nN)u@Gi4kb}MbUwi%2#T=jm^WWiRF8&>Vq7QTC{g}hea>zo1`C_o2w#K6O z_xG8mWAi{L0I=v-piHm4_!@n4Ng?1=)yPg7kcR`b10W6l{AWbI9sWut$neGM3y@Fi6uo^^Vs + +//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); \ No newline at end of file diff --git a/lib/driver/avr_isp_chip_arr.h b/lib/driver/avr_isp_chip_arr.h new file mode 100644 index 00000000000..66f16a7b96a --- /dev/null +++ b/lib/driver/avr_isp_chip_arr.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#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; \ No newline at end of file diff --git a/lib/driver/avr_isp_prog.c b/lib/driver/avr_isp_prog.c new file mode 100644 index 00000000000..0f46872ddc2 --- /dev/null +++ b/lib/driver/avr_isp_prog.c @@ -0,0 +1,639 @@ +#include "avr_isp_prog.h" +#include "avr_isp_prog_cmd.h" + +#include + +#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); + } +} diff --git a/lib/driver/avr_isp_prog.h b/lib/driver/avr_isp_prog.h new file mode 100644 index 00000000000..2c15ab06636 --- /dev/null +++ b/lib/driver/avr_isp_prog.h @@ -0,0 +1,16 @@ +#pragma once + +#include "avr_isp_spi_sw.h" +#include + +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); diff --git a/lib/driver/avr_isp_prog_cmd.h b/lib/driver/avr_isp_prog_cmd.h new file mode 100644 index 00000000000..f8b07203e66 --- /dev/null +++ b/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 diff --git a/lib/driver/avr_isp_spi_sw.c b/lib/driver/avr_isp_spi_sw.c new file mode 100644 index 00000000000..c6d9d54c892 --- /dev/null +++ b/lib/driver/avr_isp_spi_sw.c @@ -0,0 +1,71 @@ +#include "avr_isp_spi_sw.h" + +#include + +#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); +} \ No newline at end of file diff --git a/lib/driver/avr_isp_spi_sw.h b/lib/driver/avr_isp_spi_sw.h new file mode 100644 index 00000000000..44de5ff79cb --- /dev/null +++ b/lib/driver/avr_isp_spi_sw.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +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); \ No newline at end of file diff --git a/lib/driver/clock.png b/lib/driver/clock.png new file mode 100644 index 0000000000000000000000000000000000000000..93a59fe681d26795d0db342a0c55ba42fc2c7529 GIT binary patch literal 3649 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 LTALIbyM_M?u*zuP literal 0 HcmV?d00001 diff --git a/scenes/avr_isp_scene.c b/scenes/avr_isp_scene.c new file mode 100644 index 00000000000..4af125aeeaa --- /dev/null +++ b/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, +}; diff --git a/scenes/avr_isp_scene.h b/scenes/avr_isp_scene.h new file mode 100644 index 00000000000..658ee74326f --- /dev/null +++ b/scenes/avr_isp_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// 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 diff --git a/scenes/avr_isp_scene_about.c b/scenes/avr_isp_scene_about.c new file mode 100644 index 00000000000..e5f530fec89 --- /dev/null +++ b/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); +} diff --git a/scenes/avr_isp_scene_chip_detect.c b/scenes/avr_isp_scene_chip_detect.c new file mode 100644 index 00000000000..79c23939074 --- /dev/null +++ b/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); +} diff --git a/scenes/avr_isp_scene_config.h b/scenes/avr_isp_scene_config.h new file mode 100644 index 00000000000..6f22511dbe2 --- /dev/null +++ b/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) diff --git a/scenes/avr_isp_scene_input_name.c b/scenes/avr_isp_scene_input_name.c new file mode 100644 index 00000000000..3394f4362a6 --- /dev/null +++ b/scenes/avr_isp_scene_input_name.c @@ -0,0 +1,89 @@ +#include "../avr_isp_app_i.h" +#include + +#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); +} diff --git a/scenes/avr_isp_scene_load.c b/scenes/avr_isp_scene_load.c new file mode 100644 index 00000000000..e8890e37381 --- /dev/null +++ b/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); +} diff --git a/scenes/avr_isp_scene_programmer.c b/scenes/avr_isp_scene_programmer.c new file mode 100644 index 00000000000..0915e1e8a2a --- /dev/null +++ b/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); +} diff --git a/scenes/avr_isp_scene_reader.c b/scenes/avr_isp_scene_reader.c new file mode 100644 index 00000000000..8dcb4759700 --- /dev/null +++ b/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); +} diff --git a/scenes/avr_isp_scene_start.c b/scenes/avr_isp_scene_start.c new file mode 100644 index 00000000000..b00bfefce2f --- /dev/null +++ b/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); +} diff --git a/scenes/avr_isp_scene_success.c b/scenes/avr_isp_scene_success.c new file mode 100644 index 00000000000..a88ed28aa83 --- /dev/null +++ b/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); +} diff --git a/scenes/avr_isp_scene_wiring.c b/scenes/avr_isp_scene_wiring.c new file mode 100644 index 00000000000..787ed56732e --- /dev/null +++ b/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); +} diff --git a/scenes/avr_isp_scene_writer.c b/scenes/avr_isp_scene_writer.c new file mode 100644 index 00000000000..39c944fd5fc --- /dev/null +++ b/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); +} diff --git a/views/avr_isp_view_chip_detect.c b/views/avr_isp_view_chip_detect.c new file mode 100644 index 00000000000..fdcf71c36c1 --- /dev/null +++ b/views/avr_isp_view_chip_detect.c @@ -0,0 +1,213 @@ +#include "avr_isp_view_chip_detect.h" +#include +#include + +#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; +} diff --git a/views/avr_isp_view_chip_detect.h b/views/avr_isp_view_chip_detect.h new file mode 100644 index 00000000000..37f2ae23369 --- /dev/null +++ b/views/avr_isp_view_chip_detect.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#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); diff --git a/views/avr_isp_view_programmer.c b/views/avr_isp_view_programmer.c new file mode 100644 index 00000000000..34e18770b2f --- /dev/null +++ b/views/avr_isp_view_programmer.c @@ -0,0 +1,134 @@ +#include "avr_isp_view_programmer.h" +#include + +#include "../helpers/avr_isp_worker.h" +#include + +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; +} diff --git a/views/avr_isp_view_programmer.h b/views/avr_isp_view_programmer.h new file mode 100644 index 00000000000..9f005b026a2 --- /dev/null +++ b/views/avr_isp_view_programmer.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#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); diff --git a/views/avr_isp_view_reader.c b/views/avr_isp_view_reader.c new file mode 100644 index 00000000000..92d15bd7f8b --- /dev/null +++ b/views/avr_isp_view_reader.c @@ -0,0 +1,215 @@ +#include "avr_isp_view_reader.h" +#include + +#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; +} diff --git a/views/avr_isp_view_reader.h b/views/avr_isp_view_reader.h new file mode 100644 index 00000000000..44a43994815 --- /dev/null +++ b/views/avr_isp_view_reader.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#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); diff --git a/views/avr_isp_view_writer.c b/views/avr_isp_view_writer.c new file mode 100644 index 00000000000..a06b7853555 --- /dev/null +++ b/views/avr_isp_view_writer.c @@ -0,0 +1,268 @@ +#include "avr_isp_view_writer.h" +#include + +#include "../helpers/avr_isp_worker_rw.h" +#include + +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; +} diff --git a/views/avr_isp_view_writer.h b/views/avr_isp_view_writer.h new file mode 100644 index 00000000000..1ff72838765 --- /dev/null +++ b/views/avr_isp_view_writer.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#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);