STM Урок 121. Датчик температуры, давления и влажности BME280. Часть 2

 

 

 

В предыдущей части урока мы познакомились с данным датчиком, изучили его характеристики, способы подключения, его свойства, а также создали и настроили проект для его программирования.

 

В файле BME280.h добавим макросы для светодиода

 

#include <math.h>

//------------------------------------------------

#define LED_GPIO_PORT GPIOA

#define LED_PIN GPIO_PIN_5

#define LED_ON HAL_GPIO_WritePin(LED_GPIO_PORT, LED_PIN, GPIO_PIN_SET)

#define LED_OFF HAL_GPIO_WritePin(LED_GPIO_PORT, LED_PIN, GPIO_PIN_RESET)

#define LED_TGL HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_PIN)

//------------------------------------------------

 

Перейдём в файл BME280.c и добавим функцию ошибки

 

extern char str1[100];

//------------------------------------------------

void Error(void)

{

  LED_OFF;

}

//------------------------------------------------

 

Так как управляющий светодиод у нас на плате только один, то при ошибке мы его будем гасить.

После данной функции добавим функцию инициализации датчика, в которой мы сначала зажжём наш светодиод

 

//------------------------------------------------

void BME280_Init(void)

{

  uint8_t value=0;

  uint32_t value32=0;

  LED_ON;

}

//------------------------------------------------

 

Также после функции ошибки добавим функции записи и чтения по определённому адресу байта для шины I2C

 

//------------------------------------------------

static void I2Cx_WriteData(uint16_t Addr, uint8_t Reg, uint8_t Value)

{

  HAL_StatusTypeDef status = HAL_OK;

  status = HAL_I2C_Mem_Write(&hi2c1, Addr, (uint16_t)Reg, I2C_MEMADD_SIZE_8BIT, &Value, 1, 0x10000);

  if(status != HAL_OK) Error();

}

//------------------------------------------------

static uint8_t I2Cx_ReadData(uint16_t Addr, uint8_t Reg)

{

  HAL_StatusTypeDef status = HAL_OK;

  uint8_t value = 0;

  status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, &value, 1, 0x10000);

  if(status != HAL_OK) Error();

  return value;

}

//------------------------------------------------

 

Такими функциями мы пользуемся постоянно, поэтому они в объяснении не нуждаются.

Ниже добавим также ещё две подобные функции для записи двухбайтовой и трехбайтовой величин

 

//------------------------------------------------

static void I2Cx_ReadData16(uint16_t Addr, uint8_t Reg, uint16_t *Value)

{

  HAL_StatusTypeDef status = HAL_OK;

  status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, (uint8_t*)Value, 2, 0x10000);

  if(status != HAL_OK) Error();

}

//------------------------------------------------

static void I2Cx_ReadData24(uint16_t Addr, uint8_t Reg, uint32_t *Value)

{

  HAL_StatusTypeDef status = HAL_OK;

  status = HAL_I2C_Mem_Read(&hi2c1, Addr, Reg, I2C_MEMADD_SIZE_8BIT, (uint8_t*)Value, 3, 0x10000);

  if(status != HAL_OK) Error();

}

//------------------------------------------------

 

Теперь перейдём в заголовочный файл BME280.h и напишем макрос для адреса нашего датчика, по которому он будет откликаться на команды записи и чтения, предварительно узнав его в технической документации

 

 

Как мы уже, надеюсь, все знаем, адрес мы пишем сдвинутый влево на 1 позицию

 

#define LED_TGL HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_PIN)

//------------------------------------------------

#define BME280_ADDRESS 0xEC //BME280 I2C ADDRES (0x76<<1)

//------------------------------------------------

 

Вернёмся в файл BME.h и после функции I2Cx_ReadData24 добавим функцию записи байта в регистр

 

//------------------------------------------------

void BME280_WriteReg(uint8_t Reg, uint8_t Value)

{

  I2Cx_WriteData(BME280_ADDRESS, Reg, Value);

}

//------------------------------------------------

 

Подобные функции мы добавляем для того, чтобы каждый раз не вводить при вызове адрес устройства.

Добавим аналогичную функцию для чтения байта из регистра

 

//------------------------------------------------

uint8_t BME280_ReadReg(uint8_t Reg)

{

  uint8_t res = I2Cx_ReadData(BME280_ADDRESS,Reg);

  return res;

}

//------------------------------------------------

 

Ну и, по традиции, прочитаем идентификатор устройства, чтобы удостовериться, что у нас именно такое устройство, и что оно нормально обменивается данными по шине I2C.

Найдём адрес регистра с идентификатором в документации, заодно там же и увидим сам идентификатор

 

 

Зайдём в файл BME280.h и напишем макросы для регистра и значения ID

 

#define BME280_ADDRESS 0xEC //BME280 I2C ADDRES (0x76<<1)

#define BME280_REG_ID 0xD0 //BME280 ID REGISTER

#define BME280_ID 0x60 //BME280 I2C ID

 

Вернёмся в файл BME280.c и в функции инициализации попытаемся считать идентификатор с датчика

 

LED_ON;

value = BME280_ReadReg(BME280_REG_ID);

 

Создадим прототип на функцию инициализации датчика и вызовем её в функции main() файла main.c

 

/* USER CODE BEGIN 2 */

BME280_Init();

/* USER CODE END 2 */

 

Соберём проект и прошьём контроллер.

Мы видим, что светодиод по-прежнему светится, значит ошибки не возникло, это уже хорошо

 

 

Запустим программу логического анализа и настроим шину I2C

 

 

Также настроим срабатывание канала 0 по положительному фронту

 

 

Нажмём на «Start» и перезагрузим контроллер. Получим следующее

 

 

Отлично! У нас вернулся правильный ID.

Вернёмся в файл BME280.c в функцию инициализации и напишем проверку правильности считывания, не всегда же нам логическим анализом заниматься. Заодно отобразим наш ID в терминальной программе

 

value = BME280_ReadReg(BME280_REG_ID);

sprintf(str1, "rnrnID: 0x%02Xrn", value);

HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

if(value !=BME280_ID)

{

  Error();

  return;

}

 

Теперь после сборки кода и прошивки контроллера мы должны наблюдать вот это в терминальной программе

 

 

Дальше мы должны произвести программную перезагрузку датчика, для этого существует вот такой регистр

 

 

Чтобы перезагрузить датчик, мы должны по данному адресу занести строго определённое число — 0xB6.

Поэтому перейдём в заголовочный файл BME280.h и добавим там соответствующие макросы

 

#define BME280_ID 0x60 //BME280 I2C ID

#define BME280_REG_SOFTRESET 0xE0 //BME280 SOFT RESET REGISTER

#define BME280_SOFTRESET_VALUE 0xB6 //BME280 SOFT RESET VALUE

 

Вернёмся в файл BME280.c в функцию инициализации и перезагрузим наш датчик, а также соответствующим образом дождёмся его перезагрузки

 

  return;

}

BME280_WriteReg(BME280_REG_SOFTRESET,BME280_SOFTRESET_VALUE);

 

Дальше у нас два пути, чтобы дождаться, когда датчик перезагрузится. Либо подождать какое-то время с запасом, либо что-то отследить. Пойдём по второму пути. Он более профессиональный. Мы считаем регистр статуса и узнаем, готовы ли мы дальше работать с нашим датчиком.

Обратимся для этого к документации

 

 

Мы видим, что в данном регистре всего 2 рабочих бита. Нас сейчас интересует второй.

Перейдём в заголовочный файл BME280.h и добавим там соответствующие макросы

 

#define BME280_SOFTRESET_VALUE 0xB6 //BME280 SOFT RESET VALUE

#define BME280_REGISTER_STATUS 0XF3 //BME280 STATUS REGISTER

#define BME280_STATUS_MEASURING 0X08 //Running conversion

#define BME280_STATUS_IM_UPDATE 0X01 //NVM data copying

 

 

Вернёмся в файл BME280.c и добавим функцию считывания статуса над функцией инициализации

 

//------------------------------------------------

uint8_t BME280_ReadStatus(void)

