; Model A1 Programmer Adapter for the Model 01a Control Engine. ; ; $Id: ma1.asm,v 1.3 2003/11/19 07:23:54 tomj Exp $ ; DESCRIPTION: ; ; Programming adapter for the Model 01 revision A board, using the ; in-circuit PROG connector. The host talks to this adapter via ; serial interface. Supports PIC16F627A/628A/648A. ; INTERFACE: ; ; To the host: Serial, 115200 baud, 8 data bits, no parity; TxD, RxD. ; To the target: 6-wire .1" SIP jack. ; ; HARDWARE: ; ; This assumes the Model A1 Programming Adapter hardware, which ; consists of a PIC16F627A, a MAX662A to make the flash programming ; voltage, and a transistor to shutdown the target VDD. ; ; The hardware is glitch-proof; it can be plugged on or off at ; any time it's in IDLEMODE (eg. not actively programming the ; target). +12V is clamped and shutdown by default, and the ; driving PIC pins are left as inputs when not actively ; driving +12V on. ; ; This code runs in a PIC16F627A or better. A 18.432 MHz crystal ; is required, or you'll have to adjust the software delay routines ; and the UART BRG values. See schematic for details. ; ; SOFTWARE: ; ; The programmer is structured such that the PC side performs ; the Microchip-recommended algorithm, while this adapter ; handles the bit-twiddling and hardware interface. A command ; line Perl script should be just fine. The programmer should ; be forward-compatible with future products and can identify ; programmer version and PIC-in-place. ; ; The PC sends one or three bytes to this programmer (see below), ; a command is issued to the PIC, ; when complete and ready for the next command, the PIC ; issues an ACK character. ; PIC commands seem to fall into three categories: ; A. Command word, no args (PROGRAM, INCREMENT PC, etc) ; B. Command word, two byte arg (LOAD DATA) ; C. Command word, reads two bytes (READ DATA) ; NOTE: This code does the required programming delays; the host ; can simply issue commands as fast as possible. ; This code does no procedural work nor massaging of ; data. The host-side needs to put the fill 0's in the ; data for type B and C commands and all that. ; PIC commands are given an ASCII offset; PIC commands 0 - 63 are ; offset to 32 to 95, eg. nice clean ASCII. Programmer commands ; are offset by 65 -- not 64! -- starting with 'a'. ; ASCII enc offset ; 32 - 95 +32 Microchip Inc commands ; 65 - 126 +65 (not 64! skip `) M01-programmer commands ; ASCII PIC TYPE ; LOADCONF= space 0 B load config ; ! 1 n/a ; LOADPMEM= " 2 B load program memory ; LOADDMEM= # 3 B load data memory ; READPMEM= $ 4 C read program memory ; READDMEM= % 5 C read data memory ; INCREMENT= & 6 A increment address ; ' 7 n/a ; PROGRAM= ( 8 A program cycle start ; BULKPROG= ) 9 A bulk erase program memory ; * 10 n/a ; BULKDATA= + 11 A bulk erase data memory ; Programmer commands. ; VERSION= A 0 C returns programmer version ; IDLE= B 1 A reset target to idle ; PROGMODE= C 2 A set target to program mode ; NOOP= D 3 A guarenteed to be ignored ; ACKON E 4 A NOT USED enable ACK every command, ; ACKOFF F 5 A NOT USED disable ACK every command, ; RESET G 6 A reset target ; These Microchip Inc recommended timing values are ; hard-coded in the code. ; ; THLD0= 5uS data hold time ; TDLY1= 1uS post-command delay ; TDLY2= 1uS min. time -clock to +clock ; TDIS= 0.5uS prog to compare time ; TPROG= 2.5 prog cycle time ; TDPROG= 6 data EE prog time ; TERA= 6 bulk erase time processor 16f628a include "p16f628a.inc" ; __CONFIG 0x3f62 ; HS oscillator, /MCLR __CONFIG 0x3f42 ; HS oscillator sans /MCLR pin errorlevel -302 ; MOST annoying. radix dec ; Slightly configurable items. constant VERS= "2" constant SPEED= 9 ; see comments in SERinit() constant ACKCHAR= "_" ; character that ACKs each command ; Defined by the PCB. constant DATA= 7 ; PORTB pin, to target pin 13, RB7/DATA constant CLOCK= 6 ; PORTB pin, to target pin 12, RB6/CLOCK constant RB4CLAMP= 5 ; PORTB pin, to target pin 10, RB4/PGM constant HVCLAMP= 4 ; PORTB HV clamp, grounds target /MCLR constant SHORTVDD= 3 ; PORTB short Vdd, constant RXD= 1 ; PORTB hardware UART RX, constant TXD= 2 ; PORTB hardware UART TX, constant HVSHDN= 0 ; PORTB HV MAX662A SHDN pin (shutdown) ; ------------------------------------------------------------- ; Variables. constant __DC1= 32 ; delay counter (inner), constant __DC2= 33 ; delay counter (outer), constant BYTE0= 34 ; command from host, constant BYTE1= 35 ; command arg1 constant BYTE2= 36 ; command arg2 ; ------------------------------------------------------------- org 0 ; Hardware init; simple. We set up CTS then branch to idlemode(), ; which branches to cmdloop. banksel TRISA banksel PORTA movlw SPEED call serinit ; set up USART, goto idlemode ; setup hardware and reset, ; Wait for commands from the PC and execute them. cmdloop movlw ACKCHAR ; send the ACK character, call serout ; call serin ; get command, ; Remove the ASCII bias and attempt to dispatch. If it's still ; out of range, it must be a programmer command. addlw -32 ; subtract ASCII " " offset, movwf BYTE0 ; (save because we ruin it) btfss STATUS, C ; (test < 0) goto cmd2 ; branch if out of range addlw -12 ; (ruins cmd offset) btfsc STATUS, C ; (test > 12) goto cmd2 ; branch if out of range ; All PCL/PCLATH branches must be contained in program ; addresses 0 - 0xff... clrf PCLATH ; movf BYTE0, w ; fetch command offset, addwf PCL, f ; ; PIC programming commands. goto typeB ; 0 LOADCONFIG goto cmdloop ; 1 n/a goto typeB ; 2 LOADPMEM goto typeB ; 3 LOADDMEM goto typeC ; 4 READPMEM goto typeC ; 5 READDMEM goto typeA ; 6 INCREMENT goto cmdloop ; 7 n/a goto typeA25 ; 8 PROGRAM goto typeA6 ; 9 BULKPROG goto cmdloop ; 10 n/a goto typeA6 ; 11 BULKDATA ; Not a PIC command; must be a programmer command. cmd2 movf BYTE0, w ; try again, addlw -33 ; subtract remaining ASCII "A" offset, movwf BYTE0 ; (because we ruin it again) btfss STATUS, C goto cmdloop ; branch if out of range addlw -7 ; btfsc STATUS, C goto cmdloop ; branch if out of range clrf PCLATH movf BYTE0, w addwf PCL, f goto version ; A VERSION goto idlemode ; B IDLEMODE goto progmode ; C PROGMODE goto cmdloop ; D NOOP goto cmdloop ; E NOOP goto cmdloop ; F NOOP goto reset ; G RESET ; ================================================================= ; Output version string, terminated with CR LF. version movlw "M" call serout movlw "A" call serout movlw "1" call serout movlw "-" call serout movlw VERS call serout movlw 13 call serout movlw 10 call serout goto cmdloop ; Make the A1 programmer inert by making the interface pins be ; inputs and resetting the target. The target PIC then runs. ; The programmer won't glitch HV on if it's powered off in ; the IDLEMODE state, as the HV outputs are high impedance ; already (same as powerdown state). idlemode ; First, remove +12V from the target's /MCLR, then short VDD. banksel TRISB bcf TRISB, HVCLAMP ; (this is the port initialization) bcf TRISB, SHORTVDD ; make these outputs, banksel PORTB bsf PORTB, HVCLAMP ; clamp /MCLR to ground, bsf PORTB, SHORTVDD ; clamp VDD to ground, ; Wait for the target's VDD to drain and become inert. movlw 5 ; 50 mS, call dly10mS ; let Vdd drain to zero, ; Before we bring the target up, make the programmer interface ; inert by making our pins inputs. This has the side effect of ; leaving the adapter ready for power off. banksel TRISB bsf TRISB, CLOCK ; make these all inputs, bsf TRISB, DATA ; eg. inert, bsf TRISB, RB4CLAMP ; pulled up pins then go high, ; Note that we leave like HVSHDN as input, and rely on the ; external pullups to keep things inert; this is much safer than ; relying on the PIC pin staying high to do the job. If power ; is removed ungracefully from the Adapter ; the pins become high-impedance as ; brownout-detect trips when local Vdd droops. The same is true for ; HVCLAMP; as local Vdd droops, the MAX662A will already be shutdown, ; but at brownout time HVCLAMP will assert also.) bsf TRISB, HVSHDN ; MAX662A goes into shutdown (+5V out) ; With /MCLR still clamped to ground, bring VDD up and wait ; for everything to settle. banksel PORTB bcf PORTB, SHORTVDD ; let Vdd rise, movlw 5 call dly10mS ; let everything restart, ; Now let /MCLR return to VDD, allowing the target to come up. bcf PORTB, HVCLAMP ; let /MCLR rise to Vdd normally, movlw 1 call dly10mS ; goto cmdloop ; Put the A1 programmer into program mode. First we reset ; the target, then power it off by shorting it's Vdd (and not ; taking too long...). The CLOCK, DATA and RB4CLAMP lines are ; asserted idle, Vihh turned on (MAX662A) and allowed to settle, ; power is restored, and the chip taken out of reset. This is ; the manufacturers recommended sequence with generous time ; allowed. ; Note that HVSHDN and HVCLAMP are inputs when the Model A1 ; is idle; we make them outputs only during the programming ; sequence. This adds a safety margin; should the Model A1 ; be turned off while still attached to the target, ; no HV will be applied to the target: as the A1 CPU ; browns out, the pins become inputs, allowing HVSHDN ; and SHORTVDD to float high, and the pullups on the ; transistors will keep the target safe. progmode ; Reset the target (/MCLR low) then turn off it's power. banksel PORTB bsf PORTB, HVCLAMP ; drive /MCLR low, resetting target, movlw 1 call dly100uS ; reset first, bsf PORTB, SHORTVDD ; drive VDD low, power down, movlw 5 call dly10mS ; wait 50mS for power to drain, banksel TRISB ; target is OFF, bcf TRISB, DATA ; make these lines outputs bcf TRISB, CLOCK ; bcf TRISB, RB4CLAMP bcf TRISB, HVSHDN banksel PORTB bcf PORTB, DATA ; set DATA and CLOCK initial states, bcf PORTB, CLOCK ; bcf PORTB, RB4CLAMP ; hold RB4 low, bcf PORTB, HVSHDN ; turn on MAX662A, movlw 5 ; let it settle, call dly100uS banksel PORTB bcf PORTB, HVCLAMP ; let /MCLR rise to +12V nop ; (Vihh) nop ; wait one microsecond, nop nop nop bcf PORTB, SHORTVDD ; un-short Vdd movlw 5 ; let everything settle. call dly10mS goto cmdloop ; Reset the target. This assumes that IDLEMODE has been called ; before to init things. reset: bsf PORTB, HVCLAMP ; drag /MCLR to ground, movlw 10 call dly100uS ; for one mS, bcf PORTB, HVCLAMP ; release it. goto cmdloop ; ================================================================= ; Type A command with 2.5mS execution time. typeA25 call typeAx ; issue the command, movlw 25 ; 10th-mS increments, call dly100uS goto cmdloop ; Type A commands with a 6mS execution time. typeA6 call typeAx movlw 60 call dly100uS goto cmdloop ; Type A commands with no delay. typeA call typeAx goto cmdloop ; Local subroutine: ; ; Output 6 bits of BYTE0 to the target PIC. This brute force ; unwound loop is as fast as a PIC will go. typeAx banksel PORTB bcf PORTB, DATA ; set data 0 btfsc BYTE0, 0 ; or 1 bsf PORTB, DATA bsf PORTB, CLOCK ; assert clock, bcf PORTB, CLOCK ; bcf PORTB, DATA btfsc BYTE0, 1 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE0, 2 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE0, 3 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE0, 4 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE0, 5 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA return ; Fetch two bytes from the host, then output ; the command and two bytes to the target PIC. typeB banksel PORTB ; bsf PORTA, CTS ; tell host we're ready, call serin ; read LS byte, movwf BYTE1 ; call serin ; read MS byte, movwf BYTE2 ; bcf PORTA, CTS ; tell host to wait, ; Output 6 bits of BYTE0, 16 bits of BYTE1:BYTE2 ; to the target PIC. This brute force unwound ; loop is as fast as a PIC will go. bcf PORTB, DATA ; set data 0 btfsc BYTE0, 0 ; or 1 bsf PORTB, DATA bsf PORTB, CLOCK ; assert clock, bcf PORTB, CLOCK ; bcf PORTB, DATA btfsc BYTE0, 1 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE0, 2 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE0, 3 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE0, 4 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE0, 5 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK ; Tdly2; 1 uS. nop nop nop nop ; Output 16 bits in BYTE1:BYTE2 to the target PIC. bcf PORTB, DATA btfsc BYTE1, 0 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE1, 1 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE1, 2 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE1, 3 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE1, 4 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE1, 5 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE1, 6 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE1, 7 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK ; BYTE2 bcf PORTB, DATA btfsc BYTE2, 0 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE2, 1 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE2, 2 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE2, 3 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE2, 4 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE2, 5 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE2, 6 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA btfsc BYTE2, 7 bsf PORTB, DATA bsf PORTB, CLOCK bcf PORTB, CLOCK bcf PORTB, DATA ; pretty on the 'scope. goto cmdloop ; Output the 6-bit command, read 16 bits from the target PIC and ; output them to the host. Another unwound loop. ; ; The target PIC switches it's data input pin (RB7) ; to an output on the 2nd rising clock edge. typeC call typeAx ; issue command first, clrf BYTE1 ; must clear; we only clrf BYTE2 ; BSF if DATA=1 ; The documentation calls this first bit the "start bit", ; and it's definitely ignored in the host software. ; Don't bother actually reading it, just clock it, ; the DATA pin isn't even an input yet. bsf PORTB, CLOCK ; +clock, target asserts data, bcf PORTB, CLOCK ; remove clock, Tdly3 ; btfsc PORTB, DATA ; bsf BYTE1, 0 ; Now make our DATA an input. banksel TRISB bsf TRISB, DATA ; make DATA an input, banksel PORTB ; Now read the remaining 15 bits; the MS bit is ; called the "stop bit" and is also ignored. ; (That's bit7 of BYTE2.) bsf PORTB, CLOCK ; target changes its DATA to be bcf PORTB, CLOCK ; an output here btfsc PORTB, DATA bsf BYTE1, 1 bsf PORTB, CLOCK bcf PORTB, CLOCK btfsc PORTB, DATA bsf BYTE1, 2 bsf PORTB, CLOCK bcf PORTB, CLOCK btfsc PORTB, DATA bsf BYTE1, 3 bsf PORTB, CLOCK bcf PORTB, CLOCK btfsc PORTB, DATA bsf BYTE1, 4 bsf PORTB, CLOCK bcf PORTB, CLOCK btfsc PORTB, DATA bsf BYTE1, 5 bsf PORTB, CLOCK bcf PORTB, CLOCK btfsc PORTB, DATA bsf BYTE1, 6 bsf PORTB, CLOCK bcf PORTB, CLOCK btfsc PORTB, DATA bsf BYTE1, 7 ; BYTE2 bsf PORTB, CLOCK bcf PORTB, CLOCK btfsc PORTB, DATA bsf BYTE2, 0 bsf PORTB, CLOCK bcf PORTB, CLOCK btfsc PORTB, DATA bsf BYTE2, 1 bsf PORTB, CLOCK bcf PORTB, CLOCK btfsc PORTB, DATA bsf BYTE2, 2 bsf PORTB, CLOCK bcf PORTB, CLOCK btfsc PORTB, DATA bsf BYTE2, 3 bsf PORTB, CLOCK bcf PORTB, CLOCK btfsc PORTB, DATA bsf BYTE2, 4 bsf PORTB, CLOCK bcf PORTB, CLOCK btfsc PORTB, DATA bsf BYTE2, 5 bsf PORTB, CLOCK bcf PORTB, CLOCK btfsc PORTB, DATA bsf BYTE2, 6 ; This is the "stop bit" and unused; let's not read it. bsf PORTB, CLOCK bcf PORTB, CLOCK ; btfsc PORTB, DATA ; bsf BYTE2, 7 ; Put our DATA back to being an output. The target will ; be doing the same (making it's DATA an input). banksel TRISB bcf TRISB, DATA ; make DATA an output, banksel PORTB ; Output the two bytes to the host computer. movf BYTE1, w call serout movf BYTE2, w call serout goto cmdloop ; Delay W 10 uS intervals. This was hand calibrated on a ; 20 MHz HS oscillator. It measured precisely 2.5mS at ; W=250; 1.0mS at W=100; 101uS at W=10, and 11.8uS at ; W=1. dly10uS movwf __DC1 ; HAND CALIBRATED dly1 movlw 11 ; HAND CALIBRATED dly2 addlw 255 ; HAND CALIBRATED btfss STATUS, Z ; HAND CALIBRATED goto dly2 ; HAND CALIBRATED nop ; HAND CALIBRATED nop ; HAND CALIBRATED nop ; HAND CALIBRATED decfsz __DC1, f ; HAND CALIBRATED goto dly1 ; HAND CALIBRATED return ; HAND CALIBRATED ; Delay W 100 uS intervals. This was hand calibrated on a ; 20 MHz HS oscillator. It measured precisely 25mS at ; W=250; 10mS at W=100; 1.01mS at W=10; 100uS at W=1. dly100uS ; HAND CALIBRATED movwf __DC1 ; HAND CALIBRATED dly3 movlw 125 ; HAND CALIBRATED dly4 addlw 255 ; HAND CALIBRATED btfss STATUS, Z ; HAND CALIBRATED goto dly4 ; HAND CALIBRATED decfsz __DC1, f ; HAND CALIBRATED goto dly3 ; HAND CALIBRATED return ; HAND CALIBRATED ; Delay W 10mS intervals. dly10mS movwf __DC2 dly5 movlw 100 call dly100uS ; delay 10 mS, etc decfsz __DC2, f goto dly5 return ; Initialize the UART for asynchronous in and out ; and set the bit rate. This sets PB2 (TD) as output, ; PB1 (RD) as input and sets the bit rate to the factor ; W, from table below. ; ; Set bit rate; see PIC16F628a data sheet PDF page 73-75. ; ; 18 432 000 clock rate ; Bit-rate= ----------- ; 16 * (X + 1) 64 if TXSTA.BRGH=0 (low range) ; ; X= 25.042 ; ; HIGH LOW ; speed speed W ; 576000 144000 1 ; 115200 28800 9 ; 76800 19200 14 ; 57600 14400 19 ; 19200 4800 59 ; 9600 2400 119 ; 4800 1200 239 serinit banksel TRISB bsf TRISB, RXD ; 1 RB1 is input, RD bcf TRISB, TXD ; 1 RB2 is output, TD banksel TXSTA bsf TXSTA, BRGH ; 1 set HIGH B.R. range, movwf SPBRG ; 1 set bit rate divisor, bsf TXSTA, TXEN ; 1 Tx enable banksel RCSTA bsf RCSTA, SPEN ; 0 Rx enable, bsf RCSTA, CREN ; 0 continuous Rx, return ; Output W to the UART. serout banksel PIR1 __so1 btfss PIR1, TXIF ; Tx full goto __so1 ; wait... movwf TXREG ; output to transmitter. return ; Wait for a character from the UART. This is a blocking read. serin banksel PIR1 __si1 btfss PIR1, RCIF goto __si1 ; wait for RX buffer, movf RCREG, w ; read it return end