09
2016-Feb
[AVR] 루프 실행시간 측정 (아두이노의 Millis(), Micros() 분석)
작성자: Blonix
IP ADRESS: *.148.87.98 조회 수: 1456
출처 :: http://www.avrfreaks.net/forum/millis-and-micros-8bits
밑에는 복잡하게 분석을 해뒀는데 사실 단순히 AVR 코드로 Millis() 함수의 기능을 구현하는건 아주아주 간단하다.
그냥 uint32_t 형의 정수를 전역으로 선언해두고 정확히 1ms 마다 오버플로되는 타이머를 만들어서 이 변수를 매 인터럽트마다 1씩 올려주면 된다.
그리고 이 변수를 필요한곳에서 끌어다가 써주면 된다. 끝.
근데 uint32_t 자료형의 한계로 1ms 마다 1씩 상승시킬 경우 가동후 49.7일이 지나면 오버플로된다. 이게 문제될 상황이라면 알아서 대안을 생각해 해결한 뒤에 본 게시글에 댓글을 작성해주기 바란다. (당장 머리속에 떠오르는 바로는 루프dt가 비정상적으로 큰값이 나올 때, 루프 한번을 쉬게 하면 될 것 같다. 물론 센서 적분값에 약간 큰 오차가 누적되겠지만, 그 이전에 49일 연속기동을 해놓고 오차가 누적되지 않기를 바라진 않겠지?)
만약 타이머가 부족해서 도저히 1ms 로 오버플로되는 타이머를 못만들 상황이라면 아래 코드를 분석해보라. 오차보정도 포함된 코드다.
이 글이 필요한 사람중 모르는 사람은 없겠지만, 2016년 현재 avr studio 에서는 인터럽트를 SIGNAL 이 아니라 ISR 로 표기한다.
On the 88/168/328, TIMER0 is set to mode 3, fast PWM, with prescaler 64. At 16 MHz, this gives an overflow interrupt every 1024 uS. The ISR updates three volatiles:
volatile unsigned long timer0_overflow_count = 0;
volatile unsigned long timer0_millis = 0;
static unsigned char timer0_fract = 0;
SIGNAL(TIMER0_OVF_vect)
{
unsigned long m = timer0_millis;
unsigned 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++;
}
... where MILLIS_INC, FRACT_INC, and FRACT_MAX are macros:
#define clockCyclesToMicroseconds(a) ( ((a) * 1000L) / (F_CPU / 1000L) )
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)
// the fractional number of milliseconds per timer0 overflow. we shift right
// by three to fit these numbers into a byte. (for the clock speeds we care
// about - 8 and 16 MHz - this doesn't lose precision.)
#define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
#define FRACT_MAX (1000 >> 3)
... and the function millis() simply returns the value of timer0_millis:
unsigned long millis()
{
unsigned long m;
uint8_t oldSREG = SREG;
cli();
m = timer0_millis;
SREG = oldSREG;
return m;
}
The function micros() is somewhat more involved:
unsigned long micros() {
unsigned long m;
uint8_t oldSREG = SREG, t;
cli();
m = timer0_overflow_count;
t = TCNT0;
if ((TIFR0 & _BV(TOV0)) && (t < 255))
m++;
SREG = oldSREG;
return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}
... and has a resolution of 4 uS at 16 MHz.
So, at the expense of an actual millis() resolution of 1024 uS, the two PWM channels on TIMER0 retain a full 8-bits of resolution for use by analogWrite(). For boards running at 8 MHz (like a Pro Mini 3.3V), millis() resolution drops to 2048 uS, and micros() resolution drops to 8 uS.