Skip to content

Lidar Mapping (PoC) »

Rover system on a Jetson Nano Dev Kit

Last update: 2022-06-29

Operating System#


You can select one of below method:

Pre-built image#

  • Download the latest Jetson Nano Developer Kit SD Card Image. Other older versions can be found in Jetpack Archive.

    As the pre-built image is about 13 GB, which includes full JetPack source, libraies and example, you will need a micro-SD Card with at least 32 GB to be able to install ROS.

  • Download balenaEtcher and install it, and write Jetson Image in an SD Card.

  • Power on the board, either use Monitor with Keyboard and Mouse, or use Headless Mode by using DC jack (Jumper J48 connected) and connect micro-USB port to PC.

    Headless setup only available on the virtual COM port when power up via DC Jack!

    UART2 @ J50
    Starting kernel ...
    [    0.000000] earlycon: uart8250 at MMIO32 0x0000000070006000 (options '')
    [    0.000000] bootconsole [uart8250] enabled
    [   15.823522] Please complete system configuration setup on the serial port provided device mode connection. e.g. /dev/ttyACMx where x can 0, 1, 2 etc.

Build an official image#

To not include the Jetpack source code, libraries and example, you can follow the guide in Build system image to create a new system image which is about 5.5 GB. This image will have full OS and pre-installed software, such as Office, Web Browser, etc.

Power on the board, either use Monitor with Keyboard and Mouse, or use Headless Mode by using DC jack (Jumper J48 connected) and connect micro-USB port to PC.

Build a customized image#

To create a lightweight system image, checkout jetson-custom-image and follow the scripting steps to make a customized image. Note to run extra steps in the folder custom_bsp_for_nano_dev_kit.

The final image is about 2.4 GB, which only includes Ubuntu base, lightweight Xfce desktop environment, Jetson driver and libraries, and some BSP modifications

  • Skip handling key pressing during U-Boot:
    • Disable bootdelay in U-Boot by setting CONFIG_BOOTDELAY=-2
    • Keep one boot entry in extlinux.conf
  • Add spidev to /etc/modules to load SPI driver at startup
  • Add user to tty, dialout, gpio groups
  • Enable Autologin
  • Run on-screen keyboard onboard at start-up

There is no setup process required anymore. Use a keyboard and mouse to start working on the board. For touchable monitor, use onboard program to access to on-screen keyboard.

System config#


  • Time Mode: UTC
  • Username: jetson
  • Password: cccc
  • APP Partition Size: 0 (max size)
  • Primary Network: dummy then skip network setting (will add USB WiFi later)
  • Hostname: rover
  • Nvpmodel: MAXN Maximum power scheme

After finishing system configuration, system console are available at:

  • Physical UART1 at /dev/ttyTHS1 run by a service
  • Physical UART2 at /dev/ttyS0 started by command line option
  • Virtual COM Port at /dev/ttyGS0 run when micro-USB is connected in device mode

UART ports

Jetson Nano has 3 physical UART ports:

  • UART0 at the M2 Slot for WiFi/BT card
  • UART1 at the J41 Header (40-pin connector) for System Console after boot up (run by a service), Pin 8 - TX, Pin 10 - RX
  • UART2 at the J50 header for debug (early access during boot from bootloader), Pin 4 - RX, Pin 5 - TX


WiFi is used for main communication with host PC, as the Ethernet port is used to connect to Lidar.

Plug TP-Link WN725N USB dongle, make sure it is recognized.

Bus 001 Device 004: ID 0bda:8179 Realtek Semiconductor Corp. RTL8188EUS 802.11n Wireless Network Adapter

Then Wireless interface also appears and is ready to check:

sudo ifconfig wlan0 up

List WiFi Network:

sudo iwlist wlan0 scan
sudo nmcli device wifi list - List info on your wifi signal

Connect to an AP:

sudo nmcli device wifi connect "SSID" password "PASSWORD"

SSH connection

With network enabled, through either Ethernet or WiFi, Jetson board will be also accessed via SSH. If using any X11-forwarding SSH client, such as MobaXterm, GUI app can be show through SSH.

