設置 | 登錄 | 註冊

目前共有6篇帖子。

移遠EC200A模塊使用說明

1樓 巨大八爪鱼 2025-12-26 15:24
EC200A和EC200M兩個型號的區別:
EC200ACNV1-CAT4模塊3孔轉板QTMHXB0036QD cat4:下行速率:150Mbps,上行速率:50Mbps
EC200MCNLF-CAT1模塊3孔轉板QTMHXB0037QD cat1:下行速率:10Mbps,上行速率:5Mbps
只有速率上有區別。EC200A速率比EC200M快,價格更貴。

移遠EC200A模塊linux usb驅動包下載地址:
https://www.quectel.com/download-zone/?_sft_downloads-type=usb-drivers
Quectel_LTE&5G_Linux_USB_Driver_V1.0 ZIP 5.37 MB
https://www.quectel.com/content/uploads/2024/02/Quectel_LTE5G_Linux_USB_Driver_V1.0-5.zip
下載下來後,要修改c文件,把EC200A的usb設備id添加到數組裡面,還要修改makefile裡面的內核和交叉編譯器的路徑。

編譯Quectel_Linux_USB_Serial_Option_Driver_V1.0/v5.3.1前需要開啟的內核選項(./build.sh kernel-config):
Device Drivers  --->  [*] USB support  --->  <*>   USB Serial Converter support

編譯Quectel_LinuxAndroid_QMI_WWAN_Driver_V1.1前需要開啟的內核選項:
Device Drivers  --->  [*] USB support  --->  <*>   USB Wireless Device Management support

在4個文件夾中分別執行make命令編譯驅動,生成下面5個ko模塊和2個用戶態程序。
Quectel_Linux_USB_Serial_Option_Driver_V1.0/v5.3.1/drivers/usb/serial/usb_wwan.ko
Quectel_Linux_USB_Serial_Option_Driver_V1.0/v5.3.1/drivers/usb/serial/option.ko
Quectel_Linux_USB_Serial_Option_Driver_V1.0/v5.3.1/drivers/usb/serial/qcserial.ko
Quectel_LinuxAndroid_GobiNet_Driver_V1.6/GobiNet.ko
Quectel_LinuxAndroid_QMI_WWAN_Driver_V1.1/qmi_wwan_q.ko
QConnectManager_Linux_V1.5/quectel-CM/quectel-CM
QConnectManager_Linux_V1.5/quectel-CM/quectel-qmi-proxy
其中,usb_wwan.ko是qcserial.ko和option.ko的依賴項。

EC200A這個USB設備上面有0-4這五個接口(interface),五個接口的名字分別為:
root@rk3308b-buildroot:/# cat /sys/bus/usb/devices/2-1/2-1:1.0/interface
Mobile RNDIS Network Adapter
root@rk3308b-buildroot:/# cat /sys/bus/usb/devices/2-1/2-1:1.1/interface
Mobile RNDIS Network Adapter
root@rk3308b-buildroot:/# cat /sys/bus/usb/devices/2-1/2-1:1.2/interface
Mobile Diag Interface
root@rk3308b-buildroot:/# cat /sys/bus/usb/devices/2-1/2-1:1.3/interface
Mobile AT Interface
root@rk3308b-buildroot:/# cat /sys/bus/usb/devices/2-1/2-1:1.4/interface
Mobile MODEM Interface
root@rk3308b-buildroot:/#

接口0和接口1(Mobile RNDIS Network Adapter)用的是linux內核自帶的RNDIS驅動(Luckfox_Nova_SDK_250430/kernel/drivers/net/usb/rndis_host.c),在AT+QCFG="usbnet",3模式下,板子開機後會自動產生usb0網絡接口。
接口2(Mobile Diag Interface)和接口3(Mobile AT Interface)用的是option.ko或qcserial.ko驅動模塊(兩個都可以用,二選一,一個模塊同時占用兩個接口),插入模塊前需要事先插入依賴項usb_wwan.ko。插入模塊後,會出現/dev/ttyUSB0和/dev/ttyUSB1兩個串口設備,分別屬於接口2和接口3。AT指令是在接口3(/dev/ttyUSB1)上收發的。
接口4(Mobile MODEM Interface)用的是GobiNet.ko或qmi_wwan_q.ko驅動模塊。兩個模塊只能二選一,如果同時插入,只有先插入的那個模塊起作用。插入GobiNet.ko模塊後會產生/dev/qcqmi1設備文件和usb1網絡接口,插入qmi_wwan_q.ko模塊後會產生/dev/cdc-wdm0設備文件和wwan0網絡接口。

