Red de sensores bastante complicada

Estuve trabajando en un proyecto recientemente y fue el primero que estuvo lo suficientemente involucrado como para complicar la red de sensores. Al final, creo que la comunicación fue el cuello de botella en términos de rendimiento general y me pregunto cómo personas más experimentadas habrían resuelto este problema. Esta es una lectura larga, pero creo que es bastante interesante, así que por favor sigan con ella. El problema era diseñar un dirigible autónomo capaz de navegar por una carrera de obstáculos y dejar caer pelotas de ping pong en objetivos de cajas marrones. Aquí va:

Sensores

  • Módulo de cámara 4D Systems uCAM-TTL - Interfaz UART
  • Brújula digital HMC6352 - Interfaz I2C
  • Maxbotix Sonar ez4 - Interfaz analógica de 1 pin

Actuadores

  • Controladores de motor 2x L293D (conectados a motores de pasatiempo simples): se usaron para impulsar 6 motores bidireccionalmente. Requerían entradas PWM para variar la velocidad. Ahora, 3 de nuestros motores siempre hacían lo mismo (los que controlaban el movimiento hacia arriba/abajo), por lo que solo requerían 2 salidas PWM de nuestros controladores para controlar los 3 motores. Los otros 3 motores que controlaban el movimiento lateral necesitaban control individual (para el movimiento omnidireccional), por lo que se requerían otras 6 salidas PWM de nuestros controladores.
  • Servomotor - interfaz PWM

Controladores

Por razones que se aclararán más adelante, terminamos usando 2x ATmega328P. Usamos un Arduino Uno para programarlos (no teníamos acceso a un ISP), pero fabricamos una PCB personalizada para no tener que usar placas arduino, ya que eso solo agregaría un peso innecesario a nuestro dirigible. En cuanto a por qué elegimos el ATmega328P, estaba muy familiarizado con el entorno arduino y creo que eso hizo que el desarrollo del código fuera mucho más rápido y fácil.

Comunicación y Procesamiento

  • 2x Xbee básico
  • 2x ATmega328P
  • Computadora de escritorio con C++ con openCV

Entonces, como puede ver en el módulo de la cámara, la mayor parte de nuestro proyecto se basó en la visión por computadora. Los dirigibles solo podían llevar un peso limitado y no nos sentíamos cómodos implementando la visión por computadora en un microcontrolador. Entonces, lo que terminamos haciendo fue usar XBee para transmitir los datos de la imagen a una computadora de escritorio. Entonces, en el lado del servidor, recibimos datos de imagen y usamos openCV para procesar la imagen y resolver cosas a partir de ella. Ahora, el lado del servidor también necesitaba conocer la información de altura (del sonar) y la información de la brújula.

El primer problema fue que no pudimos tener la cámara controlada por un microcontrolador por un par de razones. El problema principal era que la memoria interna de la uP no podía manejar el almacenamiento de un cuadro completo. Podría haber formas de evitar esto a través de una codificación inteligente, pero para los fines de esta pregunta, supongamos que era imposible. Entonces, para resolver este problema, hicimos que el lado del servidor enviara comandos de cámara a través del transceptor XBee y el receptor XBee (a bordo del dirigible) tenía su salida conectada a la entrada de la cámara.

El siguiente problema fue que no hay suficientes PWM en un solo ATmega328P para controlar todos los motores PORQUE la interfaz I2C usa uno de los pines PWM (malditos sean...). Es por eso que decidimos usar una segunda. De todos modos, el código se prestaba perfectamente al procesamiento en paralelo porque el control de altura era completamente independiente del control de movimiento lateral (por lo que 2 micros probablemente era mejor que uno conectado a un controlador PWM). Por lo tanto, U1 era responsable de 2 salidas PWM (arriba/abajo) y lectura del Sonar. U2 era responsable de leer la brújula, controlar 6 salidas PWM (los motores laterales) y también leer el Sonar. U2 también era responsable de recibir comandos del servidor a través del XBee.

