Serial Ports: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
m (small grammar error)
m (Bot: Replace main article with main template)
 
(28 intermediate revisions by 20 users not shown)
Line 1: Line 1:
Serial ports are a legacy communications port common on IBM-PC compatible computers. Use of serial ports for connecting peripherals has largely been deprecated in favor of [[USB]] and other modern peripheral interfaces, however it is still commonly used in certain industries for interfacing with industrial hardware such as CNC machines or commercial devices such as POS terminals. Historically it was common for many dial-up modems to be connected via a computer's serial port, and the design of the underlying [[UART]] hardware itself reflects this.
Serial ports are a legacy communications port which has pretty much been succeeded by [[USB]] and other modern communications technology. However, it is much easier to program than USB, and it is still found in a lot of computers (especially older ones such as the ones the financially limited amateur OS writer might use for [[Testing|testing]]). Also, a lot of phone modems (which are still used to access the Internet quite often, even though broadband is very accessible and affordable) connect via the serial interface. Furthermore it is possible to use the serial ports for debbuging, since a lot of emulators allow the redirection of their output into a file.

Serial ports are typically controlled by [[UART]] hardware. This is the hardware chip responsible for encoding and decoding the data sent over the serial interface. Modern serial ports typically implement the [[wikipedia:RS-232|RS-232]] standard, and can use a variety of different connector interfaces. The DE-9 interface is the one most commonly used connector for serial ports in modern systems.

Serial ports are of particular interest to operating-system developers since they are much easier to implement drivers for than USB, and are still commonly found in many x86 systems. It is common for operating-system developers to use a system's serial ports for debugging purposes, since they do not require sophisticated hardware setups and are useful for transmitting information in the early stages of an operating-system's initialization. Many emulators such as [[QEMU]] and [[Bochs]] allow the redirection of serial output to either stdio or a file on the host computer.


== Wires, Pins, Connectors and the like ==
== Wires, Pins, Connectors and the like ==


