Табло курсов из урока про API котировок показывало одно число «сейчас», и первый же вопрос у кофемашины был «а вчера сколько было?». Табло молчало. История курса за сутки — это двадцать четыре значения, по одному в час, и влезают они в один пакет: массив из двадцати четырёх чисел весит меньше, чем абзац текста.
Упаковку истории в пакет мы уже делали в уроке про сотню точек — там пачка собиралась за сутки и печаталась бар-чартом. Здесь шаг дальше: буфер живёт непрерывно. Свежая котировка вдвигается в кольцо, старейшая выбрасывается, и график всегда показывает последние двадцать четыре часа — память не растёт, данные не стареют. Плюс светодиод-сторож: курс вышел из вилки вокруг среднего — плата сигналит.
Разбор массива из пакета здесь в один цикл — механика первого разбора без изменений. Новое — судьба разобранного: не разовая печать, а живой буфер, который кормит и Serial-столбики, и — на плате — линию на OLED.
Пакет истории: 24 числа и их цена
Форма пакета предельно плоская: {"pair":"USD/RUB","h":[89.4,89.1,...]}. Заголовок — пара слов, история — массив h. Точка с одним знаком после запятой стоит пять байтов с запятой-разделителем; двадцать четыре точки — около 120 байтов, плюс обвязка — пакет выходит порядка 150 байтов. Второй знак после запятой добавил бы двадцать четыре цифры, но график от этого точнее не станет: ход курса в рублях за час — десятые доли. При 9600 бод, когда байт идёт около миллисекунды, пакет занимает канал примерно 150 мс — раз в час и не на секунду больше.
Час измерения здесь не пишет своё имя — он равен позиции в массиве: двадцать третий элемент всегда «двадцать три часа назад», если пакет собран почасово. Отметки времени выкидываем целиком, экономя под сотню байтов на пакете; порядок точек — от старых к новым, и это договор, который документируется один раз. Точность float для графика избыточна: семь значащих цифр, курс 92.4 хранится без потерь — на этой шкале float даже не замечает нагрузки.
#include <ArduinoJson.h>#include <Adafruit_SSD1306.h>#include <Wire.h> Adafruit_SSD1306 display(128, 64, &Wire, -1);DynamicJsonDocument doc(512);float hist[24]; void izStroki(String istoriya) { int ok = deserializeJson(doc, istoriya); if (ok != 1) return; for (int i = 0; i < 24; i++) hist[i] = doc["h"][i];} void risuy() { float mn = hist[0], mx = hist[0]; for (int i = 1; i < 24; i++) { if (hist[i] < mn) mn = hist[i]; if (hist[i] > mx) mx = hist[i]; } float shag = mx - mn; if (shag < 0.1) shag = 0.1; // плоский рынок: не делим на ноль display.clearDisplay(); for (int i = 0; i < 24; i++) { int y = map((int)(hist[i] * 10), (int)(mn * 10), (int)(mx * 10), 56, 8); display.drawLine(4 + i * 5, y, 4 + i * 5, 60, SSD1306_WHITE); } display.setCursor(0, 0); display.print("USD/RUB "); display.print(mn, 1); display.print("-"); display.print(mx, 1); display.display();} void setup() { Serial.begin(9600); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); izStroki("{\"pair\":\"USD/RUB\",\"h\":[89.4,89.1,88.8]}"); // строка от API risuy();} void loop() {}Геометрия экрана ложится на двадцать четыре точки идеально: шаг в пять пикселей на точку даёт 4 + 24×5 = 124 пикселя при ширине 128 — влезло с полями. Каждый столбик — вертикальная линия от своего уровня до пола: виден и уровень, и тренд, а пустое место между столбиками читается как время. Шкалу считают минимум и максимум самой истории, поэтому кривая всегда занимает весь экран от пола до потолка — в тихие дни график так же честно плоский, как и в бурные.
Строка с shag — не украшение: в плоский день максимум равен минимуму, делитель обращается в ноль, и map выдаёт мусор или столбики нулевой длины — график исчезает именно тогда, когда ровный курс наиболее интересен. Минимальный шаг шкалы в одну десятую сохраняет картинку. Этот же приём работает ниже в Serial-версии, где масштаб считает map от минимума до максимума буфера.
| Экран | Что видно | Цена |
|---|---|---|
| Serial-столбики из # | сутки с первого взгляда, значения рядом с барами | ноль железа; один цикл печати |
| OLED 128x64 | форма кривой, тренд и пики | шина I2C и около двадцати строк рисования |
| График на сервере | красиво, интерактивно, но не на месте установки | на плате только отправка пакета |
В прогоне видно, как кольцо работает на трёх масштабах времени. Каждые две секунды — свежая котировка: ползунок крутится, строка kurs= бежит по монитору. Каждые двадцать четыре тика — полный оборот: точка, которую вы задвинули ползунком утром, к вечеру выдавлена новыми котировками без следа. И самое интересное — среднее: оно пересчитывается по всему буферу, поэтому одно резкое движение ползунка дёргает его на долю рубля, а систематическое удержание на новом уровне за полчаса переплавляет всю историю под себя. LED гаснет сам, как только вилка снова накрывает котировку.
Вилка ±1.5 — константа под задачу, не аксиома: для графика настроения у кофемашины хватит и трёх, для сигнализации о скачке на бирже — и полрубля много. Считайте её от дневного хода: в симуляторе ползунок даёт шесть рублей диапазона, вилка в четверть хода — разумный старт. На реальной плате вместо ползунка котировку приносит очередной ответ API, разбор тот же — deserializeJson массива в один цикл.
Кольцо: почему старое обязано уходить
Без кольца история растёт линейно: сутки — двадцать четыре точки и около 150 байтов, неделя — больше килобайта, и Uno с её 2 КБ SRAM уже не хозяин. Кольцо держит ровно сутки всегда: память под буфер занята один раз и навсегда, график всегда показывает последние двадцать четыре часа, и никакого администрирования. Цена одна — старейшая точка исчезает молча: если её нужно помнить, выгружайте на сервер или на SD раньше, чем она вылетит; долгая история — это лог на карте памяти, а не RAM.
Сдвиг массива — не единственный способ кольца, но для двадцати четырёх точек — самый дешёвый: двадцать три присваивания раз в час на плате и раз в две секунды в симуляторе — микроскопическая работа. Альтернатива — указатель головы по модулю: ноль присваиваний, зато печать начинается с середины массива и оборачивается, и каждая функция с индексами обрастает модульной арифметикой. Я на таких объёмах всегда плачу двадцатью присваиваниями: понятный код дороже трёх тактов.
Дальше буферу всё равно, куда смотреть: Serial-столбики — взгляд без железа, OLED — двадцать четыре вертикальные линии с шагом пять пикселей, как в code-блоке выше; полный набор приёмов рисования на SSD1306 разобран в уроке про график датчика на OLED — там тот же буфер, только вход не JSON, а замеры живого датчика. Серверу плату с историей отдавать проще всех: он сам просит пакет целиком и рисует красоты у себя.
- История курса — 24 точки одним пакетом: около 150 байтов при одном знаке после запятой и около 150 мс канала при 9600 бод
- Час измерения равен позиции в массиве: отметки времени выкидываются, порядок точек — договор протокола
- Кольцевой буфер: свежая котировка выбрасывает старейшую, память занята один раз, график всегда последние сутки
- Минимум, максимум и среднее пересчитываются по всему буферу; LED-вилка — ±1.5 от среднего как константа под задачу
- Плоский рынок ломает масштаб: минимальный шаг шкалы 0.1 обязателен и в Serial, и на OLED
- 1. Сколько примерно байтов весит пакет из 24 точек с одним знаком после запятой?
- 2. Все 24 точки истории равны 91.0. Что случится с графиком без защитного шага шкалы?
- 3. Что кольцевой буфер делает со старейшей точкой при вдвиге новой?
- 4. Почему для 24 точек выгоднее сдвиг массива, чем указатель головы по модулю?
- 5. Сколько времени пакет истории в 150 байтов занимает при 9600 бод?
Частые вопросы по теме урока
- Сколько примерно байтов весит пакет из 24 точек с одним знаком после запятой?
- Точка с запятой-разделителем — пять байтов, двадцать четыре точки дают около 120, плюс заголовок и скобки. Так что пакет порядка 150 байтов — это меньше половины приёмного буфера, но за один раз его не съесть. около 150 байтов
- Все 24 точки истории равны 91.0. Что случится с графиком без защитного шага шкалы?
- Максимум равен минимуму — разница границ нулевая, и map выдаёт мусор либо нулевые длины. Страховка стоит одной строки: шаг шкалы меньше одной десятой приравнивается к одной десятой. масштаб ломается: делитель ноль, столбики исчезают
- Что кольцевой буфер делает со старейшей точкой при вдвиге новой?
- Кольцо держит ровно окно: новая котировка выдавливает старейшую, память не растёт. Если точку нужно сохранить, её выгружают на сервер или SD до того, как она вылетела. выбрасывает безвозвратно
- Почему для 24 точек выгоднее сдвиг массива, чем указатель головы по модулю?
- При печати кольца с головой индексы оборачиваются через середину массива, и каждый цикл обрастает арифметикой остатков. Для маленького окна простой сдвиг понятнее, а цена — микроскопическая. двадцать три присваивания дешевле модульной арифметики в каждой функции печати
- Сколько времени пакет истории в 150 байтов занимает при 9600 бод?
- При 9600 бод байт идёт около миллисекунды, поэтому 150 байтов — примерно 150 мс канала. Раз в час такое занятие канала не замечается, а на 115200 бод тот же пакет пролетает за пятнадцать миллисекунд. около 150 мс