Wiznet makers

ruilixin6

Published August 03, 2026 ©

70 UCC

0 VAR

0 Contests

0 Followers

0 Following

MicroPython RS232: XON/XOFF Flow Control & Overflow Solutions

This tutorial introduces RS232 standards, MAX3232 level conversion, and implements an RS232 communication class with XON/XOFF software flow control on Raspberry

COMPONENTS
PROJECT DESCRIPTION

【Preliminary Note】The original hardware example in this article was written based on the RP2040. The actual hardware used in this hands-on demonstration features the W55RP20 as the main controller chip. The circuit logic and UF2 flashing operation principles are universally applicable, with only the main controller model differing. The original chip model mentioned in the circuit descriptions below is provided for reference purposes only.

 

Tweet

1. Fundamentals of RS232 Communication

RS-232 is a widely used serial Communication Protocol, originally defined by the Electronic Industries Association (EIA) in the 1960s, which is applied for data transmission between computers and external devices such as modems, printers, sensors and the like.
As we mentioned earlier when discussing the serial port communication protocol layer, the RS-232 standard and the TTL electrical standard we use in serial communication only differ in terms of the logic level standards applied, while they are identical in terms of the specific serial port protocols, including the data packet structure, error detection and correction, as well as rate matching and synchronization for serial communication.
RS-232: It uses high-level and low-level signals. The standard defines the high level as + 3V to + 25V and the low level as -3V to -25V. Due to its relatively high signal level, RS-232 is suitable for long-distance transmission.
TTL-UART: It uses TTL level, where the high level is typically + 2.5V to + 5V (usually 5V) and the low level is 0V; TTL signal level is suitable for short-distance communication.

2. Introduction to MAX3232 Chip

Here, we use the MAX3232 chip to convert TTL level (0V to + 5V) to the RS-232 standard level (±3V to ±25V). The MAX3232 is a commonly used RS-232 to TTL (or CMOS) level converter chip, which supports a data transmission rate of up to 250kbps and two channels of RS-232 to TTL (or CMOS) level conversion, and is often used for serial communication between microcontrollers and devices such as computers, modems, and printers.
The pin names and functions of the MAX3232 chip are as follows:
They are divided into the following three categories according to their functions:
Charge Pump Related Pins: These pins are used for the operation of the internal charge pump, which generates the required voltages (+ 5.5V and -5.5V) via external capacitors to provide appropriate levels for the RS-232 driver.
Power and Ground Pins: Supply power and ground for the MAX3232 to ensure normal operation of the chip, with a supply voltage range from + 3.0V to + 5.5V.
Data transmission pins: Used for level conversion between TTL/CMOS and RS-232, including RS-232 receiver input and output as well as TTL/CMOS driver input and output.
Its working principle can be simply summarized as follows: the MAX3232 converts the input low voltage (e. g., 3.3V or 5V) into high voltages that comply with the RS-232 standard (typically + 5V to + 15V and -5V to -15V) through its built-in charge pump circuit and inverters.
Internal Structure and Reference Circuit of the MAX3232 Chip
Here, the capacitance value required for the operation of the internal charge pump is correlated with the supply voltage:
RS-232 communication typically uses DB9 connectors, which feature 9 pins, each serving a specific function in RS-232 communication. The pin numbers and corresponding functions are listed below:
Here, we only use DB9 the RXD, TXD and GND pins of the interface.
There are two types of DB9 connectors:
Male connector: with 9 protruding metal pins
Female connector: features 9 small holes to receive the pins of the male connector

3. USB to RS232 Adapter