{

  //clear unuset bits

  uint8_t res = BME280_ReadReg(BME280_REGISTER_STATUS)&0x09;

  return res;

}

//------------------------------------------------

 

Вместе со считыванием значения из регистра мы по маске обнуляем несуществующие биты

Теперь в функции инициализации дождёмся нужного состояния нулевого бита

 

BME280_WriteReg(BME280_REG_SOFTRESET,BME280_SOFTRESET_VALUE);

while (BME280_ReadStatus() & BME280_STATUS_IM_UPDATE) ;

 

Дальше нам необходимо считать калибровочные ячейки и распределить их по переменным, чтобы в дальнейшем использовать для коррекции показаний датчика. Как это делается, прописано в примере кода в технической документации на датчик. Поэтому сильно в подробности ячеек мы вдаваться не будем.

Перейдём в заголовочный файл BME280.h и добавим там структуру для калибровочных значений

 

#define BME280_STATUS_IM_UPDATE 0X01 //NVM data copying

//------------------------------------------------

typedef struct

{

  uint16_t dig_T1;

  int16_t dig_T2;

  int16_t dig_T3;

  uint16_t dig_P1;

  int16_t dig_P2;

  int16_t dig_P3;

  int16_t dig_P4;

  int16_t dig_P5;

  int16_t dig_P6;

  int16_t dig_P7;

  int16_t dig_P8;

  int16_t dig_P9;

  uint8_t dig_H1;

  int16_t dig_H2;

  uint8_t dig_H3;

  int16_t dig_H4;

  int16_t dig_H5;

  int8_t dig_H6;

} BME280_CalibData;

//------------------------------------------------

 

Все калибровочные значения различных типов, поэтому нам понадобятся функции для чтения различных типов данных из соответствующих регистров, мы впоследствии такие функции обязательно добавим.

Посмотрим эти регистры и как именно там хранятся данные

 

 

Данные в данных регистрах хранятся в правильном виде (младшим байтом вперёд), за редким исключением, поэтому менять байты нам не придётся.

Также в заголовочном файле добавим макросы данных регистров

 

#define BME280_STATUS_IM_UPDATE 0X01 //NVM data copying

//------------------------------------------------

#define BME280_REGISTER_DIG_T1 0x88

#define BME280_REGISTER_DIG_T2 0x8A

#define BME280_REGISTER_DIG_T3 0x8C

#define BME280_REGISTER_DIG_P1 0x8E

#define BME280_REGISTER_DIG_P2 0x90

#define BME280_REGISTER_DIG_P3 0x92

#define BME280_REGISTER_DIG_P4 0x94

#define BME280_REGISTER_DIG_P5 0x96

#define BME280_REGISTER_DIG_P6 0x98

#define BME280_REGISTER_DIG_P7 0x9A

#define BME280_REGISTER_DIG_P8 0x9C

#define BME280_REGISTER_DIG_P9 0x9E

#define BME280_REGISTER_DIG_H1 0xA1

#define BME280_REGISTER_DIG_H2 0xE1

#define BME280_REGISTER_DIG_H3 0xE3

#define BME280_REGISTER_DIG_H4 0xE4

#define BME280_REGISTER_DIG_H5 0xE5

#define BME280_REGISTER_DIG_H6 0xE7

//------------------------------------------------

 

Вернёмся в файл BME280.c и добавим глобальную переменную типа нашей структуры

 

extern char str1[100];

BME280_CalibData CalibData;

 

 

После функции BME280_ReadReg добавим две функции для считывания двухбайтовых значений из регистров датчика — одну для беззнаковых, другую для знаковых

 

//------------------------------------------------

void BME280_ReadReg_U16(uint8_t Reg, uint16_t *Value)

{

  I2Cx_ReadData16(BME280_ADDRESS,Reg,Value);

}

//------------------------------------------------

void BME280_ReadReg_S16(uint8_t Reg, int16_t *Value)

{

  I2Cx_ReadData16(BME280_ADDRESS,Reg, (uint16_t*) Value);

}

//------------------------------------------------

 

