LIN 2.0

LIN 2.0 통신 프로그래밍 강좌 5 - 진단과 설정 구현 1

BLDC 2013. 2. 27. 12:29

 

 

완성차 업체와 같이 LIN 클러스터(네트워크)를 구성하는 업체 또는 마스터 노드 제작 업체는 클러스터를 구성하는 슬레이브 노드들이 가진 메세지들의 ID가 중복되지 않도록 구성해야한다. LIN 1.3에서는 클러스터 구성 업체가 노드 제작 업체에게 사전에 ID를 미리 할당해주면 노드 제작 업체가 이에 맞게 프로그래밍해야한다. ID는 노드에서 사용할 메시지 수에 맞게 할당되어야한다. 예를 들어 슬레이브 노드 A 제품이 3개의 메시지를 이용해야한다면 3개의 아이디를 할당받아야한다.

 

LIN 2.0에서는 ID 할당 방법이 대폭 변경되었으며, LIN Diag. and Config. 규정으로 명시되어 있다. 따라서 2.0 제품을 만들려면 이 규정을 명확히 이해하고 준수해야하다.

 

LIN 2.0에서는 슬레이브 노드 제조사가 제품 납품 이전에 노드의 아이디(LIN Diag. and Config. 표2.3 참조)가 포함된 NCF(Node Capability File)을 클러스터(마스터 노드) 제조사에게 제출해야한다. 마스터는 수집된 슬레이브 노드들의 NCF를 취합하고, NCF의 NAD(노드 주소)를 참조하여 ID를 할당하도록 스케줄 테이블을 작성한다. 이후 클러스터가 구성되어 전원이 인가되면 마스터는 스케줄 테이블에 의하여 각 노드의 모든 메시지에 아이디를 할당하여 통신을 초기화한다. 이후에는 할당된 아이디로 정보를 교환한다.

 

 

 

 

 

2.0의 초기화 방법에도 처음 전원이 투입되었을 때, 여러 노드 중 특정한 슬레이브 노드를 지정하기 위한 주소는 필요하다. 이것이 바로 NAD이다. NAD는 노드를 지정하는 주소이기 때문에 노드 당 하나의 주소만을 갖는다. 슬레이브 제조사는 제품을 납품하기 전에 마스터 제조사로부터 NAD를 할당받아서 코딩에 반영해야한다.

모든 메시지는 마스터로부터 PID를 할당받았는지를 기억하는 bit 변수(pidValid, 그림의 v)를 가져야하고, 초기값은 unvalid이다. 또한 마스터가 슬레이브에 있는 여러 메시지 중 특정한 메시지를 지정하여 PID를 할당할 수 있도록 메시지마다 고유한 아이디(MID, message ID)를 가지고 있어야한다. LIN Diag. & Config. 규정 표2.4를 보면 MID는 16-비트로 지정하도록 되어있다. 그리고 코딩 편의상 각 메시지마다 송/수신 역활을 구분하는 bit 변수(rxTx)와 메시지의 데이터 길이를 저정하는 변수(length), 실제 데이터를 저장하는 배열변수(data[])를 묶어서 하나의 구조체로 선언하도록 하자.

 

struct{

unsigned char rxTx : 1;    // 수신=0(초기값), 전송=1

unsigned char pidValid : 1;    // unvalid=0(초기값), valid=1

unsigned char length : 6;    // length = 4 ~ 8

unsigned int MID;    // 메시지 아이디

unsigned char PID;    // 할당된 PID 저장(초기값=0xFF)

unsigned char data[8];    // 송수신 데이터

}messageTable[4];    

 

모든 메시지가 8-바이트가 아니라면 메모리 절약을 위해 위 구조체의 data[]는 메시지마다 별도의 배열을 선언하고, 구조체 내에는 포인터 변수를 두는 것이 좋다. 그러나 본 강좌에서는 이해를 돕는 것이 목적이기 때문에 가급적이면 직관적으로 이해하기 쉬운 코딩을 지향한다.

messageTable은 적절한 초기값을 가져야한다. 구조체 변수에 초기값을 설정하는 방법은 인터넷을 검색하면 많은 자료가 있으니 참조하여 작성하기 바라며, 본 강좌에서는 단순하게 main() 함수의 초기화 과정에서 일일이 값을 할당할 것이다.

messageTable 배열 원소는 각각 다음 역활을 담당한다.

  • messageTable[0] : COMMAND_MSG 수신, 4-byte 데이터, MID=1
  • messageTable[1] : STATE_MSG 송신, 8-byte 데이터, MID=2
  • messageTable[2] : MASTER_REQ(마스터 요청)수신, MID=3
  • messageTable[3] : SLAVE_RES(슬레이브 응답)전송, MID=4

위의 MST_REQ와 SLV_RES는 진단과 설정 규약에 따른 것이다. MID는 프로그래머가 임의로 설정하고, 납품 시 NCF에 기입하여 마스터 제조사에 제출한다.

 

