кстате говоря… малина же это арм… а пк другая архитектура… может вам на малине ваш код попытаться запустить ?
и тогда генерация будет одинаковая ? или все таки сама по себе esp более аналоговая))) а пк больше цифровые…
Код работает и на УНО и на RP2040, просто генерация на МК интересней, сами подходы. Сначала узор по сектору, потом узоры в ковёр…и т.д. Можно и на мониторе ПК выводить силами ESP, но цветов будет мало и разрешения тоже.
…в плане практических реализаций кроме картинки-картонки интересен вариант настольный предложенный
…ещё конечно интересен проектор из дисплея, но для этого нужен вариант переделки 200 рублёвого дисплея на внешнюю подсветку. С монитором от ПК такое делают и подсвечивают от окна
(Иван Алтай вроде фотки выкладывал).
lilik а esp с модулем звука i2s тоже звуки лучше воспроизводит чем пк ? даже кажется лучше чем телефон… но я басы пробовал, попсу еще не проверял, что бы узнать как голос будет звучать… кажется в моем пк совсем плохая звуковая карта)))
эээх если бы придумали бы как на esp 32 так коротко написать программу, что бы бассы создавала)))… а то нейросети вымогают 25к за 100 треков удачных….
Непосредственно звуки воспроизводит громкоговоритель. Качество звука при этом зависит от двух вещей: от самого громкоговорителя и от качества подаваемого на него сигнала.
Сигнал в настоящее время, как правило, берется с DAC (Цифро-Аналогового Преобразователя).
А качество сигнала с DAC, как обычно, зависит от двух вещей: от качества самого DAC (в основном от качества обвязки) и от исходного цифрового сигнала.
Если Вы наблюдательны, то должны были заметить, что в перечислении сущностей, от которых зависит качество звука, отсутствуют как i2s, так и ПК.
Есть такая функция - синус называется. Если адекватно подобрать для нее частоту, то будут как раз “бассы”.
На ESP эта функция тоже есть.
andriano я просто беру max39857 понятно что не ардуино воспроизводит… она только перенаправляет данные с sd карты, и бассы получаются четкие, даже попса кажется звучит как то живо))) а может я просто внушился, но мне кажется разница существенна…
не могу так сказать если воспроизвожу через шим, но если поместить короткий трек в микроконтроллер, и опять воспроизвести через усилитель, то все равно кажется совершенно другой звук…
мне бы динамичные, с плавными переходами, и т.д. что бы программа автоматически их генерировала, мало ли lilik что то придумает)))
не верю конечно, нереально сложно… но а вдруг?))) данные только придумать надо как генерировать))) а через 1 пин воспроизводить это не то…
кстате попробовал на плеерах, например vs1053 и опять же звучание как то больше нравится, не могу такое получить на пк, и на телефоне, если с пк понятно, у меня нет звуковой карты вообще))) то с телефоном не все так однозначно, и в целом я только это заметил, и приятно удивлен открытием, может кому то тоже понравится именно так что то слушать…
Я с таким дела не имел.
Очень даже может быть.
Есть огромная масса способов испортить звук. И экономия на деталях - один из самых эффективных.
Это, кстати, еще более эффективный способ испортить звук. Тут простая арифметика: произведение динамического диапазона на частоту дискретизации не может превышать входную частоту таймера-счетчика. Для AVR примерно соответствует 7-разрядному звуку.
Начать нужно с того, чтобы сформулировать, что именно должна генерировать программа.
Это DSP.
Кстати, из DSP мне понравился ADAU1701. Но, увы, сейчас документация от разработчика для него без VPN недоступна.
Я люблю глазами, а не ушами как всю жизнь мне твердит жена ![]()
это не любовь, а потребность раскрасить серые и унылые будни разными способами!
Не знаю, я жду летнее заявление Трампа ![]()
https://rg.ru/2026/02/24/glava-pentagona-hegset-dopustil-sushchestvovanie-inoplanetian.html?utm_referrer=https%3A%2F%2Fyandex.ru%2F
…надо Ангар 18 пересмотреть.
lilik а что он скажет ? они уже тут ?))) но мы не можем это доказать…
шутки шутками, а когда вы в массовом городе(например калуга) увидите круги прямо в городе ночью, не высоко и без всяких звуков, при этом не одни а массово, смешно так не будет)))
а кому выгодно вообще поддерживать слухи о других цивилизациях ? и зачем ?)))
Наверное про падение корабля в 1947 году и гибель двух пилотов-инопланетян. Наверное покажет корабль и тела.
если я ищу то чего не понимаю, и не могу подкорректировать ии
В текущем коде точки генерируются функцией ran_Sait(3):
if (variant == 1 || variant == 3) {
points[i].x = random(CENTER_X, CENTER_X + 120); // x от 120 до 240
points[i].y = random(CENTER_Y, CENTER_Y + 60); // y от 120 до 180
}
максимум что я могу получить
Чтобы найти координаты сайтов по уже нарисованной диаграмме Вороного, нужно:
cpp
// Функция для определения координат сайтов по пикселям диаграммы Вороного
void findSitesFromVoronoi() {
const int SEARCH_RADIUS = 5; // Радиус поиска ядра локуса
// Сканируем область, где могут быть сайты
for (int y = CENTER_Y; y < CENTER_Y + 60; y++) {
for (int x = CENTER_X; x < CENTER_X + 120; x++) {
// Анализируем окрестность пикселя
boolean isPotentialSite = true;
uint16_t centerColor = tft.readPixel(x, y);
// Проверяем, все ли пиксели в окрестности одного цвета
for (int dy = -SEARCH_RADIUS; dy <= SEARCH_RADIUS; dy++) {
for (int dx = -SEARCH_RADIUS; dx <= SEARCH_RADIUS; dx++) {
if (dx == 0 && dy == 0) continue;
int nx = x + dx;
int ny = y + dy;
// Проверяем границы
if (nx < CENTER_X || nx > CENTER_X + 120 ||
ny < CENTER_Y || ny > CENTER_Y + 60) {
isPotentialSite = false;
break;
}
uint16_t pixelColor = tft.readPixel(nx, ny);
if (pixelColor != centerColor) {
isPotentialSite = false;
break;
}
}
if (!isPotentialSite) break;
}
// Если нашли потенциальный сайт
if (isPotentialSite && centerColor != GC9A01A_BLACK) {
// Здесь можно сохранить координаты
Serial.printf("Site found at: (%d, %d), Color: %04X\n",
x, y, centerColor);
}
}
}
}
Альтернативный подход - поиск центров масс
Более точный метод - найти центры масс каждого локуса:
struct Site {
float x, y;
uint16_t color;
long pixelCount;
};
void findSitesByCentroid() {
const int MAX_SITES = 30;
Site sites[MAX_SITES];
int siteCount = 0;
// Проходим по всей области поиска
for (int y = CENTER_Y; y < CENTER_Y + 60; y++) {
for (int x = CENTER_X; x < CENTER_X + 120; x++) {
uint16_t currentColor = tft.readPixel(x, y);
if (currentColor == GC9A01A_BLACK) continue;
// Ищем, есть ли уже такой цвет в найденных сайтах
int siteIndex = -1;
for (int i = 0; i < siteCount; i++) {
if (sites[i].color == currentColor) {
siteIndex = i;
break;
}
}
// Если новый цвет - создаем новый сайт
if (siteIndex == -1 && siteCount < MAX_SITES) {
siteIndex = siteCount;
sites[siteIndex].color = currentColor;
sites[siteIndex].x = 0;
sites[siteIndex].y = 0;
sites[siteIndex].pixelCount = 0;
siteCount++;
}
// Добавляем координаты к центру масс
if (siteIndex != -1) {
sites[siteIndex].x += x;
sites[siteIndex].y += y;
sites[siteIndex].pixelCount++;
}
}
}
// Вычисляем центры масс
for (int i = 0; i < siteCount; i++) {
if (sites[i].pixelCount > 0) {
sites[i].x /= sites[i].pixelCount;
sites[i].y /= sites[i].pixelCount;
Serial.printf("Site %d: (%.1f, %.1f), Color: %04X, Pixels: %ld\n",
i, sites[i].x, sites[i].y,
sites[i].color, sites[i].pixelCount);
}
}
}
как писал ранее, что бы использовать ии, надо понимать что ты делаешь))) это скорее инструмент для быстрой печати, но иногда он справляется и сам!
Кажется ИИ правильно уловил суть. Единственное, что он должен был спросить - а где брать
tft.readPixel(x, y); ![]()
…Вот как догадаешься так и всё станет на свои места…так ИИ и передай.
…вообще не торопись, решение обратной задачи это лишь первый шаг.
я не знаю справится ли он он в этот раз, но как минимуму может получится генератор треков, а то он кажется думает что это одна и таже задача)))
#include <Adafruit_GFX.h>
#include <Adafruit_GC9A01A.h>
#include <SPI.h>
#define TFT_DC 17
#define TFT_CS 16
Adafruit_GC9A01A tft(TFT_CS, TFT_DC);
// Константы координат смещения центра
const int CENTER_X = 120;
const int CENTER_Y = 120;
const float Z = 300.0;
struct Point {
float x, y;
uint16_t color;
};
// Структура для обратной задачи (на чистом C)
struct PixelVote {
long sumX;
long sumY;
int count;
uint16_t color;
boolean used;
};
const int NUM_POINTS = 30;
const int MAX_LOCUS = 50; // Максимальное количество различных цветов
Point points[NUM_POINTS];
PixelVote votes[MAX_LOCUS]; // Статический массив вместо map
int voteCount = 0;
long Y = 0;
void setup() {
Serial.begin(115200);
randomSeed(analogRead(32));
tft.begin();
tft.fillScreen(GC9A01A_BLACK);
tft.setRotation(2);
ran_Sait(3);
// Инициализируем массив голосов
for (int i = 0; i < MAX_LOCUS; i++) {
votes[i].used = false;
}
}
void loop() {
if (millis() - Y > 90000L) {
tft.fillScreen(GC9A01A_BLACK);
Y = millis();
ran_Sait(3);
// Очищаем голоса
for (int i = 0; i < MAX_LOCUS; i++) {
votes[i].used = false;
}
voteCount = 0;
}
for (byte v = 1; v < 3; v++) {
draw_RisSektor(v);
delay(500);
}
// Решаем обратную задачу
solveInverseProblem();
delay(5000);
}
// Функция вычисления метрики
float dist(float x1, float y1, float x2, float y2, byte m) {
if (m == 1) return sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2));
if (m == 2) return (abs(x1 - x2) + abs(y2 - y1));
if (m == 3) return abs(abs(x1 - x2) - abs(y1 - y2));
if (m == 4) return max(abs(x1 - x2), abs(y1 - y2));
return 0;
}
// Генерация точек (сайтов)
void ran_Sait(byte variant) {
for (int i = 0; i < NUM_POINTS; i++) {
if (variant == 1 || variant == 3) {
points[i].x = random(CENTER_X, CENTER_X + 120);
points[i].y = random(CENTER_Y, CENTER_Y + 60);
}
if (variant == 2 || variant == 3) {
byte Gg = random(256);
points[i].color = tft.color565(random(Gg), Gg, random(Gg));
}
}
}
// Добавление голоса для цвета
void addVote(int x, int y, uint16_t color) {
if (color == GC9A01A_BLACK) return;
// Ищем существующий голос для этого цвета
int index = -1;
for (int i = 0; i < voteCount; i++) {
if (votes[i].color == color) {
index = i;
break;
}
}
// Если не нашли, создаем новый
if (index == -1 && voteCount < MAX_LOCUS) {
index = voteCount;
votes[index].sumX = 0;
votes[index].sumY = 0;
votes[index].count = 0;
votes[index].color = color;
votes[index].used = true;
voteCount++;
}
// Добавляем голос
if (index != -1) {
votes[index].sumX += x;
votes[index].sumY += y;
votes[index].count++;
}
}
// Отрисовка с сбором данных
void draw_RisSektor(byte variant) {
for (float f1 = 0.0; f1 <= PI / 6.0; f1 += PI / Z) {
for (int r1 = 0; r1 <= 120; r1 += 1) {
int x = CENTER_X + cos(f1) * r1;
int y = CENTER_Y + sin(f1) * r1;
// Находим ближайшую точку
int closestPoint = 0;
float minDist = dist(x, y, points[0].x, points[0].y, variant);
for (int i = 1; i < NUM_POINTS; i++) {
float d = dist(x, y, points[i].x, points[i].y, variant);
if (d < minDist) {
minDist = d;
closestPoint = i;
}
}
// Отрисовываем с симметрией
for (float i = 0.01; i <= 2 * PI; i += PI / 3.0) {
if (minDist > 12) {
int x1 = CENTER_X + cos(f1 + i) * r1;
int y1 = CENTER_Y + sin(f1 + i) * r1;
uint16_t color = points[closestPoint].color;
tft.drawPixel(x1, y1, color);
// Собираем данные ТОЛЬКО для базового сектора
// Проверяем, что точка в базовом секторе (0 до 30 градусов)
float angle = atan2(y1 - CENTER_Y, x1 - CENTER_X);
if (angle >= 0 && angle <= PI/6.0) {
addVote(x1, y1, color);
}
int x2 = CENTER_X + cos(2 * PI - f1 + i) * r1;
int y2 = CENTER_Y + sin(2 * PI - f1 + i) * r1;
tft.drawPixel(x2, y2, color);
}
}
}
}
}
// Решение обратной задачи
void solveInverseProblem() {
Serial.println("\n=== РЕШЕНИЕ ОБРАТНОЙ ЗАДАЧИ ===");
Serial.println("Найденные сайты (центры масс локусов):");
int foundSites = 0;
for (int i = 0; i < voteCount; i++) {
if (votes[i].count > 10) { // Игнорируем слишком маленькие области
float centerX = (float)votes[i].sumX / votes[i].count;
float centerY = (float)votes[i].sumY / votes[i].count;
Serial.printf("Сайт %d: (%.1f, %.1f), цвет: %04X, пикселей: %d\n",
foundSites++, centerX, centerY,
votes[i].color, votes[i].count);
// Визуализируем
markFoundSite(centerX, centerY, votes[i].color);
}
}
Serial.printf("Всего найдено сайтов: %d\n", foundSites);
// Сравниваем с оригинальными точками
compareWithOriginal();
}
// Визуализация найденного сайта
void markFoundSite(float x, float y, uint16_t color) {
int ix = round(x);
int iy = round(y);
// Рисуем желтый кружок (лучше видно)
tft.drawCircle(ix, iy, 6, GC9A01A_YELLOW);
tft.drawLine(ix - 4, iy, ix + 4, iy, GC9A01A_YELLOW);
tft.drawLine(ix, iy - 4, ix, iy + 4, GC9A01A_YELLOW);
// Восстанавливаем цвет в центре
tft.drawPixel(ix, iy, color);
}
// Сравнение с оригинальными точками
void compareWithOriginal() {
Serial.println("\n=== ОРИГИНАЛЬНЫЕ ТОЧКИ (из ran_Sait) ===");
for (int i = 0; i < NUM_POINTS; i++) {
Serial.printf("points[%d]: (%.1f, %.1f), цвет: %04X\n",
i, points[i].x, points[i].y, points[i].color);
// Отмечаем оригинальные точки синим
int ix = round(points[i].x);
int iy = round(points[i].y);
tft.drawCircle(ix, iy, 3, GC9A01A_BLUE);
}
// Оценка точности
Serial.println("\n=== ОЦЕНКА ТОЧНОСТИ ===");
int matches = 0;
float totalError = 0;
for (int i = 0; i < NUM_POINTS; i++) {
// Ищем соответствующий голос по цвету
for (int j = 0; j < voteCount; j++) {
if (votes[j].color == points[i].color && votes[j].count > 10) {
float foundX = (float)votes[j].sumX / votes[j].count;
float foundY = (float)votes[j].sumY / votes[j].count;
float error = sqrt(pow(foundX - points[i].x, 2) +
pow(foundY - points[i].y, 2));
Serial.printf("Точка %d: ошибка = %.2f пикселей\n", i, error);
matches++;
totalError += error;
break;
}
}
}
if (matches > 0) {
Serial.printf("\nСредняя ошибка: %.2f пикселей\n", totalError / matches);
Serial.printf("Найдено %d из %d точек\n", matches, NUM_POINTS);
}
}
передал, задал пару уточняющих вопросов, я просто спросил, он извинялся и выдавал все новые, и новые кода)))
Добавил спиральность в узор.
#include <Adafruit_GFX.h>
#include <Adafruit_GC9A01A.h>
#include <SPI.h>
#define TFT_DC 17
#define TFT_CS 16
/*
SCL --> 18 (SCK)
SDA --> 23 (MOSI)
RST --> EN
*/
Adafruit_GC9A01A tft(TFT_CS, TFT_DC);
// Константы координат смещения центра
const int CENTER_X = 120;
const int CENTER_Y = 120;
const float Z = 300.0; //от 100.0 до 1000.0 регулировка качества отрисовки пикселей с пропусками и без них
struct Point {
float x, y;
int R, G, B;
};
const int NUM_POINTS = 12; // Количество точек для диаграммы Вороного
Point points[NUM_POINTS];
long Y = 0; //
void setup() {
// Инициализация генератора случайных чисел
randomSeed(analogRead(32));
tft.begin();
tft.fillScreen(GC9A01A_BLACK);
// tft.cp437(true);
tft.setRotation(2);
ran_Sait(3);
}
void loop() {
if (millis() - Y > 90000ul) {//новый цикл роста узоров по условию каждые 90 секунд
tft.fillScreen(GC9A01A_BLACK);
Y = millis();
}
for (byte v = 1; v < 4; v++) {
draw_RisSektor(v);
delay(500);
}
}
//////////////////////////////////////
//функция вычисления метрики (как вариант расстояния между двумя точками)
float dist(float x1, float y1, float x2, float y2, byte m) {
if (m == 1) {
return sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2)); //Евклидова метрика
}
if (m == 2) {
return (abs(x1 - x2) + abs(y2 - y1)); //Манхэттенская метрика
}
if (m == 3) {
return max(abs(x1 - x2), abs(y1 - y2)); // авторская метрика №1
}
}
//////////////////////////////////////
// функция генерации точек (сайтов)для локусов диаграммы Вороного
void ran_Sait(byte variant) {
for (int i = 0; i < NUM_POINTS; i++) {
if (variant == 1 || variant == 3) {
points[i].x = random(CENTER_X, CENTER_X + 120 );
points[i].y = random(CENTER_Y, CENTER_Y + 60);
}
if (variant == 2 || variant == 3) {
// points[i].color = tft.color565(random(256), random(256), random(256));
points[i].R = random(256); points[i].G = random(256); points[i].B = random(256);
}
}
}
//////////////////////////////////////
//функция вывода 12 отсимметриченных секторов узора по базовому сектору картинки
void draw_RisSektor(byte variant) {
for (float f1 = 0.0; f1 <= PI / 6.0; f1 += PI / Z) {//перебор всех точек сектора
for (int r1 = 0; r1 <= 120 ; r1 += 1) {
int x = CENTER_X + cos(f1) * r1;
int y = CENTER_Y + sin(f1) * r1;
int closestPoint = 0;
float minDist = dist(x, y, points[0].x, points[0].y, variant );
for (int i = 1; i < NUM_POINTS; i++) {//определение точки-сайта ближайшей к перебираемой точке
float d = dist(x, y, points[i].x, points[i].y, variant );
if (d < minDist) {
minDist = d;
closestPoint = i;
}
}
for (float i = 0.01; i <= 2 * PI; i += PI / 3.0) {// отрисовка точек-клонов перебираемой точки
if (minDist > 15) {//условие роста степени заполнения элементов узора
float df = (1.2 * r1) / 120;//завихрение узора в спираль
int x1 = CENTER_X + cos(f1 + i + df) * r1;
int y1 = CENTER_Y + sin(f1 + i + df) * r1;
tft.drawPixel(x1, y1, tft.color565(abs(points[closestPoint].R - 10 * minDist), abs(points[closestPoint].G - 10 * minDist), abs(points[closestPoint].B - 10 * minDist)));
int x2 = CENTER_X + cos(2 * PI - f1 + i + df) * r1;
int y2 = CENTER_Y + sin(2 * PI - f1 + i + df) * r1;
tft.drawPixel(x2, y2, tft.color565(abs(points[closestPoint].R - 10 * minDist), abs(points[closestPoint].G - 10 * minDist), abs(points[closestPoint].B - 10 * minDist)));
}
}
}
}
ran_Sait(3);//новая генерация точек-сайтов
}
//////////////////////////////////////
void cvet() {
}
/////////////////////////////////////
…купил матриц 4 штуки для варианта узора 10х10…итого 400.
https://aliexpress.ru/item/1005006922046351.html?
а когда окончательная версия готова будет ?)))
со всеми разными этими режимами….
часы в конце думаете добавлять поверх этой анимации ?
или все таки проектор будет… или с лупой… определились что в итоге делать ?
Скорее всего заброшу.
Если только стрелочные.
Режим проектора работает, но тускло, свечения светодиода(ов) дисплея не хватает для дня, только в темноте нормально видно. С линзой как увеличительным стеклом интересен вариант, но для готовой конструкции надо делать футляр-тубус.
если курочить не жалко…
у дисплея наверное есть шлейф, и его можно отогнуть, не знаю задняя крышка дисплея легко удаляется или нет, если легко, то ее снимаем, за ней делаем освещение самим, и можно отображать и днем, не знаю сколько разломать дисплеев придется, если не все там просто)))
нашли что то интересное что можно собрать еще ?








