MCV876 Controller Update: File Converter

Aus Synthesizer Wiki
Zur Navigation springen Zur Suche springen

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 kurzer 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;
}