USB Serie y SPI con Teensy 4.0

Mi caso de uso requiere que envíe Computadora (USB) -> Teensy 4.0 -> SPI -> Periférico. Estoy intentando enviar datos a través de USB (potencialmente serie USB o hay alguna otra forma). Mi requisito de transferencia de datos es enviar datos de 40,16 bits, 4000 veces por segundo, lo que se traduce en alrededor de 2,56 Mbits por segundo.

Planeo almacenar en caché 1 segundo de datos, que son 2560000 bits o 320 Kilobytes o 312,5 Kibibytes

Busco orientación sobre:

  1. El mecanismo de almacenamiento en caché (mutex) y 1024 bytes de disponibilidad de RAM transfieren datos en paralelo desde USB a Teensy 4.0 a SPI. es decir, enhebrar dentro de Teensy / Arduino.
  2. ¿Cómo transferir efectivamente 320kb de datos por segundo a través de USB a Teensy 4.0?
  3. ¿Transferencia en serie basada en texto o bytes hexadecimales? Cualquier enlace o artículo.
  4. ¿Hay algún otro lenguaje para programar Teensy 4.0 para este propósito que pueda ser más efectivo que Arduino (TeensyDuino)?
Creo que vas a encontrar esto bastante difícil. Puede intentar usar el puerto serie USB incorporado del ecosistema (se ignora la velocidad en baudios) y asegurarse de que el sistema operativo del host pase tamaños de búfer bien elegidos (como una suposición, los búferes son múltiplos del tamaño de un paquete). Definitivamente querrías usar una transferencia binaria para evitar gastos generales. Sin embargo, es posible que necesite implementar un esquema más óptimo a bajo nivel en ambos lados.
Si esto es efectivamente una necesidad de transmisión que requiere velocidad masiva pero no "agilidad" en el sentido de preguntas y respuestas, podría considerar en su lugar un chip USB-FIFO con capacidad SPI, por ejemplo, FT2232H, Cypress FX2/FX3, etc. o sus seguidores más recientes. complementos O tal vez podría mover una parte más grande de la tarea que comprende tanto la función de la PC como el host SPI a algo como una Raspberry Pi (o sus equivalentes más robustos). También puede "trocear" la transmisión de SPI en escrituras de registros individuales con una máquina de estado simple en un CPLD.
Parece que tiene varias preguntas relacionadas que se remontan a unos meses, incluidos algunos experimentos con FIFO USB-SPI de gama baja. Como tal, realmente ayudaría si pudiera "editar" esta pregunta actual para explicar mejor el objetivo general del proyecto, de qué está hablando y cuáles son los requisitos finales, cómo se ve el flujo de datos en términos de tiempo frente a cualquier externo. eventos, etc., y qué problemas con los intentos anteriores lo pusieron en este camino.
@ChrisStratton, estoy intentando ejecutar el chip AD5370 a través de una solución de microcontrolador/usb en la que puedo enviarle señales a un ritmo rápido. Mi tasa ideal es 4k (16 bits) muestras/segundo/canal. Lo hice POC con FT232H, Raspberry Pi y algunas otras opciones. El más cercano ha sido FT232H, en el que pude obtener muestras de 4k (16 bits) por segundo por canal. Pero el chip parece fallar a menudo. En teoría, FT232H tiene una velocidad SPI de 30 Mhz, AD5370 puede funcionar hasta 50 Mhz, pero para mi caso de uso, todo lo que necesito es un SPI constante de 3,84 Mhz.
Si optimizo los paquetes de datos desde USB al microcontrolador, potencialmente puedo transmitir solo 2,56 Mbits por segundo a través de USB a MC, luego aumentarlo hasta 3,84 Mbit a través de SPI (dirección de canal) al chip AD5370. Todo esto parece teóricamente posible, pero en la práctica parece bastante desafiante. Tengo POC que ejecutan 20k muestras por segundo para 1 canal. Cuál es el límite teórico de AD5370. El siguiente chip más cercano que parece alcanzar la velocidad SPI es Teensy 4.0, pero todavía no he descubierto un protocolo de comunicación USB óptimo de la computadora a Teensy.
Con un software y un almacenamiento en búfer eficientes, esto puede funcionar a toda velocidad de USB, pero puede ser complicado; quieres pensar en DMA, no en "hilos". Creo que Teensy tiene sus propios foros, o puede usar recursos más genéricos de Kinetis, MBED, etc. para el MCU real involucrado. Uno de los IC FIFO USB2 o USB3 debería tener fácilmente la velocidad; "choque" parece que podría ser un problema eléctrico en el bus USB que podría aplicarse a cualquier tipo de solución dependiendo de la desafortunada coincidencia de factores.

