From 6bdfea64359b0229aa1389c3706d324692ea674a Mon Sep 17 00:00:00 2001 From: Werner Almesberger Date: Fri, 25 May 2012 13:55:02 -0300 Subject: [PATCH] bacon/prog/: simple PIC 18F{2,4}xJxx programmer (for the Ben Nanonote) --- bacon/prog/Makefile | 63 ++++ bacon/prog/gpio-xburst.c | 42 +++ bacon/prog/gpio-xburst.h | 80 +++++ bacon/prog/picpen.c | 744 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 929 insertions(+) create mode 100644 bacon/prog/Makefile create mode 100644 bacon/prog/gpio-xburst.c create mode 100644 bacon/prog/gpio-xburst.h create mode 100644 bacon/prog/picpen.c diff --git a/bacon/prog/Makefile b/bacon/prog/Makefile new file mode 100644 index 0000000..5a5db11 --- /dev/null +++ b/bacon/prog/Makefile @@ -0,0 +1,63 @@ +# +# picpen/Makefile - PIC Programmer/Emulator for Nanonote +# +# Written 2008, 2010-2011 by Werner Almesberger +# Copyright 2008, 2010-2011 Werner Almesberger +# +# 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 2 of the License, or +# (at your option) any later version. +# + + +PREFIX=/usr + +ifeq ($(TARGET),) +TARGET = ben_openwrt +endif + +CC_ben_jlime = mipsel-linux-gcc +CC_ben_openwrt = mipsel-openwrt-linux-gcc + +UPLOAD_ben_jlime = scp f32x jlime: +UPLOAD_ben_openwrt = scp f32x ben: + +NAME = picpen +CC = $(CC_$(TARGET)) +CFLAGS = -Wall -Wshadow -O9 -g -static +OBJS = picpen.o gpio-xburst.o +LDFLAGS = -static + + +.PHONY: all install uninstall clean depend spotless + +all: $(NAME) + +$(NAME): $(OBJS) + +upload: + $(UPLOAD_$(TARGET)) + +install: $(NAME) + install -D $(NAME) $(PREFIX)/bin/$(NAME) + +uninstall: + rm -f $(PREFIX)/bin/$(NAME) + +depend: + $(CPP) $(CFLAGS) -MM -MG *.c >.depend || \ + { rm -f .depend; exit 1; } + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif + +c2-om.o: c2-bitbang.c +c2-ben.o: c2-bitbang.c + +clean: + rm -f $(OBJS) $(OBJS_ben) .depend + +spotless: clean + rm -f $(NAME) diff --git a/bacon/prog/gpio-xburst.c b/bacon/prog/gpio-xburst.c new file mode 100644 index 0000000..15ebfbf --- /dev/null +++ b/bacon/prog/gpio-xburst.c @@ -0,0 +1,42 @@ +/* + * gpio-xburst.c - Really primitive XBurst GPIO access + * + * Written 2010-2011 by Werner Almesberger + * Copyright 2010-2011 Werner Almesberger + * + * 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 2 of the License, or + * (at your option) any later version. + */ + + +#include +#include +#include +#include + +#include "gpio-xburst.h" + + +#define BASE 0x10010000 + + +volatile void *mem; + + +void gpio_init(void) +{ + int fd; + + fd = open("/dev/mem", O_RDWR | O_SYNC); + if (fd < 0) { + perror("/dev/mem"); + exit(1); + } + mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, BASE); + if (mem == MAP_FAILED) { + perror("mmap"); + exit(1); + } +} diff --git a/bacon/prog/gpio-xburst.h b/bacon/prog/gpio-xburst.h new file mode 100644 index 0000000..1c7146c --- /dev/null +++ b/bacon/prog/gpio-xburst.h @@ -0,0 +1,80 @@ +/* + * gpio-xburst.h - Really primitive XBurst GPIO access + * + * Written 2010 by Werner Almesberger + * Copyright 2010 Werner Almesberger + * + * 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 2 of the License, or + * (at your option) any later version. + */ + +/* + * Ports are numbered 0 = A, 1 = B, ... + */ + + +#ifndef GPIO_XBURST_H +#define GPIO_XBURST_H + + +#include + + +volatile void *mem; + + +#define REG(off) (*(volatile uint32_t *) (mem+(off))) + +#define port_pin(port) REG(port*0x100) +#define port_dats(port) REG(port*0x100+0x14) +#define port_datc(port) REG(port*0x100+0x18) +#define port_func(port) REG(port*0x100+0x48) +#define port_dirs(port) REG(port*0x100+0x64) +#define port_dirc(port) REG(port*0x100+0x68) + + +static inline void gpio_high(unsigned port, unsigned bit) +{ + port_dats(port) = 1 << bit; +} + + +static inline void gpio_low(unsigned port, unsigned bit) +{ + port_datc(port) = 1 << bit; +} + + +static inline void gpio_set(unsigned port, unsigned bit, int value) +{ + if (value) + gpio_high(port, bit); + else + gpio_low(port, bit); +} + + +static inline int gpio_get(unsigned port, unsigned bit) +{ + return (port_pin(port) >> bit) & 1; +} + + +static inline void gpio_output(unsigned port, unsigned bit) +{ + port_dirs(port) = 1 << bit; +} + + +static inline void gpio_input(unsigned port, unsigned bit) +{ + port_dirc(port) = 1 << bit; +} + + +void gpio_init(void); + + +#endif /* !GPIO_XBURST_H */ diff --git a/bacon/prog/picpen.c b/bacon/prog/picpen.c new file mode 100644 index 0000000..2207b22 --- /dev/null +++ b/bacon/prog/picpen.c @@ -0,0 +1,744 @@ +/* + * picpen.c - PIC (18F{2,4}xJxx) Programmer/Emulator for Nanonote + * + * Written 2012 by Werner Almesberger + * Copyright 2012 Werner Almesberger + * + * 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 2 of the License, or + * (at your option) any later version. + */ + + +#include +#include +#include +#include +#include +#include + +#include "gpio-xburst.h" + + +#define POWER_OFF 3, 2 /* PD02 */ +#define nMCLR 3, 13 /* PD13 DAT3 */ +#define PGD 3, 8 /* PD09 CMD */ +#define PGC 3, 9 /* PD08 CLK */ + +#define ICSP_KEY 0x4d434850 + +#define P5_US 1 /* Delay Between 4-Bit Command and Command Operand */ +#define P5A_US 1 /* Delay Between 4-Bit Command Operand and Next 4-Bit + Command */ +#define P6_US 1 /* Delay Between Last PGC down of Command Byte + to First PGC up of Read of Data Word */ +#define P9_US 1200 /* Delay to allow Block Programming to Occur */ +#define P10_US 54000 /* Delay to allow Row Erase to Occur */ +#define P11_US 524000 /* Delay to allow Bulk Erase to Occur */ +#define P12_US 400 /* Input Data Hold Time from nMCLR up */ +#define P13_US 1 /* VDD up Setup Time to nMCLR up */ +#define P16_US 1 /* Delay Between Last PGC down and nMCLR down */ +#define P17_US 3 /* nMCLR down to VDD down */ +#define P19_US 4000 /* Delay from First nMCLR down to First PGC up for + Key Sequence on PGD */ +#define P20_US 1 /* Delay from Last PGC down for Key Sequence on + PGD to Second nMCLR up */ + +#define CMD_INSN 0 +#define CMD_TABLAT 0x2 +#define CMD_READ_INC 0x9 /* post-increment by 1 */ +#define CMD_WRITE 0xc +#define CMD_WRITE_INC 0xd /* post-increment by 2 */ +#define CMD_WRITE_START 0xf + +#define INSN_MOVLW 0x0e +#define INSN_MOVWF 0x6e +#define INSN_MOVF_W_0 0x50 +#define INSN_MOVFF1 0xcf +#define INSN_MOVFF2 0xff + +#define REG_TABLAT 0xf5 +#define REG_TBLPTRL 0xf6 +#define REG_TBLPTRH 0xf7 +#define REG_TBLPTRU 0xf8 + +#define REG_ANCON0 0x48 /* banked ! */ +#define REG_ANCON1 0x49 /* banked ! */ +#define REG_PORTA 0x80 +#define REG_PORTC 0x82 +#define REG_LATA 0x89 +#define REG_TRISA 0x92 /* 1 = in */ +#define REG_EECON1 0xa6 +#define REG_CTMUICON 0xb1 +#define REG_CTMUCONL 0xb2 +#define REG_CTMUCONH 0xb3 +#define REG_ADCON1 0xc1 +#define REG_ADCON0 0xc2 +#define REG_ADRESL 0xc3 +#define REG_ADRESH 0xc4 + +#define BLOCK 64 + + +struct rec { + uint32_t addr; + uint8_t *data; + int len; + struct rec *next; +}; + +static int kb; + + +/* The multiplier (100) is a wild guess. Tested down to 0. */ + +static inline void delay(int us) +{ + uint32_t i; + + if (us < 10) + for (i = 0; i != us*100; i++) + asm(""); + else + usleep(us); +} + + +static void icsp_begin(int power) +{ + int i; + + gpio_init(); + + gpio_high(nMCLR); + gpio_high(PGD); + gpio_high(PGC); + + if (power) { + gpio_output(POWER_OFF); + gpio_output(PGD); + gpio_output(PGC); + gpio_output(nMCLR); + + delay(100*1000); /* precharge */ + } + + gpio_low(PGD); + gpio_low(PGC); + gpio_low(nMCLR); + + if (power) + gpio_low(POWER_OFF); + delay(P13_US); + + gpio_high(nMCLR); + delay(1); + gpio_low(nMCLR); + delay(P19_US); + + for (i = 31; i >= 0; i--) { + if ((ICSP_KEY >> i) & 1) + gpio_high(PGD); + else + gpio_low(PGD); + gpio_high(PGC); + gpio_low(PGC); + } + + delay(P20_US); + gpio_high(nMCLR); + delay(P12_US); +} + + +static void icsp_end(void) +{ + gpio_low(PGD); + gpio_low(PGC); + + delay(P16_US); + gpio_low(nMCLR); + delay(P17_US); + + gpio_high(nMCLR); +// gpio_high(POWER_OFF); + +gpio_input(PGD); +gpio_input(PGC); +} + + +static void icsp_send(uint8_t v, int n) +{ + int i; + + for (i = 0; i != n; i++) { + if ((v >> i) & 1) + gpio_high(PGD); + else + gpio_low(PGD); + gpio_high(PGC); + gpio_low(PGC); + } +} + + +static uint8_t icsp_recv(int n) +{ + uint8_t v = 0; + int i; + + for (i = 0; i != n; i++) { + gpio_high(PGC); + gpio_low(PGC); + if (gpio_get(PGD)) + v |= 1 << i; + } + return v; +} + + +static void icsp_write(uint8_t cmd, uint8_t a, uint8_t b) +{ + icsp_send(cmd, 4); + delay(P5_US); + icsp_send(b, 8); + icsp_send(a, 8); + delay(P5A_US); +} + + +static void write_reg(uint8_t addr, uint8_t v) +{ + icsp_write(CMD_INSN, INSN_MOVLW, v); + icsp_write(CMD_INSN, INSN_MOVWF, addr); +} + + +static uint8_t read_reg(uint8_t addr) +{ + uint8_t v; + + icsp_write(CMD_INSN, 0x50, addr); + /* for some strange reason we need two writes */ + icsp_write(CMD_INSN, INSN_MOVWF, REG_TABLAT); + icsp_write(CMD_INSN, INSN_MOVWF, REG_TABLAT); + + icsp_send(CMD_TABLAT, 4); + delay(P5_US); + + icsp_send(0, 8); + delay(P6_US); + gpio_input(PGD); + + v = icsp_recv(8); + delay(P5A_US); + gpio_output(PGD); + + return v; +} + + +static void set_tblptr(uint32_t addr) +{ + write_reg(REG_TBLPTRU, addr >> 16); + write_reg(REG_TBLPTRH, addr >> 8); + write_reg(REG_TBLPTRL, addr); +} + + +static void read_mem(uint32_t addr, uint8_t *buf, int len) +{ + set_tblptr(addr); + + while (len--) { + icsp_send(CMD_READ_INC, 4); + delay(P5_US); + icsp_send(0, 8); + delay(P6_US); + gpio_input(PGD); + + *buf++ = icsp_recv(8); + delay(P5A_US); + gpio_output(PGD); + } +} + + +static void bulk_erase(void) +{ + set_tblptr(0x3c0005); + icsp_write(CMD_WRITE, 0x01, 0x01); + set_tblptr(0x3c0004); + icsp_write(CMD_WRITE, 0x80, 0x80); + icsp_write(CMD_INSN, 0, 0); + + icsp_send(0, 4); + delay(P11_US+P10_US); + + icsp_send(0, 16); + delay(P5A_US); /* guess*/ +} + + +static void write_mem(uint32_t addr, const uint8_t *buf, int len) +{ + int i; + uint8_t first; + + assert(!(addr & (BLOCK-1))); + assert(!(len & (BLOCK-1))); + + /* BSF EECON1, WREN */ + icsp_write(CMD_INSN, 0x84, REG_EECON1); + + while (len) { + set_tblptr(addr); + for (i = 0; i != BLOCK/2-1; i++) { + first = *buf++; + icsp_write(CMD_WRITE_INC, *buf++, first); + } + first = *buf++; + icsp_write(CMD_WRITE_START, *buf++, first); + + icsp_send(0, 3); + gpio_low(PGD); + gpio_high(PGC); + delay(P9_US); + gpio_low(PGC); + delay(P5_US); + + icsp_send(0, 16); + delay(P5A_US); /* guess*/ + + addr += BLOCK; + len -= BLOCK; + } +} + + +/* ----- Flash-level operations -------------------------------------------- */ + + +static void flash_record(const struct rec *rec) +{ + uint8_t *tmp; + int off, padded; + + off = rec->addr & (BLOCK-1); + padded = (rec->len+off+BLOCK-1) & ~(BLOCK-1); + if (!off && padded == rec->len) { + write_mem(rec->addr, rec->data, rec->len); + return; + } + tmp = malloc(padded); + if (!tmp) { + perror("malloc"); + exit(1); + } + memset(tmp, 0xff, off); + memcpy(tmp+off, rec->data, rec->len); + memset(tmp+off+rec->len, 0xff, padded-rec->len-off); + write_mem(rec->addr-off, tmp, padded); +} + + +static void verify_record(const struct rec *rec) +{ + uint8_t *tmp; + int i; + + tmp = malloc(rec->len); + if (!tmp) { + perror("malloc"); + exit(1); + } + read_mem(rec->addr, tmp, rec->len); + for (i = 0; i != rec->len; i++) + if (rec->data[i] != tmp[i]) { + fprintf(stderr, + "%04x: wrote %02x != read %02x\n", + rec->addr+i, rec->data[i], tmp[i]); + } +} + + +static void flash_file(const struct rec *recs) +{ + const struct rec *rec; + + for (rec = recs; rec; rec = rec->next) + if (rec->addr+rec->len > kb*1024) { + fprintf(stderr, + "record 0x%x+0x%x ends outside Flash of %d bytes\n", + rec->addr, rec->len, kb*1024-8); + exit(1); + } + + bulk_erase(); + for (rec = recs; rec; rec = rec->next) + flash_record(rec); + + for (rec = recs; rec; rec = rec->next) + verify_record(rec); +} + + +static void dump_flash(void) +{ + uint8_t *tmp, c; + int i, j; + + tmp = malloc(kb*1024); + if (!tmp) { + perror("malloc"); + exit(1); + } + + read_mem(0, tmp, kb*1024); + + for (i = 0; i != kb*1024; i += 16) { + printf("%04X: ", i); + for (j = 0; j != 16; j++) + printf("%02X ", tmp[i+j]); + for (j = 0; j != 16; j++) { + c = tmp[i+j]; + printf("%c", c >= ' ' && c <= '~' ? c : '.'); + } + printf("\n"); + } +} + + +/* ----- Experiments ------------------------------------------------------- */ + + +#if 0 + +static void blink(void) +{ + write_reg(REG_TRISA, 0xfc); + while (1) { + write_reg(REG_LATA, 1); +gpio_high(PGD); + delay(200*1000); + write_reg(REG_LATA, 2); +gpio_high(PGD); + delay(200*1000); + } +} + + +static void adc(void) +{ +int i; + +#if 0 +for (i = 0; i != 256; i++) { +// write_reg(REG_ANCON0, 0xff); +// write_reg(REG_TABLAT, i); +// write_reg(REG_TABLAT, i); + printf("%02x ", read_reg(REG_PORTC)); +fflush(stdout); +} +#endif + write_reg(REG_TRISA, 0xfc); +// write_reg(REG_ANCON0, 0x10); /* AN4 is analog */ + write_reg(REG_ADCON0, 0x20); /* AN4, Vss to AVdd */ + write_reg(REG_ADCON1, 0x80); /* Tad = 0, Fosc/2, right just. */ + write_reg(REG_ADCON0, 0x21); /* Enable ADC module */ + while (1) { + uint8_t hi, lo; + + write_reg(REG_ADCON0, 0x23); /* GO */ + while (read_reg(REG_ADCON0) & 2) + write(2, ".", 1); + hi = read_reg(REG_ADRESH); + lo = read_reg(REG_ADRESL); + printf("\r%02x %02x (%u)\n", hi, lo, hi << 8 | lo); + } + +#if 0 + while (1) { + write_reg( + } +#endif + +} + + +static void cap(void) +{ + write_reg(REG_CTMUCONH, 0x00); /* page 405 */ + write_reg(REG_CTMUCONL, 0x90); + write_reg(REG_CTMUICON, 0x01); + + write_reg(REG_TRISA, 0xfc); + write_reg(REG_ADCON0, 0x20); /* AN4, Vss to AVdd */ +// write_reg(REG_ADCON1, 0x8e); /* Tad = 2, Fosc/32, right just. */ + write_reg(REG_ADCON1, 0x88); /* Tad = 2, Fosc/2, right just. */ + write_reg(REG_ADCON0, 0x21); /* Enable ADC module */ + + /* bandgap ?!? */ + + while (1) { + uint8_t hi, lo; + + write_reg(REG_CTMUCONH, 0x80); /* enable CTMU */ + write_reg(REG_CTMUCONL, 0x90); /* clear EDGxSTAT */ + write_reg(REG_CTMUCONH, 0x82); /* ground output */ + delay(1); + write_reg(REG_CTMUCONH, 0x80); /* end drain */ + write_reg(REG_CTMUCONL, 0x91); /* current on */ + write_reg(REG_CTMUCONL, 0x90); /* current off */ + + write_reg(REG_ADCON0, 0x23); /* GO */ + while (read_reg(REG_ADCON0) & 2) + write(2, ".", 1); + hi = read_reg(REG_ADRESH); + lo = read_reg(REG_ADRESL); + printf("\r%02x %02x (%u)\n", hi, lo, hi << 8 | lo); + } +} + + +static void dump(uint32_t addr) +{ + uint8_t buf[BLOCK]; + int i; + + read_mem(addr, buf, BLOCK); + for (i = 0; i != BLOCK; i++) { + if (!(i & 15)) + printf("%04X:", i); + printf(" %02X", buf[i]); + if ((i & 15) == 15) + printf("\n"); + } +} + + +static void rerwr(void) +{ + uint8_t buf[BLOCK]; + int i; + + dump(0x3fffc0); + dump(0); + bulk_erase(); + dump(0); + for (i = 0; i != BLOCK; i++) + buf[i] = i; + write_mem(0, buf, BLOCK); + dump(0x3fffc0); + dump(0); +} + +#endif + +/* ----- Chip identification ----------------------------------------------0 */ + + +struct chip { + const char *name; + uint8_t id2, id1; + int kb; +} chips[] = { + { "PIC18F24J50", 0x4c, 0x00, 16 }, + { NULL } +}; + + +#define DEVID1 0x3ffffe + + +static void identify(void) +{ + uint8_t id[2]; + const struct chip *chip; + + read_mem(DEVID1, id, 2); + for (chip = chips; chip->name; chip++) { + if ((chip->id1 ^ id[0]) & 0xe0) + continue; + if (chip->id2 == id[1]) + break; + } + fprintf(stderr, "ID = 0x%02x%02x (%s)\n", id[1], id[0], + chip->name ? chip->name : "?"); + if (!chip->name) + exit(1); + fprintf(stderr, "%d kB Flash\n", chip->kb); + kb = chip->kb; +} + + +/* ----- Intel HEX file reader --------------------------------------------- */ + + +static int hex(char c, int lineno) +{ + if (c >= '0' && c <= '9') + return c-'0'; + if (c >= 'A' && c <= 'F') + return c-'A'+10; + fprintf(stderr, "non-hex character \"%c\" in line %d\n", c, lineno); + exit(1); +} + + +/* http://en.wikipedia.org/wiki/Intel_HEX */ + + +static struct rec *load_file(const char *name) +{ + struct rec *recs = NULL, *last = NULL, *rec; + const struct rec *other; + FILE *file; + int lineno = 1; + char line[1000]; + const char *s; + uint8_t buf[sizeof(line)/2]; + uint8_t *p, *t, sum; + uint32_t xaddr = 0, addr; + + file = fopen(name, "r"); + if (!file) { + perror(name); + exit(1); + } + while (fgets(line, sizeof(line), file)) { + if (*line != ':') { + fprintf(stderr, "line %d doesn't start with colon\n", + lineno); + exit(1); + } + p = buf; + for (s = line+1; *s > ' '; s += 2) + *p++ = hex(s[0], lineno) << 4 | hex(s[1], lineno); + if (p-buf < 5) { + fprintf(stderr, "short record in line %d\n", lineno); + exit(1); + } + lineno++; + sum = 0; + for (t = buf; t != p-1; t++) + sum += *t; + if (0x100-sum != p[-1]) { + fprintf(stderr, + "checksum error (0x%02x vs. 0x%02x) in line %d\n", + p[-1], 0x100-sum, lineno); + exit(1); + } + switch (buf[3]) { + case 0: /* Data record */ + if (p-buf != buf[0]+5) { + fprintf(stderr, + "data record of %d bytes has length %d\n", + p-buf, buf[0]); + exit(1); + } + addr = xaddr << 16 | buf[1] << 8 | buf[2]; + if (last && last->addr+last->len == addr) { + last->data = realloc(last->data, + last->len+buf[0]); + if (!last->data) { + perror("realloc"); + exit(1); + } + memcpy(last->data+last->len, buf+4, buf[0]); + last->len += buf[0]; + break; + } + rec = malloc(sizeof(struct rec)); + rec->addr = xaddr << 16 | buf[1] << 8 | buf[2]; + rec->len = buf[0]; + rec->data = malloc(rec->len); + if (!rec->data) { + perror("realloc"); + exit(1); + } + rec->next = NULL; + memcpy(rec->data, buf+4, rec->len); + if (last) + last->next = rec; + else + recs = rec; + last = rec; + break; + case 1: /* End Of File record */ + goto end; + case 4: /* Extended Linear Address record */ + xaddr = buf[4] << 8 | buf[5]; + break; + default: + fprintf(stderr, + "unrecognized record type 0x%02x in line %d\n", + buf[3], lineno); + exit(1); + } + } +end: + for (rec = recs; rec; rec = rec->next) + for (other = rec->next; other; other = other->next) + if (rec->addr < other->addr+other->len && + rec->addr+rec->len > other->addr) { + fprintf(stderr, + "overlapping address ranges 0x%x+0x%x " + "and 0x%x+0x%x\n", + rec->addr, rec->len, other->addr, + other->len); + } + fclose(file); + return recs; +} + + +/* ----- Command-line parsing ---------------------------------------------- */ + + +static void usage(const char *name) +{ + fprintf(stderr, +"usage: %s [-n] [file.bin]\n", name); + exit(1); +} + + +int main(int argc, char **argv) +{ + const struct rec *recs = NULL; + int power = 1; + int c; + + while ((c = getopt(argc, argv, "n")) != EOF) + switch (c) { + case 'n': + power = 0; + break; + default: + usage(*argv); + } + + switch (argc-optind) { + case 0: + break; + case 1: + recs = load_file(argv[optind]); + break; + default: + usage(*argv); + } + + icsp_begin(power); + identify(); + + if (recs) + flash_file(recs); + else + dump_flash(); + icsp_end(); + + return 0; +}