После функции считывания статуса BME280_ReadStatus добавим функцию считывания калибровочных ячеек и распределения их по полям структуры

 

BME280_ReadCoefficients

//------------------------------------------------

void BME280_ReadCoefficients(void)

{

  BME280_ReadReg_U16(BME280_REGISTER_DIG_T1,&CalibData.dig_T1);

  sprintf(str1, "DIG_T1: %urn", CalibData.dig_T1);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  BME280_ReadReg_S16(BME280_REGISTER_DIG_T2,&CalibData.dig_T2);

  sprintf(str1, "DIG_T2: %drn", CalibData.dig_T2);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  BME280_ReadReg_S16(BME280_REGISTER_DIG_T3,&CalibData.dig_T3);

  sprintf(str1, "DIG_T3: %drn", CalibData.dig_T3);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  BME280_ReadReg_U16(BME280_REGISTER_DIG_P1,&CalibData.dig_P1);

  sprintf(str1, "DIG_P1: %urn", CalibData.dig_P1);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  BME280_ReadReg_S16(BME280_REGISTER_DIG_P2,&CalibData.dig_P2);

  sprintf(str1, "DIG_P2: %drn", CalibData.dig_P2);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  BME280_ReadReg_S16(BME280_REGISTER_DIG_P3,&CalibData.dig_P3);

  sprintf(str1, "DIG_P3: %drn", CalibData.dig_P3);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  BME280_ReadReg_S16(BME280_REGISTER_DIG_P4,&CalibData.dig_P4);

  sprintf(str1, "DIG_P4: %drn", CalibData.dig_P4);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  BME280_ReadReg_S16(BME280_REGISTER_DIG_P5,&CalibData.dig_P5);

  sprintf(str1, "DIG_P5: %drn", CalibData.dig_P5);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  BME280_ReadReg_S16(BME280_REGISTER_DIG_P6,&CalibData.dig_P6);

  sprintf(str1, "DIG_P6: %drn", CalibData.dig_P6);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  BME280_ReadReg_S16(BME280_REGISTER_DIG_P7,&CalibData.dig_P7);

  sprintf(str1, "DIG_P7: %drn", CalibData.dig_P7);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  BME280_ReadReg_S16(BME280_REGISTER_DIG_P8,&CalibData.dig_P8);

  sprintf(str1, "DIG_P8: %drn", CalibData.dig_P8);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  BME280_ReadReg_S16(BME280_REGISTER_DIG_P9,&CalibData.dig_P9);

  sprintf(str1, "DIG_P9: %drn", CalibData.dig_P9);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  CalibData.dig_H1 = BME280_ReadReg(BME280_REGISTER_DIG_H1);

  sprintf(str1, "DIG_H1: %drn", CalibData.dig_H1);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  BME280_ReadReg_S16(BME280_REGISTER_DIG_H2,&CalibData.dig_H2);

  sprintf(str1, "DIG_H2: %drn", CalibData.dig_H2);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  CalibData.dig_H3 = BME280_ReadReg(BME280_REGISTER_DIG_H3);

  sprintf(str1, "DIG_H3: %drn", CalibData.dig_H3);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  CalibData.dig_H4 = (BME280_ReadReg(BME280_REGISTER_DIG_H4) << 4) | (BME280_ReadReg(BME280_REGISTER_DIG_H4+1) & 0xF);

  sprintf(str1, "DIG_H4: %drn", CalibData.dig_H4);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  CalibData.dig_H5 = (BME280_ReadReg(BME280_REGISTER_DIG_H5+1) << 4) | (BME280_ReadReg(BME280_REGISTER_DIG_H5) >> 4);

  sprintf(str1, "DIG_H5: %drn", CalibData.dig_H5);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

  CalibData.dig_H6 = (int8_t)BME280_ReadReg(BME280_REGISTER_DIG_H6);

  sprintf(str1, "DIG_H6: %drn", CalibData.dig_H3);

  HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);

}

//------------------------------------------------

 

Вызовем данную функцию в функции инициализации

 

while (BME280_ReadStatus() & BME280_STATUS_IM_UPDATE) ;

