Hey that's me

end2end zone

My life dumped to a web site

end2end

25-Minute Read

Introduction

In a personal project, I wanted to add 2 new devices to an existing quadcopter. The quadcopter was equipped with a 6-channel receiver meaning I only had 1 spare channel to control the 2 devices:

CH 1 : Throttle
CH 2 : Rudder
CH 3 : Ailerons
CH 4 : Elevator
CH 5 : Flight mode
CH 6 : ???

The following article explains how I manage to solve the issue: How can I control more than one device using a single channel.

EDIT: To make all mixing pre-calculations easier, do not hesitate to use my Mixing Calculator for R/C Transmitter (it’s an Excel Sheet!)

I could have used an 8-channel receiver to solve this but I did not had a free receiver that I could spare. This guide will show you how to multiplex RC transmitter switches (inputs) into a single channel.

Use a microcontroller

For this type of project, 7 channels is usually required. The easiest method would have been to replace the receiver but since I like the DIY way, I decided to multiplex two switches into the 6th channel and use a microcontroller to read the signal and demultiplex each switches states.

Note: This is only valid if there is a microcontroller connected with the receiver that you can program to demultiplex the signal. Do not expect your proprietary flight controller to be able to understand your signal.

Multiplexing switches (only)

Design

The following section illustrate the design required for multiplexing as much information as possible into a single channel.

Create blocks

The generic idea is to divide the whole range of the signal (from -150% to 150%) into blocks or bands. Each block correspond to a unique configuration of all switches. Then you create the required mixes to move/update the signal value to the block corresponding to the switches unique configuration.

Minimum number of blocks

The following explain what is the minimum number of blocks that are required for multiplexing a given amount of switches. The number of required blocks for a given number of switches is defined by the all the unique combinations that are possible with the switches. To calculate this, you multiply the number of combinations of each switch by each other. For instance: To multiplex three 2-position switches, 8 blocks are required (2*2*2) which makes 8 unique combinations:

Block Number Switches
2-pos 2-pos
0 0
1 0
2 0
3 0
4 1
5 1
6 1
7 1

To multiplex two 2-position and one 3-position switches, 12 blocks are required (2*2*3) which makes 12 unique combinations:

Block Number Switches
2-pos 2-pos
0 0
1 0
2 0
3 0
4 0
5 0
6 1
7 1
8 1
9 1
10 1
11 1

Note that each switch also requires a mix for working. The amount of switches you can multiplex is then also limited by the amount of mixes you can define in your transmitter.

Supporting 3-position switches

In your design, you will have to decide to support or not 3-position switches. If you do, it will reduce the amount of switches you can multiplex into a single channel since each switches requires 3 blocks instead of 2 blocks. An acceptable compromise is to use this switch as a 2-position switch. Position 1 would then be identical as Position 0 (OFF) and Position 2 would be ON.

Dead Zone

Blocks cannot be juxtaposed to each other and dead zones must be inserted between (or within) blocks. This is required since each transmitters and micro-controllers do not offer the same performance and precision. Dead zones are required since a micro-controller might read data from block 5 but the next pulse of the same signal may introduce a 5uS delay, which would correspond to a different block (ie block 6). For instance, if the following blocks are defined:

Block Min Max
0 -150% -141%
1 -140% -131%
2 -130% -121%
3 -120% -111%

The difference between -131% (block 1) and -130% (block 2) is ~5uS. If blocks would be juxtaposed, then the micro-controller could sometimes read block 1 and sometimes block 2 which does not mean the same thing at all. This delay might be from the receiver who does not provide a perfect pulse length or from the micro-controller who does not detect the end of the pulse with enough precision. Both errors can be avoided with dead zones. By introducing a dead zone of 5 steps within blocks (of 10 steps), the same blocks would then become something like:

Block Min Max Mix Target
DEAD -150% -148%
0 -147% -143% -145%
DEAD -142% -138%
1 -137% -133% -135%
DEAD -132% -128%
2 -127% -123% -125%
DEAD -122% -113%
3 -117% -113% -115%

The block size must also have a minimum size to account for the same effect. If the block size is too small, the micro-controller might read data from a dead zone and would not know what to do. The more accurate the receiver and the micro-controller, the smaller the block size and dead zone size can be. Mixes should be created to move the actual signal values to target the middle of the effective block area (not the dead zone). See the Mixes section for more details.

