TS-9370

From embeddedTS Manuals
WARNING: This product is still in development. This means the documentation and product itself will change several times before the product is deemed ready for Engineering Sampling. Please force-refresh (shift-f5 or ⌘-shift-R on most browsers) to clear your cache when visiting this page to ensure you are viewing the most recent version of this documentation. Of course also please check back often as this information is subject to change.


TS-9370
ts-9370.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

TS-9370 Models

U-Boot

Debian

Debian 12 - Bookworm

Debian 12 - Getting Started

TS-iMX93 Bookworm Getting Started

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

The ADCs on the TS-9370 are supplied primarily via the FPGA. The i.MX93 at present (?) supplies only on-die temperature sensors. The SMC's ADCs supply dedicated voltage monitoring functions, and it also supplies an additional on-die temperature sensor that is physically isolated from the main CPU.

Audio Codec

Battery-backed RTC

Bluetooth

CAN

The TS-9370 has two FlexCAN ports that use the Linux SocketCAN implementation. These are available on the CN24 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

DisplayPort

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 Registers

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

Offset Description
0x0000 Model/Rev Info[1]
0x0040 FPGA GPIO block #0
0x0080 FPGA GPIO block #1
0x00C0 FPGA GPIO block #2
  1. See the #FPGA Revisions section for how to interpret these registers.

FPGA GPIO Instances

Note: Unlike GPIO into the CPU, at present the FPGA GPIOs do not support interrupts.

Each of the three GPIO blocks can manage up to 32 IO lines. The 32-bit registers controlling each block are defined as follows:

Offset Read Function Write Function
0x00 Output Enables Set OE Bits
0x04 Reserved Clear OE Bits
0x08 Output Data Set Data Bits
0x0c Input Data Clear Data Bits
0x10 Reserved Reserved
0x14 Reserved Reserved
0x18 Reserved Reserved
0x1c Reserved Reserved


FPGA Updates

TS-9370 FPGA Updates

GPIO

Note: This section is incomplete at this time.

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 if input power has failed:

gpioget $(gpiofind POWER_FAIL_3V)

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 (i.e., CPU GPIOs).

In the following table, gpiochips 0, 3 and 4 are on the CPU, and gpiochips 5 through 7 are on the FPGA. 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 Chip Bank Line Location
EN_GREEN_LED#[1] FPGA 0 0 Green LED
EN_YEL_LED#[1] FPGA 0 1 Yellow LED
EN_RED_LED#[1] FPGA 0 2 Red LED
NIM_RESET# FPGA 0 4 CN16 pin 5
NIM_CTS# FPGA 0 5 CN16 pin 12
NIM_PWR_ON# FPGA 0 6 CN16 pin 20
EN_NIM_USB# FPGA 0 7
EN_NIM_4V FPGA 0 8
EN_NIM_3P3V FPGA 0 9
NIM_TXD[1] FPGA 0 10 CN16 pin 3
NIM_RXD[1] FPGA 0 11 CN16 pin 2
EN_USB_HOST1_VBUS FPGA 0 12
EN_USB_HOST2_VBUS FPGA 0 13
MIKRO_TXD[1] FPGA 0 14 CN17 pin 13
MIKRO_RXD[1] FPGA 0 15 CN17 pin 14
MIKRO_SPI_CLK[1] FPGA 0 16
MIKRO_SPI_CS#[1] FPGA 0 17
MIKRO_SPI_MISO[1] FPGA 0 18
MIKRO_SPI_MOSI[1] FPGA 0 19
MIKRO_RESET# FPGA 0 20 CN17 pin 2
MIKRO_AN[1] FPGA 0 21
MIKRO_PWM[1] FPGA 0 22
MIKRO_INT[1] FPGA 0 23
EN_LS_OUT_1 FPGA 0 24
EN_LS_OUT_2 FPGA 0 25
EN_LS_OUT_3 FPGA 0 26
EN_LS_OUT_4 FPGA 0 27
EN_HS_SW FPGA 0 28 CN24 Terminal Block Pin 2 (HS_SW_OUTPUT)
DIO_FAULT# FPGA 0 29
MAGNET_IRQ[1] FPGA 0 30
GYRO_IRQ[1] FPGA 0 31
EN_2ND_PORT_232 FPGA 1 0
EN_CAN_1[1] FPGA 1 1
EN_CAN_2[1] FPGA 1 2
PUSH_SW# FPGA 1 22
AN_SEL_0 FPGA 1 23
AN_SEL_1 FPGA 1 24
EN_CL_1_2 FPGA 1 25
EN_CL_3 FPGA 1 26
DP_RESET# FPGA 1 27
NO_SCAP_CHRG# FPGA 1 28
EN_SPKR_AMP FPGA 1 29
BT_EN FPGA 1 30
WIFI_EN FPGA 1 31
EN_ADC_3_12V FPGA 2 0
EN_ADC_1_2_12V FPGA 2 1
  1. 1.00 1.01 1.02 1.03 1.04 1.05 1.06 1.07 1.08 1.09 1.10 1.11 1.12 1.13 1.14 1.15 1.16 1.17 Claimed by driver. Not available for use.

