Compare commits

...

661 Commits

Author SHA1 Message Date
github-actions
aa8b5ec2d4 Update prebuilt objects [skip actions] 2022-12-02 18:34:52 +00:00
Sebastien L
3c94f63876 Fix offline icons 2022-12-02 12:00:57 -05:00
Sebastien L
383a2fd7ce Fix include file name 2022-11-29 11:54:43 -05:00
Sebastien L
f74ecf5e60 improv-wifi initial commit 2022-11-29 11:53:20 -05:00
Sebastien L
1390e258db improv-wifi initial commit 2022-11-29 11:04:27 -05:00
philippe44
b20c8306fa fix a few spotify key bugs and remove BTDM 2022-11-28 21:09:27 -08:00
Sebastien L
877f16181b Activate new workflow 2022-11-28 13:57:31 -05:00
Sebastien
2cba6972ff update build_tools [skip actions] 2022-11-28 13:45:10 -05:00
Sebastien
fbd9516667 one more update to web push - [skip actions] 2022-11-28 10:46:15 -05:00
Sebastien
646a220fd0 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2022-11-28 10:45:04 -05:00
Sebastien
962dbf89c8 Add more auth methods for build - [skip actions] 2022-11-28 10:44:59 -05:00
github-actions
3a658f3ba2 Update prebuilt objects [skip actions] 2022-11-28 15:22:22 +00:00
github-actions
4f5a7ade9d Update prebuilt objects [skip actions] 2022-11-28 14:57:44 +00:00
Sebastien L
f15f846030 One more fix to the build system - skip actions 2022-11-28 09:52:24 -05:00
Sebastien L
14f4caf584 Allow compiling out cspot 2022-11-28 09:22:46 -05:00
Sebastien L
46eb732d82 Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-11-28 09:13:02 -05:00
Sebastien L
d092ae093c Write version.txt file before build 2022-11-28 09:12:58 -05:00
philippe44
4a51efd556 Merge pull request #206 from wizmo2/display-spi-mode-v4.3
add shaffenmeisters PR and add web config
2022-11-26 19:06:06 -08:00
Wizmo2
c1375444b4 fixed backlite 2022-11-26 21:08:39 -05:00
Wizmo2
e5eabcaf86 add shaffenmeisters PR and add web config 2022-11-25 19:40:17 -05:00
github-actions
93465db1b8 Update prebuilt objects [skip actions] 2022-11-25 19:06:15 +00:00
wizmo2
dc66d9e9dc Bluetooth Source mods (#204)
* support legacy bt pairing

* extend bt source support
2022-11-25 13:55:01 -05:00
Sebastien L
8462de1c16 Fix spotify configuration UI- release 2022-11-25 13:48:32 -05:00
Sébastien
37f48d4bc8 Align versionning in firmware - release 2022-11-24 10:07:13 -05:00
github-actions
ec019f98f3 Update prebuilt objects [skip actions] 2022-11-24 14:15:09 +00:00
Sebastien
5e5c7e0c80 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2022-11-24 09:07:45 -05:00
Sebastien
3144cf5f91 Change auth methods for pushing 2022-11-24 09:07:35 -05:00
github-actions
18cc0adfb4 Update prebuilt objects [skip actions] 2022-11-22 15:28:59 +00:00
github-actions
78ece491a9 Update prebuilt objects [skip actions] 2022-11-22 14:39:21 +00:00
Sébastien
9e66a822de Change call to web installer update 2022-11-22 09:35:34 -05:00
philippe44
0e12f7f887 bypass protobuf generation for now - release 2022-11-19 19:01:47 -08:00
philippe44
5df561890c mpr121 table init - release 2022-11-19 18:28:33 -08:00
philippe44
f6630fae4e release 2022-11-17 17:26:12 -08:00
philippe44
e2df4b1cc3 chmod on protoc 2022-11-17 17:20:20 -08:00
philippe44
3172576fc1 update CMake file 2022-11-17 16:36:25 -08:00
philippe44
e0dd7a596b release 2022-11-17 16:20:16 -08:00
philippe44
6e4dd65fd7 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2022-11-17 14:06:05 -08:00
philippe44
7e5f27af12 update cspot 2022-11-17 14:06:00 -08:00
github-actions
f805db3d27 Update prebuilt objects [skip actions] 2022-11-15 03:51:38 +00:00
philippe44
a81d0e0513 fix opus & vorbis last decode 2022-11-10 23:05:17 -08:00
Andy Boff
6c524cd094 Implement mpr121 touch sensor capability to GPIO Expanders (#192)
Co-authored-by: Andy Boff <gitdev@plek.me.uk>
2022-10-22 00:55:04 -04:00
Olecorp
e34cec1ad1 Update services.c (#189)
Reset pin config before use. Some pins (like GPIO14, JTAG TMS by default) wont work without this.
2022-10-19 22:48:20 -04:00
Sébastien
a2c16decee Adjust artifacts target folder [skip actions] 2022-10-11 11:35:15 -04:00
github-actions
d06893c43d Update prebuilt objects [skip actions] 2022-10-07 19:51:07 +00:00
Sebastien L
aecaed4a5f WIP Workflow [skip actions] 2022-10-07 15:47:49 -04:00
github-actions
113a75a61d Update prebuilt objects [skip actions] 2022-10-07 19:42:11 +00:00
Sébastien
f721d140e7 WIP Workflow [skip actions] 2022-10-07 15:39:27 -04:00
Sébastien
46848b3afe WIP Workflow [skip actions] 2022-10-07 15:37:21 -04:00
Sébastien
3d63670499 WIP Workflow [skip actions] 2022-10-07 15:31:52 -04:00
Sébastien
83114861c5 WIP Workflow [skip actions] 2022-10-07 15:30:12 -04:00
Sebastien L
5e7ab93e4a WIP Workflow [skip actions] 2022-10-07 15:29:05 -04:00
Sebastien L
2c56f2d7ae WIP Workflow [skip actions] 2022-10-07 15:27:20 -04:00
Sebastien L
c62e719c11 WIP Workflow [skip actions] 2022-10-07 15:05:21 -04:00
Sebastien L
0226355ab7 Create web_deploy.yml [skip actions] 2022-10-07 14:57:30 -04:00
Sebastien L
42bee2a7b0 WIP Workflow [skip actions] 2022-10-07 14:54:50 -04:00
Sebastien L
130f54526f WIP Workflow [skip actions] 2022-10-07 14:41:43 -04:00
Sebastien L
f14f2debc3 Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-10-07 14:40:25 -04:00
github-actions
25772c9ccb WIP Workflow [skip actions] 2022-10-07 14:40:19 -04:00
github-actions
1ae1b03fc3 Update prebuilt objects [skip actions] 2022-10-07 18:31:58 +00:00
Sebastien L
08ff02dc3c Update prebuilt objects [skip actions] 2022-10-07 14:28:33 -04:00
Sebastien L
3f4df9a348 Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-10-06 13:05:12 -04:00
github-actions
bb70ea1eaa Update prebuilt objects [skip actions] 2022-10-06 13:05:03 -04:00
github-actions
c0e8e36221 Update prebuilt objects [skip actions] 2022-10-06 16:54:31 +00:00
Sebastien L
4c94042c1d Update BuildTest.yml [skip actions] 2022-10-06 12:51:33 -04:00
Sebastien L
66be31a76c Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-10-06 12:49:35 -04:00
Sebastien L
5a53011383 Update BuiltTest.yml [skip actions] 2022-10-06 12:49:27 -04:00
Sebastien L
9ca1b87919 Update BuiltTest.yml [skip actions] 2022-10-06 12:44:19 -04:00
Sebastien L
2698577592 Update BuildTest.yml [skip actions] 2022-10-06 11:44:18 -04:00
Sebastien L
f70f973ca9 Allow dispatch options to control build [skip actions] 2022-10-06 11:31:43 -04:00
Sebastien L
83592b7079 WIP workflow [skip actions] 2022-10-06 11:16:05 -04:00
github-actions
2967723fdf Update prebuilt objects [skip actions] 2022-10-06 14:37:41 +00:00
Sébastien
d6e75dc71e Update BuildTest.yml [skip actions] 2022-10-06 10:32:35 -04:00
github-actions
fc50d55a27 Update prebuilt objects [skip actions] 2022-10-06 14:17:54 +00:00
Sebastien L
4fff2f0dc1 Squash some commits [skip actions] 2022-10-06 09:54:07 -04:00
Sebastien L
027d81af21 Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-10-06 09:53:03 -04:00
github-actions
96a2581534 Update prebuilt objects [skip actions] 2022-10-06 09:52:20 -04:00
Sébastien
461486ce21 Update BuildTest.yml
Update BuildTest.yml
Update build tools
Update BuildTest.yml
Update BuildTest.yml
Update BuildTest.yml
Update BuildTest.yml
Update BuildTest.yml
Update BuildTest.yml [skip actions]
Update BuildTest.yml [skip actions]
Update prebuilt objects [skip actions]

Update prebuilt objects [skip actions]

Update prebuilt objects [skip actions]

Update Build test [skip actions]
2022-10-06 09:52:20 -04:00
github-actions
7f0bc6c2ba Update prebuilt objects [skip actions] 2022-10-06 03:21:20 +00:00
Sébastien
551db4a7d1 Update Build test [skip actions] 2022-10-05 23:16:32 -04:00
github-actions
eee3123eba Update prebuilt objects [skip actions] 2022-10-06 03:13:55 +00:00
github-actions
d1a5e0aaa6 Update prebuilt objects [skip actions] 2022-10-06 02:46:59 +00:00
github-actions
e6d9c5f5a3 Update prebuilt objects [skip actions] 2022-10-05 19:46:26 +00:00
Sébastien
8164ccacaa Update BuildTest.yml [skip actions] 2022-10-05 15:41:09 -04:00
Sébastien
b2ceb8c0e3 Update BuildTest.yml [skip actions] 2022-10-05 15:35:01 -04:00
Sébastien
2e66a6a6cb Update BuildTest.yml 2022-10-05 15:26:00 -04:00
Sébastien
ac1817f4d3 Update BuildTest.yml 2022-10-05 15:22:40 -04:00
Sébastien
1d4bd8274b Update BuildTest.yml 2022-10-05 15:15:05 -04:00
Sébastien
58af548252 Update BuildTest.yml 2022-10-05 15:10:46 -04:00
Sébastien
c400f4467e Update BuildTest.yml 2022-10-05 14:55:09 -04:00
Sebastien L
24c69a70d2 Update build tools 2022-10-05 14:41:41 -04:00
Sébastien
0ce4e9e240 Update BuildTest.yml 2022-10-05 13:16:43 -04:00
Sébastien
b50caddb39 Update BuildTest.yml 2022-10-05 12:34:39 -04:00
Sébastien
639ed446f5 Update BuildTest.yml 2022-10-05 12:28:55 -04:00
Sebastien L
06b65f6f6c Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-10-05 12:25:59 -04:00
Sebastien L
f70cd0eeb1 Push a test build script for github actions 2022-10-05 12:24:31 -04:00
github-actions
95018f39e8 Update prebuilt objects [skip actions] 2022-10-05 16:10:26 +00:00
github-actions
c8e999590d Update prebuilt objects [skip actions] 2022-10-05 15:24:44 +00:00
github-actions
b640ff1c88 Update prebuilt objects [skip actions] 2022-10-05 15:08:49 +00:00
github-actions
bfe1be3c99 Update prebuilt objects [skip actions] 2022-10-05 14:57:02 +00:00
github-actions
9416c6916a Update prebuilt objects 2022-10-05 14:04:23 +00:00
philippe44
f23116d2c6 GH desktop thought it was smart to CRLF all the pull 2022-09-26 19:58:50 -07:00
philippe44
ff1d276148 Update plugin (credits @mherger) 2022-09-25 11:32:11 -07:00
philippe44
c143ea56ff protobuf generation still fails, bypass it - release 2022-09-24 23:31:35 -07:00
philippe44
3fc7e68163 release 2022-09-24 23:18:55 -07:00
philippe44
2dea2ca08c Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2022-09-24 23:17:17 -07:00
philippe44
2584026e5c Merge pull request #180 from michaelherger/eq-preview
Enable live EQ preview in Material skin (and Classic/EN)
2022-09-24 23:13:29 -07:00
philippe44
f2b0c40848 fix potential cspot on socket timeout & optimize HTTP server - release 2022-09-24 23:11:20 -07:00
Michael Herger
1876b26100 Enable live EQ preview in Material skin (and Classic/EN)
* add JavaScript to submit the changes before storing them
* add input validation
* fix settings page loading in case of networking issues (which I suffered from when I tried to implement the above :-))
2022-09-24 18:42:02 +02:00
philippe44
f84e856e68 update cmake files 2022-09-22 16:42:22 -07:00
Sebastien
34fa7203be Handle known connection events 2022-09-20 09:24:39 -04:00
philippe44
751d683b3e muse called abort() w/o necessity (can silently ignore RMT errors) - release 2022-09-18 00:13:57 -07:00
philippe44
b000bbd383 limit CSPOT buffering of decoded audio for less plops & lag - release 2022-09-17 10:00:56 -07:00
philippe44
6f504839bb add TJPGD directory - release 2022-09-16 23:04:12 -07:00
philippe44
b48e9fe1fa just create empty cspot_config at first boot - release 2022-09-16 22:52:24 -07:00
philippe44
eb6b99d461 no workaround to use ROM version of TJPGD (loosing 5kB of flash) 2022-09-15 14:05:16 -07:00
philippe44
405c7742ef use TJPGD in flash 2022-09-15 13:47:32 -07:00
philippe44
eab321d233 release 2022-09-13 23:53:42 -07:00
philippe44
50475e9305 release 2022-09-13 23:52:49 -07:00
philippe44
a1e15abba7 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2022-09-13 23:51:29 -07:00
philippe44
7e733f4fa9 Was using wrong protobuf - release 2022-09-13 23:51:26 -07:00
philippe44
3e35937272 syntax error in yaml - release 2022-09-12 20:12:52 -07:00
philippe44
2d9dd14e06 exclude 32 bits build for Muse 2022-09-12 19:55:34 -07:00
philippe44
698cc70aa3 release 2022-09-12 19:46:25 -07:00
philippe44
6e67606a07 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2022-09-12 19:45:53 -07:00
philippe44
f6f2f612a1 Allow AudioChunkManager to use PSRAM stack - release 2022-09-12 19:45:46 -07:00
philippe44
60bf802f3f change build number in ... 4.3 - release 2022-09-12 15:05:57 -07:00
philippe44
f7c9f08071 release 2022-09-12 14:46:06 -07:00
philippe44
9be4593d81 Tweak for JPEG - release 2022-09-12 14:16:02 -07:00
philippe44
922889fee2 try to fix misc cspot issues + silence network manager log 2022-09-12 11:54:49 -07:00
philippe44
a8e28f9ff0 espressif's patch for SPI concurrent issue + fix override mechanismi 2022-09-08 13:34:06 -07:00
philippe44
1fcda53941 fix 32 bist in AC101 and ES8388 2022-08-29 17:40:00 -07:00
philippe44
0222dbd6de update CSpot + clear audio buffer when changing track 2022-08-23 17:06:59 -07:00
Sébastien
36f18fc069 Bump to trigger build 2022-06-05 08:30:48 -04:00
Sebastien L
12f9b1eae8 Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-03-05 10:31:38 -05:00
Sebastien L
16225ed8c9 Fix credits and status page not showing 2022-03-05 10:31:27 -05:00
philippe44
57b77766ff update CSpot 2022-03-04 20:06:19 -08:00
philippe44
7b1d1ad45e fix color swap 2022-02-28 13:56:42 -08:00
Sebastien L
b191ea6ec1 Update Docker to use idf 4.3.2 2022-02-28 11:58:18 -05:00
Sebastien L
70e194e763 Added display invert Web ui option 2022-02-28 11:28:09 -05:00
Sebastien L
828c28720f Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-02-28 11:16:37 -05:00
philippe44
dd519b9229 add color swap BGR/RGB and generalize invert option 2022-02-27 14:34:11 -08:00
philippe44
54d7e222d0 fix CLI structure 2022-02-26 17:53:01 -08:00
philippe44
23a5f7fbe4 tweak muse color + add "invert" option 2022-02-26 17:37:08 -08:00
Sebastien L
3eab93b595 Network manager WIP 2022-02-25 14:23:56 -05:00
Sebastien L
bda4c18abe Merge branch 'tmp' into master-v4.3 2022-02-25 14:18:49 -05:00
Philippe G
72b66054b1 debounce is 50ms default, .defaults has always been overwritten by build params.... 2022-02-03 23:14:10 -08:00
Philippe G
9da735da95 update AMP_GPIO 2022-02-02 11:35:04 -08:00
Sebastien L
168eb061af Try to speed up the build 2022-02-01 07:59:23 -05:00
Sebastien L
c9adf6320b Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-02-01 06:48:26 -05:00
Sebastien L
7fd87eeba5 Fixing bugs 2022-01-31 21:52:57 -05:00
philippe44
042169e7eb Update CrossBuild.yml 2022-01-29 13:47:08 -08:00
Sebastien L
68e7512073 Change script permissions to run on Github Actions 2022-01-29 12:03:04 -05:00
Sebastien L
a5da6bbcf1 Many many many changes to update the UI. It hurts... 2022-01-28 22:11:09 -05:00
philippe44
44829ae59f Update CrossBuild.yml 2022-01-28 18:33:41 -08:00
Philippe G
882ed4dce9 update battery scale for muse 2022-01-28 18:21:52 -08:00
Sebastien L
28dba930c7 .3: Auto stash before merge of "master-v4.3" and "origin/master-v4.3" 2022-01-27 21:03:32 -05:00
Sebastien L
5ee2bc318e Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-01-27 20:57:14 -05:00
Sebastien L
7fcb201b06 UI Migration to Webpack+Bootstrap etc latest version 2022-01-27 20:57:03 -05:00
Philippe G
839f31d485 fix target config priority between NVS and dedicated builds 2022-01-26 15:09:25 -08:00
Philippe G
6d1cf28fd6 tweaks 2022-01-26 12:41:39 -08:00
Philippe G
8e6d409311 use muse built-in keyboard definition 2022-01-26 02:30:31 -08:00
Philippe G
eb3a8f76da really add battery led at boot 2022-01-24 16:10:43 -08:00
Philippe G
cf4ed64eb0 muse long_press & battery read at boot 2022-01-24 16:08:00 -08:00
Philippe G
3d8055a948 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2022-01-23 00:01:59 -08:00
Philippe G
0052ff9625 add battery led 2022-01-23 00:01:55 -08:00
Sebastien L
977935015e Update webpack and web ui to recent versions, bug fixes 2022-01-21 17:02:34 -05:00
Sebastien L
bd63723189 Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-01-21 17:01:27 -05:00
Philippe G
9185acf513 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2022-01-21 11:24:51 -08:00
Philippe G
6f28153a3f port default audio_controls in 4.3 2022-01-21 11:24:46 -08:00
Sebastien L
b79e1f9391 Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-01-21 08:34:56 -05:00
philippe44
9843d404bd Update CrossBuild.yml 2022-01-20 17:42:59 -08:00
Philippe G
47774c98f0 make targets "loadable" 2022-01-20 17:34:21 -08:00
Sebastien L
285225401c Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-01-20 19:35:19 -05:00
philippe44
1b74c82c72 Update esp-idf-v4.3-build.yml 2022-01-20 16:19:27 -08:00
Philippe G
f1d9e32f2c Muse volume 2022-01-20 16:05:14 -08:00
Philippe G
d16e7eed09 add Muse configscript 2022-01-20 13:12:29 -08:00
Sebastien L
89b4b5ca2d Merge remote-tracking branch 'origin/master-v4.3' into master-v4.3 2022-01-20 13:45:36 -05:00
Sebastien L
15c0e47ae3 Network WIP 2022-01-20 13:43:23 -05:00
Philippe G
d1dd27b7cb add Muse support 2022-01-19 13:40:03 -08:00
Philippe G
750e455b0d targets 2022-01-19 01:16:49 -08:00
Philippe G
533ee5e408 backend for Muse 2022-01-19 00:51:35 -08:00
Philippe G
de025602ac fix equalizer in NVS 2022-01-15 00:54:11 -08:00
Philippe G
396366b509 CSpot copyright 2022-01-13 18:48:29 -08:00
Philippe G
60584ae207 no more clicks on SPDIF & CSpot 2022-01-13 18:21:36 -08:00
Philippe G
3fb1c16f56 get ready for 4.4 2022-01-13 13:08:45 -08:00
Philippe G
4420f7da4d remove warnings (except CSpot ones) 2022-01-12 18:29:03 -08:00
Philippe G
1b83d0eb5f fix prio 2022-01-12 18:10:46 -08:00
Philippe G
b2741b5bef duration is now 1ms units & don't get cspot artwork when display disabled 2022-01-11 20:15:16 -08:00
Philippe G
04919f7b6e change priorities and force spdif_convert in IRAM 2022-01-11 14:44:06 -08:00
Philippe G
8fa3906b52 updated CSpot 2022-01-10 12:11:21 -08:00
Philippe G
72657d6951 simplify artwork and bypass server certificate verification 2022-01-10 11:24:19 -08:00
Philippe G
f09a95cc8b artwork for Spotify 2022-01-10 00:46:56 -08:00
Philippe G
3125a095fa airplay artwork and CSpot leak fix (temporary) 2022-01-09 19:40:18 -08:00
Philippe G
e9da432bfc more CSpot catchup 2022-01-07 19:16:55 -08:00
Philippe G
052600a45a add missing files 2022-01-06 18:56:27 -08:00
Philippe G
9af4cd5b23 catching up (trying to) wiht CSpot 2022-01-06 18:46:57 -08:00
Philippe G
491d0d260d set timeout for ethernet/dhcp to 30s 2022-01-05 19:38:35 -08:00
Philippe G
c1b39610fc fix LAN8720 2022-01-05 19:30:39 -08:00
Philippe G
f95ec33457 fix "join" command 2022-01-05 17:06:03 -08:00
Philippe G
fb24168d99 SOme displays needs to adjust CS_post for speed 2022-01-05 11:58:47 -08:00
Philippe G
28ac503ef7 tweaks 2022-01-04 21:55:35 -08:00
Philippe G
0127428a15 add SPI display's CS pre/post 2022-01-04 21:19:37 -08:00
Sebastien L
df8d31c679 don't try caching docker 2022-01-04 21:42:58 -05:00
Sebastien L
f2f578a719 that didn't work 2022-01-04 21:40:40 -05:00
Sebastien L
e6046fa343 one more attempt 2022-01-04 21:33:32 -05:00
Philippe G
7959850970 and more crap again... 2022-01-04 17:32:28 -08:00
Philippe G
54440e87b6 more crap 2022-01-04 17:22:32 -08:00
Philippe G
47ad526890 more nanopb 2022-01-04 17:10:54 -08:00
Philippe G
d81e95c94a protobug generation optional 2022-01-04 17:02:31 -08:00
Philippe G
bf24735422 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2022-01-04 15:09:30 -08:00
Philippe G
685ac92f6e more memory leaks fixes 2022-01-04 15:09:26 -08:00
Sebastien L
5cad1aeed6 more logs 2022-01-04 17:27:40 -05:00
Sebastien L
9e21e302b8 more attempts to figure out permissions 2022-01-04 17:16:30 -05:00
Sebastien L
3a99c65eb3 Add verbosity to protogen 2022-01-04 17:12:51 -05:00
Sebastien L
e5516db60f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2022-01-04 17:02:11 -05:00
Sebastien L
c8430bcfa2 more permissions! 2022-01-04 17:02:08 -05:00
Philippe G
97793ceea8 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2022-01-04 13:52:49 -08:00
Philippe G
e59188ec44 non-crashing CSpot + spi_master and override fixes 2022-01-04 13:52:45 -08:00
Sebastien L
bea28a0a90 Some more permissions to add 2022-01-04 16:32:53 -05:00
Sebastien L
c612410a3b update permissions on protoc-gen-nanopb 2022-01-04 16:22:21 -05:00
Sebastien L
cdf6e88362 Try to fix cache for build 2022-01-04 14:52:45 -05:00
Sebastien L
edb830f7b2 Fix permission for protoc generation 2022-01-04 14:36:57 -05:00
Philippe G
33c7ba820d chmod 2022-01-04 11:27:56 -08:00
Philippe G
39f5a81238 add nanopb (manual) 2022-01-04 11:01:14 -08:00
Philippe G
06b637c55b idf overriding method to bring back SPDIF and fix SPI + new CSPOT (which crashes) 2022-01-04 00:15:33 -08:00
Philippe G
cf1315e6a4 tweak KConfig to reset defaults
(don't forget to remove ".old" file
2022-01-02 17:51:07 -08:00
Philippe G
59cc5a5a70 cspot conditional 2022-01-02 15:17:52 -08:00
Philippe G
6dbefa5a76 had to replace the full sdkconfigs. Diff, even with .js is too painful 2022-01-02 14:07:35 -08:00
philippe44
a31a31a414 forced update 2022-01-02 11:53:28 -08:00
philippe44
e71672cf49 release 2022-01-02 11:47:47 -08:00
philippe44
6e1dbe5021 Update I2S-4MFlash-sdkconfig.defaults 2022-01-02 11:47:24 -08:00
Sebastien L
aa1cfdd6b2 minor fixes, remove wifi scan mode from config 2022-01-01 21:12:08 -05:00
Philippe G
7bf1ede250 dm9501 is not a RMII + CS delay option for displays 2022-01-01 17:56:51 -08:00
Philippe G
25249ce13e BT source improvment, GPIO expander intr fix, SPI display improvments 2021-12-31 17:49:43 -08:00
Sébastien
756d930912 Update README.md 2021-12-29 23:14:33 -05:00
Sebastien L
a93bd3c187 temp fix network manager log verbosity 2021-12-29 15:08:24 -05:00
Philippe G
f076a7260e make some squeezelite symbol weak to force removal from recovery during link 2021-12-28 20:04:24 -08:00
Sebastien L
9b20001c97 fix missing double quote in script 2021-12-28 13:19:56 -05:00
Sebastien L
6cdd2d302f Update webapp build steps 2021-12-28 13:13:11 -05:00
Sebastien L
b2ec1506d7 Update build system, add cspot service option 2021-12-28 12:43:29 -05:00
Philippe G
f3c405579f can't shift by more or equal to length... 2021-12-27 22:01:55 -08:00
Philippe G
2332e22d99 fonts 2021-12-27 19:34:06 -08:00
Philippe G
a40d7f5caa removing 1 font 2021-12-27 19:15:32 -08:00
Philippe G
d7d47d1127 unique UUID per CSPOT player 2021-12-27 14:49:12 -08:00
Philippe G
51c178ca46 duration unit change 2021-12-26 17:45:55 -08:00
Philippe G
e85733fc32 cspot duration 2021-12-26 15:48:25 -08:00
Philippe G
676acbdbc2 fixed cspot crash when switching with LMS 2021-12-25 23:05:53 -08:00
Philippe G
1422003271 player stop logic fix 2021-12-25 00:38:56 -08:00
Philippe G
7f894f1635 finhsing CSpot integration 2021-12-24 21:56:42 -08:00
Philippe G
e2bcb041e9 fixing memory leaks 2021-12-24 12:35:32 -08:00
Philippe G
5aca6f974c free mbedtls 2021-12-24 01:25:45 -08:00
Philippe G
523127bdea lock syntax error 2021-12-24 01:09:34 -08:00
Philippe G
662962ddb1 alignment with cspot 2021-12-23 11:55:51 -08:00
Philippe G
c68919d2d1 use pthread default priority for Mercury 2021-12-23 00:33:29 -08:00
Philippe G
b79878f590 Mercury should not reconnect when stopped 2021-12-23 00:23:09 -08:00
Philippe G
dc1e258d64 use network manager events for AirPlay and Spotify
- split network_manager.h in two parts
- centralize mDNS
2021-12-22 18:41:49 -08:00
Philippe G
d914e68a9b more fixes 2021-12-22 15:10:42 -08:00
Philippe G
9dfe90c26f alignment to 4.0 + misc cspot fixes 2021-12-22 12:15:05 -08:00
Philippe G
80270b772b only update VU/spectrum if we own the display 2021-12-21 01:05:28 -08:00
Philippe G
e16b7dd15b squeezelite must be on pthread core + BT sink start in internal stack 2021-12-20 23:35:47 -08:00
Philippe G
088825102e fix stack depth & memory issues when using telnet 2021-12-20 17:09:23 -08:00
Sebastien L
a354e6248a Move a few network manager parameters to nvs config 2021-12-19 09:57:57 -05:00
Philippe G
9a37d9dba4 Spotify over Ethernet! 2021-12-18 23:45:59 -08:00
Philippe G
898998efb0 big merge 2021-12-18 21:04:23 -08:00
Sébastien
955692f8ad Update esp-idf-v4.3-build.yml 2021-12-17 10:00:19 -05:00
Sebastien L
c0fc0c0276 Merge remote-tracking branch 'origin/ethernet_idf4.3' into ethernet_idf4.3 2021-12-17 09:58:46 -05:00
Sebastien L
49de5b8f23 Update actions config file one more time! 2021-12-17 09:58:38 -05:00
Sebastien L
a326699d76 Update actions config file one more time! 2021-12-17 09:56:45 -05:00
Sebastien L
efefb266f9 Fix state machine compile errors 2021-12-17 09:46:14 -05:00
Sebastien L
a9cc5c7b55 Merge branch 'ethernet_idf4.3' of https://github.com/sle118/squeezelite-esp32 into ethernet_idf4.3 2021-12-17 09:35:17 -05:00
Sebastien L
d0bcc72bce Update config scripts and compare tool 2021-12-17 09:34:56 -05:00
Sébastien
a569b2a82e Update esp-idf-v4.3-build.yml 2021-12-15 14:49:47 -05:00
Sebastien L
3a89597ff0 Merge branch 'ethernet_idf4.3' of https://github.com/sle118/squeezelite-esp32 into ethernet_idf4.3 2021-12-15 14:46:08 -05:00
Sebastien L
8892d66f9e Update build scripts with ethernet support 2021-12-15 14:45:50 -05:00
Sébastien
a7469e5258 update idf version - one more time 2021-12-15 13:06:46 -05:00
Sébastien
0e33e112ae update build script to esp-idf v4.3.1 2021-12-15 13:00:17 -05:00
Sebastien L
626303e563 One more time... 2021-12-10 16:31:11 -05:00
Sebastien L
f7dfb1d695 Fix Build Script 2021-12-10 16:26:44 -05:00
Sebastien L
0e89f988cd Update build scripts 2021-12-10 16:24:37 -05:00
Sebastien L
732f5cb793 Kicking off build 2021-12-10 16:17:38 -05:00
Sebastien L
e6b7ed28e7 Included sub-module for state machine 2021-12-10 16:06:57 -05:00
Sebastien L
2e061f5cec Added TTGO T-Watch to Presets 2021-12-10 15:45:53 -05:00
Sebastien L
f8903770c2 fix Preset options, cleanup 2021-12-10 15:36:59 -05:00
Sebastien L
63fbc2f645 Network manager implemented and relatively stable 2021-12-10 13:07:27 -05:00
Sebastien L
81756a7649 cpp state machine for ethernet 2021-11-16 10:11:38 -05:00
Sebastien L
699c1da42f Merge branch 'master-cmake' into Ethernet 2021-11-08 16:19:36 -05:00
Philippe G
8e5dbd2144 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-11-03 22:00:16 -07:00
Philippe G
974ff5fa68 strncpy is not safe + memory optimization 2021-11-03 22:00:07 -07:00
philippe44
4dce4b307b Update README.md 2021-10-31 20:27:30 -07:00
philippe44
4aa54537e5 Update README.md 2021-10-31 20:10:31 -07:00
philippe44
d27a430664 Update README.md 2021-10-31 20:05:23 -07:00
philippe44
0fa41e5ef7 Update README.md 2021-10-31 20:03:06 -07:00
philippe44
2146014f04 Update README.md 2021-10-31 18:26:43 -07:00
philippe44
7d409668c8 Update README.md 2021-10-31 18:24:23 -07:00
Philippe G
2a6f5c8a10 add "MISO" to SPI system config 2021-10-31 16:14:33 -07:00
Philippe G
0ebce7472b Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-10-31 16:13:55 -07:00
Philippe G
5f5466fcb0 add "MISO" to system's spi_config 2021-10-31 16:13:42 -07:00
philippe44
71911914cb Update README.md 2021-10-31 15:59:00 -07:00
Philippe G
2805629c4b add SPI ethernet 2021-10-31 14:47:28 -07:00
Philippe G
4f6dcc2cc7 add SPI ethernet 2021-10-31 14:45:35 -07:00
philippe44
f4615462c7 Update README.md 2021-10-30 18:34:56 -07:00
philippe44
6033c7fc14 Update README.md 2021-10-30 18:25:22 -07:00
Philippe G
97ae01b89f ethernet sample 2021-10-30 18:00:56 -07:00
Philippe G
a98b1d00b0 Ethernet + AirPlay fixes 2021-10-30 17:51:22 -07:00
philippe44
1e45348e4a Update README.md 2021-10-24 11:15:06 -07:00
Philippe G
96a05d8a6b fix opus error -136 (increase pseudo-stack) - release 2021-10-24 11:12:28 -07:00
philippe44
7a9f3e0781 Update README.md 2021-10-24 00:00:43 -07:00
Philippe G
3575245324 extra callback in preset caused NULL call - release 2021-10-20 21:24:36 -07:00
Philippe G
c36d663a7f release 2021-10-07 11:23:17 -07:00
Philippe G
13294ddd0c Continue searching for STA in AP mode when SSID has been set 2021-10-05 12:22:46 -07:00
philippe44
449ef32a4e Update README.md 2021-10-02 12:46:52 -07:00
Philippe G
50390dbc61 Fix i2s mode for 32 bits & SPDIF - release 2021-09-24 18:19:50 -07:00
Philippe G
fc5f3f5ac9 remove extra UNLOCK_S in opus - release 2021-09-18 09:30:03 -07:00
Philippe G
6f4ed0679e fix 32 bits sample size L/R swap - release
issue is in esp-idf
2021-09-11 21:40:10 -07:00
Philippe G
511df0b4b8 release 2021-08-19 19:13:35 -07:00
Philippe G
6682ee947e Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-08-19 19:12:09 -07:00
Philippe G
813e7ee712 use polling for gpio 36 & 39 - release 2021-08-19 19:11:39 -07:00
philippe44
6c9537e259 Update README.md 2021-08-19 19:10:39 -07:00
Philippe G
ac9ad2eee2 send ANIC when grfe's transitiion is not 'c' - release 2021-08-14 11:39:24 -07:00
Philippe G
f38840bbe0 release 2021-08-09 18:02:57 -07:00
Philippe G
fd56f649ab Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-08-09 18:01:53 -07:00
Philippe G
c4bd320afe close memory stream before returning on parsing error - release 2021-08-09 18:01:48 -07:00
philippe44
5e8978d1af Update README.md 2021-08-06 09:34:52 -07:00
philippe44
6e082a5654 Update README.md 2021-08-04 13:28:15 -07:00
philippe44
5af3250aea Update README.md 2021-08-04 13:27:21 -07:00
Philippe G
527187b2f8 release 2021-08-03 22:04:29 -07:00
Philippe G
102d2f6af5 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-08-03 22:02:55 -07:00
philippe44
4edd429b0a Update README.md 2021-08-03 21:59:31 -07:00
Philippe G
35099d1131 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-08-03 21:51:36 -07:00
Philippe G
ce9c3952e8 clean A1S, add ES8388 and generic codecs, add MCLK, clean Kconfig - release 2021-08-03 21:51:33 -07:00
philippe44
4ee6352185 Update README.md 2021-08-01 22:39:22 -07:00
philippe44
3a720a1e7d Update README.md 2021-07-31 00:32:03 -07:00
Philippe G
987fa5d18c plugin clarification 2021-07-29 07:50:59 -07:00
Philippe G
23c936ec93 release 2021-07-24 22:31:58 -07:00
Philippe G
d4f10a761e Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-07-24 22:29:08 -07:00
Philippe G
d68d163538 fix bits_per_sample for 32 bit + resilient cli_socket handling - release 2021-07-24 22:29:04 -07:00
philippe44
94580c6771 Update README.md 2021-07-21 20:04:33 -07:00
philippe44
2717277c6e Update README.md 2021-07-21 20:03:59 -07:00
philippe44
b6d537a207 Stop building A1S
As there are multiple version os ESP32-A1S, it should be set by dac_config now
2021-07-21 19:54:12 -07:00
Sebastien
cf047c8098 Fix WiFi signal strength icons - release 2021-07-09 11:18:01 -04:00
Philippe G
21f3768ada telnet / escape - release 2021-07-04 11:53:26 -07:00
Philippe G
9cb18fa980 look for eol in telnet - release 2021-07-03 21:32:16 -07:00
Philippe G
d4f6289500 wait for BT disable to avoid crash - release 2021-07-01 13:27:55 -07:00
Philippe G
21c3ce1fba damn release 2021-07-01 01:23:42 -07:00
Philippe G
48e8525ba9 make equalizer a string + make sure output-i2s is exited
- Can't really use BLOB (creates issue with HTTP visualizer)
- Player was stuck after WiFi loss b/c with some race conditions, BT deinit crashes and creates the reboot wanted after 5*5 failures. But when BT does not crash, reboot was not happening and player was stuck with slimproto not exited and player not rebooted
2021-07-01 01:20:59 -07:00
Philippe G
0db9631700 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-06-29 12:51:39 -07:00
Philippe G
192cb975e2 store equalizer - release 2021-06-29 12:51:36 -07:00
philippe44
d1f6085199 Update README.md 2021-06-24 15:20:54 -07:00
Philippe G
afaa5323d7 fix README 2021-06-22 21:23:57 -07:00
philippe44
f92447e9b9 Merge pull request #110 from wizmo2/controls_fix
fix audio control in bt and airplay
2021-06-22 21:21:40 -07:00
wizmo2
22f8d1d88b Update bt_app_sink.c 2021-06-17 14:48:58 -04:00
wizmo2
8136b7fd9a Fix indent 2021-06-17 14:47:33 -04:00
Wizmo2
e588deb3af fix audio control in bt and airplay 2021-06-16 21:53:44 -04:00
Christian Herzog
38ec8ac6f8 fix A1S ali link
fixes https://github.com/sle118/squeezelite-esp32/issues/104
2021-06-07 09:11:03 +02:00
Philippe G
791167f794 amp gpio polarity parse error - release 2021-05-24 10:38:33 -07:00
Philippe G
c9b859ef8c use visualizer for BT and AirPlay, regardless of buffer fullness 2021-05-23 11:26:08 -07:00
Philippe G
db74419bd7 correct threshold 2021-05-18 17:25:22 -07:00
philippe44
265403d364 Merge pull request #102 from wizmo2/battery_atten
Add Attenuation parameter to battery service
2021-05-18 17:22:30 -07:00
wizmo2
d3dd8b9078 Update battery.c 2021-05-13 20:00:02 -04:00
Wizmo2
de41fb9583 update ReadMe 2021-05-13 19:56:00 -04:00
Wizmo2
2699216d25 Add atten to battery service 2021-05-13 19:53:55 -04:00
philippe44
c7b1c7cd83 Update README.md 2021-05-11 07:47:58 -07:00
philippe44
f51af21b90 Update README.md 2021-05-11 07:43:21 -07:00
philippe44
5ec63f400c reverse SBR logic 2021-05-11 07:18:34 -07:00
Philippe G
1753e11698 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-05-11 07:16:39 -07:00
Philippe G
58c6ca059b reverse SBR enablement logic 2021-05-11 07:16:36 -07:00
philippe44
5ef63dc3e4 enable AAC SBR - release 2021-05-10 21:33:57 -07:00
philippe44
468c847499 SBR still enabled in default build - release 2021-05-10 21:29:55 -07:00
Philippe G
8c0e766cd7 optimizations - release 2021-05-10 19:41:56 -07:00
Philippe G
0f792d71ee optimization for AAC-SBR 2021-05-09 23:17:51 -07:00
Philippe G
1fc7675c14 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-05-08 23:39:35 -07:00
Philippe G
7b439ae6ee For AAC, do not enable SBR by default 2021-05-08 23:39:30 -07:00
philippe44
c5d7fd521d Update README.md 2021-05-07 14:14:06 -07:00
philippe44
a856b26181 Update README.md 2021-05-07 14:13:29 -07:00
philippe44
a949ec2d24 Update README.md 2021-05-07 14:12:23 -07:00
philippe44
5ec5236991 Update README.md 2021-04-30 11:45:18 -07:00
Philippe G
d4cd400cd9 refactor display(er) to welcome led extension 2021-04-28 18:03:27 -07:00
Philippe G
64bb5f018b Windows CRLF! 2021-04-28 18:00:05 -07:00
Philippe G
593927aac3 plugin update now on cmake (except repo.xml) 2021-04-28 17:29:28 -07:00
philippe44
51761d0890 Merge pull request #96 from michaelherger/firmware-proxy
Don't filter by HTTP verb - older firmwares are using GET rather than HEAD
2021-04-26 22:34:47 -07:00
Michael Herger
5c56abfe75 Don't filter by HTTP verb - older firmwares are using GET rather than HEAD 2021-04-27 07:31:24 +02:00
philippe44
9ac7c5bbeb Merge pull request #95 from michaelherger/firmware-proxy
Fix backwards compatibility with "older" firmwares
2021-04-26 22:09:05 -07:00
Michael Herger
be28555a40 Fix backwards compatibility with "older" firmwares checking for the -99 magic number, rather than check.bin 2021-04-27 07:06:47 +02:00
Philippe G
1d32479bc4 less verbose 2021-04-25 22:29:41 -07:00
philippe44
c83ddc4adc Merge pull request #90 from michaelherger/firmware-proxy
Firmware proxy
2021-04-25 22:12:15 -07:00
Michael Herger
5a7cf9b8fe Merge commit '387276f2f33a0fb9dde01434387aac9cdc9a8472' into firmware-proxy
# Conflicts:
#	components/wifi-manager/webapp/webapp.cmake
#	components/wifi-manager/webapp/webpack.c
#	components/wifi-manager/webapp/webpack.h
#	components/wifi-manager/webapp/webpack/dist/index.html
#	components/wifi-manager/webapp/webpack/dist/index.html.br
#	components/wifi-manager/webapp/webpack/dist/index.html.gz
#	components/wifi-manager/webapp/webpack/dist/js/index.18c3b7.bundle.js
#	components/wifi-manager/webapp/webpack/dist/js/index.abeafc.bundle.js
#	components/wifi-manager/webapp/webpack/dist/js/index.cf3fe8.bundle.js
#	components/wifi-manager/webapp/webpack/dist/js/node-modules.18c3b7.bundle.js
#	components/wifi-manager/webapp/webpack/dist/js/node-modules.abeafc.bundle.js
#	components/wifi-manager/webapp/webpack/dist/js/node-modules.cf3fe8.bundle.js
#	components/wifi-manager/webapp/webpack/dist/js/node-modules.cf3fe8.bundle.js.br
#	components/wifi-manager/webapp/webpack/dist/js/node-modules.cf3fe8.bundle.js.gz
#	components/wifi-manager/webapp/webpack/dist/js/runtime.18c3b7.bundle.js
#	components/wifi-manager/webapp/webpack/dist/js/runtime.18c3b7.bundle.js.br
#	components/wifi-manager/webapp/webpack/dist/js/runtime.18c3b7.bundle.js.gz
#	components/wifi-manager/webapp/webpack/dist/js/runtime.abeafc.bundle.js
#	components/wifi-manager/webapp/webpack/dist/js/runtime.abeafc.bundle.js.br
#	components/wifi-manager/webapp/webpack/dist/js/runtime.abeafc.bundle.js.gz
#	components/wifi-manager/webapp/webpack/dist/js/runtime.cf3fe8.bundle.js
#	components/wifi-manager/webapp/webpack/dist/js/runtime.cf3fe8.bundle.js.br
#	components/wifi-manager/webapp/webpack/dist/js/runtime.cf3fe8.bundle.js.gz
2021-04-26 07:04:53 +02:00
Michael Herger
190326726c Add firmware upload handler to SqueezeESP32 plugin
* upload firmware image: `curl -vF 'data=@./someFirmwareImage.bin' http://localhost:9000/plugins/SqueezeESP32/firmware/upload` (or the JS equivalent, using multipart form data
* receive response: `{"url":"http://192.168.0.63:9000/plugins/SqueezeESP32/firmware/squeezelite-esp32-upload-b0w7mn.bin", "size":2463375}`
* install firmware from temporary URL returned
* uploads are removed after 15 minutes or upon LMS restart
2021-04-25 01:26:10 +02:00
Sébastien
387276f2f3 adjust artifact prefix 2021-04-21 14:42:54 -04:00
Sébastien
87b8c46263 drum roll. V1 release 2021-04-21 14:06:16 -04:00
Sebastien
d7aef11856 Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2021-04-21 14:01:27 -04:00
Sebastien
be842d235d update to version 1 2021-04-21 14:01:23 -04:00
Philippe G
d49ad66177 version 1 2021-04-21 10:55:46 -07:00
Sebastien
ca28669b0a Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2021-04-21 13:27:36 -04:00
Sebastien
45f480d948 NVS ui fine-tuning, site icon now works 2021-04-21 13:27:32 -04:00
Philippe G
852e312879 Correct AAC handling of different AOT 2021-04-20 19:38:01 -07:00
Philippe G
c79cf5b58f more AAC SBR combinations 2021-04-19 22:47:35 -07:00
Michael Herger
1a4a8ba559 Allow firmware installation from LMS' player settings page 2021-04-17 19:09:31 +02:00
Michael Herger
7ad39a02f5 Extend firmware download handler to serve locally built custom firmware, too.
Just save it as `squeezelite-esp32-custom.bin` in the firmware update folder (LMS Cache/updates) and paste http://yourlms:9000/plugins/SqueezeESP32/firmware/custom.bin in the firmware URL box.
2021-04-17 07:19:04 +02:00
Michael Herger
f96d06912f Fix LMS plugin availability check. As I removed the download by ID, this needs a tweak on the UI/JS side. 2021-04-17 06:32:09 +02:00
Michael Herger
36571d3dad Improve firmware download proxy
* initialize firmware pre-fetching when a player connects
* get firmware based on the player's version string as returned by `status.json`
* keep firmware file per platform/branch/resolution combination to support different squeezelite-ESP32 players in an installation
* remove handler to get firmware by numeric ID rather than filename
2021-04-17 06:27:25 +02:00
Michael Herger
b075bbaea3 Add link to the ESP32 WiFi Manager to the settings page in LMS. 2021-04-17 06:27:25 +02:00
Philippe G
ec7982dbfc Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-04-16 11:44:44 -07:00
Philippe G
afd76bdfbb do not fail when external dac has no I2C! 2021-04-16 11:43:02 -07:00
philippe44
89ecd19d32 Update README.md 2021-04-15 15:56:34 -07:00
Philippe G
8904ec1afd remove extra bt_sink NVS initialization 2021-04-14 23:56:25 -07:00
Sebastien
5120029643 Firmware update improvements - include webpack build files 2021-04-14 21:09:45 -04:00
Sebastien
4a529d6fbd Firmware update UI revamp with support for local proxy 2021-04-14 18:16:18 -04:00
Sebastien
afe697e4b1 Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2021-04-14 18:15:38 -04:00
Philippe G
879b1f9107 handle abs & local BT volume - release 2021-04-13 18:04:23 -07:00
Sebastien
5a7d4fd535 Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2021-04-12 12:12:00 -04:00
Sebastien
d61c650f39 UI change of the update mechanism 2021-04-12 12:11:54 -04:00
Philippe G
f096ee269e clean TAS57xx - release 2021-04-10 12:44:04 -07:00
Philippe G
0610e1a2bc include wm8978 - release 2021-04-10 11:57:05 -07:00
philippe44
68db286777 Merge pull request #89 from wizmo2/wm8978
Add support for WM8978 i2c dac
2021-04-10 11:50:59 -07:00
Wizmo2
5075878f05 Updates to driver for rebase 2021-04-10 08:59:45 -04:00
Wizmo2
d965187d2c Merge branch 'wm8978' of https://github.com/wizmo2/squeezelite-esp32 into wm8978 2021-04-10 08:44:49 -04:00
wizmo2
e25b098678 Update wm8978.c 2021-04-10 08:44:13 -04:00
Wizmo2
a3b23bffc2 Config changes for WM8978 support 2021-04-10 08:44:13 -04:00
Wizmo2
dbc7a6b14e Add support for WM8978 i2c dac 2021-04-10 08:44:13 -04:00
Sebastien
a105f7fd99 Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2021-04-09 13:45:59 -04:00
Philippe G
1b39a4f7c9 DAC refactoring 2021-04-08 21:37:24 -07:00
Philippe G
cac6306a04 activate SBR mode in AAC 2021-04-07 00:45:21 -07:00
Wizmo2
749d71a36f Merge branch 'wm8978' of https://github.com/wizmo2/squeezelite-esp32 into wm8978 2021-04-06 11:15:45 -04:00
wizmo2
4d40355d5c Update wm8978.c 2021-04-06 11:14:21 -04:00
Wizmo2
c311faa90f Config changes for WM8978 support 2021-04-05 21:39:16 -04:00
Wizmo2
0821551a2f Add support for WM8978 i2c dac 2021-04-05 14:34:38 -04:00
Philippe G
3a2bfe470f show absolute battery level 2021-04-04 16:06:31 -07:00
Philippe G
f6b55c5ac9 voltage 2021-04-04 15:45:22 -07:00
Michael Herger
f9e97036cf Firmware proxy (#88)
* Add support for a firmware download proxy. This should help in situations where the player's firmware can't handle https correctly.

Two possibilities:
* full path to image: http://yourlms:9000/plugins/SqueezeESP32/firmware/ESP32-A1S.32.634.master-cmake/squeezelite-esp32-master-cmake-ESP32-A1S-32-V0.634.bin
* use Github's asset ID: http://yourlms:9000/plugins/SqueezeESP32/firmware/34298863

The former is more prone to issues related to the path. A change in the schema could break the matching regex.
The latter is simpler to use if you know the ID. But the ID is not easily available to the user. And it requires one more lookup in the plugin to get from the ID to the download path.

* Add support for proxying firmware downloads through LMS

* add magic asset ID -99 to allow the front-end to check whether the plugin does support download proxying
* web manager is expecting `lms_port` and `lms_ip` in `status.json`. If that's available, check whether plugin does support firmware downloading. If that's the case, download firmwares through LMS
* plugin would cache firmware images. In case of multiple images the file would be served directly from LMS.

* Add firmware pre-caching

* keep track of the most recently requested firmware build type
* poll Github for releases every ~6h
* download new firmware file for the same player model used before

Factor out firmware handling code to its own module.

Co-authored-by: Michael Herger <michael@herger.net>
2021-04-04 13:02:12 -04:00
Sebastien
4444fed343 Merge with LMS OTA proxy 2021-04-03 21:16:00 -04:00
Michael Herger
bc0d104290 Add support for a firmware download proxy (#85)
* Add support for a firmware download proxy. This should help in situations where the player's firmware can't handle https correctly.

Two possibilities:
* full path to image: http://yourlms:9000/plugins/SqueezeESP32/firmware/ESP32-A1S.32.634.master-cmake/squeezelite-esp32-master-cmake-ESP32-A1S-32-V0.634.bin
* use Github's asset ID: http://yourlms:9000/plugins/SqueezeESP32/firmware/34298863

The former is more prone to issues related to the path. A change in the schema could break the matching regex.
The latter is simpler to use if you know the ID. But the ID is not easily available to the user. And it requires one more lookup in the plugin to get from the ID to the download path.

* Add support for proxying firmware downloads through LMS

* add magic asset ID -99 to allow the front-end to check whether the plugin does support download proxying
* web manager is expecting `lms_port` and `lms_ip` in `status.json`. If that's available, check whether plugin does support firmware downloading. If that's the case, download firmwares through LMS
* plugin would cache firmware images. In case of multiple images the file would be served directly from LMS.

Co-authored-by: Michael Herger <michael@herger.net>
2021-04-03 21:01:40 -04:00
Philippe G
bdec9d25c6 change file permission 2021-04-03 15:14:46 -07:00
Philippe G
b25367fc0c chmod +x 2021-04-03 15:05:57 -07:00
Philippe G
603296d921 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-04-03 15:01:04 -07:00
Philippe G
a5b7d24dca chmod 2021-04-03 15:00:03 -07:00
philippe44
2733e3ec23 Merge pull request #86 from Mum-Pf/MumPf
Update README.md - Thanks!
2021-04-03 14:56:26 -07:00
Mum-Pf
9c79888b72 Update README.md
typo
2021-04-03 21:13:34 +02:00
Sebastien
2b59c38b4b Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2021-04-03 11:11:10 -04:00
Mum-Pf
a13afd76c9 Update README.md
NVS parameter for ILI9341
2021-04-02 20:18:39 +02:00
Mum-Pf
b98a481858 Update README.md
Typo... IL9341 -> ILI9341
2021-04-02 17:32:31 +02:00
philippe44
9e27a0e21d Update README.md 2021-04-01 19:20:55 -07:00
philippe44
d08f7142ae Update README.md 2021-04-01 19:20:12 -07:00
philippe44
efa3f1f07d Update README.md 2021-04-01 19:19:22 -07:00
philippe44
263679dcac Update README.md 2021-04-01 19:17:52 -07:00
Philippe G
50d7d57f48 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-03-31 22:38:00 -07:00
Philippe G
99e4b107d6 cleanup these $%@! certificates 2021-03-31 22:37:44 -07:00
Philippe G
d293de4b64 cleanup these $%@! certificates 2021-03-31 22:35:41 -07:00
philippe44
554cf89ac2 keep these $%*^¨&@ certificates 2021-03-31 22:27:03 -07:00
Philippe G
df36b65916 release 2021-03-31 21:25:30 -07:00
philippe44
d9a6b37d19 Merge pull request #68 from hubertbanas/master-cmake
Add body padding-bottom - release
2021-03-31 19:32:34 -07:00
Philippe G
0629b017b1 LMS can set player's name (only LMS scope) - release 2021-03-31 19:24:34 -07:00
Philippe G
43aa62ac56 set DEPTH in root CMake for consistency 2021-03-28 14:59:07 -07:00
Philippe G
22c2044f17 Limit rate to 96kHz in 32 bits mode + CMakeLists correction
@sle118, le tme know if the CMakeLists works for you as well. I pushed this one as I was pushing other stuff anyway
2021-03-28 13:54:45 -07:00
Sebastien
7457632990 Auto stash before merge of "master-cmake" and "origin/master-cmake" 2021-03-25 10:15:37 -04:00
philippe44
361cc08e3c release 2021-03-22 22:40:21 -07:00
Philippe G
e742905fbd release 2021-03-22 22:28:57 -07:00
Philippe G
4b719deddf Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-03-22 22:16:30 -07:00
Philippe G
b57e79ca5f need 'y' to 'Y' in sdkconfig!!!! - release 2021-03-22 22:16:27 -07:00
philippe44
6ef4c78b3b Update CrossBuild.yml 2021-03-22 22:16:02 -07:00
philippe44
f0002293a0 Update CrossBuild.yml 2021-03-22 21:51:30 -07:00
philippe44
29997c40b2 Update CrossBuild.yml 2021-03-22 21:49:52 -07:00
philippe44
ac5d54e6c1 Update CrossBuild.yml 2021-03-22 21:47:26 -07:00
philippe44
b3eae8dad1 Update CrossBuild.yml 2021-03-22 21:15:29 -07:00
philippe44
6804e81249 Update CrossBuild.yml 2021-03-22 21:12:23 -07:00
Philippe G
8639566909 update certificates for repository 2021-03-22 18:33:57 -07:00
Philippe G
87bf6255f4 tweak priorities - release 2021-03-22 18:07:09 -07:00
Philippe G
6084af8fbf optimize for 24/86/SPDIF + tweak stacks - release 2021-03-22 11:12:31 -07:00
Philippe G
8ec124c47c Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-03-21 22:22:10 -07:00
Philippe G
b8bb881820 re-activate RESAMPLE16 - release 2021-03-21 22:22:06 -07:00
philippe44
ccb4842e13 Update README.md 2021-03-21 13:35:40 -07:00
philippe44
3a7addad2e Update README.md 2021-03-21 13:16:26 -07:00
philippe44
644f4eb1e6 Update README.md 2021-03-21 13:15:15 -07:00
philippe44
00bab8f76b Update README.md 2021-03-21 13:13:43 -07:00
philippe44
72c084d7c0 Update README.md 2021-03-21 12:27:28 -07:00
philippe44
12e7d2d8fb Update README.md 2021-03-21 12:26:28 -07:00
philippe44
f5bb058541 Clean README 2021-03-21 12:20:31 -07:00
philippe44
5871252869 Update README.md 2021-03-20 19:21:38 -07:00
Philippe G
d5bf498d3d support SPDIF @ 96kHz - release 2021-03-20 19:08:42 -07:00
Philippe G
eb647aeea3 flags - release 2021-03-17 21:13:22 -07:00
Philippe G
451f187856 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-03-17 20:05:00 -07:00
Philippe G
f79c7d4ace mono channels with inversion - release 2021-03-17 20:04:53 -07:00
Sébastien
01a44be0ca Update certificates - release 2021-03-16 18:03:38 -04:00
Sébastien
4dc6424fed Add github-releases to TLS certs 2021-03-16 17:26:34 -04:00
Philippe G
a989fe06c2 add version - release 2021-03-16 13:37:43 -07:00
philippe44
1b5a877b98 Update CrossBuild.yml 2021-03-16 13:36:33 -07:00
sle118
9c9f79b0b6 Made certificate script an executable for github 2021-03-16 14:33:19 -04:00
Sébastien
6fef6d679e Update CrossBuild.yml 2021-03-16 14:26:40 -04:00
Sébastien
aa54b9dff9 Update CrossBuild.yml 2021-03-16 14:19:05 -04:00
Sébastien
05a704e7ec Update CrossBuild.yml 2021-03-16 14:16:44 -04:00
Sébastien
09e6518870 Update CrossBuild.yml 2021-03-16 14:02:54 -04:00
Sébastien
4d70d0998c Update certificates during build 2021-03-16 13:42:07 -04:00
Sébastien
9d0d957ec3 Update Certificates during build 2021-03-16 13:37:04 -04:00
philippe44
1be4c89f3c release 2021-03-15 23:20:08 -07:00
philippe44
893e67dfa4 release 2021-03-15 23:10:11 -07:00
Philippe G
eced71b2be release 2021-03-14 17:07:39 -07:00
Philippe G
1aa24393e4 release 2021-03-14 17:03:26 -07:00
philippe44
06b0d9aa3e release 2021-03-14 16:49:34 -07:00
Philippe G
6d73ba2d96 release 2021-03-14 16:28:07 -07:00
Philippe G
43785f1a7d Revert "release"
This reverts commit f772b3d07b.
2021-03-14 16:27:41 -07:00
Philippe G
f80e923c2a Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-03-14 16:27:31 -07:00
Philippe G
f772b3d07b release 2021-03-14 16:27:05 -07:00
philippe44
a6ae5d795a Update CrossBuild.yml 2021-03-14 16:23:14 -07:00
philippe44
c08286874a Update CrossBuild.yml 2021-03-14 16:22:13 -07:00
Philippe G
7b10bee68a release 2021-03-14 15:37:11 -07:00
Philippe G
d42098d1c0 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-03-14 14:44:36 -07:00
Philippe G
9563f1ed44 add 32 bits edition - release 2021-03-14 14:42:25 -07:00
philippe44
1f6b24db33 Update CrossBuild.yml 2021-03-14 14:31:15 -07:00
philippe44
5f3a22bb63 Update CrossBuild.yml 2021-03-14 14:26:09 -07:00
philippe44
574c7706a0 Update CrossBuild.yml 2021-03-14 14:18:16 -07:00
Philippe G
46a43efd70 opus & vorbis 32 bits fix 2021-03-11 17:44:23 -08:00
Philippe G
e8bba8af24 AAC 32 bits mode correction 2021-03-11 12:59:34 -08:00
Philippe G
b9466bf7b2 BT non-absolute volume handling 2021-03-07 16:33:26 -08:00
Philippe G
b0d8401274 32 bits mode for AirPlay 2021-03-06 23:04:05 -08:00
Philippe G
1063cd5899 allow -Z even w/o RESAMPLE (32 bits mode) 2021-03-06 14:02:42 -08:00
Philippe G
8f4b1022e2 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-03-05 22:57:35 -08:00
Philippe G
6d45f6d1b6 Tweak 32 bits mode (enable 20 bits spdif) 2021-03-05 22:13:20 -08:00
philippe44
6beafaa747 More bit depth comments 2021-03-05 22:07:37 -08:00
philippe44
fe80ee3c18 Update README.md 2021-03-05 21:59:51 -08:00
philippe44
a5d6600dea Update README.md 2021-03-05 21:59:22 -08:00
philippe44
8132311c75 Update README.md 2021-03-05 21:58:51 -08:00
philippe44
9ecd7e9a79 Bits depth clarification 2021-03-05 09:16:50 -08:00
Philippe G
b3ff717d32 32 bits cleanup 2021-03-04 20:30:06 -08:00
Philippe G
15f1cebcdb ignore 2021-03-04 20:27:16 -08:00
Philippe G
6d9eaf4109 add balance option - release 2021-02-25 06:10:03 -08:00
Philippe G
3f0882ead6 combined channels - release 2021-02-20 17:34:25 -08:00
Philippe G
9717f8288e preset fix - release 2021-02-12 15:13:33 -08:00
Philippe G
d253bc34e5 ILI9341 driver - release
credits Mumpf and Harry1999
2021-02-11 00:28:13 -08:00
philippe44
0f56e43451 Merge pull request #78 from Mum-Pf/MumPf
New driver for ILI9341-OLED
2021-02-11 00:22:14 -08:00
Philippe G
ed00f029a6 add preset buttons (use macro) + change name of MONO channel pseudo-gain - release 2021-02-10 23:52:53 -08:00
philippe44
ee35e1dc99 Merge pull request #64 from MatthiasLienhard/master-cmake
added power and preset buttons
2021-02-10 23:05:01 -08:00
Mum-Pf
84881ecb45 New driver for ILI9341-OLED
New driver for ILI9341-OLED-colordisplay, with rotation, flip, colordepth, gammacorrection.
Tested with 2.8inch OLED-Display ILI9241
2021-02-09 14:19:55 +01:00
Philippe G
369a9cb9bc add mono channel option - release 2021-02-06 18:02:33 -08:00
philippe44
b9deead084 Update README.md 2021-01-31 15:13:39 -08:00
philippe44
723e7442af Update README.md 2021-01-31 15:07:09 -08:00
Christian Herzog
8189a59d59 typo 2021-01-25 08:24:09 +01:00
philippe44
cc209be4f9 Update README.md 2021-01-24 00:31:08 -08:00
philippe44
1d9e8e863c Update README.md 2021-01-24 00:29:50 -08:00
philippe44
c37fd57b2c Update README.md 2021-01-24 00:29:19 -08:00
philippe44
54420387ab Update README.md 2021-01-24 00:28:56 -08:00
philippe44
173b2d13da Update README.md 2021-01-24 00:18:16 -08:00
philippe44
d60506c63f Update README.md 2021-01-23 23:52:37 -08:00
philippe44
2d80c44181 Update README.md 2021-01-23 23:51:18 -08:00
Philippe G
eb5df86733 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-01-18 22:55:30 -08:00
Philippe G
096e1d636d SSD1322 enhancement - release
at the expense of power but needed for 5.5' displays
2021-01-18 22:49:27 -08:00
philippe44
434836782c Update README.md 2021-01-16 12:02:03 -08:00
Philippe G
3b9e50ada7 alac corrected - release 2021-01-11 19:24:52 -08:00
Philippe G
78a16e41dc simplify alac writebuf alloc 2021-01-10 21:57:22 -08:00
Philippe G
2bc4a8c807 more alac fixes 2021-01-10 16:14:38 -08:00
Hubert Banas
5066351b24 Add body padding-bottom 2021-01-10 09:41:03 -05:00
Philippe G
c521fba4a6 pcm remaining bytes guardrail 2021-01-10 02:23:23 -08:00
Philippe G
174942f509 better alac management 2021-01-10 02:13:46 -08:00
Philippe G
9bf7b250e0 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-01-09 23:51:58 -08:00
Philippe G
32c2a4d68a 24àx320 VU-display fixes + alac decoder code style 2021-01-09 23:51:52 -08:00
Sebastien
64f96c68dc Update esp-idf docker image to latest v4.0 release 2021-01-09 16:54:34 -05:00
Philippe G
9807bf5476 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-01-08 15:05:52 -08:00
Philippe G
5a630887c4 IP reassembly flags consistents with new CONFIG 2021-01-08 15:03:01 -08:00
Sebastien
2a8fb902da set LWIP reassembly flag - release 2021-01-08 15:09:56 -05:00
Sebastien
08fcf84d6c Attempt to resolve connection dropping randomly - release 2021-01-08 14:45:42 -05:00
Sébastien
d162996042 add full build output to artifacts 2021-01-01 12:46:01 -05:00
Philippe G
7bd8c842c8 really triggering a new build - release 2020-12-28 19:17:37 -08:00
MatthiasLienard
fd36f6fc71 restored sdkconfig 2020-12-27 00:02:40 +01:00
Philippe G
fd9158e7c1 no need for aac protection - release
issue was (my bad) in LMS
2020-12-23 16:23:19 -08:00
Philippe G
effd24ccd4 aac was more complicated - best is to ignore last 128 bytes - release 2020-12-23 02:16:29 -08:00
Philippe G
1c730905ec mp4/aac decoder might overflow and lock at the end of track - release 2020-12-23 00:55:21 -08:00
Sebastien
9a8991ebf2 Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2020-12-21 12:12:04 -05:00
Sebastien
014030513a Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2020-12-21 12:11:56 -05:00
Sebastien
bfb85271d4 Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2020-12-21 11:09:02 -05:00
Sebastien
7c13c130b8 Wifi UI update 2020-12-21 11:01:22 -05:00
Matthias Lienhard
347a795b5f added power and preset buttons 2020-12-10 01:45:13 +01:00
Philippe G
1a3c888f44 correct fixed volume spectrum scale - release 2020-12-06 22:52:37 -08:00
Philippe G
90ee59754d discover buffer potential overflow 2020-12-05 16:10:53 -08:00
Philippe G
c33c6187ca remove unused code + plugin update 2020-12-03 20:56:56 -08:00
Philippe G
99019cd22f mirror make branch changes on plugin (need to point there ...) 2020-12-02 14:06:49 -08:00
Philippe G
3dc2ed9629 fix hires display - release 2020-12-02 13:58:49 -08:00
Sébastien
99722a6f94 Update Dockerfile 2020-11-22 10:13:25 -05:00
Philippe G
75580feaa0 close socket when connect() fails
might be linked to "too many CLI connection" but I'm not sure
2020-11-21 18:09:41 -08:00
Sebastien
2d573da503 Fixing #59 and unnecessary jack insertion messages-release 2020-11-21 09:26:56 -05:00
Sébastien
2687023b6f change build - all branches except master 2020-11-20 11:50:43 -05:00
Sébastien
0993186e01 Create codeql-analysis.yml 2020-11-19 07:48:20 -05:00
Sébastien
ede495a145 Added workflow build badge to readme - release 2020-11-17 14:27:48 -05:00
Sebastien
c82ae71726 Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2020-11-17 14:00:57 -05:00
Sebastien
cd13577d93 Reduce system load when loading HTTP UI - release 2020-11-17 13:58:19 -05:00
Sébastien
1d24543359 Update Cross build with build-number@v3 - release 2020-11-17 13:42:34 -05:00
Sebastien
08f04187ae Reduce system load when loading HTTP UI - release 2020-11-17 11:41:03 -05:00
Sebastien
8fbe1159f5 Reworking BT output 2020-11-03 17:54:31 -05:00
philippe44
2c64f50093 Update README.md 2020-10-31 22:19:54 -07:00
philippe44
cb646284c6 Update README.md 2020-10-31 22:14:45 -07:00
Sébastien
72c4f810fb Release Philippe's BT fixes 2020-10-29 07:12:24 -04:00
Philippe G
6170f49673 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2020-10-28 22:38:49 -07:00
Philippe G
d486a99aa1 more BT fixes - release 2020-10-28 22:38:39 -07:00
Sébastien
f0bd81125b Update build badge-release 2020-10-28 21:12:41 -04:00
Philippe G
251882a400 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2020-10-28 17:46:55 -07:00
Philippe G
0503d03e64 fix BT source - release 2020-10-28 17:46:46 -07:00
Sebastien
fed98f470f Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2020-10-28 20:40:07 -04:00
Sebastien
95d4dbc905 update Docker image for the build 2020-10-28 20:39:11 -04:00
Sebastien
54b9da287f update Docker image for the build 2020-10-28 18:23:22 -04:00
Sebastien
67ea623ba3 Fix binary signature with build number - release 2020-10-28 10:32:51 -04:00
Sebastien
9dbddd2c18 Disable unfinished Squeezelite config section - release 2020-10-27 17:26:06 -04:00
Sebastien
72345e1394 fix power save boot loop in IDF v4.0 - release
Disabled power save none, as this causes a boot loop under the latest esp-idf v4.0 release
2020-10-27 11:31:48 -04:00
Sebastien
f3662d4ca8 Fix build file for release 2020-10-27 09:54:27 -04:00
Sebastien
386f5a8cf7 Add artifact upload on create release 2020-10-27 09:34:02 -04:00
Sebastien
d23def1c02 Add artifact upload on create release 2020-10-27 09:32:12 -04:00
Sebastien
2861940b30 More build system changes for release 2020-10-27 09:16:46 -04:00
Sebastien
14f1e00540 Increase the Log Depth on build - Release
Also update the build name format
2020-10-27 09:07:57 -04:00
Sebastien
f1232eb83a Tweak build for release 2020-10-27 08:45:54 -04:00
Sebastien
42882265dd More Actions fixes and release 2020-10-27 08:18:51 -04:00
Sebastien
56d3c86b60 Fix compile error and attempt to release with GitHub Actions 2020-10-27 08:09:49 -04:00
Sebastien
445d335974 Additional fix to GitHub Actions 2020-10-26 21:36:18 -04:00
Sebastien
4825dc6644 Update Github Build and config command 2020-10-26 18:17:17 -04:00
Sebastien
33ad6d5950 Expand GitHub Actions for build 2020-10-26 17:58:46 -04:00
Sebastien
8ad02bd24c Update build Workflow Environment variable use 2020-10-16 12:24:20 -04:00
Sebastien
412227b958 Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2020-10-16 12:18:37 -04:00
Sébastien
754b864647 Update build badges 2020-10-16 11:46:03 -04:00
Sébastien
65571c4fb6 Update I2S-4MBFlash.yml 2020-10-16 11:43:50 -04:00
Sébastien
81bcc0def5 Fix I2S 4M flash build 2020-10-16 11:40:08 -04:00
Sébastien
24e060cb3b Add SqueezeAmp target build script 2020-10-16 11:38:34 -04:00
Sebastien
b46ae1e77c Add target file for SqueezeAmp 2020-10-16 11:37:53 -04:00
Sebastien
45b655c34c Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2020-10-16 11:36:08 -04:00
Sébastien
0d954b2132 Update README.md 2020-10-16 11:26:16 -04:00
Sébastien
6a215b81c7 Add more targets 2020-10-16 11:25:45 -04:00
Sebastien
bfc1544ed1 Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2020-10-16 11:16:15 -04:00
Sébastien
6866216dd0 Update I2S-4MBFlash.yml 2020-10-16 11:14:52 -04:00
Sebastien
42b9a54a45 Add more targets to the GitHub workflows 2020-10-16 11:12:57 -04:00
Sébastien
f6282fb798 Update I2S-4MBFlash.yml 2020-10-16 11:10:43 -04:00
Sébastien
830f1324ec Update README.md 2020-10-16 10:36:02 -04:00
Sébastien
f8a425e188 Update README.md 2020-10-16 10:33:06 -04:00
Sébastien
117aab6bef Update README.md 2020-10-16 10:30:15 -04:00
Sebastien
c73566caef Adding github build workflow 2020-10-16 10:23:43 -04:00
Sébastien
4167abb00e Update README.md 2020-10-16 09:50:52 -04:00
Sebastien
3e74292fc0 Reposition audio config and other small fixes - release 2020-10-09 16:35:30 -04:00
Sebastien
b301376fc6 Fix update page - release
Pressing check for update no longer appends duplicate entries in the branch selection drop down
2020-10-09 06:51:32 -04:00
Sebastien
262f5ff3e7 Merge remote-tracking branch 'origin/master-cmake' into master-cmake 2020-10-09 06:46:15 -04:00
Sebastien
d379858dd2 Display config UI fixes - release
Add 2 entries as a replacement for the ST77xx driver name, remove SSD1306 default.
2020-10-08 21:09:36 -04:00
Sébastien
368832d0d5 Major UI Update - release
- Bug fixes 
- Jack doesn't show as plugged in if no jack detection is configured 
- New layout
- Updated jQuery to latest version
- Updated bootstrap to latest version
- Updated the command processing backend to support UI interactions
- Added a number of accessors to normalize read/update various configuration entries 
- Added more GPIOs to the status tab GPIO list
- Added several configuration sections for hardware and system
- Removed pop-over windows from system messages
- Added a message count pill to the status tab
- Added support for message count pill based on the highest severity 
- Updated the message list table to set colours based on messages severity
- Added command processing message area close to the action buttons to provide feedback from running the commands


** See pervious commit for changed files**
2020-10-08 17:23:57 -04:00
Sebastien
be1d841039 Major UI Update
- Bug fixes 
- Jack doesn't show as plugged in if no jack detection is configured 
- New layout
- Updated jQuery to latest version
- Updated bootstrap to latest version
- Updated the command processing backend to support UI interactions
- Added a number of accessors to normalize read/update various configuration entries 
- Added more GPIOs to the status tab GPIO list
- Added several configuration sections for hardware and system
- Removed pop-over windows from system messages
- Added a message count pill to the status tab
- Added support for message count pill based on the highest severity 
- Updated the message list table to set colours based on messages severity
- Added command processing message area close to the action buttons to provide feedback from running the commands
2020-10-08 17:19:22 -04:00
Philippe G
6ae47a908b add player type 101 - release 2020-10-06 22:28:01 -07:00
Philippe G
797a21ee9f release 2020-09-26 12:21:45 -07:00
Philippe G
7f1db60c45 fix reboot logic upon server loss - release 2020-09-25 21:50:44 -07:00
Philippe G
fb530645b8 CRLF 2020-09-25 19:01:53 -07:00
Sébastien
7f932630fc release 2020-09-13 16:47:00 -04:00
Sebastien
458efb376a Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32.git into master-cmake 2020-09-13 12:17:38 -04:00
Sebastien
3ffbe022e5 Added wifi scanmode NVS option - release
f= default, fast scan
a= All channel scan
2020-09-13 12:17:27 -04:00
Philippe G
762c529563 fix a couple of gpio names (SPI/I2C)
Also a few "style" consistency changes so that within each file, look is the same, although different files can have different styles
2020-09-12 22:58:52 -07:00
Sebastien
0c224b4b84 New config UI for Services (Airplay, bt, etc) - release 2020-09-12 23:09:38 -04:00
Sebastien
bbbc924fcd Add nvs "wifi_ps" to disable wifi power save mode - release
Set disable_ps = n to disable power save mode. This may help with wifi
signal stability, but will likely result in a higher power consumption.
2020-09-12 16:05:49 -04:00
Chuck
cc5fb49ff8 Battery gauge fix (#52)
* Fix battery reporting in status.json, and adjust scaling for bettery level representation

* remove comment verbosity

* change battery_value_svc to return float

Co-authored-by: rochuck <chuck@zethus.ca>
2020-09-12 11:31:28 -04:00
Sebastien
189bc763dd WIP User Interface improvement. Fix SqueezeAmp build - release 2020-09-11 16:27:27 -04:00
Sebastien
78b7639400 WIP - User Interface improvements 2020-09-11 16:15:31 -04:00
Sebastien
56954962a3 WIP - Rework UI, add new commands for SPI and device name 2020-09-09 22:07:55 -04:00
Sebastien
5ff673ae7d Fix build issue - release 2020-09-08 17:11:52 -04:00
Sebastien
2eb995d621 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32.git into master-cmake 2020-09-08 16:47:08 -04:00
Sebastien
573ddb6fda Bug fix - some values weren't passed back in the command call - release 2020-09-08 16:46:50 -04:00
Philippe G
708a3f9c4a small tweaks 2020-09-06 17:06:52 -07:00
Philippe G
a73c659a1e solving mistery of component made of external static libs only
(and I hate CMake)
2020-09-06 16:54:09 -07:00
Philippe G
b22143a3b6 typos 2020-09-06 00:49:04 -07:00
Sebastien
6195750b41 Minor fix to the UI and command line help text - release 2020-09-04 16:24:12 -04:00
Sebastien
889b1097cc Reorganize configuration UI - release
The System tab is now hidden by default and can be enabled via a toggle
under the Credits tab, similar to how NVS tab works.  A new tab was
created to hold configurations, and display configuration was added.
2020-09-04 16:02:53 -04:00
Sebastien
41cdb8bcdd Allow saving/loading nvs from the nvs editor - release 2020-09-02 13:09:46 -04:00
Philippe G
8c33acfd35 tweak dac_controlset timing - release 2020-09-01 16:24:36 -07:00
Sebastien
0222a34286 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32.git into master-cmake 2020-09-01 16:45:11 -04:00
Sebastien
a90c9802ab Fix commands not working in telnet #43 - release 2020-09-01 16:43:48 -04:00
1454 changed files with 161019 additions and 17189 deletions

162
.github/workflows/CrossBuild.yml vendored Normal file
View File

@@ -0,0 +1,162 @@
# This is a basic workflow to help you get started with Actions
name: Cross-Build
on:
push:
branches:
- 'master-cmake'
- '!**4.3'
pull_request:
branches:
- 'master-cmake'
- '!**4.3'
jobs:
job1:
name: Build Number
runs-on: ubuntu-latest
outputs:
build_number: ${{ steps.buildnumber.outputs.build_number }}
steps:
- name: Generate common build number
id: buildnumber
uses: einaregilsson/build-number@v3
with:
token: ${{secrets.github_token}}
build:
runs-on: ubuntu-latest
needs: job1
strategy:
max-parallel: 1
matrix:
node: [I2S-4MFlash, SqueezeAmp, Muse]
depth: [16, 32]
exclude:
- node: Muse
depth: 32
steps:
- name: Set target name
run: |
echo "TARGET_BUILD_NAME=${{ matrix.node }}" >> $GITHUB_ENV
echo "build_version_prefix=1." >> $GITHUB_ENV
- uses: actions/checkout@v2
with:
fetch-depth: 15
submodules: true
- name: Cache build
id: cache-build
uses: actions/cache@v2
with:
path: |
build
/var/lib/docker
key: ${{ runner.os }}-${{ matrix.node }}-${{ matrix.depth }}
- name: Set build parameters
run: |
git update-index --chmod=+x ./server_certs/getcert.sh
cd server_certs;./getcert.sh;cat github.pem;cd ..
shopt -s nocasematch
branch_name="${GITHUB_REF//refs\/heads\//}"
branch_name="${branch_name//[^a-zA-Z0-9\-~!@_\.]/}"
BUILD_NUMBER=${{ needs.job1.outputs.build_number }}
echo "BUILD_NUMBER=${BUILD_NUMBER}" >> $GITHUB_ENV
tag="${TARGET_BUILD_NAME}.${{matrix.depth}}.${BUILD_NUMBER}.${branch_name}"
echo "tag=${tag}" >> $GITHUB_ENV
last_commit="$(git log --pretty=format:'%s' --max-count=1)"
if [[ "$last_commit" =~ .*"Release".* ]]; then echo "release_flag=1" >> $GITHUB_ENV; else echo "release_flag=0" >> $GITHUB_ENV; fi
name="1.${BUILD_NUMBER}-${{matrix.depth}}#v4.0#${TARGET_BUILD_NAME}#${branch_name}"
artifact_prefix="squeezelite-esp32-${branch_name}-${TARGET_BUILD_NAME}-${{matrix.depth}}-${build_version_prefix}${BUILD_NUMBER}"
artifact_file_name="${artifact_prefix}.zip"
artifact_bin_file_name="${artifact_prefix}.bin"
echo "name=${name}" >> $GITHUB_ENV
echo "last_commit=${last_commit}" >> $GITHUB_ENV
echo "artifact_file_name=${artifact_file_name}" >> $GITHUB_ENV
echo "PROJECT_VER=${TARGET_BUILD_NAME}-${{ steps.buildnumber.outputs.build_number }} " >> $GITHUB_ENV
echo "artifact_bin_file_name=${artifact_bin_file_name}" >> $GITHUB_ENV
description=""
description=${description}$'------------------------------\n### Revision Log\n\n'
description="$description$(git log --pretty=format:'%h %s (%cI) <%an>' --abbrev-commit --max-count=15 | sed --r 's/(^[\*]+)/\\\1/g') "
echo 'description<<~EOD' >> $GITHUB_ENV
echo ${description}>> $GITHUB_ENV
echo '~EOD' >> $GITHUB_ENV
echo #######
echo ####### Environment
echo #######
env
echo #######
echo ####### GITHUB ENV
echo #######
cat $GITHUB_ENV
- name: Build the firmware
run: |
env | grep "artifact\|tag\|GITHUB\|version\|NUMBER\|TARGET" >${TARGET_BUILD_NAME}-env.txt
echo "${tag}" >version.txt
docker pull sle118/idf:release-v4.0
docker info
docker run --env-file=${TARGET_BUILD_NAME}-env.txt -v $PWD:/project -w /project sle118/idf:release-v4.0 /bin/bash -c "cp build-scripts/${TARGET_BUILD_NAME}-sdkconfig.defaults sdkconfig && idf.py build -DDEPTH=${{ matrix.depth }} -DBUILD_NUMBER=${BUILD_NUMBER}-${{ matrix.depth }} && zip -r build_output.zip build && zip build/${artifact_file_name} partitions*.csv build/*.bin build/bootloader/bootloader.bin build/partition_table/partition-table.bin build/flash_project_args build/size_*.txt"
# - name: Build Mock firmware
# run: |
# mkdir -p build
# cd build
# mkdir -p partition_table
# mkdir -p bootloader
# echo "mock content"> squeezelite.bin
# echo "mock content"> recovery.bin
# echo "mock content"> ./bootloader/bootloader.bin
# echo "mock content"> ./partition_table/partition-table.bin
# echo "mock content"> flash_project_args
# echo "mock content"> size_comp1.txt
# echo "mock content"> size_comp2.txt
# echo "mock content"> ../partitions.csv
- uses: actions/upload-artifact@v2
with:
name: ${{ env.artifact_file_name }}
path: |
build/*.bin
build/bootloader/bootloader.bin
build/partition_table/partition-table.bin
build/flash_project_args
build/size_comp1.txt
build/size_comp2.txt
partitions.csv
sdkconfig
server_certs/github.pem
build_output.zip
- uses: actions/upload-artifact@v2
with:
name: ${{ env.artifact_bin_file_name }}
path: |
build/squeezelite.bin
- name: Create Release
if: env.release_flag == 1
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
with:
tag_name: ${{ env.tag }}
release_name: ${{ env.name }}
body: ${{ env.description }}
draft: false
prerelease: true
- name: Upload Release Asset - Squeezelite binary file
if: env.release_flag == 1
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: build/squeezelite.bin
asset_name: ${{ env.artifact_bin_file_name }}
asset_content_type: application/octet-stream
- name: Upload Release Asset - Zip file
if: env.release_flag == 1
id: upload-release-asset-zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: build/${{ env.artifact_file_name }}
asset_name: ${{ env.artifact_file_name }}
asset_content_type: application/octet-stream

232
.github/workflows/Platform_build.yml vendored Normal file
View File

@@ -0,0 +1,232 @@
name: Platform Build
on:
push:
branches:
- '**4.3'
workflow_dispatch:
inputs:
ui_build:
description: 'Force Rebuilding the UI. When not forced, the system will check for [ui-build] in the last commit message to trigger a ui rebuild'
required: true
type: boolean
release_build:
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
outputs:
build_number: ${{ steps.buildnumber.outputs.build_number }}
ui_build: ${{ steps.build_flags.outputs.ui_build }}
release_flag: ${{ steps.build_flags.outputs.release_flag }}
mock: ${{ steps.build_flags.outputs.mock }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
- name: Generate common build number
id: buildnumber
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
echo "Dumping environment"
env
. /opt/esp/python_env/idf4.3_py3.8_env/bin/activate
# build_flags support the following options
# --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
- name: Show Build Flags
run: |
echo "Running with the following options"
echo "Web Build Flag=${{steps.build_flags.outputs.ui_build}}"
echo "Mock flag=${{steps.build_flags.outputs.mock}}"
echo "Release Flag=${{steps.build_flags.outputs.release_flag}}"
- name: Refresh certificates
if: ${{ steps.build_flags.outputs.release_flag }}
run: |
git update-index --chmod=+x ./server_certs/getcert.sh
cd server_certs;./getcert.sh;cat github.pem;cd ..
- name: Setup Node.js dependencies
if: ${{ !env.ACT }}
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
cache-dependency-path: components/wifi-manager/webapp/package.json
- name: Build Web Application
if: ${{ steps.build_flags.outputs.ui_build == 1 }}
run: |
cd components/wifi-manager/webapp/
npm install
npm run-script build
- name: Update repository with prebuilt items
if: ${{ steps.build_flags.outputs.ui_build == 1 || steps.build_flags.outputs.release_flag == 1 }}
run: |
git config user.name github-actions
git config user.email github-actions@github.com
git add server_certs
git add components/wifi-manager/webapp/*.h
git add components/wifi-manager/webapp/*.c
git add components/wifi-manager/webapp/*.cmake
git add components/wifi-manager/webapp/dist/*
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
with:
name: prebuilt_objects
path: |
server_certs
components/wifi-manager/webapp/*.h
components/wifi-manager/webapp/*.c
components/wifi-manager/webapp/dist/*
components/wifi-manager/webapp/*.cmake
build:
runs-on: ubuntu-latest
container:
image: sle118/squeezelite-esp32-idfv43
needs: [bootstrap]
strategy:
matrix:
node: [I2S-4MFlash, SqueezeAmp, Muse]
depth: [16, 32]
exclude:
- node: Muse
depth: 32
- node: bootstrap
depth: 32
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
- name: Show Build Flags
run: |
echo "Running with the following options"
echo "Web Build Flag=${{needs.bootstrap.outputs.ui_build}}"
echo "Mock flag=${{needs.bootstrap.outputs.mock}}"
echo "Release Flag=${{needs.bootstrap.outputs.release_flag}}"
echo Environment File name: $GITHUB_ENV
- name: Set build parameters
run: |
. /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
- uses: actions/download-artifact@master
name: Restore common objects
with:
name: prebuilt_objects
- name: Build the firmware
if: ${{ needs.bootstrap.outputs.mock == 0 }}
run: |
. ${IDF_PYTHON_ENV_PATH}/bin/activate
chmod +x ./components/spotify/cspot/bell/nanopb/generator/protoc
chmod +x ./components/spotify/cspot/bell/nanopb/generator/protoc-gen-nanopb
chmod +x ./components/spotify/cspot/bell/nanopb/generator/*.py
chmod +x ./components/spotify/cspot/bell/nanopb/generator/*.py2
chmod +x ./components/spotify/cspot/bell/nanopb/generator/proto/*.py
echo "Copying target sdkconfig"
cp build-scripts/${TARGET_BUILD_NAME}-sdkconfig.defaults sdkconfig
echo "Building project"
idf.py build -DDEPTH=${DEPTH} -DBUILD_NUMBER=${BUILD_NUMBER}-${DEPTH}
- name: Build Mock firmware
if: ${{ needs.bootstrap.outputs.mock == 1 }}
run: |
mkdir -p build
cd build
mkdir -p partition_table
mkdir -p bootloader
echo \\"mock content\\"> ./squeezelite.bin
echo \"mock content\"> ./recovery.bin
echo \"mock content\"> ./bootloader/bootloader.bin
echo \"mock content\"> ./partition_table/partition-table.bin
echo \"mock content\"> ./ota_data_initial.bin
echo \"mock content\"> ./flash_project_args
echo \"mock content\"> ./size_comp1.txt
echo \"mock content\"> ./size_comp2.txt
echo \"mock content\"> ./partitions.csv
echo { \"write_flash_args\" : [ \"--flash_mode\", \"dio\", \"--flash_size\", \"detect\", \"--flash_freq\", \"80m\" ], \"flash_settings\" : { \"flash_mode\": \"dio\", \"flash_size\": \"detect\", \"flash_freq\": \"80m\" }, \"flash_files\" : { \"0x8000\" : \"partition_table/partition-table.bin\", \"0xd000\" : \"ota_data_initial.bin\", \"0x1000\" : \"bootloader/bootloader.bin\", \"0x10000\" : \"recovery.bin\", \"0x150000\" : \"squeezelite.bin\" }, \"partition_table\" : { \"offset\" : \"0x8000\", \"file\" : \"partition_table/partition-table.bin\" }, \"otadata\" : { \"offset\" : \"0xd000\", \"file\" : \"ota_data_initial.bin\" }, \"bootloader\" : { \"offset\" : \"0x1000\", \"file\" : \"bootloader/bootloader.bin\" }, \"app\" : { \"offset\" : \"0x10000\", \"file\" : \"recovery.bin\" }, \"squeezelite\" : { \"offset\" : \"0x150000\", \"file\" : \"squeezelite.bin\" }, \"extra_esptool_args\" : { \"after\" : \"hard_reset\", \"before\" : \"default_reset\" } } > ./flasher_args.json
- name: Create Release Artifact Zip
if: ${{ needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
run: |
if [ -z "${artifact_file_name}" ]
then
echo "No artifact file name set. Will not generate zip file."
else
echo "Generating build artifact zip file"
zip -r build_output.zip build
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
if: ${{ needs.bootstrap.outputs.mock == 0 }}
with:
name: ${{ env.artifact_prefix }}
path: |
build/flash_project_args
build/size_comp1.txt
build/size_comp2.txt
partitions.csv
sdkconfig
server_certs/github.pem
build/*.bin
build/bootloader/bootloader.bin
build/partition_table/partition-table.bin
build_output.zip
- name: Create Release
if: ${{ needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
with:
tag_name: ${{ env.tag }}
release_name: ${{ env.name }}
body: ${{ env.description }}
draft: false
prerelease: true
- name: Upload Release Asset - Squeezelite binary file
if: ${{ needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: build/squeezelite.bin
asset_name: ${{ env.artifact_bin_file_name }}
asset_content_type: application/octet-stream
- name: Upload Release Asset - Zip file
if: ${{ needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
id: upload-release-asset-zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: build/${{ env.artifact_file_name }}
asset_name: ${{ env.artifact_file_name }}
asset_content_type: application/octet-stream
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 }}
uses: ./.github/workflows/web_deploy.yml

67
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master-cmake ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master-cmake ]
schedule:
- cron: '19 12 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'cpp', 'javascript', 'python']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# 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
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

167
.github/workflows/esp-idf-v4.3-build.yml vendored Normal file
View File

@@ -0,0 +1,167 @@
# This is a basic workflow to help you get started with Actions
name: ESP-IDF v4.3.1
on:
push:
branches:
- '**4.3'
pull_request:
branches:
- '**4.3'
jobs:
job1:
name: Build Number
runs-on: ubuntu-latest
outputs:
build_number: ${{ steps.buildnumber.outputs.build_number }}
steps:
- name: Generate common build number
id: buildnumber
uses: einaregilsson/build-number@v3
with:
token: ${{secrets.github_token}}
build:
runs-on: ubuntu-latest
needs: job1
strategy:
max-parallel: 1
matrix:
node: [I2S-4MFlash, SqueezeAmp, Muse]
depth: [16, 32]
exclude:
- node: Muse
depth: 32
steps:
- name: Set target name
run: |
echo "TARGET_BUILD_NAME=${{ matrix.node }}" >> $GITHUB_ENV
echo "build_version_prefix=2." >> $GITHUB_ENV
- uses: actions/checkout@v2
with:
fetch-depth: 15
submodules: true
- name: Cache build
id: cache-build
uses: actions/cache@v2
with:
path: |
~/build
./
key: ${{ runner.os }}-${{ matrix.node }}
- name: Set build parameters
run: |
git update-index --chmod=+x ./server_certs/getcert.sh
git update-index --chmod=+x ./buildFirmware.sh
git update-index --chmod=+x ./components/spotify/cspot/bell/nanopb/generator/protoc
git update-index --chmod=+x ./components/spotify/cspot/bell/nanopb/generator/protoc-gen-nanopb
git update-index --chmod=+x ./components/spotify/cspot/bell/nanopb/generator/*.py
git update-index --chmod=+x ./components/spotify/cspot/bell/nanopb/generator/*.py2
git update-index --chmod=+x ./components/spotify/cspot/bell/nanopb/generator/proto/*.py
cd server_certs;./getcert.sh;cat github.pem;cd ..
shopt -s nocasematch
branch_name="${GITHUB_REF//refs\/heads\//}"
branch_name="${branch_name//[^a-zA-Z0-9\-~!@_\.]/}"
BUILD_NUMBER=${{ needs.job1.outputs.build_number }}
echo "BUILD_NUMBER=${BUILD_NUMBER}" >> $GITHUB_ENV
echo "DOCKER_IMAGE_NAME=sle118/squeezelite-esp32-idfv4-master" >> $GITHUB_ENV
tag="${TARGET_BUILD_NAME}.${{matrix.depth}}.${build_version_prefix}${BUILD_NUMBER}.${branch_name}"
echo "tag=${tag}" >> $GITHUB_ENV
last_commit="$(git log --pretty=format:'%s' --max-count=1)"
if [[ "$last_commit" =~ .*"Release".* ]]; then echo "release_flag=1" >> $GITHUB_ENV; else echo "release_flag=0" >> $GITHUB_ENV; fi
name="${build_version_prefix}${BUILD_NUMBER}-${{matrix.depth}}#v4.0#${TARGET_BUILD_NAME}#${branch_name}"
artifact_prefix="squeezelite-esp32-${branch_name}-${TARGET_BUILD_NAME}-${{matrix.depth}}-${build_version_prefix}${BUILD_NUMBER}"
artifact_file_name="${artifact_prefix}.zip"
artifact_bin_file_name="${artifact_prefix}.bin"
echo "name=${name}" >> $GITHUB_ENV
echo "last_commit=${last_commit}" >> $GITHUB_ENV
echo "artifact_file_name=${artifact_file_name}" >> $GITHUB_ENV
echo "PROJECT_VER=${TARGET_BUILD_NAME}-${{ steps.buildnumber.outputs.build_number }} " >> $GITHUB_ENV
echo "artifact_bin_file_name=${artifact_bin_file_name}" >> $GITHUB_ENV
description=$'### Revision Log\n'
githist="$(git log --pretty=format:'%h %s (%cI) <%an>' --abbrev-commit --max-count=15 | sed --r 's/(^[\*]+)/\\\1/g')"
description="$description$githist"
echo 'description<<~EOD' >> $GITHUB_ENV
echo ${description}>> $GITHUB_ENV
echo '~EOD' >> $GITHUB_ENV
echo #######
echo ####### Environment
echo #######
env
echo #######
echo ####### GITHUB ENV
echo #######
cat $GITHUB_ENV
- name: Build the firmware
run: |
env | grep "artifact\|tag\|GITHUB\|version\|NUMBER\|TARGET" >${TARGET_BUILD_NAME}-env.txt
echo pulling custom docker image ${DOCKER_IMAGE_NAME}
docker pull ${DOCKER_IMAGE_NAME}
docker run --env-file=${TARGET_BUILD_NAME}-env.txt -v $PWD:/project -w /project ${DOCKER_IMAGE_NAME} /bin/bash -c "./buildFirmware.sh"
# - name: Build Mock firmware
# run: |
# mkdir -p build
# cd build
# mkdir -p partition_table
# mkdir -p bootloader
# echo "mock content"> squeezelite.bin
# echo "mock content"> recovery.bin
# echo "mock content"> ./bootloader/bootloader.bin
# echo "mock content"> ./partition_table/partition-table.bin
# echo "mock content"> flash_project_args
# echo "mock content"> size_comp1.txt
# echo "mock content"> size_comp2.txt
# echo "mock content"> ../partitions.csv
- uses: actions/upload-artifact@v2
with:
name: ${{ env.artifact_file_name }}
path: |
build/*.bin
build/bootloader/bootloader.bin
build/partition_table/partition-table.bin
build/flash_project_args
build/size_comp1.txt
build/size_comp2.txt
partitions.csv
sdkconfig
server_certs/github.pem
build_output.zip
- uses: actions/upload-artifact@v2
with:
name: ${{ env.artifact_bin_file_name }}
path: |
build/squeezelite.bin
- name: Create Release
if: env.release_flag == 1
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
with:
tag_name: ${{ env.tag }}
release_name: ${{ env.name }}
body: ${{ env.description }}
draft: false
prerelease: true
- name: Upload Release Asset - Squeezelite binary file
if: env.release_flag == 1
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: build/squeezelite.bin
asset_name: ${{ env.artifact_bin_file_name }}
asset_content_type: application/octet-stream
- name: Upload Release Asset - Zip file
if: env.release_flag == 1
id: upload-release-asset-zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: build/${{ env.artifact_file_name }}
asset_name: ${{ env.artifact_file_name }}
asset_content_type: application/octet-stream

23
.github/workflows/web_deploy.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Update Web Installer
on:
workflow_call:
workflow_dispatch:
jobs:
update_web_installer:
name: Update Web Installer After Release
runs-on: ubuntu-latest
container:
image: sle118/squeezelite-esp32-idfv43
env:
WEB_INSTALLER: ${{ secrets.WEB_INSTALLER }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
- name: Update Web Installer Project
run: |
. /opt/esp/python_env/idf4.3_py3.8_env/bin/activate
git config --global --add safe.directory /__w/squeezelite-esp32/squeezelite-esp32
build_tools.py manifest --flash_file "/build/flash_project_args" --outdir "./bin_files" --manif_name "manifest" --max_count 3
build_tools.py pushinstaller --source "./bin_files" --manif_name "manifest" --target "web-installer" --url "https://github.com/sle118/squeezelite-esp32-installer.git" --artifacts "docs/artifacts" --web_installer_branch "main" --token "${{env.WEB_INSTALLER}}"

85
.gitignore vendored
View File

@@ -1,71 +1,30 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.lib
# Executables
*.exe
*.out
*.app
# Build files with potential private info
build/
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/settings.json
.vscode/tasks.json
alltags.txt
components/wifi-manager/network_manager_handlers.multi
esp32.code-workspace
sdkconfig.old
test/.vscode/c_cpp_properties.json
test/.vscode/launch.json
test/.vscode/settings.json
test/.vscode/tasks.json
test/sdkconfig
components/wifi-manager/UML-State-Machine-in-C
*.bak
# =========================
# Operating System Files
# =========================
envfile.txt
artifacts
web-installer
# Windows
# =========================
squeezelite-esp32.code-workspace
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
esp-idf-vscode-generated.gdb
# Folder config file
Desktop.ini
debug.log
# Recycle Bin used on file shares
$RECYCLE.BIN/
components/wifi-manager/esp32_improv.cpp.txt
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
*.save
libs/
/cdump.cmd
/_*
squeezelite-esp32-jsonblob.zip
/flash_cmd.txt
/writeSequeezeEsp.bat
/writeSequeezeEsp.sh
components/wifi-manager/esp32_improv.h.txt

3
.gitmodules vendored
View File

@@ -5,3 +5,6 @@
[submodule "components/esp-dsp"]
path = components/esp-dsp
url = https://github.com/philippe44/esp-dsp.git
[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

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?eclipse-pydev version="1.0"?><pydev_project>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python interpreter</pydev_property>
</pydev_project>

View File

@@ -1,15 +1,128 @@
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)
add_definitions(-DMODEL_NAME=SqueezeESP32)
# State machine hierarchy enabled and logging enabled
add_definitions(-DSTATE_MACHINE_LOGGER=1)
add_definitions(-DHIERARCHICAL_STATES=1)
# Uncomment line below to get memory usage trace details
#add_definitions(-DENABLE_MEMTRACE=1)
#uncomment line below to get network ethernet debug logs
#add_definitions(-DNETWORK_ETHERNET_LOG_LEVEL=ESP_LOG_DEBUG)
#uncomment line below to get network status debug logs
#add_definitions(-DNETWORK_STATUS_LOG_LEVEL=ESP_LOG_DEBUG)
#add_definitions(-DNETWORK_HANDLERS_LOG_LEVEL=ESP_LOG_DEBUG)
#add_definitions(-DNETWORK_WIFI_LOG_LEVEL=ESP_LOG_DEBUG)
#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")
project(recovery)
set_property(TARGET recovery.elf PROPERTY RECOVERY_PREFIX app_recovery )
include(squeezelite.cmake)
set(PROJECT_VER $ENV{PROJECT_VER})
#target_compile_definitions(__idf_squeezelite-ota PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_VERBOSE)
#target_compile_definitions(__idf_driver_bt PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_VERBOSE)
#target_compile_definitions(__idf_esp_eth PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_INFO)
#target_compile_definitions(__idf_services PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
#target_compile_definitions(__idf_driver PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
#target_compile_definitions(__idf_wifi-manager PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
#target_compile_definitions(__idf_esp_wifi PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
#target_compile_definitions(__idf_platform_console PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
#target_compile_definitions(__idf_app_recovery PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_INFO)
# target_compile_definitions(__idf_esp_eth PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_INFO)
# target_compile_definitions(__idf_esp_event PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_INFO)
# target_compile_definitions(__idf_esp_netif PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_freertos PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
#target_compile_definitions(__idf_bt PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_ERROR)
# target_compile_definitions(__idf_mdns PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_tcpip_adapter PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_tcp_transport PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
#target_compile_definitions(__idf_app_squeezelite PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_app_trace PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_app_update PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_asio PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_audio PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_bootloader_support PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_cbor PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_cmock PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_coap PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_console PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_cxx PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_display PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_driver PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_driver_bt PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_efuse PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp-dsp PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp-tls PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp32 PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_espcoredump PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_adc_cal PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_common PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_gdbstub PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_hid PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_https_ota PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_http_client PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_http_server PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_hw_support PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_ipc PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_local_ctrl PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_pm PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_ringbuf PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_rom PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_serial_slave_link PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_system PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_timer PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_esp_websocket_client PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_expat PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_fatfs PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_freemodbus PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_hal PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_heap PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_jsmn PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_json PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_libsodium PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_log PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_lwip PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_main PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_mbedtls PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(mbedcrypto PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(mbedtls PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(mbedx509 PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_mqtt PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_newlib PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_nghttp PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_nvs_flash PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_openssl PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_perfmon PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_platform_config PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_protobuf-c PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_protocomm PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_pthread PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_raop PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_sdmmc PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_soc PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_spiffs PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_spi_flash PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_squeezelite PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_squeezelite-ota PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_telnet PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_tools PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_ulp PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_unity PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# target_compile_definitions(__idf_vfs PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
# 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)

View File

@@ -1,35 +1,156 @@
FROM ubuntu:18.04
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y git wget libncurses-dev flex bison gperf \
python python-pip python-setuptools python-serial python-click \
python-cryptography python-future python-pyparsing \
python-pyelftools cmake ninja-build ccache libusb-1.0
RUN mkdir /workspace
WORKDIR /workspace
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-
# 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-idfv4-master
# 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
# Download and checkout known good esp-idf commit
RUN git clone --recursive https://github.com/espressif/esp-idf.git esp-idf
RUN cd esp-idf && git checkout 4dac7c7df885adaa86a5c79f2adeaf8d68667349
RUN git clone https://github.com/sle118/squeezelite-esp32.git
ARG IDF_CLONE_URL=https://github.com/espressif/esp-idf.git
ARG IDF_CLONE_BRANCH_OR_TAG=master
ARG IDF_CHECKOUT_REF=8bf14a9238329954c7c5062eeeda569529aedf75
# Download GCC 5.2.0
RUN wget https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz
RUN tar -xzf xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz
RUN rm xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz
ENV IDF_PATH=/opt/esp/idf
ENV IDF_TOOLS_PATH=/opt/esp
# Patch I2S in esp-idf
RUN cp /workspace/squeezelite-esp32/idf-patch/i2s.c /workspace/esp-idf/components/driver/i2s.c
RUN rm -r /workspace/squeezelite-esp32
RUN mkdir /workspace/squeezelite-esp32
# We need libpython2.7 due to GDB tools
# we also need npm 8 for the webapp to work
RUN : \
&& apt-get update \
&& apt-get install -y \
apt-utils \
bison \
ca-certificates \
ccache \
check \
curl \
flex \
git \
gperf \
lcov \
libffi-dev \
libncurses-dev \
libpython2.7 \
libusb-1.0-0-dev \
make \
ninja-build \
python3 \
python3-pip \
unzip \
wget \
xz-utils \
zip \
npm \
nodejs \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& update-alternatives --install /usr/bin/python python /usr/bin/python3 10 \
&& python -m pip install --upgrade \
pip \
virtualenv \
&& cd /opt \
&& git clone https://github.com/HBehrens/puncover.git \
&& cd puncover \
&& python setup.py -q install \
&& echo IDF_CHECKOUT_REF=$IDF_CHECKOUT_REF IDF_CLONE_BRANCH_OR_TAG=$IDF_CLONE_BRANCH_OR_TAG \
&& git clone --recursive \
${IDF_CLONE_BRANCH_OR_TAG:+-b $IDF_CLONE_BRANCH_OR_TAG} \
$IDF_CLONE_URL $IDF_PATH \
&& if [ -n "$IDF_CHECKOUT_REF" ]; then \
cd $IDF_PATH \
&& git checkout $IDF_CHECKOUT_REF \
&& git submodule update --init --recursive; \
fi \
&& update-ca-certificates --fresh \
&& $IDF_PATH/tools/idf_tools.py --non-interactive install required \
&& $IDF_PATH/tools/idf_tools.py --non-interactive install cmake \
&& $IDF_PATH/tools/idf_tools.py --non-interactive install-python-env \
&& :
RUN : \
echo Installing pygit2 ******************************************************** \
&& . /opt/esp/python_env/idf4.3_py3.8_env/bin/activate \
&& ln -sf /opt/esp/python_env/idf4.3_py3.8_env/bin/python /usr/local/bin/python \
&& pip install pygit2 requests \
&& pip show pygit2 \
&& python --version \
&& pip --version \
&& pip3 install protobuf grpcio-tools \
&& rm -rf $IDF_TOOLS_PATH/dist \
&& :
# Setup PATH to use esp-idf and gcc-5.2.0
RUN touch /root/.bashrc && \
echo export PATH="\$PATH:/workspace/xtensa-esp32-elf/bin" >> /root/.bashrc && \
echo export IDF_PATH=/workspace/esp-idf >> /root/.bashrc
COPY docker/patches $IDF_PATH
# OPTIONAL: Install vim for text editing in Bash
RUN apt-get update && apt-get install -y vim
#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 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"
ENV IDF_TOOLS_INSTALL_CMD="/opt/esp/idf/install.sh"
ENV IDF_TOOLS_PATH="/opt/esp"
ENV NODE_PATH="/v8/lib/node_modules"
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
WORKDIR /workspace/squeezelite-esp32
CMD ["bash"]
ENV IDF_CCACHE_ENABLE=1
COPY docker/entrypoint.sh /opt/esp/entrypoint.sh
COPY components/wifi-manager/webapp/package.json /opt
ENV NODE_VERSION 8
SHELL ["/bin/bash", "--login", "-c"]
# Install nvm with node and npm
# RUN wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash \
# && export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" \
# && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" \
# && nvm install $NODE_VERSION \
# && nvm alias default $NODE_VERSION \
# && nvm use default \
# && echo installing nodejs version 16 \
# && curl -sL https://deb.nodesource.com/setup_16.x | bash - \
# && echo installing node modules \
# && cd /opt \
# && nvm use default \
# && npm install -g \
# && :
RUN : \
&& curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \
&& apt-get install -y nodejs jq \
&& echo installing dev node modules globally \
&& cd /opt \
&& cat ./package.json | jq '.devDependencies | keys[] as $k | "\($k)@\(.[$k])"' | xargs -t npm install --global \
&& echo installing npm global packages \
&& npm i -g npm \
&& node --version \
&& npm install -g \
&& :
ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
ENV PATH $IDF_PYTHON_ENV_PATH:$NVM_DIR/v$NODE_VERSION/bin:$PATH
COPY ./docker/build_tools.py /usr/sbin/build_tools.py
RUN : \
&& echo Changing permissions ******************************************************** \
&& chmod +x /opt/esp/entrypoint.sh \
&& chmod +x /usr/sbin/build_tools.py \
&& :
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD [ "/bin/bash" ]

374
README.md
View File

@@ -1,20 +1,91 @@
![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)
# Squeezelite-esp32
## Supported Hardware
### SqueezeAMP
Works with the SqueezeAMP see [here](https://forums.slimdevices.com/showthread.php?110926-pre-ANNOUNCE-SqueezeAMP-and-SqueezeliteESP32) and [here](https://github.com/philippe44/SqueezeAMP/blob/master/README.md). Add repository https://raw.githubusercontent.com/sle118/squeezelite-esp32/master/plugin/repo.xml to LMS if you want to have a display
## 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
Use the `squeezelite-esp32-SqueezeAmp-sdkconfig.defaults` configuration file.
- 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 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)
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.
Other features include
- Resampling
- 10-bands equalizer
- Automatic initial setup using any WiFi device
- Full web interface for further configuration/management
- Firmware over-the-air update
To control the equalizer or use the display on LMS, a new player model is required and this is provided through a plugin that is part of LMS' 3rd party repositories
## Performances
*(opinions presented here so I = @philippe44)*
The main build of squeezelite-esp32 is a 16 bits internal core with all calculations in 32 bits or float precision. This is a design choice I've made to preserve CPU performances (it is already stretching a lot the esp32 chipset) and optimize memory usage as we only have 4MB of usable RAM. Some might correctly comment that the WROVER module have 8MB of RAM, but the processor is only able to address 4MB and the remaining 4MB must be paginated by smaller blocks and I don't have patience to that.
Now, when I did the porting of squeezelite to esp32, I've also made the core 16 or 32 bits compatible at compile-time. So far, it works in 32 bits but less tests have been done. You can chose to compile it in 32 bits mode. I'm not very interested above 16 bits samples because it does not bring anything (I have an engineering background in theory of information).
| Capability |16 bits|32 bits| comment |
|----------------------------|-------|-------|--------------------------------------------------------------------|
| max sampling rate | 192k | 96k | 192k is very challenging, especially when combined with display |
| max bit depth | 16 | 24 | 24 bits are truncated in 16 bits mode |
| spdif |16 bits|20 bits| |
| mp3, aac, opus, ogg/vorbis | 48k | 48k | |
| alac, flac, ogg/flac | 96k | 96k | |
| pcm, wav, aif | 192k | 96k | |
| equalizer | Y | N | 48kHz max (after resampling) - equalization skipped on >48k tracks |
| resampling | Y | N | |
| cross-fade | 10s | <5s | depends on buffer size and sampling rate |
The esp32 must run at 240 MHz, with Quad-SPI I/O at 80 MHz and a clock of 40 Mhz. Still, it's a lot to run, especially knowing that it has a serial Flash and PSRAM, so kudos to Espressif for their chipset optimization. Now, to have all the decoding, resampling, equalizing, gain, display, spectrum/vu is a very (very) delicate equilibrium between use of internal /external RAM, tasks priorities and buffer handling. It is not perfect and the more you push the system to the limit, the higher the risk that some files would not play (see below). In general, the display will always have the lowest priority and you'll notice slowdown in scrolling and VU/Spectrum refresh rates. Now, even display thread has some critical section and impacts the capabilities. For example, a 16 bits-depth color display with low SPI speed might prevent 24/96 flac to work but still work with pcm 24/96
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.
## 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.
### 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.
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
### 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/4000765857347.html?spm=2114.12010615.8148356.11.5d963cd0j669ns) or an external amplifier if you want direct speaker connection.
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)
The board showed above has the following IO set
The board shown above has the following IO set
- amplifier: GPIO21
- key2: GPIO13, key3: GPIO19, key4: GPIO23, key5: GPIO18, key6: GPIO5 (to be confirmed with dip switches)
- key1: not sure, something with GPIO36
- key1: not sure, using GPIO36 in a matrix
- jack insertion: GPIO39 (inserted low)
- LED: GPIO22 (active low)
(note that GPIO need pullups)
- D4 -> GPIO22 used for green LED (active low)
- D5 -> GPIO19 (muxed with key3)
- The IO connector also brings GPIO5, GPIO18, GPIO19, GPIO21, GPIO22 and GPIO23 (don't forget it's muxed with keys!)
- The JTAG connector uses GPIO 12, 13, 14 and 15 (see dip switch) but these are also used for SD-card (and GPIO13 is key2 as well)
- It's always possible to re-use GPIOO (download at boot) and GPIO1/GPIO3 which are RX/TX of UART0 but you'll lose trace
(note that some GPIO need pullups)
So a possible config would be
- set_GPIO: 21=amp,22=green:0,39=jack:0
@@ -22,7 +93,18 @@ So a possible config would be
```
[{"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
- 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:
@@ -41,6 +123,10 @@ XMT - 3.3V
Use the `squeezelite-esp32-I2S-4MFlash-sdkconfig.defaults` configuration file.
### SqueezeAmpToo !
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.
@@ -49,16 +135,31 @@ The NVS parameter "i2c_config" set the i2c's gpio used for generic purpose (e.g.
```
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>
### SPI
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=<gpio>,clk=<gpio>[,dc=<gpio>][,host=1|2]
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".
### 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. If your DAC also requires i2c, then you must go the re-compile route. 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 SqueezeAMP and A1S where these are forced at runtime. Syntax is
```
bck=<gpio>,ws=<gpio>,do=<gpio>
bck=<gpio>,ws=<gpio>,do=<gpio>[,mck][,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|AC101|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.
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:
```
{ 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"} ] }
```
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
NB: For specific builds (all except I2S), all this is ignored. For know codecs, the built-in sequences can be overwritten using dac_controlset
<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.
@@ -70,16 +171,43 @@ Leave it blank to disable SPDIF usage, you can also define them at compile time
```
bck=<gpio>,ws=<gpio>,do=<gpio>
```
NB: For well-known configuration, 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.
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
```
100nF
GPIO ----210ohm-----------||---- coax S/PDIF signal out
|
110ohm
|
Ground -------------------------- coax signal ground
```
### Display
The NVS parameter "display_config" sets the parameters for an optional display. Syntax is
```
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,HFlip][,VFlip][driver=SSD1306|SSD1326|SH1106]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1326|SH1106]
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>]
```
- 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
- 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))
- 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
- 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
Currently 128x32/64 I2C and SPI display like [this](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi) and [this](https://www.waveshare.com/wiki/1.3inch_OLED_HAT) are supported
You can tweak how the vu-meter and spectrum analyzer are displayed, as well as size of artwork through a dedicated menu in player's settings (don't forget to add the plugin).
The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay and Bluetooth. Syntax is
```
@@ -91,8 +219,6 @@ The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay a
- '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>".
You can install the excellent plugin "Music Information Screen" which is super useful to tweak the layout for these small displays.
### 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.
@@ -103,11 +229,11 @@ In AirPlay and Bluetooth mode, only these native remotes are supported, I've not
See "set GPIO" below to set the GPIO associated to infrared receiver (option "ir").
### Set GPIO
The parameter "set_GPIO" is use to assign GPIO to various functions.
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 1 when playback starts. It will be reset to 0 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\>
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.
@@ -118,24 +244,41 @@ The \<ir\> parameter set the GPIO associated to an IR receiver. No need to add p
Syntax is:
```
<gpio>=Vcc|GND|amp|ir|jack[:0|1]|green[:0|1]|red[:0|1]|spkfault[:0|1][,<repeated sequence for next GPIO>]
<gpio>=Vcc|GND|amp[:1|0]|ir|jack[:0|1]|green[:0|1]|red[:0|1]|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 SqueezeAMP 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 well-known configurations 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**
### 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
```
[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.
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.
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').
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'
- double press is 'Back' (Left in LMS's terminology).
- a quick left-right movement on the encoder is 'Pause'
The speed of double click (or left-right) can be set using the optional parameter of 'knobonly'. This is not a perfect solution, and other ideas are welcome. Be aware that the longer you set double click speed, the less responsive the interface will be. The reason is that I need to wait for that delay before deciding if it's a single or double click. It can also make menu navigation "hesitations" being easily interpreted as 'Pause'
Use parameter rotary_config with the following syntax:
```
A=<gpio>,B=<gpio>[,SW=gpio>[,volume][,longpress]]
A=<gpio>,B=<gpio>[,SW=gpio>[[,knobonly[=<ms>]]|[[,volume][,longpress]]]]
```
HW note: all gpio used for rotary have internal pull-up so normally there is no need to provide Vcc to the encoder. Nevertheless if the encoder board you're using also has its own pull-up that are stronger than ESP32's ones (which is likely the case), then there will be crosstalk between gpio, so you must bring Vcc. Look at your board schematic and you'll understand that these board pull-up create a "winning" pull-down when any other pin is grounded.
See also the "IMPORTANT NOTE" on the "Buttons" section
The SW gpio is optional, you can re-affect it to a pure button if you prefer but the volume, longpress and knobonly options make little sense as the missing switch plays an important role in these modes. You could still have the "volume" mode, but you won't be able to use it for *anything* expect volume up and down. So be aware that the use of syntax [] is a bit misleading hereabove.
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**
### Buttons
Buttons are described using a JSON string with the following syntax
```
@@ -170,10 +313,11 @@ Where (all parameters are optionals except gpio)
Where \<action\> is either the name of another configuration to load (remap) or one amongst
```
ACTRLS_NONE, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY,
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,
KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH
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,
```
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
@@ -225,86 +369,94 @@ Below is a difficult but functional 2-buttons interface for your decoding pleasu
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.
**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).
#### RMII (LAN8720)
- RMII PHY wiring is fixed and can not be changed
| GPIO | RMII Signal | Notes |
| ------ | ----------- | ------------ |
| GPIO21 | TX_EN | EMAC_TX_EN |
| GPIO19 | TX0 | EMAC_TXD0 |
| GPIO22 | TX1 | EMAC_TXD1 |
| GPIO25 | RX0 | EMAC_RXD0 |
| GPIO26 | RX1 | EMAC_RXD1 |
| GPIO27 | CRS_DV | EMAC_RX_DRV |
- 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:
```
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:
```
model=dm9051,cs=<gpio>,speed=<clk_in_Hz>,intr=<gpio>[,host=<-1|1|2>][,rst=<gpio>][,mosi=<gpio>,miso=<gpio>,clk=<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 |
| -------- | ---- | ---- |
| CS0* | 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. 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
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>
channel=0..7,scale=<scale>,cells=<2|3>[,atten=<0|1|2|3>]
```
NB: Set parameter to empty to disable battery reading
## Setting up ESP-IDF
### Docker
#### **************** todo: Docker scripts needs some rework.
You can use docker to build squeezelite-esp32
First you need to build the Docker container:
```
docker build -t esp-idf .
```
Then you need to run the container:
```
docker run -i -t -v `pwd`:/workspace/squeezelite-esp32 esp-idf
```
The above command will mount this repo into the docker container and start a bash terminal
for you to then follow the below build steps
### Manual Install of ESP-IDF
<<<<<<< HEAD
Follow the instructions from https://docs.espressif.com/projects/esp-idf/en/v4.0/get-started/index.html to install the esp-idf v4.0. This is the currently supported release of the espressif software development system.
=======
<strong>Currently the master branch of this project requires this [IDF](https://github.com/espressif/esp-idf/tree/28f1cdf5ed7149d146ad5019c265c8bc3bfa2ac9) with gcc 5.2 (toolschain dated 20181001)
If you want to use a more recent version of gcc and IDF (4.0 stable), move to cmake-master branch</strong>
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/
You also need to use esp-dsp recent version or at least make sure you have this patch https://github.com/espressif/esp-dsp/pull/12/commits/8b082c1071497d49346ee6ed55351470c1cb4264
>>>>>>> refs/remotes/origin/master
## Building Squeezelite-esp32
MOST IMPORTANT: create the right default config file
- make defconfig
(Note: You can also copy over config files from the build-scripts folder to ./sdkconfig)
Then adapt the config file to your wifi/BT/I2C device (can also be done on the command line)
- make menuconfig
Then
```
idf.py -p PORT [-b BAUD] flash
idf.py -p PORT [-b BAUD] monitor
```
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
1/ setup WiFi
## 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
- Wait for the list of access points visible from the device to populate in the web page.
- Choose an access point and enter any credential as needed
- Once connection is established, note down the address the device received; this is the address you will use to configure it going forward
2/ setup squeezelite command line (optional)
## 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, hoose the mode in which you want squeezelite to start
- Using the list of predefined options, choose the mode in which you want squeezelite to start
- Generate the command
- Add or change any additional command line option (for example player name, etc)
- Activate squeezelite execution: this tells the device to automatiaclly run the command at start
- Update the configuration
- click on the "start toggle" button. This will force a reboot.
- The toggle switch should be set to 'ON' to ensure that squeezelite is active after booting
- 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'
3/ Updating Squeezelite
## 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
- From the firmware tab, click on "Check for Updates"
- Look for updated binaries
- Select a line
- Click on "Flash!"
- The system will reboot into recovery mode (if not already in that mode), wipe the squeezelite partition and download/flash the selected version
- You can choose a local file or have a local webserver
3/ Recovery
## Recovery
- From the firmware tab, click on the "Recovery" button. This will reboot the ESP32 into recovery, where additional configuration options are available from the NVS editor
# Additional command line notes, configured from the http configuration
## Additional configuration notes (from the Web UI)
The squeezelite options are very similar to the regular Linux ones. Differences are :
- the output is -o ["BT -n '<sinkname>' "] | [I2S]
@@ -320,12 +472,48 @@ See squeezlite 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
# 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
```
Then run the container interactively :
```
for windows:
docker run -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv4-master
for linux:
docker run -it -v `pwd`:/workspace/squeezelite-esp32 sle118/squeezelite-esp32-idfv4-master
```
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.
## Additional misc notes to do you build
- as of this writing, ESP-IDF has a bug int he way the PLL values are calculated for i2s, so you *must* use the i2s.c file in the patch directory
### 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.
**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`
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)**
Create and tweak your config using `idf.py menuconfig` then build binaries using `idf.py all`. It will build the recovery and the application (squeezelite). then use `idf.py flash` to write everything. Otherwise, if you just want to download squeezelite, do (assuming you have set ESPPORT (e.g. COM10) and ESPBAUD (e.g. 921600)
```
<path_to_your_python>/python.exe <path_to_your_esptool>/esptool.py -p %ESPPORT% -b %ESPBAUD% --before default_reset --after hard_reset write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x150000 build/squeezelite.bin
```
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
- libfaad does not really support real time, but if you want to try
- libfaad does not really support real time, but if you want to try (but using helixaac is a better option)
- -O3 -DFIXED_POINT -DSMALL_STACK
- change ac_link in configure and case ac_files, remove ''
- compiler but in cfft.c and cffti1, must disable optimization using
@@ -337,20 +525,8 @@ See squeezlite command line, but keys options are
- per mad & few others, edit configure and change $ac_link to add -c (faking link)
- change ac_files to remove ''
- add DEPS_CFLAGS and DEPS_LIBS to avoid pkg-config to be required
- stack consumption can be very high with some codec variants, so set NONTHREADSAFE_PSEUDOSTACK and GLOBAL_STACK_SIZE=32000 and unset VAR_ARRAYS in config.h
- better use helixaac
- set IDF_PATH=/home/esp-idf
- set ESPPORT=COM9
- update flash partition size
- other compiler #define
- use no resampling or set RESAMPLE (soxr) or set RESAMPLE16 for fast fixed 16 bits resampling
- use LOOPBACK (mandatory)
- use BYTES_PER_FRAME=4 (8 is not fully functionnal)
- LINKALL (mandatory)
- NO_FAAD unless you want to us faad, which currently overloads the CPU
- TREMOR_ONLY (mandatory)
- When initially cloning the repo, make sure you do it recursively. For example:
- git clone --recursive https://github.com/sle118/squeezelite-esp32.git
- 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
- 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 ...
# 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 :-)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

47
buildFirmware.sh Executable file
View File

@@ -0,0 +1,47 @@
#!/bin/bash
echo "Build process started"
echo "Setting up build name and build number"
if [ -z "${TARGET_BUILD_NAME}" ]
then
export TARGET_BUILD_NAME="I2S-4MFlash"
echo "TARGET_BUILD_NAME is not set. Defaulting to ${TARGET_BUILD_NAME}"
fi
if [ -z "${BUILD_NUMBER}" ]
then
export BUILD_NUMBER="500"
echo "BUILD_NUMBER is not set. Defaulting to ${BUILD_NUMBER}"
fi
if [ -z "$DEPTH" ]
then
export DEPTH="16"
echo "DEPTH is not set. Defaulting to ${DEPTH}"
fi
if [ -z "$tag" ]
then
branch_name="$(git rev-parse --abbrev-ref HEAD)"
branch_name="${branch_name//[^a-zA-Z0-9\-~!@_\.]/}"
app_name="${TARGET_BUILD_NAME}.${DEPTH}.dev-$(git log --pretty=format:'%h' --max-count=1).${branch_name}"
echo "${app_name}">version.txt
echo "app_name is not set. Defaulting to ${app_name}"
else
echo "${tag}" >version.txt
fi
echo "Copying target sdkconfig"
cp build-scripts/${TARGET_BUILD_NAME}-sdkconfig.defaults sdkconfig
echo "Building project"
idf.py build -DDEPTH=${DEPTH} -DBUILD_NUMBER=${BUILD_NUMBER}-${DEPTH}
echo "Generating size report"
idf.py size-components >build/size_components.txt
idf.py size-components-squeezelite build/size_components_squeezelite.txt
if [ -z "${artifact_file_name}" ]
then
echo "No artifact file name set. Will not generate zip file."
else
echo "Generating build artifact zip file"
zip -r build_output.zip build
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

View File

@@ -0,0 +1,21 @@
set(lib_dir ${build_dir}/esp-idf)
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
)
# CMake is just a pile of crap
message("!! overriding ${driver} !!")
message("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
)

1206
components/_override/i2s.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,275 @@
// 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

@@ -0,0 +1,849 @@
// 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,24 +1,28 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS . ./inc inc/alac inc/FLAC inc/helix-aac inc/mad inc/ogg inc/opus inc/opusfile inc/resample16 inc/soxr inc/vorbis
PRIV_REQUIRES newlib
idf_component_register(
INCLUDE_DIRS . ./inc inc/alac inc/FLAC inc/helix-aac inc/mad inc/ogg inc/opus inc/opusfile inc/resample16 inc/soxr inc/vorbis
)
if (DEFINED AAC_DISABLE_SBR)
add_prebuilt_library(libhelix-aac lib/libhelix-aac.a )
else ()
add_prebuilt_library(libhelix-aac lib/libhelix-aac-sbr.a )
endif()
add_prebuilt_library(libmad lib/libmad.a)
add_prebuilt_library(libFLAC lib/libFLAC.a )
add_prebuilt_library(libhelix-aac lib/libhelix-aac.a )
add_prebuilt_library(libvorbisidec lib/libvorbisidec.a )
add_prebuilt_library(libogg lib/libogg.a )
add_prebuilt_library(libalac lib/libalac.a )
add_prebuilt_library(libremple16 lib/libresample16.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} PRIVATE libmad)
target_link_libraries(${COMPONENT_LIB} PRIVATE libFLAC)
target_link_libraries(${COMPONENT_LIB} PRIVATE libhelix-aac)
target_link_libraries(${COMPONENT_LIB} PRIVATE libvorbisidec)
target_link_libraries(${COMPONENT_LIB} PRIVATE libogg)
target_link_libraries(${COMPONENT_LIB} PRIVATE libalac)
target_link_libraries(${COMPONENT_LIB} PRIVATE libremple16)
target_link_libraries(${COMPONENT_LIB} PRIVATE libopusfile)
target_link_libraries(${COMPONENT_LIB} PRIVATE libopus)
target_link_libraries(${COMPONENT_LIB} INTERFACE libmad)
target_link_libraries(${COMPONENT_LIB} INTERFACE libFLAC)
target_link_libraries(${COMPONENT_LIB} INTERFACE libhelix-aac)
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

@@ -19,7 +19,7 @@ extern "C" {
struct alac_codec_s *alac_create_decoder(int magic_cookie_size, unsigned char *magic_cookie,
unsigned char *sample_size, unsigned *sample_rate,
unsigned char *channels);
unsigned char *channels, unsigned int *block_size);
void alac_delete_decoder(struct alac_codec_s *codec);
bool alac_to_pcm(struct alac_codec_s *codec, unsigned char* input,
unsigned char *output, char channels, unsigned *out_frames);

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,3 +0,0 @@
void dummy_obj() {
return;
}

View File

@@ -1,11 +1,16 @@
# the JPEG library is in ROM but seems to fail randomly (PSRAM issue?)
set(TJPGD tjpgd)
idf_component_register(SRC_DIRS . core core/ifaces fonts
INCLUDE_DIRS . fonts core
REQUIRES platform_config tools esp_common
PRIV_REQUIRES services freertos driver
)
PRIV_REQUIRES services freertos driver ${TJPGD}
EMBED_FILES note.jpg )
if (NOT TJPGD)
add_compile_definitions(TJPGD_ROM)
endif()
set_source_files_properties(display.c
PROPERTIES COMPILE_FLAGS
-Wno-format-overflow
)
-Wno-format-overflow )

View File

@@ -1,6 +1,7 @@
/**
* Copyright (c) 2017-2018 Tara Keeling
* 2020 Philippe G.
* 2021 Mumpf and Harry1999
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
@@ -10,356 +11,337 @@
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.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 SHADOW_BUFFER
#define USE_IRAM
#define PAGE_BLOCK 2048
#define ENABLE_WRITE 0x2c
#define MADCTL_MX 0x40
#define TFT_RGB_BGR 0x08
#define min(a,b) (((a) < (b)) ? (a) : (b))
static char TAG[] = "ILI9341";
#define L1_CMD_NOP 0X00
#define L1_CMD_SOFTWARE_RESET 0X01
#define L1_CMD_READ_DISPLAY_IDENTIFICATION_INFORMATION 0X04
#define L1_CMD_READ_DISPLAY_STATUS 0X09
#define L1_CMD_READ_DISPLAY_POWER_MODE 0X0A
#define L1_CMD_READ_DISPLAY_MADCTL 0X0B
#define L1_CMD_READ_DISPLAY_PIXEL_FORMAT 0X0C
#define L1_CMD_READ_DISPLAY_IMAGE_FORMAT 0X0D
#define L1_CMD_READ_DISPLAY_SIGNAL_MODE 0X0E
#define L1_CMD_READ_DISPLAY_SELF_DIAGNOSTIC_RESULT 0X0F
#define L1_CMD_ENTER_SLEEP_MODE 0X10
#define L1_CMD_SLEEP_OUT 0X11
#define L1_CMD_PARTIAL_MODE_ON 0X12
#define L1_CMD_NORMAL_DISPLAY_MODE_ON 0X13
#define L1_CMD_DISPLAY_INVERSION_OFF 0X20
#define L1_CMD_DISPLAY_INVERSION_ON 0X21
#define L1_CMD_GAMMA_SET 0X26
#define L1_CMD_DISPLAY_OFF 0X28
#define L1_CMD_DISPLAY_ON 0X29
#define L1_CMD_COLUMN_ADDRESS_SET 0X2A
#define L1_CMD_PAGE_ADDRESS_SET 0X2B
#define L1_CMD_MEMORY_WRITE 0X2C
#define L1_CMD_COLOR_SET 0X2D
#define L1_CMD_MEMORY_READ 0X2E
#define L1_CMD_PARTIAL_AREA 0X30
#define L1_CMD_VERTICAL_SCROLLING_DEFINITION 0X33
#define L1_CMD_TEARING_EFFECT_LINE_OFF 0X34
#define L1_CMD_TEARING_EFFECT_LINE_ON 0X35
#define L1_CMD_MEMORY_ACCESS_CONTROL 0X36
#define L1_CMD_VERTICAL_SCROLLING_START_ADDRESS 0X37
#define L1_CMD_IDLE_MODE_OFF 0X38
#define L1_CMD_IDLE_MODE_ON 0X39
#define L1_CMD_COLMOD_PIXEL_FORMAT_SET 0X3A
#define L1_CMD_WRITE_MEMORY_CONTINUE 0X3C
#define L1_CMD_READ_MEMORY_CONTINUE 0X3E
#define L1_CMD_SET_TEAR_SCANLINE 0X44
#define L1_CMD_GET_SCANLINE 0X45
#define L1_CMD_WRITE_DISPLAY_BRIGHTNESS 0X51
#define L1_CMD_READ_DISPLAY_BRIGHTNESS 0X52
#define L1_CMD_WRITE_CTRL_DISPLAY 0X53
#define L1_CMD_READ_CTRL_DISPLAY 0X54
#define L1_CMD_WRITE_CONTENT_ADAPTIVE_BRIGHTNESS_CONTROL 0X55
#define L1_CMD_READ_CONTENT_ADAPTIVE_BRIGHTNESS_CONTROL 0X56
#define L1_CMD_WRITE_CABC_MINIMUM_BRIGHTNESS 0X5E
#define L1_CMD_READ_CABC_MINIMUM_BRIGHTNESS 0X5F
#define L1_CMD_READ_ID1 0XDA
#define L1_CMD_READ_ID2 0XDB
#define L1_CMD_READ_ID3 0XDC
#define L2_CMD_RGB_INTERFACE_SIGNAL_CONTROL 0XB0
#define L2_CMD_FRAME_RATE_CONTROL_IN_NORMAL_MODE_FULL_COLORS 0XB1
#define L2_CMD_FRAME_RATE_CONTROL_IN_IDLE_MODE_8_COLORS 0XB2
#define L2_CMD_FRAME_RATE_CONTROL_IN_PARTIAL_MODE_FULL_COLORS 0XB3
#define L2_CMD_DISPLAY_INVERSION_CONTROL 0XB4
#define L2_CMD_BLANKING_PORCH_CONTROL 0XB5
#define L2_CMD_DISPLAY_FUNCTION_CONTROL 0XB6
#define L2_CMD_ENTRY_MODE_SET 0XB7
#define L2_CMD_BACKLIGHT_CONTROL_1 0XB8
#define L2_CMD_BACKLIGHT_CONTROL_2 0XB9
#define L2_CMD_BACKLIGHT_CONTROL_3 0XBA
#define L2_CMD_BACKLIGHT_CONTROL_4 0XBB
#define L2_CMD_BACKLIGHT_CONTROL_5 0XBC
#define L2_CMD_BACKLIGHT_CONTROL_7 0XBE
#define L2_CMD_BACKLIGHT_CONTROL_8 0XBF
#define L2_CMD_POWER_CONTROL_1 0XC0
#define L2_CMD_POWER_CONTROL_2 0XC1
#define L2_CMD_VCOM_CONTROL_1 0XC5
#define L2_CMD_VCOM_CONTROL_2 0XC7
#define L2_CMD_NV_MEMORY_WRITE 0XD0
#define L2_CMD_NV_MEMORY_PROTECTION_KEY 0XD1
#define L2_CMD_NV_MEMORY_STATUS_READ 0XD2
#define L2_CMD_READ_ID4 0XD3
#define L2_CMD_POSITIVE_GAMMA_CORRECTION 0XE0
#define L2_CMD_NEGATIVE_GAMMA_CORRECTION 0XE1
#define L2_CMD_DIGITAL_GAMMA_CONTROL_1 0XE2
#define L2_CMD_DIGITAL_GAMMA_CONTROL_2 0XE3
#define L2_CMD_INTERFACE_CONTROL 0XF6
/*
The LCD needs a bunch of command/argument values to be initialized. They are stored in this struct.
*/
typedef struct {
uint8_t cmd;
uint8_t data[16];
uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds.
} lcd_init_cmd_t;
static const lcd_init_cmd_t ili_init_cmds[]={
/* Power contorl B, power control = 0, DC_ENA = 1 */
{0xCF, {0x00, 0x83, 0X30}, 3},
/* Power on sequence control,
* cp1 keeps 1 frame, 1st frame enable
* vcl = 0, ddvdh=3, vgh=1, vgl=2
* DDVDH_ENH=1
*/
{0xED, {0x64, 0x03, 0X12, 0X81}, 4},
/* Driver timing control A,
* non-overlap=default +1
* EQ=default - 1, CR=default
* pre-charge=default - 1
*/
{0xE8, {0x85, 0x01, 0x79}, 3},
/* Power control A, Vcore=1.6V, DDVDH=5.6V */
{0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02}, 5},
/* Pump ratio control, DDVDH=2xVCl */
{0xF7, {0x20}, 1},
/* Driver timing control, all=0 unit */
{0xEA, {0x00, 0x00}, 2},
/* Power control 1, GVDD=4.75V */
{0xC0, {0x26}, 1},
/* Power control 2, DDVDH=VCl*2, VGH=VCl*7, VGL=-VCl*3 */
{0xC1, {0x11}, 1},
/* VCOM control 1, VCOMH=4.025V, VCOML=-0.950V */
{0xC5, {0x35, 0x3E}, 2},
/* VCOM control 2, VCOMH=VMH-2, VCOML=VML-2 */
{0xC7, {0xBE}, 1},
/* Memory access contorl, MX=MY=0, MV=1, ML=0, BGR=1, MH=0 */
{0x36, {0x28}, 1},
/* Pixel format, 16bits/pixel for RGB/MCU interface */
{0x3A, {0x55}, 1},
/* Frame rate control, f=fosc, 70Hz fps */
{0xB1, {0x00, 0x1B}, 2},
/* Enable 3G, disabled */
{0xF2, {0x08}, 1},
/* Gamma set, curve 1 */
{0x26, {0x01}, 1},
/* Positive gamma correction */
{0xE0, {0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0X87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00}, 15},
/* Negative gamma correction */
{0XE1, {0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F}, 15},
/* Column address set, SC=0, EC=0xEF */
{0x2A, {0x00, 0x00, 0x00, 0xEF}, 4},
/* Page address set, SP=0, EP=0x013F */
{0x2B, {0x00, 0x00, 0x01, 0x3f}, 4},
/* Memory write */
{0x2C, {0}, 0},
/* Entry mode set, Low vol detect disabled, normal display */
{0xB7, {0x07}, 1},
/* Display function control */
{0xB6, {0x0A, 0x82, 0x27, 0x00}, 4},
/* Sleep out */
{0x11, {0}, 0x80},
/* Display on */
{0x29, {0}, 0x80},
{0, {0}, 0xff},
};
//To speed up transfers, every SPI transfer sends a bunch of lines. This define specifies how many. More means more memory use,
//but less overhead for setting up / finishing transfers. Make sure 240 is dividable by this.
#define PARALLEL_LINES 16
enum { ILI9341, ILI9341_24 }; //ILI9341_24 for future use...
struct PrivateSpace {
uint8_t *iRAM, *Shadowbuffer;
uint8_t ReMap, PageSize;
uint8_t Offset;
struct {
uint16_t Height, Width;
} Offset;
uint8_t MADCtl, PageSize;
uint8_t Model;
};
// Functions are not declared to minimize # of lines
static void WriteDataByte( struct GDS_Device* Device, uint8_t Data ) {
Device->WriteData( Device, &Data, 1);
static void WriteByte( struct GDS_Device* Device, uint8_t Data ) {
Device->WriteData( Device, &Data, 1 );
}
static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
Device->WriteCommand( Device, L1_CMD_COLUMN_ADDRESS_SET );
Device->WriteData( Device, &Start, 1 );
Device->WriteData( Device, &End, 1 );
}
static void SetRowAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
Device->WriteCommand( Device, L1_CMD_PAGE_ADDRESS_SET );
Device->WriteData( Device, &Start, 1 );
Device->WriteData( Device, &End, 1 );
static void SetColumnAddress( struct GDS_Device* Device, uint16_t Start, uint16_t End ) {
uint32_t Addr = __builtin_bswap16(Start) | (__builtin_bswap16(End) << 16);
Device->WriteCommand( Device, 0x2A );
Device->WriteData( Device, (uint8_t*) &Addr, 4 );
}
static void SetRowAddress( struct GDS_Device* Device, uint16_t Start, uint16_t End ) {
uint32_t Addr = __builtin_bswap16(Start) | (__builtin_bswap16(End) << 16);
Device->WriteCommand( Device, 0x2B );
Device->WriteData( Device, (uint8_t*) &Addr, 4 );
}
static void Update( struct GDS_Device* Device ) {
static void Update16( struct GDS_Device* Device ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
//SetColumnAddress( Device, Private->Offset, Private->Offset + Device->Width / 4 - 1);
SetColumnAddress( Device, Private->Offset, Private->Offset + Device->Width - 1);
#ifdef SHADOW_BUFFER
uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer;
bool dirty = false;
uint32_t *optr = (uint32_t*) Private->Shadowbuffer, *iptr = (uint32_t*) Device->Framebuffer;
int FirstCol = Device->Width / 2, LastCol = 0, FirstRow = -1, LastRow = 0;
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;) {
for (int r = 0; r < Device->Height; r++) {
// look for change and update shadow (cheap optimization = width is always a multiple of 2)
for (int c = 0; c < Device->Width / 2; c++, iptr++, optr++) {
if (*optr != *iptr) {
dirty = true;
*optr = *iptr;
if (c < FirstCol) FirstCol = c;
if (c > LastCol) LastCol = c;
if (FirstRow < 0) FirstRow = r;
LastRow = r;
}
iptr++; optr++;
}
// wait for a large enough window - careful that window size might increase by more than a line at once !
if (FirstRow < 0 || ((LastCol - FirstCol + 1) * (r - FirstRow + 1) * 4 < PAGE_BLOCK && r != Device->Height - 1)) continue;
// one line done, check for page boundary
if (++page == Private->PageSize) {
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 );
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;
}
page = 0;
FirstCol *= 2;
LastCol = LastCol * 2 + 1;
SetRowAddress( Device, FirstRow + Private->Offset.Height, LastRow + Private->Offset.Height);
SetColumnAddress( Device, FirstCol + Private->Offset.Width, LastCol + Private->Offset.Width );
Device->WriteCommand( Device, ENABLE_WRITE );
int ChunkSize = (LastCol - FirstCol + 1) * 2;
// own use of IRAM has not proven to be much better than letting SPI do its copy
if (Private->iRAM) {
uint8_t *optr = Private->iRAM;
for (int i = FirstRow; i <= LastRow; i++) {
memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize);
optr += ChunkSize;
if (optr - Private->iRAM <= (PAGE_BLOCK - ChunkSize) && i < LastRow) continue;
Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
optr = Private->iRAM;
}
} else for (int i = FirstRow; i <= LastRow; i++) {
Device->WriteData( Device, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize );
}
FirstCol = Device->Width / 2; LastCol = 0;
FirstRow = -1;
}
#else
for (int r = 0; r < Device->Height; r += Private->PageSize) {
SetRowAddress( Device, r, r + Private->PageSize - 1 );
Device->WriteCommand( Device, L1_CMD_MEMORY_WRITE );
// always update by full lines
SetColumnAddress( Device, Private->Offset.Width, Device->Width - 1);
for (int r = 0; r < Device->Height; r += min(Private->PageSize, Device->Height - r)) {
int Height = min(Private->PageSize, Device->Height - r);
SetRowAddress( Device, Private->Offset.Height + r, Private->Offset.Height + r + Height - 1 );
Device->WriteCommand(Device, ENABLE_WRITE);
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 );
memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 );
Device->WriteData( Device, Private->iRAM, Height * Device->Width * 2 );
} else {
Device->WriteData( Device, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 );
}
}
#endif
}
//Bit Name Description
//--- --------------------------- ------------------------------------------------------
//MY Row Address Order MCU to memory write/read direction.
//MX Column Address Order MCU to memory write/read direction.
//MV Row / Column Exchange MCU to memory write/read direction.
//ML Vertical Refresh Order LCD vertical refresh direction control.
//BGR RGB-BGR Order Color selector switch control
// (0=RGB color filter panel, 1=BGR color filter panel)
//MH Horizontal Refresh ORDER LCD horizontal refreshing direction control.
// Bits 17-0
// XX XX XX XX XX XX XX XX XX XX MY MX MV ML BGR MH 0 0
typedef enum {
MAC_BIT_MH=2,
MAC_BIT_BGR,
MAC_BIT_ML,
MAC_BIT_MV,
MAC_BIT_MX,
MAC_BIT_MY,
} mac_bits;
uint16_t set_mac_bit(mac_bits bit, uint16_t val){
return (1 << bit) | val;
}
uint16_t unset_mac_bit(mac_bits bit, uint16_t val){
return ~(1 << bit) & val;
}
static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) {
static void Update24( struct GDS_Device* Device ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
Private->ReMap = HFlip ? (Private->ReMap & ~(1 << MAC_BIT_MX)) : (Private->ReMap | (1 << MAC_BIT_MX));
Private->ReMap = VFlip ? (Private->ReMap | (1 << MAC_BIT_MY)) : (Private->ReMap & ~(1 << MAC_BIT_MY));
Device->WriteCommand( Device, L1_CMD_MEMORY_ACCESS_CONTROL );
Device->WriteData( Device, &Private->ReMap, 1 );
WriteDataByte(Device,0x00);
#ifdef SHADOW_BUFFER
uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer;
int FirstCol = (Device->Width * 3) / 2, LastCol = 0, FirstRow = -1, LastRow = 0;
for (int r = 0; r < Device->Height; r++) {
// look for change and update shadow (cheap optimization = width always / by 2)
for (int c = 0; c < (Device->Width * 3) / 2; c++, optr++, iptr++) {
if (*optr != *iptr) {
*optr = *iptr;
if (c < FirstCol) FirstCol = c;
if (c > LastCol) LastCol = c;
if (FirstRow < 0) FirstRow = r;
LastRow = r;
}
}
// do we have enough to send (cols are divided by 3/2)
if (FirstRow < 0 || ((((LastCol - FirstCol + 1) * 2 ) / 3) * (r - FirstRow + 1) * 4 < PAGE_BLOCK && r != Device->Height - 1)) continue;
FirstCol = (FirstCol * 2) / 3;
LastCol = (LastCol * 2 + 1 ) / 3;
SetRowAddress( Device, FirstRow + Private->Offset.Height, LastRow + Private->Offset.Height);
SetColumnAddress( Device, FirstCol + Private->Offset.Width, LastCol + Private->Offset.Width );
Device->WriteCommand( Device, ENABLE_WRITE );
int ChunkSize = (LastCol - FirstCol + 1) * 3;
// own use of IRAM has not proven to be much better than letting SPI do its copy
if (Private->iRAM) {
uint8_t *optr = Private->iRAM;
for (int i = FirstRow; i <= LastRow; i++) {
memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize);
optr += ChunkSize;
if (optr - Private->iRAM <= (PAGE_BLOCK - ChunkSize) && i < LastRow) continue;
Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
optr = Private->iRAM;
}
} else for (int i = FirstRow; i <= LastRow; i++) {
Device->WriteData( Device, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize );
}
FirstCol = (Device->Width * 3) / 2; LastCol = 0;
FirstRow = -1;
}
#else
// always update by full lines
SetColumnAddress( Device, Private->Offset.Width, Device->Width - 1);
for (int r = 0; r < Device->Height; r += min(Private->PageSize, Device->Height - r)) {
int Height = min(Private->PageSize, Device->Height - r);
SetRowAddress( Device, Private->Offset.Height + r, Private->Offset.Height + r + Height - 1 );
Device->WriteCommand(Device, ENABLE_WRITE);
if (Private->iRAM) {
memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width * 3, Height * Device->Width * 3 );
Device->WriteData( Device, Private->iRAM, Height * Device->Width * 3 );
} else {
Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 3, Height * Device->Width * 3 );
}
}
#endif
}
static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
ESP_LOGI(TAG, "SetLayout 197 HFlip=%d VFlip=%d Rotate=%d (1=true)", Layout->HFlip, Layout->VFlip, Layout->Rotate);
// D/CX RDX WRX D17-8 D7 D6 D5 D4 D3 D2 D1 D0 HEX
//Command 0 1 ↑ XX 0 0 1 1 0 1 1 0 36h
//Parameter 1 1 ↑ XX MY MX MV ML BGR MH 0 0 00
//Orientation 0: MADCtl = 0x80 = 1000 0000 (MY=1)
if ((Device->Height)>(Device->Width)){ //Resolution = 320x240
Private->MADCtl = (1 << 7); // 0x80 = default (no Rotation an no Flip)
if (Layout->HFlip) { //Flip Horizontal
int a = Private->MADCtl;
Private->MADCtl = (a ^ (1 << 7));
}
if (Layout->Rotate) { //Rotate 180 degr.
int a = Private->MADCtl;
a = (a ^ (1 << 7));
Private->MADCtl = (a ^ (1 << 6));
}
if (Layout->VFlip) { //Flip Vertical
int a = Private->MADCtl;
Private->MADCtl = (a ^ (1 << 6));
}
} else { //Resolution = 240x320
Private->MADCtl = (1 << 5); // 0x20 = default (no Rotation an no Flip)
if (Layout->HFlip) { //Flip Horizontal
int a = Private->MADCtl;
Private->MADCtl = (a ^ (1 << 6));
}
if (Layout->Rotate) { //Rotate 180 degr.
int a = Private->MADCtl;
a = (a ^ (1 << 7));
Private->MADCtl = (a ^ (1 << 6));
}
if (Layout->VFlip) { //Flip Vertical
int a = Private->MADCtl;
Private->MADCtl = (a ^ (1 << 7));
}
}
Private->MADCtl = Layout->ColorSwap ? (Private->MADCtl | (1 << 3)) : (Private->MADCtl & ~(1 << 3));
ESP_LOGI(TAG, "SetLayout 255 Private->MADCtl=%hhu", Private->MADCtl);
Device->WriteCommand( Device, 0x36 );
WriteByte( Device, Private->MADCtl );
Device->WriteCommand( Device, Layout->Invert ? 0x21 : 0x20 );
#ifdef SHADOW_BUFFER
// force a full refresh (almost ...)
memset(Private->Shadowbuffer, 0xAA, Device->FramebufferSize);
#endif
}
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, L1_CMD_DISPLAY_ON ); }
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, L1_CMD_DISPLAY_OFF ); }
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0x29 ); } //DISPON =0x29
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0x28 ); } //DISPOFF=0x28
static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
Device->WriteCommand( Device, L1_CMD_WRITE_DISPLAY_BRIGHTNESS );
uint8_t loc_contrast = (uint8_t)((float)Contrast/5.0f* 255.0f);
Device->WriteData( Device, &loc_contrast , 1 );
WriteDataByte(Device,0x00);
Device->WriteCommand( Device, 0x51 );
WriteByte( Device, Contrast );
Device->SetContrast = NULL;
GDS_SetContrast( Device, Contrast );
Device->SetContrast = SetContrast; // 0x00 value means the lowest brightness and 0xFF value means the highest brightness.
}
static bool Init( struct GDS_Device* Device ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
int Depth = (Device->Depth + 8 - 1) / 8;
Private->PageSize = min(8, PAGE_BLOCK / (Device->Width * Depth));
// Private->Offset = (480 - Device->Width) / 4 / 2;
// find a page size that is not too small is an integer of height
Private->PageSize = min(8, PAGE_BLOCK / (Device->Width / 2));
Private->PageSize = Device->Height / (Device->Height / Private->PageSize) ;
#ifdef SHADOW_BUFFER
// Private->Shadowbuffer = malloc( Device->FramebufferSize );
// memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize);
Private->Shadowbuffer = malloc( Device->FramebufferSize );
memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize);
#endif
#ifdef USE_IRAM
Private->iRAM = heap_caps_malloc( (Private->PageSize + 1) * Device->Width * Depth, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
#endif
Private->iRAM =NULL;
//Private->iRAM =heap_caps_malloc(320*PARALLEL_LINES*sizeof(uint16_t), MALLOC_CAP_DMA);
//ESP_LOGI(TAG, "ILI9341 with offset %u, page %u, iRAM %p", Private->Offset, Private->PageSize, Private->iRAM);
ESP_LOGI(TAG, "ILI9341 ");
// need to be off and disable display RAM
Device->DisplayOff( Device );
int cmd=0;
//Send all the commands
while (ili_init_cmds[cmd].databytes!=0xff) {
Device->WriteCommand( Device, ili_init_cmds[cmd].cmd );
Device->WriteData(Device,ili_init_cmds[cmd].data,ili_init_cmds[cmd].databytes&0x1F);
if (ili_init_cmds[cmd].databytes&0x80) {
vTaskDelay(100 / portTICK_RATE_MS);
}
cmd++;
}
ESP_LOGI(TAG, "ILI9341 with bit default-depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM);
// Sleepout + Booster
Device->WriteCommand( Device, 0x11 );
// set flip modes & contrast
GDS_SetContrast( Device, 0x7f );
struct GDS_Layout Layout = { };
Device->SetLayout( Device, &Layout );
// set screen depth (16/18) *** INTERFACE PIXEL FORMAT: 0x66=18 bit; 0x55=16 bit
Device->WriteCommand( Device, 0x3A );
if (Private->Model == ILI9341_24) WriteByte( Device, Device->Depth == 24 ? 0x66 : 0x55 );
else WriteByte( Device, Device->Depth == 24 ? 0x66 : 0x55 );
ESP_LOGI(TAG, "ILI9341_Init 312 device-depth %u, 0x66/0x55=0x%X", Device->Depth, Device->Depth == 24 ? 0x66 : 0x55);
// no Display Inversion (INVOFF=0x20 INVON=0x21)
Device->WriteCommand( Device, 0x20 );
//Gamma Correction: Enable next two line and enabel one of the Test0x Section... or build you own 15 Parameter...
Device->WriteCommand( Device, 0xF2 ); WriteByte( Device, 0x03 ); // 3Gamma Function: Disable = default (0x02), Enable (0x03)
Device->WriteCommand( Device, 0x26 ); WriteByte( Device, 0x01 ); // Gamma curve selected (0x01, 0x02, 0x04, 0x08) - A maximum of 4 fixed gamma curves can be selected
//Gamma Correction Test01
Device->WriteCommand( Device, 0xE0 ); // Positive Gamma Correction (15 Parameter)
WriteByte( Device, 0x0F ); WriteByte( Device, 0x31 ); WriteByte( Device, 0x2B ); WriteByte( Device, 0x0C ); WriteByte( Device, 0x0E );
WriteByte( Device, 0x08 ); WriteByte( Device, 0x4E ); WriteByte( Device, 0xF1 ); WriteByte( Device, 0x37 ); WriteByte( Device, 0x07 );
WriteByte( Device, 0x10 ); WriteByte( Device, 0x03 ); WriteByte( Device, 0x0E ); WriteByte( Device, 0x09 ); WriteByte( Device, 0x00 );
Device->WriteCommand( Device, 0xE1 ); // Negative Gamma Correction (15 Parameter)
WriteByte( Device, 0x00 ); WriteByte( Device, 0x0E ); WriteByte( Device, 0x14 ); WriteByte( Device, 0x03 ); WriteByte( Device, 0x11 );
WriteByte( Device, 0x07 ); WriteByte( Device, 0x31 ); WriteByte( Device, 0xC1 ); WriteByte( Device, 0x48 ); WriteByte( Device, 0x08 );
WriteByte( Device, 0x0F ); WriteByte( Device, 0x0C ); WriteByte( Device, 0x31 ); WriteByte( Device, 0x36 ); WriteByte( Device, 0x0F );
// gone with the wind
Device->DisplayOn( Device );
Device->Update( Device );
return true;
}
static const struct GDS_Device ILI9341 = {
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
static const struct GDS_Device ILI9341_X = {
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff,
.SetLayout = SetLayout,
.Update = Update, .Init = Init,
};
.Update = Update16, .Init = Init,
.Mode = GDS_RGB565, .Depth = 16,
};
struct GDS_Device* ILI9341_Detect(char *Driver, struct GDS_Device* Device) {
if (!strcasestr(Driver, "ILI9341")) return NULL;
uint8_t Model;
int Depth=16; // 16bit colordepth
if (strcasestr(Driver, "ILI9341")) Model = ILI9341;
else if (strcasestr(Driver, "ILI9341_24")) Model = ILI9341_24; //for future use...
else return NULL;
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
*Device = ILI9341;
Device->Depth = 4;
*Device = ILI9341_X;
sscanf(Driver, "%*[^:]:%u", &Depth); // NVS-Parameter driver=ILI9341[:16|18]
struct PrivateSpace* Private = (struct PrivateSpace*) Device->Private;
Private->Model = Model;
ESP_LOGI(TAG, "ILI9341_Detect 391 Driver= %s Depth=%d", Driver, Depth);
if (Depth == 18) {
Device->Mode = GDS_RGB888;
Device->Depth = 24;
Device->Update = Update24;
}
if (Model == ILI9341_24) Device->SetContrast = SetContrast;
return Device;
}
}

