summaryrefslogtreecommitdiff
path: root/upload/xmodem.c
diff options
context:
space:
mode:
authorNick Van Doorn <vandoorn.nick@gmail.com>2018-09-10 23:23:21 -0700
committerNick Van Doorn <vandoorn.nick@gmail.com>2018-09-10 23:23:21 -0700
commit83639bdffb6b9355a29d2b2e55807aa3b6837bbd (patch)
treee5858b146a3901880e5babaa89aacbe0979f0552 /upload/xmodem.c
Initial commit
Diffstat (limited to 'upload/xmodem.c')
-rw-r--r--upload/xmodem.c1879
1 files changed, 1879 insertions, 0 deletions
diff --git a/upload/xmodem.c b/upload/xmodem.c
new file mode 100644
index 0000000..dd471d7
--- /dev/null
+++ b/upload/xmodem.c
@@ -0,0 +1,1879 @@
+//////////////////////////////////////////////////////////////////////////////
+// //
+// _ //
+// __ __ _ __ ___ ___ __| | ___ _ __ ___ ___ //
+// \ \/ /| '_ ` _ \ / _ \ / _` | / _ \| '_ ` _ \ / __| //
+// > < | | | | | || (_) || (_| || __/| | | | | | _| (__ //
+// /_/\_\|_| |_| |_| \___/ \__,_| \___||_| |_| |_|(_)\___| //
+// //
+// //
+//////////////////////////////////////////////////////////////////////////////
+// //
+// Copyright (c) 2012 by S.F.T. Inc. - All rights reserved //
+// Use, copying, and distribution of this software are licensed according //
+// to the GPLv2, LGPLv2, or BSD license, as appropriate (see COPYING) //
+// //
+//////////////////////////////////////////////////////////////////////////////
+
+// XMODEM adapted for arduino and POSIX systems. Windows code incomplete
+
+#include "xmodem.h"
+
+// special I/O when linked into 'SFTARDCAL' application
+#ifdef SFTARDCAL
+int my_read(SERIAL_TYPE iFile, void* pBuf, int cbBuf);
+int my_write(SERIAL_TYPE iFile, const void* pBuf, int cbBuf);
+void my_flush(SERIAL_TYPE iFile);
+#endif // SFTARDCAL
+
+// internal structure definitions
+
+// Windows requires a different way of specifying structure packing
+#ifdef WIN32
+#define PACKED
+#pragma pack(push, 1)
+#else // POSIX, ARDUINO
+#define PACKED __attribute__((__packed__))
+#endif // WIN32 vs THE REST OF THE WORLD
+
+#define _SOH_ 1 /* start of packet - note XMODEM-1K uses '2' */
+#define _EOT_ 4
+#define _ENQ_ 5
+#define _ACK_ 6
+#define _NAK_ 21 /* NAK character */
+#define _CAN_ 24 /* CAN character CTRL+X */
+
+/** \file xmodem.c
+ * \brief main source file for S.F.T. XMODEM library
+ *
+ * S.F.T. XMODEM library
+**/
+
+/** \ingroup xmodem_internal
+ * \brief Structure defining an XMODEM CHECKSUM packet
+ *
+\code
+typedef struct _XMODEM_BUF_
+{
+ char cSOH; // ** SOH byte goes here **
+ unsigned char aSEQ, aNotSEQ; // ** 1st byte = seq#, 2nd is ~seq# **
+ char aDataBuf[128]; // ** the actual data itself! **
+ unsigned char bCheckSum; // ** checksum gets 1 byte **
+} PACKED XMODEM_BUF;
+\endcode
+ *
+**/
+typedef struct _XMODEM_BUF_ {
+ char cSOH; ///< SOH byte goes here
+ unsigned char aSEQ, aNotSEQ; ///< 1st byte = seq#, 2nd is ~seq#
+ char aDataBuf[128]; ///< the actual data itself!
+ unsigned char bCheckSum; ///< checksum gets 1 byte
+} PACKED XMODEM_BUF;
+
+/** \ingroup xmodem_internal
+ * \brief Structure defining an XMODEM CRC packet
+ *
+\code
+typedef struct _XMODEMC_BUF_
+{
+ char cSOH; // ** SOH byte goes here **
+ unsigned char aSEQ, aNotSEQ; // ** 1st byte = seq#, 2nd is ~seq# **
+ char aDataBuf[128]; // ** the actual data itself! **
+ unsigned short wCRC; // ** CRC gets 2 bytes, high endian **
+} PACKED XMODEMC_BUF;
+\endcode
+ *
+**/
+typedef struct _XMODEMC_BUF_ {
+ char cSOH; ///< SOH byte goes here
+ unsigned char aSEQ, aNotSEQ; ///< 1st byte = seq#, 2nd is ~seq#
+ char aDataBuf[128]; ///< the actual data itself!
+ unsigned short wCRC; ///< CRC gets 2 bytes, high endian
+} PACKED XMODEMC_BUF;
+
+#ifdef WIN32
+// restore default packing
+#pragma pack(pop)
+#endif // WIN32
+
+/** \ingroup xmodem_internal
+ * \brief Structure that identifies the XMODEM communication state
+ *
+\code
+typedef struct _XMODEM_
+{
+ SERIAL_TYPE ser; // identifies the serial connection, data type is
+OS-dependent
+ FILE_TYPE file; // identifies the file handle, data type is OS-dependent
+ union
+ {
+ XMODEM_BUF xbuf; // XMODEM CHECKSUM buffer
+ XMODEMC_BUF xcbuf; // XMODEM CRC buffer
+ } buf; // union of both buffers, total length 133 bytes
+ unsigned char bCRC; // non-zero for CRC, zero for checksum
+} XMODEM;
+\endcode
+ *
+**/
+typedef struct _XMODEM_ {
+ SERIAL_TYPE
+ ser; ///< identifies the serial connection, data type is OS-dependent
+ FILE_TYPE file; ///< identifies the file handle, data type is OS-dependent
+
+ union {
+ XMODEM_BUF xbuf; ///< XMODEM CHECKSUM buffer
+ XMODEMC_BUF xcbuf; ///< XMODEM CRC buffer
+ } buf; ///< union of both buffers, total length 133 bytes
+
+ unsigned char bCRC; ///< non-zero for CRC, zero for checksum
+
+} XMODEM;
+
+#ifdef DEBUG_CODE
+static char szERR[32]; // place for error messages, up to 16 characters
+
+const char* XMGetError(void) {
+ return szERR;
+}
+#endif // DEBUG_CODE
+
+#if defined(STAND_ALONE) && defined(DEBUG_CODE)
+void debug_dump_buffer(int iDir, const void* pBuf, int cbBuf) {
+ int i1, i2;
+ const unsigned char *p1, *p2;
+
+ if (cbBuf <= 0) {
+ return;
+ }
+
+ p1 = p2 = (const unsigned char*)pBuf;
+
+ for (i1 = 0, i2 = 0; i1 <= cbBuf; i1++, p1++) {
+ if (!i1 || i2 >= 16 || i1 == cbBuf) {
+ if (i1) {
+ while (i2 < 16) {
+ fputs(" ", stderr); // fill up spaces where data would be
+ i2++;
+ }
+
+ fputs(" : ", stderr);
+
+ while (p2 < p1) {
+ if (*p2 >= 32 && *p2 <= 127) {
+ fputc(*p2, stderr);
+ } else {
+ fputc('.', stderr);
+ }
+
+ p2++;
+ }
+
+ fputc('\n', stderr);
+ }
+
+ if (!i1 && iDir > 0) {
+ fputs("--> ", stderr);
+ } else if (!i1 && iDir < 0) {
+ fputs("<-- ", stderr);
+ } else {
+ fputs(" ", stderr);
+ }
+
+ i2 = 0;
+ p2 = p1; // make sure
+ }
+
+ if (i1 < cbBuf) {
+ if (!i2) {
+ fprintf(stderr, "%02x: %02x", i1, *p1);
+ } else {
+ fprintf(stderr, ", %02x", *p1);
+ }
+
+ i2++;
+ }
+ }
+
+ fputc('\n', stderr);
+ fflush(stderr);
+}
+#endif // STAND_ALONE, DEBUG_CODE
+
+// char iBinaryTransfer = 0, iDisableRXOVER = 0;
+
+/** \ingroup xmodem_internal
+ * \brief Calculate checksum for XMODEM packet
+ *
+ * \param lpBuf A pointer to the XMODEM data buffer
+ * \param cbBuf The length of the XMODEM data buffer (typically 128)
+ * \return An unsigned char value to be assigned to the 'checksum' element in
+ *the XMODEM packet
+ *
+**/
+unsigned char CalcCheckSum(const char* lpBuf, short cbBuf) {
+ short iC, i1;
+
+ iC = 0;
+
+ for (i1 = 0; i1 < cbBuf; i1++) {
+ iC += lpBuf[i1];
+ }
+
+ return (unsigned char)(iC & 0xff);
+}
+
+/** \ingroup xmodem_internal
+ * \brief Calculate checksum for XMODEM packet
+ *
+ * \param sVal An unsigned short integer to be made 'high endian' by flipping
+ *bytes (as needed)
+ * \return A (possibly) byte-flipped high-endian unsigned short integer
+ *
+ * This function assumes low-endian for Arduino, and performs a universal
+ *operation
+ * for 'indeterminate' architectures.
+**/
+static unsigned short my_htons(unsigned short sVal) {
+ union {
+ unsigned char aVal[2];
+ unsigned short sVal;
+ } a, b;
+
+ // tweeked for size and speed. enjoy.
+
+ b.sVal = sVal;
+
+#ifdef ARDUINO
+
+ a.aVal[0] = b.aVal[1]; // no math involved, pre-optimized code
+ a.aVal[1] = b.aVal[0];
+
+#else
+
+ a.aVal[0] = (unsigned char)(sVal >> 8); // less optimized but universal code
+ a.aVal[1] = (unsigned char)(sVal & 0xff);
+
+#endif // ARDUINO
+
+ return a.sVal;
+}
+
+/** \ingroup xmodem_internal
+ * \brief Calculate 16-bit CRC for XMODEM packet
+ *
+ * \param lpBuf A pointer to the XMODEM data buffer
+ * \param cbBuf The length of the XMODEM data buffer (typically 128)
+ * \return A high-endian 16-bit (unsigned short) value to be assigned to the
+ *'CRC' element in the XMODEM packet
+ *
+ * This method uses the 'long way' which is SMALLER CODE for microcontrollers,
+ *but eats up a bit more CPU.
+ * Otherwise, you'd have to pre-build the 256 byte table and use "the table
+ *lookup" method.
+**/
+unsigned short CalcCRC(const char* lpBuf, short cbBuf) {
+ unsigned short wCRC;
+ short i1, i2, iAX;
+ char cAL;
+
+ // ** this function returns 2-byte string containing
+ // ** the CRC calculation result, as high endian
+
+ wCRC = 0;
+
+ for (i1 = 0; i1 < cbBuf; i1++) {
+ cAL = lpBuf[i1];
+
+ iAX = (unsigned short)cAL << 8;
+
+ wCRC = iAX ^ wCRC;
+
+ for (i2 = 0; i2 < 8; i2++) {
+ iAX = wCRC;
+
+ if (iAX & 0x8000) {
+ wCRC <<= 1;
+ wCRC ^= 0x1021;
+ } else {
+ wCRC <<= 1;
+ }
+ }
+ }
+
+ return my_htons(wCRC);
+}
+
+// void WaitASecond()
+//{
+//#ifdef ARDUINO
+// delay(1000);
+//#elif defined(WIN32)
+// Sleep(1000);
+//#else //
+// usleep(1000000);
+//#endif // ARDUINO
+//}
+
+#ifndef ARDUINO
+#ifdef WIN32
+#define MyMillis GetTickCount
+#else // WIN32
+
+/** \ingroup xmodem_internal
+ * \brief Return internal 'milliseconds' value for timing purposes
+ *
+ * \return A calculated 'milliseconds' value as an unsigned long integer
+ *
+ * This function returns the 'unsigned long' integer value for elapsed time
+ *based
+ * on the result of the 'gettimeofday()' API function. On 32-bit and Windows
+ *systems
+ * the value might wrap around, so you should be careful with your time
+ *comparisons (see the
+ * code _I_ wrote for the right way to do it). On 64-bit POSIX systems, this
+ *value will
+ * always increase.\n
+ * NOTE: Win32 defines this as a macro (see above) for the 'GetTickCount()'
+ *api, which
+ * returns a 32-bit value. POSIX x86 returns 32-bit, x64 returns
+ *64-bit. YMMV.
+**/
+unsigned long MyMillis(void) {
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL); // 2nd parameter is obsolete anyway
+
+ // NOTE: this won't roll over the way 'GetTickCount' does in WIN32 so I'll
+ // truncate it
+ // down to a 32-bit value to make it happen. Everything that uses
+ // 'MyGetTickCount'
+ // must handle this rollover properly using 'int' and not 'long' (or
+ // cast afterwards)
+ return ((unsigned int)((unsigned long)tv.tv_sec * 1000L +
+ (unsigned long)tv.tv_usec / 1000L));
+}
+#endif // WIN32
+#endif // ARDUINO
+
+// Function GenerateSEQ (wSeq%) As String
+//
+// GenerateSEQ = Chr$(wSeq%) + Chr$(Not (wSeq%) And &HFF)
+//
+// End Function
+
+/** \ingroup xmodem_internal
+ * \brief Generate a sequence number pair, place into XMODEM_BUF
+ *
+ * \param pBuf A pointer to an XMODEM_BUF structure
+ * \param bSeq An unsigned char, typically cast from an unsigned long 'block
+ *number'
+ *
+ * This function generates the sequence pair for the XMODEM packet. The 'block
+ *number'
+ * is initially assigned a value of '1', and increases by 1 for each successful
+ *packet.
+ * That value is 'truncated' to a single byte and assigned as a sequence number
+ *for the
+ * packet itself.
+**/
+void GenerateSEQ(XMODEM_BUF* pBuf, unsigned char bSeq) {
+ pBuf->aSEQ = bSeq;
+ pBuf->aNotSEQ = ~bSeq;
+}
+
+/** \ingroup xmodem_internal
+ * \brief Generate a sequence number pair, place into XMODEMC_BUF (the CRC
+ *version)
+ *
+ * \param pBuf A pointer to an XMODEM_BUF structure
+ * \param bSeq An unsigned char, typically cast from an unsigned long 'block
+ *number'
+ *
+ * This function generates the sequence pair for the XMODEM packet. The 'block
+ *number'
+ * is initially assigned a value of '1', and increases by 1 for each successful
+ *packet.
+ * That value is 'truncated' to a single byte and assigned as a sequence number
+ *for the
+ * packet itself.
+**/
+void GenerateSEQC(XMODEMC_BUF* pBuf, unsigned char bSeq) {
+ pBuf->aSEQ = bSeq;
+ pBuf->aNotSEQ =
+ (255 - bSeq); //~bSeq; these should be the same but for now I do this...
+}
+
+/** \ingroup xmodem_internal
+ * \brief Get an XMODEM block from the serial device
+ *
+ * \param ser A 'SERIAL_TYPE' identifier for the serial connection
+ * \param pBuf A pointer to the buffer that receives the data
+ * \param cbSize The number of bytes/chars to read
+ * \return The number of bytes/chars read, 0 if timed out (no data), < 0 on
+ *error
+ *
+ * Call this function to read data from the serial port, specifying the number
+ *of
+ * bytes to read. This function times out after no data transferred (silence)
+ *for
+ * a period of 'SILENCE_TIMEOUT' milliseconds. This allows spurious data
+ *transfers
+ * to continue as long as there is LESS THAN 'SILENCE_TIMEOUT' between bytes,
+ *and
+ * also allows VERY SLOW BAUD RATES (as needed). However, if the transfer
+ *takes longer
+ * than '10 times SILENCE_TIMEOUT', the function will return the total number
+ *of bytes
+ * that were received within that time.\n
+ * The default value of 5 seconds, extended to 50 seconds, allows a worst-case
+ *baud
+ * rate of about 20. This should not pose a problem. If it does, edit the
+ *code.
+**/
+short GetXmodemBlock(SERIAL_TYPE ser, char* pBuf, short cbSize) {
+ unsigned long ulCur;
+ short cb1;
+// ** This function obtains a buffer of 'cbSize' bytes, **
+// ** waiting a maximum of 5 seconds (of silence) to get it. **
+// ** It returns the data within 'pBuf', returning the actual **
+// ** number of bytes transferred. **
+
+#ifdef ARDUINO
+ char* p1;
+ short i1;
+
+ p1 = pBuf;
+ cb1 = 0;
+
+ ulCur = millis();
+ ser->setTimeout(SILENCE_TIMEOUT); // 5 seconds [of silence]
+
+ for (i1 = 0; i1 < cbSize; i1++) {
+ if (ser->readBytes(p1, 1) !=
+ 1) // 5 seconds of "silence" is what fails this
+ {
+ break;
+ }
+
+ cb1++;
+ p1++;
+
+ if ((millis() - ulCur) >
+ (unsigned long)(10L * SILENCE_TIMEOUT)) // 10 times SILENCE TIMEOUT for
+ // TOTAL TIMEOUT
+ {
+ break; // took too long, I'm going now
+ }
+ }
+
+#elif defined(SFTARDCAL)
+#ifndef WIN32
+ struct pollfd aFD[2];
+#endif // WIN32
+ int i1;
+ unsigned long ulStart;
+
+ ulStart = ulCur = MyMillis();
+
+ cb1 = 0;
+
+ do {
+#ifndef WIN32
+ aFD[0].fd = ser;
+ aFD[0].events = POLLIN | POLLERR;
+ aFD[0].revents = 0;
+
+ i1 = poll(aFD, 1, 100);
+ if (!i1) {
+ continue;
+ }
+
+ if (i1 < 0) {
+ fprintf(stderr, "poll error %d\n", errno);
+ return -1;
+ }
+#endif // WIN32
+
+#ifdef WIN32
+ if (my_pollin(ser) > 0)
+#else // WIN32
+ if (aFD[0].revents & POLLIN)
+#endif // WIN32
+ {
+ i1 = my_read(ser, pBuf + cb1, cbSize - cb1);
+
+ if (i1 > 0) {
+ cb1 += i1;
+ ulCur = MyMillis();
+ }
+ } else {
+ MySleep(1);
+ }
+ } while (!QuitFlag() && cb1 < cbSize &&
+ (MyMillis() - ulCur) < SILENCE_TIMEOUT &&
+ (MyMillis() - ulStart) < (unsigned long)10L * SILENCE_TIMEOUT);
+
+#elif defined(WIN32)
+
+#error no win32 code yet
+
+#else // POSIX
+ unsigned long ulStart;
+ char* p1;
+ int i1, i2;
+
+ if (fcntl(ser, F_SETFL, O_NONBLOCK) == -1) {
+ static int iFailFlag = 0;
+
+ if (!iFailFlag) {
+ fprintf(stderr, "Warning: 'fcntl(O_NONBLOCK)' failed, errno = %d\n",
+ errno);
+ fflush(stderr);
+ iFailFlag = 1;
+ }
+ }
+
+ p1 = pBuf;
+ cb1 = 0;
+
+ ulStart = ulCur = MyMillis();
+
+ for (i1 = 0; i1 < cbSize; i1++) {
+ while ((i2 = read(ser, p1, 1)) != 1) {
+ if (i2 < 0 && errno != EAGAIN) {
+ // read error - exit now
+ // return cb1; // how many bytes I actually read
+ goto the_end;
+ } else {
+ usleep(1000); // 1 msec
+
+ if ((MyMillis() - ulCur) > SILENCE_TIMEOUT || // too much silence?
+ (MyMillis() - ulStart) >
+ 10 * SILENCE_TIMEOUT) // too long for transfer
+ {
+ // return cb1; // finished (return how many bytes I actually
+ // read)
+ goto the_end;
+ }
+ }
+ }
+
+ // here it succeeds
+
+ cb1++;
+ p1++;
+
+ if ((MyMillis() - ulStart) >
+ 10 * SILENCE_TIMEOUT) // 10 times SILENCE TIMEOUT for TOTAL TIMEOUT
+ {
+ break; // took too long, I'm going now
+ }
+ }
+
+the_end:
+
+#if defined(STAND_ALONE) && defined(DEBUG_CODE)
+ fprintf(stderr, "GetXmodemBlock - request %d, read %d errno=%d\n", cbSize,
+ cb1, errno);
+ fflush(stderr);
+ //#ifdef DEBUG_CODE
+ debug_dump_buffer(-1, pBuf, cb1);
+//#endif // DEBUG_CODE
+#endif // STAND_ALONE
+
+#endif // ARDUINO
+
+ return cb1; // what I actually read
+}
+
+/** \ingroup xmodem_internal
+ * \brief Write a single character to the serial device
+ *
+ * \param ser A 'SERIAL_TYPE' identifier for the serial connection
+ * \param bVal The byte to send
+ * \return The number of bytes/chars written, or < 0 on error
+ *
+ * Call this function to write one byte of data to the serial port. Typically
+ * this is used to send things like an ACK or NAK byte.
+**/
+int WriteXmodemChar(SERIAL_TYPE ser, unsigned char bVal) {
+ int iRval;
+#ifdef ARDUINO
+
+ iRval = ser->write(bVal);
+// ser->flush(); // force sending it
+
+#elif defined(SFTARDCAL)
+ char buf[2]; // use size of '2' to avoid warnings about array size of '1'
+
+ buf[0] = (char)bVal;
+ return (short)my_write(ser, buf, 1);
+
+#elif defined(WIN32)
+
+#error no win32 code yet
+
+#else // POSIX
+ char buf[2]; // use size of '2' to avoid warnings about array size of '1'
+
+ if (fcntl(ser, F_SETFL, 0) == -1) // set blocking mode
+ {
+ static int iFailFlag = 0;
+
+ if (!iFailFlag) {
+ fprintf(stderr, "Warning: 'fcntl(O_NONBLOCK)' failed, errno = %d\n",
+ errno);
+ iFailFlag = 1;
+ }
+ }
+
+ buf[0] = bVal; // in case args are passed by register
+
+ iRval = write(ser, buf, 1);
+
+#if defined(STAND_ALONE) && defined(DEBUG_CODE)
+ fprintf(stderr, "WriteXmodemChar - returns %d\n", iRval);
+ if (iRval > 0) {
+ debug_dump_buffer(1, buf, 1);
+ }
+#endif // STAND_ALONE, DEBUG_CODE
+#endif // ARDUINO
+
+ return iRval;
+}
+
+/** \ingroup xmodem_internal
+ * \brief Send an XMODEM block via the serial device
+ *
+ * \param ser A 'SERIAL_TYPE' identifier for the serial connection
+ * \param pBuf A pointer to the buffer that receives the data
+ * \param cbSize The number of bytes/chars to write
+ * \return The number of bytes/chars written, < 0 on error
+ *
+ * Call this function to write data via the serial port, specifying the number
+ *of
+ * bytes to write.
+**/
+int WriteXmodemBlock(SERIAL_TYPE ser, const void* pBuf, int cbSize) {
+ int iRval;
+#ifdef ARDUINO
+
+ iRval = ser->write((const uint8_t*)pBuf, cbSize);
+// ser->flush(); // force sending it before returning
+
+#elif defined(SFTARDCAL)
+
+ return (short)my_write(ser, pBuf, cbSize);
+
+#elif defined(WIN32)
+
+#error no win32 code yet
+
+#else // POSIX
+
+ if (fcntl(ser, F_SETFL, 0) == -1) // set blocking mode
+ {
+ static int iFailFlag = 0;
+
+ if (!iFailFlag) {
+ fprintf(stderr, "Warning: 'fcntl(O_NONBLOCK)' failed, errno = %d\n",
+ errno);
+ fflush(stderr);
+ iFailFlag = 1;
+ }
+ }
+
+ iRval = write(ser, pBuf, cbSize);
+
+#if defined(STAND_ALONE) && defined(DEBUG_CODE)
+ fprintf(stderr, "\r\nWriteXmodemBlock - returns %d\n", iRval);
+ fflush(stderr);
+
+ if (iRval > 0) {
+ debug_dump_buffer(1, pBuf, cbSize);
+ }
+#endif // STAND_ALONE, DEBUG_CODE
+#endif
+
+ return iRval;
+}
+
+/** \ingroup xmodem_internal
+ * \brief Read all input from the serial port until there is 1 second of
+ *'silence'
+ *
+ * \param ser A 'SERIAL_TYPE' identifier for the serial connection
+ *
+ * Call this function to read ALL data from the serial port, until there is a
+ *period
+ * with no data (i.e. 'silence') for 1 second. At that point the function will
+ *return.\n
+ * Some operations require that any bad data be flushed out of the input to
+ *prevent
+ * synchronization problems. By using '1 second of silence' it forces
+ *re-synchronization
+ * to occur in one shot, with the possible exception of VERY noisy lines. The
+ *down side
+ * is that it may slow down transfers with a high data rate.
+**/
+void XModemFlushInput(SERIAL_TYPE ser) {
+#ifdef ARDUINO
+ unsigned long ulStart;
+
+ ulStart = millis();
+
+ do {
+ if (ser->available()) {
+ ser->read(); // don't care about the data
+ ulStart = millis(); // reset time
+ } else {
+ delay(1);
+ }
+
+ } while ((millis() - ulStart) < 1000);
+
+#elif defined(SFTARDCAL)
+
+ my_flush(ser);
+
+#elif defined(WIN32)
+
+#error no win32 code yet
+
+#else // POSIX
+ unsigned long ulStart;
+ int i1;
+#ifdef DEBUG_CODE
+ unsigned char buf[16];
+ int cbBuf;
+#else // DEBUG_CODE
+ unsigned char buf[2];
+#endif // DEBUG_CODE
+
+ if (fcntl(ser, F_SETFL, O_NONBLOCK) == -1) {
+ static int iFailFlag = 0;
+
+ if (!iFailFlag) {
+ fprintf(stderr, "Warning: 'fcntl(O_NONBLOCK)' failed, errno = %d\n",
+ errno);
+ iFailFlag = 1;
+ }
+ }
+
+ ulStart = MyMillis();
+#ifdef DEBUG_CODE
+ cbBuf = 0;
+#endif // DEBUG_CODE
+ while ((MyMillis() - ulStart) < 1000) {
+#ifdef DEBUG_CODE
+ i1 = read(ser, &(buf[cbBuf]), 1);
+#else // DEBUG_CODE
+ i1 = read(ser, buf, 1);
+#endif // DEBUG_CODE
+ if (i1 == 1) {
+#if defined(STAND_ALONE) && defined(DEBUG_CODE)
+ cbBuf++;
+ if (cbBuf >= sizeof(buf)) {
+ debug_dump_buffer(-1, buf, cbBuf);
+ cbBuf = 0;
+ }
+#endif // STAND_ALONE, DEBUG_CODE
+ ulStart = MyMillis();
+ } else {
+ usleep(1000);
+ }
+ }
+
+#if defined(STAND_ALONE) && defined(DEBUG_CODE)
+ if (cbBuf > 0) {
+ debug_dump_buffer(-1, buf, cbBuf);
+ }
+#endif // STAND_ALONE, DEBUG_CODE
+
+#endif // ARDUINO
+}
+
+/** \ingroup xmodem_internal
+ * \brief Terminate the XMODEM connection
+ *
+ * \param pX A pointer to the 'XMODEM' object identifying the transfer
+ *
+ * Call this function prior to ending the XMODEM transfer. Currently the only
+ * thing it does is flush the input.
+**/
+void XmodemTerminate(XMODEM* pX) {
+ XModemFlushInput(pX->ser);
+
+ // TODO: close files?
+}
+
+/** \ingroup xmodem_internal
+ * \brief Validate the sequence number of a received XMODEM block
+ *
+ * \param pX A pointer to an 'XMODEM_BUF'
+ * \param bSeq The expected sequence number (block & 255)
+ * \return A zero value on success, non-zero otherwise
+ *
+ * Call this function to validate a packet's sequence number against the block
+ *number
+**/
+short ValidateSEQ(XMODEM_BUF* pX, unsigned char bSeq) {
+ return pX->aSEQ != 255 - pX->aNotSEQ || // ~(pX->aNotSEQ) ||
+ pX->aSEQ != bSeq; // returns TRUE if not valid
+}
+
+/** \ingroup xmodem_internal
+ * \brief Validate the sequence number of a received XMODEM block (CRC version)
+ *
+ * \param pX A pointer to an 'XMODEMC_BUF'
+ * \param bSeq The expected sequence number (block & 255)
+ * \return A zero value on success, non-zero otherwise
+ *
+ * Call this function to validate a packet's sequence number against the block
+ *number
+**/
+short ValidateSEQC(XMODEMC_BUF* pX, unsigned char bSeq) {
+ return pX->aSEQ != 255 - pX->aNotSEQ || // ~(pX->aNotSEQ) ||
+ pX->aSEQ != bSeq; // returns TRUE if not valid
+}
+
+/** \ingroup xmodem_internal
+ * \brief Generic function to receive a file via XMODEM (CRC or Checksum)
+ *
+ * \param pX A pointer to an 'XMODEM_BUF' with valid bCRC, ser, and file
+ *members
+ * \return A zero value on success, negative on error, positive on cancel
+ *
+ * The calling function will need to poll for an SOH from the server using 'C'
+ *and 'NAK'
+ * characters (as appropriate) until an SOH is received. That value must be
+ *assigned
+ * to the 'buf' union (as appropriate), and the bCRC member assigned to
+ *non-zero if
+ * the server responded to 'C', or zero if it responded to 'NAK'. With the
+ *bCRC,
+ * ser, and file members correctly assigned, call THIS function to receive
+ *content
+ * via XMODEM and write it to 'file'.\n
+ * This function will return zero on success, a negative value on error, and a
+ *positive
+ * value if the transfer was canceled by the server.
+**/
+int ReceiveXmodem(XMODEM* pX) {
+#ifdef WIN32
+ DWORD cbWrote;
+#endif // WIN32
+ int ecount, ec2;
+ long etotal, filesize, block;
+ unsigned char cY; // the char to send in response to a packet
+// NOTE: to allow debugging the CAUSE of an xmodem block's failure, i1, i2, and
+// i3
+// are assigned to function return values and reported in error messages.
+#ifdef DEBUG_CODE
+ short i1, i2, i3;
+#define DEBUG_I1 i1 =
+#define DEBUG_I2 i2 =
+#define DEBUG_I3 i3 =
+#else // DEBUG_CODE
+#define DEBUG_I1 /*normally does nothing*/
+#define DEBUG_I2 /*normally does nothing*/
+#define DEBUG_I3 /*normally does nothing*/
+#endif // DEBUG_CODE
+
+ ecount = 0;
+ etotal = 0;
+ filesize = 0;
+ block = 1;
+
+ // ** already got the first 'SOH' character on entry to this function **
+
+ // Form2.Show 0 '** modeless show of form2 (CANSEND) **
+ // Form2!Label1.FloodType = 0
+ // Form2.Caption = "* XMODEM(Checksum) BINARY RECEIVE *"
+ // Form2!Label1.Caption = "Errors: 0 Bytes: 0"
+
+ pX->buf.xbuf.cSOH = (char)1; // assumed already got this, put into buffer
+
+ do {
+ if (!pX->bCRC &&
+ ((DEBUG_I1 GetXmodemBlock(pX->ser, ((char*)&(pX->buf.xbuf)) + 1,
+ sizeof(pX->buf.xbuf) - 1)) !=
+ sizeof(pX->buf.xbuf) - 1 ||
+ (DEBUG_I2 ValidateSEQ(&(pX->buf.xbuf), block & 255)) ||
+ (DEBUG_I3 CalcCheckSum(pX->buf.xbuf.aDataBuf,
+ sizeof(pX->buf.xbuf.aDataBuf)) !=
+ pX->buf.xbuf.bCheckSum))) {
+// did not receive properly
+// TODO: deal with repeated packet, sequence number for previous packet
+
+#ifdef DEBUG_CODE
+ sprintf(szERR, "A%ld,%d,%d,%d,%d,%d", block, i1, i2, i3,
+ pX->buf.xbuf.aSEQ, pX->buf.xbuf.aNotSEQ);
+//#ifdef STAND_ALONE
+// fprintf(stderr, "TEMPORARY (csum): seq=%x, ~seq=%x i1=%d, i2=%d,
+// i3=%d\n", pX->buf.xbuf.aSEQ, pX->buf.xbuf.aNotSEQ, i1, i2, i3);
+//#endif // STAND_ALONE
+#endif // DEBUG_CODE
+
+ XModemFlushInput(pX->ser); // necessary to avoid problems
+
+ cY = _NAK_; // send NAK (to get the checksum version)
+ ecount++; // for this packet
+ etotal++;
+ } else if (pX->bCRC &&
+ ((DEBUG_I1 GetXmodemBlock(pX->ser, ((char*)&(pX->buf.xcbuf)) + 1,
+ sizeof(pX->buf.xcbuf) - 1)) !=
+ sizeof(pX->buf.xcbuf) - 1 ||
+ (DEBUG_I2 ValidateSEQC(&(pX->buf.xcbuf), block & 255)) ||
+ (DEBUG_I3 CalcCRC(pX->buf.xcbuf.aDataBuf,
+ sizeof(pX->buf.xbuf.aDataBuf)) !=
+ pX->buf.xcbuf.wCRC))) {
+// did not receive properly
+// TODO: deal with repeated packet, sequence number for previous packet
+
+#ifdef DEBUG_CODE
+ sprintf(szERR, "B%ld,%d,%d,%d,%d,%d", block, i1, i2, i3,
+ pX->buf.xcbuf.aSEQ, pX->buf.xcbuf.aNotSEQ);
+//#ifdef STAND_ALONE
+// fprintf(stderr, "TEMPORARY (CRC): seq=%x, ~seq=%x i1=%d, i2=%d,
+// i3=%d\n", pX->buf.xcbuf.aSEQ, pX->buf.xcbuf.aNotSEQ, i1, i2, i3);
+//#endif // STAND_ALONE
+#endif // DEBUG_CODE
+
+ XModemFlushInput(pX->ser); // necessary to avoid problems
+
+ if (block > 1) {
+ cY = _NAK_; // TODO do I need this?
+ } else {
+ cY =
+ 'C'; // send 'CRC' NAK (the character 'C') (to get the CRC version)
+ }
+ ecount++; // for this packet
+ etotal++;
+ } else {
+#ifdef ARDUINO
+ if (pX->file.write((const uint8_t*)&(pX->buf.xbuf.aDataBuf),
+ sizeof(pX->buf.xbuf.aDataBuf)) !=
+ sizeof(pX->buf.xbuf.aDataBuf)) {
+ return -2; // write error on output file
+ }
+#elif defined(WIN32)
+ cbWrote = 0;
+ if (!WriteFile(pX->file, &(pX->buf.xbuf.aDataBuf),
+ sizeof(pX->buf.xbuf.aDataBuf), &cbWrote, NULL) ||
+ cbWrote != sizeof(pX->buf.xbuf.aDataBuf)) {
+ XmodemTerminate(pX);
+ return -2; // write error on output file
+ }
+#else // ARDUINO
+ if (write(pX->file, &(pX->buf.xbuf.aDataBuf),
+ sizeof(pX->buf.xbuf.aDataBuf)) !=
+ sizeof(pX->buf.xbuf.aDataBuf)) {
+ XmodemTerminate(pX);
+ return -2; // write error on output file
+ }
+#endif // ARDUINO
+ cY = _ACK_; // send ACK
+ block++;
+ filesize += sizeof(pX->buf.xbuf.aDataBuf); // TODO: need method to avoid
+ // extra crap at end of file
+ ecount = 0; // zero out error count for next packet
+ }
+
+#if defined(STAND_ALONE) || defined(SFTARDCAL)
+ fprintf(stderr,
+ "block %ld %ld bytes %d errors\r"
+#ifndef SFTARDCAL
+ "\n"
+#endif // SFTARDCAL
+ ,
+ block - 1, filesize, ecount);
+#endif // STAND_ALONE
+
+ ec2 = 0; // ** error count #2 **
+
+ while (ecount < TOTAL_ERROR_COUNT &&
+ ec2 < ACK_ERROR_COUNT) // ** loop to get SOH or EOT character **
+ {
+ WriteXmodemChar(pX->ser, cY); // ** output appropriate command char **
+
+ if (GetXmodemBlock(pX->ser, &(pX->buf.xbuf.cSOH), 1) == 1) {
+ if (pX->buf.xbuf.cSOH == _CAN_) // ** CTRL-X 'CAN' - terminate
+ {
+ XmodemTerminate(pX);
+ return 1; // terminated
+ } else if (pX->buf.xbuf.cSOH == _EOT_) // ** EOT - end
+ {
+ WriteXmodemChar(
+ pX->ser,
+ _ACK_); // ** send an ACK (most XMODEM protocols expect THIS)
+ // WriteXmodemChar(pX->ser, _ENQ_); // ** send an ENQ
+
+ return 0; // I am done
+ } else if (pX->buf.xbuf.cSOH == _SOH_) // ** SOH - sending next packet
+ {
+ break; // leave this loop
+ } else {
+ // TODO: deal with repeated packet, i.e. previous sequence number
+
+ XModemFlushInput(pX->ser); // necessary to avoid problems (since the
+ // character was unexpected)
+ // if I was asking for the next block, and got an unexpected
+ // character, do a NAK; otherwise,
+ // just repeat what I did last time
+
+ if (cY == _ACK_) // ACK
+ {
+ cY = _NAK_; // NACK
+ }
+
+ ec2++;
+ }
+ } else {
+ ecount++; // increase total error count, and try writing the 'ACK' or
+ // 'NACK' again
+ }
+ }
+
+ if (ec2 >= ACK_ERROR_COUNT) // wasn't able to get a packet
+ {
+ break;
+ }
+
+ } while (ecount < TOTAL_ERROR_COUNT);
+
+ XmodemTerminate(pX);
+ return 1; // terminated
+}
+
+/** \ingroup xmodem_internal
+ * \brief Generic function to send a file via XMODEM (CRC or Checksum)
+ *
+ * \param pX A pointer to an 'XMODEM_BUF' with valid ser, and file members, and
+ *the polled
+ * 'NAK' value assigned to the cSOH member (first byte) within the 'buf' union.
+ * \return A zero value on success, negative on error, positive on cancel
+ *
+ * The calling function will need to poll for a 'C' or NAK from the client (as
+ *appropriate)
+ * and assign that character to the cSOH member in the 'buf' union (either xbuf
+ *or xcbuf since
+ * the 'cSOH' will always be the first byte). Then call this function to send
+ *content
+ * via XMODEM from 'file'.\n
+ * It is important to record the NAK character before calling this function
+ *since the 'C' or
+ * 'NAK' value will be used to determine whether to use CRC or CHECKSUM.\n
+ * This function will return zero on success, a negative value on error, and a
+ *positive
+ * value if the transfer was canceled by the receiver.
+**/
+int SendXmodem(XMODEM* pX) {
+#ifdef WIN32
+ DWORD cbRead;
+#endif // WIN32
+ int ecount, ec2;
+ short i1;
+ long etotal, filesize, filepos, block;
+
+ ecount = 0;
+ etotal = 0;
+ filesize = 0;
+ filepos = 0;
+ block = 1;
+
+ pX->bCRC =
+ 0; // MUST ASSIGN TO ZERO FIRST or XMODEM-CHECKSUM may not work properly
+
+// ** already got first 'NAK' character on entry as pX->buf.xbuf.cSOH **
+
+#ifdef ARDUINO
+
+ filesize = pX->file.size();
+
+#else // ARDUINO
+
+#ifdef WIN32
+ filesize = (long)SetFilePointer(pX->file, 0, NULL, FILE_END);
+#else // WIN32
+ filesize = (long)lseek(pX->file, 0, SEEK_END);
+#endif // WIN32
+ if (filesize < 0) // not allowed
+ {
+#ifdef STAND_ALONE
+ fputs("SendXmodem fail (file size)\n", stderr);
+#endif // STAND_ALONE
+ return -1;
+ }
+
+#ifdef WIN32
+ SetFilePointer(pX->file, 0, NULL, FILE_BEGIN);
+#else // WIN32
+ lseek(pX->file, 0, SEEK_SET); // position at beginning
+#endif // WIN32
+
+#endif // ARDUINO
+
+ do {
+ // ** depending on type of transfer, place the packet
+ // ** into pX->buf with all fields appropriately filled.
+
+ if (filepos >= filesize) // end of transfer
+ {
+ for (i1 = 0; i1 < 8; i1++) {
+ WriteXmodemChar(pX->ser,
+ _EOT_); // ** send an EOT marking end of transfer
+
+ if (GetXmodemBlock(pX->ser, &(pX->buf.xbuf.cSOH), 1) !=
+ 1) // this takes up to 5 seconds
+ {
+ // nothing returned - try again?
+ // break; // for now I loop, uncomment to bail out
+ } else if (pX->buf.xbuf.cSOH ==
+ _ENQ_ // an 'ENQ' (apparently some expect this)
+ || pX->buf.xbuf.cSOH == _ACK_ // an 'ACK' (most XMODEM
+ // implementations expect
+ // this)
+ || pX->buf.xbuf.cSOH == _CAN_) // CTRL-X = TERMINATE
+ {
+ // both normal and 'abnormal' termination.
+ break;
+ }
+ }
+
+ XmodemTerminate(pX);
+
+#if defined(STAND_ALONE) && defined(DEBUG_CODE)
+ fprintf(stderr, "SendXmodem return %d\n", i1 >= 8 ? 1 : 0);
+#endif // STAND_ALONE
+ return i1 >= 8 ? 1 : 0; // return 1 if receiver choked on the 'EOT'
+ // marker, else 0 for 'success'
+ }
+
+// TODO: progress indicator [can be LCD for arduino, blinky lights, ??? and
+// of course stderr for everyone else]
+// If filesize& <> 0 Then Form2!Label1.FloodPercent = 100 * filepos& /
+// filesize&
+
+#if defined(STAND_ALONE) || defined(SFTARDCAL)
+ fprintf(stderr,
+ "block %ld %ld of %ld bytes %d errors\r"
+#ifndef SFTARDCAL
+ "\n"
+#endif // SFTARDCAL
+ ,
+ block, filepos, filesize, ecount);
+#endif // STAND_ALONE
+
+ if (pX->buf.xbuf.cSOH != 'C' // XMODEM CRC
+ && pX->buf.xbuf.cSOH != (char)_NAK_) // NAK
+ {
+ // increase error count, bail if it's too much
+
+ ec2++;
+ }
+
+#ifdef ARDUINO
+ pX->file.seek(filepos); // in case I'm doing a 'retry' and I have to
+ // re-read part of the file
+#elif defined(WIN32)
+ SetFilePointer(pX->file, filepos, NULL, FILE_BEGIN);
+#else // ARDUINO
+ lseek(pX->file, filepos, SEEK_SET); // same reason as above
+#endif // ARDUINO
+
+ // fortunately, xbuf and xcbuf are the same through the end of 'aDataBuf' so
+ // I can read the file NOW using 'xbuf' for both CRC and CHECKSUM versions
+
+ if ((filesize - filepos) >= sizeof(pX->buf.xbuf.aDataBuf)) {
+#ifdef ARDUINO
+ i1 = pX->file.read(pX->buf.xbuf.aDataBuf, sizeof(pX->buf.xcbuf.aDataBuf));
+#elif defined(WIN32)
+ cbRead = 0;
+ if (!ReadFile(pX->file, pX->buf.xbuf.aDataBuf,
+ sizeof(pX->buf.xbuf.aDataBuf), &cbRead, NULL)) {
+ i1 = -1;
+ } else {
+ i1 = (int)cbRead;
+ }
+#else // ARDUINO
+ i1 =
+ read(pX->file, pX->buf.xbuf.aDataBuf, sizeof(pX->buf.xcbuf.aDataBuf));
+#endif // ARDUINO
+
+ if (i1 != sizeof(pX->buf.xcbuf.aDataBuf)) {
+ // TODO: read error - send a ctrl+x ?
+ }
+ } else {
+ memset(pX->buf.xcbuf.aDataBuf, '\x1a',
+ sizeof(pX->buf.xcbuf.aDataBuf)); // fill with ctrl+z which is what
+ // the spec says
+#ifdef ARDUINO
+ i1 = pX->file.read(pX->buf.xbuf.aDataBuf, filesize - filepos);
+#elif defined(WIN32)
+ cbRead = 0;
+ if (!ReadFile(pX->file, pX->buf.xbuf.aDataBuf, filesize - filepos,
+ &cbRead, NULL)) {
+ i1 = -1;
+ } else {
+ i1 = (int)cbRead;
+ }
+#else // ARDUINO
+ i1 = read(pX->file, pX->buf.xbuf.aDataBuf, filesize - filepos);
+#endif // ARDUINO
+
+ if (i1 != (filesize - filepos)) {
+ // TODO: read error - send a ctrl+x ?
+ }
+ }
+
+ if (pX->buf.xbuf.cSOH ==
+ 'C' || // XMODEM CRC 'NAK' (first time only, typically)
+ ((pX->buf.xbuf.cSOH == _ACK_ || pX->buf.xbuf.cSOH == _NAK_) &&
+ pX->bCRC)) // identifies ACK/NACK with XMODEM CRC
+ {
+ pX->bCRC = 1; // make sure (only matters the first time, really)
+
+ // calculate the CRC, assign to the packet, and then send it
+
+ pX->buf.xcbuf.cSOH = 1; // must send SOH as 1st char
+ pX->buf.xcbuf.wCRC =
+ CalcCRC(pX->buf.xcbuf.aDataBuf, sizeof(pX->buf.xcbuf.aDataBuf));
+
+ GenerateSEQC(&(pX->buf.xcbuf), (unsigned char)block);
+
+ // send it
+
+ i1 = WriteXmodemBlock(pX->ser, &(pX->buf.xcbuf), sizeof(pX->buf.xcbuf));
+ if (i1 != sizeof(pX->buf.xcbuf)) // write error
+ {
+ // TODO: handle write error (send ctrl+X ?)
+ }
+ } else if (pX->buf.xbuf.cSOH == _NAK_ || // 'NAK' (checksum method, may
+ // also be with CRC method)
+ (pX->buf.xbuf.cSOH == _ACK_ &&
+ !pX->bCRC)) // identifies ACK with XMODEM CHECKSUM
+ {
+ pX->bCRC = 0; // make sure (this ALSO allows me to switch modes on error)
+
+ // calculate the CHECKSUM, assign to the packet, and then send it
+
+ pX->buf.xbuf.cSOH = 1; // must send SOH as 1st char
+ pX->buf.xbuf.bCheckSum =
+ CalcCheckSum(pX->buf.xbuf.aDataBuf, sizeof(pX->buf.xbuf.aDataBuf));
+
+ GenerateSEQ(&(pX->buf.xbuf), (unsigned char)block);
+
+ // send it
+
+ i1 = WriteXmodemBlock(pX->ser, &(pX->buf.xbuf), sizeof(pX->buf.xbuf));
+ if (i1 != sizeof(pX->buf.xbuf)) // write error
+ {
+ // TODO: handle write error (send ctrl+X ?)
+ }
+ }
+
+ ec2 = 0;
+
+ while (ecount < TOTAL_ERROR_COUNT &&
+ ec2 < ACK_ERROR_COUNT) // loop to get ACK or NACK
+ {
+ if (GetXmodemBlock(pX->ser, &(pX->buf.xbuf.cSOH), 1) == 1) {
+ if (pX->buf.xbuf.cSOH == _CAN_) // ** CTRL-X - terminate
+ {
+ XmodemTerminate(pX);
+
+ return 1; // terminated
+ } else if (pX->buf.xbuf.cSOH == _NAK_ || // ** NACK
+ pX->buf.xbuf.cSOH == 'C') // ** CRC NACK
+ {
+ break; // exit inner loop and re-send packet
+ } else if (pX->buf.xbuf.cSOH == _ACK_) // ** ACK - sending next packet
+ {
+ filepos += sizeof(pX->buf.xbuf.aDataBuf);
+ block++; // increment file position and block count
+
+ break; // leave inner loop, send NEXT packet
+ } else {
+ XModemFlushInput(pX->ser); // for now, do this here too
+ ec2++;
+ }
+ } else {
+ ecount++; // increase total error count, then loop back and re-send
+ // packet
+ break;
+ }
+ }
+
+ if (ec2 >= ACK_ERROR_COUNT) {
+ break; // that's it, I'm done with this
+ }
+
+ } while (
+ ecount <
+ TOTAL_ERROR_COUNT /* * 2 */); // twice error count allowed for sending
+
+ // TODO: progress indicator
+ // If filesize& <> 0 And filepos& <= filesize& Then
+ // Form2!Label1.FloodPercent = 100 * filepos& / filesize&
+ // Else
+ // Form2!Label1.FloodPercent = 100
+ // End If
+
+ // ** at this point it is important to indicate the errors
+ // ** and flush all buffers, and terminate process!
+
+ XmodemTerminate(pX);
+#ifdef STAND_ALONE
+ fputs("SendXmodem fail (total error count)\n", stderr);
+#endif // STAND_ALONE
+ return -2; // exit on error
+}
+
+/** \ingroup xmodem_internal
+ * \brief Calling function for ReceiveXmodem
+ *
+ * \param pX A pointer to an 'XMODEM_BUF' with valid ser, and file members
+ * \return A zero value on success, negative on error, positive on cancel
+ *
+ * This is a generic 'calling function' for ReceiveXmodem that checks for
+ * a response to 'C' and 'NAK' characters, and sets up the XMODEM transfer
+ * for either CRC or CHECKSUM mode.\n
+ * This function will return zero on success, a negative value on error, and a
+ *positive
+ * value if the transfer was canceled by the receiver.
+**/
+int XReceiveSub(XMODEM* pX) {
+ int i1;
+
+ // start with CRC mode [try 8 times to get CRC]
+
+ pX->bCRC = 1;
+
+ for (i1 = 0; i1 < 8; i1++) {
+ WriteXmodemChar(pX->ser, 'C'); // start with NAK for XMODEM CRC
+
+ if (GetXmodemBlock(pX->ser, &(pX->buf.xbuf.cSOH), 1) == 1) {
+ if (pX->buf.xbuf.cSOH == _SOH_) // SOH - packet is on its way
+ {
+ return ReceiveXmodem(pX);
+ } else if (pX->buf.xbuf.cSOH ==
+ _EOT_) // an EOT [blank file? allow this?]
+ {
+ return 0; // for now, do this
+ } else if (pX->buf.xbuf.cSOH == _CAN_) // cancel
+ {
+ return 1; // canceled
+ }
+ }
+ }
+
+ pX->bCRC = 0;
+
+ // try again, this time using XMODEM CHECKSUM
+ for (i1 = 0; i1 < 8; i1++) {
+ WriteXmodemChar(pX->ser, _NAK_); // switch to NAK for XMODEM Checksum
+
+ if (GetXmodemBlock(pX->ser, &(pX->buf.xbuf.cSOH), 1) == 1) {
+ if (pX->buf.xbuf.cSOH == _SOH_) // SOH - packet is on its way
+ {
+ return ReceiveXmodem(pX);
+ } else if (pX->buf.xbuf.cSOH ==
+ _EOT_) // an EOT [blank file? allow this?]
+ {
+ return 0; // for now, do this
+ } else if (pX->buf.xbuf.cSOH == _CAN_) // cancel
+ {
+ return 1; // canceled
+ }
+ }
+ }
+
+ XmodemTerminate(pX);
+
+ return -3; // fail
+}
+
+/** \ingroup xmodem_internal
+ * \brief Calling function for SendXmodem
+ *
+ * \param pX A pointer to an 'XMODEM_BUF' with valid ser, and file members
+ * \return A zero value on success, negative on error, positive on cancel
+ *
+ * This is a generic 'calling function' for SendXmodem that checks for polls by
+ *the
+ * receiver, and places the 'NAK' or 'C' character into the 'buf' member of the
+ *XMODEM
+ * structure so that SendXmodem can use the correct method, either CRC or
+ *CHECKSUM mode.\n
+ * This function will return zero on success, a negative value on error, and a
+ *positive
+ * value if the transfer was canceled by the receiver.
+**/
+int XSendSub(XMODEM* pX) {
+ unsigned long ulStart;
+
+// waiting up to 30 seconds for transfer to start. this is part of the spec?
+
+#ifdef ARDUINO
+ ulStart = millis();
+#else // ARDUINO
+ ulStart = MyMillis();
+#endif // ARDUINO
+
+ do {
+ if (GetXmodemBlock(pX->ser, &(pX->buf.xbuf.cSOH), 1) == 1) {
+ if (pX->buf.xbuf.cSOH == 'C' || // XMODEM CRC
+ pX->buf.xbuf.cSOH == _NAK_) // NAK - XMODEM CHECKSUM
+ {
+#if defined(STAND_ALONE) && defined(DEBUG_CODE)
+ fprintf(stderr, "Got %d, continuing\n", pX->buf.xbuf.cSOH);
+#endif // STAND_ALONE
+ return SendXmodem(pX);
+ } else if (pX->buf.xbuf.cSOH == _CAN_) // cancel
+ {
+#ifdef STAND_ALONE
+ fputs("XSendSub fail (cancel)\n", stderr);
+#endif // STAND_ALONE
+ return 1; // canceled
+ }
+ }
+ }
+#ifdef ARDUINO
+ while ((short)(millis() - ulStart) < 30000); // 30 seconds
+#else // ARDUINO
+ while ((int)(MyMillis() - ulStart) < 30000);
+#endif // ARDUINO
+
+ XmodemTerminate(pX);
+
+#ifdef STAND_ALONE
+ fputs("XSendSub fail (timeout)\n", stderr);
+#endif // STAND_ALONE
+ return -3; // fail
+}
+
+// typedef struct _XMODEM_
+//{
+// SERIAL_TYPE ser;
+// FILE_TYPE file;
+//
+// union
+// {
+// XMODEM_BUF xbuf;
+// XMODEMC_BUF xcbuf;
+// } buf; // 133 bytes
+//
+// unsigned char bCRC; // non-zero for CRC, zero for checksum
+//
+//} __attribute__((__packed__)) XMODEM;
+
+#ifdef ARDUINO
+
+short XReceive(SDClass* pSD, HardwareSerial* pSer, const char* szFilename) {
+ short iRval;
+ XMODEM xx;
+
+ memset(&xx, 0, sizeof(xx));
+
+ xx.ser = pSer;
+
+ if (pSD->exists((char*)szFilename)) {
+ pSD->remove((char*)szFilename);
+ }
+
+ xx.file = pSD->open((char*)szFilename, FILE_WRITE);
+ if (!xx.file) {
+ return -9; // can't create file
+ }
+
+ iRval = XReceiveSub(&xx);
+
+ xx.file.close();
+
+ if (iRval) {
+ WriteXmodemChar(pSer, _CAN_); // cancel (make sure)
+
+ pSD->remove((char*)szFilename); // delete file on error
+ }
+
+ return iRval;
+}
+
+int XSend(SDClass* pSD, HardwareSerial* pSer, const char* szFilename) {
+ short iRval;
+ XMODEM xx;
+
+ memset(&xx, 0, sizeof(xx));
+
+ xx.ser = pSer;
+
+ xx.file = pSD->open(szFilename, FILE_READ);
+ if (!xx.file) {
+ return -9; // can't open file
+ }
+
+ iRval = XSendSub(&xx);
+
+ xx.file.close();
+
+ return iRval;
+}
+
+#else // ARDUINO
+
+int XReceive(SERIAL_TYPE hSer, const char* szFilename, int nMode) {
+ int iRval;
+ XMODEM xx;
+#if !defined(ARDUINO) && !defined(WIN32)
+ int iFlags;
+#endif // !ARDUINO
+
+#ifdef DEBUG_CODE
+ szERR[0] = 0;
+#endif // DEBUG_CODE
+ memset(&xx, 0, sizeof(xx));
+
+ xx.ser = hSer;
+
+#ifdef WIN32
+ DeleteFile(szFilename);
+
+ nMode = nMode; // to avoid unused parameter warnings
+ // TODO: translate 'nMode' into file attributes? for now ignore it
+ xx.file = CreateFile(szFilename, GENERIC_READ | GENERIC_WRITE, 0, NULL,
+ CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (xx.file == INVALID_HANDLE_VALUE)
+#else // WIN32
+ unlink(szFilename); // make sure it does not exist, first
+ xx.file = open(szFilename, O_CREAT | O_TRUNC | O_WRONLY, nMode);
+
+ if (xx.file == -1) // bad file handle on POSIX systems
+#endif // WIN32
+ {
+#ifdef STAND_ALONE
+ fprintf(stderr, "XReceive fail \"%s\" errno=%d\n", szFilename, errno);
+#endif // STAND_ALONE
+ return -9; // can't create file
+ }
+
+#if !defined(ARDUINO) && !defined(WIN32)
+ iFlags = fcntl(hSer, F_GETFL);
+#endif // !ARDUINO
+
+ iRval = XReceiveSub(&xx);
+
+#if !defined(ARDUINO) && !defined(WIN32)
+ if (iFlags == -1 || fcntl(hSer, F_SETFL, iFlags) == -1) {
+ fprintf(stderr,
+ "Warning: 'fcntl' call to restore flags failed, errno=%d\n",
+ errno);
+ }
+#endif // !ARDUINO
+
+#ifdef WIN32
+ CloseHandle(xx.file);
+#else // WIN32
+ close(xx.file);
+#endif // WIN32
+
+ if (iRval) {
+#ifdef WIN32
+ DeleteFile(szFilename);
+#else // WIN32
+ unlink(szFilename); // delete file on error
+#endif // WIN32
+ }
+
+#if defined(STAND_ALONE) && defined(DEBUG_CODE)
+ fprintf(stderr, "XReceive returns %d\n", iRval);
+#endif // STAND_ALONE
+ return iRval;
+}
+
+int XSend(SERIAL_TYPE hSer, const char* szFilename) {
+ int iRval;
+ XMODEM xx;
+#if !defined(ARDUINO) && !defined(WIN32)
+ int iFlags;
+#endif // !ARDUINO
+
+#ifdef DEBUG_CODE
+ szERR[0] = 0;
+#endif // DEBUG_CODE
+ memset(&xx, 0, sizeof(xx));
+
+ xx.ser = hSer;
+
+#ifdef WIN32
+ xx.file =
+ CreateFile(szFilename, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
+
+ if (xx.file == INVALID_HANDLE_VALUE)
+#else // WIN32
+ xx.file = open(szFilename, O_RDONLY, 0);
+
+ if (xx.file == -1) // bad file handle on POSIX systems
+#endif // WIN32
+ {
+#ifdef STAND_ALONE
+ fprintf(stderr, "XSend fail \"%s\" errno=%d\n", szFilename, errno);
+#endif // STAND_ALONE
+ return -9; // can't open file
+ }
+
+#if !defined(ARDUINO) && !defined(WIN32)
+ iFlags = fcntl(hSer, F_GETFL);
+#endif // !ARDUINO
+
+ iRval = XSendSub(&xx);
+
+#if !defined(ARDUINO) && !defined(WIN32)
+ if (iFlags == -1 || fcntl(hSer, F_SETFL, iFlags) == -1) {
+ fprintf(stderr,
+ "Warning: 'fcntl' call to restore flags failed, errno=%d\n",
+ errno);
+ }
+#endif // !ARDUINO
+
+#ifdef WIN32
+ CloseHandle(xx.file);
+#else // WIN32
+ close(xx.file);
+#endif // WIN32
+
+#if defined(STAND_ALONE) && defined(DEBUG_CODE)
+ fprintf(stderr, "XSend returning %d\n", iRval);
+#endif // STAND_ALONE
+ return iRval;
+}
+
+#endif // ARDUINO
+
+#if defined(STAND_ALONE) && !defined(SFTARDCAL)
+
+static const char szSER[] = "/dev/ttyU0";
+
+#include <termios.h>
+
+/** \ingroup xmodem_standalone
+ * \brief Terminal configuration (POSIX only)
+ *
+ * \param iFile The open file handle for the serial connection
+ * \param iBaud The baud rate for the connection
+ * \param iParity The parity, < 0 for even, > 0 for odd, 0 for none
+ * \param iBits The number of bits (5, 6, 7, 8)
+ * \param iStop The number of stop bits (1 or 2)
+ *
+ * This is a sample tty config function to CORRECTLY set up a serial connection
+ * to allow XMODEM transfer. The important details here are the use of the
+ * 'termios' structure and utility functions to DISABLE all of the things that
+ * would otherwise cause trouble, like CRLF translation, CTRL+C handling, etc.
+**/
+void ttyconfig(int iFile, int iBaud, int iParity, int iBits, int iStop) {
+ int i1;
+ struct termios sIOS;
+
+ i1 = fcntl(iFile, F_GETFL);
+
+ i1 |= O_NONBLOCK; // i1 &= ~O_NONBLOCK); // turn OFF non-blocking?
+
+ fcntl(iFile, F_SETFL, i1);
+
+ if (!tcgetattr(iFile, &sIOS)) {
+ cfsetspeed(&sIOS, iBaud);
+ sIOS.c_cflag &= ~(CSIZE | PARENB | CS5 | CS6 | CS7 | CS8);
+ sIOS.c_cflag |=
+ iBits == 5 ? CS5 : iBits == 6 ? CS6 : iBits == 7 ? CS7
+ : CS8; // 8 is default
+ if (iStop == 2) {
+ sIOS.c_cflag |= CSTOPB;
+ } else {
+ sIOS.c_cflag &= ~CSTOPB;
+ }
+
+ sIOS.c_cflag &=
+ ~CRTSCTS; // hardware flow control _DISABLED_ (so I can do the reset)
+ sIOS.c_cflag |= CLOCAL; // ignore any modem status lines
+
+ if (!iParity) {
+ sIOS.c_cflag &= ~(PARENB | PARODD);
+ } else if (iParity > 0) // odd
+ {
+ sIOS.c_cflag |= (PARENB | PARODD);
+ } else // even (negative)
+ {
+ sIOS.c_cflag &= PARODD;
+ sIOS.c_cflag |= PARENB;
+ }
+
+ // sIOS.c_iflag |= IGNCR; // ignore CR
+
+ // do not translate characters or xon/xoff and ignore break
+ sIOS.c_iflag &= ~(IGNBRK | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY |
+ IMAXBEL | ISTRIP); // turn these off
+
+#if defined(__FreeBSD__)
+ sIOS.c_oflag &= ~(OPOST | ONLCR | OCRNL | TABDLY | ONOEOT | ONOCR |
+ ONLRET); // FreeBSD version
+#else // Linux? YMMV
+ sIOS.c_oflag &= ~(OPOST | ONLCR | OCRNL | TABDLY | ONOCR |
+ ONLRET); // turn these off too (see man termios)
+#endif // FBSD vs Linux
+
+// make sure echoing is disabled and control chars aren't translated or omitted
+#if defined(__FreeBSD__)
+ sIOS.c_lflag &= ~(ECHO | ECHOKE | ECHOE | ECHONL | ECHOPRT | ECHOCTL |
+ ICANON | IEXTEN | ISIG | ALTWERASE);
+#else // Linux? YMMV
+ sIOS.c_lflag &= ~(ECHO | ECHOKE | ECHOE | ECHONL | ECHOPRT | ECHOCTL |
+ ICANON | IEXTEN | ISIG);
+#endif // FBSD vs Linux
+ sIOS.c_cc[VMIN] = 0; // ensures no 'grouping' of input
+ sIOS.c_cc[VTIME] = 0; // immediate return
+
+ if (tcsetattr(iFile, TCSANOW, &sIOS)) {
+ fprintf(stderr, "error %d setting attributes\n", errno);
+ }
+ } else {
+ fprintf(stderr, "error %d getting attributes\n", errno);
+ }
+}
+
+/** \ingroup xmodem_standalone
+ * \brief Arduino 'reset' function
+ *
+ * \param iFile The open file handle for the serial connection
+ *
+ * The Arduino serial port typically has the DTR/RTS lines configured so that
+ * a proper 'pulse' will cause a hardware reset of the device. This function
+ *will
+ * send that pulse to the Arduino, and wait for a short time afterwards for the
+ * hardware reset to take place.
+**/
+void reset_arduino(int iFile) {
+ unsigned int sFlags;
+ unsigned long ulStart;
+ int i1;
+
+ // toggle the RTS and DTR high, low, then high - so much easier via
+ // POSIX-compatible OS!
+
+ ioctl(iFile, TIOCMGET, &sFlags);
+
+ sFlags &= ~(TIOCM_DTR | TIOCM_RTS); // the high to low transition discharges
+ // the capacitor (signal is inverted on
+ // board)
+ if (ioctl(iFile, TIOCMSET, &sFlags) < 0) {
+ fprintf(stderr, "WARNING: ioctl() returns < 0, errno=%d (%xH)\n", errno,
+ errno);
+ }
+
+ usleep(
+ 250000); // avrdude does this for 50 msecs, my change has it at 50msecs
+
+ sFlags |= TIOCM_DTR | TIOCM_RTS; // leave it in THIS state when I'm done
+ if (ioctl(iFile, TIOCMSET, &sFlags) < 0) {
+ fprintf(stderr, "WARNING: ioctl() returns < 0, errno=%d (%xH)\n", errno,
+ errno);
+ }
+
+ usleep(50000); // avrdude does this for 50 msecs (no change)
+
+ ulStart = MyMillis();
+
+ // flush whatever is there, (5 seconds)
+
+ while ((MyMillis() - ulStart) < 5000) {
+ i1 = read(iFile, &i1, 1);
+ if (i1 == 1) {
+ ulStart = MyMillis();
+ } else {
+ usleep(1000);
+ }
+ }
+}
+
+int main(int argc, char* argv[]) {
+ int hSer;
+ char tbuf[256];
+ int i1, iSR = 0;
+
+ if (argc < 3) {
+ fputs("Usage: [prog] [S|R] filename\n", stderr);
+ return 1;
+ }
+
+ if (argv[1][0] == 'R' || argv[1][1] == 'r') {
+ iSR = -1;
+ } else if (argv[1][0] == 'S' || argv[1][1] == 's') {
+ iSR = 1;
+ } else if (argv[1][0] == 'X' || argv[1][1] == 'x') {
+ iSR = 0; // test function
+ } else {
+ fputs("Usage: [prog] [S|R] filename (b)\n", stderr);
+ return 1;
+ }
+
+ hSer = open(szSER, (O_RDWR | O_NONBLOCK), 0);
+ if (hSer == -1) {
+ fprintf(stderr, "Unable to open \"%s\" errno=%d\n", szSER, errno);
+ return 3;
+ }
+
+ fputs("TTYCONFIG\n", stderr);
+ ttyconfig(hSer, 9600, 0, 8, 1);
+
+ reset_arduino(hSer);
+
+ fprintf(stderr, "Sleeping for 10 seconds to allow reset\n");
+
+ // usleep(10000000);
+ for (i1 = 0; i1 < 10; i1++) {
+ XModemFlushInput(hSer);
+ }
+
+ for (i1 = 0; i1 < 3; i1++) {
+ sprintf(tbuf, "X%c%s", argv[1][0], argv[2]);
+
+ fprintf(stderr, "writing: \"%s\"\n", tbuf);
+ strcat(tbuf, "\r");
+ WriteXmodemBlock(hSer, tbuf, strlen(tbuf));
+
+ fputs("flush input\n", stderr);
+ XModemFlushInput(hSer);
+
+ // wait for an LF response
+
+ if (iSR > 0) {
+ fputs("XSEND\n", stderr);
+ if (XSend(hSer, argv[2])) {
+ fputs("ERROR\n", stderr);
+ } else {
+ fputs("SUCCESS!\n", stderr);
+ i1 = 0;
+ break;
+ }
+ } else if (iSR < 0) {
+ fputs("XRECEIVE\n", stderr);
+ if (XReceive(hSer, argv[2], 0664)) {
+ fputs("ERROR\n", stderr);
+ } else {
+ fputs("SUCCESS!\n", stderr);
+ i1 = 0;
+ break;
+ }
+ } else {
+ // test function
+ XModemFlushInput(hSer); // continue doing this
+ break; // done (once only)
+ }
+ }
+
+ fputs("EXIT\n", stderr);
+ close(hSer);
+
+ return i1 ? 1 : -1;
+}
+#endif // STAND_ALONE