본문 바로가기
Embedded/nRF52 BLE 개발 안내서

nRF52 BLE 개발하기 - spi

by 큐찡 2021. 1. 23.
반응형

시작하기

저번 글에서 TWI(I2C) 인터페이스를 활용하여 BME280을 사용하는 방법에 대해서 알아보았다.

이번에는 TWI(I2C)와 더불어 많이 사용되는 SPI 인터페이스에 대해서 알아보도록 하자.

먼저 \examples\peripheral\spi\pca10040\blank\ses 폴더에서 프로젝트를 실행시켜 기본 예제를 먼저 살펴보자.

#define SPI_INSTANCE  0 /**< SPI instance index. */
static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE);  /**< SPI instance. */
static volatile bool spi_xfer_done;  /**< Flag used to indicate that SPI instance completed the transfer. */

#define TEST_STRING "Nordic"
static uint8_t       m_tx_buf[] = TEST_STRING;           /**< TX buffer. */
static uint8_t       m_rx_buf[sizeof(TEST_STRING) + 1];    /**< RX buffer. */
static const uint8_t m_length = sizeof(m_tx_buf);        /**< Transfer length. */

/**
 * @brief SPI user event handler.
 * @param event
 */
void spi_event_handler(nrf_drv_spi_evt_t const * p_event,
                       void *                    p_context)
{
    spi_xfer_done = true;
    NRF_LOG_INFO("Transfer completed.");
    if (m_rx_buf[0] != 0)
    {
        NRF_LOG_INFO(" Received:");
        NRF_LOG_HEXDUMP_INFO(m_rx_buf, strlen((const char *)m_rx_buf));
    }
}

int main(void)
{
    bsp_board_init(BSP_INIT_LEDS);

    APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
    NRF_LOG_DEFAULT_BACKENDS_INIT();

    nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
    spi_config.ss_pin   = SPI_SS_PIN;
    spi_config.miso_pin = SPI_MISO_PIN;
    spi_config.mosi_pin = SPI_MOSI_PIN;
    spi_config.sck_pin  = SPI_SCK_PIN;
    APP_ERROR_CHECK(nrf_drv_spi_init(&spi, &spi_config, spi_event_handler, NULL));

    NRF_LOG_INFO("SPI example started.");

    while (1)
    {
        // Reset rx buffer and transfer done flag
        memset(m_rx_buf, 0, m_length);
        spi_xfer_done = false;

        APP_ERROR_CHECK(nrf_drv_spi_transfer(&spi, m_tx_buf, m_length, m_rx_buf, m_length));

        while (!spi_xfer_done)
        {
            __WFE();
        }

        NRF_LOG_FLUSH();

        bsp_board_led_invert(BSP_BOARD_LED_0);
        nrf_delay_ms(200);
    }
}

nrf_drv_spi_init 함수로 SPI 인터페이스 설정을 하고 SPI 0 사용할 수 있도록 활성화시킨다.

nrf_drv_spi_transfer 함수의 전달 인자 m_tx_buf 데이터를 송신하고 m_rx_buf에 데이터를 수신한다.

송신 및 수신이 성공했다면 spi_event_handler 함수가 호출되고 송신 완료, 수신 데이터 출력을 수행한다.

200ms 마다 SPI 송신이 완료되면 LED1을 토글 하는 간단한 예제이다.

이제 BME280을 SPI 인터페이스로 동작시키기 위해서 사진과 같이 BME280과 nRF52832 DK를 연결해서 SPI 인터페이스를 사용할 수 있도록 하자.

소스코드를 보면 SPI 인터페이스를 사용하는 부분을 제외하고 나머지 BME280 관련 함수들은 TWI(I2C)와 동일하게 동작한다.

#define SPI_INSTANCE  0 /**< SPI instance index. */
static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE);  /**< SPI instance. */
static volatile bool spi_xfer_done;  /**< Flag used to indicate that SPI instance completed the transfer. */

/* Common addresses definition for bme280 sensor. */
#define BME280_ADDRESS      0x77
#define BME280_GET_ID       0xD0
#define BME280_ID           0x60
#define BME280_RESET        0xE0
#define BME280_CTRL_HUM     0xF2
#define BME280_CTRL_MEAS    0xF4
#define BME280_CONFIG       0xF5
#define BME280_CALIB00      0x88
#define BME280_CALIB26      0xE1
#define BME280_PRESS_MSB    0xF7
#define BME280_PRESS_LSB    0xF8
#define BME280_PRESS_XLSB   0xF9
#define BME280_TEMP_MSB     0xFA
#define BME280_TEMP_LSB     0xFB
#define BME280_TEMP_XLSB    0xFC
#define BME280_HUM_MSB      0xFD
#define BME280_HUM_LSB      0xFE

enum Posr {P_OSR_00 = 0, P_OSR_01, P_OSR_02, P_OSR_04, P_OSR_08, P_OSR_16};
enum Hosr {H_OSR_00 = 0, H_OSR_01, H_OSR_02, H_OSR_04, H_OSR_08, H_OSR_16};
enum Tosr {T_OSR_00 = 0, T_OSR_01, T_OSR_02, T_OSR_04, T_OSR_08, T_OSR_16};
enum IIRFilter {IIR_OFF = 0, IIR_02, IIR_04, IIR_08, IIR_16};
enum Mode {BME280Sleep = 0, forced, forced2, normal};
enum SBy {t_00_5ms = 0, t_62_5ms, t_125ms, t_250ms, t_500ms, t_1000ms, t_10ms, t_20ms};

uint8_t calib26[26];
uint8_t calib7[7];

uint8_t  dig_H1, dig_H3, dig_H6;
uint16_t dig_T1, dig_P1, dig_H4, dig_H5;
int16_t  dig_T2, dig_T3, dig_P2, dig_P3, dig_P4, dig_P5, dig_P6, dig_P7, dig_P8, dig_P9, dig_H2;

int32_t bme280_result[3];
static volatile bool bme280_done = false;

static ret_code_t spi_rx(uint8_t resister, uint8_t *p_data, uint8_t length)
{
    ret_code_t err_code;
    uint8_t temp[length];
    uint8_t rxd[2];
    
    rxd[0] = resister;
    rxd[1] = 0x00;

    spi_xfer_done = false;
    err_code = nrf_drv_spi_transfer(&spi, rxd, sizeof(rxd), temp, length + 1);
    APP_ERROR_CHECK(err_code);

    while (spi_xfer_done == false);

    memcpy(p_data, &temp[1], length);

    return err_code;
}

static ret_code_t spi_tx(uint8_t resister, uint8_t p_data, uint8_t length)
{
    ret_code_t err_code;
    uint8_t temp[length];
    uint8_t txd[2];
    
    txd[0] = resister;
    txd[1] = p_data;

    spi_xfer_done = false;
    err_code = nrf_drv_spi_transfer(&spi, txd, sizeof(txd), temp, length + 1);
    APP_ERROR_CHECK(err_code);

    while (spi_xfer_done == false);

    return err_code;
}

// Output value of “96386” equals 96386 Pa = 963.86 hPa
static uint32_t bme280_p(int32_t adc_P, int32_t t_fine) 
{
    int32_t varP1, varP2;
    uint32_t P;

    varP1 = (t_fine >> 1) - (int32_t)64000;
    varP2 = (((varP1 >> 2) * (varP1 >> 2)) >> 11 ) * ((int32_t)dig_P6);
    varP2 = varP2 + ((varP1 * ((int32_t)dig_P5)) << 1);
    varP2 = (varP2 >> 2) + (((int32_t)dig_P4) << 16);
    varP1 = (((dig_P3 * (((varP1 >> 2) * (varP1 >> 2)) >> 13 )) >> 3) + ((((int32_t)dig_P2) * varP1) >> 1)) >> 18; 
    varP1 = ((((32768 + varP1)) * ((int32_t)dig_P1)) >> 15);

    if (varP1 == 0) 
    {
        return 0; // avoid exception caused by division by zer
    }

    P = (((uint32_t)(((int32_t)1048576) - adc_P) - (varP2 >> 12))) * 3125; 

    if (P < 0x80000000)
    {
        P = (P << 1) / ((uint32_t)varP1); 
    }
    else
    {
        P = (P / (uint32_t)varP1) * 2;
    }

    varP1 = (((int32_t)dig_P9) * ((int32_t)(((P >> 3) * (P >> 3)) >> 13))) >> 12; 
    varP2 = (((int32_t)(P >> 2)) * ((int32_t)dig_P8)) >> 13;
    P = (uint32_t)((int32_t)P + ((varP1 + varP2 + dig_P7) >> 4));

    return P;
}

// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC.
static int32_t bme280_t(int32_t t_fine)
{
  int32_t T;

  T = (t_fine * 5 + 128) >> 8;

  return T;
}

// Output value of “47445” represents 47445/1024 = 46.333 %
static uint32_t bme280_h(int32_t adc_H, int32_t t_fine)
{
  int32_t varH;

  varH = (t_fine - ((int32_t)76800));
  varH = (((((adc_H << 14) - (((int32_t)dig_H4) << 20) - (((int32_t)dig_H5) * varH)) +
    ((int32_t)16384)) >> 15) * (((((((varH * ((int32_t)dig_H6)) >> 10) * (((varH * ((int32_t)dig_H3)) >> 11) + 
    ((int32_t)32768))) >> 10) + ((int32_t)2097152)) * ((int32_t)dig_H2) + 8192) >> 14));
  varH = (varH - (((((varH >> 15) * (varH >> 15)) >> 7) * ((int32_t)dig_H1)) >> 4));
  varH = (varH < 0 ? 0 : varH); 
  varH = (varH > 419430400 ? 419430400 : varH);

  return(uint32_t)(varH >> 12);
}

static void bme280_data(int32_t *p_data)
{
    ret_code_t err_code;
    uint8_t value[8];
    int32_t result[3];
    int32_t var1, var2, t_fine, adc_T;

    err_code = spi_rx(BME280_PRESS_MSB, value, sizeof(value));
    APP_ERROR_CHECK(err_code);

    result[0] = (uint32_t)(((uint32_t)value[0] << 16 | (uint32_t)value[1] << 8 | value[2]) >> 4);
    result[1] = (uint32_t)(((uint32_t)value[3] << 16 | (uint32_t)value[4] << 8 | value[5]) >> 4);
    result[2] = (uint16_t)(((uint16_t)value[6] << 8 | value[7]));

    adc_T = result[1];

    var1 = ((((adc_T >> 3) - ((int32_t)dig_T1 << 1))) * ((int32_t)dig_T2)) >> 11;
    var2 = (((((adc_T >> 4) - ((int32_t)dig_T1)) * ((adc_T >> 4) - ((int32_t)dig_T1))) >> 12) * ((int32_t)dig_T3)) >> 14;
  
    t_fine = var1 + var2;

    p_data[0] = bme280_p(result[0], t_fine);
    p_data[1] = bme280_t(t_fine);
    p_data[2] = bme280_h(result[2], t_fine);

    bme280_done = true;
}

static void bme280_configure(void)
{
    ret_code_t err_code;
    uint8_t data;
    uint8_t Posr = P_OSR_16, Hosr = H_OSR_01, Tosr = T_OSR_02, Mode = normal, IIRFilter = IIR_16, SBy = t_00_5ms;
    
    data = 0x07 & Hosr;
    err_code = spi_tx(BME280_CTRL_HUM, data, sizeof(data));
    APP_ERROR_CHECK(err_code);
    
    data = Tosr << 5 | Posr << 2 | Mode;
    err_code = spi_tx(BME280_CTRL_MEAS, data, sizeof(data));
    APP_ERROR_CHECK(err_code);

    data = SBy << 5 | IIRFilter << 2;
    err_code = spi_tx(BME280_CONFIG, data, sizeof(data));
    APP_ERROR_CHECK(err_code);
    
    err_code = spi_rx(BME280_CALIB00, calib26, sizeof(calib26));
    APP_ERROR_CHECK(err_code);

    dig_T1 = (uint16_t)(((uint16_t) calib26[1] << 8) | calib26[0]);
    dig_T2 = (int16_t)(((int16_t) calib26[3] << 8) | calib26[2]);
    dig_T3 = (int16_t)(((int16_t) calib26[5] << 8) | calib26[4]);
    dig_P1 = (uint16_t)(((uint16_t) calib26[7] << 8) | calib26[6]);
    dig_P2 = (int16_t)(((int16_t) calib26[9] << 8) | calib26[8]);
    dig_P3 = (int16_t)(((int16_t) calib26[11] << 8) | calib26[10]);
    dig_P4 = (int16_t)(((int16_t) calib26[13] << 8) | calib26[12]);
    dig_P5 = (int16_t)(((int16_t) calib26[15] << 8) | calib26[14]);
    dig_P6 = (int16_t)(((int16_t) calib26[17] << 8) | calib26[16]);
    dig_P7 = (int16_t)(((int16_t) calib26[19] << 8) | calib26[18]);
    dig_P8 = (int16_t)(((int16_t) calib26[21] << 8) | calib26[20]);
    dig_P9 = (int16_t)(((int16_t) calib26[23] << 8) | calib26[22]);
    dig_H1 = calib26[25];

    err_code = spi_rx(BME280_CALIB26, calib7, sizeof(calib7));
    APP_ERROR_CHECK(err_code);

    dig_H2 = (int16_t)(((int16_t) calib7[1] << 8) | calib7[0]);
    dig_H3 = calib7[2];
    dig_H4 = (int16_t)((((int16_t) calib7[3] << 8) | (0x0F & calib7[4]) << 4) >> 4);
    dig_H5 = (int16_t)((((int16_t) calib7[5] << 8) | (0xF0 & calib7[4]) ) >> 4 );
    dig_H6 = calib7[6];
}

static void bme280_init(void)
{
    ret_code_t err_code;
    uint8_t value;
    
    err_code = spi_rx(BME280_GET_ID, &value, sizeof(value));
    APP_ERROR_CHECK(err_code);

    if (value == BME280_ID)
    {
        NRF_LOG_INFO("bme280 init success, id=0x%02x", value);
        bme280_configure();
        nrf_delay_ms(100);
    }
    else
    {
        NRF_LOG_INFO("bme280 init fail");
    }

    NRF_LOG_FLUSH();
}

/**
 * @brief SPI user event handler.
 * @param event
 */
void spi_event_handler(nrf_drv_spi_evt_t const * p_event,
                       void *                    p_context)
{
    switch(p_event->type)
    {
        case NRF_DRV_SPI_EVENT_DONE:
            if(bme280_done == true)
            {
                bme280_done = false;
                NRF_LOG_INFO("Pressure : %d Temperature : %d Humidity : %d", bme280_result[0], bme280_result[1], bme280_result[2]);
            }
            spi_xfer_done = true;
            break;
        default:
            break;
    }
}

int main(void)
{
    bsp_board_init(BSP_INIT_LEDS);

    APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
    NRF_LOG_DEFAULT_BACKENDS_INIT();

    nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
    spi_config.ss_pin   = SPI_SS_PIN;
    spi_config.miso_pin = SPI_MISO_PIN;
    spi_config.mosi_pin = SPI_MOSI_PIN;
    spi_config.sck_pin  = SPI_SCK_PIN;
    APP_ERROR_CHECK(nrf_drv_spi_init(&spi, &spi_config, spi_event_handler, NULL));

    bme280_init();

    NRF_LOG_INFO("SPI example started.");   

    while (1)
    {
        nrf_delay_ms(1000);

        do
        {
            __WFE();
        }while (spi_xfer_done == false);
        
        bme280_data(bme280_result);
        NRF_LOG_FLUSH();
    }
}

앞으로

임베디드 센서 사용에 많이 쓰이는 TWI(I2C), SPI 인터페이스에 대해서 BME280을 이용해서 알아보았다.

다음에는 UART 인터페이스에 대해서 알아보도록 하자.

반응형

댓글