The Wikipedia page on [[wikipedia:Serial_port|Serial ports]] has a lot of information, and it is summarised here. The serial interface is very simple. There are actually two kinds of serial port: 25-pin and 9-pin. 25-pin ports are not any better, they just have more pins and are bigger. 9-pin is smaller and is used more often though in the past the 25-pin ones were used more often. The 9-pin ones are called DE-9 (or more commonly, DB-9 even though DE-9 is it's technical name) and the 25-pin ones are called DB-25. They plug in to your computer using a female plug (unless your computer is odd and has a female port, in which case your cable will need a male plug). [[wikipedia:D-subminiature|This Wikipedia page]] has more information on the plug used.
The Wikipedia page on [[wikipedia:Serial_port|Serial ports]] has a lot of information, and it is summarised here. The serial interface is very simple. There are actually two kinds of serial port: 25-pin and 9-pin. 25-pin ports are not any better, they just have more pins (most unused) and are bigger. 9-pin is smaller and is used more often though in the past the 25-pin ones were used more often. The 9-pin ones are called DE-9 (or more commonly, DB-9 even though DE-9 is its technical name) and the 25-pin ones are called DB-25. They plug in to your computer using a female plug (unless your computer is odd and has a female port, in which case your cable will need a male plug). [[wikipedia:D-subminiature|This Wikipedia page]] has more information on the plug used.


Both have the same basic types of pins. A DB-25 has most of the pins as ground pins, whereas a DE-9 has only a few ground pins. There is a transmitting pin (for sending information away) and a receiving pin (for getting information). Some serial ports can have a duplex mode--that is, they can send and receive simultaneously. There are a few other pins, used for hardware handshaking. In the past, there was no duplex mode, so if a computer wanted to send something it had to tell the other device or computer that it was about to transmit, using one of the hardware handshaking pins. The other thing would then use another handshaking pin to tell it to send whatever it wanted to send. Today there is duplex mode, but the handshaking pins are still used.
Both have the same basic types of pins. A DB-25 has most of the pins as ground pins or simply unconnected, whereas a DE-9 has only one ground pin. There is a transmitting pin (for sending information away) and a receiving pin (for getting information). Most serial ports run in a duplex mode--that is, they can send and receive simultaneously. There are a few other pins, used for hardware handshaking. In the past, there was no duplex mode, so if a computer wanted to send something it had to tell the other device or computer that it was about to transmit, using one of the hardware handshaking pins. The other device would then use another handshaking pin to tell it to send whatever it wanted to send. Today there is duplex mode, but the handshaking pins are still used.


If you want to connect two computers, you need two things in your cable:
If you want to connect two computers, you need two things in your cable:


# The cable needs to have two female plugs so it can plug into both computers.
# The cable needs to have two female plugs so it can plug into both computers.
# The cable needs to have it's transmit-receive wires and it's handshaking wires switched. This can be done in the cable itself, or as an extension called a [[Null Modem]]
# The cable needs to have its transmit-receive wires and it's handshaking wires switched. This can be done in the cable itself, or as an extension called a [[Null Modem]]


For serial devices, you don't need to setup the cable this way. The receiving end of the device has the wires switched and it has a female port, which means you can plug a male plug into it.
For serial devices, you don't need to setup the cable this way. The receiving end of the device has the wires switched and it has a female port, which means you can plug a male plug into it.
Line 17: Line 21:
During the early stages of kernel development, you might wonder why you would bother writing a serial driver. There are several reasons why you might:
During the early stages of kernel development, you might wonder why you would bother writing a serial driver. There are several reasons why you might:
;[[GDB]] debugging
;[[GDB]] debugging
:You can use the serial port to connect to a host computer, and use the GDB debugger to debug your operating system. This involves writing a stub for GDB within your OS. Helpful information might be found at http://developer.apple.com/documentation/DeveloperTools/gdb/gdb/gdb_18.html.
:You can use the serial port to connect to a host computer, and use the GDB debugger to debug your operating system. This involves writing a stub for GDB within your OS.
;Headless console
;Headless console
:You can operate the computer without a monitor, keyboard or mouse and instead use the serial port as a console using a protocol such as TTY or VT100.
:You can operate the computer without a monitor, keyboard or mouse and instead use the serial port as a console using a protocol such as TTY or VT100.
Line 28: Line 32:
If you want to use the serial port for communications, you first have to initialize it. You tell it how fast your connection speed between the other computer or device will be (this is called the baud rate)--you must have the same speed as the other device or computer is setup to use, or you will have problems. It is probably safer to use the slower speeds unless you need the faster speeds for some reason, for example if you are playing a multi-player game over a serial connection. You also need to setup the parity type and the number of bits in a character. Once again, your computer must be setup with the same values for these things as the other computer or device has, or communication will not work.
If you want to use the serial port for communications, you first have to initialize it. You tell it how fast your connection speed between the other computer or device will be (this is called the baud rate)--you must have the same speed as the other device or computer is setup to use, or you will have problems. It is probably safer to use the slower speeds unless you need the faster speeds for some reason, for example if you are playing a multi-player game over a serial connection. You also need to setup the parity type and the number of bits in a character. Once again, your computer must be setup with the same values for these things as the other computer or device has, or communication will not work.


Once you have setup these things, you still need to setup the interrupt handlers. You can poll the port to see if any new things have arrived, or if it's time to send another character, but this slows things down and will not work very well in most real-time applications or multithreaded environments. In the case of a game, this is not a good idea at all.
Once you have setup these things, you still need to setup the interrupt handlers. You can poll the port to see if any new things have arrived, or if it's time to send another character, but this slows things down and will not work very well in most real-time applications or multi-threaded environments. In the case of a game, this is not a good idea at all.


You use IRQ #4 for COM ports 1 or 3, and IRQ #3 for COM ports 2 or 4 (you can tell which port sent the interrupt when you receive the interrupt). The IRQ handlers check if you are receiving something, and if so they receive the character and handle it somehow, such as placing it into a buffer. They also check if the other side is ready to receive something from you, and if you have something to send, it is sent.
You use IRQ #4 for COM ports 1 or 3, and IRQ #3 for COM ports 2 or 4 (you can tell which port sent the interrupt when you receive the interrupt). The IRQ handlers check if you are receiving something, and if so they receive the character and handle it somehow, such as placing it into a buffer. They also check if the other side is ready to receive something from you, and if you have something to send, it is sent.
Line 35: Line 39:
The addresses for COM ports can vary depending on how they are connected to the machine and how the BIOS is configured. Some BIOS configuration utilities allow you to see and set what these are, so if you in doubt for a test machine, this might be a good place to look to get you started.
The addresses for COM ports can vary depending on how they are connected to the machine and how the BIOS is configured. Some BIOS configuration utilities allow you to see and set what these are, so if you in doubt for a test machine, this might be a good place to look to get you started.


For the most part, the first two COM ports will be at the addresses specified, the addresses for further COM ports is less reliable.
For the most part, the first two COM ports will be at the addresses specified, the addresses for further COM ports are less reliable.
{| {{wikitable}}
{| {{wikitable}}
! COM Port
! COM Port
! IO Port
! IO Port
|-
|-
| COM1 || 3F8h
| COM1 || 0x3F8
|-
|-
| COM2 || 2F8h
| COM2 || 0x2F8
|-
|-
| COM3 || 3E8h
| COM3 || 0x3E8
|-
|-
| COM4 || 2E8h
| COM4 || 0x2E8
|-
| COM5 || 0x5F8
|-
| COM6 || 0x4F8
|-
| COM7 || 0x5E8
|-
| COM8 || 0x4E8
|}
|}


You might be able to find the IO port addresses of the COM ports in the [[Memory Map (x86)#BIOS_Data_Area_.28BDA.29|BIOS Data Area]]; however be warned that this won't work on modern/UEFI systems, can tell you about serial ports that only exist in the chipset (and lack any kind of connector that anything can be plugged into), won't tell you about any additional serial ports (e.g. on expansion cards, etc) that firmware doesn't/can't know about, and will make your OS susceptible to "BIOS quirks/bugs". Because the serial ports have relatively standard IO ports it's far more effective to use manual probing techniques instead; specifically, see if the scratch pad register can store a value, then try the loopback test (that you should use to determine if the serial port is faulty anyway).
You should be able to find the IO port addresses of the COM ports in the [[Memory Map (x86)#BIOS_Data_Area_.28BDA.29|BIOS Data Area]]


Once you have the base address of your COM port, you add an offset value to get to one of the data registers. One of the registers hold what is termed the DLAB or Divisor Latch Access Bit. When this bit is set, offsets 0 and 1 are mapped to the low and high bytes of the Divisor register for setting the baud rate of the port. When this bit is clear, offsets 0 and 1 are mapped to their normal registers. The DLAB bit only affects port offsets 0 and 1, the other offsets ignore this setting.
Once you have the base address of your COM port, you add an offset value to get to one of the data registers. One of the registers hold what is termed the DLAB or Divisor Latch Access Bit. When this bit is set, offsets 0 and 1 are mapped to the low and high bytes of the Divisor register for setting the baud rate of the port. When this bit is clear, offsets 0 and 1 are mapped to their normal registers. The DLAB bit only affects port offsets 0 and 1, the other offsets ignore this setting.
Line 56: Line 68:
! IO Port Offset
! IO Port Offset
! Setting of DLAB
! Setting of DLAB
! I/O Access
! Register mapped to this port
! Register mapped to this port
|-
|-
| +0 || 0 || Data register. Reading this registers read from the Receive buffer. Writing to this register writes to the Transmit buffer.
| +0 || 0 || Read || Receive buffer.
|-
|-
| +1 || 0 || Interrupt Enable Register.
| +0 || 0 || Write || Transmit buffer.
|-
|-
| +1 || 0 || Read/Write || Interrupt Enable Register.
| +0 || 1 || With DLAB set to 1, this is the least significant byte of the divisor value for setting the baud rate.
|-
|-
| +1 || 1 || With DLAB set to 1, this is the most significant byte of the divisor value.
| +0 || 1 || Read/Write || With DLAB set to 1, this is the least significant byte of the divisor value for setting the baud rate.
|-
|-
| +1 || 1 || Read/Write || With DLAB set to 1, this is the most significant byte of the divisor value.
| +2 || - || Interrupt Identification and FIFO control registers
|-
|-
| +2 || - || Read || Interrupt Identification
| +3 || - || Line Control Register. The most significant bit of this register is the DLAB.
|-
|-
| +4 || - || Modem Control Register.
| +2 || - || Write || FIFO control registers
|-
|-
| +5 || - || Line Status Register.
| +3 || - || Read/Write || Line Control Register. The most significant bit of this register is the DLAB.
|-
|-
| +6 || - || Modem Status Register.
| +4 || - || Read/Write || Modem Control Register.
|-
|-
| +7 || - || Scratch Register.
| +5 || - || Read || Line Status Register.
|-
| +6 || - || Read || Modem Status Register.
|-
| +7 || - || Read/Write || Scratch Register.
|}
|}


Line 96: Line 113:
# Send the most significant byte of the divisor value to [PORT + 1].
# Send the most significant byte of the divisor value to [PORT + 1].
# Clear the most significant bit of the Line Control Register.
# Clear the most significant bit of the Line Control Register.

====Line Control Register====
The Line Control register sets the general connection parameters.

{| class="wikitable"
|-
! Bit 7
! Bit 6
! Bits 5-3
! Bit 2
! Bits 1-0
|-
| Divisor Latch Access Bit
| Break Enable Bit
| Parity Bits
| Stop Bits
| Data Bits
|}


====Data Bits====
====Data Bits====
Line 115: Line 150:
|}
|}


====Stop bits====
====Stop Bits====
The serial controller can be configured to send a number of bits after each character of data. These reliable bits can be used to by the controller to verify that the sending and receiving devices are in phase.
The serial controller can be configured to send a number of bits after each character of data. These reliable bits can be used to by the controller to verify that the sending and receiving devices are in phase.


Line 131: Line 166:
|}
|}


====Parity====
====Parity Bits====
The controller can be made to add or expect a parity bit at the end of each character of data transmitted. With this parity bit, if a single bit of data is inverted by interference, a parity error can be raised. The parity type can be NONE, EVEN, ODD, MARK or SPACE.
The controller can be made to add or expect a parity bit at the end of each character of data transmitted. With this parity bit, if a single bit of data is inverted by interference, a parity error can be raised. The parity type can be NONE, EVEN, ODD, MARK or SPACE.


Line 141: Line 176:


To set the port parity, set bits 3, 4 and 5 of the Line Control Register [PORT + 3].
To set the port parity, set bits 3, 4 and 5 of the Line Control Register [PORT + 3].

{| {{wikitable}}
{| {{wikitable}}
! Bit 5
! Bit 5
Line 158: Line 192:
| 1 || 1 || 1 || SPACE
| 1 || 1 || 1 || SPACE
|}
|}

===Interrupt enable register===
To communicate with a serial port in interrupt mode, the interrupt-enable-register (see table above) must be set correctly. To determine which interrupts should be enabled, a value with the following bits (0 = disabled, 1 = enabled) must be written to the interrupt-enable-register:
{| {{wikitable}}
! Bit 7-4
! Bit 3
! Bit 2
! Bit 1
! Bit 0
|-
| Reserved
| Modem Status
| Receiver Line Status
| Transmitter Holding Register Empty
| Received Data Available
|}

===First In First Out Control Register===
The First In / First Out Control Register (FCR) is for controlling the FIFO buffers. Access this register by writing to port offset +2.
{| class="wikitable"
|-
! Bits 7-6
! Bits 5-4
! Bit 3
! Bit 2
! Bit 1
! Bit 0
|-
| Interrupt Trigger Level
| Reserved
| DMA Mode Select
| Clear Transmit FIFO
| Clear Receive FIFO
| Enable FIFO's
|}

====Clear Transmit FIFO and Clear Receive FIFO====
Bit 2 being set clears the Transmit FIFO buffer while Bit 1 being set clears the Receive FIFO buffer. Both bits will set themselves back to 0 after they are done being cleared.

====Interrupt Trigger Level====
The Interrupt Trigger Level is used to configure how much data must be received in the FIFO Receive buffer before triggering a Received Data Available Interrupt.
{| {{wikitable}}
! Bit 7
! Bit 6
! Trigger Level
|-
| 0 || 0 || 1 Byte
|-
| 0 || 1 || 4 Bytes
|-
| 1 || 0 || 8 Bytes
|-
| 1 || 1 || 14 Bytes
|}

===Interrupt Identification Register===
The Interrupt Identification Register (IIR) is for identifying pending interrupts. Access this register by reading from port offset +2.
{| class="wikitable"
|-
! Bits 7-6
! Bits 5-4
! Bit 3
! Bit 2-1
! Bit 0
|-
| FIFO Buffer State
| Reserved
| Timeout Interrupt Pending (UART 16550) or Reserved
| Interrupt State
| Interrupt Pending
|}

====Interrupt State====
After Interrupt Pending is set, the Interrupt State shows the interrupt that has occurred. They have varying levels of priority, with high-value interrupts handled first, and low-value interrupts being handled last.
{| {{wikitable}}
! Bit 2
! Bit 1
! Interrupt
! Priority
|-
| 0 || 0 || Modem Status || 4 (Lowest)
|-
| 0 || 1 || Transmitter Holding Register Empty || 3
|-
| 1 || 0 || Received Data Available || 2
|-
| 1 || 1 || Receiver Line Status || 1 (Highest)
|}

====FIFO Buffer State====
{| {{wikitable}}
! Bit 7
! Bit 6
! State
|-
| 0 || 0 || No FIFO
|-
| 0 || 1 || FIFO Enabled but Unusable
|-
| 1 || 0 || FIFO Enabled
|}

===Modem Control Register===
The Modem Control Register is one half of the hardware handshaking registers.
While most serial devices no longer use hardware handshaking, The lines are still included in all 16550 compatible UARTS.
These can be used as general purpose output ports, or to actually perform handshaking.
By writing to the Modem Control Register, it will set those lines active.
{| {{wikitable}}
! Bit
! Name
! Meaning
|-
| 0 || Data Terminal Ready (DTR) || Controls the Data Terminal Ready Pin
|-
| 1 || Request to Send (RTS) || Controls the Request to Send Pin
|-
| 2 || Out 1 || Controls a hardware pin (OUT1) which is unused in PC implementations
|-
| 3 || Out 2 || Controls a hardware pin (OUT2) which is used to enable the IRQ in PC implementations
|-
| 4 || Loop || Provides a local loopback feature for diagnostic testing of the UART
|-
| 5 || 0 || Unused
|-
| 6 || 0 || Unused
|-
| 7 || 0 || Unused
|}

Most PC serial ports use OUT2 to control a circuit that disconnects (tristates) the IRQ line. This makes it possible for multiple serial ports to share a single IRQ line, as long as only one port is enabled at a time.
Loopback mode is a diagnostic feature. When bit 4 is set to logic 1,
the following occur the transmitter Serial Output (SOUT) is set to the Marking (logic 1) state; the receiver Serial
Input (SIN) is disconnected; the output of the Transmitter Shift Register is ‘‘looped back’’ into the Receiver Shift
Register input; the four MODEM Control inputs (DSR, CTS, RI, and DCD) are disconnected; and the four
MODEM Control outputs (DTR, RTS, OUT 1, and OUT 2) are internally connected to the four MODEM Control
inputs, and the MODEM Control output pins are forced to their inactive state (high). In the loopback mode, data
that is transmitted is immediately received. This feature allows the processor to verify the transmit-and received-
data paths of the UART. In the loopback mode, the receiver and transmitter interrupts are fully operational. Their sources are external to
the part. The MODEM Control Interrupts are also operational, but the interrupts’ sources are now the lower four
bits of the MODEM Control Register instead of the four MODEM Control inputs. The interrupts are still controlled
by the Interrupt Enable Register.

===Line Status Register===
The line status register is useful to check for errors and enable polling.
{| {{wikitable}}
! Bit
! Name
! Meaning
|-
| 0 || Data ready (DR) || Set if there is data that can be read
|-
| 1 || Overrun error (OE) || Set if there has been data lost
|-
| 2 || Parity error (PE) || Set if there was an error in the transmission as detected by parity
|-
| 3 || Framing error (FE) || Set if a stop bit was missing
|-
| 4 || Break indicator (BI) || Set if there is a break in data input
|-
| 5 || Transmitter holding register empty (THRE) || Set if the transmission buffer is empty (i.e. data can be sent)
|-
| 6 || Transmitter empty (TEMT) || Set if the transmitter is not doing anything
|-
| 7 || Impending Error || Set if there is an error with a word in the input buffer
|}

===Modem Status Register===
This register provides the current state of the control lines from a peripheral device.
In addition to this current-state information, four bits of the MODEM Status Register provide change information.
These bits are set to a logic 1 whenever a control input from the MODEM changes state. They are reset to logic
0 whenever the CPU reads the MODEM Status Register
{| {{wikitable}}
! Bit
! Name
! Meaning
|-
| 0 || Delta Clear to Send (DCTS) || Indicates that CTS input has changed state since the last time it was read
|-
| 1 || Delta Data Set Ready (DDSR) || Indicates that DSR input has changed state since the last time it was read
|-
| 2 || Trailing Edge of Ring Indicator (TERI) || Indicates that RI input to the chip has changed from a low to a high state
|-
| 3 || Delta Data Carrier Detect (DDCD) || Indicates that DCD input has changed state since the last time it ware read
|-
| 4 || Clear to Send (CTS) || Inverted CTS Signal
|-
| 5 || Data Set Ready (DSR) || Inverted DSR Signal
|-
| 6 || Ring Indicator (RI) || Inverted RI Signal
|-
| 7 || Data Carrier Detect (DCD) || Inverted DCD Signal
|}

If Bit 4 of the MCR (LOOP bit) is set, the upper 4 bits will mirror the 4 status output lines set in the Modem Control Register.

===Terminals===
{{Main|Terminals}}

Once you can send and receive bytes with confidence, you probably want to connect the serial port to a terminal (or more likely a terminal emulator these days). Those send specific byte sequences when a key is pressed, and can interpret codes to move the cursor on the screen and change color for example.




==Example Code==
==Example Code==
===Initialization===
===Initialization===
<source lang="C">
<syntaxhighlight lang="C">
#define PORT 0x3f8 /* COM1 */
#define PORT 0x3f8 // COM1


void init_serial() {
static int init_serial() {
outb(PORT + 1, 0x00); // Disable all interrupts
outb(PORT + 1, 0x00); // Disable all interrupts
outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor)
outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor)
Line 173: Line 406:
outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold
outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold
outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set
outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set
outb(PORT + 4, 0x1E); // Set in loopback mode, test the serial chip
outb(PORT + 0, 0xAE); // Test serial chip (send byte 0xAE and check if serial returns same byte)

// Check if serial is faulty (i.e: not same byte as sent)
if(inb(PORT + 0) != 0xAE) {
return 1;
}

// If serial is not faulty set it in normal operation mode
// (not-loopback with IRQs enabled and OUT#1 and OUT#2 bits enabled)
outb(PORT + 4, 0x0F);
return 0;
}
}
</syntaxhighlight>
</source>


Notice that the initialization code above writes to [PORT + 1] twice with different values. This is once to write to the Divisor register along with [PORT + 0] and once to write to the Interrupt register as detailed in the previous section. The second write to the Line Control register [PORT + 3] clears the DLAB again as well as setting various other bits.
Notice that the initialization code above writes to [PORT + 1] twice with different values. This is once to write to the Divisor register along with [PORT + 0] and once to write to the Interrupt register as detailed in the previous section. The second write to the Line Control register [PORT + 3] clears the DLAB again as well as setting various other bits.


===Receiving data===
===Receiving data===
<source lang="C">
<syntaxhighlight lang="C">
int serial_received() {
int serial_received() {
return inb(PORT + 5) & 1;
return inb(PORT + 5) & 1;
Line 189: Line 434:
return inb(PORT);
return inb(PORT);
}
}
</syntaxhighlight>
</source>


===Sending data===
===Sending data===


<source lang="C">
<syntaxhighlight lang="C">
int is_transmit_empty() {
int is_transmit_empty() {
return inb(PORT + 5) & 0x20;
return inb(PORT + 5) & 0x20;
Line 203: Line 448:
outb(PORT,a);
outb(PORT,a);
}
}
</syntaxhighlight>
</source>


==Glossary==
==Glossary==


;Baud Rate
;Baud Rate
:is the speed at which the serial line switches between it's two states. This is not equivalent to bps, due to the fact there are start and stop bits. On an 8/N/1 line, 10 baud = 1 byte. Modems are more complex than plain serial lines due to having multiple waveforms, but for the purposes of OSDev this is irrelevant.
:The speed at which the serial line switches between it's two states. This is not equivalent to bps, due to the fact there are start and stop bits. On an 8/N/1 line, 10 baud = 1 byte. Modems are more complex than plain serial lines due to having multiple waveforms, but for the purposes of OSDev this is irrelevant.
:The fastest baud rate a serial port can reliably run at is generally 115200 baud.
;baud rate divisor
;Baud Rate Divisor
:fastest rate a serial port can run, the number 115200.
:The value the is used by the UART to divide its internal clock by in order to get the actual intended baud rate.
;stop bits
;Stop Bits
:the NULL bit(s) sent between each character to synchronize the transmitter and the receiver.
:The NULL bit(s) sent between each character to synchronize the transmitter and the receiver.
;UART
;UART
:for Universal Asynchronous Receiver/Transceiver: the chip that picks a byte a send it bit per bit on the serial line and vice versa.
:For Universal Asynchronous Receiver/Transceiver: the chip that picks a byte a send it bit per bit on the serial line and vice versa.


== Related Links ==
== Related Links ==
Line 223: Line 469:
[[Category:Network Hardware]]
[[Category:Network Hardware]]
[[Category:Common Devices]]
[[Category:Common Devices]]
[[de:Serielle_Schnittstelle]]

Latest revision as of 00:41, 10 June 2024

Serial ports are a legacy communications port common on IBM-PC compatible computers. Use of serial ports for connecting peripherals has largely been deprecated in favor of USB and other modern peripheral interfaces, however it is still commonly used in certain industries for interfacing with industrial hardware such as CNC machines or commercial devices such as POS terminals. Historically it was common for many dial-up modems to be connected via a computer's serial port, and the design of the underlying UART hardware itself reflects this.

Serial ports are typically controlled by UART hardware. This is the hardware chip responsible for encoding and decoding the data sent over the serial interface. Modern serial ports typically implement the RS-232 standard, and can use a variety of different connector interfaces. The DE-9 interface is the one most commonly used connector for serial ports in modern systems.

Serial ports are of particular interest to operating-system developers since they are much easier to implement drivers for than USB, and are still commonly found in many x86 systems. It is common for operating-system developers to use a system's serial ports for debugging purposes, since they do not require sophisticated hardware setups and are useful for transmitting information in the early stages of an operating-system's initialization. Many emulators such as QEMU and Bochs allow the redirection of serial output to either stdio or a file on the host computer.

Wires, Pins, Connectors and the like

The Wikipedia page on Serial ports has a lot of information, and it is summarised here. The serial interface is very simple. There are actually two kinds of serial port: 25-pin and 9-pin. 25-pin ports are not any better, they just have more pins (most unused) and are bigger. 9-pin is smaller and is used more often though in the past the 25-pin ones were used more often. The 9-pin ones are called DE-9 (or more commonly, DB-9 even though DE-9 is its technical name) and the 25-pin ones are called DB-25. They plug in to your computer using a female plug (unless your computer is odd and has a female port, in which case your cable will need a male plug). This Wikipedia page has more information on the plug used.

Both have the same basic types of pins. A DB-25 has most of the pins as ground pins or simply unconnected, whereas a DE-9 has only one ground pin. There is a transmitting pin (for sending information away) and a receiving pin (for getting information). Most serial ports run in a duplex mode--that is, they can send and receive simultaneously. There are a few other pins, used for hardware handshaking. In the past, there was no duplex mode, so if a computer wanted to send something it had to tell the other device or computer that it was about to transmit, using one of the hardware handshaking pins. The other device would then use another handshaking pin to tell it to send whatever it wanted to send. Today there is duplex mode, but the handshaking pins are still used.

If you want to connect two computers, you need two things in your cable:

  1. The cable needs to have two female plugs so it can plug into both computers.
  2. The cable needs to have its transmit-receive wires and it's handshaking wires switched. This can be done in the cable itself, or as an extension called a Null Modem

For serial devices, you don't need to setup the cable this way. The receiving end of the device has the wires switched and it has a female port, which means you can plug a male plug into it.

Why Use a Serial Port?

During the early stages of kernel development, you might wonder why you would bother writing a serial driver. There are several reasons why you might:

GDB debugging
You can use the serial port to connect to a host computer, and use the GDB debugger to debug your operating system. This involves writing a stub for GDB within your OS.
Headless console
You can operate the computer without a monitor, keyboard or mouse and instead use the serial port as a console using a protocol such as TTY or VT100.
External logging
When the system itself is in danger of potentially crashing at times, it's nice to get debugging outputs safe to another computer before the test system triple-faults.
Networking and File transfers
Serial ports are useful for transferring information between systems when other more traditional methods are unavailable.

Programming the Serial Communications Port

If you want to use the serial port for communications, you first have to initialize it. You tell it how fast your connection speed between the other computer or device will be (this is called the baud rate)--you must have the same speed as the other device or computer is setup to use, or you will have problems. It is probably safer to use the slower speeds unless you need the faster speeds for some reason, for example if you are playing a multi-player game over a serial connection. You also need to setup the parity type and the number of bits in a character. Once again, your computer must be setup with the same values for these things as the other computer or device has, or communication will not work.

Once you have setup these things, you still need to setup the interrupt handlers. You can poll the port to see if any new things have arrived, or if it's time to send another character, but this slows things down and will not work very well in most real-time applications or multi-threaded environments. In the case of a game, this is not a good idea at all.

You use IRQ #4 for COM ports 1 or 3, and IRQ #3 for COM ports 2 or 4 (you can tell which port sent the interrupt when you receive the interrupt). The IRQ handlers check if you are receiving something, and if so they receive the character and handle it somehow, such as placing it into a buffer. They also check if the other side is ready to receive something from you, and if you have something to send, it is sent.

Port Addresses

The addresses for COM ports can vary depending on how they are connected to the machine and how the BIOS is configured. Some BIOS configuration utilities allow you to see and set what these are, so if you in doubt for a test machine, this might be a good place to look to get you started.

For the most part, the first two COM ports will be at the addresses specified, the addresses for further COM ports are less reliable.

COM Port IO Port
COM1 0x3F8
COM2 0x2F8
COM3 0x3E8
COM4 0x2E8
COM5 0x5F8
COM6 0x4F8
COM7 0x5E8
COM8 0x4E8

You might be able to find the IO port addresses of the COM ports in the BIOS Data Area; however be warned that this won't work on modern/UEFI systems, can tell you about serial ports that only exist in the chipset (and lack any kind of connector that anything can be plugged into), won't tell you about any additional serial ports (e.g. on expansion cards, etc) that firmware doesn't/can't know about, and will make your OS susceptible to "BIOS quirks/bugs". Because the serial ports have relatively standard IO ports it's far more effective to use manual probing techniques instead; specifically, see if the scratch pad register can store a value, then try the loopback test (that you should use to determine if the serial port is faulty anyway).

Once you have the base address of your COM port, you add an offset value to get to one of the data registers. One of the registers hold what is termed the DLAB or Divisor Latch Access Bit. When this bit is set, offsets 0 and 1 are mapped to the low and high bytes of the Divisor register for setting the baud rate of the port. When this bit is clear, offsets 0 and 1 are mapped to their normal registers. The DLAB bit only affects port offsets 0 and 1, the other offsets ignore this setting.

IO Port Offset Setting of DLAB I/O Access Register mapped to this port
+0 0 Read Receive buffer.
+0 0 Write Transmit buffer.
+1 0 Read/Write Interrupt Enable Register.
+0 1 Read/Write With DLAB set to 1, this is the least significant byte of the divisor value for setting the baud rate.
+1 1 Read/Write With DLAB set to 1, this is the most significant byte of the divisor value.
+2 - Read Interrupt Identification
+2 - Write FIFO control registers
+3 - Read/Write Line Control Register. The most significant bit of this register is the DLAB.
+4 - Read/Write Modem Control Register.
+5 - Read Line Status Register.
+6 - Read Modem Status Register.
+7 - Read/Write Scratch Register.

Line Protocol

The serial data transmitted across the wire can have a number of different parameters set. As a rule, the sending device and the receiving device require the same protocol parameter values written to each serial controller in order for communication to be successful.

These days you could consider 8N1 (8 bits, no parity, one stop bit) pretty much the default.

Baud Rate

The serial controller (UART) has an internal clock which runs at 115200 ticks per second and a clock divisor which is used to control the baud rate. This is exactly the same type of system used by the Programmable Interrupt Timer (PIT).

In order to set the speed of the port, calculate the divisor required for the given baud rate and program that in to the divisor register. For example, a divisor of 1 will give 115200 baud, a divisor of 2 will give 57600 baud, 3 will give 38400 baud, etc.

