RS-485 em microcontroladores domésticos da empresa Milander


Alguns dias atrás, tive a imprudência de prometer veladamente cortar um post sobre Milander ... Bem, vamos tentar.

Como você provavelmente já sabe, existe uma empresa russa Milander, que, entre outras coisas, produz microcontroladores baseados no núcleo ARM Cortex-M. Pela vontade do destino, fui obrigado a conhecê-los com força e conhecia a dor .

Uma pequena parte dessa dor causada pelo trabalho com o RS-485 é descrita abaixo. Peço desculpas antecipadamente se eu pensar demais em conceitos básicos, mas queria tornar este artigo acessível a uma compreensão de um público mais amplo.
Também farei uma reserva antecipada de que só lidei com 1986-1991 e 1986-1, não posso falar com confiança de outros.

TL DR
O UART de Milandrovsk não possui a interrupção "Transmit complete", a muleta é o "modo de teste de loopback", ou seja, modo de eco. Mas com as nuances.

Entrada


A interface RS-485 (também conhecida como EIA-485, embora eu nunca a tenha chamado na vida cotidiana) é uma interface half-duplex assíncrona com uma topologia de barramento. Esse padrão estipula apenas a física - ou seja, níveis de tensão e diagramas de tempo - mas não especifica o protocolo de troca, proteção contra erros de transmissão, arbitragem e similares.

De fato, o RS-485 é apenas um UART half-duplex com níveis de tensão diferencial mais altos. É essa simplicidade que garante a popularidade do RS-485.
Para converter UART em RS-485, são usados ​​microcircuitos conversores especiais, como MAX485 ou 5559IN10AU (do mesmo Milander). Eles trabalham quase "de forma transparente" para o programador, que só pode escolher o modo correto de operação do chip - recepção ou transmissão. Isso é feito usando as pernas nRE (não Receiver Output Enable) e DE (Driver Output Enable), que, como regra, são combinadas e controladas por uma perna do microcontrolador.

Levantar esta perna muda o chip para a transmissão e abaixá-lo para a recepção.
Consequentemente, tudo o que é necessário para o programador é elevar esse trecho RE-DE, transferir o número desejado de bytes, diminuir o trecho e aguardar uma resposta. Parece bastante simples, certo?
Hehe.

O problema


Essa perna deve ser abaixada no momento em que todos os bytes transmitidos sejam completamente transferidos para a linha. Como pegar esse momento? Para fazer isso, você precisa capturar o evento "Transmissão concluída" (transmissão concluída), que gera um bloco UART no microcontrolador. Na maioria das vezes, os eventos estão definindo um pouco em alguma solicitação de registro ou interrupção. Para capturar a configuração de um bit no registro, o registro deve ser pesquisado, ou seja, use código como este:

while( MDR_UART1->FR & UART_FR_BUSY ) {;} 

Isso ocorre se pudermos parar completamente o programa até que todos os bytes sejam transferidos. Como regra, não podemos permitir isso.

A interrupção nesse aspecto é muito mais conveniente, pois chega por si só, de forma assíncrona. Interrompendo, podemos rapidamente omitir o RE-DE e todo o negócio.

Obviamente, se pudéssemos fazer isso, não haveria dor e esse post também não existiria.

O fato é que no bloco UART, que Milander coloca em todos os seus microcontroladores no Cortex-M (tanto quanto eu sei), não há interrupção para o evento "Transfer Complete". Existe apenas uma bandeira. E há uma interrupção "O buffer do transmissor está vazio". E o byte interrompe, é claro.

Ainda tem
várias outras interrupções e o modo FIFO, na minha opinião, é completamente inútil. Se alguém entender por que ele é necessário, conte-nos!

