Frsky S.Port Interface

Introduction

As part of our PoC we are reviewing the Frsky S.Port interface for connection to our IRCF360° sensor to provide telemetry data.

FRSKY S.PORT Dashboard

A Frsky S.PORT dashboard has been created using processing.org Java script (see image below). The java sketch merely "sniffs" the current sensors which are being polled and displays the byte values on a dashboard. The last 2 columns display the package size & occurrence of the polled sensor.

For sensor 161 you can see the sensor is polled more than others. This is because a battery sensor has been plugged into the s.port.

The reading of the S.PORT was merely done by plugging the S.PORT signal into a FTDI FT232RL USB to TTL Serial Converter Adapter Module. This basically converts RS-232 or TTL serial transmissions to USB signals, in order to allow support for legacy devices (these just cost a few € on ebay). You can download a configuration tools from the FTDI site which allows you to configure the TX and RX pins as "inverted". The Baud in the processing Sketch is set to 57600, N, 8, 1.

The draft processing sketch is included below and will be uploaded to our GitHub sites soon.

We are adding more details as we get them, so this page is just a placeholder and will be updated as we are working on the project.

The S.Port protocol is quite well documented on the following websites:

Summary of the S.Port characteristics:

  • 3.3V inverted single wire serial protocol running at 57.600 baud serial protocol, so it can be used with an UART.

  • Half duplexed / Single wire, meaning that both transmit and receive are using the same wire

  • Receiver (e.g. FrSky X8R) polls sensors by sending:

    • 0x7E (8bit start-stop)

    • 8_bit_sensor_id

  • If a sensor is present it answers by sending:

    • 0x10 (8bit data frame header)

    • value_type (16 bit, e.g. voltage / speed)

    • value (32 bit, may be signed or unsigned depending on value type)

    • checksum (8 bit)

  • If no sensor is present no answer gets sent and the receiver goes on to the next sensor-id

  • Not all sensor ids (0x00-0xFF) are polled. known to work are: 0x00, 0xA1

  • A value type indicates what kind of value will be transmitted, e.g. voltage / current / speed etc.

  • One sensor can support multiple value types: Each time it receives a poll it may send a different value type. So if a sensor supports output of voltage and current it may output voltage on the first poll, current on the second, voltage on the third, current at the fourth and so on.

  • Value types are described here: https://code.google.com/p/opentx/source/browse/trunk/src/telemetry/frsky_sport.cpp

See our other pages related to this project:

OpenSensor Design

OpenXsensor is the evolution of OpenXvario and extends it to multi-sensing applications.

Based on an Arduino platform, it connects to different sensors and transmits the measurements via RC industry telemetry protocols.

Download their configurator:

References / Credits:

List of Sensor ID's:

Draft version of the processing sketch:

S-PORT Real Time Graphical Display of FrSky S.PORT

// Processing Sketch - This Processing Sketch is used for graphical representation of the FrSky S.PORT protocol in real-time

// In this examples we used an Frysky X8R and X4R receiver which was bound to a Frysky Taranis Plus

// Graphical represetentation of Futuba SBUS

// Example by Colin Bacon

// ROBOTmaker

// www.robotmaker.eu

//To get this to work you'll need to have S.PORT conneted receiver that is connected via FTDI USB thingy to your PC

//As the S.PORT is an inverted signal your need to configure the RX and TTX pins of the FTDI to inverted (using the configurarion tool that can be downloaded from the FTDI website)

//Further detail of how to do this are on our website

//thanks to all contributors who have published this protcol. Details on our website.

//This is still work in progress !!!!

import processing.serial.*;

int FRSKY_ALT_ZERO =0x0000;

//**

//** * info | comment

//** * ---- | -------

//** * sensor ID(s) | FRSKY_SP_ALT ~ FRSKY_SP_ALT+15 (0x0100 ~ 0x010f)

//** * physical ID(s) | 0 - Altimeter high precision / 3 - Altimeter normal precision

//** * value | (int) float * 100 [m]

//** *

//** * N.B. OpenTX use the first non-zero value and set it as offset reference.

//** *

//** * \brief altimeter (barometric altitude)

//** * \warning normal precision altimeter conflicts with GPS physical ID 3

//**

int FRSKY_SP_ALT = 0x0100;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_VARIO ~ FRSKY_SP_VARIO+15 (0x0110 ~ 0x011f)

