Arduino - Блог об Arduino, ПЛИС и современных технологиях - Блог об Arduino, ПЛИС и современных технологиях https://soltau.ru Thu, 15 Oct 2026 07:21:04 +0000 Joomla! - Open Source Content Management ru-ru Как подключить лазерный датчик дальности VL53-400 к Arduino https://soltau.ru/index.php/themes/arduino/item/613-kak-podklyuchit-lazernyj-datchik-dalnosti-vl53-400-k-arduino https://soltau.ru/index.php/themes/arduino/item/613-kak-podklyuchit-lazernyj-datchik-dalnosti-vl53-400-k-arduino Как подключить лазерный датчик дальности VL53-400 к Arduino
Разбираемся с нюансами подключения лазерного дальномера VL53-400.

Для проекта нам понадобятся:

1 Описание и характеристики датчика VL53-400

Датчик VL53-400 представляет собой лазерный дальномер и может измерять дальность от 0 до 4-х метров (для модификации с чипом VL53L1) или от 0 до 2-х метров (для модификации с чипом VL53L0). Причём есть несколько режимов работы с различной максимальной дальностью. Режимы с меньшей дальностью обеспечивают более высокую частоту опроса датчика. По умолчанию задан режим с максимальной дальностью. Точность измерений ±20 мм для VL53L1 и ±60 мм для VL53L0.

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

  • Датчик VL53-400 может измерять с частотой от 0,1 до 100 Гц.
  • Питание датчика от напряжения 3,3…5 В, потребляемый ток <36 мА.
  • Рабочая температура от −20 до +70° C.
  • Размеры датчика составляют всего 20×13×6.2 мм, вес 2 гр.
Внешний вид модуля VL53-400
Внешний вид модуля VL53-400

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

На рисунке ниже показаны углы передатчика и приёмника сенсора VL53-400.

Диаграмма направленности сенсора VL53-400
Диаграмма направленности сенсора VL53-400

Назначение выводов датчика VL53-400 такое:

ВыводНазначениеПримечание
VCCПитание3.3…5.0 В.
RXDПриёмник UARTУровень TTL. Нельзя подключать напрямую к RS232.
TXDПередатчик UARTУровень TTL. Нельзя подключать напрямую к RS232.
SCLТакт IICВ режиме IIC.
SDAДанные IICВ режиме IIC.
GNDЗемля

Датчик поддерживает 3 режима работы: нормальный (UART), Modbus (UART) и IIC. При первом включении датчик входит в нормальный режим работы. Далее можно выбрать другой режим, который датчик запоминает и при перевключении входит в этот режим.

Производитель датчика поставляет специальное программное обеспечение WitMotion для работы с данными датчиками (использование ПО не обязательно, но для изучения протокола работы и проверки работоспособности очень удобно). С помощью данного ПО можно настраивать режимы работы датчика, менять параметры режимов, записывать измерения и строить по ним графики, проводить калибровку и т.д. Скачать ПО можно с репозитория производителя. А также много интересной информации можно найти тут (на китайском языке :).

При первом запуске интерфейс программы на китайском. Для смены языка на английский нажать в верхнем меню на предпоследнюю иконку и выбрать второй пункт. После этого программу перезапустить.

Интерфейс программы WitMotion для управления сенсором VL53-400
Интерфейс программы WitMotion для управления сенсором VL53-400

Для подключения к датчику VL53-400 выбрать порт вашего USB-UART адаптера, выставить скорость и нажать кнопку Add. Датчик появится снизу, и если все параметры указаны верно, вы увидите меняющиеся показания.

Поле ID пока не трогаем. Это адрес датчика, и по умолчанию он 0x50. Данное поле следует изменить, если вы планируете работать в режиме Modbus, и у вас будет несколько устройств в сети.

2 Нормальный режим работы датчика VL53-400

Это режим работы по умолчанию. В этом режиме сенсор VL53-400 подключён по UART и автоматически передаёт данные об измеренной дистанции и статус текущего измерения текстом в кодировке ASCII. Скорость по умолчанию 115200 бод и конфигурируется в диапазоне от 2400 до 921600 бод.

Для подключения датчика к компьютеру можно использовать любой USB-UART адаптер. Вывод TXD датчика подключаем к RX адаптера, RXD датчика – к TX адаптера. Питание и землю, естественно.

Подключимся к порту и увидим следующее:

Поток данных модуля VL53-400 в нормальном режиме
Поток данных модуля VL53-400 в нормальном режиме

Вывод датчика в нормальном режиме в таком формате:

State;0, Range Valid
d: 242 mm    

Здесь State – статус текущего измерения, d – дистанция в миллиметрах. Возможные статусы:

СостояниеЗначениеОписание
0Range validДанные корректны
1Sigma failОшибка
2Signal failОшибка сигнала
3Min range failМеньше минимальной дистанции
4Phase failБольше максимальной дистанции
5Hardware failАппаратная ошибка
7 или 255No updateНет обновлений данных

Чтобы подключить датчик VL53-400 к Arduino, вывод TXD датчика подключаем к RX Arduino (обычно это D0), а RXD к TX (обычно D1), VCC к 3V3, GND – к GND. Скетч может быть, например, такой:

Скетч для чтения показаний дальномера VL53-400 (разворачивается)
const int STATE_VALID = 0;

String inputString = ""; // принятая строка
int currentState; // статус текущего измерения

void setup() {
  Serial.begin(115200);
  inputString.reserve(50); // зарезервируем 50 символов для строки
}

void loop() { /* ничего не делаем */ }

// Обрабатывает поступающие данные
void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read(); // читаем поступающий символ
    inputString += inChar; // дополняем строку новым символом
    if (inChar == '\n') { // при переходе на новую строку
      String start = inputString.substring(0, 2); // смотрим первые 2 символа строки
      if (start == "St") { //пример: 'State;0, Range Valid'
        currentState = GetIntFromString(inputString, 6); 
      }
      else if (start == "d:") { //пример: 'd: 242 mm'
        if (currentState == STATE_VALID) {
          int distance = GetIntFromString(inputString, 2); 
          Serial.println((String)distance + " mm");
        }
      }
      inputString = "";
    }
  }
}

// Ищет целое число в строке и возвращает его.
int GetIntFromString(String s, int index) {
  String stateStr = "";
  stateStr.reserve(10);
  String cur = "";
  while ((cur!=",") && (index < 9)) {
    cur = s.substring(index, index+1);
    index += 1;
    if (cur!=","){
      stateStr += cur;
    }
  }
  int val = stateStr.toInt();
  return val;
}

В результате выполнения этого скетча в мониторе порта Arduino видим:

Определение дистанции датчика VL53-400 в нормальном режиме с помощью Arduino
Определение дистанции датчика VL53-400 в нормальном режиме с помощью Arduino

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

3 Режим Modbus датчика VL53-400

В режиме Modbus датчик VL53-400 также подключается по UART. Но данный режим уже требует активного управления. Имеются команды чтения (запроса данных от датчика) и записи (установка параметров, выполнение калибровки датчика, изменение адреса и т.д.). Команд довольно много; ознакомиться со всеми можно в описании датчика VL53-400 (раздел 8.3 Modbus Register на странице 24), которое можно скачать в приложениях внизу статьи. Мы же рассмотрим только несколько основных команд, которые нам понадобятся для измерения дальности.

Общий формат команд Modbus дальномера VL53-400
ID датчикаТип командыАдрес регистра, старший байтАдрес регистра, младший байтДлина, старший байтДлина, младший байтCRC16, старший байтCRC16, младший байт

Здесь ID – это идентификатор датчика. По умолчанию он равен 0x50. Тип команды: 0x03 – чтение из указанного регистра, 0x06 – запись в указанный регистр. Каждый регистр датчика отвечает за свою функциональность. Адреса регистров полностью описаны в документации на модуль VL53-400. О контрольной сумме поговорим чуть ниже.

Шаблон команды чтения, которую понимает датчик, можно посмотреть в приложении WitMotion: меню Help Edit Register.

В данном случае шаблон ${ADDR} 03 00 34 00 02 ${CRC16}.

Реальная команда Modbus для чтения показаний дальномера VL53-400: 50 03 00 34 00 02 88 44

Здесь 0x50 – ID датчика, 0x03 – команда чтения, 0x0034 – адрес регистра измерения дальности, длина данных 2 байта (0x0002), 0x8844 – контрольная сумма CRC16/MODBUS, рассчитанная для данной команды.

Мы подробно рассматривали расчёт контрольной суммы по алгоритму CRC, в том числе CRC16. В приведённой статье также имеется программа калькулятора для расчёта контрольной суммы. На скриншоте видно, что она совпадает с примером (только байты меняются местами). При проверке с другими командами результат тот же, что говорит о том, что контрольная сумма действительно рассчитывается по алгоритму CRC-16/MODBUS (я не нашёл у производителя описания алгоритма расчёта CRC).

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

Ответы модуля расшифровываются так:

Общий формат ответов дальномера VL53-400 в режиме Modbus
ID датчикаТип командыДлина данныхДанные 1Данные 2…Данные NCRC16, старшийCRC16, младший

Для перевода датчика VL53-400 из нормального режима в режим Modbus необходимо отправить последовательность команд:

КомандаОписание
50 06 00 38 00 01C4 46 Режим работы: 0000 = нормальный, 0001 = Modbus, 0002 = IIC
50 06 00 00 00 00 84 4B Сброс: 0000 = сброс, 0001 = настройки по умолчанию.

Здесь синим цветом выделен тип команды (06=запись или 03=чтение), зелёным адрес регистра (см. описание модуля), а красным – изменяемая часть.

Далее можно отправлять команды запроса. Например:

КомандаПримеры ответаПримечание
50 03 00 04 00 01 C8 4A50 03 02 00 06 C5 8A Прочитать битрейт: 00=2400, 01=4800, 02=9600, 03=19200, 04=38400, 05=57600, 06=115200, 07=230400, 08=460800, 09=921600
50 03 00 1A 00 01 68 4F 50 03 02 00 50 45 B4 Прочитать идентификатор датчика ID. Могут быть значения от 00 до FE. 0x50 – значение по умолчанию.
50 03 00 35 00 01 99 8550 03 02 00 00 45 88
50 03 02 00 02 C4 49
50 03 02 00 07 04 4A
Прочитать статус измерения: 00=ok, 02=fail, 07=no update
50 03 00 34 00 01 C8 4550 03 02 00 E2 C5 C1
50 03 02 04 EC 46 C5
Запросить дистанцию: E2 =226 мм, 4EC =1260 мм.

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

Из таблицы видно, что на запрос дистанции (в данном примере) датчик отвечает 50 03 02 00 E2 C5 C1 и 50 03 02 04 EC 46 C5. Дистанция это 4-ый и 5-ый байты пакета: 0x00E2 и 0x04EC. Переведём эти числа из шестнадцатеричной системы в десятичную и получим дистанцию в миллиметрах: 226 мм и 1260 мм, соответственно.

Теперь у нас есть вся минимально необходимая информация, чтобы написать скетч для Arduino:

Чтение показаний дальномера VL53-400 в режиме Modbus (разворачивается)
const int STATE_VALID = 0;
const int CMD_LEN = 8; // длина команд и ответов датчика VL53

char buf[CMD_LEN]; // буфер для ответов датчика VL53

void setup() {
  pinMode(LED_BUILTIN, OUTPUT); // встроенный светодиод будет показывать статус
  
  Serial.begin(115200);
  Serial.setTimeout(100);
  
  // перевод в режим Modbus:
  byte mode[] = {0x50, 0x06, 0x00, 0x38, 0x00, 0x01, 0xC4, 0x46}; 
  Serial.write(mode, CMD_LEN);
  int rlen = Serial.readBytes(buf, CMD_LEN);
 
  // системный сброс датчика: 
  byte reset[] = {0x50, 0x06, 0x00, 0x00, 0x00, 0x00, 0x84, 0x4B};
  Serial.write(reset, CMD_LEN);
  rlen = Serial.readBytes(buf, CMD_LEN);
}

void loop() {
  int stat = UpdateStatus();
  if (stat == STATE_VALID) {
    digitalWrite(LED_BUILTIN, HIGH); // показываем, что измерение валидно 
    int dist = UpdateDistance();
    Serial.println((String)dist + "mm"); // только для демонстрации, не следует записывать в порт ничего лишнего 
  } else {
    digitalWrite(LED_BUILTIN, LOW); // показываем, что валидных данных нет
  }
}

// Запрашивает VL53-400 и возвращает статус измерения.
int UpdateStatus(){
  byte cmd[] = {0x50, 0x03, 0x00, 0x35, 0x00, 0x01, 0x99, 0x85};
  Serial.write(cmd, CMD_LEN);
  int rlen = Serial.readBytes(buf, CMD_LEN);
  return buf[4];
}

// Запрашивает VL53-400 и возвращает дистанцию, в мм.
int UpdateDistance(){
  byte cmd[] = {0x50, 0x03, 0x00, 0x34, 0x00, 0x01, 0xC8, 0x45};
  Serial.write(cmd, CMD_LEN);
  int rlen = Serial.readBytes(buf, CMD_LEN);
  return word(buf[3], buf[4]);
}

4 Режим IIC датчика VL53-400

В режиме IIC линии SDA и SCL подключаются напрямую к чипу VL53Lx. И обмен осуществляется по шине I2C непосредственно с чипом и описан в документе VL53-400 API User Manual (приложен в конце статьи). При этом I2C адрес чипа по умолчанию 0x29 (не путать с адресом датчика).

Для перевода датчика VL53-400 в режим I2C сначала нужно передать по UART команду, которую мы уже рассматривали выше, только теперь поменяем режим на 0x0002:

КомандаОписание
50 06 00 38 00 02 84 47Режим работы: 0000 = нормальный, 0001 = Modbus, 0002 = IIC

Датчик начнёт передавать по UART сообщение IICMODE.

После этого подключим модуль VL53-400 по следующей схеме:

Схема подключения датчика VL53-400 в режиме I2C
Схема подключения датчика VL53-400 в режиме I2C

Здесь MCU – управляющий микроконтроллер (в том числе Arduino). Линии SCL и SDA датчика имеют внутренние подтягивающие 10 кОм резисторы, поэтому внешние ставить не нужно.

Датчик VL53-400 может работать по IIC в трёх режимах: одиночное измерение (по запросу), непрерывное измерение без задержек между измерениями и непрерывное с заданным интервалом.

Скачать примеры работы для разных платформ (Arduino, STM, Raspberry Pi, Python и др.) можно здесь.

]]>
manager@soltau.ru (aave) Arduino Wed, 17 May 2023 18:58:54 +0000
Как подключить передатчик SYN115 и приёмник SYN480R к Arduino https://soltau.ru/index.php/themes/arduino/item/605-kak-podklyuchit-peredatchik-syn115-i-prijomnik-syn480r-k-arduino https://soltau.ru/index.php/themes/arduino/item/605-kak-podklyuchit-peredatchik-syn115-i-prijomnik-syn480r-k-arduino Как подключить передатчик SYN115 и приёмник SYN480R к Arduino
Модули радиопередатчика SYN113/SYN115 (или F113/F115) и радиоприёмника SYN840R (а также SYN470R) – это дешёвый и простой способ наладить радиообмен между двумя микроконтроллерами.

Для проекта нам понадобится:

1 Описание передатчика SYN113/SYN115 (F113/F115)

Пара модулей, состоящая из приёмника SYN113 (или SYN115 или F115) и передатчика SYN840R, обладает следующими преимуществами:

  • малая потребляемая мощность;
  • широкий диапазон рабочих температур (–40…+85°С);
  • широкий диапазон рабочих напряжений (передатчик 1,8…3,6 В, приёмник 3,3…5,5 В);
  • настраиваемый диапазон частот (300…450 МГц, рассматриваемые модули настроены на диапазон 433 МГц);
  • компактный размер и размещение на одном кристалле (размер модуля передатчика 15×12 мм, приёмника 17×12 мм);
  • простое подключение и управление;
  • дешевизна.

Модули работают в диапазоне частот от 300 до 450 МГц, перекрывая таким образом распространённую в быту частоту 433 МГц, на которой работают многие пульты дистанционного управления и беспроводные датчики.

Питание модулей – от 1,8 до 3,6 В. Передатчик F115 входит в режим ожидания и пониженного потребления, если нет входного сигнала (у F113 нет такой функции).

Скорость передачи данных – 2,5 кбит/сек или 10 кбит/сек. Для данных используется всего один вывод DAT и на передатчике, и на приёмнике.

Назначение выводов микросхемы передатчика SYN113/SYN115 (F113/F115) показано ниже:

Назначение выводов микросхемы передатчика SYNC113/SYN115 (F113/F115)
Назначение выводов микросхемы передатчика SYNC113/SYN115 (F113/F115)
ВыводНазначениеПримечание
PA_OUTВыход на антенну.Частота передачи задаётся номиналами обвязки.
XTLINВход опорного генератора частоты.
XTLOUTВыход опорного генератора частоты.
ASKВход данных ASK.ASK, amplitude shift keying – амплитудная манипуляция (АМн). Вид кодирования цифрового сигнала.
VDDПитание.1,8…3,6 В.
VSSОбщий.

Модуль с передатчиком имеет всего 4 вывода: VCC, DAT, GND, ANT.

Модуль передатчика SYN115/F115
Модуль передатчика SYN115/F115
ВыводНазначениеПримечание
DATВход данных с АМн.
ANTВыход антенны.Рекомендуется длина антенны 17,3 см (для частоты 433 МГц).
VDDПитание.
GNDОбщий.

Схема модуля передатчика F115 показана на рисунке.

Схема подключения передатчика F115
Схема подключения передатчика F115

Как уже упоминалось, передатчик кодирует сигнал с помощью амплитудной манипуляции (ASK, АМн). Принцип такой: когда передаётся бит «1», на выходе передатчика генерируется частота с максимальной амплитудой; когда передаётся бит «0», на выходе передатчика минимальная или нулевая амплитуда. Осциллограмма ниже демонстрирует это.

Пример амплитудной манипуляции (ASK) передатчика F115
Пример амплитудной манипуляции (ASK) передатчика F115

В нижней части осциллограммы – сигнал на цифровом входе передатчика, который приходит от какого-то внешнего источника. В верхней части – выходной аналоговый сигнал на антенне, то, что идёт в радиоэфир. В середине – напряжение на выводе VDD. Видно, что выходной сигнал полностью повторяет входной.

1 Описание приёмника SYN470R/SYN480R

Назначение выводов микросхемы приёмника SYN480R показано ниже:

Назначение выводов микросхемы приёмника SYN480R
Назначение выводов микросхемы приёмника SYN480R
ВыводНазначениеПримечание
VSSRFЗемля радиоприёмника.
VDDRFПитание радиоприёмника.
ANTАналоговый вход антенны.
DOЦифровой выход принимаемых данных.
CTHУровень среза данных.Меняя ёмкость, можно выбирать уровень, по которому внутренний компаратор будет принимать решение о наличии сигнала в эфире.
REFOSCОпорный генератор.Влияет на частоту приёма.
CAGCАвтоматическая регулировка усиления (АРУ).Меняя ёмкость, можно выбирать значение АРУ.
SHUT/WAKEBВыключение приёмника. / Признак приёма радиосигнала.Подача на вход лог. «0» включает приёмник. / Если на выходе лог. «0», значит приёмник принимает радиосигнал.

Модуль приёмника также имеет 4 вывода: VCC, DAT, GND, ANT.

Модуль приёмника SYN480R
Модуль приёмника SYN480R

Схема модуля приёмника SYN480R – на рисунке ниже.

Схема модуля приёмника SYN480R
Схема модуля приёмника SYN480R

