-
Notifications
You must be signed in to change notification settings - Fork 30
API Reference
By design, the API is meant to be simplistic & easy to integrate into your existing sketches/projects.
Code examples are provided as 'pseudo-code' to help convey the context of a particular function and how it should be used
Resource management is provided here to explain how the resources (IE memory and hardware) used by CRSF for Arduino can be dynamically allocated as-and-when needed, in order to prevent memory and hardware leaks. Emphasis is placed on memory leaks in particular, because of how vulnerable embedded systems are to it, and because memory leaks are normalised in the maker space. Especially with those that are using the Arduino ecosystem.
You will see references to keywords such as new
and delete
, plus references to CRSF for Arduino being globally declared as a null pointer, and being initialised only when needed. This is in stark contrast to how Arduino teaches you how to manage objects.
To convey the point (using CRSF for Arduino in the examples) here, this is what Arduino teaches you:
/* Add the class' header to my "sketch". */
#include "CRSFforArduino.h"
/* Global declaration and initialisation of a class. */
CRSFforArduino crsf = CRSFforArduino();
void setup()
{
/* Initialise the class. */
crsf.begin();
}
void loop()
{
/* Run the class' main function. */
crsf.update();
}
So, what if your firmware is finished using the class object?
You would do something like this in your loop()
, right?
void loop()
{
/* Simulate the class object's main function running for 15 seconds, and then ending itself. */
static bool crsfEnded = false;
if (crsfEnded == false)
{
static uint32_t start = millis();
uint32_t now = millis();
/* Run the main function for 15 seconds. */
if (now - start <= 15000)
{
crsf.update();
}
else
{
/* The class has finished running.
It's time to end it. */
crsf.end();
/* Set the flag to tell the rest
of the sketch that we are done here. */
crsfEnded = true;
}
}
}
While this is "simple" and "easy-to-read", it is bad practice.
What you have essentially done here is you have created a memory leak.
In the context of embedded systems (which often has very small flash memory footprints), memory leaks MUST
be avoided.
This is because a memory leak eventually consumes all available memory, and a system failure will result from the next call to allocate memory that simply does not exist. IE There is not enough memory available to allocate to something, resulting in the target micro-controller crashing or "locking up".
You do the maths about what happens if your embedded micro-controller is in say, for example, your home-brewed flight controller that you have put in one of your prized expensive helicopters, and that memory leak causes the system failure that I have described here.
Allow me to show you the light among the darkness.
A better way to manage resources is to, first off, declare your class objects as null pointers, like so...
/* Add the class' header to my "sketch". */
#include "CRSFforArduino.h"
/* Global class declaration as a null pointer. */
CRSFforArduino *crsf = nullptr;
This means no resources (IE memory and hardware) will be allocated until you tell it to do so.
This also means that, when resources are allocated, none of those resources will be freed until you tell your firmware to do so.
Fortunately, I have taken great care with respect to how CRSF for Arduino manages resources.
In the example below, I show you how to allocate memory and hardware, plus how to free those resources up when your firmware is finished using CRSF for Arduino...
void setup()
{
/* Allocate the required memory for the class. */
crsf = new CRSFforArduino();
/* Allocate the required hardware of the class. */
if (crsf->begin() == true)
{
/* Hardware allocated successfully.
The rest of the class' functionality may be initialised here. */
}
else
{
/* Hardware allocation failed.
Anything that succeeded during the call to 'begin()' MUST be
freed to avoid hardware and memory leaks. */
crsf->end();
delete crsf;
crsf = nullptr;
}
}
void loop()
{
/* Guard the class' API functions from being accessed
if the resources allocated were freed. */
if (crsf != nullptr)
{
static uint32_t start = millis();
uint32_t now = millis();
/* Run the class' main function for 15 seconds. */
if (now - start <= 15000)
{
crsf->update();
}
else
{
/* Timeout expired.
All resources that were allocated MUST be freed,
starting with freeing up the hardware. */
crsf->end();
/* Free up the memory that was allocated. */
delete crsf;
crsf = nullptr;
}
}
}
Warning
You MUST NOT use any of CRSF for Arduino's API functions before instantiation.
You MUST create an instance of CRSF for Arduino before you can use its API.
CRSFforArduino()
This is the default constructor. It creates an instance of CRSF for Arduino.
Serial1
from the Arduino API is used to provide communications to & from your TBS Crossfire or ExpressLRS receiver.
In the case of STM32 targets, Serial1
is not available on some STM32 targets.
Therefore, the next available HardwareSerial
instance is automatically picked based on availability. EG CRSF for Arduino will default to Serial2
if it's available. If Serial2
is not available, Serial3
is automatically picked.
For ESP32 targets, pins 0
and 1
are used, or (if defined) D0
and D1
are used.
CRSFforArduino(HardwareSerial *serialPort)
This constructor is where you can provide your own custom HardwareSerial
instance.
This gives you the flexibility of assigning any arbitrary UART port to CRSF for Arduino.
CRSFforArduino(HardwareSerial *serialPort, int RxPin, int TxPin)
This constructor is currently specific to ESP32 targets.
serialPort
is a pointer to your desired serial port object.
RxPin
is your specified UART Rx pin number on your development board.
TxPin
is your specified UART Tx pin number on your development board.
In the example below, CRSF for Arduino will use the default HardwareSerial
instance.
In most cases, this is Serial1
and the default pins are 0
for UART Rx and 1
for UART Tx.
#include "CRSFforArduino.h"
/* Declare a null pointer to CRSF for Arduino. */
CRSFforArduino *crsf = nullptr;
void setup()
{
/* Instantiate CRSF for Arduino. */
crsf = new CRSFforArduino();
}
void loop()
{
/* Your main code here... */
}
If desired, you can customise the pin assignments either by the HardwareSerial
pointer (if your target is SAMD21 or SAMD51 based) or you can specify the pin numbers (if your target is ESP32 based).
For SAMD21 and SAMD51 targets, Adafruit have already documented how to do custom UART pin assignments. In the context of CRSF for Arduino, all you need to do is pass in your custom Uart
object into the CRSFforArduino(HardwareSerial *serialPort)
constructor.
For ESP32 targets, this is what you do...
#include "CRSFforArduino.h"
/* Define your custom UART pinouts here. */
#define MY_UART_RX_PIN 2
#define MY_UART_TX_PIN 3
/* Declare a null pointer to CRSF for Arduino. */
CRSFforArduino *crsf = nullptr;
void setup()
{
/* Instantiate CRSF for Arduino with your custom pins. */
crsf = new CRSFforArduino(&Serial1, MY_UART_RX_PIN, MY_UART_TX_PIN);
}
void loop()
{
/* Your main code here... */
}
This is where you actually start using CRSF for Arduino.
-
CRSFforArduino::begin()
- Optional parameter:
-
baud_rate
- Use this to set your desired baud rate. If no value is provided, the default is the baud rate defined by The Crossfire Protocol.
-
- Optional parameter:
- Return type:
bool
-
true
if initialisation is successful. -
false
if initialisation failed.
-
NB: The appropriate baud rate and configuration is done for you.
The example below demonstrates how to initialise CRSF for Arduino.
NB: The return check on the initialiser has been omitted for simplicity's sake.
#include "CRSFforArduino.h"
/* Declare a null pointer to CRSF for Arduino. */
CRSFforArduino *crsf = nullptr;
void setup()
{
/* Instantiate CRSF for Arduino. */
crsf = new CRSFforArduino();
/* Initialise CRSF for Arduino. */
crsf->begin();
}
void loop()
{
/* Your main code here... */
}
CRSFforArduino::end()
This de-initialises CRSF for Arduino by disabling hardware serial communications with your RC receiver, & freeing up the previously configured hardware serial port.
[!WARN] This does not delete any previously allocated memory for CRSF for Arduino.
You MUST delete this memory yourself usingdelete <YOUR_CRSF_OBJECT>
, where<YOUR_CRSF_OBJECT>
is the name you gave to your instance of > CRSF for Arduino. EG If you're following the examples above, the name given iscrsf
. Sodelete crsf;
The example below shows how to correctly de-initialise CRSF for Arduino and free up the resources that were allocated to it.
In this example, a limited number of executions are done before CRSF for Arduino is completely destroyed.
#include "CRSFforArduino.h"
int loopExecutions = 0;
CRSFforArduino *crsf = nullptr;
void setup()
{
crsf = new CRSFforArduino();
if (crsf->begin() != true)
{
/* CRSF for Arduino failed to initialise.
Clean-up the resources that it previously allocated, and then free up the memory it allocated. */
crsf->end();
delete crsf;
crsf = nullptr;
}
}
void loop()
{
/* Guard CRSF for Arduino's API with a null check. */
if (crsf != nullptr)
{
/* Increment the loop executions counter. */
loopExecutions++;
/* After five loop executions, destroy CRSF for Arduino. */
if (loopExecutions >= 5)
{
crsf->end();
delete crsf;
crsf = nullptr;
}
}
}
CRSF for Arduino uses an event-driven API for reading RC Channels, Link Statistics, and setting Flight Modes.
These events are handled internally and they have their own respective callback functions that you can register in your sketches.
CRSFforArduino::update()
This function SHOULD
be placed inside your main loop()
or in some other function or service routine that is updated as quickly as possible.
It should be updated at least every 4 milliseconds or faster.
The example below is a sample of how CRSF for Arduino's main function SHOULD
be implemented in your firmware.
#include "CRSFforArduino.h"
CRSFforArduino *crsf = nullptr;
void setup()
{
crsf = new CRSFforArduino();
/* Initialise CRSF for Arduino, and clean up
any allocated resources if initialisation fails. */
if (crsf->begin() != true)
{
crsf->end();
delete crsf;
crsf = nullptr;
}
}
void loop()
{
/* Guard CRSF for Arduino's API with a null check. */
if (crsf != nullptr)
{
/* Call CRSF for Arduino's main function here. */
crsf->update();
}
}
This is your first port of call, because it gives you insight as to the stability of your receiver's connection with your transmitter.
Here, you have access to the following information:
- RSSI (dBm)
- Link Quality Information (%)
- Signal-to-Noise Ratio (dB)
- Transmitter Module Power (W)
The following API function is used to set the Link Statistics callback handler:
-
CRSFforArduino::setLinkStatisticsCallback(void (*callback)(serialReceiverLayer::link_statistics_t linkStatistics))
- This API function is used to set the Link Statistics callback handler.
- Parameter:
-
linkStatistics
- This structure contains all of your link statistics data.
-
The example below is a sample of how Link Statistics MAY
be implemented in your firmware.
#include "CRSFforArduino.h"
CRSFforArduino *crsf = nullptr;
void onLinkStatisticsUpdate(serialReceiverLayer::link_statistics_t);
void setup()
{
crsf = new CRSFforArduino();
/* Initialise CRSF for Arduino. */
if (crsf->begin() == true)
{
/* CRSF for Arduino initialised successfully.
We can now register the Link Statistics event. */
crsf->setLinkStatisticsCallback(onLinkStatisticsUpdate);
}
else
{
/* Clean up any resources,
if initialisation fails. */
crsf->end();
delete crsf;
crsf = nullptr;
}
}
void loop()
{
/* Guard CRSF for Arduino's API with a null check. */
if (crsf != nullptr)
{
/* Call CRSF for Arduino's main function here. */
crsf->update();
}
}
void onLinkStatisticsUpdate(serialReceiverLayer::link_statistics_t linkStatistics)
{
/* This is your Link Statistics Event Callback.
By using the linkStatistics parameter that's passed in,
you have access to the following:
- linkStatistics.rssi
- linkStatistics.lqi
- linkStatistics.snr
- linkStatistics.tx_power
For the purposes of this example, these values are simply
printed to the Serial Monitor at a rate of 5 Hz. */
static unsigned long lastPrint = 0;
if (millis() - lastPrint >= 200)
{
lastPrint = millis();
Serial.print("Link Statistics: ");
Serial.print("RSSI: ");
Serial.print(linkStatistics.rssi);
Serial.print(", Link Quality: ");
Serial.print(linkStatistics.lqi);
Serial.print(", Signal-to-Noise Ratio: ");
Serial.print(linkStatistics.snr);
Serial.print(", Transmitter Power: ");
Serial.println(linkStatistics.tx_power);
}
}
Setting up RC channels is done by telling CRSF for Arduino what callback handler you are using.
You can do this using this API function:
-
CRSFforArduino::setRcChannelsCallback(void (*callback)(serialReceiverLayer::rcChannels_t *rcChannels))
- This sets the callback handler function for your RC Channels.
- Parameter:
-
rcChannels
- This structure is automatically passed in, and it contains all sixteen of your RC channels, plus an additional fail-safe flag.
-
NB: The RC Channels values themselves are raw values. IE These are the values that have come directly off of your receiver.
If you need to convert these values over to "microseconds", you SHOULD
use CRSFforArduino::rcToUs(uint16_t rc)
.
The example below is a sample of how RC Channels MAY
be implemented in your firmware.
#include "CRSFforArduino.h"
CRSFforArduino *crsf = nullptr;
/* A flag to hold the fail-safe status. */
bool isFailsafeActive = false;
/* RC Channels data. */
int rcChannelCount = 8;
const char *rcChannelNames[] = {
"A",
"E",
"T",
"R",
"Aux1",
"Aux2",
"Aux3",
"Aux4",
"Aux5",
"Aux6",
"Aux7",
"Aux8",
"Aux9",
"Aux10",
"Aux11",
"Aux12"};
/* RC Channels Event Callback. */
void onReceiveRcChannels(serialReceiverLayer::rcChannels_t *rcData);
void setup()
{
crsf = new CRSFforArduino();
/* Initialise CRSF for Arduino. */
if (crsf->begin() == true)
{
/* CRSF for Arduino initialised successfully.
We can now register the RC Channels event. */
crsf->setRcChannelsCallback(onReceiveRcChannels);
/* Constrain the RC Channels Count to the maximum number
of channels that are specified by The Crossfire Protocol.*/
rcChannelCount = rcChannelCount > crsfProtocol::RC_CHANNEL_COUNT ? crsfProtocol::RC_CHANNEL_COUNT : rcChannelCount;
}
else
{
/* Clean up any resources,
if initialisation fails. */
crsf->end();
delete crsf;
crsf = nullptr;
}
}
void loop()
{
/* Guard CRSF for Arduino's API with a null check. */
if (crsf != nullptr)
{
/* Call CRSF for Arduino's main function here. */
crsf->update();
}
}
void onReceiveRcChannels(serialReceiverLayer::rcChannels_t *rcData)
{
/* This is your RC Channels Event Callback.
Here, you have access to all 16 11-bit channels,
plus an additional "failsafe" flag that tells you whether-or-not
your receiver is connected to your controller's transmitter module.
Using the rcData parameter that was passed in,
you have access to the following:
- failsafe - A boolean flag indicating the "Fail-safe" status.
- value[16] - An array consisting of all 16 received RC channels.
NB: RC Channels are RAW values and are NOT in "microseconds" units.
For the purposes of this example, the fail-safe flag is used to centre
all channels except for Channel 5 (AKA Aux1). Aux1 is set to the
"Disarmed" position.
The RC Channels themselves are all converted to "microseconds" for
visualisation purposes, and printed to the Serial Monitor at a rate
of 100 Hz. */
if (rcData->failsafe)
{
if (!isFailsafeActive)
{
isFailsafeActive = true;
/* Centre all RC Channels, except for Channel 5 (Aux1). */
for (int i = 0; i < rcChannelCount; i++)
{
if (i != 4)
{
rcData->value[i] = 992;
}
}
/* Set Channel 5 (Aux1) to its minimum value. */
rcData->value[4] = 191;
Serial.println("[Sketch | WARN]: Failsafe detected.");
}
}
else
{
/* Set the failsafe status to false. */
if (isFailsafeActive)
{
isFailsafeActive = false;
Serial.println("[Sketch | INFO]: Failsafe cleared.");
}
}
/* Here is where you may write your RC channels implementation.
For this example, RC Channels are simply sent to the Serial Monitor. */
static uint32_t lastPrint = millis();
if (millis() - lastPrint >= 10)
{
lastPrint = millis();
Serial.print("[Sketch | INFO]: RC Channels: <");
for (int i = 0; i < rcChannelCount; i++)
{
Serial.print(rcChannelNames[i]);
Serial.print(": ");
Serial.print(crsf->rcToUs(rcData->value[i]));
if (i < (rcChannelCount - 1))
{
Serial.print(", ");
}
}
Serial.println(">");
}
}
Flight Modes are divided up into two subsections:
-
Standard modes
These are based on the Flight Modes used by Betaflight 4.3. -
Custom modes
You assign a text-based string to your desired flight mode.
In either case, the API functions are universal, and are as follows:
-
CRSFforArduino::setFlightMode(serialReceiverLayer::flightModeId_t flightModeId, const char *flightModeName, uint8_t channel, uint16_t min, uint16_t max)
- Use this to set your desired Flight Mode, assign the Flight Mode to your desired RC Channel, and provide the minimum and maximum values in which the Flight Mode will be active.
- Parameters:
-
flightModeId
- This is the index ID of your chosen Flight Mode.
-
flightModeName
- This is where you provide your Flight Mode with a name.
It is particularly useful with Custom Flight Modes.
- This is where you provide your Flight Mode with a name.
-
channel
- The channel number you are assigning your flight mode to.
When this channel is between the
min
andmax
values, this Flight Mode is active.
- The channel number you are assigning your flight mode to.
When this channel is between the
-
min
- The minimum channel value (in microseconds) that the Flight Mode will be activated.
-
max
- The maximum channel value (in microseconds) that the Flight Mode will be activated.
-
- Return type:
bool
-
true
if the Flight Mode was set. -
false
otherwise.
Additionally, ifCRSF_FLIGHTMODES_ENABLED
inCFA_Config.hpp
is0
,setFlightMode()
will always returnfalse
.
-
-
CRSFforArduino::setFlightModeCallback(void (*callback)(serialReceiverLayer::flightModeId_t flightMode))
- This is where you set the callback handler for the Flight Modes Event.
- Parameter:
-
flightMode
- This is automatically passed in, and you can read from this inside the Flight Modes Event Callback to determine what Flight Mode is active.
-
The example below is a sample of how Flight Modes MAY
be implemented in your firmware.
#include "CRSFforArduino.hpp"
#define FLIGHT_MODE_ARM_CHANNEL 5
#define FLIGHT_MODE_ARM_MIN 1000
#define FLIGHT_MODE_ARM_MAX 1800
#define FLIGHT_MODE_ACRO_CHANNEL 6
#define FLIGHT_MODE_ACRO_MIN 750
#define FLIGHT_MODE_ACRO_MAX 1250
#define FLIGHT_MODE_ANGLE_CHANNEL 6
#define FLIGHT_MODE_ANGLE_MIN 1250
#define FLIGHT_MODE_ANGLE_MAX 1750
#define FLIGHT_MODE_HORIZON_CHANNEL 6
#define FLIGHT_MODE_HORIZON_MIN 1750
#define FLIGHT_MODE_HORIZON_MAX 2250
CRSFforArduino *crsf = nullptr;
/* Declare the onFlightModeUpdate callback function. */
void onFlightModeUpdate(serialReceiverLayer::flightModeId_t);
void setup()
{
/* Initialise CRSF for Arduino. */
crsf = new CRSFforArduino();
if (crsf->begin() != false)
{
/* CRSF for Arduino initialised successfully.
Now, the Flight Modes can be configured. */
crsf->setFlightMode(serialReceiverLayer::FLIGHT_MODE_DISARMED, FLIGHT_MODE_ARM_CHANNEL, FLIGHT_MODE_ARM_MIN, FLIGHT_MODE_ARM_MAX);
crsf->setFlightMode(serialReceiverLayer::FLIGHT_MODE_ACRO, FLIGHT_MODE_ACRO_CHANNEL, FLIGHT_MODE_ACRO_MIN, FLIGHT_MODE_ACRO_MAX);
crsf->setFlightMode(serialReceiverLayer::FLIGHT_MODE_ANGLE, FLIGHT_MODE_ANGLE_CHANNEL, FLIGHT_MODE_ANGLE_MIN, FLIGHT_MODE_ANGLE_MAX);
crsf->setFlightMode(serialReceiverLayer::FLIGHT_MODE_HORIZON, FLIGHT_MODE_HORIZON_CHANNEL, FLIGHT_MODE_HORIZON_MIN, FLIGHT_MODE_HORIZON_MAX);
crsf->setFlightModeCallback(onFlightModeUpdate);
}
else
{
/* CRSF for Arduino failed to initialise. */
crsf->end();
delete crsf;
crsf = nullptr;
}
}
void loop()
{
/* Guard CRSF for Arduino's API functions. */
if (crsf != nullptr)
{
/* Run CRSF for Arduino's main function. */
crsf->update();
}
}
void onFlightModeUpdate(serialReceiverLayer::flightModeId_t flightMode)
{
/* This is where you would normally write your own Flight Modes implementation.
For the purposes of this example, the current Flight Mode is printed to the Serial Monitor. */
/* Are we disarmed? */
bool isDisarmed = true;
if (flightMode != serialReceiverLayer::FLIGHT_MODE_DISARMED)
{
isDisarmed = false;
}
else if (isFailsafeActive)
{
isDisarmed = true;
}
/* Update the flight mode telemetry with the new value. */
crsf->telemetryWriteFlightMode(flightMode, isDisarmed);
/* Print the flight mode to the Serial Monitor, but only if the flight mode has changed. */
static serialReceiverLayer::flightModeId_t lastFlightMode = serialReceiverLayer::FLIGHT_MODE_DISARMED;
if (flightMode != lastFlightMode)
{
lastFlightMode = flightMode;
Serial.print("[Sketch | INFO]: Flight Mode: ");
switch (flightMode)
{
case serialReceiverLayer::FLIGHT_MODE_DISARMED:
Serial.println("Disarmed");
break;
case serialReceiverLayer::FLIGHT_MODE_ACRO:
Serial.println("Acro");
break;
case serialReceiverLayer::FLIGHT_MODE_WAIT:
Serial.println("Wait for GPS Lock");
break;
case serialReceiverLayer::FLIGHT_MODE_FAILSAFE:
Serial.println("Failsafe");
break;
case serialReceiverLayer::FLIGHT_MODE_GPS_RESCUE:
Serial.println("GPS Rescue");
break;
case serialReceiverLayer::FLIGHT_MODE_PASSTHROUGH:
Serial.println("Passthrough");
break;
case serialReceiverLayer::FLIGHT_MODE_ANGLE:
Serial.println("Angle");
break;
case serialReceiverLayer::FLIGHT_MODE_HORIZON:
Serial.println("Horizon");
break;
case serialReceiverLayer::FLIGHT_MODE_AIRMODE:
Serial.println("Airmode");
break;
default:
Serial.println("Unknown");
break;
}
}
}
The example below is a sample of how Custom Flight Modes MAY
be implemented in your firmware.
Helicopter-centric flight modes (IE "Normal", "Idle-Up 1", and "Idle-Up 2") are used to convey the implementation of Custom Flight Modes.
#include "CRSFforArduino.hpp"
#define FLIGHT_MODE_ARM_CHANNEL 5
#define FLIGHT_MODE_ARM_MIN 1000
#define FLIGHT_MODE_ARM_MAX 1800
#define FLIGHT_MODE_NORMAL_CHANNEL 6
#define FLIGHT_MODE_NORMAL_MIN 750
#define FLIGHT_MODE_NORMAL_MAX 1250
#define FLIGHT_MODE_IDLEUP1_CHANNEL 6
#define FLIGHT_MODE_IDLEUP1_MIN 1250
#define FLIGHT_MODE_IDLEUP1_MAX 1750
#define FLIGHT_MODE_IDLEUP2_CHANNEL 6
#define FLIGHT_MODE_IDLEUP2_MIN 1750
#define FLIGHT_MODE_IDLEUP2_MAX 2250
CRSFforArduino *crsf = nullptr;
/* Declare the onFlightModeUpdate callback function. */
void onFlightModeUpdate(serialReceiverLayer::flightModeId_t);
void setup()
{
/* Initialise CRSF for Arduino. */
crsf = new CRSFforArduino();
if (crsf->begin() != false)
{
/* CRSF for Arduino initialised successfully.
Now, the Flight Modes can be configured. */
crsf->setFlightMode(serialReceiverLayer::FLIGHT_MODE_DISARMED, "Disarmed", FLIGHT_MODE_ARM_CHANNEL, FLIGHT_MODE_ARM_MIN, FLIGHT_MODE_ARM_MAX);
crsf->setFlightMode(serialReceiverLayer::CUSTOM_FLIGHT_MODE1, "Normal", FLIGHT_MODE_NORMAL_CHANNEL, FLIGHT_MODE_NORMAL_MIN, FLIGHT_MODE_NORMAL_MAX);
crsf->setFlightMode(serialReceiverLayer::CUSTOM_FLIGHT_MODE2, "Idle-Up 1", FLIGHT_MODE_IDLEUP1_CHANNEL, FLIGHT_MODE_IDLEUP1_MIN, FLIGHT_MODE_IDLEUP1_MAX);
crsf->setFlightMode(serialReceiverLayer::CUSTOM_FLIGHT_MODE3, "Idle-Up 2", FLIGHT_MODE_IDLEUP2_CHANNEL, FLIGHT_MODE_IDLEUP2_MIN, FLIGHT_MODE_IDLEUP2_MAX);
crsf->setFlightModeCallback(onFlightModeUpdate);
}
else
{
/* CRSF for Arduino failed to initialise. */
crsf->end();
delete crsf;
crsf = nullptr;
}
}
void loop()
{
/* Guard CRSF for Arduino's API functions. */
if (crsf != nullptr)
{
/* Run CRSF for Arduino's main function. */
crsf->update();
}
}
void onFlightModeUpdate(serialReceiverLayer::flightModeId_t flightMode)
{
/* This is where you would normally write your own Flight Modes implementation.
For the purposes of this example, the current Flight Mode is printed to the Serial Monitor. */
/* Are we disarmed? */
bool isDisarmed = true;
if (flightMode != serialReceiverLayer::FLIGHT_MODE_DISARMED)
{
isDisarmed = false;
}
else if (isFailsafeActive)
{
isDisarmed = true;
}
/* Update the flight mode telemetry with the new value. */
crsf->telemetryWriteFlightMode(flightMode, isDisarmed);
/* Print the flight mode to the Serial Monitor, but only if the flight mode has changed. */
static serialReceiverLayer::flightModeId_t lastFlightMode = serialReceiverLayer::FLIGHT_MODE_DISARMED;
if (flightMode != lastFlightMode)
{
lastFlightMode = flightMode;
Serial.print("[Sketch | INFO]: Flight Mode: ");
switch (flightMode)
{
case serialReceiverLayer::FLIGHT_MODE_DISARMED:
Serial.println("Disarmed");
break;
case serialReceiverLayer::CUSTOM_FLIGHT_MODE1:
Serial.println("Normal");
break;
case serialReceiverLayer::FLIGHT_MODE_WAIT:
Serial.println("Wait for GPS Lock");
break;
case serialReceiverLayer::FLIGHT_MODE_FAILSAFE:
Serial.println("Failsafe");
break;
case serialReceiverLayer::FLIGHT_MODE_GPS_RESCUE:
Serial.println("GPS Rescue");
break;
case serialReceiverLayer::FLIGHT_MODE_PASSTHROUGH:
Serial.println("Passthrough");
break;
case serialReceiverLayer::CUSTOM_FLIGHT_MODE2:
Serial.println("Idle-Up 2");
break;
case serialReceiverLayer::CUSTOM_FLIGHT_MODE3:
Serial.println("Idle-Up 3");
break;
default:
Serial.println("Unknown");
break;
}
}
}
CRSF for Arduino supports telemetry feedback to your controller.
Note
CRSF for Arduino does not provide any hardware sensor drivers.
You MUST
provide these drivers yourself.
There are many fantastic sensor drivers available on GitHub, PlatformIO Registry and Arduino Library Manager.
Writing (and maintaining) sensor drivers is outside the scope of CRSF for Arduino.
You can implement Telemetry in your firmware with the following API functions:
-
CRSFforArduino::telemetryWriteAttitude(int16_t roll, int16_t pitch, int16_t yaw)
- This function sends attitude telemetry back to your controller.
All three parameters are in decidegress - EG Passing in a value of100
will translate to 10 degrees on your controller's telemetry screen.
If you're using either a IMU or 9-DoF fused orientation sensor, youMAY
use the data from that sensor to provide attitude data input to the Attitude Telemetry. - Parameters:
-
roll
Roll axis data. -
pitch
Pitch axis data. -
yaw
Yaw axis data.
-
- This function sends attitude telemetry back to your controller.
-
CRSFforArduino::telemetryWriteBaroAltitude(uint16_t altitude, int16_t vario)
- This function sends barometric altitude and variometer telemetry back to your controller.
If you have a barometric pressure sensor, youMAY
use the data from that sensor to provide altitude and variometer data input to the Barometric Altimeter Telemetry. - Parameters:
-
altitude
Altitude data in decimetres - EG Passing in a value of100
will translate to 10 metres on your controller's telemetry screen. -
vario
Variometer (AKA "Rate-of-climb/descent") data in centimetres per second. This unit is 1:1, so a value of 10 is 10 centimetres per second on your controller's telemetry screen.
-
- This function sends barometric altitude and variometer telemetry back to your controller.
-
CRSFforArduino::telemetryWriteBattery(float voltage, float current, uint32_t fuel, uint8_t percent)
- One of the most vital telemetric categories is battery telemetry.
This function sends your battery telemetry back to your controller. - Parameters:
-
voltage
Battery voltage in millivolts * 100 - EG Passing in a value of380.0F
will translate to 3.8 volts on your controller's telemetry screen. -
current
Current drawn in milliamperes * 10 - EG Passing in a value of150.0F
will translate to 1.5 amperes on your controller's telemetry screen. -
fuel
The battery's "fuel" (AKA The amount of energy the battery has consumed) in milliampere-hours. This unit is 1:1, so passing in a value of2200
will translate to 2200 mAh on your controller's telemetry screen.
NB: You will need to provide your own calculations forfuel
, as none is provided for you. -
percent
The remaining percentage of your battery's "fuel" as a percentage of the maximum "fuel" available.
NB: You will need to provide your own calculations forpercentage
, as none is provided for you.
-
- One of the most vital telemetric categories is battery telemetry.
-
CRSFforArduino::telemetryWriteFlightMode(serialReceiverLayer::flightModeId_t flightMode, bool disarmed)
- This function sends the
flightMode
and whether-or-not your firmware isdisarmed
back to your controller. - Parameters:
-
flightMode
The Flight Mode that your firmware is currently in. -
disarmed
Wether-or-not your firmware is disarmed.
true
is disarmed andfalse
is armed.CRSFforArduino::telemetryWriteGPS(float latitude, float longitude, float altitude, float speed, float groundCourse, uint8_t satellites)
-
- This function sends location, speed, altitude, and number of satellites in view data back to your controller.
If you have a GPS/GNSS (Collectively referred to as "GPS"), youMAY
use the data from that to provide location, altitude, speed, course, and satellites-in-view data for GPS Telemetry. - Parameters:
-
latitude
Latitude data is in decimal degrees. -
longitude
Longitude data is in decimal degrees. -
altitude
Altitude data is in centimetres -
speed
Speed data is in centimetres per second. -
groundCourse
Course data is in degrees. -
satellites
Number of satellites in view of the GPS.
This is an indicator of how accurate the aforementioned GPS data is.
-
- This function sends the
The code example below demonstrates how Telemetry MAY
be implemented in your firmware.
NB: "Sensor Values" are picked by arbitrary numbers or are randomly generated.
In a real situation, you MUST
provide your own sensor drivers to provide accurate data for your Telemetry.
#include "CRSFforArduino.hpp"
CRSFforArduino *crsf = nullptr;
/* Initialise the attitude telemetry with default values. */
int16_t roll = 0; // Roll is in decided degrees (eg -200 = -20.0 degrees).
int16_t pitch = 0; // Pitch is in decided degrees (eg 150 = 15.0 degrees).
uint16_t yaw = 0; // Yaw is in decided degrees (eg 2758 = 275.8 degrees).
/* Initialise the barometric altitude telemetry with default values. */
uint16_t baroAltitude = 0; // Barometric altitude is in decimetres (eg 10 = 1.0 metres).
int16_t verticalSpeed = 0; // Vertical speed is in centimetres per second (eg 50 = 0.5 metres per second).
/* Initialise the battery sensor telemetry with default values. */
float batteryVoltage = 420.0F; // Battery voltage is in millivolts (mV * 100).
float batteryCurrent = 000.0F; // Battery current is in milliamps (mA * 10).
uint32_t batteryFuel = 100; // Battery fuel is in milliamp hours (mAh).
uint8_t batteryPercent = 100; // Battery percentage remaining is in percent (0 - 100).
/* Initialise the GPS telemetry data with default values. */
float latitude = -41.18219482686493F; // Latitude is in decimal degrees.
float longitude = 174.9497131419602F; // Longitude is in decimal degrees.
float altitude = 100.0F; // Altitude is in centimetres.
float speed = 0.0F; // Speed is in cm/s
float groundCourse = 275.8F; // Ground Course is in degrees.
uint8_t satellites = 7; // 7 satellites are in view (implies a 3D fix).
void pollSensors();
void setup()
{
/* Initialise CRSF for Arduino. */
crsf = new CRSFforArduino();
if (crsf->begin() != false)
{
/* CRSF for Arduino initialised successfully.
Nothing to configure for this example. */
}
else
{
/* CRSF for Arduino failed to initialise. */
crsf->end();
delete crsf;
crsf = nullptr;
}
}
void loop()
{
/* Guard CRSF for Arduino's API functions. */
if (crsf != nullptr)
{
/* Poll sensors and send telemetry. */
pollSensors();
/* Run CRSF for Arduino's main function. */
crsf->update();
}
}
void pollSensors()
{
/* Update sensor data at a rate of 10 Hz. */
static unsigned long lastSensorUpdate = millis();
if (millis() - lastSensorUpdate >= 200)
{
lastSensorUpdate = millis();
/* Update the attitude telemetry with the new values. */
crsf->telemetryWriteAttitude(roll, pitch, yaw);
/* Update the barometric altitude telemetry with the new values. */
crsf->telemetryWriteBaroAltitude(baroAltitude, verticalSpeed);
/* Update the battery sensor telemetry with the new values. */
crsf->telemetryWriteBattery(batteryVoltage, batteryCurrent, batteryFuel, batteryPercent);
/* Update the GPS telemetry data with the new values. */
crsf->telemetryWriteGPS(latitude, longitude, altitude, speed, groundCourse, satellites);
}
}
Tip
If you're having issues with your telemetry being slow to respond, try some of the following:
- Set the Telemetry Ratio on your transmitter module to suit your need.
You can do this either via the ExpressLRS Lua Script or you can do this on the module itself (if applicable).
The smaller the Telemetry Ratio number is, the faster your telemetry will update. EG 1:16 is significantly faster than 1:128. - Avoid the use of functions such as
delay()
anddelayMicroseconds()
in the mainloop()
of your sketches.
The use of these functions is considered bad practice, because it can cause other tasks to lag behind and completely miss their cue to operate.
If you need to keep a track of time, make use of watching the return values of eithermillis()
ormicros()
in the form of time stamps and compare the last time that time stamp was updated with the latest time stamp value. - Avoid long-running
for
,while
anddo{}while(/* condition */)
statements.
The use of these loopsSHOULD
be used sparingly and their implementationSHOULD
be restricted to iterating through data buffers or performing repetitive tasks that would otherwise involve writing finite amounts of the same function over-and-over again. - If your receiver is connected to your development board through "solder-less" hook-up (AKA jumper) wires, check your connections, as these are often the causes of intermittent fail-safes and your controller spamming "telemetry lost"-"telemetry recovered".