FabGL
ESP32 Display Controller and Graphics Library
fabutils.cpp
1/*
2 Created by Fabrizio Di Vittorio (fdivitto2013@gmail.com) - <http://www.fabgl.com>
3 Copyright (c) 2019-2022 Fabrizio Di Vittorio.
4 All rights reserved.
5
6
7* Please contact fdivitto2013@gmail.com if you need a commercial license.
8
9
10* This library and related software is available under GPL v3.
11
12 FabGL is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
16
17 FabGL is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with FabGL. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26
27#include <string.h>
28#include <stdlib.h>
29#include <unistd.h>
30#include <sys/stat.h>
31#include <math.h>
32
33#include "ff.h"
34#include "diskio.h"
35extern "C" {
36 #include <dirent.h>
37}
38#include "esp_vfs_fat.h"
39#include "esp_task_wdt.h"
40#include "driver/sdspi_host.h"
41#include "sdmmc_cmd.h"
42#include "esp_spiffs.h"
43#include "soc/efuse_reg.h"
44#include "soc/rtc.h"
45#include "esp_ipc.h"
46#include "soc/adc_channel.h"
47
48#include "fabutils.h"
53
54
55#pragma GCC optimize ("O2")
56
57
58
59namespace fabgl {
60
61
62
64// TimeOut
65
66
67TimeOut::TimeOut()
68 : m_start(esp_timer_get_time())
69{
70}
71
72
73bool TimeOut::expired(int valueMS)
74{
75 return valueMS > -1 && ((esp_timer_get_time() - m_start) / 1000) > valueMS;
76}
77
78
79
81// isqrt
82
83// Integer square root by Halleck's method, with Legalize's speedup
84int isqrt (int x)
85{
86 if (x < 1)
87 return 0;
88 int squaredbit = 0x40000000;
89 int remainder = x;
90 int root = 0;
91 while (squaredbit > 0) {
92 if (remainder >= (squaredbit | root)) {
93 remainder -= (squaredbit | root);
94 root >>= 1;
95 root |= squaredbit;
96 } else
97 root >>= 1;
98 squaredbit >>= 2;
99 }
100 return root;
101}
102
103
104
106// calcParity
107
108bool calcParity(uint8_t v)
109{
110 v ^= v >> 4;
111 v &= 0xf;
112 return (0x6996 >> v) & 1;
113}
114
115
117// realloc32
118// free32
119
120// size must be a multiple of uint32_t (32 bit)
121void * realloc32(void * ptr, size_t size)
122{
123 uint32_t * newBuffer = (uint32_t*) heap_caps_malloc(size, MALLOC_CAP_32BIT);
124 if (ptr) {
125 moveItems(newBuffer, (uint32_t*)ptr, size / sizeof(uint32_t));
126 heap_caps_free(ptr);
127 }
128 return newBuffer;
129}
130
131
132void free32(void * ptr)
133{
134 heap_caps_free(ptr);
135}
136
137
138
140// msToTicks
141
142uint32_t msToTicks(int ms)
143{
144 return ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(ms);
145}
146
147
149// getChipPackage
150
151ChipPackage getChipPackage()
152{
153 // read CHIP_VER_PKG (block0, byte 3, 105th bit % 32 = 9, 3 bits)
154 uint32_t ver_pkg = (REG_READ(EFUSE_BLK0_RDATA3_REG) >> 9) & 7;
155 switch (ver_pkg) {
156 case 0:
157 return ChipPackage::ESP32D0WDQ6; // WROOOM-32
158 case 1:
159 return ChipPackage::ESP32D0WDQ5; // WROVER-B
160 case 2:
161 return ChipPackage::ESP32D2WDQ5;
162 case 5:
163 return ChipPackage::ESP32PICOD4; // TTGO-VGA32
164 default:
165 return ChipPackage::Unknown;
166 }
167}
168
169
171adc1_channel_t ADC1_GPIO2Channel(gpio_num_t gpio)
172{
173 switch (gpio) {
174 case ADC1_CHANNEL_0_GPIO_NUM:
175 return ADC1_CHANNEL_0;
176 case ADC1_CHANNEL_1_GPIO_NUM:
177 return ADC1_CHANNEL_1;
178 case ADC1_CHANNEL_2_GPIO_NUM:
179 return ADC1_CHANNEL_2;
180 case ADC1_CHANNEL_3_GPIO_NUM:
181 return ADC1_CHANNEL_3;
182 case ADC1_CHANNEL_4_GPIO_NUM:
183 return ADC1_CHANNEL_4;
184 case ADC1_CHANNEL_5_GPIO_NUM:
185 return ADC1_CHANNEL_5;
186 case ADC1_CHANNEL_6_GPIO_NUM:
187 return ADC1_CHANNEL_6;
188 case ADC1_CHANNEL_7_GPIO_NUM:
189 return ADC1_CHANNEL_7;
190 default:
191 return ADC1_CHANNEL_0;
192 }
193}
194
195
197// configureGPIO
198void configureGPIO(gpio_num_t gpio, gpio_mode_t mode)
199{
200 PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO);
201 gpio_set_direction(gpio, mode);
202}
203
204
206// getApbFrequency
207uint32_t getApbFrequency()
208{
209 rtc_cpu_freq_config_t conf;
210 rtc_clk_cpu_freq_get_config(&conf);
211 return conf.freq_mhz >= 80 ? 80000000 : (conf.source_freq_mhz * 80000000 / conf.div);
212}
213
214
216// getCPUFrequencyMHz
217uint32_t getCPUFrequencyMHz()
218{
219 rtc_cpu_freq_config_t conf;
220 rtc_clk_cpu_freq_get_config(&conf);
221 return conf.freq_mhz;
222}
223
224
226// esp_intr_alloc_pinnedToCore
227
228struct esp_intr_alloc_args {
229 int source;
230 int flags;
231 intr_handler_t handler;
232 void * arg;
233 intr_handle_t * ret_handle;
234 TaskHandle_t waitingTask;
235};
236
237
238void esp_intr_alloc_pinnedToCore_call(void * arg)
239{
240 auto args = (esp_intr_alloc_args*) arg;
241 esp_intr_alloc(args->source, args->flags, args->handler, args->arg, args->ret_handle);
242}
243
244
245void esp_intr_alloc_pinnedToCore(int source, int flags, intr_handler_t handler, void * arg, intr_handle_t * ret_handle, int core)
246{
247 esp_intr_alloc_args args = { source, flags, handler, arg, ret_handle, xTaskGetCurrentTaskHandle() };
248 esp_ipc_call_blocking(core, esp_intr_alloc_pinnedToCore_call, &args);
249}
250
251
252
254// converts '\' or '/' to newSep
255
256void replacePathSep(char * path, char newSep)
257{
258 for (; *path; ++path)
259 if (*path == '\\' || *path == '/')
260 *path = newSep;
261}
262
263
264
266// Sutherland-Cohen line clipping algorithm
267
268static int clipLine_code(int x, int y, Rect const & clipRect)
269{
270 int code = 0;
271 if (x < clipRect.X1)
272 code = 1;
273 else if (x > clipRect.X2)
274 code = 2;
275 if (y < clipRect.Y1)
276 code |= 4;
277 else if (y > clipRect.Y2)
278 code |= 8;
279 return code;
280}
281
282// false = line is out of clipping rect
283// true = line intersects or is inside the clipping rect (x1, y1, x2, y2 are changed if checkOnly=false)
284bool clipLine(int & x1, int & y1, int & x2, int & y2, Rect const & clipRect, bool checkOnly)
285{
286 int newX1 = x1;
287 int newY1 = y1;
288 int newX2 = x2;
289 int newY2 = y2;
290 int topLeftCode = clipLine_code(newX1, newY1, clipRect);
291 int bottomRightCode = clipLine_code(newX2, newY2, clipRect);
292 while (true) {
293 if ((topLeftCode == 0) && (bottomRightCode == 0)) {
294 if (!checkOnly) {
295 x1 = newX1;
296 y1 = newY1;
297 x2 = newX2;
298 y2 = newY2;
299 }
300 return true;
301 } else if (topLeftCode & bottomRightCode) {
302 break;
303 } else {
304 int x = 0, y = 0;
305 int ncode = topLeftCode != 0 ? topLeftCode : bottomRightCode;
306 if (ncode & 8) {
307 x = newX1 + (newX2 - newX1) * (clipRect.Y2 - newY1) / (newY2 - newY1);
308 y = clipRect.Y2;
309 } else if (ncode & 4) {
310 x = newX1 + (newX2 - newX1) * (clipRect.Y1 - newY1) / (newY2 - newY1);
311 y = clipRect.Y1;
312 } else if (ncode & 2) {
313 y = newY1 + (newY2 - newY1) * (clipRect.X2 - newX1) / (newX2 - newX1);
314 x = clipRect.X2;
315 } else if (ncode & 1) {
316 y = newY1 + (newY2 - newY1) * (clipRect.X1 - newX1) / (newX2 - newX1);
317 x = clipRect.X1;
318 }
319 if (ncode == topLeftCode) {
320 newX1 = x;
321 newY1 = y;
322 topLeftCode = clipLine_code(newX1, newY1, clipRect);
323 } else {
324 newX2 = x;
325 newY2 = y;
326 bottomRightCode = clipLine_code(newX2, newY2, clipRect);
327 }
328 }
329 }
330 return false;
331}
332
333
335// removeRectangle
336// remove "rectToRemove" from "mainRect", pushing remaining rectangles to "rects" stack
337
338void removeRectangle(Stack<Rect> & rects, Rect const & mainRect, Rect const & rectToRemove)
339{
340 if (!mainRect.intersects(rectToRemove) || rectToRemove.contains(mainRect))
341 return;
342
343 // top rectangle
344 if (mainRect.Y1 < rectToRemove.Y1)
345 rects.push(Rect(mainRect.X1, mainRect.Y1, mainRect.X2, rectToRemove.Y1 - 1));
346
347 // bottom rectangle
348 if (mainRect.Y2 > rectToRemove.Y2)
349 rects.push(Rect(mainRect.X1, rectToRemove.Y2 + 1, mainRect.X2, mainRect.Y2));
350
351 // left rectangle
352 if (mainRect.X1 < rectToRemove.X1)
353 rects.push(Rect(mainRect.X1, tmax(rectToRemove.Y1, mainRect.Y1), rectToRemove.X1 - 1, tmin(rectToRemove.Y2, mainRect.Y2)));
354
355 // right rectangle
356 if (mainRect.X2 > rectToRemove.X2)
357 rects.push(Rect(rectToRemove.X2 + 1, tmax(rectToRemove.Y1, mainRect.Y1), mainRect.X2, tmin(rectToRemove.Y2, mainRect.Y2)));
358}
359
360
362// Rect
363
364Rect IRAM_ATTR Rect::merge(Rect const & rect) const
365{
366 return Rect(imin(rect.X1, X1), imin(rect.Y1, Y1), imax(rect.X2, X2), imax(rect.Y2, Y2));
367}
368
369
370Rect IRAM_ATTR Rect::intersection(Rect const & rect) const
371{
372 return Rect(tmax(X1, rect.X1), tmax(Y1, rect.Y1), tmin(X2, rect.X2), tmin(Y2, rect.Y2));
373}
374
375
377// rgb222_to_hsv
378// R, G, B in the 0..3 range
379void rgb222_to_hsv(int R, int G, int B, double * h, double * s, double * v)
380{
381 double r = R / 3.0;
382 double g = G / 3.0;
383 double b = B / 3.0;
384 double cmax = tmax<double>(tmax<double>(r, g), b);
385 double cmin = tmin<double>(tmin<double>(r, g), b);
386 double diff = cmax - cmin;
387 if (cmax == cmin)
388 *h = 0;
389 else if (cmax == r)
390 *h = fmod((60.0 * ((g - b) / diff) + 360.0), 360.0);
391 else if (cmax == g)
392 *h = fmod((60.0 * ((b - r) / diff) + 120.0), 360.0);
393 else if (cmax == b)
394 *h = fmod((60.0 * ((r - g) / diff) + 240.0), 360.0);
395 *s = cmax == 0 ? 0 : (diff / cmax) * 100.0;
396 *v = cmax * 100.0;
397}
398
399
400
402// StringList
403
404
405StringList::StringList()
406 : m_items(nullptr),
407 m_selMap(nullptr),
408 m_ownStrings(false),
409 m_count(0),
410 m_allocated(0)
411{
412}
413
414
415StringList::~StringList()
416{
417 clear();
418}
419
420
421void StringList::clear()
422{
423 if (m_ownStrings) {
424 for (int i = 0; i < m_count; ++i)
425 free((void*) m_items[i]);
426 }
427 free32(m_items);
428 free32(m_selMap);
429 m_items = nullptr;
430 m_selMap = nullptr;
431 m_count = m_allocated = 0;
432}
433
434
435void StringList::copyFrom(StringList const & src)
436{
437 clear();
438 m_count = src.m_count;
439 checkAllocatedSpace(m_count);
440 for (int i = 0; i < m_count; ++i) {
441 m_items[i] = nullptr;
442 set(i, src.m_items[i]);
443 }
444 deselectAll();
445}
446
447
448void StringList::copySelectionMapFrom(StringList const & src)
449{
450 int maskLen = (31 + m_allocated) / 32;
451 for (int i = 0; i < maskLen; ++i)
452 m_selMap[i] = src.m_selMap[i];
453}
454
455
456void StringList::checkAllocatedSpace(int requiredItems)
457{
458 if (m_allocated < requiredItems) {
459 if (m_allocated == 0) {
460 // first time allocates exact space
461 m_allocated = requiredItems;
462 } else {
463 // next times allocate double
464 while (m_allocated < requiredItems)
465 m_allocated *= 2;
466 }
467 m_items = (char const**) realloc32(m_items, m_allocated * sizeof(char const *));
468 m_selMap = (uint32_t*) realloc32(m_selMap, (31 + m_allocated) / 32 * sizeof(uint32_t));
469 }
470}
471
472
473void StringList::insert(int index, char const * str)
474{
475 ++m_count;
476 checkAllocatedSpace(m_count);
477 moveItems(m_items + index + 1, m_items + index, m_count - index - 1);
478 m_items[index] = nullptr;
479 set(index, str);
480 deselectAll();
481}
482
483
484int StringList::append(char const * str)
485{
486 insert(m_count, str);
487 return m_count - 1;
488}
489
490
491int StringList::appendFmt(const char *format, ...)
492{
493 takeStrings();
494 va_list ap;
495 va_start(ap, format);
496 int size = vsnprintf(nullptr, 0, format, ap) + 1;
497 if (size > 0) {
498 va_end(ap);
499 va_start(ap, format);
500 char buf[size + 1];
501 vsnprintf(buf, size, format, ap);
502 insert(m_count, buf);
503 }
504 va_end(ap);
505 return m_count - 1;
506}
507
508
509void StringList::append(char const * strlist[], int count)
510{
511 for (int i = 0; i < count; ++i)
512 insert(m_count, strlist[i]);
513}
514
515
516// separator cannot be "0"
517void StringList::appendSepList(char const * strlist, char separator)
518{
519 if (strlist) {
520 takeStrings();
521 char const * start = strlist;
522 while (*start) {
523 auto end = strchr(start, separator);
524 if (!end)
525 end = strchr(start, 0);
526 int len = end - start;
527 char str[len + 1];
528 memcpy(str, start, len);
529 str[len] = 0;
530 insert(m_count, str);
531 start += len + (*end == 0 ? 0 : 1);
532 }
533 }
534}
535
536
537void StringList::set(int index, char const * str)
538{
539 if (m_ownStrings) {
540 free((void*)m_items[index]);
541 m_items[index] = (char const*) malloc(strlen(str) + 1);
542 strcpy((char*)m_items[index], str);
543 } else {
544 m_items[index] = str;
545 }
546}
547
548
549void StringList::remove(int index)
550{
551 if (m_ownStrings)
552 free((void*)m_items[index]);
553 moveItems(m_items + index, m_items + index + 1, m_count - index - 1);
554 --m_count;
555 deselectAll();
556}
557
558
559void StringList::takeStrings()
560{
561 if (!m_ownStrings) {
562 m_ownStrings = true;
563 // take existing strings
564 for (int i = 0; i < m_count; ++i) {
565 char const * str = m_items[i];
566 m_items[i] = nullptr;
567 set(i, str);
568 }
569 }
570}
571
572
573void StringList::deselectAll()
574{
575 for (int i = 0; i < (31 + m_count) / 32; ++i)
576 m_selMap[i] = 0;
577}
578
579
580bool StringList::selected(int index)
581{
582 return m_selMap[index / 32] & (1 << (index % 32));
583}
584
585
586// -1 = no items selected
587int StringList::getFirstSelected()
588{
589 for (int i = 0; i < m_count; ++i)
590 if (selected(i))
591 return i;
592 return -1;
593}
594
595
596void StringList::select(int index, bool value)
597{
598 if (value)
599 m_selMap[index / 32] |= 1 << (index % 32);
600 else
601 m_selMap[index / 32] &= ~(1 << (index % 32));
602}
603
604
605
606// StringList
608
609
610
612// FileBrowser
613
614
615char const * FileBrowser::s_SPIFFSMountPath;
616bool FileBrowser::s_SPIFFSMounted = false;
617size_t FileBrowser::s_SPIFFSMaxFiles;
618
619char const * FileBrowser::s_SDCardMountPath;
620bool FileBrowser::s_SDCardMounted = false;
621size_t FileBrowser::s_SDCardMaxFiles;
622int FileBrowser::s_SDCardAllocationUnitSize;
623int8_t FileBrowser::s_SDCardMISO;
624int8_t FileBrowser::s_SDCardMOSI;
625int8_t FileBrowser::s_SDCardCLK;
626int8_t FileBrowser::s_SDCardCS;
627sdmmc_card_t * FileBrowser::s_SDCard = nullptr;
628
629
630
631FileBrowser::FileBrowser()
632 : m_dir(nullptr),
633 m_count(0),
634 m_items(nullptr),
635 m_sorted(true),
636 m_includeHiddenFiles(false),
637 m_namesStorage(nullptr)
638{
639}
640
641
642FileBrowser::FileBrowser(char const * path)
643 : FileBrowser()
644{
645 setDirectory(path);
646}
647
648
649FileBrowser::~FileBrowser()
650{
651 clear();
652
653 free(m_dir);
654 m_dir = nullptr;
655}
656
657
658void FileBrowser::clear()
659{
660 free(m_items);
661 m_items = nullptr;
662
663 free(m_namesStorage);
664 m_namesStorage = nullptr;
665
666 m_count = 0;
667}
668
669
670// set absolute directory (full path must be specified)
671bool FileBrowser::setDirectory(const char * path)
672{
673 if (m_dir == nullptr || strcmp(path, m_dir) != 0) {
674 free(m_dir);
675 m_dir = strdup(path);
676 }
677 return reload();
678}
679
680
681// set relative directory:
682// ".." : go to the parent directory
683// "dirname": go inside the specified sub directory
684void FileBrowser::changeDirectory(const char * subdir)
685{
686 if (!m_dir || strlen(subdir) == 0)
687 return;
688 if (strcmp(subdir, "..") == 0) {
689 // go to parent directory
690 auto lastSlash = strrchr(m_dir, '/');
691 if (lastSlash) {
692 if (lastSlash != m_dir)
693 lastSlash[0] = 0;
694 else
695 lastSlash[1] = 0;
696 reload();
697 }
698 } else {
699 // go to sub directory
700 auto oldLen = strcmp(m_dir, "/") == 0 ? 0 : strlen(m_dir);
701 char * newDir = (char*) malloc(oldLen + 1 + strlen(subdir) + 1); // m_dir + '/' + subdir + 0
702 strcpy(newDir, m_dir);
703 newDir[oldLen] = '/';
704 strcpy(newDir + oldLen + 1, subdir);
705 free(m_dir);
706 m_dir = newDir;
707 reload();
708 }
709}
710
711
712int FileBrowser::countDirEntries(int * namesLength)
713{
714 int c = 0;
715 if (strcmp(m_dir, "/") == 0) {
716
717 // root dir
718 if (s_SPIFFSMounted)
719 ++c;
720 if (s_SDCardMounted)
721 ++c;
722
723 } else {
724
725 *namesLength = 0;
726 if (m_dir) {
727 auto dirp = opendir(m_dir);
728 while (dirp) {
729 auto dp = readdir(dirp);
730 if (dp == NULL)
731 break;
732 if (strcmp(".", dp->d_name) && strcmp("..", dp->d_name) && dp->d_type != DT_UNKNOWN) {
733 *namesLength += strlen(dp->d_name) + 1;
734 ++c;
735 }
736 }
737 if (dirp)
738 closedir(dirp);
739 }
740
741 }
742
743 return c;
744}
745
746
747bool FileBrowser::exists(char const * name, bool caseSensitive)
748{
749 if (caseSensitive) {
750 for (int i = 0; i < m_count; ++i)
751 if (strcmp(name, m_items[i].name) == 0)
752 return true;
753 } else {
754 for (int i = 0; i < m_count; ++i)
755 if (strcasecmp(name, m_items[i].name) == 0)
756 return true;
757 }
758 return false;
759}
760
761
762bool FileBrowser::filePathExists(char const * filepath)
763{
764 auto f = openFile(filepath, "rb");
765 if (f)
766 fclose(f);
767 return f != nullptr;
768}
769
770
771size_t FileBrowser::fileSize(char const * name)
772{
773 size_t size = 0;
774 char fullpath[strlen(m_dir) + 1 + strlen(name) + 1];
775 sprintf(fullpath, "%s/%s", m_dir, name);
776 auto fr = fopen(fullpath, "rb");
777 if (fr) {
778 fseek(fr, 0, SEEK_END);
779 size = ftell(fr);
780 fclose(fr);
781 }
782 return size;
783}
784
785
786bool FileBrowser::fileCreationDate(char const * name, int * year, int * month, int * day, int * hour, int * minutes, int * seconds)
787{
788 char fullpath[strlen(m_dir) + 1 + strlen(name) + 1];
789 sprintf(fullpath, "%s/%s", m_dir, name);
790 struct stat s;
791 if (stat(fullpath, &s))
792 return false;
793 auto tm = *localtime((time_t*)&s.st_ctime); // I know, this is not create date, but status change. Anyway I cannot find "st_birthtimespec"
794 *year = 1900 + tm.tm_year;
795 *month = 1 + tm.tm_mon;
796 *day = tm.tm_mday;
797 *hour = tm.tm_hour;
798 *minutes = tm.tm_min;
799 *seconds = imin(tm.tm_sec, 59); // [0, 61] (until C99), [0, 60] (since C99)
800 return true;
801}
802
803
804bool FileBrowser::fileUpdateDate(char const * name, int * year, int * month, int * day, int * hour, int * minutes, int * seconds)
805{
806 char fullpath[strlen(m_dir) + 1 + strlen(name) + 1];
807 sprintf(fullpath, "%s/%s", m_dir, name);
808 struct stat s;
809 if (stat(fullpath, &s))
810 return false;
811 auto tm = *localtime((time_t*)&s.st_mtime);
812 *year = 1900 + tm.tm_year;
813 *month = 1 + tm.tm_mon;
814 *day = tm.tm_mday;
815 *hour = tm.tm_hour;
816 *minutes = tm.tm_min;
817 *seconds = imin(tm.tm_sec, 59); // [0, 61] (until C99), [0, 60] (since C99)
818 return true;
819}
820
821
822bool FileBrowser::fileAccessDate(char const * name, int * year, int * month, int * day, int * hour, int * minutes, int * seconds)
823{
824 char fullpath[strlen(m_dir) + 1 + strlen(name) + 1];
825 sprintf(fullpath, "%s/%s", m_dir, name);
826 struct stat s;
827 if (stat(fullpath, &s))
828 return false;
829 auto tm = *localtime((time_t*)&s.st_atime);
830 *year = 1900 + tm.tm_year;
831 *month = 1 + tm.tm_mon;
832 *day = tm.tm_mday;
833 *hour = tm.tm_hour;
834 *minutes = tm.tm_min;
835 *seconds = imin(tm.tm_sec, 59); // [0, 61] (until C99), [0, 60] (since C99)
836 return true;
837}
838
839
840
841int DirComp(const void * i1, const void * i2)
842{
843 DirItem * d1 = (DirItem*)i1;
844 DirItem * d2 = (DirItem*)i2;
845 if (d1->isDir != d2->isDir) // directories first
846 return d1->isDir ? -1 : +1;
847 else
848 return strcmp(d1->name, d2->name);
849}
850
851
852bool FileBrowser::reload()
853{
854 bool retval = true;
855
856 clear();
857 int namesAlloc;
858 int c = countDirEntries(&namesAlloc);
859 m_items = (DirItem*) malloc(sizeof(DirItem) * (c + 1));
860 m_namesStorage = (char*) malloc(namesAlloc);
861 char * sname = m_namesStorage;
862
863 if (strcmp(m_dir, "/") == 0) {
864
865 // root dir
866 if (s_SPIFFSMounted) {
867 m_items[m_count].name = s_SPIFFSMountPath + 1; // +1 to bypass "/"
868 m_items[m_count].isDir = true;
869 ++m_count;
870 }
871 if (s_SDCardMounted) {
872 m_items[m_count].name = s_SDCardMountPath + 1; // +1 to bypass "/"
873 m_items[m_count].isDir = true;
874 ++m_count;
875 }
876
877 } else {
878
879 // first item is always ".."
880 m_items[0].name = "..";
881 m_items[0].isDir = true;
882 ++m_count;
883
884 int hiddenFilesCount = 0;
885 auto dirp = opendir(m_dir);
886 while (dirp) {
887 auto dp = readdir(dirp);
888 if (dp == NULL)
889 break;
890 if (strcmp(".", dp->d_name) && strcmp("..", dp->d_name) && dp->d_type != DT_UNKNOWN) {
891 DirItem * di = m_items + m_count;
892 // check if this is a simulated directory (like in SPIFFS)
893 auto slashPos = strchr(dp->d_name, '/');
894 if (slashPos) {
895 // yes, this is a simulated dir. Trunc and avoid to insert it twice
896 auto len = slashPos - dp->d_name;
897 strncpy(sname, dp->d_name, len);
898 sname[len] = 0;
899 if (!exists(sname)) {
900 di->name = sname;
901 di->isDir = true;
902 sname += len + 1;
903 ++m_count;
904 }
905 } else {
906 bool isHidden = dp->d_name[0] == '.';
907 if (!isHidden || m_includeHiddenFiles) {
908 strcpy(sname, dp->d_name);
909 di->name = sname;
910 di->isDir = (dp->d_type == DT_DIR);
911 sname += strlen(sname) + 1;
912 ++m_count;
913 }
914 if (isHidden)
915 ++hiddenFilesCount;
916 }
917 }
918 }
919 if (dirp)
920 closedir(dirp);
921 else
922 retval = false;
923
924 // for SPIFFS "opendir" returns always true, even for not existing dirs. An hidden file means there is the SPIFFS directory placeholder
925 if (m_count == 1 && hiddenFilesCount == 0 && getDriveType(m_dir) == DriveType::SPIFFS)
926 retval = false; // no hidden files, so the directory doesn't exist
927
928 }
929
930 if (m_sorted)
931 qsort(m_items, m_count, sizeof(DirItem), DirComp);
932
933 return retval;
934}
935
936
937// note: for SPIFFS this creates an empty ".dirname" file. The SPIFFS path is detected when path starts with "/spiffs" or s_SPIFFSMountPath
938// "dirname" is not a path, just a directory name created inside "m_dir"
939void FileBrowser::makeDirectory(char const * dirname)
940{
941 int dirnameLen = strlen(dirname);
942 if (dirnameLen > 0) {
943 if (getCurrentDriveType() == DriveType::SPIFFS) {
944 // simulated directory, puts an hidden placeholder
945 char fullpath[strlen(m_dir) + 3 + 2 * dirnameLen + 1];
946 auto name = dirname;
947 while (*name) {
948 auto next = name + 1;
949 while (*next && *next != '\\' && *next != '/')
950 ++next;
951 strcpy(fullpath, m_dir);
952 if (dirname != name) {
953 strcat(fullpath, "/");
954 strncat(fullpath, dirname, name - dirname - 1);
955 }
956 strcat(fullpath, "/");
957 strncat(fullpath, name, next - name);
958 strcat(fullpath, "/.");
959 strncat(fullpath, name, next - name);
960 replacePathSep(fullpath, '/');
961 FILE * f = fopen(fullpath, "wb");
962 fclose(f);
963 if (*next == 0)
964 break;
965 name = next + 1;
966 }
967
968 } else {
969 char fullpath[strlen(m_dir) + 1 + dirnameLen + 1];
970 sprintf(fullpath, "%s/%s", m_dir, dirname);
971 replacePathSep(fullpath, '/');
972 mkdir(fullpath, ACCESSPERMS);
973 }
974 }
975}
976
977
978// removes a file or a directory (and all files inside it)
979// The SPIFFS path is detected when path starts with "/spiffs" or s_SPIFFSMountPath
980// "name" is not a path, just a file or directory name inside "m_dir"
981void FileBrowser::remove(char const * name)
982{
983 char fullpath[strlen(m_dir) + 1 + strlen(name) + 1];
984 sprintf(fullpath, "%s/%s", m_dir, name);
985 int r = unlink(fullpath);
986
987 if (r != 0) {
988 // failed, try to remove directory
989 r = rmdir(fullpath);
990 }
991
992 if (r != 0) {
993 // failed, try simulated directory in SPIFFS
994 if (getCurrentDriveType() == DriveType::SPIFFS) {
995 // simulated directory
996 // maybe this is a directory, remove ".dir" file
997 char hidpath[strlen(m_dir) + 3 + 2 * strlen(name) + 1];
998 sprintf(hidpath, "%s/%s/.%s", m_dir, name, name);
999 unlink(hidpath);
1000 // maybe the directory contains files, remove all
1001 auto dirp = opendir(fullpath);
1002 while (dirp) {
1003 auto dp = readdir(dirp);
1004 if (dp == NULL)
1005 break;
1006 if (strcmp(".", dp->d_name) && strcmp("..", dp->d_name) && dp->d_type != DT_UNKNOWN) {
1007 char sfullpath[strlen(fullpath) + 1 + strlen(dp->d_name) + 1];
1008 sprintf(sfullpath, "%s/%s", fullpath, dp->d_name);
1009 unlink(sfullpath);
1010 }
1011 }
1012 closedir(dirp);
1013 }
1014 }
1015}
1016
1017
1018// works only for files
1019void FileBrowser::rename(char const * oldName, char const * newName)
1020{
1021 char oldfullpath[strlen(m_dir) + 1 + strlen(oldName) + 1];
1022 sprintf(oldfullpath, "%s/%s", m_dir, oldName);
1023
1024 char newfullpath[strlen(m_dir) + 1 + strlen(newName) + 1];
1025 sprintf(newfullpath, "%s/%s", m_dir, newName);
1026
1027 ::rename(oldfullpath, newfullpath);
1028}
1029
1030
1031// return a full path
1032char * FileBrowser::createTempFilename()
1033{
1034 constexpr int FLEN = 6;
1035 auto ret = (char*) malloc(strlen(m_dir) + 1 + FLEN + 4 + 1);
1036 while (true) {
1037 char name[FLEN + 1] = { 0 };
1038 for (int i = 0; i < FLEN; ++i)
1039 name[i] = 65 + (rand() % 26);
1040 sprintf(ret, "%s/%s.TMP", m_dir, name);
1041 if (!exists(name, false))
1042 return ret;
1043 }
1044}
1045
1046
1047bool FileBrowser::truncate(char const * name, size_t size)
1048{
1049 constexpr size_t BUFLEN = 512;
1050
1051 char fullpath[strlen(m_dir) + 1 + strlen(name) + 1];
1052 sprintf(fullpath, "%s/%s", m_dir, name);
1053
1054 // in future maybe...
1055 //::truncate(name, size);
1056
1057 bool retval = false;
1058
1059 // for now...
1060 char * tempFilename = createTempFilename();
1061 if (::rename(fullpath, tempFilename) == 0) {
1062 void * buf = malloc(BUFLEN);
1063 if (buf) {
1064 auto fr = fopen(tempFilename, "rb");
1065 if (fr) {
1066 auto fw = fopen(fullpath, "wb");
1067 if (fw) {
1068
1069 while (size > 0) {
1070 auto l = fread(buf, 1, tmin(size, BUFLEN), fr);
1071 if (l == 0)
1072 break;
1073 fwrite(buf, 1, l, fw);
1074 size -= l;
1075 }
1076
1077 // just in case truncate is used to expand the file
1078 for (; size > 0; --size)
1079 fputc(0, fw);
1080
1081 retval = true;
1082 fclose(fw);
1083 }
1084 fclose(fr);
1085 }
1086 }
1087 free(buf);
1088 unlink(tempFilename);
1089 }
1090 free(tempFilename);
1091 return retval;
1092}
1093
1094
1095// concatenates current directory and specified name and store result into fullpath
1096// Specifying outPath=nullptr returns required length
1097int FileBrowser::getFullPath(char const * name, char * outPath, int maxlen)
1098{
1099 return (outPath ? snprintf(outPath, maxlen, "%s/%s", m_dir, name) : snprintf(nullptr, 0, "%s/%s", m_dir, name)) + 1;
1100}
1101
1102
1103FILE * FileBrowser::openFile(char const * filename, char const * mode)
1104{
1105 char fullpath[strlen(m_dir) + 1 + strlen(filename) + 1];
1106 strcpy(fullpath, m_dir);
1107 strcat(fullpath, "/");
1108 strcat(fullpath, filename);
1109
1110 replacePathSep(fullpath, '/');
1111
1112 return fopen(fullpath, mode);
1113}
1114
1115
1116DriveType FileBrowser::getCurrentDriveType()
1117{
1118 return getDriveType(m_dir);
1119}
1120
1121
1122DriveType FileBrowser::getDriveType(char const * path)
1123{
1124 if (strncmp(path, "/spiffs", 7) == 0 || (s_SPIFFSMounted && strncmp(path, s_SPIFFSMountPath, strlen(s_SPIFFSMountPath)) == 0)) {
1125 return DriveType::SPIFFS;
1126 } else if (s_SDCardMounted && strncmp(path, s_SDCardMountPath, strlen(s_SDCardMountPath)) == 0) {
1127 return DriveType::SDCard;
1128 } else {
1129 return DriveType::None;
1130 }
1131}
1132
1133
1134bool FileBrowser::format(DriveType driveType, int drive)
1135{
1136 esp_task_wdt_init(45, false);
1137
1138 if (driveType == DriveType::SDCard && s_SDCardMounted) {
1139
1140 // unmount filesystem
1141 char drv[3] = {(char)('0' + drive), ':', 0};
1142 f_mount(0, drv, 0);
1143
1144 void * buffer = malloc(FF_MAX_SS);
1145 if (!buffer)
1146 return false;
1147
1148 // create partition
1149 DWORD plist[] = { 100, 0, 0, 0 };
1150 if (f_fdisk(drive, plist, buffer) != FR_OK) {
1151 free(buffer);
1152 return false;
1153 }
1154
1155 // make filesystem
1156 if (f_mkfs(drv, FM_ANY, 16 * 1024, buffer, FF_MAX_SS) != FR_OK) {
1157 free(buffer);
1158 return false;
1159 }
1160
1161 free(buffer);
1162
1163 remountSDCard();
1164
1165 return true;
1166
1167 } else if (driveType == DriveType::SPIFFS && s_SPIFFSMounted) {
1168
1169 // driveType == DriveType::SPIFFS
1170 bool r = (esp_spiffs_format(nullptr) == ESP_OK);
1171
1172 remountSPIFFS();
1173
1174 return r;
1175
1176 } else
1177 return false;
1178}
1179
1180
1181bool FileBrowser::mountSDCard(bool formatOnFail, char const * mountPath, size_t maxFiles, int allocationUnitSize, int MISO, int MOSI, int CLK, int CS)
1182{
1183 switch (getChipPackage()) {
1184 case ChipPackage::ESP32PICOD4:
1185 MISO = 2;
1186 MOSI = 12;
1187 break;
1188 case ChipPackage::ESP32D0WDQ5:
1189 MISO = 35;
1190 MOSI = 12;
1191 break;
1192 default:
1193 break;
1194 }
1195
1196 s_SDCardMountPath = mountPath;
1197 s_SDCardMaxFiles = maxFiles;
1198 s_SDCardAllocationUnitSize = allocationUnitSize;
1199 s_SDCardMISO = MISO;
1200 s_SDCardMOSI = MOSI;
1201 s_SDCardCLK = CLK;
1202 s_SDCardCS = CS;
1203 s_SDCardMounted = false;
1204
1205 sdmmc_host_t host = SDSPI_HOST_DEFAULT();
1206 host.slot = HSPI_HOST;
1207
1208 #if FABGL_ESP_IDF_VERSION <= FABGL_ESP_IDF_VERSION_VAL(3, 3, 5)
1209
1210 sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
1211 slot_config.gpio_miso = int2gpio(MISO);
1212 slot_config.gpio_mosi = int2gpio(MOSI);
1213 slot_config.gpio_sck = int2gpio(CLK);
1214 slot_config.gpio_cs = int2gpio(CS);
1215
1216 esp_vfs_fat_sdmmc_mount_config_t mount_config;
1217 mount_config.format_if_mount_failed = formatOnFail;
1218 mount_config.max_files = maxFiles;
1219 mount_config.allocation_unit_size = allocationUnitSize;
1220
1221 s_SDCardMounted = (esp_vfs_fat_sdmmc_mount(mountPath, &host, &slot_config, &mount_config, &s_SDCard) == ESP_OK);
1222
1223 #else
1224
1225 // slow down SD card. Using ESP32 core 2.0.0 may crash SD subsystem, having VGA output and WiFi enabled
1226 // @TODO: check again
1227 host.max_freq_khz = 19000;
1228
1229 spi_bus_config_t bus_cfg = {
1230 .mosi_io_num = int2gpio(MOSI),
1231 .miso_io_num = int2gpio(MISO),
1232 .sclk_io_num = int2gpio(CLK),
1233 .quadwp_io_num = -1,
1234 .quadhd_io_num = -1,
1235 .max_transfer_sz = 4000,
1236 };
1237 auto r = spi_bus_initialize((spi_host_device_t)host.slot, &bus_cfg, 2);
1238
1239 if (r == ESP_OK || r == ESP_ERR_INVALID_STATE) { // ESP_ERR_INVALID_STATE, maybe spi_bus_initialize already called
1240 sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
1241 slot_config.gpio_cs = int2gpio(CS);
1242 slot_config.host_id = (spi_host_device_t) host.slot;
1243
1244 esp_vfs_fat_sdmmc_mount_config_t mount_config;
1245 mount_config.format_if_mount_failed = formatOnFail;
1246 mount_config.max_files = maxFiles;
1247 mount_config.allocation_unit_size = allocationUnitSize;
1248
1249 r = esp_vfs_fat_sdspi_mount(mountPath, &host, &slot_config, &mount_config, &s_SDCard);
1250
1251 s_SDCardMounted = (r == ESP_OK);
1252 }
1253
1254 #endif
1255
1256 return s_SDCardMounted;
1257}
1258
1259
1260void FileBrowser::unmountSDCard()
1261{
1262 if (s_SDCardMounted) {
1263 #if FABGL_ESP_IDF_VERSION <= FABGL_ESP_IDF_VERSION_VAL(3, 3, 5)
1264 esp_vfs_fat_sdmmc_unmount();
1265 #else
1266 esp_vfs_fat_sdcard_unmount(s_SDCardMountPath, s_SDCard);
1267 #endif
1268 s_SDCardMounted = false;
1269 }
1270}
1271
1272
1273bool FileBrowser::remountSDCard()
1274{
1275 unmountSDCard();
1276 return mountSDCard(false, s_SDCardMountPath, s_SDCardMaxFiles, s_SDCardAllocationUnitSize, s_SDCardMISO, s_SDCardMOSI, s_SDCardCLK, s_SDCardCS);
1277}
1278
1279
1280bool FileBrowser::mountSPIFFS(bool formatOnFail, char const * mountPath, size_t maxFiles)
1281{
1282 s_SPIFFSMountPath = mountPath;
1283 s_SPIFFSMaxFiles = maxFiles;
1284 esp_vfs_spiffs_conf_t conf = {
1285 .base_path = mountPath,
1286 .partition_label = nullptr,
1287 .max_files = maxFiles,
1288 .format_if_mount_failed = true
1289 };
1290 s_SPIFFSMounted = (esp_vfs_spiffs_register(&conf) == ESP_OK);
1291 return s_SPIFFSMounted;
1292}
1293
1294
1295void FileBrowser::unmountSPIFFS()
1296{
1297 if (s_SPIFFSMounted) {
1298 esp_vfs_spiffs_unregister(nullptr);
1299 s_SPIFFSMounted = false;
1300 }
1301}
1302
1303
1304bool FileBrowser::remountSPIFFS()
1305{
1306 unmountSPIFFS();
1307 return mountSPIFFS(false, s_SPIFFSMountPath, s_SPIFFSMaxFiles);
1308}
1309
1310
1311bool FileBrowser::getFSInfo(DriveType driveType, int drive, int64_t * total, int64_t * used)
1312{
1313 *total = *used = 0;
1314
1315 if (driveType == DriveType::SDCard) {
1316
1317 FATFS * fs;
1318 DWORD free_clusters;
1319 char drv[3] = {(char)('0' + drive), ':', 0};
1320 if (f_getfree(drv, &free_clusters, &fs) != FR_OK)
1321 return false;
1322 int64_t total_sectors = (fs->n_fatent - 2) * fs->csize;
1323 int64_t free_sectors = free_clusters * fs->csize;
1324 *total = total_sectors * fs->ssize;
1325 *used = *total - free_sectors * fs->ssize;
1326
1327 return true;
1328
1329 } else if (driveType == DriveType::SPIFFS) {
1330
1331 size_t stotal = 0, sused = 0;
1332 if (esp_spiffs_info(NULL, &stotal, &sused) != ESP_OK)
1333 return false;
1334 *total = stotal;
1335 *used = sused;
1336 return true;
1337
1338 } else
1339 return false;
1340}
1341
1342
1343// FileBrowser
1345
1346
1347
1348
1350// LightMemoryPool
1351
1352
1353void LightMemoryPool::mark(int pos, int16_t size, bool allocated)
1354{
1355 m_mem[pos] = size & 0xff;
1356 m_mem[pos + 1] = ((size >> 8) & 0x7f) | (allocated ? 0x80 : 0);
1357}
1358
1359
1360int16_t LightMemoryPool::getSize(int pos)
1361{
1362 return m_mem[pos] | ((m_mem[pos + 1] & 0x7f) << 8);
1363}
1364
1365
1366bool LightMemoryPool::isFree(int pos)
1367{
1368 return (m_mem[pos + 1] & 0x80) == 0;
1369}
1370
1371
1372LightMemoryPool::LightMemoryPool(int poolSize)
1373{
1374 m_poolSize = poolSize + 2;
1375 m_mem = (uint8_t*) heap_caps_malloc(m_poolSize, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
1376 mark(0, m_poolSize - 2, false);
1377}
1378
1379
1380LightMemoryPool::~LightMemoryPool()
1381{
1382 heap_caps_free(m_mem);
1383}
1384
1385
1386void * LightMemoryPool::alloc(int size)
1387{
1388 for (int pos = 0; pos < m_poolSize; ) {
1389 int16_t blockSize = getSize(pos);
1390 if (isFree(pos)) {
1391 if (blockSize == size) {
1392 // found a block having the same size
1393 mark(pos, size, true);
1394 return m_mem + pos + 2;
1395 } else if (blockSize > size) {
1396 // found a block having larger size
1397 int remainingSize = blockSize - size - 2;
1398 if (remainingSize > 0)
1399 mark(pos + 2 + size, remainingSize, false); // create new free block at the end of this block
1400 else
1401 size = blockSize; // to avoid to waste last block
1402 mark(pos, size, true); // reduce size of this block and mark as allocated
1403 return m_mem + pos + 2;
1404 } else {
1405 // this block hasn't enough space
1406 // can merge with next block?
1407 int nextBlockPos = pos + 2 + blockSize;
1408 if (nextBlockPos < m_poolSize && isFree(nextBlockPos)) {
1409 // join blocks and stay at this pos
1410 mark(pos, blockSize + getSize(nextBlockPos) + 2, false);
1411 } else {
1412 // move to the next block
1413 pos += blockSize + 2;
1414 }
1415 }
1416 } else {
1417 // move to the next block
1418 pos += blockSize + 2;
1419 }
1420 }
1421 return nullptr;
1422}
1423
1424
1425bool LightMemoryPool::memCheck()
1426{
1427 int pos = 0;
1428 while (pos < m_poolSize) {
1429 int16_t blockSize = getSize(pos);
1430 pos += blockSize + 2;
1431 }
1432 return pos == m_poolSize;
1433}
1434
1435
1436int LightMemoryPool::totFree()
1437{
1438 int r = 0;
1439 for (int pos = 0; pos < m_poolSize; ) {
1440 int16_t blockSize = getSize(pos);
1441 if (isFree(pos))
1442 r += blockSize;
1443 pos += blockSize + 2;
1444 }
1445 return r;
1446}
1447
1448
1449int LightMemoryPool::totAllocated()
1450{
1451 int r = 0;
1452 for (int pos = 0; pos < m_poolSize; ) {
1453 int16_t blockSize = getSize(pos);
1454 if (!isFree(pos))
1455 r += blockSize;
1456 pos += blockSize + 2;
1457 }
1458 return r;
1459}
1460
1461
1462int LightMemoryPool::largestFree()
1463{
1464 int r = 0;
1465 for (int pos = 0; pos < m_poolSize; ) {
1466 int16_t blockSize = getSize(pos);
1467 if (isFree(pos) && blockSize > r)
1468 r = blockSize;
1469 pos += blockSize + 2;
1470 }
1471 return r;
1472}
1473
1474
1475// LightMemoryPool
1477
1478
1479
1481// CoreUsage
1482
1483
1484int CoreUsage::s_busiestCore = FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE;
1485
1486
1487// CoreUsage
1489
1490
1491
1493// CurrentVideoMode
1494
1495
1496VideoMode CurrentVideoMode::s_videoMode = VideoMode::None;
1497
1498
1499// CurrentVideoMode
1501
1502
1503
1505// APLLCalcParams
1506
1507
1508// Must be:
1509// maxDen > 1
1510// value >= 0
1511#if FABGLIB_USE_APLL_AB_COEF
1512void floatToFraction(double value, int maxDen, int * num, int * den)
1513{
1514 int64_t a, h[3] = { 0, 1, 0 }, k[3] = { 1, 0, 0 };
1515 int64_t x, d, n = 1;
1516 while (value != floor(value)) {
1517 n <<= 1;
1518 value *= 2;
1519 }
1520 d = value;
1521 for (int i = 0; i < 64; ++i) {
1522 a = n ? d / n : 0;
1523 if (i && !a)
1524 break;
1525 x = d;
1526 d = n;
1527 n = x % n;
1528 x = a;
1529 if (k[1] * a + k[0] >= maxDen) {
1530 x = (maxDen - k[0]) / k[1];
1531 if (x * 2 >= a || k[1] >= maxDen)
1532 i = 65;
1533 else
1534 break;
1535 }
1536 h[2] = x * h[1] + h[0];
1537 h[0] = h[1];
1538 h[1] = h[2];
1539 k[2] = x * k[1] + k[0];
1540 k[0] = k[1];
1541 k[1] = k[2];
1542 }
1543 *den = k[1];
1544 *num = h[1];
1545}
1546#endif
1547
1548
1549// definitions:
1550// apll_clk = XTAL * (4 + sdm2 + sdm1 / 256 + sdm0 / 65536) / (2 * o_div + 4)
1551// dividend = XTAL * (4 + sdm2 + sdm1 / 256 + sdm0 / 65536)
1552// divisor = (2 * o_div + 4)
1553// freq = apll_clk / (2 + b / a) => assumes tx_bck_div_num = 1 and clkm_div_num = 2
1554// Other values range:
1555// sdm0 0..255
1556// sdm1 0..255
1557// sdm2 0..63
1558// o_div 0..31
1559// Assume xtal = FABGLIB_XTAL (40MHz)
1560// The dividend should be in the range of 350 - 500 MHz (350000000-500000000), so these are the
1561// actual parameters ranges (so the minimum apll_clk is 5303030 Hz and maximum is 125000000Hz):
1562// MIN 87500000Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 0
1563// MAX 125000000Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 0
1564//
1565// MIN 58333333Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 1
1566// MAX 83333333Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 1
1567//
1568// MIN 43750000Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 2
1569// MAX 62500000Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 2
1570//
1571// MIN 35000000Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 3
1572// MAX 50000000Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 3
1573//
1574// MIN 29166666Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 4
1575// MAX 41666666Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 4
1576//
1577// MIN 25000000Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 5
1578// MAX 35714285Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 5
1579//
1580// MIN 21875000Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 6
1581// MAX 31250000Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 6
1582//
1583// MIN 19444444Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 7
1584// MAX 27777777Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 7
1585//
1586// MIN 17500000Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 8
1587// MAX 25000000Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 8
1588//
1589// MIN 15909090Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 9
1590// MAX 22727272Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 9
1591//
1592// MIN 14583333Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 10
1593// MAX 20833333Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 10
1594//
1595// MIN 13461538Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 11
1596// MAX 19230769Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 11
1597//
1598// MIN 12500000Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 12
1599// MAX 17857142Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 12
1600//
1601// MIN 11666666Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 13
1602// MAX 16666666Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 13
1603//
1604// MIN 10937500Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 14
1605// MAX 15625000Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 14
1606//
1607// MIN 10294117Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 15
1608// MAX 14705882Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 15
1609//
1610// MIN 9722222Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 16
1611// MAX 13888888Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 16
1612//
1613// MIN 9210526Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 17
1614// MAX 13157894Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 17
1615//
1616// MIN 8750000Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 18
1617// MAX 12500000Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 18
1618//
1619// MIN 8333333Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 19
1620// MAX 11904761Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 19
1621//
1622// MIN 7954545Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 20
1623// MAX 11363636Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 20
1624//
1625// MIN 7608695Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 21
1626// MAX 10869565Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 21
1627//
1628// MIN 7291666Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 22
1629// MAX 10416666Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 22
1630//
1631// MIN 7000000Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 23
1632// MAX 10000000Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 23
1633//
1634// MIN 6730769Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 24
1635// MAX 9615384Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 24
1636//
1637// MIN 6481481Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 25
1638// MAX 9259259Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 25
1639//
1640// MIN 6250000Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 26
1641// MAX 8928571Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 26
1642//
1643// MIN 6034482Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 27
1644// MAX 8620689Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 27
1645//
1646// MIN 5833333Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 28
1647// MAX 8333333Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 28
1648//
1649// MIN 5645161Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 29
1650// MAX 8064516Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 29
1651//
1652// MIN 5468750Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 30
1653// MAX 7812500Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 30
1654//
1655// MIN 5303030Hz - sdm0 = 0 sdm1 = 192 sdm2 = 4 o_div = 31
1656// MAX 7575757Hz - sdm0 = 0 sdm1 = 128 sdm2 = 8 o_div = 31
1657void APLLCalcParams(double freq, APLLParams * params, uint8_t * a, uint8_t * b, double * out_freq, double * error)
1658{
1659 double FXTAL = FABGLIB_XTAL;
1660
1661 *error = 999999999;
1662
1663 double apll_freq = freq * 2;
1664
1665 for (int o_div = 0; o_div <= 31; ++o_div) {
1666
1667 int idivisor = (2 * o_div + 4);
1668
1669 for (int sdm2 = 4; sdm2 <= 8; ++sdm2) {
1670
1671 // from tables above
1672 int minSDM1 = (sdm2 == 4 ? 192 : 0);
1673 int maxSDM1 = (sdm2 == 8 ? 128 : 255);
1674 // apll_freq = XTAL * (4 + sdm2 + sdm1 / 256) / divisor -> sdm1 = (apll_freq * divisor - XTAL * 4 - XTAL * sdm2) * 256 / XTAL
1675 int startSDM1 = ((apll_freq * idivisor - FXTAL * 4.0 - FXTAL * sdm2) * 256.0 / FXTAL);
1676#if FABGLIB_USE_APLL_AB_COEF
1677 for (int isdm1 = tmax(minSDM1, startSDM1); isdm1 <= maxSDM1; ++isdm1) {
1678#else
1679 int isdm1 = startSDM1; {
1680#endif
1681
1682 int sdm1 = isdm1;
1683 sdm1 = tmax(minSDM1, sdm1);
1684 sdm1 = tmin(maxSDM1, sdm1);
1685
1686 // apll_freq = XTAL * (4 + sdm2 + sdm1 / 256 + sdm0 / 65536) / divisor -> sdm0 = (apll_freq * divisor - XTAL * 4 - XTAL * sdm2 - XTAL * sdm1 / 256) * 65536 / XTAL
1687 int sdm0 = ((apll_freq * idivisor - FXTAL * 4.0 - FXTAL * sdm2 - FXTAL * sdm1 / 256.0) * 65536.0 / FXTAL);
1688 // from tables above
1689 sdm0 = (sdm2 == 8 && sdm1 == 128 ? 0 : tmin(255, sdm0));
1690 sdm0 = tmax(0, sdm0);
1691
1692 // dividend inside 350-500Mhz?
1693 double dividend = FXTAL * (4.0 + sdm2 + sdm1 / 256.0 + sdm0 / 65536.0);
1694 if (dividend >= 350000000 && dividend <= 500000000) {
1695 // adjust output frequency using "b/a"
1696 double oapll_freq = dividend / idivisor;
1697
1698 // Calculates "b/a", assuming tx_bck_div_num = 1 and clkm_div_num = 2:
1699 // freq = apll_clk / (2 + clkm_div_b / clkm_div_a)
1700 // abr = clkm_div_b / clkm_div_a
1701 // freq = apll_clk / (2 + abr) => abr = apll_clk / freq - 2
1702 uint8_t oa = 1, ob = 0;
1703#if FABGLIB_USE_APLL_AB_COEF
1704 double abr = oapll_freq / freq - 2.0;
1705 if (abr > 0 && abr < 1) {
1706 int num, den;
1707 floatToFraction(abr, 63, &num, &den);
1708 ob = tclamp(num, 0, 63);
1709 oa = tclamp(den, 0, 63);
1710 }
1711#endif
1712
1713 // is this the best?
1714 double ofreq = oapll_freq / (2.0 + (double)ob / oa);
1715 double err = freq - ofreq;
1716 if (abs(err) < abs(*error)) {
1717 *params = (APLLParams){(uint8_t)sdm0, (uint8_t)sdm1, (uint8_t)sdm2, (uint8_t)o_div};
1718 *a = oa;
1719 *b = ob;
1720 *out_freq = ofreq;
1721 *error = err;
1722 if (err == 0.0)
1723 return;
1724 }
1725 }
1726 }
1727
1728 }
1729 }
1730}
1731
1732
1733// derived from: https://linuxos.sk/blog/mirecove-dristy/detail/esp32-dynamicka-zmena-vzorkovacej-frekvencie-/
1734// A : 1..63
1735// B : 0..63
1736// N : 2..254
1737// M : 1..63
1738// ret: actual sample rate
1739int calcI2STimingParams(int sampleRate, int * outA, int * outB, int * outN, int * outM)
1740{
1741 *outM = 1;
1742
1743 double N = (double)APB_CLK_FREQ / sampleRate;
1744 while (N >= 255) {
1745 ++(*outM);
1746 N = (double)APB_CLK_FREQ / sampleRate / *outM;
1747 }
1748
1749 double min_error = 1.0;
1750 *outN = N;
1751 *outB = 0;
1752 *outA = 1;
1753
1754 for (int a = 1; a < 64; ++a) {
1755 int b = (N - (double)(*outN)) * (double)a;
1756 if (b > 63)
1757 continue;
1758
1759 double divisor = (double)(*outN) + (double)b / (double)a;
1760 double error = divisor > N ? divisor - N : N - divisor;
1761 if (error < min_error) {
1762 min_error = error;
1763 *outA = a;
1764 *outB = b;
1765 }
1766
1767 ++b;
1768 if (b > 63)
1769 continue;
1770 divisor = (double)(*outN) + (double)b / (double)a;
1771 error = divisor > N ? divisor - N : N - divisor;
1772 if (error < min_error) {
1773 min_error = error;
1774 *outA = a;
1775 *outB = b;
1776 }
1777 }
1778
1779 return APB_CLK_FREQ / ((double)(*outN) + (double)(*outB) / (*outA)) / *outM;
1780}
1781
1782
1783
1784
1785}
1786
uint8_t B
uint8_t G
uint8_t R
#define FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE
Definition: fabglconf.h:142
#define FABGLIB_XTAL
Definition: fabglconf.h:45
int16_t X1
Definition: fabutils.h:0
int16_t Y2
Definition: fabutils.h:3
int16_t X2
Definition: fabutils.h:2
int16_t Y1
Definition: fabutils.h:1
This file contains some utility classes and functions.
VideoMode
Specifies a video mode.
Definition: fabutils.h:1068
ChipPackage
This enum defines ESP32 module types (packages)
Definition: fabutils.h:957
DriveType
This enum defines drive types (SPIFFS or SD Card)
Definition: fabutils.h:542
This file contains fabgl::PS2Controller definition.
char const * name
Definition: fabutils.h:535
FileBrowser item specificator.
Definition: fabutils.h:533
This file contains fabgl::VGA16Controller definition.
This file contains fabgl::VGA2Controller definition.
This file contains fabgl::VGAController definition.