В предыдущей части урока мы познакомились с данным датчиком, изучили его характеристики, способы подключения, его свойства, а также создали и настроили проект для его программирования.
В файле 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 добавим функцию считывания калибровочных ячеек и распределения их по полям структуры
//------------------------------------------------
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
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добрый день.
Проделал все действия по этому уроку до момента считывания ID датчика. При сборке проекта появляется много ошибок, но начну с первой:
../Src/main.c(67): error: #130: expected a «{»
I2C_HandleTypeDef hi2c1;
Такое ощущение, что что-то не так в настройках компилятора, которые я тронул глядя на ваше видео, ибо до этого проект собирался когда это объявление переменной уже было. Где искать проблему?
Заново собрал конфигурацию в кубе, собрал проект в МДК — всё норм. Что-то было действительно с настройками компилятора. Сорри. Можно удалить мои вопросы.
Обязательно в настройках i2c в Кубе включить Dual Address Asknowledged иначе работать не будет.