/* * 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; }