Por que o Arduino é tão lento e o que pode ser feito sobre isso

LOGO


Uma vez me deparei com um excelente artigo ( tyk ) - nele o autor mostrou claramente a diferença entre usar as funções do Arduino e trabalhar com registros. Muitos artigos foram escritos, elogiando o arduino e argumentando que tudo isso é frívolo e geralmente para crianças; portanto, não o repetiremos, mas tente descobrir o que causou os resultados obtidos pelo autor desse artigo. E, igualmente importante, pensaremos no que podemos fazer. Quem estiver interessado, pergunto sob o gato.


Parte 1 "Perguntas"


Citando o autor deste artigo:


Acontece uma perda de desempenho neste caso - 28 vezes. Obviamente, isso não significa que o arduino funcione 28 vezes mais devagar, mas acho que, para maior clareza, este é o melhor exemplo de por que o Arduino não é apreciado.

Como o artigo acabou de começar, ainda não o entenderemos, mas ignoramos a segunda frase e assumimos que a velocidade do controlador é aproximadamente equivalente à frequência de comutação de pinos. I.e. somos confrontados com a tarefa de criar o gerador da frequência mais alta do que temos. Primeiro, vamos ver o quão ruim tudo é.


Vamos escrever um programa simples para o arduino (na verdade, basta copiar o piscar de olhos).