BME280_ReadCoefficients();

 

Соберём код, прошьём контроллер и в терминальной программе посмотрим считанные из датчика калибровочные значения

 

 

У вас, соответственно, будут другие значения.

Настроим время режима STANDBY.

Датчик переходит в данный режим после того, как произведёт измерения и находится в нём определённое время, которое мы сейчас и настроим с помощью включения определенных битов в регистре CONFIG.

Вот описание групп его битов

 

 

Первая группа (биты 7:5) — это настройки продолжительности неактивного режима (STANDBY) в нормальном режиме работы. Это и есть настройки нашего STANDBY.

Биты 4:2 — настройки таймингов IIR-фильтра.

Бит 0 — включение 3-проводного режима SPI.

 

Вот диаграмма работы датчика в нормальном режиме, в которой прекрасно видно, как датчик переходит в STANDBY и возвращается из него. Во время нахождения в данном состоянии мы при необходимости и считываем данные. Также здесь видно, в какое время какой ток потребляет датчик.

 

Поэтому, если нам не нужно часто считывать данные, то есть смысл для меньшего потребления установить данный тайминг в максимальное значение.

Посмотрим назначение битов конфигурационного регистра, отвечающих за этот тайминг

 

 

Перейдём в заголовочный файл BME280.h и добавим там макрос для конфигурационного регистра

 

#define BME280_STATUS_IM_UPDATE 0X01 //NVM data copying

#define BME280_REG_CONFIG 0xF5 // Configuration register

 

Также добавим маску и настройки для битов тайминга режима STANDBY

 

#define BME280_REGISTER_DIG_H6 0xE7

//------------------------------------------------

#define BME280_STBY_MSK 0xE0

#define BME280_STBY_0_5 0x00

#define BME280_STBY_62_5 0x20

#define BME280_STBY_125 0x40

#define BME280_STBY_250 0x60

#define BME280_STBY_500 0x80

#define BME280_STBY_1000 0xA0

#define BME280_STBY_10 0xC0

#define BME280_STBY_20 0xE0

 

Вернёмся в файл BME280.c и добавим функцию настройки тайминга режима STANDBY после функции BME280_ReadCoefficients

 

//------------------------------------------------

void BME280_SetStandby(uint8_t tsb) {

  uint8_t reg;

  reg = BME280_ReadReg(BME280_REG_CONFIG) & ~BME280_STBY_MSK;

  reg |= tsb & BME280_STBY_MSK;

  BME280_WriteReg(BME280_REG_CONFIG,reg);

}

//------------------------------------------------

 

Вызовем данную функцию в функции инициализации датчика, настроив максимальное время

 

BME280_ReadCoefficients();

BME280_SetStandby(BME280_STBY_1000);

 

В следующей части урока мы настроим фильтр и оверсемплинги, а также считаем с датчика значение температуры воздуха.

 

Предыдущая часть Программирование МК STM32 Следующая часть

 

 

Отладочную плату можно приобрести здесь Nucleo STM32F401RE

Логический анализатор 16 каналов можно приобрести здесь

Датчик температуры, давления и влажности BME280 можно приобрести здесь BME280

Дисплей LCD 20×4 можно приобрести тут: LCD 20×4

Переходник I2C to LCD1602 2004 можно приобрести здесь: I2C to LCD1602 2004

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM Name

2 комментария на “STM Урок 121. Датчик температуры, давления и влажности BME280. Часть 2
  1. Евгений:

    Добрый день.
    Проделал все действия по этому уроку до момента считывания ID датчика. При сборке проекта появляется много ошибок, но начну с первой:

    ../Src/main.c(67): error: #130: expected a «{»
    I2C_HandleTypeDef hi2c1;

    Такое ощущение, что что-то не так в настройках компилятора, которые я тронул глядя на ваше видео, ибо до этого проект собирался когда это объявление переменной уже было. Где искать проблему?

  2. Евгений:

    Заново собрал конфигурацию в кубе, собрал проект в МДК — всё норм. Что-то было действительно с настройками компилятора. Сорри. Можно удалить мои вопросы.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*