BBBCSIO

Serial Port Example Code

 

About the Serial Port Class

The Serial Port class writes to and reads from the one of the six UART On Chip Peripheral (OCP) devices integrated into the Beaglebone Black CPU (although only four of those devices are usable). In the Linux operating system there are two ways of accessing the UART OCP devices; the first, called TTY, is the standard and approved Linux technique. TTY is a device driver and if the existence of a serial port is detected during boot time, the device driver interface will be exposed as a file in the /dev directory of the Beaglebone Black file system. The serial ports will only be detected if that port is appropriately configured in the Device Tree.

The second type of access is called Memory Mapped access and this treats the Beaglebone Black's RAM memory as if it were a file. The UART is manipulated as if it were a bit at an offset in a virtual file. The BBBCSIO library does not, at this time, provide a class which utilizes Memory Mapped access to a serial port - the file system based TTY class named SerialPortFS is the only one available.

There are four usable serial ports on a Beaglebone Black. If these ports have been correctly configured in the Device Tree, the TTY device driver for that port will be accessible via a file in the /dev directory. This file will have names in the format ttyO<Serial Port> where <Serial Port> is the number of the serial port. Thus the file /dev/ttyO0 is the interface for the TTY device driver on serial port 0. If you do not see the ttyO* file in the /dev directory this means that the serial device is not correctly configured in the Device Tree. By the way, these files are the sort of files you can open and read and write text from (like you can do with the SYSFS GPIO subsystem) in order to read and write data to the serial port. A command like ...

cat /dev/tty04
... will continuously output all received data from the port. However reading and writing in this way is not too useful as you cannot easily set any of the serial port parameters (baudrate, parity etc.). In practice, the ttyO files best treated as true device files and if you want them to do anything you have to use an ioctl() call.

Warnings - READ THIS!!!

The pins (including the serial port pins) on the Beaglebone Black are 3.3Volt. You CANNOT connect them up to a 5V serial device without level shifting the voltage or you will burn out the BBB. You DEFINITELY CANNOT connect them to the +/- 18V of a true RS232 serial port (on the back of a PC) because your BBB will instantly die.

The serial port used in the examples below is ttyO4 - this must be configured in the Device Tree in order for it to be available for use. Typically this is done using a pre-prepared standard Device Tree Overlay. An example of this is below

echo BB-UART4 > /sys/devices/bone_capemgr.9/slots

or, if you wish to configure serial port 1, you could use...

echo BB-UART1 > /sys/devices/bone_capemgr.9/slots

The armhf Linux running on the Beaglebone Black is not a real-time operating system. However, an OCP peripheral such as the serial port has its own internal clock and so, once the information is given to the port, it will be transmitted and received at the configured port speed with no interruptions. However, transfers made via multiple calls to the serial port can, and will, have variable gaps between transmissions as the process is pre-emptively swapped in and out by the kernel.

An Example of the Serial Port Usage

The code below illustrates how to use the SerialPortFS class to read and write from a serial device (serial port 4 in this case). See the comments in the code below for a simple method to use the UART1 to test out the connection mechanism.

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Demonstrates the use of a Serial ports. Reading and writing both
        /// binary data (in a byte array) and strings.
        /// 
        /// The basic function is to open up the specified serial port
        /// and to set it to a baud rate of 115200, 1 stop, no parity etc.
        /// Then a single character and a CRLF combination will be transmitted.
        /// Once that is done, a loop is entered which counts (but does not read)
        /// received characters and transmits informational text regarding the 
        /// number of pending bytes back out the serial port.
        /// 
        /// When the user presses a key, the first four received bytes are read and 
        /// output as hex values and then the remainder are read and output as a string.
        /// 
        /// MEGA IMPORTANT NOTE: The pins (including the serial port pins) on the 
        ///   Beaglebone Black are 3.3Volt. You CANNOT connect them up to a 5V
        ///   serial device without level shifting the voltage or you will burn out
        ///   the BBB. You DEFINITELY CANNOT connect them to the +/- 18V of a true
        ///   serial port (on the back of a PC) because your BBB will instantly 
        ///   die.
        /// 
        /// NOTE: 
        ///   Make sure you set up the serial ports. You can do this using device
        ///   tree overlays like:
        ///     echo BB-UART4 > /sys/devices/bone_capemgr.9/slots
        ///     echo BB-UART1 > /sys/devices/bone_capemgr.9/slots
        ///   you MUST make sure that the pins the UARTS use are not in use by other
        ///   devices
        /// 
        /// SIMPLE Test Harness
        ///   On my BBB I set up two UARTS (UART1 and UART4) using device tree calls
        ///     echo BB-UART4 > /sys/devices/bone_capemgr.9/slots
        ///     echo BB-UART1 > /sys/devices/bone_capemgr.9/slots
        /// The TX and RX pins of these terminals are cross connected TX1->RX4 and 
        /// TX4->RX1 so they can talk to one another. UART 1 runs a terminal program
        /// called minicom which can be started with a call like
        ///     minicom -b 115200 -o -D /dev/ttyO1
        /// UART 4 runs the code below which is called like
        ///     SerialPortFSTest(SerialPortEnum.UART_4);
        /// 
        /// Because the two UARTS are both on the BBB we do not have to worry about
        /// level shifting the voltages and can easily see the input and output
        /// going back and forth.
        /// 
        /// </summary>
        /// <param name="serialID">The serial portID</param>
        /// <history>
        ///    31 Aug 15  Cynic - Originally written
        /// </history>
        public void SerialPortFSTest(SerialPortEnum serialID)
        {
            // byte convByte;
            int bufLen = 4;
            byte[] txByteBuf = new byte[bufLen];
            byte[] rxByteBuf = new byte[bufLen];

            // open the port
            SerialPortFS serialPort = new SerialPortFS(serialID, SerialPortOpenModeEnum.OPEN_NONBLOCK);
            if (serialPort.PortIsOpen == false)
            {
                throw new Exception("ssHandle == null");
            }

            // lets flush the queues - just to show we can
            serialPort.Flush();
 
            // Dump the current settings
            Console.WriteLine("");
            Console.WriteLine("Previous Settings on " + serialID.ToString());
            Console.WriteLine("BaudRate=" + serialPort.BaudRate.ToString());
            Console.WriteLine("BitLength=" + serialPort.BitLength.ToString());
            Console.WriteLine("Parity=" + serialPort.Parity.ToString());
            Console.WriteLine("StopBits=" + serialPort.StopBits.ToString());

            // set some new settings
            serialPort.BaudRate = SerialPortBaudRateEnum.BAUDRATE_115200;
            serialPort.BitLength = SerialPortBitLengthEnum.BITLENGTH_8;
            serialPort.Parity = SerialPortParityEnum.PARITY_NONE;
            serialPort.StopBits = SerialPortStopBitsEnum.STOPBITS_ONE;

            // dump the current settings
            Console.WriteLine("");
            Console.WriteLine("Current Settings on " + serialID.ToString());
            Console.WriteLine("BaudRate=" + serialPort.BaudRate.ToString());
            Console.WriteLine("BitLength=" + serialPort.BitLength.ToString());
            Console.WriteLine("Parity=" + serialPort.Parity.ToString());
            Console.WriteLine("StopBits=" + serialPort.StopBits.ToString());

            // Write a character to the UART
            txByteBuf[0] = 0x58; // an 'X'
            txByteBuf[1] = 0x0a; // CR
            txByteBuf[2] = 0x0d; // LF
            serialPort.Write(txByteBuf, 3);

            Console.WriteLine("");
            Console.WriteLine("If you now send some data to the port, then this program ");
            Console.WriteLine("will transmit text back indicating the number of bytes pending");
            Console.WriteLine("Press any key when done...");

            /* You can directly set the termios structure by doing 
             * things like the code below. The various termios 
             * bit flags and definitions are all located in the
             * SerialPortFS class
             */
            // TermiosStruct xfer = serialPort.GetTermiosState();
            // serialPort.DumpTermiosStruct(xfer);
            // set the two stop bits flag
            // xfer.c_cflag |= (SerialPortFS.CSTOPB);
            // serialPort.SetTermiosState(ref xfer);
            // xfer = serialPort.GetTermiosState();
            // serialPort.DumpTermiosStruct(xfer);

            int numBytes = 0;
            while (Console.KeyAvailable == false)
            {
                Thread.Sleep(1000);
                // find out how much is waiting
                numBytes = serialPort.BytesInRxBuffer;
                // tell the other serial port how much we have
                serialPort.Write(numBytes.ToString()+" Bytes are pending.\r\n");
            }

            // the user has now pressed a key on the console
            Console.WriteLine("");
            Console.WriteLine("Now processing the received data..");

            // find out how much is waiting
            numBytes = serialPort.BytesInRxBuffer;

            // read the data into our limited size rxByteBuf, note we specify the 
            // rxByteBuf.Length so we do not over fill
            int numRead = serialPort.ReadByteArray(rxByteBuf, rxByteBuf.Length);

            // we have the data in the rxByteBuf - display it as hex values
            Console.Write("Received " + serialPort.BytesInRxBuffer.ToString() + " bytes, Displaying "+numRead.ToString() +" as hex values: ");
            for (int i = 0; i < numRead; i++)
            {
                Console.Write("0x" + rxByteBuf[i].ToString("X2") +" ");
            }
            Console.WriteLine("");

            // there may be more data there than was in our read with ByteArray call.
            // As an example, we read and display the rest as a string
            Console.WriteLine("Now reading and displaying the remaining data as a string...");
            Console.WriteLine("SerialData->"+ serialPort.ReadString(100) + "<-");
            Console.WriteLine("");

            // close the port
            serialPort.ClosePort();
            serialPort.Dispose();
        }
    
In the above code, the serialID is passed in when the function was called. This value is a member of the SerialPortEnum class which lists all possible serial ports which can be present on the Beaglebone Black. The above code is called via a line which looks like:
SerialPortFSTest(SerialPortEnum.UART_4);