Часы реального времени

1Подключение к Arduino модуля ZS-042 с часами реального времени DS3231

Модуль ZS-042 с часами реального времени (RTC) имеет следующие характеристики:

  • Календарь до 2100 года с отсчётами секунд, минут, часов, числа месяца, месяца, дня недели и года (с учётом високосных годов);
  • 12- или 24-часовой формат;
  • 2 будильника;
  • напряжение питания: 3,3 или 5 В;
  • точность: ± 0.432 сек в день;
  • внутренний кварцевый генератор с частотой 32768 Гц;
  • поддерживаемый протокол: I2C со скоростью от 100 до 400 кГц;
  • габариты: 38×22×15 мм;
  • диапазон рабочих температур −40…+85°C.

На модуле присутствуют: микросхема таймера реального времени DS3231 (1 на рисунке), микросхема памяти AT24C32 объёмом 32 кбит (2 на рисунке), места для трёх перемычек A0, A1 и A2 (3 на рисунке), с помощью которых можно менять адресацию памяти микросхемы памяти; место для батареи питания размером 2032 (4 на рисунке).

Внешний вид модуля ZS-042

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

Название Назначение
32K выход генератора 32 кГц;
SQW выход прямоугольного сигнала; частота задаётся с помощью регистра управления 0x0E и может составлять 1, 1024, 4096 или 8192 Гц;
SCL шина тактовых импульсов интерфейса I2C;
SDA шина данных интерфейса I2C;
VCC питание – 3,3 или 5 вольт;
GND земля.

С противоположной стороны модуля выводы SCL, SDA, питание и земля дублируются. На выходе 32K постоянно присутствует сигнал с встроенного кварцевого генератора:

Сигнал на выходе 32K модуля ZS-042

Теперь нужно подключить модуль к Arduino. Мы уже знаем, что линия SDA нужно подключать к пину A4 Arduino UNO и Nano, а линию SCL – к пину A5. Для питания возьмём выход 5V платы Arduino, землю модуля соединим с землёй Arduino.

Схема подключения модуля ZS-042 с таймером DS3231 к Arduino

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

Модуль ZS-042 с таймером DS3231 подключён к Arduino

Рассмотрим диаграммы записи и чтения для таймера реального времени DS3231:

Обзор передачи данных по последовательной шине I2CДиаграмма записи и диаграмма чтения таймера реального времени DS3231

Как видно, тут всё стандартно для интерфейса I2C. Осталось только узнать, какие регистры за что отвечают, и мы будем готовы начать обмен данными с таймером DS3231. А вот и карта регистров:

Карта регистров таймера реального времени DS3231

Первым делом нужно выставить дату и время. А затем нужно будет только читать значение времени и календаря. Расширенные функции – установка будильников и т.д. – всё это делается аналогично, поэтому останавливаться на этом не будем. Итак, чтобы выставить дату и время, нас интересуют регистры 0x00…0x06. Для записи значений в них, нужно послать команду записи, указать начальный адрес (0x00), а дальше – 7 байтов, сформированных для нужной даты и времени. Например, чтобы записать дату 02 января 2019 года, среда, и время 19 час 30 мин 00 сек, нужно отправить ведомому устройству с I2C адресом 0x68 массив: 00 00 30 19 03 02 01 19. Скетч, который реализует это, будет таким:

#include <Wire.h> void setup() { Wire.begin(); // старт i2c Wire.beginTransmission(0x68); // начинаем обмен с DS3231 с i2c адресом 0x68 byte arr = {0x00, 0x00, 0x30, 0x19, 0x03, 0x02, 0x01, 0x19}; Wire.write(arr, 8); // записываем 8 байтов массива arr Wire.endTransmission(); // завершение передачи } void loop() { // здесь не делаем ничего }

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

Диаграмма выставления времени на RTC DS3231

Таймер запомнит выставленную дату и время. Если подключена батарейка, то данные будут храниться в памяти устройства до сброса или до полного разряда батареи, ведь в этом и есть назначение устройств такого рода. Давайте теперь будем с периодом 1 секунда читать значение времени и выводить в монитор последовательного порта. Для этого напишем вот такой скетч:

Скетч для чтения времени с часов DS3231 (разворачивается) #include <Wire.h> const byte DS3231 = 0x68; // I2C адрес таймера DS3231 void setup() { Wire.begin(); Serial.begin(9600); } void loop() { Wire.beginTransmission(DS3231); // начинаем обмен с DS3231 Wire.write(byte(0x00)); // записываем адрес регистра, с которого начинаем чтение!!! Wire.endTransmission(); // завершаем передачу byte t; // массив для хранения даты и времени int i = 0; // индекс текущего элемента массива Wire.beginTransmission(DS3231); // начинаем обмен с DS3231 Wire.requestFrom(DS3231, 7); // запрашиваем 7 байтов у DS3231 while(Wire.available()) { // пока есть данные от DS3231 t = Wire.read(); // читаем 1 байт и сохраняем в массив t i++; // инкрементируем индекс элемента массива } Wire.endTransmission(); // завершаем обмен printDateTime(t); // выводим дату и время delay(1000); // пауза 1 секунда } // разбирает считанный массив и выводит дату и время void printDateTime(byte *arr) { if (arr}

