Heng30的博客
搜索 分类 关于 订阅

Linux驱动如何使用raspi3b串口收发数据?

2025-03-09

串口通信是嵌入式开发的常用通信方式之一。下面就给大家演示一下,如何使用Linux驱动来进行串口通信?下面的例子主要实现了串口通信回显功能,上位机通过USB串口发送数据到raspi3b,pi3b的驱动接收到数据后,直接将数据发送回来。

设备树配置

// testoverlay.dts

/dts-v1/;
/plugin/;
/ {
    compatible = "raspberrypi,3-model-bbrcm,bcm2837";
    fragment@0 {
        target = <&uart1>;
        __overlay__ {
            echodev {
                compatible = "brightlight,echodev";
                status = "okay";
            };
        };
    };
};

驱动代码

// simple.c

#include <linux/init.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/serdev.h>

static struct of_device_id my_driver_ids[] = {
    {
        .compatible = "brightlight,echodev",
    },
    {},
};
MODULE_DEVICE_TABLE(of, my_driver_ids);

static int _echo_recv(struct serdev_device *serdev,
                            const unsigned char *buffer, size_t size) {
    pr_info("received %ld bytes with \"%s\"\n", size, buffer);
    return serdev_device_write_buf(serdev, buffer, size);
}

static const struct serdev_device_ops dops = {
    .receive_buf = _echo_recv,
};

static int _dt_probe(struct serdev_device *serdev) {
    int status = 0;
    const char* prompt = "Type something: ";
    pr_info("simple_dt_probe\n");

    serdev_device_set_client_ops(serdev, &dops);
    status = serdev_device_open(serdev);
    if (status) {
        pr_err("serdev_device_open failed\n");
        return -status;
    }

    serdev_device_set_baudrate(serdev, 9600);
    serdev_device_set_flow_control(serdev, false);
    serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);

    status = serdev_device_write_buf(serdev, prompt, sizeof(prompt));
    pr_info("send %d bytes\n", status);

    return 0;
}

static void _dt_remove(struct serdev_device *serdev) {
    pr_info("simple_dt_remove\n");
    serdev_device_close(serdev);
}

static struct serdev_device_driver my_dirver = {
    .probe = _dt_probe,
    .remove = _dt_remove,
    .driver =
        {
            .name = "my_device_driver",
            .of_match_table = my_driver_ids,
        },
};

static int __init simple_init(void) {
    pr_info("simple_init\n");
    if (serdev_device_driver_register(&my_dirver)) {
        pr_err("serdev_device_driver_register failed\n");
        return -1;
    }

    return 0;
}

static void __exit simple_exit(void) {
    pr_info("simple_exit\n");

    serdev_device_driver_unregister(&my_dirver);
}

module_init(simple_init);
module_exit(simple_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("heng30");
MODULE_VERSION("v0.0.1");
MODULE_DESCRIPTION("A simple kernel module");

Makefile编译脚本

#!/bin/sh

top-dir = $(shell pwd)
kernel-version = $(shell uname -r)
kernel-dir ?= /lib/modules/$(kernel-version)/build

obj-m += simple.o

all: dt driver

driver:
        make -C $(kernel-dir) modules M=$(top-dir)

dt:
        dtc -@ -I dts -O dtb -o testoverlay.dtbo testoverlay.dts

clean:
        rm -f *.o *.ko *.mod *.mod.c *.order *.symvers *.dtbo
        make -C $(kernel-dir) clean m=$(top-dir)

测试

  • 编译:make

  • 复制设备树节点到启动目录:sudo cp testoverlay.dtbo /boot/overlays/

  • 编辑启动配置:sudo vim /boot/config.txt

    dtoverlay=testoverlay
    
  • 配置树莓派串口选项:sudo raspi-config

    • Interface Options->Serial Port
    The serial login shell is disabled                       │
    The serial interface is enabled
    
  • 重启:sudo reboot

  • 查看串口信息:ls /proc/device-tree/soc/serial*

  • 安装驱动:insmod simple.ko

  • 向串口发送数据:sudo screen /dev/ttyUSB0 9600

  • 移除驱动:rmmod simple.ko

参考