code: purgatorio

ref: 6042bb8adffe111049edfcb8b2eb1ad84814ed69
dir: /appl/cmd/avr/burn.b/

View raw version
implement Burn;

include "sys.m";
	sys: Sys;

include "draw.m";

include "bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;

include "timers.m";
	timers: Timers;
	Timer: import timers;

include "string.m";
	str: String;

include "arg.m";

Burn: module
{
	init:	fn(nil: ref Draw->Context, nil: list of string);
};

Avr: adt {
	id:	int;
	rev:	int;
	flashsize:	int;
	eepromsize:	int;
	fusebytes:	int;
	lockbytes:	int;
	serfprog:	int;	# serial fuse programming support
	serlprog:	int;	# serial lockbit programming support
	serflread:	int;	# serial fuse/lockbit reading support
	commonlfr:	int;	# lockbits and fuses are combined
	sermemprog:	int;	# serial memory programming support
	pagesize:	int;
	eeprompagesize:	int;
	selftimed:	int;	# all instructions are self-timed
	fullpar:	int;	# part has full parallel interface
	polling:	int;	# polling can be used during SPI access
	fpoll:	int;	# flash poll value
	epoll1:	int;	# eeprom poll value 1
	epoll2:	int;	# eeprom poll value 2
	name:	string;
	signalpagel:	int;	# posn of PAGEL signal (16rD7 by default)
	signalbs2:	int;	# posn of BS2 signal (16rA0 by default)
};

F, T: con iota;
ATMEGA128: con 16rB2;	# 128k devices

avrs: array of Avr = array[] of {
	(ATMEGA128,  1, 131072, 4096, 3, 1, T,  T,  T,  F, T,  256, 8,  T,  T,  T,  16rFF, 16rFF, 16rFF, "ATmega128",   16rD7, 16rA0),
};

sfd: ref Sys->FD;
cfd: ref Sys->FD;
rd: ref Rd;
mib510 := 1;

Rd: adt {
	c:	chan of array of byte;
	pid:	int;
	fd:	ref Sys->FD;
	buf:	array of byte;
	new:	fn(fd: ref Sys->FD): ref Rd;
	read:	fn(r: self ref Rd, ms: int): array of byte;
	readn:	fn(r: self ref Rd, n: int, ms: int): array of byte;
	flush:	fn(r: self ref Rd);
	stop:	fn(r: self ref Rd);
	reader:	fn(r: self ref Rd, c: chan of int);
};

debug := 0;
verify := 0;
erase := 1;
ignore := 0;

init(nil: ref Draw->Context, args: list of string)
{
	sys = load Sys Sys->PATH;
	bufio = ckl(load Bufio Bufio->PATH, Bufio->PATH);
	str = ckl(load String String->PATH, String->PATH);
	timers = ckl(load Timers Timers->PATH, Timers->PATH);

	serial := "/dev/eia0";
	fuseext := -1;
	fuselow := -1;
	fusehigh := -1;
	arg := ckl(load Arg Arg->PATH, Arg->PATH);
	arg->init(args);
	arg->setusage("burn [-rD] [-d serialdev] file.out");
	while((o := arg->opt()) != 0)
		case o {
		'D' =>	debug++;
		'e' =>	erase = 0;
		'r' =>	verify = 1;
		'd' =>	serial = arg->earg();
		'i' =>	ignore = 1;
		'E' =>	fuseext = fuseval(arg->earg());
		'L' =>	fuselow = fuseval(arg->earg());
		'H' =>	fusehigh = fuseval(arg->earg());
		* =>	arg->usage();
		}
	args = arg->argv();
	if(len args != 1)
		arg->usage();
	arg = nil;

	sfile := hd args;
	fd := bufio->open(sfile, Sys->OREAD);
	if(fd == nil)
		err(sys->sprint("can't open %s: %r", sfile));
			
	timers->init(2);
	sfd = sys->open(serial, Sys->ORDWR);
	if(sfd == nil)
		err(sys->sprint("can't open %s: %r", "/dev/eia0"));
	cfd = sys->open(serial+"ctl", Sys->ORDWR);
	sys->fprint(cfd, "f");
	sys->fprint(cfd, "b115200");
	sys->fprint(cfd, "i8");
#	sys->fprint(cfd, "f\nb115200\ni8");
	rd = Rd.new(sfd);

	initialise();
	if(fuseext >= 0 || fuselow >= 0 || fusehigh >= 0){
		if(fuselow >= 0 && (fuselow & 16rF) == 0)
			err("don't program external clock");
		if(fuseext >= 0 && (fuseext & (1<<0)) == 0)
			err("don't program ATmega103 compatibility");
		if(fusehigh >= 0 && (fusehigh & (1<<7)) == 0)
			err("don't program OCDEN=0");
		if(fusehigh >= 0 && writefusehigh(fusehigh) >= 0)
			sys->print("set fuse high=%.2ux\n", fusehigh);
		if(fuselow >= 0 && writefuselow(fuselow) >= 0)
			sys->print("set fuse low=%.2ux\n", fuselow);
		if(fuseext >= 0 && writefuseext(fuseext) >= 0)
			sys->print("set fuse ext=%.2ux\n", fuseext);
		shutdown();
		exit;
	}

	if(!verify && erase){
		chiperase();
		sys->print("Erased flash\n");
	}

	totbytes := 0;
	while((l := fd.gets('\n')) != nil){
		(c, addr, data) := sdecode(l);
		if(c >= '1' && c <= '3'){
			if(verify){
				fdata := readflashdata(addr, len data);
				if(!eq(fdata, data))
					sys->print("mismatch: %d::%d at %4.4ux\n", len data, len fdata, addr);
			}else if(writeflashdata(addr, data) != len data)
				err("failed to program device");
			totbytes += len data;
		} else if(c == '0')
			sys->print("title: %q\n", string data);
	}
	if(!verify){
		flushpage();
		sys->print("Programmed %ud (0x%4.4ux) bytes\n", totbytes, totbytes);
	}

	shutdown();
}

