STM Урок 121. Датчик температуры, давления и влажности BME280. Часть 3
В предыдущей части урока мы начали писать инициализацию датчика, в которой считали идентификатор, калибровочные данные из датчика, также настроили тайминг режима Standby.
Теперь оверсемплинг и фильтры.
Я просмотрел несколько примеров, а также много чего позавидовал из библиотек Arduino, и решил использовать подобные настройки.
Начнём с фильтра. Коэффициент фильтрации настраивается также с помощью соответствующих битов конфигурационного регистра

Перейдём в заголовочный файл BME280.h и добавим маску и настройки для битов коэффициента фильтрации
#define BME280_STBY_20 0xE0
//------------------------------------------------
#define BME280_FILTER_MSK 0x1C
#define BME280_FILTER_OFF 0x00
#define BME280_FILTER_2 0x04
#define BME280_FILTER_4 0x08
#define BME280_FILTER_8 0x0C
#define BME280_FILTER_16 0x10
Вернёмся в файл BME280.c и добавим функцию настройки коэффициента фильтрации после функции BME280_SetStandby
//------------------------------------------------
void BME280_SetFilter(uint8_t filter) {
uint8_t reg;
reg = BME280_ReadReg(BME280_REG_CONFIG) & ~BME280_FILTER_MSK;
reg |= filter & BME280_FILTER_MSK;
BME280_WriteReg(BME280_REG_CONFIG,reg);
}
//------------------------------------------------
Вызовем данную функцию в функции инициализации датчика, настроив средний коэффициент фильтрации показаний
BME280_SetStandby(BME280_STBY_1000);
BME280_SetFilter(BME280_FILTER_4);
Далее оверсемплинги, которые настраиваются отдельно для каждого вида измерений.
Настройки оверсемплингов (передискретизации) хранятся в двух регистрах.
В одном регистре — ctrl_meas — хранятся настройки оверсемплингов температуры и давления

Вот это настройки оверсемплинга показаний температуры

А вот настройки оверсемплинга показаний давления

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

Вот сами настройки

Перейдём в заголовочный файл BME280.h и добавим там макросы для этих регистров
#define BME280_SOFTRESET_VALUE 0xB6 //BME280 SOFT RESET VALUE
#define BME280_REG_CTRL_HUM 0xF2 // Humidity measure control register
#define BME280_REGISTER_STATUS 0XF3 //BME280 STATUS REGISTER
#define BME280_REG_CTRL_MEAS 0xF4 // Control register pressure and temperature measure
Также добавим макросы для масок и настроек битов оверсемплингов
#define BME280_FILTER_16 0x10
//------------------------------------------------
#define BME280_OSRS_T_MSK 0xE0
#define BME280_OSRS_T_SKIP 0x00
#define BME280_OSRS_T_x1 0x20
#define BME280_OSRS_T_x2 0x40
#define BME280_OSRS_T_x4 0x60
#define BME280_OSRS_T_x8 0x80
#define BME280_OSRS_T_x16 0xA0
#define BME280_OSRS_P_MSK 0x1C
#define BME280_OSRS_P_SKIP 0x00
#define BME280_OSRS_P_x1 0x04
#define BME280_OSRS_P_x2 0x08
#define BME280_OSRS_P_x4 0x0C
#define BME280_OSRS_P_x8 0x10
#define BME280_OSRS_P_x16 0x14
#define BME280_OSRS_H_MSK 0x07
#define BME280_OSRS_H_SKIP 0x00
#define BME280_OSRS_H_x1 0x01
#define BME280_OSRS_H_x2 0x02
#define BME280_OSRS_H_x4 0x03
#define BME280_OSRS_H_x8 0x04
#define BME280_OSRS_H_x16 0x05
Вернёмся в файл BME280.c и добавим функции настройки оверсемплингов для различных показаний после функции BME280_SetFilter
//------------------------------------------------
void BME280_SetOversamplingTemper(uint8_t osrs)
{
uint8_t reg;
reg = BME280_ReadReg(BME280_REG_CTRL_MEAS) & ~BME280_OSRS_T_MSK;
reg |= osrs & BME280_OSRS_T_MSK;
BME280_WriteReg(BME280_REG_CTRL_MEAS,reg);
}
//------------------------------------------------
void BME280_SetOversamplingPressure(uint8_t osrs)
{
uint8_t reg;
reg = BME280_ReadReg(BME280_REG_CTRL_MEAS) & ~BME280_OSRS_P_MSK;
reg |= osrs & BME280_OSRS_P_MSK;
BME280_WriteReg(BME280_REG_CTRL_MEAS,reg);
}
//------------------------------------------------
void BME280_SetOversamplingHum(uint8_t osrs)
{
uint8_t reg;
reg = BME280_ReadReg(BME280_REG_CTRL_HUM) & ~BME280_OSRS_H_MSK;
reg |= osrs & BME280_OSRS_H_MSK;
BME280_WriteReg(BME280_REG_CTRL_HUM,reg);
//The 'ctrl_hum' register needs to be written
//after changing 'ctrl_hum' for the changes to become effwctive.
reg = BME280_ReadReg(BME280_REG_CTRL_MEAS);
BME280_WriteReg(BME280_REG_CTRL_MEAS,reg);
}
//------------------------------------------------
Я думаю, вы заметили, что после занесения значений оверсемплинга влажности в соответствующий регистр мы считали и заново записали данные регистра F4. Мы это сделали потому, что существует такое требование, гласящее о том, что изменения регистра оверсемплинга влажности станут актуальными только после записи регистра F4, о чём мы и сообщили в комментарии
![]()
Настроим оверсемплинги, вызвав данные функции с желаемыми настройками в функции инициализации
BME280_SetFilter(BME280_FILTER_4);
BME280_SetOversamplingTemper(BME280_OSRS_T_x4);
BME280_SetOversamplingPressure(BME280_OSRS_P_x2);
BME280_SetOversamplingHum(BME280_OSRS_H_x1);
Считаем настройки и посмотрим их в терминальной программе
BME280_SetOversamplingHum(BME280_OSRS_H_x1);
value32 = BME280_ReadReg(BME280_REG_CTRL_MEAS);
value32 |= BME280_ReadReg(BME280_REG_CTRL_HUM) << 8;
sprintf(str1, "Measurements status: %04Xrn", value32);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
sprintf(str1, "Temperature: %srnPressure: %srnHumidity: %srn",
(value32 & BME280_OSRS_T_MSK) ? "ON" : "OFF",
(value32 & BME280_OSRS_P_MSK) ? "ON" : "OFF",
((value32 >> 8) & BME280_OSRS_H_MSK) ? "ON" : "OFF");
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
Соберём код, прошьём контроллер и посмотрим, какие настройки у нас отобразятся в терминальной программе

