Python PySerial I/O

Created:  
Updated:   07Aug2024   04:39:18   UTC 2024-08-07T04:39:18Z
Rating:   (0 reviewsThis article has not been rated yet)

The PySerial package is open source code written and maintained by Chris Liechti with the functionality to access and communicate with the serial port from Python. PySerial automatically selects the appropriate backend whether you're running on Windows, OSX (Mac), Linux, BSDBerkeley Software Distribution (BSD) operating system (possibly any POSIXPortable Operating System Interface (POSIX) is a family of standards specified by the IEEE Computer Society for maintaining compatibility between operating systems compliant system) or IronPython. The PySerial documentation is at pyserial.readthedocs.io and source code can be found on GitHub.

This article covers the basic usage of the Python PySerial module as a software interface to devices.

Setup

Both Python and the PySerial package must first be installed on the computer to read in microcontroller data over USBUniversal Serial Bus. Also, when connecting the microcontroller to the computer with a USB cable, the USB device on the computer needs to be identified to establish a connection.

Python Installation

Python can be downloaded from python.org. A detailed tutorial on how to install Python on Windows, Mac, or Linux can be found at realpython.com.

PySerial Installation

Instructions for installing the PySerial package can be found at pyserial.readthedocs.io. It is recommended that you create a virtual environment to install PySerial in. A primer on Python virtual environments can be found at realpython.com. The command for creating a virtual environment is using the venv module is given below.

python -m venv venv

The -m flag stands for module-name. The first venv in the command is the venv module name and the second venv argument is the name of the virtual environment and the folder name it creates.

Then activate the virtual environment.

For Windows, the command is:

> .\venv\Scripts\activate

For Linux and macOS, the command is:

$ source venv/bin/activate

You can install PySerial using the following PIPPIP is a package installer for Python that allows users to install packages from the Python Package Index and other indexes. The name is a recursive acronym for "Pip Installs Packages" command.

python -m pip install pyserial

After PySerial is installed, you can verify the PySerial version with the following commands in Python:

>>> import serial
>>> print(serial.VERSION)

USB Device

The device name is needed by PySerial to access the device. When a microcontroller is connected to the USB of a computer, it shows up as two devices:

HIDUSB Human Interface Device:
Human Interface Device (HID) which provides a generic interface that does not require drivers to be installed. It is limited to 64 Bytes/ms (~64K/second) per endpoint for sending 64 byte "reports" back and forth.
CDCUSB Communication Device Class ACMAbstract Control Model:
Communication Device Class of sub-type Abstract Control Model that simulates a serial port and requires drivers (INFAn INF file (setup Information file) is a plain-text file used by Microsoft Windows-based operating systems for the installation of software and drivers. file) to be installed.

On a Linux machine, the USB CDC ACM is used with the device name /dev/ttyACM0. After plugging in the microcontroller into the USB you can see the USB devices listed by running the command dmesg. If a USB adapter is used, then these devices are usually named /dev/ttyUSB0, /dev/ttyUSB1, /dev/ttyUSB2, etc.

On a Windows PCPersonal Computer, the Device Manager will show the microcontroller under the Ports (COMCommunication & LPTLine Print Terminal) as a USB Serial Device. For example, after plugging in a microcontroller into the USB of a Windows PC, the Pico was listed as a USB Serial Device (COM4) as shown in the figure below.

Windows Device Manager

I/O Methods

This section provides a summary of common methods used in PySerial to communicate with a devices such as a microcontroller or USB serial adapter. A more comprehensive set of methods can be found in the PySerial documentation at pyserial.readthedocs.io.

Serial Object

The serial object establishes the serial connection with the port. PySerial determines the operating system environment automatically and opens the port when setting the serial object. The serial object has many input arguments, but the two most important are the port the device is connected to and the baud rate, where the port argument is a string and the baudrate argument is an integer.

The following example shows how this is done in Linux or macOS with a device connected to /dev/ttyACM0 at a buad rate of 9600bpsbits per second.