ckl[T](m: T, s: string): T
{
	if(m == nil)
		err(sys->sprint("can't load %s: %r", s));
	return m;
}

fuseval(s: string): int
{
	(n, t) := str->toint(s, 16);
	if(t != nil || n < 0 || n > 255)
		err("illegal fuse value");
	return n;
}

cache: (int, array of byte);

readflashdata(addr: int, nbytes: int): array of byte
{
	data := array[nbytes] of byte;
	ia := addr;
	ea := addr+nbytes;
	while(addr < ea){
		(ca, cd) := cache;
		if(addr >= ca && addr < ca+len cd){
			n := nbytes;
			o := addr-ca;
			if(o+n > len cd)
				n = len cd - o;
			if(addr-ia+n > len data)
				n = len data - (addr-ia);
			data[addr-ia:] = cd[o:o+n];
			addr += n;
		}else{
			ca = addr & ~16rFF;
			cd = readflashpage(ca, 16r100);
			cache = (ca, cd);
		}
	}
	return data;
}

writeflashdata(addr: int, data: array of byte): int
{
	pagesize := avrs[0].pagesize;
	ia := addr;
	ea := addr+len data;
	while(addr < ea){
		(ca, cd) := cache;
		if(addr >= ca && addr < ca+len cd){
			n := len data;
			o := addr-ca;
			if(o+n > len cd)
				n = len cd - o;
			cd[o:] = data[0:n];
			addr += n;
			data = data[n:];
		}else{
			if(flushpage() < 0)
				break;
			cache = (addr & ~16rFF, array[pagesize] of {* => byte 16rFF});
		}
	}
	return addr-ia;
}

flushpage(): int
{
	(ca, cd) := cache;
	if(len cd == 0)
		return 0;
	cache = (0, nil);
	if(writeflashpage(ca, cd) != len cd)
		return -1;
	return len cd;
}
			
shutdown()
{
#	setisp(0);
	if(rd != nil){
		rd.stop();
		rd = nil;
	}
	if(timers != nil)
		timers->shutdown();
}

err(s: string)
{
	sys->fprint(sys->fildes(2), "burn: %s\n", s);
	shutdown();
	raise "fail:error";
}

dump(a: array of byte): string
{
	s := sys->sprint("[%d]", len a);
	for(i := 0; i < len a; i++)
		s += sys->sprint(" %.2ux", int a[i]);
	return s;
}

