# RadioLib RadioLib is a universal wireless communication library for embedded devices that supports a wide variety of radio modules, protocols, and digital modes. It provides a consistent, hardware-abstracted API to control sub-GHz radio modules like SX1278, RF69, CC1101, SX1268, LR1110, and many others, enabling LoRa, FSK, OOK, and other modulation schemes. The library implements numerous communication protocols including LoRaWAN, AX.25, APRS, RTTY, Morse Code, SSTV (Slow Scan TV), Hellschreiber, POCSAG pager messages, and ADS-B. RadioLib natively supports Arduino but can also run on non-Arduino platforms like ESP-IDF, Raspberry Pi, and Tock through its hardware abstraction layer. Version 7.6.0 is the current release. ## Module Initialization All radio modules follow a consistent initialization pattern using the Module class to define pin connections, followed by calling begin() or beginFSK() to initialize the radio with default or custom settings. ```cpp #include // SX1278 LoRa module connections: // NSS pin: 10, DIO0 pin: 2, RESET pin: 9, DIO1 pin: 3 SX1278 radio = new Module(10, 2, 9, 3); void setup() { Serial.begin(9600); // Initialize with default settings (434.0 MHz, 125 kHz BW, SF7, CR 4/5) Serial.print(F("[SX1278] Initializing ... ")); int state = radio.begin(); if (state == RADIOLIB_ERR_NONE) { Serial.println(F("success!")); } else { Serial.print(F("failed, code ")); Serial.println(state); while (true) { delay(10); } } } ``` ## LoRa Packet Transmission with Interrupts The library supports non-blocking transmission using interrupt callbacks, allowing the main loop to perform other tasks while packets are being transmitted. Use setPacketSentAction() to register a callback and startTransmit() to begin transmission. ```cpp #include SX1278 radio = new Module(10, 2, 9, 3); int transmissionState = RADIOLIB_ERR_NONE; volatile bool transmittedFlag = false; #if defined(ESP8266) || defined(ESP32) ICACHE_RAM_ATTR #endif void setFlag(void) { transmittedFlag = true; } void setup() { Serial.begin(9600); int state = radio.begin(); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("Init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Set callback for when packet transmission completes radio.setPacketSentAction(setFlag); // Start first transmission transmissionState = radio.startTransmit("Hello World!"); } int count = 0; void loop() { if (transmittedFlag) { transmittedFlag = false; if (transmissionState == RADIOLIB_ERR_NONE) { Serial.println(F("Transmission finished!")); } else { Serial.print(F("Failed, code ")); Serial.println(transmissionState); } radio.finishTransmit(); // Clean up, disable transmitter delay(1000); // Send next packet String str = "Hello World! #" + String(count++); transmissionState = radio.startTransmit(str); // Can also transmit byte arrays // byte data[] = {0x01, 0x23, 0x45, 0x67}; // transmissionState = radio.startTransmit(data, 4); } } ``` ## LoRa Packet Reception with Interrupts Continuous packet reception uses setPacketReceivedAction() to register a callback and startReceive() to begin listening. Signal quality metrics like RSSI, SNR, and frequency error are available after reception. ```cpp #include SX1278 radio = new Module(10, 2, 9, 3); volatile bool receivedFlag = false; #if defined(ESP8266) || defined(ESP32) ICACHE_RAM_ATTR #endif void setFlag(void) { receivedFlag = true; } void setup() { Serial.begin(9600); int state = radio.begin(); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("Init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Set callback for when packet is received radio.setPacketReceivedAction(setFlag); // Start listening state = radio.startReceive(); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("Receive failed, code ")); Serial.println(state); while (true) { delay(10); } } } void loop() { if (receivedFlag) { receivedFlag = false; // Read received data as string String str; int state = radio.readData(str); // Or read as byte array: // byte buf[64]; // int len = radio.getPacketLength(); // int state = radio.readData(buf, len); if (state == RADIOLIB_ERR_NONE) { Serial.print(F("Data: ")); Serial.println(str); Serial.print(F("RSSI: ")); Serial.print(radio.getRSSI()); Serial.println(F(" dBm")); Serial.print(F("SNR: ")); Serial.print(radio.getSNR()); Serial.println(F(" dB")); Serial.print(F("Freq error: ")); Serial.print(radio.getFrequencyError()); Serial.println(F(" Hz")); } else if (state == RADIOLIB_ERR_CRC_MISMATCH) { Serial.println(F("CRC error!")); } else { Serial.print(F("Failed, code ")); Serial.println(state); } } } ``` ## Radio Settings Configuration All radio parameters can be configured at runtime including frequency, bandwidth, spreading factor, coding rate, sync word, output power, and more. Each setter returns a status code indicating success or the specific validation error. ```cpp #include SX1262 radio = new Module(10, 2, 3, 9); void setup() { Serial.begin(9600); // Initialize with custom settings in begin() // frequency: 915.0 MHz, bandwidth: 500.0 kHz, SF: 6, CR: 5 // sync word: 0x34, power: 2 dBm, preamble: 20 symbols int state = radio.begin(915.0, 500.0, 6, 5, 0x34, 2, 20); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("Init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Or configure at runtime if (radio.setFrequency(433.5) == RADIOLIB_ERR_INVALID_FREQUENCY) { Serial.println(F("Invalid frequency!")); } if (radio.setBandwidth(250.0) == RADIOLIB_ERR_INVALID_BANDWIDTH) { Serial.println(F("Invalid bandwidth!")); } if (radio.setSpreadingFactor(10) == RADIOLIB_ERR_INVALID_SPREADING_FACTOR) { Serial.println(F("Invalid spreading factor!")); } if (radio.setCodingRate(6) == RADIOLIB_ERR_INVALID_CODING_RATE) { Serial.println(F("Invalid coding rate!")); } if (radio.setSyncWord(0xAB) != RADIOLIB_ERR_NONE) { Serial.println(F("Unable to set sync word!")); } if (radio.setOutputPower(10) == RADIOLIB_ERR_INVALID_OUTPUT_POWER) { Serial.println(F("Invalid output power!")); // Range: -17 to 22 dBm } if (radio.setCurrentLimit(80) == RADIOLIB_ERR_INVALID_CURRENT_LIMIT) { Serial.println(F("Invalid current limit!")); // Range: 45-240 mA } if (radio.setPreambleLength(15) == RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH) { Serial.println(F("Invalid preamble length!")); // Range: 0-65535 } radio.setCRC(false); // Disable CRC // Configure TCXO voltage for modules with temperature-compensated oscillator radio.setTCXO(2.4); // Use DIO2 as RF switch control (disables DIO2 as interrupt) radio.setDio2AsRfSwitch(); Serial.println(F("Settings configured!")); } ``` ## LoRaWAN OTAA Join and Uplink RadioLib implements LoRaWAN Class A and C with support for OTAA and ABP activation. The LoRaWANNode class handles all MAC layer operations including join requests, uplinks, downlinks, and adaptive data rate. ```cpp #include SX1278 radio = new Module(10, 2, 9, 3); // LoRaWAN credentials (from TTN console) uint64_t joinEUI = 0x0000000000000000; uint64_t devEUI = 0x70B3D57ED0000000; uint8_t appKey[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; uint8_t nwkKey[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; // Regional settings: EU868, US915, AU915, AS923, IN865, KR920, CN470 const LoRaWANBand_t Region = EU868; const uint8_t subBand = 0; // For US915/AU915 use 2, for CN470 use 1 LoRaWANNode node(&radio, &Region, subBand); void setup() { Serial.begin(115200); int16_t state = radio.begin(); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("Radio init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Initialize OTAA session state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("Node init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Join the network Serial.println(F("Joining LoRaWAN network...")); state = node.activateOTAA(); if (state != RADIOLIB_LORAWAN_NEW_SESSION) { Serial.print(F("Join failed, code ")); Serial.println(state); while (true) { delay(10); } } Serial.println(F("Joined!")); } void loop() { // Build sensor payload uint8_t value1 = radio.random(100); uint16_t value2 = radio.random(2000); uint8_t uplinkPayload[3]; uplinkPayload[0] = value1; uplinkPayload[1] = highByte(value2); uplinkPayload[2] = lowByte(value2); // Send uplink and check for downlink int16_t state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload)); if (state < RADIOLIB_ERR_NONE) { Serial.print(F("Uplink error, code ")); Serial.println(state); } else if (state > 0) { Serial.println(F("Downlink received in Rx window ") + String(state)); } delay(5 * 60 * 1000UL); // Wait 5 minutes (respect duty cycle) } ``` ## FSK Module Operation (CC1101, RF69) Non-LoRa FSK modules like CC1101 and RF69 use the same API pattern with begin() for initialization and identical transmit/receive methods. ```cpp #include // CC1101: CS pin 10, GDO0 pin 2, no reset, GDO2 pin 3 CC1101 radio = new Module(10, 2, RADIOLIB_NC, 3); // Or RF69: CS pin 10, DIO0 pin 2, RESET pin 3 // RF69 radio = new Module(10, 2, 3); volatile bool transmittedFlag = false; #if defined(ESP8266) || defined(ESP32) ICACHE_RAM_ATTR #endif void setFlag(void) { transmittedFlag = true; } void setup() { Serial.begin(9600); int state = radio.begin(); // Default: 434 MHz, 4.8 kbps, 5 kHz deviation if (state != RADIOLIB_ERR_NONE) { Serial.print(F("Init failed, code ")); Serial.println(state); while (true) { delay(10); } } radio.setPacketSentAction(setFlag); // For high-power RF69H/RF69HCW modules: // radio.setOutputPower(20, true); // 20 dBm, high power mode radio.startTransmit("Hello World!"); } int count = 0; void loop() { if (transmittedFlag) { transmittedFlag = false; Serial.println(F("Transmission complete!")); radio.finishTransmit(); delay(1000); String str = "Hello World! #" + String(count++); radio.startTransmit(str); } } ``` ## Channel Activity Detection (CAD) LoRa modules can detect channel activity by scanning for LoRa preambles, useful for implementing listen-before-talk protocols or triggering receive mode only when activity is detected. ```cpp #include SX1278 radio = new Module(10, 2, 9, 3); volatile bool timeoutFlag = false; volatile bool detectedFlag = false; #if defined(ESP8266) || defined(ESP32) ICACHE_RAM_ATTR #endif void setFlagTimeout(void) { timeoutFlag = true; } #if defined(ESP8266) || defined(ESP32) ICACHE_RAM_ATTR #endif void setFlagDetected(void) { detectedFlag = true; } void setup() { Serial.begin(115200); int state = radio.begin(); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("Init failed, code ")); Serial.println(state); while (true) { delay(10); } } // DIO0 fires on CAD timeout (no preamble detected) radio.setDio0Action(setFlagTimeout, RISING); // DIO1 fires when preamble is detected radio.setDio1Action(setFlagDetected, RISING); // Start CAD scan state = radio.startChannelScan(); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("CAD start failed, code ")); Serial.println(state); } } void loop() { if (detectedFlag || timeoutFlag) { if (detectedFlag) { Serial.println(F("Preamble detected!")); // Could now call radio.startReceive() to receive the packet } else { Serial.println(F("Channel free")); } // Restart CAD radio.startChannelScan(); timeoutFlag = false; detectedFlag = false; } } ``` ## AX.25 Packet Transmission The AX25Client provides amateur radio packet transmission using 2-FSK modulation, commonly used at 1200 baud on VHF frequencies. ```cpp #include SX1278 radio = new Module(10, 2, 9, 3); AX25Client ax25(&radio); void setup() { Serial.begin(9600); // Initialize FSK at 434 MHz, 1.2 kbps for AX.25 int state = radio.beginFSK(434.0, 1.2); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("FSK init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Initialize AX.25 with source callsign state = ax25.begin("N7LEM"); // Callsign, SSID defaults to 0 if (state != RADIOLIB_ERR_NONE) { Serial.print(F("AX.25 init failed, code ")); Serial.println(state); while (true) { delay(10); } } } void loop() { // Send UI (Unnumbered Information) frame int state = ax25.transmit("Hello World!", "NJ7P"); // Message, destination callsign if (state == RADIOLIB_ERR_NONE) { Serial.println(F("Frame sent!")); } else { Serial.print(F("Failed, code ")); Serial.println(state); } delay(1000); } ``` ## APRS Position Reporting APRSClient builds on AX.25 to provide APRS position reporting with support for timestamps, messages, and digipeater paths. ```cpp #include SX1278 radio = new Module(10, 2, 9, 3); // AFSK client for Bell 202 1200 baud modulation (DIO2 on pin 5) AFSKClient audio(&radio, 5); AX25Client ax25(&audio); APRSClient aprs(&ax25); void setup() { Serial.begin(9600); // Initialize FSK modem int state = radio.beginFSK(434.0); // Use ISM band for testing! if (state != RADIOLIB_ERR_NONE) { Serial.print(F("FSK init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Initialize AX.25 state = ax25.begin("N7LEM"); // Source callsign if (state != RADIOLIB_ERR_NONE) { Serial.print(F("AX.25 init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Initialize APRS with map symbol ('>' = car) state = aprs.begin('>'); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("APRS init failed, code ")); Serial.println(state); while (true) { delay(10); } } } void loop() { char dest[] = "APRS"; char lat[] = "4911.67N"; char lon[] = "01635.96E"; // Basic position report int state = aprs.sendPosition(dest, 0, lat, lon); // Position with message char msg[] = "Mobile station"; state = aprs.sendPosition(dest, 0, lat, lon, msg); // Configure digipeater path (WIDE2-2) char* repeaters[] = { "WIDE2" }; uint8_t ssids[] = { 2 }; aprs.useRepeaters(repeaters, ssids, 1); // Position with message and timestamp char timestamp[] = "093045z"; // HHMMSS in zulu time state = aprs.sendPosition(dest, 0, lat, lon, msg, timestamp); aprs.dropRepeaters(); // Clear repeater path delay(60000); // Wait 1 minute between transmissions } ``` ## RTTY (Radio Teletype) Transmission RTTYClient implements classic radioteletype using 2-FSK modulation with configurable baud rate, frequency shift, and encoding (ASCII or ITA2/Baudot). ```cpp #include SX1278 radio = new Module(10, 2, 9, 3); RTTYClient rtty(&radio); void setup() { Serial.begin(9600); int state = radio.beginFSK(); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("FSK init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Initialize RTTY: 434.0 MHz base, 183 Hz shift, 45 baud, ASCII encoding state = rtty.begin(434.0, 183, 45); // For ITA2 (Baudot): rtty.begin(434.0, 183, 45, ITA2); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("RTTY init failed, code ")); Serial.println(state); while (true) { delay(10); } } } void loop() { // Send idle carrier for synchronization rtty.idle(); delay(500); // Print supports all Serial-like methods rtty.println("Arduino String"); rtty.println("C-String"); rtty.println(F("Flash String")); rtty.println('c'); // Character rtty.println(255, HEX); // Number with formatting rtty.println(1000); // Integer rtty.println(-3.1415, 3); // Float with precision rtty.standby(); // Turn off transmitter delay(1000); } ``` ## Morse Code Transmission MorseClient transmits Morse code using OOK (AM) or FSK (FM) modulation, supporting all ITU-R M.1677-1 characters. ```cpp #include SX1278 radio = new Module(10, 2, 9, 3); // AFSK client for audio tone generation (DIO2 on pin 5) AFSKClient audio(&radio, 5); MorseClient morse(&audio); void setup() { Serial.begin(9600); int state = radio.beginFSK(); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("FSK init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Initialize Morse: 400 Hz tone, 20 WPM default state = morse.begin(400); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("Morse init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Enable OOK mode for AM modulation radio.setOOK(true); } void loop() { morse.startSignal(); // Send start signal // Print supports Serial-like methods morse.print("CQ CQ CQ DE N7LEM"); morse.print(F("Flash String")); morse.print(255, HEX); morse.println(-3.1415, 3); // println sends end-of-work signal delay(5000); } ``` ## POCSAG Pager Transmission PagerClient implements the POCSAG protocol for sending messages to pagers, supporting numeric (BCD), ASCII, and tone-only messages. ```cpp #include SX1278 radio = new Module(10, 2, 9, 3); PagerClient pager(&radio); void setup() { Serial.begin(9600); int state = radio.beginFSK(); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("FSK init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Initialize pager: 434.0 MHz, 1200 bps (also supports 512, 2400 bps) state = pager.begin(434.0, 1200); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("Pager init failed, code ")); Serial.println(state); while (true) { delay(10); } } } void loop() { uint32_t addr = 1234567; // Pager address (RIC) // Send tone-only alert pager.sendTone(addr); delay(500); // Send numeric message (BCD: 0-9, *, U, -, (, ), space) pager.transmit("0123456789*U -()", addr); delay(500); // Send ASCII text message pager.transmit("Hello World!", addr, RADIOLIB_PAGER_ASCII); delay(500); delay(3000); } ``` ## SX128x Ranging (Distance Measurement) The SX1280/SX1282 2.4 GHz modules support LoRa ranging for time-of-flight distance measurement between two synchronized radios. ```cpp #include SX1280 radio = new Module(10, 2, 3, 9); void setup() { Serial.begin(9600); int state = radio.begin(); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("Init failed, code ")); Serial.println(state); while (true) { delay(10); } } } void loop() { // Perform ranging as master, slave address 0x12345678 int state = radio.range(true, 0x12345678); // The other device must be configured as slave: // int state = radio.range(false, 0x12345678); // Optional: provide calibration table for improved accuracy // uint16_t cal[3][6] = { ... }; // int state = radio.range(true, 0x12345678, cal); if (state == RADIOLIB_ERR_NONE) { Serial.print(F("Distance: ")); Serial.print(radio.getRangingResult()); Serial.println(F(" meters")); } else if (state == RADIOLIB_ERR_RANGING_TIMEOUT) { Serial.println(F("Ranging timeout")); } else { Serial.print(F("Failed, code ")); Serial.println(state); } delay(1000); } ``` ## LR11x0 GNSS Positioning LR1110/LR1120 modules include integrated GNSS receivers capable of autonomous position calculation without cloud assistance. ```cpp #include LR1110 radio = new Module(10, 2, 3, 9); // RF switch configuration for Wio WM1110 static const uint32_t rfswitch_dio_pins[] = { RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC }; static const Module::RfSwitchMode_t rfswitch_table[] = { { LR11x0::MODE_STBY, { LOW, LOW } }, { LR11x0::MODE_RX, { HIGH, LOW } }, { LR11x0::MODE_TX, { HIGH, HIGH } }, { LR11x0::MODE_TX_HP, { LOW, HIGH } }, { LR11x0::MODE_GNSS, { LOW, LOW } }, { LR11x0::MODE_WIFI, { LOW, LOW } }, END_OF_MODE_TABLE, }; LR11x0GnssResult_t gnssResult; LR11x0GnssPosition_t gnssPosition; void setup() { Serial.begin(9600); // Initialize for GPS constellation int state = radio.beginGNSS(RADIOLIB_LR11X0_GNSS_CONSTELLATION_GPS); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("GNSS init failed, code ")); Serial.println(state); while (true) { delay(10); } } radio.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table); // Verify firmware supports GNSS scanning state = radio.isGnssScanCapable(); if (state != RADIOLIB_ERR_NONE) { Serial.println(F("Firmware update needed")); while (true) { delay(10); } } } void loop() { int state = radio.gnssScan(&gnssResult); if (state == RADIOLIB_ERR_NONE) { state = radio.getGnssPosition(&gnssPosition); if (state == RADIOLIB_ERR_NONE) { Serial.print(F("Lat: ")); Serial.print(gnssPosition.latitude, 6); Serial.print(F(" Lon: ")); Serial.print(gnssPosition.longitude, 6); Serial.print(F(" Accuracy: ")); Serial.print(gnssPosition.accuracy); Serial.print(F("m Sats: ")); Serial.println(gnssPosition.numSatsUsed); } } else { Serial.print(F("Scan failed, code ")); Serial.println(state); } delay(1000); } ``` ## LR11x0 WiFi Scanning LR11x0 modules can passively scan for WiFi networks, useful for hybrid GNSS/WiFi positioning or network detection. ```cpp #include LR1110 radio = new Module(10, 2, 3, 9); static const uint32_t rfswitch_dio_pins[] = { RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC }; static const Module::RfSwitchMode_t rfswitch_table[] = { { LR11x0::MODE_STBY, { LOW, LOW } }, { LR11x0::MODE_RX, { HIGH, LOW } }, { LR11x0::MODE_TX, { HIGH, HIGH } }, { LR11x0::MODE_WIFI, { LOW, LOW } }, END_OF_MODE_TABLE, }; void setup() { Serial.begin(9600); int state = radio.begin(); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("Init failed, code ")); Serial.println(state); while (true) { delay(10); } } radio.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table); } void loop() { uint8_t count = 0; // Scan all WiFi signals ('*' for all types) int state = radio.wifiScan('*', &count); if (state == RADIOLIB_ERR_NONE) { Serial.print(F("Found ")); Serial.print(count); Serial.println(F(" networks:")); LR11x0WifiResultExtended_t result; for (int i = 0; i < count; i++) { state = radio.getWifiScanResult(&result, i); if (state == RADIOLIB_ERR_NONE) { Serial.print(F("802.11")); Serial.print(result.type); Serial.print(F(" ")); Serial.print(result.channelFreq); Serial.print(F(" MHz ")); // Print MAC address for (int j = 0; j < 6; j++) { if (result.mac[j] < 0x10) Serial.print("0"); Serial.print(result.mac[j], HEX); if (j < 5) Serial.print(":"); } Serial.print(F(" ")); Serial.print(result.rssi); Serial.print(F(" dBm ")); Serial.println((char*)result.ssid); } } } else { Serial.print(F("Scan failed, code ")); Serial.println(state); } delay(5000); } ``` ## SSTV (Slow Scan Television) Image Transmission SSTVClient transmits images using various SSTV modes like Scottie, Martin, and Wrasse, commonly used by amateur radio operators. ```cpp #include SX1278 radio = new Module(10, 2, 9, 3); SSTVClient sstv(&radio); // Simple test pattern: one line of 320 RGB pixels (repeated for all lines) uint32_t line[320] = { // 40 black, 40 blue, 40 green, 40 cyan, 40 red, 40 magenta, 40 yellow, 40 white 0x000000, /* ... */ 0x0000FF, /* ... */ 0x00FF00, /* ... */ 0x00FFFF, /* ... */ 0xFF0000, /* ... */ 0xFF00FF, /* ... */ 0xFFFF00, /* ... */ 0xFFFFFF /* ... */ }; void setup() { Serial.begin(9600); int state = radio.beginFSK(); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("FSK init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Initialize SSTV: 434.0 MHz, Wrasse (SC2-180) mode // Other modes: Scottie1, Scottie2, Martin1, Martin2, etc. state = sstv.begin(434.0, Wrasse); if (state != RADIOLIB_ERR_NONE) { Serial.print(F("SSTV init failed, code ")); Serial.println(state); while (true) { delay(10); } } // Timing correction (platform-dependent, typically ~0.95) sstv.setCorrection(0.95); } void loop() { // Send VIS header sstv.sendHeader(); // Send all image lines for (uint16_t i = 0; i < sstv.getPictureHeight(); i++) { sstv.sendLine(line); // Send pixel row as RGB values } radio.standby(); delay(30000); } ``` ## Summary RadioLib provides a comprehensive solution for wireless communication in embedded systems, unifying access to diverse radio hardware through a consistent API. The library excels at LoRa/LoRaWAN applications for IoT sensor networks, long-range telemetry, and asset tracking, while also supporting amateur radio digital modes like APRS, AX.25, RTTY, and Morse code. Advanced features include GNSS positioning and WiFi scanning on LR11x0 modules, distance ranging on SX128x, and channel activity detection for collision avoidance. Integration patterns typically involve initializing a Module with SPI pin assignments, creating the appropriate radio class (SX1278, CC1101, RF69, etc.), and using protocol-specific clients (LoRaWANNode, AX25Client, MorseClient) layered on top. The interrupt-driven receive and transmit modes enable efficient, non-blocking operation essential for battery-powered applications. Status codes provide detailed error reporting, and the library's hardware abstraction layer allows porting to non-Arduino platforms. Whether building a simple point-to-point link or a complete LoRaWAN sensor network, RadioLib provides the building blocks with consistent, well-documented APIs.