Block size

Based on my observation, the best values for block size and dead zone size are as follow: A block size of 10 steps is big enough to allow multiplexing a high amount of switches while leaving enough space for a reasonable dead zone between blocks. At the same time, a block size of 10 steps allows the blocks to be rounded easily which makes blocks offsets easy to calculate. The acceptable dead zone size (considering the average precision of most micro-controller and receivers), does not need to be bigger than 2 steps. This configuration leaves two blocks spaced by 4 steps which is enough to prevent issues. The following values are then considered “safe and tested” to get good and stable results:

Block size : 10 steps
Dead zone : 2 steps
Effective size : 6 steps

Mixes

The following section defines mix that are required to implement two basic scenarios.

Note that you can easily calculate the effect of a given mix by using my RC Transmitter Mix Calculator to identify the minimum, middle and maximum values of a mix.

Four 2-position switches

The following table shows the signal range and the middle of the effective area for each block. It is calculated using a block size of 10 steps and a dead zone of 2 steps which makes the effective block size to 6 steps:

Block Number Block Offsets Mix Target
Dead Effective Dead
0 -150 -149
1 -140 -139
2 -130 -129
3 -120 -119
4 -110 -109
5 -100 -99
6 -90 -89
7 -80 -79
8 -70 -69
9 -60 -59
10 -50 -49
11 -40 -39
12 -30 -29
13 -20 -19
14 -10 -9
15 0 1

The following mixes must be created to multiplex all switches unique configurations:

Mix info Mix Output
Number Switch
0 A
1 A
2 B
3 C
4 D

Note that first 2 mix are mapped to switch A. This is required since the minimum value of a High rate mix is -125% which gives a final mix value of -125% at Position 0. The only way to get a lower value (ie -146) would be to offset the mix (by -17) but then the Low rate value would have the same issue.

As you can see, the sum of all combined mixes matches the middle section of each effective block:

Block Number Switches Mixes
D C B
0 0 0
1 0 0
2 0 0
3 0 0
4 0 1
5 0 1
6 0 1
7 0 1
8 1 0
9 1 0
10 1 0
11 1 0
12 1 1
13 1 1
14 1 1
15 1 1

Use the Multiplexing 4x 2-position switches Cheat Sheet.xlsx for calculating all block offset when multiplexing four 2-position switches.

Three 2-position and one 3-position switches

The following table shows the signal range and the middle of the effective area for each block. It is calculated using a block size of 10 steps and a dead zone of 2 steps which makes the effective block size to 6 steps:

Block Number Block Offsets Mix Target
Dead Effective Dead
0 -150 -149
1 -140 -139
2 -130 -129
3 -120 -119
4 -110 -109
5 -100 -99
6 -90 -89
7 -80 -79
8 -70 -69
9 -60 -59
10 -50 -49
11 -40 -39
12 -30 -29
13 -20 -19
14 -10 -9
15 0 1
16 10 11
17 20 21
18 30 31
19 40 41
20 50 51
21 60 61
22 70 71
23 80 81

The following mixes must be created to multiplex all switches unique configurations:

Mix info Mix Output
Number Switch
0 A
1 A
2 A
3 B
4 C
5 D

Note that first 3 mix are mapped to switch A which is the 3-position switch. The first 2 mix are use to get a constant -136 on all positions. Then the 3rd mix moves the signal value over the first 3 blocks (to the previous, current or next block). As far as I know, there is no way to achieve the same result with only 2 mixes.

As you can see, the sum of all combined mixes matches the middle section of each effective block:

Block Number Switches Mixes
D C B
0 0 0
1 0 0
2 0 0
3 0 0
4 0 0
5 0 0
6 0 1
7 0 1
8 0 1
9 0 1
10 0 1
11 0 1
12 1 0
13 1 0
14 1 0
15 1 0
16 1 0
17 1 0
18 1 1
19 1 1
20 1 1
21 1 1
22 1 1
23 1 1

Use the Multiplexing 3x 2-Position and 1x 3-Position switches Cheat Sheet.xlsx for calculating all block offset when multiplexing three 2-position switches and one 3-position switch.

Three 3-position switches