import serial
ser = serial.Serial(port='/dev/ttyACM0', baudrate=9600)

For Windows with a device connected to COM3 at a buad rate of 9600bpsbits per second, the serial object is set as follows.

import serial
ser = serial.Serial(port='COM3', baudrate=9600)

The port name of the device depends on the operating system environment. On a Linux or Mac machine, a microcontroller usually has the the device name /dev/ttyACM0. If a USB adapter is used, then these devices are usually named /dev/ttyUSB0, /dev/ttyUSB1, /dev/ttyUSB2, etc. On Windows, devices are called COM1, COM2, COM3, etc.

There are more optional arguments that can be set with serial object (e.g., bytesize, parity, stopbits, timout, etc.), which can be found at pyserial.readthedocs.io.

Port Methods

The following table provides common port methods for opening and closing the ports, getting information on the ports, and setting port properties.

Port Methods
Method Description
ser.open() Opens or reopens the port using the current settings.
ser.close() Closes the port.
ser.is_open() Returns the current open/closed state of the port as a boolean.
ser.port() Returns the current port setting as a string. Can also be used to set the port (e.g., ser.port = 'COM3').
ser.get_settings() Returns a dictionary of port settings. The following keys are supported: write_timeout, inter_byte_timeout, dsrdtr, baudrate, timeout, parity, bytesize, rtscts, stopbits, xonxoff.
ser.name() Returns the device name as a string.
ser.baudrate() Returns the current baud rate setting as an integer. Can also be used to set the baud rate (e.g., ser.baudrate = 19200). The constant ser.BAUDRATES provides a list of available baud rates for the device.
ser.bytesize() Returns the current byte size setting as an integer (i.e., the total number of bits to read). Can also be used to set the byte size (e.g., ser.bytesize = bytesize=serial.EIGHTBITS). The options for byte size are FIVEBITS, SIXBITS, SEVENBITS, and EIGHTBITS. The constant ser.BYTESIZES provides a list of available byte sizes for the device.

Read/Write Methods

The following table provides common read/write methods for reading data from the ports, writing data on the ports, and setting how long these operations should take with a timeout.

Read/Write Methods
Method Description
ser.read(size=1) Read size bytes from the serial port where size is the integer number of bytes to be read. If a timeout is set it may return fewer characters than requested. With no timeout it will block until the requested number of bytes is read.
ser.readline(size=-1) Read and return one line in bytes. If size is specified, at most size bytes will be read. The line terminator is b'\n'.
ser.read_until(expected=LF, size=None) Read until the expected Line Feed LF ('\n' by default), the size (integer number of bytes to read) is exceeded, or until timeout occurs. Returns bytes from the port.
ser.write(data) Write the bytes data to the port and returns an integer. This should be of type bytes (or compatible such as bytearray or memoryview). Unicode strings must be encoded (e.g. 'hello'.encode('utf-8')).
ser.timeout() Returns the current timeout setting in seconds as a float, which controls the behavior of read(). Can also be used to set the timeout (e.g., ser.timout = 1) Possible values for the parameter timeout:
  • timeout = None: wait forever or until requested number of bytes are received
  • timeout = 0: non-blocking mode, return immediately in any case, returning zero or more, up to the requested number of bytes
  • timeout = x: set timeout to x seconds (float allowed) returns immediately when the requested number of bytes are available, otherwise wait until the timeout expires and return all bytes that were received until then.
ser.write_timeout() Returns the current timeout setting in seconds as a float, which controls the behavior of write(). Can also be used to set the write timeout (e.g., ser.timout = 1) Possible values for the parameter write_timeout:
  • timeout = None: wait forever or until the data is written
  • timeout = x: set timeout to x seconds (float allowed) returns immediately when the requested number of bytes are written, otherwise wait until the timeout expires even if not all the data was written (an exception is raised if this occurs).

Buffer Management

The following table provides common input/output buffer management methods for clearing its contents and extending its size.

