MCV876 Controller Update: File Converter
Worum geht's?
Der File Konverter ist ein kleines Programm, dass vom Compiler/Linker erzeugte Binär-Datei (im Intel .hex Format)) in ein Datenformat wandelt, dass per Midi übertragen werden kann und das der Controller so versteht, dass er sich selber updaten kann.
Benutzen
Der Linker der Entwicklungsumgebung erzeugt ein .hex File. Dieses File zieht man einfach auf das Icon der sys876.exe. Kurz darauf taucht im gleichen Verzeichnis wie die .hex Datei eine .syx Datei sonst gleichen Namens auf. Das war es schon - diese Datei ist das Sys-Ex Update File, was man per Midi an den Controller schickt, worauf er dann sofort mit diesem neuen Programm los rennt.
Datenformat
Die Programm-Daten werden in 64-Byte Nutzdatenhäppchen eingepackt. Diese Pakete sind Brutto 84 Bytes lang. Die Daten müssen für Midi umkodiert werden, da Midi nur 7 Bit Nutz-Daten übertragen kann. Dazu werden einfach immer die nächsten 7 bit aus dem 8-Bit breiten Datenstrom entnommen. Das gibt völlig undurchsichtige Werte, geht erst nach 7x8=56 Bits auf und ist ansonsten recht einfacher Code, der kompakte Daten erzeugt. Um Datenübertragungsfehler zu erkennen, bekommt jedes Paket eine 16 Bit CRC als Prüfsumme. Und damit es als Midi durchgeht, kommen noch ein paar Bytes ("hallo hier kommt ein SysEx") davor und ein Ende Byte dahinter.
Die 64 Byte sind eine für Midi, Sys-Ex und das Controller-RAM noch verträgliche Datensatzlänge. Und der Controller schreibt seinen Programm-Speicher in genau solchen 64 Byte Blöcken.
Vor die eigentlich Progamm-Daten werden noch zwei Pakete vorangestellt:
Das erste schaltet einen in normalem Midi-Betrieb laufenden Controller auf den Boot-Lader um. Nach dem eigentlichen, kurzen Kommando, das den Normalbetrieb beendet, folgen 70 Nullen, die dem Controller mindestens 22ms Luft verschaffen, um auf den Bootlader zu wechseln. (Ist der Controller bereits im Bootlader wird dieses Paket nebst den Nullen schlichtweg überlesen/ignoriert. )
Das zweite Paket enthält nur die Info, dass es a)das erste Update-Pakte ist (d.h. es setzt den Empfänger auf "jetzt geht es los" zurück), und b)die Zahl der folgenden Blöcke (damit Vollständigkeit geprüft werden kann).
Dann kommen die eigentlichen Programmdaten.
Quelltext
/*
Programm zum Erzeugen von Midi-Sys-Ex Software Update Files aus einer HEX-Datei
für das MCV-876 Midi Interface mit ATMEL ATMEGA 88 Controller.
*/
/*
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, either version 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
*/
/*
Aufruf:
sys676 avr-studio-compiler-out.hex
das erzeugt dann eine Datei
sys676 avr-studio-comipler-out.syx
die mit Midi-OX (oder einen andern "ich kann Midi senden" Programm) an das
Interface übertragen wird.
Unter Windows kann man das übrigens bequem per "Drag and Drop" bedienen, einfach die
.hex Datei auf die sys876.exe draufziehen, das Ergebnis taucht dann im gleichen Order wie
die .hex Datei auf. (Man sieht halt keine Fehlermeldungen... )
Erfolgreich übersetzt mit Visual Studio .net 2003 auf Windows XP
Leeres Konsolenprojekt Namens syx876 erzeugen, diesen Quelltext in das .cpp
File kopieren und bauen lassen. Ggfls. auf "Release" umstellen, im Release Ordner
findet sich dann die fertige Applikation.
Sollte sich nach marginalen Änderungen allerdings mit jedem Compiler übersetzen lassen.
*/
// für Windows =======================
#include "stdafx.h"
#define main _tmain
// für normale Compiler =============
/*
#include <stdio.h>
*/
//===================================
#define VERSION "1.00"
#define MAX_DATA 8192 // netto data size (ATMEGA 88 Flash Rom size)
#define MAX_SYX_DATA (2*MAX_DATA) // max. complete syx-ex size
#define MAX_SYX_BLOCK (2*64) // one Sys-Ex Block of 64 bytes netto data
unsigned char data[MAX_DATA]; // holds binary program data
// Convert one HEX-ASCII character to its numerical value, or -1 if illegal value
int conf( char c)
{
switch (c)
{
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'a': case 'A': return 10;
case 'b': case 'B': return 11;
case 'c': case 'C': return 12;
case 'd': case 'D': return 13;
case 'e': case 'E': return 14;
case 'f': case 'F': return 15;
}
return (-1);
}
// calculate 16 bit crc from previous crc and new data byte, initial (first) crc should be 0xffff, returns new crc
unsigned int crc16(unsigned int crc, unsigned char byte)
{
int i;
crc^=byte;
for(i=0;i<8;++i)
{
if (crc&1)
crc=(crc>>1)^0xA001;
else
crc>>=1;
}
return crc & 0xffff;
}
// generates first Sys-EX Block, this switches the interface from operation mode to boot-load mode
int makePrgPrep(unsigned char *str)
{
int i;
unsigned char a[] = { 0xF0, 0x70, 0x7D,0x00,0x16,0x04, 0x02, 0xF7 }; // the switch command string (see makeSysEx for byte value explanation)
for(i=0;i<sizeof(a);i++)
*str++=a[i];
for(;i<70;i++) // 70 bytes * 0,32ms for midi data transmission gives the interface the time to start the bootloader
{
*str++=0;
}
return i;
}
// make a sys-ex data block with proper midi framing and crc from binary data in "block"
int makeSysEx(unsigned char *block, int len, unsigned char *syxTarget)
{
int crc=0xffff;
unsigned int out=0;
int bits=0;
int i;
unsigned char c;
unsigned char *syxStr=syxTarget;
// make CRC
for(i=0;i<len;i++)
{
c=block[i];
crc=crc16(crc, c);
}
block[len++]= ( crc)&0xff;
block[len++]= (crc >>8 ) &0xff;
*syxStr++ = (0xF0); // start sys ex
*syxStr++ = (0x70); // manufacturer id
*syxStr++ = (0x7D); // unit type
*syxStr++ = (0x00); // unit id
*syxStr++ = (0x15); // for normal sysEx it is a adress, here magic byte 1
*syxStr++ = (0x55); // magic byte 2
for(i=0;i<len;i++)
{
c=block[i]; // data byte
/*
Midi data is 7 bit only, as MSB is command/data bit.
So data must be converted to 7 bit.
Each source data byte gives 8/7 target bytes,
as soon as there is one complete byte more (which happens every 7 input bytes)
two target bytes will be generated.
*/
out<<=8;
out|=c;
bits+=8;
while(bits>=7)
{
*syxStr++ = ((out>>(bits-7)) & 0x7f);
bits-=7;
}
}
if(bits) // don't forget about the last bits of the last data byte
{
out<<= 7-bits; // this will pad last byte with up to 6 zero bits.
*syxStr++ = (out & 0x7f);
}
*syxStr++ = 0xF7; // Sys-Ex end
return (int) (syxStr - syxTarget); // lenght of generated data
}
// argv[1] is filename to be converted
int main(int argc, _TCHAR* argv[])
{
FILE *fp;
int minAdr=0xffff;
int maxAdr=0;
int chars=0;
int error=0;
int c;
int val;
int field=-1;
int adress=0;
int cnt;
int sum=0;
int field_len=0;
int type=99;
int line=0;
// the controller allways flashes 64 byte blocks, so the last block is padded with arbitrary data
int padit[]= {110,111,114,100,99,111,114,101,32,119,97,115,32,104,101,114,101,32,43,43,43,32,
110,111,114,100,99,111,114,101,32,119,97,115,32,104,101,114,101,32,43,43,43,32,
110,111,114,100,99,111,114,101,32,119,97,115,32,104,101,114,101,32,43 } ;
printf("syx876 Version " VERSION "\nHex-File to Sys-Ex Converter for ATMega88 on MCV876\n");
// 'check' for proper input filename (it must have a "three byte extension" )
// *not* fool proof, try "/../a" and you'll get interesting results
size_t l=strlen(argv[1]);
if(argc !=2 || l<5 || *(argv[1]+l-4)!='.' )
{
printf("\nUsage: syx876 filename.hex\n");
return -1;
}
l -= 3;
fp=fopen (argv[1],"rb"); // open input files
if(fp)
{
// make output file name
*(argv[1]+l)=0;
strcat (argv[1],"syx");
while( (c=fgetc(fp)) != EOF ) // read input hex file
{
chars++;
if(c==':')
{
cnt=0;
field=0;
sum=0;
line++;
}
else if ( conf( (unsigned char) c ) >=0 )
{
if(cnt==0)
val=conf( (unsigned char) c );
else
{
val<<=4;
val += conf( (unsigned char) c );
}
cnt++;
if(cnt==2) // add up checksum
sum+= val;
if( cnt==4)
sum += val & 0xff;
if(field ==0 && cnt==2)
{ // byte count
field_len=val;
field =1;
cnt=0;
}
else if( field ==1 && cnt == 4)
{ // adress
adress=val;
field=2;
cnt=0;
}
else if( field ==2 && cnt == 2)
{ // type
type=val;
field=4;
cnt=0;
}
else if ( field ==4 && cnt ==2 && field_len>0)
{ // data
field_len--;
if(adress < minAdr)
minAdr=adress;
if(adress>maxAdr)
maxAdr=adress;
if(type==0 && adress < MAX_DATA)
data[adress]=val;
adress++;
cnt=0;
}
else if ( field ==4 && cnt ==2 && field_len == 0)
{ // checksum
cnt=0;
sum &=0xff;
if(sum)
{
error++;
printf("Line %d Checksum error (sum=%d)\n",line, sum);
}
}
}
else if( c !=10 && c !=13)
{
error ++;
printf( "Line %d unexpected character (%c)\n",line, c); // characters I don't know
}
}
printf( "\nread %d charactes, from adress %d to %d with %d errors\n", chars, minAdr, maxAdr,error);
// generate output data
if(error==0 && minAdr==0)
{
int blocks;
int blockNr;
unsigned char block[MAX_SYX_BLOCK]; // buffer to collect/prepare one binary data block
unsigned char stp[MAX_SYX_DATA]; // output data buffer, collects the sysEx data
unsigned char *str; // current write pointer
int adr;
int padded;
FILE *wp;
int i;
str=stp;
blocks= (((maxAdr)/64)+1); // total number of blocks
str+= makePrgPrep(str); // generate sys-ex header (=switch to bootloader command)
// first block is a spezial block - it tells controller number of blocks he has to expect.
blockNr=0;
block[0]=blockNr;
block[1]=blocks;
block[2]=0; // reserved
block[3]=0; // reserved
str += makeSysEx(block,4,str); // generate SysEx from block data
adr=minAdr;
// make flash data blocks
// flash data blocks are 65 bytes, 1 Byte Block number, 64 data bytes
// the controller allways writes 64 byte blocks to flash, so this format
// helps to keep the controller program straight.
while (blockNr<blocks)
{
blockNr++;
block[0]=blockNr; // first data byte in block is block number (so controller sees, when a block is lost)
padded=0;
for(i=0;i<64;i++)
{
if(adr>maxAdr) // it beyond highest adresses from hex file: last block is(must be) padded to 64 bytes
{
block[i+1]=padit[padded];
padded++;
}
else // write programm data
{
block[i+1]=data[adr++];
}
}
str +=makeSysEx(block,65,str);
}
// write output file
wp=fopen(argv[1],"wb");
if(wp)
{
fwrite ((void *) stp ,sizeof(unsigned char), str-stp, wp);
fclose(wp);
printf("%d sysEx data blocks in %d bytes written\n",blocks, str-stp);
}
else
printf("Cant open output file %s\n",argv[1]);
}
else
{
if(minAdr)
printf("Lowest adress %d is not zero!\n",minAdr);
return -2;
}
}
else
printf ("Input file %s not found\n",argv[1]);
return 0;
}