用下面的命令可以看到每個接口匹配的是哪個驅動模塊。
root@rk3308b-buildroot:/root# ls -l /sys/bus/usb/devices/2-1/2-1:1.0/driver
lrwxrwxrwx 1 root root 0 Jan  1 00:00 /sys/bus/usb/devices/2-1/2-1:1.0/driver -> ../../../../../../bus/usb/drivers/rndis_host
root@rk3308b-buildroot:/root# ls -l /sys/bus/usb/devices/2-1/2-1:1.1/driver
lrwxrwxrwx 1 root root 0 Jan  1 00:01 /sys/bus/usb/devices/2-1/2-1:1.1/driver -> ../../../../../../bus/usb/drivers/rndis_host
root@rk3308b-buildroot:/root# ls -l /sys/bus/usb/devices/2-1/2-1:1.2/driver
lrwxrwxrwx 1 root root 0 Dec 25 05:18 /sys/bus/usb/devices/2-1/2-1:1.2/driver -> ../../../../../../bus/usb/drivers/qcserial
root@rk3308b-buildroot:/root# ls -l /sys/bus/usb/devices/2-1/2-1:1.3/driver
lrwxrwxrwx 1 root root 0 Dec 25 05:18 /sys/bus/usb/devices/2-1/2-1:1.3/driver -> ../../../../../../bus/usb/drivers/qcserial
root@rk3308b-buildroot:/root# ls -l /sys/bus/usb/devices/2-1/2-1:1.4/driver
lrwxrwxrwx 1 root root 0 Jan  1 00:06 /sys/bus/usb/devices/2-1/2-1:1.4/driver -> ../../../../../../bus/usb/drivers/GobiNet
root@rk3308b-buildroot:/root#

如果提示No such file or directory,說明該接口沒有匹配上驅動模塊。
root@rk3308b-buildroot:/root# ls -l /sys/bus/usb/devices/2-1/2-1:1.2/driver
ls: cannot access '/sys/bus/usb/devices/2-1/2-1:1.2/driver': No such file or directory
root@rk3308b-buildroot:/root# ls -l /sys/bus/usb/devices/2-1/2-1:1.3/driver
ls: cannot access '/sys/bus/usb/devices/2-1/2-1:1.3/driver': No such file or directory
root@rk3308b-buildroot:/root#

板子每次開機後,需運行startup.sh開機腳本檢查是否已聯網。
聯網成功後,系統會自動同步網絡時間到本地,可以用date命令看到本地時間是正確的。
root@rk3308b-buildroot:/root# date
Thu Dec 25 11:00:17 UTC 2025
root@rk3308b-buildroot:/root#
可以ping通百度:
root@rk3308b-buildroot:/root# ping www.baidu.com -c 4
PING www.baidu.com (39.156.70.239): 56 data bytes
64 bytes from 39.156.70.239: seq=0 ttl=52 time=33.304 ms
64 bytes from 39.156.70.239: seq=1 ttl=52 time=42.224 ms
64 bytes from 39.156.70.239: seq=2 ttl=52 time=36.970 ms
64 bytes from 39.156.70.239: seq=3 ttl=52 time=41.630 ms

--- www.baidu.com ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 33.304/38.532/42.224 ms
root@rk3308b-buildroot:/root#

2樓 巨大八爪鱼 2025-12-26 15:24
驅動的具體改法見:https://zh.purasbar.com/post.php?t=34246
3樓 巨大八爪鱼 2025-12-26 15:26
配置腳本:
【startup.sh】
#!/bin/sh
insmod usb_wwan.ko
insmod qcserial.ko
./startup

【startup.c】
/**
 * @file startup.c
 * EC200M 4G模塊開機初始化程序
 */
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/uio.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>

#define RESTART (-2) ///< 模塊正在重啟

