Меню Закрыть

I2C Синхронный последовательный интерфейс.

Схема соединения.

Двухпроводный интерфейс:

  • SCL (Serial Clock) – линия тактирования.
  • SDA (Serial Data) – линия данных.

Каждое устройство соединяет свои выводы с одноименными выводами других устройств. На одной шине может быть до 127 устройств.Подтягивающие резисторы обязательны для работы устройства.

Максимальная длина шины ограничена, обычно 1–2 метра, иначе сигналы искажаются.

I2C может работать на разных скоростях:

  • Standard (100 kHz)
  • Fast (400 kHz)
  • Fast Plus (1 MHz)
  • High Speed (3.4 MHz)

Передача информации и формат кадра.

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

В исходном состоянии обе линии SDA и SCL находятся в высоком состоянии.

Процедура обмена данными начинается с формирования ведущим устройством состояния СТАРТ

  • Начало передачи.

SCL 1.

SDA становится 0.

  • Адресация

Чтобы понять к какому из слейвов обращается мастер, необходимо передать байт адресации. Состоит из первых семи бит ( адрес слейва ) + бит чтения/записи. После отправки адресного байта по шине, каждое устройство в системе сравнивает первые семь бит после сигнала СТАРТ со своим адресом. Если адреса совпали, устройство считает себя выбранным как ведомый-приёмник или как ведомый-передатчик, в зависимости от состояния бита направления.Далее слейв тянет SDA в 0, подтверждая прием байта.

  • Передача данных

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

  • Окончание передачи

SDA становится 1

SCL 1

Изображение передачи данных

Мастер START — address — слейв ACK — мастер DATA — слейв ACK — мастер DATA — слейв ACK — мастер STOP.

ACK (Acknowledgment) — сигнал, что байт данных принят(ноль на SDA). Вместо ACK может быть NACK (Not Acknowledged) — сигнал, что байт данных не принят(единица на SDA).

Арбитраж.

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

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

Второй случай когда два мастера одновременно хотят начать передачу.В I2C используется принцип «открытого коллектора»: если один мастер отпускает линию в «1», а другой тянет её в «0», на шине всегда остаётся «0». Благодаря этому при одновременной передаче выигрывает тот мастер, чьи данные совпадают с состоянием линии, а тот, кто хотел передать «1», но увидел «0», понимает, что проиграл арбитраж, прекращает передачу и ждёт освобождения шины.

Основные группы регистров I2C (на примере STM32).

Control Registers (CR1, CR2)

  • CR1 – включает периферию, управляет ACK, генерирует START/STOP.
  • CR2 – задаёт частоту тактирования, настраивает прерывания.

 Clock Configuration Register (CCR)

  • Определяет, будет ли скорость Standard (100 кГц) или Fast (400 кГц).
  • Содержит значение делителя тактовой частоты.

 TRISE

  • Настраивает максимальное время фронта сигнала (rise time), чтобы всё соответствовало стандарту I2C.

SR1 и SR2 (Status Registers)

  • SR1 – показывает события: START сгенерирован, адрес передан, байт принят, ошибка (NACK, overrun и др.).
  • SR2 – текущее состояние: занят/свободен, мастер/слейв, адресное совпадение.

DR (Data Register)

  • Через него мы пишем байт для отправки или читаем принятый байт.

 OAR1, OAR2 (Own Address Register)

  • Содержат собственный адрес устройства, если оно работает как Slave.

Пример I2C.

Для примера использования I2C возьмём датчик температуры и давления BMP280.

Получим температуру и выведем в консоль Linux.

Сначала в CubeIDE настроим I2C1.  Выставим следующие настройки.

Схема соединения следующая

BMP280 — STM32

SDA — PB7

SCL — PB6

VCC — 3.3V

GND — GND

CSB — подтянуть к VCC (выбор I2C)

SDO — GND

Подключим переходник UART/USB и включим minicom в консоли. Подробно как это сделать можно почитать в этой статье https://beartronix.com/ru/pl2303hx-usb-uart

На линиях SCL и SDA обязательны подтягивающие резисторы. В нашем случае они установлены на модуле.

Код программы.

Зададим ключевые параметры

#define RX_BUF_SIZE 32
#define BMP280_ADDR (0x76 << 1)
#define BMP280_REG_ID 0xD0
#define BMP280_REG_RESET 0xE0
#define BMP280_REG_CTRL_MEAS 0xF4
#define BMP280_REG_CONFIG 0xF5
#define BMP280_REG_TEMP_MSB 0xFA

BMP280_ADDR — адрес датчика на шине I2C. Если вывод SDO подключен к GND, используется адрес 0x76; при подключении к VCC адрес меняется на 0x77. Сдвиг на 1 бит нужен для работы с HAL, который ожидает 8-битный адрес.

BMP280_REG_ID — регистра идентификатора датчика, позволяет убедиться, что устройство отвечает корректно.

