A high-level RMT (Remote Control Transceiver) library for BlueScript on ESP32. This package provides a powerful interface to generate precise digital pulse trains. It is highly useful for driving smart LEDs (e.g., WS2812/NeoPixel), transmitting infrared (IR) remote signals, or creating custom 1-wire protocols without blocking the CPU.
Install this package in your BlueScript project:
bscript project install https://github.com/bluescript-lang/pkg-rmt-esp32.gitUsing RmtByteEncoder, you can define how logical 0 and 1 bits are translated into physical pulses. This is perfect for WS2812 LEDs.
import { RmtTxChannel, RmtByteEncoder, RmtSymbol } from "rmt";
// 1. Initialize RMT TX Channel on GPIO 12 with 10MHz resolution (1 tick = 0.1 us)
const channel = new RmtTxChannel(12, 10000000);
// 2. Define the symbols for Bit 0 and Bit 1 (WS2812-like protocol)
// Bit 0: HIGH for 4 ticks (0.4us), LOW for 8 ticks (0.8us)
const bit0 = new RmtSymbol(1, 4, 0, 8);
// Bit 1: HIGH for 8 ticks (0.8us), LOW for 4 ticks (0.4us)
const bit1 = new RmtSymbol(1, 8, 0, 4);
// 3. Create a Byte Encoder (MSB first)
const encoder = new RmtByteEncoder(channel, bit0, bit1, true);
// 4. Create color data for 2 LEDs (GRB format: Green, Red, Blue)
const colorData = new Uint8Array(6, 0);
// LED 1: Red
colorData[0] = 0; // G
colorData[1] = 255; // R
colorData[2] = 0; // B
// LED 2: Green
colorData[3] = 255; // G
colorData[4] = 0; // R
colorData[5] = 0; // B
// 5. Transmit data (loopCount = 0, timeout = 1000ms)
channel.transmit(encoder, colorData, 0, 1000);
// 6. Cleanup
encoder.close();
channel.close();Using RmtCopyEncoder along with the symbolsToUint8Array utility function, you can define exact timings for each individual high/low pulse.
import { RmtTxChannel, RmtCopyEncoder, RmtSymbol, symbolsToUint8Array } from "rmt";
// 1MHz resolution (1 tick = 1 us)
const channel = new RmtTxChannel(14, 1000000);
const encoder = new RmtCopyEncoder(channel);
// Define raw symbols
const symbols = [
new RmtSymbol(1, 9000, 0, 4500), // Header mark & space
new RmtSymbol(1, 560, 0, 1690), // Logical 1
new RmtSymbol(1, 560, 0, 560), // Logical 0
new RmtSymbol(1, 560, 0, 0) // Stop bit
];
// Convert symbols to raw byte array
const rawData = symbolsToUint8Array(symbols);
// Transmit asynchronously and do other tasks
channel.transmitAsync(encoder, rawData, 0);
console.log("Transmitting IR signal in the background...");
// Wait for transmission to finish
channel.waitTransmitCompleted(1000);
encoder.close();
channel.close();You can create a highly customized encoding process by extending the base RmtEncoder class. This allows you to hook into the ESP-IDF RMT driver's encoding callbacks directly from BlueScript.
⚠️ Important Limitations for Custom EncodersThe
encodemethod is executed directly inside a Hardware Interrupt Service Routine (ISR). Because of this, you must strictly follow these rules:
- Do not block: Never use
time.delay()or wait for locks. Execution must be as fast as possible.- No heavy processing: Avoid complex math or heavy loops.
- Do not allocate memory: Do not create new objects or arrays inside
encode()(e.g.,new Uint8Array()). Allocating memory can trigger the Garbage Collector (GC) inside the interrupt, which will crash the system. Always pre-allocate necessary buffers in the constructor.
import { RmtEncoder, RmtTxChannel } from "rmt";
export class MyFastEncoder extends RmtEncoder {
done: boolean;
constructor(channel: RmtTxChannel) {
super(channel); // Automatically registers C-level callbacks
this.done = false;
}
// WARNING: This runs in an ISR context!
encode(data: Uint8Array): integer {
// Fast, non-blocking logic only.
// DO NOT allocate objects here.
this.done = true;
return 0; // Return the number of encoded symbols.
}
isDone(): boolean {
return this.done;
}
reset(): void {
this.done = false;
}
deinit(): void {
// Cleanup resources
}
}
// Usage
const channel = new RmtTxChannel(15, 1000000);
const customEncoder = new MyFastEncoder(channel);
channel.transmit(customEncoder, new Uint8Array(4, 0), 0, 1000);Converts an array of RmtSymbol objects into the native memory layout (Uint8Array) required for transmission. The result of this function should be passed as the data argument to channel.transmit().
Represents an RMT Transmit hardware channel.
Initializes the RMT TX channel.
- pin: The GPIO pin number to output the signal.
- resolutionHz: The tick resolution in Hz (e.g.,
1000000for 1µs ticks).
Encodes and transmits data synchronously. Blocks until transmission is complete or times out.
- loopCount: Usually
0for single transmission.>0to repeat the transmission.
Starts transmission in the background (non-blocking).
Blocks the script until an ongoing asynchronous transmission finishes.
Disables and releases the hardware channel.
Represents a single RMT symbol composed of two pulse segments (Duration 0 and Duration 1).
- level0: The logical level of the first pulse (
1for HIGH,0for LOW). - ticks0: Duration of the first pulse in channel ticks.
- level1: The logical level of the second pulse.
- ticks1: Duration of the second pulse in channel ticks.
The base class for all encoders. It internally binds BlueScript methods (encode, isDone, reset, deinit) to ESP-IDF hardware callbacks. You can extend this class to implement custom encoding behaviors.
Translates standard byte arrays into predefined RmtSymbol representations bit-by-bit.
- channel: The target
RmtTxChannel. - bit0: The
RmtSymbolused to represent a binary0. - bit1: The
RmtSymbolused to represent a binary1. - msbFirst:
trueto transmit the Most Significant Bit first,falsefor LSB first.
An encoder that directly copies pre-formatted raw symbols to the RMT channel. Useful for custom or irregular waveforms.
Creates a copy encoder attached to a channel.
Return values representing the status of the last RMT operation.
| Name | Value | Description |
|---|---|---|
OK |
0 | Operation succeeded |
Fail |
1 | Generic failure |
Timeout |
2 | Operation timed out |
InvalidArg |
3 | Invalid argument provided |
NoMemory |
4 | Memory allocation failed |
NotFound |
5 | Resource not found |
NotSupported |
6 | Feature not supported |
InvalidState |
7 | Hardware in invalid state for operation |
Similarly to other standard BlueScript libraries, methods in RMT classes update the lastOperationResult property of the object instead of throwing hard errors.
You can verify success by checking lastOperationResult immediately after initializing a channel or calling a method.
const channel = new RmtTxChannel(12, 1000000);
if (channel.lastOperationResult !== RmtResult.OK) {
console.log("Failed to initialize RMT channel. Error code:", channel.lastOperationResult);
}