/** AT命令回應 */
struct response
{
    char buffer[1024]; ///< 存放多個字符串的緩衝區
    char *lines[20]; ///< 收到的回應的每一行的內容(不含換行符)
    int line_count; ///< 收到的回應的行數
};

int verbose = 0; ///< 是否在控制台列印詳細日誌

/**
 * 解析命令行參數。
 *
 * @param argc 參數個數。
 * @param argv 參數列表。
 * @retval 0 解析成功。
 * @retval -1 解析失敗,命令行參數有誤。
 */
int parse_arguments(int argc, char *argv[])
{
    int i;
    
    for (i = 1; i < argc; i++)
    {
        if (strcmp(argv[i], "-v") == 0)
            verbose = 1;
        else
        {
            printf("unknown argument: %s\n", argv[i]);
            return -1;
        }
    }
    return 0;
}

/**
 * 配置串口參數。
 *
 * @param fd 串口的文件描述符。
 * @param baudrate 串口波特率。
 * @retval 0 配置成功。
 * @retval -1 配置失敗。
 */
int configure_uart(int fd, int baudrate)
{
    int ret;
    struct termios attr;
    
    ret = tcgetattr(fd, &attr);
    if (ret == -1)
    {
        perror("tcgetattr() failed");
        return -1;
    }
    
    cfsetspeed(&attr, baudrate);
    attr.c_cflag = (attr.c_cflag & ~CSIZE) | CS8; // 8位數據
    attr.c_cflag &= ~PARENB; // 無奇偶校驗
    attr.c_cflag &= ~CSTOPB; // 1位停止位
    attr.c_cflag &= ~CRTSCTS; // 禁用硬體流控
    attr.c_cflag |= CREAD | CLOCAL; // 啟用接收
    attr.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始輸入
    attr.c_oflag &= ~OPOST; // 原始輸出
    
    // 立即返回讀取結果
    attr.c_cc[VMIN] = 0;
    attr.c_cc[VTIME] = 10; // 1秒超時
    
    ret = tcsetattr(fd, TCSANOW, &attr);
    if (ret == -1)
    {
        perror("tcsetattr() failed");
        return -1;
    }
    return 0;
}

/**
 * 發送AT命令並接收命令回應。
 *
 * @param fd 串口的文件描述符。
 * @param cmd 要發送的AT命令字符串,末尾不用加換行符。
 * @param resp 接收AT命令回應用的結構體。
 * @param timeout 超時時間。填0表示採用默認的超時時間。
 * @retval 1 命令回應接收成功,回應內容表示AT命令執行成功(OK)。
 * @retval 0 命令回應接收成功,回應內容表示AT命令執行失敗(ERROR)。
 * @retval -1 命令回應接收超時。
 * @attention 串口回顯字符和非當前命令的回應(異步通知消息)會被忽略,不會保存到resp結構體當中。
 */
