/*************************************************************************** * 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 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; /* ************************************************************************* * write_flash_page * ************************************************************************* */ static void write_flash_page(void) { uint16_t pagestart = addr; uint8_t size = SPM_PAGESIZE; uint8_t *p = buf; 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: data = pgm_read_byte_near(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 */ static void (*jump_to_app)(void) __attribute__ ((noreturn)) = 0x0000; /* ************************************************************************* * 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<