Hemos completado un gran bloque teórico que muestra cómo construir un subsistema FPGA para el complejo Redd; cómo organizar la comunicación entre el FPGA y el procesador central del complejo; lo fácil que es guardar flujos de datos de alta velocidad en la RAM, que está directamente conectada a la FPGA, para su posterior transferencia pausada al procesador central (o viceversa, para poner datos en esta RAM para su posterior salida rápida al canal). Revisamos las técnicas de rastreo del procesador Nios II. Podemos optimizar el rendimiento del sistema del procesador basado en Nios II para que el trabajo sea lo más eficiente posible. En general, hemos estudiado toda la teoría mínima necesaria, y sería hora de pasar a la práctica diseñando un dispositivo no muy complejo, pero prácticamente útil ... Pero hay un PERO.
Por los comentarios sobre los artículos, noté que algunos lectores creen que Redd y FPGA son como Lenin y el Partido. Que están inextricablemente unidos. De hecho, esto no es así en absoluto. Solo quería comenzar una conversación sobre el complejo Redd con algo interesante, pero ¿qué podría ser más interesante que FPGA? Bueno, y comenzar una conversación, interrumpir media palabra es estúpido. Y finalmente, el gran bloque lógico está completo. Y para mostrar que los FPGA están lejos de todo Redd, propongo hacer aproximadamente tres artículos sobre cosas que no están relacionadas con ellos. Bueno, y después de completar este bloque, ya vaya a la práctica de FPGA.

Introduccion
Lo más sorprendente es que tan pronto como decidí hacer una digresión sobre otros temas, los buenos jefes me lanzaron a una batalla difícil en un proyecto donde se está trabajando con el lenguaje VHDL y Xilinx FPGA. En primer lugar, es por eso que durante mucho tiempo no tomé un bolígrafo en general, y en segundo lugar, está claro que la preparación de artículos prácticos requiere una gran cantidad de experimentos. Es algo difícil lidiar con VHDL / Verilog y Xilinx / Altera al mismo tiempo. Por lo tanto, un descanso en las historias sobre FPGAs tendría que hacerse de todos modos.
Entonces En el
primer artículo de la serie, ya examinamos el diagrama estructural del complejo Redd. Hagámoslo una vez más.

En el artículo de hoy, es poco probable que los expertos en Linux encuentren mucha información valiosa, pero vale la pena repasar las imágenes superficialmente. Aquellos que, como yo, están acostumbrados a trabajar desde Windows, encontrarán una lista de técnicas listas para usar que le permiten trabajar con el complejo. En general, este artículo traerá las habilidades de esos y otros grupos de lectores a un denominador común.
Artículos anteriores del ciclo Bloques UART (puertos serie)
En el diagrama de bloques, vemos el controlador FT4232 que implementa 4 puertos seriales (UART):

Pero si habla un poco más globalmente, entonces el complejo Redd no tiene cuatro, sino seis puertos seriales. Acabo de mencionar que cuatro tienen niveles de CMOS, y dos más están soldados en la placa base, porque el complejo se basa en una PC común.

En consecuencia, tienen niveles: RS232 (más o menos 12 voltios). Puertos RS232: todo está claro con ellos, se muestran en forma de dos conectores DB-9 estándar,

¿Y dónde buscar líneas con niveles CMOS? En general, en un conector común. Su pinout se muestra en el diagrama del circuito eléctrico. Hay, entre otras cosas, contactos correspondientes a UART.

Externamente, este conector tiene el siguiente aspecto:

Cómo usarlo depende de la tarea. Puede hacer un arnés para conectar cada dispositivo. Este enfoque es útil si alguien usa el complejo Redd para probar periódicamente dispositivos fabricados del mismo tipo. Pero el objetivo principal del complejo sigue siendo depurar el equipo que se está desarrollando. Y en este caso, es más fácil conectarse temporalmente. Este patrón temporal es visible en los protectores de pantalla para todos los artículos: los cables de Aruino se insertan directamente en el conector. Por supuesto, contar los contactos sigue siendo un placer, y si accidentalmente salen volando, es tan difícil restaurar la conmutación que es más fácil volver a conectar todo desde cero; por lo tanto, para facilitar la vida, hay una placa elevadora a la que puede conectarse al menos con la ayuda de conectores de dos filas, al menos con el mismo cableado Arduino.