int send_command(int fd, const char *cmd, struct response *resp, int timeout)
{
    char name[50] = "";
    char *p;
    int curr, i, namelen, ret;
    struct iovec iov[2];
    time_t start, now;
    
    memset(resp, 0, sizeof(struct response));
    if (timeout == 0) // 0表示採用默認的超時時間
        timeout = 1000;
    
    // 發送命令
    iov[0].iov_base = (void *)cmd;
    iov[0].iov_len = strlen(cmd);
    iov[1].iov_base = "\r\n";
    iov[1].iov_len = 2;
    ret = writev(fd, iov, 2);
    if (ret == -1)
    {
        printf("%s: writev() failed\n", __func__);
        return -1;
    }
       
    // 提取命令名稱
    if (cmd[2] == '+')
    {
        for (i = 3; cmd[i] != '\0' && cmd[i] != '=' && cmd[i] != '?'; i++)
        {
            if (i - 3 == sizeof(name) - 1)
                break;
            name[i - 3] = cmd[i];
        }
        name[i - 3] = '\0';
    }
    namelen = strlen(name);
    
    // 接收回應
    time(&start);
    i = 0;
    do
    {
        curr = sizeof(resp->buffer) - i - 1;
        ret = read(fd, resp->buffer + i, curr);
        if (ret == -1)
        {
            printf("%s: read() failed\n", __func__);
            return -1;
        }
        i += ret;
        resp->buffer[i] = '\0';
       
        if (strstr(resp->buffer, "OK") != NULL)
            ret = 1;
        else if (strstr(resp->buffer, "ERROR") != NULL)
            ret = 0;
        else
            ret = -1; // 回應未接收完畢
       
        time(&now);
    } while (ret == -1 && now - start < timeout / 1000);
    
    // 拆分成字符串數組
    for (p = strtok(resp->buffer, "\r\n"); p != NULL; p = strtok(NULL, "\r\n"))
    {
        if (resp->line_count == 0 && strcmp(p, cmd) == 0)
            continue; // 忽略回顯字符
        if (p[0] == '+' && (strncmp(p + 1, name, namelen) != 0 || p[1 + namelen] != ':'))
        {
            printf("%s\n", p);
            continue; // 忽略非當前命令的回應
        }

        resp->lines[resp->line_count] = p;
        resp->line_count++;
        if (resp->line_count == sizeof(resp->lines) / sizeof(resp->lines[0]))
            break;
    }
    
    // 列印回應內容
    if (verbose)
    {
        printf("[Command] %s\n", cmd);
        for (i = 0; i < resp->line_count; i++)
            printf("%d: %s\n", i, resp->lines[i]);
    }
    else
    {
        if (ret == 0)
            printf("[Command] %s (error)\n", cmd);
        else if (ret == -1)
            printf("[Command] %s (timeout)\n", cmd);
    }
    return ret;
}

/**
 * 測試指定串口是否能正確響應AT命令。
 *
 * @param fd 串口的文件描述符。
 * @retval 1 串口能正確響應AT命令。
 * @retval 0 串口不能正確響應AT命令。
 */
int is_device_ready(int fd)
{
    int ret;
    struct response resp;
    
    ret = send_command(fd, "AT", &resp, 0);
    if (ret == 0) // 如果返回了ERROR
        ret = send_command(fd, "AT", &resp, 0); // 重試
    return ret == 1; // 返回是否成功
}

/**
 * 重啟設備。
 *
 * @param fd 串口的文件描述符。
 * @retval -1 設備未響應重啟命令。
 * @retval RESTART 設備已響應重啟命令。
 * @attention 設備重啟時,/dev/ttyUSB1設備文件會消失,導致文件描述符fd失效。\n
 *            調用本函數成功後,請立即關閉文件描述符fd。
 */
int restart_device(int fd)
{
    int ret;
    struct response resp;
    
    ret = send_command(fd, "AT+CFUN=1,1", &resp, 0);
    if (ret != 1)
    {
        printf("failed to restart the device\n");
        return -1;
    }
    return RESTART;
}

/**
 * 切換到RNDIS模式(usbnet 3號模式)。
 *
 * @param fd 串口的文件描述符。
 * @retval 0 已處於RNDIS模式,無需切換。
 * @retval -1 切換失敗。
 * @retval RESTART 設備已響應切換命令並且正在重啟。
 * @note 切換成功並重啟模塊後會出現usb0網絡接口。\n
 *       EC200M模塊的USB interface 0和1(Mobile RNDIS Network Adapter)用的是linux內核自帶的RNDIS驅動。\n
 *       在usbnet 3號模式下,板子開機後不需要insmod任何驅動模塊,就能自動產生usb0網絡接口。
 * @attention 設備重啟時,/dev/ttyUSB1設備文件會消失,導致文件描述符fd失效,請立即關閉文件描述符fd。
 */
int enable_rndis_mode(int fd)
{
    int mode, ret;
    struct response resp;
    
    ret = send_command(fd, "AT+QCFG=\"usbnet\"", &resp, 0);
    if (ret != 1)
        return -1;
    
    ret = sscanf(resp.lines[0], "+QCFG: \"usbnet\",%d", &mode);
    if (ret != 1)
    {
        printf("%s: sscanf() ret=%d\n", __func__, ret);
        return -1;
    }
    printf("usbnet mode: %d\n", mode);
    if (mode == 3)
        return 0;
    
    ret = send_command(fd, "AT+QCFG=\"usbnet\",3", &resp, 0);
    if (ret != 1)
        return -1;
    return restart_device(fd);
}

