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