Pi Pico+ rotary encoder+ssd1306 menu control

Hi All
I am attempting to put together a raspberry pi pico rotary encoder menu system which will eventually control a stepper motor to vary the angles and time durations that it operates for. My final aim in all this is to have a rotating platform for a GoPro to do rotating time lapses. I am not quite clever enough to do all this on my lonesome and I have found a YouTube video by Kevin McAleer and the associated code that he has produced. The problem that I am having is that even though the display works and shows the menu the rotary encoder will not change the highlighted line in the code and the push button on the encoder will not work either. I have seen Jacob’s explanation ( thank you Jacob) and it works fine.
I have changed the connections on the Pico for the encoder from 16,17&18 to 14,15,& 12 but it’s made no difference.
I have also changed the encoder to one which has 3 pull-up resistors and therefore commented out the pull up part of the code but it’s made no difference.
This is the link to Kevin’s code

I couldn’t acquire the link to the video.

Here is his code

> # Rotary Menu
# Kevin McAleer
# May 2021

from os import listdir
from time import sleep
from machine import I2C, Pin
from ssd1306 import SSD1306_I2C

# I2C variables
ID = 0
SDA = Pin(0)
SCL = Pin(1)
i2c = I2C(id=ID, scl=SCL, sda=SDA)

# Screen Variables
WIDTH = 128
HEIGHT = 64

line = 1 
highlight = 1
shift = 0
list_length = 0
TOTAL_LINES = 6

# create the display
oled = SSD1306_I2C(width=WIDTH, height=HEIGHT, i2c=i2c)
oled.init_display()

# Setup the Rotary Encoder
button_pin = Pin(16, Pin.IN, Pin.PULL_UP)
direction_pin = Pin(17, Pin.IN, Pin.PULL_UP)
step_pin  = Pin(18, Pin.IN, Pin.PULL_UP)

# for tracking the direction and button state
previous_value = True
button_down = False

def get_files():
    """ Get a list of Python files in the root folder of the Pico """

    files = listdir()
    menu = []
    for file in files:
        if file.endswith(".py"):
            menu.append(file)

    return menu


def show_menu(menu):
    """ Shows the menu on the screen"""

    # bring in the global variables
    global line, highlight, shift, list_length

    # menu variables
    item = 1
    line = 1
    line_height = 10

    # clear the display
    oled.fill_rect(0,0,WIDTH,HEIGHT,0)

    # Shift the list of files so that it shows on the display
    list_length = len(menu)
    short_list = menu[shift:shift+TOTAL_LINES]

    for item in short_list:
        if highlight == line:
            oled.fill_rect(0,(line-1)*line_height, WIDTH,line_height,1)
            oled.text(">",0, (line-1)*line_height,0)
            oled.text(item, 10, (line-1)*line_height,0)
            oled.show()
        else:
            oled.text(item, 10, (line-1)*line_height,1)
            oled.show()
        line += 1
    oled.show()


def launch(filename):
    """ Launch the Python script <filename> """
    global file_list
    # clear the screen
    oled.fill_rect(0,0,WIDTH,HEIGHT,0)
    oled.text("Launching", 1, 10)
    oled.text(filename,1, 20)
    oled.show()
    sleep(3)
    exec(open(filename).read())
    show_menu(file_list)


# Get the list of Python files and display the menu
file_list = get_files()
show_menu(file_list)

# Repeat forever
while True:
    if previous_value != step_pin.value():
        if step_pin.value() is False:

            # Turned Left
            if direction_pin.value() is False:
                if highlight > 1:
                    highlight -= 1
                else:
                    if shift > 0:
                        shift -= 1

            # Turned Right
            else:
                if highlight < TOTAL_LINES:
                    highlight += 1
                else:
                    if shift+TOTAL_LINES < list_length:
                        shift += 1

            show_menu(file_list)
        previous_value = step_pin.value()

    # Check for button pressed
    if button_pin.value() is False and not button_down:
        button_down = True

        print("Launching", file_list[highlight-1+shift])

        # execute script
        launch(file_list[(highlight-1) + shift])

        print("Returned from launch")

    # Decbounce button
    if button_pin.value() is True and button_down:
        button_down = False

