Compare commits

...

6 Commits

2 changed files with 158 additions and 82 deletions

View File

@ -3,17 +3,17 @@ twiboot is a simple/small bootloader for AVR MCUs with integrated TWI peripheral
It was originally created to update I2C controlled BLMCs (Brushless Motor Controller) without an AVR ISP adapter. It was originally created to update I2C controlled BLMCs (Brushless Motor Controller) without an AVR ISP adapter.
twiboot acts as a slave device on a TWI/I2C bus and allows reading/writing of the internal flash memory. twiboot acts as a slave device on a TWI/I2C bus and allows reading/writing of the internal flash memory.
As a compile time option twiboot also allows reading/writing of the whole internal EEPROM memory. As a compile time option (EEPROM_SUPPORT) twiboot also allows reading/writing of the whole internal EEPROM memory.
The bootloader is not able to update itself (only application flash memory region accessible). The bootloader is not able to update itself (only application flash memory region accessible).
Currently the following AVR MCUs are supported: Currently the following AVR MCUs are supported:
AVR MCU | Flash bytes used (.text + .data) | Bootloader region size AVR MCU | Flash bytes used (.text + .data) | Bootloader region size
--- | --- | --- --- | --- | ---
atmega8 | 724 (0x2D4) | 512 words atmega8 | 802 (0x322) | 512 words
atmega88 | 744 (0x2E8) | 512 words atmega88 | 826 (0x33A) | 512 words
atmega168 | 744 (0x2E8) | 512 words atmega168 | 826 (0x33A) | 512 words
atmega328p | 744 (0x2E8) | 512 words atmega328p | 826 (0x33A) | 512 words
(Compiled on Ubuntu 18.04 LTS (gcc 5.4.0 / avr-libc 2.0.0) with EEPROM and LED support) (Compiled on Ubuntu 18.04 LTS (gcc 5.4.0 / avr-libc 2.0.0) with EEPROM and LED support)
@ -33,7 +33,7 @@ A TWI/I2C master can use the protocol to
- write the internal eeprom (byte wise) - write the internal eeprom (byte wise)
- exit the bootloader and start the application - exit the bootloader and start the application
As a compile time option twiboot can output its state with two LEDs. As a compile time option (LED_SUPPORT) twiboot can output its state with two LEDs.
One LED will flash with a frequency of 20Hz while twiboot is active (including boot wait time). One LED will flash with a frequency of 20Hz while twiboot is active (including boot wait time).
A second LED will flash when the bootloader is addressed on the TWI/I2C bus. A second LED will flash when the bootloader is addressed on the TWI/I2C bus.
@ -74,7 +74,7 @@ Read chip info | **SLA+W**, 0x02, 0x00, 0x00, 0x00, **SLA+R**, {8 bytes}, **STO*
Read 1+ flash bytes | **SLA+W**, 0x02, 0x01, addrh, addrl, **SLA+R**, {* bytes}, **STO** | Read 1+ flash bytes | **SLA+W**, 0x02, 0x01, addrh, addrl, **SLA+R**, {* bytes}, **STO** |
Read 1+ eeprom bytes | **SLA+W**, 0x02, 0x02, addrh, addrl, **SLA+R**, {* bytes}, **STO** | Read 1+ eeprom bytes | **SLA+W**, 0x02, 0x02, addrh, addrl, **SLA+R**, {* bytes}, **STO** |
Write one flash page | **SLA+W**, 0x02, 0x01, addrh, addrl, {* bytes}, **STO** | page size as indicated in chip info Write one flash page | **SLA+W**, 0x02, 0x01, addrh, addrl, {* bytes}, **STO** | page size as indicated in chip info
Write 1+ eeprom bytes | **SLA+W**, 0x02, 0x02, addrh, addrl, {* bytes}, **STO** | Write 1+ eeprom bytes | **SLA+W**, 0x02, 0x02, addrh, addrl, {* bytes}, **STO** | write 0 < n < page size bytes at once
**SLA+R** means Start Condition, Slave Address, Read Access **SLA+R** means Start Condition, Slave Address, Read Access
@ -82,9 +82,23 @@ Write 1+ eeprom bytes | **SLA+W**, 0x02, 0x02, addrh, addrl, {* bytes}, **STO**
**STO** means Stop Condition **STO** means Stop Condition
A flash page / eeprom write is only triggered after the Stop Condition.
During the write process twiboot will NOT acknowledge its slave address.
The multiboot_tool repository contains a simple linux application that uses The multiboot_tool repository contains a simple linux application that uses
this protocol to access the bootloader over linux i2c device. this protocol to access the bootloader over linux i2c device.
The ispprog programming adapter can also be used as a avr910/butterfly to twiboot protocol bridge.
## TWI/I2C Clockstretching ##
While a write is in progress twiboot will not respond on the TWI/I2C bus and the
TWI/I2C master needs to retry/poll the slave address until the write has completed.
As a compile time option (USE_CLOCKSTRETCH) the previous behavior of twiboot can be restored:
TWI/I2C Clockstretching is then used to inform the master of the duration of the write.
Please note that there are some TWI/I2C masters that do not support clockstretching.
## Development ## ## Development ##
Issue reports, feature requests, patches or simply success stories are much appreciated. Issue reports, feature requests, patches or simply success stories are much appreciated.
@ -92,5 +106,4 @@ Issue reports, feature requests, patches or simply success stories are much appr
## Roadmap ## ## Roadmap ##
Some ideas that I want to investigate / implement in twiboot: Some ideas that I want to investigate / implement in twiboot:
- find a way to not rely on TWI clock stretching during write access
- support AVR TINYs (USI peripheral, no bootloader fuse, no Read-While-Write flash) - support AVR TINYs (USI peripheral, no bootloader fuse, no Read-While-Write flash)