Buffer Management Methods
Method Description
ser.flush() Clears buffer of file like objects.
ser.reset_input_buffer() Flush the input buffer, clearing all its contents.
ser.reset_output_buffer() Flush the output buffer, clearing all its contents.
ser.in_waiting Returns the number of bytes in the receive buffer as an integer.
ser.out_waiting Returns the number of bytes in the output buffer as an integer.
ser.set_buffer_size(rx_size=4096, tx_size=4096) Sets the buffer size. This is sent to the device driver, which can ignore this setting.
rx_size is the receiving buffer size as an integer number of bytes.
tx_size is the transmitting output buffer size as an integer number of bytes.
The default values for rx_size and rx_size are 4096 bytes.

Code Example

The following code example reads in data from a microcontroller connected to a computer USB port and displays it on the computer console. The data generated from the microcontroller in this example is simply a counter, so there is no need to connect any peripherals to the microcontroller. Both Arduino code and MicroPython code are given for the microcontroller software. A Python script with PySerial is provided for the computer software.

PySerial Code Example Hookup

Arduino Code

The Arduino code below can be ran on any board compatible with the C/C++ Arduino library. It simply prints consecutive numbers with a counter every second over USB. The baud rate is set to 9600bpsbits per second, which needs to be the same as the value set in the PySerial object in the computer Python code.

Arduino Code
              

MicroPython Code

The MicroPython code shown below can be ran on a RPiRaspberry Pi Pico or any other MicroPython compatible board. It simply prints consecutive numbers with a counter every second over USB using a print() statement. The baud rate is set to 9600bpsbits per second, which needs to be the same as the value set in the PySerial object in the computer Python code.

MicroPython Code
              

Computer Code

The computer Python code given below reads in data from a microcontroller over USB and displays it to the computer console. The serial object sets the port name and a baudrate. The port used in this example is Linux port /dev/ttyACM0, but if you're using Windows you need to change it to the COM port number the microcontroller shows up as in the Device Manager (e.g., COM1, COM2, COM3, etc.). The baudrate is set to the standard 9600bps, more than enough for this example, although you can choose a higher value if needed for a different application. The Python select library is imported for polling the serial buffer to detect incoming messages.

Computer Python Code computer_pyserial.py
              

After loading either the Arduino code or MicroPython code onto the microcontroller and running the computer Python program over five data points and then pressing CTR-C on the keyboard, you should get the console output shown in the figure below.

computer_pyserial.py Computer Python Code Console Output
              

Under the Hood

In order to communicate with the serial port on a computer, Python needs to makes operating system calls that are unique to the environment. In Windows, PySerial uses the Win32 API Dynamic Link Library (DLLDynamic Link Library) written in the C programming language. For POSIXPortable Operating System Interface (POSIX) is a family of standards specified by the IEEE Computer Society for maintaining compatibility between operating systems systems, such as Linux and Mac, PySerial makes use of the Python os module to open, read/write, and close the serial port.

Windows

In Windows, PySerial uses the Win32 API DLL to access the serial port. Documentation on the Win32 API functions for accessing data can be found at learn.microsoft.com. Since the Win32 API is written in the C programming language, PySerial makes use of Python's ctypes library to directly access the C functions in the Win32 DLL without the need to write any C code. Documentation for the ctypes library can be found at docs.python.org.

The PySerial code that handles the Windows backend for opening, reading, writing to the serial port can be found in the serialwin32.py file, which makes use of constants and types for the Win32 API from the win32.py code.

For opening a serial port, PySerial uses the CreateFileW() function from the Win32API (learn.microsoft.com).

    port_handle = CreateFileW(lpFileName,
                              dwDesiredAccess,
                              dwShareMode,
                              lpSecurityAttributes,
                              dwCreationDisposition,
                              dwFlagsAndAttributes,
                              hTemplateFile)
          
