Most of the students have learned Java in school, and (except for a few application areas that the Java designers didn't want to touch) it's a more robust language than C/C++ and more standard than C#. Furthermore (if you are willing to take the reasonable and modest steps to avoid triggering a garbage collection time-out) the Java JIT compilers in most implementations produce code that runs essentially at machine speed, about as fast as C/C++.
The computer we chose, LattePanda (LP) runs Windows/10, and Win10 runs Java, but the LP interface to the Arduino is specified and written in C#. The Arduino itself is programmed with (open-source) Firmata, and the purpose of this package is to avoid re-inventing that wheel, but rather to replace the supplied LP driver with a Java equivalent. I call it "FakeFirmata" (FF).
We originally were only using the Arduino to drive two servos, so most of the Firmata interface was not implemented, but only the two APIs to blink the on-board LED and to drive servos. The LP code is well-commented, and with reference to the Firmata C++ source code, the Firmata documentation on GitHub, adding the additional APIs should be straight-forward knowing only Java and neither C nor C#. I tried to preserve the LP text as much as possible, changing only the spelling as appropriate to Java syntax.
Unfortunately, there is no standard way in Java to access the serial port that Arduino uses for communication to the host computer. There are several serial port implementations, and every one I looked at is huge and hard to understand and overkill for this application, but I did not want to re-invent that wheel either, so we chose the Java-Simple-Serial-Connector (JSSC, also available on GitHub) implementation as being slightly more transparent. After a hiccup or two (readily identified in the Java debugger and fixed by making sure their DLL is in the correct folder), I was able to make my test program blink the blue Arduino LED and thereafter to drive servos. I uploaded my original working code to the LP User Forum.
Everything tends to grow by "feature creep," and our application is
no exception. So when I tried to allow for digital and analog input, I
had to make some substantial revisions. My efforts at making input work
failed, so what you see in this release are the original two output modes
only, not much different from the initial version uploaded to the LP User
Forum, except I added the DeadMan input processing and a pulse counter,
plus a sandbox area for learning about the Arduino. It still has the code
hook (SimHookBase) to give the TrakSim simulator look-only access
to the signals being sent to the servo controls.
Download FakeFirmata (includes JavaFlyCam and HardAta)
HardAta: Alternate Arduino Clone for LattePanda
JSSC on GitHub
Starting Up LattePanda
Fly2cam Notes
APW2 Technical Topics
TrakSim Web Pages
FakeFirmata
FakeFirmata is an open-source Firmata library to replace the one that is provided by LattePanda; it is suitable for Java apps developed in Windows. This class allows you to control Arduino GPIO from Java apps, with features including:* Writing to digital pins
* Controlling servo motors
3 Steps to Your Remote Arduino Project1. Set up your PC
2. Set up the Arduino (It is pre-installed, unless you changed the Arduino program)
3. Create a project or use the sample projectFunctionality
Constructor
* public Arduino();There is only one constructor, and it sets the parameters as defined by LP to their default values, which is the only way it will run anyway.
Configuration
* public void pinMode (int pin, byte mode);Sets the mode of the specified pin as you wish
Parameters
pin: the number of the pin whose mode you wish to set
mode: Arduino.OUTPUT, [Arduino.INPUT, Arduino.PWM,] Arduino.SERVO
New this release: Arduino.DM_SERVO, Arduino.DEADMAN, Arduino.PULSECOUNTDigital
* public void digitalWrite (int pin, byte value);Write to a digital pin that has been toggled to output mode with the pinMode() method
Parameters
pin: The digital pin to write to
value: Arduino.HIGH, Arduino.LOWServo
* public void servoWrite(int pin, int angle);Write the angle to specified pin which has been set to Servo (or DM_SERVO) mode
Parameters
pin: Servo output pin.
value: Angle from 0 to 180.Examples
digitalWrite
In this example, we will blink the LED which is connected with digital pin (D13)API Required :
1. public Arduino();
2. public void pinMode(int pin, byte mode);
3. public void digitalWrite(int pin, byte value);
Hardware Required:1. LattePanda x 1
2. led x 1 (you can use the LED attached to pin 13 on the Arduino board itself)
Circuit:1. LED inserted directly into pin 13
Code:1. Create a new project in [Eclipse], see New Java Project (below)
2. Main function code:package blinkYourBoard; // project nameimport java.lang.Thread;
import jssc.SerialPort;
import fakefirm.Arduino;class Program {
static Arduino arduino = new Arduino(); // create an instance
// and initialize with the default parametersstatic void main() {
arduino.pinMode(13, Arduino.OUTPUT);
// Set the digital pin 13 as output
while (true) { // ==== set the led on or off
arduino.digitalWrite(13, Arduino.HIGH); // set the LED=on
Thread.sleep(1000); // delay one second
arduino.digitalWrite(13, Arduino.LOW); // set the LED=off
Thread.sleep(1000); // delay one seconds
} // end while
} // end main
} // end class Program
Test:1. Click [Debug or] Run button to execute, the LED will start blinking.Servo
In this example, we will sweep the servo motor back and forth across 180 degrees.
Note: only digital pins 9 and 10 work as servo outputs in HardAta.API Required:
1. public Arduino();
2. public void pinMode(int pin, byte mode);
3. public void servoWrite(int pin, int angle);
Hardware Required:1. LattePanda x 1
2. Servo Motor x 1
Circuit:1. Servo inserted directly into pin D9:Code :
[This is my drawing, because I did not save a copy of their drawing, and it's all gone now. Anyway, I think their drawing suggested you plug it into the black header using jumper wires, whereas the servo cable connector is an exact fit to the white pins on the other side of the LP board.]
[The LattePanda website didn't say so, but the current draw of an active servo can spike the voltage level on the LP when minimally powered (like using the power brick they provided) so that other circuits (not necessarily the LP itself) became marginal. We found it useful to cut the (red) power wire of the servo cable and provide power to the servo directly from a separate power source with better capacitance (like a 5v USB phone-charger battery).]
1. Create a new project in [Eclipse], see New Java Project (below)
2. Main function code:package servoExampleimport java.lang.Thread;
import jssc.SerialPort;
import fakefirm.Arduino;class Program {
static Arduino arduino = new Arduino(); // create an instance
// and initialize with the default parametersstatic void main() {
arduino.pinMode(9, Arduino.SERVO);
while (true) {
arduino.servoWrite(9, 180);
// tell the servo motor go to position 180 degrees
Thread.sleep(1000); // delay one second
arduino.servoWrite(9, 0);
// tell the servo motor go to position 0 degrees
Thread.sleep(1000); // delay one second
} // end while
} // end main
} // end class Program
Test:1. Click debug to execute, you will find the motor sweeping forth and back continuously.
void LogHardAta()
Turns on a verbose mode in HardAta, which makes it easier to see what
the Arduino is doing. You may need to designate a listener if you want
your Java code to see these, or you can turn on the logging switch
SpeakEasy=true to see them on the Java runtime console.
interface UpdateListener
void addInputListener(int selector, UpdateListener whom)
Implement this interface in a listener class, and tell AIL about it
to catch any of the following types of selector events:
Arduino.REPORT_VERSION -- to get the current version = 0xF9 (249)You can only designate one listener for each selector, but you can use the same listener for more than one selector. There is an example listener in each of the two examples (Counting Pulses and Using the Remote As DeadMan) below, and a more comprehensive example in the TrakSim (DrDemo) example code.
Arduino.DEADMAN_MESSAGE -- to see DeadMan transitions = 0xFB (251)
Arduino.REPORT_MISCELLANY -- to get other HardAta output = 0xFD (253)
Arduino.REPORT_PULSECOUNT -- to get current pulse count
Arduino.REPORT_DIGITAL -- (unsupported) digital input
Arduino.REPORT_ANALOG -- (unsupported) analog input
The UpdateListener interface requires you to implement this method which is called for every event of the selector type:
void pinUpdated(int pin, int value)
The VERSION, DEADMAN, and MISCELLANY selector types report that reference number as the "pin" parameter; otherwise the designated input pin number is passed in this parameter. The value parameter is generally a 14-bit value specific to the event.
void pinMode(int pin, byte mode)
This is essentially the same as the original Firmata method, but extended
to accept three new pin modes:
Arduino.OUTPUT -- pin=13
Arduino.SERVO -- pin=9 or pin=10
Arduino.DM_SERVO -- (new) pin=9 or pin=10
Arduino.DEADMAN -- (new) pin=11
Arduino.PULSECOUNT -- (new) pin=8
The original Arduino.INPUT, Arduino.ANALOG and
Arduino.PWM
pin modes are supported only in the original Firmata Arduino code, and
not in HardAta. HardAta only supports these five pins in these modes.
void digitalWrite(int pin, byte value)
This is essentially the same as the original Firmata method, but implemented
in HardAta only for the blue LED D13 (pin=13).
The value should be either Arduino.LOW (off=0) or Arduino.HIGH
(on=1).
void servoWrite(int pin, int angle)
This is essentially the same as the original Firmata method, but in
HardAta only works with the designated servo pins D9 and D10.
The servo angle should be a number between 0 (full left or full reverse)
and 180 (full right or full forward). If the pinMode
of this servo is set using Arduino.DM_SERVO, then when the DeadMan
trigger is not activated, HardAta will substitute an angle=90
(stopped or straight ahead), and restore the given angle
when DeadMan is activated again.
void DoPulseCnt(int pin, int ms)
This turns pulse counting on and specifies an approximate period (in
milliseconds, 1000 is one second) to automatically generate reports, which
you should catch using addInputListener.
HardAta currently only works with pin=8. The actual period seems
to vary by 100ms or more, depending on other events in the Java eco-system,
which may impact your speed calculation; for better speed analysis you
probably should use Gaussian smoothing
across multiple periods.
void Open()
void Close()
These are essentially the same as the original Firmata methods, which
opens the serial port connection and generally starts things up, and then
closes things down when you finish. We had some trouble with the Win10
handling of the serial port through JSSC if Close
is not called when the program quits, but that may be fixed (in JSSC)
now. It's still a good policy to close things down properly when done.
void Send3bytes(int comm, int data, int
more)
This is a convenient way to send a 3-byte command packet to HardAta.
Normally you don't need this for ordinary usage.
static int GetMills()
static String FormatMillis(String prefix, int now)
Sometimes I found it useful to time-stamp my diagnostic log with an
easily read number of milliseconds from program start. GetMills
returns that value as an integer for relative time calculations, and FormatMillis
can format it as part of a System.print line.
Counting Pulses
Enable pulse counting using a pinmode call. You probably need to set up a listener object to catch the (asynchronous) pulse counts from the Arduino. Then periodically, you send a request for the current count and catch the result.We found that the driveshaft turns sensor on the Traxxas car has a limited supply voltage operating range, and it completely shut down on the +5v provided by the LP I/O connector, but it operated fine on the +3.3v provided by the radio receiver when it was powered by the ESC. However if the receiver was powered by the LP +5v, it only gave +3v to the driveshaft sensor, and the resulting pulse train was uncomfortably close to the specified +3v threshold for the LP/Arduino digital input. See the circuit diagram for the DeadMan test (below)
For this test I left the receiver connected to the Electronic Speed Control (ESC) to drive the car, and tapped off only the driveshaft sensor signal ("RPM" and ground) to connect to the LP/Arduino digital input D8 while leaving it still connected to the receiver as shipped from Traxxas, as shown here:
The relevant code is much more complicated than driving a servo, but this should give you the basic idea. It captures the turns count every second and displays it on the console:
package testPC; // project nameimport java.lang.Thread;
import fakefirm.Arduino;
import fakefirm.UpdateListener;class Program {
static Arduino ardueeno = new Arduino(); // create an instance
// and initialize with the default parametersprivate static class testListener implements UpdateListener {
int prior; // previous readingpublic void pinUpdated(int pin, int value) { // called when it arrives
boolean doit;
if (pin==8) {
if (value+prior>0) SystemDebugLog("Pulse count = " # value);
prior = value;}~if
} //~pinUpdatedpublic testListener() { // constructor, init..
prior = 0;
SystemDebugLog("new testListen");}} //~testListenerprivate static testListener ArduPinUpd = new testListener();
static void main() {
int whom = Arduino.REPORT_PULSECOUNT;
if (ardueeno.GetFirmwareRev()<0x120000) return; // not HardAta
ardueeno.addInputListener(whom,ArduPinUpd); // set listener
ardueeno.pinMode(8,Arduino.PULSECOUNT);
// Set the digital input pin 8 as pulse count
ardueeno.DoPulseCnt(8,1000); // get results each 1000ms
try {while (true) Thread.sleep(500); // delay half second
} catch (Exception ex) {System.out.println(ex);}} // end main
} // end class ProgramUsing the Remote As DeadMan
Enable the DeadMan feature using a pinmode call, then enable each servo under its control using DM_SERVO instead of SERVO. See the diagram for connecting up the receiver and servo(s) You may wish to set up a listener object to catch when the DeadMan switch is active or released.The circuit shown here also includes the pulse-count tap for the full computer/car connection, except the steering is not shown because this circuit was tested on the bench without actually controlling the steering.
Note that "D8" pin goes into the black header on the LP board on the opposite edge from the white servo headers, second hole in from the inside corner nearest to the LP (white) D9 servo header (see the diagram above) and the "GND" can plug into outside (ground) pin of any vacant (white) servo header -- or if you still have the pin on that wire from the "Counting Pulses" experiment (above), it plugs into the inside row of the black connector at the far end from the D8 hole, as shown in the diagram above. I had a link to the LP website with a diagram where they labelled all the pins, but they changed everything and I can no longer find that diagram on their website, but you might try the "First Edition Hardware Introduction" page.
The code here turns on the throttle at a moderate speed, and leaves it on while the operator pulls and releases the speed trigger on the transmitter, to enable the car to go or stop. The program monitors the HardAta output and prints out the status on the console:
package testDM; // project nameimport java.lang.Thread;
import fakefirm.Arduino;
import fakefirm.UpdateListener;class Program {
static Arduino ardueeno = new Arduino(); // create an instance
// and initialize with the default parametersprivate static class testListener implements UpdateListener {
boolean died; // true if DeadMan activatedpublic void pinUpdated(int pin, int value) { // called when it arrives
boolean doit;
if (pin==Arduino.DEADMAN_MESSAGE) { // =251
doit = (value >= 0x2000);
if (doit != died) SystemDebugLog("DeadMan = " # doit);
died = doit;}~if
} //~pinUpdatedpublic testListener() { // constructor, init..
died = false;
SystemDebugLog("new testListen");}} //~testListenerprivate static testListener ArduPinUpd = new testListener();
static void main() {
int whom = Arduino.DEADMAN_MESSAGE;
if (ardueeno.GetFirmwareRev()<0x120000) return; // not HardAta
ardueeno.addInputListener(whom,ArduPinUpd); // set listener
ardueeno.pinMode(11,Arduino.DEADMAN);
// Set the digital input pin 11 as (PWM) DeadMan switch from xmtr
ardueeno.pinMode(10,Arduino.DM_SERVO);
// Set the digital output pin 10 as ESC servo under DeadMan control
ardueeno.servoWrite(10,105); // start servo +15 degrees
try {while (true) Thread.sleep(500); // delay half second
} catch (Exception ex) {System.out.println(ex);}} // end main
} // end class Program
If you have questions, you can send me an email and I'll answer the best I can, but I may not have sufficient time or access to the necessary resources to test anything, so I cannot promise any particular improvements.
Rev. 2019 May 17