Orin nano wont detect arduino /dev/ttyUSB* or /dev/ttyACM*

Hi all,

I have just upgraded my orin nano to Jetpack 6 (Ubuntu 22.04) and now I can’t detect my arduino. When I run

ls /dev/tty*

I cannot see ttyACM* or ttyUSB*

I installed arduino from arduino.cc/en/software → arm64
I have disabled brltty using:

systemctl stop brltty-udev.service
sudo systemctl mask brltty-udev.service
systemctl stop brltty.service
systemctl disable brltty.service
sudo apt-get remove brltty

Added myself to dialout:

sudo usermod -a -G tty <my username>
sudo usermod -a -G dialout <my username>

When I run dmesg and the plug in the USB cable I get:

[  604.888101] usb 1-2.2: new full-speed USB device number 10 using tegra-xusb

and nothing else.

When I run lsusb, I get:

Bus 001 Device 014: ID 1a86:7523 QinHeng Electronics CH340 serial converter

It’s an arduino mega clone.

I appreciate your help.

You probably shouldn’t add yourself to group tty, although adding to dialout is recommended. If tty owns a device, then it means one of the terminal services is running on that tty (which is generally run by root).

When you got that log line it said that USB is working correctly, but no driver took responsibility for the device. Sometimes you need a udev rule (I see you were looking at a braille software that uses udev triggering), but I don’t know if that is the case here.

If we look up the USB registry for manufacturer and device ID here:
http://www.linux-usb.org/usb.ids
…then we see:

1a86  QinHeng Electronics
	7523  CH340 serial converter

It turns out that the CH340 driver uses the kernel symbol CONFIG_USB_SERIAL_CH341. What do you see from this?
zcat /proc/config.gz | grep 'CH341'

If you have this driver, then the driver can work with that UART. If you have the driver, but it fails to associate, then probably you need a udev rule (though not necessarily the brltty-udev.service).

Hello Linuxdev,

Thanks for your response.
I was trying every option I could find on forums. How do I remove myself from tty?
Running:

zcat /proc/config.gz | grep 'CH341'

Returns this:

# CONFIG_USB_SERIAL_CH341 is not set

I don’t know what this means? Is there a fix?

Thanks

I’ve tried this:

Which didn’t work. I assume due a mismatch between the module and kernel versions?

In the interim, I found a workaround, use a genuine arduino mega (it uses a different serial converter). I would still like the solution to the problem please :)

Some information, not necessarily in order…

If you want to remove yourself from a group, for example, remove from tty, it can be more difficult because the commands are not uniform across distributions for removal. You can list your current groups like this:
groups

You will get a listing of each group. If you were to set your groups…and I emphasize this is not append…then you would use a comma delimited list of all of those groups except for tty. I will give an example:

# You current groups might show as your account name and others:
sanespeed tty adm dialout cdrom sudo dip plugdev lpadmin lxd sambashare

# You would SET to this list, LESS the tty (there is no "-a" option, be very careful):
sudo usermod -G sanespeed,adm,dialout,cdrom,sudo,dip,plugdev,lpadmin,lxd,sambashare sanespeed

Be very careful to note that the final usermod line at the end ends with a space and the name of the account being set to that list. The comma delimited section includes a group by the account’s own name, and all groups were listed with commas between them except for tty.

Since this shows you do not have the CH341 driver:
# CONFIG_USB_SERIAL_CH341 is not set
…the implication is that this device (when used via a USB connector on that board) cannot function. The driver is missing. You must build a driver (preferably as a module built against kernel source configured to otherwise match your running kernel) and copy that .ko module file to the correct place (unless there is some special requirement you should avoid flashing to add that driver).

That JetsonHacks article is trying to give you a simplified install, but it will work only with L4T R28.1. That module is built against the wrong kernel version and wrong kernel configuration. Unless of course you have R28.1, which is extraordinarily out of date.

Which L4T release do you have? See “head -n 1 /etc/nv_tegra_release”.

Hi linuxdev,

Thanks again :)

I followed your instructions for setting groups and I’m still in tty:

sudo usermod -G sanespeed,adm,dialout,cdrom,sudo,audio,dip,video,plugdev,render,i2c,lpadmin,gdm,sambashare,weston-launch,gpio,jtop sanespeed

groups:

sanespeed adm tty dialout cdrom sudo audio dip video plugdev render i2c lpadmin gdm sambashare weston-launch gpio jtop

Part B:

# R36 (release), REVISION: 2.0, GCID: 34956989, BOARD: generic, EABI: aarch64, DATE: Thu Nov 30 19:03:58 UTC 2023

Within: /lib/modules/5.15.122-tegra/kernel/drivers/usb/serial this is this driver:
ch341.ko. Is this what was installed when I ran jetsonHacks script? and do I need an R36 compatible one?

Cheers

For reference, any document you use should be from L4T R36.2:
https://developer.nvidia.com/embedded/jetson-linux-r362

Setting group as above should have worked. You can directly edit /etc/group, but only with the application vigr. If you don’t know vi/vim, then this would be quite a learning curve (vigr is an adaptation of vi for editing /etc/group). I hesitate to mention this due to the things that can go wrong if done incorrectly (there is a second file as well, /etc/group-, and they are intended to have above normal security, and work together). However, one thing to note: After removing from that group, you will have to reboot. Have you checked groups after reboot?

And yes, you need a version of the driver compatible with your kernel. That driver is from L4T R28.1, which is extraordinarily out of date. This is from a time nearly a decade before Orin was even invented. The source code version is wrong, and so is the configuration. As soon as you use a different kernel source, the driver is no longer valid. As soon as you some configuration changes, even if using the same kernel, the driver once again becomes invalid.

You should look at the official documents for “Kernel Customization” and cross compile on your host PC. Do not use the install documentation though, you don’t need all that effort. You should start by being confident that you can compile a kernel configured with the tegra_defconfig target (explained in those docs), *plus you must set CONFIG_LOCALVERSION to “-tegra”. If you can do that, then you make the clean target, use menuconfig or nconfig target to edit (I prefer nconfig, it is the same other than having a symbol search), and adding the CONFIG_USB_SERIAL_CH341 symbol via a module (using the ‘m’ key in the config editor instead of the ‘y’ key). Build the actual kernel Image target once as an acid test to see if all is well, and then build the modules target. If you don’t build Image first you have to run the modules_prepare target. The clean target lets you start over.

You can always ask more questions, but go to the R36.2 URL and start with the docs there and find the “Kernel Customization”. If you wish to build natively on a Jetson it is slightly different, and you will likely need external disk space.

You’re right, a reboot removed me from groups.

As for the USB driver issue, Kernel Customisation is way beyond my ability at the moment. I had great difficulty flashing my jetson SSD, and so I don’t want to do anything that might compromise it. I have a workaround for the USB driver issue, so I’m going to continue with that (use a genuine arduino). Thanks again for your help. I hope at the least this is a good resource for others when they inevitably have the same issue.
NVIDIA please include arduino USB drivers in future L4T releases. I don’t understand why they were removed from this release.

If you add a driver in the form of a module, there is virtually no risk. It isn’t the building of a driver that has risk, and it isn’t really the install of just module that is notably risky. The problem is when you install a new base Image file (which is what modules load into). Just don’t use flash to install kernel modules.

But it would probably be OK for NVIDIA to make the CONFIG_USB_SERIAL_CH341 available by default as a module.

1 Like

Hi linuxdev, I faced the same problem as sanespeed. However, I have limited knowledge on “Kernel Customization”. Can you please explain what do you mean by cross compile on the host PC? Does it mean I have to reflash the image with customed kernel into jetson orin nano?

Official documents usually center on setting up the flash software with a new kernel, but this is rarely required. If you build something in the form of a module (and not all drivers can be a module, but most can), then it is a simple file copy after you build it. Some file with a .ko name extension becomes a dynamically loadable driver.

The main kernel though, which integrates many drivers, is the Image file. If a module is built against that kernel’s configuration, then the module is loadable into that module. If the module is not built against that Image file, then it is likely the module would not load. When I say “built against” I mean that your other source code is first configured to match the running Image, and then any module features are added. If you change a non-module feature, then you have not matched that kernel (this includes both adding and removing non-module features). The configuration is based on “symbols” for uniquely identifying configuration. Check this out to see a list of features (“=m” is a module, “=y” is integrated into the Image):
zcat /proc/config.gz

