This article is published by rT-Thread forum user 123, original text: https://club.rt-thread.org/as… The serial port at the application layer is faulty

When the serial port is used at the application layer, you need to specify the mode (polling, interrupt, or DMA) to open the serial port before performing the operation on the serial port. This part of the content has been mentioned in the first chapter, here is mainly used to summarize some problems encountered when using serial ports.

Default rules for serial port registration

We all know that serial port devices need to be registered before they can be accessed through the RT_device_open API, so what are the default rules for serial port registration? Look at the serial driver registration code as follows:

int rt_hw_usart_init(void)

{

rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct stm32_uart); struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; rt_err_t result = 0; stm32_uart_get_dma_config(); /* (1) */ for (int I = 0; i < obj_num; i++) { /* init UART object */ uart_obj[i].config = &uart_config[i]; uart_obj[i].serial.ops = &stm32_uart_ops; uart_obj[i].serial.config = config; /* (2) */ * register UART device */ result = rt_hw_serial_register(&uart_obj[I]. Serial, uart_obj[I]. Config ->name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_INT_TX | uart_obj[i].uart_dma_flag , NULL); /* (3) */ assert (result == RT_EOK); } return result;

}

Point (1) : get the DMA configuration for the serial port. When the serial port is configured to support DMA send or DMA receive, the mode configuration of the serial port is recorded in this function. The serial port is then dma configured when the serial port device is initialized.

Point (2) : Set default parameters, such as baud rate, stop bit, and parity check. The default parameter RT_SERIAL_CONFIG_DEFAULT is selected here. This parameter is defined in the serial.h file. The RT_SERIAL_RB_BUFSZ parameter sets the size of the receive buffer for the serial port. The default value is 64 bytes. Note that the RT_SERIAL_RB_BUFSZ parameter does not set the send buffer.

/ Default config for serial_configure structure /

define RT_SERIAL_CONFIG_DEFAULT \

{\

BAUD_RATE_115200, /* 115200 bits/s */  \
DATA_BITS_8,      /* 8 databits */     \
STOP_BITS_1,      /* 1 stopbit */      \
PARITY_NONE,      /* No parity  */     \
BIT_ORDER_LSB,    /* LSB first sent */ \
NRZ_NORMAL,       /* Normal mode */    \
RT_SERIAL_RB_BUFSZ, /* Buffer size */  \
0                                      \

}

Point (3) : When registering a serial port device, you can see that interrupt send and receive support is added by default, and because the serial port implicitly supports polling send and receive, you only need to configure DMA-related, which is uart_obj[I]. Uart_dma_flag. conclusion

Default rules for serial port registration:

  1. The serial port parameters are configured according to RT_SERIAL_CONFIG_DEFAULT. The size of the serial port receive buffer is determined by RT_SERIAL_RB_BUFSZ. The default size is 64 bytes.
  2. By default, a serial port supports interrupt and polling modes. Default rules for opening a serial port:

When the serial port is open, use rt_device_open(), where the oflags parameter supports the following values (or can support multiple values) :

define RT_DEVICE_FLAG_STREAM 0x040 /Flow pattern/

/ Receiving mode parameter /

define RT_DEVICE_FLAG_INT_RX 0x100 /Interrupt receive mode/

define RT_DEVICE_FLAG_DMA_RX 0x200 /DMA receive mode/

/ Send mode parameter /

define RT_DEVICE_FLAG_INT_TX 0x400 /Interrupt sending mode/

define RT_DEVICE_FLAG_DMA_TX 0x800 /DMA send mode/

The serial port data can be received or sent in three modes: interrupt mode, polling mode, and DMA mode. When in use, these three modes can only be used if the oflags on the serial port does not specify interrupt mode or DMA mode, then the polling mode is used by default. conclusion

Default rules for opening a serial port:

If the open oflags parameter for the serial port does not specify interrupt mode or DMA mode, the polling mode is used by default. The default rules for serial port read and write are as follows: Read data from the serial port first

We generally choose serial interrupt receive or serial DMA receive (as if there is no polling dead serial data scenario, I will not discuss this polling read mode), here is a more uniform, either way, is a non-blocking receive mode. That is, we might pass through a semaphore or message queue that wakes up the current thread to read the data when it receives it. Here we refer to the example of the document center of a serial port: Interrupt receive and DMA receive. The two demos, one using a semaphore and the other using a message queue, complete the reading of serial port data.

Therefore, the serial port read interface receives data in a non-blocking manner. When data cannot be read, the current thread is suspended to avoid wasting CPU resources. Talk about serial port write data

Serial port data is written in three modes: polling, interrupt, and DMA.

This mode of polling data is often used in scenarios such as the FinSH output, or rt_kprintf output, described in the previous chapter. Therefore, it is obvious that the write data interface in polling mode is done in a blocking manner.

Key: a serial port interrupt mode, this mode is actually has some problems, our ideal of the interrupt signal, is to open the first send interrupt is enabled, then sent the data inside the interrupt service function, such as data sending air break again send the next byte of data, in turn send the data to complete a single-byte cycle. How is the interrupt sending of serial port V1 implemented? Let’s look at the code:

rt_inline int _serial_int_tx(struct rt_serial_device serial, const rt_uint8_t data, int length)

{

int size;
size = length;
while (length)
{
    /*
     * to be polite with serial console add a line feed
     * to the carriage return character
     */
    if (*data == '\n' && (serial->parent.open_flag & RT_DEVICE_FLAG_STREAM))
    {
        if (serial->ops->putc(serial, '\r') == -1)
        {
            rt_completion_wait(&(tx->completion), RT_WAITING_FOREVER);
            continue;
        }
    }
    if (serial->ops->putc(serial, *(char*)data) == -1)
    {
        rt_completion_wait(&(tx->completion), RT_WAITING_FOREVER);
        continue;
    }
    data ++; length --;
}

return size - length;

}

