누군가 타이머를 설정하는 데 사용되는이 이상한 코드를 설명 할 수 있습니까?


10

다른 사람들이 작성한 스케치를 보면서 때로는 다음과 같은 코드가 나타납니다.

TCCR1A = 0;
TCCR1B = 0;

TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);

내가 아는 것은 타이밍 / 타이머와 관련이 있다는 것입니다 (제 생각에는). 이와 같은 코드를 어떻게 해독하고 만들 수 있습니까? 무엇 TCCR1A, TCCR1B, TCNT1, CS12, TIMSK1,와 TOIE1?



1
Atmel 웹 사이트 에서 장치의 "완료"데이터 시트를 다운로드하고 타이머에 대한 장을 읽으십시오. 데이터 시트는 놀랍게도 내 의견으로는 읽기 좋습니다.
jippie 2012

답변:


15

이상해 보이지 않습니다. 일반적인 MCU 코드는 실제로 다음과 같습니다.

여기에있는 것은 메모리 매핑 주변 장치 개념의 예입니다 . 기본적으로 MCU 하드웨어에는 할당 된 MCU의 SRAM 주소 공간에 특별한 위치가 있습니다. 이 주소에 쓰면 n 주소에 쓰인 바이트의 비트 가 주변 장치 m 의 동작을 제어합니다 .

기본적으로 특정 메모리 뱅크에는 문자 그대로 SRAM 셀에서 하드웨어로 연결되는 와이어가 거의 없습니다. 해당 바이트의이 비트에 "1"을 쓰면이 SRAM 셀이 논리적 하이로 설정되어 하드웨어의 일부가 켜집니다.

MCU의 헤더를 살펴보면 키워드 <-> 주소 매핑의 큰 테이블이 있습니다. 이것은 TCCR1B컴파일 타임에 등등 과 같은 것들이 해결되는 방식입니다.

이 메모리 매핑 메커니즘은 MCU에서 매우 광범위하게 사용됩니다. arduino의 ATmega MCU는 PIC, ARM, MSP430, STM32 및 STM8 MCU 시리즈와 마찬가지로 익숙하지 않은 많은 MCU를 사용합니다.


Arduino 코드는 MCU 제어 레지스터에 간접적으로 액세스하는 기능을 가진 이상한 것들입니다. 이것은 다소 "더욱 똑똑해"보이지만 훨씬 느리며 더 많은 프로그램 공간을 사용합니다.

신비한 상수는 모두 ATmega328P 데이터 시트에 자세하게 설명되어 있으며 , arduino에서 핀으로 작동하는 토글 핀에 더 관심이 있다면 실제로 읽어보십시오.

위에 링크 된 데이터 시트에서 발췌를 선택하십시오.

여기에 이미지 설명을 입력하십시오 여기에 이미지 설명을 입력하십시오 여기에 이미지 설명을 입력하십시오

예를 들어 TIMSK1 |= (1 << TOIE1);비트를 TOIE1로 설정합니다 TIMSK1. 이것은 헤더 파일에 0으로 정의 된 이진 1 ( 0b00000001)을 TOIE1비트 단위로 왼쪽 으로 이동하여 달성됩니다. TOIE1그런 다음 현재 값인 비트 단위로 OR됩니다 TIMSK1.

의 비트 0에 대한 설명서를 보면 TIMSK1다음과 같이 설명되어 있습니다.

이 비트가 1에 쓰여지고 상태 레지스터의 I- 플래그가 설정되면 (전역 적으로 인터럽트 가능) 타이머 / 카운터 1 오버 플로우 인터럽트가 활성화됩니다. TIFR1에있는 TOV1 플래그가 설정되면 해당 인터럽트 벡터 (57 페이지의 "인터럽트"참조)가 실행됩니다.

다른 모든 줄은 같은 방식으로 해석해야합니다.


몇 가지 참고 사항 :