Прдолжение следует...

]]>
manager@soltau.ru (aave) Arduino Fri, 05 Aug 2022 19:28:26 +0000
Как подключить АЦП ADT7411 к Arduino https://soltau.ru/index.php/themes/arduino/item/603-kak-podklyuchit-atsp-adt7411-k-arduino https://soltau.ru/index.php/themes/arduino/item/603-kak-podklyuchit-atsp-adt7411-k-arduino Как подключить АЦП ADT7411 к Arduino
Аналогово-цифровой преобразователь ADT7411 фирмы Analog Devices совмещает в себе 8-канальный 10-разрядный АЦП со встроенным термодатчиком. Научимся подключать его и считывать показания по интерфейсам SPI и I2C с помощью Arduino и…

Для проекта нам понадобятся:

1 Описание аналого-цифрового преобразователя ADT7411

Диапазон измеряемых напряжений АЦП – либо от 0 до 2,25 В, либо от 0 до напряжения питания. АЦП ADT7411 питается напряжением от 2,7 до 5,5 В. Встроенный датчик температуры позволяет измерять температуру в диапазоне от -40° до +120°C с точностью четверть градуса. Также имеется возможность подключить один внешний термодатчик.

Управление преобразователем возможно по цифровым последовательным интерфейсам SPI и I2C.

Назначение выводов микросхемы 7411 представлено на рисунке.

Назначение выводов АЦП ADT7411
Назначение выводов АЦП ADT7411
Назначение выводов АЦП ADT7411
НазваниеНазначениеПримечание
AIN3…AIN8Однопроводные аналоговые входы.Входное напряжение от 0 до 2,25 В или от 0 до VDD
D+,D– / AIN1, AIN2Или подключение внешнего термодатчика, или однопроводные аналоговые входы.
VDDПитание.2,7…5,5 В
GNDЗемля.Общий провод для аналоговой и цифровой части схемы.
CSВыбор ведомого в режиме SPI.В режиме I2C рекомендуется подтянуть к питанию.
SCL/SCLKТактовая частота для последовательных интерфейсов.Для выводов с открытым коллектором требуется подтягивающий резистор.
SDA/DINВход последовательных интерфейсов.Для выводов с открытым коллектором требуется подтягивающий резистор.
DOUT/ADDВыход данных в режиме SPI / Изменение адреса в режиме I2C.Для выводов с открытым коллектором требуется подтягивающий резистор.
INT/INTКонфигурируется на прерывание превышения температуры, напряжения на аналоговых входах или питания.Для выводов с открытым коллектором требуется подтягивающий резистор.

Внутренняя память устройства состоит из 255-ти регистров, которые показаны на рисунке ниже. Здесь присутствуют регистры конфигурации, статуса, информации о ревизии и производителе микросхемы, а также регистры показаний аналоговых каналов и термодатчиков. Некоторые из регистров мы чуть позже рассмотрим подробно, а на рисунке представлены адреса регистров, их названия и значения по умолчанию после подачи питания на ADT7411.

Структура конфигурационного регистра 0x18 АЦП ADT7411
Структура конфигурационного регистра 0x18 АЦП ADT7411
Структура конфигурационного регистра 0x19 АЦП ADT7411
Структура конфигурационного регистра 0x19 АЦП ADT7411
Структура конфигурационного регистра 0x1A АЦП ADT7411
Структура конфигурационного регистра 0x1A АЦП ADT7411

Начнём изучение с того, что подключим АЦП к отладочной плате с микросхемой FT2232H и с помощью программы SPI via FTDI изучим нюансы информационного обмена с данным АЦП, а затем на основе этих данных реализуем алгоритм на Arduino.

2 Подключение и работа микросхемы ADT7411 в режиме SPI

Подключение стандартное для SPI.

Типичная схема подключения ADT7411 в режиме SPI
Типичная схема подключения ADT7411 в режиме SPI
Подключение АЦП ADT7411 к плате с FT2232H
Подключение АЦП ADT7411 к плате с FT2232H

Питание 3,3 В подадим на ножку VDD, землю – на ножку GND. Импульсы синхронизации возьмём с ADBUS0 микросхемы FT2232H и подадим на ножку SCL/SCLK ADT7411. Выходные данные возьмём с ADBUS1 и подадим на SDA/DIN, а входные – с ADBUS2 и подадим на DOUT/ADD. Сигнал выбора ведомого будет подан с ADBUS3 на CS. На канал AIN6 (1-ую ножку микросхемы) подадим 3,3 В для тестирования аналогового канала АЦП.

Подключение ADT7411 к FT2232
Вывод ADT7411Вывод FT2232H
SCL/SCLKADBUS0
SDA/DIN ADBUS1
DOUT/ADDADBUS2
CSADBUS3
GNDGND
AIN63.3 V

Теперь запустим программу SPI via FTDI, в меню «Устройство» выберем интерфейс SPI. Подключимся к микросхеме через меню "Устройство" или щёлкнув по вкладке устройства правой кнопкой мыши. Настройки режима должны быть как на скриншоте ниже. В поле записи наберём команду 90 4D и трижды нажмём кнопку «Записать».

Почему трижды? Дело в том, что АЦП ADT7411 при подаче питания находится в режиме I2C. Чтобы перевести его в режим SPI, необходимо сгенерировать на его ножке CS три импульса с активным низким уровнем. Трижды посланная команда сделает в том числе как раз это.

Команда 0x90 показывает микросхеме ADT7411, что далее мы будем передавать ей данные, 0x4D – это передаваемые данные, а именно – адрес регистра DeviceID.

Чтение идентификатора устройства, кода производителя и версии микросхемы ADT7411
Чтение идентификатора устройства, кода производителя и версии микросхемы ADT7411

После этого в поле чтения наберём команду 91 (поставив галку «Команда») и число байт для чтения – 3. Откроем окно для вывода результата (кнопка в виде таблицы) и нажмём кнопку «Прочитать». В открытом окне должны появиться значения 3-х прочитанных регистров.

Обратите внимание, что каждая команда АЦП ADT7411 предваряется кодом 0x90, если мы собираемся записывать данные, и кодом 0x91, если будем запрашивать данные от АЦП.

В данном примере нам вернулись 3 байта: 0x02, 0x41, 0x05. Если заглянем в техническое описание (datasheet, находится в приложении к статье), то увидим, что так и должно быть: 0x02 – это постоянный идентификатор АЦП 7411, 0x41 – постоянный идентификатор производителя, а версия кристалла может меняться, и в данном случае она 5-ая. Значит, устройство работоспособное и подключено правильно.

Проведём конфигурирование АЦП. Дело в том, что АЦП ADT7411 может измерять в двух режимах: по запросу и непрерывно (т.н. режим round-robin). При подаче питания АЦП входит в режим непрерывных измерений, но он не активен. Его нужно активировать. Кроме того, нужно задать ещё несколько настроек.

Для этого (в разделе «Запись») нужно записать в три конфигурационных регистра (с адресами 18, 19 и 1A) определённые настройки. Это делается путём отправки устройству последовательности команд:

КомандаРасшифровкаПояснение
90 18 08Записываем по адресу 18 значение 08.Остановка опроса каналов, выводы 7 и 8 работают как AIN1 и AIN2, включение прерываний активным низким, вывод устройства из режима ожидания.
90 19 00Записываем по адресу 19 значение 00.Включаем непрерывный режим измерений и усреднение по 16-ти измерениям.
90 1A 19Записываем по адресу 1A значение 19.Быстрое измерение (частота 22,5 кГц), VDD используется как опорный уровень напряжения.

Почему мы отправляем именно такие значения в регистры? Каждый бит в 8-битовом числе представляет собой определённый параметр. Присваивая биту значение «1» или «0», мы включаем или выключаем эти параметры. Некоторые параметры могут кодироваться не одним, а несколькими битами. Более подробно показано на рисунке ниже, а также в техническом описании ADT7411, которое можно скачать в приложении к статье.

Структура конфигурационного регистра 0x18 АЦП ADT7411
Структура конфигурационного регистра 0x18 АЦП ADT7411
Структура конфигурационного регистра 0x19 АЦП ADT7411
Структура конфигурационного регистра 0x19 АЦП ADT7411
Структура конфигурационного регистра 0x1A АЦП ADT7411
Структура конфигурационного регистра 0x1A АЦП ADT7411

Для запуска измерений подадим команду 90 18 01. После этого АЦП запустит непрерывный цикл измерений.

Теперь прочитаем значения регистров устройства. Для этого сначала запишем (в разделе «Запись») адрес 0-го регистра: 90 00, а затем прочитаем 64 байта, указав команду 91 и число байтов для чтения – 64.

Чтение карты регистров АЦП ADT7411 по SPI
Чтение карты регистров АЦП ADT7411 по SPI

В окне принимаемых значений отобразится 64 байта, которые нам вернул АЦП ADT7411. Это значения его 64-х регистров. Давайте посмотрим, что нам вернулось.

Значение встроенного термодатчика содержится в регистрах с адресами 0x03 и 0x07, а данные аналогового входа AIN6 – в регистрах с адресами 0x05 и 0x0D.

В соответствии со скриншотом выше, в регистре 3 содержится значение 0x0B (или 0000_1011 в двоичном виде), в регистре 7 содержится значение 0x1C (или 0001_1100 в двоичном виде). Для ясности можно записать в таблицу:

Регистр 0x03Регистр 0x07
D7D6D5D4D3D2D1D0 D7D6D5D4D3D2D1D0
------T1T0 T9T8T7T6T5T4T3T2
00001011 00011100

Соединив все разряды с T9 (старший разряд) по T0 (младший), получается, что значение внутреннего термодатчика в двоичном виде 00_0111_0011 или 115 в десятичном виде. Это код температуры. Для перевода кода температуры в градусы Цельсия:

  • t = T × 0,25 если разряд T9 равен "0" (признак, что температура выше нуля);
  • t = (T – 1024) × 0,25 если разряд T9 равен "1" (признак, что температура отрицательная).

Здесь T – код температуры АЦП, t – значение температуры в градусах Цельсия, коэффициент 0,25 – число градусов Цельсия на один разряд АЦП, 1024 – это 210 – максимум шкалы для 10-разрядного АЦП.

В нашем случае разряд T9 равен нулю, поэтому температура в градусах Цельсия положительна и равна 115×0,25 = 28,75°C

Теперь переведём измерения напряжения из кода АЦП в вольты. Составим аналогичную таблицу. Значение регистра 0x5 – 0x73 (или 0111_0011 в двоичном виде), значение регистра 0xD – 0xD0 (или 1101_0000 в двоичном виде).

Регистр 0x05Регистр 0x0D
D7D6D5D4D3D2D1D0 D7D6D5D4D3D2D1D0
----A1A0-- A9A8A7A6A5A4A3A2
01110011 11010000

Из разрядов A9…A0 получаем значение на аналоговом входе AIN6: 11_0100_0000 или 832 в десятичном виде. Для перевода кода напряжения UADC в вольты U: U = UADC × Vref / 1024 = 832×3,3 / 1024 = 2,68 В

Здесь Vref – опорное напряжение, равное напряжению питания чипа ADT7411 в вашей схеме, 1024 – опять же, максимальное значение 10-разрядного АЦП.

Расшифровку остальных регистров можно посмотреть в техническом описании АЦП и сейчас рассматривать не будем.

Теперь можно написать скетч для Arduino, который будет делать то же самое: определять температуру внутреннего термодатчика АЦП ADT7411 и напряжение на входе AIN6.

Скетч управления АЦП ADT7411 по SPI (разворачивается)
#include <SPI.h>

const byte WRITE = 0x90; // команда записи
const byte READ = 0x91; // команда чтения
const float Vref = 3.3; // опорное напряжение равно напряжению питания АЦП

void setup() {
  Serial.begin(9600);
  SPI.begin();
  pinMode(SS, OUTPUT);
  delay(100); // задержка на время самопроверки АЦП ADT7411
  lockSpiMode();
  readSpiLockStatus();
  readIds();
  initAdt7411();
  startMonitoring();
}

void loop() {
  byte regs[64];
  readAll(regs);
  
  float internalTemperature = getTemperature(regs);
  Serial.println("t=" + (String)internalTemperature);
  
  float ain6 = getAin6(regs);
  Serial.println("AIN6=" + (String)ain6);
  Serial.println();
  
  delay(1000);
}

// Читает регистр режима SPI/I2C.
byte readSpiLockStatus(){
  byte regs[1];
  readRegister(0x7f, 1, regs);
  if (regs[0] & 1 == 1){
    Serial.println("Mode: SPI");
  }
  else {
    Serial.println("Mode: I2C");
  }
}

// Читает всю карту регистров АЦП ADT7411.
void readAll(byte regs[]){
  readRegister(0x00, 64, regs);
  for (int i=0; i<64; i++){
    Serial.print(String(regs[i],HEX) + " ");
  }
  Serial.println();
}

// Переводит АЦП ADT7411 в режим SPI.
void lockSpiMode(){
  for (int i=0; i<3; i++){
    digitalWrite(SS, LOW);
    digitalWrite(SS, HIGH);
  }
}

// Инициализация АЦП ADT7411.
void initAdt7411(){
  byte conf[] = {0x08, 0x00, 0x19}; // значения конфигурационных регистров
  writeRegister(0x18, &conf[0], 1);
  writeRegister(0x19, &conf[1], 1);
  writeRegister(0x1A, &conf[2], 1);
}

// Читает идентификаторы и выводит в монитор последовательного порта.
void readIds(){
  byte ids[3];
  readRegister(0x4D, 3, ids);
  Serial.println("Device ID = " + (String)ids[0]);
  Serial.println("Manufacturer ID = 0x" + String(ids[1], HEX));
  Serial.println("Silicon revision = " + (String)ids[2]);
  Serial.println();
}

// Запускает измерения.
void startMonitoring(){
   byte conf[1] = {0x01};
   writeRegister(0x18, conf, 1);
}

// Читает температуру и переводит в градусы.
float getTemperature(byte regs[]){
  int tempCode = (regs[3] & B11) | (regs[7] << 2);
  if ((tempCode >> 10) & 1 == 1){ // 11-ый бит ==1 - признак отрицательной температуры
    tempCode -= 1024;
  }
  return tempCode * 0.25;
}

// Читает показания на входе AIN6 и переводит в вольты.
float getAin6(byte regs[]){
  int uadc = ((regs[5] >> 2) & B11) | (regs[0x0D] << 2);
  return uadc * Vref / 1024;
}

// Читает массив байтов заданной длины, начиная с заданного адреса.
void readRegister(byte address, int len, byte out[]) {
  // записываем адрес:
  digitalWrite(SS, LOW);
  SPI.transfer(WRITE);
  SPI.transfer(address);
  digitalWrite(SS, HIGH);

  // читаем, начиная с этого адреса:
  digitalWrite(SS, LOW);
  SPI.transfer(READ);
  for(int i=0; i<len; i++){
    out[i] = SPI.transfer(00);
  }
  digitalWrite(SS, HIGH);
}

// Записывает в регистр массив байтов, начиная с заданного адреса:
void writeRegister(byte address, byte value[], int len) {
  digitalWrite(SS, LOW);
  SPI.transfer(WRITE);
  SPI.transfer(address);
  for(int i=0; i<len; i++){
    SPI.transfer(value[i]);
  }
  digitalWrite(SS, HIGH);
}    

В результате загрузки данного скетча в память Ардуино, в мониторе последовательного порта будет примерно следующее:

Чтение показаний АЦП ADT7411 по SPI
Чтение показаний АЦП ADT7411 по SPI

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

Диаграмма чтения регистров ID АЦП ADT7411 по SPI
Диаграмма чтения регистров ID АЦП ADT7411 по SPI

Полную временную диаграмму работы скетча можно скачать в приложении к статье. Она открывается бесплатной программой Saleae Logic 2.

Разумеется, это лишь пример, демонстрирующий основы работы с данным АЦП по интерфейсу SPI, содержащий однако все необходимые для обмена с микросхемой функции.

3 Подключение и работа микросхемы ADT7411 в режиме I2C

Теперь сделаем то же самое, только управлять АЦП будем по шине I2C (или IIC). Здесь есть несколько нюансов.

АЦП подключается по следующей схеме.

Типичная схема подключения ADT7411 в режиме I2C
Типичная схема подключения ADT7411 в режиме I2C

Питание 3,3 В подадим на ножку VDD, землю – на ножку GND. Импульсы синхронизации возьмём с ADBUS0 микросхемы FT2232H и подадим на ножку SCL/SCLK ADT7411 (обратите внимание, через резистор ~10 кОм). Данные возьмём с ADBUS1 и подадим на SDA/DIN (тоже через аналогичный резистор). Кроме того, вывод ADBUS2 также нужно подать на SDA/DIN (особенность работы микросхем FTDI в режиме I2C). Ножку CS подтянем к питанию. На канал AIN6 подадим 3,3 В для тестирования аналогового канала АЦП.

Ножку ADD можно подключить либо к земле, либо к питанию. Это влияет на то, каким будет адрес устройства на шине I2C. При подключении к земле адрес будет 72, при подключении к питанию – 75.

Подключения выводов АЦП ADT7411 в режиме I2C
Вывод ADT7411Вывод FT2232H
SCL/SCLKADBUS0
SDA/DIN ADBUS1 и ADBUS2
DOUT/ADDVDD или GND
CSVDD
GNDGND
AIN6Источник напряжения от 0 до VDD

При обмене по шине I2C АЦП может передавать только по одному байту. Ведущее устройство (мастер) сначала должно передать АЦП адрес интересующего регистра и затем прочитать ответ от АЦП. Например, чтобы прочитать значение регистра DeviceID, нужно передать АЦП адрес 0x4D, а затем вычитать 1 байт. Но вот от мастера ведомому можно передавать несколько байтов.

Запустим программу SPI via FTDI, переключимся в режим I2C (меню «Устройство» – «Интерфейс» – I2C). Оставим настройки по умолчанию. Просканируем шину I2C. При правильно собранной схеме програма обнаружит на шине одно устройство и добавит его в выпадающий список. Выберем его. Укажем в поле записи адрес 4D и нажмём кнопку «Записать». Далее в поле чтения укажем количество байт – 1 – и нажмём кнопку «Прочитать». В окне принятых результатов должно появиться число 2. Это и есть постоянное значение регистра DeviceID, как мы уже знаем.

Чтение регистров идентификаторов АЦП ADT7411 в режиме I2C
Чтение регистров идентификаторов АЦП ADT7411 в режиме I2C

Совершенно аналогично будет проводиться запись и чтение и в другие регистры. Например, для записи в конфигурационный регистр 0x18 значения 0x08 осуществляется так:

Запись в регистр конфигурации 0x18 АЦП ADT7411 в режиме I2C
Запись в регистр конфигурации 0x18 АЦП ADT7411 в режиме I2C

Соответственно, перепишем скетч на применение интерфейса IIC. Это потребует минимальных изменений.

Скетч управления АЦП ADT7411 по I2C (разворачивается)
#include <Wire.h>

const float Vref = 3.3; // опорное напряжение равно напряжению питания
const byte I2C_ADDR = 75; // адрес АЦП ADT7411 на шине I2C при подтяжке вывода ADD к питанию

void setup() {
  Serial.begin(9600);
  Wire.begin();
  delay(100); // задержка на время самопроверки АЦП ADT7411
  readIds();
  readSpiLockStatus();
  initAdt7411();
  startMonitoring();
}

void loop(){
  float internalTemperature = getTemperature();
  Serial.println("t=" + (String)internalTemperature);
  
  float ain6 = getAin6();
  Serial.println("AIN6=" + (String)ain6);
  Serial.println();

  delay(1000);
}

// Читает регистр режима SPI/I2C.
byte readSpiLockStatus(){
  byte reg = readRegister(0x7f);
  if (reg & 1 == 1){
    Serial.println("Mode: SPI");
  }
  else {
    Serial.println("Mode: I2C");
  }
}

// Инициализация АЦП ADT7411.
void initAdt7411(){
  byte conf[] = {0x08, 0x00, 0x19}; // значения конфигурационных регистров
  writeRegister(0x18, &conf[0], 1);
  writeRegister(0x19, &conf[1], 1);
  writeRegister(0x1A, &conf[2], 1);
}