View File

@@ -73,9 +73,10 @@ static void Update( struct GDS_Device* Device ) {
#endif
}
static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) {
Device->WriteCommand( Device, HFlip ? 0xA1 : 0xA0 );
Device->WriteCommand( Device, VFlip ? 0xC8 : 0xC0 );
static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
Device->WriteCommand( Device, Layout->HFlip ? 0xA1 : 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 ); }
@@ -86,6 +87,10 @@ static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
Device->WriteCommand( Device, Contrast );
}
static void SPIParams(int Speed, uint8_t *mode, uint16_t *CS_pre, uint8_t *CS_post) {
*CS_post = Speed / (8*1000*1000);
}
static bool Init( struct GDS_Device* Device ) {
#ifdef SHADOW_BUFFER
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
@@ -120,7 +125,8 @@ static bool Init( struct GDS_Device* Device ) {
Device->WriteCommand( Device, 0x40 + 0x00 );
Device->SetContrast( Device, 0x7F );
// set flip modes
Device->SetLayout( Device, false, false, false );
struct GDS_Layout Layout = { };
Device->SetLayout( Device, &Layout );
// no Display Inversion
Device->WriteCommand( Device, 0xA6 );
// set Clocks
@@ -140,6 +146,7 @@ static const struct GDS_Device SH1106 = {
.SetLayout = SetLayout,
.Update = Update, .Init = Init,
.Depth = 1,
.SPIParams = SPIParams,
#if !defined SHADOW_BUFFER && defined USE_IRAM
.Alloc = GDS_ALLOC_IRAM_SPI;
#endif

View File

@@ -85,9 +85,10 @@ static void Update( struct GDS_Device* Device ) {
#endif
}
static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) {
Device->WriteCommand( Device, HFlip ? 0xA1 : 0xA0 );
Device->WriteCommand( Device, VFlip ? 0xC8 : 0xC0 );
static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
Device->WriteCommand( Device, Layout->HFlip ? 0xA1 : 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 ); }
@@ -132,7 +133,8 @@ static bool Init( struct GDS_Device* Device ) {
Device->WriteCommand( Device, 0x40 + 0x00 );
Device->SetContrast( Device, 0x7F );
// set flip modes
Device->SetLayout( Device, false, false, false);
struct GDS_Layout Layout = { };
Device->SetLayout( Device, &Layout );
// no Display Inversion
Device->WriteCommand( Device, 0xA6 );
// set Clocks

View File

@@ -96,13 +96,14 @@ static void Update( struct GDS_Device* Device ) {
#endif
}
static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) {
static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
Private->ReMap = HFlip ? (Private->ReMap & ~(1 << 1)) : (Private->ReMap | (1 << 1));
Private->ReMap = VFlip ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
Private->ReMap = Layout->HFlip ? (Private->ReMap & ~(1 << 1)) : (Private->ReMap | (1 << 1));
Private->ReMap = Layout->VFlip ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
Device->WriteCommand( Device, 0xA0 );
Device->WriteData( Device, &Private->ReMap, 1 );
WriteDataByte( Device, 0x11 );
Device->WriteCommand( Device, Layout->Invert ? 0xA7 : 0xA6 );
}
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
@@ -145,25 +146,31 @@ static bool Init( struct GDS_Device* Device ) {
// set flip modes
Private->ReMap = 0;
Device->SetLayout( Device, false, false, false);
struct GDS_Layout Layout = { };
Device->SetLayout( Device, &Layout );
// set Display Enhancement
Device->WriteCommand( Device, 0xB4 );
WriteDataByte( Device, 0xA0 );
WriteDataByte( Device, 0xB5 );
// set Clocks
Device->WriteCommand( Device, 0xB3 );
WriteDataByte( Device, 0x91 );
WriteDataByte( Device, 0xB2 ); // 0x91 seems to be common but is too slow for 5.5'
// set MUX
Device->WriteCommand( Device, 0xCA );
WriteDataByte( Device, Device->Height - 1 );
// phase 1 & 2 period (needed?)
// phase 1 & 2 period
Device->WriteCommand( Device, 0xB1 );
WriteDataByte( Device, 0xE2 );
WriteDataByte( Device, 0xE3 ); // 0xE2 was recommended
// set pre-charge V (needed?°)
// set pre-charge V
Device->WriteCommand( Device, 0xBB );
WriteDataByte( Device, 0x1F );
WriteDataByte( Device, 0x0F); // 0x1F causes column interferences
// set COM deselect voltage (needed?)
// set COM deselect voltage
Device->WriteCommand( Device, 0xBE );
WriteDataByte( Device, 0x07 );

View File

@@ -222,17 +222,18 @@ static void DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, i
}
}
static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) {
static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
if (Private->Model == SSD1326) {
Private->ReMap = HFlip ? (Private->ReMap | ((1 << 0) | (1 << 2))) : (Private->ReMap & ~((1 << 0) | (1 << 2)));
Private->ReMap = HFlip ? (Private->ReMap | (1 << 1)) : (Private->ReMap & ~(1 << 1));
Private->ReMap = Layout->HFlip ? (Private->ReMap | ((1 << 0) | (1 << 2))) : (Private->ReMap & ~((1 << 0) | (1 << 2)));
Private->ReMap = Layout->HFlip ? (Private->ReMap | (1 << 1)) : (Private->ReMap & ~(1 << 1));
} else {
Private->ReMap = VFlip ? (Private->ReMap | ((1 << 0) | (1 << 1))) : (Private->ReMap & ~((1 << 0) | (1 << 1)));
Private->ReMap = VFlip ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
Private->ReMap = Layout->VFlip ? (Private->ReMap | ((1 << 0) | (1 << 1))) : (Private->ReMap & ~((1 << 0) | (1 << 1)));
Private->ReMap = Layout->VFlip ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
}
Device->WriteCommand( Device, 0xA0 );
Device->WriteCommand( Device, Private->ReMap );
Device->WriteCommand( Device, Layout->Invert ? 0xA7 : 0xA6 );
}
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
@@ -288,7 +289,8 @@ static bool Init( struct GDS_Device* Device ) {
Device->WriteCommand( Device, 0x00 );
Device->SetContrast( Device, 0x7F );
// set flip modes
Device->SetLayout( Device, false, false, false );
struct GDS_Layout Layout = { };
Device->SetLayout( Device, &Layout );
// no Display Inversion
Device->WriteCommand( Device, 0xA6 );
// set Clocks

View File

@@ -181,12 +181,13 @@ static void Update24( struct GDS_Device* Device ) {
#endif
}
static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) {
static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
Private->ReMap = HFlip ? (Private->ReMap & ~(1 << 1)) : (Private->ReMap | (1 << 1));
Private->ReMap = VFlip ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
Private->ReMap = Layout->HFlip ? (Private->ReMap & ~(1 << 1)) : (Private->ReMap | (1 << 1));
Private->ReMap = Layout->VFlip ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
Device->WriteCommand( Device, 0xA0 );
WriteByte( Device, Private->ReMap );
Device->WriteCommand( Device, Layout->Invert ? 0xA7 : 0xA6 );
}
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
@@ -239,7 +240,8 @@ static bool Init( struct GDS_Device* Device ) {
// set flip modes & contrast
Device->SetContrast( Device, 0x7F );
Device->SetLayout( Device, false, false, false );
struct GDS_Layout Layout = { };
Device->SetLayout( Device, &Layout );
// set Adressing Mode Horizontal
Private->ReMap |= (0 << 2);

View File

@@ -243,7 +243,7 @@ struct GDS_Device* SSD1675_Detect(char *Driver, struct GDS_Device* Device) {
char *p;
struct PrivateSpace* Private = (struct PrivateSpace*) Device->Private;
Private->ReadyPin = -1;
if ((p = strcasestr(Driver, "ready")) != NULL) Private->ReadyPin = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(Driver, "ready")) && (p = strchr(p, '='))) Private->ReadyPin = atoi(p + 1);
ESP_LOGI(TAG, "SSD1675 driver with ready GPIO %d", Private->ReadyPin);

