package lejos.pc.comm; import java.io.IOException; import java.io.InputStream; /** * Implements a sub-set of the Atmel SAM-BA download protocol. Only those * functions required for program download to the NXT flash are currently * implemented. * */ public class NXTSamba { private class MemoryInputStream extends InputStream { private int len; private byte[] buf; private int off; public MemoryInputStream(int len) { this.len = len; } private boolean fillBuffer() throws IOException { if (buf == null || off >= buf.length) { if (len <= 0) return false; this.buf = NXTSamba.this.read(); this.off = 0; if (this.buf.length > this.len) throw new IOException("protocol error"); this.len -= this.buf.length; } return true; } @Override public int read() throws IOException { if (!this.fillBuffer()) return -1; return this.buf[this.off++] & 0xFF; } @Override public void close() throws IOException { //consume all the bytes while (this.fillBuffer()) this.buf = null; } } private static final String CHARSET = "iso-8859-1"; private static final char CMD_GOTO = 'G'; private static final char CMD_TEXT = 'T'; //SAM-BA sends ">" prompt private static final char CMD_NON_TEXT = 'N'; //SAM-BA sends no prompt or newline private static final char CMD_VERSION = 'V'; private static final char CMD_READ_OCTET = 'o'; private static final char CMD_READ_HWORD = 'h'; private static final char CMD_READ_WORD = 'w'; private static final char CMD_READ_STREAM = 'R'; private static final char CMD_WRITE_OCTET = 'O'; private static final char CMD_WRITE_HWORD = 'H'; private static final char CMD_WRITE_WORD = 'W'; private static final char CMD_WRITE_STREAM = 'S'; private static final byte PROMPT_CHAR = '>'; /** * The NXT has 64KB RAM starting at 0x200000. */ private static final int RAM_BASE = 0x00200000; private static final int RAM_MAX = 0x00210000; /** * The NXT has 256KB Flash starting at 0x100000, divided into 256byte pages. */ public static final int FLASH_BASE = 0x00100000; public static final int FLASH_MAX = 0x00140000; public static final int PAGE_SIZE = 256; /** * According to the SAM7S datasheet, section 21.6 Hardware and Software Constraints, * the area 0x202000-0x210000 is unused RAM. */ private static final int SAMBA_RAM_BASE = RAM_BASE + 0x2000; private static final int SAMBA_RAM_MAX = RAM_MAX; private static final int HELPER_STACKSIZE = 0x1000; private static final int HELPER_CODEADR = SAMBA_RAM_BASE + HELPER_STACKSIZE; private static final int HELPER_DATAADR = HELPER_CODEADR + FlashWrite.CODE.length; private static final int HELPER_PACKET = PAGE_SIZE + 4; static { assert SAMBA_RAM_BASE >= RAM_BASE; assert SAMBA_RAM_MAX <= RAM_MAX; assert HELPER_CODEADR - HELPER_STACKSIZE >= SAMBA_RAM_BASE; assert HELPER_DATAADR + HELPER_PACKET <= SAMBA_RAM_MAX; } private NXTCommUSB nxtComm = null; private String version; /** * Locate all NXT devices that are running in SAM-BA mode. * @return An array of devices in SAM-BA mode * @throws lejos.pc.comm.NXTCommException */ public NXTInfo[] search() throws NXTCommException { NXTInfo[] nxtInfos; if (nxtComm == null) { try { nxtComm = (NXTCommUSB) NXTCommFactory.createNXTComm(NXTCommFactory.USB); } catch (NXTCommException e) { } if (nxtComm == null) { throw new NXTCommException("Cannot load a comm driver"); } } // Look for a USB one first nxtInfos = nxtComm.search("%%NXT-SAMBA%%", NXTCommFactory.USB); if (nxtInfos.length > 0) { return nxtInfos; } return new NXTInfo[0]; } /** * Helper function perform a read with timeout. * @return Bytes read from the device. * @throws java.io.IOException */ private byte[] read() throws IOException { byte [] ret = nxtComm.read(false); if (ret == null || ret.length == 0) throw new IOException("Read timeout"); // System.out.println("Debug: "+new String(ret, CHARSET)); return ret; } private int readAnswerWord(int len) throws IOException { byte [] ret = read(); if (ret.length != len) throw new IOException("bad packet length"); int r = 0; for (int i=len; i>0;) { r <<= 8; r |= ret[--i] & 0xFF; } return r; } private void readAnswerStream(byte[] data, int off, int len) throws IOException { while (len > 0) { byte [] ret = read(); int rlen = ret.length; if (rlen > len) throw new IOException("bad packet length"); System.arraycopy(ret, 0, data, off, rlen); off += rlen; len -= rlen; } } private static boolean endsWithLinefeed(byte[] ret) { int len = ret.length; return len >= 2 && (ret[len-2] == (byte)'\n' || ret[len-1] == (byte)'\r'); } private static boolean endsWithPrompt(byte[] ret) { int len = ret.length; return len >= 1 && ret[len - 1] == PROMPT_CHAR; } private String readLine() throws IOException { StringBuilder sb = new StringBuilder(); while (true) { byte[] ret = read(); sb.append(new String(ret, CHARSET)); if (endsWithLinefeed(ret)) return sb.toString(); } } /** * Helper function perform a write with timeout. * @param data Data to be written to the device. * @throws java.io.IOException */ private void write(byte[] data) throws IOException { if (nxtComm.write(data, true) != data.length) throw new IOException("Write timeout"); } /** * Helper function, send a string to the device. Convert from Unicode to * ASCII and send the string. * @param str String to be sent. * @throws java.io.IOException */ private void writeString(String str) throws IOException { // System.out.println("Debug: "+str); write(str.getBytes(CHARSET)); } private void sendInitCommand(char cmd) throws IOException { String command = cmd + "#"; writeString(command); } private void sendGotoCommand(int addr) throws IOException { String command = CMD_GOTO + hexFormat(addr, 8) + "#"; writeString(command); } private void sendStreamCommand(char cmd, int addr, int len) throws IOException { String command = cmd + hexFormat(addr, 8) + "," + hexFormat(len, 8) + "#"; writeString(command); } private void sendWriteCommand(char cmd, int addr, int len, int value) throws IOException { String command = cmd + hexFormat(addr, 8) + "," + hexFormat(value, 2 * len) + "#"; writeString(command); } private void sendReadCommand(char cmd, int addr, int len) throws IOException { String command = cmd + hexFormat(addr, 8) + "," + len + "#"; writeString(command); } /** * Generated <b>exactly</b> as many hex digits as specified. */ private static String hexFormat(int value, int len) { char[] buf = new char[len]; for (int i=0; i<len; i++) { int shift = 4 * (len - i - 1); int c = (value >>> shift) & 0x0F; if (c < 10) c += '0'; else c += 'A' - 10; buf[i] = (char)c; } return String.valueOf(buf); } /** * Write a 8 bit octet to the specified address. * @param addr * @param val * @throws java.io.IOException */ public void writeOctet(int addr, int val) throws IOException { sendWriteCommand(CMD_WRITE_OCTET, addr, 1, val); } /** * Write a 16 bit halfword to the specified address. * @param addr * @param val * @throws java.io.IOException */ public void writeHalfword(int addr, int val) throws IOException { sendWriteCommand(CMD_WRITE_HWORD, addr, 2, val); } /** * Write a 32 bit word to the specified address. * @param addr * @param val * @throws java.io.IOException */ public void writeWord(int addr, int val) throws IOException { sendWriteCommand(CMD_WRITE_WORD, addr, 4, val); } /** * Read a 8 bit octet from the specified address. * @param addr * @return value read from addr * @throws java.io.IOException */ public int readOctet(int addr) throws IOException { sendReadCommand(CMD_READ_OCTET, addr, 1); return readAnswerWord(1); } /** * Read a 16 bit halfword from the specified address. * @param addr * @return value read from addr * @throws java.io.IOException */ public int readHalfword(int addr) throws IOException { sendReadCommand(CMD_READ_HWORD, addr, 2); return readAnswerWord(2); } /** * Read a 32 bit word from the specified address. * @param addr * @return value read from addr * @throws java.io.IOException */ public int readWord(int addr) throws IOException { sendReadCommand(CMD_READ_WORD, addr, 4); return readAnswerWord(4); } public InputStream createInputStream(int addr, int len) throws IOException { sendStreamCommand(CMD_READ_STREAM, addr, len); return new MemoryInputStream(len); } /** * Read a 32 bit word from the specified address. * @param addr * @param data the return data * @param off the offset * @param len the length * @throws java.io.IOException */ public void readBytes(int addr, byte[] data, int off, int len) throws IOException { sendStreamCommand(CMD_READ_STREAM, addr, len); readAnswerStream(data, off, len); } /** * Write a series of bytes to the device. * @param addr * @param data * @throws java.io.IOException */ public void writeBytes(int addr, byte[] data) throws IOException { sendStreamCommand(CMD_WRITE_STREAM, addr, data.length); write(data); } /** * Start execution of code at the specified address. * @param addr * @throws java.io.IOException */ public void jump(int addr) throws IOException { sendGotoCommand(addr); } public void reboot() throws IOException { sendGotoCommand(FLASH_BASE); } /** * Wait for the flash controller to be ready to accept commands. * @throws java.io.IOException */ private void waitReady() throws IOException { while ((readWord(0xffffff68) & 0x1) == 0) Thread.yield(); } /** * Change the lock bits for a region of flash memory. * @param rgn * @param lock * @throws java.io.IOException */ private void changeLock(int rgn, boolean lock) throws IOException { int cmd = 0x5a000000 | ((64*rgn) << 8); if (lock) cmd |= 0x2; else cmd |= 0x4; waitReady(); writeWord(0xffffff60, 0x00050100); writeWord(0xffffff64, cmd); writeWord(0xffffff60, 0x00340100); } /** * Turn off the lock bits for all of flash memory. * @throws java.io.IOException */ public void unlockAllPages() throws IOException { for(int i = 0; i < 16; i++) changeLock(i, false); } /** * Write a single page to flash memory. We write the page to ram and then * use the FlashWriter code to transfer this data to flash. The FlashWriter * code must have already been downloaded. * @param page * @param data * @param offset * @throws java.io.IOException */ public void writePage(int page, byte[] data, int offset) throws IOException { this.writePage(page, data, offset, data.length - offset); } /** * Write a single page to flash memory. We write the page to ram and then * use the FlashWriter code to transfer this data to flash. The FlashWriter * code must have already been downloaded. * @param page * @param data * @param offset * @param len * @throws java.io.IOException */ public void writePage(int page, byte[] data, int offset, int len) throws IOException { if (len > PAGE_SIZE) len = PAGE_SIZE; // Generate data chunk (32 bit int pagenum + 256 byte data) byte [] buf = new byte[HELPER_PACKET]; System.arraycopy(data, offset, buf, 4, len); encodeInt(buf, 0, page); // And the data into ram writeBytes(HELPER_DATAADR, buf); // And now use the flash writer to write the data into flash. sendGotoCommand(HELPER_CODEADR); } /** * Write a series of pages to flash memory. * @param first * @param data * @param start * @param len * @throws java.io.IOException */ public void writePages(int first, byte[] data, int start, int len) throws IOException { while (len > 0) { writePage(first, data, start, len); start += PAGE_SIZE; len -= PAGE_SIZE; first++; } } /** * Read a single page from flash memory. * @param page * @param data * @param offset * @throws java.io.IOException */ public void readPage(int page, byte[] data, int offset) throws IOException { //System.out.println("Write page " + page); int addr = FLASH_BASE + page * PAGE_SIZE; readBytes(addr, data, offset, PAGE_SIZE); } /** * Read a series of pages from flash memory. * @param first * @param data * @param start * @param len * @throws java.io.IOException */ public void readPages(int first, byte[] data, int start, int len) throws IOException { int offset = start; int page = first; while (offset < start + len) { readPage(page, data, offset); page++; offset += PAGE_SIZE; } } /** * Open the specified USB device and check that it is in SAM-BA mode. We * switch the device into "quiet" mode and also download the FlashWrite * program. * @param nxt Device to open. * @return true if the device is now open, false otherwise. * @throws java.io.IOException */ public boolean open(NXTInfo nxt) throws IOException { if (nxtComm.open(nxt, NXTComm.RAW)) { try { // We first set the device to interactive/text mode verbose mode // (which is the default) and wait for the prompt. This is safe no matter // whether the device originally is in interactive or quiet mode. // Then we switch back to quiet mode and ask for the version string. // This matches the behavior of the original SAM-BA software // Switch into quiet mode, NXT may answer with line-feed if in verbose mode sendInitCommand(CMD_TEXT); while (!endsWithPrompt(read())) { /* wait for prompt */ } // Switch into quiet mode, NXT may answer with line-feed if in verbose mode sendInitCommand(CMD_NON_TEXT); readLine(); //discard response // Ask for version number, terminated by line-feed sendInitCommand(CMD_VERSION); // Example version string: "v1.4 Nov 10 2004 14:49:33" version = readLine().trim(); // strip everything after the first whitespace version = version.replaceAll("\\s.*", ""); // Check that we are all in sync System.out.println("Connected to SAM-BA " + version); // Now upload the flash writer helper routine writeBytes(HELPER_CODEADR, getModifiedHelper()); // And set the the clock into PLL/2 mode ready for writing writeWord(0xfffffc30, 0x7); return true; } catch (IOException e) { // Some sort of error } // Unable to sync things make sure the device is closed. nxtComm.close(); } return false; } private static byte[] getModifiedHelper() { final byte[] code = FlashWrite.CODE; final int len = code.length; byte[] r = new byte[len]; System.arraycopy(code, 0, r, 0, len); //encodeMagicInt(code, len + PAGEDATA_OFF, PAGEDATA_MAGIC, ADDR_PAGEDATA); return r; } private static void encodeInt(byte[] code, int off, int value) { for (int i = 0; i < 4; i++) { code[off + i] = (byte)value; value >>>= 8; } } /** * Close the device. */ public void close() { nxtComm.close(); } /** * returns the SAM-BA version string for the current device. * @return The SAM-BA version. * @throws java.io.IOException */ public String getVersion() throws IOException { return version; } }