How do I set a serial baud rate of 500,000 on the Jetson?

I’m currently making a T-shirt shooter robot that uses a Jetson and an Arduino as a control system.

However, 9600 baud is too slow and 115200 baud has too big of an error rate to send reliable commands to the robot.

If I use 9600 baud, the Arduino can’t get the commands fast enough and it just freezes, and with 115200 baud some commands will come over the line corrupted and do the opposite of the intended effect.

For some reason when I set the Jetson baud rate to above 115200 baud in my control system code, all I get is gibberish. I did some research and I think this has to be a problem with the Linux kernel because I used a Windows laptop with the same exact code running, and it was able to send and receive commands at 500000 baud.

Is there any way to get the Jetson to be able to run at 500000 baud? We can’t switch to a Windows-based control system because of obvious reasons, and the Jetson is perfect for the job.

Thanks in advanced.

There are some issues with higher clock rates. If your Arduino can use two stop bits, then you should be able to use those higher rates. Also beware that it is sensitive to noise, and thus shorter wires are better, twisted pair is better than non-twisted, shielded is better than non-shielded. Ethernet cable is a good choice of cable for serial data.

If you can’t set two stop bits, then you may have to back off to 115200.

Note that sometimes your program using the serial port can have its settings propagate to the serial UART. If so, then that’s the easiest way to do it. If not, then you can use stty.

Assuming you use “/dev/ttyTHS2” (J17 on the dev carrier board) you could view existing settings:

# sudo stty -a -F /dev/ttyTHS2
speed 9600 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V;
discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 hupcl -cstopb cread clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc

The above is 9600 speed, so for 115200:

sudo stty -F /dev/ttyTHS2 115200

…then verify speed changed with “stty -a -F /dev/ttyTHS2”.

Stop bit syntax is a bit trickier. The “cstopb” is used if one or two stop bits are used, but the leading hyphen ‘-’ changes meaning. No hyphen implies two stop bits, one hyphen means one stop bit. If using two stop bits, then reading back the settings will show “cstopb”; if using one stop bit, then reading back settings will show with a hyphen, “-cstopb”.

Setting 115200 and two stop bits:

sudo stty -F /dev/ttyTHS2 115200 cstopb

115200 and one stop bit:

sudo stty -F /dev/ttyTHS2 115200 <b>-</b>cstopb

Speed 500000 and two stop bits:

sudo stty -F /dev/ttyTHS2 500000 cstopb

I do recommend that before you go to higher speeds make sure your cable is of good quality twisted pair, and preferably shielded.

Thanks for the fast reply! Very useful information.

I’ve done as you said and used stty to set the baud rate and data bit, and I changed the Arduino code to abide by those rules.

nvidia@tegra-ubuntu:~$ sudo stty -F /dev/ttyACM0 500000 cstopb
[sudo] password for nvidia:
nvidia@tegra-ubuntu:~$ stty -a -F /dev/ttyACM0
speed 500000 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^H; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; discard = ^O; min = 100; time = 2;
-parenb -parodd -cmspar cs8 -hupcl cstopb cread clocal -crtscts
-ignbrk brkint ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl ixon -ixoff
-iuclc -ixany -imaxbel -iutf8
-opost -olcuc -ocrnl -onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
-isig -icanon iexten -echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke -flusho -extproc
nvidia@tegra-ubuntu:~$ cat /dev/ttyACM0
SERIAL INITIALIZED
STARTING MAIN LOOP

I also was able to send commands through echo /dev/ttyACM0 and it accepted them. I ran my Java code and it ran perfectly also. Thank you for your help.

For this to work every startup, where would be the best place to put the command?

The definitive place to put the setting is described as “it depends” :P

If your ttyACM0 device special file exists at the time “/etc/rc.local” runs, then this would be easy.

If you have your own programs where the programs themselves can run the command, or do the configuration themselves, then that would probably be even better.

The modern way, if you want this to automate modifying the nature of a “/dev” file, is perhaps to create a udev rule. For example, many hardware providers install a udev rule to rename the device special file when it is detected as being their hardware (I have two different JTAG debuggers which both use FTDI chipsets, but udev renames them if they belong specifically to those debuggers). Or to set up permissions, so on. A serial UART could have its setup considered eligible for setup via udev.