Eso llevó a nuestro primer problema de comunicación. La línea XBee DOUT se conectó tanto al microcontrolador como a la cámara. Ahora, por supuesto, diseñamos un protocolo para que nuestros microcomandos ignoraran los comandos de la cámara y los comandos de la cámara ignoraran los microcomandos, así que estuvo bien. Sin embargo, la cámara, al ignorar nuestros microcomandos, enviaría datos NAK en su línea de salida. Dado que el comando estaba destinado al micro, necesitábamos de alguna manera apagar la salida de la cámara al XBee. Para resolver esto, hicimos el micro control 2 FET que estaban entre la cámara y XBee (ese es el primer FET) y también entre U2 y el XBee (ese es el segundo FET). Por lo tanto, cuando la cámara intentaba enviar información al servidor, el primer FET estaba "encendido" y el segundo FET estaba "apagado".

Entonces, para darle una idea de cómo funcionó esto, aquí hay algunos ejemplos:

  1. El servidor solicita una imagen: PIC_REQUEST pasa por XBee y llega a U2 y la cámara. U2 lo ignora y la cámara devuelve los datos de la imagen.
  2. El servidor acaba de terminar de procesar una imagen y está enviando datos del motor para decirle al dirigible que gire a la derecha: MOTOR_ANGLE (70) pasa por XBee y llega a U2 y la cámara. U2 reconoce como un microcomando y, por lo tanto, apaga el FET de la cámara (pero quizás la cámara ya respondió con un NAK, quién sabe...). Luego, U2 responde al comando cambiando las salidas PWM del motor. Luego vuelve a encender el FET de la cámara (esta era la configuración predeterminada ya que los datos de la imagen eran lo más importante).
  3. El servidor se da cuenta de que hemos llegado a un punto en la carrera de obstáculos donde nuestra altura de desplazamiento predeterminada ahora debe ser de 90 pulgadas en lugar de 50 pulgadas. SET_HEIGHT pasa por XBee y sucede lo mismo que en el ejemplo 2. U2 reconoce el comando SET_HEIGHT y activa una interrupción en U1. U1 ahora sale de su bucle de control de altura y espera recibir datos en serie de U2. Así es, más datos seriales. En este punto, el FET de U2 está encendido (y el FET de la cámara está apagado), por lo que el servidor recibe la altura que U2 también envía a U1. Eso fue con fines de verificación. Ahora U1 restablece su variable interna para height2HoverAt. U2 ahora apaga su FET y vuelve a encender el FET de la cámara.

Definitivamente omití una buena cantidad de información, pero creo que es suficiente para comprender algunas de las complicaciones. Al final, nuestros problemas fueron simplemente sincronizar todo. A veces quedaban datos en los búferes, pero solo 3 bytes (todos nuestros comandos eran secuencias de 6 bytes). A veces perdíamos la conexión con nuestra cámara y teníamos que resincronizarla.

Entonces mi pregunta es: ¿Qué técnicas sugerirían ustedes para hacer que la comunicación entre todos esos componentes sea más confiable/robusta/simple/mejor?

Por ejemplo, sé que uno hubiera sido agregar un circuito de retardo entre el XBee integrado y la cámara para que el micro tuviera la oportunidad de apagar la línea de conversación de la cámara antes de que respondiera a los microcomandos con NAK. ¿Alguna otra idea como esa?

Gracias y estoy seguro de que esto requerirá muchas ediciones, así que estad atentos.