진단과 설정 프로그램의 코딩은 PID의 수신에서부터 시작한다.  LIN 프로토콜 규정 2.3.4절에 마스터 요청 프레임 아이디는 0x3C, 슬레이브 응답 프레임 아이디는 0x3D로 지정되어 있다. 마스터 요청 프레임은 무조건 수신해야한다. 수신 후 마스터 요청 프레임의 NAD가 자신의 NAD와 같은 노드만 마스터 요청 프레임을 처리하고, NAD가 같이 않으면 무시한다.

슬레이브 응답 프레임은 경우에 따라 다르다. 슬레이브 응답은 아래 그림과 같이 최근의 마스터 요청 프레임이 NAD로 지정한 노드만이 응답해야한다. 따라서 슬레이브 입장에서는 마스터 요청 프레임을 수신/처리한 후에, 다른 노드로 전송된 마스터 요청 프레임이 없을 경우에만 응답해야한다. 즉, 슬레이브 응답은 마스터 요청에서 마지막으로 지정한 NAD에 해당하는 노드만이 응답해야한다. 

 

 

 

 

또한 다음 그림과 같이 마스터 요청과 슬레이브 응답 사이에 다른 노드에 대한 마스터 요청이 없었다면 일반 메시지 프레임이 중간에 있었다 하더라도 가장 최근의 마스터 요청에서 지정한 NAD를 가진 슬레이브 노드는 반드시 슬레이브 응답을 전송해야한다.

 

 

 

 이러한 조건을 따지기 위해 논리변수 mustSlaveResponse를 도입한다.

 

한편 2.1.5절에 의하면 진단 프레임은 일반 첵섬을 사용하도록 규정되어 있다.

이제까지의 내용을 프로그램에 반영하려면 pidHandle()부터 수정하는 것이 좋겠다.

 

#define  COMMAND_MSG   messageTable[0]

#define  STATE_MSG         messageTable[1]

#define  MASTER_REQ       messageTable[2]

#define  SLAVE_RES          messageTable[3]

 

char tableHandle = 0;    // 현재의 LIN 프레임에 관련된 메시지 테이블의 인덱스

char mustSlaveResponse = 0;    // 슬레이브 응답을 해야할지 결정

 

pidHandle(char PID)

{

// 수신 메시지 프레임은 아이디가 할당되지 않았다면 수신하지 않는다.

if (PID == COMMAND_MSG.PID && COMMAND_MSG.valid == 1)

{

tableHandle = 0;

linStep = RX_STEP;

checkSum = PID;    // 개선 첵섬

dataLength = COMMAND_MSG.length;

}

// 전송 메시지 프레임은 아이디가 할당되지 않았다면 전송하지 않는다.

else if (PID == STATE_MSG.PID && STATE_MSG.valid == 1)

{

tableHandle = 1;

linStep = TX_STEP;

checkSum = PID;    // 개선 첵섬

dataLength = STATE_MSG.length;

for(i=0; i<STATE_MSG.length; i++)

tx_data[i] = STATE_MSG.data[i];    // 전송할 데이터 복사

}

// 마스터 요청 프레임은 무조건 수신한 후 NAD가 지정한 노드에서만 처리한다.

else if (PID == 0x3C)

{

tableHandle = 2;

linStep = RX_STEP;

checkSum = 0;    // 일반 첵섬

dataLength = MASTER_REQ.length;

}

// 슬레이브 응답 프레임은 마스터 요청 프레임을 처리한 후,

// 다른 노드가 처리할 마스터 요청 프레임이 없었으면 응답한다.

else if (PID == 0x3D && mustSlaveResponse == 1)

{

tableHandle = 3;

linStep = TX_STEP;

checkSum = 0;    // 일반 첵섬

dataLength = SLAVE_RES.length;

for(i=0; i<SLAVE_RES.length; i++)

tx_data[i] = SLAVE_RES.data[i];    // 전송할 데이터 복사

}

else

{

// 노드와 관련 없는 PID 수신, 또는 응답 조건 안맞음.

// dormant 상태로 전환

resetToDormant();

}

}

------------------------------------------------------------------------------------------------

 

첵섬의 초기치를 pidHandle()에서 설정해야하므로 UART_RX() 인터럽트 서비스 루틴에서 linStep이 PID_STEP일 때의 첵섬 초기치 설정은 삭제해야한다.

UART_RX()에서 응답 프레임을 수신하는 경우는 위의 메시지 테이블 배열을 이용하기 위해 다음과 같이 수정한다.

 

interrupt_service_routine UART_RX()

{

....

else if (linStep == RX_STEP)

{

rx_data[rxCount] = RX_BUFFER;

if (rxCount < messageTable[tableHandle].length)

{

checkSum += rx_data[rxCount];

if (checkSum > 255) checkSum = checkSum - 256 +1;

rxCount++;

}

else

{

if ((checkSum + rx_data[rxCount]) == 255)      // checkSum 맞음

{

for(i=0; i<messageTable[tableHandle].length; i++)

messageTable[tableHandle].data[i] = rx_data[i];

setSuccessfulTransfer();

}

else

{

//*** CheckSum error ***//

errorFlags.bits.checkSumError = 1;

setErrorInResponse();

}

 // 수신 완료

resetToDormant();

}

}

}

 