lpFileName:
Name of device to be opened, which is the port name.
dwDesiredAccess:
Requested access to the device, which can be read, write, both or neither zero. PySerial sets this to GENERIC_READ | GENERIC_WRITE.
dwShareMode:
The requested sharing mode of the file or device, which can be read, write, both, delete, all of these, or none. PySerial sets this to 0 for exclusive access, which prevents subsequent open operations on a device if they request delete, read, or write access.
lpSecurityAttributes:
Optional security descriptor, and a Boolean value that determines whether the returned handle can be inherited by child processes. PySerial sets this to None for no security.
dwCreationDisposition:
An action to take on a device that exists or does not exist. PySerial sets this to OPEN_EXISTING, which opens a device, only if it exists. If the specified device does not exist, the function fails and the last-error code is set to ERROR_FILE_NOT_FOUND.
dwFlagsAndAttributes:
Device attributes and flags that can be combined using the bitwise OR operator |. PySerial sets this to FAIL_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, where the device does not have other attributes set and the device is being opened or created for asynchronous I/OInput/Output.
hTemplateFile:
Optional valid handle to a template file with the GENERIC_READ access right. The template file supplies file attributes and extended attributes for the file that is being created. PySerial sets this to 0.

For reading from a serial port, PySerial uses the ReadFile() function from the Win32API (learn.microsoft.com).

    read_ok = ReadFile(hFile,
                       lpBuffer,
                       nNumberOfBytesToRead,
                       lpNumberOfBytesRead,
                       lpOverlapped)
          
hFile:
Handle to the device. This is set to the port handle from the open port function.
lpBuffer:
A pointer to the buffer that receives the data read from a device. PySerial sets this to a ctypes string buffer buf = ctypes.create_string_buffer(n) with a size n = min(comstat.cbInQue, size) that is the minimum of what's in the buffer que and the requested size from the read function.
nNumberOfBytesToRead:
The maximum number of bytes to be read. PySerial sets this to the minimum of what's in the buffer que and the requested size: n = min(comstat.cbInQue, size).
lpNumberOfBytesRead:
A pointer to the variable that receives the number of bytes read when using a synchronous hFile parameter. PySerial sets this to a ctype byref of type DWORD with cytypes.byref(win32.DWORD()).
lpOverlapped:
A pointer to an OVERLAPPED structure is required if the hFile parameter was opened with FILE_FLAG_OVERLAPPED, otherwise it can be NULL. PySerial sets this to a ctype byref of type OVERLAPPED with ctypes.byref(win32.OVERLAPPED ()).
read_ok:
Boolean output of the function. If the function succeeds, the return value is nonzero (TRUE). If the function fails, the return value is zero (FALSE).

For writing to a serial port, PySerial uses the WriteFile() function from the Win32API (learn.microsoft.com).

              
    success = WriteFile(hFile,
                        lpBuffer,
                        nNumberOfBytesToWrite,
                        lpNumberOfBytesWritten,
                        lpOverlapped)
              
            
hFile:
Handle to the device. This is set to the port handle from the open port function.
lpBuffer:
A pointer to the buffer containing the data to be written to the device. PySerial sets this to data argument of the write function and converts it to type bytes.
nNumberOfBytesToWrite:
The number of bytes to be written to the device. PySerial sets this to the length of the data variable: len(data).
lpNumberOfBytesWritten:
A pointer to the variable that receives the number of bytes written when using a synchronous hFile parameter. PySerial sets this to a ctype byref of type DWORD: ctypes.byref(win32.DWORD()).
lpOverlapped:
A pointer to an OVERLAPPED structure is required if the hFile parameter was opened with FILE_FLAG_OVERLAPPED, otherwise this parameter can be NULL. PySerial sets this to ctype byref of type OVERLAPPED: ctypes.byref(win32.OVERLAPPED ()).
success:
Boolean output of the function. If the function succeeds, the return value is nonzero (TRUE). If the function fails, the return value is zero (FALSE).

Clearing the input and output buffer is done with the PurgeCommfunction() from the Win32API (learn.microsoft.com). This function takes two arguments, the port handler and flags.

    output = PurgeComm(hFile, dwFlags)
            