If you change the Image, then there are risks which a module does not have. If you change the Image, then the procedure is more extensive.

Normally the only thing which forces you to flash is if you’ve burned security fuses. Security fuses force kernel and configuration to be taken from a signed partition.

One notable exception is that if the module is required for boot, for example, a module supplying the external media driver or supplying some odd filesystem type, then the driver which allows the kernel to access the device must first have the driver on that device to boot…the proverbial “chicken and egg” dilemma. In that case the module can be added to an initial ramdisk (the initrd). Sometimes the initrd can be updated externally and copied in as a file, but often one will flash if updating the initrd (it isn’t technically mandatory, but updating an initrd requires a more detailed level of knowledge, and it would go away if you flash).

I usually recommend that if one is updating the Image to keep the original in place, and simply add an alternate boot entry to some modified Image. Then you can pick the original kernel if there is a failure of the new Image.

Summary: The Image is the kernel. This is usually a file in /boot, but it can also be in a partition. There are risks for replacing Image. Modules which are built against that Image’s configuration can load at runtime, and are very low risk to add, and quite easy to copy in. These are also part of the kernel, but they are loaded on demand. The kernel loads modules from the location “/lib/modules/$(uname -r)/kernel/”, and “uname -r” is a combination of the kernel source code version and the configuration. Changing configuration or kernel version means changing the module location. Unless you are using external media there is no need to use an initrd for a module, and then this only changes if a module is required to access the module directory. Don’t change the Image if you don’t have to.

Appreciate your detailed explanation @linuxdev. From the official documentation on kernel customisation, I noticed the following command:

export CROSS_COMPILE=/bin/aarch64-buildroot-linux-gnu

I am wondering what does toolchain-path refer to? Could you please clarify on this? Also, how can I build the tegra_defconfig target as mentioned by you in the post Enable CONFIG_USB_SERIAL_PL2303 on kernel - #12 by linuxdev Specifically, what is the command that I have to execute to perform the build task? Thanks in advance.

Which specific L4T release does your Jetson run? See “head -n 1 /etc/nv_tegra_release”.

Just as a general concept, compilers put out code for some architecture. Native compile implies the compiler runs on the same architecture that the code is output for. Simplified, this means the native compiler works with the environment it is in and does not substitute with special rules. Cross compilers run on one architecture, but provide output to another architecture. So long as this is bare metal there is no need to link without outside libraries…a kernel is bare metal, so are bootloaders. They don’t use libraries.

As soon as you go to user space, and not bare metal (kernel space), there is an environment you must “fit into”. You need a linker to help with that. A regular native linker just looks at the default system. A cross linker is a cross tool (like the cross compiler) for loading outside software into a program. A cross linker runs in one architecture, but links between a library and executable code that differs from the native environment.

The part which is so difficult about the latter cross linking in user space (not bare metal) is that not only do you need to provide the cross tool, you also need to provide the alternative environment’s libraries. To some extent this also includes things like file paths, environment variables, and everything an end program might inherit.

Basic kernel compile has the option to output compiled code into an alternate location to keep the source code pristine. This is nice because for example several end users could compile code simultaneously using the same source code in read-only mode. There isn’t much a cross compile of a bare metal kernel or kernel driver needs other than the correct source code and the correct source configuration. The cross compiler is enough for tools, and everything else the compile might require usually can be from the native system (e.g., maybe there is a string parsing tool…this is not architecture dependent since strings are strings are strings…unless you start getting into character sets under special circumstances).

First you have to understand native compile setup, which isn’t actually too extensive, but it is difficult to just give commands. Then, if you use the correct cross compiler setting, cross compile is otherwise no different. The temporary output location must be used for install rather than installing it to the host PC, but this is essentially just a file to copy in the case of a UART driver.

Do you have the full kernel source from your specific L4T release? Use “head -n 1 /etc/nv_tegra_release” to find that. Then go to that URL:
https://developer.nvidia.com/linux-tegra

