Arduino에서 함수 포인터 할당은 원 자성입니까?


11

다음 스 니펫은 TimerOne 라이브러리 소스 코드 에서 가져온 것입니다 .

// TimerOne.h:
void (*isrCallback)();

// TimerOne.cpp:
ISR(TIMER1_OVF_vect) // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
  Timer1.isrCallback();
}

// TimerOne.cpp:
void TimerOne::attachInterrupt(void (*isr)(), long microseconds)
{
  if(microseconds > 0) setPeriod(microseconds);
  isrCallback = isr; // register the user's callback with the real ISR
  TIMSK1 = _BV(TOIE1); // sets the timer overflow interrupt enable bit
  resume();                                                                                            
}

질문 : 타이머가 이미 실행 중이고 주 프로그램이 호출 하면 함수 포인터 할당 중에attachInterrupt() 타이머 인터럽트가 발생할 수 있습니까? 그런 다음 운이 좋은 타이밍에서 함수 포인터는 이전 주소와 새 주소의 일부로 구성되어 ISR이 가짜 위치로 이동합니까?isrCallback = isr;Timer1.isrCallback();

함수 포인터가 확실히 1 바이트보다 넓고> 1 바이트 데이터에 액세스하는 것이 원자 적이 지 않기 때문에 이것이 사실이라고 생각합니다. 가능한 해결 방법은 다음과 같습니다.

  • 를 호출 detachInterrupt()하기 전에 타이머가 실행되고 있지 않은지 확인하려면 항상 호출 하십시오 ( attachInterrupt()예 : Timer1 문서를 명확하게 함).
  • 또는 Timer1을 수정하여 직전에 타이머 오버플로 인터럽트를 비활성화합니다. isrCallback = isr;

이것이 의미가 있습니까, 아니면 Timer1내가 놓친 소스 또는 함수 포인터 할당에 뭔가가 있습니까?

답변:


7

attachInterrupt () 및 detachInterrupt ()의 코드를 살펴보십시오 /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/cores/arduino/WInterrupts.c(어쨌든 Mac의 위치입니다. 다른 OS의 Arduino 파일 구조는 아마도 경로의 하위 레벨에서 비슷할 것입니다).

attachInterrupt ()는 문제의 인터럽트가 예방 조치를 취하지 않고 함수 포인터를 작성하기 때문에 해당 인터럽트가 아직 활성화되지 않은 것으로 가정합니다. detachInterrupts ()는 벡터에 NULL 포인터를 쓰기 전에 대상 인터럽트를 비활성화합니다. 그래서 나는 적어도 detachInterrupt()/ attachInterrupt()쌍을 사용합니다

중요한 섹션에서 그러한 코드를 직접 실행하고 싶습니다. 불행히도 시간이 걸리는 인터럽트를 놓치지 않을 수는 없지만 첫 번째 방법 (분리, 연결)이 작동하는 것처럼 보입니다. MCU의 데이터 시트에 더 많은 정보가있을 수 있습니다. 그러나 나는이 시점에서 글로벌 cli()/ sei()그것도 놓치지 않을 것이라고 확신하지 않습니다. ATMega2560 데이터 시트, 섹션 6.8에 "인터럽트를 활성화하기 위해 SEI 명령어를 사용할 때 SEI 다음의 명령어는 보류중인 인터럽트 전에 실행됩니다"(인터럽트 동안 인터럽트를 버퍼링 할 수 있음) 꺼져있다.


실제로는 소스로 다이빙하는 것이 유용합니다.
Joonas Pulakka 2016 년

0

요점이있는 것 같습니다. 논리적으로해야 할 일은 인터럽트가 처음에 비활성화 된 경우 다시 활성화하지 않는 방식으로 인터럽트를 비활성화하는 것입니다. 예를 들면 다음과 같습니다.

  uint8_t oldSREG = SREG;  // remember if interrupts are on
  cli();                   // make the next line interruptible
  isrCallback = isr;       // register the user's callback with the real ISR
  SREG = oldSREG;          // turn interrupts back on, if they were on before

"SEI 명령어를 사용하여 인터럽트를 활성화하면 SEI 다음의 명령어는 보류중인 인터럽트보다 먼저 실행됩니다 (이 예에 표시된대로)."

이것의 목적은 다음과 같은 코드를 작성하는 것입니다.

  sei ();         // enable interrupts
  sleep_cpu ();   // sleep

이 규정이 없으면 두 회선 사이에 인터럽트가 발생하여 잠이 들기 전에 깨어 나게 된 인터럽트가 발생했기 때문에 무기한 수면 상태가 될 수 있습니다 . 이전에 활성화되지 않은 경우 인터럽트가 활성화 된 후 다음 명령어는 항상 실행되므로 프로세서에서이를 방지합니다.


더 효율적이지 TIMSK1=0; TIFR1=_BV(TOV1); isrCallback=isr; TIMSK1=_BV(TOIE1);않습니까? 하나의 CPU 레지스터를 절약하고 인터럽트 대기 시간을 제공하지 않습니다.
Edgar Bonet

ICIE1, OCIE1B, OCIE1A와 같은 다른 비트는 어떻습니까? 대기 시간에 대해서는 이해하지만 인터럽트는 사용할 수없는 몇 가지 클록주기에 대처할 수 있어야합니다. 경쟁 조건이있을 수 있습니다. 인터럽트가 이미 트리거되었을 수 있습니다 (예 : CPU의 플래그가 설정 됨). TIMSK1을 끄면 처리가 중지되지 않을 수 있습니다. 발생하지 않도록 TOV1 (TIFR1에 1을 쓰면)을 재설정해야 할 수도 있습니다. 불확실성은 전 세계적으로 인터럽트를 끄는 것이 더 안전한 과정이라고 생각하게 만듭니다.
Nick Gammon
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.