Intercambio de datos entre dos Teensy 4.0 y una PC: bailando con una pandereta





Decidí escribir esta publicación con la esperanza de que la experiencia que obtuve y las soluciones que encontré puedan ser útiles para aquellos que enfrentan problemas similares en proyectos donde se supone que es el intercambio de datos entre varios Teensy y una PC.



Comenzó a finales de marzo. Mi amigo (un ingeniero electrónico puro y nunca un programador) pidió ayuda para programar la instalación que quería iniciar. Exteriormente, todo parecía bastante simple: hay 2 ADC de alta velocidad ( ADS7886 ) más algunos periféricos adicionales que deben controlarse. Los ADC deben consultarse con la mayor frecuencia posible, preferiblemente con su frecuencia máxima (1 MHz), una parte de los datos debe guardarse sin cambios y el resto debe procesarse. Y todos los datos, después de completar las mediciones y el procesamiento, se transfieren a una PC. Teniendo en cuenta el requisito "con la mayor frecuencia posible", sugerí utilizar 2 piezas de Teensy 4.0- Tienen excelentes parámetros (frecuencia de reloj de 600 MHz, mucha memoria, buena conexión y todo esto en un factor de forma microscópico). Bueno, además, la capacidad de programar en la versión avanzada del IDE de Arduino (algo más complicado para tal tarea ya es excesivo).



La codificación y la depuración se realizaron de forma remota: primero usando TeamViewer, y luego, cuando comenzó a interrumpir la sesión después de 60 segundos, cambiamos a Google Remote Desktop. Físicamente, nunca he aparecido cerca de la instalación y no sé cómo se ve y en general funciona.



Un par de meses antes de eso, hice mi proyecto en Teensy, un medidor de biopotencial de 8 canales basado en ADS1298, y estaba muy satisfecho con esta "migaja" (así es como suena el nombre del controlador traducido libremente). Por lo tanto, le recomendé a mi amigo que usara un par de Teensy 4.0, con lo que estuvo de acuerdo y quedó muy satisfecho.



Además, un Tincy debe ser el maestro (COM3) y el otro, el esclavo (esclavo, COM5), es decir. el maestro se controla directamente desde la PC, y el esclavo (al menos parcialmente) es controlado por la UART desde el maestro. Al mismo tiempo, inicialmente se asumió que ambos controladores estarían conectados a una PC y transferirían los datos acumulados a ella a través de USB.



Inicialmente, la visión del problema se veía así: depuramos la funcionalidad en el maestro Tinsey, luego transferimos el software al esclavo, lo ajustamos de acuerdo con su funcionalidad y nos aseguramos de que todo funcione. Lucro.



Inicialmente, asumí que de 2 a 2,5 semanas serían suficientes para todo. Obviamente, una semana no será suficiente, pero hasta TRES semanas ... Bueno, ¿qué se puede hacer durante tanto tiempo? Después de todo, todas las trampas deben surgir al depurar el software para el líder, y luego, solo necesita examinar cuidadosamente la lógica del seguidor.



Ok, tan pronto como se dice que se hace.



Al principio, todo fue bastante rápido. Host Tinsey recopiló datos (con un intervalo de un poco menos de 2 microsegundos, no funcionó más rápido) y los envió a la PC. Pasha (como llamaré convencionalmente a mi camarada) estaba bastante contento con eso. La siguiente etapa es realizar secuencialmente 100 ciclos de medición, procesando cada ciclo, almacenando los resultados del procesamiento para cada ciclo en la memoria de Tinsi.



Pero al transferir los datos recibidos, surgió el primer problema. Al usar la misma función de transmisión, la primera matriz de datos ("sin procesar") se truncó de la manera más cruel y despiadada, mientras que la segunda matriz (con la misma estructura de datos y aproximadamente la misma longitud) se pasó sin problemas. Esos. la transferencia de la primera matriz podría interrumpirse en el byte 1048, 2036 o 4076 (con una longitud de matriz de 28800 bytes), después de lo cual Tinsey consideró que su tarea se había completado por completo.



