diff options
author | Nick Van Doorn <vandoorn.nick@gmail.com> | 2018-09-10 23:23:21 -0700 |
---|---|---|
committer | Nick Van Doorn <vandoorn.nick@gmail.com> | 2018-09-10 23:23:21 -0700 |
commit | 83639bdffb6b9355a29d2b2e55807aa3b6837bbd (patch) | |
tree | e5858b146a3901880e5babaa89aacbe0979f0552 |
Initial commit
-rw-r--r-- | mtkSetup.adef | 27 | ||||
-rw-r--r-- | upload/Component.cdef | 22 | ||||
-rw-r--r-- | upload/crc16.c | 69 | ||||
-rw-r--r-- | upload/crc16.h | 33 | ||||
-rw-r--r-- | upload/upload.c | 161 | ||||
-rw-r--r-- | upload/xmodem.c | 1879 | ||||
-rw-r--r-- | upload/xmodem.h | 310 |
7 files changed, 2501 insertions, 0 deletions
diff --git a/mtkSetup.adef b/mtkSetup.adef new file mode 100644 index 0000000..c376de4 --- /dev/null +++ b/mtkSetup.adef @@ -0,0 +1,27 @@ +sandboxed: false +start: manual +version: 2.14.0 + +executables: +{ + mtkSetup = ( upload ) +} + +processes: +{ + run: + { + ( mtkSetup ) + } + faultAction: restartApp + envVars: + { + LE_LOG_LEVEL = DEBUG + } +} + +bindings: +{ + mtkSetup.upload.mtRst -> gpioService.le_gpioPin2 + mtkSetup.upload.mtBootstrap -> gpioService.le_gpioPin24 +} diff --git a/upload/Component.cdef b/upload/Component.cdef new file mode 100644 index 0000000..b7d57eb --- /dev/null +++ b/upload/Component.cdef @@ -0,0 +1,22 @@ +cflags: +{ + -std=gnu99 + -I$BRNKL_ROOT/apps/util + -Wno-unused-but-set-variable +} + +requires: +{ + api: + { + mtRst = le_gpio.api + mtBootstrap = le_gpio.api + } +} + +sources: +{ + upload.c + xmodem.c + $BRNKL_ROOT/apps/util/util.c +} diff --git a/upload/crc16.c b/upload/crc16.c new file mode 100644 index 0000000..3e27ecd --- /dev/null +++ b/upload/crc16.c @@ -0,0 +1,69 @@ +/* + * Copyright 2001-2010 Georges Menie (www.menie.org) + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the University of California, Berkeley nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "crc16.h" + +/* CRC16 implementation acording to CCITT standards */ + +static const unsigned short crc16tab[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, + 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, + 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, + 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, + 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, + 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, + 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, + 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, + 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, + 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, + 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, + 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, + 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, + 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, + 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, + 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, + 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, + 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, + 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, + 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, + 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, + 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, + 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, + 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, + 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, + 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0}; + +unsigned short crc16_ccitt(const void* buf, int len) { + register int counter; + register unsigned short crc = 0; + for (counter = 0; counter < len; counter++) + crc = (crc << 8) ^ crc16tab[((crc >> 8) ^ *(char*)buf++) & 0x00FF]; + return crc; +} diff --git a/upload/crc16.h b/upload/crc16.h new file mode 100644 index 0000000..3140de5 --- /dev/null +++ b/upload/crc16.h @@ -0,0 +1,33 @@ +/* + * Copyright 2001-2010 Georges Menie (www.menie.org) + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the University of California, Berkeley nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _CRC16_H_ +#define _CRC16_H_ + +unsigned short crc16_ccitt(const void* buf, int len); + +#endif /* _CRC16_H_ */ diff --git a/upload/upload.c b/upload/upload.c new file mode 100644 index 0000000..a18244e --- /dev/null +++ b/upload/upload.c @@ -0,0 +1,161 @@ +#include "legato.h" +#include "interfaces.h" +#include "xmodem.h" +#include "util.h" + +#define MTK7697_BAUD 115200 +#define MTK7697_BAUDX 115200 * 8 +static const char* SERIAL_PORT_PATH = "/dev/ttyHS0"; +static const char* LDR_BIN_PATH = "/home/root/mtfiles/mt7697_bootloader.bin"; +static const char* N9_BIN_PATH = + "/home/root/mtfiles/WIFI_RAM_CODE_MT76X7_in_flash.bin"; +static const char* CM4_BIN_PATH = "/home/root/mtfiles/ble_smart_connect.bin"; +static const char* DA_PATH = "/home/root/mtfiles/da97.bin"; + +typedef enum { LDR = 1, N9 = 3, CM4 = 2 } MtkMemorySegment; + +typedef struct { + int serialPort; + int errorCount; + int cCount; + int retry; + int startTime; +} FlashState; + +FlashState state = {0, 0, 0, 0}; + +void configureSerialPort(FlashState* s, int baud) { + close(s->serialPort); + s->serialPort = fd_openSerial(SERIAL_PORT_PATH, baud); +} + +bool flashDa(FlashState* s) { + configureSerialPort(&state, MTK7697_BAUD); + LE_INFO("Sending DA over xmodem"); + bool r = XSend(s->serialPort, DA_PATH) == 0; + LE_INFO("DA success: %d", r); + return r; +} + +void putIntoBootloader() { + // make sure the bootstrap pin is on + mtBootstrap_Activate(); + // reset state && wait + mtRst_Deactivate(); + sleep(1); + // pull out of reset + mtRst_Activate(); +} + +void retryInitSequence(FlashState* s) { + s->retry++; + s->cCount = 0; + s->errorCount = 0; + s->startTime = util_getUnixDatetime(); + close(s->serialPort); + putIntoBootloader(); + configureSerialPort(s, MTK7697_BAUD); +} + +bool verifyInitSequence(FlashState* s) { + uint8_t data; + bool initDone = false; + s->startTime = util_getUnixDatetime(); + while (!initDone) { + data = fd_getChar(s->serialPort); + fd_flush(s->serialPort); + if (data == 'C') { + s->cCount++; + LE_INFO("Got a C"); + } else if (data != 0) { + s->errorCount++; + LE_INFO("Got an error"); + } + if (s->cCount > 1) { + initDone = true; + LE_INFO("Init done"); + break; + } + if (s->errorCount > 3 || (util_getUnixDatetime() - s->startTime > 3)) { + LE_INFO("Retrying..."); + retryInitSequence(s); + } + if (s->retry > 3) { + LE_INFO("Aborting"); + break; + } + } + return initDone; +} + +bool flashBinary(FlashState* s, MtkMemorySegment segment, const char* binPath) { + putIntoBootloader(); + configureSerialPort(s, MTK7697_BAUD); + bool init = verifyInitSequence(s); + LE_INFO("Init verified: %d", init); + flashDa(s); + configureSerialPort(s, MTK7697_BAUDX); + sleep(1); + bool initDone = false; + int data = '0'; + switch (segment) { + case LDR: + fd_puts(s->serialPort, "1\r"); + break; + case N9: + fd_puts(s->serialPort, "3\r"); + break; + case CM4: + fd_puts(s->serialPort, "2\r"); + break; + } + while (!initDone) { + LE_INFO("here %c", data); + data = fd_getChar(s->serialPort); + if (data == 'C') { + initDone = true; + break; + } + } + fd_flush(s->serialPort); + LE_INFO("Sending %s over xmodem", binPath); + bool r = XSend(s->serialPort, binPath) == 0; + LE_INFO("%s success: %d", binPath, r); + sleep(1); + fd_puts(s->serialPort, "C\r"); + fd_flush(s->serialPort); + return true; +} + +bool flashLdr(FlashState* s) { + LE_INFO("Flashing LDR segment"); + return flashBinary(s, LDR, LDR_BIN_PATH); +} + +bool flashN9(FlashState* s) { + LE_INFO("Flashing N9 segment"); + return flashBinary(s, N9, N9_BIN_PATH); +} + +bool flashCm4(FlashState* s) { + LE_INFO("Flashing CM4"); + return flashBinary(s, CM4, CM4_BIN_PATH); +} + +void resetPins() { + mtBootstrap_Deactivate(); +} + +void configureGpio() { + mtRst_SetPushPullOutput(MTRST_ACTIVE_HIGH, true); + mtBootstrap_SetPushPullOutput(MTBOOTSTRAP_ACTIVE_HIGH, true); +} + +COMPONENT_INIT { + configureGpio(); + flashLdr(&state); + flashN9(&state); + flashCm4(&state); + resetPins(); + close(state.serialPort); +} 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 diff --git a/upload/xmodem.h b/upload/xmodem.h new file mode 100644 index 0000000..37bed1a --- /dev/null +++ b/upload/xmodem.h @@ -0,0 +1,310 @@ +#ifndef XMODEM_H +#define XMODEM_H + +////////////////////////////////////////////////////////////////////////////// +// // +// _ _ // +// __ __ _ __ ___ ___ __| | ___ _ __ ___ | |__ // +// \ \/ /| '_ ` _ \ / _ \ / _` | / _ \| '_ ` _ \ | '_ \ // +// > < | | | | | || (_) || (_| || __/| | | | | | _ | | | | // +// /_/\_\|_| |_| |_| \___/ \__,_| \___||_| |_| |_|(_)|_| |_| // +// // +// // +////////////////////////////////////////////////////////////////////////////// +// // +// Copyright (c) 2012 by S.F.T. Inc. - All rights reserved // +// Use, copying, and distribution of this software are licensed according // +// to the LGPLv2.1, or a BSD-like license, as appropriate (see below) // +// // +////////////////////////////////////////////////////////////////////////////// + +#ifdef ARDUINO +/** \mainpage S.F.T. XMODEM library (ARDUINO version) + * + * Copyright (c) 2012 by S.F.T. Inc. - All rights reserved\n + * + * The source files include DOXYGEN SUPPORT to properly document the library + * Please excuse the additional comments necessary to make this work. + * Instead, build the doxygen output and view the documentation, as + * well as the code itself WITHOUT all of the doxygen markup comments. + * \n + * \n + * This library was designed to work with POSIX-compliant operating systems + * such as Linux, FreeBSD, and OSX, and also on Arduino microcontrollers. + * The intent was to provide an identical code base for both ends of the + * XMODEM transfer, compilable as either C or C++ code for maximum flexibility. + * + * Normally you will only need to use one of these two functions:\n + * \n + * \ref XSend() - send a file via XMODEM\n + * \ref XReceive() - receive a file via XMODEM\n + * \n + * The rest of the documentation was provided to help you debug any problems, + * or even to write your own library (as appropriate).\n + * + * LICENSE + * + * This software is licensed under either the LGPLv2 or a BSD-like license. + * For more information, see\n + * http://opensource.org/licenses/BSD-2-Clause\n + * http://www.gnu.org/licenses/lgpl-2.1.html\n + * and the above copyright notice.\n + * \n + * In short, you may use this software anyway you like, provided that you + * do not hold S.F.T. Inc. responsible for consequential or inconsequential + * damages resulting from use, modification, abuse, or anything else done + * with this software, and you include the appropriate license (either LGPLv2 + * or a BSD-like license) and comply with the requirements of said license.\n + * So, if you use a BSD-like license, you can copy the license template at + * the abovementioned URL and sub in the copyright notice as shown above. + * Or, you may use an LGPLv2 license, and then provide source files with a + * re-distributed or derived work (including a complete re-write with this + * library as a template). A link back to the original source, of course, + * would be appreciated but is not required. +**/ +#else // ARDUINO +/** \mainpage S.F.T. XMODEM library + * + * Copyright (c) 2012 by S.F.T. Inc. - All rights reserved\n + * + * The source files include DOXYGEN SUPPORT to properly document the library + * Please excuse the additional comments necessary to make this work. + * Instead, build the doxygen output and view the documentation, as + * well as the code itself WITHOUT all of the doxygen markup comments. + * \n + * \n + * This library was designed to work with POSIX-compliant operating systems + * such as Linux, FreeBSD, and OSX, and also on Arduino microcontrollers. + * The intent was to provide an identical code base for both ends of the + * XMODEM transfer, compilable as either C or C++ code for maximum flexibility. + * + * Normally you will only need to use one of these two functions:\n + * \n + * \ref XSend() - send a file via XMODEM\n + * \ref XReceive() - receive a file via XMODEM\n + * \n + * The rest of the documentation was provided to help you debug any problems, + * or even to write your own library (as appropriate).\n + * + * LICENSE + * + * This software is licensed under either the LGPLv2 or a BSD-like license. + * For more information, see\n + * http://opensource.org/licenses/BSD-2-Clause\n + * http://www.gnu.org/licenses/lgpl-2.1.html\n + * and the above copyright notice.\n + * \n + * In short, you may use this software anyway you like, provided that you + * do not hold S.F.T. Inc. responsible for consequential or inconsequential + * damages resulting from use, modification, abuse, or anything else done + * with this software, and you include the appropriate license (either LGPLv2 + * or a BSD-like license) and comply with the requirements of said license.\n + * So, if you use a BSD-like license, you can copy the license template at + * the abovementioned URL and sub in the copyright notice as shown above. + * Or, you may use an LGPLv2 license, and then provide source files with a + * re-distributed or derived work (including a complete re-write with this + * library as a template). A link back to the original source, of course, + * would be appreciated but is not required. +**/ +#endif // ARDUINO + +/** \file xmodem.h + * \brief main header file for S.F.T. XMODEM library + * + * S.F.T. XMODEM library +**/ + +/** \defgroup xmodem_api XModem API + * high-level API functions +*/ + +/** \defgroup xmodem_internal XModem Internal + * internal support functions +*/ + +#ifdef STANDALONE +/** \defgroup xmodem_standalone XModem Stand-alone + * internal 'standalone' functions, an example for POSIX implementation +*/ +#endif // STANDALONE + +// determine if arduino build, define ARDUINO if not already done + +#if defined(__AVR__) || defined(AVR) || defined(__AVR) || defined(__AVR_ARCH__) +#ifndef ARDUINO +#define ARDUINO /* hopefully I cover all compiler variations */ +#endif // ARDUINO +#endif // __AVR__ + +#include <stdlib.h> + +// required include files +#ifdef ARDUINO +// arduino includes +#include <Arduino.h> +#include <SD.h> +#include <HardwareSerial.h> /* may already be included by 'Arduino.h' */ +#include <avr/pgmspace.h> + +#elif WIN32 +// win32 includes +#include <Windows.h> +#include <io.h> +#else // POSIX +// posix includes +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/time.h> +#include <sys/ioctl.h> // for IOCTL definitions +#include <memory.h> +#endif // OS-dependent includes + +// required per-OS definitions +#ifdef ARDUINO + +// file and serial types for Arduino +#define FILE_TYPE File +#define SERIAL_TYPE HardwareSerial* + +#elif defined(WIN32) // WINDOWS + +// file and serial types for WIN32 +#define FILE_TYPE HANDLE +#define SERIAL_TYPE HANDLE + +#else // POSIX + +// file and serial types for POSIX +#define FILE_TYPE int +#define SERIAL_TYPE int + +#endif // ARDUINO + +// common definitions + +#define SILENCE_TIMEOUT 5000 /* 5 seconds */ +#define TOTAL_ERROR_COUNT 32 +#define ACK_ERROR_COUNT 8 + +// Arduino build uses C++ so I must define functions properly + +#ifdef ARDUINO + +/** \ingroup xmodem_api + * \brief Receive a file using XMODEM protocol (ARDUINO version) + * + * \param pSD A pointer to an SDClass object, such as &SD (the default SD + *library object is 'SD') + * \param pSer A pointer to a HardwareSerial object, such as &Serial + * \param szFilename A pointer to a (const) 0-byte terminated string containing + *the file name + * \return A value of zero on success, negative on failure, positive if + *canceled + * + * Call this function to receive a file, passing the SD card's initialized + *SDClass object pointer, + * and the pointer to the 'HardwareSerial' object to be used for serial + *communication, and the + * name of the file to create from the XMODEM stream. The function will return + *a value of zero on + * success. On failure or cancelation, the file will be deleted.\n + * If the specified file exists before calling this function, it will be + *overwritten. If you do not + * want to unconditionally overwrite an existing file, you should test to see + *if it exists first + * using the SD library. + * +**/ +short XReceive(SDClass* pSD, HardwareSerial* pSer, const char* szFilename); + +/** \ingroup xmodem_api + * \brief Send a file using XMODEM protocol (ARDUINO version) + * + * \param pSD A pointer to an SDClass object, such as &SD (the default SD + *library object is 'SD') + * \param pSer A pointer to a HardwareSerial object, such as &Serial + * \param szFilename A pointer to a (const) 0-byte terminated string containing + *the file name + * \return A value of zero on success, negative on failure, positive if + *canceled + * + * Call this function to send a file, passing the SD card's initialized SDClass + *object pointer, + * and the pointer to the 'HardwareSerial' object to be used for serial + *communication, and the + * name of the file to send via the XMODEM stream. The function will return a + *value of zero on + * success. If the file does not exist, the function will return a 'failure' + *value and cancel + * the transfer. + * +**/ +int XSend(SDClass* pSD, HardwareSerial* pSer, const char* szFilename); + +#ifdef DEBUG_CODE +const char* XMGetError(void); +#endif // DEBUG_CODE + +#else // ARDUINO + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** \ingroup xmodem_api + * \brief Receive a file using XMODEM protocol + * + * \param hSer A 'HANDLE' for the open serial connection + * \param szFilename A pointer to a (const) 0-byte terminated string containing + *the file name + * \param nMode The file mode to be used on create (RWX bits) + * \return A value of zero on success, negative on failure, positive if + *canceled + * + * Call this function to receive a file, passing the handle to the open serial + *connection, and the + * name and mode of the file to create from the XMODEM stream. The function + *will return a value of zero on + * success. On failure or cancelation, the file will be deleted.\n + * If the specified file exists before calling this function, it will be + *overwritten. If you do not + * want to unconditionally overwrite an existing file, you should test to see + *if it exists first. + * +**/ +int XReceive(SERIAL_TYPE hSer, const char* szFilename, int nMode); + +/** \ingroup xmodem_api + * \brief Send a file using XMODEM protocol + * + * \param hSer A 'HANDLE' for the open serial connection + * \param szFilename A pointer to a (const) 0-byte terminated string containing + *the file name + * \return A value of zero on success, negative on failure, positive if + *canceled + * + * Call this function to receive a file, passing the handle to the open serial + *connection, and the + * name and mode of the file to send via the XMODEM stream. The function will + *return a value of zero on + * success. If the file does not exist, the function will return a 'failure' + *value and cancel + * the transfer. + * +**/ +int XSend(SERIAL_TYPE hSer, const char* szFilename); + +#ifdef DEBUG_CODE +const char* XMGetError(void); +#endif // DEBUG_CODE + +#ifdef __cplusplus +}; +#endif // __cplusplus + +#endif // ARDUINO + +#endif |