Quick demo video of communication.

These are really just some notes I've taken while messing around with the CX II's USB. I'm probably inconsistent on my use of quotation marks throughout this post, but nothing needs to be inside of quotes unless a TI-Nspire command (be it BASIC or Python) needs a string as an argument.

Hardware
I'm using an Arduino Nano clone that has a CH340G USB-to-UART adapter in place of something fancy and name-brand. I haven't changed the USB IDs. My TI-Nspire CX II calculators (on 5.3) talk to the CH340G just fine through a USB OTG adapter (a mini-A to mini-B link cable would also work), and communication is bidirectional (this is necessary). I don't know why, but the Nspire does stuff when communicating with the CH340 that the AVR doesn't like, so I wasn't able to directly communicate to my Arduino Nano clone; I was, however, able to use it to essentially break out the TxD and RxD lines from the CH340 and connect it to another Arduino. This made for a misleading breadboard layout in my demo video.

The final setup is my Arduino Nano clone running a blank sketch, and an Arduino Leonardo connected via its RX and TX pins such that the UART is shared. This is exposed on the Leonardo as Serial1, which allows me to use Serial (the USB interface on the Leonardo) to send debugging messages to my computer. The Leonardo runs a basic sketch that parses commands and returns a response code; if I perform the TI-Nspire BASIC commands Send "SET LED 1" \ Get a, a will contain the string '1' as my SET LED command returns the new state of the LED.

The equivalent commands for Send and Get within its Python environment are located in the ti_hub module, as ti_hub.ti_innovator.send(string command) and ti_hub.ti_innovator.get(string variablename). The Python implementation of .get() is a shim over the BASIC Get command, so you can have some unexpected behavior. When you run .get("a"), it runs the BASIC command "Get a", evaluates a (which can throw an error outside Python), then returns a string of what a evaluated to. On my CX II CAS, ti_hub.ti_innovator_get("a") with nothing in the receive buffer returns 'a'.

The Innovator Hub also just works like a USB-to-serial bridge. I presume it expects 115200bps, which is what most TI-Nspire stuff runs at. That makes it fairly straightforward to pretend to be an Innovator Hub.

If you power the Arduinos externally, the calculator will wake itself up for some reason as soon as it turns off; this kills your battery life. If you power the Arduinos via USB OTG power (both run within the power budget, plus my BMP180 as a data source), the calculator turns off power to the devices after inactivity.

Software
The TI-Nspire, when you first run a Send or Get command, the OS sends an ISTI command and expects a response. Over the wire, ISTI is sent as "ISTI\n" (no quotes, and note the lack of \r). The thing on the other end of the UART must respond to this with "TISTEM\r\n", or the calculator will complain about the hub being disconnected (hint: Python error messages are more descriptive than Nspire BASIC's error system). After this initial exchange, there's no more ISTI command unless you manually send one with Send "ISTI" \ Get somevar, or use somevar = ti_hub.ti_innovator.isti(). I think the response to ISTI is a whitelist, since the ti_hub implementation returns what the remote device sent (or raises tihubException) and not a True/False response.

When you're implementing two-way communication with the Arduino, you need to pay attention to what returns a response and what doesn't. Responses are strings sent over the serial port, terminated with CRLF, and you read these with the Get command. The first line you send, and thus what lands on the calculator's UART receive buffer, is what the Get command fetches, even if you send subsequent data before another Get command. If you send two lines from an Arduino, then run Get twice in a row on the calculator, the calculator first parses and stores the first line that was sent, then times out (1 second) receiving a fresh message before returning whatever value was already inside the variable you are Getting. There is no way to tell if you missed a line. You can run Get inside a loop if your Arduino (or other UART device) sends data sufficiently slowly; the timeout should keep the calculator in sync. The better way is to interrogate the Arduino explicitly with a Send command, then immediately fetch the output.

The Lua asi library should, in theory, let you talk directly through the CH340, as well as do things like control the baudrate (which defaults to an unconfigurable 115200 everywhere else). I don't know Lua, so I'm not going to experiment with this.

Quick Summary
The key takeaways here are that the serial port always runs at 115200, 8N1 framing, no flow control as far as I've observed. You need to respond correctly to the ISTI command before you do anything, and you can communicate with the Nspire OS by sending and receiving strings terminated with CRLF. The calculator's buffer only holds the first line that hits it, so it is up to you, the programmer, to properly implement some kind of flow control.
This is pretty interesting information! I love learning about the communication protocols that different devices use!
Ordered a CH340G breakout and CP2104 breakout off eBay for about $8 total. Coming from China, it's taking a while for shipping to happen, but the CP2104 breakout arrived yesterday. I rewired my board to only use the bootleg Arduino Nano, since the Leonardo doesn't fit on a breadboard. The CP2104 replaces the bootleg Nano's previous role as a CH340 breakout, so there is a single Arduino in this configuration.

Rather than draw the schematic, I'm feeling a bit lazy and will just describe what hooks up where. The UART has (among other things) 5V out, ground, TXD and RXD. 5V Out goes to Arduino VIN, ground to Arduino ground, TXD to Arduino RXD (D0), and RXD to Arduino TXD (D1). LED with current-limiting resistor goes between D5 and ground. An Adafruit BMP180 (simply a data source) connects over I2C on pins A4, A5, and receives power through VIN and ground pins shared with the Arduino.

Communications from the calculator are available on Arduino's Serial interface, and a debugging output (provided by SoftwareSerial) is available on pins 7 and 8; connect RXD of another UART-to-USB converter to pin 8.

By popular demand, the Arduino sketch I used for the pictured demo is available on my GitHub repository under GPL3 license. C++ isn't my first language; the sketch is an educational resource.

A few commands are available. "SET LED 0" and "SET LED 1" set the state of the LED, and "GET LED" retrieves the state of the LED. "GET BAR" and "GET THM" retrieve pressure (in mbar) and temperature (in degrees Celsius) from the BMP180.

  
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 UTC - 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