On our AB Electronics website we always had a primary focus on the Raspberry Pi when developing the development boards but that does not mean we ignored all the other small form factor that have been released.

We tested the Raspberry Pi development boards on a wide range of computer platforms from companies like Orange Pi, Odroid and Asus and if they work together, we add them to the compatibility list on each of the product pages.

One new computer that recently caught our eye is the Radxa X2L. Radxa are best known for their Rock series of ARM based computers that share many characteristics of the Raspberry Pi. For the X2L Radxa took a different approach.

Radxa X2L
Radxa X2L with ADC Pi board fitted to the 40 pin GPIO header

The Radxa X2L is a compact yet powerful computer designed for various applications, from desktop computing to IoT projects. The board is physically bigger than the Raspberry Pi, around 3 times larger and it is powered by a Intel® Celeron® J4125 Processor. Being Intel x64 means it can natively run Windows 10 or the wide range of Linux distributions that are available for x64 based computers.

One of the distinguishing features is the inclusion of the RP2040 microcontroller to control its GPIO header. The RP2040, developed by Raspberry Pi, has a dual-core ARM Cortex-M0+ processor, making it suitable for real-time tasks and peripheral control.

Adding analogue inputs to the Radxa X2L

Radxa X2L
Radxa X2L with ADC Pi board fitted to the 40 pin GPIO header

This setup presents a unique challenge for integrating devices like the ADC Pi, an 8 channel analogue to digital converter, which rely on I2C digital communications. The RP2040's microcontroller architecture differs significantly from the typical direct Linux control over GPIO pins found in other single-board computers. This difference necessitates a bridge between the microcontroller environment and the Linux system to communicate with peripherals like the ADC Pi. We looked at several possible solutions to this interfacing problem before finding one that should work reliably and can be modified to suit individual needs.

To allow communication between the ADC Pi and the RP240 to the host processor on the Radxa X2L, we needed to deploy a microcontroller program on the RP2040. This program is responsible for directly interfacing with the ADC Pi, reading analogue values, and then sending these readings to the Linux system via the serial port in a structured format, such as JSON strings.

This approach allows for real-time data acquisition from the ADC Pi while leveraging the processing power and flexibility of the Radxa X2L's Linux environment for data processing, storage, or further actions.

RP2040 Code

The first step involves programming the RP2040 to communicate with the ADC Pi.

We used the ADC Pi MicroPython library for the Raspberry Pi Pico that is available from our GitHub repository at github.com/abelectronicsuk/ABElectronics_MicroPython_Libraries.

The pins for the I2C bus on the GPIO header normally found on pin 3 for SDA and 5 for SCL. On the RP2040 these are connected to pins 28 for SDA and 29 for SCL.

An example MicroPython script can be seen below.

#!/usr/bin/env python

"""
================================================
AB Electronics UK ADC Pi 8-Channel ADC demo for MicroPython Library

Run using Thonny Python IDE from https://thonny.org/

Create a file in Thonny called ADCPi.py, copy contents from ADCPi.py 
to the file and save it onto the Raspberry Pi Pico

Create a file named demo_readvoltage.py, copy the code from this file and save
onto the Raspberry Pi Pico

Run with "Run Current Command" or F5 in Thonny

================================================

Initialise the ADC device using the default addresses and sample rate,
change this value if you have changed the address selection jumpers

For the Radxa X2l computer use pins 28 for sda and 29 for scl

The sample rate can be 12, 14, 16 or 18

"""
import time
import json
from ADCPi import ADCPi

def main():
    '''
    Main program function
    '''
    # I2C addresses of 0x68 and 0x69, bit rate 12 with SDA on pin 28 and SCL on pin 29
    adc = ADCPi(0x68, 0x69, 12,28,29)

    while True:
        try:
            # get ADC values
            a1 = adc.read_voltage(1)
            a2 = adc.read_voltage(2)
            a3 = adc.read_voltage(3)
            a4 = adc.read_voltage(4)
            a5 = adc.read_voltage(5)
            a6 = adc.read_voltage(6)
            a7 = adc.read_voltage(7)
            a8 = adc.read_voltage(8)
            
            # create an array using the adc values
            data = [a1, a2, a3, a4, a5, a6, a7, a8]
            
            # Create the JSON object with the data array
            output_dict = {
                "board": "adcpi",
                "data": data
            }
            
            # Convert to JSON string
            json_string = json.dumps(output_dict)
            
            print(json_string)
            
            # wait 0.1 seconds between reads
            time.sleep(0.1)
        
        except:
            # show error message if the read fails
            output_dict = {
                "board": "adcpi",
                "error": "read error"
            }
            
            # Convert to JSON string
            json_string = json.dumps(output_dict)
            
            print(json_string)


if __name__ == "__main__":
    main()


Download picoadccode-py.txt and rename to picoadccode.py

An ADCPi object is used to set up I2C communication between the RP2040 and the ADC Pi. A while loop is used to initiate data reads from the ADC Pi's channels, and format these readings into JSON strings.

The program then writes these strings to the RP2040's serial output, effectively turning the microcontroller into a bridge between the analogue world and the digital, Linux-driven Radxa X2L.

On the Radxa X2L

On the Linux side, a Python script like the one shown below can be used to read the JSON strings from the serial port. This script continuously monitors the serial port for new data, decodes the JSON strings, and processes the analogue values for any application needs, from simple logging to real-time monitoring and control.

Here's a basic outline of such a Python script:

import json
import serial
import os
import sys
import time

# Function to clear the screen
def clear_screen():
    if os.name == 'nt':  # for Windows
        _ = os.system('cls')
    else:  # for macOS and Linux(here, os.name is 'posix')
        _ = os.system('clear')

# Setup serial connection
serial_port = '/dev/ttyACM0'
baud_rate = 9600  # Adjust this depending on your device

try:
    ser = serial.Serial(serial_port, baud_rate)
    print("Serial port opened successfully. Reading data...")
    buffer = ''  # Initialize a buffer to accumulate data

    while True:
        data_in = ser.read(ser.inWaiting() or 1)  # Read available data or block for one byte
        if data_in:
            # Decode byte to string and accumulate in buffer
            buffer += data_in.decode('utf-8')
            
            # Check if a complete message has been received (indicated by a newline)
            if '\n' in buffer:
                # Split buffer at the first newline; [0] is the complete message, [1] is the start of the next message
                complete_message, buffer = buffer.split('\n', 1)
                
                # Clear the screen before printing new values
                clear_screen()
                
                # Attempt to parse the JSON string
                try:
                    data_dict = json.loads(complete_message)
                    data_array = data_dict["data"]
                    
                    # Print each value in the data array as separate rows
                    for i, value in enumerate(data_array, start=1):
                        print(f"Channel {i}: {value}")
                except json.JSONDecodeError:
                    print("Error decoding JSON. Check the format of the incoming data.")

except serial.SerialException as e:
    print(f"Error opening serial port: {e}")
except KeyboardInterrupt:
    print("Program terminated by user.")
finally:
    ser.close()  # Ensure the serial port is closed on exit

Open readadc-py.txt and rename to readadc.py

Integrating the ADC Pi with the Radxa X2L via the RP2040 microcontroller showcases the flexibility and power of combining dedicated microcontrollers with Linux single-board computers. This setup not only overcomes the challenge of reading analogue inputs on devices without native ADCs but also opens a plethora of possibilities for advanced IoT applications, data acquisition, and control systems.

By using the microcontroller for real-time tasks and the Linux environment for higher-level processing you can build complex, efficient, and powerful systems capable of bridging the analogue and digital worlds.