Hace unos días tuve la imprudencia de prometer veladamente que cortaría una publicación sobre Milander ... Bueno, intentémoslo.
Como probablemente ya sepa, hay una empresa rusa Milander, que, entre otras cosas, produce microcontroladores basados en el núcleo ARM Cortex-M. Por la voluntad del destino, me vi obligado a conocerlos con bastante fuerza, y
supe el dolor .
A continuación se describe una pequeña porción de este dolor causado por trabajar con RS-485. Pido disculpas de antemano si mastico demasiado los conceptos básicos, pero quería que este artículo sea accesible para una audiencia más amplia.
También haré una reserva por adelantado que solo traté con 1986-1991 y 1986 -1, no puedo hablar con confianza sobre los demás.
TL DREl UART de Milandrovsk carece de la interrupción "Transmisión completa", la muleta es el "modo de prueba de bucle invertido", es decir modo de eco. Pero con los matices.
Entrada
La interfaz RS-485 (también conocida como EIA-485, aunque nunca la escuché llamar en la vida cotidiana) es una interfaz semidúplex asíncrona con una topología de bus. Este estándar estipula solo la física, es decir niveles de voltaje y diagramas de temporización, pero no especifica el protocolo de intercambio, la protección contra errores de transmisión, el arbitraje y similares.
De hecho, el RS-485 es solo un UART semidúplex con altos niveles de voltaje diferencial. Es esta simplicidad la que garantiza la popularidad del RS-485.
Para convertir UART a RS-485, se utilizan microcircuitos convertidores especiales, como MAX485 o 5559IN10AU (del mismo Milander). Funcionan casi "transparentemente" para el programador, que solo puede elegir el modo correcto de funcionamiento del chip: recepción o transmisión. Esto se hace usando las patas nRE (no habilitación de salida del receptor) y DE (habilitación de salida del controlador), que, por regla general, se combinan y controlan por una pata del microcontrolador.
Levantar esta pata cambia el chip a la transmisión y bajarlo a la recepción.
En consecuencia, todo lo que se requiere del programador es elevar este tramo RE-DE, transferir el número deseado de bytes, bajar el tramo y esperar una respuesta. Suena bastante simple, ¿verdad?
Jeje.
El problema
Este tramo debe bajarse en un momento en que todos los bytes transmitidos se transfieren completamente a la línea. ¿Cómo atrapar este momento? Para hacer esto, debe detectar el evento "Transmisión completa" (transmisión completada), que genera un bloque UART en el microcontrolador. En su mayor parte, los eventos están configurando un poco en alguna solicitud de registro o interrupción. Para capturar la configuración de un bit en el registro, se debe sondear el registro, es decir usa un código como este:
while( MDR_UART1->FR & UART_FR_BUSY ) {;}
Esto es si podemos permitirnos detener completamente el programa hasta que se transfieran todos los bytes. Como regla, no podemos permitirnos esto.
La interrupción a este respecto es mucho más conveniente, ya que llega por sí sola, de forma asíncrona. En interrupción, podemos omitir rápidamente RE-DE y todo el negocio.
Por supuesto, si pudiéramos hacer esto, no habría dolor, y esta publicación tampoco existiría.
El hecho es que en el bloque UART, que Milander pone en todos sus microcontroladores en el Cortex-M (hasta donde yo sé), no hay interrupción para el evento "Transferencia completa". Solo hay una bandera. Y hay una interrupción "El búfer del transmisor está vacío". Y el byte interrumpe, por supuesto.
Todavía tengoUn montón de otras interrupciones y el modo FIFO, en mi opinión, es completamente inútil. Si alguien entiende por qué es necesario, ¡díganos!
El problema es que "Transmit Buffer está vacío", esto no es lo mismo que "Transmisión completa". Hasta donde entiendo el dispositivo UART interno, el evento "Buffer Empty" significa que hay al menos un espacio libre en el buffer del transmisor. Incluso si este lugar es solo uno (es decir, un búfer de un tamaño de byte), solo significa que el último byte transmitido se copió al registro de desplazamiento interno, desde el cual este byte se arrastrará a la línea, poco a poco.
En resumen, el evento "el búfer del transmisor está vacío" no significa que todos los bytes se hayan transmitido por completo. Si omitimos RE-DE en este momento, "cortaremos" nuestro paquete.
Que hacer
Rebus
Decodificación:“Desmalezar campos de bits” es un meme local de un tema breve pero doloroso en el foro Milander:
forum.milandr.ru/viewtopic.php?f=33&t=626 .
La solución más simple es "marcar" (de la "encuesta" en inglés - encuesta continua) flag UART_FR_BUSY.
Por supuesto, esta solución no es muy buena. Si no podemos bloquear esta bandera, tenemos que verificarla periódicamente. Para verificarlo periódicamente, debe cercar un jardín completo (especialmente si desea escribir un módulo portátil, y no solo resolver este problema una vez).
Si usa algún tipo de RTOS, entonces, en aras de esta eliminación de malezas, debe comenzar una tarea completamente diferente, despertarla en interrupción, establecerla como la prioridad más baja, sin problemas, en resumen.
Pero, parece, está bien, atormentado una vez, luego usamos y nos regocijamos. Pero no
Desafortunadamente, no es suficiente para nosotros omitir RE-DE estrictamente después de que todos los bytes se hayan transmitido hasta el final. Necesitamos bajarlo
no muy tarde . Porque no estamos solos en el autobús. Lo más probable es que algún mensaje de otro suscriptor llegue a nuestro mensaje. Y si omitimos RE-DE demasiado tarde, no cambiaremos al modo de recepción y perderemos algunos bits de respuesta.
El tiempo que podemos permitirnos “sobreexponer” el tramo RE-DE depende principalmente de la velocidad de transmisión (velocidad de transmisión) y de la velocidad del dispositivo con el que nos comunicamos en el bus.
En mi caso, la velocidad era relativamente baja (57600 baudios) y el dispositivo era bastante juguetón. Y a veces sucedió que la respuesta perdió un poco o dos.
En general, no es una buena solución.
Temporizador
La segunda opción que viene a la mente es usar un temporizador de hardware. Luego, en la interrupción "Transmitter Buffer Empty", comenzamos un temporizador con un tiempo de espera que es igual al tiempo de transmisión de un byte (este tiempo se calcula fácilmente a partir de la velocidad en baudios), y en la interrupción del temporizador, baje nuestra pierna.
Buena manera confiable. Solo el temporizador es una pena; Tradicionalmente, no hay muchos de ellos en el Milandra: dos o tres piezas.
Modo de bucle
Si los lees cuidadosamente. descripción en UART,
por ejemplo, para 1986 BE91T , puede observar este párrafo muy breve:
( ) 1 LBE UARTCR.
Si esos. Si no lee la descripción, se puede lograr casi el mismo efecto al acortar las patas del hardware RX y TX.
Pensamientos en voz altaCuriosamente, ¿de dónde viene este tipo de bucle? Por lo general, este modo se llama "eco", pero bueno.
La idea es la siguiente: antes de transmitir el último byte del paquete, debe activar el modo "loopback". ¡Entonces puede obtener una interrupción por recibir nuestro último byte en el momento en que se arrastra por completo en el autobús! Pues casi.
En la práctica, resultó que la interrupción en la recepción se activa un
poco antes de lo que debería, aproximadamente un tercio del intervalo de bits. No sé con qué está conectado esto; es posible que en el modo de prueba de bucle no ocurra un muestreo real de la línea, tal vez el modo de bucle no tenga en cuenta el último bit de parada. No lo se Sea como fuere, no podemos omitir RE-DE inmediatamente al ingresar a esta interrupción, porque así es como "cortamos" el bit de parada o parte del bit de parada de nuestro último byte.
Estrictamente hablando, podemos o no depender de la relación de la velocidad de la interfaz (es decir, la duración del intervalo de un bit) y la frecuencia del microcontrolador, pero no pude llegar a la frecuencia de 80 MHz con una velocidad de transmisión de 57600.
Otras opciones son posibles.
Si puede permitirse sondear la bandera UART_FR_BUSY por un intervalo de un bit, de hecho, incluso un poco menos, porque ingresar la interrupción y las comprobaciones preliminares también toman tiempo, entonces se encuentra la solución. Para una velocidad de 57600, el tiempo máximo de sondeo será de ~ 18 microsegundos (intervalo de un bit), en la práctica, aproximadamente 5 microsegundos.
Para aquellos que estén interesados, cito el código completo del controlador de interrupciones. void Handle :: irqHandler(void) { UMBA_ASSERT( m_isInited ); m_irqCounter++;
Si puede permitirse el puente (idealmente controlado) entre las patas del RX y TX, entonces todo está bien.
Lamentablemente, hoy no puedo ofrecer otras opciones.
Eso es todo para mí. Si alguien conoce otras formas de resolver este problema, compártalas en los comentarios.
Además, aprovechando la oportunidad y cambiando las reglas de Habr, quiero promocionar el sitio web StartMilandr, que es una colección de artículos sobre los microcontroladores Milander. Por una razón poco clara, puedes buscarlo en Google solo por accidente.
Y, por supuesto, recuerde la existencia de una
bifurcación de la biblioteca periférica estándar, en la que, a diferencia de la biblioteca oficial, los errores se corrigen y hay soporte para gcc.