mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-09 13:07:03 +03:00
Compare commits
882 Commits
v0.5.657-v
...
improv-4.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa8b5ec2d4 | ||
|
|
3c94f63876 | ||
|
|
383a2fd7ce | ||
|
|
f74ecf5e60 | ||
|
|
1390e258db | ||
|
|
b20c8306fa | ||
|
|
877f16181b | ||
|
|
2cba6972ff | ||
|
|
fbd9516667 | ||
|
|
646a220fd0 | ||
|
|
962dbf89c8 | ||
|
|
3a658f3ba2 | ||
|
|
4f5a7ade9d | ||
|
|
f15f846030 | ||
|
|
14f4caf584 | ||
|
|
46eb732d82 | ||
|
|
d092ae093c | ||
|
|
4a51efd556 | ||
|
|
c1375444b4 | ||
|
|
e5eabcaf86 | ||
|
|
93465db1b8 | ||
|
|
dc66d9e9dc | ||
|
|
8462de1c16 | ||
|
|
37f48d4bc8 | ||
|
|
ec019f98f3 | ||
|
|
5e5c7e0c80 | ||
|
|
3144cf5f91 | ||
|
|
18cc0adfb4 | ||
|
|
78ece491a9 | ||
|
|
9e66a822de | ||
|
|
0e12f7f887 | ||
|
|
5df561890c | ||
|
|
f6630fae4e | ||
|
|
e2df4b1cc3 | ||
|
|
3172576fc1 | ||
|
|
e0dd7a596b | ||
|
|
6e4dd65fd7 | ||
|
|
7e5f27af12 | ||
|
|
f805db3d27 | ||
|
|
a81d0e0513 | ||
|
|
6c524cd094 | ||
|
|
e34cec1ad1 | ||
|
|
a2c16decee | ||
|
|
d06893c43d | ||
|
|
aecaed4a5f | ||
|
|
113a75a61d | ||
|
|
f721d140e7 | ||
|
|
46848b3afe | ||
|
|
3d63670499 | ||
|
|
83114861c5 | ||
|
|
5e7ab93e4a | ||
|
|
2c56f2d7ae | ||
|
|
c62e719c11 | ||
|
|
0226355ab7 | ||
|
|
42bee2a7b0 | ||
|
|
130f54526f | ||
|
|
f14f2debc3 | ||
|
|
25772c9ccb | ||
|
|
1ae1b03fc3 | ||
|
|
08ff02dc3c | ||
|
|
3f4df9a348 | ||
|
|
bb70ea1eaa | ||
|
|
c0e8e36221 | ||
|
|
4c94042c1d | ||
|
|
66be31a76c | ||
|
|
5a53011383 | ||
|
|
9ca1b87919 | ||
|
|
2698577592 | ||
|
|
f70f973ca9 | ||
|
|
83592b7079 | ||
|
|
2967723fdf | ||
|
|
d6e75dc71e | ||
|
|
fc50d55a27 | ||
|
|
4fff2f0dc1 | ||
|
|
027d81af21 | ||
|
|
96a2581534 | ||
|
|
461486ce21 | ||
|
|
7f0bc6c2ba | ||
|
|
551db4a7d1 | ||
|
|
eee3123eba | ||
|
|
d1a5e0aaa6 | ||
|
|
e6d9c5f5a3 | ||
|
|
8164ccacaa | ||
|
|
b2ceb8c0e3 | ||
|
|
2e66a6a6cb | ||
|
|
ac1817f4d3 | ||
|
|
1d4bd8274b | ||
|
|
58af548252 | ||
|
|
c400f4467e | ||
|
|
24c69a70d2 | ||
|
|
0ce4e9e240 | ||
|
|
b50caddb39 | ||
|
|
639ed446f5 | ||
|
|
06b65f6f6c | ||
|
|
f70cd0eeb1 | ||
|
|
95018f39e8 | ||
|
|
c8e999590d | ||
|
|
b640ff1c88 | ||
|
|
bfe1be3c99 | ||
|
|
9416c6916a | ||
|
|
f23116d2c6 | ||
|
|
ff1d276148 | ||
|
|
c143ea56ff | ||
|
|
3fc7e68163 | ||
|
|
2dea2ca08c | ||
|
|
2584026e5c | ||
|
|
f2b0c40848 | ||
|
|
1876b26100 | ||
|
|
f84e856e68 | ||
|
|
34fa7203be | ||
|
|
751d683b3e | ||
|
|
b000bbd383 | ||
|
|
6f504839bb | ||
|
|
b48e9fe1fa | ||
|
|
eb6b99d461 | ||
|
|
405c7742ef | ||
|
|
eab321d233 | ||
|
|
50475e9305 | ||
|
|
a1e15abba7 | ||
|
|
7e733f4fa9 | ||
|
|
3e35937272 | ||
|
|
2d9dd14e06 | ||
|
|
698cc70aa3 | ||
|
|
6e67606a07 | ||
|
|
f6f2f612a1 | ||
|
|
60bf802f3f | ||
|
|
f7c9f08071 | ||
|
|
9be4593d81 | ||
|
|
922889fee2 | ||
|
|
a8e28f9ff0 | ||
|
|
1fcda53941 | ||
|
|
0222dbd6de | ||
|
|
36f18fc069 | ||
|
|
12f9b1eae8 | ||
|
|
16225ed8c9 | ||
|
|
57b77766ff | ||
|
|
7b1d1ad45e | ||
|
|
b191ea6ec1 | ||
|
|
70e194e763 | ||
|
|
828c28720f | ||
|
|
dd519b9229 | ||
|
|
54d7e222d0 | ||
|
|
23a5f7fbe4 | ||
|
|
3eab93b595 | ||
|
|
bda4c18abe | ||
|
|
72b66054b1 | ||
|
|
9da735da95 | ||
|
|
168eb061af | ||
|
|
c9adf6320b | ||
|
|
7fd87eeba5 | ||
|
|
042169e7eb | ||
|
|
68e7512073 | ||
|
|
a5da6bbcf1 | ||
|
|
44829ae59f | ||
|
|
882ed4dce9 | ||
|
|
28dba930c7 | ||
|
|
5ee2bc318e | ||
|
|
7fcb201b06 | ||
|
|
839f31d485 | ||
|
|
6d1cf28fd6 | ||
|
|
8e6d409311 | ||
|
|
eb3a8f76da | ||
|
|
cf4ed64eb0 | ||
|
|
3d8055a948 | ||
|
|
0052ff9625 | ||
|
|
977935015e | ||
|
|
bd63723189 | ||
|
|
9185acf513 | ||
|
|
6f28153a3f | ||
|
|
b79e1f9391 | ||
|
|
9843d404bd | ||
|
|
47774c98f0 | ||
|
|
285225401c | ||
|
|
1b74c82c72 | ||
|
|
f1d9e32f2c | ||
|
|
d16e7eed09 | ||
|
|
89b4b5ca2d | ||
|
|
15c0e47ae3 | ||
|
|
d1dd27b7cb | ||
|
|
750e455b0d | ||
|
|
533ee5e408 | ||
|
|
de025602ac | ||
|
|
396366b509 | ||
|
|
60584ae207 | ||
|
|
3fb1c16f56 | ||
|
|
4420f7da4d | ||
|
|
1b83d0eb5f | ||
|
|
b2741b5bef | ||
|
|
04919f7b6e | ||
|
|
8fa3906b52 | ||
|
|
72657d6951 | ||
|
|
f09a95cc8b | ||
|
|
3125a095fa | ||
|
|
e9da432bfc | ||
|
|
052600a45a | ||
|
|
9af4cd5b23 | ||
|
|
491d0d260d | ||
|
|
c1b39610fc | ||
|
|
f95ec33457 | ||
|
|
fb24168d99 | ||
|
|
28ac503ef7 | ||
|
|
0127428a15 | ||
|
|
df8d31c679 | ||
|
|
f2f578a719 | ||
|
|
e6046fa343 | ||
|
|
7959850970 | ||
|
|
54440e87b6 | ||
|
|
47ad526890 | ||
|
|
d81e95c94a | ||
|
|
bf24735422 | ||
|
|
685ac92f6e | ||
|
|
5cad1aeed6 | ||
|
|
9e21e302b8 | ||
|
|
3a99c65eb3 | ||
|
|
e5516db60f | ||
|
|
c8430bcfa2 | ||
|
|
97793ceea8 | ||
|
|
e59188ec44 | ||
|
|
bea28a0a90 | ||
|
|
c612410a3b | ||
|
|
cdf6e88362 | ||
|
|
edb830f7b2 | ||
|
|
33c7ba820d | ||
|
|
39f5a81238 | ||
|
|
06b637c55b | ||
|
|
cf1315e6a4 | ||
|
|
59cc5a5a70 | ||
|
|
6dbefa5a76 | ||
|
|
a31a31a414 | ||
|
|
e71672cf49 | ||
|
|
6e1dbe5021 | ||
|
|
aa1cfdd6b2 | ||
|
|
7bf1ede250 | ||
|
|
25249ce13e | ||
|
|
756d930912 | ||
|
|
a93bd3c187 | ||
|
|
f076a7260e | ||
|
|
9b20001c97 | ||
|
|
6cdd2d302f | ||
|
|
b2ec1506d7 | ||
|
|
f3c405579f | ||
|
|
2332e22d99 | ||
|
|
a40d7f5caa | ||
|
|
d7d47d1127 | ||
|
|
51c178ca46 | ||
|
|
e85733fc32 | ||
|
|
676acbdbc2 | ||
|
|
1422003271 | ||
|
|
7f894f1635 | ||
|
|
e2bcb041e9 | ||
|
|
5aca6f974c | ||
|
|
523127bdea | ||
|
|
662962ddb1 | ||
|
|
c68919d2d1 | ||
|
|
b79878f590 | ||
|
|
dc1e258d64 | ||
|
|
d914e68a9b | ||
|
|
9dfe90c26f | ||
|
|
80270b772b | ||
|
|
e16b7dd15b | ||
|
|
088825102e | ||
|
|
a354e6248a | ||
|
|
9a37d9dba4 | ||
|
|
898998efb0 | ||
|
|
955692f8ad | ||
|
|
c0fc0c0276 | ||
|
|
49de5b8f23 | ||
|
|
a326699d76 | ||
|
|
efefb266f9 | ||
|
|
a9cc5c7b55 | ||
|
|
d0bcc72bce | ||
|
|
a569b2a82e | ||
|
|
3a89597ff0 | ||
|
|
8892d66f9e | ||
|
|
a7469e5258 | ||
|
|
0e33e112ae | ||
|
|
626303e563 | ||
|
|
f7dfb1d695 | ||
|
|
0e89f988cd | ||
|
|
732f5cb793 | ||
|
|
e6b7ed28e7 | ||
|
|
2e061f5cec | ||
|
|
f8903770c2 | ||
|
|
63fbc2f645 | ||
|
|
81756a7649 | ||
|
|
699c1da42f | ||
|
|
8e5dbd2144 | ||
|
|
974ff5fa68 | ||
|
|
4dce4b307b | ||
|
|
4aa54537e5 | ||
|
|
d27a430664 | ||
|
|
0fa41e5ef7 | ||
|
|
2146014f04 | ||
|
|
7d409668c8 | ||
|
|
2a6f5c8a10 | ||
|
|
0ebce7472b | ||
|
|
5f5466fcb0 | ||
|
|
71911914cb | ||
|
|
2805629c4b | ||
|
|
4f6dcc2cc7 | ||
|
|
f4615462c7 | ||
|
|
6033c7fc14 | ||
|
|
97ae01b89f | ||
|
|
a98b1d00b0 | ||
|
|
1e45348e4a | ||
|
|
96a05d8a6b | ||
|
|
7a9f3e0781 | ||
|
|
3575245324 | ||
|
|
c36d663a7f | ||
|
|
13294ddd0c | ||
|
|
449ef32a4e | ||
|
|
50390dbc61 | ||
|
|
fc5f3f5ac9 | ||
|
|
6f4ed0679e | ||
|
|
511df0b4b8 | ||
|
|
6682ee947e | ||
|
|
813e7ee712 | ||
|
|
6c9537e259 | ||
|
|
ac9ad2eee2 | ||
|
|
f38840bbe0 | ||
|
|
fd56f649ab | ||
|
|
c4bd320afe | ||
|
|
5e8978d1af | ||
|
|
6e082a5654 | ||
|
|
5af3250aea | ||
|
|
527187b2f8 | ||
|
|
102d2f6af5 | ||
|
|
4edd429b0a | ||
|
|
35099d1131 | ||
|
|
ce9c3952e8 | ||
|
|
4ee6352185 | ||
|
|
3a720a1e7d | ||
|
|
987fa5d18c | ||
|
|
23c936ec93 | ||
|
|
d4f10a761e | ||
|
|
d68d163538 | ||
|
|
94580c6771 | ||
|
|
2717277c6e | ||
|
|
b6d537a207 | ||
|
|
cf047c8098 | ||
|
|
21f3768ada | ||
|
|
9cb18fa980 | ||
|
|
d4f6289500 | ||
|
|
21c3ce1fba | ||
|
|
48e8525ba9 | ||
|
|
0db9631700 | ||
|
|
192cb975e2 | ||
|
|
d1f6085199 | ||
|
|
afaa5323d7 | ||
|
|
f92447e9b9 | ||
|
|
22f8d1d88b | ||
|
|
8136b7fd9a | ||
|
|
e588deb3af | ||
|
|
38ec8ac6f8 | ||
|
|
791167f794 | ||
|
|
c9b859ef8c | ||
|
|
db74419bd7 | ||
|
|
265403d364 | ||
|
|
d3dd8b9078 | ||
|
|
de41fb9583 | ||
|
|
2699216d25 | ||
|
|
c7b1c7cd83 | ||
|
|
f51af21b90 | ||
|
|
5ec63f400c | ||
|
|
1753e11698 | ||
|
|
58c6ca059b | ||
|
|
5ef63dc3e4 | ||
|
|
468c847499 | ||
|
|
8c0e766cd7 | ||
|
|
0f792d71ee | ||
|
|
1fc7675c14 | ||
|
|
7b439ae6ee | ||
|
|
c5d7fd521d | ||
|
|
a856b26181 | ||
|
|
a949ec2d24 | ||
|
|
5ec5236991 | ||
|
|
d4cd400cd9 | ||
|
|
64bb5f018b | ||
|
|
593927aac3 | ||
|
|
51761d0890 | ||
|
|
5c56abfe75 | ||
|
|
9ac7c5bbeb | ||
|
|
be28555a40 | ||
|
|
1d32479bc4 | ||
|
|
c83ddc4adc | ||
|
|
5a7cf9b8fe | ||
|
|
190326726c | ||
|
|
387276f2f3 | ||
|
|
87b8c46263 | ||
|
|
d7aef11856 | ||
|
|
be842d235d | ||
|
|
d49ad66177 | ||
|
|
ca28669b0a | ||
|
|
45f480d948 | ||
|
|
852e312879 | ||
|
|
c79cf5b58f | ||
|
|
1a4a8ba559 | ||
|
|
7ad39a02f5 | ||
|
|
f96d06912f | ||
|
|
36571d3dad | ||
|
|
b075bbaea3 | ||
|
|
ec7982dbfc | ||
|
|
afd76bdfbb | ||
|
|
89ecd19d32 | ||
|
|
8904ec1afd | ||
|
|
5120029643 | ||
|
|
4a529d6fbd | ||
|
|
afe697e4b1 | ||
|
|
879b1f9107 | ||
|
|
5a7d4fd535 | ||
|
|
d61c650f39 | ||
|
|
f096ee269e | ||
|
|
0610e1a2bc | ||
|
|
68db286777 | ||
|
|
5075878f05 | ||
|
|
d965187d2c | ||
|
|
e25b098678 | ||
|
|
a3b23bffc2 | ||
|
|
dbc7a6b14e | ||
|
|
a105f7fd99 | ||
|
|
1b39a4f7c9 | ||
|
|
cac6306a04 | ||
|
|
749d71a36f | ||
|
|
4d40355d5c | ||
|
|
c311faa90f | ||
|
|
0821551a2f | ||
|
|
3a2bfe470f | ||
|
|
f6b55c5ac9 | ||
|
|
f9e97036cf | ||
|
|
4444fed343 | ||
|
|
bc0d104290 | ||
|
|
bdec9d25c6 | ||
|
|
b25367fc0c | ||
|
|
603296d921 | ||
|
|
a5b7d24dca | ||
|
|
2733e3ec23 | ||
|
|
9c79888b72 | ||
|
|
2b59c38b4b | ||
|
|
a13afd76c9 | ||
|
|
b98a481858 | ||
|
|
9e27a0e21d | ||
|
|
d08f7142ae | ||
|
|
efa3f1f07d | ||
|
|
263679dcac | ||
|
|
50d7d57f48 | ||
|
|
99e4b107d6 | ||
|
|
d293de4b64 | ||
|
|
554cf89ac2 | ||
|
|
df36b65916 | ||
|
|
d9a6b37d19 | ||
|
|
0629b017b1 | ||
|
|
43aa62ac56 | ||
|
|
22c2044f17 | ||
|
|
7457632990 | ||
|
|
361cc08e3c | ||
|
|
e742905fbd | ||
|
|
4b719deddf | ||
|
|
b57e79ca5f | ||
|
|
6ef4c78b3b | ||
|
|
f0002293a0 | ||
|
|
29997c40b2 | ||
|
|
ac5d54e6c1 | ||
|
|
b3eae8dad1 | ||
|
|
6804e81249 | ||
|
|
8639566909 | ||
|
|
87bf6255f4 | ||
|
|
6084af8fbf | ||
|
|
8ec124c47c | ||
|
|
b8bb881820 | ||
|
|
ccb4842e13 | ||
|
|
3a7addad2e | ||
|
|
644f4eb1e6 | ||
|
|
00bab8f76b | ||
|
|
72c084d7c0 | ||
|
|
12e7d2d8fb | ||
|
|
f5bb058541 | ||
|
|
5871252869 | ||
|
|
d5bf498d3d | ||
|
|
eb647aeea3 | ||
|
|
451f187856 | ||
|
|
f79c7d4ace | ||
|
|
01a44be0ca | ||
|
|
4dc6424fed | ||
|
|
a989fe06c2 | ||
|
|
1b5a877b98 | ||
|
|
9c9f79b0b6 | ||
|
|
6fef6d679e | ||
|
|
aa54b9dff9 | ||
|
|
05a704e7ec | ||
|
|
09e6518870 | ||
|
|
4d70d0998c | ||
|
|
9d0d957ec3 | ||
|
|
1be4c89f3c | ||
|
|
893e67dfa4 | ||
|
|
eced71b2be | ||
|
|
1aa24393e4 | ||
|
|
06b0d9aa3e | ||
|
|
6d73ba2d96 | ||
|
|
43785f1a7d | ||
|
|
f80e923c2a | ||
|
|
f772b3d07b | ||
|
|
a6ae5d795a | ||
|
|
c08286874a | ||
|
|
7b10bee68a | ||
|
|
d42098d1c0 | ||
|
|
9563f1ed44 | ||
|
|
1f6b24db33 | ||
|
|
5f3a22bb63 | ||
|
|
574c7706a0 | ||
|
|
46a43efd70 | ||
|
|
e8bba8af24 | ||
|
|
b9466bf7b2 | ||
|
|
b0d8401274 | ||
|
|
1063cd5899 | ||
|
|
8f4b1022e2 | ||
|
|
6d45f6d1b6 | ||
|
|
6beafaa747 | ||
|
|
fe80ee3c18 | ||
|
|
a5d6600dea | ||
|
|
8132311c75 | ||
|
|
9ecd7e9a79 | ||
|
|
b3ff717d32 | ||
|
|
15f1cebcdb | ||
|
|
6d9eaf4109 | ||
|
|
3f0882ead6 | ||
|
|
9717f8288e | ||
|
|
d253bc34e5 | ||
|
|
0f56e43451 | ||
|
|
ed00f029a6 | ||
|
|
ee35e1dc99 | ||
|
|
84881ecb45 | ||
|
|
369a9cb9bc | ||
|
|
b9deead084 | ||
|
|
723e7442af | ||
|
|
8189a59d59 | ||
|
|
cc209be4f9 | ||
|
|
1d9e8e863c | ||
|
|
c37fd57b2c | ||
|
|
54420387ab | ||
|
|
173b2d13da | ||
|
|
d60506c63f | ||
|
|
2d80c44181 | ||
|
|
eb5df86733 | ||
|
|
096e1d636d | ||
|
|
434836782c | ||
|
|
3b9e50ada7 | ||
|
|
78a16e41dc | ||
|
|
2bc4a8c807 | ||
|
|
5066351b24 | ||
|
|
c521fba4a6 | ||
|
|
174942f509 | ||
|
|
9bf7b250e0 | ||
|
|
32c2a4d68a | ||
|
|
64f96c68dc | ||
|
|
9807bf5476 | ||
|
|
5a630887c4 | ||
|
|
2a8fb902da | ||
|
|
08fcf84d6c | ||
|
|
d162996042 | ||
|
|
7bd8c842c8 | ||
|
|
fd36f6fc71 | ||
|
|
fd9158e7c1 | ||
|
|
effd24ccd4 | ||
|
|
1c730905ec | ||
|
|
9a8991ebf2 | ||
|
|
014030513a | ||
|
|
bfb85271d4 | ||
|
|
7c13c130b8 | ||
|
|
347a795b5f | ||
|
|
1a3c888f44 | ||
|
|
90ee59754d | ||
|
|
c33c6187ca | ||
|
|
99019cd22f | ||
|
|
3dc2ed9629 | ||
|
|
99722a6f94 | ||
|
|
75580feaa0 | ||
|
|
2d573da503 | ||
|
|
2687023b6f | ||
|
|
0993186e01 | ||
|
|
ede495a145 | ||
|
|
c82ae71726 | ||
|
|
cd13577d93 | ||
|
|
1d24543359 | ||
|
|
08f04187ae | ||
|
|
8fbe1159f5 | ||
|
|
2c64f50093 | ||
|
|
cb646284c6 | ||
|
|
72c4f810fb | ||
|
|
6170f49673 | ||
|
|
d486a99aa1 | ||
|
|
f0bd81125b | ||
|
|
251882a400 | ||
|
|
0503d03e64 | ||
|
|
fed98f470f | ||
|
|
95d4dbc905 | ||
|
|
54b9da287f | ||
|
|
67ea623ba3 | ||
|
|
9dbddd2c18 | ||
|
|
72345e1394 | ||
|
|
f3662d4ca8 | ||
|
|
386f5a8cf7 | ||
|
|
d23def1c02 | ||
|
|
2861940b30 | ||
|
|
14f1e00540 | ||
|
|
f1232eb83a | ||
|
|
42882265dd | ||
|
|
56d3c86b60 | ||
|
|
445d335974 | ||
|
|
4825dc6644 | ||
|
|
33ad6d5950 | ||
|
|
8ad02bd24c | ||
|
|
412227b958 | ||
|
|
754b864647 | ||
|
|
65571c4fb6 | ||
|
|
81bcc0def5 | ||
|
|
24e060cb3b | ||
|
|
b46ae1e77c | ||
|
|
45b655c34c | ||
|
|
0d954b2132 | ||
|
|
6a215b81c7 | ||
|
|
bfc1544ed1 | ||
|
|
6866216dd0 | ||
|
|
42b9a54a45 | ||
|
|
f6282fb798 | ||
|
|
830f1324ec | ||
|
|
f8a425e188 | ||
|
|
117aab6bef | ||
|
|
c73566caef | ||
|
|
4167abb00e | ||
|
|
3e74292fc0 | ||
|
|
b301376fc6 | ||
|
|
262f5ff3e7 | ||
|
|
d379858dd2 | ||
|
|
368832d0d5 | ||
|
|
be1d841039 | ||
|
|
6ae47a908b | ||
|
|
797a21ee9f | ||
|
|
7f1db60c45 | ||
|
|
fb530645b8 | ||
|
|
7f932630fc | ||
|
|
458efb376a | ||
|
|
3ffbe022e5 | ||
|
|
762c529563 | ||
|
|
0c224b4b84 | ||
|
|
bbbc924fcd | ||
|
|
cc5fb49ff8 | ||
|
|
189bc763dd | ||
|
|
78b7639400 | ||
|
|
56954962a3 | ||
|
|
5ff673ae7d | ||
|
|
2eb995d621 | ||
|
|
573ddb6fda | ||
|
|
708a3f9c4a | ||
|
|
a73c659a1e | ||
|
|
b22143a3b6 | ||
|
|
6195750b41 | ||
|
|
889b1097cc | ||
|
|
41cdb8bcdd | ||
|
|
8c33acfd35 | ||
|
|
0222a34286 | ||
|
|
a90c9802ab | ||
|
|
94da8ca950 | ||
|
|
9a9a4fef65 | ||
|
|
3a7b1f48c7 | ||
|
|
a46bbb409f | ||
|
|
08d16c2ca2 | ||
|
|
b501352ddc | ||
|
|
1c51598366 | ||
|
|
db839a9ccd | ||
|
|
5aba426b98 | ||
|
|
6c184efa92 | ||
|
|
028a090864 | ||
|
|
7e097a7ee9 | ||
|
|
ce369638da | ||
|
|
c8054ff9d2 | ||
|
|
e0e02f1e5f | ||
|
|
7f671909bb | ||
|
|
d2ca0b3f33 | ||
|
|
e448a265c0 | ||
|
|
7a3774be7c | ||
|
|
07930f6a56 | ||
|
|
8172ab535f | ||
|
|
a9d75e3c35 | ||
|
|
a51e5fb4a7 | ||
|
|
cb3cd9d840 | ||
|
|
55123d236d | ||
|
|
13364b5806 | ||
|
|
7f1b92927c | ||
|
|
7ce65b3095 | ||
|
|
8e599e2d21 | ||
|
|
db6924deb6 | ||
|
|
86b64d0415 | ||
|
|
19a53fafd3 | ||
|
|
ea3c6696e2 | ||
|
|
f64a37a633 | ||
|
|
e816e011b1 | ||
|
|
5aa08cfd0c | ||
|
|
0a822211de | ||
|
|
2b049e1717 | ||
|
|
c01a83b466 | ||
|
|
089c856df3 | ||
|
|
603791de5b | ||
|
|
3caa2fc452 | ||
|
|
c78c66faf5 | ||
|
|
110ea17d69 | ||
|
|
da194eab14 | ||
|
|
fcfa8470b2 | ||
|
|
d092bd21c1 | ||
|
|
eafb2eedae | ||
|
|
05e3c59a46 | ||
|
|
0865496d76 | ||
|
|
e6a4c85adc | ||
|
|
3c76f6fcb5 | ||
|
|
c41d30f883 | ||
|
|
16fe532dc8 | ||
|
|
a429196d31 | ||
|
|
5f3643bd6a | ||
|
|
62824da779 | ||
|
|
e131b674fd | ||
|
|
c710cff9c8 | ||
|
|
68b8c489b2 | ||
|
|
5bb3bf6994 | ||
|
|
4169bcd6ae | ||
|
|
11802f58d1 | ||
|
|
a9cfecfc91 | ||
|
|
dda27c9194 | ||
|
|
596a277434 | ||
|
|
b5fcb6b235 | ||
|
|
84190e7c6e | ||
|
|
b6aa8f9e96 | ||
|
|
6308a4bc2d | ||
|
|
fe640249e4 | ||
|
|
56822f6bd6 | ||
|
|
8e639bd03f | ||
|
|
e125ff899f | ||
|
|
efb1ce0324 | ||
|
|
0999d439dd | ||
|
|
0521ecb250 | ||
|
|
a5a0cbc557 | ||
|
|
26a2519451 | ||
|
|
899ea8b9e8 | ||
|
|
4467081169 | ||
|
|
6d0128aec4 | ||
|
|
3007ad001e | ||
|
|
ba786a62f7 | ||
|
|
6eee8cecd8 | ||
|
|
1a2de11e92 | ||
|
|
293d08deec | ||
|
|
396f4e58de | ||
|
|
8100c090fa | ||
|
|
4513c372a8 | ||
|
|
0b3df3a155 | ||
|
|
04308e71de | ||
|
|
f2a6e8f54c | ||
|
|
9e73b51116 | ||
|
|
34e7b6741f | ||
|
|
db90f29513 | ||
|
|
24035683a9 | ||
|
|
29737f720e | ||
|
|
0caaadc774 | ||
|
|
e5601010e8 | ||
|
|
56396d11ef | ||
|
|
bb44b3f718 | ||
|
|
cac9d329e1 | ||
|
|
0a32f38f91 | ||
|
|
6c0cf516c0 | ||
|
|
fda25bbd30 | ||
|
|
495d947fe9 | ||
|
|
1b5297ab04 | ||
|
|
b136df86a1 | ||
|
|
5b44ecaae7 | ||
|
|
811451f24e | ||
|
|
602b5564b0 | ||
|
|
fcf86c5e75 | ||
|
|
c545c96fc1 | ||
|
|
15eccefecb | ||
|
|
671634b99c | ||
|
|
b8f0671227 | ||
|
|
70b6d54af7 | ||
|
|
8a81fe821f | ||
|
|
3870b86a31 | ||
|
|
b0b489704e | ||
|
|
1f508039e8 | ||
|
|
dfc5d0d0ad | ||
|
|
52d8fdd976 | ||
|
|
f53edaa75c | ||
|
|
e7feeaddbe | ||
|
|
d0afc66b27 | ||
|
|
ed8ff0db97 | ||
|
|
65dbd203ca | ||
|
|
c578803b62 | ||
|
|
e3b68fd02d | ||
|
|
d16b9fac7c | ||
|
|
ea873ae3bc | ||
|
|
993cdc7492 | ||
|
|
237d11e0c6 | ||
|
|
c3e794b4e8 | ||
|
|
7adc14a5aa | ||
|
|
2ab14d62be | ||
|
|
39058213fa | ||
|
|
804c67ef9a | ||
|
|
38645af1a9 | ||
|
|
879272dfe4 | ||
|
|
d93e691534 | ||
|
|
b2ea4a2cdd | ||
|
|
70aa420406 | ||
|
|
ca70068d88 | ||
|
|
3c9fc8f0c3 | ||
|
|
562bec14fe | ||
|
|
93dbaa516a | ||
|
|
69ba176990 | ||
|
|
f613487c4d | ||
|
|
5badd8fc51 | ||
|
|
95267d4b3e | ||
|
|
f998ea2a52 | ||
|
|
c97f9e2c59 | ||
|
|
b336198853 | ||
|
|
dfeaa4fdb7 | ||
|
|
4f72f284ce | ||
|
|
5ab1f04ea5 | ||
|
|
aa71866a17 | ||
|
|
dcec78b00e | ||
|
|
4944210ef1 | ||
|
|
e2d77684e3 | ||
|
|
d2eb9eb4aa | ||
|
|
a690b177ca | ||
|
|
aa94ba8b68 | ||
|
|
c999828197 | ||
|
|
6b17d862e2 | ||
|
|
439f5b8851 | ||
|
|
54844ecea1 | ||
|
|
2fadea10b0 | ||
|
|
e550c08273 | ||
|
|
d1e46104ae | ||
|
|
59a617a40d | ||
|
|
1bbd6c8225 | ||
|
|
5fcf08e4c5 | ||
|
|
6c256469e9 | ||
|
|
055d87ce9d | ||
|
|
0acb0dc3e7 | ||
|
|
47d7baaf5f | ||
|
|
fe4f7ffb58 | ||
|
|
c6eb24020b | ||
|
|
d0a086e84b | ||
|
|
5a7a7c9e60 | ||
|
|
75af26c55e | ||
|
|
64692a925b | ||
|
|
e19c9e12dc | ||
|
|
b70373ea31 | ||
|
|
e3ea0c8140 | ||
|
|
d881a0ae25 | ||
|
|
34459f54ef | ||
|
|
168c15ce02 | ||
|
|
d4576bbdd4 | ||
|
|
2dad83e965 | ||
|
|
22a1df82e1 | ||
|
|
4de4e07d99 | ||
|
|
0809a6e70c | ||
|
|
c9fab19ce8 | ||
|
|
8c3a52d40c | ||
|
|
69120bb4de | ||
|
|
b80faf911a | ||
|
|
fb2e1884f5 | ||
|
|
1d803af331 | ||
|
|
730b6d38a5 | ||
|
|
fdbb24962a | ||
|
|
4963579e20 | ||
|
|
46007853c7 | ||
|
|
74f99e9ee7 | ||
|
|
58a2d0bea7 | ||
|
|
dd594245f6 | ||
|
|
e6bad26ef0 | ||
|
|
acbc661efb | ||
|
|
0cdb34032e | ||
|
|
74c7b9e4f5 | ||
|
|
5b1abf0fc9 | ||
|
|
b680999e62 | ||
|
|
87255733a5 | ||
|
|
08a4a73b53 | ||
|
|
4b3fab563a | ||
|
|
583f8249cb | ||
|
|
3929f3e809 |
558
.cproject
558
.cproject
@@ -3,383 +3,13 @@
|
||||
|
||||
<storageModule moduleId="org.eclipse.cdt.core.settings">
|
||||
|
||||
<cconfiguration id="cdt.managedbuild.toolchain.gnu.cross.base.293933348">
|
||||
<cconfiguration id="org.eclipse.cdt.core.default.config.959280881">
|
||||
|
||||
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.cross.base.293933348" moduleId="org.eclipse.cdt.core.settings" name="Default">
|
||||
|
||||
<macros>
|
||||
|
||||
<stringMacro name="RECOVERY_APPLICATION" type="VALUE_TEXT" value="0"/>
|
||||
|
||||
</macros>
|
||||
<storageModule buildSystemId="org.eclipse.cdt.core.defaultConfigDataProvider" id="org.eclipse.cdt.core.default.config.959280881" moduleId="org.eclipse.cdt.core.settings" name="Configuration">
|
||||
|
||||
<externalSettings/>
|
||||
|
||||
<extensions>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
</extensions>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
|
||||
<configuration artifactName="${ProjName}" buildProperties="" description="" id="cdt.managedbuild.toolchain.gnu.cross.base.293933348" name="Default" optionalBuildProperties="" parent="org.eclipse.cdt.build.core.emptycfg">
|
||||
|
||||
<folderInfo id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.949515869" name="/" resourcePath="">
|
||||
|
||||
<toolChain id="cdt.managedbuild.toolchain.gnu.cross.base.1860816932" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.base">
|
||||
|
||||
<option id="cdt.managedbuild.option.gnu.cross.prefix.211558150" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix"/>
|
||||
|
||||
<option id="cdt.managedbuild.option.gnu.cross.path.660444977" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path"/>
|
||||
|
||||
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.1109615480" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
|
||||
|
||||
<builder arguments="-j8 EXTRA_CPPFLAGS="-DRECOVERY_APPLICATION=0"" command="make" id="cdt.managedbuild.builder.gnu.cross.1247197310" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" superClass="cdt.managedbuild.builder.gnu.cross"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.c.compiler.924305212" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler">
|
||||
|
||||
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="true" id="gnu.c.compiler.option.preprocessor.def.symbols.237333664" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" useByScannerDiscovery="false" valueType="definedSymbols"/>
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.851339966" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.1689301712" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler">
|
||||
|
||||
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="true" id="gnu.cpp.compiler.option.preprocessor.def.1320841573" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols"/>
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.1117032298" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.c.linker.69706729" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.cpp.linker.1919651858" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker">
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.1241928244" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
|
||||
|
||||
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
|
||||
|
||||
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
|
||||
|
||||
</inputType>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.archiver.1543827445" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.assembler.1248561272" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler">
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.assembler.input.736707865" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
</toolChain>
|
||||
|
||||
</folderInfo>
|
||||
|
||||
</configuration>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
|
||||
|
||||
</cconfiguration>
|
||||
|
||||
<cconfiguration id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934">
|
||||
|
||||
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934" moduleId="org.eclipse.cdt.core.settings" name="recovery">
|
||||
|
||||
<macros>
|
||||
|
||||
<stringMacro name="RECOVERY_APPLICATION" type="VALUE_TEXT" value="1"/>
|
||||
|
||||
</macros>
|
||||
|
||||
<externalSettings/>
|
||||
|
||||
<extensions>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
</extensions>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
|
||||
<configuration artifactName="${ProjName}" buildProperties="" description="" id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934" name="recovery" optionalBuildProperties="" parent="org.eclipse.cdt.build.core.emptycfg">
|
||||
|
||||
<folderInfo id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934." name="/" resourcePath="">
|
||||
|
||||
<toolChain id="cdt.managedbuild.toolchain.gnu.cross.base.1561608239" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.base">
|
||||
|
||||
<option id="cdt.managedbuild.option.gnu.cross.prefix.878380733" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix"/>
|
||||
|
||||
<option id="cdt.managedbuild.option.gnu.cross.path.576225618" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path"/>
|
||||
|
||||
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.1674304340" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
|
||||
|
||||
<builder arguments="-j8 EXTRA_CPPFLAGS="-DRECOVERY_APPLICATION=1"" command="make" id="cdt.managedbuild.builder.gnu.cross.1616827916" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" superClass="cdt.managedbuild.builder.gnu.cross"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.c.compiler.1397900624" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler">
|
||||
|
||||
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="true" id="gnu.c.compiler.option.preprocessor.def.symbols.1168574489" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" useByScannerDiscovery="false" valueType="definedSymbols"/>
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.24917724" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.40066190" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler">
|
||||
|
||||
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="true" id="gnu.cpp.compiler.option.preprocessor.def.1538103313" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols"/>
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.773825889" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.c.linker.750042642" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.cpp.linker.791695355" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker">
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.363611836" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
|
||||
|
||||
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
|
||||
|
||||
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
|
||||
|
||||
</inputType>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.archiver.788163154" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.assembler.747849588" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler">
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.assembler.input.597864277" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
</toolChain>
|
||||
|
||||
</folderInfo>
|
||||
|
||||
</configuration>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
|
||||
|
||||
</cconfiguration>
|
||||
|
||||
<cconfiguration id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291">
|
||||
|
||||
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291" moduleId="org.eclipse.cdt.core.settings" name="recovery_windows">
|
||||
|
||||
<macros>
|
||||
|
||||
<stringMacro name="RECOVERY_APPLICATION" type="VALUE_TEXT" value="1"/>
|
||||
|
||||
</macros>
|
||||
|
||||
<externalSettings/>
|
||||
|
||||
<extensions>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
</extensions>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
|
||||
<configuration artifactName="${ProjName}" buildProperties="" description="Building recovery in windows" id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291" name="recovery_windows" optionalBuildProperties="org.eclipse.cdt.docker.launcher.containerbuild.property.selectedvolumes=,org.eclipse.cdt.docker.launcher.containerbuild.property.volumes=" parent="org.eclipse.cdt.build.core.emptycfg">
|
||||
|
||||
<folderInfo id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291." name="/" resourcePath="">
|
||||
|
||||
<toolChain id="cdt.managedbuild.toolchain.gnu.cross.base.845245133" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.base">
|
||||
|
||||
<option id="cdt.managedbuild.option.gnu.cross.prefix.1090509495" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix"/>
|
||||
|
||||
<option id="cdt.managedbuild.option.gnu.cross.path.447265559" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path"/>
|
||||
|
||||
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.1831977109" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
|
||||
|
||||
<builder arguments=""c:/msys32/opt/esp-idf/tools/windows/eclipse_make.py" EXTRA_CPPFLAGS='-DRECOVERY_APPLICATION=${RECOVERY_APPLICATION}'" buildPath="${workspace_loc:/squeezelite-esp32}" command="python" id="cdt.managedbuild.builder.gnu.cross.1069921467" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" parallelBuildOn="true" parallelizationNumber="optimal" superClass="cdt.managedbuild.builder.gnu.cross"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.c.compiler.1302011176" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler">
|
||||
|
||||
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="true" id="gnu.c.compiler.option.preprocessor.def.symbols.623798750" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" useByScannerDiscovery="false" valueType="definedSymbols"/>
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.539301587" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.1722031516" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler">
|
||||
|
||||
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="true" id="gnu.cpp.compiler.option.preprocessor.def.2010227748" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols"/>
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.2073997022" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.c.linker.746651743" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.cpp.linker.149944553" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker">
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.1372009292" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
|
||||
|
||||
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
|
||||
|
||||
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
|
||||
|
||||
</inputType>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.archiver.649046248" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.assembler.2132030687" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler">
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.assembler.input.1779870241" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
</toolChain>
|
||||
|
||||
</folderInfo>
|
||||
|
||||
</configuration>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
|
||||
|
||||
</cconfiguration>
|
||||
|
||||
<cconfiguration id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736">
|
||||
|
||||
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736" moduleId="org.eclipse.cdt.core.settings" name="squeezelite_windows">
|
||||
|
||||
<macros>
|
||||
|
||||
<stringMacro name="RECOVERY_APPLICATION" type="VALUE_TEXT" value="0"/>
|
||||
|
||||
</macros>
|
||||
|
||||
<externalSettings/>
|
||||
|
||||
<extensions>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
|
||||
</extensions>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
|
||||
<configuration artifactName="${ProjName}" buildProperties="" description="building squeezelite app in windows" id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736" name="squeezelite_windows" optionalBuildProperties="" parent="org.eclipse.cdt.build.core.emptycfg">
|
||||
|
||||
<folderInfo id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736." name="/" resourcePath="">
|
||||
|
||||
<toolChain id="cdt.managedbuild.toolchain.gnu.cross.base.1034176750" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.base">
|
||||
|
||||
<option id="cdt.managedbuild.option.gnu.cross.prefix.1038632104" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix"/>
|
||||
|
||||
<option id="cdt.managedbuild.option.gnu.cross.path.1589817380" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path"/>
|
||||
|
||||
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.784380822" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
|
||||
|
||||
<builder arguments=""C:/msys32/opt/esp-idf/tools/windows/eclipse_make.py" -j8 EXTRA_CPPFLAGS="-DRECOVERY_APPLICATION=0"" command="python" id="cdt.managedbuild.builder.gnu.cross.1150681639" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" superClass="cdt.managedbuild.builder.gnu.cross"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.c.compiler.824219909" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler">
|
||||
|
||||
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="true" id="gnu.c.compiler.option.preprocessor.def.symbols.217201640" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" useByScannerDiscovery="false" valueType="definedSymbols"/>
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.644208200" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.1907231332" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler">
|
||||
|
||||
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="true" id="gnu.cpp.compiler.option.preprocessor.def.959275134" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols"/>
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.604467026" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.c.linker.1073903870" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.cpp.linker.898376794" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker">
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.1560070168" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
|
||||
|
||||
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
|
||||
|
||||
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
|
||||
|
||||
</inputType>
|
||||
|
||||
</tool>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.archiver.1462690215" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/>
|
||||
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.assembler.40469999" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler">
|
||||
|
||||
<inputType id="cdt.managedbuild.tool.gnu.assembler.input.798539361" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
|
||||
|
||||
</tool>
|
||||
|
||||
</toolChain>
|
||||
|
||||
</folderInfo>
|
||||
|
||||
</configuration>
|
||||
<extensions/>
|
||||
|
||||
</storageModule>
|
||||
|
||||
@@ -389,190 +19,14 @@
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="org.eclipse.cdt.core.pathentry"/>
|
||||
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
<storageModule moduleId="org.eclipse.cdt.core.pathentry">
|
||||
|
||||
<project id="squeezelite-esp32-merge.null.1711307563" name="squeezelite-esp32-merge"/>
|
||||
<pathentry excluding="**/CMakeFiles/**" kind="out" path="build"/>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
|
||||
|
||||
<storageModule moduleId="refreshScope" versionNumber="2">
|
||||
|
||||
<configuration configurationName="squeezelite_windows">
|
||||
|
||||
<resource resourceType="PROJECT" workspacePath="/squeezelite-esp32-merge"/>
|
||||
|
||||
</configuration>
|
||||
|
||||
<configuration configurationName="recovery_windows">
|
||||
|
||||
<resource resourceType="PROJECT" workspacePath="/squeezelite-esp32"/>
|
||||
|
||||
</configuration>
|
||||
|
||||
<configuration configurationName="recovery">
|
||||
|
||||
<resource resourceType="PROJECT" workspacePath="/squeezelite-esp32"/>
|
||||
|
||||
</configuration>
|
||||
|
||||
<configuration configurationName="Default">
|
||||
|
||||
<resource resourceType="PROJECT" workspacePath="/squeezelite-esp32"/>
|
||||
|
||||
</configuration>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings"/>
|
||||
|
||||
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets">
|
||||
|
||||
<buildTargets>
|
||||
|
||||
<target name="all" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
|
||||
|
||||
<buildCommand>make</buildCommand>
|
||||
|
||||
<buildArguments>-j8 EXTRA_CPPFLAGS="-DRECOVERY_APPLICATION=1"</buildArguments>
|
||||
|
||||
<buildTarget>all</buildTarget>
|
||||
|
||||
<stopOnError>true</stopOnError>
|
||||
|
||||
<useDefaultCommand>true</useDefaultCommand>
|
||||
|
||||
<runAllBuilders>true</runAllBuilders>
|
||||
|
||||
</target>
|
||||
|
||||
<target name="app" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
|
||||
|
||||
<buildCommand>make</buildCommand>
|
||||
|
||||
<buildArguments>-j8 EXTRA_CPPFLAGS="-DRECOVERY_APPLICATION=1"</buildArguments>
|
||||
|
||||
<buildTarget>app</buildTarget>
|
||||
|
||||
<stopOnError>true</stopOnError>
|
||||
|
||||
<useDefaultCommand>true</useDefaultCommand>
|
||||
|
||||
<runAllBuilders>true</runAllBuilders>
|
||||
|
||||
</target>
|
||||
|
||||
<target name="clean" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
|
||||
|
||||
<buildCommand>make</buildCommand>
|
||||
|
||||
<buildArguments>-j8 EXTRA_CPPFLAGS="-DRECOVERY_APPLICATION=1"</buildArguments>
|
||||
|
||||
<buildTarget>clean</buildTarget>
|
||||
|
||||
<stopOnError>true</stopOnError>
|
||||
|
||||
<useDefaultCommand>true</useDefaultCommand>
|
||||
|
||||
<runAllBuilders>true</runAllBuilders>
|
||||
|
||||
</target>
|
||||
|
||||
</buildTargets>
|
||||
|
||||
</storageModule>
|
||||
|
||||
<storageModule moduleId="scannerConfiguration">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cygwin.base.58932738;cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.1067614858;cdt.managedbuild.tool.gnu.c.compiler.cygwin.base.1865841553;cdt.managedbuild.tool.gnu.c.compiler.input.cygwin.1383814557">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934;cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934.;cdt.managedbuild.tool.gnu.cross.cpp.compiler.40066190;cdt.managedbuild.tool.gnu.cpp.compiler.input.773825889">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736;cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736.;cdt.managedbuild.tool.gnu.cross.cpp.compiler.1907231332;cdt.managedbuild.tool.gnu.cpp.compiler.input.604467026">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291;cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.;cdt.managedbuild.tool.gnu.cross.cpp.compiler.1722031516;cdt.managedbuild.tool.gnu.cpp.compiler.input.2073997022">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291;cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.;cdt.managedbuild.tool.gnu.cross.c.compiler.1302011176;cdt.managedbuild.tool.gnu.c.compiler.input.539301587">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cygwin.base.58932738;cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.1067614858;cdt.managedbuild.tool.gnu.cpp.compiler.cygwin.base.410547198;cdt.managedbuild.tool.gnu.cpp.compiler.input.cygwin.1499974240">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736;cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736.;cdt.managedbuild.tool.gnu.cross.c.compiler.824219909;cdt.managedbuild.tool.gnu.c.compiler.input.644208200">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707;cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707.;cdt.managedbuild.tool.gnu.c.compiler.cygwin.base.211315976;cdt.managedbuild.tool.gnu.c.compiler.input.cygwin.857914729">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.1943329896;cdt.managedbuild.toolchain.gnu.cross.base.1943329896.30011915;cdt.managedbuild.tool.gnu.cross.cpp.compiler.1749746745;cdt.managedbuild.tool.gnu.cpp.compiler.input.1914005798">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.1476804786;cdt.managedbuild.toolchain.gnu.cross.base.1476804786.1800826258;cdt.managedbuild.tool.gnu.cross.cpp.compiler.254690821;cdt.managedbuild.tool.gnu.cpp.compiler.input.1365876654">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.1943329896;cdt.managedbuild.toolchain.gnu.cross.base.1943329896.30011915;cdt.managedbuild.tool.gnu.cross.c.compiler.2083405506;cdt.managedbuild.tool.gnu.c.compiler.input.404320567">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707;cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707.;cdt.managedbuild.tool.gnu.cpp.compiler.cygwin.base.145410566;cdt.managedbuild.tool.gnu.cpp.compiler.input.cygwin.1181636367">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.1476804786;cdt.managedbuild.toolchain.gnu.cross.base.1476804786.1800826258;cdt.managedbuild.tool.gnu.cross.c.compiler.1502936757;cdt.managedbuild.tool.gnu.c.compiler.input.1614739014">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
<scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934;cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934.;cdt.managedbuild.tool.gnu.cross.c.compiler.1397900624;cdt.managedbuild.tool.gnu.c.compiler.input.24917724">
|
||||
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
|
||||
</scannerConfigBuildInfo>
|
||||
|
||||
</storageModule>
|
||||
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"/>
|
||||
|
||||
</cproject>
|
||||
|
||||
162
.github/workflows/CrossBuild.yml
vendored
Normal file
162
.github/workflows/CrossBuild.yml
vendored
Normal 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
232
.github/workflows/Platform_build.yml
vendored
Normal 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
67
.github/workflows/codeql-analysis.yml
vendored
Normal 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
167
.github/workflows/esp-idf-v4.3-build.yml
vendored
Normal 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
23
.github/workflows/web_deploy.yml
vendored
Normal 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}}"
|
||||
83
.gitignore
vendored
83
.gitignore
vendored
@@ -1,69 +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
|
||||
/_*
|
||||
sdkconfig
|
||||
squeezelite-esp32-jsonblob.zip
|
||||
components/wifi-manager/esp32_improv.h.txt
|
||||
|
||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@@ -2,6 +2,9 @@
|
||||
path = components/telnet/libtelnet
|
||||
url = https://github.com/seanmiddleditch/libtelnet
|
||||
branch = develop
|
||||
[submodule "esp-dsp"]
|
||||
path = esp-dsp
|
||||
url = https://github.com/philippe44/esp-dsp
|
||||
[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
|
||||
|
||||
16
.project
16
.project
@@ -1,28 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>squeezelite-esp32</name>
|
||||
<name>squeezelite-esp32-idfv4</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
<project>esp-idf</project>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
|
||||
<name>org.eclipse.cdt.core.cBuilder</name>
|
||||
<triggers>clean,full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
|
||||
<triggers>full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.cdt.core.cnature</nature>
|
||||
<nature>org.eclipse.cdt.core.ccnature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
|
||||
<nature>com.espressif.idf.core.idfNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
</projectDescription>
|
||||
1
.settings/.gitignore
vendored
1
.settings/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/org.eclipse.ltk.core.refactoring.prefs
|
||||
@@ -1,100 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project>
|
||||
|
||||
<configuration id="cdt.managedbuild.toolchain.gnu.cross.base.293933348" name="Default">
|
||||
|
||||
<extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
|
||||
|
||||
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
|
||||
|
||||
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
|
||||
|
||||
<provider copy-of="extension" id="org.eclipse.cdt.managedbuilder.core.GCCBuildCommandParser"/>
|
||||
|
||||
<provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="66711578333" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD "${INPUTS}"" prefer-non-shared="true">
|
||||
|
||||
<language-scope id="org.eclipse.cdt.core.gcc"/>
|
||||
|
||||
<language-scope id="org.eclipse.cdt.core.g++"/>
|
||||
|
||||
</provider>
|
||||
|
||||
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
|
||||
|
||||
</extension>
|
||||
|
||||
</configuration>
|
||||
|
||||
<configuration id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934" name="recovery">
|
||||
|
||||
<extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
|
||||
|
||||
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
|
||||
|
||||
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
|
||||
|
||||
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuildCommandParser" id="org.eclipse.cdt.managedbuilder.core.GCCBuildCommandParser" keep-relative-paths="true" name="CDT GCC Build Output Parser" parameter="xtensa-esp32-elf-(gcc|g\+\+|c\+\+|cc|cpp|clang)" prefer-non-shared="true"/>
|
||||
|
||||
<provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="29929057949" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="xtensa-esp32-elf-gcc ${FLAGS} -std=c++11 -E -P -v -dD "${INPUTS}"" prefer-non-shared="true">
|
||||
|
||||
<language-scope id="org.eclipse.cdt.core.gcc"/>
|
||||
|
||||
<language-scope id="org.eclipse.cdt.core.g++"/>
|
||||
|
||||
</provider>
|
||||
|
||||
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
|
||||
|
||||
</extension>
|
||||
|
||||
</configuration>
|
||||
|
||||
<configuration id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291" name="recovery_windows">
|
||||
|
||||
<extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
|
||||
|
||||
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
|
||||
|
||||
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
|
||||
|
||||
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuildCommandParser" id="org.eclipse.cdt.managedbuilder.core.GCCBuildCommandParser" keep-relative-paths="false" name="CDT GCC Build Output Parser" parameter="(g?cc)|([gc]\+\+)|(clang)" prefer-non-shared="true"/>
|
||||
|
||||
<provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="1502478736382965477" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD "${INPUTS}"" prefer-non-shared="true">
|
||||
|
||||
<language-scope id="org.eclipse.cdt.core.gcc"/>
|
||||
|
||||
<language-scope id="org.eclipse.cdt.core.g++"/>
|
||||
|
||||
</provider>
|
||||
|
||||
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
|
||||
|
||||
</extension>
|
||||
|
||||
</configuration>
|
||||
|
||||
<configuration id="cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736" name="squeezelite_windows">
|
||||
|
||||
<extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
|
||||
|
||||
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
|
||||
|
||||
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
|
||||
|
||||
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuildCommandParser" id="org.eclipse.cdt.managedbuilder.core.GCCBuildCommandParser" keep-relative-paths="true" name="CDT GCC Build Output Parser" parameter="xtensa-esp32-elf-(gcc|g\+\+|c\+\+|cc|cpp|clang)" prefer-non-shared="true"/>
|
||||
|
||||
<provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="-1766868238676867652" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="xtensa-esp32-elf-gcc ${FLAGS} -std=c++11 -E -P -v -dD "${INPUTS}"" prefer-non-shared="true">
|
||||
|
||||
<language-scope id="org.eclipse.cdt.core.gcc"/>
|
||||
|
||||
<language-scope id="org.eclipse.cdt.core.g++"/>
|
||||
|
||||
</provider>
|
||||
|
||||
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
|
||||
|
||||
</extension>
|
||||
|
||||
</configuration>
|
||||
|
||||
</project>
|
||||
@@ -1,116 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/BATCH_BUILD/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/BATCH_BUILD/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/BATCH_BUILD/value=1
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/EXTRA_CFLAGS/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/EXTRA_CFLAGS/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/EXTRA_CFLAGS/value=-DRECOVERY_APPLICATION\=1
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/IDF_PATH/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/IDF_PATH/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/IDF_PATH/value=/var/opt/esp-idf
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/append=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786.212420495/appendContributed=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/BATCH_BUILD/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/BATCH_BUILD/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/BATCH_BUILD/value=1
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/EXTRA_CFLAGS/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/EXTRA_CFLAGS/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/EXTRA_CFLAGS/value=
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/IDF_PATH/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/IDF_PATH/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/IDF_PATH/value=/var/opt/esp-idf
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/PATH/delimiter=;
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/PATH/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/PATH/value=C\:/msys2/opt/xtensa-esp32-elf/bin;C\:/jdk-12.0.2/bin/server;C\:/jdk-12.0.2/bin;C\:\\Windows\\system32;C\:\\Windows;C\:\\Windows\\System32\\Wbem;C\:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C\:\\Program Files\\NVIDIA Corporation\\NVIDIA NvDLISR;C\:\\jdk-12.0.2\\bin;C\:\\Program Files\\PuTTY\\;C\:\\Program Files (x86)\\HP\\IdrsOCR_15.2.10.1114\\;C\:\\eclipse
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/append=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.1476804786/appendContributed=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/BATCH_BUILD/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/BATCH_BUILD/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/BATCH_BUILD/value=1
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/IDF_PATH/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/IDF_PATH/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/IDF_PATH/value=C\:/msys32/opt/esp-idf
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PATH/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PATH/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PATH/value=C\:\\msys32\\usr\\bin;C\:\\msys32\\mingw32\\bin;C\:\\msys32\\opt\\xtensa-esp32-elf\\bin\\
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PROJECT_NAME/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PROJECT_NAME/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PROJECT_NAME/value=recovery.custom
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PROJECT_VER/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PROJECT_VER/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/PROJECT_VER/value=custom
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/append=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291.395881736/appendContributed=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/BATCH_BUILD/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/BATCH_BUILD/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/BATCH_BUILD/value=1
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/IDF_PATH/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/IDF_PATH/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/IDF_PATH/value=C\:/msys32/opt/esp-idf
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PATH/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PATH/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PATH/value=C\:\\msys32\\opt\\openocd-esp32\\bin;c\:\\msys32\\opt\\xtensa-esp32-elf\\bin\\;c\:\\msys32\\mingw32\\bin;C\:\\msys32\\usr\\bin;c\:\\Python27;C\:\\msys32\\usr\\bin\\vendor_perl;C\:\\msys32\\usr\\bin\\core_perl
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PROJECT_NAME/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PROJECT_NAME/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PROJECT_NAME/value=recovery
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PROJECT_VER/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PROJECT_VER/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/PROJECT_VER/value=custom
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/append=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.1603996291/appendContributed=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/BATCH_BUILD/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/BATCH_BUILD/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/BATCH_BUILD/value=1
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/IDF_PATH/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/IDF_PATH/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/IDF_PATH/value=/var/opt/esp-idf/
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PATH/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PATH/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PATH/value=/var/opt/xtensa-esp32-elf/bin/\:/sbin\:/bin\:/usr/bin\:/usr/local/bin\:/snap/bin\:/bin\:/sbin\:/usr/bin\:/usr/local/bin\:/snap/bin
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PROJECT_NAME/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PROJECT_NAME/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PROJECT_NAME/value=recovery.custom
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PROJECT_VER/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PROJECT_VER/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/PROJECT_VER/value=custom
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/append=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348.839256934/appendContributed=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/BATCH_BUILD/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/BATCH_BUILD/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/BATCH_BUILD/value=1
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/IDF_PATH/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/IDF_PATH/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/IDF_PATH/value=/var/opt/esp-idf/
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PATH/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PATH/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PATH/value=/var/opt/xtensa-esp32-elf/bin/\:/sbin\:/bin\:/usr/bin\:/usr/local/bin\:/snap/bin\:/bin\:/sbin\:/usr/bin\:/usr/local/bin\:/snap/bin
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PROJECT_NAME/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PROJECT_NAME/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PROJECT_NAME/value=squeezelite.custom
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PROJECT_VER/delimiter=\:
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PROJECT_VER/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/PROJECT_VER/value=custom
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/append=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cross.base.293933348/appendContributed=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/BATCH_BUILD/delimiter=;
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/BATCH_BUILD/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/BATCH_BUILD/value=1
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/IDF_PATH/delimiter=;
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/IDF_PATH/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/IDF_PATH/value=c\:/msys32/opt/esp-idf
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/PATH/delimiter=;
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/PATH/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/PATH/value=C\:\\msys32\\usr\\bin;C\:\\msys32\\mingw32\\bin;C\:\\msys32\\opt\\xtensa-esp32-elf\\bin;C\:\\Python27
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/append=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738.859326707/appendContributed=false
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/BATCH_BUILD/delimiter=;
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/BATCH_BUILD/operation=append
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/BATCH_BUILD/value=1
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/IDF_PATH/delimiter=;
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/IDF_PATH/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/IDF_PATH/value=c\:/msys32/opt/esp-idf
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/PATH/delimiter=;
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/PATH/operation=replace
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/PATH/value=C\:\\msys32\\usr\\bin;C\:\\msys32\\mingw32\\bin;C\:\\msys32\\opt\\xtensa-esp32-elf\\bin;C\:\\Python27
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/append=true
|
||||
environment/project/cdt.managedbuild.toolchain.gnu.cygwin.base.58932738/appendContributed=false
|
||||
127
CMakeLists.txt
127
CMakeLists.txt
@@ -1,3 +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)
|
||||
project(squeezelite-esp32)
|
||||
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_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)
|
||||
175
Dockerfile
175
Dockerfile
@@ -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" ]
|
||||
|
||||
10
Makefile
10
Makefile
@@ -10,9 +10,13 @@
|
||||
|
||||
|
||||
#recovery: PROJECT_NAME:=recovery.$(PROJECT_CONFIG_TARGET)
|
||||
#recovery: CPPFLAGS+=-DRECOVERY_APPLICATION=1
|
||||
#recovery: EXTRA_CPPFLAGS+=-DRECOVERY_APPLICATION=1
|
||||
|
||||
PROJECT_NAME?=squeezelite
|
||||
EXTRA_COMPONENT_DIRS := esp-dsp
|
||||
EXTRA_CPPFLAGS+= -I$(PROJECT_PATH)/main
|
||||
|
||||
#/-Wno-error=maybe-uninitialized
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
CPPFLAGS+= -Wno-error=maybe-uninitialized
|
||||
|
||||
# for future gcc version, this could be needed: CPPFLAGS+= -Wno-error=format-overflow -Wno-error=stringop-truncation
|
||||
|
||||
|
||||
389
README.md
389
README.md
@@ -1,20 +1,91 @@
|
||||

|
||||

|
||||
# 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,99 +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
|
||||
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
|
||||
<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/
|
||||
And then copying the i2s.c patch file from this repo over to the esp-idf folder
|
||||
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
|
||||
|
||||
## 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
|
||||
|
||||
```
|
||||
# Build recovery.bin, bootloader.bin, ota_data_initial.bin, partitions.bin
|
||||
# force appropriate rebuild by touching all the files which may have a RECOVERY_APPLICATION specific source compile logic
|
||||
find . \( -name "*.cpp" -o -name "*.c" -o -name "*.h" \) -type f -print0 | xargs -0 grep -l "RECOVERY_APPLICATION" | xargs touch
|
||||
export PROJECT_NAME="recovery"
|
||||
make -j4 all EXTRA_CPPFLAGS='-DRECOVERY_APPLICATION=1'
|
||||
make flash
|
||||
#
|
||||
# Build squeezelite.bin
|
||||
# Now force a rebuild by touching all the files which may have a RECOVERY_APPLICATION specific source compile logic
|
||||
find . \( -name "*.cpp" -o -name "*.c" -o -name "*.h" \) -type f -print0 | xargs -0 grep -l "RECOVERY_APPLICATION" | xargs touch
|
||||
export PROJECT_NAME="squeezelite"
|
||||
make -j4 app EXTRA_CPPFLAGS='-DRECOVERY_APPLICATION=0'
|
||||
python ${IDF_PATH}/components/esptool_py/esptool/esptool.py --chip esp32 --port ${ESPPORT} --baud ${ESPBAUD} --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0x150000 ./build/squeezelite.bin
|
||||
# monitor serial output
|
||||
make monitor
|
||||
|
||||
```
|
||||
|
||||
You can also manually download the recovery & initial boot
|
||||
```
|
||||
python ${IDF_PATH}/components/esptool_py/esptool/esptool.py --chip esp32 --port ${ESPPORT} --baud ${ESPBAUD} --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0xd000 ./build/ota_data_initial.bin 0x1000 ./build/bootloader/bootloader.bin 0x10000 ./build/recovery.bin 0x8000 ./build/partitions.bin
|
||||
```
|
||||
|
||||
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]
|
||||
@@ -333,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
|
||||
@@ -350,22 +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 :-)
|
||||
|
||||
3
TODO
3
TODO
@@ -1,4 +1,5 @@
|
||||
- in squeezelite some buffers (stream, output, header, recv) are allocated
|
||||
although they are almost static (expect output). This creates a risk of
|
||||
memory fragmentation, especially because the large output is re-allocated for
|
||||
AirPlay
|
||||
AirPlay
|
||||
- libflac in lpc.c can be unrolled - that gains 43k of code, at the expense of 4% CPU
|
||||
65
alltags.txt
65
alltags.txt
@@ -1,65 +0,0 @@
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.15
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.20
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.47
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.49
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.50
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.51
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.52
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.53
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.54
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.55
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.59
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.60
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.61
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.62
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.63
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.64
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.65
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.66
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/I2S-4MFlash-v0.1.67
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/SqueezeAmp-v0.1.13
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/SqueezeAmp-v0.1.14
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/SqueezeAmp-v0.1.15
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/SqueezeAmp-v0.1.20
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/WiFi-Manager/SqueezeAmp-v0.1.66
|
||||
3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.12-I2S-4MFlash
|
||||
3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.12-SqueezeAmp
|
||||
3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.16-I2S-4MFlash
|
||||
3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.16-SqueezeAmp
|
||||
d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.44-I2S-4MFlash
|
||||
d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.44-SqueezeAmp
|
||||
d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.45-I2S-4MFlash
|
||||
d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.45-SqueezeAmp
|
||||
d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.46-I2S-4MFlash
|
||||
d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.46-SqueezeAmp
|
||||
d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.47-I2S-4MFlash
|
||||
d9c6c78df2f3ba49d74d91cf246f300a881af742 refs/tags/v0.1.47-SqueezeAmp
|
||||
3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.5-I2S-4MFlash
|
||||
3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.5-SqueezeAmp
|
||||
c34cf06be18ea4ab14ce28d77e1d48d0a1bb70f7 refs/tags/v0.1.50-I2S-4MFlash
|
||||
c34cf06be18ea4ab14ce28d77e1d48d0a1bb70f7 refs/tags/v0.1.50-SqueezeAmp
|
||||
c34cf06be18ea4ab14ce28d77e1d48d0a1bb70f7 refs/tags/v0.1.51-I2S-4MFlash
|
||||
c34cf06be18ea4ab14ce28d77e1d48d0a1bb70f7 refs/tags/v0.1.51-SqueezeAmp
|
||||
20edae43287b4c2d8942ea4263ccf9547f310946 refs/tags/v0.1.52-I2S-4MFlash
|
||||
20edae43287b4c2d8942ea4263ccf9547f310946 refs/tags/v0.1.52-SqueezeAmp
|
||||
964bb4d57d35bca06badfb504534b42e9b3b8678 refs/tags/v0.1.53-I2S-4MFlash
|
||||
964bb4d57d35bca06badfb504534b42e9b3b8678 refs/tags/v0.1.53-SqueezeAmp
|
||||
71531bcdc20d7c7da254699855eb2e1e7b2bd48f refs/tags/v0.1.54-I2S-4MFlash
|
||||
71531bcdc20d7c7da254699855eb2e1e7b2bd48f refs/tags/v0.1.54-SqueezeAmp
|
||||
9c56cfb1d05862bca5763b3fbe9911b4bab9619a refs/tags/v0.1.55-I2S-4MFlash
|
||||
9c56cfb1d05862bca5763b3fbe9911b4bab9619a refs/tags/v0.1.55-SqueezeAmp
|
||||
53369475dc85471a4d7f2d78c62f37fcd7f6e3da refs/tags/v0.1.57-I2S-4MFlash
|
||||
53369475dc85471a4d7f2d78c62f37fcd7f6e3da refs/tags/v0.1.57-SqueezeAmp
|
||||
3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.6-I2S-4MFlash
|
||||
3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.6-SqueezeAmp
|
||||
eef9ae969cdfd1a62d7057d8edf2c31af60804f3 refs/tags/v0.1.67-WiFi-Manager/I2S-4MFlash
|
||||
2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.71-I2S-4MFlash
|
||||
2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.71-SqueezeAmp
|
||||
2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.72-I2S-4MFlash
|
||||
2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.72-SqueezeAmp
|
||||
2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.73-I2S-4MFlash
|
||||
2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.73-SqueezeAmp
|
||||
2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.74-I2S-4MFlash
|
||||
2cf87d5943caa0d0b15c8692482ff72308c0c0a8 refs/tags/v0.1.74-SqueezeAmp
|
||||
3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.9-I2S-4MFlash
|
||||
3bd886b8dff0fb5bc59079e83f1d283097a14697 refs/tags/v0.1.9-SqueezeAmp
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1625
build-scripts/Muse-sdkconfig.defaults
Normal file
1625
build-scripts/Muse-sdkconfig.defaults
Normal file
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
1636
build-scripts/SqueezeAmp-sdkconfig.defaults
Normal file
1636
build-scripts/SqueezeAmp-sdkconfig.defaults
Normal file
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
47
buildFirmware.sh
Executable 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
|
||||
113
build_flash_cmd.sh
Normal file
113
build_flash_cmd.sh
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
echo
|
||||
echo =================================================================
|
||||
echo Build flash command
|
||||
echo =================================================================
|
||||
# Location of partitions.csv relative to this script
|
||||
partitionsCsv="../partitions.csv"
|
||||
|
||||
# File to output readme instructions to
|
||||
outputReadme="./flash_cmd.txt"
|
||||
|
||||
# File to output bash script to
|
||||
outputBashScript="./writeSequeezeEsp.sh"
|
||||
|
||||
# File to output bat script to
|
||||
outputBatScript="./writeSequeezeEsp.bat"
|
||||
|
||||
# The name of partitions to ignore from partitions.csv
|
||||
paritionsToIgnore=(
|
||||
"nvs"
|
||||
"phy_init"
|
||||
"storage"
|
||||
"coredump"
|
||||
"settings"
|
||||
)
|
||||
|
||||
# Function that maps partition name to actual bin file
|
||||
# defaults to "[PARTION_NAME_FROM_CSV].bin"
|
||||
function partitionNameToBinFile {
|
||||
if [[ "$1" == "otadata" ]]; then
|
||||
echo "ota_data_initial.bin"
|
||||
elif [[ "$1" == "ota_0" || "$1" == "factory" ]]; then
|
||||
echo "squeezelite.bin"
|
||||
else
|
||||
echo $1.bin
|
||||
fi
|
||||
}
|
||||
|
||||
# write parameters for esptool.py
|
||||
writeParameters="$writeParameters write_flash"
|
||||
writeParameters="$writeParameters --flash_mode dio --flash_freq 80m --flash_size detect"
|
||||
|
||||
# bootloader.bin and partitions.bin not in partitions.csv so manually add here
|
||||
partitionsParameters=" 0x1000 bootloader/bootloader.bin"
|
||||
partitionsParameters="$partitionsParameters 0x8000 partitions.bin"
|
||||
|
||||
# ==============================================================================
|
||||
|
||||
# Loop over partitions.csv and add partition bins and offsets to partitionsParameters
|
||||
|
||||
for line in $($IDF_PATH/components/partition_table/gen_esp32part.py --quiet build/partitions.bin | grep '^[^#]')
|
||||
do
|
||||
partitionName=$(echo $line | awk -F',' '{printf "%s", $1}' )
|
||||
partitionOffset=$(echo $line |awk -F',' '{printf "%s", $4}' )
|
||||
partitionFile=$(partitionNameToBinFile $partitionName)
|
||||
|
||||
if [[ " ${paritionsToIgnore[@]} " =~ " ${partitionName} " ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
partitionsParameters="$partitionsParameters $partitionOffset $partitionFile"
|
||||
echo "$partitionsParameters"
|
||||
|
||||
done
|
||||
|
||||
# Write README Instructions
|
||||
if [ ! -f "$outputReadme" ]; then
|
||||
touch $outputReadme
|
||||
fi
|
||||
|
||||
echo "" >> $outputReadme
|
||||
echo "====LINUX====" >> $outputReadme
|
||||
echo "To flash sequeezelite run the following script:" >> $outputReadme
|
||||
echo "$outputBashScript [PORT_HERE] [BAUD_RATE]" >> $outputReadme
|
||||
echo "e.g. $outputBashScript /dev/ttyUSB0 115200" >> $outputReadme
|
||||
echo "" >> $outputReadme
|
||||
echo "====WINDOWS====" >> $outputReadme
|
||||
echo "To flash sequeezelite run the following script:" >> $outputReadme
|
||||
echo "$outputBatScript [PORT_HERE] [BAUD_RATE]" >> $outputReadme
|
||||
echo "e.g. $outputBatScript COM11 115200" >> $outputReadme
|
||||
echo "" >> $outputReadme
|
||||
echo "If you don't know how to run the BAT file with arguments then you can" >> $outputReadme
|
||||
echo "edit the bat file in Notepad. Open the file up and edit the following:" >> $outputReadme
|
||||
echo "Change 'set port=%1' to 'set port=[PORT_HERE]'. E.g. 'set port=COM11'" >> $outputReadme
|
||||
echo "Change 'set baud=%2' to 'set baud=[BAUD_RATE]'. E.g. 'set baud=115200'" >> $outputReadme
|
||||
echo "" >> $outputReadme
|
||||
echo "====MANUAL====" >> $outputReadme
|
||||
echo "Python esptool.py --port [PORT_HERE] --baud [BAUD_RATE] $writeParameters $partitionsParameters" >> $outputReadme
|
||||
|
||||
# Write Linux BASH File
|
||||
if [ ! -f "$outputBashScript" ]; then
|
||||
touch $outputBashScript
|
||||
fi
|
||||
|
||||
echo "#!/bin/bash" >> $outputBashScript
|
||||
echo >> $outputBashScript
|
||||
echo "port=\$1" >> $outputBashScript
|
||||
echo "baud=\$2" >> $outputBashScript
|
||||
linuxFlashCommand="Python esptool.py --port \$port --baud \$baud"
|
||||
echo "$linuxFlashCommand $writeParameters $partitionsParameters" >> $outputBashScript
|
||||
|
||||
# Write Windows BAT File
|
||||
if [ ! -f "$outputBatScript" ]; then
|
||||
touch $outputBatScript
|
||||
fi
|
||||
|
||||
echo "echo off" >> $outputBatScript
|
||||
echo "" >> $outputBatScript
|
||||
echo "set port=%1" >> $outputBatScript
|
||||
echo "set baud=%2" >> $outputBatScript
|
||||
windowsFlashCommand="Python esptool.py --port %port% --baud %baud%"
|
||||
echo "$windowsFlashCommand $writeParameters $partitionsParameters" >> $outputBatScript
|
||||
|
||||
21
components/_override/CMakeLists.txt
Normal file
21
components/_override/CMakeLists.txt
Normal 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
1206
components/_override/i2s.c
Normal file
File diff suppressed because it is too large
Load Diff
275
components/_override/i2s_hal.c
Normal file
275
components/_override/i2s_hal.c
Normal 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);
|
||||
}
|
||||
849
components/_override/spi_bus_lock.c
Normal file
849
components/_override/spi_bus_lock.c
Normal 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
|
||||
1007
components/_override/spi_master.c.unused
Normal file
1007
components/_override/spi_master.c.unused
Normal file
File diff suppressed because it is too large
Load Diff
10
components/audio/CMakeLists.txt
Normal file
10
components/audio/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
idf_component_register( SRC_DIRS .
|
||||
INCLUDE_DIRS . inc
|
||||
)
|
||||
add_prebuilt_library(esp_processing lib/libesp_processing.a)
|
||||
target_link_libraries(${COMPONENT_LIB} PRIVATE esp_processing)
|
||||
#target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--undefined=")
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u pow")
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u cos")
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u sin")
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u sqrt")
|
||||
3
components/audio/link_helper.c
Normal file
3
components/audio/link_helper.c
Normal file
@@ -0,0 +1,3 @@
|
||||
void dummy_obj() {
|
||||
return;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
set(COMPONENT_SRCS "cmd_i2ctools.c")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
set(COMPONENT_REQUIRES console spi_flash)
|
||||
|
||||
register_component()
|
||||
@@ -1,919 +0,0 @@
|
||||
/* cmd_i2ctools.c
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#include <stdio.h>
|
||||
#include "cmd_i2ctools.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_log.h"
|
||||
#include "string.h"
|
||||
#include "stdio.h"
|
||||
#include "config.h"
|
||||
#include "accessors.h"
|
||||
|
||||
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
|
||||
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
|
||||
#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */
|
||||
#define READ_BIT I2C_MASTER_READ /*!< I2C master read */
|
||||
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
|
||||
#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
|
||||
#define ACK_VAL 0x0 /*!< I2C ack value */
|
||||
#define NACK_VAL 0x1 /*!< I2C nack value */
|
||||
|
||||
static const char *TAG = "cmd_i2ctools";
|
||||
|
||||
static gpio_num_t i2c_gpio_sda = 19;
|
||||
static gpio_num_t i2c_gpio_scl = 18;
|
||||
static uint32_t i2c_frequency = 100000;
|
||||
#ifdef CONFIG_I2C_LOCKED
|
||||
static i2c_port_t i2c_port = I2C_NUM_1;
|
||||
#else
|
||||
static i2c_port_t i2c_port = I2C_NUM_0;
|
||||
#endif
|
||||
|
||||
static struct {
|
||||
struct arg_int *chip_address;
|
||||
struct arg_int *register_address;
|
||||
struct arg_int *data_length;
|
||||
struct arg_end *end;
|
||||
} i2cget_args;
|
||||
|
||||
static struct {
|
||||
struct arg_int *chip_address;
|
||||
struct arg_int *register_address;
|
||||
struct arg_int *data;
|
||||
struct arg_end *end;
|
||||
} i2cset_args;
|
||||
|
||||
static struct {
|
||||
struct arg_int *chip_address;
|
||||
struct arg_int *size;
|
||||
struct arg_end *end;
|
||||
} i2cdump_args;
|
||||
|
||||
static struct {
|
||||
struct arg_lit *load;
|
||||
struct arg_int *port;
|
||||
struct arg_int *freq;
|
||||
struct arg_int *sda;
|
||||
struct arg_int *scl;
|
||||
struct arg_end *end;
|
||||
} i2cconfig_args;
|
||||
|
||||
|
||||
static struct {
|
||||
struct arg_int *port;
|
||||
struct arg_end *end;
|
||||
} i2cstop_args;
|
||||
|
||||
static struct {
|
||||
struct arg_int *port;
|
||||
struct arg_end *end;
|
||||
} i2ccheck_args;
|
||||
|
||||
static struct {
|
||||
struct arg_lit *clear;
|
||||
struct arg_lit *hflip;
|
||||
struct arg_lit *vflip;
|
||||
struct arg_lit *rotate;
|
||||
struct arg_int *address;
|
||||
struct arg_int *width;
|
||||
struct arg_int *height;
|
||||
struct arg_str *name;
|
||||
struct arg_str *driver;
|
||||
struct arg_end *end;
|
||||
} i2cdisp_args;
|
||||
|
||||
|
||||
bool is_i2c_started(i2c_port_t port){
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_LOGD(TAG,"Determining if i2c is started on port %u", port);
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
ret = i2c_master_start(cmd);
|
||||
if(ret == ESP_OK){
|
||||
ret = i2c_master_write_byte(cmd,WRITE_BIT, ACK_CHECK_EN);
|
||||
}
|
||||
if(ret == ESP_OK){
|
||||
ret = i2c_master_stop(cmd);
|
||||
}
|
||||
if(ret == ESP_OK){
|
||||
ret = i2c_master_cmd_begin(port, cmd, 50 / portTICK_RATE_MS);
|
||||
}
|
||||
i2c_cmd_link_delete(cmd);
|
||||
ESP_LOGD(TAG,"i2c is %s. %s",ret!=ESP_ERR_INVALID_STATE?"started":"not started", esp_err_to_name(ret));
|
||||
return (ret!=ESP_ERR_INVALID_STATE);
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t address;
|
||||
char * description;
|
||||
} i2c_db_t;
|
||||
|
||||
|
||||
// the list was taken from https://i2cdevices.org/addresses
|
||||
// on 2020-01-16
|
||||
static const i2c_db_t i2c_db[] = {
|
||||
{ .address = 0x00, .description = "Unknown"},
|
||||
{ .address = 0x01, .description = "Unknown"},
|
||||
{ .address = 0x02, .description = "Unknown"},
|
||||
{ .address = 0x03, .description = "Unknown"},
|
||||
{ .address = 0x04, .description = "Unknown"},
|
||||
{ .address = 0x05, .description = "Unknown"},
|
||||
{ .address = 0x06, .description = "Unknown"},
|
||||
{ .address = 0x07, .description = "Unknown"},
|
||||
{ .address = 0x0c, .description = "AK8975"},
|
||||
{ .address = 0x0d, .description = "AK8975"},
|
||||
{ .address = 0x0e, .description = "MAG3110 AK8975 IST-8310"},
|
||||
{ .address = 0x0f, .description = "AK8975"},
|
||||
{ .address = 0x10, .description = "VEML7700 VML6075"},
|
||||
{ .address = 0x11, .description = "Si4713 SAA5246 SAA5243P/K SAA5243P/L SAA5243P/E SAA5243P/H"},
|
||||
{ .address = 0x13, .description = "VCNL40x0"},
|
||||
{ .address = 0x18, .description = "MCP9808 LIS3DH LSM303"},
|
||||
{ .address = 0x19, .description = "MCP9808 LIS3DH LSM303"},
|
||||
{ .address = 0x1a, .description = "MCP9808"},
|
||||
{ .address = 0x1b, .description = "MCP9808"},
|
||||
{ .address = 0x1c, .description = "MCP9808 MMA845x FXOS8700"},
|
||||
{ .address = 0x1d, .description = "MCP9808 MMA845x ADXL345 FXOS8700"},
|
||||
{ .address = 0x1e, .description = "MCP9808 FXOS8700 HMC5883 LSM303 LSM303"},
|
||||
{ .address = 0x1f, .description = "MCP9808 FXOS8700"},
|
||||
{ .address = 0x20, .description = "FXAS21002 MCP23008 MCP23017 Chirp!"},
|
||||
{ .address = 0x21, .description = "FXAS21002 MCP23008 MCP23017 SAA4700"},
|
||||
{ .address = 0x22, .description = "MCP23008 MCP23017 PCA1070"},
|
||||
{ .address = 0x23, .description = "MCP23008 MCP23017 SAA4700"},
|
||||
{ .address = 0x24, .description = "MCP23008 MCP23017 PCD3311C PCD3312C"},
|
||||
{ .address = 0x25, .description = "MCP23008 MCP23017 PCD3311C PCD3312C"},
|
||||
{ .address = 0x26, .description = "MCP23008 MCP23017"},
|
||||
{ .address = 0x27, .description = "MCP23008 MCP23017"},
|
||||
{ .address = 0x28, .description = "BNO055 CAP1188"},
|
||||
{ .address = 0x29, .description = "BNO055 CAP1188 TCS34725 TSL2591 VL53L0x VL6180X"},
|
||||
{ .address = 0x2a, .description = "CAP1188"},
|
||||
{ .address = 0x2b, .description = "CAP1188"},
|
||||
{ .address = 0x2c, .description = "CAP1188 AD5248 AD5251 AD5252 CAT5171"},
|
||||
{ .address = 0x2d, .description = "CAP1188 AD5248 AD5251 AD5252 CAT5171"},
|
||||
{ .address = 0x2e, .description = "AD5248 AD5251 AD5252"},
|
||||
{ .address = 0x2f, .description = "AD5248 AD5243 AD5251 AD5252"},
|
||||
{ .address = 0x30, .description = "SAA2502"},
|
||||
{ .address = 0x31, .description = "SAA2502"},
|
||||
{ .address = 0x38, .description = "FT6x06 VEML6070 BMA150 SAA1064"},
|
||||
{ .address = 0x39, .description = "TSL2561 APDS-9960 VEML6070 SAA1064"},
|
||||
{ .address = 0x3a, .description = "PCF8577C SAA1064"},
|
||||
{ .address = 0x3b, .description = "SAA1064 PCF8569"},
|
||||
{ .address = 0x3c, .description = "SSD1305 SSD1306 PCF8578 PCF8569 SH1106"},
|
||||
{ .address = 0x3d, .description = "SSD1305 SSD1306 PCF8578 SH1106"},
|
||||
{ .address = 0x40, .description = "HTU21D-F TMP007 PCA9685 NE5751 TDA8421 INA260 TEA6320 TEA6330 TMP006 TEA6300 Si7021 INA219 TDA9860"},
|
||||
{ .address = 0x41, .description = "TMP007 PCA9685 STMPE811 TDA8424 NE5751 TDA8421 INA260 STMPE610 TDA8425 TMP006 INA219 TDA9860 TDA8426"},
|
||||
{ .address = 0x42, .description = "HDC1008 TMP007 TMP006 PCA9685 INA219 TDA8415 TDA8417 INA260"},
|
||||
{ .address = 0x43, .description = "HDC1008 TMP007 TMP006 PCA9685 INA219 INA260"},
|
||||
{ .address = 0x44, .description = "TMP007 TMP006 PCA9685 INA219 STMPE610 SHT31 ISL29125 STMPE811 TDA4688 TDA4672 TDA4780 TDA4670 TDA8442 TDA4687 TDA4671 TDA4680 INA260"},
|
||||
{ .address = 0x45, .description = "TMP007 TMP006 PCA9685 INA219 SHT31 TDA8376 INA260"},
|
||||
{ .address = 0x46, .description = "TMP007 TMP006 PCA9685 INA219 TDA9150 TDA8370 INA260"},
|
||||
{ .address = 0x47, .description = "TMP007 TMP006 PCA9685 INA219 INA260"},
|
||||
{ .address = 0x48, .description = "PCA9685 INA219 PN532 TMP102 INA260 ADS1115"},
|
||||
{ .address = 0x49, .description = "TSL2561 PCA9685 INA219 TMP102 INA260 ADS1115 AS7262"},
|
||||
{ .address = 0x4a, .description = "PCA9685 INA219 TMP102 ADS1115 MAX44009 INA260"},
|
||||
{ .address = 0x4b, .description = "PCA9685 INA219 TMP102 ADS1115 MAX44009 INA260"},
|
||||
{ .address = 0x4c, .description = "PCA9685 INA219 INA260"},
|
||||
{ .address = 0x4d, .description = "PCA9685 INA219 INA260"},
|
||||
{ .address = 0x4e, .description = "PCA9685 INA219 INA260"},
|
||||
{ .address = 0x4f, .description = "PCA9685 INA219 INA260"},
|
||||
{ .address = 0x50, .description = "PCA9685 MB85RC"},
|
||||
{ .address = 0x51, .description = "PCA9685 MB85RC"},
|
||||
{ .address = 0x52, .description = "PCA9685 MB85RC Nunchuck controller APDS-9250"},
|
||||
{ .address = 0x53, .description = "ADXL345 PCA9685 MB85RC"},
|
||||
{ .address = 0x54, .description = "PCA9685 MB85RC"},
|
||||
{ .address = 0x55, .description = "PCA9685 MB85RC"},
|
||||
{ .address = 0x56, .description = "PCA9685 MB85RC"},
|
||||
{ .address = 0x57, .description = "PCA9685 MB85RC MAX3010x"},
|
||||
{ .address = 0x58, .description = "PCA9685 TPA2016 SGP30"},
|
||||
{ .address = 0x59, .description = "PCA9685"},
|
||||
{ .address = 0x5a, .description = "PCA9685 CCS811 MLX90614 DRV2605 MPR121"},
|
||||
{ .address = 0x5b, .description = "PCA9685 CCS811 MPR121"},
|
||||
{ .address = 0x5c, .description = "PCA9685 AM2315 MPR121"},
|
||||
{ .address = 0x5d, .description = "PCA9685 MPR121"},
|
||||
{ .address = 0x5e, .description = "PCA9685"},
|
||||
{ .address = 0x5f, .description = "PCA9685 HTS221"},
|
||||
{ .address = 0x60, .description = "PCA9685 MPL115A2 MPL3115A2 Si5351A Si1145 MCP4725A0 TEA5767 TSA5511 SAB3037 SAB3035 MCP4725A1"},
|
||||
{ .address = 0x61, .description = "PCA9685 Si5351A MCP4725A0 TEA6100 TSA5511 SAB3037 SAB3035 MCP4725A1"},
|
||||
{ .address = 0x62, .description = "PCA9685 MCP4725A1 TSA5511 SAB3037 SAB3035 UMA1014T"},
|
||||
{ .address = 0x63, .description = "Si4713 PCA9685 MCP4725A1 TSA5511 SAB3037 SAB3035 UMA1014T"},
|
||||
{ .address = 0x64, .description = "PCA9685 MCP4725A2 MCP4725A1"},
|
||||
{ .address = 0x65, .description = "PCA9685 MCP4725A2 MCP4725A1"},
|
||||
{ .address = 0x66, .description = "PCA9685 MCP4725A3 IS31FL3731 MCP4725A1"},
|
||||
{ .address = 0x67, .description = "PCA9685 MCP4725A3 MCP4725A1"},
|
||||
{ .address = 0x68, .description = "PCA9685 AMG8833 DS1307 PCF8523 DS3231 MPU-9250 ITG3200 PCF8573 MPU6050"},
|
||||
{ .address = 0x69, .description = "PCA9685 AMG8833 MPU-9250 ITG3200 PCF8573 SPS30 MPU6050"},
|
||||
{ .address = 0x6a, .description = "PCA9685 L3GD20H PCF8573"},
|
||||
{ .address = 0x6b, .description = "PCA9685 L3GD20H PCF8573"},
|
||||
{ .address = 0x6c, .description = "PCA9685"},
|
||||
{ .address = 0x6d, .description = "PCA9685"},
|
||||
{ .address = 0x6e, .description = "PCA9685"},
|
||||
{ .address = 0x6f, .description = "PCA9685"},
|
||||
{ .address = 0x70, .description = "PCA9685 TCA9548 HT16K33"},
|
||||
{ .address = 0x71, .description = "PCA9685 TCA9548 HT16K33"},
|
||||
{ .address = 0x72, .description = "PCA9685 TCA9548 HT16K33"},
|
||||
{ .address = 0x73, .description = "PCA9685 TCA9548 HT16K33"},
|
||||
{ .address = 0x74, .description = "PCA9685 TCA9548 HT16K33"},
|
||||
{ .address = 0x75, .description = "PCA9685 TCA9548 HT16K33"},
|
||||
{ .address = 0x76, .description = "PCA9685 TCA9548 HT16K33 BME280 BMP280 MS5607 MS5611 BME680"},
|
||||
{ .address = 0x77, .description = "PCA9685 TCA9548 HT16K33 IS31FL3731 BME280 BMP280 MS5607 BMP180 BMP085 BMA180 MS5611 BME680"},
|
||||
{ .address = 0x78, .description = "PCA9685"},
|
||||
{ .address = 0x79, .description = "PCA9685"},
|
||||
{ .address = 0x7a, .description = "PCA9685"},
|
||||
{ .address = 0x7b, .description = "PCA9685"},
|
||||
{ .address = 0x7c, .description = "PCA9685"},
|
||||
{ .address = 0x7d, .description = "PCA9685"},
|
||||
{ .address = 0x7e, .description = "PCA9685"},
|
||||
{ .address = 0x7f, .description = "PCA9685"},
|
||||
{ .address = 0, .description = NULL}
|
||||
};
|
||||
void i2c_load_configuration(){
|
||||
ESP_LOGD(TAG,"Loading configuration from nvs");
|
||||
const i2c_config_t * conf = config_i2c_get((int *)&i2c_port);
|
||||
i2c_gpio_scl = conf->scl_io_num;
|
||||
i2c_gpio_sda = conf->sda_io_num;
|
||||
i2c_frequency = conf->master.clk_speed;
|
||||
}
|
||||
|
||||
const char * i2c_get_description(uint8_t address){
|
||||
uint8_t i=0;
|
||||
while(i2c_db[i].description && i2c_db[i].address!=address) i++;
|
||||
return i2c_db[i].description?i2c_db[i].description:"Unlisted";
|
||||
}
|
||||
|
||||
static esp_err_t i2c_get_port(int port, i2c_port_t *i2c_port)
|
||||
{
|
||||
if (port >= I2C_NUM_MAX) {
|
||||
ESP_LOGE(TAG, "Wrong port number: %d", port);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
switch (port) {
|
||||
case 0:
|
||||
*i2c_port = I2C_NUM_0;
|
||||
break;
|
||||
case 1:
|
||||
*i2c_port = I2C_NUM_1;
|
||||
break;
|
||||
default:
|
||||
*i2c_port = I2C_NUM_0;
|
||||
break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
static esp_err_t i2c_master_driver_install(){
|
||||
esp_err_t err=ESP_OK;
|
||||
ESP_LOGD(TAG,"Installing i2c driver on port %u", i2c_port);
|
||||
if((err=i2c_driver_install(i2c_port, I2C_MODE_MASTER, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0))!=ESP_OK){
|
||||
ESP_LOGE(TAG,"Driver install failed! %s", esp_err_to_name(err));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t i2c_master_driver_initialize()
|
||||
{
|
||||
esp_err_t err=ESP_OK;
|
||||
i2c_config_t conf = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = i2c_gpio_sda,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_io_num = i2c_gpio_scl,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = i2c_frequency
|
||||
};
|
||||
ESP_LOGI(TAG,"Initializing i2c driver configuration.\n mode = I2C_MODE_MASTER, \n scl_pullup_en = GPIO_PULLUP_ENABLE, \n i2c port = %u, \n sda_io_num = %u, \n sda_pullup_en = GPIO_PULLUP_ENABLE, \n scl_io_num = %u, \n scl_pullup_en = GPIO_PULLUP_ENABLE, \n master.clk_speed = %u", i2c_port, i2c_gpio_sda,i2c_gpio_scl,i2c_frequency);
|
||||
if((err=i2c_param_config(i2c_port, &conf))!=ESP_OK){
|
||||
ESP_LOGE(TAG,"i2c driver config load failed. %s", esp_err_to_name(err));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t i2c_initialize_driver_from_config(){
|
||||
esp_err_t err = ESP_OK;
|
||||
ESP_LOGD(TAG,"Initializing driver from configuration.");
|
||||
i2c_load_configuration();
|
||||
if(is_i2c_started(i2c_port)){
|
||||
ESP_LOGW(TAG, "Stopping i2c driver on port %u", i2c_port);
|
||||
// stop the current driver instance
|
||||
if((err=i2c_driver_delete(i2c_port))!=ESP_OK){
|
||||
ESP_LOGE(TAG,"i2c driver delete failed. %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
if(err==ESP_OK){
|
||||
err = i2c_master_driver_initialize();
|
||||
}
|
||||
if(err == ESP_OK){
|
||||
err = i2c_master_driver_install();
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static int do_i2c_stop(int argc, char **argv ){
|
||||
|
||||
int nerrors = arg_parse(argc, argv, (void **)&i2cstop_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, i2cstop_args.end, argv[0]);
|
||||
return 0;
|
||||
}
|
||||
if (i2cstop_args.port->count && i2c_get_port(i2cstop_args.port->ival[0], &i2c_port) != ESP_OK) {
|
||||
return 1;
|
||||
}
|
||||
ESP_LOGW(TAG,"Stopping i2c on port %u.",i2c_port);
|
||||
i2c_driver_delete(i2c_port);
|
||||
return 0;
|
||||
}
|
||||
static int do_i2c_check(int argc, char **argv ){
|
||||
|
||||
i2c_port_t port=0;
|
||||
int nerrors = arg_parse(argc, argv, (void **)&i2ccheck_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, i2ccheck_args.end, argv[0]);
|
||||
return 0;
|
||||
}
|
||||
port=i2c_port;
|
||||
|
||||
if (i2ccheck_args.port->count && i2c_get_port(i2ccheck_args.port->ival[0], &port) != ESP_OK) {
|
||||
return 1;
|
||||
}
|
||||
bool started=is_i2c_started(port);
|
||||
ESP_LOGI(TAG,"i2c is %s on port %u.", started?"started":"not started",port );
|
||||
return 0;
|
||||
}
|
||||
static int do_i2c_show_display(int argc, char **argv){
|
||||
char * config_string = (char * )config_alloc_get(NVS_TYPE_STR, "display_config") ;
|
||||
if(config_string){
|
||||
ESP_LOGI(TAG,"Display configuration string is : \n"
|
||||
"display_config = \"%s\"",config_string);
|
||||
free(config_string);
|
||||
}
|
||||
else {
|
||||
ESP_LOGW(TAG,"No display configuration found in nvs config display_config");
|
||||
}
|
||||
char * nvs_item = config_alloc_get(NVS_TYPE_STR, "i2c_config");
|
||||
if (nvs_item) {
|
||||
ESP_LOGI(TAG,"I2C configuration is: %s", nvs_item);
|
||||
free(nvs_item);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_i2c_set_display(int argc, char **argv)
|
||||
{
|
||||
int width=0, height=0, address=60;
|
||||
char * name = NULL;
|
||||
char * driver= NULL;
|
||||
char config_string[200]={};
|
||||
int nerrors = arg_parse(argc, argv, (void **)&i2cdisp_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, i2cdisp_args.end, argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check "--clear" option */
|
||||
if (i2cdisp_args.clear->count) {
|
||||
ESP_LOGW(TAG,"Clearing display config");
|
||||
config_set_value(NVS_TYPE_STR, "display_config", "");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Check "--address" option */
|
||||
if (i2cdisp_args.address->count) {
|
||||
address=i2cdisp_args.address->ival[0];
|
||||
}
|
||||
|
||||
/* Check "--width" option */
|
||||
if (i2cdisp_args.width->count) {
|
||||
width=i2cdisp_args.width->ival[0];
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG,"Missing parameter: --width");
|
||||
nerrors ++;
|
||||
}
|
||||
|
||||
/* Check "--height" option */
|
||||
if (i2cdisp_args.height->count) {
|
||||
height=i2cdisp_args.height->ival[0];
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG,"Missing parameter: --height");
|
||||
nerrors ++;
|
||||
}
|
||||
/* Check "--name" option */
|
||||
if (i2cdisp_args.name->count) {
|
||||
name=strdup(i2cdisp_args.name->sval[0]);
|
||||
}
|
||||
|
||||
/* Check "--driver" option */
|
||||
if (i2cdisp_args.driver->count) {
|
||||
driver=strdup(i2cdisp_args.driver->sval[0]);
|
||||
}
|
||||
|
||||
if(!name) name = strdup("I2C");
|
||||
if(!driver) driver = strdup("SSD1306");
|
||||
|
||||
bool rotate = i2cdisp_args.rotate->count>0;
|
||||
|
||||
snprintf(config_string, sizeof(config_string),"%s:width=%i,height=%i,address=%i,driver=%s%s%s",
|
||||
name,width,height,address,driver,rotate || i2cdisp_args.hflip->count?",HFlip":"",rotate || i2cdisp_args.vflip->count?",VFlip":"" );
|
||||
free(name);
|
||||
free(driver);
|
||||
|
||||
if(nerrors!=0){
|
||||
return 0;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG,"Updating display configuration string configuration to :\n"
|
||||
"display_config = \"%s\"",config_string );
|
||||
|
||||
return config_set_value(NVS_TYPE_STR, "display_config", config_string)!=ESP_OK;
|
||||
}
|
||||
|
||||
static int do_i2cconfig_cmd(int argc, char **argv)
|
||||
{
|
||||
esp_err_t err=ESP_OK;
|
||||
int res=0;
|
||||
int nerrors = arg_parse(argc, argv, (void **)&i2cconfig_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, i2cconfig_args.end, argv[0]);
|
||||
return 0;
|
||||
}
|
||||
/* Check "--load" option */
|
||||
if (i2cconfig_args.load->count) {
|
||||
ESP_LOGW(TAG,"Loading i2c config");
|
||||
i2c_load_configuration();
|
||||
}
|
||||
else {
|
||||
|
||||
/* Check "--port" option */
|
||||
if (i2cconfig_args.port->count) {
|
||||
if (i2c_get_port(i2cconfig_args.port->ival[0], &i2c_port) != ESP_OK) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
/* Check "--freq" option */
|
||||
if (i2cconfig_args.freq->count) {
|
||||
i2c_frequency = i2cconfig_args.freq->ival[0];
|
||||
}
|
||||
if (i2cconfig_args.sda->count){
|
||||
/* Check "--sda" option */
|
||||
i2c_gpio_sda = i2cconfig_args.sda->ival[0];
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG,"Missing --sda option.");
|
||||
res=1;
|
||||
}
|
||||
|
||||
if (i2cconfig_args.scl->count){
|
||||
/* Check "--sda" option */
|
||||
i2c_gpio_scl = i2cconfig_args.scl->ival[0];
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG,"Missing --scl option.");
|
||||
res=1;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SQUEEZEAMP
|
||||
if (i2c_port == I2C_NUM_0) {
|
||||
i2c_port = I2C_NUM_1;
|
||||
ESP_LOGE(TAG, "can't use i2c port 0 on SqueezeAMP. Changing to port 1.");
|
||||
}
|
||||
#endif
|
||||
if(!res){
|
||||
ESP_LOGI(TAG, "Uninstall i2c driver from port %u if needed",i2c_port);
|
||||
if(is_i2c_started(i2c_port)){
|
||||
if((err=i2c_driver_delete(i2c_port))!=ESP_OK){
|
||||
ESP_LOGE(TAG,"i2c driver delete failed. %s", esp_err_to_name(err));
|
||||
res = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!res){
|
||||
ESP_LOGI(TAG,"Initializing driver with config scl=%u sda=%u speed=%u port=%u",i2c_gpio_scl,i2c_gpio_sda,i2c_frequency,i2c_port);
|
||||
if((err=i2c_master_driver_initialize())==ESP_OK){
|
||||
ESP_LOGI(TAG,"Initalize success.");
|
||||
// now start the i2c driver
|
||||
ESP_LOGI(TAG,"Starting the i2c driver.");
|
||||
if((err=i2c_master_driver_install())!=ESP_OK){
|
||||
res=1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG,"I2C initialization failed. %s", esp_err_to_name(err));
|
||||
res=1;
|
||||
}
|
||||
}
|
||||
if(!res && !i2cconfig_args.load->count){
|
||||
ESP_LOGI(TAG,"Storing i2c parameters.");
|
||||
i2c_config_t config={
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = i2c_gpio_sda,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_io_num = i2c_gpio_scl,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = i2c_frequency
|
||||
};
|
||||
config_i2c_set(&config, i2c_port);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#define RUN_SHOW_ERROR(c)
|
||||
|
||||
|
||||
static int do_i2cdump_cmd(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **)&i2cdump_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, i2cdump_args.end, argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check chip address: "-c" option */
|
||||
int chip_addr = i2cdump_args.chip_address->ival[0];
|
||||
/* Check read size: "-s" option */
|
||||
int size = 1;
|
||||
if (i2cdump_args.size->count) {
|
||||
size = i2cdump_args.size->ival[0];
|
||||
}
|
||||
if (size != 1 && size != 2 && size != 4) {
|
||||
ESP_LOGE(TAG, "Wrong read size. Only support 1,2,4");
|
||||
return 1;
|
||||
}
|
||||
esp_err_t ret = i2c_initialize_driver_from_config();
|
||||
if(ret!=ESP_OK) return 0;
|
||||
|
||||
uint8_t data_addr;
|
||||
uint8_t data[4];
|
||||
int32_t block[16];
|
||||
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f"
|
||||
" 0123456789abcdef\r\n");
|
||||
for (int i = 0; i < 128; i += 16) {
|
||||
printf("%02x: ", i);
|
||||
for (int j = 0; j < 16; j += size) {
|
||||
fflush(stdout);
|
||||
data_addr = i + j;
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, chip_addr << 1 | WRITE_BIT, ACK_CHECK_EN);
|
||||
i2c_master_write_byte(cmd, data_addr, ACK_CHECK_EN);
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, chip_addr << 1 | READ_BIT, ACK_CHECK_EN);
|
||||
if (size > 1) {
|
||||
i2c_master_read(cmd, data, size - 1, ACK_VAL);
|
||||
}
|
||||
i2c_master_read_byte(cmd, data + size - 1, NACK_VAL);
|
||||
i2c_master_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 50 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
if (ret == ESP_OK) {
|
||||
for (int k = 0; k < size; k++) {
|
||||
printf("%02x ", data[k]);
|
||||
block[j + k] = data[k];
|
||||
}
|
||||
} else {
|
||||
for (int k = 0; k < size; k++) {
|
||||
printf("XX ");
|
||||
block[j + k] = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf(" ");
|
||||
for (int k = 0; k < 16; k++) {
|
||||
if (block[k] < 0) {
|
||||
printf("X");
|
||||
}
|
||||
if ((block[k] & 0xff) == 0x00 || (block[k] & 0xff) == 0xff) {
|
||||
printf(".");
|
||||
} else if ((block[k] & 0xff) < 32 || (block[k] & 0xff) >= 127) {
|
||||
printf("?");
|
||||
} else {
|
||||
printf("%c", block[k] & 0xff);
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
// Don't stop the driver; our firmware may be using it for screen, etc
|
||||
//i2c_driver_delete(i2c_port);
|
||||
return 0;
|
||||
}
|
||||
static int do_i2cset_cmd(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **)&i2cset_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, i2cset_args.end, argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check chip address: "-c" option */
|
||||
int chip_addr = i2cset_args.chip_address->ival[0];
|
||||
/* Check register address: "-r" option */
|
||||
int data_addr = 0;
|
||||
if (i2cset_args.register_address->count) {
|
||||
data_addr = i2cset_args.register_address->ival[0];
|
||||
}
|
||||
/* Check data: "-d" option */
|
||||
int len = i2cset_args.data->count;
|
||||
|
||||
i2c_master_driver_initialize();
|
||||
if(i2c_master_driver_install()!=ESP_OK){
|
||||
return 1;
|
||||
}
|
||||
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, chip_addr << 1 | WRITE_BIT, ACK_CHECK_EN);
|
||||
if (i2cset_args.register_address->count) {
|
||||
i2c_master_write_byte(cmd, data_addr, ACK_CHECK_EN);
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
i2c_master_write_byte(cmd, i2cset_args.data->ival[i], ACK_CHECK_EN);
|
||||
}
|
||||
i2c_master_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Write OK");
|
||||
} else if (ret == ESP_ERR_TIMEOUT) {
|
||||
ESP_LOGW(TAG, "Bus is busy");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Write Failed");
|
||||
}
|
||||
// Don't stop the driver; our firmware may be using it for screen, etc
|
||||
//i2c_driver_delete(i2c_port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_i2cget_cmd(int argc, char **argv)
|
||||
{
|
||||
esp_err_t err=ESP_OK;
|
||||
int nerrors = arg_parse(argc, argv, (void **)&i2cget_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, i2cget_args.end, argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check chip address: "-c" option */
|
||||
int chip_addr = i2cget_args.chip_address->ival[0];
|
||||
/* Check register address: "-r" option */
|
||||
int data_addr = -1;
|
||||
if (i2cget_args.register_address->count) {
|
||||
data_addr = i2cget_args.register_address->ival[0];
|
||||
}
|
||||
/* Check data length: "-l" option */
|
||||
int len = 1;
|
||||
if (i2cget_args.data_length->count) {
|
||||
len = i2cget_args.data_length->ival[0];
|
||||
}
|
||||
|
||||
|
||||
if((err=i2c_master_driver_initialize())!=ESP_OK){
|
||||
ESP_LOGE(TAG,"Error initializing i2c driver. %s",esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
if((err=i2c_master_driver_install())!=ESP_OK){
|
||||
return 1;
|
||||
}
|
||||
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
uint8_t *data = malloc(len);
|
||||
if (data_addr != -1) {
|
||||
i2c_master_write_byte(cmd, chip_addr << 1 | WRITE_BIT, ACK_CHECK_EN);
|
||||
i2c_master_write_byte(cmd, data_addr, ACK_CHECK_EN);
|
||||
i2c_master_start(cmd);
|
||||
}
|
||||
i2c_master_write_byte(cmd, chip_addr << 1 | READ_BIT, ACK_CHECK_EN);
|
||||
if (len > 1) {
|
||||
i2c_master_read(cmd, data, len - 1, ACK_VAL);
|
||||
}
|
||||
i2c_master_read_byte(cmd, data + len - 1, NACK_VAL);
|
||||
i2c_master_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
if (ret == ESP_OK) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
printf("0x%02x ", data[i]);
|
||||
if ((i + 1) % 16 == 0) {
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
if (len % 16) {
|
||||
printf("\r\n");
|
||||
}
|
||||
} else if (ret == ESP_ERR_TIMEOUT) {
|
||||
ESP_LOGW(TAG, "Bus is busy");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Read failed");
|
||||
}
|
||||
free(data);
|
||||
// Don't stop the driver; our firmware may be using it for screen, etc
|
||||
//i2c_driver_delete(i2c_port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_i2cdetect_cmd(int argc, char **argv)
|
||||
{
|
||||
uint8_t matches[128]={};
|
||||
int last_match=0;
|
||||
esp_err_t ret = i2c_initialize_driver_from_config();
|
||||
if(ret!=ESP_OK) return 0;
|
||||
uint8_t address;
|
||||
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
|
||||
for (int i = 0; i < 128 ; i += 16) {
|
||||
printf("%02x: ", i);
|
||||
for (int j = 0; j < 16 ; j++) {
|
||||
fflush(stdout);
|
||||
address = i + j;
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, (address << 1) | WRITE_BIT, ACK_CHECK_EN);
|
||||
i2c_master_stop(cmd);
|
||||
ret = i2c_master_cmd_begin(i2c_port, cmd, 50 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
if (ret == ESP_OK) {
|
||||
printf("%02x ", address);
|
||||
matches[++last_match-1] = address;
|
||||
} else if (ret == ESP_ERR_TIMEOUT) {
|
||||
printf("UU ");
|
||||
} else {
|
||||
printf("-- ");
|
||||
}
|
||||
}
|
||||
|
||||
printf("\r\n");
|
||||
|
||||
}
|
||||
if(last_match) {
|
||||
printf("\r\n------------------------------------------------------------------------------------"
|
||||
"\r\nDetected the following devices (names provided by https://i2cdevices.org/addresses).");
|
||||
|
||||
for(int i=0;i<last_match;i++){
|
||||
//printf("%02x = %s\r\n", matches[i], i2c_get_description(matches[i]));
|
||||
printf("\r\n%u [%02xh]- %s", matches[i], matches[i], i2c_get_description(matches[i]));
|
||||
}
|
||||
printf("\r\n------------------------------------------------------------------------------------\r\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
static void register_i2c_set_display(){
|
||||
i2cdisp_args.address = arg_int0("a", "address", "<n>", "Set the device address, default 60");
|
||||
i2cdisp_args.width = arg_int0("w", "width", "<n>", "Set the display width");
|
||||
i2cdisp_args.height = arg_int0("h", "height", "<n>", "Set the display height");
|
||||
i2cdisp_args.name = arg_str0("t", "type", "<I2C|SPI>", "Set the display type. default I2C");
|
||||
i2cdisp_args.driver = arg_str0("d", "driver", "<string>", "Set the display driver name. Default SSD1306");
|
||||
i2cdisp_args.clear = arg_litn(NULL, "clear", 0, 1, "clear configuration and return");
|
||||
i2cdisp_args.hflip = arg_litn(NULL, "hf", 0, 1, "Flip picture horizontally");
|
||||
i2cdisp_args.vflip = arg_litn(NULL, "vf", 0, 1, "Flip picture vertically");
|
||||
i2cdisp_args.rotate = arg_litn("r", "rotate", 0, 1, "Rotate the picture 180 deg");
|
||||
i2cdisp_args.end = arg_end(8);
|
||||
const esp_console_cmd_t i2c_set_display= {
|
||||
.command = "setdisplay",
|
||||
.help="Sets the display options for the board",
|
||||
.hint = NULL,
|
||||
.func = &do_i2c_set_display,
|
||||
.argtable = &i2cdisp_args
|
||||
};
|
||||
|
||||
const esp_console_cmd_t i2c_show_display= {
|
||||
.command = "getdisplay",
|
||||
.help="Shows display options and global i2c configuration",
|
||||
.hint = NULL,
|
||||
.func = &do_i2c_show_display,
|
||||
.argtable = NULL
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&i2c_set_display));
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&i2c_show_display));
|
||||
}
|
||||
static void register_i2cdectect(void)
|
||||
{
|
||||
const esp_console_cmd_t i2cdetect_cmd = {
|
||||
.command = "i2cdetect",
|
||||
.help = "Scan I2C bus for devices",
|
||||
.hint = NULL,
|
||||
.func = &do_i2cdetect_cmd,
|
||||
.argtable = NULL
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&i2cdetect_cmd));
|
||||
}
|
||||
|
||||
static void register_i2cget(void)
|
||||
{
|
||||
i2cget_args.chip_address = arg_int1("c", "chip", "<chip_addr>", "Specify the address of the chip on that bus");
|
||||
i2cget_args.register_address = arg_int0("r", "register", "<register_addr>", "Specify the address on that chip to read from");
|
||||
i2cget_args.data_length = arg_int0("l", "length", "<length>", "Specify the length to read from that data address");
|
||||
i2cget_args.end = arg_end(1);
|
||||
const esp_console_cmd_t i2cget_cmd = {
|
||||
.command = "i2cget",
|
||||
.help = "Read registers visible through the I2C bus",
|
||||
.hint = NULL,
|
||||
.func = &do_i2cget_cmd,
|
||||
.argtable = &i2cget_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&i2cget_cmd));
|
||||
}
|
||||
|
||||
|
||||
static void register_i2cset(void)
|
||||
{
|
||||
i2cset_args.chip_address = arg_int1("c", "chip", "<chip_addr>", "Specify the address of the chip on that bus");
|
||||
i2cset_args.register_address = arg_int0("r", "register", "<register_addr>", "Specify the address on that chip to read from");
|
||||
i2cset_args.data = arg_intn(NULL, NULL, "<data>", 0, 256, "Specify the data to write to that data address");
|
||||
i2cset_args.end = arg_end(2);
|
||||
const esp_console_cmd_t i2cset_cmd = {
|
||||
.command = "i2cset",
|
||||
.help = "Set registers visible through the I2C bus",
|
||||
.hint = NULL,
|
||||
.func = &do_i2cset_cmd,
|
||||
.argtable = &i2cset_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&i2cset_cmd));
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void register_i2cdump(void)
|
||||
{
|
||||
i2cdump_args.chip_address = arg_int1("c", "chip", "<chip_addr>", "Specify the address of the chip on that bus");
|
||||
i2cdump_args.size = arg_int0("s", "size", "<size>", "Specify the size of each read");
|
||||
i2cdump_args.end = arg_end(3);
|
||||
const esp_console_cmd_t i2cdump_cmd = {
|
||||
.command = "i2cdump",
|
||||
.help = "Examine registers visible through the I2C bus",
|
||||
.hint = NULL,
|
||||
.func = &do_i2cdump_cmd,
|
||||
.argtable = &i2cdump_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&i2cdump_cmd));
|
||||
}
|
||||
|
||||
static void register_i2ccheck(){
|
||||
i2ccheck_args.port = arg_int0("p", "port", "<0|1>", "Set the I2C bus port number");
|
||||
i2ccheck_args.end = arg_end(2);
|
||||
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "i2ccheck",
|
||||
.help = "Check if the I2C bus is installed",
|
||||
.hint = NULL,
|
||||
.func = &do_i2c_check,
|
||||
.argtable = &i2ccheck_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
|
||||
}
|
||||
|
||||
|
||||
static void register_i2cstop(){
|
||||
i2cstop_args.port = arg_int0("p", "port", "<0|1>", "Set the I2C bus port number");
|
||||
i2cstop_args.end = arg_end(2);
|
||||
|
||||
const esp_console_cmd_t i2cconfig_cmd = {
|
||||
.command = "i2cstop",
|
||||
.help = "Stop the I2C bus",
|
||||
.hint = NULL,
|
||||
.func = &do_i2c_stop,
|
||||
.argtable = &i2cstop_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&i2cconfig_cmd));
|
||||
}
|
||||
|
||||
static void register_i2cconfig(void)
|
||||
{
|
||||
i2cconfig_args.port = arg_int0("p", "port", "<0|1>", "Set the I2C bus port number");
|
||||
i2cconfig_args.freq = arg_int0("f", "freq", "<Hz>", "Set the frequency(Hz) of I2C bus. e.g. 100000");
|
||||
i2cconfig_args.sda = arg_int0("d", "sda", "<gpio>", "Set the gpio for I2C SDA. e.g. 19");
|
||||
i2cconfig_args.scl = arg_int0("c", "scl", "<gpio>", "Set the gpio for I2C SCL. e.g. 18");
|
||||
i2cconfig_args.load = arg_litn("l", "load", 0, 1, "load existing configuration and return");
|
||||
i2cconfig_args.end = arg_end(4);
|
||||
const esp_console_cmd_t i2cconfig_cmd = {
|
||||
.command = "i2cconfig",
|
||||
.help = "Config I2C bus",
|
||||
.hint = NULL,
|
||||
.func = &do_i2cconfig_cmd,
|
||||
.argtable = &i2cconfig_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&i2cconfig_cmd));
|
||||
}
|
||||
|
||||
void register_i2ctools(void)
|
||||
{
|
||||
register_i2cconfig();
|
||||
register_i2cdectect();
|
||||
register_i2cget();
|
||||
register_i2cset();
|
||||
register_i2cdump();
|
||||
register_i2c_set_display();
|
||||
register_i2cstop();
|
||||
register_i2ccheck();
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
||||
@@ -1,7 +0,0 @@
|
||||
set(COMPONENT_ADD_INCLUDEDIRS .)
|
||||
|
||||
set(COMPONENT_SRCS "cmd_nvs.c")
|
||||
|
||||
set(COMPONENT_REQUIRES console nvs_flash)
|
||||
|
||||
register_component()
|
||||
@@ -1,7 +0,0 @@
|
||||
set(COMPONENT_ADD_INCLUDEDIRS .)
|
||||
|
||||
set(COMPONENT_SRCS "cmd_system.c")
|
||||
|
||||
set(COMPONENT_REQUIRES console spi_flash)
|
||||
|
||||
register_component()
|
||||
@@ -1,489 +0,0 @@
|
||||
/* Console example — various system commands
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_INFO
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_spi_flash.h"
|
||||
#include "driver/rtc_io.h"
|
||||
#include "driver/uart.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "soc/rtc_cntl_reg.h"
|
||||
#include "esp32/rom/uart.h"
|
||||
#include "cmd_system.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_partition.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "platform_esp32.h"
|
||||
#include "config.h"
|
||||
#include "esp_sleep.h"
|
||||
#include "driver/uart.h" // for the uart driver access
|
||||
|
||||
#ifdef CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS
|
||||
#define WITH_TASKS_INFO 1
|
||||
#endif
|
||||
|
||||
|
||||
static const char * TAG = "cmd_system";
|
||||
|
||||
static void register_free();
|
||||
static void register_heap();
|
||||
static void register_version();
|
||||
static void register_restart();
|
||||
static void register_deep_sleep();
|
||||
static void register_light_sleep();
|
||||
static void register_factory_boot();
|
||||
static void register_restart_ota();
|
||||
#if WITH_TASKS_INFO
|
||||
static void register_tasks();
|
||||
#endif
|
||||
|
||||
void register_system()
|
||||
{
|
||||
register_free();
|
||||
register_heap();
|
||||
register_version();
|
||||
register_restart();
|
||||
register_deep_sleep();
|
||||
register_light_sleep();
|
||||
register_factory_boot();
|
||||
register_restart_ota();
|
||||
#if WITH_TASKS_INFO
|
||||
register_tasks();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* 'version' command */
|
||||
static int get_version(int argc, char **argv)
|
||||
{
|
||||
esp_chip_info_t info;
|
||||
esp_chip_info(&info);
|
||||
printf("IDF Version:%s\r\n", esp_get_idf_version());
|
||||
printf("Chip info:\r\n");
|
||||
printf("\tmodel:%s\r\n", info.model == CHIP_ESP32 ? "ESP32" : "Unknow");
|
||||
printf("\tcores:%d\r\n", info.cores);
|
||||
printf("\tfeature:%s%s%s%s%d%s\r\n",
|
||||
info.features & CHIP_FEATURE_WIFI_BGN ? "/802.11bgn" : "",
|
||||
info.features & CHIP_FEATURE_BLE ? "/BLE" : "",
|
||||
info.features & CHIP_FEATURE_BT ? "/BT" : "",
|
||||
info.features & CHIP_FEATURE_EMB_FLASH ? "/Embedded-Flash:" : "/External-Flash:",
|
||||
spi_flash_get_chip_size() / (1024 * 1024), " MB");
|
||||
printf("\trevision number:%d\r\n", info.revision);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_version()
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "version",
|
||||
.help = "Get version of chip and SDK",
|
||||
.hint = NULL,
|
||||
.func = &get_version,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
/** 'restart' command restarts the program */
|
||||
|
||||
|
||||
esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
|
||||
{
|
||||
#if RECOVERY_APPLICATION
|
||||
if(partition_subtype ==ESP_PARTITION_SUBTYPE_APP_FACTORY){
|
||||
ESP_LOGW(TAG,"RECOVERY application is already active");
|
||||
if(!wait_for_commit()){
|
||||
ESP_LOGW(TAG,"Unable to commit configuration. ");
|
||||
}
|
||||
ESP_LOGW(TAG, "Restarting after tx complete");
|
||||
uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
|
||||
esp_restart();
|
||||
return ESP_OK;
|
||||
}
|
||||
#else
|
||||
if(partition_subtype !=ESP_PARTITION_SUBTYPE_APP_FACTORY){
|
||||
ESP_LOGW(TAG,"SQUEEZELITE application is already active");
|
||||
if(!wait_for_commit()){
|
||||
ESP_LOGW(TAG,"Unable to commit configuration. ");
|
||||
}
|
||||
ESP_LOGW(TAG, "Restarting after tx complete");
|
||||
uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
|
||||
esp_restart();
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
esp_err_t err = ESP_OK;
|
||||
bool bFound=false;
|
||||
ESP_LOGI(TAG, "Looking for partition type %u",partition_subtype);
|
||||
const esp_partition_t *partition;
|
||||
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, partition_subtype, NULL);
|
||||
|
||||
if(it == NULL){
|
||||
ESP_LOGE(TAG,"Unable initialize partition iterator!");
|
||||
set_status_message(ERROR, "Reboot failed. Cannot iterate through partitions");
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGD(TAG, "Found partition. Getting info.");
|
||||
partition = (esp_partition_t *) esp_partition_get(it);
|
||||
ESP_LOGD(TAG, "Releasing partition iterator");
|
||||
esp_partition_iterator_release(it);
|
||||
if(partition != NULL){
|
||||
ESP_LOGI(TAG, "Found application partition %s sub type %u", partition->label,partition_subtype);
|
||||
err=esp_ota_set_boot_partition(partition);
|
||||
if(err!=ESP_OK){
|
||||
ESP_LOGE(TAG,"Unable to set partition as active for next boot. %s",esp_err_to_name(err));
|
||||
bFound=false;
|
||||
set_status_message(ERROR, "Unable to select partition for reboot.");
|
||||
}
|
||||
else{
|
||||
ESP_LOGW(TAG, "Application partition %s sub type %u is selected for boot", partition->label,partition_subtype);
|
||||
bFound=true;
|
||||
set_status_message(WARNING, "Rebooting!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG,"partition type %u not found! Unable to reboot to recovery.",partition_subtype);
|
||||
set_status_message(ERROR, "Partition not found.");
|
||||
}
|
||||
ESP_LOGD(TAG, "Yielding to other processes");
|
||||
taskYIELD();
|
||||
if(bFound) {
|
||||
ESP_LOGW(TAG,"Configuration %s changes. ",config_has_changes()?"has":"does not have");
|
||||
if(!wait_for_commit()){
|
||||
ESP_LOGW(TAG,"Unable to commit configuration. ");
|
||||
}
|
||||
ESP_LOGW(TAG, "Restarting after tx complete");
|
||||
uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
|
||||
esp_restart();
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static int restart(int argc, char **argv)
|
||||
{
|
||||
ESP_LOGW(TAG, "\n\nPerforming a simple restart to the currently active partition.");
|
||||
if(!wait_for_commit()){
|
||||
ESP_LOGW(TAG,"Unable to commit configuration. ");
|
||||
}
|
||||
ESP_LOGW(TAG, "Restarting after tx complete");
|
||||
uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
|
||||
esp_restart();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void simple_restart()
|
||||
{
|
||||
ESP_LOGW(TAG,"\n\n Called to perform a simple system reboot.");
|
||||
if(!wait_for_commit()){
|
||||
ESP_LOGW(TAG,"Unable to commit configuration. ");
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Restarting after tx complete");
|
||||
uart_wait_tx_done(UART_NUM_1, 500 / portTICK_RATE_MS);
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
esp_err_t guided_restart_ota(){
|
||||
ESP_LOGW(TAG,"\n\nCalled for a reboot to OTA Application");
|
||||
guided_boot(ESP_PARTITION_SUBTYPE_APP_OTA_0);
|
||||
return ESP_FAIL; // return fail. This should never return... we're rebooting!
|
||||
}
|
||||
esp_err_t guided_factory(){
|
||||
ESP_LOGW(TAG,"\n\nCalled for a reboot to recovery application");
|
||||
guided_boot(ESP_PARTITION_SUBTYPE_APP_FACTORY);
|
||||
return ESP_FAIL; // return fail. This should never return... we're rebooting!
|
||||
}
|
||||
static int restart_factory(int argc, char **argv)
|
||||
{
|
||||
ESP_LOGW(TAG, "Executing guided boot into recovery");
|
||||
guided_boot(ESP_PARTITION_SUBTYPE_APP_FACTORY);
|
||||
return 0; // return fail. This should never return... we're rebooting!
|
||||
}
|
||||
static int restart_ota(int argc, char **argv)
|
||||
{
|
||||
ESP_LOGW(TAG, "Executing guided boot into ota app 0");
|
||||
guided_boot(ESP_PARTITION_SUBTYPE_APP_OTA_0);
|
||||
return 0; // return fail. This should never return... we're rebooting!
|
||||
}
|
||||
static void register_restart()
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "restart",
|
||||
.help = "Software reset of the chip",
|
||||
.hint = NULL,
|
||||
.func = &restart,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
static void register_restart_ota()
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "restart_ota",
|
||||
.help = "Selects the ota app partition to boot from and performa a software reset of the chip",
|
||||
.hint = NULL,
|
||||
.func = &restart_ota,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
static void register_factory_boot()
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "recovery",
|
||||
.help = "Resets and boot to recovery (if available)",
|
||||
.hint = NULL,
|
||||
.func = &restart_factory,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
/** 'free' command prints available heap memory */
|
||||
|
||||
static int free_mem(int argc, char **argv)
|
||||
{
|
||||
printf("%d\n", esp_get_free_heap_size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_free()
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "free",
|
||||
.help = "Get the current size of free heap memory",
|
||||
.hint = NULL,
|
||||
.func = &free_mem,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
/* 'heap' command prints minumum heap size */
|
||||
static int heap_size(int argc, char **argv)
|
||||
{
|
||||
uint32_t heap_size = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT);
|
||||
ESP_LOGI(TAG, "min heap size: %u", heap_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_heap()
|
||||
{
|
||||
const esp_console_cmd_t heap_cmd = {
|
||||
.command = "heap",
|
||||
.help = "Get minimum size of free heap memory that was available during program execution",
|
||||
.hint = NULL,
|
||||
.func = &heap_size,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&heap_cmd) );
|
||||
|
||||
}
|
||||
|
||||
/** 'tasks' command prints the list of tasks and related information */
|
||||
#if WITH_TASKS_INFO
|
||||
|
||||
static int tasks_info(int argc, char **argv)
|
||||
{
|
||||
const size_t bytes_per_task = 40; /* see vTaskList description */
|
||||
char *task_list_buffer = malloc(uxTaskGetNumberOfTasks() * bytes_per_task);
|
||||
if (task_list_buffer == NULL) {
|
||||
ESP_LOGE(TAG, "failed to allocate buffer for vTaskList output");
|
||||
return 1;
|
||||
}
|
||||
fputs("Task Name\tStatus\tPrio\tHWM\tTask#", stdout);
|
||||
#ifdef CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID
|
||||
fputs("\tAffinity", stdout);
|
||||
#endif
|
||||
fputs("\n", stdout);
|
||||
vTaskList(task_list_buffer);
|
||||
fputs(task_list_buffer, stdout);
|
||||
free(task_list_buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_tasks()
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "tasks",
|
||||
.help = "Get information about running tasks",
|
||||
.hint = NULL,
|
||||
.func = &tasks_info,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
#endif // WITH_TASKS_INFO
|
||||
|
||||
/** 'deep_sleep' command puts the chip into deep sleep mode */
|
||||
|
||||
static struct {
|
||||
struct arg_int *wakeup_time;
|
||||
struct arg_int *wakeup_gpio_num;
|
||||
struct arg_int *wakeup_gpio_level;
|
||||
struct arg_end *end;
|
||||
} deep_sleep_args;
|
||||
|
||||
|
||||
static int deep_sleep(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &deep_sleep_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, deep_sleep_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
if (deep_sleep_args.wakeup_time->count) {
|
||||
uint64_t timeout = 1000ULL * deep_sleep_args.wakeup_time->ival[0];
|
||||
ESP_LOGI(TAG, "Enabling timer wakeup, timeout=%lluus", timeout);
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_timer_wakeup(timeout) );
|
||||
}
|
||||
if (deep_sleep_args.wakeup_gpio_num->count) {
|
||||
int io_num = deep_sleep_args.wakeup_gpio_num->ival[0];
|
||||
if (!rtc_gpio_is_valid_gpio(io_num)) {
|
||||
ESP_LOGE(TAG, "GPIO %d is not an RTC IO", io_num);
|
||||
return 1;
|
||||
}
|
||||
int level = 0;
|
||||
if (deep_sleep_args.wakeup_gpio_level->count) {
|
||||
level = deep_sleep_args.wakeup_gpio_level->ival[0];
|
||||
if (level != 0 && level != 1) {
|
||||
ESP_LOGE(TAG, "Invalid wakeup level: %d", level);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "Enabling wakeup on GPIO%d, wakeup on %s level",
|
||||
io_num, level ? "HIGH" : "LOW");
|
||||
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_ext1_wakeup(1ULL << io_num, level) );
|
||||
}
|
||||
rtc_gpio_isolate(GPIO_NUM_12);
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
static void register_deep_sleep()
|
||||
{
|
||||
deep_sleep_args.wakeup_time =
|
||||
arg_int0("t", "time", "<t>", "Wake up time, ms");
|
||||
deep_sleep_args.wakeup_gpio_num =
|
||||
arg_int0(NULL, "io", "<n>",
|
||||
"If specified, wakeup using GPIO with given number");
|
||||
deep_sleep_args.wakeup_gpio_level =
|
||||
arg_int0(NULL, "io_level", "<0|1>", "GPIO level to trigger wakeup");
|
||||
deep_sleep_args.end = arg_end(3);
|
||||
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "deep_sleep",
|
||||
.help = "Enter deep sleep mode. "
|
||||
"Two wakeup modes are supported: timer and GPIO. "
|
||||
"If no wakeup option is specified, will sleep indefinitely.",
|
||||
.hint = NULL,
|
||||
.func = &deep_sleep,
|
||||
.argtable = &deep_sleep_args
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
/** 'light_sleep' command puts the chip into light sleep mode */
|
||||
|
||||
static struct {
|
||||
struct arg_int *wakeup_time;
|
||||
struct arg_int *wakeup_gpio_num;
|
||||
struct arg_int *wakeup_gpio_level;
|
||||
struct arg_end *end;
|
||||
} light_sleep_args;
|
||||
|
||||
static int light_sleep(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &light_sleep_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, light_sleep_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
|
||||
if (light_sleep_args.wakeup_time->count) {
|
||||
uint64_t timeout = 1000ULL * light_sleep_args.wakeup_time->ival[0];
|
||||
ESP_LOGI(TAG, "Enabling timer wakeup, timeout=%lluus", timeout);
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_timer_wakeup(timeout) );
|
||||
}
|
||||
int io_count = light_sleep_args.wakeup_gpio_num->count;
|
||||
if (io_count != light_sleep_args.wakeup_gpio_level->count) {
|
||||
ESP_LOGE(TAG, "Should have same number of 'io' and 'io_level' arguments");
|
||||
return 1;
|
||||
}
|
||||
for (int i = 0; i < io_count; ++i) {
|
||||
int io_num = light_sleep_args.wakeup_gpio_num->ival[i];
|
||||
int level = light_sleep_args.wakeup_gpio_level->ival[i];
|
||||
if (level != 0 && level != 1) {
|
||||
ESP_LOGE(TAG, "Invalid wakeup level: %d", level);
|
||||
return 1;
|
||||
}
|
||||
ESP_LOGI(TAG, "Enabling wakeup on GPIO%d, wakeup on %s level",
|
||||
io_num, level ? "HIGH" : "LOW");
|
||||
|
||||
ESP_ERROR_CHECK( gpio_wakeup_enable(io_num, level ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL) );
|
||||
}
|
||||
if (io_count > 0) {
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_gpio_wakeup() );
|
||||
}
|
||||
if (CONFIG_CONSOLE_UART_NUM <= UART_NUM_1) {
|
||||
ESP_LOGI(TAG, "Enabling UART wakeup (press ENTER to exit light sleep)");
|
||||
ESP_ERROR_CHECK( uart_set_wakeup_threshold(CONFIG_CONSOLE_UART_NUM, 3) );
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_uart_wakeup(CONFIG_CONSOLE_UART_NUM) );
|
||||
}
|
||||
fflush(stdout);
|
||||
uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM);
|
||||
esp_light_sleep_start();
|
||||
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
|
||||
const char *cause_str;
|
||||
switch (cause) {
|
||||
case ESP_SLEEP_WAKEUP_GPIO:
|
||||
cause_str = "GPIO";
|
||||
break;
|
||||
case ESP_SLEEP_WAKEUP_UART:
|
||||
cause_str = "UART";
|
||||
break;
|
||||
case ESP_SLEEP_WAKEUP_TIMER:
|
||||
cause_str = "timer";
|
||||
break;
|
||||
default:
|
||||
cause_str = "unknown";
|
||||
printf("%d\n", cause);
|
||||
}
|
||||
ESP_LOGI(TAG, "Woke up from: %s", cause_str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_light_sleep()
|
||||
{
|
||||
light_sleep_args.wakeup_time =
|
||||
arg_int0("t", "time", "<t>", "Wake up time, ms");
|
||||
light_sleep_args.wakeup_gpio_num =
|
||||
arg_intn(NULL, "io", "<n>", 0, 8,
|
||||
"If specified, wakeup using GPIO with given number");
|
||||
light_sleep_args.wakeup_gpio_level =
|
||||
arg_intn(NULL, "io_level", "<0|1>", 0, 8, "GPIO level to trigger wakeup");
|
||||
light_sleep_args.end = arg_end(3);
|
||||
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "light_sleep",
|
||||
.help = "Enter light sleep mode. "
|
||||
"Two wakeup modes are supported: timer and GPIO. "
|
||||
"Multiple GPIO pins can be specified using pairs of "
|
||||
"'io' and 'io_level' arguments. "
|
||||
"Will also wake up on UART input.",
|
||||
.hint = NULL,
|
||||
.func = &light_sleep,
|
||||
.argtable = &light_sleep_args
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#
|
||||
# Component Makefile
|
||||
#
|
||||
# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default,
|
||||
# this will take the sources in the src/ directory, compile them and link them into
|
||||
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
|
||||
# please read the SDK documents if you need to do this.
|
||||
#
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
||||
COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/main/
|
||||
COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/components/tools/
|
||||
28
components/codecs/CMakeLists.txt
Normal file
28
components/codecs/CMakeLists.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
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(libvorbisidec lib/libvorbisidec.a )
|
||||
add_prebuilt_library(libogg lib/libogg.a )
|
||||
add_prebuilt_library(libalac lib/libalac.a )
|
||||
add_prebuilt_library(libresample16 lib/libresample16.a )
|
||||
add_prebuilt_library(libopusfile lib/libopusfile.a )
|
||||
add_prebuilt_library(libopus lib/libopus.a )
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE libmad)
|
||||
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)
|
||||
@@ -35,8 +35,8 @@
|
||||
|
||||
#include "export.h"
|
||||
|
||||
#include "assert.h"
|
||||
#include "callback.h"
|
||||
#include "flac_assert.h"
|
||||
#include "format.h"
|
||||
#include "metadata.h"
|
||||
#include "ordinals.h"
|
||||
|
||||
@@ -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.
BIN
components/codecs/lib/libhelix-aac-sbr.a
Normal file
BIN
components/codecs/lib/libhelix-aac-sbr.a
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "nvs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#define DECLARE_SET_DEFAULT(t) void config_set_default_## t (const char *key, t value);
|
||||
#define DECLARE_GET_NUM(t) esp_err_t config_get_## t (const char *key, t * value);
|
||||
|
||||
|
||||
DECLARE_SET_DEFAULT(uint8_t);
|
||||
DECLARE_SET_DEFAULT(uint16_t);
|
||||
DECLARE_SET_DEFAULT(uint32_t);
|
||||
DECLARE_SET_DEFAULT(int8_t);
|
||||
DECLARE_SET_DEFAULT(int16_t);
|
||||
DECLARE_SET_DEFAULT(int32_t);
|
||||
DECLARE_GET_NUM(uint8_t);
|
||||
DECLARE_GET_NUM(uint16_t);
|
||||
DECLARE_GET_NUM(uint32_t);
|
||||
DECLARE_GET_NUM(int8_t);
|
||||
DECLARE_GET_NUM(int16_t);
|
||||
DECLARE_GET_NUM(int32_t);
|
||||
|
||||
bool config_has_changes();
|
||||
void config_commit_to_nvs();
|
||||
void config_start_timer();
|
||||
void config_init();
|
||||
void * config_alloc_get_default(nvs_type_t type, const char *key, void * default_value, size_t blob_size);
|
||||
void config_delete_key(const char *key);
|
||||
void config_set_default(nvs_type_t type, const char *key, void * default_value, size_t blob_size);
|
||||
void * config_alloc_get(nvs_type_t nvs_type, const char *key) ;
|
||||
bool wait_for_commit();
|
||||
char * config_alloc_get_json(bool bFormatted);
|
||||
esp_err_t config_set_value(nvs_type_t nvs_type, const char *key, void * value);
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
idf_component_register(SRCS "led.c" "audio_controls.c" "buttons.c" "services.c" "monitor.c"INCLUDE_DIRS
|
||||
INCLUDE_DIRS . ../tools/
|
||||
|
||||
)
|
||||
# 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 ${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 )
|
||||
|
||||
347
components/display/ILI9341.c
Normal file
347
components/display/ILI9341.c
Normal file
@@ -0,0 +1,347 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
#include "gds.h"
|
||||
#include "gds_private.h"
|
||||
|
||||
#define SHADOW_BUFFER
|
||||
#define 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";
|
||||
|
||||
enum { ILI9341, ILI9341_24 }; //ILI9341_24 for future use...
|
||||
|
||||
struct PrivateSpace {
|
||||
uint8_t *iRAM, *Shadowbuffer;
|
||||
struct {
|
||||
uint16_t Height, Width;
|
||||
} Offset;
|
||||
uint8_t MADCtl, PageSize;
|
||||
uint8_t Model;
|
||||
};
|
||||
|
||||
// Functions are not declared to minimize # of lines
|
||||
|
||||
static void WriteByte( struct GDS_Device* Device, uint8_t Data ) {
|
||||
Device->WriteData( Device, &Data, 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 Update16( struct GDS_Device* Device ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
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; 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) {
|
||||
*optr = *iptr;
|
||||
if (c < FirstCol) FirstCol = c;
|
||||
if (c > LastCol) LastCol = c;
|
||||
if (FirstRow < 0) FirstRow = r;
|
||||
LastRow = r;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
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
|
||||
// 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 * 2, Height * Device->Width * 2 );
|
||||
Device->WriteData( Device, Private->iRAM, Height * Device->Width * 2 );
|
||||
} else {
|
||||
Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void Update24( struct GDS_Device* Device ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
|
||||
#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, 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, 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));
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
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
|
||||
|
||||
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_X = {
|
||||
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff,
|
||||
.SetLayout = SetLayout,
|
||||
.Update = Update16, .Init = Init,
|
||||
.Mode = GDS_RGB565, .Depth = 16,
|
||||
};
|
||||
|
||||
struct GDS_Device* ILI9341_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
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_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;
|
||||
}
|
||||
@@ -73,8 +73,12 @@ static void Update( struct GDS_Device* Device ) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static void SetHFlip( struct GDS_Device* Device, bool On ) { Device->WriteCommand( Device, On ? 0xA1 : 0xA0 ); }
|
||||
static void SetVFlip( struct GDS_Device *Device, bool On ) { Device->WriteCommand( Device, On ? 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 ); }
|
||||
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
|
||||
|
||||
@@ -83,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;
|
||||
@@ -117,8 +125,8 @@ static bool Init( struct GDS_Device* Device ) {
|
||||
Device->WriteCommand( Device, 0x40 + 0x00 );
|
||||
Device->SetContrast( Device, 0x7F );
|
||||
// set flip modes
|
||||
Device->SetVFlip( Device, false );
|
||||
Device->SetHFlip( Device, false );
|
||||
struct GDS_Layout Layout = { };
|
||||
Device->SetLayout( Device, &Layout );
|
||||
// no Display Inversion
|
||||
Device->WriteCommand( Device, 0xA6 );
|
||||
// set Clocks
|
||||
@@ -135,8 +143,13 @@ static bool Init( struct GDS_Device* Device ) {
|
||||
|
||||
static const struct GDS_Device SH1106 = {
|
||||
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
|
||||
.SetVFlip = SetVFlip, .SetHFlip = SetHFlip,
|
||||
.SetLayout = SetLayout,
|
||||
.Update = Update, .Init = Init,
|
||||
.Depth = 1,
|
||||
.SPIParams = SPIParams,
|
||||
#if !defined SHADOW_BUFFER && defined USE_IRAM
|
||||
.Alloc = GDS_ALLOC_IRAM_SPI;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct GDS_Device* SH1106_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
@@ -144,10 +157,7 @@ struct GDS_Device* SH1106_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
|
||||
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
|
||||
*Device = SH1106;
|
||||
Device->Depth = 1;
|
||||
#if !defined SHADOW_BUFFER && defined USE_IRAM
|
||||
Device->Alloc = GDS_ALLOC_IRAM_SPI;
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "SH1106 driver");
|
||||
|
||||
return Device;
|
||||
|
||||
@@ -85,8 +85,12 @@ static void Update( struct GDS_Device* Device ) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static void SetHFlip( struct GDS_Device* Device, bool On ) { Device->WriteCommand( Device, On ? 0xA1 : 0xA0 ); }
|
||||
static void SetVFlip( struct GDS_Device *Device, bool On ) { Device->WriteCommand( Device, On ? 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 ); }
|
||||
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
|
||||
|
||||
@@ -129,8 +133,8 @@ static bool Init( struct GDS_Device* Device ) {
|
||||
Device->WriteCommand( Device, 0x40 + 0x00 );
|
||||
Device->SetContrast( Device, 0x7F );
|
||||
// set flip modes
|
||||
Device->SetVFlip( Device, false );
|
||||
Device->SetHFlip( Device, false );
|
||||
struct GDS_Layout Layout = { };
|
||||
Device->SetLayout( Device, &Layout );
|
||||
// no Display Inversion
|
||||
Device->WriteCommand( Device, 0xA6 );
|
||||
// set Clocks
|
||||
@@ -150,8 +154,12 @@ static bool Init( struct GDS_Device* Device ) {
|
||||
|
||||
static const struct GDS_Device SSD1306 = {
|
||||
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
|
||||
.SetVFlip = SetVFlip, .SetHFlip = SetHFlip,
|
||||
.SetLayout = SetLayout,
|
||||
.Update = Update, .Init = Init,
|
||||
.Mode = GDS_MONO, .Depth = 1,
|
||||
#if !defined SHADOW_BUFFER && defined USE_IRAM
|
||||
.Alloc = GDS_ALLOC_IRAM_SPI,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct GDS_Device* SSD1306_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
@@ -159,10 +167,7 @@ struct GDS_Device* SSD1306_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
|
||||
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
|
||||
*Device = SSD1306;
|
||||
Device->Depth = 1;
|
||||
#if !defined SHADOW_BUFFER && defined USE_IRAM
|
||||
Device->Alloc = GDS_ALLOC_IRAM_SPI;
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "SSD1306 driver");
|
||||
|
||||
return Device;
|
||||
|
||||
@@ -96,22 +96,16 @@ static void Update( struct GDS_Device* Device ) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static void SetHFlip( struct GDS_Device* Device, bool On ) {
|
||||
static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
Private->ReMap = On ? (Private->ReMap & ~(1 << 1)) : (Private->ReMap | (1 << 1));
|
||||
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 SetVFlip( struct GDS_Device *Device, bool On ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
Private->ReMap = On ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
|
||||
Device->WriteCommand( Device, 0xA0 );
|
||||
Device->WriteData( Device, &Private->ReMap, 1 );
|
||||
WriteDataByte( Device, 0x11 );
|
||||
}
|
||||
|
||||
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
|
||||
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
|
||||
|
||||
@@ -128,7 +122,7 @@ static bool Init( struct GDS_Device* Device ) {
|
||||
|
||||
// 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) ;
|
||||
while (Private->PageSize && Device->Height != (Device->Height / Private->PageSize) * Private->PageSize) Private->PageSize--;
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
Private->Shadowbuffer = malloc( Device->FramebufferSize );
|
||||
@@ -152,26 +146,31 @@ static bool Init( struct GDS_Device* Device ) {
|
||||
|
||||
// set flip modes
|
||||
Private->ReMap = 0;
|
||||
Device->SetVFlip( Device, false );
|
||||
Device->SetHFlip( Device, 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 );
|
||||
|
||||
@@ -187,8 +186,9 @@ static bool Init( struct GDS_Device* Device ) {
|
||||
|
||||
static const struct GDS_Device SSD1322 = {
|
||||
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
|
||||
.SetVFlip = SetVFlip, .SetHFlip = SetHFlip,
|
||||
.SetLayout = SetLayout,
|
||||
.Update = Update, .Init = Init,
|
||||
.Mode = GDS_GRAYSCALE, .Depth = 4,
|
||||
};
|
||||
|
||||
struct GDS_Device* SSD1322_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
@@ -197,7 +197,6 @@ struct GDS_Device* SSD1322_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
|
||||
|
||||
*Device = SSD1322;
|
||||
Device->Depth = 4;
|
||||
|
||||
|
||||
return Device;
|
||||
}
|
||||
@@ -168,7 +168,7 @@ static void Update1( struct GDS_Device* Device ) {
|
||||
}
|
||||
|
||||
// in 1 bit mode, SSD1326 has a different memory map than SSD1306 and SH1106
|
||||
static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
static void IRAM_ATTR _DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint32_t XBit = ( X & 0x07 );
|
||||
uint8_t* FBOffset = Device->Framebuffer + ( ( Y * Device->Width + X ) >> 3 );
|
||||
|
||||
@@ -188,12 +188,12 @@ static void ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int
|
||||
for (int r = y1; r <= y2; r++) {
|
||||
int c = x1;
|
||||
// for a row that is not on a boundary, not column opt can be done, so handle all columns on that line
|
||||
while (c & 0x07 && c <= x2) DrawPixel1Fast( Device, c++, r, Color );
|
||||
while (c & 0x07 && c <= x2) _DrawPixel1Fast( Device, c++, r, Color );
|
||||
// at this point we are aligned on column boundary
|
||||
int chunk = (x2 - c + 1) >> 3;
|
||||
memset(optr + Width * r + (c >> 3), _Color, chunk );
|
||||
c += chunk * 8;
|
||||
while (c <= x2) DrawPixel1Fast( Device, c++, r, Color );
|
||||
while (c <= x2) _DrawPixel1Fast( Device, c++, r, Color );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,22 +222,20 @@ static void DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, i
|
||||
}
|
||||
}
|
||||
|
||||
static void SetHFlip( struct GDS_Device* Device, bool On ) {
|
||||
static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
if (Private->Model == SSD1326) Private->ReMap = On ? (Private->ReMap | ((1 << 0) | (1 << 2))) : (Private->ReMap & ~((1 << 0) | (1 << 2)));
|
||||
else Private->ReMap = On ? (Private->ReMap | ((1 << 0) | (1 << 1))) : (Private->ReMap & ~((1 << 0) | (1 << 1)));
|
||||
if (Private->Model == SSD1326) {
|
||||
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 = 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 SetVFlip( struct GDS_Device *Device, bool On ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
if (Private->Model == SSD1326) Private->ReMap = On ? (Private->ReMap | (1 << 1)) : (Private->ReMap & ~(1 << 1));
|
||||
else Private->ReMap = On ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
|
||||
Device->WriteCommand( Device, 0xA0 );
|
||||
Device->WriteCommand( Device, Private->ReMap );
|
||||
}
|
||||
|
||||
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
|
||||
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
|
||||
|
||||
@@ -251,7 +249,7 @@ static bool Init( struct GDS_Device* Device ) {
|
||||
|
||||
// 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) ;
|
||||
while (Private->PageSize && Device->Height != (Device->Height / Private->PageSize) * Private->PageSize) Private->PageSize--;
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
#ifdef USE_IRAM
|
||||
@@ -270,7 +268,6 @@ static bool Init( struct GDS_Device* Device ) {
|
||||
#ifdef USE_IRAM
|
||||
if (Device->Depth == 4 && Device->IF == GDS_IF_SPI) Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "SSD1326/7 with bit depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM);
|
||||
@@ -292,8 +289,8 @@ static bool Init( struct GDS_Device* Device ) {
|
||||
Device->WriteCommand( Device, 0x00 );
|
||||
Device->SetContrast( Device, 0x7F );
|
||||
// set flip modes
|
||||
Device->SetVFlip( Device, false );
|
||||
Device->SetHFlip( Device, false );
|
||||
struct GDS_Layout Layout = { };
|
||||
Device->SetLayout( Device, &Layout );
|
||||
// no Display Inversion
|
||||
Device->WriteCommand( Device, 0xA6 );
|
||||
// set Clocks
|
||||
@@ -317,14 +314,15 @@ static bool Init( struct GDS_Device* Device ) {
|
||||
|
||||
static const struct GDS_Device SSD132x = {
|
||||
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
|
||||
.SetVFlip = SetVFlip, .SetHFlip = SetHFlip,
|
||||
.SetLayout = SetLayout,
|
||||
.Update = Update4, .Init = Init,
|
||||
.Mode = GDS_GRAYSCALE, .Depth = 4,
|
||||
};
|
||||
|
||||
struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
uint8_t Model;
|
||||
int Depth;
|
||||
|
||||
|
||||
if (strcasestr(Driver, "SSD1326")) Model = SSD1326;
|
||||
else if (strcasestr(Driver, "SSD1327")) Model = SSD1327;
|
||||
else return NULL;
|
||||
@@ -332,22 +330,22 @@ struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
|
||||
|
||||
*Device = SSD132x;
|
||||
((struct PrivateSpace*) Device->Private)->Model = Model;
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
Private->Model = Model;
|
||||
|
||||
sscanf(Driver, "%*[^:]:%u", &Depth);
|
||||
Device->Depth = Depth;
|
||||
|
||||
if (Model == SSD1326 && Device->Depth == 1) {
|
||||
|
||||
if (Model == SSD1326 && Depth == 1) {
|
||||
Device->Update = Update1;
|
||||
Device->DrawPixelFast = DrawPixel1Fast;
|
||||
Device->DrawPixelFast = _DrawPixel1Fast;
|
||||
Device->DrawBitmapCBR = DrawBitmapCBR;
|
||||
Device->ClearWindow = ClearWindow;
|
||||
Device->Depth = 1;
|
||||
Device->Mode = GDS_MONO;
|
||||
#if !defined SHADOW_BUFFER && defined USE_IRAM
|
||||
Device->Alloc = GDS_ALLOC_IRAM_SPI;
|
||||
#endif
|
||||
} else {
|
||||
Device->Depth = 4;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return Device;
|
||||
}
|
||||
288
components/display/SSD1351.c
Normal file
288
components/display/SSD1351.c
Normal file
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 Tara Keeling
|
||||
* 2020 Philippe G.
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
#include "gds.h"
|
||||
#include "gds_private.h"
|
||||
|
||||
#define SHADOW_BUFFER
|
||||
#define USE_IRAM
|
||||
#define PAGE_BLOCK 2048
|
||||
#define ENABLE_WRITE 0x5c
|
||||
|
||||
#define min(a,b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
static char TAG[] = "SSD1351";
|
||||
|
||||
struct PrivateSpace {
|
||||
uint8_t *iRAM, *Shadowbuffer;
|
||||
uint8_t ReMap, PageSize;
|
||||
};
|
||||
|
||||
// Functions are not declared to minimize # of lines
|
||||
|
||||
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, 0x15 );
|
||||
WriteByte( Device, Start );
|
||||
WriteByte( Device, End );
|
||||
}
|
||||
static void SetRowAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
|
||||
Device->WriteCommand( Device, 0x75 );
|
||||
WriteByte( Device, Start );
|
||||
WriteByte( Device, End );
|
||||
}
|
||||
|
||||
static void Update16( struct GDS_Device* Device ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
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; 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) {
|
||||
*optr = *iptr;
|
||||
if (c < FirstCol) FirstCol = c;
|
||||
if (c > LastCol) LastCol = c;
|
||||
if (FirstRow < 0) FirstRow = r;
|
||||
LastRow = r;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
FirstCol *= 2;
|
||||
LastCol = LastCol * 2 + 1;
|
||||
SetRowAddress( Device, FirstRow, LastRow );
|
||||
SetColumnAddress( Device, FirstCol, LastCol );
|
||||
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 && 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
|
||||
// always update by full lines
|
||||
SetColumnAddress( Device, 0, 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, r, r + Height - 1 );
|
||||
Device->WriteCommand(Device, ENABLE_WRITE);
|
||||
|
||||
if (Private->iRAM) {
|
||||
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, Height * Device->Width * 2 );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void Update24( struct GDS_Device* Device ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
int FirstCol = (Device->Width * 3) / 2, LastCol = 0, FirstRow = -1, LastRow = 0;
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer;
|
||||
|
||||
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 - 1) / 3) * (r - FirstRow + 1) * 3 < PAGE_BLOCK && r != Device->Height - 1)) continue;
|
||||
|
||||
FirstCol = (FirstCol * 2) / 3;
|
||||
LastCol = (LastCol * 2 + 1) / 3;
|
||||
SetRowAddress( Device, FirstRow, LastRow );
|
||||
SetColumnAddress( Device, FirstCol, LastCol );
|
||||
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 && 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, 0, 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, r, 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;
|
||||
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 ); }
|
||||
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
|
||||
|
||||
static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
|
||||
Device->WriteCommand( Device, 0xC7 );
|
||||
WriteByte( Device, Contrast >> 4);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
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
|
||||
|
||||
ESP_LOGI(TAG, "SSD1351 with bit depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM);
|
||||
|
||||
// unlock (specially 0xA2)
|
||||
Device->WriteCommand( Device, 0xFD);
|
||||
WriteByte(Device, 0xB1);
|
||||
|
||||
// set clocks
|
||||
/*
|
||||
Device->WriteCommand( Device, 0xB3 );
|
||||
WriteByte( Device, ( 0x08 << 4 ) | 0x00 );
|
||||
*/
|
||||
|
||||
// need to be off and disable display RAM
|
||||
Device->DisplayOff( Device );
|
||||
|
||||
// need COM split (5)
|
||||
Private->ReMap = (1 << 5);
|
||||
|
||||
// Display Offset
|
||||
Device->WriteCommand( Device, 0xA2 );
|
||||
WriteByte( Device, 0x00 );
|
||||
|
||||
// Display Start Line
|
||||
Device->WriteCommand( Device, 0xA1 );
|
||||
WriteByte( Device, 0x00 );
|
||||
|
||||
// set flip modes & contrast
|
||||
Device->SetContrast( Device, 0x7F );
|
||||
struct GDS_Layout Layout = { };
|
||||
Device->SetLayout( Device, &Layout );
|
||||
|
||||
// set Adressing Mode Horizontal
|
||||
Private->ReMap |= (0 << 2);
|
||||
// set screen depth (16/18)
|
||||
if (Device->Depth == 24) Private->ReMap |= (0x02 << 6);
|
||||
// write ReMap byte
|
||||
Device->WriteCommand( Device, 0xA0 );
|
||||
WriteByte( Device, Private->ReMap );
|
||||
|
||||
// no Display Inversion
|
||||
Device->WriteCommand( Device, 0xA6 );
|
||||
|
||||
// gone with the wind
|
||||
Device->DisplayOn( Device );
|
||||
Device->Update( Device );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const struct GDS_Device SSD1351 = {
|
||||
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
|
||||
.SetLayout = SetLayout,
|
||||
.Update = Update16, .Init = Init,
|
||||
.Mode = GDS_RGB565, .Depth = 16,
|
||||
};
|
||||
|
||||
struct GDS_Device* SSD1351_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
int Depth;
|
||||
|
||||
if (!strcasestr(Driver, "SSD1351")) return NULL;
|
||||
|
||||
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
|
||||
|
||||
*Device = SSD1351;
|
||||
sscanf(Driver, "%*[^:]:%u", &Depth);
|
||||
|
||||
if (Depth == 18) {
|
||||
Device->Mode = GDS_RGB666;
|
||||
Device->Depth = 24;
|
||||
Device->Update = Update24;
|
||||
}
|
||||
|
||||
return Device;
|
||||
}
|
||||
@@ -118,7 +118,7 @@ static void Update( struct GDS_Device* Device ) {
|
||||
}
|
||||
|
||||
// remember that for these ELD drivers W and H are "inverted"
|
||||
static void IRAM_ATTR DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
static inline void _DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint32_t YBit = ( Y & 0x07 );
|
||||
Y>>= 3;
|
||||
|
||||
@@ -129,7 +129,7 @@ static void IRAM_ATTR DrawPixelFast( struct GDS_Device* Device, int X, int Y, in
|
||||
static void ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
|
||||
for (int r = y1; r <= y2; r++) {
|
||||
for (int c = x1; c <= x2; c++) {
|
||||
DrawPixelFast( Device, c, r, Color );
|
||||
_DrawPixel( Device, c, r, Color );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,8 +228,10 @@ static bool Init( struct GDS_Device* Device ) {
|
||||
|
||||
static const struct GDS_Device SSD1675 = {
|
||||
.DrawBitmapCBR = DrawBitmapCBR, .ClearWindow = ClearWindow,
|
||||
.DrawPixelFast = DrawPixelFast,
|
||||
.DrawPixelFast = _DrawPixel,
|
||||
.Update = Update, .Init = Init,
|
||||
.Mode = GDS_MONO, .Depth = 1,
|
||||
.Alloc = GDS_ALLOC_NONE,
|
||||
};
|
||||
|
||||
struct GDS_Device* SSD1675_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
@@ -238,13 +240,10 @@ struct GDS_Device* SSD1675_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
|
||||
*Device = SSD1675;
|
||||
|
||||
Device->Depth = 1;
|
||||
Device->Alloc = GDS_ALLOC_NONE;
|
||||
|
||||
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);
|
||||
|
||||
|
||||
306
components/display/ST77xx.c
Normal file
306
components/display/ST77xx.c
Normal file
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 Tara Keeling
|
||||
* 2020 Philippe G.
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
#include "gds.h"
|
||||
#include "gds_private.h"
|
||||
|
||||
#define SHADOW_BUFFER
|
||||
#define USE_IRAM
|
||||
#define PAGE_BLOCK 2048
|
||||
#define ENABLE_WRITE 0x2c
|
||||
|
||||
#define min(a,b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
static char TAG[] = "ST77xx";
|
||||
|
||||
enum { ST7735, ST7789 };
|
||||
|
||||
struct PrivateSpace {
|
||||
uint8_t *iRAM, *Shadowbuffer;
|
||||
struct {
|
||||
uint16_t Height, Width;
|
||||
} Offset;
|
||||
uint8_t MADCtl, PageSize;
|
||||
uint8_t Model;
|
||||
};
|
||||
|
||||
// Functions are not declared to minimize # of lines
|
||||
|
||||
static void WriteByte( struct GDS_Device* Device, uint8_t Data ) {
|
||||
Device->WriteData( Device, &Data, 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 Update16( struct GDS_Device* Device ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
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; 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) {
|
||||
*optr = *iptr;
|
||||
if (c < FirstCol) FirstCol = c;
|
||||
if (c > LastCol) LastCol = c;
|
||||
if (FirstRow < 0) FirstRow = r;
|
||||
LastRow = r;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
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
|
||||
// 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 * 2, Height * Device->Width * 2 );
|
||||
Device->WriteData( Device, Private->iRAM, Height * Device->Width * 2 );
|
||||
} else {
|
||||
Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void Update24( struct GDS_Device* Device ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
|
||||
#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 - 1) / 3) * (r - FirstRow + 1) * 3 < 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;
|
||||
|
||||
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 );
|
||||
|
||||
#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, 0x29 ); }
|
||||
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0x28 ); }
|
||||
|
||||
static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
|
||||
Device->WriteCommand( Device, 0x51 );
|
||||
WriteByte( Device, Contrast );
|
||||
|
||||
Device->SetContrast = NULL;
|
||||
GDS_SetContrast( Device, Contrast );
|
||||
Device->SetContrast = SetContrast;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
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
|
||||
|
||||
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 );
|
||||
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 );
|
||||
|
||||
// gone with the wind
|
||||
Device->DisplayOn( Device );
|
||||
Device->Update( Device );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const struct GDS_Device ST77xx = {
|
||||
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff,
|
||||
.SetLayout = SetLayout,
|
||||
.Update = Update16, .Init = Init,
|
||||
.Mode = GDS_RGB565, .Depth = 16,
|
||||
};
|
||||
|
||||
struct GDS_Device* ST77xx_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
uint8_t Model;
|
||||
int Depth;
|
||||
|
||||
if (strcasestr(Driver, "ST7735")) Model = ST7735;
|
||||
else if (strcasestr(Driver, "ST7789")) Model = ST7789;
|
||||
else return NULL;
|
||||
|
||||
if (!Device) Device = calloc(1, sizeof(struct GDS_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;
|
||||
Device->Depth = 24;
|
||||
Device->Update = Update24;
|
||||
}
|
||||
|
||||
if (Model == ST7789) Device->SetContrast = SetContrast;
|
||||
|
||||
return Device;
|
||||
}
|
||||
@@ -9,22 +9,37 @@
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "gds.h"
|
||||
#include "gds_private.h"
|
||||
|
||||
static struct GDS_Device Display;
|
||||
static struct GDS_BacklightPWM PWMConfig;
|
||||
|
||||
static char TAG[] = "gds";
|
||||
|
||||
struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[] ) {
|
||||
struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[], struct GDS_BacklightPWM* PWM ) {
|
||||
if (!Driver) return NULL;
|
||||
if (PWM) PWMConfig = *PWM;
|
||||
|
||||
for (int i = 0; DetectFunc[i]; i++) {
|
||||
if (DetectFunc[i](Driver, &Display)) {
|
||||
ESP_LOGD(TAG, "Detected driver %p", &Display);
|
||||
if (PWM && PWM->Init) {
|
||||
ledc_timer_config_t PWMTimer = {
|
||||
.duty_resolution = LEDC_TIMER_13_BIT,
|
||||
.freq_hz = 5000,
|
||||
.speed_mode = LEDC_HIGH_SPEED_MODE,
|
||||
.timer_num = PWMConfig.Timer,
|
||||
};
|
||||
ledc_timer_config(&PWMTimer);
|
||||
}
|
||||
ESP_LOGD(TAG, "Detected driver %p with PWM %d", &Display, PWM ? PWM->Init : 0);
|
||||
return &Display;
|
||||
}
|
||||
}
|
||||
@@ -53,12 +68,20 @@ void GDS_ClearExt(struct GDS_Device* Device, bool full, ...) {
|
||||
}
|
||||
|
||||
void GDS_Clear( struct GDS_Device* Device, int Color ) {
|
||||
if (Device->Depth == 1) Color = Color == GDS_COLOR_BLACK ? 0 : 0xff;
|
||||
else if (Device->Depth == 4) Color = Color | (Color << 4);
|
||||
memset( Device->Framebuffer, Color, Device->FramebufferSize );
|
||||
if (Color == GDS_COLOR_BLACK) memset( Device->Framebuffer, 0, Device->FramebufferSize );
|
||||
else if (Device->Depth == 1) memset( Device->Framebuffer, 0xff, Device->FramebufferSize );
|
||||
else if (Device->Depth == 4) memset( Device->Framebuffer, Color | (Color << 4), Device->FramebufferSize );
|
||||
else if (Device->Depth == 8) memset( Device->Framebuffer, Color, Device->FramebufferSize );
|
||||
else GDS_ClearWindow(Device, 0, 0, -1, -1, Color);
|
||||
Device->Dirty = true;
|
||||
}
|
||||
|
||||
#define CLEAR_WINDOW(x1,y1,x2,y2,F,W,C,T,N) \
|
||||
for (int y = y1; y <= y2; y++) { \
|
||||
T *Ptr = (T*) F + (y * W + x1)*N; \
|
||||
for (int c = (x2 - x1)*N; c-- >= 0; *Ptr++ = C); \
|
||||
}
|
||||
|
||||
void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
|
||||
// -1 means up to width/height
|
||||
if (x2 < 0) x2 = Device->Width - 1;
|
||||
@@ -80,7 +103,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
|
||||
int c = x1;
|
||||
// for a row that is not on a boundary, no optimization possible
|
||||
while (r & 0x07 && r <= y2) {
|
||||
for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color );
|
||||
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
|
||||
r++;
|
||||
}
|
||||
// go fast if we have more than 8 lines to write
|
||||
@@ -88,7 +111,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
|
||||
memset(optr + Width * r + x1, _Color, x2 - x1 + 1);
|
||||
r += 8;
|
||||
} else while (r <= y2) {
|
||||
for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color );
|
||||
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
|
||||
r++;
|
||||
}
|
||||
}
|
||||
@@ -104,16 +127,22 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
|
||||
// try to do byte processing as much as possible
|
||||
for (int r = y1; r <= y2; r++) {
|
||||
int c = x1;
|
||||
if (c & 0x01) GDS_DrawPixelFast( Device, c++, r, Color);
|
||||
if (c & 0x01) DrawPixelFast( Device, c++, r, Color);
|
||||
int chunk = (x2 - c + 1) >> 1;
|
||||
memset(optr + ((r * Width + c) >> 1), _Color, chunk);
|
||||
if (c + chunk <= x2) GDS_DrawPixelFast( Device, x2, r, Color);
|
||||
if (c + chunk <= x2) DrawPixelFast( Device, x2, r, Color);
|
||||
}
|
||||
}
|
||||
} else if (Device->Depth == 8) {
|
||||
CLEAR_WINDOW(x1,y1,x2,y2,Device->Framebuffer,Device->Width,Color,uint8_t,1);
|
||||
} else if (Device->Depth == 16) {
|
||||
CLEAR_WINDOW(x1,y1,x2,y2,Device->Framebuffer,Device->Width,Color,uint16_t,1);
|
||||
} else if (Device->Depth == 24) {
|
||||
CLEAR_WINDOW(x1,y1,x2,y2,Device->Framebuffer,Device->Width,Color,uint8_t,3);
|
||||
} else {
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
GDS_DrawPixelFast( Device, x, y, Color);
|
||||
DrawPixelFast( Device, x, y, Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,29 +167,81 @@ bool GDS_Reset( struct GDS_Device* Device ) {
|
||||
|
||||
bool GDS_Init( struct GDS_Device* Device ) {
|
||||
|
||||
Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth);
|
||||
if (Device->Depth > 8) Device->FramebufferSize = Device->Width * Device->Height * ((8 + Device->Depth - 1) / 8);
|
||||
else Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth);
|
||||
|
||||
// allocate FB unless explicitely asked not to
|
||||
if (!(Device->Alloc & GDS_ALLOC_NONE)) {
|
||||
if ((Device->Alloc & GDS_ALLOC_IRAM) || ((Device->Alloc & GDS_ALLOC_IRAM_SPI) && Device->IF == GDS_IF_SPI)) {
|
||||
heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
|
||||
Device->Framebuffer = heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
|
||||
} else {
|
||||
Device->Framebuffer = calloc( 1, Device->FramebufferSize );
|
||||
}
|
||||
NullCheck( Device->Framebuffer, return false );
|
||||
}
|
||||
|
||||
if (Device->Backlight.Pin >= 0) {
|
||||
Device->Backlight.Channel = PWMConfig.Channel++;
|
||||
Device->Backlight.PWM = PWMConfig.Max - 1;
|
||||
|
||||
ledc_channel_config_t PWMChannel = {
|
||||
.channel = Device->Backlight.Channel,
|
||||
.duty = Device->Backlight.PWM,
|
||||
.gpio_num = Device->Backlight.Pin,
|
||||
.speed_mode = LEDC_HIGH_SPEED_MODE,
|
||||
.hpoint = 0,
|
||||
.timer_sel = PWMConfig.Timer,
|
||||
};
|
||||
|
||||
ledc_channel_config(&PWMChannel);
|
||||
}
|
||||
|
||||
bool Res = Device->Init( Device );
|
||||
if (!Res) free(Device->Framebuffer);
|
||||
if (!Res && Device->Framebuffer) free(Device->Framebuffer);
|
||||
return Res;
|
||||
}
|
||||
|
||||
void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { if (Device->SetContrast) Device->SetContrast( Device, Contrast); }
|
||||
void GDS_SetHFlip( struct GDS_Device* Device, bool On ) { if (Device->SetHFlip) Device->SetHFlip( Device, On ); }
|
||||
void GDS_SetVFlip( struct GDS_Device* Device, bool On ) { if (Device->SetVFlip) Device->SetVFlip( Device, On ); }
|
||||
int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level) {
|
||||
switch(Device->Mode) {
|
||||
case GDS_MONO: return Level;
|
||||
case GDS_GRAYSCALE: return Level >> (8 - Device->Depth);
|
||||
case GDS_RGB332:
|
||||
Level >>= 5;
|
||||
return (Level << 6) | (Level << 3) | (Level >> 1);
|
||||
case GDS_RGB444:
|
||||
Level >>= 4;
|
||||
return (Level << 8) | (Level << 4) | Level;
|
||||
case GDS_RGB555:
|
||||
Level >>= 3;
|
||||
return (Level << 10) | (Level << 5) | Level;
|
||||
case GDS_RGB565:
|
||||
Level >>= 2;
|
||||
return ((Level & ~0x01) << 10) | (Level << 5) | (Level >> 1);
|
||||
case GDS_RGB666:
|
||||
Level >>= 2;
|
||||
return (Level << 12) | (Level << 6) | Level;
|
||||
case GDS_RGB888:
|
||||
return (Level << 16) | (Level << 8) | Level;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
|
||||
if (Device->SetContrast) Device->SetContrast( Device, Contrast );
|
||||
else if (Device->Backlight.Pin >= 0) {
|
||||
Device->Backlight.PWM = PWMConfig.Max * powf(Contrast / 255.0, 3);
|
||||
ledc_set_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel, Device->Backlight.PWM );
|
||||
ledc_update_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel );
|
||||
}
|
||||
}
|
||||
|
||||
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_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 ); }
|
||||
@@ -5,40 +5,50 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
/* NOTE for drivers:
|
||||
The build-in DrawPixel(Fast), DrawCBR and ClearWindow are optimized for 1 bit
|
||||
and 4 bits screen depth. For any other type of screen, DrawCBR and ClearWindow
|
||||
default to use DrawPixel, which is very sub-optimal. For such other depth, you
|
||||
must supply the DrawPixelFast. The built-in 1 bit depth function are only for
|
||||
screen with vertical framing (1 byte = 8 lines). For example SSD1326 in
|
||||
The build-in DrawPixel(Fast), DrawCBR and ClearWindow have optimized for 1 bit
|
||||
and 4 bits grayscale screen depth and 8, 16, 24 color. For any other type of screen,
|
||||
DrawCBR and ClearWindow default to use DrawPixel, which is very sub-optimal. For
|
||||
other depth, you must supply the DrawPixelFast. The built-in 1 bit depth function
|
||||
are only for screen with vertical framing (1 byte = 8 lines). For example SSD1326 in
|
||||
monochrome mode is not such type of screen, SH1106 and SSD1306 are
|
||||
*/
|
||||
|
||||
enum { GDS_COLOR_L0 = 0, GDS_COLOR_L1, GDS_COLOR_L2, GDS_COLOR_L3, GDS_COLOR_L4, GDS_COLOR_L5, GDS_COLOR_L6, GDS_COLOR_L7,
|
||||
GDS_COLOR_L8, GDS_COLOR_L9, GDS_COLOR_L10, GDS_COLOR_L11, GDS_COLOR_L12, GDS_COLOR_L13, GDS_COLOR_L14, GDS_COLOR_L15,
|
||||
GDS_COLOR_MAX
|
||||
};
|
||||
// this is an ordered enum, do not change!
|
||||
enum { GDS_MONO = 0, GDS_GRAYSCALE, GDS_RGB332, GDS_RGB444, GDS_RGB555, GDS_RGB565, GDS_RGB666, GDS_RGB888 };
|
||||
|
||||
#define GDS_COLOR_BLACK (0)
|
||||
#define GDS_COLOR_WHITE (-1)
|
||||
#define GDS_COLOR_XOR (GDS_COLOR_MAX + 1)
|
||||
#define GDS_COLOR_XOR (256)
|
||||
|
||||
struct GDS_Device;
|
||||
struct GDS_FontDef;
|
||||
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);
|
||||
|
||||
struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[] );
|
||||
struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[], struct GDS_BacklightPWM *PWM );
|
||||
|
||||
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_SetHFlip( struct GDS_Device* Device, bool On );
|
||||
void GDS_SetVFlip( struct GDS_Device* Device, bool On );
|
||||
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 );
|
||||
int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level );
|
||||
void GDS_ClearExt( struct GDS_Device* Device, bool full, ...);
|
||||
void GDS_Clear( struct GDS_Device* Device, int Color );
|
||||
void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color );
|
||||
|
||||
@@ -8,10 +8,10 @@ extern "C" {
|
||||
struct GDS_Device;
|
||||
|
||||
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 );
|
||||
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 );
|
||||
bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int Speed, int BacklightPin, int Mode );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
@@ -45,9 +44,13 @@ __attribute__( ( always_inline ) ) static inline void SwapInt( int* a, int* b )
|
||||
*a = Temp;
|
||||
}
|
||||
|
||||
// un-comment if need to be instanciated for external callers
|
||||
extern inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color );
|
||||
extern inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color );
|
||||
void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
DrawPixelFast( Device, X, Y, Color );
|
||||
}
|
||||
|
||||
void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
DrawPixel( Device, X, Y, Color );
|
||||
}
|
||||
|
||||
void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Color ) {
|
||||
int XEnd = x + Width;
|
||||
@@ -60,7 +63,7 @@ void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Colo
|
||||
if (y < 0) y = 0;
|
||||
else if (y >= Device->Height) y = Device->Height - 1;
|
||||
|
||||
for ( ; x < XEnd; x++ ) GDS_DrawPixelFast( Device, x, y, Color );
|
||||
for ( ; x < XEnd; x++ ) DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
|
||||
void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color ) {
|
||||
@@ -74,7 +77,7 @@ void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Col
|
||||
if (y < 0) y = 0;
|
||||
else if (YEnd >= Device->Height) YEnd = Device->Height - 1;
|
||||
|
||||
for ( ; y < YEnd; y++ ) GDS_DrawPixel( Device, x, y, Color );
|
||||
for ( ; y < YEnd; y++ ) DrawPixel( Device, x, y, Color );
|
||||
}
|
||||
|
||||
static inline void DrawWideLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color ) {
|
||||
@@ -94,7 +97,7 @@ static inline void DrawWideLine( struct GDS_Device* Device, int x0, int y0, int
|
||||
|
||||
for ( ; x < x1; x++ ) {
|
||||
if ( IsPixelVisible( Device, x, y ) == true ) {
|
||||
GDS_DrawPixelFast( Device, x, y, Color );
|
||||
DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
|
||||
if ( Error > 0 ) {
|
||||
@@ -123,7 +126,7 @@ static inline void DrawTallLine( struct GDS_Device* Device, int x0, int y0, int
|
||||
|
||||
for ( ; y < y1; y++ ) {
|
||||
if ( IsPixelVisible( Device, x, y ) == true ) {
|
||||
GDS_DrawPixelFast( Device, x, y, Color );
|
||||
DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
|
||||
if ( Error > 0 ) {
|
||||
@@ -199,7 +202,9 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
|
||||
if (Device->DrawBitmapCBR) {
|
||||
Device->DrawBitmapCBR( Device, Data, Width, Height, Color );
|
||||
} else if (Device->Depth == 1) {
|
||||
|
||||
Height >>= 3;
|
||||
|
||||
// need to do row/col swap and bit-reverse
|
||||
for (int r = 0; r < Height; r++) {
|
||||
uint8_t *optr = Device->Framebuffer + r*Device->Width, *iptr = Data + r;
|
||||
@@ -211,8 +216,10 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
|
||||
} else if (Device->Depth == 4) {
|
||||
uint8_t *optr = Device->Framebuffer;
|
||||
int LineLen = Device->Width >> 1;
|
||||
|
||||
Height >>= 3;
|
||||
Color &= 0x0f;
|
||||
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
// we need to linearize code to let compiler better optimize
|
||||
@@ -238,37 +245,107 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
|
||||
}
|
||||
} else if (Device->Depth == 8) {
|
||||
uint8_t *optr = Device->Framebuffer;
|
||||
int LineLen = Device->Width;
|
||||
|
||||
Height >>= 3;
|
||||
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
|
||||
// we need to linearize code to let compiler better optimize
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen;
|
||||
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + c; }
|
||||
}
|
||||
} else if (Device->Depth == 16) {
|
||||
uint16_t *optr = (uint16_t*) Device->Framebuffer;
|
||||
int LineLen = Device->Width;
|
||||
|
||||
Height >>= 3;
|
||||
Color = __builtin_bswap16(Color);
|
||||
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
|
||||
// we need to linearize code to let compiler better optimize
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
*optr = ((Byte & 0x01) * Color); optr += LineLen;
|
||||
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = (uint16_t*) Device->Framebuffer + c; }
|
||||
}
|
||||
} else if (Device->Depth == 24) {
|
||||
uint8_t *optr = Device->Framebuffer;
|
||||
int LineLen = Device->Width * 3;
|
||||
|
||||
Height >>= 3;
|
||||
if (Device->Mode == GDS_RGB666) Color = ((Color << 4) & 0xff0000) | ((Color << 2) & 0xff00) | (Color & 0x00ff);
|
||||
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
|
||||
// we need to linearize code to let compiler better optimize
|
||||
#define SET24(O,D) O[0]=(D)>>16; O[1]=(D)>>8; O[2]=(D);
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1;
|
||||
SET24(optr,(Byte & 0x01) * Color); optr += LineLen;
|
||||
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + c * 3; }
|
||||
}
|
||||
} else {
|
||||
Height >>= 3;
|
||||
|
||||
// don't know bitdepth, use brute-force solution
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = *Data++;
|
||||
GDS_DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
GDS_DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
GDS_DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
GDS_DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
GDS_DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
GDS_DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
GDS_DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
GDS_DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
|
||||
DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
|
||||
if (++r == Height) { c++; r = 0; }
|
||||
}
|
||||
/* for better understanding, here is the mundane version
|
||||
for (int x = 0; x < Width; x++) {
|
||||
for (int y = 0; y < Height; y++) {
|
||||
uint8_t Byte = *Data++;
|
||||
GDS_DrawPixel4Fast( Device, x, y * 8 + 0, ((Byte >> 7) & 0x01) * Color );
|
||||
GDS_DrawPixel4Fast( Device, x, y * 8 + 1, ((Byte >> 6) & 0x01) * Color );
|
||||
GDS_DrawPixel4Fast( Device, x, y * 8 + 2, ((Byte >> 5) & 0x01) * Color );
|
||||
GDS_DrawPixel4Fast( Device, x, y * 8 + 3, ((Byte >> 4) & 0x01) * Color );
|
||||
GDS_DrawPixel4Fast( Device, x, y * 8 + 4, ((Byte >> 3) & 0x01) * Color );
|
||||
GDS_DrawPixel4Fast( Device, x, y * 8 + 5, ((Byte >> 2) & 0x01) * Color );
|
||||
GDS_DrawPixel4Fast( Device, x, y * 8 + 6, ((Byte >> 1) & 0x01) * Color );
|
||||
GDS_DrawPixel4Fast( Device, x, y * 8 + 7, ((Byte >> 0) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 0, ((Byte >> 7) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 1, ((Byte >> 6) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 2, ((Byte >> 5) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 3, ((Byte >> 4) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 4, ((Byte >> 3) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 5, ((Byte >> 2) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 6, ((Byte >> 1) & 0x01) * Color );
|
||||
GDSDrawPixel4Fast( Device, x, y * 8 + 7, ((Byte >> 0) & 0x01) * Color );
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
Device->Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,12 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef _GDS_PRIVATE_H_
|
||||
void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color );
|
||||
void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color );
|
||||
#endif
|
||||
void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Color );
|
||||
void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color );
|
||||
void GDS_DrawLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color );
|
||||
void GDS_DrawBox( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color, bool Fill );
|
||||
|
||||
// draw a bitmap with source 1-bit depth organized in column and col0 = bit7 of byte 0
|
||||
void GDS_DrawBitmapCBR( struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -89,7 +89,7 @@ void GDS_FontDrawChar( struct GDS_Device* Device, char Character, int x, int y,
|
||||
YBit = ( i + OffsetY ) & 0x07;
|
||||
|
||||
if ( GlyphData[ YByte ] & BIT( YBit ) ) {
|
||||
GDS_DrawPixel( Device, x, y, Color );
|
||||
DrawPixel( Device, x, y, Color );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,10 +28,11 @@ typedef struct {
|
||||
const unsigned char *InData; // Pointer to jpeg data
|
||||
int InPos; // Current position in jpeg data
|
||||
int Width, Height;
|
||||
uint8_t Mode;
|
||||
union {
|
||||
uint16_t *OutData; // Decompress
|
||||
void *OutData;
|
||||
struct { // DirectDraw
|
||||
struct GDS_Device * Device;
|
||||
struct GDS_Device *Device;
|
||||
int XOfs, YOfs;
|
||||
int XMin, YMin;
|
||||
int Depth;
|
||||
@@ -35,6 +40,40 @@ typedef struct {
|
||||
};
|
||||
} JpegCtx;
|
||||
|
||||
/****************************************************************************************
|
||||
* RGB conversion (24 bits 888: RRRRRRRRGGGGGGGGBBBBBBBB and 16 bits 565: RRRRRGGGGGGBBBBB = B31..B0)
|
||||
* so in other words for an array of 888 bytes: [0]=B, [1]=G, [2]=R, ...
|
||||
* monochrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b)
|
||||
* grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) )
|
||||
*/
|
||||
static inline int Scaler332(uint8_t *Pixels) {
|
||||
return (Pixels[2] & ~0x1f) | ((Pixels[1] & ~0x1f) >> 3) | (Pixels[0] >> 6);
|
||||
}
|
||||
|
||||
static inline int Scaler444(uint8_t *Pixels) {
|
||||
return ((Pixels[2] & ~0x0f) << 4) | (Pixels[1] & ~0x0f) | (Pixels[0] >> 4);
|
||||
}
|
||||
|
||||
static inline int Scaler555(uint8_t *Pixels) {
|
||||
return ((Pixels[2] & ~0x07) << 7) | ((Pixels[1] & ~0x07) << 2) | (Pixels[0] >> 3);
|
||||
}
|
||||
|
||||
static inline int Scaler565(uint8_t *Pixels) {
|
||||
return ((Pixels[2] & ~0x07) << 8) | ((Pixels[1] & ~0x03) << 3) | (Pixels[0] >> 3);
|
||||
}
|
||||
|
||||
static inline int Scaler666(uint8_t *Pixels) {
|
||||
return ((Pixels[2] & ~0x03) << 10) | ((Pixels[1] & ~0x03) << 4) | (Pixels[0] >> 2);
|
||||
}
|
||||
|
||||
static inline int Scaler888(uint8_t *Pixels) {
|
||||
return (Pixels[2] << 16) | (Pixels[1] << 8) | Pixels[0];
|
||||
}
|
||||
|
||||
static inline int ScalerGray(uint8_t *Pixels) {
|
||||
return (Pixels[2] * 14 + Pixels[1] * 76 + Pixels[0] * 38) >> 7;
|
||||
}
|
||||
|
||||
static unsigned InHandler(JDEC *Decoder, uint8_t *Buf, unsigned Len) {
|
||||
JpegCtx *Context = (JpegCtx*) Decoder->device;
|
||||
if (Buf) memcpy(Buf, Context->InData + Context->InPos, Len);
|
||||
@@ -42,46 +81,97 @@ static unsigned InHandler(JDEC *Decoder, uint8_t *Buf, unsigned Len) {
|
||||
return Len;
|
||||
}
|
||||
|
||||
#define OUTHANDLER(F) \
|
||||
for (int y = Frame->top; y <= Frame->bottom; y++) { \
|
||||
for (int x = Frame->left; x <= Frame->right; x++) { \
|
||||
OutData[Context->Width * y + x] = F(Pixels); \
|
||||
Pixels += 3; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define OUTHANDLER24(F) \
|
||||
for (int y = Frame->top; y <= Frame->bottom; y++) { \
|
||||
uint8_t *p = OutData + (Context->Width * y + Frame->left) * 3; \
|
||||
for (int c = Frame->right - Frame->left; c-- >= 0;) { \
|
||||
uint32_t v = F(Pixels); \
|
||||
*p++ = v; *p++ = v >> 8; *p++ = v >> 16; \
|
||||
Pixels += 3; \
|
||||
} \
|
||||
}
|
||||
|
||||
static unsigned OutHandler(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
|
||||
JpegCtx *Context = (JpegCtx*) Decoder->device;
|
||||
uint8_t *Pixels = (uint8_t*) Bitmap;
|
||||
|
||||
for (int y = Frame->top; y <= Frame->bottom; y++) {
|
||||
for (int x = Frame->left; x <= Frame->right; x++) {
|
||||
// Convert the 888 to RGB565
|
||||
uint16_t Value = (*Pixels++ & ~0x07) << 8;
|
||||
Value |= (*Pixels++ & ~0x03) << 3;
|
||||
Value |= *Pixels++ >> 3;
|
||||
Context->OutData[Context->Width * y + x] = Value;
|
||||
}
|
||||
}
|
||||
|
||||
// decoded image is RGB888
|
||||
if (Context->Mode == GDS_RGB888) {
|
||||
uint8_t *OutData = (uint8_t*) Context->OutData;
|
||||
OUTHANDLER24(Scaler888);
|
||||
} else if (Context->Mode == GDS_RGB666) {
|
||||
uint8_t *OutData = (uint8_t*) Context->OutData;
|
||||
OUTHANDLER24(Scaler666);
|
||||
} else if (Context->Mode == GDS_RGB565) {
|
||||
uint16_t *OutData = (uint16_t*) Context->OutData;
|
||||
OUTHANDLER(Scaler565);
|
||||
} else if (Context->Mode == GDS_RGB555) {
|
||||
uint16_t *OutData = (uint16_t*) Context->OutData;
|
||||
OUTHANDLER(Scaler555);
|
||||
} else if (Context->Mode == GDS_RGB444) {
|
||||
uint16_t *OutData = (uint16_t*) Context->OutData;
|
||||
OUTHANDLER(Scaler444);
|
||||
} else if (Context->Mode == GDS_RGB332) {
|
||||
uint8_t *OutData = (uint8_t*) Context->OutData;
|
||||
OUTHANDLER(Scaler332);
|
||||
} else if (Context->Mode <= GDS_GRAYSCALE) {
|
||||
uint8_t *OutData = (uint8_t*) Context->OutData;
|
||||
OUTHANDLER(ScalerGray);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Convert the RGB888 to destination color plane, use DrawPixel and not "fast"
|
||||
// version as X,Y may be beyond screen
|
||||
#define OUTHANDLERDIRECT(F,S) \
|
||||
for (int y = Frame->top; y <= Frame->bottom; y++) { \
|
||||
if (y < Context->YMin) continue; \
|
||||
for (int x = Frame->left; x <= Frame->right; x++) { \
|
||||
if (x < Context->XMin) continue; \
|
||||
DrawPixel( Context->Device, x + Context->XOfs, y + Context->YOfs, F(Pixels) >> S); \
|
||||
Pixels += 3; \
|
||||
} \
|
||||
}
|
||||
|
||||
static unsigned OutHandlerDirect(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
|
||||
JpegCtx *Context = (JpegCtx*) Decoder->device;
|
||||
uint8_t *Pixels = (uint8_t*) Bitmap;
|
||||
int Shift = 8 - Context->Depth;
|
||||
|
||||
for (int y = Frame->top; y <= Frame->bottom; y++) {
|
||||
if (y < Context->YMin) continue;
|
||||
for (int x = Frame->left; x <= Frame->right; x++) {
|
||||
if (x < Context->XMin) continue;
|
||||
// Convert the 888 to RGB565
|
||||
int Value = ((Pixels[0]*11 + Pixels[1]*59 + Pixels[2]*30) / 100) >> Shift;
|
||||
Pixels += 3;
|
||||
// used DrawPixel and not "fast" version as X,Y may be beyond screen
|
||||
GDS_DrawPixel( Context->Device, x + Context->XOfs, y + Context->YOfs, Value);
|
||||
}
|
||||
}
|
||||
// decoded image is RGB888, shift only make sense for grayscale
|
||||
if (Context->Mode == GDS_RGB888) {
|
||||
OUTHANDLERDIRECT(Scaler888, 0);
|
||||
} else if (Context->Mode == GDS_RGB666) {
|
||||
OUTHANDLERDIRECT(Scaler666, 0);
|
||||
} else if (Context->Mode == GDS_RGB565) {
|
||||
OUTHANDLERDIRECT(Scaler565, 0);
|
||||
} else if (Context->Mode == GDS_RGB555) {
|
||||
OUTHANDLERDIRECT(Scaler555, 0);
|
||||
} else if (Context->Mode == GDS_RGB444) {
|
||||
OUTHANDLERDIRECT(Scaler444, 0);
|
||||
} else if (Context->Mode == GDS_RGB332) {
|
||||
OUTHANDLERDIRECT(Scaler332, 0);
|
||||
} else if (Context->Mode <= GDS_GRAYSCALE) {
|
||||
OUTHANDLERDIRECT(ScalerGray, Shift);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
//Decode the embedded image into pixel lines that can be used with the rest of the logic.
|
||||
static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, bool SizeOnly) {
|
||||
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");
|
||||
@@ -99,7 +189,9 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal
|
||||
Decoder.scale = Scale;
|
||||
|
||||
if (Res == JDR_OK && !SizeOnly) {
|
||||
Context.OutData = malloc(Decoder.width * Decoder.height * sizeof(uint16_t));
|
||||
if (RGB_Mode <= GDS_RGB332) Context.OutData = malloc(Decoder.width * Decoder.height);
|
||||
else if (RGB_Mode < GDS_RGB666) Context.OutData = malloc(Decoder.width * Decoder.height * 2);
|
||||
else if (RGB_Mode <= GDS_RGB888) Context.OutData = malloc(Decoder.width * Decoder.height * 3);
|
||||
|
||||
// find the scaling factor
|
||||
uint8_t N = 0, ScaleInt = ceil(1.0 / Scale);
|
||||
@@ -114,6 +206,7 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal
|
||||
if (Context.OutData) {
|
||||
Context.Width = Decoder.width / (1 << N);
|
||||
Context.Height = Decoder.height / (1 << N);
|
||||
Context.Mode = RGB_Mode;
|
||||
if (Width) *Width = Context.Width;
|
||||
if (Height) *Height = Context.Height;
|
||||
Res = jd_decomp(&Decoder, OutHandler, N);
|
||||
@@ -121,154 +214,169 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal
|
||||
ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", Res);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Can't allocate bitmap %dx%d", Decoder.width, Decoder.height);
|
||||
ESP_LOGE(TAG, "Can't allocate bitmap %dx%d or invalid mode %d", Decoder.width, Decoder.height, RGB_Mode);
|
||||
}
|
||||
} else if (!SizeOnly) {
|
||||
ESP_LOGE(TAG, "Image decoder: jd_prepare failed (%d)", Res);
|
||||
}
|
||||
|
||||
|
||||
// free scratch area
|
||||
if (Scratch) free(Scratch);
|
||||
return Context.OutData;
|
||||
}
|
||||
|
||||
uint16_t* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale) {
|
||||
return DecodeJPEG(Source, Width, Height, Scale, false);
|
||||
void* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, int RGB_Mode) {
|
||||
return DecodeJPEG(Source, Width, Height, Scale, false, RGB_Mode);
|
||||
}
|
||||
|
||||
void GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height) {
|
||||
DecodeJPEG(Source, Width, Height, 1, true);
|
||||
DecodeJPEG(Source, Width, Height, 1, true, -1);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Simply draw a RGB 16bits image
|
||||
* RGB conversion (24 bits: RRRRRRRRGGGGGGGGBBBBBBBB and 16 bits 565: RRRRRGGGGGGBBBBB = B31..B0)
|
||||
* so in other words for an array of 888 bytes: [0]=B, [1]=G, [2]=R, ...
|
||||
* monochrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b)
|
||||
* grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) )
|
||||
*/
|
||||
void GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, int Width, int Height, int RGB_Mode ) {
|
||||
if (Device->DrawRGB16) {
|
||||
Device->DrawRGB16( Device, Image, x, y, Width, Height, RGB_Mode );
|
||||
} else {
|
||||
switch(RGB_Mode) {
|
||||
case GDS_RGB565:
|
||||
// 6 bits pixels to be placed. Use a linearized structure for a bit of optimization
|
||||
if (Device->Depth < 6) {
|
||||
int Scale = 6 - Device->Depth;
|
||||
for (int r = 0; r < Height; r++) {
|
||||
for (int c = 0; c < Width; c++) {
|
||||
int pixel = *Image++;
|
||||
pixel = ((((pixel & 0x1f) * 11) << 1) + ((pixel >> 5) & 0x3f) * 59 + (((pixel >> 11) * 30) << 1) + 1) / 100;
|
||||
GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int Scale = Device->Depth - 6;
|
||||
for (int r = 0; r < Height; r++) {
|
||||
for (int c = 0; c < Width; c++) {
|
||||
int pixel = *Image++;
|
||||
pixel = ((((pixel & 0x1f) * 11) << 1) + ((pixel >> 5) & 0x3f) * 59 + (((pixel >> 11) * 30) << 1) + 1) / 100;
|
||||
GDS_DrawPixel( Device, c + x, r + y, pixel << Scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GDS_RGB555:
|
||||
// 5 bits pixels to be placed Use a linearized structure for a bit of optimization
|
||||
if (Device->Depth < 5) {
|
||||
int Scale = 5 - Device->Depth;
|
||||
for (int r = 0; r < Height; r++) {
|
||||
for (int c = 0; c < Width; c++) {
|
||||
int pixel = *Image++;
|
||||
pixel = ((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f) * 59 + (pixel >> 10) * 30) / 100;
|
||||
GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int Scale = Device->Depth - 5;
|
||||
for (int r = 0; r < Height; r++) {
|
||||
for (int c = 0; c < Width; c++) {
|
||||
int pixel = *Image++;
|
||||
pixel = ((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f) * 59 + (pixel >> 10) * 30) / 100;
|
||||
GDS_DrawPixel( Device, c + x, r + y, pixel << Scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GDS_RGB444:
|
||||
// 4 bits pixels to be placed
|
||||
if (Device->Depth < 4) {
|
||||
int Scale = 4 - Device->Depth;
|
||||
for (int r = 0; r < Height; r++) {
|
||||
for (int c = 0; c < Width; c++) {
|
||||
int pixel = *Image++;
|
||||
pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f) * 59 + (pixel >> 8) * 30;
|
||||
GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int Scale = Device->Depth - 4;
|
||||
for (int r = 0; r < Height; r++) {
|
||||
for (int c = 0; c < Width; c++) {
|
||||
int pixel = *Image++;
|
||||
pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f) * 59 + (pixel >> 8) * 30;
|
||||
GDS_DrawPixel( Device, c + x, r + y, pixel << Scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
static inline int ToGray888(uint8_t **Pixel) {
|
||||
uint32_t v = *(*Pixel)++; v |= *(*Pixel)++ << 8; v |= *(*Pixel)++ << 16;
|
||||
return (((v & 0xff) * 14) + ((v >> 8) & 0xff) * 76 + ((v >> 16) * 38) + 1) >> 7;
|
||||
}
|
||||
|
||||
static inline int ToGray666(uint8_t **Pixel) {
|
||||
uint32_t v = *(*Pixel)++; v |= *(*Pixel)++ << 8; v |= *(*Pixel)++ << 16;
|
||||
return (((v & 0x3f) * 14) + ((v >> 6) & 0x3f) * 76 + ((v >> 12) * 38) + 1) >> 7;
|
||||
}
|
||||
|
||||
static inline int ToGray565(uint16_t **Pixel) {
|
||||
uint16_t v = *(*Pixel)++;
|
||||
return ((((v & 0x1f) * 14) << 1) + ((v >> 5) & 0x3f) * 76 + (((v >> 11) * 38) << 1) + 1) >> 7;
|
||||
}
|
||||
|
||||
static inline int ToGray555(uint16_t **Pixel) {
|
||||
uint16_t v = *(*Pixel)++;
|
||||
return ((v & 0x1f) * 14 + ((v >> 5) & 0x1f) * 76 + (v >> 10) * 38) >> 7;
|
||||
}
|
||||
|
||||
static inline int ToGray444(uint16_t **Pixel) {
|
||||
uint16_t v = *(*Pixel)++;
|
||||
return ((v & 0x0f) * 14 + ((v >> 4) & 0x0f) * 76 + (v >> 8) * 38) >> 7;
|
||||
}
|
||||
|
||||
static inline int ToGray332(uint8_t **Pixel) {
|
||||
uint8_t v = *(*Pixel)++;
|
||||
return ((((v & 0x3) * 14) << 1) + ((v >> 2) & 0x7) * 76 + (v >> 5) * 38 + 1) >> 7;
|
||||
}
|
||||
|
||||
static inline int ToSelf(uint8_t **Pixel) {
|
||||
return *(*Pixel)++;
|
||||
}
|
||||
|
||||
#define DRAW_GRAYRGB(S,F) \
|
||||
if (Scale > 0) { \
|
||||
for (int r = 0; r < Height; r++) { \
|
||||
for (int c = 0; c < Width; c++) { \
|
||||
DrawPixel( Device, c + x, r + y, F(S) >> Scale); \
|
||||
} \
|
||||
} \
|
||||
} else { \
|
||||
for (int r = 0; r < Height; r++) { \
|
||||
for (int c = 0; c < Width; c++) { \
|
||||
DrawPixel( Device, c + x, r + y, F(S) << -Scale); \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define DRAW_RGB(T) \
|
||||
T *S = (T*) Image; \
|
||||
for (int r = 0; r < Height; r++) { \
|
||||
for (int c = 0; c < Width; c++) { \
|
||||
DrawPixel(Device, c + x, r + y, *S++); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define DRAW_RGB24 \
|
||||
uint8_t *S = (uint8_t*) Image; \
|
||||
for (int r = 0; r < Height; r++) { \
|
||||
for (int c = 0; c < Width; c++) { \
|
||||
uint32_t v = *S++; v |= *S++ << 8; v |= *S++ << 16; \
|
||||
DrawPixel(Device, c + x, r + y, v); \
|
||||
} \
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Decode the embedded image into pixel lines that can be used with the rest of the logic.
|
||||
*/
|
||||
void GDS_DrawRGB( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode ) {
|
||||
|
||||
// don't do anything if driver supplies a draw function
|
||||
if (Device->DrawRGB) {
|
||||
Device->DrawRGB( Device, Image, x, y, Width, Height, RGB_Mode );
|
||||
Device->Dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// RGB type displays
|
||||
if (Device->Mode > GDS_GRAYSCALE) {
|
||||
// image must match the display mode!
|
||||
if (Device->Mode != RGB_Mode) {
|
||||
ESP_LOGE(TAG, "non-matching display & image mode %u %u", Device->Mode, RGB_Mode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (RGB_Mode == GDS_RGB332) {
|
||||
DRAW_RGB(uint8_t);
|
||||
} else if (RGB_Mode < GDS_RGB666) {
|
||||
DRAW_RGB(uint16_t);
|
||||
} else {
|
||||
DRAW_RGB24;
|
||||
}
|
||||
|
||||
Device->Dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// set the right scaler when displaying grayscale
|
||||
if (RGB_Mode <= GDS_GRAYSCALE) {
|
||||
int Scale = 8 - Device->Depth;
|
||||
DRAW_GRAYRGB(&Image,ToSelf);
|
||||
} else if (RGB_Mode == GDS_RGB332) {
|
||||
int Scale = 3 - Device->Depth;
|
||||
DRAW_GRAYRGB(&Image,ToGray332);
|
||||
} else if (RGB_Mode < GDS_RGB666) {
|
||||
if (RGB_Mode == GDS_RGB565) {
|
||||
int Scale = 6 - Device->Depth;
|
||||
DRAW_GRAYRGB((uint16_t**)&Image,ToGray565);
|
||||
} else if (RGB_Mode == GDS_RGB555) {
|
||||
int Scale = 5 - Device->Depth;
|
||||
DRAW_GRAYRGB((uint16_t**)&Image,ToGray555);
|
||||
} else if (RGB_Mode == GDS_RGB444) {
|
||||
int Scale = 4 - Device->Depth;
|
||||
DRAW_GRAYRGB((uint16_t**)&Image,ToGray444)
|
||||
}
|
||||
} else {
|
||||
if (RGB_Mode == GDS_RGB666) {
|
||||
int Scale = 6 - Device->Depth;
|
||||
DRAW_GRAYRGB(&Image,ToGray666);
|
||||
} else if (RGB_Mode == GDS_RGB888) {
|
||||
int Scale = 8 - Device->Depth;
|
||||
DRAW_GRAYRGB(&Image,ToGray888);
|
||||
}
|
||||
}
|
||||
|
||||
Device->Dirty = true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Simply draw a RGB 8 bits image (R:3,G:3,B:2) or plain grayscale
|
||||
* monochrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b)
|
||||
* grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) )
|
||||
* Decode the embedded image into pixel lines that can be used with the rest of the logic.
|
||||
*/
|
||||
void GDS_DrawRGB8( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode ) {
|
||||
if (Device->DrawRGB8) {
|
||||
Device->DrawRGB8( Device, Image, x, y, Width, Height, RGB_Mode );
|
||||
} else if (RGB_Mode == GDS_GRAYSCALE) {
|
||||
// 8 bits pixels
|
||||
int Scale = 8 - Device->Depth;
|
||||
for (int r = 0; r < Height; r++) {
|
||||
for (int c = 0; c < Width; c++) {
|
||||
GDS_DrawPixel( Device, c + x, r + y, *Image++ >> Scale);
|
||||
}
|
||||
}
|
||||
} else if (Device->Depth < 3) {
|
||||
// 3 bits pixels to be placed
|
||||
int Scale = 3 - Device->Depth;
|
||||
for (int r = 0; r < Height; r++) {
|
||||
for (int c = 0; c < Width; c++) {
|
||||
int pixel = *Image++;
|
||||
pixel = ((((pixel & 0x3) * 11) << 1) + ((pixel >> 2) & 0x7) * 59 + (pixel >> 5) * 30 + 1) / 100;
|
||||
GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 3 bits pixels to be placed
|
||||
int Scale = Device->Depth - 3;
|
||||
for (int r = 0; r < Height; r++) {
|
||||
for (int c = 0; c < Width; c++) {
|
||||
int pixel = *Image++;
|
||||
pixel = ((((pixel & 0x3) * 11) << 1) + ((pixel >> 2) & 0x7) * 59 + (pixel >> 5) * 30 + 1) / 100;
|
||||
GDS_DrawPixel( Device, c + x, r + y, pixel << Scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Device->Dirty = true;
|
||||
}
|
||||
|
||||
//Decode the embedded image into pixel lines that can be used with the rest of the logic.
|
||||
bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit) {
|
||||
bool GDS_DrawJPEG(struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit) {
|
||||
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");
|
||||
@@ -313,6 +421,7 @@ bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int
|
||||
|
||||
Context.XMin = x - Context.XOfs;
|
||||
Context.YMin = y - Context.YOfs;
|
||||
Context.Mode = Device->Mode;
|
||||
|
||||
// do decompress & draw
|
||||
Res = jd_decomp(&Decoder, OutHandlerDirect, N);
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
|
||||
struct GDS_Device;
|
||||
|
||||
enum { GDS_RGB565, GDS_RGB555, GDS_RGB444, GDS_RGB332, GDS_GRAYSCALE };
|
||||
|
||||
// Fit options for GDS_DrawJPEG
|
||||
#define GDS_IMAGE_LEFT 0x00
|
||||
#define GDS_IMAGE_CENTER_X 0x01
|
||||
@@ -28,8 +26,7 @@ enum { GDS_RGB565, GDS_RGB555, GDS_RGB444, GDS_RGB332, GDS_GRAYSCALE };
|
||||
#define GDS_IMAGE_FIT 0x10 // re-scale by a factor of 2^N (up to 3)
|
||||
|
||||
// Width and Height can be NULL if you already know them (actual scaling is closest ^2)
|
||||
uint16_t* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale);
|
||||
void* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, int RGB_Mode); // can be 8, 16 or 24 bits per pixel in return
|
||||
void GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height);
|
||||
bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit);
|
||||
void GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, int Width, int Height, int RGB_Mode );
|
||||
void GDS_DrawRGB8( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode );
|
||||
bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit);
|
||||
void GDS_DrawRGB( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode );
|
||||
|
||||
@@ -72,6 +72,11 @@ typedef struct spi_device_t* spi_device_handle_t;
|
||||
|
||||
struct GDS_Device {
|
||||
uint8_t IF;
|
||||
int8_t RSTPin;
|
||||
struct {
|
||||
int8_t Pin, Channel;
|
||||
int PWM;
|
||||
} Backlight;
|
||||
union {
|
||||
// I2C Specific
|
||||
struct {
|
||||
@@ -80,24 +85,23 @@ struct GDS_Device {
|
||||
// SPI specific
|
||||
struct {
|
||||
spi_device_handle_t SPIHandle;
|
||||
int8_t RSTPin;
|
||||
int8_t CSPin;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// cooked text mode
|
||||
struct {
|
||||
int16_t Y, Space;
|
||||
const struct GDS_FontDef* Font;
|
||||
} Lines[MAX_LINES];
|
||||
|
||||
uint16_t Width;
|
||||
uint16_t Width, TextWidth;
|
||||
uint16_t Height;
|
||||
uint8_t Depth;
|
||||
uint8_t Depth, Mode;
|
||||
|
||||
uint8_t Alloc;
|
||||
uint8_t* Framebuffer;
|
||||
uint16_t FramebufferSize;
|
||||
uint32_t FramebufferSize;
|
||||
bool Dirty;
|
||||
|
||||
// default fonts when using direct draw
|
||||
@@ -113,28 +117,28 @@ 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 (*SetHFlip)( struct GDS_Device* Device, bool On );
|
||||
void (*SetVFlip)( struct GDS_Device* Device, bool On );
|
||||
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 (*DrawRGB16)( struct GDS_Device* Device, uint16_t *Image,int x, int y, int Width, int Height, int RGB_Mode );
|
||||
void (*DrawRGB8)( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode );
|
||||
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;
|
||||
WriteDataProc WriteData;
|
||||
|
||||
// 16 bytes for whatever the driver wants (should be aligned as it's 32 bits)
|
||||
uint32_t Private[4];
|
||||
// 32 bytes for whatever the driver wants (should be aligned as it's 32 bits)
|
||||
uint32_t Private[8];
|
||||
};
|
||||
|
||||
bool GDS_Reset( struct GDS_Device* Device );
|
||||
bool GDS_Init( struct GDS_Device* Device );
|
||||
|
||||
inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
|
||||
static inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
|
||||
bool Result = (
|
||||
( x >= 0 ) &&
|
||||
( x < Device->Width ) &&
|
||||
@@ -151,9 +155,9 @@ inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
|
||||
return Result;
|
||||
}
|
||||
|
||||
inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
static inline void DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint32_t YBit = ( Y & 0x07 );
|
||||
uint8_t* FBOffset = NULL;
|
||||
uint8_t* FBOffset;
|
||||
|
||||
/*
|
||||
* We only need to modify the Y coordinate since the pitch
|
||||
@@ -172,22 +176,48 @@ inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int X, int
|
||||
}
|
||||
}
|
||||
|
||||
inline void IRAM_ATTR GDS_DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset;
|
||||
|
||||
FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
|
||||
static inline void DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
|
||||
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
|
||||
}
|
||||
|
||||
inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
static inline void DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
Device->Framebuffer[Y * Device->Width + X] = Color;
|
||||
}
|
||||
|
||||
// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R
|
||||
static inline void DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
|
||||
*FBOffset = __builtin_bswap16(Color);
|
||||
}
|
||||
|
||||
// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B
|
||||
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB
|
||||
static inline void DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
|
||||
*FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f;
|
||||
}
|
||||
|
||||
// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B
|
||||
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB
|
||||
static inline void DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
|
||||
*FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color;
|
||||
}
|
||||
|
||||
static inline void IRAM_ATTR DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
if (Device->DrawPixelFast) Device->DrawPixelFast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 4) GDS_DrawPixel4Fast( Device, X, Y, Color);
|
||||
else if (Device->Depth == 1) GDS_DrawPixel1Fast( Device, X, Y, Color);
|
||||
else if (Device->Depth == 4) DrawPixel4Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 1) DrawPixel1Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 16) DrawPixel16Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) DrawPixel18Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) DrawPixel24Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 8) DrawPixel8Fast( Device, X, Y, Color );
|
||||
}
|
||||
|
||||
inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
|
||||
static inline void IRAM_ATTR DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
|
||||
if ( IsPixelVisible( Device, x, y ) == true ) {
|
||||
GDS_DrawPixelFast( Device, x, y, Color );
|
||||
DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,21 +26,30 @@ 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;
|
||||
#else
|
||||
case GDS_FONT_LARGE:
|
||||
case GDS_FONT_SEGMENT:
|
||||
ESP_LOGW(TAG, "large fonts disabled");
|
||||
//return &Font_droid_sans_fallback_15x17;
|
||||
return &Font_droid_sans_fallback_11x13;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,15 +100,15 @@ 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++)
|
||||
GDS_DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
|
||||
DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
|
||||
}
|
||||
|
||||
GDS_FontDrawString( Device, X, Device->Lines[N].Y, Text, GDS_COLOR_WHITE );
|
||||
@@ -110,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;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -124,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);
|
||||
@@ -135,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';
|
||||
}
|
||||
|
||||
@@ -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, ...);
|
||||
@@ -35,7 +35,7 @@ static bool I2CDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data,
|
||||
bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int Speed ) {
|
||||
I2CPortNumber = PortNumber;
|
||||
|
||||
I2CWait = pdMS_TO_TICKS( Speed ? Speed / 4000 : 100 );
|
||||
I2CWait = pdMS_TO_TICKS( Speed ? (250 * 250000) / Speed : 250 );
|
||||
|
||||
if (SDA != -1 && SCL != -1) {
|
||||
i2c_config_t Config = { 0 };
|
||||
@@ -66,15 +66,16 @@ bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int Speed ) {
|
||||
*
|
||||
* Returns true on successful init of display.
|
||||
*/
|
||||
bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin ) {
|
||||
bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin, int BacklightPin ) {
|
||||
NullCheck( Device, return false );
|
||||
|
||||
Device->WriteCommand = I2CDefaultWriteCommand;
|
||||
Device->WriteData = I2CDefaultWriteData;
|
||||
Device->Address = I2CAddress;
|
||||
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 ) {
|
||||
|
||||
@@ -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 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,14 +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;
|
||||
@@ -58,8 +60,9 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
|
||||
Device->SPIHandle = SPIDevice;
|
||||
Device->RSTPin = RSTPin;
|
||||
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 ) {
|
||||
@@ -72,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 );
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <arpa/inet.h>
|
||||
#include "esp_log.h"
|
||||
#include "globdefs.h"
|
||||
#include "config.h"
|
||||
#include "platform_config.h"
|
||||
#include "tools.h"
|
||||
#include "display.h"
|
||||
#include "gds.h"
|
||||
@@ -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,63 +45,89 @@ 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;
|
||||
|
||||
static const char *known_drivers[] = {"SH1106",
|
||||
"SSD1306",
|
||||
"SSD1322",
|
||||
"SSD1326",
|
||||
"SSD1327",
|
||||
"SSD1675",
|
||||
"SSD1351",
|
||||
"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;
|
||||
GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_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 };
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void display_init(char *welcome) {
|
||||
bool init = false;
|
||||
char *config = config_alloc_get(NVS_TYPE_STR, "display_config");
|
||||
|
||||
if (!config) {
|
||||
ESP_LOGI(TAG, "no display");
|
||||
return false;
|
||||
}
|
||||
char *config = config_alloc_get_str("display_config", CONFIG_DISPLAY_CONFIG, "N/A");
|
||||
|
||||
int width = -1, height = -1;
|
||||
char *p, *drivername = strstr(config, "driver");
|
||||
int width = -1, height = -1, backlight_pin = -1;
|
||||
char *drivername = strstr(config, "driver");
|
||||
|
||||
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);
|
||||
display = GDS_AutoDetect(drivername ? drivername : "SSD1306", drivers);
|
||||
|
||||
if ((p = strcasestr(config, "width")) != NULL) width = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "height")) != NULL) height = atoi(strchr(p, '=') + 1);
|
||||
|
||||
if (backlight_pin >= 0) {
|
||||
struct GDS_BacklightPWM PWMConfig = { .Channel = pwm_system.base_channel++, .Timer = pwm_system.timer, .Max = pwm_system.max, .Init = false };
|
||||
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 );
|
||||
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, 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 {
|
||||
@@ -112,24 +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_SetHFlip(display, strcasestr(config, "HFlip") ? true : false);
|
||||
GDS_SetVFlip(display, strcasestr(config, "VFlip") ? true : false);
|
||||
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);
|
||||
@@ -181,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;
|
||||
@@ -205,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;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
@@ -267,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);
|
||||
@@ -306,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);
|
||||
}
|
||||
@@ -327,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;
|
||||
@@ -365,4 +480,47 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
|
||||
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
bool display_is_valid_driver(const char * driver){
|
||||
return display_conf_get_driver_name(driver)!=NULL;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
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];
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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,8 +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(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);
|
||||
|
||||
@@ -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
BIN
components/display/note.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -1,6 +1,6 @@
|
||||
idf_component_register(SRCS "bt_app_core.c" "bt_app_sink.c" "bt_app_source.c"
|
||||
INCLUDE_DIRS "." "../tools/"
|
||||
REQUIRES esp_common
|
||||
PRIV_REQUIRES freertos bt io nvs_flash esp32 spi_flash newlib log console pthread
|
||||
idf_component_register( SRC_DIRS .
|
||||
INCLUDE_DIRS .
|
||||
PRIV_REQUIRES services bt display console tools platform_config
|
||||
|
||||
)
|
||||
|
||||
|
||||
@@ -7,25 +7,26 @@
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "bt_app_core.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)
|
||||
{
|
||||
@@ -41,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);
|
||||
@@ -77,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);
|
||||
@@ -87,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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
#include "esp_a2dp_api.h"
|
||||
#include "esp_avrc_api.h"
|
||||
#include "nvs.h"
|
||||
#include "config.h"
|
||||
#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_LOGI(BT_AV_TAG, "BT volume up %u", 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
|
||||
};
|
||||
|
||||
@@ -148,7 +145,7 @@ void bt_disconnect(void) {
|
||||
displayer_control(DISPLAYER_SHUTDOWN);
|
||||
if (s_audio == AUDIO_PLAYING) esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
actrls_unset();
|
||||
ESP_LOGI(BT_AV_TAG, "forced disconnection %d", s_audio);
|
||||
ESP_LOGD(BT_AV_TAG, "forced disconnection %d", s_audio);
|
||||
}
|
||||
|
||||
/* update metadata if any */
|
||||
@@ -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;
|
||||
@@ -277,12 +278,14 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
|
||||
case ESP_A2D_CONNECTION_STATE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(p_param);
|
||||
uint8_t *bda = a2d->conn_stat.remote_bda;
|
||||
ESP_LOGI(BT_AV_TAG, "A2DP connection state: %s, [%02x:%02x:%02x:%02x:%02x:%02x]",
|
||||
ESP_LOGD(BT_AV_TAG, "A2DP connection state: %s, [%02x:%02x:%02x:%02x:%02x:%02x]",
|
||||
s_a2d_conn_state_str[a2d->conn_stat.state], bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
|
||||
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
|
||||
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);
|
||||
}
|
||||
@@ -290,10 +293,13 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
|
||||
}
|
||||
case ESP_A2D_AUDIO_STATE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(p_param);
|
||||
ESP_LOGI(BT_AV_TAG, "A2DP audio state: %s", s_a2d_audio_state_str[a2d->audio_stat.state]);
|
||||
ESP_LOGD(BT_AV_TAG, "A2DP audio state: %s", s_a2d_audio_state_str[a2d->audio_stat.state]);
|
||||
|
||||
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)) {
|
||||
@@ -320,7 +326,7 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
|
||||
}
|
||||
case ESP_A2D_AUDIO_CFG_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(p_param);
|
||||
ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type %d", a2d->audio_cfg.mcc.type);
|
||||
ESP_LOGD(BT_AV_TAG, "A2DP audio stream configuration, codec type %d", a2d->audio_cfg.mcc.type);
|
||||
// for now only SBC stream is supported
|
||||
if (a2d->audio_cfg.mcc.type == ESP_A2D_MCT_SBC) {
|
||||
s_sample_rate = 16000;
|
||||
@@ -378,16 +384,16 @@ 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:
|
||||
ESP_LOGI(BT_AV_TAG, "Track changed");
|
||||
ESP_LOGD(BT_AV_TAG, "Track changed");
|
||||
bt_av_new_track();
|
||||
(*bt_app_a2d_cmd_cb)(BT_SINK_PROGRESS, 0, 0);
|
||||
break;
|
||||
case ESP_AVRC_RN_PLAY_STATUS_CHANGE:
|
||||
ESP_LOGI(BT_AV_TAG, "Playback status changed: 0x%x", event_parameter->playback);
|
||||
ESP_LOGD(BT_AV_TAG, "Playback status changed: 0x%x", event_parameter->playback);
|
||||
if (s_audio != AUDIO_IDLE) {
|
||||
switch (event_parameter->playback) {
|
||||
case ESP_AVRC_PLAYBACK_PLAYING:
|
||||
@@ -410,7 +416,7 @@ void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_param
|
||||
(*bt_app_a2d_cmd_cb)(BT_SINK_STOP);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(BT_AV_TAG, "Un-handled event");
|
||||
ESP_LOGW(BT_AV_TAG, "Un-handled event");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -433,7 +439,7 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
|
||||
switch (event) {
|
||||
case ESP_AVRC_CT_CONNECTION_STATE_EVT: {
|
||||
uint8_t *bda = rc->conn_stat.remote_bda;
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]",
|
||||
ESP_LOGD(BT_RC_CT_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]",
|
||||
rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
|
||||
|
||||
if (rc->conn_stat.connected) {
|
||||
@@ -446,11 +452,11 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state);
|
||||
ESP_LOGD(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state);
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_CT_METADATA_RSP_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text);
|
||||
ESP_LOGD(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text);
|
||||
|
||||
if (rc->meta_rsp.attr_id == ESP_AVRC_MD_ATTR_PLAYING_TIME) s_metadata.duration = atoi((char*) rc->meta_rsp.attr_text);
|
||||
else if (rc->meta_rsp.attr_id == ESP_AVRC_MD_ATTR_TITLE) strncpy(s_metadata.title, (char*) rc->meta_rsp.attr_text, METADATA_LEN);
|
||||
@@ -467,11 +473,11 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_CT_REMOTE_FEATURES_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %x, TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag);
|
||||
ESP_LOGD(BT_RC_CT_TAG, "AVRC remote features %x, TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag);
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count,
|
||||
ESP_LOGD(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count,
|
||||
rc->get_rn_caps_rsp.evt_set.bits);
|
||||
s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits;
|
||||
bt_av_new_track();
|
||||
@@ -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_LOGI(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_LOGI(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;
|
||||
@@ -516,21 +531,21 @@ static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param)
|
||||
switch (event) {
|
||||
case ESP_AVRC_TG_CONNECTION_STATE_EVT: {
|
||||
uint8_t *bda = rc->conn_stat.remote_bda;
|
||||
ESP_LOGI(BT_RC_TG_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]",
|
||||
ESP_LOGD(BT_RC_TG_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]",
|
||||
rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: {
|
||||
ESP_LOGI(BT_RC_TG_TAG, "AVRC passthrough cmd: key_code 0x%x, key_state %d", rc->psth_cmd.key_code, rc->psth_cmd.key_state);
|
||||
ESP_LOGD(BT_RC_TG_TAG, "AVRC passthrough cmd: key_code 0x%x, key_state %d", rc->psth_cmd.key_code, rc->psth_cmd.key_state);
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: {
|
||||
ESP_LOGI(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;
|
||||
}
|
||||
case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: {
|
||||
ESP_LOGI(BT_RC_TG_TAG, "AVRC register event notification: %d, param: 0x%x", rc->reg_ntf.event_id, rc->reg_ntf.event_parameter);
|
||||
ESP_LOGD(BT_RC_TG_TAG, "AVRC register event notification: %d, param: 0x%x", rc->reg_ntf.event_id, rc->reg_ntf.event_parameter);
|
||||
if (rc->reg_ntf.event_id == ESP_AVRC_RN_VOLUME_CHANGE) {
|
||||
s_volume_notify = true;
|
||||
esp_avrc_rn_param_t rn_param;
|
||||
@@ -540,7 +555,7 @@ static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param)
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_TG_REMOTE_FEATURES_EVT: {
|
||||
ESP_LOGI(BT_RC_TG_TAG, "AVRC remote features %x, CT features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.ct_feat_flag);
|
||||
ESP_LOGD(BT_RC_TG_TAG, "AVRC remote features %x, CT features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.ct_feat_flag);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -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_LOGI(BT_AV_TAG, "bt_app_task shutdown successfully");
|
||||
if (esp_bluedroid_disable() != ESP_OK) return;
|
||||
ESP_LOGI(BT_AV_TAG, "esp_bluedroid_disable called successfully");
|
||||
if (esp_bluedroid_deinit() != ESP_OK) return;
|
||||
ESP_LOGI(BT_AV_TAG, "esp_bluedroid_deinit called successfully");
|
||||
if (esp_bt_controller_disable() != ESP_OK) return;
|
||||
ESP_LOGI(BT_AV_TAG, "esp_bt_controller_disable called successfully");
|
||||
if (esp_bt_controller_deinit() != ESP_OK) return;
|
||||
ESP_LOGI(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)
|
||||
@@ -648,7 +619,7 @@ static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *pa
|
||||
switch (event) {
|
||||
case ESP_BT_GAP_AUTH_CMPL_EVT: {
|
||||
if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGI(BT_AV_TAG, "authentication success: %s", param->auth_cmpl.device_name);
|
||||
ESP_LOGD(BT_AV_TAG, "authentication success: %s", param->auth_cmpl.device_name);
|
||||
esp_log_buffer_hex(BT_AV_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN);
|
||||
} else {
|
||||
ESP_LOGE(BT_AV_TAG, "authentication failed, status:%d", param->auth_cmpl.stat);
|
||||
@@ -658,19 +629,19 @@ static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *pa
|
||||
|
||||
#if (CONFIG_BT_SSP_ENABLED == true)
|
||||
case ESP_BT_GAP_CFM_REQ_EVT:
|
||||
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val);
|
||||
ESP_LOGD(BT_AV_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val);
|
||||
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
|
||||
break;
|
||||
case ESP_BT_GAP_KEY_NOTIF_EVT:
|
||||
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey);
|
||||
ESP_LOGD(BT_AV_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey);
|
||||
break;
|
||||
case ESP_BT_GAP_KEY_REQ_EVT:
|
||||
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
|
||||
ESP_LOGD(BT_AV_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
|
||||
break;
|
||||
#endif
|
||||
|
||||
default: {
|
||||
ESP_LOGI(BT_AV_TAG, "event: %d", event);
|
||||
ESP_LOGD(BT_AV_TAG, "event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bt_app_core.h"
|
||||
typedef enum { BT_SINK_CONNECTED, BT_SINK_DISCONNECTED, BT_SINK_AUDIO_STARTED, BT_SINK_AUDIO_STOPPED, BT_SINK_PLAY, BT_SINK_STOP, BT_SINK_PAUSE,
|
||||
BT_SINK_RATE, BT_SINK_VOLUME, BT_SINK_METADATA, BT_SINK_PROGRESS } bt_sink_cmd_t;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1
components/esp-dsp
Submodule
1
components/esp-dsp
Submodule
Submodule components/esp-dsp added at 8b082c1071
10
components/esp_http_server/CMakeLists.txt
Normal file
10
components/esp_http_server/CMakeLists.txt
Normal 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
|
||||
)
|
||||
1
components/esp_http_server/Kconfig
Normal file
1
components/esp_http_server/Kconfig
Normal file
@@ -0,0 +1 @@
|
||||
source "$IDF_PATH/components/esp_http_server/Kconfig"
|
||||
68
components/esp_http_server/osal.h
Normal file
68
components/esp_http_server/osal.h
Normal 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_ */
|
||||
49
components/heap/CMakeLists.txt
Normal file
49
components/heap/CMakeLists.txt
Normal 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
74
components/heap/Kconfig
Normal 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
|
||||
32
components/heap/component.mk
Normal file
32
components/heap/component.mk
Normal 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
609
components/heap/heap_caps.c
Normal 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, ®istered_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, ®istered_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, ®istered_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, ®istered_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, ®istered_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, ®istered_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, ®istered_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, ®istered_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, ®istered_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, ®istered_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;
|
||||
}
|
||||
241
components/heap/heap_caps_init.c
Normal file
241
components/heap/heap_caps_init.c
Normal 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, ®istered_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 = ®ions[i - 1];
|
||||
soc_memory_region_t *b = ®ions[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 = ®ions[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(®istered_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(®istered_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, ®istered_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(®istered_heaps_write_lock);
|
||||
SLIST_INSERT_HEAD(®istered_heaps, p_new, next);
|
||||
MULTI_HEAP_UNLOCK(®istered_heaps_write_lock);
|
||||
|
||||
err = ESP_OK;
|
||||
|
||||
done:
|
||||
if (err != ESP_OK) {
|
||||
free(p_new);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
77
components/heap/heap_private.h
Normal file
77
components/heap/heap_private.h
Normal 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
|
||||
129
components/heap/heap_task_info.c
Normal file
129
components/heap/heap_task_info.c
Normal 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, ®istered_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
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
119
components/heap/heap_tlsf.h
Normal 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
|
||||
174
components/heap/heap_tlsf_block_functions.h
Normal file
174
components/heap/heap_tlsf_block_functions.h
Normal 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);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user