개발자/Arduino

nano 33 IoT에서 타이머 인터럽트 구현 참고 1

지구빵집 2020. 10. 25. 19:53
반응형

 

 

가장 쉬운 예제를 찾았는데, 계속 Nano 33 IoT 타이머 인터럽트를 주제로 찾으니 헛고생만 한거다. 처음부터 메인 mcu가 SAMD21 이니까 그 주제로 찾았어야 하는 데 이궁. 어려운 문제도 아닌데 진짜 멍청하게 움직였다. 

 

이 예제에서는 TC5_Handler를 사용했다.

 

아래 코드는 samplerate를 사용자가 원하는 시간으로 설정이 가능하며, 소스코드에서는 1000m second=1초로 설정이 되어 있다. Nano 33 IoT보드에 그대로 컴파일하고 업로드하면 보드위에 있는 LED가 1초 간격으로 ON, OFF 하는 것을 확인할 수 있다. 코드 출처는 따라가시오. SAMD21 Arduino Timer Example 여기입니다. 파일의 이름은 확인해야 하는 경우에 필요하므로 tiemr-exam-demo3.ino로 한다.

 

9번째 라인의 sampleRate = 1000 은 millisecond 단위로 인터럽크가 발생되는 간격을 의미한다. void TC5_Handler (void) 함수가 실제 인터럽트가 sampleRate 간격으로 발생되면 실행하는 함수이다. 인터럽트 루틴에서는 최소한의 일만 하도록 코드를 구성해야 한다. 그래서 여기서는 1초를 세는 타이머 onesecondinterval++; 만 증가시킨다. LED를 토글로 on,off 시키는 기능은 제거한다. void tcConfigure(int sampleRate) 함수는 같은 주기로 이벤트를 출력하기 위해 값을 설정하는 함수인데 잘 모르면 그대로 둔다. 

 

/*
 * This sketch illustrates how to set a timer on an SAMD21 based board in Arduino (Feather M0, Arduino Zero should work)
 * It should generate a 1Hz square wave as it is (thanks richdrich for the suggestion)
 * Some more info about Timer Counter works can be found in this article: 
 * http://www.lucadavidian.com/2017/08/08/arduino-m0-pro-il-sistema-di-clock/
 * and in the datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D21_DA1_Family_DataSheet_DS40001882F.pdf
*/

uint32_t sampleRate = 1000; //sample rate in milliseconds, determines how often TC5_Handler is called

#define LED_PIN 13 //just for an example
bool state = 0; //just for an example

uint32_t onesecondinterval=0;

void setup() {

  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  
  pinMode(LED_PIN,OUTPUT); //this configures the LED pin, you can remove this it's just example code
  tcConfigure(sampleRate); //configure the timer to run at <sampleRate>Hertz
  tcStartCounter(); //starts the timer
}

void loop() {
  //tcDisable(); //This function can be used anywhere if you need to stop/pause the timer
  //tcReset(); //This function should be called everytime you stop the timer

  if(onesecondinterval >= 60)
  {
    //send-data
    onesecondinterval = 0;
  }
}

//this function gets called by the interrupt at <sampleRate>Hertz
void TC5_Handler (void) {
  //YOUR CODE HERE 
  
  onesecondinterval++;
  Serial.println(onesecondinterval);
  
  if(state == true) {
    digitalWrite(LED_PIN,HIGH);
  } else {
    digitalWrite(LED_PIN,LOW);
  }
  state = !state;
  // END OF YOUR CODE
  TC5->COUNT16.INTFLAG.bit.MC0 = 1; //Writing a 1 to INTFLAG.bit.MC0 clears the interrupt so that it will run again
}

/* 
 *  TIMER SPECIFIC FUNCTIONS FOLLOW
 *  you shouldn't change these unless you know what you're doing
 */

