nRF52

nRF52840 SPI 드라이버 활용하기

임베디드 친구 2024. 9. 3. 13:09
728x90
반응형

nRF52840 SPI 드라이버 활용하기

SPI(Serial Peripheral Interface)는 마이크로컨트롤러와 다양한 주변 장치 간의 고속 데이터 통신을 위한 시리얼 통신 프로토콜 중 하나입니다. 주로 짧은 거리에서 고속 데이터 전송이 필요한 응용 프로그램에 사용되며, 마스터-슬레이브 구조를 기반으로 여러 슬레이브 장치가 하나의 마스터에 의해 제어될 수 있습니다.

1. SPI의 특징

1.1. 전이중(Full-Duplex) 통신

SPI는 전이중 통신을 지원하며, 마스터와 슬레이브가 동시에 데이터를 주고받을 수 있습니다.

1.2. 동기식(Synchronous) 통신

마스터는 클럭을 제공하여 통신을 동기화합니다. 이 클럭은 SCLK(Serial Clock) 라인을 통해 전달됩니다.

1.3. 데이터 라인:

  • MOSI(Master Out Slave In): 마스터에서 슬레이브로 데이터를 전송하는 라인.
  • MISO(Master In Slave Out): 슬레이브에서 마스터로 데이터를 전송하는 라인.1.4. 슬레이브 선택 라인SS/CS(Slave Select/Chip Select) 라인을 통해 특정 슬레이브 장치를 선택합니다.1.5. 다수의 슬레이브 지원하나의 마스터가 여러 개의 슬레이브 장치를 제어할 수 있습니다.

2. SPI 모듈 설정

  • SPI 모듈을 설정하기 위해 SDK 설정 파일인 sdk_config.h를 수정해야 합니다. 아래와 같은 설정을 활성화합니다.

    #define NRFX_SPIM_ENABLED 1
    #define NRFX_SPIM2_ENABLED 1
    #define NRFX_SPI_ENABLED 1
    #define NRFX_SPI2_ENABLED 1
    #define SPI_ENABLED 1
    #define SPI2_ENABLED 1
  • 또한, nRF 드라이버 파일 경로는 다음과 같습니다.

    ./integration/nrfx/legacy/nrf_drv_spi.c
    ./modules/nrfx/drivers/src/nrfx_spim.c

    3. SPI 드라이버 초기화이 섹션에서는 MCP2515 SPI CAN 모듈 드라이버를 사용하여 SPI 드라이버 초기화 방법을 설명합니다.

3.1. 드라이버 구성

먼저, nrf_drv_spi_config_t 구조체를 사용하여 SPI 속도와 모드를 설정합니다. 또한 각 통신 라인(MOSI, MISO, SCLK, SS) 핀을 설정합니다. SPI 이벤트 핸들러는 통신 요청이 완료되었는지 확인하는 용도로 사용됩니다.

중요: SPI 통신을 시작하기 전, 슬레이브 장치를 선택하기 위해 SS 핀을 Low 상태로 설정합니다. 통신이 완료되면 SS 핀을 다시 High 상태로 변경해야 합니다.

#define M_PRPBE_SPI_CAN_INSTANCE  2  /**< SPI 인스턴스 인덱스. */
static const nrf_drv_spi_t can_spi = NRF_DRV_SPI_INSTANCE(M_PRPBE_SPI_CAN_INSTANCE);  /**< SPI 인스턴스. */

#define MCP2515_PIN_INT            NRF_GPIO_PIN_MAP(1, 2)
#define M_PROBE_SPI_SS_PIN        NRF_GPIO_PIN_MAP(0, 11)
#define M_PROBE_SPI_MISO_PIN    NRF_GPIO_PIN_MAP(0, 12)
#define M_PROBE_SPI_MOSI_PIN    NRF_GPIO_PIN_MAP(0, 13)
#define M_PROBE_SPI_SCK_PIN        NRF_GPIO_PIN_MAP(0, 14)

void spi_can_event_handler(nrf_drv_spi_evt_t const * p_event, void * p_context)
{    
    if (p_event->type == NRF_DRV_SPI_EVENT_DONE)
    {
        spi_xfer_done = true;
    }
}

void mcp_spi_init(void)
{
    uint32_t err_code;

    mcp_can_setcs(M_PROBE_SPI_SS_PIN);

    nrf_drv_gpiote_in_config_t mcp2515_int_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
    mcp2515_int_config.pull = NRF_GPIO_PIN_PULLUP;

    err_code = nrf_drv_gpiote_in_init(MCP2515_PIN_INT, &mcp2515_int_config, mcp2515_int_pin_handler);
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_in_event_enable(MCP2515_PIN_INT, true);

    nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
    spi_config.frequency = NRF_DRV_SPI_FREQ_125K;
    spi_config.mode = NRF_DRV_SPI_MODE_0; // SPI 모드 설정
    spi_config.ss_pin    = M_PROBE_SPI_SS_PIN; // SS 핀 설정
    spi_config.miso_pin = M_PROBE_SPI_MISO_PIN; // MISO 핀 설정
    spi_config.mosi_pin = M_PROBE_SPI_MOSI_PIN; // MOSI 핀 설정
    spi_config.sck_pin    = M_PROBE_SPI_SCK_PIN; // SCLK 핀 설정
    spi_config.bit_order = NRF_DRV_SPI_BIT_ORDER_MSB_FIRST; // 비트 순서 설정

    err_code = nrf_drv_spi_init(&can_spi, &spi_config, spi_can_event_handler, NULL);
    APP_ERROR_CHECK(err_code);

#if MCP2515_DEBUG_MODE
    NRF_LOG_INFO("mcp_spi_init complete"); 
#endif
}

3.2 SPI 통신 시작

SPI 통신을 시작하기 위해 SS 핀을 Low 상태로 변경한 후, TX와 RX 버퍼를 준비합니다. nrf_drv_spi_transfer() 함수를 호출하여 데이터를 송수신합니다. 통신이 완료되면 SS 핀을 다시 High 상태로 설정합니다.

void mcp2515_select(void)
{
    nrf_gpio_pin_clear(m_mcp_can.m_cs);
}

void mcp2515_unselect(void)
{
    nrf_gpio_pin_set(m_mcp_can.m_cs);
}

uint8_t mcp2515_readRegister(const uint8_t address)
{
    mcp2515_select();

    uint8_t m_tx_buf[] = {MCP_READ, address, 0x00};
    uint8_t m_tx_length = sizeof(m_tx_buf);

    uint8_t m_rx_buf[3];      
    uint8_t m_rx_length = sizeof(m_rx_buf);

    memset(m_rx_buf, 0, m_rx_length);

    spi_xfer_done = false;

    APP_ERROR_CHECK(nrf_drv_spi_transfer(&can_spi, m_tx_buf, m_tx_length, m_rx_buf, m_rx_length));

    while (!spi_xfer_done)
    {
        if (nrf_sdh_is_enabled())
        {
            sd_app_evt_wait();
        }
        else
        {
            __WFE();
        }
    }

    mcp2515_unselect();

    return m_rx_buf[2];  
}

3.3 SPI를 활용한 CAN 통신 초기화

CAN 통신을 초기화하는 과정은 다음과 같습니다.

void can_init(void)
{
    NRF_LOG_INFO("CAN Initialization...");

    mcp_spi_init();

    NRF_LOG_INFO("MCP2515 SPI Initialized");

START_INIT:

    if (mcp_can_begin(CAN_500KBPS, MCP_8MHz) == CAN_OK)
    {
        NRF_LOG_INFO("CAN BUS Initialization ok");
    }
    else
    {
        NRF_LOG_INFO("CAN BUS Initialization failed");
        NRF_LOG_INFO("Init CAN BUS again");
        nrf_delay_ms(1000);
        goto START_INIT;
    }

    NRF_LOG_INFO("CAN Initialization COMPLETED.");
}

void can_thread(void* pvParameters)
{
    can_init();

    for (;;)
    {
        uint32_t can_id;
        uint8_t buf[16];
        uint8_t len;

        if (CAN_MSGAVAIL == mcp_can_check_receive())
        {
            mcp_can_read_msg(&can_id, &len, buf);
            NRF_LOG_INFO("%x %d", can_id, len);
            continue;
        }

        vTaskDelay(pdMS_TO_TICKS(1));
    }
}

int can_main(void * pvParameters)
{
    if (pdPASS != xTaskCreate(can_thread, "can", 256, pvParameters, 2, NULL))
    {
        APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
    }
}

4. 결론

SPI는 고속 데이터 전송이 가능하고 간단한 구조를 가진 시리얼 통신 프로토콜입니다. 이를 통해 센서, 메모리, 통신 장치 등 다양한 주변 장치와 쉽게 통신할 수 있습니다. 다만, 케이블 길이 제한과 복잡한 구성으로 인해 짧은 거리의 고속 통신에 적합합니다.

이 글에서는 nRF52840에서 SPI 드라이버를 설정하고, MCP2515 CAN 모듈과의 통신을 구현하는 방법을 설명했습니다. 이를 통해 SPI를 활용한 다양한 응용 프로그램을 개발하는 데 도움이 될 것입니다.

반응형