/**
 * 激活網絡。
 *
 * @param fd 串口的文件描述符。
 * @param apn 接入點名稱(Access Point Name)。一般和SIM卡的運營商有關。
 * @retval 0 激活網絡成功。
 * @retval -1 激活網絡失敗。
 * @note 激活網絡成功後,linux系統裡面就可以上網了,可以ping通百度。
 */
int activate_network(int fd, const char *apn)
{
    char cmd[100];
    char *p, *q;
    int connect_type, id, ret;
    struct response resp;
    
    // 檢查場景是否已激活
    ret = send_command(fd, "AT+QIACT?", &resp, 0);
    if (ret != 1)
        return -1; // 檢查失敗
    if (strcmp(resp.lines[0], "OK") == 0)
    {
        // 只返回了OK,表示場景未激活, 需要激活場景
        snprintf(cmd, sizeof(cmd), "AT+QICSGP=1,1,\"%s\","","",1", apn);
        ret = send_command(fd, cmd, &resp, 0);
        if (ret != 1)
            return -1;
       
        // 受網絡狀態影響,執行AT+QIACT後,等待返回結果OK或者ERROR的最大時間為150秒,在結果尚未返回之前,無法執行任何AT命令。
        ret = send_command(fd, "AT+QIACT=1", &resp, 150);
        if (ret != 1)
            return -1; // 激活失敗
       
        ret = send_command(fd, "AT+QIACT?", &resp, 0);
        if (ret != 1)
            return -1;
    }
    
    // 顯示PDP場景編號
    ret = sscanf(resp.lines[0], "+QIACT:%d", &id);
    if (ret != 1)
    {
        printf("%s: sscanf() ret=%d\n", __func__, ret);
        return -1;
    }
    printf("PDP Context ID: %d\n", id);
    
    // 顯示IP位址
    p = strchr(resp.lines[0], '"');
    if (p == NULL)
    {
        printf("IP address: unknown\n");
        return -1;
    }
    p++;
    q = strrchr(p, '"');
    if (q != NULL)
        *q = '\0';
    printf("IP address: %s\n", p);
    
    // 允許網絡數據包傳遞到linux內核
    ret = send_command(fd, "AT+QNETDEVCTL?", &resp, 0);
    if (ret != 1)
        return -1;
    ret = sscanf(resp.lines[0], "+QNETDEVCTL: %d\n", &connect_type);
    if (ret != 1)
    {
        printf("%s: sscanf() ret=%d\n", __func__, ret);
        return -1;
    }
    if (connect_type != 3)
    {
        ret = send_command(fd, "AT+QNETDEVCTL=3,1,1", &resp, 0);
        if (ret != 1)
            return -1;
       
        ret = send_command(fd, "AT+QNETDEVCTL?", &resp, 0);
        if (ret != 1)
            return -1;
        ret = sscanf(resp.lines[0], "+QNETDEVCTL: %d\n", &connect_type);
        if (ret != 1)
        {
            printf("%s: sscanf() ret=%d\n", __func__, ret);
            return -1;
        }
        if (connect_type != 3)
        {
            printf("%s: connect_type=%d\n", __func__, connect_type);
            return -1;
        }
    }
    return 0;
}

/**
 * 主函數。
 *
 * @param argc 參數個數。
 * @param argv 參數列表。
 * @retval 0 程序運行成功。
 * @retval -1 程序運行出錯。
 */