// Читает идентификаторы и выводит в монитор последовательного порта.
void readIds(){
  byte devId = readRegister(0x4D);
  Serial.println("Device ID = " + (String)devId);
  
  byte manId = readRegister(0x4E);
  Serial.println("Manufacturer ID = 0x" + String(manId, HEX));
  
  byte rev = readRegister(0x4F);
  Serial.println("Silicon revision = " + (String)rev);
  Serial.println();
}

// Запускает измерения.
void startMonitoring(){
   byte conf[1] = {0x01};
   writeRegister(0x18, conf, 1);
}

// Читает температуру и переводит в градусы.
float getTemperature(){
  byte reg3 = readRegister(0x03);
  byte reg7 = readRegister(0x07);
  int tempCode = (reg3 & B11) | (reg7 << 2);
  if ((tempCode >> 10) & 1 == 1){ // отрицательная температура
    tempCode -= 1024;
  }
  return tempCode * 0.25;
}

// Читает показания на входе AIN6 и переводит в вольты.
float getAin6(){
  byte reg5 = readRegister(0x05);
  byte regD = readRegister(0x0D);
  int uadc = ((reg5 >> 2) & B11) | (regD << 2);
  return uadc * Vref / 1024;
}

// Читает значение регистра с адресом addr.
byte readRegister(byte addr) {
  // Записывает адрес регистра:
  Wire.beginTransmission(I2C_ADDR);
  Wire.write(addr);
  Wire.endTransmission();

  // Запрашивает из регистра данные:
  Wire.requestFrom(I2C_ADDR, 1);
  byte result;
  while (Wire.available()) {
    result = Wire.read();
  }
  return result;
}

// Записывает в регистр массив байтов value заданной длины len, начиная с заданного адреса address:
void writeRegister(byte address, byte value[], int len) {
  Wire.beginTransmission(I2C_ADDR);
  Wire.write(address);
  for(int i=0; i<len; i++){
    Wire.write(value[i]);
  }
  Wire.endTransmission();
}

При подключении АЦП к Arduino, нужно также помнить, что у Arduino выводы для I2C: SDA – аналоговый вывод A4, а SCL – A5.

Подключение АЦП ADT7411 к Arduino в режиме I2C
Подключение АЦП ADT7411 к Arduino в режиме I2C

Временная диаграмма опроса регистров идентификаторов представлена на рисунке, а полная диаграмма – в приложении к статье.

Временная диаграмма чтения регистров идентификаторов АЦП ADT7411 в режиме I2C
Временная диаграмма чтения регистров идентификаторов АЦП ADT7411 в режиме I2C

Итак, мы научились управлять АЦП ADT7411 без использования специальных библиотек с помощью Arduino по интерфейсам SPI и I2C.

☆∘☆∘☆

]]>
manager@soltau.ru (aave) Arduino Tue, 26 Jul 2022 07:19:35 +0000
Как подключить MAX31865 и термодатчик PT100 к Arduino https://soltau.ru/index.php/themes/arduino/item/592-kak-podklyuchit-max31865-i-termodatchik-pt100-k-arduino https://soltau.ru/index.php/themes/arduino/item/592-kak-podklyuchit-max31865-i-termodatchik-pt100-k-arduino Как подключить MAX31865 и термодатчик PT100 к Arduino
Научимся работать с конвертером MAX31865 аналогового сигнала датчика температуры P100/P1000 в цифровой сигнал.

Для проекта нам понадобится:

1Описание и характеристики конвертера MAX31865

Конвертер MAX31865 – это преобразователь сопротивления аналоговых датчиков температуры (RTD) в цифровой сигнал. Преобразователь MAX31865 оптимизирован для работы с датчиками температуры, наиболее широко применяемые из которых это платиновые датчики PT100 и PT1000. Преобразователь рассчитан на работу с 2-, 3- и 4-проводными датчиками. Подобные датчики выпускаются разными производителями и могут отличаться по форме, внешнему виду и т.д. Для примера в приложении к статье несколько технических описаний аналогичных датчиков.

Лучше всего покупать MAX31865 в составе готового модуля, как, например, на фотографии:

Внешний вид модуля преобразователя MAX31865
Внешний вид модуля преобразователя MAX31865

Назначение выводов приведённого модуля такое:

Назначение выводов MAX31865
ВыводНазначениеПримечание
CLKВход тактовой частоты SPI.Максимум до 5 МГц.
SDOВыход данных от MAX31865 к ведущему контроллеру.
SDIВход данных от контроллера к MAX31865.
CSВход выбора ведомого (MAX31865).Активный уровень – низкий.
VINВход питания 3,3…5,0 В.Имеется встроенный преобразователь напряжения, обеспечивающий необходимое питание чипа MAX31865.
3V3Выход 3,3 В.Можно питать внешние устройства, ток не более 100 мА.
DRDYИндикатор готовности данных.
GNDЗемля.
RTD+, RTD-Подключение температурного RTD датчика.Полярность не важна.
F+, F-Подключение дополнительных проводов термодатчика.В 3- и 4-проводном типах подключений.

Датчики PT100 и PT1000 имеют номинальное сопротивление 100 Ом и 1000 Ом при 0°C, соответственно. Эти датчики очень просты по принципу действия. По сути это обычный терморезистор, т.е. резистор, который изменяет сопротивление RTD при изменении температуры. Такие датчики могут быть изготовлены и из других металлов (никель, медь и др.). Датчики на основе платины отличаются широким температурным диапазоном, высокой точностью и относительно хорошей линейностью. Из рисунка видно, что отклонение от линейного закона для платинового термодатчика PT100 начинается при температурах свыше 200°C.

Зависимость сопротивления датчика PT100 от температуры
Зависимость сопротивления датчика PT100 от температуры

Обратите внимание на точку 0°C. При этой температуре термодатчик имеет своё номинальное сопротивление, в данном случае 100 Ом.

Датчики обычно выполняют в 2-, 3- и 4-проводном вариантах. Первый из них – самый простой, но при этом и самый грубый. Дело в том, что при большой длине провода (а датчик может находиться в десятках метрах от контроллера) сопротивление провода становится сопоставимым с сопротивлением самого датчика. И на провод также действует температура. Соответственно, он будет вносить некоторую (возможно, значительную) погрешность в измерения. Для компенсации этого эффекта придумали 4-проводную схему подключения, когда 2 дополнительных провода используются для компенсации температурной погрешности датчика. А трёхпроводный датчик – это компромисс между 2- и 4-проводными: он более точный, чем 2-проводный, но менее точный 4-проводного. Зато и проводов у него меньше, чем у 4-проводного.

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

Схема измерений на чипе MAX31865
Схема измерений на чипе MAX31865

Терморезистор RTD подключен одной парой проводов к выводам RTDIN+ и RTDIN-, а второй парой проводов – к выводам FORCE+ и FORCE-. Для начала измерений с вывода BIAS микросхемы подаётся опорное напряжение. Возникает электрический ток, который проходит через опорное сопротивление RREF и дальше идёт по проводу через вывод FORCE+, через терморезистор RTD к выводу FORCE-. Зная напряжение на выходе BIAS и сопротивление опорного резистора, по закону Ома можно вычислить ток I, протекающий в этой цепи. Этот же ток протекает и через терморезистор. Далее чип MAX31865 измеряет напряжение U между выводами RTDIN+ и RTDIN-. По рассчитанному току I и измеренному напряжению U можно определить сопротивление на терморезисторе RRTD. А зная сопротивление терморезистора и закон изменения сопротивления от температуры, можно определить температуру датчика.

Есть тонкости по работе с датчиками с разным числом проводов: необходимо определённым образом распаять перемычки на модуле. Распайка перемычек показана на иллюстрации и обозначена зелёным цветом. Кроме того, для подключения 3-проводного датчика необходимо рассечь перемычку, которая по умолчанию имеется между контактными площадками, обзначенными "2" и "4"; на иллюстрации она обозначена красным цветом.

Распайка перемычек модуля MAX31865 под разные датчики
Распайка перемычек модуля MAX31865 под разные датчики

Для подключения 2-проводного датчика просто подключаем его два провода к клеммам RTD+ и RTD-.

Для подключения 4-проводного датчика необходимо определить, между какими из 4-х проводов находится терморезистор. Для этого достаточно померить сопротивление мультиметром или омметром. Оно должно быть около 100 Ом для датчика PT100 и около 1 кОм для датчика PT1000. Эти два провода подключаются к клеммам RTD+ и RTD-, оставшиеся два провода – к клеммам F+ и F-, не важно какой из них куда.

Для подключения 3-проводного датчика опять с помощью мультиметра определяем, какие 2 провода подключены через терморезистор, и подключаем их к клеммам RTD+ и RTD-. Третий провод необходимо подключить к клемме F+.

Модуль для датчика PT100 отличается от модуля для датчика PT1000 распаянным опорным сопротивлением (оно обведено на плате прямоугольником и обозначено Rref ). На модуле, который рассчитан на работу с PT100, стоит резистор Rref сопротивлением 430 Ом (обозначение на корпусе резистора 431 или 4300), а на модуле для датчика PT1000 резистор Rref=4300 Ом (обозначение на корпусе 432 или 4301). Обратите на это внимание при выборе датчика и модуля.

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

2Подключение преобразователяMAX31865 к Arduino

Есть несколько популярных библиотек Arduino для работы с MAX31865. Но наиболее популярная и проверенная, вероятнее всего, библиотека от Adafruit. Скачаем и установим её.

Библиотеку от Adafruit можно также установить через менеджер библиотек Arduino IDE: Tools Manage Libraries… и в поле фильтра ввести MAX31865. Выбрать интересующую библиотеку, нажать кнопку Install и дождаться окончания установки.

Откроем из примеров библиотеки единственный скетч для MAX31865 и загрузим его в память Arduino.

Загрузка примера из библиотеки для MAX31865
Загрузка примера из библиотеки для MAX31865

Текст скетча из библиотеки Adafruit (в будущем библиотека может измениться, как и пример в ней):

Скетч чтения температуры с MAX31865 (разворачивается)
#include <Adafruit_MAX31865.h>

Adafruit_MAX31865 thermo = Adafruit_MAX31865(10, 11, 12, 13); // Use software SPI: CS, DI, DO, CLK
//Adafruit_MAX31865 thermo = Adafruit_MAX31865(10); // use hardware SPI, just pass in the CS pin

#define RREF 430.0 // The value of the Rref resistor. Use 430.0 for PT100 and 4300.0 for PT1000
#define RNOMINAL  100.0 // The 'nominal' 0-degrees-C resistance of the sensor (100.0 for PT100, 1000.0 for PT1000)

void setup() {
  Serial.begin(115200);
  Serial.println("Adafruit MAX31865 PT100 Sensor Test!");
  thermo.begin(MAX31865_3WIRE);  // set to 2WIRE or 4WIRE as necessary
}


void loop() {
  uint16_t rtd = thermo.readRTD();

  Serial.print("RTD value: "); Serial.println(rtd);
  float ratio = rtd;
  ratio /= 32768;
  Serial.print("Ratio = "); Serial.println(ratio,8);
  Serial.print("Resistance = "); Serial.println(RREF*ratio,8);
  Serial.print("Temperature = "); Serial.println(thermo.temperature(RNOMINAL, RREF));

  // Check and print any faults
  uint8_t fault = thermo.readFault();
  if (fault) {
    Serial.print("Fault 0x"); Serial.println(fault, HEX);
    if (fault & MAX31865_FAULT_HIGHTHRESH) {
      Serial.println("RTD High Threshold"); 
    }
    if (fault & MAX31865_FAULT_LOWTHRESH) {
      Serial.println("RTD Low Threshold"); 
    }
    if (fault & MAX31865_FAULT_REFINLOW) {
      Serial.println("REFIN- > 0.85 x Bias"); 
    }
    if (fault & MAX31865_FAULT_REFINHIGH) {
      Serial.println("REFIN- < 0.85 x Bias - FORCE- open"); 
    }
    if (fault & MAX31865_FAULT_RTDINLOW) {
      Serial.println("RTDIN- < 0.85 x Bias - FORCE- open"); 
    }
    if (fault & MAX31865_FAULT_OVUV) {
      Serial.println("Under/Over voltage"); 
    }
    thermo.clearFault();
  }
  Serial.println();
  delay(1000);
}

Обратите внимание, что в функции setup() необходимо указать, по какой схеме вы подключаете датчик. По умолчанию стоит трёхпроводная схема, что указывается константой MAX31865_3WIRE. Для 2- и 4-проводных подключений датчика названия констант, соответственно, MAX31865_2WIRE и MAX31865_4WIRE. Также следует обратить внимание на константы в начале скетча RREF и RNOMINAL, которые определяют референсное (опорное) сопротивление и номинальное сопротивление термодатчика, соответственно.

Теперь подключим модуль с MAX31865 к Arduino по такой схеме (соответствие выводов можно поменять в скетче):

Схема подключения модуля MAX31865 к Arduino
Вывод модуляВывод Arduino
CLKD13
SDID11
SDOD12
CSD10
VIN3V3
GNDGND

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

3Управление преобразователемMAX31865 по SPI

Конвертер MAX31865 управляется по интерфейсу SPI в режиме 1 (MODE1) или 3 (MODE3). Можно читать из и передавать в MAX31865 как по 1 байту, так и сразу по несколько.

Для управления необходимо знать карту его регистров. Регистров всего восемь:

Карта регистров преобразователя MAX31865
Карта регистров преобразователя MAX31865

Здесь POR – это состояние регистра после подачи питания или сброса.

В первом столбце названия регистров. Во втором их адреса для чтения, а в третьем – для записи. В четвёртом столбце приведены исходные состояния регистров после сброса. В последнем – можно ли записывать в регистр (R/W, read/write) или только читать из него (R, read). Рассмотрим кратко все регистры.

Регистр конфигурации MAX31865 (адрес 0x00)

Первый из регистров – регистр конфигурации. Он задаёт режим работы чипа MAX31865, в том числе, включает измерение температуры.

Регистр конфигурации преобразователя MAX31865
Регистр конфигурации преобразователя MAX31865
Назначение битов регистра конфигурации MAX31865
D7 (VBIAS)Подаёт ток на датчик температуры, без этого измерения невозможны.
D6Включает автоматический режим измерения температуры.
D5Управляет режимом измерений: если D6=0, то установка бита D5 приводит к однократному измерению температуры.
D4Задаёт схему подключения датчика (2-, 3-, 4-проводная).
D3, D2Определяют режим обнаружения неисправностей.
D1Позволяет очистить регистр статуса, если он показывает ошибки.
D0Управляет выбором режекторного фильтра, который отрезает 50- или 60-герцовые наводки. В процессе автоматических измерений менять тип фильтра нельзя!

Регистр статуса (или ошибок) MAX31865 (адрес 0x07)

В регистр статуса преобразователь MAX31865 выставляет следующие признаки (если они возникают):

Регистр статуса преобразователя MAX31865
Регистр статуса преобразователя MAX31865
Назначение битов регистра статуса MAX31865
D7Выше заданного верхнего порога температуры.
D6Ниже заданного нижнего порога температуры.
D5Напряжение на REFIN- слишком высокое (REFIN- – один из входов опорного напряжения на резисторе Rref).
D4Напряжение на REFIN- слишком низкое.
D3Напряжение на RTDIN- слишком низкое (RTDIN- – один из входов термодатчика RRTD).
D2Ошибка питания (напряжение превышено или слишком низкое).
D1, D0Не важны.

Сбросить значение регистра состояния можно указав лог. "1" в разряде D1 регистра конфигурации, рассмотренного выше.

Регистры данных с термодатчика MAX31865 (адреса 0x01, 0x02)

Сюда MAX31865 записывает измеренные значения с термодатчика. Эти регистры доступны только для чтения. Преобразование считанного значения в градусы Цельсия рассмотрим чуть позже.

Регистры установки порогов срабатывания оповещения термодатчика MAX31865 (адреса 0x03…0x06)

Можно настроить диапазон температуры, при выходе за который чип MAX31865 оповестит об этом событии (запишет в регистр статуса соответсвующий признак). По умолчанию используется весь доступный диапазон от 0x0000 до 0xFFFF.

Теперь, имея необходимую информацию о регистрах чипа, применим эти знания на практике. По традиции, сначала научимся работать с чипом MAX31865 посредством микросхемы FT2232 или аналогичной и программы SPI via FTDI.

Подключим модуль к порту A микросхемы FT2232 стандартным образом:

Вывод модуля MAX31865Вывод FT2232
CLKADBUS0
SDIADBUS1
SDOADBUS2
CSADBUS3
VINЛюбой из BDBUS0…BDBUS7 (GPIO0…GPIO7) или можно использовать внешний источник питания напряжением 3-5 В.
GNDGND

Должно быть как-то так. Правда, несмотря на то, что у меня 4-проводный датчик, в данном случае я подключил его по схеме 2-проводного. Т.е. 2 дополнительных провода на тех же клеммах, что и выводы терморезистора. Перемычки на модуле уже распаяны, и не было времени перепаивать их для 4-проводного режима. При таком подключении, конечно, теряется преимущество 4-проводного модуля, но для задачи тестирования и демонстрации принципов управления преобразователем это допустимо.

Модуль MAX31865 подключён к отладочной плате с FT2232
Модуль MAX31865 подключён к отладочной плате с FT2232

Теперь запустим программу SPI via FTDI. В меню «Устройство» выберем интерфейс SPI, если выбран не он. Программа произведёт поиск подключённых устройств FTDI и добавит их во вкладки. Нас будет интересовать порт A, который появится в первой вкладке. Нажмём на ней правой кнопкой мыши и нажмём «Подключить». Теперь зададим режим SPI: MODE1 (п.1 на рисунке). Остальные параметры здесь можно не трогать. В разделе записи поставим флажок «Команда» и введём команду 80 C6 (п.2 ). Нажмём кнопку «Записать», и программа отправит эту команду чипу MAX31865.

Настройка программы для работы с MAX31865
Настройка программы для работы с MAX31865

Почему команда 80 C6? Здесь 80 – это адрес для записи в регистр конфигурации, см. таблицу с картой регистров. А C6 – это значение регистра конфигурации. В двоичном виде 0xC6=1100_0110. Как было описано выше, здесь мы подаём ток на термодатчик, выставляем непрерывный режим измерения, указываем, что датчик подключён по 2-проводной схеме, что ошибки нужно обрабатывать автоматически, а также сбрасываем регистр статуса.

Физически на линиях данных интерфейса SPI во время отправки команды происходит вот что. Линия CS опускается в логический "0". На линии CLK генерируются 16 тактовых импульсов для передачи 2-х байт информации (16 бит). Мастер (в данном случае микросхема FT2232H) в это время на линию SDI выставляет данные (это и есть наша команда 0x80 0xC6).

Временная диаграмма записи по SPI в регистр конфигурации MAX31865
Временная диаграмма записи по SPI в регистр конфигурации MAX31865

После этого можно начать вычитывать данные температуры. Для этого в разделе чтения указываем команду 00 (п.3 ) (это адрес регистра, с которого начинаем читать), укажем размер вычитываемых данных 8 (п.4 ). Это количество регистров чипа MAX31865. Можно прочитать данные один раз, а можно в поле количества раз поставить 0 (п.5 ), что будет означать непрерывное чтение из MAX31865. Нажмём кнопку «Прочитать». Чтобы увидеть принимаемые данные, нажмём на кнопку с изображением таблицы, расположенную слева от кнопки чтения (п.6 ). Откроется окно с принимаемыми данными (п.7 ).

В данных нас в первую очередь интересуют байты по адресам 1 и 2. Это, собственно, и есть значение температуры в «попугаях», а точнее – в отсчётах АЦП. В данном случае 0x43FC. Байт 7, как мы помним, это значение регистра статуса. В примере он 0, а значит работа идёт без проблем и замечаний. Байты со 2 по 6 – это пороги температуры. Мы их не задавали, поэтому они имеют значения по умолчанию 0xFFFF и 0x0000.