In JetPack 6.x/L4T R36.x this might just be a mainline kernel, but you must still use the same source code release as that which runs on your system. The L4T release URL should be able to get to the kernel source.

This is quite old, and this will have variations on how to do this (there is more than one way to do this), but this is the gist of steps (you can unpack kernel source as user root/sudo, but do not compile as root/sudo…keep your source code pristine):

# The locations mentioned are random and just examples. This is NATIVE compile
mkdir -p "${HOME}/build/kernel"
mkdir -p "${HOME}/build/modules"

# This will be quite a bit different for Orin...version 4.9 is outdated, so expect
# some other location name for an Orin's kernel (this is an example path):
export TOP="/usr/src/sources/kernel/kernel-4.9"

# These are all temporary output locations. You could delete them and recreate
# to start over. The environment variable names are more or less random, they
# could be any name for *these*. Cross compile is not considered here, but will
# be noted later.
export TEGRA_KERNEL_OUT="${HOME}/build/kernel"
export TEGRA_MODULES_OUT="${HOME}/build/modules"
export TEGRA_BUILD="${HOME}/build"

# --- Notes: ------------------------------------------------------------
# It is assumed kernel source is at "/usr/src/sources/kernel/kernel-4.9".
# Check if you have 6 CPU cores, e.g., via "htop".
# If you are missing cores, then experiment with "sudo nvpmodel -m 0, -m 1, and -m 2".
# Perhaps use "htop" to see core counts.
# Using "-j 6" in hints below because of assumption of 6 cores.
# -----------------------------------------------------------------------

# Compile commands start in $TOP, thus:
cd $TOP

# This sets up part of the default:
make O=$TEGRA_KERNEL_OUT tegra_defconfig

# Within the nconfig editor make sure CONFIG_LOCALVERSION is "-tegra".
# There are alternative methods to set this. You would also use this to find
# and set up the new UART driver as a module. nconfig has a symbol search
# function.
make O=$TEGRA_KERNEL_OUT nconfig

# You might be building only modules, but it is very strongly recommended to build
# the Image once as an acid test to see if configuration is valid.
# If building the kernel Image:
make -j 6 O=$TEGRA_KERNEL_OUT Image

# If you did not build Image, but are building modules:
make -j 6 O=$TEGRA_KERNEL_OUT modules_prepare

# To build modules:
make -j 6 O=$TEGRA_KERNEL_OUT modules

# To put the resulting modules in an alternate location which mimics the
# structure of where the final module will actually be copied:
make -j 6 O=$TEGRA_KERNEL_OUT INSTALL_MOD_PATH=$TEGRA_MODULES_OUT modules_install

All of that is nearly the same in a cross compile. However, in a cross compile you need to set up a slight change to environment to use the cross tools and name the architecture. If you have cross tools, then those can be named in an environment variable.

Cross tools generally get prefixed in their names based on the target output. A kernel compile would require gcc, and so it is likely that the cross-gcc is named aarch64-linux-gnu-gcc, although there are variations on that too. Kernel build is rather nicely set up, and so there are simple ways to add this information.

Here is an alternative copy of the native build listed above, but for 64-bit ARM (a.k.a., aarch64, or sometimes arm64, used in ARMv8-a):

# The locations mentioned are random and just examples. This is aarch64 compile.
# Remember that you can delete these temp locations and start over, the kernel
# source itself is kept pristine since no output is being used there in this example.
# The output in home must be writable by the user, but the source code can be
# read-only and enforced by being owned by root. Then compile only as a regular user.
mkdir -p "${HOME}/build/kernel"
mkdir -p "${HOME}/build/modules"

# Specific to cross compile:
export CROSS_COMPILE=/usr/bin/aarch64-linux-gnu-
export ARCH=arm64

# Note: arm64 is the terminology in the kernel source, aarch64 is the cross tool
# terminology. The build steps will know what to do with the above. More explanation
# follows after this example.

# This will be quite a bit different for Orin...version 4.9 is outdated, so expect
# some other location name for an Orin's kernel (this is an example path):
export TOP="/usr/src/sources/kernel/kernel-4.9"