Serial ->ops-> puTC = stm32_putc

static int stm32_putc(struct rt_serial_device *serial, char c)

{

struct stm32_uart *uart; RT_ASSERT(serial ! = RT_NULL); uart = rt_container_of(serial, struct stm32_uart, serial); UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_TC);

if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32WL) || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32F0) \

|| defined(SOC_SERIES_STM32L0) || defined(SOC_SERIES_STM32G0) || defined(SOC_SERIES_STM32H7) \
|| defined(SOC_SERIES_STM32G4) || defined(SOC_SERIES_STM32MP1) || defined(SOC_SERIES_STM32WB)
uart->handle.Instance->TDR = c;

else

uart->handle.Instance->DR = c;

endif

while (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TC) == RESET);
return 1;

}

In this function, the data is sent to the TDR data send register, and then while(1) is dead, etc., until the data is sent. So the serial port interrupt sending mode, in fact, is the same as the serial port polling sending mode, and does not use the interrupt sending function.

Finally, the serial DMA send mode. From the above we know that the serial port send polling mode and interrupt mode, in fact, are blocking send mode, this DMA send mode is not so. (stM32_dna_transmit) (stM32_dna_transmit)

static rt_size_t stm32_dma_transmit(struct rt_serial_device serial, rt_uint8_t buf, rt_size_t size, int direction)

{

if (RT_SERIAL_DMA_TX == direction)
{
    if (HAL_UART_Transmit_DMA(&uart->handle, buf, size) == HAL_OK)
    {
        return size;
    }
}
return 0;

}

Here is the call HAL_UART_Transmit_DMA function, interested can track about this function again, here I just say the conclusion, this function is to buf data directly to the sender, and then returned directly, that is to say, when the function returns, send complete data is not actually, this means that, The serial port DMA send mode is actually a non-blocking send mode.

So what are the problems?

The first is that the pattern is not uniform enough. The other two patterns were blocking send, and suddenly it becomes non-blocking send, which makes it impossible for the user-written upper-layer application to behave consistently if the pattern changes.

The second is data clutter, which works fine when the application is in polling mode and then occurs when the application is switched to DMA mode. This is something I mentioned in chapter 1 in the DMA sending section,

dma.png

It said, “The contents of this buffer have been accidentally modified.” Why did it accidentally change? Because the sending interface returned and the data was not sent. Because the sending interface is passing the buffer pointer, so when changing the content of the data, the DMA sent address will not change, so it is equivalent to directly modify the content of the data sent by DMA, resulting in data sending error, packet loss and other problems. conclusion

Serial port read interface: Receives data in a non-blocking manner.

Serial port write data interface: Interrupt mode is essentially a polling mode, does not take full advantage of interrupt; Interrupt and polling are both blocking send modes, while DMA mode is a non-blocking send mode. If not used properly, sending data will be prone to errors, packet loss and other problems. other

Finally, some other questions:

When the user uses the serial port, sometimes the development board does not do anti-interference protection measures, so it must be noted that the serial port pins need to be set to pull up mode. Sometimes, the interruption behavior of the serial port is damaged due to error interference, and the serial port data cannot work properly. In order to verify this situation, the serial port error flag can also be used to determine whether there is such a situation. The following is a test code, interested can test, when the serial port is floating and the serial port is pulled up when the anti-interference ability. This test code can be placed directly in the uart_isr of drv_usart.c: Related ISSUE static void uart_isr(struct rt_serial_device *serial) {struct stm32_uart *uart; RT_ASSERT(serial ! = RT_NULL); uart = rt_container_of(serial, struct stm32_uart, serial); if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_ORE) ! = RESET) { LOG_E("(%s) serial device Overrun error!" , serial->parent.parent.name); __HAL_UART_CLEAR_OREFLAG(&uart->handle); } if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_NE) ! = RESET) { LOG_E("(%s) serial device Noise error!" , serial->parent.parent.name); __HAL_UART_CLEAR_NEFLAG(&uart->handle); } if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_FE) ! = RESET) { LOG_E("(%s) serial device Framing error!" , serial->parent.parent.name); __HAL_UART_CLEAR_FEFLAG(&uart->handle); } if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_PE) ! = RESET) { LOG_E("(%s) serial device Parity error!" , serial->parent.parent.name); __HAL_UART_CLEAR_PEFLAG(&uart->handle); } if ((__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_RXNE) ! = RESET) && (__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_RXNE) ! = RESET)) { ... . Since the serial port V1 version has these problems, then how to fix? Yes, since the name of serial port V1, it must be a serial port V2, serial port V2 to solve the above from some problems, making the user more clear (serial port V2 version is written by myself). At present, the introduction of serial port V2 version is less, only the documentation center [UART device V2 version], also I wrote the use of the tutorial. The following plan is to sort out and summarize more documents of serial port V2, including the principle analysis of serial port V2, the comparison between serial port V2 version and V1 version, and the adaptation guide of serial port V2 version, so as to facilitate the adaptation and common use of everyone, combine the strength of everyone, and give play to the spirit of open source.

More articles: RTT Serial Port V1 Use analysis and Troubleshooting Guide (1) RTT Serial Port V1 use Analysis and Troubleshooting Guide (2) RTT Serial Port V1 Use Analysis and Troubleshooting Guide (3) RTT Serial Port V1 Use Analysis and Troubleshooting Guide (3)