Respuestas (1)

En realidad, para una tasa de datos T4.xa de 300kB sobre el puerto serie virtual no es exigente en absoluto.

Sugiero usar un búfer de anillo simple (por ejemplo, https://github.com/tonton81/Circular_Buffer ) para almacenar los datos recibidos.

Para lograr su requisito de transferencia de datos (1 bloque de 40 palabras de 16 bits cada 250 µs), lo mejor sería enviar un bloque de datos en un temporizador ISR e invocar este ISR con 4 kHz.

Aquí hay una prueba del código de principio que muestra cómo lograr esto:

    #include "SPI.h"
    #include "circular_buffer.h"
    
    Circular_Buffer<uint16_t, 16*1024> ringBuffer;  // adjust buffer size if necessary
    
    constexpr unsigned CS = 0;
    constexpr size_t blockSize = 40;
    
    void sendSPI()
    {
        if (ringBuffer.available() >= blockSize) // send only a complete data block
        {
            SPI.beginTransaction(SPISettings(4'000'000, MSBFIRST, SPI_MODE0));
            digitalWriteFast(CS, LOW); // assuming an active low chip select
            for (unsigned i = 0; i < blockSize; i++)
            {
                SPI.transfer16(ringBuffer.read()); // read one entry from the buffer and send it
            }
            digitalWriteFast(CS, HIGH);
            SPI.endTransaction();
        }
    }
    
    IntervalTimer spiTimer;
    
    void setup()
    {
        pinMode(CS, OUTPUT);
        SPI.begin();        
        spiTimer.begin(sendSPI, 1'000'000 / 4000); // transfer one block every 250µs
    }
    
    uint16_t oldDatum = -1; // error checking, we expect incrementing data from serial
    
    void loop()
    {
        while (Serial.available() && ringBuffer.available() < ringBuffer.capacity())
        {
            uint16_t datum;
            Serial.readBytes((char*)&datum, 2);
            ringBuffer.write(datum);
    
            uint16_t diff = datum - oldDatum;    
            if(diff != 1)
            {
                SerialUSB1.printf("Error @%04X\n", datum);
            }
            oldDatum = datum;
        }    
    }

Siempre que haya espacio disponible, el código copia los datos en serie recibidos en el búfer circular. La parte de envío impulsada por la interrupción del temporizador lee un bloque de 40 palabras del búfer circular y lo envía a su dispositivo.

Para verificar si se pierde algún dato, el código verifica los bytes entrantes e imprime cualquier error (los datos no se incrementan) en la segunda conexión en serie.

Usé la siguiente aplicación C# simple para probar el firmware.

    class Program
    {
        static void Main(string[] args)
        {                                                       
            int bufSize = 2 * 64 * 1024;               // generate some dummy data (incrementing 16bit values)
            var data = new byte[bufSize];              // any buffer size would work. I chose 128kB to get a nice data wrap around for the simple error detection in the firmware
            for (int i = 0; i < bufSize / 2; i++)
            {
                var bytes = BitConverter.GetBytes(i);  // convert from uint16 to byte[4]
                data[i * 2] = bytes[0];
                data[i * 2 + 1] = bytes[1];
            }

            // Send out the data as quick as possible, the underlying algorithms will pause sending if the FW doesn't read the bytes from the stream. 
            // -> this is auto syncing the rate with the FW read rate. 
            using (var port = new SerialPort("COM11"))
            {
                port.Open();
                while (!Console.KeyAvailable)      // stop program on any key press
                {
                    port.Write(data, 0, bufSize);  // send out data, driver will pause if necessary
                }
            }
        }
    }
}

Tanto FW como SW funcionan sin problemas aquí y no observé ninguna pérdida de datos. El controlador de PC subyacente es lo suficientemente inteligente como para pausar el envío cuando el FW no está leyendo los datos de la transmisión. Por lo tanto, obtiene una sincronización de la tasa de envío y recepción de datos de forma gratuita.

Aquí algunos resultados de medición para la frecuencia SPI de 4 MHz:

ingrese la descripción de la imagen aquí

Aumentado:ingrese la descripción de la imagen aquí

Y aquí una medida con frecuencia SPI de 32 MHz (no puedo resolver detalles con mi LA a esta velocidad). Pero puede ver que enviar un bloque (80 bytes) se realiza en aproximadamente 32 µs.

ingrese la descripción de la imagen aquí

esto es exactamente lo que quería "1 bloque de 40 palabras de 16 bits cada 250 µs" a través de USB a Teensy. Déjame analizar esto en detalle y espero poder entender lo que dijiste. Es posible que me comunique con usted con algunas preguntas aclaratorias. Gracias de nuevo