O problema é que "Transmit Buffer is Empty" - isso não é o mesmo que "Transmission Complete". Tanto quanto eu entendo o dispositivo UART interno, o evento "Buffer Empty" significa que há pelo menos um espaço livre no buffer do transmissor. Mesmo que esse local seja apenas um (ou seja, um buffer de tamanho de um byte), isso significa apenas que o último byte transmitido foi copiado no registro de deslocamento interno, do qual esse byte entrará na linha, pouco a pouco.

Em resumo, o evento "o buffer do transmissor está vazio" não significa que todos os bytes foram completamente transmitidos. Se omitirmos o RE-DE neste momento, "cortaremos" nosso pacote.

O que fazer?

Rebus



Decodificação:
"Campos de remoção de ervas daninhas" é um meme local de um tópico curto, mas doloroso, no fórum de Milander - forum.milandr.ru/viewtopic.php?f=33&t=626 .
A solução mais simples é "remover ervas daninhas" (do inglês "poll" - polling contínuo) sinalizar UART_FR_BUSY.

Obviamente, essa solução não é muito boa. Se não podemos bloquear este sinalizador, temos que verificá-lo periodicamente. Para verificá-lo periodicamente, você precisa cercar um jardim inteiro (especialmente se você quiser escrever um módulo portátil, e não apenas resolver esse problema uma vez).

Se você usa algum tipo de RTOS, então, para evitar esse problema, é necessário iniciar uma tarefa separada inteira, acordá-la em interrupção, definir não a menor prioridade, aborrecimento, enfim.

Mas, parece, ok, atormentado uma vez, então usamos e nos alegramos. Mas não.
Infelizmente, não é suficiente omitir o RE-DE estritamente depois que todos os bytes foram transmitidos até o final. Precisamos abaixá-lo não muito tarde . Porque não estamos sozinhos no ônibus. Provavelmente, algum tipo de resposta de outro assinante deve chegar à nossa mensagem. E se omitirmos o RE-DE tarde demais, não mudaremos para o modo de recebimento e perderemos alguns bits de resposta.

O tempo que podemos “superexpor” a perna RE-DE depende principalmente da velocidade de transmissão (taxa de transmissão) e da velocidade do dispositivo com o qual nos comunicamos no barramento.
No meu caso, a velocidade era relativamente baixa (57600 baud) e o dispositivo era bastante brincalhão. E às vezes acontecia que a resposta perdia um ou dois.

No geral, não é uma boa solução.

Temporizador


A segunda opção que vem à mente é usar um timer de hardware. Então, na interrupção “Transmitter Buffer Empty”, iniciamos um cronômetro com um tempo limite igual ao tempo de transmissão de um byte (esse tempo é facilmente calculado a partir da taxa de transmissão) e, na interrupção do temporizador, abaixamos a perna.

Maneira boa e confiável. Somente o cronômetro é uma pena; Tradicionalmente, não há muitos deles na Milandra - duas ou três peças.

Modo de loop


Se você ler com atenção. descrição no UART - por exemplo, para 1986 BE91T - você pode notar este parágrafo muito curto:



( ) 1 LBE UARTCR.


Se aqueles. Se você não ler a descrição, poderá obter quase o mesmo efeito, colocando as pernas do hardware RX e TX em curto.

Pensamentos em voz alta
Curiosamente, onde isso ocorre algum tipo de loop? Normalmente, este modo é chamado de "eco", mas tudo bem.

A idéia é a seguinte - antes de transmitir o último byte no pacote, você precisa ativar o modo "loopback". Então você pode interromper o recebimento do último byte no momento em que ele se arrasta completamente para o ônibus! Bem, quase.

Na prática, descobriu-se que a interrupção na recepção é acionada um pouco mais cedo do que deveria, cerca de um terço do intervalo de bits. Não sei com o que isso está conectado; é possível que no modo de teste de loop não ocorra uma amostragem real da linha, talvez o modo de loop não leve em consideração o último bit de parada. Eu não sei Seja como for, não podemos omitir o RE-DE imediatamente após entrar nessa interrupção, porque é assim que "cortamos" o bit de parada ou parte do bit de parada do nosso último byte.