Typically, the serial port of a computer or device uses a DB9 female connector, while one end of a serial cable is a DB9 male connector; the DB9 connector used on our development board is a female type. On modern computers, DB9 connectors have long been phased out, but virtual serial ports (implemented via USB-to-serial conversion) remain functional, and we can use a USB-to-DB9 adapter to enable communication between a microcontroller and a computer over the RS232 protocol.
Here, USB-to-RS232 adapters often use a built-in FT232 chip to convert between different Communication Protocols. The FT232 is a USB-to-serial chip designed by FTDI, which is widely used in embedded systems, microcontroller development and debugging, as well as for converting traditional RS-232 or TTL serial interfaces to USB interfaces, enabling modern devices to communicate with legacy serial devices.
The FT232 chip requires the driver provided by FTDI to be installed in most operating systems, which can be downloaded from FTDI's official website specifically:
https://ftdichip.com/drivers/vcp-drivers/
Here, the USB-to-RS232 adapter we provide adopts a domestically produced MS3020 chip, and the corresponding driver program also needs to be installed. If you directly connect the USB-to-RS232 adapter to the computer without installing the corresponding driver, normal communication will not be available. When you open the Device Manager, the display is as follows:
The driver download link is as follows:
http://www.dtech.cn/download/download.php?class3=410
You can also find the corresponding driver in the utility software we provide:
Open the PDF file of the driver installation instructions and follow the steps to complete the installation:
After successful installation, click on the Device Manager again, and you will find that the small exclamation mark on the original device has disappeared, indicating that the device can be used normally. In the corresponding serial communication software assistant, you can also select its port normally:

4. Application Experiment

