이제 슬 무선 통신을 해보도록 하겠습니다.

이번장에서 배울것.
 - message_t 메세지 버퍼
 - 무선을 통해 메세지 버퍼 송신
 - 무선을 통해 메세지 버퍼 수신

Introduction

TinyOS는 기본적인 통신을 지원 하기 위한 추상 인터페이스와 컴포넌트를 제공 합니다. 이 인터페이스들와 컴포넌트는 message_t라는 추상 메세지 버퍼를 사용 합니다.(message_t는 일반 C와 비슷한 nesC 구조체로 되어 있습니다.) message_t 구조체는 직접 접근은 되지 않으며 제공되는 acessor 나 mutator function을 사용 하여 접근 가능 합니다.

Basic Communications Interfaces

message_t를 사용하는 기본적인 인터페이스는 다음과 같습니다.(tos/interface 폴더에 위치 하고 있습니다.)

* Packet - message_t 구조체를 접근 하기위한 인터페이스입니다.,패킷의 길이, 패킷의 데이타 포인터등을 제공 합니다.
* Send - 주소와 관계없는 메세지 송신에 사용 하는 인터페이스 입니다.
* Receive - 패킷 송신에 관련된 인터페이스 입니다. receive이벤트를 가지고 있으니 꼭 구현 해주어야 합니다. 수신패킷에 접근 할 수 있는 기능도 제공 합니다.
* PacketAcknowledgements - 패킷에 기반하여 ACK 기능을 제공 합니다.
* RadioTimeStamping - 패킷 송수신에 대한 시간 정보(?)를 제공 합니다.

Active Message Interface
TinyOS의 인터넷의 패킷 처럼 무선 전송시 다중 접근 할 수 있는 AM layer를 제공 합니다.(이해가 좀 어렵습니다. 인터넷의 그것 처럼 IP,UDP 이런식으로 구조화 되어 있어 다중 레이어로 되어 있다 생각 하면 될것 같습니다.) 위와 달리 AM은 목적지 주소를 가지고 있어 유니캐스트를 지원 합니다. 그리고 AM에 대한 인터페이스 역시 tos/interface에 있습니다.