Осталось для полной инициализации датчика нам только включить режим NORMAL.
Как мы помним, режимы настраиваются в определённых битах регистра ctrl_meas

Перейдём в заголовочный файл BME280.h и добавим там макросы для маски и настроек битов настройки режима работы
#define BME280_OSRS_H_x16 0x05
//------------------------------------------------
#define BME280_MODE_MSK 0x03
#define BME280_MODE_SLEEP 0x00
#define BME280_MODE_FORCED 0x01
#define BME280_MODE_NORMAL 0x03
//------------------------------------------------
Вернёмся в файл BME280.c и добавим функцию установки текущего режима после функции BME280_SetOversamplingHum
//------------------------------------------------
void BME280_SetMode(uint8_t mode) {
uint8_t reg;
reg = BME280_ReadReg(BME280_REG_CTRL_MEAS) & ~BME280_MODE_MSK;
reg |= mode & BME280_MODE_MSK;
BME280_WriteReg(BME280_REG_CTRL_MEAS,reg);
}
//------------------------------------------------
Включим режим NORMAL в функции инициализации датчика
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
BME280_SetMode(BME280_MODE_NORMAL);
}
И по закрывающей скобке тела функции мы видим, что мы наконец-то завершили инициализацию датчика.
Осталась самая малость — считать показания величин. Ну ничего. Благодаря примерам кода в технической документации, мы всё же, надеюсь, с этим справимся.
Над функцией инициализации добавим пока заготовки функций считывания различных величин, измеряемых нашим датчиком
//------------------------------------------------
float BME280_ReadTemperature(void)
{
float temper_float = 0.0f;
return temper_float;
}
//------------------------------------------------
float BME280_ReadPressure(void)
{
float press_float = 0.0f;
return press_float;
}
//------------------------------------------------
float BME280_ReadHumidity(void)
{
float hum_float = 0.0f;
return hum_float;
}
//------------------------------------------------
float BME280_ReadAltitude(float seaLevel)
{
float att = 0.0f;
return att;
}
//------------------------------------------------
Добавим для данных функций прототипы в заголовочном файле.
Также в заголовочном файле BME280.h и добавим макросы для регистров различных показаний датчика
#define BME280_REG_CONFIG 0xF5 // Configuration register
#define BME280_REGISTER_PRESSUREDATA 0xF7
#define BME280_REGISTER_TEMPDATA 0xFA
#define BME280_REGISTER_HUMIDDATA 0xFD
Начнём с температуры.
Первоначальное «сырое» значение температуры непосредственно с сенсора после обработки блоком ADC записывается в следующий регистр