initialise()
{
	if(mib510){
		# MIB510-specific: switch rs232 to STK500
		for(i:=0; i<8; i++){
			setisp0(1);
			sys->sleep(10);
			rd.flush();
			if(setisp(1))
				break;
		}
		if(!setisp(1))
			err("no response from programmer");
	}
	resync();
	resync();
	if(!mib510){
		r := rpc(array[] of {Cmd_STK_GET_SIGN_ON}, 7);
		if(r != nil)
			sys->print("got: %q\n", string r);
	}
	r := readsig();
	if(len r > 0 && r[0] != byte 16rFF)
		sys->print("sig: %s\n", dump(r));
	(min, maj) := version();
	sys->print("Firmware version: %s.%s\n", min, maj);
	setdevice(avrs[0]);
	pgmon();
	r = readsig();
	sys->print("sig: %s\n", dump(r));
	pgmoff();
	if(len r < 3 || r[0] != byte 16r1e || r[1] != byte 16r97 || r[2] != byte 16r02)
		if(!ignore)
		err("unlikely response: check connections");

	# could set voltages here...
	sys->print("fuses: h=%.2ux l=%.2ux e=%.2ux\n", readfusehigh(), readfuselow(), readfuseext());
}

resync()
{
	for(i := 0; i < 8; i++){
		rd.flush();
		r := rpc(array[] of {Cmd_STK_GET_SYNC}, 0);
		if(r != nil)
			return;
	}
	err("lost sync with programmer");
}

getparam(p: byte): int
{
	r := rpc(array[] of {Cmd_STK_GET_PARAMETER, p}, 1);
	if(len r > 0)
		return int r[0];
	return -1;
}

version(): (string, string)
{
	maj := getparam(Parm_STK_SW_MAJOR);
	min := getparam(Parm_STK_SW_MINOR);
	if(mib510)
		return (sys->sprint("%c", maj), sys->sprint("%c", min));
	return (sys->sprint("%d", maj), sys->sprint("%d", min));
}

eq(a, b: array of byte): int
{
	if(len a != len b)
		return 0;
	for(i := 0; i < len a; i++)
		if(a[i] != b[i])
			return 0;
	return 1;
}

#
# Motorola S records
#

badsrec(s: string)
{
	err("bad S record: "+s);
}

hexc(c: int): int
{
	if(c >= '0' && c <= '9')
		return c-'0';
	if(c >= 'a' && c <= 'f')
		return c-'a'+10;
	if(c >= 'A' && c <= 'F')
		return c-'A'+10;
	return -1;
}

g8(s: string): int
{
	if(len s >= 2){
		c0 := hexc(s[0]);
		c1 := hexc(s[1]);
		if(c0 >= 0 && c1 >= 0)
			return (c0<<4) | c1;
	}
	return -1;
}

# S d len 
sdecode(s: string): (int, int, array of byte)
{
	while(len s > 0 && (s[len s-1] == '\r' || s[len s-1] == '\n'))
		s = s[0:len s-1];
	if(len s < 4 || s[0] != 'S')
		badsrec(s);
	l := g8(s[2:4]);
	if(l < 0)
		badsrec("length: "+s);
	if(2*l != len s - 4)
		badsrec("length: "+s);
	csum := l;
	na := 2;
	if(s[1] >= '1' && s[1] <= '3')
		na = s[1]-'1'+2;
	addr := 0;
	for(i:=0; i<na; i++){
		b := g8(s[4+i*2:]);
		if(b < 0)
			badsrec(s);
		csum += b;
		addr = (addr << 8) | b;
	}
	case s[1] {
	'0' or		# used as segment name (seems to be srec file name with TinyOS)
	'1' to '3' or	# data
	'5' or		# plot so far
	'7' to '9' =>	# end/start address
		;
	* =>
		badsrec("type: "+s);
	}
	data := array[l-na-1] of byte;
	for(i = 0; i < len data; i++){
		c := g8(s[4+(na+i)*2:]);
		csum += c;
		data[i] = byte c;
	}
	v := g8(s[4+l*2-2:]);
	csum += v;
	if((csum & 16rFF) != 16rFF)
		badsrec("checksum: "+s);
	return (s[1], addr, data);
}

#
# serial port
#

Rd.new(fd: ref Sys->FD): ref Rd
{
	r := ref Rd(chan[4] of array of byte, 0, fd, nil);
	c := chan of int;
	spawn r.reader(c);
	<-c;
	return r;
}

Rd.reader(r: self ref Rd, c: chan of int)
{
	r.pid = sys->pctl(0, nil);
	c <-= 1;
	for(;;){
		buf := array[258] of byte;
		n := sys->read(r.fd, buf, len buf);
		if(n <= 0){
			r.pid = 0;
			err(sys->sprint("read error: %r"));
		}
		if(debug)
			sys->print("<- %s\n", dump(buf[0:n]));
		r.c <-= buf[0:n];
	}
}