hFile:
Handle to the device. This is set to the port handle from the open port function.
dwFlags:
Discards all characters from the output or input buffer of a specified communications resource. It can also terminate pending read or write operations on the resource. This argument can be combined using the bitwise OR operator |. PySerial sets dwFlags to PURGE_RXCLEAR | PURGE_RXABORT for the clearing the input buffer and PURGE_TXCLEAR | PURGE_TXABORT for the clearing the output buffer. PURGE_RXCLEAR and PURGE_TXCLEAR clears the input/output buffer. PURGE_RXABORT and PURGE_TXABORT terminates all outstanding overlapped read/write operations and returns immediately, even if the read operations have not been completed.
output:
Boolean output of the function. If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.

Closing the port is done with the CloseHandle() function from the Win32API (learn.microsoft.com). This function only takes one argument, the port handle.

    output = CloseHandle(hObject)
            
output:
If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.

Linux and Mac OSX

For Linux and MacOS, PySerial uses the Python os module to open, read, and write to the serial port. Documentation for the os library can be found at docs.python.org. The PySerial code that handles the Linux and MacOS backend for opening, reading, writing to the serial port can be found in the serialposix.py file on GitHub.

For opening a serial port, the os.open() function is called with the port name and flags arguments (docs.python.org).

    fd = os.open(path, flags)
          
path:
Name of device to be opened, which is the port name.
flags:
Constants to be set as flags that can be combined using the bitwise OR operator |. PySerial sets the flags to os.O_RDWR | os.O_NOCTTY | os.O_NOBLOCK.
  • os.O_RDWR: Read and write
  • os.O_NOCTTY: If the named file is a terminal device, don't make it the controlling terminal for the process
  • os.O_NOBLOCK: This prevents open from blocking for a "long time" to open the file
fd:
File descriptor handle to the device.

For reading the serial port, the os.read() function is called with the file descriptor fd and n bytes to read arguments (docs.python.org).

    os.read(fd, n)
          

For writing the serial port, the os.write() function is called with the file descriptor fd and a bytestring str to write arguments (docs.python.org). Pyserial converts output data to a type bytes for the str argument: str = bytes(data).

    os.write(fd, str)
          

Clearing the input and output buffer is done with the termios module (docs.python.org). PySerial uses the termios subroutine tcflush() (docs.python.org) with two arguments: the file descriptor fd and queue selector that specifies the input/output buffer.

    termios.tcflush(fd, queue)
          
fd:
File descriptor obtained when opening the port
queue:
Specifies the input/output buffer with the constants TCIFLUSH for the input queue or TCOFLUSH for the output queue. PySerial uses termios to obtain the constants: termios.TCIFLUSH and termios.TCOFLUSH.

Closing the port is done with the os.close() function with the file descriptor fd as the only argument (docs.python.org) .

    os.close(fd)
          

Conclusion

Understanding the PySerial module allows you to interact with serial devices such as microcontrollers and USB Serial Adapters, with the capability of reading in data from serial devices onto a computer so you can process and store the data with Python. This article covered how to install PySerial and its basic I/O functionality that is commonly used. More functionality can be found in the PySerial documentation at pyserial.readthedocs.io and the source code at GitHub.

Related Content


Recording Data on a Computer to a CSV File using Python and PySerial

Created:   22Oct2022   19:21:09   UTC 2022-10-22T19:21:09Z
Updated:   07Aug2024   05:23:19   UTC 2024-08-07T05:23:19Z
Rating:  (0 reviewsThis article has not been rated yet)

Read in data to a computer from a microcontroller over USB using PySerial and storing the data to a CSV file. Microcontroller programs are given in both Arduino code and MicroPython.


Live Data Plot Animation with Python using Matplotlib

Created:   31Oct2022   07:19:06   UTC 2022-10-31T07:19:06Z
Updated:   07Aug2024   06:05:49   UTC 2024-08-07T06:05:49Z
Rating:  (0 reviewsThis article has not been rated yet)

How to plot real-time data with Python using Matplotlib.

Products


Article Rating

Sign in to rate this article

Sign In


(0) Comments

Sign in to leave a comment

Sign In