Получается, что показания температуры у нас записываются в 20 бит.
Также мы видим, что байты в регистрах расположены в обратной последовательности — старшим байтом вперёд, поэтому придётся делать переворот.
Давайте пока просто считаем данный регистр без переворота.
Для этого вернёмся в файл BME280.c и добавим функцию считывания 24-битного регистра после функции BME280_ReadReg_S16
//------------------------------------------------
void BME280_ReadReg_U24(uint8_t Reg, uint32_t *Value)
{
I2Cx_ReadData24(BME280_ADDRESS,Reg,Value);
*(uint32_t *) Value &= 0x00FFFFFF;
}
//------------------------------------------------
Так как у нас не бывает 24-битных типов, то мы используем 32-битный, соответственно, поэтому мы лишний старший байт очищаем.
Прочитаем регистр значений температуры в теле функции BME280_ReadTemperature
float temper_float = 0.0f;
uint32_t temper_raw;
BME280_ReadReg_U24(BME280_REGISTER_TEMPDATA,&temper_raw);
В функции main() файла main.c добавим локальные переменные для хранения данных различных показателей датчика
/* USER CODE BEGIN 1 */
float tf = 0.0f, pf = 0.0f, af = 0.0f, hf = 0.0f;
/* USER CODE END 1 */
Перейдём в бесконечный цикл и вызовем функцию чтения температуры, после чего добавим задержку на 1000 милисекунд, что и предусмотрено установленным нами таймингом режима STANDBY после чтения показаний датчика
/* USER CODE BEGIN 3 */
tf = BME280_ReadTemperature();
sprintf(str1, "Temperature: %.3f *Crn", tf);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
HAL_Delay(1000);
}
Конечно, в терминале мы пока никаких показаний не увидим, так как они ещё не рассчитаны, но в программе логического анализа мы посмотрим, какие байты мы прочитали из регистра значений температуры. Для этого соберём код и прошьём контроллер.
Вот наши принятые байты

Байты считываются, это уже хорошо.
Теперь посмотрим, как они записываются в переменную. Для этого вернёмся в файл BME280.c в функцию BME280_ReadTemperature и отобразим там сырое значение температуры в терминальной программе
BME280_ReadReg_U24(BME280_REGISTER_TEMPDATA,&temper_raw);
sprintf(str1, "Temperature RAW: 0x%08Xrn", temper_raw);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
Соберем код, прошьём контроллер и посмотрим наши данные

По изменениям данных мы видим, что они у нас перевёрнуты, то есть меняется старший байт, не может же температура скакать на порядок. Ну, в принципе, мы так и ожидали, так как байты в регистре и расположены наоборот, судя по даташиту, старшим байтом вперёд (формат big endian). Поэтому нам надо как-то перевернуть всё это дело. Мы с таким явлением уже встречались, когда программировали микросхему ENC28J60.
Перейдём в заголовочный файл BME280.h и добавим макросы перестановки байтов, заодно также и для 16-битных величин, они тоже нам будут нужны
#define LED_TGL HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_PIN)
//------------------------------------------------
#define be16toword(a) ((((a)>>8)&0xff)|(((a)<<8)&0xff00))
#define be24toword(a) ((((a)>>16)&0x000000ff)|((a)&0x0000ff00)|(((a)<<16)&0x00ff0000))
Вернёмся в файл BME280.c и добавим функцию считывания 24-битного регистра в формате Big Endian после функции BME280_ReadReg_U24
//------------------------------------------------
void BME280_ReadReg_BE_U24(uint8_t Reg, uint32_t *Value)
{
I2Cx_ReadData24(BME280_ADDRESS,Reg,Value);
*(uint32_t *) Value = be24toword(*(uint32_t *) Value) & 0x00FFFFFF;
}
//------------------------------------------------
Вернёмся в функцию считывания и обработки температуры BME280_ReadTemperature и поменяем там функцию
BME280_ReadReg_BE_U24(BME280_REGISTER_TEMPDATA,&temper_raw);
Соберём код, прошьём контроллер и посмотрим результат теперь

