A custom device used to control motor drivers according to the schedule, with many configurable parameters, using the Raspberry Pi Pico development board
Published on January 6, 2024 · Reading time: 4 minutes
Modular design
Assemble the device without extra knowledge, for less than €50
Based on off-the-shelf electronic components, and simple carrier and button boards designed by myself
User-friendly menus
Set parameters, invoke actions and check the event logs conveniently
Simple user interface based on easy to navigate menus, using the character LCD and just three buttons
Day and night schedules
Adjust the time and duration of the opening and closing actions independently
The door can also be opened or closed manually at any time. Custom code is run for either action
Partial operation mode
Split the opening or closing actions into evenly spaced, byte-sized operations
According to the starting and ending times and the defined number of operations per day
Technical details
It was supposed to be a quick project when I started in early 2023, but it took me an entire year to finish it. There are many things that can go wrong when it comes to electronic circuit design if you are not experienced. I have run through multiple prototypes and bought a CNC machine to make things less annoying, and documented my experience in a separate article. Let’s focus on the Adafruit’s CircuitPython runtime itself.
Asynchronous operation
There are two main processes running, the scheduler and the menu, that need to
be run in parallel. CircuitPython does not support threads, interrupts or
concurrency by default, but a
first-party asyncio
substitute
is available.
However, some actions are performed synchronously, for example the opening and
closing itself. There is no guarantee that await asyncio.sleep(1)
will take
exactly 1 second, while the blocking version, time.sleep(1)
, will provide good
enough results. Asynchronous tasks are performed on some menu screens as well.
User menu
Every menu screen inherits the Menu
class, which represents the finite list of
editable variables in “navigation” or “edit” cursor mode. Implementations have
their own state and can change the behavior of every button or the displayed
text. For example, the IdleMenu
shows the current time and device status but
has no visible cursor.
The display I have picked for this project does not have built-in backlight control and can only show basic English or Japanese text and up to 8 custom symbols. When user presses the button, the character set is dynamically updated. The display’s backlight can be controlled manually via simple transistor circuit and a PWM signal.
CircuitPython can handle single buttons and key matrices via the built-in
keypad
module. It does not report for how long the buttons have been pressed,
though. I have added this feature in order to reduce a number of keys.
Persistence
The AT24LC32 EEPROM chip is commonly found on cheap RTC + EEPROM modules and provides 4kB of non-volatile memory. There is no file system, so I had to roll out my own solution for storing settings and log entries.
Each block of data is 8 bytes long and includes the checksum (basic XOR). If the read data is invalid, the settings are reset to the safe defaults. Up to 127 log entries are stored and rotated, with EOF marker updated after every write.
Reliability
CircuitPython has good hardware support and a wide array of additional libraries, but in reality, it is meant to be used for learning and prototyping. I have decided to take the risk and use it for my always-on device.
RP2040 chip found in Raspberry Pi Pico has an internal watchdog timer that is
used to reboot the board if the program does not “feed” it for a specified
amount of time. Since the CircuitPython 8.1 (mid-2023), it is also possible to
create a safemode.py
file that defines the custom behavior after a crash when
it is caused by something other than my code.
One of the prototype boards had PWM issues caused by pin collisions. According to the RP2040 documentation, section 4.5.2. Programmer’s Model, there are eight 2-channel PWM slices. You can’t reliably use pins 0 and 16 at the same time. The same goes for pins 1 and 17, 8 and 18, and so on. I couldn’t fix it in software, so I had to change the PCB layout.
Memory optimizations
The entire program is more than 1000 lines long, and it’s difficult to keep everything in a single file. On the other hand, hardware modules depend on each other more than ever, and code splitting would result in an increased memory usage and cyclic import issues. I have created a few unit tests for partial actions, so I had to have at least two modules to make tests run in the CPython interpreter. The entire menu has been moved to a separate file as well.
Furthermore, on-device bytecode generation greatly increases the startup time.
Thankfully, there is a mpy-cross
utility, used by Adafruit developers
themselves, that can do this ahead of time to mitigate this.
Dependency management
The recommended way of installing additional libraries is by using the circup
tool. It can read dependencies from the requirements.txt
file, but
unfortunately, it ignores version numbers, and uses the latest “compiled”
version of each package.
Alternatively, it is possible to use source repositories of each library and select one of the tagged commits, which I did during the upgrade to CircuitPython 9.0. Dependencies are installed as Git submodules that can be downloaded automatically when the repository of this project is cloned.
Bill of materials
All components are THT (through-hole), not SMD (surface mounted).
- Raspberry Pi Pico
- DS3231 RTC + AT24LC32 EEPROM module
- HD44780 display module
- 12×12 mm tactile switch (x3)
- 2N7000 N-MOSFET transistor
- 1 kΩ potentiometer
- resistors: 220 Ω, 470 Ω (x8), 10 kΩ
- goldpin sockets, 2.54 mm pitch: 20-pin (x2), 16-pin, 6-pin, 4-pin
- goldpin headers, 2.54 mm pitch: 2-pin, 4-pin (x2)
- pin jumper, 2.54 mm pitch, 2-pin
- terminal block, 5.08 mm pitch, 3-pin
- M2.5×11 mm spacers (x3)
- M2.5 screws (x6)
- Carrier and button boards
Check out other blog posts:
-
Making framebuf text 10x faster in CircuitPython
2024-12-23
Finding a cause of slow text rendering and optimizing it for monochrome LCD and OLED displays.
-
Tracking libadwaita adoption in Fedora (updated)
2024-10-29
The complete list of software preinstalled in Fedora, including apps using the libadwaita library.
-
Reinstalling Debian, fast
2024-10-12
Installing Debian with core GNOME, fixing UI inconsistencies, restoring software needed on a home PC.