>

Any assistance in solving what I missing is greatly appreciated
Cheers
Nick

1 Like

You should break the problem down into a series of simpler sketches that test each portion of the task. If you can’t get past one of the steps show the code for that sketch and describe the error.

A starting point would be a simple sketch that just responds to the button press by displaying a message.

A second sketch would include code only for the rotary encoder, and simply displays the result of reading the encoder movement - the number of steps and the direction.

You could then combine them and confirm you can reliably get encoder step and direction, and button press. This is pretty much the main loop from the example you are following.

The next step would be to connect the stepper drivers and write a sketch to confirm that you can control the motor speed and direction using hard-coded values in the sketch.

Then you can merge the sketch that simply reports encoder movement and button presses into the sketch that controls the motors and you have the completed task. The bit about highlighting a list of files doesn’t seem applicable to what you want to accomplish.

Note that you have changed the encoder connection to 14,15 & 12 but your code still thinks it is connected at 16, 17 &18 - that will need to be fixed before step 2 above.

5 Likes

I looked at the code on the github link. Despite all the comments on YouTube of it being great, it is not. Kevin produces some good flashy presentations but often the important substance you need is missing. Your post is good evidence of this.

For what you want to do, it’s not necessary to use Kevin’s code, it doesn’t do much to be honest.
Observations :-

  • There is no sleep statement. The while True: loop does not need to run as fast as it possibly can.
  • Because there is no sleep statement, he had to incorporate debounce, complicating things. I rarely need debounce in my Pico programs, the Pico GPIO’s are pretty good.
  • Why use a rotary encoder, two buttons would be better, menu up menu down or just one that cycles through the list of files.
  • No parsing of the file list. It should only list the Test.py files. Two of them actually do nothing. The one that does only displays for a second, not really long enough.

I’d forget about this code and focus on getting your rotating platform to rotate first.
If you still want to get this to work, follow what @Jeff105671 said.

Regards
Jim

PS Github link

3 Likes

Hey @Nicholas193967,

I second what the others have said. It is probably a good idea to reduce the complexity of your code. I have changed Kevin’s code to remove everything but the encoder value and output that to the console which should be useful for you in testing this.

I also changed the debounce implementation as @James46717 suggested so that it uses a sleep delay instead.

from os import listdir
from time import sleep
from machine import Pin

# Setup the Rotary Encoder
button_pin = Pin(16, Pin.IN, Pin.PULL_UP)
direction_pin = Pin(17, Pin.IN, Pin.PULL_UP)
step_pin = Pin(18, Pin.IN, Pin.PULL_UP)

# for tracking the direction and button state
previous_value = True
button_down = False

# Delay of 50 ms for debouncing button input
DEBOUNCE_DELAY = 0.05  

encoder_value = 0

# Repeat forever
while True:

    if previous_value != step_pin.value():
        if step_pin.value() == False:

            # Turned Left
            if direction_pin.value() == False:
                encoder_value -= 1
                print("Encoder value: ", encoder_value)
                print("Encoder rotating: LEFT")

            # Turned Right
            else:
                encoder_value += 1
                print("Encoder value: ", encoder_value)
                print("Encoder rotating: RIGHT")

        previous_value = step_pin.value()

    # Check for button pressed
    if not button_pin.value():
        if not button_down:
            # Debounce delay
            sleep(DEBOUNCE_DELAY) 
            
            # Check button state again after delay
            if not button_pin.value():  
                button_down = True
                print("Button pressed")
                
    elif button_down:
        button_down = False

2 Likes

Thank you all for your replies. I will work on them and see what I can come up with.
Samuel , thank you for your test code ,it works perfectly , I’m going to try and interface some menu code with that, and possibly also try a button menu
Cheers
Nick

