Attention: the Quickstart Guide for BSP 2.8, based on the Ångström distribution, is not being updated anymore. Depending on your SoM, you have different options:
Vybrid and Tegra: the information is provided as-is and still accurate, since newer Toradex BSPs are not ported to those SoMs. Just keep in mind that the Guides are not being maintained anymore, even if we find bugs or outdated instructions.
Apalis TK1 (all variants), Colibri iMX6ULL (all variants), Colibri iMX7S 256MB and Colibri iMX7D 512MB: these computer on modules are still regularly maintained in our BSPs and, to get started, you must check the software page Toradex BSP Layers and Reference Images for Yocto Project. Since Torizon is not supported, at the moment a Quickstart Guide is not available.
All other i.MX-based SoMs: you have two options to get started with embedded Linux: the first is to follow the Quickstart Guide for Torizon, which provides the greatest out-of-the-box experience, or if you choose to use Yocto, check the software page Toradex BSP Layers and Reference Images for Yocto Project.
In this lesson, you will:
- Learn the basics of using UART on Linux
- Read and write from UART using the command line
- Read and write from UART using an application written in C
The information provided in this guide is based on Toradex's knowledge base article UART (Linux).
Attention: Never connect a RS-232 output pin to a TTL input. This might lead to permanent damage.
Be introduced to the UART protocol:
UART stands for Universal Asynchronous Receiver-Transmitter. These kinds of interfaces are used to achieve asynchronous serial communication between devices. The transmission speed and the data format are configurable.
There are 4 main pins that may be used on a UART interface:
- RX, the pin that will receive data from another device
- TX, the pin that will transmit data to another device
- RTS, request-to-send
- CTS, clear-to-send
Note: The RTS and CTS signals are used to achieve hardware flow control. We will not delve into this topic in this tutorial. Therefore, we will only use the RX and TX (and ground) pins.
The following block diagram shows some ways in which UART can be accessed:
Get used to the naming conventions:
The Ixora Carrier Board offers 4 UART interfaces common to all SoMs from the Apalis family, presented in the Toradex Name column from the table below. The other columns provide information about the respective protocols, converters or electric standards in which the signals are available on the carrier board:
Toradex Name |
RS232 |
TTL 3.3V |
UART1 |
X22 |
- |
UART2 |
X21 |
- |
UART3 |
X21 |
X27 |
UART4 |
- |
X27 |
The following table relates the Toradex Name to the corresponding Linux device file:
Toradex Name |
Device |
UART1 |
/dev/ttymxc0 |
UART2 |
/dev/ttymxc1 |
UART3 |
/dev/ttymxc3 |
UART4 |
/dev/ttymxc4 |
In this lesson, the UART2 interface is used in the examples and the Toradex naming convention (Toradex Name) is used unless otherwise stated.
Note: Please refer to the FAQ for more information on how to use UARTs that are disabled in the default BSP configuration.
The correspondence between UART interface signals and SoM/Carrier Board pins is provided in the following table:
Note: The notation CONNECTOR.PIN will be employed in this lesson, e.g. X12.5 means pin 5 on the X12 connector.
UART signals and pins
UART1 |
MXM3 |
Ixora board (RS-232) |
Ixora board (TTL 3.3V) |
UART1_RXD |
118 |
X22.3 |
n/a |
UART1_TXD |
112 |
X22.5 |
n/a |
UART1_RTS |
114 |
X22.4 |
n/a |
UART1_CTS |
116 |
X22.6 |
n/a |
UART2 |
MXM3 |
Ixora board (RS-232) |
Ixora board (TTL 3.3V) |
UART2_RXD |
132 |
X21.3 |
n/a |
UART2_TXD |
126 |
X21.5 |
n/a |
UART2_RTS |
128 |
X21.4 |
n/a |
UART2_CTS |
130 |
X21.6 |
n/a |
UART3 |
MXM3 |
Ixora board (RS-232) |
Ixora board (TTL 3.3V) |
UART3_RXD |
136 |
X21.1 |
X27.27 |
UART3_TXD |
134 |
X21.7 |
X27.28 |
UART4 |
MXM3 |
Ixora board (RS-232) |
Ixora board (TTL 3.3V) |
UART4_RXD |
140 |
n/a |
X27.30 |
UART4_TXD |
138 |
n/a |
X27.31 |
A loopback test will be run from command-line. This will send and receive data from the same serial port to verify if it's working.
Connect your board's UART2 TX pin (X21.5) to its RX pin (X21.3).
-
Ixora UART wiring
From a serial terminal connected to the module, configure the UART2 baud rate using the stty command. We will use a baud rate of 9600 baud:
stty -F /dev/ttymxc1 9600 -echo
Use the cat command to listen for incoming data on the serial port:
cat < /dev/ttymxc1 &
Write to the serial port. The characters sent will be printed back to you in the next line:
echo "Testing UART" > /dev/ttymxc1
Testing UART
A UART loopback test can also be written in C.
Termios, the Unix API for terminal I/O will be employed.
An example will be introduced step-by-step. First of all, include some libraries and initialize variables:
Warning: The source codes provided in this guide are distributed under the 3-clause BSD license terms. See below:
License
LICENSE
Copyright (c) 2017, Toradex All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Toradex nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Toradex BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Loopback example - Initializing
uart-example1.c
#include <stdio.h> /* I/O Definitions */
#include <unistd.h> /* Standard Symbolic Constants */
#include <fcntl.h> /* File Control Definitions */
#include <termios.h> /* POSIX Terminal Control Definitions */
#include <string.h> /* String Manipulation Definitions */
#include <errno.h> /* Error Code Definitions */
int main(int argc, char *argv[])
{
char buf_rx[100];
char buf_tx[100];
const char *device = "/dev/ttymxc1";
struct termios tty;
int fd;
int flags = O_RDWR | O_NOCTTY | O_NDELAY; /* O_RDWR Read/write access to the serial port */
/* O_NOCTTY No terminal will control the process */
/* O_NDELAY Use non-blocking I/O */
To use the serial port, open the serial device using the standard system call open(), passing the device path and the flags as arguments.
Using some functions of the termios library, get the serial port attributes and configure the communication parameters and other properties. This example uses the fairly common 9600/8N1 configuration, standing for a baud rate of 9600 baud, 8 data bits, no parity bit and 1 stop bit.
Loopback example - Configuring device
uart-example1.c
tcgetattr(fd, &tty); /* Get the current attributes of the serial port */
/* Setting baud rate (input and output) */
cfsetispeed(&tty, B9600);
cfsetospeed(&tty, B9600);
tty.c_cflag &= ~PARENB; /* Disables the Parity Enable bit(PARENB) */
tty.c_cflag &= ~CSTOPB; /* Clear CSTOPB, configuring 1 stop bit */
tty.c_cflag &= ~CSIZE; /* Using mask to clear data size setting */
tty.c_cflag |= CS8; /* Set 8 data bits */
tty.c_cflag &= ~CRTSCTS; /* Disable Hardware Flow Control */
tcsetattr(fd, TCSANOW, &tty); /* Write the configuration to the termios structure*/
Now you can use the standard system calls write() and read() to write to and read from the serial port.
First, flush the receiving buffer to discard old data. Then, store a string into the buffer variable and write it to the serial port.
After writing to the serial port, you can read from it. Since it's a loopback, you will read what you just wrote and save it to the buffer variable. Also, don't forget to use the standard system call close() to close the device when you're done. To avoid timing problems, use a delay between writing and reading.
Test the complete code. Create and configure an Eclipse project as described in Module 2.
Build and run the following code:
Loopback test code
uart-example1.c
#include <stdio.h> /* I/O Definitions */
#include <unistd.h> /* Standard Symbolic Constants */
#include <fcntl.h> /* File Control Definitions */
#include <termios.h> /* POSIX Terminal Control Definitions */
#include <string.h> /* String Manipulation Definitions */
#include <errno.h> /* Error Code Definitions */
int main(int argc, char *argv[])
{
char buf_rx[100];
char buf_tx[100];
const char *device = "/dev/ttymxc1";
struct termios tty;
int fd;
int flags = O_RDWR | O_NOCTTY | O_NDELAY; /* O_RDWR Read/write access to the serial port */
/* O_NOCTTY No terminal will control the process */
/* O_NDELAY Use non-blocking I/O */
fd = open(device, flags);
tcgetattr(fd, &tty); /* Get the current attributes of the serial port */
/* Setting baud rate (input and output) */
cfsetispeed(&tty, B9600);
cfsetospeed(&tty, B9600);
tty.c_cflag &= ~PARENB; /* Disables the Parity Enable bit(PARENB) */
tty.c_cflag &= ~CSTOPB; /* Clear CSTOPB, configuring 1 stop bit */
tty.c_cflag &= ~CSIZE; /* Using mask to clear data size setting */
tty.c_cflag |= CS8; /* Set 8 data bits */
tty.c_cflag &= ~CRTSCTS; /* Disable Hardware Flow Control */
tcsetattr(fd, TCSANOW, &tty); /* Write the configuration to the termios structure*/
tcflush(fd, TCIFLUSH);
strncpy(buf_tx, "Testing UART\n", sizeof(buf_tx));
write(fd,(char *)buf_tx,sizeof(buf_tx));
usleep(186000);
read(fd, buf_rx, sizeof(buf_rx));
printf("buf_rx = %s", buf_rx);
close(fd);
return 0;
}
We can improve upon the code by adding error checks:
Improved loopback test
uart-example1-improved.c
#include <stdio.h> /* I/O Definitions */
#include <unistd.h> /* Standard Symbolic Constants */
#include <fcntl.h> /* File Control Definitions */
#include <termios.h> /* POSIX Terminal Control Definitions */
#include <string.h> /* String Manipulation Definitions */
#include <errno.h> /* Error Code Definitions */
int main(int argc, char *argv[])
{
char buf_rx[100];
char buf_tx[100];
const char *device = "/dev/ttymxc1";
struct termios tty;
int fd;
int flags = O_RDWR | O_NOCTTY | O_NDELAY; /* O_RDWR Read/write access to the serial port */
/* O_NOCTTY No terminal will control the process */
/* O_NDELAY Use non-blocking I/O */
/*------------------------------- Opening the Serial Port -------------------------------*/
fd = open(device, flags);
if(fd == -1){
printf("Failed to open port!\n ");
return -1;
}
/*---------- Serial port settings using the termios structure --------- */
/* Settings (9600/8N1):
Baud rate: 9600 baud
Data bits: 8
Parity bit: No
Stop bit: 1
Hardware Flow Control: No
*/
tcgetattr(fd, &tty); /* Get the current attributes of the Serial port */
cfsetispeed(&tty, B9600); /* Set read speed as 9600 baud */
cfsetospeed(&tty, B9600); /* Set write speed as 9600 baud */
tty.c_cflag &= ~PARENB; /* Disables the Parity Enable bit(PARENB) */
tty.c_cflag &= ~CSTOPB; /* Clear CSTOPB, configuring 1 stop bit */
tty.c_cflag &= ~CSIZE; /* Using mask to clear data size setting */
tty.c_cflag |= CS8; /* Set 8 data bits */
tty.c_cflag &= ~CRTSCTS; /* Disable Hardware Flow Control */
if((tcsetattr(fd, TCSANOW, &tty)) != 0){ /* Write the configuration to the termios structure*/
printf("Error! Can't set attributes.\n ");
return -1;
}else{
printf("All set! \n");
}
tcflush(fd, TCIFLUSH);
strncpy(buf_tx, "Testing UART\n", sizeof(buf_tx));
int result = write(fd,(char *)buf_tx,sizeof(buf_tx));
if(result == -1){
printf("Error: %s\n",strerror(errno));
return -1;
} else {
printf("%d bytes sent\n", result);
}
usleep(186000);
if (read(fd, buf_rx, sizeof(buf_rx)) == -1) {
printf("Error: %s\n",strerror(errno));
return -1;
}
printf("buf_rx = %s\n", buf_rx);
close(fd);
return 0;
}
The following example demonstrates communication between two UART ports on your board. It shows how to use more than one serial port at a time.
For this example, we will call UART2 "first_uart" and UART3 "second_uart".
Connect UART2_RXD (X21.3) to UART3_TXD (X21.7) and UART2_TXD (X21.5) to UART3_RXD (X21.1).
Build and run the following code:
Example communicating between UARTs on the same board
multi-uart-example.c
#include <stdio.h> /* I/O Definitions */
#include <unistd.h> /* Standard Symbolic Constants */
#include <fcntl.h> /* File Control Definitions */
#include <termios.h> /* POSIX Terminal Control Definitions */
#include <string.h> /* String Manipulation Definitions */
#include <errno.h> /* Error Code Definitions */
int main(int argc, char *argv[]) {
char buf_rx[100];
char buf_tx[100];
const char *first_uart = "/dev/ttymxc1";
const char *second_uart = "/dev/ttymxc3";
struct termios tty;
int fd_first_uart;
int fd_second_uart;
int flags = O_RDWR | O_NOCTTY | O_NDELAY; /* O_RDWR Read/write access to the serial port */
/* O_NOCTTY No terminal will control the process */
/* O_NDELAY Use non-blocking I/O */
/*------------------------------- Opening the Serial Ports -------------------------------*/
fd_first_uart = open(first_uart, flags);
fd_second_uart = open(second_uart, flags);
if (fd_first_uart == -1 || fd_second_uart == -1) {
printf("Failed to open port!\n");
return -1;
}
/*---------- Serial port settings using the termios structure --------- */
/* Settings (9600/8N1):
Baud rate: 9600 baud
Data bits: 8
Parity bit: No
Stop bit: 1
Hardware Flow Control: No
*/
tcgetattr(fd_first_uart, &tty); /* Get the current attributes of the first serial port */
cfsetispeed(&tty, B9600); /* Set read speed as 9600 baud */
cfsetospeed(&tty, B9600); /* Set write speed as 9600 baud */
tty.c_cflag &= ~PARENB; /* Disables the Parity Enable bit(PARENB) */
tty.c_cflag &= ~CSTOPB; /* Clear CSTOPB, configuring 1 stop bit */
tty.c_cflag &= ~CSIZE; /* Using mask to clear data size setting */
tty.c_cflag |= CS8; /* Set 8 data bits */
tty.c_cflag &= ~CRTSCTS; /* Disable Hardware Flow Control */
if ((tcsetattr(fd_first_uart, TCSANOW, &tty)) != 0) { /* Write the configuration to the termios structure*/
printf("Error! Can't set attributes.\n");
return -1;
} else {
printf("First UART all set!\n");
}
tcflush(fd_first_uart, TCIFLUSH);
tcgetattr(fd_second_uart, &tty); /* Get the current attributes of the second serial port */
cfsetispeed(&tty, B9600); /* Set read speed as 9600 baud */
cfsetospeed(&tty, B9600); /* Set write speed as 9600 baud */
tty.c_cflag &= ~PARENB; /* Disables the Parity Enable bit(PARENB) */
tty.c_cflag &= ~CSTOPB; /* Clear CSTOPB, configuring 1 stop bit */
tty.c_cflag &= ~CSIZE; /* Using mask to clear data size setting */
tty.c_cflag |= CS8; /* Set 8 data bits */
tty.c_cflag &= ~CRTSCTS; /* Disable Hardware Flow Control */
if ((tcsetattr(fd_second_uart, TCSANOW, &tty)) != 0) { /* Write the configuration to the termios structure*/
printf("Error! Can't set attributes.\n");
return -1;
} else {
printf("Second UART all set!\n");
}
tcflush(fd_second_uart, TCIFLUSH);
strncpy(buf_tx, "Second UART writing to first UART\n", sizeof(buf_tx));
int result = write(fd_second_uart, (char *) buf_tx, sizeof(buf_tx));
if (result == -1) {
printf("Error: %s\n", strerror(errno));
return -1;
} else {
printf("%d bytes sent to first UART\n", result);
}
usleep(1860000);
if (read(fd_first_uart, buf_rx, sizeof(buf_rx)) == -1) {
printf("Error: %s\n", strerror(errno));
return -1;
} else {
printf("First UART received: %s\n", buf_rx); /* Print content read from serial */
strncpy(buf_tx, "First UART sending to second UART\n", sizeof(buf_tx));
result = write(fd_first_uart, (char *) buf_tx, sizeof(buf_tx));
if (result == -1) {
printf("Error: %s\n", strerror(errno));
return -1;
} else {
printf("%d bytes sent to second UART\n", result);
}
}
usleep(1860000);
if (read(fd_second_uart, buf_rx, sizeof(buf_rx)) == -1) {
printf("Error: %s\n", strerror(errno));
return -1;
} else {
printf("Second UART received: %s\n", buf_rx); /* Print content read from serial */
}
close(fd_first_uart);
close(fd_second_uart);
return 0;
}
This lesson covers the basics of UART usage on Linux. There are other important topics that were not discussed. This FAQ section provides further information and sources.
How can use another UART interface instead of the one used in this lesson
You can use the other available UART interfaces simply by changing the device path and the connected pins. The paths are listed in UART (Linux) for every module. To find the correct pins, refer to your module's datasheet.
Extra configuration effort may be required when using a UART that is not standard to the respective Computer on Module family. Usually this means that you have to modify the device-tree.
Can the debug UART be used for general purpose
The debug UART is used as a serial debug console by default. Toradex does not recommend using it for any other purpose, since it will prevent you from analysing error messages or controlling the system via serial. However, if your design allows you to disable the serial console, you can do it by following this article. If you do this, you can use the debug UART by following the same instructions provided in this lesson with minor changes.
How can I disable DMA for RX
Sometimes the DMA peripheral may affect the performance of the UART interface. Try disabling the DMA for RX by editing the device tree according to:
&uart2 {
status = "okay";
dma-names = "","tx";
};
substituting uart2 for the desired UART port.