* physical ID(s) | 0 - Altimeter high precision

* value | ?

*

* \todo what difference with ALT ?

**/

int FRSKY_SP_VARIO = 0x0110;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_CURR ~ FRSKY_SP_CURR+15 (0x0200 ~ 0x020f)

* physical ID(s) | 2 - FSC current sensor

* value | (int) float * 10 [A]

*

* \brief VFAS/FSC current

*/

int FRSKY_SP_CURR = 0x0200;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_VFAS ~ FRSKY_SP_VFAS+15 (0x0210 ~ 0x021f)

* physical ID(s) | 2 - FSC current sensor

* value | (int) float * 100 [V]

*

* \brief VFAS/FSC voltage

*/

int FRSKY_SP_VFAS = 0x0210;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_CELLS ~ FRSKY_SP_CELLS+15 (0x0300 ~ 0x030f)

* physical ID(s) | ?

* value | see FrskySP::lipoCell(uint8_t id, float val1, float val2) for data format

*

* \brief FLVSS Lipo cell voltage

*/

int FRSKY_SP_CELLS = 0x0300;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_T1 ~ FRSKY_SP_T1+15 (0x0400 ~ 0x040f)

* physical ID(s) | 1 - FLVSS Lipo sensor

* value | int [°C]

*

* \brief Temperature

*/

int FRSKY_SP_T1 = 0x0400;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_T2 ~ FRSKY_SP_T2+15 (0x0410 ~ 0x041f)

* physical ID(s) | ?

* value | int [°C]

*

* \brief Temperature

*/;

int FRSKY_SP_T2 = 0x0410;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_RPM ~ FRSKY_SP_RPM+15 (0x0500 ~ 0x050f)

* physical ID(s) | 4

* value | int [rpm]

*

* \brief RPM

*/

int FRSKY_SP_RPM = 0x0500;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_FUEL ~ FRSKY_SP_FUEL+15 (0x0600 ~ 0x060f)

* physical ID(s) | ?

* value | int 0~100 [%]

*

* \brief Fuel level

*/

int FRSKY_SP_FUEL = 0x0600;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_ACCX ~ FRSKY_SP_ACCX+15 (0x0700 ~ 0x071f)

* physical ID(s) | ?

* value | (int) float * 100 [g]

*

* \brief Accelerometer (X)

*/

int FRSKY_SP_ACCX = 0x0700;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_ACCY ~ FRSKY_SP_ACCY+15 (0x0710 ~ 0x071f)

* physical ID(s) | ?

* value | (int) float * 100 [g]

*

* \brief Accelerometer (Y)

*/

int FRSKY_SP_ACCY = 0x0710;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_ACCZ ~ FRSKY_SP_ACCZ+15 (0x0720 ~ 0x072f)

* physical ID(s) | ?

* value | (int) float * 100 [g]

*

* \brief Accelerometer (Z)

*/

int FRSKY_SP_ACCZ = 0x0720;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_GPS_LONG_LATI ~ FRSKY_SP_GPS_LONG_LATI+15 (0x0800 ~ 0x080f)

* physical ID(s) | 3 - GPS

* value | ?

*

* \todo

*/

int FRSKY_SP_GPS_LONG_LATI = 0x0800;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_GPS_ALT ~ FRSKY_SP_GPS_ALT+15 (0x0820 ~ 0x082f)

* physical ID(s) | 3 - GPS

* value | (int) float * 100 [m]

*

* N.B. OpenTX:

* * must have a GpsFix (FRSKY_SP_GPS_LAT_B or FRSKY_SP_GPS_LONG_B must be non null)

* * use the first non-zero value and set it as offset reference

*

* \brief GPS altitude

*/

int FRSKY_SP_GPS_ALT = 0x0820;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_GPS_SPEED ~ FRSKY_SP_GPS_SPEED+15 (0x0830 ~ 0x083f)

* physical ID(s) | 3 - GPS

* value | (int) float * 1000 [knots]

*

* \brief GPS speed

* \warning The speed shown on OpenTX has a little drift, because the knots to shown value conversion is simplified.

* Allthough, raw knots will be recorded in the logs, and the conversion will be correctly in Companion.

* This was discussed in this issue: https://github.com/opentx/opentx/issues/1422

*/

int RSKY_SP_GPS_SPEED = 0x0830;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_GPS_COURSE ~ FRSKY_SP_GPS_COURSE+15 (0x0840 ~ 0x084f)