2 Likes

Another appoarch

H All
I have been working on this continuously since I was last online.
Jeff , I determined that I couldn’t get anything on the screen without having most of the code in place.
Samuel, I have run your code and it woks well however I went back to the original code and discovered that it had a number of places which said (is False) or similar and I determined that "is " should be replaced by(==) is equal to. Now the menu is on the screen and it scrolls, but it takes 2 clicks of the rotary encoder to move one line .
Please see my modified code below

> 
# Rotary Menu
# Kevin McAleer
# May 2021

from os import listdir
from time import sleep
from machine import I2C, Pin
from ssd1306 import SSD1306_I2C
import utime
# I2C variables
ID = 0
SDA = Pin(0)
SCL = Pin(1)
i2c = I2C(id=ID, scl=SCL, sda=SDA)
#led_onboard=machine.Pin("LED", machine.Pin.OUT)  #led


# Screen Variables
WIDTH = 128
HEIGHT = 64

line = 1 
highlight = 1
shift = 0
list_length = 0
TOTAL_LINES = 7

# create the display
oled = SSD1306_I2C(width=WIDTH, height=HEIGHT, i2c=i2c)
oled.init_display()

# Setup the Rotary Encoder
button_pin = Pin(16, Pin.IN, Pin.PULL_UP)     #12
direction_pin = Pin(17, Pin.IN, Pin.PULL_UP)   #14
step_pin  = Pin(18, Pin.IN, Pin.PULL_UP)      #15

# for tracking the direction and button state
previous_value = True
button_down = False
# Delay of 50 ms for debouncing button input
DEBOUNCE_DELAY = 0.05  

def get_files():
    """ Get a list of Python files in the root folder of the Pico """

    files = listdir()
    menu = []
    for file in files:
        if file.endswith(".py"):
            menu.append(file)

    return menu


def show_menu(menu):
    """ Shows the menu on the screen"""

    # bring in the global variables
    global line, highlight, shift, list_length

    # menu variables
    item = 1
    line = 1
    line_height = 10

    # clear the display
    oled.fill_rect(0,0,WIDTH,HEIGHT,0)   # start point 0,0 ;w, h;  0 colour

    # Shift the list of files so that it shows on the display
    list_length = len(menu)
    short_list = menu[shift:shift+TOTAL_LINES]

    for item in short_list:
        if highlight == line:
            oled.fill_rect(0,(line-1)*line_height, WIDTH,line_height,1)
            oled.text(">",0, (line-1)*line_height,0)
            oled.text(item, 10, (line-1)*line_height,0)
            oled.show()
        else:
            oled.text(item, 10, (line-1)*line_height,1)
            oled.show()
        line += 1
    oled.show()


def launch(filename):
    """ Launch the Python script <filename> """
    global file_list
    #clear the screen
    oled.fill_rect(0,0,WIDTH,HEIGHT,0)
    oled.text("Launching", 1, 10)
    oled.text(filename,1, 20)
    oled.show()
    sleep(3)
    exec(open(filename).read())
    show_menu(file_list)


# Get the list of Python files and display the menu
file_list = get_files()
show_menu(file_list)

# Repeat forever
while True:
#      led_onboard.value(1)
#      #print('on')
#      utime.sleep_ms(50)
#     
#      led_onboard.value(0)
#      #print('off')
#      utime.sleep_ms(1000) 
    
    
    
    if previous_value != step_pin.value():
        if step_pin.value() == False:   #is

            # Turned Left
            if direction_pin.value() == False:   #is
                if highlight > 1:
                    highlight -= 1
                else:
                    if shift > 0:
                        shift -= 1

            # Turned Right
            else:
                if highlight < TOTAL_LINES:
                    highlight += 1
                else:
                    if shift+TOTAL_LINES < list_length:
                        shift += 1

            show_menu(file_list)
        previous_value = step_pin.value()

 # Check for button pressed
