YAPP (Yet Another PIC Programmer)

My original PIC programmer had strange habits. When I wanted to use it it wouldn't work so I would measure this and test that and change the other and after a long time nothing would have changed except that now it would start working. Possibly this was connected with it being constructed in the form of a ball of wires such as you might obtain from a pigeon eating electronic components to use as grit and then shitting them out some weeks later to be assembled into a three-dimensional tangle by three spiders and a mad cat. Whatever, I eventually got fed up with it playing up and generally being a smelly dog's bottom, and anyway I wasn't entirely sure where I'd put it, so I decided to build another one. And here it is.

PIC Programmer circuit diagram

Circuit diagram of PIC Programmer for 16F88 and similar PICs

It is intended to program the PIC 16F88 and other PICs with similar specifications. The hardware will most likely work with lots of other PICs. The software is more specific to the 16F88 and friends, but the differences are not large and so hacking it to work with other PICs is left as an exercise for the user.

It expects the target board to have a +5V supply for the PIC. It uses this both to power itself and as a reference from which to set the +13V programming voltage, which it generates itself by means of a boost converter. If your target board runs the PIC off some other voltage then there are various possible workarounds, such as making a separate little board with a +5V supply and a ZIF socket to plug the PIC into to program it.

The signals to and from the parallel port are buffered using four gates from an HEF40106. The remaining two gates form a buffered oscillator to drive the boost converter, which uses a BC547 to switch current through a 1mH inductor. The output from the boost converter is regulated by the 82k/56k potential divider and another BC547. Two further transistors act as a level shifter to switch on and off the programming voltage to the PIC. An LED illuminates when the programming voltage is switched on.

My choice of which parallel port pins/signals/polarities to use was dictated by convenience. I have no idea if there is any kind of de facto standard for this used by boughten versions since I have never seen the point of paying money for something which is so simple and cheap to build myself. If there is, and my pin allocation clashes, it is not difficult to use different pins instead and alter the port i/o functions accordingly.

Programming socket connections
Details of programming socket and plugs

I have picked up that there does appear to be a de facto standard of using a five pin header to connect the programming cable to the target board. On the face of it this makes sense, since there are five connections required, but in fact it does smell of wee to a certain extent. Depending on just what the rest of the circuitry is like, the programmer may have to fight whatever else is connected to the RB6/RB7/MCLR pins, which would be dead and chewed. So I prefer to use an eight pin connector, with RB6/RB7/MCLR connected to nothing but this connector, and their connections from the rest of the circuit connected to the three spare pins. With this arrangement, the programmer drives nothing but the PIC itself; when the programming plug is removed, it is replaced by a plug wired with three shorting links to connect RB6/RB7/MCLR to the rest of the circuit. It is convenient to use 8-pin DIL sockets for both socket and plugs, the socket being an ordinary bent-tin-pins variety, the plugs being turned-pin types.

The software accepts .hex files as produced by the Hitech PIC C compiler. This compiler supports embedding data destined for the PIC's EEPROM data memory in the .hex file along with the code; such data is distinguished by its being given the address range 0x2100-0x21ff, which does not actually correspond to a real available address. The software interprets data in this address range as being EEPROM data rather than program code. It also supports embedded data for the 16F88's two configuration words at 0x2007 and 0x2008. I haven't bothered to include support for programming the ID locations since I never use them, but it would not be hard to put it in.

It doesn't do much error checking. If it doesn't understand a line in the .hex file it ignores it.

The software examines the three standard parallel port addresses (0x378, 0x278 and 0x3bc) in turn, attempting to detect the presence of the programmer by looking for the data in line changing state to reflect changes in the state of the data out line. Of course the programmer needs to be powered up for this to work. If the environment variable PIC_LPT_ADDR is set to a value which is a valid hex address, this address will be examined first. This allows for non-standard parallel port addresses.

The timing values are based on the PIC 16F88 programming specification datasheet, interpreted liberally, ie. some of them are longer than the specified minima. Since we are using the parallel port there is not a lot of point trying to implement delays of less than one microsecond.

