Rotary encoder

rajdarge
Thu Nov 05, 2015 10:27 am
Anyone managed to get one of the published libraries or any local code working with a MINI STM32 and this rotary encoder?
http://henrysbench.capnfatz.com/henrys- … er-manual/

I can see the digital pinouts change etc, not sure if the description of the pinouts is accurate.
Currently I’m using D17(A), D18(B) and D19 (buttton).
The board has inbuilt pullups and I’m using 3.3v .

Is it a hardware problem or a software?


madias
Thu Nov 05, 2015 2:07 pm
No. I didn’t, but why do you wont use my code?
http://www.stm32duino.com/viewtopic.php?f=18&t=15

regards
Matthias


bianchifan
Thu Nov 05, 2015 5:13 pm
There are a couple of different rotaries on the market, with 12 to 36 detents and 9 to 36 pulses.
Sotimes detents equals pulses, sometimes not.
Some types will snap on phase “B” so you cannot assess.

Some time ago I ordered a couple of cheap chinese EC11, absolutely crap!
Sometimes they generate one pulse, sometimes two, sometimes not.

ALPS encs are much much more expensive..but they are preserving heart attacks ;)


rajdarge
Fri Nov 06, 2015 10:23 am
madias wrote:No. I didn’t, but why do you wont use my code?
http://www.stm32duino.com/viewtopic.php?f=18&t=15

regards
Matthias


rajdarge
Fri Nov 06, 2015 10:38 am
The encoder in question works find on my Arduino Mega Adk
(I used the the click encoder library as it has debounce, double press and long press).
https://github.com/0xPIT/encoder/tree/arduino
I move the pin A and Pin B output to 18 and 19 (pb 4, pb3) because they are meant to be 5v tolerant pins.
The board has its own 10K pullups.
but on 3.3 and 5v I get no reading at all, with any library, or code snippet.
on the Mega it works perfectly.

RogerClark
Fri Nov 06, 2015 7:42 pm
5V tolerant is not the same as accepting 5V logic levels

Its possible that the Logic low from that device is only something like 1.8V which would be a logic low on a 5V system buy still a logic high on a 3.3 V system

In this case, I doubt this is the problem, but as these devices are normally mechanical, I dont know why you dont run it from 3.3V, or alternatively use a resistor divider network to correctly change the 5V logic levels to 3.3V

Use of 5V peripherals is in my experience no longer the norm for most modern MCUs.


ahull
Fri Nov 06, 2015 7:54 pm
RogerClark wrote:5V tolerant is not the same as accepting 5V logic levels
Its possible that the Logic low from that device is only something like 1.8V which would be a logic low on a 5V system buy still a logic high on a 3.3 V system

RogerClark
Fri Nov 06, 2015 9:15 pm
I wasn’t sure if the OP had any test equipment.

They could use the Analogue input, but I’m not sure if its 5V tollerent (probably not) so they’d need a voltage divider


ahull
Sat Nov 07, 2015 1:04 am
RogerClark wrote:I wasn’t sure if the OP had any test equipment.

They could use the Analogue input, but I’m not sure if its 5V tollerent (probably not) so they’d need a voltage divider


ahull
Sat Nov 07, 2015 1:06 am
ahull wrote:RogerClark wrote:I wasn’t sure if the OP had any test equipment.

They could use the Analogue input, but I’m not sure if its 5V tollerent (probably not) so they’d need a voltage divider


simonf
Sat Nov 07, 2015 9:02 pm
i’ve used a lot of those cheep rotary encoders as volume controls for amps. I wrote a little library which irons out the bounce.


struct EncoderSetings {
uint8_t PinA;
uint8_t PinB;
uint8_t PinC;
uint8_t Min;
uint8_t Max;
uint8_t GreyCode;
uint8_t mutePin;
volatile uint8_t Val;
bool GoingUp;
bool GoingDown;
volatile bool IsDirty;
bool muted;

} ;

