Метеобокс на балконе вырос из одного датчика в три: BME280 на 0x76 отдаёт давление и влажность, BH1750 на 0x23 — освещённость, DS3231 на 0x68 — время для подписей в логе. Сканер показал три адреса и разрешил главный страх — конфликтов нет. Остался второй вопрос, который сканер не решает: как их опрашивать, чтобы каждый ходил в своём темпе и никто не ждал соседей в общем loop.
Пары модулей уже разобраны: LCD и RTC на одной шине — в уроке про LCD и RTC, карта регистров — в уроке про карту регистров, механика двухходовки — в уроке про requestFrom и регистры, финальная сборка с экраном — в уроке про итоговую метеостанцию. Здесь мой угол — маршрут: одна функция чтения на всех, три независимых таймера и расписание, в котором темп каждого датчика оправдан его физикой.
Два хода: регистр, потом чтение
Почти каждый I2C-датчик читается в два хода. Ход первый — запись: beginTransmission, один байт с номером регистра, endTransmission — так мастер говорит «начни с этой ячейки памяти». Ход второй — чтение: requestFrom на тот же адрес, и датчик отдаёт байты начиная с указанной ячейки. У BH1750 хода записи в цикле нет вовсе: один раз в setup ему отправляют команду 0x10 «меряй непрерывно», и дальше он просто выдаёт свежие два байта по запросу.
Темп — вторая половина маршрута. BME280 в forced-режиме меряет по команде — раз в секунду более чем достаточно для балкона. BH1750 измеряет сам, без остановки — читайте хоть каждые 120 мс, я держу 200. DS3231 тикает секунды сам — минутной отметки в логе хватает с избытком. Расписание собирается на millis: три независимых таймера, каждый сам решает, когда ему ходить, и лишних задержек в loop нет. Шина от такого графика даже не вспотеет: три чтения в секунду по 100 кГц — доли процента загрузки.
#include <Wire.h> #define BME 0x76#define LUX 0x23#define RTC 0x68 unsigned long tBME = 0, tLUX = 0, tRTC = 0; int readTwo(byte adr, byte reg) { // двухходовка: регистр + 2 байта Wire.beginTransmission(adr); Wire.write(reg); Wire.endTransmission(); Wire.requestFrom(adr, 2); if (Wire.available() < 2) return -1; return (Wire.read() << 8) | Wire.read();} void setup() { Wire.begin(); Serial.begin(9600); Wire.beginTransmission(LUX); // BH1750: раз включили - меряет сам Wire.write(0x10); // команда непрерывного замера Wire.endTransmission();} void loop() { // BH1750: свет - чаще всех, 200 мс if (millis() - tLUX >= 200) { tLUX = millis(); Wire.requestFrom(LUX, 2); // без регистра: выдаёт сам if (Wire.available() == 2) { int raw = (Wire.read() << 8) | Wire.read(); Serial.print("Lux: "); Serial.println((raw * 10) / 12); // /1.2 по даташиту, целыми } } // BME280: раз в секунду, сырые байты температуры (0xFA-0xFB) if (millis() - tBME >= 1000) { tBME = millis(); int raw = readTwo(BME, 0xFA); Serial.print("BME raw T: "); Serial.println(raw); } // DS3231: раз в минуту - часы из BCD-регистров 0x00-0x01 if (millis() - tRTC >= 60000) { tRTC = millis(); Wire.beginTransmission(RTC); Wire.write(0x00); // начнём с секунд Wire.endTransmission(); Wire.requestFrom(RTC, 2); if (Wire.available() == 2) { byte sec = Wire.read(); // секунды BCD - мимо byte m = Wire.read(); // минуты, BCD Serial.print("Minuty: "); Serial.println((m >> 4) * 10 + (m & 15)); } }}Разбор. readTwo — та же двухходовка, что у любого регистрового датчика: адрес, регистр, чтение двух байтов; -1 в возврате — ветка «датчик не ответил», её стоит обрабатывать на живой шине. BME280 здесь отдаёт сырые байты температуры из 0xFA-0xFB: полная работа с калибровочными коэффициентами и формулами — отдельный сюжет, он разобран в уроке про BME280. Часы DS3231 хранят значения в BCD — старший ниббл десятки, младший единицы, разворот в одну строку через сдвиг и маску.
Маршрут удобно читать как расписание поездов: свет ходит каждые 200 мс, BME — раз в секунду, часы — раз в минуту, и каждый поезд едет независимо. Если добавить четвёртый датчик, к маршруту добавится четвёртый таймер, а остальной код не шелохнётся — в этом смысл расписания на millis против общего delay. Полный кейс с экраном, усреднением и тревогами собран в уроке про итоговую метеостанцию — там же и ответ на вопрос, куда девать показания между чтениями.
| Датчик | Адрес | Что читаем | Темп |
|---|---|---|---|
| BH1750 | 0x23 | 2 байта, люксы = raw / 1,2 | 200 мс |
| BME280 | 0x76 | регистры 0xF7-0xFE, сырые ADC | 1 с (forced) |
| DS3231 | 0x68 | регистры 0x00-0x06, BCD | 1 мин |
- 1. Зачем датчику два хода: запись номера регистра, потом чтение?
- 2. Что будет, если опрашивать все три датчика в каждом проходе loop без расписания?
- Три адреса на одной паре A4-A5 живут спокойно: 0x23, 0x76, 0x68
- Стандартный ход: регистр через beginTransmission/write, затем requestFrom и read
- Расписание на millis: у каждого свой таймер и темп под его физику
- BH1750 меряет сам — читайте без регистра; BME280 forced — раз в секунду; RTC хватит минуты
- Три пары подтяжек параллельно — сверьте номиналы; смешанные 3,3/5 В — по правилам согласования
Частые вопросы по теме урока
- Зачем датчику два хода: запись номера регистра, потом чтение?
- Регистр — это адрес ячейки в памяти датчика. Запись регистра перед чтением двигает внутренний указатель, и requestFrom отдаёт байты начиная с него. Указать, с какой ячейки памяти читать: после записи регистра мастер читает его содержимое
- Что будет, если опрашивать все три датчика в каждом проходе loop без расписания?
- Часы тикают секундами, BME в forced-режиме считает по команде — чаще читать бессмысленно. Расписание на millis экономит шину и время loop без потери данных. Показания точнее не станут: DS3231 и BME280 отдают те же значения, а loop теряет темп на лишних чтениях