Introduction
This short blog post covers how I got a display and rotary encoderrotary encoder functioning with the Pi PicoPi Pico. The information here could be useful for many scenarios where a value needs to be dialed in, for instance, to control motor speed or to adjust the frequency of a circuit. My current use case was to control frequency.
The 1-minute video here demonstrates the project.
A slightly unusual LCD was used – it's a DM8BA10 10-digit 16-segment starburst with decimals display from Aliexpress. I liked it because it looks a little retro (at least when the blue backlight is switched off). I used a rotary encoder from Alpsrotary encoder from Alps.
Another slightly different thing (for me) was to use Python for this entire project. It's my first CircuitPython program, and it was an opportunity to explore how usable it is for microcontroller projects. I concluded that while it is interesting, I didn't have as much control over the hardware as I would have liked, and I frequently wished I was using C/C++. However, I also learned to appreciate just what a paradigm shift it can be to develop rapidly without needing to compile code, and in a language that is accessible for many more users than just traditional engineers. That was important to me because I wish to use this code for a simple radio project, where not all users may be familiar with traditional development methods and C programming.
Working with the Pi Pico and CircuitPython
After I purchased a Pi Pico, I downloaded the latest CircuitPython binary release (.UF2 file) and then held down the only button on the Pi Pico and plugged it into my PC's USB port, and then released the button. This only needs to be done once. The Pi Pico appears as USB storage space, and I dragged the CircuitPython binary release file onto it. Within seconds, the Pi Pico was running CircuitPython (and it's possible to use a USB serial port to interactively type Python commands, but I didn't do that).
Next, I wrote my Python code in a text editor, frequently saving it and dragging the code into the USB storage space. The code runs immediately each time I do that. Any text editor can be used, but the Mu editor has built-in code-checking capability, and a built-in serial port console which is handy for debug print statements.
Circuit
The diagram below shows the connections. The only slightly unusual thing is that one of the outputs from the rotary encoder makes its way to two pins on the Pi Pico; it was required to overcome a limitation with the code, explained later.
The LCD module has a serial interface that looks like SPI, but I didn't use the Pi's SPI interface because the LCD expects fairly slow data, and of a length that doesn't sit on an 8-bit boundary, and I wasn't sure if the SPI capability in the Pi Pico would have been happy with these things.
The display doesn't need to be illuminated depending on preference; disconnect the LED+ connection on the display to run it without the backlight.
This project is powered either from the USB connection on the Pi Pico or via a 5V supply connected to J1 in the circuit below. J1 and D1 can be removed if not required.
The Pi Pico pinout is shown here for reference; the green values are the GPIO numbers.
The DM8BA10 display total current consumption is about 200 uA at 5V with the backlight connection disconnected, or 16 mA with the backlight turned on. With the code described below, the Pi Pico current consumption is approximately 24 mA at 5V.
Code
The code is a single Python file. The information in this section is not necessary to use the project but will be useful if changes to the code are desired.
Serial commands are sent in order to control the display. Each character on the display has 16 segments of starburst arrangement, and there is a list in the code called LCD_CHARBITS that maps ASCII characters to the appropriate LCD segments that need turning on. The code currently only implements 0-9, A-Z and a small handful of other ASCII characters. The bitmap diagram is shown here. For example, the character C as in the diagram would be represented in the code using the hexadecimal value 0xA381.
The code has printing functions for numbers and text, for instance:
lcd_printtext("HI THERE") lcd_printint(42)
Main code
The main code initializes the display, and then loops forever, waiting on either the rotary encoder to be rotated or for the push-button to be pressed. It's trivially simple, the diagram below and the source code snippet show this.
update_rotval function
If the encoder is rotated, then the CircuitPython rotaryio library provides a count value, and within the update_rotval function, the difference between that and the previously captured value can be added to the displayed number. The countio library is simultaneously used to see how fast the encoder was rotated. The countio library cannot use the same pins as the encoder, and so one of the encoder pins is wired to another GPIO pin, purely for speed determination using the countio library. Based on the speed, a multiplier is used to create 'acceleration' for adjusting the count value. All of this is handled in the update_rotval function.
update_speedrange function
I wanted to use this project for a very wide range of values (tens of millions) and with a stiff rotary encoder, even the acceleration feature might be insufficient. I also wanted to allow the user to be able to optionally adjust in 1 kHz and 1 MHz increment. In the end, I decided that a single button would be used to select Hz, kHz, or MHz increments, and the display would briefly indicate this each time the button is pressed. The digitalio library is used to read the button status, change the step to 1, 1000, or 1000000, and briefly display the setting ("HZ", "K" or "M" on the display). This is all handled in the update_speedrange function.
The button feature, plus the acceleration feature, together make for an adequate user interface for this purpose. If a very large range of values is not needed, then the button can be removed from the circuit.
To actually use the rotational value to do something (e.g. set a motor speed, or set the frequency output for a signal generator), code will need to be written to send (say) I2C or SPI instructions containing the value, to the downstream device that will take action on it.
Summary
It was possible to implement a usable control and display method with the Pi Pico and Python, specifically CircuitPython, in this example. The code is on GitHub and is easily modifiable. The diagram showing the bit mapping for the LCD segments can be used to extend the displayable characters and symbols.
There's no doubt in my mind that I would have architected and coded things differently, and better, had I worked in C/C++. CircuitPython, as it stands today, cannot realistically be used for working with all possible peripherals, internal and external to the microcontroller, in as precise a manner as with C/C++ and any associated libraries. However, I think the Python implementation in some cases may be 'good enough' and I hope and wish CircuitPython becomes more mature. From my perspective, I will use CircuitPython for the situations where I'd like non-engineers to feel more comfortable, and in that case, I feel CircuitPython can be a good option.
Thanks for reading.