Light Dimmer using a Rotary Encoder

inglewoodpete

Senior Member
Background
About a year ago, my wife and I bought a camper-trailer. It was fitted with a 10W LED light strip in the main tent which is great when you want a bright light. The light switch was mounted on a side pole at the foot of the bed: not the best location when you're in bed. When turned on in the middle of the night, it was pretty bright and bound to wake the sleeping partner!

We decided that a dimmer was called for, controlled from the head of the bed but switchable from the foot of the bed when stepping into the tent at night.

The solution
I decided on a small diecast enclosure, with master switch, mounted in the original switch position at the foot of the bed near the tent's doorway. An 08M2 PICAXE would drive an N-channel MOSFET with PWM to control the light intensity. A rotary encoder, mounted at the centre of the head of the bed, would control the dimming. The master switch would provide power to the PICAXE, which starts the PWM at 100% at bootup. The camper has a 12v 200Ah battery bank, so when the encoder turns the light down to 0% (off), the standby current to the PICAXE is insignificant.

Algorythm
One problem when using a PICAXE to read two input pins before making decisions on encoder movement and direction is the amount of time for individual commands to be executed. The microcontroller needs to read the inputs, compare the encoder position value with the previous one and make decisions on the result. If the PIC should miss a 2-bit position code, this upsets the sequence, and consequently the direction of the encoder cannot be determined. By reading the entire port, then masking out any unwanted bits, the instantaneous encoder position can be read reliably. Of course, running the PICAXE at its maximum speed helps the cause, too.

So, in a nutshell, the 08M2 reads and saves the two encoder bits and places them alongside the two previously recorded bits. All stored in one half of a byte variable (nibble). For added integrity checking, the previous nibble is then held in the other nibble. This way, the byte can be compared with a known (constant) value to determine the direction that the encoder shaft has been turned.

Development
Code was developed on an AXE092 Schools Experimenter development board with an 08M2. Initially, I tried to use the encoder without a knob on the shaft. This resulted in the two-bit codes changing too quickly for the PICAXE to track reliably. Things became much more reliable after I fitted a 23mm knob: far more realistic than the bare shaft, anyway.

For those of you who want to replicate these experiments, you can breadboard the circuit following the I/O pin allocations described in the code. The rotary encoder was a cheap one I bought from Altronics but there are many suitable types available. Basically, the encoder has three pins: the centre one was common to both phase switches and the outer two were the switched phases. I used the PICAXE's internal weak pullup resistors via the PullUp command, with the encoder's phase switches pulling the two encoded lines low as its shaft is rotated.

I have broken the development of the software into three stages, with three progressive working pieces of code. Due to forum posting limitations, I have posted these programs progressively, below.
 

inglewoodpete

