STM32 CAN MultiPacket - DM1 / EC1 Message

일반적으로 CAN 데이터는 8바이트 크기를 전송되지만, 8바이트 이상의 데이터를 전송하기 위해 특별한 방법을 사용하여 J1939 프로토콜은 BAM(Broadcast Announce Message) 방식으로 최대 1785바이트를 전송할 수 있다.

J1939 프로토콜은 0xEC00 / 0xEB00 PGN을 사용하여 멀티패킷을 전송하며, CM(Connection Management) 메시지는 멀티패킷의 PGN 및 길이 패킷 개수 정보를 나타내며, DT(Data Transfer) 메시지는 패킷 인덱스 및 데이터를 포함하고 있다.

 

아래 포스트에서 작성한 코드에서 멀티 패킷을 수신하는 코드를 추가할 것이며, 기존 코드에서 변경된 사항을 패치로 정리할 것이다.

 

STM32 CAN 32비트 멀티 필터 설정

J1939 프로토콜의 CAN 데이터를 수신하여 처리하려면 CAN 통신속도 250kbps, 샘플포인트 87%로 설정해야 한다. 내부클럭(HSI)을 사용할 경우 MCU의 상태에 따라 클럭이 틀어져서 CAN 데이터를 수신 못하는

memories.tistory.com

멀티 패킷을 나타내는 0xEC00와 0xEB00를 받기위해 아래와 같이 기존코드에서 아래와 같이 필터를 추가하도록 한다.

diff --git a/F429/CANTest2/Core/Src/main.c b/F429/CANTest2/Core/Src/main.c
--- a/F429/CANTest2/Core/Src/main.c
+++ b/F429/CANTest2/Core/Src/main.c
@@ -175,6 +175,7 @@ static void MX_CAN1_Init(void)
     result |= set_can_filter(1, 0x00FD0000, 0x00FF0000);    /* 0x00FDxx00 : AT1S1, DLCC1, DPFC1, ECUID, EOI */
     result |= set_can_filter(2, 0x00FE0000, 0x00FF0000);    /* 0x00FExx00 : AMB, AT1T1I, CCVS1, DM1, EC1, EEC3, EPLP1, EPLP2, ET1, ET2, HOURS, IC1, LFC, IO, SHUTDN, TCI2 */
     result |= set_can_filter(3, 0x00FF4800, 0x00FFF800);    /* 0x00FF4x00 : E2M4A, E2M4C */
+    result |= set_can_filter(4, 0x00E80000, 0x00F80000);    /* 0x00Exxx00 : EB00, EC00 */
     result |= HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
     if(result != 0)
     {

그리고, CAN 데이터를 수신하는 인터럽트에서 멀티패킷을 처리하기 위해 아래와 같이 수정하도록 한다.

diff --git a/F429/CANTest2/Core/Src/main.c b/F429/CANTest2/Core/Src/main.c
index 1b2d130..595293a 100644
--- a/F429/CANTest2/Core/Src/main.c
+++ b/F429/CANTest2/Core/Src/main.c
@@ -72,10 +72,31 @@ void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
 {
     if(hcan->Instance == CAN1)
     {
+        uint32_t filter_mask;
         HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &rx_header, read_data);
-        printf("CAN_ID = 0x%08lX, Data = [", rx_header.ExtId);
-        for(int i = 0 ; i < 8 ; i++) printf("0x%02X, ", read_data[i]);
-        printf("\b\b]\r\n");
+
+        HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &rx_header, read_data);
+        filter_mask = rx_header.ExtId & 0x00FF0000;
+        switch(filter_mask)
+        {
+            case 0x00EB0000:
+            case 0x00EC0000:
+                j1939_parser_bam(rx_header.ExtId, read_data);
+                break;
+
+            case 0x00F00000:
+            case 0x00FD0000:
+            case 0x00FE0000:
+            case 0x00FF0000:
+                printf("CAN_ID = 0x%08lX, Data = [", rx_header.ExtId);
+                for(int i = 0 ; i < 8 ; i++) printf("0x%02X, ", read_data[i]);
+                printf("\b\b]\r\n");
+                break;
+
+            default:
+                printf("unknown data :: CAN_ID = 0x%08lx, filter_mask = 0x%08lx\r\n", rx_header.ExtId, filter_mask);
+                break;
+        }
     }
 }