In the following code, we use a custom RS232 data transceiver class to implement point-to-point communication between the master device (Raspberry Pi Pico) and the slave device (computer), and implement in the custom class software flow control (Software Flow Control) function. The so-called software flow control, also known as XON/XOFF flow control, is a software-based serial communication flow control mechanism. It uses specific control characters to start or stop data transmission. These control characters are sent through the serial communication line without requiring additional physical connections and rely entirely on the serial data line.
Software flow control typically uses the following two ASCII control characters:
XON (0x11, CTRL-Q): indicates "resume data transmission"(the sender may continue transmitting)
XOFF (0x13, CTRL-S): indicates "pause data transmission"(the sender shall suspend transmission to prevent buffer overflow at the receiver end)
So when should these two ASCII control characters be used? Let's briefly explain their application scenarios below:
Receiver sends XOFF: When the receiver's buffer is nearly full, the receiver sends an XOFF signal to notify the sender to pause data transmission
Sender Pauses Transmission: Upon receiving the XOFF signal, the sender immediately pauses data transmission and waits for further instructions
Receiver sends XON: When the receiver finishes processing the data and frees up buffer space, it will send an XON signal to inform the sender that it can resume data transmission
The sender continues transmission: After receiving the XON signal, the sender resumes data transmission until it receives another XOFF signal or completes data transmission
In the following experiment, we need to insert the Fengya No. 1 Board - Grove Interface expansion board into the Fengya No. 1 Board - Serial Port Level Conversion Board, and at the same time turn on the RS232-SW DIP switch RS232-TXD and RS232-RXD options. We use the serial peripheral 0 to connect to the TTL1 interface of the RS232 chip on the Fengya No. 1 Board - Serial Port Level Conversion Board, and set the receive pin of the Raspberry Pi Pico to GPIO17, and the transmit pin to GPIO16:
Next, we use a USB-to-RS232 adapter to connect to Fengya No. 1 Board - Serial Port Level Conversion Board 's RS232 Interface port, and the physical connection diagram is shown below:
We first customize an RS232 communication class with software flow control function, and the sample code is as follows:
# 自定义RS232串口通信类,使用软件流控
class RS232:
    """
    自定义RS232串口通信类,支持软件流控(XON/XOFF协议)。

    该类实现了RS232串口的发送和接收功能,支持基于XON/XOFF的流控制机制。流控制用于在数据传输过程中,
    根据接收缓冲区的状态来控制数据的发送与暂停,防止接收缓冲区溢出。通过该类,可以进行串口数据的发送、接收和流控管理。

    Attributes:
        uart (UART): 串口实例,代表用于通信的硬件接口。
        tx_buffer (bytearray): 发送缓冲区,用于存储待发送的数据。
        rx_buffer (bytearray): 接收缓冲区,用于存储接收到的数据。
        rx_pos (int): 接收缓冲区的指针,标记接收数据的位置。
        rx_buf_size (int): 接收缓冲区的大小,表示接收缓冲区能够存储的最大数据量。
        tx_pos (int): 发送缓冲区的指针,标记已准备发送数据的位置。
        tx_buf_size (int): 发送缓冲区的大小,表示发送缓冲区能够存储的最大数据量。
        xon (bool): 软件流控状态,表示当前是否可以发送数据。`True`表示可以发送,`False`表示需要暂停。

    Methods:
        send(data: str) -> None:
            发送数据到串口。支持XON/XOFF流控,发送过程中如果流控为暂停状态,将等待直到XON信号恢复。

        receive() -> bytearray:
            从串口接收数据。接收过程中如果接收缓冲区已满,则会发送XOFF信号暂停数据传输。
    """

    # 软件流程相关类变量:两个 ASCII 控制字符
    # XON (CTRL-Q)
    XON = b'\x11'
    # XOFF (CTRL-S)
    XOFF = b'\x13'

    def __init__(self, uart, rx_buf_size=256, tx_buf_size=256):
        """
        初始化串口对象。

        Args:
            uart (UART): 使用的串口实例。
            rx_buf_size (int, optional): 接收缓冲区的大小,默认值为256。
            tx_buf_size (int, optional): 发送缓冲区的大小,默认值为256。

        Returns:
            None
        """

        # 将传入的 UART 对象保存
        self.uart = uart
        # 发送缓冲区,用于存储待发送的数据
        self.tx_buffer = bytearray(tx_buf_size)
        # 接收缓冲区,用于存储接收到的数据
        self.rx_buffer = bytearray(rx_buf_size)

        # 接收缓冲区的指针,跟踪数据位置
        self.rx_pos = 0
        # 接收缓冲区大小
        self.rx_buf_size = rx_buf_size

        # 发送缓冲区的指针,跟踪数据位置
        self.tx_pos = 0
        # 发送缓冲区大小
        self.tx_buf_size = tx_buf_size

        # XON 状态,表示可以发送数据
        self.xon = True

    def send(self, data : str) -> None:
        """
        发送数据。

        该函数将传入的数据发送到串口,遵循XON/XOFF流控机制。如果当前流控不允许
        发送数据,函数会等待直到流控解除。

        Args:
            data (str): 待发送的数据字符串。

        Returns:
            None
        """

        # 如果data不是字符串类型,进行数据转换
        if not isinstance(data, str):
            data = str(data)

        # 如果数据长度超过发送缓冲区大小,进行截断
        if len(data) > self.tx_buf_size:
            data = data[0:self.tx_buf_size]

        # 将待发送的数据加入到发送缓冲区中
        # 按照当前的发送缓冲区指针 self.tx_pos 插入数据
        self.tx_buffer[self.tx_pos:self.tx_pos+len(data)] = data.encode('utf-8')
        # 更新发送缓冲区指针
        self.tx_pos += len(data)

        # 遍历发送缓冲区中的数据
        for i in range(self.tx_pos):

            # 如果 XOFF 被激活,等待 XON 信号继续传输
            while not self.xon:
                time.sleep(0.01)  # 等待 10 毫秒

            # 发送单字节,确保为 bytes 类型
            self.uart.write(bytes([self.tx_buffer[i]]))

        # 发送完毕后,通过重置指针清空发送缓冲区
        self.tx_pos = 0

    def receive(self) -> bytearray:
        """
        接收数据。

        该函数检查串口接收缓冲区,读取接收到的数据并返回。若接收缓冲区已满,
        则通过XOFF信号通知暂停数据传输。

        Returns:
            bytearray: 接收到的数据,作为字节数组返回。
        """

        # 循环检查 UART 接收缓冲区是否有数据
        while uart.any():

            # 逐字节接收数据
            byte = self.uart.read(1)

            # 判断是否是 XON 或 XOFF 信号
            if byte == RS232.XON:
                # 收到 XON,允许继续传输
                self.xon = True
            elif byte == RS232.XOFF:
                # 收到 XOFF,暂停传输
                self.xon = False
            else:
                # 接收到的实际数据存储到接收缓冲区
                self.rx_buffer[self.rx_pos] = byte[0]
                # 移动缓冲区指针
                self.rx_pos += 1

                # 缓冲区已满,发送 XOFF 信号
                if self.rx_pos >= self.rx_buf_size:
                    self.uart.write(RS232.XOFF)
                    break
        # 截取数据
        data = self.rx_buffer[0:self.rx_pos]
        # 重置接收缓冲区指针
        self.rx_pos = 0
        # 返回接收的数据
        return data