Estritamente falando, podemos ou não depender da proporção da velocidade da interface (ou seja, a duração do intervalo de um bit) e da frequência do microcontrolador, mas não consegui chegar à frequência de 80 MHz com uma taxa de transmissão de 57600.

Outras opções são possíveis.

Se você puder pesquisar o sinalizador UART_FR_BUSY por um intervalo de um bit - na verdade, um pouco menos, porque inserir a interrupção e verificações preliminares também leva tempo - então a solução é encontrada. Para uma velocidade de 57600, o tempo máximo de pesquisa será de ~ 18 microssegundos (intervalo de um bit), na prática - cerca de 5 microssegundos.

Para os interessados, cito todo o código do manipulador de interrupções.
 void Handle :: irqHandler(void) { UMBA_ASSERT( m_isInited ); m_irqCounter++; // ---------------------------------------------  // do     break do { if ( UART_GetITStatusMasked( m_mdrUart, UART_IT_RX ) != SET ) break; // -,     ,     UART_ClearITPendingBit( m_mdrUart, UART_IT_RX ); uint8_t byte = UART_ReceiveData( m_mdrUart ); //  485   ,        if( m_rs485Port != nullptr && m_echoBytesCounter > 0 ) { //     m_echoBytesCounter--; if( m_echoBytesCounter == 0 ) { //     ____, //        ,  -      // -   . //   ,      -. //     ,   : //  |  , |  , | // |  |  | // | | | // 9600 | 105 | 32 | // 57600 | 18 | 4,5 | // 921600 | 1 | 0 | // | | | //      /  , //      . // ,     ,   . //    while( m_mdrUart->FR & UART_FR_BUSY ) {;} //          rs485TransmitDisable(); // ,    #ifdef UART_USE_FREERTOS osSemaphoreGiveFromISR( m_transmitCompleteSem, NULL ); #endif } break; } //      -      overrun #ifdef UART_USE_FREERTOS BaseType_t result = osQueueSendToBackFromISR( m_rxQueue, &byte, NULL ); if( result == errQUEUE_FULL ) { m_isRxOverrun = true; } #else if( m_rxBuffer.isFull() ) { m_isRxOverrun = true; } else { m_rxBuffer.writeHead(byte); } #endif } while( 0 ); // ---------------------------------------------  //    -   ! //  ,  SPL    m_error = m_mdrUart->RSR_ECR; if( m_error != error_none ) { //     m_mdrUart->RSR_ECR = 0; } // ---------------------------------------------  if( UART_GetITStatusMasked( m_mdrUart, UART_IT_TX ) != SET ) return; //    485 -    if( m_txCount == m_txMsgSize - 1 && m_rs485Port != nullptr ) { setEchoModeState( true ); m_echoBytesCounter = 2; } //   else if( m_txCount == m_txMsgSize ) { //    ( )      UART_ClearITPendingBit( m_mdrUart, UART_IT_TX ); m_pTxBuf = nullptr; return; } //  ,   UMBA_ASSERT( m_pTxBuf != nullptr ); UART_SendData( m_mdrUart, m_pTxBuf[ m_txCount ] ); m_txCount++; } 


Se você puder comprar o jumper (idealmente controlado) entre as pernas do RX e TX, tudo estará bem.

Infelizmente, hoje não posso oferecer outras opções.

Isso é tudo para mim. Se alguém souber outras maneiras de resolver esse problema, compartilhe-os nos comentários.

Além disso, aproveitando a oportunidade e alterando as regras da Habr, quero promover o site StartMilandr, que é uma coleção de artigos sobre os microcontroladores Milander. Por uma razão pouco clara, você pode pesquisar no Google apenas por acidente.

E, é claro, lembre-se da existência de um fork da biblioteca periférica padrão, na qual, ao contrário da biblioteca oficial, os bugs são corrigidos e há suporte para o gcc.

Source: https://habr.com/ru/post/pt439450/


All Articles