Acceso al software UART
El puerto serie es un elemento bien establecido y bien estandarizado, por lo tanto, trabajar con él no pasa por algunas bibliotecas FTDI específicas, sino por medios estándar. Veamos cómo se ven estas herramientas en Linux.
Nombres de puerto
De una serie de artículos y foros en la red, se deduce que los nombres de puerto proporcionados por los adaptadores de serie USB están en el formato / dev / ttyUSB0, / dev / ttyUSB1, y así sucesivamente. En Linux, todos los dispositivos se pueden ver usando los mismos comandos que para ver directorios comunes (de hecho, los dispositivos son los mismos archivos). Veamos qué nombres hay en nuestro sistema. Le damos el comando:
ls / dev /
Los nombres que nos interesan están resaltados en rojo. Algo muchos de ellos. ¿Qué puerto corresponde a qué? Aquellos que están bien versados en Linux conocen miles de hechizos para todas las ocasiones. Pero para aquellos que todavía trabajaban con Windows 3.1 (bueno, en paralelo con la anciana RT-11), todavía es difícil de recordar, con la edad el nuevo es más difícil de recordar. Por lo tanto, es más fácil encontrar todo cada vez, usando formas simples. Y destaqué la entrada a este camino simple con un marco verde. Subdirectorio condicional serial. Ahora estamos mirando el
/ dev / namespace. Y veamos el espacio
/ dev / serial :

Genial Nos adentramos en la jerarquía, miramos el espacio
/ dev / serial / by-id . Solo mirando hacia el futuro, diré que para la visualización correcta debe usar el
comando ls con el interruptor
–l (gracias a mi jefe por la aclaración). Es decir, le damos el comando:
ls –l / dev / serial / by-id
Por un lado, todo está bien. Ahora sabemos qué nombres en el espacio
/ dev / ttyUSBX corresponden a qué dispositivo. En particular, los puertos organizados por el puente FT4232 (Quad) tienen nombres de
ttyUSB3 a
ttyUSB6 . Pero por otro lado, al considerar este sitio, me di cuenta de que en París, en la cámara de pesas y medidas, necesariamente debe haber una habitación en la que se coloque el estándar del desorden ... Porque de alguna manera necesitas poder medir su valor. Bueno, digamos que la falta de puertos
/ dev / ttyUSB0 y
/ dev / ttyUSB1 puede explicarse fácilmente. Pero, ¿cómo explicar que los puertos "nativos" basados en la descendencia del puente FTDI instalado están numerados entre los tres primeros y que el controlador prolífico de terceros, insertado para un proyecto específico, tomó el puerto número 2? ¿Cómo se puede trabajar en un entorno así? Mañana alguien conectará otro controlador al complejo (ya que el complejo permite que diferentes grupos de desarrolladores trabajen con diferentes equipos al mismo tiempo), y los puertos se retiran nuevamente. ¿Qué puertos necesitamos registrar en el archivo de configuración para una aplicación que funcione?
Resulta que no todo es tan malo. En primer lugar, el nombre amarillo
/ dev / ttyUSB3 y el nombre azul
/ dev / serial / by-id / usb-FTDI_Quad_RS232-HS-if00-port0 son dos alias del mismo dispositivo. Y la segunda opción también se puede presentar como el nombre del puerto, pero ya es más permanente que la primera. Es cierto, en este caso, todo es algo malo. Se puede conectar un controlador externo basado en FT4232 en el complejo, y ya será necesario ocuparse de su numeración. Y aquí "en segundo lugar" viene en nuestra ayuda. A saber, otra convención de nomenclatura alternativa. Recordamos que el directorio
/ dev / serial contenía no solo el subdirectorio
/ by-id , sino también el subdirectorio
/ by-path . Verificamos su contenido (se encuentra en la parte inferior de la siguiente figura, debajo de una línea roja).