* physical ID(s) | 3 - GPS

* value | (int) float * 100 [°]

* limits | 0~359.99°

*

* \brief GPS course (heading)

*/

int FRSKY_SP_GPS_COURSE = 0x0840;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_GPS_TIME_DATE ~ FRSKY_SP_GPS_TIME_DATE+15 (0x0850 ~ 0x085f)

* physical ID(s) | 3 - GPS

* value | ?

*

* \brief GPS time and date

* \todo

*/

int FRSKY_SP_GPS_TIME_DATE = 0x0850;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_A3 ~ FRSKY_SP_A3+15 (0x0900 ~ 0x090f)

* physical ID(s) | 5- SP2UART(Host)

* value | ?

*

* \brief A3 ADC sensor

* \todo scale and calibration

*/

int FRSKY_SP_A3 = 0x0900;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_A4 ~ FRSKY_SP_A4+15 (0x0910 ~ 0x091f)

* physical ID(s) | 5 - SP2UART(Host)

* value | ?

*

* \brief A4 ADC sensor

* \todo scale and calibration

*/

int FRSKY_SP_A4 = 0x0910;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_AIR_SPEED ~ FRSKY_SP_AIR_SPEED+15 (0x0a00 ~ 0x0a0f)

* physical ID(s) | ?

* value | knots * 10

*

* \brief Air speed sensor

* \warning The speed shown on OpenTX has a little drift, because the knots to shown value conversion is simplified.

* Allthough, raw knots will be recorded in the logs, and the conversion will be correctly in Companion.

* This was discussed in this issue: https://github.com/opentx/opentx/issues/1422

*/

int FRSKY_SP_AIR_SPEED = 0x0a00;

/**

* Not used - already handled by X8R receiver

*

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_RSSI_ID (0xf101)

* physical ID(s) | ?

*

* \brief RX RSSI (N/U)

*/

int FRSKY_SP_RSSI_ID = 0xf101;

/**

* Not used - already handled by X8R receiver

*

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_ADC1_ID (0xf102)

* physical ID(s) | ?

*

* \brief A1 ADC sensor (N/U)

*/

int FRSKY_SP_ADC1_ID = 0xf102;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_ADC2_ID (0xf103)

* physical ID(s) | ?

* value | ?

*

* \brief A2 ADC sensor

* \todo scale and calibration

*/

int FRSKY_SP_ADC2_ID = 0xf103;

/**

* info | comment

* ---- | -------

* sensor ID(s) | FRSKY_SP_BATT_ID (0xf104)

* physical ID(s) | ?

* value | ?

*

* \brief unknown

* \todo what is this sensor ?

*/

int FRSKY_SP_BATT_ID = 0xf104;

// Declare 2D array

Serial myPort; // The serial port

float x, y;

int sensorID;

String myString = null;

int[] myBuffer = new int [250];

byte[] myBytes = new byte [20];

byte[] SizeofmyBytes = new byte [20];

int [] sensorArrayID = new int [255];

int errors = 0;

boolean failsafe = false;

int idx, inByte, lost, packetErrors, pollCount, SensorID;

long last_refresh = 0;

float ymag = 0;

float newYmag = 0;

float xmag = 0;

float newXmag = 0;

int yoffSet =100;

int yStartPos = 100;

int xoffSet = 150;

// Declare 2D array

int[][] telemetryArray = new int[254][254];

int[][] myArray = new int[254][254];

int cols = width;

int rows = height;

int i=0, j=0, c=0;

int CurrentSensorValue=0;

int currentPosition;

Boolean startByteFound = false; //set the startup of reading Bytes

// ********************************************************************************

void setup()

{

size(1200, 800, FX2D); //give good dashboard resolutions

//size(1200, 800, P3D); //used for drawing cubes

//fullScreen(); //This sets it to full screen, but comment out the size commmand above

pixelDensity(1); //Sharpen the text

// List all the available serial ports

printArray(Serial.list());

// Open the port you are using at the rate you want:

// myPort = new Serial(this, Serial.list()[0], 100000,'E',8,2);

myPort = new Serial(this, Serial.list()[0], 57600);

//myString = myPort.readStringUntil(15); //clean the first buffer

//println(myPort); //Debug to show the port opened

//background(255);

// fill(255); //Set text colour

//textSize(12); //Reset text size

//stroke(255);

currentPosition = -1; //this is to signify a start-up situation

}

