TS-4300

From embeddedTS Manuals

This product is under engineering sampling, and is still under development.

This product supports applying firmware updates to the FPGA and onboard microcontroller (Wizard) so many features will be provided as updates in the field as they are completed.

Feature Status Notes
Ethernet Working Tested iperf on both ports to validate 1Gb/s. Tested nfs booting, no issues seen with Ethernet traffic. Some features like PTP are currently not tested.
eMMC Working Preliminarily Tested. The bootloader is loaded from mmc0 boot partition 0. Tested booting to Linux and reading/writing.
microSD Working Preliminarily Tested
USB Host Ports Working Tested USB drives enumerate and read/write data.
RTC Working Verified this RTC from the wizard holds and retains time.
FPGA PWM controller Working Preliminarily Tested, final PWM count will change once remaining fpga core are implemented
Bluetooth Working Preliminarily Tested, verified bluetoothctl scanning & discoverable
GPIO Working Tested CPU IO and FPGA controllers to be working, and support interrupts on rising/falling/both edges.
WIFI Testing Preliminarily working, but feature improvements for next hardware revision.
Serial Ports TODO UARTs are working, not yet tested for carrier board transceivers
CAN TODO
SPI TODO
FPGA XBAR TODO This will be provided as an FPGA update, and Linux driver update. This will allow switching around various FPGA interfaces to differenet FPGA pins.
TS-SILO Supercap Backup TODO The TS-SILO will provide backup power after power has been lost, which provides time to properly shutdown and unmount drives after power has been lost. This has been preliminarily tested in the hardware and will be provided as a future Wizard update, and Linux driver update.
i.MX93 low power sleep TODO This is the low power mode in the cpu where we can suspend to ram. This requires further documentation / testing
Wizard low power sleep TODO This feature will allow powering off the i.MX93, FPGA, and most ICs, but allow waking on RTC alarm, push button, and planned on a future PCB revision is movement from the accelerometer/gyro.
i.MX93 Low drive mode TODO This lower performance / operating mode that allows lower runtime power. This requires further changes in the FPGA/linux to implement.
i.MX93 M33 TODO This is the ARM Cortex-M33 microcontroller built into the i.MX93, allowing users to run some code in an RTOS like Zephyr or FreeRTOS. This is expected to be a u-boot configuration and linux change only
i.MX93 NPU TODO This acceleration uses the M33 which must be working first. This should be available as a software configuration once the M33 example is working.
Image Replicator TODO This is the USB tool to rewrite the image on production units. This product support will be added in the future.
Open Source FPGA TODO The FPGA is planned to be open sourced, and will be after some additional features are stabilized.
TS-4300
ts-4300.gif
Product Page
Documentation
Schematic
FTP Path
Processor
NXP i.MX9352
1.7 GHz Arm® Cortex®-A55/M33
i.MX93 Product Page
CPU Documentation

Overview

Getting Started

U-Boot

Debian

Debian 12 - Bookworm

Debian 12 - Getting Started

This Debian release is available in 3 flavors with various packages.

Image Estimated Size Description
tsimx9-debian-12-bookworm-x11-latest.tar.xz 1233 MiB
  • Includes 6.6 kernel with ts9370_defconfig that includes broad driver support
  • Base Debian with common utils
  • Common embedded tools (i2c, can, gpio, iio, serial tools, etc)
  • Includes hardware support
  • Networking tools (ethernet, wifi, bluetooth)
  • Includes Development tools
  • Includes X11 that launches matchbox and xterm on startup
  • Includes touchscreen support
tsimx9-debian-12-bookworm-headless-latest.tar.xz 961 MiB
  • Includes 6.6 kernel with ts9370_defconfig that includes broad driver support
  • Base Debian with common utils
  • Common embedded tools (i2c, can, gpio, iio, serial tools, etc)
  • Includes hardware support
  • Networking tools (ethernet, wifi, bluetooth)
  • Includes Development tools
tsimx9-debian-12-bookworm-minimal-latest.tar.xz 379 MiB
  • Includes 6.6 kernel with ts9370_defconfig that includes broad driver support
  • Includes base Debian rootfs adding only what is required for Ethernet support.

The default login is root with no password.

To write this to an SD card, first partition the SD card to have one large ext4 partition. Once it is formatted, extract this tar with:

# Assuming your SD card is /dev/sdc with one partition
mkfs.ext4 /dev/sdc1
mkdir /mnt/sd/
sudo mount /dev/sdc1 /mnt/sd/
sudo tar --numeric-owner -xJf tsimx9-debian-12-bookworm-x11-latest.tar.xz -C /mnt/sd
sudo umount /mnt/sd
sync

To rewrite the eMMC, boot to the SD card. You cannot rewrite the emmc while it is mounted elsewhere, or used to currently boot the system. Once booted to the SD, run:

mkfs.ext3 /dev/mmcblk2p1
mkdir /mnt/emmc
mount /dev/mmcblk2p1 /mnt/emmc
wget -qO- https://files.embeddedts.com/ts-arm-sbc/ts-9370-linux/distributions/debian/ tsimx9-debian-12-bookworm-x11-latest.tar.xz | tar --numeric-owner -xJ -C /mnt/emmc/
umount /mnt/emmc
sync

Debian 12 - Networking

The network in Debian is configured with /etc/network/interfaces. For complete documentation, see Debian's documentation here

Some common examples are shown below. On this release network interfaces follow the predictible network interface names. Run ip addr show to get a list of the network interfaces.

Most commonly:

  • end0 - Ethernet device 0 (CPU Ethernet)
  • enp1s0 - Ethernet PCIe port 1 slot 0 ethernet
  • usb<mac> - USB ethernet
  • wlan0 - WIFI

DHCP on end0. Edit the file /etc/network/interfaces and add:

auto end0
allow-hotplug end0
iface end0 inet dhcp

Static IP on end0. Edit the file /etc/network/interfaces and add:

auto end0
iface end0 inet static
    address 192.0.2.7/24
    gateway 192.0.2.254

These will take effect on the next boot, or by restarting the networking service:

service networking restart

Debian 12 - WIFI Client

Wireless interfaces are also managed with configuration files in "/etc/network/interfaces.d/". For example, to connect as a client to a WPA network with DHCP. Note some or all of this software may already be installed on the target SBC.

Install wpa_supplicant:

apt-get update && apt-get install wpasupplicant -y

Run:

wpa_passphrase youressid yourpassword

This command will output information similar to:

 network={
 	ssid="youressid"
 	#psk="yourpassword"
 	psk=151790fab3bf3a1751a269618491b54984e192aa19319fc667397d45ec8dee5b
 }

Use the hashed PSK in the specific network interfaces file for added security. Create the file:

/etc/network/interfaces.d/wlan0

allow-hotplug wlan0
iface wlan0 inet dhcp
    wpa-ssid youressid
    wpa-psk 151790fab3bf3a1751a269618491b54984e192aa19319fc667397d45ec8dee5b

To have this take effect immediately:

service networking restart

For more information on configuring Wi-Fi, see Debian's guide here.

Debian 12 - WIFI Access Point

First, hostapd needs to be installed in order to manage the access point on the device:

apt-get update && apt-get install hostapd -y


Note: The install process will start an unconfigured hostapd process. This process must be killed and restarted before a new hostapd.conf will take effect.

Edit /etc/hostapd/hostapd.conf to include the following lines:

interface=wlan0
driver=nl80211
ssid=YourAPName
channel=1
Note: Refer to the kernel's hostapd documentation for more wireless configuration options.


To start the access point launch hostapd:

hostapd /etc/hostapd/hostapd.conf &

This will start up an access point that can be detected by WIFI clients. A DHCP server will likely be desired to assign IP addresses. Refer to Debian's documentation for more details on DHCP configuration.

Debian 12 - Installing New Software

Debian provides the apt-get system which allows management of pre-built applications. The apt tools require a network connection to the internet in order to automatically download and install new software. The update command will download a list of the current versions of pre-built packages.

apt-get update

A common example is installing Java runtime support for a system. Find the package name first with search, and then install it.

root@tsa38x:~# apt-cache search openjdk
default-jdk - Standard Java or Java compatible Development Kit
default-jdk-doc - Standard Java or Java compatible Development Kit (documentation)
default-jdk-headless - Standard Java or Java compatible Development Kit (headless)
default-jre - Standard Java or Java compatible Runtime
default-jre-headless - Standard Java or Java compatible Runtime (headless)
jtreg - Regression Test Harness for the OpenJDK platform
libreoffice - office productivity suite (metapackage)
openjdk-11-dbg - Java runtime based on OpenJDK (debugging symbols)
openjdk-11-demo - Java runtime based on OpenJDK (demos and examples)
openjdk-11-doc - OpenJDK Development Kit (JDK) documentation
openjdk-11-jdk - OpenJDK Development Kit (JDK)
openjdk-11-jdk-headless - OpenJDK Development Kit (JDK) (headless)
openjdk-11-jre - OpenJDK Java runtime, using Hotspot JIT
openjdk-11-jre-headless - OpenJDK Java runtime, using Hotspot JIT (headless)
openjdk-11-jre-zero - Alternative JVM for OpenJDK, using Zero
openjdk-11-source - OpenJDK Development Kit (JDK) source files
uwsgi-app-integration-plugins - plugins for integration of uWSGI and application
uwsgi-plugin-jvm-openjdk-11 - Java plugin for uWSGI (OpenJDK 11)
uwsgi-plugin-jwsgi-openjdk-11 - JWSGI plugin for uWSGI (OpenJDK 11)
uwsgi-plugin-ring-openjdk-11 - Closure/Ring plugin for uWSGI (OpenJDK 11)
uwsgi-plugin-servlet-openjdk-11 - JWSGI plugin for uWSGI (OpenJDK 11)
java-package - Utility for creating Java Debian packages

In this case, the wanted package will likely be the "openjdk-11-jre" package. Names of packages can be found on Debian's wiki pages or the packages site.

With the package name apt-get install can be used to install the prebuilt packages.

apt-get install openjdk-11-jre
# More than one package can be installed at a time.
apt-get install openjdk-11-jre nano vim mplayer

For more information on using apt-get refer to Debian's documentation here.

Debian 12 - Setting up SSH

Openssh is installed in our default Debian image, but by default openssh does not permit root logins, and requires a password to be set. Additionally, a host key is required if one hasn't already been created on the target board. To allow remote root login:

sed --in-place 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
systemctl restart ssh.service
passwd root # Set any password

If you ssh to this system it will now support ssh as root.

Debian 12 - Starting Automatically

A systemd service can be created to start up headless applications. Create a file in /etc/systemd/system/yourapp.service

[Unit]
Description=Run an application on startup

[Service]
Type=simple
ExecStart=/usr/local/bin/your_app_or_script

[Install]
WantedBy=multi-user.target

If networking is a dependency add "After=network.target" in the Unit section. Once you have this file in place add it to startup with:

# Start the app on startup, but will not start it now
systemctl enable yourapp.service

# Start the app now, but doesn't change auto startup
systemctl start yourapp.service
Note: See the systemd documentation for in depth documentation on services.

Debian 12 - Cross Compiling

Bookworm aarch64 cross compile docker

Debian 12 - Compile the aarch64 Kernel

Aarch64 kernel compile guide

Ubuntu

Buildroot

Backup / Restore

While all of our products ship with images pre-loaded in to any supplied media, there are many situations where new images may need to be written. For example, to restore a device to its factory settings or apply a customized image/filesytem for application deployment. Additionally, specific units may be used for development and that unit's disk images need to be replicated to other units to be deployed in the field.

We offer a number of different ways to accomplish both capturing images to be written to other units, and the actual writing process itself. See the sections below for details on our USB Image Replicator tool to capture and/or write images, as well as details on manual processes to capture and write images on each of this device's media.


Image Replicator

This platform supports our Image Replicator tool. The Image Replicator tool is intended for use by developers as a means to write bootable images or filesystems on to a device's media (SD / eMMC / SATA / etc.) as part of their production or preparation process. In addition to writing media, the Image Replicator tool is capable of capturing images from a device's media and preparing them to be written to other devices.