Rd.read(r: self ref Rd, ms: int): array of byte
{
	if((a := r.buf) != nil){
		r.buf = nil;
		return a;
	}
	t := Timer.start(ms);
	alt{
	a = <-r.c =>
		t.stop();
	    Acc:
		for(;;){
			sys->sleep(5);
			alt{
			b := <-r.c =>
				if(b == nil)
					break Acc;
				a = cat(a, b);
			* =>
				break Acc;
			}
		}
		return a;
	<-t.timeout =>
		return nil;
	}
}

Rd.readn(r: self ref Rd, n: int, ms: int): array of byte
{
	a: array of byte;

	while((need := n - len a) > 0){
		b := r.read(ms);
		if(b == nil)
			break;
		if(len b > need){
			r.buf = b[need:];
			b = b[0:need];
		}
		a = cat(a, b);
	}
	return a;
}

Rd.flush(r: self ref Rd)
{
	r.buf = nil;
	sys->sleep(5);
	for(;;){
		alt{
		<-r.c =>
			;
		* =>
			return;
		}
	}
}

Rd.stop(r: self ref Rd)
{
	pid := r.pid;
	if(pid){
		fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
		if(fd != nil)
			sys->fprint(fd, "kill");
	}
}

cat(a, b: array of byte): array of byte
{
	if(len b == 0)
		return a;
	if(len a == 0)
		return b;
	c := array[len a + len b] of byte;
	c[0:] = a;
	c[len a:] = b;
	return c;
}

#
# STK500 communication protocol
#

STK_SIGN_ON_MESSAGE: con "AVR STK";   # Sign on string for Cmd_STK_GET_SIGN_ON

# Responses

Resp_STK_OK: con byte 16r10;
Resp_STK_FAILED: con byte 16r11;
Resp_STK_UNKNOWN: con byte 16r12;
Resp_STK_NODEVICE: con byte 16r13;
Resp_STK_INSYNC: con byte 16r14;
Resp_STK_NOSYNC: con byte 16r15;

Resp_ADC_CHANNEL_ERROR: con byte 16r16;
Resp_ADC_MEASURE_OK: con byte 16r17;
Resp_PWM_CHANNEL_ERROR: con byte 16r18;
Resp_PWM_ADJUST_OK: con byte 16r19;

# Special constants

Sync_CRC_EOP: con byte 16r20;

# Commands

Cmd_STK_GET_SYNC: con byte 16r30;
Cmd_STK_GET_SIGN_ON: con byte 16r31;

Cmd_STK_SET_PARAMETER: con byte 16r40;
Cmd_STK_GET_PARAMETER: con byte 16r41;
Cmd_STK_SET_DEVICE: con byte 16r42;
Cmd_STK_SET_DEVICE_EXT: con byte 16r45;

Cmd_STK_ENTER_PROGMODE: con byte 16r50;
Cmd_STK_LEAVE_PROGMODE: con byte 16r51;
Cmd_STK_CHIP_ERASE: con byte 16r52;
Cmd_STK_CHECK_AUTOINC: con byte 16r53;
Cmd_STK_LOAD_ADDRESS: con byte 16r55;
Cmd_STK_UNIVERSAL: con byte 16r56;
Cmd_STK_UNIVERSAL_MULTI: con byte 16r57;

Cmd_STK_PROG_FLASH: con byte 16r60;
Cmd_STK_PROG_DATA: con byte 16r61;
Cmd_STK_PROG_FUSE: con byte 16r62;
Cmd_STK_PROG_LOCK: con byte 16r63;
Cmd_STK_PROG_PAGE: con byte 16r64;
Cmd_STK_PROG_FUSE_EXT: con byte 16r65;

Cmd_STK_READ_FLASH: con byte 16r70;
Cmd_STK_READ_DATA: con byte 16r71;
Cmd_STK_READ_FUSE: con byte 16r72;
Cmd_STK_READ_LOCK: con byte 16r73;
Cmd_STK_READ_PAGE: con byte 16r74;
Cmd_STK_READ_SIGN: con byte 16r75;
Cmd_STK_READ_OSCCAL: con byte 16r76;
Cmd_STK_READ_FUSE_EXT: con byte 16r77;
Cmd_STK_READ_OSCCAL_EXT: con byte 16r78;

# Parameter constants