//Configures the TC to generate output events at the sample frequency.
//Configures the TC in Frequency Generation mode, with an event output once
//each time the audio sample frequency period expires.
 void tcConfigure(int sampleRate)
{
 // select the generic clock generator used as source to the generic clock multiplexer
 GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5)) ;
 while (GCLK->STATUS.bit.SYNCBUSY);

 tcReset(); //reset TC5

 // Set Timer counter 5 Mode to 16 bits, it will become a 16bit counter ('mode1' in the datasheet)
 TC5->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16;
 // Set TC5 waveform generation mode to 'match frequency'
 TC5->COUNT16.CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ;
 //set prescaler
 //the clock normally counts at the GCLK_TC frequency, but we can set it to divide that frequency to slow it down
 //you can use different prescaler divisons here like TC_CTRLA_PRESCALER_DIV1 to get a different range
 TC5->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1024 | TC_CTRLA_ENABLE; //it will divide GCLK_TC frequency by 1024
 //set the compare-capture register. 
 //The counter will count up to this value (it's a 16bit counter so we use uint16_t)
 //this is how we fine-tune the frequency, make it count to a lower or higher value
 //system clock should be 1MHz (8MHz/8) at Reset by default
 TC5->COUNT16.CC[0].reg = (uint16_t) (SystemCoreClock / sampleRate);
 while (tcIsSyncing());
 
 // Configure interrupt request
 NVIC_DisableIRQ(TC5_IRQn);
 NVIC_ClearPendingIRQ(TC5_IRQn);
 NVIC_SetPriority(TC5_IRQn, 0);
 NVIC_EnableIRQ(TC5_IRQn);

 // Enable the TC5 interrupt request
 TC5->COUNT16.INTENSET.bit.MC0 = 1;
 while (tcIsSyncing()); //wait until TC5 is done syncing 
} 

//Function that is used to check if TC5 is done syncing
//returns true when it is done syncing
bool tcIsSyncing()
{
  return TC5->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY;
}

//This function enables TC5 and waits for it to be ready
void tcStartCounter()
{
  TC5->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE; //set the CTRLA register
  while (tcIsSyncing()); //wait until snyc'd
}

//Reset TC5 
void tcReset()
{
  TC5->COUNT16.CTRLA.reg = TC_CTRLA_SWRST;
  while (tcIsSyncing());
  while (TC5->COUNT16.CTRLA.bit.SWRST);
}

//disable TC5
void tcDisable()
{
  TC5->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;
  while (tcIsSyncing());
}

 

아래는 잘 돌아가는 코드 두 개를 참고해서

 

예제1. 약간 쉬운 것. 파일의 이름은 확인해야 하는 경우에 필요하므로 tiemr-exam-demo2.ino로 한다. 아래 예제 코드는 여기에서 찾았고, 코드 설명은 아래와 같이 되어 있다.

 

안녕하세요. 이것이 필요한지 모르겠지만 M0 Pro에 대해 타이머의 클록 주파수를 설정하는 방법을 보여주는 예제 스케치가 있습니다. (TC4는 M0 Pro의 micros () 루틴에 대해 내부적으로 사용되기 때문에 TC3 및 TC5 만 사용할 수 있습니다. Zero에 대해 잘 모르겠습니다). 상위 카운트를 설정하고 이에 도달했을 때 인터럽트를 생성하려면 TCC를 사용하는 것이 더 나을 수 있습니다.

 

이는 PWM 제어 및 H- 브리지 모터 드라이버를 위해이 모드에서 사용하도록 설계 되었기 때문입니다. 제 부분에서는 24 비트 실시간 시계로 사용하기 위해 TCC 카운터 값을 읽으려고했습니다. 이렇게하면 16 비트 타이머 카운터에 비해 인터럽트 서비스로 인한 지연 시간이 줄어 듭니다. 안타깝게도 몇 달간 저녁을 테스트하고 매뉴얼을 읽었음에도 불구하고이 작업을 수행 할 수 없었습니다. 이벤트 시스템을 통해서만 작동하는 것 같습니다. 성공을 기원합니다! 

 

 

/////////////////////////////////////////////////////////////////////////
//
//
//   Real time clock on TC3
//
//
//
/////////////////////////////////////////////////////////////////////////

volatile uint32_t realTcount = 0x0 ;   // Counter for superticks (overflow interrupts)


void setup() {
	Serial.begin(9600);
	while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
	}
    
	setTC3clock();
}


/*
This is test code that counts to 10 s then resets and waits 3s to test
reset, start and stop functions on TC3.
*/
void loop() {
 
  delay(250);
 
    Serial.print(realTime(), 7);
    Serial.println();
    double val = realTime();
   
    if( int(val) >= 10)
    {
  resetStartTC3();  // reset so never gets above 10 s
      stopTC3();
    delay(3000); //wait 3 sec
    startTC3();
    }
   
}





void TC3_Handler()  // Interrupt on overflow
{
  TcCount16* TC = (TcCount16*) TC3; // get timer struct
    realTcount++;                    // Increment the supertick register
    TC->INTFLAG.bit.OVF = 1;    // writing a one clears the ovf flag
//  }
}

/*
Get the real time in seconds.
*/
double realTime()
{   
double  realTime =  (realTcount * 1.2288E-2) + (REG_TC3_COUNT16_COUNT * 1.875E-7) ;;
return realTime;
}


/*
  Setup the Generic clock register
*/


