Repairing a TuringPi 2

Intro

In April 2023 I finally got my TuringPi 2 - happy to finally got it into my hands I plugged it in, headed over to the TuringPi 2 Github ( https://github.com/turing-machines/BMC-Firmware ) got myself the latest BMC firmware and started to flash it. Thats when things got wrong: The power supply coils started to screech - and seconds later I was greeted with blue smoke and a dead TuringPi 2. The firmware update actually flashed the board into a paper weight.

The one green led was still on, the BMC led was off and the board did not react - also the networking LEDs were off. To be honest, this was the first time a firmware update actually physically bricked hardware / got it up into smoke.

Luckily, Turing Machines, the company behind TuringPi 2 did send me another board, thinking that the (also ordered at Turing Machines) small form factor power supply could have been the culprit - thanks a lot guys for that tremendous help!

The new board was working without an issue and did wait for quite some time until I updated it - horrified the same thing could happen again. I think I updated it to 1.1.0 and things were ok back then.

However, with half a year passed and still the dead board lying around I thought, maybe I could fix it?

The incident

What happened was a short circuit - but how? The only thing I could think of was an error in the firmware upgrade - and the BMC processor - a dual core Allwinner T113-S3 - shortcircuiting something as it did not have its initialization correctly. While this should never be possible (e.g. that a board is layouted in such a way that a processor would short-circuit if it has no program available), I had no other explanation. But how to find out what was broken? The incident actually burned the 5A SMD fuse for the 3v3 line - meaning the board was now powering up ok-ish, but without any of the 3v3 components (e.g. the BMC). Short-circuiting the fuse directly lead to warmth and a bad stench - something was still short-circuiting. So I build myself a Thermal Camera. I was quite sure that the T113-S3 was toast by now, so I removed it.

After that, I tried bridging the blown fuse again - this time with an ampere meter. About 6 Watts were going somewhere I did not know. The Thermal Camera showcased an SMD capacitor glowing red - that poor thing got mangled so badly that it decided to become conductive for DC - not good. I removed that capacitor as well - no excess power was drawn anymore after that.

The repair

I ordered two new Allwinner T113-S3 ( T113-S3 ELQFP-128, position 2 on the picture below ) as well as some spare 5A SMD fuses ( LF5A - 10PCS/LOT 1808 SMD fuse 125V fast 5A, position 1 on the picture below ) from the web. The capacitor with 570uF was replaced by a 12.2uF for testing ( position 3 on the picture below).

As soon as I had everything in place, I got the latest firmware image, 2.0.5 from the Turing Pi Website ( https://firmware.turingpi.com/turing-pi2/v2.0.5/tp2-firmware-sdcard-v2.0.5.img ). I flashed it onto a MicroSD Card with balenaEtcher and inserted the MicroSD Card into the appropriate slot on the backside of the TuringPi 2. With everything in place, I hooked everything up, got myself a flathead screwdriver and shortend the MOSI and SCLK/Clock lines of the flash module on-board of the TuringPi 2 as mentioned in this issue ticket: https://github.com/turing-machines/BMC-Firmware/issues/134 ( see position 4 in green, the left bottom two legs of the chip ). You can also see the both legs better on the picture below:

With my screwdriver still shorting both legs of the flash module, I applied power to the TuringPi 2. After some seconds, the LEDs came on and the 4 LEDs of the ethernet ports started blinking slowly. I removed the short-circuit on the flash module, pressed KEY1 3 times rapidly and the ethernet ports started turning on one LED at a time. After about 30 seconds, the LEDs stopped the animation and all LEDs blinked twice (again and again) to show the flash was a success. I powered down the Turing Pi 2, removed the MicroSD card and powered it up again - and it was fixed! 🙂
( Usual procedure for update from a MicroSD Card can be found here: https://docs.turingpi.com/docs/turing-pi2-bmc-v1x-to-v2x )

Good ending

With that, the TuringPi 2 was fixed - BMC is answering my calls, all four nodes are working. It was not an easy fix by all means, but using a microscope, loads of flux and patience made it work again. And the Thermal Camera! That less than 40 Euro thing really helped a lot, otherwise I would have not found the issue without turning the whole board into a burning mess.

Building a MLX90640 Thermal Camera

Intro

Thermal Cameras are neat tools, especially in electronics: You can quickly diagnose short circuits, identify misbehaving components or quantify if subsystems get too hot. However, thermal cameras are still a luxury item which can quickly costs hunderds of euros. Sometimes - however - you do not need the accurarcy of a FLIR or similar system, somtimes its just enough to see which part of a PCB is heating up quicker than others. Enter MLX90640: A 32x24 pixel sensor in two different FoVs, exposing its data via i2c. These sensors can be had on different websites for as low as 25 euros during sale.

Components/BoM

  • 1x MLX90640 Thermal Sensor
  • 1x Waveshare RP2040 Zero
  • 1x HT7333 LDO
  • 2x SMD Capacitor 10 uF
  • 2x SMD Resistor 2.2k Ohm

All in all about 30 Euros

Schematic

Nothing really surprising - a HT7333 with some filter caps to allow for a clean power source, two pull-up resistors to allow for the high-speed, 1 MHz communication via the i2c bus. Keep the i2c wires between RP2040 Zero and the sensor as short as possible and pull-up the i2c SDA/SCL lines directly at the sensor.

Operation modes

There are two operation modes here, you can either choose to use the sensor with the Adafruit and Sparkfun example, which will just write a (very long) ASCII line consiting of 768 comma seperated float values per frame, delimited by \n. This will also work with a Processing example to showcase the picture (about 4 FPS).
The alternative to that, would be using another approach, packing the 768 temperature values not in a data intensive ASCII line, but send them via SLIP protocol to the computer. The processing example is not available for that, but I got some other Python code in the making (about 7.5 FPS).

Serial

Firmware (Serial)

Programmed via Arduino using the Arduino-Pico framework, using the Adafruit_MLX90640 library ( https://github.com/adafruit/Adafruit_MLX90640 ) - please be aware that 1 MHz I2C operations and mlx.setRefreshRate(MLX90640_32_HZ) will only work with extremely short wires!

#include 

Adafruit_MLX90640 mlx;
float frame[32*24]; // buffer for full frame of temperatures

// uncomment *one* of the below
#define PRINT_TEMPERATURES
//#define PRINT_ASCIIART

void setup() {
  while (!Serial) delay(10);
  Serial.begin(115200);
  delay(100);

  //Serial.println("Adafruit MLX90640 Simple Test");
  if (! mlx.begin(MLX90640_I2CADDR_DEFAULT, &Wire)) {
    Serial.println("MLX90640 not found!");
    while (1) delay(10);
  }
  /*
  Serial.println("Found Adafruit MLX90640");

  Serial.print("Serial number: ");
  Serial.print(mlx.serialNumber[0], HEX);
  Serial.print(mlx.serialNumber[1], HEX);
  Serial.println(mlx.serialNumber[2], HEX);
  */

  //mlx.setMode(MLX90640_INTERLEAVED);
  mlx.setMode(MLX90640_CHESS);
/*
  Serial.print("Current mode: ");
  if (mlx.getMode() == MLX90640_CHESS) {
    Serial.println("Chess");
  } else {
    Serial.println("Interleave");    
  }
*/

//  mlx.setResolution(MLX90640_ADC_18BIT); //default
  mlx.setResolution(MLX90640_ADC_19BIT);
  /*Serial.print("Current resolution: ");
  mlx90640_resolution_t res = mlx.getResolution();
  switch (res) {
    case MLX90640_ADC_16BIT: Serial.println("16 bit"); break;
    case MLX90640_ADC_17BIT: Serial.println("17 bit"); break;
    case MLX90640_ADC_18BIT: Serial.println("18 bit"); break;
    case MLX90640_ADC_19BIT: Serial.println("19 bit"); break;
  }
*/

  //mlx.setRefreshRate(MLX90640_2_HZ);
  //mlx.setRefreshRate(MLX90640_8_HZ);
  mlx.setRefreshRate(MLX90640_32_HZ);

  /*Serial.print("Current frame rate: ");
  mlx90640_refreshrate_t rate = mlx.getRefreshRate();
  switch (rate) {
    case MLX90640_0_5_HZ: Serial.println("0.5 Hz"); break;
    case MLX90640_1_HZ: Serial.println("1 Hz"); break; 
    case MLX90640_2_HZ: Serial.println("2 Hz"); break;
    case MLX90640_4_HZ: Serial.println("4 Hz"); break;
    case MLX90640_8_HZ: Serial.println("8 Hz"); break;
    case MLX90640_16_HZ: Serial.println("16 Hz"); break;
    case MLX90640_32_HZ: Serial.println("32 Hz"); break;
    case MLX90640_64_HZ: Serial.println("64 Hz"); break;
  }
  */

  //Wire.setClock(400000); // max 1 MHz
Wire.setClock(1000000); // max 1 MHz
//  Wire.setClock(3400000); // max 1 MHz

}

void loop() {
  //delay(500);

  if (mlx.getFrame(frame) != 0) {
    //Serial.println("Failed");
    return;
  }

  //Serial.println();
  //Serial.println();
  for (uint8_t h=0; h<24; h++) {
    for (uint8_t w=0; w<32; w++) {
      float t = frame[h*32 + w];
#ifdef PRINT_TEMPERATURES
      Serial.print(t, 1);
      Serial.print(",");
#endif
#ifdef PRINT_ASCIIART
      char c = '&';
      if (t < 20) c = ' ';
      else if (t < 23) c = '.';
      else if (t < 25) c = '-';
      else if (t < 27) c = '*';
      else if (t < 29) c = '+';
      else if (t < 31) c = 'x';
      else if (t < 33) c = '%';
      else if (t < 35) c = '#';
      else if (t < 37) c = 'X';
      Serial.print(c);
#endif
    }
    //Serial.println();
  }
  Serial.println();
  //Serial.write('\n'); //10
}

Software (Serial)

To read the data from the sensor, the SparkFun Processing example ( https://learn.sparkfun.com/tutorials/qwiic-ir-array-mlx90640-hookup-guide/all#example-code ) can be used.
Alternatively, you can use Python 3.x and pyserial, giving back a list of thermal readings which can then be drawn into pictures.

import serial
import re
ser = serial.Serial('COM12', 115200, timeout=2)
while True:
    line = ser.readline().decode('ascii')   # read a '\n' terminated line
    thermalData = re.split('\,', line[:-3]) # last ,\n needs to go!
    print(thermalData)

SLIP

Firmware (SLIP)

Programmed via Arduino using the Arduino-Pico framework, using the Adafruit_MLX90640 library ( https://github.com/adafruit/Adafruit_MLX90640 ) as well as PacketSerial ( https://github.com/bakercp/PacketSerial ) - please be aware that 1 MHz I2C operations and mlx.setRefreshRate(MLX90640_32_HZ) will only work with extremely short wires!

#include 
SLIPPacketSerial packetSerial;

#include 
Adafruit_MLX90640 mlx;
float frame[32*24]; // buffer for full frame of temperatures

void setup() {
  while (!Serial) delay(10);
  Serial.begin(460800);
  Serial.setTimeout(100);
  packetSerial.setStream(&Serial); 
  delay(100);

  if (! mlx.begin(MLX90640_I2CADDR_DEFAULT, &Wire)) {
    //Serial.println("MLX90640 not found!");
    while (1) delay(10);
  }
  /*
  Serial.println("Found Adafruit MLX90640");
  Serial.print("Serial number: ");
  Serial.print(mlx.serialNumber[0], HEX);
  Serial.print(mlx.serialNumber[1], HEX);
  Serial.println(mlx.serialNumber[2], HEX);
  */

  //mlx.setMode(MLX90640_INTERLEAVED);
  mlx.setMode(MLX90640_CHESS);

//  mlx.setResolution(MLX90640_ADC_18BIT); //default
  mlx.setResolution(MLX90640_ADC_19BIT);

  //mlx.setRefreshRate(MLX90640_2_HZ);
  //mlx.setRefreshRate(MLX90640_8_HZ);
  mlx.setRefreshRate(MLX90640_32_HZ);

  //Wire.setClock(400000);
  Wire.setClock(1000000); // max 1 MHz
}

void loop() {
  packetSerial.update();
  if (mlx.getFrame(frame) != 0) {
    //Serial.println("Failed");
    return;
  } else {
    packetSerial.send((uint8_t*)&frame, sizeof(frame));   
  }
}

Software (SLIP)

Using Python 3.x, pyserial and sliplib, giving back a list of thermal readings which can then be drawn into pictures

import serial
import sliplib
import time
import struct
ser = serial.Serial('COM12', 115200, timeout=2)
while True:
    bytesToRead = ser.inWaiting()
    if (bytesToRead!=0):
        readData = ser.read(bytesToRead)
        decodedData = sliplib.decode(readData)
        if decodedData != b'':
            thermalData = []
            for i in range(0, len(decodedData), 4):
                thermalData.append(struct.unpack('f', decodedData[i:i+4])[0])
            print(thermalData)
            # ca. 7.41 Frames raw
    else:
        time.sleep(0.07)

Finishing up

After connecting everything, I finished the build up by printing a small PLA housing and even printed a TPU cap for the lense itself, so that (via friction fit) the sensor could be just thrown into the toolbox and still would be protected from dust and other issues. Could the finish be gotten more pretty? Yes. Did this hinder its function? Not at all, it already helped me fix up a defective turingPi2 🙂 (maybe something for the next post).

Optimisation

As usual in engineering, some problems can be solved by throwing money at them. In this case, a Teensy 4.x as a processor could significantly increase the data throughput. However, I did not want to pair a 40 Euro MCU with a 25 Euro Sensor - be it for costs and footprints sake - and opted in for the 2.5 Euro RP2040 Zero. Still, performance can be improved over my first optimisation using SLIP - by just using the second core of the RP2040. Ideally you would multi-thread the application, having the first core always retriving new data, while the second core packs it into SLIP packets and sends them on their way. This should be fairly easy and might result in more fps :).

Photos

RAK11300 - Getting it to work with Arduino-Pico and RadioLib!

Intro

About a year ago I got some RAK11300 modules - which are a nice combination of an RP2040 alongside a SX1262 radio on one module - designed a breakout board - tested it - and quickly shelved it. Reason was that this module was somewhat Arduino compliant - as stated - however only with RAKwireless Arduino mbedOS Repo. Alongside of it being a bit... hacked together, it had the issue of not supporting any low power modes for the RP2040 - making it run on both cores at full tilt and consuming batteries in no time. Not the best thing for a portable/battery powered sensor system, I guess?

Some people tried already to get this RP2040 working with other software - e.g. Micropython or Arduino, but I never heard someone getting it working, so I tried my luck - this is the whole journey from start to finish. Spoiler, it is now working and you can find the example code at the end with Arduino-Pico and RadioLib now fully supporting this module :).

Pinout

The most important thing was trying to figure out the internal wiring of the RP2040 to the SX1262 - while RAK did not offer complete schematics, they at least revealed parts of it after asking in the forums :).

The antenna switch direction is controlled by the SX1262 itself through DIO2.
The antenna switch power is controlled with GPIO25
It uses an TCXO
uint32_t lora_rak11300_init(void)
{
    _hwConfig.CHIP_TYPE = SX1262;          // Chip type, SX1261 or SX1262
    _hwConfig.PIN_LORA_SCLK = 10;          // LORA SPI CLK
    _hwConfig.PIN_LORA_MOSI = 11;          // LORA SPI MOSI
    _hwConfig.PIN_LORA_MISO = 12;          // LORA SPI MISO
    _hwConfig.PIN_LORA_NSS = 13;           // LORA SPI CS
    _hwConfig.PIN_LORA_RESET = 14;         // LORA RESET
    _hwConfig.PIN_LORA_BUSY = 15;          // LORA SPI BUSY
    _hwConfig.RADIO_TXEN = -1;             // LORA ANTENNA TX ENABLE (e.g. eByte E22 module)
    _hwConfig.RADIO_RXEN = 25;             // LORA ANTENNA RX ENABLE (e.g. eByte E22 module)
    _hwConfig.USE_DIO2_ANT_SWITCH = true;  // LORA DIO2 controls antenna
    _hwConfig.USE_DIO3_TCXO = true;        // LORA DIO3 controls oscillator voltage (e.g. eByte E22 module)
    _hwConfig.USE_DIO3_ANT_SWITCH = false; // LORA DIO3 controls antenna (e.g. Insight SIP ISP4520 module)
    _hwConfig.PIN_LORA_DIO_1 = 29;         // LORA DIO_1
    _hwConfig.USE_RXEN_ANT_PWR = true;     // RXEN is used as power for antenna switch
#ifdef RAK11310_PROTO
    _hwConfig.USE_LDO = true; // True on RAK11300 prototypes because of DCDC regulator problem
#else
    _hwConfig.USE_LDO = false;
#endif

To sum up:

  • RP2040 connects to the SX1262 using SPI1
  • RP2040 has the same pinout/GPIO output as a generic Raspberry Pi Pico board
  • SPI1 however has its connections twisted a bit
  • SPI1 Pinout:
    • SCLK/CLK/Clock: GPIO 10
    • MOSI: GPIO 11
    • MISO: GPIO 12
    • CS/ChipSelect: GPIO 13
  • Additional lines needed for SX1262:
    • NRESET: GPIO 14
    • Busy: GPIO 15
    • DIO1: GPIO 29
    • RXEN: GPIO 25
  • LoRaWAN compliance is mentioned to be 1.0.2 specification compliant with Rev B, however I also saw mentions of 1.0.3 with Rev B

RP2040

To get this to work, you can take the legendary Earle F. Philhower's Arduino Pico repo, use the normal RPi Pico board and overwrite the SPI1 positions like so:

SPI1.setCS(13);
SPI1.setSCK(10);
SPI1.setTX(11);
SPI1.setRX(12);
SPI1.begin(false);

however, to make it easier I put up a PR to directly change those pinouts and integrate the RAKwireless RAK11300 proper into the arduino-pico framework.

SX1262

Lets get to the SX1262 library and the implementation of the LoRaWAN communication schema:
Luckily, Jan Gromeš, maintainer of the awesome RadioLib decided to implement LoRaWAN support - and the SX1262 modules. I tried to get everything working, however, there seem to be some minor issues. His library does detect the SX1262, however, I am not able to join TTN with it. The issue ticket is currently open here.

The most weird thing is still the GPIO 25 for me, the mentioned Antenna switch power. It might be all thats needed to put this port as output and put it on high to supply power whenever someone wants to receive/transmit - but I am not really sure yet. I have not found any evidence and I feel something with the join event in the RadioLib is still off as other people also saw issues - so there are probably two different problems right now.

Getting further

To understand the whole idea better, I opened up one RAK11300 and put it under the microscope. Sadly my microscope camera ain't really good, so the picture quality is a bit hit and miss, but as there are no pictures at all on the internet, I thought it might be useful for someone.

Some details:

  • Flash Chip is a Winbond 25Q16JVIQ (16 Mbit, should be 2 MB then?)
  • RP2040 is the usual RP2-B2
  • Semtech SX1262
  • Antenna Switch is a small 6 pin part with label 259 128, I not really found anything on the net, pinout looks a bit like that:
    GPIO25  | Antenna   |   DIO2(?)
                    259
                    128
*   VR_PA/RFO | GND     |   RFI_N/RFI_P

So it looks like the RP2040 PIN 25 could be really the "power supply" for that switch, while the DIO2 of the SX1262 would be changing the switch to either supply the RX or TX chains to the antenna output, however I am not that sure.

Photos

So, without anymore addition, here are the pictures, sorry for the bad quality.

If you have any infos to help on the quest, please let me know :).

Update to the Antenna Switch / GPIO25 (@2023-11-03 17:08)

Looking at the lora_rak11300_init, we can quickly find out that the GPIO 25 is used as power to the antenna switch, e.g. switching on the use of the antenna. Its important to see that TXEN=-1, so RXEN and TXEN are not used to determine the direction (e.g. if sending or receiving, but this is done by DIO2/directly by the SX1262 module. The use of RXEN/GPIO25 is purely for powering the Antenna switch:

    _hwConfig.RADIO_TXEN = -1;             // LORA ANTENNA TX ENABLE (e.g. eByte E22 module)
    _hwConfig.RADIO_RXEN = 25;             // LORA ANTENNA RX ENABLE (e.g. eByte E22 module)
    _hwConfig.USE_RXEN_ANT_PWR = true;     // RXEN is used as power for antenna switch
    _hwConfig.USE_DIO2_ANT_SWITCH = true;  // LORA DIO2 controls antenna

( https://github.com/beegee-tokyo/SX126x-Arduino/blob/fe6178f82d81e6509a5352f1d2aa85e433e19a7a/src/boards/mcu/board.cpp#L239C25-L239C25 )

If we dig a bit deeper, we will find the SX126xIoInit ( https://github.com/beegee-tokyo/SX126x-Arduino/blob/ca879479b25071c568ded9a60f7c060f10c7791a/src/boards/sx126x/sx126x-board.cpp#L49 )

    // Use RADIO_RXEN as power for the antenna switch
    if (_hwConfig.USE_RXEN_ANT_PWR)
    {
        if (_hwConfig.RADIO_TXEN != -1)
            pinMode(_hwConfig.RADIO_TXEN, INPUT);
        pinMode(_hwConfig.RADIO_RXEN, OUTPUT);
        digitalWrite(_hwConfig.RADIO_RXEN, LOW);
    }

This also shows that the GPIO25 is used only as antenna switch power and is configured as output, but still in "low" mode, e.g. the power switch is off.

In all instances of the Antenna used or the TX/RX channels ( SX126xAntSwOn, SX126xAntSwOff, SX126xRXena, SX126xTXena - https://github.com/beegee-tokyo/SX126x-Arduino/blob/ca879479b25071c568ded9a60f7c060f10c7791a/src/boards/sx126x/sx126x-board.cpp#L496-L555 ) we can see that the GPIO25 is pulled high, e.g powers on the Antenna Switch - and powers down after sending. It does not make a difference if TX or RX is needed, its always HIGH/ON when the antenna is used and LOW/OFF when its not. Probably to preserve energy. So basically, we would need to always set GPIO25 as output and pull it high when we need to use the module.

Update to RadioLib (@2023-11-03 20:29)

I got TTN working, however Chirpstack proved to be difficult. Reason was the error "ERROR chirpstack::uplink: Deduplication error error=Unknown data-rate: Lora(LoraDataRate { spreading_factor: 7, bandwidth: 125000, coding_rate: “4/7” })" reason for that was that RadioLib accidently tries to drive EU868 packets with 4/7 instead of 4/5 coding rate - so these packets are thrown away right away ( https://forum.chirpstack.io/t/error-unknown-data-rate-on-eu868-with-coding-rate-4-7/16109 ). I made a PR ( https://github.com/jgromes/RadioLib/pull/865 ) to fix that issue. There is also an error in the LoRaWAN example as handled by this PR ( https://github.com/jgromes/RadioLib/pull/866 ). With that we can finally join TTN and Chirpstack v4 servers! What is still a problem, is re-connecting: Trying to restore a connection ends with an issue -1101 in RadioLib, seems to be what was described beforehand in https://github.com/jgromes/RadioLib/issues/858. Lets see if we find something. Other than that, its looking so much better already :)/

Update to RadioLib (@2023-11-04 16:25)

  • The issue with the restore of the connection is now also solved, it was an issue with the "software" EEPROM on the RP2040 and has now been fixed by a PR ( https://github.com/jgromes/RadioLib/pull/868 ).
  • The switching of the Antenna Switch can be done as such (kudos also to Jan from RadioLib for pointing it out :))
    static const uint32_t rfswitch_pins[] = { 25, RADIOLIB_NC, RADIOLIB_NC };
    static const Module::RfSwitchMode_t rfswitch_table[] = {
    {MODE_IDLE,  { LOW }},
    {MODE_RX,    { HIGH }},
    {MODE_TX,    { HIGH }},
    END_OF_MODE_TABLE,
    };
    (...)
    radio.setRfSwitchTable(rfswitch_pins, rfswitch_table); // must be called before begin!
    radio.begin();
  • There is currently a big PR from StevenCellist ( https://github.com/jgromes/RadioLib/pull/867 ) being put together which will make everything a lot better - I tested it and now the RAK11300 is fully working!
  • Currently still working on getting my PR for the RAK11300 included in the arduino-pico, but we are getting there 🙂

Update to RadioLib (@2023-11-12 14:02)

RadioLib has accepted the PR and the needed commits are now in the main branch.
Both RadioLib and Arduino-Pico have not yet posted a new release version, so if you want to use the tech right now, you need to install the current main git branches.

Update to Arduino-Pico (@2023-11-22)

Earle has now released arduino-pico 3.6.1 which does include my contribution and adds the RAK11300 as its own board to his awesome Arduino Core ( https://github.com/earlephilhower/arduino-pico/releases/tag/3.6.1 ). You can download it right now via the Arduino Board Manager. So we just need for the latest RadioLib to drop :).

Update to RadioLib (@2023-11-29)

Jan has now pushed RadioLib 6.3.0 which includes all changes and fixes to support the module 🙂 ( https://github.com/jgromes/RadioLib/releases/tag/6.3.0 ). I am thinking about putting up a finale example to use it and then this project to support the RAK11300 directly via Arduino will be finished.

Full Example / Closing thoughts (@2023-12-01)

Following full example shows all magic needed to use RAK11300 with the Arduino-Pico 3.6.1 (using either the standard Raspberry Pi Pico or the RAKwireless RAK11300 as board setting) and RadioLib 6.3.0. It was kind of quite a ride and I am very pleased with this working now, finally. Thanks a lot for all that helped make this a reality. All the best 🙂

/*
  RadioLib LoRaWAN End Device Persistent Example

  This example assumes you have tried one of the OTAA or ABP
  examples and are familiar with the required keys and procedures.
  This example restores and saves a session such that you can use
  deepsleep or survive power cycles. Before you start, you will 
  have to register your device at https://www.thethingsnetwork.org/
  and join the network using either OTAA or ABP.
  Please refer to one of the other LoRaWAN examples for more
  information regarding joining a network.

  NOTE: LoRaWAN requires storing some parameters persistently!
        RadioLib does this by using EEPROM, by default
        starting at address 0 and using 384 bytes.
        If you already use EEPROM in your application,
        you will have to either avoid this range, or change it
        by setting a different start address by changing the value of
        RADIOLIB_HAL_PERSISTENT_STORAGE_BASE macro, either
        during build or in src/BuildOpt.h.

  For default module settings, see the wiki page
  https://github.com/jgromes/RadioLib/wiki/Default-configuration

  For full API reference, see the GitHub Pages
  https://jgromes.github.io/RadioLib/
*/

// include the library
#include <RadioLib.h>

// RAK11300
// SX1262 on RAK11300 has the following connections:
// pin name       pin number  pin mnemonic (RAK11300 board in arduino-pico)
// SPI1 NSS/CS:   13          PIN_SPI1_SS
// BUSY/GPIO:     15          PIN_SX1262_BUSY     
// RESET/RST:     14          PIN_SX1262_NRESET
// DIO1/IRQ:      29          PIN_SX1262_DIO1     
// RXEN/swpwr:    25          PIN_SX1262_ANT_PWR  
// SPI1 CLK:      10          PIN_SPI1_SCK   
// SPI1 MOSI:     11          PIN_SPI1_MOSI  
// SPI1 MISO:     12          PIN_SPI1_MISO

// RAK 11300 setup of SX1262 Radio
SPISettings spiSettings(2000000, MSBFIRST, SPI_MODE0);
SX1262 radio = new Module(13, 29, 14, 15, SPI1, spiSettings);

// RAK11300 setup of RF switch configuration
// powers up RF switch when modules wants to send or receive data
static const uint32_t rfswitch_pins[] = {25,  RADIOLIB_NC, RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[] = {
  {Module::MODE_IDLE,  {LOW}},
  {Module::MODE_RX,    {HIGH}},
  {Module::MODE_TX,    {HIGH}},
  END_OF_MODE_TABLE,
};

// create the node instance on the EU-868 band
// using the radio module and the encryption key
// make sure you are using the correct band
// based on your geographical location!
LoRaWANNode node(&radio, &EU868);

void setup() {
  Serial.begin(9600);

  // RAK11300 set SPI1 ports correctly
  SPI1.setCS(13);
  SPI1.setSCK(10);
  SPI1.setTX(11);
  SPI1.setRX(12);
  SPI1.begin(13);

  // RAK11300 add RF switch configuration to radio
  radio.setRfSwitchTable(rfswitch_pins, rfswitch_table);

  // initialize SX1262 with default settings
  Serial.print(F("[SX1262] Initializing ... "));
  int state = radio.begin();
  if(state == RADIOLIB_ERR_NONE) {
    Serial.println(F("success!"));
  } else {
    Serial.print(F("failed, code "));
    Serial.println(state);
    while(true);
  }

  // first we need to initialize the device storage
  // this will reset all persistently stored parameters
  // NOTE: This should only be done once prior to first joining a network!
  //       After wiping persistent storage, you will also have to reset
  //       the end device in TTN and perform the join procedure again!
  // Here, a delay is added to make sure that during re-flashing
  // the .wipe() is not triggered and the session is lost
  // delay(5000);
  // node.wipe();

  // now we can start the activation
  // Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
  // uint64_t joinEUI = 0x12AD1011B0C0FFEE;
  // uint64_t devEUI = 0x70B3D57ED005E120;
  // uint8_t nwkKey[] = { 0x74, 0x6F, 0x70, 0x53, 0x65, 0x63, 0x72, 0x65,
  //                      0x74, 0x4B, 0x65, 0x79, 0x31, 0x32, 0x33, 0x34 };
  // uint8_t appKey[] = { 0x61, 0x44, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65,
  //                      0x6E, 0x74, 0x4B, 0x65, 0x79, 0x41, 0x42, 0x43 };
  // state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey);

  // after the device has been activated,
  // the session can be restored without rejoining after device power cycle
  // on EEPROM-enabled boards by calling "restore"
  Serial.print(F("[LoRaWAN] Resuming previous session ... "));
  state = node.restore();
  if(state == RADIOLIB_ERR_NONE) {
    Serial.println(F("success!"));
  } else {
    Serial.print(F("failed, code "));
    Serial.println(state);
    while(true);
  }

}

// counter to keep track of transmitted packets
int count = 0;

void loop() {
  // send uplink to port 10
  Serial.print(F("[LoRaWAN] Sending uplink packet ... "));
  String strUp = "Hello World! #" + String(count++);
  String strDown;
  int state = node.sendReceive(strUp, 10, strDown);
  if(state == RADIOLIB_ERR_NONE) {
    Serial.println(F("received a downlink!"));

    // print data of the packet (if there are any)
    Serial.print(F("[LoRaWAN] Data:\t\t"));
    if(strDown.length() > 0) {
      Serial.println(strDown);
    } else {
      Serial.println(F("<MAC commands only>"));
    }

    // print RSSI (Received Signal Strength Indicator)
    Serial.print(F("[LoRaWAN] RSSI:\t\t"));
    Serial.print(radio.getRSSI());
    Serial.println(F(" dBm"));

    // print SNR (Signal-to-Noise Ratio)
    Serial.print(F("[LoRaWAN] SNR:\t\t"));
    Serial.print(radio.getSNR());
    Serial.println(F(" dB"));

    // print frequency error
    Serial.print(F("[LoRaWAN] Frequency error:\t"));
    Serial.print(radio.getFrequencyError());
    Serial.println(F(" Hz"));

  } else if(state == RADIOLIB_ERR_RX_TIMEOUT) {
    Serial.println(F("no downlink!"));

  } else {
    Serial.print(F("failed, code "));
    Serial.println(state);
  }

  // on EEPROM enabled boards, you can save the current session
  // by calling "saveSession" which allows retrieving the session after reboot or deepsleep
  node.saveSession();

  // wait before sending another packet
  // alternatively, call a deepsleep function here
  // make sure to send the radio to sleep as well using radio.sleep()
  delay(30000);
}

RT-Thread IoT-Contest 2023 with NXP i.MX RT1060 - Getting started

The folks over at RT-Thread decided to make a IOT-Contest using hardware from different vendors and their RT-Thread OS. I was luckily enough to get chosen for a networking project with the NXP i.MX RT1060 EVKB, so I decided to document my journey first getting started with RT-Thread. Here we go! 🙂

Getting started

First of all, its important to know that there are two version of the MIMXRT1060 - the EVK and the EVKB. The EVK seems to be an older version which also includes a Camera sensor module, the EVKB version the recent one. RT-Threads example on Github is for the EVK version, which means things like User LED blinking does not work out of the box, but we will take care of this later.

NXP i.MX RT1060 EVKB files and documentation

You'll need to create a free user account on NXPs webpage to download the User manual and Schematic, otherwise the last two links will not work.

RT-Thread Setup

  • Main RT-Thread Github Page for the NXP i.MX RT1060: https://github.com/RT-Thread/rt-thread/tree/master/bsp/imxrt/imxrt1060-nxp-evk
  • If not yet done, download and install Git
  • RT-Thread Studio: https://www.rt-thread.io/studio.html
    • Just download and install (e.g. D:\RT-ThreadStudio)
  • envTool: https://github.com/RT-Thread/env-windows/releases
    • Pick the latest env-windows-v*.7z and download
    • Unzip the included folder (e.g. D:\RT-ThreadStudioEnv\env-windows-v1.3.5)
    • Start env.exe
    • Click on the Hamburger Menu, Settings, Integration
    • Click on "Register" and "Save settings", close the settings menu and the envTool
    • More detailed info for the envTool can be found: https://github.com/RT-Thread/rt-thread/blob/master/documentation/env/env.md
  • RT-Thread Github Repo: https://github.com/RT-Thread/rt-thread
    • Clone the repo ( git clone https://github.com/RT-Thread/rt-thread.git ) to your system, (e.g. to D:\RT-ThreadGithub)
  • First project
    • Start RT-Thread Studio
    • File -> Import -> RT-Thread Bsp Project into Workspace
    • Bsp Location (within the Github Repo): D:\RT-ThreadGithub\rt-thread\bsp\imxrt\imxrt1060-nxp-evk
    • Project Name: What you want, I choose blinky
    • Chip Name: MIMXRT1060
    • Debugger: DAP-LINK
    • click finish
    • This will lead to an error, pointing you to the workspace folder ( e.g. mine is D:\RT-ThreadStudio\workspace.metadata ) where a .log file resides. Open it up and scroll to the end. At this time in the development, there seems to be an error with the initial compilation shown with this error: "!MESSAGE D:\RT-ThreadGithub\rt-thread\bsp\imxrt\imxrt1060-nxp-evk>scons --dist-ide --project-path=D:\RT-ThreadStudio\workspace/blinky --project-name=blinky"``. To fix this issue, navigate with the windows explorer to the folder ``D:\RT-ThreadGithub\rt-thread\bsp\imxrt\imxrt1060-nxp-evk``. Within the folder, hold shift and right-click and choose ``ConEmu Here`` - the envTools will open up. Just copy and paste the complete scons command (``scons --dist-ide --project-path=D:\RT-ThreadStudio\workspace/blinky --project-name=blinky) into the envTools window and press enter. It should compile now.
    user@system D:\RT-ThreadGithub\rt-thread\bsp\imxrt\imxrt1060-nxp-evk
    > scons --dist-ide --project-path=D:\RT-ThreadStudio\workspace/blinky --project-name=blinky
    scons: Reading SConscript files ...
    Newlib version: 4.1.0
    make distribution....
    => imxrt1060-nxp-evk
    => start dist handle
    => copy imxrt bsp library
    => copy bsp drivers
    => copy bsp peripherals
    => components
    => include
    => libcpu
    => src
    => tools
    Update configuration files...
    suggest to use command scons --dist [--target=xxx] [--project-name=xxx] [--project-path=xxx]
    dist project successfully!
After this step, click finish in the still open import menu in RT-Thread Studio again, it should work now and generate the new project.
  • Navigate within Project Explorer through the Projectname to applications\ and open the main.c file.
  • Replace

    /* defined the LED pin: GPIO1_IO9 */
    #define LED0_PIN               GET_PIN(1, 9)
    
    int main(void)
    {
    #ifndef PHY_USING_KSZ8081
        /* set LED0 pin mode to output */
        rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
    
        while (1)
        {
            rt_pin_write(LED0_PIN, PIN_HIGH);
            rt_thread_mdelay(500);
            rt_pin_write(LED0_PIN, PIN_LOW);
            rt_thread_mdelay(500);
        }
    #endif
    }

    with

    /* defined the LED pin: GPIO1_IO8 */
    #define LED0_PIN               GET_PIN(1, 8)
    
    int main(void)
    {
    #ifndef PHY_USING_KSZ8081
        /* set LED0 pin mode to output */
        rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
    
        while (1)
        {
            rt_pin_write(LED0_PIN, PIN_HIGH);
            rt_thread_mdelay(500);
            rt_pin_write(LED0_PIN, PIN_LOW);
            rt_thread_mdelay(500);
        }
    #endif
    }

    and save the file. The User LED on the EVKB board is not on IO PIN 9, but 8. Also, this pin is shared with the ethernet controller - so if we enable this later, the User LED will not work anymore.

  • Click on the hammer icon ("Build 'Debug'") and it should compile the new software.
  • Click on the downward green arrow ("Flash Download") to download the program to the hardware board. The User LED should now be flashing.
  • There can be multiple issues at Download:
    • A window with "J-Link Emulator selection" pops up and asks for connection methods. This error means that RT-Thread Studio tries to program via Segger Link, which is the incorrect flash tool for the EVKB. If this comes up, please click no on the J-Link screen. Then check on the little black arrow attached to the Flash Download icon, that "DAP-LINK" is checked. Afterwards try Downloading again.
    • "pyocd.core.exceptions.TargetSupportError: Target type 'mimxrt1060' not recognized." If this error arises it can mean two things:
      • You did not enter the Chip Name correctly. Please check that the error is really mimxrt1060 - and no spelling issues are there. If there are, go to the Cogwheel Icon ("Debug configuration"), Debugger tab and correct the Chip Name within the Device name area. Click Ok to save and try again.
      • Scroll up through the error list and you might see the path of the pyocd software, e.g. RealThread\PyOCD\0.1.3 - this would mean you're running the default PyOCD 0.1.3 - which has some errors that will mean you cannot download to flash. Directly next to the "Flash Download" icon is the "SDK Manager", open it up and scroll down to the "Debugger_Support_Packages", "PyOCD". Choose the latest version (e.g. 0.2.0) and click on "Install packages". You can then select the old version(s) you have installed and click on "Delete packages". Afterwards close the SDK Manager. This should fix the issue.
  • If there are no issues with the Download, can you also "Open a Terminal" (computer screen icon close to "Flash Download"). And start with the correct settings (e.g. 115200 BAUD and the correct Serial port, should be chosen automatically if you already flashed a program before). You should see the RT msh console running on your EVKB and be able to send a "help" to get an overview over the device

    
    \ | /
    - RT -     Thread Operating System
    / | \     5.0.1 build May 28 2023 14:25:59
    2006 - 2022 Copyright by RT-Thread team
    msh >help
    RT-Thread shell commands:
    clear            - clear the terminal screen
    version          - show RT-Thread version information
    list             - list objects
    help             - RT-Thread shell help.
    ps               - List threads in the system.
    free             - Show the memory usage in the system.
    pin              - pin [option]
    reboot           - reset system
    
    msh >
  • To get a little bit further into a project, I replaced the main.c PIN definition and main() with following code

    // wrong definition, GPIO1_IO9 is ethernet leds on EVKB
    // #define LED0_PIN               GET_PIN(1, 9)
    
    // D8 (GPIO01-08) is the user led
    #define LED0_PIN               GET_PIN(1, 8)
    // SW5 (GPIO5-00) is the user button
    #define SW5_PIN                GET_PIN(5, 0)
    
    int main(void)
    {
    #ifndef PHY_USING_KSZ8081
        // set LED0 pin mode to output
        rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
        // set SW5 pin mode to pullup
        rt_pin_mode(SW5_PIN, PIN_MODE_INPUT_PULLUP);
    
        while (1)
        {
            if (!rt_pin_read(SW5_PIN)) {
                rt_pin_write(LED0_PIN, PIN_HIGH);
            } else {
                rt_pin_write(LED0_PIN, PIN_LOW);
            }
            /*
            rt_pin_write(LED0_PIN, PIN_HIGH);
            rt_thread_mdelay(500);
            rt_pin_write(LED0_PIN, PIN_LOW);
            rt_thread_mdelay(500);
            */
        }
    #endif
    }

    This will couple the LED to the status of the user switch (SW5): If its pressed, the LED will turn on, if its not pressed, the LED will stay off. Just save, re-compile and re-download.

Bugs

As seen there are some bugs already found:

  • RT-Thread Studio error upon trying to import a project
  • RT-Thread Studio failing to choose the correct debugger even though it was selected on creation/import of the project
  • pyocd error upon download of firmware to the NXP MCU due to old pyocd version shipped with RT-Thread Studio
  • Integration of the menuconfig tooling as "RT-Thread Settings" within RT-Thread Studio - but it just does not have any effect on the project

I hope that these issues get solved soon - but with the infos above you should be able to get started. I will see you in the next post - probably going through the project I made :).

balenaOS on the Advantech AIR-020X

Last week I posted the review of the Advantech AIR-020X with which I used to create the labSentinel 2 system.

I remarked that the hardware was great, however the software support and update capability of the system was severly lagging behind for an "industrial floor, always on" type of machine. Luckily, thats exactly what balena has been created for.

Even better, their environment already support Nvidia Jetson devices - also Nvidia Jetson Xavier NX modules. With the AIR-020X being a really nice carrierboard (and housing) for this module, I went to work.

Spoiler Alert - it works!

Installing balenaOS on the AIR-020X

1.) I setup an Ubuntu 20.04 LTS machine, installed npm and setup jetson-flash

2.) I went to https://www.balena.io/os and downloaded the latest NVIDIA JETSON XAVIER NX DEVKIT EMMC image (2.107.10) in the development version.

3.) Unzip the file after setting up jetson-flash and getting your AIR-020X into recovery mode. This means opening the bottom of the case by unscrewing the 4 philipps head screws, connecting the Micro USB port of the AIR-020X with your Ubuntu host computer, applying power to the AIR-020X, but do not yet press the power switch.

4.) There is foil/recovery switch next to the Micro USB connector and LAN port. You need to press and hold this switch and at the same time press the power on button of the unit for about 4 seconds.

5.) On Ubuntu, run lsusb | grep Nvidia - this should return a similar line to this

Bus 003 Device 005: ID 0955:7023 NVIDIA Corp. APX

Import is the ending "APX", which means it is in recovery mode.

6.) Now you can start the flash process

user@balenaTest:~/jetson-flash$ ./bin/cmd.js -f ./jetson-xavier-nx-devkit-emmc-2.107.10-v14.4.4.img -m jetson-xavier-nx-devkit-emmc

The .img value points to the unzipped image file, the -m tells the jetson-flash tool that we are running a Xavier NX system and want to install balenaOS on the internal eMMC module.

7.) This will now start the process which will take some minutes and also ask you for your sudo password. At the end you should see something like this:

[ 255.8670 ] Flashing completed

[ 255.8670 ] Coldbooting the device
[ 255.8696 ] tegrarcm_v2 --ismb2
[ 255.9454 ]
[ 255.9502 ] tegradevflash_v2 --reboot coldboot
[ 255.9530 ] Bootloader version 01.00.0000
[ 255.9984 ]
*** The target t186ref has been flashed successfully. ***
Reset the board to boot from internal eMMC.

8.) As soon as you reboot the device, you will be greeted with the balenaOS logo and can use it as any other balenaOS device.

Adding the AIR-020X to a fleet

If you want to use it e.g. in a fleet, I would recommend creating a new one with the device type Nvidia Jetson Xavier. This is important to allow sample projects to correctly work, as its basically the same thing as the more specialized version "jetson-xavier-nx-devkit-emmc" - but most demo projects just implement the former one :).

To now join the installed device onto your new fleet, download and install balenaCLI - login to your balena Cloud account and do a balena scan using balenaCLI to find your AIR-020X on the network.

-
  host:          56e1ef3.local
  address:       192.168.178.112
  osVariant:     development
  dockerInfo:
    Containers:        1
    ContainersRunning: 1
    ContainersPaused:  0
    ContainersStopped: 0
    Images:            1
    Driver:            overlay2
    SystemTime:        2023-03-10T14:05:52.568438957Z
    KernelVersion:     4.9.253-l4t-r32.7
    OperatingSystem:   balenaOS 2.107.10
    Architecture:      aarch64
  dockerVersion:
    Version:    20.10.17
    ApiVersion: 1.41

After that, you can easily join this device with

.\balena join 192.168.178.112
? Select fleet <yourFleetNameToSelect>
? Check for updates every X minutes 10
[Success] Device successfully joined balena-cloud.com!

... and voila, its online!

What does work?

Testing GPIO pins with a multimeter

The AIR-020X has a lot of custom GPIO chips, 2x RS485/RS232 interface, 1x CANbus interface, a second network interface and even a NVMe. Luckily, everything just works out of the box.

- HDMI works
- USB works
- onboard network card (dmesg + dhcp test, gets ip / works)
[   29.231807] eqos 2490000.ether_qos eth0: Link is Up - 1Gbps/Full - flow control rx/tx

- 2nd network card (dmesg + dhcp test, get ip / works)
[  104.307175] igb 0004:05:00.0 enP4p5s0: igb: enP4p5s0 NIC Link is Up 1000 Mbps Full Duplex, Flow 
- NVMe is recognized (lsblk)
nvme0n1      259:0    0 119.2G  0 disk
|-nvme0n1p1  259:1    0    96G  0 part
|-nvme0n1p2  259:2    0    64M  0 part
|-nvme0n1p3  259:3    0    64M  0 part
|-nvme0n1p4  259:4    0   448K  0 part
|-nvme0n1p5  259:5    0   448K  0 part
|-nvme0n1p6  259:6    0    63M  0 part
|-nvme0n1p7  259:7    0   512K  0 part
|-nvme0n1p8  259:8    0   256K  0 part
|-nvme0n1p9  259:9    0   256K  0 part
|-nvme0n1p10 259:10   0   300M  0 part
`-nvme0n1p11 259:11   0  22.8G  0 part
- can bus interface is auto loaded on boot (see ifconfig -a)
can0      Link encap:UNSPEC  HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
          NOARP  MTU:16  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:10
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
          Interrupt:63
- gpio/dio, works, but bit3 does sadly not work
( more info: http://ess-wiki.advantech.com.tw/view/File:AIR-020-nVidia_GPIO.docx )
Pin Number AIR-020X AIR-020T AIR-020N
GPIO bit1 	393 	269 		38
GPIO bit2 	421 	425 		149
GPIO bit3 	265 	411 		65
GPIO bit4 	424 	264 		168
GPIO bit5 	418 	476 		202
GPIO bit6 	436 	396 		246
GPIO bit7 	417 	337 		169
GPIO bit8 	268 	338 		194

# set bit 1 as GPIO pin
echo 393 > /sys/class/gpio/export
# get value 0=low, 1=high
cat /sys/class/gpio/gpio393/value
# set direction out or in
echo out > /sys/class/gpio/gpio393/direction
# get direction
cat /sys/class/gpio/gpio393/direction
out
# set value on out pin
echo 1 > /sys/class/gpio/gpio393/value

test:
# 265, bit3 did not work on export
echo 393 > /sys/class/gpio/export
echo 421 > /sys/class/gpio/export
echo 265 > /sys/class/gpio/export
echo 424 > /sys/class/gpio/export
echo 418 > /sys/class/gpio/export
echo 436 > /sys/class/gpio/export
echo 417 > /sys/class/gpio/export
echo 268 > /sys/class/gpio/export

echo out > /sys/class/gpio/gpio393/direction
echo out > /sys/class/gpio/gpio421/direction
echo out > /sys/class/gpio/gpio265/direction
echo out > /sys/class/gpio/gpio424/direction
echo out > /sys/class/gpio/gpio418/direction
echo out > /sys/class/gpio/gpio436/direction
echo out > /sys/class/gpio/gpio417/direction
echo out > /sys/class/gpio/gpio268/direction

echo 1 > /sys/class/gpio/gpio393/value
echo 1 > /sys/class/gpio/gpio421/value
echo 1 > /sys/class/gpio/gpio265/value
echo 1 > /sys/class/gpio/gpio424/value
echo 1 > /sys/class/gpio/gpio418/value
echo 1 > /sys/class/gpio/gpio436/value
echo 1 > /sys/class/gpio/gpio417/value
echo 1 > /sys/class/gpio/gpio268/value
- com ports, running as RS-232 or RS-485 (not tested, but recognized)
( more info: http://ess-wiki.advantech.com.tw/view/AIR-020-RS-485 )
root@56e8bf3:/# ls /dev/ | grep ttyTH
ttyTHS0 <- COM1
ttyTHS1 <- COM2
ttyTHS4

More info to the hardware can be found in the Advantech Wiki.

GPU Demos

Last but not least I want to point you towards the nice balena Jetson tutorial which can be found here.

It will help you getting started with Jetson samples that are hosted here.

In the end I was able to also get CUDA acceleration to work and see this smoke demo:

Nothing like some GPU accelerated smoke

With that I am closing this post. It was surprisingly easy to get this device to work - the only thing left would be to get it to boot and to work from its internal NVMe storage, but other than that its a nice tool for working with GPU workloads like Edge Impulse.