Dado que en este proyecto la tarea principal no es la belleza y elegancia del código, sino su máxima confiabilidad y máxima velocidad de avance, decidí transferir el primer arreglo en paquetes. Se eligió un tamaño de paquete de 512 bytes. Tincy envía el tamaño total del archivo a la PC, la PC calcula cuántos paquetes deben enviarse y le pide a Tincy el siguiente paquete, indicando su desplazamiento y longitud. La longitud debe indicarse porque el último paquete suele tener un tamaño inferior a 512 bytes.



Por cierto, al final, la segunda matriz ya no se transmitía por completo y también la transferí a la transmisión en paquetes.



Bien, sobrevivimos a este problema. Host Tinsey comenzó a transferir todos los datos necesarios a la PC sin ningún problema. Ahora era el turno del seguidor.



Naturalmente, también estaba conectado a una PC, y me registré en el programa de la PC (estaba escrito en WPF) para trabajar con dos puertos COM: COM3 - maestro de Tinsi, COM5 - esclavo. Excelente. Encendemos la instalación y ... ¡Veo un comando en el controlador de datos recibidos de COM5 que DEBE ser transferido a COM3! Naturalmente, dado que este comando NO va a COM3, todo se va al infierno. ¿Cómo es esto, camaradas?



Siguiendo nuestro principio fundamental ("adelante y rápido"), se decidió abandonar por completo la comunicación directa entre el esclavo Tincy (COM5) y la PC, y transferir todo el control de COM5 y la transferencia de datos desde él exclusivamente a través de la UART, utilizando el master Tincy (COM3) como una estación de transferencia. Como resultado, se restauró el funcionamiento normal de COM3. Ahora bien.



Sin embargo, después de eso tuve que pasar varios días para escribir cuidadosamente la lógica de interacción entre PC, COM3 y COM5. Teniendo en cuenta que lo que está sucediendo en las profundidades de Tincy sigue siendo una caja negra para el programador (si no usa el software comprado para depurar para Arduino, por ejemplo Visual Micro ), la única forma en que puedo juzgar su funcionamiento más o menos normal es el datos que emite a la PC.



Por lo tanto, todo el trabajo del host Tincy estaba total y completamente subordinado a los comandos de la PC: la PC emite un comando "preparatorio", Tingxi confirma la recepción del comando, la PC emite el comando para su ejecución. Una especie de apretón de manos. Las respuestas de Tincy se transmiten en formato binario o de texto (según el modo de funcionamiento), por ejemplo: "6 28800", donde 6 es el código de comando (quiero transmitir la primera matriz de datos), 28800 es la cantidad de datos que se van a transferido, en bytes. Se utilizó un apretón de manos similar para la interacción entre ambos Tincy.



Este enfoque de la interacción le permite mantener un registro de lo que está sucediendo, lo que puede, teóricamente, identificar el lugar donde surge el problema. Si necesita ir a la transferencia de datos binarios (transmisión por lotes de resultados de medición), entonces, en este caso, los datos no se escriben en el registro.



Otra ventaja del apretón de manos es que le permite deshacerse del "pegado" de comandos: debido al hecho de que Tinsey transfiere datos a la PC a alta velocidad (la transferencia de datos a través de USB es a una velocidad de 12 Mbps), tiene tiempo para transmitir el siguiente comando para que llegue al final del búfer de recepción de la PC, por ejemplo: "6 28800 5"), donde 5 es el comando para la siguiente operación. Los retrasos en la transferencia de datos no ayudaron. Aquí debe analizar la cadena recibida (teniendo en cuenta todas las opciones posibles) o usar un apretón de manos, que le permite trabajar sin dolor de cabeza.



Volvemos a nuestros carneros. Conecte COM3 y COM5, inicie el programa. COM3 funciona sin problemas. Pero COM5 ... En lugar del esperado (condicionalmente) "6 28800" - tenemos "6 1" o "6 8400" o en general "6 0". Es decir, COM5 recibe un comando de control de COM3 y reacciona a él, pero los datos que envía se encuentran en un vacío esférico profundo. Entonces, ¿quién tiene la culpa?