The Image Replicator tool is a USB disk image that can be booted on a target device to capture or write its media directly without the need for a host workstation. The USB disk image is based on Buildroot and contains a set of scripts which handle the capture and write process. The process and its scripts are flexible and can be used as-is or adapted in to larger production processes to format and load data on to devices. The single USB drive can be used to capture images from a device, and then can be inserted in to other devices to write those same images on to other devices. The capture process is not necessary if it is not needed. Images for the target device can be copied to the USB drive, booted on compatible units, and have the target images written to that unit's media.


Image Capture Process

The image capture process performs the following steps. For more detailed information, see the Image Capture section below.

  1. If no valid images exist on the disk, image capture starts.
  2. For each valid media present on the unit, a bit for bit copy of the source is made.
  3. This image is mounted, sanitized (to remove unneeded files and allow safe copying of the image to other units), and saved as either a disk image or a tarball depending on the partition layout of the source disk.
  4. All images and tarballs are compressed, with both the output files having their MD5 hash saved as well as all of the files contained in the root partition having their MD5 hashes saved to a file for later verification.

The captured images and tarballs are named such that the USB Image Replicator disk can be immediately used to boot another unit and have it perform the Image Write process to write that unit's media with the captured images.

Note: When using this process, the USB drive used for the Image Replicator must be sized large enough to handle multiple compressed images as well as the uncompressed copy of the media image actively being worked with. If the image capture process runs out of space, the process will indicate a failure.


Image Write Process

The image write process performs the following steps. For more details information see the Image Write section below.

  1. For each valid media present on the unit, find the first valid source image file for it.
  2. If a source image exists for a media that is not present on the unit, then the process indicates a failure.
  3. If the source image is a tarball, format the target disk with an appropriate filesystem, and unpack it to the target disk, verifying all files against the MD5 hash file list after they are written.
  4. If the source image is a disk image, write that to the target disk. If an MD5 file for the disk image exists, read back the written disk and compare it to the hash.


Creating a USB Image Replicator Disk

Image Replicator USB disk images can be found below:

Disk image: <-- ts7250v3-usb-image-replicator.dd.xz -->

Tarball: <-- ts7250v3-usb- --image-replicator-rootfs.tar.xz -->

Two types of USB Image Replicator images are available for this platform, a tarball and an actual disk image. They both have the same contents and are intended to provide different methods to write the Image Replicator tool to a USB disk.

Disk Image (.dd.xz)
The disk image is easier to write from different workstation OSs, will auto-expand to the full disk length on its first boot, and is intended to be used for image capture (and later image writing) due to its small size and auto-expansion process. We recommend this route for users who may not have access to a Linux workstation or need to capture images from a golden unit first.
Tarball Image (.tar.xz)
The tarball image is easiest to write from a Linux workstation, but requires creating a partition table on the USB disk (if one does not already exist), formatting the filesystem, and unpacking the tarball. It can readily be used for for both image capture and writing, but is the easiest route when image capture is not needed due to the auto-expansion process.


Note: It is recommended to use USB drives with solid-state media for this process. Slower USB drives, especially those with spinning media, may take too long to enumerate and the bootloader will not boot the Image Replicator disk. Additionally, the use of low quality, damaged, and/or worn out USB drives may cause unexpected errors that appear unrelated to the USB drive itself. If there are issues using the Image Replicator, we recommend first trying a new, fresh, high-quality USB drive from a trusted named brand.


Disk Image

This process uses a small disk image that can be written to a USB device. This disk image uses an ext3 filesystem which expands on its first boot to the full length of the disk before beginning the image capture process. This disk is recommended for users who may not have access to a Linux workstation or who need to capture images from a golden unit.

It is possible to use the disk image for just image writing, however, in order to ensure full disk space is available it is recommended to write the disk image to a USB drive, set the IR_NO_CAPTURE_* Image Replicator Runtime Options, boot it on the target unit, let the system boot and expand the disk, insert the USB drive back in to a workstation, and then copy in the desired image files for the target unit from the workstation.


Writing Disk Image From a Linux Workstation

The disk image can be written via the command line with the dd command (replace /dev/sdX with the correct USB device node):

xzcat <platform>-usb-image-replicator.dd.xz > /dev/sdX

Graphical tools also exist for this purpose, for example balenaEtcher[1] offers this functionality.


Writing Disk Image From a Windows Workstation

A number of tools exist for writing an image to a USB drive, including (but not limited to) balenaEtcher[1] and Win32DiskImager[2]


Writing Disk Image From a MacOS Workstation

We recommend using a tool such as balenaEtcher[1] to write disk images.


  1. 1.0 1.1 1.2 embeddedTS is not affiliated with this tool. balenaEtcher version 1.5.101 tested in Windows 10 on 20220216
  2. embeddedTS is not affiliated with this tool. Win32DiskImager 1.0.0 tested in Windows 10 on 20220216. Cannot handle compressed images, must first decompress disk image.


Tarball

This process is easiest on a Linux workstation, but can be performed on other operating systems as well so long as they can support a compatible filesystem, the xz compression algorithm, as well as the tarball archive format. Note that in many cases, one of our computing platforms running our stock Linux image can be used if a Linux workstation is not available. After writing the tarball to a USB disk, the full length of the USB disk would be available to copy source images to in order to write them to other units.

The image replicator and scripts require a minimum of 50 MB; this plus the size of any target disk images or tarballs to be used dictates the minimum USB disk size required. The USB drive should have only a single partition, which is formatted ext2[1] / 3 / 4[2] or FAT32/vfat[3] Note that other filesystems are not compatible with U-Boot and therefore cannot be used.


Writing Tarball From a Linux Workstation

# This assumes USB drive is /dev/sdc:
sudo mkfs.ext3 /dev/sdc1
sudo mkdir /mnt/usb/
sudo mount /dev/sdc1 /mnt/usb/
sudo tar --numeric-owner -xf /path/to/<platform>-usb-image-replicator-rootfs.tar.xz -C /mnt/usb/
sudo umount /mnt/usb/


Writing Tarball From a Windows Workstation

It is recommended to use a third party tool, as native Windows archive tools have been observed to not work correctly. Tools such as 7-Zip[4] or PeaZip[5] are known working. It may also be possible to use Windows Subsystem for Linux following the Linux Workstation instructions above, but this has not been tested.

Note that some Windows tools may attempt to use the whole disk, rather than create a partition table. A partition table with a single partition is required for U-Boot support.

With a formatted USB disk, the archive can be unpacked to the root folder of the USB disk. Be sure to not unpack the tarball contents in to a new folder on the drive as this will not be bootable.


  1. The ext2 filesystem has a max file size limit as low at 16 GiB. This may cause issues for Image Capture.
  2. Use of ext4 may require additional options. U-Boot on some platforms does not support the 64-bit addressing added as the default behavior in recent revisions of mkfs.ext4. If using e2fsprogs 1.43 or newer, the options -O ^64bit,^metadata_csum may need to be used with ext4 for proper compatibility. Older versions of e2fsprogs do not need these options passed, nor are they needed for ext2 / 3.
  3. The FAT32 (supported by vfat in Linux) filesystem has a max file size limit of 4 GiB. This may cause issues for Image Capture.
  4. embeddedTS is not affiliated with this tool. 7-Zip 21.07 tested in Windows 10 on 20220222
  5. embeddedTS is not affiliated with this tool. PeaZip 7.2.0 tested in Windows 10 on 20220222

Running the Image Replicator Tool

Once a USB drive is formatted with the Image Replicator tool (see Creating a USB Image Replicator Disk for the correct files and process), boot to this USB drive (note that the Image Replicator already sets up the correct U-Boot boot scripts to boot to the USB drive, see the aforementioned section for details on how to make U-Boot call the scripts on the USB drive). This will start either image capture if no disk images/tarballs are present on the USB drive, or image write if there are disk images/tarballs present on the USB drive.


Image Replicator Runtime Options

Some of the runtime operations of the Image Replicator can be specified. These are handled by creating empty files in the root folder of the bootable USB drive. When booted, these files are analyzed and actions taken based on them.

Option Description

IR_NO_CAPTURE_SD

IR_NO_CAPTURE_SD1

IR_NO_CAPTURE_EMMC

IR_NO_CAPTURE_SATA

When capturing, skip media matching this name. See the respective platform manual for information on which names correspond to which physical media. Note that the names are generic and match what the media is captured as, regardless of actual device node. The names are uniform between capture and write for a given system.
IR_NO_COMPRESS When capturing, do not compress the data. On slower systems, slower disks, or systems with a large amount of data to capture, the final compression can take a significant amount of time. This option will allow a capture, but will not attempt to compress it.
IR_SHELL_ONLY When booting, skip doing any of the image replication process (Capture or Write) and instead drop to a login prompt. Useful for debugging. Note that, to prevent any confusion the system will indicate a non-critical failure when skipping any of the Image Replication process.


Image Replicator LED Status

The green and red LEDs of the platform are used to indicate status.

Any LED patterns not matching the table below indicate different operational states of the platform itself, e.g. executing the bootloader, the kernel is running but Image Replicator has not yet started, etc.

Green Red State Description
Short Strobe Solid Running The Image Replicator process is running.
0.5 Hz Blink Off Succeeded All operations being performed by the Image Replicator have completed successfully.
Off 0.5 Hz Blink Non-critical Failure One or more operations being performed by the Image Replicator have failed to complete. View logs in /tmp/logs/ as well as the failure reason in /tmp/failed
Off 4 Hz Blink Critical Failure An operation has failed in a way that could leave the device inoperable if power were to be removed. View logs in /tmp/logs as well as the failure reason in /tmp/failed.


Image Capture

If no valid images to write exist on the booted USB Image Replicator drive, the image capture process starts automatically.

Note that while in progress, the USB Image Replicator drive is mounted read-write. It is not advised to remove power or disconnect the USB Image Replicator drive until the whole process has completed.

To help diagnose failures, files in /tmp/logs/ contain output from each capture process.

For each media present on the unit (SD / eMMC / SATA / etc.), the image capture process will do the following:

  1. Copy the entire media image to an appropriately named file on the USB Image Replicator drive, e.g. sdimage.dd. No data is written to the source media and it is never mounted. The source disk can follow the stock partition layout, or implement a customized one.
  2. Perform an fsck on the Linux rootfs partition in the image file. Note that, if deviating from the standard partition layout, it may be necessary to modify the scripts handling the capture process.
  3. Mount the Linux rootfs partition from the image file and sanitize the filesystem. The sanitization process removes temporary files (e.g. /log/ files), unique files (e.g. ssh private key files, machine ID files), adds a file to indicate that it is a custom image with the date as its contents, etc. The full list of operations can be found in this script. It may be necessary to modify this file for unique situations.
  4. If the media's partition layout uses only a single partition, the filesystem is packed in to a tarball on the USB Image Replicator drive which is appropriately named and compressed, e.g. sdimage.tar.xz. The image file is then unmounted and removed from the USB Image Replicator drive.
  5. If the media's partition layout uses multiple partitions, the image file is then unmounted, an md5sum of the image file taken, it is compressed and appropriately named on the USB Image Replicator drive, e.g. emmcimage.dd.xz, and then an md5sum of the compressed image is taken.

Note that when using this process, the USB Image Replicator drive that is used must be sized large enough to handle multiple compressed images as well as the uncompressed copy of the media image actively being worked with. If the image capture process runs out of space, the process will indicate a failure via the LEDs.

The images files captured are saved to the root folder of the USB Image Replicator drive. Upon completion, it is safe to remove power or unplug the USB drive.

For more details on the image capture process, see this script.


Image Write

This process is used to write existing images to media on a target unit. If appropriately named disk images or tarballs (see table below) are present in the root folder of the USB Image Replicator drive when booted, then the startup scripts will start the image writing process. The latest disk images we provide for our platforms can be downloaded from our FTP site, see the backup and restore section for links to these files.

