Arduino / AVR의 코드에 대한 클럭 사이클 모니터링?


11

코드 블록을 모니터링하고 코드가 Arduino 및 / 또는 AVR Atmel 프로세서에서 수행 한 프로세서 클럭주기 수를 결정할 수 있습니까? 또는 코드 실행 전후에 마이크로 초를 모니터링해야합니까? 참고 : 나는 "이 코드가 CPU에서 요구하는 클럭 사이클 수"만큼이나 (실제 초가 경과 한 것과 같이) 실시간에 대해서는 신경 쓰지 않습니다.

내가 생각해 낼 수있는 현재 솔루션은 time.c입니다.

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

lighting.c는 다음을 추가합니다.

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

이 계정으로 전달 된 마이크로 초를 모니터링하여 전달 된 클럭 사이클을 계산 한 다음 마이크로 초를 ToClockCycles ()에 전달할 수 있습니다. 내 질문은, 더 좋은 방법이 있습니까?

참고 사항 : AVR의 성능 모니터링에 유용한 자료가 있습니다. lmgtfy.com 및 다양한 포럼 검색은 타이머 탐색 이외의 명백한 결과를 제공하지 않습니다.

감사

답변:


6

가장 간단한 방법은 코드에서 원하는 코드를 실행하기 전에 핀을 잡아 당기고 코드를 실행 한 후에는 낮게 잡아 당기는 것입니다. 그런 다음 코드 루프를 만들거나 싱글 샷 모드에서 메모리가있는 디지털 오실로스코프를 사용하고 범위를 지정한 다음 고정하십시오. 펄스의 길이는 핀 상태 변경에서 코드 조각과 하나의 클럭 사이클을 실행하는 데 걸린 시간을 알려줍니다 (100 % 확실하지 않은 한 사이클이 필요하다고 생각합니다).


감사. 예, 아마도 이것이 가장 정확한 해결 책임을 알 수 있습니다. 나는 여전히 코드 내부에서 적어도 일반적인 사이클 사용 분석을 제공하는 코드를 사용하고 있습니다. 이 도구를 사용하여 몇 가지 테스트 도구를 작성하고 코드의 효율성 + 관련 코드가 현재 Atmel CPU에서 얼마나 효율적으로 실행되는지에 따라 최대 허용 런타임과 같은 매개 변수의 상한을 설정하는 것이 좋습니다. 사용
cyphunk

4

"모니터"란 무엇을 의미합니까?

작은 어셈블리 코드 조각에 대한 AVR의 클럭주기를 계산하는 것은 어렵지 않습니다.

코드가 실행되기 전에 포트를 설정하고 나중에 재설정 한 다음 로직 분석기 또는 오실로스코프를 사용하여 모니터링하여 타이밍을 얻을 수도 있습니다.

그리고 당신은 또한 당신이 말하는대로 빠른 실행 타이머에서 시간을 읽을 수 있습니다.


모니터에 따르면 코드에서 사용하는주기 수를 결정합니다. (참고로 코드 형식화는 주석 엔진에 의해 전개 될 것입니다) : clocks = startCountingAtmegaClocks (); for ... {for ... {digitalRead ...}} Serial.print ( "사용 된 사이클 수 :"); Serial.print (currentCountingAtmegaClocks ()-시계, DEC);
cyphunk

그러나 네 대답은 내가 선택한 옵션이라고 가정 한 것입니다. 나는 어셈블러 손으로 걸릴 것 클럭 사이클을 계산할 수 있다면 누군가가 아마도 이미 프로그래밍 방식으로 할 수있는 몇 가지 좋은 코드를 작성했습니다 가정, 추측
cyphunk

3

이것은 clockCyclesPerMicrosecond () 함수를 사용하여 통과 한 클럭을 계산하는 Arduino의 예입니다. 이 코드는 4 초간 기다린 다음 프로그램 시작 이후 경과 된 시간을 인쇄합니다. 왼쪽 3 개의 값은 총 시간 (마이크로 초, 밀리 초, 총 클럭 사이클)이며 가장 오른쪽의 3 개는 경과 시간입니다.