Обратите внимание, что каждую итерацию цикла loop() мы записываем адрес регистра 0x00. Если этого не делать, то мы будем каждый раз сдвигаться по карте регистров на 7 позиций, и возвращаемые данные будут совсем не те, что мы ожидаем.

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

Вывод даты и времени в монитор последовательного порта

А вот так выглядит временная диаграмма, порождаемая работой этого скетча:

Временная диаграмма чтения регистров времени DS3231

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

Скетч для чтения времени и температуры с часов DS3231 (разворачивается) #include <Wire.h> const byte DS3231 = 0x68; // I2C адрес таймера DS3231 void setup() { Wire.begin(); Serial.begin(9600); } void loop() { Wire.beginTransmission(DS3231); // начинаем обмен с DS3231 Wire.write(byte(0x00)); // записываем адрес регистра, с которого начинаются данные даты и времени Wire.endTransmission(); // завершаем передачу byte dateTime; // 7 байтов для хранения даты и времени int i = 0; // индекс текущего элемента массива Wire.beginTransmission(DS3231); // начинаем обмен с DS3231 Wire.requestFrom(DS3231, 7); // запрашиваем 7 байтов у DS3231 while(Wire.available()) // пока есть данные от DS3231 { dateTime = Wire.read(); // читаем 1 байт и сохраняем в массив dateTime i+=1; // инкрементируем индекс элемента массива } Wire.endTransmission(); // завершаем передачу printDateTime(dateTime); // выводим дату и время Wire.beginTransmission(DS3231); // начинаем новый обмен с DS3231 Wire.write(byte(0x11)); // записываем адрес регистра, с которого начинается температура Wire.endTransmission(); // завершаем передачу i = 0; // обнуляем счётчик элементов массива byte temp; // 2 байта для хранения температуры Wire.beginTransmission(DS3231); // начинаем обмен с DS3231 Wire.requestFrom(DS3231, 2); // запрашиваем 2 байта у DS3231 while(Wire.available()) { temp = Wire.read(); i+=1; } Wire.endTransmission(); printTemp(temp); // выводим температуру delay(1000); // пауза на 1 сек } // выводит дату и время void printDateTime(byte *dateTime) { if (dateTime} // выводит температуру void printTemp(byte *temp) { Serial.print(«\t»); // символ табуляции для разделения между временем и температурой float temperature = getTemp(temp); Serial.print(temperature); Serial.println(«oC»); // градусы Цельсия } // преобразует содержимое регистров 0x11 и 0x12 в значение температуры // для подробностей см. datasheet на ds3231 float getTemp(byte *temp){ float temperature = temp; // целая часть temperature += (temp*0.25/100); // дробная часть return temperature; }

Вот как теперь выглядит вывод нашей программы:

Вывод даты, времени и температуры в монитор последовательного порта

Само собой, в интернете полно библиотек для Arduino, которые упрощают работу с часами реального времени DS3231 и модулем ZS-042 в частности. Они делают всю рутинную работу, и вам не нужно будет разбираться с картой регистров и проводить манипуляции с перестановкой полученных байтов, чтобы получить удобочитаемое значение времени. В конце статьи дана ссылка на скачивание архива, в котором лежат несколько библиотек для работы с часами реального времени DS3231 и DS1307.

2Подключение к Arduino модуля с часами реального времени DS1307

Таймер DS1307 в отличие от DS3231 проще по функциональности: он имеет меньше регистров, не имеет встроенного датчика температуры и встроенного генератора тактовой частоты. Не имеет он также и функции будильника. Шина I2C функционирует только на частоте 100 кГц. Модуль с часами реального времени DS1307 может выглядеть вот так:

Внешний вид модуля с часами реального времени DS1307

Здесь номером 1 обозначена микросхема собственно таймера DS1307, номер 2 – микросхема памяти AT24C32 объёмом 32 кбит, 3 – кварцевый резонатор с частотой 32,768 кГц, 4 – держатель для батареи типа 2032.

На модуле имеются две группы контактов: P1 и P2. Группа P2 имеет стандартные выводы для шины I2C, плюс дополнительный вывод DS, к которому можно подключить внешний датчик температуры DS18B20. Группа P1 имеет большее число контактов:

Название Назначение вывода
SQ Выход прямоугольного сигнала 30 кГц;
DS подключение внешнего датчика температуры DS18B20;
SCL шина тактирования интерфейса I2C;
SDA шина данных интерфейса I2C;
VCC питание модуля – 3.3 или 5 вольт;
GND земля;
BAT вход питания от внешней батареи с напряжением в диапазоне 2,0…3,5 В.

Подключение этого модуля к Arduino осуществляется абсолютно так же, как и рассмотренного ранее: VCC модуля – 5V Arduino, GND – GND, SDA – A4, SCL – A5.