See also:


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 and one for off-board devices.

Device Address Description
/dev/i2c-0
0x10 #Audio CODEC
0x1e #Magnetometer[1]
0x54 Wizard µC
0x60 #RTC
0x68[2] DisplayPort Transceiver
0x6a #IMU[1]
/dev/i2c-1 0x25[3] NXP PMIC
/dev/i2c-3 [4] Mikroe bus header[5]
  1. 1.0 1.1 Some models
  2. Presumed but not confirmable until the part goes through bring-up testing
  3. Slight chance of changing if the PMIC can be set to a different address
  4. Address depends on what if any module is present.
  5. 3.3V signaling

IMU

The TS-9370 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). They are accessed as ism330dlc_accel and ism330dlc_gyro, respectively.

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;
}

Interrupts

Jumpers

LEDs

The TS-9370 provides the CPU with access to the following LEDs:

  • red:indicator
  • yellow:indicator
  • green:indicator

Under Linux, these LEDs can be controlled via the sysfs LED interface. For example, to turn on the red LED:

echo 1 > /sys/class/leds/red:indicator/brightness

A number of triggers are also available, 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.

Magnetometer

The TS-9370 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

PWM

SILO

Sleep Mode

SPI

Supervisory Microcontroller

UARTs

Location Compatibility Device UART /dev Connection TX / + Loc. RX / - Loc.
i.MX93 USB via Wizard UART1 ttyLP0 USB-C Console
i.MX93 RS-232 UART2 [1] ttyLP1 Primary RS-232 CN24-B pin 23 CN24-B pin 22
i.MX93 GPIO_00/GPIO_01 via MAX10 3.3 V UART5 ttyLP4 Nimbelink CN16 pin 3 (TX) CN16 pin 4 (RX)
i.MX93 GPIO_04/GPIO_05 1.8 V UART6 ttyLP5 BT + flow control
i.MX93 GPIO_08/GPIO_09 RS-485 UART7 ttyLP6 Primary RS-485 [2] CN24-B pin 21 CN24-B pin 20
i.MX93 GPIO_12/GPIO_13 via MAX10 RS-232/RS-485 UART8 ttyLP7 Secondary RS-232/485 [2] CN24-B pin 15 CN24-B pin 14
3.3 V Daughtercard (HD10) [3]
3.3 V Nimbelink [3]
  1. Also goes to the FPGA. If used via the FPGA, do not connect these pins.
  2. 2.0 2.1 See notes below regarding primary vs. secondary RS-485/232/422.
  3. 3.0 3.1 These are potential UART connection points that need a UART made available.

Primary vs. Secondary RS-485/232

Unlike the primary RS-485 and RS-232 UARTs, where both the hardware and device drivers are pre-set correctly at boot time, the secondary RS-485/232 interfaces must be configured before use.

For a second RS-232 connection, set the EN_2ND_PORT_232 GPIO, and then /dev/ttyLP7 (UART8) will function via the on-board RS-232 transceiver:

gpioset $(gpiofind EN_2ND_PORT_232)=1

The EN_2ND_PORT_232 GPIO defaults to 0 (RS-485) at power-up, but to transmit as well as receive, the LPUART driver for /dev/ttyLP7 (UART8) must be set to RS-485.

USB

Watchdog

WiFi

XBee/Nimbelink

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.

For Cell radios that use USB this must be enabled. This turns off USB to the bottom port on the dual high type A connector. Only enable if this is compatible with your module:

# Turn on the USB
gpioset $(gpiofind EN_NIM_USB_N)=0

This header can provide 3.3V or 4V as some cell radios require higher voltage. Check the datasheet of your module before turning on any power to this header. Most cell modems require 4V, while many other radios require 3.3V.

# For 4V modules:
gpioset $(gpiofind EN_NIM_3P3V)=0