Parm_STK_HW_VER: con byte 16r80; # ' ' - R
Parm_STK_SW_MAJOR: con byte 16r81; # ' ' - R
Parm_STK_SW_MINOR: con byte 16r82; # ' ' - R
Parm_STK_LEDS: con byte 16r83; # ' ' - R/W
Parm_STK_VTARGET: con byte 16r84; # ' ' - R/W
Parm_STK_VADJUST: con byte 16r85; # ' ' - R/W
Parm_STK_OSC_PSCALE: con byte 16r86; # ' ' - R/W
Parm_STK_OSC_CMATCH: con byte 16r87; # ' ' - R/W
Parm_STK_RESET_DURATION: con byte 16r88; # ' ' - R/W
Parm_STK_SCK_DURATION: con byte 16r89; # ' ' - R/W

Parm_STK_BUFSIZEL: con byte 16r90; # ' ' - R/W, Range {0..255}
Parm_STK_BUFSIZEH: con byte 16r91; # ' ' - R/W, Range {0..255}
Parm_STK_DEVICE: con byte 16r92; # ' ' - R/W, Range {0..255}
Parm_STK_PROGMODE: con byte 16r93; # ' ' - 'P' or 'S'
Parm_STK_PARAMODE: con byte 16r94; # ' ' - TRUE or FALSE
Parm_STK_POLLING: con byte 16r95; # ' ' - TRUE or FALSE
Parm_STK_SELFTIMED: con byte 16r96; # ' ' - TRUE or FALSE

# status bits

Stat_STK_INSYNC: con byte 16r01; # INSYNC status bit, '1' - INSYNC
Stat_STK_PROGMODE: con byte 16r02; # Programming mode,  '1' - PROGMODE
Stat_STK_STANDALONE: con byte 16r04; # Standalone mode,   '1' - SM mode
Stat_STK_RESET: con byte 16r08; # RESET button,      '1' - Pushed
Stat_STK_PROGRAM: con byte 16r10; # Program button, '   1' - Pushed
Stat_STK_LEDG: con byte 16r20; # Green LED status,  '1' - Lit
Stat_STK_LEDR: con byte 16r40; # Red LED status,    '1' - Lit
Stat_STK_LEDBLINK: con byte 16r80; # LED blink ON/OFF,  '1' - Blink

ispmode := array[] of {byte 16rAA, byte 16r55, byte 16r55, byte 16rAA, byte 16r17, byte 16r51, byte 16r31, byte 16r13,  byte 0};	# last byte is 1 to switch isp on 0 to switch off

ck(r: array of byte)
{
	if(r == nil)
		err("programming failed");
}

pgmon()
{
	ck(rpc(array[] of {Cmd_STK_ENTER_PROGMODE}, 0));
}

pgmoff()
{
	ck(rpc(array[] of {Cmd_STK_LEAVE_PROGMODE}, 0));
}

setisp0(on: int)
{
	rd.flush();
	buf := array[len ispmode] of byte;
	buf[0:] = ispmode;
	buf[8] = byte on;
	sys->write(sfd, buf, len buf);
}

setisp(on: int): int
{
	rd.flush();
	buf := array[len ispmode] of byte;
	buf[0:] = ispmode;
	buf[8] = byte on;
	r := send(buf, 2);
	return len r == 2 && ok(r);
}

readsig(): array of byte
{
	r := send(array[] of {Cmd_STK_READ_SIGN, Sync_CRC_EOP}, 5);
	# doesn't behave as documented in AVR061: it repeats the command bytes instead
	if(len r != 5 || r[0] != Cmd_STK_READ_SIGN || r[4] != Sync_CRC_EOP){
		sys->fprint(sys->fildes(2), "bad reply %s\n", dump(r));
		return nil;
	}
	return r[1:len r-1];	# trim proto bytes
}

pgrpc(a: array of byte, repn: int): array of byte
{
	pgmon();
	r := rpc(a, repn);
	pgmoff();
	return r;
}

eop := array[] of {Sync_CRC_EOP};

rpc(a: array of byte, repn: int): array of byte
{
	r := send(cat(a, eop), repn+2);
	if(!ok(r)){
		if(len r >= 2 && r[0] == Resp_STK_INSYNC && r[len r-1] == Resp_STK_NODEVICE)
			err("internal error: programming parameters not correctly set");
		if(len r >= 1 && r[0] == Resp_STK_NOSYNC)
			err("lost synchronisation");
		sys->fprint(sys->fildes(2), "bad reply %s\n", dump(r));
		return nil;
	}
	return r[1:len r-1];	# trim sync bytes
}

send(a: array of byte, repn: int): array of byte
{
	if(debug)
		sys->print("-> %s\n", dump(a));
	if(sys->write(sfd, a, len a) != len a)
		err(sys->sprint("write error: %r"));
	return rd.readn(repn, 2000);
}