The code needs to be compiled with the -O option and the resulting binary made setuid root, in order to make the port i/o stuff work.

Download the source: picprog.c.gz

#include <stdio.h> #include <stdlib.h> #include <sys/io.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <signal.h> #include <ctype.h> #define VPP 0x01 #define CLK 0x02 #define DAT 0x04 #define TPROG1 1000 #define TPROG2 1000 #define TPROG3 10000 #define TPROG4 10000 #define TSET0 1 #define THLD0 5 #define TSET1 1 #define THLD1 1 #define TDLY1 1 #define TDLY2 1 #define TDLY3 1 #define TPU 250 #define LOADCONF 0 #define LOADPROG 0x02 #define LOADDATA 0x03 #define READPROG 0x04 #define READDATA 0x05 #define INCRADDR 0x06 #define BGNERASE 0x08 #define BEGNPROG 0x18 #define BLKEPROG 0x09 #define BLKEDATA 0x0b #define CHPERASE 0x1f #define ENDPROG 0x17 int portaddr = 0; unsigned char port_out(unsigned char mask, unsigned char val) { unsigned char state; state = (inb_p(portaddr) & (mask ^ 0xff)) | ((val ? 0 : 0xff) & mask); outb_p(state, portaddr); return(state); } int port_in(void) { return(inb_p(portaddr + 1) & 0x80 ? -1 : 0); } void prog_off(void) { port_out(VPP | CLK | DAT, 0); } void prog_off_exit(int dummy) { if (portaddr) prog_off(); exit(1); } int get_port(int port) { static int ports[] = {-1, 0x378, 0x278, 0x3bc, 0}; int i, x; if (ioperm(0x80, 1, 1)) { fprintf(stderr, "Cannot get permissions for port 0x80: %s\n", strerror(errno)); exit(1); } if (port) ports[0] = port; for (i=0; portaddr = ports[i]; i++) { if (portaddr < 0) continue; if (ioperm(portaddr, 3, 1)) { fprintf(stderr, "Cannot get permissions for port 0x%x: %s\n", portaddr, strerror(errno)); exit(1); } x = 0; prog_off(); if (!port_in()) x++; port_out(DAT, 1); if (port_in()) x++; port_out(DAT, 0); if (!port_in()) x++; if (x == 3) break; ioperm(portaddr, 3, 0); } sleep(2); return(portaddr); } void reset_pic(void) { prog_off(); usleep(TPU); port_out(VPP, 1); } int send_cmd(unsigned char cmd, int val) { int i, c, r; c = cmd; usleep(THLD0); for (i=6; i; --i) { port_out(CLK, 1); port_out(DAT, c & 1); usleep(TSET1); port_out(CLK, 0); usleep(THLD1); c >>= 1; } usleep(TDLY1 + TDLY2); switch(cmd) { case LOADCONF: case LOADPROG: case LOADDATA: r = (val & 0x3fff) << 1; for (i=16; i; --i) { port_out(CLK, 1); port_out(DAT, r & 1); usleep(TSET1); port_out(CLK, 0); usleep(THLD1); r >>= 1; } break; case READPROG: case READDATA: r = 0; for (i=16; i; --i) { port_out(CLK, 1); usleep(TDLY3); r |= (port_in() & 0x8000); port_out(CLK, 0); usleep(TDLY3); r >>= 1; } break; case ENDPROG: case INCRADDR: break; case BGNERASE: usleep(TPROG2); break; case BEGNPROG: usleep(TPROG1); break; case BLKEPROG: case BLKEDATA: port_out(DAT, 0); for (i=2; i; --i) { port_out(CLK, 1); usleep(TSET1); port_out(CLK, 0); usleep(THLD1); } usleep(TPROG3); break; case CHPERASE: usleep(TPROG4); break; } usleep(TDLY1); return(r); } int write_buf(unsigned char *buf, int len, int isdata) { int i, j, v, n; n = (isdata ? 1 : 4); while (len & 0x03) len++; reset_pic(); for (i=0; i < len; i += n) { printf("%.4x\r", i); fflush(stdout); for (j=n; j; --j) { v = *buf++; v |= (*buf++ << 8); send_cmd(isdata ? LOADDATA : LOADPROG, v); if (j != 1) send_cmd(INCRADDR, 0); } send_cmd(BEGNPROG, 0); send_cmd(ENDPROG, 0); send_cmd(INCRADDR, 0); } printf("%.4x\n", i); return(i); } int read_buf(unsigned char *buf, int len, int isdata) { int i, v; reset_pic(); for (i=0; i < len; i++) { printf("%.4x\r", i); fflush(stdout); v = send_cmd(isdata ? READDATA : READPROG, 0) & (isdata ? 0xff : 0x3fff); *buf++ = (v & 0xff); *buf++ = ((v >> 8) & 0xff); send_cmd(INCRADDR, 0); } printf("%.4x\n", i); return(i); } int write_conf(int *conf) { int i; send_cmd(LOADCONF, 0); for (i=7; i; --i) send_cmd(INCRADDR, 0); send_cmd(LOADPROG, *conf++); send_cmd(BEGNPROG, 0); send_cmd(ENDPROG, 0); send_cmd(INCRADDR, 0); send_cmd(LOADPROG, *conf); send_cmd(BEGNPROG, 0); send_cmd(ENDPROG, 0); return(2); } int read_conf(int *conf) { int i; send_cmd(LOADCONF, 0); for (i=7; i; --i) send_cmd(INCRADDR, 0); *conf++ = send_cmd(READPROG, 0) & 0x3fff; send_cmd(INCRADDR, 0); *conf = send_cmd(READPROG, 0) & 0x3fff; return(2); } void hd(unsigned char *buf, int len) { int i, j, p; for (i=0; i < len; i += 16) { printf("%.4x : ", i); for (j=(len - i < 16 ? len - i : 16); j; --j) { p = *buf++; p |= (*buf++ << 8); printf("%.4x ", p); } printf("\n"); } } #define get2(x,y) getn((x),(y),2) #define get4(x,y) getn((x),(y),4) char *getn(char *s, int *ret, int n) { char c; char *t; int g; if (!s) return(NULL); t = s; for (g=0; g < n; g++) if (!*t++) return(NULL); c = *t; *t = 0; g = sscanf(s, "%x", ret); *t = c; return(g == 1 ? t : NULL); } int hexparse(char *fn, unsigned char *obuf, int *conf) { FILE *fp; unsigned char buf[16384]; int i, x, y; unsigned char *s; int lc, sum; int num, addr, type, data, chksum; int addrhi=0; int max=0; int maxd=0; if ((fp = fopen(fn, "r")) == NULL) { fprintf(stderr, "%s: cannot open: %s\n", fn, strerror(errno)); exit(1); } lc=0; while (fgets(buf, 16384, fp)) { lc++; if (buf[0] != ':') { printf("Line %d (%s) does not start with ':'\n", lc, buf); continue; } for (s=&buf[1]; *s; s++) if (!isxdigit(*s)) { *s=0; break; } s=&buf[1]; if (!(s = get2(s, &num))) { printf("Line %d (%s) : error at 'num'\n", lc, buf); continue; } if (!(s = get4(s, &addr))) { printf("Line %d (%s) : error at 'addr'\n", lc, buf); continue; } if (!(s = get2(s, &type))) { printf("Line %d (%s) : error at 'type'\n", lc, buf); continue; } if ((type != 0) && (type != 1) && (type != 4)) { printf("Line %d (%s) : invalid 'type': %.2x\n", lc, buf, type); continue; } if (type == 1) { printf("Line %d (%s) : EOF\n", lc, buf); break; } if (type == 4) { addrhi = addr << 16; printf("Line %d (%s) : 'addrhi' set to %.8x\n", lc, buf, addrhi); continue; } x = addr + addrhi; y = x >> 1; sum = num + (addr & 0xff) + ((addr >> 8) & 0xff) + type; for (i=0; i < num; i++) { if (!(s = get2(s, &data))) break; obuf[i + x] = (unsigned char)data; sum += data; } if (!s) { printf("Line %d (%s) : invalid data byte %d\n", lc, buf, i); continue; } sum = (0 - sum) & 0xff; if (!(s = get2(s, &chksum))) { printf("Line %d (%s) : error at 'chksum'\n", lc, buf); continue; } if (sum != chksum) { printf("Line %d (%s) : checksum error: %.2x calculated, %.2x given\n", lc, buf, sum, chksum); continue; } printf("Line %d : Addr = %.8x Data (%d) = ", lc, y, num); for (i=0; i < num; i++) printf("%.2x ", obuf[i + x]); printf("Checksum = %.2x\n", sum); if ((y < 0x2000) || (y >= 0x2100)) { /* EEPROM data is given addresses starting 0x2100 */ y += (num >> 1); if (y < 0x2000) { if (max < y) max = y; } else { if (maxd < y) maxd = y; } } else if (y == 0x2007) { s = &obuf[x]; for (i=0; i < (num >> 1); i++) { conf[i] = *s++; conf[i] += (*s++ << 8); } } } fclose(fp); return(max | (maxd << 16)); } int main(int argc, char *argv[]) { int envport = 0; char *envportstr; unsigned char buf[16384]; unsigned char vbuf[16384]; int conf[2]; int rconf[2]; struct sigaction sa; sigset_t dset; int i; int len; memset(buf, 0, 16384); memset(vbuf, 0, 16384); conf[0] = conf[1] = rconf[0] = rconf[1] = 0x3fff; sigemptyset(&dset); sa.sa_handler = prog_off_exit; sa.sa_mask = dset; sa.sa_flags = 0; sigaction(SIGHUP, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGQUIT, &sa, NULL); sigaction(SIGILL, &sa, NULL); sigaction(SIGABRT, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); sigaction(SIGTERM, &sa, NULL); if (argc < 2) { fprintf(stderr, "No filename given\n"); exit(1); } if (envportstr = getenv("PIC_LPT_ADDR")) { if (sscanf(envportstr, "%x", &envport) != 1) envport = 0; } printf("Opening port...\n"); if (!(i = get_port(envport))) { fflush(stdout); fprintf(stderr, "Programmer not detected\n"); exit(1); } else { printf("Programmer detected on port 0x%x\n", i); } printf("Reading file %s...\n", argv[1]); len = hexparse(argv[1], buf, conf); hd(buf, len & 0xffff); printf("Erasing...\n"); reset_pic(); send_cmd(CHPERASE, 0); printf("Writing program memory...\n"); write_buf(buf, len & 0xffff, 0); printf("Writing config words %.4x %.4x...\n", conf[0], conf[1]); write_conf(conf); printf("Reading program memory...\n"); read_buf(vbuf, len & 0xffff, 0); hd(vbuf, len & 0xffff); printf("Programming program memory %s\n", memcmp(buf, vbuf, len & 0xffff) ? "failed" : "succeeded"); if (len & 0xffff0000) { len = (len >> 16) & 0xff; printf("Writing data memory...\n"); write_buf(buf + 0x4200, len, 1); printf("Reading data memory...\n"); read_buf(vbuf, len, 1); hd(vbuf, len); printf("Programming data memory %s\n", memcmp(buf + 0x4200, vbuf, len) ? "failed" : "succeeded"); } printf("Reading config words... "); read_conf(rconf); printf("%.4x %.4x\n", rconf[0], rconf[1]); printf("Programming config words %s\n", (conf[0] != rconf[0]) || (conf[1] != rconf[1]) ? "failed" : "succeeded"); printf("Closing...\n"); prog_off(); }

Back to Pigeon's Nest

Be kind to pigeons

Valid HTML 4.01!