View File

@@ -188,20 +188,24 @@ static void Update24( struct GDS_Device* Device ) {
#endif
}
static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) {
static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
Private->MADCtl = HFlip ? (Private->MADCtl | (1 << 7)) : (Private->MADCtl & ~(1 << 7));
Private->MADCtl = VFlip ? (Private->MADCtl | (1 << 6)) : (Private->MADCtl & ~(1 << 6));
Private->MADCtl = Rotate ? (Private->MADCtl | (1 << 5)) : (Private->MADCtl & ~(1 << 5));
if (Private->Model == ST7789) {
if (Layout->Rotate) Private->Offset.Width += Layout->HFlip ? 320 - Device->Width : 0;
else Private->Offset.Height += Layout->HFlip ? 320 - Device->Height : 0;
Device->WriteCommand( Device, Layout->Invert ? 0x20 : 0x21 );
} else {
Device->WriteCommand( Device, Layout->Invert ? 0x21 : 0x20 );
}
Private->MADCtl = Layout->HFlip ? (Private->MADCtl | (1 << 7)) : (Private->MADCtl & ~(1 << 7));
Private->MADCtl = Layout->VFlip ? (Private->MADCtl | (1 << 6)) : (Private->MADCtl & ~(1 << 6));
Private->MADCtl = Layout->Rotate ? (Private->MADCtl | (1 << 5)) : (Private->MADCtl & ~(1 << 5));
Private->MADCtl = Layout->ColorSwap ? (Private->MADCtl & ~(1 << 3)) : (Private->MADCtl | (1 << 3));
Device->WriteCommand( Device, 0x36 );
WriteByte( Device, Private->MADCtl );
if (Private->Model == ST7789) {
if (Rotate) Private->Offset.Width = HFlip ? 320 - Device->Width : 0;
else Private->Offset.Height = HFlip ? 320 - Device->Height : 0;
}
#ifdef SHADOW_BUFFER
// force a full refresh (almost ...)
@@ -235,28 +239,26 @@ static bool Init( struct GDS_Device* Device ) {
Private->iRAM = heap_caps_malloc( (Private->PageSize + 1) * Device->Width * Depth, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
#endif
ESP_LOGI(TAG, "ST77xx with bit depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM);
ESP_LOGI(TAG, "ST77xx with bit depth %u, offsets %hu:%hu, page %u, iRAM %p", Device->Depth, Private->Offset.Height, Private->Offset.Width, Private->PageSize, Private->iRAM);
// Sleepout + Booster
Device->WriteCommand( Device, 0x11 );
// need BGR & Address Mode
Private->MADCtl = 1 << 3;
Device->WriteCommand( Device, 0x36 );
WriteByte( Device, Private->MADCtl );
// set flip modes & contrast
GDS_SetContrast( Device, 0x7f );
Device->SetLayout( Device, false, false, false );
struct GDS_Layout Layout = { };
Device->SetLayout( Device, &Layout );
// set screen depth (16/18)
Device->WriteCommand( Device, 0x3A );
if (Private->Model == ST7789) WriteByte( Device, Device->Depth == 24 ? 0x066 : 0x55 );
else WriteByte( Device, Device->Depth == 24 ? 0x06 : 0x05 );
// no Display Inversion
Device->WriteCommand( Device, Private->Model == ST7735 ? 0x20 : 0x21 );
// gone with the wind
Device->DisplayOn( Device );
Device->Update( Device );
@@ -283,8 +285,14 @@ struct GDS_Device* ST77xx_Detect(char *Driver, struct GDS_Device* Device) {
*Device = ST77xx;
sscanf(Driver, "%*[^:]:%u", &Depth);
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);
}
if (Depth == 18) {
Device->Mode = GDS_RGB666;

View File

@@ -235,12 +235,13 @@ void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
ledc_update_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel );
}
}
void GDS_SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate ) { if (Device->SetLayout) Device->SetLayout( Device, HFlip, VFlip, Rotate ); }
void GDS_SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) { if (Device->SetLayout) Device->SetLayout( Device, Layout ); }
void GDS_SetDirty( struct GDS_Device* Device ) { Device->Dirty = true; }
int GDS_GetWidth( struct GDS_Device* Device ) { return Device->Width; }
int GDS_GetHeight( struct GDS_Device* Device ) { return Device->Height; }
int GDS_GetDepth( struct GDS_Device* Device ) { return Device->Depth; }
int GDS_GetMode( struct GDS_Device* Device ) { return Device->Mode; }
int GDS_GetWidth( struct GDS_Device* Device ) { return Device ? Device->Width : 0; }
void GDS_SetTextWidth( struct GDS_Device* Device, int TextWidth ) { Device->TextWidth = Device && TextWidth && TextWidth < Device->Width ? TextWidth : Device->Width; }
int GDS_GetHeight( struct GDS_Device* Device ) { return Device ? Device->Height : 0; }
int GDS_GetDepth( struct GDS_Device* Device ) { return Device ? Device->Depth : 0; }
int GDS_GetMode( struct GDS_Device* Device ) { return Device ? Device->Mode : 0; }
void GDS_DisplayOn( struct GDS_Device* Device ) { if (Device->DisplayOn) Device->DisplayOn( Device ); }
void GDS_DisplayOff( struct GDS_Device* Device ) { if (Device->DisplayOff) Device->DisplayOff( Device ); }

View File

@@ -26,6 +26,12 @@ struct GDS_BacklightPWM {
int Channel, Timer, Max;
bool Init;
};
struct GDS_Layout {
bool HFlip, VFlip;
bool Rotate;
bool Invert;
bool ColorSwap;
};
typedef struct GDS_Device* GDS_DetectFunc(char *Driver, struct GDS_Device *Device);
@@ -35,9 +41,10 @@ void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast );
void GDS_DisplayOn( struct GDS_Device* Device );
void GDS_DisplayOff( struct GDS_Device* Device );
void GDS_Update( struct GDS_Device* Device );
void GDS_SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate );
void GDS_SetLayout( struct GDS_Device* Device, struct GDS_Layout* Layout);
void GDS_SetDirty( struct GDS_Device* Device );
int GDS_GetWidth( struct GDS_Device* Device );
void GDS_SetTextWidth( struct GDS_Device* Device, int TextWidth );
int GDS_GetHeight( struct GDS_Device* Device );
int GDS_GetDepth( struct GDS_Device* Device );
int GDS_GetMode( struct GDS_Device* Device );

View File

@@ -11,7 +11,7 @@ bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int speed );
bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin, int BacklightPin );
bool GDS_SPIInit( int SPI, int DC );
bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int Speed, int BacklightPin );
bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int Speed, int BacklightPin, int Mode );
#ifdef __cplusplus
}

View File

@@ -73,13 +73,13 @@ void GDS_FontDrawChar( struct GDS_Device* Device, char Character, int x, int y,
CharStartY+= OffsetY;
/* Do not attempt to draw if this character is entirely offscreen */
if ( CharEndX < 0 || CharStartX >= Device->Width || CharEndY < 0 || CharStartY >= Device->Height ) {
if ( CharEndX < 0 || CharStartX >= Device->TextWidth || CharEndY < 0 || CharStartY >= Device->Height ) {
ClipDebug( x, y );
return;
}
/* Do not attempt to draw past the end of the screen */
CharEndX = ( CharEndX >= Device->Width ) ? Device->Width - 1 : CharEndX;
CharEndX = ( CharEndX >= Device->TextWidth ) ? Device->TextWidth - 1 : CharEndX;
CharEndY = ( CharEndY >= Device->Height ) ? Device->Height - 1 : CharEndY;
Device->Dirty = true;
@@ -98,12 +98,14 @@ void GDS_FontDrawChar( struct GDS_Device* Device, char Character, int x, int y,
}
}
bool GDS_SetFont( struct GDS_Device* Display, const struct GDS_FontDef* Font ) {
const struct GDS_FontDef* GDS_SetFont( struct GDS_Device* Display, const struct GDS_FontDef* Font ) {
const struct GDS_FontDef* OldFont = Display->Font;
Display->FontForceProportional = false;
Display->FontForceMonospace = false;
Display->Font = Font;
return true;
return OldFont;
}
void GDS_FontForceProportional( struct GDS_Device* Display, bool Force ) {
@@ -144,7 +146,7 @@ int GDS_FontGetCharWidth( struct GDS_Device* Display, char Character ) {
}
int GDS_FontGetMaxCharsPerRow( struct GDS_Device* Display ) {
return Display->Width / Display->Font->Width;
return Display->TextWidth / Display->Font->Width;
}
int GDS_FontGetMaxCharsPerColumn( struct GDS_Device* Display ) {
@@ -208,7 +210,7 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int
switch ( Anchor ) {
case TextAnchor_East: {
*OutY = ( Display->Height / 2 ) - ( StringHeight / 2 );
*OutX = ( Display->Width - StringWidth );
*OutX = ( Display->TextWidth - StringWidth );
break;
}
@@ -219,19 +221,19 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int
break;
}
case TextAnchor_North: {
*OutX = ( Display->Width / 2 ) - ( StringWidth / 2 );
*OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 );
*OutY = 0;
break;
}
case TextAnchor_South: {
*OutX = ( Display->Width / 2 ) - ( StringWidth / 2 );
*OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 );
*OutY = ( Display->Height - StringHeight );
break;
}
case TextAnchor_NorthEast: {
*OutX = ( Display->Width - StringWidth );
*OutX = ( Display->TextWidth - StringWidth );
*OutY = 0;
break;
@@ -244,7 +246,7 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int
}
case TextAnchor_SouthEast: {
*OutY = ( Display->Height - StringHeight );
*OutX = ( Display->Width - StringWidth );
*OutX = ( Display->TextWidth - StringWidth );
break;
}
@@ -256,7 +258,7 @@ void GDS_FontGetAnchoredStringCoords( struct GDS_Device* Display, int* OutX, int
}
case TextAnchor_Center: {
*OutY = ( Display->Height / 2 ) - ( StringHeight / 2 );
*OutX = ( Display->Width / 2 ) - ( StringWidth / 2 );
*OutX = ( Display->TextWidth / 2 ) - ( StringWidth / 2 );
break;
}

View File

@@ -46,7 +46,7 @@ typedef enum {
TextAnchor_Center
} TextAnchor;
bool GDS_SetFont( struct GDS_Device* Display, const struct GDS_FontDef* Font );
const struct GDS_FontDef* GDS_SetFont( struct GDS_Device* Display, const struct GDS_FontDef* Font );
void GDS_FontForceProportional( struct GDS_Device* Display, bool Force );
void GDS_FontForceMonospace( struct GDS_Device* Display, bool Force );
@@ -59,7 +59,8 @@ int GDS_FontGetMaxCharsPerColumn( struct GDS_Device* Display );
int GDS_FontGetCharWidth( struct GDS_Device* Display, char Character );
int GDS_FontGetCharHeight( struct GDS_Device* Display );
int GDS_FontMeasureString( struct GDS_Device* Display, const char* Text );\
int GDS_FontMeasureString( struct GDS_Device* Display, const char* Text );
int GDS_FontMeasureStringLine( struct GDS_Device* Display, int Line, const char* Text );
void GDS_FontDrawChar( struct GDS_Device* Display, char Character, int x, int y, int Color );
void GDS_FontDrawString( struct GDS_Device* Display, int x, int y, const char* Text, int Color );

View File

@@ -8,14 +8,18 @@
#include <string.h>
#include "math.h"
#ifdef TJPGD_ROM
#include "esp32/rom/tjpgd.h"
#else
#include "tjpgd.h"
#endif
#include "esp_log.h"
#include "gds.h"
#include "gds_private.h"
#include "gds_image.h"
const char TAG[] = "ImageDec";
const static char TAG[] = "ImageDec";
#define SCRATCH_SIZE 3100
@@ -167,7 +171,7 @@ static unsigned OutHandlerDirect(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
static void* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, bool SizeOnly, int RGB_Mode) {
JDEC Decoder;
JpegCtx Context;
char *Scratch = calloc(SCRATCH_SIZE, 1);
char *Scratch = malloc(SCRATCH_SIZE);
if (!Scratch) {
ESP_LOGE(TAG, "Cannot allocate workspace");
@@ -372,7 +376,7 @@ bool GDS_DrawJPEG(struct GDS_Device* Device, uint8_t *Source, int x, int y, int
JDEC Decoder;
JpegCtx Context;
bool Ret = false;
char *Scratch = calloc(SCRATCH_SIZE, 1);
char *Scratch = malloc(SCRATCH_SIZE);
if (!Scratch) {
ESP_LOGE(TAG, "Cannot allocate workspace");

View File

@@ -95,7 +95,7 @@ struct GDS_Device {
const struct GDS_FontDef* Font;
} Lines[MAX_LINES];
uint16_t Width;
uint16_t Width, TextWidth;
uint16_t Height;
uint8_t Depth, Mode;
@@ -117,13 +117,15 @@ struct GDS_Device {
void (*SetContrast)( struct GDS_Device* Device, uint8_t Contrast );
void (*DisplayOn)( struct GDS_Device* Device );
void (*DisplayOff)( struct GDS_Device* Device );
void (*SetLayout)( struct GDS_Device* Device, bool HFlip, bool VFlip, bool Rotate );
void (*SetLayout)( struct GDS_Device* Device, struct GDS_Layout *Layout );
// must provide for depth other than 1 (vertical) and 4 (may provide for optimization)
void (*DrawPixelFast)( struct GDS_Device* Device, int X, int Y, int Color );
void (*DrawBitmapCBR)(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color );
// may provide for optimization
void (*DrawRGB)( struct GDS_Device* Device, uint8_t *Image,int x, int y, int Width, int Height, int RGB_Mode );
void (*ClearWindow)( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color );
// may provide for tweaking
void (*SPIParams)(int Speed, uint8_t *mode, uint16_t *CS_pre, uint8_t *CS_post);
// interface-specific methods
WriteCommandProc WriteCommand;

View File

@@ -26,19 +26,20 @@ static char TAG[] = "gds";
*/
static const struct GDS_FontDef *GuessFont( struct GDS_Device *Device, int FontType) {
switch(FontType) {
case GDS_FONT_DEFAULT:
return Device->Font;
case GDS_FONT_LINE_1:
return &Font_line_1;
case GDS_FONT_LINE_2:
return &Font_line_2;
case GDS_FONT_SMALL:
return &Font_droid_sans_fallback_11x13;
case GDS_FONT_MEDIUM:
default:
return &Font_droid_sans_fallback_15x17;
//return &Font_droid_sans_fallback_15x17;
case GDS_FONT_SMALL:
default:
return &Font_droid_sans_fallback_11x13;
#ifdef USE_LARGE_FONTS
case GDS_FONT_LARGE:
return &Font_droid_sans_fallback_24x28;
break;
case GDS_FONT_SEGMENT:
if (Device->Height == 32) return &Font_Tarable7Seg_16x32;
else return &Font_Tarable7Seg_32x64;
@@ -46,8 +47,8 @@ static const struct GDS_FontDef *GuessFont( struct GDS_Device *Device, int FontT
case GDS_FONT_LARGE:
case GDS_FONT_SEGMENT:
ESP_LOGW(TAG, "large fonts disabled");
return &Font_droid_sans_fallback_15x17;
break;
//return &Font_droid_sans_fallback_15x17;
return &Font_droid_sans_fallback_11x13;
#endif
}
}
@@ -99,13 +100,13 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex
Width = GDS_FontMeasureString( Device, Text );
// adjusting position, erase only EoL for rigth-justified
if (Pos == GDS_TEXT_RIGHT) X = Device->Width - Width - 1;
else if (Pos == GDS_TEXT_CENTER) X = (Device->Width - Width) / 2;
if (Pos == GDS_TEXT_RIGHT) X = Device->TextWidth - Width - 1;
else if (Pos == GDS_TEXT_CENTER) X = (Device->TextWidth - Width) / 2;
// erase if requested
if (Attr & GDS_TEXT_CLEAR) {
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->Width; c++)
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 );
}
@@ -118,7 +119,20 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex
Device->Dirty = true;
if (Attr & GDS_TEXT_UPDATE) GDS_Update( Device );
return Width + X < Device->Width;
return Width + X < Device->TextWidth;
}
/****************************************************************************************
*
*/
int GDS_GetTextWidth(struct GDS_Device* Device, int N, int Attr, char *Text) {
const struct GDS_FontDef *Font = GDS_SetFont( Device, Device->Lines[N-1].Font );
if (Attr & GDS_TEXT_MONOSPACE) GDS_FontForceMonospace( Device, true );
int Width = GDS_FontMeasureString( Device, Text );
GDS_SetFont( Device, Font );
return Width;
}
/****************************************************************************************
@@ -132,7 +146,7 @@ int GDS_TextStretch(struct GDS_Device* Device, int N, char *String, int Max) {
// we might already fit
GDS_SetFont( Device, Device->Lines[N].Font );
if (GDS_FontMeasureString( Device, String ) <= Device->Width) return 0;
if (GDS_FontMeasureString( Device, String ) <= Device->TextWidth) return 0;
// add some space for better visual
strncat(String, Space, Max-Len);
@@ -143,7 +157,7 @@ int GDS_TextStretch(struct GDS_Device* Device, int N, char *String, int Max) {
Boundary = GDS_FontMeasureString( Device, String );
// add a full display width
while (Len < Max && GDS_FontMeasureString( Device, String ) - Boundary < Device->Width) {
while (Len < Max && GDS_FontMeasureString( Device, String ) - Boundary < Device->TextWidth) {
String[Len++] = String[Extra++];
String[Len] = '\0';
}

View File

@@ -31,5 +31,6 @@ struct GDS_Device;
bool GDS_TextSetFontAuto(struct GDS_Device* Device, int N, int FontType, int Space);
bool GDS_TextSetFont(struct GDS_Device* Device, int N, const struct GDS_FontDef *Font, int Space);
bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Text);
int GDS_GetTextWidth(struct GDS_Device* Device, int N, int Attr, char *Text);
int GDS_TextStretch(struct GDS_Device* Device, int N, char *String, int Max);
void GDS_TextPos(struct GDS_Device* Device, int FontType, int Where, int Attr, char *Text, ...);

View File

@@ -75,7 +75,7 @@ bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int
Device->RSTPin = RSTPin;
Device->Backlight.Pin = BacklightPin;
Device->IF = GDS_IF_I2C;
Device->Width = Width;
Device->Width = Device->TextWidth = Width;
Device->Height = Height;
if ( RSTPin >= 0 ) {

View File

@@ -34,8 +34,8 @@ bool GDS_SPIInit( int SPI, int DC ) {
return true;
}
bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int BackLightPin, int Speed ) {
spi_device_interface_config_t SPIDeviceConfig;
bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int BackLightPin, int Speed, int Mode ) {
spi_device_interface_config_t SPIDeviceConfig = { };
spi_device_handle_t SPIDevice;
NullCheck( Device, return false );
@@ -43,15 +43,16 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
if (CSPin >= 0) {
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( CSPin, GPIO_MODE_OUTPUT ), return false );
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( CSPin, 0 ), return false );
}
}
memset( &SPIDeviceConfig, 0, sizeof( spi_device_interface_config_t ) );
SPIDeviceConfig.clock_speed_hz = Speed > 0 ? Speed : SPI_MASTER_FREQ_8M;
SPIDeviceConfig.spics_io_num = CSPin;
SPIDeviceConfig.queue_size = 1;
SPIDeviceConfig.mode = Mode;
SPIDeviceConfig.flags = SPI_DEVICE_NO_DUMMY;
if (Device->SPIParams) Device->SPIParams(SPIDeviceConfig.clock_speed_hz, &SPIDeviceConfig.mode,
&SPIDeviceConfig.cs_ena_pretrans, &SPIDeviceConfig.cs_ena_posttrans);
ESP_ERROR_CHECK_NONFATAL( spi_bus_add_device( SPIHost, &SPIDeviceConfig, &SPIDevice ), return false );
Device->WriteCommand = SPIDefaultWriteCommand;
@@ -61,7 +62,7 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
Device->CSPin = CSPin;
Device->Backlight.Pin = BackLightPin;
Device->IF = GDS_IF_SPI;
Device->Width = Width;
Device->Width = Device->TextWidth = Width;
Device->Height = Height;
if ( RSTPin >= 0 ) {
@@ -74,16 +75,23 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
}
static bool SPIDefaultWriteBytes( spi_device_handle_t SPIHandle, int WriteMode, const uint8_t* Data, size_t DataLength ) {
spi_transaction_t SPITransaction = { 0 };
spi_transaction_t SPITransaction = { };
NullCheck( SPIHandle, return false );
NullCheck( Data, return false );
if ( DataLength > 0 ) {
gpio_set_level( DCPin, WriteMode );
SPITransaction.length = DataLength * 8;
SPITransaction.tx_buffer = Data;
if (DataLength <= 4) {
SPITransaction.flags = SPI_TRANS_USE_TXDATA;
SPITransaction.tx_data[0] = *Data++; SPITransaction.tx_data[1] = *Data++;
SPITransaction.tx_data[2] = *Data++; SPITransaction.tx_data[3] = *Data;
} else {
SPITransaction.tx_buffer = Data;
}
// only do polling as we don't have contention on SPI (otherwise DMA for transfers > 16 bytes)
ESP_ERROR_CHECK_NONFATAL( spi_device_polling_transmit(SPIHandle, &SPITransaction), return false );

View File

@@ -20,6 +20,7 @@
#include "gds_draw.h"
#include "gds_text.h"
#include "gds_font.h"
#include "gds_image.h"
static const char *TAG = "display";
@@ -30,6 +31,9 @@ static const char *TAG = "display";
#define SCROLLABLE_SIZE 384
#define HEADER_SIZE 64
#define DEFAULT_SLEEP 3600
#define ARTWORK_BORDER 1
extern const uint8_t default_artwork[] asm("_binary_note_jpg_start");
static EXT_RAM_ATTR struct {
TaskHandle_t task;
@@ -41,7 +45,19 @@ static EXT_RAM_ATTR struct {
int offset, boundary;
char *metadata_config;
bool timer, refresh;
uint32_t elapsed, duration;
uint32_t elapsed;
struct {
uint32_t value;
char string[8]; // H:MM:SS
bool visible;
} duration;
struct {
bool enable, active;
bool fit;
bool updated;
int tick;
int offset;
} artwork;
TickType_t tick;
} displayer;
@@ -52,15 +68,16 @@ static const char *known_drivers[] = {"SH1106",
"SSD1327",
"SSD1675",
"SSD1351",
"ST77xx",
"ST7735",
"ST7789",
"ILI9341",
NULL
};
static void displayer_task(void *args);
struct GDS_Device *display;
extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect;
GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, NULL };
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 };
/****************************************************************************************
*
@@ -70,11 +87,11 @@ void display_init(char *welcome) {
char *config = config_alloc_get_str("display_config", CONFIG_DISPLAY_CONFIG, "N/A");
int width = -1, height = -1, backlight_pin = -1;
char *p, *drivername = strstr(config, "driver");
char *drivername = strstr(config, "driver");
if ((p = strcasestr(config, "width")) != NULL) width = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "height")) != NULL) height = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "back")) != NULL) backlight_pin = atoi(strchr(p, '=') + 1);
PARSE_PARAM(config, "width", '=', width);
PARSE_PARAM(config, "height", '=', height);
PARSE_PARAM(config, "back", '=', backlight_pin);
// query drivers to see if we have a match
ESP_LOGI(TAG, "Trying to configure display with %s", config);
@@ -83,33 +100,34 @@ void display_init(char *welcome) {
display = GDS_AutoDetect(drivername, drivers, &PWMConfig);
} else {
display = GDS_AutoDetect(drivername, drivers, NULL);
}
}
// so far so good
if (display && width > 0 && height > 0) {
int RST_pin = -1;
if ((p = strcasestr(config, "reset")) != NULL) RST_pin = atoi(strchr(p, '=') + 1);
PARSE_PARAM(config, "reset", '=', RST_pin);
// Detect driver interface
if (strstr(config, "I2C") && i2c_system_port != -1) {
if (strcasestr(config, "I2C") && i2c_system_port != -1) {
int address = 0x3C;
if ((p = strcasestr(config, "address")) != NULL) address = atoi(strchr(p, '=') + 1);
PARSE_PARAM(config, "address", '=', address);
init = true;
GDS_I2CInit( i2c_system_port, -1, -1, i2c_system_speed ) ;
GDS_I2CAttachDevice( display, width, height, address, RST_pin, backlight_pin );
ESP_LOGI(TAG, "Display is I2C on port %u", address);
} else if (strstr(config, "SPI") && spi_system_host != -1) {
int CS_pin = -1, speed = 0;
} else if (strcasestr(config, "SPI") && spi_system_host != -1) {
int CS_pin = -1, speed = 0, mode = 0;
if ((p = strcasestr(config, "cs")) != NULL) CS_pin = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "speed")) != NULL) speed = atoi(strchr(p, '=') + 1);
PARSE_PARAM(config, "cs", '=', CS_pin);
PARSE_PARAM(config, "speed", '=', speed);
PARSE_PARAM(config, "mode", '=', mode);
init = true;
GDS_SPIInit( spi_system_host, spi_system_dc_gpio );
GDS_SPIAttachDevice( display, width, height, CS_pin, RST_pin, backlight_pin, speed );
GDS_SPIAttachDevice( display, width, height, CS_pin, RST_pin, backlight_pin, speed, mode );
ESP_LOGI(TAG, "Display is SPI host %u with cs:%d", spi_system_host, CS_pin);
} else {
@@ -124,23 +142,38 @@ void display_init(char *welcome) {
if (init) {
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
static EXT_RAM_ATTR StackType_t xStack[DISPLAYER_STACK_SIZE] __attribute__ ((aligned (4)));
GDS_SetLayout( display, strcasestr(config, "HFlip"), strcasestr(config, "VFlip"), strcasestr(config, "rotate"));
GDS_SetFont(display, &Font_droid_sans_fallback_15x17 );
GDS_TextPos(display, GDS_FONT_MEDIUM, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, welcome);
struct GDS_Layout Layout = {
.HFlip = strcasestr(config, "HFlip"),
.VFlip = strcasestr(config, "VFlip"),
.Rotate = strcasestr(config, "rotate"),
.Invert = strcasestr(config, "invert"),
.ColorSwap = strcasestr(config, "cswap"),
};
GDS_SetLayout(display, &Layout);
GDS_SetFont(display, &Font_line_2);
GDS_TextPos(display, GDS_FONT_DEFAULT, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, welcome);
// start the task that will handle scrolling & counting
displayer.mutex = xSemaphoreCreateMutex();
displayer.by = 2;
displayer.pause = 3600;
displayer.speed = 33;
displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "displayer_thread", DISPLAYER_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "common_displayer", DISPLAYER_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
// set lines for "fixed" text mode
GDS_TextSetFontAuto(display, 1, GDS_FONT_LINE_1, -3);
GDS_TextSetFontAuto(display, 2, GDS_FONT_LINE_2, -3);
displayer.metadata_config = config_alloc_get(NVS_TYPE_STR, "metadata_config");
// leave room for artwork is display is horizontal-style
if (strcasestr(displayer.metadata_config, "artwork")) {
displayer.artwork.enable = true;
displayer.artwork.fit = true;
if (height <= 64 && width > height * 2) displayer.artwork.offset = width - height - ARTWORK_BORDER;
PARSE_PARAM(displayer.metadata_config, "artwork", ':', displayer.artwork.fit);
}
}
free(config);
@@ -192,18 +225,39 @@ static void displayer_task(void *args) {
// handler elapsed track time
if (displayer.timer && displayer.state == DISPLAYER_ACTIVE) {
char counter[16];
char line[19] = "-", *_line = line + 1; // [-]H:MM:SS / H:MM:SS
TickType_t tick = xTaskGetTickCount();
uint32_t elapsed = (tick - displayer.tick) * portTICK_PERIOD_MS;
if (elapsed >= 1000) {
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
displayer.tick = tick;
displayer.elapsed += elapsed / 1000;
xSemaphoreGive(displayer.mutex);
if (displayer.elapsed < 3600) snprintf(counter, 16, "%5u:%02u", displayer.elapsed / 60, displayer.elapsed % 60);
else snprintf(counter, 16, "%2u:%02u:%02u", displayer.elapsed / 3600, (displayer.elapsed % 3600) / 60, displayer.elapsed % 60);
GDS_TextLine(display, 1, GDS_TEXT_RIGHT, (GDS_TEXT_CLEAR | GDS_TEXT_CLEAR_EOL) | GDS_TEXT_UPDATE, counter);
elapsed = displayer.elapsed += elapsed / 1000;
xSemaphoreGive(displayer.mutex);
// when we have duration but no space, display remaining time
if (displayer.duration.value && !displayer.duration.visible) elapsed = displayer.duration.value - elapsed;
if (elapsed < 3600) sprintf(_line, "%u:%02u", elapsed / 60, elapsed % 60);
else sprintf(_line, "%u:%02u:%02u", (elapsed / 3600) % 100, (elapsed % 3600) / 60, elapsed % 60);
// concatenate if we have room for elapsed / duration
if (displayer.duration.visible) {
strcat(_line, "/");
strcat(_line, displayer.duration.string);
} else if (displayer.duration.value) {
_line--;
}
// just re-write the whole line it's easier
GDS_TextLine(display, 1, GDS_TEXT_LEFT, GDS_TEXT_CLEAR, displayer.header);
GDS_TextLine(display, 1, GDS_TEXT_RIGHT, GDS_TEXT_UPDATE, _line);
// if we have not received artwork after 5s, display a default icon
if (displayer.artwork.active && !displayer.artwork.updated && tick - displayer.artwork.tick > pdMS_TO_TICKS(5000)) {
ESP_LOGI(TAG, "no artwork received, setting default");
displayer_artwork((uint8_t*) default_artwork);
}
timer_sleep = 1000;
} else timer_sleep = max(1000 - elapsed, 0);
} else timer_sleep = DEFAULT_SLEEP;
@@ -216,6 +270,32 @@ static void displayer_task(void *args) {
}
}
/****************************************************************************************
*
*/
void displayer_artwork(uint8_t *data) {
if (!displayer.artwork.active) return;
int x = displayer.artwork.offset ? displayer.artwork.offset + ARTWORK_BORDER : 0;
int y = x ? 0 : 32;
GDS_ClearWindow(display, x, y, -1, -1, GDS_COLOR_BLACK);
if (data) {
displayer.artwork.updated = true;
GDS_DrawJPEG(display, data, x, y, GDS_IMAGE_CENTER | (displayer.artwork.fit ? GDS_IMAGE_FIT : 0));
} else {
displayer.artwork.updated = false;
displayer.artwork.tick = xTaskGetTickCount();
}
}
/****************************************************************************************
*
*/
bool displayer_can_artwork(void) {
return displayer.artwork.active;
}
/****************************************************************************************
*
*/
@@ -278,8 +358,8 @@ void displayer_metadata(char *artist, char *album, char *title) {
}
// get optional scroll speed & pause
if ((p = strcasestr(displayer.metadata_config, "speed")) != NULL) sscanf(p, "%*[^=]=%d", &displayer.speed);
if ((p = strcasestr(displayer.metadata_config, "pause")) != NULL) sscanf(p, "%*[^=]=%d", &displayer.pause);
PARSE_PARAM(displayer.metadata_config, "speed", '=', displayer.speed);
PARSE_PARAM(displayer.metadata_config, "pause", '=', displayer.pause);
displayer.offset = 0;
utf8_decode(displayer.string);
@@ -317,9 +397,27 @@ void displayer_timer(enum displayer_time_e mode, int elapsed, int duration) {
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
if (elapsed >= 0) displayer.elapsed = elapsed / 1000;
if (duration >= 0) displayer.duration = duration / 1000;
if (displayer.timer) displayer.tick = xTaskGetTickCount();
if (elapsed >= 0) displayer.elapsed = elapsed / 1000;
if (duration > 0) {
displayer.duration.visible = true;
displayer.duration.value = duration / 1000;
if (displayer.duration.value > 3600) sprintf(displayer.duration.string, "%u:%02u:%02u", (displayer.duration.value / 3600) % 10,
(displayer.duration.value % 3600) / 60, displayer.duration.value % 60);
else sprintf(displayer.duration.string, "%u:%02u", displayer.duration.value / 60, displayer.duration.value % 60);
char *buf;
asprintf(&buf, "%s %s/%s", displayer.header, displayer.duration.string, displayer.duration.string);
if (GDS_GetTextWidth(display, 1, 0, buf) > GDS_GetWidth(display)) {
ESP_LOGW(TAG, "Can't fit duration %s (%d) on screen using elapsed only", buf, GDS_GetTextWidth(display, 1, 0, buf));
displayer.duration.visible = false;
}
free(buf);
} else if (!duration) {
displayer.duration.visible = false;
displayer.duration.value = 0;
}
xSemaphoreGive(displayer.mutex);
}
@@ -338,25 +436,31 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
switch(cmd) {
case DISPLAYER_ACTIVATE: {
char *header = va_arg(args, char*);
displayer.artwork.active = displayer.artwork.enable && va_arg(args, int);
strncpy(displayer.header, header, HEADER_SIZE);
displayer.header[HEADER_SIZE] = '\0';
displayer.state = DISPLAYER_ACTIVE;
displayer.timer = false;
displayer.refresh = true;
displayer.string[0] = '\0';
displayer.elapsed = displayer.duration = 0;
displayer.elapsed = displayer.duration.value = 0;
displayer.duration.visible = false;
displayer.offset = displayer.boundary = 0;
display_bus(&displayer, DISPLAY_BUS_TAKE);
if (displayer.artwork.active) GDS_SetTextWidth(display, displayer.artwork.offset);
vTaskResume(displayer.task);
break;
}
case DISPLAYER_SUSPEND:
// task will display the line 2 from beginning and suspend
displayer.state = DISPLAYER_IDLE;
displayer_artwork(NULL);
display_bus(&displayer, DISPLAY_BUS_GIVE);
break;
case DISPLAYER_SHUTDOWN:
// let the task self-suspend (we might be doing i2c_write)
GDS_SetTextWidth(display, 0);
displayer_artwork(NULL);
displayer.state = DISPLAYER_DOWN;
display_bus(&displayer, DISPLAY_BUS_GIVE);
break;
@@ -381,14 +485,14 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
/****************************************************************************************
*
*/
bool display_is_valid_driver(char * driver){
bool display_is_valid_driver(const char * driver){
return display_conf_get_driver_name(driver)!=NULL;
}
/****************************************************************************************
*
*/
const char *display_conf_get_driver_name(char * driver){
const char *display_conf_get_driver_name(const char * driver){
for(uint8_t i=0;known_drivers[i]!=NULL && strlen(known_drivers[i])>0;i++ ){
if(strcasestr(driver,known_drivers[i])){
return known_drivers[i];
@@ -396,3 +500,27 @@ const char *display_conf_get_driver_name(char * driver){
}
return NULL;
}
/****************************************************************************************
*
*/
char * display_get_supported_drivers(void){
int total_size = 1;
char * supported_drivers=NULL;
const char * separator = "|";
int separator_len = strlen(separator);
for(uint8_t i=0;known_drivers[i]!=NULL && strlen(known_drivers[i])>0;i++ ){
total_size += strlen(known_drivers[i])+separator_len;
}
total_size+=2;
supported_drivers = malloc(total_size);
memset(supported_drivers,0x00,total_size);
strcat(supported_drivers,"<");
for(uint8_t i=0;known_drivers[i]!=NULL && strlen(known_drivers[i])>0;i++ ){
supported_drivers = strcat(supported_drivers,known_drivers[i]);
supported_drivers = strcat(supported_drivers,separator);
}
strcat(supported_drivers,">");
return supported_drivers;
}

View File

@@ -10,6 +10,7 @@
#include "gds.h"
/*
The displayer is not thread-safe and the caller must ensure use its own
mutexes if it wants something better. Especially, text() line() and draw()
@@ -31,10 +32,13 @@ enum displayer_time_e { DISPLAYER_ELAPSED, DISPLAYER_REMAINING };
enum display_bus_cmd_e { DISPLAY_BUS_TAKE, DISPLAY_BUS_GIVE };
bool (*display_bus)(void *from, enum display_bus_cmd_e cmd);
const char *display_conf_get_driver_name(char * driver);
bool display_is_valid_driver(char * driver);
const char *display_conf_get_driver_name(const char * driver);
bool display_is_valid_driver(const char * driver);
void displayer_scroll(char *string, int speed, int pause);
void displayer_control(enum displayer_cmd_e cmd, ...);
void displayer_metadata(char *artist, char *album, char *title);
void displayer_artwork(uint8_t *data);
void displayer_timer(enum displayer_time_e mode, int elapsed, int duration);
bool displayer_can_artwork(void);
char * display_get_supported_drivers(void);

View File

@@ -26,7 +26,7 @@ static const uint8_t Square721_BT11x14[] = {
0x02, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char ,
0x04, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char -
0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char .
0x04, 0x00, 0x0C, 0x80, 0x03, 0x70, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char /
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x80, 0x03, 0x70, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char /
0x08, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x08, 0x04, 0x08, 0x04, 0x08, 0x04, 0x08, 0x04, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char 0
0x08, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x10, 0x04, 0x08, 0x04, 0xF8, 0x07, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char 1
0x08, 0x00, 0x00, 0x00, 0x00, 0x30, 0x07, 0x08, 0x05, 0x88, 0x04, 0x88, 0x04, 0x88, 0x04, 0x70, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char 2

BIN
components/display/note.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -6,27 +6,27 @@
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "bt_app_core.h"
#include <stdint.h>
#include "esp_system.h"
#include <string.h>
#include <stdbool.h>
#include "esp_log.h"
#include "freertos/xtensa_api.h"
#include "freertos/FreeRTOSConfig.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 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 = NULL;
static xTaskHandle s_bt_app_task_handle = NULL;
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)
{
@@ -42,8 +42,7 @@ bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, i
if (param_len == 0) {
return bt_app_send_msg(&msg);
} else if (p_params && param_len > 0) {
if ((msg.param = malloc(param_len)) != NULL) {
memcpy(msg.param, p_params, param_len);
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);
@@ -78,9 +77,58 @@ static void bt_app_work_dispatched(bt_app_msg_t *msg)
static void bt_app_task_handler(void *arg)
{
bt_app_msg_t msg;
for (;;) {
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 (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE ) {
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
/*
* Set default parameters for Legacy Pairing
* Use variable pin, input pin code when pairing
*/
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
esp_bt_pin_code_t pin_code;
esp_bt_gap_set_pin(pin_type, 0, pin_code);
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);
@@ -88,36 +136,46 @@ static void bt_app_task_handler(void *arg)
default:
ESP_LOGW(TAG,"%s, unhandled sig: %d", __func__, msg.sig);
break;
} // switch (msg.sig)
}
if (msg.param) {
free(msg.param);
}
}
else
{
} 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(void)
void bt_app_task_start_up(bt_av_hdl_stack_evt_t* handler)
{
s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
assert(s_bt_app_task_queue!=NULL);
assert(xTaskCreate(bt_app_task_handler, "BtAppT", 4096, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle)==pdPASS);
return;
xTaskCreate(bt_app_task_handler, "BtAppT", 4096, handler, configMAX_PRIORITIES - 3, NULL);
}
void bt_app_task_shut_down(void)
{
if (s_bt_app_task_handle) {
vTaskDelete(s_bt_app_task_handle);
s_bt_app_task_handle = NULL;
}
if (s_bt_app_task_queue) {
vQueueDelete(s_bt_app_task_queue);
s_bt_app_task_queue = NULL;
}
running = false;
}

View File

@@ -15,9 +15,33 @@
#include <stdio.h>
#define BT_APP_CORE_TAG "BT_APP_CORE"
#define BT_APP_SIG_WORK_DISPATCH (0x01)
enum {
BT_APP_EVT_STACK_UP = 0,
};
/* A2DP global state */
enum {
APP_AV_STATE_IDLE,
APP_AV_STATE_DISCOVERING,
APP_AV_STATE_DISCOVERED,
APP_AV_STATE_UNCONNECTED,
APP_AV_STATE_CONNECTING,
APP_AV_STATE_CONNECTED,
APP_AV_STATE_DISCONNECTING,
};
/* sub states of APP_AV_STATE_CONNECTED */
enum {
APP_AV_MEDIA_STATE_IDLE,
APP_AV_MEDIA_STATE_STARTING,
APP_AV_MEDIA_STATE_STARTED,
APP_AV_MEDIA_STATE_STOPPING,
APP_AV_MEDIA_STATE_WAIT_DISCONNECT
};
extern int bt_app_source_get_a2d_state();
extern int bt_app_source_get_media_state();
/**
* @brief handler for the dispatched work
*/
@@ -36,12 +60,17 @@ typedef struct {
*/
typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src);
/**
* @brief callback for startup event
*/
typedef void bt_av_hdl_stack_evt_t(uint16_t event, void *p_param);
/**
* @brief work dispatcher for the application task
*/
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);
void bt_app_task_start_up(void);
void bt_app_task_start_up(bt_av_hdl_stack_evt_t* handler);
void bt_app_task_shut_down(void);

View File

@@ -25,7 +25,7 @@
#include "platform_config.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "trace.h"
#include "tools.h"
#include "audio_controls.h"
#include "sys/lock.h"
#include "display.h"
@@ -37,19 +37,15 @@
#define APP_RC_CT_TL_RN_PLAYBACK_CHANGE (3)
#define APP_RC_CT_TL_RN_PLAY_POS_CHANGE (4)
#define BT_AV_TAG "BT_AV"
#define BT_RC_TG_TAG "RCTG"
#define BT_RC_CT_TAG "RCCT"
static const char BT_AV_TAG[] = "BT_AV";
static const char BT_RC_TG_TAG[] = "RCTG";
static const char BT_RC_CT_TAG[] = "RCCT";
#ifndef CONFIG_BT_NAME
#define CONFIG_BT_NAME "ESP32-BT"
#endif
/* event for handler "bt_av_hdl_stack_up */
enum {
BT_APP_EVT_STACK_UP = 0,
};
char * bt_name = NULL;
static char * bt_name = NULL;
static bool (*bt_app_a2d_cmd_cb)(bt_sink_cmd_t cmd, ...);
static void (*bt_app_a2d_data_cb)(const uint8_t *data, uint32_t len);
@@ -62,14 +58,15 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param);
static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param);
/* avrc TG event handler */
static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param);
static void volume_set_by_local_host(uint8_t volume);
static void volume_set_by_local_host(int value, bool is_step);
static void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter);
static const char *s_a2d_conn_state_str[] = {"Disconnected", "Connecting", "Connected", "Disconnecting"};
static const char *s_a2d_audio_state_str[] = {"Suspended", "Stopped", "Started"};
static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap;
static _lock_t s_volume_lock;
static uint8_t s_volume = 0;
static int s_volume, abs_volume, sink_volume;
static bool s_volume_notify;
static enum { AUDIO_IDLE, AUDIO_CONNECTED, AUDIO_PLAYING } s_audio = AUDIO_IDLE;
@@ -89,16 +86,14 @@ static EXT_RAM_ATTR struct {
static void bt_volume_up(bool pressed) {
if (!pressed) return;
// volume UP/DOWN buttons are not supported by iPhone/Android
volume_set_by_local_host(s_volume < 127-3 ? s_volume + 3 : 127);
volume_set_by_local_host(+3, true);
(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, s_volume);
ESP_LOGD(BT_AV_TAG, "BT volume up %u", s_volume);
}
static void bt_volume_down(bool pressed) {
if (!pressed) return;
// volume UP/DOWN buttons are not supported by iPhone/Android
volume_set_by_local_host(s_volume > 3 ? s_volume - 3 : 0);
volume_set_by_local_host(-3, true);
(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, s_volume);
}
@@ -134,12 +129,14 @@ static void bt_next(bool pressed) {
}
const static actrls_t controls = {
NULL, // power
bt_volume_up, bt_volume_down, // volume up, volume down
bt_toggle, bt_play, // toggle, play
bt_pause, bt_stop, // pause, stop
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
bt_volume_down, bt_volume_up, bt_toggle// knob left, knob_right, knob push
};
@@ -175,7 +172,7 @@ static bool cmd_handler(bt_sink_cmd_t cmd, ...) {
// now handle events for display
switch(cmd) {
case BT_SINK_AUDIO_STARTED:
displayer_control(DISPLAYER_ACTIVATE, "BLUETOOTH");
displayer_control(DISPLAYER_ACTIVATE, "BLUETOOTH", false);
break;
case BT_SINK_AUDIO_STOPPED:
displayer_control(DISPLAYER_SUSPEND);
@@ -217,6 +214,10 @@ void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param)
bt_app_work_dispatch(bt_av_hdl_a2d_evt, event, param, sizeof(esp_a2d_cb_param_t), NULL);
break;
}
case ESP_A2D_PROF_STATE_EVT: {
ESP_LOGI(BT_AV_TAG, "Bluetooth Init complete");
break;
}
default:
ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event);
break;
@@ -283,6 +284,8 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
(*bt_app_a2d_cmd_cb)(BT_SINK_DISCONNECTED);
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){
abs_volume = -1;
s_volume = sink_volume;
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
(*bt_app_a2d_cmd_cb)(BT_SINK_CONNECTED);
}
@@ -294,6 +297,9 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) {
s_audio = AUDIO_CONNECTED;
// send memorized volume for devices that can't do absolute volume
(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, s_volume);
// verify that we can take control
if ((*bt_app_a2d_cmd_cb)(BT_SINK_AUDIO_STARTED, s_sample_rate)) {
@@ -378,7 +384,7 @@ static void bt_av_play_pos_changed(void)
}
}
void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter)
static void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter)
{
switch (event_id) {
case ESP_AVRC_RN_TRACK_CHANGE:
@@ -487,20 +493,29 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
static void volume_set_by_controller(uint8_t volume)
{
ESP_LOGD(BT_RC_TG_TAG, "Volume is set by remote controller %d%%\n", (uint32_t)volume * 100 / 0x7f);
// do not modified NVS volume
_lock_acquire(&s_volume_lock);
s_volume = volume;
s_volume = abs_volume = (volume * 100) / 127;
_lock_release(&s_volume_lock);
(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, volume);
(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, s_volume);
}
static void volume_set_by_local_host(uint8_t volume)
static void volume_set_by_local_host(int value, bool is_step)
{
ESP_LOGD(BT_RC_TG_TAG, "Volume is set locally to: %d%%", (uint32_t)volume * 100 / 0x7f);
_lock_acquire(&s_volume_lock);
s_volume = volume;
_lock_release(&s_volume_lock);
_lock_acquire(&s_volume_lock);
s_volume = is_step ? s_volume + value : value;
if (s_volume > 127) s_volume = 127;
else if (s_volume < 0) s_volume = 0;
if (abs_volume >= 0) abs_volume = s_volume;
else sink_volume = s_volume;
_lock_release(&s_volume_lock);
// volume has been set by controller, do not store it in NVS
if (abs_volume < 0) {
char p[4];
config_set_value(NVS_TYPE_STR, "bt_sink_volume", itoa(s_volume, p, 10));
}
if (s_volume_notify) {
esp_avrc_rn_param_t rn_param;
rn_param.volume = s_volume;
@@ -525,7 +540,7 @@ static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param)
break;
}
case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: {
ESP_LOGD(BT_RC_TG_TAG, "AVRC set absolute volume: %d%%", (int)rc->set_abs_vol.volume * 100/ 0x7f);
ESP_LOGD(BT_RC_TG_TAG, "AVRC set absolute volume: %d%%", (rc->set_abs_vol.volume * 100) / 127);
volume_set_by_controller(rc->set_abs_vol.volume);
break;
}
@@ -551,47 +566,16 @@ static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param)
void bt_sink_init(bt_cmd_vcb_t cmd_cb, bt_data_cb_t data_cb)
{
esp_err_t err;
bt_app_a2d_cmd_cb = cmd_handler;
cmd_handler_chain = cmd_cb;
bt_app_a2d_data_cb = data_cb;
ESP_ERROR_CHECK(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(BT_AV_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(err));
return;
}
if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(err));
return;
}
if ((err = esp_bluedroid_init()) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(err));
return;
}
if ((err = esp_bluedroid_enable()) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(err));
return;
}
/* create application task */
bt_app_task_start_up();
/* Bluetooth device name, connection mode and profile set up */
bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL);
#if (CONFIG_BT_SSP_ENABLED == true)
/* 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
// create task and run event loop
bt_app_task_start_up(bt_av_hdl_stack_evt);
char *item = config_alloc_get_default(NVS_TYPE_STR, "bt_sink_volume", "127", 0);
sink_volume = atol(item);
free(item);
/*
* Set default parameters for Legacy Pairing
@@ -617,30 +601,17 @@ void bt_sink_init(bt_cmd_vcb_t cmd_cb, bt_data_cb_t data_cb)
bError=true;
}
esp_pin_code[i]= pin_code[i];
}
if(bError){
esp_pin_code[0]='1';
esp_pin_code[1]='2';
esp_pin_code[2]='3';
esp_pin_code[3]='4';
}
esp_bt_gap_set_pin(pin_type, strlen(pin_code), esp_pin_code);
if (bError) memcpy(esp_pin_code, "1234", 4);
esp_bt_gap_set_pin(pin_type, strlen(pin_code), esp_pin_code);
free(pin_code);
}
void bt_sink_deinit(void)
{
/* this still does not work, can't figure out how to stop properly this BT stack */
bt_app_task_shut_down();
ESP_LOGD(BT_AV_TAG, "bt_app_task shutdown successfully");
if (esp_bluedroid_disable() != ESP_OK) return;
ESP_LOGD(BT_AV_TAG, "esp_bluedroid_disable called successfully");
if (esp_bluedroid_deinit() != ESP_OK) return;
ESP_LOGD(BT_AV_TAG, "esp_bluedroid_deinit called successfully");
if (esp_bt_controller_disable() != ESP_OK) return;
ESP_LOGD(BT_AV_TAG, "esp_bt_controller_disable called successfully");
if (esp_bt_controller_deinit() != ESP_OK) return;
ESP_LOGD(BT_AV_TAG, "bt stopped successfully");
}
static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
idf_build_get_property(prefix IDF_PATH)
string(CONCAT prefix "${prefix}" "/components/esp_http_server")
idf_component_register(
SRC_DIRS "${prefix}/src" "${prefix}/src/util"
INCLUDE_DIRS "${prefix}/include"
PRIV_INCLUDE_DIRS "." "${prefix}/src/port/esp32" "${prefix}/src/util"
REQUIRES nghttp # for http_parser.h
PRIV_REQUIRES lwip mbedtls esp_timer
)

View File

@@ -0,0 +1 @@
source "$IDF_PATH/components/esp_http_server/Kconfig"

View File

@@ -0,0 +1,68 @@
// 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.
#ifndef _OSAL_H_
#define _OSAL_H_
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <unistd.h>
#include <stdint.h>
#include <esp_timer.h>
#ifdef __cplusplus
extern "C" {
#endif
#define OS_SUCCESS ESP_OK
#define OS_FAIL ESP_FAIL
typedef TaskHandle_t othread_t;
static inline int httpd_os_thread_create(othread_t *thread,
const char *name, uint16_t stacksize, int prio,
void (*thread_routine)(void *arg), void *arg,
BaseType_t core_id)
{
StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
StackType_t *xStack = heap_caps_malloc(stacksize,(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT));
*thread = xTaskCreateStaticPinnedToCore(thread_routine, name, stacksize, arg, prio, xStack,xTaskBuffer,core_id);
if (*thread) {
return OS_SUCCESS;
}
return OS_FAIL;
}
/* Only self delete is supported */
static inline void httpd_os_thread_delete(void)
{
vTaskDelete(xTaskGetCurrentTaskHandle());
}
static inline void httpd_os_thread_sleep(int msecs)
{
vTaskDelay(msecs / portTICK_RATE_MS);
}
static inline othread_t httpd_os_thread_handle(void)
{
return xTaskGetCurrentTaskHandle();
}
#ifdef __cplusplus
}
#endif
#endif /* ! _OSAL_H_ */

View File

@@ -0,0 +1,49 @@
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()

74
components/heap/Kconfig Normal file
View File

@@ -0,0 +1,74 @@
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

@@ -0,0 +1,32 @@
#
# 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

609
components/heap/heap_caps.c Normal file
View File

@@ -0,0 +1,609 @@
// 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

@@ -0,0 +1,241 @@
// 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

@@ -0,0 +1,77 @@
// 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

@@ -0,0 +1,129 @@
// 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

1015
components/heap/heap_tlsf.c Normal file

File diff suppressed because it is too large Load Diff

119
components/heap/heap_tlsf.h Normal file
View File

@@ -0,0 +1,119 @@
/*
** 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

@@ -0,0 +1,174 @@
/*
** 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

@@ -0,0 +1,66 @@
/*
** 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

@@ -0,0 +1,255 @@
// 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

@@ -0,0 +1,402 @@
// 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

@@ -0,0 +1,92 @@
// 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

@@ -0,0 +1,98 @@
// 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

@@ -0,0 +1,154 @@
// 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

@@ -0,0 +1,200 @@
// 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

@@ -0,0 +1,190 @@
// 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

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

View File

@@ -0,0 +1,376 @@
// 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

@@ -0,0 +1,31 @@
// 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

@@ -0,0 +1,76 @@
// 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

@@ -0,0 +1,108 @@
// 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

@@ -0,0 +1,426 @@
// 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

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

View File

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

View File

@@ -0,0 +1,147 @@
/*
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

@@ -0,0 +1,108 @@
#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

@@ -0,0 +1,74 @@
/*
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

@@ -0,0 +1,164 @@
/*
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

@@ -0,0 +1,60 @@
/*
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

@@ -0,0 +1,134 @@
/*
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

@@ -0,0 +1,247 @@
/*
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

@@ -0,0 +1,67 @@
/*
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

@@ -0,0 +1,72 @@
/*
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

@@ -0,0 +1,54 @@
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

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

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