당신은 또한 같은 것을 볼 수 있습니다 TIMSK1 |= _BV(TOIE1);. _BV()A는 일반적으로 사용되는 매크로 로부터 원래 구현 libc의 AVR은 . 가독성 향상 _BV(TOIE1)을 위해 기능적으로와 동일합니다 (1 << TOIE1).

또한 다음과 같은 줄이 나타날 수도 있습니다. TIMSK1 &= ~(1 << TOIE1);또는 TIMSK1 &= ~_BV(TOIE1);. 비트의 비트 를 해제TIMSK1 |= _BV(TOIE1); 한다는 점에서 반대 기능을 합니다 . 이는에 의해 생성 된 비트 마스크를 가져 와서 비트 NOT 연산을 수행 한 다음 ( ) 이 NOT 값 (0b11111110)으로 AND를 수행 함으로써 달성됩니다.TOIE1TIMSK1_BV(TOIE1)~TIMSK1

이 모든 경우 에 컴파일 시간에(1 << TOIE1) 또는 컴파일 시간에_BV(TOIE1)이 완전히 해석 되므로 기능상으로 간단한 상수로 줄어들므로 런타임에 계산하는 데 실행 시간이 걸리지 않습니다.


올바르게 작성된 코드는 일반적으로 레지스터에 수행 할 작업을 자세히 설명하는 코드와 함께 주석이 표시됩니다. 최근에 작성한 매우 간단한 소프트 SPI 루틴은 다음과 같습니다.

uint8_t transactByteADC(uint8_t outByte)
{
    // Transfers one byte to the ADC, and receives one byte at the same time
    // does nothing with the chip-select
    // MSB first, data clocked on the rising edge

    uint8_t loopCnt;
    uint8_t retDat = 0;

    for (loopCnt = 0; loopCnt < 8; loopCnt++)
    {
        if (outByte & 0x80)         // if current bit is high
            PORTC |= _BV(ADC_MOSI);     // set data line
        else
            PORTC &= ~(_BV(ADC_MOSI));  // else unset it

        outByte <<= 1;              // and shift the output data over for the next iteration
        retDat <<= 1;               // shift over the data read back

        PORTC |= _BV(ADC_SCK);          // Set the clock high

        if (PINC & _BV(ADC_MISO))       // sample the input line
            retDat |= 0x01;         // and set the bit in the retval if the input is high

        PORTC &= ~(_BV(ADC_SCK));       // set clock low
    }
    return retDat;
}

PORTCPORTCATmega328P 내에서 출력 핀의 값을 제어하는 ​​레지스터입니다 . 입력 값을 사용할 수 PINC있는 레지스터 입니다. 기본적으로 이와 같은 것은 또는 기능 을 사용할 때 내부적으로 발생하는 것 입니다. 그러나 arduino "핀 번호"를 실제 하드웨어 핀 번호로 변환하는 검색 작업이 있으며, 이는 50 클럭 사이클의 영역에서 사용됩니다. 당신이 아마 짐작할 수 있듯이, 빨리 가려고한다면, 1을 요구 해야하는 작업에서 50 클럭 사이클을 낭비하는 것은 약간 어리 석습니다.PORTCdigitalWritedigitalRead

위의 함수는 아마도 8 비트를 전송하기 위해 100-200 클럭 사이클의 영역에서 어딘가를 필요로합니다. 이를 위해서는 24 개의 핀 쓰기와 8 개의 읽기가 필요합니다. 이것은 digital{stuff}함수 를 사용하는 것보다 몇 배나 빠릅니다 .


이 코드는 ATmega328P보다 많은 타이머를 포함하고 있기 때문에 Atmega32u4 (Leonardo에서 사용)에서도 작동합니다.
jfpoilpret

1
@Ricardo-소형 MCU 내장 코드의 90 % 이상이 직접 레지스터 조작을 사용합니다. 간접 유틸리티 기능을 사용하는 것은 IO / 주변 장치를 조작하는 일반적인 모드가 아닙니다. 하드웨어 제어 (예 : Atmel ASF)를 추상화하기위한 툴킷이 있지만 일반적으로 런타임 오버 헤드를 줄이기 위해 가능한 한 많이 컴파일하도록 작성되었으며 데이터 시트를 읽음으로써 주변 장치를 거의 이해해야합니다.
코너 울프

