The BasePio class provides a comprehensive abstraction for Programmable IO operations,
enabling precise timing control for digital signal generation and reception.
Itβs designed for timing-critical applications like WS2812 LED driving, IR communication,
stepper motor control, and custom protocols.
β¨ Key Features
β‘ Precise Timing - Nanosecond resolution timing control
π Buffered Operations - Efficient symbol transmission and reception
π Asynchronous Operation - Non-blocking with callback support
π― Multi-Channel Support - Simultaneous operation on multiple channels
π§ Flexible Configuration - Configurable polarity, idle states, and timing
π‘οΈ Robust Error Handling - Comprehensive validation and error reporting
ποΈ Performance Optimized - Hardware-accelerated when available
π Platform Agnostic - Works with various hardware backends
/**
* @brief Initialize the PIO peripheral
* @return hf_pio_err_t error code
*
* π Sets up PIO hardware, configures channels, and prepares for operation.
* Must be called before any PIO operations.
*
* @example
* EspPio pio(RMT_CHANNEL_0);
* hf_pio_err_t result = pio.Initialize();
* if (result == hf_pio_err_t::PIO_SUCCESS) {
* // PIO ready for use
* }
*/virtualhf_pio_err_tInitialize()noexcept=0;/**
* @brief Deinitialize the PIO peripheral
* @return hf_pio_err_t error code
*
* π§Ή Cleanly shuts down PIO hardware and releases resources.
*/virtualhf_pio_err_tDeinitialize()noexcept=0;/**
* @brief Check if PIO is initialized
* @return true if initialized, false otherwise
*
* β Query initialization status without side effects.
*/[[nodiscard]]boolIsInitialized()constnoexcept;/**
* @brief Ensure PIO is initialized (lazy initialization)
* @return true if initialized successfully, false otherwise
*
* π Automatically initializes PIO if not already initialized.
*/boolEnsureInitialized()noexcept;
/**
* @brief Start receiving symbols
* @param channel_id Channel identifier
* @param buffer Buffer to store received symbols
* @param buffer_size Size of the buffer
* @param timeout_us Timeout in microseconds (0 = no timeout)
* @return hf_pio_err_t error code
*
* π₯ Begins asynchronous symbol reception. Received symbols are stored
* in the provided buffer with precise timing information.
*
* @example
* hf_pio_symbol_t receive_buffer[64];
* hf_pio_err_t result = pio.StartReceive(0, receive_buffer, 64, 10000);
*/virtualhf_pio_err_tStartReceive(uint8_tchannel_id,hf_pio_symbol_t*buffer,size_tbuffer_size,uint32_ttimeout_us=0)noexcept=0;/**
* @brief Stop receiving and get the number of symbols received
* @param channel_id Channel identifier
* @param symbols_received [out] Number of symbols actually received
* @return hf_pio_err_t error code
*
* βΉοΈ Stops reception and returns the count of symbols received.
*/virtualhf_pio_err_tStopReceive(uint8_tchannel_id,size_t&symbols_received)noexcept=0;
/**
* @brief Check if a channel is currently busy
* @param channel_id Channel identifier
* @return true if channel is busy, false otherwise
*
* β Query channel busy status for flow control.
*/virtualboolIsChannelBusy(uint8_tchannel_id)constnoexcept=0;/**
* @brief Get channel status information
* @param channel_id Channel identifier
* @param status [out] Status information structure
* @return hf_pio_err_t error code
*
* π Retrieves comprehensive status information about a channel.
*/virtualhf_pio_err_tGetChannelStatus(uint8_tchannel_id,hf_pio_channel_status_t&status)constnoexcept=0;/**
* @brief Get PIO capabilities
* @param capabilities [out] Capability information structure
* @return hf_pio_err_t error code
*
* π Retrieves hardware capabilities and limitations.
*/virtualhf_pio_err_tGetCapabilities(hf_pio_capabilities_t&capabilities)constnoexcept=0;
/**
* @brief Set callback for transmission complete events
* @param callback Callback function
* @param user_data User data to pass to callback
*
* π Registers callback for transmission completion events.
*/virtualvoidSetTransmitCallback(hf_pio_transmit_callback_tcallback,void*user_data=nullptr)noexcept=0;/**
* @brief Set callback for reception complete events
* @param callback Callback function
* @param user_data User data to pass to callback
*
* π Registers callback for reception completion events.
*/virtualvoidSetReceiveCallback(hf_pio_receive_callback_tcallback,void*user_data=nullptr)noexcept=0;/**
* @brief Set callback for error events
* @param callback Callback function
* @param user_data User data to pass to callback
*
* π Registers callback for error events.
*/virtualvoidSetErrorCallback(hf_pio_error_callback_tcallback,void*user_data=nullptr)noexcept=0;/**
* @brief Clear all callbacks
*
* π§Ή Removes all registered callbacks.
*/virtualvoidClearCallbacks()noexcept=0;
π Data Structures
βοΈ Channel Configuration
1
2
3
4
5
6
7
8
9
structhf_pio_channel_config_t{hf_pin_num_tgpio_pin;///< GPIO pin for PIO signalhf_pio_direction_tdirection;///< Channel directionuint32_tresolution_ns;///< Time resolution in nanoseconds (user-friendly interface)hf_pio_polarity_tpolarity;///< Signal polarityhf_pio_idle_state_tidle_state;///< Idle stateuint32_ttimeout_us;///< Operation timeout in microsecondssize_tbuffer_size;///< Buffer size for symbols/durations};
π PIO Symbol
1
2
3
4
structhf_pio_symbol_t{uint32_tduration;///< Duration in resolution unitsboollevel;///< Signal level (true = high, false = low)};
π Channel Status
1
2
3
4
5
6
7
8
9
10
structhf_pio_channel_status_t{boolis_initialized;///< Channel is initializedboolis_busy;///< Channel is currently busyboolis_transmitting;///< Channel is transmittingboolis_receiving;///< Channel is receivingsize_tsymbols_queued;///< Number of symbols in queuesize_tsymbols_processed;///< Number of symbols processedhf_pio_err_tlast_error;///< Last error that occurreduint32_ttimestamp_us;///< Timestamp of last operation};
π PIO Capabilities
1
2
3
4
5
6
7
8
9
10
structhf_pio_capabilities_t{uint8_tmax_channels;///< Maximum number of channelsuint32_tmin_resolution_ns;///< Minimum time resolutionuint32_tmax_resolution_ns;///< Maximum time resolutionuint32_tmax_duration;///< Maximum single durationsize_tmax_buffer_size;///< Maximum buffer sizeboolsupports_bidirectional;///< Supports bidirectional modeboolsupports_loopback;///< Supports loopback modeboolsupports_carrier;///< Supports carrier modulation};
π PIO Statistics
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
structhf_pio_statistics_t{uint32_ttotalTransmissions;///< Total transmissions performeduint32_tsuccessfulTransmissions;///< Successful transmissionsuint32_tfailedTransmissions;///< Failed transmissionsuint32_ttotalReceptions;///< Total receptions performeduint32_tsuccessfulReceptions;///< Successful receptionsuint32_tfailedReceptions;///< Failed receptionsuint32_tsymbolsTransmitted;///< Total symbols transmitteduint32_tsymbolsReceived;///< Total symbols receiveduint32_taverageTransmissionTimeUs;///< Average transmission timeuint32_tmaxTransmissionTimeUs;///< Maximum transmission timeuint32_tminTransmissionTimeUs;///< Minimum transmission timeuint32_ttimingErrors;///< Number of timing errorsuint32_tbufferOverflows;///< Number of buffer overflows};
#include"mcu/esp32/EspPio.h"classWS2812Controller{private:EspPiopio*;staticconstexpruint32_tT0H_NS=350;// 0-bit high timestaticconstexpruint32_tT0L_NS=800;// 0-bit low timestaticconstexpruint32_tT1H_NS=700;// 1-bit high timestaticconstexpruint32_tT1L_NS=600;// 1-bit low timepublic:boolinitialize(){// Configure PIO channel for WS2812hf_pio_channel_config_tconfig;config.gpio_pin=18;// WS2812 data pinconfig.direction=hf_pio_direction_t::Transmit;config.resolution_ns=1000;// 1ΞΌs resolution (will be adjusted to closest achievable)config.polarity=hf_pio_polarity_t::Normal;config.idle_state=hf_pio_idle_state_t::Low;hf_pio_err_tresult=pio*.ConfigureChannel(0,config);return(result==hf_pio_err_t::PIO_SUCCESS);}voidsend_color(uint8_tr,uint8_tg,uint8_tb){// Convert RGB to GRB (WS2812 format)uint8_tgrb[3]={g,r,b};// Create symbol array for 24 bitshf_pio_symbol_tsymbols[24];intsymbol_index=0;for(inti=0;i<3;i++){for(intbit=7;bit>=0;bit--){boolbit_value=(grb[i]>>bit)&1;if(bit_value){// 1-bit: 700ns high, 600ns lowsymbols[symbol_index++]={7,true};// 700ns highsymbols[symbol_index++]={6,false};// 600ns low}else{// 0-bit: 350ns high, 800ns lowsymbols[symbol_index++]={4,true};// 350ns highsymbols[symbol_index++]={8,false};// 800ns low}}}// Transmit the color datapio*.Transmit(0,symbols,24,true);}voidset_all_leds(uint8_tr,uint8_tg,uint8_tb,intcount){for(inti=0;i<count;i++){send_color(r,g,b);// Small delay between LEDsesp_rom_delay_us(50);}}};
#include"mcu/esp32/EspPio.h"classIRReceiver{private:EspPiopio*;hf_pio_symbol_treceive_buffer*[128];boolreceiving*=false;public:boolinitialize(){hf_pio_channel_config_tconfig;config.gpio_pin=5;// IR receiver pinconfig.direction=hf_pio_direction_t::Receive;config.resolution_ns=1000;// 1ΞΌs resolutionconfig.polarity=hf_pio_polarity_t::Normal;config.idle_state=hf_pio_idle_state_t::High;// IR receivers idle highreturn(pio*.ConfigureChannel(0,config)==hf_pio_err_t::PIO_SUCCESS);}voidstart_receiving(){if(!receiving*){hf_pio_err_tresult=pio*.StartReceive(0,receive_buffer*,128,50000);// 50ms timeoutif(result==hf_pio_err_t::PIO_SUCCESS){receiving*=true;printf("π― Started IR reception\n");}}}voidstop_receiving(){if(receiving*){size_tsymbols_received;hf_pio_err_tresult=pio*.StopReceive(0,symbols_received);if(result==hf_pio_err_t::PIO_SUCCESS){printf("π₯ Received %zu symbols\n",symbols_received);process_ir_data(symbols_received);}receiving*=false;}}private:voidprocess_ir_data(size_tsymbol_count){if(symbol_count<4)return;// Simple NEC protocol decoderuint32_taddress=0;uint32_tcommand=0;// Check for NEC leader (9ms high, 4.5ms low)if(receive_buffer*[0].level&&receive_buffer*[0].duration>=8000&&// 8ms+ high!receive_buffer*[1].level&&receive_buffer*[1].duration>=4000){// 4ms+ lowprintf("π‘ NEC protocol detected\n");// Decode address and command (simplified)intbit_index=0;for(size_ti=2;i<symbol_count-2;i+=2){if(receive_buffer*[i].level&&receive_buffer*[i].duration>=500){// Valid pulseif(receive_buffer*[i+1].duration>=1500){// Long space = 1 bitif(bit_index<16){address|=(1<<bit_index);}else{command|=(1<<(bit_index-16));}}bit_index++;}}printf("π Address: 0x%04X, Command: 0x%04X\n",address,command);}}};
// β Always check initializationif(!pio.EnsureInitialized()){printf("β PIO initialization failed\n");returnfalse;}// β Validate channel configurationhf_pio_capabilities_tcaps;if(pio.GetCapabilities(caps)==hf_pio_err_t::PIO_SUCCESS){if(channel_id>=caps.max_channels){printf("β Channel %u exceeds maximum (%u)\n",channel_id,caps.max_channels);return;}}// β Use appropriate timing resolutionuint32_tresolution_ns=1000;// 1ΞΌs for most applicationsif(precise_timing_needed){resolution_ns=100;// 100ns for precise timing (hardware permitting)}// β Query actual achieved resolution (ESP32 specific)uint32_tactual_resolution_ns;if(pio.GetActualResolution(channel_id,actual_resolution_ns)==hf_pio_err_t::PIO_SUCCESS){ESP_LOGI(TAG,"Requested: %uns, Achieved: %uns",resolution_ns,actual_resolution_ns);}// β Check hardware constraints before configurationuint32_tmin_ns,max_ns,clock_hz;if(pio.GetResolutionConstraints(min_ns,max_ns,clock_hz)==hf_pio_err_t::PIO_SUCCESS){ESP_LOGI(TAG,"Hardware limits: %u-%uns with %u Hz clock",min_ns,max_ns,clock_hz);}// β Handle transmission errors gracefullyhf_pio_err_tresult=pio.Transmit(channel_id,symbols,count);if(result!=hf_pio_err_t::PIO_SUCCESS){printf("β οΈ Transmission error: %s\n",HfPioErrToString(result));// Implement retry logic or error recovery}// β Use callbacks for asynchronous operationpio.SetTransmitCallback([](uint8_tch,size_tsent,void*data){printf("β Transmitted %zu symbols on channel %u\n",sent,ch);});// β Monitor channel statushf_pio_channel_status_tstatus;if(pio.GetChannelStatus(channel_id,status)==hf_pio_err_t::PIO_SUCCESS){if(status.is_busy){printf("β³ Channel %u is busy\n",channel_id);}}
β Common Pitfalls
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// β Don't ignore timing requirements// WS2812 requires precise 350ns/700ns timinghf_pio_symbol_twrong_timing[]={{4,true},// 400ns - too long!{8,false}// 800ns - too long!};// β Don't use invalid channel numberspio.ConfigureChannel(99,config);// Invalid channel// β Don't ignore buffer size limitshf_pio_symbol_thuge_buffer[10000];// May exceed hardware limits// β Don't assume all protocols work the same// Different IR protocols have different timing requirements// β Don't forget to stop receptionpio.StartReceive(0,buffer,64);// Missing: pio.StopReceive(0, count);
π― Performance Optimization
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// π Use appropriate buffer sizessize_toptimal_buffer_size=64;// Balance between memory and performance// π Minimize symbol count for efficiency// Combine similar symbols when possiblehf_pio_symbol_toptimized[]={{1000,true},// 1ms high{500,false}// 500ΞΌs low};// Instead of: {100, true}, {100, true}, ..., {100, false}// π Use hardware-accelerated timing when available// ESP32 RMT provides precise timing without CPU intervention// π Batch operations for multiple channels// Configure all channels before starting transmission// π Use appropriate idle states// Match idle state to protocol requirements