Senior Member
Stage 1: Logging encoder steps as hexadecimal values to the Programming Editor's Terminal.
Rich (BB code):
'Simple Rotary Encoder Demonstration Programme
'Written by inglewoodpete
'
Symbol Version = 1 '0.1 23-May-2018  136 bytes Init. development for 2-bit rotary encoder
'
Symbol Major = 0           'Version revision for software development
#PICAXE 08M2
#Terminal 38400            'SerTxd at 32MHz
'
' **** Hardware Assignments - 6 I/O Legs + 2 x power (Prefix i for nput, o for output)
'                                 Leg
'      Gnd                       ' 8 (0v power)
'ymbol oSerData         = C.0    ' 7 [SerOut] Serial Output for debugging
'                       = C.1    ' 6 Dig Input/ADC/Dig Output
Symbol oPWMControl      = C.2    ' 5 PWM Output
Symbol iEncoder0        = pinC.3 ' 4 Digital input - One bit of encoder
Symbol iEncoder1        = pinC.4 ' 3 Digital input - One bit of encoder
'                       = C.5    ' 2 [SerIn] (or I/P only)
'      Vcc                       ' 1 (+ve power) Safe range is 2.3 - 5.5 volts
'
' **** Variables b0 to b27 or w0 to w13 (Prefix b for byte; w for word)
'
Symbol bNewValue        = b1     'w0 4-bit value made up of Previous and Current states
Symbol bCurrentState    = b2     'w1 Current copy of encoder output
Symbol bPreviousState   = b3     'w1 Previous copy of encoder output
Symbol bByteToShow      = b4     'w2 Used to display hexadecimal value
Symbol bTemp            = b5     'w2 Used within ShowHex routine
'
' **** Constants    (Prefix c, msk)
'
'Constants for timers etc
Symbol cStartupPause    = 1000      'Allows debugging terminal to open if required
'Masks etc
Symbol mskEncoderIn     = %00011000 'Bits 3 and 4 (ANDed to exclude all pins bar 3 & 4)
Symbol mskLoNibble      = %00001111
'
' ---------------------------------------------------------------------
Init: SetFreq M32
      PullUp mskEncoderIn
      '
      Pause cStartupPause                 'Allows logging terminal to open after download
      '
      SerTxd (CR, LF, "Rotary Encoder Demo Ver:", #Major, ".", #Version, CR, LF)
      '
      bPreviousState = PinsC And mskEncoderIn   'Read in the two encoder bits (X,Y)
      '
      ' ****  M a i n    L o o p  ****
      '
      Do
         bCurrentState = PinsC And mskEncoderIn    'Read the current encoder bits (X,Y)
         If bCurrentState <> bPreviousState Then   'Has encoder shaft turned?
            ' [* 4]Move previous XY bits left 2 bit-positions; [Or]merge current XY bits
            ' [/ 8]Move all 4 bits right by 3 bit positions
            ' This results in previous, current encoder bits appearing in lower nibble
            bNewValue = bPreviousState * 4 Or bCurrentState / 8 'Data now in bit posn 0-3
            '
            bByteToShow = bNewValue
            GoSub SHexNibl                         'Show 4-bit value as hex
            'SerTxd(#bit15,#bit14,#bit13,#bit12,".",#bit11,#bit10,#bit9,#bit8,CR,LF)
            bPreviousState = bCurrentState
         EndIf
      Loop
'
' **** Subroutines *****************************
'
' ---- ShowHex: Send Hexadecimal value to PE Terminal -------------------------
'
'        Send a  byte  as hex to terminal on the programming lead (ShowHex:)
'        Send a nibble as hex to terminal on the programming lead (SHexNibl:)
'        Alternate "D" entries cause a leading $ sign to be logged
'
'        Entry:   bByteToShow (byte) data to be transmitted
'        Exit:    bByteToShow (byte) unchanged
'        Used:    bTemp       (byte)
'
ShowHexD:SerTxd("$")                      'Alternate entry
ShowHex: bTemp = bByteToShow / 16         'Get high nibble
         GoSub Sh4bits                    'Show it
         Goto SHexNibl                    'Then do low nibble
ShowNibD:SerTxd("$")                      'Alternate entry
SHexNibl:bTemp = bByteToShow And mskLoNibble
Sh4bits: bTemp = bTemp + "0"              'Convert to ASCII
         If bTemp> "9" Then               'Separate out and
            bTemp = bTemp + 7             ' correct over-
         EndIf                            ' decimal part.
         SerTxd(bTemp)                    'Display it
         Return
 

inglewoodpete

Senior Member
Stage 2: Logging encoder shaft direction to the Programming Editor's Terminal, while using LEDs to supplement this. Note that you may need to change the cClockwise and cCounterClockwise constants to match the output from your specific encoder wiring.
Rich (BB code):
'Simple Rotary Encoder Demonstration Programme
'Written by inglewoodpete
'
'ymbol Version = 1 '0.01 23-May-2018  136 bytes Initial development for 2-bit rotary encoder
Symbol Version = 2 '0.02 10-Jun-2018  211 bytes Add direction validation and indication using LEDs
'
Symbol Major = 0           'Version revision for software development
#PICAXE 08M2
#Terminal 38400            'SerTxd at 32MHz
#No_Data                   'Comment this statement out if EEPROM data is to be initialised
'
' **** Hardware Assignments - 6 I/O Legs + 2 x power (Prefix i for nput, o for output)
'                                 Leg
'      Gnd                       ' 8 (0v power)
'ymbol oSerData         = C.0    ' 7 [SerOut] Serial Output for debugging
Symbol oYellowLED       = C.1    ' 6 Yellow LED - Indicates clockwise
Symbol oGreenLED        = C.2    ' 5 Green LED  - Indicates counterclockwise
'Symbol oPWMControl      = C.2   ' 5 PWM Output
Symbol iEncoder0        = pinC.3 ' 4 Digital input - One bit of encoder
Symbol iEncoder1        = pinC.4 ' 3 Digital input - One bit of encoder
'                       = C.5    ' 2 [SerIn] (or I/P only)
'      Vcc                       ' 1 (+ve power) Safe range is 2.3 - 5.5 volts
'
' **** Variables b0 to b27 or w0 to w13 (Prefix b for byte; w for word)
'
Symbol bByteToShow      = b1     'w0 Used to display hexadecimal value (for debugging)
Symbol bCurrentState    = b2     'w1 Current copy of encoder output
Symbol bPreviousState   = b3     'w1 Previous copy of encoder output
Symbol bLatestTransition= b4     'w2 4-bit value, from Previous and Current states
Symbol bPrevTransition  = b5     'w2 Copy of previous transition, in upper 4-bits
Symbol bDirection       = b6     'w3 Validated indication of rotation direction
Symbol bTemp            = b7     'w3 Used within ShowHex routine
'
' **** Constants    (Prefix c, msk)
'
'Constants for timers etc
Symbol cStartupPause    = 4000      'Allows debugging terminal to open if required
Symbol cClockwise       = $7E
Symbol cCounterClockwise= $42
'Masks etc
Symbol mskEncoderIn     = %00011000 'Bits 3 and 4 (ANDed to exclude all pins bar 3 & 4)
Symbol mskLoNibble      = %00001111
'
' ---------------------------------------------------------------------
Init: SetFreq M32
      PullUp mskEncoderIn
      '
      Pause cStartupPause              'Allows logging terminal to open after download
      '
      SerTxd (CR, LF, "Rotary Encoder Demo Ver:", #Major, ".", #Version, CR, LF)
      '
      bPreviousState = PinsC And mskEncoderIn
      Enabletime
      '
      ' ****  M a i n    L o o p  ****
      '
      Do
         bCurrentState = PinsC And mskEncoderIn
         If bCurrentState <> bPreviousState Then      'Has encoder been turned
            ' First, get transition value into lower 4-bits indicating direction (CW or ACW)
            ' [* 4]Move previous XY bits left 2 bit-positions; [Or]merge current XY bits
            ' [/ 8]Move all 4 bits right by 3 bit positions
            ' This results in previous, current encoder bits appearing in lower nibble
            bLatestTransition = bPreviousState * 4 Or bCurrentState / 8
            '                                              
            bDirection = bPrevTransition Or bLatestTransition
            bByteToShow = bDirection
            GoSub ShowHex                             'Show byte value as hex
            bByteToShow = bLatestTransition
            Select Case bDirection
            Case cClockwise
               High oYellowLED
               Low oGreenLED
               Time = 0
               SerTxd(" CW ")
               GoSub SHexNibl                         'Show 4-bit latest transition value as hex
            Case cCounterClockwise
               High oGreenLED
               Low oYellowLED
               Time = 0
               SerTxd(" CCW ")
               GoSub SHexNibl                         'Show 4-bit latest transition value as hex
            End Select
            bPreviousState = bCurrentState            'Save for next comparison in bits 2,3
            bPrevTransition = bLatestTransition * 16  'Copy transition into upper 4-bits
            SerTxd(CR, LF)
         EndIf
         If Time = 2 Then     'Turn LED off after 0.5 second
            Low oGreenLED
            Low oYellowLED
         EndIf
      Loop
'
' **** Subroutines *****************************
'
' ---- ShowHex: Send Hexadecimal value to PE Terminal -----------------------------------------
'
' Due to forum post size limitations, please download the ShowHex routine from the previous post
 
Last edited:

inglewoodpete

Senior Member
Stage 3 Part 1: Light dimmer demo. - definitions. In the final design, the PWM output connects directly to the gate of an IRF540 MOSFET, which operates as a low-side switch driving the 12v 10W LED light strip. The PWM speed is kept low (~488Hz) to allow the PICAXE to fully charge the MOSFET's gate capacitance. The maximum (10-bit) PWM resolution is used to maximise the range of brightness, from 0% (off), through ~0.1% to 100% in 16 steps. Due to the human eye's logarythmic response to light levels, the PWM drive steps roughly follow a logarythmic curve. This gives a linear appearance to the light level steps.

The code is provided in two parts due to the size of colour-enhanced code vs. this forum
Rich (BB code):
'Simple Rotary Encoder Demonstration Programme
'Written by inglewoodpete
'
'ymbol Version = 1 '0.01 23-May-2018  136 bytes Initial development for 2-bit rotary encoder
'ymbol Version = 2 '0.02 10-Jun-2018  211 bytes Add direction validation and indication using LEDs
Symbol Version = 3 '0.03 12-Oct-2018  197 bytes Light dimmer demonstration
'
Symbol Major = 0           'Version revision for software development
#PICAXE 08M2
#Terminal 38400            'SerTxd at 32MHz
'#No_Data                   'Comment this statement out if EEPROM data is to be initialised
'
' **** Hardware Assignments - 6 I/O Legs + 2 x power (Prefix i for nput, o for output)
'                                 Leg
'      Gnd                       ' 8 (0v power)
'ymbol oSerData         = C.0    ' 7 [SerOut] Serial Output for debugging
'ymbol                  = C.1    ' 6 
Symbol oPWMControl      = C.2    ' 5 PWM Output
Symbol iEncoder0        = pinC.3 ' 4 Digital input - One bit of encoder
Symbol iEncoder1        = pinC.4 ' 3 Digital input - One bit of encoder
'                       = C.5    ' 2 [SerIn] (or I/P only)
'      Vcc                       ' 1 (+ve power) Safe range is 2.3 - 5.5 volts
'
' **** Variables b0 to b27 or w0 to w13 (Prefix b for byte; w for word)
'
Symbol bCurrentState    = b2     'w1 Current copy of encoder output
Symbol bPreviousState   = b3     'w1 Previous copy of encoder output
Symbol bLatestTransition= b4     'w2 4-bit value, from Previous and Current states
Symbol bPrevTransition  = b5     'w2 Copy of previous transition, in upper 4-bits
Symbol bDirection       = b6     'w3 Validated indication of rotation direction
Symbol bLightStep       = b7     'w3     Brightness step value (0 - 15)
Symbol bEEPROMPtr       = b8     'w4     
'
Symbol bBrightness.Lo   = b24    'w12    Low byte - Logarithmic brightness value for PWM
Symbol bBrightness.Hi   = b25    'w12    High byte - Logarithmic brightness value for PWM
Symbol wBrightness      = w12    'b24/25 Logarithmic brightness value for PWM
'
' **** Constants    (Prefix c, msk)
'
'Constants for timers etc
Symbol cStartupPause    = 4000      'Allows debugging terminal to open if required
Symbol cClockwise       = $7E
Symbol cCounterClockwise= $42
'Masks etc
Symbol mskEncoderIn     = %00011000 'Bits 3 and 4 (ANDed to exclude all pins except 3 & 4)
'
' **** EEPROM Data    0 to 255 bytes (Pointers: prefix e)
'
'Reference for light levels (The human eye has a logarythmic response to light levels:
' This reference table gives an apparently linear progression from 0, 0.1% to 100% in 16 steps)
Symbol eLightTable      = 0
'                    A/B=DutyHi/DutyLo
EEPROM eLightTable, (0,0,   0,1,   0,2,   0,5,  0,12,  0,25, 0,45, 0,70)
EEPROM              (0,100, 0,165, 0,240, 1,81, 1,197, 2,88, 3,32, 3,255)
'
' ---------------------------------------------------------------------
 

inglewoodpete

Senior Member
Stage 3 Part 2: Light dimmer demo - executable code
Rich (BB code):
Init: SetFreq M32
      PullUp mskEncoderIn
      '  Turn light on fully at startup
      For bLightStep = 0 To 15
         bEEPROMPtr = bLightStep * 2
         Read bEEPROMPtr, bBrightness.Hi, bBrightness.Lo 'Read EEPROM preset steps (logarithmic curve)
         PWMOut PWMDiv64, oPWMControl, 255, wBrightness
         Pause 250                 
      Next bLightStep
      bLightStep = 15
      '
      Pause cStartupPause              'Allows logging terminal to open after download
      '
      SerTxd (CR, LF, "Rotary Encoder Dimmer Demo Ver:", #Major, ".", #Version, CR, LF)
      '
      bPreviousState = PinsC And mskEncoderIn
      '
      ' ****  M a i n    L o o p  ****
      '
      Do
         bCurrentState = PinsC And mskEncoderIn
         If bCurrentState <> bPreviousState Then      'Has encoder been turned?
            ' First, get transition value into lower 4-bits indicating direction (CW or ACW)
            ' [* 4]Move previous XY bits left 2 bit-positions; [Or]merge current XY bits
            ' [/ 8]Move all 4 bits right by 3 bit positions
            ' This results in previous, current encoder bits appearing in lower nibble
            bLatestTransition = bPreviousState * 4 Or bCurrentState / 8
            '                            
            bDirection = bPrevTransition Or bLatestTransition
            Select Case bDirection
            Case cClockwise
               bLightStep = bLightStep Max 14 + 1  'Step brightness up (Maximum value is 15)
            Case cCounterClockwise
               bLightStep = bLightStep Min 1 - 1   'Step brightness down (Minimum value is 0)
            End Select
            bEEPROMPtr = bLightStep * 2
            Read bEEPROMPtr, bBrightness.Hi, bBrightness.Lo 'Read EEPROM preset step (logarithmic curve)
            SerTxd(#bLightStep, ": ", #wBrightness, CR, LF)
            PWMOut PWMDiv64, oPWMControl, 255, wBrightness  '488Hz
            bPreviousState = bCurrentState            'Save for next comparison in bits 0,1
            bPrevTransition = bLatestTransition * 16  'Copy transition into upper 4-bits
         EndIf
      Loop
 

hippy

Technical Support
Staff member
There is noting wrong with storing the bits in a nibble so you have previous and current.

Another alternative is to have a Finite State Machine and move through the various states as the inputs change -

Code:
#Picaxe 14M2

Symbol PIN_A = pinC.1
Symbol PIN_B = pinC.0

b0 = PIN_A * 2 + PIN_B       ; AB
On b0 GOTO S_00, S_01, S_10, S_11

; AB                         Was AB -> AB Now
S_00: Do
        If PIN_A = 1 Then S_10 ; 00 -> 10 +
        If PIN_B = 1 Then S_01 ; 00 -> 01 -
      Loop

S_01: Do
        If PIN_A = 1 Then S_11 ; 01 -> 11 -
        If PIN_B = 0 Then S_00 ; 01 -> 00 +
      Loop

S_10: Do
        If PIN_A = 0 Then S_00 ; 10 -> 00 -
        If PIN_B = 1 Then S_11 ; 10 -> 11 +
      Loop

S_11: Do
        If PIN_A = 0 Then S_01 ; 11 -> 01 +
        If PIN_B = 0 Then S_10 ; 11 -> 10 -
      Loop
That just changes state and we would like to adjust something as we go. One way to do that is with some #DEFINE trickery -

Code:
#Picaxe 14M2

Symbol PIN_A = pinC.1
Symbol PIN_B = pinC.0

#Define Up(n) : w0 = w0 + 1 : Gosub Update : Goto n : End If
#Define Dn(n) : w0 = w0 - 1 : Gosub Update : Goto n : End If

b0 = PIN_A * 2 + PIN_B       ; AB
On b0 GOTO S_00, S_01, S_10, S_11

; AB                             Was AB -> AB Now
S_00: Do
        If PIN_A = 1 Then Up(S_10) ; 00 -> 10 +
        If PIN_B = 1 Then Dn(S_01) ; 00 -> 01 -
      Loop

S_01: Do
        If PIN_A = 1 Then Dn(S_11) ; 01 -> 11 -
        If PIN_B = 0 Then Up(S_00) ; 01 -> 00 +
      Loop

S_10: Do
        If PIN_A = 0 Then Dn(S_00) ; 10 -> 00 -
        If PIN_B = 1 Then Up(S_11) ; 10 -> 11 +
      Loop

S_11: Do
        If PIN_A = 0 Then Up(S_01) ; 11 -> 01 +
        If PIN_B = 0 Then Dn(S_10) ; 11 -> 10 -
      Loop

Update:
  Sertxd( #w0," " )
  Return
If that's run in the simulator, clicking C.1, C.0, C.1, C.0 increments, in reverse, C.0, C.1, C.0, C.1 decrements.
 

lbenson

Senior Member
I did similar for our trailer with a pot. What is the advantage of using a rotary encoder over a pot?
 

inglewoodpete

Senior Member
I did similar for our trailer with a pot. What is the advantage of using a rotary encoder over a pot?
Perhaps the challenge here is the terminology used in different countries. I believe a "trailer" in the US is what is known as a caravan in other parts of the world. Although there are several basic designs of a camper trailer in Australia, they are intended for off-road as well as road use, folding into a quite compact and sturdy unit for travelling. When opened at a campsite, you step up onto the foot of the bed, with the 'head' end of the bed being furthest away from the entrance to the opened camper tent. Here is a link to a magazine review of the camper trailer in question. Edit: When checked in Dec-2021, the review article appears to have been removed.

In a nutshell, a rotary encoder does not have a 'memory' like a pot.

Two controls for the light make it more user friendly. At ground or floor level, near the entrance to the tent, we find it best to be able to turn the light fully on or off. Once in bed, the second control (rotary encoder) allows the light to be turned down to off and, in particular, to be turn on just slightly (0.1% brightness) at night so as not to disturb the sleeping partner.

In this situation the light would usually be dimmed at night. In the light of morning, if the dimmer at the head of the bed was a pot, the light could be left turned down. If a pot was used and, the following evening when the light was turned on using the switch near the door, the light would not come on due to the pot still being turned right down. This would turn need someone to climb onto the bed in the dark and turn the pot up to restore a suitable level of light. The rotary encoder does not have this constraint, since the light level is controlled by the rotation of the encoder, rather than the position of the pot.

Rather a long-winded reply but I hope it makes sense.
 
Last edited:

inglewoodpete

Senior Member
There is nothing wrong with storing the bits in a nibble so you have previous and current.

Another alternative is to have a Finite State Machine and move through the various states as the inputs change -
(code removed by IP for brevity)

If that's run in the simulator, clicking C.1, C.0, C.1, C.0 increments, in reverse, C.0, C.1, C.0, C.1 decrements.
Yes, my initial development was with similar coding. While I was not using macros or define statements to condense blocks of code, it was pretty bulky and time consuming to code and test. Compare your example above with my 4 lines of code, below. Admittedly, my code model works most efficiently when two adjacent pins are used.
Rich (BB code):
         bCurrentState = PinsC And mskEncoderIn
         If bCurrentState <> bPreviousState Then      'Has encoder been turned?
            ' First, get transition value into lower 4-bits indicating direction (CW or ACW)
            ' [* 4]Move previous XY bits left 2 bit-positions; [Or]merge current XY bits
            ' [/ 8]Move all 4 bits right by 3 bit positions
            ' This results in previous, current encoder bits appearing in lower nibble
            bLatestTransition = bPreviousState * 4 Or bCurrentState / 8
            '                            
            bDirection = bPrevTransition Or bLatestTransition
The other problem I encountered during early development of a similar state-based solution (Eg. b0 = PIN_A * 2 + PIN_B) was that of reading the two input pins in sequence, with could occur up to several hundred microseconds apart. With the encoder that I used, each notch of the rotor's detent resulted in four codes being created or output. Sequential reading of the two pins caused too many false readings to be reliable. Using "Rx = pinsC & mask" appeared to eliminate false readings.
 

lbenson

Senior Member
Ok--understood. We have a Palomino 8' pop-up trailer (hard crank-up top with fold-out tent wings) in Nova Scotia, and a 24-foot hard-shell Jayco "caravan" in Florida. Because we need to get away from our get-aways.

I like the solar on the boat racks on yours--with all the needed controls built-in. What spec for the panel?
 

hippy

Technical Support
Staff member
With the encoder that I used, each notch of the rotor's detent resulted in four codes being created or output.
I guess that makes sense if it's not a pure quadrature encoder ...
Code:
     _____       _____       _____       _____
A __|     |_____|     |_____|     |_____|     |_____
        _____       _____       _____       _____
B _____|     |_____|     |_____|     |_____|     |__
But gives a quadrature-like output at each indent -
Code:
     _           _           _           _
A __| |_________| |_________| |_________| |_________
      _           _           _           _
B ___| |_________| |_________| |_________| |________
 

inglewoodpete

Senior Member
I guess that makes sense if it's not a pure quadrature encoder ...
Code:
     _____       _____       _____       _____
A __|     |_____|     |_____|     |_____|     |_____
        _____       _____       _____       _____
B _____|     |_____|     |_____|     |_____|     |__
But gives a quadrature-like output at each indent -
Code:
     _           _           _           _
A __| |_________| |_________| |_________| |_________
      _           _           _           _
B ___| |_________| |_________| |_________| |________
It is a true quadrature encoder. Perhaps a misunderstanding of what I was trying to say: the larger the duration between each pin being read, the greater the possibility of a misread. compare b0 = PIN_A * 2 + PIN_B vs. b0 = pinsC
 

inglewoodpete

Senior Member
Ok--understood. We have a Palomino 8' pop-up trailer (hard crank-up top with fold-out tent wings) in Nova Scotia, and a 24-foot hard-shell Jayco "caravan" in Florida. Because we need to get away from our get-aways.

I like the solar on the boat racks on yours--with all the needed controls built-in. What spec for the panel?
Two vans, both on the road (not at once, obviously)?

The panel on the boat rack is 120W. We also parallel a folding 160W panel. One or both of these feed into a 25amp MPPT charger.
 

eclectic

Moderator
Australia - Canada - GB

All of this information interchange in less than 12 hours!

Amazing!

Just my own personal opinion. e
 

lbenson

Senior Member
Two vans, both on the road (not at once, obviously)?

The panel on the boat rack is 120W. We also parallel a folding 160W panel. One or both of these feed into a 25amp MPPT charger.
Right now, neither on the road--parked in driveways. During winter of 2014-15 we poked around Florida for 4 months in the Jayco. Now we snowbird, so have a home base in either location. (Glad this week we are far from Mexico Beach on the FL panhandle.)

"Parallel"--so you feed 24V into the controller/charger? (CONFUSION HERE--parallel means same 12 volts, series would mean 24--right?)

Australia - Canada - GB
Have always loved being able to post a question before bedtime with a reasonable expectation of getting an answer from OZ/NZ/GB after breakfast.
 

inglewoodpete

Senior Member
I guess that makes sense if it's not a pure quadrature encoder ...
Code:
     _____       _____       _____       _____
A __|     |_____|     |_____|     |_____|     |_____
        _____       _____       _____       _____
B _____|     |_____|     |_____|     |_____|     |__
I spent some time today with the AXE092 (with 08M2 chip) to confirm my earlier assertions from last night, my local time (post #12).

Firstly, I have attached a trace from my BitScope logic analyser. The white and brown traces are pinC.4 and pinC.3 respectively. Even with a 30mm diameter knob, the available sampling period can be as little as 2.66mS. The red trace is an indication of the looping speed of the PICAXE 08M2 running at 32MHz. The loop execution time is about 520uS at best and expands to 1.6mS depending on the amount of code that is executed when an encoder step is recognised (this is with all logging commands disabled).
RotaryEncoder.JPG

To compare the two methods of reading the input pins, I used a pair of Toggle commands, one immediately after the other. The resulting pulse was 42uS, which is the overhead that adding these commands to the looping 08M2 creates. The actual time could vary slightly according to where the tokens are packed in the downloaded code.

Adding the simplest Fetch-and-Save command produced a 105uS pulse, indicating a 105 - 42 = 63uS execution time:
Code:
Toggle oYellowLED
b0 = b1
Toggle oYellowLED
Replacing the Fetch-and-Save command with reading the whole port also produced a 105uS pulse or 63uS execution time. I don't know how the firmware actually performs this but, due to the unchanged execution speed, I assume that the whole port is read in one machine-code instruction. This suggests there is no time difference between reading the two pin states.
Code:
Toggle oYellowLED
bCurrentState = PinsC
Toggle oYellowLED
Finally, for comparison, the Fetch-Multiply-Fetch&Add-Save was timed and produced a 209uS pulse (execution time of 209 - 42 = 167uS):
Code:
Toggle oYellowLED
b0 = iEncoder1 * 2 + iEncoder0
Toggle oYellowLED
This suggests that an elapsed time of around 120uS would occur between the sequential reads of the encoder pins. From this I inferred that the delay was introducing sporadic errors in reading/decoding the encoder's output. The longest code loop execution time of around 1.6mS ensures that each encoder combination (minimum period 2.6mS) can be read at least once. This was what prompted me to use Whole-of-Port read method.
 

inglewoodpete

Senior Member
Right now, neither on the road--parked in driveways. During winter of 2014-15 we poked around Florida for 4 months in the Jayco. Now we snowbird, so have a home base in either location. (Glad this week we are far from Mexico Beach on the FL panhandle.)
I, too, am glad to be a long way from Mexico Beach FL.

On your migratory habits, by chance I met a Canadian through the PICAXE forum about 10 years ago. From Toronto, he had had enough of the winter conditions and married an Australian woman. He'd rather endure the heat of an Australian summer than the cold of a Canadian winter. Anyway, we formed a successful business partnership about three years ago.
"Parallel"--so you feed 24V into the controller/charger? (CONFUSION HERE--parallel means same 12 volts, series would mean 24--right?)
I think you've answered this one yourself. I feed the two panels in parallel, adding the currents. The voltages stay the same, at a mutual MPPT point of about 16v.
 

lbenson

Senior Member
... rather endure the heat of an Australian summer than the cold of a Canadian winter
Toronto is a good bit colder in winter than coastal Nova Scotia. I was raised in Virginia, so I can say I'm experienced with heat and humidity--but not acclimated to it. I married a Canadian woman, and try to get the best of both--7 months in Nova Scotia (including the season of perfection--July to mid-September), and 5 months in Crystal River, Florida, where there are only a few nights below freezing. We try to leave CR before the temperature sticks at 30C and above.
 

Hemi345

Senior Member
In a nutshell, a rotary encoder does not have a 'memory' like a pot.

Two controls for the light make it more user friendly. At ground or floor level, near the entrance to the tent, we find it best to be able to turn the light fully on or off. Once in bed, the second control (rotary encoder) allows the light to be turned down to off and, in particular, to be turn on just slightly (0.1% brightness) at night so as not to disturb the sleeping partner.

In this situation the light would usually be dimmed at night. In the light of morning, if the dimmer at the head of the bed was a pot, the light could be left turned down. If a pot was used and, the following evening when the light was turned on using the switch near the door, the light would not come on due to the pot still being turned right down. This would turn need someone to climb onto the bed in the dark and turn the pot up to restore a suitable level of light. The rotary encoder does not have this constraint, since the light level is controlled by the rotation of the encoder, rather than the position of the pot.
I guess I still question the need to use a rotary encoder instead of a pot. If the switch near the door controls power to the PICAXE, then the following morning you would shut off the light/PICAXE by flipping the switch off. Then that evening, flipping the switch on, you could have the PICAXE ignore the pot's current position at start up and turn the light on full brightness. Only when the pot position is changed, would you then interpret the value of it and set the level of brightness.

Not trying to take anything away from your project, I'm just offering another perspective. Found your project searching the forum for tips and tricks with rotary encoders for my project so thank you for posting in so much detail. (y)
 

mrm

Well-known member
Background
About a year ago, my wife and I bought a camper-trailer. It was fitted with a 10W LED light strip in the main tent which is great when you want a bright light. The light switch was mounted on a side pole at the foot of the bed: not the best location when you're in bed. When turned on in the middle of the night, it was pretty bright and bound to wake the sleeping partner!

We decided that a dimmer was called for, controlled from the head of the bed but switchable from the foot of the bed when stepping into the tent at night.

The solution
I decided on a small diecast enclosure, with master switch, mounted in the original switch position at the foot of the bed near the tent's doorway. An 08M2 PICAXE would drive an N-channel MOSFET with PWM to control the light intensity. A rotary encoder, mounted at the centre of the head of the bed, would control the dimming. The master switch would provide power to the PICAXE, which starts the PWM at 100% at bootup. The camper has a 12v 200Ah battery bank, so when the encoder turns the light down to 0% (off), the standby current to the PICAXE is insignificant.

Algorythm
One problem when using a PICAXE to read two input pins before making decisions on encoder movement and direction is the amount of time for individual commands to be executed. The microcontroller needs to read the inputs, compare the encoder position value with the previous one and make decisions on the result. If the PIC should miss a 2-bit position code, this upsets the sequence, and consequently the direction of the encoder cannot be determined. By reading the entire port, then masking out any unwanted bits, the instantaneous encoder position can be read reliably. Of course, running the PICAXE at its maximum speed helps the cause, too.

So, in a nutshell, the 08M2 reads and saves the two encoder bits and places them alongside the two previously recorded bits. All stored in one half of a byte variable (nibble). For added integrity checking, the previous nibble is then held in the other nibble. This way, the byte can be compared with a known (constant) value to determine the direction that the encoder shaft has been turned.

Development
Code was developed on an AXE092 Schools Experimenter development board with an 08M2. Initially, I tried to use the encoder without a knob on the shaft. This resulted in the two-bit codes changing too quickly for the PICAXE to track reliably. Things became much more reliable after I fitted a 23mm knob: far more realistic than the bare shaft, anyway.

For those of you who want to replicate these experiments, you can breadboard the circuit following the I/O pin allocations described in the code. The rotary encoder was a cheap one I bought from Altronics but there are many suitable types available. Basically, the encoder has three pins: the centre one was common to both phase switches and the outer two were the switched phases. I used the PICAXE's internal weak pullup resistors via the PullUp command, with the encoder's phase switches pulling the two encoded lines low as its shaft is rotated.

I have broken the development of the software into three stages, with three progressive working pieces of code. Due to forum posting limitations, I have posted these programs progressively, below.

Do you have a circuit diagram available for this project as the principle is applicable to other uses and it would simplify replication of your good work?
 

inglewoodpete

Senior Member
Do you have a circuit diagram available for this project as the principle is applicable to other uses and it would simplify replication of your good work?
The circuit is simplicity itself. I have modified the diagram to match the code example above. Note that the LED light was a 12v 10W flexible lighting strip with internal current limiting. Other 12V LED lamps would work too but heatsinking for the MOSFET must be taken into consideration. I just bolted the MOSFET (with insulating hardware) to the metal case of the controller which doubled as the heatsink.
Rotary Encoder Dimmer.JPG Edit: Altered circuit diagram for rotary encoder to use the PICAXE's internal pull-up resistors as per software example.
 
Last edited:
  • Like
Reactions: mrm
Top