/*
* This is a derivative of SysExBoot
*
* Modified by Brian Richardson for Jabrudian Industries
*
* >>>>>>>>>>>>>> origional header >>>>>>
*
* "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 .
*
* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
#include
#include
#include
#include
#include "portmidi.h"
#include "porttime.h"
#include
#include
#include
#include "sysex_tools.h"
#include "midibootcommon.h"
#define TIMEOUT_MS 3000
#define CHUNK_SIZE 48
/*
* a sysex packet for a page_fill looks like:
* MIDI_SYSEX_START
* midiboot_sysex_id[]
* MIDIBOOT_FILLPAGE
* packed_addr+data[<=35] (unpacks to 2 addr bytes + 28 or fewer data bytes)
* MIDI_SYSEX_STOP
*/
using std::cout;
using std::cerr;
using std::endl;
using std::hex;
using std::dec;
void simple_bit_unpack(uint8_t * unpacked, const uint8_t * source,
uint16_t length)
{
for (int i = 0; i < length; i += 2) {
unpacked[i / 2] = (source[i] << 4) | source[i + 1];
}
}
uint16_t simple_bit_packed_length(uint16_t unpacked_length)
{
return unpacked_length * 2;
}
uint16_t simple_bit_pack(uint8_t * packed, const uint8_t * source,
uint16_t length)
{
for (int i = 0; i < length; i++) {
packed[i * 2] = source[i] >> 4;
packed[i * 2 + 1] = source[i] & 0xF;
}
return length * 2;
}
int getMissingLink(bool input)
{
for (int i = 0; i < Pm_CountDevices(); i++) {
const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
if ((info->input && input) || (info->output && !input)) {
if (strncmp(info->name, "Missing Link", 12) == 0) {
return i;
}
}
}
return -1;
}
void printMidiDevices(bool input)
{
for (int i = 0; i < Pm_CountDevices(); i++) {
const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
if (info->input && input)
cout << i << ", " << info->interf << ", " << info->
name << endl;
if (info->output && !input)
cout << i << ", " << info->interf << ", " << info->
name << endl;
//printf("%d: %s, %s\n", i, info->interf, info->name);
}
}
void MLCommand(PmStream * midi_in, PmStream * midi_out, long timeout,
unsigned char command)
{
// SYSEX BEGIN, EDU, MLNK, 3=enter bootloader, SYSEX END
unsigned char requestMsg[] =
{ 0xF0, 0x7D, 0x4D, 0x4C, 0x4E, 0x4B, command, 0xF7 };
//send the message
PmError error = Pm_WriteSysEx(midi_out, 0, requestMsg);
if (error) {
cerr << "error writing ENTER BOOTLOADER msg: " <<
Pm_GetErrorText(error) << endl;
exit(-1);
}
long startTime = Pt_Time();
while (Pt_Time() - startTime < timeout) ;
}
int getPageSize(PmStream * midi_in, PmStream * midi_out, long timeout)
{
PmEvent msg;
bool inSysexMessage;
unsigned int sysexByteCount;
long startTime = Pt_Time();
int pageSize = 0;
uint8_t buf[3];
uint8_t bufout[3];
//write get page size message
unsigned char requestMsg[64];
memset(&requestMsg, 0, 64);
requestMsg[0] = MIDI_SYSEX_START;
for (unsigned int i = 0; i < MIDIBOOT_SYSEX_ID_LEN; i++)
requestMsg[i + 1] = midiboot_sysex_id[i];
requestMsg[MIDIBOOT_SYSEX_ID_LEN + 1] = MIDIBOOT_GETPAGESIZE;
requestMsg[MIDIBOOT_SYSEX_ID_LEN + 2] = MIDI_SYSEX_STOP;
//send the message
PmError error = Pm_WriteSysEx(midi_out, 0, requestMsg);
if (error) {
cerr << "error writing GET PAGE SIZE msg: " <<
Pm_GetErrorText(error) << endl;
exit(-1);
}
//allow for a second timeout
while (startTime + timeout > Pt_Time()) {
unsigned int count = Pm_Read(midi_in, &msg, 1);
if (count != 0) {
for (unsigned int i = 0; i < 4; i++) {
unsigned char inByte =
(msg.message >> (i * 8)) & 0xFF;
if (inByte == MIDI_SYSEX_START) {
sysexByteCount = 0;
inSysexMessage = true;
} else if (inSysexMessage) {
if (inByte == MIDI_SYSEX_STOP) {
simple_bit_unpack(bufout, buf,
4);
pageSize =
(bufout[0] << 8) |
bufout[1];
return pageSize;
} else
if ((sysexByteCount <
MIDIBOOT_SYSEX_ID_LEN)) {
//make sure that our id matches
if (inByte ==
midiboot_sysex_id
[sysexByteCount])
sysexByteCount++;
else
inSysexMessage = false;
} else if (sysexByteCount ==
MIDIBOOT_SYSEX_ID_LEN) {
if (inByte !=
MIDIBOOT_GETPAGESIZE)
inSysexMessage = false;
else
sysexByteCount++;
} else if (sysexByteCount <
(MIDIBOOT_SYSEX_ID_LEN +
5)) {
unsigned int index =
(sysexByteCount -
MIDIBOOT_SYSEX_ID_LEN - 1);
buf[index] = inByte;
sysexByteCount++;
} else
inSysexMessage = false;
}
}
}
}
cout << endl;
return 0;
}
bool waitForAck(PmStream * midi_in, long timeout)
{
PmEvent msg;
bool inSysexMessage;
unsigned int sysexByteCount;
long startTime = Pt_Time();
//allow for a second timeout
while (startTime + timeout > Pt_Time()) {
unsigned int count = Pm_Read(midi_in, &msg, 1);
if (count != 0) {
for (unsigned int i = 0; i < 4; i++) {
unsigned char inByte =
(msg.message >> (i * 8)) & 0xFF;
if (inByte == MIDI_SYSEX_START) {
sysexByteCount = 0;
inSysexMessage = true;
} else if (inSysexMessage) {
if (sysexByteCount ==
MIDIBOOT_SYSEX_ID_LEN) {
if (inByte == MIDI_SYSEX_STOP) {
return true;
} else
inSysexMessage = false;
} else
if ((sysexByteCount <
MIDIBOOT_SYSEX_ID_LEN)
&& inByte ==
midiboot_sysex_id
[sysexByteCount])
sysexByteCount++;
else
inSysexMessage = false;
}
}
}
}
return false;
}
void writeSysexPrefix(std::vector < unsigned char >*sysexMesg)
{
sysexMesg->push_back(MIDI_SYSEX_START);
for (unsigned int i = 0; i < MIDIBOOT_SYSEX_ID_LEN; i++)
sysexMesg->push_back(midiboot_sysex_id[i]);
}
int main(int argc, char *argv[])
{
std::vector < unsigned char >inputData;
std::vector < std::vector < unsigned char >*>pages;
std::vector < std::vector < unsigned char >*>sysexMessages;
std::vector < unsigned char >packedPage;
unsigned char tmp;
int id;
std::ifstream inFile;
struct stat stFileInfo;
PmStream *midi_in;
PmStream *midi_out;
unsigned int pageSize = 0;
unsigned short numPages = 0;
const PmDeviceInfo *info;
//start our time
Pt_Start(1, 0, 0);
//test args
if (argc != 2) {
cout << "usage:" << endl;
cout << argv[0] << " binfiletoupload" << endl;
exit(-1);
}
if (stat(argv[1], &stFileInfo) != 0 || !S_ISREG(stFileInfo.st_mode)) {
cerr << "file does not exist or is not a regular file" << endl;
exit(-1);
}
printMidiDevices(true);
printMidiDevices(false);
id = getMissingLink(true);
if (id == -1) {
cerr << "I Did Not Find a Missing Link!" << endl;
exit(-1);
}
//error checking here VVVV
info = Pm_GetDeviceInfo(id);
cout << endl << "**opening input : " << info->name << endl << endl;
//error checking here VVVV
Pm_OpenInput(&midi_in, id, NULL, 512, NULL, NULL);
id = getMissingLink(false);
if (id == -1) {
cerr << "Could Not Find Missing Links Ouput" << endl;
exit(-1);
}
//error checking here VVVV
info = Pm_GetDeviceInfo(id);
cout << endl << "**opening output : " << info->name << endl << endl;
//error checking here VVVV
Pm_OpenOutput(&midi_out, id, NULL, 0, NULL, NULL, 0);
cout << "uploading file " << argv[1] << endl;
//cout << "* make sure that you have put the microcontroller in boot loader mode" << endl;
//cout << "hit enter to continue, or control-c to quit" << endl;
//std::cin.get();
//grab the page size!
MLCommand(midi_in, midi_out, 1500, 3);
pageSize = getPageSize(midi_in, midi_out, 3000);
if (pageSize == 0) {
cerr <<
"*** failed to get the page size from the microcontroller"
<< endl;
cerr << "*** are you in boot loader mode?" << endl;
cerr <<
"*** do you have both the midi input and output to/from your microcontroller connected to your computer"
<< endl;
cerr << "*** did you select the correct input and output?" <<
endl;
cerr << "*** aborting" << endl;
exit(-1);
} else if (pageSize != 128) {
cerr << "*** something is amok !!! " << endl
<< " " << pageSize << " isnt the page size we are expecting (128)" << endl
<< " Bailing ***" << endl;
exit(-1);
}
//open the file
inFile.open(argv[1], std::ios::in | std::ios::binary);
if (!inFile.is_open()) {
cerr << "cannot open file: " << argv[1] << endl;
//Pm_Close(midi_out);
Pm_Close(midi_in);
exit(-1);
}
//read in the file data
tmp = inFile.get();
while (!inFile.eof()) {
inputData.push_back(tmp);
tmp = inFile.get();
}
//see if there is data
if (inputData.size() < 1) {
cerr << "datafile is empty" << endl;
//Pm_Close(midi_out);
Pm_Close(midi_in);
exit(-1);
}
//compute the number of pages we'll need
numPages = (unsigned short)ceil((float)inputData.size() / pageSize);
if (numPages == 0)
numPages = 1;
//make sure there are enough positions in the pages
pages.reserve(numPages);
sysexMessages.reserve(numPages);
//cut the data into pages
for (unsigned short i = 0; i < inputData.size(); i++) {
unsigned int pageIndex = i / pageSize;
//if we're at a new page then push the page index, two bytes
if (i % pageSize == 0) {
pages.push_back(new std::vector < unsigned char >);
}
pages[pageIndex]->push_back(inputData[i]);
}
//pack and build the sysex messages
cout << "byte packing the file contents" << endl;
for (unsigned int i = 0; i < numPages; i++) {
//create a new sysex message for each chunk
for (unsigned int j = 0; j < pages[i]->size(); j += CHUNK_SIZE) {
std::vector < unsigned char >tmp;
unsigned int start_index = j;
unsigned int end_index = j + CHUNK_SIZE;
//make sure our index is in range
if (end_index > pages[i]->size())
end_index = pages[i]->size();
std::vector < unsigned char >*newMsg =
new std::vector < unsigned char >;
//write the header
writeSysexPrefix(newMsg);
newMsg->push_back(MIDIBOOT_FILLPAGE);
//write address to tmp
unsigned int pageAddress = i * pageSize + start_index;
tmp.clear();
tmp.push_back((unsigned char)(pageAddress >> 8) & 0xFF);
tmp.push_back((unsigned char)pageAddress & 0xFF);
//write the data to tmp
for (unsigned int k = start_index; k < end_index; k++)
tmp.push_back(pages[i]->at(k));
//pack the address+data
packedPage.clear();
packedPage.resize(simple_bit_packed_length(tmp.size()));
simple_bit_pack(&packedPage[0], &tmp[0], tmp.size());
//write the packed address+data to the sysexMessage
for (std::vector < unsigned char >::iterator it =
packedPage.begin(); it != packedPage.end(); it++)
newMsg->push_back(*it);
//write the sysex end message
newMsg->push_back(MIDI_SYSEX_STOP);
//push this sysex message onto our list of sysex messages
sysexMessages.push_back(newMsg);
}
//create the 'write page' sysex message
std::vector < unsigned char >*newMsg =
new std::vector < unsigned char >;
writeSysexPrefix(newMsg);
newMsg->push_back(MIDIBOOT_WRITEPAGE);
newMsg->push_back(MIDI_SYSEX_STOP);
//push this sysex message onto our list of sysex messages
sysexMessages.push_back(newMsg);
}
cout << "page size: " << pageSize << endl;
cout << "this many pages will be written: " << pages.size() << endl;
cout << "this many bytes will be written: " << inputData.size() << endl;
cout << "using this many sysex messages: " << sysexMessages.
size() << endl;
//upload
cout << "uploading now" << endl;
//send each sysex message
for (std::vector < std::vector < unsigned char >*>::iterator it =
sysexMessages.begin(); it != sysexMessages.end(); it++) {
PmError error;
unsigned char msg[64];
//make sure we've only got zeros
memset(&msg, 0, 64);
//fill in sysex buffer
for (unsigned int i = 0; i < (*it)->size(); i++) {
msg[i] = (*it)->at(i);
}
//send the message
error = Pm_WriteSysEx(midi_out, 0, msg);
if (error) {
cerr << "error: " << Pm_GetErrorText(error) << endl;
exit(-1);
}
//get ack
if (!waitForAck(midi_in, TIMEOUT_MS)) {
cerr <<
"FAILED: sysex message failed to acknowledge within the timeout"
<< endl;
cerr << "was the microcontroller in boot loader mode?"
<< endl;
exit(-1);
}
}
cout << "success" << endl;
//leave boot
unsigned char msg[64];
memset(&msg, 0, 64);
msg[0] = MIDI_SYSEX_START;
for (unsigned int i = 0; i < MIDIBOOT_SYSEX_ID_LEN; i++)
msg[i + 1] = midiboot_sysex_id[i];
msg[MIDIBOOT_SYSEX_ID_LEN + 1] = MIDIBOOT_LEAVE_BOOT;
msg[MIDIBOOT_SYSEX_ID_LEN + 2] = MIDI_SYSEX_STOP;
//send the message
PmError error = Pm_WriteSysEx(midi_out, 0, msg);
if (error) {
cerr << "error writing LEAVE BOOT msg: " <<
Pm_GetErrorText(error) << endl;
exit(-1);
}
MLCommand(midi_in, midi_out, 1500, 1);
Pm_Close(midi_out);
Pm_Close(midi_in);
return 0;
}