ok(r: array of byte): int
{
	return len r >= 2 && r[0] == Resp_STK_INSYNC && r[len r -1] == Resp_STK_OK;
}

universal(req: array of byte): int
{
	r := pgrpc(cat(array[] of {Cmd_STK_UNIVERSAL}, req), 1);
	if(r == nil)
		return -1;
	return int r[0];
}

setdevice(d: Avr)
{
	b := array[] of {
		Cmd_STK_SET_DEVICE,
		byte d.id,
		byte d.rev,
		byte 0,	# prog type (CHECK)
		byte d.fullpar,
		byte d.polling,
		byte d.selftimed,
		byte d.lockbytes,
		byte d.fusebytes,
		byte d.fpoll,
		byte d.fpoll,
		byte d.epoll1,
		byte d.epoll2,
		byte (d.pagesize >> 8), byte d.pagesize,
		byte (d.eepromsize>>8), byte d.eepromsize,
		byte (d.flashsize>>24), byte (d.flashsize>>16), byte (d.flashsize>>8), byte d.flashsize
	};
	ck(rpc(b, 0));
	if(mib510)
		return;
	b = array[] of {
		Cmd_STK_SET_DEVICE_EXT,
		byte 4,
		byte d.eeprompagesize,
		byte d.signalpagel,
		byte d.signalbs2,
		byte 0	# ResetDisable
	};
	ck(rpc(b, 0));
}

chiperase()
{
	ck(pgrpc(array[] of {Cmd_STK_CHIP_ERASE}, 0));
}

readfuselow(): int
{
	return universal(array[] of {byte 16r50, byte 0, byte 0, byte 0});
}

readfusehigh(): int
{
	return universal(array[] of {byte 16r58, byte 8, byte 0, byte 0});
}

readfuseext(): int
{
	return universal(array[] of {byte 16r50, byte 8, byte 0, byte 0});
}

readlockfuse(): int
{
	return universal(array[] of {byte 16r58, byte 0, byte 0, byte 0});
}

readflashpage(addr: int, nb: int): array of byte
{
	return readmem('F', addr/2, nb);
}

readeeprompage(addr: int, nb: int): array of byte
{
	return readmem('E', addr, nb);
}

readmem(memtype: int, addr: int, nb: int): array of byte
{
	if(nb > 256)
		nb = 256;
	pgmon();
	r := rpc(array[] of {Cmd_STK_LOAD_ADDRESS, byte addr, byte (addr>>8)}, 0);
	if(r != nil){
		r = send(array[] of {Cmd_STK_READ_PAGE, byte (nb>>8), byte nb, byte memtype, Sync_CRC_EOP}, nb+2);
		l := len r;
		# AVR601 says last byte should be Resp_STK_OK but it's not, at least on MIB; check for both
		if(l >= 2 && r[0] == Resp_STK_INSYNC && (r[l-1] == Resp_STK_INSYNC || r[l-1] == Resp_STK_OK))
			r = r[1:l-1];	# trim framing bytes
		else{
			sys->print("bad reply: %s\n", dump(r));
			r = nil;
		}
		if(len r < nb)
			sys->print("short [%d@%4.4ux]\n", nb, addr);
	}
	pgmoff();
	return r;
}

writeflashpage(addr: int, data: array of byte): int
{
	return writemem('F', addr/2, data);
}

writeeeprompage(addr: int, data: array of byte): int
{
	return writemem('E', addr, data);
}

writemem(memtype: int, addr: int, data: array of byte): int
{
	nb := len data;
	if(nb > 256){
		nb = 256;
		data = data[0:nb];
	}
	pgmon();
	r := rpc(array[] of {Cmd_STK_LOAD_ADDRESS, byte addr, byte (addr>>8)}, 0);
	if(r != nil){
		r = rpc(cat(array[] of {Cmd_STK_PROG_PAGE, byte (nb>>8), byte nb, byte memtype},data), 0);
		if(r == nil)
			nb = -1;
	}
	pgmoff();
	return nb;
}

writefuseext(v: int): int
{
	return universal(array[] of {byte 16rAC, byte 16rA4, byte 16rFF, byte v});
}

writefuselow(v: int): int
{
	return universal(array[] of {byte 16rAC, byte 16rA0, byte 16rFF, byte v});
}

writefusehigh(v: int): int
{
	return universal(array[] of {byte 16rAC, byte 16rA8, byte 16rFF, byte v});
}