* AMPacket - 위 Packet과 비슷한 기능입니다, 추상 구조체인 message_t를 AM 으로 제공 합니다. 목적지를 설정하거나 체크 하는등의 기능을 제공 합니다.
* AMSend - Send와 비슷하나, 목적지 주소(AM Address를 가지고 한개의 노드를 향해 메세지를 송신 합니다.

AM address는 컴파일시 결정되나, ActiveMessageAddressC에서 바꿀 수 있습니다.

Components
TinyOS 2.0에서 무선 통신을 위해 기본적으로 제공 하는 컴포넌트들 입니다. tos/system 에 위치 하고 있습니다.

* AMReceiverC - Receive,Packet,AMPacket을 제공
* AMSenderC - AMSend,Packet,AMPacket,PacketAcknowlegements,Acks를 제공
* AMSnooperC - Receive,Packet,AMPacket을 제공
* AMSnooperReceiverC - Receive,Packet,AMPacket을 제공
* ActiveMessageAddresC- Acitve Adress 체크 및 설정에 사용 합니다.주소 재설정을 사용시에는 확실히 알고 하셔야 합니다.

Naming Wrappers
TinyOS는 여러 플랫폼 지원을 위해 송수신 관련 ActiveMessageC Warpper로 하위 추상 클래스를 가지고 있습니다. ActiveMessageC는 위 대부분의 인터페이스를 가지고 있습니다. 각 플랫폼 별 ActiveMessageC는 다음과 같습니다.

* eyesIFX - Tda5250ActiveMessageC
* telosa,telosb,micaz,intelmote - CC2420ActiveMessageC
* mica - CC1000ActiveMessageC
(* ActiveMessageC를 통해 모듈을 추상화 했다고 보면 됩니다.)

TinyOS 2.0 Message Buffer
TinyOS 2.0에서는 기존 메세지 TOS_Msg를 대체하는 message_t를 가지고 있습니다. tos/types/message.h에 선언 되어 있습니다.

typedef nx_struct message_t {
  nx_uint8_t header[sizeof(message_header_t)];
  nx_uint8_t data[TOSH_DATA_LENGTH];
  nx_uint8_t footer[sizeof(message_header_t)];
  nx_uint8_t metadata[sizeof(message_metadata_t)];
} message_t;

위 구조중 header,footer,metadata는 수정 불가 입니다. 이유는 data의 포인터 위치 고정 입니다.


, .

내용이 많은건 아닌데.. 블로그 특징상 길게 쓰면 이상해 보여요 ㅜ.ㅜ; 절대 내용 많은거 아님

Internal Function

이부분은 아주 간단한 내용입니다. 일반 C 언어 처럼 함수를 선언해서 쓸수 있다는 내용 입니다. 함수의 형식은 일반 C를 따르며, command, event등을 쓰지 않으며, 다른 컴포넌트는 내부 함수는 호출 할 수 없습니다.

예는 다음과 같습니다.

module BlinkC {
  uses interface Timer<TMilli> as Timer0;
  uses interface Timer<TMilli> as Timer1;
  uses interface Timer<TMilli> as Timer2;
  uses interface Leds;
  uses interface Boot;
}
implementation
{

  void startTimers() {
    call Timer0.startPeriodic( 250 );
    call Timer1.startPeriodic( 500 );
    call Timer2.startPeriodic( 1000 );
  }

  event void Boot.booted()
  {
    startTimers();
  }

  event void Timer0.fired()
  {
    call Leds.led0Toggle();
  }
 
  event void Timer1.fired()
  {
    call Leds.led1Toggle();
  }
 
  event void Timer2.fired()
  {
    call Leds.led2Toggle();
  }
}

보시다 시피 startTimers() 란 내부함수가 있으며, 같은 컴포넌트 내에서는 제한 없이 사용 할 수 있습니다.

물론 함수를 사용 하기전에 정의는 되어 있어야겠죠.

Split-Phase Operations

command, evnet, 외 중요한 개념입니다. NesC는 와이어 처리를 컴파일시 하게 되어 있어 콜백 즉 event 시스템은 TinyOS에서도 매우 효율적으로 동작 할 수 있습니다.

Blocking 실행을 해야 하기 때문에 TinyOS에서 컴포넌트를 넘어가며 최적화 하는 능력은 매우 중요합니다. Blocking 시스템(절차적 언어)에서는 프로그램이 매우 긴 수행시간의 함수를 실행시 함수가 실행이 종료될때 까지 시스템이 멈처진 상태에 있게 됩니다. 하지만 Split-Phase 시스템에서는 함수는 최대한 빠르게 리턴되며 Operaion이 종료된 후에는 콜백을 사용 하여 "완료" 신호를 날리도록 되어 있습니다.

Blocking Split-Phase
if (send() == SUCCESS) {
  sendCount++;
}
// start phase
send(); 

//completion phase
void sendDone(error_t err) {
  if (err == SUCCESS) {
    sendCount++;
  }
}


Split-Phase의 이점은 스택메모리를 실행 될 동안 점유 하지 않으며, 함수의 실행 종료를 기다리지 않으므로 시스템의 정체를 줄이며, 스택의 활용도를 늘리게 됩니다.

Split-Phase 시스템은 TinyOS가 여러개의 작업을 동시에 수행 할 수 있게 하며 메모리를 절약 시킵니다.

Timer.startOneShot 은 Split-Phase의 좋은 예입니다.

사용자 삽입 이미지

위와 같이 Timer를 절차적으로 사용 하게 되면 Sleep()을 사용하게 되며, 그동안 시스템은 멈추게 됩니다 하지만 Split-Phase에서는 startOneShot()함수 호출후 다른 작업을 수행 할 수 있으며, 타이머는 정해진 시간에 시그널을 날리게 되어 있습니다.

, .
중요한 개념인 Task를 구현 합니다. 1.X에서는 상당히 애매한 상황이 벌여 졌지만 앞서 말한 TASK가 각 각 슬롯을 가짐으로 확실해 졌습니다.

TASK
이때까지 본 소스는 동시성과는 거리가 먼소스들이었습니다. 동시 실행 코드가 실행 되고 있을 동안에는 선점이 되지 않습니다. 이 메카니즘은 TinyOS 스케줄러의 자원 소모를 줄였으나 동시 실행 코드는 매우 작은 크기여야 합니다. 즉 동시 실행시 선점 되지 않기 때문에 즉각 반응 시스템엔 역효과를 내는 것입니다. 예로는 긴 코드가 실행 되어 패킷에 반응 하는 속도가 늦어 질 수 있는 것입니다.

이때까지 코드는 아주 짧은 실행 코드만을 지녀 별문제 없이 작동 했습니다. 하지만 매우 긴 시간을 요구하는 실행 코드의 경우 선점 되지 않는 문제는 많은 문제를 야기 할 수 있습니다. 그래도 2.0의 경우 매우 긴 시간을 요구하는 코드의 경우 여러부분으로 나눌것을 권장 합니다.

Task - Task는 어플리케이션에 백그라운드 프로세싱을 가능하게 합니다. 이정도로 생각 하시면 됩니다. OS에서 이야기하는 bottom halves와 deferred procedure call과 비슷 합니다.(*하드웨어 인터럽트가 먼저라 생각 하시면 됩니다.)

이제 Task를 실습 하도록 하겠습니다. cp 명령어를 사용 하여 새로운 BlinkC 백업 본을 만듭니다.(BlinkTask)

cp -R Blink BlinkTask

Timer0.fired() 이벤트를 수정하여 작업을 좀 넣도록 하겠습니다.

event void Timer0.fired()
  {
    uint32_t i;
    dbg("BlinkC", "Timer 0 fired @ %s.\n", sim_time_string());
    for(i =0; i<400001;i++){
      call Leds.led0Toggle();
    }
  }

대충 이작업은 MicaZ , Telosb 계열 모트가 20번의 패킷을 보낼 수 있는 시간의 작업을 수행 합니다. 퓨징 하시면 지연으로 인해 이상하게 동작 하는 모트를 보실 수 있습니다.

동작의 결과를 설명 드리면 Timer0에서 발생하는 연산이 Timer이벤트들의 실행을 방해 하며 결국은 Timer0의 연산이 끝나는 시점에서 동시에 발생 하게 되는것이다. 그래서 두개가 계속 나란히 켜지면 한개만 켜지는 경우는 없게 된다.

이문제를 해결 하기 위해서는 백그라운드로 저 작업을 실행 하면된다.

Task의 정의는 다음과 같다.

task void taskname(){}

Task는 인자를 가질수도 리턴값을 가질수도 없다. 그리고 Task를 실행 할때는 post를 사용한다.

post taskname();

여기서 중요한거 컴포넌트의 command,task,event는 task를 포스팅 할 수 있다. 그리고 이벤트 시그널을 위해서는 Task를 사용 하기 바란다. command에서 event를 사용하는건 루프를 발생 시킬 소지가 매우크다. 나중에 설명함(예로 인터페이스 I 제공 하는 컴포넌트 A 사용하는 B가 있을 경우 B의 이벤트에 핸들러에서 A의 command를 실행 하며 그 command에서 시그널을 날린다면 바로 루프가 발생하는 것이다.)

task를 다음과 같이 수정하여 만든다.

 task void computeTask(){
    uint32_t i;
    for(i=0;i<400001;i++){}
  }
  event void Timer0.fired()
  {
      call Leds.led0Toggle();
      post computeTask();
  }

그럼 TelosB는 아직 이상하지만 MicaZ는 작동 잘 할 것이다.

post는 Task를 큐에 FIFO 방식으로 넣는 역활을 한다. Task는 긴 수행시간을 가져서는 안된다. Task는 서로는 선점 하지 못하지만, 하드웨어 인터럽트는 Task를 선점 할수 있다. 긴시간의 수행시간을 가지는 Task의 경우 나누도록 한다. post 명령은 error_t 를 반환한다. (task가 반환 하는것이 아닌 post명령이다.) 이경우는 Task가 벌써 성공적으로 스팅 된 경우 이다.(물론 아직 실행은 안되었다.)

TelosB의 경우 아직 Task 가 많은 시간을 소모 하기 때문에 정상 작동 하지 않는 것이다. 다음과 같이 수정한다.

외적으로 static 변수를 사용하는건 생략 한다. 대략 컴포넌트안의 변수는 모두 Private이기때문에 static를 사용하는건 의미가 없다.
, .

이제 부터 공부의 속력을 내기 위해 요약 해석 할 예정입니다.

속도가 넘 안나네요.

Interfaces,Commands,and Events

Lesson1의 경험으로 인터페이스를 사용하는 컴포넌트는 인터페이스의 command를 실행 할 수 있으며, 역으로 이벤트는 받아서 처리 해야 한다는걸 알 수 있게 되었습니다. 그럼 BlinkC가 사용 하는 인터페이스를 분석 하도록 하겠습니다. BlinkC가 사용 하는 인터페이스는 Timer,Leds,Boot가 있습니다.

tos/interfaces/Boot.nc:
interface Boot {
  event void booted();
}

tos/interfaces/Leds.nc:
interface Leds {

  /**
   * Turn LED n on, off, or toggle its present state.
   */
  async command void led0On();
  async command void led0Off();
  async command void led0Toggle();

  async command void led1On();
  async command void led1Off();
  async command void led1Toggle();

  async command void led2On();
  async command void led2Off();
  async command void led2Toggle();

  /**
   * Get/Set the current LED settings as a bitmask. Each bit corresponds to
   * whether an LED is on; bit 0 is LED 0, bit 1 is LED 1, etc.
   */
  async command uint8_t get();
  async command void set(uint8_t val);
 
}

tos/interfaces/Timer.nc:
interface Timer
{
  // basic interface
  command void startPeriodic( uint32_t dt );
  command void startOneShot( uint32_t dt );
  command void stop();
  event void fired();

  // extended interface omitted (all commands)
}

Looking over the interfaces for Boot, Leds, and Timer, we can see that since BlinkC uses those interfaces it must implement handlers for the Boot.booted() event, and the Timer.fired() event. The Leds interface signature does not include any events, so BlinkC need not implement any in order to call the Leds commands. Here, again, is BlinkC's implementation of Boot.booted():


apps/Blink/BlinkC.nc:
  event void Boot.booted()
  {
    call Timer0.startPeriodic( 250 );
    call Timer1.startPeriodic( 500 );
    call Timer2.startPeriodic( 1000 );
  }

그럼 여기서 BlinkC가 꼭 구현해야 하는 이벤트는 어떤것이 있을까요? 위에서 보면 알 수 있으시겠지만.
여기서 구현해야 하는 Event는 Boot.booted() Timer.fired() 이벤트가 있습니다.그럼 BlinkC가 구현 하고 있는 Boot.booted() 이벤트를 보도록 하죠.

apps/Blink/BlinkC.nc:
  event void Boot.booted()
  {
    call Timer0.startPeriodic( 250 );
    call Timer1.startPeriodic( 500 );
    call Timer2.startPeriodic( 1000 );
  }

BlinkC는 세개의 타이머 객체를 사용하고 있으며, booted() 이벤트는 세개를 시작 시키는 역활을 하고 있습니다. startPeriodic 은 타이머를 시작 시키며, ()안의 숫자는 MillicSecond를 의미합니다.(<TMilli> 객체 생성시 결정) ()안의 숫자와 객체 생성시 정해진 단위로 타이머는 발생됩니다.

call이란 키워드는 command를 실행 할때 쓰며, 역으로 이벤트 전달시에는 signal을 사용합니다. 일단 BlinkC에는 없으니 넘어가도록 하겠습니다.

 event void Timer0.fired()
  {
    dbg("BlinkC", "Timer 0 fired @ %s.\n", sim_time_string());
    call Leds.led0Toggle();
  }
 
  event void Timer1.fired()
  {
    dbg("BlinkC", "Timer 1 fired @ %s \n", sim_time_string());
    call Leds.led1Toggle();
  }
 
  event void Timer2.fired()
  {
    dbg("BlinkC", "Timer 2 fired @ %s.\n", sim_time_string());
    call Leds.led2Toggle();
  }

앞에서 언급 한것 처럼 이벤트의 처리는 사용자에서 하도록 되어있으므로 위와 같이 BlinkC에서 Timer.fired() 이벤트를 처리 해야 합니다. 그리고 함수의 선언은 interface.function 식으로 구분을 확실히 해야 합니다.

, .

그럼  Lesson 2 시~~작

Lesson 2. Modules and the TinyOS execution Model
TinyOS에 실행에 있어 중요한것은 Module,command,task 입니다. 기억 하세요.

*이제 편하게 애기하듯이 할 예정입니다.

TinyOS를 컴파일 하면 한개의 바이너리 파일이 나오며, 모트는 한번에 한개의 TinyOS 이미지를 실행 실킬 수 있습니다. 이미지 생성시에는 여러개의 컴포넌트를 필요로 합니다.  대부분의 모트들이 하드웨어적으로 메모리 보호를 할 수 없으며 더구나 하나의 주소맵을 모든 컴포넌트들이 공유 합니다. 그래서 컴포넌트들은 개인적이 변수를 가지게 해야 하며 포인터를 전달 하는건 피해야 합니다. 메모리충돌을 방지하기위해서는 메모리 공유를 피하시길 바랍니다.

1장에서 보다 시피 컴포넌트는 하나 이상의 제공받는 또는 제공 하는 인터페이스를 가지며 이건 두가지 컴포넌트 타입 모듈과 구성에 똑같이 적용 됩니다. 단지 구성 파일은 지가 구현 하는게 아니고 다른 모듈을 통해서 그기능을 제공 하며, 모듈은 실제적인 기능을 가지고 있습니다.(이런이유는 뭐 LED를 가지고 설명하면,, 모트에 따라 LED를 제어 하는 실제적인 모듈은 다를 것입니다만.. 구성파일을 래퍼로 앞세워 똑같은 컴포넌트 이름,인터페이스로 기능을 제공 할 수 있게 되는 것입니다.)

모듈에 선언되는 변수는 모두 공유 될수 없는 private 변수들이며 다른 컴포넌트는 절대 그 변수 이름으로 접근 할 수 없습니다. 단지 인터페이스를 통해 접근 가능 합니다.(여기서 문제,, 전역 변수는 어떻게 하느냐.. 어렵습니다. ㅡ.ㅡ;죄송 근데 전역 변수 설정이 되면.. 쓰기도 뭐 합니다. 제 경험상 크리티컬 세션 이 잘 작동 안합니다. atomic 이죠.);

apps Blink를 다시 `~~ 분석 하죠.

module BlinkC{
  uses interface Timer<TMilli> as Timer0;
  uses interface Timer<TMilli> as Timer1;
  uses interface Timer<TMilli> as Timer2;
  uses interface Leds;
  uses interface Boot;
}
implementation{
event void Boot.booted()
{
  call Timer0.startPeriodic(250);
  call Timer1.startPeriodic(500);
  call Timer2.startPeriodic(1000);
}

event void Timer0.fired(){
  call Leds.led0Toggle();

}

-----------------------------------중    략-----------------------------------------

보시다 시피 BlinkC는 어떻한 변수도 선언 안합니다.. 여기서 잠깐 실습 Blink를 BlinkSingle로 복사하세요.

cp -R Blink BlinkSingle 명령어 사용( tinyos-2.x/app 위치에서)

그리고 다음 과 같이 수정 합니다.

event void Timer1.fired()
  {
    // call Leds.led1Toggle();
  }
 
  event void Timer2.fired()
  {
    // call Leds.led2Toggle();
  }
일단 타이머는 한개만 사용 할 예정 입니다.

그리고 변수 하나를 선언하고 사용합니다. 선언하고 쓰는 요령은 C와 같습니다.

uint8_t counter=0;
  event void Boot.booted()
  {
    call Timer0.startPeriodic( 250 );
    //call Timer1.startPeriodic( 500 );
    //call Timer2.startPeriodic( 1000 );
  }

(아!! 전 걍 저렇게 첨부터 타이머를 막았습니다.^^;;)

그리고 다른건 C와 비슷하게 쓰더라도 자료형은 다음과 같은 규칙에 의거해서 사용할것을 추천 합니다.물론 걍 C자료형 써도 무방하지만 메모리가 부족한 관계로 필요한 만큼 쓰는게 좋은겁니다.물론 float,bool다 지원 합니다.

사용자 삽입 이미지
이제 다시 BlinkC로 와서, 우리가 선언한 counter는 모트가 켜지면서 0으로 초기화 됩니다.
그리고 Timer가 터질때 counter를 증가 시켜 counter에 따라 모트를 제어 합니다.

event void Timer0.fired()
  {
    counter++;
    if (counter & 0x1) {
      call Leds.led0On();
    }
    else {
      call Leds.led0Off();
    }
    if (counter & 0x2) {
      call Leds.led2On();
    }
    else {
      call Leds.led2Off();
    }
    if (counter & 0x4) {
      call Leds.led2On();
    }
    else {
      call Leds.led2Off();
    }
  }

이와 같이 수정 합니다. 대충 알고리즘은 걍 카운터 입니다. ^^;

그리고 좀더 쉬운 방법으로 ^^ set command를 사용 하는 겁니다.

event void Timer0.fired()
  {
    counter++;
    call Leds.set(counter);
  }
결과는 똑같은 겁니다. 왜냐.. LedC 컴포넌트 소스 따라가면 위에꺼 똑같이 쳐져 있을겁니다.(1.x에서는 그랬습니다. ㅎㅎ ^^);
 실행 시켜 보시면 똑같이 동작 하게 될겁니다.

이제 Timer는 하나만 사용 하고 있습니다. 타이머도 역시 리소스를 먹으므로 이제 두개타이머는 제거 하도록 합니다.

module BlinkC {
  uses interface Timer<TMilli> as Timer0;
  uses interface Leds;
  users interface Boot;
}
implementation
{
  uint8_t counter = 0;

  event void Boot.booted()
  {
    call Timer0.startPeriodic( 250 );
  }

  event void Timer0.fired()
  {
    counter++;
    call Leds.set(counter);
  }
 
}

다음과 같이 적고 컴파일 하면 ?? 당연 에러 납니다. 구성파일에서 와이어링 해놓은 게 있기때문에 에러 나게 됩니다. BlinkAppC에서 타이머와 와이어링을 제거 하고 다시 컴파일 하면 에러 안나게 될것입니다.


configuration BlinkAppC
{
}
implementation
{
  components MainC, BlinkC, LedsC;
  components new TimerMilliC() as Timer0;
 

  BlinkC -> MainC.Boot;

  BlinkC.Timer0 -> Timer0;
  BlinkC.Leds -> LedsC;
}
이렇게 수정 하시면 됩니다.
그라면 다음에 계속..

, .