Bit Bang GPIO UART from Linux Kernel

Introduction

Lately I have had the need to bit bang a UART from inside the Linux kernel over a GPIO.

Unsurprisingly, the code is quite simple but handy to have in the back pocket.

Below, we emulate a 9600 baud UART, for 115200, BIT_TIME_US becomes (1/115200) * 1 000 000 uS.

One thing to note, the call to udelay will grind the CPU for 104uS, this will lead to better latency but overall poor system performance on the core executing the below code. usleep/usleep_range(..) can also be used, but this will add the latency associated to getting scheduled back in which may cause symbol corruption due to framing errors; your call.

Code

#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/delay.h>

#define BIT_TIME_US 104 /* 9600 baud */
#define OUTPUT_GPIO 60

void bitbang_char(char character)
{
        int i;

        /* Start Bit */
        gpio_set_value(OUTPUT_GPIO, 0); 
        udelay(BIT_TIME_US);
        for (i = 0; i < 8; ++i) { gpio_set_value(OUTPUT_GPIO, (character >> i) & 1); 
                udelay(BIT_TIME_US);
        }   
        /* Stop Bit */
        gpio_set_value(OUTPUT_GPIO, 1); 
        udelay(BIT_TIME_US);
}

void bitbang_string(const char* string)
{
        int i;

        for (i = 0; i < strlen(string) + 1; ++i)
                bitbang_char(string[i]);
}

static int __init drive_uart(void)
{
        int r = 0;

        r = gpio_is_valid(OUTPUT_GPIO);
        if (r)
                goto error;
        r = gpio_request_one(OUTPUT_GPIO, 
                             GPIOF_OUT_INIT_HIGH, "BB_UART");
        if (r)
                goto error;
        bitbang_string("Hello World\n");
error:
      return r;

}

Performance

Keep in mind, for a production scenario, this approach is questionable. On a beagle bone black, single core, at 9600 baud I was getting about 1-5% bit errors.

I managed to resolve this by switching the CPU governor into performance mode from ondemand.

echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 

In “ondemand” mode, the CPU stays roughly at a frequency of 300MHz, it likely fails to ramp up the frequency fast enough to avoid a bit framing error.

Changing the governor to “performance” sets the CPU frequency to 1GHz which gives much better performance, with a framing error of 0.01%.

Leave a Reply