Share this

Design of an Embedded Serial Communication Module for μC/OS-II

2026-04-06 08:00:43 · · #1
In embedded applications, the main reason for using an RTOS is to improve system reliability, followed by improving development efficiency and shortening the development cycle. μC/OS-II is a preemptive real-time multitasking kernel used in embedded systems. With appropriate source code modifications, it can be easily ported to microprocessors with different architectures from 8 to 32 bits. However, μC/OS-II is only a real-time kernel; unlike other real-time operating systems (such as embedded Linux), it does not provide users with API function interfaces. Under the μC/OS-II real-time kernel, the access interface for peripherals is not unified and complete, requiring users to perform much work themselves. Serial communication is an important component of microcontroller-based measurement and control systems, and the asynchronous serial port is a relatively simple and representative interrupt-driven peripheral. This article takes the serial port in a microcontroller as an example to introduce the general approach to writing interrupt service routines and peripheral drivers under μC/OS-II. 1. Interrupt Handling in μC/OS-II and Analysis of the Interrupt System of the 51 Series Microcontroller μC/OS-II interrupt service routines (ISRs) are generally written in assembly language. The following are the steps of an interrupt service routine. Save all CPU registers; call OSIntEnter() or directly increment OSIntNesting (a global variable) by 1; execute user code to perform interrupt service; call 0SIntExit(); restore all CPU registers; execute the interrupt return instruction. μC/OS-II provides two ISR interface functions with the kernel: OSIntEnter() and OSIntExit(). OSIntEnter() notifies the μC/OS-II kernel that the interrupt service routine has started. In fact, this function increments a global variable OSIntNesting by 1. This interrupt nesting counter ensures that task scheduling is performed only after all interrupt handling is completed. The other interface function, OSIntExit(), notifies the kernel that the interrupt service has ended. Depending on the situation, the interruption point is returned (which may be a task or a nested interrupt service routine) or task scheduling is performed by the kernel. The user-written ISR must be installed in a specific location so that after an interrupt occurs, the CPU runs the correct service routine based on the corresponding interrupt number. Many real-time operating systems provide API functions for installing and unloading interrupt service routines, but the μC/OS-II kernel does not provide similar functions, requiring users to implement them themselves during CPU porting. These interface functions are dependent on the specific hardware environment; the following section details interrupt handling under the 51 microcontroller. The basic interrupt process of the 51 microcontroller is as follows: The CPU samples the interrupt flag at S5P2 of each machine cycle, and polls the sampled interrupts in the next instruction cycle. If there is an interrupt request, it is processed according to priority. When responding to an interrupt, the corresponding priority activation trigger is first set to the corresponding bit, blocking interrupts of the same or lower priority. Then, according to the interrupt source type, under hardware control, the interrupt address is pushed onto the stack, and the CPU jumps to the corresponding interrupt vector entry unit. Usually, a jump instruction is placed at the entry unit to execute the interrupt service routine. When the interrupt return instruction RETI is executed, the priority activation trigger set during interrupt response is cleared, the protected breakpoint address is popped from the stack, loaded into the program counter PC, and the CPU returns to the original interrupted location to continue program execution. In the porting process, Keil C51 was used as the compilation environment. Keil C5l integrates a C compiler and assembler. Interrupt subroutines are written in assembly language and placed in the OS_CPU_A.ASM assembly file after porting μC/OS-II. Below is the ported interrupt service subroutine code using serial port interrupt as an example. CSEGAT0023H ; Serial port interrupt response entry address LJMPSerialISR ; Transfer to serial port interrupt subroutine entry address RSEG? PR?Serial1SR? OS_CPU_A SerialISR: USINGO CLR EA ; Disable interrupts first to prevent interrupt nesting PUSHALL ; Defined stack macro, used to push the value of the CPU register onto the stack LCALL_?OSIntEnter ; Monitor interrupt nesting LCALL_?Serial ; Serial port interrupt service routine LCALL_?OSintExlt SETBEA POPALL ; Defined stack pop macro, pops the value of the CPU register from the stack RETI 2 Serial Port Driver The author has successfully ported the μC/OS-II kernel on the 5l microcontroller; the porting process will not be discussed here. This section focuses on analyzing the serial port driver development under the μC/OS-II kernel. Due to the mismatch between peripheral processing speed and CPU speed in serial devices, a buffer is needed. When sending data to the serial port, the data is simply written to the buffer, and then retrieved and sent out byte by byte by byte by byte. When receiving data from the serial port, the CPU often needs to process several bytes before processing them, so this pre-received data can be stored in the buffer. In practice, the asynchronous serial port of a microcontroller only has two independent receive and transmit buffer registers (SBUF) with the same address. In practical applications, two buffers need to be allocated from memory: a receive buffer and a transmit buffer. Here, the buffers are defined as a circular queue data structure. The μC/OS-II kernel provides semaphores as a communication and synchronization mechanism, introducing data receive semaphores and data transmit semaphores to synchronize operations at both ends of the buffers. The serial port operation mode is as follows: When a user task wants to write but the buffer is full, it sleeps on the semaphore, allowing the CPU to run other tasks. The sleeping task is woken up when the ISR reads data from the buffer. Similarly, when a user task wants to read but the buffer is empty, it can also sleep on the semaphore, waking up when data arrives from the external device. Since μC/OS-II semaphores provide a timeout mechanism, the serial port also has timeout read/write capabilities. Figure 1 is a schematic diagram of serial port reception with buffers and semaphores. The data reception semaphore is initialized to 0, indicating no data in the circular buffer. After a receive interrupt, the ISR reads the received byte from the UART's receive buffer SBUF (②), places it in the receive buffer (③), and then wakes up the user task's read operation (④, ①) through the receive semaphore. Throughout the process, the variable value recording the current number of bytes in the buffer can be queried; this variable indicates whether the receive buffer is full. The UART receives data and triggers a receive interrupt, but if the buffer is full at this time, the received character is discarded. The size of the buffer should be set reasonably to reduce the possibility of data loss while avoiding waste of storage space. Figure 2 illustrates a serial port transmission diagram with a circular buffer and a timeout semaphore. The initial value of the transmit semaphore is set to the size of the transmit buffer, indicating that the buffer is empty and the transmit interrupt is disabled. When transmitting data, the user task waits on the semaphore (①). If the transmit buffer is not full, the user task writes data into the transmit buffer (②). If the first byte in the transmit buffer is written, the transmit interrupt is enabled (②). Then, the transmit ISR retrieves the earliest written byte from the transmit buffer and outputs it to the UART (④). This operation triggers the next transmit interrupt, and so on until the last byte in the transmit buffer is retrieved, at which point the transmit interrupt is disabled again. While the ISR outputs to the UART, it signals the semaphore (⑤), and the transmit task uses this semaphore count to determine if there is space in the transmit buffer. 3. Design of the Serial Communication Module Each serial port has two circular queue buffers and two semaphores: one to indicate the received byte and the other to indicate the transmitted byte. Each circular buffer has the following four elements: ◇ Stored data (INT8U array); ◇ A counter containing the number of bytes in the circular buffer; ◇ A pointer in the circular buffer to the next byte to be placed; ◇ A pointer in the circular buffer to the next byte to be retrieved. Figure 3 is a flowchart of the data receiving software module. `SerialGetehar()` is used to retrieve received data. If the buffer is empty, the task is suspended. When a byte is received, the task is awakened and receives bytes from the serial port. `SerialPutRxChar()` is used to put the received byte into the buffer. If the receive buffer is full, the byte is discarded. When a byte is inserted into the buffer, `SerialPutRxChar()` notifies the data receive semaphore, causing it to convey the message that data has arrived to all waiting tasks. To prevent application tasks from being suspended, `SceiallsEmPty()` can be called to check if there are bytes in the circular queue. Figure 4 is a flowchart of the data sending module. When data needs to be sent to the serial port, `SerialPurChar()` waits for the semaphore to be initialized to the size of the buffer when initializing the send semaphore. Therefore, when there is no more space in the buffer, SerialPutChar() suspends the task, and the suspended task will resume as soon as the UART sends another byte. SerialGctChar() is called by the interrupt service routine. If there is at least one byte left in the transmit buffer, SerialGetChar() returns the byte sent from the buffer. If the buffer is empty, SerialGetChar() returns Null, which will cause the call to stop further transmit interrupts until data can be sent. 4 Interface Functions for Asynchronous Serial Communication Application tasks can control and access the UART through the following functions: SerialCfgPort(), SerialGetChar(), SerialInit(), SerialIsEmpty(), SerialIsFull(), and SerialPutChar(). SerialCfgPort() is used to establish the characteristics of the serial port. This function must be called before calling other services for the specified port, including determining the baud rate, number of bits, parity, and stop bits. SerialGetChar() causes the application to retrieve data from the circular buffer for receiving data. `SerialInit()` is used to initialize the entire serial port software module and must be called before any other services provided by this module. `SerialInit()` clears the byte count of the ring buffer counter and initializes the IN and OUT pointers of each ring buffer to the beginning of the data storage area. The data receive semaphore is initialized to 0, indicating that there is no data in the ring buffer. The data transfer semaphore is initialized with the size of the transfer buffer, indicating that the buffer is empty. `SerialIsEmpty()` allows the application to determine if any bytes have been received from the serial port. This function allows the task to be avoided from being suspended when there is no data. `SerialIsFull()` allows the application to determine the status of the transfer ring buffer; this function can prevent the task from being suspended when the buffer is full. `SerialPutChar()` allows the application to send data to a serial port. Conclusion This serial communication module fully utilizes the task scheduling function and semaphore mechanism of the real-time kernel. The system software is modular, with enhanced readability, and is easy to modify and port. Its design ideas and methods can be well applied to measurement and control systems in various situations. The system is easy to expand and has certain reference value. This serial communication module has been used as part of a remote control terminal for a railway water supply system. It operates stably and improves the overall system's operating efficiency and real-time performance.
Read next

CATDOLL 128CM Laura

Height: 128cm Weight: 19kg Shoulder Width: 30cm Bust/Waist/Hip: 57/52/63cm Oral Depth: 3-5cm Vaginal Depth: 3-15cm Anal...

Articles 2026-02-22
CATDOLL 123CM LuisaTPE

CATDOLL 123CM LuisaTPE

Articles
2026-02-22
CATDOLL 130CM Sasha

CATDOLL 130CM Sasha

Articles
2026-02-22