/* Famicom Dumper/Programmer * * Copyright notice for this file: * Copyright (C) 2020 Cluster * http://clusterrr.com * clusterrr@clusterrr.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "defines.h" #include #include #include #include #include #include "usart.h" #include "comm.h" #include "dumper.h" #include "crc.h" static void (*jump_to_bootloader)(void) = (void*)0xF800; uint16_t flash_buffer_mask = 0xFFC0; ISR(USART0_RX_vect) { unsigned char b; while (UCSR0A & (1< 1) { value >>= 1; bit_value++; } flash_buffer_mask = 0xFFFF << bit_value; } static void set_address(uint16_t address) { unsigned char l = address & 0xFF; unsigned char h = (address >> 8) & 0xFF; PORTA = l; PORTC = h; // PPU /A13 if ((address >> 13) & 1) PORTF &= ~(1<<4); else PORTF |= 1<<4; } static inline void set_romsel(uint16_t address) { if (address & 0x8000) { ROMSEL_LOW; } else { ROMSEL_HI; } } static inline void set_coolboy_rd(uint16_t address) { if (address & 0x8000) { COOLBOY_PORT |= 1< 0) { comm_send_byte(read_prg_byte(address)); len--; address++; } set_address(0); LED_GREEN_OFF; } static void read_chr_send(uint16_t address, uint16_t len) { LED_GREEN_ON; comm_start(COMMAND_CHR_READ_RESULT, len); while (len > 0) { comm_send_byte(read_chr_byte(address)); len--; address++; } set_address(0); LED_GREEN_OFF; } static void read_prg_crc_send(uint16_t address, uint16_t len) { LED_GREEN_ON; uint16_t crc = 0; MODE_READ; PRG_READ; PHI2_HI; set_romsel(address); // set /ROMSEL low if need while (len > 0) { PORTA = address & 0xFF; PORTC = (address >> 8) & 0xFF; _delay_us(1); crc = calc_crc16(crc, PIND); len--; address++; } ROMSEL_HI; set_address(0); comm_start(COMMAND_PRG_READ_RESULT, 2); comm_send_byte(crc & 0xFF); comm_send_byte((crc >> 8) & 0xFF); LED_GREEN_OFF; } static void read_chr_crc_send(uint16_t address, uint16_t len) { LED_GREEN_ON; uint16_t crc = 0; while (len > 0) { crc = calc_crc16(crc, read_chr_byte(address)); len--; address++; } set_address(0); comm_start(COMMAND_CHR_READ_RESULT, 2); comm_send_byte(crc & 0xFF); comm_send_byte((crc >> 8) & 0xFF); LED_GREEN_OFF; } static void write_prg_byte(uint16_t address, uint8_t data) { PHI2_LOW; ROMSEL_HI; MODE_WRITE; PRG_WRITE; PORTD = data; set_address(address); // PHI2 low, ROMSEL always HIGH _delay_us(1); PHI2_HI; set_romsel(address); // ROMSEL is low if need, PHI2 high set_coolboy_wr(address); // COOLBOY's /we is low if need _delay_us(1); // WRITING // PHI2 low, ROMSEL high PHI2_LOW; ROMSEL_HI; set_coolboy_wr(0); // Back to read mode _delay_us(1); PRG_READ; MODE_READ; set_address(0); // Set phi2 to high state to keep cartridge unreseted PHI2_HI; } static void write_chr_byte(uint16_t address, uint8_t data) { PHI2_LOW; ROMSEL_HI; MODE_WRITE; PORTD = data; set_address(address); // PHI2 low, ROMSEL always HIGH CHR_WRITE_LOW; _delay_us(1); // WRITING CHR_WRITE_HI; MODE_READ; set_address(0); PHI2_HI; } static void write_prg(uint16_t address, uint16_t len, uint8_t* data) { LED_RED_ON; while (len > 0) { write_prg_byte(address, *data); address++; len--; data++; } LED_RED_OFF; } static void write_chr(uint16_t address, uint16_t len, uint8_t* data) { LED_RED_ON; while (len > 0) { write_chr_byte(address, *data); address++; len--; data++; } LED_RED_OFF; } static inline void write_prg_flash_command(uint16_t address, uint8_t data) { write_prg_byte(address | 0x8000, data); } static void erase_flash_sector() { LED_RED_ON; write_prg_flash_command(0x0000, 0xF0); write_prg_flash_command(0x0AAA, 0xAA); write_prg_flash_command(0x0555, 0x55); write_prg_flash_command(0x0AAA, 0x80); write_prg_flash_command(0x0AAA, 0xAA); write_prg_flash_command(0x0555, 0x55); write_prg_flash_command(0x0000, 0x30); uint8_t res; int16_t last_res = -1; TCNT1 = 0; // waiting for result while (1) { if (TCNT1 >= 23437) // 3 seconds { // timeout comm_start(COMMAND_FLASH_ERASE_TIMEOUT, 0); break; } res = read_prg_byte(0x8000); if ((last_res == -1) || ((res != (last_res & 0xFF)))) { // in progress last_res = res; continue; } // done if (res == 0xFF) { // ok comm_start(COMMAND_PRG_WRITE_DONE, 0); break; } else { // error comm_start(COMMAND_FLASH_ERASE_ERROR, 1); comm_send_byte(res); break; } } LED_RED_OFF; } static void write_flash(uint16_t address, uint16_t len, uint8_t* data) { LED_RED_ON; while (len > 0) { uint16_t count = 0; uint8_t* d = data; uint16_t a = address; uint16_t last_address; uint8_t last_data; uint16_t address_base = a & flash_buffer_mask; while ((len > 0) && ((a & flash_buffer_mask) == address_base)) { if (*d != 0xFF) count++; a++; len--; d++; } if (count) { write_prg_flash_command(0x0000, 0xF0); write_prg_flash_command(0x0AAA, 0xAA); write_prg_flash_command(0x0555, 0x55); write_prg_flash_command(0x0000, 0x25); write_prg_flash_command(0x0000, count-1); while (count > 0) { if (*data != 0xFF) { write_prg_flash_command(address, *data); last_address = address; last_data = *data; count--; } address++; data++; } write_prg_flash_command(0x0000, 0x29); TCNT1 = 0; // waiting for result while (1) { if (TCNT1 >= 7812) // 1 second { // timeout comm_start(COMMAND_FLASH_WRITE_TIMEOUT, 0); LED_RED_OFF; return; } uint8_t read_1 = read_prg_byte(last_address | 0x8000); uint8_t read_2 = read_prg_byte(last_address | 0x8000); uint8_t read_3 = read_prg_byte(last_address | 0x8000); if (((read_1 ^ read_2) & (1 << 6)) && ((read_2 ^ read_3) & (1 << 6))) { if (read_1 & (1 << 1)) { comm_start(COMMAND_FLASH_WRITE_ERROR, 3); comm_send_byte(read_1); comm_send_byte(read_2); comm_send_byte(read_3); LED_RED_OFF; return; } else if (read_1 & (1 << 5)) { comm_start(COMMAND_FLASH_WRITE_TIMEOUT, 3); comm_send_byte(read_1); comm_send_byte(read_2); comm_send_byte(read_3); LED_RED_OFF; return; } } else { read_1 = read_prg_byte(last_address | 0x8000); read_2 = read_prg_byte(last_address | 0x8000); if (read_1 == read_2 && read_2 == last_data) break; // ok } } } address = a; data = d; } comm_start(COMMAND_PRG_WRITE_DONE, 0); LED_RED_OFF; } static uint8_t transfer_fds_byte(uint8_t *output, uint8_t input, uint8_t *end_of_head) { TCNT1 = 0; while (!IRQ_FIRED) { // waiting for interrupt // timeout 5 secs if (TCNT1 >= 39060) { write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop comm_start(COMMAND_FDS_TIMEOUT, 0); return 0; } } if (output) *output = read_prg_byte(FDS_DATA_READ); write_prg_byte(FDS_DATA_WRITE, input); // clear interrupt uint8_t status = read_prg_byte(FDS_DISK_STATUS); if (end_of_head) *end_of_head |= (status >> 6) & 1; TCNT1 = 0; while (IRQ_FIRED) { // is interrupt flag cleared? // timeout 5 secs if (TCNT1 >= 39060) { write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop comm_start(COMMAND_FDS_TIMEOUT, 0); return 0; } } return 1; } static uint8_t read_fds_block_send(uint16_t length, uint8_t send, uint8_t *crc_ok, uint8_t *end_of_head, uint16_t *file_size, uint32_t gap_delay) { uint8_t data; uint8_t status; uint32_t b; write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_CONTROL_MOTOR_ON); // motor on without transfer if (gap_delay < 30000) DELAY_CLOCK(gap_delay); else DELAY_KILO_CLOCK(gap_delay / 1000); if (send) { LED_GREEN_ON; comm_start(COMMAND_FDS_READ_RESULT_BLOCK, length + 2); } // start transfer, enable IRQ write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_CONTROL_MOTOR_ON | FDS_CONTROL_TRANSFER_ON | FDS_CONTROL_IRQ_ON); for (b = 0; b < length; b++) { if (!transfer_fds_byte(&data, 0, end_of_head)) return 0; if (file_size) { if (b == 13) *file_size |= data; else if (b == 14) *file_size |= data << 8; } if (send) comm_send_byte(data); } if (!transfer_fds_byte(0, 0, end_of_head)) return 0; write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_CONTROL_MOTOR_ON | FDS_CONTROL_TRANSFER_ON | FDS_CONTROL_IRQ_ON | FDS_CONTROL_CRC); // enable CRC control if (!transfer_fds_byte(0, 0, end_of_head)) return 0; status = read_prg_byte(FDS_DISK_STATUS); *crc_ok &= ((status >> 4) & 1) ^ 1; *end_of_head |= (status >> 6) & 1; if (send) { comm_send_byte(*crc_ok); // CRC check result comm_send_byte(*end_of_head); // end of head meet? } LED_GREEN_OFF; return 1; // success } static uint8_t write_fds_block(uint8_t *data, uint16_t length, uint32_t gap_delay) { uint8_t end_of_head = 0; LED_RED_ON; write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_CONTROL_MOTOR_ON); // motor on without transfer read_prg_byte(FDS_DRIVE_STATUS); // check if disk is inserted write_prg_byte(FDS_CONTROL, FDS_CONTROL_WRITE | FDS_CONTROL_MOTOR_ON); // enable writing without transfer if (gap_delay < 30000) DELAY_CLOCK(gap_delay); else DELAY_KILO_CLOCK(gap_delay / 1000); write_prg_byte(FDS_DATA_WRITE, 0x00); // write $00 // start transfer, enable IRQ write_prg_byte(FDS_CONTROL, FDS_CONTROL_WRITE | FDS_CONTROL_MOTOR_ON | FDS_CONTROL_TRANSFER_ON | FDS_CONTROL_IRQ_ON); transfer_fds_byte(0, 0x80, &end_of_head); // write $80 while (length) { if (end_of_head) { write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop comm_start(COMMAND_FDS_END_OF_HEAD, 0); return 0; } if (!transfer_fds_byte(0, *data, &end_of_head)) { write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop comm_start(COMMAND_FDS_TIMEOUT, 0); return 0; } data++; length--; } if (!transfer_fds_byte(0, 0xFF, &end_of_head)) { write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop comm_start(COMMAND_FDS_TIMEOUT, 0); return 0; } if (end_of_head) { write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop comm_start(COMMAND_FDS_END_OF_HEAD, 0); return 0; } write_prg_byte(FDS_CONTROL, FDS_CONTROL_WRITE | FDS_CONTROL_MOTOR_ON | FDS_CONTROL_TRANSFER_ON | FDS_CONTROL_IRQ_ON | FDS_CONTROL_CRC); // enable CRC control DELAY_CLOCK(FDS_WRITE_CRC_DELAY); TCNT1 = 0; while (1) { uint8_t status = read_prg_byte(FDS_DRIVE_STATUS); if (!(status & 2)) break; // ready // timeout 1 sec if (TCNT1 >= 7812) { write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop comm_start(COMMAND_FDS_TIMEOUT, 0); return 0; } } LED_RED_OFF; return 1; } static void fds_transfer(uint8_t block_read_start, uint8_t block_read_count, uint8_t block_write_count, uint8_t *block_write_ids, uint16_t *write_lengths, uint8_t *write_data) { uint8_t crc_ok = 1; uint8_t end_of_head = 0; uint8_t current_block = 0; uint8_t current_writing_block = 0; write_prg_byte(FDS_IRQ_CONTROL, 0x00); // disable timer IRQ write_prg_byte(FDS_MASTER_IO, 0x01); // enable disk registers write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset uint8_t ram_adapter_connected = 1; write_prg_byte(FDS_EXT_WRITE, 0x00); // Ext. connector write_prg_byte(0x0000, 0xFF); // To prevent open bus read if ((read_prg_byte(FDS_EXT_READ) & 0x7F) != 0x00) ram_adapter_connected = 0; write_prg_byte(FDS_EXT_WRITE, 0xFF); // Ext. connector write_prg_byte(0x0000, 0x00); // To prevent open bus read if ((read_prg_byte(FDS_EXT_READ) & 0x7F) != 0x7F) ram_adapter_connected = 0; if (!ram_adapter_connected) { comm_start(COMMAND_FDS_NOT_CONNECTED, 0); return; } if (read_prg_byte(FDS_DRIVE_STATUS) & 1) { comm_start(COMMAND_FDS_DISK_NOT_INSERTED, 0); return; } // battery test write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_CONTROL_MOTOR_ON); // monor on, unreset _delay_ms(100); if ((read_prg_byte(FDS_EXT_READ) & 0x80) == 0) { // battery low write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop comm_start(COMMAND_FDS_BATTERY_LOW, 0); return; } // waiting until drive is rewinded TCNT1 = 0; uint8_t secs = 0; do { // timeout 15 secs if (TCNT1 >= 7812) { TCNT1 = 0; secs++; if (secs >= 15) { write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop comm_start(COMMAND_FDS_TIMEOUT, 0); return; } } } while (!(read_prg_byte(FDS_DRIVE_STATUS) & 2)); TCNT1 = 0; secs = 0; do { // timeout 15 secs if (TCNT1 >= 7812) { TCNT1 = 0; secs++; if (secs >= 15) { write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop comm_start(COMMAND_FDS_TIMEOUT, 0); return; } } } while (read_prg_byte(FDS_DRIVE_STATUS) & 2); // disk info block if (block_write_count && (current_block == block_write_ids[current_writing_block])) { // gap delay while writing = ~28300 bits = (~28300 / 8)bits * ~165cycles = ~583687.5 uint16_t write_length = write_lengths[current_writing_block]; if (!write_fds_block(write_data, write_length, FDS_WRITE_GAP_BEFORE_FIRST_BLOCK)) return; write_data += write_length; current_writing_block++; block_write_count--; } else { // gap delay while reading = ~486974 cycles if (!read_fds_block_send(56, (current_block >= block_read_start) && block_read_count, &crc_ok, &end_of_head, 0, FDS_READ_GAP_BEFORE_FIRST_BLOCK)) return; } if ((current_block >= block_read_start) && block_read_count) block_read_count--; current_block++; if (crc_ok && !end_of_head && (block_read_count || block_write_count)) { // file amount block if (block_write_count && (current_block == block_write_ids[current_writing_block])) { uint16_t write_length = write_lengths[current_writing_block]; if (!write_fds_block(write_data, write_length, FDS_WRITE_GAP_BETWEEN_BLOCKS)) return; write_data += write_length; current_writing_block++; block_write_count--; } else { if (!read_fds_block_send(2, (current_block >= block_read_start) && block_read_count, &crc_ok, &end_of_head, 0, FDS_READ_GAP_BETWEEN_BLOCKS)) return; } if ((current_block >= block_read_start) && block_read_count) block_read_count--; current_block++; } while (crc_ok && !end_of_head && (block_read_count || block_write_count)) { // file header block uint16_t file_size = 0; // size of the next file if (block_write_count && (current_block == block_write_ids[current_writing_block])) { uint16_t write_length = write_lengths[current_writing_block]; if (!write_fds_block(write_data, write_length, FDS_WRITE_GAP_BETWEEN_BLOCKS)) return; write_data += write_length; current_writing_block++; block_write_count--; } else { if (!read_fds_block_send(16, (current_block >= block_read_start) && block_read_count, &crc_ok, &end_of_head, &file_size, FDS_READ_GAP_BETWEEN_BLOCKS)) return; } if ((current_block >= block_read_start) && block_read_count) block_read_count--; current_block++; if (crc_ok && !end_of_head && (block_read_count || block_write_count)) { // file data block if (block_write_count && (current_block == block_write_ids[current_writing_block])) { uint16_t write_length = write_lengths[current_writing_block]; if (!write_fds_block(write_data, write_length, FDS_WRITE_GAP_BETWEEN_BLOCKS)) return; write_data += write_length; current_writing_block++; block_write_count--; } else { if (!read_fds_block_send(file_size + 1, (current_block >= block_read_start) && block_read_count, &crc_ok, &end_of_head, 0, FDS_READ_GAP_BETWEEN_BLOCKS)) return; } if ((current_block >= block_read_start) && block_read_count) block_read_count--; current_block++; } } write_prg_byte(FDS_CONTROL, FDS_CONTROL_READ | FDS_MOTOR_OFF); // reset, stop _delay_ms(50); if (current_writing_block && !block_write_count && !block_read_count) { comm_start(COMMAND_FDS_WRITE_DONE, 0); return; } comm_start(COMMAND_FDS_READ_RESULT_END, 0); } static void get_mirroring() { comm_start(COMMAND_MIRRORING_RESULT, 4); LED_GREEN_ON; set_address(0); _delay_us(1); comm_send_byte((PINE >> 2) & 1); set_address(1<<10); _delay_us(1); comm_send_byte((PINE >> 2) & 1); set_address(1<<11); _delay_us(1); comm_send_byte((PINE >> 2) & 1); set_address((1<<10) | (1<<11)); _delay_us(1); comm_send_byte((PINE >> 2) & 1); set_address(0); } static void init_ports() { DDRB |= (1 << 6) | (1 << 7); // LEDS DDRF = 0b10110111; // CPU R/W, IRQ, PPU /RD, PPU /A13, CIRAM /CE, PPU /WR, /ROMSEL, PHI2 PORTF = 0b11111111; // CPU R/W, IRQ, PPU /RD, PPU /A13, CIRAM /CE, PPU /WR, /ROMSEL, PHI2 DDRE &= ~(1<<2); // CIRAM A10 PORTE |= 1<<2; // CIRAM A10 MODE_READ; set_address(0); DDRA = 0xFF; // Address low DDRC = 0xFF; // Address high COOLBOY_DDR |= (1<= 10000) { if (!led_down) { led_bright++; if (led_bright >= 110) led_down = 1; } else { led_bright--; if (!led_bright) led_down = 0; } if (led_bright >= 100) OCR1B = led_bright - 100; if (led_down) { int led_bright2 = 110-led_bright; if (led_bright2 <= 20) { if (led_bright2 > 10) led_bright2 = 20 - led_bright2; OCR1C = led_bright2*2; } } t = 0; } if (comm_recv_done) { comm_recv_done = 0; t = led_down = led_bright = 0; // Just timer without PWM for timeouts TCCR1A = OCR1B = OCR1C = 0; TCCR1B = (1<> 8) & 0xFF); comm_send_byte((RECV_BUFFER_SIZE - 5) & 0xFF); comm_send_byte(((RECV_BUFFER_SIZE - 5) >> 8) & 0xFF); break; case COMMAND_COOLBOY_READ_REQUEST: case COMMAND_PRG_READ_REQUEST: address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8); length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8); read_prg_send(address, length); break; case COMMAND_PRG_CRC_READ_REQUEST: address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8); length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8); read_prg_crc_send(address, length); break; case COMMAND_PRG_WRITE_REQUEST: address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8); length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8); write_prg(address, length, (uint8_t*)&recv_buffer[4]); comm_start(COMMAND_PRG_WRITE_DONE, 0); break; case COMMAND_RESET: reset_phi2(); comm_start(COMMAND_RESET_ACK, 0); break; case COMMAND_FLASH_ERASE_SECTOR_REQUEST: case COMMAND_COOLBOY_ERASE_SECTOR_REQUEST: erase_flash_sector(); break; case COMMAND_FLASH_WRITE_REQUEST: case COMMAND_COOLBOY_WRITE_REQUEST: address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8); length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8); write_flash(address, length, (uint8_t*)&recv_buffer[4]); break; case COMMAND_CHR_INIT: comm_start(COMMAND_CHR_STARTED, 0); break; case COMMAND_CHR_READ_REQUEST: address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8); length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8); read_chr_send(address, length); break; case COMMAND_CHR_CRC_READ_REQUEST: address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8); length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8); read_chr_crc_send(address, length); break; case COMMAND_CHR_WRITE_REQUEST: address = recv_buffer[0] | ((uint16_t)recv_buffer[1]<<8); length = recv_buffer[2] | ((uint16_t)recv_buffer[3]<<8); write_chr(address, length, (uint8_t*)&recv_buffer[4]); comm_start(COMMAND_CHR_WRITE_DONE, 0); break; case COMMAND_FDS_READ_REQUEST: fds_transfer(recv_buffer[0], recv_buffer[1], 0, 0, 0, 0); break; case COMMAND_FDS_WRITE_REQUEST: fds_transfer(0, 0, recv_buffer[0], (uint8_t*) &recv_buffer[1], (uint16_t*) &recv_buffer[1 + recv_buffer[0]], (uint8_t*) &recv_buffer[1 + recv_buffer[0] + recv_buffer[0] * 2]); break; case COMMAND_MIRRORING_REQUEST: get_mirroring(); break; case COMMAND_SET_FLASH_BUFFER_SIZE: set_flash_buffer_size(recv_buffer[0] | ((uint16_t) recv_buffer[1] << 8)); comm_start(COMMAND_SET_VALUE_DONE, 0); break; case COMMAND_BOOTLOADER: cli(); MCUCSR = 0; jump_to_bootloader(); } LED_GREEN_OFF; LED_RED_OFF; } } }