Edit1:Empalmar los datos UART de la cámara a través de uno de los micros no nos parecía posible. Había dos opciones para datos de cámara, mapa de bits sin formato o JPEG. Para un mapa de bits sin procesar, la cámara simplemente le envía datos tan rápido como puede. El ATmega328P solo tiene 128 bytes para un búfer en serie (técnicamente, esto es configurable, pero no estoy seguro de cómo) y no pensamos que podríamos sacarlo del búfer y llegar al XBee lo suficientemente rápido. Eso dejó el método JPEG donde envía cada paquete y espera a que el controlador lo ACK (pequeño protocolo de negociación). Lo más rápido que podía llegar era 115200 baudios. Ahora, por alguna razón, lo más rápido que pudimos transmitir de manera confiable grandes cantidades de datos a través del XBee fue 57600 baudios (esto es incluso después de que hicimos el emparejamiento de nodo/red para permitir la capacidad de reenvío automático). Agregar la parada adicional en nuestra red (cámara a micro a XBee en lugar de solo cámara a XBee) para el micro simplemente redujo demasiado el tiempo que tomó transferir una imagen. Necesitábamos una cierta frecuencia de actualización en las imágenes para que nuestro algoritmo de control de motores funcionara.

Está poniendo mucho esfuerzo en no expandir sus habilidades de microcontrolador. No tengo nada en contra de Arduino, pero no es muy apropiado para eso. ¿Puedes eventualmente hacer que funcione? Probablemente. Sin embargo, sería mucho más útil aprender una plataforma más capaz. Si está preguntando cómo lo harían las personas con más experiencia, diría algo como un SBC ARM para openCV y control, y un FPGA que sirve como puente para todas las interfaces. Sin embargo, eso sería un pequeño salto, por lo que sugeriría probar una cosa nueva e importante... ¿quizás un micro de 32 bits con suficientes periféricos para interactuar con todo?
Jaja, sí, me estaba esforzando mucho. Vea que este proyecto era una tarea escolar y queríamos centrarnos en hacer que funcionara, no en hacer que uno de los dos EE del equipo aprendiera una nueva plataforma de microcontrolador. De hecho, estoy pensando en ingresar a ARM y micros más avanzados este verano. El otro EE de nuestro equipo había tomado una clase de FPGA en realidad... Iré a gritarle por no sugerir eso =P

Respuestas (3)

Entiendo que querías elegir un entorno de desarrollo con el que estuvieras familiarizado, de modo que pudieras comenzar a ejecutar, pero creo que la compensación de hardware/software puede haberte encerrado al quedarte con Arduino y no elegir una parte que tuviera todo. los periféricos de hardware que necesitaba y escribir todo en C controlado por interrupciones en su lugar.

Estoy de acuerdo con la sugerencia de @Matt Jenkins y me gustaría ampliarla.

Hubiera elegido un uC con 2 UART. Uno conectado al Xbee y otro conectado a la cámara. El uC acepta un comando del servidor para iniciar una lectura de cámara y se puede escribir una rutina para transferir datos desde el canal UART de la cámara al canal UART XBee byte por byte, por lo que no hay búfer (o como mucho solo un pequeño uno) necesario. Habría tratado de eliminar el otro uC por completo eligiendo una parte que también se adaptara a todas sus necesidades de PWM (¿8 canales PWM?) Y si quisiera quedarse con 2 uC diferentes cuidando su eje respectivo, entonces quizás un Hubiera sido mejor una interfaz de comunicaciones diferente, ya que se tomarían todos sus otros UART.

Alguien más también sugirió mudarse a una plataforma Linux integrada para ejecutar todo (incluido openCV) y creo que eso también habría sido algo para explorar. Sin embargo, he estado allí antes, un proyecto escolar de 4 meses y solo necesitas terminarlo lo antes posible, no se puede detener por parálisis por análisis. ¡Espero que te haya resultado bien!


EDIT #1 En respuesta a los comentarios @JGord:

Hice un proyecto que implementó el reenvío de UART con un ATmega164p. Tiene 2 UART. Aquí hay una imagen de una captura del analizador lógico (analizador lógico USB Saleae) de ese proyecto que muestra el reenvío de UART:captura del analizador

La línea superior son los datos de origen (en este caso, sería su cámara) y la línea inferior es el canal UART al que se reenvía (XBee en su caso). La rutina escrita para hacer esto manejó la interrupción de recepción de UART. Ahora, ¿creería usted que mientras se lleva a cabo este reenvío de UART, podría configurar felizmente sus canales PWM y manejar sus rutinas I2C también? Déjame explicarte cómo.