Во время чтения физически на линиях: CS опускается в лог."0", на линии CLK генерируется столько тактовых импульсов, сколько байтов нужно прочитать и отправить, умноженное на 8. Например, в данном случае мы отправили однобайтовую команду 0x00, а затем запросили 8 байтов. Т.е. в сумме обмен состоит из 9-ти байтов. Значит, тактовых импульсов 9×8=72. Первый байт передаётся по линии SDI от ведущего (FT2232H) к ведомому, а следующие 8 уже от ведомого (MAX31865) к ведущему на линии SDO. И эти 8 байт как раз и есть содержимое 8-ми регистров MAX31865. На временной диаграмме это 0xC1 0x44 0x0E 0xFF 0xFF 0x00 0x00 0x00.

Временная диаграмма чтения по SPI всех регистров MAX31865, начиная с 0-го
Временная диаграмма чтения по SPI всех регистров MAX31865, начиная с 0-го

Теперь нужно перевести полученное значение в температуру. MAX31865 возвращает код напряжения, измеренного аналого-цифровым преобразователем на терморезисторе. Сначала необходимо посчитать сопротивление термодатчика по формуле:RRTD = (ADC_Code × RREF) / 215 / 2, где ADC_Code – прочитанное из регистров 1 и 2 значение, RREF – сопротивление опорного резистора, а 215 – диапазон 15-разрядного АЦП. В приведённом случае, например, получим:

    RRTD = 0x440E * 430 Ом / 32768 / 2 = 114,3 Ом.

Теперь, зная сопротивление термодатчика RTD, можно определить температуру. Обычно это делается по таблицам или рассчитывается по формулам. Но проще всего воспользоваться т.н. коэффициентом α. Это коэффициент, который показывает на сколько Ом изменяется сопротивление RTD при изменении температуры на 1°C. Обычно «альфа» равняется 0,385 или 0,392 (точное значение указывается в документации на датчик). В моём случае α=0,3851.

Указание коэффициента α на датчик PT100
Указание коэффициента α на датчик PT100

Использование α рекомендуется в диапазоне температур от 0 до 100°C.

Таким образом, чтобы вычислить температуру по коэффициенту α, необходимо: TRTD = (RRTD − 100) / α или

    TRTD = (114,3 - 100) / 0,3851 = 37,13°C.

4Управление преобразователем MAX31865c Arduino без библиотек

Итак, мы постигли основы работы с термодатчиком PT100 и преобразователем MAX31865. Наших знаний теперь хватит для того чтобы написать свою реализацию опроса резистивного температурного детектора. Подключим модуль с датчиком температуры к Arduino. Схема подключения была приведена выше в разделе 2.

Модуль с MAX31865 и термодатчиком PT100 подключены к Arduino Nano
Модуль с MAX31865 и термодатчиком PT100 подключены к Arduino Nano

Давайте немного разовьём идею. Один раз в секунду будем опрашивать температуру с MAX31865. Перед тем как возвращаеть измерение пользователю, будем смотреть вывод готовности данных DRDY и забирать данные только по готовности, а в случае неготовности данных оповещать об этом. Также добавим отключение датчика после каждого измерения до следующего.

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

Скетч будет такой.

Скетч чтения температуры с MAX31865 без библиотек (разворачивается)
#include <SPI.h>

#define RREF          430.0  // опорное сопротивление
#define RNOMINAL      100.0  // номинальное сопротивление термодатчика
#define ALPHA         0.3851 // коэффициент "альфа"
#define CFG_REG_READ  0x00   // адрес для чтения конфигурационного регистра
#define CFG_REG_WRITE 0x80   // адрес для записи конфигурационного регистра
#define DRDY          9      // пин готовности данных - D9

SPISettings spiSettings(1000000, MSBFIRST, SPI_MODE1); // настройки SPI 

void setup() {
  SPI.begin(); // инициализируем SPI
  Serial.begin(115200); // инициализируем UART
  Serial.println("Test MAX31865 and temperature detector PT100");
}

void loop() {
  start_conversion(); // запускаем процесс измерения температуры
  if (check_data_ready()) // проверяем готовность данных
  {
    int rtd = read_rtd(); // читаем показания термодатчика
    float temp = convert_to_temperature(rtd); // переводим показания в температуру
    Serial.println("T = " + (String)temp); // выводим температуру в консоль
  }
  else 
  {
    Serial.println("Data NOT ready"); // сообщаем о неготовности данных
  }
  stop_conversion(); // останавливаем измерения на 1 сек
  delay(1000);
}

// Запускает однократное измерение.
void start_conversion() {
  SPI.beginTransaction(spiSettings); // начинаем передачу по SPI
  digitalWrite(SS, LOW); // активируем вывод выбора ведомого
  SPI.transfer(CFG_REG_WRITE); // передаём адрес записи конфигурационного регистра
  SPI.transfer(0xA2); // передаём значение в регистр (сделать однокаратное измерение, 2- или 4-проводная схема)
  digitalWrite(SS, HIGH); // деактивируем вывод выбора ведомого
  SPI.endTransaction(); // заканчиваем передачу по SPI
}

// Останавливает измерения.
void stop_conversion() {
  SPI.beginTransaction(spiSettings);
  digitalWrite(SS, LOW);
  SPI.transfer(CFG_REG_WRITE); // передаём адрес записи конфигурационного регистра
  SPI.transfer(0x02); // передаём значение в регистр (выключить измерения, сброс регистра статуса)
  digitalWrite(SS, HIGH);
  SPI.endTransaction();
}

// Проверяет готовность данных.
bool check_data_ready() {
  int data_ready = 1; // признак готовности данных
  int cnt = 0; // счётчик для ограничения числа повторов, чтобы не войти в бесконечный цикл опроса
  while ((data_ready == 1) && (cnt < 10)) { // несколько раз опрашиваем MAX31865 на предмет готовности данных
    data_ready = digitalRead(DRDY); // когда данные готовы, MAX31865 выставляет DRDY в "0"
    delay(10);
    cnt += 1;
  }
  return (data_ready != 1); // если "0", то данные готовы, если "1" - не готовы
}

// Читает показания термодатчика PT100.
int read_rtd() {
  SPI.beginTransaction(spiSettings);
  digitalWrite(SS, LOW);
  SPI.transfer(CFG_REG_READ); // передаём адрес чтения конфигурационного регистра 
  int inByte[8]; // массив для хранения значений 8-ми регистров MAX31865
  for (int i=0; i<8; i++) { // читаем 8 байт из MAX31865
    inByte[i] = SPI.transfer(0x00);
    Serial.print(inByte[i], HEX); Serial.print(" "); // вывод содержимого регистров
  }
  Serial.println();
  digitalWrite(SS, HIGH);
  SPI.endTransaction();
  int rtd = word(inByte[1], inByte[2]); // собирает значение из 2-х байт
  Serial.print("RTD = "); Serial.println(rtd);
  return rtd;
}

// Преобразует показания с датчика PT100 в градусы Цельсия.
float convert_to_temperature(int rtd) {
  float rrtd = rtd * RREF / 32768 / 2; // сопротивление термодатчика
  Serial.println("RRTD = " + (String)rrtd);
  float temp = (rrtd - RNOMINAL) / ALPHA; // перевод в температуру с помощью коэф-та α
  return temp;
}

После загрузки данного скетча в Arduino запустим монитор последовательного порта. В нём ежесекундно будет выводиться содержимое 8-ми регистров MAX31865, «сырые» показания АЦП чипа, сопротивление датчика PT100 и температура датчика:

Вывод скетча со значениями температуры датчика PT100
Вывод скетча со значениями температуры датчика PT100

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

]]>
manager@soltau.ru (aave) Arduino Thu, 19 Aug 2021 19:27:44 +0000
Как подключить и использовать NRF24L01 с Arduino https://soltau.ru/index.php/themes/arduino/item/589-kak-podklyuchit-i-ispolzovat-nrf24l01-s-arduino https://soltau.ru/index.php/themes/arduino/item/589-kak-podklyuchit-i-ispolzovat-nrf24l01-s-arduino Как подключить и использовать NRF24L01 с Arduino
Научимся передавать и принимать данные «по воздуху» с помощью чипа nRF24L01.

Для проекта нам понадобятся:

1Описание и характеристики приёмопередатчика NRF24L01

NRF24L01 – миниатюрный (4×4 мм) приёмопередатчик на одном чипе. Он очень дешёвый, при этом обеспечивает устойчивую радиосвязь на расстояниях в десятки метров (а при усилении и с хорошей антенной – сотни метров в зоне прямой видимости), и поэтому получил широкое распространение среди радиолюбителей. Основные технические характеристики чипа nRF24L01:

  • частота ~2,4 ГГц, 126 радиоканалов;
  • программируемая выходная мощность передатчика 0, -6, -12 или -18 дБм;
  • 3 скорости передачи: 250 кбит/с, 1 Мб/с и 2 Мб/с;
  • автоматическая обработка пакетов;
  • частотная модуляция GFSK радиосигнала;
  • ультранизкое потребление;
  • напряжение питания 1,9…3,6 В;
  • порты ввода-вывода толерантны к 5 В;
  • совместимость с nRF2401A, nRF2402, nRF24E1 и nRF24E2;
  • управление по цифровому 4-проводному интерфейсу SPI, скорость до 10 Мб/с.

NRF24L01 можно приобрести в составе готового модуля. Назначение выводов модуля показано на иллюстрации. На обратной стороне модуля радиоэлементов нет.

Внешний вид и назначение выводов приёмопередатчика nRF24L01
Внешний вид и назначение выводов приёмопередатчика nRF24L01

Для организации приёма и передачи понадобятся как минимум два подобных модуля с nRF24L01.

Выводы приёмопередатчика nRF24L01
НазваниеНазначениеПримечание
GNDЗемля
VDDПитание1,9…3,6 В. Если сигнал на MOSI/MISO >3,6 В, то питание не более 2,7…3,3 В.
CEЦифровой входChip Enable. Активирует режим приёма или передачи. Активный уровень "1" (HIGH).
CSNЦифровой входSPI Chip Select. Режим выбора устройства SPI. Активный "0" (LOW).
SCKЦифровой входSPI Clock. Линия синхронизации SPI.
MOSIЦифровой входВход данных SPI от контроллера к nRF24L01. Толерантность к 5 В.
MISOЦифровой выходВыход данных SPI от nRF24L01 к контроллеру. Толерантность к 5 В.
IRQЦифровой выходПин прерывания. Активный "0" (LOW).

По традиции, сначала подключим nRF24L01 к Arduino и проверим его работоспособность, используя существующие библиотеки. А затем детально рассмотрим протокол управления и научимся работать с приёмопередатчиком без библиотек.

2Подключение приёмопередатчика nRF24L01к Arduino

Подключим модули к Arduino, как показано на рисунке. Каждый модуль к своему контроллеру.

Схема подключения nRF24L01 к Arduino
Схема подключения nRF24L01 к Arduino

Модули подключаются одинаково, как можно догадаться. А программы в Ардуино будут разные. Один модуль будет работать в режиме приёмника, а другой – передатчика. Но сначала необходимо загрузить библиотеку для nRF24L01 и установить её обычным способом. Библиотека прилагается также в конце статьи.

Напишем для передатчика простейший скетч, который раз в секунду будет отправлять сообщение и инкрементируемый счётчик. Загрузим в одну из плат Arduino скетч передатчика:

Скетч передатчика nRF24L01 (разворачивается)
#include "SPI.h"  // подключаем библиотеку для работы с шиной SPI
#include "RF24.h" // подключаем библиотеку для работы с nRF24L01

const int CE = 9;
const int CSN = 10;
RF24 radio(CE, CSN); // создаём объект для работы с библиотекой RF24, указывая номера выводов nRF24L01
byte tx_data[32] = {
    0x48,0x65,0x6C,0x6C,0x6F,0x2C,0x20,0x53,
    0x6F,0x6C,0x74,0x61,0x75,0x2E,0x72,0x75,
    0x21,0x20,0x52,0x46,0x32,0x34,0x20,0x74,
    0x65,0x73,0x74,0x69,0x6E,0x67,0x20,0x20
    }; // массив для передачи данных
int cnt = 0; // счётчик

void setup() {
  Serial.begin(57600);
  radio.begin(); // инициализация nRF24L01
  radio.setChannel(115); // задаём канал передачи данных (0...127)
  radio.setDataRate(RF24_250KBPS); // задаём скорость передачи данных (RF24_250KBPS, RF24_1MBPS, RF24_2MBPS)
  radio.setPALevel(RF24_PA_MIN); // задаём минимальную мощность усилителя передатчика (RF24_PA_MIN=-18dBm, RF24_PA_LOW=-12dBm, RF24_PA_HIGH=-6dBm, RF24_PA_MAX=0dBm)
  radio.openWritingPipe(0x0123456789LL); // открываем "трубу" с заданным 40-разрядным адресом для передачи данных
  Serial.println("Transmitter started");
}

void loop() {
  tx_data[31] = cnt; // последнее число массива - инкрементируемый счётчик  
  if (radio.write(&tx_data, sizeof(tx_data))) {  // отправляем по радио массив, и если успешно,
    Serial.println((String)cnt + " sent"); // то выводим принятые данные в монитор порта
    if (cnt < 255) {
      cnt += 1;
    }
    else {
      cnt = 0;
    }
  }
  else { // в случае отсутствия подтверждения приёма со стороны приёмника
    Serial.println("No ACK");
  }
  delay(1000);
}

	

В другую Ардуинку загрузим скетч приёмника:

Скетч приёмника nRF24L01 (разворачивается)
#include "SPI.h"  // подключаем библиотеку для работы с шиной SPI
#include "RF24.h" // подключаем библиотеку для работы с nRF24L01

const int CE = 9;
const int CSN = 10;
RF24 radio(CE, CSN); // создаём объект для работы с библиотекой RF24, указывая номера выводов nRF24L01
byte rx_data[32]; // буфер для приёма данных

void setup(){
  Serial.begin(57600);
  radio.begin(); // инициализация nRF24L01
  radio.setChannel(115); // задаём канал передачи данных такой же, как у передатчика
  radio.setDataRate(RF24_250KBPS); // задаём скорость передачи данных как у передатчика
  radio.setPALevel(RF24_PA_MIN); // задаём минимальную мощность усилителя
  radio.openReadingPipe(0, 0x0123456789LL); // открываем "трубу" с заданным 40-разрядным адресом как у передатчика
  radio.startListening(); // включаем приёмник и начинаем прослушивать открытую "трубу"
  Serial.println("Receiver started");
}

void loop() {
  if (radio.available()) { // если в приёмном буфере имеются принятые данные
    Serial.println("Data received: ");
    radio.read(&rx_data, sizeof(rx_data)); // сохраняем данные в массив
    for (int i=0; i<sizeof(rx_data); i++) { // и выводим в монитор порта
      Serial.print(rx_data[i], HEX);
      Serial.print(" ");
    }
    Serial.println();
  }
}

В моём случае макет с подключёнными модулями выглядит так:

Подключение передатчика и приёмника nRF24L01 к Arduino
Подключение передатчика и приёмника nRF24L01 к Arduino

Часто пользователи сообщают о проблемах в питании данных модулей nRF24L01. Это проявляется в том, что приёма нет. В таком случае попробуйте припаять на свободное место модуля конденсатор ёмкостью 1…2 пФ.

Передатчик nRF24L01 подключён к Arduino Nano
Передатчик nRF24L01 подключён к Arduino Nano

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

Принимаемые радиоприёмником nRF24L01 пакеты в мониторе последовательного порта
Принимаемые радиоприёмником nRF24L01 пакеты в мониторе последовательного порта

Мы убедились, что модули работают, и что мы можем с помощью них передавать и принимать данные по радиоканалу. Теперь залезем под «капот» и разберёмся, как же управлять приёмопередатчиком nRF24L01.

3Режимы работы и управление приёмопередатчиком nRF24L01

Приёмопередатчик может находиться в четырёх состояниях: выключен, ожидание, приём, передача. Чип nRF24L01 имеет встроенную машину состояний, которая обеспечивает корректный переход между этими состояниями. Пользователь управляет состояниями, загружая в управляющие регистры специальные команды.

Режим выключения (power down mode)

В этом режиме чип потребляет минимальный ток, но может принимать команды по SPI от контроллера.

Режим ожидания (standby mode)

Есть два режима ожидания.

Первый режим ожидания (Standby-I) используется для снижения потребления. В этом режиме чип принимает команды по SPI, быстро переходит в режим передачи, и так же быстро возвращается обратно в режим ожидания 1. Для перехода в этот режим нужно подать на CE "HIGH" и в регистре CONFIG установить бит PWR_UP в "1".

Второй режим ожидания (Standby-II) более энергозатратен по сравнению с первым: чип работает полностью, и устройство готово к приёму и передаче практически моментально (около 130 мкс). Как только в передающем буфере оказываются данные, микросхема nRF24L01 начинает передачу. Для перехода в этот режим нужно держать CE в "HIGH" и в регистре CONFIG должен быть установлен бит PWR_UP в "1".

Режим приёма (RX mode)

Это режим, в котором чип nRF24L01 используется как приёмник. При этом он постоянно сканирует эфир на наличие валидных пакетов. Как только валидный пакет будет найден, он помещается в свободный слот приёмного буфера. Если буфер полностью заполнен, то пакет игнорируется.

Для перехода в режим приёма нужно установить биты PWR_UP и PRIM_RX в "1", и вывод CE установить в "HIGH". Режим будет держаться до тех пор, пока контроллер не переключит его в другой (например, режим ожидания или выключения).

Режим передачи (TX mode)

Режим передачи используется для передачи данных.

Для перехода в режим приёма нужно установить бит PWR_UP в "1", бит PRIM_RX в "0", и вывод CE установить в "HIGH". Данные передаются пакетами максимальной длиной по 32 байта. Режим будет держаться до тех пор, пока передаётся пакет. По окончанию передачи пакета чип перейдёт в режим ожидания 2 (standby-II).

Важно не держать nRF24L01 в режиме передачи дольше 4 мс! Чип поддерживает расширенный режим Enhanced ShockBurst, который соблюдает это требование. Кроме того, этот режим обеспечивает автоматическую обработку пакетов.

В таблице ниже сведены все условия для управления режимами чипа.

Режимы работы nRF24L01
РежимРегистр PWR_UPРегистр PRIM_RXВывод CEСостояние буфера
Передача111Не важно
Приём101Передача всех данных из передающего буфера.
Приём101, импульсом минимум на 10 мксПередача 1 пакета данных из передающего буфера.
Ожидание-2101Буфер передачи пуст
Ожидание-11Не важно0Нет данных для передачи
Выключен0Не важноНе важноНе важно

Продолжение следует...

]]>
manager@soltau.ru (aave) Arduino Thu, 08 Jul 2021 17:32:57 +0000
Как подключить датчик температуры и влажности HTU21D https://soltau.ru/index.php/themes/arduino/item/588-kak-podklyuchit-datchik-temperatury-i-vlazhnosti-htu21d https://soltau.ru/index.php/themes/arduino/item/588-kak-podklyuchit-datchik-temperatury-i-vlazhnosti-htu21d Как подключить датчик температуры и влажности HTU21D
Рассмотрим подключение цифрового датчика температуры и влажности HTU21D и научимся считывать с него показания. Причём как с использованием готовых библиотек, так и без них.

Для проекта нам понадобится:

1Описание и характеристики датчикавлажности и температуры HTU21D

Датчик HTU21D является цифровым датчиком для измерения температуры и влажности с подключением по интерфейсу I2C. Каждый датчик индивидуально калибруется и тестируется на заводе. Основные технические характеристики:

  • напряжение питания: 1,5…3,6 В;
  • ток потребления: ≤ 4 мА, в режиме ожидания – 0,08 мкА;
  • диапазон измерения влажности: 0…100%;
  • диапазон измерения температуры: -40…+125°C с точностью ±0,3°C;

