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

nRF52 BLE 개발하기 - twi_sensor(I2C)

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

시작하기

저번 글에서 BME280를 사용하여 TWI(I2C) 인터페이스를 동작시켜보았다.

이번에는 온도, 습도, 대기압 측정값을 읽어와 출력할 수 있도록 기존의 twi_sensor 예제를 수정해보자.

먼저 \examples\peripheral\twi_sensor\pca10040\blank\ses 폴더에서 프로젝트를 실행시키자.

기존의 twi_sensor 예제는 LM75B 온도 센서를 사용한 소스코드이므로 BME280 데이터시트를 보면서 수정을 했다.

우선 main 함수는 기존 예제와 비슷하다.

twi_init 함수를 호출해 TWI 0을 활성화하고 bme280_init 함수를 호출해 BME280을 설정한다.

1초마다 bme280_data 함수를 호출해 온도, 습도, 대기압 값을 bme280_result에 저장한다.

예제에서 twi_rx 함수로 수신을 받고 twi_tx 함수로 송신을 하도록 소스코드를 작성했다.

/* TWI instance ID. */
#define TWI_INSTANCE_ID     0

/* 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;

/* Indicates if operation on TWI has ended. */
static volatile bool m_xfer_done = false;

/* TWI instance. */
static const nrf_drv_twi_t m_twi = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID);

int main(void)
{
    APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
    NRF_LOG_DEFAULT_BACKENDS_INIT();

    twi_init();
    bme280_init();
    nrf_delay_ms(500);

    while (true)
    {
        nrf_delay_ms(1000);

        do
        {
            __WFE();
        }while (m_xfer_done == false);

        bme280_data(bme280_result); 
        NRF_LOG_FLUSH();
        
    }
}
static ret_code_t twi_rx(uint8_t address, uint8_t resister, uint8_t *p_data, uint8_t length)
{
    ret_code_t err_code;

    m_xfer_done = false;
    err_code = nrf_drv_twi_tx(&m_twi, address, &resister, sizeof(resister), true);
    APP_ERROR_CHECK(err_code);

    while (m_xfer_done == false);

    if (err_code == NRF_SUCCESS)
    {
        m_xfer_done = false;
        err_code = nrf_drv_twi_rx(&m_twi, address, p_data, length);
        APP_ERROR_CHECK(err_code);
        
        while (m_xfer_done == false);
    }

    return err_code;
}

static ret_code_t twi_tx(uint8_t address, uint8_t resister, uint8_t p_data, uint8_t length)
{
    ret_code_t err_code;
    uint8_t buffer[2];
    
    buffer[0] = resister;
    buffer[1] = p_data;

    m_xfer_done = false;
    err_code = nrf_drv_twi_tx(&m_twi, address, buffer, length, true);
    APP_ERROR_CHECK(err_code);

    while (m_xfer_done == false);

    return err_code;
}

bme280_init 함수를 살펴보자.

twi_rx 함수의 전달 인자로 BME280_ADDRESS(0x77), BME280_GET_ID(0xD0), 수신받을 변수, 길이를 넘겨준다.

BME280이 정상적인 상태라면 BME280_ID(0x60)를 수신받을 수 있다.

bme280_configure 함수를 호출해 BME280을 설정한다.

static void bme280_init(void)
{
    ret_code_t err_code;
    uint8_t value;
    
    err_code = twi_rx(BME280_ADDRESS, 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();
}

BME280 데이터시트를 보면 다양한 환경에 따른 추천 설정 정보를 제공해주고 있다.

그중 여기서는 실내에 적합한 설정값을 적용하도록 한다.

BME280은 트리밍 파라미터라고 불리는 보정값이 내부에 저장되어 있다.

이 보정값을 읽어와 온도, 습도, 대기압 값에 적용해서 최종적으로 측정값이 된다.

이 값을 dig_H, dig_T, dig_P 변수에 저장한다.

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 = twi_tx(BME280_ADDRESS, BME280_CTRL_HUM, data, sizeof(data));
    APP_ERROR_CHECK(err_code);
    
    data = Tosr << 5 | Posr << 2 | Mode;
    err_code = twi_tx(BME280_ADDRESS, BME280_CTRL_MEAS, data, sizeof(data));
    APP_ERROR_CHECK(err_code);

    data = SBy << 5 | IIRFilter << 2;
    err_code = twi_tx(BME280_ADDRESS, BME280_CONFIG, data, sizeof(data));
    APP_ERROR_CHECK(err_code);

    err_code = twi_rx(BME280_ADDRESS, 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 = twi_rx(BME280_ADDRESS, 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];
}

BME280 설정이 끝나고 나면 bme280_data 함수를 호출해 측정값을 읽어올 수 있다.

bme280_data 함수를 살펴보도록 하자.

twi_rx 함수의 전달 인자로 BME280_ADDRESS(0x77), BME280_PRESS_MSB(0xF7), 수신받을 변수, 수신받을 개수로 8을 넘겨준다.

이렇게 하면 대기압(3개), 온도(3개), 습도(2개)의 로우 데이터를 수신받을 수 있다.

수신받은 로우 데이터 값을 각 온도, 습도, 대기압 측정값으로 변환하기 위해 bme280_t, bme280_h, bme280_p 함수를 호출한다.

로우 데이터를 측정값으로 변환하는 계산식은 데이터시트를 참고하였다.

이렇게 BME280을 TWI(I2C) 인터페이스를 이용해 온도, 습도, 대기압 측정값을 읽어와 출력해줄 수 있다.

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 = twi_rx(BME280_ADDRESS, 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;
}

BME280 전체 소스코드는 다음과 같다.

/* TWI instance ID. */
#define TWI_INSTANCE_ID     0

/* 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;

/* Indicates if operation on TWI has ended. */
static volatile bool m_xfer_done = false;

/* TWI instance. */
static const nrf_drv_twi_t m_twi = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID);

