想在树莓派上使用USB转串口模块,看这一篇就够了

使用USB转串口模块,并不可免的需要安装驱动,目前主要涉及的是CH340以及CH9344驱动。其安装方法、验证方法以及其中有可能出现的问题看本博文就够了。

驱动安装

串口驱动基本上在https://www.wch.cn/downloads/网站上可以找到

CH340驱动

  1. 源码编译安装

    驱动下载地址:``http://www.wch.cn/download/CH341SER_LINUX_ZIP.html`

    包含如下文件:ch34x.c、Makefile、readme.txt

    源码安装步骤为:

    unzip CH341SER_LINUX.zip
    cd CH341SER_LINUX/driver
    make
    make load
    sudo make install
    
    • 如果make报异常:/lib/modules/4.19.75-v7+/build: 没有那个文件或目录

      是linux-headers的问题。安装

      sudo apt-get install linux-headers
      ls /usr/src #查看linux-headrs的版本
      sudo ln -s /usr/src/linux-headers-4.19.66-v7+ /lib/modules/4.19.75-v7+/build #形成链接即可
      
    • 报错:unknown type name ‘wait_queue_t’

      wait_queue_t wait 并没有用,打开driver/ch34x.c查看源码,直接注释掉

    • 报错:implicit declaration of function ‘signal_pending’

      这个错误是因为没有包含一个头文件signal.h,在文件包含头文件的地方加入 #include <linux/sched/signal.h>

    • 报错:.write = ch341_tty_write

      详细的错误截图:

    在这里插入图片描述

    make -C /lib/modules/6.6.47+rpt-rpi-2712/build  M=/home/pi/Downloads/CH341SER_LINUX/driver
    make[1]: Entering directory '/usr/src/linux-headers-6.6.47+rpt-rpi-2712'
      CC [M]  /home/pi/Downloads/CH341SER_LINUX/driver/ch341.o
    /home/pi/Downloads/CH341SER_LINUX/driver/ch341.c:1460:18: error: initialization of ‘ssize_t (*)(struct tty_struct *, const u8 *, size_t){aka ‘long int (*)(struct tty_struct *, const unsigned char *, long unsigned int)} from incompatible pointer type ‘int (*)(struct tty_struct *, const unsigned char *, int)[-Werror=incompatible-pointer-types]
     1460 |         .write = ch341_tty_write,
          |                  ^~~~~~~~~~~~~~~
    /home/pi/Downloads/CH341SER_LINUX/driver/ch341.c:1460:18: note: (near initialization for ‘ch341_ops.write’)
    cc1: some warnings being treated as errors
    make[3]: *** [/usr/src/linux-headers-6.6.47+rpt-common-rpi/scripts/Makefile.build:248: /home/pi/Downloads/CH341SER_LINUX/driver/ch341.o] Error 1
    make[2]: *** [/usr/src/linux-headers-6.6.47+rpt-common-rpi/Makefile:1946: /home/pi/Downloads/CH341SER_LINUX/driver] Error 2
    make[1]: *** [/usr/src/linux-headers-6.6.47+rpt-common-rpi/Makefile:246: __sub-make] Error 2
    make[1]: Leaving directory '/usr/src/linux-headers-6.6.47+rpt-rpi-2712'
    make: *** [Makefile:5: default] Error 2
    

    具体来说,错误发生在 ch341.c 文件的第 1460 行,这里指定了一个名为 ch341_tty_write 的函数作为 tty_structwrite 方法。编译器报告说这个函数的返回类型 int 与期望的 ssize_t 类型不一致。

    在 Linux 内核编程中,tty_structwrite 方法预期返回一个 ssize_t 类型,这是为了表示写入的字节数或者一个负值来指示错误。而 ch341_tty_write 函数似乎返回的是 int 类型,这导致了编译器报错。

    处理:主要是修改ch341_tty_write 函数的参数,包括

    • static int ch341_tty_write(struct tty_struct *tty, const unsigned char *buf, size_t count)改成static ssize_t ch341_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
    • int stat;改成ssize_t stat;

    完整源码:

    static ssize_t ch341_tty_write(struct tty_struct *tty, const unsigned char *buf, size_t count)
    {
    	struct ch341 *ch341 = tty->driver_data;
    	ssize_t stat;
    	unsigned long flags;
    	int wbn;
    	struct ch341_wb *wb;
    
    	if (!count)
    		return 0;
    
    	spin_lock_irqsave(&ch341->write_lock, flags);
    	wbn = ch341_wb_alloc(ch341);
    	if (wbn < 0) {
    		spin_unlock_irqrestore(&ch341->write_lock, flags);
    		return 0;
    	}
    	wb = &ch341->wb[wbn];
    
    	if (!ch341->dev) {
    		wb->use = 0;
    		spin_unlock_irqrestore(&ch341->write_lock, flags);
    		return -ENODEV;
    	}
    
    	count = (count > ch341->writesize) ? ch341->writesize : count;
    	memcpy(wb->buf, buf, count);
    	wb->len = count;
    
    	stat = usb_autopm_get_interface_async(ch341->data);
    	if (stat) {
    		wb->use = 0;
    		spin_unlock_irqrestore(&ch341->write_lock, flags);
    		return stat;
    	}
    
    	if (ch341->susp_count) {
    		usb_anchor_urb(wb->urb, &ch341->delayed);
    		spin_unlock_irqrestore(&ch341->write_lock, flags);
    		return count;
    	}
    
    	stat = ch341_start_wb(ch341, wb);
    	spin_unlock_irqrestore(&ch341->write_lock, flags);
    
    	if (stat < 0)
    		return stat;
    	return count;
    }
    
  2. 通过Git安装

    sudo apt-get update
    sudo apt-get install dkms
    sudo chmod +x ./install.sh
    sudo ./install.sh
    

    这个方法不知道为啥,不正确,首先安装dkms后,chmod这个install.sh在哪,也不知道

    但是可以git安装CH340:

    sudo apt-get install git
    git clone https://github.com/juliagoda/CH341SER.git
    cd CH341SER
    make
    sudo make load
    sudo make install
    
  3. 测试

    • 命令查看是否识别硬件:lsusb,正常是看到:

    在这里插入图片描述

    • 检查硬件连接信息:dmesg | grep USB,正常的话可以看到:

      在这里插入图片描述

    • 检查加载的模块lsmod | grep ch34 or lsmod | grep ch,正常是看到:

      在这里插入图片描述

    • 手动加载模块:sudo modprobe ch341

    • 获取更详细的设备连接信息:usb-devices

      如果是获得信息如下:

      T:  Bus=01 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#=  2 Spd=480 MxCh= 0
      D:  Ver= 2.00 Cls=ff(vend.) Sub=80 Prot=37 MxPS=64 #Cfgs=  1
      P:  Vendor=1a86 ProdID=55d9 Rev=01.36
      S:  Manufacturer=wch.cn
      S:  Product=USB2.0 To Multi Serial Ports
      C:  #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=200mA
      I:  If#= 0 Alt= 0 #EPs= 4 Cls=ff(vend.) Sub=80 Prot=37 Driver=(none)
      E:  Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
      E:  Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
      E:  Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
      E:  Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
      

      确实识别到了您的多串口设备,但重要的一点是在设备信息行 I: If#= 0 Alt= 0 #EPs= 4 Cls=ff(vend.) Sub=80 Prot=37 Driver=(none) 中显示 "Driver=(none)"。这意味着没有为这个设备加载适当的驱动。这可能是为什么 /dev/ttyUSB* 端口没有被创建的原因。suo

    • 查看端口号(检查设备节点):

      dmesg | grep ttyUSB* 
      # 或者
      ls /dev/ttyUSB*
      

