TS-9370
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. |
| 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 |
| Serial | Working | Tested all TTL, RS-485, RS-232 ports. Verified CPU's RTS ports toggle automatically supporting TX-EN. Tested picocom to work on all ports to send/receive data. Testing with linux-serial-test shows a 100ms rx delay is required when sending at full speed. Some UART test applications are showing dropped characters immediately on open which is being investigated. Does not appear to drop data after the port is opened and some initial data is sent. |
| CAN | Working | Tested against CAN devices. Controller and installed transceiver support CAN-FD, but FD devices have not yet been tested. |
| GPIO | Working | Tested CPU IO and FPGA controllers to be working, and support interrupts on rising/falling/both edges. |
| Nimbelink | Working | Tested UARTs. Examples yet to be provided on suggested control of the other I/O for bringing up specific modems. |
| Terminal Block Circuit Breaker | Working | Preliminarily tested, requires FPGA v0.2.6 or later. |
| 4-20mA/0-13.2V ADC | Completed | Working with FPGA v0.2.8 and Kernels after 07/16/2025 |
| SPI | Working | Preliminarily tested with default spidev + loopbacks and an oscilloscope. |
| FPGA XBAR | Working | Tested in FPGA v0.2.7 and later. |
| i.MX93 NPU | Working | Tested with 6.6.52 kernel. See NPU documentation section for script to install software support. |
| DisplayPort | Working | Working as of P4 |
| Mikrobus | Working | All Mikrobus Interfaces, SPI, I2C, PWM, INT, RST, TXD/RXD, and MIKRO_AN have had preliminary testing. |
| Accelerometer-Gyro | Working | Supports individual samples with iio_info. |
| Magnetometer | Working | Supports individual samples with iio_info. |
| WIFI | Working | Tested client with wpa_supplicant and ap with hostapd |
| Audio Codec | Working | Tested mono in and mono out with aplay/arecord. Works at 48KHz, requires configuration change for 44.1KHz |
| Bluetooth | Working | Tested with bluetoothctl scan |
| i.MX93 low power sleep | Working | Suspend to ram works, tested with wakeup from tty |
| i.MX93 M33 | Working | 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. Tested with sysfs remoteproc interface |
| Image Replicator | Working | This is the USB tool to rewrite the image on production units. |
| microSD | Not working | Not working on the TS-9370 P4, will be fixed on the next revision. |
| 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. |
| 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. |
| Open Source FPGA | TODO | The FPGA is planned to be open sourced, and will be after some additional features are stabilized. |
![]() | |
| Product Page | |
| Documentation | |
|---|---|
| Schematic | |
| FTP Path | |
| Processor | |
| NXP i.MX9352 | |
| 1.7 GHz Arm® Cortex®-A55/M33 | |
| i.MX93 Product Page | |
| CPU Documentation |
Overview
The TS-9370 is a single board computer providing an i.MX93 based SOC, an Intel MAX 10 FPGA, an onboard eMMC, MicroSD, WIFI, BT, an amplified speaker output, and an assortment of ADC, and wide range IO.
Getting Started
A Linux workstation is recommended and assumed for development using this documentation. For users in Windows or OSX, we recommend virtualizing Linux using VMWare or similar to make the full power of Linux available. The developer should be comfortable with Linux to work with embedded Linux on the target platform. Most of our platforms run Debian, which is recommended for ease of use if there is no personal distribution preference.
The main reasons that Linux is useful are:
- Linux filesystems on the microSD card can be accessed on the PC.
- More ARM cross-compilers are available.
- If recovery is needed, a bootable medium can be written.
- A network filesystem can be served.
- Builds such as Linux kernel, Buildroot, Yocto, and distro-seed will not work from WSL1/2 on a case-insensitive filesystem.
| WARNING: | Be sure to take appropriate Electrostatic Discharge (ESD) precautions. Disconnect the power source before moving, cabling, or performing setup procedures. Inappropriate handling may cause damage to the board. |
U-Boot
U-Boot is a bootloader and comes preinstalled on this board. The U-Boot bootloader is loaded in the eMMC hardware boot partitions in /dev/mmcblk0boot0. U-Boot sets up the hardware and then loads the OS from the available storage devices. U-Boot allows booting images from the microSD, eMMC, NFS, or USB. Most users will not need to customize u-boot further, and can proceed to the #Debian, #Ubuntu, #Yocto, or #Buildroot sections for information on application development.
U-Boot Standard Boot
This platform uses u-boot's "Standard Boot" as the method to search available storage media for a bootable operating system.
By default the board will attempt booting to these devices:
| boot_target | Description |
|---|---|
| usb | U-boot will probe for any available USB Storage media |
| mmc1 | microSD |
| mmc0 | Onboard eMMC flash |
| pxe | Network boot |
The order, and the enabled boot targets can be customized by changing the variable:
# Boot to eMMC only:
setenv boot_targets mmc0
# Boot to USB, then eMMC:
setenv boot_targets usb mmc0
# Default targets
setenv boot_targets usb mmc1 mmc0 pxe
While searching each media, u-boot searches for valid boot methods on each device.
| bootmeths | Description |
|---|---|
| script | Legacy distro-boot scripts. Looks for /boot/boot.scr, /boot/boot.scr.uimg, and executes those |
| extlinux | Looks for extlinux/extlinux.conf. See the syslinux project for more details. |
| efi | Looks for an EFI boot partition, and executes the EFI payload such as grub, or the Linux kernel. |
| pxe | For PXE boot only, checks for valid DHCP describing boot. |
The order, and the enabled boot methods can be customized by changing the variable:
# Only boot extlinux:
setenv bootmeths extlinux
# Try script, then EFI
setenv bootmeths script efi
# Default bootmeths
setenv bootmeths script extlinux efi pxe
On our factory preprogrammed images the typical boot skips USB, mmc1, and boots to a script on eMMC.
U-Boot Environment
The U-Boot environment is stored in the on-board eMMC flash in the /dev/mmcblk0boot0 partition.
It can be accessed from the u-boot shell:
# Print all environment variables
env print -a
# Sets the variable bootdelay to 5 seconds
env set bootdelay 5
# Variables can also contain commands
env set hellocmd 'led green:indicator off; echo Hello world; led green:indicator on;'
# Execute commands saved in a variable
env run hellocmd
# Commit env changes to the emmc
# Otherwise changes are lost
env save
# Remove a variable
env delete hellocmd
# Restore env to default
env default -a
# Save default env or it will revert next reset
env save
Booting From NFS
U-Boot Development
The TS-9370 u-boot is built using Yocto, even if booting other Distributions such as Debian/Ubuntu. See the Yocto Walnascar build instructions for getting started.
Once the yocto environment is set up, build a u-boot with:
bitbake bitbake imx-boot u-boot-imx
In your Yocto build directory this will generate a tmp/deploy/images/ts9370/imx-boot-ts9370-sd.bin-flash_singleboot file. This should be written to mmcblk0boot0 at offset 0.
Install the dependencies on your Linux Workstation:
- wizusb
- access to Wizard registers over USB
- NXP mfgtools
- uuu tool for booting/flashing i.MX93 over USB
Connect a TC2030-USB cable to CN66. This connector is on the corner near the type A USB. Keep in mind this is run in parallel with the top port of J2 which should not have anything connected while CN66 is connected.
To load a singleboot image on the board, run:
# Switch boot to USB Serial Downloader
wizusb 17 0x1
# Reset the board, so it comes up in the new strapping
wizusb 8 0x1
# Boot + Program u-boot over USB:
uuu -b emmc /path/to/singleboot
# Switch boot to USDHC1 8-bit eMMC
wizusb 17 0x2
# Reset the board, so it comes up in the new strapping
wizusb 8 0x1
Debian
Debian 13 - Trixie
Debian 13 - Getting Started
This Debian release is available in 3 flavors with various packages.
| Image | Estimated Size | Description |
|---|---|---|
| tsimx9-debian-13-trixie-x11-latest.tar.xz | 1677 MiB |
|
| tsimx9-debian-13-trixie-headless-latest.tar.xz | 1369 MiB |
|
| tsimx9-debian-13-trixie-minimal-latest.tar.xz | 792 MiB |
|
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-13-trixie-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-13-trixie-x11-latest.tar.xz | tar --numeric-owner -xJ -C /mnt/emmc/
umount /mnt/emmc
sync
Debian 13 - 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 13 - 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 151790fab3bf3a1751a269618491b54984e192aa19319fc667397d45ec8dee5bTo have this take effect immediately:
service networking restart
For more information on configuring Wi-Fi, see Debian's guide here.
Debian 13 - 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 13 - 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 13 - 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 13 - 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 13 - Cross Compiling
For building kernels targeting the Debian images we recommend using cqfd. This allows building a compatible kernel using identical tools from any build host.
To build a kernel for the embeddedTS i.MX93 based platforms:
- Make sure docker is installed, and can run hello world:
- docker run hello-world
- Make sure cqfd is installed:
# Install cqfd-linux-lts builds scripts
git clone https://github.com/embeddedTS/cqfd-linux-lts
cd cqfd-linux-lts
# Clone the Linux kernel
git clone https://github.com/embeddedTS/linux-tsimx.git -b lf-6.12.y linux
# Build the kernel:
./build_tsimx93_tar.sh
This will set up the container including Debian's cross compilers, and any dependencies needed to build the kernel. This runs once on initial setup, and the only when the Dockerfile is modified. Next, it builds the defconfig, compiles the kernel, and assembles a tar of the build objects for that image. After it completes, it will print out the path to the tar file.
At this point, the tarball can be unpacked to a bootable media for the device. This can be done from a booted device, or by mounting removable media from a host Linux workstation. For example, if the root folder of the target filesystem to be updated is mounted to /mnt/, the following can be used to unpack the above tarball:
# Ensure the target filesystem is mounted to /mnt first!
# Extract kernel tarball to target filesystem,
tar xhf linux-*.tar.gz -C /mnt
| Note: | The tar h argument to tar is necessary on recent distributions that merge /usr/. Not using it will render the system unbootable. |
Debian 13 - Compile the aarch64 Kernel
For building kernels targeting the Debian images we recommend using cqfd. This allows building a compatible kernel using identical tools from any build host.
To build a kernel for the embeddedTS i.MX93 based platforms:
- Make sure docker is installed, and can run hello world:
- docker run hello-world
- Make sure cqfd is installed:
# Install cqfd-linux-lts builds scripts
git clone https://github.com/embeddedTS/cqfd-linux-lts
cd cqfd-linux-lts
# Clone the Linux kernel
git clone https://github.com/embeddedTS/linux-tsimx.git -b lf-6.12.y linux
# Build the kernel:
./build_tsimx93_tar.sh
This will set up the container including Debian's cross compilers, and any dependencies needed to build the kernel. This runs once on initial setup, and the only when the Dockerfile is modified. Next, it builds the defconfig, compiles the kernel, and assembles a tar of the build objects for that image. After it completes, it will print out the path to the tar file.
At this point, the tarball can be unpacked to a bootable media for the device. This can be done from a booted device, or by mounting removable media from a host Linux workstation. For example, if the root folder of the target filesystem to be updated is mounted to /mnt/, the following can be used to unpack the above tarball:
# Ensure the target filesystem is mounted to /mnt first!
# Extract kernel tarball to target filesystem,
tar xhf linux-*.tar.gz -C /mnt
| Note: | The tar h argument to tar is necessary on recent distributions that merge /usr/. Not using it will render the system unbootable. |
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 |
|
| tsimx9-debian-12-bookworm-headless-latest.tar.xz | 961 MiB |
|
| tsimx9-debian-12-bookworm-minimal-latest.tar.xz | 379 MiB |
|
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 151790fab3bf3a1751a269618491b54984e192aa19319fc667397d45ec8dee5bTo 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
Debian provides cross compilers in their matching distribution. For example, if you are running Debian 12 on your workstation or VM:
sudo dpkg --add-architecture arm64
sudo apt-get update
# Cross compiler
sudo apt-get install gcc-aarch64-linux-gnu
# Install any needed libraries/headers from Debian 12 for arm64:
sudo apt-get install libc6-dev:arm64 libgpiod-dev:arm64
A hello world can be built with:
aarch64-linux-gnu-gcc hello.c -o hello
Tools like docker/cqfd can make this simpler to run Debian 12 in a container just for the build. See our cqfd hello world project that demonstrates a simpler way to run these from most Linux systems.
Debian 12 - Compile the aarch64 Kernel
For building kernels targeting the Debian images we recommend using cqfd. This allows building a compatible kernel using identical tools from any build host.
To build a kernel for the embeddedTS i.MX93 based platforms:
- Make sure docker is installed, and can run hello world:
- docker run hello-world
- Make sure cqfd is installed:
# Install cqfd-linux-lts builds scripts
git clone https://github.com/embeddedTS/cqfd-linux-lts
cd cqfd-linux-lts
# Clone the Linux kernel
git clone https://github.com/embeddedTS/linux-tsimx.git -b lf-6.12.y linux
# Build the kernel:
./build_tsimx93_tar.sh
This will set up the container including Debian's cross compilers, and any dependencies needed to build the kernel. This runs once on initial setup, and the only when the Dockerfile is modified. Next, it builds the defconfig, compiles the kernel, and assembles a tar of the build objects for that image. After it completes, it will print out the path to the tar file.
At this point, the tarball can be unpacked to a bootable media for the device. This can be done from a booted device, or by mounting removable media from a host Linux workstation. For example, if the root folder of the target filesystem to be updated is mounted to /mnt/, the following can be used to unpack the above tarball:
# Ensure the target filesystem is mounted to /mnt first!
# Extract kernel tarball to target filesystem,
tar xhf linux-*.tar.gz -C /mnt
| Note: | The tar h argument to tar is necessary on recent distributions that merge /usr/. Not using it will render the system unbootable. |
Ubuntu
Ubuntu 24.04 - Noble
Ubuntu 24.04 - Getting Started
This Ubuntu release is available in 2 flavors with various packages.
| Image | Estimated Size | Description |
|---|---|---|
| tsimx9-ubuntu-24.04-noble-x11-latest.tar.xz | 1640 MiB |
|
| tsimx9-ubuntu-24.04-noble-headless-latest.tar.xz | 1409 MiB |
|
| tsimx9-ubuntu-24.04-noble-minimal-latest.tar.xz | 898 MiB |
|
The default login is "user/user" which includes sudo permissions.
To write this to an SD card, first partition the SD card to have one large ext3, or ext4 partition. See the guide here for more information. 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-ubuntu-24.04-noble-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.ext4 /dev/mmcblk2p1
mkdir /mnt/emmc
mount /dev/mmcblk2p1 /mnt/emmc
wget -qO- https://files.embeddedts.com/ts-arm-sbc/ts-9370-linux/distributions/ubuntu/tsimx9-ubuntu-24.04-noble-x11-latest.tar.xz | tar --numeric-owner -xJ -C /mnt/emmc/
umount /mnt/emmc
sync
Ubuntu 24.04 - Networking
The network in Ubuntu is configured netplan. For complete documentation, see Netplan'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/netplan/ethernet.yaml and add:
network:
version: 2
renderer: networkd
ethernets:
end0:
dhcp4: true
dhcp6: true
Static IP on end0. Edit the file /etc/netplan/ethernet.yaml and add:
network:
version: 2
renderer: networkd
ethernets:
end0:
dhcp4: no
addresses: [192.168.0.50/24]
gateway4: 192.168.0.1
nameservers:
addresses: [8.8.8.8,8.8.4.4]
After creating the yaml file, set the appropriate permissions and apply the netplan:
sudo chmod 600 /etc/netplan/*.yaml
sudo netplan apply
Ubuntu 24.04 - WIFI Client
Wireless configuration under Ubuntu, similar to Ethernet, also uses netplan for configuration. For example, create /etc/netplan/wifi.yaml:
network:
version: 2
renderer: networkd
wifis:
wlan0:
dhcp4: yes
dhcp6: yes
access-points:
"yourssid":
password: yourpassphrase"
After creating the yaml file, set the appropriate permissions and apply the netplan:
sudo chmod 600 /etc/netplan/*.yaml
sudo netplan apply
Ubuntu 24.04 - 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.
Ubuntu 24.04 - Installing New Software
Ubuntu provides the apt-get system which lets you manage pre-built applications. Before you do this you need to update Ubuntu's list of package versions and locations. This assumes you have a valid network connection to the internet.
apt-get update
For example, lets say you wanted to install openjdk for Java support. You can use the apt-cache command to search the local cache of Debian's packages.
root@ts-imx6:~# apt-cache search openjdk jvm-7-avian-jre - lightweight virtual machine using the OpenJDK class library freemind - Java Program for creating and viewing Mindmaps icedtea-7-plugin - web browser plugin based on OpenJDK and IcedTea to execute Java applets default-jdk - Standard Java or Java compatible Development Kit default-jdk-doc - Standard Java or Java compatible Development Kit (documentation) 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) icedtea-7-jre-jamvm - Alternative JVM for OpenJDK, using JamVM openjdk-7-dbg - Java runtime based on OpenJDK (debugging symbols) openjdk-7-demo - Java runtime based on OpenJDK (demos and examples) openjdk-7-doc - OpenJDK Development Kit (JDK) documentation openjdk-7-jdk - OpenJDK Development Kit (JDK) openjdk-7-jre - OpenJDK Java runtime, using Hotspot Zero openjdk-7-jre-headless - OpenJDK Java runtime, using Hotspot Zero (headless) openjdk-7-jre-lib - OpenJDK Java runtime (architecture independent libraries) openjdk-7-source - OpenJDK Development Kit (JDK) source files uwsgi-app-integration-plugins - plugins for integration of uWSGI and application uwsgi-plugin-jvm-openjdk-7 - Java plugin for uWSGI (OpenJDK 7) uwsgi-plugin-jwsgi-openjdk-7 - JWSGI plugin for uWSGI (OpenJDK 7)
In this case you will likely want openjdk-7-jre to provide a runtime environment, and possibly openjdk-7-jdk to provide a development environment.
Once you have the package name you can use apt-get to install the package and any dependencies. This assumes you have a network connection to the internet.
apt-get install openjdk-7-jre
# You can also chain packages to be installed
apt-get install openjdk-7-jre nano vim mplayer
For more information on using apt-get refer to Ubuntu's documentation here.
Ubuntu 24.04 - Setting up SSH
To install ssh, install the package as normal with apt-get:
apt-get install openssh-server
Make sure the device is configured on the network and set a password for the remote user. SSH will not allow remote connections without a password or a valid SSH key pair.
passwd root
| Note: | The default OpenSSH server will not permit root to login via SSH as a security precaution. To allow root to log in via ssh anyway, edit the /etc/ssh/sshd_config file and add the line PermitRootLogin yes in the authentication section. This change will take effect after reboot or after sshd service restart.
|
After this setup it is now possible to connect from a remote PC supporting SSH. On Linux/OS X this is the "ssh" command, or from Windows using a client such as PuTTY.
| Note: | If a DNS server is not present on the target network, it is possible to save time at login by adding "UseDNS no" in /etc/ssh/sshd_config. |
Ubuntu 24.04 - 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. |
Ubuntu 24.04 - Cross Compiling
Ubuntu 24.04 - Compile the aarch64 Kernel
For building kernels targeting the Debian images we recommend using cqfd. This allows building a compatible kernel using identical tools from any build host.
To build a kernel for the embeddedTS i.MX93 based platforms:
- Make sure docker is installed, and can run hello world:
- docker run hello-world
- Make sure cqfd is installed:
# Install cqfd-linux-lts builds scripts
git clone https://github.com/embeddedTS/cqfd-linux-lts
cd cqfd-linux-lts
# Clone the Linux kernel
git clone https://github.com/embeddedTS/linux-tsimx.git -b lf-6.12.y linux
# Build the kernel:
./build_tsimx93_tar.sh
This will set up the container including Debian's cross compilers, and any dependencies needed to build the kernel. This runs once on initial setup, and the only when the Dockerfile is modified. Next, it builds the defconfig, compiles the kernel, and assembles a tar of the build objects for that image. After it completes, it will print out the path to the tar file.
At this point, the tarball can be unpacked to a bootable media for the device. This can be done from a booted device, or by mounting removable media from a host Linux workstation. For example, if the root folder of the target filesystem to be updated is mounted to /mnt/, the following can be used to unpack the above tarball:
# Ensure the target filesystem is mounted to /mnt first!
# Extract kernel tarball to target filesystem,
tar xhf linux-*.tar.gz -C /mnt
| Note: | The tar h argument to tar is necessary on recent distributions that merge /usr/. Not using it will render the system unbootable. |
Yocto
The Yocto Project is a framework for creating customized, production-grade Linux systems specifically geared toward embedded devices. It provides a flexible set of tools, metadata, and build definitions that allow developers to assemble complete Linux images tailored to purpose-built hardware, strict resource limits, and long product lifecycles. Its build system, based on OpenEmbedded and BitBake, enables deterministic image generation
Yocto emphasizes reproducibility, maintainability, and scalability for long-term embedded deployments. Its layered architecture allows hardware support, policy decisions, and application logic to be separated cleanly, making it easier to maintain products across multiple hardware revisions or market variants. The project also includes mechanisms for generating toolchains and SDKs, along with integrated SPDX license data and Software Bills of Materials (SBOMs) to support compliance, supply-chain visibility, and regulatory requirements.
Yocto NXP - Walnascar Overview
The NXP Semiconductors Yocto BSP provides strong hardware support for NXP System-on-Chip (SoC) platforms. For full details, see the NXP Yocto User Guide. This BSP provides NXP’s recipes for their own forks of U-Boot and the Linux kernel. NXP's Yocto fork provides NXP's forks of some mainline drivers that provide full featured hardware enablement before the mainline Linux/U-Boot reach parity.
Our support builds on this base. We define our own machine configurations, use our fork of NXP’s kernel and U-Boot for our boards, and include a few small utilities to support our hardware.
Yocto NXP - Walnascar Create Image
This section describes how to build a Yocto image for the TS-9370 using the NXP Walnascar BSP and the embeddedTS Yocto layer. The layer used for TS-9370 support is available here: [1](https://github.com/embeddedTS/meta-ts-nxp)
The steps below follow the standard NXP Walnascar workflow, with the additional requirement of adding the meta-ts-nxp layer to enable embeddedTS board support.
Install the Google repo tool:
mkdir ~/bin
curl http://commondatastorage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
# Add this to your shell path (or include it in ~/.bashrc)
PATH=${PATH}:~/bin
Download the NXP Walnascar BSP
Create a working directory and download the NXP manifest:
mkdir walnascar
cd walnascar
repo init -u https://github.com/nxp-imx/imx-manifest -b imx-linux-walnascar -m imx-6.12.34-2.1.0.xml
repo sync
Add the embeddedTS Yocto Layer
git clone https://github.com/embeddedTS/meta-ts-nxp.git -b walnascar sources/meta-ts-nxp/
ln -sf sources/meta-ts-nxp/tsimx-setup-release.sh imx-setup-release.sh
This layer provides the ts9370 machine definition, our fork of NXP's kernel and U-Boot, and the supporting utilities required for TS-9370 builds.
Configure the machine and distribution, then initialize the build directory:
MACHINE=ts9370 DISTRO=fsl-imx-xwayland source ./imx-setup-release.sh -b bld-xwayland
Build the full Yocto image:
bitbake imx-image-full
The output images will appear in:
tmp/deploy/images/ts9370/
These artifacts include the kernel, bootloader, root filesystem, and all files needed to deploy a Walnascar-based Yocto system on the TS-9370.
Yocto NXP - Walnascar mfgtools
To use the NXP mfgtools (uuu) to flash the SOM, it needs to connect as a USB device to the development machine. Connect a TC2030-USB cable to CN66. This connector is on the corner near the type A USB. Keep in mind this is run in parallel with the top port of J2 which should not have anything connected while CN66 is connected.
The relevant outputs of the yocto build are the imx-boot bootloader container and the compressed disk image which ends in '.wic.zst' depending on which image is built. uuu needs the uncompressed file though so use zstd utility to decompress the .wic.zst file first.
Flashing the yocto build is similar to the process in U-boot Development except uuu is used to flash both the bootloader and the disk image. It requires the wizusb utility and the uuu utility from the mfgtools repo.
Install the dependencies on your Linux Workstation:
- wizusb
- access to Wizard registers over USB
- NXP mfgtools
- uuu tool for booting/flashing i.MX93 over USB
To load the yocto build output on the board, run:
# Switch boot to USB Serial Downloader
wizusb 17 0x1
# Reset the board, so it comes up in the new strapping
wizusb 8 0x1
# Boot, Program u-boot and disk image over USB:
uuu -brun emmc_all imx-boot imx-full-image.wic
# Switch boot to USDHC1 8-bit eMMC
wizusb 17 0x2
# Reset the board, so it comes up in the new strapping
wizusb 8 0x1
Yocto Mainline BSP
The mainline bsp is pending kernel support in linux-lts and then will follow up in meta-ts, and our kas configs in ts-oe-bsp.
Yocto SDK
From the yocto build system, bitbake can create an SDK installer that can be used on other systems to compile applications that will run on the target hardware. To create the SDK installation script:
bitbake -c populate_sdk imx-image-full
Where imx-image-full is whatever image is installed on the target hardware. This will add all necessary libraries from that image for cross-compiled binaries to link against. When that bitbake completes, you will end up with a very large (5GB) installation shell script in tmp/deploy/sdk/ directory. Executing that shell script as root will install the SDK into /opt by default.
To use the SDK after installing it on a host, source the SDK's environment file:
. /opt/fsl-imx-wayland/6.12-walnascar/environment-setup-armv8a-poky-linux
This sets environment variables such as $CC and $CROSS_COMPILE so that you should be able to run any build system (make, autoconf, cmake) as normal and build an aarch64 binary linked against the library versions in the target image, which can be copied over to target hardware for testing.
See yocto sdk manual for reference.
Buildroot
The full-featured Debian image may be too cumbersome for some applications. Applications that require faster bootup time or a smaller root filesystem will benefit greatly from using a lighter distribution like Buildroot. Using Buildroot for generating images makes it easy to keep software up to date, both userspace and kernel. Additionally, the use of Buildroot allows for building full images completely from source, with semi-reproducable builds, and full software license reports.
To assist customers heading down this path, we maintain our own Buildroot br2-external tree. This tree includes upstream Buildroot as a submodule, which eases updating between Buildroot releases. See the Buildroot manual for more information on Buildroot and br2-external trees.
In order to provide an easy transition from a larger Linux distribution to Buildroot, we provide and maintain two levels of configurations:
- The base configuration for each device brings in hardware support to get the unit booted, but offers minimal software support and relies mostly on tools provided by BusyBox.
- An "extra packages" defconfig that can be merged in with any of the base configurations in order to provide many additional packages to create an environment that is more consistent with larger Linux distributions.
The larger Buildroot configuration averages about 10 seconds of boot time, much of which is spent on networking. The base configurations can reduce this time significantly.
Our Buildroot br2-external currently uses the lf-6.12.y branch of our Linux i.MX kernel repository for the i.MX 9x devices.
| Note: | Note that our base configurations include that device's utilities package where possible. Normally, these utilities (e.g. tshwctl, tsmicroctl, etc.) list the git hash of the build source in the help output. However, due to the Buildroot process, the git hash in these utilities reflects the git hash of Buildroot-ts, NOT of the utilities repository. There is no way to work around this without building the utilities outside of Buildroot.
|
Buildroot - Installing
When building Buildroot from source, the output files can be used to create a bootable eMMC rootfs for the TS-9370. The output files are also compatible with our USB Image Replicator.
The default configuration was designed to be close to our stock Debian distribution.
Buildroot - Building
Buildroot is intended to be completely cross-compiled from a host Linux workstation. This process creates a cross-compiler which is then used to build all target applications, kernel, etc., and then output a bootable image / tarball. The following instructions will create a bootable image / tarball for the target system:
Clone the repository:
git clone --recurse-submodules https://github.com/embeddedTS/buildroot-ts.git
cd buildroot-ts/
Configure the build:
# The following command uses a Buildroot script to merge two config files.
# The extra_packages_defconfig includes more usual packages to match our stock images
./buildroot/support/kconfig/merge_config.sh technologic/configs/extra_packages_defconfig technologic/configs/tsimx9_defconfig
# A smaller base image can be made with bare hardware support using:
# make tsimx9_defconfig
At this point, the default configuration can be modified if desired:
make menuconfig
And finally, start the build process:
make
The Buildroot process can take a large amount of time to build depending on available system resources. Note that if any changes occur in the config file, it is recommended to clean the build tree and start the process over. Buildroot ccache is not enabled by default, but can be to help speed up repeated builds. See the Buildroot manual for more information about ccache and Buildroot.
Once it is finished building, Buildroot will output a filesystem tarball to buildroot/output/images/rootfs.tar.xz. This file can be used with the Installing Buildroot instructions to get this tarball booted on the target device.
Buildroot - Cross Compiling
In order to generate a cross-compiler from Buildroot, first configure the target build as outlined in the first steps of the build instructions. Once configured, a separate make command can be issued to generate a tarball package of the cross-compiler. This can be unpacked to any location on the host Linux workstation's filesystem and then used to cross-compile additional applications for the target. The build, setup, and use of the cross-compiler can be done with the following steps:
# Be sure the target is configured first!
# The following command will output the cross-compiler package as well as build the target image completely if not built already
make sdk
# Unpack the tarball to new directory in the users home directory
# Note that the tarball name may be slightly different depending on how the toolchain is configured in Buildroot
mkdir ~/buildroot-toolchain
tar xf buildroot/output/images/arm-buildroot-linux-gnueabihf_sdk-buildroot.tar.gz -C ~/buildroot-toolchain/
# Update the path information for the toolchain (must be done when the tarball is unpacked, or if the root folder of the toolchain is moved!)
# Note that, as above, the path for the compiler may be slightly different depending on how the toolchain is configured in Buildroot
~/buildroot-toolchain/arm-buildroot-linux-gnueabihf_sdk-buildroot/relocate-sdk.sh
# Create a simple Hello World application source
cat << EOF > hello.c
#include <stdio.h>
void main(void) { printf("Hello!\n"); }
EOF
# Build a binary from the Hello World source that can be run on the target device
~/buildroot-toolchain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/arm-linux-gcc hello.c -o hello
# This cross compiler can be added to the user's PATH variable for easy access
export PATH=$PATH:~/buildroot-toolchain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
arm-linux-gcc hello.c -o hello
The hello binary can then be copied to the target device and executed on it.
Note that the make sdk command can be run at any time to generate the toolchain tarball. Even after Buildroot has generated the output image.
Buildroot is extremely flexible in its generation and use of a cross-compiler. See the Buildroot manual for more information on advanced use of the Buildroot generated toolchain as well as using Buildroot's generated cross-compiler as an external compiler for Buildroot.
Buildroot - Configuring Network
Buildroot implements the ip, ifconfig, route, etc., commands to manipulate the settings of interfaces. The first Ethernet interface is set up to come up automatically with our default configuration. The interfaces can also be manually set up:
# Bring up the CPU network interface
ifconfig eth0 up
# Set an IP address (assumes 255.255.255.0 subnet mask)
ifconfig eth0 192.168.0.50
# Set a specific subnet
ifconfig eth0 192.168.0.50 netmask 255.255.0.0
# Configure a default route. This is the server that provides an internet connection.
route add default gw 192.168.0.1
# Edit /etc/resolv.conf for the local DNS server
echo "nameserver 192.168.0.1" > /etc/resolv.conf
Most commonly, networks will offer DHCP which can be set up with one command:
# To setup the default CPU Ethernet port
udhcpc -i eth0
# All Ethernet ports can be made active and request DHCP addresses with:
udhcpc
To have network settings take effect on startup in Buildroot, edit /etc/network/interfaces:
# interface file auto-generated by Buildroot
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
pre-up /etc/network/nfs_check
wait-delay 15
Note that the default network startup may timeout on some networks, e.g. network protocols such as STP can delay packet movement. This can be resolved in Buildroot by adding network configuration options to fail after a number of attempts (rather than a timeout) or retry for a DHCP lease indefinitely. For example, adding one of the following lines under the iface eth0 inet dhcp section:
udhcpc_opts -t 0to infinitely retryudhcpc_opts -t 5to fail after five attempts.
See the man page for interfaces(5) for further information on the syntax of the interfaces file and all of the options that can be passed.
For more information on network configuration in general, Debian provides a great resource here that can be readily applied to Buildroot in most cases.
Buildroot - Installing New Software
Buildroot does not include a package manager by default (though it is possible to enable one). This means installing software directly on the platform can be cumbersome and is not the intended path when using Buildroot. It is recommended to modify the Buildroot configuration to include additional packages. See the Building Buildroot section for information on modifying the configuration to build additional packages.
If a desired package is not available in Buildroot, there are a number of options available moving forward. It is possible to add packages to the build process, though this does require some knowledge of Buildroot internals. Another option is to use the cross compiler that is output by Buildroot in order to compile packages on a host system and then copy them over to the target. It is also possible to install a toolchain directly on the device, and compile applications natively. The last option is the least recommended as it greatly increases the final image size and adds unnecessary complexity.
Buildroot - Setting Up SSH
The default configuration has Dropbear set up. Dropbear is a lightweight SSH server.
Make sure the device is configured on the network and set a password for the remote user. SSH will not allow remote connections without a password set. The default configuration does not set a password for the root user, nor are any other users configured.
passwd root
After this setup it is now possible to connect from a remote PC supporting SSH. On Linux/OS X this is the ssh command, or from Windows using a client such as PuTTY.
Buildroot - Starting Automatically
Buildroot defaults to using the BusyBox init system, and all of our provided configurations use this as well. The following custom startup script uses this format. For information on other init systems that Buildroot can use, as well as creating startup scripts for these, see the Buildroot manual.
The most straightforward way to add an application to startup is to create a startup script. This example startup script that will toggle the red LED on during startup, and off during shutdown. In this case the script is named customstartup which can be changed as needed.
Create the file /etc/init.d/S99customstartup with the following contents. Be sure to set the script as executable!
#! /bin/sh
# /etc/init.d/customstartup
case "$1" in
start)
echo 1 > /sys/class/leds/red-led/brightness
## If you are launching a daemon or other long running processes
## this should be started with
# nohup /usr/local/bin/yourdaemon &
;;
stop)
# if you have anything that needs to run on shutdown
echo 0 > /sys/class/leds/red-led/brightness
;;
*)
echo "Usage: customstartup start|stop" >&2
exit 3
;;
esac
exit 0
| Note: | The $PATH variable is not set up by default in init scripts so this will either need to be done manually or the full path to your application must be included. |
Buildroot provides numerous mechanisms to create this file in the target filesystem at build time. See the Buildroot manual for more information on this.
This script will be automatically called at startup and shutdown thanks to the file location and naming. However, it can also be manually started or stopped:
/etc/init.d/S99customstartup start
/etc/init.d/S99customstartup stop
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 onto a device's media (SD / eMMC / SATA / etc.) as part of the developer's production or preparation process. In addition to writing media, the Image Replicator tool can capture images from a device's media and prepare them to be written to other devices.
The Image Replicator tool is a USB disk image that boots from a target device to capture or write USB drive-resident media directly onto the target without a host workstation. It runs from a USB drive and allows:
- Capturing an image of the device storage (eMMC/microSD)
- Writing an existing image back to media
- Creating bootable media for mass deployment
The USB disk image is based on Buildroot and contains a set of scripts that handle the capture and write processes. The processes and scripts are flexible, can be used as-is, or adapted into larger production processes to format and load data onto devices. A single USB drive can be used to capture images from a device and then, once inserted in to other devices, write those same images onto 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.
- If no valid images exist on the disk, image capture starts.
- For each valid media present on the unit, a bit for bit copy of the source is made.
- 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.
- All images and tarballs are compressed, with both the output files' MD5 hash saved, as well as all of the files contained in the root partition (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, writing 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 an active media image. If the image capture process runs out of space, the process indicates a failure. |
Image Write Process
The image write process performs the following steps. For more detailed information see the Image Write section below.
- For each valid media present on the unit, find the first valid source image file for it.
- If a source image exists for a media that is not present on the unit, then the process indicates a failure.
- If the source image is a tarball, format the target disk with an appropriate file system and unpack it to the target disk, verifying all files against the MD5 hash file list after they are written.
- 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: tsimx9-usb-image-replicator.dd.xz
Tarball: tsimx9-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 yet 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 OSes, 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 both image capture and writing, but is easiest when image capture is not needed, due to the auto-expansion process.
| Note: | This process recommends a solid-state USB drive. Slower USB drives, especially those with spinning media, may take too long to enumerate and the bootloader will not boot the Image Replicator disk. 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 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, and 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 only for image writing. However, to ensure that 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 to 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.
Tarball
This process is easiest on a Linux workstation, but can be performed on other operating systems 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 write them to other units.
The image replicator and scripts require a minimum of 50 MB. Additionally, the size of target disk images or tarballs used also dictate 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
A third party tool is recommended, as native Windows archive tools have occasionally not worked 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 into a new folder on the drive, as this will not boot.
- ↑ The ext2 filesystem has a max file size limit as low at 16 GiB. This may cause issues for Image Capture.
- ↑ . Use of ext4 may require additional options. U-Boot on some platforms does not support the 64-bit addressing (added as default) in recent revisions of
mkfs.ext4. If using e2fsprogs 1.43 or newer, the options-O ^64bit,^metadata_csummay 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. - ↑ . The FAT32 (supported by vfat in Linux) filesystem has a max file size limit of 4 GiB. This may cause issues for Image Capture.
- ↑ embeddedTS is not affiliated with this tool. 7-Zip 21.07 tested in Windows 10 on 20220222
- ↑ 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 |
|---|---|
|
|
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. Do not 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:
- 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. - 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.
- 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. - 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. - If the media's partition layout uses multiple partitions, the image file is then unmounted, an md5sum of the image file taken, 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 |
|
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.
|
|---|---|---|
|
|
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 |
|
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.
|
|---|---|---|
|
|
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 binary blob. This will be written to the bootloader area of eMMC. If the file /u-boot.bin.md5 is present on the USB drive, this will be used to verify the data written to disk.
|
|---|
| Supervisory Microcontroller |
|
Binary blob to update the microcontroller via tssupervisorupdate.
|
|---|
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
Features
ADC
The onboard ADC uses a TI ADC121S021 SPI ADC. This ADC supports 50-200ksps sampling at 12-bits on a single channel. This is connected to onboard muxes to support up to 6 analog inputs.
Under Linux this is supported with IIO.
# Read AN_1 Voltage (0-12V):
iio_attr -c an-1-voltage voltage0
# Read AN_2 Voltage (0-12V):
iio_attr -c an-2-voltage voltage0
# Read AN_3 Voltage (0-12V):
iio_attr -c an-3-voltage voltage0
# Read AN_4 Voltage (0-48V):
iio_attr -c an-4-voltage voltage0
# Read MIKRO_AN (0-3.3V)
iio_attr -c adc-mux voltage4
These will output a raw and scale value. Multiply the raw value * the scale value to get the value in millivolts.
AN_1, AN_2, and AN_3 have built in 120ohm current loop shunts that can be enabled. These allow the ADC to sample 0-24mA. The current shunt resistors for AN_1 and AN_2 are enabled together, while AN_3 can be enabled/disabled independently.
## Switch AN_3 to current loop:
# Enable current loop, assert EN_CL_3:
gpioset 5 26=1
# Turn off voltage divider, deassert EN_ADC_3_12V:
gpioset 5 21=0
## Switch AN_1 and AN_2 to current loop:
# Enable current loop, assert EN_CL_1_2
gpioset 5 25=1
# Turn off voltage divider, deassert EN_ADC_3_12V:
gpioset 5 22=0
For the current loops, Linux does not provide the scaling itself. Read the voltage's raw value, but multiply by:
raw * 3300*1000/(4095*120)
to get the scaled value in uA.
Audio Codec
Bluetooth
The Type 2DL Murata module provides Bluetooth 5.3 BR/EDR/LE support. Linux support is provided by Bluez.
The drivers will automatically load on startup. Use bluetoothctl to interface with bluez:
root@tsimx9:~# bluetoothctl Agent registered [CHG] Controller 10:32:2C:71:14:15 Pairable: yes [bluetooth]# power on Changing power on succeeded [bluetooth]# scan on Discovery started [CHG] Controller 10:32:2C:71:14:15 Discovering: yes [NEW] Device 78:35:19:76:19:69 78-35-19-76-19-69 [NEW] Device 2C:55:91:80:2E:CC 2C-55-91-80-2E-CC
CAN
The TS-9370 has two FlexCAN ports available on the CN24 Terminal Block.
The i.MX93 includes 2 CAN controllers. These support CAN 2.0B, and CAN FD. Both of these include drivers which support the SocketCAN interface. Before proceeding with the examples, see the Kernel's CAN documentation here.
## First, set the baud rate and bring up the device:
ip link set can0 type can bitrate 250000
ip link set can0 up
## Dump data & errors:
candump can0 &
## Send the packet with:
#can_id = 0x7df
#data 0 = 0x3
#data 1 = 0x1
#data 2 = 0x0c
cansend can0 7DF#03010C
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
The CPU is a i.MX9352 CPU which is a dual core Arm® Cortex®-A55 at 1.7GHz. This is a 64-bit Arm® v8.2-A architecture core.
- 32 KB L1 Instruction Cache
- 32 KB L1 Data Cache
- 64 KB per-core L2 cache
- Media Processing Engine (MPE) with Arm® NeonTM technology supporting the
Advanced Single Instruction Multiple Data architecture
- Floating Point Unit (FPU) with support of the Arm® VFPv4-D16 architecture
DisplayPort
The standard DisplayPort connector supports displays up to 1080p 60Hz. HDMI is supported through DisplayPort to HDMI adapters.
eMMC Interface
The onboard eMMC supports the eMMC5.1 specification, and SDR104 speeds with an 8-bit bus. The eMMC performance provides high speed access, typically with larger eMMC providing faster speeds:
- 32 GB eMMC
- 118 MB/s Write
- 311 MB/s Read
- 16 GB eMMC
- 107 MB/s Write
- 175 MB/s Read
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.
| Offset | Description |
|---|---|
| 0x0-0x1FFFFF | Singleboot image including arm-trusted-firmware, LPDDR4 firmware, OP-TEE, imx93 Sentinal firmware, and U-Boot. |
| 0x300000-0x303FFF | U-boot environment |
| 0x340000-0x3BFFFF | Pending FPGA Update Image |
Ethernet Ports
The NXP processor implements two 10/100/1000 ethernet controllers with support built into the Linux kernel. Standard Linux utilities such as ifconfig/ip can be used to control this interface. See the Networking section of the installed OS for more details.
PTPv2
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] |
| 0x0020 | IRQ core |
| 0x0040 | FPGA GPIO block #0 |
| 0x0080 | FPGA GPIO block #1 |
| 0x00C0 | FPGA GPIO block #2 |
- ↑ See the #FPGA Revisions section for how to interpret these registers.
FPGA GPIO Core
The GPIO core supports:
- set/clr regs for data and oe
- IRQ level/edge, edge select between rising/falling/both edges
- 32 IO per bank.
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:
| Address | Name | Read/Write | Description |
|---|---|---|---|
| 0x00 | oe | Read | Output enable state of GPIO pins. |
| Write | Set bits to enable output for corresponding GPIOs. | ||
| 0x04 | oe_clr | Read | N/A |
| 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. |
| 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 | irq_mask_clear | Read | N/A |
| Write | Unmasks IRQ for corresponding bits. 0 = No effect, 1 = Unmask interrupt. | ||
| 0x1C | irq_mask_and_ack | Read | N/A |
| 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 200mA
- 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,ts9370-xbar"; node, and add pin mappings underneath that. See pinfunc-ts9370.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,ts9370-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 (lpspi4_cs_mux_1), Default MIKRO_SPI_CS# | |||||||
| GPIO0_IO17 | lpspi4_cs_mux_1 | 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_cs_mux_2 | 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 #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_mux_1 | |||||||
| N/A | MIKRO_SPI_MISO | N/A | N/A | N/A | N/A | N/A | N/A |
| Pin #58 (DC_3), Default lpspi4_miso_mux_2 | |||||||
| N/A | DC_3 | N/A | N/A | N/A | N/A | N/A | N/A |
| Pin #59 (GPIO0_IO3), Default EN_BLUE_LED | |||||||
| GPIO0_IO3 | N/A | N/A | 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 SPI MUX
The #FPGA XBAR maps signals that have single drivers, but sharing the SPI bus multiple places requires driving the correct MISO signal back to the FPGA based on which device is selected. To support sharing the CPU SPI bus, the FPGA implements a mux compatible with Linux's spi-mux. This is supported with existing drivers in our kernels.
While this could use GPIO for chip selects in Linux, this has impacts on performance. Most SPI devices require the chip select not just for selecting the active device, but framing all communication. By muxing the bus, we can use the real hardware chip select, and support DMA with the chip select framing.
The FPGA still routes a common SPI clock, MOSI, to each device. The MUX selects which MISO is sent back to the CPU, and where the hardware chip select is routed.
The FPGA GPIO bank 2 IO 7, 8, 9 make but spimux0, spimux1, spimux2. These allow selecting 8 possible locations for the SPI. In the FPGA XBAR, this is the signals:
lpspi4_miso_mux[7:0]
lpspi4_cs_mux[7:0]
Not all of these are brought out in the default FPGA:
| SPI MUX | Location |
|---|---|
| 000 | Onboard ADC |
| 001 | Mikrobus SPI |
| 010 | Daughtercard header SPI |
| 011 | N/A |
| 100 | N/A |
| 101 | N/A |
| 110 | N/A |
| 111 | N/A |
FPGA Updates
For most Linux users, the FPGA can be updated with:
curl -sSL http://files.embeddedts.com/ts-arm-sbc/ts-9370-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 if input power has failed:
gpioget $(gpiofind PUSH_SW_N)
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 MIKRO_RESET#:
gpioset $(gpiofind MIKRO_RESET_N)=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).
| Schematic Net Name | Bank | Line | Direction [1] | Reset Output Value [2] | Location |
|---|---|---|---|---|---|
| EN_GREEN_LED# | 4 | 0 | DO | 1 | Green LED |
| EN_YEL_LED# | 4 | 1 | DO | 1 | Yellow LED |
| EN_RED_LED# | 4 | 2 | DO | 1 | Red LED |
| EN_BLUE_LED | 4 | 3 | DO | 0 | Blue LED |
| NIM_RESET# | 4 | 4 | DIO | 0 | CN16 pin 5 |
| NIM_CTS# | 4 | 5 | DI | N/A | CN16 pin 12 |
| NIM_PWR_ON# | 4 | 6 | DIO | 0 | CN16 pin 20 |
| EN_NIM_USB# | 4 | 7 | DO | 1 | Onboard |
| EN_NIM_4V [3] | 4 | 8 | DO | 0 | CN16 pin 20 |
| EN_NIM_3P3V [3] | 4 | 9 | DO | 0 | CN16 pin 20 |
| NIM_TXD | 4 | 10 | DIO | 0 | CN16 pin 3 |
| NIM_RXD | 4 | 11 | DIO | 0 | CN16 pin 2 |
| EN_USB_HOST1_VBUS | 4 | 12 | DO | 0 | Onboard |
| EN_USB_HOST2_VBUS | 4 | 13 | DO | 0 | Onboard |
| MIKRO_TXD | 4 | 14 | DIO | 0 | CN17 pin 13 |
| MIKRO_RXD | 4 | 15 | DIO | 0 | CN17 pin 14 |
| MIKRO_SPI_CLK | 4 | 16 | DIO | 0 | CN17 pin 4 |
| MIKRO_SPI_CS# | 4 | 17 | DIO | 0 | CN17 pin 3 |
| MIKRO_SPI_MISO | 0 | 18 | DIO | 0 | CN17 pin 5 |
| MIKRO_SPI_MOSI | 4 | 19 | DIO | 0 | CN17 pin 6 |
| MIKRO_RESET# | 4 | 20 | DIO | 0 | CN17 pin 2 |
| MIKRO_AN | 4 | 21 | DIO | 0 | CN17 pin 1 |
| MIKRO_PWM | 4 | 22 | DIO | 0 | CN17 pin 16 |
| MIKRO_INT | 4 | 23 | DIO | 0 | CN17 pin 15 |
| NIM_STATUS | 4 | 30 | DIO | 0 | CN16 pin 13 |
| EN_2ND_PORT_232 | 5 | 0 | Output | 0 | Onboard |
| EN_CAN_1 | 5 | 1 | Output | 0 | Onboard |
| EN_CAN_2 | 5 | 2 | Output | 0 | Onboard |
| USB_HUB_RESET# | 5 | 4 | DO | 0 | Onboard |
| PUSH_SW# | 5 | 5 | DI | N/A | SW1 |
| EN_ADC_3_12V | 5 | 21 | DO | 0 | #FPGA ADC Controller |
| EN_ADC_1_2_12V | 5 | 22 | DO | 0 | #FPGA ADC Controller |
| AN_SEL_0 | 5 | 23 | DO | 0 | #FPGA ADC Controller |
| AN_SEL_1 | 5 | 24 | DO | 0 | #FPGA ADC Controller |
| EN_CL_1_2 | 5 | 25 | DO | 0 | #FPGA ADC Controller |
| EN_CL_3 | 5 | 26 | DO | 0 | #FPGA ADC Controller |
| DP_RESET# | 5 | 27 | DO | 0 | Onboard |
| NO_SCAP_CHRG# | 5 | 28 | DI | N/A | #Jumpers (HD4) |
EN_SPKR_AMP
|
5 | 29 | DO | 0 | Onboard |
| BT_EN | 5 | 30 | DO | 0 | Onboard |
| WIFI_EN | 5 | 31 | DO | 0 | Onboard |
| MAGNET_IRQ | 6 | 0 | DI | N/A | Onboard |
| GYRO_IRQ | 6 | 1 | DI | N/A | Onboard |
| clear_fault | 6 | 15 | DO | 0 | Ciruit Breaker |
| EN_HS_SW | 6 | 16 | DO | 0 | Terminal Block pin 2 |
| EN_LS_OUT_1 | 6 | 17 | DO | 0 | Terminal Block pin 6 |
| EN_LS_OUT_2 | 6 | 18 | DO | 0 | Terminal Block pin 5 |
| EN_LS_OUT_3 | 6 | 19 | DO | 0 | Terminal Block pin 4 |
| EN_LS_OUT_4 | 6 | 20 | DO | 0 | Terminal Block pin 3 |
| circuit_breaker_fault | 6 | 21 | DO | 0 | Ciruit Breaker |
| DIO_1_IN | 6 | 23 | DI | N/A | Terminal Block pin 6 |
| DIO_2_IN | 6 | 24 | DI | N/A | Terminal Block pin 5 |
| DIO_3_IN | 6 | 25 | DI | N/A | Terminal Block pin 4 |
| DIO_4_IN | 6 | 26 | DI | N/A | Terminal Block pin 3 |
| DC_1 | 6 | 27 | DIO | 0 | HD10 pin 1 |
| DC_3 | 6 | 28 | DIO | 0 | HD10 pin 3 |
| DC_5 | 6 | 29 | DIO | 0 | HD10 pin 5 |
| DC_7 | 6 | 30 | DIO | 0 | HD10 pin 7 |
| DC_9 | 6 | 31 | DIO | 0 | HD10 pin 9 |
I2C
The i.MX93 supports I2C at up to 1 MHz. This product uses two CPU I2C busses for on-board ICs and one for off-board devices.
To use existing Kernel drivers, the board device tree may need to be tweaked to describe what is on the bus. If a device does not have a kernel driver, it is possible to open the /dev/i2c-# device and interface with I2C directly from userspace.
| Device | Address | Description |
|---|---|---|
| /dev/i2c-0 | #Daughter Card Header | |
| /dev/i2c-3 | ||
| 0x10 | #Audio CODEC | |
| 0x1e | #Magnetometer[1] | |
| 0x54 | Wizard µC | |
| 0x60 | #RTC | |
| 0x68 | DisplayPort Transceiver | |
| 0x6a | #IMU[1] | |
| /dev/i2c-1 | 0x25 | NXP PMIC |
| /dev/i2c-8 | Mikroe bus header |
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:indicatoryellow:indicatorgreen: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.
A fourth, blue LED is under the control of the Wizard µC.
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
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 initial kernels for i.MX93-based products are based on NXP's repository, where NPU support only exists via the M33 co-processor. NXP provides separate firmware for operating the M33 as a standalone model-runner or as a Tensorflow "delegate". This section only discusses the latter method of using the NPU. The Tensorflow delegate takes full control of the M33 and enables Linux's use of the NPU as a machine-learning coprocessor. It shows up as /dev/ethosu0 when kernel-level support is present, activated by compile-time configuration parameters and device tree entries. Additional required software components are as follows:
| Component/Repository | Description |
| ethos-u-firmware | Firmware binary. Source may be downloaded from NXP as part of their MCUExpresso BSP. |
| ethos-u-driver-stack-imx | User-space driver and libraries that go through /dev/ethosu0 on NXP processors |
| tflite-ethosu-delegate-imx | This is how Tensorflow knows how to use Ethos-U on NXP processors |
| nxp-imx/tensorflow-imx | NXP's fork of Tensorflow, including Tensorflow Lite, that is needed on the i.MX93 |
| nxp-imx/ethos-u-vela.git | NXP's Vela fork is required in order to translate Tensorflow Lite models for the Ethos-U |
Details on these, as well as on how to use NXP's software support via Yocto, can be found in their i.MX Machine Learning User's Guide.
It should be noted that every one of the above repositories has a kernel release-specific branch. Failures, either when building or at runtime, are common when intermixing releases or when not using a branch that is correctly matched to the kernel in use.
Our standard Debian releases currently include kernel support for Ethos-U, but the additional software listed above must be downloaded and built in order to make use of the NPU.
To make this as pain-free as possible, we provide a script that downloads, builds, and installs all of these components and their dependencies atop our headless or X11 distributions. The script must be executed on the i.MX93 system running the distribution, and requires a high-speed Internet connection. Expect it to take up to three hours.
wget http://files.embeddedts.com/ts-socket-macrocontrollers/ts-4300-linux/scripts/install-npu-software.sh
chmod +x install-npu-software.sh
./install-npu-software.sh
When complete, a much shorter script can be used to verify the installation by downloading and executing this demo (similar to one that appears in the ML User's Guide):
wget http://files.embeddedts.com/ts-socket-macrocontrollers/ts-4300-linux/scripts/run-hopper-example.sh
chmod +x run-hopper-example.sh
./run-hopper-example.sh
The script downloads a Tensorflow model, runs Vela to prepare it for Ethos-U, and then performs an inference task using a test image and label choices that come with Tensorflow.
Wizard Microcontroller
Wizard RTC
Wizard Reset Controller
Wizard Hardware Monitoring
Wizard TS-SILO
Sleep Mode
SPI
This platform brings out the one "LPSPI4" controller from the i.MX93.
This is connected to:
- Onboard ADC
- Mikrobus SPI
- /dev/spidev1.1
- Daughtercard Header (When selected through #FPGA XBAR)
- /dev/spidev1.2
See SPI MUX for more information about how this LPSPI4 is mapped to other pins.
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:
Supervisory Microcontroller
UARTs
| UART Dev | Type | TX / + | RX / - | CTS | RTS | DCD | DTR |
|---|---|---|---|---|---|---|---|
| ttyLP0 | USB CDC-ACM | USB-C Console | |||||
| ttyLP1 | RS-232 | CN24-B pin 23 | CN24-B pin 22 | ||||
| ttyLP2 | RS-232/RS-485 | CN24-B pin 15 | CN24-B pin 14 | ||||
| ttyLP4 | TTL UART | Nimbelink CN16 pin 3 | Nimbelink CN16 pin 4 | Nimbelink CN16 pin 12 | |||
| ttyLP5 | Onboard | Onboard WIFI+BT chipset | |||||
| ttyLP6 | RS-485 | CN24-B pin 21 | CN24-B pin 20 | ||||
| ttyLP7 | 3.3V TTL | Mikrobus pin 13 | Mikrobus pin 14 | ||||
The RS-485/RS-232 UART ttyLP2 can be selected as RS-232 or RS-485 in software.
# Select RS-232
gpioset $(gpiofind EN_2ND_PORT_232)=1
# Select RS-485 (default)
gpioset $(gpiofind EN_2ND_PORT_232)=0
When using this port as RS-485, be sure to set the UART into RS-485 for proper half-duplex control. For example, in C:
int fd = open(device, O_RDWR | O_NOCTTY);
if (fd < 0) {
perror("open");
return 1;
}
struct serial_rs485 rs485conf;
memset(&rs485conf, 0, sizeof(rs485conf));
/* Enable RS485 mode */
rs485conf.flags |= SER_RS485_ENABLED;
if (ioctl(fd, TIOCSRS485, &rs485conf) < 0) {
perror("ioctl TIOCSRS485");
close(fd);
return 1;
}
Or with python+pyserial:
import serial
from serial.rs485 import RS485Settings
import serial.rs485
ser = serial.Serial('/dev/ttyLP4', 115200)
ser.rs485_mode = RS485Settings(
rts_level_for_tx=True,
rts_level_for_rx=False,
)
USB
The TS-9370 has 4 USB 2.0 type A host ports.
Watchdog
WiFi
Murata Type 2DL WIFI
This board includes the Murata Type-2DL WIFI+BT Module using the NXP IW611 chipset.
Features:
- 2.4 GHz & 5 GHz support
- 802.11 a/b/g/n/ac/ax 1x1 601Mbps
- Bluetooth 5.3 BR/EDR/LE
- Client / AP / P2P-GO / P2P Client / Monitor mode support
- Supports up to 4 simultaneous modes while on the same channel.
Client
The client mode uses the "mlan0" interface. See the distribution section for details on the recommended setup for WIFI clients, eg for Debian 12.
A simple example using a WPA client set up by hand:
wpa_passphrase "yourssid" "password" > /etc/wpasupplicant.conf
wpa_supplicant -B -i mlan0 -c /etc/wpa_supplicant.conf
# Request an IP:
dhclient mlan0
AP
The AP mode uses the "uap0" interface. In the simplest example, create /etc/hostapd.conf
interface=uap0 driver=nl80211 ssid=YourAPName channel=1
Start hostapd:
hostapd -b /etc/hostapd.conf
Specifications
IO specifications
Rail Specifications
Power Consumption
Power Input Specifications
External Interfaces
Audio Header
The audio header is a 2x5 0.1" pitch male pin header which brings out a record channel and output channel from the #Audio Codec.
|
|
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. |
Terminal Block
The Terminal blocks include:
- Wide voltage DIO
- Low side outputs (DIG_IO_1-3)
- Capable of sinking 700mA
- Built in Circuit breaker if current is exceeded
- Weak pullup to 5V
- Digital Inputs
- Max input 30V
- VIL: Input <= 1.06V
- VIH: Input >= 2.26V
- Max input 30V
- High side switch
- Output provides 300mA of switched VIN
- Input ADC (AN_4)
- Designed for 0-48V (0-54.828V scaling range)
- Scale to mv: raw_sample * (3300/4095) * ((47000 + 3010)/3010)
- Absolute max: 59.813V
- Designed for 0-48V (0-54.828V scaling range)
- Analog Inputs (AN_1 - AN_3)
- Voltage scaling mode
- Designed for 0-12V sampling (0-13.266V scaling range)
- Scale to mv: raw_sample * (3300/4095) * ((6040 + 2000)/2000)
- Absolute max: 14.472V
- Current scaling mode
- Designed for 0-24mA sampling (0-27.5mA scaling range)
- Scale to uA: raw_sample * 3300*1000/(4095*120)
- Absolute max: 28867uA
- Voltage scaling mode
- Low side outputs (DIG_IO_1-3)
|
| ||||||||||||||||||||||||||||||||||||||||||||||||||||
Daughter Card Header
The HD10 connector is a 2x5 0.1" pitch male pin header with I2C/I3C, 5 FPGA IO, 5V, and 3.3V.
|
|
DisplayPort Connector
Ethernet Connectors
MicroSD Connector
mikroBUS Header
The Mikrobus provides compatibility with Mikroe's 750+ click boards. This interface has SPI, I2C, PWM, analog, a TTL UART, 3.3V and 5V.
|
|
Power Connector
The power input uses a 2 pin removable terminal block. The mating connector from On Shore Technologies OSTTJ025102 is included with the board.
The 2 pin terminal block supports 5-48VDC input. A typical power supply will provide at least 1A at 12V. More details can be found in the #Power Consumption section.
|
Push Button
The push button (SW1) is accessed using the GPIO subsystem.
# One time sample (value is 0 when pressed):
root@tsimx9:~# gpioget $(gpiofind 'PUSH_SW#')
1
# Monitor for events (break to exit):
root@tsimx9:~# gpiomon $(gpiofind 'PUSH_SW#')
event: FALLING EDGE offset: 5 timestamp: [ 449.319270767]
event: RISING EDGE offset: 5 timestamp: [ 449.717927039]
event: FALLING EDGE offset: 5 timestamp: [ 450.262004257]
event: RISING EDGE offset: 5 timestamp: [ 450.552418878]
^Croot@tsimx9:~#USB Ports
Jumpers
USB-C Console Connector
XBee/Nimbelink Header
| Note: | The socket is designed to support various radios from multiple vendors. Even within the same product line, e.g. Airgain's Skywire cell modems, some modules may deviate slightly from the standards set out by the manufacturers. Due to this, we recommend reviewing the datasheet carefully for any potential modules intended to be used in combination with this platform. Our support team (email or support portal) is happy to help advise with any questions on device compatibility. |
CN16 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.
|
|
While pin 1 commonly lines up with the antennas on the modems, the pin 1 orientation should be verified in your modem's datasheet.
XBEE-brand modules use 3.3V and require USB to be disabled:
# Enable 3.3V
gpioset 7 6=0
# Turn off USB
gpioset 7 9=0
# Access /dev/ttymxc3For other types of modules, this header can provide 3.3V or 4V, as many 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 most other radios require 3.3V.
Nimbelink modules have modem-specific methods of powering on, usage, and powering off. Verify in the datasheet of the modem appropriate for your cell network to determine if the module uses USB. If not, verify the expected serial baud rate and settings.
# For modules wanting 4V:
gpioset 7 6=1
# For modules wanting 3.3V:
#gpioset 7 6=0
# To turn off power on this pin, configure NIMBEL_PWR an input:
# gpioget 7 6
# If your modem supports USB, this must be enabled,
# disabling the upper external USB port and enabling
# the modem's.
gpioset 7 9=1
# Some modems require NIM_PWR_ON to be "pressed" before they
# turn on. WARNING: If the modem is already on, this same
# sequence may turn it off.
#gpioset 7 8=1
#sleep 1
#gpioset 7 8=0If 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.
| Note: | Some Nimbelink cell modems have a long startup and may not show up on USB for many seconds. |
Serial TTY-based modems can be tested with picocom
# Verify the baud rate for your specific modem
picocom -b 921600 /dev/ttymxc3With picocom type AT and press enter and it should return "OK". Press Ctrl+a x to close picocom.
Revisions and Changes
PCB Revisions
| Revision | Changes |
|---|---|
| P4 |
|
U-Boot Revisions
FPGA Revisions
To determine the version of the FPGA on a TS-9370, 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-9370 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-9370: 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.
| Date | Version | Description |
|---|---|---|
| 2024-10-09 | v0.0.2 |
|
| 2025-03-06 | v0.2.0 |
|
| 2025-03-07 | v0.2.1 |
|
| 2025-03-07 | v0.2.2 |
|
| 2025-03-10 | v0.2.3 |
|
| 2025-03-13 | v0.2.4 |
|
| 2025-03-14 | v0.2.5 |
|
| 2025-03-19 | v0.2.6 |
|
| 2025-07-15 | v0.2.7 |
|
| 2025-07-16 | v0.2.8 |
|
| 2025-07-22 | v0.2.9 |
|
| 2025-08-12 | v0.2.10 |
|
| 2025-08-25 | v0.2.11 |
|
| 2025-08-26 | v0.2.12 |
|
| 2025-09-12 | v0.2.13 |
|
Wizard Firmware Revisions
Software Images
Debian Changelog
See our Debian release archive for all released images, including the latest for each supported Debian version.
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.
