Login [Register]
Don't have an account? Register now to chat, post, use our tools, and much more.
I'm currently using the sample HelloWorld project from ArTICL's library for sending and receiving messages. I can't get the arduino to send the "Hello, world! Smile" message to the calculator. But sending from the calculator to Arduino works.

The calculator thinks it received something from the Arduino but really it's just giving me the Str1 variable used for sending to the Arduino. Am I doing something wrong or could it be the code because I noticed that TypeLetter sends
Code:
ticl->send(header, NULL, 0);
while the Helloworld Program doesn't utilize a
Code:
 cbl->send


Any help is appreciated! Smile
You seem to be experiencing the same issue as the recent https://www.cemetech.net/forum/viewtopic.php?t=16165 : sending the hello world message as string to the calculator.
Lionel Debroux wrote:
You seem to be experiencing the same issue as the recent https://www.cemetech.net/forum/viewtopic.php?t=16165 : sending the hello world message as string to the calculator.


It does appear to be the same issue. I've spent about six hours trying to get it to work with no success and that includes modifying the code, sending out a word with all capital letters only, testing different calculators and OS versions, etc. I've went through all of the posts here too looking for a way to fix it but it's just a dead end. All I could mostly find is posts with the similar issue. This leads me to believe the code for utilizing Get( is defective.

I found a temporary workaround to send stuff to the calculator by utilizing part of TypeLetter's code to break a string into single chars and sending those out. This is a very tedious process though and does not help anyone looking to store the result of Get(
As I hinted in the other topic, if I had to debug the issue myself, I'd use:
* TilEm 2 ("TilEm-NG") emulating your calculator's model. With TilEm, you can use TI's freely downloadable OS upgrades for the monochrome 83+ and 84+, or dump your own calculator's Flash memory using TILP's ROM dumping feature and feed that to TilEm;
* usbmon + Wireshark - I do have a Windows VM, but building up the testing setup would be quite a bit more work and trouble than my already configured Linux environment
to gather dumps of the communication between a real calculator of the same model as yours and the emulated calculator on the one side, and the Arduino running the HelloWorld example and the emulated calculator on the other side.
Without this approach, several months ago, gathering dumps of the lab equipment <-> calculator communication and using the knowledge gained from these dumps to reimplement a subset of the protocol in libticalcs would have been hard.
I will see if I'm able to figure this issue out. Seeing that all of the ArTICL videos utilized the TI 84 Plus C Silver Edition, I decided to buy one and sure enough, the Get( command has no issues with the same exact configuration. I was able to successfully receive the "Hello, world! Smile" message.

So now it's a matter of figuring out what's different between the two calculators.

Currently my suspicion lies in the onRequest callback. Inserting a delay() inside, the TI 84 Plus CSE will actually wait the time specified before sending the Hello World message, while the TI 84 Plus does not and simply resorts to an error or returning the send value.
I am happy to say that I believe I have figured out the issue. I am providing an explanation as well as the modified code at the bottom.