在这里插入图片描述

安装CH9344驱动

这个驱动是给芯片CH384以及CH9344的。

安装步骤类似CH341,在安装时常见的错误也是在修改chXXX_tty_write 函数,这里是ch9344_tty_write,需要进行修改:

  • static int ch9344_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)改成static ssize_t ch9344_tty_write(struct tty_struct *tty, const unsigned char *buf, size_t count)
  • int stat;改成ssize_t stat;
  • int packlen, total_len, sendlen;改成ssize_t packlen, total_len, sendlen;

该函数的完整源码:

static ssize_t ch9344_tty_write(struct tty_struct *tty, const unsigned char *buf, size_t count)
{
	struct ch9344 *ch9344 = tty->driver_data;
	ssize_t stat;
	unsigned long flags;
	int wbn;
	struct ch9344_wb *wb;
	int portnum = ch9344_get_portnum(tty->index);
	int timeout;
	int maxep = ch9344->writesize / 20;
	int packnum, maxpacknum;
	ssize_t packlen, total_len, sendlen;

	if (!count)
		return 0;

	if (ch9344->writesize % (maxep - 3))
		maxpacknum = ch9344->writesize / (maxep - 3) + 1;
	else
		maxpacknum = ch9344->writesize / (maxep - 3);

	count = count > (ch9344->writesize - maxpacknum * 3) ? (ch9344->writesize - maxpacknum * 3) : count;
	total_len = count;
	sendlen = 0;

	if (count % (maxep - 3))
		packnum = count / (maxep - 3) + 1;
	else
		packnum = count / (maxep - 3);

transmit:
	spin_lock_irqsave(&ch9344->write_lock, flags);
	wbn = ch9344_wb_alloc(ch9344, portnum);
	if (wbn < 0) {
		spin_unlock_irqrestore(&ch9344->write_lock, flags);
		return 0;
	}
	wb = &ch9344->wb[portnum][wbn];

	if (!ch9344->dev) {
		wb->use = 0;
		spin_unlock_irqrestore(&ch9344->write_lock, flags);
		return -ENODEV;
	}

	/* packets deal */
	if (total_len > maxep - 3) {
		packlen = maxep - 3;
		total_len -= packlen;
	} else {
		packlen = total_len;
		total_len = 0;
	}
	*(wb->buf) = ch9344->port_offset + ch9344_get_portnum(tty->index);
	*(wb->buf + 1) = packlen;
	*(wb->buf + 2) = packlen >> 8;
	memcpy(wb->buf + 3, buf + sendlen, packlen);
	wb->len = packlen + 3;
	sendlen += packlen;
	wb->portnum = portnum;

	stat = usb_autopm_get_interface_async(ch9344->data);
	if (stat) {
		wb->use = 0;
		spin_unlock_irqrestore(&ch9344->write_lock, flags);
		return stat;
	}

	if (ch9344->susp_count) {
		if (ch9344->putbuffer) {
			usb_anchor_urb(ch9344->putbuffer->urb, &ch9344->delayed);
			ch9344->putbuffer = NULL;
		}
		usb_anchor_urb(wb->urb, &ch9344->delayed);
		spin_unlock_irqrestore(&ch9344->write_lock, flags);
		return sendlen;
	} else {
		if (ch9344->putbuffer) {
			ch9344_start_wb(ch9344, ch9344->putbuffer);
			ch9344->putbuffer = NULL;
		}
	}

	if (!ch9344->ttyport[portnum].write_empty) {
		spin_unlock_irqrestore(&ch9344->write_lock, flags);
		timeout = wait_event_interruptible_timeout(ch9344->ttyport[portnum].wioctl,
							   ch9344->ttyport[portnum].write_empty,
							   msecs_to_jiffies(DEFAULT_TIMEOUT));
		if (timeout <= 0) {
			wb->use = 0;
			ch9344->ttyport[portnum].write_empty = true;
			return sendlen ? sendlen : -ETIMEDOUT;
		}
		spin_lock_irqsave(&ch9344->write_lock, flags);
	}

	ch9344->ttyport[portnum].write_empty = false;
	stat = ch9344_start_wb(ch9344, wb);
	if (stat < 0) {
		ch9344->ttyport[portnum].write_empty = true;
		spin_unlock_irqrestore(&ch9344->write_lock, flags);
		return stat;
	}
	spin_unlock_irqrestore(&ch9344->write_lock, flags);

	if (total_len != 0)
		goto transmit;

	if (sendlen > 0)
		ch9344->ttyport[portnum].iocount.tx += sendlen;

	return sendlen;
}