Cada periférico UART (para mi AVR de todos modos) se compone de un par de registros de desplazamiento, un registro de datos y un registro de control/estado. Este hardware hará las cosas por sí solo (suponiendo que ya haya inicializado la velocidad en baudios y demás) sin su intervención si:

  1. Entra un byte o
  2. Un byte se coloca en su registro de datos y se marca para su salida.

De importancia aquí es el registro de desplazamiento y el registro de datos. Supongamos que entra un byte en UART0 y queremos reenviar ese tráfico a la salida de UART1. Cuando se ha desplazado un nuevo byte al registro de desplazamiento de entrada de UART0, se transfiere al registro de datos de UART0 y se activa una interrupción de recepción de UART0. Si ha escrito un ISR para él, puede tomar el byte en el registro de datos UART0 y moverlo al registro de datos UART1 y luego configurar el registro de control para que UART1 comience a transferir. Lo que hace es decirle al periférico UART1 que tome lo que acaba de poner en su registro de datos, lo ponga en su registro de desplazamiento de salida y comience a cambiarlo. Desde aquí, puede salir de su ISR y volver a la tarea que estaba haciendo su uC antes de que se interrumpiera. Ahora UART0, después de haber borrado su registro de desplazamiento, y haber borrado su registro de datos puede comenzar a cambiar nuevos datos si aún no lo ha hecho durante la ISR, y UART1 está desplazando el byte que acaba de ingresar; todo eso sucede en solo sin su intervención mientras su uC está haciendo alguna otra tarea. Todo el ISR tarda microsegundos en ejecutarse, ya que solo estamos moviendo 1 byte alrededor de la memoria, y esto deja mucho tiempo para salir y hacer otras cosas hasta que llegue el siguiente byte en UART0 (que tarda cientos de microsegundos).

Esta es la belleza de tener periféricos de hardware: simplemente escribe en algunos registros asignados a la memoria y se encargará del resto desde allí y señalará su atención a través de interrupciones como la que acabo de explicar anteriormente. Este proceso ocurrirá cada vez que ingrese un nuevo byte en UART0.

Observe cómo solo hay un retraso de 1 byte en la captura lógica, ya que solo estamos "almacenando en búfer" 1 byte si quiere pensarlo de esa manera. No estoy seguro de cómo ha llegado a su O(2N)estimación. Voy a suponer que ha alojado las funciones de la biblioteca serial de Arduino en un bucle de bloqueo en espera de datos. Si tenemos en cuenta la sobrecarga de tener que procesar un comando de "leer cámara" en el uC, el método impulsado por interrupción es más como O(N+c)donde cabarca el retraso de un solo byte y la instrucción "leer cámara". Esto sería extremadamente pequeño dado que está enviando una gran cantidad de datos (datos de imagen, ¿verdad?).

Todos estos detalles sobre el periférico UART (y todos los periféricos en la uC) se explican detalladamente en la hoja de datos y todo es accesible en C. No sé si el entorno Arduino le brinda ese bajo acceso para que pueda comenzar a acceder se registra, y esa es la cuestión, si no lo hace, está limitado por su implementación. Tienes el control de todo si lo has escrito en C (incluso más si lo has hecho en ensamblador) y realmente puedes llevar el microcontrolador a su verdadero potencial.