udev can be kind of a big topic, but parts of it are basic. If you go to “/etc/udev/” you will notice a theme that rules which are in “rules.d/” subdirectory override any rules of the same name which are part of the operating system. The same is true of “/etc/systemd/” which uses the same udev rules. Rules you get with the operating system are in “/lib/systemd/” and might be a “unit”, a “service” or a “target”. You’ll find this of particular interest:

find /lib/system -name '*serial*'

…perhaps ttyACM has something in there, I dont’ know since I don’t have any ttyACM devices.

But lets say you find “/lib/systemd/system/setserial.service” is something you want to change. To start with, if you place a rule in one of the relevant “/etc” locations, then the “/etc” version will take precedence. The file you start with could be a copy of one from “/lib/systemd/”, and then edited…remove your version, and it reverts back to the system version. If you place a symbolic link from the right place in “/etc” to a file in “/lib/system/”, then you are essentially declaring to do nothing…and do what it does anyway. This latter might not exactly be true because sometimes there are boot options and the way to pick the default is by putting a sym link from “/etc” into “/lib/systemd/” to the right file using an alternate name…one isn’t editing files that way, but editing instead a file name for “defaults”.

A good example I can think of, but which does not actually work on a Jetson (that is yet another story…there is some breakage in transitioning from older init style scripts and newer systemd init), is that on a PC you set whether to boot to text mode or graphical mode via naming the systemd target as either “multi-user.target” (text mode) or “graphical.target” (GUI mode). The system has the choice made by placing a symbolic link into “/etc” which points into the “/lib/systemd/” version of multi-user.target or graphical.target. systemd is programmed to look for “default.target”, and so the final symbolic link in “/etc” would actually be named “default.target”. Example from a Fedora desktop:

# Real files:
/lib/systemd/system/multi-user.target
/lib/systemd/system/graphical.target
# Symbolic link in "/etc/systemd/system/":
lrwxrwxrwx. 1 root root 36 Jan  1  2018 default.target -> /lib/systemd/system/graphical.target

When I boot a system with default.target pointing at “graphical.target” it is the same as running “sudo systemctl isolate graphical.target”. Not specifying uses “default.target”…which is exactly the same as graphical.target in my case due to the symbolic link.

EXAMPLE via UDEV:

In your case you would be putting a file in “/etc/udev/rules.d/”, and give it a name something like “99-custom-ACM-arduino.rules” (you can give a later number for things initialized later, or an earlier number for things needing initializing before other before other udev rules…some udev rules depend on other rules). A skeleton of this file would look like this…but I have to warn you my idVendor and other details are custom to the hardware…you can’t just copy this verbatim, you’ll need to adjust for your hardware:

# /etc/udev/rules.d/10-lauterbach.rules

ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="0897", SYMLINK+="lauterbach/trace32/%k", MODE:="0666"

…as a result, when I boot up and the trace32 debugger is visible, I get this (the “9” is USB bus 9, the “1” is USB device number on that bus…these change depending on the slot the debugger is connected to and how many other devices are enumerating):

/dev/lauterbach/trace32/9-1
# ls -l /dev/lauterbach/trace32/9-1 
lrwxrwxrwx. 1 root root 21 Jul  5 11:50 /dev/lauterbach/trace32/9-1 -> ../../bus/usb/009/002

FYI, if you run “lsusb” and the device is “NVIDIA” vendor, then ID will start with “ID 0955:”. After the 0955: you will see a specific device number. 0955 is an example of NVIDIA’s “idVendor”. It happens that the example debugger show is “idVendor” of “0897” when listed under lsusb. Whether or not just an idVendor is enough depends on your device and whether there are other USB devices using the same lsusb idVendor.

If you really want to set up your platform to formally handle this you’d use a udev rule. If your application is a script or easy to edit config for, then you’d set it up in the application. If you don’t need the setup until the system is up and running, and you know the device is always going to be there during boot, you could use “/etc/rc.local”.