Monitorar ciclos de relógio para obter código no arduino / AVR?

11

É possível monitorar um bloco de código e determinar o número de ciclos de clock do processador que o código levou em um processador Armelino e / ou AVR atmel? ou devo monitorar os microssegundos passados ​​antes e depois da execução do código? Nota: Não me preocupo com o tempo real (por exemplo, quantos segundos reais se passaram) tanto quanto em "quantos ciclos de clock esse código requer da CPU"

A solução atual que posso encontrar é do time.c:

#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )

cablagem.c acrescenta:

#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )

Por essa conta, eu poderia calcular os estilos de relógio passados ​​monitorando os microssegundos passados ​​e depois passá-los para microsecondsToClockCycles (). Minha pergunta é: existe uma maneira melhor?

Nota: existem bons recursos para o monitoramento de desempenho do AVR. lmgtfy.com e várias pesquisas em fóruns não fornecem nenhum resultado óbvio, além da exploração de temporizadores

obrigado

cyphunk
fonte

Respostas:

6

O método mais simples é fazer com que seu código puxe alguns pinos antes de executar o código que você deseja cronometrar, e baixe-o depois que terminar de fazer o que quer. Em seguida, faça o loop de código (ou use o osciloscópio digital com memória no modo de disparo único) e apenas escopo e pino. A duração do pulso indica quanto tempo levou para executar o código, mais um ciclo de clock para alterar o estado do pino (acho que leva um ciclo, não 100% de certeza).

Dago
fonte
Obrigado. Sim, posso ver que essa é provavelmente a solução mais precisa. Ainda estou apostando no código que me daria pelo menos uma análise geral do uso do ciclo dentro do código. Vou usar isso para criar algumas ferramentas de teste e seria bom definir meus limites superiores para parâmetros como tempo de execução máximo permitido com base na eficiência com que o código + tudo relacionado a ele está sendo executado na CPU Atmel atual em use
cyphunk 26/11/2009
4

O que você quer dizer com "monitor"?

Não deve ser difícil contar os ciclos de clock do AVR para pequenos pedaços de código de montagem.

Você também pode definir uma porta antes que o código seja executado e redefini-la posteriormente e monitorá-la com um analisador lógico ou um osziloscópio para obter o tempo.

E você também pode ler a hora em um timer de execução rápida, como você diz.

starblue
fonte
Por monitor, quero dizer determinar o número de ciclos usados ​​pelo código. algo como (observe que a formatação do código provavelmente será achatada pelo mecanismo de comentários): clocks = startCountingAtmegaClocks (); for ... {for ... {digitalRead ...}} Serial.print ("num de ciclos usados:"); Serial.print (currentCountingAtmegaClocks () - relógios, DEC);
cyphunk
Mas sim, sua resposta é como assumi minhas opções. Eu acho que, presumo, se eu posso calcular os ciclos de clock do montador levaria à mão que alguém talvez já tenha escrito algum código legal fazer isso programaticamente
cyphunk
3

Este é um exemplo para o Arduino usando a função clockCyclesPerMicrosecond () para calcular os relógios que passaram. Esse código aguardará 4 segundos e imprimirá o tempo decorrido desde o início do programa. Os valores da esquerda 3 são o tempo total (microssegundos, milissegundos, ciclos totais de clock) e os três à direita são os tempos decorridos:

Resultado:

clocks for 1us:16
runtime us, ms, ck :: elapsed tme us, ms ck
4003236 4002	64051776	::	4003236	4002	64051760
8006668 8006	128106688	::	4003432	4004	64054912
12010508    12010	192168128	::	4003840	4004	64061440
16014348    16014	256229568	::	4003840	4004	64061440
20018188    20018	320291008	::	4003840	4004	64061440
24022028    24022	384352448	::	4003840	4004	64061440
28026892    28026	448430272	::	4004864	4004	64077824
32030732    32030	512491712	::	4003840	4004	64061440
36034572    36034	576553152	::	4003840	4004	64061440
40038412    40038	640614592	::	4003840	4004	64061440
44042252    44042	704676032	::	4003840	4004	64061440
48046092    48046	768737472	::	4003840	4004	64061440
52050956    52050	832815296	::	4004864	4004	64077824

Tenho certeza de que há uma explicação razoável por que os primeiros loops também tiveram ciclos de relógio decorridos mais curtos do que a maioria e por que todos os outros loops alternam entre dois comprimentos de ciclos de relógio.

Código:

