Słomkowski's technical musings

Playing with software, hardware and touching the sky with a paraglider.

Octoglow VFD - Orange Pi Zero board and software


Module which houses the brain of the Octoglow - the Orange Pi Zero single board computer. It also contains I2C DAC to drive analog gauges to show CPU usage in real time.

The last board in the collection, the brain. Its main component is Orange Pi Zero, a cheap Chinese alternative to Raspberry Pi Zero. Its main advantage is onboard Wi-Fi and Ethernet. The motivation for the very existence of this PCB is to hide the too modern layout of the single board computer from view and provide additional peripherals. Since the schematic is so simple, there’s not much to explain.

As seen in the photos, the board holds the Orange Pi by three screws and 2x13 header. It provides the required 5 V from system bus. Speaking of which, there are four 10-pin system bus connectors, just to make the whole thing future-proof. The MOSFET level shifter shifts 5 V to 3.3 V for I²C.

There is also I²C DAC5573 devised for controlling the external analog gauge meters. It is four-channel, but only two of the outputs are routed to external connectors. There are potentiometers RV1 and RV2 for fine-adjusting the gauge meters. Currently one channel is actually used, it displays the CPU usage. Quite a nice touch.

Switch SW3 is used to disconnect the system bus from Orange Pi and connect to external socket instead. It was put to simplify the development process. There is also SW2 switch which acts as programmatic shut down button. There is script which listens on appropriate GPIO pin to trigger system shutdown.

I put two system indicators too. Since I didn’t manage to find nice-looking LEDs, I stayed with miniature light bulbs. They are switched on by default, they go off only when respective GPIO is set as output and then set to 0. This way, when the boot sequence is complete and octoglowd daemon is running, they are all off.

Electrical wiring schematic.
Electrical wiring schematic.
↑ click to enlarge ↑

Software stack

The Pi has 16 GB MicroSD with Armbian installed. It is connected to the home network by Wi-Fi. The central application which controls the whole device is octoglowd, described in detail in the following section. Octoglow daemon is a Java application. To have it run at startup, I use supervisord. It is convenient, because it also tries to restart the app after unexpected crashes. I also created Python script (software/running-proc-gpio-eventlistenter.py) to drive the light bulb associated with running daemon. Supervisor monitors the daemon, when it crashes, the light goes up automatically.

The other script software/reset-button-handler.sh constantly monitors the state of the button GPIO. If it detects state 0, it calls shutdown -h now. This is done to safely shut down the Linux system without the need to log into it via SSH.

Armbian installation

As of 2023, Armbian version 23.8 bookworm is installed. Using armbian-config tool, we need to enable GPIO and I²C. Following packages were installed, some are required, some are for convenience:

apt install powertop openjdk-17-jre-headless supervisor most multitail pydf mtr htop vim iputils-ping btop wavemon git

I also added my default shell scripts. I created separate user for octoglowd daemon:

adduser octoglow
gpasswd -a octoglow i2c

Adjusting I²C frequency

By default, the I²C frequency is 100 kHz, however I encountered read errors and bus hangups, probably because of the interference generated by Geiger and magic eye step-up converters. To fix it, I decreased the I²C frequency to 40 kHz, which solved the problem.

To change the I²C frequency of Orange Pi Zero in Armbian, you have to recompile DTB file. They are located in /boot/dtb. For Orange Pi Zero, the file is sun8i-h2-plus-orangepi-zero.dtb. Decompile it:

dtc -I dtb -O dts sun8i-h2-plus-orangepi-zero.dtb -o sun8i-h2-plus-orangepi-zero.dts

Look for the I²C 0 entry: i2c@01c2ac00 in dts file, it should look similar to this:

i2c@1c2ac00 {
    compatible = "allwinner,sun6i-a31-i2c";
    reg = <0x1c2ac00 0x400>;
    interrupts = <0x0 0x6 0x4>;
    clocks = <0x3 0x3b>;
    resets = <0x3 0x2e>;
    pinctrl-names = "default";
    pinctrl-0 = <0x1d>;
    status = "disabled";
    #address-cells = <0x1>;
    #size-cells = <0x0>;
    phandle = <0x5c>;
};

Add entry setting I²C frequency to 40 kHz in hexadecimal format:

clock-frequency = <0x9c40>;

Compile the dts file and reboot the Orange Pi Zero:

dtc -I dts -O dtb sun8i-h2-plus-orangepi-zero.dts -o sun8i-h2-plus-orangepi-zero.dtb

Octoglow daemon

octoglowd (software/octoglowd) is a JVM application written in Kotlin. I made heavy use of coroutines, the Kotlin approach to concurrent programming. It provided me the great opportunity to learn them. I used some notable libraries:

It should be noted that this app is an second attempt to write Octoglow daemon. The previous one was done in Rust, but I suffered from steep learning curve and immature language. Lacking good async libraries also didn’t help. This abandoned code is archived in branch octoglowd-rust in the repository.

The architecture is all about standalone workers called daemons, which take care of all aspects of the functionality. Each daemon defines its pooling interval. Some more important daemons:

There is naturally code for communicating with each I²C device. I made an abstraction class I2CDevice, which wraps dvlopt library in coroutines nicely.

Front display daemon

FrontDisplayDaemon is the most complicated daemon because it accepts input from the dial and manages the pooling data from various devices and redraws the display. It is based on the concept of views. Each view is concerned with collecting data from single sensor or service and displaying it on the screen. The user cycles between the views by turning the knob. If there’s no input from the user for a prolonged time, the views are cycled automatically, by predefined interval. Each view can also define its own menu. You enter it by pressing the knob. For example, GeigerView has menu to enable or disable the magic eye.

Let’s describe the functionality taking CryptocurrencyView as example. Its task is to download exchange rates of three defined coins from coinpaprika.com and display them on the screen. Function poolStatusData() interacts with coinpaprika API and saves the result to the database. Function redrawDisplay() draws the data on the screen. Each exchange rate has its differential chart drawn to easily spot the trend. The differential chart functionality is used in many views. The resolution of the VFD display is limiting the amount of information which can be shown at once, but it does the trick nonetheless.

Some of the views which are implemented are shown below: