nRF52832 개발일지 (4)

치삼이·2021년 7월 19일
1

nRF52832 개발일지

목록 보기
5/8
post-thumbnail

👨‍💻 개발일지 (4) : Nordic SDK로 DWM1001 SPI 통신하기 -1-

지난번까지는 UART 통신을 바탕으로 Driver 분석을 진행했습니다. 이번에는 SPI 통신을 이용해 DWM1001을 제어를 해보겠습니다.

🔗 개발일지 (1) 에서 설명했듯 DWM1001은 DW1000 이라는 UWB칩 + nRF52832를 붙여놓은 SoC 입니다. Host MCU(nRF52832) 에서 DW1000에 있는 레지스터를 읽거나 쓰면서 제어하게 되는데 이때 사용되는 인터페이스가 SPI 입니다.

1. nRF52832 SPI 확인

우선 nRF5832의 SPI 부분의 Product Specification에서 SPI관련 인스턴스 및 레지스터 부터 살펴보도록 하겠습니다.

  • SPIM - Serial peripheral interface master with EasyDMA
  • SPIS - Serial peripheral interface slave with EasyDMA
  • SPI - Serial peripheral interface master

nRF52832는 SPI는 👆 위에서 언급한 세 가지의 형태의 인스턴스가 존재합니다. SPI에서 DMA를 이용하지 않으면 마스터 모드만 사용할 수 있습니다.

📝 보통 호스트 MCU를 마스터 센서나 엑츄에이터를 슬레이브로 사용합니다.

SPI는 SPI0, SPI1, SPI2 세 개의 인스턴스를 만들 수 있으며, 각각 SPIM0, SPIS0, SPI0 등 뒤에 같은 숫자가 붙은 인스턴스는 리소스를 공유 합니다. 다음에 살펴보겠지만 I2C 통신도 같은 리소스를 공유 합니다.

SPIM, SPIS를 제외한 SPI관련 레지스터만 간단하게 살펴보도록 하겠습니다. 🔍

PSEL.SCK, PSEL.MOSI, PSEL.MISO 가 중복 되네요. RXD와 TXD는 데이터를 저장하는 레지스터 입니다. FREQUENCY 레지스터는 클럭주파수를 조정하며 최대 8Mhz 까지 셋팅할 수 있습니다. CONFIG는 SPI의 CPOL, CPHA를 조정해 SPI의 모드를 셋팅하는 레지스터 입니다.

2. Nordic SDK의 SPI 분석

Nordic SDK의 SPI 드라이버는 🔗 개발일지 (2) 에서 말했던것처럼 아래의 그림으로 표현 할 수 있습니다.

우리는 제일 하단에 위치한 nrfx_spi.cnrfx_spi.h 두 개의 파일만 확인 하겠습니다. nrf_drv_spi.cnrf_drv_spi.h 는 UART 때와 마찬가지로 자료형만 바꿔주며 매크로를 이용해 SPI 와 SPIM 둘 중 어떤 인스턴스를 이용하는지 판단하여 알맞은 함수를 호출 해줍니다.

먼저 nrfx_spi.h 에 정의되어 있는 nrfx_config_t을 살펴보도록 하겠습니다.

typedef struct
{
    uint8_t sck_pin;                ///< SCK pin number.
    uint8_t mosi_pin;               ///< MOSI pin number (optional).
                                    /**< Set to @ref NRFX_SPI_PIN_NOT_USED
                                     *   if this signal is not needed. */
    uint8_t miso_pin;               ///< MISO pin number (optional).
                                    /**< Set to @ref NRFX_SPI_PIN_NOT_USED
                                     *   if this signal is not needed. */
    uint8_t ss_pin;                 ///< Slave Select pin number (optional).
                                    /**< Set to @ref NRFX_SPI_PIN_NOT_USED
                                     *   if this signal is not needed. The driver
                                     *   supports only active low for this signal.
                                     *   If the signal must be active high,
                                     *   it must be controlled externally. */
    uint8_t irq_priority;           ///< Interrupt priority.
    uint8_t orc;                    ///< Overrun character.
                                    /**< This character is used when all bytes from the TX buffer are sent,
                                         but the transfer continues due to RX. */
    nrf_spi_frequency_t frequency;  ///< SPI frequency.
    nrf_spi_mode_t      mode;       ///< SPI mode.
    nrf_spi_bit_order_t bit_order;  ///< SPI bit order.
} nrfx_spi_config_t;

다음은 nrfx_spi.c 에 정의 되어 있는 spi_control_block_t 입니다. 이 타입으로 인스턴스를 관리합니다.

typedef struct
{
    nrfx_spi_evt_handler_t  handler;
    void *                  p_context;
    nrfx_spi_evt_t          evt;  // Keep the struct that is ready for event handler. Less memcpy.
    nrfx_drv_state_t        state;
    volatile bool           transfer_in_progress;

    // [no need for 'volatile' attribute for the following members, as they
    //  are not concurrently used in IRQ handlers and main line code]
    uint8_t     ss_pin;
    uint8_t     miso_pin;
    uint8_t     orc;
    size_t      bytes_transferred;

    bool        abort;
} spi_control_block_t;

전에 살펴보았던 nrfx_uart.c, nrfx_uart.h 의 컨셉과 크게 다르지 않다는 것을 확인할 수 있었습니다.

3. DW1000 SPI 통신

위에서 설명했듯, DW1000은 UWB를 위한 칩셋 입니다. DW1000의 모든 기능을 사용하려면 당연히 UWB에 대해 공부해야 합니다... 😵 하지만 이번에는 SPI만 다루기 때문에 DW1000의 동작 방식, RF 및 HW의 지식은 다루지 않겠습니다. 오직 SPI 인터페이스에 관한 부분만 다루도록 하겠습니다. 🤣

아래 👇 링크로 들어가시면 최신 DW1000 User Manual을 다운로드 할 수 있습니다.

🔗 DW1000 User Manual

2.2 절의 Inferfacing to the DW1000 부터 확인하면 되겠습니다.

3.1 SPI의 트랜잭션 및 레지스터 맵

DW1000과 HostMCU(nRF52832)의 SPI 통신의 컨셉은 다음과 같습니다.

마스터 디바이스에서 Transaction Header에 Read 명령을 내리면서 동시에 슬레이브에서 교환되는 바이트는 무시합니다. 그리고 슬레이브에서 Transaction Body를 송신하면서 상호작용을 합니다. 쓰기 연산은 오직 마스터에서만 나가며 이때 슬레이브에의해 교환되는 바이트는 모두 무시됩니다.

위의 레지스터 표는 DW1000을 제어하기 위한 레지스터 표 로써, 총 0x00 ~ 0x3F 총 64개 입니다. 이 레지스터의 ID를 Transaction Header에 담아 DW1000에게 Read 혹은 Write 명령을 내리게 됩니다.

3.2 Single Octet Header

이제 헤더가 어떻게 생겼는지 알아 보도록 하겠습니다.

MSB는 Read 인지 혹은 Write인지를 나타내는 비트 6번째는 서브 인덱스의 존재를 나타내는 비트 나머지 비트는 모두 레지스터의 ID를 나타내는 비트 입니다. XX00 0000 ~ XX11 1111 까지 레지스터 표에서 언급했던 갯수와 같습니다.

예를 들어 보겠습니다. 마스터에서 슬레이브로 0x00 이라는 바이트를 송신했다면 0x00 은 0000 0000으로 해석되며 "0x00 번지 레지스터를 읽어으며 서브 인덱스는 없음" 으로 해석 됩니다. 이렇게 송신하게 되면 DW1000 에서는 0x31, 0x01, 0xCA, 0xDE 라는 일련의 바이트를 차례로 전송해 줍니다.

3.3 Two Octet Header

Single Octet Header 를 알아 봤으니 이제 서브 인덱스가 있는 Two Octet Header 에 대해 간단히 살펴보겠습니다.

Two Octet Header의 포맷입니다. 첫번째 헤더의 6번째 비트는 무조건 1로 세팅되어야 합니다. 두번째 헤더의 첫번째 비트는 서브 인덱스의 확장 주소여부를 묻는 포멧입니다. 만약 더 긴 주소가 필요하다면 1로 세팅되어야 합니다.

다시 예시를 보도록 하겠습니다. 0x40, 0x02는 0100 0000 0000 0010의 일련의 비트로 해석할 수 있습니다. 해석해 보자면... " 0x00 레지스터에대한 읽기 명령 이며 서브 인덱스가 있다. 서브 인덱스 주소는 0x02 부터 이다."로 해석될 수 있습니다. 이렇게 되면 레지스터의 3번째와 4번째 주소를 읽어 값을 반환해 줍니다.

3.4 Three Octet Header

마지막 Three Octet Header 입니다. 서브 인덱스로도 다 표현이 안되는 긴 인덱스를 가진 명령들을 위해 쓰입니다.

Three Octet Header의 포맷을 보면 마지막은 오직 주소로만 이용됩니다. 하지만 읽는 방법이 약간 다릅니다.

일련의 바이트 0xC9, 0xB6, 0x02, 0xA5 입니다. 비트 시퀀스로 해석하면 아래와 👇 같습니다.

1100 1001 1011 0110 0000 0010 1010 0101

해석하는 법은 다음과 같습니다.

요약하자면 2번째 헤더의 하위 7개 비트는 서브 인덱스의 하위 주소로 가고 3번째 헤더는 모조리 상위 주소로 간다는 이야기 입니다. 위 헤더는 "레지스터 ID 0x09의 서브 인덱스 0x136번지에 쓰기연산, 내용은 0xA5" 정도로 해석할 수 있겠습니다.

생각보다 DW1000 SPI에 대해 많은걸 썻네요.. 딱 SPI 내용만 적은 건디...🤔🤔 물론 SPI 자체에 대한 꼭 필요한 지식은 아니며 DW1000을 쓸때만 필요한 포맷입니다. 하지만 개발일지 (5) 에서 코딩할 내용을 생각하면 아예 필요없는건 아니니까... 🤣

0개의 댓글