Note that the USB Image Replicator drive remains read-only through the entire process but target devices may be mounted or actively written. It is not advised to remove power or disconnect the USB Image Replicator drive until the whole process has completed.

To help diagnose failures, files in /tmp/logs/ contain output from each writing process.

The Image Replicator script expects disk images or tarballs to have specific names to match the target media. The script will attempt to match tarball and then disk image names (in the order they are listed in the table below) for each target media, using the first file that is found to write to the target media. Note that symlinks can be used on the USB Image Replicator disk if formatted with a filesystem that supports symlinks. This can be used, for example, to write the same tarball to both SD and eMMC from only a single copy of the source tarball.

Upon completion, it is safe to remove power or unplug the USB drive.

For more details on the image write process, see this script.

The following table is the list of valid file names and how they are processed:

Target media Accepted filenames Description
SD Card

/sdimage.tar.xz

/sdimage.tar.bz2

/sdimage.tar.gz

/sdimage.tar

Tar of the filesystem. This will repartition the SD card to a single partition and extract this tarball to the filesystem. If present, a file named /md5sums.txt in the tarball will have its contents checked against the whole filesystem after the tarball is extracted. This md5sums.txt file is optional and can be omitted, but it must not be blank if present. This file is present in our official images and is created during image capture with the Image Replicator tool.

/sdimage.dd.xz

/sdimage.dd.bz2

/sdimage.dd.gz

/sdimage.dd

Disk image of the media. This will be written to the SD card block device directly. If present on the USB Image Replicator drive, a file named /sdimage.dd.md5 will be used to verify the data written to the SD card against this checksum. This file is provided with our official images and is created during image capture with the Image Replicator tool.
eMMC

/emmcimage.tar.xz

/emmcimage.tar.bz2

/emmcimage.tar.gz

/emmcimage.tar

Tar of the filesystem. This will repartition the eMMC to a single partition and extract this tarball to the filesystem. If present, a file named /md5sums.txt in the tarball will have its contents checked against the whole filesystem after the tarball is extracted. This md5sums.txt file is optional and can be omitted, but it must not be blank if present. This file is present in our official images and is created during image capture with the Image Replicator tool.

/emmcimage.dd.xz

/emmcimage.dd.bz2

/emmcimage.dd.gz

/emmcimage.dd

Disk image of the media. This will be written to the eMMC block device directly. If present on the USB Image Replicator drive, a file named /emmcimage.dd.md5 will be used to verify the data written to the SD card against this checksum. This file is provided with our official images and is created during image capture with the Image Replicator tool.
U-Boot

/u-boot-dtb.bin

/SPL

U-Boot binary blob. This will be written to the bootloader area of eMMC. Note that both files are required for U-Boot, if either file is missing then the Image Replicator tool will not write either of them. If the file /SPL.md5 is present on the USB drive, this will be used to verify the data written to disk.

Building the Image Replicator from Source

The Image Replicator tool uses Buildroot to create the bootable USB disk image and tarball. See the project repository on github for information on compatibility and instructions on building: https://github.com/embeddedTS/buildroot-ts


microSD Card

Note: Our Image Replicator tool can be used to automate this process.


Download your distribution tarball:

These instructions assume an SD card with one partition. Most SD cards ship this way by default, but if there are modified partitions, a utility such as 'gparted' or 'fdisk' may be needed to remove the existing partition table and recreate it with a single partition.

Note: That the partition table must be "MBR" or "msdos", as the "GPT" partition table format is NOT supported by U-Boot.

Using other OSs

At this time, we're unable to provide assistance with writing SD cards for our products from non-Linux based operating systems. We acknowledge however, that there are methods to write images and files from a variety of difference operating systems. If a native installation of Linux is unavailable, we recommend using a Virtual Machine. See the Getting Started section for links to common virtualization software and Linux installation.

Using a Linux workstation

An SD card can be written to allow it to be bootable. Download the above file and write this from a Linux workstation using the information below. A USB SD adapter can be used to access the card; or if the workstation supports direct connection of SD cards, that can be used instead. Once inserted in to the workstation, it is necessary to discover which /dev/ device corresponds with the inserted SD card before the image can be written.

Option 1: using 'lsblk'


Newer distributions include a utility called 'lsblk' which allows simple identification of the intended card.

Note: This command may need to be run as the root user:
$ lsblk

NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sdY      8:0    0   400G  0 disk 
├─sdY1   8:1    0   398G  0 part /
├─sdY2   8:2    0     1K  0 part 
└─sdY5   8:5    0     2G  0 part [SWAP]
sr0     11:0    1  1024M  0 rom  
sdX      8:32   1   3.9G  0 disk 
├─sdX1   8:33   1   7.9M  0 part 
├─sdX2   8:34   1     2M  0 part 
├─sdX3   8:35   1     2M  0 part 
└─sdX4   8:36   1   3.8G  0 part

In this case the, SD card is 4GB, so sdX is the target device and already contains 4 partitions. Note that sdX is not a real device, it could be sda, sdb, mmcblk0, etc. embeddedTS is not responsible for any damages cause by using the improper device node for imaging an SD card. The instructions below to write to the device will destroy the partition table and any existing data!

Option 2: Using 'dmesg'


After plugging in the device, the 'dmesg' command can be used to list recent kernel events. When inserting a USB adapter, the last few lines of 'dmesg' output will be similar to the following (note that this command may need to be run as the root user):

$ dmesg
...
scsi 54:0:0:0: Direct-Access     Generic  Storage Device   0.00 PQ: 0 ANSI: 2
sd 54:0:0:0: Attached scsi generic sg2 type 0
sd 54:0:0:0: [sdX] 3862528 512-byte logical blocks: (3.97 GB/3.84 GiB)
...

In this case, sdX is shown as a 3.97GB card with a single partition. Note that sdX is not a real device, it could be sda, sdb, mmcblk0, etc. embeddedTS is not responsible for any damages cause by using the improper device node for imaging an SD card. The instructions below to write to the device will destroy the partition table and any existing data!

Running these commands will write the SD card to our default latest image.

# Verify nothing else has the disk mounted with 'mount'
# If partitions are mounted automatically, they can be unmounted with
sudo umount /dev/sdX1

sudo mkfs.ext3 /dev/sdX1
sudo mkdir /mnt/sd
sudo mount /dev/sdX1 /mnt/sd/
wget https://files.embeddedTS.com/ts-arm-sbc/ts-7250-v3-linux/distributions/debian/tsimx6ul-debian-bullseye-latest.tar.xz

sudo tar -xJf tsimx6ul-debian-bullseye-latest.tar.xz -C /mnt/sd
sudo umount /mnt/sd
sync
Note: The ext4 filesystem can be used instead of ext3, but it may require additional options. U-Boot does not support the 64bit addressing added as the default behavior in recent revisions of mkfs.ext4. If using e2fsprogs 1.43 or newer, the options "-O ^64bit,^metadata_csum" must be used with ext4 for proper compatibility. Older versions of e2fsprogs do not need these options passed nor are they needed for ext3.


After the image is written, the files can all be verified on disk against the original files created in the tarball. Reinsert the disk to verify any block cache is gone, then run the following:

mount /dev/sdX1 /mnt/sd
cd /mnt/sd/
sudo md5sum --quiet -c md5sums.txt
umount /mnt/sd
sync

The 'md5sum' command will report any differences between files and their checksums. Any differences are an indication of failure to write data or a damaged disk.

Booted from SD

Note: Our Image Replicator tool can be used to automate this process.


These instructions assume the TS-7250-V3 is booted from SD card all the way to Linux. They also assume that the eMMC is unmodified, with a single partition. If the partition table has been modified, a utility such as 'gparted' or 'fdisk' may be needed to remove the existing partition table and recreate it with a single partition. Note that the partition table must be "MBR" or "msdos", the "GPT" partition table format is not supported by U-Boot.

# Verify nothing else has the partition mounted
umount /dev/mmcblk1p1

mkfs.ext3 /dev/mmcblk1p1
mount /dev/mmcblk1p1 /mnt/emmc
wget https://files.embeddedTS.com/ts-arm-sbc/ts-7250-v3-linux/distributions/debian/tsimx6ul-debian-bullseye-latest.tar.xz
tar -xf tsimx6ul-debian-bullseye-latest.tar.xz -C /mnt/emmc
umount /mnt/emmc
sync
Note: The ext4 filesystem can be used instead of ext3, but it may require additional options. U-Boot does not support the 64bit addressing added as the default behavior in recent revisions of mkfs.ext4. If using e2fsprogs 1.43 or newer, the options "-O ^64bit,^metadata_csum" must be used with ext4 for proper compatibility. Older versions of e2fsprogs do not need these options passed nor are they needed for ext3.

Once written, the files on disk can be verified to ensure they are the same as the source files in the archive. To do so, run the following commands:

mount /dev/mmcblk1p1 /mnt/emmc
cd /mnt/emmc/
md5sum --quiet -c md5sums.txt
cd -
umount /mnt/emmc
sync

The 'md5sum' command will report any differences between files and their checksums. Any differences are an indication of failure to write data or a damaged disk.

Features

ADCs

Audio Codec

Battery-backed RTC

Bluetooth

CAN

The TS-TPC-9390 has two FlexCAN ports that use the Linux SocketCAN implementation. These are available on the P2 Terminal Block.

These interfaces can be brought up with:

ip link set can0 up type can bitrate 1000000
ip link set can1 up type can bitrate 1000000


At this point, the port can be used with standard SocketCAN libraries. In Debian, we provide the utilities cansend and candump to test the ports or as a simple packet send/receive tool. In order to test the port, tie CAN_1_H to CAN_2_H and CAN_1_L to CAN_2_L. Then use the following commands:

candump can0
# This command will echo all data received on the bus to the terminal

cansend can0 7Df#03010c
#This command will send out the above CAN packet to the bus


The above example packet is designed to work with the Ozen Elektronik myOByDic 1610 ECU simulator to read the RPM speed. In this case, the ECU simulator would return data from candump with:

 <0x7e8> [8] 04 41 0c 60 40 00 00 00 
 <0x7e9> [8] 04 41 0c 60 40 00 00 00 

In the output above, columns 6 and 7 are the current RPM value. This shows a simple way to prove out the communication before moving to another language.

The following example sends the same packet and parses the same response in C:

#include <stdio.h>
#include <pthread.h>
#include <net/if.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <assert.h>
#include <linux/can.h>
#include <linux/can/raw.h>

int main(void)
{
	int s;
	int nbytes;
	struct sockaddr_can addr;
	struct can_frame frame;
	struct ifreq ifr;
	struct iovec iov;
	struct msghdr msg;
	char ctrlmsg[CMSG_SPACE(sizeof(struct timeval)) + CMSG_SPACE(sizeof(__u32))];
	char *ifname = "can0";
 
	if((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
		perror("Error while opening socket");
		return -1;
	}
 
	strcpy(ifr.ifr_name, ifname);
	ioctl(s, SIOCGIFINDEX, &ifr);
	addr.can_family  = AF_CAN;
	addr.can_ifindex = ifr.ifr_ifindex;
 
	if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		perror("socket");
		return -2;
	}
 
 	/* For the ozen myOByDic 1610 this requests the RPM guage */
	frame.can_id  = 0x7df;
	frame.can_dlc = 3;
	frame.data[0] = 3;
	frame.data[1] = 1;
	frame.data[2] = 0x0c;
 
	nbytes = write(s, &frame, sizeof(struct can_frame));
	if(nbytes < 0) {
		perror("write");
		return -3;
	}

	iov.iov_base = &frame;
	msg.msg_name = &addr;
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = &ctrlmsg;
	iov.iov_len = sizeof(frame);
	msg.msg_namelen = sizeof(struct sockaddr_can);
	msg.msg_controllen = sizeof(ctrlmsg);  
	msg.msg_flags = 0;

	do {
		nbytes = recvmsg(s, &msg, 0);
		if (nbytes < 0) {
			perror("read");
			return -4;
		}

		if (nbytes < (int)sizeof(struct can_frame)) {
			fprintf(stderr, "read: incomplete CAN frame\n");
		}
	} while(nbytes == 0);

	if(frame.data[0] == 0x4)
		printf("RPM at %d of 255\n", frame.data[3]);
 
	return 0;
}

