Passei muito tempo no mês passado fazendo o UART (para MIDI) trabalhar com um STM (STM32F103C8T6) usando interrupções, sem muito sucesso.
No entanto, esta noite, usando DMA, funcionou muito rápido.
Como, tanto quanto eu leio, o DMA é mais rápido e alivia a CPU, por que nem sempre o DMA é utilizado para interrupções? Especialmente porque no STM32 parece haver alguns problemas.
Estou usando STM32CubeMx / HAL.
uart
dma
stm32cubemx
stm32f103c8t6
Michel Keijzers
fonte
fonte
Respostas:
Embora o DMA libere a CPU e, portanto, possa reduzir a latência de outros aplicativos acionados por interrupção em execução no mesmo núcleo, há custos associados:
Há apenas uma quantidade limitada de canais DMA e existem limitações sobre como esses canais podem interagir com os diferentes periféricos. Outro periférico no mesmo canal pode ser mais adequado para uso de DMA.
Por exemplo, se você tem uma transferência I2C em massa a cada 5ms, isso parece ser um candidato melhor para DMA do que um comando de depuração ocasional que chega no UART2.
Configurar e manter o DMA é um custo por si só. (Normalmente, configurar o DMA é considerado mais complexo do que configurar a transferência acionada por interrupção normal por caractere, devido ao gerenciamento de memória, mais periféricos envolvidos, o uso do DMA interrompe-se e a possibilidade de você precisar analisar os primeiros caracteres fora do DMA de qualquer forma, veja abaixo.)
O DMA pode usar energia adicional , uma vez que é outro domínio do núcleo que precisa ser cronometrado. Por outro lado, você pode suspender a CPU enquanto a transferência do DMA estiver em andamento, se o núcleo suportar isso.
O DMA exige que os buffers de memória funcionem (a menos que você esteja usando DMA periférico a periférico), para que haja algum custo de memória associado a ele.
(O custo da memória também pode estar presente ao usar interrupções por caractere, mas também pode ser muito menor ou desaparecer se as mensagens forem interpretadas imediatamente dentro da interrupção.)
O DMA produz uma latência porque a CPU é notificada apenas quando a transferência é concluída / metade da conclusão (consulte as outras respostas).
Exceto ao transmitir dados para / de um buffer de anel, você precisa saber com antecedência quantos dados você receberá / enviará.
Isso pode significar que é necessário processar os primeiros caracteres de uma mensagem usando interrupções por caractere: por exemplo, ao fazer interface com um XBee, você primeiro lê o tipo e tamanho do pacote e, em seguida, aciona uma transferência de DMA para um buffer alocado.
Para outros protocolos, isso pode não ser possível, se eles usam apenas delimitadores de final de mensagem: por exemplo, protocolos baseados em texto que usam
'\n'
como delimitador. (A menos que o periférico DMA suporte a correspondência em um caractere.)Como você pode ver, há muitas compensações a serem consideradas aqui. Alguns estão relacionados a limitações de hardware (número de canais, conflitos com outros periféricos, correspondência de caracteres), outros são baseados no protocolo usado (delimitadores, comprimento conhecido, buffers de memória).
Para adicionar alguma evidência anedótica, eu enfrentei todas essas compensações em um projeto de hobby que usava muitos periféricos diferentes com protocolos muito diferentes. Havia algumas compensações a serem feitas, principalmente com base na pergunta "quantos dados estou transferindo e com que frequência vou fazer isso?". Isso essencialmente fornece uma estimativa aproximada do impacto de uma simples transferência acionada por interrupção na CPU. Assim, dei prioridade à transferência I2C acima mencionada a cada 5ms na transferência UART a cada poucos segundos que usava o mesmo canal DMA. Outra transferência UART acontecendo com mais frequência e com mais dados, por outro lado, tem prioridade sobre outra transferência I2C, o que ocorre mais raramente. É tudo uma troca.
Obviamente, usar o DMA também tem vantagens, mas não foi isso que você pediu.
fonte
Usar DMA geralmente significa que você não está mais interrompendo todos os caracteres, mas apenas depois que um "buffer cheio" de caracteres foi recebido (ou transmitido). Isso aumenta a latência do processamento desses caracteres - o primeiro caractere não é processado até que o último caractere no buffer tenha sido recebido.
Essa latência pode ser uma coisa ruim, especialmente em um aplicativo sensível à latência, como o MIDI, onde alguns ms aqui e ali podem resultar em sérios problemas de jogabilidade para apresentações ao vivo.
fonte
O DMA não substitui as interrupções - elas geralmente são usadas juntas! Se você estiver usando o DMA para enviar dados por um UART, por exemplo, ainda precisará de uma interrupção para informar quando o envio está concluído.
fonte
O uso do DMA apresenta algumas questões e desafios interessantes além de todas as outras considerações sobre o uso de periféricos UART. Vou dar alguns exemplos: Suponha que o seu uC esteja sentado em um barramento RS485 (ou o que seja) com outros dispositivos. Existem muitas mensagens no barramento, algumas são destinadas ao seu uC, outras não. Além disso, suponha que esses vizinhos de barramento falem um protocolo de dados diferente, o que implica que os comprimentos das mensagens são diferentes.
Algumas perguntas que surgem apenas ao usar o DMA são:
Enfim, apenas comida para pensar.
fonte
No lado de recebimento (pelo que me lembro), o DMA termina em uma correspondência de caracteres ou na contagem de terminais. Alguns protocolos e muitos aplicativos interativos não se encaixam facilmente neste modelo e você realmente precisa lidar com as coisas, caractere por caractere. As técnicas de DMA também podem ser frágeis se o link de comunicação não for confiável, perder um único caractere no fluxo pode facilmente atrapalhar sua máquina de estado de DMA.
fonte
Eu usei o STM32CubeMx / HAL em alguns projetos agora e constatei que o software de manipulação UART que ele gera possui deficiências definidas no lado de recebimento.
Na transmissão, você normalmente deseja enviar um bloco de dados ou linha de texto. Nesse caso, você sabe de antemão quanto tempo dura a transferência de dados e, portanto, o uso do DMA é uma solução óbvia. Você recebe uma interrupção assim que a transferência é concluída e pode usar a função de retorno de chamada completa UART TX para indicar ao código principal que a transmissão está concluída e você pode enviar outro bloco de dados.
Quando se trata de recepção de dados, todas as funções fornecidas pelo ST assumem que você sabe quantos caracteres o dispositivo remetente fornecerá antes de começar a enviar. Normalmente isso não é conhecido. A funcionalidade de interrupção coloca os dados recebidos em um buffer e indica apenas que existem dados disponíveis quando o número predefinido de caracteres foi recebido. Se você tentar usar o DMA ou interromper a funcionalidade para receber dados configurando transferências sequenciais de caracteres únicos, o tempo de configuração de cada uma delas significará que você perderá caracteres com algo diferente das taxas de dados mais lentas (a taxa de transmissão que você deseja começar a perder dados dependerá da velocidade do clock do processador) e carregará o processador excessivamente, não deixando ciclos de instruções para nenhum outro processamento
Para contornar isso, escrevi minha própria função de manipulador de interrupção que armazena os dados em um pequeno buffer circular local e define uma contagem que é lida pelo código principal (um semáforo de contagem RTOS) para indicar que há dados recebidos prontos. O código principal pode, então, coletar os dados desse buffer à vontade, não importa se houver algum atraso na coleta dos dados, desde que o buffer local não transborde antes que os dados sejam coletados.
fonte