Table of Contents

    Peripherals

    In Linux, peripherals are devices that are external to the computer's central processing unit (CPU). They can be classified into various categories based on their function. Each type of peripheral in Linux is typically accessed through a device file located in the /dev directory. These device files are interfaces to the drivers that allow user-space programs to interact with the hardware. Here's an overview of common types of peripherals in Linux, along with examples of their device files and how they are generally numbered:

    Block Devices

    Block devices are storage devices that can contain filesystems, such as hard drives, SSDs, and USB flash drives. They are accessed in blocks of data, hence the name.

    • Device File Examples: /dev/sda, /dev/sdb1
    • Numbering: The first detected hard drive is /dev/sda, the second is /dev/sdb, and so on. Partitions on these drives are numbered, e.g., /dev/sda1 for the first partition on the first drive.

    Character Devices

    Character devices are accessed character by character. This category includes a wide variety of devices, including serial ports, keyboards, and more.

    • Device File Examples: /dev/tty1 (virtual console), /dev/ttyS0 (serial port), /dev/input/event0 (input device event interface)
    • Numbering: Numbering can vary based on the device type. Serial ports are numbered sequentially (ttyS0, ttyS1, etc.). Input devices like keyboards and mice may have event numbers (event0, event1, etc.).

    Network Interfaces

    Network interfaces aren't typically represented as files in /dev but are crucial peripherals. They are represented by interface names and configured using networking tools.

    • Interface Examples: eth0 (Ethernet), wlan0 (Wi-Fi)
    • Numbering: Numbering is sequential based on detection order or system configuration.

    Audio and Video Devices

    These devices include sound cards, webcams, and TV tuners. They are accessed through specific device files or API interfaces.

    • Device File Examples: /dev/video0 (video device), /dev/snd/controlC0 (sound device control interface)
    • Numbering: Video and audio devices are numbered sequentially starting from 0.

    Printers

    Printers in Linux can be accessed through various subsystems, including CUPS (Common UNIX Printing System). Direct interaction with device files for printers is less common with modern printing systems.

    • Device File Examples: /dev/usb/lp0 (USB printer)
    • Numbering: USB printers are numbered sequentially starting from 0.

    USB Devices

    USB devices, including storage devices, input devices, and others, can also be accessed directly for low-level interaction.

    • Device File Examples: /dev/bus/usb/001/001 (USB device file, bus 001, device 001)
    • Numbering: USB devices are numbered based on the bus and device number. The numbering resets when the device is unplugged and plugged back in.

    Note: The actual device files and numbering can vary based on the Linux distribution, kernel version, and system configuration. For many peripherals, higher-level APIs and subsystems abstract away the need to interact directly with device files, providing a more user-friendly interface for accessing device functionality.


    Handling devices in Linux

    Managing peripherals in Linux using C involves direct interaction with hardware through the Linux kernel. This can be achieved via various methods, including system calls, direct memory access, and the use of specific Linux APIs. Here's a beginner-friendly tutorial to get you started with managing peripherals in Linux using C.

    Understanding Linux Device Files

    In Linux, everything is treated as a file, including hardware peripherals. These are accessible through device files located in the /dev directory. For example, serial ports might be represented as /dev/ttyS0, and USB devices might be listed as /dev/usb/hiddev0.

    Basic File Operations

    To interact with a peripheral device, you typically open, read from/write to, and close the device file using standard C file operations:

    • Open: Use open() to get a file descriptor for the device.
    • Read/Write: Use read() and write() with the file descriptor to communicate with the device.
    • Close: Use close() to release the file descriptor.

    Example - Accessing a Serial Port

    Here's how you can open a serial port and configure it using the termios structure.

    Required Headers

    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <termios.h>
    

    Opening a Serial Port

    PlantUML diagrame for port manuplation in linux:

    @startuml
    !theme toy
    start
    :Declare serial_fd;
    :Attempt to open serial port "/dev/ttyS0";
    if (serial_fd == -1) then (true)
      :perror("open");
      stop
    else (false)
    endif
    
    :Declare struct termios options;
    :Get current serial port settings;
    :Set read and write speed to 9600 baud;
    :Enable the receiver and set local mode;
    :Apply the settings to serial port;
    
    :Read/Write to the serial port;
    
    :Close the serial port;
    stop
    @enduml
    

    C programming for managing serial ports in Linux:

    int main() {
        int serial_fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
        if (serial_fd == -1) {
            perror("open");
            return -1;
        }
    
        struct termios options;
        tcgetattr(serial_fd, &options); // Get current serial port settings
        cfsetispeed(&options, B9600); // Set read and write speed to 9600 baud
        cfsetospeed(&options, B9600);
    
        options.c_cflag |= (CLOCAL | CREAD); // Enable the receiver and set local mode
        tcsetattr(serial_fd, TCSANOW, &options); // Apply the settings
    
        // Now you can read/write to the serial port using read() and write()
    
        close(serial_fd); // Close the serial port
        return 0;
    }
    

    Compiling Your Program

    To compile your C program, use the gcc compiler:

    gcc -o myprogram myprogram.c
    

    Replace myprogram.c with the name of your source file. This command compiles your C code and outputs an executable named myprogram.

    Running Your Program

    Run your compiled program from the terminal:

    ./myprogram
    

    Important Considerations

    • Permissions: Accessing device files usually requires root permissions. Run your program with sudo or set appropriate permissions on the device file.
    • Safety: Direct hardware access can lead to system instability or damage if not done carefully. Always double-check your code and understand the hardware specifications you're working with.

    Going Further

    • Explore IOCTLs: For more complex devices, you'll need to use IOCTL (input/output control) calls to perform specific control operations.
    • Kernel Module Development: Sometimes, you might need to write a custom kernel module to manage a peripheral. This involves a deeper understanding of the Linux kernel and C programming.
    • Documentation and Resources: Check out the Linux man pages (man 2 open, man 2 read, man 2 write, man 2 ioctl) for more information on system calls. The Linux Kernel Module Programming Guide is also a great resource for diving deeper into how Linux interacts with hardware.

    Sophisticated examples of interfacing with Linux devices using C

    Copy a file from a directory to a USB flash drive in C

    Creating a C program that copies files to a USB drive in Linux, including mounting and unmounting the USB drive, involves several steps and requires careful handling. This process can be divided into distinct phases: detecting the USB device, mounting it, copying the file(s), and unmounting the device. Below is a simplified example to demonstrate these steps.

    Important Notes:
    1. Mounting and Unmounting USB Drives: This usually requires root privileges. Therefore, the program should be run with appropriate permissions.
    2. USB Detection: Automatically detecting and mounting USB drives is complex and typically handled by the operating system. For simplicity, this example assumes you know the device identifier (e.g., /dev/sdb1) and mount point (e.g., /mnt/usb).
    3. Error Handling: This example includes basic error handling for clarity. Ensure you expand this based on your requirements.
    Compile with gcc

    To use the system function, compile the program with gcc and link with the -l option as necessary.

    Example Program:

    This program mounts a specified USB device, copies a file to it, and then unmounts it.

    PlantUML diagram for mounting a USB in linux:

    @startuml
    !theme toy
    start
    :Declare sourceFile, destination, device, mountPoint;
    
    :Prepare mount command;
    if (Execute mount command) then (success)
      :Prepare copy command;
      if (Execute copy command) then (success)
        :Prepare unmount command;
        if (Execute unmount command) then (success)
          :File copied successfully and USB drive unmounted;
        else (failure)
          :Failed to unmount;
        endif
      else (failure)
        :Failed to copy file;
        :Attempt to unmount;
      endif
    else (failure)
      :Failed to mount;
    endif
    
    stop
    @enduml
    

    C code for mounting a USB in linux:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main() {
        char *sourceFile = "/path/to/source/file";
        char *destination = "/mnt/usb/destinationFilename";
        char *device = "/dev/sdb1"; // Change this to your USB device
        char *mountPoint = "/mnt/usb"; // Ensure this mount point exists
    
        // Mount the USB drive
        char mountCmd[256];
        sprintf(mountCmd, "mount %s %s", device, mountPoint);
        if (system(mountCmd) != 0) {
            fprintf(stderr, "Failed to mount %s on %s\n", device, mountPoint);
            return 1;
        }
    
        // Copy the file
        char cpCmd[512];
        sprintf(cpCmd, "cp %s %s", sourceFile, destination);
        if (system(cpCmd) != 0) {
            fprintf(stderr, "Failed to copy file from %s to %s\n", sourceFile, destination);
            // Attempt to unmount before exiting due to failure
            system("umount /mnt/usb");
            return 2;
        }
    
        // Unmount the USB drive
        char umountCmd[256];
        sprintf(umountCmd, "umount %s", mountPoint);
        if (system(umountCmd) != 0) {
            fprintf(stderr, "Failed to unmount %s\n", mountPoint);
            return 3;
        }
    
        printf("File copied successfully and USB drive unmounted.\n");
        return 0;
    }
    

    Before running the program, ensure you have customized the paths according to your system settings:

    • Adjust the file paths for copying
    • Verify the existence of the /mnt/usb mount point, and create it if necessary
    • Possess superuser permissions.

    usb_exemple_execution

    Important Considerations:
    • Device and Mount Point: You need to replace /dev/sdb1 and /mnt/usb with your actual USB device identifier and mount point. The mount point directory must exist before running this program.
    • Running as Root: Operations like mounting and unmounting usually require root privileges. Run this program as root or use sudo.
    • Security Note: Using system() to execute commands that involve input strings can be insecure, especially if any part of the command is user-controlled. This can lead to shell injection vulnerabilities. Ensure that your inputs are sanitized or use more secure alternatives like fork() and exec() for production code.
    • Error Handling and Robustness: This example provides basic functionality. In a real-world application, more robust error handling, possibly involving direct system calls for file operations, and dynamic USB detection might be necessary.

    Compile this program using gcc and run it with root permissions to see it in action.

    Interfacing with I2C and serial (UART) devices in Linux using C involves direct interaction with device files. For I2C, you'll usually interact with devices via the /dev/i2c-X interface, where X is the bus number. For serial communication, you'll use the /dev/ttyS0 (for COM1), /dev/ttyS1 (for COM2), etc., depending on which serial port you're working with.


    Serial Communication Example in C

    This example opens a serial port, configures it, and writes a simple message.

    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <termios.h>
    #include <string.h>
    
    int main() {
        int serial_port = open("/dev/ttyS0", O_RDWR);
    
        if (serial_port < 0) {
            perror("open");
            return 1;
        }
    
        struct termios tty;
        memset(&tty, 0, sizeof tty);
    
        // Get current serial port settings
        if (tcgetattr(serial_port, &tty) != 0) {
            perror("tcgetattr");
            close(serial_port);
            return 1;
        }
    
        // Set Baud Rate
        cfsetospeed(&tty, B9600);
        cfsetispeed(&tty, B9600);
    
        // Setting other Port Stuff
        tty.c_cflag &= ~PARENB; // Make 8n1
        tty.c_cflag &= ~CSTOPB;
        tty.c_cflag &= ~CSIZE;
        tty.c_cflag |= CS8;
        tty.c_cflag &= ~CRTSCTS; // no flow control
        tty.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
        tty.c_lflag &= ~ICANON;
        tty.c_lflag &= ~ECHO; // Disable echo
        tty.c_lflag &= ~ECHOE; // Disable erasure
        tty.c_lflag &= ~ECHONL; // Disable new-line echo
        tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
        tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
        tty.c_iflag &= ~(ICRNL | INLCR); // Disable CR to NL translation, etc.
        tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g., newline chars)
        tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
    
        // Apply the settings
        if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
            perror("tcsetattr");
            close(serial_port);
            return 1;
        }
    
        // Write to serial port
        char msg[] = "Hello, Serial!\n";
        write(serial_port, msg, sizeof(msg));
    
        // Close the serial port
        close(serial_port);
    }
    

    I2C Communication Example in C

    This example sends a byte to an I2C device. You'll need to know the device's I2C address and the bus number it's connected to.

    PlantUML diagram first!

    @startuml
    !theme toy
    start
    :Declare device and address variables;
    
    :Attempt to open I2C bus device;
    if (open device success) then (yes)
      :Set device as I2C slave;
      if (set slave success) then (yes)
        :Prepare data buffer;
        :Write data to I2C device;
        if (write success) then (yes)
          -right-> [Success] :Close I2C bus device;
        else (no)
          :perror("Failed to write to the i2c device.");
          -right-> [Fail] :Close I2C bus device;
        endif
      else (no)
        :perror("Failed to acquire bus access and/or talk to slave.");
      endif
    else (no)
      :perror("Failed to open the i2c bus");
    endif
    
    stop
    @enduml
    

    C code:

    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <linux/i2c-dev.h>
    #include <sys/ioctl.h>
    
    int main() {
        const char *device = "/dev/i2c-1"; // Change to your I2C bus
        int address = 0x40; // Change to your device's I2C address
    
        int file;
        if ((file = open(device, O_RDWR)) < 0) {
            perror("Failed to open the i2c bus");
            exit(1);
        }
    
        if (ioctl(file, I2C_SLAVE, address) < 0) {
            perror("Failed to acquire bus access and/or talk to slave.\n");
            exit(1);
        }
    
        // Now you can use I2C_RDWR or I2C_SMBUS ioctls to communicate with the device
        // Example: write a byte to the device
        uint8_t buffer[1] = {0x00}; // Data to send
        if (write(file, buffer, 1) != 1) {
            perror("Failed to write to the i2c device.\n");
        }
    
        close(file);
        return 0;
    }
    
    Compiling and Running

    For both examples, compile with gcc:

    gcc -o serial_example serial_example.c
    gcc -o i2c_example i2c_example.c -lrt
    

    Run with appropriate permissions, potentially using sudo, because accessing hardware devices typically requires root privileges.

    📝 Article Author : SEMRADE Tarik
    🏷️ Author position : Embedded Software Engineer
    🔗 Author LinkedIn : LinkedIn profile

    Comments