See the Kernel's CAN documentation here. Other languages have bindings to access CAN such as Python, Java using JNI.

In production use of CAN we also recommend setting a restart-ms for each active CAN port.

ip link set can0 type can restart-ms 100

This allows the CAN bus to automatically recover in the event of a bus-off condition.

CPU

eMMC Interface

The onboard eMMC supports the eMMC5.1 specification, and SDR104 speeds with an 8-bit bus. The onboard eMMC supports fast sequential access of approximately 250 MiB/s read or 110MiB/s write.

The eMMC provides 4 devices:

Device Description
/dev/mmcblk0 User partition that includes GPT partitions and bootable OS
/dev/mmcblk0boot0 Bootloader partition that includes ATF/OP-TEE/DDR firmware/U-Boot, and u-boot environment
/dev/mmcblk0boot1 Unused bootloader partition
/dev/mmcblk0rpmb Replay protected memory block

Besides including a bootable image, /dev/mmcblk0 does not include any preallocated data at various offset.

The mmcblk0boot0 partition does include data at some specific offsets. This device is not guaranteed to be the same size on all equivalent parts, but will provide at least 4MB.

mmcblk0boot0
Offset Description
0x0-0x1FFFFF Singleboot image including arm-trusted-firmware, LPDDR5 firmware, OP-TEE, imx93 Sentinal firmware, and U-Boot.
0x200000-0x203FFF U-boot environment
0x204000-0x207FFF U-boot environment (Redundant)
0x240000-0x2BFFFF Pending FPGA Update Image

Ethernet Ports (1000BaseT)

FPGA

FPGA Bus

The TS-4300's FPGA is connected to the CPU over the FlexSPI bus. This provides 32-bit access to the FPGA, mapped at 0x2800_0000.

FPGA Memory Map

Offset Description
0x0000 Model/Rev Info
0x0020 IRQ core
0x0040 FPGA GPIO block #0
0x0080 FPGA GPIO block #1
0x00C0 FPGA GPIO block #2
0x0400 FPGA PWM 0
0x0420 FPGA PWM 1
0x2000 FPGA XBAR


FPGA Info Core

The FPGA model/info core is used for testing / identification of the board. In general user should not need to implement access to this core without a custom FPGA, but the register interface is as follows:

# Read the MODEL register
memtool md 0x28000000+4
Address Name Bits Field RO/RW Description
0x0 MODEL [31:16] Reserved RO Reserved bits
[15:0] Model Number RO Model number of the core (e.g., 0x9370, 0x9390, 0x4300)
0x4 VERSION [31] Git Dirty Bit RO Indicates if the working tree is dirty
[30:24] Major Tag Version RO Major version number
[23:16] Minor Tag Version RO Minor version number
[15:8] Patch Tag Version RO Patch version number
[7:0] Commits Since Tag RO Number of commits since the tag
0x8 GIT_INFO [31:0] Git Short Hash RO 31-bit Git short hash
0xC BUILD_TIME [31:0] Build Time RO Build time encoded as epoch ≫ 1
0x10 SCRATCH0 [31:0] Scratch Reg 0 RW General-purpose scratch register 0
0x14 SCRATCH1 [31:0] Scratch Reg 1 RW General-purpose scratch register 1
0x18 UNIQUE_IDENTIFIER [31:0] Unique Identifier RW Custom identifier for user-built FPGA images (0 for stock)

FPGA GPIO

Each of the three GPIO blocks can manage up to 32 IO lines, and support level IRQs, rising/falling/both edge IRQs.

For Linux usage of the GPIO controller, see the GPIO section which shows how to use the GPIO/IRQs from userspace. While we recommend using the existing driver stack, the register documentation the 32-bit registers controlling each block are defined as follows:

Offset Name Read/Write Description
0x00 oe Read Get the output enable state of GPIO pins
oe_set Write Set bits to enable output for corresponding GPIOs.
0x04 N/A Read N/A
oe_clr Write Clear bits to disable output for corresponding GPIOs.
0x08 output_data Read Current state of GPIO output data.
output_data_set Write Set bits to drive high the corresponding GPIO outputs.
0x0C input_data Read Current state of GPIO input data.
output_data_clr Write Set bits to drive low the corresponding GPIO outputs.
0x10 irq_pending Read Shows active and unmasked interrupts. 0 = Inactive/masked, 1 = Active and unmasked.
irq_ack Write Acknowledge interrupt. 0 = No effect, 1 = Ack interrupt.
0x14 irq_mask Read Shows IRQ mask. 0 = Unmasked, 1 = Masked.
irq_mask_set Write Masks IRQ for corresponding bits. 0 = No effect, 1 = Mask interrupt.
0x18 N/A Read N/A
0x18 irq_mask_clr Write Unmasks IRQ for corresponding bits. 0 = No effect, 1 = Unmask interrupt.
0x1C N/A Read N/A
irq_mask_and_ack Write Acknowledge and mask the corresponding IRQ. 0 = No effect, 1 = Acknowledge and mask.
0x20 irq_edge_level Read/Write 0 = Level-sensitive IRQ, 1 = Edge-triggered IRQ.
0x24 irq_edge_sel Read/Write 0 = Use polarity to select edge, 1 = Either edge.
0x28 irq_polarity Read/Write 0 = Active-low/falling edge, 1 = Active-high/rising edge.

FPGA PWM

This system includes PWM that supports 10-bit duty/period, a 66.666666mhz input clock, and 12 values of input clock shift.

Linux supports this API through the /sys/ interface using file I/O. First export the pwm channel to enable it:

# Export PWM channel 0
echo 0 > /sys/class/pwm/pwmchip0/export
File Description
/sys/class/pwm/pwmchip0/pwm0/period Period in nanoseconds. Must be bigger than the duty cycle or writes will fail. Can only change when the pwm is disabled.
/sys/class/pwm/pwmchip0/pwm0/duty_cycle Duty cycle in nanoseconds. Can change at any time, must be less than period.
/sys/class/pwm/pwmchip0/pwm0/enable When 1, pwm is outputting. When 0, outputs idle state of the PWM.
/sys/class/pwm/pwmchip0/pwm0/polarity When "normal", idle high and duty cycle low. When "inversed", idle low and duty cycle high. A valid period must be set before this can be changed.

For example, for a 50hz signal with 25% duty cycle:

# Set Period to 20ms
echo 20000000 > /sys/class/pwm/pwmchip0/pwm0/period
# Set duty cycle to 5ms
echo 5000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
# Enable PWM and output 50hz signal
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable

# Duty cycle can be changed while it is enabled
echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle

The Linux PWM API will attempt to arrive at the exact period at the cost of the duty cycle resolution. For the most possible duty cycle resolution use one of the max period ns values from the table below.

Shift PWM Input Frequency (Hz) Duty Cycle units (ns) Max Period (ns) Max Period (Hz)
0 66666666 15 15345 65167
1 33333333 30 30690 32583
2 16666666 60 61380 16291
3 8333333 120 122760 8145
4 4166666 240 245520 4072
5 2083333 480 491040 2036
6 1041666 960 982080 1018
7 520833 1920 1964161 509
8 260416 3840 3928330 254
9 130208 7860 7856660 127
10 65104 15360 15713320 63
11 32552 30720 31426640 31

If period is set to one of these values, the full 10 bits of duty cycle is available. Past that, the Linux API will use the closest available value. Debug output can be enabled with:

echo "file pwm-ts.c +p" > /sys/kernel/debug/dynamic_debug/control

If this is enabled, the kernel can output additional information after setting a frequency:

echo 0 > /sys/class/pwm/pwmchip0/export
# 10ms period:
echo 10000000 > /sys/class/pwm/pwmchip0/pwm0/period
# 5ms duty cycle:
echo 5000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
dmesg | tail

This will output:

[   75.758146] ts-pwm 500001a8.mikro_pwm: cycle=1293661 shift=10 cnt=773
[   75.758184] ts-pwm 500001a8.mikro_pwm: shift=10 cnt=773 duty_cnt=387

The last value in cnt indicates how much resolution is available for the duty cycle at this given period. In the best case there are 10 bits (0-2047) to specify duty cycle, but this above example is 0-773 to arrive at this particular period. You can determine the duty cycle increments with period / cnt. From the above example:

10000000 / 773 = 12936.61

The duty cycle can then be configured in increments of 12936ns. Smaller values will round to the closest value.

This PWM will allow a max speed of 79.2MHz / 3 = 26.4MHz, but this will sacrifice all of the available duty cycle except an on/50%/off. The slowest speed is highest divisor at 38hz.

While the Linux driver is recommended for most users, the PWM core is located at 0x28000400, each PWM are 0x20 registers apart.

Address Bits Description
0x00 (CONFIG) 31:2 Reserved
1 PWM Inversion (0 = Idle high, duty cycle low), (1 = Idle low, duty cycle high)
0 PWM Enable (1 = Enable PWM output, 0 = Drive default state)
0x04 (PERIOD) 31:10 Reserved
9:0 PWM Period (0–1023 steps)
0x08 (DUTY) 31:10 Reserved
9:0 PWM Duty Cycle (0–1023 steps)
0x0C (SHIFT) 31:12 Reserved
11:0 Shift (Clock frequency = 66666666 / (1 >> shift))

FPGA Circuit Breaker

This board includes:

  • 1x High side switches capable of sourcing 300mA
  • 4x Low side switches capable of sinking 500mA each, or 1A total.

If these sink/source values are exceeded, the "DIO_FAULT#" IO will trip to prevent damage. Any detected edge on DIO_FAULT# will trigger a latch in the FPGA, and the "circuit_breaker_fault" will assert. As soon as this signal is asserted, all of the high side and low side switches will disable, regardless of the data out value in the GPIO.

The circuit_breaker_fault will stay high until the fault condition is cleared. The fault condition is cleared by creating a rising edge on clear_fault.

For example:

# Set EN_LS_OUT_1 high
gpioset 6 17=1

If this then exceeds the current limit:

# Read circuit_breaker_fault
gpioget 6 21

This pin can be read as an interrupt or polled. If it returns 1, then the circuit breaker has tripped. Once the issue causing the overcurrent condition is cleared, the clear_fault IO must be pulsed. The circuit_breaker_fault signal is cleared on the rising edge.

# Set clear_fault low
gpioset 6 15=0
# Set clear_fault high
gpioset 6 15=1

# re-read circuit_breaker_fault, and it should now return 0 if the condition has not re-tripped.
gpioget 6 21

FPGA XBAR

The FPGA XBAR controller allows rerouting some fpga pins between other pins. For example, the FPGA has connections to many of the CPU UARTs, SPI, and has several internal cores. This lets the users control which controllers are connected to certain pins. For example, the daughtercard header is all GPIO by default. The xbar allows routing the CPU's UART away from a RS-485 transceiver, and instead to this daughter card header. This does not make the daughter card header RS-485 levels, but just changes which controller drives these pins.

Under Linux the pin mapping can be controlled 2 ways, either through a device tree modification, or through debugfs in userspace.

For Userspace:


cd "/sys/kernel/debug/pinctrl/28002000.pinctrl-tsxbar-pinctrl/"

# echo a list of every output pin
cat pingroups

# echo a list of every function available:
cat pinmux-functions

# To see functions on a given pin:
cat pinmux-functions | grep MIKRO_TXD
# This returns:
##function 3: GPIO0_IO14, groups = [ MIKRO_TXD ]
##function 4: UART8_TXD, groups = [ MIKRO_TXD ]

# By default, MIKRO_TXD is a uart.  To select the GPIO:
echo "MIKRO_TXD GPIO0_IO14" > pinmux-select
# To select the UART again:
echo "MIKRO_TXD UART8_TXD" > pinmux-select


For the device tree:


In the device tree, find the compatible = "technologic,ts9390-xbar"; node, and add pin mappings underneath that. See pinfunc-ts9390.h in the same path as the device tree for the available options. For example, this is how to define the mikrobus UART pins as GPIO or UART:

		fpga_xbar: pinctrl@28002000 {
			compatible = "technologic,ts9390-xbar";
			reg = <0x2000 0x200>;

			pinctrl_uart_mikrobus: mikrobusuartgrp {
				xbar,pins = <
					TS9390_PAD_MIKRO_TXD__UART8_TXD
					TS9390_PAD_UART8_RXD__MIKRO_RXD
				>;
			};

			pinctrl_gpio_mikrobus: mikrobusgpiogrp {
				xbar,pins = <
					TS9390_PAD_MIKRO_TXD__GPIO0_IO14
					TS9390_PAD_MIKRO_RXD__GPIO0_IO15
				>;
			};
		};

The mikrobus UART is the default, but this is how it would be explicitly mapped in the device tree:

&lpuart8 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_uart8 &pinctrl_uart_mikrobus>;
	status = "okay";
};

The XBAR controller supports these muxing options:

Alt 0 Alt 1 Alt 2 Alt 3 Alt 4 Alt 5 Alt 6 Alt 7
Pin #00 (UART6_TXD), Default BT_RXD
N/A UART6_TXD N/A N/A N/A N/A N/A N/A
Pin #01 (HIGHZ), Default BT_TXD
N/A N/A N/A N/A N/A N/A N/A N/A
Pin #02 (HIGHZ), Default BT_RTS
N/A N/A N/A N/A N/A N/A N/A N/A
Pin #03 (UART6_RTS), Default BT_CTS
N/A UART6_RTS N/A N/A N/A N/A N/A N/A
Pin #04 (DC_1), Default DP_19P2MHZ_CLK
N/A DC_1 N/A N/A N/A N/A N/A N/A
Pin #05 (HIGHZ), Default DP_OSC_CLK
N/A N/A N/A N/A N/A N/A N/A N/A
Pin #06 (UART8_TXD), Default MIKRO_TXD
GPIO0_IO14 UART8_TXD N/A N/A N/A N/A N/A N/A
Pin #07 (GPIO0_IO15), Default MIKRO_RXD
GPIO0_IO15 N/A N/A N/A N/A N/A N/A N/A
Pin #08 (LPSPI4_CLK), Default MIKRO_SPI_CLK
GPIO0_IO16 LPSPI4_CLK N/A N/A N/A N/A N/A N/A
Pin #09 (LPSPI_CS), Default MIKRO_SPI_CS#
GPIO0_IO17 LPSPI_CS N/A N/A N/A N/A N/A N/A
Pin #10 (GPIO0_IO18), Default MIKRO_SPI_MISO
GPIO0_IO18 N/A N/A N/A N/A N/A N/A N/A
Pin #11 (LPSPI4_MOSI), Default MIKRO_SPI_MOSI
GPIO0_IO19 LPSPI4_MOSI N/A N/A N/A N/A N/A N/A
Pin #12 (GPIO0_IO20), Default MIKRO_RESET#
GPIO0_IO20 N/A N/A N/A N/A N/A N/A N/A
Pin #13 (GPIO0_IO21), Default MIKRO_AN
GPIO0_IO21 N/A N/A N/A N/A N/A N/A N/A
Pin #14 (PWM0_OUT), Default MIKRO_PWM
GPIO0_IO22 PWM0_OUT N/A N/A N/A N/A N/A N/A
Pin #15 (GPIO0_IO23), Default MIKRO_INT
GPIO0_IO23 N/A N/A N/A N/A N/A N/A N/A
Pin #16 (GPIO2_IO27), Default DC_1
GPIO2_IO27 UART7_TX LPSPI4_CLK N/A N/A N/A N/A N/A
Pin #17 (GPIO2_IO28), Default DC_3
GPIO2_IO28 N/A N/A N/A N/A N/A N/A N/A
Pin #18 (GPIO2_IO29), Default DC_5
GPIO2_IO29 UART7_RTS LPSPI4_MOSI N/A N/A N/A N/A N/A
Pin #19 (GPIO2_IO30), Default DC_7
GPIO2_IO30 LPSPI4_MOSI N/A N/A N/A N/A N/A N/A
Pin #20 (GPIO2_IO31), Default DC_9
GPIO2_IO31 N/A N/A N/A N/A N/A N/A N/A
Pin #21 (HIGHZ), Default DIO_1_IN
N/A N/A N/A N/A N/A N/A N/A N/A
Pin #22 (HIGHZ), Default DIO_2_IN
N/A N/A N/A N/A N/A N/A N/A N/A
Pin #23 (HIGHZ), Default DIO_3_IN
N/A N/A N/A N/A N/A N/A N/A N/A
Pin #24 (HIGHZ), Default DIO_4_IN
N/A N/A N/A N/A N/A N/A N/A N/A
Pin #25 (GPIO2_IO17), Default EN_LS_OUT_1
GPIO2_IO17 N/A N/A N/A N/A N/A N/A N/A
Pin #26 (GPIO2_IO18), Default EN_LS_OUT_2
GPIO2_IO18 N/A N/A N/A N/A N/A N/A N/A
Pin #27 (GPIO2_IO19), Default EN_LS_OUT_3
GPIO2_IO19 N/A N/A N/A N/A N/A N/A N/A
Pin #28 (GPIO2_IO20), Default EN_LS_OUT_4
GPIO2_IO20 N/A N/A N/A N/A N/A N/A N/A
Pin #29 (GPIO2_IO16), Default EN_HS_SW
GPIO2_IO16 N/A N/A N/A N/A N/A N/A N/A
Pin #30 (UART7_TXD), Default PRIM_485_TXD
N/A UART7_TXD N/A N/A N/A N/A N/A N/A
Pin #31 (UART7_RTS), Default PRIM_485_TXEN
N/A UART7_RTS N/A N/A N/A N/A N/A N/A
Pin #32 (HIGHZ), Default PRIM_485_RXD
N/A N/A N/A N/A N/A N/A N/A N/A
Pin #33 (UART3_RTS), Default SEC_485_TXEN
N/A UART3_RTS N/A N/A N/A N/A N/A N/A
Pin #34 (HIGHZ), Default SEC_485_RXD
N/A N/A N/A N/A N/A N/A N/A N/A
Pin #35 (UART3_TXD), Default SEC_UART_TX
N/A UART3_TXD N/A N/A N/A N/A N/A N/A
Pin #36 (GPIO0_IO0), Default EN_GREEN_LED#
GPIO0_IO0 N/A N/A N/A N/A N/A N/A N/A
Pin #37 (GPIO0_IO2), Default EN_RED_LED#
GPIO0_IO2 N/A N/A N/A N/A N/A N/A N/A
Pin #38 (HIGHZ), Default PUSH_SW#
N/A N/A N/A N/A N/A N/A N/A N/A
Pin #39 (PLL_CLK_OUT), Default CODEC_CLK
N/A PLL_CLK_OUT N/A N/A N/A N/A N/A N/A
Pin #40 (GPIO0_IO4), Default NIM_RESET#
GPIO0_IO4 N/A N/A N/A N/A N/A N/A N/A
Pin #41 (GPIO0_IO5), Default NIM_CTS#
GPIO0_IO5 N/A N/A N/A N/A N/A N/A N/A
Pin #42 (GPIO0_IO6), Default NIM_PWR_ON#
GPIO0_IO6 N/A N/A N/A N/A N/A N/A N/A
Pin #43 (HIGHZ), Default NIM_STATUS
N/A N/A N/A N/A N/A N/A N/A N/A
Pin #44 (UART5_TXD), Default NIM_TXD
GPIO0_IO10 UART5_TXD N/A N/A N/A N/A N/A N/A
Pin #45 (GPIO0_IO11), Default NIM_RXD
GPIO0_IO11 N/A N/A N/A N/A N/A N/A N/A
Pin #46 (HIGHZ), Default NO_SCAP_CHRG#
N/A N/A N/A N/A N/A N/A N/A N/A
Pin #47 (GPIO2_IO6), Default EN_RELAY_2
GPIO2_IO6 N/A N/A N/A N/A N/A N/A N/A
Pin #48 (GPIO2_IO5), Default EN_RELAY_1
GPIO2_IO5 N/A N/A N/A N/A N/A N/A N/A
Pin #49 (SECOND_PORT_RXD_3V), Default UART3_RXD
N/A SECOND_PORT_RXD_3V N/A N/A N/A N/A N/A N/A
Pin #50 (HIGHZ), Default UART3_CTS
N/A N/A N/A N/A N/A N/A N/A N/A
Pin #51 (NIM_RXD), Default UART5_RXD
N/A NIM_RXD N/A N/A N/A N/A N/A N/A
Pin #52 (BT_TXD), Default UART6_RXD
N/A BT_TXD N/A N/A N/A N/A N/A N/A
Pin #53 (BT_RTS), Default UART6_CTS
N/A BT_RTS N/A N/A N/A N/A N/A N/A
Pin #54 (PRIM_485_RXD_3V), Default UART7_RXD
N/A PRIM_485_RXD_3V DC_3 SECOND_PORT_RXD_3V N/A N/A N/A N/A
Pin #55 (DC_7), Default UART7_CTS
N/A DC_7 N/A N/A N/A N/A N/A N/A
Pin #56 (MIKRO_RXD), Default UART8_RXD
N/A MIKRO_RXD N/A N/A N/A N/A N/A N/A
Pin #57 (MIKRO_SPI_MISO), Default LPSPI4_MISO
N/A MIKRO_SPI_MISO DC_3 N/A N/A N/A N/A N/A

While the existing drivers should be used for any iomux interaction, this is the register documentation for interacting directly with the core.

Address Bits Read/Write Description
0x0 31:24 RW PIN[n+3] FUNC_SEL
23:16 RW PIN[n+2] FUNC_SEL
15:8 RW PIN[n+1] FUNC_SEL
7:0 RW PIN[n+0] FUNC_SEL

This repeats for 0x0+ceil(N_PINS/4).

For each FUNC_SEL register:

Bits Description
3 HIGH_Z_EN, 1 = enable high-z, regardless of lower bits, 0 drive peripheral values
2:0 Value of 0-7 selects a function that drives oe+data

FPGA IRQ

The FPGA has a single IRQ from the CPU, which it expands to its own IRQ capable cores. It is recommended to interact with GPIO underneath the FPGA which are IRQ capable rather than the IRQ core itself.

This core takes the 1 IRQ, and expands it to up to 32 IRQs. On the current design this is connected to:

IRQ Description
0 FPGA GPIO 0
1 FPGA GPIO 1
2 FPGA GPIO 2

Most physical IRQ capable pins are connected underneath the GPIO cores which provide flexibility for IRQ type, polarity, acks, etc to not miss edges.

We recommend to use the existing Linux BSP which implements drivers for this already ("technologic,fpga-irqc"), but this core uses this register map.

Address Name Access Description
0x00 irq_status R Shows active and unmasked interrupts.
0 = Interrupt inactive or masked.
1 = Interrupt active and unmasked.
W *Not applicable*
0x04 irq_mask_set R Returns mask register; 1 = masked, 0 = unmasked.
W **Write 1** to a bit position to enable that interrupt (sets the corresponding bit).
0x08 irq_mask_clr R N/A
W **Write 1** to a bit position to unmask that interrupt.

FPGA Updates

For most Linux users, the FPGA can be updated with:

curl -sSL http://files.embeddedts.com/ts-arm-sbc/ts-9390-linux/fpga/update-fpga.sh | sh

Then reboot.

This FPGA supports multiple application loads. On startup, it always starts the first image which is a bootloader that is capable of rewriting the FPGA's flash. If there is no update, it sets a register that switches the FPGA to the second image which contains the common functionality like GPIO, IRQs, etc.