# These are all temporary output locations. You could delete them and recreate
# to start over. The environment variable names are more or less random, they
# could be any name for *these*. Cross compile is not considered here, but will
# be noted later.
export TEGRA_KERNEL_OUT="${HOME}/build/kernel"
export TEGRA_MODULES_OUT="${HOME}/build/modules"
export TEGRA_BUILD="${HOME}/build"

# --- Notes: ------------------------------------------------------------
# It is assumed kernel source is at "/usr/src/sources/kernel/kernel-4.9".
# Check if you have 6 CPU cores, e.g., via "htop".
# If you are missing cores, then experiment with "sudo nvpmodel -m 0, -m 1, and -m 2".
# Perhaps use "htop" to see core counts.
# Using "-j 6" in hints below because of assumption of 6 cores.
# -----------------------------------------------------------------------

# Compile commands start in $TOP, thus:
cd $TOP

# This sets up part of the default:
make O=$TEGRA_KERNEL_OUT tegra_defconfig

# Within the nconfig editor make sure CONFIG_LOCALVERSION is "-tegra".
# There are alternative methods to set this. You would also use this to find
# and set up the new UART driver as a module. nconfig has a symbol search
# function.
make O=$TEGRA_KERNEL_OUT nconfig

# You might be building only modules, but it is very strongly recommended to build
# the Image once as an acid test to see if configuration is valid.
# If building the kernel Image:
make -j 6 O=$TEGRA_KERNEL_OUT Image

# If you did not build Image, but are building modules:
make -j 6 O=$TEGRA_KERNEL_OUT modules_prepare

# To build modules:
make -j 6 O=$TEGRA_KERNEL_OUT modules

# To put the resulting modules in an alternate location which mimics the
# structure of where the final module will actually be copied:
make -j 6 O=$TEGRA_KERNEL_OUT INSTALL_MOD_PATH=$TEGRA_MODULES_OUT modules_install

In any compile any use of the tool gcc is prefixed by what you see in the CROSS_COMPILE variable. If you’ve installed a cross tool at /usr/bin/aarch64-linux-gnu-gcc, and if you have CROSS_COMPILE set to /usr/bin/aarch64-linux-gnu-, then all use of gcc finds the cross tool by combining with CROSS_COMPILE. It is similar for cross linkers, that same prefix is prepended. If the prefix is empty, then you get exactly that tool and not the cross tool

The “ARCH=arm64” sets up for that bare metal. Do not use this if native compiling.

Note that if you don’t trust that the original source code is pristine and unmodified and unconfigured, that you can cd to the top of the source and run this command to make it pristine:
sudo make mrproper
(I only used sudo because I think kernel source should be owned by root and read-only to all others; it is the other users which compile the source, although there is no enforcement of that policy)

You will find the output in the temporary module output location is what you set up $TEGRA_MODULES_OUT as. For example, if you created a driver in “net” called “sample.ko”, then you would find the arm64 version at (the “uname -r” is that of the kernel source, not that of the host o/s):
${TEGRA_MODULES_OUT}/lib/modules/$(uname -r)/kernel/drivers/net/sample.ko

For this contrived example you would copy “${TEGRA_MODULES_OUT}/lib/modules/$(uname -r)/kernel/drivers/net/sample.ko” to the Jetson’s “/lib/modules/$(uname -r)/kernel/drivers/net/sample.ko”.

The big key is to make sure the kernel configuration takes place before making config edits. Then use something like nconfig to add your driver as a module.

The output of head -n 1 /etc/nv_tegra_release is

R36 (release), REVISION: 2.0, GCID: 34956989, BOARD: generic, EABI: aarch64, DATE: Thu Nov 30 19:03:58 UTC 2023

Since there is enough storage in my sd card (about 20 GB), I decided to go for native compile for kernel customisation.

I downloaded the kernel source file from the official page: https://developer.nvidia.com/downloads/embedded/l4t/r36_release_v2.0/sources/public_sources.tbz2

From the official documentation on kernel customisation, I unpacked the .tbz2 file and then extract the kernel source file.

mkdir <install-path>/Linux_for_Tegra
tar xf public_sources.tbz2 -C <install-path>/Linux_for_Tegra/…
cd <install-path>/Linux_for_Tegra/source
tar xf kernel_src.tbz2