Совсем другое дело!
Теперь сдвинем всё это на 4 бита вправо, так как именно так у нас хранится сырое значение температуры и четыре самых младших бита не участвуют в показаниях
BME280_ReadReg_BE_U24(BME280_REGISTER_TEMPDATA,&temper_raw);
temper_raw >>= 4;
Соберём код, прошьём контроллер и посмотрим теперь результат

Всё отлично передвинулось.
Теперь добавим глобальную переменную для временного хранения показаний температуры
BME280_CalibData CalibData;
int32_t temper_int;
Вернёмся в функцию BME280_ReadTemperature и добавим ещё некоторые локальные переменные для промежуточных расчётов
uint32_t temper_raw;
int32_t val1, val2;
Затем, следуя примеру из даташита, посчитаем промежуточное значение температуры, пока без использования плавающей точки
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
val1 = ((((temper_raw>>3) - ((int32_t)CalibData.dig_T1 <<1))) *
((int32_t)CalibData.dig_T2)) >> 11;
val2 = (((((temper_raw>>4) - ((int32_t)CalibData.dig_T1)) *
((temper_raw>>4) - ((int32_t)CalibData.dig_T1))) >> 12) *
((int32_t)CalibData.dig_T3)) >> 14;
temper_int = val1 + val2;
А теперь посчитаем показание с плавающей точкой, опять же следуя рекомендациям даташита
temper_int = val1 + val2;
temper_float = ((temper_int * 5 + 128) >> 8);
temper_float /= 100.0f;
Соберём код, прошьём контроллер и посмотрим результат работы функции в терминальной программе

Ну, раз уж так у нас всё хорошо прочиталось, то можно вывод сырых показаний на терминал удалить
sprintf(str1, "Temperature RAW: 0x%08Xrn", temper_raw);
HAL_UART_Transmit(&huart2,(uint8_t*)str1,strlen(str1),0x1000);
В следующей части урока мы научимся считывать с датчика атмосферное давление и влажность воздуха и затем подключим дисплей LCD2004 для удобства мониторинга показаний.
Отладочную плату можно приобрести здесь Nucleo STM32F401RE
Логический анализатор 16 каналов можно приобрести здесь
Датчик температуры, давления и влажности BME280 можно приобрести здесь BME280
Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)