U-boot checks checks for a valid 'fit' image in the eMMC boot 1 partition (mmcblk0boot1) at offset 0x280000. If it finds this valid update, then before switching out of the FPGA bootloader it rewrites the second application load. This take approximately 40 seconds. After writing, it then erases the region of mmcblk0boot1 containing the update, and then sets the register in the bootloader that updates the FPGA.

This update process is designed so the typical case of updating the application load is safe even in the case of sudden power loss. The FPGA bootloader is in a separate erase block of the FPGA's internal flash, so if we are interrupted in the middle of erasing/writing flash it only affects the application load. On the next start up, since the update has not been completed and still exists on mmcblk0boot1, it will restart the update and try again until it succeeds.

GPIO

The i.MX93 CPU and FPGA GPIOs are exposed using a kernel character device. This interface provides a set of files and directories for interacting with GPIO which can be used from any language that interact with special files in linux using ioctl() or similar. For our platforms, we pre-install the "libgpiod" library and binaries. Documentation on these tools can be found here. This section only covers using these userspace tools and does not provide guidance on using the libgpiod library in end applications. Please see the libgpiod documentation for this purpose.

A user with suitable permissions to read and write /dev/gpiochip* files can immediately interact with GPIO pins. For example, to see read the push switch:

gpioget $(gpiofind PUSH_SW#)

Multiple pins in the same chip can be read simultaneously by passing multiple pin numbers separated by spaces.

To write to a pin, the gpioset command is used. For example, to set Relay 1:

gpioset $(gpiofind EN_RELAY_1)=1

Multiple pins in the same chip can be set simultaneously by passing multiple pin=value pairs separated by spaces.

If a call with gpioset or gpioget fails with "Device or resource busy," that means that specific GPIO is claimed by another device. The command cat /sys/kernel/debug/gpio can be used to get a list of all of the system GPIO and what has claimed them.

The gpiomon tool can be used to monitor pins for changes to GPIOs that generate interrupts.

In the following table, gpiochips 0 through 2 are on the FPGA. gpiochip 3 is on the CPU, but none of the pins are available as GPIOs. Because this numbering is subject to change, it is advisable to use the gpiofind command to look it up by its label, as shown in the usage examples above and elsewhere in this manual.

Schematic Net Name Direction Bank Line Location
EN_GREEN_LED# Out 4 0 Green LED
EN_RED_LED# Out 4 2 Red LED
SPI4_CN2_CS# Bidir 4 4 CN2_065
EN_USB_HOST_5V Bidir 4 5 CN1_004
OFF_BD_RESET# Bidir 4 6 CN1_009
SEL_LVDS Out 4 7 Onboard
SPI_4_CS_1V8# Bidir 4 8 Onboard
DIO_01 Bidir 5 0 CN2_060
DIO_02 Bidir 5 1 CN2_062
DIO_03 Bidir 5 2 CN2_064
DIO_04 Bidir 5 3 CN2_066
DIO_05 Bidir 5 4 CN2_068
DIO_06 Bidir 5 5 CN2_070
DIO_07 Bidir 5 6 CN2_072
DIO_08 Bidir 5 7 CN1_002
DIO_09 Bidir 5 8 CN1_006
DIO_10 Bidir 5 9 CN1_008
DIO_11 Bidir 5 10 CN1_010
DIO_12 Bidir 5 11 CN1_012
DIO_13 Bidir 5 12 CN1_014
DIO_14 Bidir 5 13 CN1_018
DIO_15 Bidir 5 14 CN1_020
DIO_16 Bidir 5 15 CN1_022
DIO_17 Bidir 5 16 CN1_024
DIO_18 Bidir 5 17 CN1_026
DIO_19 Bidir 5 18 CN1_028
DIO_20 Bidir 5 19 CN1_030
DIO_21 Bidir 5 20 CN1_032
DIO_22 Bidir 5 21 CN1_034
DIO_23 Bidir 5 22 CN1_038
DIO_24 Bidir 5 23 CN1_040
DIO_25 Bidir 5 24 CN1_042
DIO_26 Bidir 5 25 CN1_044
DIO_27 Bidir 5 26 CN1_046
DIO_28 Bidir 5 27 CN1_048
DIO_29 Bidir 5 28 CN1_099
DIO_30 Bidir 5 29 CN1_052
DIO_31 Bidir 5 30 CN1_054
DIO_32 Bidir 5 31 CN1_056
DIO_33 Bidir 6 0 CN1_058
DIO_33 Bidir 6 0 CN1_058
DIO_34 Bidir 6 1 CN1_060
DIO_35 Bidir 6 2 CN1_064
DIO_36 Bidir 6 3 CN1_066
DIO_37 Bidir 6 4 CN1_068
DIO_38 Bidir 6 5 CN1_070
DIO_39 Bidir 6 6 CN1_072
DIO_40 Bidir 6 7 CN1_074
DIO_41 Bidir 6 8 CN1_076
DIO_42 Bidir 6 9 CN1_078
DIO_43 Bidir 6 10 CN1_080
DIO_44 Bidir 6 11 CN1_082
DIO_45 Bidir 6 12 CN1_084
DIO_46 Bidir 6 13 CN1_086
DIO_47 Bidir 6 14 CN1_088
DIO_48 Bidir 6 15 CN1_090
DIO_49 Bidir 6 16 CN1_092
DIO_50 Bidir 6 17 CN1_094
DIO_51 Bidir 6 18 CN1_096
DIO_52 Bidir 6 19 CN1_098
DIO_53 Bidir 6 20 CN1_100
DIO_54 Bidir 6 21 CN1_011
DIO_55 Bidir 6 22 CN1_013
DIO_56 Bidir 6 23 CN1_017
DIO_57 Bidir 6 24 CN1_019
DIO_58 Bidir 6 25 CN1_021
DIO_59 Bidir 6 26 CN1_023
DIO_60 Bidir 6 27 CN1_025
DIO_61 Bidir 6 28 CN1_027
DIO_62 Bidir 6 29 CN1_031
DIO_63 Bidir 6 30 CN1_033
DIO_64 Bidir 6 31 CN1_035
DIO_65 Bidir 7 0 CN1_037
DIO_65 Bidir 7 0 CN1_037
DIO_66 Bidir 7 1 CN1_039
DIO_67 Bidir 7 2 CN1_041
DIO_68 Bidir 7 3 CN1_043
DIO_69 Bidir 7 4 CN1_045
DIO_70 Bidir 7 5 CN1_049
DIO_71 Bidir 7 6 CN1_051
DIO_72 Bidir 7 7 CN1_053
DIO_73 Bidir 7 8 CN1_055
DIO_74 Bidir 7 9 CN1_057
DIO_75 Bidir 7 10 CN1_059
DIO_76 Bidir 7 11 CN1_061
DIO_77 Bidir 7 12 CN1_063
DIO_78 Bidir 7 13 CN1_065
DIO_79 Bidir 7 14 CN1_067
DIO_80 Bidir 7 15 CN1_073
DIO_81 Bidir 7 16 CN1_077
DIO_82 Bidir 7 17 CN1_079
DIO_83 Bidir 7 18 CN1_081
DIO_84 Bidir 7 19 CN1_083
DIO_85 Bidir 7 20 CN1_085
DIO_86 Bidir 7 21 CN1_087
DIO_87 Bidir 7 22 CN1_089
DIO_88 Bidir 7 23 CN1_091
DIO_89 Bidir 7 24 CN1_093
DIO_90 Bidir 7 25 CN1_097


I2C

The i.MX93 supports I2C at up to 1 MHz, though devices present on the bus, as well as software settings such as in the Device Tree, can limit this to 400 kHz or even 100 kHz. This product uses two CPU I2C busses for on-board ICs, one I3C-capable bus for daughtercard devices, and one FPGA-based I2C controller for Mikroe bus devices.

Device Address Description
/dev/i2c-0 [1] #Daughter Card Header (HD10)
/dev/i2c-1 0x25[2] NXP PMIC
/dev/i2c-3
0x14 Capacitive Touch Panel[3]
0x1e #Magnetometer[4]
0x2e TPM
0x50 TI #Audio CODEC
0x54 Wizard µC
0x60 #RTC
0x68[5] DisplayPort Transceiver
0x6a #IMU[4]
MAX10 [1] Mikroe bus header[6]
  1. 1.0 1.1 Address depends on what if any module is present.
  2. Slight chance of changing if the PMIC can be set to a different address
  3. On models equipped with a 7" LCD Display, i.e. that uses the Goodix GT911 touch-panel controller.
  4. 4.0 4.1 Some models
  5. Presumed but not confirmable until the part goes through bring-up testing
  6. 3.3V signaling

I3C

TS-9390 I3C

IMU

The TS-4300 includes an ST ISM330DLCTR, containing both a 3-axis accelerometer and a gyroscope. It is connected via the primary I2C bus.

Linux provides access to both as part of the IIO subsystem (via iio-tools and libiio).

ST ISM330 Accelerometer

This platform features an ST accelerometer / gyroscope. The accelerometer has an acceleration range of ±2/±4/±8/±16 g.

Early units were built using the "ism330dlc", and newer units are built using the "ism330dhcx". These are functionally the same and provide the same channels and performance, but IIO requires you to specify the part number. Our example python/c code will show how to work with either.

The accelerometer is accessed through IIO with channels:

  • accel_x
  • accel_y
  • accel_z
  • timestamp

For example:

# ISM330DHCX
iio_attr -c ism330dhcx_accel accel_x
iio_attr -c ism330dhcx_accel accel_y
iio_attr -c ism330dhcx_accel accel_z
# ISM330DLC
iio_attr -c ism330dlc_accel accel_x
iio_attr -c ism330dlc_accel accel_y
iio_attr -c ism330dlc_accel accel_z

The below examples will be written for the ism330dhcx_accel, but if this fails instead use the ism330dlc_accel device. These commands will provide a single sample of all of the values:

root@tsimx6ul:~# iio_attr -c ism330dhcx_accel accel_x
dev 'ism330dhcx_accel', channel 'accel_x' (input), attr 'injection_raw', ERROR: Permission denied (-13)
dev 'ism330dhcx_accel', channel 'accel_x' (input), attr 'raw', value '-183'
dev 'ism330dhcx_accel', channel 'accel_x' (input), attr 'scale', value '0.000598'
dev 'ism330dhcx_accel', channel 'accel_x' (input), attr 'scale_available', value '0.000598 0.001196 0.002392 0.004785'
root@tsimx6ul:~# iio_attr -c ism330dhcx_accel accel_y
dev 'ism330dhcx_accel', channel 'accel_y' (input), attr 'injection_raw', ERROR: Permission denied (-13)
dev 'ism330dhcx_accel', channel 'accel_y' (input), attr 'raw', value '-292'
dev 'ism330dhcx_accel', channel 'accel_y' (input), attr 'scale', value '0.000598'
dev 'ism330dhcx_accel', channel 'accel_y' (input), attr 'scale_available', value '0.000598 0.001196 0.002392 0.004785'
root@tsimx6ul:~# iio_attr -c ism330dhcx_accel accel_z
dev 'ism330dhcx_accel', channel 'accel_z' (input), attr 'injection_raw', ERROR: Permission denied (-13)
dev 'ism330dhcx_accel', channel 'accel_z' (input), attr 'raw', value '16491'
dev 'ism330dhcx_accel', channel 'accel_z' (input), attr 'scale', value '0.000598'
dev 'ism330dhcx_accel', channel 'accel_z' (input), attr 'scale_available', value '0.000598 0.001196 0.002392 0.004785'

To get the real world value, multiply the scale * the raw value. In this case:

  • X: -0.109434 g
  • Y: -0.174616 g
  • Z: 9.861618 g

The default scale is ±2, but ±2/±4/±8/±16 can be selected by setting the scale:

dev 'ism330dhcx_accel', channel 'accel_z' (input), attr 'scale', value '0.000598'
dev 'ism330dhcx_accel', channel 'accel_z' (input), attr 'scale_available', value '0.000598 0.001196 0.002392 0.004785'

To set ±4, you would write the second available scale:

iio_attr -c ism330dhcx_accel accel_x scale 0.001196

The scale values are not independent on this device, and setting x/y/z will set the scale for all 3.

This driver also supports pulling continuous samples using the buffer interface. These can be accessed using iio_readdev:

iio_readdev ism330dhcx_accel -T 0 -s 128 > samples.bin

The format of this file is specified with iio_attr:

root@tsimx6ul:~# iio_attr -c ism330dhcx_accel
dev 'ism330dhcx_accel', channel 'accel_x' (input, index: 0, format: le:S16/16>>0), found 4 channel-specific attributes
dev 'ism330dhcx_accel', channel 'accel_y' (input, index: 1, format: le:S16/16>>0), found 4 channel-specific attributes
dev 'ism330dhcx_accel', channel 'accel_z' (input, index: 2, format: le:S16/16>>0), found 4 channel-specific attributes
dev 'ism330dhcx_accel', channel 'timestamp' (input, index: 3, format: le:S64/64>>0), found 0 channel-specific attributes

The samples are padded to the nearest 8-bytes, so this means the binary format is:

Bits Description
15:0 accel_x, little endian, signed
15:0 accel_y, little endian, signed
15:0 accel_z, little endian, signed
63:0 timestamp, little endian, signed
15:0 Padding

The unix utility hexdump supports formatting options which can parse these fields:

root@tsimx6ul:~# hexdump samples.bin --format '1/2 "X:%d " 1/2 "Y:%d " 1/2 "Z:%d " 1/8 "TS:%d" 1/2 "" "\n"' | head -n 4
X:-95 Y:-163 Z:8221 TS:200185381271666439
X:-107 Y:-147 Z:8248 TS:200190332264480519
X:-100 Y:-155 Z:8263 TS:200195283888013063
X:-95 Y:-159 Z:8253 TS:200200232540667655

This gives the raw values which can then be multiplied by the scale to get the real world value.

The IIO library can also be used to fill buffers with samples for processing. For example:

#!/usr/bin/env python3

import struct
import iio

ctx = iio.Context('local:')
ctx.set_timeout(0)
dev = ctx.find_device('ism330dhcx_accel') or ctx.find_device('ism330dlc_accel')

with open(f'/sys/bus/iio/devices/{dev.id}/sampling_frequency', 'w') as f:
	f.write(f"833.000")

for chan_name in ["accel_x", "accel_y", "accel_z"]:
	chn = dev.find_channel(chan_name)
	chn.enabled = True

# We will request 64 samples at a time
buffer = iio.Buffer(dev, 64, False)
# sample size (3x 16-bit signed data)
sample_size = 6
# Refill and process the buffer
buffer.refill()
data = buffer.read()
for i in range(0, len(data), sample_size):
	if i + sample_size <= len(data):
		x, y, z = struct.unpack('<hhh', data[i:i+sample_size])
		print(f'  accel_x={x}, accel_y={y}, accel_z={z}')

for chn in dev.channels:
	chn.enabled = False

This can also be done using the C library:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iio.h>

#define NUM_CHANNELS 3
#define SAMPLE_SIZE 6 // 3x 16-bit signed data (2 bytes per axis)

void process_samples(struct iio_buffer *buffer, size_t sample_size) {
    char *data = iio_buffer_start(buffer);
    size_t buffer_size = iio_buffer_end(buffer) - iio_buffer_start(buffer);
    int16_t x, y, z;

    for (size_t i = 0; i < buffer_size; i += sample_size) {
        memcpy(&x, &data[i], sizeof(x));
        memcpy(&y, &data[i + sizeof(x)], sizeof(y));
        memcpy(&z, &data[i + 2 * sizeof(x)], sizeof(z));
        printf("accel_x=%d, accel_y=%d, accel_z=%d\n", x, y, z);
    }
}

int main() {
    struct iio_context *ctx;
    struct iio_device *dev;
    struct iio_channel *channels[NUM_CHANNELS];
    struct iio_buffer *buffer;
    const char *channel_names[NUM_CHANNELS] = { "accel_x", "accel_y", "accel_z" };

    // Create context and find device
    ctx = iio_create_local_context();
    if (!ctx || !(dev = iio_context_find_device(ctx, "ism330dhcx_accel")) &&
        !(dev = iio_context_find_device(ctx, "ism330dlc_accel"))) {
        fprintf(stderr, "Unable to create context or find device\n");
        iio_context_destroy(ctx);
        return 1;
    }

    // Enable channels and set sampling frequency
    for (int i = 0; i < NUM_CHANNELS; i++) {
        channels[i] = iio_device_find_channel(dev, channel_names[i], false);
        if (!channels[i] || iio_channel_attr_write(channels[i], "sampling_frequency", "833.000") < 0) {
            fprintf(stderr, "Unable to find or configure channel %s\n", channel_names[i]);
            iio_context_destroy(ctx);
            return 1;
        }
        iio_channel_enable(channels[i]);
    }

    // Create buffer and process samples
    buffer = iio_device_create_buffer(dev, 64, false);
    if (!buffer || iio_buffer_refill(buffer) < 0) {
        fprintf(stderr, "Unable to create or refill buffer\n");
        iio_context_destroy(ctx);
        return 1;
    }

    process_samples(buffer, SAMPLE_SIZE);

    // Cleanup
    iio_buffer_destroy(buffer);
    iio_context_destroy(ctx);

    return 0;
}

ST ISM330 Gyroscope

This platform features an ST accelerometer / gyroscope. The gyroscope has a selectable angular range of ±125/±250/±500/±1000/±2000 dps

Early units were built using the "ism330dlc", and newer units are built using the "ism330dhcx". These are functionally the same and provide the same channels and performance, but IIO requires you to specify the part number. Our example python/c code will show how to work with either.

The gyroscope is accessed through IIO with channels:

  • anglvel_x
  • anglvel_y
  • anglvel_z
  • timestamp

For example:

# ISM330DHCX
iio_attr -c ism330dhcx_gyro anglvel_x
iio_attr -c ism330dhcx_gyro anglvel_y
iio_attr -c ism330dhcx_gyro anglvel_z
# ISM330DLC
iio_attr -c ism330dlc_gyro anglvel_x
iio_attr -c ism330dlc_gyro anglvel_y
iio_attr -c ism330dlc_gyro anglvel_z
root@tsimx6ul:~# iio_attr -c ism330dhcx_gyro anglvel_x
dev 'ism330dhcx_gyro', channel 'anglvel_x' (input), attr 'raw', value '2359'
dev 'ism330dhcx_gyro', channel 'anglvel_x' (input), attr 'scale', value '0.000153'
dev 'ism330dhcx_gyro', channel 'anglvel_x' (input), attr 'scale_available', value '0.000153 0.000305 0.000611 0.001222'
root@tsimx6ul:~# iio_attr -c ism330dhcx_gyro anglvel_y
dev 'ism330dhcx_gyro', channel 'anglvel_y' (input), attr 'raw', value '-1667'
dev 'ism330dhcx_gyro', channel 'anglvel_y' (input), attr 'scale', value '0.000153'
dev 'ism330dhcx_gyro', channel 'anglvel_y' (input), attr 'scale_available', value '0.000153 0.000305 0.000611 0.001222'
root@tsimx6ul:~# iio_attr -c ism330dhcx_gyro anglvel_z
dev 'ism330dhcx_gyro', channel 'anglvel_z' (input), attr 'raw', value '2761'
dev 'ism330dhcx_gyro', channel 'anglvel_z' (input), attr 'scale', value '0.000153'
dev 'ism330dhcx_gyro', channel 'anglvel_z' (input), attr 'scale_available', value '0.000153 0.000305 0.000611 0.001222'

This shows a snapshot of the x, y, z values. To get the real world value, multiply the scale * the raw value. In this case:

  • X: 0.360927 dps
  • Y: -0.255051 dps
  • Z: 0.422433 dps

The default scale is ±250, but ±250/±500/±1000/±2000 can be selected by setting the scale:

dev 'ism330dhcx_gyro', channel 'anglvel_z' (input), attr 'scale', value '0.000153'
dev 'ism330dhcx_gyro', channel 'anglvel_z' (input), attr 'scale_available', value '0.000153 0.000305 0.000611 0.001222'

To set ±1000, you would write the third available scale:

iio_attr -c ism330dhcx_gyro anglvel_z scale 0.000611

The scale values are not independent on this device, and setting x/y/z will set the scale for all 3.

This driver also supports pulling continuous samples using the buffer interface. These can be accessed using iio_readdev:

iio_readdev ism330dhcx_gyro -T 0 -s 128 > samples.bin

The format of this file is specified with iio_attr:

root@tsimx6ul:~# iio_attr -c ism330dhcx_gyro
dev 'ism330dlc_gyro', channel 'anglvel_x' (input, index: 0, format: le:S16/16>>0), found 3 channel-specific attributes
dev 'ism330dlc_gyro', channel 'anglvel_y' (input, index: 1, format: le:S16/16>>0), found 3 channel-specific attributes
dev 'ism330dlc_gyro', channel 'anglvel_z' (input, index: 2, format: le:S16/16>>0), found 3 channel-specific attributes
dev 'ism330dlc_gyro', channel 'timestamp' (input, index: 3, format: le:S64/64>>0), found 0 channel-specific attributes

The samples are padded to the nearest 8-bytes, so this means the binary format is:

Bits Description
15:0 anglvel_x, little endian, signed
15:0 anglvel_y, little endian, signed
15:0 anglvel_z, little endian, signed
63:0 timestamp, little endian, signed
15:0 Padding

The unix utility hexdump supports formatting options which can parse these fields into their raw values:

root@tsimx6ul:~# hexdump samples.bin --format '1/2 "X:%d " 1/2 "Y:%d " 1/2 "Z:%d " 1/8 "TS:%d" 1/2 "" "\n"' | head -n 40
X:-58 Y:-199 Z:24 TS:419695978925948679
X:-67 Y:-196 Z:29 TS:419701023781322503
X:-64 Y:-197 Z:28 TS:419705968690298631
X:-58 Y:-203 Z:29 TS:419711008204553991

The IIO library can also be used to fill buffers with samples for processing. For example:

#!/usr/bin/env python3

import struct
import iio

ctx = iio.Context('local:')
ctx.set_timeout(0)
dev = ctx.find_device('ism330dhcx_gyro') or ctx.find_device('ism330dlc_gyro')

with open(f'/sys/bus/iio/devices/{dev.id}/sampling_frequency', 'w') as f:
	f.write(f"833.000")

for chan_name in ["anglvel_x", "anglvel_y", "anglvel_z"]:
	chn = dev.find_channel(chan_name)
	chn.enabled = True

# We will request 64 samples at a time
buffer = iio.Buffer(dev, 64, False)
# sample size (3x 16-bit signed data)
sample_size = 6
# Refill and process the buffer
buffer.refill()
data = buffer.read()
for i in range(0, len(data), sample_size):
	if i + sample_size <= len(data):
		x, y, z = struct.unpack('<hhh', data[i:i+sample_size])
		print(f'  anglvel_x={x}, anglvel_y={y}, anglvel_z={z}')

for chn in dev.channels:
	chn.enabled = False

This can also be done using the C library:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iio.h>

#define NUM_CHANNELS 3
#define SAMPLE_SIZE 6 // 3x 16-bit signed data (2 bytes per axis)

void process_samples(struct iio_buffer *buffer, size_t sample_size) {
    char *data = iio_buffer_start(buffer);
    size_t buffer_size = iio_buffer_end(buffer) - iio_buffer_start(buffer);
    int16_t x, y, z;

    for (size_t i = 0; i < buffer_size; i += sample_size) {
        memcpy(&x, &data[i], sizeof(x));
        memcpy(&y, &data[i + sizeof(x)], sizeof(y));
        memcpy(&z, &data[i + 2 * sizeof(x)], sizeof(z));
        printf("anglvel_x=%d, anglvel_y=%d, anglvel_z=%d\n", x, y, z);
    }
}

int main() {
    struct iio_context *ctx;
    struct iio_device *dev;
    struct iio_channel *channels[NUM_CHANNELS];
    struct iio_buffer *buffer;
    const char *channel_names[NUM_CHANNELS] = { "anglvel_x", "anglvel_y", "anglvel_z" };

    // Create context and find device
    ctx = iio_create_local_context();
    if (!ctx || !(dev = iio_context_find_device(ctx, "ism330dhcx_gyro")) &&
        !(dev = iio_context_find_device(ctx, "ism330dlc_gyro"))) {
        fprintf(stderr, "Unable to create context or find device\n");
        iio_context_destroy(ctx);
        return 1;
    }

    // Enable channels and set sampling frequency
    for (int i = 0; i < NUM_CHANNELS; i++) {
        channels[i] = iio_device_find_channel(dev, channel_names[i], false);
        if (!channels[i] || iio_channel_attr_write(channels[i], "sampling_frequency", "833.000") < 0) {
            fprintf(stderr, "Unable to find or configure channel %s\n", channel_names[i]);
            iio_context_destroy(ctx);
            return 1;
        }
        iio_channel_enable(channels[i]);
    }

    // Create buffer and process samples
    buffer = iio_device_create_buffer(dev, 64, false);
    if (!buffer || iio_buffer_refill(buffer) < 0) {
        fprintf(stderr, "Unable to create or refill buffer\n");
        iio_context_destroy(ctx);
        return 1;
    }

    process_samples(buffer, SAMPLE_SIZE);

    // Cleanup
    iio_buffer_destroy(buffer);
    iio_context_destroy(ctx);

    return 0;
}

Jumpers

LEDs

There are two LEDS on the TS-4300 that may be controlled by the user through the sysfs interface. These are colored red and green.

To turn an LED on, write a 1 to 'brightness'. To turn it off again, write a 0.

# Example:  Turn on the Red LED...
echo 1 > /sys/class/leds/red:indicator/brightness

# Turn it off again...
echo 0 > /sys/class/leds/red:indicator/brightness

# Make the green LED act as a heartbeat
echo heartbeat > /sys/class/leds/green:indicator/trigger

A number of triggers are also available for each LED, including timers, disk activity, and heartbeat. These allow the LEDs to represent various system activities as they occur. See the kernel LED documentation for more information on triggers and general use of LED class devices.

A third, blue LED is under the control of the Wizard µC.

Magnetometer

The TS-4300 includes an ST IIS2MDCTR 3-axis magnetometer, which has a magnetic field dynamic range of ±50 gauss (16 bits of precision at up to 150 Hz). It is connected via the primary I2C bus.

The magnetometer is accessed through Linux's industrial I/O (IIO) subsystem as lis2mdl with channels:

  • magn_x
  • magn_y
  • magn_z
  • timestamp

For example:

root@tsimx6ul:~# iio_attr -c lis2mdl -c magn_x
dev 'lis2mdl', channel 'magn_x' (input), attr 'raw', value '630'
dev 'lis2mdl channel 'magn_x' (input), attr 'scale', value '0.001500'
root@tsimx6ul:~# iio_attr -c lis2mdl -c magn_y
dev 'lis2mdl channel 'magn_y' (input), attr 'raw', value '-165'
dev 'lis2mdl channel 'magn_y' (input), attr 'scale', value '0.001500'
root@tsimx6ul:~# iio_attr -c lis2mdl -c magn_z
dev 'lis2mdl channel 'magn_z' (input), attr 'raw', value '9'
dev 'lis2mdl channel 'magn_z' (input), attr 'scale', value '0.001500'

This shows a snapshot of the x, y, z values. To get the measured field strength along each axis, multiply the scale by the raw value to get the actual reading in milligauss. In the above example:

  • X: 0.945 mG (milligauss)
  • Y: -0.2475 mG
  • Z: 0.0135 mG

MicroSD Interface

NPU

The i.MX93 includes an ARM Ethos-U65 NPU to support accelerating neural networks for AI applications.

Features:

  • 256 MACs operating up to 1GHz and 2 OPS/MAC
  • Targets 8-bit and 16-bit integer RNN
  • Handles 8-bit weights

The NPU requires use of the onboard m33 microcontroller to run firmware to manage the NPU.

The software support involves a staging kernel driver providing /dev/ethosu, and a userspace stack that connects to this to run inference.

These steps are still in development and are not functional yet

First, download the ethos firwmare to the boot image:

mkdir /boot/aux/
cd /boot/aux/
git clone https://github.com/nxp-imx/ethos-u-firmware.git -b lf-6.6.3_1.0.0

Then update your /boot/boot.scr to load the required firmware. Add this at the beginning of /boot/source:

load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} /boot/aux/ethos-u-firmware/ethosu_firmware
cp.b ${loadaddr} 0x201e0000 0x20000
bootaux 0x1ffe0000