void setTC3clock()
{
  GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID( GCM_TCC2_TC3 ));
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
  TcCount16* TC = (TcCount16*) TC3; // get timer struct
  TC->CTRLA.reg &= ~TC_CTRLA_ENABLE;   // Disable TC
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT16;  // Set Timer counter Mode to 16 bits
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_NFRQ; // Set TC as normal Normal Frq
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV8;   // Set perscaler
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  // Interrupts
  TC->INTENSET.reg = 0;              // disable all interrupts
  TC->INTENSET.bit.OVF = 1;          // enable overfollow interrup
  // Enable InterruptVector
  NVIC_EnableIRQ(TC3_IRQn);
  // Enable TC
  TC->CTRLA.reg |= TC_CTRLA_ENABLE;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
}



void resetStartTC3()
{
   TcCount16* TC = (TcCount16*) TC3; // get timer struct
   realTcount = 0x00;                // Zero superclicks
    TC->CTRLBSET.reg |= TC_CTRLBCLR_CMD_RETRIGGER;   // restart
}

void stopTC3()
{
  TcCount16* TC = (TcCount16*) TC3; // get timer struct
    TC->CTRLBSET.reg |= TC_CTRLBSET_CMD_STOP;   // Stop counter
}


void startTC3()
{
  TcCount16* TC = (TcCount16*) TC3; // get timer struct
    TC->CTRLBSET.reg |= TC_CTRLBSET_CMD_RETRIGGER;   //  Start
}

 

 

 

 예제2. 어려운 것. 파일의 이름은 확인해야 하는 경우에 필요하므로 tiemr-example-test.ino로 한다.

 

/*
  
   Testing with Arduino NANO 33 IoT
   @author Markus Bader
   @brief this program shows how to use the TC timer with interrupts on an Arduino Zero board
   @email markus.bader@tuwien.ac.at

   https://forum.arduino.cc/index.php?topic=332275.0
   Post: #8
*/

int pin_ovf_led = 2;  // debug pin for overflow led
int pin_mc0_led = 5;  // debug pin for compare led
unsigned int loop_count = 0;
unsigned int irq_ovf_count = 0;

void setup() {

  Serial.begin(9600);

  pinMode(pin_ovf_led, OUTPUT);   // for debug leds
  digitalWrite(pin_ovf_led, LOW); // for debug leds
  pinMode(pin_mc0_led, OUTPUT);   // for debug leds
  digitalWrite(pin_mc0_led, LOW); // for debug leds

  // Enable clock for TC
  REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC2_TC3) ;
  while ( GCLK->STATUS.bit.SYNCBUSY == 1 ); // wait for sync

  // The type cast must fit with the selected timer mode
  TcCount16* TC = (TcCount16*) TC3; // get timer struct

  TC->CTRLA.reg &= ~TC_CTRLA_ENABLE;   // Disable TC
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT16;  // Set Timer counter Mode to 16 bits
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_NFRQ; // Set TC as normal Normal Frq
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV256;   // Set perscaler (1,2,4,8,64,256,1024)
  //TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1024;   // Set perscaler
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // TC->PER.reg = 0xFF;   // Set counter Top using the PER register but the 16/32 bit timer counts allway to max
  // while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->CC[0].reg = 0xFFF;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // Interrupts
  TC->INTENSET.reg = 0;              // disable all interrupts
  TC->INTENSET.bit.OVF = 1;          // enable overfollow
  TC->INTENSET.bit.MC0 = 1;          // enable compare match to CC0

  // Enable InterruptVector
  NVIC_EnableIRQ(TC3_IRQn);

  // Enable TC
  TC->CTRLA.reg |= TC_CTRLA_ENABLE;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

}

void loop() {
  // dummy
  delay(500);
}

void TC3_Handler()
{
  TcCount16* TC = (TcCount16*) TC3; // get timer struct

  if (TC->INTFLAG.bit.OVF == 1) {  // A overflow caused the interrupt
    digitalWrite(pin_ovf_led, irq_ovf_count % 2); // for debug leds
    digitalWrite(pin_mc0_led, HIGH); // for debug leds
    TC->INTFLAG.bit.OVF = 1;    // writing a one clears the flag ovf flag
    irq_ovf_count++;                 // for debug leds
  }

  Serial.println(irq_ovf_count);

  if (TC->INTFLAG.bit.MC0 == 1) {  // A compare to cc0 caused the interrupt
    digitalWrite(pin_mc0_led, LOW);  // for debug leds
    TC->INTFLAG.bit.MC0 = 1;    // writing a one clears the flag ovf flag
  }

}

 

 

 

Nano 33 IoT 보드의 Pinmap

 

 

 

 

 

 

 

 

반응형