Skip to content

Commit ce0fc7c

Browse files
authored
refactor(crsf): Performance & reliability improvements (#32)
1 parent 5538106 commit ce0fc7c

29 files changed

+2639
-1583
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ templates/
1414

1515
# Arduino IDE artefacts
1616
.development
17+
18+
# Developer's notes
19+
notes/

README.md

Lines changed: 89 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ With that being said, installation from the Releases tab is nearly identical to
9090
### The API
9191

9292
1. Add my library to your sketch with `#include "CRSFforArduino.h"`
93-
2. Underneath that, you need to declare `CRSFforArduino crsf = CRSFforArduino(&Serial1)`
94-
3. In your `setup()`, do `crsf.begin()` to start communicating with your connected ExpressLRS receiver.
95-
4. In your `loop()`, you need to call `crsf.update()`, as this polls your receiver for new RC channels data.
96-
5. Do `crsf.getChannel(n)` to get the raw value from your desired RC channel. Here, `n` refers to your channel number from 1 to 16.
93+
2. Underneath that, you need to declare `CRSFforArduino crsf = CRSFforArduino()`. For now, you can leave the parentheses empty. There is ongoing work to allow you to specify what pins your receiver is connected to. For now, it defaults to pins 0 & 1 for Tx & Rx respectively.
94+
3. In your `setup()`, do `crsf.begin()` to start communicating with your connected ExpressLRS receiver. In case something goes wrong, `crsf.begin()` returns a boolean value of `true` if initialisation is successful, & `false` if it is not.
95+
4. In your `loop()`, you need to call `crsf.update()`. This handles all of the data processing (including receiving RC channels & sending telemetry) & should be called as often as possible. You no longer need to read back the return value of `crsf.update()`, as it no longer returns anything. Everything is handled internally now.
96+
5. To read your RC channel values, use `crsf.readRcChannel(n)`. Here, `n` refers to your channel number from 1 to 16.
9797

98-
If you want to convert the raw channel value to microseconds, do `crsf.rcToUs(n)`, where `n` is the raw RC value you want to convert. `rcToUs()` will return the converted value in microseconds.
98+
If you want to read the raw RC value instead of microseconds from your RC channel, `readRcChannel()` can take an optional second argument of `true` to return the raw RC value instead of microseconds. For example, `crsf.readRcChannel(1, true)` will return the raw RC value from channel 1.
9999

100100
The example below demonstrates what your code should look like, using the instructions above:
101101

@@ -107,8 +107,8 @@ The example below demonstrates what your code should look like, using the instru
107107
#include "CRSFforArduino.h"
108108

109109
/* 2. Declare a CRSFforArduino object.
110-
You can call it literally anything you want, as long as you tell CRSF for Arduino what serial port your receiver is connected to. */
111-
CRSFforArduino crsf = CRSFforArduino(&Serial1);
110+
You can call it literally anything you want. */
111+
CRSFforArduino crsf = CRSFforArduino();
112112

113113
void setup()
114114
{
@@ -117,13 +117,19 @@ void setup()
117117
{
118118
;
119119
}
120+
Serial.println("Channels Example");
120121

121122
/* 3. Start communicating with a connected ELRS receiver. */
122-
crsf.begin();
123+
if (!crsf.begin())
124+
{
125+
Serial.println("CRSF for Arduino initialization failed!");
126+
while (1)
127+
{
128+
;
129+
}
130+
}
123131

124132
/* Pro tip: Always show a little message in the Serial Monitor to let you know that your sketch is initialized successfully. */
125-
Serial.println("Channels Example");
126-
delay(1000);
127133
Serial.println("Ready");
128134
delay(1000);
129135
}
@@ -133,24 +139,31 @@ void loop()
133139
/* 4. Update the main loop with new RC data. */
134140
crsf.update();
135141

136-
/* 5. Here, your RC channel values are converted to microseconds, & are sent to the Serial Monitor. */
137-
Serial.print("RC Channels <A: ");
138-
Serial.print(crsf.rcToUs(crsf.getChannel(1)));
139-
Serial.print(", E: ");
140-
Serial.print(crsf.rcToUs(crsf.getChannel(2)));
141-
Serial.print(", T: ");
142-
Serial.print(crsf.rcToUs(crsf.getChannel(3)));
143-
Serial.print(", R: ");
144-
Serial.print(crsf.rcToUs(crsf.getChannel(4)));
145-
Serial.print(", Aux1: ");
146-
Serial.print(crsf.rcToUs(crsf.getChannel(5)));
147-
Serial.print(", Aux2: ");
148-
Serial.print(crsf.rcToUs(crsf.getChannel(6)));
149-
Serial.print(", Aux3: ");
150-
Serial.print(crsf.rcToUs(crsf.getChannel(7)));
151-
Serial.print(", Aux4: ");
152-
Serial.print(crsf.rcToUs(crsf.getChannel(8)));
153-
Serial.println(">");
142+
/* 5. Here, you can do whatever you want with your RC channel values.
143+
In this example, your RC channel values are printed to the Serial Monitor every 100 milliseconds. */
144+
static unsigned long lastPrint = 0;
145+
if (millis() - lastPrint >= 100)
146+
{
147+
lastPrint = millis();
148+
149+
Serial.print("RC Channels <A: ");
150+
Serial.print(crsf.readRcChannel(1));
151+
Serial.print(", E: ");
152+
Serial.print(crsf.readRcChannel(2));
153+
Serial.print(", T: ");
154+
Serial.print(crsf.readRcChannel(3));
155+
Serial.print(", R: ");
156+
Serial.print(crsf.readRcChannel(4));
157+
Serial.print(", Aux1: ");
158+
Serial.print(crsf.readRcChannel(5));
159+
Serial.print(", Aux2: ");
160+
Serial.print(crsf.readRcChannel(6));
161+
Serial.print(", Aux3: ");
162+
Serial.print(crsf.readRcChannel(7));
163+
Serial.print(", Aux4: ");
164+
Serial.print(crsf.readRcChannel(8));
165+
Serial.println(">");
166+
}
154167
}
155168
```
156169

@@ -180,7 +193,7 @@ float lat, lon, alt, spd, gCourse;
180193
int numSats;
181194

182195
/* 3. Declare a CRSFforArduino object. */
183-
CRSFforArduino crsf = CRSFforArduino(&Serial1);
196+
CRSFforArduino crsf = CRSFforArduino();
184197

185198
void setup()
186199
{
@@ -230,52 +243,50 @@ void loop()
230243
/* 9. Update the main loop with new RC data. */
231244
crsf.update();
232245

233-
/* 10. Here, your RC channel values are converted to microseconds,
234-
& are sent to the Serial Monitor every 100 milliseconds. */
235-
unsigned long currentMillis = millis();
236-
static unsigned long previousMillis = 0;
237-
238-
if (currentMillis - previousMillis >= 100)
246+
/* 10. Here, you can do whatever you want with your RC channel values.
247+
In this example, your RC channel values are printed to the Serial Monitor every 100 milliseconds. */
248+
static unsigned long lastPrint = 0;
249+
if (millis() - lastPrint >= 100)
239250
{
240-
previousMillis = currentMillis;
251+
lastPrint = millis();
241252

242253
Serial.print("RC Channels <A: ");
243-
Serial.print(crsf.rcToUs(crsf.getChannel(1)));
254+
Serial.print(crsf.readRcChannel(1));
244255
Serial.print(", E: ");
245-
Serial.print(crsf.rcToUs(crsf.getChannel(2)));
256+
Serial.print(crsf.readRcChannel(2));
246257
Serial.print(", T: ");
247-
Serial.print(crsf.rcToUs(crsf.getChannel(3)));
258+
Serial.print(crsf.readRcChannel(3));
248259
Serial.print(", R: ");
249-
Serial.print(crsf.rcToUs(crsf.getChannel(4)));
260+
Serial.print(crsf.readRcChannel(4));
250261
Serial.print(", Aux1: ");
251-
Serial.print(crsf.rcToUs(crsf.getChannel(5)));
262+
Serial.print(crsf.readRcChannel(5));
252263
Serial.print(", Aux2: ");
253-
Serial.print(crsf.rcToUs(crsf.getChannel(6)));
264+
Serial.print(crsf.readRcChannel(6));
254265
Serial.print(", Aux3: ");
255-
Serial.print(crsf.rcToUs(crsf.getChannel(7)));
266+
Serial.print(crsf.readRcChannel(7));
256267
Serial.print(", Aux4: ");
257-
Serial.print(crsf.rcToUs(crsf.getChannel(8)));
268+
Serial.print(crsf.readRcChannel(8));
258269
Serial.println(">");
259270
}
260271
}
261272

262-
// SAMD51 SERCOM3 IRQ handlers.
263-
void SERCOM3_0_Handler()
273+
// SAMD51 SERCOM1 IRQ handlers.
274+
void SERCOM1_0_Handler()
264275
{
265276
gpsSerial.IrqHandler();
266277
}
267278

268-
void SERCOM3_1_Handler()
279+
void SERCOM1_1_Handler()
269280
{
270281
gpsSerial.IrqHandler();
271282
}
272283

273-
void SERCOM3_2_Handler()
284+
void SERCOM1_2_Handler()
274285
{
275286
gpsSerial.IrqHandler();
276287
}
277288

278-
void SERCOM3_3_Handler()
289+
void SERCOM1_3_Handler()
279290
{
280291
gpsSerial.IrqHandler();
281292
}
@@ -301,7 +312,9 @@ Flashing is a lot simpler with PlatformIO when compared to the Arduino IDE.
301312
1. Build your sketch ► `pio run` in your CLI or `ctrl+alt+b` on your keyboard.
302313
2. Flash your sketch ► `pio run -t upload` in your CLI or `ctrl+alt+u` on your keyboard.
303314
304-
The above steps will default to the Adafruit Metro M4 development board.
315+
The above steps will default to the Adafruit Metro M0 development board.
316+
To build & flash your sketch to a different development board, use `pio run -e <board name>` to build your sketch & `pio run -e <board name> -t upload` to flash your sketch to your development board.
317+
For example, if you are using an Adafruit Metro M4 Express, you would use `pio run -e adafruit_metro_m4` to build your sketch & `pio run -e adafruit_metro_m4 -t upload` to flash your sketch to your Metro M4 Express.
305318
306319
### Flashing - Arduino IDE
307320
@@ -362,46 +375,6 @@ I am now aware that Arduino are making an R4 of their UNO & Adafruit are making
362375
363376
As for other development boards (& their host microcontrollers), if they meet the minimum requirements & you are still having compatibility issues, it is likely that I have not yet added your board to the Compatibility Table. Consider opening up an [Issue](https://github.com/ZZ-Cat/CRSFforArduino/issues/new/choose) & we can go from there.
364377
365-
## AVR based microcontrollers are not compatible
366-
367-
Development boards of yesteryear that were built around ATmega AVR microcontrollers such as the 328, 32u4, 2560 & 4809 are incompatible with CRSF for Arduino.
368-
Their processing capabilities (such as their limited 16 MHz CPU speed) simply are not enough to meet the requirements of the CRSF protocol.
369-
370-
The following is a list of known development boards that are incompatible.
371-
This list is not exhaustive, but it should give you an idea of what _not_ to flash CRSF for Arduino onto. If you even try to do so, CRSF for Arduino will simply not function (instead of allegedly exhibiting ["strange behaviour"](https://github.com/ZZ-Cat/CRSFforArduino/issues/12)):
372-
373-
- Adafruit Metro 328
374-
- Adafruit Feather 32u4 (including all of its variants)
375-
- Arduino Diecimila
376-
- Arduino Duemilanove
377-
- Arduino Leonardo
378-
- Arduino Mega2560
379-
- Arduino Micro
380-
- Arduino Nano
381-
- Arduino Nano Every
382-
- Arduino UNO R3
383-
- Arduino UNO WiFi R2
384-
385-
There is better hardware out there that has the same form factor as these old development boards.
386-
Here are some examples:
387-
388-
- These have an equivalent "breadboard-friendly" form factor that is similar to the Arduio Micro & the Arduino Nano:
389-
- Adafruit Feather M0 (all variants, including the Feather M0 Express)
390-
- Adafruit Feather M4 Express
391-
- Seeed Studio XIAO SAMD21
392-
- These have the "Arduino R3" form factor - The same shape as the Arduino UNO:
393-
- Adafruit:
394-
- Metro M0 Express
395-
- Metro M4 AirLift Lite
396-
- Metro M4 Express
397-
- Arduino:
398-
- Zero
399-
- These have the "Arduino Mega" form factor:
400-
- Adafruit:
401-
- Grand Central M4
402-
403-
Many of Arduino's [discontinued development boards](https://docs.arduino.cc/retired/) are also incompatible with CRSF for Arduino.
404-
405378
## Compatible receivers
406379
407380
Generally speaking, if your transmitter & receiver combo supports ExpressLRS or TBS Crossfire, it's automatically compatible.
@@ -420,8 +393,32 @@ You _must_ refer to your development board's documentation to determine where yo
420393
421394
## Telemetry
422395
423-
Currently, there is no telemetry feedback from the host microcontroller's side... _yet!_
424-
This is coming soon.
396+
Currently, the only telemetry that is supported is GPS data.
397+
This is because I am still working on the telemetry side of things.
398+
399+
The following telemetry data is supported:
400+
401+
- GPS data:
402+
- Latitude (in decimal degrees)
403+
- Longitude (in decimal degrees)
404+
- Altitude (in centimetres)
405+
- Speed (in centimetres per second)
406+
- Course over ground (in degrees)
407+
- Number of satellites
408+
409+
## Known issues & limitations
410+
411+
- CRSF for Arduino is not compatible with AVR based microcontrollers.
412+
- This is because the AVR microcontrollers are simply not powerful enough to meet the minimum requirements of the CRSF protocol.
413+
- Certain instances of DMA can cause SAMD51 based development boards to crash.
414+
- A workaround is in place to prevent this from happening, but it is not a permanent solution.
415+
- This is currently being investigated.
416+
- Software serial is not supported.
417+
- This is because software serial is not capable of running at the required baud rate of 420 KB/s.
418+
- This also means that CRSF for Arduino is restricted to using hardware serial only.
419+
- CRSF for Arduino provides no sensor drivers. This is by design.
420+
- There are already plenty of libraries out there that provide sensor drivers for your development board.
421+
- You are free to use any sensor library that you want with CRSF for Arduino.
425422
426423
## Software license
427424

examples/channels/channels.ino

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @author Cassandra "ZZ Cat" Robinson (nicad.heli.flier@gmail.com)
44
* @brief This example sketch shows how to receive RC channels from a CRSF receiver using the CRSF for Arduino library.
55
* @version 0.4.0
6-
* @date 2023-07-27
6+
* @date 2023-08-01
77
*
88
* @copyright Copyright (c) 2023, Cassandra "ZZ Cat" Robinson. All rights reserved.
99
*
@@ -106,7 +106,10 @@
106106

107107
#include "CRSFforArduino.h"
108108

109-
CRSFforArduino crsf = CRSFforArduino(&Serial1);
109+
#define SERIAL_RX_PIN 0 // Set SERIAL_RX_PIN to the pin that the CRSF receiver's TX pin is connected to.
110+
#define SERIAL_TX_PIN 1 // Set SERIAL_TX_PIN to the pin that the CRSF receiver's RX pin is connected to.
111+
112+
CRSFforArduino crsf = CRSFforArduino(SERIAL_RX_PIN, SERIAL_TX_PIN);
110113

111114
void setup()
112115
{
@@ -136,8 +139,13 @@ void setup()
136139

137140
void loop()
138141
{
139-
if (crsf.update())
142+
crsf.update();
143+
144+
/* Print RC channels every 100 ms. */
145+
static unsigned long lastPrint = 0;
146+
if (millis() - lastPrint >= 100)
140147
{
148+
lastPrint = millis();
141149
Serial.print("RC Channels <A: ");
142150
Serial.print(crsf.rcToUs(crsf.getChannel(1)));
143151
Serial.print(", E: ");

examples/gps_telemetry/gps_telemetry.ino

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @author Cassandra "ZZ Cat" Robinson (nicad.heli.flier@gmail.com)
44
* @brief This example sketch demonstrates how to pass data from a GPS module into CRSF for Arduino & transmit it as telemetry.
55
* @version 0.4.0
6-
* @date 2023-07-27
6+
* @date 2023-08-01
77
*
88
* @copyright Copyright (c) 2023, Cassandra "ZZ Cat" Robinson. All rights reserved.
99
*
@@ -33,6 +33,8 @@
3333
/* Configuration Options. */
3434
#define VIEW_RC_CHANNELS 0 // Set VIEW_RC_CHANNELS to 1 to view the RC channel data in the serial monitor.
3535
#define GENERATE_RANDOM_GPS_DATA 0 // Set GENERATE_RANDOM_GPS_DATA to 1 to generate random GPS telemetry data.
36+
#define SERIAL_RX_PIN 0 // Set SERIAL_RX_PIN to the pin that the CRSF receiver's TX pin is connected to.
37+
#define SERIAL_TX_PIN 1 // Set SERIAL_TX_PIN to the pin that the CRSF receiver's RX pin is connected to.
3638

3739
uint32_t timeNow = 0;
3840

@@ -44,7 +46,7 @@ float speed = 500.0F; // Speed is in cm/s
4446
float groundCourse = 275.8F; // Ground Course is in degrees.
4547
uint8_t satellites = 4;
4648

47-
CRSFforArduino crsf = CRSFforArduino(&Serial1);
49+
CRSFforArduino crsf = CRSFforArduino(SERIAL_RX_PIN, SERIAL_TX_PIN);
4850

4951
void setup()
5052
{

0 commit comments

Comments
 (0)