UART Communication C Programming - GitMasterNikanjam/RaspberryPi_WiKi GitHub Wiki
UART (Universal Asynchronous Receiver-Transmitter) communication is a popular serial communication protocol used for communicating between devices. In the context of Raspberry Pi 4B, you can use UART to communicate with other devices such as sensors, modules, or even another Raspberry Pi.
Here's a basic overview of how you can use UART communication in C++ programming for Raspberry Pi 4B:
Raspberry Pi 4B has several UART interfaces available. The primary UART (UART0) is typically used for the console, but you can also use the secondary UART (UART1) for your own purposes.
Make sure your Raspberry Pi 4B is powered off before connecting any external devices to the GPIO pins.
By default, the UART pins on Raspberry Pi are assigned to the Linux console. You may need to disable the console and enable UART for general-purpose use. You can do this through the Raspberry Pi configuration tool or by editing the /boot/config.txt file.
In your C++ code, you'll need to open the UART device file for reading and writing. The device file for UART1 on Raspberry Pi 4B is typically /dev/ttyS0.
You can use standard file I/O functions like open(), read(), write(), and close() to interact with the UART device.
Alternatively, you can use libraries like WiringPi or BCM2835, which provide higher-level functions for working with GPIO including UART.
The '#include <fcntl.h>' directive is a preprocessor directive in C and C++ that includes the definitions and declarations needed for performing file control operations. The name "fcntl" stands for "file control". This header file provides constants and function prototypes for various file-related operations, including opening files, setting file status flags, and file descriptor manipulation.
While it's possible to use <fcntl.h> alone for file I/O operations, including <unistd.h> alongside it provides a more comprehensive set of functions and constants that are commonly used in systems programming, making your code more portable and easier to maintain.
Some commonly used functions and constants defined in fcntl.h include:
open(): Used to open files and devices.
fcntl(): Performs various control operations on open files.
Constants like O_RDONLY, O_WRONLY, and O_RDWR, which specify the file access modes (read-only, write-only, and read-write, respectively).
Constants like O_CREAT, O_TRUNC, and O_APPEND, which specify additional options when opening files (create if not exists, truncate file to zero length, and append to the end of the file, respectively).
In the context of the example provided, fcntl.h is included to use the open() function and the file access mode constants (O_RDONLY, O_RDWR) when opening the UART device file for reading or reading/writing.
The open() function in C and C++ is used to open files and devices. When working with UART (Universal Asynchronous Receiver-Transmitter) communication, you can use open() to open the UART device file, which is typically located at /dev/ttyS0, /dev/ttyAMA0, or similar paths depending on the specific UART interface on your system.
Here's a brief overview of some commonly used modes and options with the open() function when dealing with UART communication:
By default, when you open a file descriptor using open(), it operates in blocking mode. This means that read and write operations will block until data is available to be read or space is available for writing.
Example: open("/dev/ttyS0", O_RDWR);
You can set the file descriptor to non-blocking mode using the O_NONBLOCK flag. In non-blocking mode, read and write operations will return immediately, even if no data is available for reading or the output buffer is full for writing.
Example: open("/dev/ttyS0", O_RDWR | O_NONBLOCK);
If you only need to read data from the UART device, you can open the file descriptor in read-only mode using the O_RDONLY flag.
Example: open("/dev/ttyS0", O_RDONLY);
If you only need to write data to the UART device, you can open the file descriptor in write-only mode using the O_WRONLY flag.
Example: open("/dev/ttyS0", O_WRONLY);
If you need both read and write access to the UART device, you can open the file descriptor in read-write mode using the O_RDWR flag.
Example: open("/dev/ttyS0", O_RDWR);
You can combine multiple flags using bitwise OR (|) to specify additional options. For example, you can use O_CREAT to create the file if it doesn't exist, O_EXCL to ensure that the file is newly created, or O_APPEND to append data to the end of the file.
Example: open("/dev/ttyS0", O_RDWR | O_CREAT | O_APPEND);
The O_NOCTTY option, when used with the open() function, is typically employed in UNIX-like operating systems to prevent the file descriptor from becoming the controlling terminal for the process.
Example:
// Open UART device file in read-only mode, prevent it from becoming the controlling terminal
int uart_fd = open("/dev/ttyS0", O_RDONLY | O_NOCTTY);
When working with UART communication, it's common to open the UART device file in read-write mode (O_RDWR) if bidirectional communication is needed. You may also choose between blocking and non-blocking modes based on your application's requirements for handling read and write operations.
Open Uart in nonblocking, noctty, read and write options.
// Open UART device file in read-write mode, prevent it from becoming the controlling terminal, and set non-blocking mode
int uart_fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK);
if (uart_fd == -1) {
std::cerr << "Error opening UART device file: " << strerror(errno) << std::endl;
return 1;
}The <termios.h> header file is a standard C header file that provides facilities for controlling terminal I/O interfaces. It is commonly used on UNIX-like operating systems (such as Linux) to configure and manipulate terminal settings, including those of UART devices.
Here's a breakdown of what you can find in the <termios.h> header:
1- Terminal Settings Structure:
The struct termios structure, defined in <termios.h>, holds the attributes of a terminal device. It contains fields representing various parameters such as baud rate, parity, stop bits, and data bits, among others.
2- Functions for Terminal I/O Control:
<termios.h> provides functions like tcgetattr() and tcsetattr() for getting and setting terminal attributes, respectively. These functions allow you to read the current terminal settings and modify them as needed.
3- Baud Rate Constants:
It defines symbolic constants like B9600, B115200, etc., representing commonly used baud rates for serial communication. These constants are used when setting the baud rate of a UART device.
4- Control Flags:
The header file defines various control flags used to configure terminal behavior, such as enabling/disabling hardware flow control, enabling/disabling echoing of input characters, and specifying whether input is processed in canonical mode (line-oriented) or non-canonical mode (raw).
5- Error Constants:
<termios.h> also defines error constants like EAGAIN and EWOULDBLOCK, which are returned by terminal I/O functions in case of non-blocking I/O operations when no data is available to read or no space is available to write.
6- Functions for Line Discipline:
Line discipline refers to the handling of input and output characters by the terminal device. <termios.h> provides functions like cfmakeraw() and cfsetspeed() for setting terminal attributes related to line discipline.
7- Miscellaneous Constants and Functions:
Additionally, <termios.h> may define other constants and functions related to terminal I/O control, such as control characters and functions for manipulating terminal settings.
In summary, <termios.h> is a fundamental header file for controlling terminal I/O interfaces on UNIX-like systems. It provides the necessary structures, constants, and functions to configure and manipulate terminal settings, including those of UART devices, facilitating effective communication between devices.
Configuring UART settings involves setting various parameters that govern the communication between your device (such as a Raspberry Pi) and the UART peripheral (such as an external sensor or another microcontroller). These settings ensure that both devices communicate effectively by agreeing on parameters like baud rate, data bits, stop bits, and parity.
Here are the key parameters that you typically configure when setting up UART communication:
1- Baud Rate:
The baud rate specifies the speed of communication in bits per second (bps). It determines how fast data is transmitted between devices. Both devices must use the same baud rate to communicate correctly.
2- Data Bits:
Data bits determine the size of each data character transmitted over the UART interface. Common values include 5, 6, 7, or 8 bits per character.
3- Stop Bits:
Stop bits indicate the end of a data character and provide a brief pause before the next character is transmitted. Common values include one or two stop bits.
4- Parity:
Parity is an error-checking mechanism used to detect errors in transmitted data. It can be set to None, Even, Odd, Mark, or Space. When parity is enabled, an additional bit (the parity bit) is transmitted along with each data character to ensure that the total number of bits (including the parity bit) is even or odd, depending on the selected parity mode.
5- Flow Control:
Flow control mechanisms, such as hardware (RTS/CTS) or software (XON/XOFF), are used to regulate the flow of data between devices to prevent data loss or buffer overflow. Flow control can be enabled or disabled based on the requirements of your application.
When configuring UART settings in software, you typically use the termios interface on UNIX-like operating systems. Here's how you can configure UART settings using the termios interface in C/C++:
#include <termios.h>
int uart_fd; // File descriptor for UART device
// Get current UART settings
struct termios uart_config;
tcgetattr(uart_fd, &uart_config);
// Modify UART settings
cfsetispeed(&uart_config, B115200); // Set baud rate to 115200
cfsetospeed(&uart_config, B115200);
uart_config.c_cflag &= ~PARENB; // No parity
uart_config.c_cflag &= ~CSTOPB; // 1 stop bit
uart_config.c_cflag &= ~CSIZE; // 8 data bits
uart_config.c_cflag |= CS8;
// Apply modified UART settings
tcsetattr(uart_fd, TCSANOW, &uart_config);
In this example:
We use tcgetattr() to get the current UART settings and store them in the uart_config structure.
We modify the uart_config structure to set the desired parameters, such as baud rate, parity, stop bits, and data bits.
We use tcsetattr() to apply the modified UART settings to the UART device.
By configuring UART settings correctly, you ensure that both devices can communicate effectively over the UART interface, exchanging data reliably and accurately.
cfsetispeed and cfsetospeed are two functions used to set the input and output baud rates, respectively, for a terminal device using the termios interface in C programming. Here's the difference between them:
The line uart_config.c_cflag |= CS8; is a bitwise operation that sets the number of data bits to eight (CS8 stands for 8 data bits) in the termios structure uart_config.
The line uart_config.c_cflag &= ~CSIZE; is a bitwise operation that clears the current setting for character size (CSIZE) in the termios structure uart_config.
Full example for read and write uart in non blocking mode:
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <cstring>
#include <cerrno>
int main() {
// Open UART device file in read-write mode, prevent it from becoming the controlling terminal, and set non-blocking mode
int uart_fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK);
if (uart_fd == -1) {
std::cerr << "Error opening UART device file: " << strerror(errno) << std::endl;
return 1;
}
// Configure UART settings
struct termios uart_config;
tcgetattr(uart_fd, &uart_config);
cfsetispeed(&uart_config, B115200); // Set baud rate to 115200
cfsetospeed(&uart_config, B115200);
uart_config.c_cflag &= ~PARENB; // No parity
uart_config.c_cflag &= ~CSTOPB; // 1 stop bit
uart_config.c_cflag &= ~CSIZE; // 8 data bits
uart_config.c_cflag |= CS8;
tcsetattr(uart_fd, TCSANOW, &uart_config);
// Example read operation (non-blocking)
char read_buffer[255];
int bytes_read = read(uart_fd, read_buffer, sizeof(read_buffer));
if (bytes_read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
// No data available for reading
std::cout << "No data available for reading" << std::endl;
} else if (bytes_read > 0) {
// Data read successfully
read_buffer[bytes_read] = '\0'; // Null-terminate the string
std::cout << "Received: " << read_buffer << std::endl;
}
// Example write operation (non-blocking)
const char* write_buffer = "Hello UART!";
int bytes_written = write(uart_fd, write_buffer, strlen(write_buffer));
if (bytes_written == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
// Transmit buffer is full, cannot write
std::cout << "Transmit buffer is full, cannot write" << std::endl;
} else if (bytes_written > 0) {
// Data written successfully
std::cout << "Data written successfully" << std::endl;
}
// Close UART
close(uart_fd);
return 0;
}Reading all data available from uart port in non blocking mode:
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <cstring>
#include <cerrno>
#include <sys/ioctl.h> // Required for ioctl() function
int main() {
// Open UART device file in read-write mode, prevent it from becoming the controlling terminal, and set non-blocking mode
int uart_fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK);
if (uart_fd == -1) {
std::cerr << "Error opening UART device file: " << strerror(errno) << std::endl;
return 1;
}
// Configure UART settings
struct termios uart_config;
tcgetattr(uart_fd, &uart_config);
cfsetispeed(&uart_config, B115200); // Set baud rate to 115200
cfsetospeed(&uart_config, B115200);
uart_config.c_cflag &= ~PARENB; // No parity
uart_config.c_cflag &= ~CSTOPB; // 1 stop bit
uart_config.c_cflag &= ~CSIZE; // 8 data bits
uart_config.c_cflag |= CS8;
tcsetattr(uart_fd, TCSANOW, &uart_config);
while(true)
{
uart_fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK);
// Example read operation (non-blocking)
// Check number of characters available in UART receive buffer
int available_chars;
ioctl(uart_fd, FIONREAD, &available_chars);
if (available_chars > 0) {
char read_buffer[available_chars];
int bytes_read = read(uart_fd, &read_buffer, sizeof(read_buffer));
if (bytes_read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
// No data available for reading
std::cout << "No data received on uart" << std::endl;
} else if (bytes_read > 0) {
// Data read successfully
read_buffer[bytes_read] = '\0'; // Null-terminate the string
std::cout << read_buffer;
}
// Close UART
close(uart_fd);
}
}
return 0;
}Read GPS NMEA data and convert it to states:
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <cstring>
#include <cerrno>
#include <sys/ioctl.h> // Required for ioctl() function -> for get avilable char in uart recieved.
#include "TinyGPS++.h" // for gps data parsing.
#include <iomanip> // For cout precision setting.
using namespace std;
char *gpsStream; // Pointer for GPS data stream.
// The TinyGPSPlus object
TinyGPSPlus gps;
int main() {
// Open UART device file in read-write mode, prevent it from becoming the controlling terminal, and set non-blocking mode
int uart_fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK);
if (uart_fd == -1) {
std::cerr << "Error opening UART device file: " << strerror(errno) << std::endl;
return 1;
}
// Configure UART settings
struct termios uart_config;
tcgetattr(uart_fd, &uart_config);
cfsetispeed(&uart_config, B115200); // Set baud rate to 115200
cfsetospeed(&uart_config, B115200);
uart_config.c_cflag &= ~PARENB; // No parity
uart_config.c_cflag &= ~CSTOPB; // 1 stop bit
uart_config.c_cflag &= ~CSIZE; // 8 data bits
uart_config.c_cflag |= CS8;
tcsetattr(uart_fd, TCSANOW, &uart_config);
while(true)
{
uart_fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK);
// Example read operation (non-blocking)
// Check number of characters available in UART receive buffer
int available_chars;
ioctl(uart_fd, FIONREAD, &available_chars);
if (available_chars > 0) {
char read_buffer[available_chars];
int bytes_read = read(uart_fd, &read_buffer, sizeof(read_buffer));
if (bytes_read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
// No data available for reading
std::cout << "No data received on uart" << std::endl;
} else if (bytes_read > 0)
{
// Data read successfully
read_buffer[bytes_read] = '\0'; // Null-terminate the string
//std::cout << read_buffer;
gpsStream = read_buffer;
while (*gpsStream)
if(gps.encode(*gpsStream++))
{
if(gps.location.isUpdated())
{
cout<< std::fixed << setprecision(10)<< gps.location.lat()<< ", ";
cout<< std::fixed << setprecision(10)<< gps.location.lng()<< ", ";
cout<< std::fixed << setprecision(10)<< gps.altitude.meters()<< endl;
}
}
}
/* GPS method for get data:
gps.satellites.value()
gps.satellites.isValid()
gps.hdop.hdop()
gps.hdop.isValid()
gps.location.lat()
gps.location.isValid()
gps.location.lng()
gps.location.isValid()
gps.altitude.meters()
gps.altitude.isValid()
gps.speed.kmph()
gps.speed.isValid()
gps.course.deg()
gps.course.isValid()
gps.date.month()
gps.date.day()
gps.date.year()
gps.time.hour()
gps.time.minute()
gps.time.second()
*/
}
// Close UART
close(uart_fd);
}
return 0;
}