/*
* A sysex bootloader for avr chips.
* Copyright 2010 Alex Norman
*
* This file is part of SysexBoot.
*
* SysexBoot 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 3 of the License, or
* (at your option) any later version.
*
* SysexBoot 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 SysexBoot. If not, see .
*
*/
#define F_CPU 16000000UL
#define IN_BUFSIZE 260
#include
#include
#include
#include
#include
#include
#include
#include "twi.h"
#include "bytequeue/bytequeue.h"
#include "bootloadercfg.h"
#include "midibootcommon.h"
#define SYSEX_BEGIN 0xF0
#define SYSEX_END 0xF7
#define MIDI_CIN_SYSEX_START 0x4
#define MIDI_CIN_SYSEX_ENDS_1 0x5
#define MIDI_CIN_SYSEX_ENDS_2 0x6
#define MIDI_CIN_SYSEX_ENDS_3 0x7
// From I2C master
uint8_t midiInBuf[MIDIIN_BUF_SIZE];
volatile byteQueue_t midiByteQueue;
// To I2C master
#define MIDIOUT_BUF_SIZE 64
uint8_t midiOutBuf[MIDIOUT_BUF_SIZE];
volatile byteQueue_t midiOutQueue;
uint8_t onReceiveService(uint8_t inByte)
{
if (bytequeue_enqueue((byteQueue_t*) &midiByteQueue, inByte))
{
if (bytequeue_length((byteQueue_t*) &midiByteQueue) < MIDIIN_BUF_SIZE - 1)
return 1;
}
return 0;
}
void onRequestService(void)
{
uint8_t data[5];
uint8_t i;
// Packet length
data[0] = bytequeue_length((byteQueue_t*) &midiOutQueue);
if (data[0] > 3)
data[0] = 3;
// USB midi status byte
data[1] = MIDI_CIN_SYSEX_START;
for (i = 0; i < data[0]; i++)
{
data[i+2] = bytequeue_get((byteQueue_t*) &midiOutQueue, i);
// Search for SYSEX_END and set USB status byte if needed.
if (data[i+2] == SYSEX_END)
{
data[1] += (i+1);
}
}
// If we're not sending a full packet, it better have a sysex_end in it
if ((data[1] == MIDI_CIN_SYSEX_START) && (data[0] < 3))
{
data[0] = 0;
} else {
bytequeue_remove((byteQueue_t*) &midiOutQueue, data[0]);
data[0] = 4;
}
twi_transmit(data, data[0]+1);
if (bytequeue_length((byteQueue_t*) &midiOutQueue) == 0)
PORTC &= ~(_BV(1));
}
void (*jump_to_app)(void) = 0x0;
void exit_bootloader(void){
cli();
boot_rww_enable();
MCUCR = _BV(IVCE);
MCUCR = 0;
jump_to_app();
}
void midi_send_byte(uint8_t b)
{
bytequeue_enqueue((byteQueue_t*) &midiOutQueue, b);
PORTC |= _BV(1); // notify PIC we've got data
}
void midi_send_encoded_byte(uint8_t b)
{
midi_send_byte(b >> 4);
midi_send_byte(b & 0xF);
}
void send_sysex_start(void) {
uint8_t i = 0;
midi_send_byte(SYSEX_BEGIN);
for(i = 0; i < MIDIBOOT_SYSEX_ID_LEN; i++)
midi_send_byte(midiboot_sysex_id[i]);
}
void send_sysex_end(void) {
midi_send_byte(SYSEX_END);
}
//The ack is just our sysex id in a sysex message, that is all
void send_ack(void){
send_sysex_start();
send_sysex_end();
}
int main(void) {
uint8_t i, size, curByte;
bool cancelTimeout = false;
bool inSysexMode = false;
uint16_t inByteIndex = 0;
uint16_t pageAddress = 0;
uint16_t counterLow = 0;
//page size is 128, need 2 more bytes for page address
//128 + 2 == 130..
//we actually don't need so much space but, whatever
uint8_t tmpUnpackedData[IN_BUFSIZE / 2];
//once we pack it, it is 149 bytes
uint8_t tmpPackedData[IN_BUFSIZE];
midiboot_sysex_t sysexMode = MIDIBOOT_INVALID;
//set up the bootloader conditions
MCUCR = _BV(IVCE);
MCUCR = _BV(IVSEL);
// Everything INPUT except LED pins
DDRC = _BV(1) | _BV(2) | _BV(3);
// Enable pullups, set LED red
PORTC = 0xFB;
bytequeue_init((byteQueue_t *)&midiByteQueue, midiInBuf, MIDIIN_BUF_SIZE);
bytequeue_init((byteQueue_t*) &midiOutQueue, midiOutBuf, MIDIOUT_BUF_SIZE);
// Init I2C
twi_setAddress(4);
twi_attachSlaveTxEvent(onRequestService);
twi_attachSlaveRxEvent(onReceiveService);
twi_init();
sei();
//do main loop
while(true){
// Cheesy timeout method, but frugal on bytes
if ((++counterLow == 0) && (!cancelTimeout))
{
exit_bootloader();
}
//read data from the queue and deal with it
size = bytequeue_length((byteQueue_t *)&midiByteQueue);
//deal with input data
for(i = 0; i < size; i++){
curByte = bytequeue_get((byteQueue_t *)&midiByteQueue, i);
if(curByte == SYSEX_BEGIN){
inSysexMode = true;
inByteIndex = 0;
sysexMode = MIDIBOOT_INVALID;
} else if(curByte == SYSEX_END){
if(inSysexMode){
//see what mode we're in
switch(sysexMode){
case MIDIBOOT_LEAVE_BOOT:
exit_bootloader();
break;
case MIDIBOOT_GETPAGESIZE:
//if we've been sent the correct size packet then send back our data
if(inByteIndex == (1 + MIDIBOOT_SYSEX_ID_LEN)){
cancelTimeout = true;
//pack the contents of SPM_PAGESIZE into tmpPackedData
//SPM_PAGESIZE is 2 bytes wide
send_sysex_start();
midi_send_byte(MIDIBOOT_GETPAGESIZE);
midi_send_encoded_byte(SPM_PAGESIZE >> 8);
midi_send_encoded_byte(SPM_PAGESIZE & 0xFF);
send_sysex_end();
}
break;
//actually write the page that has been filled up through MIDIBOOT_FILLPAGE
case MIDIBOOT_WRITEPAGE:
cli();
//erase the page
eeprom_busy_wait ();
boot_page_erase (pageAddress);
boot_spm_busy_wait ();
//write the page
boot_page_write (pageAddress); // Store buffer in flash page.
boot_spm_busy_wait(); // Wait until the memory is written.
sei();
send_ack();
break;
//write data into the temporary page buffer [boot_page_fill]
case MIDIBOOT_FILLPAGE:
if(inByteIndex > (MIDIBOOT_SYSEX_ID_LEN + 1)) {
const uint16_t packedSize = inByteIndex - MIDIBOOT_SYSEX_ID_LEN - 1;
const uint16_t unpackedSize = packedSize / 2;
if(unpackedSize <= 64){
//the first two bytes are the address
uint16_t bytesToWrite = unpackedSize - 2;
uint16_t writeStartAddr;
//unpack our addr+data
uint16_t n;
for (n = 0; n < packedSize; n+=2)
{
tmpUnpackedData[n >> 1] = (tmpPackedData[n] << 4) | tmpPackedData[n+1];
}
//grab the start address
writeStartAddr = (tmpUnpackedData[0] << 8) | tmpUnpackedData[1];
//page address, just zero out the lower bits, used for WRITEPAGE
pageAddress = writeStartAddr & ~(SPM_PAGESIZE - 1);
cli();
//fill the temp page buffer
uint8_t * pageData = tmpUnpackedData + 2;
uint16_t j;
for(j = 0; j < bytesToWrite; j+=2){
uint16_t w = (((uint16_t)pageData[j + 1]) << 8) + pageData[j];
boot_page_fill (writeStartAddr + j, w);
}
sei();
//ack that we've done this so that we can get some more data
send_ack();
}
}
break;
default:
break;
}
}
inSysexMode = false;
} else if(inSysexMode){
//make sure the sysex prefix matches ours
if((inByteIndex < MIDIBOOT_SYSEX_ID_LEN) && (curByte != midiboot_sysex_id[inByteIndex])){
inSysexMode = false;
} else {
//we're in sysex mode and we've matched the midiboot_sysex_id
//the next byte tells us what to do
if(inByteIndex == MIDIBOOT_SYSEX_ID_LEN){
if(curByte >= MIDIBOOT_LEAVE_BOOT && curByte <= MIDIBOOT_WRITEPAGE)
{
sysexMode = curByte;
}
else
inSysexMode = false;
//if we're filling a page, write it to the tmpPackedData, we'll unpack later
} else if (sysexMode == MIDIBOOT_FILLPAGE){
//the first MIDIBOOT_SYSEX_ID_LEN + 1 bytes have already been dealt with
uint16_t index = inByteIndex - MIDIBOOT_SYSEX_ID_LEN - 1;
if(index < IN_BUFSIZE) {
tmpPackedData[index] = curByte;
} else {
//XXX ERROR!!!
sysexMode = MIDIBOOT_INVALID;
inSysexMode = false;
}
}
//increment the index
inByteIndex++;
}
}
}
//advance the pointer
bytequeue_remove((byteQueue_t *)&midiByteQueue, size);
}
return 0;
}