CAN 통신 serialize(?) - uart 처럼 쓰기

2022. 6. 5. 09:44·개발/MCU

음.. 통신을 CAN으로 했는데 기존 232 프로토콜 하고 맞춰서 사용을 해야 하는 상황이다.

 

그래서 overlay 느낌으로, can 드라이버 위에다가

can_serialize로 명명해서 하나 만든 후,

 

write 할 내용은 ring buffer 에 넣고 8바이트 TX 하게 한다.

그리고 TX 완료 인터럽트 뜰때마다 전송할 거 있으면 8바이트 혹은 그 이하로 꺼내서 전송해주도록 했다.

 

read 내용은 RX 인터럽트 뜨면, ring buffer에 때려 넣고 상위단에 콜백 때려주는 식으로 만들었다.

 

타깃 MCU는 STM32F103RB이다.

 

일단 can 드라이버 작성해둔 것인데

 

can.c -> write 함수 (아래에 중하다 보는 부분을 풀어 적어놨다.)

// can.c

typedef struct {
    uint32_t    id;
    bool        id_extended;

    uint8_t     tx_data[8];
    uint8_t     length;
} can_write_t;

size_t can_write(void* _self, void* buffer, size_t len) {
    can_t* self = (can_t*)_self;

    // Write 요청할 때, can id와 버퍼, 그리고 버퍼 길이를 포함한 can_write_t로 받는다.
    // can_write 함수는 인터페이스로 추상화가 되어있어서 이렇게 해두었다.
    can_write_t* data_to_write = (can_write_t*)buffer;

    // 최소한의 검증(구조체가 맞는지..)
    if (len != sizeof(can_write_t))
        return 0;

    // CAN TX Header. RTR, Timestamp 전송, 데이터 길이, CAN ID, ID Type 등을 정의
    // HAL로 CAN 전송할때 필요한 객체이다.
    CAN_TxHeaderTypeDef tx_header;
    tx_header.RTR = CAN_RTR_DATA;
    tx_header.TransmitGlobalTime = DISABLE;	// Timestamp 전송 안함

    tx_header.DLC = data->length;	// 데이터 길이(8 Byte 가 max)

    tx_header.StdId = data->id;	// Standard ID
    tx_header.ExtId = data->id;	// Extended ID
    // ^ 둘다 때려박아놓고, IDE로 ID 지정만 잘 해주면된다.

    // ID 타입 Standard ID (0x000 ~ 0x7FF)
    tx_header.IDE = CAN_ID_STD;
    if (data->id_extended) // 확장 아이디로 되어있으면 CAN_ID_EXT 로 다시 설정해주고.
        tx_header.IDE = CAN_ID_EXT;

    uint32_t mailbox = 0;    

    // 후술 
    if (HAL_CAN_AddTxMessage(self->handle, &tx_header, data->tx_data, &mailbox))
        return 0;   // 전송 실패

    // CAN_TX_MAILBOX0 ~ CAN_TX_MAILBOX2 에 해당되지 않는다면, 전송 요청 실패한 것.
    if (mailbox != CAN_TX_MAILBOX0 && 
        mailbox != CAN_TX_MAILBOX1 && 
        mailbox != CAN_TX_MAILBOX2) {

        return 0;
    }

    // 전송 요청 성공했으면 할당된 메일박스 ID를 Return 해준다.
    return mailbox;
}

 

위 코드 중 가장 중요한 HAL_CAN_AddTxMessage 부분을 조금 더 보겠다.

// can.c

uint32_t mailbox = 0;

if (HAL_CAN_AddTxMessage(self->handle, &tx_header, data->tx_data, &mailbox))
	return 0;   // 전송 실패
  • self->handle
    • HAL CAN struct이다.
  • tx_header
    • TX 관련된 정보 (ID 값, ID 타입(STD/EXT), 전송 데이터 길이(DLC))
  • data->tx_data
    • 실제로 전송할 데이터 버퍼 (Max 8 Byte)
  • mailbox (*핵심)
    • mailbox는 전송 요청에 성공하면, 어떤 메일 박스에 할당이 되었는지를 돌려준다.

STM32F103 같은 경우에는

#define CAN_TX_MAILBOX0             (0x00000001U)  /*!< Tx Mailbox 0  */
#define CAN_TX_MAILBOX1             (0x00000002U)  /*!< Tx Mailbox 1  */
#define CAN_TX_MAILBOX2             (0x00000004U)  /*!< Tx Mailbox 2  */

이렇게 총 3개의 TX 메일박스가 있다.


만약에 전송 요청 당시에 메일박스 3개가 다 Busy 상태면 mailbox 변수는 0 이 돼서 나오고,

아니라면 할당된 메일박스 ID를 준다 (CAN_TX_MAILBOX0 ~ CAN_TX_MAILBOX2)

 

이제 이 할당된 mailbox 값을 return을 해주는 게 내가 만든 can_write 함수가 해주는 일이다.

 

 

그러면 윗단에서는 아래와 같이 처리한다.

1. 할당된 mailbox 값을 가지고 있다가 CAN TX 완료 콜백을 받으면(HAL TxMailbox Complete에서 내가 콜 하는 콜백)

2. 기존에 받은 mailbox 값과 대조를 해서 자기 TX가 끝났다는 걸 감지할 수가 있다.

// can.c

// 이거 쓰려면 CAN_IT_TX_MAILBOX_EMPTY <- 이거 
// HAL_CAN_ActivateNotification 통해서 활성화 해줘야 한다.