The following table shows the signal range and the middle of the effective area for each block. Again, it is calculated using a block size of 10 steps and a dead zone of 2 steps which makes the effective block size to 6 steps:

Block Number Block Offsets Mix Target
Dead Effective Dead
0 -134 -133
1 -124 -123
2 -114 -113
3 -104 -103
4 -94 -93
5 -84 -83
6 -74 -73
7 -64 -63
8 -54 -53
9 -44 -43
10 -34 -33
11 -24 -23
12 -14 -13
13 -4 -3
14 6 7
15 16 17
16 26 27
17 36 37
18 46 47
19 56 57
20 66 67
21 76 77
22 86 87
23 96 97
24 106 107
25 116 117
26 126 127

The following mixes must be created to multiplex all switches unique configurations:

Mix info Mix Output
Number Switch
0 A
1 B
2 C

Note that only 3 mix is required for multiplexing three 3-position switches. Mixes are also centered around 0 (instead of starting at -150).

As you can see, the sum of all combined mixes matches the middle section of each effective block:

Block Number Switches Mixes
C B A
0 0 0
1 0 0
2 0 0
3 0 1
4 0 1
5 0 1
6 0 2
7 0 2
8 0 2
9 1 0
10 1 0
11 1 0
12 1 1
13 1 1
14 1 1
15 1 2
16 1 2
17 1 2
18 2 0
19 2 0
20 2 0
21 2 1
22 2 1
23 2 1
24 2 2
25 2 2
26 2 2

Use the Multiplexing 3x 3-Position switches Cheat Sheet.xlsx for calculating all block offset when multiplexing three 3-position switches.

Decoding

Decoding the switches configuration is relatively easy: First identify the block number matching the signal’s value using a sequence of “if” statements. Then, update switches state based on the currently selected block. Refer to tables above for offsets & switches states for each selected block.

Note that if you get a signal value that is within the dead zone, it probably means that you have an issue with your transmitter mixes. Verify your mixes and try again.

Since reading switches states does not imply any analog value, you do not really care if the signal value is within the effective area (or not) so clamping is not necessary beside detecting instability issue in the signal. However, in the low probability that you get a signal within a dead zone, then the first dead zone should be considered as if you read the first value of the effective area and the last dead zone as the last value of the effective area.

Required Libraries

PinChangeInt

This library allows the arduino to attach interrupts on multiple pins. eRCaGuy_Timer2_Counter (optional).

This library configures the arduino’s timer2 to 0.5µs precision. It is used for a micros() function replacement and allows times calculations that are far more precise (8 times!) than the default’s 4µs resolution.

Code sample

The following arduino code (*.ino) can be used to demultiplex the three scenarios above:

//Using RcReceiverSignal v1.1.203
//required to read the receiver's value
//available at http://www.end2endzone.com/rcreceiversignal-an-arduino-library-for-retreiving-the-rc-transmitter-value-from-an-rc-receiver-pulse/
#include <RcReceiverSignal.h>

//Using PinChangeInt version 2402
//RcReceiverSignal library has a dependency to PinChangeInt library.
//available at http://code.google.com/p/arduino-pinchangeint/
#include <PinChangeInt.h>

//Using eRCaGuy_Timer2_Counter version 20140709 (last updated 9 July 2014)
//Required to have a micros() replacement function which has a 
//1us resolution instead of 4usec.
//For more information on this library, see the following:
// http://electricrcaircraftguy.com/2014/02/Timer2Counter-more-precise-Arduino-micros-function.html
// http://www.instructables.com/id/How-to-get-an-Arduino-micros-function-with-05us-pr/
#include <eRCaGuy_Timer2_Counter.h>

//project's constants
#define RECEIVER_AUX1_IN_PIN 2 // we could choose any pin

//project's switches
#define ENABLE_SERIAL_OUTPUT

//*****************************************************************************
//  TODO: UNCOMMENT ONE OF THE FOLLOWING:
//****************************************************************************/
//#define _4X2POS
//#define _3X2POS1X3POS
//#define _3X3POS

DECLARE_RECEIVER_SIGNAL(receiver_aux1_handler);

inline short clamp(const short & iMin, const short & iValue, const short & iMax) {
  if (iValue < iMin)
    return iMin;
  if (iValue > iMax)
    return iMax;
  return iValue;
}