安装minicom

Minicom 是一个基于文本的串行通信程序,广泛用于 Unix 和 Unix-like 系统(包括 Linux)。它非常适合用来管理通过串行端口连接的设备,如调制解调器、路由器和交换机。Minicom 类似于 Windows 上的 HyperTerminal,但具有更多功能和灵活性。在树莓派,可以直接在software中进行添加:

在这里插入图片描述

  1. 使用 Minicom 时,你需要知道你想要连接的串行设备的设备文件名(通常是 /dev/ttyUSB0/dev/ttyS0 等)以及通信参数(如波特率、数据位、停止位等)。可以使用以下命令配置串行端口:

    sudo minicom -s
    

这将打开 Minicom 的设置菜单。在这里,你可以设置:

  • 串行端口配置:选择 “Serial port setup” 来配置串行端口。

    • 更改串行设备(默认为 /dev/modem,通常需要更改为 /dev/ttyUSB0 或其他正确的设备文件)。
    • 设置波特率和其他串行参数(如 9600、19200、115200 等)。
  • 保存设置:配置完毕后,选择 “Save setup as dfl” 来保存设置为默认配置。

  • 退出设置:选择 “Exit” 来退出设置菜单并启动串行会话。

  1. 连接到设备:一旦设置正确,你可以直接通过运行 Minicom 来连接设备:

    sudo minicom
    

    这将使用之前保存的设置启动串行通信会话。

  2. 使用 Minicom

    在 Minicom 中,你可以发送文本命令到连接的设备,并查看来自设备的响应。Minicom 提供了一套控制命令,通常通过按 Ctrl-A 后跟另一个字符来调用。常见的控制命令包括:

    • Ctrl-A Z:显示帮助菜单,列出所有 Minicom 命令。

    • Ctrl-A X:退出 Minicom。

    • Ctrl-A E:打开或关闭本地回显。

    • Ctrl-A L:记录会话到文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月司

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值