void setup() { pinMode(13, OUTPUT); } void loop() { digitalWrite(13, 1); // turn the LED on (HIGH is the voltage level) digitalWrite(13, 0); // turn the LED off by making the voltage LOW } 

Costurar no controlador. Como não tenho um osciloscópio, mas apenas um analisador lógico chinês, ele precisa ser configurado corretamente. A frequência máxima do analisador é de 24 MHz e, portanto, deve ser equalizada com a frequência do controlador - definida para 16 MHz. Nós olhamos ...


Teste_1


... por muito tempo. Estamos tentando lembrar do que depende a velocidade do controlador - exatamente, a frequência. Nós olhamos em arduino.cc . A velocidade do clock é de 16 MHz e aqui temos 145,5 kHz. O que fazer Vamos tentar resolvê-lo na testa. No mesmo arduino.cc , examinamos o restante do quadro:


  • Leonardo - não é adequado - também há 16 MHz
  • Mega - também - 16 MHz
  • 101 - vai fazer - 32MHz
  • DUE - Melhor ainda - 84 MHz

Pode-se supor que, se você aumentar a frequência do controlador em 2 vezes, a frequência intermitente do LED também aumentará em 2 vezes e, se em 5, em 5 vezes.


Teste_2


Não obtivemos os resultados desejados. E o gerador é cada vez menos parecido com um meandro. Pensamos mais - agora, provavelmente, a linguagem é ruim. Parece que é com c, c ++, mas é difícil (de acordo com o efeito Dunning-Krueger , não podemos perceber que já estamos escrevendo em c ++), portanto, estamos procurando alternativas. Uma breve pesquisa nos leva ao BASCOM-AVR (não é nada mal dito aqui ), coloque-o e escreva o código:


 $Regfile="m328pdef.dat" $Crystal=16000000 Config Portb.5 = Output Do Toggle Portb.5 Loop 

Temos:


Teste_3


O resultado é muito melhor, além disso, conseguimos o meandro perfeito, mas ... BASIC em 2018, sério? Talvez nós deixemos isso no passado.


Parte 2 "Respostas"


Parece que é hora de parar de fazer de bobo e começar a entender (e também lembrar de si e assembler). Apenas copie o código “útil” do artigo mencionado no início para loop ().


Aqui, acredito, é necessária uma explicação: todo o código será escrito no projeto arduino, mas no ambiente Atmel Studio 7.0 (existe um desmontador conveniente lá), as capturas de tela serão dele.


 void setup() { DDRB |= (1 << 5); // PB5 } void loop() { PORTB &= ~(1 << 5); //OFF PORTB |= (1 << 5); //ON } 

resultado:


Teste_4


Aqui está! Quase o que você precisa. Somente a forma não é particularmente semelhante ao meandro e à frequência, embora já esteja mais próxima, mas ainda não seja a mesma. Também tentamos aumentar o zoom e encontrar lacunas no sinal a cada milissegundo.


Teste_5


Isso ocorre devido ao acionamento de interrupções do timer responsável por millis (). Então, o que faremos é simplesmente desconectar. Estamos procurando por ISR (função de manipulador de interrupção). Nós encontramos:


 ISR(TIMER0_OVF_vect) { // copy these to local variables so they can be stored in registers // (volatile variables must be read from memory on every access) unsigned long m = timer0_millis; nsigned char f = timer0_fract; m += MILLIS_INC; f += FRACT_INC; if (f >= FRACT_MAX) { f -= FRACT_MAX; m += 1; } timer0_fract = f; timer0_millis = m; timer0_overflow_count++; } 

Um monte de código inútil para nós. Você pode alterar o modo de operação do timer ou desativar a interrupção, mas isso é desnecessário para nossos propósitos, portanto, basta desativar todas as interrupções com o comando cli (). Basta olhar para o nosso código:


 PORTB &= ~(1 << 5); //OFF PORTB |= (1 << 5); //ON 

muitos operadores, reduza a uma tarefa.


 PORTB = 0b00000000; //OFF PORTB = 0b11111111; //ON 

Sim, e mudar para loop () requer muitos comandos, pois essa é uma função extra no loop principal.


 int main(void) { init(); // ... setup(); for (;;) { loop(); if (serialEventRun) serialEventRun(); } return 0; } 

Então, faça um loop infinito em setup (). Temos o seguinte:


 void setup() { cli(); DDRB |= (1 << 5); // PB5 while (1) { PORTB = 0b00000000; //OFF PORTB = 0b11111111; //ON } } 

Teste_6


61 ns é o máximo correspondente à frequência do controlador. É possível mais rápido? Spoiler - não. Vamos tentar entender o porquê - para isso, desmontamos nosso código:


Code_asm_1


Como pode ser visto na tela, para gravar na porta 1 ou 0, são gastos exatamente 1 ciclo de clock, mas a transição que não pode ser concluída em menos de um ciclo de clock (o RJMP é executado em dois ciclos de clock e, por exemplo, JMP, em três ) E estamos quase lá - para conseguir um meandro, você precisa aumentar o tempo em que 0 é dado por duas medidas. Adicione a este dois comandos assembler nop que não fazem nada além de levar 1 ciclo de clock:


 void setup() { cli(); DDRB |= (1 << 5); // PB5 while (1) { PORTB = 0b00000000; //OFF asm("nop"); asm("nop"); PORTB = 0b11111111; //ON } } 

Test_end


Parte 3 "Conclusões"


Infelizmente, tudo o que fizemos foi absolutamente inútil do ponto de vista prático, porque não podemos mais executar nenhum código. Além disso, em 99,9% dos casos, as frequências de comutação de portas são suficientes para qualquer finalidade. Sim, e se realmente precisamos gerar um meandro suave, você pode usar o stm32 com dma ou um chip de timer externo como o NE555. Este artigo é útil para entender os dispositivos mega328p e arduino em geral.


No entanto, gravando nos registros de valores de 8 bits PORTB = 0b11111111; muito mais rápido que digitalWrite(13, 1); mas você terá que pagar por isso pela incapacidade de transferir o código para outras placas, porque os nomes dos registros podem ser diferentes.


Resta apenas uma pergunta: por que o uso de pedras mais rápidas não deu resultados? A resposta é muito simples - em sistemas complexos, a frequência gpio é menor que a frequência principal. Mas quanto menor e como configurá-lo sempre pode ser visto na folha de dados de um controlador específico.


A publicação citou artigos:



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


All Articles