void demultiplex4x2Pos(const short & iSignal, bool & oSwitchA, bool & oSwitchB, bool & oSwitchC, bool & oSwitchD) {
  #define setSwitches(d,c,b,a) oSwitchA=(a==1); oSwitchB=(b==1); oSwitchC=(c==1); oSwitchD=(d==1);
       if ( -150 <= iSignal && iSignal <=  -141 ) { setSwitches( 0 , 0 , 0 , 0 ) }
  else if ( -140 <= iSignal && iSignal <=  -131 ) { setSwitches( 0 , 0 , 0 , 1 ) }
  else if ( -130 <= iSignal && iSignal <=  -121 ) { setSwitches( 0 , 0 , 1 , 0 ) }
  else if ( -120 <= iSignal && iSignal <=  -111 ) { setSwitches( 0 , 0 , 1 , 1 ) }
  else if ( -110 <= iSignal && iSignal <=  -101 ) { setSwitches( 0 , 1 , 0 , 0 ) }
  else if ( -100 <= iSignal && iSignal <=   -91 ) { setSwitches( 0 , 1 , 0 , 1 ) }
  else if (  -90 <= iSignal && iSignal <=   -81 ) { setSwitches( 0 , 1 , 1 , 0 ) }
  else if (  -80 <= iSignal && iSignal <=   -71 ) { setSwitches( 0 , 1 , 1 , 1 ) }
  else if (  -70 <= iSignal && iSignal <=   -61 ) { setSwitches( 1 , 0 , 0 , 0 ) }
  else if (  -60 <= iSignal && iSignal <=   -51 ) { setSwitches( 1 , 0 , 0 , 1 ) }
  else if (  -50 <= iSignal && iSignal <=   -41 ) { setSwitches( 1 , 0 , 1 , 0 ) }
  else if (  -40 <= iSignal && iSignal <=   -31 ) { setSwitches( 1 , 0 , 1 , 1 ) }
  else if (  -30 <= iSignal && iSignal <=   -21 ) { setSwitches( 1 , 1 , 0 , 0 ) }
  else if (  -20 <= iSignal && iSignal <=   -11 ) { setSwitches( 1 , 1 , 0 , 1 ) }
  else if (  -10 <= iSignal && iSignal <=    -1 ) { setSwitches( 1 , 1 , 1 , 0 ) }
  else if (    0 <= iSignal && iSignal <=     9 ) { setSwitches( 1 , 1 , 1 , 1 ) }
  else {
    setSwitches(0,0,0,0);
  }
  #undef setSwitches
}