211
main.c
View File

@ -24,12 +24,15 @@
#define VERSION_STRING "TWIBOOT v2.1" #define VERSION_STRING "TWIBOOT v2.1"
#define EEPROM_SUPPORT 1 #define EEPROM_SUPPORT 1
#define LED_SUPPORT 1 #define LED_SUPPORT 1
#define USE_CLOCKSTRETCH 0
/* 25ms @8MHz */ #define F_CPU 8000000ULL
#define TIMER_RELOAD (0xFF - 195) #define TIMER_DIVISOR 1024
#define TIMER_IRQFREQ_MS 25
#define TIMEOUT_MS 1000
/* 40 * 25ms */ #define TIMER_MSEC2TICKS(x) ((x * F_CPU) / (TIMER_DIVISOR * 1000ULL))
#define TIMEOUT 40 #define TIMER_MSEC2IRQCNT(x) (x / TIMER_IRQFREQ_MS)
#if LED_SUPPORT #if LED_SUPPORT
#define LED_INIT() DDRB = ((1<<PORTB4) | (1<<PORTB5)) #define LED_INIT() DDRB = ((1<<PORTB4) | (1<<PORTB5))
@ -56,23 +59,19 @@
/* SLA+R */ /* SLA+R */
#define CMD_WAIT 0x00 #define CMD_WAIT 0x00
#define CMD_READ_VERSION 0x01 #define CMD_READ_VERSION 0x01
#define CMD_READ_MEMORY 0x02 #define CMD_ACCESS_MEMORY 0x02
/* internal mappings */ /* internal mappings */
#define CMD_READ_CHIPINFO (0x10 | CMD_READ_MEMORY) #define CMD_ACCESS_CHIPINFO (0x10 | CMD_ACCESS_MEMORY)
#define CMD_READ_FLASH (0x20 | CMD_READ_MEMORY) #define CMD_ACCESS_FLASH (0x20 | CMD_ACCESS_MEMORY)
#define CMD_READ_EEPROM (0x30 | CMD_READ_MEMORY) #define CMD_ACCESS_EEPROM (0x30 | CMD_ACCESS_MEMORY)
#define CMD_READ_PARAMETERS (0x40 | CMD_READ_MEMORY) /* only in APP */ #define CMD_WRITE_FLASH_PAGE (0x40 | CMD_ACCESS_MEMORY)
#define CMD_WRITE_EEPROM_PAGE (0x50 | CMD_ACCESS_MEMORY)
/* SLA+W */ /* SLA+W */
#define CMD_SWITCH_APPLICATION CMD_READ_VERSION #define CMD_SWITCH_APPLICATION CMD_READ_VERSION
#define CMD_WRITE_MEMORY CMD_READ_MEMORY
/* internal mappings */ /* internal mappings */
#define CMD_BOOT_BOOTLOADER (0x10 | CMD_SWITCH_APPLICATION) /* only in APP */ #define CMD_BOOT_BOOTLOADER (0x10 | CMD_SWITCH_APPLICATION) /* only in APP */
#define CMD_BOOT_APPLICATION (0x20 | CMD_SWITCH_APPLICATION) #define CMD_BOOT_APPLICATION (0x20 | CMD_SWITCH_APPLICATION)
#define CMD_WRITE_CHIPINFO (0x10 | CMD_WRITE_MEMORY) /* invalid */
#define CMD_WRITE_FLASH (0x20 | CMD_WRITE_MEMORY)
#define CMD_WRITE_EEPROM (0x30 | CMD_WRITE_MEMORY)
#define CMD_WRITE_PARAMETERS (0x40 | CMD_WRITE_MEMORY) /* only in APP */
/* CMD_SWITCH_APPLICATION parameter */ /* CMD_SWITCH_APPLICATION parameter */
#define BOOTTYPE_BOOTLOADER 0x00 /* only in APP */ #define BOOTTYPE_BOOTLOADER 0x00 /* only in APP */
@ -82,7 +81,6 @@
#define MEMTYPE_CHIPINFO 0x00 #define MEMTYPE_CHIPINFO 0x00
#define MEMTYPE_FLASH 0x01 #define MEMTYPE_FLASH 0x01
#define MEMTYPE_EEPROM 0x02 #define MEMTYPE_EEPROM 0x02
#define MEMTYPE_PARAMETERS 0x03 /* only in APP */
/* /*
* LED_GN flashes with 20Hz (while bootloader is running) * LED_GN flashes with 20Hz (while bootloader is running)
@ -130,8 +128,7 @@ const static uint8_t chipinfo[8] = {
#endif #endif
}; };
/* wait 40 * 25ms = 1s */ static uint8_t boot_timeout = TIMER_MSEC2IRQCNT(TIMEOUT_MS);
static uint8_t boot_timeout = TIMEOUT;
static uint8_t cmd = CMD_WAIT; static uint8_t cmd = CMD_WAIT;
/* flash buffer */ /* flash buffer */
@ -147,26 +144,24 @@ static void write_flash_page(void)
uint8_t size = SPM_PAGESIZE; uint8_t size = SPM_PAGESIZE;
uint8_t *p = buf; uint8_t *p = buf;
if (pagestart >= BOOTLOADER_START) if (pagestart < BOOTLOADER_START)
{ {
return; 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();
boot_rww_enable();
} }
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();
boot_rww_enable();
} /* write_flash_page */ } /* write_flash_page */
@ -174,12 +169,11 @@ static void write_flash_page(void)
/* ************************************************************************* /* *************************************************************************
* read_eeprom_byte * read_eeprom_byte
* ************************************************************************* */ * ************************************************************************* */
static uint8_t read_eeprom_byte(void) static uint8_t read_eeprom_byte(uint16_t address)
{ {
EEARL = addr; EEARL = address;
EEARH = (addr >> 8); EEARH = (address >> 8);
EECR |= (1<<EERE); EECR |= (1<<EERE);
addr++;
return EEDR; return EEDR;
} /* read_eeprom_byte */ } /* read_eeprom_byte */
@ -195,17 +189,34 @@ static void write_eeprom_byte(uint8_t val)
EEDR = val; EEDR = val;
addr++; addr++;
#if defined (__AVR_ATmega8__) #if defined (EEWE)
EECR |= (1<<EEMWE); EECR |= (1<<EEMWE);
EECR |= (1<<EEWE); EECR |= (1<<EEWE);
#elif defined (__AVR_ATmega88__) || defined (__AVR_ATmega168__) || \ #elif defined (EEPE)
defined (__AVR_ATmega328P__)
EECR |= (1<<EEMPE); EECR |= (1<<EEMPE);
EECR |= (1<<EEPE); EECR |= (1<<EEPE);
#else
#error "EEWE/EEPE not defined"
#endif #endif
eeprom_busy_wait(); eeprom_busy_wait();
} /* write_eeprom_byte */ } /* write_eeprom_byte */
#if (USE_CLOCKSTRETCH == 0)
/* *************************************************************************
* write_eeprom_buffer
* ************************************************************************* */
static void write_eeprom_buffer(uint8_t size)
{
uint8_t *p = buf;
while (size--)
{
write_eeprom_byte(*p++);
}
} /* write_eeprom_buffer */
#endif /* (USE_CLOCKSTRETCH == 0) */
#endif /* EEPROM_SUPPORT */ #endif /* EEPROM_SUPPORT */
@ -222,12 +233,13 @@ static uint8_t TWI_data_write(uint8_t bcnt, uint8_t data)
switch (data) switch (data)
{ {
case CMD_SWITCH_APPLICATION: case CMD_SWITCH_APPLICATION:
case CMD_WRITE_MEMORY: case CMD_ACCESS_MEMORY:
/* no break */ /* no break */
case CMD_WAIT: case CMD_WAIT:
/* abort countdown */ /* abort countdown */
boot_timeout = 0; boot_timeout = 0;
cmd = data;
break; break;
default: default:
@ -236,8 +248,6 @@ static uint8_t TWI_data_write(uint8_t bcnt, uint8_t data)
ack = 0x00; ack = 0x00;
break; break;
} }
cmd = data;
break; break;
case 1: case 1:
@ -252,19 +262,19 @@ static uint8_t TWI_data_write(uint8_t bcnt, uint8_t data)
ack = 0x00; ack = 0x00;
break; break;
case CMD_WRITE_MEMORY: case CMD_ACCESS_MEMORY:
if (data == MEMTYPE_CHIPINFO) if (data == MEMTYPE_CHIPINFO)
{ {
cmd = CMD_WRITE_CHIPINFO; cmd = CMD_ACCESS_CHIPINFO;
} }
else if (data == MEMTYPE_FLASH) else if (data == MEMTYPE_FLASH)
{ {
cmd = CMD_WRITE_FLASH; cmd = CMD_ACCESS_FLASH;
} }
#if (EEPROM_SUPPORT) #if (EEPROM_SUPPORT)
else if (data == MEMTYPE_EEPROM) else if (data == MEMTYPE_EEPROM)
{ {
cmd = CMD_WRITE_EEPROM; cmd = CMD_ACCESS_EEPROM;
} }
#endif /* (EEPROM_SUPPORT) */ #endif /* (EEPROM_SUPPORT) */
else else
@ -288,20 +298,41 @@ static uint8_t TWI_data_write(uint8_t bcnt, uint8_t data)
default: default:
switch (cmd) switch (cmd)
{ {
case CMD_WRITE_FLASH:
buf[bcnt -4] = data;
if (bcnt >= sizeof(buf) +3)
{
write_flash_page();
ack = 0x00;
}
break;
#if (EEPROM_SUPPORT) #if (EEPROM_SUPPORT)
case CMD_WRITE_EEPROM: #if (USE_CLOCKSTRETCH)
case CMD_ACCESS_EEPROM:
write_eeprom_byte(data); write_eeprom_byte(data);
break; break;
#else
case CMD_ACCESS_EEPROM:
cmd = CMD_WRITE_EEPROM_PAGE;
/* fall through */
case CMD_WRITE_EEPROM_PAGE:
#endif /* (USE_CLOCKSTRETCH) */
#endif /* (EEPROM_SUPPORT) */ #endif /* (EEPROM_SUPPORT) */
case CMD_ACCESS_FLASH:
{
uint8_t pos = bcnt -4;
buf[pos] = data;
if (pos >= (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: default:
ack = 0x00; ack = 0x00;
@ -328,18 +359,18 @@ static uint8_t TWI_data_read(uint8_t bcnt)
data = info[bcnt]; data = info[bcnt];
break; break;
case CMD_READ_CHIPINFO: case CMD_ACCESS_CHIPINFO:
bcnt %= sizeof(chipinfo); bcnt %= sizeof(chipinfo);
data = chipinfo[bcnt]; data = chipinfo[bcnt];
break; break;
case CMD_READ_FLASH: case CMD_ACCESS_FLASH:
data = pgm_read_byte_near(addr++); data = pgm_read_byte_near(addr++);
break; break;
#if (EEPROM_SUPPORT) #if (EEPROM_SUPPORT)
case CMD_READ_EEPROM: case CMD_ACCESS_EEPROM:
data = read_eeprom_byte(); data = read_eeprom_byte(addr++);
break; break;
#endif /* (EEPROM_SUPPORT) */ #endif /* (EEPROM_SUPPORT) */
@ -373,7 +404,6 @@ static void TWI_vect(void)
if (TWI_data_write(bcnt++, TWDR) == 0x00) if (TWI_data_write(bcnt++, TWDR) == 0x00)
{ {
control &= ~(1<<TWEA); control &= ~(1<<TWEA);
bcnt = 0;
} }
break; break;
@ -389,8 +419,38 @@ static void TWI_vect(void)
/* prev. SLA+W, data received, NACK returned -> IDLE */ /* prev. SLA+W, data received, NACK returned -> IDLE */
case 0x88: case 0x88:
TWI_data_write(bcnt++, TWDR);
/* fall through */
/* STOP or repeated START -> IDLE */ /* STOP or repeated START -> IDLE */
case 0xA0: 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<<TWEA);
TWCR = (1<<TWINT) | control;
#if (EEPROM_SUPPORT)
if (cmd == CMD_WRITE_EEPROM_PAGE)
{
write_eeprom_buffer(bcnt -4);
}
else
#endif /* (EEPROM_SUPPORT) */
{
write_flash_page();
}
}
#endif /* (USE_CLOCKSTRETCH) */
bcnt = 0;
/* fall through */
/* prev. SLA+R, data sent, NACK returned -> IDLE */ /* prev. SLA+R, data sent, NACK returned -> IDLE */
case 0xC0: case 0xC0:
LED_RT_OFF(); LED_RT_OFF();
@ -413,7 +473,7 @@ static void TWI_vect(void)
static void TIMER0_OVF_vect(void) static void TIMER0_OVF_vect(void)
{ {
/* restart timer */ /* restart timer */
TCNT0 = TIMER_RELOAD; TCNT0 = 0xFF - TIMER_MSEC2TICKS(TIMER_IRQFREQ_MS);
/* blink LED while running */ /* blink LED while running */
LED_GN_TOGGLE(); LED_GN_TOGGLE();
@ -483,11 +543,12 @@ int main(void)
LED_GN_ON(); LED_GN_ON();
/* timer0: running with F_CPU/1024 */ /* timer0: running with F_CPU/1024 */
#if defined (__AVR_ATmega8__) #if defined (TCCR0)
TCCR0 = (1<<CS02) | (1<<CS00); TCCR0 = (1<<CS02) | (1<<CS00);
#elif defined (__AVR_ATmega88__) || defined (__AVR_ATmega168__) || \ #elif defined (TCCR0B)
defined (__AVR_ATmega328P__)
TCCR0B = (1<<CS02) | (1<<CS00); TCCR0B = (1<<CS02) | (1<<CS00);
#else
#error "TCCR0(B) not defined"
#endif #endif
/* TWI init: set address, auto ACKs */ /* TWI init: set address, auto ACKs */
@ -501,19 +562,20 @@ int main(void)
TWI_vect(); TWI_vect();
} }
#if defined (__AVR_ATmega8__) #if defined (TIFR)
if (TIFR & (1<<TOV0)) if (TIFR & (1<<TOV0))
{ {
TIMER0_OVF_vect(); TIMER0_OVF_vect();
TIFR = (1<<TOV0); TIFR = (1<<TOV0);
} }
#elif defined (__AVR_ATmega88__) || defined (__AVR_ATmega168__) || \ #elif defined (TIFR0)
defined (__AVR_ATmega328P__)
if (TIFR0 & (1<<TOV0)) if (TIFR0 & (1<<TOV0))
{ {
TIMER0_OVF_vect(); TIMER0_OVF_vect();
TIFR0 = (1<<TOV0); TIFR0 = (1<<TOV0);
} }
#else
#error "TIFR(0) not defined"
#endif #endif
} }
@ -521,11 +583,12 @@ int main(void)
TWCR = 0x00; TWCR = 0x00;
/* disable timer0 */ /* disable timer0 */
#if defined (__AVR_ATmega8__) #if defined (TCCR0)
TCCR0 = 0x00; TCCR0 = 0x00;
#elif defined (__AVR_ATmega88__) || defined (__AVR_ATmega168__) || \ #elif defined (TCCR0B)
defined (__AVR_ATmega328P__)
TCCR0B = 0x00; TCCR0B = 0x00;
#else
#error "TCCR0(B) not defined"
#endif #endif
LED_OFF(); LED_OFF();