如何通过 USB 实现从 Pi 4 到 Pi Pico 的快速数据传输

问题描述 投票:0回答:1

我正在尝试建立从 PI 4 到 Pi Pico 的“快速”数据传输。根据规格,Pico 的 USB 控制器应该能够达到 12 Mbit/s。

方法#1

我当前使用的代码速度很慢。如果我们接收到一个字节,代码将 GPIO 引脚 2 设置为高电平;在接收到 2048 个字节后或在超时的情况下设置为低电平:

#include <stdio.h>
#include "pico/stdlib.h"

const uint8_t DEBUG_PIN = 2;

int main() {

    stdio_usb_init();

    uint16_t numBytesReceived = 0; 
    int currentChar = PICO_ERROR_TIMEOUT;

    gpio_init(DEBUG_PIN);
    gpio_set_dir(DEBUG_PIN, GPIO_OUT);

    while (true) 
    {
         currentChar = getchar_timeout_us(50000);        
         if (currentChar != PICO_ERROR_TIMEOUT) {
             gpio_put(DEBUG_PIN, 1);
             numBytesReceived++;

             if (2048 == numBytesReceived) {
                 gpio_put(DEBUG_PIN, 0);
                 numBytesReceived = 0;                
             }
         }
         else {
             gpio_put(DEBUG_PIN, 0);         
             numBytesReceived = 0;
        
         }
     }
  }

在我的 Pi 4 上,我使用代码进行测试

import serial
import time
serialPort = "/dev/ttyACM0"

ser = serial.Serial(
    port=serialPort
)
while True:
    data_array = bytearray(2048)
    ser.write(data_array)
    ser.flush()
    time.sleep(0.2)

用示波器测量,传输时间在24ms到29ms之间。在最好的情况下,我的吞吐量为

2048 * 8 / 0.024ms = 680 kBit/s,

距离 12 Mbit/s 的目标还很远。

方法#2

使用 pico 示例 dev_lowlevel。我注释掉了所有 printf() 调用并修改了 main()ep1_out_handler() 以用于测量目的,如下所示:

int main(void) {
    stdio_init_all();
    //printf("USB Device Low-Level hardware example\n");
    usb_device_init();

    gpio_init(DEBUG_PIN);
    gpio_set_dir(DEBUG_PIN, GPIO_OUT);
    g_debug_state = 0;
    gpio_put(DEBUG_PIN, g_debug_state);
    g_debug_state = 1;


    // Wait until configured
    while (!configured) {
        tight_loop_contents();
    }

    // Get ready to rx from host
    usb_start_transfer(usb_get_endpoint_configuration(EP1_OUT_ADDR), NULL, 64);

    // Everything is interrupt driven so just loop here
    while (1) {
        tight_loop_contents();
    }
}

void ep1_out_handler(uint8_t *buf, uint16_t len) {
    gpio_put(DEBUG_PIN, g_debug_state);
    g_debug_state = ! g_debug_state;

    struct usb_endpoint_configuration *ep = usb_get_endpoint_configuration(EP2_IN_ADDR);
    usb_start_transfer(ep, NULL, 0);
}

在主机端,我修改了 dev_lowlevel_loopback.py,如下所示:

import usb.core
import usb.util
import time
# find our device
dev = usb.core.find(idVendor=0x0000, idProduct=0x0001)

# was it found?
if dev is None:
    raise ValueError('Device not found')

# get an endpoint instance
cfg = dev.get_active_configuration()
intf = cfg[(0, 0)]

outep = usb.util.find_descriptor(
    intf,
    # match the first OUT endpoint
    custom_match= \
        lambda e: \
            usb.util.endpoint_direction(e.bEndpointAddress) == \
            usb.util.ENDPOINT_OUT)

inep = usb.util.find_descriptor(
    intf,
    # match the first IN endpoint
    custom_match= \
        lambda e: \
            usb.util.endpoint_direction(e.bEndpointAddress) == \
            usb.util.ENDPOINT_IN)

assert inep is not None
assert outep is not None


test_string = bytearray(64)
while True:
    for _ in range(32):
        outep.write(test_string)
        inep.read(0)
    time.sleep(0.2)

这里,传输时间在 16 毫秒到 19 毫秒之间变化,吞吐量为:

2048 * 8 / 0.016ms = 1024 kBit/s - 仍然很慢。

我可能会在每次写入后调用 inep.read(0) 来浪费一些时间,但是将其忽略会给出

回溯(最近一次调用最后一次):文件 “/home/pi/repositories/pico-examples/usb/device/dev_lowlevel/dev_lowlevel_loopback.py”, 第 56 行,在 outep.write(test_string)

[...]

usb.core.USBTimeoutError: [Errno 110] 操作超时

下一次写。

在函数usb_start_transfer(...)我们可以找到注释

