/* SP12: A serial programmer for working with Atmel AVR uCs */ /* Copyright (C) 1997-2008 Ken Huntington, Kevin Towers, */ /* Artur Pundsack, Pitronics. */ /* 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. */ /* This program is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU General Public License for more details. */ /* You should have received a copy of the GNU General Public License */ /* along with this program; if not, write to the Free Software */ /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, */ /* MA 02111-1307, USA. */ /* Pitronics can be reached by email: sbolt@xs4all.nl */ /* Kevin Towers can be reached by email: ktowers@omnexcontrols.com */ /* Ken Huntington can be reached by email: kenh@compmore.net */ /* Artur Pundsack can be reached by email: ap@pa-tec.de */ #include #include #include #include "dos_cpt.h" #include "sp12.h" /* Delay by executing not quite empty loops */ void loopDelay(unsigned long loops) { unsigned long Ti; float dummy; for (Ti = 0; Ti < loops; Ti++) { dummy = rand(); } } /* Assume the device connected to the Centronics port is powered, */ /* but that device sck was not held low during power-up, so a */ /* reset pulse is needed before the SPI can be enabled. */ /* Assume also that power has yet to be switched on (for separate */ /* programmer powered by parallel port, instead of in-circuit */ /* programming. */ /* If uC is 1200, emit just one PROGRAM_ENABLE command. */ /* Else retry until the third byte read back by clockOutCommand() */ /* is 0x53, or at least 32 retries have been unsuccessful. */ int enableSPI(int uC) { unsigned long readBack = 0; int tries = 0; /* * Initialise programming lines. */ bit_clear(outbyte, ENABLE | SCK | MOSI | RESET); outportb(portAddress, outbyte); /* * Delay to make sure capacitor on separate programmer is charged. */ delay(timeConst.powerOn); /* * If the resetPuls time constant > 0, then Hi pulse device reset */ if (timeConst.resetPuls > 0) { bit_set(outbyte, RESET); outportb(portAddress, outbyte); /* * Delay to make sure that a capacitor * from reset to gnd is (dis)charged */ loopDelay(timeConst.resetPuls); bit_clear(outbyte, RESET); outportb(portAddress, outbyte); /* * Delay before program (SPI) enable command */ delay(timeConst.programEnable); } /* * Command program enable and check synchronization */ while (tries < 33) { tries++; readBack = clockOutCommand(PROGRAM_ENABLE); readBack = (readBack & 0x0000FF00L) >> 8; if ((readBack == 0x53) || (uC == 1200)) { break; } else { /* * Pulse SCK lo-hi-lo */ bit_clear(outbyte, SCK); outportb(portAddress, outbyte); loopDelay(timeConst.sckLO); bit_set(outbyte, SCK); outportb(portAddress, outbyte); loopDelay(timeConst.sckHI); bit_clear(outbyte, SCK); outportb(portAddress, outbyte); loopDelay(timeConst.sckLO); } } if (tries > 1) printf("Sp12 tried %d times to find a working device.\n", tries); if ((readBack != 0x53) && (uC != 1200)) printf("No device connected.\n"); /* printf("readBack: %#lx, tries: %d\n", readBack, tries); */ return(tries); } /* Adapt Sck timing to user's clock speed setting */ /* (Init sets the timing equal to sck0.5M, and the flag will be */ /* zero unless altered by the command line.) */ float setSckTiming(float clkSpeed) { float signal; clkSpeed = (float) 2 / (clkSpeed * 1e6); if (clkSpeed > timeConst.port) timeConst.sckLO = (unsigned long) (timeConst.loop * (clkSpeed - timeConst.port)); else timeConst.sckLO = 1; if (strcmp(device.name, "AT90S1200(A)") == 0 && (clkSpeed * 2) > timeConst.port) timeConst.sckHI = (unsigned long) (timeConst.loop * (clkSpeed * 2 - timeConst.port)); else timeConst.sckHI = timeConst.sckLO; // printf("sckLO %ld, sckHI %ld\n", timeConst.sckLO, timeConst.sckHI); signal = 1.8e-6 / clkSpeed; if (strcmp(device.name, "AT90S1200(A)") == 0) { if ((signal / 2) > (1.8e-6 / timeConst.port)) signal = 4.0e-6 / timeConst.port; } else { if (signal > (1.8e-6 / timeConst.port)) signal = 2.0e-6 / timeConst.port; } return(signal); } /* Before writing to the flash area, the device has to be erased. */ /* Note: This command also erases the eeprom area (all to 0xFF). */ /* There is no need for general erase before writing to the */ /* eeprom area, since the device has an auto-erase built into */ /* the eeprom write cycle. Thus you can alter one or more eeprom */ /* addresses without disturbing the rest of the device. */ void eraseDevice(void) { clockOutCommand(CHIP_ERASE); /* * Delay before program (SPI) enable sequence */ delay(timeConst.chipErase); /* * syncronisation must be ok, so just one PROGRAM_ENABLE * command will be clocked out. */ enableSPI(1200); } /* A routine to switch control the optional test bits on the */ /* parallel port. */ void portControl(int portFlag, int exitFlag) { if (exitFlag) { outbyte = (unsigned char) portFlag; } else { outbyte = (unsigned char) portFlag & PORTACTIVE; delay(10); } outportb(portAddress, outbyte); } /* Reads flash and eeprom area's. Return 1 if a flash address does */ /* not contain 0xFFFF, or 3 if an eeprom address also isn't blank; */ /* return 2 if the flash is blank, but the eeprom isn't; */ /* return 0 is both area's are blank. */ /* Power bits prior state assumed on; left on at return. */ /* SCK prior state assumed lo; left lo at return. */ /* AP, 15.01.2008: - Set the extended adress byte if necessary */ int blankCheck(long flashLimit, long eepromLimit) { long address, writeCmd; int signal = 0; if (flashLimit > 0x0000FFFF){ writeCmd = LOAD_EXT_ADDR; clockOutCommand(writeCmd); } for (address = 0; address < flashLimit; address++) { if (readCodeWord(address) != 0xffff) { signal |= 0x01; break; /* arrea is not blank; exit loop */ } } for (address = 0; address < eepromLimit; address++) { if (readDataByte(address) != 0xff) { signal |= 0x02; break; /* arrea is not blank; exit loop */ } } return(signal); } /* Sends a lock or fuses string to the microcontroller. */ /* Returns 9 if the writeCommand is zero (not available according */ /* to _sp12dev). */ /* The lock or fuses string may represent a binary or hex number. */ /* If it's binary, the length of the string is compared with */ /* W_FUSESLEN; a useful sanity check. The function returns 2 if */ /* the length is wrong. A hex number is not checked, as there is no */ /* way to know how many leading zeroes are intended. */ /* Next, the block is ored into the writeCommand and uploaded to */ /* the device. */ /* Since (re)enabling brown-out detection usually causes the */ /* AT90(L)S2333/4433 to hang, the fuses are only written, not */ /* verified. */ int writeFuses(unsigned long commandHM, unsigned long commandLM, \ char *fuses, int WfusesLsb, int WfusesLen) { unsigned long fuseWord = 0; int Bi; if (!commandHM) return(9); if (fuses[1] == 'x') fuseWord = strtoul(fuses, NULL, 16); else if (strlen(fuses) != WfusesLen) return(2); else fuseWord = strtoul(fuses, NULL, 2); fuseWord = fuseWord << WfusesLsb; fuseWord = fuseWord & commandHM; fuseWord = fuseWord | commandLM; // printf("\nfuseword %04lx\n", fuseWord); clockOutCommand(fuseWord); /* * Read back the fuses as a binary string */ fuseWord = fuseWord >> WfusesLsb; for (Bi = WfusesLen - 1; Bi >= 0; Bi--) { fuses[Bi] = (fuseWord & 0x01L) + 48; fuseWord = fuseWord >> 1; } fuses[WfusesLen] = '\0'; /* * ByteWrite delay not long enough to allow reading back * of true ATmega103/603 fuses directly after this */ delay(timeConst.chipErase); return(0); } /* Reads the lock or fuse bit block from the microcontroller. */ /* Returns 9 if the readCommand is zero (not available according */ /* to _sp12dev). */ int readFuses(unsigned long readCommand, char *fuses, \ int RfusesLsb, int RfusesLen) { unsigned long fuseWord = 0; int Bi; if (!readCommand) return(9); fuseWord = clockOutCommand(readCommand); fuseWord = fuseWord >> RfusesLsb; for (Bi = RfusesLen - 1; Bi >= 0; Bi--) { fuses[Bi] = (fuseWord & 0x01L) + 48; fuseWord = fuseWord >> 1; } fuses[RfusesLen] = '\0'; return(0); } /* Read the signature bits from the device attached to the */ /* Centronics parallel port. */ void readDeviceCode(void) { unsigned long readCmd; readCmd = READ_DEVICE | 0x0200L; device.sigByte_2 = (unsigned char) clockOutCommand(readCmd); readCmd = READ_DEVICE | 0x0100L; device.sigByte_1 = (unsigned char) clockOutCommand(readCmd); readCmd = READ_DEVICE | 0x0000L; device.sigByte_0 = (unsigned char) clockOutCommand(readCmd); } /* Shifts the command highest bit first into unsigned char output, */ /* which then is used to clock them out bit by bit through */ /* Centronics data-7. */ /* Reads back and returns whatever the uC clocks out on Miso */ /* while the command is clocked in. */ /* Power bits prior state assumed on; left on at return. */ /* SCK prior state assumed lo; left lo at return. */ unsigned long clockOutCommand(unsigned long command) { unsigned long inBuf; unsigned long readBack = 0x0L; unsigned char inbyte; /* from Centronics status reg. */ int mostSigBit; int i; mostSigBit = 31; /* * Bits mostSigBit-0 to be clocked out */ for (i = mostSigBit; i >= 0; i--) { /* * Bits mostSigBit-0 to be clocked out */ if ((command >> i) & 0x01L) bit_set(outbyte, MOSI); else bit_clear(outbyte, MOSI); bit_clear(outbyte, SCK); outportb(portAddress, outbyte); /* * Minimum delay SCK lo period */ loopDelay(timeConst.sckLO); /* * Set bit-0 (SCK) hi, leave all other bits as they are */ bit_set(outbyte, SCK); outportb(portAddress, outbyte); /* * Minimum delay SCK hi period */ loopDelay(timeConst.sckHI); /* * Fetch parallel port status register, clear all bits * except 7 (MOSI), invert, shift and or into buffer */ inbyte = inportb(portStatus); inBuf = ((unsigned long) (inbyte ^ MISO_INV)) >> MISO_BITNR; inBuf = (inBuf & 0x00000001L) << i; readBack = readBack | inBuf; } /* * return SCK to lo */ bit_clear(outbyte, SCK); outportb(portAddress, outbyte); return(readBack); } /* Accepts a dataByte and a 16-bit address. Writes the byte into */ /* the device connected to the Centronics port and verifies arrival. */ /* Returns 1 if verify fails three times, else 0. */ /* The time constant byteWrite is adjusted dynamically, */ /* if the device is not a 1200(A) and/or optimizeFlag allows. */ /* Power bits prior state assumed on; left on at return. */ /* SCK prior state assumed lo; left lo at return. */ int writeByteVerified(unsigned long writeCommand, unsigned long readCommand, int optimizeFlag) { int idx; unsigned char readBack; int failure = 0; unsigned char dataByte; /* * First check if the data isn't already in the uC */ dataByte = (unsigned char) writeCommand; readBack = (unsigned char) clockOutCommand(readCommand); if (readBack == dataByte) return(failure); /* * Else write the data into the uC */ dataByte = (unsigned char) writeCommand; clockOutCommand(writeCommand); switch (dataByte) { case 0xFF: case 0x7F: case 0x80: case 0x00: loopDelay(timeConst.byteWriteDefault); readBack = (unsigned char) clockOutCommand(readCommand); break; default: loopDelay(timeConst.byteWrite); readBack = (unsigned char) clockOutCommand(readCommand); if ((strcmp(device.name, "AT90S1200(A)") != 0 && optimizeFlag != 10) || optimizeFlag == 11) { if (readBack != dataByte) { timeConst.byteWrite += timeConst.byteWriteDefault / 12; timeConst.byteWriteAdjusted = 1; loopDelay(timeConst.byteWrite); readBack = (unsigned char) clockOutCommand(readCommand); } else { if (timeConst.byteWriteAdjusted == 0) timeConst.byteWrite -= timeConst.byteWriteDefault / 12; } } } idx = 1; if (readBack != dataByte) { timeConst.byteWrite = timeConst.byteWriteDefault; timeConst.byteWriteAdjusted = 0; do { clockOutCommand(writeCommand); loopDelay(timeConst.byteWrite); readBack = (unsigned char) clockOutCommand(readCommand); } while (readBack != dataByte && ++idx < 4); writeRetries += idx - 1; if (readBack != dataByte) failure = 1; } return(failure); }