Custom RS232 class works as follows:
Send data send (): First check whether data is a string; if not, convert it to a string, encode the data into a UTF-8 byte string, and store it in the transmit buffer tx_buffer; then send the data byte by byte. Before each data transmission, check the xon status; if xon is False (i. e., an XOFF signal has been received), pause transmission until an XON signal is received; after the data is sent, reset the transmit buffer pointer tx_pos to 0
Receive data receive (): Check whether there is received data via self. uart. any () and read the data one by one; then perform XON/XOFF signal detection and received data storage. If the receive buffer is full (i. e. rx_pos is equal to or exceeds rx_buf_size), send an XOFF signal to the sender to notify it to suspend data transmission, finally return all received data and reset the receive buffer pointer rx_pos
The complete code is shown below, located in the folder of the supporting materials: elegance-devkit v1\Demo\11 UART_RS232:
# Python env   : MicroPython v1.23.0
# -*- coding: utf-8 -*-        
# @Time    : 2024/9/22 下午11:23   
# @Author  : 李清水            
# @File    : main.py       
# @Description : UART类实验,RS232串口通信实现软件流控功能

# ======================================== 导入相关模块 =========================================

# 硬件相关的模块
from machine import UART, Pin, Timer
# 时间相关的模块
import time

# ======================================== 全局变量 ============================================

# 串口发送计数变量
send_count: int = 0

# ======================================== 功能函数 ============================================

# 定时器回调函数,用于定时接收数据
def receive_data(timer: Timer) -> None:
    """
    定时器回调函数,用于定时接收数据。

    该函数会检查串口接收的数据,并根据流控状态控制LED灯的开关。
    如果流控信号允许发送数据,LED会熄灭;如果流控信号禁止发送数据,LED会亮起。

    Args:
        timer (Timer): 定时器实例。

    Returns:
        None
    """
    global rs232,LED

    # 接收数据
    data: bytearray = rs232.receive()

    # 判断是否接收到数据
    if len(data) > 0:
        # 将接收到的数据转换为十六进制并打印
        hex_data = [hex(byte) for byte in data]
        print("Received data:", hex_data)

    # 判断rs232的流控状态
    if rs232.xon:
        # 如果可以发送数据,熄灭 LED
        LED.value(0)
    else:
        # 如果不能发送数据,点亮 LED
        LED.value(1)

# ======================================== 自定义类 ============================================