In some case, it is recommended to turn power saving mode off

sudo iw dev wlan0 set power_save off

Update the system at the first time:

sudo apt update

Ethernet should be set to use a static IP which is in the subnet of where Lidar is also set its IP in.

Bring up the interface:

sudo ifconfig eth0 up

Edit the network interface:

auto eth0
iface eth0 inet static

It is needed to change network connection priority, as Linux prefers to use Ethernet when it is connected.

Install ifmetric tool:

sudo apt install -y ifmetric

To use this, first see the metrics using route command:

route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface         UG    100    0        0 eth0         UG    600    0        0 wlan0

Here, eth0 has lower metric, so it will be preferred over wlan0. If you want to prefer wlan0, then lower its metric:

sudo ifmetric wlan0 50

Now Linux will be using wlan0 for Internet. The change will be reflected immediately.


To skip entering password on sudo command, add current user to sudoers with the rule NOPASSWD:

sudo bash -c "echo '$USER ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/$USER"

Install nano for editing in terminal:

sudo apt update && \
sudo apt install -y nano

To make terminal display colorfully, in .bashrc, enable force_color_prompt=yes.

To disable animation on GUI:

gsettings set org.gnome.desktop.interface enable-animations false

Enable SPI#

Starting from L4T kernel 32.4.2+, the initial user created in system configuration will be added into all GPIO groups. Run groups to see the current user is added into gpio, i2c. Note that, Jetson L4T does not have spi group, SPI devices use gpio group.

By default, GPIO and I2C are enabled. SPI, PWM, I2S and some extra pins are disabled.

To change the setting of GPIO, the official tool will be used:

sudo /opt/nvidia/jetson-io/

This tool will configure the Device Tree and add it using FDT option in the /boot/extlinux/extlinux.conf file

In Jetpack 4.6, SPI device driver are not loaded into kernel, a manual method is to run sudo modprobe spidev, but spidev should be added into /etc/modules to load that driver at boot time.

sudo bash -c 'echo spidev > /etc/modules'

UART Config#


The stock Jetson Nano starts a console on the /dev/ttyTHS1 serial port at startup through a service /etc/systemd/ which launches getty. Note that normal udev rules will be overridden by the console while the service is running.

To disable the console:

sudo systemctl stop nvgetty && \
sudo systemctl disable nvgetty && \
sudo udevadm trigger

Jetson Nano 40-pin header


The debug serial interface is chosen at the startup of kernel. From L4T kernel 32.4.2+, the debug serial can be disabled through the command line options.

The command line can be read from /proc/cmdline:

cat /proc/cmdline
append: tegraid= ddr_die=4096M@2048M section=512M memtype=0 vpr_resize usb_port_owner_info=0 lane_owner_info=0 emc_max_dvfs=0 touch_id=0@63 video=tegrafb no_console_suspend=1 console=ttyS0,115200n8 debug_uartport=lsport,4 earlyprintk=uart8250-32bit,0x70006000 maxcpus=4 usbcore.old_scheme_first=1 lp0_vec=0x1000@0xff780000 core_edp_mv=1075 core_edp_ma=4000 gpt  earlycon=uart8250,mmio32,0x70006000  root=/dev/mmcblk0p1 rw rootwait rootfstype=ext4 console=ttyS0,115200n8 console=tty0 fbcon=map:0 net.ifnames=0 quiet root=/dev/mmcblk0p1 rw rootwait rootfstype=ext4 console=ttyS0,115200n8 console=tty0 fbcon=map:0 net.ifnames=0

Examine /boot/extlinux/extlinux.conf:

DEFAULT primary

MENU TITLE L4T boot options

LABEL primary
      MENU LABEL primary kernel
      LINUX /boot/Image
      INITRD /boot/initrd
      APPEND ${cbootargs} quiet root=/dev/mmcblk0p1 rw rootwait rootfstype=ext4 console=ttyS0,115200n8 console=tty0 fbcon=map:0 net.ifnames=0

