RSS

Archive for the ‘Electronics’ Category

Cheap Yellow Display SPI

Sunday, March 16th, 2025

Cheap Yellow Display Banner

Cheap Yellow Displays look to be an ideal way of experimenting with the ESP32 ecosystem. They are small, simple and are equipped with a number of built in sensors / modules. They sounded ideal for a project to monitor a test environment, displaying data and sending detailed logs over a WiFi network.

Early tests went well with the various features prototyped and each feature working well. The problems started when the various components were brought together, at which point the SPI implementation on the board started to become an issue.

This post runs through the details of the problem and the hardware solution selected.

There are a number of variants of the Cheap Yellow Display and this post suggests a destructive hardware modification to the circuit board to resolve the issue. It is recommended that the schematic is double checked before proceeding with any modifications.

What Are Cheap Yellow Displays?

I first came across the Cheap Yellow Displays when they were mentioned in a Hackaday Post in early January. They are small boards, about the size of a credit card. The boards contain an array of devices including:

  • Screen with touch sensor
  • Ambient light
  • RGB LED
  • Audio amplifier
  • SD Card slot

Cheap Yellow Display

All of this is powered by an ESP32 with 4MB of flash and can be obtained for about £9 when purchased from China. Sounds perfect for a small project that will monitor UART traffic and log / send the data over the network.

There is also an open source repository containing code, schematics and a list of hacks for the board.

The board ordered was the ESP32-2432S028, time to break out VS Code and ESP-IDF.

One feature missing from this package was the ability to natively connect an ESP-PROG programmer to the board. This means that development will have to fall back to using ESP_LOGx or printf statements for debugging. No JTAG here.

Proof of Concept

As mentioned, the board is going to be used to connect to a 3.3V TTL UART and log the information, optionally sending the data to a server on the network. The device has a display with touch screen so this becomes an obvious choice to configure and monitor operation of the device.

The SD Card slot also means that data can be logged to a file for longer term storage and transfer to a host computer.

For the initial proof of concept we need to look at the following tasks:

  • Use LVGL to create a user interface on the LCD screen
  • Respond to touch events
  • Access the SD card reader

The first two tasks we proven to work using the LVGL examples in the open source repository.

Access to the SD card was proven using the SD SPI example in the Espressif repository.

Combining the Examples

Next step was pulling the two pieces of work together, this is where things started to go wrong. Adding the SD Card example code to the LVGL code generated initialisation errors when spi_bus_initialize was called.

The code as written used the following interfaces:

  • SPI2_HOST for LCD display
  • SPI3_HOST fo the touch sensor

IDF exposes three SPI hosts, 1 – 3, so the logical step is to assign SPI1_HOST to the SD card interface. This is where the problems started to arise. Using the third SPI interface resulted in the following error:

E (296) spi: spi_bus_initialize(802): SPI bus already initialized

The issue can be distilled down to the following code:

void app_main(void)
{
    spi_bus_config_t buscfg_display = {};
    buscfg_display.mosi_io_num = (gpio_num_t) 13;
    buscfg_display.miso_io_num = (gpio_num_t) 12;
    buscfg_display.sclk_io_num = (gpio_num_t) 14;
    buscfg_display.quadwp_io_num = -1;
    buscfg_display.quadhd_io_num = -1;
    buscfg_display.max_transfer_sz = 4000;
    buscfg_display.flags = 0;
    buscfg_display.intr_flags = 0;

    ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg_display, SPI_DMA_CH_AUTO));

    spi_bus_config_t buscfg_touch = {};
    buscfg_touch.mosi_io_num = GPIO_NUM_32;
    buscfg_touch.miso_io_num = GPIO_NUM_39;
    buscfg_touch.sclk_io_num = GPIO_NUM_33;
    buscfg_touch.quadwp_io_num = -1;
    buscfg_touch.quadhd_io_num = -1;
    buscfg_touch.data4_io_num = -1;
    buscfg_touch.data5_io_num = -1;
    buscfg_touch.data6_io_num = -1;
    buscfg_touch.data7_io_num = -1;
    buscfg_touch.max_transfer_sz = 4000;
    buscfg_touch.flags = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_GPIO_PINS;
    buscfg_touch.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO;
    buscfg_touch.intr_flags = ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM;

    ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg_touch, SPI_DMA_CH_AUTO));

    spi_bus_config_t buscfg_sdcard = {};
    buscfg_sdcard.mosi_io_num = 23;
    buscfg_sdcard.miso_io_num = 19;
    buscfg_sdcard.sclk_io_num = 18;
    buscfg_sdcard.quadwp_io_num = -1;
    buscfg_sdcard.quadhd_io_num = -1;
    buscfg_sdcard.max_transfer_sz = 4000;

    ESP_ERROR_CHECK(spi_bus_initialize(SPI1_HOST, &buscfg_sdcard, SPI_DMA_CH_AUTO));
}

The traceback for the error is:

0x4008582b: _esp_error_check_failed at /Users/username/esp/esp-idf/components/esp_system/esp_err.c:49
0x400d6859: app_main at /Users/username/Public/spitest/main/spitest.c:49 (discriminator 1)