void demultiplex3x2Pos1x3Pos(const short & iSignal, unsigned char & oSwitchA, bool & oSwitchB, bool & oSwitchC, bool & oSwitchD) {
  #define setSwitches(d,c,b,a) oSwitchA=a; oSwitchB=(b==1); oSwitchC=(c==1); oSwitchD=(d==1);
       if ( -150 <= iSignal && iSignal <=  -141 ) { setSwitches( 0 , 0 , 0 , 0 ) }
  else if ( -140 <= iSignal && iSignal <=  -131 ) { setSwitches( 0 , 0 , 0 , 1 ) }
  else if ( -130 <= iSignal && iSignal <=  -121 ) { setSwitches( 0 , 0 , 0 , 2 ) }
  else if ( -120 <= iSignal && iSignal <=  -111 ) { setSwitches( 0 , 0 , 1 , 0 ) }
  else if ( -110 <= iSignal && iSignal <=  -101 ) { setSwitches( 0 , 0 , 1 , 1 ) }
  else if ( -100 <= iSignal && iSignal <=   -91 ) { setSwitches( 0 , 0 , 1 , 2 ) }
  else if (  -90 <= iSignal && iSignal <=   -81 ) { setSwitches( 0 , 1 , 0 , 0 ) }
  else if (  -80 <= iSignal && iSignal <=   -71 ) { setSwitches( 0 , 1 , 0 , 1 ) }
  else if (  -70 <= iSignal && iSignal <=   -61 ) { setSwitches( 0 , 1 , 0 , 2 ) }
  else if (  -60 <= iSignal && iSignal <=   -51 ) { setSwitches( 0 , 1 , 1 , 0 ) }
  else if (  -50 <= iSignal && iSignal <=   -41 ) { setSwitches( 0 , 1 , 1 , 1 ) }
  else if (  -40 <= iSignal && iSignal <=   -31 ) { setSwitches( 0 , 1 , 1 , 2 ) }
  else if (  -30 <= iSignal && iSignal <=   -21 ) { setSwitches( 1 , 0 , 0 , 0 ) }
  else if (  -20 <= iSignal && iSignal <=   -11 ) { setSwitches( 1 , 0 , 0 , 1 ) }
  else if (  -10 <= iSignal && iSignal <=    -1 ) { setSwitches( 1 , 0 , 0 , 2 ) }
  else if (    0 <= iSignal && iSignal <=     9 ) { setSwitches( 1 , 0 , 1 , 0 ) }
  else if (   10 <= iSignal && iSignal <=    19 ) { setSwitches( 1 , 0 , 1 , 1 ) }
  else if (   20 <= iSignal && iSignal <=    29 ) { setSwitches( 1 , 0 , 1 , 2 ) }
  else if (   30 <= iSignal && iSignal <=    39 ) { setSwitches( 1 , 1 , 0 , 0 ) }
  else if (   40 <= iSignal && iSignal <=    49 ) { setSwitches( 1 , 1 , 0 , 1 ) }
  else if (   50 <= iSignal && iSignal <=    59 ) { setSwitches( 1 , 1 , 0 , 2 ) }
  else if (   60 <= iSignal && iSignal <=    69 ) { setSwitches( 1 , 1 , 1 , 0 ) }
  else if (   70 <= iSignal && iSignal <=    79 ) { setSwitches( 1 , 1 , 1 , 1 ) }
  else if (   80 <= iSignal && iSignal <=    89 ) { setSwitches( 1 , 1 , 1 , 2 ) }
  else {
    setSwitches(0,0,0,0);
  }
  #undef setSwitches
}

void demultiplex3x3Pos(const short & iSignal, unsigned char & oSwitchA, unsigned char & oSwitchB, unsigned char & oSwitchC) {
  #define setSwitches(c,b,a) oSwitchA=a; oSwitchB=b; oSwitchC=c;
       if ( -134 <= iSignal && iSignal <=  -125 ) { setSwitches( 0 , 0 , 0 ) }
  else if ( -124 <= iSignal && iSignal <=  -115 ) { setSwitches( 0 , 0 , 1 ) }
  else if ( -114 <= iSignal && iSignal <=  -105 ) { setSwitches( 0 , 0 , 2 ) }
  else if ( -104 <= iSignal && iSignal <=   -95 ) { setSwitches( 0 , 1 , 0 ) }
  else if (  -94 <= iSignal && iSignal <=   -85 ) { setSwitches( 0 , 1 , 1 ) }
  else if (  -84 <= iSignal && iSignal <=   -75 ) { setSwitches( 0 , 1 , 2 ) }
  else if (  -74 <= iSignal && iSignal <=   -65 ) { setSwitches( 0 , 2 , 0 ) }
  else if (  -64 <= iSignal && iSignal <=   -55 ) { setSwitches( 0 , 2 , 1 ) }
  else if (  -54 <= iSignal && iSignal <=   -45 ) { setSwitches( 0 , 2 , 2 ) }
  else if (  -44 <= iSignal && iSignal <=   -35 ) { setSwitches( 1 , 0 , 0 ) }
  else if (  -34 <= iSignal && iSignal <=   -25 ) { setSwitches( 1 , 0 , 1 ) }
  else if (  -24 <= iSignal && iSignal <=   -15 ) { setSwitches( 1 , 0 , 2 ) }
  else if (  -14 <= iSignal && iSignal <=    -5 ) { setSwitches( 1 , 1 , 0 ) }
  else if (   -4 <= iSignal && iSignal <=     5 ) { setSwitches( 1 , 1 , 1 ) }
  else if (    6 <= iSignal && iSignal <=    15 ) { setSwitches( 1 , 1 , 2 ) }
  else if (   16 <= iSignal && iSignal <=    25 ) { setSwitches( 1 , 2 , 0 ) }
  else if (   26 <= iSignal && iSignal <=    35 ) { setSwitches( 1 , 2 , 1 ) }
  else if (   36 <= iSignal && iSignal <=    45 ) { setSwitches( 1 , 2 , 2 ) }
  else if (   46 <= iSignal && iSignal <=    55 ) { setSwitches( 2 , 0 , 0 ) }
  else if (   56 <= iSignal && iSignal <=    65 ) { setSwitches( 2 , 0 , 1 ) }
  else if (   66 <= iSignal && iSignal <=    75 ) { setSwitches( 2 , 0 , 2 ) }
  else if (   76 <= iSignal && iSignal <=    85 ) { setSwitches( 2 , 1 , 0 ) }
  else if (   86 <= iSignal && iSignal <=    95 ) { setSwitches( 2 , 1 , 1 ) }
  else if (   96 <= iSignal && iSignal <=   105 ) { setSwitches( 2 , 1 , 2 ) }
  else if (  106 <= iSignal && iSignal <=   115 ) { setSwitches( 2 , 2 , 0 ) }
  else if (  116 <= iSignal && iSignal <=   125 ) { setSwitches( 2 , 2 , 1 ) }
  else if (  126 <= iSignal && iSignal <=   135 ) { setSwitches( 2 , 2 , 2 ) }
  else {
    setSwitches(0,0,0);
  }
  #undef setSwitches
}