After that, with reference to your example code, I make the directory to store the kernel and modules.

mkdir -p “${HOME}/build/kernel”
mkdir -p “${HOME}/build/modules”

I set the TOP to be the path of the kernel source directory and export the environment variables for the output directories.

export TOP=“<install-path>/Linux_for_Tegra/source/kernel/kernel-jammy-src”
export TEGRA_KERNEL_OUT=“${HOME}/build/kernel”
export TEGRA_MODULES_OUT=“${HOME}/build/modules”
export TEGRA_BUILD=“${HOME}/build”

I change the directory to TOP.

cd $TOP

Then, I build the defconfig target. (p.s. tegra_defconfig is not an available rule)

make O=$TEGRA_KERNEL_OUT defconfig

I open the nconfig editor.

make O=$TEGRA_KERNEL_OUT nconfig

Within the nconfig editor, in the General Setup menu, I set the Local version to -tegra.

Then, to set CONFIG_USB_SERIAL_CH341, I go to Device Drivers > USB support > USB Serial Converter support, and set the USB Winchiphead CH341 Single Port Serial Driver option to M.

After finished editing the configuration, I saved the changes in .config file.

After that, I build the kernel Image followed by the modules and then install the modules.

make -j 6 O=$TEGRA_KERNEL_OUT Image
make -j 6 O=$TEGRA_KERNEL_OUT modules
make -j 6 O=$TEGRA_KERNEL_OUT INSTALL_MOD_PATH=$TEGRA_MODULES_OUT modules_install

Then, I copy the ch341.ko file located at $TEGRA_KERNEL_OUT/drivers/usb/serial to /lib/modules/$(uname -r)/kernel/drivers/usb/serial.

cd ~
sudo cp $TEGRA_KERNEL_OUT/drivers/usb/serial/ch341.ko /lib/modules/$(uname -r)/kernel/drivers/usb/serial

Lastly, I update the module dependency information for all installed kernels and reboot.

sudo depmod -a
sudo reboot

After reboot, I check if the CONFIG_USB_SERIAL_CH341 is set.

zcat /proc/config.gz | grep ‘CH341’

To my disappointment, it returns

CONFIG_USB_SERIAL_CH341 is not set

May I know why is it so? Did I miss any steps? Thanks.

Your source download is correct. Your other procedures appear correct as well.

What happens if you “sudo modprobe ch341”? You might want to monitor “dmesg --follow” prior to running that command and note any log lines which occur due to the modprobe. It would also be useful, after that command, to see the output of “lsmod”.

Incidentally, the “/proc/config.gz” is a list of features as they were set at the time the Image was created. It won’t know about modules you added later on.

With the arduino plugged into Jetson Orin Nano, nothing happens when I run sudo modprobe ch341.
Running the lsmod gives me the following output (I just copied those related to ch341).

Module Size Used by
ch341 20480 0
usbserial 40960 1 ch341

When I plugged the arduino into Jetson Orin Nano, the dmesg --follow output is as follows:

[ 1054.859694] usb 1-2.1: new full-speed USB device number 9 using tegra-xusb
[ 1054.968680] ch341 1-2.1:1.0: ch341-uart converter detected
[ 1054.975907] usb 1-2.1: ch341-uart converter now attached to ttyUSB0
[ 1055.075382] usb 1-2.1: usbfs: interface 0 claimed by ch341 while ‘brltty’ sets config #1
[ 1055.077465] ch341-uart ttyUSB0: ch341-uart converter now disconnected from ttyUSB0
[ 1055.077511] ch341 1-2.1:1.0: device disconnected

Your driver is installed and available. It is available as “/dev/ttyUSB0”. However, the log shows it then disconnect; if this is a result of unplugging the USB cable, then this is as expected. If the device disconnects despite leaving the cable in, then that is another problem.

If the cable is plugged in, with no other effort, do you see the device via:
ls -l /dev/ttyUSB*

If you don’t see it, then run “sudo modprobe ch341” and check again with ls.

I noticed that I have to disable brltty as mentioned by @sanespeed. Anyways, thanks @linuxdev for your guidance in solving my issue!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.