Добрый день.
Можете прояснить, почему у меня не отрабатывает в процедуре закомментированная строка?
float BME280_ReadTemperature(void)
{
float temper_float = 0.0f;
uint32_t temper_raw;
int32_t val1, val2;
BME280_ReadReg_BE_U24(BME280_REGISTER_TEMPDATA,&temper_raw);
temper_raw >>= 4;
val1 = ((((temper_raw>>3) — ((int32_t)CalibData.dig_T1 <> 11;
val2 = (((((temper_raw>>4) — ((int32_t)CalibData.dig_T1)) *
((temper_raw>>4) — ((int32_t)CalibData.dig_T1))) >> 12) *
((int32_t)CalibData.dig_T3)) >> 14;
temper_int = val1 + val2;
temper_float = ((temper_int * 5 + 128) >> 8);
//temper_float = temper_float/100.0f;
return temper_float;
}
т.е. и при отсутствии комментирующих слэшей и с ними результат в терминале температура умноженная на 100. Причем в main.c напротив вызова этой функции стоит предупреждение: implicit declaration of function 'BME280_ReadTemperature' is invalid in C99.
Если в настройках во вкладке С/С++ убрать галочку с «C99 mode», то компилируется вообще с кучей ошибок..
Евгений.
Добрый день.
implicit declaration — это когда не находится прототип функции.
Добрый день. Ну вобщем то мне это на электрониксе объяснили и действительно декларирование этой функции чудесным образом помогло. А как же у вас ваш код отрабатывает без её декларирования? Я же кодом из вашего урока полностью пользуюсь
И ещё одна странность у меня выявилась.
Подцепил датчик на кабель длиной 10 метров, в помещении вроде бы всё норм, показывает довольно точно температуру, вынес за окно и вот что имеем:
Temperature: 1.810 *C
Pressure: 1022383.813 Pa; 1022.384 hPa; 766.851 mmHg
Altitude: -75.769 m
Humidity: 27.641 %
Temperature: 1.490 *C
Pressure: 1022411.688 Pa; 1022.412 hPa; 766.872 mmHg
Altitude: -76.000 m
Humidity: 28.019 %
Temperature: 1.150 *C
Pressure: 1022431.063 Pa; 1022.431 hPa; 766.886 mmHg
Altitude: -76.160 m
Humidity: 28.752 %
Temperature: 0.470 *C
Pressure: 1022432.188 Pa; 1022.432 hPa; 766.887 mmHg
Altitude: -76.170 m
Humidity: 29.136 %
Temperature: 0.110 *C
Pressure: 1022446.563 Pa; 1022.447 hPa; 766.898 mmHg
Altitude: -76.288 m
Humidity: 29.575 %
Temperature: 409.340 *C
Pressure: 1540857.625 Pa; 1540.858 hPa; 1155.738 mmHg
Altitude: -3681.034 m
Humidity: 0.000 %
Temperature: 408.950 *C
Pressure: 1541868.000 Pa; 1541.868 hPa; 1156.496 mmHg
Altitude: -3687.024 m
Humidity: 0.263 %
Temperature: 408.600 *C
Pressure: 1542814.125 Pa; 1542.814 hPa; 1157.206 mmHg
Altitude: -3692.630 m
Humidity: 1.371 %
Temperature: 408.270 *C
Pressure: 1543696.125 Pa; 1543.696 hPa; 1157.867 mmHg
Altitude: -3697.853 m
Humidity: 2.366 %
Temperature: 407.940 *C
Pressure: 1544553.250 Pa; 1544.553 hPa; 1158.510 mmHg
Altitude: -3702.927 m
Humidity: 3.341 %
т.е. когда температура упала в минус, попёрли странные цифры 400 градусов.
Вы не пробовали измерять отрицательную температуру?
Вот занёс в тепло обратно и видно, что когда нагрелся до 0+ цифры стали нормальными:
Temperature: 409.240 *C
Pressure: 1541205.500 Pa; 1541.205 hPa; 1155.999 mmHg
Altitude: -3683.097 m
Humidity: 42.121 %
Temperature: 409.450 *C
Pressure: 1540698.375 Pa; 1540.698 hPa; 1155.619 mmHg
Altitude: -3680.091 m
Humidity: 41.276 %
Temperature: 0.040 *C
Pressure: 1022530.563 Pa; 1022.531 hPa; 766.961 mmHg
Altitude: -76.982 m
Humidity: 50.817 %
Temperature: 0.230 *C
Pressure: 1022504.500 Pa; 1022.505 hPa; 766.941 mmHg
Altitude: -76.766 m
Humidity: 50.436 %
Temperature: 0.420 *C
Pressure: 1022497.750 Pa; 1022.498 hPa; 766.936 mmHg
Altitude: -76.711 m
Humidity: 50.062 %
Попробуйте изменить строку uint32_t temper_raw; на int temper_raw;
Только меня смущает, что давление в Па раз эдак в 10 выше нормального?
Функция расчёта val2 написана неправильно. В ней участвует беззнаковая переменная temper_raw в результате переменная полученная из CalibData.dig_T3 теряет знак. Надо хотя бы так:
val2 = (int32_t)(((((temper_raw>>4) — ((int32_t)CalibData.dig_T1)) *
((temper_raw>>4) — ((int32_t)CalibData.dig_T1))) >> 12) *
((int32_t)CalibData.dig_T3)) >> 14;
Или так:
val2 = (int32_t)(((((temper_raw>>4) — CalibData.dig_T1) *
((temper_raw>>4) — CalibData.dig_T1)) >> 12) * CalibData.dig_T3) >> 14;
Для корректного отображения отрицательных температур нужно изменить тип temper_raw на знаковый — int32_t и добавить беззнаковую переменную — uint32_t temper_raw_unsigned.
//В выражении:
BME280_ReadReg_BE_U24(BME280_REGISTER_TEMPDATA,&temper_raw);
//подставить — temper_raw_unsigned, как и требует прототип функции, т.е. будет:
BME280_ReadReg_BE_U24(BME280_REGISTER_TEMPDATA,&temper_raw_unsigned);
//а далее сразу же привести полученное значение к знаковому типу
temper_raw=(int32_t)temper_raw_add;
//и будет вам счастье)
Спасибо!
Dobriy Vecher Narod Stream,
Ya poluchayu vot takie vot dannie
1835.00 *C vmesto 18.35 *C. v chem moya oshibka?
Spasibo