This highlights the line causing the error as:

ESP_ERROR_CHECK(spi_bus_initialize(SPI1_HOST, &buscfg_sdcard, SPI_DMA_CH_AUTO));

There are some notes in the IDF documentation about using SPI1 and possible issues that can be encountered.

The unanswered question is why do we need three SPI busses?

Brief Recap – SPI

Readers who are experienced with how SPI works can skip this section.

SPI has the concept of a central (master) device and a number of peripherals (slave) devices. The devices communicate using two or three lines with an optional device selection signal (chip select). The four lines are usually labelled as:

  • COPI (MOSI) – data signals from the central controller to the peripherals
  • CIPO (MISO) – data from the peripheral to the central device
  • CLK (SCK) – clock signal generated by the central device
  • CS – chip select, used to identify the peripheral being addressed.

The chip select signal is not always connected to a central device. Take for example, when there is only one SPI peripheral in a circuit. You do not always need the chip select signal, if the peripheral has a CS line then this could be connected to high or low depending upon the peripheral requirements. In this was the peripheral is always listening which is fine as this is the only device on the bus.

For a system with multiple SPI peripherals there are two options:

  • Put each peripheral on its own bus
  • Use the CS line to switch peripherals on / off

The first option means that for every peripheral we would need three signals, COPI, CIPO and CLK. The CS line could be hard wired as the bus is a single peripheral bus model. This design means that two devices would consume six pins from the microcontroller.

The second option is less costly as you use the same three data and clock pins (COPI, CIPO and CLK) for all of the peripherals. You need an additional CS pin for each peripheral but this still results in a saving in the pin count. So for the two peripheral example, you need the three data and clock pins along with a CS1 and CS2 line, so only 5 pins rather than 6.

A more detailed description of the history of SPI and other features of the bus including timing characteristics (modes) can be found on WikiPedia on the Serial Peripheral Interface page.

Cheap Yellow Display SPI Implementation

A quick look at the schematic for the Cheap Yellow Display shows how the three SPI devices have been connected to the ESP32. The pin out is as follows:

Signal Name LCD Touch Sensor SD Card
COPI (MOSI) 13 32 23
CIPO (MISO) 12 39 19
CLK 14 25 18
CS 15 33 5

The use of three distinct sets of pins, one for each interface, means that we will need three SPI interfaces requiring investigation and resolution of the SPI1_HOST initialisation error.

Hardware Solution

Another option is to look at a hardware solution, moving one of the devices to another bus and using CS to determine the source and destination for the data. Control of the LCD and SD card are both under control of the application. The touch screen peripheral is reacting to the user input and the application has no control over when this will occur. The touch screen peripheral will therefore remain on one bus and the LCD and SD card will share a bus. The wiring becomes:

Signal Name LCD Touch Sensor SD Card
COPI (MOSI) 13 32 13
CIPO (MISO) 12 39 12
CLK 14 25 14
CS 15 33 5

First task, disconnect the data and clock signals for the SD card leaving the CS pin connected. The image below shows the three tracks that need cutting (highlighted in red):

Track cuts

Next up, connect the SD card data and clock pins to the LCD SPI bus. The modified board looks something like this:

Modified Board

Software Modifications

The software modifications should be relatively trivial, a simple change to the MOSI, MISO and CLK pins used for the SD card. The CS pin assignment remains unchanged from the sample application.

Conclusion

Modifying the hardware has reduced the number of SPI host interfaces required bringing the interface count down to two. Early experience has shown that the system starts and runs as expected. The three peripherals, touch, LCD and SD card all respond as expected.

Back to providing UART monitoring functionality.

nRF52840 Does Not Appear as a Wireshark Interface

Monday, January 6th, 2025

nRF52840 Banner

Recent work has been heading towards Bluetooth software enhancement on the ESP32. The basic design of the system follows the classic server (central) and peripheral model. The ESP device is acting as a server with connections from peripheral devices such as sensors or event Bluetooth client applications such as LightBlue.

It would be really useful if as part of the debugging process we have some visibility of the Bluetooth traffic in the air. This will allow the diagnosis of software issues. There are two ways we can tackle this problem:

  • Write our own client on a second ESP32
  • Use commercially available software

The first of these will be cheaper initially but will require extra time to develop the software. The second option has a small initial outlay but allows us to build on the experience and expertise of others.

It is this second option that we will examine here.

BLE Sniffer

Nordic offer a range of development platforms for WiFi and Bluetooth. One of these platforms, the nrf52480, has the option for your own software development as well as some custom firmware allowing sniffing of BLE traffic.

Setting up the System

There are some really good instructions over on the Nordic web site discussing Setting up nRF Sniffer for Bluetooth LE. The nrf52480 dongle is installed as an interface for Wireshark. Wireshark then provides the user interface and the ability to decode the Bluetooth packets.

All was fine until section 3 of the guide where we find the following instructions:

3. Enable the nRF Sniffer capture tool in Wireshark.
3.1 Refresh the interfaces in Wireshark by selecting Capture > Refresh Interfaces or pressing F5.
3.2 Select View > Interface Toolbars > nRF Sniffer for Bluetooth LE to enable the nRF Sniffer interface.

The instruction at 3.2 no longer seems to be relevant as Wireshark displays the Interfaces panel when it starts. Time to search for the nRF Sniffer for Bluetooth LE in the list of installed interfaces.

No luck.

Troubleshooting

One of the prerequisite steps (section 1 of the document linked above) is to install the Python requirements with the command:

python3 -m pip install -r requirements.txt

This completed as expected and the command installed the required packages. So let’s run the shell script to check the hardware:

./nrf_sniffer_ble.sh --extcap-interfaces

The output from this script included a full

pyserial not found, please run: "/opt/homebrew/opt/python@3.13/bin/python3.13 -m pip install -r requirements.txt" and retry

Double checking with the python3 -m pip install -r requirements.txt command results in the following output:

Requirement already satisfied: pyserial>=3.5 in /Users/user-name/.pyenv/versions/3.12.0/lib/python3.12/site-packages

Looking at the output it appears that there is a conflict with the Python environments. The script is trying to use the homebrew version of Python but the command line is using a Python virtual environment.

Examining the script we find that the is special consideration for MacOS:

unamestr=`uname`
if [ "$unamestr" = 'Darwin' ]; then
        hb_x86_py3="/usr/local/bin/python3"
        hb_apple_silicon_py3="/opt/homebrew/bin/python3"
        .
        .
        .

The remainder of the if statement makes some checks on the various versions of Python that could be installed on the system. In this case the script selects /opt/homebrew/opt/python@3.13/bin/python3.13. However, which python shows /Users/username/.pyenv/shims/python as the Python interpreter being used.

So this looks to be the problem, the script is using the incorrect version of Python. Editing the script and adding the line py3=python3 towards the end of the script and re-running the ./nrf_sniffer_ble.sh –extcap-interfaces command results in the following in the output:

extcap {version=4.1.1}{display=nRF Sniffer for Bluetooth LE}{help=https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Sniffer-for-Bluetooth-LE}
control {number=0}{type=selector}{display=Device}{tooltip=Device list}
control {number=1}{type=selector}{display=Key}{tooltip=}
control {number=2}{type=string}{display=Value}{tooltip=6 digit passkey or 16 or 32 bytes encryption key in hexadecimal starting with '0x', big endian format.If the entered key is shorter than 16 or 32 bytes, it will be zero-padded in front'}{validation=\b^(([0-9]{6})|(0x[0-9a-fA-F]{1,64})|([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2}) (public|random))$\b}
.
.
.

This is looking more positive. Back to Wireshark and this time the interface is shown in the list of interfaces available.

Conclusion

This problem took a little tracing and appears to have occurred due to two issues:

  • VIRTUAL_ENV was not set by the Python virtual environment being used
  • Several different version of Python installed on the host system

The Python versions had been installed as pre-requisites for other packages installed by homebrew and as such could result in more issues if uninstalled.

Editing the Nordic script, while not ideal, was probably the most pragmatic solution to this issue.

Repeatable Deployments 5 – NVMe Base Duo

Monday, October 21st, 2024

NVMe Base Duo Banner

A few weeks ago we looked at using the Pimoroni NVMe Base to add 500 GBytes of storage to a Raspberry Pi, first manually and then using Ansible.

A few days ago I started to look at doing the same but this time with the NVMe Base Duo. This would allow the addition of two drives to the Raspberry Pi. Should be simple, right?

TL;DR The scripts and instructions for running them can be found in the AnsibleNVMe GitHub repository.

Setting up the Hardware

As with the NVMe Base, setting up the NVMe Base Duo was simple, just follow the installation instructions on theproduct page. Again, the most difficult part was connecting the NVMe Base Duo board to the Raspberry Pi using the flat flex connector.

A quick check of the /dev directory shows the two drives as devices:

clusteruser@TestServer:~ $ ls /dev/nv*
/dev/nvme0  /dev/nvme0n1  /dev/nvme1  /dev/nvme1n1

Setting up the two drives should be a case of running the configuration script from the previous post with two different device / mount point names, namely:

  • nvme0 / nvme0n1
  • nvme1 / nvme1n1

Ansible tasks should allow us to reuse the commands used to mount a single drive on the NVMe Base without repeating the instructions with copy/paste.

Ansible Tasks

First thing to do is identify the tasks that should be executed on both drives. These are found in the ConfigureNVMeBase.yml file. They make up the bulk of the file. These tasks are:

- name: Format the NVMe drive {{ nvmebase_device_name }}
  command: mkfs.ext4 /dev/{{ nvmebase_device_name }} -L Data
  when: format_nvmebase | bool

- name: Make the mount point for {{ nvmebase_device_name }}
  command: mkdir /mnt/{{ nvmebase_device_name }}

- name: Mount the newly formatted drive ({{ nvmebase_device_name }})
  command: mount /dev/{{ nvmebase_device_name }} /mnt/{{ nvmebase_device_name }}

- name: Make sure that {{ ansible_user }} can read and write to the mount point
  command: chown -R {{ ansible_user }}:{{ ansible_user }} /mnt/{{ nvmebase_device_name }}

- name: Get the UUID of {{ nvmebase_device_name }}
  command: blkid /dev/{{ nvmebase_device_name }}
  register: blkid_output

- name: Extract UUID from blkid output
  set_fact:
    device_uuid: "{{ blkid_output.stdout | regex_search('UUID=\"([^\"]+)\"', '\\1') }}"

- name: Clean the extracted UUID
  set_fact:
    clean_uuid: "{{ device_uuid | regex_replace('\\[', '') | regex_replace(']', '') |  regex_replace(\"'\" '') }}"

- name: Add UUID entry for {{ nvmebase_device_name }} to /etc/fstab
  lineinfile:
    path: /etc/fstab
    line: "UUID={{ clean_uuid }} /mnt/{{ nvmebase_device_name }} ext4 defaults,auto,users,rw,nofail,noatime 0 0"
    state: present
    create: yes

Breaking these instruction out into a new file, ConfigureNVMeBaseTasks.yml file gives us the consolidated tasks list. The device and drive mount point are derived from the Ansible variable nvmebase_device_name which is set in the calling script as follows:

- include_tasks: ConfigureNVMeDriveTasks.yml
  vars:
	  nvmebase_device_name: nvme0n1

The ansible_user variable (used in the chown command in the tasks file) is taken from the group_vars/all.yml file.

And for the second drive we would use:

- include_tasks: ConfigureNVMeDriveTasks.yml
  vars:
	  nvmebase_device_name: nvme1n1
  when: nvme_duo == true

Note the addition of the when clause to only execute the tasks in the ConfigureNVMeDriveTasks.yml if the nvme_duo variable is true. This clause will also be used when Samba is configured.

Install Samba (Optional)

The installation of Samba follows similar steps as detailed in the Adding NVMe Base using Ansible post. The only addition is to add the second drive to the configuration section of the script.

Change Hostname (Optional)

One final, optional step is to change the name of the Raspberry Pi. This is useful when a number of devices are being configured. This step requires the hostname variable being set in the group_vars/all.yml file. Execute the ChangeHostname.yml script once the variable has been changed. Note that the script may fail following the reboot step as Ansible tries to reconnect with the Raspberry Pi using the old host name.

Lessons Learned

For some reason which was never tracked down, the installation of the sometimes failed on the NVMe Base Duo with access permission issues. The access issue presents itself on the Samba shares and also when attempting to use the drive when logged into the Raspberry Pi. This was resolved by setting the ansible_user variable in the group_vars/all.yml file.

The first installation of the NVMe Base Duo added the PCIe Generation 3 support. This caused some issues accessing the devices in the /dev directory. Removing this support allowed both of the drives to be accessed.

Conclusion

Breaking the drive installation and configuration into a new tasks file allows Ansible to reuse the same steps for two different devices. Couple this with when clauses allows changes to the control flow when deploying to two different types of devices.

Repeatable Deployments Part 2 – NVMe Base

Monday, June 24th, 2024

NVMe Base on Raspberry Pi

A while ago, I started a series about creating Repeatable Deployments documenting the first step of the process, namely getting an OS onto a Raspberry Pi equipped with a USB SSD drive.

In this post we will look at the steps required to install a NVMe SSD into the environment and automate configuring the system. At the end of the post we should have two drives installed:

  • USB SSD drive holding the operating system and any applications used
  • NVMe drive for holding persistent data

The first of these drives will be considered transient with the operating system and applications changing but always installed in a repeatable manner. The second drive will be used to hold data which should persist even if the operating system changes.

The Hardware

The release of the Raspberry Pi 5 gave us supported access to the high speed PCIe and this means we have the option to install SSD drives using PCIe to give high speed disc access to the Raspberry Pi. A number of manufacturers have developed boards to support the addition of PCIe devices from SSD (which we will look at) to AI coprocessor boards.

There have been a number of reports concerning the compatibility of various drives and the boards offering access to the PCIe bus on the Raspberry Pi. For this reason I decided to use the a board that is supplied with a known working SSD. Luckily Pimoroni has two boards on offer that can be purchased stand alone or with a known working SSD:

For this post we will look at the single drive option with a 500GB SSD.

As usual with Pimoroni, ordering was easy and delivery was quick with the unit arriving the next day.

Pimoroni also provide a list of alternative known working drives along with some that may work. This list ocan be found on the corresponding produce page (links above). There is also a link to the Pi Benchmarks site that provides speed information for the various drives.

Assembly

Following the assembly guide was fairly painless. The most difficult step was to install the flat flex connector between the NVMe Base and the Raspberry Pi 5.

Configuration

The product page for the NVMe Base contains a good installation guide. This guide assumes that the SSD installed on the NVMe base is going to be used as the main boot drive for the system and that the OS etc. will be copied from existing bootable media. This is not the case for this installation.

System Update

As with all Pi setups, the first thing we should do is make sure that we have the most recent OS and software.

sudo apt get update -y
sudo apt get dist-upgrade -y
sudo reboot now

This should ensure that we have the latest and greatest deployed to the board.

Firmware Update and Setting the PCIe Mode

The first two steps are common to both the bootable scenario and this scenario. The firmware will need to be checked and if necessary updated and then experimental PCIe mode enabled.

Firstly, choose the boot ROM version and set this to the latest and reboot the system. The following command will configure the system to use the latest boot ROM.

sudo raspi-config nonint do_boot_rom E1 1
sudo reboot now

Further information on using the raspi-config tool in command mode can be found in the Raspi-Config documentation. Note however, that at the time of writing the documentation was slightly out of date as there was no mention of the need for the number 1 at the end of the command. This is required to answer No to the question about resetting the bootloader to the default configuration.

Next up, check the bootloader version and update this if necessary by using the rpi-eeprom-update command:

sudo rpi-eeprom-update

This generated the following output:

BOOTLOADER: up to date
   CURRENT: Fri 16 Feb 15:28:41 UTC 2024 (1708097321)
    LATEST: Fri 16 Feb 15:28:41 UTC 2024 (1708097321)
   RELEASE: latest (/lib/firmware/raspberrypi/bootloader-2712/latest)
            Use raspi-config to change the release.

PCIe Experimental Mode (Optional)

The last step is optional and turns on an experimental high speed feature, namely PCIe mode 3. Edit the /boot/firmware/config.txt file and add the following to the end of the file:

[all]
dtparam=pciex1_gen=3

This step is optional and without this entry the system will run in PCIe mode 2.

Reboot

So the boot loader is up to date, time for another reboot using the command sudo reboot now.

Mounting the Drive

We should now be at the point where the system has the correct configuration to allow access to the drive mounted to the NVMe Base. These drives should be in the /dev directory:

ls /dev/nv*

shows:

/dev/nvme0  /dev/nvme0n1

This looks good, as the drive shows up as the two devices. Now we need to put a file system on the drive, the following formats the drive using the ext4 file system:

sudo mkfs.ext4 /dev/nvme0n1 -L Data

For the 500GB ADATA drive supplied with the NVMe Base kit, this command generates the following:

mke2fs 1.47.0 (5-Feb-2023)
Discarding device blocks: done
Creating filesystem with 125026900 4k blocks and 31260672 inodes
Filesystem UUID: 008efe62-93f2-4875-bf52-5843953db8d0
Superblock backups stored on blocks:
	32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
	4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968,
	102400000

Allocating group tables: done
Writing inode tables: done
Creating journal (262144 blocks): done
Writing superblocks and filesystem accounting information: done

Next up we need to mount the file system and make it accessible to the user. Assuming the default (current) user is pi and the user is in the group pi then the following commands will make the drive available to the current session:

sudo mkdir /mnt/nvme0
sudo chown -R pi:pi /mnt/nvme0
sudo mount /dev/nvme0n1 /mnt/nvme0

Firstly, we make the mount point for the partition and then ensure that the pi user has access to the mount point. Finally we mount the partition /dev/nvme0n1 as /mnt/nvme0. The availability of the drive can be checked with the df command, running:

df -h

should result in something like:

Filesystem      Size  Used Avail Use% Mounted on
udev            3.8G     0  3.8G   0% /dev
tmpfs           806M  5.2M  801M   1% /run
/dev/sda2       229G  1.8G  216G   1% /
tmpfs           4.0G     0  4.0G   0% /dev/shm
tmpfs           5.0M   48K  5.0M   1% /run/lock
/dev/sda1       510M   63M  448M  13% /boot/firmware
tmpfs           806M     0  806M   0% /run/user/1000
/dev/nvme0n1    469G   28K  445G   1% /mnt/nvme0

Access should now be checked by copying some files or creating a directory on the drive.

Mounting the Partition Automatically

The final step to take is to mount the partition and make the file system available as soon as the system boots. If the Raspberry Pi is rebooted now and the df -h command is run then something like the following is shown:

Filesystem      Size  Used Avail Use% Mounted on
udev            3.8G     0  3.8G   0% /dev
tmpfs           806M  5.2M  801M   1% /run
/dev/sda2       229G  1.8G  216G   1% /
tmpfs           4.0G     0  4.0G   0% /dev/shm
tmpfs           5.0M   48K  5.0M   1% /run/lock
/dev/sda1       510M   63M  448M  13% /boot/firmware
tmpfs           806M     0  806M   0% /run/user/1000

Note that the drive /mnt/nvme0 has not appeared in the list of file systems available. Executing ls /dev/nvm* shows the device is available but it has not been mounted.

The first step in mounting the drive automatically is to find the unique identified (UUID) for the device using the command:

sudo blkid /dev/nvme0n1

This resulted in the following output:

/dev/nvme0n1: LABEL="Data" UUID="008efe62-93f2-4875-bf52-5843953db8d0" BLOCK_SIZE="4096" TYPE="ext4"

Make a note of the UUID shown for your drive. The final step is to add this to the /etc/fstab file, so edit this file with the command:

sudo nano /etc/fstab

and add the following line to the end of the file:

UUID=008efe62-93f2-4875-bf52-5843953db8d0 /mnt/usb1 ext4 defaults,auto,users,rw,nofail,noatime 0 0

Remember to substitute the UUID for the drive on your system for the UUID above.

Time for one final reboot of the system and log on to the Raspberry Pi. Check the availability of the file system with the df -h command. The drive should now be available and accessible to the pi user as well as any users in the pi group.

Conclusion

The NVMe Base boards offer a way to add M-key NVMe SSD drives to the Raspberry Pi using PCIe gen 2 and even experimental access using PCIe gen 3. This results in high speed access to data stored on these drives as well as increased reliability over SD card boot devices.

The standard installation documentation is excellent but it assumes that the NVMe Base is going to host the OS as well as user data on the drive installed on the NVMe base. It also makes the assumption that the user will be using the Raspberry Pi desktop and not running in a headless situation.

Hopefully the above shows how to install the NVMe Base and SSD drive as a data storage only media with a second drive acting as the OS boot device.

In the next post we will look at taking the above steps and automating them to allow for reliable, repeated installations.

Meadow OLED PCBs

Sunday, January 28th, 2024

Meadow OLED Board Banner Image

A few weeks ago I promised an update on my experience using the PCBWay and Round Tracks plugins for KiCAD.

Well the boards are back and I must say they are looking good.

Rounded Tracks Plugin

The rounded tracks plugin certainly gives the 1970s feel to the PCBs. The batch ordered had a gloss black finish and this made the rounded effect a little difficult to see, a matt finish may have looked better or maybe even a green PCB for that real 1970s vibe.

This plugin has seen a little more use since the first order and here are a few things I have picked up:

  • Apply teardrops after using the plugin
  • Keep the original PCB layout

Adding teardrops to the board really does give the retro feel to the board. I have found that rounding the tracks after adding teardrops can leave a disjoint connection between the track at the teardrop connection to the pad.

So here is a section of the board with the rounded tracks applied after the teardrops:

Rounded Track Applied After Teardrop

Rounded Track Applied After Teardrop

This shows that the track exits the corner of the teardrop which is not ideal. Next we have the same pad with the rounded tracks applied before the teardrops:

Rounded Track Applied Before Teardrop

Rounded Track Applied Before Teardrop

The second case is certainly more aesthetically pleasing.

The plugin asks if you want to apply the changes to the current PCB or if it should create a copy. I went for the halfway house and applied the changes to the PCB, reviewed and ordered the boards and then reverted the changes. This worked well as the plugin ran in under a second and allowed the retention of the original design. It is always going to be easier to apply any changes to the PCB on a board with angular tracks than it is to apply the changes to a board with rounded tracks.

PCBWay Plugin

This plugin really made ordering the PCBs a dream. The only thing to remember is to login to your PCBWay account before attempting to use the plugin. If you do this the plugin will create and upload a ZIP file with the gerbers directly to your PCBWay account. This allows the plugin to set all of the parameters for board dimensions and layers all seamlessly.

Finished Boards

Here is a photo of the finished board connected to a Meadow F7 Micro board running a sok test:

Meadow and OLED Display

Meadow and OLED Display

Conclusion

The Rounded Tracks plugin is only really of interest if you want the retro 1970 look and feel to the final PCB. The PCBWay plugin is really useful as it streamlines the ordering process and removes the need to manually create the gerber files and upload them to the PCBWay website.

Trying Some New KiCad Plugins

Saturday, December 16th, 2023

Meadow PCB Header

A recent change to a PCB design has given the opportunity to try out a couple of KiCad plugins. The board is a simple one and is used to provide feedback when running network soak tests. The board is designed for the Meadow ecosystem and is a fairly simple design consisting of:

  • SSD1306 OLED display
  • Indicator LEDs
  • Reset button
  • Headers to connect the board to a Meadow board

The board has been tested and it works well. The changes doubled the number of LEDs and changed some of the components footprints.

Round Tracks

The concept behind Round Tracks is simple, take a PCB layout and give the tracks the feel of the 1970s. It does this by taking the layout and looking for any tracks that change direction. The plugin then takes these tracks and rounds the corners.

Applying this to the OLED PCBs gives the following output:

Meadow OLED PCB Feather

PCBWay Plug-in for KiCad

The next plugin is the PCBWay plugin. This should take the PCB layout and generate a zip file to upload to the PCBWay web site for manufacture. It appears to be really simple to use and starts an order for you and then uploads the files into the order.

Order submitted and now we just have to wait for the PCBs to arrive.

Conclusion

Manufacturing is complete and they are now on their way. There is going to be a small wait while the PCBs make their way from China to the UK and then we can see how the plugins faired.

Update to follow in a few weeks.

Python Oscilloscope Control

Friday, December 1st, 2023

Python Scope Control

For the last year or so, the goto scope in the lab has been a Rigol MSO5104. This is a 100MHz, 4 channel scope with 16 digital channels. Connectivity options include USB and wired LAN. The LAN connection allows the scope to be controller via a web control panel which is great for interactive control from a host computer but is not much use for automation.

Enter pyvisa.

Hackaday Article

Hackaday published an article Grabbing Data From a Rigol Scope with Python in late November of 2023. It appears that I am not the only one looking to control a Rigol oscilloscope from a host computer.

The article discusses using pyvisa to communicate with a DHO900-series scope not a MSO5000-series scope, let’s see if the same commands will work with the MSO5104.

Communicating With The MSO5104

First up, try to connect to the scope over the LAN:

import pyvisa
visa = pyvisa.ResourceManager('@py')
scope = visa.open_resource('TCPIP::192.168.1.95::INSTR')

So that seemed to work, so let’s try to get the scope to identify itself:

scope.query('*IDN?')
'RIGOL TECHNOLOGIES,MSO5104,MS5A*********,00.01.03.03.00\n'

So far, so good, the * characters represent part of the devices serial number. Now let’s try a few commands to see if we can make the scope to do something:

scope.write(':SINGLE')
9

This changes the mode of the scope to single shot and the Single button turned on as if it had been pressed. The return value is the number of characters sent to the scope.

Conclusion

It appears that the original Hackaday article is going to be part of a series of articles as a follow-up article was published a few days later (see Scope GUI Made Easier). This could turn into something useful.

Getting Started with Ansible

Monday, August 28th, 2023

20x4 LCD Display

Recent work has involved reviewing some test environments for an IoT development board. The aim is to improve some of the components used for testing as well as adding new functionality. The requirements are:

  • Provide an updated version of existing functionality
  • Single board environment with all functionality deployed for quick testing
  • Cluster distributing the test environment for load testing

The most cost effective way to do this is to use a number of Raspberry Pi single board computers. These boards are now becoming available in quantities after several years of limited availability.

The Problem

How to setup the environment in such a way that will allow a fresh environment to be created reliably.

Enter ansible.

Ping

First step, try to contact a board and this is where ping comes in. This command will verify that ansible can connect to a board. The following command will test the connection to each board:

ansible cluster -m ping -i hosts

This command requires a text file hosts containing the list of boards to the contacted. The file is simple and may only contact two lines:

[cluster]
node

In the above example, the file defines a group of machines to be contacted and this is named cluster and in this case the group contains only one machine and this is named node. The name cluster is also mentioned in the ansible command above.

Additional machines can also be named under the cluster entry by simply placing additional entries on a new line in the file.

So far this is nothing new and it is covered in the Ansible documentation.

What Happened

The first step was to use the Raspberry Pi Imager application to create a new image on a new SSD. Nothing complex:

  • Raspberry Pi 64-bit Lite OS
  • Set the machine name to be node
  • Enable SSH
  • Set the user name to clusteruser and give the user a secure password

The password was then stored on the local machine in an environment variable CLUSTER_PASSWORD to allow the scripts to be stored in source control without giving away any secrets.

Time to test the connection with the following command:

ansible cluster -m ping -i hosts --extra-vars "ansible_user=clusteruser ansible_password=$CLUSTER_PASSWORD"

Breaking this down, we want to ping all of the machines defined in the cluster group. The group is defined in the file hosts and we are going to log on to the machines with the user name clusteruser and with the password contained in the CLUSTER_PASSWORD environment variable.

Now running the above command results in the following:

node | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

Conclusion

A good start to the project, now on to something more complex, time to install and configure some software.

And I can’t believe I’ve missed Ansible for so long.

Oscilloscope Firmware Update

Saturday, July 29th, 2023

Oscilloscope Buttons

It may appear at the start of his story that this is an unhappy tale, but bear with it, there is a happy ending.

Having an oscilloscope when working with electronics is a must even for hobbyists. The amount of information you can gather about the behaviour of a circuit is invaluable. The daily workhorse here is the Rigol MSO5104 Mixed Signal Oscilloscope.

This scope is really a small computer and like all computers a regular check for firmware updates is always a good idea. A recent visit to the Rigol web site resulted in a new firmware version downloaded to the laptop.

Time to Install

Installation should be a quick process:

  • Extract the files on the PC
  • Copy the firmware file (.gel file) to a USB drive
  • Connect the USB drive to the oscilloscope and select Local Update from the System menu

Following the process resulted in the update dialog being shown with a progress bar followed by instructions to restart the oscilloscope.

Power cycling the oscilloscope presented the expected Rigol logo along with a progress bar, so far, so good. The boot continued until just before the end of the process bar and then it stopped.

Wait a little while longer.

No movement after 5 minutes, time to power cycle. The same thing happened. Rinse and repeat, still no progress.

Time to call the local distributor.

Recovery

A call to the local Rigol distributor in the UK was in order. I found their technical support really good. In fact it was FAN-TAS-TIC. The solution turned out to be fairly simple.

  • Power off the oscilloscope
  • Turn the oscilloscope on again
  • As the oscilloscope starts press the Single button repeatedly

Doing this would eventually show the Rigol logon on the screen with two options:

  • Upgrade Firmware
  • Recover Defaults

Selecting Recover Defaults took us through the recovery process and after a few minutes restoring the configuration and restarting the oscilloscope it was back up and running again.

Conclusion

Computers are all around us running in everything including the modern oscilloscope. Rigol has thoughtfully added a recovery mechanism into the firmware update process and so today all was not lost.

A call out to the UK distributors, the support was really, really good.

20×4 LCD Display and NuttX

Sunday, July 23rd, 2023

20x4 LCD Display

Time for some more experimentation with NuttX, today serial LCDs.

Serial LCDs

Small LCD displays can be found in many scientific instruments as they provide a simple way to display a small amount of information to the user. Typically these displays are 16×2 (2 lines of 16 characters) or 20×4 (4 lines of 20 characters) displays. The header to this article shows part of the output from a 20×4 display.

Communication with these displays is normally through a 4 or 8 bit interface when talking directly to the controller chip for the LCD. Using 4 or 8 data lines for communication with the LCD is a large burden on a small microcontroller. To overcome this, several vendors have produced a backpack that can be attached to the display. The backpack uses a smaller number of microcontroller lines for communication while still being able to talk to the LCD controller chip.

This post looks at using such and LCD and backpack with NuttX running on the Raspberry Pi Pico W.

NuttX Channel (Youtube)

A great place to start with NuttX is to have a look at the NuttX Channel on Youtube as there are a number of quick getting started tutorials covering a number of subject. In fact there is one covering a 16×2 LCD display which is similar to what will be used in this tutorial, with a small difference.

The video linked above covers a lot of what is needed to get 16×2 LCD up and running. There are some small changes that are needed as NuttX has moved on since the video was released.

Hardware

The major changes compared to the video above are:

  • Microcontroller used will be the Raspberry Pi Pico W
  • LCD display will be 20×4 display

The LCD display used here will be a larger physical display (20×4 instead of 16×2) but it will still use the same interface on the backpack, namely the PCF8574 GPIO expander. This uses I2C as a communication protocol so reduces the number of GPIO lines required from 8 to 2.

There are two I2C busses on the Pico W, I2C0 and I2C1 and in this case I2C1 will be the chosen interface. Something that caused some issues but more on that later.

For now we start with a base configuration with the LCd connected to GPIO6 and GPIO7 on the Pico W.

Configuring NuttX

Configuration followed much of the video linked above enabling the following:

  • Enable I2C master and I2c1 in the System Type menu
  • I2C Driver Support in the Device Drivers menu
  • PCF8574 support through Device Drivers, Alphanumeric Drivers menus
  • Segment LCD CODEC in the Library Routines menu
  • Segment LCD Test in the Application Configuration, Examples menu

Time to build, deploy and run.

  • make clean followed by make -j built the system
  • The application was then deployed to the Pico W and board was reset
  • The application can be run by connecting a terminal/serial application to the board an running the command slcd

Nothing appears on the display. Time to check the connections and break out the debugger.

Troubleshooting

Checking the connections showed that everything looked to be in order. The display was showing a faint pixel pattern which is typical of these displayed when they have been powered correctly but there is no communication. Double checking the I2C connections showed everything in theory was good.

Over to the software. Running through the configuration again and all looks good here. So lets try I2C0 instead of I2C1, quick change of the configuration in the software and moving some cables around and it works!

So lets go back to the I2C1 configuration, recompile and deploy to the board and it works. What!

It turns out that I had not moved the connections from I2C0 back to I2C1.

The default application was also only displaying 1 line of text. So let’s expand this to display 4 lines of text, namely:

  • Hello
  • Line1
  • Line2
  • Line3

Running the application gives only two line of text:

  • Hello
  • Line3

How odd.

Lets Read the Sources

After a few hours of tracing through the sources we find ourselves looking in the file rp2040_common_bringup.c where there is this block of code:

#ifdef CONFIG_LCD_BACKPACK
    /* slcd:0, i2c:0, rows=2, cols=16 */

    ret = board_lcd_backpack_init(0, 0, 2, 16);
    if (ret < 0)
    {
        syslog(LOG_ERR, "Failed to initialize PCF8574 LCD, error %d\n", ret);
        return ret;
        return ret;
    }
#endif

This suggests that the serial LCD test example is always configured to use a 16×2 LCD display on I2C0. This explains why we saw only two lines of output on the display and also why the code did not work on I2C1.

Changing ret = board_lcd_backpack_init(0, 0, 2, 16); to ret = board_lcd_backpack_init(0, 1, 4, 20); and recompiling generated the output we see at the top of this post.

Navigating to the System Type menu also allowed the I2C1 pins to be changed to 26 and 27 and the system continued to generate the expected results.

Conclusion

This piece of work took a little more time than expected and it would have been nice to have had the options within the configuration system to change the display and I2C parameters. As it stands at the moment using a 20×4 display requires changes to the OS source code. This is a trivial change but it does make merging / rebasing with later versions of the system a little more problematic.