From 295974618bbfa756350f42efdd841ea0a870fbef Mon Sep 17 00:00:00 2001 From: Marco Maccaferri Date: Wed, 26 Dec 2018 08:31:44 +0100 Subject: [PATCH] Implemented binary upload protocols --- src/com/maccasoft/tools/Application.java | 291 ++++++++++++-- src/com/maccasoft/tools/Preferences.java | 33 +- .../maccasoft/tools/PreferencesDialog.java | 16 +- src/com/maccasoft/tools/SerialTerminal.java | 361 +++++++++++++++--- src/com/maccasoft/tools/StatusLine.java | 8 +- 5 files changed, 620 insertions(+), 89 deletions(-) diff --git a/src/com/maccasoft/tools/Application.java b/src/com/maccasoft/tools/Application.java index 6757fde..2b4999f 100644 --- a/src/com/maccasoft/tools/Application.java +++ b/src/com/maccasoft/tools/Application.java @@ -797,6 +797,36 @@ public class Application { new MenuItem(menu, SWT.SEPARATOR); + item = new MenuItem(menu, SWT.PUSH); + item.setText("Upload Packed CP/M binary"); + item.addListener(SWT.Selection, new Listener() { + + @Override + public void handleEvent(Event e) { + try { + handleCompileAndUploadBinary(); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + }); + + item = new MenuItem(menu, SWT.PUSH); + item.setText("Upload XModem binary"); + item.addListener(SWT.Selection, new Listener() { + + @Override + public void handleEvent(Event e) { + try { + handleCompileAndUploadXModem(); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + }); + + new MenuItem(menu, SWT.SEPARATOR); + item = new MenuItem(menu, SWT.PUSH); item.setText("Serial Terminal\tCtrl+Shift+T"); item.setAccelerator(SWT.MOD1 + SWT.MOD2 + 'T'); @@ -850,20 +880,7 @@ public class Application { } }); - toolItem = new ToolItem(toolBar, SWT.PUSH); - toolItem.setImage(ImageRegistry.getImageFromResources("application_go.png")); - toolItem.setToolTipText("Upload Intel HEX"); - toolItem.addSelectionListener(new SelectionAdapter() { - - @Override - public void widgetSelected(SelectionEvent e) { - try { - handleCompileAndUploadIntelHex(); - } catch (Exception e1) { - e1.printStackTrace(); - } - } - }); + createUploadToolItem(toolBar); new ToolItem(toolBar, SWT.SEPARATOR); @@ -932,6 +949,98 @@ public class Application { return toolBar; } + void createUploadToolItem(ToolBar toolBar) { + final ToolItem toolItem = new ToolItem(toolBar, SWT.DROP_DOWN); + toolItem.setImage(ImageRegistry.getImageFromResources("application_go.png")); + + final Menu menu = new Menu(shell, SWT.POP_UP); + + MenuItem menuItem = new MenuItem(menu, SWT.RADIO); + menuItem.setText("Upload Intel HEX"); + menuItem.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + try { + if (((MenuItem) e.widget).getSelection()) { + toolItem.setToolTipText(((MenuItem) e.widget).getText()); + preferences.setLastUploadType(menu.indexOf((MenuItem) e.widget)); + handleCompileAndUploadIntelHex(); + } + } catch (Exception e1) { + e1.printStackTrace(); + } + } + }); + + menuItem = new MenuItem(menu, SWT.RADIO); + menuItem.setText("Upload Packed CP/M binary"); + menuItem.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + try { + if (((MenuItem) e.widget).getSelection()) { + toolItem.setToolTipText(((MenuItem) e.widget).getText()); + preferences.setLastUploadType(menu.indexOf((MenuItem) e.widget)); + handleCompileAndUploadBinary(); + } + } catch (Exception e1) { + e1.printStackTrace(); + } + } + }); + + menuItem = new MenuItem(menu, SWT.RADIO); + menuItem.setText("Upload XModem binary"); + menuItem.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + try { + if (((MenuItem) e.widget).getSelection()) { + toolItem.setToolTipText(((MenuItem) e.widget).getText()); + preferences.setLastUploadType(menu.indexOf((MenuItem) e.widget)); + handleCompileAndUploadXModem(); + } + } catch (Exception e1) { + e1.printStackTrace(); + } + } + }); + + int selection = preferences.getLastUploadType(); + if (selection < 0 || selection >= menu.getItemCount()) { + selection = 0; + } + menu.getItem(selection).setSelection(true); + + toolItem.setToolTipText(menu.getItem(selection).getText()); + + toolItem.addListener(SWT.Selection, new Listener() { + + @Override + public void handleEvent(Event event) { + if (event.detail == SWT.ARROW) { + Rectangle rect = toolItem.getBounds(); + Point pt = new Point(rect.x, rect.y + rect.height); + pt = toolBar.toDisplay(pt); + menu.setLocation(pt.x, pt.y); + menu.setVisible(true); + } + else { + MenuItem[] menuItem = menu.getItems(); + for (int i = 0; i < menuItem.length; i++) { + if (menuItem[i].getSelection()) { + menuItem[i].notifyListeners(SWT.Selection, new Event()); + break; + } + } + } + } + }); + } + protected Control createContents(Composite parent) { GC gc = new GC(parent); FontMetrics fontMetrics = gc.getFontMetrics(); @@ -1605,16 +1714,6 @@ public class Application { private void handleOpenTerminal() { if (terminal == null) { terminal = new SerialTerminal(); - terminal.setPort(preferences.getSerialPort()); - terminal.setBaud(preferences.getSerialBaud()); - terminal.addPropertyChangeListener(new PropertyChangeListener() { - - @Override - public void propertyChange(PropertyChangeEvent evt) { - preferences.setSerialPort(terminal.getPort()); - preferences.setSerialBaud(terminal.getBaud()); - } - }); terminal.open(); terminal.getShell().addDisposeListener(new DisposeListener() { @@ -1661,7 +1760,7 @@ public class Application { return; } - display.asyncExec(new Runnable() { + display.syncExec(new Runnable() { @Override public void run() { @@ -1718,6 +1817,148 @@ public class Application { } } + private void handleCompileAndUploadBinary() throws Exception { + CTabItem tabItem = tabFolder.getSelection(); + if (tabItem == null) { + return; + } + SourceEditorTab tab = (SourceEditorTab) tabItem.getData(); + + console.clear(); + + final List includePaths = new ArrayList(); + if (tab.getFile() != null) { + includePaths.add(tab.getFile().getParentFile()); + } + + final StringReader reader = new StringReader(tab.getEditor().getText()); + + final String name = tab.getText(); + final File file = tab.getFile(); + + new Thread(new Runnable() { + + PrintStream out; + IProgressMonitor monitor; + SerialPort serialPort; + + @Override + public void run() { + out = new PrintStream(console.getOutputStream()); + + monitor = statusLine.getProgressMonitor(); + monitor.beginTask("Compile", IProgressMonitor.UNKNOWN); + + try { + String baseName = name; + if (baseName.indexOf('.') != -1) { + baseName = baseName.substring(0, baseName.lastIndexOf('.')); + } + + Source source = compile(reader, name, file, includePaths); + if (source == null) { + return; + } + + display.syncExec(new Runnable() { + + @Override + public void run() { + handleOpenTerminal(); + } + }); + + serialPort = terminal.getSerialPort(); + byte[] data = source.generateObjectCode(); + + monitor.beginTask("Upload", (data.length + 127) / 128); + out.println("Sending to serial port " + serialPort.getPortName() + " ..."); + + terminal.uploadPackedBinary(baseName + ".COM", data, monitor); + + } catch (Exception e) { + e.printStackTrace(); + } + + out.println("Done"); + monitor.done(); + } + + }).start(); + + } + + private void handleCompileAndUploadXModem() throws Exception { + CTabItem tabItem = tabFolder.getSelection(); + if (tabItem == null) { + return; + } + SourceEditorTab tab = (SourceEditorTab) tabItem.getData(); + + console.clear(); + + final List includePaths = new ArrayList(); + if (tab.getFile() != null) { + includePaths.add(tab.getFile().getParentFile()); + } + + final StringReader reader = new StringReader(tab.getEditor().getText()); + + final String name = tab.getText(); + final File file = tab.getFile(); + + new Thread(new Runnable() { + + PrintStream out; + IProgressMonitor monitor; + SerialPort serialPort; + + @Override + public void run() { + out = new PrintStream(console.getOutputStream()); + + monitor = statusLine.getProgressMonitor(); + monitor.beginTask("Compile", IProgressMonitor.UNKNOWN); + + try { + String baseName = name; + if (baseName.indexOf('.') != -1) { + baseName = baseName.substring(0, baseName.lastIndexOf('.')); + } + + Source source = compile(reader, name, file, includePaths); + if (source == null) { + return; + } + + display.syncExec(new Runnable() { + + @Override + public void run() { + handleOpenTerminal(); + } + }); + + serialPort = terminal.getSerialPort(); + byte[] data = source.generateObjectCode(); + + monitor.beginTask("Upload", (data.length + 127) / 128); + out.println("Sending to serial port " + serialPort.getPortName() + " ..."); + + terminal.uploadXModem(baseName + ".COM", data, monitor); + + } catch (Exception e) { + e.printStackTrace(); + } + + out.println("Done"); + monitor.done(); + } + + }).start(); + + } + static { System.setProperty("SWT_GTK3", "0"); } diff --git a/src/com/maccasoft/tools/Preferences.java b/src/com/maccasoft/tools/Preferences.java index 970b161..02d4da7 100644 --- a/src/com/maccasoft/tools/Preferences.java +++ b/src/com/maccasoft/tools/Preferences.java @@ -79,9 +79,13 @@ public class Preferences { boolean generateHex; boolean generateListing; - String lastPath; String serialPort; int serialBaud; + String downloadCommand; + String xmodemCommand; + + int lastUploadType; + String lastPath; List lru; final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this); @@ -101,6 +105,9 @@ public class Preferences { generateListing = true; serialBaud = 115200; + downloadCommand = "A:DOWNLOAD {0}"; + xmodemCommand = "A:XMODEM {0} /R /X0 /Q"; + lru = new ArrayList(); } @@ -299,6 +306,30 @@ public class Preferences { this.serialBaud = serialBaud; } + public String getDownloadCommand() { + return downloadCommand; + } + + public void setDownloadCommand(String downloadCommand) { + this.downloadCommand = downloadCommand; + } + + public String getXmodemCommand() { + return xmodemCommand; + } + + public void setXmodemCommand(String xmodemCommand) { + this.xmodemCommand = xmodemCommand; + } + + public int getLastUploadType() { + return lastUploadType; + } + + public void setLastUploadType(int lastUploadType) { + this.lastUploadType = lastUploadType; + } + public void save() throws IOException { ObjectMapper mapper = new ObjectMapper(); mapper.configure(SerializationFeature.INDENT_OUTPUT, true); diff --git a/src/com/maccasoft/tools/PreferencesDialog.java b/src/com/maccasoft/tools/PreferencesDialog.java index b552342..993190a 100644 --- a/src/com/maccasoft/tools/PreferencesDialog.java +++ b/src/com/maccasoft/tools/PreferencesDialog.java @@ -58,6 +58,8 @@ public class PreferencesDialog extends Dialog { Button generateBinary; Button generateHex; Button generateListing; + Text downloadCommand; + Text xmodemCommand; Preferences preferences; String defaultFont; @@ -160,7 +162,17 @@ public class PreferencesDialog extends Dialog { createCompilerGroup(composite); - addSeparator(composite); + label = new Label(composite, SWT.NONE); + label.setText("Download cmd."); + downloadCommand = new Text(composite, SWT.BORDER); + downloadCommand.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + downloadCommand.setText(preferences.getDownloadCommand()); + + label = new Label(composite, SWT.NONE); + label.setText("XModem cmd."); + xmodemCommand = new Text(composite, SWT.BORDER); + xmodemCommand.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + xmodemCommand.setText(preferences.getXmodemCommand()); return composite; } @@ -369,6 +381,8 @@ public class PreferencesDialog extends Dialog { preferences.setGenerateBinary(generateBinary.getSelection()); preferences.setGenerateHex(generateHex.getSelection()); preferences.setGenerateListing(generateListing.getSelection()); + preferences.setDownloadCommand(downloadCommand.getText()); + preferences.setXmodemCommand(xmodemCommand.getText()); super.okPressed(); } diff --git a/src/com/maccasoft/tools/SerialTerminal.java b/src/com/maccasoft/tools/SerialTerminal.java index 2a59d7d..aec22aa 100644 --- a/src/com/maccasoft/tools/SerialTerminal.java +++ b/src/com/maccasoft/tools/SerialTerminal.java @@ -10,9 +10,7 @@ package com.maccasoft.tools; -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeSupport; - +import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; @@ -39,13 +37,12 @@ import com.maccasoft.tools.internal.ImageRegistry; import jssc.SerialPort; import jssc.SerialPortEvent; import jssc.SerialPortEventListener; +import jssc.SerialPortException; import jssc.SerialPortList; +import jssc.SerialPortTimeoutException; public class SerialTerminal extends Window { - public static final String PROP_PORT = "port"; - public static final String PROP_BAUD = "baud"; - public static final String[] BAUD_RATES = new String[] { String.valueOf(SerialPort.BAUDRATE_9600), String.valueOf(SerialPort.BAUDRATE_19200), @@ -54,6 +51,13 @@ public class SerialTerminal extends Window { String.valueOf(SerialPort.BAUDRATE_115200) }; + public static final byte SOH = 0x01; + public static final byte EOT = 0x04; + public static final byte ACK = 0x06; + public static final byte NAK = 0x15; + public static final byte CAN = 0x18; + public static final byte C = 0x43; // 'C' which use in XModem/CRC + Display display; Composite container; Terminal term; @@ -66,16 +70,15 @@ public class SerialTerminal extends Window { int baud; SerialPort serialPort; - final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this); + Preferences preferences; final SelectionAdapter comPortSelectionListener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - String oldPort = port; port = comPort.getText(); updateSerialPortSettings(); - changeSupport.firePropertyChange(PROP_PORT, oldPort, port); + preferences.setSerialPort(port); term.setFocus(); } }; @@ -84,10 +87,9 @@ public class SerialTerminal extends Window { @Override public void widgetSelected(SelectionEvent e) { - int oldBaud = baud; baud = Integer.valueOf(baudRate.getText()); updateSerialPortSettings(); - changeSupport.firePropertyChange(PROP_BAUD, oldBaud, baud); + preferences.setSerialBaud(baud); term.setFocus(); } }; @@ -268,6 +270,10 @@ public class SerialTerminal extends Window { protected Control createContents(Composite parent) { display = parent.getDisplay(); + preferences = Preferences.getInstance(); + port = preferences.getSerialPort(); + baud = preferences.getSerialBaud(); + container = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(1, false); layout.marginWidth = layout.marginHeight = 0; @@ -291,12 +297,11 @@ public class SerialTerminal extends Window { @Override public void widgetDisposed(DisposeEvent e) { try { - PropertyChangeListener[] l = changeSupport.getPropertyChangeListeners(); - for (int i = 0; i < l.length; i++) { - changeSupport.removePropertyChangeListener(l[i]); - } - serialPort.removeEventListener(); + } catch (Exception ex) { + // Do nothing + } + try { if (serialPort.isOpened()) { serialPort.closePort(); } @@ -414,50 +419,6 @@ public class SerialTerminal extends Window { getShell().setText("Serial Terminal on " + port); } - public void addPropertyChangeListener(PropertyChangeListener listener) { - changeSupport.addPropertyChangeListener(listener); - } - - public void removePropertyChangeListener(PropertyChangeListener listener) { - changeSupport.removePropertyChangeListener(listener); - } - - public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { - changeSupport.addPropertyChangeListener(propertyName, listener); - } - - public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { - changeSupport.removePropertyChangeListener(propertyName, listener); - } - - public String getPort() { - return port; - } - - public void setPort(String port) { - if (comPort != null && !comPort.isDisposed()) { - int index = comPort.indexOf(port); - if (index != -1) { - comPort.select(index); - } - } - this.port = port; - } - - public int getBaud() { - return baud; - } - - public void setBaud(int baud) { - if (baudRate != null && !baudRate.isDisposed()) { - int index = baudRate.indexOf(String.valueOf(baud)); - if (index != -1) { - baudRate.select(index); - } - } - this.baud = baud; - } - public void setFocus() { term.setFocus(); } @@ -492,4 +453,284 @@ public class SerialTerminal extends Window { public void dispose() { getShell().dispose(); } + + public void uploadPackedBinary(String name, byte[] data, IProgressMonitor monitor) throws SerialPortException, SerialPortTimeoutException { + int checksum; + String s; + + try { + serialPort.removeEventListener(); + } catch (SerialPortException e) { + // Do nothing + } + + try { + s = preferences.getDownloadCommand(); + s = s.replace("{0}", name.toUpperCase()); + serialPort.writeString(s); + serialPort.writeInt(13); + flushOutput(); + do { + s = readString(); + } while (s.length() != 0 && !s.contains("DOWNLOAD ")); + + serialPort.writeString("U0\r"); + flushOutput(); + try { + Thread.sleep(100); + } catch (InterruptedException e1) { + // Do nothing + } + + serialPort.writeString(":"); + flushOutput(); + try { + Thread.sleep(1000); + } catch (InterruptedException e1) { + // Do nothing + } + + checksum = 0; + for (int i = 0; i < data.length; i++) { + serialPort.writeString(String.format("%02X", data[i] & 0xFF)); + flushOutput(); + checksum += data[i] & 0xFF; + if (i > 0 && (i & 127) == 0) { + waitDot(); + if (monitor != null) { + monitor.worked(1); + } + } + if (monitor != null && monitor.isCanceled()) { + return; + } + } + + serialPort.writeString(">"); + flushOutput(); + if ((data.length & 127) != 0) { + waitDot(); + if (monitor != null) { + monitor.worked(1); + } + } + } finally { + try { + serialPort.addEventListener(serialEventListener); + } catch (SerialPortException e) { + // Do nothing + } + } + + serialPort.writeString(String.format("%02X", data.length & 0xFF)); + flushOutput(); + + serialPort.writeString(String.format("%02X", checksum & 0xFF)); + flushOutput(); + } + + void flushOutput() throws SerialPortException { + while (serialPort.getOutputBufferBytesCount() > 0) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + // Do nothing + } + } + } + + String readString() throws SerialPortException, SerialPortTimeoutException { + String s = ""; + + while (true) { + byte[] b = serialPort.readBytes(1, 5000); + print(b[0]); + if (b[0] == '\r') { + try { + b = serialPort.readBytes(1, 200); + print(b[0]); + } catch (SerialPortTimeoutException e) { + // Do nothing + } + break; + } + if (b[0] >= ' ') { + s = s + (char) b[0]; + } + } + + return s; + } + + void print(byte b) { + display.syncExec(new Runnable() { + + @Override + public void run() { + term.print(b); + } + }); + } + + void waitDot() throws SerialPortException, SerialPortTimeoutException { + while (true) { + byte[] b = serialPort.readBytes(1, 5000); + if (b != null) { + print(b[0]); + if (b[0] == '.') { + break; + } + } + } + } + + public void uploadXModem(String name, byte[] data, IProgressMonitor monitor) throws SerialPortException, SerialPortTimeoutException { + String s; + + try { + serialPort.removeEventListener(); + } catch (SerialPortException e) { + // Do nothing + } + + try { + s = preferences.getXmodemCommand(); + s = s.replace("{0}", name.toUpperCase()); + serialPort.writeString(s); + serialPort.writeInt(13); + flushOutput(); + do { + s = readString(); + } while (s.length() != 0 && !s.contains("XMODEM ")); + + s = ""; + while (true) { + byte[] b = serialPort.readBytes(1, 15000); + print(b[0]); + if (b[0] >= ' ') { + s = s + (char) b[0]; + if (s.endsWith("Overwrite (Y/N)?")) { + serialPort.writeString("Y\r"); + s = ""; + } + if (s.endsWith("with CRCs")) { + break; + } + } + } + + boolean doCrc = false; + int packet = 1; + int errors = 0; + + int i = 0; + while (i < data.length) { + try { + byte[] b = serialPort.readBytes(1, 15000); + + if (b[0] == CAN) { + break; + } + + if (b[0] == C) { + doCrc = true; + b[0] = NAK; + } + + if (b[0] == ACK) { + i += 128; + packet++; + if (monitor != null) { + monitor.worked(1); + } + errors++; + } + + if (b[0] == NAK) { + errors++; + if (errors >= 10) { + return; + } + } + + if (b[0] == ACK || b[0] == NAK) { + serialPort.writeByte(SOH); + serialPort.writeByte((byte) packet); + serialPort.writeByte((byte) (packet ^ 0xFF)); + + int checksum = 0, crc = 0; + int x = 0; + while (x < 128 && (i + x) < data.length) { + serialPort.writeByte(data[i + x]); + checksum += data[i + x] & 0xFF; + crc = updateCrc(crc, data[i + x] & 0xFF); + x++; + } + if (x < 128) { + serialPort.writeByte((byte) 0x1A); + checksum += 0x1A; + crc = updateCrc(crc, 0x1A); + x++; + while (x < 128) { + serialPort.writeByte((byte) 0); + crc = updateCrc(crc, 0); + x++; + } + } + + if (doCrc) { + serialPort.writeByte((byte) ((crc >> 8) & 0xFF)); + serialPort.writeByte((byte) (crc & 0xFF)); + } + else { + serialPort.writeByte((byte) checksum); + } + } + + if (monitor != null && monitor.isCanceled()) { + serialPort.writeBytes(new byte[] { + CAN, CAN, CAN + }); + return; + } + } catch (SerialPortTimeoutException e) { + errors++; + if (errors >= 10) { + return; + } + } + } + + if (i >= data.length) { + serialPort.writeByte(EOT); + byte[] b = serialPort.readBytes(1, 5000); + if (b[0] == NAK) { + serialPort.writeByte(EOT); + } + b = serialPort.readBytes(1, 5000); + if (b[0] == NAK) { + serialPort.writeByte(EOT); + } + } + } finally { + try { + serialPort.addEventListener(serialEventListener); + } catch (SerialPortException e) { + // Do nothing + } + } + } + + int updateCrc(int crc, int b) { + for (int i = 0; i < 8; i++) { + boolean bit = ((b >>> (7 - i) & 1) == 1); + boolean c15 = ((crc >>> 15 & 1) == 1); + crc <<= 1; + if (c15 ^ bit) { + crc ^= 0x1021; + } + } + return crc & 0xFFFF; + } + } diff --git a/src/com/maccasoft/tools/StatusLine.java b/src/com/maccasoft/tools/StatusLine.java index 11c862a..678e98f 100644 --- a/src/com/maccasoft/tools/StatusLine.java +++ b/src/com/maccasoft/tools/StatusLine.java @@ -31,6 +31,8 @@ public class StatusLine implements IProgressMonitor { Label caretPositionLabel; ProgressIndicator progressBar; + boolean canceled; + public StatusLine(Composite parent) { display = parent.getDisplay(); @@ -98,6 +100,8 @@ public class StatusLine implements IProgressMonitor { @Override public void beginTask(String name, final int totalWork) { + canceled = false; + final boolean animated = (totalWork == UNKNOWN || totalWork == 0); display.syncExec(new Runnable() { @@ -162,12 +166,12 @@ public class StatusLine implements IProgressMonitor { @Override public boolean isCanceled() { - return false; + return canceled; } @Override public void setCanceled(boolean value) { - + canceled = value; } @Override