驱动与外设开发
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) cleanbash
# 编译和加载
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.koUSB 设备枚举
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 ./myappgdb 远程调试
bash
# 在模组上启动 gdbserver
gdbserver :1234 ./myapp
# 在开发机上连接
arm-linux-gnueabihf-gdb ./myapp
(gdb) target remote 192.168.1.100:1234
(gdb) break main
(gdb) continue