Todo aquí está vinculado a la arquitectura física. Y ya he dicho muchas veces que todos los controladores dentro del complejo están soldados a las placas, por lo que la jerarquía interna no cambiará. Por lo tanto, el nombre
/dev/serial/by-path/pci-0000:00:15.0-usb-0:6.5:1.0-port0 será el más difícil.
En total, tenemos la siguiente forma de buscar el nombre del puerto (debe hacerse una vez, los resultados para su instancia del complejo se pueden poner en la tabla y usar constantemente):
- Emita el comando ls –l / dev / serial / by-id .
- Emita el comando ls –l / dev / serial / by-path .
- A partir de los resultados del punto 1, busque el nombre del puerto correspondiente al puerto requerido del puente requerido. Encuentre el mismo nombre de puerto en los resultados del párrafo 2. Tome el nombre físico correspondiente a este párrafo.
Para los puertos atendidos por el controlador en la placa base, todo es un poco más complicado. Aquí no puedes hacer el camino desde el comando más simple "
ls / dev ", pero debes recordar algo (bueno, o al menos recuerda que puedes contactar aquí para obtener ayuda). En todas partes dice que los nombres de puerto típicos son
ttyS0-ttyS3 . La pregunta sigue siendo, ¿en qué nombres son los puertos reales en nuestro sistema? Encontré el siguiente hechizo respondiendo esta pregunta:
ls / sys / class / tty / * / device / driverAquí está la respuesta del sistema:

Resulta que necesitamos usar los nombres
/ dev / ttyS2 y
/ dev / ttyS3 . ¿Por qué? No lo sé. Pero una cosa agrada: aquí no se prevén cambios especiales, por lo tanto, estas constantes pueden recordarse y usarse sin temor a que cambien.
Desarrollo de código
Al desarrollar, debe usar la maravillosa
Guía de programación en serie para sistemas operativos POSIX (el primer enlace directo que obtiene es
https://www.cmrr.umn.edu/~strupp/serial.html , pero nadie sabe cuánto tiempo durará). Es especialmente importante que indique cómo trabajar con un conjunto completo de señales, porque los puertos en el complejo están completamente implementados. Es cierto que hoy usaremos solo las líneas Tx y Rx.
Por lo general, doy los resultados de la forma de onda, pero ahora resulta que estoy en condiciones casi reales: el complejo está ubicado donde mis manos no pueden alcanzar, por lo que no puedo conectar la sonda del osciloscopio. Para ver al menos algún resultado, a petición mía, los colegas agregaron un par de publicaciones al complejo de acuerdo con el siguiente esquema clásico:

Intentemos transferir de un puerto a otro. En nuestro caso, los puertos
/dev/serial/by-path/pci-0000:00:15.0-usb-0:6.5:1.2-port0 y
/dev/serial/by-path/pci-0000:00:15.0- están conectados usb-0: 6.5: 1.3-port0 .
Ya discutimos cómo se escriben los programas para el procesador central de Redd en uno de los
artículos anteriores , por lo que hoy nos limitaremos solo al texto del programa escrito bajo la impresión del documento
Guía de programación en serie para sistemas operativos POSIX . En realidad, el principal punto interesante es cambiar la estrategia de recepción a la lectura sin bloqueo, el resto es trivial. Sin embargo, dado el desorden en los ejemplos en la red sobre este tema, es mejor tener una muestra trivial a mano (verá más adelante que incluso un ejemplo basado en este maravilloso documento no funcionó al 100%, el código a continuación difiere de los cánones descritos en él en una línea, pero más sobre eso a continuación).
El mismo código de muestra#include <cstdio> #include <unistd.h> /* UNIX standard function definitions */ #include <fcntl.h> /* File control definitions */ #include <errno.h> /* Error number definitions */ #include <termios.h> /* POSIX terminal control definitions */ int OpenUART(const char* portName, speed_t baudRate) { // int fd = open(portName, O_RDWR | O_NOCTTY | O_NDELAY); // if (fd == -1) { return fd; } // fcntl(fd, F_SETFL, FNDELAY); // termios options; tcgetattr(fd, &options); // , // , . ... cfsetspeed(&options, baudRate); // ... // 1 , , 8 options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_cflag |= (CLOCAL | CREAD); // , ... tcsetattr(fd, TCSANOW, &options); return fd; } int main() { printf("hello from ReddUARTTest!\n"); int fd1 = OpenUART("/dev/serial/by-path/pci-0000:00:15.0-usb-0:6.5:1.3-port0", 9600); int fd2 = OpenUART("/dev/serial/by-path/pci-0000:00:15.0-usb-0:6.5:1.2-port0", 9600); if ((fd1 != -1) && (fd2 != -1)) { static const unsigned char dataForSend[] = {0xff,0xfe,0xfd,0xfb}; // write(fd1, dataForSend, sizeof(dataForSend)); unsigned char dataForReceive[128]; ssize_t cnt = 0; // , , // int readSteps = 0; // , while (cnt < (ssize_t)sizeof(dataForSend)) { readSteps += 1; ssize_t rd = read(fd2, dataForReceive + cnt, sizeof(dataForReceive) - cnt); // - , if (rd <= 0) { usleep(1000); } else // - { cnt += rd; } } // printf("%d read operations\n", readSteps); printf("Read Data: "); for (unsigned int i = 0; i < cnt; i++) { printf("%X ", dataForReceive[i]); } printf("\n"); } else { printf("Error with any port open!\n"); } // if (fd1 != -1) { close(fd1); } if (fd2 != -1) { close(fd2); } return 0; }
Ejecutar: obtenemos el resultado previsto:
hello from ReddUARTTest! 14 read operations Read Data: FF FE FD FB
Se puede ver que 4 bytes ocuparon 14 intentos, es decir, la lectura no estaba bloqueando. A veces, el sistema devolvió un estado de "no hay datos nuevos" y el programa se suspendió durante un milisegundo.
En general, todo está bien, pero sin un osciloscopio no puedo estar seguro de que dos puertos basados en el mismo chip realmente establezcan la velocidad. Ya salté sobre el hecho de que la velocidad era la misma (para eso tenía un controlador), pero no el que ordené. Al menos, verifiquemos que al menos esté controlado. Para hacer esto, estableceré la velocidad del puerto de recepción para duplicar la del puerto de transmisión. Y conociendo la física del proceso de transferencia de datos, puede predecir cómo se distorsionan estos datos durante la recepción. Veamos la transferencia del byte 0xff en forma gráfica. S - bit de inicio (siempre hay cero), P - bit de parada (siempre hay uno), 0-7 - bits de datos (para la constante 0xFF - todas las unidades).

Ahora superpongamos esta vista con una vista de cómo todo será visto por un receptor que opera al doble de la velocidad:

Genial Debe aceptarse el valor "1111 1110" (los datos van al bit menos significativo hacia adelante), es decir, 0xFE. La segunda mitad del valor transmitido no afecta la recepción, ya que las unidades corresponden al silencio en la línea. Es decir, transmitimos un byte, un byte también vendrá.
Construiremos el mismo gráfico para la verificación, que corresponderá al valor 0xFE transmitido:

Espere el valor "1111 1000" o 0xF8. Bueno, verifiquemos qué esperar con el valor pasado 0xFD:

Obtenemos el valor 0xE6. Bueno, para el valor transmitido 0xFB obtenemos el 0x9E recibido (puede trazar el gráfico y verlo usted mismo). Genial Cambiamos una sola línea en la aplicación de prueba, reemplazando la velocidad de 9600 con 19200:
int fd2 = OpenUART("/dev/serial/by-path/pci-0000:00:15.0-usb-0:6.5:1.2-port0", 19200);
Comenzamos y obtenemos este resultado del trabajo:
hello from ReddUARTTest! 9 read operations Read Data: FE F8 E6 9E
Por cierto, no hice en vano este control. Al principio, utilicé otras funciones de configuración de velocidad (par cfsetispeed / cfsetospeed), ¡y no funcionaron! Gracias a esta prueba, el problema se identificó y resolvió a tiempo. Cuando trabajas con equipos, nunca puedes confiar en la intuición. ¡Todo debe ser revisado!
Administración de línea de alimentación 220 voltios
En general, las líneas eléctricas de 220 voltios no están relacionadas con el tema del artículo (puentes FTDI), pero sí están relacionadas con el tema de esta sección (puertos seriales). Echemos un vistazo rápido a ellos.

Cuando enumeramos los puertos, vimos este nombre:

Este es un puerto serie virtual. Es tan virtual que no importa qué parámetros tenga (velocidad de puerto, número de bits, formato de paridad, etc.). Independientemente de los parámetros que haya configurado, aún podrá manejar comandos perfectamente. Y son estos equipos los que controlan las tomas de corriente en el complejo.

Al desarrollar el sistema de comando, se decidió abandonar las interfaces de comando complejas. La administración toma un byte, sin enmarcar cadenas y otros adornos, aunque el byte es textual (de modo que se pueda transferir convenientemente desde el terminal al depurar). Esta concisión se explica fácilmente: la interfaz de cadena le permite lidiar con la interferencia en un canal UART inseguro. Pero en nuestro caso, físicamente, el trabajo pasa por el canal USB, que está protegido por códigos de control cíclicos. El procesamiento del flujo de retorno requiere escribir código adicional o vaciar constantemente los búferes, lo que no siempre es conveniente. Es por eso que no hay puntos de referencia para las cadenas, no hay respuestas. Se cree que el canal es estable. Si desea una respuesta, puede solicitarla explícitamente. Es decir, el rendimiento del bloque siempre se puede verificar fácilmente enviando un byte adicional después del comando.
Considere los comandos que se pueden enviar:
El comando '?' (signo de interrogación) es el único que devuelve una respuesta. En respuesta a esto, siempre vienen 3 bytes, cada uno de los cuales corresponde al estado de una de las salidas. En realidad, los estados corresponden a los comandos. Por ejemplo, 'abc': las tres salidas ahora están apagadas, 'Abc': la primera está encendida, la segunda y la tercera están apagadas, etc.
Para los experimentos con este subsistema, sugiero no escribir un programa especial (no es diferente del que se dio anteriormente, solo los datos enviados a los puertos serán diferentes), sino usar las herramientas del sistema operativo y jugar interactivamente con sockets.
Después de muchos experimentos con el seguimiento del puerto a través del comando cat y el envío de comandos en una ventana paralela usando el programa echo, me di cuenta de que por alguna razón no podía lograr resultados en un par de terminales ssh basadas en masilla (incluso jugando con esos puertos con los que solo que experimentó perfectamente con su programa). Por lo tanto, tuve que instalar el programa minicom estándar. Déjame recordarte el comando de instalación:
sudo apt-get minicomA continuación, ejecútelo con el comando:
minicom –D / dev / ttyACM0El nombre del puerto es corto, porque con los experimentos manuales es más fácil ingresarlo. En el trabajo de software, como siempre, es mejor usar un nombre que esté vinculado a la jerarquía de hardware. Una vez más, noto que no configuro ningún otro parámetro de puerto porque es virtual. Funcionará con cualquier configuración.
Luego presionamos el signo de interrogación en el terminal e instantáneamente (sin avance de línea) obtenemos una respuesta

Esto significa que todas las salidas están actualmente apagadas. Digamos que queremos encender la segunda salida. Presione la 'B' mayúscula. No hay reacción en la pantalla. Presione '?' Nuevamente, obtenemos una nueva línea con la respuesta:

Todo funciona No olvide apagar 220 voltios (comando 'b'). Puede salir del terminal presionando sucesivamente ctrl + A, luego X. El experimento ha finalizado.
Neumáticos SPI y I 2 C
Los buses SPI (que también pueden funcionar en modo Quad-SPI) y I
2 C se implementan en combinación con puentes universales. Es decir, en general, el complejo tiene dos puentes, cada uno de los cuales se puede encender en modo SPI o en I
2 C. En el diagrama estructural, la sección correspondiente se ve así:

La esencia de encender los autobuses finales es visible desde el diagrama del circuito eléctrico. Considere solo uno de los dos controladores:

Por lo tanto, los autobuses SPI e I
2 C no se cruzan de ninguna manera. Las restricciones en su uso conjunto están determinadas solo por las restricciones impuestas por FTDI en el controlador FT4222H. Desafortunadamente, la documentación establece que solo una interfaz puede estar activa a la vez:

Cómo gestionar las líneas CFG1_0..CFG1_1 y CFG2_0..CFG2_1, nos encontraremos en el próximo artículo. Ahora creemos que todos están anulados.
En general, el trabajo con el controlador está muy bien descrito en el documento
FT4222H USB2.0 TO QUADSPI / I2C BRIDGE IC , por lo tanto, no consideraremos las características de los modos de operación de los controladores. Todo está muy claro en el documento mencionado.
En cuanto al soporte de software, su descripción se puede encontrar en el documento no menos notable
AN_329 Guía del usuario para LibFT4222 . Ya hemos trabajado dos veces con el puente FTDI: en la segunda mitad de
este artículo y en la segunda mitad de
este . Por lo tanto, al comparar este documento con estos artículos, puede resolverlo rápidamente y comenzar a escribir su propio código. Permítanme mostrarles el código de referencia que envía datos al bus SPI, sin detenerse en los detalles de su implementación, parece dolorosamente que ya se ha analizado con FT2232.
Código que envía datos al bus SPI. #include "../ftd2xx/ftd2xx.h" #include "../LibFT4222/inc/LibFT4222.h" void SpiTest (int pos) { FT_HANDLE ftHandle = NULL; FT_STATUS ftStatus; FT4222_STATUS ft4222Status; // ftStatus = FT_Open(pos, &ftHandle); if (FT_OK != ftStatus) { // open failed printf ("error: Cannot Open FTDI Device\n"); return; } ft4222Status = FT4222_SPIMaster_Init(ftHandle, SPI_IO_SINGLE, CLK_DIV_4, CLK_IDLE_LOW, CLK_LEADING, 0x01); if (FT4222_OK != ft4222Status) { printf ("error: Cannot switch to SPI Master Mode\n"); // spi master init failed return; } uint8 wrBuf [] = {0x9f,0xff,0xff,0xff,0xff,0xff,0xff}; uint8 rdBuf [sizeof (wrBuf)]; uint16 dwRead; ft4222Status = FT4222_SPIMaster_SingleReadWrite (ftHandle,rdBuf,wrBuf,sizeof (wrBuf),&dwRead,TRUE); if (FT4222_OK != ft4222Status) { printf ("error: Error on ReadWrite\n"); } else { printf ("received: "); for (int i=0;i<6;i++) { printf ("0x%X ",rdBuf[i]); } printf ("\n"); } FT4222_UnInitialize(ftHandle); FT_Close(ftHandle); }
Piezas de bus SPI
Los desarrolladores de código para microcontroladores a menudo usan el bus SPI como generador de una frecuencia predeterminada. De hecho, los pulsos generados puramente mediante programación a través de líneas GPIO dependen de muchos factores. En primer lugar, ramificar, girar el bucle, requiere ciclos de procesador. En segundo lugar, las interrupciones, DMA y otros factores imprevistos pueden interferir con el procesador. SPI es más o menos estable, sé capaz de poner bytes en el búfer. Una aplicación típica del bloque SPI, que no tiene una relación directa con este SPI en sí, es el control de los LED RGB, para los cuales la precisión de establecer la duración de los pulsos es muy importante.
Desafortunadamente, esto no es aceptable para los puentes FTDI. El fragmento de código anterior generará estos pulsos en el bus:

En este caso, las reglas de funcionamiento de SPI no se violan, desde el punto de vista de este bus, todo funciona correctamente. Solo tenga en cuenta que las soluciones personalizadas habituales en los controladores no funcionarán aquí. Es cierto que el complejo tiene muchos conectores USB gratuitos. Todos los bloques no estándar pueden desarrollarse por separado y conectarse a ellos.
Partes de neumáticos I 2 C
Lo único que tiene sentido es indicar la ausencia de resistencias pull-up para el bus I
2 C en el lado del complejo. Pero esto es normal: en el lado del dispositivo de trabajo, todavía hay un ascensor. Hoy en día, un pull-up puede ser de cualquier voltaje, por lo que es lógico que esté configurado en el dispositivo de destino.
Conclusión
Hoy adquirimos habilidades prácticas para trabajar con neumáticos implementados por puentes FTDI. En general, trabajar con ellos es estándar, es solo que todo el conocimiento se resume en un solo artículo, para no buscarlos poco a poco. La próxima vez consideraremos un módulo que controla dispositivos no estándar, implementado sobre la base del controlador STM32. En el diagrama estructural, esta sección le corresponde:

Pero realmente, todo es un poco más interesante allí ...