1
기본적으로 arduino는 실제 문서를 참조하거나 하드웨어가 수행 하는 방식을 실제로 귀찮게하지 않고 "X를 수행하는 함수가 있습니다"라고 말하면 매우 정상적이지 않습니다. 나는 그것이 입문 도구로서의 가치라는 것을 알고 있지만, 빠른 프로토 타이핑을 제외하고는 실제 전문가 환경에서는 결코 이루어지지 않았습니다.
코너 울프

1
분명히, 임베디드 MCU 펌웨어에서 아두 이노 코드를 특이하게 만드는 것은 아두 이노 코드에만 국한된 것이 아니라 전체 접근 방식의 기능입니다. 기본적으로 실제 MCU를 제대로 이해 한 후에는 하드웨어 레지스터를 직접 사용하는 등의 작업을 수행하는 데 추가 시간이 거의 또는 전혀 걸리지 않습니다. 따라서 실제 MCU 개발을 배우고 싶다면 MCU가 실제로 하고있는 일을 이해 하고 누출하는 경향이있는 다른 사람의 추상화 에 의존하는 것이 훨씬 낫습니다 .
코너 울프

1
나는 여기서 약간 냉소적이지만, arduino 커뮤니티에서 볼 수있는 많은 동작은 안티 패턴을 프로그래밍하는 것입니다. 필자는 "복사-붙여 넣기"프로그래밍, 라이브러리를 블랙 박스로 취급하는 것, 커뮤니티에서 일반적으로 열악한 디자인 관행을 많이 봅니다. 물론, 나는 EE.stackexchange에서 상당히 활동적이므로 중재자 도구가 있기 때문에 다소 기울어 진 견해를 가질 수 있으며 많은 닫힌 질문을 볼 수 있습니다. 아두 이노 질문에서 "C & P가 무엇을 고쳐야하는지 알려줘"라는 편견이 아니라 "왜 이것이 작동하지 않는가"에 대한 편견이 있습니다.
코너 울프

3

TCCR1A 타이머 / 카운터 1 제어 레지스터 A

TCCR1B 타이머 / 카운터 1 제어 레지스터 B

TCNT1 타이머 / 카운터 1의 카운터 값입니다.

CS12 타이머 / 카운터에 대한 세 번째 클럭 선택 비트입니다 1

TIMSK1 타이머 / 카운터 1의 인터럽트 마스크 레지스터

TOIE1 타이머 / 카운터 1 오버 플로우 인터럽트 활성화

따라서이 코드는 타이머 / 카운터 1을 62.5kHz로 활성화하고 값을 34286으로 설정합니다. 그런 다음 오버플로 인터럽트를 활성화하여 65535에 도달하면 인터럽트 기능을 트리거합니다. ISR(timer0_overflow_vect)


1

CS12는 TCCR1B 레지스터의 비트 2를 나타내므로 값이 2입니다.

(1 << CS12)는 값 1 (0b00000001)을 가져 와서 왼쪽으로 2 번 이동하여 (0b00000100)을 얻습니다. 작업 순서에 따라 ()의 작업이 먼저 발생하므로 "| ="가 평가되기 전에 수행됩니다.

(1 << CS10)은 값 1 (0b00000001)을 가져와 0 번 왼쪽으로 이동하여 (0b00000001)을 얻습니다. 작업 순서에 따라 ()의 작업이 먼저 발생하므로 "| ="가 평가되기 전에 수행됩니다.

이제 TCCR1B | = 0b00000101을 얻습니다. TCCR1B = TCCR1B | 0b00000101.

"|"이후 "OR", TCCR1B에서 CS12 이외의 모든 비트는 영향을받지 않습니다.

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