Even it is impossible to edit ${cbootargs} without rebuilding kernel, it is still possible to set new command line by overriding the APPEND field.

Remove ttyS0

Take the expanded command line in /proc/cmdline and remove all console=ttyS0,115200n8 options, make a new value for APPEND.

When a cmdline’s console= is set, the auto-generated serial-getty@ttyXy.service should start agetty and provide system console on that device. If there is no console= option presented, system will attempt to open the first virtual serial port at /dev/tty0.

Reboot the system. Check the dmesg log to see 3 UART ports are available, and system console is on tty0:

dmesg | grep tty
[    0.001665] console [tty0] enabled
[    1.591294] 70006000.serial: ttyS0 at MMIO 0x70006000 (irq = 63, base_baud = 25500000) is a Tegra
[    1.592156] 70006040.serial: ttyTHS1 at MMIO 0x70006040 (irq = 64, base_baud = 0) is a TEGRA_UART
[    1.592584] 70006200.serial: ttyTHS2 at MMIO 0x70006200 (irq = 65, base_baud = 0) is a TEGRA_UART

UART2 still is accessible in boot up time, as it is attached to the bootloader. Send any data at very early time will stop the auto boot. To fix this point, need to use a modifided Uboot firmware.

After reboot, run below command to check if there is any ttyS0 or ttyTHSx is running. It should not show any of them.

ps -aux | grep tty


To get rid of using sudo permission, add current user into the dialout and spi group.

Run ls -al /dev/tty* to check the user group of ttyS0, ttyTHS1. Normally, it is needed to add current user into tty and dialout groups. For SPI, it is under gpio group:

sudo usermod -a -G tty,dialout,gpio $USER && \
sudo reboot
UART Testing

For testing, a serial terminal must be installed. Choose one of below.


sudo apt install -y putty

Follow GUI to run.


sudo apt install -y minicom
minicom -D /dev/ttyS0 -b 115200

Press Ctrl-A X to exit.


sudo apt install -y picocom
picocom /dev/ttyS0 -b 115200

Press Ctrl-A and Ctrl-X to exit.


sudo apt install -y screen
screen /dev/ttyS0 115200

Press Ctrl-A K to exit.


Refer to below pinout diagram:

Rover wiring

UART RX/TX problem

Here is some reports showing that Jetson boards have problems on UART RX/TX pin when connecting that port directly to an other board. It maily causes by the capitive loading on the pin, if the loop-back test does not have any problem.

If that problem happens, please add a 10K pull-down resister on RX/TX pin.

Refer to Unreliable serial communcation via the UART TX/RX GPIO Pins.

Serial driver#

Download source code and build:

git clone && \
cd SerialPort && \
make && \
sudo make install

An example to communicate with Serial port:

// Serial library
#include "serial/SerialPort.h"
#include <unistd.h>
#include <stdio.h>

#define SERIAL_PORT "/dev/ttyS0"