산출:

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

첫 번째 루프가 대부분의 시간보다 짧은 클록 사이클을 갖는 이유와 다른 모든 루프가 두 길이의 클록 사이클 간을 전환하는 이유는 합리적입니다.

암호:

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);     

}

주석 : 4 초 지연을 제거하면 Serial.print ()의 효과가 훨씬 더 명확하게 나타납니다. 여기서 2 개의 런이 비교됩니다. 각 로그에서 서로 가까이에 4 개의 샘플 만 포함 시켰습니다.

실행 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

실행 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

경과 시간은 총 실행 시간에 따라 증가합니다. 1 초가 지난 후 클럭은 평균 40k에서 44k로 증가합니다. 이것은 1 초 후에 몇 밀리 초 동안 일관되게 발생하며 경과 된 시계는 최소한 다음 10 초 동안 44k 정도 남아 있습니다 (추가 테스트는하지 않았습니다). 이것이 모니터링이 유용하거나 필요한 이유입니다. 아마도 효율성 감소는 구성이나 버그의 직렬화와 관련이 있습니까? 또는 코드가 메모리를 올바르게 사용하지 않고 성능에 영향을 미치는 누수가 있습니다.


몇 년이 지난 후에도 여전히 오실로스코프로 코드를 사용하여 시계를 더 정확하게 보여주는 무언가를 원합니다. 16MHZ와 8MHZ에서 digitalWrite ()에 필요한 클럭 사이클 수를 결정하려고합니다. 16MHZ에서는 8us / 64clk를 얻습니다. 그러나 8MHZ에서는 0us / 0clk를 얻습니다.
cyphunk

1

소스에 추가 된 모든 코드 줄은 성능에 영향을 미치며 최적화가 적용될 수 있습니다. 작업을 수행하려면 최소한 변경이 필요합니다.

방금 "Annotated Assembly File Debugger"라는 Atmel Studio 플러그인을 찾았습니다. http://www.atmel.com/webdoc/aafdebugger/pr01.html 실제로 생성 된 어셈블리 언어를 단계별로 실행하는 것은 지루한 일이지만 실제로 무슨 일이 일어나고 있는지 정확하게 보여줄 것입니다. 각 명령에 소요되는 사이클 수를 디코딩해야 할 수도 있지만 게시 된 다른 옵션보다 훨씬 더 가깝습니다.

프로젝트의 출력 폴더에 모르는 사람들은 LSS 확장자를 가진 파일입니다. 이 파일에는 모든 원본 소스 코드가 주석으로 포함되어 있으며 각 줄 아래에는 해당 코드 줄을 기반으로 생성 된 어셈블리 언어가 있습니다. LSS 파일 생성을 끌 수 있으므로 다음 설정을 확인하십시오.

프로젝트 속성 | 툴체인 | AVR / GNU 공통 | 출력 파일

확인란 ".lss (lss 파일 생성)


1

내장 타이머 중 하나를 사용할 수 있습니다. 블록 전에 prescaller = 1 및 TCNT = 0에 대해 설정된 모든 것을 가져옵니다. 그런 다음 블록 앞 줄에서 타이머를 활성화하고 블록 뒤 줄에서 타이머를 비활성화하십시오. TCNT는 이제 블록에 걸린주기 수를 유지하며 활성화 및 비활성화 코드에 대한 고정주기를 줄입니다.

TNCT는 16 비트 타이머에서 65535 클럭 사이클 후에 오버플로됩니다. overflow 플래그를 사용하여 런타임을 두 배로 늘릴 수 있습니다. 그래도 더 오래 필요하면 프리스케일러를 사용할 수 있지만 해상도는 떨어집니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.