# 自定义RS232串口通信类,使用软件流控
class RS232:
    """
    自定义RS232串口通信类,支持软件流控(XON/XOFF协议)。

    该类实现了RS232串口的发送和接收功能,支持基于XON/XOFF的流控制机制。流控制用于在数据传输过程中,
    根据接收缓冲区的状态来控制数据的发送与暂停,防止接收缓冲区溢出。通过该类,可以进行串口数据的发送、接收和流控管理。

    Attributes:
        uart (UART): 串口实例,代表用于通信的硬件接口。
        tx_buffer (bytearray): 发送缓冲区,用于存储待发送的数据。
        rx_buffer (bytearray): 接收缓冲区,用于存储接收到的数据。
        rx_pos (int): 接收缓冲区的指针,标记接收数据的位置。
        rx_buf_size (int): 接收缓冲区的大小,表示接收缓冲区能够存储的最大数据量。
        tx_pos (int): 发送缓冲区的指针,标记已准备发送数据的位置。
        tx_buf_size (int): 发送缓冲区的大小,表示发送缓冲区能够存储的最大数据量。
        xon (bool): 软件流控状态,表示当前是否可以发送数据。`True`表示可以发送,`False`表示需要暂停。

    Methods:
        send(data: str) -> None:
            发送数据到串口。支持XON/XOFF流控,发送过程中如果流控为暂停状态,将等待直到XON信号恢复。

        receive() -> bytearray:
            从串口接收数据。接收过程中如果接收缓冲区已满,则会发送XOFF信号暂停数据传输。
    """

    # 软件流程相关类变量:两个 ASCII 控制字符
    # XON (CTRL-Q)
    XON = b'\x11'
    # XOFF (CTRL-S)
    XOFF = b'\x13'

    def __init__(self, uart, rx_buf_size=256, tx_buf_size=256):
        """
        初始化串口对象。

        Args:
            uart (UART): 使用的串口实例。
            rx_buf_size (int, optional): 接收缓冲区的大小,默认值为256。
            tx_buf_size (int, optional): 发送缓冲区的大小,默认值为256。

        Returns:
            None
        """

        # 将传入的 UART 对象保存
        self.uart = uart
        # 发送缓冲区,用于存储待发送的数据
        self.tx_buffer = bytearray(tx_buf_size)
        # 接收缓冲区,用于存储接收到的数据
        self.rx_buffer = bytearray(rx_buf_size)

        # 接收缓冲区的指针,跟踪数据位置
        self.rx_pos = 0
        # 接收缓冲区大小
        self.rx_buf_size = rx_buf_size

        # 发送缓冲区的指针,跟踪数据位置
        self.tx_pos = 0
        # 发送缓冲区大小
        self.tx_buf_size = tx_buf_size

        # XON 状态,表示可以发送数据
        self.xon = True

    def send(self, data : str) -> None:
        """
        发送数据。

        该函数将传入的数据发送到串口,遵循XON/XOFF流控机制。如果当前流控不允许
        发送数据,函数会等待直到流控解除。

        Args:
            data (str): 待发送的数据字符串。

        Returns:
            None
        """

        # 如果data不是字符串类型,进行数据转换
        if not isinstance(data, str):
            data = str(data)

        # 如果数据长度超过发送缓冲区大小,进行截断
        if len(data) > self.tx_buf_size:
            data = data[0:self.tx_buf_size]

        # 将待发送的数据加入到发送缓冲区中
        # 按照当前的发送缓冲区指针 self.tx_pos 插入数据
        self.tx_buffer[self.tx_pos:self.tx_pos+len(data)] = data.encode('utf-8')
        # 更新发送缓冲区指针
        self.tx_pos += len(data)

        # 遍历发送缓冲区中的数据
        for i in range(self.tx_pos):

            # 如果 XOFF 被激活,等待 XON 信号继续传输
            while not self.xon:
                time.sleep(0.01)  # 等待 10 毫秒

            # 发送单字节,确保为 bytes 类型
            self.uart.write(bytes([self.tx_buffer[i]]))

        # 发送完毕后,通过重置指针清空发送缓冲区
        self.tx_pos = 0

    def receive(self) -> bytearray:
        """
        接收数据。

        该函数检查串口接收缓冲区,读取接收到的数据并返回。若接收缓冲区已满,
        则通过XOFF信号通知暂停数据传输。

        Returns:
            bytearray: 接收到的数据,作为字节数组返回。
        """

        # 循环检查 UART 接收缓冲区是否有数据
        while uart.any():

            # 逐字节接收数据
            byte = self.uart.read(1)

            # 判断是否是 XON 或 XOFF 信号
            if byte == RS232.XON:
                # 收到 XON,允许继续传输
                self.xon = True
            elif byte == RS232.XOFF:
                # 收到 XOFF,暂停传输
                self.xon = False
            else:
                # 接收到的实际数据存储到接收缓冲区
                self.rx_buffer[self.rx_pos] = byte[0]
                # 移动缓冲区指针
                self.rx_pos += 1

                # 缓冲区已满,发送 XOFF 信号
                if self.rx_pos >= self.rx_buf_size:
                    self.uart.write(RS232.XOFF)
                    break
        # 截取数据
        data = self.rx_buffer[0:self.rx_pos]
        # 重置接收缓冲区指针
        self.rx_pos = 0
        # 返回接收的数据
        return data