Теперь пришла пора познакомиться с устройством регистров часов DS1307. Карта регистров приведена на рисунке:

Карта регистров часов реального времени DS1307

Если присмотреться, увидим, что регистры 0x00…0x06 в точности совпадают с аналогичными регистрами рассмотренного таймера DS3231, а регистр 0x07 отвечает за частоту генерируемого прямоугольного сигнала. Кроме того, I2C адрес DS1307 также аналогичен адресу модуля DS3231. Поэтому логично предположить, что скетч установки времени подойдёт и здесь. В этом легко убедиться, если загрузить скетч в Arduino с подключённым модулем DS1307. Не забудьте только обновить установочный массив в соответствии с временем, которое будете выставлять на часах. Пример разобран в предыдущем разделе.

Скетч вывода времени также будет работать с этим модулем. После установки времени загрузим скетч и проверим это. Всё работает!

Модуль с таймером DS1307 подключён к Arduino

Библиотеки для работы с часами реального времени DS1307 и DS3231

В архиве лежат две разные библиотеки для Arduino (используйте ту, которая будет вам наиболее удобна), а также технические описания (datasheet) на микросхемы DS1307 и DS3231.

  • с Depositfiles

Установка библиотек проводится стандартным способом: помещением директории с библиотекой в директорию libraries среды Arduino IDE или через меню Sketch Include Library. Проще всего начать знакомство с библиотекой с изучения примеров, которые появятся в меню File Examples после установки библиотеки. Там имеются примеры и установки времени, и чтения показаний часов.

Во многих проектах Ардуино требуется отслеживать и фиксировать время наступления тех или иных событий. Модуль часов реального времени, оснащенный дополнительной батарей, позволяет хранить текущую дату, не завися от наличия питания на самом устройстве. В этой статье мы поговорим о наиболее часто встречающихся модулях RTC DS1307, DS1302, DS3231, которые можно использовать с платой Arduino.

Модули часов реального времени в проектах Arduino

Модуль часов представляет собой небольшую плату, содержащей, как правило, одну из микросхем DS1307, DS1302, DS3231.Кроме этого, на плате практически можно найти механизм установки батарейки питания. Такие платы часто применяется для учета времени, даты, дня недели и других хронометрических параметров. Модули работают от автономного питания – батареек, аккумуляторов, и продолжают проводить отсчет, даже если на Ардуино отключилось питание. Наиболее распространенными моделями часов являются DS1302, DS1307, DS3231. Они основаны на подключаемом к Arduino модуле RTC (часы реального времени).

Часы ведут отсчет в единицах, которые удобны обычному человеку – минуты, часы, дни недели и другие, в отличие от обычных счетчиков и тактовых генераторов, которые считывают «тики». В Ардуино имеется специальная функция millis(), которая также может считывать различные временные интервалы. Но основным недостатком этой функции является сбрасывание в ноль при включении таймера. С ее помощью можно считать только время, установить дату или день недели невозможно. Для решения этой проблемы и используются модули часов реального времени.

Электронная схема включает в себя микросхему, источник питания, кварцевый резонатор и резисторы. Кварцевый резонатор работает на частоте 32768 Гц, которая является удобной для обычного двоичного счетчика. В схеме DS3231 имеется встроенный кварц и термостабилизация, которые позволяют получить значения высокой точности.

Модуль DS1307

DS1307 – это модуль, который используется для отсчета времени. Он собран на основе микросхемы DS1307ZN, питание поступает от литиевой батарейки для реализации автономной работы в течение длительного промежутка времени. Батарея на плате крепится на обратной стороне. На модуле имеется микросхема AT24C32 – это энергонезависимая память EEPROM на 32 Кбайт. Обе микросхемы связаны между собой шиной I2C. DS1307 обладает низким энергопотреблением и содержит часы и календарь по 2100 год.

Модуль обладает следующими параметрами:

  • Питание – 5В;
  • Диапазон рабочих температур от -40С до 85С;
  • 56 байт памяти;
  • Литиевая батарейка LIR2032;
  • Реализует 12-ти и 24-х часовые режимы;
  • Поддержка интерфейса I2C.

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

Взаимодействие с другими устройствами и обмен с ними информацией производится с помощью интерфейса I2C с контактов SCL и SDA. В схеме установлены резисторы, которые позволяют обеспечивать необходимый уровень сигнала. Также на плате имеется специальное место для крепления датчика температуры DS18B20.Контакты распределены в 2 группы, шаг 2,54 мм. В первой группе контактов находятся следующие выводы:

  • DS – вывод для датчика DS18B20;
  • SCL – линия тактирования;
  • SDA – линия данных;
  • VCC – 5В;
  • GND.

Во второй группе контактов находятся:

  • SQ – 1 МГц;
  • DS ;
  • SCL;
  • SDA;
  • VCC;
  • GND;
  • BAT – вход для литиевой батареи.