Lo primero que me viene a la mente es que hay un problema en algún lugar de la UART. Simplemente no hay nadie más contra quien pecar. Excelente. Hacemos un programa de prueba: COM5 genera 100 números (del 0 al 99) y los envía a COM3, y ese envía directamente los datos recibidos a la PC. De hecho, los datos se transmiten con grandes lagunas. Incluso si la velocidad en baudios se reduce de 500 kbaudios a 9600 baudios. WTF ???



Perdí un par de días más tratando de derrotar esta basura con un empujón científico. No ayudó.



Pero aquí, como dicen, "no habría felicidad, pero la desgracia ayudó".



Desde el principio de trabajar con dos Tincy, notamos que puede programarlos solo uno a la vez: si, al conectar solo COM3 (o solo COM5), puede cargarles el programa usando los botones Arduino IDE en la pantalla , entonces si ambos Tincy están conectados a la PC, pueden ser programados más o menos exitosamente solo presionando físicamente el botón de reinicio en la placa Tinsey. Esos. software compilado para COM3 - presionó el botón, espere hasta que aparezca el apreciado Reboot Ok. Después de eso, compilé el software para COM5, presioné su botón, esperando reiniciar Ok.



Entonces, este mecanismo también se rompió en algún momento. Esos. COM5, al intentar reprogramar, inmediatamente dejó de ser detectado como un dispositivo Tincy. Independientemente de cuántas veces lo desconectamos, esperamos a que este dispositivo desapareciera de la lista de dispositivos y se conectaran, esperando que apareciera en la lista. Además, ¡esta enfermedad también se ha extendido a COM3!



Entonces, unas vagas dudas empezaron a atormentarme. ¿No es el hecho de la conexión simultánea de COM3 y COM5 al mismo PC una fuente de problemas?



Para probar esta hipótesis, convencí a Pasha de que alimentara COM5 desde un PowerBank (portátil antiguo) de terceros. Y - ¡he aquí! - UART comenzó a transmitir datos sin pérdida y a cualquier velocidad (de 9600 a 500k). Después de eso, Pasha alimentó COM5 desde una fuente de + 5V, que está disponible en la instalación, colocando un diodo en serie para protección, como se recomienda en el manual de Tinsi.



El único pero: inicialmente en las pruebas COM5 no quería transferir más de 64 bytes (1 byte de comando + 63 bytes de datos), después de 63 bytes de datos, el contador de datos transmitidos se restableció a 0. El problema se resolvió expandiendo el transmitir / recibir búferes a 9000 bytes:



Serial1.addMemoryForRead (búfer, 9000);

Serial1.addMemoryForWrite (búfer, 9000);



Entonces en el modo de funcionamiento, COM5 se alimenta de su propia fuente, y durante la programación, se conecta a un PC a través de USB (con COM3 desconectado). Desafortunadamente, esto no resolvió el problema con la transferencia de resultados de medición desde COM5, pero ahora sabía con certeza que el UART no tenía nada que ver con eso y tuve que buscar en mi propio código. Después de eso, la victoria de la razón sobre la sansaparilla se percibió solo como una cuestión de tiempo.



Era el día 28 desde el inicio de las obras del proyecto ... Y luego se perdió otro mes porque me enfermé de COVID-19 ... Pero al final, se reanudó el trabajo en el proyecto. Y luego hubo otro problema relacionado con la UART.



La transferencia de datos: tanto los comandos de un solo byte como las matrices de 3-5 bytes (comando + parámetros) de COM5 a COM3 se llevaron a cabo sin problemas, vi los datos transmitidos en la PC y no hubo errores. Pero una matriz similar no pasó de COM3 a COM5, solo el primer byte. Además, la función que recibe datos en ambos controladores fue la misma. Reorganizar los controladores no resolvió el problema. Dado que la matriz transferida de COM3 a COM5 contenía información sobre el desplazamiento del inicio de la lectura de datos y sobre la cantidad de datos solicitados por la PC, en respuesta, yo, naturalmente, no recibí nada. Agregar retrasos a la rutina de lectura del búfer UART no resolvió el problema.