uint32_t timer2GetCountWrapperFunction() {
  return timer2.get_count();
}

void setup() {
  //configure Timer2
  timer2.setup(); //this MUST be done before the other Timer2_Counter functions work; Note: since this messes up PWM outputs on pins 3 & 11, as well as 
                  //interferes with the tone() library (http://arduino.cc/en/reference/tone), you can always revert Timer2 back to normal by calling 
                  //timer2.unsetup()

  //configure RcReceiverSignal with an external time counter
  //eRCaGuy_Timer2_Counter lirary has 0.5us resolution.
  //The counter value must be divided by 2 to convert from 0.5us steps to 1us steps
  //which results in microseconds resolution.
  RcReceiverSignal::setExternalTimeCounter(&timer2GetCountWrapperFunction, 1, 2);

  //link RcReceiverSignal to use PinChangeInt library
  RcReceiverSignal::setAttachInterruptFunction(&PCintPort::attachInterrupt);
  RcReceiverSignal::setPinStatePointer(&PCintPort::pinState);

  #ifdef ENABLE_SERIAL_OUTPUT
    Serial.begin(115200);
    Serial.println("ready");
  #endif

  receiver_aux1_handler_setup(RECEIVER_AUX1_IN_PIN);
}

void loop() {
  //detect when the receiver AUX1 value has changed
  if (receiver_aux1_handler.hasChanged())
  {
    unsigned long pwmValue = receiver_aux1_handler.getPwmValue();
    RcReceiverSignal::VALUE signal = receiver_aux1_handler.getSignalValue(pwmValue);

    char buffer[100];

#ifdef _4X2POS
    bool switchA = false;
    bool switchB = false;
    bool switchC = false;
    bool switchD = false;
    demultiplex4x2Pos(signal, switchA, switchB, switchC, switchD);

    //print switches state
    sprintf(buffer, "PWM=%04d s=%04d  A=%d B=%d C=%d D=%d", 
                      (int)pwmValue,
                      (int)signal,
                      switchA,
                      switchB,
                      switchC,
                      switchD);
#endif 
#ifdef _3X2POS1X3POS
    unsigned char switchA = 0;
    bool switchB = false;
    bool switchC = false;
    bool switchD = false;
    demultiplex3x2Pos1x3Pos(signal, switchA, switchB, switchC, switchD);

    //print switches state
    sprintf(buffer, "PWM=%04d s=%04d  A=%d B=%d C=%d D=%d", 
                      (int)pwmValue,
                      (int)signal,
                      switchA,
                      switchB,
                      switchC,
                      switchD);
#endif
#ifdef _3X3POS
    unsigned char switchA = 0;
    unsigned char switchB = 0;
    unsigned char switchC = 0;
    demultiplex3x3Pos(signal, switchA, switchB, switchC);

    //print switches state
    sprintf(buffer, "PWM=%04d s=%04d  A=%d B=%d C=%d", 
                      (int)pwmValue,
                      (int)signal,
                      switchA,
                      switchB,
                      switchC);
#endif
    Serial.println(buffer);
  }
}