Для подключения к плате Ардуино нужны сама плата (в данном случае рассматривается Arduino Uno), модуль часов реального времени RTC DS1307, провода и USB кабель.

Чтобы подключить контроллер к Ардуино, используются 4 пина – VCC, земля, SCL, SDA.. VCC с часов подключается к 5В на Ардуино, земля с часов – к земле с Ардуино, SDA – А4, SCL – А5.

Для начала работы с модулем часов нужно установить библиотеки DS1307RTC, TimeLib и Wire. Можно использовать для работы и RTCLib.

Проверка RTC модуля

При запуске первого кода программа будет считывать данные с модуля раз в секунду. Сначала можно посмотреть, как поведет себя программа, если достать из модуля батарейку и заменить на другую, пока плата Ардуино не присоединена к компьютеру. Нужно подождать несколько секунд и вытащить батарею, в итоге часы перезагрузятся. Затем нужно выбрать пример в меню Examples→RTClib→ds1307. Важно правильно поставить скорость передачи на 57600 bps.

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

Будет показывать время 0:0:0. Это связано с тем, что в часах пропадает питание, и отсчет времени прекратится. По этой причине нельзя вытаскивать батарею во время работы модуля.

Чтобы провести настройку времени на модуле, нужно в скетче найти строку

RTC.adjust(DateTime(__DATE__, __TIME__));

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

Настройка произведена корректно и дополнительно перенастраивать часы реального времени не придется.

Считывание времени. Как только модуль настроен, можно отправлять запросы на получение времени. Для этого используется функция now(), возвращающая объект DateTime, который содержит информацию о времени и дате. Существует ряд библиотек, которые используются для считывания времени. Например, RTC.year() и RTC.hour() – они отдельно получают информацию о годе и часе. При работе с ними может возникнуть проблема: например, запрос на вывод времени будет сделан в 1:19:59. Прежде чем показать время 1:20:00, часы выведут время 1:19:00, то есть, по сути, будет потеряна одна минута. Поэтому эти библиотеки целесообразно использовать в случаях, когда считывание происходит нечасто – раз в несколько дней. Существуют и другие функции для вызова времени, но если нужно уменьшить или избежать погрешностей, лучше использовать now() и из нее уже вытаскивать необходимые показания.

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

Проект представляет собой обычные часы, на индикатор будет выведено точное время, а двоеточие между цифрами будет мигать с интервалом раз в одну секунду. Для реализации проекта потребуются плата Arduino Uno, цифровой индикатор, часы реального времени (в данном случае вышеописанный модуль ds1307), шилд для подключения (в данном случае используется Troyka Shield), батарейка для часов и провода.

В проекте используется простой четырехразрядный индикатор на микросхеме TM1637. Устройство обладает двухпроводным интерфейсом и обеспечивает 8 уровней яркости монитора. Используется только для показа времени в формате часы:минуты. Индикатор прост в использовании и легко подключается. Его выгодно применять для проектов, когда не требуется поминутная или почасовая проверка данных. Для получения более полной информации о времени и дате используются жидкокристаллические мониторы.

Модуль часов подключается к контактам SCL/SDA, которые относятся к шине I2C. Также нужно подключить землю и питание. К Ардуино подключается так же, как описан выше: SDA – A4, SCL – A5, земля с модуля к земле с Ардуино, VCC -5V.

Индикатор подключается просто – выводы с него CLK и DIO подключаются к любым цифровым пинам на плате.

Скетч. Для написания кода используется функция setup, которая позволяет инициализировать часы и индикатор, записать время компиляции. Вывод времени на экран будет выполнен с помощью loop.

#include <Wire.h> #include «TM1637.h» #include «DS1307.h» //нужно включить все необходимые библиотеки для работы с часами и дисплеем. char compileTime = __TIME__; //время компиляции. #define DISPLAY_CLK_PIN 10 #define DISPLAY_DIO_PIN 11 //номера с выходов Ардуино, к которым присоединяется экран; void setup() { display.set(); display.init(); //подключение и настройка экрана. clock.begin(); //включение часов. byte hour = getInt(compileTime, 0); byte minute = getInt(compileTime, 2); byte second = getInt(compileTime, 4); //получение времени. clock.fillByHMS(hour, minute, second); //подготовка для записывания в модуль времени. clock.setTime(); //происходит запись полученной информации во внутреннюю память, начало считывания времени. } void loop() { int8_t timeDisp; //отображение на каждом из четырех разрядов. clock.getTime();//запрос на получение времени. timeDisp = clock.hour / 10; timeDisp = clock.hour % 10; timeDisp = clock.minute / 10; timeDisp = clock.minute % 10; //различные операции для получения десятков, единиц часов, минут и так далее. display.display(timeDisp); //вывод времени на индикатор display.point(clock.second % 2 ? POINT_ON : POINT_OFF);//включение и выключение двоеточия через секунду. } char getInt(const char* string, int startIndex) { return int(string — ‘0’) * 10 + int(string) — ‘0’; //действия для корректной записи времени в двухзначное целое число. В ином случае на экране будет отображена просто пара символов. }

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