// ********************************************************************************

void draw()

{

while (myPort.available() > 0) {

idx=0; //reset the counter

inByte = myPort.read(); //Read the Byte that just came in

//if (myPort.read () == 0x7E) {

if (inByte == 126)

{ //Start Byte Found

startByteFound = true;

drawCanvas(); //We now know that this is new packet, so close the previous packet and draw the results on the canvas.

currentPosition = 1; //Set the position in the array as the first

yoffSet = 100; //Rest the canvas start positions

xoffSet = 100; //Rest the canvas start positions

}

else //it's either a subsequent byte or a lost packet

{

if (currentPosition >= 0) //Check if it's not a startup package with value -1 then continue

{

if (startByteFound) //If the start byte has already been found then this is a subseqent byte, else its a lost packet and this can be ignored

{

currentPosition ++; //We know this is a second or subsequent byte of a packet, so increase the Byte position in the packet

//Check if this is the second byte position as this would then be the sensorID which needs to be stored

if (currentPosition ==2)

{

sensorID = inByte;

countSensorOccurrances(); //store the number of times the sensors is scanned

}

stuffArray(); //Stuff the array with the bytes that come into to the packet

}

} //if StartByteFound

}

}

}

void stuffArray() {

//add the current byte value to the current sensorID. Do this until a new startByte is received

myArray [sensorID] [currentPosition] = inByte;

myArray [sensorID] [14] = currentPosition; //store the size of the packet

}

void countSensorOccurrances() {

CurrentSensorValue = myArray[sensorID][15]; //Get current sensor 'hit' count

myArray[sensorID][15]= CurrentSensorValue + 1;//increment the value for the correspoing sensorID position and put it back. if (CurrentSensorValue >= 200) CurrentSensorValue=0;

}

void drawCanvas() {

//Draw a table of values

background(100); //Set the table background to grey

fill(255); //Set text colour to black

//Display currernt packet size

noStroke(); //turn off the board lines

textSize(14); //set text size

text("Size of Packet", 100,50);

rect(110, 60, currentPosition , 5);

//Display the current sensorID

text("SensorID", 250,50);

text(sensorID, 250,70);

//Create the table title

textSize(26); //set larger text for the title

text("FrySky S.PORT - Status Dashboard", width/2-150, 30); //Place the title in the centre of the dashboard

//Create the table grid lines

stroke(0,255,0); //set the colour of the grid lines

line(xoffSet, yoffSet, 1000, yoffSet);

textSize(14); //set text size

//create a list of all sensors found

for( i=0;i<250;i++)

{

if (myArray[i][15]!= 0) //ignore empty sensor ID's

{

yoffSet = 20 + yoffSet; //start postiion from top

text("Sensor " + i + " = " , 150,yoffSet);

xoffSet = 190;

for (j=0;j<11;j++)

{

xoffSet = 60 + xoffSet; //start postiion from left

text( myArray[i][j], xoffSet,yoffSet);

line(xoffSet -5, yStartPos, xoffSet-5, yoffSet + 5);

}

text( myArray[i][14], xoffSet+60,yoffSet ); // Print the packetsize values

line(150, yoffSet+5, 1000, yoffSet + 5);

line(xoffSet -5 + 60, yStartPos, xoffSet-5+60, yoffSet + 5);

text( myArray[i][15], xoffSet+120,yoffSet ); // Print the occurance values

line(150, yoffSet+5, 1000, yoffSet + 5);

line(xoffSet -5 + 120, yStartPos, xoffSet-5+120, yoffSet + 5);

//Draw a little packet size indicator bar

}

}

}

void echoResponse(){

//Draw the list of results

//send a response

//if (sensorID == 0xE4)

//{ // dec228 - Physical ID 5 - RPM

// sendData(FRSKY_SP_RPM, 11111);

//myPort.write(0x10);

//myPort.write(sensorID);

//myPort.write(sensorID); //sensorID

//myPort.write(sensorID); //Value

//myPort.write(0xFF); //False CRC

//}

//myPort.write(0x10);

//myPort.write(sensorID);

//myPort.write(sensorID); //sensorID

//myPort.write(sensorID); //Value

//myPort.write(0xFF); //False CRC

}

// ********************************************************************************

//void sendData(int id, int val) {

// myPort.write(id);

// myPort.write(val);

//}