Explanation
Since the TI 84+ CSE was able to utilize Get( without any issues, while the TI-84+ could not, I decided to enable verbosity in HelloWorld. This was done by simply uncommenting the following code inside the eventLoopTick function:

Code:
//cbl.setVerbosity(true, &Serial);      // Comment this in for message information


With verbosity enabled, I decided to comment the two serial print lines in CBL2.cpp to make the serial output less cluttered

Code:

// See if there's a message coming
   rval = get(msg_header, data_, &length, maxlength_, quick_fail ? TIMEOUT : GET_ENTER_TIMEOUT);
   if (rval) {
      if (serial_) {
         //serial_->print("No msg: code ");
         //serial_->println(rval);
      }
      return 0;         // No message coming
   }


I also commented the serial print call in the getByte function of TICL.cpp

Code:

      previousMicros = micros();
      while ((linevals = ((digitalRead(ring_) << 1) | digitalRead(tip_))) == 0x03) {
         if (micros() - previousMicros > timeout) {
            resetLines();
            if (serial_) {
               //serial_->print("died waiting for bit "); serial_->println(bit);
            }
            return ERR_READ_ENTER_TIMEOUT;
         }
      }


Now when I compared the serial output from utilizing both of the calculators. The first thing I noticed was that The TI-84+ had a shorter output of 46 lines, compared to the CSE's 90 lines of output. After analyzing the output from sending different message to both calculators. I began noticing a couple things.



1. The portion I highlighted in blue would always remain the same except for the bytes on lines 7 and 9 depending on the calculator. It appears that this information is utilized for sending to the calculator.

2. After several tests, I noticed that in the red portion, the TI 84+ was always short by 4 compared to the CSE. On line 33, I noticed that the CSE always sent a byte of 4 while the for the normal calculator it was always zero. This can also be noticed in the blue portion as well. The Arduino always gets a byte of 4 for the CSE but 0 for the TI-84+ on line 9.

Aside from those differences, the outputs were virtually the same. At this point, I needed to change to correct the bytes so that the output would like the CSE output. I added a counter to the code to see how many times bytes were sent with TICL::sendbyte() This picture shows that shows that. https://i.imgur.com/UBsBXct.png

After taking note of when byte 4 was supposed to be sent, I adjusted the code to send it it as well as add 4 to the total byte. All changes take place in TICL.cpp.

These 2 lines are declared at the top:

Code:

int bytePosition;
boolean needsRepair = false;


The following code goes right at the start of TICL:sendByte():

Code:

if(bytePosition == 10 && byte == 0){
         byte = 4;
         needsRepair = true;
}
if(bytePosition == 21 && needsRepair){
         byte += 4;
      }
bytePosition += 1;


and finally, the following is placed inside TICL:get() right after datalength is set.

Code:

if(header[1] == 86 && header[0] == 130){
      // Recv typ 0x56 from EP 0x82
      needsRepair = false;
      bytePosition = 0;
   }



This is the final code edit:

Code:

/*************************************************
 * TICL.cpp - Core of ArTICL library for linking *
 *            TI calculators and Arduinos.       *
 *            Created by Christopher Mitchell,   *
 *            2011-2019, all rights reserved.    *
 *************************************************/

#include "Arduino.h"
#include "TICL.h"

int bytePosition;
boolean needsRepair = false;

// Constructor with default communication lines
TICL::TICL() {
   setLines(DEFAULT_TIP, DEFAULT_RING);
   serial_ = NULL;
}

// Constructor with custom communication lines. Fun
// fact: You can use this and multiple TICL objects to
// talk to multiple endpoints at the same time.
TICL::TICL(int tip, int ring) {
   setLines(tip, ring);
   serial_ = NULL;
}

// This should be called during the setup() function
// to set the communication lines to their initial values
void TICL::begin() {
   resetLines();
}

// Determine whether debug printing is enabled
void TICL::setVerbosity(bool verbose, HardwareSerial* serial) {
   if (verbose) {
      serial_ = serial;
   } else {
      serial_ = NULL;
   }
}

// Change the lines after construction
void TICL::setLines(int tip, int ring) {
   tip_ = tip;
   ring_ = ring;
}

// Send an entire message from the Arduino to
// the attached TI device, byte by byte
int TICL::send(uint8_t* header, uint8_t* data, int datalength, uint8_t(*data_callback)(int)) {
   if (serial_) {
      serial_->print("snd type 0x");
      serial_->print(header[1], HEX);
      serial_->print(" as EP 0x");
      serial_->print(header[0], HEX);
      serial_->print(" len ");
      serial_->println(datalength);
   }

   // Send all of the bytes in the header
   for(int idx = 0; idx < 4; idx++) {
      int rval = sendByte(header[idx]);
      if (rval != 0) {
         return rval;
      }
   }
   
   // If no data, we're done
   if (datalength == 0) {
      return 0;
   }
   
   // These  also indicate that there are
   // no data bytes to be sent
   if (header[1] == CTS ||
      header[1] == VER ||
      header[1] == ACK ||
      header[1] == ERR ||
      header[1] == RDY ||
      header[1] == SCR ||
      header[1] == KEY ||
      header[1] == EOT)
   {
      return 0;
   }
   
   // Send all of the bytes in the data buffer
   uint16_t checksum = 0;
   for(int idx = 0; idx < datalength; idx++) {
      uint8_t outbyte;
      // Get a byte if we need
      if (data_callback != NULL) {
         outbyte = data_callback(idx);
      } else {
         outbyte = data[idx];
      }
      // Try to send this byte
      int rval = sendByte(outbyte);
      if (rval != 0) {
         return rval;
      }
      checksum += outbyte;
   }
   
   // Send the checksum
   int rval = sendByte(checksum & 0x00ff);
   if (rval != 0) {
      return rval;
   }
   rval = sendByte((checksum >> 8) & 0x00ff);
   return rval;
}

// Send a single byte from the Arduino to the attached
// TI device, returning nonzero if a failure occurred.
int TICL::sendByte(uint8_t byte) {
   unsigned long previousMicros;
   if (serial_) {

      
      if(bytePosition == 10 && byte == 0){
         byte = 4;
         needsRepair = true;
      }
      if(bytePosition == 21 && needsRepair){
         byte += 4;
      }
      bytePosition += 1;
      
      serial_->print("["); serial_->print(bytePosition); serial_->print("]");
      serial_->print("Sending byte ");
      serial_->println(byte);
   }

   // Send all of the bits in this byte
   for(int bit = 0; bit < 8; bit++) {
      
      // Wait for both lines to be high before sending the bit
      previousMicros = micros();
      while (digitalRead(ring_) == LOW || digitalRead(tip_) == LOW) {
         if (micros() - previousMicros > TIMEOUT) {
            resetLines();
            return ERR_WRITE_TIMEOUT;
         }
      }
      
      // Pull one line low to indicate a new bit is going out
      bool bitval = (byte & 1);
      int line = (bitval)?ring_:tip_;
      pinMode(line, OUTPUT);
      digitalWrite(line, LOW);
      
      // Wait for peer to acknowledge by pulling opposite line low
      line = (bitval)?tip_:ring_;
      previousMicros = micros();
      while (digitalRead(line) == HIGH) {
         if (micros() - previousMicros > TIMEOUT) {
            resetLines();
            return ERR_WRITE_TIMEOUT;
         }
      }

      // Wait for peer to indicate readiness by releasing that line
      resetLines();
      previousMicros = micros();
      while (digitalRead(line) == LOW) {
         if (micros() - previousMicros > TIMEOUT) {
            resetLines();
            return ERR_WRITE_TIMEOUT;
         }
      }
      resetLines();
      
      // Rotate the next bit to send into the low bit of the byte
      byte >>= 1;
   }

   return 0;
}

// Returns 0 for a successfully-read message or non-zero
// for failure. If return value is 0 and datalength is zero,
// then the message is just a 4-byte message in the header
// buffer. If the
int TICL::get(uint8_t* header, uint8_t* data, int* datalength,
              int maxlength, int timeout)
{
   int rval;

   // Get the 4-byte header: sender, message, length
   for(int idx = 0; idx < 4; idx++) {
      rval = getByte(&header[idx], timeout);
      if (rval) {
         return rval;
      }
   }
   *datalength = (int)header[2] | ((int)header[3] << 8);
   
   if (serial_) {
      serial_->print("Recv typ 0x");
      serial_->print(header[1], HEX);
      serial_->print(" from EP 0x");
      serial_->print(header[0], HEX);
      serial_->print(" len ");
      serial_->println(*datalength);
   }

   if(header[1] == 86 && header[0] == 130){
      // Recv typ 0x56 from EP 0x82
      needsRepair = false;
      bytePosition = 0;
   }

   if (*datalength == 0) {
      return 0;
   }

   // These  also indicate that there are
   // no data bytes to be received
   if (header[1] == CTS ||
      header[1] == VER ||
      header[1] == ACK ||
      header[1] == ERR ||
      header[1] == RDY ||
      header[1] == SCR ||
      header[1] == KEY ||
      header[1] == EOT)
   {
      return 0;
   }
   
   // Check if this is a data-free message
   if (*datalength > maxlength) {
      if (serial_) {
         serial_->print("Msg buf ovfl: ");
         serial_->print(*datalength);
         serial_->print(" > ");
         serial_->println(maxlength);
      }
      return ERR_BUFFER_OVERFLOW;
   }
   
   // Get the data bytes, if there are any.
   uint16_t checksum = 0;
   for(int idx = 0; idx < *datalength; idx++) {
      // Try to get all the bytes, or fail if any of the
      // individual byte reads fail
      rval = getByte(&data[idx]);
      if (rval != 0) {
         return rval;
      }
         
      // Update checksum
      checksum += data[idx];
   }
   
   // Receive and check the checksum
   uint8_t recv_checksum[2];
   for(int idx = 0; idx < 2; idx++) {
      rval = getByte(&recv_checksum[idx]);
      if (rval)
         return rval;
   }
   
   // Die on a bad checksum
   if (checksum !=
      (uint16_t)(((int)recv_checksum[1] << 8) | (int)recv_checksum[0]))
   {
      return ERR_BAD_CHECKSUM;
   }
   
   return 0;
}

// Receive a single byte from the attached TI device,
// returning nonzero if a failure occurred.
int TICL::getByte(uint8_t* byte, int timeout) {
   unsigned long previousMicros = 0;
   *byte = 0;
   
   // Pull down each bit and store it
   for (int bit = 0; bit < 8; bit++) {
      int linevals;

      previousMicros = micros();
      while ((linevals = ((digitalRead(ring_) << 1) | digitalRead(tip_))) == 0x03) {
         if (micros() - previousMicros > timeout) {
            resetLines();
            if (serial_) {
               //serial_->print("died waiting for bit "); serial_->println(bit);
            }
            return ERR_READ_ENTER_TIMEOUT;
         }
      }
      
      // Store the bit, then acknowledge it
      *byte = (*byte >> 1) | ((linevals == 0x01)?0x80:0x00);
      int line = (linevals == 0x01)?tip_:ring_;
      pinMode(line, OUTPUT);
      digitalWrite(line, LOW);
      
      // Wait for the peer to indicate readiness
      line = (linevals == 0x01)?ring_:tip_;      
      previousMicros = micros();
      while (digitalRead(line) == LOW) {            //wait for the other one to go high again
         if (micros() - previousMicros > TIMEOUT) {
            resetLines();
            if (serial_) {
               serial_->print("died waiting for bit ack "); serial_->println(bit);
            }
            return ERR_READ_TIMEOUT;
         }
      }

      // Now set them both high and to input
      resetLines();
   }
   if (serial_) {
      serial_->print("Got byte ");
      serial_->println(*byte);
   }
   return 0;
}

void TICL::resetLines(void) {
   pinMode(ring_, INPUT_PULLUP);           // set pin to input with pullups
   pinMode(tip_, INPUT_PULLUP);            // set pin to input with pullups
}



Hopefully someone can come up with a more sophisticated fix but these changes don't seem to cause any issues.

Users in these two threads had the same issue:
https://www.cemetech.net/forum/viewtopic.php?t=13880
https://cemetech.net/forum/viewtopic.php?t=16165
I do understand that this works for your specific purpose, but this change is incorrect in general Smile
The condition which triggers "needsRepair = true;" seems both too simplistic and misplaced: rather than trying to fix the packet data and then the checksum in the sendByte() low-level function which does not have knowledge of the higher level protocol (the layer to which sendByte() belongs should only be concerned about dealing with raw bytes), the packets' contents need to be made correct upfront.

0x04 is precisely the encoding of the string type on both models, BTW: https://github.com/debrouxl/tilibs/blob/experimental2/libtifiles/trunk/src/types83p.h / https://github.com/debrouxl/tilibs/blob/experimental2/libtifiles/trunk/src/types84p.h . The 84+ trace shows that the library's requesting a real value (type 0x00), instead of a string.
Okay, thank you. On my free time I'll attempt to make the correct changes and update that top post. Those headers are also interesting. In this case, I don't know why the TI-84 is requesting a real. With no modifications to HelloWorld.ino or HelloWorld.8xp it does that. This is even though The command Get(Str1 is being run. Yet the CSE which is just about the same thing works fine.

Get(Str1 requests a real on the TI-84+ and this is after testing it with two different calculators that were on the latest TI OS as well as on a couple downgraded versions.
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are GMT - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement