Skip to content

驱动与外设开发

Linux 驱动开发基础

驱动类型

字符设备驱动(Character Device):
  - 按字节流访问
  - 适合:串口、GPIO、传感器
  - 设备文件:/dev/ttyXXX, /dev/gpioXXX

块设备驱动(Block Device):
  - 按块(512B/4KB)访问
  - 适合:Flash、SD 卡、eMMC
  - 设备文件:/dev/mmcblkX, /dev/sdX

网络设备驱动(Network Device):
  - 通过 socket 接口访问
  - 适合:以太网、Wi-Fi、蜂窝网络
  - 接口:eth0, wlan0, wwan0

内核模块基础

c
// 最简单的内核模块
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Quectel FAE");
MODULE_DESCRIPTION("Simple kernel module example");

static int __init my_module_init(void)
{
    printk(KERN_INFO "My module loaded\n");
    return 0;
}

static void __exit my_module_exit(void)
{
    printk(KERN_INFO "My module unloaded\n");
}

module_init(my_module_init);
module_exit(my_module_exit);
makefile
# Makefile
obj-m += my_module.o

KERNEL_DIR ?= /lib/modules/$(shell uname -r)/build

all:
	make -C $(KERNEL_DIR) M=$(PWD) modules

clean:
	make -C $(KERNEL_DIR) M=$(PWD) clean
bash
# 编译和加载
make
insmod my_module.ko
dmesg | tail -5  # 查看内核日志
rmmod my_module

串口驱动

标准 UART 驱动

移远模组的串口通常已有内核驱动支持,通过设备树配置:

c
// 设备树配置(sc200e.dts)
&uart2 {
    status = "okay";
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&uart2_active>;
    pinctrl-1 = <&uart2_sleep>;
};

用户空间串口操作

c
#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>

int uart_open(const char *device, int baudrate) {
    int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd < 0) return -1;
    
    struct termios options;
    tcgetattr(fd, &options);
    
    // 设置波特率
    speed_t speed;
    switch (baudrate) {
        case 9600:   speed = B9600;   break;
        case 115200: speed = B115200; break;
        case 921600: speed = B921600; break;
        default:     speed = B115200;
    }
    cfsetispeed(&options, speed);
    cfsetospeed(&options, speed);
    
    // 8N1 配置
    options.c_cflag &= ~PARENB;   // 无奇偶校验
    options.c_cflag &= ~CSTOPB;   // 1位停止位
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;        // 8位数据位
    
    // 原始模式
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    options.c_iflag &= ~(IXON | IXOFF | IXANY);
    options.c_oflag &= ~OPOST;
    
    // 读取超时
    options.c_cc[VMIN] = 0;
    options.c_cc[VTIME] = 10;  // 1秒超时
    
    tcsetattr(fd, TCSANOW, &options);
    return fd;
}

int main() {
    int fd = uart_open("/dev/ttyHS0", 115200);
    if (fd < 0) {
        perror("Open UART failed");
        return -1;
    }
    
    // 发送 AT 指令
    const char *cmd = "AT\r\n";
    write(fd, cmd, strlen(cmd));
    
    // 读取响应
    char buf[256];
    int n = read(fd, buf, sizeof(buf) - 1);
    if (n > 0) {
        buf[n] = '\0';
        printf("Response: %s\n", buf);
    }
    
    close(fd);
    return 0;
}

USB 设备驱动

蜂窝模组 USB 驱动

移远蜂窝模组通过 USB 连接到 Linux 主机,需要正确的驱动:

bash
# 查看 USB 设备
lsusb
# Bus 001 Device 002: ID 2c7c:0125 Quectel Wireless Solutions EC25

# 加载驱动
modprobe option  # 串口驱动(AT 指令端口)
modprobe qmi_wwan  # 网络驱动(数据连接)

# 或使用移远提供的驱动
# 下载:https://github.com/quectel/GobiNet
cd GobiNet
make
insmod GobiNet.ko
insmod GobiSerial.ko

USB 设备枚举

bash
# 查看 USB 串口设备
ls /dev/ttyUSB*
# /dev/ttyUSB0  # DM 诊断端口
# /dev/ttyUSB1  # NMEA 端口(GNSS)
# /dev/ttyUSB2  # AT 指令端口
# /dev/ttyUSB3  # AT 指令端口(备用)

# 查看网络接口
ip link show
# wwan0 或 usb0(取决于驱动模式)

看门狗驱动

c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/watchdog.h>

int main() {
    int fd = open("/dev/watchdog", O_RDWR);
    if (fd < 0) {
        perror("Open watchdog failed");
        return -1;
    }
    
    // 设置超时时间(秒)
    int timeout = 30;
    ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
    
    printf("Watchdog started, timeout: %d seconds\n", timeout);
    
    // 主循环:定期喂狗
    while (1) {
        // 执行业务逻辑...
        
        // 喂狗(写入任意字符)
        write(fd, "1", 1);
        printf("Watchdog fed\n");
        
        sleep(10);  // 每10秒喂一次
    }
    
    // 正常退出时关闭看门狗
    // 写入 'V' 表示正常关闭
    write(fd, "V", 1);
    close(fd);
    
    return 0;
}

调试技巧

内核日志

bash
# 实时查看内核日志
dmesg -w

# 查看特定级别日志
dmesg --level=err,warn

# 设置日志级别(0-7,数字越小越严重)
echo 7 > /proc/sys/kernel/printk  # 显示所有级别

# 在驱动中打印日志
printk(KERN_ERR "Error: %s\n", message);    // 错误
printk(KERN_WARNING "Warning: %d\n", code); // 警告
printk(KERN_INFO "Info: %s\n", info);       // 信息
printk(KERN_DEBUG "Debug: %x\n", value);    // 调试

strace 系统调用跟踪

bash
# 跟踪程序的系统调用
strace ./myapp

# 只跟踪特定系统调用
strace -e trace=read,write,open ./myapp

# 跟踪正在运行的进程
strace -p <PID>

# 输出到文件
strace -o trace.log ./myapp

gdb 远程调试

bash
# 在模组上启动 gdbserver
gdbserver :1234 ./myapp

# 在开发机上连接
arm-linux-gnueabihf-gdb ./myapp
(gdb) target remote 192.168.1.100:1234
(gdb) break main
(gdb) continue

褚成志的笔记