마지막으로 J1939 프로토콜에 따라, BAM 메시지인 CM과 DT를 규격에 맞게 아래와 같이 수신되는 데이터를 BAM_t 구조체에 저장하도록 한다.

+#define J1939_EC1         0x18FEE300
+#define J1939_DM1         0x18FECA00
+#define J1939_BAM_CM      0x00EC0000
+#define J1939_BAM_DT      0x00EB0000
+#define MAX_BAM_LENGTH    1785
+
+typedef struct _bam_t
+    uint32_t pgn;
+    uint16_t length;
+    uint8_t  num_of_packet;
+
+    uint16_t index;
+    uint8_t  message[MAX_BAM_LENGTH];
+}BAM_t;
+
+BAM_t bam;
 uint8_t read_data[8];
 CAN_RxHeaderTypeDef rx_header;
+
+int j1939_parser_bam(uint32_t ext_id, uint8_t *data)
+{
+    uint32_t bam_id = ext_id & 0x00FF0000;
+
+    if(bam_id == J1939_BAM_CM)
+    {
+        if(data[0] != 0x20)
+        {
+            printf("INFO :: %s() : invalid sync byte : data[0] = 0x%02x\r\n", __func__, data[0]);
+            return -1;
+        }
+
+        if(bam.pgn != 0)
+        {
+            printf("INFO :: %s() : already received BAM_CM : pgn = 0x%04lx\r\n", __func__, bam.pgn);
+            return -1;
+        }
+
+
+        memset(&bam, 0x00, sizeof(BAM_t));
+        bam.length        = data[2] << 8  | data[1];
+        bam.num_of_packet = data[3];
+        bam.pgn           = data[7] << 16 | data[6] << 8 | data[5];
+    }
+    else if(bam_id == J1939_BAM_DT)
+    {
+        if(bam.index < bam.length)
+        {
+            if((data[0] * 7) < bam.length)
+            {
+                memcpy(&bam.message[bam.index], &data[1], 7);
+                bam.index += 7;
+            }
+            else
+            {
+                for(int i = 0 ; i < 7 ; i++)
+                {
+                    if(bam.index < bam.length)
+                        bam.message[bam.index++] = data[1+i];
+                }
+            }
+
+            if(bam.index == bam.length)
+            {
+                uint32_t pgn = bam.pgn << 8;
+
+                if((pgn & J1939_DM1) == pgn)
+                {
+                    printf("DM1 Message : length = %d -----------------------\r\n", bam.length);
+                    for(int i = 0 ; i < bam.length ; i++)
+                    {
+                        printf("0x%02X, ", bam.message[i]);
+                        if((i%0x08) == 0x07) printf("\r\n");
+                    }
+                    printf("-------------------------------------------------\r\n");
+                }
+                else if((pgn & J1939_EC1) == pgn)
+                {
+                    printf("EC1 Message : length = %d -----------------------\r\n", bam.length);
+                    for(int i = 0 ; i < bam.length ; i++)
+                    {
+                        printf("0x%02X, ", bam.message[i]);
+                        if((i%0x08) == 0x07) printf("\r\n");
+                    }
+                    printf("-------------------------------------------------\r\n");
+                }
+                else
+                {
+                    printf("Warning :: %s() : pgn = 0x%08lx\r\n", __func__, pgn);
+                }
+                memset(&bam, 0x00, sizeof(BAM_t));
+            }
+        }
+    }
+    else
+    {
+        return -1;
+    }
+
+    return 0;
+}

BAM_t 메시지 구조체를 만들어 멀티 패킷으로 들어오는 데이터에서 메타 데이터를 제거하고 Pure 데이터만 message 배열에 저장하도록 한다. EC1 메시지는 19바이트 크기를 가지며, DM1 메시지는 에러상태에 따라서 메시지 크기가 가변되기 때문에 멀티 패킷을 사용해야 한다. 아래 이미지는 EC1/DM1 메시지를 만들어 STM32에서 수신한 결과를 출력한 것이다.

위 테스트 코드는 아래 GitHub에서 다운받을 수 있으며, USB2CAN 모듈을 사용하여 일반 & 멀티 패킷 메시지를 처리할 수 있다.

 

highgon2/STM32

STM32 TestCode. Contribute to highgon2/STM32 development by creating an account on GitHub.

github.com