320 lines
8.5 KiB
C++
320 lines
8.5 KiB
C++
|
|
#include "XModem.h"
|
|
#include "CmdStatus.h"
|
|
|
|
// The original TommyPROM code used XModem CRC protocol but some Linux communication
|
|
// programs do not seem to support this correctly. The default is now to use basic XModem
|
|
// with the 8-bit checksum instead of the 16-bit CRC error checking. This shouldn't
|
|
// matter because communication errors aren't likely to happen on a 3 foot USB cable like
|
|
// they did over the long distance dail-up lines that XModem was designed for. Uncomment
|
|
// the XMODEM_CRC_PROTOCOL line below to restore the original XMODEM CRC support.
|
|
|
|
//#define XMODEM_CRC_PROTOCOL
|
|
|
|
enum
|
|
{
|
|
// XMODEM control characters.
|
|
XMDM_SOH = 0x01,
|
|
XMDM_EOT = 0x04,
|
|
XMDM_ACK = 0x06,
|
|
XMDM_NAK = 0x15,
|
|
XMDM_CAN = 0x18,
|
|
XMDM_ESC = 0x1b,
|
|
XMDM_CRC = 'C',
|
|
#ifdef XMODEM_CRC_PROTOCOL
|
|
XMDM_TRANSFER_START = XMDM_CRC
|
|
#else
|
|
XMDM_TRANSFER_START = XMDM_NAK
|
|
#endif
|
|
};
|
|
|
|
enum
|
|
{
|
|
// Misc constants for XMODEM.
|
|
PKTLEN = 128
|
|
};
|
|
|
|
|
|
uint32_t XModem::ReceiveFile(uint32_t address)
|
|
{
|
|
uint8_t buffer[PKTLEN];
|
|
int c;
|
|
uint8_t seq = 1;
|
|
uint32_t numBytes = 0;
|
|
bool complete = false;
|
|
|
|
#ifdef XMODEM_CRC_PROTOCOL
|
|
Serial.println(F("Send the image file using XMODEM CRC"));
|
|
#else
|
|
Serial.println(F("Send the image file using XMODEM"));
|
|
#endif
|
|
if (!StartReceive())
|
|
{
|
|
cmdStatus.error("Timeout waiting for transfer to start.");
|
|
return 0;
|
|
}
|
|
|
|
while (!complete)
|
|
{
|
|
if ((c = GetChar()) < 0)
|
|
{
|
|
cmdStatus.error("Timeout waiting for start of next packet.");
|
|
cmdStatus.setValueDec(0, "seq", seq);
|
|
return 0;
|
|
}
|
|
|
|
switch (c)
|
|
{
|
|
case XMDM_SOH:
|
|
// Start of a packet
|
|
if (ReceivePacket(buffer, PKTLEN, seq++, address))
|
|
{
|
|
numBytes += PKTLEN;
|
|
address += PKTLEN;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case XMDM_EOT:
|
|
// End of transfer
|
|
Serial.write(XMDM_ACK);
|
|
complete = true;
|
|
break;
|
|
|
|
case XMDM_CAN:
|
|
case XMDM_ESC:
|
|
// Cancel from sender
|
|
cmdStatus.error("Transfer canceled by sender.");
|
|
cmdStatus.setValueDec(0, "seq", seq);
|
|
return 0;
|
|
break;
|
|
|
|
default:
|
|
// Fail the transfer on anything else
|
|
cmdStatus.error("Unexpected character received waiting for next packet.");
|
|
cmdStatus.setValueDec(0, "char", c);
|
|
cmdStatus.setValueDec(1, "seq", seq);
|
|
return 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return numBytes;
|
|
}
|
|
|
|
|
|
// This method it not very tolerant of communication errors. If the receiver
|
|
// does not send a positive ACK for each packet or does not ACK the packet
|
|
// within one second then the transfer will fail. Unlike in the dial-up
|
|
// days of old, this is designed to be run on a 3 foot cable betwee two fast
|
|
// hosts, so communication errors or timeouts are extremely unlikely.
|
|
bool XModem::SendFile(uint32_t address, uint32_t fileSize)
|
|
{
|
|
uint8_t seq = 1;
|
|
int rxChar = -1;
|
|
uint32_t bytesSent = 0;
|
|
|
|
#ifdef XMODEM_CRC_PROTOCOL
|
|
Serial.println(F("Set the terminal to receive XModem CRC"));
|
|
#else
|
|
Serial.println(F("Set the terminal to receive XModem"));
|
|
#endif
|
|
while (rxChar == -1)
|
|
{
|
|
rxChar = GetChar();
|
|
}
|
|
if (rxChar != XMDM_TRANSFER_START)
|
|
{
|
|
#ifdef XMODEM_CRC_PROTOCOL
|
|
cmdStatus.error("Expected XModem CRC start char");
|
|
#else
|
|
cmdStatus.error("Expected XModem NAK char to start");
|
|
#endif
|
|
cmdStatus.setValueDec(0, "char", rxChar);
|
|
return false;
|
|
}
|
|
|
|
while (bytesSent < fileSize)
|
|
{
|
|
SendPacket(address, seq++);
|
|
address += PKTLEN;
|
|
|
|
rxChar = GetChar(5000);
|
|
if (rxChar != XMDM_ACK)
|
|
{
|
|
cmdStatus.error("Expected XModem ACK.");
|
|
cmdStatus.setValueDec(0, "char", rxChar);
|
|
return false;
|
|
}
|
|
bytesSent += PKTLEN;
|
|
}
|
|
|
|
// Signal end of transfer and process the ACK
|
|
Serial.write(XMDM_EOT);
|
|
rxChar = GetChar(5000);
|
|
if (rxChar != XMDM_ACK)
|
|
{
|
|
cmdStatus.error("Expected XModem ACK to EOT, but received:");
|
|
cmdStatus.setValueDec(0, "char", rxChar);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void XModem::Cancel()
|
|
{
|
|
// Send a cancel and then eat input until the line is quiet for 3 seconds.
|
|
Serial.write(XMDM_CAN);
|
|
while (GetChar(3000) != -1)
|
|
{}
|
|
}
|
|
|
|
|
|
// Private functions
|
|
int XModem::GetChar(int msWaitTime)
|
|
{
|
|
do
|
|
{
|
|
if (Serial.available() > 0)
|
|
{
|
|
return Serial.read();
|
|
}
|
|
delay(1);
|
|
} while (msWaitTime--);
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
uint16_t XModem::UpdateCrc(uint16_t crc, uint8_t data)
|
|
{
|
|
#ifdef XMODEM_CRC_PROTOCOL
|
|
crc = crc ^ ((uint16_t)data << 8);
|
|
for (int ix = 0; (ix < 8); ix++)
|
|
{
|
|
if (crc & 0x8000)
|
|
{
|
|
crc = (crc << 1) ^ 0x1021;
|
|
}
|
|
else
|
|
{
|
|
crc <<= 1;
|
|
}
|
|
}
|
|
#else
|
|
crc = (crc + data) & 0xff;
|
|
#endif
|
|
return crc;
|
|
}
|
|
|
|
|
|
bool XModem::StartReceive()
|
|
{
|
|
for (int retries = 30; (retries); --retries)
|
|
{
|
|
// Send the XMDM_TRANSFER_START until the sender of the file responds with
|
|
// something. The start character will be sent once a second for a number of
|
|
// seconds. If nothing is received in that time then return false to indicate
|
|
// that the transfer did not start.
|
|
Serial.write(XMDM_TRANSFER_START);
|
|
for (int ms = 1000; (ms); --ms)
|
|
{
|
|
if (Serial.available() > 0)
|
|
{
|
|
return true;
|
|
}
|
|
delay(1);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool XModem::ReceivePacket(uint8_t buffer[], unsigned bufferSize, uint8_t seq, uint32_t destAddr)
|
|
{
|
|
int c;
|
|
uint8_t rxSeq1, rxSeq2;
|
|
uint16_t calcCrc = 0;
|
|
uint16_t rxCrc;
|
|
|
|
rxSeq1 = (uint8_t) GetChar();
|
|
rxSeq2 = (uint8_t) GetChar();
|
|
|
|
for (unsigned ix = 0; (ix < bufferSize); ix++)
|
|
{
|
|
if ((c = GetChar()) < 0)
|
|
{
|
|
// If the read times out then fail this packet. Note that this check isn't
|
|
// done for the sequence and CRC. If they timeout then the values won't match
|
|
// so there is not point in the extra code to check for the error. The worst
|
|
// that will happen is that the transfer will need to wait 3 timeouts before
|
|
// realizing that something is wrong.
|
|
cmdStatus.error("Timeout waiting for next packet char.");
|
|
cmdStatus.setValueDec(0, "seq", seq);
|
|
Serial.write(XMDM_CAN);
|
|
return false;
|
|
}
|
|
buffer[ix] = (uint8_t) c;
|
|
calcCrc = UpdateCrc(calcCrc, buffer[ix]);
|
|
}
|
|
|
|
#ifdef XMODEM_CRC_PROTOCOL
|
|
rxCrc = ((uint16_t) GetChar()) << 8;
|
|
rxCrc |= GetChar();
|
|
#else
|
|
rxCrc = GetChar();
|
|
#endif
|
|
if ((calcCrc != rxCrc) || (rxSeq1 != seq) || ((rxSeq1 ^ rxSeq2) != 0xff))
|
|
{
|
|
// Fail if the CRC or sequence number is not correct or if the two received
|
|
// sequence numbers are not the complement of one another.
|
|
cmdStatus.error("Bad CRC or sequence number.");
|
|
cmdStatus.setValueDec(0, "seq", seq);
|
|
cmdStatus.setValueDec(1, "rxSeq1", rxSeq1);
|
|
cmdStatus.setValueDec(2, "rxSeq2", rxSeq2);
|
|
cmdStatus.setValueHex(3, "calcCrc", calcCrc);
|
|
cmdStatus.setValueHex(4, "rxCrc", rxCrc);
|
|
Serial.write(XMDM_CAN);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// The data is good. Process the packet then ACK it to the sender.
|
|
if (!promDevice.writeData(buffer, bufferSize, destAddr))
|
|
{
|
|
cmdStatus.error("Write failed");
|
|
cmdStatus.setValueHex(0, "address", destAddr);
|
|
return false;
|
|
}
|
|
Serial.write(XMDM_ACK);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void XModem::SendPacket(uint32_t address, uint8_t seq)
|
|
{
|
|
uint16_t crc = 0;
|
|
|
|
Serial.write(XMDM_SOH);
|
|
Serial.write(seq);
|
|
Serial.write(~seq);
|
|
for (int ix = 0; (ix < PKTLEN); ix++)
|
|
{
|
|
byte c = promDevice.readData(address++);
|
|
Serial.write(c);
|
|
crc = UpdateCrc(crc, c);
|
|
}
|
|
#ifdef XMODEM_CRC_PROTOCOL
|
|
Serial.write(crc >> 8);
|
|
#endif
|
|
Serial.write(crc & 0xff);
|
|
}
|