├── README.md ├── TWI_Master └── TWI_Master.ino └── TWI_Slave ├── Makefile ├── TODO ├── common_define.h └── twi_slave.c /README.md: -------------------------------------------------------------------------------- 1 | # ATtiny88 I2C Bootloader 2 | 3 | The goal of this project is to provide a safe bootloader that fits within about ~1 KB of flash for the ATtiny88 (and presumably ATtiny48) chips. 4 | It was originally designed for the [keyboard.io project](http://keyboard.io). 5 | 6 | The `TWI_Slave` directory contains such a bootloader, which expects to act as an I2C slave, with an I2C address of `0x58` (which can be altered by setting two of of the pins. 7 | 8 | The `TWI_Mater` directory contains sample code for the I2C master that will flash some sample firmware to the ATtiny88. 9 | 10 | ## Building TWI_Slave 11 | 12 | 1. Install an avr toolchain (if you're on OS X, try [CrossPack for AVR®](https://www.obdev.at/products/crosspack/)) 13 | 2. `cd TWI_Slave` 14 | 3. `make` 15 | 16 | ## Bootloader Protocol 17 | 18 | The bootloader loads itself to `0x1C00` (in the upper kilobyte) of the flash, and has a protocol for updating the lower 7 KB of flash. 19 | It always ensures that the first two bytes at `0x0000` (the reset vector) are preserved so that the bootloader is always the first thing executed. 20 | If the bootloader receives no I2C commands after a few seconds, it jumps to `0x0038`, which is after the vector table and the standard AVR initialization (which has already been done). 21 | 22 | The protocol it speaks supports a few different commands. 23 | Each command starts with a single byte indicating the command to be run, and they have different formats. 24 | 25 | All two-byte values (addresses, lengths, CRCs, etc.) are sent in little-endian format. 26 | 27 | | Opcode | Description | Format | 28 | | ------ |-------------------| ------ | 29 | | 0x01 | Set page address | 01 aa bb | 30 | | 0x02 | Send frame (16 bytes) | 02 (... 16 bytes ... ) CRC16_lo CRC16_hi 00 | 31 | | 0x03 | Reset (start application) | 03 | 32 | | 0x04 | Erase flash | 04 | 33 | | 0x06 | Get version and CRC16 | 06 a0 a1 l0 l1 | 34 | 35 | To write new firmware, you must, for each page (64 bytes): 36 | 37 | * Set the page address to write. 38 | * 4 times: Send a frame (`0x02`), a frame of 16 bytes (one quarter of a page) is sent and prepared to be written. 39 | 40 | After the 4th frame is sent, the page is written. 41 | Each frame is accompanied by the CRC16 of that frame, which is checked. 42 | 43 | All commands return ACK (0) on success for the last byte, except for send frame (op 2). 44 | Send frame requires an extra 0 byte to be sent, and will return a NAK (3) on success. 45 | 46 | The get version and CRC16 (0x06) can be used to retrieve the CRC16 of any address (`a0 a1`) and length (`l0 l1`) of flash. 47 | It transmits back 3 bytes: the program version, and the CRC16 of the requested region. 48 | 49 | The recommended update procedure is: 50 | 51 | * Erase flash (4) 52 | * For each page: set the address (1), send frame (2) four times 53 | * Get version and CRC16 (6), starting at address `0x0004` (since the first four bytes are always preserved by the bootloader) to verify the entire firmware was written correctly 54 | * Reset (3) 55 | -------------------------------------------------------------------------------- /TWI_Master/TWI_Master.ino: -------------------------------------------------------------------------------- 1 | // this is an Arduino sketch to upload a firmware image to the TWI_Slave bootloader. 2 | // The example firmware is the compiled version of the following C code, which 3 | // flashes two LEDs on PB0 and PB1. 4 | // 5 | // // Main Starts from here 6 | // int main (void) { 7 | // DDRB |= ((1 << PB0) | (1 << PB1)); // PB0 and PB1 are output 8 | // while (1) { 9 | // PORTB &= ~(1 << PB0); 10 | // PORTB |= (1 << PB1); 11 | // _delay_ms(400); 12 | // PORTB |= (1 << PB0); 13 | // PORTB &= ~(1 << PB1); 14 | // _delay_ms(200); 15 | // } 16 | // } 17 | 18 | 19 | #include 20 | #include "Arduino.h" 21 | #include "Wire.h" 22 | 23 | void setup() { 24 | Wire.begin(); 25 | } 26 | 27 | #define page_size 64 28 | #define frame_size 16 29 | #define blank 0xff 30 | #define pages 3 31 | #define firmware_length 192 32 | #define DELAY 1 33 | const uint16_t offsets[pages] = {0, 64, 128}; 34 | const byte firmware[firmware_length] PROGMEM = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0xc0, 0x1a, 0xc0, 0x19, 0xc0, 0x18, 0xc0, 0x17, 0xc0, 0x16, 0xc0, 0x15, 0xc0, 0x14, 0xc0, 0x13, 0xc0, 0x12, 0xc0, 0x11, 0xc0, 0x10, 0xc0, 0xf, 0xc0, 0xe, 0xc0, 0xd, 0xc0, 0xc, 0xc0, 0xb, 0xc0, 0xa, 0xc0, 0x9, 0xc0, 0x8, 0xc0, 0x11, 0x24, 0x1f, 0xbe, 0xcf, 0xef, 0xd2, 0xe0, 0xde, 0xbf, 0xcd, 0xbf, 0x2, 0xd0, 0x1b, 0xc0, 0xe3, 0xcf, 0x84, 0xb1, 0x83, 0x60, 0x84, 0xb9, 0x28, 0x98, 0x29, 0x9a, 0x2f, 0xef, 0x83, 0xec, 0x99, 0xe0, 0x21, 0x50, 0x80, 0x40, 0x90, 0x40, 0xe1, 0xf7, 0x0, 0xc0, 0x0, 0x0, 0x28, 0x9a, 0x29, 0x98, 0x2f, 0xef, 0x81, 0xee, 0x94, 0xe0, 0x21, 0x50, 0x80, 0x40, 0x90, 0x40, 0xe1, 0xf7, 0x0, 0xc0, 0x0, 0x0, 0xe9, 0xcf, 0xf8, 0x94, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; 35 | 36 | byte written = 0; 37 | byte addr = 0x50; 38 | 39 | byte read_crc16(byte *version, uint16_t *crc16, uint16_t offset, uint16_t length) { 40 | byte result = 2; 41 | 42 | Wire.beginTransmission(addr); 43 | Wire.write(0x06); // get version and CRC16 44 | Wire.write(offset & 0xff); // addr (lo) 45 | Wire.write(offset >> 8); // addr (hi) 46 | Wire.write(length & 0xff); // len (lo) 47 | Wire.write(length >> 8); // len (hi) 48 | result = Wire.endTransmission(); 49 | if (result != 0) { 50 | return result; 51 | } 52 | Wire.requestFrom(addr, (uint8_t) 3); 53 | Serial.print("Available bytes: "); 54 | Serial.print(Wire.available()); 55 | Serial.print("\n"); 56 | if (Wire.available() == 0) { 57 | } 58 | byte v = Wire.read(); 59 | *version = v; 60 | if (Wire.available() == 0) { 61 | return 0xFF; 62 | } 63 | byte crc16_lo = Wire.read(); 64 | if (Wire.available() == 0) { 65 | return 0xFF; 66 | } 67 | byte crc16_hi = Wire.read(); 68 | while (Wire.available()) { 69 | byte c = Wire.read(); 70 | } 71 | *crc16 = (crc16_hi << 8) | crc16_lo; 72 | return result; 73 | } 74 | 75 | void loop() { 76 | if (written != 0) { 77 | // we're done 78 | return; 79 | } 80 | 81 | Serial.print("Communicating\n"); 82 | 83 | byte result = 2; 84 | while (result != 0) { 85 | Serial.print("Reading CRC16\n"); 86 | 87 | byte version; 88 | uint16_t crc16; 89 | result = read_crc16(&version, &crc16, 0, firmware_length); 90 | 91 | Serial.print("result "); 92 | Serial.print(result); 93 | Serial.print("\n"); 94 | 95 | if (result != 0) { 96 | _delay_ms(100); 97 | continue; 98 | } 99 | Serial.print("Version: "); 100 | Serial.print(version); 101 | Serial.print("\n\nExisting CRC16 of 0000-1FFF: "); 102 | Serial.print(crc16, HEX); 103 | Serial.print("\n"); 104 | } 105 | 106 | 107 | Serial.print("Erasing\n"); 108 | Wire.beginTransmission(addr); 109 | Wire.write(0x04); // erase user space 110 | result = Wire.endTransmission(); 111 | Serial.print("result = "); 112 | Serial.print(result); 113 | Serial.print("\n"); 114 | if (result != 0) { 115 | _delay_ms(1000); 116 | return; 117 | } 118 | 119 | byte o = 0; 120 | 121 | for (uint16_t i = 0; i < firmware_length; i += page_size) { 122 | Serial.print("Setting addr\n"); 123 | Wire.beginTransmission(addr); 124 | Wire.write(0x1); // write page addr 125 | Wire.write(offsets[o] & 0xff); // write page addr 126 | Wire.write(offsets[o] >> 8); 127 | result = Wire.endTransmission(); 128 | Serial.print("result = "); 129 | Serial.print(result); 130 | Serial.print("\n"); 131 | _delay_ms(DELAY); 132 | // got something other than ACK. Start over. 133 | if (result != 0) { 134 | return; 135 | } 136 | 137 | // transmit each frame separately 138 | for (uint8_t frame = 0; frame < page_size / frame_size; frame++) { 139 | Wire.beginTransmission(addr); 140 | Wire.write(0x2); // continue page 141 | uint16_t crc16 = 0xffff; 142 | for (uint8_t j = frame * frame_size; j < (frame + 1) * frame_size; j++) { 143 | if (i + j < firmware_length) { 144 | uint8_t b = pgm_read_byte(&firmware[i + j]); 145 | Wire.write(b); 146 | crc16 = _crc16_update(crc16, b); 147 | } else { 148 | Wire.write(blank); 149 | crc16 = _crc16_update(crc16, blank); 150 | } 151 | } 152 | // write the CRC16, little end first 153 | Wire.write(crc16 & 0xff); 154 | Wire.write(crc16 >> 8); 155 | Wire.write(0x00); // dummy end byte 156 | result = Wire.endTransmission(); 157 | Serial.print("got "); 158 | Serial.print(result); 159 | Serial.print(" for page "); 160 | Serial.print(offsets[o]); 161 | Serial.print(" frame "); 162 | Serial.print(frame); 163 | Serial.print("\n"); 164 | // got something other than NACK. Start over. 165 | if (result != 3) { 166 | return; 167 | } 168 | delay(DELAY); 169 | } 170 | o++; 171 | } 172 | 173 | // verify firmware 174 | while (result != 0) { 175 | Serial.print("Reading CRC16\n"); 176 | 177 | byte version; 178 | uint16_t crc16; 179 | // skip the first 4 bytes, are they were probably overwritten by the reset vector preservation 180 | result = read_crc16(&version, &crc16, offsets[0] + 4, firmware_length - 4); 181 | 182 | Serial.print("result "); 183 | Serial.print(result); 184 | Serial.print("\n"); 185 | 186 | if (result != 0) { 187 | _delay_ms(100); 188 | continue; 189 | } 190 | Serial.print("Version: "); 191 | Serial.print(version); 192 | Serial.print("\n\nCRC CRC16 of "); 193 | Serial.print(offsets[0] + 4, HEX); 194 | Serial.print("-"); 195 | Serial.print(offsets[0] + firmware_length, HEX); 196 | Serial.print(": "); 197 | Serial.print(crc16, HEX); 198 | Serial.print("\n"); 199 | 200 | // calculate our own CRC16 201 | uint16_t check_crc16 = 0xffff; 202 | for (uint16_t i = 4; i < firmware_length; i++) { 203 | check_crc16 = _crc16_update(check_crc16, pgm_read_byte(&firmware[i])); 204 | } 205 | if (crc16 != check_crc16) { 206 | Serial.print("CRC does not match ours: "); 207 | Serial.print(check_crc16, HEX); 208 | Serial.print("\n"); 209 | return; 210 | } 211 | Serial.print("CRC check: OK\n"); 212 | } 213 | 214 | written = 1; // firmware successfully rewritten 215 | 216 | Serial.print("resetting\n"); 217 | Wire.beginTransmission(addr); 218 | Wire.write(0x03); // execute app 219 | result = Wire.endTransmission(); 220 | Serial.print("result "); 221 | Serial.print(result); 222 | Serial.print("\n"); 223 | 224 | Serial.print("done\n"); 225 | } 226 | -------------------------------------------------------------------------------- /TWI_Slave/Makefile: -------------------------------------------------------------------------------- 1 | # Name: Makefile 2 | # Author: Scott Perry 3 | # Copyright: 2015 4 | # License: MIT 5 | 6 | # You should at least check the settings for 7 | # DEVICE ....... The AVR device you compile for 8 | # CLOCK ........ Target AVR clock rate in Hertz 9 | # OBJECTS ...... The object files created from your source files. This list is 10 | # usually the same as the list of source files with suffix ".o". 11 | # PROGRAMMER ... Options to avrdude which define the hardware you use for 12 | # uploading to the AVR and the interface where this hardware 13 | # is connected. We recommend that you leave it undefined and 14 | # add settings like this to your ~/.avrduderc file: 15 | # default_programmer = "dragon_isp"; 16 | # default_serial = "usb"; 17 | # FUSES ........ Parameters for avrdude to flash the fuses appropriately. 18 | 19 | # To override any of these for your local setup, create a file Makefile.local 20 | # (ignored by git) 21 | -include Makefile.local 22 | 23 | # See avr-help for all possible devices 24 | DEVICE ?= attiny88 25 | 26 | # 8Mhz 27 | CLOCK ?= 8000000 28 | 29 | # Add something like this to Makefile.local if you haven't yet set up ~/.avrduderc with a default programmer. 30 | # PROGRAMMER = -c usbtiny -P usb 31 | # PROGRAMMER = -c dragon_isp -P usb 32 | 33 | # Add more objects for each .c file here 34 | OBJECTS = twi_slave.o 35 | 36 | # For computing fuse byte values for other devices and options see 37 | # the fuse bit calculator at http://www.engbedded.com/fusecalc/ 38 | FUSES = -U lfuse:w:0xee:m -U hfuse:w:0xdd:m -U efuse:w:0xfe:m 39 | 40 | AVRDUDE_PATH ?= avrdude 41 | GCC_PATH ?= avr-gcc 42 | # Tune the lines below only if you know what you are doing: 43 | 44 | # Optimize for many things (including perf) 45 | #OPTIMIZATION = -O2 46 | 47 | # Optimize, but focus on keeping code size small 48 | OPTIMIZATION = -Os 49 | 50 | 51 | 52 | AVRDUDE = $(AVRDUDE_PATH) $(PROGRAMMER) -p $(DEVICE) 53 | COMPILE = $(GCC_PATH) -fverbose-asm -g -Wall -Wextra -Werror -pedantic $(OPTIMIZATION) -std=gnu11 -flto -mmcu=$(DEVICE) -DF_CPU=$(CLOCK) -Wl,--section-start=.text=0x1C00 54 | 55 | # symbolic targets: 56 | all: twi_slave.hex 57 | 58 | .c.o: 59 | $(COMPILE) -c $< -o $@ 60 | 61 | .S.o: 62 | $(COMPILE) -x assembler-with-cpp -c $< -o $@ 63 | # "-x assembler-with-cpp" should not be necessary since this is the default 64 | # file type for the .S (with capital S) extension. However, upper case 65 | # characters are not always preserved on Windows. To ensure WinAVR 66 | # compatibility define the file type manually. 67 | 68 | .c.s: 69 | $(COMPILE) -S $< -o $@ 70 | 71 | flash: all 72 | $(AVRDUDE) -B 2 -U flash:w:twi_slave.hex:i 73 | 74 | fuse: 75 | $(AVRDUDE) -B 100 $(FUSES) 76 | 77 | # Xcode uses the Makefile targets "", "clean" and "install" 78 | install: flash fuse 79 | 80 | # if you use a bootloader, change the command below appropriately: 81 | load: all 82 | bootloadHID twi_slave.hex 83 | 84 | clean: 85 | rm -f twi_slave.hex twi_slave.elf $(OBJECTS) 86 | 87 | # file targets: 88 | twi_slave.elf: $(OBJECTS) 89 | $(COMPILE) -o twi_slave.elf $(OBJECTS) 90 | 91 | twi_slave.hex: twi_slave.elf 92 | rm -f twi_slave.hex 93 | avr-objcopy -j .text -j .data -O ihex twi_slave.elf twi_slave.hex 94 | avr-size --format=avr --mcu=$(DEVICE) twi_slave.elf 95 | # write bootloader jump vector to beginning 96 | sed -i'' -e '$$ d' twi_slave.hex # two dollar signs because Make otherwise interprets them 97 | # write a new vector table to jump to the bootloader 98 | # rjmp to 0x1C00, etc. 99 | echo ':02000000FFCD32' >> twi_slave.hex 100 | echo ':02000200FFCD30' >> twi_slave.hex 101 | echo ':02000400FFCD2E' >> twi_slave.hex 102 | echo ':02000600FFCD2C' >> twi_slave.hex 103 | echo ':02000800FFCD2A' >> twi_slave.hex 104 | echo ':02000A00FFCD28' >> twi_slave.hex 105 | echo ':02000C00FFCD26' >> twi_slave.hex 106 | echo ':02000E00FFCD24' >> twi_slave.hex 107 | echo ':02001000FFCD22' >> twi_slave.hex 108 | echo ':02001200FFCD20' >> twi_slave.hex 109 | echo ':02001400FFCD1E' >> twi_slave.hex 110 | echo ':02001600FFCD1C' >> twi_slave.hex 111 | echo ':02001800FFCD1A' >> twi_slave.hex 112 | echo ':02001A00FFCD18' >> twi_slave.hex 113 | echo ':02001C00FFCD16' >> twi_slave.hex 114 | echo ':02001E00FFCD14' >> twi_slave.hex 115 | echo ':02002000FFCD12' >> twi_slave.hex 116 | echo ':02002200FFCD10' >> twi_slave.hex 117 | echo ':02002400FFCD0E' >> twi_slave.hex 118 | echo ':02002600FFCD0C' >> twi_slave.hex 119 | echo ':02002800FFCD0A' >> twi_slave.hex 120 | echo ':00000001FF' >> twi_slave.hex 121 | # If you have an EEPROM section, you must also create a hex file for the 122 | # EEPROM and add it to the "flash" target. 123 | 124 | # Targets for code debugging and analysis: 125 | disasm: twi_slave.elf 126 | avr-objdump -d twi_slave.elf 127 | 128 | size-map: twi_slave.elf 129 | avr-nm --size-sort -C -r -l twi_slave.elf 130 | 131 | cpp: 132 | $(COMPILE) -E main.c 133 | 134 | astyle: 135 | find . -type f -name \*.c |xargs -n 1 astyle --style=google 136 | find . -type f -name \*.h |xargs -n 1 astyle --style=google 137 | -------------------------------------------------------------------------------- /TWI_Slave/TODO: -------------------------------------------------------------------------------- 1 | [x] on the right hand, we trigger on power on. on the left hand, we can trigger on reset, since the atmega has a reset line for us 2 | * make sure to pull our address bits from the same pins as the app code 3 | * use different addresses than when runnign app code 4 | * make sure to set comm_en 5 | * see if we can fit in an LED driver to show status? 6 | * figure out how the jump vector works and what we need to do to massage user code to make it work -------------------------------------------------------------------------------- /TWI_Slave/common_define.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /*****************************************************************************/ 4 | #define TWI_CMD_PAGEUPDATE_ADDR 0x01 // TWI Command to program a flash page address to write 5 | #define TWI_CMD_PAGEUPDATE_FRAME 0x02 // TWI Command to program the next frame of the page of flash 6 | #define TWI_CMD_EXECUTEAPP 0x03 // TWI Command to jump to the application program 7 | #define TWI_CMD_ERASEFLASH 0x04 // TWI Command to erase the entire application section of flash memory 8 | #define TWI_CMD_GETCRC16 0x06 // TWI Command to get application version and CRC16 of memory contents 9 | #define TWI_CMD_GETERRCONDN 0x07 // TWI Command to get Error condition 10 | 11 | /*****************************************************************************/ 12 | 13 | 14 | #define BOOT_SETTLE_DELAY 100 // Debounce delay for the boot pin in MCU cycles 15 | #define SLAVE_BASE_ADDRESS 0x50 // The base address identifier of this slave device on the TWI (I2C) bus 16 | #define INTVECT_PAGE_ADDRESS 0x000 // The location of the start of the interrupt vector table address 17 | 18 | 19 | /***************************************************************/ 20 | 21 | 22 | // Page size selection for the controller with 8K flash 23 | // The flash memory page size for these devices 24 | #define PAGE_SIZE 64 // SPM_PAGESIZE is sometimes 32 for attiny88, which is incorrect 25 | #define FRAME_SIZE 16 // how big of chunks of data we can write over TWI 26 | 27 | // Page 112, the start of bootloader section 28 | #define BOOT_PAGE_ADDRESS 0X1C00 29 | 30 | #define FLASH_SIZE 8192 31 | 32 | // 8KB of flash divided by pages of size 64 bytes 33 | #define TOTAL_NO_OF_PAGES (FLASH_SIZE / PAGE_SIZE) 34 | 35 | // The number of pages being used for bootloader code 36 | #define BOOTLOADER_PAGES (TOTAL_NO_OF_PAGES - BOOT_PAGE_ADDRESS/PAGE_SIZE) 37 | 38 | // For bounds check during page write/erase operation to protect the bootloader code from being corrupted 39 | #define LAST_PAGE_NO_TO_BE_ERASED (TOTAL_NO_OF_PAGES - BOOTLOADER_PAGES) 40 | 41 | 42 | /*****************************************************************************/ 43 | /*****************************************************************************/ 44 | // Select the correct Bit name of to SELFPROGRAMming 45 | #define SELFPROGEN SELFPRGEN 46 | 47 | 48 | uint8_t pageBuffer[PAGE_SIZE]; 49 | /*****************************************************************************/ 50 | /*****************************************************************************/ 51 | 52 | 53 | 54 | /*****************************************************************************/ 55 | /*****************************************************************************/ 56 | 57 | #define STATUSMASK_SPMBUSY 0x01 // The mask bit for SPM busy status code 58 | #define STATUSMASK_BLSCERR 0x02 // The mask bit for attempt to override bootloader section 59 | #define STATUSMASK_TWIABORT 0x04 // The mask bit for indicating TWI abort fn called 60 | #define STATUSMASK_SLTR_BUSY 0x08 // The mask bit for slave transmit 61 | #define STATUSMASK_SLRBAA_BUSY 0x10 // The mask bit for slave receive and ack 62 | #define STATUSMASK_SLRBAN_BUSY 0x20 // The mask bit for slave receive and Nack 63 | #define STATUSMASK_EEPROM_BUSY 0x40 // The mask bit for EEPROM busy 64 | #define STATUSMASK_BOOTLOADER 0x80 // The mask bit for bootloader operations 65 | /*****************************************************************************/ 66 | /*****************************************************************************/ 67 | 68 | #define BVERSION 0x01 // This bootloader revision identifier 69 | -------------------------------------------------------------------------------- /TWI_Slave/twi_slave.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "common_define.h" 8 | #include 9 | 10 | // AD01: lower two bits of device address 11 | #define AD01 (PINB & (_BV(0) | _BV(1))) 12 | 13 | //configuring LAST_INTVECT_ADDRESS as per device selected 14 | /*****************************************************************************/ 15 | #define LAST_INTVECT_ADDRESS TWI_vect // The start of the application code 16 | 17 | 18 | /*****************************************************************************/ 19 | #define TWI_SLAW_RECEIVED 0x60 // Status slave address and write command received 20 | #define TWI_SLAR_RECEIVED 0xa8 // Status slave address and read command received 21 | #define TWI_SLAVE_TX_ACK_RECEIVED 0xb8 // Status slave transmit and acknowledgement returned 22 | #define TWI_SLAVE_TX_NACK_RECEIVED 0xc0 // Status slave transmit and no acknowledgement of last byte 23 | #define TWI_SLAVE_RX_ACK_RETURNED 0x80 // Status slave receive and acknowledgement returned 24 | #define TWI_SLAVE_RX_NACK_RETURNED 0x88 // Status slave receive and no acknowledgement of last byte 25 | 26 | #define ACK TWI_SLAVE_RX_ACK_RETURNED 27 | #define NAK TWI_SLAVE_RX_NACK_RETURNED 28 | 29 | //#define DEVICE_KEYBOARDIO_MODEL_01 30 | #define DEVICE_KEYBOARDIO_MODEL_100 31 | //#define DEVICE_KEYBOARDIO_MODEL_101 32 | 33 | struct recv_result { 34 | uint8_t val; 35 | uint8_t res; 36 | }; 37 | 38 | // globals 39 | 40 | // reuse pageAddr variable for CRC16 to save space 41 | uint16_t pageAddr; 42 | #define sendCrc16 pageAddr 43 | // which frame of the page we are processing 44 | #define frame GPIOR0 45 | 46 | void setup_pins() { 47 | 48 | #if defined DEVICE_KEYBOARDIO_MODEL_01 49 | DDRC |= _BV(7); // C7 is COMM_EN - this turns on the PCA9614 that does differential i2c between hands 50 | PORTC |= _BV(7); // Without it, the right hand can't talk to the world. 51 | 52 | DDRB = _BV(5)|_BV(3)|_BV(2); //0b00101100; 53 | PORTB &= ~(_BV(5)|_BV(3)|_BV(2)); // Drive MOSI/SCK/SS low 54 | #endif 55 | 56 | DDRC |= (_BV(3)); // set ROW3 to output 57 | // We're going to use row 3, keys # 0 and 7 to force the keyboard to stay in bootloader mode 58 | PORTC &= ~(_BV(3)); // Without it, we can't scan the keys 59 | 60 | DDRD = 0x00; // make the col pins inputs 61 | PORTD = 0xFF; // turn on pullup 62 | } 63 | 64 | void init_twi() { 65 | TWAR = (SLAVE_BASE_ADDRESS | AD01) << 1; // ignore the general call address 66 | TWCR = _BV(TWEN) | _BV(TWEA); // activate, ack our address 67 | // Enable, but don't enable ACK until we are ready to receive packets. 68 | } 69 | 70 | void wait_for_activity(uint8_t ack) { 71 | // possibly Enable ACK and clear pending interrupts. 72 | TWCR = _BV(TWINT) | _BV(TWEN) | (ack == ACK ? _BV(TWEA) : 0); 73 | 74 | do {} while ((TWCR & _BV(TWINT)) == 0); 75 | wdt_reset(); 76 | } 77 | 78 | void __attribute__ ((noinline)) abort_twi() { 79 | // Recover from error condition by releasing bus lines. 80 | TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); 81 | } 82 | 83 | void process_slave_transmit(uint8_t data) { 84 | // Prepare data for transmission. 85 | TWDR = data; 86 | 87 | wait_for_activity(ACK); 88 | 89 | // Check TWI status code for SLAVE_TX_NACK. 90 | if (TWSR != TWI_SLAVE_TX_ACK_RECEIVED) { 91 | abort_twi(); 92 | return; 93 | } 94 | } 95 | 96 | struct recv_result slave_receive_byte(uint8_t ack) { 97 | uint8_t val; 98 | // Receive byte and return ACK. 99 | wait_for_activity(ack); 100 | 101 | // Check TWI status code for SLAVE_RX_ACK. or SLAVE_RX_NACK 102 | // Basically, if the status register has the same value as 103 | // the type of packet we're looking for, then proceeed 104 | 105 | if (TWSR != ack) { 106 | abort_twi(); 107 | return (struct recv_result) { .val = 0, .res = 0 }; 108 | } 109 | 110 | // Get byte 111 | val = TWDR; 112 | if (ack == TWI_SLAVE_RX_NACK_RETURNED) { 113 | // If we're doing a NACK, then twiddle TWCR 114 | TWCR = _BV(TWINT) | _BV(TWEN); 115 | } 116 | return (struct recv_result) { .val = val, .res = 1 }; 117 | } 118 | 119 | // receive two-byte word (little endian) over TWI 120 | uint16_t slave_receive_word() { 121 | uint8_t lo; 122 | struct recv_result r; 123 | r = slave_receive_byte(ACK); 124 | lo = r.val; 125 | if (r.res) { 126 | r = slave_receive_byte(ACK); 127 | } 128 | return (r.val << 8) | lo; 129 | } 130 | 131 | // don't call this, call update_page unless you need to bypass safety checks 132 | void __attribute__ ((noinline)) unsafe_update_page(uint16_t pageAddress) { 133 | for (uint8_t i = 0; i < PAGE_SIZE; i += 2) { 134 | uint16_t tempWord; 135 | memcpy(&tempWord, &pageBuffer[i], sizeof(tempWord)); 136 | boot_spm_busy_wait(); 137 | boot_page_fill(pageAddress + i, tempWord); // Fill the temporary buffer with the given data 138 | } 139 | 140 | // Write page from temporary buffer to the given location in flash memory 141 | boot_spm_busy_wait(); 142 | boot_page_write(pageAddress); 143 | } 144 | 145 | void buffer_reset_vector() { 146 | // Load existing RESET vector contents into buffer. 147 | uint32_t v = pgm_read_dword(INTVECT_PAGE_ADDRESS); 148 | memcpy(pageBuffer, &v, sizeof(v)); 149 | } 150 | 151 | void update_page(uint16_t pageAddress) { 152 | // Mask out in-page address bits. 153 | pageAddress &= ~(PAGE_SIZE - 1); 154 | // Protect RESET vector if this is page 0. 155 | if (pageAddress == INTVECT_PAGE_ADDRESS) { 156 | buffer_reset_vector(); 157 | } 158 | 159 | // Ignore any attempt to update boot section. 160 | if (pageAddress >= BOOT_PAGE_ADDRESS) { 161 | return; 162 | } 163 | 164 | unsafe_update_page(pageAddress); 165 | } 166 | 167 | void __attribute__ ((noinline)) erase_page_buffer() { 168 | // clear the page buffer 169 | for (uint8_t i = 0; i < PAGE_SIZE; i++) { 170 | pageBuffer[i] = 0xFF; 171 | } 172 | } 173 | 174 | 175 | void process_read_address() { 176 | frame = 0; // reset which frame we are reading 177 | 178 | erase_page_buffer(); 179 | 180 | // Receive two-byte page address. 181 | pageAddr = slave_receive_word(); 182 | } 183 | 184 | uint8_t process_read_frame() { 185 | // check disabled for space reasons 186 | // Check the SPM is ready, abort if not. 187 | if ((SPMCSR & _BV(SELFPROGEN)) != 0) { 188 | abort_twi(); 189 | return 0; 190 | } 191 | 192 | #if PAGE_SIZE > 256 193 | #error update this offset optimization for larger PAGE_SIZE 194 | #endif 195 | 196 | uint8_t offset = frame * FRAME_SIZE; 197 | uint8_t *bufferPtr = &pageBuffer[offset]; 198 | 199 | // Receive page data in frame-sized chunks 200 | uint16_t crc16 = 0xffff; 201 | for (uint8_t i = 0; i < FRAME_SIZE; i++) { 202 | struct recv_result r = slave_receive_byte(ACK); 203 | *bufferPtr = r.val; 204 | if (!r.res) { 205 | return 0; 206 | } 207 | crc16 = _crc16_update(crc16, r.val); 208 | bufferPtr++; 209 | } 210 | // check received CRC16 211 | if (crc16 != slave_receive_word()) { 212 | return 0; 213 | } 214 | frame++; 215 | return 1; 216 | } 217 | 218 | // Now program if everything went well. 219 | void process_page_update() { 220 | update_page(pageAddr); 221 | } 222 | 223 | void __attribute__ ((noreturn)) cleanup_and_run_application(void) { 224 | wdt_disable(); // After Reset the WDT state does not change 225 | 226 | #if defined DEVICE_KEYBOARDIO_MODEL_01 227 | 228 | asm volatile ("rjmp __vectors-0x1bc8"); // jump to start of user code at 0x38 229 | 230 | #elif defined DEVICE_KEYBOARDIO_MODEL_100 231 | // More precisely, this elif is about whether we're building with GCC5- or GCC7+ 232 | // But the Model 01 MUST use GCC5- And we strongly recommend 7+ for everything else going forward 233 | 234 | asm volatile ("rjmp __vectors-0x1bd8"); // jump to start of user code at 0x28 (0x1bd8 is 0x1c00 -0x28) 235 | // On GCC5 and earlier with Keyboardio's TWI implementation, 236 | // this points to 0x1bc8 instead, which corresponds to 0x38. 237 | 238 | #endif 239 | 240 | __builtin_unreachable(); 241 | } 242 | 243 | 244 | void process_page_erase() { 245 | erase_page_buffer(); 246 | 247 | // read the reset vector 248 | buffer_reset_vector(); 249 | 250 | boot_spm_busy_wait(); 251 | boot_page_erase(0); // have to erase the first page or else it will not write correctly 252 | 253 | unsafe_update_page(0); // restore just the initial vector 254 | 255 | uint16_t addr = PAGE_SIZE; 256 | 257 | while (addr < BOOT_PAGE_ADDRESS) { 258 | boot_spm_busy_wait(); 259 | boot_page_erase(addr); 260 | addr += PAGE_SIZE; 261 | } 262 | } 263 | 264 | void process_getcrc16() { 265 | // get program memory address and length to calcaulate CRC16 of 266 | uint16_t addr = slave_receive_word(); 267 | uint16_t len = slave_receive_word(); 268 | uint16_t max = addr + len; 269 | 270 | // bail if it overflows 271 | // disable sanity check for space 272 | // it's actually a duplicate of the condition on the while loop 273 | // so it's safe to leave disabled 274 | // if (max < addr) { 275 | // return; 276 | // } 277 | // bail if it exceeds flash capacity 278 | if ( max >= FLASH_SIZE) { 279 | return; 280 | } 281 | 282 | sendCrc16 = 0xffff; 283 | while (addr < max) { 284 | sendCrc16 = _crc16_update(sendCrc16, pgm_read_byte(addr)); 285 | addr++; 286 | } 287 | } 288 | 289 | void transmit_crc16_and_version() { 290 | // write the version, then crc16, lo first, then hi 291 | process_slave_transmit(BVERSION); 292 | process_slave_transmit(sendCrc16 & 0xff); 293 | process_slave_transmit(sendCrc16 >> 8); 294 | } 295 | 296 | void send_transmit_success() { 297 | // nack for a last dummy byte to say we read everything 298 | (void)slave_receive_byte(NAK); 299 | } 300 | 301 | void send_transmit_error() { 302 | // AC for a last dummy byte to say we had an error 303 | (void)slave_receive_byte(ACK); 304 | } 305 | 306 | 307 | void process_slave_receive() { 308 | struct recv_result r = slave_receive_byte(ACK); 309 | uint8_t commandCode = r.val; 310 | if (!r.res) { 311 | return; 312 | } 313 | 314 | // Process command byte. 315 | switch (commandCode) { 316 | case TWI_CMD_PAGEUPDATE_ADDR: 317 | process_read_address(); 318 | break; 319 | case TWI_CMD_PAGEUPDATE_FRAME: 320 | if (!process_read_frame(&pageAddr)) { 321 | send_transmit_error(); 322 | break; 323 | } 324 | if (frame == PAGE_SIZE / FRAME_SIZE) { 325 | process_page_update(); 326 | } 327 | send_transmit_success(); 328 | break; 329 | 330 | case TWI_CMD_EXECUTEAPP: 331 | wdt_enable(WDTO_15MS); // Set WDT min for cleanup using reset 332 | asm volatile ("HERE:rjmp HERE");//Yes it's an infinite loop 333 | __builtin_unreachable(); 334 | // fall through 335 | case TWI_CMD_ERASEFLASH: 336 | process_page_erase(); 337 | break; 338 | 339 | case TWI_CMD_GETCRC16: 340 | process_getcrc16(); 341 | break; 342 | 343 | default: 344 | abort_twi(); 345 | } 346 | 347 | return; 348 | } 349 | 350 | void read_and_process_packet() { 351 | 352 | wait_for_activity(ACK); 353 | 354 | // Set WDT max for command timeout once we're addressed 355 | wdt_enable(WDTO_8S); 356 | 357 | // Check TWI status code for SLA+W or SLA+R. 358 | switch (TWSR) { 359 | case TWI_SLAW_RECEIVED: 360 | process_slave_receive(); 361 | break; 362 | case TWI_SLAR_RECEIVED: 363 | transmit_crc16_and_version(); 364 | init_twi(); 365 | break; 366 | 367 | default: 368 | abort_twi(); 369 | } 370 | } 371 | 372 | #if defined DEVICE_KEYBOARDIO_MODEL_01 373 | 374 | // Send a given byte via SPI N times 375 | void __attribute__ ((noinline)) spi_send_bytes(uint8_t val, uint8_t n) { 376 | for (uint8_t i = 0; i < n; i++) { 377 | SPDR = val; 378 | loop_until_bit_is_set(SPSR, SPIF); 379 | } 380 | } 381 | 382 | void init_spi_for_led_control() { 383 | SPCR = _BV(SPE) | _BV(MSTR) | _BV(SPIE) | _BV(SPR0); 384 | SPSR = _BV(SPI2X); 385 | 386 | #define NUM_LEDS 32 387 | 388 | // LED SPI start frame: 32 zero bits 389 | #define LED_START_FRAME_BYTES 4 390 | 391 | // LED SPI end frame: 32 zero bits + (NUM_LEDS / 2) bits 392 | #define LED_END_FRAME_BYTES (4 + (NUM_LEDS / 2 / 8)) 393 | 394 | spi_send_bytes(0, LED_START_FRAME_BYTES); 395 | // Exploit zero global brightness to ignore RGB values, so we can 396 | // save space by sending the same byte for the entire frame 397 | spi_send_bytes(0xe0, NUM_LEDS * 4); 398 | spi_send_bytes(0, LED_END_FRAME_BYTES); 399 | } 400 | 401 | #endif 402 | 403 | // Main Starts from here 404 | int main() { 405 | 406 | // If a watchdog reset occurred (command timeout or TWI command to 407 | // start the application), the watchdog interval will likely 408 | // be reset to 15ms. Immediately clear WDRF and update WDT 409 | // configuration, to avoid reset loops. 410 | MCUSR = 0; 411 | wdt_enable(WDTO_8S); 412 | setup_pins(); 413 | 414 | #if defined DEVICE_KEYBOARDIO_MODEL_01 415 | // Turn LEDs off before deciding what to do next. 416 | init_spi_for_led_control(); 417 | #endif 418 | 419 | _delay_us(5); 420 | 421 | // If the innermost thumb key and the outermost key on row 3 are both held, then it's bootloader time 422 | if (!(PIND & (_BV(0) | _BV(7)))) { 423 | init_twi(); 424 | while (1) { 425 | read_and_process_packet(); // Process the TWI Commands 426 | } 427 | } else { 428 | cleanup_and_run_application(); 429 | } 430 | 431 | } 432 | --------------------------------------------------------------------------------