/*************************************************************************** * Copyright (C) 10/2020 by Olaf Rempel * * razzor@kopf-tisch.de * * * * 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; version 2 of the License, * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #define VERSION_STRING "TWIBOOT v3.1" #define EEPROM_SUPPORT 1 #define LED_SUPPORT 1 #ifndef USE_CLOCKSTRETCH #define USE_CLOCKSTRETCH 0 #endif #ifndef VIRTUAL_BOOT_SECTION #define VIRTUAL_BOOT_SECTION 0 #endif #ifndef TWI_ADDRESS #define TWI_ADDRESS 0x29 #endif #define F_CPU 8000000ULL #define TIMER_DIVISOR 1024 #define TIMER_IRQFREQ_MS 25 #define TIMEOUT_MS 1000 #define TIMER_MSEC2TICKS(x) ((x * F_CPU) / (TIMER_DIVISOR * 1000ULL)) #define TIMER_MSEC2IRQCNT(x) (x / TIMER_IRQFREQ_MS) #if (LED_SUPPORT) #define LED_INIT() DDRB = ((1<> 8) & 0xFF, BOOTLOADER_START & 0xFF, #if (EEPROM_SUPPORT) ((E2END +1) >> 8 & 0xFF), (E2END +1) & 0xFF #else 0x00, 0x00 #endif }; static uint8_t boot_timeout = TIMER_MSEC2IRQCNT(TIMEOUT_MS); static uint8_t cmd = CMD_WAIT; /* flash buffer */ static uint8_t buf[SPM_PAGESIZE]; static uint16_t addr; #if (VIRTUAL_BOOT_SECTION) /* reset/application vectors received from host, needed for verify read */ static uint8_t rstvect_save[2]; static uint8_t appvect_save[2]; #endif /* (VIRTUAL_BOOT_SECTION) */ /* ************************************************************************* * write_flash_page * ************************************************************************* */ static void write_flash_page(void) { uint16_t pagestart = addr; uint8_t size = SPM_PAGESIZE; uint8_t *p = buf; #if (VIRTUAL_BOOT_SECTION) if (pagestart == (RSTVECT_ADDR & ~(SPM_PAGESIZE -1))) { /* save original vectors for verify read */ rstvect_save[0] = buf[RSTVECT_PAGE_OFFSET]; rstvect_save[1] = buf[RSTVECT_PAGE_OFFSET + 1]; appvect_save[0] = buf[APPVECT_PAGE_OFFSET]; appvect_save[1] = buf[APPVECT_PAGE_OFFSET + 1]; /* replace reset vector with jump to bootloader address */ uint16_t rst_vector = OPCODE_RJMP(BOOTLOADER_START -1); buf[RSTVECT_PAGE_OFFSET] = (rst_vector & 0xFF); buf[RSTVECT_PAGE_OFFSET + 1] = (rst_vector >> 8) & 0xFF; /* replace application vector with jump to original reset vector */ uint16_t app_vector = rstvect_save[0] | (rstvect_save[1] << 8); app_vector = OPCODE_RJMP(app_vector - APPVECT_NUM); buf[APPVECT_PAGE_OFFSET] = (app_vector & 0xFF); buf[APPVECT_PAGE_OFFSET + 1] = (app_vector >> 8) & 0xFF; } #endif /* (VIRTUAL_BOOT_SECTION) */ if (pagestart < BOOTLOADER_START) { boot_page_erase(pagestart); boot_spm_busy_wait(); do { uint16_t data = *p++; data |= *p++ << 8; boot_page_fill(addr, data); addr += 2; size -= 2; } while (size); boot_page_write(pagestart); boot_spm_busy_wait(); #if defined (ASRE) || defined (RWWSRE) /* only required for bootloader section */ boot_rww_enable(); #endif } } /* write_flash_page */ #if (EEPROM_SUPPORT) /* ************************************************************************* * read_eeprom_byte * ************************************************************************* */ static uint8_t read_eeprom_byte(uint16_t address) { EEARL = address; EEARH = (address >> 8); EECR |= (1<> 8); EEDR = val; addr++; #if defined (EEWE) EECR |= (1<= (sizeof(buf) -2)) { ack = 0x00; } if ((cmd == CMD_ACCESS_FLASH) && (pos >= (sizeof(buf) -1)) ) { #if (USE_CLOCKSTRETCH) write_flash_page(); #else cmd = CMD_WRITE_FLASH_PAGE; #endif } break; } default: ack = 0x00; break; } break; } return ack; } /* TWI_data_write */ /* ************************************************************************* * TWI_data_read * ************************************************************************* */ static uint8_t TWI_data_read(uint8_t bcnt) { uint8_t data; switch (cmd) { case CMD_READ_VERSION: bcnt %= sizeof(info); data = info[bcnt]; break; case CMD_ACCESS_CHIPINFO: bcnt %= sizeof(chipinfo); data = chipinfo[bcnt]; break; case CMD_ACCESS_FLASH: switch (addr) { /* return cached values for verify read */ #if (VIRTUAL_BOOT_SECTION) case RSTVECT_ADDR: data = rstvect_save[0]; break; case (RSTVECT_ADDR + 1): data = rstvect_save[1]; break; case APPVECT_ADDR: data = appvect_save[0]; break; case (APPVECT_ADDR + 1): data = appvect_save[1]; break; #endif /* (VIRTUAL_BOOT_SECTION) */ default: data = pgm_read_byte_near(addr); break; } addr++; break; #if (EEPROM_SUPPORT) case CMD_ACCESS_EEPROM: data = read_eeprom_byte(addr++); break; #endif /* (EEPROM_SUPPORT) */ default: data = 0xFF; break; } return data; } /* TWI_data_read */ #if defined (TWCR) /* ************************************************************************* * TWI_vect * ************************************************************************* */ static void TWI_vect(void) { static uint8_t bcnt; uint8_t control = TWCR; switch (TWSR & 0xF8) { /* SLA+W received, ACK returned -> receive data and ACK */ case 0x60: bcnt = 0; LED_RT_ON(); break; /* prev. SLA+W, data received, ACK returned -> receive data and ACK */ case 0x80: if (TWI_data_write(bcnt++, TWDR) == 0x00) { /* the ACK returned by TWI_data_write() is not for the current * data in TWDR, but for the next byte received */ control &= ~(1< send data */ case 0xA8: bcnt = 0; LED_RT_ON(); /* fall through */ /* prev. SLA+R, data sent, ACK returned -> send data */ case 0xB8: TWDR = TWI_data_read(bcnt++); break; /* prev. SLA+W, data received, NACK returned -> IDLE */ case 0x88: TWI_data_write(bcnt++, TWDR); /* fall through */ /* STOP or repeated START -> IDLE */ case 0xA0: #if (USE_CLOCKSTRETCH == 0) if ((cmd == CMD_WRITE_FLASH_PAGE) #if (EEPROM_SUPPORT) || (cmd == CMD_WRITE_EEPROM_PAGE) #endif ) { /* disable ACK for now, re-enable after page write */ control &= ~(1< IDLE */ case 0xC0: LED_RT_OFF(); control |= (1< reset hardware */ default: control |= (1< prepare ACK/NAK */ else if (state == USI_STATE_SLA) { bcnt = 0; /* SLA+W received -> send ACK */ if (data == ((TWI_ADDRESS<<1) | 0x00)) { LED_RT_ON(); usi_state = USI_STATE_SLAW_ACK | USI_WAIT_FOR_ACK | USI_ENABLE_SDA_OUTPUT | USI_ENABLE_SCL_HOLD; USIDR = 0x00; } /* SLA+R received -> send ACK */ else if (data == ((TWI_ADDRESS<<1) | 0x01)) { LED_RT_ON(); usi_state = USI_STATE_SLAR_ACK | USI_WAIT_FOR_ACK | USI_ENABLE_SDA_OUTPUT | USI_ENABLE_SCL_HOLD; USIDR = 0x00; } /* not addressed -> send NAK */ else { usi_state = USI_STATE_NAK | USI_WAIT_FOR_ACK | USI_ENABLE_SDA_OUTPUT | USI_ENABLE_SCL_HOLD; USIDR = 0x80; } } /* sent NAK -> go to idle */ else if (state == USI_STATE_NAK) { usi_state = USI_STATE_IDLE; } /* sent ACK after SLA+W -> wait for data */ /* sent ACK after DAT+W -> wait for more data */ else if ((state == USI_STATE_SLAW_ACK) || (state == USI_STATE_DATW_ACK) ) { usi_state = USI_STATE_DATW | USI_ENABLE_SCL_HOLD; } /* data received -> send ACK/NAK */ else if (state == USI_STATE_DATW) { if (TWI_data_write(bcnt++, data)) { usi_state = USI_STATE_DATW_ACK | USI_WAIT_FOR_ACK | USI_ENABLE_SDA_OUTPUT | USI_ENABLE_SCL_HOLD; USIDR = 0x00; } else { usi_state = USI_STATE_NAK | USI_WAIT_FOR_ACK | USI_ENABLE_SDA_OUTPUT | USI_ENABLE_SCL_HOLD; USIDR = 0x80; } } /* sent ACK after SLA+R -> send data */ /* received ACK after DAT+R -> send more data */ else if ((state == USI_STATE_SLAR_ACK) || ((state == USI_STATE_DATR_ACK) && !(data & 0x01)) ) { USIDR = TWI_data_read(bcnt++); usi_state = USI_STATE_DATR | USI_ENABLE_SDA_OUTPUT | USI_ENABLE_SCL_HOLD; } /* sent data after SLA+R -> receive ACK/NAK */ else if (state == USI_STATE_DATR) { usi_state = USI_STATE_DATR_ACK | USI_WAIT_FOR_ACK | USI_ENABLE_SCL_HOLD; USIDR = 0x80; } /* received NAK after DAT+R -> go to idle */ else if ((state == USI_STATE_DATR_ACK) && (data & 0x01)) { usi_state = USI_STATE_IDLE; } /* default -> go to idle */ else { usi_state = USI_STATE_IDLE; } /* set SDA direction according to current state */ if (usi_state & USI_ENABLE_SDA_OUTPUT) { USI_PIN_SDA_OUTPUT(); } else { USI_PIN_SDA_INPUT(); } if (usi_state & USI_ENABLE_SCL_HOLD) { /* Enable TWI Mode, hold SCL low after counter overflow, count both SCL edges */ USICR = (1< 1) { boot_timeout--; } else if (boot_timeout == 1) { /* trigger app-boot */ cmd = CMD_BOOT_APPLICATION; } } /* TIMER0_OVF_vect */ #if (VIRTUAL_BOOT_SECTION) static void (*jump_to_app)(void) __attribute__ ((noreturn)) = (void*)APPVECT_ADDR; #else static void (*jump_to_app)(void) __attribute__ ((noreturn)) = (void*)0x0000; #endif /* ************************************************************************* * init1 * ************************************************************************* */ void init1(void) __attribute__((naked, section(".init1"))); void init1(void) { /* make sure r1 is 0x00 */ asm volatile ("clr __zero_reg__"); /* on some MCUs the stack pointer defaults NOT to RAMEND */ #if defined(__AVR_ATmega8__) || defined(__AVR_ATmega8515__) || \ defined(__AVR_ATmega8535__) || defined (__AVR_ATmega16__) || \ defined (__AVR_ATmega32__) || defined (__AVR_ATmega64__) || \ defined (__AVR_ATmega128__) || defined (__AVR_ATmega162__) SP = RAMEND; #endif } /* init1 */ /* * For newer devices the watchdog timer remains active even after a * system reset. So disable it as soon as possible. * automagically called on startup */ #if defined (__AVR_ATmega88__) || defined (__AVR_ATmega168__) || \ defined (__AVR_ATmega328P__) /* ************************************************************************* * disable_wdt_timer * ************************************************************************* */ void disable_wdt_timer(void) __attribute__((naked, section(".init3"))); void disable_wdt_timer(void) { MCUSR = 0; WDTCSR = (1<