fig.1. structuer of layer
fig.1 에서 볼 수 있듯이 2 개의 layer로 protocol이 만들어져 있다. layer 1에 해당하는 Lower layer는 제조사에 정의하여 직접적인 물리신호 생성, 물리신호 수신에 관여한다.
각각의 service primitive에 대한 설명은 다음과 같다. DATA REQ 는 PDU 전송을 위해 lower layer 에게 요청하는 것이다 이과정을 통해서 ARQ에서 PDU를 lower layer에게 전달한다. DATA CNF 요청된 PDU 전송을 수행하였음을 알리는 과정이고 이 과정에서 ACK를 수신한다. 수신측에서 받게 되는 SP이고 DATA IND 은 lower layer로부터 PDU를 받는 과정을 의미 한다.
fig.2. FSM struct
fig.2.는 FSM의 구조를 시각화한 것이다. FSM의 STATE는 크게 3가지로 구분했는데
else if (arqEvent_checkEventFlag(arqEvent_arqTimeout)) //data TX finished
{
if(retxCnt > ARQ_MAXRETRANSMISSION){
pc.printf("[WARNING]Failed to send data, max retx cat reached!\n");
main_state = MAINSTATE_IDLE;
retxCnt = 0;
}else{
pc.printf("timeout! retransmit\n");
arqLLI_sendData(arqPdu, pduSize, dest_ID);//ack의 time out이 발생해서 retransmission 이 발생함
//Setting ARQ parameter
retxCnt += 1;
main_state = MAINSTATE_TX;
arqEvent_clearEventFlag(arqEvent_arqTimeout);
}
}
위의 코드에서 볼 수 있듯이 retxCnt는 재전송 횟수를 저장하는 변수이고 이는 acqTimeout 이벤트가 발생하면 재전송이 일어나는데 이때 증가한다. 하지만 요구사항은 ARQ_MAXRETRANSMISSION일때 FSM이 deadlock에 빠지지 않고 다시 IDLE상태로 돌아가는 것이였다 이를 위해서 필자는 위의 코드를 작성했다
if(retxCnt > ARQ_MAXRETRANSMISSION)
을 실행해서 재전송 횟수가 10을 초과한다면
pc.printf("[WARNING]Failed to send data, max retx cat reached!\n"); main_state = MAINSTATE_IDLE; retxCnt = 0;
재전송 횟수를 초과 했다는 메세지를 화면에 출력하고 main_state를 다시 IDLE 상태로 되돌려 수신 가능한 상태로 만들고 재전송 횟수를 저장하는 변수인 retCnt를 0으로 초기화해서 이를 다시 되돌려 놓았다.
fig.3. 10번의 재전송 메세지 출력후 IDLE상태로 되돌아가는 과정
fig.3. 에서 볼 수 있듯 Tx에서 ack를 시간안에 수신 받지 못하자 10번의 재전송을 시도하고 이후 ARQ_MAXRETRANSMISSION
을 초과하면 다시 deadlock에 빼지지않고 IDLE상태로 되돌아간다. 위의 fig.4에서 디버깅 메세지가 출력된 것을 볼 수 있는데 이는 필자가 arqEvent_clearEventFlag(*arqEvent_arqTimeout*);을 새로 작성한 timeout이벤트 코드에 마지막에 작성하지 않았기 때문이다. 따라서 저 디버깅 메세지를 없에기 위해서
else if (arqEvent_checkEventFlag(arqEvent_arqTimeout)) //data TX finished
{
if(retxCnt > ARQ_MAXRETRANSMISSION){
pc.printf("[WARNING]Failed to send data, max retx cat reached!\n");
main_state = MAINSTATE_IDLE;
retxCnt = 0;
arqEvent_clearEventFlag(arqEvent_arqTimeout);
}else{
pc.printf("timeout! retransmit\\n");
arqLLI_sendData(arqPdu, pduSize, dest_ID);//ack의 time out이 발생해서 retransmission 이 발생함
//Setting ARQ parameter
retxCnt += 1;
main_state = MAINSTATE_TX;
arqEvent_clearEventFlag(arqEvent_arqTimeout);
}
}
위와 같이 if(retxCnt > ARQ_MAXRETRANSMISSION)
문 내부에세도 clearEvent를 해주어서 디버깅 메세지가 발생하는 것을 막을 수 있다.
동작과정 설명에 앞서 src의 노드의 ID가 1이고 dest 노드의 ID가 2라고 가정한 후 src에서 dest노드로 "hello" 라는 메시지를 보내는 경우 받는쪽에서 패킷을 2번 받지 못하여 retransmission 2번이 일어나 세번째 전송에 ack수신에 성공하는 경우의 모든 동작에 대해서 설명하려고 한다.
Tx와 Rx의 설명에 앞서 main함수의 전체적인 구조는 다음과 같다.
fig.4 . main function structure
fig.4.를 보면 초기에 node의 id를 설정하고나면 FSM의 상태에 따라서 구분되어 실행되는 switch문이 while문안에 있어서 반복적으로 main_state의 변수에 저장된 값에 따라서 실행가능한 부분이 다르다.
#define MAINSTATE_IDLE 0
#define MAINSTATE_TX 1
#define MAINSTATE_ACK 2
정의 된 main_state
또한 main 함수는 초기에 arqEvent_clearAllEventFlag();
가 실행되므로 초기에 설정값은 항상 0이다 따라서 초기에는 IDLE상태로 수신가능한 상태이다.
src에서 dest node로 “hello”를 전송하려는 경우에 대해서 설명하면
fig.5. 초기실행
src에서 dest node로 “hello”를 전송하려는 경우에 대해서 설명하면 초기에 IDLE 상태에서 키보드 이벤트가 발생하면 pdu를 생성하고 이를 LL로 전달하는 과정을 거친다. 세부적인 과정은 다음과 같다. 키 이벤트가 발생하면
실행되는 함수는 void arqMain_processInputWord(void)
이다. 이 함수의 역할은 키보드로 부터 sdu를 생성하고 main_state를 arqEvent_setEventFlag(*arqEvent_dataToSend*);
를 실행해서 FSM을 변화시킨다. 이 함수의 flowchart는 다음과 같다.
fig.6. arqMain_processInputWord.
“hello”라는 문자열을 전송하는 경에는 originalWord[wordLen++]
배열에 hello가 저장되고 마지막에 널 문자까지 삽입되어서 저장된다. 또한
if (main_state == MAINSTATE_IDLE &&
!arqEvent_checkEventFlag(arqEvent_dataToSend))
위 함수의 조건문은 위와 같은데 이 때문에 이미 전송할 데이터가 존재하거나 IDLE상태가 아니라면 sdu를 만들 수 없다. 또한 arqEvent_checkEventFlag(arqEvent_dataToSend)
이벤트 플레그와 이진수 연산을 수행해 같은 이벤트 플레그가 파라미터로 전달되면 0이 아닌 값을 반환하고 같지 않은 이벤트 플레그 상수가 들어오면 0을 반환한다. 이후 main loop에서 설명을 이어가면
fig.7 flowchart
flow chart에서 볼 수 있듯이 IDLE는 TX, RX에서의 동작이 다른데 이는 arq_checkEventFlag()를 포함안 if 문에 따라서 실행된다
송신TX측에서는 arqMsg_encodeData(arqPdu, originalWord, seqNum, wordLen);
이 실행되어서 pdu배열이 생성된다.
uint8_t arqMsg_encodeData(uint8_t* msg_data, uint8_t* data, int seq, int len)
{
msg_data[ARQMSG_OFFSET_TYPE] = ARQMSG_TYPE_DATA;
msg_data[ARQMSG_OFFSET_SEQ] = seq;
memcpy(&msg_data[ARQMSG_OFFSET_DATA], data, len*sizeof(uint8_t));
return len+ARQMSG_OFFSET_DATA;
}
위 코드에서 볼 수 있듯이 각각 msg_data 배열의 지정된 배열의 자리에 지정된 정보가 삽입된다. 선언된 배열의 자리는 다음과 같다.
#define ARQMSG_OFFSET_TYPE 0
#define ARQMSG_OFFSET_SEQ 1
#define ARQMSG_OFFSET_DATA 2
seq number는 pdu배열의 1번 인덱스 자리에 그 이후에 msg는 memcpy(&msg_data[ARQMSG_OFFSET_DATA], data, len*sizeof(uint8_t));
가 사용되어서 메모리에 복사된다.
데이터 전송이 일어나고 나면 seqNum = (seqNum + 1)%ARQMSSG_MAX_SEQNUM;
을 수행해서 sequence number를 증가시키는데 %ARQMSSG_MAX_SEQNUM;
나머지 연산을 적용해서 1024넘으면 다시 0이 되게 만든다 ‘hello’를 전송하는 경우에는 pdu의 총 길이는 0,0,h,e,l,l,o,0(널문자),이므로 총 8 이다.
이후 과정은 다음과 같다.
fig.8. flowchart of MAINSTATE_TX
eventFlag = 1 그리고 MASTATE_TX이기 때문에 위의 과정이 실행된다.
위 case 문이 실행이 되면 arqTimer_startTimer(); 가 호출되며 아래의 함수가 실행된다.
void arqTimer_startTimer()
{
uint8_t waitTime = ARQTIMER_MINWAITTIME + rand()%(ARQTIMER_MAXWAITTIME-ARQTIMER_MINWAITTIME);
timer.attach(arqTimer_timeoutHandler, waitTime);
timerStatus = 1;
}
#define DBGMSG_ARQ 0 //debug print control
#define ARQ_MAXRETRANSMISSION 10
#define ARQTIMER_MAXWAITTIME 5
#define ARQTIMER_MINWAITTIME 2
우의 두개의 코드에 #define ARQTIMER_MAXWAITTIME 5
, #define ARQTIMER_MINWAITTIME 2
따라서 rand()%3과 같다. 그러므로 waitTime = 2 + rand()%3이 되어서 2~4가 된다.
또한 timer.attach(arqTimer_timeoutHandler, waitTime);
이 실행되면
void arqTimer_timeoutHandler(void)
{
timerStatus = 0;
arqEvent_setEventFlag(arqEvent_arqTimeout);
}
fig.9 time out 이벤트 발생 후
위의 코드가 실행됨에 따라서 timeout이벤트가 발생하면 evenflag 변수에 arqEvent_arqTimeout
때문에 32가 저장된다. 따라서 timeout 이벤트가 발생하게 되고 위 과정이 실행이 되고 timeout 이벤트가 두번이상 발생하고 데이터를 제전송하고 나서 다시 eventflag는 0이 되지만 void arqLLI_dataCnfFunc(int err)
이 함수가 다시 실행되고 TX state에서 dataTxDone가 된다.
fig.10 -1
위에서 time out이벤트로 인해서 두번의 재전송을 실행하고 3번째에 ack를 수신하면
fig. 10-2
ACK를 수신하면 위 과정이 수행되고 나서 다시 MAINSTATE_IDLE로 되돌아간다. 다시 data를 수신할 수있는 상태가 된다.
fig.11 . main function structure
RX 측에서도 동일한 코드르 가지고 있기 때문에 기본적인 동작은 동일하나 IDLE상태에서 data 수신한 이벤트가 발생하고 이번 protocol에서는 ack를 송신측에 전송한다는 관점에서 다르다.
fig. 12 -1. Rx pdu 수신과 ack 전송 과정
fig. 12 -2Rx pdu 수신과 ack 전송 과정
위의 fig가 전체적인 Rx의 동작과정이다 pdu를 받아서 ack를 생성하고 이때 Tx로 전환되고 다시 ack를 전송하고 나서 mainSTate가 IDLE 로 전환되는 과정을 보인다.