unsigned long us, ms, ck;
unsigned long _us, _ms, _ck;
unsigned long __us, __ms, __ck;
void setup() {
        Serial.begin(9600);
}
boolean firstloop=1;
void loop() { 
        delay(4000);

        if (firstloop) {
                Serial.print("clocks for 1us:");
                ck=microsecondsToClockCycles(1);
                Serial.println(ck,DEC);
                firstloop--;
                Serial.println("runtime us, ms, ck :: elapsed tme us, ms ck");
        }

        _us=us;
        _ms=ms;
        _ck=ck;

        us=micros(); // us since program start
        ms=millis();
        //ms=us/1000;
        ck=microsecondsToClockCycles(us);
        Serial.print(us,DEC);
        Serial.print("\t");
        Serial.print(ms,DEC);
        Serial.print("\t");
        Serial.print(ck,DEC);     
        Serial.print("\t::\t");

        __us = us - _us;
        __ms = ms - _ms;
        __ck = ck - _ck;
        Serial.print(__us,DEC);
        Serial.print("\t");
        Serial.print(__ms,DEC);
        Serial.print("\t");
        Serial.println(__ck,DEC);     

}

Nota: se você remover o atraso de 4 segundos, começará a ver os efeitos do Serial.print () com muito mais clareza. Observe que aqui são comparadas duas execuções. Eu incluí apenas 4 amostras próximas umas das outras nos respectivos logs.

Execução 1:

5000604 5000	80009664	::	2516	2	40256
6001424 6001	96022784	::	2520	3	40320
7002184 7002	112034944	::	2600	3	41600
8001292 8001	128020672	::	2600	3	41600

Execução 2:

5002460 5002	80039360	::	2524	3	40384
6000728 6000	96011648	::	2520	2	40320
7001452 7001	112023232	::	2600	3	41600
8000552 8000	128008832	::	2604	3	41664

O tempo decorrido aumenta ao longo do tempo total de execução. Após um segundo, os relógios aumentam, em média, de 40k para 44k. Isso acontece consistentemente alguns milissegundos após 1 segundo e os relógios decorridos permanecem em torno de 44k por pelo menos os próximos 10 segundos (eu ainda não o testei mais). É por isso que o monitoramento é útil ou necessário. Talvez a diminuição da eficiência tenha a ver com configuração ou bugs em série? Ou talvez o código não esteja usando a memória corretamente e tenha um vazamento que afeta o desempenho etc.

cyphunk
fonte
muitos anos depois, eu ainda gostaria de algo que mostre os relógios com mais precisão com o código (ao contrário de um osciloscópio). Estou tentando determinar o número de ciclos de relógio necessários para um digitalWrite () em 16MHZ e 8MHZ. Em 16MHZ eu recebo 8us / 64clk. Mas em 8MHZ eu recebo 0us / 0clk.
cyphunk
1

Como cada linha de código adicionada à sua fonte terá um impacto no desempenho e poderá alterar as otimizações aplicadas. As alterações devem ser o mínimo necessário para executar a tarefa.

Acabei de encontrar um plug-in do Atmel Studio chamado "Annotated Assembly File Debugger". http://www.atmel.com/webdoc/aafdebugger/pr01.html Parece percorrer a linguagem assembly gerada real enquanto provavelmente tedioso mostrará exatamente o que está acontecendo. Você ainda pode decodificar quantos ciclos são necessários para cada instrução, mas isso fica muito mais próximo do que algumas das outras opções publicadas.

Para aqueles que não sabem na pasta Saída do seu projeto, é um arquivo com uma extensão LSS. Este arquivo contém todo o seu código-fonte original como comentários e abaixo de cada linha está a linguagem de montagem que foi gerada com base nessa linha de código. A geração do arquivo LSS pode ser desativada. Verifique a seguinte configuração.

Propriedades do projeto | Toolchain | Comum AVR / GNU | OutputFiles

Caixa de seleção ".lss (Gerar arquivo lss)

James
fonte
1

Você pode usar um dos temporizadores embutidos. Prepare tudo para prescaller = 1 e TCNT = 0 antes do bloco. Em seguida, ative o cronômetro na linha antes do bloco e desative-o na linha após o bloco. O TCNT agora retém o número de ciclos que o bloco levou, menos os ciclos fixos para o código de habilitação e desabilitação.

Observe que o TNCT transbordará após o relógio 65535 girar em um timer de 16 bits. Você pode usar o sinalizador de estouro para duplicar o tempo de execução. Se você ainda precisar de mais tempo, poderá usar um pré-calibrador, mas terá menos resolução.

bigjosh
fonte