int main( /*int argc, char *argv[]*/) {
    SerialPort serial;

    char errorOpening = serial.openDevice(SERIAL_PORT, 115200);
    if (errorOpening!=1) return errorOpening;
    printf ("Successful connection to %s\n",SERIAL_PORT);

    // Display ASCII characters (from 32 to 128)
    for (int c=32;c<128;c++)

    // Read lines and print them out
    char line[1024];

    while(1) {
        int n = serial.readBytes(line, sizeof(line));
        if (n>=0) {
            std::cout << std::string(line, n) << std::endl;

    // Close the serial device

    return 0 ;

Compile and run:

g++ example.cpp -lserial -o example

nRF24L01p driver#

Download source code from GitHub and build:

git clone && \
cd RF24 && \
./configure --driver=SPIDEV && \
make && \
sudo make install
Work with the original source code

GitHub source code is at
The guide to install in Linux at

Download the file:


Make it executable:

chmod +x

Run it and choose the option:

  • RF24 Core
  • SPIDEV driver
Do you want to install GIT using APT (Used to download source code) [y/N]? n
Do you want to install the RF24 core library, [y/N]? y
Do you want to install the RF24Network library [y/N]? n
Do you want to install the RF24Mesh library [y/N]? n
Do you want to install the RF24Gateway library [y/N]? n

Cloning into './rf24libs/RF24'...

__* Install RF24 core using? *__
1.BCM2835 Driver(Performance) 2.SPIDEV(Compatibility, Default)
3.WiringPi(Its WiringPi!) 4.MRAA(Intel Devices) 5.LittleWire
[Installing Libs to /usr/local/lib]
[Installing Headers to /usr/local/include/RF24]

Fix CS pin

Jetson Nano spi-tegra114 driver has an issue in driving the CSN pin, therefore, the SPI command must explicitly request to toggle CSN pin.

Open the file ~/rf24libs/RF24/utility/SPIDEV/spi.cpp to find tr.cs_change = 0; and replace them by tr.cs_change = 1;.

Go back to the `~/rf24libs/RF24 and rebuild the library:

sudo make clean all install

Fixed in a Seeed Studio’s branch, but not in Nvidia’s

Refer: spi: tegra: handle cs_change in modes sw_based_cs & cs_gpios.

Source to test data receiving. Check the transferring site in Base.

#include <iostream>    // cin, cout, endl
#include <time.h>      // CLOCK_MONOTONIC_RAW, timespec, clock_gettime()
#include <RF24/RF24.h>

// create RF24 instance
RF24 radio(15 /* CE = sys_gpio_15 */,
            0 /* CSN = 0 means spidev0.0 */
            /* default speed is 10 Mbps */);

// max payload of RF24 is 32 bytes
uint8_t payload[32];
uint32_t total_nrx = 0;

// custom defined timer for evaluating transmission time in microseconds
struct timespec startTimer, endTimer;

int main(int argc, char** argv) {
    setbuf(stdout, NULL);

    // perform hardware check
    if (!radio.begin()) {
        cout << "radio hardware is not responding!!" << endl;
        return 0; // quit now

    radio.setChannel(100); // 2400 + 100 = 2500 MHz, out of WiFi band

    // address, defaut length is 5
    uint8_t rx_address[6] = "1Addr"; // read from
    radio.openReadingPipe(1, rx_address); // using pip 1

    // (smaller) function that prints raw register values
    // (larger) function that prints human readable data

    // Start
    std::cout << "Start RX" << std::endl;
    radio.startListening(); // put radio in RX mode

    uint8_t pipe;
    uint8_t nrx;

    clock_gettime(CLOCK_MONOTONIC_RAW, &startTimer); // start the timer
    cout << "Begin: " << startTimer.tv_sec << "." << startTimer.tv_nsec << endl;
    while(true) {
        if(radio.available(&pipe)) {
            nrx = radio.getPayloadSize();
  , nrx);
            total_nrx += nrx;

            if (total_nrx >= 1000000) {
            clock_gettime(CLOCK_MONOTONIC_RAW, &endTimer); // end the timer
            cout << "End: " << endTimer.tv_sec << "." << endTimer.tv_nsec << endl;

    int32_t diff_sec = endTimer.tv_sec - startTimer.tv_sec;
    int32_t diff_nsec = endTimer.tv_nsec - startTimer.tv_nsec;
    if (diff_nsec < 0) {
        diff_nsec = 1000000000 - diff_nsec;
        diff_sec -= 1;

    cout << "Received 1000000 bytes in " << diff_sec << "." << diff_nsec << " seconds" << endl;

Compile the source code:

g++ -Ofast -Wall -pthread  rf24_rx.cpp -lrf24 -o rf24_rx

Run it and see the log:

================ SPI Configuration ================
CSN Pin         = /dev/spidev0.0
CE Pin          = Custom GPIO15
SPI Speedz      = 10 Mhz
================ NRF Configuration ================
STATUS          = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1    = 0x65646f4e31 0x7264644131
RX_ADDR_P2-5    = 0xc3 0xc4 0xc5 0xc6
TX_ADDR         = 0x65646f4e31
RX_PW_P0-6      = 0x20 0x20 0x20 0x20 0x20 0x20
EN_AA           = 0x3f
EN_RXADDR       = 0x03
RF_CH           = 0x64
RF_SETUP        = 0x03
CONFIG          = 0x0e
DYNPD/FEATURE   = 0x00 0x00
Data Rate       = 1 MBPS
Model           = nRF24L01+
CRC Length      = 16 bits
PA Power        = PA_LOW
ARC             = 0
================ SPI Configuration ================
CSN Pin                 = /dev/spidev0.0
CE Pin                  = Custom GPIO15
SPI Frequency           = 10 Mhz
================ NRF Configuration ================
Channel                 = 100 (~ 2500 MHz)
RF Data Rate            = 1 MBPS
RF Power Amplifier      = PA_LOW
RF Low Noise Amplifier  = Enabled
CRC Length              = 16 bits
Address Length          = 5 bytes
Static Payload Length   = 32 bytes
Auto Retry Delay        = 1500 microseconds
Auto Retry Attempts     = 15 maximum
Packets lost on
    current channel     = 0
Retry attempts made for
    last transmission   = 0
Multicast               = Disabled
Custom ACK Payload      = Disabled
Dynamic Payloads        = Disabled
Auto Acknowledgment     = Enabled
Primary Mode            = TX
TX address              = 0x65646f4e31
pipe 0 ( open ) bound   = 0x65646f4e31
pipe 1 ( open ) bound   = 0x7264644131
pipe 2 (closed) bound   = 0xc3
pipe 3 (closed) bound   = 0xc4
pipe 4 (closed) bound   = 0xc5
pipe 5 (closed) bound   = 0xc6
Start RX
Begin: 793.156527770
End: 821.645141301
Received 1000000 bytes in 28.488613531 seconds

The example receives 1 MB in 28.5 seconds, which means 35 KBps. While the transfer rate set on the channel is 1Mbps (equals 125 KBps), the example only achieve 28% of bandwidth.

OLED driver#

Download source code and build:

git clone && \
cd OLED_SSD1306_I2C_Linux && \
make && \
sudo make install

Write a simple app to a progress bar with label and numeric value:

#include <string.h>
#include <unistd.h>
#include <SSD1306/ssd1306.h>

int main() {
    char counter = 0;
    char buffer[3];
    while(1) {
        sprintf(buffer, "%d", counter++);
        SSD1306_WriteString(0,0, "counter:", &Font_7x10, SSD1306_WHITE, SSD1306_OVERRIDE);
        SSD1306_WriteString(0,10, buffer, &Font_11x18, SSD1306_WHITE, SSD1306_OVERRIDE);
    return 0;

Compile and run:

gcc progress_bar.c -lssd1306 -o progress_bar && \

ROS Melodic#

Adding repository and source list

sudo apt-add-repository universe
sudo apt-add-repository multiverse
sudo apt-add-repository restricted
sudo apt update

Setup source list to get ROS packages:

sudo sh -c 'echo "deb $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'

Add keys:

sudo apt install -y curl && \
curl -s | sudo apt-key add -

Pull the package list:

sudo apt update

Install ROS Melodic desktop:

sudo apt install -y ros-melodic-desktop

It’s convenient if the ROS environment variables are automatically added to a bash session every time a new shell is launched:

echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc && \
source ~/.bashrc

A good way to check the installation is to ensure that environment variables like ROS_ROOT and ROS_PACKAGE_PATH are set:

printenv | grep ROS

Initialize the package rosdep to track package dependency:

sudo apt install -y python-rosdep && \
sudo rosdep init && \
rosdep update

Build packages are needed for code compilation.

sudo apt install -y python-rosinstall python-rosinstall-generator python-wstool build-essential

Create a catkin workspace and try to build it:

mkdir -p ~/catkin_ws/src && \
cd ~/catkin_ws/src && \
catkin_init_workspace && \
cd .. && \

The workspace should be built successfully.

Livox SDK#


Livox SDK needs to be built in the host machine, therefore, some tool-chain and build tools have to be installed.

sudo apt update && \
sudo apt install -y build-essential && \
sudo apt install -y curl && \
sudo apt install -y git && \
sudo apt install -y cmake

The pointcloud Library (PCL) is a large scale, open project[1] for pointcloud processing. The PCL framework contains numerous state-of-the art algorithms including filtering, feature estimation, surface reconstruction, registration, model fitting and segmentation.

sudo apt install -y libpcl-dev
sudo apt install -y ros-melodic-pcl-ros

Eigen is a C++ template library for linear algebra: matrices, vectors, numerical solvers, and related algorithms.

sudo apt install -y libeigen3-dev

OpenCV (Open Source Computer Vision Library) is an open-source computer vision library and has bindings for C++, Python, and Java. It is used for a very wide range of applications, including medical image analysis, stitching street view images, surveillance video, detecting and recognizing faces, tracking moving objects, extracting 3D models, and much more. OpenCV can take advantage of multi-core processing and features GPU acceleration for real-time operation.

sudo apt install -y python-opencv python3-opencv

Re-link libraries, on Jetson Nano:

sudo ln -s /usr/bin/vtk6 /usr/bin/vtk && \
sudo ln -s /usr/lib/python2.7/dist-packages/vtk/ /usr/lib/aarch64-linux-gnu/

Livox SDK#

The official guide is at

Livox SDK is the software development kit designed for all Livox products. It is developed based on C/C++ following Livox SDK Communication Protocol, and provides easy-to-use C style API. With Livox SDK, users can quickly connect to Livox products and receive pointcloud data.


git clone && \
cd Livox-SDK && \
cd build && \
cmake .. && \
make && \
sudo make install

The Livox SDK will be built and installed in /usr/local/lib:

Install the project...
-- Install configuration: ""
-- Installing: /usr/local/lib/liblivox_sdk_static.a
-- Installing: /usr/local/include/livox_def.h
-- Installing: /usr/local/include/livox_sdk.h

Livox ROS driver#

Get livox_ros_driver from GitHub

git clone ws_livox/src

Then build it:

cd ws_livox && \

If running catkin_make gives error of command not found, it’s probably that the ROS setup.bash is not executed and included in ~/.bashrc. See above section to source it.

This driver will create a new node named livox_lidar_publisher, which publishes 2 new types of messages:

Livox pointcloud message

# Livox publish pointcloud msg format.

Header header             # ROS standard message header
uint64 timebase           # The time of first point
uint32 point_num          # Total number of pointclouds
uint8  lidar_id           # Lidar device id number
uint8[3]  rsvd            # Reserved use
CustomPoint[] points      # Pointcloud data

Livox Point

# Livox custom pointcloud format.

uint32 offset_time      # offset time relative to the base time
float32 x               # X axis, unit:m
float32 y               # Y axis, unit:m
float32 z               # Z axis, unit:m
uint8 reflectivity      # reflectivity, 0~255
uint8 tag               # livox tag
uint8 line              # laser number in lidar


The configuration file is in ws_livox/src/livox_ros_driver/config.

LiDAR’s configuration parameter

Parameter Type Description Default
broadcast_code String LiDAR broadcast code N/A
enable_connect Boolean false
return_mode Int Return mode:
0 – First single return mode
1 – The strongest single return mode
2 – Dual return mode
coordinate Int Coordinate:
0 – Cartesian
1 – Spherical
imu_rate Int Push frequency of IMU sensor data:
0 – stop push
1 – 200 Hz
Currently only Horizon supports this, MID serials do not support it
extrinsic_parameter_source Int Whether to enable extrinsic parameter automatic compensation of LiDAR external reference
0 – Disabled
1 – Enabled

Timestamp synchronization

Parameter Type Description Default
enable_timesync Boolean false
device_name String Name of the serial device which outputs GPRMC/GNRMC messages every second /dev/ttyUSB0
comm_device_type Int Type of device sending timestamp information
0 – Serial port or USB virtual serial port device
other – not support
baudrate_index Int Baud rate of serial device:
0 – 2400
1 – 4800
2 – 9600
3 – 19200
4 – 38400
5 – 57600
6 – 115200
7 – 230400
8 – 460800
9 – 500000
10 – 576000
11 – 921600
2 (9600)
parity_index Int parity type
0 – 8 bits data without parity
1 – 7 bits data 1bit even parity
2 – 7 bits data 1bit odd parity
3 – 7 bits data 1bit 0, without parity
    "lidar_config": [
            "broadcast_code": "1PQDH5B00100041",
            "enable_connect": false,
            "return_mode": 0,
            "coordinate": 0,
            "imu_rate": 0,
            "extrinsic_parameter_source": 0,
            "enable_high_sensitivity": false
    "timesync_config": {
        "enable_timesync": false,
        "device_name": "/dev/ttyUSB0",
        "comm_device_type": 0,
        "baudrate_index": 2,
        "parity_index": 0


Prepare a GPS device to ensure that the GPS can output UTC time information in GPRMC/GNRMC format through the serial port or USB virtual serial port, and support PPS signal output.

  1. Connect the GPS serial port to the host running livox_ros_driver, set the corresponding device name in the config file
  2. Connect the GPS PPS signal line to LiDAR
  3. Be sure to set the output frequency of GPRMC/GNRMC time information of GPS to 1 Hz


Different launch files have different configuration parameter values and are used in different scenarios:

Launch file name Description
livox_lidar.launch Connect to Livox LiDAR device
Publish pointcloud2 format data
livox_lidar_msg.launch Connect to Livox LiDAR device
Publish livox customized pointcloud data
livox_lidar_rviz.launch Connect to Livox LiDAR device
Publish pointcloud2 format data
Autoload rviz

Launch parameters

Parameter Description Default
publish_freq Set the frequency of pointcloud publish 10.0
multi_topic 0 – All LiDAR devices use the same topic to publish pointcloud data
1 – Each LiDAR device has its own topic to publish pointcloud data
xfer_format 0 – Livox pointcloud2(PointXYZRTL) pointcloud format
1 – Livox customized pointcloud format
2 – Standard pointcloud2 (PointXYZI) pointcloud format in the PCL library

Test ROS#

An easy method to connect Livox Lidar with Jetson board is through a LAN router. Below section shows another method to connect 2 modules directly.

Set Livox’s Static IP

Use Livox Viewer to set a Static IP for the Lidar module. Livox only accepts IP in network.

Set Jetson’s Static IP

sudo nano /etc/network/interfaces
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static

Then run the livox_lidar_rviz example

cd ws_livox
source ./devel/setup.bash
roslaunch livox_ros_driver livox_lidar_rviz.launch

This will run Livox ROS and Rviz to visualize the received pointcloud.

Auto-mount USB#

Check out and install:

git clone && \
cd USB_Automount && \

Plugged-in USB will be mounted into /media/<Label> or /media/<sdXy>.

Automount scripts

Here are scripts to auto-mount storage devices:

sudo nano /usr/local/bin/

# This script is called from our systemd unit file to mount or unmount
# a USB drive.

    echo "Usage: $0 {add|remove} device_name (e.g. sdb1)"
    exit 1

if [[ $# -ne 2 ]]; then


# See if this drive is already mounted, and if so where
MOUNT_POINT=$(/bin/mount | /bin/grep ${DEVICE} | /usr/bin/awk '{ print $3 }')

    if [[ -n ${MOUNT_POINT} ]]; then
        echo "Warning: ${DEVICE} is already mounted at ${MOUNT_POINT}"
        exit 1

    # Get info for this drive: $ID_FS_LABEL, $ID_FS_UUID, and $ID_FS_TYPE
    eval $(/sbin/blkid -o udev ${DEVICE})

    # Figure out a mount point to use
    if [[ -z "${LABEL}" ]]; then
    elif /bin/grep -q " /media/${LABEL} " /etc/mtab; then
        # Already in use, make a unique one

    echo "Mount point: ${MOUNT_POINT}"

    /bin/mkdir -p ${MOUNT_POINT}

    # Global mount options

    # File system type specific mount options
    if [[ ${ID_FS_TYPE} == "vfat" ]]; then

    if ! /bin/mount -o ${OPTS} ${DEVICE} ${MOUNT_POINT}; then
        echo "Error mounting ${DEVICE} (status = $?)"
        /bin/rmdir ${MOUNT_POINT}
        exit 1

    echo "__** Mounted ${DEVICE} at ${MOUNT_POINT} **__"

    if [[ -z ${MOUNT_POINT} ]]; then
        echo "Warning: ${DEVICE} is not mounted"
        /bin/umount -l ${DEVICE}
        echo "____ Unmounted ${DEVICE}"

    # Delete all empty dirs in /media that aren't being used as mount
    # points. This is kind of overkill, but if the drive was unmounted
    # prior to removal we no longer know its mount point, and we don't
    # want to leave it orphaned...
    for f in /media/* ; do
        if [[ -n $(/usr/bin/find "$f" -maxdepth 0 -type d -empty) ]]; then
            if ! /bin/grep -q " $f " /etc/mtab; then
                echo "____ Removing mount point $f"
                /bin/rmdir "$f"

case "${ACTION}" in

Change permission:

sudo chmod 0755 /usr/local/bin/

Add service:

sudo nano /etc/systemd/system/usb-mount@.service
Description=Mount USB Drive on %i

ExecStart=/usr/local/bin/ add %i
ExecStop=/usr/local/bin/ remove %i

The file name uses @ to pass arguments.

Add rules for udev when USB event is detected:

sudo nano /etc/udev/rules.d/99-usb-mount.rules
KERNEL=="sd[a-z][0-9]", SUBSYSTEMS=="usb", ACTION=="add", RUN+="/bin/systemctl start usb-mount@%k.service"
KERNEL=="sd[a-z][0-9]", SUBSYSTEMS=="usb", ACTION=="remove", RUN+="/bin/systemctl stop usb-mount@%k.service"

Restart services and rules:

sudo udevadm control --reload-rules && \
sudo systemctl daemon-reload

If a USB is not mount automatically, check the system log:

cat /var/log/syslog

Auto-start application#

A simple app that shows IP Address on OLED screen.

#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <SSD1306/ssd1306.h>

using namespace std;
char i2c_dev[] = "/dev/i2c-1";
char noIP[] = "No IP";

void showIP(int line, char type, char* ip) {
    char buffer[32] = {0};
    sprintf(buffer, "%c:%s", type, ip);
    SSD1306_WriteString(0,line*10, buffer, &Font_7x10, SSD1306_WHITE, SSD1306_OVERRIDE);

void getIPAddresses() {
    struct ifaddrs *interfaces = NULL;
    struct ifaddrs *temp_addr = NULL;
    int success = 0;
    bool found = false;
    // retrieve the current interfaces - returns 0 on success
    success = getifaddrs(&interfaces);
    if (success == 0) {
        // Loop through linked list of interfaces
        temp_addr = interfaces;
        while(temp_addr != NULL) {
            if(temp_addr->ifa_addr->sa_family == AF_INET) {
                // Check if interface is en0 which is the wifi connection on the iPhone
                if(strcmp(temp_addr->ifa_name, "wlan0")==0){
                    showIP(0, 'W',
                        inet_ntoa(((struct sockaddr_in*)temp_addr->ifa_addr)->sin_addr)
                    found = true;
                if(strcmp(temp_addr->ifa_name, "eth0")==0){
                    showIP(1, 'E',
                        inet_ntoa(((struct sockaddr_in*)temp_addr->ifa_addr)->sin_addr)
                    found = true;

            temp_addr = temp_addr->ifa_next;
    // Free memory

    if (!found) {
       showIP(3, '?', noIP);

int main(int argc, char** argv) {
   while(1) {

Create a service:

Description=IP Show



Enable the service:

sudo systemctl enable ipshow.service