STM32 I2C DMA IRQ 방식

SHT3x-DIS 온습도 센서를 포팅할 때 I2C 폴링 방식으로 온습도 raw data를 읽어 처리하였지만, 폴링 방식을 사용할 경우 read 실행 후, 결과를 받는데 일정 시간이 소요된다.

 

STM32 SHT3x 온습도 센서 포팅

이전 포스트에서 포팅한 ads1115 모듈은 16비트의 4채널을 가진 ADC 모듈로 외부 온도센서 등을 연결하여 온도를 측정하는 방식이라면 SHT3x는 모듈에 온습도 센서를 포함하고 있어, 추가적인 온습도

memories.tistory.com

이것은 단일 스레드 환경에서 전체 시스템의 딜레이를 발생할 수 있어 인터럽트 방식으로 하고자 하였으나, 안타깝게도 Read irq Pin은 존재하지 않고, Alert irq Pin밖에 제공하지 않는다. 그래서 기능 검증을 위해 폴링 방식으로 사용하였으나, 약간의 여유가 생겨 이 부분을 dma irq 방식으로 변경하여 read시 발생하는 딜레이를 없앨 수 있다.

 

SHT3x-DIS 모듈은 초기화 과정에서 Write 명령어를 실행 후, 온습도 데이터는 Read 명령어만 실행하기 때문에 아래와 같이 Rx에 대해서만 DMA와 인터럽트를 등록하도록 한다.

I2C read / write 명령어는 아래와 같은 함수로 구성되며, 폴링 방식인 경우 timeout 값을 설정하여 IC로부터 응답을 받기까지 일정 시간이 소요된다.

uint32_t i2c_read(uint8_t dev_addr, uint16_t mem_addr, uint16_t mem_size, uint8_t* data, uint16_t size, uint32_t timeout)
{
  if (HAL_I2C_Mem_Read(&hi2c4, (uint16_t)(dev_addr << 1) | 0x01, mem_addr, mem_size, data, size, timeout) != HAL_OK) {
    return -1;
  }
  return 0;
}

uint32_t i2c_read_dma(uint8_t dev_addr, uint16_t mem_addr, uint16_t mem_size, uint8_t* data, uint16_t size)
{
  if (HAL_I2C_Mem_Read_DMA(&hi2c4, (uint16_t)(dev_addr << 1) | 0x01, mem_addr, mem_size, data, size) != HAL_OK) {
    return -1;
  }
  return 0;
}

uint32_t i2c_write(uint8_t dev_addr, uint16_t mem_addr, uint16_t mem_size, uint8_t* data, uint16_t size, uint32_t timeout)
{
  if (HAL_I2C_Mem_Write(&hi2c4, (uint16_t)(dev_addr << 1), mem_addr, mem_size, data, size, timeout) != HAL_OK) {
    return -1;
  }
  return 0;
}

void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef* hi2c)
{
  if (hi2c->Instance == I2C4) {
    sht3x_rx_callback();
  }
}

하지만, DMA 방식으로 IC로부터 결과 값을 받을 경우 일정 시간 대기 없이 바로 다른 액션을 취할 수 있다. 그렇다면 결과 값을 읽는 시점은 STM32에서 제공하는 HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c) 콜백함수를 이용하여 읽을 수 있다. 이 콜백은 요청한 데이터가 읽기가 완료되면 콜백함수가 실행된다.

 

read 명령어 실행 시, 읽는 데이터 변수를 지역 변수가 아닌 전역 변수를 사용하고 콜백 함수가 실행될 때 데이터의 무결성 검사와 온습도 데이터로 환산하면 폴링 방식과 다르게 전체 시스템에 딜레이 없이 처리할 수 있다.

static uint8_t buf[SHT3X_TEMPERHUMID_SIZE] = {0, };
static float temperature;
static float humidity;

uint32_t sht3x_rx_callback(void)
{
  uint8_t crc_temper = crc8(buf, SHT3X_MEM_SIZE);
  uint8_t crc_humid = crc8(&buf[3], SHT3X_MEM_SIZE);

  if (crc_temper != buf[2] || crc_humid != buf[5]) {
    printf("ERROR :: %s() crc error : temperature 0x%02x, expected(0x%02x), humidity 0x%02x, expected(0x%02x)\r\n", __func__, crc_temper, buf[2], crc_humid, buf[5]);
    return 1;
  }

  /** REF: SHT3x-DIS: Datasheelt 4.13 Conversion of Singed Output */
  temperature = -45.0f + (175.0f * ((uint16_t)((buf[0] << 8) | buf[1]) / 65535.0f));
  humidity = 100.0f * ((uint16_t)((buf[3] << 8) | buf[4]) / 65535.0f);

  return 0;
}

uint32_t sht3x_read_temperature_humidity(void)
{
  memset(buf, 0, SHT3X_TEMPERHUMID_SIZE);
  if (i2c_read_dma(I2C_ADDR_SHT3X, SHT3x_MEASURE_HIGHREP_STRETCH, SHT3X_MEM_SIZE, buf, SHT3X_TEMPERHUMID_SIZE) != 0) {
    return -1;
  }

  return 0;
}

폴링 타이아웃이 20ms가 큰 시간은 아니지만, 온습도이기 때문에 1초에 한번씩 갱신할 경우 다른 모듈에 영향을 미칠 수 있어 위와 같이 수정하여 전체 시스템에 영향 없이 처리할 수 있다. 500ms 단위로 읽어도 큰 문제 없는 것을 확인하였다.