static ret_code_t twi_rx(uint8_t address, uint8_t resister, uint8_t *p_data, uint8_t length)
{
    ret_code_t err_code;

    m_xfer_done = false;
    err_code = nrf_drv_twi_tx(&m_twi, address, &resister, sizeof(resister), true);
    APP_ERROR_CHECK(err_code);

    while (m_xfer_done == false);

    if (err_code == NRF_SUCCESS)
    {
        m_xfer_done = false;
        err_code = nrf_drv_twi_rx(&m_twi, address, p_data, length);
        APP_ERROR_CHECK(err_code);
        
        while (m_xfer_done == false);
    }

    return err_code;
}

static ret_code_t twi_tx(uint8_t address, uint8_t resister, uint8_t p_data, uint8_t length)
{
    ret_code_t err_code;
    uint8_t buffer[2];
    
    buffer[0] = resister;
    buffer[1] = p_data;

    m_xfer_done = false;
    err_code = nrf_drv_twi_tx(&m_twi, address, buffer, length, true);
    APP_ERROR_CHECK(err_code);

    while (m_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 = twi_rx(BME280_ADDRESS, 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 = twi_tx(BME280_ADDRESS, BME280_CTRL_HUM, data, sizeof(data));
    APP_ERROR_CHECK(err_code);
    
    data = Tosr << 5 | Posr << 2 | Mode;
    err_code = twi_tx(BME280_ADDRESS, BME280_CTRL_MEAS, data, sizeof(data));
    APP_ERROR_CHECK(err_code);

    data = SBy << 5 | IIRFilter << 2;
    err_code = twi_tx(BME280_ADDRESS, BME280_CONFIG, data, sizeof(data));
    APP_ERROR_CHECK(err_code);

    err_code = twi_rx(BME280_ADDRESS, 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 = twi_rx(BME280_ADDRESS, 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 = twi_rx(BME280_ADDRESS, 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 Function for handling data from sensor.
 */
__STATIC_INLINE void data_handler(int32_t *p_data)
{
    NRF_LOG_INFO("Pressure : %d Temperature : %d Humidity : %d", p_data[0], p_data[1], p_data[2]);
}

/**
 * @brief TWI events handler.
 */
void twi_handler(nrf_drv_twi_evt_t const * p_event, void * p_context)
{
    switch (p_event->type)
    {
        case NRF_DRV_TWI_EVT_DONE:
            if (p_event->xfer_desc.type == NRF_DRV_TWI_XFER_RX)
            {   
                if(bme280_done == true)
                {
                    data_handler(bme280_result);
                    bme280_done = false;
                }
            }
            m_xfer_done = true;
            break;
        default:
            break;
    }
}

/**
 * @brief UART initialization.
 */
void twi_init (void)
{
    ret_code_t err_code;

    const nrf_drv_twi_config_t bme280_config = {
       .scl                = ARDUINO_SCL_PIN,
       .sda                = ARDUINO_SDA_PIN,
       .frequency          = NRF_DRV_TWI_FREQ_100K,
       .interrupt_priority = APP_IRQ_PRIORITY_HIGH,
       .clear_bus_init     = false
    };

    err_code = nrf_drv_twi_init(&m_twi, &bme280_config, twi_handler, NULL);
    APP_ERROR_CHECK(err_code);

    nrf_drv_twi_enable(&m_twi);
}

/**
 * @brief Function for reading data from temperature sensor.
 */
static void read_sensor_data()
{
    m_xfer_done = false;
}

/**
 * @brief Function for main application entry.
 */
int main(void)
{
    APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
    NRF_LOG_DEFAULT_BACKENDS_INIT();

    twi_init();
    bme280_init();
    nrf_delay_ms(500);

    while (true)
    {
        nrf_delay_ms(1000);

        do
        {
            __WFE();
        }while (m_xfer_done == false);

        bme280_data(bme280_result); 
        NRF_LOG_FLUSH();
        
    }
}

앞으로

다음에는 SPI 인터페이스를 사용하여 BME280을 다뤄보도록 하겠다.

 

 
 

 

 

반응형

댓글