int main(int argc, char *argv[])
{
    int fd = -1;
    int ret;
    
    ret = parse_arguments(argc, argv);
    if (ret == -1)
        return -1;
    
restart:
    if (fd != -1)
    {
        close(fd);
        fd = -1;
        do
        {
            sleep(10);
        } while (access("/dev/ttyUSB1", 0) == -1);
        printf("device restarted\n");
    }
    
    fd = open("/dev/ttyUSB1", O_RDWR);
    if (fd == -1)
    {
        perror("open() failed");
        return -1;
    }
    
    ret = configure_uart(fd, 115200);
    if (ret == -1)
        goto end;
    
    if (!is_device_ready(fd))
    {
        printf("device is not ready\n");
        ret = -1;
        goto end;
    }
    
    ret = enable_rndis_mode(fd);
    if (ret == -1)
    {
        printf("failed to enable RNDIS mode\n");
        goto end;
    }
    else if (ret == RESTART)
        goto restart;
    
    ret = activate_network(fd, "cmnet");
    if (ret == -1)
    {
        printf("failed to activate network\n");
        goto end;
    }
    
    ret = 0;
end:
    close(fd);
    return ret;
}
巨大八爪鱼勘誤:ret = send_command(fd, "AT+QIACT=1", &resp, 150);寫錯了,最後一個參數應該是150000。
巨大八爪鱼在usbnet 3號模式下,EC200M模塊的USB interface 0和1(Mobile RNDIS Network Adapter)用的是linux內核自帶的RNDIS驅動。
如果不是3號模式,而是1號模式的話,那麼USB接口的排布是不一樣的。
root@rk3308b-buildroot:/root# cat /sys/bus/usb/devices/2-1/2-1:1.0/interface
Mobile ECM Network Adapter
root@rk3308b-buildroot:/root# cat /sys/bus/usb/devices/2-1/2-1:1.1/interface
cat: '/sys/bus/usb/devices/2-1/2-1:1.1/interface': No such file or directory
root@rk3308b-buildroot:/root# cat /sys/bus/usb/devices/2-1/2-1:1.2/interface
Mobile Diag Interface
root@rk3308b-buildroot:/root# cat /sys/bus/usb/devices/2-1/2-1:1.3/interface
Mobile AT Interface
root@rk3308b-buildroot:/root# cat /sys/bus/usb/devices/2-1/2-1:1.4/interface
Mobile AT Interface
root@rk3308b-buildroot:/root#
並且EC200M有6個接口,比EC200A多了一個6號接口。
root@rk3308b-buildroot:/root# cat /sys/bus/usb/devices/2-1/2-1:1.6/interface
Mobile NMEA Interface
root@rk3308b-buildroot:/root#

在usbnet 1號模式下,EC200M模塊的USB interface 0(Mobile ECM Network Adapter)和1(無接口名稱)用的是linux內核自帶的cdc_ether驅動(net/usb/cdc_ether.c)。
root@rk3308b-buildroot:/root# ls -l /sys/bus/usb/devices/2-1/2-1:1.0/driver
lrwxrwxrwx 1 root root 0 Jan  1 00:46 /sys/bus/usb/devices/2-1/2-1:1.0/driver -> ../../../../../../bus/usb/drivers/cdc_ether
root@rk3308b-buildroot:/root# ls -l /sys/bus/usb/devices/2-1/2-1:1.1/driver
lrwxrwxrwx 1 root root 0 Jan  1 00:48 /sys/bus/usb/devices/2-1/2-1:1.1/driver -> ../../../../../../bus/usb/drivers/cdc_ether
root@rk3308b-buildroot:/root# ls -l /sys/bus/usb/devices/2-1/2-1:1.2/driver
lrwxrwxrwx 1 root root 0 Jan  1 00:46 /sys/bus/usb/devices/2-1/2-1:1.2/driver -> ../../../../../../bus/usb/drivers/option
root@rk3308b-buildroot:/root# ls -l /sys/bus/usb/devices/2-1/2-1:1.3/driver
lrwxrwxrwx 1 root root 0 Jan  1 00:46 /sys/bus/usb/devices/2-1/2-1:1.3/driver -> ../../../../../../bus/usb/drivers/option
root@rk3308b-buildroot:/root# ls -l /sys/bus/usb/devices/2-1/2-1:1.4/driver
ls: cannot access '/sys/bus/usb/devices/2-1/2-1:1.4/driver': No such file or directory
root@rk3308b-buildroot:/root# ls -l /sys/bus/usb/devices/2-1/2-1:1.6/driver
ls: cannot access '/sys/bus/usb/devices/2-1/2-1:1.6/driver': No such file or directory
root@rk3308b-buildroot:/root#
巨大八爪鱼在usbnet 1號模式下,usb interface 1無接口名稱。但在usbnet 3號模式下,usb insterface 1有接口名稱,名稱是Mobile RNDIS Network Adapter。

內容轉換:

回覆帖子
內容:
用戶名: 您目前是匿名發表。
驗證碼:
看不清?換一張