Sample data

Multiplexing an analog input and switches

Design

Including an analog value (usually a rotating knob) into the multiplexed signal is also possible. However, only a single analog value can be multiplexed.

Please note that including an analog value reduces the number of switches that can be multiplexed into the signal.

Define resolution

Defining the resolution of the analog value means that you must choose the granularity of the value. By default the analog value has at least 200 different values and ranges from -100% to +100%. Since you also want to multiplex switches into the same signal, the resolution must be reduced from 200 different values to a lot less. To support a desired resolution, multiple blocks will need to be sacrificed. I do recommend a resolution of 40 steps (with values from 0 to 39) which is a nice resolution to allow enough details and can also be subdivided into other zones.

What’s different

The design for including an analog value is different than having only switches.

Block size

Block size must be increased to allow the desired resolution. The higher the resolution, the less switches you can multiplex. The block size must be big enough to fit both dead zones and the desired resolution.

Dead zone

A dead zone of 3 steps is also suggested. For instance, to support a resolution of 40 different steps, the block size must be of 46 steps (3+40+3=46). The following table shows the signal range and the middle of the effective area for each block. It is calculated using a block size of 46 steps and a dead zone of 3 steps which makes the effective block size to 40 steps:

Block Number Block Offsets
Dead Effective
0 -150
1 -104
2 -58
3 -12
4 34
5 80

Note that only 6 blocks can be defined using a block size of 46 steps. These blocks only allows 2 sub configurations for multiplexing switches:

  • 2-position switch + 3-position switch
  • Two 2-position switches

Mixes

When multiplexing an analog value, mixes do not have to target the middle of the block’s effective zone (as with switches) since the signal’s value can move within the whole effective area of the block. Assuming the first configuration (2-pos + 3-pos), the following mixes must be created to multiplex all switches unique configurations:

Mix info Mix Output
Number Switch
0 RKnob
1 RKnob
2 A
3 B

Note that first 2 mix are mapped to the right knob to reach the effective range of the first block (-147% to -108%). Switch B is a 3-position switch and offsets the analog range between block 0 to 2. Then the 3rd mix, assigned to Switch A (2-position), offsets the 3 effective block of switch B to block 0-2 or 3-5.

As you can see, the sum of all combined mixes matches the middle section of each effective block:

Block Number Switches Mixes
A B RKnob
0 0 0
0 0 0
1 0 1
1 0 1
2 0 2
2 0 2
3 1 0
3 1 0
4 1 1
4 1 1
5 1 2
5 1 2

Use the Multiplexing Analog Knob with 1x 2-Position and 1x 3-Position switches Cheat Sheet.xlsx for calculating all block offset when multiplexing an analog value with a 2-position and a 3-position switch.

Decoding

Decoding an analog value with switches configuration is different:

  • First identify the block number matching the signal’s value using a sequence of “if” statements.
  • Then clamp the value within the effective block area. This is required since the signal can get close to a dead zone (or even reach a dead zone!). To get the actual analog value, you must also offset the block’s effective range to get a constant 0-39 range.
  • Finally, update switches state based on the currently selected block. Refer to tables above for offsets & switches states for each selected block.

Note that reading a value (with the micro-controller) that is outside the analog effective area should be considered the same as reading an analog value of 0 or 39 depending on the closest dead zone.

Code sample

The following arduino code (*.ino) can be used to demultiplex the scenario above:

//Using RcReceiverSignal v1.1.203
//required to read the receiver's value
//available at http://www.end2endzone.com/rcreceiversignal-an-arduino-library-for-retreiving-the-rc-transmitter-value-from-an-rc-receiver-pulse/
#include <RcReceiverSignal.h>

//Using PinChangeInt version 2402
//RcReceiverSignal library has a dependency to PinChangeInt library.
//available at http://code.google.com/p/arduino-pinchangeint/
#include <PinChangeInt.h>

//Using eRCaGuy_Timer2_Counter version 20140709 (last updated 9 July 2014)
//Required to have a micros() replacement function which has a 
//1us resolution instead of 4usec.
//For more information on this library, see the following:
// http://electricrcaircraftguy.com/2014/02/Timer2Counter-more-precise-Arduino-micros-function.html
// http://www.instructables.com/id/How-to-get-an-Arduino-micros-function-with-05us-pr/
#include <eRCaGuy_Timer2_Counter.h>