This will copy this code into the m33's memory and start it. Boot back up to linux and install the driver stack:

git clone https://github.com/nxp-imx/ethos-u-driver-stack-imx.git -b lf-6.6.3_1.0.0
# Install build requirements
apt-get update
apt-get install cmake build-essential libflatbuffers-dev -y

# Build
mkdir build
cd build
cmake ../
make -j2

PWM

Relays

Each of the two relays can carry a 2A max load at up to 250 VAC.

SILO

Sleep Mode

SPI

This platform brings out one SPI controller.

Controller Chip select Device
lpspi4 0 /dev/spidev0.0 Mikrobus SPI

The /dev/spidev* devices can be accessed from Linux. See the kernel spidev documentation for more information on interfacing with the SPI peripherals from C.

Other languages also have bindings to interface with spidev:

UARTs

TS-4300 UARTs

USB

Watchdog

WiFi

XBee/Nimbelink/Airgain

The CN16 header is a 2mm pitch 2x10 header which supports XBEE form factor modules. These include NimbeLink and Digi cell modems, Zigbee, Digi mesh, and other third party radios.

Refer to your specific modem's documentation for its unique requirements. Cell modems churn very quickly in the market and are not fully addressed here. This example has been shown to work on some modems, but may not work on all.


For most Digi modules:

# Turn off all power (this is the startup default)
gpioset 4 8=0 9=0

# Assert NIM_RESET#
gpioset 4 4=0

# Turn on power, most digi modules are 3.3V only
gpioset 4 9=1
sleep 0.5

# deassert NIM_RESET#, reading this switches direction to high-z
gpioget 5=1

At this point, wait bit for the module to startup, then open the UART and begin talking to it.


For most Nimbelink modules

# Turn off all power (this is the startup default)
gpioset 4 8=0 9=0

# Assert NIM_RESET#
gpioset 4 4=0

# If your modem uses USB, enable the USB port.
# Enabling this on non-usb modules can cause issues
# Assert EN_NIM_USB#
gpioset 4 7=0

## Enable power, this must come from your module's datasheet or
## this could cause damage!
## If your module is 4V:
#gpioset 4 8=1
## If your module is 3.3V:
#gpioset 4 9=1

# Wait for modem rails to power up
sleep 1

# deassert NIM_RESET#, reading this switches direction to high-z
gpioget 4 5=1
# Allow modem to start up
sleep .5

# Some modules require asserting/deasserting NIM_PWR_ON
gpioset 4 6=1
sleep .5
gpioset 4 6=0

# Some modules present NIM_STATUS which can be used to determine when the module
# has booted, can poll until this asserts
gpioget 4 30

At this point the modem should be starting up. If it is a USB modem, some start up very quickly, and some may take ~90 seconds before the USB device appears.

Most lower speed modems look like a USB to serial. Others direclty rely on the CPU UART (ttyLP4). Both will need a "ppp" script which may be modem/carrier specific to connect to the cell network. Refer to Airgain's Nimbelink documentation for recommendations on these.

Some higher speed modems look like a USB to Ethernet, which will show up as something like a "usb0" interface. These typically just need DHCP and are ready to transfer data.

Specifications

IO specifications

Power Consumption

Power Input Specifications

External Interfaces

TS-Socket

USB-C Console Connector

Revisions and Changes

PCB Revisions

TS-4300 PCB Changelog

U-Boot Revisions

Depending on context, you can determine your U-Boot revision in one of several ways:

1. The U-Boot build date can be viewed as the first line of USB console output when the unit is powered on. For example:

U-Boot 2016.03-00408-gd450758c91 (Oct 10 2019 - 11:59:08 -0700)

CPU:   Freescale i.MX6UL rev1.2 at 396 MHz
...

2. U-Boot has a version command that outputs similar version information to what is shown above.

3. At a Linux shell, the following command prints the version strings of any U-Boot and/or SPL image that is present in eMMC:

strings /dev/mmcblk0boot0 | grep '^U-Boot .*(.*)'

The output is the same string(s) that will be printed on the console at board startup.

TS-9370 U-Boot Changelog

FPGA Revisions

To determine the version of the FPGA on a TS-4300, see the startup output in U-Boot:

U-Boot 2024.04-00047-gd6a3cd9d75c-dirty (Mar 07 2025 - 21:03:22 +0000)

Reset Status: POR 

CPU:   NXP i.MX93(52) Rev1.1 A55 at 1700 MHz
CPU:   Extended Industrial temperature grade  (-40C to 125C) at 32C

DRAM:  1 GiB
Model: embeddedTS TS-4300
Core:  200 devices, 25 uclasses, devicetree: fit
MMC:   FSL_SDHC: 0, FSL_SDHC: 1
Loading Environment from MMC... OK
In:    serial
Out:   serial
Err:   serial

BuildInfo:
  - ELE firmware version 1.2.0-38f309fe

switch to partitions #0, OK
mmc0(part 0) is current device
FPGA Bootloader: v0.1.2
FPGA TS-4300: v0.2.2
flash target is MMC:0

In this case, the bootloader is v0.1.2, and the FPGA application is v0.2.2.

See the FPGA Updates section for more details on updating to the latest.

TS-4300 FPGA Changelog

Wizard Firmware Revisions

TS-9370 Wizard Changelog

Root Filesystem Software Images

Debian Changelog

TS-9390 Debian Changelog

Product Notes

FCC Advisory

This equipment generates, uses, and can radiate radio frequency energy and if not installed and used properly (that is, in strict accordance with the manufacturer's instructions), may cause interference to radio and television reception. It has been type tested and found to comply with the limits for a Class A digital device in accordance with the specifications in Part 15 of FCC Rules, which are designed to provide reasonable protection against such interference when operated in a commercial environment. Operation of this equipment in a residential area is likely to cause interference, in which case the owner will be required to correct the interference at his own expense.

If this equipment does cause interference, which can be determined by turning the unit on and off, the user is encouraged to try the following measures to correct the interference:

Reorient the receiving antenna. Relocate the unit with respect to the receiver. Plug the unit into a different outlet so that the unit and receiver are on different branch circuits. Ensure that mounting screws and connector attachment screws are tightly secured. Ensure that good quality, shielded, and grounded cables are used for all data communications. If necessary, the user should consult the dealer or an experienced radio/television technician for additional suggestions. The following booklets prepared by the Federal Communications Commission (FCC) may also prove helpful:

How to Identify and Resolve Radio-TV Interference Problems (Stock No. 004-000-000345-4) Interface Handbook (Stock No. 004-000-004505-7) These booklets may be purchased from the Superintendent of Documents, U.S. Government Printing Office, Washington, DC 20402.

Limited Warranty

See our Terms and Conditions for more details.


WARNING: Setting any of the eMMC's write-once registers (e.g. enabling enhanced area and/or write reliability) will immediately void ALL of our return policies and replacement warranties. This includes but is not limited to: the 45-day full money back evaluation period; any returns outside of the 45-day evaluation period; warranty returns within the 1 year warranty period that would require SBC replacement. Our 1 year limited warranty still applies, however it is at our discretion to decide if the SBC can be repaired, no warranty replacements will be provided if the OTP registers have been written.

Trademarks

Arm and Cortex are registered trademarks of Arm Limited (or its subsidiaries) in the US and/or elsewhere.