Compare commits

...

418 Commits

Author SHA1 Message Date
philippe44
752cfbf3b2 Update .gitattributes 2025-03-30 14:19:09 +02:00
philippe44
cecb7fd876 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2025-02-17 23:26:07 +01:00
philippe44
e92e431b45 add MCK in get_dac_config 2025-02-17 23:26:01 +01:00
philippe44
db792e47bd Update Platform_build.yml 2025-02-17 22:47:05 +01:00
philippe44
a22f75a13a limit display checks 2025-02-17 22:39:48 +01:00
philippe44
1f220895e6 Update README.md 2025-02-17 12:55:44 +01:00
philippe44
769ff99f7d Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-09-28 23:17:23 +02:00
philippe44
424fb93ec4 add 2nd encoder for volume only 2024-09-28 23:17:09 +02:00
philippe44
e270963dbd Update README.md 2024-09-28 23:11:52 +02:00
philippe44
2cae41d29c Update README.md 2024-09-28 23:11:10 +02:00
philippe44
84b95cd79c Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-09-28 18:55:48 +02:00
philippe44
6369f4bd69 another misplaced NVS #ifdef 2024-09-28 18:55:43 +02:00
philippe44
4c1bca3166 Update CHANGELOG 2024-09-28 14:43:23 +02:00
philippe44
3a5163e6f6 Update esp_app_main.c
autoexec default was created at the wrong place!
2024-09-28 14:42:08 +02:00
philippe44
cbe42b56bc Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-09-27 18:53:46 +02:00
philippe44
ab9812cb75 make i2s emergency stop platform independant 2024-09-27 18:52:26 +02:00
philippe44
084caedd7e Update README.md 2024-09-27 16:35:15 +02:00
philippe44
f254bf49af Update README.md 2024-09-27 15:35:59 +02:00
philippe44
66bd26f007 Update README.md 2024-09-27 15:35:03 +02:00
philippe44
dd6c932c39 Update README.md 2024-09-27 15:06:38 +02:00
philippe44
50070378ad Merge pull request #440 from digidocs/loudness_plugin_fix1
SqueezeESP32 plugin loudness control fix
2024-09-12 12:33:31 +02:00
github-actions
b50bc8f376 Update prebuilt objects [skip actions] 2024-09-12 09:32:21 +00:00
philippe44
e6723dfa2f Update CHANGELOG 2024-09-12 11:27:55 +02:00
philippe44
ffaff5ac27 Merge pull request #430 from StefanKrupop/aw9523_expander
Add support for AW9523 GPIO expander
2024-09-12 11:25:31 +02:00
philippe44
33ef4b01e7 Update CHANGELOG 2024-09-12 11:24:23 +02:00
github-actions
302865b167 Update prebuilt objects [skip actions] 2024-09-11 18:05:43 +00:00
philippe44
7f0ae69e81 Merge pull request #439 from digidocs/eq_update_fix2
Fix for ESP32 equalizer settings don't update when expected
2024-09-11 20:02:25 +02:00
David Carr
e21e2cf7f9 Equalizer: change gain to int8 and memcmp-based update check 2024-09-04 15:07:42 -05:00
David Carr
57cd009e4c Revert "Equalizer: check if requested gain has changed before updating"
This reverts commit 78e8d60021.
2024-09-04 14:52:27 -05:00
David Carr
fdd8b0a4c9 Change to //= operator 2024-09-04 14:48:07 -05:00
David Carr
78e8d60021 Equalizer: check if requested gain has changed before updating 2024-09-04 13:49:51 -05:00
digidocs
4068e07a45 Fix for I2S noise burst when ESP32 panic occurs (#437) 2024-09-02 08:51:40 -04:00
David Carr
f8d7ac23e1 Updated SqueezeESP32 plugin and zip file 2024-08-21 12:07:57 -05:00
David Carr
befc81f573 Fix for wrong loudness value being sent when user requests loudness 0 2024-08-21 11:01:18 -05:00
David Carr
a633524936 Fix for ESP32 equalizer settings don't update when expected 2024-08-21 00:08:06 -05:00
Stefan Krupop
9d71b8ee26 Added "aw9523" to list of possible expanders 2024-07-26 21:01:10 +02:00
Stefan Krupop
672aca8258 Fixed resetting interrupt 2024-07-26 20:18:55 +02:00
Stefan Krupop
a2351ba0d5 Add support for AW9523 port expander 2024-07-25 01:13:14 +02:00
wizmo2
40a698e2f1 add checks for grfX handlers to prevent divide-by-zero with slimproto (#424)
Thank you!
2024-06-07 18:19:33 -04:00
github-actions
38d28ae8c4 Update prebuilt objects [skip actions] 2024-05-07 21:41:36 +00:00
Sebastien L
21407e8c1c Fix crash from led_vu when no display - release 2024-05-07 17:39:02 -04:00
github-actions
12aa555ff3 Update prebuilt objects [skip actions] 2024-03-22 03:03:10 +00:00
philippe44
cb47ec855b Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-03-20 22:36:28 -07:00
philippe44
787a5d9a6e spdif glitch at track transition 2024-03-20 22:36:22 -07:00
github-actions
7b9deb795c Update prebuilt objects [skip actions] 2024-01-28 06:33:54 +00:00
philippe44
4b1f8a8d4b see CHANGELOG - release 2024-01-27 22:32:01 -08:00
philippe44
4f8661100b Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-19 16:14:12 -08:00
philippe44
6b2eb1b3c0 see CHANGELOG 2024-01-19 16:14:08 -08:00
github-actions
f3593fa2f4 Update prebuilt objects [skip actions] 2024-01-17 02:53:45 +00:00
philippe44
ccc7b86369 See CHANGELOG - release 2024-01-16 18:51:33 -08:00
philippe44
9e3c6dcf30 Update README.md 2024-01-10 21:53:03 -08:00
github-actions
7be81887a6 Update prebuilt objects [skip actions] 2024-01-11 05:41:18 +00:00
philippe44
29f71fc677 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-10 21:36:53 -08:00
philippe44
a14a6edc1b reset flac packet flag - release 2024-01-10 21:36:50 -08:00
github-actions
35bf0a3c10 Update prebuilt objects [skip actions] 2024-01-11 03:00:35 +00:00
philippe44
55a8658f3a OggFlac & displayer fixes (see CHANGELOG) - release 2024-01-10 18:58:47 -08:00
github-actions
b0d9e1668c Update prebuilt objects [skip actions] 2024-01-04 08:48:28 +00:00
philippe44
922367baf5 no false alarm on OggS miss + homogeneous use of u32/size_t/int - release 2024-01-04 00:46:36 -08:00
philippe44
01320db007 always copy granule unless it's -1 (not valid) 2024-01-03 23:47:00 -08:00
philippe44
f677695fc7 handle pages with no terminated packet 2024-01-03 23:25:08 -08:00
github-actions
7902af2bf0 Update prebuilt objects [skip actions] 2024-01-03 04:08:04 +00:00
philippe44
5faecb08f6 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-02 18:15:43 -08:00
philippe44
8e3d91020f bring ogg parsing context purely inside stream.c - release 2024-01-02 18:15:39 -08:00
github-actions
4f54a47c83 Update prebuilt objects [skip actions] 2024-01-02 08:50:27 +00:00
philippe44
58840f894f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-02 00:44:20 -08:00
philippe44
94109ebf38 fix copy typo - release 2024-01-02 00:43:32 -08:00
github-actions
fc20618fa2 Update prebuilt objects [skip actions] 2024-01-02 08:37:41 +00:00
philippe44
70720d3445 don't allocate segments - release 2024-01-02 00:35:47 -08:00
github-actions
4abe1304e8 Update prebuilt objects [skip actions] 2024-01-01 02:54:46 +00:00
philippe44
be7fb4b669 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-12-31 18:50:29 -08:00
philippe44
3554af1460 add vorbis/ogg live metadata (and fix some ogg issues) - release 2023-12-31 18:50:25 -08:00
Sébastien
02d422c1f4 update workflow dependency 2023-12-28 09:11:46 -05:00
github-actions
343b728323 Update prebuilt objects [skip actions] 2023-12-28 13:56:07 +00:00
philippe44
9679c5c104 fix flush mode 2023-12-27 19:46:16 -08:00
philippe44
5c90086bbd sync with upstream/cspot 2023-11-21 12:52:55 -08:00
philippe44
807a0e547a airplay improvements 2023-11-19 22:57:21 -08:00
philippe44
1d54331224 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-11-16 22:52:23 -08:00
philippe44
e85a3cddf1 GDS optimization 2023-11-16 22:52:18 -08:00
github-actions
d4d97d5a60 Update prebuilt objects [skip actions] 2023-11-17 05:45:59 +00:00
philippe44
2afbee7cb1 add SH1122 - release 2023-11-16 21:43:56 -08:00
philippe44
bbf9a3af70 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-11-13 22:30:51 -08:00
philippe44
a9ca4c4450 attempt to fix cspot track issues - release 2023-11-13 22:30:45 -08:00
github-actions
55bce084eb Update prebuilt objects [skip actions] 2023-11-09 21:49:08 +00:00
philippe44
d31697e7ef force pad_select for gpio use in dac_controlset - release 2023-11-09 13:47:08 -08:00
philippe44
e830b4db73 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-11-09 13:42:11 -08:00
philippe44
8fe21327b8 Update README.md 2023-11-08 16:54:54 -08:00
philippe44
3f487366ee Update README.md 2023-11-08 16:51:47 -08:00
philippe44
b875585aec Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-11-08 16:37:53 -08:00
philippe44
b46fccfc83 execute dac_controlset even w/o i2c (for gpio) - release 2023-11-08 16:37:49 -08:00
philippe44
655c17fb29 Update README.md 2023-11-08 11:36:29 -08:00
philippe44
b8bda0435a Update README.md 2023-11-08 11:34:17 -08:00
github-actions
dc5cb31efb Update prebuilt objects [skip actions] 2023-11-08 06:18:54 +00:00
philippe44
44ccbb49a6 ledvu update 2023-11-07 22:15:54 -08:00
philippe44
6589387cd3 Merge pull request #351 from wizmo2/ledvu-update-clean
Ledvu update (clean)
2023-11-07 22:08:42 -08:00
github-actions
0d3f6a8870 Update prebuilt objects [skip actions] 2023-11-06 01:24:27 +00:00
philippe44
bb6ec4a629 chmod... 2023-11-05 17:12:55 -08:00
philippe44
a029304776 chmod... 2023-11-05 15:52:02 -08:00
Wizmo2
dad8efff8b Merge branch 'ledvu-update-clean' of https://github.com/wizmo2/squeezelite-esp32 into ledvu-update-clean 2023-11-05 14:30:23 -05:00
Wizmo2
e8c6169e09 formating changes 2023-11-05 14:27:45 -05:00
Wizmo2
fc8d15f58d formating changes 2023-11-04 23:07:25 -04:00
Wizmo2
f9d7e15d4b add suspend service 2023-11-04 22:56:37 -04:00
github-actions
67722bef94 Update prebuilt objects [skip actions] 2023-11-04 02:25:17 +00:00
github-actions
8ea777ddff Update prebuilt objects [skip actions] 2023-11-04 01:11:45 +00:00
philippe44
ad8d587e94 no reboot on external active decoder - release 2023-11-03 18:09:39 -07:00
Wizmo2
667b90fafc ledvu-update-clean 2023-10-30 19:24:23 -04:00
philippe44
7be653f722 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-10-30 01:06:41 -07:00
philippe44
f6269a2f7a gap when changing queue workaround 2023-10-30 01:06:37 -07:00
philippe44
4b70514fa3 Update CHANGELOG 2023-10-29 00:08:20 -07:00
philippe44
00a0b8c36e more warnings 2023-10-28 20:54:37 -07:00
philippe44
9f4474a19c remove warnings 2023-10-28 20:51:57 -07:00
github-actions
58fac99e95 Update prebuilt objects [skip actions] 2023-10-29 03:19:48 +00:00
Sebastien L
cb7c8fd6be Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2023-10-28 23:13:38 -04:00
Sebastien L
b9a1bf0432 remove bootswatch 2023-10-28 23:13:20 -04:00
github-actions
63658efefe Update prebuilt objects [skip actions] 2023-10-29 02:53:20 +00:00
Sebastien L
c76bbc3524 Fix recovery 2023-10-28 22:48:12 -04:00
philippe44
9619b1d792 Update CHANGELOG 2023-10-28 00:13:39 -07:00
philippe44
0bbd5a064f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-10-27 18:27:24 -07:00
philippe44
986521fd4a fix vorbis & opus memory leaks 2023-10-27 18:26:57 -07:00
github-actions
406a56a3a3 Update prebuilt objects [skip actions] 2023-10-26 05:59:15 +00:00
philippe44
c9455f70ff Update CHANGELOG 2023-10-25 22:47:14 -07:00
philippe44
b5b76480e4 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-10-25 22:45:37 -07:00
philippe44
e6744deab8 fix vorbis not closing properly - release 2023-10-25 22:45:26 -07:00
github-actions
94baf86989 Update prebuilt objects [skip actions] 2023-10-25 01:48:05 +00:00
philippe44
0c856a37c1 well, fix 16 bits as well... 2023-10-24 16:27:27 -07:00
philippe44
d5f28375ce fix SPDIF miscount 2023-10-24 13:39:56 -07:00
philippe44
7ea5a93647 backport webUI changes as well (-disable) 2023-10-23 17:15:02 -07:00
philippe44
ca38a14420 cspot fixes backport 2023-10-23 17:09:50 -07:00
philippe44
9ebe717e74 add delay and gpio on dac_controlset 2023-10-10 13:51:47 -07:00
philippe44
12347bdb29 Update README.md 2023-10-09 14:38:06 -07:00
philippe44
a42c9ff860 Update README.md 2023-10-09 14:35:54 -07:00
philippe44
e67413697c Update README.md 2023-10-09 14:35:01 -07:00
philippe44
c4b797e54f update defaults 2023-10-09 13:45:46 -07:00
philippe44
9971fb0ff3 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-10-08 17:15:20 -07:00
philippe44
8280bc4903 catchup with cspot "official" - release 2023-10-08 17:15:14 -07:00
github-actions
adc6a86725 Update prebuilt objects [skip actions] 2023-10-07 06:40:24 +00:00
philippe44
61f58f9a52 cspot fixes - release 2023-10-06 23:38:07 -07:00
github-actions
e3650413f5 Update prebuilt objects [skip actions] 2023-10-06 18:24:24 +00:00
Sebastien L
750ffbf464 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-10-06 14:19:52 -04:00
Sebastien L
65f52a23bc fix manual workflow triggering [skip actions] 2023-10-06 14:19:47 -04:00
Sébastien
da411bf1c8 Update CHANGELOG [skip actions] 2023-10-06 14:07:28 -04:00
Sebastien L
0a319269c2 resolve bootswatch contrast issue [skip actions]
This will solve https://github.com/sle118/squeezelite-esp32/issues/319
2023-10-06 14:05:49 -04:00
Sebastien L
b0ce38bf14 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-10-06 11:50:02 -04:00
Sebastien L
767b677947 Update build system [skip actions] 2023-10-06 11:49:58 -04:00
github-actions
724818390b Update prebuilt objects [skip actions] 2023-10-04 22:54:15 +00:00
philippe44
105e800cc1 fix podcast on Spotify - release 2023-10-04 15:51:55 -07:00
github-actions
ab09f009f7 Update prebuilt objects [skip actions] 2023-10-03 03:09:28 +00:00
Sébastien
9c179adf85 Update CHANGELOG release 2023-10-02 23:07:00 -04:00
philippe44
719b289659 update cspot 2023-10-02 19:06:59 -07:00
Sébastien
eb7df4a5e9 attempt at fixing build parameter logic. [skip actions] 2023-10-02 07:50:39 -04:00
Sebastien L
9a54239323 UI build should be manually triggered 2023-10-01 21:37:25 -04:00
Sébastien
b790156be0 Update CHANGELOG 2023-10-01 16:48:27 -04:00
Sébastien
f87b3adec2 Update CHANGELOG-release 2023-10-01 16:38:05 -04:00
Sebastien L
35d42f2096 roll back webapp 2023-10-01 16:23:01 -04:00
Sébastien
d2d0cadeed Make UI build optional 2023-09-30 23:33:04 -04:00
github-actions
cd76619e96 Update prebuilt objects [skip actions] 2023-09-30 01:41:41 +00:00
philippe44
781699362f add CHANGELOG (at last) - release 2023-09-29 18:37:47 -07:00
philippe44
c4dbf60cb6 Update README.md 2023-09-28 17:23:06 -07:00
philippe44
3dd7b4b02c Update README.md 2023-09-28 17:19:35 -07:00
philippe44
83c1a2b8e0 Update README.md 2023-09-28 17:18:59 -07:00
philippe44
e8021c38f0 Update README.md 2023-09-28 17:17:58 -07:00
philippe44
71bb57f1eb Update README.md 2023-09-28 17:16:40 -07:00
philippe44
7eb4b218e3 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-28 17:11:05 -07:00
philippe44
953d1657f9 add soft on/off schematics 2023-09-28 17:09:56 -07:00
philippe44
b21a143196 Delete 269289674-4d8eb4f6-0d88-4dab-858f-c3de3ec33139.png 2023-09-28 16:54:50 -07:00
philippe44
898f1d92ed Add files via upload 2023-09-28 16:53:13 -07:00
philippe44
f6fd11783c Merge pull request #313 from wizmo2/rmt-esp32s3
Support for ESP32S3 RMT channel restrictions
2023-09-28 10:33:11 -07:00
Wizmo2
f33cb569ce rmt helper to function 2023-09-28 07:34:58 -04:00
philippe44
f5d6f26c01 Update README.md 2023-09-27 19:42:02 -07:00
philippe44
8df6b853e6 Merge pull request #312 from wizmo2/st7789-mod
Allow offset on ST7789
2023-09-27 19:41:21 -07:00
philippe44
f7c0107d40 Merge pull request #314 from wizmo2/led_vu_status
Add battery status to led_vu
2023-09-27 19:40:43 -07:00
philippe44
5068309d25 manage Spotify credentials 2023-09-27 19:36:38 -07:00
philippe44
506a5aaf7a Update README.md 2023-09-27 19:22:50 -07:00
philippe44
fe409730e0 Update README.md 2023-09-27 18:13:31 -07:00
Wizmo2
4d6cfaca1c format changes 2023-09-27 06:25:30 -04:00
Wizmo2
69e61ce451 add battery status to vu meter effect 2023-09-25 20:57:37 -04:00
Wizmo2
60bd591bf8 support for c3 and s3 channel restrictions 2023-09-25 19:32:13 -04:00
Wizmo2
3ea26a0c6f allow offset on ST7789 2023-09-25 17:47:26 -04:00
philippe44
53fa83b2dd cspot dsconnect when paused handling 2023-09-24 00:18:30 -07:00
philippe44
90f53db953 http_download needs a bigger stack for cspot which required to move it to EXTRAM 2023-09-23 22:20:01 -07:00
philippe44
b413780048 some small speed further optimization to 24 bits SPDIF 2023-09-22 22:38:37 -07:00
philippe44
456f16fc79 Update README.md 2023-09-22 22:32:05 -07:00
philippe44
04e2917351 Update README.md 2023-09-22 22:29:58 -07:00
philippe44
dc9e1191a2 move sleep init at the very end of boot (all must be initialized) 2023-09-22 15:27:27 -07:00
philippe44
72a8fb2249 Merge pull request #307 from UrbanLienert/spdif-24bit
24bit SPDIF output
2023-09-22 15:26:29 -07:00
UrbanLienert
f409a9ee28 made vucp static 2023-09-22 08:06:23 +02:00
UrbanLienert
b85cf98cdf rmoved duplicated lines 2023-09-21 16:24:20 +02:00
UrbanLienert
e85c967220 changed 16bit as well 2023-09-20 18:51:13 +02:00
philippe44
ce21ff1b76 Update README.md 2023-09-18 22:34:32 -07:00
philippe44
7c71fb6a65 Update README.md 2023-09-18 18:40:30 -07:00
philippe44
bb22d4ea02 Update README.md 2023-09-18 18:39:37 -07:00
philippe44
e0e749fb5b add (some) spurious IR detection capability 2023-09-18 18:34:03 -07:00
UrbanLienert
c650bc7658 new version fix 2023-09-18 22:39:10 +02:00
philippe44
a76ce3f344 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-18 11:40:05 -07:00
philippe44
1d059be001 clz for 64 bits requies special version( wake bitmap) 2023-09-18 11:40:01 -07:00
UrbanLienert
32195b50ba 24bit spdif output 2023-09-18 17:13:58 +02:00
philippe44
a7539a5332 Update README.md 2023-09-17 20:38:44 -07:00
philippe44
450ebae399 Update README.md 2023-09-17 20:37:36 -07:00
philippe44
d238063c49 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-17 20:00:15 -07:00
philippe44
58f2e4488b don't count failed IR as activity 2023-09-17 20:00:11 -07:00
philippe44
b83e2722c0 Update README.md 2023-09-16 23:17:19 -07:00
philippe44
96a3f8aab0 Update README.md 2023-09-16 19:48:17 -07:00
philippe44
66b88d186a fix some buttons snafu & power off led on suspend 2023-09-16 19:42:31 -07:00
philippe44
bb185d76dc add buttons/rotary/ir/BT sink in sleep + battery low-voltage 2023-09-16 17:55:41 -07:00
philippe44
f4c0a91e84 Merge pull request #303 from wizmo2/fix_led_vu
Fix led_vu
2023-09-16 17:24:46 -07:00
Wizmo2
2ecf2e6098 hacked led_strip 2023-09-16 17:13:07 -04:00
Wizmo2
f32e4de84b static declaration in function 2023-09-16 16:47:40 -04:00
Wizmo2
55303ec1b3 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into fix_led_vu 2023-09-16 16:46:27 -04:00
philippe44
effc574e50 Update README.md 2023-09-16 00:35:09 -07:00
philippe44
3941a26b67 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-16 00:24:49 -07:00
philippe44
b47074e668 Update README.md 2023-09-15 21:32:46 -07:00
philippe44
822de92df1 Update README.md 2023-09-15 21:31:54 -07:00
philippe44
6bd778c7c6 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-15 21:26:36 -07:00
philippe44
28ecad4652 ACTRLS_SLEEP should be last (indexed array) and red led is not default for parsing 2023-09-15 21:26:33 -07:00
philippe44
ddd6bddde7 Update README.md 2023-09-15 20:46:41 -07:00
philippe44
f4e899fc60 add option to maintain some RTC GPIO pull-up/down 2023-09-15 20:38:47 -07:00
philippe44
c61ff05081 take into account case with no wake option 2023-09-15 18:50:26 -07:00
philippe44
c4df0c93f9 Update README.md 2023-09-15 18:47:23 -07:00
philippe44
fae09ba29e Update README.md 2023-09-15 18:43:02 -07:00
Wizmo2
fd0c38c49f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into fix_led_vu 2023-09-15 20:28:18 -04:00
philippe44
a83f14113e add sleep option + potentially fix led_vu issue 2023-09-15 17:18:06 -07:00
Wizmo2
7f0b411dac fix endings 2023-09-15 20:15:53 -04:00
Wizmo2
3350a8dbc7 restore static struct for led_vu 2023-09-15 20:08:31 -04:00
philippe44
a2eddb5411 Update README.md 2023-09-15 16:50:20 -07:00
philippe44
65ff5f7c2a Update README.md 2023-09-14 19:26:53 -07:00
philippe44
5ed2f6d03e Update README.md 2023-09-14 19:23:56 -07:00
philippe44
d8bd588982 Update README.md 2023-09-14 19:21:30 -07:00
philippe44
e4481a95f9 Update README.md 2023-09-14 19:21:17 -07:00
github-actions
992f5c361c Update prebuilt objects [skip actions] 2023-09-13 23:03:19 +00:00
philippe44
08c7ccdf28 put timestamp at end of ellipse 2023-09-12 18:37:27 -07:00
philippe44
0002256630 Fix AirPlay artwork disappearing 2023-09-12 18:16:38 -07:00
philippe44
b3ee25e3be add "power" gpio 2023-09-12 15:03:58 -07:00
philippe44
d53ae66547 Update README.md 2023-09-12 15:03:28 -07:00
Sébastien
26708ea51a Update bug_report.md 2023-09-12 12:43:12 -04:00
Sébastien
9c711d73f5 Update issue templates 2023-09-12 12:32:20 -04:00
github-actions
d0ac871a3b Update prebuilt objects [skip actions] 2023-09-12 01:39:04 +00:00
philippe44
38fee20680 better handling of AIrPlay network issues - release 2023-09-11 18:36:08 -07:00
philippe44
12ae3d08b6 Update README.md 2023-09-11 13:28:06 -07:00
github-actions
2931a5100e Update prebuilt objects [skip actions] 2023-09-11 05:32:43 +00:00
philippe44
c148cd16ae Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-10 22:27:41 -07:00
philippe44
38fb90d179 pseudo-idle task requires more stack (cJSON) - release 2023-09-10 22:27:36 -07:00
github-actions
61ff6a8915 Update prebuilt objects [skip actions] 2023-09-10 05:15:25 +00:00
philippe44
add6ff37ba Merge pull request #295 from urknall/master-v4.3
typo
2023-09-09 15:58:49 -07:00
philippe44
ae2ad85dec fix another ancient bug with sync start time (visible with AirPlay and SPDIF, but impacts all) 2023-09-09 15:57:58 -07:00
philippe44
a72f471c35 ancient AirPlay bug : passing thread context in mutex destroy 2023-09-08 22:35:06 -07:00
urknall
f1108332d9 typo 2023-09-08 21:32:48 +02:00
philippe44
d10d12a85b ES8388 i2c addresses 2023-09-05 17:11:16 -07:00
philippe44
956392ebfd remove i2s.c0 2023-09-05 11:51:36 -07:00
philippe44
f4ddc450a1 Update README.md 2023-09-04 18:35:13 -07:00
philippe44
926c567345 full i2s fix & loudness limited log 2023-09-04 16:06:49 -07:00
philippe44
0b077b5234 fix spdif+spotify stuttering and refactor stats => add pseudo_idle chained callbacks 2023-09-03 17:34:45 -07:00
philippe44
45cae64c83 Update README.md 2023-09-03 17:31:04 -07:00
philippe44
7c1a1081c4 as equalizer parameter change produced a small glitch, do not update these if volume has not changes 2023-09-01 21:44:54 -07:00
philippe44
4227fc9603 Update README.md 2023-09-01 21:33:07 -07:00
philippe44
c7e4d9711c add addressable led on led API including green/red + make rmt properly shared accross services 2023-09-01 21:22:42 -07:00
philippe44
e09837158c finalize esp32 vs esp32-s3 single sourceset 2023-08-31 21:26:27 -07:00
philippe44
067a1f2800 proposed way to handle loudness 0..10 2023-08-31 18:00:58 -07:00
philippe44
ad4d5db2f1 make some room in recovery 2023-08-30 23:47:25 -07:00
philippe44
55053d5941 fix spdif for s3 and remove one override
SPDIF on esp32 was partly incorrect due to word ordering and required i2s_hal override. This is not needed anymore as the "mistery" of SPDIF hack has been properly sorted out
2023-08-30 22:00:05 -07:00
philippe44
e9ccd8eef7 log consistency 2023-08-29 16:49:18 -07:00
philippe44
aa8554b722 remove some un-needed logs 2023-08-29 16:46:30 -07:00
philippe44
fd502a01d7 more esp32-s3 compatibility 2023-08-29 12:23:13 -07:00
philippe44
948a02efee Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-08-29 12:09:27 -07:00
philippe44
d1858c3cc3 make it compatible with idf 4.4 2023-08-29 12:09:15 -07:00
philippe44
eaa35b3677 Update README.md 2023-08-29 11:45:49 -07:00
philippe44
9f170020e2 fix plugin loudness value being memorized upon tab change 2023-08-28 20:47:52 -07:00
philippe44
809b55579f add plugin and fix loudness = 0 disable equalizer 2023-08-28 18:59:17 -07:00
philippe44
85a3bf8836 add loudness (alpha) 2023-08-28 17:51:50 -07:00
philippe44
3df589d7ab Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-08-27 15:10:05 -07:00
philippe44
118462f5d7 logs of what IR cannot decode 2023-08-27 15:10:00 -07:00
wizmo2
ca7670f754 fix DAC config to add I2S (either 'I2S' or blank) (#293)
Co-authored-by: Wizmo2 <wizmo.home@tahoo.com>
2023-08-27 16:02:57 -04:00
philippe44
b154f60e8e Update README.md 2023-08-26 19:49:31 -07:00
philippe44
e2b13a8a3f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-08-26 19:43:04 -07:00
philippe44
da87c859d0 add RC5 (maybe) 2023-08-26 19:42:32 -07:00
github-actions
3a424d0d58 Update prebuilt objects [skip actions] 2023-08-27 01:00:24 +00:00
github-actions
4433b82366 Update prebuilt objects [skip actions] 2023-08-26 21:17:01 +00:00
github-actions
f9fc84a23c Update prebuilt objects [skip actions] 2023-08-26 20:43:00 +00:00
github-actions
293a62e812 Update prebuilt objects [skip actions] 2023-08-26 20:19:30 +00:00
philippe44
b5b7dc6ebb fix DMAP parsing and a few compiler warning - release 2023-08-26 13:16:09 -07:00
philippe44
888371ef08 Update README.md 2023-08-25 12:59:47 -07:00
philippe44
9f3d10f78e Update README.md 2023-08-25 11:12:04 -07:00
philippe44
92060439c3 Update README.md 2023-08-25 11:11:21 -07:00
github-actions
cc55c0adbe Update prebuilt objects [skip actions] 2023-08-25 18:09:45 +00:00
philippe44
0be0a8979a Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-08-25 11:06:00 -07:00
philippe44
1d65bf9106 add option to disable LMS - release 2023-08-25 11:05:55 -07:00
philippe44
6d51ffa3c7 Update README.md 2023-08-23 13:02:42 -07:00
philippe44
149c9d8142 fix build for TWATCH2020 2023-08-22 16:30:18 -07:00
philippe44
eb458564c7 ancien SSD132x bug with non-inverted display 2023-08-16 01:06:49 -07:00
philippe44
5d24995ea0 allocate led_vu TCB to gain a few bytes & set non-existing GPIO in default config for esp32s3 2023-08-15 23:14:15 -07:00
philippe44
50543bfe12 because if it can, it will 2023-08-15 12:44:59 -07:00
philippe44
8ee0812085 fix plugin version 2023-08-15 11:41:54 -07:00
philippe44
7db73fe5ed Delete repo.xml 2023-08-15 11:31:10 -07:00
philippe44
4047fa2331 ledvu small changes & update plugin 2023-08-14 17:14:54 -07:00
philippe44
af8db69030 Merge pull request #202 from wizmo2/led_visu-v4.3
Support for WS2812 RGB LED Strip v4.3
2023-08-14 15:18:07 -07:00
Wizmo2
5b6c0fb06b move task to ext ram 2023-08-13 15:34:46 +01:00
wizmo2
8cb7122dd6 Merge branch 'sle118:master-v4.3' into led_visu-v4.3 2023-08-12 18:29:36 +01:00
philippe44
4aa0848cf5 Merge pull request #279 from wizmo2/fix_i2s_pin_displays
Fix i2s pin displays
2023-08-12 10:23:02 -07:00
Wizmo2
8213d0b152 copy set_i2c_pin from output_i2s.c 2023-08-12 18:15:29 +01:00
Wizmo2
943d50fe5f merge copy of cspot try2 2023-08-12 12:41:32 +01:00
Wizmo2
a67ec65713 marge cspot to master 2023-08-12 11:05:48 +01:00
wizmo2
54b0a5ae9c Merge branch 'sle118:master-v4.3' into led_visu-v4.3 2023-08-12 10:03:08 +01:00
github-actions
4bb973ac0c Update prebuilt objects [skip actions] 2023-08-12 01:15:57 +00:00
philippe44
d7706e5608 fix stdin redirection issue - release 2023-08-11 18:12:52 -07:00
github-actions
4f5fc90f4b Update prebuilt objects [skip actions] 2023-08-06 02:19:16 +00:00
philippe44
ba156586e9 make sure to reboot upon all LMS server discover failures - release 2023-08-05 19:16:35 -07:00
wizmo2
37955aac9b Merge branch 'sle118:master-v4.3' into led_visu-v4.3 2023-08-05 10:46:43 +01:00
github-actions
22fec069c9 Update prebuilt objects [skip actions] 2023-08-05 05:12:49 +00:00
philippe44
e14c815ea0 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-08-04 22:05:39 -07:00
philippe44
0912034706 fix typo - release 2023-08-04 22:05:27 -07:00
github-actions
f5284c6db6 Update prebuilt objects [skip actions] 2023-08-05 04:55:33 +00:00
philippe44
2b24c0d330 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-08-04 21:36:00 -07:00
philippe44
9f4839fc14 fix ogg (opus & vorbis) playback - release 2023-08-04 21:35:56 -07:00
Wizmo2
0f89f8336a resolve buffer issues 2023-07-29 11:36:30 -04:00
wizmo2
5eaaa9a9c0 Merge branch 'sle118:master-v4.3' into led_visu-v4.3 2023-07-29 09:08:23 +01:00
github-actions
2e3e2a7de4 Update prebuilt objects [skip actions] 2023-07-26 20:23:18 +00:00
philippe44
7b9f8bc788 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-07-26 13:19:25 -07:00
philippe44
232afb948b Bell catchup 2023-07-26 13:19:20 -07:00
github-actions
ce1021e711 Update prebuilt objects [skip actions] 2023-07-26 05:31:50 +00:00
philippe44
859efdb954 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-07-25 22:28:23 -07:00
philippe44
8d4813ceb4 Bell/cspot catchup - release 2023-07-25 22:28:19 -07:00
github-actions
ffae63b425 Update prebuilt objects [skip actions] 2023-07-26 04:34:16 +00:00
philippe44
4cd210838f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-07-25 21:31:13 -07:00
philippe44
05256fef94 add login5 proto files - release 2023-07-25 21:31:06 -07:00
github-actions
36a7b14a6d Update prebuilt objects [skip actions] 2023-07-26 02:12:59 +00:00
philippe44
bc6e25c85c Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-07-25 19:08:37 -07:00
philippe44
bdceb2d832 MacOS auth for Spotify 2023-07-25 19:08:30 -07:00
Sébastien
b38354420c Update README.md
Align with docker sle118/squeezelite-esp32-idfv435, and esp-idf version 4.3.5
2023-06-28 06:50:09 -04:00
wizmo2
9139d4f024 Merge branch 'master-v4.3' into led_visu-v4.3 2023-06-16 20:00:26 -04:00
Wizmo2
5458d4bf01 resolve conflict issue 2023-06-12 18:24:12 -04:00
wizmo2
bc759c06ef Revert sdkconfig
Manually revert origin version
2023-06-12 18:12:46 -04:00
wizmo2
b602d94440 Revert .gitignore
Manually revert origin version
2023-06-12 18:11:32 -04:00
Wizmo2
fcd23552ff Merge branch 'led_visu-v4.3' of https://github.com/wizmo2/squeezelite-esp32 into led_visu-v4.3 2023-06-11 09:07:54 -04:00
Wizmo2
ac7136c71c rr 2023-06-11 09:05:13 -04:00
Manuel Hernández
9bfa050d76 Update README.md with new ESP32-A1S board (#264) 2023-06-11 08:19:57 -04:00
Sebastien L
ed53fa21a7 Updates to ensure cspot permissions are set 2023-06-05 16:29:18 -04:00
philippe44
d152b535d4 Update README.md 2023-06-03 23:43:05 +02:00
Sebastien L
4d35c7e8c5 Attempt to resolve node dependencies 2023-06-03 16:50:50 -04:00
philippe44
8b764c0c2d fix vorbis and ogg ending early - release 2023-06-03 00:43:22 +02:00
philippe44
71c696ccd7 enable DMAP_FULL to verify 2023-06-02 21:34:47 +02:00
philippe44
5fd09e2a34 don't parse dmap unknown fields 2023-06-02 16:19:03 +02:00
Sébastien
79164da5f6 Update Platform_build.yml 2023-05-18 11:12:12 -04:00
Sébastien
d00528c88f bump build-tools to use docker sle118/squeezelite-esp32-idfv435 2023-05-18 11:09:18 -04:00
Sébastien
7ae0302032 bump esp-idf to 4.3.5
now using docker image sle118/squeezelite-esp32-idfv435
2023-05-18 10:18:00 -04:00
philippe44
e531bea28a move to 4.3.5
- remove SPI workaround and heap optimization
- move a few items from DRAM to either EXTRAM or keep them in text
2023-05-18 13:49:23 +02:00
philippe44
910ea583a2 cmke .. again 2023-05-18 01:00:32 +02:00
philippe44
52bb4069df remove useless cmake instruction 2023-05-18 00:32:21 +02:00
philippe44
8ef3f8178b more esp32/esp32-s3 convergence 2023-05-17 23:55:28 +02:00
philippe44
9db17c8da8 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-05-15 15:29:21 +02:00
philippe44
e78e5a4df7 prepare 4.4 migration + esp32s3 + mck on 0,1,3 2023-05-15 15:29:16 +02:00
philippe44
50163fb60b Update README.md 2023-05-15 14:42:56 +02:00
philippe44
9d7044f30c trim CMakefile for recovery/squeezelite generation 2023-05-13 22:23:49 +02:00
github-actions
e4ecb842aa Update prebuilt objects [skip actions] 2023-05-12 19:19:32 +00:00
philippe44
ef692b1b50 more aggressive handling of Spotify loudness war... - release 2023-05-12 21:16:20 +02:00
philippe44
93a2c0969c synchronizing with host versions 2023-05-10 16:45:50 +02:00
philippe44
bc4e56eabc unify Host and Embedded versions 2023-05-10 12:26:43 +02:00
philippe44
fa91879535 more missing stuff 2023-05-07 11:50:46 +02:00
philippe44
2f9b506e9b include typos 2023-05-07 00:28:17 +02:00
philippe44
806cb054ba add missing files, removing un-nedded ones 2023-05-07 00:12:02 +02:00
philippe44
ff663a20b6 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-05-06 23:50:32 +02:00
philippe44
8bad480112 new cspot/bell 2023-05-06 23:50:26 +02:00
Wizmo2
959cbe0c38 plugin change to turn leds off on power down 2023-05-01 09:45:10 +01:00
Wizmo2
290f4b5cb4 fix white-space 2023-04-30 16:05:58 +01:00
Sébastien
a38cb55468 Update codeql-analysis.yml [skip actions] 2023-04-26 18:02:51 -04:00
Sébastien
c0a9fd3100 Update codeql-analysis.yml [skip actions] 2023-04-26 17:39:13 -04:00
wizmo2
a8174de88b Update sdkconfig
revert sdkconfig
2023-04-26 15:40:48 -04:00
Sébastien
8cf9c16140 allow manual run - [skip actions] 2023-04-26 13:54:00 -04:00
Sébastien
12147eb570 Exclude bundles form analysis [skip actions] 2023-04-26 13:51:52 -04:00
Wizmo2
f722797587 dos2unix cspot 2023-04-23 18:41:47 +01:00
Wizmo2
3ac5c3187a Merge branch 'master-v4.3' of https://github.com/wizmo2/squeezelite-esp32 into led_visu-v4.3 2023-04-23 01:30:15 -04:00
github-actions
3afb2615c3 Update prebuilt objects [skip actions] 2023-04-19 22:26:14 +00:00
philippe44
e0e7e718ba fix vorbis & opus never ending - release 2023-04-20 00:23:23 +02:00
philippe44
f0527f70ac fix compile error 2023-04-19 12:39:48 +02:00
philippe44
3ecc09b989 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-19 00:43:11 +02:00
philippe44
7dbed7a67b trying to follow cspot... 2023-04-19 00:43:06 +02:00
github-actions
043d73dd6e Update prebuilt objects [skip actions] 2023-04-18 16:23:07 +00:00
philippe44
dc62afd788 backport typo - release 2023-04-18 18:19:28 +02:00
github-actions
d51df83981 Update prebuilt objects [skip actions] 2023-04-17 17:37:39 +00:00
philippe44
f4388a8c0a proper solution to track "plop" - release 2023-04-17 19:34:49 +02:00
philippe44
4ee9878a6f fix no reboot on squeezelite connection loss 2023-04-16 17:16:07 +02:00
Wizmo2
7f6406726f update to latest build script 2023-04-16 10:48:38 -04:00
Wizmo2
1e8d5371fe Merge branch 'master-v4.3' of https://github.com/wizmo2/squeezelite-esp32 into led_visu-v4.3 2023-04-16 10:44:57 -04:00
Sébastien
025b5d8c75 Update README.md 2023-04-14 20:55:31 -04:00
Sebastien L
0bebaccf57 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-12 14:44:09 -04:00
Sebastien L
7e8313baf3 Merge readme with master 2023-04-12 14:44:02 -04:00
philippe44
ce4cebd994 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-09 10:31:01 -07:00
philippe44
ba81c2ecd5 fix plop when switchign track (still need to fix it when seeking) - release 2023-04-09 10:30:56 -07:00
github-actions
131b1d36b0 Update prebuilt objects [skip actions] 2023-04-09 13:48:46 +00:00
Sebastien L
0d37c270e2 more fixing 2023-04-09 09:43:54 -04:00
Sebastien L
6054affb81 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-09 09:38:05 -04:00
Sebastien L
196a1d179a Attempt to fix build [skip actions] 2023-04-08 12:36:16 -04:00
Sebastien L
7574054e22 Attempt to fix build [skip actions] 2023-04-08 12:34:19 -04:00
Sébastien
cd088d2500 Update Platform_build.yml [skip actions] 2023-04-08 12:23:37 -04:00
github-actions
d16ce964d6 Update prebuilt objects [skip actions] 2023-04-08 14:44:47 +00:00
github-actions
84d22cce07 Update prebuilt objects [skip actions] 2023-04-07 19:17:01 +00:00
philippe44
0495b7ea7a release 2023-04-07 12:13:43 -07:00
Sebastien L
b98ddd76cb Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-04-07 10:53:29 -04:00
Sebastien L
7ac628a29d Improved audio ui, bug fixes 2023-04-07 10:53:22 -04:00
philippe44
d2b8edce60 fix vorbis as well 2023-04-06 22:45:40 -07:00
philippe44
b4af1e7bef commit BT buttons 2023-04-05 16:33:39 -07:00
philippe44
f91392e044 preset buttons is 0..9 2023-04-05 15:29:48 -07:00
philippe44
1e0fce53c7 and decoder ... 2023-04-04 22:30:38 -07:00
philippe44
3372aaea69 NULL the overflow buffer 2023-04-04 22:21:33 -07:00
philippe44
8d1888a198 new opus decoder 2023-04-04 22:13:31 -07:00
philippe44
18b830eaa3 add preset 7..10 2023-04-02 13:40:07 -07:00
philippe44
cbc1ab38fb gain some .rodata from DMAP decoder (unused strings) 2023-04-01 21:03:10 -07:00
philippe44
27a0d2a4d3 fix Spotify volume normalization 2023-04-01 14:24:56 -07:00
philippe44
02fc039bbe maximize number of AirPlay RTP buffers 2023-04-01 12:37:52 -07:00
philippe44
6fad1f8251 fix empty seek table for good 2023-04-01 00:39:19 -07:00
philippe44
42621561df codec optimizations 2023-03-30 22:22:48 -07:00
philippe44
5ecb371fb0 pretty things up a bit 2023-03-29 23:41:35 -07:00
philippe44
cad286c8d7 provide squeezelite error log in UI 2023-03-29 23:02:16 -07:00
philippe44
a9a9018794 fix country code in cspot 2023-03-29 12:06:20 -07:00
github-actions
236cfef05d Update prebuilt objects [skip actions] 2023-03-29 07:18:33 +00:00
Wizmo2
f5207def53 better memory management and more effect tweaks 2022-11-25 18:01:58 -05:00
Wizmo2
3f4bba2443 imporve wavefom effect 2022-11-25 13:51:58 -05:00
Wizmo2
c88680adab update malloc 2022-11-24 12:27:02 -05:00
Wizmo2
8667bd82b1 merge readme chnages 2022-11-24 04:16:12 -05:00
Wizmo2
aa865e17b2 fix for 4.3 2022-11-24 03:50:51 -05:00
Wizmo2
afd0da16a5 merge changes from led_visu to v4.3 2022-11-24 03:49:19 -05:00
986 changed files with 97594 additions and 59264 deletions

2
.gitattributes vendored
View File

@@ -1,5 +1,5 @@
# Auto detect text files and perform LF normalization
* text=auto
# * text=auto
# Custom for Visual Studio
*.cs diff=csharp

90
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,90 @@
---
name: Bug report
about: How to Submit an Issue for the squeezelite-esp32 Project
title: ''
labels: ''
assignees: ''
---
To help us resolve your issue as quickly as possible, please follow these guidelines when submitting an issue. Providing all the necessary information will save both your time and ours.
### Describe the bug
A clear and concise description of what the bug is.
### Preliminary Information
1. **Firmware Version**: Specify the version of the firmware you are using.
2. **Plugin Version**: Mention the version of the plugin installed on your LMS (Logitech Media Server).
### Hardware Details
Please describe your hardware setup:
- **ESP32 Module**: For example, ESP32 WROVER, ESP32-S3, etc.
- **Board Type**: If applicable, e.g., ESP32 audio kit, etc.
- **DAC Chip**: Specify the DAC chip you are using.
- **Additional Hardware**: Include details about any other hardware like rotary controls, buttons, screens (SPI, I2C), Ethernet, IO expansion, etc.
### NVS Settings
Follow these steps to share your NVS settings:
1. Open the web UI of your device.
2. Click on the "Credit" tab.
3. Enable the "Show NVS Editor" checkbox. This allows you to view or change the NVS configuration even when not in recovery mode.
4. Navigate to the "NVS Editor" tab.
5. Scroll to the bottom and click "Download Config".
6. Share the downloaded content here.
<details>
<pre><code>
Your log content here
</code></pre>
</details>
### Logs
To share logs:
1. Connect your player to a computer using a USB cable. Use a built-in Serial-USB adapter if your player has one, or an external USB adapter otherwise.
2. Go to the [web installer](https://sle118.github.io/squeezelite-esp32-installer/).
3. Click "Connect to Device".
4. Select the appropriate serial port.
5. Click "Logs And Console".
6. Download the logs and share them here. Please remove any sensitive information like Wi-Fi passwords or MAC addresses.
- **If the problem occurs soon after booting**: Share the full log until the issue occurs.
- **If the problem occurs later during playback**: Trim the logs to include information just before and after the problem occurs.
#### Example Log
Here's an example log for reference. Make sure to obfuscate sensitive information like Wi-Fi passwords, MAC addresses, and change IP addresses to something more generic.
<details>
<pre><code>
=== START OF LOG ===
Example of log from the console
rst:0x1 (POWERON_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
...
I (1041) cpu_start: Application information:
I (1044) cpu_start: Project name: Squeezelite-ESP32
I (1050) cpu_start: App version: I2S-4MFlash-1336
I (1055) cpu_start: Compile time: Aug 12 2023 01:20:18
I (1062) cpu_start: ELF file SHA256: 34241d6e99fd1d6b...
I (1068) cpu_start: ESP-IDF: v4.3.5-dirty
...
I (1133) heap_init: At 40094A8C len 0000B574 (45 KiB): IRAM
I (1139) spiram: Adding pool of 4066K of external SPI memory to heap allocator
=== END OF LOG ===
</code></pre>
</details>
### Issue Description
1. **Observed Behavior**: Describe what you think is wrong.
2. **Expected Behavior**: Describe what you expect should happen.
3. **Steps to Reproduce**: Provide a step-by-step guide on how to replicate the issue.

View File

@@ -13,13 +13,12 @@ on:
description: 'Force a Release build. When not forced, the system will check for release word in the last commit message to trigger a release'
required: true
type: boolean
jobs:
bootstrap:
name: Global setup
runs-on: ubuntu-latest
container:
image: sle118/squeezelite-esp32-idfv43
image: sle118/squeezelite-esp32-idfv435
outputs:
build_number: ${{ steps.buildnumber.outputs.build_number }}
ui_build: ${{ steps.build_flags.outputs.ui_build }}
@@ -35,14 +34,15 @@ jobs:
uses: einaregilsson/build-number@v3
with:
token: ${{secrets.github_token}}
- name: Set build flags
id: build_flags
run: |
git config --global --add safe.directory /__w/squeezelite-esp32/squeezelite-esp32
[ ${{github.event.inputs.ui_build}} ] && ui_build_option="--ui_build" || ui_build_option=""
[ ${{github.event.inputs.release_build}} ] && release_build_option="--force" || release_build_option=""
echo "ui_build_option=$ui_build_option" >> $GITHUB_ENV
echo "release_build_option=$release_build_option" >> $GITHUB_ENV
[ ${{github.event.inputs.release_build}} ] && release_build_option="--force" || release_build_option=""
echo "ui_build_option=$ui_build_option" >> "$GITHUB_OUTPUT"
echo "release_build_option=$release_build_option" >> "$GITHUB_OUTPUT"
echo "Dumping environment"
env
. /opt/esp/python_env/idf4.3_py3.8_env/bin/activate
@@ -50,7 +50,7 @@ jobs:
# --mock - to mock the compilation part - this is to be used for testing only
# --force - to force a release build even if the last commit message doesn't contain the word "release"
# --ui_build - to force a ui_build even if the last commit message doesn't contain "[ui-build]"
build_tools.py build_flags $ui_build_option $release_build_option
build_tools.py build_flags $ui_build_option $release_build_option
- name: Show Build Flags
run: |
echo "Running with the following options"
@@ -88,7 +88,7 @@ jobs:
git commit -m "Update prebuilt objects [skip actions]"
git push https://${{secrets.github_token}}@github.com/sle118/squeezelite-esp32.git
- name: Locally store commonly built objects
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: prebuilt_objects
path: |
@@ -100,7 +100,7 @@ jobs:
build:
runs-on: ubuntu-latest
container:
image: sle118/squeezelite-esp32-idfv43
image: sle118/squeezelite-esp32-idfv435
needs: [bootstrap]
strategy:
matrix:
@@ -128,9 +128,9 @@ jobs:
. /opt/esp/python_env/idf4.3_py3.8_env/bin/activate
git config --global --add safe.directory /__w/squeezelite-esp32/squeezelite-esp32
git status
build_tools.py environment --build ${{ needs.bootstrap.outputs.build_number }} --env_file "$GITHUB_ENV" --node "${{matrix.node}}" --depth ${{matrix.depth}} --major 2 --docker sle118/squeezelite-esp32-idfv43
build_tools.py environment --build ${{ needs.bootstrap.outputs.build_number }} --env_file "$GITHUB_ENV" --node "${{matrix.node}}" --depth ${{matrix.depth}} --major 2 --docker sle118/squeezelite-esp32-idfv435
- uses: actions/download-artifact@master
- uses: actions/download-artifact@v4
name: Restore common objects
with:
name: prebuilt_objects
@@ -138,11 +138,6 @@ jobs:
if: ${{ needs.bootstrap.outputs.mock == 0 }}
run: |
. ${IDF_PYTHON_ENV_PATH}/bin/activate
chmod +x ./components/spotify/cspot/bell/external/nanopb/generator/protoc
chmod +x ./components/spotify/cspot/bell/external/nanopb/generator/protoc-gen-nanopb
chmod +x ./components/spotify/cspot/bell/external/nanopb/generator/*.py
chmod +x ./components/spotify/cspot/bell/external/nanopb/generator/*.py2
chmod +x ./components/spotify/cspot/bell/external/nanopb/generator/proto/*.py
echo "Copying target sdkconfig"
cp build-scripts/${TARGET_BUILD_NAME}-sdkconfig.defaults sdkconfig
echo "Building project"
@@ -176,7 +171,7 @@ jobs:
zip build/${artifact_file_name} partitions*.csv components/ build/*.bin build/bootloader/bootloader.bin build/partition_table/partition-table.bin build/flash_project_args build/size_*.txt
fi
- name: Upload Build Artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: ${{ needs.bootstrap.outputs.mock == 0 }}
with:
name: ${{ env.artifact_prefix }}
@@ -202,7 +197,7 @@ jobs:
release_name: ${{ env.name }}
body: ${{ env.description }}
draft: false
prerelease: true
prerelease: false
- name: Upload Release Asset - Squeezelite binary file
if: ${{ needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
id: upload-release-asset
@@ -228,5 +223,7 @@ jobs:
update_web_installer:
name: Update Web Installer After Release
needs: [ bootstrap, build ]
if: ${{( always() && !cancelled() ) && needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
if: ${{ always() && !cancelled() && needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
uses: ./.github/workflows/web_deploy.yml
secrets:
WEB_INSTALLER: ${{ secrets.WEB_INSTALLER }}

View File

@@ -19,6 +19,7 @@ on:
branches: [ master-cmake ]
schedule:
- cron: '19 12 * * 4'
workflow_dispatch:
jobs:
analyze:
@@ -39,7 +40,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -50,7 +51,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -62,6 +63,13 @@ jobs:
#- run: |
# make bootstrap
# make release
# Exclude specific artifacts from analysis
- name: Exclude Artifacts
run: |
# Exclude components/wifi-manager/webapp/dist/js/index* from analysis
echo 'components/wifi-manager/webapp/dist/js/index*' >> .codeql-exclude-paths
echo 'components/wifi-manager/webapp/dist/js/index*' >> .codeql-exclude-paths.txt
echo 'components/wifi-manager/webapp/dist/index*' >> .codeql-exclude-paths
echo 'components/wifi-manager/webapp/dist/index*' >> .codeql-exclude-paths.txt
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@@ -1,6 +1,9 @@
name: Update Web Installer
on:
workflow_call:
secrets:
WEB_INSTALLER:
required: true
workflow_dispatch:
jobs:
update_web_installer:

3
.gitmodules vendored
View File

@@ -8,3 +8,6 @@
[submodule "components/wifi-manager/UML-State-Machine-in-C"]
path = components/wifi-manager/UML-State-Machine-in-C
url = https://github.com/kiishor/UML-State-Machine-in-C
[submodule "components/wifi-manager/webapp/src/bootswatch"]
path = components/wifi-manager/webapp/src/bootswatch
url = https://github.com/thomaspark/bootswatch.git

103
CHANGELOG Normal file
View File

@@ -0,0 +1,103 @@
2025-02-17
- reverse some checks on display not NULL in gds.c. As it is about being fast, I'd prefer the caller to know that there is no display and don't call. I'm sure I have missed something when there is only led_vu and no display, but people will remind me soon enough :-)
2024-09-28
- add dedicated volume encoder
- fix memory leak in rotary config creation
2024-09-28
- create autoexec NVS entry at the right place (not only whne BT is enabled!
- try to make i2s panic mode work for all esp versions
2024-09-12
- add AW9523 GPIO expander credits @Stefan Krupop (https://github.com/sle118/squeezelite-esp32/pull/430
2024-09-10
- Merge pull request # 439 from digidocs/eq_update_fix2 (# 309)
- Fix for I2S noise burst when ESP32 panic occurs (# 437)
2024-05-05
- Fix crash when led_vu is configured without display
2024-01-27
- complete libflac fix and add chaining enablement
- fixed stream Ogg demux issue with unknown granule
2024-01-19
- fixed libflac with OggFlac
- AirPlay missed frame logging
2024-01-16
- catch-up with cspot latest
- refactor airplay flush/first packet
- new libFLAC that supports multi-stream OggFlac
- fix output threshold
- log missed frames
2024-01-10
- add OggFlac to stream metadata
- fix OggFlac deadlock in flac callback when not enough data in streambuf
- fix no displayer due to threadshold too high (use 500ms instead)
- reset outputbuf when cspot starts
2024-01-01
- ogg stream are parsed to foward metadata to LMS
- fix some ogg parsing on multi-stream containers
2023-11-19
- more robust (?) airplay RTP frame recovery
- initialize of scratch string in monitor (trying to figure out random reboot)
2023-11-16
- add SH1122 support
- optimize GDS DrawPixel function
2023-11-09
- force gpio_pad_select_gpio in dac_controlset in case somebody uses UART gpio's (or other pre-programmed)
2023-11-08
- execute dac_controlset even when there is no i2s (for gpio)
2023-11-07
- led-vu gain + misc fixes
- bump plugin version to 0.600
2023-11-03
- don't reboot when external decoder is connected even with a LMS server
2023-10-28
- fix recovery size (remove bootstrap)
- improve NVS initialization structure
2023-10-27
- fix vorbis (and opus) memory leak
2023-10-25
- fix vorbis codec close
2023-10-23
- fix Spotify track insertion
- [WEB] Allow running without LMS with option "Audio/Disable Squeezelite"
2023-10.07
- catchup with official cspot
2023-10-06
- fix cspot PREV on first track, NEXT on last track and normal ending
- use DMA_AUTO for SPI
- cspot share same time log
2023-10-06
- Fix bootswatch bug that caused difficult to read UI ( issue #319)
2023-10-02
- update cspot
2023-09-29
- sleep mechanism
- spotify can store credentials so that zeroconf is optional and players are always registered
- add battery to led_vu (see credits)
- spdif can do 24 bits (see credits)
- rmt fixes
- airplay & spotify artwork fixes
- airplay stability improvments
- fix UI text color

View File

@@ -1,8 +1,13 @@
cmake_minimum_required(VERSION 3.5)
set(EXTRA_COMPONENT_DIRS components/platform_console/app_recovery components/platform_console/app_squeezelite )
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(PROJECT_VER $ENV{PROJECT_VER})
add_definitions(-DMODEL_NAME=SqueezeESP32)
if(NOT DEFINED DEPTH)
set(DEPTH "16")
endif()
# State machine hierarchy enabled and logging enabled
add_definitions(-DSTATE_MACHINE_LOGGER=1)
add_definitions(-DHIERARCHICAL_STATES=1)
@@ -18,14 +23,112 @@ add_definitions(-DHIERARCHICAL_STATES=1)
#add_definitions(-DNETWORK_MANAGER_LOG_LEVEL=ESP_LOG_DEBUG)
#add_definitions(-DNETWORK_HTTP_SERVER_LOG_LEVEL=ESP_LOG_DEBUG)
if(NOT DEFINED DEPTH)
set(DEPTH "16")
endif()
message(STATUS "Building RECOVERY")
# utility to build sizes
function(build_size target_name)
set(target_elf ${target_name}.elf)
set(target_map ${target_name}.map)
set(idf_size ${python} ${IDF_PATH}/tools/idf_size.py)
if(DEFINED OUTPUT_JSON AND OUTPUT_JSON)
list(APPEND idf_size "--json")
endif()
add_custom_target(size-${target_name} ALL
DEPENDS ${target_elf}
COMMAND ${idf_size} ${target_map} -o "size-${target_name}"
)
add_custom_target(size-files-${target_name} ALL
DEPENDS ${target_elf}
COMMAND ${idf_size} --files ${target_map}
)
add_custom_target(size-components-${target_name} ALL
DEPENDS ${target_elf}
COMMAND ${idf_size} --archives ${target_map}
)
endfunction()
# manually add the 2 versions for application: recovery and squeezelite
set(EXTRA_COMPONENT_DIRS components/platform_console/app_recovery components/platform_console/app_squeezelite )
project(recovery)
set_property(TARGET recovery.elf PROPERTY RECOVERY_PREFIX app_recovery )
include(squeezelite.cmake)
set(PROJECT_VER $ENV{PROJECT_VER})
# we need own "esp_app_desc" to take precedence
add_custom_command(
TARGET recovery.elf
PRE_LINK
COMMAND xtensa-esp32-elf-objcopy --weaken-symbol esp_app_desc ${BUILD_DIR}/esp-idf/app_update/libapp_update.a
VERBATIM
)
# when building recovery, add app_recovery to the link
get_target_property(BCA recovery.elf LINK_LIBRARIES)
list(REMOVE_ITEM BCA "idf::app_squeezelite" "idf::app_recovery" "-Wl,--Map=${BUILD_DIR}/recovery.map")
set_target_properties(recovery.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_recovery;-Wl,--Map=${BUILD_DIR}/recovery.map")
# create files with size for recovery
# build_size(recovery)
# build squeezelite, add app_squeezelite to the link
add_executable(squeezelite.elf "CMakeLists.txt")
add_dependencies(squeezelite.elf recovery.elf)
set_target_properties(squeezelite.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_squeezelite;-Wl,--Map=${BUILD_DIR}/squeezelite.map,--wrap=esp_panic_handler")
add_custom_command(
TARGET squeezelite.elf
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Generating ${BUILD_DIR}/squeezelite.bin"
COMMAND ${ESPTOOLPY} elf2image ${ESPTOOLPY_FLASH_OPTIONS} ${ESPTOOLPY_ELF2IMAGE_OPTIONS} -o "squeezelite.bin" "squeezelite.elf"
VERBATIM
)
# create files with size for squeezelite
# build_size(squeezelite)
# make it part of cleanup
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
APPEND PROPERTY
ADDITIONAL_MAKE_CLEAN_FILES "${BUILD_DIR}/squeezelite.elf" "${BUILD_DIR}/squeezelite.map"
)
# adding OTA_0 partition
partition_table_get_partition_info(otaapp_offset "--partition-type app --partition-subtype ota_0" "offset")
idf_component_get_property(main_args esptool_py FLASH_ARGS)
idf_component_get_property(sub_args esptool_py FLASH_SUB_ARGS)
esptool_py_flash_target(squeezelite-flash "${main_args}" "${sub_args}")
esptool_py_flash_target_image(squeezelite-flash squeezelite "${otaapp_offset}" "${BUILD_DIR}/squeezelite.bin")
esptool_py_flash_target_image(flash squeezelite "${otaapp_offset}" "${BUILD_DIR}/squeezelite.bin")
# and JTAG scripts
add_custom_target(_jtag_scripts ALL
BYPRODUCTS "flash_dbg_project_args"
POST_BUILD
COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_SOURCE_DIR}/generate_debug_scripts.cmake"
)
if(CMAKE_HOST_UNIX)
# Add custom target to set executable permissions before build for cspot component
add_custom_target(set_cspot_permissions
COMMAND ${CMAKE_COMMAND} -E echo "************************************************************************************************"
COMMAND ${CMAKE_COMMAND} -E echo "**** Setting permissions for required files"
COMMAND ${CMAKE_COMMAND} -E echo "**** ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/protoc-gen-nanopb"
COMMAND ${CMAKE_COMMAND} -E echo "**** ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/*.py"
COMMAND ${CMAKE_COMMAND} -E echo "**** ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/*.py2"
COMMAND ${CMAKE_COMMAND} -E echo "**** ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/proto/*.py"
COMMAND ${CMAKE_COMMAND} -E echo "**** ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/protoc"
COMMAND chmod +x ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/protoc-gen-nanopb
COMMAND chmod +x ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/*.py
COMMAND chmod +x ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/*.py2
COMMAND chmod +x ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/proto/*.py
COMMAND chmod +x ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/protoc
COMMAND ${CMAKE_COMMAND} -E echo "************************************************************************************************"
)
# Add a dependency to ensure permissions are set before building cspot component
add_dependencies(__idf_spotify set_cspot_permissions)
endif()
# ======================= DEBUG FLAGS ============================
#target_compile_definitions(__idf_esp_eth PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_INFO)
@@ -125,4 +228,4 @@ set(PROJECT_VER $ENV{PROJECT_VER})
# target_compile_definitions(__idf_wear_levelling PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_wifi_provisioning PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_wpa_supplicant PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_xtensa PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_xtensa PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)

View File

@@ -2,29 +2,38 @@ FROM ubuntu:20.04
ARG DEBIAN_FRONTEND=noninteractive
ENV GCC_TOOLS_BASE=/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-8.4.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-
ENV GCC_TOOLS_BASE=/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-
# To build the image for a branch or a tag of IDF, pass --build-arg IDF_CLONE_BRANCH_OR_TAG=name.
# To build the image with a specific commit ID of IDF, pass --build-arg IDF_CHECKOUT_REF=commit-id.
# It is possibe to combine both, e.g.:
# IDF_CLONE_BRANCH_OR_TAG=release/vX.Y
# IDF_CHECKOUT_REF=<some commit on release/vX.Y branch>.
# The following commit contains the ldgen fix: eab738c79e063b3d6f4c345ea5e1d4f8caef725b
# to build an image using that commit: docker build . --build-arg IDF_CHECKOUT_REF=eab738c79e063b3d6f4c345ea5e1d4f8caef725b -t sle118/squeezelite-esp32-idfv43
# Docker build for release 4.3.2 as of 2022/02/28
# docker build . --build-arg IDF_CHECKOUT_REF=8bf14a9238329954c7c5062eeeda569529aedf75 -t sle118/squeezelite-esp32-idfv43
# To run the image interactive (windows):
# docker run --rm -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv43
# To run the image interactive (linux):
# docker run --rm -v `pwd`:/project -w /project -it sle118/squeezelite-esp32-idfv43
# Docker build for release 4.3.5 as of 2023/05/18
# docker build . --build-arg IDF_CHECKOUT_REF=6d04316cbe4dc35ea7e4885e9821bd9958ac996d -t sle118/squeezelite-esp32-idfv435
# Updating the docker image in the repository
# docker push sle118/squeezelite-esp32-idfv435
# or to do both:
# docker build . --build-arg IDF_CHECKOUT_REF=6d04316cbe4dc35ea7e4885e9821bd9958ac996d -t sle118/squeezelite-esp32-idfv435 && docker push sle118/squeezelite-esp32-idfv435
#
# (windows) To run the image interactive :
# docker run --rm -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv435
# (windows powershell)
# docker run --rm -v ${PWD}:/project -w /project -it sle118/squeezelite-esp32-idfv435
# (linux) To run the image interactive :
# docker run --rm -v `pwd`:/project -w /project -it sle118/squeezelite-esp32-idfv435
# to build the web app inside of the interactive session
# pushd components/wifi-manager/webapp/ && npm install && npm run-script build && popd
#
# to run the docker with netwotrk port published on the host:
# docker run --rm -p 5000:5000/tcp -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv43
# (windows)
# docker run --rm -p 5000:5000/tcp -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv435
# (linux)
# docker run --rm -p 5000:5000/tcp -v `pwd`:/project -w /project -it sle118/squeezelite-esp32-idfv435
ARG IDF_CLONE_URL=https://github.com/espressif/esp-idf.git
ARG IDF_CLONE_BRANCH_OR_TAG=master
ARG IDF_CHECKOUT_REF=8bf14a9238329954c7c5062eeeda569529aedf75
ARG IDF_CHECKOUT_REF=6d04316cbe4dc35ea7e4885e9821bd9958ac996d
ENV IDF_PATH=/opt/esp/idf
ENV IDF_TOOLS_PATH=/opt/esp
@@ -35,6 +44,7 @@ RUN : \
&& apt-get update \
&& apt-get install -y \
apt-utils \
build-essential \
bison \
ca-certificates \
ccache \
@@ -42,22 +52,26 @@ RUN : \
curl \
flex \
git \
git-lfs \
gperf \
lcov \
libbsd-dev \
libpython3.8 \
libffi-dev \
libncurses-dev \
libpython2.7 \
libusb-1.0-0-dev \
make \
ninja-build \
python3 \
python3.8 \
python3-pip \
python3-venv \
ruby \
unzip \
wget \
xz-utils \
zip \
npm \
nodejs \
npm \
nodejs \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& update-alternatives --install /usr/bin/python python /usr/bin/python3 10 \
@@ -97,8 +111,8 @@ RUN : \
COPY docker/patches $IDF_PATH
#set idf environment variabies
ENV PATH /opt/esp/idf/components/esptool_py/esptool:/opt/esp/idf/components/espcoredump:/opt/esp/idf/components/partition_table:/opt/esp/idf/components/app_update:/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-8.4.0/xtensa-esp32-elf/bin:/opt/esp/tools/xtensa-esp32s2-elf/esp-2021r2-8.4.0/xtensa-esp32s2-elf/bin:/opt/esp/tools/xtensa-esp32s3-elf/esp-2021r2-8.4.0/xtensa-esp32s3-elf/bin:/opt/esp/tools/riscv32-esp-elf/esp-2021r2-8.4.0/riscv32-esp-elf/bin:/opt/esp/tools/esp32ulp-elf/2.28.51-esp-20191205/esp32ulp-elf-binutils/bin:/opt/esp/tools/esp32s2ulp-elf/2.28.51-esp-20191205/esp32s2ulp-elf-binutils/bin:/opt/esp/tools/cmake/3.16.4/bin:/opt/esp/tools/openocd-esp32/v0.10.0-esp32-20211111/openocd-esp32/bin:/opt/esp/python_env/idf4.3_py3.8_env/bin:/opt/esp/idf/tools:$PATH
ENV GCC_TOOLS_BASE="/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-8.4.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-"
ENV PATH /opt/esp/idf/components/esptool_py/esptool:/opt/esp/idf/components/espcoredump:/opt/esp/idf/components/partition_table:/opt/esp/idf/components/app_update:/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32-elf/bin:/opt/esp/tools/xtensa-esp32s2-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32s2-elf/bin:/opt/esp/tools/xtensa-esp32s3-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32s3-elf/bin:/opt/esp/tools/riscv32-esp-elf/esp-2021r2-patch3-8.4.0/riscv32-esp-elf/bin:/opt/esp/tools/esp32ulp-elf/2.28.51-esp-20191205/esp32ulp-elf-binutils/bin:/opt/esp/tools/esp32s2ulp-elf/2.28.51-esp-20191205/esp32s2ulp-elf-binutils/bin:/opt/esp/tools/cmake/3.16.4/bin:/opt/esp/tools/openocd-esp32/v0.11.0-esp32-20220706/openocd-esp32/bin:/opt/esp/python_env/idf4.3_py3.8_env/bin:/opt/esp/idf/tools:$PATH
ENV GCC_TOOLS_BASE="/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-"
ENV IDF_PATH="/opt/esp/idf"
ENV IDF_PYTHON_ENV_PATH="/opt/esp/python_env/idf4.3_py3.8_env"
ENV IDF_TOOLS_EXPORT_CMD="/opt/esp/idf/export.sh"
@@ -109,7 +123,32 @@ ENV NODE_VERSION="8"
ENV OPENOCD_SCRIPTS="/opt/esp/tools/openocd-esp32/v0.10.0-esp32-20211111/openocd-esp32/share/openocd/scripts"
# Ccache is installed, enable it by default
# The constraint file has been downloaded and the right Python package versions installed. No need to check and
# download this at every invocation of the container.
ENV IDF_PYTHON_CHECK_CONSTRAINTS=no
# Ccache is installed, enable it by default
ENV IDF_CCACHE_ENABLE=1
# Install QEMU runtime dependencies
RUN : \
&& apt-get update && apt-get install -y -q \
libglib2.0-0 \
libpixman-1-0 \
&& rm -rf /var/lib/apt/lists/* \
&& :
# Install QEMU
ARG QEMU_VER=esp-develop-20220919
ARG QEMU_DIST=qemu-${QEMU_VER}.tar.bz2
ARG QEMU_SHA256=f6565d3f0d1e463a63a7f81aec94cce62df662bd42fc7606de4b4418ed55f870
RUN : \
&& wget --no-verbose https://github.com/espressif/qemu/releases/download/${QEMU_VER}/${QEMU_DIST} \
&& echo "${QEMU_SHA256} *${QEMU_DIST}" | sha256sum --check --strict - \
&& tar -xf ${QEMU_DIST} -C /opt \
&& rm ${QEMU_DIST} \
&& :
COPY docker/entrypoint.sh /opt/esp/entrypoint.sh
COPY components/wifi-manager/webapp/package.json /opt
@@ -142,6 +181,9 @@ RUN : \
&& node --version \
&& npm install -g \
&& :
RUN : \
&& npm install -g html-webpack-plugin
ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
ENV PATH $IDF_PYTHON_ENV_PATH:$NVM_DIR/v$NODE_VERSION/bin:$PATH
@@ -152,5 +194,7 @@ RUN : \
&& chmod +x /usr/sbin/build_tools.py \
&& :
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD [ "/bin/bash" ]

390
README.md
View File

@@ -1,27 +1,31 @@
![Cross-Build](https://github.com/sle118/squeezelite-esp32/workflows/Cross-Build/badge.svg?branch=master-cmake)
![ESP-IDF v4.3.1](https://github.com/sle118/squeezelite-esp32/actions/workflows/esp-idf-v4.3-build.yml/badge.svg?branch=master-v4.3)
[![Platform Build](https://github.com/sle118/squeezelite-esp32/actions/workflows/Platform_build.yml/badge.svg)](https://github.com/sle118/squeezelite-esp32/actions/workflows/Platform_build.yml)
# Squeezelite-esp32
## What is this
Squeezelite-esp32 is an audio software suite made to run on espressif's ESP32 wifi (b/g/n) and bluetooth chipset. It offers the following capabilities
## Forewords
**More and more people seems to use this without a LMS server, just for BT, AirPlay or Spotify. It's fine but understand that squeezeliteESP32 is primarily a Logitech Media Server player and has been designed around that concept. All the others are add-ons stitched to it, so other modes have their shortcomings. So please make sure you read [this](#Additional-configuration-notes-from-the-Web-UI) before opening an issue**
## What is this?
Squeezelite-esp32 is an audio software suite made to run on espressif's esp32 and esp32-s3 wifi (b/g/n) and bluetooth chipsets. It offers the following capabilities
- Stream your local music and connect to all major on-line music providers (Spotify, Deezer, Tidal, Qobuz) using [Logitech Media Server - a.k.a LMS](https://forums.slimdevices.com/) and enjoy multi-room audio synchronization. LMS can be extended by numerous plugins and can be controlled using a Web browser or dedicated applications (iPhone, Android). It can also send audio to UPnP, Sonos, ChromeCast and AirPlay speakers/devices.
- Stream directly from a Bluetooth device (iPhone, Android)
- Stream directly from an AirPlay controller (iPhone, iTunes ...) and enjoy synchronization multiroom as well (although it's AirPlay 1 only)
- Stream direcly from Spotify using SpotifyConnect (thanks to [cspot](https://github.com/feelfreelinux/cspot)
- Stream from a **Bluetooth** device (iPhone, Android)
- Stream from an **AirPlay** controller (iPhone, iTunes ...) and enjoy synchronization multiroom as well (although it's AirPlay 1 only)
- Stream directly from **Spotify** using SpotifyConnect (thanks to [cspot](https://github.com/feelfreelinux/cspot)) - please read carefully [this](#spotify)
Depending on the hardware connected to the ESP32, you can send audio to a local DAC, to SPDIF or to a Bluetooth speaker. The bare minimum required hardware is a WROVER module with 4MB of Flash and 4MB of PSRAM (https://www.espressif.com/en/products/modules/esp32). With that module standalone, just apply power and you can stream to a Bluetooth speaker. You can also send audio to most I2S DAC as well as to SPDIF receivers using just a cable or an optical transducer.
Depending on the hardware connected to the esp32, you can send audio to a local DAC, to SPDIF or to a Bluetooth speaker. The bare minimum required hardware is a WROVER module with 4MB of Flash and 4MB of PSRAM (https://www.espressif.com/en/products/modules/esp32). With that module standalone, just apply power and you can stream to a Bluetooth speaker. You can also send audio to most I2S DAC as well as to SPDIF receivers using just a cable or an optical transducer.
But squeezelite-esp32 is highly extensible and you can add
- Buttons and Rotary Encoder and map/combine them to various functions (play, pause, volume, next ...)
- IR receiver (no pullup resistor or capacitor needed, just the 38kHz receiver)
- Monochrome, GrayScale or Color displays using SPI or I2C (supported drivers are SH1106, SSD1306, SSD1322, SSD1326/7, SSD1351, ST7735, ST7789 and ILI9341).
- Ethernet using a Microchip LAN8720 with RMII interface or Davicom DM9051 over SPI.
- [Buttons](#buttons) and [Rotary Encoder](#rotary-encoder) and map/combine them to various functions (play, pause, volume, next ...)
- [Volume Encoder](#volume-rotary-encoder) for a dedicated volume rotary encoder
- [GPIO expander](#gpio-expanders) (buttons, led and rotary)
- [IR receiver](#infrared) (no pullup resistor or capacitor needed, just the 38kHz receiver)
- [Monochrome, GrayScale or Color displays](#display) using SPI or I2C (supported drivers are SH1106, SSD1306, SSD1322, SSD1326/7, SSD1351, ST7735, ST7789 and ILI9341).
- [LED strip](#led-strip) for VU-meter
- [Ethernet](#ethernet) using a Microchip LAN8720 with RMII interface or Davicom DM9051/W5500 over SPI.
Other features include
- Resampling
- 10-bands equalizer
- Resampling (16 bits mode)
- 10-bands equalizer (16 bits mode)
- Automatic initial setup using any WiFi device
- Full web interface for further configuration/management
- Firmware over-the-air update
@@ -51,29 +55,51 @@ The esp32 must run at 240 MHz, with Quad-SPI I/O at 80 MHz and a clock of 40 Mhz
In 16 bits mode, although 192 kHz is reported as max rate, it's highly recommended to limit reported sampling rate to 96k (-Z 96000). Note that some high-speed 24/96k on-line streams might stutter because of TCP/IP stack performances. It is usually due to the fact that the server sends small packets of data and the esp32 cannot receive encoded audio fast enough, regardless of task priority settings (I've tried to tweak that a fair bit). The best option in that case is to let LMS proxy the stream as it will provide larger chunks and a "smoother" stream that can then be handled.
Note as well that some codecs consume more CPU than others or have not been optimized as much. I've done my best to tweak these, but that level of optimization includes writing some assembly which is painful. One very demanding codec is AAC when files are encoded with SBR. It allows reconstruction of upper part of spectrum and thus higher sampling rate, but the codec spec is such that this is optional, you can decode simply lower band and accept lower sampling rate - See the AAC_DISABLE_SBR option below.
**IMPORTANT: on esp32 (not esp32-s3), using Spotify with SPDIF produces stuttering audio when "stats" are enabled. You MUST disable them**
## Supported Hardware
Any esp32-based hardware with at least 4MB of flash and 4MB of PSRAM will be capable of running squeezelite-esp32 and there are various boards that include such chip. A few are mentionned below, but any should work. You can find various help & instructions [here](https://forums.slimdevices.com/showthread.php?112697-ANNOUNCE-Squeezelite-ESP32-(dedicated-thread))
**For the sake of clarity, WROOM modules DO NOT work as they don't include PSRAM. Some designs might add it externally, but it's (very) unlikely.**
### Raw WROVER module
Per above description, a [WROVER module](https://www.espressif.com/en/products/modules/esp32) is enough to run Squeezelite-esp32, but that requires a bit of tinkering to extend it to have analogue audio or hardware buttons (e.g.)
Please note that when sending to a Bluetooth speaker (source), only 44.1 kHz can be used, so you either let LMS do the resampling, but you must make sure it only sends 44.1kHz tracks or enable internal resampling (using -R) option. If you connect a DAC, choice of sample rates will depends on its capabilities. See below for more details.
Most DAC will work out-of-the-box with simply an I2S connection, but some require specific commands to be sent using I2C. See DAC option below to understand how to send these dedicated commands. There is build-in support for TAS575x, TAS5780, TAS5713 and AC101 DAC.
### Raw WROOM esp32-s3 module
The esp32-s3 based modules like [this](https://www.espressif.com/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_en.pdf) are also supported but requires esp-idf 4.4. It is not yet part of official releases, but it compiles & runs. The s3 does not have bluetooth audio. Note that CPU performances are greatly enhanced.
### SqueezeAMP
This is the main hardware companion of Squeezelite-esp32 and has been developped together. Details on capabilities can be found [here](https://forums.slimdevices.com/showthread.php?110926-pre-ANNOUNCE-SqueezeAMP-and-SqueezeliteESP32) and [here](https://github.com/philippe44/SqueezeAMP).
if you want to rebuild, use the `squeezelite-esp32-SqueezeAmp-sdkconfig.defaults` configuration file.
If you want to rebuild, use the `squeezelite-esp32-SqueezeAmp-sdkconfig.defaults` configuration file.
NB: You can use the pre-build binaries SqueezeAMP4MBFlash which has all the hardware I/O set properly. You can also use the generic binary I2S4MBFlash in which case the NVS parameters shall be set to get the exact same behavior
- set_GPIO: 12=green,13=red,34=jack,2=spkfault
- batt_config: channel=7,scale=20.24
- dac_config: model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0
- spdif_config: bck=33,ws=25,do=15
- set_GPIO: `12=green,13=red,34=jack,2=spkfault`
- bat_config: `channel=7,scale=20.24`
- dac_config: `model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0`
- spdif_config: `bck=33,ws=25,do=15`
The IR can be used as a wake-up signal using (setting `sleep_config` with `wake=0:0`). It's a pull-up so it stays at 1 when not receiving anything which means it cannot be used in conjuction with other wake-up IOs. See [Sleeping](#sleeping) for more details regarding the limitation of waking-up upon multiple inputs.
### MuseLuxe
This portable battery-powered [speaker](https://raspiaudio.com/produit/esp-muse-luxe) is compatible with squeezelite-esp32 for which there is a dedicated build supplied with every update. If you want to rebuild, use the `squeezelite-esp32-Muse-sdkconfig.defaults` configuration file.
NB: You can use the pre-build binaries Muse4MBFlash which has all the hardware I/O set properly. You can also use the generic binary I2S4MBFlash in which case the NVS parameters shall be set to get the exact same behavior
- target: `muse`
- bat_config: `channel=5,scale=7.48,atten=3,cells=1`
- spi_config: `"mosi=15,miso=2,clk=14` *(this one is probably optional)*
- dac_config: `model=I2S,bck=5,ws=25,do=26,di=35,i2c=16,sda=18,scl=23,mck=0`
- dac_controlset: `{"init":[ {"reg":0,"val":128}, {"reg":0,"val":0}, {"reg":25,"val":4}, {"reg":1,"val":80}, {"reg":2,"val":0}, {"reg":8,"val":0}, {"reg":4,"val":192}, {"reg":0,"val":18}, {"reg":1,"val":0}, {"reg":23,"val":24}, {"reg":24,"val":2}, {"reg":38,"val":9}, {"reg":39,"val":144}, {"reg":42,"val":144}, {"reg":43,"val":128}, {"reg":45,"val":128}, {"reg":27,"val":0}, {"reg":26,"val":0}, {"reg":2,"val":240}, {"reg":2,"val":0}, {"reg":29,"val":28}, {"reg":4,"val":48}, {"reg":25,"val":0}, {"reg":46,"val":33}, {"reg":47,"val":33} ]}`
- actrls_config: buttons
- define a "buttons" variable with: `[{"gpio":32, "pull":true, "debounce":10, "normal":{"pressed":"ACTRLS_VOLDOWN"}}, {"gpio":19, "pull":true, "debounce":40, "normal":{"pressed":"ACTRLS_VOLUP"}}, {"gpio":12, "pull":true, "debounce":40, "long_press":1000, "normal":{"pressed":"ACTRLS_TOGGLE"},"longpress":{"pressed":"ACTRLS_POWER"}}]`
### ESP32-A1S
Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4001060963585.html) or an external amplifier if you want direct speaker connection. Note that there is a version with AC101 codec and another one with ES8388 (see below)
Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://aliexpress.com/item/4000130915903.html) or an external amplifier if you want direct speaker connection. Note that there is a version with AC101 codec and another one with ES8388 with probably two variants - these boards are a mess (see below)
The board shown above has the following IO set
- amplifier: GPIO21
@@ -89,25 +115,32 @@ The board shown above has the following IO set
(note that some GPIO need pullups)
So a possible config would be
- set_GPIO: 21=amp,22=green:0,39=jack:0
- set_GPIO: `21=amp,22=green:0,39=jack:0`
- a button mapping:
```
[{"gpio":5,"normal":{"pressed":"ACTRLS_TOGGLE"}},{"gpio":18,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLUP"}, "shifted":{"pressed":"ACTRLS_NEXT"}}, {"gpio":23,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLDOWN"},"shifted":{"pressed":"ACTRLS_PREV"}}]
```
for AC101
- dac_config: model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32
```json
[{"gpio":5,"normal":{"pressed":"ACTRLS_TOGGLE"}},{"gpio":18,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLUP"}, "shifted":{"pressed":"ACTRLS_NEXT"}}, {"gpio":23,"pull":true,"shifter_gpio":5,"normal":{"pressed":"ACTRLS_VOLDOWN"},"shifted":{"pressed":"ACTRLS_PREV"}}]
```
for **AC101**
- dac_config: `model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32`
for ES8388
- dac_config: model=ES8388,bck=5,ws=25,do=26,sda=18,scl=23,i2c=16
### T-WATCH2020 by LilyGo
This is a fun [smartwatch](http://www.lilygo.cn/prod_view.aspx?TypeId=50036&Id=1290&FId=t3:50036:3) based on ESP32. It has a 240x240 ST7789 screen and onboard audio. Not very useful to listen to anything but it works. This is an example of a device that requires an I2C set of commands for its dac (see below). There is a build-option if you decide to rebuild everything by yourself, otherwise the I2S default option works with the following parameters
for **ES8388** (it seems that there are variants with same version number - a total mess)
- dac_config: `model=ES8388,bck=5,ws=25,do=26,sda=18,scl=23,i2c=16`
or
- dac_config: `model=ES8388,bck=27,ws=25,do=26,sda=33,scl=32,i2c=16`
### T-WATCH2020 by LilyGo
This is a fun [smartwatch](http://www.lilygo.cn/prod_view.aspx?TypeId=50036&Id=1290&FId=t3:50036:3) based on ESP32. It has a 240x240 ST7789 screen and onboard audio. Not very useful to listen to anything but it works. This is an example of a device that requires an I2C set of commands for its DAC/APU (see below). There is a build-option if you decide to rebuild everything by yourself, otherwise the I2S default option works with the following parameters
- dac_config: `model=I2S,bck=26,ws=25,do=33,i2c=53,sda=21,scl=22`
- dac_controlset:
```json
{ "init": [ {"reg":41, "val":128}, {"reg":18, "val":255} ], "poweron": [ {"reg":18, "val":64, "mode":"or"} ], "poweroff": [ {"reg":18, "val":191, "mode":"and"} ] }
```
- spi_config: `dc=27,data=19,clk=18`
- display_config: `SPI,driver=ST7789,width=240,height=240,cs=5,back=12,speed=16000000,HFlip,VFlip`
- dac_config: model=I2S,bck=26,ws=25,do=33,i2c=106,sda=21,scl=22
- dac_controlset: { "init": [ {"reg":41, "val":128}, {"reg":18, "val":255} ], "poweron": [ {"reg":18, "val":64, "mode":"or"} ], "poweroff": [ {"reg":18, "val":191, "mode":"and"} ] }
- spi_config: dc=27,data=19,clk=18
- display_config: SPI,driver=ST7789,width=240,height=240,cs=5,back=12,speed=16000000,HFlip,VFlip
### ESP32-WROVER + I2S DAC
Squeezelite-esp32 requires esp32 chipset and 4MB PSRAM. ESP32-WROVER meets these requirements. To get an audio output an I2S DAC can be used. Cheap PCM5102 I2S DACs work others may also work. PCM5012 DACs can be hooked up via:
Squeezelite-esp32 requires esp32 chipset and 4MB PSRAM. ESP32-WROVER meets these requirements. To get an audio output an I2S DAC can be used. Cheap PCM5102 I2S DACs work but many others also do. PCM5012 DACs can be hooked up via:
I2S - WROVER
VCC - 3.3V
@@ -131,50 +164,66 @@ And the super cool project https://github.com/rochuck/squeeze-amp-too
## Configuration
To access NVS, in the webUI, go to credits and select "shows nvs editor". Go into the NVS editor tab to change NFS parameters. In syntax description below \<\> means a value while \[\] describe optional parameters.
As mentionned above, there are a few dedicated builds that are provided today: SqueezeAMP and Muse but if you build it yourself, you can also create a build for T-WATCH2020. The default build is a generic firmware named I2S which can be configured through NVS to produce *exactly* the same results than dedicated builds. The difference is that parameters must be entered and can accidently be erased. The GUI provides a great help to load "known config sets" as well.
By design choice, there is no code that is only embedded for a given version, all code is always there. The philosophy is to minimize as much as possible platform-specific code and use of specific `#ifdef` is prohibited, no matter what. So if you want to add your own platfrom, please look **very hard** at the `main\KConfig.projbuild` to see how you can, using parameters below, make your device purely a configuration-based solution. When there is really no other option, look at `targets\<target>` to add your own code. I will not accept PR for code that can avoid creating such dedicated code whenever possible. The NVS "target" will be used to call target-specific code then, but again this is purely runtime, not compile-time.
### I2C
The NVS parameter "i2c_config" set the i2c's gpio used for generic purpose (e.g. display). Leave it blank to disable I2C usage. Note that on SqueezeAMP, port must be 1. Default speed is 400000 but some display can do up to 800000 or more. Syntax is
```
sda=<gpio>,scl=<gpio>[,port=0|1][,speed=<speed>]
```
<strong>Please note that you can not use the same GPIO or port as the DAC</strong>
**Please note that you can not use the same GPIO or port as the DAC.**
### SPI
The esp32 has 4 SPI sub-systems, one is unaccessible so numbering is 0..2 and SPI0 is reserved for Flash/PSRAM. The NVS parameter "spi_config" set the spi's gpio used for generic purpose (e.g. display). Leave it blank to disable SPI usage. The DC parameter is needed for displays. Syntax is
```
data|mosi=<gpio>,clk=<gpio>[,dc=<gpio>][,host=1|2][,miso=<gpio>]
```
Default "host" is 1. The "miso" parameter is only used when SPI bus is to be shared with other peripheral (e.g. ethernet, see below), otherwise it can be omitted. Note that "data" can also be named "mosi".
Default and only "host" is 1 as others are used already by flash and spiram. The optional "miso" (MasterInSlaveOut) parameter is only used when SPI bus is bi-directional and shared with other peripheral like ethernet, gpio expander. Note that "data" can also be named "mosi" (MasterOutSlaveIn).
### DAC/I2S
The NVS parameter "dac_config" set the gpio used for i2s communication with your DAC. You can define the defaults at compile time but nvs parameter takes precedence except for SqueezeAMP and A1S where these are forced at runtime. Syntax is
The NVS parameter "dac_config" set the gpio used for i2s communication with your DAC. You can define the defaults at compile time but nvs parameter takes precedence except for named configurations
```
bck=<gpio>,ws=<gpio>,do=<gpio>[,mck][,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|AC101|I2S][,sda=<gpio>,scl=gpio[,i2c=<addr>]]
bck=<gpio>,ws=<gpio>,do=<gpio>[,mck=0|1|2][,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|AC101|WM8978|ES8388|I2S][,sda=<gpio>,scl=<gpio>[,i2c=<addr>]]
```
if "model" is not set or is not recognized, then default "I2S" is used. The option "mck" is used for some codecs that require a master clock (although they should not). Only GPIO0 can be used as MCLK and be aware that this cannot coexit with RMII Ethernet (see ethernet section below). I2C parameters are optional and only needed if your DAC requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed.
if "model" is not set or is not recognized, then default "I2S" is used. The option "mck" is used for some codecs that require a master clock (although they should not). By default GPIO0 is used as MCLK and only recent builds (post mid-2023) can use 1 or 2. Also be aware that this cannot coexit with RMII Ethernet (see ethernet section below). I2C parameters are optional and only needed if your DAC requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed.
So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power on and off using a JSON syntax:
So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power, speaker and headset on and off using a JSON syntax:
```json
{ <command>: [ <item1>, <item2>, ... <item3> ],
<command>: [ <item1>, <item2>, ... <item3> ],
... }
```
{ init: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
poweron: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
poweroff: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ] }
Where `<command>` is one of init, poweron, poweroff, speakeron, speakeroff, headseton, headsetoff (it **must** be an array even for a single item). Item is any of the following elements
```
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here. The parameter 'mode' allows to *or* the register with the value or to *and* it. Don't set 'mode' if you simply want to write. **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax
{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}
{"gpio":<gpio>,"level":0|1}
{"delay":<ms>}
```
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here.
NB: For specific builds (all except I2S), all this is ignored. For know codecs, the built-in sequences can be overwritten using dac_controlset
The `reg` key allow to write registers on i2c bus. The parameter `mode` allows to *or* the register with the value or to *and* it. Don't set `mode` if you simply want to write. The `val` parameter can be an array [v1, v2,...] to write a serie of bytes in a single i2c burst (in that case 'mode' is ignored). **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax. The `gpio` key is simply to set a gpio as part of DAC action and `delay` allows a pause between elements.
The 'power' command is used when powering on/off the DAC after the idle period (see -C option of squeezelite) and the 'speaker/headset' commands are sent when switching between speakers and headsets (see headset jack detection).
NB: For named configurations ((SqueezeAMP, Muse ... all except I2S), all this is ignored. For know codecs, the built-in sequences can be overwritten using dac_controlset
**Please note that you can not use the same GPIO or port as the I2C.**
<strong>Please note that you can not use the same GPIO or port as the I2C</strong>
### SPDIF
The NVS parameter "spdif_config" sets the i2s's gpio needed for SPDIF.
SPDIF is made available by re-using i2s interface in a non-standard way, so although only one pin (DO) is needed, the controller must be fully initialized, so the bit clock (bck) and word clock (ws) must be set as well. As i2s and SPDIF are mutually exclusive, you can reuse the same IO if your hardware allows so.
You can define the defaults at compile time but nvs parameter takes precedence except for SqueezeAMP where these are forced at runtime.
You can define the defaults at compile time but nvs parameter takes precedence except for named configurations (SqueezeAMP, Muse ...)
Leave it blank to disable SPDIF usage, you can also define them at compile time using "make menuconfig". Syntax is
```
bck=<gpio>,ws=<gpio>,do=<gpio>
```
NB: For well-known configuration, this is ignored
NB: For named configurations, this is ignored
To optimize speed, a bit-manipulation trick is used and as a result, the bit depth is limited to 20 bits, even in 32 bits mode. As said before, this is more than enough for any human ear. In theory, it could be extended up to 23 bits but I don't see the need. Now, you can also get SPDIF using a specialized chip that offers a I2S interface like a DAC but spits out SPDIF (optical and coax). Refers to DAC chapter then.
The maximum bit depth is 24 bits, even in 32 bits mode (this a SPDIF limitation - thank @UrbanLienert for theupdate from 20 to 24 bit). Now, you can also get SPDIF using a specialized chip that offers a I2S interface like a DAC but spits out SPDIF (optical and coax). Refers to DAC chapter then.
If you want coax, you can also use a poor-man's trick to generate signal from a 3.3V GPIO. All that does is dividing the 3.3V to generate a 0.6V peak-to-peak and then remove DC
```
@@ -185,26 +234,26 @@ GPIO ----210ohm-----------||---- coax S/PDIF signal out
|
Ground -------------------------- coax signal ground
```
### Display
The NVS parameter "display_config" sets the parameters for an optional display. Syntax is
The NVS parameter "display_config" sets the parameters for an optional display. It can be I2C (see [here](#i2c) for shared bus) or SPI (see [here](#spi) for shared bus) Syntax is
```
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,reset=<gpio>][,HFlip][,VFlip][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789|ILI9341[:16|18][,rotate]][,mode=<mode>]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789[:x=<offset>][:y=<offset>]|ILI9341[:16|18][,rotate]]
```
- back: a LED backlight used by some older devices (ST7735). It is PWM controlled for brightness
- reset: some display have a reset pin that is should normally be pulled up if unused
- reset: some display have a reset pin that is should normally be pulled up if unused. Most displays require reset and will not initialize well otherwise.
- VFlip and HFlip are optional can be used to change display orientation
- rotate: for non-square *drivers*, move to portrait mode. Note that *width* and *height* must be inverted then
- Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz
- mode: Default mode = 0. Some display modules use different transaction line timings. Check the module documentation if a non-standard mode is required.
- SH1106 is 128x64 monochrome I2C/SPI [here]((https://www.waveshare.com/wiki/1.3inch_OLED_HAT))
- SH1106 is 128x64 monochrome I2C/SPI [here](https://www.waveshare.com/wiki/1.3inch_OLED_HAT)
- SSD1306 is 128x32 monochrome I2C/SPI [here](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi)
- SSD1322 is 256x64 grayscale 16-levels SPI in multiple sizes [here](https://www.buydisplay.com/oled-display/oled-display-module?resolution=159) - it is very nice
- SSD1326 is 256x32 monochrome or grayscale 16-levels SPI [here](https://www.aliexpress.com/item/32833603664.html?spm=a2g0o.productlist.0.0.2d19776cyQvsBi&algo_pvid=c7a3db92-e019-4095-8a28-dfdf0a087f98&algo_expid=c7a3db92-e019-4095-8a28-dfdf0a087f98-1&btsid=0ab6f81e15955375483301352e4208&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_)
- SSD1327 is 128x128 16-level grayscale SPI [here](https://www.amazon.com/gp/product/B079N1LLG8/ref=ox_sc_act_title_1?smid=A1N6DLY3NQK2VM&psc=1) - artwork can be up to 96x96 with vertical vu-meter/spectrum
- SSD1351 is 128x128 65k/262k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.5inch-rgb-oled-module.htm)
- SSD1675 is an e-ink paper and is experimental as e-ink is really not suitable for LMS du to its very low refresh rate
- ST7735 is a 128x160 65k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.8inch-lcd-module.htm). This needs a backlight control
- ST7735 is a 128x160 65k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.8inch-lcd-module.htm). This needs a backlight control. Some have X/Y offsets betwen the driver and the glass (green/black/red models) that can be added using "x" and "y" options (case sensitive!)
- ST7789 is a 240x320 65k (262k not enabled) color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/2inch-lcd-module.htm). It also exist with 240x240 displays. See **rotate** for use in portrait mode
- ILI9341 is another 240x320 65k (262k capable) color SPI. I've not used it much, the driver it has been provided by one external contributor to the project
@@ -212,16 +261,15 @@ You can tweak how the vu-meter and spectrum analyzer are displayed, as well as s
The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay and Bluetooth. Syntax is
```
[format=<display_content>][,speed=<speed>][,pause=<pause>]
[format=<display_content>][,speed=<speed>][,pause=<pause>][,artwork[:0|1]]
```
- 'speed' is the scrolling speed in ms (default is 33ms)
- 'pause' is the pause time between scrolls in ms (default is 3600ms)
- 'format' can contain free text and any of the 3 keywords %artist%, %album%, %title%. Using that format string, the keywords are replaced by their value to build the string to be displayed. Note that the plain text following a keyword that happens to be empty during playback of a track will be removed. For example, if you have set format=%artist% - %title% and there is no artist in the metadata then only <title> will be displayed not " - <title>".
- 'format' can contain free text and any of the 3 keywords `%artist%`, `%album%`, `%title%`. Using that format string, the keywords are replaced by their value to build the string to be displayed. Note that the plain text following a keyword that happens to be empty during playback of a track will be removed. For example, if you have set format=`%artist% - %title%` and there is no artist in the metadata then only `<title>` will be displayed not ` - <title>`.
- 'artwork' enables coverart display, if available (does not work for Bluetooth). The optional parameter indicates if the artwork should be resized (1) to fit the available space. Note that the built-in resizer can only do 2,4 and 8 downsizing, so fit is not optimal. The artwork will be placed at the right of the display for landscape displays and underneath the two information lines for others (there is no user option to tweak that).
### Infrared
You can use any IR receiver compatible with NEC protocol (38KHz). Vcc, GND and output are the only pins that need to be connected, no pullup, no filtering capacitor, it's a straight connection.
You can use any IR receiver compatible with NEC protocol (38KHz) or RC5. Vcc, GND and output are the only pins that need to be connected, no pullup, no filtering capacitor, it's a straight connection.
The IR codes are send "as is" to LMS, so only a Logitech SB remote from Boom, Classic or Touch will work. I think the file Slim_Devices_Remote.ir in the "server" directory of LMS can be modified to adapt to other codes, but I've not tried that.
@@ -234,31 +282,69 @@ The parameter "set_GPIO" is used to assign GPIO to various functions.
GPIO can be set to GND provide or Vcc at boot. This is convenient to power devices that consume less than 40mA from the side connector. Be careful because there is no conflict checks being made wrt which GPIO you're changing, so you might damage your board or create a conflict here.
The \<amp\> parameter can use used to assign a GPIO that will be set to active level (default 1) when playback starts. It will be reset when squeezelite becomes idle. The idle timeout is set on the squeezelite command line through -C \<timeout\>
The `<amp>` parameter can use used to assign a GPIO that will be set to active level (default 1) when playback starts. It will be reset when squeezelite becomes idle. The idle timeout is set on the squeezelite command line through `-C <timeout>`
The `<power>` parameter can use used to assign a GPIO that will be set to active level (default 1) when player is powered on and reset when powered off (in LMS, does not apply to AirPlay, Spotify or BT).
If you have an audio jack that supports insertion (use :0 or :1 to set the level when inserted), you can specify which GPIO it's connected to. Using the parameter jack_mutes_amp allows to mute the amp when headset (e.g.) is inserted.
You can set the Green and Red status led as well with their respective active state (:0 or :1)
You can set the Green and Red status led as well with their respective active state (:0 or :1) or specific the chipset if you use addressable RGB led.
The \<ir\> parameter set the GPIO associated to an IR receiver. No need to add pullup or capacitor
The `<ir>` parameter set the GPIO associated to an IR receiver. No need to add pullup or capacitor
Syntax is:
```
<gpio>=Vcc|GND|amp[:1|0]|ir|jack[:0|1]|green[:0|1]|red[:0|1]|spkfault[:0|1][,<repeated sequence for next GPIO>]
<gpio>=Vcc|GND|amp[:1|0]|power[:1:0]|ir[:nec|rc5]|jack[:0|1]|green[:0|1|ws2812]|red[:0|1|ws2812]|spkfault[:0|1][,<repeated sequence for next GPIO>]
```
You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for well-known configurations where these are forced at runtime.
You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for named configurations ((SqueezeAMP, Muse ...) where these are forced at runtime.
**Note that gpio 36 and 39 are input only and cannot use interrupt. When set to jack or speaker fault, a 100ms polling checks their value but that's expensive**
### GPIO expanders
It is possible to add GPIO expanders using I2C or SPI bus. They should mainly be used for buttons but they can support generic-purpose outputs as well. These additional GPIOs can be numbered starting from an arbitrary value (40 and above as esp32 has GPIO 0..39). Then these new "virtual" GPIOs from (e.g) 100 to 115 can be used in [button](#Buttons) configuration, [set_GPIO](#set-gpio) or other config settings.
Each expander can support up to 32 GPIO. To use an expander for buttons, an interrupt must be provided, polling mode is not acceptable. An expander w/o interruption can still be configured, but only output will be usable. Note that the same interrupt can be shared accross expanders, as long as they are using open drain or open collectors (which they probably all do)
The parameter "gpio_exp_config" is a semicolon (;) separated list with following syntax for each expander
```
model=<model>,addr=<addr>,[,port=system|dac][,base=<n>][,count=<n>][,intr=<gpio>][,cs=<gpio>][,speed=<Hz>]
```
- model: pca9535, pca85xx, mcp23017, aw9523 and mcp23s17 (SPI version)
- addr: chip i2c/spi address (decimal)
- port (I2C): use either "system" port (shared with display for example) or "dac" port (system is default)
- cs (SPI): gpio used for Chip Select
- speed (SPI): speed of the SPI bus for that device (in Hz)
- base: GPIO numbering offset to use everywhere else (default 40 on esp32 and 48 on esp32-s3)
- count: number of GPIO of expander (default 16 - might be obsolted if model if sufficient to decide)
- intr: real GPIO to use as interrupt.
Note that PWM ("led_brightness" below) is not supported for expanded GPIOs and they cannot be used for high speed or precise timing signals like CS, D/C, Reset and Ready. Buttons, rotary encoder, amplifier control and power are supported. Depending on the actual chipset, pullup or pulldown might be supported so you might have to add external resistors (only MCP23x17 does pullup). The pca8575 is not a great chip, it generate a fair bit of spurious interrupts when used for GPIO out. When using a SPI expander, the bus must be configured using shared [SPI](#SPI) bus
### LED
See §**set_GPIO** for how to set the green and red LEDs. In addition, their brightness can be controlled using the "led_brigthness" parameter. The syntax is
See [set_GPIO](#set-gpio) for how to set the green and red LEDs (including addressable RGB ones). In addition, their brightness can be controlled using the "led_brigthness" parameter. The syntax is
```
[green=0..100][,red=0..100]
```
NB: For well-known configuration, this is ignored
### Rotary Encoder
One rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.
NB: For named configuration, GPIO affected to green and red LED cannot be changed but brightness option applies
Encoder is normally hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT and AirPlay. Using the option 'volume' makes it hard-coded to volume down/up/play toggle all the time (even in LMS). The option 'longpress' allows an alternate mode when SW is long-pressed. In that mode, left is previous, right is next and press is toggle. Every long press on SW alternates between modes (the main mode actual behavior depends on 'volume').
### LED Strip
One LED strip with up to 255 addressable LEDs can be configured to offer enhanced visualizations. The VU Meter visualizer includes a battery status indicator (see Battery). Currently only WS2812B LEDs are supported. Set the LED Strip hardware configuration, or the NVS led_vu_config syntax is
```
type=[WS2812],length=<n>,gpio=<dataPin>[,scale=<gain>]
```
where `<n>` is the number of LEDs in the strip (1..255). A `<scale>` gain value (percentage) can be added to enhance effect responses.
The latest LMS plugin update is required to set the visualizer mode and brightness in the ESP32 Settings page for the player, or a controllable display (see Extra/SqueezeESP32 menus). The plugin adds additional LMS CLI commands.
| Command | Notes |
| -------------------------------------------------- | ----------- |
| \<playerid\> led_visual \[\<mode\>\] \[\<brightness\>\] | Toggles or selects the visualizer "mode".<br />The visualizer brightness(0..255) can be controlled using the "brightness" tag. |
| \<playerid\> dmx \<R,G,B,R,G,B, ... R,G,B\> \[\<offset\>\] | Sets the LED color starting at position "offset"<br /> with "R"(red),"G"(green),and "B"(blue) color sequences.<br />Add additional RGB values to the delimited string to set multiple LEDs.<br /> |
### Rotary Encoder
One general rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.
Encoder is normally hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT, AirPlay and Spotify. Using the option 'volume' makes it hard-coded to volume down/up/play toggle all the time (even in LMS). The option 'longpress' allows an alternate mode when SW is long-pressed. In that mode, left is previous, right is next and press is toggle. Every long press on SW alternates between modes (the main mode actual behavior depends on 'volume').
There is also the possibility to use 'knobonly' option (exclusive with 'volume' and 'longpress'). This mode attempts to offer a single knob full navigation which is a bit contorded due to LMS UI's principles. Left, Right and Press obey to LMS's navigation rules and especially Press always goes to lower submenu item, even when navigating in the Music Library. That causes a challenge as there is no 'Play', 'Back' or 'Pause' button. Workaround are as of below:
- longpress is 'Play'
@@ -279,15 +365,25 @@ The SW gpio is optional, you can re-affect it to a pure button if you prefer but
See also the "IMPORTANT NOTE" on the "Buttons" section and remember that when 'lms_ctrls_raw' (see below) is activated, none of these knobonly,volume,longpress options apply, raw button codes (not actions) are simply sent to LMS
**Note that gpio 36 and 39 are input only and cannot use interrupt, so they cannot be set to A or B. When using them for SW, a 100ms polling is used which is expensive**
**Note that on esp32, gpio 36 and 39 are input only and cannot use interrupt, so they cannot be set to A or B. When using them for SW, a 100ms polling is used which is expensive**
### Volume Rotary Encoder
One dedicated volume rotary encoder is supported, quadrature shift with press. Encoder is hard-coded to volume-up, down and play toggle for LMS, BT, AirPlay and Spotify (see note above for filtering and HW note as well GPIO 36 and 39 on esp32)
Use parameter volume_rotary with the following syntax:
```
A=<gpio>,B=<gpio>[,SW=gpio>]
```
### Buttons
Buttons are described using a JSON string with the following syntax
```
```json
[
{"gpio":<num>,
"type":"BUTTON_LOW | BUTTON_HIGH",
{"gpio":<num>,
"type":"BUTTON_LOW | BUTTON_HIGH",
"pull":[true|false],
"long_press":<ms>,
"long_press":<ms>,
"debounce":<ms>,
"shifter_gpio":<-1|num>,
"normal": {"pressed":"<action>","released":"<action>"},
@@ -311,20 +407,22 @@ Where (all parameters are optionals except gpio)
- "shifted": action to take when a button is pressed/released and shifted (see above/below)
- "longshifted": action to take when a button is long-pressed/released and shifted (see above/below)
Where \<action\> is either the name of another configuration to load (remap) or one amongst
Where `<action>` is either the name of another configuration to load (remap) or one amongst
```
ACTRLS_NONE, ACTRLS_POWER, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY,
ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT,
BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT,
BCTRLS_PS1, BCTRLS_PS2, BCTRLS_PS3, BCTRLS_PS4, BCTRLS_PS5, BCTRLS_PS6,
KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
BCTRLS_PS1, BCTRLS_PS2, BCTRLS_PS3, BCTRLS_PS4, BCTRLS_PS5, BCTRLS_PS6, BCTRLS_PS7, BCTRLS_PS8, BCTRLS_PS9, BCTRLS_PS10,
KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
ACTRLS_SLEEP,
```
Note that ACTRLS_SLEEP is not an actual button that can be sent to LMS, but it's a hook to activate deep sleep mode (see [Sleeping](#sleeping)).
One you've created such a string, use it to fill a new NVS parameter with any name below 16(?) characters. You can have as many of these configs as you can. Then set the config parameter "actrls_config" with the name of your default config
For example a config named "buttons" :
```
```json
[{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,"normal":{"pressed":"ACTRLS_VOLDOWN"},"longpress":{"pressed":"buttons_remap"}},
{"gpio":5,"type":"BUTTON_LOW","pull":true,"shifter_gpio":4,"normal":{"pressed":"ACTRLS_VOLUP"}, "shifted":{"pressed":"ACTRLS_TOGGLE"}}]
```
@@ -333,7 +431,7 @@ Defines two buttons
- second on GPIO 5, active low. When pressed it triggers a volume up command. If first button is pressed together with this button, then a play/pause toggle command is generated.
While the config named "buttons_remap"
```
```json
[{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,"normal":{"pressed":"BCTRLS_DOWN"},"longpress":{"pressed":"buttons"}},
{"gpio":5,"type":"BUTTON_LOW","pull":true,"shifter_gpio":4,"normal":{"pressed":"BCTRLS_UP"}}]
```
@@ -341,10 +439,14 @@ Defines two buttons
- first on GPIO 4, active low. When pressed, it triggers a navigation down command. When pressed more than 1000ms, it changes the button configuration for the one described above
- second on GPIO 5, active low. When pressed it triggers a navigation up command. That button, in that configuration, has no shift option
Below is a difficult but functional 2-buttons interface for your decoding pleasure
Below is a difficult but functional 2-buttons interface for your decoding pleasure:
*buttons*
`actrls_config`:
```
buttons
```
`buttons`:
```json
[{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,
"normal":{"pressed":"ACTRLS_VOLDOWN"},
"longpress":{"pressed":"buttons_remap"}},
@@ -354,8 +456,8 @@ Below is a difficult but functional 2-buttons interface for your decoding pleasu
"longpress":{"pressed":"ACTRLS_NEXT"}}
]
```
*buttons_remap*
```
`buttons_remap`:
```json
[{"gpio":4,"type":"BUTTON_LOW","pull":true,"long_press":1000,
"normal":{"pressed":"BCTRLS_DOWN"},
"longpress":{"pressed":"buttons"}},
@@ -366,18 +468,20 @@ Below is a difficult but functional 2-buttons interface for your decoding pleasu
"longshifted":{"pressed":"BCTRLS_LEFT"}}
]
```
<strong>IMPORTANT NOTE</strong>: LMS also supports the possibility to send 'raw' button codes. It's a bit complicated, so bear with me. Buttons can either be processed by SqueezeESP32 and mapped to a "function" like play/pause or they can be just sent to LMS as plain (raw) code and the full logic of press/release/longpress is handled by LMS, you don't have any control on that.
**IMPORTANT NOTE**: LMS also supports the possibility to send 'raw' button codes. It's a bit complicated, so bear with me. Buttons can either be processed by SqueezeESP32 and mapped to a "function" like play/pause or they can be just sent to LMS as plain (raw) code and the full logic of press/release/longpress is handled by LMS, you don't have any control on that.
The benefit of the "raw" mode is that you can build a player which is as close as possible to a Boom (e.g.) but you can't use the remapping function nor longress or shift logics to do your own mapping when you have a limited set of buttons. In 'raw' mode, all you really need to define is the mapping between the gpio and the button. As far as LMS is concerned, any other option in these JSON payloads does not matter. Now, when you use BT or AirPlay, the full JSON construct described above fully applies, so the shift, longpress, remapping options still work.
The benefit of the "raw" mode is that you can build a player which is as close as possible to a Boom (e.g.) but you can't use the remapping function nor longpress or shift logics to do your own mapping when you have a limited set of buttons. In 'raw' mode, all you really need to define is the mapping between the gpio and the button. As far as LMS is concerned, any other option in these JSON payloads does not matter. Now, when you use BT or AirPlay, the full JSON construct described above fully applies, so the shift, longpress, remapping options still work.
**Be aware that when using non "raw" mode, the CLI (Command Line Interface) of LMS is used and *must* be available without password**
There is no good or bad option, it's your choice. Use the NVS parameter "lms_ctrls_raw" to change that option
**Note that gpio 36 and 39 are input only and cannot use interrupt. When using them for a button, a 100ms polling is started which is expensive. Long press is also likely to not work very well**
### Ethernet (coming soon)
Wired ethernet is supported by esp32 with various options but squeezelite is only supporting a Microchip LAN8720 with a RMII interface like [this](https://www.aliexpress.com/item/32858432526.html) or Davicom DM9051 over SPI like [that](https://www.amazon.com/dp/B08JLFWX9Z).
### Ethernet
Wired ethernet is supported by esp32 with various options but squeezeESP32 is only supporting a Microchip LAN8720 with a RMII interface like [this](https://www.aliexpress.com/item/32858432526.html) or SPI-ethernet bridges like Davicom DM9051 [that](https://www.amazon.com/dp/B08JLFWX9Z) or W5500 like [this](https://www.aliexpress.com/item/32312441357.html).
**Note:** Touch buttons that can be find on some board like the LyraT V4.3 are not supported currently.
#### RMII (LAN8720)
- RMII PHY wiring is fixed and can not be changed
@@ -389,6 +493,7 @@ Wired ethernet is supported by esp32 with various options but squeezelite is onl
| GPIO25 | RX0 | EMAC_RXD0 |
| GPIO26 | RX1 | EMAC_RXD1 |
| GPIO27 | CRS_DV | EMAC_RX_DRV |
| GPIO0 | REF_CLK | 50MHz clock |
- SMI (Serial Management Interface) wiring is not fixed and you can change it either in the configuration or using "eth_config" parameter with the following syntax:
```
@@ -397,31 +502,62 @@ model=lan8720,mdc=<gpio>,mdio=<gpio>[,rst=<gpio>]
Connecting a reset pin for the LAN8720 is optional but recommended to avoid that GPIO0 (50MHz input clock) locks the esp32 in download mode at boot time.
- Clock
The APLL of the esp32 is required for the audio codec, so we **need** a LAN8720 that provides a 50MHz clock. That clock **must** be connected to GPIO0, there is no alternative. This means that if your DAC requires an MCLK, then you are out of luck. It is not possible to have both to work together. There might be some workaround using CLK_OUT2 and GPIO3, but I don't have time for this.
#### SPI (DM9051)
Ethernet over SPI is supported as well and requires less GPIOs but is obvsiously slower. Another benefit is that the SPI bus can be shared with the display, but it's also possible to have a dedicated SPI interface. The esp32 has 4 SPI sub-systems, one is unaccessible so numbering is 0..2 and SPI0 is reserved for Flash/PSRAM. The "eth_config" parameter syntax becomes:
The APLL of the esp32 is required for the audio codec, so we **need** a LAN8720 that provides a 50MHz clock. That clock **must** be connected to GPIO0, there is no alternative. This means that if your DAC requires an MCLK, you need a recent build (later than mid-2023) to be able to select either GPIO 1 or 2.
#### SPI (DM9051 or W5500)
Ethernet over SPI is supported as well and requires less GPIOs but is obvsiously slower. SPI is the shared bus set with [spi_config](#spi). The "eth_config" parameter syntax becomes:
```
model=dm9051,cs=<gpio>,speed=<clk_in_Hz>,intr=<gpio>[,host=<-1|1|2>][,rst=<gpio>][,mosi=<gpio>,miso=<gpio>,clk=<gpio>]
model=dm9051|w5500,cs=<gpio>,speed=<clk_in_Hz>,intr=<gpio>[,rst=<gpio>]
```
- To use the system SPI, shared with display (see spi_config) "host" must be set to -1. Any other value will reserve the SPI interface (careful of conflict with spi_config). The default "host" is 2 to avoid conflicting wiht default "spi_config" settings.
- When not using system SPI, "mosi" for data out, "miso" for data in and "clk" **must** be set
- The esp32 has a special I/O multiplexer for faster speed (up to 80 MHz) but that requires using specific GPIOs, which depends on SPI bus (See [here](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html) for more details)
| Pin Name | SPI2 | SPI3 |
| Pin Name | SPI1 | SPI2 |
| -------- | ---- | ---- |
| CS0* | 15 | 5 |
| CS | 15 | 5 |
| SCLK | 14 | 18 |
| MISO | 12 | 19 |
| MOSI | 13 | 23 |
** THIS IS NOT AVAILABLE YET, SO MORE TO COME ON HOW TO USE WIRED ETHERNET***
### Battery / ADC
The NVS parameter "bat_config" sets the ADC1 channel used to measure battery/DC voltage. The "atten" value attenuates the input voltage to the ADC input (the read value maintains a 0-1V rage) where: 0=no attenuation(0..800mV), 1=2.5dB attenuation(0..1.1V), 2=6dB attenuation(0..1.35V), 3=11dB attenuation(0..2.6V). Scale is a float ratio applied to every sample of the 12 bits ADC. A measure is taken every 10s and an average is made every 5 minutes (not a sliding window). Syntax is
```
channel=0..7,scale=<scale>,cells=<2|3>[,atten=<0|1|2|3>]
channel=0..7,scale=<scale>,cells=<1..3>[,atten=<0|1|2|3>]
```
NB: Set parameter to empty to disable battery reading. For well-known configuration, this is ignored (except for SqueezeAMP where number of cells is required)
# Configuration
NB: Set parameter to empty to disable battery reading. For named configurations (SqueezeAMP, Muse ...), this is ignored (except for SqueezeAMP where number of cells is required)
### Sleeping
The esp32 can be put in deep sleep mode to save some power. How much really depends on the connected periperals, so best is to do your own measures. Waking-up from deep sleep is the equivalent of a reboot, but as the chip takes a few seconds to connect, it's still an efficient process.
The esp32 can enter deep sleep after an audio inactivity timeout, after a button has been pressed, after a GPIO is set to a given level (there is a subtle difference, see below) or if the battery reaches a threashold. It wakes up only on some GPIO events. Note that *all* GPIO are isolated when sleeping (unless they are set with the `rtc`option) so you can not assume anything about their value, except that they will not drain current. The `rtc` option allows to keep some GPIO (from the RTC domain only) either pulled up or down. This can be useful if you want to keep some periperal active, for example a GPIO expander whose interrupt will be used to wake-up the system.
The NVS parameter `sleep_config` is mostly used for setting sleep conditions
```
[delay=<mins>][,sleep=<gpio>[:0|1]][,wake=<gpio>[:0|1][|<gpio>[:0|1]...][,rtc=<gpio>[:0|1][|<gpio>[:0|1]...][,batt=<voltage>][,spurious=<mins>]
```
- delay: inactivity in **minutes** before going to sleep
- spurious: when using IR, wake-up can be triggered by any activity on the allocated GPIO, hence other remotes may cause unwanted wake-up. This sets (in **minutes** - default is 1) an inactivity delay after which sleep resumes.
- sleep: GPIO that will put the system into sleep and it can be a level 0 or 1.
- wake: **list** of GPIOs that with cause it to wake up (reboot) with their respective values. In such list, GPIO's are separated by an actual '|'.
- batt: threshold in **volts** under which the system will enter into sleep.
The battery voltage is measured every 10 seconds and 30 values are averaged before producing a result. The result must be 3 times below the threshold to enter sleep, so it takes a total of 10\*30\*3 = 15 minutes.
Be mindful that if the same GPIO is used to go to sleep and wakeup with the *same* level (in other word it's a transition/edge that triggers the action) the above will not work and the esp32 will immediately restart. In such case, you case use a button definition. The benefit of buttons is that not only can you re-use one actual button (e.g. 'stop') to make it the sleep trigger (using a long-press or a shift-press) but by selecting the ACTRLS_SLEEP action upon 'release', you can got to sleep upon release (1-0-1 transition) but also wake up upon another press (0 level applied on GPIO) because you only go to sleep *after* the GPIO returned to 1.
Please see [buttons](#buttons) for detailed syntax.
The option to use multiple GPIOs is very limited on esp32 and the esp-idf 4.3.x we are using: it is only possible to wake-up when **any** of the defined GPIO is set to 1. The fact that you can specify different levels in the wake list is irrelevant for now, it's just a provision for future upgrades to more recent versions of esp-idf.
**Only the following GPIOs can be used to wake-up the esp32**
- ESP32: 0, 2, 4, 12-15, 25-27, 32-39;
- ESP32-S3: 0-21.
Some have asked for a soft power on/off option. Although this is not built-in, it's easy to create yours as long as the regulator/power supply of the board can be controlled by Vcc or GND. Depending on how it is active, add a pull-up/down resistor to the regulator's control and connect it also to one GPIO of the esp32. Then using set_GPIO, set that GPIO to Vcc or GND. Use a hardware button that forces the regulator on with a pull- up/down and once the esp32 has booted, it will force the GPIO to the desired value maintaining the board on by software. To power it off by software, just use the deep sleep option which will suspend all GPIO hence switching off the regulator.
# Software configuration
## Setup WiFi
- Boot the esp, look for a new wifi access point showing up and connect to it. Default build ssid and passwords are "squeezelite"/"squeezelite".
- Once connected, navigate to 192.168.4.1
@@ -430,7 +566,6 @@ NB: Set parameter to empty to disable battery reading. For well-known configurat
- Once connection is established, note down the address the device received; this is the address you will use to configure it going forward
## Setup squeezelite command line (optional)
At this point, the device should have disabled its built-in access point and should be connected to a known WiFi network.
- navigate to the address that was noted in step #1
- Using the list of predefined options, choose the mode in which you want squeezelite to start
@@ -442,8 +577,16 @@ At this point, the device should have disabled its built-in access point and sho
- The toggle switch should be set to 'ON' to ensure that squeezelite is active after booting (you might have to fiddle with it a few times)
- You can enable accessto NVS parameters under 'credits'
## Monitor
## Spotify
By default, SqueezeESP32 will use ZeroConf to advertise its Spotify capabilties. This means that until at least one local Spotify Connect application controllers discovers and connects to it, SqueezeESP32 will not be registered to Spotify servers. As a consequence, Spotify's WebAPI will not be able to see it (for example, Home Assistant services will miss it). Once you are connected to it using for example Spotify Desktop app, it will be registered and displayed everywhere.
If you want the player to be registered at start-up, you need to disable the ZeroConf option using the WebUI or `cspot_config::ZeroConf`. In that mode, the first time you run SqueezeESP32, it will be in ZeroConf mode and when you connect to it using a controller for the firt time, it receives and store credentials that will be used next time (after reboot).
Set ZeroConf to 1 will always force ZeroConf mode to be used.
The ZeroConf mode consumes less memory as it uses the built-in HTTP and mDNS servers to broadcast its capabilities. A Spotify controller will then discover these and trigger the SqueezeESP32 Spotify stack (cspot) to start. When the controller disconnects, the stack is shut down. In non-ZeroConf mode, the stack starts immediately (providing stored credentials are valid) and always run - a disconnect will not shut it down.
## Monitor
In addition of the esp-idf serial link monitor option, you can also enable a telnet server (see NVS parameters) where you'll have access to a ton of logs of what's happening inside the WROVER.
## Update Squeezelite
@@ -468,37 +611,51 @@ For example, so use a BT speaker named MySpeaker, accept audio up to 192kHz and
squeezelite -o "BT -n 'BT <sinkname>'" -b 500:2000 -R -u m -Z 192000 -r "44100-44100"
See squeezlite command line, but keys options are
See squeezelite command line, but keys options are
- Z <rate> : tell LMS what is the max sample rate supported before LMS resamples
- R (see above)
- r "<minrate>-<maxrate>"
- C <sec> : set timeout to switch off amp gpio
- W : activate WAV and AIFF header parsing
- s <name>|-disable: connect to a specific server. Use -disable to not search for any server
**There is a safety feature to protect against WiFi/LMS connection loss that forces a reboot every few minutes when there is no LMS server detected. In case you don't want to use LMS at all, please set the server name to "-disable" on squeezelite command line ("-s -disable")**
# Building everything yourself
## Setting up ESP-IDF
### Docker
A simple alternative to building the project's binaries is to leverage the same docker image that is being used on the GitHub Actions to build our releases. The instructions below assume that you have cloned the squeezelite-esp32 code that you want to build locally and that you have opened a command line/bash session in the folder that contains the code.
Pull the most recent docker image for the environment:
```
docker pull sle118/squeezelite-esp32-idfv4-master
docker pull sle118/squeezelite-esp32-idfv435
```
Then run the container interactively :
```
for windows:
docker run -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv4-master
docker run -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv435
for linux:
docker run -it -v `pwd`:/workspace/squeezelite-esp32 sle118/squeezelite-esp32-idfv4-master
docker run -it -v `pwd`:/workspace/squeezelite-esp32 sle118/squeezelite-esp32-idfv435
```
The above command will mount this repo into the docker container and start a bash terminal. From there, simply run idf.py build to build, etc. Note that at the time of writing these lines, flashing is not possible for docker running under windows https://github.com/docker/for-win/issues/1018.
### Manual Install of ESP-IDF
You can install IDF manually on Linux or Windows (using the Subsystem for Linux) following the instructions at: https://www.instructables.com/id/ESP32-Development-on-Windows-Subsystem-for-Linux/ or see here https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html for a direct install.
First you need git and python (e.g 3.10.x), install these and let it add to system path.
**Use the esp-idf 4.0 https://github.com/espressif/esp-idf/tree/release/v4.0 and a recent add esp-dsp (after 08/2020)**
## Building Squeezelite-esp32
When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git`
**Use the esp-idf 4.3.5 https://github.com/espressif/esp-idf/tree/release/v4.3.5 ** or the 4.4.5 (and above version) if you want to build for esp32-s3. You should clone recursively the whole branch (at the version you need) `git clone -b v4.3.5 https://github.com/espressif/esp-idf --recursive`and run the installer (`install.bat [esp32[,esp32s3]]` from there. Some Windows version (at least) have now a SSL certificate issue. You can workaround this by editing idf-tools.py and adding the following under ìmport ssl`
```
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
```
And because the fun never ends, some Windows installations might fail to build a few files and spit a tons of errors on the output. It seems that the cache of the compile is a problem, so try to disable it by running `idf.py --no-ccache build` (I know...)
## Building SqueezeESP32
When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git`. You also should install cspot additional components for protobuf use.
```
$ sudo pip3 install protobuf grpcio-tools
```
NB: I need to check on a fresh installation, but you might also require "protoc". You should do that within the esp32 local Python environment.
Don't forget to choose one of the config files in build_scripts/ and rename it sdkconfig.defaults or sdkconfig as many important WiFi/BT options are set there. **The codecs libraries will not be rebuilt by these scripts (it's a tedious process - see below)**
@@ -511,6 +668,7 @@ Use `idf.py monitor` to monitor the application (see esp-idf documentation)
Note: You can use `idf.py build -DDEPTH=32` to build the 32 bits version and add the `-DVERSION=<your_version>` to add a custom version name (it will be 0.0-<your_version>). If you want to change the whole version string, see squeezelite.h. You can also disable the SBR extension of AAC codecs as it consumes a lot of CPU and might overload the esp32. Use `-DAAC_DISABLE_SBR=1` for that
If you have already cloned the repository and you are getting compile errors on one of the submodules (e.g. telnet), run the following git command in the root of the repository location: `git submodule update --init --recursive`
### Rebuild codecs (highly recommended to NOT try that)
- for codecs libraries, add -mlongcalls if you want to rebuild them, but you should not (use the provided ones in codecs/lib). if you really want to rebuild them, open an issue
- libmad, libflac (no esp's version), libvorbis (tremor - not esp's version), alac work
@@ -529,5 +687,11 @@ If you have already cloned the repository and you are getting compile errors on
- stack consumption can be very high with some codec variants, so set NONTHREADSAFE_PSEUDOSTACK and GLOBAL_STACK_SIZE=48000 and unset VAR_ARRAYS in config.h
- libmad has been patched to avoid using a lot of stack and is not provided here. There is an issue with sync detection in 1.15.1b from where the original stack patch was done but since a few fixes have been made wrt sync detection. This 1.15.1b-10 found on debian fixes the issue where mad thinks it has reached sync but has not and so returns a wrong sample rate. It comes at the expense of 8KB (!) of code where a simple check in squeezelite/mad.c that next_frame[0] is 0xff and next_frame[1] & 0xf0 is 0xf0 does the trick ...
# Hardware tips
There is a possibility to have a software on/off where a temporary switch can power-up the esp32 which then will auto-sustain its power. Depending on the selected hardware, it a can also include a power-off by using a long press on the same button.
The auto-power is simply acheived by using `setGPIO` and forcing a GPIO to Vcc or GND and the sustain on/off requires a button creation whose longpress is an ACTRLS_SLEEP action (see also the [Sleeping](#sleeping) section). Credits [Renber78](http://github.com/Renber78) for schedmatics below
![alt text](https://github.com/sle118/squeezelite-esp32/blob/7eb4b218e31aa4692c5280fbec4619f690032c4a/Soft%20Power.png)
# Footnotes
(1) SPDIF is made by tricking the I2S bus but this consumes a fair bit of CPU as it multiplies by four the throughput on the i2s bus. To optimize some computation, the parity of the spdif frames must always be 0, so at least one bit has to be available to force it. As SPDIF samples are 20+4 bits length maximum, the LSB is used for that purpose, so the bit 24 is randomly toggling. It does not matter for 16 bits samples but it has been chosen to truncate the last 4 bits for 24 bits samples. I'm sure that some smart dude can further optimize spdif_convert() and use the user bit instead. You're welcome to do a PR but, as said above, I (philippe44) am not interested by 24 bits mental illness :-) and I've already made an effort to provide 20 bits which already way more what's needed :-)

BIN
Soft Power.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -216,6 +216,7 @@ CONFIG_TARGET=""
# I2S settings
#
CONFIG_I2S_NUM=0
CONFIG_I2S_MCK_IO=-1
CONFIG_I2S_BCK_IO=-1
CONFIG_I2S_WS_IO=-1
CONFIG_I2S_DO_IO=-1
@@ -296,6 +297,12 @@ CONFIG_AUDIO_CONTROLS=""
CONFIG_AMP_GPIO=-1
# end of AMP configuration
#
# POWER configuration
#
CONFIG_POWER_GPIO=-1
# end of POWER configuration
#
# Audio JACK
#
@@ -941,7 +948,7 @@ CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set
CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2
# CONFIG_FREERTOS_ASSERT_FAIL_ABORT is not set
CONFIG_FREERTOS_ASSERT_DISABLE=y
CONFIG_FREERTOS_ISR_STACKSIZE=2096

View File

@@ -217,6 +217,7 @@ CONFIG_TARGET="muse"
# I2S settings
#
CONFIG_I2S_NUM=0
CONFIG_I2S_MCK_IO=-1
CONFIG_I2S_BCK_IO=-1
CONFIG_I2S_WS_IO=-1
CONFIG_I2S_DO_IO=-1
@@ -263,6 +264,12 @@ CONFIG_CSPOT_SINK=y
#
# end of Display Screen
#
# POWER configuration
#
CONFIG_POWER_GPIO=-1
# end of POWER configuration
#
# Various I/O
#
@@ -900,7 +907,7 @@ CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set
CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2
# CONFIG_FREERTOS_ASSERT_FAIL_ABORT is not set
CONFIG_FREERTOS_ASSERT_DISABLE=y
CONFIG_FREERTOS_ISR_STACKSIZE=2096

View File

@@ -209,6 +209,7 @@ CONFIG_TARGET="squeezeamp"
# I2S settings
#
CONFIG_I2S_NUM=0
CONFIG_I2S_MCK_IO=-1
CONFIG_I2S_BCK_IO=-1
CONFIG_I2S_WS_IO=-1
CONFIG_I2S_DO_IO=-1
@@ -287,6 +288,12 @@ CONFIG_AUDIO_CONTROLS=""
CONFIG_AMP_GPIO=-1
# end of AMP configuration
#
# POWER configuration
#
CONFIG_POWER_GPIO=-1
# end of POWER configuration
#
# Compiler options
#
@@ -911,7 +918,7 @@ CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set
CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2
# CONFIG_FREERTOS_ASSERT_FAIL_ABORT is not set
CONFIG_FREERTOS_ASSERT_DISABLE=y
CONFIG_FREERTOS_ISR_STACKSIZE=2096

View File

@@ -1,21 +1,24 @@
set(lib_dir ${build_dir}/esp-idf)
if(IDF_TARGET STREQUAL esp32 AND IDF_VERSION_MAJOR EQUAL 4 AND IDF_VERSION_MINOR LESS 4)
set(lib_dir ${build_dir}/esp-idf)
set(driver esp32/i2s.c)
string(REPLACE ".c" ".c.obj" driver_obj "${driver}")
set(driver i2s.c i2s_hal.c spi_bus_lock.c)
string(REPLACE ".c" ".c.obj" driver_obj "${driver}")
idf_component_register( SRCS ${driver}
REQUIRES driver
INCLUDE_DIRS ${IDF_PATH}/components/driver
PRIV_INCLUDE_DIRS ${IDF_PATH}/components/driver/include/driver
)
idf_component_register( SRCS ${driver}
REQUIRES driver
INCLUDE_DIRS ${IDF_PATH}/components/driver
PRIV_INCLUDE_DIRS ${IDF_PATH}/components/driver/include/driver
)
# CMake is just a pile of crap
message("!! overriding ${driver} !!")
message("CAREFUL, LIBRARIES STRIPPING FROM DUPLICATED COMPONENTS DEPENDS ON THIS BEING REBUILD")
# CMake is just a pile of crap
message(STATUS "!! overriding ${driver} !!")
message(STATUS "CAREFUL, LIBRARIES STRIPPING FROM DUPLICATED COMPONENTS DEPENDS ON THIS BEING REBUILD")
add_custom_command(
TARGET ${COMPONENT_LIB}
PRE_LINK
COMMAND xtensa-esp32-elf-ar -d ${lib_dir}/driver/libdriver.a ${driver_obj}
VERBATIM
)
add_custom_command(
TARGET ${COMPONENT_LIB}
PRE_LINK
COMMAND xtensa-esp32-elf-ar -d ${lib_dir}/driver/libdriver.a ${driver_obj}
VERBATIM
)
else()
message(STATUS "==> NO OVERRIDE <==")
endif()

View File

@@ -38,7 +38,8 @@
#include "esp_attr.h"
#include "esp_log.h"
#include "esp_pm.h"
#include "esp_efuse.h"
#include "soc/chip_revision.h"
#include "hal/efuse_hal.h"
#include "esp_rom_gpio.h"
#include "sdkconfig.h"
@@ -193,7 +194,7 @@ static float i2s_apll_get_fi2s(int bits_per_sample, int sdm0, int sdm1, int sdm2
#if CONFIG_IDF_TARGET_ESP32
/* ESP32 rev0 silicon issue for APLL range/accuracy, please see ESP32 ECO document for more information on this */
if (esp_efuse_get_chip_ver() == 0) {
if (!ESP_CHIP_REV_ABOVE(efuse_hal_chip_revision(), 100)) {
sdm0 = 0;
sdm1 = 0;
}

View File

@@ -1,275 +0,0 @@
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The HAL layer for I2S (common part)
#include "soc/soc.h"
#include "soc/soc_caps.h"
#include "hal/i2s_hal.h"
#define I2S_TX_PDM_FP_DEF 960 // Set to the recommended value(960) in TRM
#define I2S_RX_PDM_DSR_DEF 0
void i2s_hal_set_tx_mode(i2s_hal_context_t *hal, i2s_channel_t ch, i2s_bits_per_sample_t bits)
{
if (bits <= I2S_BITS_PER_SAMPLE_16BIT) {
i2s_ll_set_tx_fifo_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
} else {
i2s_ll_set_tx_fifo_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 2 : 3);
}
i2s_ll_set_tx_chan_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
#if SOC_I2S_SUPPORTS_DMA_EQUAL
i2s_ll_set_tx_dma_equal(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
#endif
}
void i2s_hal_set_rx_mode(i2s_hal_context_t *hal, i2s_channel_t ch, i2s_bits_per_sample_t bits)
{
if (bits <= I2S_BITS_PER_SAMPLE_16BIT) {
i2s_ll_set_rx_fifo_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
} else {
i2s_ll_set_rx_fifo_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 2 : 3);
}
i2s_ll_set_rx_chan_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
#if SOC_I2S_SUPPORTS_DMA_EQUAL
i2s_ll_set_rx_dma_equal(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
#endif
}
void i2s_hal_set_in_link(i2s_hal_context_t *hal, uint32_t bytes_num, uint32_t addr)
{
i2s_ll_set_in_link_addr(hal->dev, addr);
i2s_ll_set_rx_eof_num(hal->dev, bytes_num);
}
#if SOC_I2S_SUPPORTS_PDM
void i2s_hal_tx_pdm_cfg(i2s_hal_context_t *hal, uint32_t fp, uint32_t fs)
{
i2s_ll_tx_pdm_cfg(hal->dev, fp, fs);
}
void i2s_hal_get_tx_pdm(i2s_hal_context_t *hal, uint32_t *fp, uint32_t *fs)
{
i2s_ll_get_tx_pdm(hal->dev, fp, fs);
}
void i2s_hal_rx_pdm_cfg(i2s_hal_context_t *hal, uint32_t dsr)
{
i2s_ll_rx_pdm_cfg(hal->dev, dsr);
}
void i2s_hal_get_rx_pdm(i2s_hal_context_t *hal, uint32_t *dsr)
{
i2s_ll_get_rx_pdm(hal->dev, dsr);
}
#endif
void i2s_hal_set_clk_div(i2s_hal_context_t *hal, int div_num, int div_a, int div_b, int tx_bck_div, int rx_bck_div)
{
i2s_ll_set_clkm_div_num(hal->dev, div_num);
i2s_ll_set_clkm_div_a(hal->dev, div_a);
i2s_ll_set_clkm_div_b(hal->dev, div_b);
i2s_ll_set_tx_bck_div_num(hal->dev, tx_bck_div);
i2s_ll_set_rx_bck_div_num(hal->dev, rx_bck_div);
}
void i2s_hal_set_tx_bits_mod(i2s_hal_context_t *hal, i2s_bits_per_sample_t bits)
{
i2s_ll_set_tx_bits_mod(hal->dev, bits);
}
void i2s_hal_set_rx_bits_mod(i2s_hal_context_t *hal, i2s_bits_per_sample_t bits)
{
i2s_ll_set_rx_bits_mod(hal->dev, bits);
}
void i2s_hal_reset(i2s_hal_context_t *hal)
{
// Reset I2S TX/RX module first, and then, reset DMA and FIFO.
i2s_ll_reset_tx(hal->dev);
i2s_ll_reset_rx(hal->dev);
i2s_ll_reset_dma_in(hal->dev);
i2s_ll_reset_dma_out(hal->dev);
i2s_ll_reset_rx_fifo(hal->dev);
i2s_ll_reset_tx_fifo(hal->dev);
}
void i2s_hal_start_tx(i2s_hal_context_t *hal)
{
i2s_ll_start_out_link(hal->dev);
i2s_ll_start_tx(hal->dev);
}
void i2s_hal_start_rx(i2s_hal_context_t *hal)
{
i2s_ll_start_in_link(hal->dev);
i2s_ll_start_rx(hal->dev);
}
void i2s_hal_stop_tx(i2s_hal_context_t *hal)
{
i2s_ll_stop_out_link(hal->dev);
i2s_ll_stop_tx(hal->dev);
}
void i2s_hal_stop_rx(i2s_hal_context_t *hal)
{
i2s_ll_stop_in_link(hal->dev);
i2s_ll_stop_rx(hal->dev);
}
void i2s_hal_format_config(i2s_hal_context_t *hal, const i2s_config_t *i2s_config)
{
switch (i2s_config->communication_format) {
case I2S_COMM_FORMAT_STAND_MSB:
if (i2s_config->mode & I2S_MODE_TX) {
i2s_ll_set_tx_format_msb_align(hal->dev);
}
if (i2s_config->mode & I2S_MODE_RX) {
i2s_ll_set_rx_format_msb_align(hal->dev);
}
break;
case I2S_COMM_FORMAT_STAND_PCM_SHORT:
if (i2s_config->mode & I2S_MODE_TX) {
i2s_ll_set_tx_pcm_long(hal->dev);
}
if (i2s_config->mode & I2S_MODE_RX) {
i2s_ll_set_rx_pcm_long(hal->dev);
}
break;
case I2S_COMM_FORMAT_STAND_PCM_LONG:
if (i2s_config->mode & I2S_MODE_TX) {
i2s_ll_set_tx_pcm_short(hal->dev);
}
if (i2s_config->mode & I2S_MODE_RX) {
i2s_ll_set_rx_pcm_short(hal->dev);
}
break;
default: //I2S_COMM_FORMAT_STAND_I2S
if (i2s_config->mode & I2S_MODE_TX) {
i2s_ll_set_tx_format_philip(hal->dev);
}
if (i2s_config->mode & I2S_MODE_RX) {
i2s_ll_set_rx_format_philip(hal->dev);
}
break;
}
}
void i2s_hal_config_param(i2s_hal_context_t *hal, const i2s_config_t *i2s_config)
{
//reset i2s
i2s_ll_reset_tx(hal->dev);
i2s_ll_reset_rx(hal->dev);
//reset dma
i2s_ll_reset_dma_in(hal->dev);
i2s_ll_reset_dma_out(hal->dev);
i2s_ll_enable_dma(hal->dev);
i2s_ll_set_lcd_en(hal->dev, 0);
i2s_ll_set_camera_en(hal->dev, 0);
i2s_ll_set_dscr_en(hal->dev, 0);
i2s_ll_set_tx_chan_mod(hal->dev, i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? i2s_config->channel_format : (i2s_config->channel_format >> 1)); // 0-two channel;1-right;2-left;3-righ;4-left
i2s_ll_set_tx_fifo_mod(hal->dev, i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 0 : 1); // 0-right&left channel;1-one channel
i2s_ll_set_tx_mono(hal->dev, 0);
i2s_ll_set_rx_chan_mod(hal->dev, i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? i2s_config->channel_format : (i2s_config->channel_format >> 1)); // 0-two channel;1-right;2-left;3-righ;4-left
i2s_ll_set_rx_fifo_mod(hal->dev, i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 0 : 1); // 0-right&left channel;1-one channel
i2s_ll_set_rx_mono(hal->dev, 0);
i2s_ll_set_dscr_en(hal->dev, 1); //connect dma to fifo
i2s_ll_stop_tx(hal->dev);
i2s_ll_stop_rx(hal->dev);
if (i2s_config->mode & I2S_MODE_TX) {
int order = i2s_config->bits_per_sample == 32 ? 0 : 1;
i2s_ll_set_tx_msb_right(hal->dev, order);
i2s_ll_set_tx_right_first(hal->dev, ~order);
i2s_ll_set_tx_slave_mod(hal->dev, 0); // Master
i2s_ll_set_tx_fifo_mod_force_en(hal->dev, 1);
if (i2s_config->mode & I2S_MODE_SLAVE) {
i2s_ll_set_tx_slave_mod(hal->dev, 1); //TX Slave
}
}
if (i2s_config->mode & I2S_MODE_RX) {
i2s_ll_set_rx_msb_right(hal->dev, 0);
i2s_ll_set_rx_right_first(hal->dev, 0);
i2s_ll_set_rx_slave_mod(hal->dev, 0); // Master
i2s_ll_set_rx_fifo_mod_force_en(hal->dev, 1);
if (i2s_config->mode & I2S_MODE_SLAVE) {
i2s_ll_set_rx_slave_mod(hal->dev, 1); //RX Slave
}
}
#if SOC_I2S_SUPPORTS_PDM
if (!(i2s_config->mode & I2S_MODE_PDM)) {
i2s_ll_set_rx_pdm_en(hal->dev, 0);
i2s_ll_set_tx_pdm_en(hal->dev, 0);
} else {
if (i2s_config->mode & I2S_MODE_TX) {
i2s_ll_tx_pdm_cfg(hal->dev, I2S_TX_PDM_FP_DEF, i2s_config->sample_rate/100);
}
if(i2s_config->mode & I2S_MODE_RX) {
i2s_ll_rx_pdm_cfg(hal->dev, I2S_RX_PDM_DSR_DEF);
}
// PDM mode have nothing to do with communication format configuration.
return;
}
#endif
#if SOC_I2S_SUPPORTS_ADC_DAC
if (i2s_config->mode & (I2S_MODE_DAC_BUILT_IN | I2S_MODE_ADC_BUILT_IN)) {
if (i2s_config->mode & I2S_MODE_DAC_BUILT_IN) {
i2s_ll_build_in_dac_ena(hal->dev);
}
if (i2s_config->mode & I2S_MODE_ADC_BUILT_IN) {
i2s_ll_build_in_adc_ena(hal->dev);
i2s_ll_set_rx_chan_mod(hal->dev, 1);
i2s_ll_set_rx_fifo_mod(hal->dev, 1);
i2s_ll_set_rx_mono(hal->dev, 0);
}
// Buildin ADC and DAC have nothing to do with communication format configuration.
return;
}
#endif
i2s_hal_format_config(hal, i2s_config);
}
void i2s_hal_enable_master_mode(i2s_hal_context_t *hal)
{
i2s_ll_set_tx_slave_mod(hal->dev, 0); //MASTER Slave
i2s_ll_set_rx_slave_mod(hal->dev, 1); //RX Slave
}
void i2s_hal_enable_slave_mode(i2s_hal_context_t *hal)
{
i2s_ll_set_tx_slave_mod(hal->dev, 1); //TX Slave
i2s_ll_set_rx_slave_mod(hal->dev, 1); //RX Slave
}
void i2s_hal_init(i2s_hal_context_t *hal, int i2s_num)
{
//Get hardware instance.
hal->dev = I2S_LL_GET_HW(i2s_num);
}

View File

@@ -1,849 +0,0 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include <stdatomic.h>
#include "sdkconfig.h"
#include "spi_common_internal.h"
#include "esp_intr_alloc.h"
#include "soc/soc_caps.h"
#include "stdatomic.h"
#include "esp_log.h"
#include <strings.h>
#include "esp_heap_caps.h"
/*
* This lock is designed to solve the conflicts between SPI devices (used in tasks) and
* the background operations (ISR or cache access).
*
* There are N (device/task) + 1 (BG) acquiring processer candidates that may touch the bus.
*
* The core of the lock is a `status` atomic variable, which is always available. No intermediate
* status is allowed. The atomic operations (mainly `atomic_fetch_and`, `atomic_fetch_or`)
* atomically read the status, and bitwisely write status value ORed / ANDed with given masks.
*
* Definitions of the status:
* - [30] WEAK_BG_FLAG, active when the BG is the cache
* - [29:20] LOCK bits, active when corresponding device is asking for acquiring
* - [19:10] PENDING bits, active when the BG acknowledges the REQ bits, but hasn't fully handled them.
* - [ 9: 0] REQ bits, active when corresponding device is requesting for BG operations.
*
* The REQ bits together PENDING bits are called BG bits, which represent the actual BG request
* state of devices. Either one of REQ or PENDING being active indicates the device has pending BG
* requests. Reason of having two bits instead of one is in the appendix below.
*
* Acquiring processer means the current processor (task or ISR) allowed to touch the critical
* resources, or the SPI bus.
*
* States of the lock:
* - STATE_IDLE: There's no acquiring processor. No device is acquiring the bus, and no BG
* operation is in progress.
*
* - STATE_ACQ: The acquiring processor is a device task. This means one of the devices is
* acquiring the bus.
*
* - STATE_BG: The acquiring processor is the ISR, and there is no acquiring device.
*
* - STATE_BG_ACQ: The acquiring processor is the ISR, and there is an acquiring device.
*
*
* Whenever a bit is written to the status, it means the a device on a task is trying to acquire
* the lock (either for the task, or the ISR). When there is no LOCK bits or BG bits active, the
* caller immediately become the acquiring processor. Otherwise, the task has to block, and the ISR
* will not be invoked until scheduled by the current acquiring processor.
*
* The acquiring processor is responsible to assign the next acquiring processor by calling the
* scheduler, usually after it finishes some requests, and cleared the corresponding status bit.
* But there is one exception, when the last bit is cleared from the status, after which there is
* no other LOCK bits or BG bits active, the acquiring processor lost its role immediately, and
* don't need to call the scheduler to assign the next acquiring processor.
*
* The acquiring processor may also choose to assign a new acquiring device when there is no, by
* calling `spi_bus_lock_bg_rotate_acq_dev` in the ISR. But the acquiring processor, in this case,
* is still the ISR, until it calls the scheduler.
*
*
* Transition of the FSM:
*
* - STATE_IDLE: no acquiring device, nor acquiring processor, no LOCK or BG bits active
* -> STATE_BG: by `req_core`
* -> STATE_ACQ: by `acquire_core`
*
* - STATE_BG:
* * No acquiring device, the ISR is the acquiring processor, there is BG bits active, but no LOCK
* bits
* * The BG operation should be enabled while turning into this state.
*
* -> STATE_IDLE: by `bg_exit_core` after `clear_pend_core` for all BG bits
* -> STATE_BG_ACQ: by `schedule_core`, when there is new LOCK bit set (by `acquire_core`)
*
* - STATE_BG_ACQ:
* * There is acquiring device, the ISR is the acquiring processor, there may be BG bits active for
* the acquiring device.
* * The BG operation should be enabled while turning into this state.
*
* -> STATE_ACQ: by `bg_exit_core` after `clear_pend_core` for all BG bits for the acquiring
* device.
*
* Should not go to the STATE_ACQ (unblock the acquiring task) until all requests of the
* acquiring device are finished. This is to preserve the sequence of foreground (polling) and
* background operations of the device. The background operations queued before the acquiring
* should be completed first.
*
* - STATE_ACQ:
* * There is acquiring device, the task is the acquiring processor, there is no BG bits active for
* the acquiring device.
* * The acquiring task (if blocked at `spi_bus_lock_acquire_start` or `spi_bus_lock_wait_bg_done`)
* should be resumed while turning into this state.
*
* -> STATE_BG_ACQ: by `req_core`
* -> STATE_BG_ACQ (other device): by `acquire_end_core`, when there is LOCK bit for another
* device, and the new acquiring device has active BG bits.
* -> STATE_ACQ (other device): by `acquire_end_core`, when there is LOCK bit for another devices,
* but the new acquiring device has no active BG bits.
* -> STATE_BG: by `acquire_end_core` when there is no LOCK bit active, but there are active BG
* bits.
* -> STATE_IDLE: by `acquire_end_core` when there is no LOCK bit, nor BG bit active.
*
* The `req_core` used in the task is a little special. It asks for acquiring processor for the
* ISR. When it succeed for the first time, it will invoke the ISR (hence passing the acquiring
* role to the BG). Otherwise it will not block, the ISR will be automatically be invoked by other
* acquiring processor. The caller of `req_core` will never become acquiring processor by this
* function.
*
*
* Appendix: The design, that having both request bit and pending bit, is to solve the
* concurrency issue between tasks and the bg, when the task can queue several requests,
* however the request bit cannot represent the number of requests queued.
*
* Here's the workflow of task and ISR work concurrently:
* - Task: (a) Write to Queue -> (b) Write request bit
* The Task have to write request bit (b) after the data is prepared in the queue (a),
* otherwise the BG may fail to read from the queue when it sees the request bit set.
*
* - BG: (c) Read queue -> (d) Clear request bit
* Since the BG cannot know the number of requests queued, it have to repeatedly check the
* queue (c), until it find the data is empty, and then clear the request bit (d).
*
* The events are possible to happen in the order: (c) -> (a) -> (b) -> (d). This may cause a false
* clear of the request bit. And there will be data prepared in the queue, but the request bit is
* inactive.
*
* (e) move REQ bits to PEND bits, happen before (c) is introduced to solve this problem. In this
* case (d) is changed to clear the PEND bit. Even if (e) -> (c) -> (a) -> (b) -> (d), only PEND
* bit is cleared, while the REQ bit is still active.
*/
struct spi_bus_lock_dev_t;
typedef struct spi_bus_lock_dev_t spi_bus_lock_dev_t;
typedef struct spi_bus_lock_t spi_bus_lock_t;
#define MAX_DEV_NUM 10
// Bit 29-20: lock bits, Bit 19-10: pending bits
// Bit 9-0: request bits, Bit 30:
#define LOCK_SHIFT 20
#define PENDING_SHIFT 10
#define REQ_SHIFT 0
#define WEAK_BG_FLAG BIT(30) /**< The bus is permanently requested by background operations.
* This flag is weak, will not prevent acquiring of devices. But will help the BG to be re-enabled again after the bus is release.
*/
// get the bit mask wher bit [high-1, low] are all 1'b1 s.
#define BIT1_MASK(high, low) ((UINT32_MAX << (high)) ^ (UINT32_MAX << (low)))
#define LOCK_BIT(mask) ((mask) << LOCK_SHIFT)
#define REQUEST_BIT(mask) ((mask) << REQ_SHIFT)
#define PENDING_BIT(mask) ((mask) << PENDING_SHIFT)
#define DEV_MASK(id) (LOCK_BIT(1<<id) | PENDING_BIT(1<<id) | REQUEST_BIT(1<<id))
#define ID_DEV_MASK(mask) (ffs(mask) - 1)
#define REQ_MASK BIT1_MASK(REQ_SHIFT+MAX_DEV_NUM, REQ_SHIFT)
#define PEND_MASK BIT1_MASK(PENDING_SHIFT+MAX_DEV_NUM, PENDING_SHIFT)
#define BG_MASK BIT1_MASK(REQ_SHIFT+MAX_DEV_NUM*2, REQ_SHIFT)
#define LOCK_MASK BIT1_MASK(LOCK_SHIFT+MAX_DEV_NUM, LOCK_SHIFT)
#define DEV_REQ_MASK(dev) ((dev)->mask & REQ_MASK)
#define DEV_PEND_MASK(dev) ((dev)->mask & PEND_MASK)
#define DEV_BG_MASK(dev) ((dev)->mask & BG_MASK)
struct spi_bus_lock_t {
/**
* The core of the lock. These bits are status of the lock, which should be always available.
* No intermediate status is allowed. This is realized by atomic operations, mainly
* `atomic_fetch_and`, `atomic_fetch_or`, which atomically read the status, and bitwise write
* status value ORed / ANDed with given masks.
*
* The request bits together pending bits represent the actual bg request state of one device.
* Either one of them being active indicates the device has pending bg requests.
*
* Whenever a bit is written to the status, it means the a device on a task is trying to
* acquire the lock. But this will succeed only when no LOCK or BG bits active.
*
* The acquiring processor is responsible to call the scheduler to pass its role to other tasks
* or the BG, unless it clear the last bit in the status register.
*/
//// Critical resources, they are only writable by acquiring processor, and stable only when read by the acquiring processor.
atomic_uint_fast32_t status;
spi_bus_lock_dev_t* volatile acquiring_dev; ///< The acquiring device
bool volatile acq_dev_bg_active; ///< BG is the acquiring processor serving the acquiring device, used for the wait_bg to skip waiting quickly.
bool volatile in_isr; ///< ISR is touching HW
//// End of critical resources
atomic_intptr_t dev[DEV_NUM_MAX]; ///< Child locks.
bg_ctrl_func_t bg_enable; ///< Function to enable background operations.
bg_ctrl_func_t bg_disable; ///< Function to disable background operations
void* bg_arg; ///< Argument for `bg_enable` and `bg_disable` functions.
spi_bus_lock_dev_t* last_dev; ///< Last used device, to decide whether to refresh all registers.
int periph_cs_num; ///< Number of the CS pins the HW has.
//debug information
int host_id; ///< Host ID, for debug information printing
uint32_t new_req; ///< Last int_req when `spi_bus_lock_bg_start` is called. Debug use.
};
struct spi_bus_lock_dev_t {
SemaphoreHandle_t semphr; ///< Binray semaphore to notify the device it claimed the bus
spi_bus_lock_t* parent; ///< Pointer to parent spi_bus_lock_t
uint32_t mask; ///< Bitwise OR-ed mask of the REQ, PEND, LOCK bits of this device
};
portMUX_TYPE s_spinlock = portMUX_INITIALIZER_UNLOCKED;
DRAM_ATTR static const char TAG[] = "bus_lock";
#define LOCK_CHECK(a, str, ret_val, ...) \
if (!(a)) { \
ESP_LOGE(TAG,"%s(%d): "str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
return (ret_val); \
}
static inline int mask_get_id(uint32_t mask);
static inline int dev_lock_get_id(spi_bus_lock_dev_t *dev_lock);
/*******************************************************************************
* atomic operations to the status
******************************************************************************/
SPI_MASTER_ISR_ATTR static inline uint32_t lock_status_fetch_set(spi_bus_lock_t *lock, uint32_t set)
{
return atomic_fetch_or(&lock->status, set);
}
IRAM_ATTR static inline uint32_t lock_status_fetch_clear(spi_bus_lock_t *lock, uint32_t clear)
{
return atomic_fetch_and(&lock->status, ~clear);
}
IRAM_ATTR static inline uint32_t lock_status_fetch(spi_bus_lock_t *lock)
{
return atomic_load(&lock->status);
}
SPI_MASTER_ISR_ATTR static inline void lock_status_init(spi_bus_lock_t *lock)
{
atomic_store(&lock->status, 0);
}
// return the remaining status bits
IRAM_ATTR static inline uint32_t lock_status_clear(spi_bus_lock_t* lock, uint32_t clear)
{
//the fetch and clear should be atomic, avoid missing the all '0' status when all bits are clear.
uint32_t state = lock_status_fetch_clear(lock, clear);
return state & (~clear);
}
/*******************************************************************************
* Schedule service
*
* The modification to the status bits may cause rotating of the acquiring processor. It also have
* effects to `acquired_dev` (the acquiring device), `in_isr` (HW used in BG), and
* `acq_dev_bg_active` (wait_bg_end can be skipped) members of the lock structure.
*
* Most of them should be atomic, and special attention should be paid to the operation
* sequence.
******************************************************************************/
SPI_MASTER_ISR_ATTR static inline void resume_dev_in_isr(spi_bus_lock_dev_t *dev_lock, BaseType_t *do_yield)
{
xSemaphoreGiveFromISR(dev_lock->semphr, do_yield);
}
IRAM_ATTR static inline void resume_dev(const spi_bus_lock_dev_t *dev_lock)
{
xSemaphoreGive(dev_lock->semphr);
}
SPI_MASTER_ISR_ATTR static inline void bg_disable(spi_bus_lock_t *lock)
{
BUS_LOCK_DEBUG_EXECUTE_CHECK(lock->bg_disable);
lock->bg_disable(lock->bg_arg);
}
IRAM_ATTR static inline void bg_enable(spi_bus_lock_t* lock)
{
BUS_LOCK_DEBUG_EXECUTE_CHECK(lock->bg_enable);
lock->bg_enable(lock->bg_arg);
}
// Set the REQ bit. If we become the acquiring processor, invoke the ISR and pass that to it.
// The caller will never become the acquiring processor after this function returns.
SPI_MASTER_ATTR static inline void req_core(spi_bus_lock_dev_t *dev_handle)
{
spi_bus_lock_t *lock = dev_handle->parent;
// Though `acquired_dev` is critical resource, `dev_handle == lock->acquired_dev`
// is a stable statement unless `acquire_start` or `acquire_end` is called by current
// device.
if (dev_handle == lock->acquiring_dev){
// Set the REQ bit and check BG bits if we are the acquiring processor.
// If the BG bits were not active before, invoke the BG again.
// Avoid competitive risk against the `clear_pend_core`, `acq_dev_bg_active` should be set before
// setting REQ bit.
lock->acq_dev_bg_active = true;
uint32_t status = lock_status_fetch_set(lock, DEV_REQ_MASK(dev_handle));
if ((status & DEV_BG_MASK(dev_handle)) == 0) {
bg_enable(lock); //acquiring processor passed to BG
}
} else {
uint32_t status = lock_status_fetch_set(lock, DEV_REQ_MASK(dev_handle));
if (status == 0) {
bg_enable(lock); //acquiring processor passed to BG
}
}
}
//Set the LOCK bit. Handle related stuff and return true if we become the acquiring processor.
SPI_MASTER_ISR_ATTR static inline bool acquire_core(spi_bus_lock_dev_t *dev_handle)
{
spi_bus_lock_t* lock = dev_handle->parent;
portENTER_CRITICAL_SAFE(&s_spinlock);
uint32_t status = lock_status_fetch_set(lock, dev_handle->mask & LOCK_MASK);
portEXIT_CRITICAL_SAFE(&s_spinlock);
// Check all bits except WEAK_BG
if ((status & (BG_MASK | LOCK_MASK)) == 0) {
//succeed at once
lock->acquiring_dev = dev_handle;
BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acq_dev_bg_active);
if (status & WEAK_BG_FLAG) {
//Mainly to disable the cache (Weak_BG), that is not able to disable itself
bg_disable(lock);
}
return true;
}
return false;
}
/**
* Find the next acquiring processor according to the status. Will directly change
* the acquiring device if new one found.
*
* Cases:
* - BG should still be the acquiring processor (Return false):
* 1. Acquiring device has active BG bits: out_desired_dev = new acquiring device
* 2. No acquiring device, but BG active: out_desired_dev = randomly pick one device with active BG bits
* - BG should yield to the task (Return true):
* 3. Acquiring device has no active BG bits: out_desired_dev = new acquiring device
* 4. No acquiring device while no active BG bits: out_desired_dev=NULL
*
* Acquiring device task need to be resumed only when case 3.
*
* This scheduling can happen in either task or ISR, so `in_isr` or `bg_active` not touched.
*
* @param lock
* @param status Current status
* @param out_desired_dev Desired device to work next, see above.
*
* @return False if BG should still be the acquiring processor, otherwise True (yield to task).
*/
IRAM_ATTR static inline bool
schedule_core(spi_bus_lock_t *lock, uint32_t status, spi_bus_lock_dev_t **out_desired_dev)
{
spi_bus_lock_dev_t* desired_dev = NULL;
uint32_t lock_bits = (status & LOCK_MASK) >> LOCK_SHIFT;
uint32_t bg_bits = status & BG_MASK;
bg_bits = ((bg_bits >> REQ_SHIFT) | (bg_bits >> PENDING_SHIFT)) & REQ_MASK;
bool bg_yield;
if (lock_bits) {
int dev_id = mask_get_id(lock_bits);
desired_dev = (spi_bus_lock_dev_t *)atomic_load(&lock->dev[dev_id]);
BUS_LOCK_DEBUG_EXECUTE_CHECK(desired_dev);
lock->acquiring_dev = desired_dev;
bg_yield = ((bg_bits & desired_dev->mask) == 0);
lock->acq_dev_bg_active = !bg_yield;
} else {
lock->acq_dev_bg_active = false;
if (bg_bits) {
int dev_id = mask_get_id(bg_bits);
desired_dev = (spi_bus_lock_dev_t *)atomic_load(&lock->dev[dev_id]);
BUS_LOCK_DEBUG_EXECUTE_CHECK(desired_dev);
lock->acquiring_dev = NULL;
bg_yield = false;
} else {
desired_dev = NULL;
lock->acquiring_dev = NULL;
bg_yield = true;
}
}
*out_desired_dev = desired_dev;
return bg_yield;
}
//Clear the LOCK bit and trigger a rescheduling.
IRAM_ATTR static inline void acquire_end_core(spi_bus_lock_dev_t *dev_handle)
{
spi_bus_lock_t* lock = dev_handle->parent;
//uint32_t status = lock_status_clear(lock, dev_handle->mask & LOCK_MASK);
spi_bus_lock_dev_t* desired_dev = NULL;
portENTER_CRITICAL_SAFE(&s_spinlock);
uint32_t status = lock_status_clear(lock, dev_handle->mask & LOCK_MASK);
bool invoke_bg = !schedule_core(lock, status, &desired_dev);
portEXIT_CRITICAL_SAFE(&s_spinlock);
if (invoke_bg) {
bg_enable(lock);
} else if (desired_dev) {
resume_dev(desired_dev);
} else if (status & WEAK_BG_FLAG) {
bg_enable(lock);
}
}
// Move the REQ bits to corresponding PEND bits. Must be called by acquiring processor.
// Have no side effects on the acquiring device/processor.
SPI_MASTER_ISR_ATTR static inline void update_pend_core(spi_bus_lock_t *lock, uint32_t status)
{
uint32_t active_req_bits = status & REQ_MASK;
#if PENDING_SHIFT > REQ_SHIFT
uint32_t pending_mask = active_req_bits << (PENDING_SHIFT - REQ_SHIFT);
#else
uint32_t pending_mask = active_req_bits >> (REQ_SHIFT - PENDING_SHIFT);
#endif
// We have to set the PEND bits and then clear the REQ bits, since BG bits are using bitwise OR logic,
// this will not influence the effectiveness of the BG bits of every device.
lock_status_fetch_set(lock, pending_mask);
lock_status_fetch_clear(lock, active_req_bits);
}
// Clear the PEND bit (not REQ bit!) of a device, return the suggestion whether we can try to quit the ISR.
// Lost the acquiring processor immediately when the BG bits for active device are inactive, indiciating by the return value.
// Can be called only when ISR is acting as the acquiring processor.
SPI_MASTER_ISR_ATTR static inline bool clear_pend_core(spi_bus_lock_dev_t *dev_handle)
{
bool finished;
spi_bus_lock_t *lock = dev_handle->parent;
uint32_t pend_mask = DEV_PEND_MASK(dev_handle);
BUS_LOCK_DEBUG_EXECUTE_CHECK(lock_status_fetch(lock) & pend_mask);
uint32_t status = lock_status_clear(lock, pend_mask);
if (lock->acquiring_dev == dev_handle) {
finished = ((status & DEV_REQ_MASK(dev_handle)) == 0);
if (finished) {
lock->acq_dev_bg_active = false;
}
} else {
finished = (status == 0);
}
return finished;
}
// Return true if the ISR has already touched the HW, which means previous operations should
// be terminated first, before we use the HW again. Otherwise return false.
// In either case `in_isr` will be marked as true, until call to `bg_exit_core` with `wip=false` successfully.
SPI_MASTER_ISR_ATTR static inline bool bg_entry_core(spi_bus_lock_t *lock)
{
BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acquiring_dev || lock->acq_dev_bg_active);
/*
* The interrupt is disabled at the entry of ISR to avoid competitive risk as below:
*
* The `esp_intr_enable` will be called (b) after new BG request is queued (a) in the task;
* while `esp_intr_disable` should be called (c) if we check and found the sending queue is empty (d).
* If (c) happens after (d), if things happens in this sequence:
* (d) -> (a) -> (b) -> (c), the interrupt will be disabled while there's pending BG request in the queue.
*
* To avoid this, interrupt is disabled here, and re-enabled later if required. (c) -> (d) -> (a) -> (b) -> revert (c) if !d
*/
bg_disable(lock);
if (lock->in_isr) {
return false;
} else {
lock->in_isr = true;
return true;
}
}
// Handle the conditions of status and interrupt, avoiding the ISR being disabled when there is any new coming BG requests.
// When called with `wip=true`, means the ISR is performing some operations. Will enable the interrupt again and exit unconditionally.
// When called with `wip=false`, will only return `true` when there is no coming BG request. If return value is `false`, the ISR should try again.
// Will not change acquiring device.
SPI_MASTER_ISR_ATTR static inline bool bg_exit_core(spi_bus_lock_t *lock, bool wip, BaseType_t *do_yield)
{
//See comments in `bg_entry_core`, re-enable interrupt disabled in entry if we do need the interrupt
if (wip) {
bg_enable(lock);
BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acquiring_dev || lock->acq_dev_bg_active);
return true;
}
bool ret;
uint32_t status = lock_status_fetch(lock);
if (lock->acquiring_dev) {
if (status & DEV_BG_MASK(lock->acquiring_dev)) {
BUS_LOCK_DEBUG_EXECUTE_CHECK(lock->acq_dev_bg_active);
ret = false;
} else {
// The request may happen any time, even after we fetched the status.
// The value of `acq_dev_bg_active` is random.
resume_dev_in_isr(lock->acquiring_dev, do_yield);
ret = true;
}
} else {
BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acq_dev_bg_active);
ret = !(status & BG_MASK);
}
if (ret) {
//when successfully exit, but no transaction done, mark BG as inactive
lock->in_isr = false;
}
return ret;
}
IRAM_ATTR static inline void dev_wait_prepare(spi_bus_lock_dev_t *dev_handle)
{
xSemaphoreTake(dev_handle->semphr, 0);
}
SPI_MASTER_ISR_ATTR static inline esp_err_t dev_wait(spi_bus_lock_dev_t *dev_handle, TickType_t wait)
{
BaseType_t ret = xSemaphoreTake(dev_handle->semphr, wait);
if (ret == pdFALSE) return ESP_ERR_TIMEOUT;
return ESP_OK;
}
/*******************************************************************************
* Initialization & Deinitialization
******************************************************************************/
esp_err_t spi_bus_init_lock(spi_bus_lock_handle_t *out_lock, const spi_bus_lock_config_t *config)
{
spi_bus_lock_t* lock = (spi_bus_lock_t*)calloc(sizeof(spi_bus_lock_t), 1);
if (lock == NULL) {
return ESP_ERR_NO_MEM;
}
lock_status_init(lock);
lock->acquiring_dev = NULL;
lock->last_dev = NULL;
lock->periph_cs_num = config->cs_num;
lock->host_id = config->host_id;
*out_lock = lock;
return ESP_OK;
}
void spi_bus_deinit_lock(spi_bus_lock_handle_t lock)
{
for (int i = 0; i < DEV_NUM_MAX; i++) {
assert(atomic_load(&lock->dev[i]) == (intptr_t)NULL);
}
free(lock);
}
static int try_acquire_free_dev(spi_bus_lock_t *lock, bool cs_required)
{
if (cs_required) {
int i;
for (i = 0; i < lock->periph_cs_num; i++) {
intptr_t null = (intptr_t) NULL;
//use 1 to occupy the slot, actual setup comes later
if (atomic_compare_exchange_strong(&lock->dev[i], &null, (intptr_t) 1)) {
break;
}
}
return ((i == lock->periph_cs_num)? -1: i);
} else {
int i;
for (i = DEV_NUM_MAX - 1; i >= 0; i--) {
intptr_t null = (intptr_t) NULL;
//use 1 to occupy the slot, actual setup comes later
if (atomic_compare_exchange_strong(&lock->dev[i], &null, (intptr_t) 1)) {
break;
}
}
return i;
}
}
esp_err_t spi_bus_lock_register_dev(spi_bus_lock_handle_t lock, spi_bus_lock_dev_config_t *config,
spi_bus_lock_dev_handle_t *out_dev_handle)
{
if (lock == NULL) return ESP_ERR_INVALID_ARG;
int id = try_acquire_free_dev(lock, config->flags & SPI_BUS_LOCK_DEV_FLAG_CS_REQUIRED);
if (id == -1) return ESP_ERR_NOT_SUPPORTED;
spi_bus_lock_dev_t* dev_lock = (spi_bus_lock_dev_t*)heap_caps_calloc(sizeof(spi_bus_lock_dev_t), 1, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (dev_lock == NULL) {
return ESP_ERR_NO_MEM;
}
dev_lock->semphr = xSemaphoreCreateBinary();
if (dev_lock->semphr == NULL) {
free(dev_lock);
atomic_store(&lock->dev[id], (intptr_t)NULL);
return ESP_ERR_NO_MEM;
}
dev_lock->parent = lock;
dev_lock->mask = DEV_MASK(id);
ESP_LOGV(TAG, "device registered on bus %d slot %d.", lock->host_id, id);
atomic_store(&lock->dev[id], (intptr_t)dev_lock);
*out_dev_handle = dev_lock;
return ESP_OK;
}
void spi_bus_lock_unregister_dev(spi_bus_lock_dev_handle_t dev_handle)
{
int id = dev_lock_get_id(dev_handle);
spi_bus_lock_t* lock = dev_handle->parent;
BUS_LOCK_DEBUG_EXECUTE_CHECK(atomic_load(&lock->dev[id]) == (intptr_t)dev_handle);
if (lock->last_dev == dev_handle) lock->last_dev = NULL;
atomic_store(&lock->dev[id], (intptr_t)NULL);
if (dev_handle->semphr) {
vSemaphoreDelete(dev_handle->semphr);
}
free(dev_handle);
}
IRAM_ATTR static inline int mask_get_id(uint32_t mask)
{
return ID_DEV_MASK(mask);
}
IRAM_ATTR static inline int dev_lock_get_id(spi_bus_lock_dev_t *dev_lock)
{
return mask_get_id(dev_lock->mask);
}
void spi_bus_lock_set_bg_control(spi_bus_lock_handle_t lock, bg_ctrl_func_t bg_enable, bg_ctrl_func_t bg_disable, void *arg)
{
lock->bg_enable = bg_enable;
lock->bg_disable = bg_disable;
lock->bg_arg = arg;
}
IRAM_ATTR int spi_bus_lock_get_dev_id(spi_bus_lock_dev_handle_t dev_handle)
{
return (dev_handle? dev_lock_get_id(dev_handle): -1);
}
//will be called when cache disabled
IRAM_ATTR bool spi_bus_lock_touch(spi_bus_lock_dev_handle_t dev_handle)
{
spi_bus_lock_dev_t* last_dev = dev_handle->parent->last_dev;
dev_handle->parent->last_dev = dev_handle;
if (last_dev != dev_handle) {
int last_dev_id = (last_dev? dev_lock_get_id(last_dev): -1);
ESP_DRAM_LOGV(TAG, "SPI dev changed from %d to %d",
last_dev_id, dev_lock_get_id(dev_handle));
}
return (dev_handle != last_dev);
}
/*******************************************************************************
* Acquiring service
******************************************************************************/
IRAM_ATTR esp_err_t spi_bus_lock_acquire_start(spi_bus_lock_dev_t *dev_handle, TickType_t wait)
{
LOCK_CHECK(wait == portMAX_DELAY, "timeout other than portMAX_DELAY not supported", ESP_ERR_INVALID_ARG);
spi_bus_lock_t* lock = dev_handle->parent;
// Clear the semaphore before checking
dev_wait_prepare(dev_handle);
if (!acquire_core(dev_handle)) {
//block until becoming the acquiring processor (help by previous acquiring processor)
esp_err_t err = dev_wait(dev_handle, wait);
//TODO: add timeout handling here.
if (err != ESP_OK) return err;
}
ESP_DRAM_LOGV(TAG, "dev %d acquired.", dev_lock_get_id(dev_handle));
BUS_LOCK_DEBUG_EXECUTE_CHECK(lock->acquiring_dev == dev_handle);
//When arrives at here, requests of this device should already be handled
uint32_t status = lock_status_fetch(lock);
(void) status;
BUS_LOCK_DEBUG_EXECUTE_CHECK((status & DEV_BG_MASK(dev_handle)) == 0);
return ESP_OK;
}
IRAM_ATTR esp_err_t spi_bus_lock_acquire_end(spi_bus_lock_dev_t *dev_handle)
{
//release the bus
spi_bus_lock_t* lock = dev_handle->parent;
LOCK_CHECK(lock->acquiring_dev == dev_handle, "Cannot release a lock that hasn't been acquired.", ESP_ERR_INVALID_STATE);
acquire_end_core(dev_handle);
ESP_LOGV(TAG, "dev %d released.", dev_lock_get_id(dev_handle));
return ESP_OK;
}
SPI_MASTER_ISR_ATTR spi_bus_lock_dev_handle_t spi_bus_lock_get_acquiring_dev(spi_bus_lock_t *lock)
{
return lock->acquiring_dev;
}
/*******************************************************************************
* BG (background operation) service
******************************************************************************/
SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_entry(spi_bus_lock_t* lock)
{
return bg_entry_core(lock);
}
SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_exit(spi_bus_lock_t* lock, bool wip, BaseType_t* do_yield)
{
return bg_exit_core(lock, wip, do_yield);
}
SPI_MASTER_ATTR esp_err_t spi_bus_lock_bg_request(spi_bus_lock_dev_t *dev_handle)
{
req_core(dev_handle);
return ESP_OK;
}
IRAM_ATTR esp_err_t spi_bus_lock_wait_bg_done(spi_bus_lock_dev_handle_t dev_handle, TickType_t wait)
{
spi_bus_lock_t *lock = dev_handle->parent;
LOCK_CHECK(lock->acquiring_dev == dev_handle, "Cannot wait for a device that is not acquired", ESP_ERR_INVALID_STATE);
LOCK_CHECK(wait == portMAX_DELAY, "timeout other than portMAX_DELAY not supported", ESP_ERR_INVALID_ARG);
// If no BG bits active, skip quickly. This is ensured by `spi_bus_lock_wait_bg_done`
// cannot be executed with `bg_request` on the same device concurrently.
if (lock_status_fetch(lock) & DEV_BG_MASK(dev_handle)) {
// Clear the semaphore before checking
dev_wait_prepare(dev_handle);
if (lock_status_fetch(lock) & DEV_BG_MASK(dev_handle)) {
//block until becoming the acquiring processor (help by previous acquiring processor)
esp_err_t err = dev_wait(dev_handle, wait);
//TODO: add timeout handling here.
if (err != ESP_OK) return err;
}
}
BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acq_dev_bg_active);
BUS_LOCK_DEBUG_EXECUTE_CHECK((lock_status_fetch(lock) & DEV_BG_MASK(dev_handle)) == 0);
return ESP_OK;
}
SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_clear_req(spi_bus_lock_dev_t *dev_handle)
{
bool finished = clear_pend_core(dev_handle);
ESP_EARLY_LOGV(TAG, "dev %d served from bg.", dev_lock_get_id(dev_handle));
return finished;
}
SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_check_dev_acq(spi_bus_lock_t *lock,
spi_bus_lock_dev_handle_t *out_dev_lock)
{
BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acquiring_dev);
uint32_t status = lock_status_fetch(lock);
return schedule_core(lock, status, out_dev_lock);
}
SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_check_dev_req(spi_bus_lock_dev_t *dev_lock)
{
spi_bus_lock_t* lock = dev_lock->parent;
uint32_t status = lock_status_fetch(lock);
uint32_t dev_status = status & dev_lock->mask;
// move REQ bits of all device to corresponding PEND bits.
// To reduce executing time, only done when the REQ bit of the calling device is set.
if (dev_status & REQ_MASK) {
update_pend_core(lock, status);
return true;
} else {
return dev_status & PEND_MASK;
}
}
SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_req_exist(spi_bus_lock_t *lock)
{
uint32_t status = lock_status_fetch(lock);
return status & BG_MASK;
}
/*******************************************************************************
* Static variables of the locks of the main flash
******************************************************************************/
#if CONFIG_SPI_FLASH_SHARE_SPI1_BUS
static spi_bus_lock_dev_t lock_main_flash_dev;
static spi_bus_lock_t main_spi_bus_lock = {
/*
* the main bus cache is permanently required, this flag is set here and never clear so that the
* cache will always be enabled if acquiring devices yield.
*/
.status = ATOMIC_VAR_INIT(WEAK_BG_FLAG),
.acquiring_dev = NULL,
.dev = {ATOMIC_VAR_INIT((intptr_t)&lock_main_flash_dev)},
.new_req = 0,
.periph_cs_num = SOC_SPI_PERIPH_CS_NUM(0),
};
const spi_bus_lock_handle_t g_main_spi_bus_lock = &main_spi_bus_lock;
esp_err_t spi_bus_lock_init_main_bus(void)
{
spi_bus_main_set_lock(g_main_spi_bus_lock);
return ESP_OK;
}
static StaticSemaphore_t main_flash_semphr;
static spi_bus_lock_dev_t lock_main_flash_dev = {
.semphr = NULL,
.parent = &main_spi_bus_lock,
.mask = DEV_MASK(0),
};
const spi_bus_lock_dev_handle_t g_spi_lock_main_flash_dev = &lock_main_flash_dev;
esp_err_t spi_bus_lock_init_main_dev(void)
{
g_spi_lock_main_flash_dev->semphr = xSemaphoreCreateBinaryStatic(&main_flash_semphr);
if (g_spi_lock_main_flash_dev->semphr == NULL) {
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
#else //CONFIG_SPI_FLASH_SHARE_SPI1_BUS
//when the dev lock is not initialized, point to NULL
const spi_bus_lock_dev_handle_t g_spi_lock_main_flash_dev = NULL;
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
idf_component_register(
INCLUDE_DIRS . ./inc inc/alac inc/helix-aac inc/mad inc/resample16 inc/soxr inc/vorbis inc/opus inc/opusfile
INCLUDE_DIRS . ./inc inc/alac inc/helix-aac inc/mad inc/resample16 inc/soxr inc/vorbis inc/opus
)
if (DEFINED AAC_DISABLE_SBR)
@@ -14,7 +14,6 @@ add_prebuilt_library(libvorbisidec lib/libvorbisidec.a )
add_prebuilt_library(libogg lib/libogg.a )
add_prebuilt_library(libalac lib/libalac.a )
add_prebuilt_library(libresample16 lib/libresample16.a )
add_prebuilt_library(libopusfile lib/libopusfile.a )
add_prebuilt_library(libopus lib/libopus.a )
target_link_libraries(${COMPONENT_LIB} INTERFACE libmad)
@@ -24,5 +23,4 @@ target_link_libraries(${COMPONENT_LIB} INTERFACE libvorbisidec)
target_link_libraries(${COMPONENT_LIB} INTERFACE libogg)
target_link_libraries(${COMPONENT_LIB} INTERFACE libalac)
target_link_libraries(${COMPONENT_LIB} INTERFACE libresample16)
target_link_libraries(${COMPONENT_LIB} INTERFACE libopusfile)
target_link_libraries(${COMPONENT_LIB} INTERFACE libopus)

View File

@@ -1,6 +1,6 @@
/* libFLAC - Free Lossless Audio Codec library
* Copyright (C) 2000-2009 Josh Coalson
* Copyright (C) 2011-2022 Xiph.Org Foundation
* Copyright (C) 2011-2023 Xiph.Org Foundation
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -782,6 +782,25 @@ FLAC_API void FLAC__stream_decoder_delete(FLAC__StreamDecoder *decoder);
*/
FLAC_API FLAC__bool FLAC__stream_decoder_set_ogg_serial_number(FLAC__StreamDecoder *decoder, long serial_number);
/** Set the "allow Ogg chaining" flag. If set, the Ogg decoder will
* prepare to receive a new stream once the last Ogg page arrives for
* the stream encapsulating the FLAC audio data. This can be used to
* support chained Ogg FLAC streams; a new \c STREAMINFO signals the
* beginning of a new stream.
*
* \note
* This function has no effect with native FLAC decoding.
*
* \default \c false
* \param decoder A decoder instance to set.
* \param allow Whether to allow chained streams.
* \assert
* \code decoder != NULL \endcode
* \retval FLAC__bool
* \c false if the decoder is already initialized, else \c true.
*/
FLAC_API FLAC__bool FLAC__stream_decoder_set_ogg_chaining(FLAC__StreamDecoder* decoder, FLAC__bool value);
/** Set the "MD5 signature checking" flag. If \c true, the decoder will
* compute the MD5 signature of the unencoded audio data while decoding
* and compare it to the signature from the STREAMINFO block, if it
@@ -906,6 +925,17 @@ FLAC_API FLAC__StreamDecoderState FLAC__stream_decoder_get_state(const FLAC__Str
*/
FLAC_API const char *FLAC__stream_decoder_get_resolved_state_string(const FLAC__StreamDecoder *decoder);
/** Get the "allow Ogg chaining" flag as described in
* \code FLAC__stream_decoder_set_ogg_chaining \endcode.
*
* \param decoder A decoder instance to query.
* \assert
* \code decoder != NULL \endcode
* \retval FLAC__bool
* See above.
*/
FLAC_API FLAC__bool FLAC__stream_decoder_get_ogg_chaining(const FLAC__StreamDecoder* decoder);
/** Get the "MD5 signature checking" flag.
* This is the value of the setting, not whether or not the decoder is
* currently checking the MD5 (remember, it can be turned off automatically

View File

@@ -1,7 +1,7 @@
#ifndef __CONFIG_TYPES_H__
#define __CONFIG_TYPES_H__
/* these are filled in by configure */
/* these are filled in by configure or cmake*/
#define INCLUDE_INTTYPES_H 1
#define INCLUDE_STDINT_H 1
#define INCLUDE_SYS_TYPES_H 1
@@ -21,5 +21,6 @@ typedef uint16_t ogg_uint16_t;
typedef int32_t ogg_int32_t;
typedef uint32_t ogg_uint32_t;
typedef int64_t ogg_int64_t;
typedef uint64_t ogg_uint64_t;
#endif

View File

@@ -11,7 +11,6 @@
********************************************************************
function: toplevel libogg include
last mod: $Id$
********************************************************************/
#ifndef _OGG_H

View File

@@ -10,8 +10,7 @@
* *
********************************************************************
function: #ifdef jail to whip a few platforms into the UNIX ideal.
last mod: $Id$
function: Define a consistent set of types on each platform.
********************************************************************/
#ifndef _OS_TYPES_H
@@ -44,6 +43,7 @@
typedef unsigned long long ogg_uint64_t;
# elif defined(__MWERKS__)
typedef long long ogg_int64_t;
typedef unsigned long long ogg_uint64_t;
typedef int ogg_int32_t;
typedef unsigned int ogg_uint32_t;
typedef short ogg_int16_t;
@@ -62,6 +62,7 @@
typedef __int64 ogg_int64_t;
typedef __int32 ogg_int32_t;
typedef unsigned __int32 ogg_uint32_t;
typedef unsigned __int64 ogg_uint64_t;
typedef __int16 ogg_int16_t;
typedef unsigned __int16 ogg_uint16_t;
# endif
@@ -69,12 +70,13 @@
#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */
# include <inttypes.h>
# include <sys/types.h>
typedef int16_t ogg_int16_t;
typedef uint16_t ogg_uint16_t;
typedef u_int16_t ogg_uint16_t;
typedef int32_t ogg_int32_t;
typedef uint32_t ogg_uint32_t;
typedef u_int32_t ogg_uint32_t;
typedef int64_t ogg_int64_t;
typedef u_int64_t ogg_uint64_t;
#elif defined(__HAIKU__)
@@ -85,6 +87,7 @@
typedef int ogg_int32_t;
typedef unsigned int ogg_uint32_t;
typedef long long ogg_int64_t;
typedef unsigned long long ogg_uint64_t;
#elif defined(__BEOS__)
@@ -95,6 +98,7 @@
typedef int32_t ogg_int32_t;
typedef uint32_t ogg_uint32_t;
typedef int64_t ogg_int64_t;
typedef uint64_t ogg_uint64_t;
#elif defined (__EMX__)
@@ -104,6 +108,8 @@
typedef int ogg_int32_t;
typedef unsigned int ogg_uint32_t;
typedef long long ogg_int64_t;
typedef unsigned long long ogg_uint64_t;
#elif defined (DJGPP)
@@ -112,11 +118,13 @@
typedef int ogg_int32_t;
typedef unsigned int ogg_uint32_t;
typedef long long ogg_int64_t;
typedef unsigned long long ogg_uint64_t;
#elif defined(R5900)
/* PS2 EE */
typedef long ogg_int64_t;
typedef unsigned long ogg_uint64_t;
typedef int ogg_int32_t;
typedef unsigned ogg_uint32_t;
typedef short ogg_int16_t;
@@ -129,6 +137,7 @@
typedef signed int ogg_int32_t;
typedef unsigned int ogg_uint32_t;
typedef long long int ogg_int64_t;
typedef unsigned long long int ogg_uint64_t;
#elif defined(__TMS320C6X__)
@@ -138,6 +147,7 @@
typedef signed int ogg_int32_t;
typedef unsigned int ogg_uint32_t;
typedef long long int ogg_int64_t;
typedef unsigned long long int ogg_uint64_t;
#else

View File

@@ -6,7 +6,7 @@
* IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
* *
* THE libopusfile SOURCE CODE IS (C) COPYRIGHT 1994-2012 *
* by the Xiph.Org Foundation and contributors http://www.xiph.org/ *
* by the Xiph.Org Foundation and contributors https://xiph.org/ *
* *
********************************************************************
@@ -28,7 +28,7 @@
reference
<tt><a href="https://www.xiph.org/ogg/doc/libogg/reference.html">libogg</a></tt>
and
<tt><a href="https://mf4.xiph.org/jenkins/view/opus/job/opus/ws/doc/html/index.html">libopus</a></tt>
<tt><a href="https://opus-codec.org/docs/opus_api-1.3.1/">libopus</a></tt>
libraries.
<tt>libopusfile</tt> provides several sets of built-in routines for
@@ -58,7 +58,7 @@
it is stored in the header to allow you to resample to it after decoding
(the <tt>libopusfile</tt> API does not currently provide a resampler,
but the
<a href="http://www.speex.org/docs/manual/speex-manual/node7.html#SECTION00760000000000000000">the
<a href="https://www.speex.org/docs/manual/speex-manual/node7.html#SECTION00760000000000000000">the
Speex resampler</a> is a good choice if you need one).
In general, if you are playing back the audio, you should leave it at
48&nbsp;kHz, provided your audio hardware supports it.
@@ -68,7 +68,7 @@
Opus files can contain anywhere from 1 to 255 channels of audio.
The channel mappings for up to 8 channels are the same as the
<a href="http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9">Vorbis
<a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9">Vorbis
mappings</a>.
A special stereo API can convert everything to 2 channels, making it simple
to support multichannel files in an application which only has stereo
@@ -147,18 +147,18 @@ typedef struct OggOpusFile OggOpusFile;
/**@endcond*/
/**\defgroup error_codes Error Codes*/
/*@{*/
/**@{*/
/**\name List of possible error codes
Many of the functions in this library return a negative error code when a
function fails.
This list provides a brief explanation of the common errors.
See each individual function for more details on what a specific error code
means in that context.*/
/*@{*/
/**@{*/
/**A request did not succeed.*/
#define OP_FALSE (-1)
/*Currently not used externally.*/
/**Currently not used externally.**/
#define OP_EOF (-2)
/**There was a hole in the page sequence numbers (e.g., a page was corrupt or
missing).*/
@@ -185,7 +185,7 @@ typedef struct OggOpusFile OggOpusFile;
#define OP_EBADHEADER (-133)
/**The ID header contained an unrecognized version number.*/
#define OP_EVERSION (-134)
/*Currently not used at all.*/
/**Currently not used at all.**/
#define OP_ENOTAUDIO (-135)
/**An audio packet failed to decode properly.
This is usually caused by a multistream Ogg packet where the durations of
@@ -200,11 +200,11 @@ typedef struct OggOpusFile OggOpusFile;
/**The first or last granule position of a link failed basic validity checks.*/
#define OP_EBADTIMESTAMP (-139)
/*@}*/
/*@}*/
/**@}*/
/**@}*/
/**\defgroup header_info Header Information*/
/*@{*/
/**@{*/
/**The maximum number of channels in an Ogg Opus stream.*/
#define OPUS_CHANNEL_COUNT_MAX (255)
@@ -284,7 +284,7 @@ struct OpusHead{
A particular tag may occur more than once, and order is significant.
The character set encoding for the strings is always UTF-8, but the tag
names are limited to ASCII, and treated as case-insensitive.
See <a href="http://www.xiph.org/vorbis/doc/v-comment.html">the Vorbis
See <a href="https://www.xiph.org/vorbis/doc/v-comment.html">the Vorbis
comment header specification</a> for details.
In filling in this structure, <tt>libopusfile</tt> will null-terminate the
@@ -311,7 +311,7 @@ struct OpusTags{
};
/**\name Picture tag image formats*/
/*@{*/
/**@{*/
/**The MIME type was not recognized, or the image data did not match the
declared MIME type.*/
@@ -325,7 +325,7 @@ struct OpusTags{
/**The image is a GIF.*/
#define OP_PIC_FORMAT_GIF (3)
/*@}*/
/**@}*/
/**The contents of a METADATA_BLOCK_PICTURE tag.*/
struct OpusPictureTag{
@@ -398,7 +398,7 @@ struct OpusPictureTag{
These can be used to query the headers returned by <tt>libopusfile</tt>, or
to parse Opus headers from sources other than an Ogg Opus stream, provided
they use the same format.*/
/*@{*/
/**@{*/
/**Parses the contents of the ID header packet of an Ogg Opus stream.
\param[out] _head Returns the contents of the parsed packet.
@@ -671,12 +671,12 @@ void opus_picture_tag_init(OpusPictureTag *_pic) OP_ARG_NONNULL(1);
\param _pic The #OpusPictureTag structure to clear.*/
void opus_picture_tag_clear(OpusPictureTag *_pic) OP_ARG_NONNULL(1);
/*@}*/
/**@}*/
/*@}*/
/**@}*/
/**\defgroup url_options URL Reading Options*/
/*@{*/
/**@{*/
/**\name URL reading options
Options for op_url_stream_create() and associated functions.
These allow you to provide proxy configuration parameters, skip SSL
@@ -685,7 +685,7 @@ void opus_picture_tag_clear(OpusPictureTag *_pic) OP_ARG_NONNULL(1);
times, only the value specified by the last occurrence has an effect
(unless otherwise specified).
They may be expanded in the future.*/
/*@{*/
/**@{*/
/**@cond PRIVATE*/
@@ -698,7 +698,7 @@ void opus_picture_tag_clear(OpusPictureTag *_pic) OP_ARG_NONNULL(1);
#define OP_HTTP_PROXY_PASS_REQUEST (6720)
#define OP_GET_SERVER_INFO_REQUEST (6784)
#define OP_URL_OPT(_request) ((_request)+(char *)0)
#define OP_URL_OPT(_request) ((char *)(_request))
/*These macros trigger compilation errors or warnings if the wrong types are
provided to one of the URL options.*/
@@ -843,11 +843,11 @@ void opus_server_info_clear(OpusServerInfo *_info) OP_ARG_NONNULL(1);
#define OP_GET_SERVER_INFO(_info) \
OP_URL_OPT(OP_GET_SERVER_INFO_REQUEST),OP_CHECK_SERVER_INFO_PTR(_info)
/*@}*/
/*@}*/
/**@}*/
/**@}*/
/**\defgroup stream_callbacks Abstract Stream Reading Interface*/
/*@{*/
/**@{*/
/**\name Functions for reading from streams
These functions define the interface used to read from and seek in a stream
of data.
@@ -856,7 +856,7 @@ void opus_server_info_clear(OpusServerInfo *_info) OP_ARG_NONNULL(1);
These functions also include some convenience routines for working with
standard <code>FILE</code> pointers, complete streams stored in a single
block of memory, or URLs.*/
/*@{*/
/**@{*/
/**Reads up to \a _nbytes bytes of data from \a _stream.
\param _stream The stream to read from.
@@ -1034,18 +1034,18 @@ OP_WARN_UNUSED_RESULT void *op_url_stream_vcreate(OpusFileCallbacks *_cb,
OP_WARN_UNUSED_RESULT void *op_url_stream_create(OpusFileCallbacks *_cb,
const char *_url,...) OP_ARG_NONNULL(1) OP_ARG_NONNULL(2);
/*@}*/
/*@}*/
/**@}*/
/**@}*/
/**\defgroup stream_open_close Opening and Closing*/
/*@{*/
/**@{*/
/**\name Functions for opening and closing streams
These functions allow you to test a stream to see if it is Opus, open it,
and close it.
Several flavors are provided for each of the built-in stream types, plus a
more general version which takes a set of application-provided callbacks.*/
/*@{*/
/**@{*/
/**Test to see if this is an Opus stream.
For good results, you will need at least 57 bytes (for a pure Opus-only
@@ -1159,20 +1159,16 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_url(const char *_url,
This value will be passed verbatim as the first
argument to all of the callbacks.
\param _cb The callbacks with which to access the stream.
<code><a href="#op_read_func">read()</a></code> must
be implemented.
<code><a href="#op_seek_func">seek()</a></code> and
<code><a href="#op_tell_func">tell()</a></code> may
be <code>NULL</code>, or may always return -1 to
indicate a stream is unseekable, but if
<code><a href="#op_seek_func">seek()</a></code> is
implemented and succeeds on a particular stream, then
<code><a href="#op_tell_func">tell()</a></code> must
also.
<code><a href="#op_close_func">close()</a></code> may
be <code>NULL</code>, but if it is not, it will be
called when the \c OggOpusFile is destroyed by
op_free().
\ref op_read_func "read()" must be implemented.
\ref op_seek_func "seek()" and \ref op_tell_func
"tell()" may be <code>NULL</code>, or may always
return -1 to indicate a stream is unseekable, but if
\ref op_seek_func "seek()" is implemented and
succeeds on a particular stream, then \ref
op_tell_func "tell()" must also.
\ref op_close_func "close()" may be <code>NULL</code>,
but if it is not, it will be called when the \c
OggOpusFile is destroyed by op_free().
It will not be called if op_open_callbacks() fails
with an error.
\param _initial_data An initial buffer of data from the start of the
@@ -1183,10 +1179,8 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_url(const char *_url,
stream to be opened, even if it is unseekable.
\param _initial_bytes The number of bytes in \a _initial_data.
If the stream is seekable, its current position (as
reported by
<code><a href="#opus_tell_func">tell()</a></code>
at the start of this function) must be equal to
\a _initial_bytes.
reported by \ref op_tell_func "tell()" at the start
of this function) must be equal to \a _initial_bytes.
Otherwise, seeking to absolute positions will
generate inconsistent results.
\param[out] _error Returns 0 on success, or a failure code on error.
@@ -1206,11 +1200,10 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_open_url(const char *_url,
implemented, such as an unsupported channel
family.</dd>
<dt>#OP_EINVAL</dt>
<dd><code><a href="#op_seek_func">seek()</a></code>
was implemented and succeeded on this source, but
<code><a href="#op_tell_func">tell()</a></code>
did not, or the starting position indicator was
not equal to \a _initial_bytes.</dd>
<dd>\ref op_seek_func "seek()" was implemented and
succeeded on this source, but \ref op_tell_func
"tell()" did not, or the starting position
indicator was not equal to \a _initial_bytes.</dd>
<dt>#OP_ENOTFORMAT</dt>
<dd>The stream contained a link that did not have
any logical Opus streams in it.</dd>
@@ -1341,20 +1334,16 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_test_url(const char *_url,
This value will be passed verbatim as the first
argument to all of the callbacks.
\param _cb The callbacks with which to access the stream.
<code><a href="#op_read_func">read()</a></code> must
be implemented.
<code><a href="#op_seek_func">seek()</a></code> and
<code><a href="#op_tell_func">tell()</a></code> may
be <code>NULL</code>, or may always return -1 to
indicate a stream is unseekable, but if
<code><a href="#op_seek_func">seek()</a></code> is
implemented and succeeds on a particular stream, then
<code><a href="#op_tell_func">tell()</a></code> must
also.
<code><a href="#op_close_func">close()</a></code> may
be <code>NULL</code>, but if it is not, it will be
called when the \c OggOpusFile is destroyed by
op_free().
\ref op_read_func "read()" must be implemented.
\ref op_seek_func "seek()" and \ref op_tell_func
"tell()" may be <code>NULL</code>, or may always
return -1 to indicate a stream is unseekable, but if
\ref op_seek_func "seek()" is implemented and
succeeds on a particular stream, then \ref
op_tell_func "tell()" must also.
\ref op_close_func "close()" may be <code>NULL</code>,
but if it is not, it will be called when the \c
OggOpusFile is destroyed by op_free().
It will not be called if op_open_callbacks() fails
with an error.
\param _initial_data An initial buffer of data from the start of the
@@ -1367,9 +1356,8 @@ OP_WARN_UNUSED_RESULT OggOpusFile *op_test_url(const char *_url,
\param _initial_bytes The number of bytes in \a _initial_data.
If the stream is seekable, its current position (as
reported by
<code><a href="#opus_tell_func">tell()</a></code>
at the start of this function) must be equal to
\a _initial_bytes.
\ref op_tell_func "tell()" at the start of this
function) must be equal to \a _initial_bytes.
Otherwise, seeking to absolute positions will
generate inconsistent results.
\param[out] _error Returns 0 on success, or a failure code on error.
@@ -1418,11 +1406,11 @@ int op_test_open(OggOpusFile *_of) OP_ARG_NONNULL(1);
\param _of The \c OggOpusFile to free.*/
void op_free(OggOpusFile *_of);
/*@}*/
/*@}*/
/**@}*/
/**@}*/
/**\defgroup stream_info Stream Information*/
/*@{*/
/**@{*/
/**\name Functions for obtaining information about streams
These functions allow you to get basic information about a stream, including
@@ -1437,18 +1425,17 @@ void op_free(OggOpusFile *_of);
streams returned by op_test_callbacks() or one of the associated
convenience functions.
Their documention will indicate so explicitly.*/
/*@{*/
/**@{*/
/**Returns whether or not the stream being read is seekable.
This is true if
<ol>
<li>The <code><a href="#op_seek_func">seek()</a></code> and
<code><a href="#op_tell_func">tell()</a></code> callbacks are both
non-<code>NULL</code>,</li>
<li>The <code><a href="#op_seek_func">seek()</a></code> callback was
successfully executed at least once, and</li>
<li>The <code><a href="#op_tell_func">tell()</a></code> callback was
successfully able to report the position indicator afterwards.</li>
<li>The \ref op_seek_func "seek()" and \ref op_tell_func "tell()"
callbacks are both non-<code>NULL</code>,</li>
<li>The \ref op_seek_func "seek()" callback was successfully executed at
least once, and</li>
<li>The \ref op_tell_func "tell()" callback was successfully able to report
the position indicator afterwards.</li>
</ol>
This function may be called on partially-opened streams.
\param _of The \c OggOpusFile whose seekable status is to be returned.
@@ -1638,11 +1625,11 @@ opus_int64 op_raw_tell(const OggOpusFile *_of) OP_ARG_NONNULL(1);
\retval #OP_EINVAL The stream was only partially open.*/
ogg_int64_t op_pcm_tell(const OggOpusFile *_of) OP_ARG_NONNULL(1);
/*@}*/
/*@}*/
/**@}*/
/**@}*/
/**\defgroup stream_seeking Seeking*/
/*@{*/
/**@{*/
/**\name Functions for seeking in Opus streams
These functions let you seek in Opus streams, if the underlying stream
@@ -1667,7 +1654,7 @@ ogg_int64_t op_pcm_tell(const OggOpusFile *_of) OP_ARG_NONNULL(1);
values as would be obtained by decoding the stream straight through.
However, such differences are expected to be smaller than the loss
introduced by Opus's lossy compression.*/
/*@{*/
/**@{*/
/**Seek to a byte offset relative to the <b>compressed</b> data.
This also scans packets to update the PCM cursor.
@@ -1702,11 +1689,11 @@ int op_raw_seek(OggOpusFile *_of,opus_int64 _byte_offset) OP_ARG_NONNULL(1);
seeking to the target destination was impossible.*/
int op_pcm_seek(OggOpusFile *_of,ogg_int64_t _pcm_offset) OP_ARG_NONNULL(1);
/*@}*/
/*@}*/
/**@}*/
/**@}*/
/**\defgroup stream_decoding Decoding*/
/*@{*/
/**@{*/
/**\name Functions for decoding audio data
These functions retrieve actual decoded audio data from the stream.
@@ -1744,7 +1731,7 @@ int op_pcm_seek(OggOpusFile *_of,ogg_int64_t _pcm_offset) OP_ARG_NONNULL(1);
If you are reading from an <https:> URL (particularly if seeking is not
supported), you should make sure to check for this error and warn the user
appropriately.*/
/*@{*/
/**@{*/
/**Indicates that the decoding callback should produce signed 16-bit
native-endian output samples.*/
@@ -1890,7 +1877,7 @@ void op_set_dither_enabled(OggOpusFile *_of,int _enabled) OP_ARG_NONNULL(1);
signed native-endian 16-bit values at 48&nbsp;kHz
with a nominal range of <code>[-32768,32767)</code>.
Multiple channels are interleaved using the
<a href="http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9">Vorbis
<a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9">Vorbis
channel ordering</a>.
This must have room for at least \a _buf_size values.
\param _buf_size The number of values that can be stored in \a _pcm.
@@ -1972,7 +1959,7 @@ OP_WARN_UNUSED_RESULT int op_read(OggOpusFile *_of,
signed floats at 48&nbsp;kHz with a nominal range of
<code>[-1.0,1.0]</code>.
Multiple channels are interleaved using the
<a href="http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9">Vorbis
<a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9">Vorbis
channel ordering</a>.
This must have room for at least \a _buf_size floats.
\param _buf_size The number of floats that can be stored in \a _pcm.
@@ -2150,8 +2137,8 @@ OP_WARN_UNUSED_RESULT int op_read_stereo(OggOpusFile *_of,
OP_WARN_UNUSED_RESULT int op_read_float_stereo(OggOpusFile *_of,
float *_pcm,int _buf_size) OP_ARG_NONNULL(1);
/*@}*/
/*@}*/
/**@}*/
/**@}*/
# if OP_GNUC_PREREQ(4,0)
# pragma GCC visibility pop

Binary file not shown.

Binary file not shown.

Binary file not shown.

176
components/display/SH1122.c Normal file
View File

@@ -0,0 +1,176 @@
/**
* Copyright (c) 2017-2018 Tara Keeling
* 2020 Philippe G.
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <esp_heap_caps.h>
#include <esp_log.h>
#include "gds.h"
#include "gds_private.h"
#define SHADOW_BUFFER
#define PAGE_BLOCK 1024
#define min(a,b) (((a) < (b)) ? (a) : (b))
static char TAG[] = "SH1122";
struct PrivateSpace {
uint8_t *iRAM, *Shadowbuffer;
uint8_t PageSize;
};
// Functions are not declared to minimize # of lines
static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
Device->WriteCommand( Device, 0x10 | (Start >> 4) );
Device->WriteCommand( Device, 0x00 | (Start & 0x0f) );
}
static void SetRowAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
Device->WriteCommand( Device, 0xB0 );
Device->WriteCommand( Device, Start );
}
static void Update( struct GDS_Device* Device ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
// RAM is by columns of 4 pixels ...
SetColumnAddress( Device, 0, Device->Width / 4 - 1);
#ifdef SHADOW_BUFFER
uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer;
bool dirty = false;
for (int r = 0, page = 0; r < Device->Height; r++) {
// look for change and update shadow (cheap optimization = width always / by 2)
for (int c = Device->Width / 2 / 2; --c >= 0;) {
if (*optr != *iptr) {
dirty = true;
*optr = *iptr;
}
iptr++; optr++;
}
// one line done, check for page boundary
if (++page == Private->PageSize) {
if (dirty) {
SetRowAddress( Device, r - page + 1, r );
if (Private->iRAM) {
memcpy(Private->iRAM, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 );
Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 );
} else {
Device->WriteData( Device, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2);
}
dirty = false;
}
page = 0;
}
}
#else
SetRowAddress( Device, 0, Device->Height - 1 );
for (int r = 0; r < Device->Height; r += Private->PageSize) {
if (Private->iRAM) {
memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
} else {
Device->WriteData( Device, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
}
}
#endif
}
static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
if (Layout->HFlip) {
Device->WriteCommand( Device, 0x40 + 0x20 );
Device->WriteCommand( Device, 0xA1 );
} else {
Device->WriteCommand( Device, 0x40 + 0x00 );
Device->WriteCommand( Device, 0xA0 );
}
Device->WriteCommand( Device, Layout->VFlip ? 0xC8 : 0xC0 );
Device->WriteCommand( Device, Layout->Invert ? 0xA7 : 0xA6 );
}
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
Device->WriteCommand( Device, 0x81 );
Device->WriteCommand( Device, Contrast );
}
static bool Init( struct GDS_Device* Device ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
// find a page size that is not too small is an integer of height
Private->PageSize = min(8, PAGE_BLOCK / (Device->Width / 2));
while (Private->PageSize && Device->Height != (Device->Height / Private->PageSize) * Private->PageSize) Private->PageSize--;
#ifdef SHADOW_BUFFER
Private->Shadowbuffer = malloc( Device->FramebufferSize );
memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize);
#endif
// only use iRAM for SPI
if (Device->IF == GDS_IF_SPI) {
Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
}
ESP_LOGI(TAG, "SH1122 page %u, iRAM %p", Private->PageSize, Private->iRAM);
// need to be off and disable display RAM
Device->DisplayOff( Device );
Device->WriteCommand( Device, 0xA5 );
// Display Offset
Device->WriteCommand( Device, 0xD3 );
Device->WriteCommand( Device, 0 );
// set flip modes
struct GDS_Layout Layout = { };
Device->SetLayout( Device, &Layout );
// set Clocks => check value
Device->WriteCommand( Device, 0xD5 );
Device->WriteCommand( Device, ( 0x04 << 4 ) | 0x00 );
// MUX Ratio => fixed
Device->WriteCommand( Device, 0xA8 );
Device->WriteCommand( Device, Device->Height - 1);
// no Display Inversion
Device->WriteCommand( Device, 0xA6 );
// gone with the wind
Device->WriteCommand( Device, 0xA4 );
Device->DisplayOn( Device );
Device->Update( Device );
return true;
}
static const struct GDS_Device SH1122 = {
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
.SetLayout = SetLayout,
.Update = Update, .Init = Init,
.Mode = GDS_GRAYSCALE, .Depth = 4,
.HighNibble = true,
};
struct GDS_Device* SH1122_Detect(char *Driver, struct GDS_Device* Device) {
if (!strcasestr(Driver, "SH1122")) return NULL;
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
*Device = SH1122;
return Device;
}

View File

@@ -71,8 +71,8 @@ static void Update( struct GDS_Device* Device ) {
if (dirty) {
uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Private->Shadowbuffer + (r - page + 1) * Device->Width / 2);
SetRowAddress( Device, r - page + 1, r );
// need byte swapping
for (int i = page * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8);
//memcpy(Private->iRAM, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 );
Device->WriteCommand( Device, 0x5c );
Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 );
dirty = false;
@@ -84,14 +84,10 @@ static void Update( struct GDS_Device* Device ) {
for (int r = 0; r < Device->Height; r += Private->PageSize) {
SetRowAddress( Device, r, r + Private->PageSize - 1 );
Device->WriteCommand( Device, 0x5c );
if (Private->iRAM) {
uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Device->Framebuffer + r * Device->Width / 2);
for (int i = Private->PageSize * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8);
//memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
} else {
Device->WriteData( Device, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
}
// need byte swapping
uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Device->Framebuffer + r * Device->Width / 2);
for (int i = Private->PageSize * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8);
Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
}
#endif
}

View File

@@ -233,7 +233,7 @@ static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
}
Device->WriteCommand( Device, 0xA0 );
Device->WriteCommand( Device, Private->ReMap );
Device->WriteCommand( Device, Layout->Invert ? 0xA7 : 0xA6 );
Device->WriteCommand( Device, Layout->Invert ? 0xA7 : 0xA4 );
}
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }

View File

@@ -289,10 +289,8 @@ struct GDS_Device* ST77xx_Detect(char *Driver, struct GDS_Device* Device) {
struct PrivateSpace* Private = (struct PrivateSpace*) Device->Private;
Private->Model = Model;
if (Model == ST7735) {
sscanf(Driver, "%*[^:]%*[^x]%*[^=]=%hu", &Private->Offset.Height);
sscanf(Driver, "%*[^:]%*[^y]%*[^=]=%hu", &Private->Offset.Width);
}
sscanf(Driver, "%*[^:]%*[^x]%*[^=]=%hu", &Private->Offset.Height);
sscanf(Driver, "%*[^:]%*[^y]%*[^=]=%hu", &Private->Offset.Width);
if (Depth == 18) {
Device->Mode = GDS_RGB666;

View File

@@ -19,6 +19,12 @@
#include "gds.h"
#include "gds_private.h"
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define LEDC_SPEED_MODE LEDC_LOW_SPEED_MODE
#else
#define LEDC_SPEED_MODE LEDC_HIGH_SPEED_MODE
#endif
static struct GDS_Device Display;
static struct GDS_BacklightPWM PWMConfig;
@@ -34,7 +40,7 @@ struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[], s
ledc_timer_config_t PWMTimer = {
.duty_resolution = LEDC_TIMER_13_BIT,
.freq_hz = 5000,
.speed_mode = LEDC_HIGH_SPEED_MODE,
.speed_mode = LEDC_SPEED_MODE,
.timer_num = PWMConfig.Timer,
};
ledc_timer_config(&PWMTimer);
@@ -103,7 +109,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
int c = x1;
// for a row that is not on a boundary, no optimization possible
while (r & 0x07 && r <= y2) {
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
for (c = x1; c <= x2; c++) Device->DrawPixelFast( Device, c, r, Color );
r++;
}
// go fast if we have more than 8 lines to write
@@ -111,7 +117,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
memset(optr + Width * r + x1, _Color, x2 - x1 + 1);
r += 8;
} else while (r <= y2) {
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
for (c = x1; c <= x2; c++) Device->DrawPixelFast( Device, c, r, Color );
r++;
}
}
@@ -127,10 +133,10 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
// try to do byte processing as much as possible
for (int r = y1; r <= y2; r++) {
int c = x1;
if (c & 0x01) DrawPixelFast( Device, c++, r, Color);
if (c & 0x01) Device->DrawPixelFast( Device, c++, r, Color);
int chunk = (x2 - c + 1) >> 1;
memset(optr + ((r * Width + c) >> 1), _Color, chunk);
if (c + chunk <= x2) DrawPixelFast( Device, x2, r, Color);
if (c + chunk <= x2) Device->DrawPixelFast( Device, x2, r, Color);
}
}
} else if (Device->Depth == 8) {
@@ -142,7 +148,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
} else {
for (int y = y1; y <= y2; y++) {
for (int x = x1; x <= x2; x++) {
DrawPixelFast( Device, x, y, Color);
Device->DrawPixelFast( Device, x, y, Color);
}
}
}
@@ -165,10 +171,76 @@ bool GDS_Reset( struct GDS_Device* Device ) {
return true;
}
static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint32_t YBit = ( Y & 0x07 );
uint8_t* FBOffset;
/*
* We only need to modify the Y coordinate since the pitch
* of the screen is the same as the width.
* Dividing Y by 8 gives us which row the pixel is in but not
* the bit position.
*/
Y>>= 3;
FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
if ( Color == GDS_COLOR_XOR ) {
*FBOffset ^= BIT( YBit );
} else {
*FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( YBit ) : *FBOffset | BIT( YBit );
}
}
static void IRAM_ATTR DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
}
static void IRAM_ATTR DrawPixel4FastHigh( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
*FBOffset = X & 0x01 ? ((*FBOffset & 0xf0) | (Color & 0x0f)) : (*FBOffset & 0x0f) | ((Color & 0x0f) << 4);
}
static void IRAM_ATTR DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
Device->Framebuffer[Y * Device->Width + X] = Color;
}
// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R
static void IRAM_ATTR DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
*FBOffset = __builtin_bswap16(Color);
}
// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB
static void IRAM_ATTR DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f;
}
// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB
static void IRAM_ATTR DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color;
}
bool GDS_Init( struct GDS_Device* Device ) {
if (Device->Depth > 8) Device->FramebufferSize = Device->Width * Device->Height * ((8 + Device->Depth - 1) / 8);
else Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth);
// set the proper DrawPixel function if not already set by driver
if (!Device->DrawPixelFast) {
if (Device->Depth == 1) Device->DrawPixelFast = DrawPixel1Fast;
else if (Device->Depth == 4 && Device->HighNibble) Device->DrawPixelFast = DrawPixel4FastHigh;
else if (Device->Depth == 4) Device->DrawPixelFast = DrawPixel4Fast;
else if (Device->Depth == 8) Device->DrawPixelFast = DrawPixel8Fast;
else if (Device->Depth == 16) Device->DrawPixelFast = DrawPixel16Fast;
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) Device->DrawPixelFast = DrawPixel18Fast;
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) Device->DrawPixelFast = DrawPixel24Fast;
}
// allocate FB unless explicitely asked not to
if (!(Device->Alloc & GDS_ALLOC_NONE)) {
@@ -188,7 +260,7 @@ bool GDS_Init( struct GDS_Device* Device ) {
.channel = Device->Backlight.Channel,
.duty = Device->Backlight.PWM,
.gpio_num = Device->Backlight.Pin,
.speed_mode = LEDC_HIGH_SPEED_MODE,
.speed_mode = LEDC_SPEED_MODE,
.hpoint = 0,
.timer_sel = PWMConfig.Timer,
};
@@ -231,8 +303,8 @@ void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
if (Device->SetContrast) Device->SetContrast( Device, Contrast );
else if (Device->Backlight.Pin >= 0) {
Device->Backlight.PWM = PWMConfig.Max * powf(Contrast / 255.0, 3);
ledc_set_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel, Device->Backlight.PWM );
ledc_update_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel );
ledc_set_duty( LEDC_SPEED_MODE, Device->Backlight.Channel, Device->Backlight.PWM );
ledc_update_duty( LEDC_SPEED_MODE, Device->Backlight.Channel );
}
}

View File

@@ -45,7 +45,7 @@ __attribute__( ( always_inline ) ) static inline void SwapInt( int* a, int* b )
}
void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
DrawPixelFast( Device, X, Y, Color );
Device->DrawPixelFast( Device, X, Y, Color );
}
void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ) {
@@ -63,7 +63,7 @@ void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Colo
if (y < 0) y = 0;
else if (y >= Device->Height) y = Device->Height - 1;
for ( ; x < XEnd; x++ ) DrawPixelFast( Device, x, y, Color );
for ( ; x < XEnd; x++ ) Device->DrawPixelFast( Device, x, y, Color );
}
void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color ) {
@@ -97,7 +97,7 @@ static inline void DrawWideLine( struct GDS_Device* Device, int x0, int y0, int
for ( ; x < x1; x++ ) {
if ( IsPixelVisible( Device, x, y ) == true ) {
DrawPixelFast( Device, x, y, Color );
Device->DrawPixelFast( Device, x, y, Color );
}
if ( Error > 0 ) {
@@ -126,7 +126,7 @@ static inline void DrawTallLine( struct GDS_Device* Device, int x0, int y0, int
for ( ; y < y1; y++ ) {
if ( IsPixelVisible( Device, x, y ) == true ) {
DrawPixelFast( Device, x, y, Color );
Device->DrawPixelFast( Device, x, y, Color );
}
if ( Error > 0 ) {
@@ -213,37 +213,65 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
iptr += Height;
}
}
} else if (Device->Depth == 4) {
} else if (Device->Depth == 4) {
uint8_t *optr = Device->Framebuffer;
int LineLen = Device->Width >> 1;
Height >>= 3;
Color &= 0x0f;
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
uint8_t Byte = BitReverseTable256[*Data++];
// we need to linearize code to let compiler better optimize
if (c & 0x01) {
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen;
} else {
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
}
// end of a column, move to next one
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
if (Device->HighNibble) {
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
uint8_t Byte = BitReverseTable256[*Data++];
// we need to linearize code to let compiler better optimize
if (c & 0x01) {
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
} else {
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen;
}
// end of a column, move to next one
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
}
} else {
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
uint8_t Byte = BitReverseTable256[*Data++];
// we need to linearize code to let compiler better optimize
if (c & 0x01) {
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen;
} else {
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
}
// end of a column, move to next one
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
}
}
} else if (Device->Depth == 8) {
uint8_t *optr = Device->Framebuffer;
@@ -320,14 +348,14 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
// don't know bitdepth, use brute-force solution
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
uint8_t Byte = *Data++;
DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
Device->DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
Device->DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
Device->DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
Device->DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
Device->DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
Device->DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
Device->DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
Device->DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
if (++r == Height) { c++; r = 0; }
}
/* for better understanding, here is the mundane version

View File

@@ -98,6 +98,7 @@ struct GDS_Device {
uint16_t Width, TextWidth;
uint16_t Height;
uint8_t Depth, Mode;
bool HighNibble;
uint8_t Alloc;
uint8_t* Framebuffer;
@@ -155,69 +156,9 @@ static inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
return Result;
}
static inline void DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint32_t YBit = ( Y & 0x07 );
uint8_t* FBOffset;
/*
* We only need to modify the Y coordinate since the pitch
* of the screen is the same as the width.
* Dividing Y by 8 gives us which row the pixel is in but not
* the bit position.
*/
Y>>= 3;
FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
if ( Color == GDS_COLOR_XOR ) {
*FBOffset ^= BIT( YBit );
} else {
*FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( YBit ) : *FBOffset | BIT( YBit );
}
}
static inline void DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
}
static inline void DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
Device->Framebuffer[Y * Device->Width + X] = Color;
}
// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R
static inline void DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
*FBOffset = __builtin_bswap16(Color);
}
// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB
static inline void DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f;
}
// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB
static inline void DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color;
}
static inline void IRAM_ATTR DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
if (Device->DrawPixelFast) Device->DrawPixelFast( Device, X, Y, Color );
else if (Device->Depth == 4) DrawPixel4Fast( Device, X, Y, Color );
else if (Device->Depth == 1) DrawPixel1Fast( Device, X, Y, Color );
else if (Device->Depth == 16) DrawPixel16Fast( Device, X, Y, Color );
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) DrawPixel18Fast( Device, X, Y, Color );
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) DrawPixel24Fast( Device, X, Y, Color );
else if (Device->Depth == 8) DrawPixel8Fast( Device, X, Y, Color );
}
static inline void IRAM_ATTR DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
if ( IsPixelVisible( Device, x, y ) == true ) {
DrawPixelFast( Device, x, y, Color );
Device->DrawPixelFast( Device, x, y, Color );
}
}

View File

@@ -108,7 +108,7 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex
int Y_min = max(0, Device->Lines[N].Y), Y_max = max(0, Device->Lines[N].Y + Device->Lines[N].Font->Height);
for (int c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->TextWidth; c++)
for (int y = Y_min; y < Y_max; y++)
DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
Device->DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
}
GDS_FontDrawString( Device, X, Device->Lines[N].Y, Text, GDS_COLOR_WHITE );

View File

@@ -15,6 +15,7 @@
#include "platform_config.h"
#include "tools.h"
#include "display.h"
#include "services.h"
#include "gds.h"
#include "gds_default_if.h"
#include "gds_draw.h"
@@ -62,6 +63,7 @@ static EXT_RAM_ATTR struct {
} displayer;
static const char *known_drivers[] = {"SH1106",
"SH1122",
"SSD1306",
"SSD1322",
"SSD1326",
@@ -73,11 +75,13 @@ static const char *known_drivers[] = {"SH1106",
"ILI9341",
NULL
};
static void displayer_task(void *args);
static void display_sleep(void);
struct GDS_Device *display;
extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect;
GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect, NULL };
extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SH1122_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect;
GDS_DetectFunc *drivers[] = { SH1106_Detect, SH1122_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect, NULL };
/****************************************************************************************
*
@@ -174,11 +178,21 @@ void display_init(char *welcome) {
if (height <= 64 && width > height * 2) displayer.artwork.offset = width - height - ARTWORK_BORDER;
PARSE_PARAM(displayer.metadata_config, "artwork", ':', displayer.artwork.fit);
}
// and finally register ourselves to power off upon deep sleep
services_sleep_setsuspend(display_sleep);
}
free(config);
}
/****************************************************************************************
*
*/
static void display_sleep(void) {
GDS_DisplayOff(display);
}
/****************************************************************************************
* This is not thread-safe as displayer_task might be in the middle of line drawing
* but it won't crash (I think) and making it thread-safe would be complicated for a

View File

@@ -1,6 +1,8 @@
idf_component_register( SRC_DIRS .
INCLUDE_DIRS .
PRIV_REQUIRES services bt display console tools platform_config
if(IDF_TARGET STREQUAL "esp32")
idf_component_register( SRC_DIRS .
INCLUDE_DIRS .
PRIV_REQUIRES services bt display console tools platform_config
)
endif()

View File

@@ -0,0 +1,172 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_bt_api.h"
#include "bt_app_core.h"
#include "tools.h"
static const char *TAG = "btappcore";
static void bt_app_task_handler(void *arg);
static bool bt_app_send_msg(bt_app_msg_t *msg);
static void bt_app_work_dispatched(bt_app_msg_t *msg);
static xQueueHandle s_bt_app_task_queue;
static bool running;
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback)
{
ESP_LOGV(TAG,"%s event 0x%x, param len %d", __func__, event, param_len);
bt_app_msg_t msg;
memset(&msg, 0, sizeof(bt_app_msg_t));
msg.sig = BT_APP_SIG_WORK_DISPATCH;
msg.event = event;
msg.cb = p_cback;
if (param_len == 0) {
return bt_app_send_msg(&msg);
} else if (p_params && param_len > 0) {
if ((msg.param = clone_obj_psram(p_params, param_len)) != NULL) {
/* check if caller has provided a copy callback to do the deep copy */
if (p_copy_cback) {
p_copy_cback(&msg, msg.param, p_params);
}
return bt_app_send_msg(&msg);
}
}
return false;
}
static bool bt_app_send_msg(bt_app_msg_t *msg)
{
if (msg == NULL) {
return false;
}
if (xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) {
ESP_LOGE(TAG,"%s xQueue send failed", __func__);
return false;
}
return true;
}
static void bt_app_work_dispatched(bt_app_msg_t *msg)
{
if (msg->cb) {
msg->cb(msg->event, msg->param);
}
}
static void bt_app_task_handler(void *arg)
{
bt_app_msg_t msg;
esp_err_t err;
s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
esp_bt_controller_mem_release(ESP_BT_MODE_BLE);
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
ESP_LOGE(TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(err));
goto exit;
}
if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
ESP_LOGE(TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(err));
goto exit;
}
if ((err = esp_bluedroid_init()) != ESP_OK) {
ESP_LOGE(TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(err));
goto exit;
}
if ((err = esp_bluedroid_enable()) != ESP_OK) {
ESP_LOGE(TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(err));
goto exit;
}
/* Bluetooth device name, connection mode and profile set up */
bt_app_work_dispatch((bt_av_hdl_stack_evt_t*) arg, BT_APP_EVT_STACK_UP, NULL, 0, NULL);
#if (CONFIG_BT_SSP_ENABLED)
/* Set default parameters for Secure Simple Pairing */
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
#endif
running = true;
while (running) {
if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) {
ESP_LOGV(TAG,"%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event);
switch (msg.sig) {
case BT_APP_SIG_WORK_DISPATCH:
bt_app_work_dispatched(&msg);
break;
default:
ESP_LOGW(TAG,"%s, unhandled sig: %d", __func__, msg.sig);
break;
}
if (msg.param) {
free(msg.param);
}
} else {
ESP_LOGW(TAG,"No messaged received from queue.");
}
}
ESP_LOGD(TAG, "bt_app_task shutting down");
if (esp_bluedroid_disable() != ESP_OK) goto exit;
// this disable has a sleep timer BTA_DISABLE_DELAY in bt_target.h and
// if we don't wait for it then disable crashes... don't know why
vTaskDelay(2*200 / portTICK_PERIOD_MS);
ESP_LOGD(TAG, "esp_bluedroid_disable called successfully");
if (esp_bluedroid_deinit() != ESP_OK) goto exit;
ESP_LOGD(TAG, "esp_bluedroid_deinit called successfully");
if (esp_bt_controller_disable() != ESP_OK) goto exit;
ESP_LOGD(TAG, "esp_bt_controller_disable called successfully");
if (esp_bt_controller_deinit() != ESP_OK) goto exit;
ESP_LOGD(TAG, "bt stopped successfully");
exit:
vQueueDelete(s_bt_app_task_queue);
running = false;
vTaskDelete(NULL);
}
void bt_app_task_start_up(bt_av_hdl_stack_evt_t* handler)
{
xTaskCreate(bt_app_task_handler, "BtAppT", 4096, handler, configMAX_PRIORITIES - 3, NULL);
}
void bt_app_task_shut_down(void)
{
running = false;
}

View File

@@ -136,7 +136,7 @@ const static actrls_t controls = {
NULL, NULL, // rew, fwd
bt_prev, bt_next, // prev, next
NULL, NULL, NULL, NULL, // left, right, up, down
NULL, NULL, NULL, NULL, NULL, NULL, // pre1-6
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, // pre1-10
bt_volume_down, bt_volume_up, bt_toggle// knob left, knob_right, knob push
};

File diff suppressed because it is too large Load Diff

View File

@@ -25,6 +25,8 @@ static const char * TAG = "bt_app_source";
static const char * BT_RC_CT_TAG="RCCT";
extern int32_t output_bt_data(uint8_t *data, int32_t len);
extern void output_bt_tick(void);
extern void output_bt_stop(void);
extern void output_bt_start(void);
extern char* output_state_str(void);
extern bool output_stopped(void);
extern bool is_recovery_running;
@@ -803,6 +805,7 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START &&
a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
ESP_LOGI(TAG,"a2dp media started successfully.");
output_bt_start();
set_a2dp_media_state(APP_AV_MEDIA_STATE_STARTED);
} else {
// not started succesfully, transfer to idle state
@@ -831,6 +834,7 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_STOP &&
a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
ESP_LOGI(TAG,"a2dp media stopped successfully...");
output_bt_stop();
set_a2dp_media_state(APP_AV_MEDIA_STATE_IDLE);
} else {
ESP_LOGI(TAG,"a2dp media stopping...");

View File

@@ -1,49 +0,0 @@
set(srcs
"heap_caps.c"
"heap_caps_init.c"
"multi_heap.c"
"heap_tlsf.c")
if(NOT CONFIG_HEAP_POISONING_DISABLED)
list(APPEND srcs "multi_heap_poisoning.c")
endif()
if(CONFIG_HEAP_TASK_TRACKING)
list(APPEND srcs "heap_task_info.c")
endif()
if(CONFIG_HEAP_TRACING_STANDALONE)
list(APPEND srcs "heap_trace_standalone.c")
set_source_files_properties(heap_trace_standalone.c
PROPERTIES COMPILE_FLAGS
-Wno-frame-address)
endif()
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS include
LDFRAGMENTS linker.lf
PRIV_REQUIRES soc)
if(CONFIG_HEAP_TRACING)
set(WRAP_FUNCTIONS
calloc
malloc
free
realloc
heap_caps_malloc
heap_caps_free
heap_caps_realloc
heap_caps_malloc_default
heap_caps_realloc_default)
foreach(wrap ${WRAP_FUNCTIONS})
target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=${wrap}")
endforeach()
endif()
if(NOT CMAKE_BUILD_EARLY_EXPANSION)
idf_build_get_property(build_components BUILD_COMPONENTS)
if(freertos IN_LIST build_components)
target_compile_options(${COMPONENT_TARGET} PRIVATE "-DMULTI_HEAP_FREERTOS")
endif()
endif()

View File

@@ -1,74 +0,0 @@
menu "Heap memory debugging"
choice HEAP_CORRUPTION_DETECTION
prompt "Heap corruption detection"
default HEAP_POISONING_DISABLED
help
Enable heap poisoning features to detect heap corruption caused by out-of-bounds access to heap memory.
See the "Heap Memory Debugging" page of the IDF documentation
for a description of each level of heap corruption detection.
config HEAP_POISONING_DISABLED
bool "Basic (no poisoning)"
config HEAP_POISONING_LIGHT
bool "Light impact"
config HEAP_POISONING_COMPREHENSIVE
bool "Comprehensive"
endchoice
choice HEAP_TRACING_DEST
bool "Heap tracing"
default HEAP_TRACING_OFF
help
Enables the heap tracing API defined in esp_heap_trace.h.
This function causes a moderate increase in IRAM code side and a minor increase in heap function
(malloc/free/realloc) CPU overhead, even when the tracing feature is not used.
So it's best to keep it disabled unless tracing is being used.
config HEAP_TRACING_OFF
bool "Disabled"
config HEAP_TRACING_STANDALONE
bool "Standalone"
select HEAP_TRACING
config HEAP_TRACING_TOHOST
bool "Host-based"
select HEAP_TRACING
endchoice
config HEAP_TRACING
bool
default F
help
Enables/disables heap tracing API.
config HEAP_TRACING_STACK_DEPTH
int "Heap tracing stack depth"
range 0 0 if IDF_TARGET_ARCH_RISCV # Disabled for RISC-V due to `__builtin_return_address` limitation
default 0 if IDF_TARGET_ARCH_RISCV
range 0 10
default 2
depends on HEAP_TRACING
help
Number of stack frames to save when tracing heap operation callers.
More stack frames uses more memory in the heap trace buffer (and slows down allocation), but
can provide useful information.
config HEAP_TASK_TRACKING
bool "Enable heap task tracking"
depends on !HEAP_POISONING_DISABLED
help
Enables tracking the task responsible for each heap allocation.
This function depends on heap poisoning being enabled and adds four more bytes of overhead for each block
allocated.
config HEAP_ABORT_WHEN_ALLOCATION_FAILS
bool "Abort if memory allocation fails"
default n
help
When enabled, if a memory allocation operation fails it will cause a system abort.
endmenu

View File

@@ -1,32 +0,0 @@
#
# Component Makefile
#
COMPONENT_OBJS := heap_caps_init.o heap_caps.o multi_heap.o heap_tlsf.o
ifndef CONFIG_HEAP_POISONING_DISABLED
COMPONENT_OBJS += multi_heap_poisoning.o
ifdef CONFIG_HEAP_TASK_TRACKING
COMPONENT_OBJS += heap_task_info.o
endif
endif
ifdef CONFIG_HEAP_TRACING_STANDALONE
COMPONENT_OBJS += heap_trace_standalone.o
endif
ifdef CONFIG_HEAP_TRACING
WRAP_FUNCTIONS = calloc malloc free realloc heap_caps_malloc heap_caps_free heap_caps_realloc heap_caps_malloc_default heap_caps_realloc_default
WRAP_ARGUMENT := -Wl,--wrap=
COMPONENT_ADD_LDFLAGS = -l$(COMPONENT_NAME) $(addprefix $(WRAP_ARGUMENT),$(WRAP_FUNCTIONS))
endif
COMPONENT_ADD_LDFRAGMENTS += linker.lf
CFLAGS += -DMULTI_HEAP_FREERTOS

View File

@@ -1,609 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <sys/param.h>
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "multi_heap.h"
#include "esp_log.h"
#include "heap_private.h"
#include "esp_system.h"
/*
This file, combined with a region allocator that supports multiple heaps, solves the problem that the ESP32 has RAM
that's slightly heterogeneous. Some RAM can be byte-accessed, some allows only 32-bit accesses, some can execute memory,
some can be remapped by the MMU to only be accessed by a certain PID etc. In order to allow the most flexible memory
allocation possible, this code makes it possible to request memory that has certain capabilities. The code will then use
its knowledge of how the memory is configured along with a priority scheme to allocate that memory in the most sane way
possible. This should optimize the amount of RAM accessible to the code without hardwiring addresses.
*/
static esp_alloc_failed_hook_t alloc_failed_callback;
/*
This takes a memory chunk in a region that can be addressed as both DRAM as well as IRAM. It will convert it to
IRAM in such a way that it can be later freed. It assumes both the address as well as the length to be word-aligned.
It returns a region that's 1 word smaller than the region given because it stores the original Dram address there.
*/
IRAM_ATTR static void *dram_alloc_to_iram_addr(void *addr, size_t len)
{
uintptr_t dstart = (uintptr_t)addr; //First word
uintptr_t dend = dstart + len - 4; //Last word
assert(esp_ptr_in_diram_dram((void *)dstart));
assert(esp_ptr_in_diram_dram((void *)dend));
assert((dstart & 3) == 0);
assert((dend & 3) == 0);
#if SOC_DIRAM_INVERTED // We want the word before the result to hold the DRAM address
uint32_t *iptr = esp_ptr_diram_dram_to_iram((void *)dend);
#else
uint32_t *iptr = esp_ptr_diram_dram_to_iram((void *)dstart);
#endif
*iptr = dstart;
return iptr + 1;
}
static void heap_caps_alloc_failed(size_t requested_size, uint32_t caps, const char *function_name)
{
if (alloc_failed_callback) {
alloc_failed_callback(requested_size, caps, function_name);
}
#ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS
esp_system_abort("Memory allocation failed");
#endif
}
esp_err_t heap_caps_register_failed_alloc_callback(esp_alloc_failed_hook_t callback)
{
if (callback == NULL) {
return ESP_ERR_INVALID_ARG;
}
alloc_failed_callback = callback;
return ESP_OK;
}
bool heap_caps_match(const heap_t *heap, uint32_t caps)
{
return heap->heap != NULL && ((get_all_caps(heap) & caps) == caps);
}
/*
Routine to allocate a bit of memory with certain capabilities. caps is a bitfield of MALLOC_CAP_* bits.
*/
IRAM_ATTR void *heap_caps_malloc( size_t size, uint32_t caps )
{
void *ret = NULL;
if (size > HEAP_SIZE_MAX) {
// Avoids int overflow when adding small numbers to size, or
// calculating 'end' from start+size, by limiting 'size' to the possible range
heap_caps_alloc_failed(size, caps, __func__);
return NULL;
}
if (caps & MALLOC_CAP_EXEC) {
//MALLOC_CAP_EXEC forces an alloc from IRAM. There is a region which has both this as well as the following
//caps, but the following caps are not possible for IRAM. Thus, the combination is impossible and we return
//NULL directly, even although our heap capabilities (based on soc_memory_tags & soc_memory_regions) would
//indicate there is a tag for this.
if ((caps & MALLOC_CAP_8BIT) || (caps & MALLOC_CAP_DMA)) {
heap_caps_alloc_failed(size, caps, __func__);
return NULL;
}
caps |= MALLOC_CAP_32BIT; // IRAM is 32-bit accessible RAM
}
if (caps & MALLOC_CAP_32BIT) {
/* 32-bit accessible RAM should allocated in 4 byte aligned sizes
* (Future versions of ESP-IDF should possibly fail if an invalid size is requested)
*/
size = (size + 3) & (~3); // int overflow checked above
}
for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) {
//Iterate over heaps and check capabilities at this priority
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap->heap == NULL) {
continue;
}
if ((heap->caps[prio] & caps) != 0) {
//Heap has at least one of the caps requested. If caps has other bits set that this prio
//doesn't cover, see if they're available in other prios.
if ((get_all_caps(heap) & caps) == caps) {
//This heap can satisfy all the requested capabilities. See if we can grab some memory using it.
if ((caps & MALLOC_CAP_EXEC) && esp_ptr_in_diram_dram((void *)heap->start)) {
//This is special, insofar that what we're going to get back is a DRAM address. If so,
//we need to 'invert' it (lowest address in DRAM == highest address in IRAM and vice-versa) and
//add a pointer to the DRAM equivalent before the address we're going to return.
ret = multi_heap_malloc(heap->heap, size + 4); // int overflow checked above
if (ret != NULL) {
return dram_alloc_to_iram_addr(ret, size + 4); // int overflow checked above
}
} else {
//Just try to alloc, nothing special.
ret = multi_heap_malloc(heap->heap, size);
if (ret != NULL) {
return ret;
}
}
}
}
}
}
heap_caps_alloc_failed(size, caps, __func__);
//Nothing usable found.
return NULL;
}
#define MALLOC_DISABLE_EXTERNAL_ALLOCS -1
//Dual-use: -1 (=MALLOC_DISABLE_EXTERNAL_ALLOCS) disables allocations in external memory, >=0 sets the limit for allocations preferring internal memory.
static int malloc_alwaysinternal_limit=MALLOC_DISABLE_EXTERNAL_ALLOCS;
void heap_caps_malloc_extmem_enable(size_t limit)
{
malloc_alwaysinternal_limit=limit;
}
/*
Default memory allocation implementation. Should return standard 8-bit memory. malloc() essentially resolves to this function.
*/
IRAM_ATTR void *heap_caps_malloc_default( size_t size )
{
if (malloc_alwaysinternal_limit==MALLOC_DISABLE_EXTERNAL_ALLOCS) {
return heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL);
} else {
void *r;
if (size <= (size_t)malloc_alwaysinternal_limit) {
r=heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL );
} else {
r=heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM );
}
if (r==NULL) {
//try again while being less picky
r=heap_caps_malloc( size, MALLOC_CAP_DEFAULT );
}
return r;
}
}
/*
Same for realloc()
Note: keep the logic in here the same as in heap_caps_malloc_default (or merge the two as soon as this gets more complex...)
*/
IRAM_ATTR void *heap_caps_realloc_default( void *ptr, size_t size )
{
if (malloc_alwaysinternal_limit==MALLOC_DISABLE_EXTERNAL_ALLOCS) {
return heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL );
} else {
void *r;
if (size <= (size_t)malloc_alwaysinternal_limit) {
r=heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL );
} else {
r=heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM );
}
if (r==NULL && size>0) {
//We needed to allocate memory, but we didn't. Try again while being less picky.
r=heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT );
}
return r;
}
}
/*
Memory allocation as preference in decreasing order.
*/
IRAM_ATTR void *heap_caps_malloc_prefer( size_t size, size_t num, ... )
{
va_list argp;
va_start( argp, num );
void *r = NULL;
while (num--) {
uint32_t caps = va_arg( argp, uint32_t );
r = heap_caps_malloc( size, caps );
if (r != NULL) {
break;
}
}
va_end( argp );
return r;
}
/*
Memory reallocation as preference in decreasing order.
*/
IRAM_ATTR void *heap_caps_realloc_prefer( void *ptr, size_t size, size_t num, ... )
{
va_list argp;
va_start( argp, num );
void *r = NULL;
while (num--) {
uint32_t caps = va_arg( argp, uint32_t );
r = heap_caps_realloc( ptr, size, caps );
if (r != NULL || size == 0) {
break;
}
}
va_end( argp );
return r;
}
/*
Memory callocation as preference in decreasing order.
*/
IRAM_ATTR void *heap_caps_calloc_prefer( size_t n, size_t size, size_t num, ... )
{
va_list argp;
va_start( argp, num );
void *r = NULL;
while (num--) {
uint32_t caps = va_arg( argp, uint32_t );
r = heap_caps_calloc( n, size, caps );
if (r != NULL) break;
}
va_end( argp );
return r;
}
/* Find the heap which belongs to ptr, or return NULL if it's
not in any heap.
(This confirms if ptr is inside the heap's region, doesn't confirm if 'ptr'
is an allocated block or is some other random address inside the heap.)
*/
IRAM_ATTR static heap_t *find_containing_heap(void *ptr )
{
intptr_t p = (intptr_t)ptr;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap->heap != NULL && p >= heap->start && p < heap->end) {
return heap;
}
}
return NULL;
}
IRAM_ATTR void heap_caps_free( void *ptr)
{
if (ptr == NULL) {
return;
}
if (esp_ptr_in_diram_iram(ptr)) {
//Memory allocated here is actually allocated in the DRAM alias region and
//cannot be de-allocated as usual. dram_alloc_to_iram_addr stores a pointer to
//the equivalent DRAM address, though; free that.
uint32_t *dramAddrPtr = (uint32_t *)ptr;
ptr = (void *)dramAddrPtr[-1];
}
heap_t *heap = find_containing_heap(ptr);
assert(heap != NULL && "free() target pointer is outside heap areas");
multi_heap_free(heap->heap, ptr);
}
IRAM_ATTR void *heap_caps_realloc( void *ptr, size_t size, uint32_t caps)
{
bool ptr_in_diram_case = false;
heap_t *heap = NULL;
void *dram_ptr = NULL;
if (ptr == NULL) {
return heap_caps_malloc(size, caps);
}
if (size == 0) {
heap_caps_free(ptr);
return NULL;
}
if (size > HEAP_SIZE_MAX) {
heap_caps_alloc_failed(size, caps, __func__);
return NULL;
}
//The pointer to memory may be aliased, we need to
//recover the corresponding address before to manage a new allocation:
if(esp_ptr_in_diram_iram((void *)ptr)) {
uint32_t *dram_addr = (uint32_t *)ptr;
dram_ptr = (void *)dram_addr[-1];
heap = find_containing_heap(dram_ptr);
assert(heap != NULL && "realloc() pointer is outside heap areas");
//with pointers that reside on diram space, we avoid using
//the realloc implementation due to address translation issues,
//instead force a malloc/copy/free
ptr_in_diram_case = true;
} else {
heap = find_containing_heap(ptr);
assert(heap != NULL && "realloc() pointer is outside heap areas");
}
// are the existing heap's capabilities compatible with the
// requested ones?
bool compatible_caps = (caps & get_all_caps(heap)) == caps;
if (compatible_caps && !ptr_in_diram_case) {
// try to reallocate this memory within the same heap
// (which will resize the block if it can)
void *r = multi_heap_realloc(heap->heap, ptr, size);
if (r != NULL) {
return r;
}
}
// if we couldn't do that, try to see if we can reallocate
// in a different heap with requested capabilities.
void *new_p = heap_caps_malloc(size, caps);
if (new_p != NULL) {
size_t old_size = 0;
//If we're dealing with aliased ptr, information regarding its containing
//heap can only be obtained with translated address.
if(ptr_in_diram_case) {
old_size = multi_heap_get_allocated_size(heap->heap, dram_ptr);
} else {
old_size = multi_heap_get_allocated_size(heap->heap, ptr);
}
assert(old_size > 0);
memcpy(new_p, ptr, MIN(size, old_size));
heap_caps_free(ptr);
return new_p;
}
heap_caps_alloc_failed(size, caps, __func__);
return NULL;
}
IRAM_ATTR void *heap_caps_calloc( size_t n, size_t size, uint32_t caps)
{
void *result;
size_t size_bytes;
if (__builtin_mul_overflow(n, size, &size_bytes)) {
return NULL;
}
result = heap_caps_malloc(size_bytes, caps);
if (result != NULL) {
bzero(result, size_bytes);
}
return result;
}
size_t heap_caps_get_total_size(uint32_t caps)
{
size_t total_size = 0;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap_caps_match(heap, caps)) {
total_size += (heap->end - heap->start);
}
}
return total_size;
}
size_t heap_caps_get_free_size( uint32_t caps )
{
size_t ret = 0;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap_caps_match(heap, caps)) {
ret += multi_heap_free_size(heap->heap);
}
}
return ret;
}
size_t heap_caps_get_minimum_free_size( uint32_t caps )
{
size_t ret = 0;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap_caps_match(heap, caps)) {
ret += multi_heap_minimum_free_size(heap->heap);
}
}
return ret;
}
size_t heap_caps_get_largest_free_block( uint32_t caps )
{
multi_heap_info_t info;
heap_caps_get_info(&info, caps);
return info.largest_free_block;
}
void heap_caps_get_info( multi_heap_info_t *info, uint32_t caps )
{
bzero(info, sizeof(multi_heap_info_t));
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap_caps_match(heap, caps)) {
multi_heap_info_t hinfo;
multi_heap_get_info(heap->heap, &hinfo);
info->total_free_bytes += hinfo.total_free_bytes;
info->total_allocated_bytes += hinfo.total_allocated_bytes;
info->largest_free_block = MAX(info->largest_free_block,
hinfo.largest_free_block);
info->minimum_free_bytes += hinfo.minimum_free_bytes;
info->allocated_blocks += hinfo.allocated_blocks;
info->free_blocks += hinfo.free_blocks;
info->total_blocks += hinfo.total_blocks;
}
}
}
void heap_caps_print_heap_info( uint32_t caps )
{
multi_heap_info_t info;
printf("Heap summary for capabilities 0x%08X:\n", caps);
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap_caps_match(heap, caps)) {
multi_heap_get_info(heap->heap, &info);
printf(" At 0x%08x len %d free %d allocated %d min_free %d\n",
heap->start, heap->end - heap->start, info.total_free_bytes, info.total_allocated_bytes, info.minimum_free_bytes);
printf(" largest_free_block %d alloc_blocks %d free_blocks %d total_blocks %d\n",
info.largest_free_block, info.allocated_blocks,
info.free_blocks, info.total_blocks);
}
}
printf(" Totals:\n");
heap_caps_get_info(&info, caps);
printf(" free %d allocated %d min_free %d largest_free_block %d\n", info.total_free_bytes, info.total_allocated_bytes, info.minimum_free_bytes, info.largest_free_block);
}
bool heap_caps_check_integrity(uint32_t caps, bool print_errors)
{
bool all_heaps = caps & MALLOC_CAP_INVALID;
bool valid = true;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap->heap != NULL
&& (all_heaps || (get_all_caps(heap) & caps) == caps)) {
valid = multi_heap_check(heap->heap, print_errors) && valid;
}
}
return valid;
}
bool heap_caps_check_integrity_all(bool print_errors)
{
return heap_caps_check_integrity(MALLOC_CAP_INVALID, print_errors);
}
bool heap_caps_check_integrity_addr(intptr_t addr, bool print_errors)
{
heap_t *heap = find_containing_heap((void *)addr);
if (heap == NULL) {
return false;
}
return multi_heap_check(heap->heap, print_errors);
}
void heap_caps_dump(uint32_t caps)
{
bool all_heaps = caps & MALLOC_CAP_INVALID;
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap->heap != NULL
&& (all_heaps || (get_all_caps(heap) & caps) == caps)) {
multi_heap_dump(heap->heap);
}
}
}
void heap_caps_dump_all(void)
{
heap_caps_dump(MALLOC_CAP_INVALID);
}
size_t heap_caps_get_allocated_size( void *ptr )
{
heap_t *heap = find_containing_heap(ptr);
size_t size = multi_heap_get_allocated_size(heap->heap, ptr);
return size;
}
IRAM_ATTR void *heap_caps_aligned_alloc(size_t alignment, size_t size, uint32_t caps)
{
void *ret = NULL;
if(!alignment) {
return NULL;
}
//Alignment must be a power of two:
if((alignment & (alignment - 1)) != 0) {
return NULL;
}
if (size > HEAP_SIZE_MAX) {
// Avoids int overflow when adding small numbers to size, or
// calculating 'end' from start+size, by limiting 'size' to the possible range
heap_caps_alloc_failed(size, caps, __func__);
return NULL;
}
for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) {
//Iterate over heaps and check capabilities at this priority
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (heap->heap == NULL) {
continue;
}
if ((heap->caps[prio] & caps) != 0) {
//Heap has at least one of the caps requested. If caps has other bits set that this prio
//doesn't cover, see if they're available in other prios.
if ((get_all_caps(heap) & caps) == caps) {
//Just try to alloc, nothing special.
ret = multi_heap_aligned_alloc(heap->heap, size, alignment);
if (ret != NULL) {
return ret;
}
}
}
}
}
heap_caps_alloc_failed(size, caps, __func__);
//Nothing usable found.
return NULL;
}
IRAM_ATTR void heap_caps_aligned_free(void *ptr)
{
heap_caps_free(ptr);
}
void *heap_caps_aligned_calloc(size_t alignment, size_t n, size_t size, uint32_t caps)
{
size_t size_bytes;
if (__builtin_mul_overflow(n, size, &size_bytes)) {
return NULL;
}
void *ptr = heap_caps_aligned_alloc(alignment,size_bytes, caps);
if(ptr != NULL) {
memset(ptr, 0, size_bytes);
}
return ptr;
}

View File

@@ -1,241 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "heap_private.h"
#include <assert.h>
#include <string.h>
#include <sys/lock.h>
#include "esp_log.h"
#include "multi_heap.h"
#include "multi_heap_platform.h"
#include "esp_heap_caps_init.h"
#include "soc/soc_memory_layout.h"
static const char *TAG = "heap_init";
/* Linked-list of registered heaps */
struct registered_heap_ll registered_heaps;
static void register_heap(heap_t *region)
{
size_t heap_size = region->end - region->start;
assert(heap_size <= HEAP_SIZE_MAX);
region->heap = multi_heap_register((void *)region->start, heap_size);
if (region->heap != NULL) {
ESP_EARLY_LOGD(TAG, "New heap initialised at %p", region->heap);
}
}
void heap_caps_enable_nonos_stack_heaps(void)
{
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
// Assume any not-yet-registered heap is
// a nonos-stack heap
if (heap->heap == NULL) {
register_heap(heap);
if (heap->heap != NULL) {
multi_heap_set_lock(heap->heap, &heap->heap_mux);
}
}
}
}
/* Initialize the heap allocator to use all of the memory not
used by static data or reserved for other purposes
*/
void heap_caps_init(void)
{
/* Get the array of regions that we can use for heaps
(with reserved memory removed already.)
*/
size_t num_regions = soc_get_available_memory_region_max_count();
soc_memory_region_t regions[num_regions];
num_regions = soc_get_available_memory_regions(regions);
//The heap allocator will treat every region given to it as separate. In order to get bigger ranges of contiguous memory,
//it's useful to coalesce adjacent regions that have the same type.
for (size_t i = 1; i < num_regions; i++) {
soc_memory_region_t *a = &regions[i - 1];
soc_memory_region_t *b = &regions[i];
if (b->start == (intptr_t)(a->start + a->size) && b->type == a->type ) {
a->type = -1;
b->start = a->start;
b->size += a->size;
}
}
/* Count the heaps left after merging */
size_t num_heaps = 0;
for (size_t i = 0; i < num_regions; i++) {
if (regions[i].type != -1) {
num_heaps++;
}
}
/* Start by allocating the registered heap data on the stack.
Once we have a heap to copy it to, we will copy it to a heap buffer.
*/
heap_t temp_heaps[num_heaps];
size_t heap_idx = 0;
ESP_EARLY_LOGI(TAG, "Initializing. RAM available for dynamic allocation:");
for (size_t i = 0; i < num_regions; i++) {
soc_memory_region_t *region = &regions[i];
const soc_memory_type_desc_t *type = &soc_memory_types[region->type];
heap_t *heap = &temp_heaps[heap_idx];
if (region->type == -1) {
continue;
}
heap_idx++;
assert(heap_idx <= num_heaps);
memcpy(heap->caps, type->caps, sizeof(heap->caps));
heap->start = region->start;
heap->end = region->start + region->size;
MULTI_HEAP_LOCK_INIT(&heap->heap_mux);
if (type->startup_stack) {
/* Will be registered when OS scheduler starts */
heap->heap = NULL;
} else {
register_heap(heap);
}
SLIST_NEXT(heap, next) = NULL;
ESP_EARLY_LOGI(TAG, "At %08X len %08X (%d KiB): %s",
region->start, region->size, region->size / 1024, type->name);
}
assert(heap_idx == num_heaps);
/* Allocate the permanent heap data that we'll use as a linked list at runtime.
Allocate this part of data contiguously, even though it's a linked list... */
assert(SLIST_EMPTY(&registered_heaps));
heap_t *heaps_array = NULL;
for (size_t i = 0; i < num_heaps; i++) {
if (heap_caps_match(&temp_heaps[i], MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL)) {
/* use the first DRAM heap which can fit the data */
heaps_array = multi_heap_malloc(temp_heaps[i].heap, sizeof(heap_t) * num_heaps);
if (heaps_array != NULL) {
break;
}
}
}
assert(heaps_array != NULL); /* if NULL, there's not enough free startup heap space */
memcpy(heaps_array, temp_heaps, sizeof(heap_t)*num_heaps);
/* Iterate the heaps and set their locks, also add them to the linked list. */
for (size_t i = 0; i < num_heaps; i++) {
if (heaps_array[i].heap != NULL) {
multi_heap_set_lock(heaps_array[i].heap, &heaps_array[i].heap_mux);
}
if (i == 0) {
SLIST_INSERT_HEAD(&registered_heaps, &heaps_array[0], next);
} else {
SLIST_INSERT_AFTER(&heaps_array[i-1], &heaps_array[i], next);
}
}
}
esp_err_t heap_caps_add_region(intptr_t start, intptr_t end)
{
if (start == 0) {
return ESP_ERR_INVALID_ARG;
}
for (size_t i = 0; i < soc_memory_region_count; i++) {
const soc_memory_region_t *region = &soc_memory_regions[i];
// Test requested start only as 'end' may be in a different region entry, assume 'end' has same caps
if (region->start <= start && (intptr_t)(region->start + region->size) > start) {
const uint32_t *caps = soc_memory_types[region->type].caps;
return heap_caps_add_region_with_caps(caps, start, end);
}
}
return ESP_ERR_NOT_FOUND;
}
esp_err_t heap_caps_add_region_with_caps(const uint32_t caps[], intptr_t start, intptr_t end)
{
esp_err_t err = ESP_FAIL;
if (caps == NULL || start == 0 || end == 0 || end <= start) {
return ESP_ERR_INVALID_ARG;
}
//Check if region overlaps the start and/or end of an existing region. If so, the
//region is invalid (or maybe added twice)
/*
* assume that in on region, start must be less than end (cannot equal to) !!
* Specially, the 4th scenario can be allowed. For example, allocate memory from heap,
* then change the capability and call this function to create a new region for special
* application.
* In the following chart, 'start = start' and 'end = end' is contained in 3rd scenario.
* This all equal scenario is incorrect because the same region cannot be add twice. For example,
* add the .bss memory to region twice, if not do the check, it will cause exception.
*
* the existing heap region s(tart) e(nd)
* |----------------------|
* 1.add region [Correct] (s1<s && e1<=s) |-----|
* 2.add region [Incorrect] (s2<=s && s<e2<=e) |---------------|
* 3.add region [Incorrect] (s3<=s && e<e3) |-------------------------------------|
* 4 add region [Correct] (s<s4<e && s<e4<=e) |-------|
* 5.add region [Incorrect] (s<s5<e && e<e5) |----------------------------|
* 6.add region [Correct] (e<=s6 && e<e6) |----|
*/
heap_t *heap;
SLIST_FOREACH(heap, &registered_heaps, next) {
if ((start <= heap->start && end > heap->start)
|| (start < heap->end && end > heap->end)) {
return ESP_FAIL;
}
}
heap_t *p_new = heap_caps_malloc(sizeof(heap_t), MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT);
if (p_new == NULL) {
err = ESP_ERR_NO_MEM;
goto done;
}
memcpy(p_new->caps, caps, sizeof(p_new->caps));
p_new->start = start;
p_new->end = end;
MULTI_HEAP_LOCK_INIT(&p_new->heap_mux);
p_new->heap = multi_heap_register((void *)start, end - start);
SLIST_NEXT(p_new, next) = NULL;
if (p_new->heap == NULL) {
err = ESP_ERR_INVALID_SIZE;
goto done;
}
multi_heap_set_lock(p_new->heap, &p_new->heap_mux);
/* (This insertion is atomic to registered_heaps, so
we don't need to worry about thread safety for readers,
only for writers. */
static multi_heap_lock_t registered_heaps_write_lock = MULTI_HEAP_LOCK_STATIC_INITIALIZER;
MULTI_HEAP_LOCK(&registered_heaps_write_lock);
SLIST_INSERT_HEAD(&registered_heaps, p_new, next);
MULTI_HEAP_UNLOCK(&registered_heaps_write_lock);
err = ESP_OK;
done:
if (err != ESP_OK) {
free(p_new);
}
return err;
}

View File

@@ -1,77 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <soc/soc_memory_layout.h>
#include "multi_heap.h"
#include "multi_heap_platform.h"
#include "sys/queue.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Some common heap registration data structures used
for heap_caps_init.c to share heap information with heap_caps.c
*/
#define HEAP_SIZE_MAX (SOC_MAX_CONTIGUOUS_RAM_SIZE)
/* Type for describing each registered heap */
typedef struct heap_t_ {
uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS]; ///< Capabilities for the type of memory in this heap (as a prioritised set). Copied from soc_memory_types so it's in RAM not flash.
intptr_t start;
intptr_t end;
multi_heap_lock_t heap_mux;
multi_heap_handle_t heap;
SLIST_ENTRY(heap_t_) next;
} heap_t;
/* All registered heaps.
Forms a single linked list, even though most entries are contiguous.
This means at the expense of 4 bytes per heap, new heaps can be
added at runtime in a fast & thread-safe way.
*/
extern SLIST_HEAD(registered_heap_ll, heap_t_) registered_heaps;
bool heap_caps_match(const heap_t *heap, uint32_t caps);
/* return all possible capabilities (across all priorities) for a given heap */
inline static IRAM_ATTR uint32_t get_all_caps(const heap_t *heap)
{
if (heap->heap == NULL) {
return 0;
}
uint32_t all_caps = 0;
for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) {
all_caps |= heap->caps[prio];
}
return all_caps;
}
/*
Because we don't want to add _another_ known allocation method to the stack of functions to trace wrt memory tracing,
these are declared private. The newlib malloc()/realloc() implementation also calls these, so they are declared
separately in newlib/syscalls.c.
*/
void *heap_caps_realloc_default(void *p, size_t size);
void *heap_caps_malloc_default(size_t size);
#ifdef __cplusplus
}
#endif

View File

@@ -1,129 +0,0 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <multi_heap.h>
#include "multi_heap_internal.h"
#include "heap_private.h"
#include "esp_heap_task_info.h"
#ifdef CONFIG_HEAP_TASK_TRACKING
/*
* Return per-task heap allocation totals and lists of blocks.
*
* For each task that has allocated memory from the heap, return totals for
* allocations within regions matching one or more sets of capabilities.
*
* Optionally also return an array of structs providing details about each
* block allocated by one or more requested tasks, or by all tasks.
*
* Returns the number of block detail structs returned.
*/
size_t heap_caps_get_per_task_info(heap_task_info_params_t *params)
{
heap_t *reg;
heap_task_block_t *blocks = params->blocks;
size_t count = *params->num_totals;
size_t remaining = params->max_blocks;
// Clear out totals for any prepopulated tasks.
if (params->totals) {
for (size_t i = 0; i < count; ++i) {
for (size_t type = 0; type < NUM_HEAP_TASK_CAPS; ++type) {
params->totals[i].size[type] = 0;
params->totals[i].count[type] = 0;
}
}
}
SLIST_FOREACH(reg, &registered_heaps, next) {
multi_heap_handle_t heap = reg->heap;
if (heap == NULL) {
continue;
}
// Find if the capabilities of this heap region match on of the desired
// sets of capabilities.
uint32_t caps = get_all_caps(reg);
uint32_t type;
for (type = 0; type < NUM_HEAP_TASK_CAPS; ++type) {
if ((caps & params->mask[type]) == params->caps[type]) {
break;
}
}
if (type == NUM_HEAP_TASK_CAPS) {
continue;
}
multi_heap_block_handle_t b = multi_heap_get_first_block(heap);
multi_heap_internal_lock(heap);
for ( ; b ; b = multi_heap_get_next_block(heap, b)) {
if (multi_heap_is_free(b)) {
continue;
}
void *p = multi_heap_get_block_address(b); // Safe, only arithmetic
size_t bsize = multi_heap_get_allocated_size(heap, p); // Validates
TaskHandle_t btask = (TaskHandle_t)multi_heap_get_block_owner(b);
// Accumulate per-task allocation totals.
if (params->totals) {
size_t i;
for (i = 0; i < count; ++i) {
if (params->totals[i].task == btask) {
break;
}
}
if (i < count) {
params->totals[i].size[type] += bsize;
params->totals[i].count[type] += 1;
}
else {
if (count < params->max_totals) {
params->totals[count].task = btask;
params->totals[count].size[type] = bsize;
params->totals[i].count[type] = 1;
++count;
}
}
}
// Return details about allocated blocks for selected tasks.
if (blocks && remaining > 0) {
if (params->tasks) {
size_t i;
for (i = 0; i < params->num_tasks; ++i) {
if (btask == params->tasks[i]) {
break;
}
}
if (i == params->num_tasks) {
continue;
}
}
blocks->task = btask;
blocks->address = p;
blocks->size = bsize;
++blocks;
--remaining;
}
}
multi_heap_internal_unlock(heap);
}
*params->num_totals = count;
return params->max_blocks - remaining;
}
#endif // CONFIG_HEAP_TASK_TRACKING

File diff suppressed because it is too large Load Diff

View File

@@ -1,119 +0,0 @@
/*
** Two Level Segregated Fit memory allocator, version 3.1.
** Written by Matthew Conte
** http://tlsf.baisoku.org
**
** Based on the original documentation by Miguel Masmano:
** http://www.gii.upv.es/tlsf/main/docs
**
** This implementation was written to the specification
** of the document, therefore no GPL restrictions apply.
**
** Copyright (c) 2006-2016, Matthew Conte
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** * Neither the name of the copyright holder nor the
** names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <assert.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include "heap_tlsf_config.h"
#if defined(__cplusplus)
extern "C" {
#endif
/*
** Cast and min/max macros.
*/
#define tlsf_cast(t, exp) ((t) (exp))
#define tlsf_min(a, b) ((a) < (b) ? (a) : (b))
#define tlsf_max(a, b) ((a) > (b) ? (a) : (b))
/* A type used for casting when doing pointer arithmetic. */
typedef ptrdiff_t tlsfptr_t;
typedef struct block_header_t
{
/* Points to the previous physical block. */
struct block_header_t* prev_phys_block;
/* The size of this block, excluding the block header. */
size_t size;
/* Next and previous free blocks. */
struct block_header_t* next_free;
struct block_header_t* prev_free;
} block_header_t;
#include "heap_tlsf_block_functions.h"
/* tlsf_t: a TLSF structure. Can contain 1 to N pools. */
/* pool_t: a block of memory that TLSF can manage. */
typedef void* tlsf_t;
typedef void* pool_t;
/* Create/destroy a memory pool. */
tlsf_t tlsf_create(void* mem, size_t max_bytes);
tlsf_t tlsf_create_with_pool(void* mem, size_t pool_bytes, size_t max_bytes);
pool_t tlsf_get_pool(tlsf_t tlsf);
/* Add/remove memory pools. */
pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes);
void tlsf_remove_pool(tlsf_t tlsf, pool_t pool);
/* malloc/memalign/realloc/free replacements. */
void* tlsf_malloc(tlsf_t tlsf, size_t size);
void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t size);
void* tlsf_memalign_offs(tlsf_t tlsf, size_t align, size_t size, size_t offset);
void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size);
void tlsf_free(tlsf_t tlsf, void* ptr);
/* Returns internal block size, not original request size */
size_t tlsf_block_size(void* ptr);
/* Overheads/limits of internal structures. */
size_t tlsf_size(tlsf_t tlsf);
size_t tlsf_align_size(void);
size_t tlsf_block_size_min(void);
size_t tlsf_block_size_max(tlsf_t tlsf);
size_t tlsf_pool_overhead(void);
size_t tlsf_alloc_overhead(void);
size_t tlsf_fit_size(tlsf_t tlsf, size_t size);
/* Debugging. */
typedef void (*tlsf_walker)(void* ptr, size_t size, int used, void* user);
void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user);
/* Returns nonzero if any internal consistency check fails. */
int tlsf_check(tlsf_t tlsf);
int tlsf_check_pool(pool_t pool);
#if defined(__cplusplus)
};
#endif

View File

@@ -1,174 +0,0 @@
/*
** Two Level Segregated Fit memory allocator, version 3.1.
** Written by Matthew Conte
** http://tlsf.baisoku.org
**
** Based on the original documentation by Miguel Masmano:
** http://www.gii.upv.es/tlsf/main/docs
**
** This implementation was written to the specification
** of the document, therefore no GPL restrictions apply.
**
** Copyright (c) 2006-2016, Matthew Conte
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** * Neither the name of the copyright holder nor the
** names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
/*
** Data structures and associated constants.
*/
/*
** Since block sizes are always at least a multiple of 4, the two least
** significant bits of the size field are used to store the block status:
** - bit 0: whether block is busy or free
** - bit 1: whether previous block is busy or free
*/
#define block_header_free_bit (1 << 0)
#define block_header_prev_free_bit (1 << 1)
/*
** The size of the block header exposed to used blocks is the size field.
** The prev_phys_block field is stored *inside* the previous free block.
*/
#define block_header_overhead (sizeof(size_t))
/* User data starts directly after the size field in a used block. */
#define block_start_offset (offsetof(block_header_t, size) + sizeof(size_t))
/*
** A free block must be large enough to store its header minus the size of
** the prev_phys_block field, and no larger than the number of addressable
** bits for FL_INDEX.
** The block_size_max macro returns the maximum block for the minimum pool
** use tlsf_block_size_max for a value specific to the pool
*/
#define block_size_min (sizeof(block_header_t) - sizeof(block_header_t*))
#define block_size_max (tlsf_cast(size_t, 1) << FL_INDEX_MAX_MIN)
/*
** block_header_t member functions.
*/
static inline __attribute__((__always_inline__)) size_t block_size(const block_header_t* block)
{
return block->size & ~(block_header_free_bit | block_header_prev_free_bit);
}
static inline __attribute__((__always_inline__)) void block_set_size(block_header_t* block, size_t size)
{
const size_t oldsize = block->size;
block->size = size | (oldsize & (block_header_free_bit | block_header_prev_free_bit));
}
static inline __attribute__((__always_inline__)) int block_is_last(const block_header_t* block)
{
return block_size(block) == 0;
}
static inline __attribute__((__always_inline__)) int block_is_free(const block_header_t* block)
{
return tlsf_cast(int, block->size & block_header_free_bit);
}
static inline __attribute__((__always_inline__)) void block_set_free(block_header_t* block)
{
block->size |= block_header_free_bit;
}
static inline __attribute__((__always_inline__)) void block_set_used(block_header_t* block)
{
block->size &= ~block_header_free_bit;
}
static inline __attribute__((__always_inline__)) int block_is_prev_free(const block_header_t* block)
{
return tlsf_cast(int, block->size & block_header_prev_free_bit);
}
static inline __attribute__((__always_inline__)) void block_set_prev_free(block_header_t* block)
{
block->size |= block_header_prev_free_bit;
}
static inline __attribute__((__always_inline__)) void block_set_prev_used(block_header_t* block)
{
block->size &= ~block_header_prev_free_bit;
}
static inline __attribute__((__always_inline__)) block_header_t* block_from_ptr(const void* ptr)
{
return tlsf_cast(block_header_t*,
tlsf_cast(unsigned char*, ptr) - block_start_offset);
}
static inline __attribute__((__always_inline__)) void* block_to_ptr(const block_header_t* block)
{
return tlsf_cast(void*,
tlsf_cast(unsigned char*, block) + block_start_offset);
}
/* Return location of next block after block of given size. */
static inline __attribute__((__always_inline__)) block_header_t* offset_to_block(const void* ptr, size_t size)
{
return tlsf_cast(block_header_t*, tlsf_cast(tlsfptr_t, ptr) + size);
}
/* Return location of previous block. */
static inline __attribute__((__always_inline__)) block_header_t* block_prev(const block_header_t* block)
{
return block->prev_phys_block;
}
/* Return location of next existing block. */
static inline __attribute__((__always_inline__)) block_header_t* block_next(const block_header_t* block)
{
block_header_t* next = offset_to_block(block_to_ptr(block),
block_size(block) - block_header_overhead);
return next;
}
/* Link a new block with its physical neighbor, return the neighbor. */
static inline __attribute__((__always_inline__)) block_header_t* block_link_next(block_header_t* block)
{
block_header_t* next = block_next(block);
next->prev_phys_block = block;
return next;
}
static inline __attribute__((__always_inline__)) void block_mark_as_free(block_header_t* block)
{
/* Link the block to the next block, first. */
block_header_t* next = block_link_next(block);
block_set_prev_free(next);
block_set_free(block);
}
static inline __attribute__((__always_inline__)) void block_mark_as_used(block_header_t* block)
{
block_header_t* next = block_next(block);
block_set_prev_used(next);
block_set_used(block);
}

View File

@@ -1,66 +0,0 @@
/*
** Two Level Segregated Fit memory allocator, version 3.1.
** Written by Matthew Conte
** http://tlsf.baisoku.org
**
** Based on the original documentation by Miguel Masmano:
** http://www.gii.upv.es/tlsf/main/docs
**
** This implementation was written to the specification
** of the document, therefore no GPL restrictions apply.
**
** Copyright (c) 2006-2016, Matthew Conte
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** * Neither the name of the copyright holder nor the
** names of its contributors may be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
enum tlsf_config
{
/* log2 of number of linear subdivisions of block sizes. Larger
** values require more memory in the control structure. Values of
** 4 or 5 are typical, 3 is for very small pools.
*/
SL_INDEX_COUNT_LOG2_MIN = 3,
/* All allocation sizes and addresses are aligned to 4 bytes. */
ALIGN_SIZE_LOG2 = 2,
ALIGN_SIZE = (1 << ALIGN_SIZE_LOG2),
/*
** We support allocations of sizes up to (1 << FL_INDEX_MAX) bits.
** However, because we linearly subdivide the second-level lists, and
** our minimum size granularity is 4 bytes, it doesn't make sense to
** create first-level lists for sizes smaller than SL_INDEX_COUNT * 4,
** or (1 << (SL_INDEX_COUNT_LOG2 + 2)) bytes, as there we will be
** trying to split size ranges into more slots than we have available.
** Instead, we calculate the minimum threshold size, and place all
** blocks below that size into the 0th first-level list.
** Values below are the absolute minimum to accept a pool addition
*/
FL_INDEX_MAX_MIN = 14, // For a less than 16kB pool
SL_INDEX_COUNT_MIN = (1 << SL_INDEX_COUNT_LOG2_MIN),
FL_INDEX_COUNT_MIN = (FL_INDEX_MAX_MIN - (SL_INDEX_COUNT_LOG2_MIN + ALIGN_SIZE_LOG2) + 1),
};

View File

@@ -1,255 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include <sdkconfig.h>
#define HEAP_TRACE_SRCFILE /* don't warn on inclusion here */
#include "esp_heap_trace.h"
#undef HEAP_TRACE_SRCFILE
#include "esp_attr.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define STACK_DEPTH CONFIG_HEAP_TRACING_STACK_DEPTH
#if CONFIG_HEAP_TRACING_STANDALONE
static portMUX_TYPE trace_mux = portMUX_INITIALIZER_UNLOCKED;
static bool tracing;
static heap_trace_mode_t mode;
/* Buffer used for records, starting at offset 0
*/
static heap_trace_record_t *buffer;
static size_t total_records;
/* Count of entries logged in the buffer.
Maximum total_records
*/
static size_t count;
/* Actual number of allocations logged */
static size_t total_allocations;
/* Actual number of frees logged */
static size_t total_frees;
/* Has the buffer overflowed and lost trace entries? */
static bool has_overflowed = false;
esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t num_records)
{
if (tracing) {
return ESP_ERR_INVALID_STATE;
}
buffer = record_buffer;
total_records = num_records;
memset(buffer, 0, num_records * sizeof(heap_trace_record_t));
return ESP_OK;
}
esp_err_t heap_trace_start(heap_trace_mode_t mode_param)
{
if (buffer == NULL || total_records == 0) {
return ESP_ERR_INVALID_STATE;
}
portENTER_CRITICAL(&trace_mux);
tracing = false;
mode = mode_param;
count = 0;
total_allocations = 0;
total_frees = 0;
has_overflowed = false;
heap_trace_resume();
portEXIT_CRITICAL(&trace_mux);
return ESP_OK;
}
static esp_err_t set_tracing(bool enable)
{
if (tracing == enable) {
return ESP_ERR_INVALID_STATE;
}
tracing = enable;
return ESP_OK;
}
esp_err_t heap_trace_stop(void)
{
return set_tracing(false);
}
esp_err_t heap_trace_resume(void)
{
return set_tracing(true);
}
size_t heap_trace_get_count(void)
{
return count;
}
esp_err_t heap_trace_get(size_t index, heap_trace_record_t *record)
{
if (record == NULL) {
return ESP_ERR_INVALID_STATE;
}
esp_err_t result = ESP_OK;
portENTER_CRITICAL(&trace_mux);
if (index >= count) {
result = ESP_ERR_INVALID_ARG; /* out of range for 'count' */
} else {
memcpy(record, &buffer[index], sizeof(heap_trace_record_t));
}
portEXIT_CRITICAL(&trace_mux);
return result;
}
void heap_trace_dump(void)
{
size_t delta_size = 0;
size_t delta_allocs = 0;
printf("%u allocations trace (%u entry buffer)\n",
count, total_records);
size_t start_count = count;
for (int i = 0; i < count; i++) {
heap_trace_record_t *rec = &buffer[i];
if (rec->address != NULL) {
printf("%d bytes (@ %p) allocated CPU %d ccount 0x%08x caller ",
rec->size, rec->address, rec->ccount & 1, rec->ccount & ~3);
for (int j = 0; j < STACK_DEPTH && rec->alloced_by[j] != 0; j++) {
printf("%p%s", rec->alloced_by[j],
(j < STACK_DEPTH - 1) ? ":" : "");
}
if (mode != HEAP_TRACE_ALL || STACK_DEPTH == 0 || rec->freed_by[0] == NULL) {
delta_size += rec->size;
delta_allocs++;
printf("\n");
} else {
printf("\nfreed by ");
for (int j = 0; j < STACK_DEPTH; j++) {
printf("%p%s", rec->freed_by[j],
(j < STACK_DEPTH - 1) ? ":" : "\n");
}
}
}
}
if (mode == HEAP_TRACE_ALL) {
printf("%u bytes alive in trace (%u/%u allocations)\n",
delta_size, delta_allocs, heap_trace_get_count());
} else {
printf("%u bytes 'leaked' in trace (%u allocations)\n", delta_size, delta_allocs);
}
printf("total allocations %u total frees %u\n", total_allocations, total_frees);
if (start_count != count) { // only a problem if trace isn't stopped before dumping
printf("(NB: New entries were traced while dumping, so trace dump may have duplicate entries.)\n");
}
if (has_overflowed) {
printf("(NB: Buffer has overflowed, so trace data is incomplete.)\n");
}
}
/* Add a new allocation to the heap trace records */
static IRAM_ATTR void record_allocation(const heap_trace_record_t *record)
{
if (!tracing || record->address == NULL) {
return;
}
portENTER_CRITICAL(&trace_mux);
if (tracing) {
if (count == total_records) {
has_overflowed = true;
/* Move the whole buffer back one slot.
This is a bit slow, compared to treating this buffer as a ringbuffer and rotating a head pointer.
However, ringbuffer code gets tricky when we remove elements in mid-buffer (for leak trace mode) while
trying to keep track of an item count that may overflow.
*/
memmove(&buffer[0], &buffer[1], sizeof(heap_trace_record_t) * (total_records -1));
count--;
}
// Copy new record into place
memcpy(&buffer[count], record, sizeof(heap_trace_record_t));
count++;
total_allocations++;
}
portEXIT_CRITICAL(&trace_mux);
}
// remove a record, used when freeing
static void remove_record(int index);
/* record a free event in the heap trace log
For HEAP_TRACE_ALL, this means filling in the freed_by pointer.
For HEAP_TRACE_LEAKS, this means removing the record from the log.
*/
static IRAM_ATTR void record_free(void *p, void **callers)
{
if (!tracing || p == NULL) {
return;
}
portENTER_CRITICAL(&trace_mux);
if (tracing && count > 0) {
total_frees++;
/* search backwards for the allocation record matching this free */
int i;
for (i = count - 1; i >= 0; i--) {
if (buffer[i].address == p) {
break;
}
}
if (i >= 0) {
if (mode == HEAP_TRACE_ALL) {
memcpy(buffer[i].freed_by, callers, sizeof(void *) * STACK_DEPTH);
} else { // HEAP_TRACE_LEAKS
// Leak trace mode, once an allocation is freed we remove it from the list
remove_record(i);
}
}
}
portEXIT_CRITICAL(&trace_mux);
}
/* remove the entry at 'index' from the ringbuffer of saved records */
static IRAM_ATTR void remove_record(int index)
{
if (index < count - 1) {
// Remove the buffer entry from the list
memmove(&buffer[index], &buffer[index+1],
sizeof(heap_trace_record_t) * (total_records - index - 1));
} else {
// For last element, just zero it out to avoid ambiguity
memset(&buffer[index], 0, sizeof(heap_trace_record_t));
}
count--;
}
#include "heap_trace.inc"
#endif /*CONFIG_HEAP_TRACING_STANDALONE*/

View File

@@ -1,402 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include "multi_heap.h"
#include <sdkconfig.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Flags to indicate the capabilities of the various memory systems
*/
#define MALLOC_CAP_EXEC (1<<0) ///< Memory must be able to run executable code
#define MALLOC_CAP_32BIT (1<<1) ///< Memory must allow for aligned 32-bit data accesses
#define MALLOC_CAP_8BIT (1<<2) ///< Memory must allow for 8/16/...-bit data accesses
#define MALLOC_CAP_DMA (1<<3) ///< Memory must be able to accessed by DMA
#define MALLOC_CAP_PID2 (1<<4) ///< Memory must be mapped to PID2 memory space (PIDs are not currently used)
#define MALLOC_CAP_PID3 (1<<5) ///< Memory must be mapped to PID3 memory space (PIDs are not currently used)
#define MALLOC_CAP_PID4 (1<<6) ///< Memory must be mapped to PID4 memory space (PIDs are not currently used)
#define MALLOC_CAP_PID5 (1<<7) ///< Memory must be mapped to PID5 memory space (PIDs are not currently used)
#define MALLOC_CAP_PID6 (1<<8) ///< Memory must be mapped to PID6 memory space (PIDs are not currently used)
#define MALLOC_CAP_PID7 (1<<9) ///< Memory must be mapped to PID7 memory space (PIDs are not currently used)
#define MALLOC_CAP_SPIRAM (1<<10) ///< Memory must be in SPI RAM
#define MALLOC_CAP_INTERNAL (1<<11) ///< Memory must be internal; specifically it should not disappear when flash/spiram cache is switched off
#define MALLOC_CAP_DEFAULT (1<<12) ///< Memory can be returned in a non-capability-specific memory allocation (e.g. malloc(), calloc()) call
#define MALLOC_CAP_IRAM_8BIT (1<<13) ///< Memory must be in IRAM and allow unaligned access
#define MALLOC_CAP_RETENTION (1<<14)
#define MALLOC_CAP_INVALID (1<<31) ///< Memory can't be used / list end marker
/**
* @brief callback called when a allocation operation fails, if registered
* @param size in bytes of failed allocation
* @param caps capabillites requested of failed allocation
* @param function_name function which generated the failure
*/
typedef void (*esp_alloc_failed_hook_t) (size_t size, uint32_t caps, const char * function_name);
/**
* @brief registers a callback function to be invoked if a memory allocation operation fails
* @param callback caller defined callback to be invoked
* @return ESP_OK if callback was registered.
*/
esp_err_t heap_caps_register_failed_alloc_callback(esp_alloc_failed_hook_t callback);
/**
* @brief Allocate a chunk of memory which has the given capabilities
*
* Equivalent semantics to libc malloc(), for capability-aware memory.
*
* In IDF, ``malloc(p)`` is equivalent to ``heap_caps_malloc(p, MALLOC_CAP_8BIT)``.
*
* @param size Size, in bytes, of the amount of memory to allocate
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory to be returned
*
* @return A pointer to the memory allocated on success, NULL on failure
*/
void *heap_caps_malloc(size_t size, uint32_t caps);
/**
* @brief Free memory previously allocated via heap_caps_malloc() or heap_caps_realloc().
*
* Equivalent semantics to libc free(), for capability-aware memory.
*
* In IDF, ``free(p)`` is equivalent to ``heap_caps_free(p)``.
*
* @param ptr Pointer to memory previously returned from heap_caps_malloc() or heap_caps_realloc(). Can be NULL.
*/
void heap_caps_free( void *ptr);
/**
* @brief Reallocate memory previously allocated via heap_caps_malloc() or heap_caps_realloc().
*
* Equivalent semantics to libc realloc(), for capability-aware memory.
*
* In IDF, ``realloc(p, s)`` is equivalent to ``heap_caps_realloc(p, s, MALLOC_CAP_8BIT)``.
*
* 'caps' parameter can be different to the capabilities that any original 'ptr' was allocated with. In this way,
* realloc can be used to "move" a buffer if necessary to ensure it meets a new set of capabilities.
*
* @param ptr Pointer to previously allocated memory, or NULL for a new allocation.
* @param size Size of the new buffer requested, or 0 to free the buffer.
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory desired for the new allocation.
*
* @return Pointer to a new buffer of size 'size' with capabilities 'caps', or NULL if allocation failed.
*/
void *heap_caps_realloc( void *ptr, size_t size, uint32_t caps);
/**
* @brief Allocate a aligned chunk of memory which has the given capabilities
*
* Equivalent semantics to libc aligned_alloc(), for capability-aware memory.
* @param alignment How the pointer received needs to be aligned
* must be a power of two
* @param size Size, in bytes, of the amount of memory to allocate
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory to be returned
*
* @return A pointer to the memory allocated on success, NULL on failure
*
*
*/
void *heap_caps_aligned_alloc(size_t alignment, size_t size, uint32_t caps);
/**
* @brief Used to deallocate memory previously allocated with heap_caps_aligned_alloc
*
* @param ptr Pointer to the memory allocated
* @note This function is deprecated, plase consider using heap_caps_free() instead
*/
void __attribute__((deprecated)) heap_caps_aligned_free(void *ptr);
/**
* @brief Allocate a aligned chunk of memory which has the given capabilities. The initialized value in the memory is set to zero.
*
* @param alignment How the pointer received needs to be aligned
* must be a power of two
* @param n Number of continuing chunks of memory to allocate
* @param size Size, in bytes, of a chunk of memory to allocate
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory to be returned
*
* @return A pointer to the memory allocated on success, NULL on failure
*
*/
void *heap_caps_aligned_calloc(size_t alignment, size_t n, size_t size, uint32_t caps);
/**
* @brief Allocate a chunk of memory which has the given capabilities. The initialized value in the memory is set to zero.
*
* Equivalent semantics to libc calloc(), for capability-aware memory.
*
* In IDF, ``calloc(p)`` is equivalent to ``heap_caps_calloc(p, MALLOC_CAP_8BIT)``.
*
* @param n Number of continuing chunks of memory to allocate
* @param size Size, in bytes, of a chunk of memory to allocate
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory to be returned
*
* @return A pointer to the memory allocated on success, NULL on failure
*/
void *heap_caps_calloc(size_t n, size_t size, uint32_t caps);
/**
* @brief Get the total size of all the regions that have the given capabilities
*
* This function takes all regions capable of having the given capabilities allocated in them
* and adds up the total space they have.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*
* @return total size in bytes
*/
size_t heap_caps_get_total_size(uint32_t caps);
/**
* @brief Get the total free size of all the regions that have the given capabilities
*
* This function takes all regions capable of having the given capabilities allocated in them
* and adds up the free space they have.
*
* Note that because of heap fragmentation it is probably not possible to allocate a single block of memory
* of this size. Use heap_caps_get_largest_free_block() for this purpose.
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*
* @return Amount of free bytes in the regions
*/
size_t heap_caps_get_free_size( uint32_t caps );
/**
* @brief Get the total minimum free memory of all regions with the given capabilities
*
* This adds all the low water marks of the regions capable of delivering the memory
* with the given capabilities.
*
* Note the result may be less than the global all-time minimum available heap of this kind, as "low water marks" are
* tracked per-region. Individual regions' heaps may have reached their "low water marks" at different points in time. However
* this result still gives a "worst case" indication for all-time minimum free heap.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*
* @return Amount of free bytes in the regions
*/
size_t heap_caps_get_minimum_free_size( uint32_t caps );
/**
* @brief Get the largest free block of memory able to be allocated with the given capabilities.
*
* Returns the largest value of ``s`` for which ``heap_caps_malloc(s, caps)`` will succeed.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*
* @return Size of largest free block in bytes.
*/
size_t heap_caps_get_largest_free_block( uint32_t caps );
/**
* @brief Get heap info for all regions with the given capabilities.
*
* Calls multi_heap_info() on all heaps which share the given capabilities. The information returned is an aggregate
* across all matching heaps. The meanings of fields are the same as defined for multi_heap_info_t, except that
* ``minimum_free_bytes`` has the same caveats described in heap_caps_get_minimum_free_size().
*
* @param info Pointer to a structure which will be filled with relevant
* heap metadata.
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*
*/
void heap_caps_get_info( multi_heap_info_t *info, uint32_t caps );
/**
* @brief Print a summary of all memory with the given capabilities.
*
* Calls multi_heap_info on all heaps which share the given capabilities, and
* prints a two-line summary for each, then a total summary.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*
*/
void heap_caps_print_heap_info( uint32_t caps );
/**
* @brief Check integrity of all heap memory in the system.
*
* Calls multi_heap_check on all heaps. Optionally print errors if heaps are corrupt.
*
* Calling this function is equivalent to calling heap_caps_check_integrity
* with the caps argument set to MALLOC_CAP_INVALID.
*
* @param print_errors Print specific errors if heap corruption is found.
*
* @return True if all heaps are valid, False if at least one heap is corrupt.
*/
bool heap_caps_check_integrity_all(bool print_errors);
/**
* @brief Check integrity of all heaps with the given capabilities.
*
* Calls multi_heap_check on all heaps which share the given capabilities. Optionally
* print errors if the heaps are corrupt.
*
* See also heap_caps_check_integrity_all to check all heap memory
* in the system and heap_caps_check_integrity_addr to check memory
* around a single address.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
* @param print_errors Print specific errors if heap corruption is found.
*
* @return True if all heaps are valid, False if at least one heap is corrupt.
*/
bool heap_caps_check_integrity(uint32_t caps, bool print_errors);
/**
* @brief Check integrity of heap memory around a given address.
*
* This function can be used to check the integrity of a single region of heap memory,
* which contains the given address.
*
* This can be useful if debugging heap integrity for corruption at a known address,
* as it has a lower overhead than checking all heap regions. Note that if the corrupt
* address moves around between runs (due to timing or other factors) then this approach
* won't work and you should call heap_caps_check_integrity or
* heap_caps_check_integrity_all instead.
*
* @note The entire heap region around the address is checked, not only the adjacent
* heap blocks.
*
* @param addr Address in memory. Check for corruption in region containing this address.
* @param print_errors Print specific errors if heap corruption is found.
*
* @return True if the heap containing the specified address is valid,
* False if at least one heap is corrupt or the address doesn't belong to a heap region.
*/
bool heap_caps_check_integrity_addr(intptr_t addr, bool print_errors);
/**
* @brief Enable malloc() in external memory and set limit below which
* malloc() attempts are placed in internal memory.
*
* When external memory is in use, the allocation strategy is to initially try to
* satisfy smaller allocation requests with internal memory and larger requests
* with external memory. This sets the limit between the two, as well as generally
* enabling allocation in external memory.
*
* @param limit Limit, in bytes.
*/
void heap_caps_malloc_extmem_enable(size_t limit);
/**
* @brief Allocate a chunk of memory as preference in decreasing order.
*
* @attention The variable parameters are bitwise OR of MALLOC_CAP_* flags indicating the type of memory.
* This API prefers to allocate memory with the first parameter. If failed, allocate memory with
* the next parameter. It will try in this order until allocating a chunk of memory successfully
* or fail to allocate memories with any of the parameters.
*
* @param size Size, in bytes, of the amount of memory to allocate
* @param num Number of variable paramters
*
* @return A pointer to the memory allocated on success, NULL on failure
*/
void *heap_caps_malloc_prefer( size_t size, size_t num, ... );
/**
* @brief Allocate a chunk of memory as preference in decreasing order.
*
* @param ptr Pointer to previously allocated memory, or NULL for a new allocation.
* @param size Size of the new buffer requested, or 0 to free the buffer.
* @param num Number of variable paramters
*
* @return Pointer to a new buffer of size 'size', or NULL if allocation failed.
*/
void *heap_caps_realloc_prefer( void *ptr, size_t size, size_t num, ... );
/**
* @brief Allocate a chunk of memory as preference in decreasing order.
*
* @param n Number of continuing chunks of memory to allocate
* @param size Size, in bytes, of a chunk of memory to allocate
* @param num Number of variable paramters
*
* @return A pointer to the memory allocated on success, NULL on failure
*/
void *heap_caps_calloc_prefer( size_t n, size_t size, size_t num, ... );
/**
* @brief Dump the full structure of all heaps with matching capabilities.
*
* Prints a large amount of output to serial (because of locking limitations,
* the output bypasses stdout/stderr). For each (variable sized) block
* in each matching heap, the following output is printed on a single line:
*
* - Block address (the data buffer returned by malloc is 4 bytes after this
* if heap debugging is set to Basic, or 8 bytes otherwise).
* - Data size (the data size may be larger than the size requested by malloc,
* either due to heap fragmentation or because of heap debugging level).
* - Address of next block in the heap.
* - If the block is free, the address of the next free block is also printed.
*
* @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type
* of memory
*/
void heap_caps_dump(uint32_t caps);
/**
* @brief Dump the full structure of all heaps.
*
* Covers all registered heaps. Prints a large amount of output to serial.
*
* Output is the same as for heap_caps_dump.
*
*/
void heap_caps_dump_all(void);
/**
* @brief Return the size that a particular pointer was allocated with.
*
* @param ptr Pointer to currently allocated heap memory. Must be a pointer value previously
* returned by heap_caps_malloc,malloc,calloc, etc. and not yet freed.
*
* @note The app will crash with an assertion failure if the pointer is not valid.
*
* @return Size of the memory allocated at this block.
*
*/
size_t heap_caps_get_allocated_size( void *ptr );
#ifdef __cplusplus
}
#endif

View File

@@ -1,92 +0,0 @@
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "soc/soc_memory_layout.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Initialize the capability-aware heap allocator.
*
* This is called once in the IDF startup code. Do not call it
* at other times.
*/
void heap_caps_init(void);
/**
* @brief Enable heap(s) in memory regions where the startup stacks are located.
*
* On startup, the pro/app CPUs have a certain memory region they use as stack, so we
* cannot do allocations in the regions these stack frames are. When FreeRTOS is
* completely started, they do not use that memory anymore and heap(s) there can
* be enabled.
*/
void heap_caps_enable_nonos_stack_heaps(void);
/**
* @brief Add a region of memory to the collection of heaps at runtime.
*
* Most memory regions are defined in soc_memory_layout.c for the SoC,
* and are registered via heap_caps_init(). Some regions can't be used
* immediately and are later enabled via heap_caps_enable_nonos_stack_heaps().
*
* Call this function to add a region of memory to the heap at some later time.
*
* This function does not consider any of the "reserved" regions or other data in soc_memory_layout, caller needs to
* consider this themselves.
*
* All memory within the region specified by start & end parameters must be otherwise unused.
*
* The capabilities of the newly registered memory will be determined by the start address, as looked up in the regions
* specified in soc_memory_layout.c.
*
* Use heap_caps_add_region_with_caps() to register a region with custom capabilities.
*
* @param start Start address of new region.
* @param end End address of new region.
*
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if a parameter is invalid, ESP_ERR_NOT_FOUND if the
* specified start address doesn't reside in a known region, or any error returned by heap_caps_add_region_with_caps().
*/
esp_err_t heap_caps_add_region(intptr_t start, intptr_t end);
/**
* @brief Add a region of memory to the collection of heaps at runtime, with custom capabilities.
*
* Similar to heap_caps_add_region(), only custom memory capabilities are specified by the caller.
*
* @param caps Ordered array of capability masks for the new region, in order of priority. Must have length
* SOC_MEMORY_TYPE_NO_PRIOS. Does not need to remain valid after the call returns.
* @param start Start address of new region.
* @param end End address of new region.
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if a parameter is invalid
* - ESP_ERR_NO_MEM if no memory to register new heap.
* - ESP_ERR_INVALID_SIZE if the memory region is too small to fit a heap
* - ESP_FAIL if region overlaps the start and/or end of an existing region
*/
esp_err_t heap_caps_add_region_with_caps(const uint32_t caps[], intptr_t start, intptr_t end);
#ifdef __cplusplus
}
#endif

View File

@@ -1,98 +0,0 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#ifdef CONFIG_HEAP_TASK_TRACKING
#ifdef __cplusplus
extern "C" {
#endif
// This macro controls how much space is provided for partitioning the per-task
// heap allocation info according to one or more sets of heap capabilities.
#define NUM_HEAP_TASK_CAPS 4
/** @brief Structure to collect per-task heap allocation totals partitioned by selected caps */
typedef struct {
TaskHandle_t task; ///< Task to which these totals belong
size_t size[NUM_HEAP_TASK_CAPS]; ///< Total allocations partitioned by selected caps
size_t count[NUM_HEAP_TASK_CAPS]; ///< Number of blocks partitioned by selected caps
} heap_task_totals_t;
/** @brief Structure providing details about a block allocated by a task */
typedef struct {
TaskHandle_t task; ///< Task that allocated the block
void *address; ///< User address of allocated block
uint32_t size; ///< Size of the allocated block
} heap_task_block_t;
/** @brief Structure to provide parameters to heap_caps_get_per_task_info
*
* The 'caps' and 'mask' arrays allow partitioning the per-task heap allocation
* totals by selected sets of heap region capabilities so that totals for
* multiple regions can be accumulated in one scan. The capabilities flags for
* each region ANDed with mask[i] are compared to caps[i] in order; the
* allocations in that region are added to totals->size[i] and totals->count[i]
* for the first i that matches. To collect the totals without any
* partitioning, set mask[0] and caps[0] both to zero. The allocation totals
* are returned in the 'totals' array of heap_task_totals_t structs. To allow
* easily comparing the totals array between consecutive calls, that array can
* be left populated from one call to the next so the order of tasks is the
* same even if some tasks have freed their blocks or have been deleted. The
* number of blocks prepopulated is given by num_totals, which is updated upon
* return. If there are more tasks with allocations than the capacity of the
* totals array (given by max_totals), information for the excess tasks will be
* not be collected. The totals array pointer can be NULL if the totals are
* not desired.
*
* The 'tasks' array holds a list of handles for tasks whose block details are
* to be returned in the 'blocks' array of heap_task_block_t structs. If the
* tasks array pointer is NULL, block details for all tasks will be returned up
* to the capacity of the buffer array, given by max_blocks. The function
* return value tells the number of blocks filled into the array. The blocks
* array pointer can be NULL if block details are not desired, or max_blocks
* can be set to zero.
*/
typedef struct {
int32_t caps[NUM_HEAP_TASK_CAPS]; ///< Array of caps for partitioning task totals
int32_t mask[NUM_HEAP_TASK_CAPS]; ///< Array of masks under which caps must match
TaskHandle_t *tasks; ///< Array of tasks whose block info is returned
size_t num_tasks; ///< Length of tasks array
heap_task_totals_t *totals; ///< Array of structs to collect task totals
size_t *num_totals; ///< Number of task structs currently in array
size_t max_totals; ///< Capacity of array of task totals structs
heap_task_block_t *blocks; ///< Array of task block details structs
size_t max_blocks; ///< Capacity of array of task block info structs
} heap_task_info_params_t;
/**
* @brief Return per-task heap allocation totals and lists of blocks.
*
* For each task that has allocated memory from the heap, return totals for
* allocations within regions matching one or more sets of capabilities.
*
* Optionally also return an array of structs providing details about each
* block allocated by one or more requested tasks, or by all tasks.
*
* @param params Structure to hold all the parameters for the function
* (@see heap_task_info_params_t).
* @return Number of block detail structs returned (@see heap_task_block_t).
*/
extern size_t heap_caps_get_per_task_info(heap_task_info_params_t *params);
#ifdef __cplusplus
}
#endif
#endif // CONFIG_HEAP_TASK_TRACKING

View File

@@ -1,154 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "sdkconfig.h"
#include <stdint.h>
#include <esp_err.h>
#ifdef __cplusplus
extern "C" {
#endif
#if !defined(CONFIG_HEAP_TRACING) && !defined(HEAP_TRACE_SRCFILE)
#warning "esp_heap_trace.h is included but heap tracing is disabled in menuconfig, functions are no-ops"
#endif
#ifndef CONFIG_HEAP_TRACING_STACK_DEPTH
#define CONFIG_HEAP_TRACING_STACK_DEPTH 0
#endif
typedef enum {
HEAP_TRACE_ALL,
HEAP_TRACE_LEAKS,
} heap_trace_mode_t;
/**
* @brief Trace record data type. Stores information about an allocated region of memory.
*/
typedef struct {
uint32_t ccount; ///< CCOUNT of the CPU when the allocation was made. LSB (bit value 1) is the CPU number (0 or 1).
void *address; ///< Address which was allocated
size_t size; ///< Size of the allocation
void *alloced_by[CONFIG_HEAP_TRACING_STACK_DEPTH]; ///< Call stack of the caller which allocated the memory.
void *freed_by[CONFIG_HEAP_TRACING_STACK_DEPTH]; ///< Call stack of the caller which freed the memory (all zero if not freed.)
} heap_trace_record_t;
/**
* @brief Initialise heap tracing in standalone mode.
*
* This function must be called before any other heap tracing functions.
*
* To disable heap tracing and allow the buffer to be freed, stop tracing and then call heap_trace_init_standalone(NULL, 0);
*
* @param record_buffer Provide a buffer to use for heap trace data. Must remain valid any time heap tracing is enabled, meaning
* it must be allocated from internal memory not in PSRAM.
* @param num_records Size of the heap trace buffer, as number of record structures.
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing is currently in progress.
* - ESP_OK Heap tracing initialised successfully.
*/
esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t num_records);
/**
* @brief Initialise heap tracing in host-based mode.
*
* This function must be called before any other heap tracing functions.
*
* @return
* - ESP_ERR_INVALID_STATE Heap tracing is currently in progress.
* - ESP_OK Heap tracing initialised successfully.
*/
esp_err_t heap_trace_init_tohost(void);
/**
* @brief Start heap tracing. All heap allocations & frees will be traced, until heap_trace_stop() is called.
*
* @note heap_trace_init_standalone() must be called to provide a valid buffer, before this function is called.
*
* @note Calling this function while heap tracing is running will reset the heap trace state and continue tracing.
*
* @param mode Mode for tracing.
* - HEAP_TRACE_ALL means all heap allocations and frees are traced.
* - HEAP_TRACE_LEAKS means only suspected memory leaks are traced. (When memory is freed, the record is removed from the trace buffer.)
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE A non-zero-length buffer has not been set via heap_trace_init_standalone().
* - ESP_OK Tracing is started.
*/
esp_err_t heap_trace_start(heap_trace_mode_t mode);
/**
* @brief Stop heap tracing.
*
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing was not in progress.
* - ESP_OK Heap tracing stopped..
*/
esp_err_t heap_trace_stop(void);
/**
* @brief Resume heap tracing which was previously stopped.
*
* Unlike heap_trace_start(), this function does not clear the
* buffer of any pre-existing trace records.
*
* The heap trace mode is the same as when heap_trace_start() was
* last called (or HEAP_TRACE_ALL if heap_trace_start() was never called).
*
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing was already started.
* - ESP_OK Heap tracing resumed.
*/
esp_err_t heap_trace_resume(void);
/**
* @brief Return number of records in the heap trace buffer
*
* It is safe to call this function while heap tracing is running.
*/
size_t heap_trace_get_count(void);
/**
* @brief Return a raw record from the heap trace buffer
*
* @note It is safe to call this function while heap tracing is running, however in HEAP_TRACE_LEAK mode record indexing may
* skip entries unless heap tracing is stopped first.
*
* @param index Index (zero-based) of the record to return.
* @param[out] record Record where the heap trace record will be copied.
* @return
* - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig.
* - ESP_ERR_INVALID_STATE Heap tracing was not initialised.
* - ESP_ERR_INVALID_ARG Index is out of bounds for current heap trace record count.
* - ESP_OK Record returned successfully.
*/
esp_err_t heap_trace_get(size_t index, heap_trace_record_t *record);
/**
* @brief Dump heap trace record data to stdout
*
* @note It is safe to call this function while heap tracing is running, however in HEAP_TRACE_LEAK mode the dump may skip
* entries unless heap tracing is stopped first.
*
*
*/
void heap_trace_dump(void);
#ifdef __cplusplus
}
#endif

View File

@@ -1,200 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include <sdkconfig.h>
#include "soc/soc_memory_layout.h"
#include "esp_attr.h"
/* Encode the CPU ID in the LSB of the ccount value */
inline static uint32_t get_ccount(void)
{
uint32_t ccount = cpu_hal_get_cycle_count() & ~3;
#ifndef CONFIG_FREERTOS_UNICORE
ccount |= xPortGetCoreID();
#endif
return ccount;
}
/* Architecture-specific return value of __builtin_return_address which
* should be interpreted as an invalid address.
*/
#ifdef __XTENSA__
#define HEAP_ARCH_INVALID_PC 0x40000000
#else
#define HEAP_ARCH_INVALID_PC 0x00000000
#endif
// Caller is 2 stack frames deeper than we care about
#define STACK_OFFSET 2
#define TEST_STACK(N) do { \
if (STACK_DEPTH == N) { \
return; \
} \
callers[N] = __builtin_return_address(N+STACK_OFFSET); \
if (!esp_ptr_executable(callers[N]) \
|| callers[N] == (void*) HEAP_ARCH_INVALID_PC) { \
callers[N] = 0; \
return; \
} \
} while(0)
/* Static function to read the call stack for a traced heap call.
Calls to __builtin_return_address are "unrolled" via TEST_STACK macro as gcc requires the
argument to be a compile-time constant.
*/
static IRAM_ATTR __attribute__((noinline)) void get_call_stack(void **callers)
{
bzero(callers, sizeof(void *) * STACK_DEPTH);
TEST_STACK(0);
TEST_STACK(1);
TEST_STACK(2);
TEST_STACK(3);
TEST_STACK(4);
TEST_STACK(5);
TEST_STACK(6);
TEST_STACK(7);
TEST_STACK(8);
TEST_STACK(9);
}
_Static_assert(STACK_DEPTH >= 0 && STACK_DEPTH <= 10, "CONFIG_HEAP_TRACING_STACK_DEPTH must be in range 0-10");
typedef enum {
TRACE_MALLOC_CAPS,
TRACE_MALLOC_DEFAULT
} trace_malloc_mode_t;
void *__real_heap_caps_malloc(size_t size, uint32_t caps);
void *__real_heap_caps_malloc_default( size_t size );
void *__real_heap_caps_realloc_default( void *ptr, size_t size );
/* trace any 'malloc' event */
static IRAM_ATTR __attribute__((noinline)) void *trace_malloc(size_t size, uint32_t caps, trace_malloc_mode_t mode)
{
uint32_t ccount = get_ccount();
void *p;
if ( mode == TRACE_MALLOC_CAPS ) {
p = __real_heap_caps_malloc(size, caps);
} else { //TRACE_MALLOC_DEFAULT
p = __real_heap_caps_malloc_default(size);
}
heap_trace_record_t rec = {
.address = p,
.ccount = ccount,
.size = size,
};
get_call_stack(rec.alloced_by);
record_allocation(&rec);
return p;
}
void __real_heap_caps_free(void *p);
/* trace any 'free' event */
static IRAM_ATTR __attribute__((noinline)) void trace_free(void *p)
{
void *callers[STACK_DEPTH];
get_call_stack(callers);
record_free(p, callers);
__real_heap_caps_free(p);
}
void * __real_heap_caps_realloc(void *p, size_t size, uint32_t caps);
/* trace any 'realloc' event */
static IRAM_ATTR __attribute__((noinline)) void *trace_realloc(void *p, size_t size, uint32_t caps, trace_malloc_mode_t mode)
{
void *callers[STACK_DEPTH];
uint32_t ccount = get_ccount();
void *r;
/* trace realloc as free-then-alloc */
get_call_stack(callers);
record_free(p, callers);
if (mode == TRACE_MALLOC_CAPS ) {
r = __real_heap_caps_realloc(p, size, caps);
} else { //TRACE_MALLOC_DEFAULT
r = __real_heap_caps_realloc_default(p, size);
}
/* realloc with zero size is a free */
if (size != 0) {
heap_trace_record_t rec = {
.address = r,
.ccount = ccount,
.size = size,
};
memcpy(rec.alloced_by, callers, sizeof(void *) * STACK_DEPTH);
record_allocation(&rec);
}
return r;
}
/* Note: this changes the behaviour of libc malloc/realloc/free a bit,
as they no longer go via the libc functions in ROM. But more or less
the same in the end. */
IRAM_ATTR void *__wrap_malloc(size_t size)
{
return trace_malloc(size, 0, TRACE_MALLOC_DEFAULT);
}
IRAM_ATTR void __wrap_free(void *p)
{
trace_free(p);
}
IRAM_ATTR void *__wrap_realloc(void *p, size_t size)
{
return trace_realloc(p, size, 0, TRACE_MALLOC_DEFAULT);
}
IRAM_ATTR void *__wrap_calloc(size_t nmemb, size_t size)
{
size = size * nmemb;
void *result = trace_malloc(size, 0, TRACE_MALLOC_DEFAULT);
if (result != NULL) {
memset(result, 0, size);
}
return result;
}
IRAM_ATTR void *__wrap_heap_caps_malloc(size_t size, uint32_t caps)
{
return trace_malloc(size, caps, TRACE_MALLOC_CAPS);
}
void __wrap_heap_caps_free(void *p) __attribute__((alias("__wrap_free")));
IRAM_ATTR void *__wrap_heap_caps_realloc(void *p, size_t size, uint32_t caps)
{
return trace_realloc(p, size, caps, TRACE_MALLOC_CAPS);
}
IRAM_ATTR void *__wrap_heap_caps_malloc_default( size_t size )
{
return trace_malloc(size, 0, TRACE_MALLOC_DEFAULT);
}
IRAM_ATTR void *__wrap_heap_caps_realloc_default( void *ptr, size_t size )
{
return trace_realloc(ptr, size, 0, TRACE_MALLOC_DEFAULT);
}

View File

@@ -1,190 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
/* multi_heap is a heap implementation for handling multiple
heterogenous heaps in a single program.
Any contiguous block of memory can be registered as a heap.
*/
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Opaque handle to a registered heap */
typedef struct multi_heap_info *multi_heap_handle_t;
/**
* @brief allocate a chunk of memory with specific alignment
*
* @param heap Handle to a registered heap.
* @param size size in bytes of memory chunk
* @param alignment how the memory must be aligned
*
* @return pointer to the memory allocated, NULL on failure
*/
void *multi_heap_aligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment);
/** @brief malloc() a buffer in a given heap
*
* Semantics are the same as standard malloc(), only the returned buffer will be allocated in the specified heap.
*
* @param heap Handle to a registered heap.
* @param size Size of desired buffer.
*
* @return Pointer to new memory, or NULL if allocation fails.
*/
void *multi_heap_malloc(multi_heap_handle_t heap, size_t size);
/** @brief free() a buffer aligned in a given heap.
*
* @param heap Handle to a registered heap.
* @param p NULL, or a pointer previously returned from multi_heap_aligned_alloc() for the same heap.
* @note This function is deprecated, consider using multi_heap_free() instead
*/
void __attribute__((deprecated)) multi_heap_aligned_free(multi_heap_handle_t heap, void *p);
/** @brief free() a buffer in a given heap.
*
* Semantics are the same as standard free(), only the argument 'p' must be NULL or have been allocated in the specified heap.
*
* @param heap Handle to a registered heap.
* @param p NULL, or a pointer previously returned from multi_heap_malloc() or multi_heap_realloc() for the same heap.
*/
void multi_heap_free(multi_heap_handle_t heap, void *p);
/** @brief realloc() a buffer in a given heap.
*
* Semantics are the same as standard realloc(), only the argument 'p' must be NULL or have been allocated in the specified heap.
*
* @param heap Handle to a registered heap.
* @param p NULL, or a pointer previously returned from multi_heap_malloc() or multi_heap_realloc() for the same heap.
* @param size Desired new size for buffer.
*
* @return New buffer of 'size' containing contents of 'p', or NULL if reallocation failed.
*/
void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size);
/** @brief Return the size that a particular pointer was allocated with.
*
* @param heap Handle to a registered heap.
* @param p Pointer, must have been previously returned from multi_heap_malloc() or multi_heap_realloc() for the same heap.
*
* @return Size of the memory allocated at this block. May be more than the original size argument, due
* to padding and minimum block sizes.
*/
size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p);
/** @brief Register a new heap for use
*
* This function initialises a heap at the specified address, and returns a handle for future heap operations.
*
* There is no equivalent function for deregistering a heap - if all blocks in the heap are free, you can immediately start using the memory for other purposes.
*
* @param start Start address of the memory to use for a new heap.
* @param size Size (in bytes) of the new heap.
*
* @return Handle of a new heap ready for use, or NULL if the heap region was too small to be initialised.
*/
multi_heap_handle_t multi_heap_register(void *start, size_t size);
/** @brief Associate a private lock pointer with a heap
*
* The lock argument is supplied to the MULTI_HEAP_LOCK() and MULTI_HEAP_UNLOCK() macros, defined in multi_heap_platform.h.
*
* The lock in question must be recursive.
*
* When the heap is first registered, the associated lock is NULL.
*
* @param heap Handle to a registered heap.
* @param lock Optional pointer to a locking structure to associate with this heap.
*/
void multi_heap_set_lock(multi_heap_handle_t heap, void* lock);
/** @brief Dump heap information to stdout
*
* For debugging purposes, this function dumps information about every block in the heap to stdout.
*
* @param heap Handle to a registered heap.
*/
void multi_heap_dump(multi_heap_handle_t heap);
/** @brief Check heap integrity
*
* Walks the heap and checks all heap data structures are valid. If any errors are detected, an error-specific message
* can be optionally printed to stderr. Print behaviour can be overriden at compile time by defining
* MULTI_CHECK_FAIL_PRINTF in multi_heap_platform.h.
*
* @param heap Handle to a registered heap.
* @param print_errors If true, errors will be printed to stderr.
* @return true if heap is valid, false otherwise.
*/
bool multi_heap_check(multi_heap_handle_t heap, bool print_errors);
/** @brief Return free heap size
*
* Returns the number of bytes available in the heap.
*
* Equivalent to the total_free_bytes member returned by multi_heap_get_heap_info().
*
* Note that the heap may be fragmented, so the actual maximum size for a single malloc() may be lower. To know this
* size, see the largest_free_block member returned by multi_heap_get_heap_info().
*
* @param heap Handle to a registered heap.
* @return Number of free bytes.
*/
size_t multi_heap_free_size(multi_heap_handle_t heap);
/** @brief Return the lifetime minimum free heap size
*
* Equivalent to the minimum_free_bytes member returned by multi_heap_get_info().
*
* Returns the lifetime "low water mark" of possible values returned from multi_free_heap_size(), for the specified
* heap.
*
* @param heap Handle to a registered heap.
* @return Number of free bytes.
*/
size_t multi_heap_minimum_free_size(multi_heap_handle_t heap);
/** @brief Structure to access heap metadata via multi_heap_get_info */
typedef struct {
size_t total_free_bytes; ///< Total free bytes in the heap. Equivalent to multi_free_heap_size().
size_t total_allocated_bytes; ///< Total bytes allocated to data in the heap.
size_t largest_free_block; ///< Size of largest free block in the heap. This is the largest malloc-able size.
size_t minimum_free_bytes; ///< Lifetime minimum free heap size. Equivalent to multi_minimum_free_heap_size().
size_t allocated_blocks; ///< Number of (variable size) blocks allocated in the heap.
size_t free_blocks; ///< Number of (variable size) free blocks in the heap.
size_t total_blocks; ///< Total number of (variable size) blocks in the heap.
} multi_heap_info_t;
/** @brief Return metadata about a given heap
*
* Fills a multi_heap_info_t structure with information about the specified heap.
*
* @param heap Handle to a registered heap.
* @param info Pointer to a structure to fill with heap metadata.
*/
void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info);
#ifdef __cplusplus
}
#endif

View File

@@ -1,7 +0,0 @@
[mapping:heap]
archive: libheap.a
entries:
heap_tlsf (noflash)
multi_heap (noflash)
if HEAP_POISONING_DISABLED = n:
multi_heap_poisoning (noflash)

View File

@@ -1,376 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/cdefs.h>
#include "heap_tlsf.h"
#include <multi_heap.h>
#include "multi_heap_internal.h"
/* Note: Keep platform-specific parts in this header, this source
file should depend on libc only */
#include "multi_heap_platform.h"
/* Defines compile-time configuration macros */
#include "multi_heap_config.h"
#ifndef MULTI_HEAP_POISONING
/* if no heap poisoning, public API aliases directly to these implementations */
void *multi_heap_malloc(multi_heap_handle_t heap, size_t size)
__attribute__((alias("multi_heap_malloc_impl")));
void *multi_heap_aligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment)
__attribute__((alias("multi_heap_aligned_alloc_impl")));
void multi_heap_aligned_free(multi_heap_handle_t heap, void *p)
__attribute__((alias("multi_heap_free_impl")));
void multi_heap_free(multi_heap_handle_t heap, void *p)
__attribute__((alias("multi_heap_free_impl")));
void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size)
__attribute__((alias("multi_heap_realloc_impl")));
size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p)
__attribute__((alias("multi_heap_get_allocated_size_impl")));
multi_heap_handle_t multi_heap_register(void *start, size_t size)
__attribute__((alias("multi_heap_register_impl")));
void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info)
__attribute__((alias("multi_heap_get_info_impl")));
size_t multi_heap_free_size(multi_heap_handle_t heap)
__attribute__((alias("multi_heap_free_size_impl")));
size_t multi_heap_minimum_free_size(multi_heap_handle_t heap)
__attribute__((alias("multi_heap_minimum_free_size_impl")));
void *multi_heap_get_block_address(multi_heap_block_handle_t block)
__attribute__((alias("multi_heap_get_block_address_impl")));
void *multi_heap_get_block_owner(multi_heap_block_handle_t block)
{
return NULL;
}
#endif
#define ALIGN(X) ((X) & ~(sizeof(void *)-1))
#define ALIGN_UP(X) ALIGN((X)+sizeof(void *)-1)
#define ALIGN_UP_BY(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
typedef struct multi_heap_info {
void *lock;
size_t free_bytes;
size_t minimum_free_bytes;
size_t pool_size;
tlsf_t heap_data;
} heap_t;
/* Return true if this block is free. */
static inline bool is_free(const block_header_t *block)
{
return ((block->size & 0x01) != 0);
}
/* Data size of the block (excludes this block's header) */
static inline size_t block_data_size(const block_header_t *block)
{
return (block->size & ~0x03);
}
/* Check a block is valid for this heap. Used to verify parameters. */
static void assert_valid_block(const heap_t *heap, const block_header_t *block)
{
pool_t pool = tlsf_get_pool(heap->heap_data);
void *ptr = block_to_ptr(block);
MULTI_HEAP_ASSERT((ptr >= pool) &&
(ptr < pool + heap->pool_size),
(uintptr_t)ptr);
}
void *multi_heap_get_block_address_impl(multi_heap_block_handle_t block)
{
void *ptr = block_to_ptr(block);
return (ptr);
}
size_t multi_heap_get_allocated_size_impl(multi_heap_handle_t heap, void *p)
{
return tlsf_block_size(p);
}
multi_heap_handle_t multi_heap_register_impl(void *start_ptr, size_t size)
{
assert(start_ptr);
if(size < (tlsf_size(NULL) + tlsf_block_size_min() + sizeof(heap_t))) {
//Region too small to be a heap.
return NULL;
}
heap_t *result = (heap_t *)start_ptr;
size -= sizeof(heap_t);
result->heap_data = tlsf_create_with_pool(start_ptr + sizeof(heap_t), size, 0);
if(!result->heap_data) {
return NULL;
}
result->lock = NULL;
result->free_bytes = size - tlsf_size(result->heap_data);
result->pool_size = size;
result->minimum_free_bytes = result->free_bytes;
return result;
}
void multi_heap_set_lock(multi_heap_handle_t heap, void *lock)
{
heap->lock = lock;
}
void inline multi_heap_internal_lock(multi_heap_handle_t heap)
{
MULTI_HEAP_LOCK(heap->lock);
}
void inline multi_heap_internal_unlock(multi_heap_handle_t heap)
{
MULTI_HEAP_UNLOCK(heap->lock);
}
multi_heap_block_handle_t multi_heap_get_first_block(multi_heap_handle_t heap)
{
assert(heap != NULL);
pool_t pool = tlsf_get_pool(heap->heap_data);
block_header_t* block = offset_to_block(pool, -(int)block_header_overhead);
return (multi_heap_block_handle_t)block;
}
multi_heap_block_handle_t multi_heap_get_next_block(multi_heap_handle_t heap, multi_heap_block_handle_t block)
{
assert(heap != NULL);
assert_valid_block(heap, block);
block_header_t* next = block_next(block);
if(block_data_size(next) == 0) {
//Last block:
return NULL;
} else {
return (multi_heap_block_handle_t)next;
}
}
bool multi_heap_is_free(multi_heap_block_handle_t block)
{
return is_free(block);
}
void *multi_heap_malloc_impl(multi_heap_handle_t heap, size_t size)
{
if (size == 0 || heap == NULL) {
return NULL;
}
multi_heap_internal_lock(heap);
void *result = tlsf_malloc(heap->heap_data, size);
if(result) {
heap->free_bytes -= tlsf_block_size(result);
if (heap->free_bytes < heap->minimum_free_bytes) {
heap->minimum_free_bytes = heap->free_bytes;
}
}
multi_heap_internal_unlock(heap);
return result;
}
void multi_heap_free_impl(multi_heap_handle_t heap, void *p)
{
if (heap == NULL || p == NULL) {
return;
}
assert_valid_block(heap, p);
multi_heap_internal_lock(heap);
heap->free_bytes += tlsf_block_size(p);
tlsf_free(heap->heap_data, p);
multi_heap_internal_unlock(heap);
}
void *multi_heap_realloc_impl(multi_heap_handle_t heap, void *p, size_t size)
{
assert(heap != NULL);
if (p == NULL) {
return multi_heap_malloc_impl(heap, size);
}
assert_valid_block(heap, p);
if (heap == NULL) {
return NULL;
}
multi_heap_internal_lock(heap);
size_t previous_block_size = tlsf_block_size(p);
void *result = tlsf_realloc(heap->heap_data, p, size);
if(result) {
heap->free_bytes += previous_block_size;
heap->free_bytes -= tlsf_block_size(result);
if (heap->free_bytes < heap->minimum_free_bytes) {
heap->minimum_free_bytes = heap->free_bytes;
}
}
multi_heap_internal_unlock(heap);
return result;
}
void *multi_heap_aligned_alloc_impl_offs(multi_heap_handle_t heap, size_t size, size_t alignment, size_t offset)
{
if(heap == NULL) {
return NULL;
}
if(!size) {
return NULL;
}
//Alignment must be a power of two:
if(((alignment & (alignment - 1)) != 0) ||(!alignment)) {
return NULL;
}
multi_heap_internal_lock(heap);
void *result = tlsf_memalign_offs(heap->heap_data, alignment, size, offset);
if(result) {
heap->free_bytes -= tlsf_block_size(result);
if(heap->free_bytes < heap->minimum_free_bytes) {
heap->minimum_free_bytes = heap->free_bytes;
}
}
multi_heap_internal_unlock(heap);
return result;
}
void *multi_heap_aligned_alloc_impl(multi_heap_handle_t heap, size_t size, size_t alignment)
{
return multi_heap_aligned_alloc_impl_offs(heap, size, alignment, 0);
}
bool multi_heap_check(multi_heap_handle_t heap, bool print_errors)
{
(void)print_errors;
bool valid = true;
assert(heap != NULL);
multi_heap_internal_lock(heap);
if(tlsf_check(heap->heap_data)) {
valid = false;
}
if(tlsf_check_pool(tlsf_get_pool(heap->heap_data))) {
valid = false;
}
multi_heap_internal_unlock(heap);
return valid;
}
static void multi_heap_dump_tlsf(void* ptr, size_t size, int used, void* user)
{
(void)user;
MULTI_HEAP_STDERR_PRINTF("Block %p data, size: %d bytes, Free: %s \n",
(void *)ptr,
size,
used ? "No" : "Yes");
}
void multi_heap_dump(multi_heap_handle_t heap)
{
assert(heap != NULL);
multi_heap_internal_lock(heap);
MULTI_HEAP_STDERR_PRINTF("Showing data for heap: %p \n", (void *)heap);
tlsf_walk_pool(tlsf_get_pool(heap->heap_data), multi_heap_dump_tlsf, NULL);
multi_heap_internal_unlock(heap);
}
size_t multi_heap_free_size_impl(multi_heap_handle_t heap)
{
if (heap == NULL) {
return 0;
}
return heap->free_bytes;
}
size_t multi_heap_minimum_free_size_impl(multi_heap_handle_t heap)
{
if (heap == NULL) {
return 0;
}
return heap->minimum_free_bytes;
}
static void multi_heap_get_info_tlsf(void* ptr, size_t size, int used, void* user)
{
multi_heap_info_t *info = user;
if(used) {
info->allocated_blocks++;
} else {
info->free_blocks++;
if(size > info->largest_free_block ) {
info->largest_free_block = size;
}
}
info->total_blocks++;
}
void multi_heap_get_info_impl(multi_heap_handle_t heap, multi_heap_info_t *info)
{
memset(info, 0, sizeof(multi_heap_info_t));
if (heap == NULL) {
return;
}
multi_heap_internal_lock(heap);
tlsf_walk_pool(tlsf_get_pool(heap->heap_data), multi_heap_get_info_tlsf, info);
info->total_allocated_bytes = (heap->pool_size - tlsf_size(heap->heap_data)) - heap->free_bytes;
info->minimum_free_bytes = heap->minimum_free_bytes;
info->total_free_bytes = heap->free_bytes;
info->largest_free_block = tlsf_fit_size(heap->heap_data, info->largest_free_block);
multi_heap_internal_unlock(heap);
}

View File

@@ -1,31 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#ifdef ESP_PLATFORM
#include "sdkconfig.h"
#include "soc/soc.h"
#include "soc/soc_caps.h"
#endif
/* Configuration macros for multi-heap */
#ifdef CONFIG_HEAP_POISONING_LIGHT
#define MULTI_HEAP_POISONING
#endif
#ifdef CONFIG_HEAP_POISONING_COMPREHENSIVE
#define MULTI_HEAP_POISONING
#define MULTI_HEAP_POISONING_SLOW
#endif

View File

@@ -1,76 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
/* Opaque handle to a heap block */
typedef const struct block_header_t *multi_heap_block_handle_t;
/* Internal definitions for the "implementation" of the multi_heap API,
as defined in multi_heap.c.
If heap poisioning is disabled, these are aliased directly to the public API.
If heap poisoning is enabled, wrapper functions call each of these.
*/
void *multi_heap_malloc_impl(multi_heap_handle_t heap, size_t size);
/* Allocate a memory region of minimum `size` bytes, aligned on `alignment`. */
void *multi_heap_aligned_alloc_impl(multi_heap_handle_t heap, size_t size, size_t alignment);
/* Allocate a memory region of minimum `size` bytes, where memory's `offset` is aligned on `alignment`. */
void *multi_heap_aligned_alloc_impl_offs(multi_heap_handle_t heap, size_t size, size_t alignment, size_t offset);
void multi_heap_free_impl(multi_heap_handle_t heap, void *p);
void *multi_heap_realloc_impl(multi_heap_handle_t heap, void *p, size_t size);
multi_heap_handle_t multi_heap_register_impl(void *start, size_t size);
void multi_heap_get_info_impl(multi_heap_handle_t heap, multi_heap_info_t *info);
size_t multi_heap_free_size_impl(multi_heap_handle_t heap);
size_t multi_heap_minimum_free_size_impl(multi_heap_handle_t heap);
size_t multi_heap_get_allocated_size_impl(multi_heap_handle_t heap, void *p);
void *multi_heap_get_block_address_impl(multi_heap_block_handle_t block);
/* Some internal functions for heap poisoning use */
/* Check an allocated block's poison bytes are correct. Called by multi_heap_check(). */
bool multi_heap_internal_check_block_poisoning(void *start, size_t size, bool is_free, bool print_errors);
/* Fill a region of memory with the free or malloced pattern.
Called when merging blocks, to overwrite the old block header.
*/
void multi_heap_internal_poison_fill_region(void *start, size_t size, bool is_free);
/* Allow heap poisoning to lock/unlock the heap to avoid race conditions
if multi_heap_check() is running concurrently.
*/
void multi_heap_internal_lock(multi_heap_handle_t heap);
void multi_heap_internal_unlock(multi_heap_handle_t heap);
/* Some internal functions for heap debugging code to use */
/* Get the handle to the first (fixed free) block in a heap */
multi_heap_block_handle_t multi_heap_get_first_block(multi_heap_handle_t heap);
/* Get the handle to the next block in a heap, with validation */
multi_heap_block_handle_t multi_heap_get_next_block(multi_heap_handle_t heap, multi_heap_block_handle_t block);
/* Test if a heap block is free */
bool multi_heap_is_free(const multi_heap_block_handle_t block);
/* Get the data address of a heap block */
void *multi_heap_get_block_address(multi_heap_block_handle_t block);
/* Get the owner identification for a heap block */
void *multi_heap_get_block_owner(multi_heap_block_handle_t block);

View File

@@ -1,108 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#ifdef MULTI_HEAP_FREERTOS
#include "freertos/FreeRTOS.h"
#include "sdkconfig.h"
#include "esp_rom_sys.h"
#if CONFIG_IDF_TARGET_ESP32
#include "esp32/rom/ets_sys.h" // will be removed in idf v5.0
#elif CONFIG_IDF_TARGET_ESP32S2
#include "esp32s2/rom/ets_sys.h"
#endif
#include <assert.h>
typedef portMUX_TYPE multi_heap_lock_t;
/* Because malloc/free can happen inside an ISR context,
we need to use portmux spinlocks here not RTOS mutexes */
#define MULTI_HEAP_LOCK(PLOCK) do { \
if((PLOCK) != NULL) { \
portENTER_CRITICAL((PLOCK)); \
} \
} while(0)
#define MULTI_HEAP_UNLOCK(PLOCK) do { \
if ((PLOCK) != NULL) { \
portEXIT_CRITICAL((PLOCK)); \
} \
} while(0)
#define MULTI_HEAP_LOCK_INIT(PLOCK) do { \
vPortCPUInitializeMutex((PLOCK)); \
} while(0)
#define MULTI_HEAP_LOCK_STATIC_INITIALIZER portMUX_INITIALIZER_UNLOCKED
/* Not safe to use std i/o while in a portmux critical section,
can deadlock, so we use the ROM equivalent functions. */
#define MULTI_HEAP_PRINTF esp_rom_printf
#define MULTI_HEAP_STDERR_PRINTF(MSG, ...) esp_rom_printf(MSG, __VA_ARGS__)
inline static void multi_heap_assert(bool condition, const char *format, int line, intptr_t address)
{
/* Can't use libc assert() here as it calls printf() which can cause another malloc() for a newlib lock.
Also, it's useful to be able to print the memory address where corruption was detected.
*/
#ifndef NDEBUG
if(!condition) {
#ifndef CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT
esp_rom_printf(format, line, address);
#endif // CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT
abort();
}
#else // NDEBUG
(void) condition;
#endif // NDEBUG
}
#define MULTI_HEAP_ASSERT(CONDITION, ADDRESS) \
multi_heap_assert((CONDITION), "CORRUPT HEAP: multi_heap.c:%d detected at 0x%08x\n", \
__LINE__, (intptr_t)(ADDRESS))
#ifdef CONFIG_HEAP_TASK_TRACKING
#include <freertos/task.h>
#define MULTI_HEAP_BLOCK_OWNER TaskHandle_t task;
#define MULTI_HEAP_SET_BLOCK_OWNER(HEAD) (HEAD)->task = xTaskGetCurrentTaskHandle()
#define MULTI_HEAP_GET_BLOCK_OWNER(HEAD) ((HEAD)->task)
#else
#define MULTI_HEAP_BLOCK_OWNER
#define MULTI_HEAP_SET_BLOCK_OWNER(HEAD)
#define MULTI_HEAP_GET_BLOCK_OWNER(HEAD) (NULL)
#endif
#else // MULTI_HEAP_FREERTOS
#include <assert.h>
#define MULTI_HEAP_PRINTF printf
#define MULTI_HEAP_STDERR_PRINTF(MSG, ...) fprintf(stderr, MSG, __VA_ARGS__)
#define MULTI_HEAP_LOCK(PLOCK) (void) (PLOCK)
#define MULTI_HEAP_UNLOCK(PLOCK) (void) (PLOCK)
#define MULTI_HEAP_LOCK_INIT(PLOCK) (void) (PLOCK)
#define MULTI_HEAP_LOCK_STATIC_INITIALIZER 0
#define MULTI_HEAP_ASSERT(CONDITION, ADDRESS) assert((CONDITION) && "Heap corrupt")
#define MULTI_HEAP_BLOCK_OWNER
#define MULTI_HEAP_SET_BLOCK_OWNER(HEAD)
#define MULTI_HEAP_GET_BLOCK_OWNER(HEAD) (NULL)
#endif // MULTI_HEAP_FREERTOS

View File

@@ -1,426 +0,0 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/param.h>
#include <multi_heap.h>
#include "multi_heap_internal.h"
/* Note: Keep platform-specific parts in this header, this source
file should depend on libc only */
#include "multi_heap_platform.h"
/* Defines compile-time configuration macros */
#include "multi_heap_config.h"
#ifdef MULTI_HEAP_POISONING
/* Alias MULTI_HEAP_POISONING_SLOW to SLOW for better readabilty */
#ifdef SLOW
#error "external header has defined SLOW"
#endif
#ifdef MULTI_HEAP_POISONING_SLOW
#define SLOW 1
#endif
#define MALLOC_FILL_PATTERN 0xce
#define FREE_FILL_PATTERN 0xfe
#define HEAD_CANARY_PATTERN 0xABBA1234
#define TAIL_CANARY_PATTERN 0xBAAD5678
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
typedef struct {
uint32_t head_canary;
MULTI_HEAP_BLOCK_OWNER
size_t alloc_size;
} poison_head_t;
typedef struct {
uint32_t tail_canary;
} poison_tail_t;
#define POISON_OVERHEAD (sizeof(poison_head_t) + sizeof(poison_tail_t))
/* Given a "poisoned" region with pre-data header 'head', and actual data size 'alloc_size', fill in the head and tail
region checks.
Returns the pointer to the actual usable data buffer (ie after 'head')
*/
static uint8_t *poison_allocated_region(poison_head_t *head, size_t alloc_size)
{
uint8_t *data = (uint8_t *)(&head[1]); /* start of data ie 'real' allocated buffer */
poison_tail_t *tail = (poison_tail_t *)(data + alloc_size);
head->alloc_size = alloc_size;
head->head_canary = HEAD_CANARY_PATTERN;
MULTI_HEAP_SET_BLOCK_OWNER(head);
uint32_t tail_canary = TAIL_CANARY_PATTERN;
if ((intptr_t)tail % sizeof(void *) == 0) {
tail->tail_canary = tail_canary;
} else {
/* unaligned tail_canary */
memcpy(&tail->tail_canary, &tail_canary, sizeof(uint32_t));
}
return data;
}
/* Given a pointer to some allocated data, check the head & tail poison structures (before & after it) that were
previously injected by poison_allocated_region().
Returns a pointer to the poison header structure, or NULL if the poison structures are corrupt.
*/
static poison_head_t *verify_allocated_region(void *data, bool print_errors)
{
poison_head_t *head = (poison_head_t *)((intptr_t)data - sizeof(poison_head_t));
poison_tail_t *tail = (poison_tail_t *)((intptr_t)data + head->alloc_size);
/* check if the beginning of the data was overwritten */
if (head->head_canary != HEAD_CANARY_PATTERN) {
if (print_errors) {
MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Bad head at %p. Expected 0x%08x got 0x%08x\n", &head->head_canary,
HEAD_CANARY_PATTERN, head->head_canary);
}
return NULL;
}
/* check if the end of the data was overrun */
uint32_t canary;
if ((intptr_t)tail % sizeof(void *) == 0) {
canary = tail->tail_canary;
} else {
/* tail is unaligned */
memcpy(&canary, &tail->tail_canary, sizeof(canary));
}
if (canary != TAIL_CANARY_PATTERN) {
if (print_errors) {
MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Bad tail at %p. Expected 0x%08x got 0x%08x\n", &tail->tail_canary,
TAIL_CANARY_PATTERN, canary);
}
return NULL;
}
return head;
}
#ifdef SLOW
/* Go through a region that should have the specified fill byte 'pattern',
verify it.
if expect_free is true, expect FREE_FILL_PATTERN otherwise MALLOC_FILL_PATTERN.
if swap_pattern is true, swap patterns in the buffer (ie replace MALLOC_FILL_PATTERN with FREE_FILL_PATTERN, and vice versa.)
Returns true if verification checks out.
*/
static bool verify_fill_pattern(void *data, size_t size, bool print_errors, bool expect_free, bool swap_pattern)
{
const uint32_t FREE_FILL_WORD = (FREE_FILL_PATTERN << 24) | (FREE_FILL_PATTERN << 16) | (FREE_FILL_PATTERN << 8) | FREE_FILL_PATTERN;
const uint32_t MALLOC_FILL_WORD = (MALLOC_FILL_PATTERN << 24) | (MALLOC_FILL_PATTERN << 16) | (MALLOC_FILL_PATTERN << 8) | MALLOC_FILL_PATTERN;
const uint32_t EXPECT_WORD = expect_free ? FREE_FILL_WORD : MALLOC_FILL_WORD;
const uint32_t REPLACE_WORD = expect_free ? MALLOC_FILL_WORD : FREE_FILL_WORD;
bool valid = true;
/* Use 4-byte operations as much as possible */
if ((intptr_t)data % 4 == 0) {
uint32_t *p = data;
while (size >= 4) {
if (*p != EXPECT_WORD) {
if (print_errors) {
MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Invalid data at %p. Expected 0x%08x got 0x%08x\n", p, EXPECT_WORD, *p);
}
valid = false;
#ifndef NDEBUG
/* If an assertion is going to fail as soon as we're done verifying the pattern, leave the rest of the
buffer contents as-is for better post-mortem analysis
*/
swap_pattern = false;
#endif
}
if (swap_pattern) {
*p = REPLACE_WORD;
}
p++;
size -= 4;
}
data = p;
}
uint8_t *p = data;
for (size_t i = 0; i < size; i++) {
if (p[i] != (uint8_t)EXPECT_WORD) {
if (print_errors) {
MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Invalid data at %p. Expected 0x%02x got 0x%02x\n", p, (uint8_t)EXPECT_WORD, *p);
}
valid = false;
#ifndef NDEBUG
swap_pattern = false; // same as above
#endif
}
if (swap_pattern) {
p[i] = (uint8_t)REPLACE_WORD;
}
}
return valid;
}
#endif
void *multi_heap_aligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment)
{
if (!size) {
return NULL;
}
if (size > SIZE_MAX - POISON_OVERHEAD) {
return NULL;
}
multi_heap_internal_lock(heap);
poison_head_t *head = multi_heap_aligned_alloc_impl_offs(heap, size + POISON_OVERHEAD,
alignment, sizeof(poison_head_t));
uint8_t *data = NULL;
if (head != NULL) {
data = poison_allocated_region(head, size);
#ifdef SLOW
/* check everything we got back is FREE_FILL_PATTERN & swap for MALLOC_FILL_PATTERN */
bool ret = verify_fill_pattern(data, size, true, true, true);
assert( ret );
#endif
} else {
multi_heap_internal_unlock(heap);
return NULL;
}
multi_heap_internal_unlock(heap);
return data;
}
void *multi_heap_malloc(multi_heap_handle_t heap, size_t size)
{
if (!size) {
return NULL;
}
if(size > SIZE_MAX - POISON_OVERHEAD) {
return NULL;
}
multi_heap_internal_lock(heap);
poison_head_t *head = multi_heap_malloc_impl(heap, size + POISON_OVERHEAD);
uint8_t *data = NULL;
if (head != NULL) {
data = poison_allocated_region(head, size);
#ifdef SLOW
/* check everything we got back is FREE_FILL_PATTERN & swap for MALLOC_FILL_PATTERN */
bool ret = verify_fill_pattern(data, size, true, true, true);
assert( ret );
#endif
}
multi_heap_internal_unlock(heap);
return data;
}
void multi_heap_free(multi_heap_handle_t heap, void *p)
{
if (p == NULL) {
return;
}
multi_heap_internal_lock(heap);
poison_head_t *head = verify_allocated_region(p, true);
assert(head != NULL);
#ifdef SLOW
/* replace everything with FREE_FILL_PATTERN, including the poison head/tail */
memset(head, FREE_FILL_PATTERN,
head->alloc_size + POISON_OVERHEAD);
#endif
multi_heap_free_impl(heap, head);
multi_heap_internal_unlock(heap);
}
void multi_heap_aligned_free(multi_heap_handle_t heap, void *p)
{
multi_heap_free(heap, p);
}
void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size)
{
poison_head_t *head = NULL;
poison_head_t *new_head;
void *result = NULL;
if(size > SIZE_MAX - POISON_OVERHEAD) {
return NULL;
}
if (p == NULL) {
return multi_heap_malloc(heap, size);
}
if (size == 0) {
multi_heap_free(heap, p);
return NULL;
}
/* p != NULL, size != 0 */
head = verify_allocated_region(p, true);
assert(head != NULL);
multi_heap_internal_lock(heap);
#ifndef SLOW
new_head = multi_heap_realloc_impl(heap, head, size + POISON_OVERHEAD);
if (new_head != NULL) {
/* For "fast" poisoning, we only overwrite the head/tail of the new block so it's safe
to poison, so no problem doing this even if realloc resized in place.
*/
result = poison_allocated_region(new_head, size);
}
#else // SLOW
/* When slow poisoning is enabled, it becomes very fiddly to try and correctly fill memory when resizing in place
(where the buffer may be moved (including to an overlapping address with the old buffer), grown, or shrunk in
place.)
For now we just malloc a new buffer, copy, and free. :|
Note: If this ever changes, multi_heap defrag realloc test should be enabled.
*/
size_t orig_alloc_size = head->alloc_size;
new_head = multi_heap_malloc_impl(heap, size + POISON_OVERHEAD);
if (new_head != NULL) {
result = poison_allocated_region(new_head, size);
memcpy(result, p, MIN(size, orig_alloc_size));
multi_heap_free(heap, p);
}
#endif
multi_heap_internal_unlock(heap);
return result;
}
void *multi_heap_get_block_address(multi_heap_block_handle_t block)
{
char *head = multi_heap_get_block_address_impl(block);
return head + sizeof(poison_head_t);
}
void *multi_heap_get_block_owner(multi_heap_block_handle_t block)
{
return MULTI_HEAP_GET_BLOCK_OWNER((poison_head_t*)multi_heap_get_block_address_impl(block));
}
multi_heap_handle_t multi_heap_register(void *start, size_t size)
{
#ifdef SLOW
if (start != NULL) {
memset(start, FREE_FILL_PATTERN, size);
}
#endif
return multi_heap_register_impl(start, size);
}
static inline void subtract_poison_overhead(size_t *arg) {
if (*arg > POISON_OVERHEAD) {
*arg -= POISON_OVERHEAD;
} else {
*arg = 0;
}
}
size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p)
{
poison_head_t *head = verify_allocated_region(p, true);
assert(head != NULL);
size_t result = multi_heap_get_allocated_size_impl(heap, head);
return result;
}
void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info)
{
multi_heap_get_info_impl(heap, info);
/* don't count the heap poison head & tail overhead in the allocated bytes size */
info->total_allocated_bytes -= info->allocated_blocks * POISON_OVERHEAD;
/* trim largest_free_block to account for poison overhead */
subtract_poison_overhead(&info->largest_free_block);
/* similarly, trim total_free_bytes so there's no suggestion that
a block this big may be available. */
subtract_poison_overhead(&info->total_free_bytes);
subtract_poison_overhead(&info->minimum_free_bytes);
}
size_t multi_heap_free_size(multi_heap_handle_t heap)
{
size_t r = multi_heap_free_size_impl(heap);
subtract_poison_overhead(&r);
return r;
}
size_t multi_heap_minimum_free_size(multi_heap_handle_t heap)
{
size_t r = multi_heap_minimum_free_size_impl(heap);
subtract_poison_overhead(&r);
return r;
}
/* Internal hooks used by multi_heap to manage poisoning, while keeping some modularity */
bool multi_heap_internal_check_block_poisoning(void *start, size_t size, bool is_free, bool print_errors)
{
if (is_free) {
#ifdef SLOW
return verify_fill_pattern(start, size, print_errors, true, false);
#else
return true; /* can only verify empty blocks in SLOW mode */
#endif
} else {
void *data = (void *)((intptr_t)start + sizeof(poison_head_t));
poison_head_t *head = verify_allocated_region(data, print_errors);
if (head != NULL && head->alloc_size > size - POISON_OVERHEAD) {
/* block can be bigger than alloc_size, for reasons of alignment & fragmentation,
but block can never be smaller than head->alloc_size... */
if (print_errors) {
MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Size at %p expected <=0x%08x got 0x%08x\n", &head->alloc_size,
size - POISON_OVERHEAD, head->alloc_size);
}
return false;
}
return head != NULL;
}
}
void multi_heap_internal_poison_fill_region(void *start, size_t size, bool is_free)
{
memset(start, is_free ? FREE_FILL_PATTERN : MALLOC_FILL_PATTERN, size);
}
#else // !MULTI_HEAP_POISONING
#ifdef MULTI_HEAP_POISONING_SLOW
#error "MULTI_HEAP_POISONING_SLOW requires MULTI_HEAP_POISONING"
#endif
#endif // MULTI_HEAP_POISONING

View File

@@ -1,3 +0,0 @@
idf_component_register(SRC_DIRS "."
PRIV_INCLUDE_DIRS "."
PRIV_REQUIRES cmock test_utils heap)

View File

@@ -1,5 +0,0 @@
#
#Component Makefile
#
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View File

@@ -1,147 +0,0 @@
/*
Tests for the capabilities-based memory allocator.
*/
#include <esp_types.h>
#include <stdio.h>
#include "unity.h"
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "esp_spi_flash.h"
#include <stdlib.h>
#include <sys/param.h>
#include <string.h>
#include <malloc.h>
TEST_CASE("Capabilities aligned allocator test", "[heap]")
{
uint32_t alignments = 0;
printf("[ALIGNED_ALLOC] Allocating from default CAP: \n");
for(;alignments <= 1024; alignments++) {
uint8_t *buf = (uint8_t *)memalign(alignments, (alignments + 137));
if(((alignments & (alignments - 1)) != 0) || (!alignments)) {
TEST_ASSERT( buf == NULL );
//printf("[ALIGNED_ALLOC] alignment: %u is not a power of two, don't allow allocation \n", aligments);
} else {
TEST_ASSERT( buf != NULL );
printf("[ALIGNED_ALLOC] alignment required: %u \n", alignments);
printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf);
//Address of obtained block must be aligned with selected value
TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0);
//Write some data, if it corrupts memory probably the heap
//canary verification will fail:
memset(buf, 0xA5, (alignments + 137));
free(buf);
}
}
//Alloc from a non permitted area:
uint32_t *not_permitted_buf = (uint32_t *)heap_caps_aligned_alloc(alignments, (alignments + 137), MALLOC_CAP_EXEC | MALLOC_CAP_32BIT);
TEST_ASSERT( not_permitted_buf == NULL );
#if CONFIG_ESP32_SPIRAM_SUPPORT || CONFIG_ESP32S2_SPIRAM_SUPPORT
alignments = 0;
printf("[ALIGNED_ALLOC] Allocating from external memory: \n");
for(;alignments <= 1024 * 1024; alignments++) {
//Now try to take aligned memory from IRAM:
uint8_t *buf = (uint8_t *)heap_caps_aligned_alloc(alignments, 10*1024, MALLOC_CAP_SPIRAM);
if(((alignments & (alignments - 1)) != 0) || (!alignments)) {
TEST_ASSERT( buf == NULL );
//printf("[ALIGNED_ALLOC] alignment: %u is not a power of two, don't allow allocation \n", aligments);
} else {
TEST_ASSERT( buf != NULL );
printf("[ALIGNED_ALLOC] alignment required: %u \n", alignments);
printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf);
//Address of obtained block must be aligned with selected value
TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0);
//Write some data, if it corrupts memory probably the heap
//canary verification will fail:
memset(buf, 0xA5, (10*1024));
heap_caps_free(buf);
}
}
#endif
}
TEST_CASE("Capabilities aligned calloc test", "[heap]")
{
uint32_t alignments = 0;
printf("[ALIGNED_ALLOC] Allocating from default CAP: \n");
for(;alignments <= 1024; alignments++) {
uint8_t *buf = (uint8_t *)heap_caps_aligned_calloc(alignments, 1, (alignments + 137), MALLOC_CAP_DEFAULT);
if(((alignments & (alignments - 1)) != 0) || (!alignments)) {
TEST_ASSERT( buf == NULL );
//printf("[ALIGNED_ALLOC] alignment: %u is not a power of two, don't allow allocation \n", aligments);
} else {
TEST_ASSERT( buf != NULL );
printf("[ALIGNED_ALLOC] alignment required: %u \n", alignments);
printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf);
//Address of obtained block must be aligned with selected value
TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0);
//Write some data, if it corrupts memory probably the heap
//canary verification will fail:
memset(buf, 0xA5, (alignments + 137));
heap_caps_free(buf);
}
}
//Check if memory is initialized with zero:
uint8_t byte_array[1024];
memset(&byte_array, 0, sizeof(byte_array));
uint8_t *buf = (uint8_t *)heap_caps_aligned_calloc(1024, 1, 1024, MALLOC_CAP_DEFAULT);
TEST_ASSERT(memcmp(byte_array, buf, sizeof(byte_array)) == 0);
heap_caps_free(buf);
//Same size, but different chunk:
buf = (uint8_t *)heap_caps_aligned_calloc(1024, 1024, 1, MALLOC_CAP_DEFAULT);
TEST_ASSERT(memcmp(byte_array, buf, sizeof(byte_array)) == 0);
heap_caps_free(buf);
//Alloc from a non permitted area:
uint32_t *not_permitted_buf = (uint32_t *)heap_caps_aligned_calloc(alignments, 1, (alignments + 137), MALLOC_CAP_32BIT);
TEST_ASSERT( not_permitted_buf == NULL );
#if CONFIG_ESP32_SPIRAM_SUPPORT || CONFIG_ESP32S2_SPIRAM_SUPPORT
alignments = 0;
printf("[ALIGNED_ALLOC] Allocating from external memory: \n");
for(;alignments <= 1024 * 1024; alignments++) {
//Now try to take aligned memory from IRAM:
uint8_t *buf = (uint8_t *)(uint8_t *)heap_caps_aligned_calloc(alignments, 1, 10*1024, MALLOC_CAP_SPIRAM);
if(((alignments & (alignments - 1)) != 0) || (!alignments)) {
TEST_ASSERT( buf == NULL );
//printf("[ALIGNED_ALLOC] alignment: %u is not a power of two, don't allow allocation \n", aligments);
} else {
TEST_ASSERT( buf != NULL );
printf("[ALIGNED_ALLOC] alignment required: %u \n", alignments);
printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf);
//Address of obtained block must be aligned with selected value
TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0);
//Write some data, if it corrupts memory probably the heap
//canary verification will fail:
memset(buf, 0xA5, (10*1024));
heap_caps_free(buf);
}
}
#endif
}
TEST_CASE("aligned_alloc(0) should return a NULL pointer", "[heap]")
{
void *p;
p = heap_caps_aligned_alloc(32, 0, MALLOC_CAP_DEFAULT);
TEST_ASSERT(p == NULL);
}

View File

@@ -1,108 +0,0 @@
#include "freertos/FreeRTOS.h"
#include <esp_types.h>
#include <stdio.h>
#include "unity.h"
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include <stdlib.h>
#include <sys/param.h>
#include <string.h>
#include <test_utils.h>
//This test only makes sense with poisoning disabled (light or comprehensive)
#if !defined(CONFIG_HEAP_POISONING_COMPREHENSIVE) && !defined(CONFIG_HEAP_POISONING_LIGHT)
#define NUM_POINTERS 128
#define ITERATIONS 10000
TEST_CASE("Heap many random allocations timings", "[heap]")
{
void *p[NUM_POINTERS] = { 0 };
size_t s[NUM_POINTERS] = { 0 };
uint32_t cycles_before;
uint64_t alloc_time_average = 0;
uint64_t free_time_average = 0;
uint64_t realloc_time_average = 0;
for (int i = 0; i < ITERATIONS; i++) {
uint8_t n = esp_random() % NUM_POINTERS;
if (esp_random() % 4 == 0) {
/* 1 in 4 iterations, try to realloc the buffer instead
of using malloc/free
*/
size_t new_size = esp_random() % 1024;
cycles_before = portGET_RUN_TIME_COUNTER_VALUE();
void *new_p = heap_caps_realloc(p[n], new_size, MALLOC_CAP_DEFAULT);
realloc_time_average = portGET_RUN_TIME_COUNTER_VALUE() - cycles_before;
printf("realloc %p -> %p (%zu -> %zu) time spent cycles: %lld \n", p[n], new_p, s[n], new_size, realloc_time_average);
heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true);
if (new_size == 0 || new_p != NULL) {
p[n] = new_p;
s[n] = new_size;
if (new_size > 0) {
memset(p[n], n, new_size);
}
}
continue;
}
if (p[n] != NULL) {
if (s[n] > 0) {
/* Verify pre-existing contents of p[n] */
uint8_t compare[s[n]];
memset(compare, n, s[n]);
TEST_ASSERT(( memcmp(compare, p[n], s[n]) == 0 ));
}
TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true));
cycles_before = portGET_RUN_TIME_COUNTER_VALUE();
heap_caps_free(p[n]);
free_time_average = portGET_RUN_TIME_COUNTER_VALUE() - cycles_before;
printf("freed %p (%zu) time spent cycles: %lld\n", p[n], s[n], free_time_average);
if (!heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true)) {
printf("FAILED iteration %d after freeing %p\n", i, p[n]);
heap_caps_dump(MALLOC_CAP_DEFAULT);
TEST_ASSERT(0);
}
}
s[n] = rand() % 1024;
heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true);
cycles_before = portGET_RUN_TIME_COUNTER_VALUE();
p[n] = heap_caps_malloc(s[n], MALLOC_CAP_DEFAULT);
alloc_time_average = portGET_RUN_TIME_COUNTER_VALUE() - cycles_before;
printf("malloc %p (%zu) time spent cycles: %lld \n", p[n], s[n], alloc_time_average);
if (!heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true)) {
printf("FAILED iteration %d after mallocing %p (%zu bytes)\n", i, p[n], s[n]);
heap_caps_dump(MALLOC_CAP_DEFAULT);
TEST_ASSERT(0);
}
if (p[n] != NULL) {
memset(p[n], n, s[n]);
}
}
for (int i = 0; i < NUM_POINTERS; i++) {
cycles_before = portGET_RUN_TIME_COUNTER_VALUE();
heap_caps_free( p[i]);
free_time_average = portGET_RUN_TIME_COUNTER_VALUE() - cycles_before;
if (!heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true)) {
printf("FAILED during cleanup after freeing %p\n", p[i]);
heap_caps_dump(MALLOC_CAP_DEFAULT);
TEST_ASSERT(0);
}
}
TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true));
}
#endif

View File

@@ -1,74 +0,0 @@
/*
Tests for D/IRAM support in heap capability allocator
*/
#include <esp_types.h>
#include <stdio.h>
#include "unity.h"
#include "esp_heap_caps.h"
#include "soc/soc_memory_layout.h"
#define ALLOC_SZ 1024
static void *malloc_block_diram(uint32_t caps)
{
void *attempts[256] = { 0 }; // Allocate up to 256 ALLOC_SZ blocks to exhaust all non-D/IRAM memory temporarily
int count = 0;
void *result;
while(count < sizeof(attempts)/sizeof(void *)) {
result = heap_caps_malloc(ALLOC_SZ, caps);
TEST_ASSERT_NOT_NULL_MESSAGE(result, "not enough free heap to perform test");
if (esp_ptr_in_diram_dram(result) || esp_ptr_in_diram_iram(result)) {
break;
}
attempts[count] = result;
result = NULL;
count++;
}
for (int i = 0; i < count; i++) {
free(attempts[i]);
}
TEST_ASSERT_NOT_NULL_MESSAGE(result, "not enough D/IRAM memory is free");
return result;
}
TEST_CASE("Allocate D/IRAM as DRAM", "[heap]")
{
uint32_t *dram = malloc_block_diram(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
for (int i = 0; i < ALLOC_SZ / sizeof(uint32_t); i++) {
uint32_t v = i + 0xAAAA;
dram[i] = v;
volatile uint32_t *iram = esp_ptr_diram_dram_to_iram(dram + i);
TEST_ASSERT_EQUAL(v, dram[i]);
TEST_ASSERT_EQUAL(v, *iram);
*iram = UINT32_MAX;
TEST_ASSERT_EQUAL(UINT32_MAX, *iram);
TEST_ASSERT_EQUAL(UINT32_MAX, dram[i]);
}
free(dram);
}
TEST_CASE("Allocate D/IRAM as IRAM", "[heap]")
{
uint32_t *iram = malloc_block_diram(MALLOC_CAP_EXEC);
for (int i = 0; i < ALLOC_SZ / sizeof(uint32_t); i++) {
uint32_t v = i + 0xEEE;
iram[i] = v;
volatile uint32_t *dram = esp_ptr_diram_iram_to_dram(iram + i);
TEST_ASSERT_EQUAL_HEX32(v, iram[i]);
TEST_ASSERT_EQUAL_HEX32(v, *dram);
*dram = UINT32_MAX;
TEST_ASSERT_EQUAL_HEX32(UINT32_MAX, *dram);
TEST_ASSERT_EQUAL_HEX32(UINT32_MAX, iram[i]);
}
free(iram);
}

View File

@@ -1,164 +0,0 @@
/*
Generic test for heap tracing support
Only compiled in if CONFIG_HEAP_TRACING is set
*/
#include <esp_types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sdkconfig.h"
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#ifdef CONFIG_HEAP_TRACING
// only compile in heap tracing tests if tracing is enabled
#include "esp_heap_trace.h"
TEST_CASE("heap trace leak check", "[heap]")
{
heap_trace_record_t recs[8];
heap_trace_init_standalone(recs, 8);
printf("Leak check test\n"); // Print something before trace starts, or stdout allocations skew total counts
fflush(stdout);
heap_trace_start(HEAP_TRACE_LEAKS);
void *a = malloc(64);
memset(a, '3', 64);
void *b = malloc(96);
memset(b, '4', 11);
printf("a.address %p vs %p b.address %p vs %p\n", a, recs[0].address, b, recs[1].address);
heap_trace_dump();
TEST_ASSERT_EQUAL(2, heap_trace_get_count());
heap_trace_record_t trace_a, trace_b;
heap_trace_get(0, &trace_a);
heap_trace_get(1, &trace_b);
printf("trace_a.address %p trace_bb.address %p\n", trace_a.address, trace_b.address);
TEST_ASSERT_EQUAL_PTR(a, trace_a.address);
TEST_ASSERT_EQUAL_PTR(b, trace_b.address);
TEST_ASSERT_EQUAL_PTR(recs[0].address, trace_a.address);
TEST_ASSERT_EQUAL_PTR(recs[1].address, trace_b.address);
free(a);
TEST_ASSERT_EQUAL(1, heap_trace_get_count());
heap_trace_get(0, &trace_b);
TEST_ASSERT_EQUAL_PTR(b, trace_b.address);
/* buffer deletes trace_a when freed,
so trace_b at head of buffer */
TEST_ASSERT_EQUAL_PTR(recs[0].address, trace_b.address);
heap_trace_stop();
}
TEST_CASE("heap trace wrapped buffer check", "[heap]")
{
const size_t N = 8;
heap_trace_record_t recs[N];
heap_trace_init_standalone(recs, N);
heap_trace_start(HEAP_TRACE_LEAKS);
void *ptrs[N+1];
for (int i = 0; i < N+1; i++) {
ptrs[i] = malloc(i*3);
}
// becuase other mallocs happen as part of this control flow,
// we can't guarantee N entries of ptrs[] are in the heap check buffer.
// but we should guarantee at least the last one is
bool saw_last_ptr = false;
for (int i = 0; i < N; i++) {
heap_trace_record_t rec;
heap_trace_get(i, &rec);
if (rec.address == ptrs[N-1]) {
saw_last_ptr = true;
}
}
TEST_ASSERT(saw_last_ptr);
void *other = malloc(6);
heap_trace_dump();
for (int i = 0; i < N+1; i++) {
free(ptrs[i]);
}
heap_trace_dump();
bool saw_other = false;
for (int i = 0; i < heap_trace_get_count(); i++) {
heap_trace_record_t rec;
heap_trace_get(i, &rec);
// none of ptr[]s should be in the heap trace any more
for (int j = 0; j < N+1; j++) {
TEST_ASSERT_NOT_EQUAL(ptrs[j], rec.address);
}
if (rec.address == other) {
saw_other = true;
}
}
// 'other' pointer should be somewhere in the leak dump
TEST_ASSERT(saw_other);
heap_trace_stop();
}
static void print_floats_task(void *ignore)
{
heap_trace_start(HEAP_TRACE_ALL);
char buf[16] = { };
volatile float f = 12.3456;
sprintf(buf, "%.4f", f);
TEST_ASSERT_EQUAL_STRING("12.3456", buf);
heap_trace_stop();
vTaskDelete(NULL);
}
TEST_CASE("can trace allocations made by newlib", "[heap]")
{
const size_t N = 8;
heap_trace_record_t recs[N];
heap_trace_init_standalone(recs, N);
/* Verifying that newlib code performs an allocation is very fiddly:
- Printing a float allocates data associated with the task, but only the
first time a task prints a float of this length. So we do it in a one-shot task
to avoid possibility it already happened.
- If newlib is updated this test may start failing if the printf() implementation
changes. (This version passes for both nano & regular formatting in newlib 2.2.0)
- We also do the tracing in the task so we only capture things directly related to it.
*/
xTaskCreate(print_floats_task, "print_float", 4096, NULL, 5, NULL);
vTaskDelay(10);
/* has to be at least a few as newlib allocates via multiple different function calls */
TEST_ASSERT(heap_trace_get_count() > 3);
}
#endif

View File

@@ -1,60 +0,0 @@
/*
Tests for a leak tag
*/
#include <stdio.h>
#include "unity.h"
#include "esp_heap_caps_init.h"
#include "esp_system.h"
#include <stdlib.h>
static char* check_calloc(int size)
{
char *arr = calloc(size, sizeof(char));
TEST_ASSERT_NOT_NULL(arr);
return arr;
}
TEST_CASE("Check for leaks (no leak)", "[heap]")
{
char *arr = check_calloc(1000);
free(arr);
}
TEST_CASE("Check for leaks (leak)", "[heap][ignore]")
{
check_calloc(1000);
}
TEST_CASE("Not check for leaks", "[heap][leaks]")
{
check_calloc(1000);
}
TEST_CASE("Set a leak level = 7016", "[heap][leaks=7016]")
{
check_calloc(7000);
}
static void test_fn(void)
{
check_calloc(1000);
}
TEST_CASE_MULTIPLE_STAGES("Not check for leaks in MULTIPLE_STAGES mode", "[heap][leaks]", test_fn, test_fn, test_fn);
TEST_CASE_MULTIPLE_STAGES("Check for leaks in MULTIPLE_STAGES mode (leak)", "[heap][ignore]", test_fn, test_fn, test_fn);
static void test_fn2(void)
{
check_calloc(1000);
esp_restart();
}
static void test_fn3(void)
{
check_calloc(1000);
}
TEST_CASE_MULTIPLE_STAGES("Check for leaks in MULTIPLE_STAGES mode (manual reset)", "[heap][leaks][reset=SW_CPU_RESET, SW_CPU_RESET]", test_fn2, test_fn2, test_fn3);

View File

@@ -1,134 +0,0 @@
/*
Generic test for malloc/free
*/
#include <esp_types.h>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "unity.h"
#include "esp_heap_caps.h"
#include "sdkconfig.h"
static int **allocatedMem;
static int noAllocated;
static int tryAllocMem(void) {
int i, j;
const int allocateMaxK=1024*5; //try to allocate a max of 5MiB
allocatedMem=malloc(sizeof(int *)*allocateMaxK);
if (!allocatedMem) return 0;
for (i=0; i<allocateMaxK; i++) {
allocatedMem[i]=malloc(1024);
if (allocatedMem[i]==NULL) break;
for (j=0; j<1024/4; j++) allocatedMem[i][j]=(0xdeadbeef);
}
noAllocated=i;
return i;
}
static void tryAllocMemFree(void) {
int i, j;
for (i=0; i<noAllocated; i++) {
for (j=0; j<1024/4; j++) {
TEST_ASSERT(allocatedMem[i][j]==(0xdeadbeef));
}
free(allocatedMem[i]);
}
free(allocatedMem);
}
TEST_CASE("Malloc/overwrite, then free all available DRAM", "[heap]")
{
int m1=0, m2=0;
m1=tryAllocMem();
tryAllocMemFree();
m2=tryAllocMem();
tryAllocMemFree();
printf("Could allocate %dK on first try, %dK on 2nd try.\n", m1, m2);
TEST_ASSERT(m1==m2);
}
#if CONFIG_SPIRAM_USE_MALLOC
#if (CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL > 1024)
TEST_CASE("Check if reserved DMA pool still can allocate even when malloc()'ed memory is exhausted", "[heap]")
{
char** dmaMem=malloc(sizeof(char*)*512);
assert(dmaMem);
int m=tryAllocMem();
int i=0;
for (i=0; i<512; i++) {
dmaMem[i]=heap_caps_malloc(1024, MALLOC_CAP_DMA);
if (dmaMem[i]==NULL) break;
}
for (int j=0; j<i; j++) free(dmaMem[j]);
free(dmaMem);
tryAllocMemFree();
printf("Could allocate %dK of DMA memory after allocating all of %dK of normal memory.\n", i, m);
TEST_ASSERT(i);
}
#endif
#endif
/* As you see, we are desperately trying to outsmart the compiler, so that it
* doesn't warn about oversized allocations in the next two unit tests.
* To be removed when we switch to GCC 8.2 and add
* -Wno-alloc-size-larger-than=PTRDIFF_MAX to CFLAGS for this file.
*/
void* (*g_test_malloc_ptr)(size_t) = &malloc;
void* (*g_test_calloc_ptr)(size_t, size_t) = &calloc;
void* test_malloc_wrapper(size_t size)
{
return (*g_test_malloc_ptr)(size);
}
void* test_calloc_wrapper(size_t count, size_t size)
{
return (*g_test_calloc_ptr)(count, size);
}
TEST_CASE("alloc overflows should all fail", "[heap]")
{
/* allocates 8 bytes if size_t overflows */
TEST_ASSERT_NULL(test_calloc_wrapper(SIZE_MAX / 2 + 4, 2));
/* will overflow if any poisoning is enabled
(should fail for sensible OOM reasons, otherwise) */
TEST_ASSERT_NULL(test_malloc_wrapper(SIZE_MAX - 1));
TEST_ASSERT_NULL(test_calloc_wrapper(SIZE_MAX - 1, 1));
/* will overflow when the size is rounded up to word align it */
TEST_ASSERT_NULL(heap_caps_malloc(SIZE_MAX-1, MALLOC_CAP_32BIT));
TEST_ASSERT_NULL(heap_caps_malloc(SIZE_MAX-1, MALLOC_CAP_EXEC));
}
TEST_CASE("unreasonable allocs should all fail", "[heap]")
{
TEST_ASSERT_NULL(test_calloc_wrapper(16, 1024*1024));
TEST_ASSERT_NULL(test_malloc_wrapper(16*1024*1024));
TEST_ASSERT_NULL(test_malloc_wrapper(SIZE_MAX / 2));
TEST_ASSERT_NULL(test_malloc_wrapper(SIZE_MAX - 256));
TEST_ASSERT_NULL(test_malloc_wrapper(xPortGetFreeHeapSize() - 1));
}
TEST_CASE("malloc(0) should return a NULL pointer", "[heap]")
{
void *p;
p = malloc(0);
TEST_ASSERT(p == NULL);
}

View File

@@ -1,247 +0,0 @@
/*
Tests for the capabilities-based memory allocator.
*/
#include <esp_types.h>
#include <stdio.h>
#include "unity.h"
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "esp_spi_flash.h"
#include <stdlib.h>
#include <sys/param.h>
#ifndef CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
TEST_CASE("Capabilities allocator test", "[heap]")
{
char *m1, *m2[10];
int x;
size_t free8start, free32start, free8, free32;
/* It's important we printf() something before we take the empty heap sizes,
as the first printf() in a task allocates heap resources... */
printf("Testing capabilities allocator...\n");
free8start = heap_caps_get_free_size(MALLOC_CAP_8BIT);
free32start = heap_caps_get_free_size(MALLOC_CAP_32BIT);
printf("Free 8bit-capable memory (start): %dK, 32-bit capable memory %dK\n", free8start, free32start);
TEST_ASSERT(free32start >= free8start);
printf("Allocating 10K of 8-bit capable RAM\n");
m1= heap_caps_malloc(10*1024, MALLOC_CAP_8BIT);
printf("--> %p\n", m1);
free8 = heap_caps_get_free_size(MALLOC_CAP_8BIT);
free32 = heap_caps_get_free_size(MALLOC_CAP_32BIT);
printf("Free 8bit-capable memory (both reduced): %dK, 32-bit capable memory %dK\n", free8, free32);
//Both should have gone down by 10K; 8bit capable ram is also 32-bit capable
TEST_ASSERT(free8<=(free8start-10*1024));
TEST_ASSERT(free32<=(free32start-10*1024));
//Assume we got DRAM back
TEST_ASSERT((((int)m1)&0xFF000000)==0x3F000000);
free(m1);
//The goal here is to allocate from IRAM. Since there is no external IRAM (yet)
//the following gives size of IRAM-only (not D/IRAM) memory.
size_t free_iram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL) -
heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
size_t alloc32 = MIN(free_iram / 2, 10*1024) & (~3);
if(free_iram) {
printf("Freeing; allocating %u bytes of 32K-capable RAM\n", alloc32);
m1 = heap_caps_malloc(alloc32, MALLOC_CAP_32BIT);
printf("--> %p\n", m1);
//Check that we got IRAM back
TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000);
free8 = heap_caps_get_free_size(MALLOC_CAP_8BIT);
free32 = heap_caps_get_free_size(MALLOC_CAP_32BIT);
printf("Free 8bit-capable memory (after 32-bit): %dK, 32-bit capable memory %dK\n", free8, free32);
//Only 32-bit should have gone down by alloc32: 32-bit isn't necessarily 8bit capable
TEST_ASSERT(free32<=(free32start-alloc32));
TEST_ASSERT(free8==free8start);
free(m1);
} else {
printf("This platform has no 32-bit only capable RAM, jumping to next test \n");
}
printf("Allocating impossible caps\n");
m1= heap_caps_malloc(10*1024, MALLOC_CAP_8BIT|MALLOC_CAP_EXEC);
printf("--> %p\n", m1);
TEST_ASSERT(m1==NULL);
if(free_iram) {
printf("Testing changeover iram -> dram");
// priorities will exhaust IRAM first, then start allocating from DRAM
for (x=0; x<10; x++) {
m2[x]= heap_caps_malloc(alloc32, MALLOC_CAP_32BIT);
printf("--> %p\n", m2[x]);
}
TEST_ASSERT((((int)m2[0])&0xFF000000)==0x40000000);
TEST_ASSERT((((int)m2[9])&0xFF000000)==0x3F000000);
} else {
printf("This platform has no IRAM-only so changeover will never occur, jumping to next test\n");
}
printf("Test if allocating executable code still gives IRAM, even with dedicated IRAM region depleted\n");
if(free_iram) {
// (the allocation should come from D/IRAM)
free_iram = heap_caps_get_free_size(MALLOC_CAP_EXEC);
m1= heap_caps_malloc(MIN(free_iram / 2, 10*1024), MALLOC_CAP_EXEC);
printf("--> %p\n", m1);
TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000);
for (x=0; x<10; x++) free(m2[x]);
} else {
// (the allocation should come from D/IRAM)
free_iram = heap_caps_get_free_size(MALLOC_CAP_EXEC);
m1= heap_caps_malloc(MIN(free_iram / 2, 10*1024), MALLOC_CAP_EXEC);
printf("--> %p\n", m1);
TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000);
}
free(m1);
printf("Done.\n");
}
#endif
#ifdef CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY
TEST_CASE("IRAM_8BIT capability test", "[heap]")
{
uint8_t *ptr;
size_t free_size, free_size32, largest_free_size;
/* need to print something as first printf allocates some heap */
printf("IRAM_8BIT capability test\n");
free_size = heap_caps_get_free_size(MALLOC_CAP_IRAM_8BIT);
free_size32 = heap_caps_get_free_size(MALLOC_CAP_32BIT);
largest_free_size = heap_caps_get_largest_free_block(MALLOC_CAP_IRAM_8BIT);
ptr = heap_caps_malloc(largest_free_size, MALLOC_CAP_IRAM_8BIT);
TEST_ASSERT((((int)ptr)&0xFF000000)==0x40000000);
TEST_ASSERT(heap_caps_get_free_size(MALLOC_CAP_IRAM_8BIT) == (free_size - heap_caps_get_allocated_size(ptr)));
TEST_ASSERT(heap_caps_get_free_size(MALLOC_CAP_32BIT) == (free_size32 - heap_caps_get_allocated_size(ptr)));
free(ptr);
}
#endif
TEST_CASE("heap_caps metadata test", "[heap]")
{
/* need to print something as first printf allocates some heap */
printf("heap_caps metadata test\n");
heap_caps_print_heap_info(MALLOC_CAP_8BIT);
multi_heap_info_t original;
heap_caps_get_info(&original, MALLOC_CAP_8BIT);
void *b = heap_caps_malloc(original.largest_free_block, MALLOC_CAP_8BIT);
TEST_ASSERT_NOT_NULL(b);
printf("After allocating %d bytes:\n", original.largest_free_block);
heap_caps_print_heap_info(MALLOC_CAP_8BIT);
multi_heap_info_t after;
heap_caps_get_info(&after, MALLOC_CAP_8BIT);
TEST_ASSERT(after.largest_free_block <= original.largest_free_block);
TEST_ASSERT(after.total_free_bytes <= original.total_free_bytes);
free(b);
heap_caps_get_info(&after, MALLOC_CAP_8BIT);
printf("\n\n After test, heap status:\n");
heap_caps_print_heap_info(MALLOC_CAP_8BIT);
/* Allow some leeway here, because LWIP sometimes allocates up to 144 bytes in the background
as part of timer management.
*/
TEST_ASSERT_INT32_WITHIN(200, after.total_free_bytes, original.total_free_bytes);
TEST_ASSERT_INT32_WITHIN(200, after.largest_free_block, original.largest_free_block);
TEST_ASSERT(after.minimum_free_bytes < original.total_free_bytes);
}
/* Small function runs from IRAM to check that malloc/free/realloc
all work OK when cache is disabled...
*/
static IRAM_ATTR __attribute__((noinline)) bool iram_malloc_test(void)
{
spi_flash_guard_get()->start(); // Disables flash cache
bool result = true;
void *x = heap_caps_malloc(64, MALLOC_CAP_EXEC);
result = result && (x != NULL);
void *y = heap_caps_realloc(x, 32, MALLOC_CAP_EXEC);
result = result && (y != NULL);
heap_caps_free(y);
spi_flash_guard_get()->end(); // Re-enables flash cache
return result;
}
TEST_CASE("heap_caps_xxx functions work with flash cache disabled", "[heap]")
{
TEST_ASSERT( iram_malloc_test() );
}
#ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS
TEST_CASE("When enabled, allocation operation failure generates an abort", "[heap][reset=abort,SW_CPU_RESET]")
{
const size_t stupid_allocation_size = (128 * 1024 * 1024);
void *ptr = heap_caps_malloc(stupid_allocation_size, MALLOC_CAP_DEFAULT);
(void)ptr;
TEST_FAIL_MESSAGE("should not be reached");
}
#endif
static bool called_user_failed_hook = false;
void heap_caps_alloc_failed_hook(size_t requested_size, uint32_t caps, const char *function_name)
{
printf("%s was called but failed to allocate %d bytes with 0x%X capabilities. \n",function_name, requested_size, caps);
called_user_failed_hook = true;
}
TEST_CASE("user provided alloc failed hook must be called when allocation fails", "[heap]")
{
TEST_ASSERT(heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook) == ESP_OK);
const size_t stupid_allocation_size = (128 * 1024 * 1024);
void *ptr = heap_caps_malloc(stupid_allocation_size, MALLOC_CAP_DEFAULT);
TEST_ASSERT(called_user_failed_hook != false);
called_user_failed_hook = false;
ptr = heap_caps_realloc(ptr, stupid_allocation_size, MALLOC_CAP_DEFAULT);
TEST_ASSERT(called_user_failed_hook != false);
called_user_failed_hook = false;
ptr = heap_caps_aligned_alloc(0x200, stupid_allocation_size, MALLOC_CAP_DEFAULT);
TEST_ASSERT(called_user_failed_hook != false);
(void)ptr;
}
TEST_CASE("allocation with invalid capability should also trigger the alloc failed hook", "[heap]")
{
const size_t allocation_size = 64;
const uint32_t invalid_cap = MALLOC_CAP_INVALID;
TEST_ASSERT(heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook) == ESP_OK);
called_user_failed_hook = false;
void *ptr = heap_caps_malloc(allocation_size, invalid_cap);
TEST_ASSERT(called_user_failed_hook != false);
called_user_failed_hook = false;
ptr = heap_caps_realloc(ptr, allocation_size, invalid_cap);
TEST_ASSERT(called_user_failed_hook != false);
called_user_failed_hook = false;
ptr = heap_caps_aligned_alloc(0x200, allocation_size, invalid_cap);
TEST_ASSERT(called_user_failed_hook != false);
(void)ptr;
}

View File

@@ -1,67 +0,0 @@
/*
Generic test for realloc
*/
#include <stdlib.h>
#include <string.h>
#include "unity.h"
#include "sdkconfig.h"
#include "esp_heap_caps.h"
#include "soc/soc_memory_layout.h"
#ifndef CONFIG_HEAP_POISONING_COMPREHENSIVE
/* (can't realloc in place if comprehensive is enabled) */
TEST_CASE("realloc shrink buffer in place", "[heap]")
{
void *x = malloc(64);
TEST_ASSERT(x);
void *y = realloc(x, 48);
TEST_ASSERT_EQUAL_PTR(x, y);
}
#endif
#ifndef CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
TEST_CASE("realloc shrink buffer with EXEC CAPS", "[heap]")
{
const size_t buffer_size = 64;
void *x = heap_caps_malloc(buffer_size, MALLOC_CAP_EXEC);
TEST_ASSERT(x);
void *y = heap_caps_realloc(x, buffer_size - 16, MALLOC_CAP_EXEC);
TEST_ASSERT(y);
//y needs to fall in a compatible memory area of IRAM:
TEST_ASSERT(esp_ptr_executable(y)|| esp_ptr_in_iram(y) || esp_ptr_in_diram_iram(y));
free(y);
}
TEST_CASE("realloc move data to a new heap type", "[heap]")
{
const char *test = "I am some test content to put in the heap";
char buf[64];
memset(buf, 0xEE, 64);
strlcpy(buf, test, 64);
char *a = malloc(64);
memcpy(a, buf, 64);
// move data from 'a' to IRAM
char *b = heap_caps_realloc(a, 64, MALLOC_CAP_EXEC);
TEST_ASSERT_NOT_NULL(b);
TEST_ASSERT_NOT_EQUAL(a, b);
TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_INVALID, true));
TEST_ASSERT_EQUAL_HEX32_ARRAY(buf, b, 64 / sizeof(uint32_t));
// Move data back to DRAM
char *c = heap_caps_realloc(b, 48, MALLOC_CAP_8BIT);
TEST_ASSERT_NOT_NULL(c);
TEST_ASSERT_NOT_EQUAL(b, c);
TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_INVALID, true));
TEST_ASSERT_EQUAL_HEX8_ARRAY(buf, c, 48);
free(c);
}
#endif

View File

@@ -1,72 +0,0 @@
/*
Tests for registering new heap memory at runtime
*/
#include <stdio.h>
#include "unity.h"
#include "esp_heap_caps_init.h"
#include "esp_system.h"
#include <stdlib.h>
/* NOTE: This is not a well-formed unit test, it leaks memory */
TEST_CASE("Allocate new heap at runtime", "[heap][ignore]")
{
const size_t BUF_SZ = 1000;
const size_t HEAP_OVERHEAD_MAX = 200;
void *buffer = malloc(BUF_SZ);
TEST_ASSERT_NOT_NULL(buffer);
uint32_t before_free = esp_get_free_heap_size();
TEST_ESP_OK( heap_caps_add_region((intptr_t)buffer, (intptr_t)buffer + BUF_SZ) );
uint32_t after_free = esp_get_free_heap_size();
printf("Before %u after %u\n", before_free, after_free);
/* allow for some 'heap overhead' from accounting structures */
TEST_ASSERT(after_free >= before_free + BUF_SZ - HEAP_OVERHEAD_MAX);
}
/* NOTE: This is not a well-formed unit test, it leaks memory and
may fail if run twice in a row without a reset.
*/
TEST_CASE("Allocate new heap with new capability", "[heap][ignore]")
{
const size_t BUF_SZ = 100;
#ifdef CONFIG_ESP_SYSTEM_MEMPROT_FEATURE
const size_t ALLOC_SZ = 32;
#else
const size_t ALLOC_SZ = 64; // More than half of BUF_SZ
#endif
const uint32_t MALLOC_CAP_INVENTED = (1 << 30); /* this must be unused in esp_heap_caps.h */
/* no memory exists to provide this capability */
TEST_ASSERT_NULL( heap_caps_malloc(ALLOC_SZ, MALLOC_CAP_INVENTED) );
void *buffer = malloc(BUF_SZ);
TEST_ASSERT_NOT_NULL(buffer);
uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS] = { MALLOC_CAP_INVENTED };
TEST_ESP_OK( heap_caps_add_region_with_caps(caps, (intptr_t)buffer, (intptr_t)buffer + BUF_SZ) );
/* ta-da, it's now possible! */
TEST_ASSERT_NOT_NULL( heap_caps_malloc(ALLOC_SZ, MALLOC_CAP_INVENTED) );
}
/* NOTE: This is not a well-formed unit test.
* If run twice without a reset, it will failed.
*/
TEST_CASE("Add .bss memory to heap region runtime", "[heap][ignore]")
{
#define BUF_SZ 1000
#define HEAP_OVERHEAD_MAX 200
static uint8_t s_buffer[BUF_SZ];
printf("s_buffer start %08x end %08x\n", (intptr_t)s_buffer, (intptr_t)s_buffer + BUF_SZ);
uint32_t before_free = esp_get_free_heap_size();
TEST_ESP_OK( heap_caps_add_region((intptr_t)s_buffer, (intptr_t)s_buffer + BUF_SZ) );
uint32_t after_free = esp_get_free_heap_size();
printf("Before %u after %u\n", before_free, after_free);
/* allow for some 'heap overhead' from accounting structures */
TEST_ASSERT(after_free >= before_free + BUF_SZ - HEAP_OVERHEAD_MAX);
/* Twice add must be failed */
TEST_ASSERT( (heap_caps_add_region((intptr_t)s_buffer, (intptr_t)s_buffer + BUF_SZ) != ESP_OK) );
}

View File

@@ -1,54 +0,0 @@
TEST_PROGRAM=test_multi_heap
all: $(TEST_PROGRAM)
ifneq ($(filter clean,$(MAKECMDGOALS)),)
.NOTPARALLEL: # prevent make clean racing the other targets
endif
SOURCE_FILES = $(abspath \
../multi_heap.c \
../heap_tlsf.c \
../multi_heap_poisoning.c \
test_multi_heap.cpp \
main.cpp \
)
INCLUDE_FLAGS = -I../include -I../../../tools/catch
GCOV ?= gcov
CPPFLAGS += $(INCLUDE_FLAGS) -D CONFIG_LOG_DEFAULT_LEVEL -g -fstack-protector-all -m32 -DCONFIG_HEAP_POISONING_COMPREHENSIVE
CFLAGS += -Wall -Werror -fprofile-arcs -ftest-coverage
CXXFLAGS += -std=c++11 -Wall -Werror -fprofile-arcs -ftest-coverage
LDFLAGS += -lstdc++ -fprofile-arcs -ftest-coverage -m32
OBJ_FILES = $(filter %.o, $(SOURCE_FILES:.cpp=.o) $(SOURCE_FILES:.c=.o))
COVERAGE_FILES = $(OBJ_FILES:.o=.gc*)
$(TEST_PROGRAM): $(OBJ_FILES)
g++ $(LDFLAGS) -o $(TEST_PROGRAM) $(OBJ_FILES)
$(OUTPUT_DIR):
mkdir -p $(OUTPUT_DIR)
test: $(TEST_PROGRAM)
./$(TEST_PROGRAM)
$(COVERAGE_FILES): $(TEST_PROGRAM) test
coverage.info: $(COVERAGE_FILES)
find ../ -name "*.gcno" -exec $(GCOV) -r -pb {} +
lcov --capture --directory $(abspath ../) --no-external --output-file coverage.info --gcov-tool $(GCOV)
coverage_report: coverage.info
genhtml coverage.info --output-directory coverage_report
@echo "Coverage report is in coverage_report/index.html"
clean:
rm -f $(OBJ_FILES) $(TEST_PROGRAM)
rm -f $(COVERAGE_FILES) *.gcov
rm -rf coverage_report/
rm -f coverage.info
.PHONY: clean all test

View File

@@ -1,2 +0,0 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env bash
#
# Run the test suite with all configurations enabled
#
FAIL=0
for FLAGS in "CONFIG_HEAP_POISONING_NONE" "CONFIG_HEAP_POISONING_LIGHT" "CONFIG_HEAP_POISONING_COMPREHENSIVE" ; do
echo "==== Testing with config: ${FLAGS} ===="
CPPFLAGS="-D${FLAGS}" make clean test || FAIL=1
done
make clean
if [ $FAIL == 0 ]; then
echo "All configurations passed"
else
echo "Some configurations failed, see log."
exit 1
fi

View File

@@ -1,508 +0,0 @@
#include "catch.hpp"
#include "multi_heap.h"
#include "../multi_heap_config.h"
#include <string.h>
#include <assert.h>
static void *__malloc__(size_t bytes)
{
return malloc(bytes);
}
static void __free__(void *ptr)
{
free(ptr);
}
/* Insurance against accidentally using libc heap functions in tests */
#undef free
#define free #error
#undef malloc
#define malloc #error
#undef calloc
#define calloc #error
#undef realloc
#define realloc #error
TEST_CASE("multi_heap simple allocations", "[multi_heap]")
{
uint8_t small_heap[4 * 1024];
multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap));
size_t test_alloc_size = (multi_heap_free_size(heap) + 4) / 2;
printf("New heap:\n");
multi_heap_dump(heap);
printf("*********************\n");
uint8_t *buf = (uint8_t *)multi_heap_malloc(heap, test_alloc_size);
printf("small_heap %p buf %p\n", small_heap, buf);
REQUIRE( buf != NULL );
REQUIRE((intptr_t)buf >= (intptr_t)small_heap);
REQUIRE( (intptr_t)buf < (intptr_t)(small_heap + sizeof(small_heap)));
REQUIRE( multi_heap_get_allocated_size(heap, buf) >= test_alloc_size );
REQUIRE( multi_heap_get_allocated_size(heap, buf) < test_alloc_size + 16);
memset(buf, 0xEE, test_alloc_size);
REQUIRE( multi_heap_malloc(heap, test_alloc_size) == NULL );
multi_heap_free(heap, buf);
printf("Empty?\n");
multi_heap_dump(heap);
printf("*********************\n");
/* Now there should be space for another allocation */
buf = (uint8_t *)multi_heap_malloc(heap, test_alloc_size);
REQUIRE( buf != NULL );
multi_heap_free(heap, buf);
REQUIRE( multi_heap_free_size(heap) > multi_heap_minimum_free_size(heap) );
}
TEST_CASE("multi_heap fragmentation", "[multi_heap]")
{
uint8_t small_heap[4 * 1024];
multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap));
const size_t alloc_size = 128;
void *p[4];
for (int i = 0; i < 4; i++) {
multi_heap_dump(heap);
REQUIRE( multi_heap_check(heap, true) );
p[i] = multi_heap_malloc(heap, alloc_size);
printf("%d = %p ****->\n", i, p[i]);
multi_heap_dump(heap);
REQUIRE( p[i] != NULL );
}
printf("allocated %p %p %p %p\n", p[0], p[1], p[2], p[3]);
REQUIRE( multi_heap_malloc(heap, alloc_size * 5) == NULL ); /* no room to allocate 5*alloc_size now */
printf("4 allocations:\n");
multi_heap_dump(heap);
printf("****************\n");
multi_heap_free(heap, p[0]);
multi_heap_free(heap, p[1]);
multi_heap_free(heap, p[3]);
printf("1 allocations:\n");
multi_heap_dump(heap);
printf("****************\n");
void *big = multi_heap_malloc(heap, alloc_size * 3);
//Blocks in TLSF are organized in different form, so this makes no sense
multi_heap_free(heap, big);
multi_heap_free(heap, p[2]);
printf("0 allocations:\n");
multi_heap_dump(heap);
printf("****************\n");
big = multi_heap_malloc(heap, alloc_size * 2);
//Blocks in TLSF are organized in different form, so this makes no sense
multi_heap_free(heap, big);
}
/* Test that malloc/free does not leave free space fragmented */
TEST_CASE("multi_heap defrag", "[multi_heap]")
{
void *p[4];
uint8_t small_heap[4 * 1024];
multi_heap_info_t info, info2;
multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap));
printf("0 ---\n");
multi_heap_dump(heap);
REQUIRE( multi_heap_check(heap, true) );
multi_heap_get_info(heap, &info);
REQUIRE( 0 == info.allocated_blocks );
REQUIRE( 1 == info.free_blocks );
printf("1 ---\n");
p[0] = multi_heap_malloc(heap, 128);
p[1] = multi_heap_malloc(heap, 32);
multi_heap_dump(heap);
REQUIRE( multi_heap_check(heap, true) );
printf("2 ---\n");
multi_heap_free(heap, p[0]);
p[2] = multi_heap_malloc(heap, 64);
multi_heap_dump(heap);
REQUIRE( p[2] == p[0] );
REQUIRE( multi_heap_check(heap, true) );
printf("3 ---\n");
multi_heap_free(heap, p[2]);
p[3] = multi_heap_malloc(heap, 32);
multi_heap_dump(heap);
REQUIRE( p[3] == p[0] );
REQUIRE( multi_heap_check(heap, true) );
multi_heap_get_info(heap, &info2);
REQUIRE( 2 == info2.allocated_blocks );
REQUIRE( 2 == info2.free_blocks );
multi_heap_free(heap, p[0]);
multi_heap_free(heap, p[1]);
multi_heap_get_info(heap, &info2);
REQUIRE( 0 == info2.allocated_blocks );
REQUIRE( 1 == info2.free_blocks );
REQUIRE( info.total_free_bytes == info2.total_free_bytes );
}
/* Test that malloc/free does not leave free space fragmented
Note: With fancy poisoning, realloc is implemented as malloc-copy-free and this test does not apply.
*/
#ifndef MULTI_HEAP_POISONING_SLOW
TEST_CASE("multi_heap defrag realloc", "[multi_heap]")
{
void *p[4];
uint8_t small_heap[4 * 1024];
multi_heap_info_t info, info2;
multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap));
printf("0 ---\n");
multi_heap_dump(heap);
REQUIRE( multi_heap_check(heap, true) );
multi_heap_get_info(heap, &info);
REQUIRE( 0 == info.allocated_blocks );
REQUIRE( 1 == info.free_blocks );
printf("1 ---\n");
p[0] = multi_heap_malloc(heap, 128);
p[1] = multi_heap_malloc(heap, 32);
multi_heap_dump(heap);
REQUIRE( multi_heap_check(heap, true) );
printf("2 ---\n");
p[2] = multi_heap_realloc(heap, p[0], 64);
multi_heap_dump(heap);
REQUIRE( p[2] == p[0] );
REQUIRE( multi_heap_check(heap, true) );
printf("3 ---\n");
p[3] = multi_heap_realloc(heap, p[2], 32);
multi_heap_dump(heap);
REQUIRE( p[3] == p[0] );
REQUIRE( multi_heap_check(heap, true) );
multi_heap_get_info(heap, &info2);
REQUIRE( 2 == info2.allocated_blocks );
REQUIRE( 2 == info2.free_blocks );
multi_heap_free(heap, p[0]);
multi_heap_free(heap, p[1]);
multi_heap_get_info(heap, &info2);
REQUIRE( 0 == info2.allocated_blocks );
REQUIRE( 1 == info2.free_blocks );
REQUIRE( info.total_free_bytes == info2.total_free_bytes );
}
#endif
void multi_heap_allocation_impl(int heap_size)
{
uint8_t *big_heap = (uint8_t *) __malloc__(2*heap_size);
const int NUM_POINTERS = 64;
printf("Running multi-allocation test with heap_size %d...\n", heap_size);
REQUIRE( big_heap );
multi_heap_handle_t heap = multi_heap_register(big_heap, heap_size);
void *p[NUM_POINTERS] = { 0 };
size_t s[NUM_POINTERS] = { 0 };
const size_t initial_free = multi_heap_free_size(heap);
const int ITERATIONS = 10000;
for (int i = 0; i < ITERATIONS; i++) {
/* check all pointers allocated so far are valid inside big_heap */
for (int j = 0; j < NUM_POINTERS; j++) {
if (p[j] != NULL) {
}
}
uint8_t n = rand() % NUM_POINTERS;
if (rand() % 4 == 0) {
/* 1 in 4 iterations, try to realloc the buffer instead
of using malloc/free
*/
size_t new_size = rand() % 1024;
void *new_p = multi_heap_realloc(heap, p[n], new_size);
printf("realloc %p -> %p (%zu -> %zu)\n", p[n], new_p, s[n], new_size);
multi_heap_check(heap, true);
if (new_size == 0 || new_p != NULL) {
p[n] = new_p;
s[n] = new_size;
if (new_size > 0) {
REQUIRE( p[n] >= big_heap );
REQUIRE( p[n] < big_heap + heap_size );
memset(p[n], n, new_size);
}
}
continue;
}
if (p[n] != NULL) {
if (s[n] > 0) {
/* Verify pre-existing contents of p[n] */
uint8_t compare[s[n]];
memset(compare, n, s[n]);
/*REQUIRE*/assert( memcmp(compare, p[n], s[n]) == 0 );
}
REQUIRE( multi_heap_check(heap, true) );
multi_heap_free(heap, p[n]);
printf("freed %p (%zu)\n", p[n], s[n]);
if (!multi_heap_check(heap, true)) {
printf("FAILED iteration %d after freeing %p\n", i, p[n]);
multi_heap_dump(heap);
REQUIRE(0);
}
}
s[n] = rand() % 1024;
REQUIRE( multi_heap_check(heap, true) );
p[n] = multi_heap_malloc(heap, s[n]);
printf("malloc %p (%zu)\n", p[n], s[n]);
if (p[n] != NULL) {
REQUIRE( p[n] >= big_heap );
REQUIRE( p[n] < big_heap + heap_size );
}
if (!multi_heap_check(heap, true)) {
printf("FAILED iteration %d after mallocing %p (%zu bytes)\n", i, p[n], s[n]);
multi_heap_dump(heap);
REQUIRE(0);
}
if (p[n] != NULL) {
memset(p[n], n, s[n]);
}
}
for (int i = 0; i < NUM_POINTERS; i++) {
multi_heap_free(heap, p[i]);
if (!multi_heap_check(heap, true)) {
printf("FAILED during cleanup after freeing %p\n", p[i]);
multi_heap_dump(heap);
REQUIRE(0);
}
}
REQUIRE( initial_free == multi_heap_free_size(heap) );
__free__(big_heap);
}
TEST_CASE("multi_heap many random allocations", "[multi_heap]")
{
size_t poolsize[] = { 15, 255, 4095, 8191 };
for (size_t i = 0; i < sizeof(poolsize)/sizeof(size_t); i++) {
multi_heap_allocation_impl(poolsize[i] * 1024);
}
}
TEST_CASE("multi_heap_get_info() function", "[multi_heap]")
{
uint8_t heapdata[4 * 1024];
multi_heap_handle_t heap = multi_heap_register(heapdata, sizeof(heapdata));
multi_heap_info_t before, after, freed;
multi_heap_get_info(heap, &before);
printf("before: total_free_bytes %zu\ntotal_allocated_bytes %zu\nlargest_free_block %zu\nminimum_free_bytes %zu\nallocated_blocks %zu\nfree_blocks %zu\ntotal_blocks %zu\n",
before.total_free_bytes,
before.total_allocated_bytes,
before.largest_free_block,
before.minimum_free_bytes,
before.allocated_blocks,
before.free_blocks,
before.total_blocks);
REQUIRE( 0 == before.allocated_blocks );
REQUIRE( 0 == before.total_allocated_bytes );
REQUIRE( before.total_free_bytes == before.minimum_free_bytes );
void *x = multi_heap_malloc(heap, 32);
multi_heap_get_info(heap, &after);
printf("after: total_free_bytes %zu\ntotal_allocated_bytes %zu\nlargest_free_block %zu\nminimum_free_bytes %zu\nallocated_blocks %zu\nfree_blocks %zu\ntotal_blocks %zu\n",
after.total_free_bytes,
after.total_allocated_bytes,
after.largest_free_block,
after.minimum_free_bytes,
after.allocated_blocks,
after.free_blocks,
after.total_blocks);
REQUIRE( 1 == after.allocated_blocks );
REQUIRE( 32 == after.total_allocated_bytes );
REQUIRE( after.minimum_free_bytes < before.minimum_free_bytes);
REQUIRE( after.minimum_free_bytes > 0 );
multi_heap_free(heap, x);
multi_heap_get_info(heap, &freed);
printf("freed: total_free_bytes %zu\ntotal_allocated_bytes %zu\nlargest_free_block %zu\nminimum_free_bytes %zu\nallocated_blocks %zu\nfree_blocks %zu\ntotal_blocks %zu\n",
freed.total_free_bytes,
freed.total_allocated_bytes,
freed.largest_free_block,
freed.minimum_free_bytes,
freed.allocated_blocks,
freed.free_blocks,
freed.total_blocks);
REQUIRE( 0 == freed.allocated_blocks );
REQUIRE( 0 == freed.total_allocated_bytes );
REQUIRE( before.total_free_bytes == freed.total_free_bytes );
REQUIRE( after.minimum_free_bytes == freed.minimum_free_bytes );
}
TEST_CASE("multi_heap minimum-size allocations", "[multi_heap]")
{
uint8_t heapdata[4096];
void *p[sizeof(heapdata) / sizeof(void *)] = {NULL};
const size_t NUM_P = sizeof(p) / sizeof(void *);
size_t allocated_size = 0;
multi_heap_handle_t heap = multi_heap_register(heapdata, sizeof(heapdata));
size_t before_free = multi_heap_free_size(heap);
size_t i;
for (i = 0; i < NUM_P; i++) {
//TLSF minimum block size is 4 bytes
p[i] = multi_heap_malloc(heap, 1);
if (p[i] == NULL) {
break;
}
}
REQUIRE( i < NUM_P); // Should have run out of heap before we ran out of pointers
printf("Allocated %zu minimum size chunks\n", i);
REQUIRE(multi_heap_free_size(heap) < before_free);
multi_heap_check(heap, true);
/* Free in random order */
bool has_allocations = true;
while (has_allocations) {
i = rand() % NUM_P;
multi_heap_free(heap, p[i]);
p[i] = NULL;
multi_heap_check(heap, true);
has_allocations = false;
for (i = 0; i < NUM_P && !has_allocations; i++) {
has_allocations = (p[i] != NULL);
}
}
/* all freed! */
REQUIRE( before_free == multi_heap_free_size(heap) );
}
TEST_CASE("multi_heap_realloc()", "[multi_heap]")
{
const uint32_t PATTERN = 0xABABDADA;
uint8_t small_heap[4 * 1024];
multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap));
uint32_t *a = (uint32_t *)multi_heap_malloc(heap, 64);
uint32_t *b = (uint32_t *)multi_heap_malloc(heap, 32);
REQUIRE( a != NULL );
REQUIRE( b != NULL );
REQUIRE( b > a); /* 'b' takes the block after 'a' */
*a = PATTERN;
uint32_t *c = (uint32_t *)multi_heap_realloc(heap, a, 72);
REQUIRE( multi_heap_check(heap, true));
REQUIRE( c != NULL );
REQUIRE( c > b ); /* 'a' moves, 'c' takes the block after 'b' */
REQUIRE( *c == PATTERN );
#ifndef MULTI_HEAP_POISONING_SLOW
// "Slow" poisoning implementation doesn't reallocate in place, so these
// test will fail...
uint32_t *d = (uint32_t *)multi_heap_realloc(heap, c, 36);
REQUIRE( multi_heap_check(heap, true) );
REQUIRE( c == d ); /* 'c' block should be shrunk in-place */
REQUIRE( *d == PATTERN);
uint32_t *e = (uint32_t *)multi_heap_malloc(heap, 64);
REQUIRE( multi_heap_check(heap, true));
REQUIRE( a == e ); /* 'e' takes the block formerly occupied by 'a' */
multi_heap_free(heap, d);
uint32_t *f = (uint32_t *)multi_heap_realloc(heap, b, 64);
REQUIRE( multi_heap_check(heap, true) );
REQUIRE( f == b ); /* 'b' should be extended in-place, over space formerly occupied by 'd' */
#ifdef MULTI_HEAP_POISONING
#define TOO_MUCH 7420 + 1
#else
#define TOO_MUCH 7420 + 1
#endif
/* not enough contiguous space left in the heap */
uint32_t *g = (uint32_t *)multi_heap_realloc(heap, e, TOO_MUCH);
REQUIRE( g == NULL );
multi_heap_free(heap, f);
/* try again */
g = (uint32_t *)multi_heap_realloc(heap, e, 128);
REQUIRE( multi_heap_check(heap, true) );
REQUIRE( e == g ); /* 'g' extends 'e' in place, into the space formerly held by 'f' */
#endif
}
// TLSF only accepts heaps aligned to 4-byte boundary so
// only aligned allocation tests make sense.
TEST_CASE("multi_heap aligned allocations", "[multi_heap]")
{
uint8_t test_heap[4 * 1024];
multi_heap_handle_t heap = multi_heap_register(test_heap, sizeof(test_heap));
uint32_t aligments = 0; // starts from alignment by 4-byte boundary
size_t old_size = multi_heap_free_size(heap);
size_t leakage = 1024;
printf("[ALIGNED_ALLOC] heap_size before: %d \n", old_size);
printf("New heap:\n");
multi_heap_dump(heap);
printf("*********************\n");
for(;aligments <= 256; aligments++) {
//Use some stupid size value to test correct alignment even in strange
//memory layout objects:
uint8_t *buf = (uint8_t *)multi_heap_aligned_alloc(heap, (aligments + 137), aligments );
if(((aligments & (aligments - 1)) != 0) || (!aligments)) {
REQUIRE( buf == NULL );
} else {
REQUIRE( buf != NULL );
REQUIRE((intptr_t)buf >= (intptr_t)test_heap);
REQUIRE((intptr_t)buf < (intptr_t)(test_heap + sizeof(test_heap)));
printf("[ALIGNED_ALLOC] alignment required: %u \n", aligments);
printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf);
//Address of obtained block must be aligned with selected value
REQUIRE(((intptr_t)buf & (aligments - 1)) == 0);
//Write some data, if it corrupts memory probably the heap
//canary verification will fail:
memset(buf, 0xA5, (aligments + 137));
multi_heap_free(heap, buf);
}
}
printf("[ALIGNED_ALLOC] heap_size after: %d \n", multi_heap_free_size(heap));
REQUIRE((old_size - multi_heap_free_size(heap)) <= leakage);
}

View File

@@ -0,0 +1,11 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES platform_config tools esp_common
PRIV_REQUIRES services freertos driver
)
set_source_files_properties(led_strip.c
PROPERTIES COMPILE_FLAGS
-Wno-format-overflow
)

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,406 @@
/* ----------------------------------------------------------------------------
File: led_strip.c
Author(s): Lucas Bruder <LBruder@me.com>
Date Created: 11/23/2016
Last modified: 11/26/2016
Updated: C. Rohs - The update thread now
only runs when signalled. The double buffer code was modified to copy on show
instead of the ping pong buffer that destroyed the buffers contents.
The current code is not thread safe, but is more performant, and the thread
safety does not matter the was it is currently used.
Description: LED Library for driving various led strips on ESP32.
This library uses double buffering to display the LEDs.
------------------------------------------------------------------------- */
#include "led_strip.h"
#include "freertos/task.h"
#include <string.h>
#define LED_STRIP_TASK_SIZE (1024)
#define LED_STRIP_TASK_PRIORITY (configMAX_PRIORITIES - 1)
#define LED_STRIP_REFRESH_PERIOD_MS (30U) // TODO: add as parameter to led_strip_init
#define LED_STRIP_NUM_RMT_ITEMS_PER_LED (24U) // Assumes 24 bit color for each led
// RMT Clock source is @ 80 MHz. Dividing it by 8 gives us 10 MHz frequency, or 100ns period.
#define LED_STRIP_RMT_CLK_DIV (8)
/****************************
WS2812 Timing
****************************/
#define LED_STRIP_RMT_TICKS_BIT_1_HIGH_WS2812 9 // 900ns (900ns +/- 150ns per datasheet)
#define LED_STRIP_RMT_TICKS_BIT_1_LOW_WS2812 3 // 300ns (350ns +/- 150ns per datasheet)
#define LED_STRIP_RMT_TICKS_BIT_0_HIGH_WS2812 3 // 300ns (350ns +/- 150ns per datasheet)
#define LED_STRIP_RMT_TICKS_BIT_0_LOW_WS2812 9 // 900ns (900ns +/- 150ns per datasheet)
/****************************
SK6812 Timing
****************************/
#define LED_STRIP_RMT_TICKS_BIT_1_HIGH_SK6812 6
#define LED_STRIP_RMT_TICKS_BIT_1_LOW_SK6812 6
#define LED_STRIP_RMT_TICKS_BIT_0_HIGH_SK6812 3
#define LED_STRIP_RMT_TICKS_BIT_0_LOW_SK6812 9
/****************************
APA106 Timing
****************************/
#define LED_STRIP_RMT_TICKS_BIT_1_HIGH_APA106 14 // 1.36us +/- 150ns per datasheet
#define LED_STRIP_RMT_TICKS_BIT_1_LOW_APA106 3 // 350ns +/- 150ns per datasheet
#define LED_STRIP_RMT_TICKS_BIT_0_HIGH_APA106 3 // 350ns +/- 150ns per datasheet
#define LED_STRIP_RMT_TICKS_BIT_0_LOW_APA106 14 // 1.36us +/- 150ns per datasheet
// Function pointer for generating waveforms based on different LED drivers
typedef void (*led_fill_rmt_items_fn)(struct led_color_t *led_strip_buf, rmt_item32_t *rmt_items, uint32_t led_strip_length);
static inline void led_strip_fill_item_level(rmt_item32_t* item, int high_ticks, int low_ticks)
{
item->level0 = 1;
item->duration0 = high_ticks;
item->level1 = 0;
item->duration1 = low_ticks;
}
static inline void led_strip_rmt_bit_1_sk6812(rmt_item32_t* item)
{
led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_1_HIGH_SK6812, LED_STRIP_RMT_TICKS_BIT_1_LOW_SK6812);
}
static inline void led_strip_rmt_bit_0_sk6812(rmt_item32_t* item)
{
led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_0_HIGH_SK6812, LED_STRIP_RMT_TICKS_BIT_0_LOW_SK6812);
}
static void led_strip_fill_rmt_items_sk6812(struct led_color_t *led_strip_buf, rmt_item32_t *rmt_items, uint32_t led_strip_length)
{
uint32_t rmt_items_index = 0;
for (uint32_t led_index = 0; led_index < led_strip_length; led_index++) {
struct led_color_t led_color = led_strip_buf[led_index];
for (uint8_t bit = 8; bit != 0; bit--) {
uint8_t bit_set = (led_color.green >> (bit - 1)) & 1;
if(bit_set) {
led_strip_rmt_bit_1_sk6812(&(rmt_items[rmt_items_index]));
} else {
led_strip_rmt_bit_0_sk6812(&(rmt_items[rmt_items_index]));
}
rmt_items_index++;
}
for (uint8_t bit = 8; bit != 0; bit--) {
uint8_t bit_set = (led_color.red >> (bit - 1)) & 1;
if(bit_set) {
led_strip_rmt_bit_1_sk6812(&(rmt_items[rmt_items_index]));
} else {
led_strip_rmt_bit_0_sk6812(&(rmt_items[rmt_items_index]));
}
rmt_items_index++;
}
for (uint8_t bit = 8; bit != 0; bit--) {
uint8_t bit_set = (led_color.blue >> (bit - 1)) & 1;
if(bit_set) {
led_strip_rmt_bit_1_sk6812(&(rmt_items[rmt_items_index]));
} else {
led_strip_rmt_bit_0_sk6812(&(rmt_items[rmt_items_index]));
}
rmt_items_index++;
}
}
}
static inline void led_strip_rmt_bit_1_ws2812(rmt_item32_t* item)
{
led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_1_HIGH_WS2812, LED_STRIP_RMT_TICKS_BIT_1_LOW_WS2812);
}
static inline void led_strip_rmt_bit_0_ws2812(rmt_item32_t* item)
{
led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_0_HIGH_WS2812, LED_STRIP_RMT_TICKS_BIT_0_LOW_WS2812);
}
static void led_strip_fill_rmt_items_ws2812(struct led_color_t *led_strip_buf, rmt_item32_t *rmt_items, uint32_t led_strip_length)
{
uint32_t rmt_items_index = 0;
for (uint32_t led_index = 0; led_index < led_strip_length; led_index++) {
struct led_color_t led_color = led_strip_buf[led_index];
for (uint8_t bit = 8; bit != 0; bit--) {
uint8_t bit_set = (led_color.green >> (bit - 1)) & 1;
if(bit_set) {
led_strip_rmt_bit_1_ws2812(&(rmt_items[rmt_items_index]));
} else {
led_strip_rmt_bit_0_ws2812(&(rmt_items[rmt_items_index]));
}
rmt_items_index++;
}
for (uint8_t bit = 8; bit != 0; bit--) {
uint8_t bit_set = (led_color.red >> (bit - 1)) & 1;
if(bit_set) {
led_strip_rmt_bit_1_ws2812(&(rmt_items[rmt_items_index]));
} else {
led_strip_rmt_bit_0_ws2812(&(rmt_items[rmt_items_index]));
}
rmt_items_index++;
}
for (uint8_t bit = 8; bit != 0; bit--) {
uint8_t bit_set = (led_color.blue >> (bit - 1)) & 1;
if(bit_set) {
led_strip_rmt_bit_1_ws2812(&(rmt_items[rmt_items_index]));
} else {
led_strip_rmt_bit_0_ws2812(&(rmt_items[rmt_items_index]));
}
rmt_items_index++;
}
}
}
static inline void led_strip_rmt_bit_1_apa106(rmt_item32_t* item)
{
led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_1_HIGH_APA106, LED_STRIP_RMT_TICKS_BIT_1_LOW_APA106);
}
static inline void led_strip_rmt_bit_0_apa106(rmt_item32_t* item)
{
led_strip_fill_item_level(item, LED_STRIP_RMT_TICKS_BIT_0_HIGH_APA106, LED_STRIP_RMT_TICKS_BIT_0_LOW_APA106);
}
static void led_strip_fill_rmt_items_apa106(struct led_color_t *led_strip_buf, rmt_item32_t *rmt_items, uint32_t led_strip_length)
{
uint32_t rmt_items_index = 0;
for (uint32_t led_index = 0; led_index < led_strip_length; led_index++) {
struct led_color_t led_color = led_strip_buf[led_index];
for (uint8_t bit = 8; bit != 0; bit--) {
uint8_t bit_set = (led_color.red >> (bit - 1)) & 1;
if(bit_set) {
led_strip_rmt_bit_1_apa106(&(rmt_items[rmt_items_index]));
} else {
led_strip_rmt_bit_0_apa106(&(rmt_items[rmt_items_index]));
}
rmt_items_index++;
}
for (uint8_t bit = 8; bit != 0; bit--) {
uint8_t bit_set = (led_color.green >> (bit - 1)) & 1;
if(bit_set) {
led_strip_rmt_bit_1_apa106(&(rmt_items[rmt_items_index]));
} else {
led_strip_rmt_bit_0_apa106(&(rmt_items[rmt_items_index]));
}
rmt_items_index++;
}
for (uint8_t bit = 8; bit != 0; bit--) {
uint8_t bit_set = (led_color.blue >> (bit - 1)) & 1;
if(bit_set) {
led_strip_rmt_bit_1_apa106(&(rmt_items[rmt_items_index]));
} else {
led_strip_rmt_bit_0_apa106(&(rmt_items[rmt_items_index]));
}
rmt_items_index++;
}
}
}
static void led_strip_task(void *arg)
{
struct led_strip_t *led_strip = (struct led_strip_t *)arg;
led_fill_rmt_items_fn led_make_waveform = NULL;
size_t num_items_malloc = (LED_STRIP_NUM_RMT_ITEMS_PER_LED * led_strip->led_strip_length);
rmt_item32_t *rmt_items = (rmt_item32_t*) malloc(sizeof(rmt_item32_t) * num_items_malloc);
if (!rmt_items) {
vTaskDelete(NULL);
}
switch (led_strip->rgb_led_type) {
case RGB_LED_TYPE_WS2812:
led_make_waveform = led_strip_fill_rmt_items_ws2812;
break;
case RGB_LED_TYPE_SK6812:
led_make_waveform = led_strip_fill_rmt_items_sk6812;
break;
case RGB_LED_TYPE_APA106:
led_make_waveform = led_strip_fill_rmt_items_apa106;
break;
default:
// Will avoid keeping it point to NULL
led_make_waveform = led_strip_fill_rmt_items_ws2812;
break;
};
for(;;) {
rmt_wait_tx_done(led_strip->rmt_channel, portMAX_DELAY);
vTaskDelay(LED_STRIP_REFRESH_PERIOD_MS / portTICK_PERIOD_MS);
xSemaphoreTake(led_strip->access_semaphore, portMAX_DELAY);
led_make_waveform(led_strip->led_strip_working,
rmt_items,
led_strip->led_strip_length);
rmt_write_items(led_strip->rmt_channel,
rmt_items,
num_items_malloc,
false);
}
if (rmt_items) {
free(rmt_items);
}
vTaskDelete(NULL);
}
static bool led_strip_init_rmt(struct led_strip_t *led_strip)
{
rmt_config_t rmt_cfg = {
.rmt_mode = RMT_MODE_TX,
.channel = led_strip->rmt_channel,
.clk_div = LED_STRIP_RMT_CLK_DIV,
.gpio_num = led_strip->gpio,
.mem_block_num = 1,
.tx_config = {
.loop_en = false,
.carrier_freq_hz = 100, // Not used, but has to be set to avoid divide by 0 err
.carrier_duty_percent = 50,
.carrier_level = RMT_CARRIER_LEVEL_LOW,
.carrier_en = false,
.idle_level = RMT_IDLE_LEVEL_LOW,
.idle_output_en = true,
}
};
esp_err_t cfg_ok = rmt_config(&rmt_cfg);
if (cfg_ok != ESP_OK) {
return false;
}
esp_err_t install_ok = rmt_driver_install(rmt_cfg.channel, 0, 0);
if (install_ok != ESP_OK) {
return false;
}
return true;
}
bool led_strip_init(struct led_strip_t *led_strip)
{
static EXT_RAM_ATTR TaskHandle_t task_created;
StaticTask_t* xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
static EXT_RAM_ATTR StackType_t xStack[LED_STRIP_TASK_SIZE] __attribute__ ((aligned (4)));
if ((led_strip == NULL) ||
(led_strip->led_strip_working == NULL) ||
(led_strip->led_strip_showing == NULL) ||
(led_strip->access_semaphore == NULL)) {
return false;
}
if(led_strip->led_strip_working == led_strip->led_strip_showing) {
return false;
}
memset(led_strip->led_strip_working, 0, sizeof(struct led_color_t) * led_strip->led_strip_length);
memset(led_strip->led_strip_showing, 0, sizeof(struct led_color_t) * led_strip->led_strip_length);
bool init_rmt = led_strip_init_rmt(led_strip);
if (!init_rmt) {
return false;
}
xSemaphoreGive(led_strip->access_semaphore);
task_created = xTaskCreateStatic(led_strip_task,
"led_strip_task",
LED_STRIP_TASK_SIZE,
led_strip,
LED_STRIP_TASK_PRIORITY,
xStack, xTaskBuffer);
if (!task_created) {
return false;
}
return true;
}
bool led_strip_set_pixel_color(struct led_strip_t *led_strip, uint32_t pixel_num, struct led_color_t *color)
{
bool set_led_success = true;
if ((!led_strip) || (!color) || (pixel_num > led_strip->led_strip_length)) {
return false;
}
led_strip->led_strip_working[pixel_num] = *color;
return set_led_success;
}
bool led_strip_set_pixel_rgb(struct led_strip_t *led_strip, uint32_t pixel_num, uint8_t red, uint8_t green, uint8_t blue)
{
bool set_led_success = true;
if ((!led_strip) || (pixel_num > led_strip->led_strip_length)) {
return false;
}
led_strip->led_strip_working[pixel_num].red = red;
led_strip->led_strip_working[pixel_num].green = green;
led_strip->led_strip_working[pixel_num].blue = blue;
return set_led_success;
}
bool led_strip_get_pixel_color(struct led_strip_t *led_strip, uint32_t pixel_num, struct led_color_t *color)
{
bool get_success = true;
if ((!led_strip) ||
(pixel_num > led_strip->led_strip_length) ||
(!color)) {
color = NULL;
return false;
}
*color = led_strip->led_strip_working[pixel_num];
return get_success;
}
/**
* Updates the led buffer to be shown
*/
bool led_strip_show(struct led_strip_t *led_strip)
{
bool success = true;
if (!led_strip) {
return false;
}
/* copy the current buffer for display */
memcpy(led_strip->led_strip_showing,led_strip->led_strip_working, sizeof(struct led_color_t) * led_strip->led_strip_length);
xSemaphoreGive(led_strip->access_semaphore);
return success;
}
/**
* Clears the LED strip
*/
bool led_strip_clear(struct led_strip_t *led_strip)
{
bool success = true;
if (!led_strip) {
return false;
}
memset(led_strip->led_strip_working,
0,
sizeof(struct led_color_t) * led_strip->led_strip_length);
return success;
}

View File

@@ -0,0 +1,86 @@
/* ---------------------------------------------------------------------------
File: led_strip.h
Author(s): Lucas Bruder <LBruder@me.com>
Date Created: 11/23/2016
Last modified: 11/26/2016
Description:
This library can drive led strips through the RMT module on the ESP32.
------------------------------------------------------------------------ */
#ifndef LED_STRIP_H
#define LED_STRIP_H
#ifdef __cplusplus
extern "C" {
#endif
#include <driver/rmt.h>
#include <driver/gpio.h>
#include "freertos/semphr.h"
#include <stddef.h>
enum rgb_led_type_t {
RGB_LED_TYPE_WS2812 = 0,
RGB_LED_TYPE_SK6812 = 1,
RGB_LED_TYPE_APA106 = 2,
RGB_LED_TYPE_MAX,
};
/**
* RGB LED colors
*/
struct led_color_t {
uint8_t red;
uint8_t green;
uint8_t blue;
};
struct led_strip_t {
enum rgb_led_type_t rgb_led_type; // should be const, but workaround needed for initialization
uint32_t led_strip_length;
// RMT peripheral settings
rmt_channel_t rmt_channel;
gpio_num_t gpio; // Must be less than GPIO_NUM_33
struct led_color_t *led_strip_working;
struct led_color_t *led_strip_showing;
SemaphoreHandle_t access_semaphore;
};
bool led_strip_init(struct led_strip_t *led_strip);
/**
* Sets the pixel at pixel_num to color.
*/
bool led_strip_set_pixel_color(struct led_strip_t *led_strip, uint32_t pixel_num, struct led_color_t *color);
bool led_strip_set_pixel_rgb(struct led_strip_t *led_strip, uint32_t pixel_num, uint8_t red, uint8_t green, uint8_t blue);
/**
* Get the pixel color at pixel_num for the led strip that is currently being shown!
* NOTE: If you call set_pixel_color then get_pixel_color for the same pixel_num, you will not
* get back the same pixel value. This gets you the color of the pixel currently being shown, not the one
* being updated
*
* If there is an invalid argument, color will point to NULL and this function will return false.
*/
bool led_strip_get_pixel_color(struct led_strip_t *led_strip, uint32_t pixel_num, struct led_color_t *color);
/**
* Updates the led buffer to be shown using double buffering.
*/
bool led_strip_show(struct led_strip_t *led_strip);
/**
* Clears the LED strip.
*/
bool led_strip_clear(struct led_strip_t *led_strip);
#ifdef __cplusplus
}
#endif
#endif // LED_STRIP_H

View File

@@ -0,0 +1,412 @@
/*
* Control of LED strip within squeezelite-esp32
*
* (c) Wizmo 2021
*
* Loosely based on code by
* Chuck Rohs 2020, chuck@zethus.ca
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*
* ToDo:
* Driver does support other led device. Maybe look at supporting in future.
* The VU refresh rate has been decreaced (100->75) to optimize animation of spin dial. Could make
* configurable like text scrolling (or use the same value)
* Artwork function, but not released as very buggy and not really practical
*/
#include <ctype.h>
#include <math.h>
#include "esp_log.h"
#include "globdefs.h"
#include "monitor.h"
#include "led_strip.h"
#include "platform_config.h"
#include "services.h"
#include "led_vu.h"
static const char *TAG = "led_vu";
static void (*battery_handler_chain)(float value, int cells);
static void battery_svc(float value, int cells);
static int battery_status = 0;
#define LED_VU_STACK_SIZE (3*1024)
#define LED_VU_PEAK_HOLD 6U
#define LED_VU_DEFAULT_GPIO 22
#define LED_VU_DEFAULT_LENGTH 19
#define LED_VU_MAX_LENGTH 255
#define LED_VU_STATUS_GREEN 75
#define LED_VU_STATUS_RED 25
#define max(a,b) (((a) > (b)) ? (a) : (b))
struct led_strip_t* led_display = NULL;
static EXT_RAM_ATTR struct led_strip_t led_strip_config;
static EXT_RAM_ATTR struct {
int gpio;
int length;
int vu_length;
int vu_start_l;
int vu_start_r;
int vu_status;
int vu_scale;
} strip;
static int led_addr(int pos ) {
if (pos < 0) return pos + strip.length;
if (pos >= strip.length) return pos - strip.length;
return pos;
}
static void battery_svc(float value, int cells) {
battery_status = battery_level_svc();
ESP_LOGI(TAG, "Called for battery service with volt:%f cells:%d status:%d", value, cells, battery_status);
if (battery_handler_chain) battery_handler_chain(value, cells);
}
/****************************************************************************************
* Suspend.
*
*/
static void led_vu_sleep(void) {
led_vu_clear(led_display);
}
/****************************************************************************************
* Initialize the led vu strip if configured.
*
*/
void led_vu_init()
{
char* config = config_alloc_get_str("led_vu_config", NULL, "N/A");
PARSE_PARAM(config, "length",'=', strip.length);
PARSE_PARAM(config, "gpio",'=', strip.gpio);
// check for valid configuration
if (!strip.gpio) {
ESP_LOGI(TAG, "led_vu configuration invalid");
goto done;
}
strip.vu_scale = 100;
PARSE_PARAM(config, "scale",'=',strip.vu_scale);
battery_handler_chain = battery_handler_svc;
battery_handler_svc = battery_svc;
battery_status = battery_level_svc();
if (strip.length > LED_VU_MAX_LENGTH) strip.length = LED_VU_MAX_LENGTH;
// initialize vu meter settings
if (strip.length < 10) {
// single bar for small strips
strip.vu_length = strip.length;
strip.vu_start_l = 0;
strip.vu_start_r = strip.vu_start_l;
strip.vu_status = 0;
} else {
strip.vu_length = (strip.length - 1) / 2;
strip.vu_start_l = (strip.length % 2) ? strip.vu_length -1 : strip.vu_length;
strip.vu_start_r = strip.vu_length + 1;
strip.vu_status = strip.vu_length;
}
ESP_LOGI(TAG, "vu meter using length:%d left:%d right:%d status:%d scale:%d", strip.vu_length, strip.vu_start_l, strip.vu_start_r, strip.vu_status, strip.vu_scale);
// create driver configuration
led_strip_config.rgb_led_type = RGB_LED_TYPE_WS2812;
led_strip_config.access_semaphore = xSemaphoreCreateBinary();
led_strip_config.led_strip_length = strip.length;
led_strip_config.led_strip_working = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT);
led_strip_config.led_strip_showing = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT);
led_strip_config.gpio = strip.gpio;
led_strip_config.rmt_channel = RMT_NEXT_TX_CHANNEL();
// initialize driver
bool led_init_ok = led_strip_init(&led_strip_config);
if (led_init_ok) {
led_display = &led_strip_config;
ESP_LOGI(TAG, "led_vu using gpio:%d length:%d on channel:%d", strip.gpio, strip.length, led_strip_config.rmt_channel);
} else {
ESP_LOGE(TAG, "led_vu init failed");
goto done;
}
// reserver max memory for remote management systems
rmt_set_mem_block_num(led_strip_config.rmt_channel, 7);
services_sleep_setsuspend(led_vu_sleep);
led_vu_clear(led_display);
done:
free(config);
return;
}
inline bool inRange(double x, double y, double z) {
return (x > y && x < z);
}
/****************************************************************************************
* Returns the led strip length
*/
uint16_t led_vu_string_length() {
if (!led_display) return 0;
return (uint16_t)strip.length;
}
/****************************************************************************************
* Returns a user defined scale (percent)
*/
uint16_t led_vu_scale() {
if (!led_display) return 0;
return (uint16_t)strip.vu_scale;
}
/****************************************************************************************
* Turns all LEDs off (Black)
*/
void led_vu_clear() {
if (!led_display) return;
led_strip_clear(led_display);
led_strip_show(led_display);
}
/****************************************************************************************
* Sets all LEDs to one color
* r = red (0-255), g = green (0-255), b - blue (0-255)
* note - all colors are adjusted for brightness
*/
void led_vu_color_all(uint8_t r, uint8_t g, uint8_t b) {
if (!led_display) return;
struct led_color_t color_on = {.red = r, .green = g, .blue = b};
for (int i = 0 ; i < strip.length ; i ++){
led_strip_set_pixel_color(led_display, i, &color_on);
}
led_strip_show(led_display);
}
/****************************************************************************************
* Sets LEDs based on a data packet consiting of rgb data
* offset - starting LED,
* length - number of leds (3x rgb bytes)
* data - array of rgb values in multiples of 3 bytes
*/
void led_vu_data(uint8_t* data, uint16_t offset, uint16_t length) {
if (!led_display) return;
uint8_t* p = (uint8_t*) data;
for (int i = 0; i < length; i++) {
led_strip_set_pixel_rgb(led_display, i+offset, *p, *(p+1), *(p+2));
p+=3;
}
led_strip_show(led_display);
}
/****************************************************************************************
* Progress bar display
* data - array of gain values(0-100)
* offset - starting position
* length - size of array
*/
void led_vu_spectrum(uint8_t* data, int bright, int length, int style) {
if (!led_display) return;
uint8_t gain,r,g,b;
int width = strip.length / length;
int pos = 0;
uint8_t* p = (uint8_t*) data;
for (int i=0; i<length; i++) {
gain = *p;
r = gain*gain/bright;
if (!style) {
g = 0;
b = gain;
} else {
g = r;
r = 0;
b = gain * (bright-gain)/bright;
}
for (int j=0; j<width; j++) {
led_strip_set_pixel_rgb(led_display, pos, r, g, b);
pos++;
}
p++;
}
led_strip_show(led_display);
}
/****************************************************************************************
* Progress bar display
* pct - percentage complete (0-100)
*/
void led_vu_progress_bar(int pct, int bright) {
if (!led_display) return;
// define colors
struct led_color_t color_on = {.red = bright, .green = 0, .blue = 0};
struct led_color_t color_off = {.red = 0, .green = bright, .blue = 0};
// calcuate led position
int led_lit = strip.length * pct / 100;
// set colors
for (int i = 0; i < strip.length; i++) {
led_strip_set_pixel_color(led_display, i, (i < led_lit) ? &color_off : &color_on);
}
led_strip_show(led_display);
}
/****************************************************************************************
* Spin dial display
* gain - brightness (0-100), rate - color change speed (0-100)
* comet - alternate display mode
*/
void led_vu_spin_dial(int gain, int rate, int speed, bool comet)
{
if (!led_display) return;
static int led_pos = 0;
static uint8_t r = 0;
static uint8_t g = 0;
static uint8_t b = 0;
// calculate next color
uint8_t step = rate / 2; // controls color change speed
if (r == 0 && g == 0 && b == 0) {
r = LED_VU_MAX; g = step;
} else if (b == 0) {
g = (g > LED_VU_MAX-step) ? LED_VU_MAX : g + step;
r = (r < step) ? 0 : r - step;
if (r == 0) b = step;
} else if (r == 0) {
b = (b > LED_VU_MAX-step) ? LED_VU_MAX : b + step;
g = (g < step) ? 0 : g- step;
if (g == 0) r = step;
} else {
r = (r > LED_VU_MAX-step) ? LED_VU_MAX : r + step;
b = (b < step) ? 0 : b - step;
if (r == 0) b = step;
}
uint8_t rp = r * gain / LED_VU_MAX;
uint8_t gp = g * gain / LED_VU_MAX;
uint8_t bp = b * gain / LED_VU_MAX;
// set led color
speed++;
if (comet) {
led_strip_clear(led_display);
led_strip_set_pixel_rgb(led_display, led_addr(led_pos-1), rp/2, gp/2, bp/2);
led_strip_set_pixel_rgb(led_display, led_addr(led_pos-2), rp/4, gp/4, bp/4);
led_strip_set_pixel_rgb(led_display, led_addr(led_pos-3), rp/8, gp/8, bp/8);
//led_strip_set_pixel_rgb(led_display, led_addr(led_pos-4), 0, 0, 0);
}
for (int i = 0; i < speed; i++) {
led_strip_set_pixel_rgb(led_display, led_pos, rp, gp, bp);
led_pos = led_addr(++led_pos);
}
led_strip_show(led_display);
}
/****************************************************************************************
* VU meter display
* vu_l - left response (0-100), vu_r - right response (0-100)
* comet - alternate display mode
*/
void led_vu_display(int vu_l, int vu_r, int bright, bool comet) {
static int peak_l = 0;
static int peak_r = 0;
static int decay_l = 0;
static int decay_r = 0;
if (!led_display) return;
// single bar
if (strip.vu_start_l == strip.vu_start_r) {
vu_r = (vu_l + vu_r) / 2;
vu_l = 0;
}
// scale vu samples to length
vu_l = vu_l * strip.vu_length / bright;
vu_r = vu_r * strip.vu_length / bright;
// calculate hold peaks
if (peak_l > vu_l) {
if (decay_l-- < 0) {
decay_l = LED_VU_PEAK_HOLD;
peak_l--;
}
} else {
peak_l = vu_l;
decay_l = LED_VU_PEAK_HOLD;
}
if (peak_r > vu_r) {
if (decay_r-- < 0) {
decay_r = LED_VU_PEAK_HOLD;
peak_r--;
}
} else {
peak_r = vu_r;
decay_r = LED_VU_PEAK_HOLD;
}
// turn off all leds
led_strip_clear(led_display);
// set the led bar values
uint8_t step = bright / (strip.vu_length-1);
if (step < 1) step = 1; // dor low brightness or larger strips
uint8_t g = bright * 2 / 3; // more red at top
uint8_t r = 0;
int shift = 0;
for (int i = 0; i < strip.vu_length; i++) {
// set left
if (i == peak_l) {
led_strip_set_pixel_rgb(led_display, strip.vu_start_l - i, r, g, bright);
} else if (i <= vu_l) {
shift = vu_l - i;
if (comet)
led_strip_set_pixel_rgb(led_display, strip.vu_start_l - i, r>>shift, g>>shift, 0);
else
led_strip_set_pixel_rgb(led_display, strip.vu_start_l - i, r, g, 0);
}
// set right
if (i == peak_r) {
led_strip_set_pixel_rgb(led_display, strip.vu_start_r + i, r, g, bright);
} else if (i <= vu_r) {
shift = vu_r - i;
if (comet)
led_strip_set_pixel_rgb(led_display, strip.vu_start_r + i, r>>shift, g>>shift, 0);
else
led_strip_set_pixel_rgb(led_display, strip.vu_start_r + i, r, g, 0);
}
// adjust colors (with limit checks)
r = (r > bright-step) ? bright : r + step;
g = (g < step) ? 0 : g - step;
}
// show battery status
if (battery_status > LED_VU_STATUS_GREEN)
led_strip_set_pixel_rgb(led_display, strip.vu_status, 0, bright, 0);
else if (battery_status > LED_VU_STATUS_RED)
led_strip_set_pixel_rgb(led_display, strip.vu_status, bright/2, bright/2, 0);
else if (battery_status > 0)
led_strip_set_pixel_rgb(led_display, strip.vu_status, bright, 0, 0);
led_strip_show(led_display);
}

View File

@@ -0,0 +1,32 @@
/*
* Control of LED strip within squeezelite-esp32
*
* (c) Wizmo 2021
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*
*/
#include <ctype.h>
#define LED_VU_MAX 255U
#define LED_VU_BRIGHT 20U
#define led_vu_color_red(B) led_vu_color_all(B, 0, 0)
#define led_vu_color_green(B) led_vu_color_all(0, B, 0)
#define led_vu_color_blue(B) led_vu_color_all(0, 0, B)
#define led_vu_color_yellow(B) led_vu_color_all(B/2, B/2, 0)
extern struct led_strip_t* led_display;
uint16_t led_vu_string_length();
uint16_t led_vu_scale();
void led_vu_progress_bar(int pct, int bright);
void led_vu_display(int vu_l, int vu_r, int bright, bool comet);
void led_vu_spin_dial(int gain, int rate, int speed, bool comet);
void led_vu_spectrum(uint8_t* data, int bright, int length, int style);
void led_vu_color_all(uint8_t r, uint8_t g, uint8_t b);
void led_vu_data(uint8_t* data, uint16_t offset, uint16_t length);
void led_vu_clear();

View File

@@ -13,14 +13,14 @@ esp_err_t store_nvs_value_len(nvs_type_t type, const char *key, void * data, siz
esp_err_t store_nvs_value(nvs_type_t type, const char *key, void * data);
esp_err_t get_nvs_value(nvs_type_t type, const char *key, void*value, const uint8_t buf_size);
void * get_nvs_value_alloc(nvs_type_t type, const char *key);
void * get_nvs_value_alloc_for_partition(const char * partition,const char * namespace,nvs_type_t type, const char *key, size_t * size);
esp_err_t erase_nvs_for_partition(const char * partition, const char * namespace,const char *key);
esp_err_t store_nvs_value_len_for_partition(const char * partition,const char * namespace,nvs_type_t type, const char *key, const void * data,size_t data_len);
void * get_nvs_value_alloc_for_partition(const char * partition,const char * ns,nvs_type_t type, const char *key, size_t * size);
esp_err_t erase_nvs_for_partition(const char * partition, const char * ns,const char *key);
esp_err_t store_nvs_value_len_for_partition(const char * partition,const char * ns,nvs_type_t type, const char *key, const void * data,size_t data_len);
esp_err_t erase_nvs(const char *key);
void print_blob(const char *blob, size_t len);
const char *type_to_str(nvs_type_t type);
nvs_type_t str_to_type(const char *type);
esp_err_t erase_nvs_partition(const char * partition, const char * namespace);
esp_err_t erase_nvs_partition(const char * partition, const char * ns);
void erase_settings_partition();
#ifdef __cplusplus
}

View File

@@ -121,7 +121,7 @@ void config_start_timer(){
nvs_type_t config_get_item_type(cJSON * entry){
if(entry==NULL){
ESP_LOGE(TAG,"null pointer received!");
return true;
return 0;
}
cJSON * item_type = cJSON_GetObjectItemCaseSensitive(entry, "type");
if(item_type ==NULL ) {
@@ -142,7 +142,7 @@ cJSON * config_set_value_safe(nvs_type_t nvs_type, const char *key, const void
return NULL;
}
cJSON * existing = cJSON_GetObjectItemCaseSensitive(nvs_json, key);
cJSON * existing = cJSON_GetObjectItemCaseSensitive(nvs_json, key);
if(existing !=NULL && nvs_type == NVS_TYPE_STR && config_get_item_type(existing) != NVS_TYPE_STR ) {
ESP_LOGW(TAG, "Storing numeric value from string");
numvalue = atof((char *)value);
@@ -538,7 +538,7 @@ bool config_set_group_bit(int bit_num,bool flag){
return result;
}
void config_set_default(nvs_type_t type, const char *key, void * default_value, size_t blob_size) {
void config_set_default(nvs_type_t type, const char *key, const void * default_value, size_t blob_size) {
if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
ESP_LOGE(TAG, "Unable to lock config");
return;

View File

@@ -56,7 +56,7 @@ cJSON * config_alloc_get_cjson(const char *key);
esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value);
void config_get_uint16t_from_str(const char *key, uint16_t *value, uint16_t default_value);
void config_delete_key(const char *key);
void config_set_default(nvs_type_t type, const char *key, void * default_value, size_t blob_size);
void config_set_default(nvs_type_t type, const char *key, const void * default_value, size_t blob_size);
void * config_alloc_get(nvs_type_t nvs_type, const char *key) ;
bool wait_for_commit();
char * config_alloc_get_json(bool bFormatted);

View File

@@ -9,8 +9,6 @@ idf_component_register( SRCS
INCLUDE_DIRS .
REQUIRES nvs_flash
PRIV_REQUIRES console app_update tools services spi_flash platform_config vfs pthread wifi-manager platform_config newlib telnet display squeezelite tools)
target_link_libraries(${COMPONENT_LIB} "-Wl,--undefined=GDS_DrawPixelFast")
target_link_libraries(${COMPONENT_LIB} ${build_dir}/esp-idf/$<TARGET_PROPERTY:RECOVERY_PREFIX>/lib$<TARGET_PROPERTY:RECOVERY_PREFIX>.a )
set_source_files_properties(cmd_config.c
PROPERTIES COMPILE_FLAGS

View File

@@ -27,6 +27,9 @@ const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
#endif
};
void register_optional_cmd(void) {
}
int main(int argc, char **argv){
return 1;
}

View File

@@ -40,7 +40,22 @@ const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
#endif
};
extern int main(int argc, char **argv);
extern void register_audio_config(void);
extern void register_rotary_config(void);
extern void register_ledvu_config(void);
extern void register_nvs();
void register_optional_cmd(void) {
#if CONFIG_WITH_CONFIG_UI
register_rotary_config();
#endif
register_audio_config();
register_ledvu_config();
register_nvs();
}
extern int squeezelite_main(int argc, char **argv);
static int launchsqueezelite(int argc, char **argv);
/** Arguments used by 'squeezelite' function */
@@ -54,39 +69,32 @@ static struct {
} thread_parms ;
#define ADDITIONAL_SQUEEZELITE_ARGS 5
static void squeezelite_thread(void *arg){
static void squeezelite_thread(void *arg){
ESP_LOGV(TAG ,"Number of args received: %u",thread_parms.argc );
ESP_LOGV(TAG ,"Values:");
for(int i = 0;i<thread_parms.argc; i++){
ESP_LOGV(TAG ," %s",thread_parms.argv[i]);
}
ESP_LOGI(TAG ,"Calling squeezelite");
int ret = squeezelite_main(thread_parms.argc, thread_parms.argv);
cmd_send_messaging("cfg-audio-tmpl",ret > 1 ? MESSAGING_ERROR : MESSAGING_WARNING,"squeezelite exited with error code %d\n", ret);
ESP_LOGI(TAG ,"Calling squeezelite");
int ret = main(thread_parms.argc,thread_parms.argv);
ESP_LOGV(TAG ,"Exited from squeezelite's main(). Freeing argv structure.");
for(int i=0;i<thread_parms.argc;i++){
ESP_LOGV(TAG ,"Freeing char buffer for parameter %u", i+1);
free(thread_parms.argv[i]);
}
ESP_LOGV(TAG ,"Freeing argv pointer");
free(thread_parms.argv);
if(!wait_for_commit()){
ESP_LOGW(TAG,"Unable to commit configuration. ");
}
messaging_post_message(MESSAGING_ERROR, MESSAGING_CLASS_SYSTEM, "squeezelite exited with error code %d", ret);
if (ret == 1) {
if (ret <= 1) {
int wait = 60;
messaging_post_message(MESSAGING_ERROR, MESSAGING_CLASS_SYSTEM, "Rebooting in %d sec", wait);
wait_for_commit();
cmd_send_messaging("cfg-audio-tmpl",MESSAGING_ERROR,"Rebooting in %d sec\n", wait);
vTaskDelay( pdMS_TO_TICKS(wait * 1000));
esp_restart();
} else {
messaging_post_message(MESSAGING_ERROR, MESSAGING_CLASS_SYSTEM, "Correct command line and reboot");
cmd_send_messaging("cfg-audio-tmpl",MESSAGING_ERROR,"Correct command line and reboot\n");
vTaskSuspend(NULL);
}
ESP_LOGV(TAG, "Exited from squeezelite's main(). Freeing argv structure.");
for(int i=0;i<thread_parms.argc;i++) free(thread_parms.argv[i]);
free(thread_parms.argv);
}
static int launchsqueezelite(int argc, char **argv) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,6 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "soc/rtc_cntl_reg.h"
#include "esp32/rom/uart.h"
#include "sdkconfig.h"
#include "platform_console.h"
#include "messaging.h"

View File

@@ -20,7 +20,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "soc/rtc_cntl_reg.h"
#include "esp32/rom/uart.h"
#include "esp_rom_uart.h"
#include "cmd_system.h"
#include "sdkconfig.h"
#include "esp_partition.h"
@@ -28,7 +28,6 @@
#include "platform_esp32.h"
#include "platform_config.h"
#include "esp_sleep.h"
#include "driver/uart.h" // for the uart driver access
#include "messaging.h"
#include "platform_console.h"
#include "tools.h"
@@ -63,10 +62,13 @@ static void register_free();
static void register_setdevicename();
static void register_heap();
static void register_dump_heap();
static void register_abort();
static void register_version();
static void register_restart();
#if CONFIG_WITH_CONFIG_UI
static void register_deep_sleep();
static void register_light_sleep();
#endif
static void register_factory_boot();
static void register_restart_ota();
static void register_set_services();
@@ -74,24 +76,43 @@ static void register_set_services();
static void register_tasks();
#endif
extern BaseType_t network_manager_task;
FILE * system_open_memstream(const char * cmdname,char **buf,size_t *buf_size){
FILE *f = open_memstream(buf, buf_size);
if (f == NULL) {
cmd_send_messaging(cmdname,MESSAGING_ERROR,"Unable to open memory stream.");
}
return f;
}
void register_system()
{
register_free();
register_setdevicename();
register_set_services();
register_free();
register_heap();
register_dump_heap();
register_setdevicename();
register_abort();
register_version();
register_restart();
register_deep_sleep();
register_light_sleep();
register_factory_boot();
register_restart_ota();
#if WITH_TASKS_INFO
register_tasks();
#endif
#if CONFIG_WITH_CONFIG_UI
register_deep_sleep();
register_light_sleep();
#endif
}
void simple_restart()
{
log_send_messaging(MESSAGING_WARNING,"Rebooting.");
if(!wait_for_commit()){
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
}
vTaskDelay(750/ portTICK_PERIOD_MS);
esp_restart();
}
/* 'version' command */
static int get_version(int argc, char **argv)
{
@@ -125,40 +146,48 @@ static void register_version()
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
/* 'abort' command */
static int cmd_abort(int argc, char **argv)
{
cmd_send_messaging(argv[0],MESSAGING_INFO,"ABORT!\r\n");
abort();
return 0;
}
static void register_abort()
{
const esp_console_cmd_t cmd = {
.command = "abort",
.help = "Crash now!",
.hint = NULL,
.func = &cmd_abort,
};
cmd_to_json(&cmd);
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
{
if(is_recovery_running){
if(partition_subtype ==ESP_PARTITION_SUBTYPE_APP_FACTORY){
log_send_messaging(MESSAGING_WARNING,"RECOVERY application is already active");
if(!wait_for_commit()){
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
}
vTaskDelay(750/ portTICK_PERIOD_MS);
esp_restart();
return ESP_OK;
// log_send_messaging(MESSAGING_WARNING,"RECOVERY application is already active");
simple_restart();
}
}
else {
if(partition_subtype !=ESP_PARTITION_SUBTYPE_APP_FACTORY){
log_send_messaging(MESSAGING_WARNING,"SQUEEZELITE application is already active");
if(!wait_for_commit()){
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
}
vTaskDelay(750/ portTICK_PERIOD_MS);
esp_restart();
return ESP_OK;
// log_send_messaging(MESSAGING_WARNING,"SQUEEZELITE application is already active");
simple_restart();
}
}
esp_err_t err = ESP_OK;
bool bFound=false;
log_send_messaging(MESSAGING_INFO, "Looking for partition type %u",partition_subtype);
// log_send_messaging(MESSAGING_INFO, "Looking for partition type %u",partition_subtype);
const esp_partition_t *partition;
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, partition_subtype, NULL);
if(it == NULL){
log_send_messaging(MESSAGING_ERROR,"Reboot failed. Cannot iterate through partitions");
log_send_messaging(MESSAGING_ERROR,"Reboot failed. Partitions error");
}
else
{
@@ -167,15 +196,11 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
ESP_LOGD(TAG, "Releasing partition iterator");
esp_partition_iterator_release(it);
if(partition != NULL){
log_send_messaging(MESSAGING_INFO, "Found application partition %s sub type %u", partition->label,partition_subtype);
log_send_messaging(MESSAGING_INFO, "Rebooting to %s", partition->label);
err=esp_ota_set_boot_partition(partition);
if(err!=ESP_OK){
bFound=false;
log_send_messaging(MESSAGING_ERROR,"Unable to select partition for reboot: %s",esp_err_to_name(err));
}
else{
bFound=true;
}
}
else
{
@@ -184,13 +209,7 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
}
ESP_LOGD(TAG, "Yielding to other processes");
taskYIELD();
if(bFound) {
if(!wait_for_commit()){
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration changes. ");
}
vTaskDelay(750/ portTICK_PERIOD_MS);
esp_restart();
}
simple_restart();
}
return ESP_OK;
@@ -198,46 +217,31 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
static int restart(int argc, char **argv)
{
log_send_messaging(MESSAGING_WARNING, "\n\nPerforming a simple restart to the currently active partition.");
if(!wait_for_commit()){
cmd_send_messaging(argv[0],MESSAGING_WARNING,"Unable to commit configuration. ");
}
vTaskDelay(750/ portTICK_PERIOD_MS);
esp_restart();
simple_restart();
return 0;
}
void simple_restart()
{
log_send_messaging(MESSAGING_WARNING,"System reboot requested.");
if(!wait_for_commit()){
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
}
vTaskDelay(750/ portTICK_PERIOD_MS);
esp_restart();
}
esp_err_t guided_restart_ota(){
log_send_messaging(MESSAGING_WARNING,"System reboot to Application requested");
log_send_messaging(MESSAGING_WARNING,"Booting to Squeezelite");
guided_boot(ESP_PARTITION_SUBTYPE_APP_OTA_0);
return ESP_FAIL; // return fail. This should never return... we're rebooting!
}
esp_err_t guided_factory(){
log_send_messaging(MESSAGING_WARNING,"System reboot to recovery requested");
log_send_messaging(MESSAGING_WARNING,"Booting to recovery");
guided_boot(ESP_PARTITION_SUBTYPE_APP_FACTORY);
return ESP_FAIL; // return fail. This should never return... we're rebooting!
}
static int restart_factory(int argc, char **argv)
{
cmd_send_messaging(argv[0],MESSAGING_WARNING, "Executing guided boot into recovery");
cmd_send_messaging(argv[0],MESSAGING_WARNING, "Booting to Recovery");
guided_boot(ESP_PARTITION_SUBTYPE_APP_FACTORY);
return 0; // return fail. This should never return... we're rebooting!
}
static int restart_ota(int argc, char **argv)
{
cmd_send_messaging(argv[0],MESSAGING_WARNING, "Executing guided boot into ota app 0");
cmd_send_messaging(argv[0],MESSAGING_WARNING, "Booting to Squeezelite");
guided_boot(ESP_PARTITION_SUBTYPE_APP_OTA_0);
return 0; // return fail. This should never return... we're rebooting!
}
@@ -249,7 +253,9 @@ static void register_restart()
.hint = NULL,
.func = &restart,
};
#if CONFIG_WITH_CONFIG_UI
cmd_to_json(&cmd);
#endif
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
static void register_restart_ota()
@@ -260,7 +266,9 @@ static void register_restart_ota()
.hint = NULL,
.func = &restart_ota,
};
#if CONFIG_WITH_CONFIG_UI
cmd_to_json(&cmd);
#endif
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
@@ -272,7 +280,9 @@ static void register_factory_boot()
.hint = NULL,
.func = &restart_factory,
};
#if CONFIG_WITH_CONFIG_UI
cmd_to_json(&cmd);
#endif
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
/** 'free' command prints available heap memory */
@@ -288,11 +298,14 @@ static void register_free()
{
const esp_console_cmd_t cmd = {
.command = "free",
.help = "Get the current size of free heap memory",
.help = "Get free heap memory",
.hint = NULL,
.func = &free_mem,
};
#if CONFIG_WITH_CONFIG_UI
cmd_to_json(&cmd);
#endif
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
static int dump_heap(int argc, char **argv)
@@ -304,16 +317,16 @@ static int dump_heap(int argc, char **argv)
/* 'heap' command prints minumum heap size */
static int heap_size(int argc, char **argv)
{
ESP_LOGI(TAG,"Heap internal:%zu (min:%zu) (largest block:%zu)\nexternal:%zu (min:%zu) (largest block:%zd)\ndma :%zu (min:%zu) (largest block:%zd)",
heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM),
heap_caps_get_free_size(MALLOC_CAP_DMA),
heap_caps_get_minimum_free_size(MALLOC_CAP_DMA),
heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
// ESP_LOGI(TAG,"Heap internal:%zu (min:%zu) (largest block:%zu)\nexternal:%zu (min:%zu) (largest block:%zd)\ndma :%zu (min:%zu) (largest block:%zd)",
// heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
// heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
// heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
// heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
// heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
// heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM),
// heap_caps_get_free_size(MALLOC_CAP_DMA),
// heap_caps_get_minimum_free_size(MALLOC_CAP_DMA),
// heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
cmd_send_messaging(argv[0],MESSAGING_INFO,"Heap internal:%zu (min:%zu) (largest block:%zu)\nexternal:%zu (min:%zu) (largest block:%zd)\ndma :%zu (min:%zu) (largest block:%zd)",
heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
@@ -458,9 +471,8 @@ static int setdevicename(int argc, char **argv)
char *buf = NULL;
size_t buf_size = 0;
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.");
return 1;
}
nerrors+=setnamevar("a2dp_dev_name", f, name);
@@ -489,11 +501,13 @@ static void register_heap()
{
const esp_console_cmd_t heap_cmd = {
.command = "heap",
.help = "Get minimum size of free heap memory found during execution",
.help = "Get minimum size of free heap memory",
.hint = NULL,
.func = &heap_size,
};
#if CONFIG_WITH_CONFIG_UI
cmd_to_json(&heap_cmd);
#endif
ESP_ERROR_CHECK( esp_console_cmd_register(&heap_cmd) );
}
@@ -522,6 +536,7 @@ static void register_setdevicename()
.func = &setdevicename,
.argtable = &name_args
};
cmd_to_json_with_cb(&set_name,&setdevicename_cb);
ESP_ERROR_CHECK(esp_console_cmd_register(&set_name));
}
@@ -562,7 +577,7 @@ static void register_tasks()
/** 'deep_sleep' command puts the chip into deep sleep mode */
#if CONFIG_WITH_CONFIG_UI
static struct {
struct arg_int *wakeup_time;
struct arg_int *wakeup_gpio_num;
@@ -619,15 +634,15 @@ static void register_deep_sleep()
const esp_console_cmd_t cmd = {
.command = "deep_sleep",
.help = "Enter deep sleep mode. "
"Two wakeup modes are supported: timer and GPIO. "
"If no wakeup option is specified, will sleep indefinitely.",
.help = "Enter deep sleep mode. ",
.hint = NULL,
.func = &deep_sleep,
.argtable = &deep_sleep_args
};
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
#endif
static int enable_disable(FILE * f,char * nvs_name, struct arg_lit *arg){
esp_err_t err = config_set_value(NVS_TYPE_STR, nvs_name, arg->count>0?"Y":"N");
const char * name = arg->hdr.longopts?arg->hdr.longopts:arg->hdr.glossary;
@@ -650,9 +665,8 @@ static int do_set_services(int argc, char **argv)
}
char *buf = NULL;
size_t buf_size = 0;
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.");
return 1;
}
@@ -675,7 +689,7 @@ static int do_set_services(int argc, char **argv)
if(err!=ESP_OK){
nerrors++;
fprintf(f,"Error setting telnet service to %s. %s\n",set_services_args.telnet->sval[0], esp_err_to_name(err));
fprintf(f,"Error setting telnet to %s. %s\n",set_services_args.telnet->sval[0], esp_err_to_name(err));
}
else {
fprintf(f,"Telnet service changed to %s\n",set_services_args.telnet->sval[0]);
@@ -707,7 +721,6 @@ cJSON * set_services_cb(){
#if WITH_TASKS_INFO
console_set_bool_parameter(values,"stats",set_services_args.stats);
#endif
if ((p = config_alloc_get(NVS_TYPE_STR, "telnet_enable")) != NULL) {
if(strcasestr("YX",p)!=NULL){
cJSON_AddStringToObject(values,set_services_args.telnet->hdr.longopts,"Telnet Only");
@@ -718,7 +731,6 @@ cJSON * set_services_cb(){
else {
cJSON_AddStringToObject(values,set_services_args.telnet->hdr.longopts,"Disabled");
}
FREE_AND_NULL(p);
}
@@ -746,6 +758,8 @@ static void register_set_services(){
cmd_to_json_with_cb(&cmd,&set_services_cb);
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
#if CONFIG_WITH_CONFIG_UI
static struct {
struct arg_int *wakeup_time;
struct arg_int *wakeup_gpio_num;
@@ -791,7 +805,7 @@ static int light_sleep(int argc, char **argv)
ESP_ERROR_CHECK( esp_sleep_enable_uart_wakeup(CONFIG_ESP_CONSOLE_UART_NUM) );
}
fflush(stdout);
uart_tx_wait_idle(CONFIG_ESP_CONSOLE_UART_NUM);
esp_rom_uart_tx_wait_idle(CONFIG_ESP_CONSOLE_UART_NUM);
esp_light_sleep_start();
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
const char *cause_str;
@@ -837,4 +851,4 @@ static void register_light_sleep()
};
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
#endif

View File

@@ -17,6 +17,7 @@ void register_system();
esp_err_t guided_factory();
esp_err_t guided_restart_ota();
void simple_restart();
FILE * system_open_memstream(const char * cmdname,char **buf,size_t *buf_size);
#ifdef __cplusplus
}

View File

@@ -204,8 +204,10 @@ void register_wifi_join()
void register_wifi()
{
#ifdef WIFI_CMDLINE
register_wifi_join();
if(bypass_network_manager){
initialise_wifi();
}
#endif
}

View File

@@ -313,8 +313,8 @@ void initialize_console() {
vfs.open = stdin_dummy;
vfs.read = stdin_read;
ESP_ERROR_CHECK(esp_vfs_register("/dev/console", &vfs, NULL));
freopen("/dev/console", "r", stdin);
ESP_ERROR_CHECK(esp_vfs_register("/dev/redirect", &vfs, NULL));
freopen("/dev/redirect", "r", stdin);
/* Disable buffering on stdin */
setvbuf(stdin, NULL, _IONBF, 0);

Some files were not shown because too many files have changed in this diff Show More