Датчик измеряет температуру с разрешением 12…14 бит и влажность с разрешением 8…12 бит. Разрешение может быть изменено программно.

На самом деле HTU21D является представителем целого небольшого семейства датчиков температуры и влажности. Все они начинают свои названия с HTU2xx. В названии датчиков заложена следующая информация:

  • Число после "HTU2" показывает точность измерения влажности: "0" соответствует ±5%, "1" соответствует ±3%.
  • Буква после численного обозначения показывает тип датчика: D – цифровой (интерфейс I2C), P – аналоговый (PWM, ШИМ), S – сигма-дельта модуляция (SDM), преобразуемая в аналоговую.
  • Опциональное "F" в конце означает наличие специального фильтра, который защищает датчик от пыли и влаги.
Наименование семейства датчиков HTU2xx
Наименование семейства датчиков HTU2xx

Сенсор имеет миниатюрные размеры. Зачастую датчик можно приобрести в составе готового модуля, который подключается непосредственно к контроллеру (Arduino или другому). Одна из многочисленных разновидностей готового модуля с датчиком показана на фотографии.

Модуль с датчиком HTU21D
Модуль с датчиком HTU21D

На данном модуле минимум радиодеталей: собственно, сам датчик HTU21D, два подтягивающих линии SCL и SDA резистора номиналом по 4,7 кОм, а также фильтрующий конденсатор по питанию.

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

2Подключение датчика влажности и температуры HTU21D к Arduino

Датчик HTU21D управляется по интерфейсу I2C, и ему необходим 3-вольтовый сигнал управления (абсолютный максимум для ножек ввода-вывода, согласно техническому описанию, составляет 3,9 В). Arduino генерирует 5-вольтовый сигнал. Поэтому желательно подключать датчик с помощью преобразователя логического уровня по такой схеме:

Схема подключения датчика HTU21D к Arduino через преобразователь логического уровня
Схема подключения датчика HTU21D к Arduino через преобразователь логического уровня

На данной схеме показаны резисторы 4.7 кОм, которые подтягивают линии SCL и SDA к питанию 3,3 В. Если вы замкнули перемычку в модуле, то эти резисторы не нужны.

Для того чтобы использовать датчик, скачаем библиотеку HTU21D (она также приложена в конце статьи). Установим библиотеку как обычно. Загрузим пример SparkFun_HTU21D_Demo (File Examples SparkFun HTU21D humidity and temperature sensor breakout). В мониторе порта побегут измеренные значения температуры и влажности. Вживую это может выглядеть так:

Подключение датчика HTU21D к Arduino через преобразователь логического уровня
Подключение датчика HTU21D к Arduino через преобразователь логического уровня

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

3Чтение показаний датчика HTU21D без библиотек

Взаимодействие с датчиком осуществляется по интерфейсу I2C. Чтобы посылать команды датчику и читать ответы необходимо знать набор команд:

Команды датчика HTU21D
КомандаКод командыПримечание
Начать измерение температуры0xE3Удержание мастера
Начать измерение влажности0xE5Удержание мастера
Начать измерение температуры0xF3Без удержания мастера
Начать измерение влажности0xF5Без удержания мастера
Записать в пользовательский регистр0xE6
Прочитать пользовательский регистр0xE7
Сброс0xFE

Датчик поддерживает два режима измерений – с удержанием мастера и без удержания мастера (ведущего устройства). При получении команды с удержанием мастера датчик блокирует линию частоты SCK и все дальнейшие команды не будут выполняться, пока датчик не закончит измерения. В режиме без удержания датчик будет принимать следующие команды даже после начала измерений.

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

Структура пользовательского регистра датчика HTU21D
Структура пользовательского регистра датчика HTU21D

Сброс вызывает выключение датчика и повторное включение с настройками по умолчанию. Сброс занимает около 15 мс.

Для изучения датчика используем отладочную плату с микросхемой FT2232H. Она так же 3-вольтовая, как и датчик, и преобразователь уровня здесь не нужен. Подключим GND датчика к GND отладочной платы, 3.3V датчика к одной из ножек общего назначения GPIO отладочной платы (например, ADBUS7, как на фото), линию SCL интерфейса I2C датчика к выводу ADBUS0 платы, а линию SDA датчика – параллельно к выводам ADBUS1 и ADBUS2.

Подключение датчика HTU21D к FT2232H
Подключение датчика HTU21D к FT2232H

Подключим отладочную плату к USB порту компьютера. Далее воспользуемся программой SPI via FTDI для управления датчиком. Запустим её и выберем интерфейс I2C: меню «Устройство» Интерфейс I2C. Теперь нажмём Ctrl+C или «Подключить» в меню «Устройство». После подключения красный индикатор на вкладке устройства должен стать зелёным.

Выбор интерфейса I2C в настройках программы
Выбор интерфейса I2C в настройках программы

При подключении к FT2232 на портах GPIO обычно логическая единица. Соответственно, датчик HTU21D сразу включится. Для проверки можно нажать на кнопку «Сканировать». Программа проведёт поиск устройств на шине I2C и добавит адреса найденных устройств в выпадающий список. I2C адрес датчика HTU21D – 0x40. Теперь выберем из выпадающего списка наш адрес 0x40 (или 64 в десятичном представлении), и мы готовы посылать датчику команды и читать ответы.

Если датчик не включился, то идём в меню программы «Инструменты», выбираем «Чтение/запись GPIO», и подаём на вывод GPIO7 логическую единицу.

I2C адрес датчика HTU21D постоянный и несменяемый, поэтому нельзя на одну шину «повесить» одновременно несколько подобных датчиков. Однако можно включать питание датчиков таким образом, чтобы в один момент времени был активен только один датчик. Тогда можно иметь на одной шине I2C несколько датчиков HTU21D.

Сначала прочитаем пользовательский регистр датчика HTU21D чтобы узнать, в каком режиме он работает. Для этого ставим галочку в поле чтения в разделе «Команда» и вводим команду E7. Это значит, что перед чтением мы отправим датчику байт 0xE7, который, как мы помним, является командой чтения пользовательского регистра. Размер буфера указываем "1", т.к. необходимо прочитать один байт. Нажимаем на кнопку с таблицей для отображения принятых данных и нажимаем кнопку «Прочитать».

Чтение пользовательского регистра датчика HTU21D
Чтение пользовательского регистра датчика HTU21D

В приведённом примере датчик вернул число 0x02. В двоичном формате это 0000_0010. В соответствии с рисунком выше, где представлена структура пользовательского регистра, получается, что датчик настроен на разрешение при измерении влажности 12 бит, разрешение температуры 14 бит, получает питание более 2,25 В, встроенный нагреватель отключён, а также отключена перезагрузка регистра при включении. Что, кстати, соответствует значению по умолчанию для этого регистра. А вот так выглядит сигнал на шине I2C во время обмена данными с датчиком:

Временная диаграмма чтения пользовательского регистра датчика HTU21D по I2C
Временная диаграмма чтения пользовательского регистра датчика HTU21D по I2C

На линии SCL передаются по 9 тактовых импульсов для синхронизации данных: первые 8 – для синхронизации данных (передние фронты этих импульсов обозначены белыми стрелками), а во время 9-го импульса мастер проверяет наличие сигнала ACK от ведомого. Сигнал подтверждения ACK – это когда ведомый «прижимает» линию SDA к земле.

На верхнем рисунке мастер (в нашем случае микросхема FT2232H) отправляет на линию данных SDA шины I2C информацию, что сейчас будет передавать данные устройству с адресом 0x40, и получает подтверждение (ACK, от английского acknowledge) от ведомого (датчика HTU21D), что на шине имеется устройство с данным адресом. Если бы устройства с адресом 0x40 не было на шине, мастер бы не получил ответа, и дальнейший обмен не состоялся бы. Далее мастер отправляет на шину команду 0xE7 и получает подтверждение от ведомого, что он принял команду (ACK).

После этого (нижний рисунок) мастер отправляет на шину I2C сообщение, что сейчас будет читать из устройства с адресом 0x40, и получает от ведомого подтверждение (ACK). Данные 0x02 выставляет на шину уже ведомый. После этого ведомый не выставляет подтверждение (NAK, not acknowledge), что означает, что он закончил передачу.

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

Теперь самое интересное: давайте же прочитаем показания температуры и влажности.

Как уже упоминалось, датчик HTU21D может работать в двух режимах: с удержанием ведущего (hold master) и без удержания ведущего (no hold master). В режиме с удержанием ведущего датчик притягивает линию тактовых импульсов SCK к земле, и таким образом мастер оказывается в режиме ожидания. Пока датчик не закончит измерения, обмен приостанавливается. Однако в программе SPI via FTDI не реализовано такое поведение. Поэтому остаётся только использовать режим без удержания мастера.

В режиме без удержания мастера ведомый возвращает данные, как только они будут готовы, по запросу ведущего. Если запрос на чтение от ведущего пришёл, а данных у ведомого ещё нет, ведомый просто не выставляет ACK на шине. Это показано на рисунке ниже. Здесь голубым цветом отмечены участки, которые выставляет на шину ведомый, т.е. датчик HTU21D. Как видно, это 3 байта данных и несколько подтверждений ACK.

Последовательность обмена с датчиком HTU21D в режиме без удержания мастера (no hold master)
Последовательность обмена с датчиком HTU21D в режиме без удержания мастера (no hold master)

Чтобы прочитать данные температуры датчика HTU21D в программе необходимо в разделе записи указать команду F3 и нажать кнопку «Записать». После этого в разделе чтения указать число байтов для чтения. В данном случае это будет 3: два байта данных и один байт контрольной суммы. В приведённом примере датчик вернул 0x6C389D.

Настройки программы для чтения температуры с датчика HTU21D в режиме без удержания мастера (no hold master)
Настройки программы для чтения температуры с датчика HTU21D в режиме без удержания мастера (no hold master)

Вот как это преобразовать в значение температуры. В двоичном виде число 0x6C389D будет 01101100_00111000_10011101. Вместо значения битов статуса (см. предыдущий рисунок), какое бы значение там ни стояло, подставляем два бита "0". Далее объединяем младшую (LSB) часть со старшей (MSB) в одно число. Это будет значение STemp «в попугаях».

Данные (MSB)Данные (LSB)СтатусКонтрольная сумма
01101100001110000010011101
0x6C0x380x000x9D
0x6C38"STemp"0x9D

Если статус равен "00", значит в данных значение температуры; если статус "10", это данные влажности.

Далее подставляем STemp = 0x6C38 в формулу: T = STemp / 216 × 175.72 − 46.85 и получаем 24,68°C.

Контрольная же сумма считается по стандартному алгоритму 8-разрядных CRC. Мы уже разбирали этот алгоритм. Программу для расчёта контрольной суммы CRC можно скачать в конце этой статьи. Как видно из рисунка, если задать сообщение 0x6C38, то получим значение контрольной суммы как раз те самые 0x9D. Параметры для расчёта CRC приведены в техническом описании (datasheet) на датчик HTU21D.

Расчёт контрольной суммы показаний датчика HTU21D
Расчёт контрольной суммы показаний датчика HTU21D

Аналогично запрашивается значение влажности. Сначала отправляем команду датчику для начала измерения влажности: F5. А затем запрашиваем так же три байта.

Чтение показаний влажности датчика HTU21D
Чтение показаний влажности датчика HTU21D

В данном примере от датчика пришло число 0x6DAA32 или 01101101_10101010_00110010 в двоичном виде. Не забудем также, что вместо двух битов статуса записываются нули. Получаем:

Данные (MSB)Данные (LSB)СтатусКонтрольная сумма
01101101101010001000110010
0x6D0xA80x020x32
0x6DA8"SRH"0x32

Формула для пересчёта измеренного числа в проценты относительной влажности такая: RH = SRH / 216 × 125 − 6

Подставив 0x6DA8 в формулу получим значение влажности 47,54%.

4Чтение показаний датчика HTU21D без библиотек с помощью Arduino

Теперь, когда мы разобрались с протоколом обмена, давайте сделаем то же самое с помощью Arduino. Схему мы уже собирали. На этот раз давайте соберём схему без преобразователя уровня: если подключать датчик ненадолго, это не критично (нам же только провести один тест). В «боевой» схеме преобразователь уровня категорически рекомендуется. SCL и SDA шины I2C подтянем к питанию 3,3 В через резисторы номиналом 4,7…10 кОм.

Подключение датчика HTU2xx
Наименование семейства датчиков HTU2xx

А скетч будет такой:

Скетч чтения показаний температуры и влажности с HTU21D без библиотек (разворачивается)
#include <Wire.h>

const int htu21_addr = 0x40; // адрес датчика htu21d

void setup() {
  Serial.begin(9600);
  Wire.begin(); // инициализация i2c
}

void loop() {
  ReadTemperature();  
  ReadHumidity();
  delay(1000);
}

// Читает с датчика HTU21D температуру и выводит в COM-порт.
void ReadTemperature() {
  Wire.beginTransmission(htu21_addr); // начинаем общение с датчиком
  Wire.write(0xF3); // записываем команду на измерение температуры
  Wire.endTransmission(); // окончание передачи

  delay(100); // ждём готовности данных
  Wire.requestFrom(htu21_addr, 3); // запрашиваем 3 байта  

  int Stemp; // "сырые" данные температуры
  while (Wire.available()) { 
    int tmsb = Wire.read(); // читаем MSB температуры с датчика
    int tlsb = Wire.read() & 0xFC; // читаем LSB температуры с датчика и сбрасываем 2 младших бита
    int crc = Wire.read(); // читаем котрольную сумму    
    Stemp = tmsb * 256 + tlsb; // вычисляем значение Stemp
    // *256 это эквивалентно сдвигу на 8 бит влево: <<8 
    //Serial.println(Stemp, HEX);
  }
  double t = ConvertTemp(Stemp); // вычисляем температуру
  Serial.println("T = " + (String)t + "C"); // Выводим данные в COM-порт
}

// Преобразует температуру из "сырых" показаний датчика HTU21D в градусы Цельсия.
double ConvertTemp(int stemp){
  double t = stemp * 0.00268127 - 46.85; // 0.00268127 = 175.72/0x10000
  return t;
}

// Читает с датчика HTU21D относительную влажность и выводит в COM-порт.
void ReadHumidity() {
  Wire.beginTransmission(htu21_addr); // начинаем общение с датчиком
  Wire.write(0xF5); // записываем команду на измерение влажности
  Wire.endTransmission(); // окончание передачи

  delay(100); // ждём готовности данных
  Wire.requestFrom(htu21_addr, 3); // запрашиваем 3 байта  

  int Srh; // "сырые" данные влажности
  while (Wire.available()) { 
    int rmsb = Wire.read(); // читаем MSB влажность с датчика
    int rlsb = Wire.read() & 0xFC; // читаем LSB влажности с датчика и сбрасываем 2 младших бита
    int crc = Wire.read(); // читаем котрольную сумму
    Srh = rmsb * 256 + rlsb ; // вычисляем значение Srh
    //Serial.println(Srh, HEX);
  }
  double rh = ConvertRH(Srh); // вычисляем влажность
  Serial.println("RH = " + (String)rh + "%"); // Выводим данные в COM-порт
}

// Преобразует влажность из "сырых" показаний датчика HTU21D в проценты.
double ConvertRH(int srh) {
  double rh = srh * 0.0019073 - 6; // 0.0019073 = 125/0x10000
  return rh;
}

При запуске монитора последовательного порта увидим следующее:

Вывод показаний датчика HTU21D в монитор порта
Вывод показаний датчика HTU21D в монитор порта

Как видно, мы научились читать и расшифровывать показания датчика HTU21D с помощью Ардуино и без применения сторонних библиотек.

]]>
manager@soltau.ru (aave) Arduino Fri, 18 Jun 2021 19:56:26 +0000
Как подключить к Arduino преобразователь логического уровня сигнала https://soltau.ru/index.php/themes/arduino/item/586-kak-podklyuchit-k-arduino-preobrazovatel-logicheskogo-urovnya-signala https://soltau.ru/index.php/themes/arduino/item/586-kak-podklyuchit-k-arduino-preobrazovatel-logicheskogo-urovnya-signala Как подключить к Arduino преобразователь логического уровня сигнала
Разбираемся, для чего нужен преобразователь уровня сигнала и как им пользоваться.

Для проекта нам понадобится:

1Назначение и описание преобразователя уровня сигнала

Для коммуникации с цифровыми устройствами (датчиками, контроллерами и т.д.) и управления ими используется цифровой сигнал. Цифровой сигнал – это такой сигнал, в котором вся необходимая информация кодируется двумя уровнями напряжения, т.н. логическим нулём и логической единицей. За логический "0" обычно (но не обязательно) принимают напряжение 0 В, за логическую "1" – высокий уровень напряжения. Высокий он называется только относительно логического нуля. По факту это обычно довольно слабые напряжения. Самые распространённые на сегодня напряжения для передачи цифрового сигнала это 5 В и 3.3 В. Техника развивается, технологии изготовления цифровых устройств не стоят на месте. Поэтому в последнее время также встречаются более низкие напряжения 2.4 и 1.2 В. Подробнее о напряжениях, применяемых в цифровой технике, можно почитать здесь.

Если ваш микроконтроллер использует 5-вольтовую логику (как, например, Arduino), то нельзя просто взять и подключить к нему напрямую устройство, в котором применяется другой логический уровень. Для согласования уровней применяются специальные преобразователи напряжения.

В англоязычной литературе их могут называть по-разному: logic shifter, level shifter, level converter.

Они бывают одноканальные, но чаще встречаются многоканальные. Например, 2-канальные, 4-канальные, 8-канальные и другие. Число каналов показывает, сколько разных логических сигналов можно подключить к данному преобразователю. Например, если вы применяете 4-проводный интерфейс SPI для связи с вашим датчиком, то необходимо применять 4-канальный преобразователь.

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

Пример 4-канального двунаправленного преобразователя уровня показан на фотографии. Его основной элемент – четыре транзистора BSS138, которые обеспечивают быструю коммутацию сигналов.

4-канальный преобразователь логического уровня на транзисторах BSS138
4-канальный преобразователь логического уровня на транзисторах BSS138

У показанного преобразователя есть два ряда контактов. С одной стороны подключаются сигналы низковольтового устройства (LV), с другой – сигналы высоковольтового (HV). К входам-выходам HV1…HV4 подключаются сигнальные линии высоковольтного устройства, а к входам-выходам LV1…LV4 – линии низковольтового устройства. К входу питания HV подключается питание высоковольтной части, к входу питания LV – питание низковольтной части схемы. Земля GND – общая.

Иногда каналы обозначаются не HV и LV, а по-другому. Например, A и B.

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

Ещё один пример преобразователя напряжения уровня – модуль HW-221 на основе микросхемы TXS0108E.

8-канальный преобразователь логического уровня на микросхеме TXS0108E
8-канальный преобразователь логического уровня на микросхеме TXS0108E

Здесь уже имеются 8 вводов-выводов. Причём к выводам со стороны порта A (A1…A8) должна подключаться низковольтовая логика, а к выводам B1…B8 – высоковольтная. Соответственно, питание VCCA должно быть от 1.4 до 3.6 В, а питание VCCB – от 1.65 до 5.5 В. Напряжение VCCA должно быть не больше, чем VCCB. Также на данном модуле присутствует вход разрешения работы OE. Работа разрешена при подключении его к питанию VCCA. При подключении OE к земле, все вводы-выводы переходят в третье состояние.

2Пример подключения преобразователя логического уровня

Давайте посмотрим на практическом примере, как работает преобразователь уровня.

