15.1 概述
UART 是一种用于串行通信的硬件协议,广泛应用于计算机和外围设备之间的数据传输。低速时它通过两个数据线(TX 和 RX)进行通信。
UART通信包括如下:
• 数据传输:UART 通过串行方式逐位传输数据,每次传输一个字节(8位)。
• 波特率:传输速度由波特率决定,常见的波特率有 9600、115200 等。
• 起始位和停止位:每个数据帧以一个起始位开始,以一个或多个停止位结束。
详细的UART协议, 请参考第14章。
15.2 UART 驱动
在很多SoC芯片里,基本上都包含UART硬件, 有的甚至包含好几个UART单元, Rockchip SoC芯片(比如rk3588/rk3576/rk3568)也不例外, 都有UART单元。 这些固有的UART功能, 只需要在DTS里配置, SDK里把UART驱动编译进kernel就能工作。
实际应用场合千奇百怪, 有的项目需要数量众多的UART单元, 这里我们基于普通的GPIO来模拟UART功能, 以便满足各种各样的项目需求。 使用GPIO模拟UART, 也称之为bitbang UART, 那让我们开始设计吧。
15.2.1 UART 需求
1). 首先需要在/dev下面有一个设备节点, 比如/dev/bitbang_uart, 如下:
neardi@LPA3588:~/drivers/uart$ ls /dev/bitbang_uart -l
crw------- 1 root root 511, 0 Sep 12 10:14 /dev/bitbang_uart
2). 支持应用层更改波特率, 驱动里实现IOCTL功能。
3). 支持应用层read/write功能。因此在驱动里, 接收使用一个circle buffer来实现。
15.2.2 DTS配置
此配置是基于Neardi RK3588开发板 来设置。 首先从定义可以看见, la2-32, [la4-36是空闲的(接口定义) 在此我们作为UART的TX/RX 另外在kernel里创建一个线程使用轮询的方式来接收UART的RX数据。 在DTS如下配置:
/ {
demo_uart: demo_uart@0 {
compatible = "neardi,bitbang_uart";
tx-gpios = <&gpio1 RK_PA2 GPIO_ACTIVE_HIGH>;
rx-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_HIGH>;
// interrupts = <GIC_SPI 390 IRQ_TYPE_EDGE_FALLING>;
pinctrl-names = "default";
pinctrl-0 = <&demo_uart_pin>;
};
};
&pinctrl {
demo_uart {
demo_uart_pin: demo_uart_pin {
rockchip,pins = 1 RK_PA2 RK_FUNC_GPIO &pcfg_pull_none,
1 RK_PA4 RK_FUNC_GPIO &pcfg_pull_none;
};
};
};
UART TX/RX使用(GPIO1 RK_PA2)/(GPIO1 RK_PA4), 配置成GPIO功能(RK_FUNC_GPIO), 驱动强度默认是pcfg_pull_none。
15.2.3 初始化
使用Linux Kernel平台驱动, 方便与DTS相结合。
1.) 定义一些全局变量及宏定义, 比如波特率:
#define DEVICE_NAME "bitbang_uart"
#define CLASS_NAME "bitbang_uart_class"
#define DEFAULT_BAUD_RATE 19200
#define BIT_DURATION(baud_rate) (1000000 / (baud_rate)) // Duration of each bit in microseconds
#define BUFFER_SIZE 256
#define IOCTL_SET_BAUD_RATE 0
#define IOCTL_CLEAR_BUFFER 1
2.) UART驱动的注册及读取GPIO pin信息, 如下:
static int uart_probe(struct platform_device *pdev)
{
int ret;
struct device_node *np = pdev->dev.of_node;
gpio_tx = of_get_named_gpio(np, "tx-gpios", 0);
if (!gpio_is_valid(gpio_tx))
{
pr_err("Invalid TX GPIO\n");
return -EINVAL;
}
gpio_rx = of_get_named_gpio(np, "rx-gpios", 0);
if (!gpio_is_valid(gpio_rx))
{
pr_err("Invalid RX GPIO\n");
return -EINVAL;
}
// request GPIO pin
ret = gpio_request(gpio_tx, "uart_bitbang_tx");
if (ret)
{
pr_err("Failed to request uart_bitbang_tx\n");
return ret;
}
ret = gpio_request(gpio_rx, "uart_bitbang_rx");
if (ret)
{
pr_err("Failed to request uart_bitbang_rx\n");
gpio_free(gpio_tx);
return ret;
}
// set GPIO direction
gpio_direction_output(gpio_tx, 1);
gpio_direction_input(gpio_rx);
// register character device.
major_number = register_chrdev(0, DEVICE_NAME, &uart_fops);
if (major_number < 0)
{
pr_err("Failed to register char device\n");
gpio_free(gpio_tx);
gpio_free(gpio_rx);
return major_number;
}
// create device class.
uart_bitbang_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(uart_bitbang_class))
{
pr_err("Failed to create class device\n");
unregister_chrdev(major_number, DEVICE_NAME);
gpio_free(gpio_tx);
gpio_free(gpio_rx);
return PTR_ERR(uart_bitbang_class);
}
// create device node.
uart_device = device_create(uart_bitbang_class, NULL, MKDEV(major_number, 0), NULL, DEVICE_NAME);
if (IS_ERR(uart_device))
{
pr_err("Failed to create device\n");
class_destroy(uart_bitbang_class);
unregister_chrdev(major_number, DEVICE_NAME);
gpio_free(gpio_tx);
gpio_free(gpio_rx);
return PTR_ERR(uart_device);
}
pr_info("UART BITBANG driver initialized\n");
return 0;
}
static int uart_remove(struct platform_device *pdev)
{
uart_thread_running = false;
pr_info("Device node destroyed\n");
device_destroy(uart_bitbang_class, MKDEV(major_number, 0));
pr_info("Class unregistered\n");
class_unregister(uart_bitbang_class);
pr_info("Class destroyed\n");
class_destroy(uart_bitbang_class);
pr_info("Char device unregistered\n");
unregister_chrdev(major_number, DEVICE_NAME);
gpio_free(gpio_tx);
gpio_free(gpio_rx);
pr_info("UART BITBANG driver exited\n");
return 0;
}
static const struct of_device_id uart_of_match[] = {
{
.compatible = "neardi,bitbang_uart",
},
{/* sentinel */}};
MODULE_DEVICE_TABLE(of, uart_of_match);
static struct platform_driver uart_driver = {
.probe = uart_probe,
.remove = uart_remove,
.driver = {
.name = "bitbang_uart",
.of_match_table = uart_of_match,
},
};
module_platform_driver(uart_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("UART GPIO Bitbang Driver");
MODULE_AUTHOR("Linx Zhang");
初始化成功后, 可以在系统里看见GPIO状态, 如下:
root@LPA3588:~# cat /sys/kernel/debug/gpio
gpiochip1: GPIOs 32-63, parent: platform/fec20000.gpio, gpio1:
gpio-34 ( |uart_bitbang_tx ) out hi
gpio-36 ( |uart_bitbang_rx ) in hi
gpio-40 ( |gpio_export ) out lo
gpio-41 ( |vcc-mipidphy1-regula) out lo
gpio-42 ( |vcc-mipidphy0-regula) out lo
gpio-44 ( |reset ) out hi
gpio-52 ( |hp-det ) in hi ACTIVE LOW
gpio-54 ( |work1 ) out hi ACTIVE LOW
gpio-58 ( |vcc5v0_host_pwren ) out hi
gpio-61 ( |hdmirx-det ) in hi ACTIVE LOW
gpio-62 ( |enable ) out hi
gpio-63 ( |reset ) out hi
15.2.4 UART 发送数据
UART发送数据代码, 相对简单一点, 依据UART协议, 严格按照波特率在TX引脚发送方波即可, 代码如下:
static ssize_t uart_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
char kbuf[BUFFER_SIZE];
size_t i;
int bit = 0;
if (count > BUFFER_SIZE)
return -EINVAL;
if (copy_from_user(kbuf, buf, count))
return -EFAULT;
for (i = 0; i < count; i++)
{
// send start bit
gpio_set_value(gpio_tx, 0);
udelay(BIT_DURATION(baud_rate));
// send data bits.
for (bit = 0; bit < 8; bit++)
{
gpio_set_value(gpio_tx, (kbuf[i] >> bit) & 1);
udelay(BIT_DURATION(baud_rate));
}
// send stop bit.
gpio_set_value(gpio_tx, 1);
udelay(BIT_DURATION(baud_rate));
}
return count;
}
驱动加载成功后, 使用如下命令即可发送数据, 如不带换行符只发送ASCII字符 UA:
root@LPA3588:~# echo -n UA > /dev/bitbang_uart
字符U的HEX值是0x55, 字符A的HEX值是0x41; 使用示波器抓取TX管脚上的波形如下:
看上去发送波形还算标准, 从波形读取数据是0x55及0x41, 在UART另外一端也是收到字符'UA‘。
15.2.5 UART 接收数据
UART RX接收数据有2种方式, (1). 中断接收, (2). 轮询方法。中断方式不会消耗CPU资源, 轮询方法则是CPU一直在查询RX的状态。 这里的驱动代码, 使用轮询方法; 有兴趣的同学可以更改成中断方式。
当上层APP打开此设备时, 在驱动代码里创建一个接收数据的线程, 一直读取RX的电平信号, APP关闭设备时, 则退出线程。 线程代码如下:
static int uart_polling_thread(void *data)
{
int bit_count = 0;
unsigned char byte = 0;
pr_info("Enter ==> %s\n", __func__);
uart_thread_running = true;
while (uart_thread_running)
{
if (bit_count == 0)
{
// detect the start bit
if (gpio_get_value(gpio_rx) == 0)
{
bit_count++;
/**
* Wait for one bit time to ensure sampling in the middle of the data bit.
*/
udelay(BIT_DURATION(baud_rate));
}
else
{
/**
* In this case, there is no data at gpio_rx, so sleep a momnent to avoid 100% cpu.
*/
//usleep_range(1, 1);
}
}
else if (bit_count <= 8)
{
// receiving data bits.
byte |= (gpio_get_value(gpio_rx) << (bit_count - 1));
bit_count++;
udelay(BIT_DURATION(baud_rate));
}
else
{
udelay(BIT_DURATION(baud_rate) / 2);
// detecting the stop bit.
if (gpio_get_value(gpio_rx) == 1)
{
unsigned long flags;
spin_lock_irqsave(&buffer_lock, flags);
uart_buffer[buffer_head] = byte;
buffer_head = (buffer_head + 1) % BUFFER_SIZE;
spin_unlock_irqrestore(&buffer_lock, flags);
byte = 0;
bit_count = 0;
}
}
}
pr_info("Exit <== %s\n", __func__);
return 0;
}
此接收数据代码有验证波特率9600及19200, 速率更高的115200需要调节代码里的等待时间:
udelay(BIT_DURATION(baud_rate));
15.2.6 完整驱动代码
1.) uart.c
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/device.h>
//#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#define DEVICE_NAME "bitbang_uart"
#define CLASS_NAME "bitbang_uart_class"
#define DEFAULT_BAUD_RATE 19200
#define BIT_DURATION(baud_rate) (1000000 / (baud_rate)) // Duration of each bit in microseconds
#define BUFFER_SIZE 256
#define IOCTL_SET_BAUD_RATE 0
#define IOCTL_CLEAR_BUFFER 1
static int gpio_tx;
static int gpio_rx;
static char uart_buffer[BUFFER_SIZE];
static int buffer_head = 0;
static int buffer_tail = 0;
static DEFINE_SPINLOCK(buffer_lock);
static int baud_rate = DEFAULT_BAUD_RATE;
static int major_number;
static struct class *uart_bitbang_class;
static struct device *uart_device;
//static struct hrtimer uart_timer;
//static int bit_count = 0;
//static unsigned char byte = 0;
static struct task_struct *uart_thread;
static bool uart_thread_running = false;
static int uart_polling_thread(void *data)
{
int bit_count = 0;
unsigned char byte = 0;
pr_info("Enter ==> %s\n", __func__);
uart_thread_running = true;
while (uart_thread_running)
{
if (bit_count == 0)
{
// detect the start bit
if (gpio_get_value(gpio_rx) == 0)
{
bit_count++;
/**
* Wait for one bit time to ensure sampling in the middle of the data bit.
*/
udelay(BIT_DURATION(baud_rate));
}
else
{
/**
* In this case, there is no data at gpio_rx, so sleep a momnent to avoid 100% cpu.
*/
//usleep_range(1, 1);
}
}
else if (bit_count <= 8)
{
// receiving data bits.
byte |= (gpio_get_value(gpio_rx) << (bit_count - 1));
bit_count++;
udelay(BIT_DURATION(baud_rate));
}
else
{
udelay(BIT_DURATION(baud_rate) / 2);
// detecting the stop bit.
if (gpio_get_value(gpio_rx) == 1)
{
unsigned long flags;
spin_lock_irqsave(&buffer_lock, flags);
uart_buffer[buffer_head] = byte;
buffer_head = (buffer_head + 1) % BUFFER_SIZE;
spin_unlock_irqrestore(&buffer_lock, flags);
byte = 0;
bit_count = 0;
}
}
}
pr_info("Exit <== %s\n", __func__);
return 0;
}
static void clear_buffer(void)
{
unsigned long flags;
spin_lock_irqsave(&buffer_lock, flags);
buffer_head = 0;
buffer_tail = 0;
spin_unlock_irqrestore(&buffer_lock, flags);
pr_info("Buffer cleared\n");
}
static long uart_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
case IOCTL_SET_BAUD_RATE:
if (copy_from_user(&baud_rate, (int __user *)arg, sizeof(baud_rate)))
return -EFAULT;
pr_info("Baud rate set to %d\n", baud_rate);
break;
case IOCTL_CLEAR_BUFFER:
clear_buffer();
break;
default:
return -EINVAL;
}
return 0;
}
static ssize_t uart_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
int bytes_read = 0;
unsigned long flags;
while (count && (buffer_head != buffer_tail))
{
spin_lock_irqsave(&buffer_lock, flags);
if (put_user(uart_buffer[buffer_tail], buf++))
{
spin_unlock_irqrestore(&buffer_lock, flags);
return -EFAULT;
}
buffer_tail = (buffer_tail + 1) % BUFFER_SIZE;
spin_unlock_irqrestore(&buffer_lock, flags);
count--;
bytes_read++;
}
return bytes_read;
}
static ssize_t uart_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
char kbuf[BUFFER_SIZE];
size_t i;
int bit = 0;
if (count > BUFFER_SIZE)
return -EINVAL;
if (copy_from_user(kbuf, buf, count))
return -EFAULT;
for (i = 0; i < count; i++)
{
// send start bit
gpio_set_value(gpio_tx, 0);
udelay(BIT_DURATION(baud_rate));
// send data bits.
for (bit = 0; bit < 8; bit++)
{
gpio_set_value(gpio_tx, (kbuf[i] >> bit) & 1);
udelay(BIT_DURATION(baud_rate));
}
// send stop bit.
gpio_set_value(gpio_tx, 1);
udelay(BIT_DURATION(baud_rate));
}
return count;
}
static int uart_open(struct inode *inode, struct file *file)
{
pr_info("UART device opened\n");
uart_thread = kthread_run(uart_polling_thread, NULL, "uart_polling_thread");
if (IS_ERR(uart_thread))
{
pr_err("Failed to create UART polling thread\n");
return PTR_ERR(uart_thread);
}
return 0;
}
static int uart_release(struct inode *inode, struct file *file)
{
pr_info("UART device closed\n");
uart_thread_running = false;
//kthread_stop(uart_thread);
return 0;
}
static const struct file_operations uart_fops = {
.owner = THIS_MODULE,
.open = uart_open,
.read = uart_read,
.write = uart_write,
.release = uart_release,
.unlocked_ioctl = uart_ioctl,
};
static int uart_probe(struct platform_device *pdev)
{
int ret;
struct device_node *np = pdev->dev.of_node;
gpio_tx = of_get_named_gpio(np, "tx-gpios", 0);
if (!gpio_is_valid(gpio_tx))
{
pr_err("Invalid TX GPIO\n");
return -EINVAL;
}
gpio_rx = of_get_named_gpio(np, "rx-gpios", 0);
if (!gpio_is_valid(gpio_rx))
{
pr_err("Invalid RX GPIO\n");
return -EINVAL;
}
// request GPIO pin
ret = gpio_request(gpio_tx, "uart_bitbang_tx");
if (ret)
{
pr_err("Failed to request uart_bitbang_tx\n");
return ret;
}
ret = gpio_request(gpio_rx, "uart_bitbang_rx");
if (ret)
{
pr_err("Failed to request uart_bitbang_rx\n");
gpio_free(gpio_tx);
return ret;
}
// set GPIO direction
gpio_direction_output(gpio_tx, 1);
gpio_direction_input(gpio_rx);
// register character device.
major_number = register_chrdev(0, DEVICE_NAME, &uart_fops);
if (major_number < 0)
{
pr_err("Failed to register char device\n");
gpio_free(gpio_tx);
gpio_free(gpio_rx);
return major_number;
}
// create device class.
uart_bitbang_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(uart_bitbang_class))
{
pr_err("Failed to create class device\n");
unregister_chrdev(major_number, DEVICE_NAME);
gpio_free(gpio_tx);
gpio_free(gpio_rx);
return PTR_ERR(uart_bitbang_class);
}
// create device node.
uart_device = device_create(uart_bitbang_class, NULL, MKDEV(major_number, 0), NULL, DEVICE_NAME);
if (IS_ERR(uart_device))
{
pr_err("Failed to create device\n");
class_destroy(uart_bitbang_class);
unregister_chrdev(major_number, DEVICE_NAME);
gpio_free(gpio_tx);
gpio_free(gpio_rx);
return PTR_ERR(uart_device);
}
pr_info("UART BITBANG driver initialized\n");
return 0;
}
static int uart_remove(struct platform_device *pdev)
{
uart_thread_running = false;
pr_info("Device node destroyed\n");
device_destroy(uart_bitbang_class, MKDEV(major_number, 0));
pr_info("Class unregistered\n");
class_unregister(uart_bitbang_class);
pr_info("Class destroyed\n");
class_destroy(uart_bitbang_class);
pr_info("Char device unregistered\n");
unregister_chrdev(major_number, DEVICE_NAME);
gpio_free(gpio_tx);
gpio_free(gpio_rx);
pr_info("UART BITBANG driver exited\n");
return 0;
}
static const struct of_device_id uart_of_match[] = {
{
.compatible = "neardi,bitbang_uart",
},
{/* sentinel */}};
MODULE_DEVICE_TABLE(of, uart_of_match);
static struct platform_driver uart_driver = {
.probe = uart_probe,
.remove = uart_remove,
.driver = {
.name = "bitbang_uart",
.of_match_table = uart_of_match,
},
};
module_platform_driver(uart_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("UART GPIO Bitbang Driver");
MODULE_AUTHOR("Linx Zhang");
2). Makefile
obj-m += uart.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
15.3 测试
驱动加载成功后, 会生成 /dev/bitbang_uart 设备。 创建一个APP程序打开此设备, 并读取数据。
在设备上创建一个test.cpp文件, 内容如下:
#include <iostream>
#include <fstream>
#include <string>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <iomanip>
#define IOCTL_SET_BAUD_RATE 0
#define IOCTL_CLEAR_BUFFER 1
int main()
{
const char *device = "/dev/bitbang_uart";
char buffer[128];
int fd = open(device, O_RDWR);
if (fd < 0)
{
std::cerr << "Failed to open device: " << device << std::endl;
return 1;
}
// clear the cache
if (ioctl(fd, IOCTL_CLEAR_BUFFER) < 0)
{
std::cerr << "Failed to clear buffer" << std::endl;
close(fd);
return 1;
}
while (true)
{
ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);
if (bytesRead < 0)
{
std::cerr << "Failed to read from device" << std::endl;
close(fd);
return 1;
}
if (bytesRead > 0)
{
buffer[bytesRead] = '\0'; // Null-terminate the string
std::cout << "Read " << bytesRead << " bytes: " << buffer << std::endl;
}
usleep(100000); // Sleep for 100ms
}
close(fd);
return 0;
}
使用如下命令编译:
aarch64-linux-gnu-g++ -o test test.cpp
此测试APP仅仅是读取UART接收的数据, 之后打印出来。 测试步骤如下:
15.3.1 加载驱动
首先确保UART驱动加载成功, 如下:
neardi@LPA3588:~/drivers/uart$ sudo insmod uart.ko
neardi@LPA3588:~/drivers/uart$ lsmod
Module Size Used by
uart 16384 0
15.3.2 运行测试APP
使用杜邦线与PC上面的UART连接, 在终端命令行运行sudo ./test:
neardi@LPA3588:~/drivers/uart$ sudo ./test
其次在PC上面打开第三方UART工具, 以19200的波特率发送数据, 如下:
设备UART成功地接收到PC发送的数据。 发送数据的测试, 参考15.2.4。