BMP280_REG_RESET — регистр программного сброса, используется для инициализации датчика.

BMP280_REG_CTRL_MEAS — регистр управления измерениями температуры и давления, здесь настраиваются режим работы и точность.

BMP280_REG_CONFIG — регистр конфигурации, в котором задаются фильтры и интервалы standby.

BMP280_REG_TEMP_MSB — адрес старшего байта регистра с сырыми данными температуры, который читается для последующей компенсации.

Далее необходимо написать функции инициализации датчика и конвертации температуры. Зададим необходимые прототипы функций

/* USER CODE BEGIN PFP */
void bmp280_init(void);
int32_t bmp280_read_temp_raw(void);
uint8_t bmp280_read_reg(uint8_t reg);
void bmp280_write_reg(uint8_t reg, uint8_t value);
void bmp280_read_calib(void);
float bmp280_compensate_temp(int32_t adc_T);
/* USER CODE END PFP */

Напишем функции

/* USER CODE BEGIN 4 */
uint8_t bmp280_read_reg(uint8_t reg)
{
    uint8_t value;
    HAL_I2C_Mem_Read(&hi2c1, BMP280_ADDR, reg, 1, &value, 1, HAL_MAX_DELAY);
    return value;
}

void bmp280_write_reg(uint8_t reg, uint8_t value)
{
    HAL_I2C_Mem_Write(&hi2c1, BMP280_ADDR, reg, 1, &value, 1, HAL_MAX_DELAY);
}

void bmp280_init(void)
{
    uint8_t id = bmp280_read_reg(BMP280_REG_ID);
    // Сброс
    bmp280_write_reg(BMP280_REG_RESET, 0xB6);
    HAL_Delay(100);
    // Настройка: температура + давление, нормальный режим
    bmp280_write_reg(BMP280_REG_CTRL_MEAS, 0x27); // Normal mode
    bmp280_write_reg(BMP280_REG_CONFIG, 0xA0);
}

int32_t bmp280_read_temp_raw(void)
{
    uint8_t data[3];
    HAL_I2C_Mem_Read(&hi2c1, BMP280_ADDR, BMP280_REG_TEMP_MSB, 1, data, 3, HAL_MAX_DELAY);
    int32_t raw = ((int32_t)data[0] << 12) | ((int32_t)data[1] << 4) | (data[2] >> 4);
    return raw;
}

void bmp280_read_calib(void)
{
    uint8_t data[6];
    HAL_I2C_Mem_Read(&hi2c1, BMP280_ADDR, 0x88, 1, data, 6, HAL_MAX_DELAY);
    dig_T1 = (uint16_t)(data[0] | (data[1] << 8));
    dig_T2 = (int16_t)(data[2] | (data[3] << 8));
    dig_T3 = (int16_t)(data[4] | (data[5] << 8));
}

float bmp280_compensate_temp(int32_t adc_T)
{
    int32_t var1, var2;
    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;
    float T = (t_fine * 5 + 128) >> 8;
    return T / 100.0f;
}
/* USER CODE END 4 */

В этом разделе реализованы основные функции работы с датчиком BMP280. Функции bmp280_read_reg и bmp280_write_reg обеспечивают низкоуровневое чтение и запись регистров датчика по I2C. bmp280_init выполняет инициализацию датчика, включая сброс и настройку режимов измерений. bmp280_read_temp_raw считывает необработанные данные температуры, а bmp280_read_calib загружает калибровочные коэффициенты для точного расчёта. Наконец, bmp280_compensate_temp корректирует сырые данные с учётом калибровки и возвращает температуру в градусах Цельсия. 

В функции main  вызовем наши функции инициализации и калибровки

bmp280_init();
bmp280_read_calib();

Считываем температуру

int32_t raw_temp = bmp280_read_temp_raw();

Переведем с учетом таблиц компенсации

float temp_C = bmp280_compensate_temp(raw_temp);

Для удобства вывода разделим полученное значение на целую и дробную части

int32_t temp100 = (int32_t)(temp_C * 100); // температура *100
int32_t t_int = temp100 / 100; // целая часть
int32_t t_frac = temp100 % 100; // дробная часть

И выведем в консоль

char buf[32];
int len = snprintf(buf, sizeof(buf), "Temperature: %ld.%02ld C\r\n", t_int, t_frac);
HAL_UART_Transmit(&huart3, (uint8_t*)buf, len, HAL_MAX_DELAY);

Получим следующее

В статье показано, как работать с датчиком BMP280 на STM32 через I2C: от инициализации и чтения регистров до калибровки и вычисления температуры. Мы изучили, как работает шина I2C, рассмотрели схему соединения и формат передаваемых кадров. Приведенный подход позволяет получать точные данные температуры и является основой для расширения функционала, например, измерения давления или записи данных в реальном времени.

 

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

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