void usb_start_transfer(struct usb_endpoint_configuration *ep, uint8_t *buf, uint16_t len) {
    // We are asserting that the length is <= 64 bytes for simplicity of the example.
    // For multi packet transfers see THE tinyusb port.

我试图找到tinusb端口,但迷失在Pico的所有tinusb示例中。

方法#3

我尝试使用usb-test。我修改了 static bool usbtest_xfer_cb(...) 以用于测量和调试目的,如下所示:

static bool usbtest_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
{
    USBTEST_LOG1("%s: ep_addr=0x%02x result=%u xferred_bytes=%u\n", __func__, ep_addr, result, xferred_bytes);

    board_led_activity();

    TU_VERIFY(result == XFER_RESULT_SUCCESS);

    if (!xferred_bytes)
        USBTEST_LOG2("                 ZLP\n");

    if (ep_addr == _bulk_out) {        
        TU_ASSERT ( usbd_edpt_xfer(rhport, _bulk_out, _bulk_out_buf, sizeof(_bulk_out_buf)) );
        board_led_on();
        gpio_put(DEBUG_PIN, g_debug_state);
        g_debug_state = !g_debug_state;
        
    }
    else if (ep_addr == _bulk_in) {
        TU_ASSERT ( usbd_edpt_xfer(rhport, _bulk_in, _bulk_in_buf, sizeof(_bulk_in_buf)) );
        //gpio_put(DEBUG_PIN, g_debug_state);
        //g_debug_state = !g_debug_state;
    }
    else
        return false;

    return true;
}

test.py 中,我仅启用了测试编号 27,该测试被标记为“批量写入”。像这样调用脚本

sudo python test.py -t -d

给出输出

#27 迭代=1 长度=512 变化=256 sglen=1 :: 0.616 ms - 811.7 kB/s dmesg: usbtest 1-1.3:1.0: TEST 27: 批量写入 0Mbytes

根据输出,应该已经发送了一些数据。但 LED 均未亮起(请参阅上面的代码),示波器也未显示 GPIO-2 上的任何移动。看起来根本不起作用。

问题

如何通过 USB 从 Pi 4 到 Pi Pico 快速传输数据?

raspberry-pi usb raspberry-pi-pico
1个回答
0
投票

为什么 getchar_timeout_us(uint32_t timeout_us) 这么慢?

如果您深入研究源代码(SDK 版本 1.5.1),您将看到 getchar_timeout_us(uint32_t timeout_us) 为每个字节调用 int stdio_usb_in_chars(char *buf, int length) (源)

int stdio_usb_in_chars(char *buf, int length) {
    // [...]
    int rc = PICO_ERROR_NO_DATA;
    if (stdio_usb_connected() && tud_cdc_available()) {
        if (!mutex_try_enter_block_until(&stdio_usb_mutex, make_timeout_time_ms(PICO_STDIO_DEADLOCK_TIMEOUT_MS))) {
            return PICO_ERROR_NO_DATA; // would deadlock otherwise
        }
        if (stdio_usb_connected() && tud_cdc_available()) {
            int count = (int) tud_cdc_read(buf, (uint32_t) length);
            rc = count ? count : PICO_ERROR_NO_DATA;
        } else {
            // because our mutex use may starve out the background task, run tud_task here (we own the mutex)
            tud_task();
        }
        mutex_exit(&stdio_usb_mutex);
    }
    return rc;
}

对于每个字节,我们检查 USB 是否已连接、cdc 是否可用并锁定互斥体。

最好直接使用stdio_usb_in_chars

直接使用stdio_usb_in_chars,我可以达到迄今为止最高的传输速率。这是 pico 的测试代码:

extern "C" {
    int stdio_usb_in_chars(char *buf, int length);
}

bool dataAvailableCallback(struct repeating_timer *t) {
    printf("%lu %f\n", bytes_read, (float)bytes_read / num_reads);
    data_available = false;
    bytes_read = 0;
    num_reads = 0;
    return true;
}

int main() {
    stdio_init_all();

    struct repeating_timer timer;
    add_repeating_timer_ms(-1000, dataAvailableCallback, NULL, &timer);
    int bytesRed = 0;
    while(true) {
        bytesRed = stdio_usb_in_chars(cdc_buffer, sizeof(cdc_buffer));
        if (bytesRed > 0) {
            bytes_read += bytesRed;
            num_reads++;   
        }    
    }
 }

代码读取尽可能多的数据。每秒我们都会打印红色的字节数(以及每次调用stdio_usb_in_chars的红色字节数)。

在 Pi4 上我使用了这个脚本:

#!/usr/bin/env python3

import sys, struct, serial

out_data = bytes([x % 255 for x in range(0, 2048)])

with serial.serial_for_url(sys.argv[1], timeout = 1) as f:
    while True:
        f.write(out_data)
        f.flush()
        waiting = f.in_waiting
        if waiting > 0:
            data = f.read(waiting)
            print(data)

输出示例:

b'512192 64.000000\r\n'
b'511616 64.000000\r\n'
b'509184 64.000000\r\n'
b'510016 64.000000\r\n'

因此,我们可以传输 500 kByte/sec = 4 MBit/sec。这距离 12 MBit/sec 的理论最大值还很远,但比之前的所有东西都要好。

© www.soinside.com 2019 - 2024. All rights reserved.