A função millis
estaria em execução no intervalo de mais de 100 microssegundos ou menos. Existe uma maneira confiável de medir o tempo gasto por uma única chamada em milissegundos?
Uma abordagem que vem à mente é usar micros
, no entanto, uma chamada para micros
incluir também o tempo gasto pela chamada de função micros
; portanto, dependendo de quanto tempo os micros levarem, a medição millis
poderá ser desativada.
Preciso encontrar isso como um aplicativo em que estou trabalhando, que exige medições precisas de tempo para cada etapa executada no código, inclusive millis
.
miilis
.Respostas:
Se você quiser saber exatamente quanto tempo levará algo, existe apenas uma solução: veja a desmontagem!
Começando com o código mínimo:
Esse código compilado e alimentado
avr-objdump -S
produz uma desmontagem documentada. Aqui estão os trechos interessantes:void loop()
produz:Qual é uma chamada de função (
call
), quatro cópias (que copiam cada um dos bytes nouint32_t
valor de retorno demillis()
(observe que os documentos do arduino chamam isso de along
, mas eles estão incorretos por não especificarem explicitamente os tamanhos das variáveis)) e, finalmente, o função de retorno.call
requer 4 ciclos de relógio e cada umsts
requer 2 ciclos de relógio, portanto, temos um mínimo de 12 ciclos de relógio apenas para sobrecarga de chamada de função.Agora, vamos ver a desmontagem da
<millis>
função, localizada em0x14e
:Como você pode ver, a
millis()
função é bastante simples:in
salva as configurações do registro de interrupção (1 ciclo)cli
desliga as interrupções (1 ciclo)lds
copie um dos 4 bytes do valor atual do contador mili em um registro temporário (2 ciclos de relógio)lds
Byte 2 (2 ciclos de relógio)lds
Byte 3 (2 ciclos de relógio)lds
Byte 4 (2 ciclos de relógio)out
restaurar configurações de interrupção (1 ciclo de relógio)movw
registros aleatórios em torno de (1 ciclo de relógio)movw
e novamente (1 ciclo de relógio)ret
retorno da sub-rotina (4 ciclos)Portanto, se somarmos todos, teremos um total de 17 ciclos de relógio na
millis()
própria função, além de uma sobrecarga de chamada de 12, para um total de 29 ciclos de relógio.Supondo uma taxa de clock de 16 Mhz (a maioria dos arduinos), cada ciclo de clock é de
1 / 16e6
segundos ou 0,0000000625 segundos, ou seja, 62,5 nanossegundos. 62,5 ns * 29 = 1,812 microssegundos.Portanto, o tempo total de execução de uma única
millis()
chamada na maioria dos Arduinos será de 1.812 microssegundos .Referência do conjunto AVR
Como nota lateral, há espaço para otimização aqui! Se você atualizar a
unsigned long millis(){}
definição da funçãoinline unsigned long millis(){}
, remova a sobrecarga de chamada (ao custo de um tamanho de código um pouco maior). Além disso, parece que o compilador está fazendo duas jogadas desnecessárias (as duasmovw
chamadas, mas eu não a olhei tão de perto).Realmente, considerando que a sobrecarga da chamada de função é de 5 instruções e o conteúdo real da
millis()
função é de apenas 6 instruções, acho que amillis()
função deve ser realmenteinline
por padrão, mas a base de código do Arduino é bastante otimizada.Aqui está a desmontagem completa para qualquer pessoa interessada:
fonte
sts
não devem ser contados como custos indiretos da chamada: este é o custo de armazenar o resultado em uma variável volátil, o que você normalmente não faria. 2) No meu sistema (Arduino 1.0.5, gcc 4.8.2), não tenho osmovw
. Então, o custo da chamadamillis()
é: 4 ciclos de sobrecarga de chamada + 15 ciclos emmillis()
si = 19 ciclos no total (1,188 µs a 16 MHz).x
é umuint16_t
. No máximo, duas cópias, se essa for a causa. De qualquer forma, a questão é quanto tempomillis()
leva quando usado , não quando chamado, ignorando o resultado. Como qualquer uso prático envolverá fazer algo com o resultado, forcei o armazenamento do resultadovolatile
. Normalmente, o mesmo efeito seria alcançado pelo uso posterior da variável definida como o valor de retorno da chamada, mas eu não queria que essa chamada extra ocupasse espaço na resposta.uint16_t
na fonte não corresponde ao assembly (4 bytes armazenados na RAM). Você provavelmente postou a fonte e a desmontagem de duas versões diferentes.Escreva um esboço que milisem mil vezes, não fazendo um loop, mas copiando e colando. Meça isso e compare-o com o tempo real esperado. Lembre-se de que os resultados podem variar com diferentes versões do IDE (e seu compilador em particular).
Outra opção é alternar um pino de E / S antes e após a chamada em milissegundos, depois medir o tempo para um valor muito pequeno e um valor um pouco maior. Compare os tempos medidos e calcule a sobrecarga.
A maneira mais precisa é dar uma olhada na lista de desmontagem, o código gerado. Mas isso não é para os fracos de coração. Você terá que estudar cuidadosamente a folha de dados quanto tempo leva para cada ciclo de instruções.
fonte
millis()
chamadas?delay
, você está certo. Mas a ideia permanece a mesma, você pode cronometrar um grande número de chamadas e fazer a média delas. Desligar as interrupções globalmente pode não ser uma boa ideia embora; o)Eu segundo chamando millis repetidamente e depois comparando o real versus o esperado.
Haverá uma sobrecarga mínima, mas diminuirá em importância quanto mais vezes você chamar millis ().
Se você olhar para
Você pode ver que millis () é muito pequeno, com apenas 4 instruções
(cli is simply # define cli() \__asm__ \__volatile__ ("cli" ::))
e um retorno.Eu chamaria isso cerca de 10 milhões de vezes usando um loop FOR que é volátil como condicional. A palavra-chave volátil impedirá que o compilador tente qualquer otimização no próprio loop.
Não garanto que o seguinte seja sintaticamente perfeito.
meu palpite é que leva ~ 900ms ou cerca de 56us por chamada a milis. (Eu não tenho um caixa eletrônico à mão com aruduino.
fonte
int temp1,temp2;
paravolatile int temp1,temp2;
para impedir que o compilador os otimize potencialmente.