Ya queríamos usar I2C para transferir los datos acumulados del esclavo Tincy al maestro, ya que había suficientes patas libres, pero aquí había un fastidio: había un alto potencial en la salida, donde debería haber estado la señal del reloj, y no hubo pulsos. Mientras navegaba por la Web en busca de una solución al problema, encontré en algún foro una mención de que en Arduino el funcionamiento simultáneo de UART e I2C es imposible. Es cierto que esto se aplicó a otro controlador, pero, quizás, también a nosotros.



No podía abandonar por completo el UART: necesitaba que el inicio de las mediciones en ambos controladores estuviera estrictamente vinculado a la señal de sincronización (el borde negativo del mismo pulso de sincronización), que venía con una frecuencia de varios kHz, y no podía garantizar esto usando I2C ... UART, que opera a una frecuencia de 500 kHz, resolvió este problema bastante bien.



Así que abandoné la idea de usar I2C y volví a un UART "puro". También hubo una idea de usar un canal SPI adicional, pero Pasha se resistió aquí; no le gustó la necesidad de subir a la placa Tincy con un soldador: en Tincy, solo SPI0 está conectado a los conectores, y el SPI1 y Los pines SPI2 son almohadillas en la parte inferior de la placa.



La idea de poner un búfer intermedio y usar el mismo bus SPI0 para operar COM5 en modo Maestro al sondear el ADC y en modo Esclavo al transferir matrices de datos a COM3 también encontró una resistencia resuelta - Pasha tenía pánico de que en algún lugar del programa hubiera un falla, las conclusiones de los controladores irán al estado incorrecto y ambos controladores se matarán entre sí. Como todavía era el proyecto de Pashin, no apreté mucho esta solución, aunque, dada la claridad del trabajo en SPI con periféricos, asumí que con la transferencia de datos sincrónica todo debería funcionar como un reloj.



Por lo tanto, no tuve más remedio que resistir con todas mis fuerzas y empujar este carro UART hasta que llegue a donde debe estar.



Después de un par de días tratando de encontrar la respuesta a la pregunta "¡¿qué diablos ?!" Escribí otra microprueba y, de repente, ¡he aquí! - ¡Vi un paquete de datos transmitido sin distorsión! Un análisis superficial mostró que en el modo "operativo", los datos del búfer de recepción UART se leyeron solo una vez, al comienzo de la función de procesamiento de comandos. Accedió a la subrutina para leer desde el búfer y consideró el primer byte recibido como un comando y los siguientes como sus parámetros o datos. Al mismo tiempo, como mostró la salida "inversa" a la PC (una matriz de bytes recibidos por el esclavo Tincy y transmitidos al maestro, y de él a la PC), el esclavo Tincy "vio" solo el primer byte , como se ha mencionado más arriba.



Aproximadamente el mismo problema esperaba al transferir una gran matriz de datos de prueba de COM5 a COM3: durante la prueba, el comando se recibió y se procesó correctamente, pero en lugar de datos solo vi ceros. Sin embargo, en la nueva prueba, "automáticamente" puse una llamada más a la función de lectura desde el búfer de entrada UART dentro de la prueba. ¡Y aparecieron los datos!



Después de insertar la lectura adicional en el código del esclavo Tinsey, el problema de "desaparecer" los datos desapareció. Es cierto, en el camino resultó que la mejor manera de transferir datos es solo en paquetes pequeños (hasta 64 bytes, a pesar de la "expansión" de los búferes de lectura / escritura en ambos Tincy), bueno, los retrasos tuvieron que insertarse , pero todo esto le venía bien a Pasha.



Al final, después de una marcha adicional de la lógica a la manera de Vlendish (lectura - depuración y depuración), todo funcionó como se esperaba.



El fin.



All Articles