Do not be tempted to use a divisor of 0 to try to get an infinite baud rate, it won't work. Most serial controllers will generate a unspecified and unpredictable baud rate (and anyway infinite baud would mean infinite transmission errors as they are proportional.)

To set the divisor to the controller:

  1. Set the most significant bit of the Line Control Register. This is the DLAB bit, and allows access to the divisor registers.
  2. Send the least significant byte of the divisor value to [PORT + 0].
  3. Send the most significant byte of the divisor value to [PORT + 1].
  4. Clear the most significant bit of the Line Control Register.

Line Control Register

The Line Control register sets the general connection parameters.

Bit 7 Bit 6 Bits 5-3 Bit 2 Bits 1-0
Divisor Latch Access Bit Break Enable Bit Parity Bits Stop Bits Data Bits

Data Bits

The number of bits in a character is variable. Having fewer bits is, of course, faster, but they store less information. If you are only sending ASCII text, you probably only need 7 bits.

Set this value by writing to the two least significant bits of the Line Control Register [PORT + 3].

Bit 1 Bit 0 Character Length (bits)
0 0 5
0 1 6
1 0 7
1 1 8

Stop Bits

The serial controller can be configured to send a number of bits after each character of data. These reliable bits can be used to by the controller to verify that the sending and receiving devices are in phase.

If the character length is specifically 5 bits, the stop bits can only be set to 1 or 1.5. For other character lengths, the stop bits can only be set to 1 or 2.

To set the number of stop bits, set bit 2 of the Line Control Register [PORT + 3].

Bit 2 Stop bits
0 1
1 1.5 / 2 (depending on character length)

Parity Bits

The controller can be made to add or expect a parity bit at the end of each character of data transmitted. With this parity bit, if a single bit of data is inverted by interference, a parity error can be raised. The parity type can be NONE, EVEN, ODD, MARK or SPACE.

If parity is set to NONE, no parity bit will be added and none will be expected. If one is sent by the transmitter and not expected by the receiver, it will likely cause an error.

If the parity is MARK or SPACE, the parity bit will be expected to be always set to 1 or 0 respectively.

If the parity is set to EVEN or ODD, the controller calculates the accuracy of the parity by adding together the values of all the data bits and the parity bit. If the port is set to have EVEN parity, the result must be even. If it is set to have ODD parity, the result must be odd.

To set the port parity, set bits 3, 4 and 5 of the Line Control Register [PORT + 3].

Bit 5 Bit 4 Bit 3 Parity
- - 0 NONE
0 0 1 ODD
0 1 1 EVEN
1 0 1 MARK
1 1 1 SPACE

Interrupt enable register

To communicate with a serial port in interrupt mode, the interrupt-enable-register (see table above) must be set correctly. To determine which interrupts should be enabled, a value with the following bits (0 = disabled, 1 = enabled) must be written to the interrupt-enable-register:

Bit 7-4 Bit 3 Bit 2 Bit 1 Bit 0
Reserved Modem Status Receiver Line Status Transmitter Holding Register Empty Received Data Available

First In First Out Control Register

The First In / First Out Control Register (FCR) is for controlling the FIFO buffers. Access this register by writing to port offset +2.

Bits 7-6 Bits 5-4 Bit 3 Bit 2 Bit 1 Bit 0
Interrupt Trigger Level Reserved DMA Mode Select Clear Transmit FIFO Clear Receive FIFO Enable FIFO's

Clear Transmit FIFO and Clear Receive FIFO

Bit 2 being set clears the Transmit FIFO buffer while Bit 1 being set clears the Receive FIFO buffer. Both bits will set themselves back to 0 after they are done being cleared.

Interrupt Trigger Level

The Interrupt Trigger Level is used to configure how much data must be received in the FIFO Receive buffer before triggering a Received Data Available Interrupt.

Bit 7 Bit 6 Trigger Level
0 0 1 Byte
0 1 4 Bytes
1 0 8 Bytes
1 1 14 Bytes

Interrupt Identification Register

The Interrupt Identification Register (IIR) is for identifying pending interrupts. Access this register by reading from port offset +2.

Bits 7-6 Bits 5-4 Bit 3 Bit 2-1 Bit 0
FIFO Buffer State Reserved Timeout Interrupt Pending (UART 16550) or Reserved Interrupt State Interrupt Pending

Interrupt State

After Interrupt Pending is set, the Interrupt State shows the interrupt that has occurred. They have varying levels of priority, with high-value interrupts handled first, and low-value interrupts being handled last.

Bit 2 Bit 1 Interrupt Priority
0 0 Modem Status 4 (Lowest)
0 1 Transmitter Holding Register Empty 3
1 0 Received Data Available 2
1 1 Receiver Line Status 1 (Highest)

FIFO Buffer State

Bit 7 Bit 6 State
0 0 No FIFO
0 1 FIFO Enabled but Unusable
1 0 FIFO Enabled

Modem Control Register

The Modem Control Register is one half of the hardware handshaking registers. While most serial devices no longer use hardware handshaking, The lines are still included in all 16550 compatible UARTS. These can be used as general purpose output ports, or to actually perform handshaking. By writing to the Modem Control Register, it will set those lines active.

Bit Name Meaning
0 Data Terminal Ready (DTR) Controls the Data Terminal Ready Pin
1 Request to Send (RTS) Controls the Request to Send Pin
2 Out 1 Controls a hardware pin (OUT1) which is unused in PC implementations
3 Out 2 Controls a hardware pin (OUT2) which is used to enable the IRQ in PC implementations
4 Loop Provides a local loopback feature for diagnostic testing of the UART
5 0 Unused
6 0 Unused
7 0 Unused