# ======================================== 初始化配置 ==========================================

# 上电延时3s
time.sleep(3)
# 打印调试消息
print("FreakStudio: RS232 Software Flow Control Demo")

# 创建串口对象,设置波特率为115200
uart: UART = UART(0, 115200)
# 初始化uart对象,波特率为115200,数据位为8,无校验位,停止位为1
# 设置接收引脚为GPIO17,发送引脚为GPIO16
# 设置串口超时时间为100ms
uart.init(baudrate  = 115200,
          bits      = 8,
          parity    = None,
          stop      = 1,
          tx        = 16,
          rx        = 17,
          timeout   = 100)

# 初始化RS232对象,设置接收缓冲区大小为32,发送缓冲区大小为32
rs232: RS232 = RS232(uart, 32, 32)

# 设置GPIO 25为LED输出引脚,下拉电阻使能
LED: Pin = Pin(25, Pin.OUT, Pin.PULL_DOWN)

# 创建定时器对象,每隔10ms接收数据
timer: Timer = Timer(-1)
timer.init(period=10, mode=Timer.PERIODIC, callback=receive_data)

# ========================================  主程序  ===========================================

# 循环执行
while True:
    # 发送固定数据,每次发送完成都换行
    rs232.send("RS232 Send Data :" + str(send_count)+'\r\n')
    # 发送计数变量递增
    send_count = send_count + 1
    # 延时1s
    time.sleep(1)
Here, in the main loop, we use the data transmission method in the custom class to send data at regular intervals of approximately 1 second. The timer checks for incoming data every 10 milliseconds; if any data is received, it will be output to the terminal, and the LED will indicate the current flow control status of the serial port.
Here, the timer can be simply understood as an execution flow that periodically executes the corresponding callback function receive_data. It is triggered periodically, will call a certain function at set time intervals and is not affected by the time. sleep delay method. In this program, through the periodic call of the timer, we can realize continuous monitoring of serial port data.
Burn the code, open the terminal, and the following content will be displayed:
Open the serial port assistant and set the baud rate to 115200, keep other parameters as default, then select the COM port corresponding to the following name:
MacroSilicon USB Serial Ports
Here, when using the serial port assistant for communication, please note that the display should be in character format, the transmission should be in hexadecimal format, and do not select the "Send New Line" option.
It can be seen that the custom RS232 communication class is configured to send data at a timer interval of 1 second. Let's try sending hexadecimal data:
13 22 75 85 65 21 32
As can be seen, all data except the flow control character XOFF (0x13) is displayed on the terminal. Meanwhile, on the serial port assistant software, you can observe that the Raspberry Pi Pico has stopped sending data, and the onboard LED on the Raspberry Pi Pico is lit up:
Resend data: 11 22 33 44 55 66, in which XON (0x11) flow control character indicates that the PC side is ready to receive data:
It can be found that at this point, the RS232 communication of the Raspberry Pi Pico restarts, and the onboard LED turns off at the same time:
Here, we will send a hexadecimal data whose length exceeds the set data buffer:
11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 00
And set in advance that the serial port assistant displays in hexadecimal format:
Then send the data, and you can see that the Raspberry Pi Pico has sent the flow control character XOFF (0x13):
Furthermore, the size of the data received by the Raspberry Pi Pico never exceeds the set buffer size:
 
From the above tests, it can be seen that our custom RS232 software flow control communication class can implement the basic functions of transmission interruption and resumption. Compared with hardware flow control, software flow control is simple and easy to use without requiring extra wiring, but it also has certain drawbacks: it relies on special control characters to manage data transmission, which will occupy part of the transmission bandwidth. Meanwhile, XON (0x11) and XOFF (0x13) are specific control characters; however, if the transmitted data itself contains these characters, they may be misinterpreted as flow control commands, thus triggering erroneous transmission pauses or resumptions.
 
Documents
Comments Write