if button_pin.value() == False and not button_down:    #is
        button_down = True
        sleep(DEBOUNCE_DELAY) 
        print("Launching", file_list[highlight-1+shift])

        # execute script
        launch(file_list[(highlight-1) + shift])

        print("Returned from launch")
# Debounce delay
        sleep(DEBOUNCE_DELAY) 
# Decbounce button
if button_pin.value() == True and button_down:   #is
        button_down = False

>

Elliot , I have tried to run the code you provided but it gives me this error,
ImportError: no module named ‘rotary.rotary_irq’ , .Icannot find a link to this module although I can find “rotary_irq_rp2.py” but this doesn’t work in this instance
so I am still at it , just slow progress . Thank you for your assistance.
cheers
Nick

Did you start with the first suggestion: “… a simple sketch that just responds to the button press by displaying a message.”?

If you got that far then the subsequent sketches would show you the results of adding the additional code and confirming that it was working as expected. If you didn’t get to that first step then I would not recommend trying to move further on in the process - it will make debugging very difficult if you can’t display messages to screen.

2 Likes

Hi Jeff
Yes I have put together a code that displays to the shell and at the same time puts a message on the ssd1306 and responds to left or right turn of the rotary enc and also to the button press.
see code below
One thing that I would also like to do is to count the number of times the switch is pressed and display that( I think it requires strings but I am not familiar with the process) and then comes
creating executable menu’s
cheers
Nick


from os import listdir
from time import sleep
from machine import I2C, Pin, SoftI2C
import utime
from time import sleep
import ssd1306
i2c = SoftI2C(scl=Pin(5), sda=Pin(4))   #22  21

oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
oled.init_display()

# Setup the Rotary Encoder
button_pin = Pin(16, Pin.IN, Pin.PULL_UP)     #12
direction_pin = Pin(17, Pin.IN, Pin.PULL_UP)   #14
step_pin  = Pin(18, Pin.IN, Pin.PULL_UP)      #15

# for tracking the direction and button state
previous_value = True
button_down = False

# Delay of 50 ms for debouncing button input
DEBOUNCE_DELAY = 0.05  

encoder_value = 0

# Repeat forever
while True:
    oled.fill_rect(0,0,oled_width,oled_height,0)   # start point 0,0 ;w, h;  0 colour
    if previous_value != step_pin.value():
        if step_pin.value() == False:

            # Turned Left
            if direction_pin.value() == False:
                encoder_value -= 1
                # oled.text("Encoder value: ",1,30 ) #encoder_value,)
                # oled.text("Encoder rotating: LEFT")
                print("Encoder value: ", encoder_value)
                print("Encoder rotating: LEFT")
                oled.text("Launching", 1, 10)
                oled.text("rotating left",1, 20)
                oled.text("Hi There",1, 30)
                oled.show()
                #oled.fill()
            # Turned Right
            else:
                encoder_value += 1
                ##encoder_string = str(encoder value)
                #screen1_row1 = ("Encoder value", encoder_value)
                print("Encoder value: ", encoder_value)
                print("Encoder rotating: RIGHT")
                oled.text("Encoder value: ", 1, 10)    # encoder_value)
                oled.text("Enc rot: RIGHT",1, 20)
                ##oled.text("Enccoder_str",1, 30)
                oled.show()
                #oled.fill()

        previous_value = step_pin.value()

    # Check for button pressed
    if not button_pin.value():
        if not button_down:
            # Debounce delay
            sleep(DEBOUNCE_DELAY) 
            
            # Check button state again after delay
            if not button_pin.value():  
                button_down = True
                print("Button Pressed")
                oled.text("Button pressed",1,20)
                oled.show()
    elif button_down:
        button_down = False

Hey @Nicholas193967,

I’m assuming the code you pasted is confirmed working? That’s a great start!

To display the number of times the button was pressed, you should simply be able to increment a counter. To write it to the OLED display you should just be able to use the str() function.

Hope this helps!

Zach
Yes code confirmed working
Thanks
Nick

1 Like