# For 3.3V modules:
# gpioset $(gpiofind EN_NIM_3P3V)=1

# And then:
gpioset $(gpiofind EN_CELL_MODEM_PWR)=1

Many NimbeLink modems require NIMBEL_PWR_ON_N to be toggled before they "turn on". If one thinks of turning on the power (above) as "attaching the battery" of a cellphone, toggling NIMBEL_PWR_ON is analogous to "pressing the power button" for a second. Here's a script to do exactly that.

gpioset $(gpiofind NIM_PWR_ON_N)=0
sleep 1
gpioset $(gpiofind NIM_PWR_ON_N)=1

If your device doesn't turn on or off as expected, be sure to consult the manual for it. There are often device-specific procedures for powering on and sometimes even custom AT command sequences needed in order to safely power off.

Some Nimbelink modules supply RESET and STATUS signals, which on this product can be controlled via GPIO.

Specifications

IO specifications

Power Consumption

Power Input Specifications

External Interfaces

Audio Header (HD1)

Battery Connector

The RTC uses replacable lithium CR1632 batteries. Through PCB revision P3, the battery's "+" terminal faces the Ethernet connectors.

WARNING: Installing the battery backwards or shorting the "+" terminal to ground will discharge the battery, resulting in a failure to keep time when power is off and possible RTC clock errors when Linux is running.

CN24 Terminal Block

[1]
CN24-A
Pin # Description
1 GND
2 HS_SW_OUTPUT
3 DIO_4_STC
4 DIO_3_STC
5 DIO_2_STC
6 DIO_1_STC
7 AN_3_STC
8 AN_2_STC
9 AN_1_STC
10 SPKR_2
11 SPKR_1
12 GND
CN24-B
Pin # Description
13 GND
14 2ND_RXD_485_MINUS
15 2ND_TXD_485_PLUS
16 CAN_2_L
17 CAN_2_H
18 CAN_1_L
19 CAN_1_H
20 PRIM_485_MINUS
21 PRIM_485_PLUS
22 PRIM_232_RXD
23 PRIM_232_TXD
24 GND
  1. Image copied from the TS-7180; needs updating.

See also:

Daughter Card Header (HD10)

DisplayPort Connector (CN20)

Ethernet Connectors

MicroSD Connector

mikroe (mikroBUS) Header (CN17)

The Mikrobus provides compatibility with Mikroe's 750+ click boards. This interface has SPI, I2C, PWM, analog, a TTL UART, 3.3V and 5V.


Pin Name Description
1 MIKRO_AN How does the FPGA provide this (which is where this signal goes)?
2 MIKRO_RESET# GPIO from FPGA
3 MIKRO_SPI_CS# lpspi4
4 MIKRO_SPI_CLK lpspi4
5 MIKRO_SPI_MISO lpspi4 (SDO)
6 MIKRO_SPI_MOSI lpspi4 (SDI)
7 VDD_3V3 3.3V source
8 GND GND
Pin Description
16 MIKRO_PWM From TPM, FPGA should optionally route here. Currently only goes to HD10.
15 MIKRO_INT Should funnel into FPGA_CPU_IRQ.
14 MIKRO_RXD UART5 RXD
13 MIKRO_TXD UART5 TXD
12 I2C4_SCL /dev/i2c-3
11 I2C4_SDA /dev/i2c-3
10 SW_5.2V 5V Source
9 GND GND

Power Connector

Push Button

USB Ports

USB-C Console Connector

XBee/Nimbelink Header (CN16)

TS-9370 Revisions and Changes

PCB Revisions

TS-9370 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 running system, view its model/rev/info block:

root@tsimx9:~# memtool md -l 0x28000000+0x10
28000000: 00009370 0000011f 2c21926e 33823234                p.......n.!,42.3
root@tsimx9:~#


Date Model Version Hash Description
2024-08-22 TS-9390 0x0106 08073988 33521790
  • Tri-stated ball A12 (uart2_rxd_3v_pad)
2024-10-07 TS-9370 0x011f 2c21926e 33823234
  • Moved RTS lines for RS-485 auto-TXEN.
  • Clock routing for DisplayPort daughtercard
  • Fixes 66 MHz operation

Supervisory Microcontroller (SMC) Firmware Revisions

TS-9370 SMC Changelog

Root Filesystem Software Images

Debian Changelog

See our Debian release archive for all released images, including the latest for each supported Debian version.

TS-9370 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.