// HAL CAN 완료 콜백 mailbox 0~2

void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan) {
    can_t* self = (can_t*)get_device_by_reference((uint32_t)hcan);

    emit_cb(&self->desc, CAN_TX_CH_0_DONE);	// my custom callback
}

void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan) {
    can_t* self = (can_t*)get_device_by_reference((uint32_t)hcan);

    emit_cb(&self->desc, CAN_TX_CH_1_DONE);	// my custom callback
}

void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan) {
    can_t* self = (can_t*)get_device_by_reference((uint32_t)hcan);

    emit_cb(&self->desc, CAN_TX_CH_2_DONE);	// my custom callback
}

 

이제 윗단에서 처리하는 것에 대해서 적어보면

 

-> Mailbox 값 대조를 통해서 자기 Tx가 끝났음을 확인했으니 다음 Tx 요청을 할 수 있다.

 

윗단에서 콜백 처리하는 것을 보면 이런 식이다.

// can_serilize.c

switch (evt_id) {	// 
 
case CAN_TX_CH_0_DONE:
case CAN_TX_CH_1_DONE:
case CAN_TX_CH_2_DONE:
    // assigned_mailbox 값이 can_write 에서 할당된 mailbox 값이다.
    // 대조해서, 자기가 할당받은 mailbox와, TX 완료된 mailbox를 비교해서
    // 같은 mailbox 라고 하면
    // write 할 데이터가 더 있으면 다음 can_write를 진행하게된다.
    if (self->assigned_mailbox == CAN_TX_MAILBOX0 && evt_id == CAN_TX_CH_0_DONE ||
    	self->assigned_mailbox == CAN_TX_MAILBOX1 && evt_id == CAN_TX_CH_1_DONE ||
    	self->assigned_mailbox == CAN_TX_MAILBOX2 && evt_id == CAN_TX_CH_2_DONE) {

        // CAN 으로 전송할 데이터가 남아있는가? (Lightweight Ringbuffer 라이브러리 사용)
        if (lwrb_get_full(&self->tx_rb)) {
        	// 얼마나 남아있는가? 8개 이상이면 8, 아니면 그대로 tx.length에 설정한다.
        	self->tx.length = lwrb_read(&self->tx_rb, self->tx.tx_data, 8);

        	// can_write call 한다. (interface로 추상화 되어있어서 write로..)
        	self->assigned_mailbox = write(self->dev_can, &self->tx, sizeof(can_write_t));
        } else {
        	// 쓸 데이터가 없으면 할당받은 mailbox 값 초기화 해버린다.
        	// 그래야 지금 TX 중인지 여부 확인 할 수 있으니 
        	// (HAL에 정의된 mailbox ID중 0은 없기에 이걸로 TX 중인지 여부 판단한다.)
        	self->assigned_mailbox = 0;
    	}
    }
    break;   
}

 

암튼 이런 식으로 해놓고 RX도 이런 식으로 해뒀는데, 잘 되는 것 같아서 좋다.

 

그리고 can_serialize 도 인터페이스로 추상화해놓았다.

그래서 최종 사용 단에서는 uart 인터페이스와 사용하는 게 전혀 다르지 않다.

 

나중에 can에서 232로 바꿀 일이 생긴다면, 그리고 vice versa

초기화 코드에서 장치 ID 하나만 바꾸면, 로직은 안 건들여도 된다.

 

comm_init(UART_1) 이걸 comm_init(CAN_SERIALIZE)

이런 식으로 만.

 

계속 더 좋은 코드가 뭔지에 대한 고민을 하고 여러 시행착오를 겪었는데,

인터페이스 통하는 방식이 지금까지 내가 시도해봤던 방식 중엔 제일 깔끔한 것 같다.

 

C 쓰다가 Python으로 넘어오면서 여러가지 실험을 해볼 수 있었는데, 코드가 개선되 것 같아서 기분이 좋다.

 

나중에 코드 정리해서 github에 올려야겠다.

 

'개발 > MCU' 카테고리의 다른 글

STM32 MCU C언어 통신 인터페이스 설계(1)  (0) 2022.06.01
'개발/MCU' 카테고리의 다른 글
  • STM32 MCU C언어 통신 인터페이스 설계(1)
거북이황
거북이황
nomad.hwang@gmail.com
  • 거북이황
    거북이황 개발하는 이야기
    거북이황
  • 전체
    오늘
    어제
    • 분류 전체보기 (25)
      • 프로젝트 (4)
        • 댓글리 (4)
      • 개발 (19)
        • MCU (2)
        • Linux (3)
        • Project (1)
        • AWS (9)
      • 공부 (2)
        • Network (2)
        • CS (0)
  • 블로그 메뉴

    • Github
    • 홈
    • 태그
    • 방명록
  • 링크

    • Github
    • Gist
  • 공지사항

  • 인기 글

  • 태그

    nodejs 설치
    iot provision
    Ai
    Nodejs 16
    orangepi
    ssh proxy
    댓글리
    fleet provisioning
    reverse proxy
    SSH
    provisiong
    빌드
    aws
    NodeJS LTS
    Nodejs cmd
    오랜지파이
    armbian
    ssh tunneling
    aws iot provision
    fleet provision
    방화벽
    Linux
    nodejs
    Kernel
    iot core
    reverse tunneling
    IOT
    Fleet
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
거북이황
CAN 통신 serialize(?) - uart 처럼 쓰기
상단으로

티스토리툴바