Most PC serial ports use OUT2 to control a circuit that disconnects (tristates) the IRQ line. This makes it possible for multiple serial ports to share a single IRQ line, as long as only one port is enabled at a time. Loopback mode is a diagnostic feature. When bit 4 is set to logic 1, the following occur the transmitter Serial Output (SOUT) is set to the Marking (logic 1) state; the receiver Serial Input (SIN) is disconnected; the output of the Transmitter Shift Register is ‘‘looped back’’ into the Receiver Shift Register input; the four MODEM Control inputs (DSR, CTS, RI, and DCD) are disconnected; and the four MODEM Control outputs (DTR, RTS, OUT 1, and OUT 2) are internally connected to the four MODEM Control inputs, and the MODEM Control output pins are forced to their inactive state (high). In the loopback mode, data that is transmitted is immediately received. This feature allows the processor to verify the transmit-and received- data paths of the UART. In the loopback mode, the receiver and transmitter interrupts are fully operational. Their sources are external to the part. The MODEM Control Interrupts are also operational, but the interrupts’ sources are now the lower four bits of the MODEM Control Register instead of the four MODEM Control inputs. The interrupts are still controlled by the Interrupt Enable Register.

Line Status Register

The line status register is useful to check for errors and enable polling.

Bit Name Meaning
0 Data ready (DR) Set if there is data that can be read
1 Overrun error (OE) Set if there has been data lost
2 Parity error (PE) Set if there was an error in the transmission as detected by parity
3 Framing error (FE) Set if a stop bit was missing
4 Break indicator (BI) Set if there is a break in data input
5 Transmitter holding register empty (THRE) Set if the transmission buffer is empty (i.e. data can be sent)
6 Transmitter empty (TEMT) Set if the transmitter is not doing anything
7 Impending Error Set if there is an error with a word in the input buffer

Modem Status Register

This register provides the current state of the control lines from a peripheral device. In addition to this current-state information, four bits of the MODEM Status Register provide change information. These bits are set to a logic 1 whenever a control input from the MODEM changes state. They are reset to logic 0 whenever the CPU reads the MODEM Status Register

Bit Name Meaning
0 Delta Clear to Send (DCTS) Indicates that CTS input has changed state since the last time it was read
1 Delta Data Set Ready (DDSR) Indicates that DSR input has changed state since the last time it was read
2 Trailing Edge of Ring Indicator (TERI) Indicates that RI input to the chip has changed from a low to a high state
3 Delta Data Carrier Detect (DDCD) Indicates that DCD input has changed state since the last time it ware read
4 Clear to Send (CTS) Inverted CTS Signal
5 Data Set Ready (DSR) Inverted DSR Signal
6 Ring Indicator (RI) Inverted RI Signal
7 Data Carrier Detect (DCD) Inverted DCD Signal

If Bit 4 of the MCR (LOOP bit) is set, the upper 4 bits will mirror the 4 status output lines set in the Modem Control Register.

Terminals

Main article: Terminals

Once you can send and receive bytes with confidence, you probably want to connect the serial port to a terminal (or more likely a terminal emulator these days). Those send specific byte sequences when a key is pressed, and can interpret codes to move the cursor on the screen and change color for example.


Example Code

Initialization

#define PORT 0x3f8          // COM1

static int init_serial() {
   outb(PORT + 1, 0x00);    // Disable all interrupts
   outb(PORT + 3, 0x80);    // Enable DLAB (set baud rate divisor)
   outb(PORT + 0, 0x03);    // Set divisor to 3 (lo byte) 38400 baud
   outb(PORT + 1, 0x00);    //                  (hi byte)
   outb(PORT + 3, 0x03);    // 8 bits, no parity, one stop bit
   outb(PORT + 2, 0xC7);    // Enable FIFO, clear them, with 14-byte threshold
   outb(PORT + 4, 0x0B);    // IRQs enabled, RTS/DSR set
   outb(PORT + 4, 0x1E);    // Set in loopback mode, test the serial chip
   outb(PORT + 0, 0xAE);    // Test serial chip (send byte 0xAE and check if serial returns same byte)

   // Check if serial is faulty (i.e: not same byte as sent)
   if(inb(PORT + 0) != 0xAE) {
      return 1;
   }

   // If serial is not faulty set it in normal operation mode
   // (not-loopback with IRQs enabled and OUT#1 and OUT#2 bits enabled)
   outb(PORT + 4, 0x0F);
   return 0;
}

Notice that the initialization code above writes to [PORT + 1] twice with different values. This is once to write to the Divisor register along with [PORT + 0] and once to write to the Interrupt register as detailed in the previous section. The second write to the Line Control register [PORT + 3] clears the DLAB again as well as setting various other bits.

Receiving data

int serial_received() {
   return inb(PORT + 5) & 1;
}

char read_serial() {
   while (serial_received() == 0);

   return inb(PORT);
}

Sending data

int is_transmit_empty() {
   return inb(PORT + 5) & 0x20;
}

void write_serial(char a) {
   while (is_transmit_empty() == 0);

   outb(PORT,a);
}

Glossary

Baud Rate
The speed at which the serial line switches between it's two states. This is not equivalent to bps, due to the fact there are start and stop bits. On an 8/N/1 line, 10 baud = 1 byte. Modems are more complex than plain serial lines due to having multiple waveforms, but for the purposes of OSDev this is irrelevant.
The fastest baud rate a serial port can reliably run at is generally 115200 baud.
Baud Rate Divisor
The value the is used by the UART to divide its internal clock by in order to get the actual intended baud rate.
Stop Bits
The NULL bit(s) sent between each character to synchronize the transmitter and the receiver.
UART
For Universal Asynchronous Receiver/Transceiver: the chip that picks a byte a send it bit per bit on the serial line and vice versa.

Related Links