-
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;
}
}
}
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.
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.
In the example below, CRSF for Arduino will use the default HardwareSerial
instance.
In most cases, this is Serial1
.
#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... */
}
This is where you actually start using CRSF for Arduino.
CRSFforArduino::begin()
- 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.
This does not delete any previously allocated memory for CRSF for Arduino.
You MUST delete this memory yourself using delete <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 is crsf
. So delete 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 must 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.
#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 RSSI, Link Quality, Signal-to-Noise Ratio, and Transmitter Power.
CRSFforArduino::setLinkStatisticsCallback(void (*callback)(serialReceiverLayer::link_statistics_t linkStatistics))
#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.
There are two ways to implement RC channels in your projects:
- Process your RC channels in total isolation - IE Within the context of your desired RC Channels Callback Handler.
- Copy the parameter passed in by the callback to a globally declared instance of
serialReceiverLayer::rcChannels_t
.
You decide which method is best for your application. The examples provided in this section will walk you through both implementation techniques.
CRSFforArduino::setRcChannelsCallback(void (*callback)(serialReceiverLayer::rcChannels_t *rcChannels))
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)
.
#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(">");
}
}