//project's constants
#define RECEIVER_AUX1_IN_PIN 2 // we could choose any pin

//project's switches
#define ENABLE_SERIAL_OUTPUT

DECLARE_RECEIVER_SIGNAL(receiver_aux1_handler);

inline short clamp(const short & iMin, const short & iValue, const short & iMax) {
  if (iValue < iMin)
    return iMin;
  if (iValue > iMax)
    return iMax;
  return iValue;
}

void demultiplexAnalog40_1x2Pos1x3Pos(const short & iSignal, unsigned char & oAnalogA, bool & oSwitch2, unsigned char & oSwitch3) {
  #define setSwitches(effectiveMin,signal,effectiveMax,a,b) oAnalogA=clamp(effectiveMin,signal,effectiveMax) - (effectiveMin); oSwitch2=a; oSwitch3=b;
       if ( -150 <= iSignal && iSignal <= -105 ) { setSwitches( -147 ,iSignal, -108, 0, 0 ) }
  else if ( -104 <= iSignal && iSignal <=  -59 ) { setSwitches( -101 ,iSignal,  -62, 0, 1 ) }
  else if (  -58 <= iSignal && iSignal <=  -13 ) { setSwitches(  -55 ,iSignal,  -16, 0, 2 ) }
  else if (  -12 <= iSignal && iSignal <=   33 ) { setSwitches(   -9 ,iSignal,   30, 1, 0 ) }
  else if (   34 <= iSignal && iSignal <=   79 ) { setSwitches(   37 ,iSignal,   76, 1, 1 ) }
  else if (   80 <= iSignal && iSignal <=  125 ) { setSwitches(   83 ,iSignal,  122, 1, 2 ) }
  else
  {
    setSwitches( 0,0,0,0,0 );
  }
  #undef setSwitches
}

uint32_t timer2GetCountWrapperFunction() {
  return timer2.get_count();
}

void setup() {
  //configure Timer2
  timer2.setup(); //this MUST be done before the other Timer2_Counter functions work; Note: since this messes up PWM outputs on pins 3 & 11, as well as 
                  //interferes with the tone() library (http://arduino.cc/en/reference/tone), you can always revert Timer2 back to normal by calling 
                  //timer2.unsetup()

  //configure RcReceiverSignal with an external time counter
  //eRCaGuy_Timer2_Counter lirary has 0.5us resolution.
  //The counter value must be divided by 2 to convert from 0.5us steps to 1us steps
  //which results in microseconds resolution.
  RcReceiverSignal::setExternalTimeCounter(&timer2GetCountWrapperFunction, 1, 2);

  //link RcReceiverSignal to use PinChangeInt library
  RcReceiverSignal::setAttachInterruptFunction(&PCintPort::attachInterrupt);
  RcReceiverSignal::setPinStatePointer(&PCintPort::pinState);

  #ifdef ENABLE_SERIAL_OUTPUT
    Serial.begin(115200);
    Serial.println("ready");
  #endif

  receiver_aux1_handler_setup(RECEIVER_AUX1_IN_PIN);
}

void loop() {
  //detect when the receiver AUX1 value has changed
  if (receiver_aux1_handler.hasChanged())
  {
    unsigned long pwmValue = receiver_aux1_handler.getPwmValue();
    RcReceiverSignal::VALUE signal = receiver_aux1_handler.getSignalValue(pwmValue);

    char buffer[100];

    unsigned char analogA = 0;
    bool switch2 = 0;
    unsigned char switch3 = 0;
    demultiplexAnalog40_1x2Pos1x3Pos(signal, analogA, switch2, switch3);

    //print switches state
    sprintf(buffer, "PWM=%04d s=%04d  analogA=%2d A=%d B=%d", 
                      (int)pwmValue,
                      (int)signal,
                      analogA,
                      switch2,
                      switch3);
    Serial.println(buffer);
  }
}

Sample data

Say Something

Comments

Recent Posts

Categories

About

Hey! I'm Antoine author of end2end zone. Take a couple of minutes to get to know me a little better.