AD

Friday, 1 September 2017

Soft UART - A UART software implementation for Raspberry Pi


1. Introduction


I am working on a personal project that requires two serial ports on a Raspberry Pi board. I used the embedded serial port plus a USB dongle for a while, but then I started wondering if there was a way of implementing a UART through one of the spare GPIO pins.

I searched the subject for a while and found many people wanting a software-based serial port on a Raspberry Pi. However, I could not find any ready-to-use solution - at least not in the way I wanted.

So I decided to create my own solution. For those who are not interested in the rest this article, here is the link:



2. Research


As I said, I searched for some ready-to-use solution. I found some examples of bit banging, but I wanted a kernel module able to emulate a TTY device. That would enable me to use this device with any application, such as minicom or gpsd.

I found the project RpiSoft-UART, which provides a character device able to transmit and receive data almost in the same way as a UART does. It still does not implement a TTY interface. For example, it does not allow changing the baud rate without reloading the module.

I also found the article Writing a Linux Kernel Module — Part 2: A Character Device. It is an excellent article describing how to implement a character device. It helps to understand the ideas behind the code written for RpiSoft-UART RpiSoft-UART.

Another interesting source of information was chapter TTY Drivers of the book Linux Device Drivers, 3rd Edition by Greg Kroah-Hartman, Alessandro Rubini, Jonathan Corbet. It explains how to implement a TTY device in details. It is outdated, as the TTY module structures have changed since the kernel 3.10. But it is still a good reading.

In the end, I found the project raspicomm-module. It is a kernel module for the RaspiComm extension board for Raspberry Pi. The hardware provides additional serial ports for a Raspberry Pi board, and the kernel module provides the software interfaces to the hardware as a TTY device. That helped to figure out the parts that were missing in the other references.




3. Implementation



In the end of the day, implementing a kernel module is a matter of filling the correct boilerplate - there are certain functions that the kernel expects to find in the module.

I tried to keep the implementation as simple as possible. There are three two important files:
  • module.c: Implements the interface between the module and the kernel (by filling the boilerplate).
  • raspberry_soft_uart.c: Implements the bit banging itself. A high-resolution timer is set according to the desired baud rate. Every time the timer goes off, a bit is written and/or  read to/from the GPIO pins.
  • raspberry_gpio.c: Implements the interface between the module and the GPIO, more or less as WiringPi.

4. Testing


The easiest way of testing it is connecting the selected Soft UART pins to the embedded UART pins. It would be like this:

GPIO 17 (pin 11) --- RXD0 (pin 8)
GPIO 27 (pin 13) --- TXD0 (pin 8)

There are more instructions on how to test it using minicom at 

5. Conclusion


My kernel module seems to work more or less as expected. I noticed that there are some communication errors in the reception, especially when the baud rate is faster than 4800 bps or when the CPU usage is high. The transmission works just fine.

I am not sure of what could be done to improve the reception, but I am open to suggestions.

6. References

6 comments:

  1. Hi Adriano,

    Works great on a Rpi 3 @ 9600 bps.

    I wonder if there is a way to load several modules to have
    more than one soft UART on different pins, like...

    /dev/ttySOFT0
    /dev/ttySOFT1
    ...

    I'mn no C wizard but tried to increase the "#define N_PORTS" in
    module.c to 2 and ls -l /dev/tty* shows two ports but
    when trying to open /dev/ttySOFT1 in minicom the Pi just hangs.

    I'm aware that the Pi have limited resources but since 9600 bps
    works fine even when all 4 cores are under 100% load (tested
    with stress utility) it will probably work fine with more than
    one soft UART if using lower speeds like 300/1200/2400 bps.

    Also wan't to say thanks for sharing your code!

    ReplyDelete
    Replies
    1. Hi Christer,

      It is good to know that you are making good use of the code.

      Unfortunately I haven't developed the module in a way that would enable it to provide more than one UART. The #define you've changed came from another project I used as a starting point.

      It is possible to modify the module to do what you described, but it would require more than just changing some parameters.

      Feel free to contribute to the project if you are interested.

      Kind regards,

      Adriano Marto Reis.

      Delete
    2. I also wanted to implement multiple software UARTs, was thinking of loading the same module multiple times but insmod obviously doesn't work that way. If I want to compile the same module under another name, where shall I start editing?

      Delete
    3. Hi Vicary,

      I have never tried that, and there could be some traps I am not foreseing. But it could be possible to load the module more than once if you build the module changing its name. For example, you could have modules called soft_uart1, soft_uart2, etc.

      You would need to change the name of the module in the Makefile, lines 1, 3, and 15.

      I would recommend you to also change the default GPIO pins in module.c, lines 20 and 23 - each instance of the module need to use different pins.

      Let me know if it works.

      Good luck and kind regards,

      Adriano Marto Reis.

      Delete
  2. Hi Adriano,

    I am trying to port your code on my GL_INET GL-MiFi Gateway.
    https://www.gl-inet.com/products/gl-mifi/

    There is no help regarding cross compilation of this code with Openwrt build system.
    If you have ever done it, it will be of great help for me and will help me reduce my R&D Cycles

    Thanks
    Ashish S

    ReplyDelete
  3. Interesting code!

    To improve reception you could 'oversample' by reading the GPIO multiple times during a single bit time. Furthermore if you find that during a bit time there is an edge you would need to delay a bit to sync your bit readings with the transmitted bits.

    ReplyDelete