void ScanEncoder(EncoderSetings &ThsEnc)
{
uint8 OldGreyCode;
//hostSerial.println("Reading the encoder... press a key to abort.");
if (digitalRead(ThsEnc.mutePin)==LOW && !(ThsEnc.muted)){
// mute pressed
ThsEnc.muted= true;
ThsEnc.IsDirty = true;
RotaryIsDirty = true;
}
{
OldGreyCode = ThsEnc.GreyCode;
ReadGreyCode(ThsEnc);
if ( ThsEnc.GreyCode != OldGreyCode)
{

// "Forward" is shown by the position going from (0 to 1) or (1 to 3)
// or (3 to 2) or (2 to 0). Anything else indicates that the user is
// turning the device the other way. Remember: this is Gray code, not
// binary.
if (OldGreyCode == 3) { // Starting Rotation
ThsEnc.GoingUp = ((ThsEnc.GreyCode == 1));
ThsEnc.GoingDown = ((ThsEnc.GreyCode == 2));
}
else
{
if (ThsEnc.GreyCode == 3) { // Ending Rotation
if ((ThsEnc.GoingUp) && (OldGreyCode == 2)) {
if (ThsEnc.Val < ThsEnc.Max) {
ThsEnc.Val++;
ThsEnc.IsDirty = true;
RotaryIsDirty = true;
}
}
if ((ThsEnc.GoingDown) && (OldGreyCode == 1)) {
if (ThsEnc.Val > ThsEnc.Min) {
ThsEnc.Val--;
ThsEnc.IsDirty = true;
RotaryIsDirty = true;
}
}
ThsEnc.GoingDown = false; // Back In the Parked Position
ThsEnc.GoingUp = false; // Back In the Parked Position
}
}
// debounce the pot
// if ((ThsEnc.GreyCode!=OldGreyCode) && (OldGreyCode=3)) _delay_ms(10);
if (ThsEnc.muted ) {
ThsEnc.muted=false;
ThsEnc.IsDirty = true;
RotaryIsDirty = true;
ThsEnc.GoingDown = false; // Back In the Parked Position
ThsEnc.GoingUp = false; // Back In the Parked Position
}
}
}
}

void SetupEncoder( uint8 Index, uint8 PinA , uint8 PinB, uint8 PinC, uint8 mutepin, uint8 Min, uint8 Max, uint8 Val )

{

Rotary[Index].Min = Min;
Rotary[Index].Max = Max;
Rotary[Index].PinA = PinA ;
Rotary[Index].PinB = PinB;
Rotary[Index].PinC = PinC;
Rotary[Index].mutePin = mutepin ;
Rotary[Index].Val = Val ;
Rotary[Index].GoingDown = false ;
Rotary[Index].GoingUp = false ;
Rotary[Index].IsDirty = false ;
Rotary[Index].muted = false ;
ResetPins(Rotary[Index]);
ReadGreyCode(Rotary[Index]);

}

void ResetPins(EncoderSetings &ThisEncoder)
{
// Rotary encoder input lines
// Configure as input, turn on pullup resistors
pinMode(ThisEncoder.PinA , INPUT_PULLUP );
pinMode(ThisEncoder.PinB, INPUT_PULLUP );
pinMode(ThisEncoder.mutePin , INPUT_PULLUP );
pinMode(ThisEncoder.PinC, OUTPUT );
digitalWrite(ThisEncoder.PinC, LOW);
}

void ReadGreyCode(EncoderSetings &MyEncoder)
{
MyEncoder.GreyCode = ((digitalRead(MyEncoder.PinB) * 2) + digitalRead(MyEncoder.PinA));
}

bool RotaryHasChanged(uint8_t &Ind)
{
return Rotary[Ind].IsDirty;
}

uint8_t ReadRotaryValue(uint8_t &Ind)
{
Rotary[Ind].IsDirty = false;
if (Rotary[Ind].muted) return 0;
else return Rotary[Ind].Val ;
}

void ScanAllEncoders() {

for (uint8_t index = 0; index < EncoderCount; index++ ) {
ScanEncoder(Rotary[index]);
}
}

bool ARotaryHasChanged()
{
if (RotaryIsDirty)
{
RotaryIsDirty = false;
return true;
}
else return false;
}


RogerClark
Wed Jun 27, 2018 11:59 pm
@simonf

I tried to use the code you posted but it doesnt compile.

I suspect you have some globals which you didnt include in the post

If possible could you post the missing code ?

Thanks

Roger


dannyf
Thu Jun 28, 2018 11:23 am
I wrote a little library which irons out the bounce.

that’s a lot of code for driving a rotary encoder.

mine is here: https://github.com/dannyf00/My-MCU-Libr … encoder1.c

it uses a state machine implemented via an array. incredibly resistant to bounces – i often use it without any debouncing hardware.

compiles to pretty much any mcu.