Supongo que no me expliqué bien. El problema de colocar el micro entre la cámara y el XBee era que el micro no podía hacer nada más mientras obtenía los datos de la imagen a menos que todo lo demás se interrumpiera con los temporizadores. Además, si asume que obtener una imagen con N píxeles tomó 5 segundos, cuando coloca el micro ahora toma ~ 10 segundos. Seguro que sigue siendo O(N), pero realmente tiene un tiempo de ejecución de 2N, lo que en este caso fue suficiente para arruinar nuestro objetivo de frecuencia de actualización. En última instancia, el espacio de memoria no era realmente el factor limitante, sino principalmente la velocidad. Suspiro, parece la única respuesta real...
era utilizar un hardware más avanzado. Tenía la esperanza de que alguien sugiriera algo realmente inteligente que sirviera como un truco útil para todos mis años como EE. Bueno, supongo que al menos eso significa que no fui demasiado tonto para pensar en el truco =P Ah, y funcionó bastante bien.
@JGord, por coincidencia, hice un proyecto usando el reenvío UART de la manera que estoy describiendo entre una tarjeta con chip y una lavadora usando un ATmega164 jonathan-lee.ca/DC18-Smart-Card.html Estoy hablando usando UART ISR para mover un solo byte de un registro de datos UART al registro de datos del otro UART y luego regresar. El hardware UART actúa de forma independiente y desplaza los datos desde allí. El ISR toma microsegundos y, una vez que regresa, el uC es libre de hacer lo que quiera hasta que se cambie el siguiente byte. Una mejor explicación viene en una edición, cada vez que llego a casa.
Interesante. Entonces, ¿sería su sugerencia que el servidor hable solo con el micro? Entonces, ¿ese microtransmite comandos del servidor a la cámara y respuestas de la cámara al servidor? Entonces, en el UART0 ISR (fin de hablar con el servidor) pude verificar si es o no un comando de cámara. En caso afirmativo, refleje en UART1 a la cámara. De lo contrario, no refleje y, en su lugar, cambie algún valor (ángulo lateral, altura, etc., alguna variable con la que mi bucle de control verifica). Entonces, el ISR de UART1 sería simplemente reflejar los datos de la imagen en UART0 siempre. Sí, supongo que eso funcionaría. Así que realmente solo obtengo un micro con 2 UART...
y suficientes canales PWM habrían sido el camino a seguir. Así que ahora digamos que el servidor envió una solicitud get_data a la que se supone que el micro debe responder con una secuencia de 6 bytes en la que envía una altura, un rumbo de brújula y algunos ECC. Por alguna razón, el servidor lee 6 bytes pero no tiene el protocolo correcto, es decir, los bytes del mensaje de inicio y finalización no se alinean. ¿Quién sabe por qué? ¿Simplemente tirarías el mensaje y lo solicitarías de nuevo o qué? ¿Porque si hubiera algún byte ficticio en el búfer, el mensaje de 6 bytes nunca volvería a alinearse? ¿Recomendaría enjuagar después de un mensaje fallido?
@JGord, sí, eso es exactamente lo que sugiero con el enfoque 2 UART, lo has resumido mejor. En cuanto a la tolerancia a fallas, lo que ha mencionado (solicitar retransmisión) suena razonable, aunque supongo que eso le causa problemas al cumplir sus objetivos de frecuencia de actualización. ¿Cuál es el ECC que agregaste? suma de control? En cualquier caso es buena señal que hayas incluido algo por si acaso. Personalmente, intentaría encontrar la causa subyacente de por qué los 6 bytes no son lo que espera. Es más difícil para mí comentar sobre este aspecto ya que no conozco los detalles de su implementación.

¿Por qué no pudiste canalizar los datos de la cámara a través del µC? No me refiero a almacenar en búfer las imágenes, sino a transmitir los datos UART a través del µC para que pueda decidir qué debe devolverse y qué no.

Es más fácil si tiene un µC con dos UART, pero podría emularse en el software.

Hah, sí, esa fue una de las cosas que dejé fuera... pregunta de edición ahora. Sin embargo, es una buena idea, nos emocionamos mucho cuando pensamos en eso también.

Se me ha ocurrido otra opción, pero esto podría ser algo voluminoso y demasiado pesado para su proyecto: -

  • ¿Por qué no usar un servidor USB inalámbrico, conectado a un pequeño concentrador USB, con adaptadores USB->RS232 para brindar múltiples canales de control a las diferentes partes del sistema?

Sí, voluminosos, pero si los quitas y tal vez usas USB en lugar de RS232 cuando sea posible, podrías salirte con la tuya...