From 09381c6097773b08622c810428a84840b5b08070 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Wed, 6 Jan 2010 17:55:16 +0000 Subject: AVR SP12 programmer --- src/device.c | 434 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 434 insertions(+) create mode 100644 src/device.c (limited to 'src/device.c') diff --git a/src/device.c b/src/device.c new file mode 100644 index 0000000..53fe054 --- /dev/null +++ b/src/device.c @@ -0,0 +1,434 @@ +/* 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); +} + -- cgit v1.2.3