Il y a quelques jours, j'ai eu l'imprudence de promettre voilé de couper un article sur Milander ... Eh bien, essayons.
Comme vous le savez probablement déjà, il existe une société russe Milander, qui, entre autres, produit des microcontrôleurs basés sur le cœur ARM Cortex-M. Par la volonté du destin, j'ai été obligé de les connaître suffisamment et j'ai
connu la douleur .
Une petite partie de cette douleur causée par le travail avec RS-485 est décrite ci-dessous. Je m'excuse à l'avance si je mâche trop sur les concepts de base, mais je voulais rendre cet article accessible à une compréhension d'un public plus large.
Je ferai également une réserve à l’avance que je n’ai traité que de 198691 et 19861, je ne peux pas parler des autres avec confiance.
TL DRL'UART de Milandrovsk n'a pas l'interruption «Transmission terminée», la béquille est le «mode de test de bouclage», c'est-à-dire mode écho. Mais avec les nuances.
Entrée
L'interface RS-485 (également connue sous le nom d'EIA-485, bien que je ne l'ai jamais entendue appeler dans la vie quotidienne) est une interface semi-duplex asynchrone avec une topologie de bus. Cette norme stipule uniquement la physique - c'est-à-dire niveaux de tension et chronogrammes - mais ne spécifie pas le protocole d'échange, la protection contre les erreurs de transmission, l'arbitrage, etc.
En fait, le RS-485 n'est qu'un UART semi-duplex avec des niveaux de tension différentielle plus élevés. C'est cette simplicité qui assure la popularité du RS-485.
Pour convertir UART en RS-485, des microcircuits de convertisseur spéciaux sont utilisés, tels que MAX485 ou 5559IN10AU (du même Milander). Ils fonctionnent presque "de manière transparente" pour le programmeur, qui ne peut que choisir le mode de fonctionnement correct de la puce - réception ou transmission. Cela se fait en utilisant les jambes nRE (pas Receiver Output Enable) et DE (Driver Output Enable), qui, en règle générale, sont combinées et contrôlées par une jambe du microcontrôleur.
Le fait de soulever cette jambe fait passer la puce en émission et la baisse en réception.
En conséquence, tout ce qui est requis du programmeur est d'augmenter cette jambe RE-DE, de transférer le nombre d'octets souhaité, d'abaisser la jambe et d'attendre une réponse. Cela semble assez simple, non?
Hehe.
Le problème
Cette jambe doit être abaissée à un moment où tous les octets transmis sont complètement transférés vers la ligne. Comment saisir ce moment? Pour ce faire, vous devez intercepter l'événement "Transmit complete" (transmission terminée), qui génère un bloc UART dans le microcontrôleur. Pour la plupart, les événements définissent un bit dans une demande de registre ou d'interruption. Pour attraper le réglage d'un bit dans le registre, le registre doit être interrogé, c'est-à-dire utilisez un code comme celui-ci:
while( MDR_UART1->FR & UART_FR_BUSY ) {;}
C'est si nous pouvons nous permettre d'arrêter complètement le programme jusqu'à ce que tous les octets soient transférés. En règle générale, nous ne pouvons pas nous le permettre.
L'interruption à cet égard est beaucoup plus pratique, car elle arrive d'elle-même, de manière asynchrone. En cas d'interruption, nous pouvons rapidement omettre RE-DE et l'ensemble de l'entreprise.
Bien sûr, si nous pouvions le faire, il n'y aurait pas de douleur et ce poste n'existerait pas non plus.
Le fait est que dans le bloc UART, que Milander place dans tous ses microcontrôleurs sur le Cortex-M (pour autant que je sache), il n'y a pas d'interruption pour l'événement "Transfer Complete". Il n'y a qu'un drapeau. Et il y a une interruption "Le tampon de l'émetteur est vide." Et l'interruption d'octet, bien sûr.
Ont encoreun tas d'autres interruptions et le mode FIFO, à mon avis, est complètement inutile. Si quelqu'un comprend pourquoi il est nécessaire, dites-le nous!
Le problème est que «le tampon de transmission est vide» - ce n'est pas du tout la même chose que «transmission terminée». Pour autant que je sache le périphérique UART interne, l'événement "Buffer Empty" signifie qu'il y a au moins un espace libre dans le tampon de l'émetteur. Même si cet endroit n'est qu'un (c'est-à-dire un tampon d'une taille d'octet), cela signifie seulement que le dernier octet transmis a été copié dans le registre à décalage interne, à partir duquel cet octet se glissera sur la ligne, bit par bit.
En bref, l'événement «le tampon de l'émetteur est vide» ne signifie pas que tous les octets ont été entièrement transmis. Si nous omettons RE-DE à ce moment, nous «couperons» notre paquet.
Que faire?
Rebus
Décodage:"Weeding bit fields" est un mème local d'un court mais douloureux sujet sur le forum de
Milander -
forum.milandr.ru/viewtopic.php?f=33&t=626 .
La solution la plus simple consiste à "désherber" (de l'anglais "poll" - sondage continu) le drapeau UART_FR_BUSY.
Bien sûr, cette solution n'est pas très agréable. Si nous ne pouvons pas bloquer ce drapeau, nous devons le vérifier périodiquement. Pour le vérifier périodiquement, vous devez clôturer tout un jardin (surtout si vous voulez écrire un module portable, et pas seulement résoudre ce problème une fois).
Si vous utilisez une sorte de RTOS, alors pour ce désherbage, vous devez commencer une tâche distincte, la réveiller en interruption, ne pas lui donner la priorité la plus basse, sans tracas, en bref.
Mais, il semblerait, d'accord, tourmenté une fois, puis nous utilisons et nous réjouissons. Mais non.
Malheureusement, il ne nous suffit pas d'omettre RE-DE strictement après que tous les octets ont été transmis à la fin. Nous devons l'abaisser
pas trop tard . Parce que nous ne sommes pas seuls dans le bus. Très probablement, une sorte de réponse d'un autre abonné devrait venir à notre message. Et si nous omettons RE-DE trop tard, nous ne passerons pas en mode réception et ne perdrons pas quelques bits de réponse.
Le temps que nous pouvons nous permettre de «surexposer» la jambe RE-DE dépend principalement de la vitesse de transmission (débit en bauds) et de la vitesse de l'appareil avec lequel nous communiquons sur le bus.
Dans mon cas, la vitesse était relativement faible (57600 bauds) et l'appareil était assez fringant. Et parfois, il arrivait que la réponse perde un peu ou deux.
Dans l'ensemble, ce n'est pas une bonne solution.
Minuterie
La deuxième option qui vient à l'esprit est d'utiliser une minuterie matérielle. Ensuite, dans l'interruption «Transmitter Buffer Empty», nous démarrons un chronomètre avec un délai qui est égal au temps de transmission d'un octet (ce temps est facilement calculé à partir du débit en bauds), et dans l'interruption du chronomètre, abaissons notre jambe.
Bon moyen fiable. Seul le minuteur est dommage; Traditionnellement, il n'y en a pas beaucoup dans la Milandra - deux ou trois pièces.
Mode boucle
Si vous les lisez attentivement. description sur UART -
par exemple, pour 1986 BE91T - vous pouvez remarquer ce très court paragraphe:
( ) 1 LBE UARTCR.
Si ceux-là. Si vous ne lisez pas la description, alors le même effet peut être obtenu en court-circuitant les jambes du matériel RX et TX.
Pensées à haute voixFait intéressant, d'où vient cette sorte de boucle? Habituellement, ce mode est appelé "écho", mais bon.
L'idée est la suivante - avant de transmettre le dernier octet du package, vous devez activer le mode "loopback". Ensuite, vous pouvez obtenir une interruption pour recevoir notre propre dernier octet au moment où il rampe complètement dans le bus! Enfin presque.
En pratique, il s'est avéré que l'interruption à la réception est déclenchée un
peu plus tôt que prévu, environ un tiers de l'intervalle de bits. Je ne sais pas à quoi cela est lié; il est possible qu'en mode test de boucle il n'y ait pas d'échantillonnage réel de la ligne, peut-être que le mode boucle ne prend pas en compte le dernier bit d'arrêt. Je ne sais pas. Quoi qu'il en soit, nous ne pouvons pas omettre RE-DE immédiatement en entrant dans cette interruption, car c'est ainsi que nous «coupons» le bit d'arrêt ou une partie du bit d'arrêt de notre dernier octet.
À strictement parler, nous pouvons ou non dépendre du rapport de la vitesse de l'interface (c'est-à-dire de la durée d'un intervalle de bits) et de la fréquence du microcontrôleur, mais je ne pouvais pas atteindre la fréquence de 80 MHz avec un débit en bauds de 57600.
D'autres options sont possibles.
Si vous pouvez vous permettre d'interroger l'indicateur UART_FR_BUSY pendant un intervalle de bits - en fait, même un peu moins, car la saisie de l'interruption et des vérifications préliminaires prennent également du temps - alors la solution est trouvée. Pour une vitesse de 57600, le temps d'interrogation maximum sera de ~ 18 microsecondes (intervalle d'un bit), en pratique - environ 5 microsecondes.
Pour ceux qui sont intéressés, je cite tout le code du gestionnaire d'interruption. void Handle :: irqHandler(void) { UMBA_ASSERT( m_isInited ); m_irqCounter++;
Si vous pouvez vous permettre le cavalier (idéalement contrôlé) entre les jambes du RX et du TX, alors tout va bien.
Malheureusement, aujourd'hui, je ne peux pas offrir d'autres options.
C’est tout pour moi. Si quelqu'un connaît d'autres moyens de résoudre ce problème, veuillez les partager dans les commentaires.
De plus, en saisissant l'occasion et en modifiant les règles Habr, je souhaite promouvoir le site Web StartMilandr, qui est une collection d'articles sur les microcontrôleurs Milander. Pour une raison peu claire, vous ne pouvez le rechercher sur Google que par accident.
Et, bien sûr, rappelez-vous l'existence d'une
fourchette de la bibliothèque périphérique standard, dans laquelle, contrairement à la bibliothèque officielle, les bogues sont corrigés et il existe un support gcc.