stevestrong
Thu Jun 28, 2018 11:29 am
That code from @dannyf is a really simple and therefore efficient code.
The only question is in which time interval is called, under which circumstances.
May it suffice to call that in an ISR attached to systick (e.g. each 1 ms, 1kHz frequency)?

fredbox
Thu Jun 28, 2018 7:26 pm
mine is here: https://github.com/dannyf00/My-MCU-Libr … encoder1.c
//determine increment / decrement of the encoder
unsigned char encoder1_read(void) {
static const signed char encoder_states[]={0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
...
return encoder_states[ABs]; //return the relative value (+1 = clockwise, 0, -1 = counterclockwise)

dannyf
Thu Jun 28, 2018 8:32 pm
it depends. the code allows the return of either relative (+/-1, or 0) or absolute – part of the code is commented off.

depending on your needs, you can turn on or off either.


RogerClark
Thu Jun 28, 2018 9:58 pm
Thanks guys, I will try @dannf’s code

Ideally I was hoping to avoid needing to use an RC network for hardware denouncing.


RogerClark
Fri Jul 06, 2018 10:53 am
Finally got around to looking at the code, by dannyf, but it doesn’t appear to be for STM32 ??
It looks like its for PIC32

zoomx
Fri Jul 06, 2018 1:37 pm
Yes, I believe too, but maybe…

#define ENC1_PORT PORTB //inputs on portb
#define ENC1_DDR TRISB
#define ENC1_A (1<<10) //rotary A on p.2
#define ENC1_B (1<<11) //rotary B on p.5


dannyf
Fri Jul 06, 2018 2:23 pm
It looks like its for PIC32

It was originally written for some AVR, then PIC, and then PIC24. but the code is platform agnostic.

for example if you wish to implement in the STM32duino environment, just use the appropriate gpio.h/.c files. or hard-map the gpio macros like this:

#define IO_IN(ddr, pin) pinMode(pin, INPUT) //or with pull-up
#define IO_GET(port, pin) digitalRead(pin) //read a digital pin


dannyf
Fri Jul 06, 2018 3:08 pm
I should have probably expanded a little.

the two lines of code here:

if (ENC1_PORT & ENC1_A) ABs |= 0x02; //set the 1st bit if A is high now;
if (ENC1_PORT & ENC1_B) ABs |= 0x01; //set the 0th bit if B is high;


fredbox
Fri Jul 06, 2018 10:13 pm
Is this rotary encoder code meant to be used with the KY-040 style rotary encoders that change states four times on each click? When I test this code, I get about four counts per click. 360 degrees of rotation changes the count by about 80.// called from systick once per millisecond
void encoder1_read(void)
{
volatile static const signed char encoder_states[]={0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
volatile static unsigned char ABs=0x00; //AB key read out, Previous in the high 2 bits and current in the low two bits;

ABs = (ABs << 2) & 0x0f; //left 2 bits now contain the previous AB key read-out;
if (digitalRead(ENC_CLK)) ABs |= 0x02; //set the 1st bit if A is high now;
if (digitalRead(ENC_DATA)) ABs |= 0x01; //set the 0th bit if B is high;
encoderCount += encoder_states[ABs];
}


fredbox
Sat Jul 07, 2018 2:39 am
It makes sense that the count would change by four:
ABs[0x0D], ABs[0x04], ABs[0x02], ABs[0x0B] all = 1
ABs[0x0E], ABs[0x08], ABs[0x01], ABs[0x07] all = -1

I simplified the code to only check for 0x0D and 0x0E for increment and decrement.
Now I am getting a nice steady 1 count per click with no apparent bounce. No capacitors are needed.// call this from the systick interrupt every millisecond
// modified from the original code by dannyf
// at https://github.com/dannyf00/My-MCU-Libraries---2nd-try/blob/master/encoder1.c
void encoder1_read(void)
{
volatile static uint8_t ABs = 0;
ABs = (ABs << 2) & 0x0f; //left 2 bits now contain the previous AB key read-out;
ABs |= (digitalRead(ENC_CLK) << 1) | digitalRead(ENC_DATA);
switch (ABs)
{
case 0x0d:
encoderCount++;
break;
case 0x0e:
encoderCount--;
break;
}
}


Pito
Sat Jul 07, 2018 8:06 am
@fredbox&dannyf: there are 2 kinds of encoders (see below). Maybe adding the second encoder version (1/4 one) would create “the ultimate encoder solution” :idea:

Encoders.JPG
Encoders.JPG (33.31 KiB) Viewed 357 times

RogerClark
Sat Jul 07, 2018 8:14 am
My encoders don’t ever seem to have either A or B connected to Gnd, in steady state.

i.e if I measure the encoder with the resistance setting on the multi meter, neither A or B is connected to the middle pin.

If I attach an ISR to the falling edge of output A, and read the value of Output B, the direction of rotation is given by either B is connected to Gnd or not

I’m not sure if I’ve wired the encoder up incorrectly as I presumed the middle pin was the common / gnd pin and the outers where Output A and B

Anyway.
I’m using hardware debouncing and its working fine.


Pito
Sat Jul 07, 2018 8:24 am
[RogerClark – Sat Jul 07, 2018 8:14 am] –
My encoders don’t ever seem to have either A or B connected to Gnd, in steady state.

That is the full cycle/detent type (see the picture above, “open in steady state”).

If you connect the 2 pullup resistors (ie 10k) it will read “1” on both in steady state (at the detent, but there are encoders without the detents, they rotate “smoothly”). While increment (CW/CCW) it will pass via “O” and return to “1” in steady state.

The 1/4 type will stay in “1” or “0” at a detent based on the actual position.


RogerClark
Sat Jul 07, 2018 8:57 am
@pito

Thanks

I’m using 10k and 220nF (could probably use 100nF), with resistors both on the charge and discharge side of the encoder.

Seems to work very reliably just with a single ISR and reading the other output.

I briefly looked it doing it without hardware debounceing, but found it was quicker for me to solder the hardware than write and debug the code


dannyf
Sat Jul 07, 2018 12:37 pm
being a state machined based approach, the code is very resistant to bounces. I often run it naked, without any software / hardware debouncer – not recommended. Of the two, a rc-based hardware debouncer is my favorite.

However, be warned that the typical 1r+1c debouncers can trigger reset when coupled with a good switch / encoder. If that happens, put a small serial resistor there to delimit the discharging current.

for software debouncing, the Kuhn debouncer is very good for its simplicity: https://dannyelectronics.wordpress.com/ … debouncer/

as to 1/4 increments vs. 1/1 increments, one of the code pieces linked earlier has such features. But fredfox’s modification is very good as well.


fredbox
Sat Jul 07, 2018 4:26 pm
The encoders I have are mounted on a PCB with pull-up resistors. Search for ‘KY-040 module’ on Ali or Ebay. They are inexpensive (5pcs for $2.50 US).

There is a good discussion of debouncing on Hackaday (including the Kuhn method) at https://hackaday.com/2015/12/09/embed-w … ns-part-i/ and https://hackaday.com/2015/12/10/embed-w … s-part-ii/.

i use the “test_for_press_only” listing at the end of part II to read the momentary push button switch on the encoder and small tactile switches.


Pito
Sat Jul 07, 2018 5:44 pm
FYI – I’ve been using these Bourns ones

https://www.bourns.com/products/encoder … roduct/ECW

they cost me $5 each around y2k. Got them with a detent and without, 1cycle and 1/4, etc. Messed with DDS vfos at that time so I did the investment :roll: (btw I saw these in a TEK dso oscope teardown, so may be they will last long..).

What is an another feature worth of implementing with the ultimate rotary encoder library is the “acceleration” – when you turn the knob faster than you usually do, the increments start to grow exponentially (ie. 2, 4, 8, 16.. based on the angular speed and/or the duration of the fast “kick”).

Thus with a slow knob rotation you incr/decr by 1, with a faster turn you can do “thousands”. I have that in my old vfos, but it is an ancient asm pic16 code not worth of messing with. It works fine, I can dial in a xMHz change with a few fast knob kicks with 1kHz “slow” stepping or dozens of kHz with 1Hz stepping..

PS: “accelerated input” could be done for example by measuring the “elapsed millis while the encoder’s A and B inputs incr/decrement” and each “Xnumber_of_elapsed_millis” will double the incr/decr step, till the millis2millis changes at A and B end.


RogerClark
Sun Jul 08, 2018 2:55 am
I am using mine for VFO control, but I suspect I’ll need to replace these cheap mechanical versions with higher performance versions (possibly that don’t appear to have steps), it the control works OK in the target radio
(Its not my radio its someone else’s so I won’t even know if it works until I send it to them to test – but it works on my testbed)

dannyf
Sun Jul 08, 2018 10:47 am
try those optical encoders, generally for speed sensing on motors. it is not hard to find 512/1024 ppr.

Leave a Reply

Your email address will not be published. Required fields are marked *