Для этого подключимся к какому-нибудь 3-вольтовому датчику, например, датчику температуры и влажности HTU21D. Этот датчик управляется по интерфейсу I2C, и ему необходим 3-вольтовый сигнал управления. В то же время Arduino генерирует 5-вольтовый сигнал. Тут нам и придёт на помощь преобразователь логического уровня. Соединим устройства по такой схеме:

Схема подключения датчика HTU21D к Arduino через преобразователь логического уровня
Схема подключения датчика HTU21D к Arduino через преобразователь логического уровня

Для того чтобы использовать датчик, скачаем библиотеку HTU21D (она также приложена внизу статьи). Установим библиотеку как обычно. Загрузим пример SparkFun_HTU21D_Demo (File Examples SparkFun HTU21D humidity and temperature sensor breakout). В мониторе порта побегут измеренные значения температуры и влажности. Вживую это выглядит так:

Работа с датчиком HTU21D посредством Arduino и преобразователя логического уровня
Работа с датчиком HTU21D посредством Arduino и преобразователя

Сгорит ли датчик HTU21D, если его подключить напрямую к Arduino без преобразователя уровня? Вряд ли. Но он будет работать на повышенном напряжении, что сократит срок его службы на неопределённое время. Кроме того, датчик может греться, а значит, будет искажать показания и температуры, и влажности. Также возможны «глюки» в управлении. Поэтому лучше всё же подключать датчик HTU21D к Arduino через конвертер уровня. На крайний случай, если его нет, можно подключить линии SDA и SCL датчика через ограничительные резисторы сопротивлением ~330 Ом.

Более подробно о работе с сенсором HTU21D рассказывается в следующей статье.

]]>
manager@soltau.ru (aave) Arduino Wed, 03 Mar 2021 18:56:13 +0000
Как подключить MAX7219 к Arduino https://soltau.ru/index.php/themes/arduino/item/584-kak-podklyuchit-max7219-k-arduino https://soltau.ru/index.php/themes/arduino/item/584-kak-podklyuchit-max7219-k-arduino Как подключить MAX7219 к Arduino
Разбираемся, как подключить к Arduino драйвер 8-и разрядного светодиодного индикатора с последовательным интерфейсом MAX7219 и как им управлять. Сделаем часы реального времени с помощью нескольких объединённых LED-матриц.

Для проекта нам понадобятся:

1Описание драйвера MAX7219

Микросхема MAX7219 – это компактный драйвер дисплея, который позволяет управлять 7-сегментными индикаторами разрядностью до 8 цифр или 64 отдельными светодиодами. Сам драйвер управляется по последовательному интерфейсу (чаще всего SPI) с помощью микроконтроллера, например, Arduino. MAX7219 позволяет изменять состояние каждого сегмента или светодиода без обновления состояния всех остальных выводов. А также он позволяет создавать цепочку из аналогичных драйверов, чтобы управлять большим количеством подключённых светодиодов.

Микросхема MAX7219 выполнена в 24-выводном корпусе. Назначение и расположение выводов показаны ниже:

Конфигурация выводов и типичная схема подключения драйвера MAX7219
Конфигурация выводов и типичная схема подключения драйвера MAX7219

На рисунке справа представлена типичная схема подключения MAX7219. Как видно, драйвер управляется микроконтроллером по последовательному интерфейсу. К выходу сегментов и выходу разрядов подключён дисплей либо светодиодная матрица.

Назначение выводов драйвера MAX7219
Название выводаНазначение
DINВход последовательных данных для управления драйвером.
DOUTВыход последовательных данных для соединения в цепочку со следующим драйвером.
DIG 0...DIG 7Выходы управления разрядами 8-символьного сегментного дисплея.
SEG A...SEG G, DPВыходы управления отдельными сегментами и десятичной точкой.
CLKВход тактовой частоты последовательного интерфейса, до 10 МГц.
LOADВход загрузки данных. Данные «защёлкиваются» по фронту отрицательного импульса.
ISETРегулировка максимального потребляемого тока на один сегмент (следовательно, яркости).
V+Питание, 5±0.5 В.
GNDЗемля.

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

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

2Принципы управления драйвером MAX7219

Драйвер MAX7219 управляется по последовательному интерфейсу SPI. Никаких неожиданностей здесь нет, всё стандартно, как мы уже не раз разбирали. Выбор ведомого (CS) низким уровнем, скорость обмена до 10 МГц.

Данные передаются посылками по 16 бит. В первых 8-ми битах содержится адрес регистра, в который необходимо передать данные (или команду). А вторые 8 бит, собственно, данные. Для каждого регистра будут свои данные, т.к. регистры отличаются по назначению.

Формат командной посылки драйвера MAX7219
Формат командной посылки драйвера MAX7219

Регистров всего 14, чуть позднее мы познакомимся с большинством из них подробнее. В таблице адреса регистров обозначены как, например, 0xX3. Что это значит? Сигнатура "0x" говорит о том, что дальнейшее число записано в шестнадцатеричном виде. Далее идёт "X", что означает, что первая половина байта не влияет и может быть любой. Я буду здесь всегда ставить ноль. И последнее число, собственно, адрес регистра. В рассматриваемом примере "0xX3" адрес регистра – "3", и будем записывать его как "0x03".

Карта регистров драйвера MAX7219
Карта регистров драйвера MAX7219

MSB означает most significant bit, т.е. наиболее значимый бит. Это старший бит в байте. Напротив, LSB означает least significant bit, т.е. наименее значимый бит. Это младший бит байта.

3Подключение и работа с драйвером MAX7219

У меня в наличии есть готовый модуль с драйвером MAX7219 и светодиодной матрицей 1088AS (8 на 8 точек). У модуля есть 2 ряда выводов: VCC, GND, DIN, CS, CLK – это вход модуля, а с противоположной стороны VCC, GND, DOUT, CS, CLK – это выходы модуля.

Модуль с драйвером MAX7219ENG и LED матрицей 1088AS, вид сверху
Модуль с драйвером MAX7219ENG и LED матрицей 1088AS, вид сверху

Понятно, что VCC – это вход питания (5 В), а GND – земля. DIN – вход последовательных данных от микроконтроллера или от предыдущего в цепочке модуля; DOUT – это выход последовательных данных на следующий модуль. CS (он же LOAD) – загрузка данных в драйвер. CLK – вход тактовой частоты.

Модуль с драйвером MAX7219ENG и LED матрицей 1088AS, вид сбоку
Модуль с драйвером MAX7219ENG и LED матрицей 1088AS, вид сбоку

Точнее, под рукой нашлись восемь таких модулей Поэтому предлагаю сразу усложнить задачу: соединить в цепочку несколько драйверов MAX7219. Можно для примера сделать часы в формате ЧЧ:ММ:СС, по одному модулю на цифру или символ ":". Соединение будет предельно простым, каскадным. Это значит, что управляющий сигнал мы подадим только на вход первого модуля в цепочке, а далее соединим по цепочке выходы со входами всех модулей друг за другом. Для надёжности закрепим всю конструкцию на жёсткой основе из обрезка пластикового кабель-канала.

Кстати, 8 полностью включённых панелей при максимальной яркости светодиодов потребляют более 1 А. Необходимо их запитывать от блока питания.

Цепочка из LED матриц и драйверов MAX7219ENG
Цепочка из LED матриц и драйверов MAX7219ENG

Кроме того, раз делаем часы, то нужно откуда-то брать время. Значит, ещё нужен модуль с часами реального времени (RTC). Например, DS1302. Он будет хранить время. Мы с помощью Arduino будем считывать его и выводить на LED матрицы.

Модуль с часами реального времени DS1302
Модуль с часами реального времени DS1302

4Изучение драйвера MAX7219с помощью FT2232H

Но прежде чем подключать модули с MAX7219 к Arduino, попробуем поизучать их с помощью отладочной платы с микросхемой FT2232H. Она позволяет обмениваться по SPI с устройствами и не требует программирования. Все настройки делаются в программе SPI via FTDI, и для быстрого знакомства с новым устройством это очень удобно.

Для начала переведём все драйверы MAX7219 в цепочке в режим теста. Для этого необходимо в каждое из устройств в нашей цепочке передать команду 0F 01. В драйвере MAX7219 имеется сдвиговый регистр, и нам нужно «протолкнуть» команду по всем регистрам в цепочке. Чтобы команда прошла по всей цепочке, необходимо повторить её выдачу 8 раз (по числу драйверов в цепочке).

Настройки программы для перевода в режим теста 8-ми драйверов MAX7219
Настройки программы для перевода в режим теста 8-ми драйверов MAX7219

Почему команда именно такая? Смторим в документацию и видим, что адрес регистра Display Test – 0x0F. А чтобы перейти в режим теста, нужно в регистр Display Test записать 1 (см. таблицу 10 из технического описания драйвера MAX7219). В режиме теста должны загореться все светодиоды, подключённые к драйверу. Выполним команду один раз: зажглась первая LED панель, после второго выполнения команды уже горят две LED панели. После восьмикратного выполнения команды все восемь светодиодных панелей зажглись.

Цепочка из драйверов MAX7219 в режиме теста
Цепочка из драйверов MAX7219 в режиме теста

Чтобы выйти из режима теста, следует записать 8 раз команду 0F 00. Уже догадались, почему такая команда? Выход из тестового режима осуществляется записью нуля в регистр 0x0F.

Кстати, если в поле количества раз поставить 8, то можно записать команду за одно нажатие на кнопку «Записать».

После включения драйвер MAX7219 переходит в режим выключения, за который отвечает регистр Shutdown (0xXC). В этом режиме единственные команды, которые мы можем послать – это переход в режим теста и выход из него. Чтобы перевести устройство в рабочий режим, нужно в регистр 0x0C записать "1" (см. таблицу 3 технического описания):

Настройки программы для перевода MAX7219 в рабочий режим
Настройки программы для перевода MAX7219 в рабочий режим

Теперь устройство готово к приёму всех команд.

Драйвер MAX7219 может работать в режиме декодирования или без него. Причём можно включать и отключать режим декодирования для каждого из разрядов дисплея (или столбцов LED матрицы) индивидуально. Давайте отключим режим декодирования. Для этого нужно послать команду 09 00 восемь раз (см. таблицу 4 технического описания). В режиме без декодирования биты D0..D7 отвечают за сегменты индикатора. Или, как в нашем случае, за один ряд светодиодов LED панели.

Соответствие битов D0..D7 сегментам в режиме без декодирования
Соответствие битов D0..D7 сегментам в режиме без декодирования

Режим с декодированием используется, когда драйвер MAX7219 подключён к 7-сегментному индикатору. Этот режим позволяет передавать драйверу число, и он сам зажигает нужные сегменты дисплея, чтобы отобразить это число на дисплее. Но так как в нашем случае к драйверу подключена LED панель, то режим с декодированием нам не подходит.

Давайте для примера зажгём в первой строчке всех панелей убывающее число светодиодов: на первой панели в первой строчке будет гореть 8 светодиодов, на второй – 7, и так далее. Для этого мы должны так же послать 8 команд, но теперь они будут разные. За запись в первую строчку отвечает регистр Digit 0 (0x01). Кроме того, мы имеем сдвиговый регистр, поэтому команда, которую мы пошлём первой, окажется в последнем регистре. То есть сначала мы управляем последним регистром, затем предпоследним, и так далее. Таким образом, вот последовательность команд:

    01 01 (00000001)
    01 03 (00000011)
    01 07 (00000111)
    01 0F (00001111)
    01 1F (00011111)
    01 3F (00111111)
    01 7F (01111111)
    01 FF (11111111)

Первое число – номер регистра (Digit 0), второе число – то, что мы записываем в регистр. В скобках указано двоичное представление того, что записываем в регистр. Единицы будут горящими светодиодами на LED панели, нули – потухшими.

Горит первая строка с убывающим числом светодиодов
Горит первая строка с убывающим числом светодиодов

Теперь давайте изменим яркость свечения LED панели. Для этого необходимо в регистр Intensity (0x0A) записать число от 0 до 15 (0x0F), где "0" соответствует минимальной яркости, а 0x0F – максимальной (см. таблицу 7 технического описания). Я поставлю что-то около нуля, например, 1. Для этого нужно 8 раз отправить команду 0A 01.

Максимальная и минимальная яркости: управление яркостью драйвером MAX7219
Максимальная и минимальная яркости: управление яркостью драйвером MAX7219

Теперь мы знаем, как управлять драйвером MAX7219 и уже почти готовы подключить его к Arduino.

5Подключение драйвера MAX7219к Arduino

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

Бегущая строка на драйверах MAX7219 (разворачивается)
#include <SPI.h>
// #include <EEPROM.h>

#define SYM_WIDTH 6  // ширина символа
#define NUM_LINES 8  // число строк LED панели
#define NUM_DISP 8   // число LED матриц
#define DEL 100      // регулирует скорость бегущей строки
#define MAX_LEN 100  // максимальная длина сообщения

enum decodeMode : byte {
  no_decode = 0,
  decode_lo = 1,
  decode_hi = 0x0f,
  decode_full = 0xff
};

enum shutdownMode : byte {
  shutdown = 0,
  normal_operation = 1
};

enum intensity : byte {
  min = 0,
  x1 = 1,
  x2 = 2,
  x3 = 3,
  x4 = 4,
  x5 = 5,
  x6 = 6,
  x7 = 7,
  x8 = 8,
  x9 = 9,
  x10 = 0xA,
  x11 = 0xB,
  x12 = 0xC,
  x13 = 0xD,
  x14 = 0xE,
  max = 0xf
};

enum scanLimit : byte {
  dig0 = 0,  // показывать 1-ю линию
  dig01 = 1,
  dig02 = 2,
  dig03 = 3,
  dig04 = 4,
  dig05 = 5,
  dig06 = 6,
  dig07 = 7  // показывать все линии
};

// Массив символов (шрифт) в кодировке windows-1251.
byte characters[159][SYM_WIDTH] = {
  { 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000 },  // space, элемент 0, ASCII 32 => адрес 32-32
  { 0b00000000, 0b00000000, 0b01001111, 0b00000000, 0b00000000, 0b00000000 },  // !
  { 0b00000000, 0b00000111, 0b00000000, 0b00000111, 0b00000000, 0b00000000 },  // '' 
  { 0b00010100, 0b01111111, 0b00010100, 0b01111111, 0b00010100, 0b00000000 },  // #
  { 0b00100100, 0b00101010, 0b01111111, 0b00101010, 0b00010010, 0b00000000 },  // $
  { 0b00100011, 0b00010011, 0b00001000, 0b01100100, 0b01100010, 0b00000000 },  // %
  { 0b00110110, 0b01001001, 0b01010101, 0b00100010, 0b01010000, 0b00000000 },  // &
  { 0b00000000, 0b00000101, 0b00000011, 0b00000000, 0b00000000, 0b00000000 },  // ;
  { 0b00000000, 0b00011100, 0b00100010, 0b01000001, 0b00000000, 0b00000000 },  // (
  { 0b00000000, 0b01000001, 0b00100010, 0b00011100, 0b00000000, 0b00000000 },  // )
  { 0b00010100, 0b00001000, 0b00111110, 0b00001000, 0b00010100, 0b00000000 },  // * // 10
  { 0b00001000, 0b00001000, 0b00111110, 0b00001000, 0b00001000, 0b00000000 },  // +
  { 0b00000000, 0b01010000, 0b00110000, 0b00000000, 0b00000000, 0b00000000 },  // ,
  { 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00000000 },  // -
  { 0b00000000, 0b01100000, 0b01100000, 0b00000000, 0b00000000, 0b00000000 },  // .
  { 0b00100000, 0b00010000, 0b00001000, 0b00000100, 0b00000010, 0b00000000 },  // /
  { 0b00111110, 0b01010001, 0b01001001, 0b01000101, 0b00111110, 0b00000000 },  // 0
  { 0b00000000, 0b01000010, 0b01111111, 0b01000000, 0b00000000, 0b00000000 },  // 1
  { 0b01000010, 0b01100001, 0b01010001, 0b01001001, 0b01000110, 0b00000000 },  // 2
  { 0b00100001, 0b01000001, 0b01000101, 0b01001011, 0b00110001, 0b00000000 },  // 3
  { 0b00011000, 0b00010100, 0b00010010, 0b01111111, 0b00010000, 0b00000000 },  // 4 // 20
  { 0b00100111, 0b01000101, 0b01000101, 0b01000101, 0b00111001, 0b00000000 },  // 5
  { 0b00111100, 0b01001010, 0b01001001, 0b01001001, 0b00110000, 0b00000000 },  // 6
  { 0b00000011, 0b01110001, 0b00001001, 0b00000101, 0b00000011, 0b00000000 },  // 7
  { 0b00110110, 0b01001001, 0b01001001, 0b01001001, 0b00110110, 0b00000000 },  // 8
  { 0b00000110, 0b01001001, 0b01001001, 0b00101001, 0b00011110, 0b00000000 },  // 9
  { 0b00000000, 0b01101100, 0b01101100, 0b00000000, 0b00000000, 0b00000000 },  // :
  { 0b00000000, 0b01010110, 0b00110110, 0b00000000, 0b00000000, 0b00000000 },  // ;
  { 0b00001000, 0b00010100, 0b00100010, 0b01000001, 0b00000000, 0b00000000 },  // <
  { 0b00010100, 0b00010100, 0b00010100, 0b00010100, 0b00010100, 0b00000000 },  // =
  { 0b00000000, 0b01000001, 0b00100010, 0b00010100, 0b00001000, 0b00000000 },  // > // 30
  { 0b00000010, 0b00000001, 0b01010001, 0b00001001, 0b00000110, 0b00000000 },  // ?
  { 0b00110010, 0b01001001, 0b01111001, 0b01000001, 0b00111110, 0b00000000 },  // @
  { 0b01111110, 0b00010001, 0b00010001, 0b00010001, 0b01111110, 0b00000000 },  // A
  { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b00111110, 0b00000000 },  // B
  { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00100010, 0b00000000 },  // C
  { 0b01111111, 0b01000001, 0b01000001, 0b01000001, 0b00111110, 0b00000000 },  // D
  { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b01001001, 0b00000000 },  // E
  { 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000001, 0b00000000 },  // F
  { 0b00111110, 0b01000001, 0b01001001, 0b01001001, 0b00111010, 0b00000000 },  // G
  { 0b01111111, 0b00001000, 0b00001000, 0b00001000, 0b01111111, 0b00000000 },  // H // 40
  { 0b01000001, 0b01000001, 0b01111111, 0b01000001, 0b01000001, 0b00000000 },  // I
  { 0b00110000, 0b01000001, 0b01000001, 0b00111111, 0b00000001, 0b00000000 },  // J
  { 0b01111111, 0b00001000, 0b00010100, 0b00100010, 0b01000001, 0b00000000 },  // K
  { 0b01111111, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b00000000 },  // L
  { 0b01111111, 0b00000010, 0b00001100, 0b00000010, 0b01111111, 0b00000000 },  // M
  { 0b01111111, 0b00000100, 0b00001000, 0b00010000, 0b01111111, 0b00000000 },  // N
  { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00111110, 0b00000000 },  // O
  { 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000110, 0b00000000 },  // P
  { 0b00111110, 0b01000001, 0b01010001, 0b00100001, 0b01011110, 0b00000000 },  // Q
  { 0b01111111, 0b00001001, 0b00001001, 0b00011001, 0b01100110, 0b00000000 },  // R // 50
  { 0b01000110, 0b01001001, 0b01001001, 0b01001001, 0b00110001, 0b00000000 },  // S
  { 0b00000001, 0b00000001, 0b01111111, 0b00000001, 0b00000001, 0b00000000 },  // T
  { 0b00111111, 0b01000000, 0b01000000, 0b01000000, 0b00111111, 0b00000000 },  // U
  { 0b00001111, 0b00110000, 0b01000000, 0b00110000, 0b00001111, 0b00000000 },  // V
  { 0b00111111, 0b01000000, 0b00111000, 0b01000000, 0b00111111, 0b00000000 },  // W
  { 0b01100011, 0b00010100, 0b00001000, 0b00010100, 0b01100011, 0b00000000 },  // X
  { 0b00000011, 0b00000100, 0b01111000, 0b00000100, 0b00000011, 0b00000000 },  // Y
  { 0b01100001, 0b01010001, 0b01001001, 0b01000101, 0b01000011, 0b00000000 },  // Z
  { 0b01111111, 0b01000001, 0b01000001, 0b00000000, 0b00000000, 0b00000000 },  // [
  { 0b00000010, 0b00000100, 0b00001000, 0b00010000, 0b00100000, 0b00000000 },  // '\' // 60
  { 0b00000000, 0b00000000, 0b01000001, 0b01000001, 0b01111111, 0b00000000 },  // ]
  { 0b00000100, 0b00000010, 0b00000001, 0b00000010, 0b00000100, 0b00000000 },  // ^
  { 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b00000000 },  // _
  { 0b00000000, 0b00000001, 0b00000010, 0b00000100, 0b00000000, 0b00000000 },  // `
  { 0b00100000, 0b01010100, 0b01010100, 0b01010100, 0b01111000, 0b00000000 },  // a
  { 0b01111111, 0b01001000, 0b01000100, 0b01000100, 0b00111000, 0b00000000 },  // b
  { 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00100000, 0b00000000 },  // c
  { 0b00111000, 0b01000100, 0b01000100, 0b01001000, 0b01111111, 0b00000000 },  // d
  { 0b00111000, 0b01010100, 0b01010100, 0b01010100, 0b00011000, 0b00000000 },  // e
  { 0b00001000, 0b01111110, 0b00001001, 0b00000001, 0b00000010, 0b00000000 },  // f // 70
  { 0b00001100, 0b01010010, 0b01010010, 0b01010010, 0b00111110, 0b00000000 },  // g
  { 0b01111111, 0b00001000, 0b00000100, 0b00000100, 0b01111000, 0b00000000 },  // h
  { 0b00000000, 0b01000100, 0b01111101, 0b01000000, 0b00000000, 0b00000000 },  // i
  { 0b00100000, 0b01000000, 0b01000100, 0b00111101, 0b00000000, 0b00000000 },  // j
  { 0b01111111, 0b00010000, 0b00101000, 0b01000100, 0b00000000, 0b00000000 },  // k
  { 0b00000000, 0b01000001, 0b01111111, 0b01000000, 0b00000000, 0b00000000 },  // l
  { 0b01111000, 0b00000100, 0b00001000, 0b00000100, 0b01111000, 0b00000000 },  // m
  { 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b01111000, 0b00000000 },  // n
  { 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00111000, 0b00000000 },  // o
  { 0b01111100, 0b00010100, 0b00010100, 0b00010100, 0b00001000, 0b00000000 },  // p // 80
  { 0b00001000, 0b00010100, 0b00010100, 0b01111100, 0b00000000, 0b00000000 },  // q
  { 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b00001000, 0b00000000 },  // r
  { 0b01001000, 0b01010100, 0b01010100, 0b01010100, 0b00100000, 0b00000000 },  // s
  { 0b00000100, 0b00111111, 0b01000100, 0b01000000, 0b00100000, 0b00000000 },  // t
  { 0b00111100, 0b01000000, 0b01000000, 0b00100000, 0b01111100, 0b00000000 },  // u
  { 0b00011100, 0b00100000, 0b01000000, 0b00100000, 0b00011100, 0b00000000 },  // v
  { 0b00111100, 0b01000000, 0b00110000, 0b01000000, 0b00111100, 0b00000000 },  // w
  { 0b01000100, 0b00101000, 0b00010000, 0b00101000, 0b01000100, 0b00000000 },  // x
  { 0b00001100, 0b01010000, 0b01010000, 0b01010000, 0b00111100, 0b00000000 },  // y
  { 0b01000100, 0b01100100, 0b01010100, 0b01001100, 0b01000100, 0b00000000 },  // z // 90
  { 0b00000000, 0b00001000, 0b00110110, 0b01000001, 0b00000000, 0b00000000 },  // {
  { 0b00000000, 0b00000000, 0b01111111, 0b00000000, 0b00000000, 0b00000000 },  // |
  { 0b00000000, 0b01000001, 0b00110110, 0b00001000, 0b00000000, 0b00000000 },  // }
  { 0b00001000, 0b00000100, 0b00000100, 0b00001000, 0b00000100, 0b00000000 },  // ~ => элемент 94, ASCII 126 => адрес 126-32, далее пропущены 16*4 символов
  { 0b01111100, 0b00010010, 0b00010001, 0b00010001, 0b01111111, 0b00000000 },  // А => элемент 95, win-1251 192 => адрес 192-32-16*4-1
  { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b00110001, 0b00000000 },  // Б
  { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b00110110, 0b00000000 },  // В
  { 0b01111111, 0b00000001, 0b00000001, 0b00000001, 0b00000001, 0b00000000 },  // Г
  { 0b11000000, 0b01111100, 0b01000010, 0b01000001, 0b01111111, 0b11000000 },  // Д
  { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b01000001, 0b00000000 },  // Е
  { 0b01110111, 0b00001000, 0b01111111, 0b00001000, 0b01110111, 0b00000000 },  // Ж
  { 0b00100010, 0b01000001, 0b01000001, 0b01001001, 0b00110110, 0b00000000 },  // З
  { 0b01111111, 0b00010000, 0b00001000, 0b00000100, 0b01111111, 0b00000000 },  // И
  { 0b01111111, 0b00010000, 0b00001001, 0b00000100, 0b01111111, 0b00000000 },  // Й
  { 0b01111111, 0b00001000, 0b00010100, 0b00100010, 0b01000001, 0b00000000 },  // К
  { 0b01111100, 0b00000010, 0b00000001, 0b00000001, 0b01111111, 0b00000000 },  // Л
  { 0b01111111, 0b00000010, 0b00000100, 0b00000010, 0b01111111, 0b00000000 },  // М
  { 0b01111111, 0b00001000, 0b00001000, 0b00001000, 0b01111111, 0b00000000 },  // Н
  { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00111110, 0b00000000 },  // О
  { 0b01111111, 0b00000001, 0b00000001, 0b00000001, 0b01111111, 0b00000000 },  // П
  { 0b01111111, 0b00010001, 0b00010001, 0b00010001, 0b00001110, 0b00000000 },  // Р // 111
  { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00100010, 0b00000000 },  // С // 112
  { 0b00000001, 0b00000001, 0b01111111, 0b00000001, 0b00000001, 0b00000000 },  // Т
  { 0b01000111, 0b01001000, 0b01010000, 0b01010000, 0b00111111, 0b00000000 },  // У
  { 0b00011100, 0b00100010, 0b01111111, 0b00100010, 0b00011100, 0b00000000 },  // Ф
  { 0b01100011, 0b00010100, 0b00001000, 0b00010100, 0b01100011, 0b00000000 },  // Х
  { 0b00111111, 0b01000000, 0b01000000, 0b01000000, 0b01111111, 0b11000000 },  // Ц
  { 0b00001111, 0b00010000, 0b00010000, 0b00010000, 0b01111111, 0b00000000 },  // Ч
  { 0b01111111, 0b01000000, 0b01111100, 0b01000000, 0b01111111, 0b00000000 },  // Ш
  { 0b01111111, 0b01000000, 0b01111100, 0b01000000, 0b01111111, 0b11000000 },  // Щ
  { 0b00000001, 0b01111111, 0b01001000, 0b01001000, 0b00110000, 0b00000000 },  // Ъ
  { 0b01111111, 0b01001000, 0b01001000, 0b00110000, 0b01111111, 0b00000000 },  // Ы
  { 0b01111111, 0b01001000, 0b01001000, 0b01001000, 0b00110000, 0b00000000 },  // Ь
  { 0b00100010, 0b01001001, 0b01001001, 0b01001001, 0b00111110, 0b00000000 },  // Э
  { 0b01111111, 0b00001000, 0b00111110, 0b01000001, 0b00111110, 0b00000000 },  // Ю
  { 0b01110110, 0b00001001, 0b00001001, 0b00001001, 0b01111111, 0b00000000 },  // Я
  { 0b00000000, 0b00100100, 0b01010100, 0b01010100, 0b01111000, 0b01000000 },  // а
  { 0b00000000, 0b00111000, 0b01010100, 0b01010100, 0b00100100, 0b00000000 },  // б
  { 0b00000000, 0b01111100, 0b01010100, 0b01010100, 0b00101000, 0b00000000 },  // в
  { 0b00000000, 0b01111100, 0b00000100, 0b00000100, 0b00000000, 0b00000000 },  // г
  { 0b00000000, 0b10011000, 0b10100100, 0b10100100, 0b01111100, 0b00000000 },  // д
  { 0b00000000, 0b00111000, 0b01010100, 0b01010100, 0b01011000, 0b00000000 },  // е
  { 0b01101100, 0b00010000, 0b01111100, 0b00010000, 0b01101100, 0b00000000 },  // ж
  { 0b00000000, 0b00101000, 0b01000100, 0b01010100, 0b00101000, 0b00000000 },  // з
  { 0b00000000, 0b00111100, 0b01000000, 0b01000000, 0b01111100, 0b00000000 },  // и
  { 0b00000000, 0b00111100, 0b01000000, 0b01000010, 0b01111100, 0b00000000 },  // й
  { 0b00000000, 0b01111100, 0b00010000, 0b00101000, 0b01000100, 0b00000000 },  // к
  { 0b00000000, 0b01110000, 0b00001000, 0b00000100, 0b01111100, 0b00000000 },  // л
  { 0b01111100, 0b00001000, 0b00010000, 0b00001000, 0b01111100, 0b00000000 },  // м
  { 0b00000000, 0b01111100, 0b00010000, 0b00010000, 0b01111100, 0b00000000 },  // н
  { 0b00000000, 0b00111000, 0b01000100, 0b01000100, 0b00111000, 0b00000000 },  // о
  { 0b00000000, 0b01111100, 0b00000100, 0b00000100, 0b01111100, 0b00000000 },  // п
  { 0b00000000, 0b11111100, 0b01000100, 0b01000100, 0b00111000, 0b00000000 },  // р
  { 0b00000000, 0b00111000, 0b01000100, 0b01000100, 0b00101000, 0b00000000 },  // с
  { 0b00000000, 0b00000100, 0b01111100, 0b00000100, 0b00000000, 0b00000000 },  // т
  { 0b00000000, 0b10011100, 0b10100000, 0b10100000, 0b01111100, 0b00000000 },  // у
  { 0b00111000, 0b01000100, 0b11111110, 0b01000100, 0b00111000, 0b00000000 },  // ф
  { 0b00000000, 0b01101100, 0b00010000, 0b00010000, 0b01101100, 0b00000000 },  // х
  { 0b00000000, 0b00111100, 0b01000000, 0b01000000, 0b01111100, 0b11000000 },  // ц
  { 0b00000000, 0b00001100, 0b00010000, 0b00010000, 0b01111100, 0b00000000 },  // ч
  { 0b01111100, 0b01000000, 0b01111000, 0b01000000, 0b01111100, 0b00000000 },  // ш
  { 0b01111100, 0b01000000, 0b01111000, 0b01000000, 0b01111100, 0b11000000 },  // щ
  { 0b00000100, 0b01111100, 0b01010000, 0b01010000, 0b00100000, 0b00000000 },  // ъ
  { 0b01111100, 0b01010000, 0b01010000, 0b00100000, 0b01111100, 0b00000000 },  // ы
  { 0b00000000, 0b01111100, 0b01010000, 0b01010000, 0b00100000, 0b00000000 },  // ь
  { 0b00000000, 0b01000100, 0b01010100, 0b01010100, 0b00111000, 0b00000000 },  // э
  { 0b01111100, 0b00010000, 0b00111000, 0b01000100, 0b00111000, 0b00000000 },  // ю
  { 0b00000000, 0b01001000, 0b00110100, 0b00010100, 0b01111100, 0b00000000 }   // я
};

int inputIndeces[MAX_LEN];  // буфер для приёма индексов сообщения из последовательного порта
int curInputIndex = 0;      // индекс текущего принятого символа

int messageIndeces[MAX_LEN] = {
  110, 143, 135, 129, 132, 145, 12, 0, 51, 79, 76, 84, 65, 85, 14, 82, 85, 1, 0, 26, 9
};                    // индексы отображаемого сообщения в массиве шрифта
int messageLen = 21;  // длина текущего сообщения

byte buffer[NUM_LINES][NUM_DISP] = {
  { 0, 0, 0, 0, 0, 0, 0, 1 },
  { 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0 }
};

void setup() {

  InitLedPanel();
  InitBuffer();

  // storeFont();  // выполнить однократно, затем закомментировать

  for (int i = 0; i < 2; i++) {
    test(true);
    delay(50);
    test(false);
    delay(200);
  }
  delay(1000);

  for (int i = 0; i < 2; i++) {
    testLines();
  }
}

// Инициализирует SPI и последовательный порт, задаёт режим работы и яркость панели.
void InitLedPanel() {
  Serial.begin(115200);
  SPI.begin();

  pinMode(SS, OUTPUT);
  digitalWrite(SS, HIGH);

  setMode(normal_operation);
  setBrightness(min);
  setDecodeMode(no_decode);
  setScanLimit(dig07);
}

// Инициализирует пустой буфер.
void InitBuffer(void) {
  for (int l = 0; l < NUM_LINES; l++) {
    for (int d = 0; d < NUM_DISP; d++) {
      buffer[l][d] = 0x00;
    }
  }
}

void test(bool on) {
  for (int i = 0; i < NUM_DISP; i++) {
    digitalWrite(SS, LOW);
    SPI.transfer(0x0F);  // регистр самотестирования дисплея
    SPI.transfer((byte)on);
    digitalWrite(SS, HIGH);
  }
}

void testLines() {
  for (int j = 0; j <= 8; j++) {
    for (byte i = 1; i <= 8; i++) {

      digitalWrite(SS, LOW);

      SPI.transfer(i);
      SPI.transfer(0xFF >> j);

      SPI.transfer(i);
      SPI.transfer(0xFE >> j);

      SPI.transfer(i);
      SPI.transfer(0xFC >> j);

      SPI.transfer(i);
      SPI.transfer(0xF8 >> j);

      SPI.transfer(i);
      SPI.transfer(0xF0 >> j);

      SPI.transfer(i);
      SPI.transfer(0xE0 >> j);

      SPI.transfer(i);
      SPI.transfer(0xC0 >> j);

      SPI.transfer(i);
      SPI.transfer(0x80 >> j);

      digitalWrite(SS, HIGH);
    }
    delay(DEL);
  }
}

// Нормальный режим / выключение.
void setMode(shutdownMode value) {
  for (int i = 0; i < NUM_DISP; i++) {
    digitalWrite(SS, LOW);
    SPI.transfer(0x0C);
    SPI.transfer(value);
    digitalWrite(SS, HIGH);
  }
  Serial.println("Mode = " + (String)value);
}

// Задаёт яркость.
void setBrightness(intensity value) {
  for (int i = 0; i < NUM_DISP; i++) {
    digitalWrite(SS, LOW);
    SPI.transfer(0x0A);
    SPI.transfer(value);
    digitalWrite(SS, HIGH);
  }
  Serial.println("Brightness = " + (String)value);
}

// Режим декодирования.
void setDecodeMode(decodeMode value) {
  for (int i = 0; i < NUM_DISP; i++) {
    digitalWrite(SS, LOW);
    SPI.transfer(0x09);
    SPI.transfer(value);
    digitalWrite(SS, HIGH);
  }
  Serial.println("Decode mode = " + (String)value);
}

// Сколько цифр отображать.
void setScanLimit(scanLimit value) {
  for (int i = 0; i < NUM_DISP; i++) {
    digitalWrite(SS, LOW);
    SPI.transfer(0x0B);
    SPI.transfer(value);
    digitalWrite(SS, HIGH);
  }
  Serial.println("Scan limit = " + (String)value);
}

void loop() {
  for (int n = 0; n < messageLen; n++) {         // для каждого символа сообщения
    for (int col = 0; col < SYM_WIDTH; col++) {  // для каждого столбца символа

      ShiftBuffer();  // сдвигаем буфер, освобождается крайний правый столбец

      byte symCol = characters[messageIndeces[n]][col];  // текущий столбец символа
      //byte symCol = getEeSymbol(messageIndeces[n], col);  // текущий столбец символа из ППЗУ
      PushColumn(symCol);  // размещаем столбец символа в крайнем правом столбце буфера

      DisplayBuffer();  // отображаем текущий буфер
      delay(DEL);
    }
  }
}

/// Сохраняет шрифт в ППЗУ.
// void storeFont() {
//   int eeAddress = 0;  // адрес в ПЗУ
//   for (int n = 0; n < 159; n++) {
//     for (int col = 0; col < SYM_WIDTH; col++) {
//       EEPROM.put(eeAddress, characters[n][col]);
//       eeAddress += 1;
//     }
//   }
//   Serial.println("Font table stored to EEPROM");
// }

// // Возвращает символ из ППЗУ по адресу строки и столбца.
// byte getEeSymbol(int row, int col) {
//   byte sym;
//   int addr = row * SYM_WIDTH + col;
//   EEPROM.get(addr, sym);
//   return sym;
// }

// Сдвигает содержимое буфера на 1 столбец влево.
void ShiftBuffer() {
  for (int l = 0; l < NUM_LINES; l++) {
    byte msb = 0;  // старший бит числа
    for (int d = 0; d < NUM_DISP; d++) {
      if (d < NUM_DISP - 1) {
        msb = buffer[l][d + 1] >> 7;  // запомнили старший бит следующего числа
      } else {
        msb = 0;
      }
      buffer[l][d] = (buffer[l][d] << 1) | msb;  // сдвигаем на 1 бит и младший заполняем значением предыдущего старшего
    }
  }

  // Serial.println("Shifted:");  // для отладки
  // for (int l = 0; l < NUM_LINES; l++) {
  //   for (int d = 0; d < NUM_DISP; d++) {
  //     char buf[3];
  //     sprintf(buf, "%02x", buffer[l][d]);
  //     Serial.print(buf);
  //     Serial.print(" ");
  //   }
  //   Serial.println();
  // }
}

// Вдвигаем столбец символа в крайний правый столбец буфера.
void PushColumn(byte col) {
  for (int i = 0; i < NUM_LINES; i++) {                          // каждый бит столбца размещается в своей строке
    byte curBit = (col >> i) & 1;                                // текущий бит столбца
    buffer[i][NUM_DISP - 1] = buffer[i][NUM_DISP - 1] | curBit;  // размещаем в младшем разряде последнего байта буфера
  }
}

// Выводит текущий буфер на LED панель.
void DisplayBuffer() {
  for (int i = 0; i < NUM_LINES; i++) {
    WriteLine((byte)i, buffer[i]);
  }
}

// Выводит на LED панель заданную строку.
void WriteLine(byte line, byte *ar) {
  digitalWrite(SS, LOW);
  for (int i = 0; i < NUM_DISP; i++) {
    SPI.transfer(line + 1);              // адрес линии 1..8.
    SPI.transfer(ar[NUM_DISP - i - 1]);  // задвигаем байты "с конца"
  }
  digitalWrite(SS, HIGH);
}

// Определяет индекс символа в таблице шрифта.
int IndexInFontTable(String sym) {
  String w1251Sym = Utf8_Win1251(sym);  // для латиницы ничего не меняется, для кириллицы получаем значение больше 127 и меньшее 256.
  byte asciiVal = (byte)w1251Sym[0];
  byte fontIndex = asciiVal - 32;  // индекс символа в таблице шрифта
  if (asciiVal > 126) {
    fontIndex -= 65;  // 65 = 16*4+1 - столько символов пропущено в таблице шрифта, и сдвиг индекса на 1
  }
  Serial.println("orig=[" + sym + "]; win1251=" + w1251Sym + "; table_index=" + (String)fontIndex);
  return fontIndex;
}

// Перекодирует строку из UTF-8 в Windows-1251.
String Utf8_Win1251(String source) {
  String target;
  char m[2] = { '0', '\0' };
  int k = source.length();
  int i = 0;
  while (i < k) {
    unsigned char n = source[i];
    i++;
    m[0] = n;
    target = target + String(m);
  }
  return target;
}

void serialEvent() {
  while (Serial.available()) {

    char inSym = (char)Serial.read();  // принимаем символ из порта

    if ((inSym != '\n') && (inSym != '\r')) {  // если пришёл НЕ символ переноса строки

      int indexInFontTable = IndexInFontTable((String)inSym);  // ищем его индекс в таблице шрифтов
      inputIndeces[curInputIndex] = indexInFontTable;          // размещаем индекс во временном буфере
      curInputIndex += 1;                                      // запоминаем текущее положение в буфере

    } else {
      if (curInputIndex != 0) {  // защита от двойного сброса
        InitBuffer();
        Serial.println("New message indeces:");
        for (int i = 0; i < curInputIndex; i++) {
          messageIndeces[i] = inputIndeces[i];  // обновляем индексы выводимого сообщения
          Serial.print(String(messageIndeces[i]) + " ");
        }
        Serial.println();
        messageLen = curInputIndex;  // запоминаем размер сообщения
        curInputIndex = 0;
      }
    }
  }
}

Здесь закомментированы участки кода, которые позволят сохранить массив шрифта в ППЗУ Arduino, и таким образом сэкономить динамическую память. В следующем видео даны подробные разъяснения к коду, а также демонстрируется результат работы скетча.

Перед реализацией часов осталось освоить ещё несколько деталей проекта: вывод цифр на дисплей и часы реального времени DS1302. О работе с часами DS1302 подробно описано в этой статье.

]]>
Arduino Sat, 20 Feb 2021 20:34:00 +0000
Как подключить датчик RCWL-0516 к Arduino https://soltau.ru/index.php/themes/arduino/item/583-kak-podklyuchit-datchik-rcwl-0516-k-arduino https://soltau.ru/index.php/themes/arduino/item/583-kak-podklyuchit-datchik-rcwl-0516-k-arduino Как подключить датчик RCWL-0516 к Arduino
RCWL-0516 – это миниатюрный модуль, который представляет собой доплеровский радар. Он позволяет обнаруживать движение в пределах 5-9 метров, причём даже сквозь радиопрозрачные стены (дерево, кирпич). Рассмотрим его в деталях и…

Для проекта нам понадобятся:

1Описание, принцип действия и схема радара RCWL-0516

Датчик представляет собой модуль размером 35.9×17.3 мм и практически плоский, за исключением микросхемы BISS0001 (аналог RCWL-9196) на лицевой стороне и линейного стабилизатора напряжения 7133-1 на обратной. Благодаря линейному стабилизатору модуль можно питать напряжениями от 4 до 27 В, которое подаётся на вывод VIN. Внешний вид модуля приведён на фото.

Верхняя сторона датчика RCWL-0516
Верхняя сторона датчика RCWL-0516

Обратите внимание, что вывод 3V3 – это не вход питания, а выход линейника! На него подавать ничего не нужно. Можно с него брать напряжение 3.3 В (потребители до 30 мА).

Микросхема BISS0001 – это т.н. PIR-контроллер. Аббревиатура PIR происходит от Passive Infra-Red, т.е. пассивный инфракрасный детектор. Дело в том, что изначально микросхема разрабатывалась как контроллер именно для инфракрасных детекторов движения.

Нижняя сторона датчика RCWL-0516
Нижняя сторона датчика RCWL-0516

Датчик RCWL-0516 работает на эффекте Доплера. Напомню, эффект Доплера – это изменение частоты отражённой волны при движении наблюдаемого объекта. Модуль постоянно излучает в пространство радиоволновое излучение определённой частоты (около 3150 МГц). Отражаясь от объекта, волна возвращается и фиксируется датчиком. Если её частота несколько изменилась, значит, объект находится в движении.

В случае обнаружения движения датчик выставляет на выходе OUT логическую единицу (3.3 В). Причём датчик может работать в двух режимах: перезапускаемом (retriggerable) и неперезапускаемом (non-retriggerable).

  • перезапускаемый режим – датчик будет держать на выходе OUT логическую единицу так долго, сколько будет фиксировать движение;
  • неперезапускаемый режим – датчик будет держать на выходе OUT логическую единицу установленное время (от 2 до 300 секунд).

Режим задаётся так. Если на входе "1" микросхемы BISS0001 логическая единица – режим перезапускаемый, если логический ноль – неперезапускаемый. В данном модуле на входе "1" микросхемы 3.3 В, т.е. он работает в перезапускаемом режиме.

Для регулировки времени срабатывания триггера (времени удержания импульса на выходе OUT) служит место для конденсатора C-TM. Без установленного конденсатора время срабатывания триггера – 2 сек. Добавление ёмкости повысит длительность импульса триггера.

После срабатывания триггера датчик на некоторое время «слепнет». Такой же эффект происходит сразу после включения. Поэтому после включения датчику нужно дать время (обычно до 10 секунд) чтобы «успокоиться» и настроиться на окружающую обстановку.

Для регулировки дальности обнаружения датчика служит место для резистора R-GN. По умолчанию датчик настроен на максимальною дистанцию обнаружения 7…9 м. Добавление резистора сопротивлением 1 МОм снизит дистанцию примерно в 1.5…2 раза.

Вывод CDS соединён с выводом 9 микросхемы BISS0001, который позволяет отключить датчик (активация/деактивация). К этому выводу в параллель (на место CDS в углу платы на лицевой стороне модуля) можно подключить фоторезистор, который будет включать датчик только в тёмное время суток. А пока освещения достаточно, его сопротивление маленькое, и радиоизлучатель будет выключен. С помощью резистора на месте R-CDS можно регулировать порог срабатывания фоторезистора. Если же просто «посадить» выход CDS на землю, то датчик будет неактивен.

В приложении к статье можно скачать принципиальную схему и описание детектора RCWL-0516. Они сделаны для более ранней модификации данного модуля, но практически идентичны, за исключением нескольких деталей. Так, например, на схемах отсутствует линейный стабилизатор напряжения на входе питания. Но в измерительной части всё соответствует рассматриваемому устройству.

В документации приведена рекомендация по размещению датчика RCWL-0516. Датчик крепится на потолке помещения на высоте не более 7 метров.

Пример размещения датчика RCWL-0516 внутри помещения
Пример размещения датчика RCWL-0516 внутри помещения

Буквой L обозначен радиус действия радара. Естественно, это всё довольно условно, т.к. диаграмма направленности планарной антенны (а это именно тот тип антенны, который применяется у нас в датчике) несколько сложнее, чем полусфера, и она излучает в разных направлениях с разной мощностью. Но главная идея в том, чтобы обзор датчика был как можно шире, а перпендикуляр, проведённый из центра датчика, был направлен в центр области, которую необходимо контролировать.

2Подключение и работа радара RCWL-0516

Сначала проверим работоспособность датчика RCWL-0516 без Arduino. Для индикации подключим к выходу OUT датчика светодиод. Когда датчик будет детектировать движение, на выходе OUT будет появляться напряжение 3.3 В, и светодиод будет загораться.

Схема подключения датчика RCWL-0516
Схема подключения датчика RCWL-0516

А вот так это выглядит вживую:

Вывод срабатывания датчика RCWL-0516 на светодиод
Вывод срабатывания датчика RCWL-0516 на светодиод

Очевидно, что для чтения показаний датчика RCWL-0516 с помощью Arduino, достаточно прочитать логический уровень на любом входе. Например, будем использовать аналоговый вход A0. Но просто прочитать значение – слишком простая задача. Давайте будем передавать информацию о срабатывании датчика по радиоканалу на удалённое устройство. В роли удалённого устройства также будет Arduino. Радиоканал устроим с помощью уже известной нам пары XY-MK-5V и FS1000A (вот здесь и здесь).

Передатчик будет выглядеть так:

Передатчик показаний датчика RCWL-0516 на Arduino и FS1000A
Передатчик показаний датчика RCWL-0516 на Arduino и FS1000A

Здесь данные с выхода OUT датчика RCWL-0516 поступают на аналоговый вход A0 Arduino. А выход TX (D0) последовательного порта Arduino идёт на ножку DATA передатчика. Питается и датчик движения, и передатчик напряжением с выхода 5V Arduino.

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

Допустим, когда датчик RCWL-0516 в состоянии ожидания, мы будем передавать по радио число 0xF0, а когда датчик фиксирует движение, будем передавать 0x0F. Когда датчик зафиксирует движение, также будем зажигать встроенный светодиод Arduino. Таким образом, скетч передатчика будет такой:

Скетч передатчика показаний датчика RCWL-0516 (разворачивается)
  void setup() {
  Serial.begin(9600);  
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  int val = analogRead(A0);
  if (val > 500){ // фиксация движения, на выходе HIGH
     Serial.write(0x0F);
     digitalWrite(LED_BUILTIN, HIGH);
  } 
  else { // нет движения
    Serial.write(0xF0);
    digitalWrite(LED_BUILTIN, LOW);
  }
}
    

Давайте сначала подключим приёмник к компьютеру с помощью преобразователя UART-USB и посмотрим, что вообще мы принимаем из радиоэфира (см. статью для подробностей).

Данные, принимаемые из радиоэфира
Данные, принимаемые из радиоэфира

Видно, что в целом мы видим то, что и хотим увидеть: числа 0x0f и 0xf0. Но встречаются и искажения информации, вызванные шумами радиоэфира, от которых придётся избавляться. Самый простой способ – брать несколько соседних значений. И если они все равны ожидаемому, то считаем это за срабатывание. Поэтому скетч для приёмника будет несколько сложнее.

Продолжение следует...

]]>
manager@soltau.ru (aave) Arduino Sun, 17 Jan 2021 19:33:08 +0000
Как подключить дисплей HPDL-1414 к Arduino https://soltau.ru/index.php/themes/arduino/item/582-kak-podklyuchit-displej-hpdl-1414-k-arduino https://soltau.ru/index.php/themes/arduino/item/582-kak-podklyuchit-displej-hpdl-1414-k-arduino Как подключить дисплей HPDL-1414 к Arduino
Дисплей HPDL-1414 – это миниатюрный 4-символьный «умный» дисплей. Он имеет встроенную память для хранения набора предустановленных символов, встроенный декодер ASCII-кодов, а также необходимую «обвязку», т.е. готов для подключения прямо к…

Для работы нам понадобятся:

1Описание и подключение«умного» экрана HPDL-1414

Экран HPDL-1414 – это миниатюрный модуль размером 20×17.5 мм и высотой 6 мм без учёта высоты выводов. На экран одновременно можно выводить до 4-х символов. К недостаткам дисплея можно отнести то, что символы, которых нет в ПЗУ дисплея, вывести невозможно, т.е. произвольное управление сегментами дисплея не предусмотрено.

Размеры экрана HPDL 1414
Размеры экрана HPDL 1414

Высота каждого символа – 2.85 мм, ширина – 2.1 мм. Символ состоит из 16-ти сегментов и десятичной точки. Сегменты обозначаются латинскими буквами от A до M.

Сегменты и размеры символа экрана HPDL 1414
Сегменты и размеры символа экрана HPDL 1414

На боковой грани модуля, на которой расположен вывод 1, располагается маркировка вида HPDL 1414 YYWW Z. Здесь HPDL 1414 – название модуля; YYWW – это год и неделя изготовления, а Z – это категория интенсивности свечения.

Буквенно-цифровая маркировка экрана HPDL 1414
Буквенно-цифровая маркировка экрана HPDL 1414

Всего имеются 4 категории яркости:

Категории яркости свечения
Обозначение категории яркостиЯркость, мкКд
H2020…2630
I2630…3420
J3420…4200
K4200…5040

В соответствии со сказанным, дата изготовления приведённого на фотографии модуля – 41-я неделя 1999 года, и самая большая яркость свечения (категория K).

Модуль HPDL 1414 имеет 12 «ножек»-выводов. Первая ножка обозначена срезом на верхней продольной выступающей полоске. Назначение выводов следующее:

Назначение выводов экрана HPDL-1414
Номер выводаОбозначениеНазначение
1D5Вход данных 5
2D4Вход данных 4
3WR#Запись данных в выбранный разряд
4A1Выбор разряда экрана 1
5A0Выбор разряда экрана 0
6VddПитание, 4.5…5.5 В
7GNDЗемля
8D0Вход данных 0
9D1Вход данных 1
10D2Вход данных 2
11D3Вход данных 3
12D6Вход данных 6

Выводы располагаются на корпусе модуля против часовой стрелки.

Назначение выводов экрана HPDL 1414
Назначение выводов экрана HPDL 1414

Напряжение питания экрана составляет от 4.5 до 5.5 В.

2Принципы работы с экраном HPDL-1414

Дисплей HPDL-1414 имеет встроенную память, в которой записаны 64 символа. Для вывода заданного символа на дисплей прежде всего необходимо с помощью линий адреса A0 и A1 выбрать один из четырёх разрядов экрана. И после этого выставить на линиях данных D0…D6 адрес ячейки памяти нужного символа в соответствии с приведённой таблицей.

Таблица символов экрана HPDL 1414
Таблица символов экрана HPDL 1414

Кстати, приведённая таблица представляет собой первые 64 печатных символа из таблицы символов ASCII. ASCII – это таблица, в которой в определённом стандартизированном порядке представлены распространённые печатные (и непечатные) символы.

Таблица символов ASCII
Таблица символов ASCII. Розовым цветом выделены непечатные символы, голубым – символы, которые имеются в ПЗУ экрана 1414.

Например, для вывода на экран HPDL-1414 символа "@" необходимо выставить на линии данных D0..D6 шестнадцатеричное число 0x40. В этом и заключается функциональность ASCII-декодера: он преобразует числовой код символа из таблицы ASCII в символ на экране.

Временная диаграмма передачи данных представлена ниже.

Временная диаграмма для передачи данных экрана HPDL 1414
Временная диаграмма для передачи данных экрана HPDL 1414

На диаграмме используются следующие обозначения.

ОбозначениеРасшифровкаНазначениеСреднее значение, нс
tASAddress Setup TimeВремя выставления адреса115
tWDWrite Delay TimeЗадержка записи15
tWWrite TimeВремя записи100
tDSData Setup TimeВремя установки данных60
tDHData Hold TimeВремя удержания данных45
tAHAddress Hold TimeВремя удержания адреса45

Согласно диаграмме, сначала следует выставить разряд символа экрана (от 0 до 3) на линиях A0 и A1, затем опустить строб записи данных WR#, затем выставить адрес символа согласно таблице ASCII на линиях D0..D6, а затем поднять строб WR#.

3Управление экраном HPDL-1414 с помощью Arduino

Теперь у нас есть все необходимые данные для работы с дисплеем 1414. Подключим его к Arduino вот по такой схеме.

Схема подключения экрана HPDL 1414 к Arduino
Схема подключения экрана HPDL 1414 к Arduino

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

Простейший скетч для работы с экраном HPDL 1414 (разворачивается)
#define AD0 2 // пины установки разряда экрана HPDL 1414
#define AD1 3
#define WR 4 // пин разрешения записи
const int D[]={5,6,7,8,9,10,11}; // номера пинов для D0..D6

void setup() {
  // задаём режимы работы выводов Arduino
  pinMode(AD0,OUTPUT);
  pinMode(AD1,OUTPUT);
  pinMode(WR,OUTPUT);
  for (int i=0; i<7; i++){ pinMode(D[i],OUTPUT); }
  
  digitalWrite(WR, HIGH); // ставим строб разрешения записи в исходное состояние
  
  // Выводим на экран HP1414 четыре символа:
  writeChar(3, 'A'); // переменная типа Char указывается в одинарных кавычках
  writeChar(2, 'A');
  writeChar(1, 'V');
  writeChar(0, 'E');
}

void loop() {
    // ничего не делаем
}

// Выводит в заданный разряд экрана заданный символ
void writeChar(int digit, unsigned char chr) {
    digitalWrite(AD0, 1 & digit); // выставляем младший разряд адреса
    digitalWrite(AD1, 1 & digit>>1); // выставляем старший разряд адреса
    digitalWrite(WR, LOW); // разрешаем запись данных
    int address = (int)chr; // переменная типа char хранит номер символа в таблице ASCII,
        // поэтому достаточно преобразовать его в int
    for (int i=0; i<7; i++) {
        digitalWrite(D[i], address>>i & 1); // поразрядно выставляем адрес символа на линии данных D0..D6
    }
    digitalWrite(WR, HIGH); // запрещаем запись данных, символ выводится на экран
}

Получим такой результат:

Первый тест экрана HPDL 1414
Первый тест экрана HPDL 1414

Тут нужно дать несколько пояснений. В функции writeChar() имеется строка int address = (int)chr, которая преобразует символ типа Char в целое число. Дело в том, что переменные типа Char в Arduino сами по себе хранят численные значения символов согласно таблице ASCII. Поэтому достаточно преобразовать переменную типа Char в Integer, чтобы узнать её адрес в таблице ASCII. Например, символ "A" при преобразовании в Integer будет 65 или 0x41. А это как раз то, что нам и нужно выставить на линиях D0..D6 экрана HPDL 1414.

Далее с помощью операции сдвига >> i мы сдвигаем адрес символа на i разрядов, берём младший разряд сдвинутого числа с помощью операции логического умножения & 1. Таким образом каждый разряд выставляем на соответствующих линиях данных D0..D6.

Операция логическое «И» называется конъюнкция или логическое умножение.

4Вывод бегущей строки на экран HPDL-1414 с помощью Arduino

Теперь немного усложним задачу: будем выводить на экран 1414 бегущую строку. По сути, у нас уже есть всё необходимое для этого. Добавим лишь одну функцию, которая будет разбивать переданную строку на подстроки и выводить их со сдвигом на экран.

Скетч для вывода бегущей строки на экран HPDL 1414 (разворачивается)
void writeString(String s) {
    s.toUpperCase(); // т.к. в ПЗУ символы только в верхнем регистре, 
        // то переводим строку в верхний регистр
    int t = 150; // длительность задержки, мс
    int position = 0; // текущая позиция в строке
    for (int i=0; i<s.length()+4; i++)  {  // выводим по 4 символа строки
        for (int dig=0; dig<4; dig++) { // один символ на один разряд индикатора
            writeChar(3-dig, s.charAt(position+dig));
        }
        position += 1; // смещаемся на 1 позицию в строке
        delay(t); // задержка регулирует скорость бегущей строки
    }
}
  

Использовать эту функцию крайне просто:

    writeString("hello, soltau.ru!");

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

А чтобы строка как бы «выезжала» с правого края дисплея, добавим перед циклом for в предыдущем скетче в функцию writeString() вот что:

// выводим первые 4 символа строки:
writeChar(0, s.charAt(0));
delay(t);
writeChar(1, s.charAt(0));
writeChar(0, s.charAt(1));
delay(t);
writeChar(2, s.charAt(0));
writeChar(1, s.charAt(1));
writeChar(0, s.charAt(2));
delay(t);  

Конечно, использовать 7 выводов для управления дисплеем весьма расточительно. Чтобы сэкономить выводы Arduino можно использовать сдвиговый регистр (шифтер). Он преобразует последовательный интерфейс SPI в параллельный, и с его помощью можно сократить число задействованных выводов до 4-х. Ну и ещё 2 вывода для выбора разряда дисплея. Всего будет задействовано 6 выводов вместо текущих 9-ти. Подключение сдвигового регистра 74HC595 мы подробно обсуждали здесь. Можно воспользоваться и другим сдвиговым регистром, на ваше усмотрение.

]]>
Arduino Sun, 07 Feb 2021 10:42:43 +0000