● 마스터 요청 수신 처리 - masterReqHandle()

 

successfulTransfer가 1로 설정되어 있고, lastPID가 0x3C일 경우, 마스터 요청이 수신된 것이다. main()에서는 successfulTransfer와 lastPID에 따라 다음과 같이 처리 방법을 결정해야한다. 본 강좌에서는 수신 메시지가 마스터 요청과 무조건 프레임(COMMAND_MSG) 단 2개이므로, 두 경우만 처리하는 것으로 코딩하였다. 전송의 경우에도 successfulTransfer가 1로 설정되지만, PID가 다를 것이므로 프로그램에는 영향이 없다.

 

void main()

{

.....

while(1)

{

.....

if (linStatus.bits.lastPID == COMMAND_MSG.PID)

commandlHandle();

else if (linStatus.bits.lastPID == MASTER_REQ.PID)

masterReqHandle();

}

}

 

위에서 commandHandle()은 마스터로부터 수신한 지령을 처리하는 함수로써, 제품에 따라 기능에 맞게 작성해야한다. masterReqHandle()는 마스터 요청을 수신하여 처리하는 함수이며, LIN 진단과 설정 규정에 맞게 작성해야한다. 마스터 요청 메시지의 내용을 아래 표에 정리하였다.

SuID : supplier Id, 공급자 아이디        MID : message Id, 메시지 프레임 아이디

FID : function Id, 기능 아이디            LSB : Least significant byte

MSB : Most significant byte

 

위의 표에서 프레임 아이디 할당아이디로 읽기 요청은 의무사항이므로 반드시 구현해야하고, 나머지는 옵션이므로 구현하지 않아도 무방하다. 다만 어떤 요청들을 구현했는지는 NCF 문서 작성시에 명확히 기입하여 마스터 제조사에게 제출해야한다.

 

마스터 요청 수신 후 처리 과정을 요약하면 다음과 같다.

  • NAD가 자신의 NAD(변수 myNAD)와 같으면 처리한다.
    • PCI와 SID에 따라 처리
    • 처리 후 긍정 응답 메시지 작성
    • 처리 불가일 경우, 부정 응답 작성
    • mustSlaveResponse=1
  • NAD != myNAD이면 mustSlaveResponse=0 (다른 슬레이브 노드에 대한 마스터 요청이므로 응답하지않는다.)

단, 마스터 요청이 프레임 아이디 할당일 경우, 긍정 응답만 있고 부정 응답은 규정에 없다는 점에 주의해야한다. 위의 과정에 따라 masterReqHandle() 함수의 프레임을 잡아보면 다음과 같다. 단, 본 강좌에서는 사용자 정의로 사용되는 데이터 덤프 및 진단은 구현하지 않는다. 구현하지 않은 요청에 대한 코드는 단순히 해당 부분을 지우면 된다.

 

------------------------------------------------------------------------------------------------

int myNAD = 1;    // 마스터 제조사로부터 할당받는다.

// 클러스터에서 마스터로부터 변경 요청을 받을 수 있으므로 const로 선언하지 말것.

 

const int mySID=1, myFID=1, myVID=1;    // 공급자, 기능, 변형 아이디

// 공급자 아이디는 LIN 조합에 가입하여 할당받거나

// 마스터 제조사와 협의하여 임시로 받는다. 나머지는 임의 할당.

 

 void masterReqHandle()

{

char mrNAD = messageTable[2].data[0];

char mrPCI = messageTable[2].data[1];

char mrSID = messageTable[2].data[2];

char mrD1 = messageTable[2].data[3];

char mrD2 = messageTable[2].data[4];

char mrD3 = messageTable[2].data[5];

char mrD4 = messageTable[2].data[6];

char mrD5 = messageTable[2].data[7];

 

if (mrNAD == myNAD){

if (mrPCI == 0x06 && mrSID == 0xB1)    // 프레임 아이디 할당

{    .....    }

else if (mrPCI == 0x06 && mrSID == 0xB2)    // 아이디로 읽기

{    .....    }

else if (mrPCI == 0x06 && mrSID == 0xB0)    // NAD 할당

{    .....    }

else if (mrPCI == 0x06 && mrSID == 0xB3)    // 조건부 NAD 변경

{    .....    }

else if (mrPCI == 0x06 && mrSID == 0xB4)    // 데이터 덤프

{    .....    }

else if (mrPCI == 0xXX && mrSID== 0xXX)    // 진단

{    .....    }

else

{    .....    }

}

else if (mrNAD == 0)    // 휴면 진입 지령

{   

 ....

mustSlaveResponse = 0;    // 휴면 진입에 대한 응답은 하지않는다.

}

else{

mustSlaveResponse = 0;    // 다른 노드가 슬레이브 응답 전송

}

}

------------------------------------------------------------------------------------------------

강좌가 길어져서 마스터 요청의 세부 요청 별 구현은 다음 글에 계속됩니다.