Программу можно немного модернизировать. При отключении питания выше написанный скетч приведет к тому, что после включения на дисплее будет указано время, которое было установлено при компиляции. В функции setup каждый раз будет рассчитываться время, которое прошло с 00:00:00 до начала компиляции. Этот хэш будет сравниваться с тем, что хранятся в EEPROM, которые сохраняются при отключении питания.

Для записи и чтения времени в энергонезависимую память или из нее нужно добавить функции EEPROMWriteInt и EEPROMReadInt. Они нужны для проверки совпадения/несовпадения хэша с хэшем, записанным в EEPROM.

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

В результате в коде нужно будет указать новую библиотеку (для жидкокристаллических экранов это LiquidCrystal), и добавить в функцию loop() строки для получения даты.

Алгоритм работы следующий:

  • Подключение всех компонентов;
  • Загрузка скетча;
  • Проверка – на экране монитора должны меняться ежесекундно время и дата. Если на экране указано неправильное время, нужно добавить в скетч функцию RTC.write (tmElements_t tm). Проблемы с неправильно указанным временем связаны с тем, что модуль часов сбрасывает дату и время на 00:00:00 01/01/2000 при выключении.
  • Функция write позволяет получить дату и время с компьютера, после чего на экране будут указаны верные параметры.

Модули часов используются во многих проектах. Они нужны для систем регистрации данных, при создании таймеров и управляющих устройств, которые работают по заданному расписанию, в бытовых приборах. С помощью широко распространенных и дешевых модулей вы можете создать такие проекты как будильник или регистратор данных с сенсоров, записывая информацию на SD-карту или показывая время на экране дисплея. В этой статье мы рассмотрели типичные сценарии использования и варианты подключения наиболее популярных видов модулей.

Виртуальные часы

Целью задания является создание трехмерной интерактивной модели аналоговых часов.

Обязательные требования к программе:

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

  2. Часы на экране обязательно должны иметь минутную и часовую стрелки. Секундная — по желанию, но очень приветствуется (иначе трудно будет определить, ходят часы или нет).

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

  4. Сцена должна быть интерактивной, т.е. давать приемлемую частоту кадров в секунду (>10) при визуализации на машине с аппаратным ускорителем трехмерной графики. Если программа будет работать медленно, баллы могут быть снижены

  5. Необходимо реализовать вращения часов (или, возможно, камеры) с помощью мыши (предпочтительно) или клавиатуры. Можно также предусмотреть режимы с автоматическим вращением.

Пожелания к программе:

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

  2. Желательно наличие возможностей для управления процессом визуализации. Например, наличие/отсутствие текстур, режимы заливки, детализации и т.д.

  3. Приветствуется выполнение задания в виде демонстрации, т.е. c возможностью работы в полноэкранном режиме и немедленным выходом по клавише Escape. Можно написать программу как Screen Saver.

  4. Постарайтесь использовать максимум возможностей OpenGL. Блики, отражения, спецэффекты — за все это обязательно даются дополнительные баллы.

  5. Проявите вкус — сделайте так, чтобы нравилось прежде всего Вам. Но не увлекайтесь — оставайтесь реалистами.

Максимальная оценка — 20 баллов. За минимальную реализацию требований ставиться 10 баллов. Еще до 10 баллов можно получить за использование в работе возможностей OpenGL (текстур, прозрачности , environmentmappingи пр.), оригинальных и продвинутых алгоритмов, количество настроек, а также за эстетичность и красоту сцены.

    1. Интерактивный ландшафт

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

Обязательная часть задания

Для выполнения обязательной части задания необходимы:

  1. генерация трехмерного ландшафта

  2. раскраска для придания реалистичности

  3. эффект тумана

  4. возможность «полета» над ландшафтом (управление)

Более подробное описание:

Генерация ландшафта

Один из вариантов задания поверхности ландшафта — задание так называемого «поля высот» — функции вида z=f(x, y), которая сопоставляет каждой точке (x, y) плоскости OXY число z — высоту поверхности ландшафта в этой точке. Один из способов задания функции f — табличный, когда функция f представляется матрицей T размера M x N, и для целых x и y f=T, а для дробных x и y из диапазонов и соответственно f вычисляется интерполяцией значений f в ближайших точках плоскости OXY с целыми x и y, а вне указанных диапазонов x и y значение функции считается неопределенным.

Допустим, в памяти лежит двухмерный массив со значениями матрицы T. Пусть N=M. Если теперь для каждого квадрата x , где x и y принадлежат диапазону построить две грани: ((x, y, T), (x+1, y, T), (x+1, y+1, T)) и ((x, y, T), (x+1, y+1, T), (x, y+1, T)), то мы получим трехмерную модель поверхности, описываемой матрицей Т.

Но каким образом задать массив значений матрицы Т? Один из способов — сгенерировать псевдослучайную поверхность с помощью фрактального разбиения. Для этого положим размерность матрицы T равной 2^N+1, где N — натуральное число. Зададим некоторые произвольные (псевдослучайные) значения для четырех угловых элементов матрицы Т. Теперь для каждого из четырех ребер матрицы Т (это столбцы или строки элементов, соединяющие угловые элементы) вычислим значение элемента матрицы Т, соответствующего середине ребра. Для этого возьмем среднее арифметическое значений элементов матрицы Т в вершинах ребра и прибавим к получившемуся значению некоторое псевдослучайное число, пропорциональное длине ребра. Значение центрального элемента матрицы Т вычислим аналогично, только будем брать среднее арифметическое четырех значений элементов матрицы в серединах ее ребер.

Теперь разобьем матрицу Т на четыре квадратные подматрицы. Значения их угловых элементов уже определены и мы можем рекурсивно применить к подматрицам Т описанную выше процедуру. Будем спускаться рекурсивно по дереву подматриц, пока все элементы Т не будут определены. С помощью подбора коэффициентов генерации псевдослучайной добавки можно регулировать «изрезанность» поверхности. Для реалистичности поверхности важно сделать величину псевдослучайной добавки зависящей от длины текущего ребра — с уменьшением размера ребра должно уменьшаться и возможное отклонение высоты его середины от среднего арифметического высот его вершин.

Один из других вариантов — использовать изображения в градациях серого для карты высот. (В этом случае ландшафт можно оттекстурировать с помощью соответствующей цветной картинки и линейной генерации текстурных координат)

Внимание: использование NURBS возможно, но не приветствуется в силу ограниченности использования NURBS для реальных приложений.

Раскраска ландшафта

Чтобы сделать получившуюся модель немного более напоминающей ландшафт, ее можно раскрасить. Каждой вершине можно сопоставить свой цвет, зависящий от высоты этой вершины. Например, вершины выше определенного уровня можно покрасить в белый цвет в попытке сымитировать шапки гор, вершины пониже — в коричневый цвет скал, а вершины уровнем еще ниже — в зеленый цвет травы. Значения «уровней» раскраски поверхности следует подобрать из эстетических соображений.

Освещение ландшафта

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

Цвет вершин можно задавать через glColor*()совместно сglColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

Туман

Чтобы усилить (или хотя бы создать) иллюзию больших размеров модели и ее протяженности, можно воспользоваться эффектом тумана. Тип тумана (линейный или экспоненциальный) следует выбрать из индивидуальных эстетических предпочтений. Способ создания тумана описан в разделе 4.4.

Управление

Элементарное управление движением камеры по клавиатурным «стрелочкам». Нажатие на стрелку «вверх» — передвижение по направлению взгляда вперед. «Назад» — по направлению взгляда назад. «Влево», «Вправо» по аналогии, «Page Up», «Page Down» — вверх, вниз, соответственно.

В GLUT’е получать нажатия не алфавитно-цифровых клавиш можно через функцию glutSpecialFunc(void (*)(int key, int x, int y)), где key — константа, обозначающая клавишу (см. в glut.h -GLUT_KEY_). Функция используется аналогичноglutKeyboardFunc().

Дополнительная часть

Управление мышью:

Движение мыши в горизонтальной плоскости (смещение по оси X) управляет углом поворота направления взгляда в горизонтальной плоскости (альфа, от 0 до 2*PI). Движение мыши в вертикальной плоскости (смещение по оси Y) управляет углом поворота направления взгляда в вертикальной плоскости относительно горизонта (бета, от -PI до PI). Зная оба угла, вектор направления взгляда в мировых координатах вычисляется следующим образом:

direction_z = sin(бета); direction_x = cos(альфа) * cos(бета); direction_y = sin(альфа) * cos(бета),

а затем нормализуется.

Вектор направления «вбок» вычисляется как векторное произведение вектора направления вертикально вверх, то есть вектора (0, 0, 1) и уже известного вектора направления взгляда.

Вектор направления «вверх» вычисляется как векторное произведение вектора направления взгляда и вектора направления «вбок».

Положение камеры в OpenGL можно передать через gluLookAt(). Подсказка: параметрtarget можно положить равнымposition+direction

Смещение позиции камеры должно происходить не на фиксированное расстояние за один кадр, а вычисляться, исходя из скорости передвижения камеры, и времени, ушедшего на обсчет последнего кадра. Передвижение камеры должно осуществляться в направлении взгляда. Скажем, по левой кнопке мыши — вперед, а по правой — назад. Для того, чтобы засечь время, можно воспользоваться функцией timeGetTime(), описанной в «mmsystem.h», и реализованной в библиотеке «winmm.lib» (только дляWindows)

#include «mmsystem.h»

void Display()

{

int system_time_before_rendering;

system_time_before_rendering = timeGetTime();

RenderFrame();

int time_spent_rendering_msec = timeGetTime() -system_time_before_rendering;

}

В GLUT’е для этого есть специальный вызов time = glutGet(GLUT_ELAPSED_TIME) (аналогично timeGetTime())

Вода, или нечто на нее похожее

При раскраске ландшафта можно добавить еще один, самый нижний «уровень» — уровень воды. Вершины, располагающиеся на этом уровне можно покрасить в цвет воды — предположительно, синий. Для того, чтобы получившиеся «водоемы» не выглядели продолжением поверхности ландшафта просто покрашенным в синий цвет, а имели плоскую поверхность, при генерации поля высот можно установить порог высоты, ниже которого «опускаться» вершинам запрещается. Если для элемента матрицы генерируется значение высоты ниже этого порога, элемент инициализируется пороговым значением.

Объекты

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

Отражения в воде

Сделать так, чтобы ландшафт отражался в воде, которая уже должна присутствовать на ландшафте (то есть подразумевается, что это дополнительное задание является развитием дополнительного задания 2). Один из вариантов реализации: рассчитав текущую матрицу камеры, отразить ее относительно плоскости воды и изображение ландшафта, не выводя при этом грани поверхности воды. Затем, пользуясь еще не отраженной матрицей камеры, визуализировать грани поверхности воды полупрозрачными. Это создаст иллюзию поверхности воды, сквозь которую видно отражение. Затем, опять же с неотраженной матрицей камеры, нужно нарисовать сам ландшафт. (этот подход является частным случаем описанного в разделе 7.3.)

Тени

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

Оценка:

База

Ландшафт

8 баллов

Раскраска

2 балла

Управление

2 балла

Дополнительно

Управление мышью

+2 балла

Объекты

+3 балла

Вода

+4 балла

Отражение

+4 балла

*Тени

+5 баллов

Всего

30 баллов

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

Дополнительные источники информации:

http://www.vterrain.org- на этом есть почти все про ландшафты.

Задержки в Ардуино играют очень большую роль. Без них не сможет работать даже самый простой пример Blink, который моргает светодиодом через заданный промежуток времени. Но большинство начинающих программистов мало знают о временных задержках и используют только Arduino delay, не зная побочных эффектов этой команды. В этой статье я подробно расскажу о временных функциях и особенностях их использования в среде разработки Arduino IDE.

Использование функции arduino delay

Синтаксис

Ардуино delay является самой простой командой и её чаще всего используют новички. По сути она является задержкой, которая приостанавливает работу программы, на указанное в скобках число миллисекунд. (В одной секунде 1000 миллисекунд.) Максимальное значение может быть 4294967295 мс, что примерно ровняется 50 суткам. Давайте рассмотрим простой пример, наглядно показывающий работу этой команды. void setup() { pinMode(13, OUTPUT); } void loop() { digitalWrite(13, HIGH); // подаем высокий сигнал на 13 пин delay(10000); // пауза 10000мс или 10 секунд digitalWrite13, LOW); // подаем низкий сигнал на 13 пин delay(10000); // пауза 10000мс или 10 секунд }

В методе setup прописываем, что пин 13 будет использоваться, как выход. В основной части программы сначала на пин подается высокий сигнал, затем делаем задержку в 10 секунд. На это время программа как бы приостанавливается. Дальше подается низкий сигнал и опять задержка и все начинается сначала. В итоге мы получаем, что на пин поочередно подается, то 5 В, то 0.

Нужно отчетливо понимать, что на время паузы с помощью delay работа программы приостанавливается, приложение не будет получать никаких данных с датчиков. Это является самым большим недостатком использования функции delay в Arduino. Обойти это ограничения можно с помощью прерываний, но об этом мы поговорим в отельной статье.

Пример delay с миганием светодиодом

Пример схемы для иллюстрации работы функции delay.
Можно построить схему со светодиодом и резистором. Тогда у нас получится стандартный пример – мигание светодиодом. Для этого на пин, который мы обозначили как выходной, необходимо подключить светодиод плюсовым контактом. Свободную ногу светодиода через резистор приблизительно на 220 Ом (можно немного больше) подключаем на землю. Определить полярность можно, если посмотреть на его внутренности. Большая чашечка внутри соединена с минусом, а маленькая ножка с плюсом. Если ваш светодиод новый, то определить полярность можно по длине выводов: длинная ножка – плюс, короткая – минус.

Функция millis вместо delay

Функция millis() позволит выполнить задержку без delay на ардуино, тем самым обойти недостатки предыдущих способов. Максимальное значение параметра millis такое же, как и у функции delay (4294967295мс или 50 суток). При переполнении значение просто сбрасывается в 0, не забывайте об этом.

С помощью millis мы не останавливаем выполнение всего скетча, а просто указываем, сколько времени ардуино должна просто “обходить” именно тот блок кода, который мы хотим приостановить. В отличие от delay millis сама по себе ничего не останавливает. Данная команда просто возвращает нам от встроенного таймера микроконтроллера количество миллисекунд, прошедших с момента запуска. При каждом вызове loop Мы сами измеряем время, прошедшее с последнего вызова нашего кода и если разница времени меньше желаемой паузы, то игнорируем код. Как только разница станет больше нужной паузы, мы выполняем код, получаем текущее время с помощью той же millis и запоминаем его – это время будет новой точкой отсчета. В следующем цикле отсчет уже будет от новой точки и мы опять будем игнорировать код, пока новая разница millis и нашего сохраненного прежде значения не достигнет вновь желаемой паузы.

Задержка без delay с помощью millis требует большего кода, но с ее помощью можно моргать светодиодом и ставить на паузу скетч, не останавливая при этом систему.

Вот пример, наглядно иллюстрирующий работу команды:

unsigned long timing; // Переменная для хранения точки отсчета void setup() { Serial.begin(9600); } void loop() { /* В этом месте начинается выполнение аналога delay() Вычисляем разницу между текущим моментом и ранее сохраненной точкой отсчета. Если разница больше нужного значения, то выполняем код. Если нет — ничего не делаем */ if (millis() — timing > 10000){ // Вместо 10000 подставьте нужное вам значение паузы timing = millis(); Serial.println («10 seconds»); } }

Сначала мы вводим переменную timing, в ней будет храниться количество миллисекунд. По умолчанию значение переменной равно 0. В основной части программы проверяем условие: если количество миллисекунд с запуска микроконтроллера минус число, записанное в переменную timing больше, чем 10000, то выполняется действие по выводу сообщения в монитор порта и в переменную записывается текущее значение времени. В результате работы программы каждые 10 секунд в монитор порта будет выводиться надпись 10 seconds. Данный способ позволяет моргать светодиодом без delay.

Функция для вывода времени работы Arduino

Данная функция выводит в последовательный порт время работы платы Arduino после включения или перезагрузки в формате ЧАСЫ:МИНУТЫ:СЕКУНДЫ 00:00:00

ВСТУПЛЕНИЕ

Что такое функция millis() ?

Время работы Arduino ничем не ограничено. Этот микроконтроллер может быть включен постоянно.
Для подсчета времени с момента включения или c момента перезагрузки платы существует функция millis() .
Arduino считает количество миллисекунд, которое прошло с момента запуска платы.
Их количество можно узнать с помощью функции millis().

Например, пусть у нас есть переменная time. В случае использования строки кода time=millis(); , переменная time будет содержать некоторое количество миллисекунд.
В одной секунде содержится 1000 миллисекунд, поэтому время измеряемое в миллисекундах неудобно для восприятия человеком, но вполне понятно машинам, роботам и микросхемам.
После 50 суток работы Arduino встроенный счетчик миллисекунд обнуляется и начинает отсчет снова.

Ниже приводится код функции TimePrint, которая переводит миллисекунды в понятный человеку формат.

Arduino

Данная функция выводит в последовательный порт время работы платы Arduino после включения или перезагрузки в формате часы:минуты:секунды ( 00:00:00 ).
Вызывается в вашем коде, как TimePrint();
Использует встроенную функцию millis(). Обратите внимание, что счетчик функции millis() сбрасывается после 50 дней работы.
Полезна для отладки и тестирования различных датчиков и сенсоров.

Логика расчета секунд, минут и часов при пересчете из миллисекунд.
Как пересчитывать секунды, минуты, часы и дни в Arduino.

Чтобы узнать общее количество секунд, значение функции millis() необходимо разделить на 1000.
Чтобы количество секунд перевести в минуты, делим их значение на 60. Остаток от целочисленного деления – точные секунды ( от 0 до 59 ).
Чтобы количество минут перевести в часы, делим их значение на 60. Остаток от целочисленного деления – точные минуты ( от 0 до 59 ).
Таким образом получаем значение времени работы платы Arduino в обычном понятном формате.

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

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

Arduino

1 time=millis()/1000

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

Arduino

1 2 3 if (time/60/60<10) { Serial.print («0»); } Serial.print (time/60/60); Serial.print («:»);

Затем высчитываем целые минуты, их остаток уже за вычетом целых часов.

Arduino

1 2 3 if (time/60%60<10) { Serial.print («0»); } Serial.print ((time/60)%60); Serial.print («:»);

И высчитываем секунды.

Arduino

1 2 if (time%60<10) { Serial.print («0»); } Serial.println (time%60);

Время с момента включения Ардуино будет выглядеть следующим образом, например : 05:42:14
Чтобы перевести время в число необходимо выполнить обратную операцию.
Таким образом, вывод времени на Ардуино будет осуществляться в удобном формате.

Это пример классического программирования функции millis(), при использовании классов и объектов (классы и объекты в Arduino) код можно оптимизировать.

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

Использовать функцию millis() и семисегментный индикатор, чтобы сделать цифровые часы, не рекомендуется, так как для этого нужно использовать более точные часы реального времени.

RTC ( Real Time Clock ) – эти микросхемы имеют собственный элемент питания, не зависят от выключения и перезагрузок платы Ардуино, позволяют отслеживать и время и дату.

Например, можно использовать плату DS1302 Real Time Clock.

Модули часов реального времени производятся на нескольких популярных микросхемах.

Обсуждение на форуме.