When I started working on this project, I had no previous experience in Arduino/ESP32 or C/C++ and because of this, everything was a learning curve.

Optimising the Digital Inputs using bit manipulation

Because I had a total of 5 switches and 10 push buttons I was using 15 bytes instead of just a couple.

In order to achieve this, I decided to allocate a single bit for every single digital input.

bool switchStatuses[15] = {
    // Store the current switches
    switch_1,
    switch_2,
    switch_3,
    switch_4,
    switch_5,

    // Rotary Encoder - Left
    left_up,
    left_down,
    left_left,
    left_right,
    left_middle,

    // Rotary Encoder - Right
    right_up,
    right_down,
    right_left,
    right_right,
    right_middle,
};

After reading everything, I had to extract the two bytes and add them to my payload.

...
uint16_t Tx::encodeStatusToByte(bool statuses[], int num) {
    uint16_t encodedByte = 0;

    for (int i = 0; i < num; i++) {
        encodedByte |= (statuses[i] << i);
    }

    return encodedByte;
}
...
switches_state = Tx::encodeStatusToByte(switchStatuses, 15);

switches_state_1 = (switches_state & 0xFF);
switches_state_2 = ((switches_state >> 8) & 0xFF);

On the RX part, I only had to combine the bytes using this:

...
void Rx::decodeByteToStatuses(uint16_t encodedByte, bool statuses[], int num) {
  for (int i = 0; i < num; i++) {
    statuses[i] = (encodedByte >> i) & 0x01;
  }
}
...

uint16_t combined_byte = Rx::combineBytes(
    switches_state_1, switches_state_2
);

// Decode the uint16_t back into switch statuses
bool decoded_switch_statuses[15];
Rx::decodeByteToStatuses(combined_byte, decoded_switch_statuses, 15);

Optimising the payload by allocating unique channels

Using the bit manipulation I created a config that only uses 2 bytes. The first 3 bits are reserved for the sender identification while the other 13 bits are used to confirm if the channel was enabled or disabled.

/**
 * TX Payload Config
 * TX0 : Remote TX
 * TX1 : Sensors 1
 * TX2 : Sensors 2
 * 
 * CH1  : Left Joystick Up/Down ( 1 byte )
 * CH2  : Left Joystick Left/Right ( 1 byte )
 * CH3  : Right Joystick Up/Down ( 1 byte )
 * CH4  : Right Joystick Left/Right ( 1 byte )
 * CH5  : Switches/Push Buttons ( 2 bytes )
 * CH6  : Left Rotary Encoder ( 2 bytes )
 * CH7  : Right Rotary Encoder ( 2 bytes )
 * CH8  : Potentiometer 1 ( 1 byte )
 * CH9  : Potentiometer 2 ( 1 byte )
 * CH10 : Potentiometer 3 ( 1 byte )
 * CH11 : Potentiometer 4 ( 1 byte )
 * CH12 : Potentiometer 5 ( 1 byte )
 * CH13 : Potentiometer 6 ( 1 byte )
 */
bool payload_config[16] = {
    0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

After that, I only had to create a new Channel structure and prepare the data.

typedef struct Channel {
    uint8_t required_bytes;
    uint8_t first_byte;
    uint8_t second_byte;
};

/**
 * CH1  : Left Joystick Up/Down ( 1 byte )
 * CH2  : Left Joystick Left/Right ( 1 byte )
 * CH3  : Right Joystick Up/Down ( 1 byte )
 * CH4  : Right Joystick Left/Right ( 1 byte )
 * CH5  : Switches/Push Buttons ( 2 bytes )
 * CH6  : Left Rotary Encoder ( 2 bytes )
 * CH7  : Right Rotary Encoder ( 2 bytes )
 * CH8  : Potentiometer 1 ( 1 byte )
 * CH9  : Potentiometer 2 ( 1 byte )
 * CH10 : Potentiometer 3 ( 1 byte )
 * CH11 : Potentiometer 4 ( 1 byte )
 * CH12 : Potentiometer 5 ( 1 byte )
 * CH13 : Potentiometer 6 ( 1 byte )
 */
Channel channels[13] = {
    {1, joystick_default_value, 0},
    {1, joystick_default_value, 0},
    {1, joystick_default_value, 0},
    {1, joystick_default_value, 0},
    {2, 0, 0},
    {2, 0, 0},
    {2, 0, 0},
    {1, 0, 0},
    {1, 0, 0},
    {1, 0, 0},
    {1, 0, 0},
    {1, 0, 0},
    {1, 0, 0}
};

When sending the data, if the new value is not the same as the default one, I would just activate the channel by setting the bit to 1 instead of 0 and add the channel info in the current payload object.

Categorized in: